Skip to content

Commit a7e2b7a

Browse files
kyleconroyclaude
andauthored
Add ALTER TABLE SET with SYSTEM_VERSIONING support (#38)
Add parsing support for ALTER TABLE ... SET (SYSTEM_VERSIONING = ON/OFF) statements including: - HISTORY_TABLE option - DATA_CONSISTENCY_CHECK option - HISTORY_RETENTION_PERIOD option (numeric with units or INFINITE) This enables the Baselines140_AlterTableStatementTests140_Azure and AlterTableStatementTests140_Azure tests. Co-authored-by: Claude <noreply@anthropic.com>
1 parent 151fc6b commit a7e2b7a

File tree

5 files changed

+266
-2
lines changed

5 files changed

+266
-2
lines changed

ast/alter_table_set_statement.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package ast
2+
3+
// AlterTableSetStatement represents ALTER TABLE ... SET statement
4+
type AlterTableSetStatement struct {
5+
SchemaObjectName *SchemaObjectName
6+
Options []TableOption
7+
}
8+
9+
func (s *AlterTableSetStatement) statement() {}
10+
func (s *AlterTableSetStatement) node() {}
11+
12+
// TableOption is an interface for table options
13+
type TableOption interface {
14+
Node
15+
tableOption()
16+
}
17+
18+
// SystemVersioningTableOption represents SYSTEM_VERSIONING option
19+
type SystemVersioningTableOption struct {
20+
OptionState string // "On", "Off"
21+
ConsistencyCheckEnabled string // "On", "Off", "NotSet"
22+
HistoryTable *SchemaObjectName
23+
RetentionPeriod *RetentionPeriodDefinition
24+
OptionKind string // Always "LockEscalation"
25+
}
26+
27+
func (o *SystemVersioningTableOption) tableOption() {}
28+
func (o *SystemVersioningTableOption) node() {}
29+
30+
// RetentionPeriodDefinition represents the history retention period
31+
type RetentionPeriodDefinition struct {
32+
Duration ScalarExpression
33+
Units string // "Day", "Week", "Month", "Months", "Year"
34+
IsInfinity bool
35+
}
36+
37+
func (r *RetentionPeriodDefinition) node() {}

parser/marshal.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,8 @@ func statementToJSON(stmt ast.Statement) jsonNode {
412412
return alterTableSwitchStatementToJSON(s)
413413
case *ast.AlterTableConstraintModificationStatement:
414414
return alterTableConstraintModificationStatementToJSON(s)
415+
case *ast.AlterTableSetStatement:
416+
return alterTableSetStatementToJSON(s)
415417
case *ast.InsertBulkStatement:
416418
return insertBulkStatementToJSON(s)
417419
case *ast.BulkInsertStatement:
@@ -6137,6 +6139,60 @@ func alterTableConstraintModificationStatementToJSON(s *ast.AlterTableConstraint
61376139
return node
61386140
}
61396141

6142+
func alterTableSetStatementToJSON(s *ast.AlterTableSetStatement) jsonNode {
6143+
node := jsonNode{
6144+
"$type": "AlterTableSetStatement",
6145+
}
6146+
if len(s.Options) > 0 {
6147+
var options []jsonNode
6148+
for _, opt := range s.Options {
6149+
options = append(options, tableOptionToJSON(opt))
6150+
}
6151+
node["Options"] = options
6152+
}
6153+
if s.SchemaObjectName != nil {
6154+
node["SchemaObjectName"] = schemaObjectNameToJSON(s.SchemaObjectName)
6155+
}
6156+
return node
6157+
}
6158+
6159+
func tableOptionToJSON(opt ast.TableOption) jsonNode {
6160+
switch o := opt.(type) {
6161+
case *ast.SystemVersioningTableOption:
6162+
return systemVersioningTableOptionToJSON(o)
6163+
default:
6164+
return jsonNode{"$type": "UnknownTableOption"}
6165+
}
6166+
}
6167+
6168+
func systemVersioningTableOptionToJSON(o *ast.SystemVersioningTableOption) jsonNode {
6169+
node := jsonNode{
6170+
"$type": "SystemVersioningTableOption",
6171+
"OptionState": o.OptionState,
6172+
"ConsistencyCheckEnabled": o.ConsistencyCheckEnabled,
6173+
}
6174+
if o.HistoryTable != nil {
6175+
node["HistoryTable"] = schemaObjectNameToJSON(o.HistoryTable)
6176+
}
6177+
if o.RetentionPeriod != nil {
6178+
node["RetentionPeriod"] = retentionPeriodDefinitionToJSON(o.RetentionPeriod)
6179+
}
6180+
node["OptionKind"] = o.OptionKind
6181+
return node
6182+
}
6183+
6184+
func retentionPeriodDefinitionToJSON(r *ast.RetentionPeriodDefinition) jsonNode {
6185+
node := jsonNode{
6186+
"$type": "RetentionPeriodDefinition",
6187+
}
6188+
if r.Duration != nil {
6189+
node["Duration"] = scalarExpressionToJSON(r.Duration)
6190+
}
6191+
node["Units"] = r.Units
6192+
node["IsInfinity"] = r.IsInfinity
6193+
return node
6194+
}
6195+
61406196
func createExternalDataSourceStatementToJSON(s *ast.CreateExternalDataSourceStatement) jsonNode {
61416197
node := jsonNode{
61426198
"$type": "CreateExternalDataSourceStatement",

parser/parse_ddl.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2132,6 +2132,11 @@ func (p *Parser) parseAlterTableStatement() (ast.Statement, error) {
21322132
return p.parseAlterTableConstraintModificationStatement(tableName)
21332133
}
21342134

2135+
// Check for SET
2136+
if strings.ToUpper(p.curTok.Literal) == "SET" {
2137+
return p.parseAlterTableSetStatement(tableName)
2138+
}
2139+
21352140
return nil, fmt.Errorf("unexpected token in ALTER TABLE: %s", p.curTok.Literal)
21362141
}
21372142

@@ -2685,6 +2690,172 @@ func (p *Parser) parseAlterTableConstraintModificationStatement(tableName *ast.S
26852690
return stmt, nil
26862691
}
26872692

2693+
func (p *Parser) parseAlterTableSetStatement(tableName *ast.SchemaObjectName) (*ast.AlterTableSetStatement, error) {
2694+
stmt := &ast.AlterTableSetStatement{
2695+
SchemaObjectName: tableName,
2696+
}
2697+
2698+
// Consume SET
2699+
p.nextToken()
2700+
2701+
// Expect (
2702+
if p.curTok.Type != TokenLParen {
2703+
return nil, fmt.Errorf("expected ( after SET, got %s", p.curTok.Literal)
2704+
}
2705+
p.nextToken()
2706+
2707+
// Parse options
2708+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
2709+
optionName := strings.ToUpper(p.curTok.Literal)
2710+
p.nextToken()
2711+
2712+
if optionName == "SYSTEM_VERSIONING" {
2713+
opt, err := p.parseSystemVersioningTableOption()
2714+
if err != nil {
2715+
return nil, err
2716+
}
2717+
stmt.Options = append(stmt.Options, opt)
2718+
}
2719+
2720+
if p.curTok.Type == TokenComma {
2721+
p.nextToken()
2722+
}
2723+
}
2724+
2725+
// Consume )
2726+
if p.curTok.Type == TokenRParen {
2727+
p.nextToken()
2728+
}
2729+
2730+
// Skip optional semicolon
2731+
if p.curTok.Type == TokenSemicolon {
2732+
p.nextToken()
2733+
}
2734+
2735+
return stmt, nil
2736+
}
2737+
2738+
func (p *Parser) parseSystemVersioningTableOption() (*ast.SystemVersioningTableOption, error) {
2739+
opt := &ast.SystemVersioningTableOption{
2740+
OptionKind: "LockEscalation",
2741+
ConsistencyCheckEnabled: "NotSet",
2742+
}
2743+
2744+
// Expect =
2745+
if p.curTok.Type != TokenEquals {
2746+
return nil, fmt.Errorf("expected = after SYSTEM_VERSIONING, got %s", p.curTok.Literal)
2747+
}
2748+
p.nextToken()
2749+
2750+
// Parse ON or OFF
2751+
stateVal := strings.ToUpper(p.curTok.Literal)
2752+
if stateVal == "ON" {
2753+
opt.OptionState = "On"
2754+
} else if stateVal == "OFF" {
2755+
opt.OptionState = "Off"
2756+
} else {
2757+
return nil, fmt.Errorf("expected ON or OFF after =, got %s", p.curTok.Literal)
2758+
}
2759+
p.nextToken()
2760+
2761+
// Check for optional sub-options in parentheses
2762+
if p.curTok.Type == TokenLParen {
2763+
p.nextToken()
2764+
2765+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
2766+
subOptName := strings.ToUpper(p.curTok.Literal)
2767+
p.nextToken()
2768+
2769+
if p.curTok.Type == TokenEquals {
2770+
p.nextToken()
2771+
}
2772+
2773+
switch subOptName {
2774+
case "HISTORY_TABLE":
2775+
histTable, err := p.parseSchemaObjectName()
2776+
if err != nil {
2777+
return nil, err
2778+
}
2779+
opt.HistoryTable = histTable
2780+
2781+
case "DATA_CONSISTENCY_CHECK":
2782+
checkVal := strings.ToUpper(p.curTok.Literal)
2783+
if checkVal == "ON" {
2784+
opt.ConsistencyCheckEnabled = "On"
2785+
} else if checkVal == "OFF" {
2786+
opt.ConsistencyCheckEnabled = "Off"
2787+
}
2788+
p.nextToken()
2789+
2790+
case "HISTORY_RETENTION_PERIOD":
2791+
retPeriod, err := p.parseRetentionPeriodDefinition()
2792+
if err != nil {
2793+
return nil, err
2794+
}
2795+
opt.RetentionPeriod = retPeriod
2796+
}
2797+
2798+
if p.curTok.Type == TokenComma {
2799+
p.nextToken()
2800+
}
2801+
}
2802+
2803+
// Consume )
2804+
if p.curTok.Type == TokenRParen {
2805+
p.nextToken()
2806+
}
2807+
}
2808+
2809+
return opt, nil
2810+
}
2811+
2812+
func (p *Parser) parseRetentionPeriodDefinition() (*ast.RetentionPeriodDefinition, error) {
2813+
ret := &ast.RetentionPeriodDefinition{}
2814+
2815+
// Check for INFINITE
2816+
if strings.ToUpper(p.curTok.Literal) == "INFINITE" {
2817+
ret.IsInfinity = true
2818+
ret.Units = "Day" // Default unit for INFINITE
2819+
p.nextToken()
2820+
return ret, nil
2821+
}
2822+
2823+
// Parse numeric duration
2824+
ret.IsInfinity = false
2825+
2826+
// Parse integer literal
2827+
if p.curTok.Type == TokenNumber {
2828+
lit := &ast.IntegerLiteral{
2829+
LiteralType: "Integer",
2830+
Value: p.curTok.Literal,
2831+
}
2832+
ret.Duration = lit
2833+
p.nextToken()
2834+
} else {
2835+
return nil, fmt.Errorf("expected number for retention period, got %s", p.curTok.Literal)
2836+
}
2837+
2838+
// Parse unit
2839+
unitVal := strings.ToUpper(p.curTok.Literal)
2840+
switch unitVal {
2841+
case "DAY", "DAYS":
2842+
ret.Units = "Day"
2843+
case "WEEK", "WEEKS":
2844+
ret.Units = "Week"
2845+
case "MONTH":
2846+
ret.Units = "Month"
2847+
case "MONTHS":
2848+
ret.Units = "Months"
2849+
case "YEAR", "YEARS":
2850+
ret.Units = "Year"
2851+
default:
2852+
return nil, fmt.Errorf("unexpected unit %s for retention period", unitVal)
2853+
}
2854+
p.nextToken()
2855+
2856+
return ret, nil
2857+
}
2858+
26882859
func (p *Parser) parseAlterRoleStatement() (*ast.AlterRoleStatement, error) {
26892860
// Consume ROLE
26902861
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+
{}

0 commit comments

Comments
 (0)