This commit is contained in:
Aditya Tiwari 2026-02-03 19:19:28 +05:30 committed by GitHub
commit e0e4aa401a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 140 additions and 0 deletions

View file

@ -599,6 +599,13 @@ matrix_selector : expr LEFT_BRACKET positive_duration_expr RIGHT_BRACKET
subquery_expr : expr LEFT_BRACKET positive_duration_expr COLON positive_duration_expr RIGHT_BRACKET
{
// Check if offset/@ modifiers are immediately before the subquery brackets.
hasModifiers, errMsg := yylex.(*parser).hasModifiersBeforeRange($1.(Expr), $2.Pos)
if hasModifiers {
errRange := mergeRanges(&$2, &$6)
yylex.(*parser).addParseErrf(errRange, "%s", errMsg)
}
var rangeNl time.Duration
var stepNl time.Duration
if numLit, ok := $3.(*NumberLiteral); ok {
@ -620,6 +627,13 @@ subquery_expr : expr LEFT_BRACKET positive_duration_expr COLON positive_durati
}
| expr LEFT_BRACKET positive_duration_expr COLON RIGHT_BRACKET
{
// Check if offset/@ modifiers are immediately before the subquery brackets.
hasModifiers, errMsg := yylex.(*parser).hasModifiersBeforeRange($1.(Expr), $2.Pos)
if hasModifiers {
errRange := mergeRanges(&$2, &$5)
yylex.(*parser).addParseErrf(errRange, "%s", errMsg)
}
var rangeNl time.Duration
if numLit, ok := $3.(*NumberLiteral); ok {
rangeNl = time.Duration(math.Round(numLit.Val*float64(time.Second)))

View file

@ -1594,6 +1594,13 @@ yydefault:
case 90:
yyDollar = yyS[yypt-6 : yypt+1]
{
// Check if offset/@ modifiers are immediately before the subquery brackets.
hasModifiers, errMsg := yylex.(*parser).hasModifiersBeforeRange(yyDollar[1].node.(Expr), yyDollar[2].item.Pos)
if hasModifiers {
errRange := mergeRanges(&yyDollar[2].item, &yyDollar[6].item)
yylex.(*parser).addParseErrf(errRange, "%s", errMsg)
}
var rangeNl time.Duration
var stepNl time.Duration
if numLit, ok := yyDollar[3].node.(*NumberLiteral); ok {
@ -1616,6 +1623,13 @@ yydefault:
case 91:
yyDollar = yyS[yypt-5 : yypt+1]
{
// Check if offset/@ modifiers are immediately before the subquery brackets.
hasModifiers, errMsg := yylex.(*parser).hasModifiersBeforeRange(yyDollar[1].node.(Expr), yyDollar[2].item.Pos)
if hasModifiers {
errRange := mergeRanges(&yyDollar[2].item, &yyDollar[5].item)
yylex.(*parser).addParseErrf(errRange, "%s", errMsg)
}
var rangeNl time.Duration
if numLit, ok := yyDollar[3].node.(*NumberLiteral); ok {
rangeNl = time.Duration(math.Round(numLit.Val * float64(time.Second)))

View file

@ -1191,6 +1191,49 @@ func (p *parser) getAtModifierVars(e Node) (**int64, *ItemType, *posrange.Pos, b
return timestampp, preprocp, endPosp, true
}
// hasModifiersBeforeRange checks if an expression has offset/@ modifiers that are
// immediately adjacent to a bracket (no space), which should be rejected for subqueries.
func (*parser) hasModifiersBeforeRange(expr Expr, bracketPos posrange.Pos) (bool, string) {
var endPos posrange.Pos
var hasOffset bool
var hasAtModifier bool
switch e := expr.(type) {
case *VectorSelector:
endPos = e.PosRange.End
hasOffset = e.OriginalOffset != 0 || e.OriginalOffsetExpr != nil
hasAtModifier = e.Timestamp != nil
case *MatrixSelector:
vs, ok := e.VectorSelector.(*VectorSelector)
if !ok {
return false, ""
}
endPos = e.EndPos
hasOffset = vs.OriginalOffset != 0 || vs.OriginalOffsetExpr != nil
hasAtModifier = vs.Timestamp != nil
case *SubqueryExpr:
endPos = e.EndPos
hasOffset = e.OriginalOffset != 0 || e.OriginalOffsetExpr != nil
hasAtModifier = e.Timestamp != nil
default:
// Other expression types (function calls, binary ops, etc.) don't have modifiers
// at the selector level, so they're fine.
return false, ""
}
// Check if modifiers exist and are adjacent to the bracket (no space)
if endPos == bracketPos {
if hasOffset {
return true, "no offset modifiers allowed before range"
}
if hasAtModifier {
return true, "no @ modifiers allowed before range"
}
}
return false, ""
}
func (p *parser) experimentalDurationExpr(e Expr) {
if !ExperimentalDurationExpr {
p.addParseErrf(e.PositionRange(), "experimental duration expression is not enabled")

View file

@ -2779,6 +2779,75 @@ var testExpr = []struct {
},
},
},
// Test subquery: offset/@ modifiers immediately before subquery brackets (should error)
{
input: `some_metric offset 5m[2m:10s]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 21, End: 29},
Err: errors.New("no offset modifiers allowed before range"),
Query: `some_metric offset 5m[2m:10s]`,
},
},
},
{
input: `some_metric @ 123[2m:10s]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 17, End: 25},
Err: errors.New("no @ modifiers allowed before range"),
Query: `some_metric @ 123[2m:10s]`,
},
},
},
// Test subquery: matrix selector with offset/@ before subquery (should error)
{
input: `some_metric[5m] offset 1m[2m:10s]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 25, End: 33},
Err: errors.New("no offset modifiers allowed before range"),
Query: `some_metric[5m] offset 1m[2m:10s]`,
},
},
},
{
input: `some_metric[5m] @ 123[2m:10s]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 21, End: 29},
Err: errors.New("no @ modifiers allowed before range"),
Query: `some_metric[5m] @ 123[2m:10s]`,
},
},
},
// Test subquery: nested subquery with offset/@ before outer subquery (should error)
{
input: `some_metric[2m:10s] offset 1m[5m:1m]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 29, End: 36},
Err: errors.New("no offset modifiers allowed before range"),
Query: `some_metric[2m:10s] offset 1m[5m:1m]`,
},
},
},
{
input: `some_metric[2m:10s] @ 123[5m:1m]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 25, End: 32},
Err: errors.New("no @ modifiers allowed before range"),
Query: `some_metric[2m:10s] @ 123[5m:1m]`,
},
},
},
{
input: `(foo + bar)[5m]`,
fail: true,