Skip to content

Commit 76ee043

Browse files
ajitpratap0Ajit Pratap Singhclaude
authored
refactor: split parser.go into logical modules (Issue #78) (#120)
Split the monolithic parser.go (2523 lines) into focused modules: New files created: - expressions.go (583 lines): Expression parsing logic - parseExpression, parseAndExpression, parseComparisonExpression - parsePrimaryExpression, parseCaseExpression, parseSubquery - window.go (285 lines): Window function support - parseFunctionCall, parseWindowSpec - parseWindowFrame, parseFrameBound, parseNullsClause - grouping.go (156 lines): SQL-99 grouping operations - parseGroupingExpressionList, parseRollup - parseCube, parseGroupingSets - select.go (524 lines): SELECT statement parsing - parseSelectStatement, parseSelectWithSetOperations - parseColumnDef, parseTableConstraint - dml.go (512 lines): DML operations - parseInsertStatement, parseUpdateStatement - parseDeleteStatement, parseMergeStatement - parseMergeWhenClause, parseMergeAction - cte.go (175 lines): Common Table Expressions - parseWithStatement, parseCommonTableExpr - parseMainStatementAfterWith Reduced parser.go to 356 lines (core): - Parser struct and Parse/ParseContext methods - Basic utilities: advance, peekToken, matchToken - parseStatement switch - Helper functions for identifiers and table references Benefits: - Each file < 600 lines (was 2523) - Logical grouping of related functions - Easier navigation and maintenance - No breaking changes - all tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Ajit Pratap Singh <ajitpratapsingh@Ajits-Mac-mini.local> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 85ad3e5 commit 76ee043

7 files changed

Lines changed: 2233 additions & 2168 deletions

File tree

pkg/sql/parser/cte.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Package parser - cte.go
2+
// Common Table Expression (CTE) parsing with recursive CTE support.
3+
4+
package parser
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/ajitpratap0/GoSQLX/pkg/sql/ast"
10+
)
11+
12+
// WITH summary(region, total) AS (SELECT region, SUM(amount) FROM sales GROUP BY region) SELECT * FROM summary
13+
func (p *Parser) parseWithStatement() (ast.Statement, error) {
14+
// Consume WITH
15+
p.advance()
16+
17+
// Check for RECURSIVE keyword
18+
recursive := false
19+
if p.currentToken.Type == "RECURSIVE" {
20+
recursive = true
21+
p.advance()
22+
}
23+
24+
// Parse Common Table Expressions
25+
ctes := []*ast.CommonTableExpr{}
26+
27+
for {
28+
cte, err := p.parseCommonTableExpr()
29+
if err != nil {
30+
return nil, fmt.Errorf("error parsing CTE: %v", err)
31+
}
32+
ctes = append(ctes, cte)
33+
34+
// Check for more CTEs (comma-separated)
35+
if p.currentToken.Type == "," {
36+
p.advance() // Consume comma
37+
continue
38+
}
39+
break
40+
}
41+
42+
// Create WITH clause
43+
withClause := &ast.WithClause{
44+
Recursive: recursive,
45+
CTEs: ctes,
46+
}
47+
48+
// Parse the main statement that follows the WITH clause
49+
mainStmt, err := p.parseMainStatementAfterWith()
50+
if err != nil {
51+
return nil, fmt.Errorf("error parsing statement after WITH: %v", err)
52+
}
53+
54+
// Attach WITH clause to the main statement
55+
switch stmt := mainStmt.(type) {
56+
case *ast.SelectStatement:
57+
stmt.With = withClause
58+
return stmt, nil
59+
case *ast.SetOperation:
60+
// For set operations, attach WITH to the left statement if it's a SELECT
61+
if leftSelect, ok := stmt.Left.(*ast.SelectStatement); ok {
62+
leftSelect.With = withClause
63+
}
64+
return stmt, nil
65+
case *ast.InsertStatement:
66+
stmt.With = withClause
67+
return stmt, nil
68+
case *ast.UpdateStatement:
69+
stmt.With = withClause
70+
return stmt, nil
71+
case *ast.DeleteStatement:
72+
stmt.With = withClause
73+
return stmt, nil
74+
default:
75+
return nil, fmt.Errorf("WITH clause not supported with statement type: %T", stmt)
76+
}
77+
}
78+
79+
// parseCommonTableExpr parses a single Common Table Expression.
80+
// It handles CTE name, optional column list, AS keyword, and the CTE query in parentheses.
81+
//
82+
// Syntax: cte_name [(column_list)] AS (query)
83+
func (p *Parser) parseCommonTableExpr() (*ast.CommonTableExpr, error) {
84+
// Check recursion depth to prevent stack overflow in recursive CTEs
85+
// This is critical since CTEs can call parseStatement which leads back to more CTEs
86+
p.depth++
87+
defer func() { p.depth-- }()
88+
89+
if p.depth > MaxRecursionDepth {
90+
return nil, fmt.Errorf("maximum recursion depth exceeded (%d) - CTE too deeply nested", MaxRecursionDepth)
91+
}
92+
93+
// Parse CTE name
94+
if p.currentToken.Type != "IDENT" {
95+
return nil, p.expectedError("CTE name")
96+
}
97+
name := p.currentToken.Literal
98+
p.advance()
99+
100+
// Parse optional column list
101+
var columns []string
102+
if p.currentToken.Type == "(" {
103+
p.advance() // Consume (
104+
105+
for {
106+
if p.currentToken.Type != "IDENT" {
107+
return nil, p.expectedError("column name")
108+
}
109+
columns = append(columns, p.currentToken.Literal)
110+
p.advance()
111+
112+
if p.currentToken.Type == "," {
113+
p.advance() // Consume comma
114+
continue
115+
}
116+
break
117+
}
118+
119+
if p.currentToken.Type != ")" {
120+
return nil, p.expectedError(")")
121+
}
122+
p.advance() // Consume )
123+
}
124+
125+
// Parse AS keyword
126+
if p.currentToken.Type != "AS" {
127+
return nil, p.expectedError("AS")
128+
}
129+
p.advance()
130+
131+
// Parse the CTE query (must be in parentheses)
132+
if p.currentToken.Type != "(" {
133+
return nil, p.expectedError("( before CTE query")
134+
}
135+
p.advance() // Consume (
136+
137+
// Parse the inner statement
138+
stmt, err := p.parseStatement()
139+
if err != nil {
140+
return nil, fmt.Errorf("error parsing CTE statement: %v", err)
141+
}
142+
143+
if p.currentToken.Type != ")" {
144+
return nil, p.expectedError(") after CTE query")
145+
}
146+
p.advance() // Consume )
147+
148+
return &ast.CommonTableExpr{
149+
Name: name,
150+
Columns: columns,
151+
Statement: stmt,
152+
}, nil
153+
}
154+
155+
// parseMainStatementAfterWith parses the main statement after WITH clause.
156+
// It supports SELECT, INSERT, UPDATE, and DELETE statements, routing them to the appropriate
157+
// parsers while preserving set operation support for SELECT statements.
158+
func (p *Parser) parseMainStatementAfterWith() (ast.Statement, error) {
159+
switch p.currentToken.Type {
160+
case "SELECT":
161+
p.advance() // Consume SELECT
162+
return p.parseSelectWithSetOperations()
163+
case "INSERT":
164+
p.advance() // Consume INSERT
165+
return p.parseInsertStatement()
166+
case "UPDATE":
167+
p.advance() // Consume UPDATE
168+
return p.parseUpdateStatement()
169+
case "DELETE":
170+
p.advance() // Consume DELETE
171+
return p.parseDeleteStatement()
172+
default:
173+
return nil, p.expectedError("SELECT, INSERT, UPDATE, or DELETE after WITH")
174+
}
175+
}

0 commit comments

Comments
 (0)