diff --git a/sqlparser/lib/src/ast/ast.dart b/sqlparser/lib/src/ast/ast.dart index f6732730..869a5d43 100644 --- a/sqlparser/lib/src/ast/ast.dart +++ b/sqlparser/lib/src/ast/ast.dart @@ -27,6 +27,7 @@ part 'moor/declared_statement.dart'; part 'moor/import_statement.dart'; part 'moor/inline_dart.dart'; part 'moor/moor_file.dart'; +part 'moor/nested_star_result_column.dart'; part 'schema/column_definition.dart'; part 'schema/table_definition.dart'; part 'statements/block.dart'; diff --git a/sqlparser/lib/src/ast/moor/nested_star_result_column.dart b/sqlparser/lib/src/ast/moor/nested_star_result_column.dart new file mode 100644 index 00000000..d73a02a0 --- /dev/null +++ b/sqlparser/lib/src/ast/moor/nested_star_result_column.dart @@ -0,0 +1,10 @@ +part of '../ast.dart'; + +/// A nested star result column, denoted by `**` in user queries. +/// +/// Nested star result columns behave similar to a regular [StarResultColumn] +/// when the query is actually run. However, they will affect generated code +/// when using moor. +class NestedStarResultColumn extends StarResultColumn { + NestedStarResultColumn(String tableName) : super(tableName); +} diff --git a/sqlparser/lib/src/reader/parser/crud.dart b/sqlparser/lib/src/reader/parser/crud.dart index b25e9db2..8969d93d 100644 --- a/sqlparser/lib/src/reader/parser/crud.dart +++ b/sqlparser/lib/src/reader/parser/crud.dart @@ -209,11 +209,16 @@ mixin CrudParser on ParserBase { // we have a star result column. If it's followed by anything else, it can // still refer to a column in a table as part of a expression // result column - final identifier = _previous; + final identifier = _previous as IdentifierToken; - if (_match(const [TokenType.dot]) && _match(const [TokenType.star])) { - return StarResultColumn((identifier as IdentifierToken).identifier) - ..setSpan(identifier, _previous); + if (_matchOne(TokenType.dot)) { + if (_matchOne(TokenType.star)) { + return StarResultColumn(identifier.identifier) + ..setSpan(identifier, _previous); + } else if (enableMoorExtensions && _matchOne(TokenType.doubleStar)) { + return NestedStarResultColumn(identifier.identifier) + ..setSpan(identifier, _previous); + } } // not a star result column. go back and parse the expression. diff --git a/sqlparser/lib/src/reader/tokenizer/scanner.dart b/sqlparser/lib/src/reader/tokenizer/scanner.dart index 66accd6d..eaabebd3 100644 --- a/sqlparser/lib/src/reader/tokenizer/scanner.dart +++ b/sqlparser/lib/src/reader/tokenizer/scanner.dart @@ -78,6 +78,9 @@ class Scanner { } break; case charStar: + if (scanMoorTokens && _match(charStar)) { + _addToken(TokenType.doubleStar); + } _addToken(TokenType.star); break; case charSlash: diff --git a/sqlparser/lib/src/reader/tokenizer/token.dart b/sqlparser/lib/src/reader/tokenizer/token.dart index b74065b0..e86b8765 100644 --- a/sqlparser/lib/src/reader/tokenizer/token.dart +++ b/sqlparser/lib/src/reader/tokenizer/token.dart @@ -9,6 +9,9 @@ enum TokenType { $do, doublePipe, star, + + /// A `**` token. This is only scanned when scanning for moor tokens. + doubleStar, slash, percent, plus, diff --git a/sqlparser/test/parser/moor_file_test.dart b/sqlparser/test/parser/moor_file_test.dart index 22a240e7..4f200d6e 100644 --- a/sqlparser/test/parser/moor_file_test.dart +++ b/sqlparser/test/parser/moor_file_test.dart @@ -16,6 +16,7 @@ CREATE TABLE tbl ( all: SELECT /* COUNT(*), */ * FROM tbl WHERE $predicate; @special: SELECT * FROM tbl; typeHints(:foo AS TEXT): SELECT :foo; +nested: SELECT foo.** FROM tbl foo; '''; void main() { @@ -87,6 +88,13 @@ void main() { ) ], ), + DeclaredStatement( + SimpleName('nested'), + SelectStatement( + columns: [NestedStarResultColumn('foo')], + from: TableReference('tbl', 'foo'), + ), + ), ]), ); }); diff --git a/sqlparser/test/scanner/moor_tokens_test.dart b/sqlparser/test/scanner/moor_tokens_test.dart index 7f121549..a463cce5 100644 --- a/sqlparser/test/scanner/moor_tokens_test.dart +++ b/sqlparser/test/scanner/moor_tokens_test.dart @@ -4,7 +4,7 @@ import 'package:sqlparser/src/reader/tokenizer/token.dart'; void main() { test('parses moor specific tokens', () { - const part = 'c INTEGER MAPPED BY `const Mapper()` NOT NULL'; + const part = 'c INTEGER MAPPED BY `const Mapper()` NOT NULL **'; final scanner = Scanner(part, scanMoorTokens: true); final tokens = scanner.scanTokens(); @@ -17,6 +17,7 @@ void main() { TokenType.inlineDart, // `const Mapper()` TokenType.not, TokenType.$null, + TokenType.doubleStar, TokenType.eof, ]); diff --git a/sqlparser/test/scanner/scanner_test.dart b/sqlparser/test/scanner/scanner_test.dart new file mode 100644 index 00000000..02b3f219 --- /dev/null +++ b/sqlparser/test/scanner/scanner_test.dart @@ -0,0 +1,11 @@ +import 'package:sqlparser/sqlparser.dart'; +import 'package:sqlparser/src/reader/tokenizer/scanner.dart'; +import 'package:test/test.dart'; + +void main() { + test('parses ** as two tokens when not using moor mode', () { + final tokens = Scanner('**').scanTokens(); + expect(tokens.map((e) => e.type), + containsAllInOrder([TokenType.star, TokenType.star])); + }); +}