@@ -1283,28 +1283,200 @@ func (p *Parser) parseOptionClause() ([]ast.OptimizerHintBase, error) {
12831283
12841284 // Parse hints
12851285 for p .curTok .Type != TokenRParen && p .curTok .Type != TokenEOF {
1286- if p .curTok .Type == TokenIdent || p .curTok .Type == TokenLabel {
1287- hintKind := convertHintKind (p .curTok .Literal )
1286+ if p .curTok .Type == TokenComma {
12881287 p .nextToken ()
1288+ continue
1289+ }
12891290
1290- // Check if this is a literal hint (LABEL = value, etc.)
1291- if p .curTok .Type == TokenEquals {
1292- p .nextToken () // consume =
1293- value , err := p .parseScalarExpression ()
1294- if err != nil {
1295- return nil , err
1296- }
1297- hints = append (hints , & ast.LiteralOptimizerHint {
1298- HintKind : hintKind ,
1299- Value : value ,
1300- })
1301- } else {
1302- hints = append (hints , & ast.OptimizerHint {HintKind : hintKind })
1291+ hint , err := p .parseOptimizerHint ()
1292+ if err != nil {
1293+ return nil , err
1294+ }
1295+ if hint != nil {
1296+ hints = append (hints , hint )
1297+ }
1298+ }
1299+
1300+ // Consume )
1301+ if p .curTok .Type == TokenRParen {
1302+ p .nextToken ()
1303+ }
1304+
1305+ return hints , nil
1306+ }
1307+
1308+ func (p * Parser ) parseOptimizerHint () (ast.OptimizerHintBase , error ) {
1309+ // Handle both identifiers and keywords that can appear as optimizer hints
1310+ // USE is a keyword (TokenUse), so we need to handle it specially
1311+ if p .curTok .Type == TokenUse {
1312+ p .nextToken () // consume USE
1313+ if p .curTok .Type == TokenIdent && strings .ToUpper (p .curTok .Literal ) == "PLAN" {
1314+ p .nextToken () // consume PLAN
1315+ value , err := p .parseScalarExpression ()
1316+ if err != nil {
1317+ return nil , err
13031318 }
1304- } else if p .curTok .Type == TokenComma {
1319+ return & ast.LiteralOptimizerHint {HintKind : "UsePlan" , Value : value }, nil
1320+ }
1321+ return & ast.OptimizerHint {HintKind : "Use" }, nil
1322+ }
1323+
1324+ if p .curTok .Type != TokenIdent && p .curTok .Type != TokenLabel {
1325+ // Skip unknown tokens to avoid infinite loop
1326+ p .nextToken ()
1327+ return nil , nil
1328+ }
1329+
1330+ upper := strings .ToUpper (p .curTok .Literal )
1331+
1332+ switch upper {
1333+ case "PARAMETERIZATION" :
1334+ p .nextToken () // consume PARAMETERIZATION
1335+ if p .curTok .Type == TokenIdent {
1336+ subUpper := strings .ToUpper (p .curTok .Literal )
13051337 p .nextToken ()
1306- } else {
1338+ if subUpper == "SIMPLE" {
1339+ return & ast.OptimizerHint {HintKind : "ParameterizationSimple" }, nil
1340+ } else if subUpper == "FORCED" {
1341+ return & ast.OptimizerHint {HintKind : "ParameterizationForced" }, nil
1342+ }
1343+ }
1344+ return & ast.OptimizerHint {HintKind : "Parameterization" }, nil
1345+
1346+ case "MAXRECURSION" :
1347+ p .nextToken () // consume MAXRECURSION
1348+ value , err := p .parseScalarExpression ()
1349+ if err != nil {
1350+ return nil , err
1351+ }
1352+ return & ast.LiteralOptimizerHint {HintKind : "MaxRecursion" , Value : value }, nil
1353+
1354+ case "OPTIMIZE" :
1355+ p .nextToken () // consume OPTIMIZE
1356+ if p .curTok .Type == TokenIdent {
1357+ subUpper := strings .ToUpper (p .curTok .Literal )
1358+ if subUpper == "FOR" {
1359+ p .nextToken () // consume FOR
1360+ return p .parseOptimizeForHint ()
1361+ } else if subUpper == "CORRELATED" {
1362+ p .nextToken () // consume CORRELATED
1363+ if p .curTok .Type == TokenIdent && strings .ToUpper (p .curTok .Literal ) == "UNION" {
1364+ p .nextToken () // consume UNION
1365+ if p .curTok .Type == TokenIdent && strings .ToUpper (p .curTok .Literal ) == "ALL" {
1366+ p .nextToken () // consume ALL
1367+ }
1368+ }
1369+ return & ast.OptimizerHint {HintKind : "OptimizeCorrelatedUnionAll" }, nil
1370+ }
1371+ }
1372+ return & ast.OptimizerHint {HintKind : "Optimize" }, nil
1373+
1374+ case "CHECKCONSTRAINTS" :
1375+ p .nextToken () // consume CHECKCONSTRAINTS
1376+ if p .curTok .Type == TokenIdent && strings .ToUpper (p .curTok .Literal ) == "PLAN" {
1377+ p .nextToken () // consume PLAN
1378+ return & ast.OptimizerHint {HintKind : "CheckConstraintsPlan" }, nil
1379+ }
1380+ return & ast.OptimizerHint {HintKind : "CheckConstraints" }, nil
1381+
1382+ case "LABEL" :
1383+ p .nextToken () // consume LABEL
1384+ if p .curTok .Type == TokenEquals {
1385+ p .nextToken () // consume =
1386+ value , err := p .parseScalarExpression ()
1387+ if err != nil {
1388+ return nil , err
1389+ }
1390+ return & ast.LiteralOptimizerHint {HintKind : "Label" , Value : value }, nil
1391+ }
1392+ return & ast.OptimizerHint {HintKind : "Label" }, nil
1393+
1394+ case "MAX_GRANT_PERCENT" :
1395+ p .nextToken () // consume MAX_GRANT_PERCENT
1396+ if p .curTok .Type == TokenEquals {
1397+ p .nextToken () // consume =
1398+ value , err := p .parseScalarExpression ()
1399+ if err != nil {
1400+ return nil , err
1401+ }
1402+ return & ast.LiteralOptimizerHint {HintKind : "MaxGrantPercent" , Value : value }, nil
1403+ }
1404+ return & ast.OptimizerHint {HintKind : "MaxGrantPercent" }, nil
1405+
1406+ case "MIN_GRANT_PERCENT" :
1407+ p .nextToken () // consume MIN_GRANT_PERCENT
1408+ if p .curTok .Type == TokenEquals {
1409+ p .nextToken () // consume =
1410+ value , err := p .parseScalarExpression ()
1411+ if err != nil {
1412+ return nil , err
1413+ }
1414+ return & ast.LiteralOptimizerHint {HintKind : "MinGrantPercent" , Value : value }, nil
1415+ }
1416+ return & ast.OptimizerHint {HintKind : "MinGrantPercent" }, nil
1417+
1418+ case "FAST" :
1419+ p .nextToken () // consume FAST
1420+ // FAST can take a numeric argument
1421+ if p .curTok .Type == TokenNumber {
1422+ value , err := p .parseScalarExpression ()
1423+ if err != nil {
1424+ return nil , err
1425+ }
1426+ return & ast.LiteralOptimizerHint {HintKind : "Fast" , Value : value }, nil
1427+ }
1428+ return & ast.OptimizerHint {HintKind : "Fast" }, nil
1429+
1430+ default :
1431+ // Handle generic hints
1432+ hintKind := convertHintKind (p .curTok .Literal )
1433+ p .nextToken ()
1434+
1435+ // Check if this is a literal hint (LABEL = value, etc.)
1436+ if p .curTok .Type == TokenEquals {
1437+ p .nextToken () // consume =
1438+ value , err := p .parseScalarExpression ()
1439+ if err != nil {
1440+ return nil , err
1441+ }
1442+ return & ast.LiteralOptimizerHint {HintKind : hintKind , Value : value }, nil
1443+ }
1444+ return & ast.OptimizerHint {HintKind : hintKind }, nil
1445+ }
1446+ }
1447+
1448+ func (p * Parser ) parseOptimizeForHint () (ast.OptimizerHintBase , error ) {
1449+ hint := & ast.OptimizeForOptimizerHint {
1450+ HintKind : "OptimizeFor" ,
1451+ IsForUnknown : false ,
1452+ }
1453+
1454+ // Check for UNKNOWN
1455+ if p .curTok .Type == TokenIdent && strings .ToUpper (p .curTok .Literal ) == "UNKNOWN" {
1456+ p .nextToken ()
1457+ hint .IsForUnknown = true
1458+ return hint , nil
1459+ }
1460+
1461+ // Expect (
1462+ if p .curTok .Type != TokenLParen {
1463+ return nil , fmt .Errorf ("expected ( after OPTIMIZE FOR, got %s" , p .curTok .Literal )
1464+ }
1465+ p .nextToken ()
1466+
1467+ // Parse variable-value pairs
1468+ for p .curTok .Type != TokenRParen && p .curTok .Type != TokenEOF {
1469+ if p .curTok .Type == TokenComma {
13071470 p .nextToken ()
1471+ continue
1472+ }
1473+
1474+ pair , err := p .parseVariableValuePair ()
1475+ if err != nil {
1476+ return nil , err
1477+ }
1478+ if pair != nil {
1479+ hint .Pairs = append (hint .Pairs , pair )
13081480 }
13091481 }
13101482
@@ -1313,7 +1485,43 @@ func (p *Parser) parseOptionClause() ([]ast.OptimizerHintBase, error) {
13131485 p .nextToken ()
13141486 }
13151487
1316- return hints , nil
1488+ return hint , nil
1489+ }
1490+
1491+ func (p * Parser ) parseVariableValuePair () (* ast.VariableValuePair , error ) {
1492+ // Expect @variable (variables are TokenIdent starting with @)
1493+ if p .curTok .Type != TokenIdent || ! strings .HasPrefix (p .curTok .Literal , "@" ) {
1494+ return nil , nil
1495+ }
1496+
1497+ pair := & ast.VariableValuePair {
1498+ Variable : & ast.VariableReference {
1499+ Name : p .curTok .Literal ,
1500+ },
1501+ IsForUnknown : false ,
1502+ }
1503+ p .nextToken ()
1504+
1505+ // Expect =
1506+ if p .curTok .Type != TokenEquals {
1507+ // Could be UNKNOWN
1508+ if p .curTok .Type == TokenIdent && strings .ToUpper (p .curTok .Literal ) == "UNKNOWN" {
1509+ p .nextToken ()
1510+ pair .IsForUnknown = true
1511+ return pair , nil
1512+ }
1513+ return nil , fmt .Errorf ("expected = after variable, got %s" , p .curTok .Literal )
1514+ }
1515+ p .nextToken () // consume =
1516+
1517+ // Parse the value
1518+ value , err := p .parseScalarExpression ()
1519+ if err != nil {
1520+ return nil , err
1521+ }
1522+ pair .Value = value
1523+
1524+ return pair , nil
13171525}
13181526
13191527// convertHintKind converts hint identifiers to their canonical names
0 commit comments