Skip to content

Commit a282ea8

Browse files
committed
Add FILETABLE support for CREATE/ALTER TABLE statements
- Add AS FILETABLE parsing in CREATE TABLE statements - Add FILETABLE_DIRECTORY, FILETABLE_COLLATE_FILENAME options - Add FILETABLE constraint name options (PRIMARY_KEY, STREAMID, FULLPATH) - Add FEDERATED ON clause parsing for federation schemes - Add ENABLE/DISABLE FILETABLE_NAMESPACE for ALTER TABLE - Add FileTableDirectoryTableOption, FileTableCollateFileNameTableOption, FileTableConstraintNameTableOption, FederationScheme AST types - Enable Baselines110_CreateAlterTableStatementTests110 test
1 parent 4002266 commit a282ea8

7 files changed

Lines changed: 414 additions & 6 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package ast
2+
3+
// AlterTableFileTableNamespaceStatement represents ALTER TABLE ... ENABLE/DISABLE FILETABLE_NAMESPACE
4+
type AlterTableFileTableNamespaceStatement struct {
5+
SchemaObjectName *SchemaObjectName `json:"SchemaObjectName,omitempty"`
6+
IsEnable bool `json:"IsEnable,omitempty"`
7+
}
8+
9+
func (s *AlterTableFileTableNamespaceStatement) node() {}
10+
func (s *AlterTableFileTableNamespaceStatement) statement() {}

ast/create_table_statement.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,17 @@ type CreateTableStatement struct {
1111
TextImageOn *IdentifierOrValueExpression
1212
FileStreamOn *IdentifierOrValueExpression
1313
Options []TableOption
14+
FederationScheme *FederationScheme
1415
}
1516

