scan order asc/desc

This commit is contained in:
Josh Baker 2016-07-12 13:18:16 -06:00
parent 4fd7e1f821
commit 75adea6a9c
13 changed files with 126 additions and 61 deletions

View File

@ -150,7 +150,7 @@ func (c *Controller) aofshrink() {
objs := make(map[string]objFields) objs := make(map[string]objFields)
c.mu.Lock() c.mu.Lock()
fnames := col.FieldArr() // reload an array of field names to match each object fnames := col.FieldArr() // reload an array of field names to match each object
col.ScanGreaterOrEqual(nextID, 0, collection.TypeAll, col.ScanGreaterOrEqual(nextID, 1, collection.TypeAll, false,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields []float64) bool {
if id != nextID { if id != nextID {
objs[id] = objFields{obj, fields} objs[id] = objFields{obj, fields}

View File

@ -236,32 +236,69 @@ func (c *Collection) FieldArr() []string {
} }
// Scan iterates though the collection. A cursor can be used for paging. // Scan iterates though the collection. A cursor can be used for paging.
func (c *Collection) Scan(cursor uint64, stype ScanType, iterator func(id string, obj geojson.Object, fields []float64) bool) (ncursor uint64) { func (c *Collection) Scan(cursor uint64, stype ScanType, desc bool,
iterator func(id string, obj geojson.Object, fields []float64) bool,
) (ncursor uint64) {
var i uint64 var i uint64
var active = true var active = true
c.items.Ascend(func(item btree.Item) bool { iter := func(item btree.Item) bool {
if i >= cursor { if i >= cursor {
iitm := item.(*itemT) iitm := item.(*itemT)
active = iterator(iitm.id, iitm.object, iitm.fields) active = iterator(iitm.id, iitm.object, iitm.fields)
} }
i++ i++
return active return active
}) }
if desc {
c.items.Descend(iter)
} else {
c.items.Ascend(iter)
}
return i return i
} }
// ScanGreaterOrEqual iterates though the collection starting with specified id. A cursor can be used for paging. // ScanGreaterOrEqual iterates though the collection starting with specified id. A cursor can be used for paging.
func (c *Collection) ScanGreaterOrEqual(id string, cursor uint64, stype ScanType, iterator func(id string, obj geojson.Object, fields []float64) bool) (ncursor uint64) { func (c *Collection) ScanRange(cursor uint64, stype ScanType, start, end string, desc bool,
iterator func(id string, obj geojson.Object, fields []float64) bool,
) (ncursor uint64) {
var i uint64 var i uint64
var active = true var active = true
c.items.AscendGreaterOrEqual(&itemT{id: id}, func(item btree.Item) bool { iter := func(item btree.Item) bool {
if i >= cursor { if i >= cursor {
iitm := item.(*itemT) iitm := item.(*itemT)
active = iterator(iitm.id, iitm.object, iitm.fields) active = iterator(iitm.id, iitm.object, iitm.fields)
} }
i++ i++
return active return active
}) }
if desc {
c.items.DescendRange(&itemT{id: start}, &itemT{id: end}, iter)
} else {
c.items.AscendRange(&itemT{id: start}, &itemT{id: end}, iter)
}
return i
}
// ScanGreaterOrEqual iterates though the collection starting with specified id. A cursor can be used for paging.
func (c *Collection) ScanGreaterOrEqual(id string, cursor uint64, stype ScanType, desc bool,
iterator func(id string, obj geojson.Object, fields []float64) bool,
) (ncursor uint64) {
var i uint64
var active = true
iter := func(item btree.Item) bool {
if i >= cursor {
iitm := item.(*itemT)
active = iterator(iitm.id, iitm.object, iitm.fields)
}
i++
return active
}
if desc {
c.items.DescendLessOrEqual(&itemT{id: id}, iter)
} else {
c.items.AscendGreaterOrEqual(&itemT{id: id}, iter)
}
return i return i
} }

View File

@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/controller/glob"
"github.com/tidwall/tile38/controller/server" "github.com/tidwall/tile38/controller/server"
) )
@ -147,7 +148,7 @@ func (c *Controller) setConfigProperty(name, value string, fromLoad bool) error
func (c *Controller) getConfigProperties(pattern string) map[string]interface{} { func (c *Controller) getConfigProperties(pattern string) map[string]interface{} {
m := make(map[string]interface{}) m := make(map[string]interface{})
for _, name := range validProperties { for _, name := range validProperties {
matched, _ := globMatch(pattern, name) matched, _ := glob.Match(pattern, name)
if matched { if matched {
m[name] = c.getConfigProperty(name) m[name] = c.getConfigProperty(name)
} }

View File

@ -4,6 +4,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/tidwall/tile38/controller/glob"
"github.com/tidwall/tile38/controller/server" "github.com/tidwall/tile38/controller/server"
"github.com/tidwall/tile38/geojson" "github.com/tidwall/tile38/geojson"
) )
@ -14,13 +15,13 @@ var tmfmt = "2006-01-02T15:04:05.999999999Z07:00"
func FenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, details *commandDetailsT) []string { func FenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, details *commandDetailsT) []string {
jshookName := jsonString(hookName) jshookName := jsonString(hookName)
jstime := jsonString(details.timestamp.Format(tmfmt)) jstime := jsonString(details.timestamp.Format(tmfmt))
glob := fence.glob pattern := fence.glob
if details.command == "drop" { if details.command == "drop" {
return []string{`{"cmd":"drop","hook":` + jshookName + `,"time":` + jstime + `}`} return []string{`{"cmd":"drop","hook":` + jshookName + `,"time":` + jstime + `}`}
} }
match := true match := true
if glob != "" && glob != "*" { if pattern != "" && pattern != "*" {
match, _ = globMatch(glob, details.id) match, _ = glob.Match(pattern, details.id)
} }
if !match { if !match {
return nil return nil
@ -202,7 +203,7 @@ func fenceMatchRoam(c *Controller, fence *liveFenceSwitches, tkey, tid string, o
return true // skip self return true // skip self
} }
if fence.roam.pattern { if fence.roam.pattern {
match, _ = globMatch(fence.roam.id, id) match, _ = glob.Match(fence.roam.id, id)
} else { } else {
match = fence.roam.id == id match = fence.roam.id == id
} }

View File

@ -1,18 +0,0 @@
package controller
import "path"
func globMatch(pattern, name string) (matched bool, err error) {
return path.Match(pattern, name)
}
func globIsGlob(pattern string) bool {
for i := 0; i < len(pattern); i++ {
switch pattern[i] {
case '[', '*', '?':
_, err := globMatch(pattern, "whatever")
return err == nil
}
}
return false
}

View File

@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/controller/glob"
"github.com/tidwall/tile38/controller/log" "github.com/tidwall/tile38/controller/log"
"github.com/tidwall/tile38/controller/server" "github.com/tidwall/tile38/controller/server"
) )
@ -329,7 +330,7 @@ func (c *Controller) cmdHooks(msg *server.Message) (res string, err error) {
var hooks []*Hook var hooks []*Hook
for name, hook := range c.hooks { for name, hook := range c.hooks {
match, _ := globMatch(pattern, name) match, _ := glob.Match(pattern, name)
if match { if match {
hooks = append(hooks, hook) hooks = append(hooks, hook)
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/tidwall/btree" "github.com/tidwall/btree"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/controller/glob"
"github.com/tidwall/tile38/controller/server" "github.com/tidwall/tile38/controller/server"
) )
@ -44,7 +45,7 @@ func (c *Controller) cmdKeys(msg *server.Message) (res string, err error) {
} }
match = true match = true
} else { } else {
match, _ = globMatch(pattern, key) match, _ = glob.Match(pattern, key)
} }
if match { if match {
if once { if once {
@ -69,14 +70,14 @@ func (c *Controller) cmdKeys(msg *server.Message) (res string, err error) {
} else { } else {
if strings.HasSuffix(pattern, "*") { if strings.HasSuffix(pattern, "*") {
greaterPivot = pattern[:len(pattern)-1] greaterPivot = pattern[:len(pattern)-1]
if globIsGlob(greaterPivot) { if glob.IsGlob(greaterPivot) {
greater = false greater = false
c.cols.Ascend(iterator) c.cols.Ascend(iterator)
} else { } else {
greater = true greater = true
c.cols.AscendGreaterOrEqual(&collectionT{Key: greaterPivot}, iterator) c.cols.AscendGreaterOrEqual(&collectionT{Key: greaterPivot}, iterator)
} }
} else if globIsGlob(pattern) { } else if glob.IsGlob(pattern) {
greater = false greater = false
c.cols.Ascend(iterator) c.cols.Ascend(iterator)
} else { } else {

View File

@ -2,11 +2,11 @@ package controller
import ( import (
"bytes" "bytes"
"strings"
"time" "time"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/controller/collection" "github.com/tidwall/tile38/controller/collection"
"github.com/tidwall/tile38/controller/glob"
"github.com/tidwall/tile38/controller/server" "github.com/tidwall/tile38/controller/server"
"github.com/tidwall/tile38/geojson" "github.com/tidwall/tile38/geojson"
) )
@ -48,23 +48,16 @@ func (c *Controller) cmdScan(msg *server.Message) (res string, err error) {
} }
sw.count = uint64(count) sw.count = uint64(count)
} else { } else {
if strings.HasSuffix(sw.glob, "*") { g := glob.Parse(sw.glob, s.desc)
greaterGlob := sw.glob[:len(sw.glob)-1] if g.Limits[0] == "" && g.Limits[1] == "" {
if globIsGlob(greaterGlob) { s.cursor = sw.col.Scan(s.cursor, stype, s.desc,
s.cursor = sw.col.Scan(s.cursor, stype, func(id string, o geojson.Object, fields []float64) bool {
func(id string, o geojson.Object, fields []float64) bool { return sw.writeObject(id, o, fields, false)
return sw.writeObject(id, o, fields, false) },
}, )
)
} else {
s.cursor = sw.col.ScanGreaterOrEqual(greaterGlob, s.cursor, stype,
func(id string, o geojson.Object, fields []float64) bool {
return sw.writeObject(id, o, fields, false)
},
)
}
} else { } else {
s.cursor = sw.col.Scan(s.cursor, stype, s.cursor = sw.col.ScanRange(
s.cursor, stype, g.Limits[0], g.Limits[1], s.desc,
func(id string, o geojson.Object, fields []float64) bool { func(id string, o geojson.Object, fields []float64) bool {
return sw.writeObject(id, o, fields, false) return sw.writeObject(id, o, fields, false)
}, },

View File

@ -8,6 +8,7 @@ import (
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/controller/collection" "github.com/tidwall/tile38/controller/collection"
"github.com/tidwall/tile38/controller/glob"
"github.com/tidwall/tile38/controller/server" "github.com/tidwall/tile38/controller/server"
"github.com/tidwall/tile38/geojson" "github.com/tidwall/tile38/geojson"
) )
@ -53,7 +54,7 @@ type scanWriter struct {
} }
func (c *Controller) newScanWriter( func (c *Controller) newScanWriter(
wr *bytes.Buffer, msg *server.Message, key string, output outputT, precision uint64, glob string, limit uint64, wheres []whereT, nofields bool, wr *bytes.Buffer, msg *server.Message, key string, output outputT, precision uint64, globPattern string, limit uint64, wheres []whereT, nofields bool,
) ( ) (
*scanWriter, error, *scanWriter, error,
) { ) {
@ -75,13 +76,13 @@ func (c *Controller) newScanWriter(
wheres: wheres, wheres: wheres,
precision: precision, precision: precision,
nofields: nofields, nofields: nofields,
glob: glob, glob: globPattern,
limit: limit, limit: limit,
} }
if glob == "*" || glob == "" { if globPattern == "*" || globPattern == "" {
sw.globEverything = true sw.globEverything = true
} else { } else {
if !globIsGlob(glob) { if !glob.IsGlob(globPattern) {
sw.globSingle = true sw.globSingle = true
} }
} }
@ -241,7 +242,7 @@ func (sw *scanWriter) writeObject(id string, o geojson.Object, fields []float64,
} }
keepGoing = false // return current object and stop iterating keepGoing = false // return current object and stop iterating
} else { } else {
ok, _ := globMatch(sw.glob, id) ok, _ := glob.Match(sw.glob, id)
if !ok { if !ok {
return true return true
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/controller/bing" "github.com/tidwall/tile38/controller/bing"
"github.com/tidwall/tile38/controller/glob"
"github.com/tidwall/tile38/controller/server" "github.com/tidwall/tile38/controller/server"
"github.com/tidwall/tile38/geojson" "github.com/tidwall/tile38/geojson"
"github.com/tidwall/tile38/geojson/geohash" "github.com/tidwall/tile38/geojson/geohash"
@ -230,7 +231,7 @@ func (c *Controller) cmdSearchArgs(cmd string, vs []resp.Value, types []string)
err = errInvalidNumberOfArguments err = errInvalidNumberOfArguments
return return
} }
s.roam.pattern = globIsGlob(s.roam.id) s.roam.pattern = glob.IsGlob(s.roam.id)
var smeters string var smeters string
if vs, smeters, ok = tokenval(vs); !ok || smeters == "" { if vs, smeters, ok = tokenval(vs); !ok || smeters == "" {
err = errInvalidNumberOfArguments err = errInvalidNumberOfArguments

View File

@ -130,6 +130,7 @@ type searchScanBaseTokens struct {
nofields bool nofields bool
limit uint64 limit uint64
sparse uint8 sparse uint8
desc bool
} }
func parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vsout []resp.Value, t searchScanBaseTokens, err error) { func parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vsout []resp.Value, t searchScanBaseTokens, err error) {
@ -141,6 +142,7 @@ func parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vsout []resp.Value,
var slimit string var slimit string
var ssparse string var ssparse string
var scursor string var scursor string
var asc bool
for { for {
nvs, wtok, ok := tokenval(vs) nvs, wtok, ok := tokenval(vs)
if ok && len(wtok) > 0 { if ok && len(wtok) > 0 {
@ -273,7 +275,22 @@ func parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vsout []resp.Value,
"cross": true, "cross": true,
} }
} }
continue
} else if (wtok[0] == 'D' || wtok[0] == 'd') && strings.ToLower(wtok) == "desc" {
vs = nvs
if t.desc || asc {
err = errDuplicateArgument(strings.ToUpper(wtok))
return
}
t.desc = true
continue
} else if (wtok[0] == 'A' || wtok[0] == 'a') && strings.ToLower(wtok) == "asc" {
vs = nvs
if t.desc || asc {
err = errDuplicateArgument(strings.ToUpper(wtok))
return
}
asc = true
continue continue
} else if (wtok[0] == 'M' || wtok[0] == 'm') && strings.ToLower(wtok) == "match" { } else if (wtok[0] == 'M' || wtok[0] == 'm') && strings.ToLower(wtok) == "match" {
vs = nvs vs = nvs
@ -301,6 +318,15 @@ func parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vsout []resp.Value,
err = errors.New("FENCE is not allowed for SCAN") err = errors.New("FENCE is not allowed for SCAN")
return return
} }
} else {
if t.desc {
err = errors.New("DESC is not allowed for " + strings.ToUpper(cmd))
return
}
if asc {
err = errors.New("ASC is not allowed for " + strings.ToUpper(cmd))
return
}
} }
if ssparse != "" && slimit != "" { if ssparse != "" && slimit != "" {
err = errors.New("LIMIT is not allowed when SPARSE is specified") err = errors.New("LIMIT is not allowed when SPARSE is specified")
@ -353,7 +379,6 @@ func parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vsout []resp.Value,
vs = nvs vs = nvs
} }
} }
if scursor != "" { if scursor != "" {
if t.cursor, err = strconv.ParseUint(scursor, 10, 64); err != nil { if t.cursor, err = strconv.ParseUint(scursor, 10, 64); err != nil {
err = errInvalidArgument(scursor) err = errInvalidArgument(scursor)

View File

@ -216,7 +216,6 @@
"since": "1.0.0", "since": "1.0.0",
"group": "keys" "group": "keys"
}, },
"SCAN": { "SCAN": {
"summary": "Incrementally iterate though a key", "summary": "Incrementally iterate though a key",
"complexity": "O(N) where N is the number of ids in the key", "complexity": "O(N) where N is the number of ids in the key",
@ -243,6 +242,18 @@
"type": "pattern", "type": "pattern",
"optional": true "optional": true
}, },
{
"name": "order",
"optional": true,
"enumargs": [
{
"name": "ASC"
},
{
"name": "DESC"
}
]
},
{ {
"command": "WHERE", "command": "WHERE",
"name": ["field","min","max"], "name": ["field","min","max"],

View File

@ -378,7 +378,6 @@ var commandsJSON = `{
"since": "1.0.0", "since": "1.0.0",
"group": "keys" "group": "keys"
}, },
"SCAN": { "SCAN": {
"summary": "Incrementally iterate though a key", "summary": "Incrementally iterate though a key",
"complexity": "O(N) where N is the number of ids in the key", "complexity": "O(N) where N is the number of ids in the key",
@ -405,6 +404,18 @@ var commandsJSON = `{
"type": "pattern", "type": "pattern",
"optional": true "optional": true
}, },
{
"name": "order",
"optional": true,
"enumargs": [
{
"name": "ASC"
},
{
"name": "DESC"
}
]
},
{ {
"command": "WHERE", "command": "WHERE",
"name": ["field","min","max"], "name": ["field","min","max"],