Skip to content

Commit 934b947

Browse files
kyleconroyclaude
andcommitted
Add CREATE TABLE 130 features: PARTITION, NOT ENFORCED, filtered indexes, ASC/DESC in CLUSTERED INDEX
- Add TablePartitionOption and TablePartitionOptionSpecifications AST types for Azure Synapse PARTITION clause: PARTITION(column RANGE [LEFT|RIGHT] FOR VALUES (...)) - Add PARTITION parsing in CREATE TABLE WITH clause - Add NOT ENFORCED support for PRIMARY KEY and UNIQUE constraints (Azure Synapse) - Add WHERE clause support for inline column indexes (filtered indexes) - Add ASC/DESC sort order support for CLUSTERED INDEX in WITH clause - Add column list support for inline UNIQUE constraints on column definitions - Fix NOT ENFORCED parsing to not consume NOT when followed by NULL Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 06e3f96 commit 934b947

4 files changed

Lines changed: 177 additions & 2 deletions

File tree

ast/table_distribution_option.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,22 @@ type TableReplicateDistributionPolicy struct{}
3434

3535
func (t *TableReplicateDistributionPolicy) node() {}
3636
func (t *TableReplicateDistributionPolicy) tableDistributionPolicy() {}
37+
38+
// TablePartitionOption represents PARTITION option for Azure Synapse tables
39+
// PARTITION(column RANGE [LEFT|RIGHT] FOR VALUES (v1, v2, ...))
40+
type TablePartitionOption struct {
41+
PartitionColumn *Identifier
42+
PartitionOptionSpecs *TablePartitionOptionSpecifications
43+
OptionKind string // "Partition"
44+
}
45+
46+
func (t *TablePartitionOption) node() {}
47+
func (t *TablePartitionOption) tableOption() {}
48+
49+
// TablePartitionOptionSpecifications represents the partition specifications
50+
type TablePartitionOptionSpecifications struct {
51+
Range string // "Left", "Right", "NotSpecified"
52+
BoundaryValues []ScalarExpression // the values in the FOR VALUES clause
53+
}
54+
55+
func (t *TablePartitionOptionSpecifications) node() {}

