diff --git a/ast/alter_table_set_statement.go b/ast/alter_table_set_statement.go new file mode 100644 index 00000000..5a5be301 --- /dev/null +++ b/ast/alter_table_set_statement.go @@ -0,0 +1,37 @@ +package ast + +// AlterTableSetStatement represents ALTER TABLE ... SET statement +type AlterTableSetStatement struct { + SchemaObjectName *SchemaObjectName + Options []TableOption +} + +func (s *AlterTableSetStatement) statement() {} +func (s *AlterTableSetStatement) node() {} + +// TableOption is an interface for table options +type TableOption interface { + Node + tableOption() +} + +// SystemVersioningTableOption represents SYSTEM_VERSIONING option +type SystemVersioningTableOption struct { + OptionState string // "On", "Off" + ConsistencyCheckEnabled string // "On", "Off", "NotSet" + HistoryTable *SchemaObjectName + RetentionPeriod *RetentionPeriodDefinition + OptionKind string // Always "LockEscalation" +} + +func (o *SystemVersioningTableOption) tableOption() {} +func (o *SystemVersioningTableOption) node() {} + +// RetentionPeriodDefinition represents the history retention period +type RetentionPeriodDefinition struct { + Duration ScalarExpression + Units string // "Day", "Week", "Month", "Months", "Year" + IsInfinity bool +} + +func (r *RetentionPeriodDefinition) node() {} diff --git a/parser/marshal.go b/parser/marshal.go index bfedfd47..b0773f93 100644 --- a/parser/marshal.go +++ b/parser/marshal.go @@ -412,6 +412,8 @@ func statementToJSON(stmt ast.Statement) jsonNode { return alterTableSwitchStatementToJSON(s) case *ast.AlterTableConstraintModificationStatement: return alterTableConstraintModificationStatementToJSON(s) + case *ast.AlterTableSetStatement: + return alterTableSetStatementToJSON(s) case *ast.InsertBulkStatement: return insertBulkStatementToJSON(s) case *ast.BulkInsertStatement: @@ -6137,6 +6139,60 @@ func alterTableConstraintModificationStatementToJSON(s *ast.AlterTableConstraint return node } +func alterTableSetStatementToJSON(s *ast.AlterTableSetStatement) jsonNode { + node := jsonNode{ + "$type": "AlterTableSetStatement", + } + if len(s.Options) > 0 { + var options []jsonNode + for _, opt := range s.Options { + options = append(options, tableOptionToJSON(opt)) + } + node["Options"] = options + } + if s.SchemaObjectName != nil { + node["SchemaObjectName"] = schemaObjectNameToJSON(s.SchemaObjectName) + } + return node +} + +func tableOptionToJSON(opt ast.TableOption) jsonNode { + switch o := opt.(type) { + case *ast.SystemVersioningTableOption: + return systemVersioningTableOptionToJSON(o) + default: + return jsonNode{"$type": "UnknownTableOption"} + } +} + +func systemVersioningTableOptionToJSON(o *ast.SystemVersioningTableOption) jsonNode { + node := jsonNode{ + "$type": "SystemVersioningTableOption", + "OptionState": o.OptionState, + "ConsistencyCheckEnabled": o.ConsistencyCheckEnabled, + } + if o.HistoryTable != nil { + node["HistoryTable"] = schemaObjectNameToJSON(o.HistoryTable) + } + if o.RetentionPeriod != nil { + node["RetentionPeriod"] = retentionPeriodDefinitionToJSON(o.RetentionPeriod) + } + node["OptionKind"] = o.OptionKind + return node +} + +func retentionPeriodDefinitionToJSON(r *ast.RetentionPeriodDefinition) jsonNode { + node := jsonNode{ + "$type": "RetentionPeriodDefinition", + } + if r.Duration != nil { + node["Duration"] = scalarExpressionToJSON(r.Duration) + } + node["Units"] = r.Units + node["IsInfinity"] = r.IsInfinity + return node +} + func createExternalDataSourceStatementToJSON(s *ast.CreateExternalDataSourceStatement) jsonNode { node := jsonNode{ "$type": "CreateExternalDataSourceStatement", diff --git a/parser/parse_ddl.go b/parser/parse_ddl.go index c1b77f7b..8056a54e 100644 --- a/parser/parse_ddl.go +++ b/parser/parse_ddl.go @@ -2132,6 +2132,11 @@ func (p *Parser) parseAlterTableStatement() (ast.Statement, error) { return p.parseAlterTableConstraintModificationStatement(tableName) } + // Check for SET + if strings.ToUpper(p.curTok.Literal) == "SET" { + return p.parseAlterTableSetStatement(tableName) + } + return nil, fmt.Errorf("unexpected token in ALTER TABLE: %s", p.curTok.Literal) } @@ -2685,6 +2690,172 @@ func (p *Parser) parseAlterTableConstraintModificationStatement(tableName *ast.S return stmt, nil } +func (p *Parser) parseAlterTableSetStatement(tableName *ast.SchemaObjectName) (*ast.AlterTableSetStatement, error) { + stmt := &ast.AlterTableSetStatement{ + SchemaObjectName: tableName, + } + + // Consume SET + p.nextToken() + + // Expect ( + if p.curTok.Type != TokenLParen { + return nil, fmt.Errorf("expected ( after SET, got %s", p.curTok.Literal) + } + p.nextToken() + + // Parse options + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + optionName := strings.ToUpper(p.curTok.Literal) + p.nextToken() + + if optionName == "SYSTEM_VERSIONING" { + opt, err := p.parseSystemVersioningTableOption() + if err != nil { + return nil, err + } + stmt.Options = append(stmt.Options, opt) + } + + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + + // Consume ) + if p.curTok.Type == TokenRParen { + p.nextToken() + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + +func (p *Parser) parseSystemVersioningTableOption() (*ast.SystemVersioningTableOption, error) { + opt := &ast.SystemVersioningTableOption{ + OptionKind: "LockEscalation", + ConsistencyCheckEnabled: "NotSet", + } + + // Expect = + if p.curTok.Type != TokenEquals { + return nil, fmt.Errorf("expected = after SYSTEM_VERSIONING, got %s", p.curTok.Literal) + } + p.nextToken() + + // Parse ON or OFF + stateVal := strings.ToUpper(p.curTok.Literal) + if stateVal == "ON" { + opt.OptionState = "On" + } else if stateVal == "OFF" { + opt.OptionState = "Off" + } else { + return nil, fmt.Errorf("expected ON or OFF after =, got %s", p.curTok.Literal) + } + p.nextToken() + + // Check for optional sub-options in parentheses + if p.curTok.Type == TokenLParen { + p.nextToken() + + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + subOptName := strings.ToUpper(p.curTok.Literal) + p.nextToken() + + if p.curTok.Type == TokenEquals { + p.nextToken() + } + + switch subOptName { + case "HISTORY_TABLE": + histTable, err := p.parseSchemaObjectName() + if err != nil { + return nil, err + } + opt.HistoryTable = histTable + + case "DATA_CONSISTENCY_CHECK": + checkVal := strings.ToUpper(p.curTok.Literal) + if checkVal == "ON" { + opt.ConsistencyCheckEnabled = "On" + } else if checkVal == "OFF" { + opt.ConsistencyCheckEnabled = "Off" + } + p.nextToken() + + case "HISTORY_RETENTION_PERIOD": + retPeriod, err := p.parseRetentionPeriodDefinition() + if err != nil { + return nil, err + } + opt.RetentionPeriod = retPeriod + } + + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + + // Consume ) + if p.curTok.Type == TokenRParen { + p.nextToken() + } + } + + return opt, nil +} + +func (p *Parser) parseRetentionPeriodDefinition() (*ast.RetentionPeriodDefinition, error) { + ret := &ast.RetentionPeriodDefinition{} + + // Check for INFINITE + if strings.ToUpper(p.curTok.Literal) == "INFINITE" { + ret.IsInfinity = true + ret.Units = "Day" // Default unit for INFINITE + p.nextToken() + return ret, nil + } + + // Parse numeric duration + ret.IsInfinity = false + + // Parse integer literal + if p.curTok.Type == TokenNumber { + lit := &ast.IntegerLiteral{ + LiteralType: "Integer", + Value: p.curTok.Literal, + } + ret.Duration = lit + p.nextToken() + } else { + return nil, fmt.Errorf("expected number for retention period, got %s", p.curTok.Literal) + } + + // Parse unit + unitVal := strings.ToUpper(p.curTok.Literal) + switch unitVal { + case "DAY", "DAYS": + ret.Units = "Day" + case "WEEK", "WEEKS": + ret.Units = "Week" + case "MONTH": + ret.Units = "Month" + case "MONTHS": + ret.Units = "Months" + case "YEAR", "YEARS": + ret.Units = "Year" + default: + return nil, fmt.Errorf("unexpected unit %s for retention period", unitVal) + } + p.nextToken() + + return ret, nil +} + func (p *Parser) parseAlterRoleStatement() (*ast.AlterRoleStatement, error) { // Consume ROLE p.nextToken() diff --git a/parser/testdata/AlterTableStatementTests140_Azure/metadata.json b/parser/testdata/AlterTableStatementTests140_Azure/metadata.json index ccffb5b9..9e26dfee 100644 --- a/parser/testdata/AlterTableStatementTests140_Azure/metadata.json +++ b/parser/testdata/AlterTableStatementTests140_Azure/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} \ No newline at end of file diff --git a/parser/testdata/Baselines140_AlterTableStatementTests140_Azure/metadata.json b/parser/testdata/Baselines140_AlterTableStatementTests140_Azure/metadata.json index ccffb5b9..9e26dfee 100644 --- a/parser/testdata/Baselines140_AlterTableStatementTests140_Azure/metadata.json +++ b/parser/testdata/Baselines140_AlterTableStatementTests140_Azure/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} \ No newline at end of file