From 3f0776faf865d83ea4202886f7803785197bed61 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sat, 29 Jun 2019 22:47:40 +0200 Subject: [PATCH] Fix tests, parse delete statements --- sqlparser/lib/src/ast/ast.dart | 5 +++ sqlparser/lib/src/ast/statements/delete.dart | 17 ++++++++ sqlparser/lib/src/reader/parser/parser.dart | 42 +++++++++++++++---- .../lib/src/reader/tokenizer/scanner.dart | 5 ++- sqlparser/lib/src/reader/tokenizer/token.dart | 2 + sqlparser/test/parser/delete_test.dart | 27 ++++++++++++ sqlparser/test/parser/select/from_test.dart | 21 ++++++---- .../test/parser/select/group_by_test.dart | 6 +-- sqlparser/test/parser/select/limit_test.dart | 15 ++++--- .../test/parser/select/order_by_test.dart | 5 ++- 10 files changed, 116 insertions(+), 29 deletions(-) create mode 100644 sqlparser/lib/src/ast/statements/delete.dart create mode 100644 sqlparser/test/parser/delete_test.dart diff --git a/sqlparser/lib/src/ast/ast.dart b/sqlparser/lib/src/ast/ast.dart index 140f9f4f..b3c354f8 100644 --- a/sqlparser/lib/src/ast/ast.dart +++ b/sqlparser/lib/src/ast/ast.dart @@ -18,6 +18,7 @@ part 'expressions/simple.dart'; part 'expressions/subquery.dart'; part 'expressions/variables.dart'; +part 'statements/delete.dart'; part 'statements/select.dart'; part 'statements/statement.dart'; @@ -122,6 +123,7 @@ abstract class AstNode { abstract class AstVisitor { T visitSelectStatement(SelectStatement e); T visitResultColumn(ResultColumn e); + T visitDeleteStatement(DeleteStatement e); T visitOrderBy(OrderBy e); T visitOrderingTerm(OrderingTerm e); @@ -204,6 +206,9 @@ class RecursiveVisitor extends AstVisitor { @override T visitSelectStatement(SelectStatement e) => visitChildren(e); + @override + T visitDeleteStatement(DeleteStatement e) => visitChildren(e); + @override T visitUnaryExpression(UnaryExpression e) => visitChildren(e); diff --git a/sqlparser/lib/src/ast/statements/delete.dart b/sqlparser/lib/src/ast/statements/delete.dart new file mode 100644 index 00000000..b841418f --- /dev/null +++ b/sqlparser/lib/src/ast/statements/delete.dart @@ -0,0 +1,17 @@ +part of '../ast.dart'; + +class DeleteStatement extends Statement { + final TableReference from; + final Expression where; + + DeleteStatement({@required this.from, this.where}); + + @override + T accept(AstVisitor visitor) => visitor.visitDeleteStatement(this); + + @override + Iterable get childNodes => [from, if (where != null) where]; + + @override + bool contentEquals(DeleteStatement other) => true; +} diff --git a/sqlparser/lib/src/reader/parser/parser.dart b/sqlparser/lib/src/reader/parser/parser.dart index 19b75c2e..69fb001e 100644 --- a/sqlparser/lib/src/reader/parser/parser.dart +++ b/sqlparser/lib/src/reader/parser/parser.dart @@ -102,7 +102,8 @@ class Parser { } Statement statement() { - final stmt = select(); + final stmt = select() ?? _deleteStmt(); + _matchOne(TokenType.semicolon); return stmt; } @@ -218,12 +219,9 @@ class Parser { TableOrSubquery _tableOrSubquery() { // this is what we're parsing: https://www.sqlite.org/syntax/table-or-subquery.html // we currently only support regular tables and nested selects - if (_matchOne(TokenType.identifier)) { - // ignore the schema name, it's not supported. Besides that, we're on the - // first branch in the diagram here - final tableName = (_previous as IdentifierToken).identifier; - final alias = _as(); - return TableReference(tableName, alias?.identifier); + final tableRef = _tableReference(); + if (tableRef != null) { + return tableRef; } else if (_matchOne(TokenType.leftParen)) { final innerStmt = select(); _consume(TokenType.rightParen, @@ -237,6 +235,17 @@ class Parser { _error('Expected a table name or a nested select statement'); } + TableReference _tableReference() { + if (_matchOne(TokenType.identifier)) { + // ignore the schema name, it's not supported. Besides that, we're on the + // first branch in the diagram here + final tableName = (_previous as IdentifierToken).identifier; + final alias = _as(); + return TableReference(tableName, alias?.identifier); + } + return null; + } + JoinClause _joinClause(TableOrSubquery start) { var operator = _parseJoinOperatorNoComma(); if (operator == null) { @@ -375,7 +384,7 @@ class Parser { /// Parses a [Limit] clause, or returns null if there is no limit token after /// the current position. Limit _limit() { - if (!_match(const [TokenType.limit])) return null; + if (!_matchOne(TokenType.limit)) return null; final count = expression(); Token offsetSep; @@ -389,6 +398,23 @@ class Parser { return Limit(count: count, offsetSeparator: offsetSep, offset: offset); } + DeleteStatement _deleteStmt() { + if (!_matchOne(TokenType.delete)) return null; + _consume(TokenType.from, 'Expected a FROM here'); + + final table = _tableReference(); + Expression where; + if (table == null) { + _error('Expected a table reference'); + } + + if (_matchOne(TokenType.where)) { + where = expression(); + } + + return DeleteStatement(from: table, where: where); + } + /* We parse expressions here. * Operators have the following precedence: * - + ~ NOT (unary) diff --git a/sqlparser/lib/src/reader/tokenizer/scanner.dart b/sqlparser/lib/src/reader/tokenizer/scanner.dart index fc74f3fd..a18c3734 100644 --- a/sqlparser/lib/src/reader/tokenizer/scanner.dart +++ b/sqlparser/lib/src/reader/tokenizer/scanner.dart @@ -141,9 +141,10 @@ class Scanner { _numeric(char); } else if (canStartColumnName(char)) { _identifier(); + } else { + errors.add(TokenizerError( + 'Unexpected character.', SourceLocation(_currentOffset))); } - errors.add(TokenizerError( - 'Unexpected character.', SourceLocation(_currentOffset))); break; } } diff --git a/sqlparser/lib/src/reader/tokenizer/token.dart b/sqlparser/lib/src/reader/tokenizer/token.dart index dafc459c..3a1d3d12 100644 --- a/sqlparser/lib/src/reader/tokenizer/token.dart +++ b/sqlparser/lib/src/reader/tokenizer/token.dart @@ -52,6 +52,7 @@ enum TokenType { identifier, select, + delete, distinct, all, from, @@ -94,6 +95,7 @@ const Map keywords = { 'AND': TokenType.and, 'OR': TokenType.or, 'BETWEEN': TokenType.between, + 'DELETE': TokenType.delete, 'FROM': TokenType.from, 'NATURAL': TokenType.natural, 'LEFT': TokenType.leftParen, diff --git a/sqlparser/test/parser/delete_test.dart b/sqlparser/test/parser/delete_test.dart new file mode 100644 index 00000000..efba7946 --- /dev/null +++ b/sqlparser/test/parser/delete_test.dart @@ -0,0 +1,27 @@ +import 'package:sqlparser/src/reader/tokenizer/token.dart'; +import 'package:test/test.dart'; +import 'package:sqlparser/sqlparser.dart'; +import 'package:sqlparser/src/utils/ast_equality.dart'; + +import 'utils.dart'; + +void main() { + test('parses delete statements', () { + final parsed = SqlEngine().parse('DELETE FROM table WHERE id = 5').rootNode; + + enforceEqual( + parsed, + DeleteStatement( + from: TableReference('table', null), + where: BinaryExpression( + Reference(columnName: 'id'), + token(TokenType.equal), + NumericLiteral( + 5, + token(TokenType.numberLiteral), + ), + ), + ), + ); + }); +} diff --git a/sqlparser/test/parser/select/from_test.dart b/sqlparser/test/parser/select/from_test.dart index 2cf6c0a6..d9111df4 100644 --- a/sqlparser/test/parser/select/from_test.dart +++ b/sqlparser/test/parser/select/from_test.dart @@ -16,14 +16,16 @@ void _enforceFrom(SelectStatement stmt, List expected) { void main() { group('from', () { test('a simple table', () { - final stmt = SqlEngine().parse('SELECT * FROM table') as SelectStatement; + final stmt = + SqlEngine().parse('SELECT * FROM table').rootNode as SelectStatement; enforceEqual(stmt.from.single, TableReference('table', null)); }); test('from more than one table', () { - final stmt = SqlEngine().parse('SELECT * FROM table AS test, table2') - as SelectStatement; + final stmt = SqlEngine() + .parse('SELECT * FROM table AS test, table2') + .rootNode as SelectStatement; _enforceFrom( stmt, @@ -35,9 +37,10 @@ void main() { }); test('from inner select statements', () { - final stmt = SqlEngine().parse( + final stmt = SqlEngine() + .parse( 'SELECT * FROM table1, (SELECT * FROM table2 WHERE a) as "inner"') - as SelectStatement; + .rootNode as SelectStatement; _enforceFrom( stmt, @@ -56,9 +59,11 @@ void main() { }); test('from a join', () { - final stmt = SqlEngine().parse('SELECT * FROM table1 ' - 'INNER JOIN table2 USING (test) ' - 'LEFT OUTER JOIN table3 ON TRUE') as SelectStatement; + final stmt = SqlEngine() + .parse('SELECT * FROM table1 ' + 'INNER JOIN table2 USING (test) ' + 'LEFT OUTER JOIN table3 ON TRUE') + .rootNode as SelectStatement; _enforceFrom(stmt, [ JoinClause( diff --git a/sqlparser/test/parser/select/group_by_test.dart b/sqlparser/test/parser/select/group_by_test.dart index fcdeed3e..a420ed1f 100644 --- a/sqlparser/test/parser/select/group_by_test.dart +++ b/sqlparser/test/parser/select/group_by_test.dart @@ -7,9 +7,9 @@ import '../utils.dart'; void main() { test('parses group by statements', () { - final stmt = SqlEngine().parse( - "SELECT * FROM test GROUP BY country HAVING country LIKE '%G%'") - as SelectStatement; + final stmt = SqlEngine() + .parse("SELECT * FROM test GROUP BY country HAVING country LIKE '%G%'") + .rootNode as SelectStatement; return enforceEqual( stmt.groupBy, diff --git a/sqlparser/test/parser/select/limit_test.dart b/sqlparser/test/parser/select/limit_test.dart index 99426b90..2ee8e4ab 100644 --- a/sqlparser/test/parser/select/limit_test.dart +++ b/sqlparser/test/parser/select/limit_test.dart @@ -8,8 +8,9 @@ import '../utils.dart'; void main() { group('limit clauses', () { test('with just a limit', () { - final select = SqlEngine().parse('SELECT * FROM test LIMIT 5 * 3') - as SelectStatement; + final select = SqlEngine() + .parse('SELECT * FROM test LIMIT 5 * 3') + .rootNode as SelectStatement; enforceEqual( select.limit, @@ -24,8 +25,9 @@ void main() { }); test('with offset', () { - final select = SqlEngine().parse('SELECT * FROM test LIMIT 10 OFFSET 2') - as SelectStatement; + final select = SqlEngine() + .parse('SELECT * FROM test LIMIT 10 OFFSET 2') + .rootNode as SelectStatement; enforceEqual( select.limit, @@ -38,8 +40,9 @@ void main() { }); test('with offset as comma', () { - final select = SqlEngine().parse('SELECT * FROM test LIMIT 10, 2') - as SelectStatement; + final select = SqlEngine() + .parse('SELECT * FROM test LIMIT 10, 2') + .rootNode as SelectStatement; enforceEqual( select.limit, diff --git a/sqlparser/test/parser/select/order_by_test.dart b/sqlparser/test/parser/select/order_by_test.dart index e0830858..6dae2145 100644 --- a/sqlparser/test/parser/select/order_by_test.dart +++ b/sqlparser/test/parser/select/order_by_test.dart @@ -7,8 +7,9 @@ import '../utils.dart'; void main() { test('parses order by clauses', () { - final parsed = SqlEngine().parse('SELECT * FROM table ORDER BY -a, b DESC') - as SelectStatement; + final parsed = SqlEngine() + .parse('SELECT * FROM table ORDER BY -a, b DESC') + .rootNode as SelectStatement; enforceEqual( parsed.orderBy,