Fix tests, parse delete statements

This commit is contained in:
Simon Binder 2019-06-29 22:47:40 +02:00
parent ff530dd4ea
commit 3f0776faf8
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
10 changed files with 116 additions and 29 deletions

View File

@ -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> {
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<T> extends AstVisitor<T> {
@override
T visitSelectStatement(SelectStatement e) => visitChildren(e);
@override
T visitDeleteStatement(DeleteStatement e) => visitChildren(e);
@override
T visitUnaryExpression(UnaryExpression e) => visitChildren(e);

View File

@ -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<T>(AstVisitor<T> visitor) => visitor.visitDeleteStatement(this);
@override
Iterable<AstNode> get childNodes => [from, if (where != null) where];
@override
bool contentEquals(DeleteStatement other) => true;
}

View File

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

View File

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

View File

@ -52,6 +52,7 @@ enum TokenType {
identifier,
select,
delete,
distinct,
all,
from,
@ -94,6 +95,7 @@ const Map<String, TokenType> keywords = {
'AND': TokenType.and,
'OR': TokenType.or,
'BETWEEN': TokenType.between,
'DELETE': TokenType.delete,
'FROM': TokenType.from,
'NATURAL': TokenType.natural,
'LEFT': TokenType.leftParen,

View File

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

View File

@ -16,14 +16,16 @@ void _enforceFrom(SelectStatement stmt, List<Queryable> 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(

View File

@ -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,

View File

@ -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,

View File

@ -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,