From d9f8e38d6a84f24b5a5d48a6a8e58d1d7ea95343 Mon Sep 17 00:00:00 2001 From: Chun Han <116052805+MrPresent-Han@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:59:12 +0800 Subject: [PATCH] fix: query failed for int value on edge(#46075) (#46126) related: #46075 Signed-off-by: MrPresent-Han Co-authored-by: MrPresent-Han --- .../parser/planparserv2/parser_visitor.go | 44 +++++++++++++++ .../planparserv2/plan_parser_v2_test.go | 5 +- internal/proxy/task_query_test.go | 56 +++++++++++++++++++ pkg/util/typeutil/schema.go | 4 +- 4 files changed, 107 insertions(+), 2 deletions(-) diff --git a/internal/parser/planparserv2/parser_visitor.go b/internal/parser/planparserv2/parser_visitor.go index 3fee45db03..6265fb6f19 100644 --- a/internal/parser/planparserv2/parser_visitor.go +++ b/internal/parser/planparserv2/parser_visitor.go @@ -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 } diff --git a/internal/parser/planparserv2/plan_parser_v2_test.go b/internal/parser/planparserv2/plan_parser_v2_test.go index 74148eb11e..490f59565c 100644 --- a/internal/parser/planparserv2/plan_parser_v2_test.go +++ b/internal/parser/planparserv2/plan_parser_v2_test.go @@ -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 diff --git a/internal/proxy/task_query_test.go b/internal/proxy/task_query_test.go index f288352698..49950d5b5a 100644 --- a/internal/proxy/task_query_test.go +++ b/internal/proxy/task_query_test.go @@ -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) + }) }) } diff --git a/pkg/util/typeutil/schema.go b/pkg/util/typeutil/schema.go index 0b85ea553d..465ed5c430 100644 --- a/pkg/util/typeutil/schema.go +++ b/pkg/util/typeutil/schema.go @@ -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 }