Add FGET command

This commit is contained in:
Chris Rice 2024-03-18 20:10:04 +00:00
parent 72478a5a6f
commit 48394af724
7 changed files with 113 additions and 4 deletions

View File

@ -144,6 +144,11 @@ To set a field when an object already exists:
> fset fleet truck1 speed 90
```
To get a field when an object already exists:
```
> fget fleet truck1 speed
```
## Searching
Tile38 has support to search for objects and points that are within or intersects other objects. All object types can be searched including Polygons, MultiPolygons, GeometryCollections, etc.

View File

@ -196,6 +196,26 @@
"since": "1.0.0",
"group": "keys"
},
"FGET": {
"summary": "Gets the value for the field of an id",
"complexity": "O(1)",
"arguments": [
{
"name": "key",
"type": "string"
},
{
"name": "id",
"type": "string"
},
{
"name": "field",
"type": "string"
}
],
"since": "1.33.0",
"group": "keys"
},
"BOUNDS": {
"summary": "Get the combined bounds of all the objects in a key",
"complexity": "O(1)",

View File

@ -362,6 +362,26 @@ var commandsJSON = `{
"since": "1.0.0",
"group": "keys"
},
"FGET": {
"summary": "Gets the value for the field of an id",
"complexity": "O(1)",
"arguments": [
{
"name": "key",
"type": "string"
},
{
"name": "id",
"type": "string"
},
{
"name": "field",
"type": "string"
}
],
"since": "1.33.0",
"group": "keys"
},
"BOUNDS": {
"summary": "Get the combined bounds of all the objects in a key",
"complexity": "O(1)",

View File

@ -907,6 +907,46 @@ func (s *Server) cmdFSET(msg *Message) (resp.Value, commandDetails, error) {
return res, d, nil
}
// FGET key id field
func (s *Server) cmdFGET(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)
// >> Response
var buf bytes.Buffer
switch msg.OutputType {
case JSON:
buf.WriteString(`{"ok":true`)
buf.WriteString(`,"value":` + f.Value().JSON())
buf.WriteString(`,"elapsed":"` + time.Since(start).String() + "\"}")
return resp.StringValue(buf.String()), nil
case RESP:
return resp.StringValue(f.Value().Data()), nil
}
return NOMessage, nil
}
// EXPIRE key id seconds
func (s *Server) cmdEXPIRE(msg *Message) (resp.Value, commandDetails, error) {
start := time.Now()

View File

@ -665,6 +665,8 @@ func (s *Server) commandInScript(msg *Message) (
res, err = s.cmdBOUNDS(msg)
case "get":
res, err = s.cmdGET(msg)
case "fget":
res, err = s.cmdFGET(msg)
case "jget":
res, err = s.cmdJget(msg)
case "jset":
@ -735,7 +737,7 @@ func (s *Server) luaTile38AtomicRW(msg *Message) (resp.Value, error) {
return resp.NullValue(), errReadOnly
}
case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search",
"ttl", "bounds", "server", "info", "type", "jget", "test":
"ttl", "bounds", "server", "info", "type", "jget", "fget", "test":
// read operations
if s.config.followHost() != "" && !s.fcuponce {
return resp.NullValue(), errCatchingUp
@ -788,7 +790,7 @@ func (s *Server) luaTile38AtomicRO(msg *Message) (resp.Value, error) {
return resp.NullValue(), errReadOnly
case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search",
"ttl", "bounds", "server", "info", "type", "jget", "test":
"ttl", "bounds", "server", "info", "type", "jget", "fget", "test":
// read operations
if s.config.followHost() != "" && !s.fcuponce {
return resp.NullValue(), errCatchingUp
@ -839,7 +841,7 @@ func (s *Server) luaTile38NonAtomic(msg *Message) (resp.Value, error) {
return resp.NullValue(), errReadOnly
}
case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search",
"ttl", "bounds", "server", "info", "type", "jget", "test":
"ttl", "bounds", "server", "info", "type", "jget", "fget", "test":
// read operations
s.mu.RLock()
defer s.mu.RUnlock()

View File

@ -1024,7 +1024,7 @@ func (s *Server) handleInputCommand(client *Client, msg *Message) error {
}
case "get", "keys", "scan", "nearby", "within", "intersects", "hooks",
"chans", "search", "ttl", "bounds", "server", "info", "type", "jget",
"evalro", "evalrosha", "healthz", "role":
"evalro", "evalrosha", "healthz", "role", "fget":
// read operations
s.mu.RLock()
@ -1227,6 +1227,8 @@ func (s *Server) command(msg *Message, client *Client) (
res, err = s.cmdBOUNDS(msg)
case "get":
res, err = s.cmdGET(msg)
case "fget":
res, err = s.cmdFGET(msg)
case "jget":
res, err = s.cmdJget(msg)
case "jset":

View File

@ -19,6 +19,7 @@ func subTestKeys(g *testGroup) {
g.regSubTest("RENAMENX", keys_RENAMENX_test)
g.regSubTest("EXPIRE", keys_EXPIRE_test)
g.regSubTest("FSET", keys_FSET_test)
g.regSubTest("FGET", keys_FGET_test)
g.regSubTest("GET", keys_GET_test)
g.regSubTest("KEYS", keys_KEYS_test)
g.regSubTest("PERSIST", keys_PERSIST_test)
@ -201,6 +202,25 @@ func keys_FSET_test(mc *mockServer) error {
Do("FSET", "mykey", "myid", "f2", 0).JSON().OK(),
)
}
func keys_FGET_test(mc *mockServer) error {
return mc.DoBatch(
Do("SET", "mykey", "myid", "HASH", "9my5xp7").OK(),
Do("GET", "mykey", "myid", "WITHFIELDS", "HASH", 7).Str("[9my5xp7]"),
Do("FSET", "mykey", "myid", "f1", 105.6).Str("1"),
Do("FGET", "mykey", "myid", "f1").Str("105.6"),
Do("FSET", "mykey", "myid", "f1", 1.1, "f2", 2.2).Str("2"),
Do("FGET", "mykey", "myid", "f2").Str("2.2"),
Do("FGET", "mykey", "myid", "f1").Str("1.1"),
Do("FGET", "mykey", "myid", "f1").JSON().Str(`{"ok":true,"value":1.1}`),
Do("FSET", "mykey", "myid", "f3", "a").Str("1"),
Do("FGET", "mykey", "myid", "f3").Str("a"),
Do("FGET", "mykey", "myid", "f4").Str("0"),
Do("FGET", "mykey", "myid", "f4").JSON().Str(`{"ok":true,"value":0}`),
Do("FGET", "mykey", "myid").Err("wrong number of arguments for 'fget' command"),
Do("FGET", "mykey2", "myid", "a", "b").Err("key not found"),
Do("FGET", "mykey", "myid2", "a", "b").Err("id not found"),
)
}
func keys_GET_test(mc *mockServer) error {
return mc.DoBatch(
Do("SET", "mykey", "myid", "STRING", "value").OK(),