Support compound select statements in from

This commit is contained in:
Simon Binder 2020-04-07 15:37:35 +02:00
parent 516d2143f7
commit af5333db3c
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
8 changed files with 118 additions and 39 deletions

View File

@ -3,6 +3,8 @@
- Remove `SqlEngine.withOptions` constructor - just use the regular one
- Changed `SelectStatement.from` from `List<Queryable>` to `Queryable?`. Selecting from multiple
tables with a comma will now be parsed as a `JoinClause`.
- Changed `SelectStatementAsSource.statement` from `SelectStatement` to `BaseSelectStatement` and allow
compound select statements to appear in a `FROM` clause
## 0.7.0

View File

@ -22,36 +22,7 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
// resolved
visitChildren(e, arg);
final columnSets = [
e.base.resolvedColumns,
for (var part in e.additional) part.select.resolvedColumns
];
// each select statement must return the same amount of columns
final amount = columnSets.first.length;
for (var i = 1; i < columnSets.length; i++) {
if (columnSets[i].length != amount) {
context.reportError(AnalysisError(
type: AnalysisErrorType.compoundColumnCountMismatch,
relevantNode: e,
message: 'The parts of this compound statement return different '
'amount of columns',
));
break;
}
}
final resolved = <CompoundSelectColumn>[];
// merge all columns at each position into a CompoundSelectColumn
for (var i = 0; i < amount; i++) {
final columnsAtThisIndex = [
for (var set in columnSets) if (set.length > i) set[i]
];
resolved.add(CompoundSelectColumn(columnsAtThisIndex));
}
e.resolvedColumns = resolved;
_resolveCompoundSelect(e);
}
@override
@ -140,8 +111,16 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
// the inner select statement doesn't have access to columns defined in
// the outer statements, which is why we use _resolveSelect instead of
// passing availableColumns down to a recursive call of _handle
_resolveSelect(select.statement);
availableColumns.addAll(select.statement.resolvedColumns);
final stmt = select.statement;
if (stmt is CompoundSelectStatement) {
_resolveCompoundSelect(stmt);
} else if (stmt is SelectStatement) {
_resolveSelect(stmt);
} else {
throw AssertionError('Unknown type of select statement: $stmt');
}
availableColumns.addAll(stmt.resolvedColumns);
},
isJoin: (join) {
_handle(join.primary, availableColumns);
@ -243,6 +222,39 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
scope.availableColumns = availableColumns;
}
void _resolveCompoundSelect(CompoundSelectStatement statement) {
final columnSets = [
statement.base.resolvedColumns,
for (var part in statement.additional) part.select.resolvedColumns
];
// each select statement must return the same amount of columns
final amount = columnSets.first.length;
for (var i = 1; i < columnSets.length; i++) {
if (columnSets[i].length != amount) {
context.reportError(AnalysisError(
type: AnalysisErrorType.compoundColumnCountMismatch,
relevantNode: statement,
message: 'The parts of this compound statement return different '
'amount of columns',
));
break;
}
}
final resolved = <CompoundSelectColumn>[];
// merge all columns at each position into a CompoundSelectColumn
for (var i = 0; i < amount; i++) {
final columnsAtThisIndex = [
for (var set in columnSets) if (set.length > i) set[i]
];
resolved.add(CompoundSelectColumn(columnsAtThisIndex));
}
statement.resolvedColumns = resolved;
}
String _nameOfResultColumn(ExpressionResultColumn c) {
if (c.as != null) return c.as;

View File

@ -190,12 +190,13 @@ class TypeResolver {
}
break;
case 'sum':
final firstType = justResolve(parameters.first);
if (firstType.type?.type == BasicType.int) {
final firstType =
parameters.isEmpty ? null : justResolve(parameters.first);
if (firstType?.type?.type == BasicType.int) {
return firstType;
} else {
return ResolveResult(ResolvedType(
type: BasicType.real, nullable: firstType.nullable));
type: BasicType.real, nullable: firstType?.nullable));
}
break; // can't happen, though
case 'lower':

View File

@ -16,7 +16,18 @@ extension ExpandParameters on SqlInvocation {
} else if (sqlParameters is StarFunctionParameter) {
// if * is used as a parameter, it refers to all columns in all tables
// that are available in the current scope.
return scope.availableColumns.whereType<TableColumn>().toList();
final allColumns = scope.availableColumns;
// When we look at `SELECT SUM(*), foo FROM ...`, the star in `SUM`
// shouldn't expand to include itself.
final unrelated = allColumns.where((column) {
if (column is! ExpressionColumn) return true;
final expression = (column as ExpressionColumn).expression;
return !expression.selfAndDescendants.contains(this);
});
return unrelated.toList();
}
throw ArgumentError('Unknown parameters: $sqlParameters');

View File

@ -74,7 +74,7 @@ class TableReference extends TableOrSubquery
class SelectStatementAsSource extends TableOrSubquery implements Renamable {
@override
final String as;
final SelectStatement statement;
final BaseSelectStatement statement;
SelectStatementAsSource({@required this.statement, this.as});

View File

@ -280,7 +280,7 @@ mixin CrudParser on ParserBase {
return tableRef;
} else if (_matchOne(TokenType.leftParen)) {
final first = _previous;
final innerStmt = _selectNoCompound();
final innerStmt = select();
_consume(TokenType.rightParen,
'Expected a right bracket to terminate the inner select');

View File

@ -74,6 +74,24 @@ void main() {
);
});
test('resolves columns from nested results', () {
final engine = SqlEngine(EngineOptions(useMoorExtensions: true))
..registerTable(demoTable)
..registerTable(anotherTable);
final context = engine.analyze('SELECT SUM(*) AS rst FROM '
'(SELECT COUNT(*) FROM demo UNION ALL SELECT COUNT(*) FROM tbl);');
expect(context.errors, isEmpty);
final select = context.root as SelectStatement;
expect(select.resolvedColumns, hasLength(1));
expect(
context.typeOf(select.resolvedColumns.single).type.type,
BasicType.int,
);
});
group('reports correct column name for rowid aliases', () {
final engine = SqlEngine()
..registerTable(demoTable)

View File

@ -60,7 +60,7 @@ void main() {
);
});
test('from inner select statements', () {
test('inner select statements', () {
final stmt = SqlEngine()
.parse(
'SELECT * FROM table1, (SELECT * FROM table2 WHERE a) as "inner"')
@ -87,6 +87,41 @@ void main() {
);
});
test('inner compound select statements', () {
final stmt = SqlEngine()
.parse('SELECT SUM(*) FROM (SELECT COUNT(*) FROM table1 UNION ALL '
'SELECT COUNT(*) from table2)')
.rootNode as SelectStatement;
final countStar = ExpressionResultColumn(
expression: FunctionExpression(
name: 'COUNT',
parameters: StarFunctionParameter(),
),
);
_enforceFrom(
stmt,
SelectStatementAsSource(
statement: CompoundSelectStatement(
base: SelectStatement(
columns: [countStar],
from: TableReference('table1'),
),
additional: [
CompoundSelectPart(
mode: CompoundSelectMode.unionAll,
select: SelectStatement(
columns: [countStar],
from: TableReference('table2'),
),
),
],
),
),
);
});
test('from a join', () {
final stmt = SqlEngine()
.parse('SELECT * FROM table1 '