diff --git a/internal/parser/planparserv2/pattern_match.go b/internal/parser/planparserv2/pattern_match.go index bec94a8f20..f524e4b369 100644 --- a/internal/parser/planparserv2/pattern_match.go +++ b/internal/parser/planparserv2/pattern_match.go @@ -1,6 +1,8 @@ package planparserv2 import ( + "strings" + "github.com/milvus-io/milvus/pkg/v2/proto/planpb" ) @@ -12,20 +14,27 @@ var wildcards = map[byte]struct{}{ var escapeCharacter byte = '\\' // hasWildcards returns true if pattern contains any wildcard. -func hasWildcards(pattern string) bool { - l := len(pattern) - i := l - 1 - for ; i >= 0; i-- { - _, ok := wildcards[pattern[i]] - if ok { - if i > 0 && pattern[i-1] == escapeCharacter { - i-- +func hasWildcards(pattern string) (string, bool) { + var result strings.Builder + hasWildcard := false + + for i := 0; i < len(pattern); i++ { + if pattern[i] == escapeCharacter && i+1 < len(pattern) { + next := pattern[i+1] + if _, ok := wildcards[next]; ok { + result.WriteByte(next) + i++ continue } - return true } + + if _, ok := wildcards[pattern[i]]; ok { + hasWildcard = true + } + result.WriteByte(pattern[i]) } - return false + + return result.String(), hasWildcard } // findLastNotOfWildcards find the last location not of last wildcard. @@ -55,14 +64,14 @@ func translatePatternMatch(pattern string) (op planpb.OpType, operand string, er return planpb.OpType_PrefixMatch, "", nil } - exist := hasWildcards(pattern[:loc+1]) + newPattern, exist := hasWildcards(pattern[:loc+1]) if loc >= l-1 && !exist { // equal match. - return planpb.OpType_Equal, pattern, nil + return planpb.OpType_Equal, newPattern, nil } if !exist { // prefix match. - return planpb.OpType_PrefixMatch, pattern[:loc+1], nil + return planpb.OpType_PrefixMatch, newPattern, nil } return planpb.OpType_Match, pattern, nil diff --git a/internal/parser/planparserv2/pattern_match_test.go b/internal/parser/planparserv2/pattern_match_test.go index 88380bdd14..d39a4b6bca 100644 --- a/internal/parser/planparserv2/pattern_match_test.go +++ b/internal/parser/planparserv2/pattern_match_test.go @@ -11,38 +11,44 @@ func Test_hasWildcards(t *testing.T) { pattern string } tests := []struct { - name string - args args - want bool + name string + args args + want bool + target string }{ { args: args{ pattern: "no-wildcards", }, - want: false, + want: false, + target: "no-wildcards", }, { args: args{ pattern: "has\\%", }, - want: false, + want: false, + target: "has%", }, { args: args{ pattern: "%", }, - want: true, + want: true, + target: "%", }, { args: args{ pattern: "has%", }, - want: true, + want: true, + target: "has%", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := hasWildcards(tt.args.pattern); got != tt.want { + patten, got := hasWildcards(tt.args.pattern) + if got != tt.want || patten != tt.target { t.Errorf("hasWildcards(%s) = %v, want %v", tt.args.pattern, got, tt.want) } }) diff --git a/internal/parser/planparserv2/plan_parser_v2_test.go b/internal/parser/planparserv2/plan_parser_v2_test.go index d2a368f38b..b1a18430b3 100644 --- a/internal/parser/planparserv2/plan_parser_v2_test.go +++ b/internal/parser/planparserv2/plan_parser_v2_test.go @@ -204,25 +204,44 @@ func TestExpr_Like(t *testing.T) { helper, err := typeutil.CreateSchemaHelper(schema) assert.NoError(t, err) - exprStrs := []string{ - `VarCharField like "prefix%"`, - `VarCharField like "equal"`, - `JSONField["A"] like "name*"`, - `$meta["A"] like "name*"`, - } - for _, exprStr := range exprStrs { - assertValidExpr(t, helper, exprStr) - } + expr := `A like "8\\_0%"` + plan, err := CreateSearchPlan(helper, expr, "FloatVectorField", &planpb.QueryInfo{ + Topk: 0, + MetricType: "", + SearchParams: "", + RoundDecimal: 0, + }, nil) + assert.NoError(t, err, expr) + assert.NotNil(t, plan) + fmt.Println(plan) + assert.Equal(t, planpb.OpType_PrefixMatch, plan.GetVectorAnns().GetPredicates().GetUnaryRangeExpr().GetOp()) + assert.Equal(t, "8_0", plan.GetVectorAnns().GetPredicates().GetUnaryRangeExpr().GetValue().GetStringVal()) - // TODO: enable these after regex-match is supported. - //unsupported := []string{ - // `VarCharField like "not_%_supported"`, - // `JSONField["A"] like "not_%_supported"`, - // `$meta["A"] like "not_%_supported"`, - //} - //for _, exprStr := range unsupported { - // assertInvalidExpr(t, helper, exprStr) - //} + expr = `A like "8_\\_0%"` + plan, err = CreateSearchPlan(helper, expr, "FloatVectorField", &planpb.QueryInfo{ + Topk: 0, + MetricType: "", + SearchParams: "", + RoundDecimal: 0, + }, nil) + assert.NoError(t, err, expr) + assert.NotNil(t, plan) + fmt.Println(plan) + assert.Equal(t, planpb.OpType_Match, plan.GetVectorAnns().GetPredicates().GetUnaryRangeExpr().GetOp()) + assert.Equal(t, `8_\_0%`, plan.GetVectorAnns().GetPredicates().GetUnaryRangeExpr().GetValue().GetStringVal()) + + expr = `A like "8\\%-0%"` + plan, err = CreateSearchPlan(helper, expr, "FloatVectorField", &planpb.QueryInfo{ + Topk: 0, + MetricType: "", + SearchParams: "", + RoundDecimal: 0, + }, nil) + assert.NoError(t, err, expr) + assert.NotNil(t, plan) + fmt.Println(plan) + assert.Equal(t, planpb.OpType_PrefixMatch, plan.GetVectorAnns().GetPredicates().GetUnaryRangeExpr().GetOp()) + assert.Equal(t, `8%-0`, plan.GetVectorAnns().GetPredicates().GetUnaryRangeExpr().GetValue().GetStringVal()) } func TestExpr_TextMatch(t *testing.T) {