mirror of https://github.com/AMT-Cheif/drift.git
types2: Support inference for insert statements (#297)
This commit is contained in:
parent
c95a5f0aad
commit
92030c07a8
|
@ -57,3 +57,14 @@ class RoughTypeExpectation extends TypeExpectation {
|
||||||
enum _RoughType {
|
enum _RoughType {
|
||||||
numeric,
|
numeric,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Type expectation for result columns in a select statement.
|
||||||
|
///
|
||||||
|
/// This can be used to set expectations in an `INSERT INTO SELECT` statement,
|
||||||
|
/// where the column types are constrained by what's expected in the insert
|
||||||
|
/// statement.
|
||||||
|
class SelectTypeExpectation extends TypeExpectation {
|
||||||
|
final List<TypeExpectation> columnExpectations;
|
||||||
|
|
||||||
|
SelectTypeExpectation(this.columnExpectations);
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,67 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
||||||
session.finish();
|
session.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitSelectStatement(SelectStatement e, TypeExpectation arg) {
|
||||||
|
_handleWhereClause(e);
|
||||||
|
|
||||||
|
var currentColumnIndex = 0;
|
||||||
|
final columnExpectations = arg is SelectTypeExpectation
|
||||||
|
? arg.columnExpectations
|
||||||
|
: const <TypeExpectation>[];
|
||||||
|
|
||||||
|
for (final child in e.childNodes) {
|
||||||
|
if (child == e.where) continue; // handled above
|
||||||
|
|
||||||
|
if (child is ResultColumn) {
|
||||||
|
if (child is ExpressionResultColumn) {
|
||||||
|
final expectation = currentColumnIndex < columnExpectations.length
|
||||||
|
? columnExpectations[currentColumnIndex]
|
||||||
|
: const NoTypeExpectation();
|
||||||
|
visit(child, expectation);
|
||||||
|
|
||||||
|
currentColumnIndex++;
|
||||||
|
} else if (child is StarResultColumn) {
|
||||||
|
currentColumnIndex += child.scope.availableColumns.length;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
visit(child, arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitInsertStatement(InsertStatement e, TypeExpectation arg) {
|
||||||
|
if (e.withClause != null) visit(e.withClause, arg);
|
||||||
|
visitList(e.targetColumns, const NoTypeExpectation());
|
||||||
|
|
||||||
|
final targets = e.resolvedTargetColumns ?? const [];
|
||||||
|
targets.forEach(_handleColumn);
|
||||||
|
|
||||||
|
final expectations = targets.map((r) {
|
||||||
|
if (session.graph.knowsType(r)) {
|
||||||
|
return ExactTypeExpectation(session.typeOf(r));
|
||||||
|
}
|
||||||
|
return const NoTypeExpectation();
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
e.source.when(
|
||||||
|
isSelect: (select) {
|
||||||
|
visit(select.stmt, SelectTypeExpectation(expectations));
|
||||||
|
},
|
||||||
|
isValues: (values) {
|
||||||
|
for (final tuple in values.values) {
|
||||||
|
for (var i = 0; i < tuple.expressions.length; i++) {
|
||||||
|
final expectation = i < expectations.length
|
||||||
|
? expectations[i]
|
||||||
|
: const NoTypeExpectation();
|
||||||
|
visit(tuple.expressions[i], expectation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitCrudStatement(CrudStatement stmt, TypeExpectation arg) {
|
void visitCrudStatement(CrudStatement stmt, TypeExpectation arg) {
|
||||||
if (stmt is HasWhereClause) {
|
if (stmt is HasWhereClause) {
|
||||||
|
@ -172,6 +233,36 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
||||||
visit(e.operand, const NoTypeExpectation());
|
visit(e.operand, const NoTypeExpectation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitReference(Reference e, TypeExpectation arg) {
|
||||||
|
final resolved = e.resolvedColumn;
|
||||||
|
if (resolved == null) return;
|
||||||
|
|
||||||
|
_handleColumn(resolved);
|
||||||
|
_lazyCopy(e, resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleColumn(Column column) {
|
||||||
|
if (session.graph.knowsType(column)) return;
|
||||||
|
|
||||||
|
if (column is TableColumn) {
|
||||||
|
session.markTypeResolved(column, column.type);
|
||||||
|
} else if (column is ExpressionColumn) {
|
||||||
|
_lazyCopy(column, column.expression);
|
||||||
|
} else if (column is DelegatedColumn && column.innerColumn != null) {
|
||||||
|
_handleColumn(column.innerColumn);
|
||||||
|
_lazyCopy(column, column.innerColumn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _lazyCopy(Typeable to, Typeable from) {
|
||||||
|
if (session.graph.knowsType(from)) {
|
||||||
|
session.markTypeResolved(to, session.typeOf(from));
|
||||||
|
} else {
|
||||||
|
session.addRelationship(CopyTypeFrom(to, from));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _handleWhereClause(HasWhereClause stmt) {
|
void _handleWhereClause(HasWhereClause stmt) {
|
||||||
if (stmt.where != null) {
|
if (stmt.where != null) {
|
||||||
// assume that a where statement is a boolean expression. Sqlite
|
// assume that a where statement is a boolean expression. Sqlite
|
||||||
|
|
|
@ -367,9 +367,12 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R> {
|
||||||
R visit(AstNode e, A arg) => e.accept(this, arg);
|
R visit(AstNode e, A arg) => e.accept(this, arg);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
R visitChildren(AstNode e, A arg) {
|
R visitChildren(AstNode e, A arg) => visitList(e.childNodes, arg);
|
||||||
for (final child in e.childNodes) {
|
|
||||||
child.accept(this, arg);
|
@protected
|
||||||
|
R visitList(Iterable<AstNode> nodes, A arg) {
|
||||||
|
for (final node in nodes) {
|
||||||
|
node.accept(this, arg);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,4 +72,31 @@ void main() {
|
||||||
expect(_resolveFirstVariable('SELECT CAST(? AS TEXT)'), null);
|
expect(_resolveFirstVariable('SELECT CAST(? AS TEXT)'), null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('types in insert statements', () {
|
||||||
|
test('for VALUES', () {
|
||||||
|
final resolver =
|
||||||
|
_obtainResolver('INSERT INTO demo VALUES (:id, :content);');
|
||||||
|
final root = resolver.session.context.root;
|
||||||
|
final variables = root.allDescendants.whereType<Variable>();
|
||||||
|
|
||||||
|
final idVar = variables.singleWhere((v) => v.resolvedIndex == 1);
|
||||||
|
final contentVar = variables.singleWhere((v) => v.resolvedIndex == 2);
|
||||||
|
|
||||||
|
expect(resolver.session.typeOf(idVar), id.type);
|
||||||
|
expect(resolver.session.typeOf(contentVar), content.type);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('for SELECT', () {
|
||||||
|
final resolver = _obtainResolver('INSERT INTO demo SELECT :id, :content');
|
||||||
|
final root = resolver.session.context.root;
|
||||||
|
final variables = root.allDescendants.whereType<Variable>();
|
||||||
|
|
||||||
|
final idVar = variables.singleWhere((v) => v.resolvedIndex == 1);
|
||||||
|
final contentVar = variables.singleWhere((v) => v.resolvedIndex == 2);
|
||||||
|
|
||||||
|
expect(resolver.session.typeOf(idVar), id.type);
|
||||||
|
expect(resolver.session.typeOf(contentVar), content.type);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue