@@ -232,6 +232,25 @@ func (p *Parser) parseBulkOpenRowset() (*ast.BulkOpenRowset, error) {
232232 }
233233 }
234234
235+ // Parse optional column list (e.g., AS a(c1, c2))
236+ if p .curTok .Type == TokenLParen {
237+ p .nextToken ()
238+ for {
239+ if p .curTok .Type == TokenIdent || p .curTok .Type == TokenLBracket {
240+ result .Columns = append (result .Columns , p .parseIdentifier ())
241+ }
242+ if p .curTok .Type == TokenComma {
243+ p .nextToken ()
244+ continue
245+ }
246+ break
247+ }
248+ if p .curTok .Type != TokenRParen {
249+ return nil , fmt .Errorf ("expected ) after column list, got %s" , p .curTok .Literal )
250+ }
251+ p .nextToken ()
252+ }
253+
235254 return result , nil
236255}
237256
@@ -579,6 +598,32 @@ func (p *Parser) parseExecuteSpecification() (*ast.ExecuteSpecification, error)
579598
580599 spec := & ast.ExecuteSpecification {}
581600
601+ // Check for EXECUTE ('string') form - ExecutableStringList
602+ if p .curTok .Type == TokenLParen {
603+ strList , err := p .parseExecutableStringList ()
604+ if err != nil {
605+ return nil , err
606+ }
607+ spec .ExecutableEntity = strList
608+
609+ // Parse optional AS USER/LOGIN context
610+ if p .curTok .Type == TokenAs {
611+ ctx , err := p .parseExecuteContextForSpec ()
612+ if err != nil {
613+ return nil , err
614+ }
615+ spec .ExecuteContext = ctx
616+ }
617+
618+ // Parse optional AT LinkedServer
619+ if p .curTok .Type == TokenIdent && strings .ToUpper (p .curTok .Literal ) == "AT" {
620+ p .nextToken ()
621+ spec .LinkedServer = p .parseIdentifier ()
622+ }
623+
624+ return spec , nil
625+ }
626+
582627 // Check for return variable assignment @var =
583628 if p .curTok .Type == TokenIdent && strings .HasPrefix (p .curTok .Literal , "@" ) {
584629 varName := p .curTok .Literal
@@ -636,6 +681,115 @@ func (p *Parser) parseExecuteSpecification() (*ast.ExecuteSpecification, error)
636681 return spec , nil
637682}
638683
684+ func (p * Parser ) parseExecutableStringList () (* ast.ExecutableStringList , error ) {
685+ // We're positioned on (, consume it
686+ p .nextToken ()
687+
688+ strList := & ast.ExecutableStringList {}
689+
690+ // Parse the first string expression (may be concatenated with +)
691+ for {
692+ if p .curTok .Type == TokenString || p .curTok .Type == TokenNationalString {
693+ expr , err := p .parseScalarExpression ()
694+ if err != nil {
695+ return nil , err
696+ }
697+ // parseScalarExpression handles the + concatenation, so we get a BinaryExpression
698+ // But we need to flatten it to individual StringLiterals for the Strings array
699+ p .flattenStringExpression (expr , & strList .Strings )
700+ } else {
701+ break
702+ }
703+
704+ // Check for comma (parameters follow) or closing paren
705+ if p .curTok .Type == TokenComma {
706+ p .nextToken ()
707+ break
708+ }
709+ if p .curTok .Type == TokenRParen {
710+ break
711+ }
712+ }
713+
714+ // Parse parameters (after the first comma)
715+ for p .curTok .Type != TokenRParen && p .curTok .Type != TokenEOF {
716+ param , err := p .parseExecuteParameter ()
717+ if err != nil {
718+ return nil , err
719+ }
720+ strList .Parameters = append (strList .Parameters , param )
721+
722+ if p .curTok .Type != TokenComma {
723+ break
724+ }
725+ p .nextToken ()
726+ }
727+
728+ if p .curTok .Type != TokenRParen {
729+ return nil , fmt .Errorf ("expected ) after EXECUTE string list, got %s" , p .curTok .Literal )
730+ }
731+ p .nextToken ()
732+
733+ return strList , nil
734+ }
735+
736+ func (p * Parser ) flattenStringExpression (expr ast.ScalarExpression , strings * []ast.ScalarExpression ) {
737+ switch e := expr .(type ) {
738+ case * ast.BinaryExpression :
739+ // Recursively flatten for + concatenation
740+ p .flattenStringExpression (e .FirstExpression , strings )
741+ p .flattenStringExpression (e .SecondExpression , strings )
742+ default :
743+ * strings = append (* strings , expr )
744+ }
745+ }
746+
747+ func (p * Parser ) parseExecuteContextForSpec () (* ast.ExecuteContext , error ) {
748+ // We're positioned on AS, consume it
749+ p .nextToken ()
750+
751+ ctx := & ast.ExecuteContext {}
752+
753+ upper := strings .ToUpper (p .curTok .Literal )
754+ switch upper {
755+ case "USER" :
756+ ctx .Kind = "User"
757+ p .nextToken ()
758+ if p .curTok .Type == TokenEquals {
759+ p .nextToken ()
760+ expr , err := p .parseScalarExpression ()
761+ if err != nil {
762+ return nil , err
763+ }
764+ ctx .Principal = expr
765+ }
766+ case "LOGIN" :
767+ ctx .Kind = "Login"
768+ p .nextToken ()
769+ if p .curTok .Type == TokenEquals {
770+ p .nextToken ()
771+ expr , err := p .parseScalarExpression ()
772+ if err != nil {
773+ return nil , err
774+ }
775+ ctx .Principal = expr
776+ }
777+ case "CALLER" :
778+ ctx .Kind = "Caller"
779+ p .nextToken ()
780+ case "OWNER" :
781+ ctx .Kind = "Owner"
782+ p .nextToken ()
783+ case "SELF" :
784+ ctx .Kind = "Self"
785+ p .nextToken ()
786+ default :
787+ return nil , fmt .Errorf ("expected USER, LOGIN, CALLER, OWNER, or SELF after AS, got %s" , p .curTok .Literal )
788+ }
789+
790+ return ctx , nil
791+ }
792+
639793func (p * Parser ) parseExecuteParameter () (* ast.ExecuteParameter , error ) {
640794 param := & ast.ExecuteParameter {IsOutput : false }
641795
0 commit comments