Add window function (OVER clause) and CTE (WITH clause) by jeffreyaven · Pull Request #594 · stackql/stackql · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f94ef96
Add window function (OVER clause) and CTE (WITH clause) execution lay…
claude Dec 2, 2025
56af7c7
Add go mod tidy step to lint workflow
claude Dec 2, 2025
a7febab
Fix pseudo-version timestamp for stackql-parser replace directive
claude Dec 2, 2025
3de5a45
Move CTE tests to separate test package to avoid cyclop complexity issue
claude Dec 2, 2025
a58cd75
Add integration tests for window functions and CTEs
claude Dec 2, 2025
26ad1cc
Fix godot lint: add periods to comments
claude Dec 2, 2025
e73251b
Fix CTE support by properly registering CTEs as indirects.
claude Dec 2, 2025
aab78a8
Add test to verify DecoratedColumn includes OVER clause
claude Dec 2, 2025
82062d3
Fix window function OVER clause being lost in DecoratedColumn
claude Dec 2, 2025
b62c527
Fix CTE resolution by checking for CTE indirect before hierarchy reso…
claude Dec 2, 2025
6b03be8
Fix CTE handling and SQLite type casting in test queries
claude Dec 3, 2025
51d4c3c
Fix CTE handling in dependency planner and CAST type in test queries
claude Dec 3, 2025
6f062a4
Fix CTE column resolution by calling processIndirect
claude Dec 3, 2025
fbcfd74
Fix test setup and simplify CTE multiple test query
claude Dec 3, 2025
9fd9170
Fix mnd lint: add nolint comment for IndirectDepthMax value
claude Dec 3, 2025
1b46e5c
Revert processIndirect for CTEs - use simpler native backend approach
claude Dec 3, 2025
e422ac7
Extract CTE columns from SELECT AST for column resolution
claude Dec 3, 2025
1902f62
Fix lint: use if instead of switch, avoid variable shadowing
claude Dec 3, 2025
ddc4a54
Remove native backend marking for CTEs - let normal indirect processi…
claude Dec 3, 2025
9310974
Call processIndirect for CTE references to get proper column context
claude Dec 3, 2025
03320a5
Fix CTE double-processing - only register names, process in processIn…
claude Dec 3, 2025
897a1cd
Add CTEType handling to FROM clause rewriting
claude Dec 3, 2025
f467628
Set CTE indirect on hierarchy objects for query rewriting
claude Dec 3, 2025
9dfded5
Remove incorrect SetIndirect call - indirect already set via WithIndi…
claude Dec 3, 2025
feac75c
Set builder on indirect's primitive composer for CTE execution
claude Dec 3, 2025
63525c7
Convert CTEs to subqueries at AST level for proper execution
claude Dec 3, 2025
9c3c79c
Remove unused internaldto import from parameter_router.go
claude Dec 3, 2025
32052e4
Fix CTE registration: wrap cte.Select in Subquery struct
claude Dec 3, 2025
79df09d
Remove duplicate SetBuilder call that caused cyclic primitive graph
claude Dec 3, 2025
bfaf7b9
Add additional window function tests for better coverage
claude Dec 3, 2025
f7ffdfc
Fix goimports formatting in expected.go
claude Dec 3, 2025
ee44bbe
Fix goimports alignment in expected.go
claude Dec 3, 2025
d60d024
Fix expected output for window count test
claude Dec 3, 2025
8979e9f
Update stackql-parser to v0.0.16-alpha01
jeffreyaven Dec 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/lint.yml
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ require (
github.com/stackql/any-sdk v0.3.1-beta01
github.com/stackql/go-suffix-map v0.0.1-alpha01
github.com/stackql/psql-wire v0.1.2-alpha01
github.com/stackql/stackql-parser v0.0.15-alpha06
github.com/stackql/stackql-parser v0.0.16-alpha01
github.com/stretchr/testify v1.10.0
golang.org/x/sync v0.15.0
gonum.org/v1/gonum v0.15.1
Expand Down
58 changes: 58 additions & 0 deletions internal/stackql/astanalysis/earlyanalysis/ast_expand.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type indirectExpandAstVisitor struct {
selectCount int
mutateCount int
createBuilder []primitivebuilder.Builder
cteRegistry map[string]*sqlparser.Subquery // CTE name -> subquery definition
}

func newIndirectExpandAstVisitor(
Expand All @@ -75,6 +76,7 @@ func newIndirectExpandAstVisitor(
tcc: tcc,
whereParams: whereParams,
indirectionDepth: indirectionDepth,
cteRegistry: make(map[string]*sqlparser.Subquery),
}
return rv, nil
}
Expand Down Expand Up @@ -103,6 +105,41 @@ func (v *indirectExpandAstVisitor) processMaterializedView(
return nil
}

// processCTEReference handles CTE references by converting them to subquery indirects.
// Returns true if the table name was a CTE reference and was processed, false otherwise.
func (v *indirectExpandAstVisitor) processCTEReference(
node *sqlparser.AliasedTableExpr,
tableName string,
) bool {
cteSubquery, isCTE := v.cteRegistry[tableName]
if !isCTE {
return false
}
logging.GetLogger().Infof("processCTEReference: Converting CTE '%s' to subquery", tableName)
logging.GetLogger().Debugf("processCTEReference: CTE subquery = %s", sqlparser.String(cteSubquery))
// Modify the original node to replace the TableName with the CTE subquery
// This is critical: downstream code (GetHIDs) checks node.Expr type to identify subqueries
node.Expr = cteSubquery
// Set the alias to the CTE name if no explicit alias was provided
if node.As.IsEmpty() {
node.As = sqlparser.NewTableIdent(tableName)
}
logging.GetLogger().Debugf("processCTEReference: Node alias set to '%s'", node.As.GetRawVal())
sq := internaldto.NewSubqueryDTO(node, cteSubquery)
indirect, err := astindirect.NewSubqueryIndirect(sq)
if err != nil {
logging.GetLogger().Errorf("processCTEReference: Failed to create subquery indirect: %v", err)
return true //nolint:nilerr //TODO: investigate
}
err = v.processIndirect(node, indirect)
if err != nil {
logging.GetLogger().Errorf("processCTEReference: processIndirect failed: %v", err)
} else {
logging.GetLogger().Infof("processCTEReference: Successfully processed CTE '%s' as subquery", tableName)
}
return true
}

func (v *indirectExpandAstVisitor) processIndirect(node sqlparser.SQLNode, indirect astindirect.Indirect) error {
err := indirect.Parse()
if err != nil {
Expand Down Expand Up @@ -214,6 +251,19 @@ func (v *indirectExpandAstVisitor) Visit(node sqlparser.SQLNode) error {
addIf(node.StraightJoinHint, sqlparser.StraightJoinHint)
addIf(node.SQLCalcFoundRows, sqlparser.SQLCalcFoundRowsStr)

// Extract CTEs from WITH clause and store in registry as Subqueries.
// CTEs are converted to subqueries at the AST level for uniform handling.
if node.With != nil {
logging.GetLogger().Infof("Registering %d CTEs from WITH clause", len(node.With.CTEs))
for _, cte := range node.With.CTEs {
cteName := cte.Name.GetRawVal()
// Wrap the CTE's SELECT statement in a Subquery struct
cteSubquery := &sqlparser.Subquery{Select: cte.Select}
v.cteRegistry[cteName] = cteSubquery
logging.GetLogger().Debugf("Registered CTE '%s' with subquery: %s", cteName, sqlparser.String(cteSubquery))
}
}

if node.Comments != nil {
node.Comments.Accept(v) //nolint:errcheck // future proof
}
Expand Down Expand Up @@ -785,6 +835,11 @@ func (v *indirectExpandAstVisitor) Visit(node sqlparser.SQLNode) error {
return nil //nolint:nilerr //TODO: investigate
}
return nil
case sqlparser.TableName:
// Check if this is a CTE reference - convert to subquery
if v.processCTEReference(node, n.GetRawVal()) {
return nil
}
}
err := node.Expr.Accept(v)
if err != nil {
Expand Down Expand Up @@ -822,6 +877,9 @@ func (v *indirectExpandAstVisitor) Visit(node sqlparser.SQLNode) error {
if node.IsEmpty() {
return nil
}
// Note: CTE references are handled in AliasedTableExpr case above,
// where they are converted to subqueries. This case only handles
// regular table names (provider.service.resource).
containsBackendMaterial := v.handlerCtx.GetDBMSInternalRouter().ExprIsRoutable(node)
if containsBackendMaterial {
v.containsNativeBackendMaterial = true
Expand Down
228 changes: 228 additions & 0 deletions internal/stackql/astanalysis/earlyanalysis/cte_test.go
Loading
Loading