Skip to content

Commit 38fd44b

Browse files
committed
Add OpenRowset Cosmos and TableReference support
Add parsing for OPENROWSET with named parameters (PROVIDER, CONNECTION, OBJECT, CREDENTIAL) for Cosmos DB integration, and traditional OPENROWSET syntax with positional arguments ('provider', 'connstr', tablename). New AST types: OpenRowsetCosmos, OpenRowsetTableReference, LiteralOpenRowsetCosmosOption, OpenRowsetColumnDefinition
1 parent c0eab45 commit 38fd44b

5 files changed

Lines changed: 319 additions & 2 deletions

File tree

ast/openrowset.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package ast
2+
3+
// OpenRowsetCosmos represents an OPENROWSET with PROVIDER = ..., CONNECTION = ..., OBJECT = ... syntax.
4+
type OpenRowsetCosmos struct {
5+
Options []OpenRowsetCosmosOption `json:"Options,omitempty"`
6+
WithColumns []*OpenRowsetColumnDefinition `json:"WithColumns,omitempty"`
7+
Alias *Identifier `json:"Alias,omitempty"`
8+
ForPath bool `json:"ForPath"`
9+
}
10+
11+
func (o *OpenRowsetCosmos) node() {}
12+
func (o *OpenRowsetCosmos) tableReference() {}
13+
14+
// OpenRowsetCosmosOption is the interface for OpenRowset Cosmos options.
15+
type OpenRowsetCosmosOption interface {
16+
openRowsetCosmosOption()
17+
}
18+
19+
// LiteralOpenRowsetCosmosOption represents an option with a literal value.
20+
type LiteralOpenRowsetCosmosOption struct {
21+
Value ScalarExpression `json:"Value,omitempty"`
22+
OptionKind string `json:"OptionKind,omitempty"`
23+
}
24+
25+
func (l *LiteralOpenRowsetCosmosOption) openRowsetCosmosOption() {}
26+
27+
// OpenRowsetTableReference represents a traditional OPENROWSET('provider', 'connstr', object) syntax.
28+
type OpenRowsetTableReference struct {
29+
ProviderName ScalarExpression `json:"ProviderName,omitempty"`
30+
ProviderString ScalarExpression `json:"ProviderString,omitempty"`
31+
Object *SchemaObjectName `json:"Object,omitempty"`
32+
WithColumns []*OpenRowsetColumnDefinition `json:"WithColumns,omitempty"`
33+
Alias *Identifier `json:"Alias,omitempty"`
34+
ForPath bool `json:"ForPath"`
35+
}
36+
37+
func (o *OpenRowsetTableReference) node() {}
38+
func (o *OpenRowsetTableReference) tableReference() {}
39+
40+
// OpenRowsetColumnDefinition represents a column definition in WITH clause.
41+
type OpenRowsetColumnDefinition struct {
42+
ColumnIdentifier *Identifier `json:"ColumnIdentifier,omitempty"`
43+
DataType DataTypeReference `json:"DataType,omitempty"`
44+
}

