Skip to content

Commit 3f84a69

Browse files
authored
Merge branch 'main' into claude/check-missing-ast-json-p2W5R
2 parents 95ca314 + df8b3b5 commit 3f84a69

File tree

6 files changed

+279
-20
lines changed

6 files changed

+279
-20
lines changed

ast/optimize_for_optimizer_hint.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package ast
2+
3+
// OptimizeForOptimizerHint represents an OPTIMIZE FOR hint.
4+
type OptimizeForOptimizerHint struct {
5+
Pairs []*VariableValuePair `json:"Pairs,omitempty"`
6+
IsForUnknown bool `json:"IsForUnknown,omitempty"`
7+
HintKind string `json:"HintKind,omitempty"`
8+
}
9+
10+
func (*OptimizeForOptimizerHint) node() {}
11+
func (*OptimizeForOptimizerHint) optimizerHint() {}

ast/variable_value_pair.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package ast
2+
3+
// VariableValuePair represents a variable-value pair in an OPTIMIZE FOR hint.
4+
type VariableValuePair struct {
5+
Variable *VariableReference `json:"Variable,omitempty"`
6+
Value ScalarExpression `json:"Value,omitempty"`
7+
IsForUnknown bool `json:"IsForUnknown,omitempty"`
8+
}
9+
10+
func (*VariableValuePair) node() {}

parser/marshal.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,11 +941,41 @@ func optimizerHintToJSON(h ast.OptimizerHintBase) jsonNode {
941941
node["HintKind"] = hint.HintKind
942942
}
943943
return node
944+
case *ast.OptimizeForOptimizerHint:
945+
node := jsonNode{
946+
"$type": "OptimizeForOptimizerHint",
947+
}
948+
if len(hint.Pairs) > 0 {
949+
pairs := make([]jsonNode, len(hint.Pairs))
950+
for i, pair := range hint.Pairs {
951+
pairs[i] = variableValuePairToJSON(pair)
952+
}
953+
node["Pairs"] = pairs
954+
}
955+
node["IsForUnknown"] = hint.IsForUnknown
956+
if hint.HintKind != "" {
957+
node["HintKind"] = hint.HintKind
958+
}
959+
return node
944960
default:
945961
return jsonNode{"$type": "UnknownOptimizerHint"}
946962
}
947963
}
948964

965+
func variableValuePairToJSON(p *ast.VariableValuePair) jsonNode {
966+
node := jsonNode{
967+
"$type": "VariableValuePair",
968+
}
969+
if p.Variable != nil {
970+
node["Variable"] = scalarExpressionToJSON(p.Variable)
971+
}
972+
if p.Value != nil {
973+
node["Value"] = scalarExpressionToJSON(p.Value)
974+
}
975+
node["IsForUnknown"] = p.IsForUnknown
976+
return node
977+
}
978+
949979
func queryExpressionToJSON(qe ast.QueryExpression) jsonNode {
950980
switch q := qe.(type) {
951981
case *ast.QuerySpecification:

parser/parse_select.go

Lines changed: 226 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -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
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"skip": true}
1+
{"skip": false}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"skip": true}
1+
{"skip": false}

0 commit comments

Comments
 (0)