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

3
.gitignore vendored
View File

@ -8,4 +8,5 @@ pubspec.lock
.dart_tool/
benchmark_results.json
.flutter-plugins-dependencies
.flutter-plugins-dependencies
flutter_export_environment.sh

View File

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

View File

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

View File

@ -18,6 +18,9 @@ abstract class ResultSet implements ResolvesToResultSet {
@override
ResultSet get resultSet => this;
@override
bool get visibleToChildren => false;
Column findColumn(String name) {
return resolvedColumns.firstWhere((c) => c.name == name,
orElse: () => null);
@ -34,9 +37,7 @@ class CustomResultSet with ResultSet {
/// A database table. The information stored here will be used to resolve
/// references and for type inference.
class Table
with ResultSet, VisibleToChildren, HasMetaMixin
implements HumanReadable {
class Table with ResultSet, HasMetaMixin implements HumanReadable {
/// The name of this table, as it appears in sql statements. This should be
/// the raw name, not an escaped version.
///
@ -66,6 +67,9 @@ class Table
/// The ast node that created this table
final TableInducingStatement definition;
@override
bool get visibleToChildren => true;
TableColumn _rowIdColumn;
/// Constructs a table from the known [name] and [resolvedColumns].
@ -102,3 +106,31 @@ class Table
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
void visitDoUpdate(DoUpdate e, void arg) {
final surroundingInsert = e.parents.whereType<InsertStatement>().first;
final table = surroundingInsert.resolvedTable;
final table = surroundingInsert.table.resultSet;
if (table != null) {
// add "excluded" table qualifier that referring to the row that would
// 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);
@ -94,7 +94,6 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
@override
void visitInsertStatement(InsertStatement e, void arg) {
final table = _resolveTableReference(e.table);
e.resolvedTable = table;
visitChildren(e, arg);
e.scope.availableColumns = table.resolvedColumns;
visitChildren(e, arg);
@ -118,10 +117,10 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
final scope = e.scope;
if (e.target.introducesNew) {
scope.register('new', table);
scope.register('new', TableAlias(table, 'new'));
}
if (e.target.introducesOld) {
scope.register('old', table);
scope.register('old', TableAlias(table, 'old'));
}
visitChildren(e, arg);
@ -251,7 +250,13 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
ResultSet _resolveTableReference(TableReference r) {
final scope = r.scope;
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(
type: AnalysisErrorType.referencedUnknownTable,

View File

@ -22,7 +22,7 @@ class WithClause extends AstNode {
bool contentEquals(WithClause other) => other.recursive == recursive;
}
class CommonTableExpression extends AstNode with ResultSet, VisibleToChildren {
class CommonTableExpression extends AstNode with ResultSet {
final String cteTableName;
/// 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;
}
@override
bool get visibleToChildren => true;
}

View File

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

View File

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

View File

@ -122,6 +122,9 @@ class ExpressionResultColumn extends ResultColumn
ExpressionResultColumn({@required this.expression, this.as});
@override
bool get visibleToChildren => false;
@override
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
/// 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
/// `CREATE VIRTUAL TABLE` statement.
final String name;
@ -76,4 +76,7 @@ abstract class Module implements Referencable, VisibleToChildren {
/// refers to this module. The module is responsible for setting
/// [Table.definition].
Table parseTable(CreateVirtualTableStatement stmt);
@override
bool get visibleToChildren => true;
}