Parse insert statements

This commit is contained in:
Simon Binder 2019-08-29 15:04:39 +02:00
parent 6a046ec408
commit 2f8dc6d68e
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
8 changed files with 253 additions and 2 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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 [];
}

View File

@ -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');

View File

@ -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);
}
}

View File

@ -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');

View File

@ -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 = {

View File

@ -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)],
),
),
),
);
});
}