Add explicit aliases with new codegen

This commit is contained in:
Simon Binder 2021-05-04 21:00:28 +02:00
parent c5d696a9eb
commit 1de5479d60
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
9 changed files with 194 additions and 18 deletions

View File

@ -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(

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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].

View File

@ -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)';

View File

@ -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',
);
});
}

View File

@ -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);
}

View File

@ -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!,

View File

@ -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);