Skip to content

Commit 9da649c

Browse files
kyleconroyclaude
andcommitted
Add rowset table reference parsing support
- Add OPENXML table reference parsing with WITH clause schema declarations - Add OPENQUERY table reference parsing for linked server queries - Add OPENDATASOURCE/AdHoc table reference parsing - Update OPENROWSET to handle semicolon-separated syntax (DataSource;UserId;Password) - Add Alias support to FullTextTableReference marshaling - Add Mapping field to SchemaDeclarationItem for XPath mappings Enables: BaselinesCommon_RowsetsInSelectTests, RowsetsInSelectTests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 87dd9a1 commit 9da649c

12 files changed

Lines changed: 495 additions & 18 deletions

File tree

ast/adhoc_table_reference.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package ast
2+
3+
// AdHocTableReference represents a table accessed via OPENDATASOURCE
4+
// Syntax: OPENDATASOURCE('provider', 'connstr').'object'
5+
// Uses AdHocDataSource from execute_statement.go
6+
type AdHocTableReference struct {
7+
DataSource *AdHocDataSource `json:"DataSource,omitempty"`
8+
Object *SchemaObjectNameOrValueExpression `json:"Object,omitempty"`
9+
Alias *Identifier `json:"Alias,omitempty"`
10+
ForPath bool `json:"ForPath"`
11+
}
12+
13+
func (*AdHocTableReference) node() {}
14+
func (*AdHocTableReference) tableReference() {}
15+
16+
// SchemaObjectNameOrValueExpression represents either a schema object name or a value expression
17+
type SchemaObjectNameOrValueExpression struct {
18+
SchemaObjectName *SchemaObjectName `json:"SchemaObjectName,omitempty"`
19+
ValueExpression ScalarExpression `json:"ValueExpression,omitempty"`
20+
}

ast/openquery_table_reference.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package ast
2+
3+
// OpenQueryTableReference represents OPENQUERY(linked_server, 'query') table reference
4+
type OpenQueryTableReference struct {
5+
LinkedServer *Identifier `json:"LinkedServer,omitempty"`
6+
Query ScalarExpression `json:"Query,omitempty"`
7+
Alias *Identifier `json:"Alias,omitempty"`
8+
ForPath bool `json:"ForPath"`
9+
}
10+
11+
func (*OpenQueryTableReference) node() {}
12+
func (*OpenQueryTableReference) tableReference() {}

