Skip to content

Commit db2e211

Browse files
committed
Add PARALLEL WITH statement chaining and fix EXISTS TABLE explain format
- Add PARALLEL token to lexer keywords - Add ParallelWithQuery AST node for chaining statements with PARALLEL WITH - Add parseParallelWith in parser to handle statement chaining - Fix table expression alias handling to not consume PARALLEL when followed by WITH - Fix ExistsTableQuery explain output to match ClickHouse format (space alignment for missing database) Tests fixed: - 03305_parallel_with (all statements) - 03604_parallel_with_query_lock (all statements) - 01048_exists_query (all statements) - 00101_materialized_views_and_insert_without_explicit_database (exists-related) - 01073_attach_if_not_exists
1 parent 838f6b4 commit db2e211

11 files changed

Lines changed: 87 additions & 34 deletions

File tree

ast/ast.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,3 +1597,13 @@ type ExistsExpr struct {
15971597
func (e *ExistsExpr) Pos() token.Position { return e.Position }
15981598
func (e *ExistsExpr) End() token.Position { return e.Position }
15991599
func (e *ExistsExpr) expressionNode() {}
1600+
1601+
// ParallelWithQuery represents multiple statements executed in parallel with PARALLEL WITH.
1602+
type ParallelWithQuery struct {
1603+
Position token.Position `json:"-"`
1604+
Statements []Statement `json:"statements"`
1605+
}
1606+
1607+
func (p *ParallelWithQuery) Pos() token.Position { return p.Position }
1608+
func (p *ParallelWithQuery) End() token.Position { return p.Position }
1609+
func (p *ParallelWithQuery) statementNode() {}

internal/explain/explain.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
246246
explainCreateIndexQuery(sb, n, indent, depth)
247247
case *ast.UpdateQuery:
248248
explainUpdateQuery(sb, n, indent, depth)
249+
case *ast.ParallelWithQuery:
250+
explainParallelWithQuery(sb, n, indent, depth)
249251

250252
// Types
251253
case *ast.DataType:

internal/explain/statements.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,7 @@ func explainExistsTableQuery(sb *strings.Builder, n *ast.ExistsQuery, indent str
10061006
}
10071007

10081008
// For TABLE/DICTIONARY/VIEW, show database and object name
1009-
name := n.Table
1009+
name := " " + n.Table // Prefix with space for alignment (where database would be)
10101010
children := 1
10111011
if n.Database != "" {
10121012
name = n.Database + " " + n.Table
@@ -1690,3 +1690,39 @@ func explainUpdateQuery(sb *strings.Builder, n *ast.UpdateQuery, indent string,
16901690
Node(sb, assign, depth+2)
16911691
}
16921692
}
1693+
1694+
func explainParallelWithQuery(sb *strings.Builder, n *ast.ParallelWithQuery, indent string, depth int) {
1695+
if n == nil || len(n.Statements) == 0 {
1696+
fmt.Fprintf(sb, "%sParallelWithQuery\n", indent)
1697+
return
1698+
}
1699+
1700+
// Build the name from the first statement
1701+
name := getParallelWithName(n.Statements[0])
1702+
count := len(n.Statements)
1703+
1704+
fmt.Fprintf(sb, "%sParallelWithQuery %d %s (children %d)\n", indent, count, name, count)
1705+
1706+
for _, stmt := range n.Statements {
1707+
Node(sb, stmt, depth+1)
1708+
}
1709+
}
1710+
1711+
func getParallelWithName(stmt ast.Statement) string {
1712+
switch s := stmt.(type) {
1713+
case *ast.DropQuery:
1714+
tableName := ""
1715+
if len(s.Tables) > 0 {
1716+
if s.Tables[0].Table != "" {
1717+
tableName = s.Tables[0].Table
1718+
}
1719+
}
1720+
return "DropQuery__" + tableName
1721+
case *ast.CreateQuery:
1722+
return "CreateQuery_" + s.Table
1723+
case *ast.InsertQuery:
1724+
return "InsertQuery__"
1725+
default:
1726+
return "Statement"
1727+
}
1728+
}

parser/expression.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,10 @@ func (p *Parser) parseImplicitAlias(expr ast.Expression) ast.Expression {
272272
if upper == "INTERSECT" {
273273
return expr
274274
}
275+
// Don't consume PARALLEL as alias if followed by WITH (parallel query syntax)
276+
if p.currentIs(token.PARALLEL) && p.peekIs(token.WITH) {
277+
return expr
278+
}
275279
// Don't consume window frame keywords as implicit aliases
276280
switch upper {
277281
case "ROWS", "RANGE", "GROUPS", "UNBOUNDED", "PRECEDING", "FOLLOWING", "CURRENT":

parser/parser.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ func (p *Parser) ParseStatements(ctx context.Context) ([]ast.Statement, error) {
9999

100100
stmt := p.parseStatement()
101101
if stmt != nil {
102+
// Check for PARALLEL WITH to chain statements
103+
if p.currentIs(token.PARALLEL) && p.peekIs(token.WITH) {
104+
stmt = p.parseParallelWith(stmt)
105+
}
102106
statements = append(statements, stmt)
103107
}
104108

@@ -114,6 +118,25 @@ func (p *Parser) ParseStatements(ctx context.Context) ([]ast.Statement, error) {
114118
return statements, nil
115119
}
116120

121+
// parseParallelWith parses PARALLEL WITH clauses to chain statements
122+
func (p *Parser) parseParallelWith(first ast.Statement) *ast.ParallelWithQuery {
123+
parallel := &ast.ParallelWithQuery{
124+
Position: first.Pos(),
125+
Statements: []ast.Statement{first},
126+
}
127+
128+
for p.currentIs(token.PARALLEL) && p.peekIs(token.WITH) {
129+
p.nextToken() // skip PARALLEL
130+
p.nextToken() // skip WITH
131+
stmt := p.parseStatement()
132+
if stmt != nil {
133+
parallel.Statements = append(parallel.Statements, stmt)
134+
}
135+
}
136+
137+
return parallel
138+
}
139+
117140
func (p *Parser) parseStatement() ast.Statement {
118141
switch p.current.Token {
119142
case token.SELECT:
@@ -1343,6 +1366,10 @@ func (p *Parser) parseTableExpression() *ast.TableExpression {
13431366
p.nextToken()
13441367
}
13451368
} else if (p.currentIs(token.IDENT) || p.current.Token.IsKeyword()) && !p.isKeywordForClause() {
1369+
// Don't consume PARALLEL as alias if followed by WITH (parallel query syntax)
1370+
if p.currentIs(token.PARALLEL) && p.peekIs(token.WITH) {
1371+
return expr
1372+
}
13461373
expr.Alias = p.current.Value
13471374
p.nextToken()
13481375
}
Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt35": true,
4-
"stmt36": true,
5-
"stmt37": true,
6-
"stmt38": true
7-
}
8-
}
1+
{}
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt22": true,
4-
"stmt23": true,
5-
"stmt24": true
6-
}
7-
}
1+
{}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"explain_todo": {
33
"stmt3": true,
4-
"stmt5": true,
5-
"stmt6": true
4+
"stmt5": true
65
}
76
}
Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt1": true,
4-
"stmt2": true,
5-
"stmt5": true,
6-
"stmt6": true,
7-
"stmt11": true,
8-
"stmt13": true,
9-
"stmt14": true
10-
}
11-
}
1+
{}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt4": true
4-
}
5-
}
1+
{}

0 commit comments

Comments
 (0)