rustis/commands/string_commands.rs
1use crate::{
2 client::{PreparedCommand, prepare_command},
3 commands::{RequestPolicy, ResponsePolicy},
4 resp::{FastPathCommandBuilder, cmd, serialize_flag},
5};
6use serde::de::DeserializeOwned;
7use serde::{
8 Deserialize, Deserializer, Serialize,
9 de::{self, SeqAccess, Visitor},
10};
11use std::fmt;
12
13/// A group of Redis commands related to [`Strings`](https://redis.io/docs/data-types/strings/)
14/// # See Also
15/// [Redis Generic Commands](https://redis.io/commands/?group=string)
16pub trait StringCommands<'a>: Sized {
17 /// If key already exists and is a string,
18 /// this command appends the value at the end of the string.
19 /// If key does not exist it is created and set as an empty string,
20 /// so APPEND will be similar to SET in this special case.
21 ///
22 /// # Return
23 /// the length of the string after the append operation.
24 ///
25 /// # See Also
26 /// [<https://redis.io/commands/append/>](https://redis.io/commands/append/)
27 #[must_use]
28 fn append(
29 self,
30 key: impl Serialize,
31 value: impl Serialize,
32 ) -> PreparedCommand<'a, Self, usize> {
33 prepare_command(self, cmd("APPEND").key(key).arg(value))
34 }
35
36 /// Decrements the number stored at key by one.
37 ///
38 /// If the key does not exist, it is set to 0 before performing the operation.
39 /// An error is returned if the key contains a value of the wrong type or contains
40 /// a string that can not be represented as integer.
41 /// This operation is limited to 64 bit signed integers.
42 ///
43 /// # Return
44 /// the value of key after the decrement
45 ///
46 /// # See Also
47 /// [<https://redis.io/commands/decr/>](https://redis.io/commands/decr/)
48 #[must_use]
49 fn decr(self, key: impl Serialize) -> PreparedCommand<'a, Self, i64> {
50 prepare_command(self, cmd("DECR").key(key))
51 }
52
53 /// Decrements the number stored at key by one.
54 ///
55 /// If the key does not exist, it is set to 0 before performing the operation.
56 /// An error is returned if the key contains a value of the wrong type or contains
57 /// a string that can not be represented as integer.
58 /// This operation is limited to 64 bit signed integers.
59 ///
60 /// # Return
61 /// the value of key after the decrement
62 ///
63 /// # See Also
64 /// [<https://redis.io/commands/decrby/>](https://redis.io/commands/decrby/)
65 #[must_use]
66 fn decrby(self, key: impl Serialize, decrement: i64) -> PreparedCommand<'a, Self, i64> {
67 prepare_command(self, cmd("DECRBY").key(key).arg(decrement))
68 }
69
70 /// Get the value of key.
71 ///
72 /// Get the value of key. If the key does not exist the special
73 /// value nil is returned. An error is returned if the value
74 /// stored at key is not a string, because GET only handles
75 /// string values.
76 ///
77 /// # Return
78 /// the value of key, or `nil` when key does not exist.
79 ///
80 /// # Warning
81 /// A missing key answers `nil`, which no scalar `R` can hold: `""` and `0` are
82 /// values a present key holds. Declare `R` as an [`Option`] to accept it. See
83 /// [Command results](crate::resp#command-results).
84 ///
85 /// # Example
86 /// ```
87 /// use rustis::{
88 /// client::{Client, ClientPreparedCommand},
89 /// commands::{FlushingMode, ServerCommands, StringCommands},
90 /// resp::{cmd},
91 /// Result
92 /// };
93 ///
94 /// #[tokio::main]
95 /// async fn main() -> Result<()> {
96 /// let client = Client::connect("127.0.0.1:6379").await?;
97 /// client.flushall(FlushingMode::Sync).await?;
98 ///
99 /// // an Option accepts the absence...
100 /// let value: Option<String> = client.get("key").await?;
101 /// assert_eq!(None, value);
102 ///
103 /// // ... while a bare type has no honest value for it
104 /// assert!(client.get::<String>("key").await.is_err());
105 /// assert!(client.get::<i64>("counter").await.is_err());
106 ///
107 /// client.set("key", "value").await?;
108 /// let value: String = client.get("key").await?;
109 /// assert_eq!("value", value);
110 ///
111 /// Ok(())
112 /// }
113 /// ```
114 ///
115 /// # See Also
116 /// [<https://redis.io/commands/get/>](https://redis.io/commands/get/)
117 #[must_use]
118 fn get<R: DeserializeOwned>(self, key: impl Serialize) -> PreparedCommand<'a, Self, R> {
119 prepare_command(self, FastPathCommandBuilder::get(key))
120 }
121
122 /// Get the value of key and delete the key.
123 ///
124 /// This command is similar to GET, except for the fact that it also deletes the key on success
125 /// (if and only if the key's value type is a string).
126 ///
127 /// # Return
128 /// the value of key, `nil` when key does not exist, or an error if the key's value type isn't a string.
129 ///
130 /// # See Also
131 /// [<https://redis.io/commands/getdel/>](https://redis.io/commands/getdel/)
132 #[must_use]
133 fn getdel<R: DeserializeOwned>(self, key: impl Serialize) -> PreparedCommand<'a, Self, R> {
134 prepare_command(self, cmd("GETDEL").key(key))
135 }
136
137 /// Returns the hash digest of a string value as a hexadecimal string.
138 ///
139 /// The digest is stable for a given value, so it can be captured and later
140 /// passed to [`delex`](StringCommands::delex) or `SET`'s `IFDEQ`/`IFDNE`
141 /// conditions for compare-and-delete / compare-and-set flows.
142 ///
143 /// # Return
144 /// the hexadecimal digest of the value, or `nil` when the key does not exist.
145 ///
146 /// # See Also
147 /// [<https://redis.io/commands/digest/>](https://redis.io/commands/digest/)
148 #[must_use]
149 fn digest<R: DeserializeOwned>(self, key: impl Serialize) -> PreparedCommand<'a, Self, R> {
150 prepare_command(self, cmd("DIGEST").key(key).readonly())
151 }
152
153 /// Conditionally removes `key` based on a value or digest comparison.
154 ///
155 /// With no condition the key is deleted unconditionally (like `DEL` on a
156 /// single key). With a [`DelexCondition`] the key is deleted only if its
157 /// current value (or its digest) satisfies the comparison.
158 ///
159 /// # Return
160 /// * `1` if the key was deleted.
161 /// * `0` if the key does not exist or the condition was not met.
162 ///
163 /// # See Also
164 /// [<https://redis.io/commands/delex/>](https://redis.io/commands/delex/)
165 #[must_use]
166 fn delex<'b>(
167 self,
168 key: impl Serialize,
169 condition: impl Into<Option<DelexCondition<'b>>>,
170 ) -> PreparedCommand<'a, Self, i64> {
171 prepare_command(self, cmd("DELEX").key(key).arg(condition.into()))
172 }
173
174 /// Get the value of key and optionally set its expiration. GETEX is similar to GET, but is a write command with additional options.
175 ///
176 /// Decrements the number stored at key by decrement.
177 /// If the key does not exist, it is set to 0 before performing the operation.
178 /// An error is returned if the key contains a value of the wrong type
179 /// or contains a string that can not be represented as integer.
180 /// This operation is limited to 64 bit signed integers.
181 ///
182 /// # Return
183 /// the value of key, or `nil` when key does not exist.
184 ///
185 /// # Example
186 /// ```
187 /// use rustis::{
188 /// client::{Client, ClientPreparedCommand},
189 /// commands::{FlushingMode, GetExOptions, GenericCommands, ServerCommands, StringCommands},
190 /// resp::cmd,
191 /// Result,
192 /// };
193 ///
194 /// #[tokio::main]
195 /// async fn main() -> Result<()> {
196 /// let client = Client::connect("127.0.0.1:6379").await?;
197 /// client.flushall(FlushingMode::Sync).await?;
198 ///
199 /// client.set("key", "value").await?;
200 /// let value: String = client.getex("key", GetExOptions::Ex(60)).await?;
201 /// assert_eq!("value", value);
202 ///
203 /// let ttl = client.ttl("key").await?;
204 /// assert!(59 <= ttl && ttl <= 60);
205 ///
206 /// Ok(())
207 /// }
208 /// ```
209 ///
210 /// # See Also
211 /// [<https://redis.io/commands/getex/>](https://redis.io/commands/getex/)
212 #[must_use]
213 fn getex<R: DeserializeOwned>(
214 self,
215 key: impl Serialize,
216 options: GetExOptions,
217 ) -> PreparedCommand<'a, Self, R> {
218 prepare_command(self, cmd("GETEX").key(key).arg(options))
219 }
220
221 /// Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive).
222 ///
223 /// Negative offsets can be used in order to provide an offset starting from the end of the string.
224 /// So -1 means the last character, -2 the penultimate and so forth.
225 ///
226 /// The function handles out of range requests by limiting the resulting range to the actual length of the string.
227 ///
228 /// # Example
229 /// ```
230 /// use rustis::{
231 /// client::Client,
232 /// commands::{FlushingMode, ServerCommands, StringCommands},
233 /// Result,
234 /// };
235 ///
236 /// #[tokio::main]
237 /// async fn main() -> Result<()> {
238 /// let client = Client::connect("127.0.0.1:6379").await?;
239 /// client.flushall(FlushingMode::Sync).await?;
240 /// client.set("mykey", "This is a string").await?;
241 ///
242 /// let value: String = client.getrange("mykey", 0, 3).await?;
243 /// assert_eq!("This", value);
244 /// let value: String = client.getrange("mykey", -3, -1).await?;
245 /// assert_eq!("ing", value);
246 /// let value: String = client.getrange("mykey", 0, -1).await?;
247 /// assert_eq!("This is a string", value);
248 /// let value: String = client.getrange("mykey", 10, 100).await?;
249 /// assert_eq!("string", value);
250 /// Ok(())
251 /// }
252 /// ```
253 ///
254 /// # See Also
255 /// [<https://redis.io/commands/getrange/>](https://redis.io/commands/getrange/)
256 #[must_use]
257 fn getrange<R: DeserializeOwned>(
258 self,
259 key: impl Serialize,
260 start: isize,
261 end: isize,
262 ) -> PreparedCommand<'a, Self, R> {
263 prepare_command(
264 self,
265 cmd("GETRANGE").key(key).arg(start).arg(end).readonly(),
266 )
267 }
268
269 /// Increments the number stored at key by one.
270 ///
271 /// If the key does not exist, it is set to 0 before performing the operation.
272 /// An error is returned if the key contains a value of the wrong type
273 /// or contains a string that can not be represented as integer.
274 /// This operation is limited to 64 bit signed integers.
275 ///
276 /// Note: this is a string operation because Redis does not have a dedicated integer type.
277 /// The string stored at the key is interpreted as a base-10 64 bit signed integer to execute the operation.
278 ///
279 /// Redis stores integers in their integer representation, so for string values that actually hold an integer,
280 /// there is no overhead for storing the string representation of the integer.
281 ///
282 /// # Return
283 /// the value of key after the increment
284 ///
285 /// # See Also
286 /// [<https://redis.io/commands/incr/>](https://redis.io/commands/incr/)
287 #[must_use]
288 fn incr(self, key: impl Serialize) -> PreparedCommand<'a, Self, i64> {
289 prepare_command(self, cmd("INCR").key(key))
290 }
291
292 /// Increments the number stored at key by increment.
293 ///
294 /// If the key does not exist, it is set to 0 before performing the operation.
295 /// An error is returned if the key contains a value of the wrong type
296 /// or contains a string that can not be represented as integer.
297 /// This operation is limited to 64 bit signed integers.
298 ///
299 /// See [incr](StringCommands::incr) for extra information on increment/decrement operations.
300 ///
301 /// # Return
302 /// the value of key after the increment
303 ///
304 /// # See Also
305 /// [<https://redis.io/commands/incrby/>](https://redis.io/commands/incrby/)
306 #[must_use]
307 fn incrby(self, key: impl Serialize, increment: i64) -> PreparedCommand<'a, Self, i64> {
308 prepare_command(self, cmd("INCRBY").key(key).arg(increment))
309 }
310
311 /// Increment the value at `key`, bounded, and set its expiration, atomically.
312 ///
313 /// The key is created at `0` when it does not exist. Without an increment
314 /// it is bumped by `1` in integer mode.
315 ///
316 /// Where [`incr`](StringCommands::incr) and [`expire`](crate::commands::GenericCommands::expire)
317 /// would need a Lua script to be atomic, this is one command — which is
318 /// what makes it a window-counter rate limiter:
319 /// [`ubound_int`](IncrExOptions::ubound_int) is the cap and
320 /// [`enx`](IncrExOptions::enx) starts the window only once.
321 ///
322 /// # Return
323 /// A pair of the value after the operation and the increment actually
324 /// applied. The applied increment is `0` when a bound stopped the operation,
325 /// and smaller than requested when [`saturate`](IncrExOptions::saturate)
326 /// capped it. Both are integers in integer mode, doubles under
327 /// [`by_float`](IncrExOptions::by_float) — so `(i64, i64)` or `(f64, f64)`.
328 ///
329 /// # See Also
330 /// [<https://redis.io/commands/increx/>](https://redis.io/commands/increx/)
331 #[must_use]
332 fn increx<R: DeserializeOwned>(
333 self,
334 key: impl Serialize,
335 options: IncrExOptions,
336 ) -> PreparedCommand<'a, Self, R> {
337 prepare_command(self, cmd("INCREX").key(key).arg(options))
338 }
339
340 ///Increment the string representing a floating point number stored at key by the specified increment.
341 /// By using a negative increment value, the result is that the value stored at the key is decremented (by the obvious properties of addition).
342 /// If the key does not exist, it is set to 0 before performing the operation.
343 /// An error is returned if one of the following conditions occur:
344 ///
345 /// - The key contains a value of the wrong type (not a string).
346 ///
347 /// - The current key content or the specified increment are not parsable as a double precision floating point number.
348 ///
349 /// If the command is successful the new incremented value is stored as the new value of the key (replacing the old one),
350 /// and returned to the caller as a string.
351 ///
352 /// Both the value already contained in the string key and the increment argument can be optionally provided in exponential notation,
353 /// however the value computed after the increment is stored consistently in the same format, that is,
354 /// an integer number followed (if needed) by a dot, and a variable number of digits representing the decimal part of the number.
355 /// Trailing zeroes are always removed.
356 ///
357 /// The precision of the output is fixed at 17 digits after the decimal point
358 /// regardless of the actual internal precision of the computation.
359 ///
360 /// # Return
361 /// the value of key after the increment
362 ///
363 /// # See Also
364 /// [<https://redis.io/commands/incrbyfloat/>](https://redis.io/commands/incrbyfloat/)
365 #[must_use]
366 fn incrbyfloat(self, key: impl Serialize, increment: f64) -> PreparedCommand<'a, Self, f64> {
367 prepare_command(self, cmd("INCRBYFLOAT").key(key).arg(increment))
368 }
369
370 /// The LCS command implements the longest common subsequence algorithm
371 ///
372 /// # Return
373 /// The string representing the longest common substring.
374 ///
375 /// # See Also
376 /// [<https://redis.io/commands/lcs/>](https://redis.io/commands/lcs/)
377 #[must_use]
378 fn lcs<R: DeserializeOwned>(
379 self,
380 key1: impl Serialize,
381 key2: impl Serialize,
382 ) -> PreparedCommand<'a, Self, R> {
383 prepare_command(self, cmd("LCS").key(key1).key(key2).readonly())
384 }
385
386 /// The LCS command implements the longest common subsequence algorithm
387 ///
388 /// # Return
389 /// The length of the longest common substring.
390 ///
391 /// # See Also
392 /// [<https://redis.io/commands/lcs/>](https://redis.io/commands/lcs/)
393 #[must_use]
394 fn lcs_len(
395 self,
396 key1: impl Serialize,
397 key2: impl Serialize,
398 ) -> PreparedCommand<'a, Self, usize> {
399 prepare_command(self, cmd("LCS").key(key1).key(key2).arg("LEN").readonly())
400 }
401
402 /// The LCS command implements the longest common subsequence algorithm
403 ///
404 /// # Return
405 /// An array with the LCS length and all the ranges in both the strings,
406 /// start and end offset for each string, where there are matches.
407 /// When `with_match_len` is given each match will also have the length of the match
408 ///
409 /// # See Also
410 /// [<https://redis.io/commands/lcs/>](https://redis.io/commands/lcs/)
411 #[must_use]
412 fn lcs_idx(
413 self,
414 key1: impl Serialize,
415 key2: impl Serialize,
416 min_match_len: Option<usize>,
417 with_match_len: bool,
418 ) -> PreparedCommand<'a, Self, LcsResult> {
419 prepare_command(
420 self,
421 cmd("LCS")
422 .key(key1)
423 .key(key2)
424 .arg("IDX")
425 .arg(min_match_len.map(|len| ("MINMATCHLEN", len)))
426 .arg_if(with_match_len, "WITHMATCHLEN")
427 .readonly(),
428 )
429 }
430
431 /// Returns the values of all specified keys.
432 ///
433 /// For every key that does not hold a string value or does not exist,
434 /// the special value nil is returned. Because of this, the operation never fails.
435 ///
436 /// # Return
437 /// Array reply: list of values at the specified keys.
438 ///
439 /// # See Also
440 /// [<https://redis.io/commands/mget/>](https://redis.io/commands/mget/)
441 #[must_use]
442 fn mget<R: DeserializeOwned>(self, keys: impl Serialize) -> PreparedCommand<'a, Self, R> {
443 prepare_command(
444 self,
445 cmd("MGET")
446 .keys(keys)
447 .cluster_info(RequestPolicy::MultiShard, None, 1)
448 .readonly(),
449 )
450 }
451
452 /// Sets the given keys to their respective values.
453 ///
454 /// # Return
455 /// always OK since MSET can't fail.
456 ///
457 /// # See Also
458 /// [<https://redis.io/commands/mset/>](https://redis.io/commands/mset/)
459 #[must_use]
460 fn mset(self, items: impl Serialize) -> PreparedCommand<'a, Self, ()> {
461 prepare_command(
462 self,
463 cmd("MSET").key_with_step(items, 2).cluster_info(
464 RequestPolicy::MultiShard,
465 ResponsePolicy::AllSucceeded,
466 2,
467 ),
468 )
469 }
470
471 /// Atomically sets multiple string keys with an optional shared expiration in a single operation.
472 ///
473 /// # Return
474 /// * `false` - if none of the keys were set
475 /// * `true` - if all of the keys were set.
476 ///
477 /// # Cluster
478 /// In cluster mode all keys must hash to the same slot, otherwise the
479 /// command fails client-side with a mismatched-slot error.
480 ///
481 /// # See Also
482 /// [<https://redis.io/commands/msetex/>](https://redis.io/commands/msetex/)
483 #[must_use]
484 fn msetex<'b>(
485 self,
486 items: impl Serialize,
487 condition: impl Into<Option<SetCondition<'b>>>,
488 expiration: impl Into<Option<SetExpiration>>,
489 ) -> PreparedCommand<'a, Self, bool> {
490 prepare_command(
491 self,
492 cmd("MSETEX")
493 .key_with_count_and_step(items, 2)
494 .arg(condition.into())
495 .arg(expiration.into())
496 .cluster_info(RequestPolicy::MultiShard, ResponsePolicy::AllSucceeded, 2),
497 )
498 }
499
500 /// Sets the given keys to their respective values.
501 /// MSETNX will not perform any operation at all even if just a single key already exists.
502 ///
503 /// Because of this semantic MSETNX can be used in order to set different keys representing
504 /// different fields of a unique logic object in a way that ensures that either
505 /// all the fields or none at all are set.
506 ///
507 /// MSETNX is atomic, so all given keys are set at once. It is not possible for
508 /// clients to see that some of the keys were updated while others are unchanged.
509 ///
510 /// # Return
511 /// specifically:
512 /// - 1 if the all the keys were set.
513 /// - 0 if no key was set (at least one key already existed).
514 ///
515 /// # Cluster
516 /// Unlike [`mset`](StringCommands::mset), MSETNX is routed to a single node:
517 /// its all-or-nothing atomicity cannot be preserved if the keys were split
518 /// across shards. All keys must therefore hash to the same slot in cluster
519 /// mode, otherwise the command fails client-side with a mismatched-slot error.
520 ///
521 /// # See Also
522 /// [<https://redis.io/commands/msetnx/>](https://redis.io/commands/msetnx/)
523 #[must_use]
524 fn msetnx(self, items: impl Serialize) -> PreparedCommand<'a, Self, bool> {
525 prepare_command(
526 self,
527 cmd("MSETNX")
528 .key_with_step(items, 2)
529 .cluster_info(None, None, 2),
530 )
531 }
532
533 ///Set key to hold the string value.
534 ///
535 /// If key already holds a value, it is overwritten, regardless of its type.
536 /// Any previous time to live associated with the key is discarded on successful SET operation.
537 ///
538 /// # See Also
539 /// [<https://redis.io/commands/set/>](https://redis.io/commands/set/)
540 #[must_use]
541 fn set(self, key: impl Serialize, value: impl Serialize) -> PreparedCommand<'a, Self, ()> {
542 prepare_command(self, FastPathCommandBuilder::set(key, value))
543 }
544
545 /// Set key to hold the string value.
546 ///
547 /// # Return
548 /// * `true` if SET was executed correctly.
549 /// * `false` if the SET operation was not performed because the user
550 /// specified the NX or XX option but the condition was not met.
551 ///
552 /// # See Also
553 /// [<https://redis.io/commands/set/>](https://redis.io/commands/set/)
554 #[must_use]
555 fn set_with_options<'b>(
556 self,
557 key: impl Serialize,
558 value: impl Serialize,
559 condition: impl Into<Option<SetCondition<'b>>>,
560 expiration: impl Into<Option<SetExpiration>>,
561 ) -> PreparedCommand<'a, Self, bool> {
562 prepare_command(
563 self,
564 cmd("SET")
565 .key(key)
566 .arg(value)
567 .arg(condition.into())
568 .arg(expiration.into()),
569 )
570 }
571
572 /// Set key to hold the string value wit GET option enforced
573 ///
574 /// # See Also
575 /// [<https://redis.io/commands/set/>](https://redis.io/commands/set/)
576 #[must_use]
577 fn set_get_with_options<'b, R: DeserializeOwned>(
578 self,
579 key: impl Serialize,
580 value: impl Serialize,
581 condition: impl Into<Option<SetCondition<'b>>>,
582 expiration: impl Into<Option<SetExpiration>>,
583 ) -> PreparedCommand<'a, Self, R> {
584 prepare_command(
585 self,
586 cmd("SET")
587 .key(key)
588 .arg(value)
589 .arg(condition.into())
590 .arg("GET")
591 .arg(expiration.into()),
592 )
593 }
594
595 /// Overwrites part of the string stored at key,
596 /// starting at the specified offset,
597 /// for the entire length of value.
598 ///
599 /// # Return
600 /// the length of the string after it was modified by the command.
601 ///
602 /// # See Also
603 /// [<https://redis.io/commands/setrange/>](https://redis.io/commands/setrange/)
604 #[must_use]
605 fn setrange(
606 self,
607 key: impl Serialize,
608 offset: usize,
609 value: impl Serialize,
610 ) -> PreparedCommand<'a, Self, usize> {
611 prepare_command(self, cmd("SETRANGE").key(key).arg(offset).arg(value))
612 }
613
614 /// Returns the length of the string value stored at key.
615 ///
616 /// An error is returned when key holds a non-string value.
617 ///
618 /// # Return
619 /// the length of the string at key, or 0 when key does not exist.
620 ///
621 /// # See Also
622 /// [<https://redis.io/commands/strlen/>](https://redis.io/commands/strlen/)
623 #[must_use]
624 fn strlen(self, key: impl Serialize) -> PreparedCommand<'a, Self, usize> {
625 prepare_command(self, cmd("STRLEN").key(key).readonly())
626 }
627}
628
629/// Options for the [`getex`](StringCommands::getex) and the [`hgetex`](crate::commands::HashCommands::hgetex) commands
630#[derive(Serialize)]
631#[serde(rename_all = "UPPERCASE")]
632#[non_exhaustive]
633pub enum GetExOptions {
634 /// Set the specified expire time, in seconds.
635 Ex(u64),
636 /// Set the specified expire time, in milliseconds.
637 Px(u64),
638 /// Set the specified Unix time at which the key will expire, in seconds.
639 Exat(u64),
640 /// Set the specified Unix time at which the key will expire, in milliseconds.
641 Pxat(u64),
642 /// Remove the time to live associated with the key.
643 Persist,
644}
645
646/// Part of the result for the [`lcs`](StringCommands::lcs) command
647#[derive(Debug, PartialEq, Eq)]
648#[non_exhaustive]
649pub struct LcsMatch(pub (usize, usize), pub (usize, usize), pub Option<usize>);
650
651impl<'de> Deserialize<'de> for LcsMatch {
652 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
653 where
654 D: Deserializer<'de>,
655 {
656 struct LcsMatchVisitor;
657
658 impl<'de> Visitor<'de> for LcsMatchVisitor {
659 type Value = LcsMatch;
660
661 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
662 formatter.write_str("LcsMatch")
663 }
664
665 fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
666 where
667 A: SeqAccess<'de>,
668 {
669 let Some(first): Option<(usize, usize)> = seq.next_element()? else {
670 return Err(de::Error::invalid_length(0, &"fewer elements in tuple"));
671 };
672
673 let Some(second): Option<(usize, usize)> = seq.next_element()? else {
674 return Err(de::Error::invalid_length(1, &"fewer elements in tuple"));
675 };
676
677 let match_len: Option<usize> = seq.next_element()?;
678
679 Ok(LcsMatch(first, second, match_len))
680 }
681 }
682
683 deserializer.deserialize_seq(LcsMatchVisitor)
684 }
685}
686
687/// Result for the [`lcs`](StringCommands::lcs) command
688#[derive(Debug, Deserialize)]
689#[non_exhaustive]
690pub struct LcsResult {
691 pub matches: Vec<LcsMatch>,
692 pub len: usize,
693}
694
695/// Options for the [`increx`](StringCommands::increx) command
696#[derive(Default, Serialize)]
697#[serde(rename_all = "UPPERCASE")]
698pub struct IncrExOptions {
699 #[serde(skip_serializing_if = "Option::is_none")]
700 byint: Option<i64>,
701 #[serde(skip_serializing_if = "Option::is_none")]
702 byfloat: Option<f64>,
703 #[serde(skip_serializing_if = "Option::is_none")]
704 lbound: Option<IncrExBound>,
705 #[serde(skip_serializing_if = "Option::is_none")]
706 ubound: Option<IncrExBound>,
707 #[serde(
708 skip_serializing_if = "std::ops::Not::not",
709 serialize_with = "serialize_flag"
710 )]
711 saturate: bool,
712 #[serde(rename = "", skip_serializing_if = "Option::is_none")]
713 expiration: Option<GetExOptions>,
714 #[serde(
715 skip_serializing_if = "std::ops::Not::not",
716 serialize_with = "serialize_flag"
717 )]
718 enx: bool,
719}
720
721/// A bound of the [`increx`](StringCommands::increx) command, in whichever mode
722/// the increment put it in.
723#[derive(Serialize)]
724#[serde(untagged)]
725enum IncrExBound {
726 Int(i64),
727 Float(f64),
728}
729
730impl IncrExOptions {
731 /// Increment by a 64-bit signed integer. Negative decrements.
732 ///
733 /// The stored value must be integer-typed: a stored `"1.5"` cannot be read
734 /// back as an integer, exactly as with [`incrby`](StringCommands::incrby).
735 #[must_use]
736 pub fn by_int(increment: i64) -> Self {
737 Self {
738 byint: Some(increment),
739 ..Default::default()
740 }
741 }
742
743 /// Increment by a floating-point value.
744 ///
745 /// The stored value may be an integer or a float, since integers promote to
746 /// floats losslessly. A result of NaN or infinity is rejected.
747 #[must_use]
748 pub fn by_float(increment: f64) -> Self {
749 Self {
750 byfloat: Some(increment),
751 ..Default::default()
752 }
753 }
754
755 /// Lower bound, in integer mode.
756 #[must_use]
757 pub fn lbound_int(mut self, lower_bound: i64) -> Self {
758 self.lbound = Some(IncrExBound::Int(lower_bound));
759 self
760 }
761
762 /// Lower bound, in [`by_float`](IncrExOptions::by_float) mode.
763 #[must_use]
764 pub fn lbound_float(mut self, lower_bound: f64) -> Self {
765 self.lbound = Some(IncrExBound::Float(lower_bound));
766 self
767 }
768
769 /// Upper bound, in integer mode.
770 #[must_use]
771 pub fn ubound_int(mut self, upper_bound: i64) -> Self {
772 self.ubound = Some(IncrExBound::Int(upper_bound));
773 self
774 }
775
776 /// Upper bound, in [`by_float`](IncrExOptions::by_float) mode.
777 #[must_use]
778 pub fn ubound_float(mut self, upper_bound: f64) -> Self {
779 self.ubound = Some(IncrExBound::Float(upper_bound));
780 self
781 }
782
783 /// Cap an out-of-bounds result at the bound instead of skipping the
784 /// operation. Without it, a bound violation leaves the key and its TTL
785 /// untouched and reports a zero increment.
786 #[must_use]
787 pub fn saturate(mut self) -> Self {
788 self.saturate = true;
789 self
790 }
791
792 /// Set the expiration, in seconds.
793 #[must_use]
794 pub fn ex(mut self, seconds: u64) -> Self {
795 self.expiration = Some(GetExOptions::Ex(seconds));
796 self
797 }
798
799 /// Set the expiration, in milliseconds.
800 #[must_use]
801 pub fn px(mut self, milliseconds: u64) -> Self {
802 self.expiration = Some(GetExOptions::Px(milliseconds));
803 self
804 }
805
806 /// Set the Unix time at which the key expires, in seconds.
807 #[must_use]
808 pub fn exat(mut self, unix_time_seconds: u64) -> Self {
809 self.expiration = Some(GetExOptions::Exat(unix_time_seconds));
810 self
811 }
812
813 /// Set the Unix time at which the key expires, in milliseconds.
814 #[must_use]
815 pub fn pxat(mut self, unix_time_milliseconds: u64) -> Self {
816 self.expiration = Some(GetExOptions::Pxat(unix_time_milliseconds));
817 self
818 }
819
820 /// Remove the expiration of the key.
821 #[must_use]
822 pub fn persist(mut self) -> Self {
823 self.expiration = Some(GetExOptions::Persist);
824 self
825 }
826
827 /// Set the expiration only when the key has none. An existing TTL is kept
828 /// as it is, while the increment still applies. Requires one of
829 /// [`ex`](IncrExOptions::ex), [`px`](IncrExOptions::px),
830 /// [`exat`](IncrExOptions::exat) or [`pxat`](IncrExOptions::pxat), and is
831 /// incompatible with [`persist`](IncrExOptions::persist).
832 #[must_use]
833 pub fn enx(mut self) -> Self {
834 self.enx = true;
835 self
836 }
837}
838
839/// Expiration option for the [`set_with_options`](StringCommands::set_with_options) and [`hsetex`](crate::commands::HashCommands::hsetex) commands
840#[derive(Serialize)]
841#[serde(rename_all = "UPPERCASE")]
842#[non_exhaustive]
843pub enum SetExpiration {
844 /// Set the specified expire time, in seconds.
845 Ex(u64),
846 /// Set the specified expire time, in milliseconds.
847 Px(u64),
848 /// Set the specified Unix time at which the key will expire, in seconds.
849 Exat(u64),
850 /// Set the specified Unix time at which the key will expire, in milliseconds.
851 Pxat(u64),
852 /// Retain the time to live associated with the key.
853 KeepTtl,
854}
855
856/// Condition option for the [`delex`](StringCommands::delex) command.
857///
858/// Mirrors the `IFEQ`/`IFNE`/`IFDEQ`/`IFDNE` value/digest comparisons of
859/// [`SetCondition`], without the `NX`/`XX` existence conditions, which `DELEX`
860/// does not accept.
861#[derive(Serialize)]
862#[serde(rename_all = "UPPERCASE")]
863#[non_exhaustive]
864pub enum DelexCondition<'a> {
865 /// Delete only if the current value is equal to the provided value.
866 IFEQ(&'a str),
867 /// Delete only if the current value is not equal to the provided value.
868 IFNE(&'a str),
869 /// Delete only if the digest of the current value is equal to the provided digest.
870 IFDEQ(&'a str),
871 /// Delete only if the digest of the current value is not equal to the provided digest.
872 IFDNE(&'a str),
873}
874
875/// Condition option for the [`set_with_options`](StringCommands::set_with_options) command
876#[derive(Serialize)]
877#[serde(rename_all = "UPPERCASE")]
878#[non_exhaustive]
879pub enum SetCondition<'a> {
880 /// Only set the key if it does not already exist.
881 NX,
882 /// Only set the key if it already exist.
883 XX,
884 /// Set the key’s value and expiration only if its current value is equal to the provided value.
885 /// If the key doesn’t exist, it won’t be created.
886 IFEQ(&'a str),
887 /// Set the key’s value and expiration only if its current value is not equal to the provided value.
888 /// If the key doesn’t exist, it will be created.
889 IFNE(&'a str),
890 /// Set the key’s value and expiration only if the hash digest of its current value is equal to the provided digest.
891 /// If the key doesn’t exist, it won’t be created.
892 IFDEQ(&'a str),
893 /// Set the key’s value and expiration only if the hash digest of its current value is not equal to the provided digest.
894 /// If the key doesn’t exist, it will be created.
895 IFDNE(&'a str),
896}