diff --git a/parser/parser_query.go b/parser/parser_query.go index 2e9721a..0c121ea 100644 --- a/parser/parser_query.go +++ b/parser/parser_query.go @@ -486,7 +486,7 @@ func (p *Parser) parseTableExpr(pos Pos) (*TableExpr, error) { return nil, err } // it's a table name - if tableIdentifier.Database != nil || !p.matchTokenKind(TokenKindLParen) { // database.table + if !p.matchTokenKind(TokenKindLParen) { // database.table expr = tableIdentifier } else { // table function expr @@ -494,8 +494,14 @@ func (p *Parser) parseTableExpr(pos Pos) (*TableExpr, error) { if err != nil { return nil, err } + // A parameterized view is called like a table function and can + // be qualified with its database: db.view(name = value). + var name Expr = tableIdentifier.Table + if tableIdentifier.Database != nil { + name = tableIdentifier + } expr = &TableFunctionExpr{ - Name: tableIdentifier.Table, + Name: name, Args: tableArgs, } } diff --git a/parser/parser_table.go b/parser/parser_table.go index 1da62a2..a0e558b 100644 --- a/parser/parser_table.go +++ b/parser/parser_table.go @@ -845,6 +845,11 @@ func (p *Parser) parseTableArgPrimaryExpr(pos Pos) (Expr, error) { case p.matchTokenKind(TokenKindInt), p.matchTokenKind(TokenKindFloat), p.matchTokenKind(TokenKindString), p.matchKeyword(KeywordNull): return p.parseLiteral(p.Pos()) + case p.matchTokenKind(TokenKindLBracket): + return p.parseArrayParams(p.Pos()) + case p.matchTokenKind(TokenKindLBrace): + // a query parameter such as {id:UInt64} + return p.parseQueryParam(p.Pos()) default: return nil, fmt.Errorf("unexpected token: %q, expected , ", p.currentTokenString()) } diff --git a/parser/parser_test.go b/parser/parser_test.go index 746be40..1e11b5f 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -299,6 +299,12 @@ func TestParser_InvalidSyntax(t *testing.T) { // a leading SEMI or ANTI takes LEFT or RIGHT only "SELECT * FROM t1 ANTI INNER JOIN t2 ON t1.a = t2.a", "SELECT * FROM t1 ANTI ANY LEFT JOIN t2 ON t1.a = t2.a", + // arguments of a parameterized view call + "SELECT * FROM db.v(a = {x})", // query parameter without a type + "SELECT * FROM db.v(a = {'k': 1})", // not a query parameter + "SELECT * FROM db.v(a = [1, )", // unterminated array + "SELECT * FROM db.v(a = 1)(b = 2)", // a call takes one argument list + "SELECT * FROM db.t (SELECT 1)", // a subquery cannot follow a table name } for _, sql := range invalidSQLs { parser := NewParser(sql) diff --git a/parser/testdata/query/format/beautify/select_from_parameterized_view.sql b/parser/testdata/query/format/beautify/select_from_parameterized_view.sql new file mode 100644 index 0000000..4f15796 --- /dev/null +++ b/parser/testdata/query/format/beautify/select_from_parameterized_view.sql @@ -0,0 +1,13 @@ +-- Origin SQL: +SELECT id +FROM db.v(min_id = {min_id:UInt64}, ids = [{id:UInt64}], since = toDateTime64({since:String}, 3)) AS a +INNER JOIN `db.v`(min_id = 1, ids = [1, 2], since = now()) AS b ON a.id = b.id + + +-- Beautify SQL: +SELECT + id +FROM + db.v(min_id={min_id: UInt64}, ids=[{id: UInt64}], since=toDateTime64({since: String}, 3)) AS a + INNER JOIN + `db.v`(min_id=1, ids=[1, 2], since=now()) AS b ON a.id = b.id; diff --git a/parser/testdata/query/format/select_from_parameterized_view.sql b/parser/testdata/query/format/select_from_parameterized_view.sql new file mode 100644 index 0000000..759decd --- /dev/null +++ b/parser/testdata/query/format/select_from_parameterized_view.sql @@ -0,0 +1,8 @@ +-- Origin SQL: +SELECT id +FROM db.v(min_id = {min_id:UInt64}, ids = [{id:UInt64}], since = toDateTime64({since:String}, 3)) AS a +INNER JOIN `db.v`(min_id = 1, ids = [1, 2], since = now()) AS b ON a.id = b.id + + +-- Format SQL: +SELECT id FROM db.v(min_id={min_id: UInt64}, ids=[{id: UInt64}], since=toDateTime64({since: String}, 3)) AS a INNER JOIN `db.v`(min_id=1, ids=[1, 2], since=now()) AS b ON a.id = b.id; diff --git a/parser/testdata/query/output/select_from_parameterized_view.sql.golden.json b/parser/testdata/query/output/select_from_parameterized_view.sql.golden.json new file mode 100644 index 0000000..9b6ff13 --- /dev/null +++ b/parser/testdata/query/output/select_from_parameterized_view.sql.golden.json @@ -0,0 +1,369 @@ +[ + { + "SelectPos": 0, + "StatementEnd": 191, + "With": null, + "Top": null, + "HasDistinct": false, + "DistinctOn": null, + "SelectItems": [ + { + "Expr": { + "Name": "id", + "QuoteType": 1, + "NamePos": 7, + "NameEnd": 9 + }, + "Modifiers": [], + "Alias": null + } + ], + "From": { + "FromPos": 10, + "Expr": { + "JoinPos": 15, + "Left": { + "Table": { + "TablePos": 15, + "TableEnd": 112, + "Alias": null, + "Expr": { + "Expr": { + "Name": { + "Database": { + "Name": "db", + "QuoteType": 1, + "NamePos": 15, + "NameEnd": 17 + }, + "Table": { + "Name": "v", + "QuoteType": 1, + "NamePos": 18, + "NameEnd": 19 + } + }, + "Args": { + "LeftParenPos": 19, + "RightParenPos": 106, + "Args": [ + { + "NamePos": 20, + "Name": { + "Name": "min_id", + "QuoteType": 0, + "NamePos": 20, + "NameEnd": 26 + }, + "Value": { + "LBracePos": 29, + "RBracePos": 43, + "Name": { + "Name": "min_id", + "QuoteType": 1, + "NamePos": 30, + "NameEnd": 36 + }, + "Type": { + "Name": { + "Name": "UInt64", + "QuoteType": 1, + "NamePos": 37, + "NameEnd": 43 + } + } + } + }, + { + "NamePos": 46, + "Name": { + "Name": "ids", + "QuoteType": 0, + "NamePos": 46, + "NameEnd": 49 + }, + "Value": { + "LeftBracketPos": 52, + "RightBracketPos": 64, + "Items": { + "ListPos": 53, + "ListEnd": 64, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LBracePos": 53, + "RBracePos": 63, + "Name": { + "Name": "id", + "QuoteType": 1, + "NamePos": 54, + "NameEnd": 56 + }, + "Type": { + "Name": { + "Name": "UInt64", + "QuoteType": 1, + "NamePos": 57, + "NameEnd": 63 + } + } + }, + "Alias": null + } + ] + } + } + }, + { + "NamePos": 67, + "Name": { + "Name": "since", + "QuoteType": 0, + "NamePos": 67, + "NameEnd": 72 + }, + "Value": { + "Name": { + "Name": "toDateTime64", + "QuoteType": 1, + "NamePos": 75, + "NameEnd": 87 + }, + "Args": { + "LeftParenPos": 75, + "RightParenPos": 105, + "Args": [ + { + "LBracePos": 88, + "RBracePos": 101, + "Name": { + "Name": "since", + "QuoteType": 1, + "NamePos": 89, + "NameEnd": 94 + }, + "Type": { + "Name": { + "Name": "String", + "QuoteType": 1, + "NamePos": 95, + "NameEnd": 101 + } + } + }, + { + "NumPos": 104, + "NumEnd": 105, + "Literal": "3", + "Base": 10 + } + ] + } + } + } + ] + } + }, + "AliasPos": 111, + "Alias": { + "Name": "a", + "QuoteType": 1, + "NamePos": 111, + "NameEnd": 112 + } + }, + "HasFinal": false + }, + "StatementEnd": 112, + "SampleRatio": null, + "HasFinal": false + }, + "Right": { + "JoinPos": 113, + "Left": { + "Table": { + "TablePos": 125, + "TableEnd": 176, + "Alias": null, + "Expr": { + "Expr": { + "Name": { + "Name": "db.v", + "QuoteType": 3, + "NamePos": 125, + "NameEnd": 129 + }, + "Args": { + "LeftParenPos": 130, + "RightParenPos": 170, + "Args": [ + { + "NamePos": 131, + "Name": { + "Name": "min_id", + "QuoteType": 0, + "NamePos": 131, + "NameEnd": 137 + }, + "Value": { + "NumPos": 140, + "NumEnd": 141, + "Literal": "1", + "Base": 10 + } + }, + { + "NamePos": 143, + "Name": { + "Name": "ids", + "QuoteType": 0, + "NamePos": 143, + "NameEnd": 146 + }, + "Value": { + "LeftBracketPos": 149, + "RightBracketPos": 154, + "Items": { + "ListPos": 150, + "ListEnd": 154, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "NumPos": 150, + "NumEnd": 151, + "Literal": "1", + "Base": 10 + }, + "Alias": null + }, + { + "Expr": { + "NumPos": 153, + "NumEnd": 154, + "Literal": "2", + "Base": 10 + }, + "Alias": null + } + ] + } + } + }, + { + "NamePos": 157, + "Name": { + "Name": "since", + "QuoteType": 0, + "NamePos": 157, + "NameEnd": 162 + }, + "Value": { + "Name": { + "Name": "now", + "QuoteType": 1, + "NamePos": 165, + "NameEnd": 168 + }, + "Args": { + "LeftParenPos": 165, + "RightParenPos": 169, + "Args": [] + } + } + } + ] + } + }, + "AliasPos": 175, + "Alias": { + "Name": "b", + "QuoteType": 1, + "NamePos": 175, + "NameEnd": 176 + } + }, + "HasFinal": false + }, + "StatementEnd": 176, + "SampleRatio": null, + "HasFinal": false + }, + "Right": null, + "Modifiers": [ + "INNER", + "JOIN" + ], + "Constraints": { + "OnPos": 177, + "On": { + "ListPos": 180, + "ListEnd": 191, + "HasDistinct": false, + "Items": [ + { + "Expr": { + "LeftExpr": { + "Fields": [ + { + "Name": "a", + "QuoteType": 1, + "NamePos": 180, + "NameEnd": 181 + }, + { + "Name": "id", + "QuoteType": 1, + "NamePos": 182, + "NameEnd": 184 + } + ] + }, + "Operation": "=", + "RightExpr": { + "Fields": [ + { + "Name": "b", + "QuoteType": 1, + "NamePos": 187, + "NameEnd": 188 + }, + { + "Name": "id", + "QuoteType": 1, + "NamePos": 189, + "NameEnd": 191 + } + ] + }, + "HasGlobal": false, + "HasNot": false + }, + "Alias": null + } + ] + } + } + }, + "Modifiers": null, + "Constraints": 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_from_parameterized_view.sql b/parser/testdata/query/select_from_parameterized_view.sql new file mode 100644 index 0000000..4aa7615 --- /dev/null +++ b/parser/testdata/query/select_from_parameterized_view.sql @@ -0,0 +1,3 @@ +SELECT id +FROM db.v(min_id = {min_id:UInt64}, ids = [{id:UInt64}], since = toDateTime64({since:String}, 3)) AS a +INNER JOIN `db.v`(min_id = 1, ids = [1, 2], since = now()) AS b ON a.id = b.id