Skip to content

Commit 1be5aa2

Browse files
committed
Add FOR JSON clause parsing support
Add JsonForClause and JsonForClauseOption AST types to handle FOR JSON AUTO, PATH, ROOT('name'), INCLUDE_NULL_VALUES, and WITHOUT_ARRAY_WRAPPER options in SELECT statements.
1 parent 5184819 commit 1be5aa2

5 files changed

Lines changed: 100 additions & 2 deletions

File tree

ast/for_clause.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,19 @@ type XmlForClauseOption struct {
4141
}
4242

4343
func (*XmlForClauseOption) node() {}
44+
45+
// JsonForClause represents a FOR JSON clause with its options.
46+
type JsonForClause struct {
47+
Options []*JsonForClauseOption `json:"Options,omitempty"`
48+
}
49+
50+
func (*JsonForClause) node() {}
51+
func (*JsonForClause) forClause() {}
52+
53+
// JsonForClauseOption represents an option in a FOR JSON clause.
54+
type JsonForClauseOption struct {
55+
OptionKind string `json:"OptionKind,omitempty"`
56+
Value *StringLiteral `json:"Value,omitempty"`
57+
}
58+
59+
func (*JsonForClauseOption) node() {}

parser/marshal.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,6 +1300,16 @@ func forClauseToJSON(fc ast.ForClause) jsonNode {
13001300
node["Options"] = opts
13011301
}
13021302
return node
1303+
case *ast.JsonForClause:
1304+
node := jsonNode{"$type": "JsonForClause"}
1305+
if len(f.Options) > 0 {
1306+
opts := make([]jsonNode, len(f.Options))
1307+
for i, opt := range f.Options {
1308+
opts[i] = jsonForClauseOptionToJSON(opt)
1309+
}
1310+
node["Options"] = opts
1311+
}
1312+
return node
13031313
default:
13041314
return jsonNode{"$type": "UnknownForClause"}
13051315
}
@@ -1316,6 +1326,17 @@ func xmlForClauseOptionToJSON(opt *ast.XmlForClauseOption) jsonNode {
13161326
return node
13171327
}
13181328

1329+
func jsonForClauseOptionToJSON(opt *ast.JsonForClauseOption) jsonNode {
1330+
node := jsonNode{"$type": "JsonForClauseOption"}
1331+
if opt.OptionKind != "" {
1332+
node["OptionKind"] = opt.OptionKind
1333+
}
1334+
if opt.Value != nil {
1335+
node["Value"] = stringLiteralToJSON(opt.Value)
1336+
}
1337+
return node
1338+
}
1339+
13191340
func topRowFilterToJSON(t *ast.TopRowFilter) jsonNode {
13201341
node := jsonNode{
13211342
"$type": "TopRowFilter",

parser/parse_select.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3495,6 +3495,10 @@ func (p *Parser) parseForClause() (ast.ForClause, error) {
34953495
p.nextToken() // consume XML
34963496
return p.parseXmlForClause()
34973497

3498+
case "JSON":
3499+
p.nextToken() // consume JSON
3500+
return p.parseJsonForClause()
3501+
34983502
default:
34993503
return nil, fmt.Errorf("unexpected token after FOR: %s", p.curTok.Literal)
35003504
}
@@ -3613,3 +3617,60 @@ func (p *Parser) parseXmlForClauseOption() (*ast.XmlForClauseOption, error) {
36133617

36143618
return option, nil
36153619
}
3620+
3621+
// parseJsonForClause parses FOR JSON options.
3622+
func (p *Parser) parseJsonForClause() (*ast.JsonForClause, error) {
3623+
clause := &ast.JsonForClause{}
3624+
3625+
// Parse JSON options separated by commas
3626+
for {
3627+
option, err := p.parseJsonForClauseOption()
3628+
if err != nil {
3629+
return nil, err
3630+
}
3631+
clause.Options = append(clause.Options, option)
3632+
3633+
if p.curTok.Type != TokenComma {
3634+
break
3635+
}
3636+
p.nextToken() // consume comma
3637+
}
3638+
3639+
return clause, nil
3640+
}
3641+
3642+
// parseJsonForClauseOption parses a single JSON FOR clause option.
3643+
func (p *Parser) parseJsonForClauseOption() (*ast.JsonForClauseOption, error) {
3644+
option := &ast.JsonForClauseOption{}
3645+
3646+
keyword := strings.ToUpper(p.curTok.Literal)
3647+
p.nextToken() // consume the option keyword
3648+
3649+
switch keyword {
3650+
case "AUTO":
3651+
option.OptionKind = "Auto"
3652+
case "PATH":
3653+
option.OptionKind = "Path"
3654+
case "ROOT":
3655+
option.OptionKind = "Root"
3656+
// Check for optional root name: ROOT('name')
3657+
if p.curTok.Type == TokenLParen {
3658+
p.nextToken() // consume (
3659+
if p.curTok.Type == TokenString {
3660+
option.Value = p.parseStringLiteralValue()
3661+
p.nextToken() // consume string
3662+
}
3663+
if p.curTok.Type == TokenRParen {
3664+
p.nextToken() // consume )
3665+
}
3666+
}
3667+
case "INCLUDE_NULL_VALUES":
3668+
option.OptionKind = "IncludeNullValues"
3669+
case "WITHOUT_ARRAY_WRAPPER":
3670+
option.OptionKind = "WithoutArrayWrapper"
3671+
default:
3672+
option.OptionKind = keyword
3673+
}
3674+
3675+
return option, nil
3676+
}
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)