Skip to content

Commit d06e080

Browse files
committed
Add CREATE MATERIALIZED VIEW support with distribution and options
- Add parseCreateMaterializedViewStatement for MATERIALIZED VIEW parsing - Add ViewOption interface with ViewStatementOption, ViewDistributionOption, and ViewForAppendOption types - Parse DISTRIBUTION = HASH(columns) and FOR_APPEND options - Use $ref for duplicate identifiers in DistributionColumns - Enable MaterializedViewTests130/140/150/160 and Baselines* variants
1 parent 94eaa07 commit d06e080

9 files changed

Lines changed: 188 additions & 9 deletions

File tree

ast/create_view_statement.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,35 @@ type CreateViewStatement struct {
1313
func (c *CreateViewStatement) node() {}
1414
func (c *CreateViewStatement) statement() {}
1515

16-
// ViewOption represents a view option like SCHEMABINDING.
17-
type ViewOption struct {
16+
// ViewOption is an interface for different view option types.
17+
type ViewOption interface {
18+
viewOption()
19+
}
20+
21+
// ViewStatementOption represents a simple view option like SCHEMABINDING.
22+
type ViewStatementOption struct {
23+
OptionKind string `json:"OptionKind,omitempty"`
24+
}
25+
26+
func (v *ViewStatementOption) viewOption() {}
27+
28+
// ViewDistributionOption represents a DISTRIBUTION option for materialized views.
29+
type ViewDistributionOption struct {
30+
OptionKind string `json:"OptionKind,omitempty"`
31+
Value *ViewHashDistributionPolicy `json:"Value,omitempty"`
32+
}
33+
34+
func (v *ViewDistributionOption) viewOption() {}
35+
36+
// ViewHashDistributionPolicy represents the hash distribution policy for materialized views.
37+
type ViewHashDistributionPolicy struct {
38+
DistributionColumn *Identifier `json:"DistributionColumn,omitempty"`
39+
DistributionColumns []*Identifier `json:"DistributionColumns,omitempty"`
40+
}
41+
42+
// ViewForAppendOption represents the FOR_APPEND option for materialized views.
43+
type ViewForAppendOption struct {
1844
OptionKind string `json:"OptionKind,omitempty"`
1945
}
46+
47+
func (v *ViewForAppendOption) viewOption() {}

parser/marshal.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2817,6 +2817,13 @@ func createViewStatementToJSON(s *ast.CreateViewStatement) jsonNode {
28172817
}
28182818
node["Columns"] = cols
28192819
}
2820+
if len(s.ViewOptions) > 0 {
2821+
opts := make([]jsonNode, len(s.ViewOptions))
2822+
for i, opt := range s.ViewOptions {
2823+
opts[i] = viewOptionToJSON(opt)
2824+
}
2825+
node["ViewOptions"] = opts
2826+
}
28202827
if s.SelectStatement != nil {
28212828
node["SelectStatement"] = selectStatementToJSON(s.SelectStatement)
28222829
}
@@ -2825,6 +2832,50 @@ func createViewStatementToJSON(s *ast.CreateViewStatement) jsonNode {
28252832
return node
28262833
}
28272834

2835+
func viewOptionToJSON(opt ast.ViewOption) jsonNode {
2836+
switch o := opt.(type) {
2837+
case *ast.ViewStatementOption:
2838+
return jsonNode{
2839+
"$type": "ViewOption",
2840+
"OptionKind": o.OptionKind,
2841+
}
2842+
case *ast.ViewDistributionOption:
2843+
node := jsonNode{
2844+
"$type": "ViewDistributionOption",
2845+
"OptionKind": o.OptionKind,
2846+
}
2847+
if o.Value != nil {
2848+
valueNode := jsonNode{
2849+
"$type": "ViewHashDistributionPolicy",
2850+
}
2851+
if o.Value.DistributionColumn != nil {
2852+
valueNode["DistributionColumn"] = identifierToJSON(o.Value.DistributionColumn)
2853+
}
2854+
if len(o.Value.DistributionColumns) > 0 {
2855+
cols := make([]jsonNode, len(o.Value.DistributionColumns))
2856+
for i, c := range o.Value.DistributionColumns {
2857+
// First column is same as DistributionColumn, use $ref
2858+
if i == 0 && o.Value.DistributionColumn != nil {
2859+
cols[i] = jsonNode{"$ref": "Identifier"}
2860+
} else {
2861+
cols[i] = identifierToJSON(c)
2862+
}
2863+
}
2864+
valueNode["DistributionColumns"] = cols
2865+
}
2866+
node["Value"] = valueNode
2867+
}
2868+
return node
2869+
case *ast.ViewForAppendOption:
2870+
return jsonNode{
2871+
"$type": "ViewForAppendOption",
2872+
"OptionKind": o.OptionKind,
2873+
}
2874+
default:
2875+
return jsonNode{"$type": "UnknownViewOption"}
2876+
}
2877+
}
2878+
28282879
func createSchemaStatementToJSON(s *ast.CreateSchemaStatement) jsonNode {
28292880
node := jsonNode{
28302881
"$type": "CreateSchemaStatement",

parser/parse_statements.go

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1789,6 +1789,8 @@ func (p *Parser) parseCreateStatement() (ast.Statement, error) {
17891789
return p.parseCreateSequenceStatement()
17901790
case "SPATIAL":
17911791
return p.parseCreateSpatialIndexStatement()
1792+
case "MATERIALIZED":
1793+
return p.parseCreateMaterializedViewStatement()
17921794
case "SERVER":
17931795
// Check if it's SERVER ROLE or SERVER AUDIT
17941796
p.nextToken() // consume SERVER
@@ -2899,7 +2901,7 @@ func (p *Parser) parseCreateViewStatement() (*ast.CreateViewStatement, error) {
28992901
p.nextToken()
29002902
// Parse view options
29012903
for p.curTok.Type == TokenIdent {
2902-
opt := ast.ViewOption{OptionKind: p.curTok.Literal}
2904+
opt := &ast.ViewStatementOption{OptionKind: p.curTok.Literal}
29032905
stmt.ViewOptions = append(stmt.ViewOptions, opt)
29042906
p.nextToken()
29052907
if p.curTok.Type == TokenComma {
@@ -2927,6 +2929,104 @@ func (p *Parser) parseCreateViewStatement() (*ast.CreateViewStatement, error) {
29272929
return stmt, nil
29282930
}
29292931

2932+
func (p *Parser) parseCreateMaterializedViewStatement() (*ast.CreateViewStatement, error) {
2933+
// Consume MATERIALIZED
2934+
p.nextToken()
2935+
2936+
// Expect VIEW
2937+
if p.curTok.Type != TokenView {
2938+
return nil, fmt.Errorf("expected VIEW after MATERIALIZED, got %s", p.curTok.Literal)
2939+
}
2940+
p.nextToken()
2941+
2942+
stmt := &ast.CreateViewStatement{
2943+
IsMaterialized: true,
2944+
}
2945+
2946+
// Parse view name
2947+
son, err := p.parseSchemaObjectName()
2948+
if err != nil {
2949+
return nil, err
2950+
}
2951+
stmt.SchemaObjectName = son
2952+
2953+
// Parse WITH options for materialized view
2954+
if p.curTok.Type == TokenWith || strings.ToUpper(p.curTok.Literal) == "WITH" {
2955+
p.nextToken() // consume WITH
2956+
if p.curTok.Type == TokenLParen {
2957+
p.nextToken()
2958+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
2959+
optionName := strings.ToUpper(p.curTok.Literal)
2960+
p.nextToken()
2961+
2962+
if optionName == "DISTRIBUTION" {
2963+
// Parse DISTRIBUTION = HASH(col1, col2, ...)
2964+
if p.curTok.Type == TokenEquals {
2965+
p.nextToken()
2966+
}
2967+
if strings.ToUpper(p.curTok.Literal) == "HASH" {
2968+
p.nextToken()
2969+
if p.curTok.Type == TokenLParen {
2970+
p.nextToken()
2971+
distOpt := &ast.ViewDistributionOption{
2972+
OptionKind: "Distribution",
2973+
Value: &ast.ViewHashDistributionPolicy{},
2974+
}
2975+
// Parse column list
2976+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
2977+
col := p.parseIdentifier()
2978+
if distOpt.Value.DistributionColumn == nil {
2979+
distOpt.Value.DistributionColumn = col
2980+
}
2981+
distOpt.Value.DistributionColumns = append(distOpt.Value.DistributionColumns, col)
2982+
if p.curTok.Type == TokenComma {
2983+
p.nextToken()
2984+
} else {
2985+
break
2986+
}
2987+
}
2988+
if p.curTok.Type == TokenRParen {
2989+
p.nextToken()
2990+
}
2991+
stmt.ViewOptions = append(stmt.ViewOptions, distOpt)
2992+
}
2993+
}
2994+
} else if optionName == "FOR_APPEND" {
2995+
stmt.ViewOptions = append(stmt.ViewOptions, &ast.ViewForAppendOption{
2996+
OptionKind: "ForAppend",
2997+
})
2998+
}
2999+
3000+
if p.curTok.Type == TokenComma {
3001+
p.nextToken()
3002+
} else if p.curTok.Type != TokenRParen {
3003+
break
3004+
}
3005+
}
3006+
if p.curTok.Type == TokenRParen {
3007+
p.nextToken()
3008+
}
3009+
}
3010+
}
3011+
3012+
// Expect AS
3013+
if p.curTok.Type != TokenAs {
3014+
p.skipToEndOfStatement()
3015+
return stmt, nil
3016+
}
3017+
p.nextToken()
3018+
3019+
// Parse SELECT statement
3020+
selStmt, err := p.parseSelectStatement()
3021+
if err != nil {
3022+
p.skipToEndOfStatement()
3023+
return stmt, nil
3024+
}
3025+
stmt.SelectStatement = selStmt
3026+
3027+
return stmt, nil
3028+
}
3029+
29303030
func (p *Parser) parseCreateSchemaStatement() (*ast.CreateSchemaStatement, error) {
29313031
// Consume SCHEMA
29323032
p.nextToken()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}

0 commit comments

Comments
 (0)