Skip to content

Commit 14461cc

Browse files
kyleconroyclaude
andcommitted
Add ledger table support for SQL Server 2022
- Add AST types for LedgerTableOption and LedgerViewOption - Add GENERATED ALWAYS AS TRANSACTION_ID START/END support - Add GENERATED ALWAYS AS SEQUENCE_NUMBER START/END support - Add LEDGER table option parsing with LEDGER_VIEW configuration - Add column name mapping options: TRANSACTION_ID_COLUMN_NAME, SEQUENCE_NUMBER_COLUMN_NAME, OPERATION_TYPE_COLUMN_NAME, OPERATION_TYPE_DESC_COLUMN_NAME - Add APPEND_ONLY option support - Add JSON marshaling for all ledger types Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6b11d89 commit 14461cc

6 files changed

Lines changed: 193 additions & 3 deletions

File tree

ast/ledger_table_option.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package ast
2+
3+
// LedgerTableOption represents the LEDGER table option
4+
type LedgerTableOption struct {
5+
OptionState string // "On", "Off"
6+
AppendOnly string // "On", "Off", "NotSet"
7+
LedgerViewOption *LedgerViewOption // Optional view configuration
8+
OptionKind string // "LockEscalation" (matches ScriptDom)
9+
}
10+
11+
func (o *LedgerTableOption) tableOption() {}
12+
func (o *LedgerTableOption) node() {}
13+
14+
// LedgerViewOption represents the LEDGER_VIEW configuration
15+
type LedgerViewOption struct {
16+
ViewName *SchemaObjectName
17+
TransactionIdColumnName *Identifier
18+
SequenceNumberColumnName *Identifier
19+
OperationTypeColumnName *Identifier
20+
OperationTypeDescColumnName *Identifier
21+
OptionKind string // "LockEscalation" (matches ScriptDom)
22+
}
23+
24+
func (o *LedgerViewOption) node() {}

