Skip to content

Commit 1cd8269

Browse files
kyleconroyclaude
andcommitted
Add column definition enhancements for IDENTITY/constraints
- Handle signed numbers (+/-) and decimal literals in IDENTITY clause - Fix lexer to parse trailing decimal numbers like "1." - Skip data type parsing when current token is a constraint keyword (handles columns without explicit type like "timestamp NOT NULL") - Add column list parsing for column-level PRIMARY KEY constraints - Add ConstraintIdentifier to DEFAULT and CHECK constraints - Fix numeric literal types (IntegerLiteral vs NumericLiteral) Enables: Baselines90_ColumnDefinitionTests, Baselines80_ColumnDefinitionTests, ColumnDefinitionTests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent dd5a7b8 commit 1cd8269

5 files changed

Lines changed: 78 additions & 23 deletions

File tree

parser/lexer.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -803,11 +803,18 @@ func (l *Lexer) readNumber() Token {
803803
for isDigit(l.ch) {
804804
l.readChar()
805805
}
806-
// Handle decimal point
807-
if l.ch == '.' && isDigit(l.peekChar()) {
808-
l.readChar()
809-
for isDigit(l.ch) {
810-
l.readChar()
806+
// Handle decimal point (including trailing decimal like "1.")
807+
if l.ch == '.' {
808+
// Peek ahead to see if this looks like a decimal number
809+
// Allow: 1.5, 1., .5 patterns
810+
nextCh := l.peekChar()
811+
// Only consume the dot if it's followed by a digit, whitespace, comma, or paren
812+
// (i.e., not followed by an identifier character that would make it a qualified name like "1.a")
813+
if isDigit(nextCh) || nextCh == ',' || nextCh == ')' || nextCh == ' ' || nextCh == '\t' || nextCh == '\r' || nextCh == '\n' || nextCh == 0 {
814+
l.readChar() // consume .
815+
for isDigit(l.ch) {
816+
l.readChar()
817+
}
811818
}
812819
}
813820
return Token{

parser/marshal.go

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6122,12 +6122,22 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) {
61226122
// Fall through to parse constraints (NOT NULL, CHECK, FOREIGN KEY, etc.)
61236123
} else {
61246124
// Parse data type - be lenient if no data type is provided
6125-
dataType, err := p.parseDataTypeReference()
6126-
if err != nil {
6127-
// Lenient: return column definition without data type
6128-
return col, nil
6129-
}
6130-
col.DataType = dataType
6125+
// First check if this looks like a constraint keyword (column without explicit type)
6126+
upperLit := strings.ToUpper(p.curTok.Literal)
6127+
isConstraintKeyword := p.curTok.Type == TokenNot || p.curTok.Type == TokenNull ||
6128+
upperLit == "UNIQUE" || upperLit == "PRIMARY" || upperLit == "CHECK" ||
6129+
upperLit == "DEFAULT" || upperLit == "CONSTRAINT" || upperLit == "IDENTITY" ||
6130+
upperLit == "REFERENCES" || upperLit == "FOREIGN" || upperLit == "ROWGUIDCOL" ||
6131+
p.curTok.Type == TokenComma || p.curTok.Type == TokenRParen
6132+
6133+
if !isConstraintKeyword {
6134+
dataType, err := p.parseDataTypeReference()
6135+
if err != nil {
6136+
// Lenient: return column definition without data type
6137+
return col, nil
6138+
}
6139+
col.DataType = dataType
6140+
}
61316141

61326142
// Parse optional IDENTITY specification
61336143
if p.curTok.Type == TokenIdent && strings.ToUpper(p.curTok.Literal) == "IDENTITY" {
@@ -6138,20 +6148,20 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) {
61386148
if p.curTok.Type == TokenLParen {
61396149
p.nextToken() // consume (
61406150

6141-
// Parse seed
6142-
if p.curTok.Type == TokenNumber {
6143-
identityOpts.IdentitySeed = &ast.IntegerLiteral{LiteralType: "Integer", Value: p.curTok.Literal}
6144-
p.nextToken()
6151+
// Parse seed - use parseScalarExpression to handle +/- signs and various literals
6152+
seed, err := p.parseScalarExpression()
6153+
if err == nil {
6154+
identityOpts.IdentitySeed = seed
61456155
}
61466156

61476157
// Expect comma
61486158
if p.curTok.Type == TokenComma {
61496159
p.nextToken() // consume ,
61506160

61516161
// Parse increment
6152-
if p.curTok.Type == TokenNumber {
6153-
identityOpts.IdentityIncrement = &ast.IntegerLiteral{LiteralType: "Integer", Value: p.curTok.Literal}
6154-
p.nextToken()
6162+
increment, err := p.parseScalarExpression()
6163+
if err == nil {
6164+
identityOpts.IdentityIncrement = increment
61556165
}
61566166
}
61576167

@@ -6256,6 +6266,39 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) {
62566266
constraint.IndexType = &ast.IndexType{IndexTypeKind: "NonClustered"}
62576267
}
62586268
}
6269+
// Parse optional column list (column ASC, column DESC, ...)
6270+
if p.curTok.Type == TokenLParen {
6271+
p.nextToken() // consume (
6272+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
6273+
colRef := &ast.ColumnReferenceExpression{
6274+
ColumnType: "Regular",
6275+
MultiPartIdentifier: &ast.MultiPartIdentifier{
6276+
Identifiers: []*ast.Identifier{p.parseIdentifier()},
6277+
Count: 1,
6278+
},
6279+
}
6280+
sortOrder := ast.SortOrderNotSpecified
6281+
if strings.ToUpper(p.curTok.Literal) == "ASC" {
6282+
sortOrder = ast.SortOrderAscending
6283+
p.nextToken()
6284+
} else if strings.ToUpper(p.curTok.Literal) == "DESC" {
6285+
sortOrder = ast.SortOrderDescending
6286+
p.nextToken()
6287+
}
6288+
constraint.Columns = append(constraint.Columns, &ast.ColumnWithSortOrder{
6289+
Column: colRef,
6290+
SortOrder: sortOrder,
6291+
})
6292+
if p.curTok.Type == TokenComma {
6293+
p.nextToken()
6294+
} else {
6295+
break
6296+
}
6297+
}
6298+
if p.curTok.Type == TokenRParen {
6299+
p.nextToken() // consume )
6300+
}
6301+
}
62596302
// Parse WITH (index_options)
62606303
if strings.ToUpper(p.curTok.Literal) == "WITH" {
62616304
p.nextToken() // consume WITH
@@ -6270,7 +6313,10 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) {
62706313
col.Constraints = append(col.Constraints, constraint)
62716314
} else if p.curTok.Type == TokenDefault {
62726315
p.nextToken() // consume DEFAULT
6273-
defaultConstraint := &ast.DefaultConstraintDefinition{}
6316+
defaultConstraint := &ast.DefaultConstraintDefinition{
6317+
ConstraintIdentifier: constraintName,
6318+
}
6319+
constraintName = nil // clear for next constraint
62746320

62756321
// Parse the default expression
62766322
expr, err := p.parseScalarExpression()
@@ -6299,8 +6345,10 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) {
62996345
p.nextToken() // consume )
63006346
}
63016347
col.Constraints = append(col.Constraints, &ast.CheckConstraintDefinition{
6302-
CheckCondition: cond,
6348+
CheckCondition: cond,
6349+
ConstraintIdentifier: constraintName,
63036350
})
6351+
constraintName = nil // clear for next constraint
63046352
}
63056353
} else if upperLit == "FOREIGN" {
63066354
// Parse FOREIGN KEY constraint for column
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)