diff --git a/ast/alter_table_drop_table_element_statement.go b/ast/alter_table_drop_table_element_statement.go index fe1a388f..61dd15a1 100644 --- a/ast/alter_table_drop_table_element_statement.go +++ b/ast/alter_table_drop_table_element_statement.go @@ -2,7 +2,7 @@ package ast // AlterTableDropTableElementStatement represents an ALTER TABLE ... DROP statement. type AlterTableDropTableElementStatement struct { - SchemaObjectName *SchemaObjectName + SchemaObjectName *SchemaObjectName AlterTableDropTableElements []*AlterTableDropTableElement } @@ -11,9 +11,51 @@ func (*AlterTableDropTableElementStatement) statement() {} // AlterTableDropTableElement represents an element being dropped from a table. type AlterTableDropTableElement struct { - TableElementType string - Name *Identifier - IsIfExists bool + TableElementType string + Name *Identifier + IsIfExists bool + DropClusteredConstraintOptions []DropClusteredConstraintOption } func (*AlterTableDropTableElement) node() {} + +// DropClusteredConstraintOption is an interface for options when dropping clustered constraints. +type DropClusteredConstraintOption interface { + node() + dropClusteredConstraintOption() +} + +// DropClusteredConstraintStateOption represents an ON/OFF option like ONLINE = ON. +type DropClusteredConstraintStateOption struct { + OptionKind string + OptionState string +} + +func (*DropClusteredConstraintStateOption) node() {} +func (*DropClusteredConstraintStateOption) dropClusteredConstraintOption() {} + +// DropClusteredConstraintMoveOption represents a MOVE TO option. +type DropClusteredConstraintMoveOption struct { + OptionKind string + OptionValue *FileGroupOrPartitionScheme +} + +func (*DropClusteredConstraintMoveOption) node() {} +func (*DropClusteredConstraintMoveOption) dropClusteredConstraintOption() {} + +// DropClusteredConstraintValueOption represents a value option like MAXDOP = 21. +type DropClusteredConstraintValueOption struct { + OptionKind string + OptionValue ScalarExpression +} + +func (*DropClusteredConstraintValueOption) node() {} +func (*DropClusteredConstraintValueOption) dropClusteredConstraintOption() {} + +// FileGroupOrPartitionScheme represents a filegroup or partition scheme reference. +type FileGroupOrPartitionScheme struct { + Name *IdentifierOrValueExpression + PartitionSchemeColumns []*Identifier +} + +func (*FileGroupOrPartitionScheme) node() {} diff --git a/parser/marshal.go b/parser/marshal.go index 7ccea93e..02c2f832 100644 --- a/parser/marshal.go +++ b/parser/marshal.go @@ -601,10 +601,65 @@ func alterTableDropTableElementToJSON(e *ast.AlterTableDropTableElement) jsonNod if e.Name != nil { node["Name"] = identifierToJSON(e.Name) } + if len(e.DropClusteredConstraintOptions) > 0 { + options := make([]jsonNode, len(e.DropClusteredConstraintOptions)) + for i, o := range e.DropClusteredConstraintOptions { + options[i] = dropClusteredConstraintOptionToJSON(o) + } + node["DropClusteredConstraintOptions"] = options + } node["IsIfExists"] = e.IsIfExists return node } +func dropClusteredConstraintOptionToJSON(o ast.DropClusteredConstraintOption) jsonNode { + switch opt := o.(type) { + case *ast.DropClusteredConstraintStateOption: + return jsonNode{ + "$type": "DropClusteredConstraintStateOption", + "OptionState": opt.OptionState, + "OptionKind": opt.OptionKind, + } + case *ast.DropClusteredConstraintMoveOption: + node := jsonNode{ + "$type": "DropClusteredConstraintMoveOption", + "OptionKind": opt.OptionKind, + } + if opt.OptionValue != nil { + node["OptionValue"] = fileGroupOrPartitionSchemeToJSON(opt.OptionValue) + } + return node + case *ast.DropClusteredConstraintValueOption: + node := jsonNode{ + "$type": "DropClusteredConstraintValueOption", + "OptionKind": opt.OptionKind, + } + if opt.OptionValue != nil { + node["OptionValue"] = scalarExpressionToJSON(opt.OptionValue) + } + return node + default: + return jsonNode{"$type": "UnknownDropClusteredConstraintOption"} + } +} + +func fileGroupOrPartitionSchemeToJSON(fg *ast.FileGroupOrPartitionScheme) jsonNode { + node := jsonNode{ + "$type": "FileGroupOrPartitionScheme", + } + if fg.Name != nil { + node["Name"] = identifierOrValueExpressionToJSON(fg.Name) + } + if len(fg.PartitionSchemeColumns) > 0 { + cols := make([]jsonNode, len(fg.PartitionSchemeColumns)) + for i, c := range fg.PartitionSchemeColumns { + cols[i] = identifierToJSON(c) + } + node["PartitionSchemeColumns"] = cols + } + return node +} + func alterTableAlterIndexStatementToJSON(s *ast.AlterTableAlterIndexStatement) jsonNode { node := jsonNode{ "$type": "AlterTableAlterIndexStatement", diff --git a/parser/parse_ddl.go b/parser/parse_ddl.go index c016ce97..cfa31554 100644 --- a/parser/parse_ddl.go +++ b/parser/parse_ddl.go @@ -2149,7 +2149,7 @@ func (p *Parser) parseAlterTableDropStatement(tableName *ast.SchemaObjectName) ( } // Parse multiple elements separated by commas - // Format: DROP [COLUMN] name, [CONSTRAINT] name, [INDEX] name, ... + // Format: DROP [COLUMN] name [WITH (options)], [CONSTRAINT] name [WITH (options)], ... var currentElementType string = "NotSpecified" for { @@ -2179,6 +2179,16 @@ func (p *Parser) parseAlterTableDropStatement(tableName *ast.SchemaObjectName) ( Name: p.parseIdentifier(), IsIfExists: false, } + + // Check for WITH clause + if p.curTok.Type == TokenWith { + options, err := p.parseDropClusteredConstraintOptions() + if err != nil { + return nil, err + } + element.DropClusteredConstraintOptions = options + } + stmt.AlterTableDropTableElements = append(stmt.AlterTableDropTableElements, element) // After adding an element, reset type to NotSpecified for next element @@ -2201,6 +2211,126 @@ func (p *Parser) parseAlterTableDropStatement(tableName *ast.SchemaObjectName) ( return stmt, nil } +func (p *Parser) parseDropClusteredConstraintOptions() ([]ast.DropClusteredConstraintOption, error) { + // Consume WITH + p.nextToken() + + if p.curTok.Type != TokenLParen { + return nil, fmt.Errorf("expected ( after WITH, got %s", p.curTok.Literal) + } + p.nextToken() // consume ( + + var options []ast.DropClusteredConstraintOption + + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + optionName := strings.ToUpper(p.curTok.Literal) + + switch optionName { + case "ONLINE": + p.nextToken() // consume ONLINE + if p.curTok.Type != TokenEquals { + return nil, fmt.Errorf("expected = after ONLINE, got %s", p.curTok.Literal) + } + p.nextToken() // consume = + state := strings.ToUpper(p.curTok.Literal) + var optionState string + if state == "ON" { + optionState = "On" + } else if state == "OFF" { + optionState = "Off" + } else { + return nil, fmt.Errorf("expected ON or OFF after ONLINE =, got %s", p.curTok.Literal) + } + p.nextToken() // consume ON/OFF + options = append(options, &ast.DropClusteredConstraintStateOption{ + OptionKind: "Online", + OptionState: optionState, + }) + + case "MOVE": + p.nextToken() // consume MOVE + if strings.ToUpper(p.curTok.Literal) != "TO" { + return nil, fmt.Errorf("expected TO after MOVE, got %s", p.curTok.Literal) + } + p.nextToken() // consume TO + + fg, err := p.parseFileGroupOrPartitionScheme() + if err != nil { + return nil, err + } + options = append(options, &ast.DropClusteredConstraintMoveOption{ + OptionKind: "MoveTo", + OptionValue: fg, + }) + + case "MAXDOP": + p.nextToken() // consume MAXDOP + if p.curTok.Type != TokenEquals { + return nil, fmt.Errorf("expected = after MAXDOP, got %s", p.curTok.Literal) + } + p.nextToken() // consume = + if p.curTok.Type != TokenNumber { + return nil, fmt.Errorf("expected number after MAXDOP =, got %s", p.curTok.Literal) + } + options = append(options, &ast.DropClusteredConstraintValueOption{ + OptionKind: "MaxDop", + OptionValue: &ast.IntegerLiteral{ + LiteralType: "Integer", + Value: p.curTok.Literal, + }, + }) + p.nextToken() // consume number + + default: + return nil, fmt.Errorf("unexpected option in DROP WITH clause: %s", p.curTok.Literal) + } + + // Check for comma or end of options + if p.curTok.Type == TokenComma { + p.nextToken() // consume comma + } + } + + if p.curTok.Type != TokenRParen { + return nil, fmt.Errorf("expected ) to close WITH options, got %s", p.curTok.Literal) + } + p.nextToken() // consume ) + + return options, nil +} + +func (p *Parser) parseFileGroupOrPartitionScheme() (*ast.FileGroupOrPartitionScheme, error) { + fg := &ast.FileGroupOrPartitionScheme{} + + // Parse filegroup/partition scheme name (can be identifier or string literal) + iove, err := p.parseIdentifierOrValueExpression() + if err != nil { + return nil, err + } + fg.Name = iove + + // Check for partition scheme columns (column1, column2, ...) + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + if p.curTok.Type != TokenIdent && p.curTok.Type != TokenLBracket { + return nil, fmt.Errorf("expected column identifier in partition scheme, got %s", p.curTok.Literal) + } + fg.PartitionSchemeColumns = append(fg.PartitionSchemeColumns, p.parseIdentifier()) + + if p.curTok.Type == TokenComma { + p.nextToken() // consume comma + } + } + if p.curTok.Type != TokenRParen { + return nil, fmt.Errorf("expected ) to close partition scheme columns, got %s", p.curTok.Literal) + } + p.nextToken() // consume ) + } + + return fg, nil +} + func (p *Parser) parseAlterTableAlterIndexStatement(tableName *ast.SchemaObjectName) (*ast.AlterTableAlterIndexStatement, error) { // Consume ALTER p.nextToken() diff --git a/parser/testdata/AlterTableDropTableElementStatementTests90/metadata.json b/parser/testdata/AlterTableDropTableElementStatementTests90/metadata.json index ccffb5b9..9e26dfee 100644 --- a/parser/testdata/AlterTableDropTableElementStatementTests90/metadata.json +++ b/parser/testdata/AlterTableDropTableElementStatementTests90/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} \ No newline at end of file diff --git a/parser/testdata/Baselines90_AlterTableDropTableElementStatementTests90/metadata.json b/parser/testdata/Baselines90_AlterTableDropTableElementStatementTests90/metadata.json index ccffb5b9..9e26dfee 100644 --- a/parser/testdata/Baselines90_AlterTableDropTableElementStatementTests90/metadata.json +++ b/parser/testdata/Baselines90_AlterTableDropTableElementStatementTests90/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} \ No newline at end of file