diff --git a/sqlparser/lib/src/ast/ast.dart b/sqlparser/lib/src/ast/ast.dart index a05fe722..ccfc2429 100644 --- a/sqlparser/lib/src/ast/ast.dart +++ b/sqlparser/lib/src/ast/ast.dart @@ -52,6 +52,10 @@ abstract class AstNode with HasMetaMixin { /// all nodes. Token last; + /// Whether this ast node is synthetic, meaning that it doesn't appear in the + /// actual source. + bool synthetic; + /// The first index in the source that belongs to this node. Not set for all /// nodes. int get firstPosition => first.span.start.offset; diff --git a/sqlparser/lib/src/reader/parser/crud.dart b/sqlparser/lib/src/reader/parser/crud.dart index 5982b3eb..db599efc 100644 --- a/sqlparser/lib/src/reader/parser/crud.dart +++ b/sqlparser/lib/src/reader/parser/crud.dart @@ -674,7 +674,7 @@ mixin CrudParser on ParserBase { baseWindowName: baseWindowName, partitionBy: partitionBy, orderBy: orderBy, - frameSpec: spec ?? FrameSpec(), + frameSpec: spec ?? (FrameSpec()..synthetic = true), )..setSpan(leftParen, _previous); } diff --git a/sqlparser/lib/src/reader/parser/expressions.dart b/sqlparser/lib/src/reader/parser/expressions.dart index 51e2961d..622e2c4a 100644 --- a/sqlparser/lib/src/reader/parser/expressions.dart +++ b/sqlparser/lib/src/reader/parser/expressions.dart @@ -266,9 +266,8 @@ mixin ExpressionParser on ParserBase { final selectStmt = _fullSelect(); // returns null if there's no select if (selectStmt != null) { - final stmt = select(noCompound: true) as SelectStatement; _consume(TokenType.rightParen, 'Expected a closing bracket'); - return SubQuery(select: stmt)..setSpan(left, _previous); + return SubQuery(select: selectStmt)..setSpan(left, _previous); } else { final expr = expression(); _consume(TokenType.rightParen, 'Expected a closing bracket'); @@ -382,7 +381,8 @@ mixin ExpressionParser on ParserBase { _consume(TokenType.leftParen, 'Expected opening parenthesis for tuple'); final expressions = []; - final subQuery = _fullSelect(); + // if desired, attempt to parse select statement + final subQuery = orSubQuery ? _fullSelect() : null; if (subQuery == null) { // no sub query found. read expressions that form the tuple. // tuples can be empty `()`, so only start parsing values when it's not diff --git a/sqlparser/test/parser/create_table_test.dart b/sqlparser/test/parser/create_table_test.dart index 24925567..feac6ce0 100644 --- a/sqlparser/test/parser/create_table_test.dart +++ b/sqlparser/test/parser/create_table_test.dart @@ -1,6 +1,5 @@ import 'package:sqlparser/sqlparser.dart'; import 'package:sqlparser/src/ast/ast.dart'; -import 'package:sqlparser/src/utils/ast_equality.dart'; import 'package:test/test.dart'; import '../common_data.dart'; @@ -118,11 +117,8 @@ void main() { }); test('parses MAPPED BY constraints when in moor mode', () { - const stmt = 'CREATE TABLE a (b NOT NULL MAPPED BY `Mapper()` PRIMARY KEY)'; - final parsed = SqlEngine(useMoorExtensions: true).parse(stmt).rootNode; - - enforceEqual( - parsed, + testStatement( + 'CREATE TABLE a (b NOT NULL MAPPED BY `Mapper()` PRIMARY KEY)', CreateTableStatement(tableName: 'a', columns: [ ColumnDefinition( columnName: 'b', @@ -134,15 +130,13 @@ void main() { ], ), ]), + moorMode: true, ); }); test('parses JSON KEY constraints in moor mode', () { - const stmt = 'CREATE TABLE a (b INTEGER JSON KEY "my_json_key")'; - final parsed = SqlEngine(useMoorExtensions: true).parse(stmt).rootNode; - - enforceEqual( - parsed, + testStatement( + 'CREATE TABLE a (b INTEGER JSON KEY "my_json_key")', CreateTableStatement( tableName: 'a', columns: [ @@ -158,6 +152,7 @@ void main() { ), ], ), + moorMode: true, ); }); } diff --git a/sqlparser/test/parser/expression_test.dart b/sqlparser/test/parser/expression_test.dart index 59621e53..34b21906 100644 --- a/sqlparser/test/parser/expression_test.dart +++ b/sqlparser/test/parser/expression_test.dart @@ -146,8 +146,8 @@ void main() { final tokens = scanner.scanTokens(); final parser = Parser(tokens); final expression = parser.expression(); - enforceHasSpan(expression); + enforceHasSpan(expression); enforceEqual(expression, expected); }); }); diff --git a/sqlparser/test/parser/moor_file_test.dart b/sqlparser/test/parser/moor_file_test.dart index 5ada3236..284b1b0f 100644 --- a/sqlparser/test/parser/moor_file_test.dart +++ b/sqlparser/test/parser/moor_file_test.dart @@ -1,5 +1,4 @@ import 'package:sqlparser/sqlparser.dart'; -import 'package:sqlparser/src/utils/ast_equality.dart'; import 'package:test/test.dart'; import 'utils.dart'; @@ -19,12 +18,8 @@ all: SELECT /* COUNT(*), */ * FROM tbl WHERE $predicate; void main() { test('parses moor files', () { - final parsed = SqlEngine(useMoorExtensions: true).parseMoorFile(content); - final file = parsed.rootNode; - enforceHasSpan(file); - - enforceEqual( - file, + testMoorFile( + content, MoorFile([ ImportStatement('other.dart'), ImportStatement('another.moor'), diff --git a/sqlparser/test/parser/multiple_statements.dart b/sqlparser/test/parser/multiple_statements.dart index 0842b81b..07f5d05a 100644 --- a/sqlparser/test/parser/multiple_statements.dart +++ b/sqlparser/test/parser/multiple_statements.dart @@ -4,38 +4,35 @@ import 'package:sqlparser/src/reader/tokenizer/scanner.dart'; import 'package:sqlparser/src/utils/ast_equality.dart'; import 'package:test/test.dart'; +import 'utils.dart'; + void main() { test('can parse multiple statements', () { final sql = 'a: UPDATE tbl SET a = b; b: SELECT * FROM tbl;'; - final tokens = Scanner(sql).scanTokens(); - final moorFile = Parser(tokens).moorFile(); - final statements = moorFile.statements; - - enforceEqual( - statements[0], - DeclaredStatement( - 'a', - UpdateStatement( - table: TableReference('tbl', null), - set: [ - SetComponent( - column: Reference(columnName: 'a'), - expression: Reference(columnName: 'b'), - ), - ], + testMoorFile( + sql, + MoorFile([ + DeclaredStatement( + 'a', + UpdateStatement( + table: TableReference('tbl', null), + set: [ + SetComponent( + column: Reference(columnName: 'a'), + expression: Reference(columnName: 'b'), + ), + ], + ), ), - ), - ); - enforceEqual( - statements[1], - DeclaredStatement( - 'b', - SelectStatement( - columns: [StarResultColumn(null)], - from: [TableReference('tbl', null)], + DeclaredStatement( + 'b', + SelectStatement( + columns: [StarResultColumn(null)], + from: [TableReference('tbl', null)], + ), ), - ), + ]), ); }); diff --git a/sqlparser/test/parser/partition_test.dart b/sqlparser/test/parser/partition_test.dart index 2042295a..1806ffea 100644 --- a/sqlparser/test/parser/partition_test.dart +++ b/sqlparser/test/parser/partition_test.dart @@ -63,6 +63,8 @@ void main() { final tokens = scanner.scanTokens(); final parser = Parser(tokens); final expression = parser.expression(); + + enforceHasSpan(expression); enforceEqual(expression, expected); }); }); diff --git a/sqlparser/test/parser/select/from_test.dart b/sqlparser/test/parser/select/from_test.dart index 614792a3..a26e432c 100644 --- a/sqlparser/test/parser/select/from_test.dart +++ b/sqlparser/test/parser/select/from_test.dart @@ -6,6 +6,7 @@ import 'package:sqlparser/src/utils/ast_equality.dart'; import '../utils.dart'; void _enforceFrom(SelectStatement stmt, List expected) { + enforceHasSpan(stmt); expect(stmt.from.length, expected.length); for (var i = 0; i < stmt.from.length; i++) { diff --git a/sqlparser/test/parser/select/group_by_test.dart b/sqlparser/test/parser/select/group_by_test.dart index 44499ce7..d144a84b 100644 --- a/sqlparser/test/parser/select/group_by_test.dart +++ b/sqlparser/test/parser/select/group_by_test.dart @@ -11,6 +11,7 @@ void main() { .parse("SELECT * FROM test GROUP BY country HAVING country LIKE '%G%'") .rootNode as SelectStatement; + enforceHasSpan(stmt); return enforceEqual( stmt.groupBy, GroupBy( diff --git a/sqlparser/test/parser/select/limit_test.dart b/sqlparser/test/parser/select/limit_test.dart index 3ab80bbd..c0a463f3 100644 --- a/sqlparser/test/parser/select/limit_test.dart +++ b/sqlparser/test/parser/select/limit_test.dart @@ -12,6 +12,7 @@ void main() { .parse('SELECT * FROM test LIMIT 5 * 3') .rootNode as SelectStatement; + enforceHasSpan(select); enforceEqual( select.limit, Limit( @@ -29,6 +30,7 @@ void main() { .parse('SELECT * FROM test LIMIT 10 OFFSET 2') .rootNode as SelectStatement; + enforceHasSpan(select); enforceEqual( select.limit, Limit( @@ -46,6 +48,7 @@ void main() { .parse('SELECT * FROM test LIMIT 10, 2') .rootNode as SelectStatement; + enforceHasSpan(select); enforceEqual( select.limit, Limit( diff --git a/sqlparser/test/parser/select/order_by_test.dart b/sqlparser/test/parser/select/order_by_test.dart index cc6837ad..f5016224 100644 --- a/sqlparser/test/parser/select/order_by_test.dart +++ b/sqlparser/test/parser/select/order_by_test.dart @@ -11,6 +11,7 @@ void main() { .parse('SELECT * FROM tbl ORDER BY -a, b DESC') .rootNode as SelectStatement; + enforceHasSpan(parsed); enforceEqual( parsed.orderBy, OrderBy( diff --git a/sqlparser/test/parser/utils.dart b/sqlparser/test/parser/utils.dart index 932eeaab..1ebc12c7 100644 --- a/sqlparser/test/parser/utils.dart +++ b/sqlparser/test/parser/utils.dart @@ -18,6 +18,14 @@ IdentifierToken identifier(String content) { return IdentifierToken(false, fakeSpan(content)); } +void testMoorFile(String moorFile, MoorFile expected) { + final parsed = + SqlEngine(useMoorExtensions: true).parseMoorFile(moorFile).rootNode; + + enforceHasSpan(parsed); + enforceEqual(parsed, expected); +} + void testStatement(String sql, AstNode expected, {bool moorMode = false}) { final parsed = SqlEngine(useMoorExtensions: moorMode).parse(sql).rootNode; enforceHasSpan(parsed); @@ -38,9 +46,9 @@ void testAll(Map testCases) { /// The parser should make sure [AstNode.hasSpan] is true on relevant nodes. void enforceHasSpan(AstNode node) { - final problematic = [node] - .followedBy(node.allDescendants) - .firstWhere((node) => !node.hasSpan, orElse: () => null); + final problematic = [node].followedBy(node.allDescendants).firstWhere( + (node) => !node.hasSpan && !node.synthetic, + orElse: () => null); if (problematic != null) { throw ArgumentError('Node $problematic did not have a span');