mirror of https://github.com/AMT-Cheif/drift.git
Resolve types of columns in compound select statements
This commit is contained in:
parent
f6a5009380
commit
2a782a010e
|
@ -59,5 +59,6 @@ enum AnalysisErrorType {
|
||||||
synctactic,
|
synctactic,
|
||||||
|
|
||||||
unknownFunction,
|
unknownFunction,
|
||||||
|
compoundColumnCountMismatch,
|
||||||
other,
|
other,
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@ class TableColumn extends Column {
|
||||||
@override
|
@override
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
/// The type of this column, which is immediately available.
|
/// The type of this column, which is available before any resolution happens
|
||||||
|
/// (we know if from the table).
|
||||||
final ResolvedType type;
|
final ResolvedType type;
|
||||||
|
|
||||||
/// The column constraints set on this column.
|
/// The column constraints set on this column.
|
||||||
|
@ -39,3 +40,15 @@ class ExpressionColumn extends Column {
|
||||||
|
|
||||||
ExpressionColumn({@required this.name, this.expression});
|
ExpressionColumn({@required this.name, this.expression});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The result column of a [CompoundSelectStatement].
|
||||||
|
class CompoundSelectColumn extends Column {
|
||||||
|
/// The column in [CompoundSelectStatement.base] each of the
|
||||||
|
/// [CompoundSelectStatement.additional] that contributed to this column.
|
||||||
|
final List<Column> columns;
|
||||||
|
|
||||||
|
CompoundSelectColumn(this.columns);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => columns.first.name;
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,44 @@ class ColumnResolver extends RecursiveVisitor<void> {
|
||||||
visitChildren(e);
|
visitChildren(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitCompoundSelectStatement(CompoundSelectStatement e) {
|
||||||
|
// first, visit all children so that the compound parts have their columns
|
||||||
|
// resolved
|
||||||
|
visitChildren(e);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitUpdateStatement(UpdateStatement e) {
|
void visitUpdateStatement(UpdateStatement e) {
|
||||||
final table = _resolveTableReference(e.table);
|
final table = _resolveTableReference(e.table);
|
||||||
|
|
|
@ -63,6 +63,9 @@ class TypeResolver {
|
||||||
return ResolveResult(column.type);
|
return ResolveResult(column.type);
|
||||||
} else if (column is ExpressionColumn) {
|
} else if (column is ExpressionColumn) {
|
||||||
return resolveOrInfer(column.expression);
|
return resolveOrInfer(column.expression);
|
||||||
|
} else if (column is CompoundSelectColumn) {
|
||||||
|
// todo maybe use a type that matches every column in here?
|
||||||
|
return resolveColumn(column.columns.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw StateError('Unknown column $column');
|
throw StateError('Unknown column $column');
|
||||||
|
|
|
@ -279,13 +279,14 @@ mixin ExpressionParser on ParserBase {
|
||||||
..token = typedToken
|
..token = typedToken
|
||||||
..setSpan(_previous, _previous);
|
..setSpan(_previous, _previous);
|
||||||
}
|
}
|
||||||
} else if (_checkLenientIdentifier()) {
|
} else if (_checkIdentifier()) {
|
||||||
final first = _consumeIdentifier(
|
final first = _consumeIdentifier(
|
||||||
'This error message should never be displayed. Please report.');
|
'This error message should never be displayed. Please report.');
|
||||||
|
|
||||||
// could be table.column, function(...) or just column
|
// could be table.column, function(...) or just column
|
||||||
if (_matchOne(TokenType.dot)) {
|
if (_matchOne(TokenType.dot)) {
|
||||||
final second = _consumeIdentifier('Expected a column name here');
|
final second =
|
||||||
|
_consumeIdentifier('Expected a column name here', lenient: true);
|
||||||
return Reference(
|
return Reference(
|
||||||
tableName: first.identifier, columnName: second.identifier)
|
tableName: first.identifier, columnName: second.identifier)
|
||||||
..setSpan(first, second);
|
..setSpan(first, second);
|
||||||
|
|
|
@ -103,11 +103,12 @@ abstract class ParserBase {
|
||||||
|
|
||||||
/// Returns whether the next token is an [TokenType.identifier] or a
|
/// Returns whether the next token is an [TokenType.identifier] or a
|
||||||
/// [KeywordToken]. If this method returns true, calling [_consumeIdentifier]
|
/// [KeywordToken]. If this method returns true, calling [_consumeIdentifier]
|
||||||
/// with the lenient parameter will now throw.
|
/// with same [lenient] parameter will now throw.
|
||||||
bool _checkLenientIdentifier() {
|
bool _checkIdentifier({bool lenient = false}) {
|
||||||
final next = _peek;
|
final next = _peek;
|
||||||
return next.type == TokenType.identifier ||
|
if (next.type == TokenType.identifier) return true;
|
||||||
(next is KeywordToken && next.canConvertToIdentifier());
|
|
||||||
|
return next is KeywordToken && (next.canConvertToIdentifier() || lenient);
|
||||||
}
|
}
|
||||||
|
|
||||||
Token _advance() {
|
Token _advance() {
|
||||||
|
@ -130,10 +131,11 @@ abstract class ParserBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes an identifier.
|
/// Consumes an identifier.
|
||||||
IdentifierToken _consumeIdentifier(String message) {
|
IdentifierToken _consumeIdentifier(String message, {bool lenient = false}) {
|
||||||
final next = _peek;
|
final next = _peek;
|
||||||
// non-standard keywords can be parsed as an identifier
|
// non-standard keywords can be parsed as an identifier, we allow all
|
||||||
if (next is KeywordToken && next.canConvertToIdentifier()) {
|
// keywords when lenient is true
|
||||||
|
if (next is KeywordToken && (next.canConvertToIdentifier() || lenient)) {
|
||||||
return (_advance() as KeywordToken).convertToIdentifier();
|
return (_advance() as KeywordToken).convertToIdentifier();
|
||||||
}
|
}
|
||||||
return _consume(TokenType.identifier, message) as IdentifierToken;
|
return _consume(TokenType.identifier, message) as IdentifierToken;
|
||||||
|
|
Loading…
Reference in New Issue