@@ -186,6 +186,18 @@ func (p *Parser) parseQueryExpressionWithInto() (ast.QueryExpression, *ast.Schem
186186 }
187187 }
188188
189+ // Parse FOR clause (FOR BROWSE, FOR XML, FOR UPDATE, FOR READ ONLY)
190+ if strings .ToUpper (p .curTok .Literal ) == "FOR" {
191+ forClause , err := p .parseForClause ()
192+ if err != nil {
193+ return nil , nil , nil , err
194+ }
195+ // Attach to QuerySpecification
196+ if qs , ok := left .(* ast.QuerySpecification ); ok {
197+ qs .ForClause = forClause
198+ }
199+ }
200+
189201 return left , into , on , nil
190202}
191203
@@ -1797,7 +1809,7 @@ func (p *Parser) parseNamedTableReference() (*ast.NamedTableReference, error) {
17971809 } else if p .curTok .Type == TokenIdent {
17981810 // Could be an alias without AS, but need to be careful not to consume keywords
17991811 upper := strings .ToUpper (p .curTok .Literal )
1800- if upper != "WHERE" && upper != "GROUP" && upper != "HAVING" && upper != "ORDER" && upper != "OPTION" && upper != "GO" && upper != "WITH" && upper != "ON" && upper != "JOIN" && upper != "INNER" && upper != "LEFT" && upper != "RIGHT" && upper != "FULL" && upper != "CROSS" && upper != "OUTER" {
1812+ if upper != "WHERE" && upper != "GROUP" && upper != "HAVING" && upper != "ORDER" && upper != "OPTION" && upper != "GO" && upper != "WITH" && upper != "ON" && upper != "JOIN" && upper != "INNER" && upper != "LEFT" && upper != "RIGHT" && upper != "FULL" && upper != "CROSS" && upper != "OUTER" && upper != "FOR" {
18011813 ref .Alias = & ast.Identifier {Value : p .curTok .Literal , QuoteType : "NotQuoted" }
18021814 p .nextToken ()
18031815 }
@@ -1857,7 +1869,7 @@ func (p *Parser) parseNamedTableReferenceWithName(son *ast.SchemaObjectName) (*a
18571869 // Could be an alias without AS, but need to be careful not to consume keywords
18581870 if p .curTok .Type == TokenIdent {
18591871 upper := strings .ToUpper (p .curTok .Literal )
1860- if upper != "WHERE" && upper != "GROUP" && upper != "HAVING" && upper != "ORDER" && upper != "OPTION" && upper != "GO" && upper != "WITH" && upper != "ON" && upper != "JOIN" && upper != "INNER" && upper != "LEFT" && upper != "RIGHT" && upper != "FULL" && upper != "CROSS" && upper != "OUTER" {
1872+ if upper != "WHERE" && upper != "GROUP" && upper != "HAVING" && upper != "ORDER" && upper != "OPTION" && upper != "GO" && upper != "WITH" && upper != "ON" && upper != "JOIN" && upper != "INNER" && upper != "LEFT" && upper != "RIGHT" && upper != "FULL" && upper != "CROSS" && upper != "OUTER" && upper != "FOR" {
18611873 ref .Alias = p .parseIdentifier ()
18621874 }
18631875 } else {
@@ -3436,3 +3448,168 @@ func (p *Parser) parsePredictTableReference() (*ast.PredictTableReference, error
34363448
34373449 return ref , nil
34383450}
3451+
3452+ // parseForClause parses FOR BROWSE, FOR XML, FOR UPDATE, FOR READ ONLY clauses.
3453+ func (p * Parser ) parseForClause () (ast.ForClause , error ) {
3454+ p .nextToken () // consume FOR
3455+
3456+ keyword := strings .ToUpper (p .curTok .Literal )
3457+
3458+ switch keyword {
3459+ case "BROWSE" :
3460+ p .nextToken () // consume BROWSE
3461+ return & ast.BrowseForClause {}, nil
3462+
3463+ case "READ" :
3464+ p .nextToken () // consume READ
3465+ if strings .ToUpper (p .curTok .Literal ) == "ONLY" {
3466+ p .nextToken () // consume ONLY
3467+ }
3468+ return & ast.ReadOnlyForClause {}, nil
3469+
3470+ case "UPDATE" :
3471+ p .nextToken () // consume UPDATE
3472+ clause := & ast.UpdateForClause {}
3473+
3474+ // Check for OF column_list
3475+ if strings .ToUpper (p .curTok .Literal ) == "OF" {
3476+ p .nextToken () // consume OF
3477+
3478+ // Parse column list
3479+ for {
3480+ col , err := p .parseColumnReference ()
3481+ if err != nil {
3482+ return nil , err
3483+ }
3484+ clause .Columns = append (clause .Columns , col )
3485+
3486+ if p .curTok .Type != TokenComma {
3487+ break
3488+ }
3489+ p .nextToken () // consume comma
3490+ }
3491+ }
3492+ return clause , nil
3493+
3494+ case "XML" :
3495+ p .nextToken () // consume XML
3496+ return p .parseXmlForClause ()
3497+
3498+ default :
3499+ return nil , fmt .Errorf ("unexpected token after FOR: %s" , p .curTok .Literal )
3500+ }
3501+ }
3502+
3503+ // parseXmlForClause parses FOR XML options.
3504+ func (p * Parser ) parseXmlForClause () (* ast.XmlForClause , error ) {
3505+ clause := & ast.XmlForClause {}
3506+
3507+ // Parse XML options separated by commas
3508+ for {
3509+ option , err := p .parseXmlForClauseOption ()
3510+ if err != nil {
3511+ return nil , err
3512+ }
3513+ clause .Options = append (clause .Options , option )
3514+
3515+ if p .curTok .Type != TokenComma {
3516+ break
3517+ }
3518+ p .nextToken () // consume comma
3519+ }
3520+
3521+ return clause , nil
3522+ }
3523+
3524+ // parseXmlForClauseOption parses a single XML FOR clause option.
3525+ func (p * Parser ) parseXmlForClauseOption () (* ast.XmlForClauseOption , error ) {
3526+ option := & ast.XmlForClauseOption {}
3527+
3528+ keyword := strings .ToUpper (p .curTok .Literal )
3529+ p .nextToken () // consume the option keyword
3530+
3531+ switch keyword {
3532+ case "AUTO" :
3533+ option .OptionKind = "Auto"
3534+ case "EXPLICIT" :
3535+ option .OptionKind = "Explicit"
3536+ case "RAW" :
3537+ option .OptionKind = "Raw"
3538+ // Check for optional element name: RAW ('name')
3539+ if p .curTok .Type == TokenLParen {
3540+ p .nextToken () // consume (
3541+ if p .curTok .Type == TokenString {
3542+ option .Value = p .parseStringLiteralValue ()
3543+ p .nextToken () // consume string
3544+ }
3545+ if p .curTok .Type == TokenRParen {
3546+ p .nextToken () // consume )
3547+ }
3548+ }
3549+ case "PATH" :
3550+ option .OptionKind = "Path"
3551+ // Check for optional path name: PATH ('name')
3552+ if p .curTok .Type == TokenLParen {
3553+ p .nextToken () // consume (
3554+ if p .curTok .Type == TokenString {
3555+ option .Value = p .parseStringLiteralValue ()
3556+ p .nextToken () // consume string
3557+ }
3558+ if p .curTok .Type == TokenRParen {
3559+ p .nextToken () // consume )
3560+ }
3561+ }
3562+ case "ELEMENTS" :
3563+ // Check for XSINIL or ABSENT
3564+ nextKeyword := strings .ToUpper (p .curTok .Literal )
3565+ if nextKeyword == "XSINIL" {
3566+ option .OptionKind = "ElementsXsiNil"
3567+ p .nextToken () // consume XSINIL
3568+ } else if nextKeyword == "ABSENT" {
3569+ option .OptionKind = "ElementsAbsent"
3570+ p .nextToken () // consume ABSENT
3571+ } else {
3572+ option .OptionKind = "Elements"
3573+ }
3574+ case "XMLDATA" :
3575+ option .OptionKind = "XmlData"
3576+ case "XMLSCHEMA" :
3577+ option .OptionKind = "XmlSchema"
3578+ // Check for optional namespace: XMLSCHEMA ('namespace')
3579+ if p .curTok .Type == TokenLParen {
3580+ p .nextToken () // consume (
3581+ if p .curTok .Type == TokenString {
3582+ option .Value = p .parseStringLiteralValue ()
3583+ p .nextToken () // consume string
3584+ }
3585+ if p .curTok .Type == TokenRParen {
3586+ p .nextToken () // consume )
3587+ }
3588+ }
3589+ case "ROOT" :
3590+ option .OptionKind = "Root"
3591+ // Check for optional root name: ROOT ('name')
3592+ if p .curTok .Type == TokenLParen {
3593+ p .nextToken () // consume (
3594+ if p .curTok .Type == TokenString {
3595+ option .Value = p .parseStringLiteralValue ()
3596+ p .nextToken () // consume string
3597+ }
3598+ if p .curTok .Type == TokenRParen {
3599+ p .nextToken () // consume )
3600+ }
3601+ }
3602+ case "TYPE" :
3603+ option .OptionKind = "Type"
3604+ case "BINARY" :
3605+ // BINARY BASE64
3606+ if strings .ToUpper (p .curTok .Literal ) == "BASE64" {
3607+ option .OptionKind = "BinaryBase64"
3608+ p .nextToken () // consume BASE64
3609+ }
3610+ default :
3611+ option .OptionKind = keyword
3612+ }
3613+
3614+ return option , nil
3615+ }
0 commit comments