Skip to content

Commit 44ef6db

Browse files
committed
fix(parser): support quoted identifiers in DML and DDL statements
- Changed DML parsers (INSERT, UPDATE, DELETE, MERGE) to use isIdentifier() instead of isType(TokenTypeIdentifier) for table/column name parsing - Changed DDL parsers (CREATE, DROP, TRUNCATE, REFRESH) similarly - Added comprehensive tests for double-quoted identifiers in all DML/DDL statements The existing isIdentifier() method correctly handles both regular identifiers and double-quoted strings (TokenTypeDoubleQuotedString), but the DML/DDL parsers were bypassing this by checking for TokenTypeIdentifier directly. This fix ensures ANSI SQL double-quoted identifiers work consistently across all statement types, matching the existing behavior in SELECT. Fixes #200
1 parent 2463f34 commit 44ef6db

3 files changed

Lines changed: 366 additions & 44 deletions

File tree

pkg/sql/parser/ddl.go

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ func (p *Parser) parseCreateView(orReplace, temporary bool) (*ast.CreateViewStat
9898
stmt.IfNotExists = true
9999
}
100100

101-
// Parse view name
102-
if !p.isType(models.TokenTypeIdentifier) {
101+
// Parse view name (supports double-quoted identifiers for PostgreSQL compatibility)
102+
if !p.isIdentifier() {
103103
return nil, p.expectedError("view name")
104104
}
105105
stmt.Name = p.currentToken.Literal
@@ -109,7 +109,7 @@ func (p *Parser) parseCreateView(orReplace, temporary bool) (*ast.CreateViewStat
109109
if p.isType(models.TokenTypeLParen) {
110110
p.advance() // Consume (
111111
for {
112-
if !p.isType(models.TokenTypeIdentifier) {
112+
if !p.isIdentifier() {
113113
return nil, p.expectedError("column name")
114114
}
115115
stmt.Columns = append(stmt.Columns, p.currentToken.Literal)
@@ -202,8 +202,8 @@ func (p *Parser) parseCreateMaterializedView() (*ast.CreateMaterializedViewState
202202
stmt.IfNotExists = true
203203
}
204204

205-
// Parse view name
206-
if !p.isType(models.TokenTypeIdentifier) {
205+
// Parse view name (supports double-quoted identifiers for PostgreSQL compatibility)
206+
if !p.isIdentifier() {
207207
return nil, p.expectedError("materialized view name")
208208
}
209209
stmt.Name = p.currentToken.Literal
@@ -213,7 +213,7 @@ func (p *Parser) parseCreateMaterializedView() (*ast.CreateMaterializedViewState
213213
if p.isType(models.TokenTypeLParen) {
214214
p.advance() // Consume (
215215
for {
216-
if !p.isType(models.TokenTypeIdentifier) {
216+
if !p.isIdentifier() {
217217
return nil, p.expectedError("column name")
218218
}
219219
stmt.Columns = append(stmt.Columns, p.currentToken.Literal)
@@ -234,7 +234,7 @@ func (p *Parser) parseCreateMaterializedView() (*ast.CreateMaterializedViewState
234234
// Parse optional TABLESPACE
235235
if p.isTokenMatch("TABLESPACE") {
236236
p.advance() // Consume TABLESPACE
237-
if !p.isType(models.TokenTypeIdentifier) {
237+
if !p.isIdentifier() {
238238
return nil, p.expectedError("tablespace name")
239239
}
240240
stmt.Tablespace = p.currentToken.Literal
@@ -307,8 +307,8 @@ func (p *Parser) parseCreateTable(temporary bool) (*ast.CreateTableStatement, er
307307
stmt.IfNotExists = true
308308
}
309309

310-
// Parse table name
311-
if !p.isType(models.TokenTypeIdentifier) {
310+
// Parse table name (supports double-quoted identifiers for PostgreSQL compatibility)
311+
if !p.isIdentifier() {
312312
return nil, p.expectedError("table name")
313313
}
314314
stmt.Name = p.currentToken.Literal
@@ -398,7 +398,7 @@ func (p *Parser) parseCreateTable(temporary bool) (*ast.CreateTableStatement, er
398398
if p.isType(models.TokenTypeEq) {
399399
p.advance() // Consume =
400400
}
401-
if p.isType(models.TokenTypeIdentifier) || p.isType(models.TokenTypeString) {
401+
if p.isIdentifier() || p.isType(models.TokenTypeString) {
402402
opt.Value = p.currentToken.Literal
403403
p.advance()
404404
}
@@ -434,7 +434,7 @@ func (p *Parser) parsePartitionByClause() (*ast.PartitionBy, error) {
434434

435435
// Parse column list
436436
for {
437-
if !p.isType(models.TokenTypeIdentifier) {
437+
if !p.isIdentifier() {
438438
return nil, p.expectedError("column name")
439439
}
440440
partitionBy.Columns = append(partitionBy.Columns, p.currentToken.Literal)
@@ -466,8 +466,8 @@ func (p *Parser) parsePartitionDefinition() (*ast.PartitionDefinition, error) {
466466
}
467467
p.advance() // Consume PARTITION
468468

469-
// Parse partition name
470-
if !p.isType(models.TokenTypeIdentifier) {
469+
// Parse partition name (supports double-quoted identifiers)
470+
if !p.isIdentifier() {
471471
return nil, p.expectedError("partition name")
472472
}
473473
partDef.Name = p.currentToken.Literal
@@ -581,7 +581,7 @@ func (p *Parser) parsePartitionDefinition() (*ast.PartitionDefinition, error) {
581581
// Parse optional TABLESPACE
582582
if p.isTokenMatch("TABLESPACE") {
583583
p.advance() // Consume TABLESPACE
584-
if !p.isType(models.TokenTypeIdentifier) {
584+
if !p.isIdentifier() {
585585
return nil, p.expectedError("tablespace name")
586586
}
587587
partDef.Tablespace = p.currentToken.Literal
@@ -611,8 +611,8 @@ func (p *Parser) parseCreateIndex(unique bool) (*ast.CreateIndexStatement, error
611611
stmt.IfNotExists = true
612612
}
613613

614-
// Parse index name
615-
if !p.isType(models.TokenTypeIdentifier) {
614+
// Parse index name (supports double-quoted identifiers)
615+
if !p.isIdentifier() {
616616
return nil, p.expectedError("index name")
617617
}
618618
stmt.Name = p.currentToken.Literal
@@ -624,8 +624,8 @@ func (p *Parser) parseCreateIndex(unique bool) (*ast.CreateIndexStatement, error
624624
}
625625
p.advance() // Consume ON
626626

627-
// Parse table name
628-
if !p.isType(models.TokenTypeIdentifier) {
627+
// Parse table name (supports double-quoted identifiers for PostgreSQL compatibility)
628+
if !p.isIdentifier() {
629629
return nil, p.expectedError("table name")
630630
}
631631
stmt.Table = p.currentToken.Literal
@@ -634,7 +634,7 @@ func (p *Parser) parseCreateIndex(unique bool) (*ast.CreateIndexStatement, error
634634
// Parse optional USING
635635
if p.isType(models.TokenTypeUsing) {
636636
p.advance() // Consume USING
637-
if !p.isType(models.TokenTypeIdentifier) {
637+
if !p.isIdentifier() {
638638
return nil, p.expectedError("index method")
639639
}
640640
stmt.Using = p.currentToken.Literal
@@ -650,7 +650,7 @@ func (p *Parser) parseCreateIndex(unique bool) (*ast.CreateIndexStatement, error
650650
// Parse column list
651651
for {
652652
col := ast.IndexColumn{}
653-
if !p.isType(models.TokenTypeIdentifier) {
653+
if !p.isIdentifier() {
654654
return nil, p.expectedError("column name")
655655
}
656656
col.Column = p.currentToken.Literal
@@ -739,9 +739,9 @@ func (p *Parser) parseDropStatement() (*ast.DropStatement, error) {
739739
stmt.IfExists = true
740740
}
741741

742-
// Parse object names (can be comma-separated)
742+
// Parse object names (can be comma-separated, supports double-quoted identifiers)
743743
for {
744-
if !p.isType(models.TokenTypeIdentifier) {
744+
if !p.isIdentifier() {
745745
return nil, p.expectedError("object name")
746746
}
747747
stmt.Names = append(stmt.Names, p.currentToken.Literal)
@@ -788,8 +788,8 @@ func (p *Parser) parseRefreshStatement() (*ast.RefreshMaterializedViewStatement,
788788
p.advance()
789789
}
790790

791-
// Parse view name
792-
if !p.isType(models.TokenTypeIdentifier) {
791+
// Parse view name (supports double-quoted identifiers for PostgreSQL compatibility)
792+
if !p.isIdentifier() {
793793
return nil, p.expectedError("materialized view name")
794794
}
795795
stmt.Name = p.currentToken.Literal
@@ -827,9 +827,9 @@ func (p *Parser) parseTruncateStatement() (*ast.TruncateStatement, error) {
827827
p.advance() // Consume TABLE
828828
}
829829

830-
// Parse table names (can be comma-separated)
830+
// Parse table names (can be comma-separated, supports double-quoted identifiers)
831831
for {
832-
if !p.isType(models.TokenTypeIdentifier) {
832+
if !p.isIdentifier() {
833833
return nil, p.expectedError("table name")
834834
}
835835
stmt.Tables = append(stmt.Tables, p.currentToken.Literal)

pkg/sql/parser/dml.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ func (p *Parser) parseInsertStatement() (ast.Statement, error) {
2121
}
2222
p.advance() // Consume INTO
2323

24-
// Parse table name
25-
if !p.isType(models.TokenTypeIdentifier) {
24+
// Parse table name (supports double-quoted identifiers for PostgreSQL compatibility)
25+
if !p.isIdentifier() {
2626
return nil, p.expectedError("table name")
2727
}
2828
tableName := p.currentToken.Literal
@@ -34,8 +34,8 @@ func (p *Parser) parseInsertStatement() (ast.Statement, error) {
3434
p.advance() // Consume (
3535

3636
for {
37-
// Parse column name
38-
if !p.isType(models.TokenTypeIdentifier) {
37+
// Parse column name (supports double-quoted identifiers)
38+
if !p.isIdentifier() {
3939
return nil, p.expectedError("column name")
4040
}
4141
columns = append(columns, &ast.Identifier{Name: p.currentToken.Literal})
@@ -143,8 +143,8 @@ func (p *Parser) parseInsertStatement() (ast.Statement, error) {
143143
func (p *Parser) parseUpdateStatement() (ast.Statement, error) {
144144
// We've already consumed the UPDATE token in matchToken
145145

146-
// Parse table name
147-
if !p.isType(models.TokenTypeIdentifier) {
146+
// Parse table name (supports double-quoted identifiers for PostgreSQL compatibility)
147+
if !p.isIdentifier() {
148148
return nil, p.expectedError("table name")
149149
}
150150
tableName := p.currentToken.Literal
@@ -159,8 +159,8 @@ func (p *Parser) parseUpdateStatement() (ast.Statement, error) {
159159
// Parse assignments
160160
updates := make([]ast.UpdateExpression, 0)
161161
for {
162-
// Parse column name
163-
if !p.isType(models.TokenTypeIdentifier) {
162+
// Parse column name (supports double-quoted identifiers)
163+
if !p.isIdentifier() {
164164
return nil, p.expectedError("column name")
165165
}
166166
columnName := p.currentToken.Literal
@@ -250,8 +250,8 @@ func (p *Parser) parseDeleteStatement() (ast.Statement, error) {
250250
}
251251
p.advance() // Consume FROM
252252

253-
// Parse table name
254-
if !p.isType(models.TokenTypeIdentifier) {
253+
// Parse table name (supports double-quoted identifiers for PostgreSQL compatibility)
254+
if !p.isIdentifier() {
255255
return nil, p.expectedError("table name")
256256
}
257257
tableName := p.currentToken.Literal
@@ -311,7 +311,7 @@ func (p *Parser) parseMergeStatement() (ast.Statement, error) {
311311
// Parse optional target alias (AS alias or just alias)
312312
if p.isType(models.TokenTypeAs) {
313313
p.advance() // Consume AS
314-
if !p.isType(models.TokenTypeIdentifier) && !p.isNonReservedKeyword() {
314+
if !p.isIdentifier() && !p.isNonReservedKeyword() {
315315
return nil, p.expectedError("target alias after AS")
316316
}
317317
stmt.TargetAlias = p.currentToken.Literal
@@ -337,7 +337,7 @@ func (p *Parser) parseMergeStatement() (ast.Statement, error) {
337337
// Parse optional source alias
338338
if p.isType(models.TokenTypeAs) {
339339
p.advance() // Consume AS
340-
if !p.isType(models.TokenTypeIdentifier) && !p.isNonReservedKeyword() {
340+
if !p.isIdentifier() && !p.isNonReservedKeyword() {
341341
return nil, p.expectedError("source alias after AS")
342342
}
343343
stmt.SourceAlias = p.currentToken.Literal
@@ -449,7 +449,7 @@ func (p *Parser) parseMergeAction(clauseType string) (*ast.MergeAction, error) {
449449

450450
// Parse SET clauses
451451
for {
452-
if !p.isType(models.TokenTypeIdentifier) && !p.canBeAlias() {
452+
if !p.isIdentifier() && !p.canBeAlias() {
453453
return nil, p.expectedError("column name")
454454
}
455455
// Handle qualified column names (e.g., t.name)
@@ -459,7 +459,7 @@ func (p *Parser) parseMergeAction(clauseType string) (*ast.MergeAction, error) {
459459
// Check for qualified name (table.column)
460460
if p.isType(models.TokenTypePeriod) {
461461
p.advance() // Consume .
462-
if !p.isType(models.TokenTypeIdentifier) && !p.canBeAlias() {
462+
if !p.isIdentifier() && !p.canBeAlias() {
463463
return nil, p.expectedError("column name after .")
464464
}
465465
columnName = columnName + "." + p.currentToken.Literal
@@ -496,7 +496,7 @@ func (p *Parser) parseMergeAction(clauseType string) (*ast.MergeAction, error) {
496496
if p.isType(models.TokenTypeLParen) {
497497
p.advance() // Consume (
498498
for {
499-
if !p.isType(models.TokenTypeIdentifier) {
499+
if !p.isIdentifier() {
500500
return nil, p.expectedError("column name")
501501
}
502502
action.Columns = append(action.Columns, p.currentToken.Literal)
@@ -601,7 +601,7 @@ func (p *Parser) parseOnConflictClause() (*ast.OnConflict, error) {
601601
var targets []ast.Expression
602602

603603
for {
604-
if !p.isType(models.TokenTypeIdentifier) {
604+
if !p.isIdentifier() {
605605
return nil, p.expectedError("column name in ON CONFLICT target")
606606
}
607607
targets = append(targets, &ast.Identifier{Name: p.currentToken.Literal})
@@ -622,7 +622,7 @@ func (p *Parser) parseOnConflictClause() (*ast.OnConflict, error) {
622622
// ON CONSTRAINT constraint_name
623623
p.advance() // Consume ON
624624
p.advance() // Consume CONSTRAINT
625-
if !p.isType(models.TokenTypeIdentifier) {
625+
if !p.isIdentifier() {
626626
return nil, p.expectedError("constraint name")
627627
}
628628
onConflict.Constraint = p.currentToken.Literal
@@ -651,7 +651,7 @@ func (p *Parser) parseOnConflictClause() (*ast.OnConflict, error) {
651651
// Parse update assignments
652652
var updates []ast.UpdateExpression
653653
for {
654-
if !p.isType(models.TokenTypeIdentifier) {
654+
if !p.isIdentifier() {
655655
return nil, p.expectedError("column name")
656656
}
657657
columnName := p.currentToken.Literal

0 commit comments

Comments
 (0)