mirror of https://github.com/AMT-Cheif/drift.git
Parse insert statements
This commit is contained in:
parent
6a046ec408
commit
2f8dc6d68e
|
@ -8,6 +8,10 @@ class ReferenceResolver extends RecursiveVisitor<void> {
|
|||
|
||||
@override
|
||||
void visitReference(Reference e) {
|
||||
if (e.resolved != null) {
|
||||
return super.visitReference(e);
|
||||
}
|
||||
|
||||
final scope = e.scope;
|
||||
|
||||
if (e.tableName != null) {
|
||||
|
@ -65,7 +69,7 @@ class ReferenceResolver extends RecursiveVisitor<void> {
|
|||
|
||||
@override
|
||||
void visitAggregateExpression(AggregateExpression e) {
|
||||
if (e.windowName != null) {
|
||||
if (e.windowName != null && e.resolved != null) {
|
||||
final resolved = e.scope.resolve<NamedWindowDeclaration>(e.windowName);
|
||||
e.resolved = resolved;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ part 'schema/table_definition.dart';
|
|||
|
||||
part 'statements/create_table.dart';
|
||||
part 'statements/delete.dart';
|
||||
part 'statements/insert.dart';
|
||||
part 'statements/select.dart';
|
||||
part 'statements/statement.dart';
|
||||
part 'statements/update.dart';
|
||||
|
@ -133,6 +134,7 @@ abstract class AstNode {
|
|||
abstract class AstVisitor<T> {
|
||||
T visitSelectStatement(SelectStatement e);
|
||||
T visitResultColumn(ResultColumn e);
|
||||
T visitInsertStatement(InsertStatement e);
|
||||
T visitDeleteStatement(DeleteStatement e);
|
||||
T visitUpdateStatement(UpdateStatement e);
|
||||
T visitCreateTableStatement(CreateTableStatement e);
|
||||
|
@ -248,6 +250,9 @@ class RecursiveVisitor<T> extends AstVisitor<T> {
|
|||
@override
|
||||
T visitSelectStatement(SelectStatement e) => visitChildren(e);
|
||||
|
||||
@override
|
||||
T visitInsertStatement(InsertStatement e) => visitChildren(e);
|
||||
|
||||
@override
|
||||
T visitDeleteStatement(DeleteStatement e) => visitChildren(e);
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
part of '../ast.dart';
|
||||
|
||||
enum InsertMode {
|
||||
insert,
|
||||
replace,
|
||||
insertOrReplace,
|
||||
insertOrRollback,
|
||||
insertOrAbort,
|
||||
insertOrFail,
|
||||
insertOrIgnore
|
||||
}
|
||||
|
||||
class InsertStatement extends Statement with CrudStatement {
|
||||
final InsertMode mode;
|
||||
final TableReference table;
|
||||
final List<Reference> targetColumns;
|
||||
final InsertSource source;
|
||||
|
||||
// todo parse upsert clauses
|
||||
|
||||
InsertStatement(
|
||||
{this.mode = InsertMode.insert,
|
||||
@required this.table,
|
||||
@required this.targetColumns,
|
||||
@required this.source});
|
||||
|
||||
@override
|
||||
T accept<T>(AstVisitor<T> visitor) => visitor.visitInsertStatement(this);
|
||||
|
||||
@override
|
||||
Iterable<AstNode> get childNodes sync* {
|
||||
yield table;
|
||||
yield* targetColumns;
|
||||
yield* source.childNodes;
|
||||
}
|
||||
|
||||
@override
|
||||
bool contentEquals(InsertStatement other) {
|
||||
return other.mode == mode && other.source.runtimeType == source.runtimeType;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class InsertSource {
|
||||
Iterable<AstNode> get childNodes;
|
||||
|
||||
const InsertSource();
|
||||
|
||||
T when<T>(
|
||||
{T Function(ValuesSource) isValues,
|
||||
T Function(SelectInsertSource) isSelect,
|
||||
T Function(DefaultValues) isDefaults}) {
|
||||
if (this is ValuesSource) {
|
||||
return isValues?.call(this as ValuesSource);
|
||||
} else if (this is SelectInsertSource) {
|
||||
return isSelect?.call(this as SelectInsertSource);
|
||||
} else if (this is DefaultValues) {
|
||||
return isDefaults?.call(this as DefaultValues);
|
||||
} else {
|
||||
throw StateError('Did not expect $runtimeType as InsertSource');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses a list of values for an insert statement (`VALUES (a, b, c)`).
|
||||
class ValuesSource extends InsertSource {
|
||||
final List<TupleExpression> values;
|
||||
|
||||
ValuesSource(this.values);
|
||||
|
||||
@override
|
||||
Iterable<AstNode> get childNodes => values;
|
||||
}
|
||||
|
||||
/// Inserts the rows returned by [stmt].
|
||||
class SelectInsertSource extends InsertSource {
|
||||
final SelectStatement stmt;
|
||||
|
||||
SelectInsertSource(this.stmt);
|
||||
|
||||
@override
|
||||
Iterable<AstNode> get childNodes => [stmt];
|
||||
}
|
||||
|
||||
/// Use `DEFAULT VALUES` for an insert statement.
|
||||
class DefaultValues extends InsertSource {
|
||||
const DefaultValues();
|
||||
|
||||
@override
|
||||
final Iterable<AstNode> childNodes = const [];
|
||||
}
|
|
@ -368,6 +368,75 @@ mixin CrudParser on ParserBase {
|
|||
or: failureMode, table: table, set: set, where: where);
|
||||
}
|
||||
|
||||
InsertStatement _insertStmt() {
|
||||
if (!_match(const [TokenType.insert, TokenType.replace])) return null;
|
||||
|
||||
final firstToken = _previous;
|
||||
InsertMode insertMode;
|
||||
if (_previous.type == TokenType.insert) {
|
||||
// insert modes can have a failure clause (INSERT OR xxx)
|
||||
if (_matchOne(TokenType.or)) {
|
||||
const tokensToModes = {
|
||||
TokenType.replace: InsertMode.insertOrReplace,
|
||||
TokenType.rollback: InsertMode.insertOrRollback,
|
||||
TokenType.abort: InsertMode.insertOrAbort,
|
||||
TokenType.fail: InsertMode.insertOrFail,
|
||||
TokenType.ignore: InsertMode.insertOrIgnore
|
||||
};
|
||||
|
||||
if (_match(tokensToModes.keys)) {
|
||||
insertMode = tokensToModes[_previous.type];
|
||||
} else {
|
||||
_error(
|
||||
'After the INSERT OR, expected an insert mode (REPLACE, ROLLBACK, etc.)');
|
||||
}
|
||||
} else {
|
||||
insertMode = InsertMode.insert;
|
||||
}
|
||||
} else {
|
||||
// is it wasn't an insert, it must have been a replace
|
||||
insertMode = InsertMode.replace;
|
||||
}
|
||||
assert(insertMode != null);
|
||||
_consume(TokenType.into, 'Expected INSERT INTO');
|
||||
|
||||
final table = _tableReference();
|
||||
final targetColumns = <Reference>[];
|
||||
|
||||
if (_matchOne(TokenType.leftParen)) {
|
||||
do {
|
||||
final columnRef = _consumeIdentifier('Expected a column');
|
||||
targetColumns.add(Reference(columnName: columnRef.identifier));
|
||||
} while (_matchOne(TokenType.comma));
|
||||
|
||||
_consume(TokenType.rightParen,
|
||||
'Expected clpsing parenthesis after column list');
|
||||
}
|
||||
final source = _insertSource();
|
||||
|
||||
return InsertStatement(
|
||||
mode: insertMode,
|
||||
table: table,
|
||||
targetColumns: targetColumns,
|
||||
source: source,
|
||||
)..setSpan(firstToken, _previous);
|
||||
}
|
||||
|
||||
InsertSource _insertSource() {
|
||||
if (_matchOne(TokenType.$values)) {
|
||||
final values = <TupleExpression>[];
|
||||
do {
|
||||
values.add(_consumeTuple());
|
||||
} while (_matchOne(TokenType.comma));
|
||||
return ValuesSource(values);
|
||||
} else if (_matchOne(TokenType.$default)) {
|
||||
_consume(TokenType.$values, 'Expected DEFAULT VALUES');
|
||||
return const DefaultValues();
|
||||
} else {
|
||||
return SelectInsertSource(select());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
WindowDefinition _windowDefinition() {
|
||||
_consume(TokenType.leftParen, 'Expected opening parenthesis');
|
||||
|
|
|
@ -391,4 +391,19 @@ mixin ExpressionParser on ParserBase {
|
|||
windowName: windowName,
|
||||
)..setSpan(name, _previous);
|
||||
}
|
||||
|
||||
TupleExpression _consumeTuple() {
|
||||
final firstToken =
|
||||
_consume(TokenType.leftParen, 'Expected opening parenthesis for tuple');
|
||||
final expressions = <Expression>[];
|
||||
|
||||
do {
|
||||
expressions.add(expression());
|
||||
} while (_matchOne(TokenType.comma));
|
||||
|
||||
_consume(TokenType.rightParen, 'Expected right parenthesis to close tuple');
|
||||
|
||||
return TupleExpression(expressions: expressions)
|
||||
..setSpan(firstToken, _previous);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,6 +129,7 @@ abstract class ParserBase {
|
|||
|
||||
// Common operations that we are referenced very often
|
||||
Expression expression();
|
||||
TupleExpression _consumeTuple();
|
||||
|
||||
/// Parses a [SelectStatement], or returns null if there is no select token
|
||||
/// after the current position.
|
||||
|
@ -153,7 +154,11 @@ class Parser extends ParserBase
|
|||
|
||||
Statement statement({bool expectEnd = true}) {
|
||||
final first = _peek;
|
||||
final stmt = select() ?? _deleteStmt() ?? _update() ?? _createTable();
|
||||
final stmt = select() ??
|
||||
_deleteStmt() ??
|
||||
_update() ??
|
||||
_insertStmt() ??
|
||||
_createTable();
|
||||
|
||||
if (stmt == null) {
|
||||
_error('Expected a sql statement to start here');
|
||||
|
|
|
@ -57,6 +57,8 @@ enum TokenType {
|
|||
select,
|
||||
delete,
|
||||
update,
|
||||
insert,
|
||||
into,
|
||||
distinct,
|
||||
all,
|
||||
from,
|
||||
|
@ -124,6 +126,7 @@ enum TokenType {
|
|||
unique,
|
||||
check,
|
||||
$default,
|
||||
$values,
|
||||
conflict,
|
||||
references,
|
||||
cascade,
|
||||
|
@ -141,6 +144,8 @@ enum TokenType {
|
|||
|
||||
const Map<String, TokenType> keywords = {
|
||||
'SELECT': TokenType.select,
|
||||
'INSERT': TokenType.insert,
|
||||
'INTO': TokenType.into,
|
||||
'COLLATE': TokenType.collate,
|
||||
'DISTINCT': TokenType.distinct,
|
||||
'UPDATE': TokenType.update,
|
||||
|
@ -228,6 +233,7 @@ const Map<String, TokenType> keywords = {
|
|||
'OTHERS': TokenType.others,
|
||||
'TIES': TokenType.ties,
|
||||
'WINDOW': TokenType.window,
|
||||
'VALUES': TokenType.$values,
|
||||
};
|
||||
|
||||
const Map<String, TokenType> moorKeywords = {
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:sqlparser/sqlparser.dart';
|
||||
|
||||
import 'utils.dart';
|
||||
|
||||
void main() {
|
||||
test('parses insert statements', () {
|
||||
testStatement(
|
||||
'INSERT OR REPLACE INTO tbl (a, b, c) VALUES (d, e, f)',
|
||||
InsertStatement(
|
||||
mode: InsertMode.insertOrReplace,
|
||||
table: TableReference('tbl', null),
|
||||
targetColumns: [
|
||||
Reference(columnName: 'a'),
|
||||
Reference(columnName: 'b'),
|
||||
Reference(columnName: 'c'),
|
||||
],
|
||||
source: ValuesSource([
|
||||
TupleExpression(expressions: [
|
||||
Reference(columnName: 'd'),
|
||||
Reference(columnName: 'e'),
|
||||
Reference(columnName: 'f'),
|
||||
]),
|
||||
]),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('insert statement with default values', () {
|
||||
testStatement(
|
||||
'INSERT INTO tbl DEFAULT VALUES',
|
||||
InsertStatement(
|
||||
mode: InsertMode.insert,
|
||||
table: TableReference('tbl', null),
|
||||
targetColumns: const [],
|
||||
source: const DefaultValues(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('insert statement with select as source', () {
|
||||
testStatement(
|
||||
'REPLACE INTO tbl SELECT * FROM tbl',
|
||||
InsertStatement(
|
||||
mode: InsertMode.replace,
|
||||
table: TableReference('tbl', null),
|
||||
targetColumns: const [],
|
||||
source: SelectInsertSource(
|
||||
SelectStatement(
|
||||
columns: [StarResultColumn(null)],
|
||||
from: [TableReference('tbl', null)],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue