Skip to content

Added GEOADD, GEODIST, GEOSEARCH commands #1742

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ require (
github.com/cespare/xxhash/v2 v2.3.0
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da
github.com/dicedb/dicedb-go v1.0.10
github.com/emirpasic/gods v1.18.1
github.com/gobwas/glob v0.2.3
github.com/google/btree v1.1.3
github.com/google/go-cmp v0.6.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFP
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dicedb/dicedb-go v1.0.10 h1:inKRSmzpXp7M4vleOfJUV4woISCUFVceDckeum461aw=
github.com/dicedb/dicedb-go v1.0.10/go.mod h1:V1fiCJnPfSObKWrOJ/zrhHEGlLwT9k3pKCto3sz1oW8=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
Expand Down
123 changes: 123 additions & 0 deletions internal/cmd/cmd_geoadd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright (c) 2022-present, DiceDB contributors
// All rights reserved. Licensed under the BSD 3-Clause License. See LICENSE file in the project root for full license information.

package cmd

import (
"strconv"

"github.com/dicedb/dice/internal/errors"
"github.com/dicedb/dice/internal/object"
"github.com/dicedb/dice/internal/shardmanager"
dsstore "github.com/dicedb/dice/internal/store"
"github.com/dicedb/dice/internal/types"

Check failure on line 13 in internal/cmd/cmd_geoadd.go

View workflow job for this annotation

GitHub Actions / lint

could not import github.com/dicedb/dice/internal/types (-: # github.com/dicedb/dice/internal/types
"github.com/dicedb/dicedb-go/wire"
)

var cGEOADD = &CommandMeta{
Name: "GEOADD",
Syntax: "GEOADD key [NX | XX] [CH] longitude latitude member [longitude latitude member ...]",
HelpShort: "GEOADD adds all the specified GEO members with the specified longitude & latitude pair to the sorted set stored at key",
HelpLong: `
GEOADD adds all the specified GEO members with the specified longitude & latitude pair to the sorted set stored at key
The command takes arguments in the standard format x,y so the longitude must be specified before the latitude.
There are limits to the coordinates that can be indexed: areas very near to the poles are not indexable.

The exact limits, as specified by EPSG:900913 / EPSG:3785 / OSGEO:41001 are the following:

- Valid longitudes are from -180 to 180 degrees.
- Valid latitudes are from -85.05112878 to 85.05112878 degrees.

This has similar options as ZADD
- NX: Only add new elements and do not update existing elements
- XX: Only update existing elements and do not add new elements
`,
Examples: `
localhost:7379> GEOADD Delhi NX 77.2096 28.6145 "Central Delhi"
OK 1
localhost:7379> GEOADD Delhi 77.2167 28.6315 CP 77.2295 28.6129 IndiaGate 77.1197 28.6412 Rajouri 77.1000 28.5562 Airport 77.1900 28.6517 KarolBagh
OK 5
localhost:7379> GEOADD Delhi NX 77.2096 280 "Central Delhi"
ERR Invalid Longitude, Latitude pair ('77.209600', '280.000000')! Check the range in Docs
`,
Eval: evalGEOADD,
Execute: executeGEOADD,
}

func init() {
CommandRegistry.AddCommand(cGEOADD)
}

func newGEOADDRes(count int64) *CmdRes {
return &CmdRes{
Rs: &wire.Result{
Message: "OK",
Status: wire.Status_OK,
Response: &wire.Result_GEOADDRes{
GEOADDRes: &wire.GEOADDRes{
Count: count,
},
},
},
}
}

var (
GEOADDResNilRes = newGEOADDRes(0)
)

func evalGEOADD(c *Cmd, s *dsstore.Store) (*CmdRes, error) {
if len(c.C.Args) < 4 {
return GEOADDResNilRes, errors.ErrWrongArgumentCount("GEOADD")
}

key := c.C.Args[0]
params, nonParams := parseParams(c.C.Args[1:])

if len(nonParams)%3 != 0 {
return GEOADDResNilRes, errors.ErrWrongArgumentCount("GEOADD")
}

var gr *types.GeoRegistry
obj := s.Get(key)
if obj == nil {
gr = types.NewGeoRegistry()
} else {
if obj.Type != object.ObjTypeGeoRegistry {
return GEOADDResNilRes, errors.ErrWrongTypeOperation
}
gr = obj.Value.(*types.GeoRegistry)
}

GeoCoordinates, members := []*types.GeoCoordinate{}, []string{}
for i := 0; i < len(nonParams); i += 3 {
lon, errLon := strconv.ParseFloat(nonParams[i], 10)
lat, errLat := strconv.ParseFloat(nonParams[i+1], 10)
if errLon != nil || errLat != nil {
return GEOADDResNilRes, errors.ErrInvalidNumberFormat
}
coordinate, err := types.NewGeoCoordinateFromLonLat(lon, lat)
if err != nil {
return GEOADDResNilRes, err
}
GeoCoordinates = append(GeoCoordinates, coordinate)
members = append(members, nonParams[i+2])
}

count, err := gr.Add(GeoCoordinates, members, params)
if err != nil {
return GEOADDResNilRes, err
}

s.Put(key, s.NewObj(gr, -1, object.ObjTypeGeoRegistry), dsstore.WithPutCmd(dsstore.ZAdd))
return newGEOADDRes(count), nil
}

func executeGEOADD(c *Cmd, sm *shardmanager.ShardManager) (*CmdRes, error) {
if len(c.C.Args) < 4 {
return GEOADDResNilRes, errors.ErrWrongArgumentCount("GEOADD")
}

shard := sm.GetShardForKey(c.C.Args[0])
return evalGEOADD(c, shard.Thread.Store())
}
103 changes: 103 additions & 0 deletions internal/cmd/cmd_geodist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) 2022-present, DiceDB contributors
// All rights reserved. Licensed under the BSD 3-Clause License. See LICENSE file in the project root for full license information.

package cmd

import (
"github.com/dicedb/dice/internal/errors"
"github.com/dicedb/dice/internal/object"
"github.com/dicedb/dice/internal/shardmanager"
dsstore "github.com/dicedb/dice/internal/store"
"github.com/dicedb/dice/internal/types"
"github.com/dicedb/dicedb-go/wire"
)

var cGEODIST = &CommandMeta{
Name: "GEODIST",
Syntax: "GEODIST key member1 member2 [M | KM | FT | MI]",
HelpShort: "GEODIST Return the distance between two members in the geospatial index represented by the sorted set.",
HelpLong: `
GEODIST Return the distance between two members in the geospatial index represented by the sorted set.
If any of the member is null, this will return Nil Output

The unit must be one of the following, and defaults to meters:
- m for meters.
- km for kilometers.
- mi for miles.
- ft for feet.
`,
Examples: `
localhost:7379> GEOADD Delhi 77.2096 28.6145 centralDelhi 77.2167 28.6315 CP 77.2295 28.6129 IndiaGate
OK 3
localhost:7379> GEODIST Delhi CP IndiaGate km
OK 2.416700
`,
Eval: evalGEODIST,
Execute: executeGEODIST,
}

func init() {
CommandRegistry.AddCommand(cGEODIST)
}

func newGEODISTRes(distance float64) *CmdRes {
return &CmdRes{
Rs: &wire.Result{
Message: "OK",
Status: wire.Status_OK,
Response: &wire.Result_GEODISTRes{
GEODISTRes: &wire.GEODISTRes{
Distance: distance,
},
},
},
}
}

var (
GEODISTResNilRes = newGEODISTRes(0)
)

func evalGEODIST(c *Cmd, s *dsstore.Store) (*CmdRes, error) {
if len(c.C.Args) < 3 || len(c.C.Args) > 4 {
return GEODISTResNilRes, errors.ErrWrongArgumentCount("GEODIST")
}

key := c.C.Args[0]
params, nonParams := parseParams(c.C.Args[1:])

unit := types.GetUnitTypeFromParsedParams(params)
if len(c.C.Args) == 4 && len(unit) == 0 {
return GEODISTResNilRes, errors.ErrInvalidUnit(c.C.Args[3])
} else if len(unit) == 0 {
unit = types.M
}

var gr *types.GeoRegistry
obj := s.Get(key)
if obj == nil {
return GEODISTResNilRes, nil
}
if obj.Type != object.ObjTypeGeoRegistry {
return GEODISTResNilRes, errors.ErrWrongTypeOperation
}
gr = obj.Value.(*types.GeoRegistry)

dist, err := gr.GetDistanceBetweenMembers(nonParams[0], nonParams[1], unit)

if err != nil {
return GEODISTResNilRes, err
}

return newGEODISTRes(dist), nil

}

func executeGEODIST(c *Cmd, sm *shardmanager.ShardManager) (*CmdRes, error) {
if len(c.C.Args) < 3 || len(c.C.Args) > 4 {
return GEODISTResNilRes, errors.ErrWrongArgumentCount("GEODIST")
}

shard := sm.GetShardForKey(c.C.Args[0])
return evalGEODIST(c, shard.Thread.Store())
}
89 changes: 89 additions & 0 deletions internal/cmd/cmd_geohash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) 2022-present, DiceDB contributors
// All rights reserved. Licensed under the BSD 3-Clause License. See LICENSE file in the project root for full license information.

package cmd

import (
"github.com/dicedb/dice/internal/errors"
"github.com/dicedb/dice/internal/object"
"github.com/dicedb/dice/internal/shardmanager"
dsstore "github.com/dicedb/dice/internal/store"
"github.com/dicedb/dice/internal/types"
"github.com/dicedb/dicedb-go/wire"
)

var cGEOHASH = &CommandMeta{
Name: "GEOHASH",
Syntax: "GEOHASH key [member [member ...]]",
HelpShort: "Returns valid Geohash strings representing the position of one or more elements in a sorted set value representing a geospatial index",
HelpLong: `
The command returns 11 characters Geohash strings, so no precision is lost compared to the Redis internal 52 bit representation
The returned Geohashes have the following properties:
1. They can be shortened removing characters from the right. It will lose precision but will still point to the same area.
2. Strings with a similar prefix are nearby, but the contrary is not true, it is possible that strings with different prefixes are nearby too.
3. It is possible to use them in geohash.org URLs such as http://geohash.org/<geohash-string>.
`,
Examples: `
localhost:7379> GEOADD Delhi 77.2167 28.6315 CP 77.2295 28.6129 IndiaGate 77.1197 28.6412 Rajouri 77.1000 28.5562 Airport 77.1900 28.6517 KarolBagh
OK 5
localhost:7379> GEOHASh Delhi CP IndiaGate
OK
0) ttnfvh5qxd0
1) ttnfv2uf1z0
`,
Eval: evalGEOHASH,
Execute: executeGEOHASH,
}

func init() {
CommandRegistry.AddCommand(cGEOHASH)
}

func newGEOHASHRes(hashArr []string) *CmdRes {
return &CmdRes{
Rs: &wire.Result{
Message: "OK",
Status: wire.Status_OK,
Response: &wire.Result_GEOHASHRes{
GEOHASHRes: &wire.GEOHASHRes{
Hashes: hashArr,
},
},
},
}
}

var (
GEOHASHResNilRes = newGEOHASHRes([]string{})
)

func evalGEOHASH(c *Cmd, s *dsstore.Store) (*CmdRes, error) {
if len(c.C.Args) < 2 {
return GEOHASHResNilRes, errors.ErrWrongArgumentCount("GEOHASH")
}

key := c.C.Args[0]
var gr *types.GeoRegistry
obj := s.Get(key)
if obj == nil {
return GEOHASHResNilRes, nil
}
if obj.Type != object.ObjTypeGeoRegistry {
return GEOHASHResNilRes, errors.ErrWrongTypeOperation
}
gr = obj.Value.(*types.GeoRegistry)

hashArr := gr.Get11BytesHash(c.C.Args[1:])

return newGEOHASHRes(hashArr), nil

}

func executeGEOHASH(c *Cmd, sm *shardmanager.ShardManager) (*CmdRes, error) {
if len(c.C.Args) < 2 {
return GEOHASHResNilRes, errors.ErrWrongArgumentCount("GEOHASH")
}

shard := sm.GetShardForKey(c.C.Args[0])
return evalGEOHASH(c, shard.Thread.Store())
}
Loading