diff --git a/parser/parser_column.go b/parser/parser_column.go index ef96cccf..a48753c0 100644 --- a/parser/parser_column.go +++ b/parser/parser_column.go @@ -378,46 +378,100 @@ func (p *Parser) peekKeyword(keyword string) bool { return token.Kind == TokenKindKeyword && strings.EqualFold(token.String, keyword) } +// clauseStarterKeywords lists the keywords that begin a clause following the +// SELECT item list. Single source of truth used by both the terminator check +// (current-token) and the lookahead check (peek), so they cannot drift. +var clauseStarterKeywords = []string{ + KeywordFrom, KeywordWhere, KeywordPrewhere, KeywordGroup, + KeywordHaving, KeywordWindow, KeywordOrder, KeywordLimit, + KeywordOffset, KeywordSettings, KeywordFormat, KeywordUnion, + KeywordExcept, +} + +// matchClauseStarterKeyword reports whether the current token is one of the +// clause-starter keywords. +func (p *Parser) matchClauseStarterKeyword() bool { + for _, kw := range clauseStarterKeywords { + if p.matchKeyword(kw) { + return true + } + } + return false +} + +// peekIsClauseStarterKeyword reports whether the next token is one of the +// clause-starter keywords. +func (p *Parser) peekIsClauseStarterKeyword() bool { + for _, kw := range clauseStarterKeywords { + if p.peekKeyword(kw) { + return true + } + } + return false +} + +// peekIsEndOfStatement reports whether the next token is EOF or `;`. +func (p *Parser) peekIsEndOfStatement() bool { + next, err := p.lexer.peekToken() + if err != nil || next == nil { + return true + } + return next.Kind == ";" +} + +// keywordIsSelectItemIdentifier reports whether the current keyword token is +// being used as a bare column-reference identifier inside a SELECT projection +// rather than starting a clause/expression. This is true when the next token +// is `,`, `AS`, or another clause-starter keyword — any of which prove the +// current keyword cannot legally begin a clause or expression (clause/expr +// starters always require a value/expression next, never another +// clause-starter or a list separator). +// +// ClickHouse server accepts essentially every reserved word as a bare column +// name in a projection; matching that behavior here lets the parser handle +// queries like `SELECT a, limit FROM t` or `SELECT a, from, b FROM t` +// without backtick escaping. Backticked identifiers are tokenized as +// TokenKindIdent (not TokenKindKeyword), so trailing-comma handling for +// keyword-named tables — e.g. `SELECT count(*), FROM `limit`` — is preserved. +// +// End-of-statement (EOF or `;`) is intentionally NOT included here. It's a +// valid disambiguator only in expression position (the current keyword IS +// the projection expression), not in terminator/alias position (where a +// trailing clause-starter keyword like `FROM` at EOF must still be treated +// as a terminator, not a no-AS alias). parseColumnExpr applies the eos +// disambiguator inline. +func (p *Parser) keywordIsSelectItemIdentifier() bool { + if !p.matchTokenKind(TokenKindKeyword) { + return false + } + return p.peekTokenKind(TokenKindComma) || + p.peekKeyword(KeywordAs) || + p.peekIsClauseStarterKeyword() +} + // isSelectItemTerminatorKeyword checks whether the current token is a keyword -// that begins a clause following the SELECT item list. When true, we should not -// treat the keyword itself as a bare alias. +// that begins a clause following the SELECT item list. When true, we should +// not treat the keyword itself as a bare alias. func (p *Parser) isSelectItemTerminatorKeyword() bool { - switch { - case p.matchKeyword(KeywordFrom): - return true - case p.matchKeyword(KeywordWhere): - return true - case p.matchKeyword(KeywordPrewhere): - return true - case p.matchKeyword(KeywordGroup): - return true - case p.matchKeyword(KeywordHaving): - return true - case p.matchKeyword(KeywordWindow): - return true - case p.matchKeyword(KeywordOrder): - return true - case p.matchKeyword(KeywordLimit): - return true - case p.matchKeyword(KeywordOffset): - return true - case p.matchKeyword(KeywordSettings): - return true - case p.matchKeyword(KeywordFormat): - return true - case p.matchKeyword(KeywordUnion): - return true - case p.matchKeyword(KeywordExcept): - return true - default: + if p.keywordIsSelectItemIdentifier() { return false } + return p.matchClauseStarterKeyword() } func (p *Parser) parseColumnExpr(pos Pos) (Expr, error) { //nolint:funlen - // Should parse the keyword as an identifier if the keyword is followed by one of comma, `AS`. - // For example: `SELECT 1 as interval GROUP BY interval` is a valid syntax in ClickHouse. - if p.matchTokenKind(TokenKindKeyword) && (p.peekTokenKind(TokenKindComma) || p.peekKeyword(KeywordAs)) { + // Should parse the keyword as an identifier if the keyword is followed by + // `,`, `AS`, another clause-starter keyword, or end-of-statement (EOF or + // `;`). ClickHouse accepts most reserved words as bare column names in + // projections (e.g. `SELECT 1 AS interval GROUP BY interval`, + // `SELECT a, case FROM t`, `SELECT case`); a clause/expression starter + // always requires a value/expression next, so the lookahead unambiguously + // identifies these as identifier uses. The end-of-statement disambiguator + // is only valid in expression position, so it's applied inline here + // rather than in keywordIsSelectItemIdentifier (which is shared with the + // terminator/alias check). + if p.keywordIsSelectItemIdentifier() || + (p.matchTokenKind(TokenKindKeyword) && p.peekIsEndOfStatement()) { return p.parseIdent() } switch { @@ -823,6 +877,10 @@ func (p *Parser) parseSelectItem() (*SelectItem, error) { var alias *Ident switch { case p.tryConsumeKeywords(KeywordAs): + // `SELECT 1 AS ` works for any keyword because + // matchTokenKind(TokenKindIdent) coerces TokenKindKeyword to ident + // (see parser_common.go matchTokenKind), so parseIdent accepts a + // keyword token here without needing a special-case. alias, err = p.parseIdent() if err != nil { return nil, err diff --git a/parser/testdata/query/format/beautify/select_clause_keyword_as_column.sql b/parser/testdata/query/format/beautify/select_clause_keyword_as_column.sql new file mode 100644 index 00000000..9a6f78bd --- /dev/null +++ b/parser/testdata/query/format/beautify/select_clause_keyword_as_column.sql @@ -0,0 +1,14 @@ +-- Origin SQL: +SELECT id, from, to, where, group, order FROM transfers + + +-- Beautify SQL: +SELECT + id, + from, + to, + where, + group, + order +FROM + transfers; diff --git a/parser/testdata/query/format/beautify/select_clause_keyword_as_only_column.sql b/parser/testdata/query/format/beautify/select_clause_keyword_as_only_column.sql new file mode 100644 index 00000000..f99ea66f --- /dev/null +++ b/parser/testdata/query/format/beautify/select_clause_keyword_as_only_column.sql @@ -0,0 +1,7 @@ +-- Origin SQL: +SELECT limit + + +-- Beautify SQL: +SELECT + limit; diff --git a/parser/testdata/query/format/beautify/select_expr_keyword_as_column.sql b/parser/testdata/query/format/beautify/select_expr_keyword_as_column.sql new file mode 100644 index 00000000..c8151122 --- /dev/null +++ b/parser/testdata/query/format/beautify/select_expr_keyword_as_column.sql @@ -0,0 +1,13 @@ +-- Origin SQL: +SELECT id, case, cast, extract, interval FROM events + + +-- Beautify SQL: +SELECT + id, + case, + cast, + extract, + interval +FROM + events; diff --git a/parser/testdata/query/format/beautify/select_keyword_as_alias.sql b/parser/testdata/query/format/beautify/select_keyword_as_alias.sql new file mode 100644 index 00000000..92d7c960 --- /dev/null +++ b/parser/testdata/query/format/beautify/select_keyword_as_alias.sql @@ -0,0 +1,9 @@ +-- Origin SQL: +SELECT 1 AS interval, 2 AS from, 3 AS limit + + +-- Beautify SQL: +SELECT + 1 AS interval, + 2 AS from, + 3 AS limit; diff --git a/parser/testdata/query/format/beautify/select_keyword_as_column.sql b/parser/testdata/query/format/beautify/select_keyword_as_column.sql new file mode 100644 index 00000000..6bc8b17e --- /dev/null +++ b/parser/testdata/query/format/beautify/select_keyword_as_column.sql @@ -0,0 +1,15 @@ +-- Origin SQL: +SELECT cloud, account, region, name, usage, limit, usage_percentage FROM service_quotas + + +-- Beautify SQL: +SELECT + cloud, + account, + region, + name, + usage, + limit, + usage_percentage +FROM + service_quotas; diff --git a/parser/testdata/query/format/beautify/select_keyword_as_last_column.sql b/parser/testdata/query/format/beautify/select_keyword_as_last_column.sql new file mode 100644 index 00000000..488dcddd --- /dev/null +++ b/parser/testdata/query/format/beautify/select_keyword_as_last_column.sql @@ -0,0 +1,10 @@ +-- Origin SQL: +SELECT a, limit FROM t + + +-- Beautify SQL: +SELECT + a, + limit +FROM + t; diff --git a/parser/testdata/query/format/beautify/select_keyword_as_only_column.sql b/parser/testdata/query/format/beautify/select_keyword_as_only_column.sql new file mode 100644 index 00000000..24feb1ce --- /dev/null +++ b/parser/testdata/query/format/beautify/select_keyword_as_only_column.sql @@ -0,0 +1,7 @@ +-- Origin SQL: +SELECT interval + + +-- Beautify SQL: +SELECT + interval; diff --git a/parser/testdata/query/format/beautify/select_keyword_as_only_column_semicolon.sql b/parser/testdata/query/format/beautify/select_keyword_as_only_column_semicolon.sql new file mode 100644 index 00000000..8f346e59 --- /dev/null +++ b/parser/testdata/query/format/beautify/select_keyword_as_only_column_semicolon.sql @@ -0,0 +1,7 @@ +-- Origin SQL: +SELECT case; + + +-- Beautify SQL: +SELECT + case; diff --git a/parser/testdata/query/format/beautify/select_trailing_comma_before_from_keyword_table.sql b/parser/testdata/query/format/beautify/select_trailing_comma_before_from_keyword_table.sql new file mode 100644 index 00000000..907f3997 --- /dev/null +++ b/parser/testdata/query/format/beautify/select_trailing_comma_before_from_keyword_table.sql @@ -0,0 +1,9 @@ +-- Origin SQL: +SELECT count(x), FROM `limit` + + +-- Beautify SQL: +SELECT + count(x) +FROM + `limit`; diff --git a/parser/testdata/query/format/select_clause_keyword_as_column.sql b/parser/testdata/query/format/select_clause_keyword_as_column.sql new file mode 100644 index 00000000..4c05484e --- /dev/null +++ b/parser/testdata/query/format/select_clause_keyword_as_column.sql @@ -0,0 +1,6 @@ +-- Origin SQL: +SELECT id, from, to, where, group, order FROM transfers + + +-- Format SQL: +SELECT id, from, to, where, group, order FROM transfers; diff --git a/parser/testdata/query/format/select_clause_keyword_as_only_column.sql b/parser/testdata/query/format/select_clause_keyword_as_only_column.sql new file mode 100644 index 00000000..e6cf3a1e --- /dev/null +++ b/parser/testdata/query/format/select_clause_keyword_as_only_column.sql @@ -0,0 +1,6 @@ +-- Origin SQL: +SELECT limit + + +-- Format SQL: +SELECT limit; diff --git a/parser/testdata/query/format/select_expr_keyword_as_column.sql b/parser/testdata/query/format/select_expr_keyword_as_column.sql new file mode 100644 index 00000000..06c1078c --- /dev/null +++ b/parser/testdata/query/format/select_expr_keyword_as_column.sql @@ -0,0 +1,6 @@ +-- Origin SQL: +SELECT id, case, cast, extract, interval FROM events + + +-- Format SQL: +SELECT id, case, cast, extract, interval FROM events; diff --git a/parser/testdata/query/format/select_keyword_as_alias.sql b/parser/testdata/query/format/select_keyword_as_alias.sql new file mode 100644 index 00000000..e42c284c --- /dev/null +++ b/parser/testdata/query/format/select_keyword_as_alias.sql @@ -0,0 +1,6 @@ +-- Origin SQL: +SELECT 1 AS interval, 2 AS from, 3 AS limit + + +-- Format SQL: +SELECT 1 AS interval, 2 AS from, 3 AS limit; diff --git a/parser/testdata/query/format/select_keyword_as_column.sql b/parser/testdata/query/format/select_keyword_as_column.sql new file mode 100644 index 00000000..614ce459 --- /dev/null +++ b/parser/testdata/query/format/select_keyword_as_column.sql @@ -0,0 +1,6 @@ +-- Origin SQL: +SELECT cloud, account, region, name, usage, limit, usage_percentage FROM service_quotas + + +-- Format SQL: +SELECT cloud, account, region, name, usage, limit, usage_percentage FROM service_quotas; diff --git a/parser/testdata/query/format/select_keyword_as_last_column.sql b/parser/testdata/query/format/select_keyword_as_last_column.sql new file mode 100644 index 00000000..e9177ff3 --- /dev/null +++ b/parser/testdata/query/format/select_keyword_as_last_column.sql @@ -0,0 +1,6 @@ +-- Origin SQL: +SELECT a, limit FROM t + + +-- Format SQL: +SELECT a, limit FROM t; diff --git a/parser/testdata/query/format/select_keyword_as_only_column.sql b/parser/testdata/query/format/select_keyword_as_only_column.sql new file mode 100644 index 00000000..ef75da6f --- /dev/null +++ b/parser/testdata/query/format/select_keyword_as_only_column.sql @@ -0,0 +1,6 @@ +-- Origin SQL: +SELECT interval + + +-- Format SQL: +SELECT interval; diff --git a/parser/testdata/query/format/select_keyword_as_only_column_semicolon.sql b/parser/testdata/query/format/select_keyword_as_only_column_semicolon.sql new file mode 100644 index 00000000..387116bb --- /dev/null +++ b/parser/testdata/query/format/select_keyword_as_only_column_semicolon.sql @@ -0,0 +1,6 @@ +-- Origin SQL: +SELECT case; + + +-- Format SQL: +SELECT case; diff --git a/parser/testdata/query/format/select_trailing_comma_before_from_keyword_table.sql b/parser/testdata/query/format/select_trailing_comma_before_from_keyword_table.sql new file mode 100644 index 00000000..d4bb3d7b --- /dev/null +++ b/parser/testdata/query/format/select_trailing_comma_before_from_keyword_table.sql @@ -0,0 +1,6 @@ +-- Origin SQL: +SELECT count(x), FROM `limit` + + +-- Format SQL: +SELECT count(x) FROM `limit`; diff --git a/parser/testdata/query/output/select_clause_keyword_as_column.sql.golden.json b/parser/testdata/query/output/select_clause_keyword_as_column.sql.golden.json new file mode 100644 index 00000000..4d585fdf --- /dev/null +++ b/parser/testdata/query/output/select_clause_keyword_as_column.sql.golden.json @@ -0,0 +1,109 @@ +[ + { + "SelectPos": 0, + "StatementEnd": 55, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": "id", + "QuoteType": 1, + "NamePos": 7, + "NameEnd": 9 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "from", + "QuoteType": 1, + "NamePos": 11, + "NameEnd": 15 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "to", + "QuoteType": 1, + "NamePos": 17, + "NameEnd": 19 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "where", + "QuoteType": 1, + "NamePos": 21, + "NameEnd": 26 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "group", + "QuoteType": 1, + "NamePos": 28, + "NameEnd": 33 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "order", + "QuoteType": 1, + "NamePos": 35, + "NameEnd": 40 + }, + "Modifiers": [], + "Alias": null + } + ], + "From": { + "FromPos": 41, + "Expr": { + "Table": { + "TablePos": 46, + "TableEnd": 55, + "Alias": null, + "Expr": { + "Database": null, + "Table": { + "Name": "transfers", + "QuoteType": 1, + "NamePos": 46, + "NameEnd": 55 + } + }, + "HasFinal": false + }, + "StatementEnd": 55, + "SampleRatio": null, + "HasFinal": false + } + }, + "Window": null, + "Prewhere": null, + "Where": null, + "GroupBy": null, + "WithTotal": false, + "Having": null, + "OrderBy": null, + "LimitBy": null, + "Limit": null, + "Settings": null, + "Format": null, + "UnionAll": null, + "UnionDistinct": null, + "Except": null + } +] \ No newline at end of file diff --git a/parser/testdata/query/output/select_clause_keyword_as_only_column.sql.golden.json b/parser/testdata/query/output/select_clause_keyword_as_only_column.sql.golden.json new file mode 100644 index 00000000..45750abe --- /dev/null +++ b/parser/testdata/query/output/select_clause_keyword_as_only_column.sql.golden.json @@ -0,0 +1,37 @@ +[ + { + "SelectPos": 0, + "StatementEnd": 12, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": "limit", + "QuoteType": 1, + "NamePos": 7, + "NameEnd": 12 + }, + "Modifiers": [], + "Alias": null + } + ], + "From": null, + "Window": null, + "Prewhere": null, + "Where": null, + "GroupBy": null, + "WithTotal": false, + "Having": null, + "OrderBy": null, + "LimitBy": null, + "Limit": null, + "Settings": null, + "Format": null, + "UnionAll": null, + "UnionDistinct": null, + "Except": null + } +] \ No newline at end of file diff --git a/parser/testdata/query/output/select_expr_keyword_as_column.sql.golden.json b/parser/testdata/query/output/select_expr_keyword_as_column.sql.golden.json new file mode 100644 index 00000000..02be1601 --- /dev/null +++ b/parser/testdata/query/output/select_expr_keyword_as_column.sql.golden.json @@ -0,0 +1,99 @@ +[ + { + "SelectPos": 0, + "StatementEnd": 52, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": "id", + "QuoteType": 1, + "NamePos": 7, + "NameEnd": 9 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "case", + "QuoteType": 1, + "NamePos": 11, + "NameEnd": 15 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "cast", + "QuoteType": 1, + "NamePos": 17, + "NameEnd": 21 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "extract", + "QuoteType": 1, + "NamePos": 23, + "NameEnd": 30 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "interval", + "QuoteType": 1, + "NamePos": 32, + "NameEnd": 40 + }, + "Modifiers": [], + "Alias": null + } + ], + "From": { + "FromPos": 41, + "Expr": { + "Table": { + "TablePos": 46, + "TableEnd": 52, + "Alias": null, + "Expr": { + "Database": null, + "Table": { + "Name": "events", + "QuoteType": 1, + "NamePos": 46, + "NameEnd": 52 + } + }, + "HasFinal": false + }, + "StatementEnd": 52, + "SampleRatio": null, + "HasFinal": false + } + }, + "Window": null, + "Prewhere": null, + "Where": null, + "GroupBy": null, + "WithTotal": false, + "Having": null, + "OrderBy": null, + "LimitBy": null, + "Limit": null, + "Settings": null, + "Format": null, + "UnionAll": null, + "UnionDistinct": null, + "Except": null + } +] \ No newline at end of file diff --git a/parser/testdata/query/output/select_keyword_as_alias.sql.golden.json b/parser/testdata/query/output/select_keyword_as_alias.sql.golden.json new file mode 100644 index 00000000..ca324124 --- /dev/null +++ b/parser/testdata/query/output/select_keyword_as_alias.sql.golden.json @@ -0,0 +1,72 @@ +[ + { + "SelectPos": 0, + "StatementEnd": 43, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "NumPos": 7, + "NumEnd": 8, + "Literal": "1", + "Base": 10 + }, + "Modifiers": [], + "Alias": { + "Name": "interval", + "QuoteType": 1, + "NamePos": 12, + "NameEnd": 20 + } + }, + { + "Expr": { + "NumPos": 22, + "NumEnd": 23, + "Literal": "2", + "Base": 10 + }, + "Modifiers": [], + "Alias": { + "Name": "from", + "QuoteType": 1, + "NamePos": 27, + "NameEnd": 31 + } + }, + { + "Expr": { + "NumPos": 33, + "NumEnd": 34, + "Literal": "3", + "Base": 10 + }, + "Modifiers": [], + "Alias": { + "Name": "limit", + "QuoteType": 1, + "NamePos": 38, + "NameEnd": 43 + } + } + ], + "From": null, + "Window": null, + "Prewhere": null, + "Where": null, + "GroupBy": null, + "WithTotal": false, + "Having": null, + "OrderBy": null, + "LimitBy": null, + "Limit": null, + "Settings": null, + "Format": null, + "UnionAll": null, + "UnionDistinct": null, + "Except": null + } +] \ No newline at end of file diff --git a/parser/testdata/query/output/select_keyword_as_column.sql.golden.json b/parser/testdata/query/output/select_keyword_as_column.sql.golden.json new file mode 100644 index 00000000..585f95c8 --- /dev/null +++ b/parser/testdata/query/output/select_keyword_as_column.sql.golden.json @@ -0,0 +1,119 @@ +[ + { + "SelectPos": 0, + "StatementEnd": 87, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": "cloud", + "QuoteType": 1, + "NamePos": 7, + "NameEnd": 12 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "account", + "QuoteType": 1, + "NamePos": 14, + "NameEnd": 21 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "region", + "QuoteType": 1, + "NamePos": 23, + "NameEnd": 29 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "name", + "QuoteType": 1, + "NamePos": 31, + "NameEnd": 35 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "usage", + "QuoteType": 1, + "NamePos": 37, + "NameEnd": 42 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "limit", + "QuoteType": 1, + "NamePos": 44, + "NameEnd": 49 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "usage_percentage", + "QuoteType": 1, + "NamePos": 51, + "NameEnd": 67 + }, + "Modifiers": [], + "Alias": null + } + ], + "From": { + "FromPos": 68, + "Expr": { + "Table": { + "TablePos": 73, + "TableEnd": 87, + "Alias": null, + "Expr": { + "Database": null, + "Table": { + "Name": "service_quotas", + "QuoteType": 1, + "NamePos": 73, + "NameEnd": 87 + } + }, + "HasFinal": false + }, + "StatementEnd": 87, + "SampleRatio": null, + "HasFinal": false + } + }, + "Window": null, + "Prewhere": null, + "Where": null, + "GroupBy": null, + "WithTotal": false, + "Having": null, + "OrderBy": null, + "LimitBy": null, + "Limit": null, + "Settings": null, + "Format": null, + "UnionAll": null, + "UnionDistinct": null, + "Except": null + } +] \ No newline at end of file diff --git a/parser/testdata/query/output/select_keyword_as_last_column.sql.golden.json b/parser/testdata/query/output/select_keyword_as_last_column.sql.golden.json new file mode 100644 index 00000000..1b263e48 --- /dev/null +++ b/parser/testdata/query/output/select_keyword_as_last_column.sql.golden.json @@ -0,0 +1,69 @@ +[ + { + "SelectPos": 0, + "StatementEnd": 22, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": "a", + "QuoteType": 1, + "NamePos": 7, + "NameEnd": 8 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "limit", + "QuoteType": 1, + "NamePos": 10, + "NameEnd": 15 + }, + "Modifiers": [], + "Alias": null + } + ], + "From": { + "FromPos": 16, + "Expr": { + "Table": { + "TablePos": 21, + "TableEnd": 22, + "Alias": null, + "Expr": { + "Database": null, + "Table": { + "Name": "t", + "QuoteType": 1, + "NamePos": 21, + "NameEnd": 22 + } + }, + "HasFinal": false + }, + "StatementEnd": 22, + "SampleRatio": null, + "HasFinal": false + } + }, + "Window": null, + "Prewhere": null, + "Where": null, + "GroupBy": null, + "WithTotal": false, + "Having": null, + "OrderBy": null, + "LimitBy": null, + "Limit": null, + "Settings": null, + "Format": null, + "UnionAll": null, + "UnionDistinct": null, + "Except": null + } +] \ No newline at end of file diff --git a/parser/testdata/query/output/select_keyword_as_only_column.sql.golden.json b/parser/testdata/query/output/select_keyword_as_only_column.sql.golden.json new file mode 100644 index 00000000..1aad7c04 --- /dev/null +++ b/parser/testdata/query/output/select_keyword_as_only_column.sql.golden.json @@ -0,0 +1,37 @@ +[ + { + "SelectPos": 0, + "StatementEnd": 15, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": "interval", + "QuoteType": 1, + "NamePos": 7, + "NameEnd": 15 + }, + "Modifiers": [], + "Alias": null + } + ], + "From": null, + "Window": null, + "Prewhere": null, + "Where": null, + "GroupBy": null, + "WithTotal": false, + "Having": null, + "OrderBy": null, + "LimitBy": null, + "Limit": null, + "Settings": null, + "Format": null, + "UnionAll": null, + "UnionDistinct": null, + "Except": null + } +] \ No newline at end of file diff --git a/parser/testdata/query/output/select_keyword_as_only_column_semicolon.sql.golden.json b/parser/testdata/query/output/select_keyword_as_only_column_semicolon.sql.golden.json new file mode 100644 index 00000000..c960ad18 --- /dev/null +++ b/parser/testdata/query/output/select_keyword_as_only_column_semicolon.sql.golden.json @@ -0,0 +1,37 @@ +[ + { + "SelectPos": 0, + "StatementEnd": 11, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": "case", + "QuoteType": 1, + "NamePos": 7, + "NameEnd": 11 + }, + "Modifiers": [], + "Alias": null + } + ], + "From": null, + "Window": null, + "Prewhere": null, + "Where": null, + "GroupBy": null, + "WithTotal": false, + "Having": null, + "OrderBy": null, + "LimitBy": null, + "Limit": null, + "Settings": null, + "Format": null, + "UnionAll": null, + "UnionDistinct": null, + "Except": null + } +] \ No newline at end of file diff --git a/parser/testdata/query/output/select_trailing_comma_before_from_keyword_table.sql.golden.json b/parser/testdata/query/output/select_trailing_comma_before_from_keyword_table.sql.golden.json new file mode 100644 index 00000000..bd0d22b3 --- /dev/null +++ b/parser/testdata/query/output/select_trailing_comma_before_from_keyword_table.sql.golden.json @@ -0,0 +1,82 @@ +[ + { + "SelectPos": 0, + "StatementEnd": 28, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "count", + "QuoteType": 1, + "NamePos": 7, + "NameEnd": 12 + }, + "Params": { + "LeftParenPos": 12, + "RightParenPos": 14, + "Items": { + "ListPos": 13, + "ListEnd": 14, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "Name": "x", + "QuoteType": 1, + "NamePos": 13, + "NameEnd": 14 + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Modifiers": [], + "Alias": null + } + ], + "From": { + "FromPos": 17, + "Expr": { + "Table": { + "TablePos": 23, + "TableEnd": 28, + "Alias": null, + "Expr": { + "Database": null, + "Table": { + "Name": "limit", + "QuoteType": 3, + "NamePos": 23, + "NameEnd": 28 + } + }, + "HasFinal": false + }, + "StatementEnd": 28, + "SampleRatio": null, + "HasFinal": false + } + }, + "Window": null, + "Prewhere": null, + "Where": null, + "GroupBy": null, + "WithTotal": false, + "Having": null, + "OrderBy": null, + "LimitBy": null, + "Limit": null, + "Settings": null, + "Format": null, + "UnionAll": null, + "UnionDistinct": null, + "Except": null + } +] \ No newline at end of file diff --git a/parser/testdata/query/select_clause_keyword_as_column.sql b/parser/testdata/query/select_clause_keyword_as_column.sql new file mode 100644 index 00000000..155cebdb --- /dev/null +++ b/parser/testdata/query/select_clause_keyword_as_column.sql @@ -0,0 +1 @@ +SELECT id, from, to, where, group, order FROM transfers diff --git a/parser/testdata/query/select_clause_keyword_as_only_column.sql b/parser/testdata/query/select_clause_keyword_as_only_column.sql new file mode 100644 index 00000000..e1ca609f --- /dev/null +++ b/parser/testdata/query/select_clause_keyword_as_only_column.sql @@ -0,0 +1 @@ +SELECT limit diff --git a/parser/testdata/query/select_expr_keyword_as_column.sql b/parser/testdata/query/select_expr_keyword_as_column.sql new file mode 100644 index 00000000..e9a74d42 --- /dev/null +++ b/parser/testdata/query/select_expr_keyword_as_column.sql @@ -0,0 +1 @@ +SELECT id, case, cast, extract, interval FROM events diff --git a/parser/testdata/query/select_keyword_as_alias.sql b/parser/testdata/query/select_keyword_as_alias.sql new file mode 100644 index 00000000..a7f40e51 --- /dev/null +++ b/parser/testdata/query/select_keyword_as_alias.sql @@ -0,0 +1 @@ +SELECT 1 AS interval, 2 AS from, 3 AS limit diff --git a/parser/testdata/query/select_keyword_as_column.sql b/parser/testdata/query/select_keyword_as_column.sql new file mode 100644 index 00000000..1aaa3956 --- /dev/null +++ b/parser/testdata/query/select_keyword_as_column.sql @@ -0,0 +1 @@ +SELECT cloud, account, region, name, usage, limit, usage_percentage FROM service_quotas diff --git a/parser/testdata/query/select_keyword_as_last_column.sql b/parser/testdata/query/select_keyword_as_last_column.sql new file mode 100644 index 00000000..c6e98a30 --- /dev/null +++ b/parser/testdata/query/select_keyword_as_last_column.sql @@ -0,0 +1 @@ +SELECT a, limit FROM t diff --git a/parser/testdata/query/select_keyword_as_only_column.sql b/parser/testdata/query/select_keyword_as_only_column.sql new file mode 100644 index 00000000..de0f0533 --- /dev/null +++ b/parser/testdata/query/select_keyword_as_only_column.sql @@ -0,0 +1 @@ +SELECT interval diff --git a/parser/testdata/query/select_keyword_as_only_column_semicolon.sql b/parser/testdata/query/select_keyword_as_only_column_semicolon.sql new file mode 100644 index 00000000..af671de5 --- /dev/null +++ b/parser/testdata/query/select_keyword_as_only_column_semicolon.sql @@ -0,0 +1 @@ +SELECT case; diff --git a/parser/testdata/query/select_trailing_comma_before_from_keyword_table.sql b/parser/testdata/query/select_trailing_comma_before_from_keyword_table.sql new file mode 100644 index 00000000..b7747a3c --- /dev/null +++ b/parser/testdata/query/select_trailing_comma_before_from_keyword_table.sql @@ -0,0 +1 @@ +SELECT count(x), FROM `limit`