parser/marshal.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5680,6 +5680,12 @@ func (p *Parser) parseCreateTableStatement() (*ast.CreateTableStatement, error)
56805680
return nil, err
56815681
}
56825682
stmt.Options = append(stmt.Options, opt)
5683+
} else if optionName == "LEDGER" {
5684+
opt, err := p.parseLedgerTableOption()
5685+
if err != nil {
5686+
return nil, err
5687+
}
5688+
stmt.Options = append(stmt.Options, opt)
56835689
} else if optionName == "CLUSTERED" {
56845690
// Could be CLUSTERED INDEX or CLUSTERED COLUMNSTORE INDEX
56855691
if strings.ToUpper(p.curTok.Literal) == "COLUMNSTORE" {
@@ -7512,6 +7518,22 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) {
75127518
} else if startEnd == "END" {
75137519
col.GeneratedAlways = "UserNameEnd"
75147520
}
7521+
} else if genType == "TRANSACTION_ID" {
7522+
startEnd := strings.ToUpper(p.curTok.Literal)
7523+
p.nextToken()
7524+
if startEnd == "START" {
7525+
col.GeneratedAlways = "TransactionIdStart"
7526+
} else if startEnd == "END" {
7527+
col.GeneratedAlways = "TransactionIdEnd"
7528+
}
7529+
} else if genType == "SEQUENCE_NUMBER" {
7530+
startEnd := strings.ToUpper(p.curTok.Literal)
7531+
p.nextToken()
7532+
if startEnd == "START" {
7533+
col.GeneratedAlways = "SequenceNumberStart"
7534+
} else if startEnd == "END" {
7535+
col.GeneratedAlways = "SequenceNumberEnd"
7536+
}
75157537
}
75167538
} else if p.curTok.Type == TokenNot {
75177539
p.nextToken() // consume NOT
@@ -9741,6 +9763,8 @@ func tableOptionToJSON(opt ast.TableOption) jsonNode {
97419763
return node
97429764
case *ast.SystemVersioningTableOption:
97439765
return systemVersioningTableOptionToJSON(o)
9766+
case *ast.LedgerTableOption:
9767+
return ledgerTableOptionToJSON(o)
97449768
case *ast.MemoryOptimizedTableOption:
97459769
return jsonNode{
97469770
"$type": "MemoryOptimizedTableOption",
@@ -17725,6 +17749,42 @@ func retentionPeriodDefinitionToJSON(r *ast.RetentionPeriodDefinition) jsonNode
1772517749
return node
1772617750
}
1772717751

17752+
func ledgerTableOptionToJSON(o *ast.LedgerTableOption) jsonNode {
17753+
node := jsonNode{
17754+
"$type": "LedgerTableOption",
17755+
"OptionState": o.OptionState,
17756+
"AppendOnly": o.AppendOnly,
17757+
}
17758+
if o.LedgerViewOption != nil {
17759+
node["LedgerViewOption"] = ledgerViewOptionToJSON(o.LedgerViewOption)
17760+
}
17761+
node["OptionKind"] = o.OptionKind
17762+
return node
17763+
}
17764+
17765+
func ledgerViewOptionToJSON(o *ast.LedgerViewOption) jsonNode {
17766+
node := jsonNode{
17767+
"$type": "LedgerViewOption",
17768+
}
17769+
if o.ViewName != nil {
17770+
node["ViewName"] = schemaObjectNameToJSON(o.ViewName)
17771+
}
17772+
if o.TransactionIdColumnName != nil {
17773+
node["TransactionIdColumnName"] = identifierToJSON(o.TransactionIdColumnName)
17774+
}
17775+
if o.SequenceNumberColumnName != nil {
17776+
node["SequenceNumberColumnName"] = identifierToJSON(o.SequenceNumberColumnName)
17777+
}
17778+
if o.OperationTypeColumnName != nil {
17779+
node["OperationTypeColumnName"] = identifierToJSON(o.OperationTypeColumnName)
17780+
}
17781+
if o.OperationTypeDescColumnName != nil {
17782+
node["OperationTypeDescColumnName"] = identifierToJSON(o.OperationTypeDescColumnName)
17783+
}
17784+
node["OptionKind"] = o.OptionKind
17785+
return node
17786+
}
17787+
1772817788
func createExternalDataSourceStatementToJSON(s *ast.CreateExternalDataSourceStatement) jsonNode {
1772917789
node := jsonNode{
1773017790
"$type": "CreateExternalDataSourceStatement",

parser/parse_ddl.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7151,6 +7151,12 @@ func (p *Parser) parseAlterTableSetStatement(tableName *ast.SchemaObjectName) (*
71517151
return nil, err
71527152
}
71537153
stmt.Options = append(stmt.Options, rdaOpt)
7154+
} else if optionName == "LEDGER" {
7155+
opt, err := p.parseLedgerTableOption()
7156+
if err != nil {
7157+
return nil, err
7158+
}
7159+
stmt.Options = append(stmt.Options, opt)
71547160
}
71557161

71567162
if p.curTok.Type == TokenComma {
@@ -7245,6 +7251,106 @@ func (p *Parser) parseSystemVersioningTableOption() (*ast.SystemVersioningTableO
72457251
return opt, nil
72467252
}
72477253

7254+
func (p *Parser) parseLedgerTableOption() (*ast.LedgerTableOption, error) {
7255+
opt := &ast.LedgerTableOption{
7256+
AppendOnly: "NotSet",
7257+
OptionKind: "LockEscalation",
7258+
LedgerViewOption: &ast.LedgerViewOption{OptionKind: "LockEscalation"}, // Always created per ScriptDom
7259+
}
7260+
7261+
// Expect =
7262+
if p.curTok.Type != TokenEquals {
7263+
return nil, fmt.Errorf("expected = after LEDGER, got %s", p.curTok.Literal)
7264+
}
7265+
p.nextToken()
7266+
7267+
// Parse ON or OFF
7268+
stateVal := strings.ToUpper(p.curTok.Literal)
7269+
if stateVal == "ON" {
7270+
opt.OptionState = "On"
7271+
} else if stateVal == "OFF" {
7272+
opt.OptionState = "Off"
7273+
} else {
7274+
return nil, fmt.Errorf("expected ON or OFF after =, got %s", p.curTok.Literal)
7275+
}
7276+
p.nextToken()
7277+
7278+
// Check for optional sub-options in parentheses
7279+
if p.curTok.Type == TokenLParen {
7280+
p.nextToken()
7281+
7282+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
7283+
subOptName := strings.ToUpper(p.curTok.Literal)
7284+
p.nextToken()
7285+
7286+
if p.curTok.Type == TokenEquals {
7287+
p.nextToken()
7288+
}
7289+
7290+
switch subOptName {
7291+
case "LEDGER_VIEW":
7292+
viewOpt := &ast.LedgerViewOption{OptionKind: "LockEscalation"}
7293+
viewName, err := p.parseSchemaObjectName()
7294+
if err != nil {
7295+
return nil, err
7296+
}
7297+
viewOpt.ViewName = viewName
7298+
7299+
// Check for optional column name mappings in parentheses
7300+
if p.curTok.Type == TokenLParen {
7301+
p.nextToken()
7302+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
7303+
colOptName := strings.ToUpper(p.curTok.Literal)
7304+
p.nextToken()
7305+
if p.curTok.Type == TokenEquals {
7306+
p.nextToken()
7307+
}
7308+
7309+
switch colOptName {
7310+
case "TRANSACTION_ID_COLUMN_NAME":
7311+
viewOpt.TransactionIdColumnName = p.parseIdentifier()
7312+
case "SEQUENCE_NUMBER_COLUMN_NAME":
7313+
viewOpt.SequenceNumberColumnName = p.parseIdentifier()
7314+
case "OPERATION_TYPE_COLUMN_NAME":
7315+
viewOpt.OperationTypeColumnName = p.parseIdentifier()
7316+
case "OPERATION_TYPE_DESC_COLUMN_NAME":
7317+
viewOpt.OperationTypeDescColumnName = p.parseIdentifier()
7318+
}
7319+
7320+
if p.curTok.Type == TokenComma {
7321+
p.nextToken()
7322+
}
7323+
}
7324+
if p.curTok.Type == TokenRParen {
7325+
p.nextToken()
7326+
}
7327+
}
7328+
opt.LedgerViewOption = viewOpt
7329+
7330+
case "APPEND_ONLY":
7331+
appendVal := strings.ToUpper(p.curTok.Literal)
7332+
if appendVal == "ON" {
7333+
opt.AppendOnly = "On"
7334+
} else if appendVal == "OFF" {
7335+
opt.AppendOnly = "Off"
7336+
}
7337+
p.nextToken()
7338+
}
7339+
7340+
if p.curTok.Type == TokenComma {
7341+
p.nextToken()
7342+
}
7343+
}
7344+
7345+
// Consume )
7346+
if p.curTok.Type == TokenRParen {
7347+
p.nextToken()
7348+
}
7349+
}
7350+
7351+
return opt, nil
7352+
}
7353+
72487354
func (p *Parser) parseRetentionPeriodDefinition() (*ast.RetentionPeriodDefinition, error) {
72497355
ret := &ast.RetentionPeriodDefinition{}
72507356

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)