parser/marshal.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,6 +2401,54 @@ func tableReferenceToJSON(ref ast.TableReference) jsonNode {
24012401
}
24022402
node["ForPath"] = r.ForPath
24032403
return node
2404+
case *ast.OpenRowsetCosmos:
2405+
node := jsonNode{
2406+
"$type": "OpenRowsetCosmos",
2407+
}
2408+
if len(r.Options) > 0 {
2409+
opts := make([]jsonNode, len(r.Options))
2410+
for i, o := range r.Options {
2411+
opts[i] = openRowsetCosmosOptionToJSON(o)
2412+
}
2413+
node["Options"] = opts
2414+
}
2415+
if len(r.WithColumns) > 0 {
2416+
cols := make([]jsonNode, len(r.WithColumns))
2417+
for i, c := range r.WithColumns {
2418+
cols[i] = openRowsetColumnDefinitionToJSON(c)
2419+
}
2420+
node["WithColumns"] = cols
2421+
}
2422+
if r.Alias != nil {
2423+
node["Alias"] = identifierToJSON(r.Alias)
2424+
}
2425+
node["ForPath"] = r.ForPath
2426+
return node
2427+
case *ast.OpenRowsetTableReference:
2428+
node := jsonNode{
2429+
"$type": "OpenRowsetTableReference",
2430+
}
2431+
if r.ProviderName != nil {
2432+
node["ProviderName"] = scalarExpressionToJSON(r.ProviderName)
2433+
}
2434+
if r.ProviderString != nil {
2435+
node["ProviderString"] = scalarExpressionToJSON(r.ProviderString)
2436+
}
2437+
if r.Object != nil {
2438+
node["Object"] = schemaObjectNameToJSON(r.Object)
2439+
}
2440+
if len(r.WithColumns) > 0 {
2441+
cols := make([]jsonNode, len(r.WithColumns))
2442+
for i, c := range r.WithColumns {
2443+
cols[i] = openRowsetColumnDefinitionToJSON(c)
2444+
}
2445+
node["WithColumns"] = cols
2446+
}
2447+
if r.Alias != nil {
2448+
node["Alias"] = identifierToJSON(r.Alias)
2449+
}
2450+
node["ForPath"] = r.ForPath
2451+
return node
24042452
case *ast.PredictTableReference:
24052453
node := jsonNode{
24062454
"$type": "PredictTableReference",
@@ -17217,3 +17265,32 @@ func sensitivityClassificationOptionToJSON(opt *ast.SensitivityClassificationOpt
1721717265
}
1721817266
return node
1721917267
}
17268+
17269+
func openRowsetCosmosOptionToJSON(opt ast.OpenRowsetCosmosOption) jsonNode {
17270+
switch o := opt.(type) {
17271+
case *ast.LiteralOpenRowsetCosmosOption:
17272+
node := jsonNode{
17273+
"$type": "LiteralOpenRowsetCosmosOption",
17274+
"OptionKind": o.OptionKind,
17275+
}
17276+
if o.Value != nil {
17277+
node["Value"] = scalarExpressionToJSON(o.Value)
17278+
}
17279+
return node
17280+
default:
17281+
return jsonNode{"$type": "UnknownOpenRowsetCosmosOption"}
17282+
}
17283+
}
17284+
17285+
func openRowsetColumnDefinitionToJSON(col *ast.OpenRowsetColumnDefinition) jsonNode {
17286+
node := jsonNode{
17287+
"$type": "OpenRowsetColumnDefinition",
17288+
}
17289+
if col.ColumnIdentifier != nil {
17290+
node["ColumnIdentifier"] = identifierToJSON(col.ColumnIdentifier)
17291+
}
17292+
if col.DataType != nil {
17293+
node["DataType"] = dataTypeReferenceToJSON(col.DataType)
17294+
}
17295+
return node
17296+
}

parser/parse_dml.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,16 @@ func (p *Parser) parseOpenRowset() (ast.TableReference, error) {
405405
return p.parseBulkOpenRowset()
406406
}
407407

408+
// Check for Cosmos form: OPENROWSET(PROVIDER = '...', CONNECTION = '...', ...)
409+
if p.curTok.Type == TokenIdent && strings.ToUpper(p.curTok.Literal) == "PROVIDER" && p.peekTok.Type == TokenEquals {
410+
return p.parseOpenRowsetCosmos()
411+
}
412+
413+
// Check for traditional form: OPENROWSET('provider', 'connstr', tablename)
414+
if p.curTok.Type == TokenString {
415+
return p.parseOpenRowsetTableReference()
416+
}
417+
408418
// Parse identifier
409419
if p.curTok.Type != TokenIdent {
410420
return nil, fmt.Errorf("expected identifier in OPENROWSET, got %s", p.curTok.Literal)
@@ -434,6 +444,192 @@ func (p *Parser) parseOpenRowset() (ast.TableReference, error) {
434444
}, nil
435445
}
436446

