Skip to content

Commit a273941

Browse files
kyleconroyclaude
andcommitted
Add support for CREATE WINDOW VIEW parsing
- Add WindowView and InnerEngine fields to CreateQuery AST - Handle CREATE WINDOW VIEW syntax with TO clause and INNER ENGINE - Parse INNER ENGINE clause for window views (storage for internal data) - Update EXPLAIN output to show ViewTargets with Storage definition - ORDER BY for window views goes inside ViewTargets, not separate Storage Fixes 01049_window_view_window_functions/stmt41. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5392e89 commit a273941

File tree

5 files changed

+87
-17
lines changed

5 files changed

+87
-17
lines changed

ast/ast.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ type CreateQuery struct {
275275
Table string `json:"table,omitempty"`
276276
View string `json:"view,omitempty"`
277277
Materialized bool `json:"materialized,omitempty"`
278+
WindowView bool `json:"window_view,omitempty"` // WINDOW VIEW type
279+
InnerEngine *EngineClause `json:"inner_engine,omitempty"` // INNER ENGINE for window views
278280
ToDatabase string `json:"to_database,omitempty"` // Target database for materialized views
279281
To string `json:"to,omitempty"` // Target table for materialized views
280282
Populate bool `json:"populate,omitempty"` // POPULATE for materialized views

internal/explain/statements.go

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,9 @@ func explainCreateQuery(sb *strings.Builder, n *ast.CreateQuery, indent string,
225225
// When SETTINGS comes after COMMENT (not before), Settings goes outside Storage definition
226226
// SettingsBeforeComment=true means SETTINGS came first, so it stays in Storage
227227
settingsInStorage := len(n.Settings) > 0 && (n.Comment == "" || n.SettingsBeforeComment)
228-
hasStorageChild := n.Engine != nil || len(n.OrderBy) > 0 || len(n.PrimaryKey) > 0 || n.PartitionBy != nil || n.SampleBy != nil || n.TTL != nil || settingsInStorage || len(n.ColumnsPrimaryKey) > 0 || hasColumnPrimaryKey
228+
// For WINDOW VIEW with INNER ENGINE, ORDER BY goes inside ViewTargets, not in regular Storage definition
229+
orderByInRegularStorage := len(n.OrderBy) > 0 && !(n.WindowView && n.InnerEngine != nil)
230+
hasStorageChild := n.Engine != nil || orderByInRegularStorage || len(n.PrimaryKey) > 0 || n.PartitionBy != nil || n.SampleBy != nil || n.TTL != nil || settingsInStorage || len(n.ColumnsPrimaryKey) > 0 || hasColumnPrimaryKey
229231
if hasStorageChild {
230232
children++
231233
}
@@ -245,6 +247,10 @@ func explainCreateQuery(sb *strings.Builder, n *ast.CreateQuery, indent string,
245247
if n.Materialized && n.To != "" && !hasStorageChild {
246248
children++ // ViewTargets
247249
}
250+
// For window views with INNER ENGINE, count ViewTargets as a child
251+
if n.WindowView && n.InnerEngine != nil {
252+
children++ // ViewTargets with Storage definition
253+
}
248254
if n.AsSelect != nil {
249255
children++
250256
}
@@ -374,7 +380,9 @@ func explainCreateQuery(sb *strings.Builder, n *ast.CreateQuery, indent string,
374380
inCreateQueryContext = false
375381
}
376382
}
377-
hasStorage := n.Engine != nil || len(n.OrderBy) > 0 || len(n.PrimaryKey) > 0 || n.PartitionBy != nil || n.SampleBy != nil || n.TTL != nil || settingsInStorage || len(n.ColumnsPrimaryKey) > 0 || hasColumnPrimaryKey
383+
// For WINDOW VIEW with INNER ENGINE, ORDER BY goes inside ViewTargets
384+
hasOrderByInStorage := len(n.OrderBy) > 0 && !(n.WindowView && n.InnerEngine != nil)
385+
hasStorage := n.Engine != nil || hasOrderByInStorage || len(n.PrimaryKey) > 0 || n.PartitionBy != nil || n.SampleBy != nil || n.TTL != nil || settingsInStorage || len(n.ColumnsPrimaryKey) > 0 || hasColumnPrimaryKey
378386
if hasStorage {
379387
storageChildren := 0
380388
if n.Engine != nil {
@@ -549,8 +557,58 @@ func explainCreateQuery(sb *strings.Builder, n *ast.CreateQuery, indent string,
549557
// output just ViewTargets without children
550558
fmt.Fprintf(sb, "%s ViewTargets\n", indent)
551559
}
560+
// For window views, output AsSelect before ViewTargets
561+
if n.WindowView && n.AsSelect != nil {
562+
if hasFormat {
563+
inCreateQueryContext = true
564+
}
565+
Node(sb, n.AsSelect, depth+1)
566+
if hasFormat {
567+
inCreateQueryContext = false
568+
}
569+
}
570+
// For window views with INNER ENGINE, output ViewTargets with Storage definition
571+
if n.WindowView && n.InnerEngine != nil {
572+
// Count children in storage definition: engine + order by (if any)
573+
storageChildren := 1 // Always have the engine
574+
if len(n.OrderBy) > 0 {
575+
storageChildren++
576+
}
577+
fmt.Fprintf(sb, "%s ViewTargets (children 1)\n", indent)
578+
fmt.Fprintf(sb, "%s Storage definition (children %d)\n", indent, storageChildren)
579+
// Output the engine
580+
if n.InnerEngine.HasParentheses {
581+
fmt.Fprintf(sb, "%s Function %s (children 1)\n", indent, n.InnerEngine.Name)
582+
if len(n.InnerEngine.Parameters) > 0 {
583+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.InnerEngine.Parameters))
584+
for _, param := range n.InnerEngine.Parameters {
585+
Node(sb, param, depth+5)
586+
}
587+
} else {
588+
fmt.Fprintf(sb, "%s ExpressionList\n", indent)
589+
}
590+
} else {
591+
fmt.Fprintf(sb, "%s Function %s\n", indent, n.InnerEngine.Name)
592+
}
593+
// Output ORDER BY if present
594+
if len(n.OrderBy) > 0 {
595+
if len(n.OrderBy) == 1 {
596+
if ident, ok := n.OrderBy[0].(*ast.Identifier); ok {
597+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, ident.Name())
598+
} else {
599+
Node(sb, n.OrderBy[0], depth+3)
600+
}
601+
} else {
602+
fmt.Fprintf(sb, "%s Function tuple (children 1)\n", indent)
603+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.OrderBy))
604+
for _, o := range n.OrderBy {
605+
Node(sb, o, depth+5)
606+
}
607+
}
608+
}
609+
}
552610
// For non-materialized views, output AsSelect after storage
553-
if n.AsSelect != nil && !n.Materialized {
611+
if n.AsSelect != nil && !n.Materialized && !n.WindowView {
554612
// Set context flag to prevent Format from being output at SelectWithUnionQuery level
555613
// (it will be output at CreateQuery level instead)
556614
if hasFormat {

parser/parser.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2202,6 +2202,12 @@ func (p *Parser) parseCreate() ast.Statement {
22022202
p.nextToken()
22032203
}
22042204

2205+
// Handle WINDOW (for WINDOW VIEW)
2206+
if p.currentIs(token.WINDOW) {
2207+
create.WindowView = true
2208+
p.nextToken()
2209+
}
2210+
22052211
// What are we creating?
22062212
switch p.current.Token {
22072213
case token.TABLE:
@@ -2944,11 +2950,11 @@ func (p *Parser) parseCreateView(create *ast.CreateQuery) {
29442950
}
29452951
}
29462952

2947-
// Handle TO (target table for materialized views only)
2948-
// TO clause is not valid for regular views - only for MATERIALIZED VIEW
2953+
// Handle TO (target table for materialized views and window views)
2954+
// TO clause is not valid for regular views - only for MATERIALIZED VIEW or WINDOW VIEW
29492955
if p.currentIs(token.TO) {
2950-
if !create.Materialized {
2951-
p.errors = append(p.errors, fmt.Errorf("TO clause is only valid for MATERIALIZED VIEW, not VIEW"))
2956+
if !create.Materialized && !create.WindowView {
2957+
p.errors = append(p.errors, fmt.Errorf("TO clause is only valid for MATERIALIZED VIEW or WINDOW VIEW, not VIEW"))
29522958
return
29532959
}
29542960
p.nextToken()
@@ -2980,6 +2986,18 @@ func (p *Parser) parseCreateView(create *ast.CreateQuery) {
29802986
}
29812987
}
29822988

2989+
// Parse INNER ENGINE (for window views) - comes before regular ENGINE
2990+
if p.currentIs(token.INNER) {
2991+
p.nextToken() // skip INNER
2992+
if p.currentIs(token.ENGINE) {
2993+
p.nextToken() // skip ENGINE
2994+
if p.currentIs(token.EQ) {
2995+
p.nextToken()
2996+
}
2997+
create.InnerEngine = p.parseEngineClause()
2998+
}
2999+
}
3000+
29833001
// Parse ENGINE (for materialized views)
29843002
if p.currentIs(token.ENGINE) {
29853003
p.nextToken()
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt41": true
4-
}
5-
}
1+
{}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt34": true
4-
}
5-
}
1+
{}

0 commit comments

Comments
 (0)