ast/openrowset.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,16 @@ type LiteralOpenRowsetCosmosOption struct {
2424

2525
func (l *LiteralOpenRowsetCosmosOption) openRowsetCosmosOption() {}
2626

27-
// OpenRowsetTableReference represents a traditional OPENROWSET('provider', 'connstr', object) syntax.
27+
// OpenRowsetTableReference represents OPENROWSET with various syntaxes:
28+
// - OPENROWSET('provider', 'connstr', object)
29+
// - OPENROWSET('provider', 'server'; 'user'; 'password', 'query')
2830
type OpenRowsetTableReference struct {
2931
ProviderName ScalarExpression `json:"ProviderName,omitempty"`
3032
ProviderString ScalarExpression `json:"ProviderString,omitempty"`
33+
DataSource ScalarExpression `json:"DataSource,omitempty"`
34+
UserId ScalarExpression `json:"UserId,omitempty"`
35+
Password ScalarExpression `json:"Password,omitempty"`
36+
Query ScalarExpression `json:"Query,omitempty"`
3137
Object *SchemaObjectName `json:"Object,omitempty"`
3238
WithColumns []*OpenRowsetColumnDefinition `json:"WithColumns,omitempty"`
3339
Alias *Identifier `json:"Alias,omitempty"`

ast/openxml_table_reference.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package ast
2+
3+
// OpenXmlTableReference represents an OPENXML table-valued function
4+
// Syntax: OPENXML(variable, rowpattern [, flags]) [WITH (schema) | WITH table_name | AS alias]
5+
type OpenXmlTableReference struct {
6+
Variable ScalarExpression `json:"Variable,omitempty"`
7+
RowPattern ScalarExpression `json:"RowPattern,omitempty"`
8+
Flags ScalarExpression `json:"Flags,omitempty"`
9+
SchemaDeclarationItems []*SchemaDeclarationItem `json:"SchemaDeclarationItems,omitempty"`
10+
TableName *SchemaObjectName `json:"TableName,omitempty"`
11+
Alias *Identifier `json:"Alias,omitempty"`
12+
ForPath bool `json:"ForPath"`
13+
}
14+
15+
func (*OpenXmlTableReference) node() {}
16+
func (*OpenXmlTableReference) tableReference() {}

ast/predict_table_reference.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ type PredictTableReference struct {
1313
func (*PredictTableReference) node() {}
1414
func (*PredictTableReference) tableReference() {}
1515

16-
// SchemaDeclarationItem represents a column definition in PREDICT WITH clause
16+
// SchemaDeclarationItem represents a column definition in PREDICT/OPENXML WITH clause
1717
type SchemaDeclarationItem struct {
1818
ColumnDefinition *ColumnDefinitionBase `json:"ColumnDefinition,omitempty"`
19+
Mapping ScalarExpression `json:"Mapping,omitempty"` // Optional XPath mapping for OPENXML
1920
}
2021

2122
func (*SchemaDeclarationItem) node() {}

parser/marshal.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2943,6 +2943,18 @@ func tableReferenceToJSON(ref ast.TableReference) jsonNode {
29432943
if r.ProviderString != nil {
29442944
node["ProviderString"] = scalarExpressionToJSON(r.ProviderString)
29452945
}
2946+
if r.DataSource != nil {
2947+
node["DataSource"] = scalarExpressionToJSON(r.DataSource)
2948+
}
2949+
if r.UserId != nil {
2950+
node["UserId"] = scalarExpressionToJSON(r.UserId)
2951+
}
2952+
if r.Password != nil {
2953+
node["Password"] = scalarExpressionToJSON(r.Password)
2954+
}
2955+
if r.Query != nil {
2956+
node["Query"] = scalarExpressionToJSON(r.Query)
2957+
}
29462958
if r.Object != nil {
29472959
node["Object"] = schemaObjectNameToJSON(r.Object)
29482960
}
@@ -2958,6 +2970,73 @@ func tableReferenceToJSON(ref ast.TableReference) jsonNode {
29582970
}
29592971
node["ForPath"] = r.ForPath
29602972
return node
2973+
case *ast.AdHocTableReference:
2974+
node := jsonNode{
2975+
"$type": "AdHocTableReference",
2976+
}
2977+
if r.DataSource != nil {
2978+
node["DataSource"] = adHocDataSourceToJSON(r.DataSource)
2979+
}
2980+
if r.Object != nil {
2981+
objNode := jsonNode{
2982+
"$type": "SchemaObjectNameOrValueExpression",
2983+
}
2984+
if r.Object.SchemaObjectName != nil {
2985+
objNode["SchemaObjectName"] = schemaObjectNameToJSON(r.Object.SchemaObjectName)
2986+
}
2987+
if r.Object.ValueExpression != nil {
2988+
objNode["ValueExpression"] = scalarExpressionToJSON(r.Object.ValueExpression)
2989+
}
2990+
node["Object"] = objNode
2991+
}
2992+
if r.Alias != nil {
2993+
node["Alias"] = identifierToJSON(r.Alias)
2994+
}
2995+
node["ForPath"] = r.ForPath
2996+
return node
2997+
case *ast.OpenXmlTableReference:
2998+
node := jsonNode{
2999+
"$type": "OpenXmlTableReference",
3000+
}
3001+
if r.Variable != nil {
3002+
node["Variable"] = scalarExpressionToJSON(r.Variable)
3003+
}
3004+
if r.RowPattern != nil {
3005+
node["RowPattern"] = scalarExpressionToJSON(r.RowPattern)
3006+
}
3007+
if r.Flags != nil {
3008+
node["Flags"] = scalarExpressionToJSON(r.Flags)
3009+
}
3010+
if len(r.SchemaDeclarationItems) > 0 {
3011+
items := make([]jsonNode, len(r.SchemaDeclarationItems))
3012+
for i, item := range r.SchemaDeclarationItems {
3013+
items[i] = schemaDeclarationItemToJSON(item)
3014+
}
3015+
node["SchemaDeclarationItems"] = items
3016+
}
3017+
if r.TableName != nil {
3018+
node["TableName"] = schemaObjectNameToJSON(r.TableName)
3019+
}
3020+
if r.Alias != nil {
3021+
node["Alias"] = identifierToJSON(r.Alias)
3022+
}
3023+
node["ForPath"] = r.ForPath
3024+
return node
3025+
case *ast.OpenQueryTableReference:
3026+
node := jsonNode{
3027+
"$type": "OpenQueryTableReference",
3028+
}
3029+
if r.LinkedServer != nil {
3030+
node["LinkedServer"] = identifierToJSON(r.LinkedServer)
3031+
}
3032+
if r.Query != nil {
3033+
node["Query"] = scalarExpressionToJSON(r.Query)
3034+
}
3035+
if r.Alias != nil {
3036+
node["Alias"] = identifierToJSON(r.Alias)
3037+
}
3038+
node["ForPath"] = r.ForPath
3039+
return node
29613040
case *ast.PredictTableReference:
29623041
node := jsonNode{
29633042
"$type": "PredictTableReference",
@@ -3091,6 +3170,9 @@ func tableReferenceToJSON(ref ast.TableReference) jsonNode {
30913170
if r.PropertyName != nil {
30923171
node["PropertyName"] = scalarExpressionToJSON(r.PropertyName)
30933172
}
3173+
if r.Alias != nil {
3174+
node["Alias"] = identifierToJSON(r.Alias)
3175+
}
30943176
node["ForPath"] = r.ForPath
30953177
return node
30963178
case *ast.SemanticTableReference:
@@ -3136,6 +3218,9 @@ func schemaDeclarationItemToJSON(item *ast.SchemaDeclarationItem) jsonNode {
31363218
if item.ColumnDefinition != nil {
31373219
node["ColumnDefinition"] = columnDefinitionBaseToJSON(item.ColumnDefinition)
31383220
}
3221+
if item.Mapping != nil {
3222+
node["Mapping"] = scalarExpressionToJSON(item.Mapping)
3223+
}
31393224
return node
31403225
}
31413226

parser/parse_dml.go

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -642,24 +642,72 @@ func (p *Parser) parseOpenRowsetTableReference() (*ast.OpenRowsetTableReference,
642642
}
643643
p.nextToken() // consume ,
644644

645-
// Parse provider string (string literal)
646-
providerString, err := p.parseScalarExpression()
645+
// Parse the second argument - could be:
646+
// - ProviderString (connection string) followed by comma and object
647+
// - DataSource followed by semicolons for UserId and Password, then comma and Query
648+
secondArg, err := p.parseScalarExpression()
647649
if err != nil {
648650
return nil, err
649651
}
650-
result.ProviderString = providerString
651652

652-
if p.curTok.Type != TokenComma {
653-
return nil, fmt.Errorf("expected , after provider string, got %s", p.curTok.Literal)
654-
}
655-
p.nextToken() // consume ,
653+
// Check if next token is semicolon (DataSource; UserId; Password format)
654+
if p.curTok.Type == TokenSemicolon {
655+
result.DataSource = secondArg
656+
p.nextToken() // consume ;
656657

657-
// Parse object (schema object name or expression)
658-
obj, err := p.parseSchemaObjectName()
659-
if err != nil {
660-
return nil, err
658+
// Parse UserId
659+
userId, err := p.parseScalarExpression()
660+
if err != nil {
661+
return nil, err
662+
}
663+
result.UserId = userId
664+
665+
if p.curTok.Type != TokenSemicolon {
666+
return nil, fmt.Errorf("expected ; after UserId, got %s", p.curTok.Literal)
667+
}
668+
p.nextToken() // consume ;
669+
670+
// Parse Password
671+
password, err := p.parseScalarExpression()
672+
if err != nil {
673+
return nil, err
674+
}
675+
result.Password = password
676+
677+
if p.curTok.Type != TokenComma {
678+
return nil, fmt.Errorf("expected , after Password, got %s", p.curTok.Literal)
679+
}
680+
p.nextToken() // consume ,
681+
682+
// Parse Query
683+
query, err := p.parseScalarExpression()
684+
if err != nil {
685+
return nil, err
686+
}
687+
result.Query = query
688+
} else if p.curTok.Type == TokenComma {
689+
// ProviderString, object format
690+
result.ProviderString = secondArg
691+
p.nextToken() // consume ,
692+
693+
// Parse object (schema object name or string expression)
694+
if p.curTok.Type == TokenString {
695+
// Could be a query string instead of object name
696+
query, err := p.parseScalarExpression()
697+
if err != nil {
698+
return nil, err
699+
}
700+
result.Query = query
701+
} else {
702+
obj, err := p.parseSchemaObjectName()
703+
if err != nil {
704+
return nil, err
705+
}
706+
result.Object = obj
707+
}
708+
} else {
709+
return nil, fmt.Errorf("expected , or ; after second argument, got %s", p.curTok.Literal)
661710
}
662-
result.Object = obj
663711

664712
if p.curTok.Type != TokenRParen {
665713
return nil, fmt.Errorf("expected ) in OPENROWSET, got %s", p.curTok.Literal)

0 commit comments

Comments
 (0)