Skip to content

Commit e47180f

Browse files
kyleconroyclaude
andcommitted
Add CREATE SELECTIVE XML INDEX statement support
- Add CreateSelectiveXmlIndexStatement AST type - Add SQLDataType field to SelectiveXmlIndexPromotedPath - Parse primary selective XML index with FOR clause paths - Parse secondary selective XML index (USING XML INDEX ... FOR) - Add JSON marshaling for the new types Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 380b24e commit e47180f

5 files changed

Lines changed: 255 additions & 12 deletions

File tree

ast/alter_index_statement.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,29 @@ type SelectiveXmlIndexPromotedPath struct {
2020
Name *Identifier
2121
Path *StringLiteral
2222
XQueryDataType *StringLiteral
23+
SQLDataType *SqlDataTypeReference
2324
MaxLength *IntegerLiteral
2425
IsSingleton bool
2526
}
2627

2728
func (s *SelectiveXmlIndexPromotedPath) node() {}
2829

30+
// CreateSelectiveXmlIndexStatement represents CREATE SELECTIVE XML INDEX statement
31+
type CreateSelectiveXmlIndexStatement struct {
32+
Name *Identifier
33+
OnName *SchemaObjectName
34+
XmlColumn *Identifier
35+
IsSecondary bool
36+
UsingXmlIndexName *Identifier // For secondary indexes
37+
PathName *Identifier // For secondary indexes
38+
PromotedPaths []*SelectiveXmlIndexPromotedPath
39+
XmlNamespaces *XmlNamespaces
40+
IndexOptions []IndexOption
41+
}
42+
43+
func (s *CreateSelectiveXmlIndexStatement) statement() {}
44+
func (s *CreateSelectiveXmlIndexStatement) node() {}
45+
2946
// XmlNamespaces represents a WITH XMLNAMESPACES clause
3047
type XmlNamespaces struct {
3148
XmlNamespacesElements []XmlNamespacesElement

parser/marshal.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,8 @@ func statementToJSON(stmt ast.Statement) jsonNode {
498498
return createTypeTableStatementToJSON(s)
499499
case *ast.CreateXmlIndexStatement:
500500
return createXmlIndexStatementToJSON(s)
501+
case *ast.CreateSelectiveXmlIndexStatement:
502+
return createSelectiveXmlIndexStatementToJSON(s)
501503
case *ast.CreatePartitionFunctionStatement:
502504
return createPartitionFunctionStatementToJSON(s)
503505
case *ast.CreateEventNotificationStatement:
@@ -13972,6 +13974,9 @@ func selectiveXmlIndexPromotedPathToJSON(p *ast.SelectiveXmlIndexPromotedPath) j
1397213974
if p.XQueryDataType != nil {
1397313975
node["XQueryDataType"] = stringLiteralToJSON(p.XQueryDataType)
1397413976
}
13977+
if p.SQLDataType != nil {
13978+
node["SQLDataType"] = sqlDataTypeReferenceToJSON(p.SQLDataType)
13979+
}
1397513980
if p.MaxLength != nil {
1397613981
node["MaxLength"] = scalarExpressionToJSON(p.MaxLength)
1397713982
}
@@ -17492,6 +17497,46 @@ func createXmlIndexStatementToJSON(s *ast.CreateXmlIndexStatement) jsonNode {
1749217497
return node
1749317498
}
1749417499

17500+
func createSelectiveXmlIndexStatementToJSON(s *ast.CreateSelectiveXmlIndexStatement) jsonNode {
17501+
node := jsonNode{
17502+
"$type": "CreateSelectiveXmlIndexStatement",
17503+
}
17504+
node["IsSecondary"] = s.IsSecondary
17505+
if s.XmlColumn != nil {
17506+
node["XmlColumn"] = identifierToJSON(s.XmlColumn)
17507+
}
17508+
if s.UsingXmlIndexName != nil {
17509+
node["UsingXmlIndexName"] = identifierToJSON(s.UsingXmlIndexName)
17510+
}
17511+
if s.PathName != nil {
17512+
node["PathName"] = identifierToJSON(s.PathName)
17513+
}
17514+
if len(s.PromotedPaths) > 0 {
17515+
paths := make([]jsonNode, len(s.PromotedPaths))
17516+
for i, path := range s.PromotedPaths {
17517+
paths[i] = selectiveXmlIndexPromotedPathToJSON(path)
17518+
}
17519+
node["PromotedPaths"] = paths
17520+
}
17521+
if s.XmlNamespaces != nil {
17522+
node["XmlNamespaces"] = xmlNamespacesToJSON(s.XmlNamespaces)
17523+
}
17524+
if s.Name != nil {
17525+
node["Name"] = identifierToJSON(s.Name)
17526+
}
17527+
if s.OnName != nil {
17528+
node["OnName"] = schemaObjectNameToJSON(s.OnName)
17529+
}
17530+
if len(s.IndexOptions) > 0 {
17531+
opts := make([]jsonNode, len(s.IndexOptions))
17532+
for i, opt := range s.IndexOptions {
17533+
opts[i] = indexOptionToJSON(opt)
17534+
}
17535+
node["IndexOptions"] = opts
17536+
}
17537+
return node
17538+
}
17539+
1749517540
func createPartitionFunctionStatementToJSON(s *ast.CreatePartitionFunctionStatement) jsonNode {
1749617541
node := jsonNode{
1749717542
"$type": "CreatePartitionFunctionStatement",

parser/parse_statements.go

Lines changed: 191 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2803,6 +2803,8 @@ func (p *Parser) parseCreateStatement() (ast.Statement, error) {
28032803
return p.parseCreateIndexStatement()
28042804
case "PRIMARY":
28052805
return p.parseCreateXmlIndexStatement()
2806+
case "SELECTIVE":
2807+
return p.parseCreateSelectiveXmlIndexStatement()
28062808
case "COLUMN":
28072809
return p.parseCreateColumnMasterKeyStatement()
28082810
case "CRYPTOGRAPHIC":
@@ -13288,34 +13290,32 @@ func (p *Parser) parseCreateXmlIndexStatement() (*ast.CreateXmlIndexStatement, e
1328813290
return stmt, nil
1328913291
}
1329013292

13291-
func (p *Parser) parseCreateXmlIndexFromXml() (*ast.CreateXmlIndexStatement, error) {
13293+
func (p *Parser) parseCreateXmlIndexFromXml() (ast.Statement, error) {
1329213294
// XML has already been consumed, curTok is INDEX
1329313295
if p.curTok.Type == TokenIndex {
1329413296
p.nextToken() // consume INDEX
1329513297
}
1329613298

13297-
stmt := &ast.CreateXmlIndexStatement{
13298-
Primary: false,
13299-
SecondaryXmlIndexType: "NotSpecified",
13300-
Name: p.parseIdentifier(),
13301-
}
13299+
name := p.parseIdentifier()
13300+
var onName *ast.SchemaObjectName
13301+
var xmlColumn *ast.Identifier
1330213302

1330313303
// Parse ON table_name
1330413304
if strings.ToUpper(p.curTok.Literal) == "ON" {
1330513305
p.nextToken() // consume ON
13306-
stmt.OnName, _ = p.parseSchemaObjectName()
13306+
onName, _ = p.parseSchemaObjectName()
1330713307
}
1330813308

1330913309
// Parse (column)
1331013310
if p.curTok.Type == TokenLParen {
1331113311
p.nextToken() // consume (
13312-
stmt.XmlColumn = p.parseIdentifier()
13312+
xmlColumn = p.parseIdentifier()
1331313313
if p.curTok.Type == TokenRParen {
1331413314
p.nextToken() // consume )
1331513315
}
1331613316
}
1331713317

13318-
// Parse USING XML INDEX name FOR VALUE|PATH|PROPERTY
13318+
// Parse USING XML INDEX name
1331913319
if strings.ToUpper(p.curTok.Literal) == "USING" {
1332013320
p.nextToken() // consume USING
1332113321
if strings.ToUpper(p.curTok.Literal) == "XML" {
@@ -13324,9 +13324,39 @@ func (p *Parser) parseCreateXmlIndexFromXml() (*ast.CreateXmlIndexStatement, err
1332413324
if p.curTok.Type == TokenIndex {
1332513325
p.nextToken() // consume INDEX
1332613326
}
13327-
stmt.SecondaryXmlIndexName = p.parseIdentifier()
13327+
usingName := p.parseIdentifier()
1332813328
if strings.ToUpper(p.curTok.Literal) == "FOR" {
1332913329
p.nextToken() // consume FOR
13330+
// Check if this is a selective XML index (FOR followed by parenthesis with path names)
13331+
// vs regular secondary XML index (FOR followed by VALUE|PATH|PROPERTY)
13332+
if p.curTok.Type == TokenLParen {
13333+
// This is a secondary selective XML index
13334+
selectiveStmt := &ast.CreateSelectiveXmlIndexStatement{
13335+
Name: name,
13336+
OnName: onName,
13337+
XmlColumn: xmlColumn,
13338+
IsSecondary: true,
13339+
UsingXmlIndexName: usingName,
13340+
}
13341+
p.nextToken() // consume (
13342+
// Parse path name(s)
13343+
if p.curTok.Type == TokenIdent {
13344+
selectiveStmt.PathName = p.parseIdentifier()
13345+
}
13346+
if p.curTok.Type == TokenRParen {
13347+
p.nextToken() // consume )
13348+
}
13349+
return selectiveStmt, nil
13350+
}
13351+
// Regular secondary XML index
13352+
stmt := &ast.CreateXmlIndexStatement{
13353+
Primary: false,
13354+
SecondaryXmlIndexType: "NotSpecified",
13355+
Name: name,
13356+
OnName: onName,
13357+
XmlColumn: xmlColumn,
13358+
SecondaryXmlIndexName: usingName,
13359+
}
1333013360
switch strings.ToUpper(p.curTok.Literal) {
1333113361
case "VALUE":
1333213362
stmt.SecondaryXmlIndexType = "Value"
@@ -13338,9 +13368,26 @@ func (p *Parser) parseCreateXmlIndexFromXml() (*ast.CreateXmlIndexStatement, err
1333813368
stmt.SecondaryXmlIndexType = "Property"
1333913369
p.nextToken()
1334013370
}
13371+
// Parse WITH (options) if present
13372+
if strings.ToUpper(p.curTok.Literal) == "WITH" {
13373+
p.nextToken() // consume WITH
13374+
if p.curTok.Type == TokenLParen {
13375+
stmt.IndexOptions = p.parseCreateIndexOptions()
13376+
}
13377+
}
13378+
return stmt, nil
1334113379
}
1334213380
}
1334313381

13382+
// Non-secondary XML index
13383+
stmt := &ast.CreateXmlIndexStatement{
13384+
Primary: false,
13385+
SecondaryXmlIndexType: "NotSpecified",
13386+
Name: name,
13387+
OnName: onName,
13388+
XmlColumn: xmlColumn,
13389+
}
13390+
1334413391
// Parse WITH (options) if present
1334513392
if strings.ToUpper(p.curTok.Literal) == "WITH" {
1334613393
p.nextToken() // consume WITH
@@ -13353,6 +13400,140 @@ func (p *Parser) parseCreateXmlIndexFromXml() (*ast.CreateXmlIndexStatement, err
1335313400
return stmt, nil
1335413401
}
1335513402

13403+
func (p *Parser) parseCreateSelectiveXmlIndexStatement() (*ast.CreateSelectiveXmlIndexStatement, error) {
13404+
// SELECTIVE has already been matched, consume it
13405+
p.nextToken() // consume SELECTIVE
13406+
if strings.ToUpper(p.curTok.Literal) == "XML" {
13407+
p.nextToken() // consume XML
13408+
}
13409+
if p.curTok.Type == TokenIndex {
13410+
p.nextToken() // consume INDEX
13411+
}
13412+
13413+
stmt := &ast.CreateSelectiveXmlIndexStatement{
13414+
IsSecondary: false,
13415+
Name: p.parseIdentifier(),
13416+
}
13417+
13418+
// Parse ON table_name
13419+
if strings.ToUpper(p.curTok.Literal) == "ON" {
13420+
p.nextToken() // consume ON
13421+
stmt.OnName, _ = p.parseSchemaObjectName()
13422+
}
13423+
13424+
// Parse (column)
13425+
if p.curTok.Type == TokenLParen {
13426+
p.nextToken() // consume (
13427+
stmt.XmlColumn = p.parseIdentifier()
13428+
if p.curTok.Type == TokenRParen {
13429+
p.nextToken() // consume )
13430+
}
13431+
}
13432+
13433+
// Parse optional WITH XMLNAMESPACES clause
13434+
if strings.ToUpper(p.curTok.Literal) == "WITH" && strings.ToUpper(p.peekTok.Literal) == "XMLNAMESPACES" {
13435+
p.nextToken() // consume WITH
13436+
stmt.XmlNamespaces = p.parseXmlNamespaces()
13437+
}
13438+
13439+
// Parse FOR clause with paths
13440+
if strings.ToUpper(p.curTok.Literal) == "FOR" {
13441+
p.nextToken() // consume FOR
13442+
if p.curTok.Type == TokenLParen {
13443+
p.nextToken() // consume (
13444+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
13445+
path := p.parseSelectiveXmlIndexPath()
13446+
if path != nil {
13447+
stmt.PromotedPaths = append(stmt.PromotedPaths, path)
13448+
}
13449+
if p.curTok.Type == TokenComma {
13450+
p.nextToken() // consume ,
13451+
} else {
13452+
break
13453+
}
13454+
}
13455+
if p.curTok.Type == TokenRParen {
13456+
p.nextToken() // consume )
13457+
}
13458+
}
13459+
}
13460+
13461+
// Parse WITH (options) if present
13462+
if strings.ToUpper(p.curTok.Literal) == "WITH" {
13463+
p.nextToken() // consume WITH
13464+
if p.curTok.Type == TokenLParen {
13465+
stmt.IndexOptions = p.parseCreateIndexOptions()
13466+
}
13467+
}
13468+
13469+
return stmt, nil
13470+
}
13471+
13472+
func (p *Parser) parseSelectiveXmlIndexPath() *ast.SelectiveXmlIndexPromotedPath {
13473+
path := &ast.SelectiveXmlIndexPromotedPath{}
13474+
13475+
// Parse path name (identifier)
13476+
path.Name = p.parseIdentifier()
13477+
13478+
// Check for = 'path_value'
13479+
if p.curTok.Type == TokenEquals {
13480+
p.nextToken() // consume =
13481+
if p.curTok.Type == TokenString || p.curTok.Type == TokenNationalString {
13482+
path.Path, _ = p.parseStringLiteral()
13483+
}
13484+
}
13485+
13486+
// Parse optional AS XQUERY/SQL clause
13487+
if p.curTok.Type == TokenAs {
13488+
p.nextToken() // consume AS
13489+
upperLit := strings.ToUpper(p.curTok.Literal)
13490+
if upperLit == "XQUERY" {
13491+
p.nextToken() // consume XQUERY
13492+
// Check for optional type or MAXLENGTH
13493+
if p.curTok.Type == TokenString || p.curTok.Type == TokenNationalString {
13494+
// XQuery type like 'xs:string' or 'node()'
13495+
path.XQueryDataType, _ = p.parseStringLiteral()
13496+
}
13497+
// Check for MAXLENGTH
13498+
if strings.ToUpper(p.curTok.Literal) == "MAXLENGTH" {
13499+
p.nextToken() // consume MAXLENGTH
13500+
if p.curTok.Type == TokenLParen {
13501+
p.nextToken() // consume (
13502+
if p.curTok.Type == TokenNumber {
13503+
path.MaxLength = &ast.IntegerLiteral{
13504+
LiteralType: "Integer",
13505+
Value: p.curTok.Literal,
13506+
}
13507+
p.nextToken() // consume number
13508+
}
13509+
if p.curTok.Type == TokenRParen {
13510+
p.nextToken() // consume )
13511+
}
13512+
}
13513+
}
13514+
// Check for SINGLETON
13515+
if strings.ToUpper(p.curTok.Literal) == "SINGLETON" {
13516+
path.IsSingleton = true
13517+
p.nextToken() // consume SINGLETON
13518+
}
13519+
} else if upperLit == "SQL" {
13520+
p.nextToken() // consume SQL
13521+
// Parse SQL data type
13522+
dt, _ := p.parseDataTypeReference()
13523+
if sdt, ok := dt.(*ast.SqlDataTypeReference); ok {
13524+
path.SQLDataType = sdt
13525+
}
13526+
// Check for SINGLETON
13527+
if strings.ToUpper(p.curTok.Literal) == "SINGLETON" {
13528+
path.IsSingleton = true
13529+
p.nextToken() // consume SINGLETON
13530+
}
13531+
}
13532+
}
13533+
13534+
return path
13535+
}
13536+
1335613537
func (p *Parser) parseCreateXmlSchemaCollectionFromXml() (*ast.CreateXmlSchemaCollectionStatement, error) {
1335713538
// XML has already been consumed, expect SCHEMA
1335813539
if strings.ToUpper(p.curTok.Literal) == "SCHEMA" {
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)