diff --git a/internal/collection/collection.go b/internal/collection/collection.go index 86bc4a41..78fe6236 100644 --- a/internal/collection/collection.go +++ b/internal/collection/collection.go @@ -309,10 +309,19 @@ func (c *Collection) FieldArr() []string { // Scan iterates though the collection ids. func (c *Collection) Scan(desc bool, + offset uint64, + inc func(n uint64), iterator func(id string, obj geojson.Object, fields []float64) bool, ) bool { var keepon = true + var count uint64 + inc(offset) iter := func(key string, value interface{}) bool { + count++ + if count <= offset { + return true + } + inc(1) iitm := value.(*itemT) keepon = iterator(iitm.id, iitm.obj, c.getFieldValues(iitm.id)) return keepon @@ -327,10 +336,19 @@ func (c *Collection) Scan(desc bool, // ScanRange iterates though the collection starting with specified id. func (c *Collection) ScanRange(start, end string, desc bool, + offset uint64, + inc func(n uint64), iterator func(id string, obj geojson.Object, fields []float64) bool, ) bool { var keepon = true + var count uint64 + inc(offset) iter := func(key string, value interface{}) bool { + count++ + if count <= offset { + return true + } + inc(1) if !desc { if key >= end { return false @@ -355,10 +373,19 @@ func (c *Collection) ScanRange(start, end string, desc bool, // SearchValues iterates though the collection values. func (c *Collection) SearchValues(desc bool, + offset uint64, + inc func(n uint64), iterator func(id string, obj geojson.Object, fields []float64) bool, ) bool { var keepon = true + var count uint64 + inc(offset) iter := func(item btree.Item) bool { + count++ + if count <= offset { + return true + } + inc(1) iitm := item.(*itemT) keepon = iterator(iitm.id, iitm.obj, c.getFieldValues(iitm.id)) return keepon @@ -373,10 +400,19 @@ func (c *Collection) SearchValues(desc bool, // SearchValuesRange iterates though the collection values. func (c *Collection) SearchValuesRange(start, end string, desc bool, + offset uint64, + inc func(n uint64), iterator func(id string, obj geojson.Object, fields []float64) bool, ) bool { var keepon = true + var count uint64 + inc(offset) iter := func(item btree.Item) bool { + count++ + if count <= offset { + return true + } + inc(1) iitm := item.(*itemT) keepon = iterator(iitm.id, iitm.obj, c.getFieldValues(iitm.id)) return keepon @@ -497,14 +533,24 @@ func (c *Collection) geoSparseInner( // Within returns all object that are fully contained within an object or // bounding box. Set obj to nil in order to use the bounding box. func (c *Collection) Within( - obj geojson.Object, sparse uint8, + obj geojson.Object, + offset uint64, + sparse uint8, + inc func(n uint64), iter func(id string, obj geojson.Object, fields []float64) bool, ) bool { + var count uint64 + inc(offset) if sparse > 0 { return c.geoSparse(obj, sparse, func(id string, o geojson.Object, fields []float64) ( match, ok bool, ) { + count++ + if count <= offset { + return false, true + } + inc(1) if match = o.Within(obj); match { ok = iter(id, o, fields) } @@ -514,6 +560,11 @@ func (c *Collection) Within( } return c.geoSearch(obj.Rect(), func(id string, o geojson.Object, fields []float64) bool { + count++ + if count <= offset { + return true + } + inc(1) if o.Within(obj) { return iter(id, o, fields) } @@ -525,14 +576,24 @@ func (c *Collection) Within( // Intersects returns all object that are intersect an object or bounding box. // Set obj to nil in order to use the bounding box. func (c *Collection) Intersects( - obj geojson.Object, sparse uint8, + obj geojson.Object, + offset uint64, + sparse uint8, + inc func(n uint64), iter func(id string, obj geojson.Object, fields []float64) bool, ) bool { + var count uint64 + inc(offset) if sparse > 0 { return c.geoSparse(obj, sparse, func(id string, o geojson.Object, fields []float64) ( match, ok bool, ) { + count++ + if count <= offset { + return false, true + } + inc(1) if match = o.Intersects(obj); match { ok = iter(id, o, fields) } @@ -542,6 +603,11 @@ func (c *Collection) Intersects( } return c.geoSearch(obj.Rect(), func(id string, o geojson.Object, fields []float64) bool { + count++ + if count <= offset { + return true + } + inc(1) if o.Intersects(obj) { return iter(id, o, fields) } @@ -553,14 +619,23 @@ func (c *Collection) Intersects( // Nearby returns the nearest neighbors func (c *Collection) Nearby( target geojson.Object, + offset uint64, + inc func(n uint64), iter func(id string, obj geojson.Object, fields []float64) bool, ) bool { alive := true center := target.Center() + var count uint64 + inc(offset) c.index.Nearby( []float64{center.X, center.Y}, []float64{center.X, center.Y}, func(_, _ []float64, itemv interface{}) bool { + count++ + if count <= offset { + return true + } + inc(1) item := itemv.(*itemT) alive = iter(item.id, item.obj, c.getFieldValues(item.id)) return alive diff --git a/internal/collection/collection_test.go b/internal/collection/collection_test.go index 123ad202..0eab0175 100644 --- a/internal/collection/collection_test.go +++ b/internal/collection/collection_test.go @@ -230,7 +230,7 @@ func TestCollectionScan(t *testing.T) { } var n int var prevID string - c.Scan(false, func(id string, obj geojson.Object, fields []float64) bool { + c.Scan(false, 0, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { if n > 0 { expect(t, id > prevID) } @@ -241,7 +241,7 @@ func TestCollectionScan(t *testing.T) { }) expect(t, n == c.Count()) n = 0 - c.Scan(true, func(id string, obj geojson.Object, fields []float64) bool { + c.Scan(true, 0, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { if n > 0 { expect(t, id < prevID) } @@ -253,7 +253,7 @@ func TestCollectionScan(t *testing.T) { expect(t, n == c.Count()) n = 0 - c.ScanRange("0060", "0070", false, + c.ScanRange("0060", "0070", false, 0, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { if n > 0 { expect(t, id > prevID) @@ -266,7 +266,7 @@ func TestCollectionScan(t *testing.T) { expect(t, n == 10) n = 0 - c.ScanRange("0070", "0060", true, + c.ScanRange("0070", "0060", true, 0, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { if n > 0 { expect(t, id < prevID) @@ -317,7 +317,7 @@ func TestCollectionSearch(t *testing.T) { } var n int var prevValue string - c.SearchValues(false, func(id string, obj geojson.Object, fields []float64) bool { + c.SearchValues(false, 0, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { if n > 0 { expect(t, obj.String() > prevValue) } @@ -328,7 +328,7 @@ func TestCollectionSearch(t *testing.T) { }) expect(t, n == c.Count()) n = 0 - c.SearchValues(true, func(id string, obj geojson.Object, fields []float64) bool { + c.SearchValues(true, 0, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { if n > 0 { expect(t, obj.String() < prevValue) } @@ -340,7 +340,7 @@ func TestCollectionSearch(t *testing.T) { expect(t, n == c.Count()) n = 0 - c.SearchValuesRange("0060", "0070", false, + c.SearchValuesRange("0060", "0070", false, 0, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { if n > 0 { expect(t, obj.String() > prevValue) @@ -353,7 +353,7 @@ func TestCollectionSearch(t *testing.T) { expect(t, n == 10) n = 0 - c.SearchValuesRange("0070", "0060", true, + c.SearchValuesRange("0070", "0060", true, 0, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { if n > 0 { expect(t, obj.String() < prevValue) @@ -436,7 +436,7 @@ func TestSpatialSearch(t *testing.T) { var n int n = 0 - c.Within(q1, 0, + c.Within(q1, 0, 0, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { n++ return true @@ -445,7 +445,7 @@ func TestSpatialSearch(t *testing.T) { expect(t, n == 3) n = 0 - c.Within(q2, 0, + c.Within(q2, 0, 0, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { n++ return true @@ -454,7 +454,7 @@ func TestSpatialSearch(t *testing.T) { expect(t, n == 7) n = 0 - c.Within(q3, 0, + c.Within(q3, 0, 0, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { n++ return true @@ -463,7 +463,7 @@ func TestSpatialSearch(t *testing.T) { expect(t, n == 4) n = 0 - c.Intersects(q1, 0, + c.Intersects(q1, 0, 0, func(n uint64) {}, func(_ string, _ geojson.Object, _ []float64) bool { n++ return true @@ -472,7 +472,7 @@ func TestSpatialSearch(t *testing.T) { expect(t, n == 4) n = 0 - c.Intersects(q2, 0, + c.Intersects(q2, 0, 0, func(n uint64) {}, func(_ string, _ geojson.Object, _ []float64) bool { n++ return true @@ -481,7 +481,7 @@ func TestSpatialSearch(t *testing.T) { expect(t, n == 7) n = 0 - c.Intersects(q3, 0, + c.Intersects(q3, 0, 0, func(n uint64) {}, func(_ string, _ geojson.Object, _ []float64) bool { n++ return true @@ -490,7 +490,7 @@ func TestSpatialSearch(t *testing.T) { expect(t, n == 5) n = 0 - c.Intersects(q3, 0, + c.Intersects(q3, 0, 0, func(n uint64) {}, func(_ string, _ geojson.Object, _ []float64) bool { n++ return n <= 1 @@ -502,7 +502,7 @@ func TestSpatialSearch(t *testing.T) { exitems := []geojson.Object{ r2, p1, p4, r1, p3, r3, p2, } - c.Nearby(q4, + c.Nearby(q4, 0, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { items = append(items, obj) return true @@ -528,7 +528,7 @@ func TestCollectionSparse(t *testing.T) { } var n int n = 0 - c.Within(rect, 1, + c.Within(rect, 0,1, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { n++ return true @@ -537,7 +537,7 @@ func TestCollectionSparse(t *testing.T) { expect(t, n == 4) n = 0 - c.Within(rect, 2, + c.Within(rect, 0, 2, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { n++ return true @@ -546,7 +546,7 @@ func TestCollectionSparse(t *testing.T) { expect(t, n == 16) n = 0 - c.Within(rect, 3, + c.Within(rect, 0, 3, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { n++ return true @@ -555,7 +555,7 @@ func TestCollectionSparse(t *testing.T) { expect(t, n == 64) n = 0 - c.Within(rect, 3, + c.Within(rect, 0, 3, func(n uint64) {}, func(id string, obj geojson.Object, fields []float64) bool { n++ return n <= 30 @@ -564,7 +564,7 @@ func TestCollectionSparse(t *testing.T) { expect(t, n == 31) n = 0 - c.Intersects(rect, 3, + c.Intersects(rect, 0, 3, func(n uint64) {}, func(id string, _ geojson.Object, _ []float64) bool { n++ return true @@ -573,7 +573,7 @@ func TestCollectionSparse(t *testing.T) { expect(t, n == 64) n = 0 - c.Intersects(rect, 3, + c.Intersects(rect, 0, 3, func(n uint64) {}, func(id string, _ geojson.Object, _ []float64) bool { n++ return n <= 30 diff --git a/internal/server/crud.go b/internal/server/crud.go index 8ab3e237..12648aea 100644 --- a/internal/server/crud.go +++ b/internal/server/crud.go @@ -372,9 +372,9 @@ func (server *Server) cmdPdel(msg *Message) (res resp.Value, d commandDetailsT, if col != nil { g := glob.Parse(d.pattern, false) if g.Limits[0] == "" && g.Limits[1] == "" { - col.Scan(false, iter) + col.Scan(false, 0, func(n uint64) {}, iter) } else { - col.ScanRange(g.Limits[0], g.Limits[1], false, iter) + col.ScanRange(g.Limits[0], g.Limits[1], false, 0, func(n uint64) {}, iter) } var atLeastOneNotDeleted bool for i, dc := range d.children { diff --git a/internal/server/fence.go b/internal/server/fence.go index 0390f93f..6a45c700 100644 --- a/internal/server/fence.go +++ b/internal/server/fence.go @@ -299,10 +299,10 @@ func extendRoamMessage( } g := glob.Parse(pattern, false) if g.Limits[0] == "" && g.Limits[1] == "" { - col.Scan(false, iterator) + col.Scan(false, 0, func(n uint64) {}, iterator) } else { col.ScanRange(g.Limits[0], g.Limits[1], - false, iterator) + false, 0, func(n uint64) {}, iterator) } } nmsg = append(nmsg, ']') @@ -364,7 +364,7 @@ func fenceMatchRoam( prevNearbys := fence.roam.nearbys[tid] var newNearbys map[string]bool - col.Intersects(obj, 0, func( + col.Intersects(obj, 0, 0, func(n uint64) {}, func( id string, obj2 geojson.Object, fields []float64, ) bool { if c.hasExpired(fence.roam.key, id) { diff --git a/internal/server/scan.go b/internal/server/scan.go index 1d685b81..1f36185c 100644 --- a/internal/server/scan.go +++ b/internal/server/scan.go @@ -66,7 +66,7 @@ func (c *Server) cmdScan(msg *Message) (res resp.Value, err error) { } else { g := glob.Parse(sw.globPattern, s.desc) if g.Limits[0] == "" && g.Limits[1] == "" { - sw.col.Scan(s.desc, + sw.col.Scan(s.desc, s.cursor, sw.IncCursor, func(id string, o geojson.Object, fields []float64) bool { return sw.writeObject(ScanWriterParams{ id: id, @@ -76,7 +76,7 @@ func (c *Server) cmdScan(msg *Message) (res resp.Value, err error) { }, ) } else { - sw.col.ScanRange(g.Limits[0], g.Limits[1], s.desc, + sw.col.ScanRange(g.Limits[0], g.Limits[1], s.desc, s.cursor, sw.IncCursor, func(id string, o geojson.Object, fields []float64) bool { return sw.writeObject(ScanWriterParams{ id: id, diff --git a/internal/server/scanner.go b/internal/server/scanner.go index 4c8c2b67..d612058a 100644 --- a/internal/server/scanner.go +++ b/internal/server/scanner.go @@ -42,6 +42,7 @@ type scanWriter struct { wheres []whereT whereins []whereinT whereevals []whereevalT + numberIters uint64 numberItems uint64 nofields bool cursor uint64 @@ -68,6 +69,7 @@ type ScanWriterParams struct { noLock bool ignoreGlobMatch bool clip geojson.Object + skipTesting bool } func (c *Server) newScanWriter( @@ -165,7 +167,7 @@ func (sw *scanWriter) writeHead() { func (sw *scanWriter) writeFoot() { sw.mu.Lock() defer sw.mu.Unlock() - cursor := sw.cursor + sw.numberItems + cursor := sw.numberIters if !sw.hitLimit { cursor = 0 } @@ -324,28 +326,43 @@ func (sw *scanWriter) globMatch(id string, o geojson.Object) (ok, keepGoing bool return true, true } +// Increment cursor +func (sw *scanWriter) IncCursor(n uint64) { + sw.numberIters += n +} + +// ok is whether the object passes the test and should be written +// keepGoing is whether there could be more objects to test +func (sw *scanWriter) testObject(id string, o geojson.Object, fields []float64, ignoreGlobMatch bool) ( + ok, keepGoing bool, fieldVals []float64) { + if !ignoreGlobMatch { + match, kg := sw.globMatch(id, o) + if !match { + return true, kg, fieldVals + } + } + nf, ok := sw.fieldMatch(fields, o) + return ok,true, nf +} + //id string, o geojson.Object, fields []float64, noLock bool func (sw *scanWriter) writeObject(opts ScanWriterParams) bool { if !opts.noLock { sw.mu.Lock() defer sw.mu.Unlock() } + var fieldVals []float64 + var ok bool keepGoing := true - if !opts.ignoreGlobMatch { - var match bool - match, keepGoing = sw.globMatch(opts.id, opts.o) - if !match { - return true + if opts.skipTesting { + fieldVals = sw.fvals + } else { + ok, keepGoing, fieldVals = sw.testObject(opts.id, opts.o, opts.fields, opts.ignoreGlobMatch) + if !ok { + return keepGoing } } - nfields, ok := sw.fieldMatch(opts.fields, opts.o) - if !ok { - return true - } sw.count++ - if sw.count <= sw.cursor { - return true - } if sw.output == outputCount { return sw.count < sw.limit } @@ -382,7 +399,7 @@ func (sw *scanWriter) writeObject(opts ScanWriterParams) bool { } else if len(sw.farr) > 0 { jsfields = `,"fields":[` - for i, field := range nfields { + for i, field := range fieldVals { if i > 0 { jsfields += "," } diff --git a/internal/server/search.go b/internal/server/search.go index 4d383a13..0da73149 100644 --- a/internal/server/search.go +++ b/internal/server/search.go @@ -382,6 +382,7 @@ func (server *Server) cmdNearby(msg *Message) (res resp.Value, err error) { distance: meters, noLock: true, ignoreGlobMatch: true, + skipTesting: true, }) } server.nearestNeighbors(&s, sw, s.obj.(*geojson.Circle), iter) @@ -405,18 +406,15 @@ func (server *Server) nearestNeighbors( s *liveFenceSwitches, sw *scanWriter, target *geojson.Circle, iter func(id string, o geojson.Object, fields []float64, dist float64, ) bool) { - limit := int(sw.cursor + sw.limit) maxDist := target.Haversine() + limit := int(sw.limit) var items []iterItem - sw.col.Nearby(target, func(id string, o geojson.Object, fields []float64) bool { + sw.col.Nearby(target, sw.cursor, sw.IncCursor, func(id string, o geojson.Object, fields []float64) bool { if server.hasExpired(s.key, id) { return true } - if _, ok := sw.fieldMatch(fields, o); !ok { - return true - } - match, keepGoing := sw.globMatch(id, o) - if !match { + ok, keepGoing, _ := sw.testObject(id, o, fields,true) + if !ok { return true } dist := target.HaversineTo(o.Center()) @@ -482,7 +480,7 @@ func (server *Server) cmdWithinOrIntersects(cmd string, msg *Message) (res resp. sw.writeHead() if sw.col != nil { if cmd == "within" { - sw.col.Within(s.obj, s.sparse, func( + sw.col.Within(s.obj, s.cursor, s.sparse, sw.IncCursor, func( id string, o geojson.Object, fields []float64, ) bool { if server.hasExpired(s.key, id) { @@ -496,7 +494,7 @@ func (server *Server) cmdWithinOrIntersects(cmd string, msg *Message) (res resp. }) }) } else if cmd == "intersects" { - sw.col.Intersects(s.obj, s.sparse, func( + sw.col.Intersects(s.obj, s.cursor, s.sparse, sw.IncCursor, func( id string, o geojson.Object, fields []float64, @@ -580,7 +578,7 @@ func (server *Server) cmdSearch(msg *Message) (res resp.Value, err error) { } else { g := glob.Parse(sw.globPattern, s.desc) if g.Limits[0] == "" && g.Limits[1] == "" { - sw.col.SearchValues(s.desc, + sw.col.SearchValues(s.desc, s.cursor, sw.IncCursor, func(id string, o geojson.Object, fields []float64) bool { return sw.writeObject(ScanWriterParams{ id: id, @@ -594,7 +592,7 @@ func (server *Server) cmdSearch(msg *Message) (res resp.Value, err error) { // must disable globSingle for string value type matching because // globSingle is only for ID matches, not values. sw.globSingle = false - sw.col.SearchValuesRange(g.Limits[0], g.Limits[1], s.desc, + sw.col.SearchValuesRange(g.Limits[0], g.Limits[1], s.desc, s.cursor, sw.IncCursor, func(id string, o geojson.Object, fields []float64) bool { return sw.writeObject(ScanWriterParams{ id: id, diff --git a/tests/keys_search_test.go b/tests/keys_search_test.go index ba532adb..f0767656 100644 --- a/tests/keys_search_test.go +++ b/tests/keys_search_test.go @@ -6,10 +6,15 @@ import ( func subTestSearch(t *testing.T, mc *mockServer) { runStep(t, mc, "KNN", keys_KNN_test) + runStep(t, mc, "KNN_CURSOR", keys_KNN_cursor_test) runStep(t, mc, "WITHIN_CIRCLE", keys_WITHIN_CIRCLE_test) runStep(t, mc, "INTERSECTS_CIRCLE", keys_INTERSECTS_CIRCLE_test) runStep(t, mc, "WITHIN", keys_WITHIN_test) + runStep(t, mc, "WITHIN_CURSOR", keys_WITHIN_CURSOR_test) runStep(t, mc, "INTERSECTS", keys_INTERSECTS_test) + runStep(t, mc, "INTERSECTS_CURSOR", keys_INTERSECTS_CURSOR_test) + runStep(t, mc, "SCAN_CURSOR", keys_SCAN_CURSOR_test) + runStep(t, mc, "SEARCH_CURSOR", keys_SEARCH_CURSOR_test) } func keys_KNN_test(mc *mockServer) error { @@ -26,6 +31,28 @@ func keys_KNN_test(mc *mockServer) error { }) } +func keys_KNN_cursor_test(mc *mockServer) error { + return mc.DoBatch([][]interface{}{ + {"SET", "mykey", "1", "FIELD", "foo", 5.5, "POINT", 5, 5}, {"OK"}, + {"SET", "mykey", "2", "FIELD", "foo", 19.19, "POINT",19, 19}, {"OK"}, + {"SET", "mykey", "3", "FIELD", "foo", 12.19, "POINT", 12, 19}, {"OK"}, + {"SET", "mykey", "4", "FIELD", "foo", -5.5, "POINT", -5, 5}, {"OK"}, + {"SET", "mykey", "5", "FIELD", "foo", 13.21, "POINT", 33, 21}, {"OK"}, + {"NEARBY", "mykey", "LIMIT", 2, "POINTS", "POINT", 20, 20}, { + "[2 [[2 [19 19] [foo 19.19]] [3 [12 19] [foo 12.19]]]]"}, + {"NEARBY", "mykey", "CURSOR", 2, "LIMIT", 1, "POINTS", "POINT", 20, 20}, { + "[3 [[5 [33 21] [foo 13.21]]]]"}, + {"NEARBY", "mykey", "LIMIT", 2, "WHERE", "foo", -10, 15, "POINTS", "POINT", 20, 20}, { + "[3 [[3 [12 19] [foo 12.19]] [5 [33 21] [foo 13.21]]]]"}, + {"NEARBY", "mykey", "CURSOR", 3, "LIMIT", 1, "WHERE", "foo", -10, 15, "POINTS", "POINT", 20, 20}, { + "[4 [[1 [5 5] [foo 5.5]]]]"}, + {"NEARBY", "mykey", "CURSOR", 4, "LIMIT", 1, "WHERE", "foo", -10, 15, "POINTS", "POINT", 20, 20}, { + "[5 [[4 [-5 5] [foo -5.5]]]]"}, + {"NEARBY", "mykey", "CURSOR", 4, "LIMIT", 10, "WHERE", "foo", -10, 15, "POINTS", "POINT", 20, 20}, { + "[0 [[4 [-5 5] [foo -5.5]]]]"}, + }) +} + func keys_WITHIN_test(mc *mockServer) error { return mc.DoBatch([][]interface{}{ {"SET", "mykey", "point1", "POINT", 37.7335, -122.4412}, {"OK"}, @@ -73,6 +100,44 @@ func keys_WITHIN_test(mc *mockServer) error { }) } +func keys_WITHIN_CURSOR_test(mc *mockServer) error { + testArea := `{ + "type": "Polygon", + "coordinates": [ + [ + [-122.44126439094543,37.72906137107], + [-122.43980526924135,37.72906137107], + [-122.43980526924135,37.73421283683962], + [-122.44126439094543,37.73421283683962], + [-122.44126439094543,37.72906137107] + ] + ] + }` + return mc.DoBatch([][]interface{}{ + {"SET", "mykey", "point1", "FIELD", "foo", 1, "POINT", 37.7335, -122.4412}, {"OK"}, + {"SET", "mykey", "point2", "FIELD", "foo", 2, "POINT", 37.7335, -122.44121}, {"OK"}, + {"SET", "mykey", "line3", "FIELD", "foo", 3, "OBJECT", `{"type":"LineString","coordinates":[[-122.4408378,37.7341129],[-122.4408378,37.733]]}`}, {"OK"}, + {"SET", "mykey", "poly4", "FIELD", "foo", 4, "OBJECT", `{"type":"Polygon","coordinates":[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]]]}`}, {"OK"}, + {"SET", "mykey", "multipoly5","FIELD", "foo", 5, "OBJECT", `{"type":"MultiPolygon","coordinates":[[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]]],[[[-122.44091033935547,37.731981251280985],[-122.43994474411011,37.731981251280985],[-122.43994474411011,37.73254976045042],[-122.44091033935547,37.73254976045042],[-122.44091033935547,37.731981251280985]]]]}`}, {"OK"}, + {"SET", "mykey", "point6", "FIELD", "foo", 6, "POINT", -5, 5}, {"OK"}, + {"SET", "mykey", "point7", "FIELD", "foo", 7, "POINT", 33, 21}, {"OK"}, + {"SET", "mykey", "poly8", "FIELD", "foo", 8, "OBJECT", `{"type":"Polygon","coordinates":[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]],[[-122.44060993194579,37.73345766902749],[-122.44044363498686,37.73345766902749],[-122.44044363498686,37.73355524732416],[-122.44060993194579,37.73355524732416],[-122.44060993194579,37.73345766902749]],[[-122.44060724973677,37.7336888869566],[-122.4402102828026,37.7336888869566],[-122.4402102828026,37.7339752567853],[-122.44060724973677,37.7339752567853],[-122.44060724973677,37.7336888869566]]]}`}, {"OK"}, + {"SET", "mykey", "point9", "FIELD", "foo", 9, "POINT", 37.7335, -122.4412}, {"OK"}, + {"WITHIN", "mykey", "LIMIT", 3, "IDS", "OBJECT", testArea}, { + "[3 [point1 point2 line3]]"}, + {"WITHIN", "mykey", "CURSOR", 3, "LIMIT", 3, "IDS", "OBJECT", testArea}, { + "[6 [poly4 multipoly5 poly8]]"}, + {"WITHIN", "mykey", "WHERE", "foo", 3, 5, "IDS", "OBJECT", testArea}, { + "[0 [line3 poly4 multipoly5]]"}, + {"WITHIN", "mykey", "LIMIT", 1, "WHERE", "foo", 3, 5, "IDS", "OBJECT", testArea}, { + "[3 [line3]]"}, + {"WITHIN", "mykey", "CURSOR", 3, "LIMIT", 1, "WHERE", "foo", 8, 9, "IDS", "OBJECT", testArea}, { + "[6 [poly8]]"}, + {"WITHIN", "mykey", "CURSOR", 6, "LIMIT", 1, "WHERE", "foo", 8, 9, "IDS", "OBJECT", testArea}, { + "[7 [point9]]"}, + }) +} + func keys_INTERSECTS_test(mc *mockServer) error { return mc.DoBatch([][]interface{}{ {"SET", "mykey", "point1", "POINT", 37.7335, -122.4412}, {"OK"}, @@ -123,6 +188,44 @@ func keys_INTERSECTS_test(mc *mockServer) error { }) } +func keys_INTERSECTS_CURSOR_test(mc *mockServer) error { + testArea := `{ + "type": "Polygon", + "coordinates": [ + [ + [-122.44126439094543,37.732906137107], + [-122.43980526924135,37.732906137107], + [-122.43980526924135,37.73421283683962], + [-122.44126439094543,37.73421283683962], + [-122.44126439094543,37.732906137107] + ] + ] + }` + return mc.DoBatch([][]interface{}{ + {"SET", "mykey", "point1", "FIELD", "foo", 1, "POINT", 37.7335, -122.4412}, {"OK"}, + {"SET", "mykey", "point2", "FIELD", "foo", 2, "POINT", 37.7335, -122.44121}, {"OK"}, + {"SET", "mykey", "line3", "FIELD", "foo", 3, "OBJECT", `{"type":"LineString","coordinates":[[-122.4408378,37.7341129],[-122.4408378,37.733]]}`}, {"OK"}, + {"SET", "mykey", "poly4", "FIELD", "foo", 4, "OBJECT", `{"type":"Polygon","coordinates":[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]]]}`}, {"OK"}, + {"SET", "mykey", "multipoly5", "FIELD", "foo", 5, "OBJECT", `{"type":"MultiPolygon","coordinates":[[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]]],[[[-122.44091033935547,37.731981251280985],[-122.43994474411011,37.731981251280985],[-122.43994474411011,37.73254976045042],[-122.44091033935547,37.73254976045042],[-122.44091033935547,37.731981251280985]]]]}`}, {"OK"}, + {"SET", "mykey", "point6", "FIELD", "foo", 6, "POINT", -5, 5}, {"OK"}, + {"SET", "mykey", "point7", "FIELD", "foo", 7, "POINT", 33, 21}, {"OK"}, + {"SET", "mykey", "poly8", "FIELD", "foo", 8, "OBJECT", `{"type":"Polygon","coordinates":[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]],[[-122.44060993194579,37.73345766902749],[-122.44044363498686,37.73345766902749],[-122.44044363498686,37.73355524732416],[-122.44060993194579,37.73355524732416],[-122.44060993194579,37.73345766902749]],[[-122.44060724973677,37.7336888869566],[-122.4402102828026,37.7336888869566],[-122.4402102828026,37.7339752567853],[-122.44060724973677,37.7339752567853],[-122.44060724973677,37.7336888869566]]]}`}, {"OK"}, + {"SET", "mykey", "point9", "FIELD", "foo", 9, "POINT", 37.7335, -122.4412}, {"OK"}, + {"INTERSECTS", "mykey", "LIMIT", 3, "IDS", "OBJECT", testArea}, { + "[3 [point1 point2 line3]]"}, + {"INTERSECTS", "mykey", "CURSOR", 3, "LIMIT", 3, "IDS", "OBJECT", testArea}, { + "[6 [poly4 multipoly5 poly8]]"}, + {"INTERSECTS", "mykey", "WHERE", "foo", 3, 5, "IDS", "OBJECT", testArea}, { + "[0 [line3 poly4 multipoly5]]"}, + {"INTERSECTS", "mykey", "LIMIT", 1, "WHERE", "foo", 3, 5, "IDS", "OBJECT", testArea}, { + "[3 [line3]]"}, + {"INTERSECTS", "mykey", "CURSOR", 3, "LIMIT", 1, "WHERE", "foo", 8, 9, "IDS", "OBJECT", testArea}, { + "[6 [poly8]]"}, + {"INTERSECTS", "mykey", "CURSOR", 6, "LIMIT", 1, "WHERE", "foo", 8, 9, "IDS", "OBJECT", testArea}, { + "[7 [point9]]"}, + }) +} + func keys_WITHIN_CIRCLE_test(mc *mockServer) error { return mc.DoBatch([][]interface{}{ {"SET", "mykey", "1", "POINT", 37.7335, -122.4412}, {"OK"}, @@ -154,3 +257,48 @@ func keys_INTERSECTS_CIRCLE_test(mc *mockServer) error { "[0 [1 2]]"}, }) } + +func keys_SCAN_CURSOR_test(mc *mockServer) error { + return mc.DoBatch([][]interface{}{ + {"SET", "mykey", "id1", "FIELD", "foo", 1, "STRING", "bar1"}, {"OK"}, + {"SET", "mykey", "id2", "FIELD", "foo", 2, "STRING", "bar2"}, {"OK"}, + {"SET", "mykey", "id3", "FIELD", "foo", 3, "STRING", "bar3"}, {"OK"}, + {"SET", "mykey", "id4", "FIELD", "foo", 4, "STRING", "bar4"}, {"OK"}, + {"SET", "mykey", "id5", "FIELD", "foo", 5, "STRING", "bar5"}, {"OK"}, + {"SET", "mykey", "id6", "FIELD", "foo", 6, "STRING", "bar6"}, {"OK"}, + {"SET", "mykey", "id7", "FIELD", "foo", 7, "STRING", "bar7"}, {"OK"}, + {"SET", "mykey", "id8", "FIELD", "foo", 8, "STRING", "bar8"}, {"OK"}, + {"SET", "mykey", "id9", "FIELD", "foo", 9, "STRING", "bar9"}, {"OK"}, + {"SCAN", "mykey", "LIMIT", 3, "IDS"}, {"[3 [id1 id2 id3]]"}, + {"SCAN", "mykey", "CURSOR", 3, "LIMIT", 3, "IDS"}, {"[6 [id4 id5 id6]]"}, + {"SCAN", "mykey", "WHERE", "foo", 3, 5, "IDS"}, {"[0 [id3 id4 id5]]"}, + {"SCAN", "mykey", "LIMIT", 1, "WHERE", "foo", 3, 5, "IDS"}, {"[3 [id3]]"}, + {"SCAN", "mykey", "CURSOR", 3, "LIMIT", 1, "WHERE", "foo", 8, 9, "IDS"}, { + "[8 [id8]]"}, + {"SCAN", "mykey", "CURSOR", 6, "LIMIT", 1, "WHERE", "foo", 8, 9, "IDS"}, { + "[8 [id8]]"}, + }) +} + +func keys_SEARCH_CURSOR_test(mc *mockServer) error { + return mc.DoBatch([][]interface{}{ + {"SET", "mykey", "id1", "FIELD", "foo", 1, "STRING", "bar1"}, {"OK"}, + {"SET", "mykey", "id2", "FIELD", "foo", 2, "STRING", "bar2"}, {"OK"}, + {"SET", "mykey", "id3", "FIELD", "foo", 3, "STRING", "bar3"}, {"OK"}, + {"SET", "mykey", "id4", "FIELD", "foo", 4, "STRING", "bar4"}, {"OK"}, + {"SET", "mykey", "id5", "FIELD", "foo", 5, "STRING", "bar5"}, {"OK"}, + {"SET", "mykey", "id6", "FIELD", "foo", 6, "STRING", "bar6"}, {"OK"}, + {"SET", "mykey", "id7", "FIELD", "foo", 7, "STRING", "bar7"}, {"OK"}, + {"SET", "mykey", "id8", "FIELD", "foo", 8, "STRING", "bar8"}, {"OK"}, + {"SET", "mykey", "id9", "FIELD", "foo", 9, "STRING", "bar9"}, {"OK"}, + {"SEARCH", "mykey", "LIMIT", 3, "IDS"}, {"[3 [id1 id2 id3]]"}, + {"SEARCH", "mykey", "CURSOR", 3, "LIMIT", 3, "IDS"}, {"[6 [id4 id5 id6]]"}, + {"SEARCH", "mykey", "WHERE", "foo", 3, 5, "IDS"}, {"[0 [id3 id4 id5]]"}, + {"SEARCH", "mykey", "LIMIT", 1, "WHERE", "foo", 3, 5, "IDS"}, {"[3 [id3]]"}, + {"SEARCH", "mykey", "CURSOR", 3, "LIMIT", 1, "WHERE", "foo", 8, 9, "IDS"}, { + "[8 [id8]]"}, + {"SEARCH", "mykey", "CURSOR", 6, "LIMIT", 1, "WHERE", "foo", 8, 9, "IDS"}, { + "[8 [id8]]"}, + {"SEARCH", "mykey", "LIMIT", 3, "DESC", "IDS"}, {"[3 [id9 id8 id7]]"}, + }) +}