fix: query failed for int value on edge(#46075) (#46126)

related: #46075

Signed-off-by: MrPresent-Han <chun.han@gmail.com>
Co-authored-by: MrPresent-Han <chun.han@gmail.com>
This commit is contained in:
Chun Han 2025-12-10 15:59:12 +08:00 committed by GitHub
parent ab2e51b1c7
commit d9f8e38d6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 107 additions and 2 deletions

View File

@ -20,6 +20,24 @@ type ParserVisitorArgs struct {
Timezone string
}
// int64OverflowError is a special error type used to handle the case where
// 9223372036854775808 (which exceeds int64 max) is used with unary minus
// to represent -9223372036854775808 (int64 minimum value).
// This happens because ANTLR parses -9223372036854775808 as Unary(SUB, Integer(9223372036854775808)),
// causing the integer literal to exceed int64 range before the unary minus is applied.
type int64OverflowError struct {
literal string
}
func (e *int64OverflowError) Error() string {
return fmt.Sprintf("int64 overflow: %s", e.literal)
}
func isInt64OverflowError(err error) bool {
_, ok := err.(*int64OverflowError)
return ok
}
type ParserVisitor struct {
parser.BasePlanVisitor
schema *typeutil.SchemaHelper
@ -108,6 +126,15 @@ func (v *ParserVisitor) VisitInteger(ctx *parser.IntegerContext) interface{} {
literal := ctx.IntegerConstant().GetText()
i, err := strconv.ParseInt(literal, 0, 64)
if err != nil {
// Special case: 9223372036854775808 is out of int64 range,
// but -9223372036854775808 is valid (int64 minimum value).
// This happens because ANTLR parses -9223372036854775808 as:
// Unary(SUB, Integer(9223372036854775808))
// The integer literal 9223372036854775808 exceeds int64 max (9223372036854775807)
// before the unary minus is applied. We handle this in VisitUnary.
if literal == "9223372036854775808" {
return &int64OverflowError{literal: literal}
}
return err
}
return &ExprWithType{
@ -950,6 +977,23 @@ func (v *ParserVisitor) VisitReverseRange(ctx *parser.ReverseRangeContext) inter
func (v *ParserVisitor) VisitUnary(ctx *parser.UnaryContext) interface{} {
child := ctx.Expr().Accept(v)
if err := getError(child); err != nil {
// Special case: handle -9223372036854775808
// ANTLR parses -9223372036854775808 as Unary(SUB, Integer(9223372036854775808)).
// The integer literal 9223372036854775808 exceeds int64 max, but when combined
// with unary minus, it represents the valid int64 minimum value.
if isInt64OverflowError(err) && ctx.GetOp().GetTokenType() == parser.PlanParserSUB {
return &ExprWithType{
dataType: schemapb.DataType_Int64,
expr: &planpb.Expr{
Expr: &planpb.Expr_ValueExpr{
ValueExpr: &planpb.ValueExpr{
Value: NewInt(math.MinInt64),
},
},
},
nodeDependent: true,
}
}
return err
}

View File

@ -912,6 +912,9 @@ func TestCreateRetrievePlan(t *testing.T) {
schema := newTestSchemaHelper(t)
_, err := CreateRetrievePlan(schema, "Int64Field > 0", nil)
assert.NoError(t, err)
_, err = CreateRetrievePlan(schema, "id > -9223372036854775808", nil)
assert.NoError(t, err)
}
func TestCreateSearchPlan(t *testing.T) {
@ -1007,7 +1010,7 @@ func TestExpr_Invalid(t *testing.T) {
`"str" != false`,
`VarCharField != FloatField`,
`FloatField == VarCharField`,
`A == -9223372036854775808`,
`A == -9223372036854775809`,
// ---------------------- relational --------------------
//`not_in_schema < 1`, // maybe in json
//`1 <= not_in_schema`, // maybe in json

View File

@ -18,6 +18,7 @@ package proxy
import (
"context"
"fmt"
"math"
"testing"
"time"
@ -840,6 +841,61 @@ func TestTaskQuery_functions(t *testing.T) {
assert.Equal(t, []int64{11}, result.GetFieldsData()[0].GetScalars().GetLongData().Data)
})
})
t.Run("test SelectMinPK with pk equals MaxInt64 and firstInt true", func(t *testing.T) {
// Test case to cover pk == minIntPK && firstInt condition in SelectMinPK
// When the first int64 PK equals math.MaxInt64 (which equals initial minIntPK),
// and firstInt is true, the condition firstInt || pk < minIntPK evaluates to true
// This ensures the branch is taken even when pk == minIntPK
const (
Dim = 8
Int64FieldName = "Int64Field"
FloatVectorFieldName = "FloatVectorField"
Int64FieldID = common.StartOfUserFieldID + 1
FloatVectorFieldID = common.StartOfUserFieldID + 2
)
maxInt64PK := int64(math.MaxInt64)
FloatVector := []float32{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}
fieldDataMaxPK := getFieldData(Int64FieldName, Int64FieldID, schemapb.DataType_Int64, []int64{maxInt64PK}, 1)
vectorDataMaxPK := getFieldData(FloatVectorFieldName, FloatVectorFieldID, schemapb.DataType_FloatVector, FloatVector[0:8], Dim)
// Create result with MaxInt64 as the first PK to trigger pk == minIntPK && firstInt condition
// Only the first result has data with pk = maxInt64PK
rMaxPK := &internalpb.RetrieveResults{
Ids: &schemapb.IDs{
IdField: &schemapb.IDs_IntId{
IntId: &schemapb.LongArray{
Data: []int64{maxInt64PK},
},
},
},
FieldsData: []*schemapb.FieldData{fieldDataMaxPK, vectorDataMaxPK},
HasMoreResult: false,
}
// Create a second result with no data (empty result)
rEmpty := &internalpb.RetrieveResults{
Ids: &schemapb.IDs{
IdField: &schemapb.IDs_IntId{
IntId: &schemapb.LongArray{
Data: []int64{},
},
},
},
FieldsData: []*schemapb.FieldData{},
HasMoreResult: false,
}
// Test with MaxInt64 first to ensure it's handled correctly when pk == minIntPK && firstInt
// The reduced result should include the maxInt64PK result
result, err := reduceRetrieveResults(context.Background(),
[]*internalpb.RetrieveResults{rMaxPK, rEmpty},
&queryParams{limit: typeutil.Unlimited})
assert.NoError(t, err)
assert.Equal(t, 2, len(result.GetFieldsData()))
// Should include the maxInt64PK result
assert.Equal(t, []int64{maxInt64PK}, result.GetFieldsData()[0].GetScalars().GetLongData().Data)
})
})
}

View File

@ -2045,6 +2045,7 @@ func SelectMinPK[T ResultWithID](results []T, cursors []int64) (int, bool) {
minIntPK int64 = math.MaxInt64
firstStr = true
firstInt = true
minStrPK string
)
for i, cursor := range cursors {
@ -2064,7 +2065,8 @@ func SelectMinPK[T ResultWithID](results []T, cursors []int64) (int, bool) {
sel = i
}
case int64:
if pk < minIntPK {
if firstInt || pk < minIntPK {
firstInt = false
minIntPK = pk
sel = i
}