mirror of https://github.com/AMT-Cheif/drift.git
Analysis for FROM clauses in UPDATE statements
This commit is contained in:
parent
3000cb2e44
commit
d61e219311
|
@ -1,5 +1,6 @@
|
||||||
## 0.14.1-dev
|
## 0.14.1-dev
|
||||||
|
|
||||||
|
- Support `FROM` clauses in `UPDATE` statements
|
||||||
- Fix `rank` columns of fts5 tables being misreported as integers
|
- Fix `rank` columns of fts5 tables being misreported as integers
|
||||||
|
|
||||||
## 0.14.0
|
## 0.14.0
|
||||||
|
|
|
@ -70,6 +70,27 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitUpdateStatement(UpdateStatement e, void arg) {
|
||||||
|
final availableColumns = <Column>[];
|
||||||
|
|
||||||
|
// Add columns from the main table, if it was resolved
|
||||||
|
final baseTable = _resolveTableReference(e.table);
|
||||||
|
if (baseTable != null) {
|
||||||
|
availableColumns.addAll(baseTable.resolvedColumns ?? const []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also add columns from a FROM clause, if one is present
|
||||||
|
final from = e.from;
|
||||||
|
if (from != null) _handle(from, availableColumns);
|
||||||
|
|
||||||
|
e.scope.availableColumns = availableColumns;
|
||||||
|
for (final child in e.childNodes) {
|
||||||
|
// Visit remaining children
|
||||||
|
if (child != e.table && child != e.from) visit(child, arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _addIfResolved(AstNode node, TableReference ref) {
|
void _addIfResolved(AstNode node, TableReference ref) {
|
||||||
final table = _resolveTableReference(ref);
|
final table = _resolveTableReference(ref);
|
||||||
if (table != null) {
|
if (table != null) {
|
||||||
|
@ -77,12 +98,6 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void visitUpdateStatement(UpdateStatement e, void arg) {
|
|
||||||
_addIfResolved(e, e.table);
|
|
||||||
visitExcept(e, e.table, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitInsertStatement(InsertStatement e, void arg) {
|
void visitInsertStatement(InsertStatement e, void arg) {
|
||||||
_addIfResolved(e, e.table!);
|
_addIfResolved(e, e.table!);
|
||||||
|
|
|
@ -58,7 +58,7 @@ class AstPreparingVisitor extends RecursiveVisitor<void, void> {
|
||||||
|
|
||||||
if (isInFROM) {
|
if (isInFROM) {
|
||||||
final surroundingSelect =
|
final surroundingSelect =
|
||||||
e.parents.firstWhere((node) => node is BaseSelectStatement).scope;
|
e.parents.firstWhere((node) => node is HasFrom).scope;
|
||||||
final forked = surroundingSelect.createSibling();
|
final forked = surroundingSelect.createSibling();
|
||||||
e.scope = forked;
|
e.scope = forked;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -14,9 +14,14 @@ abstract class BaseSelectStatement extends CrudStatement with ResultSet {
|
||||||
abstract class SelectStatementNoCompound implements BaseSelectStatement {}
|
abstract class SelectStatementNoCompound implements BaseSelectStatement {}
|
||||||
|
|
||||||
class SelectStatement extends BaseSelectStatement
|
class SelectStatement extends BaseSelectStatement
|
||||||
implements StatementWithWhere, SelectStatementNoCompound, HasPrimarySource {
|
implements
|
||||||
|
StatementWithWhere,
|
||||||
|
SelectStatementNoCompound,
|
||||||
|
HasPrimarySource,
|
||||||
|
HasFrom {
|
||||||
final bool distinct;
|
final bool distinct;
|
||||||
final List<ResultColumn> columns;
|
final List<ResultColumn> columns;
|
||||||
|
@override
|
||||||
Queryable? from;
|
Queryable? from;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -24,6 +24,14 @@ abstract class HasPrimarySource extends Statement {
|
||||||
Queryable? get table;
|
Queryable? get table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Interfaces for statements that have a `FROM` clause.
|
||||||
|
///
|
||||||
|
/// This includes selects and, since recently, updates.
|
||||||
|
abstract class HasFrom extends Statement {
|
||||||
|
/// The table, join clause or subquery appearing in the `FROM` clause.
|
||||||
|
Queryable? get from;
|
||||||
|
}
|
||||||
|
|
||||||
/// Interface for statements that have a primary where clause (select, update,
|
/// Interface for statements that have a primary where clause (select, update,
|
||||||
/// delete).
|
/// delete).
|
||||||
abstract class StatementWithWhere extends Statement implements HasWhereClause {}
|
abstract class StatementWithWhere extends Statement implements HasWhereClause {}
|
||||||
|
|
|
@ -17,11 +17,12 @@ const Map<TokenType, FailureMode> _tokensToMode = {
|
||||||
};
|
};
|
||||||
|
|
||||||
class UpdateStatement extends CrudStatement
|
class UpdateStatement extends CrudStatement
|
||||||
implements StatementWithWhere, HasPrimarySource {
|
implements StatementWithWhere, HasPrimarySource, HasFrom {
|
||||||
final FailureMode? or;
|
final FailureMode? or;
|
||||||
@override
|
@override
|
||||||
TableReference table;
|
TableReference table;
|
||||||
final List<SetComponent> set;
|
final List<SetComponent> set;
|
||||||
|
@override
|
||||||
Queryable? from;
|
Queryable? from;
|
||||||
@override
|
@override
|
||||||
Expression? where;
|
Expression? where;
|
||||||
|
|
|
@ -132,4 +132,29 @@ INSERT INTO demo VALUES (?, ?)
|
||||||
expect(columns.map((e) => context.typeOf(e).type?.type),
|
expect(columns.map((e) => context.typeOf(e).type?.type),
|
||||||
[BasicType.text, BasicType.int]);
|
[BasicType.text, BasicType.int]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('handles update statement with from clause', () {
|
||||||
|
// Example from here: https://www.sqlite.org/lang_update.html#upfrom
|
||||||
|
engine..registerTableFromSql('''
|
||||||
|
CREATE TABLE inventory (
|
||||||
|
itemId INTEGER PRIMARY KEY,
|
||||||
|
quantity INTEGER NOT NULL DEFAULT 0,
|
||||||
|
);
|
||||||
|
''')..registerTableFromSql('''
|
||||||
|
CREATE TABLE sales (
|
||||||
|
itemId INTEGER NOT NULL,
|
||||||
|
quantity INTEGER NOT NULL,
|
||||||
|
);
|
||||||
|
''');
|
||||||
|
|
||||||
|
final result = engine.analyze('''
|
||||||
|
UPDATE inventory
|
||||||
|
SET quantity = quantity - daily.amt
|
||||||
|
FROM (SELECT sum(quantity) AS amt, itemId FROM sales GROUP BY 2)
|
||||||
|
AS daily
|
||||||
|
WHERE inventory.itemId = daily.itemId;
|
||||||
|
''');
|
||||||
|
|
||||||
|
expect(result.errors, isEmpty);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,3 +26,12 @@ final Table anotherTable = Table(
|
||||||
name: 'tbl',
|
name: 'tbl',
|
||||||
resolvedColumns: [anotherId, dateTime],
|
resolvedColumns: [anotherId, dateTime],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
extension RegisterTableExtension on SqlEngine {
|
||||||
|
/// Utility function that parses a `CREATE TABLE` statement and registers the
|
||||||
|
/// created table to the engine.
|
||||||
|
void registerTableFromSql(String createTable) {
|
||||||
|
final stmt = parse(createTable).rootNode as CreateTableStatement;
|
||||||
|
registerTable(schemaReader.read(stmt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue