tile38/controller/crud.go

621 lines
14 KiB
Go
Raw Normal View History

2016-03-05 02:08:16 +03:00
package controller
import (
"bytes"
2016-03-28 18:57:41 +03:00
"sort"
2016-03-05 02:08:16 +03:00
"strconv"
"strings"
"time"
"github.com/google/btree"
2016-03-28 18:57:41 +03:00
"github.com/tidwall/resp"
2016-03-06 17:55:00 +03:00
"github.com/tidwall/tile38/controller/collection"
2016-03-28 18:57:41 +03:00
"github.com/tidwall/tile38/controller/server"
2016-03-05 02:08:16 +03:00
"github.com/tidwall/tile38/geojson"
"github.com/tidwall/tile38/geojson/geohash"
)
2016-03-28 18:57:41 +03:00
type fvt struct {
field string
value float64
}
type byField []fvt
func (a byField) Len() int {
return len(a)
}
func (a byField) Less(i, j int) bool {
return a[i].field < a[j].field
}
func (a byField) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func orderFields(fmap map[string]int, fields []float64) []fvt {
var fv fvt
fvs := make([]fvt, 0, len(fmap))
for field, idx := range fmap {
if idx < len(fields) {
fv.field = field
fv.value = fields[idx]
2016-03-30 19:32:38 +03:00
if fv.value != 0 {
2016-03-28 18:57:41 +03:00
fvs = append(fvs, fv)
}
}
}
sort.Sort(byField(fvs))
return fvs
}
func (c *Controller) cmdGet(msg *server.Message) (string, error) {
2016-03-05 02:08:16 +03:00
start := time.Now()
2016-03-28 18:57:41 +03:00
vs := msg.Values[1:]
var ok bool
2016-03-05 02:08:16 +03:00
var key, id, typ, sprecision string
2016-03-28 18:57:41 +03:00
if vs, key, ok = tokenval(vs); !ok || key == "" {
2016-03-05 02:08:16 +03:00
return "", errInvalidNumberOfArguments
}
2016-03-28 18:57:41 +03:00
if vs, id, ok = tokenval(vs); !ok || id == "" {
2016-03-05 02:08:16 +03:00
return "", errInvalidNumberOfArguments
}
2016-04-01 22:46:39 +03:00
withfields := false
if _, peek, ok := tokenval(vs); ok && strings.ToLower(peek) == "withfields" {
withfields = true
vs = vs[1:]
}
2016-03-05 02:08:16 +03:00
col := c.getCol(key)
if col == nil {
2016-03-28 18:57:41 +03:00
if msg.OutputType == server.RESP {
return "$-1\r\n", nil
}
2016-03-05 02:08:16 +03:00
return "", errKeyNotFound
}
o, fields, ok := col.Get(id)
if !ok {
2016-03-28 18:57:41 +03:00
if msg.OutputType == server.RESP {
return "$-1\r\n", nil
}
2016-03-05 02:08:16 +03:00
return "", errIDNotFound
}
2016-03-28 18:57:41 +03:00
vals := make([]resp.Value, 0, 2)
2016-03-05 02:08:16 +03:00
var buf bytes.Buffer
2016-03-28 18:57:41 +03:00
if msg.OutputType == server.JSON {
buf.WriteString(`{"ok":true`)
}
if vs, typ, ok = tokenval(vs); !ok || strings.ToLower(typ) == "object" {
if msg.OutputType == server.JSON {
buf.WriteString(`,"object":`)
buf.WriteString(o.JSON())
} else {
2016-04-01 22:46:39 +03:00
vals = append(vals, resp.StringValue(o.JSON()))
2016-03-28 18:57:41 +03:00
}
2016-03-05 02:08:16 +03:00
} else {
2016-03-28 18:57:41 +03:00
switch strings.ToLower(typ) {
2016-03-05 02:08:16 +03:00
default:
return "", errInvalidArgument(typ)
case "point":
2016-03-28 18:57:41 +03:00
point := o.CalculatedPoint()
if msg.OutputType == server.JSON {
buf.WriteString(`,"point":`)
buf.WriteString(point.ExternalJSON())
} else {
if point.Z != 0 {
vals = append(vals, resp.ArrayValue([]resp.Value{
resp.StringValue(strconv.FormatFloat(point.Y, 'f', -1, 64)),
resp.StringValue(strconv.FormatFloat(point.X, 'f', -1, 64)),
resp.StringValue(strconv.FormatFloat(point.Z, 'f', -1, 64)),
}))
} else {
vals = append(vals, resp.ArrayValue([]resp.Value{
resp.StringValue(strconv.FormatFloat(point.Y, 'f', -1, 64)),
resp.StringValue(strconv.FormatFloat(point.X, 'f', -1, 64)),
}))
}
}
2016-03-05 02:08:16 +03:00
case "hash":
2016-03-28 18:57:41 +03:00
if vs, sprecision, ok = tokenval(vs); !ok || sprecision == "" {
2016-03-05 02:08:16 +03:00
return "", errInvalidNumberOfArguments
}
2016-03-28 18:57:41 +03:00
if msg.OutputType == server.JSON {
buf.WriteString(`,"hash":`)
}
2016-03-05 02:08:16 +03:00
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
}
2016-03-28 18:57:41 +03:00
if msg.OutputType == server.JSON {
buf.WriteString(`"` + p + `"`)
} else {
2016-04-01 22:46:39 +03:00
vals = append(vals, resp.StringValue(p))
2016-03-28 18:57:41 +03:00
}
2016-03-05 02:08:16 +03:00
case "bounds":
2016-03-28 18:57:41 +03:00
bbox := o.CalculatedBBox()
if msg.OutputType == server.JSON {
buf.WriteString(`,"bounds":`)
buf.WriteString(bbox.ExternalJSON())
} else {
vals = append(vals, resp.ArrayValue([]resp.Value{
2016-03-29 00:16:21 +03:00
resp.ArrayValue([]resp.Value{
resp.FloatValue(bbox.Min.Y),
resp.FloatValue(bbox.Min.X),
}),
resp.ArrayValue([]resp.Value{
resp.FloatValue(bbox.Max.Y),
resp.FloatValue(bbox.Max.X),
}),
2016-03-28 18:57:41 +03:00
}))
}
2016-03-05 02:08:16 +03:00
}
}
2016-03-28 18:57:41 +03:00
if len(vs) != 0 {
2016-03-05 02:08:16 +03:00
return "", errInvalidNumberOfArguments
}
2016-04-01 22:46:39 +03:00
if withfields {
fvs := orderFields(col.FieldMap(), fields)
if len(fvs) > 0 {
fvals := make([]resp.Value, 0, len(fvs)*2)
2016-03-28 18:57:41 +03:00
if msg.OutputType == server.JSON {
2016-04-01 22:46:39 +03:00
buf.WriteString(`,"fields":{`)
}
for i, fv := range fvs {
if msg.OutputType == server.JSON {
if i > 0 {
buf.WriteString(`,`)
}
buf.WriteString(jsonString(fv.field) + ":" + strconv.FormatFloat(fv.value, 'f', -1, 64))
} else {
fvals = append(fvals, resp.StringValue(fv.field), resp.StringValue(strconv.FormatFloat(fv.value, 'f', -1, 64)))
2016-03-05 02:08:16 +03:00
}
2016-04-01 22:46:39 +03:00
i++
}
if msg.OutputType == server.JSON {
buf.WriteString(`}`)
2016-03-28 18:57:41 +03:00
} else {
2016-04-01 22:46:39 +03:00
vals = append(vals, resp.ArrayValue(fvals))
2016-03-05 02:08:16 +03:00
}
2016-03-28 18:57:41 +03:00
}
}
2016-04-01 22:46:39 +03:00
switch msg.OutputType {
case server.JSON:
2016-03-28 18:57:41 +03:00
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return buf.String(), nil
2016-04-01 22:46:39 +03:00
case server.RESP:
var oval resp.Value
if withfields {
oval = resp.ArrayValue(vals)
} else {
oval = vals[0]
}
data, err := oval.MarshalRESP()
if err != nil {
return "", err
}
return string(data), nil
2016-03-05 02:08:16 +03:00
}
2016-04-01 22:46:39 +03:00
return "", nil
2016-03-05 02:08:16 +03:00
}
2016-03-28 18:57:41 +03:00
func (c *Controller) cmdDel(msg *server.Message) (res string, d commandDetailsT, err error) {
start := time.Now()
vs := msg.Values[1:]
var ok bool
if vs, d.key, ok = tokenval(vs); !ok || d.key == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
if vs, d.id, ok = tokenval(vs); !ok || d.id == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
if len(vs) != 0 {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
found := false
2016-03-05 02:08:16 +03:00
col := c.getCol(d.key)
if col != nil {
2016-03-28 18:57:41 +03:00
d.obj, d.fields, ok = col.Remove(d.id)
if ok {
if col.Count() == 0 {
c.deleteCol(d.key)
2016-03-30 19:32:38 +03:00
d.revert = func() {
c.setCol(d.key, col)
col.ReplaceOrInsert(d.id, d.obj, nil, d.fields)
}
} else {
d.revert = func() {
col.ReplaceOrInsert(d.id, d.obj, nil, d.fields)
}
2016-03-28 18:57:41 +03:00
}
found = true
2016-03-05 02:08:16 +03:00
}
}
d.command = "del"
2016-03-28 18:57:41 +03:00
d.updated = found
2016-04-02 17:20:30 +03:00
d.timestamp = time.Now()
2016-03-28 18:57:41 +03:00
switch msg.OutputType {
case server.JSON:
res = `{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}"
case server.RESP:
if d.updated {
res = ":1\r\n"
} else {
res = ":0\r\n"
}
}
2016-03-05 02:08:16 +03:00
return
}
2016-03-28 18:57:41 +03:00
func (c *Controller) cmdDrop(msg *server.Message) (res string, d commandDetailsT, err error) {
start := time.Now()
vs := msg.Values[1:]
var ok bool
if vs, d.key, ok = tokenval(vs); !ok || d.key == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
if len(vs) != 0 {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
col := c.getCol(d.key)
if col != nil {
c.deleteCol(d.key)
2016-03-30 19:32:38 +03:00
d.revert = func() {
c.setCol(d.key, col)
}
2016-03-28 18:57:41 +03:00
d.updated = true
2016-03-05 02:08:16 +03:00
} else {
d.key = "" // ignore the details
2016-03-28 18:57:41 +03:00
d.updated = false
2016-03-05 02:08:16 +03:00
}
d.command = "drop"
2016-04-02 17:20:30 +03:00
d.timestamp = time.Now()
2016-03-28 18:57:41 +03:00
switch msg.OutputType {
case server.JSON:
res = `{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}"
case server.RESP:
if d.updated {
res = ":1\r\n"
} else {
res = ":0\r\n"
}
}
2016-03-05 02:08:16 +03:00
return
}
2016-03-28 18:57:41 +03:00
func (c *Controller) cmdFlushDB(msg *server.Message) (res string, d commandDetailsT, err error) {
start := time.Now()
vs := msg.Values[1:]
if len(vs) != 0 {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
c.cols = btree.New(16)
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"
2016-03-28 18:57:41 +03:00
d.updated = true
2016-04-02 17:20:30 +03:00
d.timestamp = time.Now()
2016-03-28 18:57:41 +03:00
switch msg.OutputType {
case server.JSON:
res = `{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}"
case server.RESP:
res = "+OK\r\n"
}
2016-03-05 02:08:16 +03:00
return
}
2016-03-28 18:57:41 +03:00
func (c *Controller) parseSetArgs(vs []resp.Value) (d commandDetailsT, fields []string, values []float64, etype string, evs []resp.Value, err error) {
var ok bool
2016-03-05 02:08:16 +03:00
var typ string
2016-03-28 18:57:41 +03:00
if vs, d.key, ok = tokenval(vs); !ok || d.key == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
if vs, d.id, ok = tokenval(vs); !ok || d.id == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
2016-03-05 02:08:16 +03:00
var arg string
2016-03-28 18:57:41 +03:00
var nvs []resp.Value
2016-03-05 02:08:16 +03:00
fields = make([]string, 0, 8)
values = make([]float64, 0, 8)
for {
2016-03-28 18:57:41 +03:00
if nvs, arg, ok = tokenval(vs); !ok || arg == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
if lc(arg, "field") {
2016-03-28 18:57:41 +03:00
vs = nvs
2016-03-05 02:08:16 +03:00
var name string
var svalue string
var value float64
2016-03-28 18:57:41 +03:00
if vs, name, ok = tokenval(vs); !ok || name == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
if isReservedFieldName(name) {
err = errInvalidArgument(name)
return
}
2016-03-28 18:57:41 +03:00
if vs, svalue, ok = tokenval(vs); !ok || svalue == "" {
2016-03-05 02:08:16 +03:00
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
}
2016-03-28 18:57:41 +03:00
if vs, typ, ok = tokenval(vs); !ok || typ == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
if len(vs) == 0 {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
etype = typ
2016-03-28 18:57:41 +03:00
evs = vs
2016-03-05 02:08:16 +03:00
switch {
default:
err = errInvalidArgument(typ)
return
case lc(typ, "point"):
var slat, slon, sz string
2016-03-28 18:57:41 +03:00
if vs, slat, ok = tokenval(vs); !ok || slat == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
if vs, slon, ok = tokenval(vs); !ok || slon == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
vs, sz, ok = tokenval(vs)
if !ok || sz == "" {
2016-03-05 02:08:16 +03:00
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
2016-03-28 18:57:41 +03:00
if vs, sminlat, ok = tokenval(vs); !ok || sminlat == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
if vs, sminlon, ok = tokenval(vs); !ok || sminlon == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
if vs, smaxlat, ok = tokenval(vs); !ok || smaxlat == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
if vs, smaxlon, ok = tokenval(vs); !ok || smaxlon == "" {
2016-03-05 02:08:16 +03:00
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{
2016-04-03 05:19:43 +03:00
{
{X: minlon, Y: minlat, Z: 0},
{X: minlon, Y: maxlat, Z: 0},
{X: maxlon, Y: maxlat, Z: 0},
{X: maxlon, Y: minlat, Z: 0},
{X: minlon, Y: minlat, Z: 0},
2016-03-05 02:08:16 +03:00
},
},
}
d.obj = g
case lc(typ, "hash"):
var sp geojson.SimplePoint
var shash string
2016-03-28 18:57:41 +03:00
if vs, shash, ok = tokenval(vs); !ok || shash == "" {
2016-03-05 02:08:16 +03:00
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-28 18:57:41 +03:00
var object string
if vs, object, ok = tokenval(vs); !ok || object == "" {
err = errInvalidNumberOfArguments
return
}
d.obj, err = geojson.ObjectJSON(object)
2016-03-05 02:08:16 +03:00
if err != nil {
return
}
}
2016-03-28 18:57:41 +03:00
if len(vs) != 0 {
err = errInvalidNumberOfArguments
}
2016-03-05 02:08:16 +03:00
return
}
2016-03-28 18:57:41 +03:00
func (c *Controller) cmdSet(msg *server.Message) (res string, d commandDetailsT, err error) {
start := time.Now()
vs := msg.Values[1:]
2016-03-05 02:08:16 +03:00
var fields []string
var values []float64
2016-03-28 18:57:41 +03:00
d, fields, values, _, _, err = c.parseSetArgs(vs)
2016-03-05 02:08:16 +03:00
if err != nil {
return
}
2016-03-30 19:32:38 +03:00
addedcol := false
2016-03-05 02:08:16 +03:00
col := c.getCol(d.key)
if col == nil {
col = collection.New()
c.setCol(d.key, col)
2016-03-30 19:32:38 +03:00
addedcol = true
2016-03-05 02:08:16 +03:00
}
d.oldObj, d.oldFields, d.fields = col.ReplaceOrInsert(d.id, d.obj, fields, values)
2016-03-30 19:32:38 +03:00
d.revert = func() {
if addedcol {
c.deleteCol(d.key)
} else if d.oldObj != nil {
col.ReplaceOrInsert(d.id, d.oldObj, nil, d.oldFields)
} else {
col.Remove(d.id)
}
}
2016-03-05 02:08:16 +03:00
d.command = "set"
2016-03-28 18:57:41 +03:00
d.updated = true // perhaps we should do a diff on the previous object?
2016-04-02 17:20:30 +03:00
fmap := col.FieldMap()
d.fmap = make(map[string]int)
for key, idx := range fmap {
d.fmap[key] = idx
}
d.timestamp = time.Now()
2016-03-28 18:57:41 +03:00
switch msg.OutputType {
case server.JSON:
res = `{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}"
case server.RESP:
res = "+OK\r\n"
}
2016-03-05 02:08:16 +03:00
return
}
2016-03-28 18:57:41 +03:00
func (c *Controller) parseFSetArgs(vs []resp.Value) (d commandDetailsT, err error) {
2016-03-05 02:08:16 +03:00
var svalue string
2016-03-28 18:57:41 +03:00
var ok bool
if vs, d.key, ok = tokenval(vs); !ok || d.key == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
if vs, d.id, ok = tokenval(vs); !ok || d.id == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
if vs, d.field, ok = tokenval(vs); !ok || d.field == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
if isReservedFieldName(d.field) {
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
if vs, svalue, ok = tokenval(vs); !ok || svalue == "" {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
2016-03-28 18:57:41 +03:00
if len(vs) != 0 {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
d.value, err = strconv.ParseFloat(svalue, 64)
if err != nil {
err = errInvalidArgument(svalue)
return
}
return
}
2016-03-28 18:57:41 +03:00
func (c *Controller) cmdFset(msg *server.Message) (res string, d commandDetailsT, err error) {
start := time.Now()
vs := msg.Values[1:]
d, err = c.parseFSetArgs(vs)
2016-03-05 02:08:16 +03:00
col := c.getCol(d.key)
if col == nil {
err = errKeyNotFound
return
}
var ok bool
2016-04-02 17:20:30 +03:00
d.obj, d.fields, d.updated, ok = col.SetField(d.id, d.field, d.value)
2016-03-05 02:08:16 +03:00
if !ok {
err = errIDNotFound
return
}
d.command = "fset"
2016-04-02 17:20:30 +03:00
d.timestamp = time.Now()
fmap := col.FieldMap()
d.fmap = make(map[string]int)
for key, idx := range fmap {
d.fmap[key] = idx
}
2016-03-28 18:57:41 +03:00
switch msg.OutputType {
case server.JSON:
res = `{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}"
case server.RESP:
2016-04-02 17:20:30 +03:00
if d.updated {
2016-03-28 18:57:41 +03:00
res = ":1\r\n"
} else {
res = ":0\r\n"
}
}
2016-03-05 02:08:16 +03:00
return
}