tile38/internal/server/json.go

387 lines
7.7 KiB
Go
Raw Permalink Normal View History

package server
2016-03-05 02:08:16 +03:00
import (
"bytes"
"encoding/json"
"strconv"
"strings"
"time"
"github.com/tidwall/geojson"
"github.com/tidwall/gjson"
"github.com/tidwall/resp"
"github.com/tidwall/sjson"
"github.com/tidwall/tile38/internal/collection"
)
2016-03-05 02:08:16 +03:00
2016-12-28 21:16:28 +03:00
func appendJSONString(b []byte, s string) []byte {
for i := 0; i < len(s); i++ {
if s[i] < ' ' || s[i] == '\\' || s[i] == '"' || s[i] > 126 {
d, _ := json.Marshal(s)
return append(b, string(d)...)
}
}
b = append(b, '"')
b = append(b, s...)
b = append(b, '"')
return b
}
2016-03-05 02:08:16 +03:00
func jsonString(s string) string {
for i := 0; i < len(s); i++ {
2016-09-11 17:49:48 +03:00
if s[i] < ' ' || s[i] == '\\' || s[i] == '"' || s[i] > 126 {
2016-03-05 02:08:16 +03:00
d, _ := json.Marshal(s)
return string(d)
}
}
2016-09-11 17:49:48 +03:00
b := make([]byte, len(s)+2)
b[0] = '"'
2016-11-07 23:04:21 +03:00
copy(b[1:], s)
2016-09-11 17:49:48 +03:00
b[len(b)-1] = '"'
2016-11-07 23:04:21 +03:00
return string(b)
2016-03-05 02:08:16 +03:00
}
func isJSONNumber(data string) bool {
// Returns true if the given string can be encoded as a JSON number value.
// See:
// https://json.org
// http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
if data == "" {
return false
}
i := 0
// sign
if data[i] == '-' {
i++
}
if i == len(data) {
return false
}
// int
if data[i] == '0' {
i++
} else {
for ; i < len(data); i++ {
if data[i] >= '0' && data[i] <= '9' {
continue
}
break
}
}
// frac
if i == len(data) {
return true
}
if data[i] == '.' {
i++
if i == len(data) {
return false
}
if data[i] < '0' || data[i] > '9' {
return false
}
i++
for ; i < len(data); i++ {
if data[i] >= '0' && data[i] <= '9' {
continue
}
break
}
}
// exp
if i == len(data) {
return true
}
if data[i] == 'e' || data[i] == 'E' {
i++
if i == len(data) {
return false
}
if data[i] == '+' || data[i] == '-' {
i++
}
if i == len(data) {
return false
}
if data[i] < '0' || data[i] > '9' {
return false
}
i++
for ; i < len(data); i++ {
if data[i] >= '0' && data[i] <= '9' {
continue
}
break
}
}
return i == len(data)
}
func appendJSONSimpleBounds(dst []byte, o geojson.Object) []byte {
bbox := o.Rect()
dst = append(dst, `{"sw":{"lat":`...)
dst = strconv.AppendFloat(dst, bbox.Min.Y, 'f', -1, 64)
dst = append(dst, `,"lon":`...)
dst = strconv.AppendFloat(dst, bbox.Min.X, 'f', -1, 64)
dst = append(dst, `},"ne":{"lat":`...)
dst = strconv.AppendFloat(dst, bbox.Max.Y, 'f', -1, 64)
dst = append(dst, `,"lon":`...)
dst = strconv.AppendFloat(dst, bbox.Max.X, 'f', -1, 64)
dst = append(dst, `}}`...)
return dst
}
func appendJSONSimplePoint(dst []byte, o geojson.Object) []byte {
point := o.Center()
var z float64
if gPoint, ok := o.(*geojson.Point); ok {
z = gPoint.Z()
}
dst = append(dst, `{"lat":`...)
dst = strconv.AppendFloat(dst, point.Y, 'f', -1, 64)
dst = append(dst, `,"lon":`...)
dst = strconv.AppendFloat(dst, point.X, 'f', -1, 64)
if z != 0 {
dst = append(dst, `,"z":`...)
dst = strconv.AppendFloat(dst, z, 'f', -1, 64)
}
dst = append(dst, '}')
return dst
}
2018-08-03 03:36:18 +03:00
func appendJSONTimeFormat(b []byte, t time.Time) []byte {
b = append(b, '"')
b = t.AppendFormat(b, "2006-01-02T15:04:05.999999999Z07:00")
b = append(b, '"')
return b
}
func jsonTimeFormat(t time.Time) string {
var b []byte
b = appendJSONTimeFormat(b, t)
return string(b)
}
func (s *Server) cmdJget(msg *Message) (resp.Value, error) {
start := time.Now()
2017-10-05 18:20:40 +03:00
if len(msg.Args) < 3 {
return NOMessage, errInvalidNumberOfArguments
}
if len(msg.Args) > 5 {
return NOMessage, errInvalidNumberOfArguments
}
key := msg.Args[1]
id := msg.Args[2]
var doget bool
var path string
var raw bool
if len(msg.Args) > 3 {
doget = true
path = msg.Args[3]
if len(msg.Args) == 5 {
if strings.ToLower(msg.Args[4]) == "raw" {
raw = true
} else {
return NOMessage, errInvalidArgument(msg.Args[4])
}
}
}
col := s.getCol(key)
if col == nil {
if msg.OutputType == RESP {
2017-10-05 18:20:40 +03:00
return resp.NullValue(), nil
}
return NOMessage, errKeyNotFound
}
o, _, ok := col.Get(id)
if !ok {
if msg.OutputType == RESP {
2017-10-05 18:20:40 +03:00
return resp.NullValue(), nil
}
return NOMessage, errIDNotFound
}
var res gjson.Result
if doget {
res = gjson.Get(o.String(), path)
} else {
res = gjson.Parse(o.String())
}
var val string
if raw {
val = res.Raw
} else {
val = res.String()
}
var buf bytes.Buffer
if msg.OutputType == JSON {
buf.WriteString(`{"ok":true`)
}
switch msg.OutputType {
case JSON:
if res.Exists() {
buf.WriteString(`,"value":` + jsonString(val))
}
buf.WriteString(`,"elapsed":"` + time.Since(start).String() + "\"}")
2017-10-05 18:20:40 +03:00
return resp.StringValue(buf.String()), nil
case RESP:
if !res.Exists() {
2017-10-05 18:20:40 +03:00
return resp.NullValue(), nil
}
2017-10-05 18:20:40 +03:00
return resp.StringValue(val), nil
}
return NOMessage, nil
}
func (s *Server) cmdJset(msg *Message) (res resp.Value, d commandDetails, err error) {
// JSET key path value [RAW]
start := time.Now()
2017-10-05 18:20:40 +03:00
var raw, str bool
switch len(msg.Args) {
default:
return NOMessage, d, errInvalidNumberOfArguments
case 5:
case 6:
switch strings.ToLower(msg.Args[5]) {
default:
return NOMessage, d, errInvalidArgument(msg.Args[5])
case "raw":
raw = true
case "str":
str = true
}
}
key := msg.Args[1]
id := msg.Args[2]
path := msg.Args[3]
val := msg.Args[4]
if !str && !raw {
switch val {
default:
raw = isJSONNumber(val)
case "true", "false", "null":
raw = true
}
}
col := s.getCol(key)
var createcol bool
if col == nil {
col = collection.New()
createcol = true
}
var json string
var geoobj bool
o, _, ok := col.Get(id)
if ok {
geoobj = objIsSpatial(o)
json = o.String()
}
if raw {
// set as raw block
json, err = sjson.SetRaw(json, path, val)
} else {
// set as a string
json, err = sjson.Set(json, path, val)
}
if err != nil {
return NOMessage, d, err
}
if geoobj {
nmsg := *msg
nmsg.Args = []string{"SET", key, id, "OBJECT", json}
// SET key id OBJECT json
return s.cmdSet(&nmsg, false)
}
if createcol {
s.setCol(key, col)
}
2016-12-14 17:37:02 +03:00
d.key = key
d.id = id
d.obj = collection.String(json)
2016-12-14 17:37:02 +03:00
d.timestamp = time.Now()
d.updated = true
s.clearIDExpires(key, id)
2018-08-16 23:07:55 +03:00
col.Set(d.id, d.obj, nil, nil)
switch msg.OutputType {
case JSON:
var buf bytes.Buffer
buf.WriteString(`{"ok":true`)
buf.WriteString(`,"elapsed":"` + time.Since(start).String() + "\"}")
2017-10-05 18:20:40 +03:00
return resp.StringValue(buf.String()), d, nil
case RESP:
2017-10-05 18:20:40 +03:00
return resp.SimpleStringValue("OK"), d, nil
}
return NOMessage, d, nil
}
func (s *Server) cmdJdel(msg *Message) (res resp.Value, d commandDetails, err error) {
start := time.Now()
2017-10-05 18:20:40 +03:00
if len(msg.Args) != 4 {
return NOMessage, d, errInvalidNumberOfArguments
}
key := msg.Args[1]
id := msg.Args[2]
path := msg.Args[3]
col := s.getCol(key)
if col == nil {
if msg.OutputType == RESP {
2017-10-05 18:20:40 +03:00
return resp.IntegerValue(0), d, nil
}
return NOMessage, d, errKeyNotFound
}
var json string
var geoobj bool
o, _, ok := col.Get(id)
if ok {
geoobj = objIsSpatial(o)
json = o.String()
}
njson, err := sjson.Delete(json, path)
if err != nil {
return NOMessage, d, err
}
if njson == json {
switch msg.OutputType {
case JSON:
return NOMessage, d, errPathNotFound
case RESP:
2017-10-05 18:20:40 +03:00
return resp.IntegerValue(0), d, nil
}
return NOMessage, d, nil
}
json = njson
if geoobj {
nmsg := *msg
nmsg.Args = []string{"SET", key, id, "OBJECT", json}
// SET key id OBJECT json
return s.cmdSet(&nmsg, false)
}
2016-12-14 17:37:02 +03:00
d.key = key
d.id = id
d.obj = collection.String(json)
2016-12-14 17:37:02 +03:00
d.timestamp = time.Now()
d.updated = true
s.clearIDExpires(d.key, d.id)
2018-08-16 23:07:55 +03:00
col.Set(d.id, d.obj, nil, nil)
switch msg.OutputType {
case JSON:
var buf bytes.Buffer
buf.WriteString(`{"ok":true`)
buf.WriteString(`,"elapsed":"` + time.Since(start).String() + "\"}")
2017-10-05 18:20:40 +03:00
return resp.StringValue(buf.String()), d, nil
case RESP:
2017-10-05 18:20:40 +03:00
return resp.IntegerValue(1), d, nil
}
return NOMessage, d, nil
}