diff --git a/parser/keyword.go b/parser/keyword.go index 142f644..4d07342 100644 --- a/parser/keyword.go +++ b/parser/keyword.go @@ -164,7 +164,10 @@ const ( KeywordOuter = "OUTER" KeywordOutfile = "OUTFILE" KeywordOver = "OVER" + KeywordOverlay = "OVERLAY" + KeywordOverlayUTF8 = "OVERLAYUTF8" KeywordPartition = "PARTITION" + KeywordPlacing = "PLACING" KeywordPipeline = "PIPELINE" KeywordPolicy = "POLICY" KeywordPopulate = "POPULATE" @@ -488,8 +491,11 @@ var keywords = NewSet( KeywordOverridable, KeywordOutfile, KeywordOver, + KeywordOverlay, + KeywordOverlayUTF8, KeywordPartition, KeywordPipeline, + KeywordPlacing, KeywordPolicy, KeywordPopulate, KeywordPreceding, diff --git a/parser/parser_column.go b/parser/parser_column.go index 9634b3f..405116f 100644 --- a/parser/parser_column.go +++ b/parser/parser_column.go @@ -767,6 +767,192 @@ func (p *Parser) parseColumnArgList(pos Pos) (*ColumnArgList, error) { }, nil } +// keywordArgForm is a function's keyword-separated argument syntax, e.g. +// `trim(BOTH ' ' FROM s)`. These keywords are matched only inside such an +// argument list, never in getNextPrecedence: FROM also starts a clause, so +// giving it a precedence would make `SELECT x FROM t` one expression. +type keywordArgForm struct { + Modifiers []string + RequireModifier bool + Separators []string // stand in for a comma, in this order + RequiredSeparators int + ClosedKeywordForm bool // the keyword form may not be mixed with commas + MaxSlots int // argument limit once a separator keyword is used +} + +var keywordArgFunctions = map[string]keywordArgForm{ + KeywordTrim: { + Modifiers: []string{KeywordBoth, KeywordLeading, KeywordTrailing}, + RequireModifier: true, + Separators: []string{KeywordFrom}, + RequiredSeparators: 1, + }, + KeywordSubstring: { + Separators: []string{KeywordFrom, KeywordFor}, + RequiredSeparators: 1, + MaxSlots: 3, + }, + KeywordOverlay: { + Separators: []string{KeywordPlacing, KeywordFrom, KeywordFor}, + RequiredSeparators: 2, + ClosedKeywordForm: true, + }, + KeywordOverlayUTF8: { + Separators: []string{KeywordPlacing, KeywordFrom, KeywordFor}, + RequiredSeparators: 2, + ClosedKeywordForm: true, + }, +} + +func (p *Parser) parseKeywordArgItem(form keywordArgForm, slot int, allowSeparators bool) (Expr, int, error) { + expr, hasModifier, err := p.parseKeywordArgModifier(form, slot) + if err != nil { + return nil, 0, err + } + + consumed := 0 + for allowSeparators && slot+consumed < len(form.Separators) && + p.matchKeyword(form.Separators[slot+consumed]) { + operation := TokenKind(p.current().ToString()) + if err := p.lexer.consumeToken(); err != nil { + return nil, 0, err + } + + rightExpr, err := p.parseExpr(p.Pos()) + if err != nil { + return nil, 0, err + } + + expr = &BinaryOperation{ + LeftExpr: expr, + Operation: operation, + RightExpr: rightExpr, + } + consumed++ + } + + if !hasModifier && consumed == 0 { + return expr, 0, nil + } + + if form.RequireModifier && !hasModifier { + return nil, 0, fmt.Errorf("expected one of %s before %s", + strings.Join(form.Modifiers, ", "), form.Separators[0]) + } + + if slot+consumed < form.RequiredSeparators { + return nil, 0, fmt.Errorf("expected %s", form.Separators[slot+consumed]) + } + + return expr, consumed, nil +} + +func (p *Parser) parseKeywordArgModifier(form keywordArgForm, slot int) (Expr, bool, error) { + if slot != 0 || !p.matchOneOfKeywords(form.Modifiers...) { + expr, err := p.parseExpr(p.Pos()) + return expr, false, err + } + // Only a modifier when an expression follows it; a bare `both` is an identifier. + if p.peekTokenKind(TokenKindComma) || p.peekTokenKind(TokenKindRParen) { + expr, err := p.parseExpr(p.Pos()) + return expr, false, err + } + + unaryPos := p.Pos() + kind := TokenKind(p.current().ToString()) + if err := p.lexer.consumeToken(); err != nil { + return nil, false, err + } + + expr, err := p.parseExpr(p.Pos()) + if err != nil { + return nil, false, err + } + + return &UnaryExpr{ + UnaryPos: unaryPos, + Kind: kind, + Expr: expr, + }, true, nil +} + +func (p *Parser) parseKeywordArgFunctionParams(pos Pos, form keywordArgForm) (*ParamExprList, error) { + if err := p.expectTokenKind(TokenKindLParen); err != nil { + return nil, err + } + + itemsPos := p.Pos() + hasDistinct := p.tryConsumeKeywords(KeywordDistinct) + + items := make([]Expr, 0) + // A comma advances the argument slot just as a separator keyword does, so the + // index runs across the list instead of resetting per item. + slot := 0 + usedSeparator, usedComma := false, false + for !p.lexer.isEOF() && !p.matchTokenKind(TokenKindRParen) { + item, consumed, err := p.parseKeywordArgItem(form, slot, !(form.ClosedKeywordForm && usedComma)) + if err != nil { + return nil, err + } + + if consumed > 0 { + usedSeparator = true + } + slot += consumed + + var alias *Ident + if p.tryConsumeKeywords(KeywordAs) { + if alias, err = p.parseAnyKeyword(); err != nil { + return nil, err + } + } + + items = append(items, &ColumnExpr{Expr: item, Alias: alias}) + if p.tryConsumeTokenKind(TokenKindComma) == nil { + break + } + + usedComma = true + slot++ + + if usedSeparator && (form.ClosedKeywordForm || (form.MaxSlots > 0 && slot >= form.MaxSlots)) { + return nil, fmt.Errorf("expected ')', but got ','") + } + } + + itemList := &ColumnExprList{ + ListPos: itemsPos, + ListEnd: itemsPos, + HasDistinct: hasDistinct, + Items: items, + } + if len(items) > 0 { + itemList.ListEnd = items[len(items)-1].End() + } + + rightParenPos := p.Pos() + if err := p.expectTokenKind(TokenKindRParen); err != nil { + return nil, err + } + + paramExprList := &ParamExprList{ + LeftParenPos: pos, + RightParenPos: rightParenPos, + Items: itemList, + } + // Parametric argument lists, as in parseFunctionParams. + if p.matchTokenKind(TokenKindLParen) { + columnArgList, err := p.parseColumnArgList(p.Pos()) + if err != nil { + return nil, err + } + + paramExprList.ColumnArgList = columnArgList + } + + return paramExprList, nil +} + func (p *Parser) parseFunctionParams(pos Pos) (*ParamExprList, error) { if err := p.expectTokenKind(TokenKindLParen); err != nil { return nil, err diff --git a/parser/parser_table.go b/parser/parser_table.go index 10e6012..17c4a6a 100644 --- a/parser/parser_table.go +++ b/parser/parser_table.go @@ -3,6 +3,7 @@ package parser import ( "errors" "fmt" + "strings" ) func (p *Parser) parseDDL(pos Pos) (DDL, error) { @@ -423,7 +424,12 @@ func (p *Parser) parseIdentOrFunction(_ Pos) (Expr, error) { Params: params, }, nil case p.matchTokenKind(TokenKindLParen): - params, err := p.parseFunctionParams(p.Pos()) + var params *ParamExprList + if form, ok := keywordArgFunctions[strings.ToUpper(ident.Name)]; ok { + params, err = p.parseKeywordArgFunctionParams(p.Pos(), form) + } else { + params, err = p.parseFunctionParams(p.Pos()) + } if err != nil { return nil, err } diff --git a/parser/position_test.go b/parser/position_test.go index dab4d8b..7b2a4c0 100644 --- a/parser/position_test.go +++ b/parser/position_test.go @@ -90,3 +90,27 @@ func TestDictionaryAttributeEnd(t *testing.T) { require.Equal(t, "IS_OBJECT_ID", sql[36:48]) require.Equal(t, Pos(48), attrs[0].End()) } + +func TestKeywordArgFunctionPositions(t *testing.T) { + // The keyword forms reuse UnaryExpr/BinaryOperation, so their spans must match + // what the same nodes report anywhere else. + sql := "SELECT trim(BOTH ' ' FROM s)" + stmt := parseOneStmt(t, sql).(*SelectQuery) + fn := stmt.SelectItems[0].Expr.(*FunctionExpr) + require.Equal(t, Pos(7), fn.Pos()) + require.Equal(t, Pos(len(sql)-1), fn.End(), "ends on the closing paren, as any call does") + + from := fn.Params.Items.Items[0].(*ColumnExpr).Expr.(*BinaryOperation) + require.Equal(t, "BOTH", sql[12:16]) + require.Equal(t, "s", sql[26:27]) + // the operation spans its operands: BOTH ... s + require.Equal(t, Pos(12), from.Pos()) + require.Equal(t, Pos(27), from.End()) + + // the characters literal runs from inside the opening quote to the closing one + modifier := from.LeftExpr.(*UnaryExpr) + require.Equal(t, Pos(12), modifier.Pos()) + require.Equal(t, modifier.Expr.End(), modifier.End()) + require.Equal(t, Pos(18), modifier.Expr.Pos()) + require.Equal(t, Pos(19), modifier.Expr.End()) +} diff --git a/parser/precedence_test.go b/parser/precedence_test.go index 69e6872..f8e3055 100644 --- a/parser/precedence_test.go +++ b/parser/precedence_test.go @@ -134,6 +134,192 @@ func TestSignedNumberAfterClosingBracketIsBinaryOperator(t *testing.T) { require.Equal(t, "arr[-1]", Format(expr)) } +// keywordArgOf returns the sole argument of a call, unwrapping its ColumnExpr. +func keywordArgOf(t *testing.T, sql, name string) Expr { + t.Helper() + expr := parseSelectItemExpr(t, sql) + fn, ok := expr.(*FunctionExpr) + require.True(t, ok, "%s: expected FunctionExpr, got %T", sql, expr) + require.Equal(t, name, fn.Name.Name, sql) + require.Len(t, fn.Params.Items.Items, 1, sql) + arg, ok := fn.Params.Items.Items[0].(*ColumnExpr) + require.True(t, ok, "%s: argument should be wrapped in ColumnExpr, got %T", sql, fn.Params.Items.Items[0]) + return arg.Expr +} + +func TestTrimKeywordArgs(t *testing.T) { + from, ok := keywordArgOf(t, "SELECT trim(BOTH ' ' FROM ' x ')", "trim").(*BinaryOperation) + require.True(t, ok) + require.Equal(t, TokenKind(KeywordFrom), from.Operation) + modifier, ok := from.LeftExpr.(*UnaryExpr) + require.True(t, ok, "left side should be the BOTH modifier, got %T", from.LeftExpr) + require.Equal(t, TokenKind(KeywordBoth), modifier.Kind) + require.Equal(t, "SELECT trim(BOTH ' ' FROM ' x ')", Format(parseOneStmt(t, "SELECT trim(BOTH ' ' FROM ' x ')"))) + + for _, sql := range []string{ + "SELECT trim(LEADING '0' FROM '00042')", + "SELECT trim(TRAILING '/' FROM '/api/')", + } { + require.Equal(t, sql, Format(parseOneStmt(t, sql)), sql) + } + + // The modifier, the characters and FROM are all required. + for _, sql := range []string{ + "SELECT trim(' ' FROM ' x ')", + "SELECT trim(BOTH FROM ' x ')", + "SELECT trim(BOTH ' ')", + } { + _, err := NewParser(sql).ParseStmts() + require.Error(t, err, sql) + } + + // BOTH is not reserved, so it stays usable as an ordinary name. ClickHouse + // rejects trim(both) itself — its trim grammar always reads BOTH as the + // modifier — so only the non-trim spellings are asserted. + for _, sql := range []string{"SELECT both FROM t", "SELECT max(both) FROM t", "SELECT a AS both FROM t"} { + require.Equal(t, sql, Format(parseOneStmt(t, sql)), sql) + } + + // Only `trim` has the keyword form; the explicit variants take commas alone, + // as in ClickHouse. + for _, sql := range []string{ + "SELECT trimBoth('xxhixx', 'x')", + "SELECT trimLeft('xxhi', 'x')", + "SELECT trimRight('hixx', 'x')", + } { + require.Equal(t, sql, Format(parseOneStmt(t, sql)), sql) + } + + for _, sql := range []string{ + "SELECT trimBoth(BOTH ' ' FROM ' x ')", + "SELECT trimLeft(LEADING ' ' FROM ' x ')", + "SELECT trimRight(TRAILING ' ' FROM ' x ')", + } { + _, err := NewParser(sql).ParseStmts() + require.Error(t, err, sql) + } + + // An argument alias works as it does for any other function. + for _, sql := range []string{"SELECT trim('x' AS y)", "SELECT trim(BOTH ' ' FROM ' x ' AS y)"} { + require.Equal(t, sql, Format(parseOneStmt(t, sql)), sql) + } + + // A trailing comma argument here is a ClickHouse arity error, not a syntax + // error, so the parser accepts it. + for _, sql := range []string{"SELECT trim(BOTH ' ' FROM ' x ', 'y')", "SELECT trim(' x ', 'y')"} { + require.Equal(t, sql, Format(parseOneStmt(t, sql)), sql) + } +} + +func TestSubstringKeywordArgs(t *testing.T) { + // substring(s FROM 2 FOR 3) nests left-to-right: ((s FROM 2) FOR 3). + forOp, ok := keywordArgOf(t, "SELECT substring('hello' FROM 2 FOR 3)", "substring").(*BinaryOperation) + require.True(t, ok) + require.Equal(t, TokenKind(KeywordFor), forOp.Operation) + fromOp, ok := forOp.LeftExpr.(*BinaryOperation) + require.True(t, ok, "left side should be the FROM operation, got %T", forOp.LeftExpr) + require.Equal(t, TokenKind(KeywordFrom), fromOp.Operation) + + // FOR is optional, FROM is not, and the arguments are ordinary expressions. + for _, sql := range []string{ + "SELECT substring('hello' FROM 2)", + "SELECT substring(concat(a, b) FROM x + 1 FOR len(y))", + "SELECT substring('hello', 2, 3)", + } { + require.Equal(t, sql, Format(parseOneStmt(t, sql)), sql) + } + + // A comma fills the same slot a separator keyword would, so substring mixes + // the two freely. + for _, sql := range []string{ + "SELECT substring('hello', 2 FOR 3)", + "SELECT substring('hello' FROM 2, 3)", + } { + require.Equal(t, sql, Format(parseOneStmt(t, sql)), sql) + } + + // A fourth slot is a syntax error however the earlier separators were spelled. + for _, sql := range []string{ + "SELECT substring('hello', 2 FOR 3, 4)", + "SELECT substring('hello' FROM 2, 3, 4)", + "SELECT substring('hello' FROM 2 FOR 3, 4)", + } { + _, err := NewParser(sql).ParseStmts() + require.Error(t, err, sql) + } + + // substringUTF8 has no keyword form, unlike overlayUTF8. + for _, sql := range []string{ + "SELECT substring('hello' FOR 3)", + "SELECT substringUTF8('hello' FROM 2 FOR 3)", + } { + _, err := NewParser(sql).ParseStmts() + require.Error(t, err, sql) + } + + // FROM keeps its clause meaning outside these argument lists. + require.Equal(t, "SELECT substring FROM t", Format(parseOneStmt(t, "SELECT substring FROM t"))) +} + +func TestOverlayKeywordArgs(t *testing.T) { + // overlay(s PLACING r FROM n FOR m) chains three separators in order. + forOp, ok := keywordArgOf(t, "SELECT overlay('Hello world' PLACING 'SQL' FROM 7 FOR 5)", "overlay").(*BinaryOperation) + require.True(t, ok) + require.Equal(t, TokenKind(KeywordFor), forOp.Operation) + fromOp, ok := forOp.LeftExpr.(*BinaryOperation) + require.True(t, ok, "expected the FROM operation, got %T", forOp.LeftExpr) + require.Equal(t, TokenKind(KeywordFrom), fromOp.Operation) + placingOp, ok := fromOp.LeftExpr.(*BinaryOperation) + require.True(t, ok, "expected the PLACING operation, got %T", fromOp.LeftExpr) + require.Equal(t, TokenKind(KeywordPlacing), placingOp.Operation) + + for _, sql := range []string{ + "SELECT overlay('Hello world' PLACING 'SQL' FROM 7)", + "SELECT overlayUTF8(s PLACING r FROM x + 1 FOR len(y)) FROM t", + "SELECT overlay(a, b, c) FROM t", + } { + require.Equal(t, sql, Format(parseOneStmt(t, sql)), sql) + } + + // Separators only count in the declared order, and both PLACING and FROM + // are required — ClickHouse rejects overlay(s PLACING r). + for _, sql := range []string{ + "SELECT overlay(s FROM 2 PLACING r)", + "SELECT overlay(s FOR 3)", + "SELECT overlay('Hello world' PLACING 'SQL')", + } { + _, err := NewParser(sql).ParseStmts() + require.Error(t, err, sql) + } + + // Unlike substring, overlay cannot mix commas with its keyword form in + // either direction. + for _, sql := range []string{ + "SELECT overlay('Hello world' PLACING 'SQL' FROM 1, 2)", + "SELECT overlay('Hello world' PLACING 'SQL' FROM 1 FOR 2, 3)", + "SELECT overlay('Hello world', 'SQL' FROM 1)", + "SELECT overlay('Hello world', 'SQL', 1 FOR 2)", + "SELECT overlayUTF8('Hello world' PLACING 'SQL' FROM 1, 2)", + "SELECT overlayUTF8('Hello world', 'SQL' FROM 1)", + } { + _, err := NewParser(sql).ParseStmts() + require.Error(t, err, sql) + } + + // The all-comma form keeps its own arity. + for _, sql := range []string{ + "SELECT overlay('Hello world', 'SQL', 1)", + "SELECT overlay('Hello world', 'SQL', 1, 2)", + } { + require.Equal(t, sql, Format(parseOneStmt(t, sql)), sql) + } + + // OVERLAY and PLACING are non-reserved, so they stay usable as names. + for _, sql := range []string{"SELECT overlay, placing FROM t", "SELECT a AS overlay FROM t", "SELECT max(placing) FROM t"} { + require.Equal(t, sql, Format(parseOneStmt(t, sql)), sql) + } +} + func TestIntersect(t *testing.T) { stmts, err := NewParser("SELECT 1 INTERSECT SELECT 2").ParseStmts() require.NoError(t, err) diff --git a/parser/testdata/query/format/beautify/select_function_keyword_args.sql b/parser/testdata/query/format/beautify/select_function_keyword_args.sql new file mode 100644 index 0000000..a0722be --- /dev/null +++ b/parser/testdata/query/format/beautify/select_function_keyword_args.sql @@ -0,0 +1,83 @@ +-- Origin SQL: +SELECT trim(BOTH ' ' FROM ' x '); +SELECT trim(LEADING '0' FROM '00042'); +SELECT trim(TRAILING '/' FROM '/api/'); +SELECT substring('hello' FROM 2); +SELECT substring('hello' FROM 2 FOR 3); +SELECT substring('hello', 2 FOR 3); +SELECT substring('hello' FROM 2, 3); +SELECT substring(concat(a, b) FROM x + 1 FOR len(y)) FROM t; +SELECT overlay('Hello world' PLACING 'SQL' FROM 7); +SELECT overlay('Hello world' PLACING 'SQL' FROM 7 FOR 5); +SELECT overlayUTF8(s PLACING r FROM x + 1 FOR len(y)) FROM t; +SELECT max(both), a AS placing FROM t; +SELECT overlay, placing FROM t; +SELECT trim('x' AS y), substring('hello' FROM 2 AS s); +SELECT trimBoth('xxhixx', 'x'); +SELECT trimLeft('xxhi', 'x'), trimRight('hixx', 'x'); +SELECT ltrim(' x '), rtrim(' x '); +SELECT position('a' IN 'abc'), locate('a' IN 'abc'); +SELECT CAST('1' AS UInt64); +SELECT substringUTF8('hello', 2, 3), substr('hello', 2, 3); +SELECT mid('hello', 2, 3), byteSlice('hello', 2, 3); + + +-- Beautify SQL: +SELECT + trim(BOTH ' ' FROM ' x '); +SELECT + trim(LEADING '0' FROM '00042'); +SELECT + trim(TRAILING '/' FROM '/api/'); +SELECT + substring('hello' FROM 2); +SELECT + substring('hello' FROM 2 FOR 3); +SELECT + substring('hello', 2 FOR 3); +SELECT + substring('hello' FROM 2, 3); +SELECT + substring(concat(a, b) FROM x + 1 FOR len(y)) +FROM + t; +SELECT + overlay('Hello world' PLACING 'SQL' FROM 7); +SELECT + overlay('Hello world' PLACING 'SQL' FROM 7 FOR 5); +SELECT + overlayUTF8(s PLACING r FROM x + 1 FOR len(y)) +FROM + t; +SELECT + max(both), + a AS placing +FROM + t; +SELECT + overlay, + placing +FROM + t; +SELECT + trim('x' AS y), + substring('hello' FROM 2 AS s); +SELECT + trimBoth('xxhixx', 'x'); +SELECT + trimLeft('xxhi', 'x'), + trimRight('hixx', 'x'); +SELECT + ltrim(' x '), + rtrim(' x '); +SELECT + position('a' IN 'abc'), + locate('a' IN 'abc'); +SELECT + CAST('1' AS UInt64); +SELECT + substringUTF8('hello', 2, 3), + substr('hello', 2, 3); +SELECT + mid('hello', 2, 3), + byteSlice('hello', 2, 3); diff --git a/parser/testdata/query/format/select_function_keyword_args.sql b/parser/testdata/query/format/select_function_keyword_args.sql new file mode 100644 index 0000000..b09b401 --- /dev/null +++ b/parser/testdata/query/format/select_function_keyword_args.sql @@ -0,0 +1,46 @@ +-- Origin SQL: +SELECT trim(BOTH ' ' FROM ' x '); +SELECT trim(LEADING '0' FROM '00042'); +SELECT trim(TRAILING '/' FROM '/api/'); +SELECT substring('hello' FROM 2); +SELECT substring('hello' FROM 2 FOR 3); +SELECT substring('hello', 2 FOR 3); +SELECT substring('hello' FROM 2, 3); +SELECT substring(concat(a, b) FROM x + 1 FOR len(y)) FROM t; +SELECT overlay('Hello world' PLACING 'SQL' FROM 7); +SELECT overlay('Hello world' PLACING 'SQL' FROM 7 FOR 5); +SELECT overlayUTF8(s PLACING r FROM x + 1 FOR len(y)) FROM t; +SELECT max(both), a AS placing FROM t; +SELECT overlay, placing FROM t; +SELECT trim('x' AS y), substring('hello' FROM 2 AS s); +SELECT trimBoth('xxhixx', 'x'); +SELECT trimLeft('xxhi', 'x'), trimRight('hixx', 'x'); +SELECT ltrim(' x '), rtrim(' x '); +SELECT position('a' IN 'abc'), locate('a' IN 'abc'); +SELECT CAST('1' AS UInt64); +SELECT substringUTF8('hello', 2, 3), substr('hello', 2, 3); +SELECT mid('hello', 2, 3), byteSlice('hello', 2, 3); + + +-- Format SQL: +SELECT trim(BOTH ' ' FROM ' x '); +SELECT trim(LEADING '0' FROM '00042'); +SELECT trim(TRAILING '/' FROM '/api/'); +SELECT substring('hello' FROM 2); +SELECT substring('hello' FROM 2 FOR 3); +SELECT substring('hello', 2 FOR 3); +SELECT substring('hello' FROM 2, 3); +SELECT substring(concat(a, b) FROM x + 1 FOR len(y)) FROM t; +SELECT overlay('Hello world' PLACING 'SQL' FROM 7); +SELECT overlay('Hello world' PLACING 'SQL' FROM 7 FOR 5); +SELECT overlayUTF8(s PLACING r FROM x + 1 FOR len(y)) FROM t; +SELECT max(both), a AS placing FROM t; +SELECT overlay, placing FROM t; +SELECT trim('x' AS y), substring('hello' FROM 2 AS s); +SELECT trimBoth('xxhixx', 'x'); +SELECT trimLeft('xxhi', 'x'), trimRight('hixx', 'x'); +SELECT ltrim(' x '), rtrim(' x '); +SELECT position('a' IN 'abc'), locate('a' IN 'abc'); +SELECT CAST('1' AS UInt64); +SELECT substringUTF8('hello', 2, 3), substr('hello', 2, 3); +SELECT mid('hello', 2, 3), byteSlice('hello', 2, 3); diff --git a/parser/testdata/query/output/select_function_keyword_args.sql.golden.json b/parser/testdata/query/output/select_function_keyword_args.sql.golden.json new file mode 100644 index 0000000..76eff19 --- /dev/null +++ b/parser/testdata/query/output/select_function_keyword_args.sql.golden.json @@ -0,0 +1,1956 @@ +[ + { + "SelectPos": 0, + "StatementEnd": 31, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "trim", + "QuoteType": 1, + "NamePos": 7, + "NameEnd": 11 + }, + "Params": { + "LeftParenPos": 11, + "RightParenPos": 31, + "Items": { + "ListPos": 12, + "ListEnd": 30, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "UnaryPos": 12, + "Kind": "BOTH", + "Expr": { + "LiteralPos": 18, + "LiteralEnd": 19, + "Literal": " " + } + }, + "Operation": "FROM", + "RightExpr": { + "LiteralPos": 27, + "LiteralEnd": 30, + "Literal": " x " + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 34, + "StatementEnd": 70, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "trim", + "QuoteType": 1, + "NamePos": 41, + "NameEnd": 45 + }, + "Params": { + "LeftParenPos": 45, + "RightParenPos": 70, + "Items": { + "ListPos": 46, + "ListEnd": 69, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "UnaryPos": 46, + "Kind": "LEADING", + "Expr": { + "LiteralPos": 55, + "LiteralEnd": 56, + "Literal": "0" + } + }, + "Operation": "FROM", + "RightExpr": { + "LiteralPos": 64, + "LiteralEnd": 69, + "Literal": "00042" + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 73, + "StatementEnd": 110, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "trim", + "QuoteType": 1, + "NamePos": 80, + "NameEnd": 84 + }, + "Params": { + "LeftParenPos": 84, + "RightParenPos": 110, + "Items": { + "ListPos": 85, + "ListEnd": 109, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "UnaryPos": 85, + "Kind": "TRAILING", + "Expr": { + "LiteralPos": 95, + "LiteralEnd": 96, + "Literal": "/" + } + }, + "Operation": "FROM", + "RightExpr": { + "LiteralPos": 104, + "LiteralEnd": 109, + "Literal": "/api/" + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 113, + "StatementEnd": 144, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "substring", + "QuoteType": 1, + "NamePos": 120, + "NameEnd": 129 + }, + "Params": { + "LeftParenPos": 129, + "RightParenPos": 144, + "Items": { + "ListPos": 131, + "ListEnd": 144, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "LiteralPos": 131, + "LiteralEnd": 136, + "Literal": "hello" + }, + "Operation": "FROM", + "RightExpr": { + "NumPos": 143, + "NumEnd": 144, + "Literal": "2", + "Base": 10 + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 147, + "StatementEnd": 184, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "substring", + "QuoteType": 1, + "NamePos": 154, + "NameEnd": 163 + }, + "Params": { + "LeftParenPos": 163, + "RightParenPos": 184, + "Items": { + "ListPos": 165, + "ListEnd": 184, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "LeftExpr": { + "LiteralPos": 165, + "LiteralEnd": 170, + "Literal": "hello" + }, + "Operation": "FROM", + "RightExpr": { + "NumPos": 177, + "NumEnd": 178, + "Literal": "2", + "Base": 10 + }, + "HasGlobal": false, + "HasNot": false + }, + "Operation": "FOR", + "RightExpr": { + "NumPos": 183, + "NumEnd": 184, + "Literal": "3", + "Base": 10 + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 187, + "StatementEnd": 220, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "substring", + "QuoteType": 1, + "NamePos": 194, + "NameEnd": 203 + }, + "Params": { + "LeftParenPos": 203, + "RightParenPos": 220, + "Items": { + "ListPos": 205, + "ListEnd": 220, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 205, + "LiteralEnd": 210, + "Literal": "hello" + }, + "Alias": null + }, + { + "Expr": { + "LeftExpr": { + "NumPos": 213, + "NumEnd": 214, + "Literal": "2", + "Base": 10 + }, + "Operation": "FOR", + "RightExpr": { + "NumPos": 219, + "NumEnd": 220, + "Literal": "3", + "Base": 10 + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 223, + "StatementEnd": 257, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "substring", + "QuoteType": 1, + "NamePos": 230, + "NameEnd": 239 + }, + "Params": { + "LeftParenPos": 239, + "RightParenPos": 257, + "Items": { + "ListPos": 241, + "ListEnd": 257, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "LiteralPos": 241, + "LiteralEnd": 246, + "Literal": "hello" + }, + "Operation": "FROM", + "RightExpr": { + "NumPos": 253, + "NumEnd": 254, + "Literal": "2", + "Base": 10 + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + }, + { + "Expr": { + "NumPos": 256, + "NumEnd": 257, + "Literal": "3", + "Base": 10 + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 260, + "StatementEnd": 319, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "substring", + "QuoteType": 1, + "NamePos": 267, + "NameEnd": 276 + }, + "Params": { + "LeftParenPos": 276, + "RightParenPos": 311, + "Items": { + "ListPos": 277, + "ListEnd": 310, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "LeftExpr": { + "Name": { + "Name": "concat", + "QuoteType": 1, + "NamePos": 277, + "NameEnd": 283 + }, + "Params": { + "LeftParenPos": 283, + "RightParenPos": 288, + "Items": { + "ListPos": 284, + "ListEnd": 288, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "Name": "a", + "QuoteType": 1, + "NamePos": 284, + "NameEnd": 285 + }, + "Alias": null + }, + { + "Expr": { + "Name": "b", + "QuoteType": 1, + "NamePos": 287, + "NameEnd": 288 + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Operation": "FROM", + "RightExpr": { + "LeftExpr": { + "Name": "x", + "QuoteType": 1, + "NamePos": 295, + "NameEnd": 296 + }, + "Operation": "+", + "RightExpr": { + "NumPos": 299, + "NumEnd": 300, + "Literal": "1", + "Base": 10 + }, + "HasGlobal": false, + "HasNot": false + }, + "HasGlobal": false, + "HasNot": false + }, + "Operation": "FOR", + "RightExpr": { + "Name": { + "Name": "len", + "QuoteType": 1, + "NamePos": 305, + "NameEnd": 308 + }, + "Params": { + "LeftParenPos": 308, + "RightParenPos": 310, + "Items": { + "ListPos": 309, + "ListEnd": 310, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "Name": "y", + "QuoteType": 1, + "NamePos": 309, + "NameEnd": 310 + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Modifiers": [], + "Alias": null + } + ], + "From": { + "FromPos": 313, + "Expr": { + "Table": { + "TablePos": 318, + "TableEnd": 319, + "Alias": null, + "Expr": { + "Database": null, + "Table": { + "Name": "t", + "QuoteType": 1, + "NamePos": 318, + "NameEnd": 319 + } + }, + "HasFinal": false + }, + "StatementEnd": 319, + "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, + "Intersect": null + }, + { + "SelectPos": 321, + "StatementEnd": 370, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "overlay", + "QuoteType": 1, + "NamePos": 328, + "NameEnd": 335 + }, + "Params": { + "LeftParenPos": 335, + "RightParenPos": 370, + "Items": { + "ListPos": 337, + "ListEnd": 370, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "LeftExpr": { + "LiteralPos": 337, + "LiteralEnd": 348, + "Literal": "Hello world" + }, + "Operation": "PLACING", + "RightExpr": { + "LiteralPos": 359, + "LiteralEnd": 362, + "Literal": "SQL" + }, + "HasGlobal": false, + "HasNot": false + }, + "Operation": "FROM", + "RightExpr": { + "NumPos": 369, + "NumEnd": 370, + "Literal": "7", + "Base": 10 + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 373, + "StatementEnd": 428, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "overlay", + "QuoteType": 1, + "NamePos": 380, + "NameEnd": 387 + }, + "Params": { + "LeftParenPos": 387, + "RightParenPos": 428, + "Items": { + "ListPos": 389, + "ListEnd": 428, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "LeftExpr": { + "LeftExpr": { + "LiteralPos": 389, + "LiteralEnd": 400, + "Literal": "Hello world" + }, + "Operation": "PLACING", + "RightExpr": { + "LiteralPos": 411, + "LiteralEnd": 414, + "Literal": "SQL" + }, + "HasGlobal": false, + "HasNot": false + }, + "Operation": "FROM", + "RightExpr": { + "NumPos": 421, + "NumEnd": 422, + "Literal": "7", + "Base": 10 + }, + "HasGlobal": false, + "HasNot": false + }, + "Operation": "FOR", + "RightExpr": { + "NumPos": 427, + "NumEnd": 428, + "Literal": "5", + "Base": 10 + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 431, + "StatementEnd": 491, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "overlayUTF8", + "QuoteType": 1, + "NamePos": 438, + "NameEnd": 449 + }, + "Params": { + "LeftParenPos": 449, + "RightParenPos": 483, + "Items": { + "ListPos": 450, + "ListEnd": 482, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "LeftExpr": { + "LeftExpr": { + "Name": "s", + "QuoteType": 1, + "NamePos": 450, + "NameEnd": 451 + }, + "Operation": "PLACING", + "RightExpr": { + "Name": "r", + "QuoteType": 1, + "NamePos": 460, + "NameEnd": 461 + }, + "HasGlobal": false, + "HasNot": false + }, + "Operation": "FROM", + "RightExpr": { + "LeftExpr": { + "Name": "x", + "QuoteType": 1, + "NamePos": 467, + "NameEnd": 468 + }, + "Operation": "+", + "RightExpr": { + "NumPos": 471, + "NumEnd": 472, + "Literal": "1", + "Base": 10 + }, + "HasGlobal": false, + "HasNot": false + }, + "HasGlobal": false, + "HasNot": false + }, + "Operation": "FOR", + "RightExpr": { + "Name": { + "Name": "len", + "QuoteType": 1, + "NamePos": 477, + "NameEnd": 480 + }, + "Params": { + "LeftParenPos": 480, + "RightParenPos": 482, + "Items": { + "ListPos": 481, + "ListEnd": 482, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "Name": "y", + "QuoteType": 1, + "NamePos": 481, + "NameEnd": 482 + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Modifiers": [], + "Alias": null + } + ], + "From": { + "FromPos": 485, + "Expr": { + "Table": { + "TablePos": 490, + "TableEnd": 491, + "Alias": null, + "Expr": { + "Database": null, + "Table": { + "Name": "t", + "QuoteType": 1, + "NamePos": 490, + "NameEnd": 491 + } + }, + "HasFinal": false + }, + "StatementEnd": 491, + "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, + "Intersect": null + }, + { + "SelectPos": 493, + "StatementEnd": 530, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "max", + "QuoteType": 1, + "NamePos": 500, + "NameEnd": 503 + }, + "Params": { + "LeftParenPos": 503, + "RightParenPos": 508, + "Items": { + "ListPos": 504, + "ListEnd": 508, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "Name": "both", + "QuoteType": 1, + "NamePos": 504, + "NameEnd": 508 + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "a", + "QuoteType": 1, + "NamePos": 511, + "NameEnd": 512 + }, + "Modifiers": [], + "Alias": { + "Name": "placing", + "QuoteType": 1, + "NamePos": 516, + "NameEnd": 523 + } + } + ], + "From": { + "FromPos": 524, + "Expr": { + "Table": { + "TablePos": 529, + "TableEnd": 530, + "Alias": null, + "Expr": { + "Database": null, + "Table": { + "Name": "t", + "QuoteType": 1, + "NamePos": 529, + "NameEnd": 530 + } + }, + "HasFinal": false + }, + "StatementEnd": 530, + "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, + "Intersect": null + }, + { + "SelectPos": 532, + "StatementEnd": 562, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": "overlay", + "QuoteType": 1, + "NamePos": 539, + "NameEnd": 546 + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": "placing", + "QuoteType": 1, + "NamePos": 548, + "NameEnd": 555 + }, + "Modifiers": [], + "Alias": null + } + ], + "From": { + "FromPos": 556, + "Expr": { + "Table": { + "TablePos": 561, + "TableEnd": 562, + "Alias": null, + "Expr": { + "Database": null, + "Table": { + "Name": "t", + "QuoteType": 1, + "NamePos": 561, + "NameEnd": 562 + } + }, + "HasFinal": false + }, + "StatementEnd": 562, + "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, + "Intersect": null + }, + { + "SelectPos": 564, + "StatementEnd": 616, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "trim", + "QuoteType": 1, + "NamePos": 571, + "NameEnd": 575 + }, + "Params": { + "LeftParenPos": 575, + "RightParenPos": 584, + "Items": { + "ListPos": 577, + "ListEnd": 584, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 577, + "LiteralEnd": 578, + "Literal": "x" + }, + "Alias": { + "Name": "y", + "QuoteType": 1, + "NamePos": 583, + "NameEnd": 584 + } + } + ] + }, + "ColumnArgList": null + } + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": { + "Name": "substring", + "QuoteType": 1, + "NamePos": 587, + "NameEnd": 596 + }, + "Params": { + "LeftParenPos": 596, + "RightParenPos": 616, + "Items": { + "ListPos": 598, + "ListEnd": 616, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "LiteralPos": 598, + "LiteralEnd": 603, + "Literal": "hello" + }, + "Operation": "FROM", + "RightExpr": { + "NumPos": 610, + "NumEnd": 611, + "Literal": "2", + "Base": 10 + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": { + "Name": "s", + "QuoteType": 1, + "NamePos": 615, + "NameEnd": 616 + } + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 619, + "StatementEnd": 648, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "trimBoth", + "QuoteType": 1, + "NamePos": 626, + "NameEnd": 634 + }, + "Params": { + "LeftParenPos": 634, + "RightParenPos": 648, + "Items": { + "ListPos": 636, + "ListEnd": 647, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 636, + "LiteralEnd": 642, + "Literal": "xxhixx" + }, + "Alias": null + }, + { + "Expr": { + "LiteralPos": 646, + "LiteralEnd": 647, + "Literal": "x" + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 651, + "StatementEnd": 702, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "trimLeft", + "QuoteType": 1, + "NamePos": 658, + "NameEnd": 666 + }, + "Params": { + "LeftParenPos": 666, + "RightParenPos": 678, + "Items": { + "ListPos": 668, + "ListEnd": 677, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 668, + "LiteralEnd": 672, + "Literal": "xxhi" + }, + "Alias": null + }, + { + "Expr": { + "LiteralPos": 676, + "LiteralEnd": 677, + "Literal": "x" + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": { + "Name": "trimRight", + "QuoteType": 1, + "NamePos": 681, + "NameEnd": 690 + }, + "Params": { + "LeftParenPos": 690, + "RightParenPos": 702, + "Items": { + "ListPos": 692, + "ListEnd": 701, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 692, + "LiteralEnd": 696, + "Literal": "hixx" + }, + "Alias": null + }, + { + "Expr": { + "LiteralPos": 700, + "LiteralEnd": 701, + "Literal": "x" + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 705, + "StatementEnd": 741, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "ltrim", + "QuoteType": 1, + "NamePos": 712, + "NameEnd": 717 + }, + "Params": { + "LeftParenPos": 717, + "RightParenPos": 725, + "Items": { + "ListPos": 719, + "ListEnd": 724, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 719, + "LiteralEnd": 724, + "Literal": " x " + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": { + "Name": "rtrim", + "QuoteType": 1, + "NamePos": 728, + "NameEnd": 733 + }, + "Params": { + "LeftParenPos": 733, + "RightParenPos": 741, + "Items": { + "ListPos": 735, + "ListEnd": 740, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 735, + "LiteralEnd": 740, + "Literal": " x " + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 744, + "StatementEnd": 794, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "position", + "QuoteType": 1, + "NamePos": 751, + "NameEnd": 759 + }, + "Params": { + "LeftParenPos": 759, + "RightParenPos": 772, + "Items": { + "ListPos": 761, + "ListEnd": 771, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "LiteralPos": 761, + "LiteralEnd": 762, + "Literal": "a" + }, + "Operation": "IN", + "RightExpr": { + "LiteralPos": 768, + "LiteralEnd": 771, + "Literal": "abc" + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": { + "Name": "locate", + "QuoteType": 1, + "NamePos": 775, + "NameEnd": 781 + }, + "Params": { + "LeftParenPos": 781, + "RightParenPos": 794, + "Items": { + "ListPos": 783, + "ListEnd": 793, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "LiteralPos": 783, + "LiteralEnd": 784, + "Literal": "a" + }, + "Operation": "IN", + "RightExpr": { + "LiteralPos": 790, + "LiteralEnd": 793, + "Literal": "abc" + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 797, + "StatementEnd": 822, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "CastPos": 804, + "Expr": { + "LiteralPos": 810, + "LiteralEnd": 811, + "Literal": "1" + }, + "Separator": "AS", + "AsPos": 813, + "AsType": { + "Name": { + "Name": "UInt64", + "QuoteType": 1, + "NamePos": 816, + "NameEnd": 822 + } + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 825, + "StatementEnd": 882, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "substringUTF8", + "QuoteType": 1, + "NamePos": 832, + "NameEnd": 845 + }, + "Params": { + "LeftParenPos": 845, + "RightParenPos": 859, + "Items": { + "ListPos": 847, + "ListEnd": 859, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 847, + "LiteralEnd": 852, + "Literal": "hello" + }, + "Alias": null + }, + { + "Expr": { + "NumPos": 855, + "NumEnd": 856, + "Literal": "2", + "Base": 10 + }, + "Alias": null + }, + { + "Expr": { + "NumPos": 858, + "NumEnd": 859, + "Literal": "3", + "Base": 10 + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": { + "Name": "substr", + "QuoteType": 1, + "NamePos": 862, + "NameEnd": 868 + }, + "Params": { + "LeftParenPos": 868, + "RightParenPos": 882, + "Items": { + "ListPos": 870, + "ListEnd": 882, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 870, + "LiteralEnd": 875, + "Literal": "hello" + }, + "Alias": null + }, + { + "Expr": { + "NumPos": 878, + "NumEnd": 879, + "Literal": "2", + "Base": 10 + }, + "Alias": null + }, + { + "Expr": { + "NumPos": 881, + "NumEnd": 882, + "Literal": "3", + "Base": 10 + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + }, + { + "SelectPos": 885, + "StatementEnd": 935, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": { + "Name": "mid", + "QuoteType": 1, + "NamePos": 892, + "NameEnd": 895 + }, + "Params": { + "LeftParenPos": 895, + "RightParenPos": 909, + "Items": { + "ListPos": 897, + "ListEnd": 909, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 897, + "LiteralEnd": 902, + "Literal": "hello" + }, + "Alias": null + }, + { + "Expr": { + "NumPos": 905, + "NumEnd": 906, + "Literal": "2", + "Base": 10 + }, + "Alias": null + }, + { + "Expr": { + "NumPos": 908, + "NumEnd": 909, + "Literal": "3", + "Base": 10 + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "Modifiers": [], + "Alias": null + }, + { + "Expr": { + "Name": { + "Name": "byteSlice", + "QuoteType": 1, + "NamePos": 912, + "NameEnd": 921 + }, + "Params": { + "LeftParenPos": 921, + "RightParenPos": 935, + "Items": { + "ListPos": 923, + "ListEnd": 935, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LiteralPos": 923, + "LiteralEnd": 928, + "Literal": "hello" + }, + "Alias": null + }, + { + "Expr": { + "NumPos": 931, + "NumEnd": 932, + "Literal": "2", + "Base": 10 + }, + "Alias": null + }, + { + "Expr": { + "NumPos": 934, + "NumEnd": 935, + "Literal": "3", + "Base": 10 + }, + "Alias": null + } + ] + }, + "ColumnArgList": null + } + }, + "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, + "Intersect": null + } +] \ No newline at end of file diff --git a/parser/testdata/query/select_function_keyword_args.sql b/parser/testdata/query/select_function_keyword_args.sql new file mode 100644 index 0000000..5018d30 --- /dev/null +++ b/parser/testdata/query/select_function_keyword_args.sql @@ -0,0 +1,21 @@ +SELECT trim(BOTH ' ' FROM ' x '); +SELECT trim(LEADING '0' FROM '00042'); +SELECT trim(TRAILING '/' FROM '/api/'); +SELECT substring('hello' FROM 2); +SELECT substring('hello' FROM 2 FOR 3); +SELECT substring('hello', 2 FOR 3); +SELECT substring('hello' FROM 2, 3); +SELECT substring(concat(a, b) FROM x + 1 FOR len(y)) FROM t; +SELECT overlay('Hello world' PLACING 'SQL' FROM 7); +SELECT overlay('Hello world' PLACING 'SQL' FROM 7 FOR 5); +SELECT overlayUTF8(s PLACING r FROM x + 1 FOR len(y)) FROM t; +SELECT max(both), a AS placing FROM t; +SELECT overlay, placing FROM t; +SELECT trim('x' AS y), substring('hello' FROM 2 AS s); +SELECT trimBoth('xxhixx', 'x'); +SELECT trimLeft('xxhi', 'x'), trimRight('hixx', 'x'); +SELECT ltrim(' x '), rtrim(' x '); +SELECT position('a' IN 'abc'), locate('a' IN 'abc'); +SELECT CAST('1' AS UInt64); +SELECT substringUTF8('hello', 2, 3), substr('hello', 2, 3); +SELECT mid('hello', 2, 3), byteSlice('hello', 2, 3); diff --git a/parser/walk_test.go b/parser/walk_test.go index 4c9fe08..0048b0a 100644 --- a/parser/walk_test.go +++ b/parser/walk_test.go @@ -267,3 +267,30 @@ func TestWalk_ShowStmtNewFields(t *testing.T) { require.Equal(t, "5", numberLiterals[0].Literal, "Should contain LIMIT value") } + +func TestWalk_KeywordArgFunction(t *testing.T) { + // The keyword argument forms build UnaryExpr/BinaryOperation rather than a + // node of their own, so Walk must reach every operand without new wiring. + sql := `SELECT trim(BOTH ' ' FROM s), overlay(a PLACING b FROM c FOR d);` + stmts, err := NewParser(sql).ParseStmts() + require.NoError(t, err) + require.Len(t, stmts, 1) + + var unary, binary, idents int + Walk(stmts[0], func(node Expr) bool { + switch node.(type) { + case *UnaryExpr: + unary++ + case *BinaryOperation: + binary++ + case *Ident: + idents++ + } + return true + }) + + require.Equal(t, 1, unary, "the BOTH modifier") + require.Equal(t, 4, binary, "trim's FROM plus overlay's PLACING/FROM/FOR") + // s, a, b, c, d plus the two function names. + require.Equal(t, 7, idents) +}