@@ -2641,6 +2641,12 @@ func (p *Parser) parseTableReference() (ast.TableReference, error) {
26412641 break
26422642 }
26432643
2644+ // Check for LOCAL modifier (undocumented feature) and join hints
2645+ // Syntax: INNER LOCAL MERGE JOIN - LOCAL is just skipped
2646+ if p .curTok .Type == TokenIdent && strings .ToUpper (p .curTok .Literal ) == "LOCAL" {
2647+ p .nextToken () // skip LOCAL
2648+ }
2649+
26442650 // Check for join hints (REMOTE, LOOP, HASH, MERGE, REDUCE, REPLICATE, REDISTRIBUTE)
26452651 joinHint := ""
26462652 if p .curTok .Type == TokenIdent {
@@ -2765,6 +2771,12 @@ func (p *Parser) parseJoinTypeAndHint() (joinType, joinHint string) {
27652771 return "" , ""
27662772 }
27672773
2774+ // Check for LOCAL modifier (undocumented feature) and join hints
2775+ // Syntax: INNER LOCAL MERGE JOIN - LOCAL is just skipped
2776+ if p .curTok .Type == TokenIdent && strings .ToUpper (p .curTok .Literal ) == "LOCAL" {
2777+ p .nextToken () // skip LOCAL
2778+ }
2779+
27682780 // Check for join hints (REMOTE, LOOP, HASH, MERGE, REDUCE, REPLICATE, REDISTRIBUTE)
27692781 if p .curTok .Type == TokenIdent {
27702782 upper := strings .ToUpper (p .curTok .Literal )
@@ -3813,27 +3825,77 @@ func (p *Parser) parseNamedTableReference() (*ast.NamedTableReference, error) {
38133825 }
38143826 }
38153827
3828+ // Check for naked HOLDLOCK/NOWAIT before alias: table HOLDLOCK, table2
3829+ if p .curTok .Type == TokenHoldlock {
3830+ ref .TableHints = append (ref .TableHints , & ast.TableHint {HintKind : "HoldLock" })
3831+ p .nextToken ()
3832+ }
3833+ if p .curTok .Type == TokenNowait {
3834+ ref .TableHints = append (ref .TableHints , & ast.TableHint {HintKind : "Nowait" })
3835+ p .nextToken ()
3836+ }
3837+
38163838 // Parse optional alias (AS alias or just alias)
38173839 if p .curTok .Type == TokenAs {
38183840 p .nextToken ()
3819- if p .curTok .Type != TokenIdent {
3841+ if p .curTok .Type == TokenIdent || p .curTok .Type == TokenLBracket {
3842+ ref .Alias = p .parseIdentifier ()
3843+ } else {
38203844 return nil , fmt .Errorf ("expected identifier after AS, got %s" , p .curTok .Literal )
38213845 }
3822- ref .Alias = & ast.Identifier {Value : p .curTok .Literal , QuoteType : "NotQuoted" }
3823- p .nextToken ()
3824- } else if p .curTok .Type == TokenIdent {
3846+ } else if p .curTok .Type == TokenIdent || p .curTok .Type == TokenLBracket {
38253847 // Could be an alias without AS, but need to be careful not to consume keywords
3826- upper := strings .ToUpper (p .curTok .Literal )
3827- if upper != "WHERE" && upper != "GROUP" && upper != "HAVING" && upper != "WINDOW" && 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" && upper != "USING" && upper != "WHEN" && upper != "OUTPUT" && upper != "PIVOT" && upper != "UNPIVOT" {
3828- ref .Alias = & ast.Identifier {Value : p .curTok .Literal , QuoteType : "NotQuoted" }
3848+ if p .curTok .Type == TokenIdent {
3849+ upper := strings .ToUpper (p .curTok .Literal )
3850+ if upper != "WHERE" && upper != "GROUP" && upper != "HAVING" && upper != "WINDOW" && 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" && upper != "USING" && upper != "WHEN" && upper != "OUTPUT" && upper != "PIVOT" && upper != "UNPIVOT" {
3851+ ref .Alias = p .parseIdentifier ()
3852+ }
3853+ } else {
3854+ ref .Alias = p .parseIdentifier ()
3855+ }
3856+ }
3857+
3858+ // Check for old-style hints AFTER alias: table alias (1) or table alias (nolock)
3859+ // peekIsOldStyleIndexHint is safe to use here since we're after the alias
3860+ if p .curTok .Type == TokenLParen && (p .peekIsTableHint () || p .peekIsOldStyleIndexHint ()) {
3861+ p .nextToken () // consume (
3862+ for p .curTok .Type != TokenRParen && p .curTok .Type != TokenEOF {
3863+ hint , err := p .parseTableHint ()
3864+ if err != nil {
3865+ return nil , err
3866+ }
3867+ if hint != nil {
3868+ ref .TableHints = append (ref .TableHints , hint )
3869+ }
3870+ if p .curTok .Type == TokenComma {
3871+ p .nextToken ()
3872+ } else if p .curTok .Type != TokenRParen {
3873+ if p .isTableHintToken () {
3874+ continue
3875+ }
3876+ break
3877+ }
3878+ }
3879+ if p .curTok .Type == TokenRParen {
38293880 p .nextToken ()
38303881 }
38313882 }
38323883
3884+ // Check for naked HOLDLOCK/NOWAIT after alias: table alias HOLDLOCK
3885+ if p .curTok .Type == TokenHoldlock {
3886+ ref .TableHints = append (ref .TableHints , & ast.TableHint {HintKind : "HoldLock" })
3887+ p .nextToken ()
3888+ }
3889+ if p .curTok .Type == TokenNowait {
3890+ ref .TableHints = append (ref .TableHints , & ast.TableHint {HintKind : "Nowait" })
3891+ p .nextToken ()
3892+ }
3893+
38333894 // Check for new-style hints (with WITH keyword): alias WITH (hints)
38343895 if p .curTok .Type == TokenWith && p .peekTok .Type == TokenLParen {
38353896 p .nextToken () // consume WITH
3836- if p .curTok .Type == TokenLParen && p .peekIsTableHint () {
3897+ // In WITH context, numbers are valid index hints: WITH (0)
3898+ if p .curTok .Type == TokenLParen && (p .peekIsTableHint () || p .peekIsOldStyleIndexHint ()) {
38373899 p .nextToken () // consume (
38383900 for p .curTok .Type != TokenRParen && p .curTok .Type != TokenEOF {
38393901 hint , err := p .parseTableHint ()
@@ -3915,6 +3977,16 @@ func (p *Parser) parseNamedTableReferenceWithName(son *ast.SchemaObjectName) (*a
39153977 }
39163978 }
39173979
3980+ // Check for naked HOLDLOCK/NOWAIT before alias: table HOLDLOCK, table2
3981+ if p .curTok .Type == TokenHoldlock {
3982+ ref .TableHints = append (ref .TableHints , & ast.TableHint {HintKind : "HoldLock" })
3983+ p .nextToken ()
3984+ }
3985+ if p .curTok .Type == TokenNowait {
3986+ ref .TableHints = append (ref .TableHints , & ast.TableHint {HintKind : "Nowait" })
3987+ p .nextToken ()
3988+ }
3989+
39183990 // Parse optional alias (AS alias or just alias)
39193991 if p .curTok .Type == TokenAs {
39203992 p .nextToken ()
@@ -3934,6 +4006,42 @@ func (p *Parser) parseNamedTableReferenceWithName(son *ast.SchemaObjectName) (*a
39344006 }
39354007 }
39364008
4009+ // Check for old-style hints AFTER alias: table alias (1) or table alias (nolock)
4010+ // peekIsOldStyleIndexHint is safe to use here since we're after the alias
4011+ if p .curTok .Type == TokenLParen && (p .peekIsTableHint () || p .peekIsOldStyleIndexHint ()) {
4012+ p .nextToken () // consume (
4013+ for p .curTok .Type != TokenRParen && p .curTok .Type != TokenEOF {
4014+ hint , err := p .parseTableHint ()
4015+ if err != nil {
4016+ return nil , err
4017+ }
4018+ if hint != nil {
4019+ ref .TableHints = append (ref .TableHints , hint )
4020+ }
4021+ if p .curTok .Type == TokenComma {
4022+ p .nextToken ()
4023+ } else if p .curTok .Type != TokenRParen {
4024+ if p .isTableHintToken () {
4025+ continue
4026+ }
4027+ break
4028+ }
4029+ }
4030+ if p .curTok .Type == TokenRParen {
4031+ p .nextToken ()
4032+ }
4033+ }
4034+
4035+ // Check for naked HOLDLOCK/NOWAIT after alias: table alias HOLDLOCK
4036+ if p .curTok .Type == TokenHoldlock {
4037+ ref .TableHints = append (ref .TableHints , & ast.TableHint {HintKind : "HoldLock" })
4038+ p .nextToken ()
4039+ }
4040+ if p .curTok .Type == TokenNowait {
4041+ ref .TableHints = append (ref .TableHints , & ast.TableHint {HintKind : "Nowait" })
4042+ p .nextToken ()
4043+ }
4044+
39374045 // Check for TABLESAMPLE after alias (supports syntax: t1 AS alias TABLESAMPLE (...))
39384046 if ref .TableSampleClause == nil && strings .ToUpper (p .curTok .Literal ) == "TABLESAMPLE" {
39394047 tableSample , err := p .parseTableSampleClause ()
@@ -3971,7 +4079,8 @@ func (p *Parser) parseNamedTableReferenceWithName(son *ast.SchemaObjectName) (*a
39714079 // Check for new-style hints (with WITH keyword): alias WITH (hints)
39724080 if p .curTok .Type == TokenWith && p .peekTok .Type == TokenLParen {
39734081 p .nextToken () // consume WITH
3974- if p .curTok .Type == TokenLParen && p .peekIsTableHint () {
4082+ // In WITH context, numbers are valid index hints: WITH (0)
4083+ if p .curTok .Type == TokenLParen && (p .peekIsTableHint () || p .peekIsOldStyleIndexHint ()) {
39754084 p .nextToken () // consume (
39764085 for p .curTok .Type != TokenRParen && p .curTok .Type != TokenEOF {
39774086 hint , err := p .parseTableHint ()
@@ -4435,6 +4544,49 @@ func (p *Parser) parseSimpleExpression() (ast.ScalarExpression, error) {
44354544
44364545// parseTableHint parses a single table hint
44374546func (p * Parser ) parseTableHint () (ast.TableHintType , error ) {
4547+ // Handle old-style numeric index hint (just a number like "0" or "1")
4548+ if p .curTok .Type == TokenNumber {
4549+ hint := & ast.IndexTableHint {
4550+ HintKind : "Index" ,
4551+ IndexValues : []* ast.IdentifierOrValueExpression {
4552+ {
4553+ Value : p .curTok .Literal ,
4554+ ValueExpression : & ast.IntegerLiteral {
4555+ LiteralType : "Integer" ,
4556+ Value : p .curTok .Literal ,
4557+ },
4558+ },
4559+ },
4560+ }
4561+ p .nextToken ()
4562+ // Check for additional comma-separated values
4563+ for p .curTok .Type == TokenComma {
4564+ p .nextToken ()
4565+ if p .curTok .Type == TokenNumber {
4566+ hint .IndexValues = append (hint .IndexValues , & ast.IdentifierOrValueExpression {
4567+ Value : p .curTok .Literal ,
4568+ ValueExpression : & ast.IntegerLiteral {
4569+ LiteralType : "Integer" ,
4570+ Value : p .curTok .Literal ,
4571+ },
4572+ })
4573+ p .nextToken ()
4574+ } else if p .curTok .Type == TokenIdent {
4575+ hint .IndexValues = append (hint .IndexValues , & ast.IdentifierOrValueExpression {
4576+ Value : p .curTok .Literal ,
4577+ Identifier : & ast.Identifier {
4578+ Value : p .curTok .Literal ,
4579+ QuoteType : "NotQuoted" ,
4580+ },
4581+ })
4582+ p .nextToken ()
4583+ } else {
4584+ break
4585+ }
4586+ }
4587+ return hint , nil
4588+ }
4589+
44384590 hintName := strings .ToUpper (p .curTok .Literal )
44394591 p .nextToken () // consume hint name
44404592
@@ -4686,6 +4838,12 @@ func (p *Parser) peekIsTableHint() bool {
46864838 return false
46874839}
46884840
4841+ // peekIsOldStyleIndexHint checks if the peek token is a number (for old-style index hint like (0))
4842+ // This is only valid after an alias or table name, not for function calls
4843+ func (p * Parser ) peekIsOldStyleIndexHint () bool {
4844+ return p .peekTok .Type == TokenNumber
4845+ }
4846+
46894847func (p * Parser ) parseSchemaObjectName () (* ast.SchemaObjectName , error ) {
46904848 var identifiers []* ast.Identifier
46914849
0 commit comments