diff --git a/parser/ast.go b/parser/ast.go index aff1ff1..eb62bfa 100644 --- a/parser/ast.go +++ b/parser/ast.go @@ -187,6 +187,40 @@ func (p *BinaryOperation) Accept(visitor ASTVisitor) error { return visitor.VisitBinaryExpr(p) } +type IndexOperation struct { + LeftExpr Expr + Operation TokenKind + Index Expr +} + +func (i *IndexOperation) Accept(visitor ASTVisitor) error { + visitor.enter(i) + defer visitor.leave(i) + if err := i.LeftExpr.Accept(visitor); err != nil { + return err + } + if err := i.Index.Accept(visitor); err != nil { + return err + } + return visitor.VisitIndexOperation(i) +} + +func (i *IndexOperation) Pos() Pos { + return i.LeftExpr.Pos() +} + +func (i *IndexOperation) End() Pos { + return i.Index.End() +} + +func (i *IndexOperation) String() string { + var builder strings.Builder + builder.WriteString(i.LeftExpr.String()) + builder.WriteString(string(i.Operation)) + builder.WriteString(i.Index.String()) + return builder.String() +} + type JoinTableExpr struct { Table *TableExpr StatementEnd Pos diff --git a/parser/ast_visitor.go b/parser/ast_visitor.go index a06bf3c..5595204 100644 --- a/parser/ast_visitor.go +++ b/parser/ast_visitor.go @@ -4,6 +4,7 @@ type ASTVisitor interface { VisitOperationExpr(expr *OperationExpr) error VisitTernaryExpr(expr *TernaryOperation) error VisitBinaryExpr(expr *BinaryOperation) error + VisitIndexOperation(expr *IndexOperation) error VisitAlterTable(expr *AlterTable) error VisitAlterTableAttachPartition(expr *AlterTableAttachPartition) error VisitAlterTableDetachPartition(expr *AlterTableDetachPartition) error @@ -188,6 +189,13 @@ func (v *DefaultASTVisitor) VisitBinaryExpr(expr *BinaryOperation) error { return nil } +func (v *DefaultASTVisitor) VisitIndexOperation(expr *IndexOperation) error { + if v.Visit != nil { + return v.Visit(expr) + } + return nil +} + func (v *DefaultASTVisitor) VisitJoinTableExpr(expr *JoinTableExpr) error { if v.Visit != nil { return v.Visit(expr) diff --git a/parser/lexer.go b/parser/lexer.go index 4de4d54..1f2654d 100644 --- a/parser/lexer.go +++ b/parser/lexer.go @@ -15,6 +15,7 @@ const ( TokenInt TokenKind = "" TokenFloat TokenKind = "" TokenString TokenKind = "" + TokenDot = "." ) const ( @@ -328,19 +329,14 @@ func (l *Lexer) consumeToken() error { return nil } case '.': - // check if the next token is a number. If so, parse it as a float number - if l.peekOk(1) && IsDigit(l.peekN(1)) { - return l.consumeNumber() - } - // check if the previous lastToken is an Ident. If so, it's a field name. - if l.lastToken != nil && l.lastToken.Kind != TokenIdent { - return fmt.Errorf("'.' should be after an Ident, but got <%q>", l.lastToken.Kind) + l.lastToken = &Token{ + String: l.slice(0, 1), + Kind: TokenDot, + Pos: Pos(l.current), + End: Pos(l.current + 1), } - } - - // The subsequent lastToken after the dot should be an Ident. - if l.lastToken != nil && l.lastToken.Kind == "." && !IsIdentStart(l.peekN(0)) { - return fmt.Errorf("'.' should follow with an Ident, but got <%q>", l.lastToken.Kind) + l.skipN(1) + return nil } if IsIdentStart(l.peekN(0)) { diff --git a/parser/lexer_test.go b/parser/lexer_test.go index ce5a4a1..addc963 100644 --- a/parser/lexer_test.go +++ b/parser/lexer_test.go @@ -106,7 +106,6 @@ func TestConsumeNumber(t *testing.T) { t.Run("Float number", func(t *testing.T) { floats := []string{ "123.456", - ".456", "123.456e+10", "123.456e-10", "123.456e10", @@ -126,7 +125,6 @@ func TestConsumeNumber(t *testing.T) { t.Run("Invalid float number", func(t *testing.T) { invalidFloats := []string{ - ".456a", "123.456b", "123.456e", "123.456e+", diff --git a/parser/parse_system.go b/parser/parse_system.go index 817e82f..9974450 100644 --- a/parser/parse_system.go +++ b/parser/parse_system.go @@ -921,7 +921,7 @@ func (p *Parser) parseGrantSource(_ Pos) (*TableIdentifier, error) { return nil, err } - if p.tryConsumeTokenKind(".") == nil { + if p.tryConsumeTokenKind(TokenDot) == nil { return &TableIdentifier{ Table: ident, }, nil diff --git a/parser/parser_column.go b/parser/parser_column.go index be18de0..cef786a 100644 --- a/parser/parser_column.go +++ b/parser/parser_column.go @@ -20,6 +20,7 @@ const ( PrecedenceMulDivMod PrecedenceBracket PrecedenceArrow + PrecedenceDot PrecedenceDoubleColon ) @@ -56,6 +57,8 @@ func (p *Parser) getNextPrecedence() int { return PrecedenceBracket case p.matchTokenKind(opTypeCast): return PrecedenceDoubleColon + case p.matchTokenKind(TokenDot): + return PrecedenceDot case p.matchKeyword(KeywordBetween), p.matchKeyword(KeywordLike), p.matchKeyword(KeywordIlike): return PrecedenceBetweenLike case p.matchKeyword(KeywordIn): @@ -78,7 +81,7 @@ func (p *Parser) parseInfix(expr Expr, precedence int) (Expr, error) { p.matchTokenKind(opTypeDiv), p.matchTokenKind(opTypeMod), p.matchKeyword(KeywordIn), p.matchKeyword(KeywordLike), p.matchKeyword(KeywordIlike), p.matchKeyword(KeywordAnd), p.matchKeyword(KeywordOr), - p.matchTokenKind(opTypeCast), p.matchTokenKind(opTypeArrow), p.matchTokenKind(opTypeDoubleEQ): + p.matchTokenKind(opTypeArrow), p.matchTokenKind(opTypeDoubleEQ): op := p.last().ToString() _ = p.lexer.consumeToken() rightExpr, err := p.parseSubExpr(p.Pos(), precedence) @@ -90,6 +93,38 @@ func (p *Parser) parseInfix(expr Expr, precedence int) (Expr, error) { Operation: TokenKind(op), RightExpr: rightExpr, }, nil + case p.matchTokenKind(opTypeCast): + _ = p.lexer.consumeToken() + + if p.matchTokenKind(TokenIdent) && p.last().String == "Tuple" { + name, err := p.parseIdent() + if err != nil { + return nil, err + } + if _, err = p.consumeTokenKind("("); err != nil { + return nil, err + } + // it's a tuple type definition after "::" operator + rightExpr, err := p.parseNestedType(name, p.Pos()) + if err != nil { + return nil, err + } + return &BinaryOperation{ + LeftExpr: expr, + Operation: opTypeCast, + RightExpr: rightExpr, + }, nil + } + + rightExpr, err := p.parseSubExpr(p.Pos(), precedence) + if err != nil { + return nil, err + } + return &BinaryOperation{ + LeftExpr: expr, + Operation: opTypeCast, + RightExpr: rightExpr, + }, nil case p.matchKeyword(KeywordBetween): return p.parseBetweenClause(expr) case p.matchKeyword(KeywordGlobal): @@ -106,7 +141,24 @@ func (p *Parser) parseInfix(expr Expr, precedence int) (Expr, error) { Operation: "GLOBAL IN", RightExpr: rightExpr, }, nil - + case p.matchTokenKind(TokenDot): + _ = p.lexer.consumeToken() + // access column with dot notation + var rightExpr Expr + var err error + if p.matchTokenKind(TokenIdent) { + rightExpr, err = p.parseIdent() + } else { + rightExpr, err = p.parseDecimal(p.Pos()) + } + if err != nil { + return nil, err + } + return &IndexOperation{ + LeftExpr: expr, + Operation: TokenDot, + Index: rightExpr, + }, nil case p.matchKeyword(KeywordNot): _ = p.lexer.consumeToken() switch { @@ -331,6 +383,8 @@ func (p *Parser) parseColumnExpr(pos Pos) (Expr, error) { //nolint:funlen return p.parseQueryParam(p.Pos()) } return p.parseMapLiteral(p.Pos()) + case p.matchTokenKind(TokenDot): + return p.parseNumber(p.Pos()) case p.matchTokenKind(opTypeQuery): // Placeholder `?` _ = p.lexer.consumeToken() diff --git a/parser/parser_common.go b/parser/parser_common.go index 5f21e30..c5e148c 100644 --- a/parser/parser_common.go +++ b/parser/parser_common.go @@ -110,7 +110,7 @@ func (p *Parser) parseIdentOrStar() (*Ident, error) { } func (p *Parser) tryParseDotIdent(_ Pos) (*Ident, error) { - if p.tryConsumeTokenKind(".") == nil { + if p.tryConsumeTokenKind(TokenDot) == nil { return nil, nil // nolint } return p.parseIdent() @@ -211,6 +211,17 @@ func (p *Parser) parseNumber(pos Pos) (*NumberLiteral, error) { lastToken, err = p.consumeTokenKind(TokenInt) case p.matchTokenKind(TokenFloat): lastToken, err = p.consumeTokenKind(TokenFloat) + case p.matchTokenKind(TokenDot): + _ = p.lexer.consumeToken() + lastToken, err = p.consumeTokenKind(TokenInt) + if err != nil { + return nil, err + } + if lastToken.Base != 10 { + return nil, fmt.Errorf("invalid decimal literal: %q", lastToken.String) + } + lastToken.String = "." + lastToken.String + lastToken.Kind = TokenFloat default: return nil, fmt.Errorf("expected or , but got %q", p.lastTokenKind()) } diff --git a/parser/parser_table.go b/parser/parser_table.go index 2e4e92f..718c1ae 100644 --- a/parser/parser_table.go +++ b/parser/parser_table.go @@ -223,14 +223,14 @@ func (p *Parser) parseIdentOrFunction(_ Pos) (Expr, error) { }, nil } return funcExpr, nil - case p.tryConsumeTokenKind(".") != nil: + case p.tryConsumeTokenKind(TokenDot) != nil: switch { case p.matchTokenKind(TokenIdent): nextIdent, err := p.parseIdent() if err != nil { return nil, err } - if p.tryConsumeTokenKind(".") != nil { + if p.tryConsumeTokenKind(TokenDot) != nil { thirdIdent, err := p.parseIdent() if err != nil { return nil, err @@ -310,7 +310,7 @@ func (p *Parser) parseTableSchemaClause(pos Pos) (*TableSchemaClause, error) { return nil, err } switch { - case p.matchTokenKind("."): + case p.matchTokenKind(TokenDot): // it's a database.table dotIdent, err := p.tryParseDotIdent(p.Pos()) if err != nil { @@ -489,7 +489,7 @@ func (p *Parser) parseTableArgExpr(pos Pos) (Expr, error) { } switch { // nest identifier - case p.matchTokenKind("."): + case p.matchTokenKind(TokenDot): dotIdent, err := p.tryParseDotIdent(p.Pos()) if err != nil { return nil, err diff --git a/parser/testdata/query/access_tuple_with_dot.sql b/parser/testdata/query/access_tuple_with_dot.sql new file mode 100644 index 0000000..38b1184 --- /dev/null +++ b/parser/testdata/query/access_tuple_with_dot.sql @@ -0,0 +1,6 @@ +SELECT tuple('a','b','c').3, .1234; + +SELECT toTypeName( tuple('a' as first,'b' as second ,'c' as third)::Tuple(first String,second String,third String)), + (tuple('a' as first,'b' as second ,'c' as third)::Tuple(first String,second String,third String)).second, + tuple('a','b','c').3, + tupleElement(tuple('a','b','c'),1) \ No newline at end of file diff --git a/parser/testdata/query/format/access_tuple_with_dot.sql b/parser/testdata/query/format/access_tuple_with_dot.sql new file mode 100644 index 0000000..e7672c9 --- /dev/null +++ b/parser/testdata/query/format/access_tuple_with_dot.sql @@ -0,0 +1,11 @@ +-- Origin SQL: +SELECT tuple('a','b','c').3, .1234; + +SELECT toTypeName( tuple('a' as first,'b' as second ,'c' as third)::Tuple(first String,second String,third String)), + (tuple('a' as first,'b' as second ,'c' as third)::Tuple(first String,second String,third String)).second, + tuple('a','b','c').3, + tupleElement(tuple('a','b','c'),1) + +-- Format SQL: +SELECT tuple('a', 'b', 'c').3, .1234; +SELECT toTypeName(tuple('a' AS first, 'b' AS second, 'c' AS third)::Tuple(first String, second String, third String)), (tuple('a' AS first, 'b' AS second, 'c' AS third)::Tuple(first String, second String, third String)).second, tuple('a', 'b', 'c').3, tupleElement(tuple('a', 'b', 'c'), 1); diff --git a/parser/testdata/query/output/access_tuple_with_dot.sql.golden.json b/parser/testdata/query/output/access_tuple_with_dot.sql.golden.json new file mode 100644 index 0000000..563e409 --- /dev/null +++ b/parser/testdata/query/output/access_tuple_with_dot.sql.golden.json @@ -0,0 +1,641 @@ +[ + { + "SelectPos": 0, + "StatementEnd": 34, + "With": null, + "Top": null, + "SelectItems": [ + { + "Expr": { + "LeftExpr": { + "Name": { + "Name": "tuple", + "QuoteType": 1, + "NamePos": 7, + "NameEnd": 12 + }, + "Params": { + "LeftParenPos": 12, + "RightParenPos": 24, + "Items": { + "ListPos": 14, + "ListEnd": 23, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 14, + "LiteralEnd": 15, + "Literal": "a" + }, + "Alias": null + }, + { + "Expr": { + "LiteralPos": 18, + "LiteralEnd": 19, + "Literal": "b" + }, + "Alias": null + }, + { + "Expr": { + "LiteralPos": 22, + "LiteralEnd": 23, + "Literal": "c" + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Operation": ".", + "Index": { + "NumPos": 26, + "NumEnd": 27, + "Literal": "3", + "Base": 10 + } + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "NumPos": 29, + "NumEnd": 34, + "Literal": ".1234", + "Base": 10 + }, + "Modifiers": [], + "Alias": null + } + ], + "From": null, + "ArrayJoin": 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 + }, + { + "SelectPos": 37, + "StatementEnd": 336, + "With": null, + "Top": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "toTypeName", + "QuoteType": 1, + "NamePos": 44, + "NameEnd": 54 + }, + "Params": { + "LeftParenPos": 54, + "RightParenPos": 151, + "Items": { + "ListPos": 56, + "ListEnd": 150, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "Name": { + "Name": "tuple", + "QuoteType": 1, + "NamePos": 56, + "NameEnd": 61 + }, + "Params": { + "LeftParenPos": 61, + "RightParenPos": 102, + "Items": { + "ListPos": 63, + "ListEnd": 102, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 63, + "LiteralEnd": 64, + "Literal": "a" + }, + "Alias": { + "Name": "first", + "QuoteType": 1, + "NamePos": 69, + "NameEnd": 74 + } + }, + { + "Expr": { + "LiteralPos": 76, + "LiteralEnd": 77, + "Literal": "b" + }, + "Alias": { + "Name": "second", + "QuoteType": 1, + "NamePos": 82, + "NameEnd": 88 + } + }, + { + "Expr": { + "LiteralPos": 91, + "LiteralEnd": 92, + "Literal": "c" + }, + "Alias": { + "Name": "third", + "QuoteType": 1, + "NamePos": 97, + "NameEnd": 102 + } + } + ] + }, + "ColumnArgList": null + } + }, + "Operation": "::", + "RightExpr": { + "LeftParenPos": 111, + "RightParenPos": 150, + "Name": { + "Name": "Tuple", + "QuoteType": 1, + "NamePos": 105, + "NameEnd": 110 + }, + "Columns": [ + { + "NamePos": 111, + "ColumnEnd": 123, + "Name": { + "Ident": { + "Name": "first", + "QuoteType": 1, + "NamePos": 111, + "NameEnd": 116 + }, + "DotIdent": null + }, + "Type": { + "Name": { + "Name": "String", + "QuoteType": 1, + "NamePos": 117, + "NameEnd": 123 + } + }, + "NotNull": null, + "Nullable": null, + "DefaultExpr": null, + "MaterializedExpr": null, + "AliasExpr": null, + "Codec": null, + "TTL": null, + "Comment": null, + "CompressionCodec": null + }, + { + "NamePos": 124, + "ColumnEnd": 137, + "Name": { + "Ident": { + "Name": "second", + "QuoteType": 1, + "NamePos": 124, + "NameEnd": 130 + }, + "DotIdent": null + }, + "Type": { + "Name": { + "Name": "String", + "QuoteType": 1, + "NamePos": 131, + "NameEnd": 137 + } + }, + "NotNull": null, + "Nullable": null, + "DefaultExpr": null, + "MaterializedExpr": null, + "AliasExpr": null, + "Codec": null, + "TTL": null, + "Comment": null, + "CompressionCodec": null + }, + { + "NamePos": 138, + "ColumnEnd": 150, + "Name": { + "Ident": { + "Name": "third", + "QuoteType": 1, + "NamePos": 138, + "NameEnd": 143 + }, + "DotIdent": null + }, + "Type": { + "Name": { + "Name": "String", + "QuoteType": 1, + "NamePos": 144, + "NameEnd": 150 + } + }, + "NotNull": null, + "Nullable": null, + "DefaultExpr": null, + "MaterializedExpr": null, + "AliasExpr": null, + "Codec": null, + "TTL": null, + "Comment": null, + "CompressionCodec": null + } + ] + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "LeftExpr": { + "LeftParenPos": 161, + "RightParenPos": 257, + "Items": { + "ListPos": 162, + "ListEnd": 256, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "Name": { + "Name": "tuple", + "QuoteType": 1, + "NamePos": 162, + "NameEnd": 167 + }, + "Params": { + "LeftParenPos": 167, + "RightParenPos": 208, + "Items": { + "ListPos": 169, + "ListEnd": 208, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 169, + "LiteralEnd": 170, + "Literal": "a" + }, + "Alias": { + "Name": "first", + "QuoteType": 1, + "NamePos": 175, + "NameEnd": 180 + } + }, + { + "Expr": { + "LiteralPos": 182, + "LiteralEnd": 183, + "Literal": "b" + }, + "Alias": { + "Name": "second", + "QuoteType": 1, + "NamePos": 188, + "NameEnd": 194 + } + }, + { + "Expr": { + "LiteralPos": 197, + "LiteralEnd": 198, + "Literal": "c" + }, + "Alias": { + "Name": "third", + "QuoteType": 1, + "NamePos": 203, + "NameEnd": 208 + } + } + ] + }, + "ColumnArgList": null + } + }, + "Operation": "::", + "RightExpr": { + "LeftParenPos": 217, + "RightParenPos": 256, + "Name": { + "Name": "Tuple", + "QuoteType": 1, + "NamePos": 211, + "NameEnd": 216 + }, + "Columns": [ + { + "NamePos": 217, + "ColumnEnd": 229, + "Name": { + "Ident": { + "Name": "first", + "QuoteType": 1, + "NamePos": 217, + "NameEnd": 222 + }, + "DotIdent": null + }, + "Type": { + "Name": { + "Name": "String", + "QuoteType": 1, + "NamePos": 223, + "NameEnd": 229 + } + }, + "NotNull": null, + "Nullable": null, + "DefaultExpr": null, + "MaterializedExpr": null, + "AliasExpr": null, + "Codec": null, + "TTL": null, + "Comment": null, + "CompressionCodec": null + }, + { + "NamePos": 230, + "ColumnEnd": 243, + "Name": { + "Ident": { + "Name": "second", + "QuoteType": 1, + "NamePos": 230, + "NameEnd": 236 + }, + "DotIdent": null + }, + "Type": { + "Name": { + "Name": "String", + "QuoteType": 1, + "NamePos": 237, + "NameEnd": 243 + } + }, + "NotNull": null, + "Nullable": null, + "DefaultExpr": null, + "MaterializedExpr": null, + "AliasExpr": null, + "Codec": null, + "TTL": null, + "Comment": null, + "CompressionCodec": null + }, + { + "NamePos": 244, + "ColumnEnd": 256, + "Name": { + "Ident": { + "Name": "third", + "QuoteType": 1, + "NamePos": 244, + "NameEnd": 249 + }, + "DotIdent": null + }, + "Type": { + "Name": { + "Name": "String", + "QuoteType": 1, + "NamePos": 250, + "NameEnd": 256 + } + }, + "NotNull": null, + "Nullable": null, + "DefaultExpr": null, + "MaterializedExpr": null, + "AliasExpr": null, + "Codec": null, + "TTL": null, + "Comment": null, + "CompressionCodec": null + } + ] + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + }, + "Operation": ".", + "Index": { + "Name": "second", + "QuoteType": 1, + "NamePos": 259, + "NameEnd": 265 + } + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "LeftExpr": { + "Name": { + "Name": "tuple", + "QuoteType": 1, + "NamePos": 274, + "NameEnd": 279 + }, + "Params": { + "LeftParenPos": 279, + "RightParenPos": 291, + "Items": { + "ListPos": 281, + "ListEnd": 290, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 281, + "LiteralEnd": 282, + "Literal": "a" + }, + "Alias": null + }, + { + "Expr": { + "LiteralPos": 285, + "LiteralEnd": 286, + "Literal": "b" + }, + "Alias": null + }, + { + "Expr": { + "LiteralPos": 289, + "LiteralEnd": 290, + "Literal": "c" + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Operation": ".", + "Index": { + "NumPos": 293, + "NumEnd": 294, + "Literal": "3", + "Base": 10 + } + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": { + "Name": "tupleElement", + "QuoteType": 1, + "NamePos": 303, + "NameEnd": 315 + }, + "Params": { + "LeftParenPos": 315, + "RightParenPos": 336, + "Items": { + "ListPos": 316, + "ListEnd": 336, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "Name": { + "Name": "tuple", + "QuoteType": 1, + "NamePos": 316, + "NameEnd": 321 + }, + "Params": { + "LeftParenPos": 321, + "RightParenPos": 333, + "Items": { + "ListPos": 323, + "ListEnd": 332, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 323, + "LiteralEnd": 324, + "Literal": "a" + }, + "Alias": null + }, + { + "Expr": { + "LiteralPos": 327, + "LiteralEnd": 328, + "Literal": "b" + }, + "Alias": null + }, + { + "Expr": { + "LiteralPos": 331, + "LiteralEnd": 332, + "Literal": "c" + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Alias": null + }, + { + "Expr": { + "NumPos": 335, + "NumEnd": 336, + "Literal": "1", + "Base": 10 + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Modifiers": [], + "Alias": null + } + ], + "From": null, + "ArrayJoin": 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