2016-03-05 02:08:16 +03:00
|
|
|
package controller
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"math"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/google/btree"
|
2016-03-06 17:55:00 +03:00
|
|
|
"github.com/tidwall/tile38/controller/collection"
|
2016-03-05 02:08:16 +03:00
|
|
|
"github.com/tidwall/tile38/geojson"
|
|
|
|
"github.com/tidwall/tile38/geojson/geohash"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (c *Controller) cmdGet(line string) (string, error) {
|
|
|
|
start := time.Now()
|
|
|
|
var key, id, typ, sprecision string
|
|
|
|
if line, key = token(line); key == "" {
|
|
|
|
return "", errInvalidNumberOfArguments
|
|
|
|
}
|
|
|
|
if line, id = token(line); id == "" {
|
|
|
|
return "", errInvalidNumberOfArguments
|
|
|
|
}
|
|
|
|
col := c.getCol(key)
|
|
|
|
if col == nil {
|
|
|
|
return "", errKeyNotFound
|
|
|
|
}
|
|
|
|
o, fields, ok := col.Get(id)
|
|
|
|
if !ok {
|
|
|
|
return "", errIDNotFound
|
|
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
|
|
buf.WriteString(`{"ok":true`)
|
|
|
|
if line, typ = token(line); typ == "" || strings.ToLower(typ) == "object" {
|
|
|
|
buf.WriteString(`,"object":`)
|
|
|
|
buf.WriteString(o.JSON())
|
|
|
|
} else {
|
|
|
|
ltyp := strings.ToLower(typ)
|
|
|
|
switch ltyp {
|
|
|
|
default:
|
|
|
|
return "", errInvalidArgument(typ)
|
|
|
|
case "point":
|
|
|
|
buf.WriteString(`,"point":`)
|
|
|
|
buf.WriteString(o.CalculatedPoint().ExternalJSON())
|
|
|
|
case "hash":
|
|
|
|
if line, sprecision = token(line); sprecision == "" {
|
|
|
|
return "", errInvalidNumberOfArguments
|
|
|
|
}
|
|
|
|
buf.WriteString(`,"hash":`)
|
|
|
|
precision, err := strconv.ParseInt(sprecision, 10, 64)
|
|
|
|
if err != nil || precision < 1 || precision > 64 {
|
|
|
|
return "", errInvalidArgument(sprecision)
|
|
|
|
}
|
|
|
|
p, err := o.Geohash(int(precision))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
buf.WriteString(`"` + p + `"`)
|
|
|
|
case "bounds":
|
|
|
|
buf.WriteString(`,"bounds":`)
|
|
|
|
buf.WriteString(o.CalculatedBBox().ExternalJSON())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if line != "" {
|
|
|
|
return "", errInvalidNumberOfArguments
|
|
|
|
}
|
|
|
|
fmap := col.FieldMap()
|
|
|
|
if len(fmap) > 0 {
|
|
|
|
buf.WriteString(`,"fields":{`)
|
|
|
|
var i int
|
|
|
|
for field, idx := range fmap {
|
|
|
|
if len(fields) > idx {
|
|
|
|
if !math.IsNaN(fields[idx]) {
|
|
|
|
if i > 0 {
|
|
|
|
buf.WriteString(`,`)
|
|
|
|
}
|
|
|
|
buf.WriteString(jsonString(field) + ":" + strconv.FormatFloat(fields[idx], 'f', -1, 64))
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buf.WriteString(`}`)
|
|
|
|
}
|
|
|
|
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
|
|
|
return buf.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Controller) cmdDel(line string) (d commandDetailsT, err error) {
|
|
|
|
if line, d.key = token(line); d.key == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, d.id = token(line); d.id == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line != "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
col := c.getCol(d.key)
|
|
|
|
if col != nil {
|
|
|
|
d.obj, d.fields, _ = col.Remove(d.id)
|
|
|
|
if col.Count() == 0 {
|
|
|
|
c.deleteCol(d.key)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
d.command = "del"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Controller) cmdDrop(line string) (d commandDetailsT, err error) {
|
|
|
|
if line, d.key = token(line); d.key == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line != "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
col := c.getCol(d.key)
|
|
|
|
if col != nil {
|
|
|
|
c.deleteCol(d.key)
|
|
|
|
} else {
|
|
|
|
d.key = "" // ignore the details
|
|
|
|
}
|
|
|
|
d.command = "drop"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Controller) cmdFlushDB(line string) (d commandDetailsT, err error) {
|
|
|
|
if line != "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c.cols = btree.New(16)
|
|
|
|
c.colsm = make(map[string]*collection.Collection)
|
2016-03-19 17:16:19 +03:00
|
|
|
c.hooks = make(map[string]*Hook)
|
|
|
|
c.hookcols = make(map[string]map[string]*Hook)
|
2016-03-05 02:08:16 +03:00
|
|
|
d.command = "flushdb"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Controller) parseSetArgs(line string) (d commandDetailsT, fields []string, values []float64, etype, eline string, err error) {
|
|
|
|
|
|
|
|
var typ string
|
|
|
|
if line, d.key = token(line); d.key == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, d.id = token(line); d.id == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var arg string
|
|
|
|
var nline string
|
|
|
|
fields = make([]string, 0, 8)
|
|
|
|
values = make([]float64, 0, 8)
|
|
|
|
for {
|
|
|
|
if nline, arg = token(line); arg == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if lc(arg, "field") {
|
|
|
|
line = nline
|
|
|
|
var name string
|
|
|
|
var svalue string
|
|
|
|
var value float64
|
|
|
|
if line, name = token(line); name == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if isReservedFieldName(name) {
|
|
|
|
err = errInvalidArgument(name)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, svalue = token(line); svalue == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
value, err = strconv.ParseFloat(svalue, 64)
|
|
|
|
if err != nil {
|
|
|
|
err = errInvalidArgument(svalue)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fields = append(fields, name)
|
|
|
|
values = append(values, value)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if line, typ = token(line); typ == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
etype = typ
|
|
|
|
eline = line
|
|
|
|
|
|
|
|
switch {
|
|
|
|
default:
|
|
|
|
err = errInvalidArgument(typ)
|
|
|
|
return
|
|
|
|
case lc(typ, "point"):
|
|
|
|
var slat, slon, sz string
|
|
|
|
if line, slat = token(line); slat == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, slon = token(line); slon == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
line, sz = token(line)
|
|
|
|
if sz == "" {
|
|
|
|
var sp geojson.SimplePoint
|
|
|
|
sp.Y, err = strconv.ParseFloat(slat, 64)
|
|
|
|
if err != nil {
|
|
|
|
err = errInvalidArgument(slat)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
sp.X, err = strconv.ParseFloat(slon, 64)
|
|
|
|
if err != nil {
|
|
|
|
err = errInvalidArgument(slon)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
d.obj = sp
|
|
|
|
} else {
|
|
|
|
var sp geojson.Point
|
|
|
|
sp.Coordinates.Y, err = strconv.ParseFloat(slat, 64)
|
|
|
|
if err != nil {
|
|
|
|
err = errInvalidArgument(slat)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
sp.Coordinates.X, err = strconv.ParseFloat(slon, 64)
|
|
|
|
if err != nil {
|
|
|
|
err = errInvalidArgument(slon)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
sp.Coordinates.Z, err = strconv.ParseFloat(sz, 64)
|
|
|
|
if err != nil {
|
|
|
|
err = errInvalidArgument(sz)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
d.obj = sp
|
|
|
|
}
|
|
|
|
case lc(typ, "bounds"):
|
|
|
|
var sminlat, sminlon, smaxlat, smaxlon string
|
|
|
|
if line, sminlat = token(line); sminlat == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, sminlon = token(line); sminlon == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, smaxlat = token(line); smaxlat == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, smaxlon = token(line); smaxlon == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var minlat, minlon, maxlat, maxlon float64
|
|
|
|
minlat, err = strconv.ParseFloat(sminlat, 64)
|
|
|
|
if err != nil {
|
|
|
|
err = errInvalidArgument(sminlat)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
minlon, err = strconv.ParseFloat(sminlon, 64)
|
|
|
|
if err != nil {
|
|
|
|
err = errInvalidArgument(sminlon)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
maxlat, err = strconv.ParseFloat(smaxlat, 64)
|
|
|
|
if err != nil {
|
|
|
|
err = errInvalidArgument(smaxlat)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
maxlon, err = strconv.ParseFloat(smaxlon, 64)
|
|
|
|
if err != nil {
|
|
|
|
err = errInvalidArgument(smaxlon)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
g := geojson.Polygon{
|
|
|
|
Coordinates: [][]geojson.Position{
|
|
|
|
[]geojson.Position{
|
|
|
|
geojson.Position{X: minlon, Y: minlat, Z: 0},
|
|
|
|
geojson.Position{X: minlon, Y: maxlat, Z: 0},
|
|
|
|
geojson.Position{X: maxlon, Y: maxlat, Z: 0},
|
|
|
|
geojson.Position{X: maxlon, Y: minlat, Z: 0},
|
|
|
|
geojson.Position{X: minlon, Y: minlat, Z: 0},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
d.obj = g
|
|
|
|
case lc(typ, "hash"):
|
|
|
|
var sp geojson.SimplePoint
|
|
|
|
var shash string
|
|
|
|
if line, shash = token(line); shash == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var lat, lon float64
|
|
|
|
lat, lon, err = geohash.Decode(shash)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
sp.X = lon
|
|
|
|
sp.Y = lat
|
|
|
|
d.obj = sp
|
|
|
|
case lc(typ, "object"):
|
2016-03-05 23:29:49 +03:00
|
|
|
d.obj, err = geojson.ObjectJSON(line)
|
2016-03-05 02:08:16 +03:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Controller) cmdSet(line string) (d commandDetailsT, err error) {
|
|
|
|
var fields []string
|
|
|
|
var values []float64
|
|
|
|
d, fields, values, _, _, err = c.parseSetArgs(line)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
col := c.getCol(d.key)
|
|
|
|
if col == nil {
|
|
|
|
col = collection.New()
|
|
|
|
c.setCol(d.key, col)
|
|
|
|
}
|
|
|
|
d.oldObj, d.oldFields, d.fields = col.ReplaceOrInsert(d.id, d.obj, fields, values)
|
|
|
|
d.command = "set"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Controller) parseFSetArgs(line string) (d commandDetailsT, err error) {
|
|
|
|
var svalue string
|
|
|
|
if line, d.key = token(line); d.key == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, d.id = token(line); d.id == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, d.field = token(line); d.field == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if isReservedFieldName(d.field) {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, svalue = token(line); svalue == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line != "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
d.value, err = strconv.ParseFloat(svalue, 64)
|
|
|
|
if err != nil {
|
|
|
|
err = errInvalidArgument(svalue)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Controller) cmdFset(line string) (d commandDetailsT, err error) {
|
|
|
|
d, err = c.parseFSetArgs(line)
|
|
|
|
col := c.getCol(d.key)
|
|
|
|
if col == nil {
|
|
|
|
err = errKeyNotFound
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var ok bool
|
|
|
|
d.obj, d.fields, ok = col.SetField(d.id, d.field, d.value)
|
|
|
|
if !ok {
|
|
|
|
err = errIDNotFound
|
|
|
|
return
|
|
|
|
}
|
|
|
|
d.command = "fset"
|
|
|
|
return
|
|
|
|
}
|