Infer types for insert statements

This commit is contained in:
Simon Binder 2019-08-29 15:32:45 +02:00
parent 2f8dc6d68e
commit dd8b4ab03a
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
8 changed files with 67 additions and 4 deletions

View File

@ -65,10 +65,10 @@ package to generate type-safe methods from sql.
Most on this list is just not supported yet because I didn't found a use case for
them yet. If you need them, just leave an issue and I'll try to implement them soon.
- For now, `INSERT` statements are not supported, but they will be soon.
- Compound select statements (`UNION` / `INTERSECT`) are not supported yet
- Common table expressions are not supported
- Some advanced expressions, like `CAST`s aren't supported yet.
- An `UPSERT` clause is not yet supported on insert statements
If you run into parsing errors with what you think is valid sql, please create an issue.

View File

@ -1,4 +1,4 @@
import 'dart:math';
import 'dart:math' show min, max;
import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

View File

@ -22,6 +22,14 @@ class ColumnResolver extends RecursiveVisitor<void> {
visitChildren(e);
}
@override
void visitInsertStatement(InsertStatement e) {
final table = _resolveTableReference(e.table);
visitChildren(e);
e.scope.availableColumns = table.resolvedColumns;
visitChildren(e);
}
@override
void visitDeleteStatement(DeleteStatement e) {
final table = _resolveTableReference(e.from);

View File

@ -1,7 +1,8 @@
part of '../analysis.dart';
/// Resolves the type of columns in a select statement and the type of
/// expressions appearing in a select statement.
/// Resolves types for all nodes in the AST which can have a type. This includes
/// expressions, variables and so on. For select statements, we also try to
/// figure out what types they return.
class TypeResolvingVisitor extends RecursiveVisitor<void> {
final AnalysisContext context;
TypeResolver get types => context.types;
@ -19,4 +20,29 @@ class TypeResolvingVisitor extends RecursiveVisitor<void> {
super.visitChildren(e);
}
@override
void visitInsertStatement(InsertStatement e) {
// resolve target columns - this is easy, as we should have the table
// structure available.
e.targetColumns.forEach(types.resolveExpression);
// if the insert statement has a VALUES source, we can now infer the type
// for those expressions by comparing with the target column.
if (e.source is ValuesSource) {
final targetTypes = e.resolvedTargetColumns.map(context.typeOf).toList();
final source = e.source as ValuesSource;
for (var tuple in source.values) {
final expressions = tuple.expressions;
for (var i = 0; i < min(expressions.length, targetTypes.length); i++) {
if (i < targetTypes.length) {
context.types.markResult(expressions[i], targetTypes[i]);
}
}
}
}
visitChildren(e);
}
}

View File

@ -29,6 +29,11 @@ class TypeResolver {
return calculated;
}
/// Manually writes the [result] for the [Typeable] [t].
void markResult(Typeable t, ResolveResult result) {
_results.putIfAbsent(t, () => result);
}
ResolveResult resolveOrInfer(Typeable t) {
if (t is Column) {
return resolveColumn(t);

View File

@ -11,6 +11,8 @@ class Reference extends Expression with ReferenceOwner {
final String tableName;
final String columnName;
Column get resolvedColumn => resolved as Column;
Reference({this.tableName, this.columnName});
@override

View File

@ -16,6 +16,15 @@ class InsertStatement extends Statement with CrudStatement {
final List<Reference> targetColumns;
final InsertSource source;
List<Column> get resolvedTargetColumns {
if (targetColumns.isNotEmpty) {
return targetColumns.map((c) => c.resolvedColumn).toList();
} else {
// no columns declared - assume all columns from the table
return table.resultSet?.resolvedColumns;
}
}
// todo parse upsert clauses
InsertStatement(

View File

@ -46,6 +46,19 @@ void main() {
});
});
test('handles VALUES clause in insert statements', () {
final engine = SqlEngine()..registerTable(demoTable);
final context = engine.analyze('INSERT INTO demo VALUES (?, ?), (?, ?)');
final variables =
context.root.allDescendants.whereType<Variable>().toList();
expect(context.typeOf(variables[0]), ResolveResult(id.type));
expect(context.typeOf(variables[1]), ResolveResult(content.type));
expect(context.typeOf(variables[2]), ResolveResult(id.type));
expect(context.typeOf(variables[3]), ResolveResult(content.type));
});
test('handles nth_value', () {
final ctx = SqlEngine().analyze("SELECT nth_value('string', ?1) = ?2");
final variables = ctx.root.allDescendants.whereType<Variable>().iterator;