Refactor sql scoping, make aliased tables explicit

This commit is contained in:
Simon Binder 2020-02-05 22:52:45 +01:00
parent aa52c4ba3d
commit b143ee5a4b
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
10 changed files with 83 additions and 30 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ pubspec.lock
benchmark_results.json benchmark_results.json
.flutter-plugins-dependencies .flutter-plugins-dependencies
flutter_export_environment.sh

View File

@ -187,7 +187,7 @@ packages:
path: "../../../moor_flutter" path: "../../../moor_flutter"
relative: true relative: true
source: path source: path
version: "2.0.0" version: "2.1.0"
multi_server_socket: multi_server_socket:
dependency: transitive dependency: transitive
description: description:

View File

@ -7,17 +7,18 @@ mixin ReferenceOwner {
} }
/// Mixin for classes which can be referenced by a [ReferenceOwner]. /// Mixin for classes which can be referenced by a [ReferenceOwner].
mixin Referencable {} mixin Referencable {
/// Whether this referencable is still visible in child scopes. This doesn't
/// A referencable which is still visible in child scopes. This doesn't apply to /// apply to many things, basically only to tables.
/// many things, basically only tables. ///
/// /// For instance: "SELECT *, 1 AS d, (SELECT id FROM demo WHERE id = out.id)
/// For instance: "SELECT *, 1 AS d, (SELECT id FROM demo WHERE id = out.id) /// FROM demo AS out;"
/// FROM demo AS out;" /// is a valid sql query when the demo table has an id column. However,
/// is a valid sql query when the demo table has an id column. However, /// "SELECT *, 1 AS d, (SELECT id FROM demo WHERE id = d) FROM demo AS out;"
/// "SELECT *, 1 AS d, (SELECT id FROM demo WHERE id = d) FROM demo AS out;" is /// is not, the "d" referencable is not visible for the child select
/// not, the "d" referencable is not visible for the child select statement. /// statement.
mixin VisibleToChildren on Referencable {} bool get visibleToChildren => false;
}
/// Class which keeps track of references for tables, columns and functions in a /// Class which keeps track of references for tables, columns and functions in a
/// query. /// query.
@ -86,7 +87,7 @@ class ReferenceScope {
if (scope._references.containsKey(upper)) { if (scope._references.containsKey(upper)) {
final candidates = scope._references[upper]; final candidates = scope._references[upper];
final resolved = candidates.whereType<T>().where((x) { final resolved = candidates.whereType<T>().where((x) {
return x is VisibleToChildren || !isAtParent; return x.visibleToChildren || !isAtParent;
}); });
if (resolved.isNotEmpty) { if (resolved.isNotEmpty) {
return resolved.first; return resolved.first;
@ -102,7 +103,7 @@ class ReferenceScope {
} }
/// Returns everything that is in scope and a subtype of [T]. /// Returns everything that is in scope and a subtype of [T].
List<T> allOf<T>() { List<T> allOf<T extends Referencable>() {
var scope = this; var scope = this;
var isInCurrentScope = true; var isInCurrentScope = true;
final collected = <T>[]; final collected = <T>[];
@ -112,7 +113,8 @@ class ReferenceScope {
scope._references.values.expand((list) => list).whereType<T>(); scope._references.values.expand((list) => list).whereType<T>();
if (!isInCurrentScope) { if (!isInCurrentScope) {
foundValues = foundValues.whereType<VisibleToChildren>().cast(); foundValues =
foundValues.where((element) => element.visibleToChildren).cast();
} }
collected.addAll(foundValues); collected.addAll(foundValues);

View File

@ -18,6 +18,9 @@ abstract class ResultSet implements ResolvesToResultSet {
@override @override
ResultSet get resultSet => this; ResultSet get resultSet => this;
@override
bool get visibleToChildren => false;
Column findColumn(String name) { Column findColumn(String name) {
return resolvedColumns.firstWhere((c) => c.name == name, return resolvedColumns.firstWhere((c) => c.name == name,
orElse: () => null); orElse: () => null);
@ -34,9 +37,7 @@ class CustomResultSet with ResultSet {
/// A database table. The information stored here will be used to resolve /// A database table. The information stored here will be used to resolve
/// references and for type inference. /// references and for type inference.
class Table class Table with ResultSet, HasMetaMixin implements HumanReadable {
with ResultSet, VisibleToChildren, HasMetaMixin
implements HumanReadable {
/// The name of this table, as it appears in sql statements. This should be /// The name of this table, as it appears in sql statements. This should be
/// the raw name, not an escaped version. /// the raw name, not an escaped version.
/// ///
@ -66,6 +67,9 @@ class Table
/// The ast node that created this table /// The ast node that created this table
final TableInducingStatement definition; final TableInducingStatement definition;
@override
bool get visibleToChildren => true;
TableColumn _rowIdColumn; TableColumn _rowIdColumn;
/// Constructs a table from the known [name] and [resolvedColumns]. /// Constructs a table from the known [name] and [resolvedColumns].
@ -102,3 +106,31 @@ class Table
return name; return name;
} }
} }
class TableAlias implements ResultSet, HumanReadable {
final ResultSet delegate;
final String alias;
TableAlias(this.delegate, this.alias);
@override
List<Column> get resolvedColumns => delegate.resolvedColumns;
@override
Column findColumn(String name) => delegate.findColumn(name);
@override
ResultSet get resultSet => this;
@override
bool get visibleToChildren => delegate.visibleToChildren;
@override
String humanReadableDescription() {
final delegateDescription = delegate is HumanReadable
? (delegate as HumanReadable).humanReadableDescription()
: delegate.toString();
return '$alias (alias to $delegateDescription)';
}
}

View File

@ -73,12 +73,12 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
@override @override
void visitDoUpdate(DoUpdate e, void arg) { void visitDoUpdate(DoUpdate e, void arg) {
final surroundingInsert = e.parents.whereType<InsertStatement>().first; final surroundingInsert = e.parents.whereType<InsertStatement>().first;
final table = surroundingInsert.resolvedTable; final table = surroundingInsert.table.resultSet;
if (table != null) { if (table != null) {
// add "excluded" table qualifier that referring to the row that would // add "excluded" table qualifier that referring to the row that would
// have been inserted had the uniqueness constraint not been violated. // have been inserted had the uniqueness constraint not been violated.
e.scope.register('excluded', table); e.scope.register('excluded', TableAlias(table, 'excluded'));
} }
visitChildren(e, arg); visitChildren(e, arg);
@ -94,7 +94,6 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
@override @override
void visitInsertStatement(InsertStatement e, void arg) { void visitInsertStatement(InsertStatement e, void arg) {
final table = _resolveTableReference(e.table); final table = _resolveTableReference(e.table);
e.resolvedTable = table;
visitChildren(e, arg); visitChildren(e, arg);
e.scope.availableColumns = table.resolvedColumns; e.scope.availableColumns = table.resolvedColumns;
visitChildren(e, arg); visitChildren(e, arg);
@ -118,10 +117,10 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
final scope = e.scope; final scope = e.scope;
if (e.target.introducesNew) { if (e.target.introducesNew) {
scope.register('new', table); scope.register('new', TableAlias(table, 'new'));
} }
if (e.target.introducesOld) { if (e.target.introducesOld) {
scope.register('old', table); scope.register('old', TableAlias(table, 'old'));
} }
visitChildren(e, arg); visitChildren(e, arg);
@ -251,7 +250,13 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
ResultSet _resolveTableReference(TableReference r) { ResultSet _resolveTableReference(TableReference r) {
final scope = r.scope; final scope = r.scope;
final resolvedTable = scope.resolve<ResultSet>(r.tableName, orElse: () { final resolvedTable = scope.resolve<ResultSet>(r.tableName, orElse: () {
final available = scope.allOf<Table>().map((t) => t.name); final available = scope.allOf<ResultSet>().map((t) {
if (t is HumanReadable) {
return (t as HumanReadable).humanReadableDescription();
}
return t.toString();
});
context.reportError(UnresolvedReferenceError( context.reportError(UnresolvedReferenceError(
type: AnalysisErrorType.referencedUnknownTable, type: AnalysisErrorType.referencedUnknownTable,

View File

@ -22,7 +22,7 @@ class WithClause extends AstNode {
bool contentEquals(WithClause other) => other.recursive == recursive; bool contentEquals(WithClause other) => other.recursive == recursive;
} }
class CommonTableExpression extends AstNode with ResultSet, VisibleToChildren { class CommonTableExpression extends AstNode with ResultSet {
final String cteTableName; final String cteTableName;
/// If this common table expression has explicit column names, e.g. with /// If this common table expression has explicit column names, e.g. with
@ -78,4 +78,7 @@ class CommonTableExpression extends AstNode with ResultSet, VisibleToChildren {
return _cachedColumns; return _cachedColumns;
} }
@override
bool get visibleToChildren => true;
} }

View File

@ -43,7 +43,7 @@ abstract class TableOrSubquery extends Queryable {}
/// set. /// set.
class TableReference extends TableOrSubquery class TableReference extends TableOrSubquery
with ReferenceOwner with ReferenceOwner
implements Renamable, ResolvesToResultSet, VisibleToChildren { implements Renamable, ResolvesToResultSet {
final String tableName; final String tableName;
Token tableNameToken; Token tableNameToken;
@ -64,6 +64,9 @@ class TableReference extends TableOrSubquery
ResultSet get resultSet { ResultSet get resultSet {
return resolved as ResultSet; return resolved as ResultSet;
} }
@override
bool get visibleToChildren => true;
} }
/// A nested select statement that appears after a FROM clause. This is /// A nested select statement that appears after a FROM clause. This is
@ -189,6 +192,9 @@ class TableValuedFunction extends Queryable
@override @override
Iterable<AstNode> get childNodes => [parameters]; Iterable<AstNode> get childNodes => [parameters];
@override
bool get visibleToChildren => false;
@override @override
bool contentEquals(TableValuedFunction other) { bool contentEquals(TableValuedFunction other) {
return other.name == name; return other.name == name;

View File

@ -17,8 +17,6 @@ class InsertStatement extends CrudStatement {
final InsertSource source; final InsertSource source;
final UpsertClause upsert; final UpsertClause upsert;
ResultSet /*?*/ resolvedTable;
List<Column> get resolvedTargetColumns { List<Column> get resolvedTargetColumns {
if (targetColumns.isNotEmpty) { if (targetColumns.isNotEmpty) {
return targetColumns.map((c) => c.resolvedColumn).toList(); return targetColumns.map((c) => c.resolvedColumn).toList();

View File

@ -122,6 +122,9 @@ class ExpressionResultColumn extends ResultColumn
ExpressionResultColumn({@required this.expression, this.as}); ExpressionResultColumn({@required this.expression, this.as});
@override
bool get visibleToChildren => false;
@override @override
Iterable<AstNode> get childNodes => [expression]; Iterable<AstNode> get childNodes => [expression];

View File

@ -65,7 +65,7 @@ abstract class TableValuedFunctionHandler {
/// An sqlite module, which can be used in a `CREATE VIRTUAL TABLE` statement /// An sqlite module, which can be used in a `CREATE VIRTUAL TABLE` statement
/// to find providers. /// to find providers.
abstract class Module implements Referencable, VisibleToChildren { abstract class Module implements Referencable {
/// The name of this module, which is referenced by the `USING` clause in a /// The name of this module, which is referenced by the `USING` clause in a
/// `CREATE VIRTUAL TABLE` statement. /// `CREATE VIRTUAL TABLE` statement.
final String name; final String name;
@ -76,4 +76,7 @@ abstract class Module implements Referencable, VisibleToChildren {
/// refers to this module. The module is responsible for setting /// refers to this module. The module is responsible for setting
/// [Table.definition]. /// [Table.definition].
Table parseTable(CreateVirtualTableStatement stmt); Table parseTable(CreateVirtualTableStatement stmt);
@override
bool get visibleToChildren => true;
} }