parser/marshal.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5677,6 +5677,15 @@ func (p *Parser) parseCreateTableStatement() (*ast.CreateTableStatement, error)
56775677
},
56785678
},
56795679
}
5680+
// Parse optional ASC/DESC
5681+
sortUpper := strings.ToUpper(p.curTok.Literal)
5682+
if sortUpper == "ASC" {
5683+
col.SortOrder = ast.SortOrderAscending
5684+
p.nextToken()
5685+
} else if sortUpper == "DESC" {
5686+
col.SortOrder = ast.SortOrderDescending
5687+
p.nextToken()
5688+
}
56805689
indexType.Columns = append(indexType.Columns, col)
56815690
if p.curTok.Type == TokenComma {
56825691
p.nextToken()
@@ -5746,6 +5755,62 @@ func (p *Parser) parseCreateTableStatement() (*ast.CreateTableStatement, error)
57465755
// Unknown distribution - skip for now
57475756
p.nextToken()
57485757
}
5758+
} else if optionName == "PARTITION" {
5759+
// Parse PARTITION(column RANGE [LEFT|RIGHT] FOR VALUES (v1, v2, ...))
5760+
if p.curTok.Type == TokenLParen {
5761+
p.nextToken() // consume (
5762+
partOpt := &ast.TablePartitionOption{
5763+
OptionKind: "Partition",
5764+
PartitionOptionSpecs: &ast.TablePartitionOptionSpecifications{},
5765+
}
5766+
// Parse partition column
5767+
partOpt.PartitionColumn = p.parseIdentifier()
5768+
// Expect RANGE keyword
5769+
if strings.ToUpper(p.curTok.Literal) == "RANGE" {
5770+
p.nextToken() // consume RANGE
5771+
// Check for LEFT or RIGHT
5772+
rangeDir := strings.ToUpper(p.curTok.Literal)
5773+
if rangeDir == "LEFT" {
5774+
partOpt.PartitionOptionSpecs.Range = "Left"
5775+
p.nextToken()
5776+
} else if rangeDir == "RIGHT" {
5777+
partOpt.PartitionOptionSpecs.Range = "Right"
5778+
p.nextToken()
5779+
} else {
5780+
partOpt.PartitionOptionSpecs.Range = "NotSpecified"
5781+
}
5782+
// Expect FOR keyword
5783+
if strings.ToUpper(p.curTok.Literal) == "FOR" {
5784+
p.nextToken() // consume FOR
5785+
}
5786+
// Expect VALUES keyword
5787+
if strings.ToUpper(p.curTok.Literal) == "VALUES" {
5788+
p.nextToken() // consume VALUES
5789+
}
5790+
// Parse boundary values list
5791+
if p.curTok.Type == TokenLParen {
5792+
p.nextToken() // consume (
5793+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
5794+
val, _ := p.parseScalarExpression()
5795+
if val != nil {
5796+
partOpt.PartitionOptionSpecs.BoundaryValues = append(partOpt.PartitionOptionSpecs.BoundaryValues, val)
5797+
}
5798+
if p.curTok.Type == TokenComma {
5799+
p.nextToken()
5800+
} else {
5801+
break
5802+
}
5803+
}
5804+
if p.curTok.Type == TokenRParen {
5805+
p.nextToken() // consume )
5806+
}
5807+
}
5808+
}
5809+
if p.curTok.Type == TokenRParen {
5810+
p.nextToken() // consume )
5811+
}
5812+
stmt.Options = append(stmt.Options, partOpt)
5813+
}
57495814
} else {
57505815
// Skip unknown option value
57515816
if p.curTok.Type == TokenEquals {
@@ -6065,6 +6130,15 @@ func (p *Parser) parseCreateTableOptions(stmt *ast.CreateTableStatement) (*ast.C
60656130
},
60666131
},
60676132
}
6133+
// Parse optional ASC/DESC
6134+
sortUpper := strings.ToUpper(p.curTok.Literal)
6135+
if sortUpper == "ASC" {
6136+
col.SortOrder = ast.SortOrderAscending
6137+
p.nextToken()
6138+
} else if sortUpper == "DESC" {
6139+
col.SortOrder = ast.SortOrderDescending
6140+
p.nextToken()
6141+
}
60686142
indexType.Columns = append(indexType.Columns, col)
60696143
if p.curTok.Type == TokenComma {
60706144
p.nextToken()
@@ -7140,6 +7214,22 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) {
71407214
constraint.IndexType = &ast.IndexType{IndexTypeKind: "NonClustered"}
71417215
}
71427216
}
7217+
// Parse optional column list (column ASC, column DESC, ...)
7218+
if p.curTok.Type == TokenLParen {
7219+
p.nextToken() // consume (
7220+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
7221+
colWithSort := p.parseColumnWithSortOrder()
7222+
constraint.Columns = append(constraint.Columns, colWithSort)
7223+
if p.curTok.Type == TokenComma {
7224+
p.nextToken()
7225+
} else {
7226+
break
7227+
}
7228+
}
7229+
if p.curTok.Type == TokenRParen {
7230+
p.nextToken() // consume )
7231+
}
7232+
}
71437233
// Parse WITH (index_options)
71447234
if strings.ToUpper(p.curTok.Literal) == "WITH" {
71457235
p.nextToken() // consume WITH
@@ -7151,6 +7241,13 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) {
71517241
fg, _ := p.parseFileGroupOrPartitionScheme()
71527242
constraint.OnFileGroupOrPartitionScheme = fg
71537243
}
7244+
// Parse NOT ENFORCED (Azure Synapse) - but only if next token is ENFORCED
7245+
if p.curTok.Type == TokenNot && strings.ToUpper(p.peekTok.Literal) == "ENFORCED" {
7246+
p.nextToken() // consume NOT
7247+
p.nextToken() // consume ENFORCED
7248+
enforced := false
7249+
constraint.IsEnforced = &enforced
7250+
}
71547251
col.Constraints = append(col.Constraints, constraint)
71557252
} else if upperLit == "PRIMARY" {
71567253
p.nextToken() // consume PRIMARY
@@ -7222,6 +7319,13 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) {
72227319
fg, _ := p.parseFileGroupOrPartitionScheme()
72237320
constraint.OnFileGroupOrPartitionScheme = fg
72247321
}
7322+
// Parse NOT ENFORCED (Azure Synapse) - but only if next token is ENFORCED
7323+
if p.curTok.Type == TokenNot && strings.ToUpper(p.peekTok.Literal) == "ENFORCED" {
7324+
p.nextToken() // consume NOT
7325+
p.nextToken() // consume ENFORCED
7326+
enforced := false
7327+
constraint.IsEnforced = &enforced
7328+
}
72257329
col.Constraints = append(col.Constraints, constraint)
72267330
} else if p.curTok.Type == TokenDefault {
72277331
p.nextToken() // consume DEFAULT
@@ -7620,6 +7724,15 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) {
76207724
}
76217725
}
76227726
}
7727+
// Parse optional WHERE clause for filtered index
7728+
if p.curTok.Type == TokenWhere {
7729+
p.nextToken() // consume WHERE
7730+
filterExpr, err := p.parseBooleanExpression()
7731+
if err != nil {
7732+
return nil, err
7733+
}
7734+
indexDef.FilterPredicate = filterExpr
7735+
}
76237736
col.Index = indexDef
76247737
} else if upperLit == "SPARSE" {
76257738
p.nextToken() // consume SPARSE
@@ -7864,6 +7977,14 @@ func (p *Parser) parsePrimaryKeyConstraint() (*ast.UniqueConstraintDefinition, e
78647977
constraint.OnFileGroupOrPartitionScheme = fg
78657978
}
78667979

7980+
// Parse NOT ENFORCED (Azure Synapse) - but only if next token is ENFORCED
7981+
if p.curTok.Type == TokenNot && strings.ToUpper(p.peekTok.Literal) == "ENFORCED" {
7982+
p.nextToken() // consume NOT
7983+
p.nextToken() // consume ENFORCED
7984+
enforced := false
7985+
constraint.IsEnforced = &enforced
7986+
}
7987+
78677988
return constraint, nil
78687989
}
78697990

