2018-10-29 01:49:45 +03:00
|
|
|
package server
|
2016-03-05 02:08:16 +03:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"math"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2016-03-28 18:57:41 +03:00
|
|
|
|
2019-10-30 20:17:59 +03:00
|
|
|
lua "github.com/yuin/gopher-lua"
|
2016-03-05 02:08:16 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
const defaultSearchOutput = outputObjects
|
|
|
|
|
|
|
|
var errInvalidNumberOfArguments = errors.New("invalid number of arguments")
|
|
|
|
var errKeyNotFound = errors.New("key not found")
|
|
|
|
var errIDNotFound = errors.New("id not found")
|
2016-10-31 20:17:30 +03:00
|
|
|
var errIDAlreadyExists = errors.New("id already exists")
|
2016-12-12 20:33:28 +03:00
|
|
|
var errPathNotFound = errors.New("path not found")
|
2018-12-28 04:15:53 +03:00
|
|
|
var errKeyHasHooksSet = errors.New("key has hooks set")
|
2020-03-20 08:09:24 +03:00
|
|
|
var errNotRectangle = errors.New("not a rectangle")
|
2016-03-05 02:08:16 +03:00
|
|
|
|
|
|
|
func errInvalidArgument(arg string) error {
|
|
|
|
return fmt.Errorf("invalid argument '%s'", arg)
|
|
|
|
}
|
|
|
|
func errDuplicateArgument(arg string) error {
|
|
|
|
return fmt.Errorf("duplicate argument '%s'", arg)
|
|
|
|
}
|
|
|
|
func token(line string) (newLine, token string) {
|
|
|
|
for i := 0; i < len(line); i++ {
|
|
|
|
if line[i] == ' ' {
|
|
|
|
return line[i+1:], line[:i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", line
|
|
|
|
}
|
|
|
|
|
2018-10-29 01:49:45 +03:00
|
|
|
func tokenval(vs []string) (nvs []string, token string, ok bool) {
|
2016-03-28 18:57:41 +03:00
|
|
|
if len(vs) > 0 {
|
2018-10-29 01:49:45 +03:00
|
|
|
token = vs[0]
|
2016-03-28 18:57:41 +03:00
|
|
|
nvs = vs[1:]
|
|
|
|
ok = true
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-10-29 01:49:45 +03:00
|
|
|
func tokenvalbytes(vs []string) (nvs []string, token []byte, ok bool) {
|
2016-11-09 23:43:26 +03:00
|
|
|
if len(vs) > 0 {
|
2018-10-29 01:49:45 +03:00
|
|
|
token = []byte(vs[0])
|
2016-11-09 23:43:26 +03:00
|
|
|
nvs = vs[1:]
|
|
|
|
ok = true
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func lcb(s1 []byte, s2 string) bool {
|
|
|
|
if len(s1) != len(s2) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for i := 0; i < len(s1); i++ {
|
|
|
|
ch := s1[i]
|
|
|
|
if ch >= 'A' && ch <= 'Z' {
|
|
|
|
if ch+32 != s2[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else if ch != s2[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
2016-03-05 02:08:16 +03:00
|
|
|
func lc(s1, s2 string) bool {
|
|
|
|
if len(s1) != len(s2) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for i := 0; i < len(s1); i++ {
|
|
|
|
ch := s1[i]
|
|
|
|
if ch >= 'A' && ch <= 'Z' {
|
|
|
|
if ch+32 != s2[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else if ch != s2[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
type whereT struct {
|
|
|
|
field string
|
2020-03-25 21:53:44 +03:00
|
|
|
index int
|
2016-03-05 02:08:16 +03:00
|
|
|
minx bool
|
|
|
|
min float64
|
|
|
|
maxx bool
|
|
|
|
max float64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (where whereT) match(value float64) bool {
|
|
|
|
if !where.minx {
|
|
|
|
if value < where.min {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if value <= where.min {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !where.maxx {
|
|
|
|
if value > where.max {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if value >= where.max {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2017-08-23 23:13:12 +03:00
|
|
|
type whereinT struct {
|
Lua scripting feature. (#224)
* Start on lua scripting
* Implement evalsha, script load, script exists, and script flush
* Type conversions from lua to resp/json.
Refactor to make luastate and luascripts persistent in the controller.
* Change controller.command and all underlying commands to return resp.Value.
Serialize only during the ouput.
* First stab at tile38 call from lua
* Change tile38 into tile38.call in Lua
* Property return errors from scripts
* Minor refactoring. No locking on script run
* Cleanup/refactoring
* Create a pool of 5 lua states, allow for more as needed. Refactor.
* Use safe map for scripts. Add a limit for max number of lua states. Refactor.
* Refactor
* Refactor script commands into atomic, read-only, and non-atomic classes.
Proper locking for all three classes.
Add tests for scripts
* More tests for scripts
* Properly escape newlines in lua-produced errors
* Better test for readonly failure
* Correctly convert ok/err messages between lua and resp.
Add pcall, sha1hex, error_reply, status_reply functions to tile38 namespace in lua.
* Add pcall test. Change writeErr to work with string argument
* Make sure eval/evalsha never attempt to write AOF
* Add eval-set and eval-get to benchmarks
* Fix eval benchmark tests, add more
* Improve benchmarks
* Optimizations and refactoring.
* Add lua memtest
* Typo
* Add dependency
* golint fixes
* gofmt fixes
* Add scripting commands to the core/commands.json
* Use ARGV for args inside lua
2017-10-05 18:20:40 +03:00
|
|
|
field string
|
2020-03-25 21:53:44 +03:00
|
|
|
index int
|
2020-03-25 08:08:55 +03:00
|
|
|
valArr []float64
|
2017-08-23 23:13:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (wherein whereinT) match(value float64) bool {
|
2020-03-25 08:08:55 +03:00
|
|
|
for _, val := range wherein.valArr {
|
|
|
|
if val == value {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2017-08-23 23:13:12 +03:00
|
|
|
}
|
|
|
|
|
2018-02-15 22:08:27 +03:00
|
|
|
type whereevalT struct {
|
2018-10-29 01:49:45 +03:00
|
|
|
c *Server
|
2018-08-14 03:05:30 +03:00
|
|
|
luaState *lua.LState
|
|
|
|
fn *lua.LFunction
|
2018-02-15 22:08:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (whereeval whereevalT) Close() {
|
|
|
|
luaSetRawGlobals(
|
|
|
|
whereeval.luaState, map[string]lua.LValue{
|
2018-08-14 03:05:30 +03:00
|
|
|
"ARGV": lua.LNil,
|
2018-02-15 22:08:27 +03:00
|
|
|
})
|
|
|
|
whereeval.c.luapool.Put(whereeval.luaState)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (whereeval whereevalT) match(fieldsWithNames map[string]float64) bool {
|
|
|
|
fieldsTbl := whereeval.luaState.CreateTable(0, len(fieldsWithNames))
|
|
|
|
for field, val := range fieldsWithNames {
|
|
|
|
fieldsTbl.RawSetString(field, lua.LNumber(val))
|
|
|
|
}
|
|
|
|
|
|
|
|
luaSetRawGlobals(
|
|
|
|
whereeval.luaState, map[string]lua.LValue{
|
2018-08-14 03:05:30 +03:00
|
|
|
"FIELDS": fieldsTbl,
|
2018-02-15 22:08:27 +03:00
|
|
|
})
|
|
|
|
defer luaSetRawGlobals(
|
|
|
|
whereeval.luaState, map[string]lua.LValue{
|
2018-08-14 03:05:30 +03:00
|
|
|
"FIELDS": lua.LNil,
|
2018-02-15 22:08:27 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
whereeval.luaState.Push(whereeval.fn)
|
|
|
|
if err := whereeval.luaState.PCall(0, 1, nil); err != nil {
|
|
|
|
panic(err.Error())
|
|
|
|
}
|
|
|
|
ret := whereeval.luaState.Get(-1)
|
|
|
|
whereeval.luaState.Pop(1)
|
|
|
|
|
|
|
|
// Make bool out of returned lua value
|
|
|
|
switch ret.Type() {
|
|
|
|
case lua.LTNil:
|
|
|
|
return false
|
|
|
|
case lua.LTBool:
|
|
|
|
return ret == lua.LTrue
|
|
|
|
case lua.LTNumber:
|
|
|
|
return float64(ret.(lua.LNumber)) != 0
|
|
|
|
case lua.LTString:
|
|
|
|
return ret.String() != ""
|
|
|
|
case lua.LTTable:
|
|
|
|
tbl := ret.(*lua.LTable)
|
|
|
|
if tbl.Len() != 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
var match bool
|
2018-08-14 03:05:30 +03:00
|
|
|
tbl.ForEach(func(lk lua.LValue, lv lua.LValue) { match = true })
|
2018-02-15 22:08:27 +03:00
|
|
|
return match
|
|
|
|
}
|
|
|
|
panic(fmt.Sprintf("Script returned value of type %s", ret.Type()))
|
|
|
|
}
|
|
|
|
|
2016-03-05 02:08:16 +03:00
|
|
|
type searchScanBaseTokens struct {
|
2018-08-14 03:05:30 +03:00
|
|
|
key string
|
|
|
|
cursor uint64
|
|
|
|
output outputT
|
|
|
|
precision uint64
|
|
|
|
fence bool
|
|
|
|
distance bool
|
2018-08-23 02:26:27 +03:00
|
|
|
nodwell bool
|
2018-08-14 03:05:30 +03:00
|
|
|
detect map[string]bool
|
|
|
|
accept map[string]bool
|
2022-09-02 05:43:30 +03:00
|
|
|
globs []string
|
2018-08-14 03:05:30 +03:00
|
|
|
wheres []whereT
|
|
|
|
whereins []whereinT
|
|
|
|
whereevals []whereevalT
|
|
|
|
nofields bool
|
|
|
|
ulimit bool
|
|
|
|
limit uint64
|
|
|
|
usparse bool
|
|
|
|
sparse uint8
|
|
|
|
desc bool
|
|
|
|
clip bool
|
2021-12-10 04:14:50 +03:00
|
|
|
buffer float64
|
|
|
|
hasbuffer bool
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
|
|
|
|
2019-10-30 20:17:59 +03:00
|
|
|
func (s *Server) parseSearchScanBaseTokens(
|
2018-10-29 01:49:45 +03:00
|
|
|
cmd string, t searchScanBaseTokens, vs []string,
|
2018-08-14 03:05:30 +03:00
|
|
|
) (
|
2018-10-29 01:49:45 +03:00
|
|
|
vsout []string, tout searchScanBaseTokens, err error,
|
2018-08-14 03:05:30 +03:00
|
|
|
) {
|
2016-03-29 00:16:21 +03:00
|
|
|
var ok bool
|
|
|
|
if vs, t.key, ok = tokenval(vs); !ok || t.key == "" {
|
2016-03-05 02:08:16 +03:00
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
2018-08-14 03:05:30 +03:00
|
|
|
|
|
|
|
fromFence := t.fence
|
|
|
|
|
2016-03-05 02:08:16 +03:00
|
|
|
var slimit string
|
|
|
|
var ssparse string
|
|
|
|
var scursor string
|
2016-07-12 22:18:16 +03:00
|
|
|
var asc bool
|
2016-03-05 02:08:16 +03:00
|
|
|
for {
|
2016-03-29 00:16:21 +03:00
|
|
|
nvs, wtok, ok := tokenval(vs)
|
|
|
|
if ok && len(wtok) > 0 {
|
2018-08-14 03:05:30 +03:00
|
|
|
switch strings.ToLower(wtok) {
|
2021-12-10 04:14:50 +03:00
|
|
|
case "buffer":
|
|
|
|
vs = nvs
|
|
|
|
var sbuf string
|
|
|
|
if vs, sbuf, ok = tokenval(vs); !ok || sbuf == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var buf float64
|
|
|
|
buf, err = strconv.ParseFloat(sbuf, 64)
|
|
|
|
if err != nil || buf < 0 || math.IsInf(buf, 0) || math.IsNaN(buf) {
|
|
|
|
err = errInvalidArgument(sbuf)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.buffer = buf
|
|
|
|
t.hasbuffer = true
|
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "cursor":
|
2016-03-29 00:16:21 +03:00
|
|
|
vs = nvs
|
2016-03-05 02:08:16 +03:00
|
|
|
if scursor != "" {
|
|
|
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
|
|
|
return
|
|
|
|
}
|
2016-03-29 00:16:21 +03:00
|
|
|
if vs, scursor, ok = tokenval(vs); !ok || scursor == "" {
|
2016-03-05 02:08:16 +03:00
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "where":
|
2016-03-29 00:16:21 +03:00
|
|
|
vs = nvs
|
2016-03-05 02:08:16 +03:00
|
|
|
var field, smin, smax string
|
2016-03-29 00:16:21 +03:00
|
|
|
if vs, field, ok = tokenval(vs); !ok || field == "" {
|
2016-03-05 02:08:16 +03:00
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
2016-03-29 00:16:21 +03:00
|
|
|
if vs, smin, ok = tokenval(vs); !ok || smin == "" {
|
2016-03-05 02:08:16 +03:00
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
2016-03-29 00:16:21 +03:00
|
|
|
if vs, smax, ok = tokenval(vs); !ok || smax == "" {
|
2016-03-05 02:08:16 +03:00
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var minx, maxx bool
|
|
|
|
var min, max float64
|
|
|
|
if strings.ToLower(smin) == "-inf" {
|
|
|
|
min = math.Inf(-1)
|
|
|
|
} else {
|
|
|
|
if strings.HasPrefix(smin, "(") {
|
|
|
|
minx = true
|
|
|
|
smin = smin[1:]
|
|
|
|
}
|
|
|
|
min, err = strconv.ParseFloat(smin, 64)
|
|
|
|
if err != nil {
|
|
|
|
err = errInvalidArgument(smin)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if strings.ToLower(smax) == "+inf" {
|
|
|
|
max = math.Inf(+1)
|
|
|
|
} else {
|
|
|
|
if strings.HasPrefix(smax, "(") {
|
|
|
|
maxx = true
|
|
|
|
smax = smax[1:]
|
|
|
|
}
|
|
|
|
max, err = strconv.ParseFloat(smax, 64)
|
|
|
|
if err != nil {
|
|
|
|
err = errInvalidArgument(smax)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2020-03-25 21:53:44 +03:00
|
|
|
t.wheres = append(t.wheres, whereT{field, -1, minx, min, maxx, max})
|
2016-03-05 02:08:16 +03:00
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "wherein":
|
2017-08-23 23:13:12 +03:00
|
|
|
vs = nvs
|
Lua scripting feature. (#224)
* Start on lua scripting
* Implement evalsha, script load, script exists, and script flush
* Type conversions from lua to resp/json.
Refactor to make luastate and luascripts persistent in the controller.
* Change controller.command and all underlying commands to return resp.Value.
Serialize only during the ouput.
* First stab at tile38 call from lua
* Change tile38 into tile38.call in Lua
* Property return errors from scripts
* Minor refactoring. No locking on script run
* Cleanup/refactoring
* Create a pool of 5 lua states, allow for more as needed. Refactor.
* Use safe map for scripts. Add a limit for max number of lua states. Refactor.
* Refactor
* Refactor script commands into atomic, read-only, and non-atomic classes.
Proper locking for all three classes.
Add tests for scripts
* More tests for scripts
* Properly escape newlines in lua-produced errors
* Better test for readonly failure
* Correctly convert ok/err messages between lua and resp.
Add pcall, sha1hex, error_reply, status_reply functions to tile38 namespace in lua.
* Add pcall test. Change writeErr to work with string argument
* Make sure eval/evalsha never attempt to write AOF
* Add eval-set and eval-get to benchmarks
* Fix eval benchmark tests, add more
* Improve benchmarks
* Optimizations and refactoring.
* Add lua memtest
* Typo
* Add dependency
* golint fixes
* gofmt fixes
* Add scripting commands to the core/commands.json
* Use ARGV for args inside lua
2017-10-05 18:20:40 +03:00
|
|
|
var field, nvalsStr, valStr string
|
2017-08-23 23:13:12 +03:00
|
|
|
if vs, field, ok = tokenval(vs); !ok || field == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
Lua scripting feature. (#224)
* Start on lua scripting
* Implement evalsha, script load, script exists, and script flush
* Type conversions from lua to resp/json.
Refactor to make luastate and luascripts persistent in the controller.
* Change controller.command and all underlying commands to return resp.Value.
Serialize only during the ouput.
* First stab at tile38 call from lua
* Change tile38 into tile38.call in Lua
* Property return errors from scripts
* Minor refactoring. No locking on script run
* Cleanup/refactoring
* Create a pool of 5 lua states, allow for more as needed. Refactor.
* Use safe map for scripts. Add a limit for max number of lua states. Refactor.
* Refactor
* Refactor script commands into atomic, read-only, and non-atomic classes.
Proper locking for all three classes.
Add tests for scripts
* More tests for scripts
* Properly escape newlines in lua-produced errors
* Better test for readonly failure
* Correctly convert ok/err messages between lua and resp.
Add pcall, sha1hex, error_reply, status_reply functions to tile38 namespace in lua.
* Add pcall test. Change writeErr to work with string argument
* Make sure eval/evalsha never attempt to write AOF
* Add eval-set and eval-get to benchmarks
* Fix eval benchmark tests, add more
* Improve benchmarks
* Optimizations and refactoring.
* Add lua memtest
* Typo
* Add dependency
* golint fixes
* gofmt fixes
* Add scripting commands to the core/commands.json
* Use ARGV for args inside lua
2017-10-05 18:20:40 +03:00
|
|
|
if vs, nvalsStr, ok = tokenval(vs); !ok || nvalsStr == "" {
|
2017-08-23 23:13:12 +03:00
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var i, nvals uint64
|
Lua scripting feature. (#224)
* Start on lua scripting
* Implement evalsha, script load, script exists, and script flush
* Type conversions from lua to resp/json.
Refactor to make luastate and luascripts persistent in the controller.
* Change controller.command and all underlying commands to return resp.Value.
Serialize only during the ouput.
* First stab at tile38 call from lua
* Change tile38 into tile38.call in Lua
* Property return errors from scripts
* Minor refactoring. No locking on script run
* Cleanup/refactoring
* Create a pool of 5 lua states, allow for more as needed. Refactor.
* Use safe map for scripts. Add a limit for max number of lua states. Refactor.
* Refactor
* Refactor script commands into atomic, read-only, and non-atomic classes.
Proper locking for all three classes.
Add tests for scripts
* More tests for scripts
* Properly escape newlines in lua-produced errors
* Better test for readonly failure
* Correctly convert ok/err messages between lua and resp.
Add pcall, sha1hex, error_reply, status_reply functions to tile38 namespace in lua.
* Add pcall test. Change writeErr to work with string argument
* Make sure eval/evalsha never attempt to write AOF
* Add eval-set and eval-get to benchmarks
* Fix eval benchmark tests, add more
* Improve benchmarks
* Optimizations and refactoring.
* Add lua memtest
* Typo
* Add dependency
* golint fixes
* gofmt fixes
* Add scripting commands to the core/commands.json
* Use ARGV for args inside lua
2017-10-05 18:20:40 +03:00
|
|
|
if nvals, err = strconv.ParseUint(nvalsStr, 10, 64); err != nil {
|
|
|
|
err = errInvalidArgument(nvalsStr)
|
2017-08-23 23:13:12 +03:00
|
|
|
return
|
|
|
|
}
|
2020-03-25 08:08:55 +03:00
|
|
|
valArr := make([]float64, nvals)
|
2017-08-23 23:13:12 +03:00
|
|
|
var val float64
|
|
|
|
for i = 0; i < nvals; i++ {
|
Lua scripting feature. (#224)
* Start on lua scripting
* Implement evalsha, script load, script exists, and script flush
* Type conversions from lua to resp/json.
Refactor to make luastate and luascripts persistent in the controller.
* Change controller.command and all underlying commands to return resp.Value.
Serialize only during the ouput.
* First stab at tile38 call from lua
* Change tile38 into tile38.call in Lua
* Property return errors from scripts
* Minor refactoring. No locking on script run
* Cleanup/refactoring
* Create a pool of 5 lua states, allow for more as needed. Refactor.
* Use safe map for scripts. Add a limit for max number of lua states. Refactor.
* Refactor
* Refactor script commands into atomic, read-only, and non-atomic classes.
Proper locking for all three classes.
Add tests for scripts
* More tests for scripts
* Properly escape newlines in lua-produced errors
* Better test for readonly failure
* Correctly convert ok/err messages between lua and resp.
Add pcall, sha1hex, error_reply, status_reply functions to tile38 namespace in lua.
* Add pcall test. Change writeErr to work with string argument
* Make sure eval/evalsha never attempt to write AOF
* Add eval-set and eval-get to benchmarks
* Fix eval benchmark tests, add more
* Improve benchmarks
* Optimizations and refactoring.
* Add lua memtest
* Typo
* Add dependency
* golint fixes
* gofmt fixes
* Add scripting commands to the core/commands.json
* Use ARGV for args inside lua
2017-10-05 18:20:40 +03:00
|
|
|
if vs, valStr, ok = tokenval(vs); !ok || valStr == "" {
|
2017-08-23 23:13:12 +03:00
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
Lua scripting feature. (#224)
* Start on lua scripting
* Implement evalsha, script load, script exists, and script flush
* Type conversions from lua to resp/json.
Refactor to make luastate and luascripts persistent in the controller.
* Change controller.command and all underlying commands to return resp.Value.
Serialize only during the ouput.
* First stab at tile38 call from lua
* Change tile38 into tile38.call in Lua
* Property return errors from scripts
* Minor refactoring. No locking on script run
* Cleanup/refactoring
* Create a pool of 5 lua states, allow for more as needed. Refactor.
* Use safe map for scripts. Add a limit for max number of lua states. Refactor.
* Refactor
* Refactor script commands into atomic, read-only, and non-atomic classes.
Proper locking for all three classes.
Add tests for scripts
* More tests for scripts
* Properly escape newlines in lua-produced errors
* Better test for readonly failure
* Correctly convert ok/err messages between lua and resp.
Add pcall, sha1hex, error_reply, status_reply functions to tile38 namespace in lua.
* Add pcall test. Change writeErr to work with string argument
* Make sure eval/evalsha never attempt to write AOF
* Add eval-set and eval-get to benchmarks
* Fix eval benchmark tests, add more
* Improve benchmarks
* Optimizations and refactoring.
* Add lua memtest
* Typo
* Add dependency
* golint fixes
* gofmt fixes
* Add scripting commands to the core/commands.json
* Use ARGV for args inside lua
2017-10-05 18:20:40 +03:00
|
|
|
if val, err = strconv.ParseFloat(valStr, 64); err != nil {
|
|
|
|
err = errInvalidArgument(valStr)
|
2017-08-23 23:13:12 +03:00
|
|
|
return
|
|
|
|
}
|
2020-04-13 02:04:18 +03:00
|
|
|
valArr[i] = val
|
2017-08-23 23:13:12 +03:00
|
|
|
}
|
2020-03-25 21:53:44 +03:00
|
|
|
t.whereins = append(t.whereins, whereinT{field, -1, valArr})
|
2017-08-23 23:13:12 +03:00
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "whereevalsha":
|
|
|
|
fallthrough
|
|
|
|
case "whereeval":
|
2018-02-15 22:08:27 +03:00
|
|
|
scriptIsSha := strings.ToLower(wtok) == "whereevalsha"
|
|
|
|
vs = nvs
|
|
|
|
var script, nargsStr, arg string
|
|
|
|
if vs, script, ok = tokenval(vs); !ok || script == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if vs, nargsStr, ok = tokenval(vs); !ok || nargsStr == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var i, nargs uint64
|
|
|
|
if nargs, err = strconv.ParseUint(nargsStr, 10, 64); err != nil {
|
|
|
|
err = errInvalidArgument(nargsStr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var luaState *lua.LState
|
2019-10-30 20:17:59 +03:00
|
|
|
luaState, err = s.luapool.Get()
|
2018-02-15 22:08:27 +03:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
argsTbl := luaState.CreateTable(len(vs), 0)
|
|
|
|
for i = 0; i < nargs; i++ {
|
|
|
|
if vs, arg, ok = tokenval(vs); !ok || arg == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
argsTbl.Append(lua.LString(arg))
|
|
|
|
}
|
|
|
|
|
|
|
|
var shaSum string
|
|
|
|
if scriptIsSha {
|
|
|
|
shaSum = script
|
|
|
|
} else {
|
|
|
|
shaSum = Sha1Sum(script)
|
|
|
|
}
|
|
|
|
|
|
|
|
luaSetRawGlobals(
|
|
|
|
luaState, map[string]lua.LValue{
|
2018-08-14 03:05:30 +03:00
|
|
|
"ARGV": argsTbl,
|
2018-02-15 22:08:27 +03:00
|
|
|
})
|
|
|
|
|
2019-10-30 20:17:59 +03:00
|
|
|
compiled, ok := s.luascripts.Get(shaSum)
|
2018-02-15 22:08:27 +03:00
|
|
|
var fn *lua.LFunction
|
|
|
|
if ok {
|
|
|
|
fn = &lua.LFunction{
|
|
|
|
IsG: false,
|
|
|
|
Env: luaState.Env,
|
|
|
|
|
|
|
|
Proto: compiled,
|
|
|
|
GFunction: nil,
|
|
|
|
Upvalues: make([]*lua.Upvalue, 0),
|
|
|
|
}
|
|
|
|
} else if scriptIsSha {
|
|
|
|
err = errShaNotFound
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
fn, err = luaState.Load(strings.NewReader(script), "f_"+shaSum)
|
|
|
|
if err != nil {
|
|
|
|
err = makeSafeErr(err)
|
|
|
|
return
|
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
s.luascripts.Put(shaSum, fn.Proto)
|
2018-02-15 22:08:27 +03:00
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
t.whereevals = append(t.whereevals, whereevalT{s, luaState, fn})
|
2018-02-15 22:08:27 +03:00
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "nofields":
|
2016-03-29 00:16:21 +03:00
|
|
|
vs = nvs
|
2016-03-05 02:08:16 +03:00
|
|
|
if t.nofields {
|
|
|
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.nofields = true
|
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "limit":
|
2016-03-29 00:16:21 +03:00
|
|
|
vs = nvs
|
2016-03-05 02:08:16 +03:00
|
|
|
if slimit != "" {
|
|
|
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
|
|
|
return
|
|
|
|
}
|
2016-03-29 00:16:21 +03:00
|
|
|
if vs, slimit, ok = tokenval(vs); !ok || slimit == "" {
|
2016-03-05 02:08:16 +03:00
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "sparse":
|
2016-03-29 00:16:21 +03:00
|
|
|
vs = nvs
|
2016-03-05 02:08:16 +03:00
|
|
|
if ssparse != "" {
|
|
|
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
|
|
|
return
|
|
|
|
}
|
2016-03-29 00:16:21 +03:00
|
|
|
if vs, ssparse, ok = tokenval(vs); !ok || ssparse == "" {
|
2016-03-05 02:08:16 +03:00
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "fence":
|
2016-03-29 00:16:21 +03:00
|
|
|
vs = nvs
|
2018-08-14 03:05:30 +03:00
|
|
|
if t.fence && !fromFence {
|
2016-03-05 02:08:16 +03:00
|
|
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.fence = true
|
2016-04-01 22:46:39 +03:00
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "commands":
|
2016-12-15 20:00:08 +03:00
|
|
|
vs = nvs
|
|
|
|
if t.accept != nil {
|
|
|
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.accept = make(map[string]bool)
|
|
|
|
var peek string
|
|
|
|
if vs, peek, ok = tokenval(vs); !ok || peek == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, s := range strings.Split(peek, ",") {
|
|
|
|
part := strings.TrimSpace(strings.ToLower(s))
|
|
|
|
if t.accept[part] {
|
|
|
|
err = errDuplicateArgument(s)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.accept[part] = true
|
|
|
|
}
|
|
|
|
if len(t.accept) == 0 {
|
|
|
|
t.accept = nil
|
|
|
|
}
|
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "distance":
|
2017-01-10 19:49:48 +03:00
|
|
|
vs = nvs
|
|
|
|
if t.distance {
|
|
|
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.distance = true
|
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "detect":
|
2016-04-01 22:46:39 +03:00
|
|
|
vs = nvs
|
|
|
|
if t.detect != nil {
|
|
|
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.detect = make(map[string]bool)
|
|
|
|
var peek string
|
|
|
|
if vs, peek, ok = tokenval(vs); !ok || peek == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, s := range strings.Split(peek, ",") {
|
|
|
|
part := strings.TrimSpace(strings.ToLower(s))
|
|
|
|
switch part {
|
|
|
|
default:
|
|
|
|
err = errInvalidArgument(peek)
|
|
|
|
return
|
|
|
|
case "inside", "outside", "enter", "exit", "cross":
|
|
|
|
}
|
|
|
|
if t.detect[part] {
|
|
|
|
err = errDuplicateArgument(s)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.detect[part] = true
|
|
|
|
}
|
|
|
|
if len(t.detect) == 0 {
|
|
|
|
t.detect = map[string]bool{
|
|
|
|
"inside": true,
|
|
|
|
"outside": true,
|
|
|
|
"enter": true,
|
|
|
|
"exit": true,
|
|
|
|
"cross": true,
|
|
|
|
}
|
|
|
|
}
|
2016-07-12 22:18:16 +03:00
|
|
|
continue
|
2018-08-23 02:26:27 +03:00
|
|
|
case "nodwell":
|
|
|
|
vs = nvs
|
|
|
|
if t.desc || asc {
|
|
|
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.nodwell = true
|
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "desc":
|
2016-07-12 22:18:16 +03:00
|
|
|
vs = nvs
|
|
|
|
if t.desc || asc {
|
|
|
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.desc = true
|
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "asc":
|
2016-07-12 22:18:16 +03:00
|
|
|
vs = nvs
|
|
|
|
if t.desc || asc {
|
|
|
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
asc = true
|
2016-03-05 02:08:16 +03:00
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "match":
|
2016-03-29 00:16:21 +03:00
|
|
|
vs = nvs
|
2022-09-02 05:43:30 +03:00
|
|
|
var glob string
|
|
|
|
if vs, glob, ok = tokenval(vs); !ok || glob == "" {
|
2016-03-05 02:08:16 +03:00
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
2022-09-02 05:43:30 +03:00
|
|
|
t.globs = append(t.globs, glob)
|
2016-03-05 02:08:16 +03:00
|
|
|
continue
|
2018-08-14 03:05:30 +03:00
|
|
|
case "clip":
|
2018-05-08 02:18:18 +03:00
|
|
|
vs = nvs
|
|
|
|
if t.clip {
|
|
|
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.clip = true
|
|
|
|
continue
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// check to make sure that there aren't any conflicts
|
2016-07-13 06:11:02 +03:00
|
|
|
if cmd == "scan" || cmd == "search" {
|
2016-03-05 02:08:16 +03:00
|
|
|
if ssparse != "" {
|
2016-07-13 06:11:02 +03:00
|
|
|
err = errors.New("SPARSE is not allowed for " + strings.ToUpper(cmd))
|
2016-03-05 02:08:16 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if t.fence {
|
2016-07-13 06:11:02 +03:00
|
|
|
err = errors.New("FENCE is not allowed for " + strings.ToUpper(cmd))
|
2016-03-05 02:08:16 +03:00
|
|
|
return
|
|
|
|
}
|
2016-07-12 22:18:16 +03:00
|
|
|
} else {
|
|
|
|
if t.desc {
|
|
|
|
err = errors.New("DESC is not allowed for " + strings.ToUpper(cmd))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if asc {
|
|
|
|
err = errors.New("ASC is not allowed for " + strings.ToUpper(cmd))
|
|
|
|
return
|
|
|
|
}
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
|
|
|
if ssparse != "" && slimit != "" {
|
|
|
|
err = errors.New("LIMIT is not allowed when SPARSE is specified")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if scursor != "" && ssparse != "" {
|
|
|
|
err = errors.New("CURSOR is not allowed when SPARSE is specified")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if scursor != "" && t.fence {
|
|
|
|
err = errors.New("CURSOR is not allowed when FENCE is specified")
|
|
|
|
return
|
|
|
|
}
|
2016-04-01 22:46:39 +03:00
|
|
|
if t.detect != nil && !t.fence {
|
|
|
|
err = errors.New("DETECT is not allowed when FENCE is not specified")
|
|
|
|
return
|
|
|
|
}
|
2016-03-05 02:08:16 +03:00
|
|
|
|
|
|
|
t.output = defaultSearchOutput
|
2018-10-29 01:49:45 +03:00
|
|
|
var nvs []string
|
2016-03-05 02:08:16 +03:00
|
|
|
var sprecision string
|
|
|
|
var which string
|
2016-03-29 00:16:21 +03:00
|
|
|
if nvs, which, ok = tokenval(vs); ok && which != "" {
|
2016-03-05 02:08:16 +03:00
|
|
|
updline := true
|
|
|
|
switch strings.ToLower(which) {
|
|
|
|
default:
|
|
|
|
if cmd == "scan" {
|
|
|
|
err = errInvalidArgument(which)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
updline = false
|
|
|
|
case "count":
|
|
|
|
t.output = outputCount
|
|
|
|
case "objects":
|
|
|
|
t.output = outputObjects
|
|
|
|
case "points":
|
|
|
|
t.output = outputPoints
|
|
|
|
case "hashes":
|
|
|
|
t.output = outputHashes
|
2016-03-29 00:16:21 +03:00
|
|
|
if nvs, sprecision, ok = tokenval(nvs); !ok || sprecision == "" {
|
2016-03-05 02:08:16 +03:00
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case "bounds":
|
|
|
|
t.output = outputBounds
|
|
|
|
case "ids":
|
|
|
|
t.output = outputIDs
|
|
|
|
}
|
|
|
|
if updline {
|
2016-03-29 00:16:21 +03:00
|
|
|
vs = nvs
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if scursor != "" {
|
|
|
|
if t.cursor, err = strconv.ParseUint(scursor, 10, 64); err != nil {
|
|
|
|
err = errInvalidArgument(scursor)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if sprecision != "" {
|
2020-08-12 20:58:49 +03:00
|
|
|
t.precision, err = strconv.ParseUint(sprecision, 10, 64)
|
|
|
|
if err != nil || t.precision == 0 || t.precision > 12 {
|
2016-03-05 02:08:16 +03:00
|
|
|
err = errInvalidArgument(sprecision)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if slimit != "" {
|
2017-01-31 02:41:12 +03:00
|
|
|
t.ulimit = true
|
2016-03-05 02:08:16 +03:00
|
|
|
if t.limit, err = strconv.ParseUint(slimit, 10, 64); err != nil || t.limit == 0 {
|
|
|
|
err = errInvalidArgument(slimit)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ssparse != "" {
|
2017-01-31 02:41:12 +03:00
|
|
|
t.usparse = true
|
2016-03-05 02:08:16 +03:00
|
|
|
var sparse uint64
|
|
|
|
if sparse, err = strconv.ParseUint(ssparse, 10, 8); err != nil || sparse == 0 || sparse > 8 {
|
|
|
|
err = errInvalidArgument(ssparse)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.sparse = uint8(sparse)
|
|
|
|
t.limit = math.MaxUint64
|
|
|
|
}
|
2016-03-29 00:16:21 +03:00
|
|
|
vsout = vs
|
2018-08-14 03:05:30 +03:00
|
|
|
tout = t
|
2016-03-05 02:08:16 +03:00
|
|
|
return
|
|
|
|
}
|
2019-06-11 00:47:42 +03:00
|
|
|
|
|
|
|
type parentStack []*areaExpression
|
|
|
|
|
|
|
|
func (ps *parentStack) isEmpty() bool {
|
|
|
|
return len(*ps) == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ps *parentStack) push(e *areaExpression) {
|
|
|
|
*ps = append(*ps, e)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ps *parentStack) pop() (e *areaExpression, empty bool) {
|
|
|
|
n := len(*ps)
|
|
|
|
if n == 0 {
|
|
|
|
return nil, true
|
|
|
|
}
|
|
|
|
x := (*ps)[n-1]
|
|
|
|
*ps = (*ps)[:n-1]
|
|
|
|
return x, false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) parseAreaExpression(vsin []string, doClip bool) (vsout []string, ae *areaExpression, err error) {
|
|
|
|
ps := &parentStack{}
|
|
|
|
vsout = vsin[:]
|
2019-06-13 23:10:47 +03:00
|
|
|
var negate, needObj bool
|
2019-06-11 00:47:42 +03:00
|
|
|
loop:
|
|
|
|
for {
|
|
|
|
nvs, wtok, ok := tokenval(vsout)
|
|
|
|
if !ok || len(wtok) == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
switch strings.ToLower(wtok) {
|
2019-06-12 03:13:33 +03:00
|
|
|
case tokenLParen:
|
2019-06-11 00:47:42 +03:00
|
|
|
newExpr := &areaExpression{negate: negate, op: NOOP}
|
|
|
|
negate = false
|
2019-06-13 23:10:47 +03:00
|
|
|
needObj = false
|
2019-06-11 00:47:42 +03:00
|
|
|
if ae != nil {
|
|
|
|
ae.children = append(ae.children, newExpr)
|
|
|
|
}
|
|
|
|
ae = newExpr
|
2019-06-13 22:04:04 +03:00
|
|
|
ps.push(ae)
|
2019-06-11 00:47:42 +03:00
|
|
|
vsout = nvs
|
2019-06-12 03:13:33 +03:00
|
|
|
case tokenRParen:
|
2019-06-13 23:10:47 +03:00
|
|
|
if needObj {
|
|
|
|
err = errInvalidArgument(tokenRParen)
|
2019-06-11 00:47:42 +03:00
|
|
|
return
|
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
parent, empty := ps.pop()
|
|
|
|
if empty {
|
2019-06-12 03:13:33 +03:00
|
|
|
err = errInvalidArgument(tokenRParen)
|
2019-06-11 00:47:42 +03:00
|
|
|
return
|
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
ae = parent
|
2019-06-11 00:47:42 +03:00
|
|
|
vsout = nvs
|
2019-06-12 03:13:33 +03:00
|
|
|
case tokenNOT:
|
2019-06-14 20:02:26 +03:00
|
|
|
negate = !negate
|
2019-06-13 23:10:47 +03:00
|
|
|
needObj = true
|
2019-06-11 00:47:42 +03:00
|
|
|
vsout = nvs
|
2019-06-12 03:13:33 +03:00
|
|
|
case tokenAND:
|
2019-06-13 23:10:47 +03:00
|
|
|
if needObj {
|
|
|
|
err = errInvalidArgument(tokenAND)
|
2019-06-11 00:47:42 +03:00
|
|
|
return
|
|
|
|
}
|
2019-06-13 23:10:47 +03:00
|
|
|
needObj = true
|
2019-06-11 00:47:42 +03:00
|
|
|
if ae == nil {
|
2019-06-12 03:13:33 +03:00
|
|
|
err = errInvalidArgument(tokenAND)
|
2019-06-11 00:47:42 +03:00
|
|
|
return
|
|
|
|
} else if ae.obj == nil {
|
|
|
|
switch ae.op {
|
|
|
|
case OR:
|
|
|
|
numChildren := len(ae.children)
|
|
|
|
if numChildren < 2 {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
ae.children = append(
|
|
|
|
ae.children[:numChildren-1],
|
|
|
|
&areaExpression{
|
|
|
|
op: AND,
|
|
|
|
children: []*areaExpression{ae.children[numChildren-1]}})
|
2019-06-11 00:47:42 +03:00
|
|
|
case NOOP:
|
|
|
|
ae.op = AND
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ae = &areaExpression{op: AND, children: []*areaExpression{ae}}
|
|
|
|
}
|
|
|
|
vsout = nvs
|
2019-06-12 03:13:33 +03:00
|
|
|
case tokenOR:
|
2019-06-13 23:10:47 +03:00
|
|
|
if needObj {
|
|
|
|
err = errInvalidArgument(tokenOR)
|
2019-06-11 00:47:42 +03:00
|
|
|
return
|
|
|
|
}
|
2019-06-13 23:10:47 +03:00
|
|
|
needObj = true
|
2019-06-11 00:47:42 +03:00
|
|
|
if ae == nil {
|
2019-06-12 03:13:33 +03:00
|
|
|
err = errInvalidArgument(tokenOR)
|
2019-06-11 00:47:42 +03:00
|
|
|
return
|
|
|
|
} else if ae.obj == nil {
|
|
|
|
switch ae.op {
|
|
|
|
case AND:
|
|
|
|
if len(ae.children) < 2 {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
ae = &areaExpression{op: OR, children: []*areaExpression{ae}}
|
2019-06-11 00:47:42 +03:00
|
|
|
case NOOP:
|
|
|
|
ae.op = OR
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ae = &areaExpression{op: OR, children: []*areaExpression{ae}}
|
|
|
|
}
|
|
|
|
vsout = nvs
|
2021-09-05 12:48:34 +03:00
|
|
|
case "point", "circle", "object", "bounds", "hash", "quadkey", "tile", "get", "sector":
|
2019-10-30 20:17:59 +03:00
|
|
|
parsedVs, parsedObj, areaErr := s.parseArea(vsout, doClip)
|
|
|
|
if areaErr != nil {
|
2019-06-11 00:47:42 +03:00
|
|
|
err = areaErr
|
|
|
|
return
|
2019-10-30 20:17:59 +03:00
|
|
|
}
|
|
|
|
newExpr := &areaExpression{negate: negate, obj: parsedObj, op: NOOP}
|
|
|
|
negate = false
|
|
|
|
needObj = false
|
|
|
|
if ae == nil {
|
|
|
|
ae = newExpr
|
2019-06-11 00:47:42 +03:00
|
|
|
} else {
|
2019-10-30 20:17:59 +03:00
|
|
|
ae.children = append(ae.children, newExpr)
|
2019-06-11 00:47:42 +03:00
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
vsout = parsedVs
|
2019-06-11 00:47:42 +03:00
|
|
|
default:
|
|
|
|
break loop
|
|
|
|
}
|
|
|
|
}
|
2019-06-13 23:10:47 +03:00
|
|
|
if !ps.isEmpty() || needObj || ae == nil || (ae.obj == nil && len(ae.children) == 0) {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
}
|
2019-06-11 00:47:42 +03:00
|
|
|
return
|
|
|
|
}
|