447+
func (p *Parser) parseOpenRowsetCosmos() (*ast.OpenRowsetCosmos, error) {
448+
result := &ast.OpenRowsetCosmos{
449+
ForPath: false,
450+
}
451+
452+
// Parse options: PROVIDER = 'value', CONNECTION = 'value', etc.
453+
// Note: Some option names like CREDENTIAL are keywords, so check for those too
454+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
455+
// Check if this is a valid option name (identifier or keyword like CREDENTIAL)
456+
optionName := strings.ToUpper(p.curTok.Literal)
457+
isValidOption := p.curTok.Type == TokenIdent || p.curTok.Type == TokenCredential ||
458+
optionName == "PROVIDER" || optionName == "CONNECTION" || optionName == "OBJECT" ||
459+
optionName == "SERVER_CREDENTIAL"
460+
if !isValidOption {
461+
break
462+
}
463+
464+
p.nextToken() // consume option name
465+
466+
if p.curTok.Type != TokenEquals {
467+
return nil, fmt.Errorf("expected = after %s, got %s", optionName, p.curTok.Literal)
468+
}
469+
p.nextToken() // consume =
470+
471+
// Parse option value
472+
value, err := p.parseScalarExpression()
473+
if err != nil {
474+
return nil, err
475+
}
476+
477+
// Map option names to expected OptionKind values
478+
optionKind := optionName
479+
switch optionName {
480+
case "PROVIDER":
481+
optionKind = "Provider"
482+
case "CONNECTION":
483+
optionKind = "Connection"
484+
case "OBJECT":
485+
optionKind = "Object"
486+
case "CREDENTIAL":
487+
optionKind = "Credential"
488+
case "SERVER_CREDENTIAL":
489+
optionKind = "Server_Credential"
490+
}
491+
492+
opt := &ast.LiteralOpenRowsetCosmosOption{
493+
Value: value,
494+
OptionKind: optionKind,
495+
}
496+
result.Options = append(result.Options, opt)
497+
498+
if p.curTok.Type == TokenComma {
499+
p.nextToken()
500+
} else {
501+
break
502+
}
503+
}
504+
505+
if p.curTok.Type != TokenRParen {
506+
return nil, fmt.Errorf("expected ) in OPENROWSET, got %s", p.curTok.Literal)
507+
}
508+
p.nextToken() // consume )
509+
510+
// Parse optional WITH (columns)
511+
if strings.ToUpper(p.curTok.Literal) == "WITH" {
512+
p.nextToken() // consume WITH
513+
if p.curTok.Type == TokenLParen {
514+
p.nextToken() // consume (
515+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
516+
colDef := &ast.OpenRowsetColumnDefinition{}
517+
colDef.ColumnIdentifier = p.parseIdentifier()
518+
519+
// Parse data type
520+
dataType, err := p.parseDataTypeReference()
521+
if err != nil {
522+
return nil, err
523+
}
524+
colDef.DataType = dataType
525+
526+
result.WithColumns = append(result.WithColumns, colDef)
527+
528+
if p.curTok.Type == TokenComma {
529+
p.nextToken()
530+
} else {
531+
break
532+
}
533+
}
534+
if p.curTok.Type == TokenRParen {
535+
p.nextToken() // consume )
536+
}
537+
}
538+
}
539+
540+
// Parse optional alias
541+
if p.curTok.Type == TokenAs {
542+
p.nextToken() // consume AS
543+
if p.curTok.Type == TokenIdent || p.curTok.Type == TokenLBracket {
544+
result.Alias = p.parseIdentifier()
545+
}
546+
}
547+
548+
return result, nil
549+
}
550+
551+
func (p *Parser) parseOpenRowsetTableReference() (*ast.OpenRowsetTableReference, error) {
552+
result := &ast.OpenRowsetTableReference{
553+
ForPath: false,
554+
}
555+
556+
// Parse provider name (string literal)
557+
providerName, err := p.parseScalarExpression()
558+
if err != nil {
559+
return nil, err
560+
}
561+
result.ProviderName = providerName
562+
563+
if p.curTok.Type != TokenComma {
564+
return nil, fmt.Errorf("expected , after provider name, got %s", p.curTok.Literal)
565+
}
566+
p.nextToken() // consume ,
567+
568+
// Parse provider string (string literal)
569+
providerString, err := p.parseScalarExpression()
570+
if err != nil {
571+
return nil, err
572+
}
573+
result.ProviderString = providerString
574+
575+
if p.curTok.Type != TokenComma {
576+
return nil, fmt.Errorf("expected , after provider string, got %s", p.curTok.Literal)
577+
}
578+
p.nextToken() // consume ,
579+
580+
// Parse object (schema object name or expression)
581+
obj, err := p.parseSchemaObjectName()
582+
if err != nil {
583+
return nil, err
584+
}
585+
result.Object = obj
586+
587+
if p.curTok.Type != TokenRParen {
588+
return nil, fmt.Errorf("expected ) in OPENROWSET, got %s", p.curTok.Literal)
589+
}
590+
p.nextToken() // consume )
591+
592+
// Parse optional WITH (columns)
593+
if strings.ToUpper(p.curTok.Literal) == "WITH" {
594+
p.nextToken() // consume WITH
595+
if p.curTok.Type == TokenLParen {
596+
p.nextToken() // consume (
597+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
598+
colDef := &ast.OpenRowsetColumnDefinition{}
599+
colDef.ColumnIdentifier = p.parseIdentifier()
600+
601+
// Parse data type
602+
dataType, err := p.parseDataTypeReference()
603+
if err != nil {
604+
return nil, err
605+
}
606+
colDef.DataType = dataType
607+
608+
result.WithColumns = append(result.WithColumns, colDef)
609+
610+
if p.curTok.Type == TokenComma {
611+
p.nextToken()
612+
} else {
613+
break
614+
}
615+
}
616+
if p.curTok.Type == TokenRParen {
617+
p.nextToken() // consume )
618+
}
619+
}
620+
}
621+
622+
// Parse optional alias
623+
if p.curTok.Type == TokenAs {
624+
p.nextToken() // consume AS
625+
if p.curTok.Type == TokenIdent || p.curTok.Type == TokenLBracket {
626+
result.Alias = p.parseIdentifier()
627+
}
628+
}
629+
630+
return result, nil
631+
}
632+
437633
func (p *Parser) parseBulkOpenRowset() (*ast.BulkOpenRowset, error) {
438634
// We're positioned on BULK, consume it
439635
p.nextToken()
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)