17+
// FederationScheme represents a FEDERATED ON clause
18+
type FederationScheme struct {
19+
DistributionName *Identifier
20+
ColumnName *Identifier
21+
}
22+
23+
func (*FederationScheme) node() {}
24+
1625
// TableDataCompressionOption represents a DATA_COMPRESSION option
1726
type TableDataCompressionOption struct {
1827
DataCompressionOption *DataCompressionOption

ast/filetable_options.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package ast
2+
3+
// FileTableDirectoryTableOption represents a FILETABLE_DIRECTORY table option
4+
type FileTableDirectoryTableOption struct {
5+
Value ScalarExpression `json:"Value,omitempty"`
6+
OptionKind string `json:"OptionKind,omitempty"`
7+
}
8+
9+
func (*FileTableDirectoryTableOption) node() {}
10+
func (*FileTableDirectoryTableOption) tableOption() {}
11+
12+
// FileTableCollateFileNameTableOption represents a FILETABLE_COLLATE_FILENAME table option
13+
type FileTableCollateFileNameTableOption struct {
14+
Value *Identifier `json:"Value,omitempty"`
15+
OptionKind string `json:"OptionKind,omitempty"`
16+
}
17+
18+
func (*FileTableCollateFileNameTableOption) node() {}
19+
func (*FileTableCollateFileNameTableOption) tableOption() {}
20+
21+
// FileTableConstraintNameTableOption represents various FILETABLE constraint name options
22+
type FileTableConstraintNameTableOption struct {
23+
Value *Identifier `json:"Value,omitempty"`
24+
OptionKind string `json:"OptionKind,omitempty"`
25+
}
26+
27+
func (*FileTableConstraintNameTableOption) node() {}
28+
func (*FileTableConstraintNameTableOption) tableOption() {}

parser/marshal.go

Lines changed: 296 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,8 @@ func statementToJSON(stmt ast.Statement) jsonNode {
486486
return dropSchemaStatementToJSON(s)
487487
case *ast.AlterTableTriggerModificationStatement:
488488
return alterTableTriggerModificationStatementToJSON(s)
489+
case *ast.AlterTableFileTableNamespaceStatement:
490+
return alterTableFileTableNamespaceStatementToJSON(s)
489491
case *ast.AlterTableSwitchStatement:
490492
return alterTableSwitchStatementToJSON(s)
491493
case *ast.AlterTableConstraintModificationStatement:
@@ -3621,10 +3623,24 @@ func (p *Parser) parseCreateTableStatement() (*ast.CreateTableStatement, error)
36213623
}
36223624
stmt.SchemaObjectName = name
36233625

3624-
// Expect ( - if not present, be lenient
3626+
// Check for AS FILETABLE
3627+
if p.curTok.Type == TokenAs {
3628+
p.nextToken() // consume AS
3629+
if strings.ToUpper(p.curTok.Literal) == "FILETABLE" {
3630+
stmt.AsFileTable = true
3631+
p.nextToken()
3632+
} else if strings.ToUpper(p.curTok.Literal) == "NODE" {
3633+
stmt.AsNode = true
3634+
p.nextToken()
3635+
} else if strings.ToUpper(p.curTok.Literal) == "EDGE" {
3636+
stmt.AsEdge = true
3637+
p.nextToken()
3638+
}
3639+
}
3640+
3641+
// Check for ON, TEXTIMAGE_ON, FILESTREAM_ON, WITH clauses (for AS FILETABLE)
36253642
if p.curTok.Type != TokenLParen {
3626-
p.skipToEndOfStatement()
3627-
return stmt, nil
3643+
return p.parseCreateTableOptions(stmt)
36283644
}
36293645
p.nextToken()
36303646

@@ -3805,6 +3821,229 @@ func (p *Parser) parseCreateTableStatement() (*ast.CreateTableStatement, error)
38053821
stmt.AsEdge = true
38063822
p.nextToken()
38073823
}
3824+
} else if upperLit == "FEDERATED" {
3825+
p.nextToken() // consume FEDERATED
3826+
// Expect ON
3827+
if p.curTok.Type == TokenOn {
3828+
p.nextToken() // consume ON
3829+
}
3830+
// Expect (
3831+
if p.curTok.Type == TokenLParen {
3832+
p.nextToken() // consume (
3833+
}
3834+
// Parse distribution_name = column_name
3835+
distributionName := p.parseIdentifier()
3836+
if p.curTok.Type == TokenEquals {
3837+
p.nextToken() // consume =
3838+
}
3839+
columnName := p.parseIdentifier()
3840+
stmt.FederationScheme = &ast.FederationScheme{
3841+
DistributionName: distributionName,
3842+
ColumnName: columnName,
3843+
}
3844+
// Expect )
3845+
if p.curTok.Type == TokenRParen {
3846+
p.nextToken() // consume )
3847+
}
3848+
} else {
3849+
break
3850+
}
3851+
}
3852+
3853+
// Skip optional semicolon
3854+
if p.curTok.Type == TokenSemicolon {
3855+
p.nextToken()
3856+
}
3857+
3858+
return stmt, nil
3859+
}
3860+
3861+
// parseCreateTableOptions parses table options (ON, TEXTIMAGE_ON, FILESTREAM_ON, WITH) for tables without column definitions (like AS FILETABLE)
3862+
func (p *Parser) parseCreateTableOptions(stmt *ast.CreateTableStatement) (*ast.CreateTableStatement, error) {
3863+
for {
3864+
upperLit := strings.ToUpper(p.curTok.Literal)
3865+
if p.curTok.Type == TokenOn {
3866+
p.nextToken() // consume ON
3867+
// Parse filegroup or partition scheme with optional columns
3868+
fg, err := p.parseFileGroupOrPartitionScheme()
3869+
if err != nil {
3870+
return nil, err
3871+
}
3872+
stmt.OnFileGroupOrPartitionScheme = fg
3873+
} else if upperLit == "TEXTIMAGE_ON" {
3874+
p.nextToken() // consume TEXTIMAGE_ON
3875+
// Parse filegroup identifier or string literal
3876+
if p.curTok.Type == TokenString {
3877+
value := p.curTok.Literal
3878+
// Strip quotes from string literal
3879+
if len(value) >= 2 && value[0] == '\'' && value[len(value)-1] == '\'' {
3880+
value = value[1 : len(value)-1]
3881+
}
3882+
stmt.TextImageOn = &ast.IdentifierOrValueExpression{
3883+
Value: value,
3884+
ValueExpression: &ast.StringLiteral{
3885+
LiteralType: "String",
3886+
Value: value,
3887+
},
3888+
}
3889+
p.nextToken()
3890+
} else {
3891+
ident := p.parseIdentifier()
3892+
stmt.TextImageOn = &ast.IdentifierOrValueExpression{
3893+
Value: ident.Value,
3894+
Identifier: ident,
3895+
}
3896+
}
3897+
} else if upperLit == "FILESTREAM_ON" {
3898+
p.nextToken() // consume FILESTREAM_ON
3899+
// Parse filegroup identifier or string literal
3900+
if p.curTok.Type == TokenString {
3901+
value := p.curTok.Literal
3902+
// Strip quotes from string literal
3903+
if len(value) >= 2 && value[0] == '\'' && value[len(value)-1] == '\'' {
3904+
value = value[1 : len(value)-1]
3905+
}
3906+
stmt.FileStreamOn = &ast.IdentifierOrValueExpression{
3907+
Value: value,
3908+
ValueExpression: &ast.StringLiteral{
3909+
LiteralType: "String",
3910+
Value: value,
3911+
},
3912+
}
3913+
p.nextToken()
3914+
} else {
3915+
ident := p.parseIdentifier()
3916+
stmt.FileStreamOn = &ast.IdentifierOrValueExpression{
3917+
Value: ident.Value,
3918+
Identifier: ident,
3919+
}
3920+
}
3921+
} else if p.curTok.Type == TokenWith {
3922+
// Parse WITH clause with table options
3923+
p.nextToken() // consume WITH
3924+
if p.curTok.Type == TokenLParen {
3925+
p.nextToken() // consume (
3926+
// Parse table options
3927+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
3928+
optionName := strings.ToUpper(p.curTok.Literal)
3929+
p.nextToken() // consume option name
3930+
3931+
if optionName == "DATA_COMPRESSION" {
3932+
if p.curTok.Type == TokenEquals {
3933+
p.nextToken() // consume =
3934+
}
3935+
opt, err := p.parseDataCompressionOption()
3936+
if err != nil {
3937+
break
3938+
}
3939+
stmt.Options = append(stmt.Options, &ast.TableDataCompressionOption{
3940+
DataCompressionOption: opt,
3941+
OptionKind: "DataCompression",
3942+
})
3943+
} else if optionName == "FILETABLE_DIRECTORY" {
3944+
if p.curTok.Type == TokenEquals {
3945+
p.nextToken() // consume =
3946+
}
3947+
// Parse the directory name as a literal or NULL
3948+
opt := &ast.FileTableDirectoryTableOption{
3949+
OptionKind: "FileTableDirectory",
3950+
}
3951+
if strings.ToUpper(p.curTok.Literal) == "NULL" {
3952+
opt.Value = &ast.NullLiteral{
3953+
LiteralType: "Null",
3954+
Value: "NULL",
3955+
}
3956+
p.nextToken()
3957+
} else if p.curTok.Type == TokenString {
3958+
value := p.curTok.Literal
3959+
if len(value) >= 2 && value[0] == '\'' && value[len(value)-1] == '\'' {
3960+
value = value[1 : len(value)-1]
3961+
}
3962+
opt.Value = &ast.StringLiteral{
3963+
LiteralType: "String",
3964+
Value: value,
3965+
IsNational: false,
3966+
IsLargeObject: false,
3967+
}
3968+
p.nextToken()
3969+
} else {
3970+
value := p.curTok.Literal
3971+
opt.Value = &ast.StringLiteral{
3972+
LiteralType: "String",
3973+
Value: value,
3974+
IsNational: false,
3975+
IsLargeObject: false,
3976+
}
3977+
p.nextToken()
3978+
}
3979+
stmt.Options = append(stmt.Options, opt)
3980+
} else if optionName == "FILETABLE_COLLATE_FILENAME" {
3981+
if p.curTok.Type == TokenEquals {
3982+
p.nextToken() // consume =
3983+
}
3984+
// Parse the collation name as an identifier
3985+
collationName := p.parseIdentifier()
3986+
stmt.Options = append(stmt.Options, &ast.FileTableCollateFileNameTableOption{
3987+
OptionKind: "FileTableCollateFileName",
3988+
Value: collationName,
3989+
})
3990+
} else if optionName == "MEMORY_OPTIMIZED" {
3991+
if p.curTok.Type == TokenEquals {
3992+
p.nextToken() // consume =
3993+
}
3994+
stateUpper := strings.ToUpper(p.curTok.Literal)
3995+
state := "On"
3996+
if stateUpper == "OFF" {
3997+
state = "Off"
3998+
}
3999+
p.nextToken() // consume ON/OFF
4000+
stmt.Options = append(stmt.Options, &ast.MemoryOptimizedTableOption{
4001+
OptionKind: "MemoryOptimized",
4002+
OptionState: state,
4003+
})
4004+
} else if optionName == "FILETABLE_PRIMARY_KEY_CONSTRAINT_NAME" {
4005+
if p.curTok.Type == TokenEquals {
4006+
p.nextToken() // consume =
4007+
}
4008+
constraintName := p.parseIdentifier()
4009+
stmt.Options = append(stmt.Options, &ast.FileTableConstraintNameTableOption{
4010+
OptionKind: "FileTablePrimaryKeyConstraintName",
4011+
Value: constraintName,
4012+
})
4013+
} else if optionName == "FILETABLE_STREAMID_UNIQUE_CONSTRAINT_NAME" {
4014+
if p.curTok.Type == TokenEquals {
4015+
p.nextToken() // consume =
4016+
}
4017+
constraintName := p.parseIdentifier()
4018+
stmt.Options = append(stmt.Options, &ast.FileTableConstraintNameTableOption{
4019+
OptionKind: "FileTableStreamIdUniqueConstraintName",
4020+
Value: constraintName,
4021+
})
4022+
} else if optionName == "FILETABLE_FULLPATH_UNIQUE_CONSTRAINT_NAME" {
4023+
if p.curTok.Type == TokenEquals {
4024+
p.nextToken() // consume =
4025+
}
4026+
constraintName := p.parseIdentifier()
4027+
stmt.Options = append(stmt.Options, &ast.FileTableConstraintNameTableOption{
4028+
OptionKind: "FileTableFullPathUniqueConstraintName",
4029+
Value: constraintName,
4030+
})
4031+
} else {
4032+
// Skip unknown option value
4033+
if p.curTok.Type == TokenEquals {
4034+
p.nextToken()
4035+
}
4036+
p.nextToken()
4037+
}
4038+
4039+
if p.curTok.Type == TokenComma {
4040+
p.nextToken()
4041+
}
4042+
}
4043+
if p.curTok.Type == TokenRParen {
4044+
p.nextToken()
4045+
}
4046+
}
38084047
} else {
38094048
break
38104049
}
@@ -5794,6 +6033,22 @@ func createTableStatementToJSON(s *ast.CreateTableStatement) jsonNode {
57946033
}
57956034
node["Options"] = opts
57966035
}
6036+
if s.FederationScheme != nil {
6037+
node["FederationScheme"] = federationSchemeToJSON(s.FederationScheme)
6038+
}
6039+
return node
6040+
}
6041+
6042+
func federationSchemeToJSON(fs *ast.FederationScheme) jsonNode {
6043+
node := jsonNode{
6044+
"$type": "FederationScheme",
6045+
}
6046+
if fs.DistributionName != nil {
6047+
node["DistributionName"] = identifierToJSON(fs.DistributionName)
6048+
}
6049+
if fs.ColumnName != nil {
6050+
node["ColumnName"] = identifierToJSON(fs.ColumnName)
6051+
}
57976052
return node
57986053
}
57996054

