redis_driver/commands/
geo_commands.rs

1use crate::{
2    prepare_command,
3    resp::{
4        cmd, ArgsOrCollection, CommandArg, CommandArgs, FromSingleValueArray, FromValue, IntoArgs,
5        SingleArgOrCollection, Value,
6    },
7    Error, PreparedCommand, Result,
8};
9
10/// A group of Redis commands related to [`Geospatial`](https://redis.io/docs/data-types/geospatial/) indices
11///
12/// # See Also
13/// [Redis Geospatial Commands](https://redis.io/commands/?group=geo)
14pub trait GeoCommands {
15    /// Adds the specified geospatial items (longitude, latitude, name) to the specified key.
16    ///
17    /// # Return
18    /// * When used without optional arguments, the number of elements added to the sorted set (excluding score updates).
19    /// * If the CH option is specified, the number of elements that were changed (added or updated).
20    ///
21    /// # See Also
22    /// [<https://redis.io/commands/geoadd/>](https://redis.io/commands/geoadd/)
23    #[must_use]
24    fn geoadd<K, M, I>(
25        &mut self,
26        key: K,
27        condition: GeoAddCondition,
28        change: bool,
29        items: I,
30    ) -> PreparedCommand<Self, usize>
31    where
32        Self: Sized,
33        K: Into<CommandArg>,
34        M: Into<CommandArg>,
35        I: ArgsOrCollection<(f64, f64, M)>,
36    {
37        prepare_command(
38            self,
39            cmd("GEOADD")
40                .arg(key)
41                .arg(condition)
42                .arg_if(change, "CH")
43                .arg(items),
44        )
45    }
46
47    /// Return the distance between two members in the geospatial index
48    /// represented by the sorted set.
49    ///
50    /// # Return
51    /// The distance in the specified unit, or None if one or both the elements are missing.
52    ///
53    /// # See Also
54    /// [<https://redis.io/commands/geodist/>](https://redis.io/commands/geodist/)
55    #[must_use]
56    fn geodist<K, M>(
57        &mut self,
58        key: K,
59        member1: M,
60        member2: M,
61        unit: GeoUnit,
62    ) -> PreparedCommand<Self, Option<f64>>
63    where
64        Self: Sized,
65        K: Into<CommandArg>,
66        M: Into<CommandArg>,
67    {
68        prepare_command(
69            self,
70            cmd("GEODIST").arg(key).arg(member1).arg(member2).arg(unit),
71        )
72    }
73
74    /// Return valid [Geohash](https://en.wikipedia.org/wiki/Geohash) strings representing the position of one or more elements
75    /// in a sorted set value representing a geospatial index (where elements were added using [geoadd](crate::GeoCommands::geoadd)).
76    ///
77    /// # Return
78    /// An array where each element is the Geohash corresponding to each member name passed as argument to the command.
79    ///
80    /// # See Also
81    /// [<https://redis.io/commands/geohash/>](https://redis.io/commands/geohash/)
82    #[must_use]
83    fn geohash<K, M, C>(&mut self, key: K, members: C) -> PreparedCommand<Self, Vec<String>>
84    where
85        Self: Sized,
86        K: Into<CommandArg>,
87        M: Into<CommandArg>,
88        C: SingleArgOrCollection<M>,
89    {
90        prepare_command(self, cmd("GEOHASH").arg(key).arg(members))
91    }
92
93    /// Return the positions (longitude,latitude) of all the specified members
94    ///  of the geospatial index represented by the sorted set at key.
95    ///
96    /// # Return
97    /// n array where each element is a two elements array representing longitude and latitude
98    /// (x,y) of each member name passed as argument to the command.
99    /// Non existing elements are reported as NULL elements of the array.
100    ///
101    /// # See Also
102    /// [<https://redis.io/commands/geopos/>](https://redis.io/commands/geopos/)
103    #[must_use]
104    fn geopos<K, M, C>(
105        &mut self,
106        key: K,
107        members: C,
108    ) -> PreparedCommand<Self, Vec<Option<(f64, f64)>>>
109    where
110        Self: Sized,
111        K: Into<CommandArg>,
112        M: Into<CommandArg>,
113        C: SingleArgOrCollection<M>,
114    {
115        prepare_command(self, cmd("GEOPOS").arg(key).arg(members))
116    }
117
118    /// Return the members of a sorted set populated with geospatial information using [geoadd](crate::GeoCommands::geoadd),
119    /// which are within the borders of the area specified by a given shape.
120    ///
121    /// # Return
122    /// An array of members + additional information depending
123    /// on which `with_xyz` options have been selected
124    ///
125    /// # See Also
126    /// [<https://redis.io/commands/geosearch/>](https://redis.io/commands/geosearch/)
127    #[must_use]
128    fn geosearch<K, M1, M2, A>(
129        &mut self,
130        key: K,
131        from: GeoSearchFrom<M1>,
132        by: GeoSearchBy,
133        options: GeoSearchOptions,
134    ) -> PreparedCommand<Self, A>
135    where
136        Self: Sized,
137        K: Into<CommandArg>,
138        M1: Into<CommandArg>,
139        M2: FromValue,
140        A: FromSingleValueArray<GeoSearchResult<M2>>,
141    {
142        prepare_command(
143            self,
144            cmd("GEOSEARCH").arg(key).arg(from).arg(by).arg(options),
145        )
146    }
147
148    /// This command is like [geosearch](crate::GeoCommands::geosearch), but stores the result in destination key.
149    ///
150    /// # Return
151    /// the number of elements in the resulting set.
152    ///
153    /// # See Also
154    /// [<https://redis.io/commands/geosearchstore/>](https://redis.io/commands/geosearchstore/)
155    #[must_use]
156    fn geosearchstore<D, S, M>(
157        &mut self,
158        destination: D,
159        source: S,
160        from: GeoSearchFrom<M>,
161        by: GeoSearchBy,
162        options: GeoSearchStoreOptions,
163    ) -> PreparedCommand<Self, usize>
164    where
165        Self: Sized,
166        D: Into<CommandArg>,
167        S: Into<CommandArg>,
168        M: Into<CommandArg>,
169    {
170        prepare_command(
171            self,
172            cmd("GEOSEARCHSTORE")
173                .arg(destination)
174                .arg(source)
175                .arg(from)
176                .arg(by)
177                .arg(options),
178        )
179    }
180}
181
182/// Condition for the [`geoadd`](crate::GeoCommands::geoadd) command
183pub enum GeoAddCondition {
184    /// No option
185    None,
186    /// Don't update already existing elements. Always add new elements.
187    NX,
188    /// Only update elements that already exist. Never add elements.
189    XX,
190}
191
192impl Default for GeoAddCondition {
193    fn default() -> Self {
194        GeoAddCondition::None
195    }
196}
197
198impl IntoArgs for GeoAddCondition {
199    fn into_args(self, args: CommandArgs) -> CommandArgs {
200        match self {
201            GeoAddCondition::None => args,
202            GeoAddCondition::NX => args.arg("NX"),
203            GeoAddCondition::XX => args.arg("XX"),
204        }
205    }
206}
207
208/// Distance Unit
209pub enum GeoUnit {
210    Meters,
211    Kilometers,
212    Miles,
213    Feet,
214}
215
216impl IntoArgs for GeoUnit {
217    fn into_args(self, args: CommandArgs) -> CommandArgs {
218        args.arg(match self {
219            GeoUnit::Meters => CommandArg::Str("m"),
220            GeoUnit::Kilometers => CommandArg::Str("km"),
221            GeoUnit::Miles => CommandArg::Str("mi"),
222            GeoUnit::Feet => CommandArg::Str("ft"),
223        })
224    }
225}
226
227/// The query's center point is provided by one of these mandatory options:
228pub enum GeoSearchFrom<M>
229where
230    M: Into<CommandArg>,
231{
232    /// Use the position of the given existing `member` in the sorted set.
233    FromMember { member: M },
234    /// Use the given `longitude` and `latitude` position.
235    FromLonLat { longitude: f64, latitude: f64 },
236}
237
238impl<M> IntoArgs for GeoSearchFrom<M>
239where
240    M: Into<CommandArg>,
241{
242    fn into_args(self, args: CommandArgs) -> CommandArgs {
243        match self {
244            GeoSearchFrom::FromMember { member } => args.arg("FROMMEMBER").arg(member),
245            GeoSearchFrom::FromLonLat {
246                longitude,
247                latitude,
248            } => args.arg("FROMLONLAT").arg(longitude).arg(latitude),
249        }
250    }
251}
252
253/// The query's shape is provided by one of these mandatory options:
254pub enum GeoSearchBy {
255    /// Search inside circular area according to given `radius` in the specified `unit`.
256    ByRadius { radius: f64, unit: GeoUnit },
257    /// Search inside an axis-aligned rectangle, determined by `height` and `width` in the specified `unit`.
258    ByBox {
259        width: f64,
260        height: f64,
261        unit: GeoUnit,
262    },
263}
264
265impl IntoArgs for GeoSearchBy {
266    fn into_args(self, args: CommandArgs) -> CommandArgs {
267        match self {
268            GeoSearchBy::ByRadius { radius, unit } => args.arg("BYRADIUS").arg(radius).arg(unit),
269            GeoSearchBy::ByBox {
270                width,
271                height,
272                unit,
273            } => args.arg("BYBOX").arg(width).arg(height).arg(unit),
274        }
275    }
276}
277
278/// Matching items are returned unsorted by default.
279/// To sort them, use one of the following two options:
280pub enum GeoSearchOrder {
281    /// Sort returned items from the nearest to the farthest, relative to the center point.
282    Asc,
283    /// Sort returned items from the farthest to the nearest, relative to the center point.
284    Desc,
285}
286
287impl IntoArgs for GeoSearchOrder {
288    fn into_args(self, args: CommandArgs) -> CommandArgs {
289        match self {
290            GeoSearchOrder::Asc => args.arg("ASC"),
291            GeoSearchOrder::Desc => args.arg("DESC"),
292        }
293    }
294}
295
296/// Options for the [`geosearch`](crate::GeoCommands::geosearch) command
297#[derive(Default)]
298pub struct GeoSearchOptions {
299    command_args: CommandArgs,
300}
301
302impl GeoSearchOptions {
303    #[must_use]
304    pub fn order(self, order: GeoSearchOrder) -> Self {
305        Self {
306            command_args: self.command_args.arg(order),
307        }
308    }
309
310    #[must_use]
311    pub fn count(self, count: usize, any: bool) -> Self {
312        Self {
313            command_args: self.command_args.arg("COUNT").arg(count).arg_if(any, "ANY"),
314        }
315    }
316
317    #[must_use]
318    pub fn with_coord(self) -> Self {
319        Self {
320            command_args: self.command_args.arg("WITHCOORD"),
321        }
322    }
323
324    #[must_use]
325    pub fn with_dist(self) -> Self {
326        Self {
327            command_args: self.command_args.arg("WITHDIST"),
328        }
329    }
330
331    #[must_use]
332    pub fn with_hash(self) -> Self {
333        Self {
334            command_args: self.command_args.arg("WITHHASH"),
335        }
336    }
337}
338
339impl IntoArgs for GeoSearchOptions {
340    fn into_args(self, args: CommandArgs) -> CommandArgs {
341        args.arg(self.command_args)
342    }
343}
344
345/// Result of the [`geosearch`](crate::GeoCommands::geosearch) command.
346#[derive(Debug)]
347pub struct GeoSearchResult<M>
348where
349    M: FromValue,
350{
351    /// The matched member.
352    pub member: M,
353
354    /// The distance of the matched member from the specified center.
355    pub distance: Option<f64>,
356
357    /// The geohash integer of the matched member
358    pub geo_hash: Option<i64>,
359
360    /// The coordinates (longitude, latitude) of the matched member
361    pub coordinates: Option<(f64, f64)>,
362}
363
364impl<M> FromValue for GeoSearchResult<M>
365where
366    M: FromValue,
367{
368    fn from_value(value: Value) -> Result<Self> {
369        match value {
370            Value::BulkString(_) => Ok(GeoSearchResult {
371                member: value.into()?,
372                distance: None,
373                geo_hash: None,
374                coordinates: None,
375            }),
376            Value::Array(_) => {
377                let values: Vec<Value> = value.into()?;
378                let mut it = values.into_iter();
379                let mut distance: Option<f64> = None;
380                let mut geo_hash: Option<i64> = None;
381                let mut coordinates: Option<(f64, f64)> = None;
382
383                let member = match it.next() {
384                    Some(value) => value.into()?,
385                    None => {
386                        return Err(Error::Client("Unexpected geo search result".to_owned()));
387                    }
388                };
389
390                for value in it {
391                    match value {
392                        Value::BulkString(Some(_)) => distance = Some(value.into()?),
393                        Value::Integer(h) => geo_hash = Some(h),
394                        Value::Array(_) => coordinates = Some(value.into()?),
395                        _ => return Err(Error::Client("Unexpected geo search result".to_owned())),
396                    }
397                }
398
399                Ok(GeoSearchResult {
400                    member,
401                    distance,
402                    geo_hash,
403                    coordinates,
404                })
405            }
406            _ => Err(Error::Client("Unexpected geo search result".to_owned())),
407        }
408    }
409}
410
411/// Options for the [`geosearchstore`](crate::GeoCommands::geosearchstore) command
412#[derive(Default)]
413pub struct GeoSearchStoreOptions {
414    command_args: CommandArgs,
415}
416
417impl GeoSearchStoreOptions {
418    #[must_use]
419    pub fn order(self, order: GeoSearchOrder) -> Self {
420        Self {
421            command_args: self.command_args.arg(order),
422        }
423    }
424
425    #[must_use]
426    pub fn count(self, count: usize, any: bool) -> Self {
427        Self {
428            command_args: self.command_args.arg("COUNT").arg(count).arg_if(any, "ANY"),
429        }
430    }
431
432    #[must_use]
433    pub fn store_dist(self, store_dist: bool) -> Self {
434        Self {
435            command_args: self.command_args.arg_if(store_dist, "STOREDIST"),
436        }
437    }
438}
439
440impl IntoArgs for GeoSearchStoreOptions {
441    fn into_args(self, args: CommandArgs) -> CommandArgs {
442        args.arg(self.command_args)
443    }
444}