mirror of https://github.com/AMT-Cheif/drift.git
Add explicit aliases with new codegen
This commit is contained in:
parent
c5d696a9eb
commit
1de5479d60
|
@ -1710,15 +1710,15 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
|||
|
||||
Selectable<int> cfeTest() {
|
||||
return customSelect(
|
||||
'WITH RECURSIVE cnt(x)AS (SELECT 1 UNION ALL SELECT x + 1 FROM cnt LIMIT 1000000) SELECT x FROM cnt',
|
||||
'WITH RECURSIVE cnt(x) AS (SELECT 1 UNION ALL SELECT x + 1 FROM cnt LIMIT 1000000) SELECT x FROM cnt',
|
||||
variables: [],
|
||||
readsFrom: {}).map((QueryRow row) => row.read<int>('x'));
|
||||
}
|
||||
|
||||
Selectable<int?> nullableQuery() {
|
||||
return customSelect('SELECT MAX(oid) FROM config',
|
||||
return customSelect('SELECT MAX(oid) AS _c0 FROM config',
|
||||
variables: [],
|
||||
readsFrom: {config}).map((QueryRow row) => row.read<int?>('MAX(oid)'));
|
||||
readsFrom: {config}).map((QueryRow row) => row.read<int?>('_c0'));
|
||||
}
|
||||
|
||||
Future<List<Config>> addConfig(
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import 'package:sqlparser/sqlparser.dart';
|
||||
|
||||
class ExplicitAliasTransformer extends Transformer<bool> {
|
||||
int _aliasCounter = 0;
|
||||
final Map<Expression, String> _renamed = {};
|
||||
|
||||
AstNode rewrite(AstNode node) {
|
||||
node = transform(node, true)!;
|
||||
return _PatchReferences(this).transform(node, null)!;
|
||||
}
|
||||
|
||||
String? newNameFor(Column column) {
|
||||
while (column is CompoundSelectColumn) {
|
||||
// In compound select statement, the first column determines the overall
|
||||
// name
|
||||
column = column.columns.first;
|
||||
}
|
||||
|
||||
if (column is ExpressionColumn) {
|
||||
return _renamed[column.expression];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
AstNode? visitCommonTableExpression(CommonTableExpression e, bool arg) {
|
||||
// No need to add explicit column names when they're defined in the CTE
|
||||
// definition.
|
||||
e.as = transformChild(e.as, e, arg && e.columnNames == null);
|
||||
return e;
|
||||
}
|
||||
|
||||
@override
|
||||
AstNode? visitCompoundSelectStatement(CompoundSelectStatement e, bool arg) {
|
||||
// For compound select statements, the column names are only determined by
|
||||
// the base select statement. So, let's not transform the names in the other
|
||||
// select statements.
|
||||
e.withClause = transformNullableChild(e.withClause, e, arg);
|
||||
e.base = transformChild(e.base, e, arg);
|
||||
transformChildren(e.additional, e, false);
|
||||
return e;
|
||||
}
|
||||
|
||||
@override
|
||||
AstNode? visitExpressionResultColumn(ExpressionResultColumn e, bool arg) {
|
||||
final expr = e.expression;
|
||||
if (expr is! Reference && e.as == null && arg) {
|
||||
// Automatically add an alias to column names
|
||||
final name = '_c${_aliasCounter++}';
|
||||
_renamed[expr] = name;
|
||||
return super.visitExpressionResultColumn(
|
||||
ExpressionResultColumn(expression: expr, as: name),
|
||||
arg,
|
||||
);
|
||||
} else {
|
||||
return super.visitExpressionResultColumn(e, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _PatchReferences extends Transformer<void> {
|
||||
final ExplicitAliasTransformer _transformer;
|
||||
|
||||
_PatchReferences(this._transformer);
|
||||
|
||||
@override
|
||||
AstNode? visitReference(Reference e, void arg) {
|
||||
final resolved = e.resolvedColumn;
|
||||
if (resolved != null) {
|
||||
final name = _transformer.newNameFor(resolved);
|
||||
if (name != null) {
|
||||
return Reference(columnName: name, entityName: e.entityName);
|
||||
}
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
}
|
|
@ -134,7 +134,7 @@ class QueryHandler {
|
|||
}
|
||||
|
||||
columns.add(ResultColumn(column.name, moorType, type?.nullable ?? true,
|
||||
typeConverter: converter));
|
||||
typeConverter: converter, sqlParserColumn: column));
|
||||
|
||||
final table = _tableOfColumn(column);
|
||||
candidatesForSingleTable.removeWhere((t) => t != table);
|
||||
|
|
|
@ -352,7 +352,11 @@ class ResultColumn implements HasType {
|
|||
@override
|
||||
final UsedTypeConverter typeConverter;
|
||||
|
||||
ResultColumn(this.name, this.type, this.nullable, {this.typeConverter});
|
||||
/// The analyzed column from the `sqlparser` package.
|
||||
final Column sqlParserColumn;
|
||||
|
||||
ResultColumn(this.name, this.type, this.nullable,
|
||||
{this.typeConverter, this.sqlParserColumn});
|
||||
|
||||
/// Hash-code that matching [compatibleTo], so that two compatible columns
|
||||
/// will have the same [compatibilityHashCode].
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:math' show max;
|
|||
|
||||
import 'package:moor_generator/moor_generator.dart';
|
||||
import 'package:moor_generator/src/analyzer/options.dart';
|
||||
import 'package:moor_generator/src/analyzer/sql_queries/explicit_alias_transformer.dart';
|
||||
import 'package:moor_generator/src/model/sql_query.dart';
|
||||
import 'package:moor_generator/src/utils/string_escaper.dart';
|
||||
import 'package:moor_generator/writer.dart';
|
||||
|
@ -23,8 +24,9 @@ class QueryWriter {
|
|||
final SqlQuery query;
|
||||
final Scope scope;
|
||||
|
||||
SqlSelectQuery get _select => query as SqlSelectQuery;
|
||||
ExplicitAliasTransformer _transformer;
|
||||
|
||||
SqlSelectQuery get _select => query as SqlSelectQuery;
|
||||
UpdatingQuery get _update => query as UpdatingQuery;
|
||||
|
||||
MoorOptions get options => scope.writer.options;
|
||||
|
@ -46,6 +48,18 @@ class QueryWriter {
|
|||
ResultSetWriter(query, resultSetScope).write();
|
||||
}
|
||||
|
||||
// The new sql code generation generates query code from the parsed AST,
|
||||
// which eliminates unnecessary whitespace and comments. These can sometimes
|
||||
// have a semantic meaning though, for instance when they're used in
|
||||
// columns. So, we transform the query to add an explicit alias to every
|
||||
// column!
|
||||
// We do this transformation so late because it shouldn't have an impact on
|
||||
// analysis, Dart getter names stay the same.
|
||||
if (resultSet != null && options.newSqlCodeGeneration) {
|
||||
_transformer = ExplicitAliasTransformer();
|
||||
_transformer.rewrite(query.fromContext.root);
|
||||
}
|
||||
|
||||
if (query is SqlSelectQuery) {
|
||||
_writeSelect();
|
||||
} else if (query is UpdatingQuery) {
|
||||
|
@ -137,17 +151,22 @@ class QueryWriter {
|
|||
/// Returns Dart code that, given a variable of type `QueryRow` named `row`
|
||||
/// in the same scope, reads the [column] from that row and brings it into a
|
||||
/// suitable type.
|
||||
static String readingCode(ResultColumn column, GenerationOptions options) {
|
||||
String readingCode(ResultColumn column, GenerationOptions generationOptions) {
|
||||
var rawDartType = dartTypeNames[column.type];
|
||||
if (column.nullable && options.nnbd) {
|
||||
if (column.nullable && generationOptions.nnbd) {
|
||||
rawDartType = '$rawDartType?';
|
||||
}
|
||||
|
||||
final dartLiteral = asDartLiteral(column.name);
|
||||
String specialName;
|
||||
if (options.newSqlCodeGeneration) {
|
||||
specialName = _transformer.newNameFor(column.sqlParserColumn);
|
||||
}
|
||||
|
||||
final dartLiteral = asDartLiteral(specialName ?? column.name);
|
||||
var code = 'row.read<$rawDartType>($dartLiteral)';
|
||||
|
||||
if (column.typeConverter != null) {
|
||||
final needsAssert = !column.nullable && options.nnbd;
|
||||
final needsAssert = !column.nullable && generationOptions.nnbd;
|
||||
|
||||
final converter = column.typeConverter;
|
||||
code = '${_converter(converter)}.mapToDart($code)';
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import 'package:moor_generator/src/analyzer/sql_queries/explicit_alias_transformer.dart';
|
||||
import 'package:sqlparser/sqlparser.dart';
|
||||
import 'package:sqlparser/utils/node_to_text.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
final engine = SqlEngine(EngineOptions(useMoorExtensions: true));
|
||||
final result = engine.parse('CREATE TABLE a (id INTEGER);');
|
||||
engine.registerTable(const SchemaFromCreateTable()
|
||||
.read(result.rootNode as CreateTableStatement));
|
||||
|
||||
void _test(String input, String output) {
|
||||
final node = engine.analyze(input).root;
|
||||
final transformer = ExplicitAliasTransformer();
|
||||
transformer.rewrite(node);
|
||||
|
||||
expect(node.toSql(), output);
|
||||
}
|
||||
|
||||
test('rewrites simple queries', () {
|
||||
_test('SELECT 1 + 2', 'SELECT 1 + 2 AS _c0');
|
||||
});
|
||||
|
||||
test('does not rewrite simple references', () {
|
||||
_test('SELECT id FROM a', 'SELECT id FROM a');
|
||||
});
|
||||
|
||||
test('rewrites references', () {
|
||||
_test('SELECT "1+2" FROM (SELECT 1+2)',
|
||||
'SELECT _c0 FROM (SELECT 1 + 2 AS _c0)');
|
||||
});
|
||||
|
||||
test('rewrites compound select statements', () {
|
||||
_test("SELECT 1 + 2, 'foo' UNION ALL SELECT 3+ 4, 'bar'",
|
||||
"SELECT 1 + 2 AS _c0, 'foo' AS _c1 UNION ALL SELECT 3 + 4, 'bar'");
|
||||
});
|
||||
|
||||
test('rewrites references for compount select statements', () {
|
||||
_test(
|
||||
'''
|
||||
SELECT "1 + 2", "'foo'" FROM
|
||||
(SELECT 1 + 2, 'foo' UNION ALL SELECT 3+ 4, 'bar')
|
||||
''',
|
||||
'SELECT _c0, _c1 FROM '
|
||||
"(SELECT 1 + 2 AS _c0, 'foo' AS _c1 UNION ALL SELECT 3 + 4, 'bar')",
|
||||
);
|
||||
});
|
||||
|
||||
test('rewrites references for compount select statements', () {
|
||||
_test(
|
||||
'''
|
||||
WITH
|
||||
foo AS (SELECT 2 * 3 UNION ALL SELECT 3)
|
||||
SELECT "2 * 3" FROM foo;
|
||||
''',
|
||||
'WITH foo AS (SELECT 2 * 3 AS _c0 UNION ALL SELECT 3) '
|
||||
'SELECT _c0 FROM foo',
|
||||
);
|
||||
});
|
||||
|
||||
test('does not rewrite compound select statements with explicit names', () {
|
||||
_test(
|
||||
'''
|
||||
WITH
|
||||
foo(x) AS (SELECT 2 * 3 UNION ALL SELECT 3)
|
||||
SELECT x FROM foo;
|
||||
''',
|
||||
'WITH foo(x) AS (SELECT 2 * 3 UNION ALL SELECT 3) '
|
||||
'SELECT x FROM foo',
|
||||
);
|
||||
});
|
||||
}
|
|
@ -540,7 +540,7 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R?> {
|
|||
|
||||
@override
|
||||
R? visitParentheses(Parentheses e, A arg) {
|
||||
return e.expression.accept(this, arg);
|
||||
return visitExpression(e, arg);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -641,11 +641,12 @@ extension TransformerUtils<A> on Transformer<A> {
|
|||
return transformed as T;
|
||||
}
|
||||
|
||||
void transformChildren(List<AstNode?> children, AstNode parent, A arg) {
|
||||
final newChildren = <AstNode>[];
|
||||
void transformChildren<T extends AstNode?>(
|
||||
List<T> children, AstNode parent, A arg) {
|
||||
final newChildren = <T>[];
|
||||
|
||||
for (final child in children) {
|
||||
final transformed = transform(child!, arg);
|
||||
final transformed = transform(child as AstNode, arg) as T?;
|
||||
if (transformed != null) {
|
||||
newChildren.add(transformed..parent = parent);
|
||||
}
|
||||
|
|
|
@ -1091,7 +1091,10 @@ class Parser {
|
|||
mode = CompoundSelectMode.unionAll;
|
||||
}
|
||||
|
||||
final select = _selectNoCompound()!;
|
||||
final select = _selectNoCompound();
|
||||
if (select == null) {
|
||||
_error('Expected a select statement here!');
|
||||
}
|
||||
|
||||
return CompoundSelectPart(
|
||||
mode: mode!,
|
||||
|
|
|
@ -309,7 +309,7 @@ class NodeSqlBuilder extends AstVisitor<void, void> {
|
|||
void visitCommonTableExpression(CommonTableExpression e, void arg) {
|
||||
_identifier(e.cteTableName);
|
||||
if (e.columnNames != null) {
|
||||
_symbol('(${e.columnNames!.join(', ')})');
|
||||
_symbol('(${e.columnNames!.join(', ')})', spaceAfter: true);
|
||||
}
|
||||
|
||||
_keyword(TokenType.as);
|
||||
|
@ -1038,9 +1038,9 @@ class NodeSqlBuilder extends AstVisitor<void, void> {
|
|||
|
||||
@override
|
||||
void visitSelectStatementAsSource(SelectStatementAsSource e, void arg) {
|
||||
_symbol('(');
|
||||
_symbol('(', spaceBefore: true);
|
||||
visit(e.statement, arg);
|
||||
_symbol(')');
|
||||
_symbol(')', spaceAfter: true);
|
||||
|
||||
if (e.as != null) {
|
||||
_keyword(TokenType.as);
|
||||
|
|
Loading…
Reference in New Issue