@@ -5816,6 +6071,33 @@ func tableOptionToJSON(opt ast.TableOption) jsonNode {
58166071
"OptionKind": o.OptionKind,
58176072
"OptionState": o.OptionState,
58186073
}
6074+
case *ast.FileTableDirectoryTableOption:
6075+
node := jsonNode{
6076+
"$type": "FileTableDirectoryTableOption",
6077+
"OptionKind": o.OptionKind,
6078+
}
6079+
if o.Value != nil {
6080+
node["Value"] = scalarExpressionToJSON(o.Value)
6081+
}
6082+
return node
6083+
case *ast.FileTableCollateFileNameTableOption:
6084+
node := jsonNode{
6085+
"$type": "FileTableCollateFileNameTableOption",
6086+
"OptionKind": o.OptionKind,
6087+
}
6088+
if o.Value != nil {
6089+
node["Value"] = identifierToJSON(o.Value)
6090+
}
6091+
return node
6092+
case *ast.FileTableConstraintNameTableOption:
6093+
node := jsonNode{
6094+
"$type": "FileTableConstraintNameTableOption",
6095+
"OptionKind": o.OptionKind,
6096+
}
6097+
if o.Value != nil {
6098+
node["Value"] = identifierToJSON(o.Value)
6099+
}
6100+
return node
58196101
default:
58206102
return jsonNode{"$type": "UnknownTableOption"}
58216103
}
@@ -11922,6 +12204,17 @@ func alterTableTriggerModificationStatementToJSON(s *ast.AlterTableTriggerModifi
1192212204
return node
1192312205
}
1192412206

12207+
func alterTableFileTableNamespaceStatementToJSON(s *ast.AlterTableFileTableNamespaceStatement) jsonNode {
12208+
node := jsonNode{
12209+
"$type": "AlterTableFileTableNamespaceStatement",
12210+
"IsEnable": s.IsEnable,
12211+
}
12212+
if s.SchemaObjectName != nil {
12213+
node["SchemaObjectName"] = schemaObjectNameToJSON(s.SchemaObjectName)
12214+
}
12215+
return node
12216+
}
12217+
1192512218
func alterTableSwitchStatementToJSON(s *ast.AlterTableSwitchStatement) jsonNode {
1192612219
node := jsonNode{
1192712220
"$type": "AlterTableSwitchStatement",

0 commit comments

Comments
 (0)