Skip to content

Commit ca81ad1

Browse files
committed
Add WITH clause column schema support for BULK OPENROWSET
Add WithColumns field to BulkOpenRowset for schema specification in OPENROWSET BULK statements. Supports COLLATE clause and column ordinal or JSON path specifications.
1 parent 888f466 commit ca81ad1

4 files changed

Lines changed: 75 additions & 5 deletions

File tree

ast/bulk_insert_statement.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,12 @@ func (o *OrderBulkInsertOption) bulkInsertOption() {}
6666

6767
// BulkOpenRowset represents an OPENROWSET (BULK ...) table reference.
6868
type BulkOpenRowset struct {
69-
DataFiles []ScalarExpression `json:"DataFiles,omitempty"`
70-
Options []BulkInsertOption `json:"Options,omitempty"`
71-
Columns []*Identifier `json:"Columns,omitempty"`
72-
Alias *Identifier `json:"Alias,omitempty"`
73-
ForPath bool `json:"ForPath"`
69+
DataFiles []ScalarExpression `json:"DataFiles,omitempty"`
70+
Options []BulkInsertOption `json:"Options,omitempty"`
71+
WithColumns []*OpenRowsetColumnDefinition `json:"WithColumns,omitempty"`
72+
Columns []*Identifier `json:"Columns,omitempty"`
73+
Alias *Identifier `json:"Alias,omitempty"`
74+
ForPath bool `json:"ForPath"`
7475
}
7576

7677
func (b *BulkOpenRowset) node() {}

ast/openrowset.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ func (o *OpenRowsetTableReference) tableReference() {}
3939

4040
// OpenRowsetColumnDefinition represents a column definition in WITH clause.
4141
type OpenRowsetColumnDefinition struct {
42+
ColumnOrdinal ScalarExpression `json:"ColumnOrdinal,omitempty"`
4243
ColumnIdentifier *Identifier `json:"ColumnIdentifier,omitempty"`
4344
DataType DataTypeReference `json:"DataType,omitempty"`
45+
Collation *Identifier `json:"Collation,omitempty"`
4446
}

parser/marshal.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2389,6 +2389,13 @@ func tableReferenceToJSON(ref ast.TableReference) jsonNode {
23892389
}
23902390
node["Options"] = opts
23912391
}
2392+
if len(r.WithColumns) > 0 {
2393+
cols := make([]jsonNode, len(r.WithColumns))
2394+
for i, c := range r.WithColumns {
2395+
cols[i] = openRowsetColumnDefinitionToJSON(c)
2396+
}
2397+
node["WithColumns"] = cols
2398+
}
23922399
if len(r.Columns) > 0 {
23932400
cols := make([]jsonNode, len(r.Columns))
23942401
for i, c := range r.Columns {
@@ -17301,11 +17308,17 @@ func openRowsetColumnDefinitionToJSON(col *ast.OpenRowsetColumnDefinition) jsonN
1730117308
node := jsonNode{
1730217309
"$type": "OpenRowsetColumnDefinition",
1730317310
}
17311+
if col.ColumnOrdinal != nil {
17312+
node["ColumnOrdinal"] = scalarExpressionToJSON(col.ColumnOrdinal)
17313+
}
1730417314
if col.ColumnIdentifier != nil {
1730517315
node["ColumnIdentifier"] = identifierToJSON(col.ColumnIdentifier)
1730617316
}
1730717317
if col.DataType != nil {
1730817318
node["DataType"] = dataTypeReferenceToJSON(col.DataType)
1730917319
}
17320+
if col.Collation != nil {
17321+
node["Collation"] = identifierToJSON(col.Collation)
17322+
}
1731017323
return node
1731117324
}

parser/parse_dml.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,60 @@ func (p *Parser) parseBulkOpenRowset() (*ast.BulkOpenRowset, error) {
687687
}
688688
p.nextToken()
689689

690+
// Parse optional WITH (column_definitions) - for schema specification
691+
if p.curTok.Type == TokenWith {
692+
p.nextToken() // consume WITH
693+
if p.curTok.Type == TokenLParen {
694+
p.nextToken() // consume (
695+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
696+
colDef := &ast.OpenRowsetColumnDefinition{}
697+
colDef.ColumnIdentifier = p.parseIdentifier()
698+
699+
// Parse data type
700+
dataType, err := p.parseDataTypeReference()
701+
if err != nil {
702+
return nil, err
703+
}
704+
colDef.DataType = dataType
705+
706+
// Parse optional COLLATE
707+
if strings.ToUpper(p.curTok.Literal) == "COLLATE" {
708+
p.nextToken() // consume COLLATE
709+
colDef.Collation = p.parseIdentifier()
710+
}
711+
712+
// Parse optional column ordinal (integer) or JSON path (string)
713+
if p.curTok.Type == TokenNumber {
714+
colDef.ColumnOrdinal = &ast.IntegerLiteral{
715+
LiteralType: "Integer",
716+
Value: p.curTok.Literal,
717+
}
718+
p.nextToken()
719+
} else if p.curTok.Type == TokenString {
720+
// JSON path specification like '$.stateName' or 'strict $.population'
721+
colDef.ColumnOrdinal = &ast.StringLiteral{
722+
LiteralType: "String",
723+
IsNational: false,
724+
IsLargeObject: false,
725+
Value: strings.Trim(p.curTok.Literal, "'"),
726+
}
727+
p.nextToken()
728+
}
729+
730+
result.WithColumns = append(result.WithColumns, colDef)
731+
732+
if p.curTok.Type == TokenComma {
733+
p.nextToken()
734+
} else {
735+
break
736+
}
737+
}
738+
if p.curTok.Type == TokenRParen {
739+
p.nextToken() // consume )
740+
}
741+
}
742+
}
743+
690744
// Parse optional alias
691745
if p.curTok.Type == TokenAs {
692746
p.nextToken()

0 commit comments

Comments
 (0)