mirror of https://github.com/tidwall/tile38.git
Implement test command
This commit is contained in:
parent
a300cb2bf8
commit
c849ab19ac
|
@ -620,6 +620,8 @@ func (c *Server) commandInScript(msg *Message) (
|
|||
res, err = c.cmdType(msg)
|
||||
case "keys":
|
||||
res, err = c.cmdKeys(msg)
|
||||
case "test":
|
||||
res, err = c.cmdTest(msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -668,7 +670,7 @@ func (c *Server) luaTile38AtomicRW(msg *Message) (resp.Value, error) {
|
|||
return resp.NullValue(), errReadOnly
|
||||
}
|
||||
case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search",
|
||||
"ttl", "bounds", "server", "info", "type", "jget":
|
||||
"ttl", "bounds", "server", "info", "type", "jget", "test":
|
||||
// read operations
|
||||
if c.config.followHost() != "" && !c.fcuponce {
|
||||
return resp.NullValue(), errCatchingUp
|
||||
|
@ -700,7 +702,7 @@ func (c *Server) luaTile38AtomicRO(msg *Message) (resp.Value, error) {
|
|||
return resp.NullValue(), errReadOnly
|
||||
|
||||
case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search",
|
||||
"ttl", "bounds", "server", "info", "type", "jget":
|
||||
"ttl", "bounds", "server", "info", "type", "jget", "test":
|
||||
// read operations
|
||||
if c.config.followHost() != "" && !c.fcuponce {
|
||||
return resp.NullValue(), errCatchingUp
|
||||
|
@ -735,7 +737,7 @@ func (c *Server) luaTile38NonAtomic(msg *Message) (resp.Value, error) {
|
|||
return resp.NullValue(), errReadOnly
|
||||
}
|
||||
case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search",
|
||||
"ttl", "bounds", "server", "info", "type", "jget":
|
||||
"ttl", "bounds", "server", "info", "type", "jget", "test":
|
||||
// read operations
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
|
|
@ -1203,6 +1203,8 @@ func (server *Server) command(msg *Message, client *Client) (
|
|||
res, err = server.cmdPsubscribe(msg)
|
||||
case "publish":
|
||||
res, err = server.cmdPublish(msg)
|
||||
case "test":
|
||||
res, err = server.cmdTest(msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,300 @@
|
|||
// TEST command: spatial tests without walking the tree.
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mmcloughlin/geohash"
|
||||
"github.com/tidwall/geojson"
|
||||
"github.com/tidwall/geojson/geometry"
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/tile38/internal/bing"
|
||||
"github.com/tidwall/tile38/internal/clip"
|
||||
)
|
||||
|
||||
func (s * Server) parseArea(ovs[]string, doClip bool) (vs []string, o geojson.Object, err error) {
|
||||
var ok bool
|
||||
var typ string
|
||||
vs = ovs[:]
|
||||
if vs, typ, ok = tokenval(vs); !ok || typ == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
ltyp := strings.ToLower(typ)
|
||||
switch ltyp {
|
||||
case "point":
|
||||
var slat, slon string
|
||||
if vs, slat, ok = tokenval(vs); !ok || slat == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, slon, ok = tokenval(vs); !ok || slon == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
var lat, lon float64
|
||||
if lat, err = strconv.ParseFloat(slat, 64); err != nil {
|
||||
err = errInvalidArgument(slat)
|
||||
return
|
||||
}
|
||||
if lon, err = strconv.ParseFloat(slon, 64); err != nil {
|
||||
err = errInvalidArgument(slon)
|
||||
return
|
||||
}
|
||||
o = geojson.NewPoint(geometry.Point{X: lon, Y: lat})
|
||||
case "circle":
|
||||
if doClip {
|
||||
err = errInvalidArgument("cannot clip with " + ltyp)
|
||||
return
|
||||
}
|
||||
var slat, slon, smeters string
|
||||
if vs, slat, ok = tokenval(vs); !ok || slat == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, slon, ok = tokenval(vs); !ok || slon == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
var lat, lon, meters float64
|
||||
if lat, err = strconv.ParseFloat(slat, 64); err != nil {
|
||||
err = errInvalidArgument(slat)
|
||||
return
|
||||
}
|
||||
if lon, err = strconv.ParseFloat(slon, 64); err != nil {
|
||||
err = errInvalidArgument(slon)
|
||||
return
|
||||
}
|
||||
if vs, smeters, ok = tokenval(vs); !ok || smeters == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if meters, err = strconv.ParseFloat(smeters, 64); err != nil {
|
||||
err = errInvalidArgument(smeters)
|
||||
return
|
||||
}
|
||||
if meters < 0 {
|
||||
err = errInvalidArgument(smeters)
|
||||
return
|
||||
}
|
||||
o = geojson.NewCircle(geometry.Point{X: lon, Y: lat}, meters, defaultCircleSteps)
|
||||
case "object":
|
||||
if doClip {
|
||||
err = errInvalidArgument("cannot clip with " + ltyp)
|
||||
return
|
||||
}
|
||||
var obj string
|
||||
if vs, obj, ok = tokenval(vs); !ok || obj == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
o, err = geojson.Parse(obj, &s.geomParseOpts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case "bounds":
|
||||
var sminLat, sminLon, smaxlat, smaxlon string
|
||||
if vs, sminLat, ok = tokenval(vs); !ok || sminLat == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, sminLon, ok = tokenval(vs); !ok || sminLon == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, smaxlat, ok = tokenval(vs); !ok || smaxlat == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, smaxlon, ok = tokenval(vs); !ok || smaxlon == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
var minLat, minLon, maxLat, maxLon float64
|
||||
if minLat, err = strconv.ParseFloat(sminLat, 64); err != nil {
|
||||
err = errInvalidArgument(sminLat)
|
||||
return
|
||||
}
|
||||
if minLon, err = strconv.ParseFloat(sminLon, 64); err != nil {
|
||||
err = errInvalidArgument(sminLon)
|
||||
return
|
||||
}
|
||||
if maxLat, err = strconv.ParseFloat(smaxlat, 64); err != nil {
|
||||
err = errInvalidArgument(smaxlat)
|
||||
return
|
||||
}
|
||||
if maxLon, err = strconv.ParseFloat(smaxlon, 64); err != nil {
|
||||
err = errInvalidArgument(smaxlon)
|
||||
return
|
||||
}
|
||||
o = geojson.NewRect(geometry.Rect{
|
||||
Min: geometry.Point{X: minLon, Y: minLat},
|
||||
Max: geometry.Point{X: maxLon, Y: maxLat},
|
||||
})
|
||||
case "hash":
|
||||
var hash string
|
||||
if vs, hash, ok = tokenval(vs); !ok || hash == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
box := geohash.BoundingBox(hash)
|
||||
o = geojson.NewRect(geometry.Rect{
|
||||
Min: geometry.Point{X: box.MinLng, Y: box.MinLat},
|
||||
Max: geometry.Point{X: box.MaxLng, Y: box.MaxLat},
|
||||
})
|
||||
case "quadkey":
|
||||
var key string
|
||||
if vs, key, ok = tokenval(vs); !ok || key == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
var minLat, minLon, maxLat, maxLon float64
|
||||
minLat, minLon, maxLat, maxLon, err = bing.QuadKeyToBounds(key)
|
||||
if err != nil {
|
||||
err = errInvalidArgument(key)
|
||||
return
|
||||
}
|
||||
o = geojson.NewRect(geometry.Rect{
|
||||
Min: geometry.Point{X: minLon, Y: minLat},
|
||||
Max: geometry.Point{X: maxLon, Y: maxLat},
|
||||
})
|
||||
case "tile":
|
||||
var sx, sy, sz string
|
||||
if vs, sx, ok = tokenval(vs); !ok || sx == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, sy, ok = tokenval(vs); !ok || sy == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, sz, ok = tokenval(vs); !ok || sz == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
var x, y int64
|
||||
var z uint64
|
||||
if x, err = strconv.ParseInt(sx, 10, 64); err != nil {
|
||||
err = errInvalidArgument(sx)
|
||||
return
|
||||
}
|
||||
if y, err = strconv.ParseInt(sy, 10, 64); err != nil {
|
||||
err = errInvalidArgument(sy)
|
||||
return
|
||||
}
|
||||
if z, err = strconv.ParseUint(sz, 10, 64); err != nil {
|
||||
err = errInvalidArgument(sz)
|
||||
return
|
||||
}
|
||||
var minLat, minLon, maxLat, maxLon float64
|
||||
minLat, minLon, maxLat, maxLon = bing.TileXYToBounds(x, y, z)
|
||||
o = geojson.NewRect(geometry.Rect{
|
||||
Min: geometry.Point{X: minLon, Y: minLat},
|
||||
Max: geometry.Point{X: maxLon, Y: maxLat},
|
||||
})
|
||||
case "get":
|
||||
if doClip {
|
||||
err = errInvalidArgument("cannot clip with " + ltyp)
|
||||
return
|
||||
}
|
||||
var key, id string
|
||||
if vs, key, ok = tokenval(vs); !ok || key == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, id, ok = tokenval(vs); !ok || id == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
col := s.getCol(key)
|
||||
if col == nil {
|
||||
err = errKeyNotFound
|
||||
return
|
||||
}
|
||||
o, _, ok = col.Get(id)
|
||||
if !ok {
|
||||
err = errIDNotFound
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Server) cmdTest (msg *Message) (res resp.Value, err error) {
|
||||
start := time.Now()
|
||||
vs := msg.Args[1:]
|
||||
|
||||
var ok bool
|
||||
var test string
|
||||
var obj1, obj2, clipped geojson.Object
|
||||
if vs, obj1, err = s.parseArea(vs, false); err != nil {
|
||||
return
|
||||
}
|
||||
if vs, test, ok = tokenval(vs); !ok || test == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
lTest := strings.ToLower(test)
|
||||
if lTest != "within" && lTest != "intersects" {
|
||||
err = errInvalidArgument(test)
|
||||
return
|
||||
}
|
||||
var wtok string
|
||||
var nvs []string
|
||||
var doClip bool
|
||||
nvs, wtok, ok = tokenval(vs)
|
||||
if ok && len(wtok) > 0 {
|
||||
switch strings.ToLower(wtok) {
|
||||
case "clip":
|
||||
vs = nvs
|
||||
if lTest != "intersects" {
|
||||
err = errInvalidArgument("cannot clip with" + wtok)
|
||||
return
|
||||
}
|
||||
doClip = true
|
||||
}
|
||||
}
|
||||
if vs, obj2, err = s.parseArea(vs, doClip); err != nil {
|
||||
return
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
err = errInvalidNumberOfArguments
|
||||
}
|
||||
|
||||
var result int
|
||||
if lTest == "within" {
|
||||
if obj1.Within(obj2) {
|
||||
result = 1
|
||||
}
|
||||
} else if lTest == "intersects" {
|
||||
if obj1.Intersects(obj2) {
|
||||
result = 1
|
||||
if doClip {
|
||||
clipped = clip.Clip(obj1, obj2)
|
||||
}
|
||||
}
|
||||
}
|
||||
switch msg.OutputType {
|
||||
case JSON:
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`{"ok":true`)
|
||||
buf.WriteString(fmt.Sprintf(`,"result":%d`, result))
|
||||
if clipped != nil {
|
||||
buf.WriteString(`,"object":` + clipped.JSON())
|
||||
}
|
||||
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.StringValue(buf.String()), nil
|
||||
case RESP:
|
||||
if clipped != nil {
|
||||
return resp.ArrayValue([]resp.Value{
|
||||
resp.IntegerValue(result),
|
||||
resp.StringValue(clipped.JSON())}), nil
|
||||
}
|
||||
return resp.IntegerValue(result), nil
|
||||
}
|
||||
return NOMessage, nil
|
||||
}
|
Loading…
Reference in New Issue