Skip to content

Commit ba6f706

Browse files
committed
Add CREATE TYPE AS TABLE support and computed column parsing
- Add CreateTypeTableStatement for CREATE TYPE ... AS TABLE statements - Add ComputedColumnExpression field to ColumnDefinition - Parse READONLY modifier in CREATE FUNCTION parameters - Use parseDataTypeReference for user-defined types in CREATE FUNCTION - Parse column list for UNIQUE table constraints - Only output Clustered field when explicitly specified in constraints Enables tests: Baselines100_TableParametersTests, TableParametersTests
1 parent 98edb19 commit ba6f706

7 files changed

Lines changed: 139 additions & 19 deletions

File tree

ast/create_simple_statements.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,15 @@ type CreateTypeUdtStatement struct {
277277
func (s *CreateTypeUdtStatement) node() {}
278278
func (s *CreateTypeUdtStatement) statement() {}
279279

280+
// CreateTypeTableStatement represents a CREATE TYPE ... AS TABLE statement (table type).
281+
type CreateTypeTableStatement struct {
282+
Name *SchemaObjectName `json:"Name,omitempty"`
283+
Definition *TableDefinition `json:"Definition,omitempty"`
284+
}
285+
286+
func (s *CreateTypeTableStatement) node() {}
287+
func (s *CreateTypeTableStatement) statement() {}
288+
280289
// CreateXmlIndexStatement represents a CREATE XML INDEX statement.
281290
type CreateXmlIndexStatement struct {
282291
Name *Identifier `json:"Name,omitempty"`

ast/create_table_statement.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,18 @@ func (t *TableDefinition) node() {}
2525

2626
// ColumnDefinition represents a column definition in CREATE TABLE
2727
type ColumnDefinition struct {
28-
ColumnIdentifier *Identifier
29-
DataType DataTypeReference
30-
Collation *Identifier
31-
DefaultConstraint *DefaultConstraintDefinition
32-
IdentityOptions *IdentityOptions
33-
Constraints []ConstraintDefinition
34-
IsPersisted bool
35-
IsRowGuidCol bool
36-
IsHidden bool
37-
IsMasked bool
38-
Nullable *NullableConstraintDefinition
28+
ColumnIdentifier *Identifier
29+
DataType DataTypeReference
30+
ComputedColumnExpression ScalarExpression
31+
Collation *Identifier
32+
DefaultConstraint *DefaultConstraintDefinition
33+
IdentityOptions *IdentityOptions
34+
Constraints []ConstraintDefinition
35+
IsPersisted bool
36+
IsRowGuidCol bool
37+
IsHidden bool
38+
IsMasked bool
39+
Nullable *NullableConstraintDefinition
3940
}
4041

4142
func (c *ColumnDefinition) node() {}

parser/marshal.go

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,8 @@ func statementToJSON(stmt ast.Statement) jsonNode {
406406
return createTypeUddtStatementToJSON(s)
407407
case *ast.CreateTypeUdtStatement:
408408
return createTypeUdtStatementToJSON(s)
409+
case *ast.CreateTypeTableStatement:
410+
return createTypeTableStatementToJSON(s)
409411
case *ast.CreateXmlIndexStatement:
410412
return createXmlIndexStatementToJSON(s)
411413
case *ast.CreatePartitionFunctionStatement:
@@ -2816,6 +2818,23 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) {
28162818
// Parse column name (parseIdentifier already calls nextToken)
28172819
col.ColumnIdentifier = p.parseIdentifier()
28182820

2821+
// Check for computed column (AS expression)
2822+
if strings.ToUpper(p.curTok.Literal) == "AS" {
2823+
p.nextToken() // consume AS
2824+
// Parse computed column expression
2825+
expr, err := p.parseScalarExpression()
2826+
if err != nil {
2827+
return nil, err
2828+
}
2829+
col.ComputedColumnExpression = expr
2830+
// Check for PERSISTED
2831+
if strings.ToUpper(p.curTok.Literal) == "PERSISTED" {
2832+
col.IsPersisted = true
2833+
p.nextToken() // consume PERSISTED
2834+
}
2835+
return col, nil
2836+
}
2837+
28192838
// Parse data type - be lenient if no data type is provided
28202839
dataType, err := p.parseDataTypeReference()
28212840
if err != nil {
@@ -3990,6 +4009,9 @@ func columnDefinitionToJSON(c *ast.ColumnDefinition) jsonNode {
39904009
"IsMasked": c.IsMasked,
39914010
"ColumnIdentifier": identifierToJSON(c.ColumnIdentifier),
39924011
}
4012+
if c.ComputedColumnExpression != nil {
4013+
node["ComputedColumnExpression"] = scalarExpressionToJSON(c.ComputedColumnExpression)
4014+
}
39934015
if c.IdentityOptions != nil {
39944016
node["IdentityOptions"] = identityOptionsToJSON(c.IdentityOptions)
39954017
}
@@ -4056,9 +4078,12 @@ func constraintDefinitionToJSON(c ast.ConstraintDefinition) jsonNode {
40564078
func uniqueConstraintToJSON(c *ast.UniqueConstraintDefinition) jsonNode {
40574079
node := jsonNode{
40584080
"$type": "UniqueConstraintDefinition",
4059-
"Clustered": c.Clustered,
40604081
"IsPrimaryKey": c.IsPrimaryKey,
40614082
}
4083+
// Output Clustered if it's true, or if IndexType is set (meaning NONCLUSTERED was explicitly specified)
4084+
if c.Clustered || c.IndexType != nil {
4085+
node["Clustered"] = c.Clustered
4086+
}
40624087
if c.ConstraintIdentifier != nil {
40634088
node["ConstraintIdentifier"] = identifierToJSON(c.ConstraintIdentifier)
40644089
}
@@ -6566,13 +6591,19 @@ func (p *Parser) parseCreateFunctionStatement() (*ast.CreateFunctionStatement, e
65666591

65676592
// Parse data type if present
65686593
if p.curTok.Type != TokenRParen && p.curTok.Type != TokenComma {
6569-
dataType, err := p.parseDataType()
6594+
dataType, err := p.parseDataTypeReference()
65706595
if err != nil {
65716596
return nil, err
65726597
}
65736598
param.DataType = dataType
65746599
}
65756600

6601+
// Check for READONLY modifier
6602+
if strings.ToUpper(p.curTok.Literal) == "READONLY" {
6603+
param.Modifier = "ReadOnly"
6604+
p.nextToken()
6605+
}
6606+
65766607
stmt.Parameters = append(stmt.Parameters, param)
65776608

65786609
if p.curTok.Type == TokenComma {
@@ -6595,7 +6626,7 @@ func (p *Parser) parseCreateFunctionStatement() (*ast.CreateFunctionStatement, e
65956626
p.nextToken()
65966627

65976628
// Parse return type
6598-
returnDataType, err := p.parseDataType()
6629+
returnDataType, err := p.parseDataTypeReference()
65996630
if err != nil {
66006631
p.skipToEndOfStatement()
66016632
return stmt, nil
@@ -9347,6 +9378,19 @@ func createTypeUdtStatementToJSON(s *ast.CreateTypeUdtStatement) jsonNode {
93479378
return node
93489379
}
93499380

9381+
func createTypeTableStatementToJSON(s *ast.CreateTypeTableStatement) jsonNode {
9382+
node := jsonNode{
9383+
"$type": "CreateTypeTableStatement",
9384+
}
9385+
if s.Definition != nil {
9386+
node["Definition"] = tableDefinitionToJSON(s.Definition)
9387+
}
9388+
if s.Name != nil {
9389+
node["Name"] = schemaObjectNameToJSON(s.Name)
9390+
}
9391+
return node
9392+
}
9393+
93509394
func createXmlIndexStatementToJSON(s *ast.CreateXmlIndexStatement) jsonNode {
93519395
node := jsonNode{
93529396
"$type": "CreateXmlIndexStatement",

parser/parse_statements.go

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,9 +268,42 @@ func (p *Parser) parseTableConstraint() (ast.TableConstraint, error) {
268268
constraint.IndexType = &ast.IndexType{IndexTypeKind: "NonClustered"}
269269
p.nextToken()
270270
}
271-
// Skip the column list
271+
// Parse the column list
272272
if p.curTok.Type == TokenLParen {
273-
p.skipParenthesizedContent()
273+
p.nextToken() // consume (
274+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
275+
colRef := &ast.ColumnReferenceExpression{
276+
ColumnType: "Regular",
277+
}
278+
// Parse column name
279+
colName := p.parseIdentifier()
280+
colRef.MultiPartIdentifier = &ast.MultiPartIdentifier{
281+
Identifiers: []*ast.Identifier{colName},
282+
Count: 1,
283+
}
284+
// Check for sort order
285+
sortOrder := ast.SortOrderNotSpecified
286+
upperColNext := strings.ToUpper(p.curTok.Literal)
287+
if upperColNext == "ASC" {
288+
sortOrder = ast.SortOrderAscending
289+
p.nextToken()
290+
} else if upperColNext == "DESC" {
291+
sortOrder = ast.SortOrderDescending
292+
p.nextToken()
293+
}
294+
constraint.Columns = append(constraint.Columns, &ast.ColumnWithSortOrder{
295+
Column: colRef,
296+
SortOrder: sortOrder,
297+
})
298+
if p.curTok.Type == TokenComma {
299+
p.nextToken()
300+
} else {
301+
break
302+
}
303+
}
304+
if p.curTok.Type == TokenRParen {
305+
p.nextToken()
306+
}
274307
}
275308
return constraint, nil
276309
} else if upperLit == "FOREIGN" {
@@ -6811,6 +6844,39 @@ func (p *Parser) parseCreateTypeStatement() (ast.Statement, error) {
68116844
p.nextToken()
68126845
}
68136846
return stmt, nil
6847+
case "AS":
6848+
// Check if this is AS TABLE
6849+
p.nextToken() // consume AS
6850+
if strings.ToUpper(p.curTok.Literal) == "TABLE" {
6851+
p.nextToken() // consume TABLE
6852+
// Parse the table definition
6853+
if p.curTok.Type == TokenLParen {
6854+
p.nextToken() // consume (
6855+
tableDef, err := p.parseTableDefinitionBody()
6856+
if err != nil {
6857+
stmt := &ast.CreateTypeStatement{
6858+
Name: name,
6859+
}
6860+
p.skipToEndOfStatement()
6861+
return stmt, nil
6862+
}
6863+
stmt := &ast.CreateTypeTableStatement{
6864+
Name: name,
6865+
Definition: tableDef,
6866+
}
6867+
// Skip closing paren if present
6868+
if p.curTok.Type == TokenRParen {
6869+
p.nextToken()
6870+
}
6871+
// Skip semicolon if present
6872+
if p.curTok.Type == TokenSemicolon {
6873+
p.nextToken()
6874+
}
6875+
return stmt, nil
6876+
}
6877+
}
6878+
// Fall through to generic type
6879+
fallthrough
68146880
default:
68156881
// Generic CREATE TYPE statement
68166882
stmt := &ast.CreateTypeStatement{
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+
{"invalid_syntax": true}
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)