milvus/internal/parser/planparserv2/pattern_match_test.go
Jiquan Long e549148a19
enhance: full-support for wildcard pattern matching (#30288)
issue: #29988 
This pr adds full-support for wildcard pattern matching from end to end.
Before this pr, the users can only use prefix match in their expression,
for example, "like 'prefix%'". With this pr, more flexible syntax can be
combined.

To do so, this pr makes these changes:
- 1. support regex query both on index and raw data;
- 2. translate the pattern matching to regex query, so that it can be
handled by the regex query logic;
- 3. loose the limit of the expression parsing, which allows general
pattern matching syntax;

With the support of regex query in segcore backend, we can also add
mysql-like `REGEXP` syntax later easily.

---------

Signed-off-by: longjiquan <jiquan.long@zilliz.com>
2024-02-01 12:37:04 +08:00

141 lines
2.6 KiB
Go

package planparserv2
import (
"testing"
"github.com/milvus-io/milvus/internal/proto/planpb"
)
func Test_hasWildcards(t *testing.T) {
type args struct {
pattern string
}
tests := []struct {
name string
args args
want bool
}{
{
args: args{
pattern: "no-wildcards",
},
want: false,
},
{
args: args{
pattern: "has\\%",
},
want: false,
},
{
args: args{
pattern: "%",
},
want: true,
},
{
args: args{
pattern: "has%",
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := hasWildcards(tt.args.pattern); got != tt.want {
t.Errorf("hasWildcards(%s) = %v, want %v", tt.args.pattern, got, tt.want)
}
})
}
}
func Test_findLocOfLastWildcard(t *testing.T) {
type args struct {
pattern string
}
tests := []struct {
name string
args args
want int
}{
{
args: args{
pattern: "no-wildcards",
},
want: 11,
},
{
args: args{
pattern: "only\\%",
},
want: 5,
},
{
args: args{
pattern: "prefix%%",
},
want: 5,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := findLastNotOfWildcards(tt.args.pattern); got != tt.want {
t.Errorf("findLastNotOfWildcards(%s) = %v, want %v", tt.args.pattern, got, tt.want)
}
})
}
}
func Test_translatePatternMatch(t *testing.T) {
type args struct {
pattern string
}
tests := []struct {
name string
args args
wantOp planpb.OpType
wantOperand string
wantErr bool
}{
{
args: args{pattern: "prefix%"},
wantOp: planpb.OpType_PrefixMatch,
wantOperand: "prefix",
wantErr: false,
},
{
args: args{pattern: "equal"},
wantOp: planpb.OpType_Equal,
wantOperand: "equal",
wantErr: false,
},
{
args: args{pattern: "%%%%%%"},
wantOp: planpb.OpType_PrefixMatch,
wantOperand: "",
wantErr: false,
},
{
args: args{pattern: "prefix%suffix"},
wantOp: planpb.OpType_Match,
wantOperand: "prefix%suffix",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotOp, gotOperand, err := translatePatternMatch(tt.args.pattern)
if (err != nil) != tt.wantErr {
t.Errorf("translatePatternMatch(%s) error = %v, wantErr %v", tt.args.pattern, err, tt.wantErr)
return
}
if gotOp != tt.wantOp {
t.Errorf("translatePatternMatch(%s) gotOp = %v, want %v", tt.args.pattern, gotOp, tt.wantOp)
}
if gotOperand != tt.wantOperand {
t.Errorf("translatePatternMatch(%s) gotOperand = %v, want %v", tt.args.pattern, gotOperand, tt.wantOperand)
}
})
}
}