tile38/internal/server/crud.go

1152 lines
23 KiB
Go
Raw Normal View History

package server
2016-03-05 02:08:16 +03:00
import (
"bytes"
2022-09-24 01:29:46 +03:00
"math"
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
)
2022-09-23 17:51:05 +03:00
// BOUNDS key
func (s *Server) cmdBOUNDS(msg *Message) (resp.Value, error) {
start := time.Now()
2022-09-23 17:51:05 +03:00
// >> Args
args := msg.Args
if len(args) != 2 {
return retrerr(errInvalidNumberOfArguments)
}
2022-09-23 17:51:05 +03:00
key := args[1]
// >> Operation
col, _ := s.cols.Get(key)
if col == nil {
if msg.OutputType == RESP {
2017-10-05 18:20:40 +03:00
return resp.NullValue(), nil
}
2022-09-23 17:51:05 +03:00
return retrerr(errKeyNotFound)
}
2022-09-23 17:51:05 +03:00
// >> Response
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)))
buf.WriteString(`,"elapsed":"` + time.Since(start).String() + "\"}")
2017-10-05 18:20:40 +03:00
return resp.StringValue(buf.String()), nil
}
2022-09-23 17:51:05 +03:00
// RESP
vals = append(vals, resp.ArrayValue([]resp.Value{
resp.ArrayValue([]resp.Value{
resp.FloatValue(minX),
resp.FloatValue(minY),
}),
resp.ArrayValue([]resp.Value{
resp.FloatValue(maxX),
resp.FloatValue(maxY),
}),
}))
return vals[0], nil
}
2017-10-05 18:20:40 +03:00
2022-09-23 19:04:01 +03:00
// TYPE key
// undocumented return "none" or "hash"
func (s *Server) cmdTYPE(msg *Message) (resp.Value, error) {
2016-08-26 23:42:52 +03:00
start := time.Now()
2022-09-23 19:04:01 +03:00
// >> Args
args := msg.Args
if len(args) != 2 {
return retrerr(errInvalidNumberOfArguments)
2016-08-26 23:42:52 +03:00
}
2022-09-23 19:04:01 +03:00
key := args[1]
// >> Operation
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
}
2022-09-23 19:04:01 +03:00
return retrerr(errKeyNotFound)
2016-08-26 23:42:52 +03:00
}
2022-09-23 19:04:01 +03:00
// >> Response
2016-08-26 23:42:52 +03:00
typ := "hash"
2022-09-23 19:04:01 +03:00
if msg.OutputType == JSON {
return resp.StringValue(`{"ok":true,"type":` + jsonString(typ) +
`,"elapsed":"` + time.Since(start).String() + "\"}"), nil
2016-08-26 23:42:52 +03:00
}
2022-09-23 19:04:01 +03:00
return resp.SimpleStringValue(typ), nil
2016-08-26 23:42:52 +03:00
}
2016-03-28 18:57:41 +03:00
2022-09-23 20:42:43 +03:00
// GET key id [WITHFIELDS] [OBJECT|POINT|BOUNDS|(HASH geohash)]
func (s *Server) cmdGET(msg *Message) (resp.Value, error) {
2016-03-05 02:08:16 +03:00
start := time.Now()
2016-03-28 18:57:41 +03:00
2022-09-23 20:42:43 +03:00
// >> Args
args := msg.Args
if len(args) < 3 {
return retrerr(errInvalidNumberOfArguments)
2016-03-05 02:08:16 +03:00
}
2022-09-23 20:42:43 +03:00
key, id := args[1], args[2]
2016-04-01 22:46:39 +03:00
withfields := false
2022-09-23 20:42:43 +03:00
kind := "object"
var precision int64
for i := 3; i < len(args); i++ {
switch strings.ToLower(args[i]) {
case "withfields":
withfields = true
case "object":
kind = "object"
case "point":
kind = "point"
case "bounds":
kind = "bounds"
case "hash":
kind = "hash"
i++
if i == len(args) {
return retrerr(errInvalidNumberOfArguments)
}
var err error
precision, err = strconv.ParseInt(args[i], 10, 64)
if err != nil || precision < 1 || precision > 12 {
return retrerr(errInvalidArgument(args[i]))
}
default:
return retrerr(errInvalidNumberOfArguments)
}
2016-04-01 22:46:39 +03:00
}
2022-09-23 20:42:43 +03:00
// >> Operation
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
}
2022-09-23 20:42:43 +03:00
return retrerr(errKeyNotFound)
2016-03-05 02:08:16 +03:00
}
2022-09-21 00:20:53 +03:00
o := col.Get(id)
2022-09-23 20:42:43 +03:00
if o == 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
}
2022-09-23 20:42:43 +03:00
return retrerr(errIDNotFound)
2016-03-05 02:08:16 +03:00
}
2016-03-28 18:57:41 +03:00
2022-09-23 20:42:43 +03:00
// >> Response
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`)
}
2022-09-23 20:42:43 +03:00
switch kind {
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 msg.OutputType == JSON {
2016-07-10 23:23:50 +03:00
buf.WriteString(`,"hash":`)
}
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-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-23 21:18:01 +03:00
buf.WriteString(jsonString(f.Name()) + ":" +
f.Value().JSON())
2016-04-01 22:46:39 +03:00
} else {
2022-09-23 21:18:01 +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
}
}
2022-09-23 20:42:43 +03:00
if msg.OutputType == JSON {
buf.WriteString(`,"elapsed":"` + time.Since(start).String() + "\"}")
2017-10-05 18:20:40 +03:00
return resp.StringValue(buf.String()), nil
2016-03-05 02:08:16 +03:00
}
2022-09-23 20:42:43 +03:00
var oval resp.Value
if withfields {
oval = resp.ArrayValue(vals)
} else {
oval = vals[0]
}
return oval, nil
2016-03-05 02:08:16 +03:00
}
2022-09-21 00:20:53 +03:00
// DEL key id [ERRON404]
2022-09-23 19:04:01 +03:00
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
}
2022-09-23 19:04:01 +03:00
// PDEL key pattern
func (s *Server) cmdPDEL(msg *Message) (resp.Value, commandDetails, error) {
start := time.Now()
2022-09-23 19:04:01 +03:00
// >> Args
args := msg.Args
if len(args) != 3 {
return retwerr(errInvalidNumberOfArguments)
}
2022-09-23 19:04:01 +03:00
key := args[1]
pattern := args[2]
2022-09-23 19:04:01 +03:00
// >> Operation
now := time.Now()
var children []*commandDetails
col, _ := s.cols.Get(key)
if col != nil {
2022-09-23 19:04:01 +03:00
g := glob.Parse(pattern, false)
var ids []string
iter := func(o *object.Object) bool {
if match, _ := glob.Match(pattern, o.ID()); match {
ids = append(ids, o.ID())
}
return true
}
if g.Limits[0] == "" && g.Limits[1] == "" {
2019-04-24 15:09:41 +03:00
col.Scan(false, nil, msg.Deadline, iter)
} else {
2022-09-23 19:04:01 +03:00
col.ScanRange(g.Limits[0], g.Limits[1],
false, nil, msg.Deadline, iter)
}
2022-09-23 19:04:01 +03:00
for _, id := range ids {
obj := col.Delete(id)
children = append(children, &commandDetails{
command: "del",
updated: true,
timestamp: now,
key: key,
obj: obj,
})
s.groupDisconnectObject(key, id)
}
if col.Count() == 0 {
2022-09-23 19:04:01 +03:00
s.cols.Delete(key)
}
}
2022-09-23 19:04:01 +03:00
// >> Response
var d commandDetails
var res resp.Value
d.command = "pdel"
2022-09-23 19:04:01 +03:00
d.children = children
d.key = key
d.pattern = pattern
d.updated = len(d.children) > 0
d.timestamp = now
d.parent = true
switch msg.OutputType {
case JSON:
2022-09-23 19:04:01 +03:00
res = resp.StringValue(`{"ok":true,"elapsed":"` +
time.Since(start).String() + "\"}")
case RESP:
2022-09-23 19:04:01 +03:00
res = resp.IntegerValue(len(d.children))
}
2022-09-23 19:04:01 +03:00
return res, d, nil
}
func (s *Server) cmdDROPop(key string) *collection.Collection {
col, _ := s.cols.Get(key)
if col != nil {
s.cols.Delete(key)
}
s.groupDisconnectCollection(key)
return col
}
2022-09-23 20:42:43 +03:00
// DROP key
func (s *Server) cmdDROP(msg *Message) (resp.Value, commandDetails, error) {
2016-03-28 18:57:41 +03:00
start := time.Now()
2022-09-23 20:42:43 +03:00
// >> Args
args := msg.Args
if len(args) != 2 {
return retwerr(errInvalidNumberOfArguments)
2016-03-05 02:08:16 +03:00
}
2022-09-23 20:42:43 +03:00
key := args[1]
// >> Operation
col := s.cmdDROPop(key)
2022-09-23 20:42:43 +03:00
// >> Response
var res resp.Value
var d commandDetails
d.key = key
d.updated = col != nil
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:
2022-09-23 21:18:01 +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-23 20:42:43 +03:00
return res, d, nil
2016-03-05 02:08:16 +03:00
}
2022-09-23 21:18:01 +03:00
// RENAME key newkey
// RENAMENX key newkey
func (s *Server) cmdRENAME(msg *Message) (resp.Value, commandDetails, error) {
2018-12-28 04:15:53 +03:00
start := time.Now()
2022-09-23 21:18:01 +03:00
// >> Args
args := msg.Args
if len(args) != 3 {
return retwerr(errInvalidNumberOfArguments)
2018-12-28 04:15:53 +03:00
}
2022-09-23 21:18:01 +03:00
nx := strings.ToLower(args[0]) == "renamenx"
key := args[1]
newKey := args[2]
// >> Operation
col, _ := s.cols.Get(key)
if col == nil {
2022-09-23 21:18:01 +03:00
return retwerr(errKeyNotFound)
}
2022-09-23 21:18:01 +03:00
var ierr error
2021-12-09 19:24:26 +03:00
s.hooks.Ascend(nil, func(v interface{}) bool {
h := v.(*Hook)
2022-09-23 21:18:01 +03:00
if h.Key == key || h.Key == newKey {
ierr = errKeyHasHooksSet
return false
2018-12-28 04:15:53 +03:00
}
return true
})
2022-09-23 21:18:01 +03:00
if ierr != nil {
return retwerr(ierr)
}
var updated bool
newCol, _ := s.cols.Get(newKey)
if newCol == nil {
2022-09-23 21:18:01 +03:00
updated = true
} else if !nx {
s.cols.Delete(newKey)
updated = true
2018-12-28 04:15:53 +03:00
}
2022-09-23 21:18:01 +03:00
if updated {
s.cols.Delete(key)
s.cols.Set(newKey, col)
2018-12-28 04:15:53 +03:00
}
2022-09-23 21:18:01 +03:00
// >> Response
var d commandDetails
var res resp.Value
d.command = "rename"
d.key = key
d.newKey = newKey
d.updated = updated
2018-12-28 04:15:53 +03:00
d.timestamp = time.Now()
2022-09-23 21:18:01 +03:00
2018-12-28 04:15:53 +03:00
switch msg.OutputType {
case JSON:
2022-09-23 21:18:01 +03:00
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)
}
}
2022-09-23 21:18:01 +03:00
return res, d, nil
2018-12-28 04:15:53 +03:00
}
2022-09-23 21:40:48 +03:00
// FLUSHDB
func (s *Server) cmdFLUSHDB(msg *Message) (resp.Value, commandDetails, error) {
2016-03-28 18:57:41 +03:00
start := time.Now()
2022-09-23 21:40:48 +03:00
// >> Args
args := msg.Args
if len(args) != 1 {
return retwerr(errInvalidNumberOfArguments)
2016-03-05 02:08:16 +03:00
}
2022-09-23 21:40:48 +03:00
// >> Operation
// clear the entire database
// drop each collection
keys := s.cols.Keys()
for _, key := range keys {
s.cmdDROPop(key)
}
// delete all channels
var names []string
s.hooks.Ascend(nil, func(item any) bool {
hook := item.(*Hook)
if hook.channel {
names = append(names, hook.Name)
}
return true
})
for _, name := range names {
s.cmdDELHOOKop(name, true)
}
// delete all hooks
names = names[:0]
s.hooks.Ascend(nil, func(item any) bool {
hook := item.(*Hook)
if !hook.channel {
names = append(names, hook.Name)
}
return true
})
for _, name := range names {
s.cmdDELHOOKop(name, false)
}
s.cols.Clear()
s.groupHooks.Clear()
s.groupObjects.Clear()
s.hookExpires.Clear()
s.hooks.Clear()
s.hooksOut.Clear()
s.hookTree.Clear()
s.hookCross.Clear()
2022-09-23 21:40:48 +03:00
// >> Response
var d commandDetails
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()
2022-09-23 21:40:48 +03:00
var res resp.Value
if msg.OutputType == JSON {
2022-09-23 21:18:01 +03:00
res = resp.StringValue(`{"ok":true,"elapsed":"` +
time.Since(start).String() + "\"}")
2022-09-23 21:40:48 +03:00
} else {
2017-10-05 18:20:40 +03:00
res = resp.SimpleStringValue("OK")
2016-03-28 18:57:41 +03:00
}
2022-09-23 21:40:48 +03:00
return res, d, nil
2016-03-05 02:08:16 +03:00
}
2022-09-20 03:47:38 +03:00
// SET key id [FIELD name value ...] [EX seconds] [NX|XX]
2022-09-23 21:18:01 +03:00
// (OBJECT geojson)|(POINT lat lon z)|(BOUNDS minlat minlon maxlat maxlon)|
// (HASH geohash)|(STRING value)
2022-09-20 03:47:38 +03:00
func (s *Server) cmdSET(msg *Message) (resp.Value, commandDetails, error) {
start := time.Now()
if s.config.maxMemory() > 0 && s.outOfMemory.Load() {
2022-09-20 03:47:38 +03:00
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
}
fkey := args[i+1]
2022-09-20 03:47:38 +03:00
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-24 01:29:46 +03:00
if oobj == nil {
return retwerr(errInvalidNumberOfArguments)
}
2016-03-05 02:08:16 +03:00
2022-09-20 03:47:38 +03:00
// >> Operation
nada := func() (resp.Value, commandDetails, error) {
2022-09-20 03:47:38 +03:00
// exclude operation due to 'xx' or 'nx' match
2022-09-24 01:29:46 +03:00
if msg.OutputType == JSON {
2022-09-20 03:47:38 +03:00
if nx {
return retwerr(errIDAlreadyExists)
} else {
return retwerr(errIDNotFound)
}
}
2022-09-24 01:29:46 +03:00
return resp.NullValue(), commandDetails{}, nil
2022-09-20 03:47:38 +03:00
}
col, ok := s.cols.Get(key)
if !ok {
if xx {
return nada()
}
col = collection.New()
s.cols.Set(key, col)
}
if xx || nx {
if col.Get(id) == nil {
if xx {
return nada()
}
} else {
if nx {
return nada()
}
}
2022-09-20 03:47:38 +03:00
}
var flist field.List
if old := col.Get(id); old != nil {
flist = old.Fields()
}
for _, f := range fields {
flist = flist.Set(f)
}
obj := object.New(id, oobj, ex, flist)
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()
if s.config.maxMemory() > 0 && s.outOfMemory.Load() {
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 20:03:53 +03:00
obj := object.New(id, o.Geo(), o.Expires(), ofields)
2022-09-21 00:20:53 +03:00
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-23 21:40:48 +03:00
// >> Args
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-23 21:40:48 +03:00
// >> Operation
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-23 21:40:48 +03:00
// replace the expiration by getting the old object
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 20:03:53 +03:00
obj = object.New(id, o.Geo(), ex, o.Fields())
2022-09-21 00:20:53 +03:00
col.Set(obj)
2022-09-20 03:47:38 +03:00
}
}
2022-09-23 21:40:48 +03:00
// >> Response
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-24 01:29:46 +03:00
// >> Args
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]
2022-09-24 01:29:46 +03:00
// >> Operation
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 {
2022-09-21 20:03:53 +03:00
obj = object.New(id, o.Geo(), 0, o.Fields())
2022-09-21 00:20:53 +03:00
col.Set(obj)
cleared = true
}
2022-09-24 01:29:46 +03:00
// >> Response
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:
2022-09-23 21:40:48 +03:00
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-24 01:29:46 +03:00
// >> Args
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]
2022-09-24 01:29:46 +03:00
// >> Operation
col, _ := s.cols.Get(key)
2022-09-24 01:29:46 +03:00
if col == nil {
if msg.OutputType == JSON {
return retrerr(errKeyNotFound)
2016-07-15 22:22:48 +03:00
}
2022-09-24 01:29:46 +03:00
return resp.IntegerValue(-2), nil
2016-07-15 22:22:48 +03:00
}
2022-09-24 01:29:46 +03:00
o := col.Get(id)
if o == nil {
if msg.OutputType == JSON {
2022-09-20 03:47:38 +03:00
return retrerr(errIDNotFound)
}
2022-09-24 01:29:46 +03:00
return resp.IntegerValue(-2), nil
}
var ttl float64
if o.Expires() == 0 {
ttl = -1
} else {
now := start.UnixNano()
ttl = math.Max(float64(o.Expires()-now)/float64(time.Second), 0)
}
// >> Response
if msg.OutputType == JSON {
return resp.SimpleStringValue(
`{"ok":true,"ttl":` + strconv.Itoa(int(ttl)) + `,"elapsed":"` +
time.Since(start).String() + "\"}"), nil
2016-07-15 22:22:48 +03:00
}
2022-09-24 01:29:46 +03:00
return resp.IntegerValue(int(ttl)), nil
2016-07-15 22:22:48 +03:00
}
2024-03-19 06:19:16 +03:00
// EXISTS key id
func (s *Server) cmdEXISTS(msg *Message) (resp.Value, error) {
start := time.Now()
// >> Args
args := msg.Args
if len(args) != 3 {
return retrerr(errInvalidNumberOfArguments)
}
key, id := args[1], args[2]
// >> Operation
col, _ := s.cols.Get(key)
if col == nil {
return retrerr(errKeyNotFound)
}
o := col.Get(id)
exists := o != nil
// >> Response
if msg.OutputType == JSON {
return resp.SimpleStringValue(
`{"ok":true,"exists":` + strconv.FormatBool(exists) + `,"elapsed":"` +
time.Since(start).String() + "\"}"), nil
}
return resp.BoolValue(exists), nil
}
// FEXISTS key id field
func (s *Server) cmdFEXISTS(msg *Message) (resp.Value, error) {
start := time.Now()
// >> Args
args := msg.Args
if len(args) != 4 {
return retrerr(errInvalidNumberOfArguments)
}
key, id, field := args[1], args[2], args[3]
// >> Operation
col, _ := s.cols.Get(key)
if col == nil {
return retrerr(errKeyNotFound)
}
o := col.Get(id)
if o == nil {
return retrerr(errIDNotFound)
}
f := o.Fields().Get(field)
exists := f.Name() != ""
// >> Response
if msg.OutputType == JSON {
return resp.SimpleStringValue(
`{"ok":true,"exists":` + strconv.FormatBool(exists) + `,"elapsed":"` +
time.Since(start).String() + "\"}"), nil
}
return resp.BoolValue(exists), nil
}