@@ -7918,6 +8039,14 @@ func (p *Parser) parseUniqueConstraint() (*ast.UniqueConstraintDefinition, error
79188039
constraint.OnFileGroupOrPartitionScheme = fg
79198040
}
79208041

8042+
// Parse NOT ENFORCED (Azure Synapse) - but only if next token is ENFORCED
8043+
if p.curTok.Type == TokenNot && strings.ToUpper(p.peekTok.Literal) == "ENFORCED" {
8044+
p.nextToken() // consume NOT
8045+
p.nextToken() // consume ENFORCED
8046+
enforced := false
8047+
constraint.IsEnforced = &enforced
8048+
}
8049+
79218050
return constraint, nil
79228051
}
79238052

@@ -9265,6 +9394,18 @@ func tableOptionToJSON(opt ast.TableOption) jsonNode {
92659394
node["Value"] = tableDistributionPolicyToJSON(o.Value)
92669395
}
92679396
return node
9397+
case *ast.TablePartitionOption:
9398+
node := jsonNode{
9399+
"$type": "TablePartitionOption",
9400+
"OptionKind": o.OptionKind,
9401+
}
9402+
if o.PartitionColumn != nil {
9403+
node["PartitionColumn"] = identifierToJSON(o.PartitionColumn)
9404+
}
9405+
if o.PartitionOptionSpecs != nil {
9406+
node["PartitionOptionSpecs"] = tablePartitionOptionSpecsToJSON(o.PartitionOptionSpecs)
9407+
}
9408+
return node
92689409
case *ast.SystemVersioningTableOption:
92699410
return systemVersioningTableOptionToJSON(o)
92709411
case *ast.MemoryOptimizedTableOption:
@@ -9417,6 +9558,21 @@ func tableDistributionPolicyToJSON(policy ast.TableDistributionPolicy) jsonNode
94179558
}
94189559
}
94199560

9561+
func tablePartitionOptionSpecsToJSON(specs *ast.TablePartitionOptionSpecifications) jsonNode {
9562+
node := jsonNode{
9563+
"$type": "TablePartitionOptionSpecifications",
9564+
"Range": specs.Range,
9565+
}
9566+
if len(specs.BoundaryValues) > 0 {
9567+
vals := make([]jsonNode, len(specs.BoundaryValues))
9568+
for i, v := range specs.BoundaryValues {
9569+
vals[i] = scalarExpressionToJSON(v)
9570+
}
9571+
node["BoundaryValues"] = vals
9572+
}
9573+
return node
9574+
}
9575+
94209576
func tableIndexTypeToJSON(t ast.TableIndexType) jsonNode {
94219577
switch v := t.(type) {
94229578
case *ast.TableClusteredIndexType:
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)