Allow for multiple MATCH patterns

Each MATCH is inclusive OR, thus

    WITHIN fleet MATCH train* truck* BOUNDS 33 -112 34 -113

will find all trains and trucks that within the provides bounds.
This commit is contained in:
tidwall 2022-09-01 19:43:30 -07:00
parent d2953307a0
commit f24c251ee6
8 changed files with 96 additions and 61 deletions

View File

@ -55,6 +55,20 @@ func objIsSpatial(obj geojson.Object) bool {
func hookJSONString(hookName string, metas []FenceMeta) string { func hookJSONString(hookName string, metas []FenceMeta) string {
return string(appendHookDetails(nil, hookName, metas)) return string(appendHookDetails(nil, hookName, metas))
} }
func multiGlobMatch(globs []string, s string) bool {
if len(globs) == 0 || (len(globs) == 1 && globs[0] == "*") {
return true
}
for _, pattern := range globs {
match, _ := glob.Match(pattern, s)
if match {
return true
}
}
return false
}
func fenceMatch( func fenceMatch(
hookName string, sw *scanWriter, fence *liveFenceSwitches, hookName string, sw *scanWriter, fence *liveFenceSwitches,
metas []FenceMeta, details *commandDetails, metas []FenceMeta, details *commandDetails,
@ -66,11 +80,8 @@ func fenceMatch(
`,"time":` + jsonTimeFormat(details.timestamp) + `}`, `,"time":` + jsonTimeFormat(details.timestamp) + `}`,
} }
} }
if len(fence.glob) > 0 && !(len(fence.glob) == 1 && fence.glob[0] == '*') { if !multiGlobMatch(fence.globs, details.id) {
match, _ := glob.Match(fence.glob, details.id) return nil
if !match {
return nil
}
} }
if details.obj == nil || !objIsSpatial(details.obj) { if details.obj == nil || !objIsSpatial(details.obj) {
return nil return nil

View File

@ -143,7 +143,7 @@ func (s *Server) cmdSetHook(msg *Message) (
} }
var wr bytes.Buffer var wr bytes.Buffer
hook.ScanWriter, err = s.newScanWriter( hook.ScanWriter, err = s.newScanWriter(
&wr, cmsg, args.key, args.output, args.precision, args.glob, false, &wr, cmsg, args.key, args.output, args.precision, args.globs, false,
args.cursor, args.limit, args.wheres, args.whereins, args.whereevals, args.cursor, args.limit, args.wheres, args.whereins, args.whereevals,
args.nofields) args.nofields)
if err != nil { if err != nil {

View File

@ -14,7 +14,7 @@ import (
type liveBuffer struct { type liveBuffer struct {
key string key string
glob string globs []string
fence *liveFenceSwitches fence *liveFenceSwitches
details []*commandDetails details []*commandDetails
cond *sync.Cond cond *sync.Cond
@ -101,12 +101,12 @@ func (s *Server) goLive(
var sw *scanWriter var sw *scanWriter
var wr bytes.Buffer var wr bytes.Buffer
lfs := inerr.(liveFenceSwitches) lfs := inerr.(liveFenceSwitches)
lb.glob = lfs.glob lb.globs = lfs.globs
lb.key = lfs.key lb.key = lfs.key
lb.fence = &lfs lb.fence = &lfs
s.mu.RLock() s.mu.RLock()
sw, err = s.newScanWriter( sw, err = s.newScanWriter(
&wr, msg, lfs.key, lfs.output, lfs.precision, lfs.glob, false, &wr, msg, lfs.key, lfs.output, lfs.precision, lfs.globs, false,
lfs.cursor, lfs.limit, lfs.wheres, lfs.whereins, lfs.whereevals, lfs.nofields) lfs.cursor, lfs.limit, lfs.wheres, lfs.whereins, lfs.whereevals, lfs.nofields)
s.mu.RUnlock() s.mu.RUnlock()

View File

@ -7,7 +7,6 @@ import (
"github.com/tidwall/geojson" "github.com/tidwall/geojson"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/glob"
) )
func (s *Server) cmdScanArgs(vs []string) ( func (s *Server) cmdScanArgs(vs []string) (
@ -46,7 +45,7 @@ func (s *Server) cmdScan(msg *Message) (res resp.Value, err error) {
} }
wr := &bytes.Buffer{} wr := &bytes.Buffer{}
sw, err := s.newScanWriter( sw, err := s.newScanWriter(
wr, msg, args.key, args.output, args.precision, args.glob, false, wr, msg, args.key, args.output, args.precision, args.globs, false,
args.cursor, args.limit, args.wheres, args.whereins, args.whereevals, args.cursor, args.limit, args.wheres, args.whereins, args.whereevals,
args.nofields) args.nofields)
if err != nil { if err != nil {
@ -65,8 +64,8 @@ func (s *Server) cmdScan(msg *Message) (res resp.Value, err error) {
} }
sw.count = uint64(count) sw.count = uint64(count)
} else { } else {
g := glob.Parse(sw.globPattern, args.desc) limits := multiGlobParse(sw.globs, args.desc)
if g.Limits[0] == "" && g.Limits[1] == "" { if limits[0] == "" && limits[1] == "" {
sw.col.Scan(args.desc, sw, sw.col.Scan(args.desc, sw,
msg.Deadline, msg.Deadline,
func(id string, o geojson.Object, fields []float64) bool { func(id string, o geojson.Object, fields []float64) bool {
@ -78,7 +77,7 @@ func (s *Server) cmdScan(msg *Message) (res resp.Value, err error) {
}, },
) )
} else { } else {
sw.col.ScanRange(g.Limits[0], g.Limits[1], args.desc, sw, sw.col.ScanRange(limits[0], limits[1], args.desc, sw,
msg.Deadline, msg.Deadline,
func(id string, o geojson.Object, fields []float64) bool { func(id string, o geojson.Object, fields []float64) bool {
return sw.writeObject(ScanWriterParams{ return sw.writeObject(ScanWriterParams{

View File

@ -52,9 +52,8 @@ type scanWriter struct {
once bool once bool
count uint64 count uint64
precision uint64 precision uint64
globPattern string globs []string
globEverything bool globEverything bool
globSingle bool
fullFields bool fullFields bool
values []resp.Value values []resp.Value
matchValues bool matchValues bool
@ -79,7 +78,7 @@ type ScanWriterParams struct {
func (s *Server) newScanWriter( func (s *Server) newScanWriter(
wr *bytes.Buffer, msg *Message, key string, output outputT, wr *bytes.Buffer, msg *Message, key string, output outputT,
precision uint64, globPattern string, matchValues bool, precision uint64, globs []string, matchValues bool,
cursor, limit uint64, wheres []whereT, whereins []whereinT, cursor, limit uint64, wheres []whereT, whereins []whereinT,
whereevals []whereevalT, nofields bool, whereevals []whereevalT, nofields bool,
) ( ) (
@ -102,21 +101,18 @@ func (s *Server) newScanWriter(
wr: wr, wr: wr,
key: key, key: key,
msg: msg, msg: msg,
globs: globs,
limit: limit, limit: limit,
cursor: cursor, cursor: cursor,
output: output, output: output,
nofields: nofields, nofields: nofields,
precision: precision, precision: precision,
whereevals: whereevals, whereevals: whereevals,
globPattern: globPattern,
matchValues: matchValues, matchValues: matchValues,
} }
if globPattern == "*" || globPattern == "" {
if len(globs) == 0 || (len(globs) == 1 && globs[0] == "*") {
sw.globEverything = true sw.globEverything = true
} else {
if !glob.IsGlob(globPattern) {
sw.globSingle = true
}
} }
sw.orgWheres = wheres sw.orgWheres = wheres
sw.orgWhereins = whereins sw.orgWhereins = whereins
@ -344,25 +340,23 @@ func (sw *scanWriter) fieldMatch(fields []float64, o geojson.Object) (fvals []fl
} }
func (sw *scanWriter) globMatch(id string, o geojson.Object) (ok, keepGoing bool) { func (sw *scanWriter) globMatch(id string, o geojson.Object) (ok, keepGoing bool) {
if !sw.globEverything { if sw.globEverything {
if sw.globSingle { return true, true
if sw.globPattern != id { }
return false, true var val string
} if sw.matchValues {
return true, false val = o.String()
} } else {
var val string val = id
if sw.matchValues { }
val = o.String() for _, pattern := range sw.globs {
} else { ok, _ := glob.Match(pattern, val)
val = id if ok {
} return true, true
ok, _ := glob.Match(sw.globPattern, val)
if !ok {
return false, true
} }
} }
return true, true return false, true
} }
// Increment cursor // Increment cursor

View File

@ -488,7 +488,7 @@ func (s *Server) cmdNearby(msg *Message) (res resp.Value, err error) {
return NOMessage, sargs return NOMessage, sargs
} }
sw, err := s.newScanWriter( sw, err := s.newScanWriter(
wr, msg, sargs.key, sargs.output, sargs.precision, sargs.glob, false, wr, msg, sargs.key, sargs.output, sargs.precision, sargs.globs, false,
sargs.cursor, sargs.limit, sargs.wheres, sargs.whereins, sargs.whereevals, sargs.nofields) sargs.cursor, sargs.limit, sargs.wheres, sargs.whereins, sargs.whereevals, sargs.nofields)
if err != nil { if err != nil {
return NOMessage, err return NOMessage, err
@ -580,7 +580,7 @@ func (s *Server) cmdWithinOrIntersects(cmd string, msg *Message) (res resp.Value
return NOMessage, sargs return NOMessage, sargs
} }
sw, err := s.newScanWriter( sw, err := s.newScanWriter(
wr, msg, sargs.key, sargs.output, sargs.precision, sargs.glob, false, wr, msg, sargs.key, sargs.output, sargs.precision, sargs.globs, false,
sargs.cursor, sargs.limit, sargs.wheres, sargs.whereins, sargs.whereevals, sargs.nofields) sargs.cursor, sargs.limit, sargs.wheres, sargs.whereins, sargs.whereevals, sargs.nofields)
if err != nil { if err != nil {
return NOMessage, err return NOMessage, err
@ -644,6 +644,35 @@ func (s *Server) cmdSeachValuesArgs(vs []string) (
return return
} }
func multiGlobParse(globs []string, desc bool) [2]string {
var limits [2]string
for i, pattern := range globs {
g := glob.Parse(pattern, desc)
if g.Limits[0] == "" && g.Limits[1] == "" {
limits[0], limits[1] = "", ""
break
}
if i == 0 {
limits[0], limits[1] = g.Limits[0], g.Limits[1]
} else if desc {
if g.Limits[0] > limits[0] {
limits[0] = g.Limits[0]
}
if g.Limits[1] < limits[1] {
limits[1] = g.Limits[1]
}
} else {
if g.Limits[0] < limits[0] {
limits[0] = g.Limits[0]
}
if g.Limits[1] > limits[1] {
limits[1] = g.Limits[1]
}
}
}
return limits
}
func (s *Server) cmdSearch(msg *Message) (res resp.Value, err error) { func (s *Server) cmdSearch(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Args[1:] vs := msg.Args[1:]
@ -664,7 +693,7 @@ func (s *Server) cmdSearch(msg *Message) (res resp.Value, err error) {
return NOMessage, err return NOMessage, err
} }
sw, err := s.newScanWriter( sw, err := s.newScanWriter(
wr, msg, sargs.key, sargs.output, sargs.precision, sargs.glob, true, wr, msg, sargs.key, sargs.output, sargs.precision, sargs.globs, true,
sargs.cursor, sargs.limit, sargs.wheres, sargs.whereins, sargs.whereevals, sargs.nofields) sargs.cursor, sargs.limit, sargs.wheres, sargs.whereins, sargs.whereevals, sargs.nofields)
if err != nil { if err != nil {
return NOMessage, err return NOMessage, err
@ -681,8 +710,8 @@ func (s *Server) cmdSearch(msg *Message) (res resp.Value, err error) {
} }
sw.count = uint64(count) sw.count = uint64(count)
} else { } else {
g := glob.Parse(sw.globPattern, sargs.desc) limits := multiGlobParse(sw.globs, sargs.desc)
if g.Limits[0] == "" && g.Limits[1] == "" { if limits[0] == "" && limits[1] == "" {
sw.col.SearchValues(sargs.desc, sw, msg.Deadline, sw.col.SearchValues(sargs.desc, sw, msg.Deadline,
func(id string, o geojson.Object, fields []float64) bool { func(id string, o geojson.Object, fields []float64) bool {
return sw.writeObject(ScanWriterParams{ return sw.writeObject(ScanWriterParams{
@ -696,8 +725,7 @@ func (s *Server) cmdSearch(msg *Message) (res resp.Value, err error) {
} else { } else {
// must disable globSingle for string value type matching because // must disable globSingle for string value type matching because
// globSingle is only for ID matches, not values. // globSingle is only for ID matches, not values.
sw.globSingle = false sw.col.SearchValuesRange(limits[0], limits[1], sargs.desc, sw,
sw.col.SearchValuesRange(g.Limits[0], g.Limits[1], sargs.desc, sw,
msg.Deadline, msg.Deadline,
func(id string, o geojson.Object, fields []float64) bool { func(id string, o geojson.Object, fields []float64) bool {
return sw.writeObject(ScanWriterParams{ return sw.writeObject(ScanWriterParams{

View File

@ -200,7 +200,7 @@ type searchScanBaseTokens struct {
nodwell bool nodwell bool
detect map[string]bool detect map[string]bool
accept map[string]bool accept map[string]bool
glob string globs []string
wheres []whereT wheres []whereT
whereins []whereinT whereins []whereinT
whereevals []whereevalT whereevals []whereevalT
@ -543,14 +543,12 @@ func (s *Server) parseSearchScanBaseTokens(
continue continue
case "match": case "match":
vs = nvs vs = nvs
if t.glob != "" { var glob string
err = errDuplicateArgument(strings.ToUpper(wtok)) if vs, glob, ok = tokenval(vs); !ok || glob == "" {
return
}
if vs, t.glob, ok = tokenval(vs); !ok || t.glob == "" {
err = errInvalidNumberOfArguments err = errInvalidNumberOfArguments
return return
} }
t.globs = append(t.globs, glob)
continue continue
case "clip": case "clip":
vs = nvs vs = nvs

View File

@ -591,23 +591,28 @@ func keys_MATCH_test(mc *mockServer) error {
{"SET", "fleet", "truck2", "POINT", "33.0002", "-112.0002"}, {"OK"}, {"SET", "fleet", "truck2", "POINT", "33.0002", "-112.0002"}, {"OK"},
{"SET", "fleet", "luck1", "POINT", "33.0003", "-112.0003"}, {"OK"}, {"SET", "fleet", "luck1", "POINT", "33.0003", "-112.0003"}, {"OK"},
{"SET", "fleet", "luck2", "POINT", "33.0004", "-112.0004"}, {"OK"}, {"SET", "fleet", "luck2", "POINT", "33.0004", "-112.0004"}, {"OK"},
{"SET", "fleet", "train1", "POINT", "33.0005", "-112.0005"}, {"OK"},
{"SCAN", "fleet", "IDS"}, {"[0 [luck1 luck2 truck1 truck2]]"}, {"SCAN", "fleet", "IDS"}, {"[0 [luck1 luck2 train1 truck1 truck2]]"},
{"SCAN", "fleet", "MATCH", "*", "IDS"}, {"[0 [luck1 luck2 truck1 truck2]]"}, {"SCAN", "fleet", "MATCH", "*", "IDS"}, {"[0 [luck1 luck2 train1 truck1 truck2]]"},
{"SCAN", "fleet", "MATCH", "truck*", "IDS"}, {"[0 [truck1 truck2]]"}, {"SCAN", "fleet", "MATCH", "truck*", "IDS"}, {"[0 [truck1 truck2]]"},
{"SCAN", "fleet", "MATCH", "luck*", "IDS"}, {"[0 [luck1 luck2]]"}, {"SCAN", "fleet", "MATCH", "luck*", "IDS"}, {"[0 [luck1 luck2]]"},
{"SCAN", "fleet", "MATCH", "*2", "IDS"}, {"[0 [luck2 truck2]]"}, {"SCAN", "fleet", "MATCH", "*2", "IDS"}, {"[0 [luck2 truck2]]"},
{"SCAN", "fleet", "MATCH", "*2*", "IDS"}, {"[0 [luck2 truck2]]"}, {"SCAN", "fleet", "MATCH", "*2*", "IDS"}, {"[0 [luck2 truck2]]"},
{"SCAN", "fleet", "MATCH", "*u*", "IDS"}, {"[0 [luck1 luck2 truck1 truck2]]"}, {"SCAN", "fleet", "MATCH", "*u*", "IDS"}, {"[0 [luck1 luck2 truck1 truck2]]"},
{"SCAN", "fleet", "MATCH", "*u*", "MATCH", "*u*", "IDS"}, {"[0 [luck1 luck2 truck1 truck2]]"},
{"SCAN", "fleet", "MATCH", "*u*", "MATCH", "*a*", "IDS"}, {"[0 [luck1 luck2 train1 truck1 truck2]]"},
{"SCAN", "fleet", "MATCH", "train*", "MATCH", "truck*", "IDS"}, {"[0 [train1 truck1 truck2]]"},
{"SCAN", "fleet", "MATCH", "train*", "MATCH", "truck*", "MATCH", "luck1", "IDS"}, {"[0 [luck1 train1 truck1 truck2]]"},
{"NEARBY", "fleet", "IDS", "POINT", 33.00005, -112.00005, 100000}, { {"NEARBY", "fleet", "IDS", "POINT", 33.00005, -112.00005, 100000}, {
match("[0 [luck1 luck2 truck1 truck2]]"), match("[0 [luck1 luck2 train1 truck1 truck2]]"),
}, },
{"NEARBY", "fleet", "MATCH", "*", "IDS", "POINT", 33.00005, -112.00005, 100000}, { {"NEARBY", "fleet", "MATCH", "*", "IDS", "POINT", 33.00005, -112.00005, 100000}, {
match("[0 [luck1 luck2 truck1 truck2]]"), match("[0 [luck1 luck2 train1 truck1 truck2]]"),
}, },
{"NEARBY", "fleet", "MATCH", "t*", "IDS", "POINT", 33.00005, -112.00005, 100000}, { {"NEARBY", "fleet", "MATCH", "t*", "IDS", "POINT", 33.00005, -112.00005, 100000}, {
match("[0 [truck1 truck2]]"), match("[0 [train1 truck1 truck2]]"),
}, },
{"NEARBY", "fleet", "MATCH", "t*2", "IDS", "POINT", 33.00005, -112.00005, 100000}, { {"NEARBY", "fleet", "MATCH", "t*2", "IDS", "POINT", 33.00005, -112.00005, 100000}, {
match("[0 [truck2]]"), match("[0 [truck2]]"),
@ -617,13 +622,13 @@ func keys_MATCH_test(mc *mockServer) error {
}, },
{"INTERSECTS", "fleet", "IDS", "BOUNDS", 33, -113, 34, -112}, { {"INTERSECTS", "fleet", "IDS", "BOUNDS", 33, -113, 34, -112}, {
match("[0 [luck1 luck2 truck1 truck2]]"), match("[0 [luck1 luck2 train1 truck1 truck2]]"),
}, },
{"INTERSECTS", "fleet", "MATCH", "*", "IDS", "BOUNDS", 33, -113, 34, -112}, { {"INTERSECTS", "fleet", "MATCH", "*", "IDS", "BOUNDS", 33, -113, 34, -112}, {
match("[0 [luck1 luck2 truck1 truck2]]"), match("[0 [luck1 luck2 train1 truck1 truck2]]"),
}, },
{"INTERSECTS", "fleet", "MATCH", "t*", "IDS", "BOUNDS", 33, -113, 34, -112}, { {"INTERSECTS", "fleet", "MATCH", "t*", "IDS", "BOUNDS", 33, -113, 34, -112}, {
match("[0 [truck1 truck2]]"), match("[0 [train1 truck1 truck2]]"),
}, },
{"INTERSECTS", "fleet", "MATCH", "t*2", "IDS", "BOUNDS", 33, -113, 34, -112}, { {"INTERSECTS", "fleet", "MATCH", "t*2", "IDS", "BOUNDS", 33, -113, 34, -112}, {
match("[0 [truck2]]"), match("[0 [truck2]]"),