mirror of https://github.com/AMT-Cheif/drift.git
Refactor sql scoping, make aliased tables explicit
This commit is contained in:
parent
aa52c4ba3d
commit
b143ee5a4b
|
@ -8,4 +8,5 @@ pubspec.lock
|
|||
.dart_tool/
|
||||
|
||||
benchmark_results.json
|
||||
.flutter-plugins-dependencies
|
||||
.flutter-plugins-dependencies
|
||||
flutter_export_environment.sh
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue