mirror of
https://gitee.com/milvus-io/milvus.git
synced 2025-12-07 09:38:39 +08:00
fix: Fix bug where prefix matching fails when wildcards are in prefix (#40020)
issue: #40019 --------- Signed-off-by: Cai Zhang <cai.zhang@zilliz.com>
This commit is contained in:
parent
762a644d76
commit
dc46b08bdf
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user