tile38/internal/server/crud.go

1011 lines
22 KiB
Go
Raw Normal View History

package server
2016-03-05 02:08:16 +03:00
import (
"bytes"
2022-09-20 03:47:38 +03:00
"errors"
2016-03-05 02:08:16 +03:00
"strconv"
"strings"
"time"
"github.com/mmcloughlin/geohash"
"github.com/tidwall/geojson"
"github.com/tidwall/geojson/geometry"
2016-03-28 18:57:41 +03:00
"github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/collection"
2022-09-20 03:47:38 +03:00
"github.com/tidwall/tile38/internal/field"
"github.com/tidwall/tile38/internal/glob"
2022-09-21 00:20:53 +03:00
"github.com/tidwall/tile38/internal/object"
2016-03-05 02:08:16 +03:00
)
2021-12-09 19:24:26 +03:00
func (s *Server) cmdBounds(msg *Message) (resp.Value, error) {
start := time.Now()
vs := msg.Args[1:]
var ok bool
var key string
if vs, key, ok = tokenval(vs); !ok || key == "" {
return NOMessage, errInvalidNumberOfArguments
}
if len(vs) != 0 {
return NOMessage, errInvalidNumberOfArguments
}
col, _ := s.cols.Get(key)
if col == nil {
if msg.OutputType == RESP {
2017-10-05 18:20:40 +03:00
return resp.NullValue(), nil
}
return NOMessage, errKeyNotFound
}
vals := make([]resp.Value, 0, 2)
var buf bytes.Buffer
if msg.OutputType == JSON {
buf.WriteString(`{"ok":true`)
}
2017-08-11 03:32:40 +03:00
minX, minY, maxX, maxY := col.Bounds()
2016-10-03 21:37:16 +03:00
bbox := geojson.NewRect(geometry.Rect{
Min: geometry.Point{X: minX, Y: minY},
Max: geometry.Point{X: maxX, Y: maxY},
})
if msg.OutputType == JSON {
buf.WriteString(`,"bounds":`)
buf.WriteString(string(bbox.AppendJSON(nil)))
} else {
vals = append(vals, resp.ArrayValue([]resp.Value{
resp.ArrayValue([]resp.Value{
2016-10-03 21:37:16 +03:00
resp.FloatValue(minX),
resp.FloatValue(minY),
}),
resp.ArrayValue([]resp.Value{
2016-10-03 21:37:16 +03:00
resp.FloatValue(maxX),
resp.FloatValue(maxY),
}),
}))
}
switch msg.OutputType {
case JSON:
buf.WriteString(`,"elapsed":"` + time.Since(start).String() + "\"}")
2017-10-05 18:20:40 +03:00
return resp.StringValue(buf.String()), nil
case RESP:
2017-10-05 18:20:40 +03:00
return vals[0], nil
}
return NOMessage, nil
}
2017-10-05 18:20:40 +03:00
2021-12-09 19:24:26 +03:00
func (s *Server) cmdType(msg *Message) (resp.Value, error) {
2016-08-26 23:42:52 +03:00
start := time.Now()
vs := msg.Args[1:]
2016-08-26 23:42:52 +03:00
var ok bool
var key string
if _, key, ok = tokenval(vs); !ok || key == "" {
return NOMessage, errInvalidNumberOfArguments
2016-08-26 23:42:52 +03:00
}
col, _ := s.cols.Get(key)
2016-08-26 23:42:52 +03:00
if col == nil {
if msg.OutputType == RESP {
2017-10-05 18:20:40 +03:00
return resp.SimpleStringValue("none"), nil
2016-08-26 23:42:52 +03:00
}
return NOMessage, errKeyNotFound
2016-08-26 23:42:52 +03:00
}
typ := "hash"
switch msg.OutputType {
case JSON:
return resp.StringValue(`{"ok":true,"type":` + string(typ) + `,"elapsed":"` + time.Since(start).String() + "\"}"), nil
case RESP:
2017-10-05 18:20:40 +03:00
return resp.SimpleStringValue(typ), nil
2016-08-26 23:42:52 +03:00
}
return NOMessage, nil
2016-08-26 23:42:52 +03:00
}
2016-03-28 18:57:41 +03:00
2021-12-09 19:24:26 +03:00
func (s *Server) cmdGet(msg *Message) (resp.Value, error) {
2016-03-05 02:08:16 +03:00
start := time.Now()
vs := msg.Args[1:]
2016-03-28 18:57:41 +03:00
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 == "" {
return NOMessage, errInvalidNumberOfArguments
2016-03-05 02:08:16 +03:00
}
2016-03-28 18:57:41 +03:00
if vs, id, ok = tokenval(vs); !ok || id == "" {
return NOMessage, errInvalidNumberOfArguments
2016-03-05 02:08:16 +03:00
}
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:]
}
col, _ := s.cols.Get(key)
2016-03-05 02:08:16 +03:00
if col == nil {
if msg.OutputType == RESP {
2017-10-05 18:20:40 +03:00
return resp.NullValue(), nil
2016-03-28 18:57:41 +03:00
}
return NOMessage, errKeyNotFound
2016-03-05 02:08:16 +03:00
}
2022-09-21 00:20:53 +03:00
o := col.Get(id)
ok = o != nil
2016-03-05 02:08:16 +03:00
if !ok {
if msg.OutputType == RESP {
2017-10-05 18:20:40 +03:00
return resp.NullValue(), nil
2016-03-28 18:57:41 +03:00
}
return NOMessage, errIDNotFound
2016-03-05 02:08:16 +03:00
}
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
if msg.OutputType == JSON {
2016-03-28 18:57:41 +03:00
buf.WriteString(`{"ok":true`)
}
2016-07-10 23:23:50 +03:00
vs, typ, ok = tokenval(vs)
typ = strings.ToLower(typ)
if !ok {
typ = "object"
}
switch typ {
default:
return NOMessage, errInvalidArgument(typ)
2016-07-10 23:23:50 +03:00
case "object":
if msg.OutputType == JSON {
2016-03-28 18:57:41 +03:00
buf.WriteString(`,"object":`)
2022-09-21 00:20:53 +03:00
buf.WriteString(string(o.Geo().AppendJSON(nil)))
2016-03-28 18:57:41 +03:00
} else {
2022-09-21 00:20:53 +03:00
vals = append(vals, resp.StringValue(o.Geo().String()))
2016-03-28 18:57:41 +03:00
}
2016-07-10 23:23:50 +03:00
case "point":
if msg.OutputType == JSON {
2016-07-10 23:23:50 +03:00
buf.WriteString(`,"point":`)
2022-09-21 00:20:53 +03:00
buf.Write(appendJSONSimplePoint(nil, o.Geo()))
2016-07-10 23:23:50 +03:00
} else {
2022-09-21 00:20:53 +03:00
point := o.Geo().Center()
z := extractZCoordinate(o.Geo())
if z != 0 {
2016-07-10 23:23:50 +03:00
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(z, 'f', -1, 64)),
2016-07-10 23:23:50 +03:00
}))
2016-03-28 18:57:41 +03:00
} else {
vals = append(vals, resp.ArrayValue([]resp.Value{
2016-07-10 23:23:50 +03:00
resp.StringValue(strconv.FormatFloat(point.Y, 'f', -1, 64)),
resp.StringValue(strconv.FormatFloat(point.X, 'f', -1, 64)),
2016-03-28 18:57:41 +03:00
}))
}
2016-03-05 02:08:16 +03:00
}
2016-07-10 23:23:50 +03:00
case "hash":
if vs, sprecision, ok = tokenval(vs); !ok || sprecision == "" {
return NOMessage, errInvalidNumberOfArguments
2016-07-10 23:23:50 +03:00
}
if msg.OutputType == JSON {
2016-07-10 23:23:50 +03:00
buf.WriteString(`,"hash":`)
}
precision, err := strconv.ParseInt(sprecision, 10, 64)
2020-08-12 01:11:06 +03:00
if err != nil || precision < 1 || precision > 12 {
return NOMessage, errInvalidArgument(sprecision)
2016-07-10 23:23:50 +03:00
}
2022-09-21 00:20:53 +03:00
center := o.Geo().Center()
p := geohash.EncodeWithPrecision(center.Y, center.X, uint(precision))
if msg.OutputType == JSON {
2016-07-10 23:23:50 +03:00
buf.WriteString(`"` + p + `"`)
} else {
vals = append(vals, resp.StringValue(p))
}
case "bounds":
if msg.OutputType == JSON {
2016-07-10 23:23:50 +03:00
buf.WriteString(`,"bounds":`)
2022-09-21 00:20:53 +03:00
buf.Write(appendJSONSimpleBounds(nil, o.Geo()))
2016-07-10 23:23:50 +03:00
} else {
bbox := o.Rect()
2016-07-10 23:23:50 +03:00
vals = append(vals, resp.ArrayValue([]resp.Value{
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-05 02:08:16 +03:00
}
2016-07-10 23:23:50 +03:00
2016-03-28 18:57:41 +03:00
if len(vs) != 0 {
return NOMessage, errInvalidNumberOfArguments
2016-03-05 02:08:16 +03:00
}
2016-04-01 22:46:39 +03:00
if withfields {
2022-09-21 00:20:53 +03:00
nfields := o.Fields().Len()
2022-09-20 03:47:38 +03:00
if nfields > 0 {
fvals := make([]resp.Value, 0, nfields*2)
if msg.OutputType == JSON {
2016-04-01 22:46:39 +03:00
buf.WriteString(`,"fields":{`)
}
2022-09-20 03:47:38 +03:00
var i int
2022-09-21 00:20:53 +03:00
o.Fields().Scan(func(f field.Field) bool {
if msg.OutputType == JSON {
2016-04-01 22:46:39 +03:00
if i > 0 {
buf.WriteString(`,`)
}
2022-09-20 03:47:38 +03:00
buf.WriteString(jsonString(f.Name()) + ":" + f.Value().JSON())
2016-04-01 22:46:39 +03:00
} else {
2022-09-20 03:47:38 +03:00
fvals = append(fvals,
resp.StringValue(f.Name()), resp.StringValue(f.Value().Data()))
2016-03-05 02:08:16 +03:00
}
2016-04-01 22:46:39 +03:00
i++
2022-09-20 03:47:38 +03:00
return true
})
if msg.OutputType == JSON {
2016-04-01 22:46:39 +03:00
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 JSON:
buf.WriteString(`,"elapsed":"` + time.Since(start).String() + "\"}")
2017-10-05 18:20:40 +03:00
return resp.StringValue(buf.String()), nil
case RESP:
2016-04-01 22:46:39 +03:00
var oval resp.Value
if withfields {
oval = resp.ArrayValue(vals)
} else {
oval = vals[0]
}
2017-10-05 18:20:40 +03:00
return oval, nil
2016-03-05 02:08:16 +03:00
}
return NOMessage, nil
2016-03-05 02:08:16 +03:00
}
2022-09-21 00:20:53 +03:00
// DEL key id [ERRON404]
func (s *Server) cmdDel(msg *Message) (resp.Value, commandDetails, error) {
2016-03-28 18:57:41 +03:00
start := time.Now()
2022-09-21 00:20:53 +03:00
// >> Args
args := msg.Args
if len(args) < 3 {
return retwerr(errInvalidNumberOfArguments)
2016-03-05 02:08:16 +03:00
}
2022-09-21 00:20:53 +03:00
key := args[1]
id := args[2]
2022-03-09 01:58:23 +03:00
erron404 := false
2022-09-21 00:20:53 +03:00
for i := 3; i < len(args); i++ {
switch strings.ToLower(args[i]) {
case "erron404":
2022-03-09 01:58:23 +03:00
erron404 = true
2022-09-21 00:20:53 +03:00
default:
return retwerr(errInvalidArgument(args[i]))
2022-03-09 01:58:23 +03:00
}
}
2022-09-21 00:20:53 +03:00
// >> Operation
updated := false
var old *object.Object
col, _ := s.cols.Get(key)
2016-03-05 02:08:16 +03:00
if col != nil {
2022-09-21 00:20:53 +03:00
old = col.Delete(id)
if old != nil {
2016-07-13 07:59:36 +03:00
if col.Count() == 0 {
2022-09-21 00:20:53 +03:00
s.cols.Delete(key)
2016-03-28 18:57:41 +03:00
}
2022-09-21 00:20:53 +03:00
updated = true
2022-03-09 01:58:23 +03:00
} else if erron404 {
2022-09-21 00:20:53 +03:00
return retwerr(errIDNotFound)
2016-03-05 02:08:16 +03:00
}
2022-03-09 01:58:23 +03:00
} else if erron404 {
2022-09-21 00:20:53 +03:00
return retwerr(errKeyNotFound)
2016-03-05 02:08:16 +03:00
}
2022-09-21 00:20:53 +03:00
s.groupDisconnectObject(key, id)
// >> Response
var d commandDetails
2016-03-05 02:08:16 +03:00
d.command = "del"
2022-09-21 00:20:53 +03:00
d.key = key
d.obj = old
d.updated = updated
2016-04-02 17:20:30 +03:00
d.timestamp = time.Now()
2022-09-21 00:20:53 +03:00
var res resp.Value
2016-03-28 18:57:41 +03:00
switch msg.OutputType {
case JSON:
2022-09-21 00:20:53 +03:00
res = resp.StringValue(`{"ok":true,"elapsed":"` +
time.Since(start).String() + "\"}")
case RESP:
2016-03-28 18:57:41 +03:00
if d.updated {
2017-10-05 18:20:40 +03:00
res = resp.IntegerValue(1)
2016-03-28 18:57:41 +03:00
} else {
2017-10-05 18:20:40 +03:00
res = resp.IntegerValue(0)
2016-03-28 18:57:41 +03:00
}
}
2022-09-21 00:20:53 +03:00
return res, d, nil
2016-03-05 02:08:16 +03:00
}
2021-12-09 19:24:26 +03:00
func (s *Server) cmdPdel(msg *Message) (res resp.Value, d commandDetails, err error) {
start := time.Now()
vs := msg.Args[1:]
var ok bool
if vs, d.key, ok = tokenval(vs); !ok || d.key == "" {
err = errInvalidNumberOfArguments
return
}
if vs, d.pattern, ok = tokenval(vs); !ok || d.pattern == "" {
err = errInvalidNumberOfArguments
return
}
if len(vs) != 0 {
err = errInvalidNumberOfArguments
return
}
now := time.Now()
2022-09-21 00:20:53 +03:00
iter := func(o *object.Object) bool {
if match, _ := glob.Match(d.pattern, o.ID()); match {
2018-11-24 01:53:33 +03:00
d.children = append(d.children, &commandDetails{
command: "del",
updated: true,
timestamp: now,
key: d.key,
2022-09-21 00:20:53 +03:00
obj: o,
})
}
return true
}
var expired int
col, _ := s.cols.Get(d.key)
if col != nil {
g := glob.Parse(d.pattern, false)
if g.Limits[0] == "" && g.Limits[1] == "" {
2019-04-24 15:09:41 +03:00
col.Scan(false, nil, msg.Deadline, iter)
} else {
2019-04-24 15:09:41 +03:00
col.ScanRange(g.Limits[0], g.Limits[1], false, nil, msg.Deadline, iter)
}
var atLeastOneNotDeleted bool
for i, dc := range d.children {
2022-09-21 00:20:53 +03:00
old := col.Delete(dc.obj.ID())
if old == nil {
d.children[i].command = "?"
atLeastOneNotDeleted = true
} else {
2022-09-21 00:20:53 +03:00
dc.obj = old
d.children[i] = dc
}
2022-09-21 00:20:53 +03:00
s.groupDisconnectObject(dc.key, dc.obj.ID())
}
if atLeastOneNotDeleted {
2018-11-24 01:53:33 +03:00
var nchildren []*commandDetails
for _, dc := range d.children {
if dc.command == "del" {
nchildren = append(nchildren, dc)
}
}
d.children = nchildren
}
if col.Count() == 0 {
s.cols.Delete(d.key)
}
}
d.command = "pdel"
d.updated = len(d.children) > 0
d.timestamp = now
d.parent = true
switch msg.OutputType {
case JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Since(start).String() + "\"}")
case RESP:
total := len(d.children) - expired
if total < 0 {
total = 0
}
2017-10-05 18:20:40 +03:00
res = resp.IntegerValue(total)
}
return
}
2021-12-09 19:24:26 +03:00
func (s *Server) cmdDrop(msg *Message) (res resp.Value, d commandDetails, err error) {
2016-03-28 18:57:41 +03:00
start := time.Now()
vs := msg.Args[1:]
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 len(vs) != 0 {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
col, _ := s.cols.Get(d.key)
2016-03-05 02:08:16 +03:00
if col != nil {
s.cols.Delete(d.key)
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
}
2021-12-09 19:24:26 +03:00
s.groupDisconnectCollection(d.key)
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 JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Since(start).String() + "\"}")
case RESP:
2016-03-28 18:57:41 +03:00
if d.updated {
2017-10-05 18:20:40 +03:00
res = resp.IntegerValue(1)
2016-03-28 18:57:41 +03:00
} else {
2017-10-05 18:20:40 +03:00
res = resp.IntegerValue(0)
2016-03-28 18:57:41 +03:00
}
}
2016-03-05 02:08:16 +03:00
return
}
2021-12-09 19:24:26 +03:00
func (s *Server) cmdRename(msg *Message) (res resp.Value, d commandDetails, err error) {
nx := msg.Command() == "renamenx"
2018-12-28 04:15:53 +03:00
start := time.Now()
vs := msg.Args[1:]
var ok bool
if vs, d.key, ok = tokenval(vs); !ok || d.key == "" {
err = errInvalidNumberOfArguments
return
}
if vs, d.newKey, ok = tokenval(vs); !ok || d.newKey == "" {
err = errInvalidNumberOfArguments
return
}
if len(vs) != 0 {
err = errInvalidNumberOfArguments
return
}
col, _ := s.cols.Get(d.key)
if col == nil {
err = errKeyNotFound
return
}
2021-12-09 19:24:26 +03:00
s.hooks.Ascend(nil, func(v interface{}) bool {
h := v.(*Hook)
2018-12-28 04:15:53 +03:00
if h.Key == d.key || h.Key == d.newKey {
err = errKeyHasHooksSet
return false
2018-12-28 04:15:53 +03:00
}
return true
})
2018-12-28 04:15:53 +03:00
d.command = "rename"
newCol, _ := s.cols.Get(d.newKey)
if newCol == nil {
d.updated = true
} else if nx {
d.updated = false
} else {
s.cols.Delete(d.newKey)
d.updated = true
2018-12-28 04:15:53 +03:00
}
if d.updated {
s.cols.Delete(d.key)
s.cols.Set(d.newKey, col)
2018-12-28 04:15:53 +03:00
}
d.timestamp = time.Now()
switch msg.OutputType {
case JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Since(start).String() + "\"}")
2018-12-28 04:15:53 +03:00
case RESP:
if !nx {
res = resp.SimpleStringValue("OK")
} else if d.updated {
2018-12-28 04:15:53 +03:00
res = resp.IntegerValue(1)
} else {
res = resp.IntegerValue(0)
}
}
return
}
2022-09-20 03:47:38 +03:00
func (s *Server) cmdFLUSHDB(msg *Message) (res resp.Value, d commandDetails, err error) {
2016-03-28 18:57:41 +03:00
start := time.Now()
vs := msg.Args[1:]
2016-03-28 18:57:41 +03:00
if len(vs) != 0 {
2016-03-05 02:08:16 +03:00
err = errInvalidNumberOfArguments
return
}
// clear the entire database
s.cols.Clear()
s.groupHooks.Clear()
s.groupObjects.Clear()
s.hookExpires.Clear()
s.hooks.Clear()
s.hooksOut.Clear()
s.hookTree.Clear()
s.hookCross.Clear()
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 JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Since(start).String() + "\"}")
case RESP:
2017-10-05 18:20:40 +03:00
res = resp.SimpleStringValue("OK")
2016-03-28 18:57:41 +03:00
}
2016-03-05 02:08:16 +03:00
return
}
2022-09-20 03:47:38 +03:00
// SET key id [FIELD name value ...] [EX seconds] [NX|XX]
// (OBJECT geojson)|(POINT lat lon z)|(BOUNDS minlat minlon maxlat maxlon)|(HASH geohash)|(STRING value)
func (s *Server) cmdSET(msg *Message) (resp.Value, commandDetails, error) {
start := time.Now()
if s.config.maxMemory() > 0 && s.outOfMemory.on() {
return retwerr(errOOM)
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
// >> Args
var key string
var id string
var fields []field.Field
var ex int64
var xx bool
var nx bool
2022-09-21 00:20:53 +03:00
var oobj geojson.Object
2022-09-20 03:47:38 +03:00
args := msg.Args
if len(args) < 3 {
return retwerr(errInvalidNumberOfArguments)
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
key, id = args[1], args[2]
for i := 3; i < len(args); i++ {
switch strings.ToLower(args[i]) {
case "field":
if i+2 >= len(args) {
return retwerr(errInvalidNumberOfArguments)
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
fkey := strings.ToLower(args[i+1])
fval := args[i+2]
i += 2
if isReservedFieldName(fkey) {
return retwerr(errInvalidArgument(fkey))
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
fields = append(fields, field.Make(fkey, fval))
case "ex":
if i+1 >= len(args) {
return retwerr(errInvalidNumberOfArguments)
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
exval := args[i+1]
i += 1
x, err := strconv.ParseFloat(exval, 64)
2016-03-05 02:08:16 +03:00
if err != nil {
2022-09-20 03:47:38 +03:00
return retwerr(errInvalidArgument(exval))
2016-07-15 22:22:48 +03:00
}
2022-09-20 03:47:38 +03:00
ex = time.Now().UnixNano() + int64(float64(time.Second)*x)
case "nx":
if xx {
return retwerr(errInvalidArgument(args[i]))
2016-07-15 22:22:48 +03:00
}
2022-09-20 03:47:38 +03:00
nx = true
case "xx":
2016-10-03 18:31:13 +03:00
if nx {
2022-09-20 03:47:38 +03:00
return retwerr(errInvalidArgument(args[i]))
2016-10-03 18:31:13 +03:00
}
xx = true
2022-09-20 03:47:38 +03:00
case "string":
if i+1 >= len(args) {
return retwerr(errInvalidNumberOfArguments)
2016-10-03 18:31:13 +03:00
}
2022-09-20 03:47:38 +03:00
str := args[i+1]
i += 1
2022-09-21 00:20:53 +03:00
oobj = collection.String(str)
2022-09-20 03:47:38 +03:00
case "point":
if i+2 >= len(args) {
return retwerr(errInvalidNumberOfArguments)
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
slat := args[i+1]
slon := args[i+2]
i += 2
var z float64
var hasZ bool
if i+1 < len(args) {
// probe for possible z coordinate
var err error
z, err = strconv.ParseFloat(args[i+1], 64)
if err == nil {
hasZ = true
i++
}
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
y, err := strconv.ParseFloat(slat, 64)
2016-03-05 02:08:16 +03:00
if err != nil {
2022-09-20 03:47:38 +03:00
return retwerr(errInvalidArgument(slat))
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
x, err := strconv.ParseFloat(slon, 64)
2016-03-05 02:08:16 +03:00
if err != nil {
2022-09-20 03:47:38 +03:00
return retwerr(errInvalidArgument(slon))
}
if !hasZ {
2022-09-21 00:20:53 +03:00
oobj = geojson.NewPoint(geometry.Point{X: x, Y: y})
2022-09-20 03:47:38 +03:00
} else {
2022-09-21 00:20:53 +03:00
oobj = geojson.NewPointZ(geometry.Point{X: x, Y: y}, z)
2022-09-20 03:47:38 +03:00
}
case "bounds":
if i+4 >= len(args) {
return retwerr(errInvalidNumberOfArguments)
}
var vals [4]float64
for j := 0; j < 4; j++ {
var err error
vals[j], err = strconv.ParseFloat(args[i+1+j], 64)
if err != nil {
return retwerr(errInvalidArgument(args[i+1+j]))
}
}
i += 4
2022-09-21 00:20:53 +03:00
oobj = geojson.NewRect(geometry.Rect{
2022-09-20 03:47:38 +03:00
Min: geometry.Point{X: vals[1], Y: vals[0]},
Max: geometry.Point{X: vals[3], Y: vals[2]},
})
case "hash":
if i+1 >= len(args) {
return retwerr(errInvalidNumberOfArguments)
}
shash := args[i+1]
i += 1
lat, lon := geohash.Decode(shash)
2022-09-21 00:20:53 +03:00
oobj = geojson.NewPoint(geometry.Point{X: lon, Y: lat})
2022-09-20 03:47:38 +03:00
case "object":
if i+1 >= len(args) {
return retwerr(errInvalidNumberOfArguments)
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
json := args[i+1]
i += 1
var err error
2022-09-21 00:20:53 +03:00
oobj, err = geojson.Parse(json, &s.geomParseOpts)
2016-03-05 02:08:16 +03:00
if err != nil {
2022-09-20 03:47:38 +03:00
return retwerr(err)
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
default:
return retwerr(errInvalidArgument(args[i]))
2016-03-05 02:08:16 +03:00
}
}
2022-09-20 03:47:38 +03:00
// >> Operation
var nada bool
col, ok := s.cols.Get(key)
if !ok {
2016-10-03 18:31:13 +03:00
if xx {
2022-09-20 03:47:38 +03:00
nada = true
} else {
col = collection.New()
s.cols.Set(key, col)
2016-10-03 18:31:13 +03:00
}
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
var ofields field.List
if !nada {
2022-09-21 00:20:53 +03:00
o := col.Get(id)
if o != nil {
ofields = o.Fields()
}
2022-09-20 03:47:38 +03:00
if xx || nx {
if (nx && ok) || (xx && !ok) {
nada = true
}
2016-10-03 18:31:13 +03:00
}
}
2022-09-20 03:47:38 +03:00
if nada {
// exclude operation due to 'xx' or 'nx' match
switch msg.OutputType {
default:
case JSON:
if nx {
return retwerr(errIDAlreadyExists)
} else {
return retwerr(errIDNotFound)
}
case RESP:
return resp.NullValue(), commandDetails{}, nil
}
return retwerr(errors.New("nada unknown output"))
}
for _, f := range fields {
ofields = ofields.Set(f)
}
2022-09-21 00:20:53 +03:00
obj := object.New(id, oobj, 0, ex, ofields)
old := col.Set(obj)
2022-09-20 03:47:38 +03:00
// >> Response
var d commandDetails
2016-03-05 02:08:16 +03:00
d.command = "set"
2022-09-20 03:47:38 +03:00
d.key = key
d.obj = obj
2022-09-21 00:20:53 +03:00
d.old = old
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
d.timestamp = time.Now()
2022-09-20 03:47:38 +03:00
var res resp.Value
2016-03-28 18:57:41 +03:00
switch msg.OutputType {
default:
case JSON:
2022-09-20 03:47:38 +03:00
res = resp.StringValue(`{"ok":true,"elapsed":"` +
time.Since(start).String() + "\"}")
case RESP:
2017-10-05 18:20:40 +03:00
res = resp.SimpleStringValue("OK")
2016-03-28 18:57:41 +03:00
}
2022-09-20 03:47:38 +03:00
return res, d, nil
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
func retwerr(err error) (resp.Value, commandDetails, error) {
return resp.Value{}, commandDetails{}, err
}
func retrerr(err error) (resp.Value, error) {
return resp.Value{}, err
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
// FSET key id [XX] field value [field value...]
func (s *Server) cmdFSET(msg *Message) (resp.Value, commandDetails, error) {
start := time.Now()
2021-12-09 19:24:26 +03:00
if s.config.maxMemory() > 0 && s.outOfMemory.on() {
2022-09-20 03:47:38 +03:00
return retwerr(errOOM)
2018-01-24 02:54:10 +03:00
}
2022-09-20 03:47:38 +03:00
// >> Args
var id string
var key string
var xx bool
2022-09-20 03:47:38 +03:00
var fields []field.Field // raw fields
args := msg.Args
if len(args) < 5 {
return retwerr(errInvalidNumberOfArguments)
}
key, id = args[1], args[2]
for i := 3; i < len(args); i++ {
arg := strings.ToLower(args[i])
switch arg {
case "xx":
xx = true
default:
fkey := arg
i++
if i == len(args) {
return retwerr(errInvalidNumberOfArguments)
}
if isReservedFieldName(fkey) {
return retwerr(errInvalidArgument(fkey))
}
fval := args[i]
fields = append(fields, field.Make(fkey, fval))
}
}
// >> Operation
var d commandDetails
var updateCount int
2022-09-20 03:47:38 +03:00
col, ok := s.cols.Get(key)
if !ok {
return retwerr(errKeyNotFound)
2016-03-05 02:08:16 +03:00
}
2022-09-21 00:20:53 +03:00
o := col.Get(id)
ok = o != nil
if !(ok || xx) {
2022-09-20 03:47:38 +03:00
return retwerr(errIDNotFound)
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
if ok {
2022-09-21 00:20:53 +03:00
ofields := o.Fields()
2022-09-20 03:47:38 +03:00
for _, f := range fields {
prev := ofields.Get(f.Name())
if !prev.Value().Equals(f.Value()) {
ofields = ofields.Set(f)
updateCount++
}
}
2022-09-21 00:20:53 +03:00
obj := object.New(id, o.Geo(), 0, o.Expires(), ofields)
col.Set(obj)
d.command = "fset"
2022-09-20 03:47:38 +03:00
d.key = key
2022-09-21 00:20:53 +03:00
d.obj = obj
d.timestamp = time.Now()
d.updated = updateCount > 0
2016-04-02 17:20:30 +03:00
}
2022-09-20 03:47:38 +03:00
// >> Response
var res resp.Value
2016-03-28 18:57:41 +03:00
switch msg.OutputType {
case JSON:
2022-09-20 03:47:38 +03:00
res = resp.StringValue(`{"ok":true,"elapsed":"` +
time.Since(start).String() + "\"}")
case RESP:
res = resp.IntegerValue(updateCount)
2016-03-28 18:57:41 +03:00
}
2022-09-20 03:47:38 +03:00
return res, d, nil
2016-03-05 02:08:16 +03:00
}
2016-07-15 22:22:48 +03:00
2022-09-20 03:47:38 +03:00
// EXPIRE key id seconds
func (s *Server) cmdEXPIRE(msg *Message) (resp.Value, commandDetails, error) {
2016-07-15 22:22:48 +03:00
start := time.Now()
2022-09-20 03:47:38 +03:00
args := msg.Args
if len(args) != 4 {
return retwerr(errInvalidNumberOfArguments)
2016-07-15 22:22:48 +03:00
}
2022-09-20 03:47:38 +03:00
key, id, svalue := args[1], args[2], args[3]
value, err := strconv.ParseFloat(svalue, 64)
2016-07-15 22:22:48 +03:00
if err != nil {
2022-09-20 03:47:38 +03:00
return retwerr(errInvalidArgument(svalue))
2016-07-15 22:22:48 +03:00
}
2022-09-20 03:47:38 +03:00
var ok bool
2022-09-21 00:20:53 +03:00
var obj *object.Object
col, _ := s.cols.Get(key)
2016-07-15 22:22:48 +03:00
if col != nil {
2022-09-20 03:47:38 +03:00
// replace the expiration by getting the old objec
ex := time.Now().Add(time.Duration(float64(time.Second) * value)).UnixNano()
2022-09-21 00:20:53 +03:00
o := col.Get(id)
ok = o != nil
2022-09-20 03:47:38 +03:00
if ok {
2022-09-21 00:20:53 +03:00
obj = object.New(id, o.Geo(), 0, ex, o.Fields())
col.Set(obj)
2022-09-20 03:47:38 +03:00
}
}
2022-09-20 03:47:38 +03:00
var d commandDetails
if ok {
2022-09-20 03:47:38 +03:00
d.key = key
2022-09-21 00:20:53 +03:00
d.obj = obj
2022-09-20 03:47:38 +03:00
d.command = "expire"
d.updated = true
2022-09-20 03:47:38 +03:00
d.timestamp = time.Now()
2016-07-15 22:22:48 +03:00
}
2022-09-20 03:47:38 +03:00
var res resp.Value
2016-07-15 22:22:48 +03:00
switch msg.OutputType {
case JSON:
if ok {
2022-09-20 03:47:38 +03:00
res = resp.StringValue(`{"ok":true,"elapsed":"` +
time.Since(start).String() + "\"}")
} else if col == nil {
return retwerr(errKeyNotFound)
} else {
2022-09-20 03:47:38 +03:00
return retwerr(errIDNotFound)
}
case RESP:
2016-07-15 22:22:48 +03:00
if ok {
2017-10-05 18:20:40 +03:00
res = resp.IntegerValue(1)
2016-07-15 22:22:48 +03:00
} else {
2017-10-05 18:20:40 +03:00
res = resp.IntegerValue(0)
2016-07-15 22:22:48 +03:00
}
}
2022-09-20 03:47:38 +03:00
return res, d, nil
2016-07-15 22:22:48 +03:00
}
2022-09-20 03:47:38 +03:00
// PERSIST key id
func (s *Server) cmdPERSIST(msg *Message) (resp.Value, commandDetails, error) {
2016-07-15 22:22:48 +03:00
start := time.Now()
2022-09-20 03:47:38 +03:00
args := msg.Args
if len(args) != 3 {
return retwerr(errInvalidNumberOfArguments)
2016-07-15 22:22:48 +03:00
}
2022-09-20 03:47:38 +03:00
key, id := args[1], args[2]
col, _ := s.cols.Get(key)
2022-09-21 00:20:53 +03:00
if col == nil {
if msg.OutputType == RESP {
return resp.IntegerValue(0), commandDetails{}, nil
}
2022-09-21 00:20:53 +03:00
return retwerr(errKeyNotFound)
}
2022-09-21 00:20:53 +03:00
o := col.Get(id)
if o == nil {
if msg.OutputType == RESP {
2022-09-20 03:47:38 +03:00
return resp.IntegerValue(0), commandDetails{}, nil
2016-07-15 22:22:48 +03:00
}
2022-09-20 03:47:38 +03:00
return retwerr(errIDNotFound)
2016-07-15 22:22:48 +03:00
}
2022-09-20 03:47:38 +03:00
2022-09-21 00:20:53 +03:00
var obj *object.Object
var cleared bool
if o.Expires() != 0 {
obj = object.New(id, o.Geo(), 0, 0, o.Fields())
col.Set(obj)
cleared = true
}
2022-09-20 03:47:38 +03:00
var res resp.Value
var d commandDetails
d.command = "persist"
2022-09-21 00:20:53 +03:00
d.key = key
d.obj = obj
d.updated = cleared
d.timestamp = time.Now()
2022-09-20 03:47:38 +03:00
2016-07-15 22:22:48 +03:00
switch msg.OutputType {
case JSON:
res = resp.SimpleStringValue(`{"ok":true,"elapsed":"` + time.Since(start).String() + "\"}")
case RESP:
if cleared {
2017-10-05 18:20:40 +03:00
res = resp.IntegerValue(1)
2016-07-15 22:22:48 +03:00
} else {
2017-10-05 18:20:40 +03:00
res = resp.IntegerValue(0)
2016-07-15 22:22:48 +03:00
}
}
2022-09-20 03:47:38 +03:00
return res, d, nil
2016-07-15 22:22:48 +03:00
}
2022-09-20 03:47:38 +03:00
// TTL key id
func (s *Server) cmdTTL(msg *Message) (resp.Value, error) {
2016-07-15 22:22:48 +03:00
start := time.Now()
2022-09-20 03:47:38 +03:00
args := msg.Args
if len(args) != 3 {
return retrerr(errInvalidNumberOfArguments)
2016-07-15 22:22:48 +03:00
}
2022-09-20 03:47:38 +03:00
key, id := args[1], args[2]
2016-07-15 22:22:48 +03:00
var v float64
2022-09-20 03:47:38 +03:00
var ok bool
2016-07-15 22:22:48 +03:00
var ok2 bool
col, _ := s.cols.Get(key)
2016-07-15 22:22:48 +03:00
if col != nil {
2022-09-21 00:20:53 +03:00
o := col.Get(id)
ok = o != nil
2016-07-15 22:22:48 +03:00
if ok {
2022-09-21 00:20:53 +03:00
if o.Expires() != 0 {
now := start.UnixNano()
2022-09-21 00:20:53 +03:00
if now > o.Expires() {
ok2 = false
} else {
2022-09-21 00:20:53 +03:00
v = float64(o.Expires()-now) / float64(time.Second)
if v < 0 {
v = 0
}
ok2 = true
2016-07-15 22:22:48 +03:00
}
}
}
}
2022-09-20 03:47:38 +03:00
var res resp.Value
2016-07-15 22:22:48 +03:00
switch msg.OutputType {
case JSON:
if ok {
var ttl string
if ok2 {
ttl = strconv.FormatFloat(v, 'f', -1, 64)
} else {
ttl = "-1"
}
2017-10-05 18:20:40 +03:00
res = resp.SimpleStringValue(
2022-09-20 03:47:38 +03:00
`{"ok":true,"ttl":` + ttl + `,"elapsed":"` +
time.Since(start).String() + "\"}")
} else {
2022-09-20 03:47:38 +03:00
if col == nil {
return retrerr(errKeyNotFound)
}
return retrerr(errIDNotFound)
}
case RESP:
2016-07-15 22:22:48 +03:00
if ok {
if ok2 {
2017-10-05 18:20:40 +03:00
res = resp.IntegerValue(int(v))
2016-07-15 22:22:48 +03:00
} else {
2017-10-05 18:20:40 +03:00
res = resp.IntegerValue(-1)
2016-07-15 22:22:48 +03:00
}
} else {
2017-10-05 18:20:40 +03:00
res = resp.IntegerValue(-2)
2016-07-15 22:22:48 +03:00
}
}
2022-09-20 03:47:38 +03:00
return res, nil
2016-07-15 22:22:48 +03:00
}