Skip to main content

Keyspace

Struct Keyspace 

Source
pub struct Keyspace { /* private fields */ }
Expand description

One database: every key, whatever type it holds.

Implementations§

Source§

impl Keyspace

Source

pub fn arset<'v>( &mut self, key: &[u8], index: u64, values: impl Iterator<Item = &'v [u8]> + Clone, ) -> Result<u64>

ARSET key index value [value ...], which writes at consecutive indices.

Answers how many of the positions were empty before, which is not the same as how many values were written: ARSET k 0 a b twice answers 2 and then 0.

§Errors

Code::Invalid when the last index the write would reach does not exist, so that a write which would run off the top of the index space fails before any of it lands rather than half way through.

Source

pub fn armset<'v>( &mut self, key: &[u8], pairs: impl Iterator<Item = (u64, &'v [u8])> + Clone, ) -> Result<u64>

ARMSET key index value [index value ...], which writes scattered pairs.

Answers how many of the positions were empty before, the same as Keyspace::arset. The pairs arrive already parsed, because the wire layer has to read every index before it writes any of them: a bad index in the last pair fails the whole command and leaves the earlier pairs unwritten.

Source

pub fn arget(&mut self, key: &[u8], index: u64) -> Result<Option<Element<'_>>>

ARGET key index. A hole and a missing key are the same answer.

Source

pub fn arget_into<F>( &mut self, key: &[u8], indices: impl Iterator<Item = u64>, f: F, ) -> Result<()>
where F: FnMut(Option<Element<'_>>),

ARMGET key index [index ...], straight into the reply.

f is called once per index in the order they were asked for, with the element or None for a hole, and it is called while the element is still in the array so that nothing is copied on the way (Y18).

Source

pub fn argetrange<F>( &mut self, key: &[u8], start: u64, end: u64, f: F, ) -> Result<u64>
where F: FnMut(Option<Element<'_>>),

ARGETRANGE key start end, every position in the range and not every element.

f is called once per position, holes included, low to high or high to low depending on which way round the two ends came in. The count is answered first so that the caller can write the array header before the first element.

§Errors

Code::Invalid when the range covers more than GETRANGE_MAX positions.

Source

pub fn arlen(&mut self, key: &[u8]) -> Result<u64>

ARLEN key, the highest populated index plus one.

Zero for a key that is not there, and note that this is not the number of elements. Keyspace::arcount is that.

Source

pub fn arcount(&mut self, key: &[u8]) -> Result<u64>

ARCOUNT key, how many indices hold something.

Source

pub fn ardel( &mut self, key: &[u8], indices: impl Iterator<Item = u64>, ) -> Result<u64>

ARDEL key index [index ...]. Answers how many held something.

The key goes when the last element does.

Source

pub fn ardelrange( &mut self, key: &[u8], ranges: impl Iterator<Item = (u64, u64)>, ) -> Result<u64>

ARDELRANGE key start end [start end ...]. Answers how many went.

Each pair may come in either order. The cost is in the elements the ranges touch and not in how wide they are, so clearing the whole index space of a key holding three elements is three deletes.

Source

pub fn arinsert<'v>( &mut self, key: &[u8], values: impl Iterator<Item = &'v [u8]> + Clone, ) -> Result<u64>

ARINSERT key value [value ...], which appends at the cursor.

Answers the index the last value landed on. The cursor starts at zero and a plain ARSET never moves it, so an array somebody has written by index and then appended to will have the first append land on top of index zero. That is Redis’s behaviour and it is the reason ARSEEK exists.

§Errors

Code::Invalid when the batch would run off the top of the index space, checked before any of it is written.

Source

pub fn arring<'v>( &mut self, key: &[u8], size: u64, values: impl Iterator<Item = &'v [u8]> + Clone, ) -> Result<u64>

ARRING key size value [value ...], a ring buffer over the indices.

Answers the index the last value landed on. size has to be at least one, which the caller checks because Redis reports a bad size before it has even looked at the key.

Source

pub fn arnext(&mut self, key: &[u8]) -> Result<Option<u64>>

ARNEXT key, where the next append would go.

Zero for a key that is not there and zero for a cursor nothing has moved yet, which are the same answer because they mean the same thing. None is the null a client sees when the cursor has run out of index space and there is no honest answer to give.

Source

pub fn arseek(&mut self, key: &[u8], index: u64) -> Result<bool>

ARSEEK key index, which points the cursor.

Answers whether there was a key to point. A missing key answers false and is not created, because an array with nothing in it is not a key here and an error would be worse: the caller asked to move a cursor, and the honest answer is that there was no cursor to move.

index is the one place in the array commands where 2^64 - 1 is a legal argument. It leaves the cursor in the terminal state, which is what the rewritten command has to say to reproduce that state on load.

Source

pub fn arlastitems<F>( &mut self, key: &[u8], count: u64, newest_first: bool, f: F, ) -> Result<u64>
where F: FnMut(Option<Element<'_>>),

ARLASTITEMS key count [REV], the newest positions from the cursor.

f is called once per position, oldest first unless newest_first, and a hole inside the window is a None rather than something skipped. The count is answered so the caller can close its array header.

Source

pub fn arscan<F>( &mut self, key: &[u8], start: u64, end: u64, limit: u64, f: F, ) -> Result<u64>
where F: FnMut(u64, Element<'_>),

ARSCAN key start end [LIMIT count], the elements and not the positions.

f is called with the index and the element for everything populated in the range, low to high or high to low depending on which way round the ends came in, and at most limit times. Answers how many that was.

Unlike Keyspace::argetrange this has no ceiling on the range, and it does not need one: holes cost nothing, so ARSCAN k 0 18446744073709551614 against a key holding three elements is three visits and not eighteen quintillion.

Source

pub fn argrep<F>( &mut self, key: &[u8], start: Bound, end: Bound, limit: u64, grep: &mut Grep<'_>, f: F, ) -> Result<u64>
where F: FnMut(u64, Element<'_>),

ARGREP key start end predicate ... [AND | OR] [LIMIT n] [WITHVALUES] [NOCASE].

Keyspace::arscan’s walk with a test in front of the callback, so it costs the elements in the range and not its width. f is called with the index and the element for everything that answers grep, at most limit times, and the count is what came back.

The two bounds arrive as Bound rather than as numbers because + means the end of the array as it is now, which is not known until the key has been found.

Source

pub fn arop( &mut self, key: &[u8], start: u64, end: u64, op: Op, want: &[u8], ) -> Result<Aggregate>

AROP key start end OP [value], one number out of a whole range.

The walk is Keyspace::arscan’s, so it costs the elements in the range and not its width, and every operation here is order independent so the direction the ends came in does not matter.

Source

pub fn arinfo(&mut self, key: &[u8], full: bool) -> Result<Info>

ARINFO key [FULL], the shape of the array.

§Errors

Code::Invalid carrying no such key for a key that is not there, which is the one array command that treats a missing key as a mistake rather than as an empty array. It is reporting on a structure, and there is no structure.

Source§

impl Keyspace

Source

pub fn getbit(&mut self, key: &[u8], offset: u64) -> Result<bool>

GETBIT key offset.

A missing key, and any offset past the end of a key that is there, read as zero. Nothing is created and nothing is re-encoded.

Source

pub fn setbit(&mut self, key: &[u8], offset: u64, bit: bool) -> Result<bool>

SETBIT key offset value, answering the bit that was there before.

The value grows to hold the offset, padded with zero bytes, and keeps whatever deadline it had. A key that was not there is created, even when the bit being written is zero.

Source

pub fn bitcount( &mut self, key: &[u8], range: Option<(i64, i64, Unit)>, ) -> Result<u64>

BITCOUNT key [start end [BYTE | BIT]].

A missing key, an empty string and a range that ends before it starts all answer zero. The two indexes may be negative, counting from the end, and both are clamped rather than refused.

Source

pub fn bitpos( &mut self, key: &[u8], bit: bool, start: Option<i64>, end: Option<i64>, unit: Unit, ) -> Result<i64>

BITPOS key bit [start [end [BYTE | BIT]]].

Answers minus one when there is no such bit, with the one exception Redis carved out: looking for a zero with no end index given, over a range that is all ones, answers the first bit past the end of the string. The idea is that a string is followed by an infinity of zeros unless the caller said where to stop. Giving an explicit end turns that back into minus one, and so does asking about a range that is empty once it has been clamped.

Source

pub fn bitop<'k, I>(&mut self, op: Op, dest: &[u8], srcs: I) -> Result<usize>
where I: Iterator<Item = &'k [u8]> + Clone,

BITOP op dest src [src ...], answering the length of the result.

A result with no bytes in it deletes the destination, and any other result creates it whatever it holds, so a BITOP AND over sources that share nothing leaves a destination full of zero bytes rather than no destination at all. Sources that are shorter than the longest read as zeros past their end, and a source that is not there reads as empty.

§Panics

If srcs is empty, or holds more than one key for Op::Not. Both are refused with a message on the wire before this is called.

Source

pub fn bitfield(&mut self, key: &[u8], ops: &[Sub]) -> Result<Vec<Option<i64>>>

BITFIELD key [subcommand ...], answering one reply per subcommand.

A None in the answers is the nil an OVERFLOW FAIL subcommand gives when its value would not fit; that one does not write and the ones around it still do. The subcommands are expected to have been checked already, which is what makes it safe for this to be the point of no return.

The value grows once, before anything runs, to hold the last bit any writing subcommand touches. That happens even if every one of those writes then fails its overflow check, which is Redis’s behaviour and falls out of it growing the string before it looks at the values.

Source

pub fn bitfield_with<T>( &mut self, key: &[u8], grow: Option<usize>, run: impl FnOnce(&mut [u8]) -> T, ) -> Result<T>

BITFIELD, with the subcommands run against the value in place.

This is the form the wire uses. It hands over the bytes and lets the caller walk its own arguments a second time, calling apply on each, which is what lets a BITFIELD with two hundred subcommands write two hundred replies without a list of them existing anywhere.

grow is how many bytes the value has to reach, which is the last byte any writing subcommand touches, and None for a call that only reads. The growing happens once and before anything runs, even if every one of those writes then fails its overflow check, because that is what Redis does: it makes the string long enough while it is looking up the key and only then starts on the values. A call that only reads stores nothing, which is what keeps BITFIELD k GET u8 0 from turning an embstr into a raw.

Source§

impl Keyspace

Source

pub fn expire_cycle(&mut self, budget: usize) -> Cycle

Sweep dead keys until the budget runs out or the sweep stops paying.

budget is how many keys this is allowed to look at, and it is a ceiling and not a target: a database with nothing dead in it returns after one round having spent a fraction of it, and a database with nothing volatile in it returns having spent none of it at all.

Safe to call on any database at any time. It takes only keys that are past their deadline, which are keys no client can see, so nothing observable changes except the memory going back and INFO stats counting the reclaim. Redis counts its cycle into expired_keys alongside lazy expiry and so does this.

Source§

impl Keyspace

Source

pub fn geoadd<'m, I>( &mut self, key: &[u8], points: I, opts: ZAdd, ) -> Result<usize>
where I: Iterator<Item = (f64, f64, &'m [u8])> + Clone,

GEOADD key [NX|XX] [CH] longitude latitude member [...].

Answers what the ZADD underneath answers, which is how many members were added, or how many were added or moved with CH.

Every coordinate is checked before anything is stored, so a call with one bad pair in the middle of it leaves the key exactly as it was. Redis does the same, and it matters more here than it looks: GEOADD is how a whole dataset gets loaded, and a partial load with no way to tell where it stopped is worse than a refusal.

Source

pub fn geopos<'m, F>( &mut self, key: &[u8], members: impl Iterator<Item = &'m [u8]>, f: F, ) -> Result<()>
where F: FnMut(Option<(f64, f64)>),

GEOPOS key member [member ...].

Hands each position over as it is found rather than collecting them, because the reply is as long as the argument list and the wire already knows that number. A member that is not there, or whose score is not a position anything wrote, gets nothing.

Source

pub fn geohash<'m, F>( &mut self, key: &[u8], members: impl Iterator<Item = &'m [u8]>, f: F, ) -> Result<()>
where F: FnMut(Option<&[u8]>),

GEOHASH key member [member ...], the same shape one step further on.

Source

pub fn geodist(&mut self, key: &[u8], a: &[u8], b: &[u8]) -> Result<Option<f64>>

GEODIST key member1 member2 [unit], in metres whatever the unit was.

The caller divides, because the unit is a wire concern and the number this hands back is the one every other distance in the crate is in.

Nothing at all if either member is missing, and nothing for a key that is not there, which are the same nil on the wire.

Source

pub fn geocentre( &mut self, key: &[u8], member: &[u8], ) -> Result<Option<(f64, f64)>>

Where a member is, for a search that takes its centre from one.

FROMMEMBER, and the two GEORADIUSBYMEMBER forms. A key that is not there answers nothing, because those commands have their own reply for that and it is not the error a missing member gets.

Source

pub fn geosearch( &mut self, key: &[u8], shape: &Shape, limit: Limit, ) -> Result<usize>

Run a search and leave what it found on the keyspace.

Answers how many hits there are, which is what the wire needs before it can write the array header. The hits themselves come from Keyspace::geohits, which borrows rather than copies.

The nine boxes are walked in Redis’s order and a box that a previous one already covered is skipped, which is not an optimisation: at a radius of a few thousand kilometres the step is small enough that neighbouring boxes come out identical, and walking one twice would report every member in it twice.

Source

pub fn geohits(&self) -> &Scratch

What the last Keyspace::geosearch found.

Source

pub fn geosearchstore( &mut self, dest: &[u8], src: &[u8], shape: &Shape, limit: Limit, dist: bool, ) -> Result<usize>

GEOSEARCHSTORE, and the STORE and STOREDIST forms of GEORADIUS.

Answers how many members went into the destination. A search that found nothing deletes the destination rather than leaving an empty sorted set or leaving the old contents, which is the rule every store form follows.

dist is STOREDIST, which stores the distance in the shape’s unit as the score instead of the geohash. The two are not interchangeable: a key written with STOREDIST is a sorted set of distances and is not a geo key any more, and GEOPOS on it answers positions somewhere off the coast of Africa rather than an error.

Source§

impl Keyspace

Source

pub fn hset<'a>( &mut self, key: &[u8], pairs: impl Iterator<Item = (&'a [u8], &'a [u8])> + Clone, ) -> Result<usize>

HSET key field value [field value ...]. Answers how many were new.

The pairs arrive as an iterator for the reason SADD’s members do: the wire layer has them as positions in the connection’s read buffer, and collecting them into a slice first would be an allocation per command on a shard thread.

Redis’s parser rejects an odd number of arguments before this is reached. The embedded API has no parser in front of it, so an empty iterator does not create the key, the same guard SADD has.

Source

pub fn hreplace<'a>( &mut self, key: &[u8], pairs: impl Iterator<Item = (&'a [u8], &'a [u8])> + Clone, ) -> Result<()>

Replace whatever is under key with a hash of exactly these pairs.

The write side of HIMPORT SET, and the one hash write that is a whole value rather than an edit. A field the pairs do not name is gone afterwards and so is any deadline the old value carried, because the old value is gone rather than having been written over, and that is what a real server does with the same command.

The lengths and the type are both checked before anything is deleted, so a call that cannot go through leaves the key exactly as it was. A caller that has its own complaints to make about the arguments still has to ask the type first, since WRONGTYPE comes before any of them.

Source

pub fn hsetnx(&mut self, key: &[u8], field: &[u8], value: &[u8]) -> Result<bool>

HSETNX key field value. Answers whether it was written.

Unlike SETNX this is per field and not per key, so it writes into a hash that already exists as long as that one field is missing.

Source

pub fn hget<R>( &mut self, key: &[u8], field: &[u8], f: impl FnOnce(Option<Text<'_>>) -> R, ) -> Result<R>

HGET key field, as a borrow rather than a copy.

f is handed None for a missing key and for a missing field alike, because both are a nil reply and the caller has no reason to tell them apart. HEXISTS is the command that does.

Source

pub fn hmget<'a, F>( &mut self, key: &[u8], fields: impl Iterator<Item = &'a [u8]>, f: F, ) -> Result<()>
where F: FnMut(Option<Text<'_>>),

HMGET key field [field ...], one call of f per field asked for.

Every field gets a call, including the ones that are not there, because the reply is positional: a client sending three fields gets three entries back and matches them up by position. A missing key answers all nils rather than an empty array for the same reason.

Source

pub fn hdel<'a>( &mut self, key: &[u8], fields: impl Iterator<Item = &'a [u8]>, ) -> Result<usize>

HDEL key field [field ...]. Answers how many were there.

The key goes when the last field does.

Source

pub fn hexpire<'a, F>( &mut self, key: &[u8], at: u64, cond: Cond, fields: impl Iterator<Item = &'a [u8]>, f: F, ) -> Result<()>
where F: FnMut(Applied),

HEXPIREAT and the three commands that turn into it.

at is an absolute unix millisecond, which is what HEXPIRE, HPEXPIRE and HEXPIREAT all become before they get here, and one call of f happens per field asked for because the reply is positional.

The deadline is checked against ttl::MAX_AT before any field is touched, because Redis rejects the whole command rather than failing field by field, and a command that names ten fields either sets all ten or errors.

A key that is not there answers Applied::Missing for every field, which is the -2 Redis replies, because a missing key and an empty hash are the same thing. The key goes when the last field does, which happens when the deadline given has already passed.

Source

pub fn httl<'a, F>( &mut self, key: &[u8], fields: impl Iterator<Item = &'a [u8]>, f: F, ) -> Result<()>
where F: FnMut(Ask),

HTTL and its relatives, one call of f per field asked for.

What comes back is when the deadline falls due. Turning that into what is left, and into seconds where the command asks for seconds, is the reply layer’s job, because Ask::remaining_ms is where that arithmetic lives and it needs the moment being asked at.

Source

pub fn hpersist<'a, F>( &mut self, key: &[u8], fields: impl Iterator<Item = &'a [u8]>, f: F, ) -> Result<()>
where F: FnMut(Ask),

HPERSIST key FIELDS numfields field [field ...].

Ask::At means the deadline that was there has been taken off, which the reply layer reports as 1.

Source

pub fn hgetdel<'a, F>( &mut self, key: &[u8], fields: impl Iterator<Item = &'a [u8]>, f: F, ) -> Result<()>
where F: FnMut(Option<Text<'_>>),

HGETDEL key FIELDS numfields field [field ...].

The value goes out and the field goes away, in that order, which is the whole command: a client that wants both without a race would otherwise send HGET and HDEL and hope. One call of f per field asked for, including the ones that were not there, because the reply is positional the way HMGET’s is.

The key goes when the last field does.

Source

pub fn hgetex<'a, F>( &mut self, key: &[u8], expire: Expire, fields: impl Iterator<Item = &'a [u8]>, f: F, ) -> Result<()>
where F: FnMut(Option<Text<'_>>),

HGETEX key [EX s | PX ms | EXAT ts | PXAT ts | PERSIST] FIELDS ....

The read and the deadline change in one command, which is what makes it worth having: a plain HSET clears the deadline on the field it writes, so there is no way to touch a field’s expiry and see its value with the commands that were there before.

strings::Expire::Keep is a plain HGETEX with no option, and it is the default here rather than Clear, which is the one place this disagrees with SET. Clear is PERSIST and At is the other four.

A deadline that has already gone deletes the field, and the value still goes out, because the read happened first. The key goes with the last field.

Source

pub fn hsetex<'a>( &mut self, key: &[u8], exists: Exists, expire: Expire, pairs: impl Iterator<Item = (&'a [u8], &'a [u8])> + Clone, ) -> Result<bool>

HSETEX key [FNX | FXX] [EX .. | KEEPTTL] FIELDS n field value [..].

Answers whether it wrote, which is all of it or none of it. FNX wants every field named to be missing and FXX wants every one of them to be there, so a list where one field disagrees writes nothing at all. That is stricter than HSETNX, which is per field, and it is what makes this usable as a compare and set over a group of fields.

strings::Expire::Clear is a plain HSETEX and is the default, since a write clears the deadline on the field it writes anyway. Keep is KEEPTTL and has to put the deadline back afterwards for that reason.

A deadline that has already gone still answers written, unlike the HEXPIRE family which has a separate code for it. The fields are stored and then removed, and if that empties the hash the key goes too, so HSETEX key EXAT 1 on a key that did not exist leaves it not existing.

Source

pub fn hlen(&mut self, key: &[u8]) -> Result<usize>

HLEN key.

Source

pub fn hexists(&mut self, key: &[u8], field: &[u8]) -> Result<bool>

HEXISTS key field.

Source

pub fn hstrlen(&mut self, key: &[u8], field: &[u8]) -> Result<usize>

HSTRLEN key field, without writing the value anywhere.

A value held as an integer answers with how many digits it would take, counted rather than formatted, which is what Text::byte_len is for.

Source

pub fn hgetall<F>(&mut self, key: &[u8], f: F) -> Result<bool>
where F: FnMut(Text<'_>, Text<'_>),

HGETALL key, HKEYS key and HVALS key, which differ only in what the caller does with each pair.

One method for the three because the walk is the whole of the work and three copies of it would be three chances for one of them to drift. The caller taking a pair and using half of it costs nothing, since neither half is formatted until something asks for it.

Ok(false) means the key was not there, which is an empty reply for all three and never a nil.

Source

pub fn with_hash<R>( &mut self, key: &[u8], f: impl FnOnce(Option<&Hash>) -> R, ) -> Result<R>

Hand the hash under key to f, or hand it None if there is no key.

The same thing Keyspace::with_set is for, and here it matters more. HGETALL on RESP3 answers a map, whose header carries the pair count, so the wire layer needs the length and then the pairs. Going back through Keyspace::hlen for the header would be a second key lookup on the command that is most likely to be in a loop.

A callback rather than a returned &Hash because the reap happens under &mut self and a borrow carved out of that cannot outlive the call.

Source

pub fn hscan<F>( &mut self, key: &[u8], cursor: Cursor, count: usize, f: F, ) -> Result<Cursor>
where F: FnMut(Text<'_>, Text<'_>),

HSCAN key cursor [COUNT n], with the cursor to resume from.

NOVALUES is the caller’s business: it gets both halves and drops the one it does not want, exactly as HKEYS does.

Source

pub fn hincrby(&mut self, key: &[u8], field: &[u8], by: i64) -> Result<i64>

HINCRBY key field increment. Answers the sum.

A field that is not there counts as zero and is created, which is what makes this the counter primitive it is used as. A field holding something that is not an integer is an error and leaves the hash exactly as it was, and so is a sum that leaves the range: Redis checks the overflow before the write rather than wrapping and storing the wrap.

Source

pub fn hincrbyfloat(&mut self, key: &[u8], field: &[u8], by: f64) -> Result<f64>

HINCRBYFLOAT key field increment. Answers the sum.

The same rules with the float versions of the errors. An infinite increment is not refused up front, for the reason INCRBYFLOAT gives: Redis parses it, does the addition and then reports that the result is not finite, so HINCRBYFLOAT k f inf says the increment would produce infinity and not that the increment is not a float.

Source

pub fn hrandfield<R>( &mut self, key: &[u8], f: impl FnOnce(Option<(Text<'_>, Text<'_>)>) -> R, ) -> Result<R>

HRANDFIELD key, as a borrow.

f is handed None when the key is not there, which is a nil and not an empty reply.

Source

pub fn hrandfield_n<F>(&mut self, key: &[u8], count: i64, f: F) -> Result<()>
where F: FnMut(Text<'_>, Text<'_>),

HRANDFIELD key count, which is two commands wearing one name.

A negative count is the with repeats form: exactly that many fields, drawn one at a time, and the same field can come back more than once. It is the only form that can answer more fields than the hash holds.

A positive count is distinct fields, at most as many as the hash holds. SRANDMEMBER splits its distinct form two ways because a set can be millions of members and drawing three of them should not walk all of them. A hash draws differently: Redis’s own HRANDFIELD with a positive count builds the whole answer either way, so this walks the fields once and takes each with the probability that leaves the right number at the end. That is Knuth’s selection sampling, it needs no memory at all, and it is O(len) rather than O(count).

A shuffle is deliberately not done. Redis does not promise an order here and the walk order is not the insertion order once a field has been removed, so shuffling would buy a guarantee nobody is owed at the price of an allocation.

Source§

impl Keyspace

Source

pub fn pfadd<'e, I>(&mut self, key: &[u8], eles: I) -> Result<bool>
where I: Iterator<Item = &'e [u8]>,

PFADD key [element ...], answering whether anything changed.

Creating the key counts as a change, so PFADD fresh with no elements answers 1 and PFADD fresh again answers 0. An element that lands in a register already holding at least as large a count is not a change either, and in that case the value is not rewritten at all.

Source

pub fn pfcount<'k, I>(&mut self, keys: I) -> Result<u64>
where I: Iterator<Item = &'k [u8]> + Clone,

PFCOUNT key [key ...].

One key answers out of the header cache when it is good and fills it in when it is not. Several keys are merged into one set of registers first, and that answer is never cached, because there is nowhere to put it: the union of two sketches is not a key.

A key that is not there counts as an empty sketch rather than an error, so PFCOUNT missing is 0 and a missing key among several is skipped.

Source

pub fn pfmerge<'k, I>(&mut self, dest: &'k [u8], srcs: I) -> Result<()>
where I: Iterator<Item = &'k [u8]> + Clone,

PFMERGE dest [source ...].

The destination is one of the sources, so a merge never loses what was already there, and PFMERGE dest with no sources at all is a no-op that still answers OK. A destination that is not there is created.

The result stays sparse when every input was sparse and it fits, which is what a real server does: merging two hundred element sketches leaves a two hundred and seventy nine byte one, not a dense one.

Source

pub fn pfgetreg(&mut self, key: &[u8], regs: &mut [u8; 16384]) -> Result<()>

PFDEBUG GETREG key, which converts the sketch to dense first.

The conversion is Redis’s and it is not a side effect worth hiding: the registers of a sparse sketch cannot be handed out one at a time without walking the opcodes for each, so the debugging command that wants all 16384 of them converts once and leaves it converted.

Source

pub fn pftodense(&mut self, key: &[u8]) -> Result<bool>

PFDEBUG TODENSE key, answering whether it had to convert anything.

Source

pub fn pfencoding(&mut self, key: &[u8]) -> Result<Encoding>

PFDEBUG ENCODING key, which is sparse or dense.

Source

pub fn pfdecode<T>( &mut self, key: &[u8], run: impl FnOnce(&[u8]) -> T, ) -> Result<T>

PFDEBUG DECODE key, handing the opcodes to run as one line of text.

The text goes into the scratch buffer and is lent out rather than returned, the way Keyspace::bitfield_with lends its value out, so that a debugging command does not allocate on a shard thread.

Source§

impl Keyspace

Source

pub fn export(&mut self, key: &[u8]) -> Option<Record>

Take a copy of everything under key, deadline included.

None for a key that is not there, and for one whose deadline has gone, which is reaped on the way through the same as every other read.

This clones the body, so exporting a set of a million members costs a set of a million members. Keyspace::rename exists so that the one case which does not need a copy does not pay for one.

Source

pub fn take(&mut self, key: &[u8]) -> Option<Record>

Lift everything under key out and leave the key gone.

The same answer Keyspace::export gives, without the clone. A body in the slab is already a value standing on its own, so a caller that is about to delete the source can have that body itself rather than a copy of it, and taking a set of a million members costs a slot number.

This is what MOVE wants and what COPY cannot have. The difference is that a move leaves nothing behind, so there is never a moment where two records point at one slot.

The record is removed here rather than by the caller, because the body is out of the slab by then and a record still pointing at a slot that has been freed is the one state this file exists to prevent. A del on top of this would free the body a second time and underflow the count of keys that hold one.

Source

pub fn import(&mut self, key: &[u8], rec: Record)

Put rec under key, over whatever was there.

The caller has already decided that writing over the destination is allowed, which is why this answers nothing. Whatever was under key is freed first, body and all, so this cannot leak a slab slot.

Source

pub fn dump(&mut self, key: &[u8]) -> Option<Vec<u8>>

DUMP key, which is a value on its own with a checksum on the end.

None for a key that is not there, and for a key holding something with no RDB shape, which today is only the sparse array and which no command on the wire can create. Both answer the null bulk that DUMP gives for a missing key, so a client cannot tell them apart and there is nothing here for it to tell apart yet.

The deadline is deliberately left behind. Redis’s DUMP does the same and the reason is that a payload has no idea how long it will be in flight, so carrying an absolute deadline would arrive already expired and carrying a relative one would quietly extend it. RESTORE takes the ttl as an argument instead, which puts the decision on whoever knows.

Source

pub fn restore( &mut self, key: &[u8], payload: &[u8], expire_at: Option<u64>, replace: bool, ) -> Result<Moved, Bad>

RESTORE key ttl payload, with replace for the REPLACE option.

Moved::Taken for a key that is already there without REPLACE, which is checked before the payload is looked at because that is the order Redis checks in and a busy key should not depend on whether the bytes behind it happened to be good.

The clone in export is not paid here. The payload is parsed straight into a body and that body goes into the slab, so restoring a set of a million members builds one set.

§Errors

rdb::Bad::Footer when the version is from the future or the checksum does not match, and rdb::Bad::Format when the bytes were intact and still did not describe anything this server can hold. The wire layer has a different message for each and clients depend on the difference.

Source

pub fn rename(&mut self, src: &[u8], dst: &[u8], only_if_new: bool) -> Moved

RENAME src dst, and RENAMENX when only_if_new.

The body never moves. A set or a hash is a slot number in a record, and a slot number under a different key is the same set, so this writes the source’s record bytes under the destination and deletes the source record without freeing anything. That is why renaming a large collection is the same call as renaming a short string.

The deadline travels with the source and the destination’s own deadline goes with the value it belonged to, which falls out of moving the whole record rather than being a rule applied on top of it.

Renaming a key onto itself is allowed and does nothing, which is Redis’s answer. RENAMENX on the same key answers Moved::Taken instead, because the destination does exist, and a key is not new because it is the one you already had.

Source

pub fn copy(&mut self, src: &[u8], dst: &[u8], replace: bool) -> Moved

COPY src dst, within one database.

Across two databases the caller runs Keyspace::export on one and Keyspace::import on the other, because a database cannot see its neighbours from in here.

A destination whose deadline has gone counts as free, so this answers Moved::Ok without replace on a key that has technically expired and not yet been collected. That is Redis’s behaviour and it is the only one that is consistent with EXISTS saying zero for the same key. A key copied onto itself answers Moved::Ok and does nothing, and without replace it answers Moved::Taken, which is the same pair of answers Keyspace::rename gives. The wire never asks: Redis refuses COPY k k with an error and so does the dispatch. This is for the embedded caller, who can ask, and for whom freeing the body and then writing a record that points at it would be the worst of the answers available.

Source

pub fn touch<'k>(&mut self, keys: impl Iterator<Item = &'k [u8]>) -> usize

TOUCH key [key ...]. Answers how many of them are there.

The same answer EXISTS gives, including a key named twice counting twice. On a real server the difference is that this moves the key up the eviction order, and there is no eviction here yet, so for now the two are the same walk and the day eviction lands this is where the bump goes.

Source§

impl Keyspace

Source

pub fn new() -> Keyspace

An empty database on the system clock.

Source

pub fn with_clock(clock: Clock) -> Keyspace

An empty database on a clock of the caller’s choosing.

Source

pub const fn seed(&mut self, seed: u64)

Pin what SPOP and SRANDMEMBER draw.

A database seeds itself from the clock and a counter, which is what a server wants and what a test cannot assert against. Every test in this crate that cares which member comes back calls this first, the same way every expiry test drives a fixed clock, and for the same reason: the one input that makes a result unrepeatable is better handed in than reached for.

It is public because reproducing a bug report is the same problem. A seed printed in a crash report is worth having somewhere to put.

Source

pub const fn random(&mut self) -> u64

The next number from that same stream.

For a command whose value lives in a Foreign body and so cannot reach the draw any other way. VRANDMEMBER is the one, and it should be as repeatable under Keyspace::seed as SRANDMEMBER is, which it would not be if it carried a generator of its own.

Source

pub const fn policy(&self) -> Policy

What this database would evict, which is CONFIG GET maxmemory-policy.

Source

pub fn set_policy(&mut self, policy: Policy)

Change what this database would evict.

Every key already stored keeps whatever is in its access field, which is why Redis warns on OBJECT FREQ that switching at runtime takes time to adjust. Under the new policy those bits mean something else, and the only honest thing to do about it is to let them be corrected by use. A key nobody has touched since the switch reads as freshly used rather than as stale, which is the safe direction: the other one evicts the working set on the first pass after an operator changes a setting.

The candidate pool does go, because a score only means anything against another score under the same rule and every number in there was worked out under the old one.

Source

pub const fn lfu(&self) -> Lfu

The two numbers the LFU counter moves by, which are two CONFIG values.

Source

pub const fn set_lfu(&mut self, lfu: Lfu)

Change how fast the LFU counter climbs and decays.

Source

pub fn idle_secs(&mut self, key: &[u8]) -> Option<u64>

Seconds since key was last used, which is OBJECT IDLETIME.

None for a key that is not there. A key that has never been stamped reads as zero rather than as ancient, which is what Access::is_unset is for.

This does not count as a use. Redis looks the key up with its no touch flag here, and it has to: a diagnostic that resets the number it reports would answer zero every time it was asked.

Source

pub fn freq(&mut self, key: &[u8]) -> Option<u8>

How often key is used, which is OBJECT FREQ.

The eight bit counter, decayed to now, on the same terms as Keyspace::idle_secs: None for a key that is not there, and asking is not using.

The caller is the one that has to check the policy first. This reports what the bits say, and under a policy that is not LFU they say something else, which is a refusal on the wire rather than a number.

Source

pub const fn limits(&self) -> &Limits

Where a set changes representation, which is three CONFIG values.

Source

pub const fn set_limits(&mut self, limits: Limits)

Change where a set changes representation.

Moving these does not rewrite the sets that already exist, which is what Redis does too: CONFIG SET set-max-listpack-entries 0 leaves every listpack alone and only decides what the next SADD builds.

Source

pub const fn hash_limits(&self) -> &Limits

Where a hash changes representation, which is two CONFIG values.

Source

pub const fn set_hash_limits(&mut self, limits: Limits)

Change where a hash changes representation.

Same rule as the set: moving these leaves every hash that already exists exactly as it is, and only decides what the next HSET builds.

Source

pub const fn list_limits(&self) -> &Limits

Where a list changes representation, which is one CONFIG value.

Source

pub const fn set_list_limits(&mut self, limits: Limits)

Change where a list changes representation.

Same rule again: this decides what the next LPUSH builds and leaves every list that already exists alone. list-max-listpack-size is one number rather than two, and list::Limits::of is what turns it into the pair this holds.

Source

pub const fn stream_limits(&self) -> &Limits

Where a stream starts a new node, which is two CONFIG values.

Source

pub const fn set_stream_limits(&mut self, limits: Limits)

Change where a stream starts a new node.

Same rule as the other four: this decides what the next XADD builds and leaves every node that is already full exactly as it is, which is also what Redis does, since a node is never resized after it is written.

Source

pub const fn zset_limits(&self) -> &Limits

Where a sorted set changes representation, which is two CONFIG values.

Source

pub const fn set_zset_limits(&mut self, limits: Limits)

Change where a sorted set changes representation.

Same rule as the other three: this decides what the next ZADD builds and leaves every sorted set that already exists exactly as it is.

Source

pub const fn clock(&self) -> &Clock

The clock expiry compares against.

Source

pub const fn map(&self) -> &RawMap

The map underneath, for statistics and for compaction.

Source

pub fn len(&self) -> usize

How many keys are stored, including any that are dead and not yet noticed. This is Redis’s DBSIZE, which counts the same way.

Source

pub fn is_empty(&self) -> bool

Whether anything is stored.

Source

pub fn kind_of(&mut self, key: &[u8]) -> Option<Kind>

What key holds, or None if there is nothing under it.

This is TYPE. A key past its deadline is reaped first, so a dead key answers None and not the type it used to be.

One lookup, because the tag and the deadline are both in the record the lookup returned. Reading the kind out before the reap rather than after is what keeps it to one.

It does not go through the lookup that stamps, and so it leaves the eviction clock where it was, which is right and is worth saying rather than leaving to be inferred from the shape of the code. TYPE is one of the commands Redis looks up with its no touch flag, along with the OBJECT subcommands underneath this, which read through reap for the same reason.

Source

pub fn set_encoding(&mut self, key: &[u8]) -> Option<Encoding>

How a set is represented, or None if key is not a set.

This follows the slot and asks the body rather than reading the record, because the record only holds a number. Putting a copy of the representation in the record’s two spare encoding bits would mean rewriting the record every time a set was promoted, for the sake of a command nobody calls in a loop, and would leave two places able to disagree about the same fact. A demoted set is brought back to answer, which is the one thing this costs that the others do not. The word is a property of the body and the body is on the device, so there is nothing else to read it off. It is one device read for a command nobody sends in a loop, and the alternative is a copy of the word in the record’s two spare encoding bits with two places then able to disagree about it.

Source

pub fn hash_encoding(&mut self, key: &[u8]) -> Option<Encoding>

How a hash is represented, or None if key is not a hash.

The same shape as Keyspace::set_encoding and for the same reason: the record holds a slot number and the body is the thing that knows which of the two it currently is. A demoted hash is brought back to answer, which is the same trade the set makes.

Source

pub fn list_encoding(&mut self, key: &[u8]) -> Option<Encoding>

How a list is represented, or None if key is not a list.

The same shape as Keyspace::set_encoding, and the same argument for asking the body rather than reading a copy out of the record. A demoted list is brought back to answer, as the set and the hash are.

Source

pub fn zset_encoding(&mut self, key: &[u8]) -> Option<Encoding>

How a sorted set is represented, or None if key is not one.

Source

pub fn put_foreign(&mut self, key: &[u8], body: Box<dyn Foreign>) -> u32

Put a foreign body under key, over whatever was there.

The keyspace takes the box and frees it when the key goes, which is the whole reason a graph lives in here rather than in a table beside it. See crate::foreign for why that mattered enough to spend the last tag pattern on.

Overwriting is allowed and is what a caller that has just decided to replace a key wants. A caller that did not mean to overwrite asks Keyspace::kind_of first, which is what the commands above do so they can answer WRONGTYPE rather than quietly throw a hash away.

Source

pub fn foreign(&mut self, key: &[u8]) -> Result<Option<&dyn Foreign>>

The foreign body under key.

None for a key that is not there or has expired, an error for a key holding something this crate does understand, which is the same three way answer every other type’s entry point gives.

The caller turns the &dyn Foreign back into its own type with downcast_ref, and a None from that is a key holding a different foreign body, which is also WRONGTYPE and is the caller’s to report because only it knows which one it wanted.

Source

pub fn foreign_mut(&mut self, key: &[u8]) -> Result<Option<&mut dyn Foreign>>

The same, with a mutable borrow.

Source

pub fn reap_foreign(&mut self, key: &[u8])

Drop key if the foreign body under it has gone empty.

Redis deletes a key when its collection empties, and a client can see the difference, so every command that removes something calls this afterwards rather than each of them deciding what empty means.

Source

pub fn type_name(&mut self, key: &[u8]) -> Option<&'static str>

What TYPE should say about key.

Kind::name for everything this crate knows, and the body’s own word for a foreign one, because a client asking about a graph is told graph and not foreign. None for a key that is not there, which is the none Redis answers with.

Source

pub fn encoding_name(&mut self, key: &[u8]) -> Option<&'static str>

OBJECT ENCODING key, as the word Redis puts on the wire.

One place that knows every type’s answer, so that adding the hash means adding an arm here and not finding the four callers that each worked it out for themselves.

Source

pub fn set_expiry(&mut self, key: &[u8], at: Option<u64>) -> bool

Put a deadline on key, or take one off. Answers whether it was there.

Any type. A deadline lives in the record and changes its length, so this writes the record again rather than patching it, and for a set that is five bytes or thirteen and never the members. The body is left exactly where it is, which is why this writes through the map instead of taking the free the body path an overwrite takes.

This is the raw write. Keyspace::expire and Keyspace::persist are what EXPIRE and its family call, and they come through here once they have worked out whether the deadline is allowed to move.

Source

pub fn deadline_of(&mut self, key: &[u8]) -> Ask

The key’s deadline, as the three way answer TTL and PTTL are built on.

Ask::Missing for a key that is not there, Ask::NoDeadline for one that is and has no deadline, and the absolute millisecond otherwise. A key past its deadline is reaped on the way through, so it answers Missing and not the moment that has gone.

Asking when a key dies is not using it, so this does not stamp the eviction clock. Redis reads the key with its no touch flag here for the same reason, and it matters more than it looks: a client polling TTL on a key would otherwise keep that key at the top of the working set for as long as it kept asking whether it was about to go.

Source

pub fn expire(&mut self, key: &[u8], at: u64, cond: Cond) -> Applied

Move key’s deadline to at, if cond lets it.

This is EXPIRE, PEXPIRE, EXPIREAT and PEXPIREAT, which differ only in the unit and the origin of the number. All four turn it into one absolute millisecond before they get here, so the condition rules live in one place and the four commands cannot drift apart.

A deadline that has already passed deletes the key rather than being stored, and the answer says so. EXPIRE cannot report the difference because it replies 1 either way, but the caller is not always EXPIRE, and a delete is a different thing from a deadline.

The condition is checked before the past check, which is the order Redis uses and is the one that matters: EXPIRE key 0 XX on a key with no deadline answers 0 and leaves the key alone, rather than deleting it.

Source

pub fn persist(&mut self, key: &[u8]) -> bool

Take key’s deadline off. Answers whether there was one to take.

This is PERSIST, and the reply is the same 0 for a key that is not there and a key that was never going to expire, which is Redis’s answer and not a shortcut here.

Source

pub fn attach(&mut self, blocks: Store)

Give this database somewhere to keep values that are not in memory.

Until this is called nothing is ever demoted, no record is ever cold and every command takes exactly the path it took before, which is why a database that never opens a file pays nothing for this existing.

The store is whatever the caller wants it to be. In a server it is the shard’s log. In a test it is a vector. This crate does not depend on either and does not want to: Blocks is an append that hands back an address and a read that takes one, and that is the whole of the contract between the memory engine and whatever is under it.

Source

pub const fn tier(&self) -> Option<&Tier<Store>>

The tier, if one was attached, for its counters.

Source

pub const fn tier_mut(&mut self) -> Option<&mut Tier<Store>>

The tier, mutably, for a caller driving a sweep.

Source

pub fn demote(&mut self, key: &[u8]) -> Result<bool>

Move one key’s value out to the file.

Answers whether it went. A key that is not there, one that is int encoded, one holding a type that does not move yet and one whose value is shorter than the pointer that would replace it all answer false, and so does every key on a database with nothing attached.

This is the single key form, which is what a test and a DEBUG subcommand want. What a server under memory pressure wants is Keyspace::relieve.

One entry point for both kinds of value, because a caller naming a key should not have to know whether its body is in the record or in a slab. The two paths underneath are different all the way down: a string goes through the tier, and a collection goes through demote_body beside this, which frees a slab slot and grows a record.

§Errors

Whatever the store says when it will not take the bytes.

Source

pub fn store_bytes(&self) -> Option<u64>

How many bytes the attached store is holding, or None if there is not one.

None and Some(0) are different answers and the difference is the one maxstore turns on. A database with nothing attached cannot migrate and has to evict, and a database with an empty file attached can migrate the moment it needs to.

Source

pub fn relieve(&mut self, shed: usize) -> Result<Relief>

Move values out to the file until at least shed bytes of memory have gone.

Answers with a Relief, which is how many keys went and how much memory that gave back. This is maxmemory under the inversion 14 describes: the limit that used to throw keys away now moves them, and what a client stored is still there afterwards.

Bytes to shed rather than a target to reach, because the caller with the limit is a server holding sixteen databases against one number and what it knows is how far over it is, not what any one database should be holding. usize::MAX means everything that can go, which is what a sweep wants.

Victims are chosen by the same policy maxmemory-policy names, so a database set to allkeys-lru demotes the coldest keys and one set to volatile-ttl demotes the ones closest to expiring. See Tier::relieve for what a sweep does and where it stops.

§Why noeviction still moves values

Because it says do not lose data, and moving a value to the file does not lose any. The policy is two things at once in Redis, whether to give memory back at all and which keys to take it from, and only the second of those means anything here. So the default policy picks victims the way allkeys-lru does and the promise it was set for is kept: every key a client stored is still readable afterwards.

The alternative is a server that was given a file, was given a limit, and answers writes with OOM until somebody finds the third setting that turns the file on. That is a trap and not a default.

§Two passes, because the two kinds of value are counted in different

places

Strings go first, through Tier::relieve, which measures itself against the arena because a string is its record and moving one makes the arena smaller. Collections cannot be swept that way. A collection’s body is in a slab the arena knows nothing about, and moving one makes the arena bigger, because a twenty byte pointer replaces an eight byte slot number. A loop that watched the arena would demote every collection in the database, watch its number go up the whole time, and never stop.

So the second pass is here rather than in the tier, and it measures itself against Keyspace::memory_bytes, which is the arena and the slabs together. That is the only number that goes down when a body moves, and it is the number the server’s limit is compared against anyway.

§Errors

Whatever the store says when it will not take the bytes.

Source

pub fn clear(&mut self)

Throw every key away. This is FLUSHDB on one database.

The expiry counter is not reset, because Redis does not reset it either: expired_keys in INFO stats counts what this process has expired since it started, and emptying a database is not expiring anything. The count of keys that carry a deadline is a different number and it does go to zero, because it is a fact about what is in the database right now and there is nothing in it.

Source

pub const fn expired_keys(&self) -> u64

Keys reclaimed by running into them after their deadline.

Redis calls this expired_keys in INFO stats and counts both lazy and active expiry into it, and so does this. Keyspace::expire_cycle is the active half and it counts into the same number, which is what makes this the total a dashboard can compare against a write rate rather than the share of it that happened to be reclaimed by a read.

Source

pub const fn evicted_keys(&self) -> u64

Keys thrown away to make room.

Redis calls this evicted_keys in INFO stats. It stays at zero under noeviction, which is the whole point of that policy, and a monitoring dashboard that sees it move on a server configured that way is looking at a bug rather than at load.

Source

pub fn expires(&self) -> usize

How many live keys carry a deadline.

This is what INFO keyspace reports as expires=, and it is the live count rather than a running total: a key that gets a TTL and then has it taken away with PERSIST is in it and then is not.

The map keeps it, because the map keeps the second index these keys are in. Nothing in this file counts it, which is deliberate: a count kept alongside the thing it counts is a count that eventually disagrees with it, and the one place that can be wrong should be the one place that owns the entries.

Source

pub const fn samples(&self) -> usize

How many keys a round of eviction sampling looks at.

Source

pub const fn set_samples(&mut self, samples: usize)

Set how many keys a round of eviction sampling looks at.

Zero is not refused here, because the caller doing the refusing is CONFIG SET and it has a message to produce. A zero that reaches here samples one bucket and takes the best of it, because the loop runs its body before it checks, which is a better answer than dividing by nothing.

Source

pub fn evict_one(&mut self) -> bool

Throw away one key, chosen by the policy. Answers whether one went.

This is one step and not a loop on purpose. The caller is the thing that knows how much room it needs back, and a loop in here would either take too much or have to be told the same number twice. It also means the caller can put a bound on how long it spends evicting before it answers the client, which matters because the client is waiting on a write that this is making room for.

It answers false without doing anything under noeviction, and also when a volatile policy is set on a database where nothing has a deadline. Those are the same answer to the caller and they mean the same thing: this server cannot give memory back and is about to have to refuse a write.

Source

pub fn memory_bytes(&self) -> usize

Bytes held by the index, the arena and every body hanging off them.

Asks every collection, so this is O(the number of collections) and is for the places that want the number exactly and are asked for it rarely: INFO memory, MEMORY USAGE and the tests. Keyspace::settled_memory_bytes is the one a memory limit uses.

Source

pub fn settled_memory_bytes(&mut self) -> usize

The same number, asked only of the collections that could have moved.

See Slab::track_bytes for how that is known. With tracking on this costs what the batch touched instead of what the database holds, which is what lets a server with a maxmemory ask once a batch. With tracking off it is Keyspace::memory_bytes and the two cannot disagree, because they are the same sum over the same values either way.

Source

pub fn track_memory(&mut self, on: bool)

Start or stop keeping the running total in every slab.

One call for all seven, because a limit is a property of the server and not of a type, and a database tracking its sets but not its hashes would answer a number that is neither of the two things it could mean.

Source

pub fn compact_step(&mut self) -> Option<usize>

Give back one segment’s worth of space if one has gone mostly dead.

Overwriting a key does not reuse its bytes, it writes the new record at the bump pointer and counts the old one as dead, so a workload that sets the same keys over and over holds far more than it is storing until something compacts. This is that something, and it does at most one segment per call so that the loop can afford to ask every turn.

Source

pub fn compact_hard(&mut self) -> Option<usize>

The same, for a store that is over a memory limit and has to give pages back rather than wait for a segment to be worth collecting.

See RawMap::compact_hard for why the choice of segment changes and why it only changes under pressure.

Source

pub fn prefetch(&self, hash: u64)

Ask the cache for the bucket this key will land in.

The first of the loop’s two walks (04 section 3) calls this.

Source

pub fn hash_of(key: &[u8]) -> u64

The hash this database files key under.

Source§

impl Keyspace

Source

pub fn push<'v>( &mut self, key: &[u8], end: End, values: impl Iterator<Item = &'v [u8]> + Clone, ) -> Result<usize>

LPUSH key element [element ...] and RPUSH. Answers the new length.

The elements arrive as an iterator rather than a slice, the same as SADD, because the wire layer has them as positions in the connection’s read buffer and collecting them into a slice would be an allocation on a shard thread.

They go in one at a time, so LPUSH k a b c leaves the list holding c b a. That reads like a bug and is not: each element in turn is put at the head, and the last one sent ends up in front.

Source

pub fn pushx<'v>( &mut self, key: &[u8], end: End, values: impl Iterator<Item = &'v [u8]> + Clone, ) -> Result<usize>

LPUSHX key element [element ...] and RPUSHX.

The same as Keyspace::push except that a key which is not there stays not there, and the answer is zero.

Source

pub fn pop(&mut self, key: &[u8], end: End) -> Result<Option<Vec<u8>>>

LPOP key and RPOP key, one element.

This allocates, because the element it answers with is the element it just took out of the structure that was holding it, the same bind SPOP is in. Keyspace::pop_into is the version the wire uses, which copies straight into the reply buffer instead.

Source

pub fn pop_into<F>( &mut self, key: &[u8], end: End, count: usize, f: F, ) -> Result<usize>
where F: FnMut(Element<'_>),

LPOP key count and RPOP key count, straight into the reply.

Answers how many were taken. f is called with each element in the order the reply wants them, which for LPOP with a count is head first and for RPOP is tail first, and it is called before the element is dropped so nothing has to be copied to a Vec on the way (Y18).

A count larger than the list takes the whole list, and the key goes with it.

Source

pub fn llen(&mut self, key: &[u8]) -> Result<usize>

LLEN key. Zero for a key that is not there.

Source

pub fn lindex(&mut self, key: &[u8], index: i64) -> Result<Option<Element<'_>>>

LINDEX key index, counting from the back when the index is negative.

Source

pub fn lrange( &mut self, key: &[u8], start: i64, stop: i64, ) -> Result<impl Iterator<Item = Element<'_>>>

LRANGE key start stop, both ends inclusive and both able to be negative.

The answer borrows the database for as long as it is alive, so the caller walks it straight into the reply rather than collecting it (Y18). A key that is not there is an empty range and not a nil, which is what Redis replies and is the one place a list differs from a set.

Source

pub fn lset(&mut self, key: &[u8], index: i64, value: &[u8]) -> Result<()>

LSET key index element.

Two errors and no boolean, because both of them are errors on the wire: no such key for a missing key and index out of range for an index the list does not reach. A list is never empty, so those really are the only two ways to miss.

Source

pub fn linsert( &mut self, key: &[u8], before: bool, pivot: &[u8], value: &[u8], ) -> Result<i64>

LINSERT key BEFORE|AFTER pivot element.

The new length, or -1 when the pivot is not in the list, or 0 when the key is not there. Three answers in one signed number is Redis’s choice and it is a bad one, but it is on the wire and cannot be changed.

Source

pub fn lrem(&mut self, key: &[u8], count: i64, value: &[u8]) -> Result<usize>

LREM key count element. Answers how many went.

A positive count removes that many from the front, a negative one that many from the back, and zero removes all of them. The key goes if the list ends up empty.

Source

pub fn ltrim(&mut self, key: &[u8], start: i64, stop: i64) -> Result<()>

LTRIM key start stop, keeping the window and throwing the rest away.

A window that selects nothing deletes the key, which is what an empty range means here: LTRIM k 1 0 is the documented way to empty a list.

Source

pub fn lpos( &mut self, key: &[u8], value: &[u8], rank: i64, count: usize, maxlen: usize, out: &mut Vec<usize>, ) -> Result<()>

LPOS key element [RANK rank] [COUNT count] [MAXLEN len].

The positions land in out, which the caller supplies and which is cleared first, because this runs on a shard thread and a shard thread that allocates aborts. count of zero means every match and maxlen of zero means no limit on how far to look, both of which are Redis’s spellings for no limit.

§Errors

A rank of zero, which has no reading. Everything else about a missing key or a missing element is an empty answer rather than an error.

Source

pub fn lpos_into<F>( &mut self, key: &[u8], value: &[u8], rank: i64, count: usize, maxlen: usize, found: F, ) -> Result<usize>
where F: FnMut(usize),

LPOS, with each position handed over as it is found.

This is what the wire calls. The positions go straight into the reply buffer as they are discovered, so a LPOS key x COUNT 0 over a list with ten thousand matches never builds a list of ten thousand numbers anywhere (Y18). Answers how many there were.

§Errors

A rank of zero, and WRONGTYPE for a key that is not a list.

Source

pub fn lmove( &mut self, src: &[u8], dst: &[u8], from: End, to: End, ) -> Result<Option<&[u8]>>

LMOVE src dst LEFT|RIGHT LEFT|RIGHT, and RPOPLPUSH under it.

Answers the element that moved, or nothing when the source is empty or missing. The destination is made if it is not there, and the source key goes if that was its last element.

src and dst being the same key is not a special case to work around, it is LMOVE k k LEFT RIGHT, which is the documented way to rotate a list and is what a round robin scheduler is built out of. It falls out of taking the element before deciding where to put it.

This is the one list command that has to copy an element, for the reason SPOP gives: the value it answers with no longer has a structure to borrow from. Moving the bytes from one list to the other without the copy would need both bodies borrowed at once, and the destination may be the source.

The copy is not an allocation, though, which is the difference between this and the first version of it. The element goes into the database’s one scratch buffer and the answer borrows that, so a queue that runs RPOPLPUSH in a loop does no allocator work at all after the first call, where before it did a malloc and a free per element. The answer borrows the database until the caller is done with it, which is what both callers want anyway: they write it to the reply and drop it.

Source

pub fn lmovem<F>( &mut self, src: &[u8], dst: &[u8], b: Movem, f: F, ) -> Result<usize>
where F: FnMut(&[u8]),

LMOVEM src dst LEFT|RIGHT LEFT|RIGHT [COUNT|EXACTLY n OBO|BULK].

Keyspace::lmove for more than one element at a time, which Redis 8.10 added. f gets each element that moved, in the order it now sits in the destination, which is the order the reply wants. The count that comes back is how many that was, and zero means the reply is a nil rather than an empty array.

Movem::exactly is the EXACTLY spelling against the COUNT one: all of them or none of them, so a source shorter than the count moves nothing and answers zero. That is the whole difference and it is checked before anything is taken, which is the only way to make it true.

§Why everything is taken before anything is put

Because the destination is allowed to be the source. LMOVEM k k LEFT RIGHT COUNT 2 BULK is a rotation by two and has to work, the same way LMOVE k k LEFT RIGHT is a rotation by one. Popping the whole block first and pushing it afterwards gets that for nothing, where anything that interleaved the two would be reading a list it was writing.

The block goes through the same scratch buffer Keyspace::lmove uses, with the row buffer next to it holding where each element ends in it, so moving a hundred elements is two buffers that were already there rather than a Vec per element. Both are taken out of the database for the duration, because the bytes have to be in hand while push has &mut self.

Source§

impl Keyspace

Source

pub fn sadd<'m>( &mut self, key: &[u8], members: impl Iterator<Item = &'m [u8]> + Clone, ) -> Result<usize>

SADD key member [member ...]. Answers how many were new.

The members arrive as an iterator and not a slice, the way MSET’s pairs do, because the wire layer has them as positions in the connection’s read buffer and a slice would mean collecting them first. A shard thread that allocates in order to call a command is the thing Y1 is trying to avoid. The iterator is walked more than once, which is why it has to be Clone, and an iterator over borrowed slices is two words to copy.

Source

pub fn srem<'m>( &mut self, key: &[u8], members: impl Iterator<Item = &'m [u8]>, ) -> Result<usize>

SREM key member [member ...]. Answers how many were there.

A set that loses its last member loses its key too.

Source

pub fn sismember(&mut self, key: &[u8], member: &[u8]) -> Result<bool>

SISMEMBER key member.

Source

pub fn smismember<'m>( &mut self, key: &[u8], members: impl Iterator<Item = &'m [u8]>, ) -> Result<Vec<bool>>

SMISMEMBER key member [member ...], which is SISMEMBER in bulk.

One key lookup for the whole call rather than one per member, which is the only reason the command exists.

Source

pub fn scard(&mut self, key: &[u8]) -> Result<usize>

SCARD key, which is zero for a key that is not there.

Source

pub fn smembers( &mut self, key: &[u8], ) -> Result<Option<impl Iterator<Item = Member<'_>>>>

SMEMBERS key, as a borrow of the set rather than a copy of it.

The members come back as Members, which are either the bytes where they lie or an integer nobody has formatted yet, so a set of a thousand integers becomes a thousand pieces of reply text and not a thousand Vecs that are then copied into the reply and dropped. That is Y18, and it is why this borrows the database for as long as the answer is alive.

Source

pub fn spop(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>>

SPOP key. Takes one member out at random and hands it back.

This is the one set command that has to allocate, because the member it answers with is the member it just took out of the structure holding it. Keyspace::srandmember is the same draw without the removal and does not allocate, which is why the two are not one method with a flag.

The key goes when the last member does, the same as SREM.

Source

pub fn spop_n(&mut self, key: &[u8], count: usize) -> Result<Vec<Vec<u8>>>

SPOP key count. Takes count members out, or all of them if there are fewer than that.

Drawing from the length that is left rather than from the length it started with is what makes the members distinct without a single test for it. Each removal moves some other member into the hole it made and shortens the set by one, so the next draw is over exactly the members that are still there and every one of them is equally likely.

Source

pub fn spop_into<F>(&mut self, key: &[u8], count: usize, f: F) -> Result<usize>
where F: FnMut(Member<'_>),

SPOP key [count], as a borrow rather than a copy. Answers how many.

The same draw as Keyspace::spop_n and none of the allocating. Each member is handed to f where it lies and taken out afterwards, so the bytes go from the set into the reply buffer and nothing is built in between. spop_n answers a Vec of Vecs, which is one allocation and then one more per member, and that is the right shape for an embedded caller who wants the answer in one piece and the wrong shape for a thread that must not allocate.

That garbage is the whole of SPOP’s gate row. aki came in at 0.58x at P16 and 0.29x at P1 on this command, and the loss was never in the draw: the draw is an index into an array and a swap with the last row. It was in the allocation a member on the way out.

Drawing from the length that is left rather than the length it started with is what makes the members distinct with no test for it, the same reason Keyspace::spop_n gives.

Source

pub fn srandmember<R>( &mut self, key: &[u8], f: impl FnOnce(Option<Member<'_>>) -> R, ) -> Result<R>

SRANDMEMBER key, as a borrow rather than a copy.

The member is handed to f where it lies, so the single draw form allocates nothing at all: the bytes go from the set into the reply buffer and an integer member is never written as digits anywhere in between. That is the whole of the gate row this command has on M3, where the loss against Redis was in the garbage rather than in the draw.

f is handed None when the key is not there, which is a nil reply and not an empty one.

Source

pub fn srandmember_n<F>(&mut self, key: &[u8], count: i64, f: F) -> Result<()>
where F: FnMut(Member<'_>),

SRANDMEMBER key count, which is three different commands wearing one name.

A negative count is the with repeats form: exactly that many members, drawn one at a time, and the same member can come back more than once. It is the only form that can answer more members than the set holds.

A positive count is distinct members, at most as many as the set holds, and it is drawn two different ways depending on how much of the set is being asked for. Wanting more than a third of it is a walk of the whole set picking each member with the probability that leaves the right number at the end, which is Knuth’s selection sampling and needs no memory at all. Wanting less than that is drawing positions and throwing away the repeats, which needs somewhere to remember what has been drawn and is the only thing here that allocates.

Both are O(count), which is the point of having two. Selection sampling alone would walk a million members to answer SRANDMEMBER key 3, and rejection alone would draw forever as the count approached the size. Redis splits the same way at the same ratio.

Source

pub fn sscan<F>( &mut self, key: &[u8], cursor: Cursor, count: usize, f: F, ) -> Result<Cursor>
where F: FnMut(Member<'_>),

SSCAN key cursor. Walks part of the set and says where to resume.

A missing key is a finished scan and not an error, which is what lets a client loop on the cursor without checking whether the key survived the walk. MATCH is not here: filtering the members is the caller’s, so that the pattern is run against the member where it lies rather than against a copy made to be filtered.

Source

pub fn smove( &mut self, source: &[u8], destination: &[u8], member: &[u8], ) -> Result<bool>

SMOVE source destination member. Answers whether it moved.

The order of the checks is Redis’s and it is not the order it looks like it should be. A source that is not there answers zero without ever looking at what the destination holds, so SMOVE nothing a-string m is a zero and not a WRONGTYPE, and a source that is there checks both types before it moves anything.

Moving a member onto its own set is a no op that still answers whether the member was there, which is the one case where a 1 means nothing changed.

Source

pub fn sinter<'k, F>( &mut self, keys: impl Iterator<Item = &'k [u8]>, limit: usize, f: F, ) -> Result<usize>
where F: FnMut(&[u8]),

SINTER key [key ...], and SINTERCARD’s limit.

Zero for a limit means no limit. The count comes back whether or not the caller collected anything, so Keyspace::sintercard is this with a callback that throws its argument away.

Source

pub fn sintercard<'k>( &mut self, keys: impl Iterator<Item = &'k [u8]>, limit: usize, ) -> Result<usize>

SINTERCARD numkeys key [key ...] [LIMIT limit].

Source

pub fn sunion<'k, F>( &mut self, keys: impl Iterator<Item = &'k [u8]>, limit: usize, f: F, ) -> Result<usize>
where F: FnMut(&[u8]),

SUNION key [key ...], and SUNIONCARD’s limit.

A key that is not there contributes nothing and is dropped rather than emptying the answer, which is the opposite of what it does to an intersection and is right for the same reason: an empty set adds no members and removes none.

Zero for a limit means no limit, as it does on Keyspace::sinter.

Source

pub fn sunioncard<'k>( &mut self, keys: impl Iterator<Item = &'k [u8]>, limit: usize, ) -> Result<usize>

SUNIONCARD numkeys key [key ...] [LIMIT limit].

Source

pub fn sdiff<'k, F>( &mut self, keys: impl Iterator<Item = &'k [u8]>, limit: usize, f: F, ) -> Result<usize>
where F: FnMut(&[u8]),

SDIFF key [key ...], and SDIFFCARD’s limit.

The first key is the one being walked, so a first key that is not there is an empty answer whatever the rest hold. A later key that is not there takes nothing away and is dropped.

Source

pub fn sdiffcard<'k>( &mut self, keys: impl Iterator<Item = &'k [u8]>, limit: usize, ) -> Result<usize>

SDIFFCARD numkeys key [key ...] [LIMIT limit].

Source

pub fn sinterstore<'k>( &mut self, destination: &[u8], keys: impl Iterator<Item = &'k [u8]>, ) -> Result<usize>

SINTERSTORE destination key [key ...]. Answers the size of the result.

Source

pub fn sunionstore<'k>( &mut self, destination: &[u8], keys: impl Iterator<Item = &'k [u8]>, ) -> Result<usize>

SUNIONSTORE destination key [key ...].

Source

pub fn sdiffstore<'k>( &mut self, destination: &[u8], keys: impl Iterator<Item = &'k [u8]>, ) -> Result<usize>

SDIFFSTORE destination key [key ...].

Source

pub fn with_set<R>( &mut self, key: &[u8], f: impl FnOnce(Option<&Set>) -> R, ) -> Result<R>

Hand the set under key to f, or hand it None if there is no key.

This is what the wire layer reaches for when one command wants the body more than once. SMEMBERS needs the count for the reply header and then the members, and SMISMEMBER needs one membership test per argument, and going back through Keyspace::scard and Keyspace::sismember for each of those is a key lookup a piece. One lookup, then a borrow of the body for as long as the caller needs it.

It is a callback rather than a returned &Set because the reap has to happen under &mut self and the borrow checker will not let a &Set carved out of that outlive the call.

Source§

impl Keyspace

Source

pub fn stream(&mut self, key: &[u8]) -> Result<Option<&Stream>>

The stream under key, for reading.

None for a key that is not there or has expired, and an error for one holding something else, which is the three way answer every type’s entry point here gives. XINFO and XLEN go through this rather than through a method each, because reading a field off a stream is not a decision.

§Errors

Code::WrongType for a key holding anything but a stream.

Source

pub fn stream_mut(&mut self, key: &[u8]) -> Result<Option<&mut Stream>>

The same, for a caller that is going to change it.

§Errors

Code::WrongType for a key holding anything but a stream.

Source

pub fn xadd( &mut self, key: &[u8], id: Add, fields: &[(&[u8], &[u8])], trim: Trim, mkstream: bool, now: u64, ) -> Result<Option<Id>>

XADD key [NOMKSTREAM] [trim] id field value [field value ...].

Answers the ID that was written, or None when NOMKSTREAM was asked for and the key was not there.

The trim runs after the append, which is Redis’s order and matters when the threshold is MAXLEN 1: the entry that was just written is the one that survives.

§Errors

Code::WrongType for a key holding something else, and Code::Invalid for an ID that is zero, is not above the last one, or asks for a sequence inside a millisecond that has already filled up.

Source

pub fn xdel(&mut self, key: &[u8], ids: impl Iterator<Item = Id>) -> Result<u64>

XDEL key id [id ...]. Answers how many were there to delete.

§Errors

Code::WrongType for a key holding something else.

Source

pub fn xdelex<F>( &mut self, key: &[u8], refs: Refs, ids: impl Iterator<Item = Id>, f: F, ) -> Result<()>
where F: FnMut(Fate),

XDELEX key [KEEPREF|DELREF|ACKED] IDS numids id [id ...].

The callback gets what became of each ID, in the order they were given. A key that is not there is not an error and not a short reply either: every ID gets Fate::Missing, which is what a real server answers and is why the ID list is walked even when there is nothing to walk it against.

§Errors

Code::WrongType for a key holding something else.

Source

pub fn xackdel<F>( &mut self, key: &[u8], group: &[u8], refs: Refs, ids: impl Iterator<Item = Id>, f: F, ) -> Result<()>
where F: FnMut(Fate),

XACKDEL key group [KEEPREF|DELREF|ACKED] IDS numids id [id ...].

The same shape, and a group that is not there behaves like a key that is not there rather than raising NOGROUP, because the answer this command gives per ID is about the pending list and an absent group is holding nothing.

§Errors

Code::WrongType for a key holding something else.

Source

pub fn xnack( &mut self, key: &[u8], group: &[u8], retry: Retry, force: bool, ids: impl Iterator<Item = Id>, ) -> Result<Option<u64>>

XNACK key group <SILENT|FAIL|FATAL> IDS numids id [id ...] [RETRYCOUNT n] [FORCE].

Answers how many entries were released, and None when there is no such key or group, which this command does raise NOGROUP for.

§Errors

Code::WrongType for a key holding something else.

Source

pub fn xtrim(&mut self, key: &[u8], trim: Trim) -> Result<u64>

XTRIM key strategy. Answers how many entries went.

§Errors

Code::WrongType for a key holding something else.

Source

pub fn xsetid( &mut self, key: &[u8], last: Id, added: Option<u64>, max_deleted: Option<Id>, ) -> Result<()>

XSETID key id [ENTRIESADDED n] [MAXDELETEDID id].

§Errors

Code::WrongType for a key holding something else, Code::NotFound for a key that is not there, and Code::Invalid for an ID below an entry the stream still holds.

Source

pub fn xrange_into<F>( &mut self, key: &[u8], start: Id, end: Id, count: Option<usize>, rev: bool, f: F, ) -> Result<usize>
where F: FnMut(Id, Fields<'_>) -> bool,

XRANGE and XREVRANGE, which differ only in the direction.

start and end are the low and the high end either way, so the wire layer swaps XREVRANGE’s arguments once rather than every reader here working out which is which. Answers how many entries the callback saw.

§Errors

Code::WrongType for a key holding something else.

Source

pub fn xread_into<F>( &mut self, key: &[u8], after: Id, count: Option<usize>, f: F, ) -> Result<usize>
where F: FnMut(Id, Fields<'_>) -> bool,

XREAD ... STREAMS key id, which is a plain range with no group.

Everything after after, up to count. Answers how many the callback saw, which is zero for a key that is not there, because XREAD on a missing key is nothing to report rather than an error.

§Errors

Code::WrongType for a key holding something else.

Source

pub fn xgroup_create( &mut self, key: &[u8], group: &[u8], at: Start, mkstream: bool, read: Option<u64>, ) -> Result<bool>

XGROUP CREATE key group id [MKSTREAM] [ENTRIESREAD n].

Answers whether the group was made, which is false when one of that name was already there and is the BUSYGROUP the wire reports.

§Errors

Code::WrongType for a key holding something else, and Code::NotFound for a key that is not there without MKSTREAM.

Source

pub fn xgroup_destroy(&mut self, key: &[u8], group: &[u8]) -> Result<bool>

XGROUP DESTROY key group. Answers whether there was one.

A group that is not there is a zero and a key that is not there is an error, which is Redis’s rule for every XGROUP subcommand and is worth stating because the two look like the same kind of nothing from a client. They are not: destroying a group nobody made is a no op, and destroying a group on a key nobody made is a mistake about which key.

§Errors

Code::WrongType for a key holding something else, and Code::NotFound for a key that is not there.

Source

pub fn xgroup_setid( &mut self, key: &[u8], group: &[u8], at: Start, read: Option<u64>, ) -> Result<Option<()>>

XGROUP SETID key group id [ENTRIESREAD n].

None when there is no such group, which the wire reports as NOGROUP.

§Errors

Code::WrongType for a key holding something else, and Code::NotFound for a key that is not there.

Source

pub fn xgroup_create_consumer( &mut self, key: &[u8], group: &[u8], consumer: &[u8], now: u64, ) -> Result<Option<bool>>

XGROUP CREATECONSUMER key group consumer.

Answers whether the consumer was made, and None when there is no such group.

§Errors

Code::WrongType for a key holding something else, and Code::NotFound for a key that is not there.

Source

pub fn xgroup_del_consumer( &mut self, key: &[u8], group: &[u8], consumer: &[u8], ) -> Result<Option<u64>>

XGROUP DELCONSUMER key group consumer.

Answers how many pending entries went with it, and None when there is no such group.

§Errors

Code::WrongType for a key holding something else, and Code::NotFound for a key that is not there.

Source

pub fn xreadgroup_into<F>( &mut self, key: &[u8], want: Read<'_>, now: u64, f: F, ) -> Result<Option<usize>>
where F: FnMut(Id, Option<Fields<'_>>) -> bool,

XREADGROUP GROUP group consumer [COUNT n] [NOACK] STREAMS key id.

Answers how many entries the callback saw, and None when there is no such group. The callback takes an Option because a history read can name an entry that has since been deleted, and Redis puts a null in the reply for it rather than leaving it out.

§Errors

Code::WrongType for a key holding something else, and Code::NotFound for a key that is not there, which is the NOGROUP Redis answers because a missing key cannot have the group either.

Source

pub fn xack( &mut self, key: &[u8], group: &[u8], ids: impl Iterator<Item = Id>, ) -> Result<u64>

XACK key group id [id ...]. Answers how many were pending.

§Errors

Code::WrongType for a key holding something else.

Source

pub fn xpending_into<F>( &mut self, key: &[u8], group: &[u8], want: Filter, now: u64, f: F, ) -> Result<Option<usize>>
where F: FnMut(Id, &Nack, Option<&Consumer>) -> bool,

XPENDING key group [[IDLE ms] start end count [consumer]], the long form.

The callback gets each entry with its NACK and its owner. Answers how many it saw, and None when there is no such group.

§Errors

Code::WrongType for a key holding something else.

Source

pub fn xclaim( &mut self, key: &[u8], ids: &[Id], how: Claim<'_>, now: u64, gone: &mut Vec<Id>, ) -> Result<Option<Vec<Id>>>

XCLAIM key group consumer min-idle-time id [id ...].

Answers what was claimed, and fills gone with the IDs that were in the pending list and are no longer in the stream, which the claim clears out on the way past because nobody can ever finish them. None when there is no such group.

§Errors

Code::WrongType for a key holding something else.

Source

pub fn xautoclaim( &mut self, key: &[u8], start: Id, how: Claim<'_>, count: usize, now: u64, gone: &mut Vec<Id>, ) -> Result<Option<(Option<Id>, Vec<Id>)>>

XAUTOCLAIM key group consumer min-idle-time start [COUNT n] [JUSTID].

Answers where a following call should carry on from, which is None at the end of the list and is the 0-0 Redis replies with, along with what was claimed. gone is filled the same way Keyspace::xclaim fills it.

§Errors

Code::WrongType for a key holding something else.

Source§

impl Keyspace

The string commands.

These hang off the database rather than off a per type object, because a key belongs to the database: GET against a set has to be able to see that it is a set.

Source

pub fn get(&mut self, key: &[u8]) -> Result<Option<Str<'_>>>

GET key.

One probe of the map for the whole command. It used to be three, because the reap looked the key up to see whether it was dead, the type check looked it up to see whether it was a string, and the read looked it up again to read it, and all three walked a bucket for the same record. Keyspace::live_rec hands back where that record is and the rest is two arena reads at a known address.

Source

pub fn mget<'a>(&'a mut self, keys: &[&[u8]]) -> Vec<Option<Str<'a>>>

MGET key [key ...].

Every dead key is reaped first and the whole answer is then read from a store nobody is going to mutate, which is what lets all of the returned values borrow from it at once instead of being copied out one at a time.

Source

pub fn mget_one(&mut self, key: &[u8]) -> Option<Str<'_>>

One key of an MGET, which is nil rather than an error for a key that holds another type.

Keyspace::mget collects the whole answer into a Vec for a caller that wants it in one piece. The wire wants the keys one at a time and in order, and a Vec there would be an allocation per call on a thread that must not allocate, so the dispatcher walks the keys itself and calls this for each. It is not get, because MGET does not answer WRONGTYPE: Redis gives nil for the odd key out rather than failing the ninety nine good ones alongside it.

Source

pub fn strlen(&mut self, key: &[u8]) -> Result<usize>

STRLEN key, which is zero for a key that is not there.

Answered out of the record even when the value is on the file, because a demoted record carries the length next to the address. Going to the device for a number that is already in memory would be a device read spent on nothing, and it would be one that a client could use to pull a whole database back into memory a key at a time.

Source

pub fn exists(&mut self, key: &[u8]) -> bool

EXISTS key, for one key.

Asking whether a key is there does not count as using it, which is Redis’s rule and not a nicety. A health check that runs EXISTS over a list of keys every second would otherwise be enough on its own to make all of them look like the hottest keys in the database.

Source

pub fn encoding(&mut self, key: &[u8]) -> Option<Encoding>

How a string is stored, which is OBJECT ENCODING for a string key.

None for a key that is not there and for a key holding another type, because the two encoding bits in a record only mean anything when the record is the value. A set keeps its representation in its body, so Keyspace::set_encoding asks the body, and Keyspace::encoding_name is the command that routes between them.

Every OBJECT subcommand looks without touching, so this does too.

Source

pub fn expire_at(&mut self, key: &[u8]) -> Option<u64>

The key’s deadline as an absolute unix millisecond, if it has one.

EXPIRETIME and PEXPIRETIME, which do not count as using the key. See Keyspace::deadline_of.

Source

pub fn getrange( &mut self, key: &[u8], start: i64, end: i64, ) -> Result<Cow<'_, [u8]>>

GETRANGE key start end, and SUBSTR, which is the same command.

Both ends are inclusive and both may be negative, counting back from the end. Everything out of range clamps, and a start past the end gives the empty string rather than an error, which is Redis’s behaviour and not an oversight in it.

Borrowed for a string, owned for an integer, because an integer’s digits do not exist anywhere until somebody asks for them.

Source

pub fn set( &mut self, key: &[u8], val: &[u8], opts: SetOptions<'_>, ) -> Result<SetOutcome>

SET key value [NX|XX] [GET] [IFEQ v|IFNE v|IFDEQ d|IFDNE d] [EX s|PX ms|EXAT s|PXAT ms|KEEPTTL].

The order the conditions are tested in is Redis’s: the key is looked at once, NX, XX and the four IF forms all decide against that one look, and GET reports what was there whether or not the write went ahead.

The old value comes back owned, which costs a copy of it. On the wire that copy is pure waste, because the reply is written and the bytes are never looked at again, so the wire calls Keyspace::set_with instead and this is that with a to_vec on the end.

Source

pub fn set_with<F>( &mut self, key: &[u8], val: &[u8], opts: SetOptions<'_>, previous: F, ) -> Result<SetOutcome>
where F: FnOnce(Str<'_>),

SET, handing the old value to previous rather than copying it out.

Keyspace::set with the allocation taken off it. previous is called with the value as it lies in the record, before the write goes over it, and only when GET was asked for and there was something there. Nothing after that point can fail, so a caller that writes the value straight into a reply is not going to have to take it back out again.

SetOutcome::previous is always None here. The value went to the closure, and putting it in both places would be the copy this exists to avoid.

Source

pub fn set_plain(&mut self, key: &[u8], val: &[u8]) -> Result<()>

SET key value, with nothing else asked for.

Source

pub fn setnx(&mut self, key: &[u8], val: &[u8]) -> Result<bool>

SETNX key value, which answers whether it stored.

Source

pub fn setex(&mut self, key: &[u8], seconds: i64, val: &[u8]) -> Result<()>

SETEX key seconds value.

A zero or negative time to live is an error and not a delete, which is what Redis does: SETEX k 0 v returns ERR invalid expire time.

Source

pub fn psetex(&mut self, key: &[u8], millis: i64, val: &[u8]) -> Result<()>

PSETEX key milliseconds value.

Source

pub fn getset(&mut self, key: &[u8], val: &[u8]) -> Result<Option<Vec<u8>>>

GETSET key value, which is SET key value GET without the options.

Source

pub fn getdel(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>>

GETDEL key.

Source

pub fn getdel_with<F>(&mut self, key: &[u8], f: F) -> Result<bool>
where F: FnOnce(Str<'_>),

GETDEL, handing the value to f rather than copying it out.

Keyspace::getdel with the allocation taken off it, the same pair Keyspace::set and Keyspace::set_with are. f is called with the value where it still lies, before the key goes, and the answer says whether there was one.

Source

pub fn getex(&mut self, key: &[u8], expire: Expire) -> Result<Option<Str<'_>>>

GETEX key [EX s|PX ms|EXAT s|PXAT ms|PERSIST].

Expire::Keep is plain GETEX, which reads without touching the deadline, and Expire::Clear is GETEX PERSIST.

Source

pub fn del(&mut self, key: &[u8]) -> bool

DEL key, for one key. Answers whether it was there.

Any type, and it takes the body with it. DEL is the one command that genuinely does not care what it is deleting.

Source

pub fn mset<'k>( &mut self, pairs: impl Iterator<Item = (&'k [u8], &'k [u8])> + Clone, ) -> Result<()>

MSET key value [key value ...].

Always succeeds, always overwrites, and always clears any deadline the keys had, which is SET without options applied to each pair in turn.

The pairs arrive as an iterator rather than a slice because the wire layer has them as positions in the connection’s read buffer, and a slice would mean collecting them into a Vec first. MSET is on the list of four commands M2 is measured on, and a shard thread that allocates aborts, so an API that forces an allocation to call it is the wrong API. The iterator is walked twice, which is why it has to be Clone, and an iterator over borrowed slices is two words to copy.

Source

pub fn msetnx<'k>( &mut self, pairs: impl Iterator<Item = (&'k [u8], &'k [u8])> + Clone, ) -> Result<bool>

MSETNX key value [key value ...], which stores all of them or none.

The whole set of keys is checked before anything is written, so a duplicate key inside one call does not defeat itself.

Source

pub fn append(&mut self, key: &[u8], tail: &[u8]) -> Result<usize>

APPEND key value, answering the new length.

Appending to a key that is not there creates it, which makes APPEND on an empty key the same as SET. Any deadline the key had is kept, which is Redis’s behaviour: APPEND is not a fresh SET.

Source

pub fn setrange( &mut self, key: &[u8], offset: usize, val: &[u8], ) -> Result<usize>

SETRANGE key offset value, answering the new length.

A write past the end pads with zero bytes, and a write of nothing to a key that is not there creates nothing and answers zero. Both of those are Redis’s, and both are the kind of edge a client library’s test suite checks.

Source

pub fn incr(&mut self, key: &[u8]) -> Result<i64>

INCR key.

Source

pub fn decr(&mut self, key: &[u8]) -> Result<i64>

DECR key.

Source

pub fn decrby(&mut self, key: &[u8], by: i64) -> Result<i64>

DECRBY key decrement.

Negating first would overflow on i64::MIN, which is why the decrement is carried through as a subtraction rather than turned into an addition.

Source

pub fn incrby(&mut self, key: &[u8], by: i64) -> Result<i64>

INCRBY key increment, and with an increment of one, INCR.

This is the command the milestone’s gate is about, so the path it takes is worth stating. A key that is already int encoded is one probe, an add and an eight byte store back into the record the probe landed on. No arena allocation, no free, no second record, and no rehash. Every other case falls through to a rewrite, which is what INCR on a string that happens to look like a number costs.

Source

pub fn incrbyfloat(&mut self, key: &[u8], by: f64) -> Result<f64>

INCRBYFLOAT key increment.

The result is stored as a string, never as an integer, because Redis stores it with its own formatting and OBJECT ENCODING reports embstr afterwards even when the number came out whole.

Source

pub fn msetex<'k>( &mut self, pairs: impl Iterator<Item = (&'k [u8], &'k [u8])> + Clone, exists: Exists, expire: Expire, ) -> Result<bool>

MSETEX numkeys key value [key value ...] [NX|XX] [EX s|PX ms|EXAT s|PXAT ms|KEEPTTL].

Redis 8.4. MSET with a condition and a shared deadline, and the condition is over the whole set rather than per key: NX needs every key to be missing and XX needs every one to be present, and a partial match writes nothing and answers false. Without an expiration option the deadline is cleared, the same way plain SET clears it, and Expire::Keep is KEEPTTL, which leaves each key its own.

A duplicate key inside one call is not an error and the last value wins.

Source

pub fn delex(&mut self, key: &[u8], compare: Option<Compare<'_>>) -> bool

DELEX key [IFEQ v|IFNE v|IFDEQ d|IFDNE d].

Redis 8.4’s compare and delete, the other half of SET ... IFEQ. The point of it is the read modify write nobody was doing correctly: a client that reads a value, decides it is stale and deletes it can be beaten to the key by another client between the read and the delete, and WATCH plus MULTI costs a round trip to avoid it.

None compares against nothing and deletes unconditionally, which is plain DEL for one key. A key that is not there answers false whatever the condition says, including the NE forms that a missing key satisfies, because there is still nothing to delete.

Source

pub fn digest(&mut self, key: &[u8]) -> Result<Option<u64>>

DIGEST key, the XXH3 of the value.

Redis 8.4, and the reason it exists is IFDEQ: a client that wants to compare and swap against a large value sends eight bytes instead of the value. None is a key that is not there, which is a nil reply.

Source

pub fn increx(&mut self, key: &[u8], opts: IncrEx) -> Result<Counted>

INCREX key [BYINT n|BYFLOAT f] [SATURATE] [LBOUND l] [UBOUND u] [EX s|PX ms|EXAT s|PXAT ms|PERSIST] [ENX].

Redis 8.8, and the first Redis primitive that implements a workload rather than a data structure. What it replaces is INCR followed by EXPIRE, which is two round trips, or a Lua script, which is one round trip and a script cache.

The rate limiter is INCREX key EX window ENX: the counter goes up, and the window is started only when the key had no deadline, so a burst inside one window expires together at the deadline the first call set rather than each call pushing it out. The quota counter is UBOUND without SATURATE, which refuses rather than clamping and reports zero applied. The stock level is LBOUND 0 SATURATE, which takes what it can.

A refused increment writes nothing at all: it does not create the key and it does not touch the deadline of a key that was there.

Source

pub fn string_copy(&mut self, key: &[u8]) -> Result<Vec<u8>>

One string value, copied out, or an empty one for a key that is not there.

What LCS needs, and the only read here that hands back an owned value. It is its own method rather than a step inside Keyspace::lcs because the two keys LCS names can be on two stripes of the same database, and then there is no single keyspace that can be asked for both.

§Errors

WRONGTYPE if the key holds something that is not a string.

Source

pub fn lcs(&mut self, a: &[u8], b: &[u8]) -> Result<Vec<u8>>

LCS key1 key2, the longest common subsequence itself.

A key that is not there is the empty string, which is Redis’s reading and not an error.

Source

pub fn lcs_len(&mut self, a: &[u8], b: &[u8]) -> Result<usize>

LCS key1 key2 LEN.

Source

pub fn lcs_idx(&mut self, a: &[u8], b: &[u8], minmatchlen: u32) -> Result<Idx>

LCS key1 key2 IDX [MINMATCHLEN n].

WITHMATCHLEN is not a parameter here because every run comes back with its length attached. Whether that length reaches the client is the reply writer’s decision and not the store’s.

Source§

impl Keyspace

Source

pub fn scan( &mut self, from: KeyCursor, budget: usize, ty: Option<Kind>, out: impl FnMut(&[u8]), ) -> KeyCursor

A batch of keys, and where the next batch starts.

This is SCAN. budget is COUNT, ty is TYPE, and MATCH belongs to the caller because a glob is a wire concern and this is not the wire.

The cursor is opaque to the client and is not opaque here: it names a place in the keyspace rather than a place in memory, which is what lets it survive the index doubling between two calls. The reasoning is in yo_index::Cursor.

A key that is there for the whole scan comes back at least once. A key added or removed partway through may or may not, and any key may come back twice. That is Redis’s contract and a client written against Redis already copes with all three.

Source

pub fn keys(&mut self, out: impl FnMut(&[u8]))

Every key in the database, once each.

This is KEYS, and it is the command whose reputation is deserved: it visits every bucket in the index before it answers anything, and a database of ten million keys is ten million calls to out with the shard doing nothing else. It is here because tooling needs it and because SCAN is the answer for everything else.

One walk with an unbounded budget rather than a loop over Keyspace::scan, which is the same walk without the chance of a duplicate, because nothing can split the index while this is running.

Source

pub fn random_key(&mut self) -> Option<&[u8]>

One key, chosen at random, or None if the database is empty.

This is RANDOMKEY. It picks a random position in the index and takes a key from the bucket that lands in, which is a constant number of loads and does not depend on how many keys there are.

Uniform within the bucket and only roughly uniform across the keyspace, since a bucket holding two keys and a bucket holding twelve are equally likely to be landed on. Redis’s is biased the same way and for the same reason. What it is not is skewed towards any particular key, which is what matters for the thing RANDOMKEY is actually used for, which is sampling a live database to see what is in it.

The answer borrows the database’s scratch buffer, so it is good until the next call and the caller copies it if it wants to keep it. That is what takes the allocation off the command: sampling is a thing callers do in a loop, and a key name is a handful of bytes that used to cost a malloc and a free every time round.

Source§

impl Keyspace

Source

pub fn zadd<'m, I>(&mut self, key: &[u8], pairs: I, opts: ZAdd) -> Result<usize>
where I: Iterator<Item = (f64, &'m [u8])> + Clone,

ZADD key [NX|XX] [GT|LT] [CH] score member [score member ...].

Answers how many members were added, or how many were added or changed if CH was given, which is the only thing CH does.

The pairs arrive as an iterator and not a slice, the way SADD’s members do, because the wire layer has them as positions in the connection’s read buffer and a slice would mean collecting them first. It is walked more than once, which is why it has to be Clone.

Source

pub fn zincrby( &mut self, key: &[u8], member: &[u8], by: f64, opts: ZAdd, ) -> Result<Option<f64>>

ZADD key ... INCR score member, and ZINCRBY key increment member.

Answers the member’s new score, or nothing at all if a gate refused it, which is the nil ZADD INCR replies with and is why this is one method rather than an INCR flag on Keyspace::zadd that would have to return two different shapes.

ZINCRBY is this with no gate, where the answer is never nil.

Source

pub fn zcard(&mut self, key: &[u8]) -> Result<usize>

ZCARD key.

Source

pub fn zscore(&mut self, key: &[u8], member: &[u8]) -> Result<Option<f64>>

ZSCORE key member.

Source

pub fn zmscore<'m>( &mut self, key: &[u8], members: impl Iterator<Item = &'m [u8]>, out: &mut Vec<Option<f64>>, ) -> Result<()>

ZMSCORE key member [member ...], which is ZSCORE in bulk.

One key lookup for the whole call rather than one per member, which is the only reason the command exists.

Source

pub fn zrem<'m>( &mut self, key: &[u8], members: impl Iterator<Item = &'m [u8]>, ) -> Result<usize>

ZREM key member [member ...]. Answers how many were there.

A sorted set that loses its last member loses its key too, because an empty sorted set does not exist in Redis.

Source

pub fn zrank( &mut self, key: &[u8], member: &[u8], rev: bool, ) -> Result<Option<(usize, f64)>>

ZRANK key member [WITHSCORE], and ZREVRANK with rev set.

The score comes back whether it was asked for or not, because finding the rank already read it and handing it over costs nothing.

Source

pub fn zwindow(&mut self, key: &[u8], q: &Query<'_>) -> Result<Window>

How many members a query covers, and where they start.

Every range command starts here. It is separate from Keyspace::zwalk because a RESP array writes its length before its members, and a reply that collected the members in order to count them would allocate on the read path.

Source

pub fn zwalk<F>(&mut self, key: &[u8], w: Window, f: F) -> Result<()>
where F: FnMut(Member<'_>, f64),

Hand over the members a window covers, in order, without collecting them.

The window is the caller’s rather than the query’s, so that a caller that has already asked Keyspace::zwindow does not compute it twice, and so that ZRANGESTORE can walk a window it has already narrowed.

Source

pub fn zcount(&mut self, key: &[u8], q: &Query<'_>) -> Result<usize>

ZCOUNT, ZLEXCOUNT, and the count half of any other range command.

Source

pub fn zremrange(&mut self, key: &[u8], q: &Query<'_>) -> Result<usize>

ZREMRANGEBYRANK, ZREMRANGEBYSCORE and ZREMRANGEBYLEX, which differ only in what the query was measured in.

Answers how many went. The window is taken out from its high end down, so that every rank still to be removed is the rank it was when the window was worked out.

Source

pub fn zpop<F>( &mut self, key: &[u8], end: From, count: usize, f: F, ) -> Result<usize>
where F: FnMut(Member<'_>, f64),

ZPOPMIN key [count] and ZPOPMAX key [count].

Nothing is collected. The member at the end is handed to f, which writes it wherever it is going, and only then is it removed, which is why this does not allocate where SPOP has to.

Source

pub fn zpop_one( &mut self, key: &[u8], end: From, ) -> Result<Option<(Vec<u8>, f64)>>

ZPOPMIN key and ZPOPMAX key, as an owned member for a caller that has nowhere to write it yet.

This is what BZPOPMIN needs: a worker that has been parked has no reply buffer open at the moment the member becomes available, so this one has to allocate where Keyspace::zpop does not.

Source

pub fn zrandmember<F>(&mut self, key: &[u8], count: i64, f: F) -> Result<usize>
where F: FnMut(Member<'_>, f64),

ZRANDMEMBER key [count].

A positive count draws without replacement and answers at most as many as there are, and a negative one draws with replacement and answers exactly as many as asked for, which is Redis’s rule and is why the count is signed here rather than paired with a flag.

The draw without replacement is a partial shuffle of the row numbers and not a retry loop, because a retry loop on a count near the size of the set spends most of its time drawing members it already has.

Source

pub fn zscan<F>( &mut self, key: &[u8], cursor: Cursor, count: usize, f: F, ) -> Result<Cursor>
where F: FnMut(Member<'_>, f64),

ZSCAN key cursor [COUNT count].

A small sorted set comes back whole with a cursor of Cursor::END, the same guarantee SSCAN and HSCAN give, because a listpack has no stable position to resume from and 128 members is not worth a resume.

Source

pub fn zsetop<'k, F>( &mut self, op: Op, keys: impl Iterator<Item = &'k [u8]>, weights: &[f64], agg: Aggregate, f: F, ) -> Result<usize>
where F: FnMut(Member<'_>, f64),

ZUNION, ZINTER and ZDIFF, which differ only in op.

The members come out in rank order, which means the result has to be put in order before any of it can be handed over, and that is what the return value’s ordering costs. ZINTERCARD exists precisely because counting does not need any of that, and it does not come through here.

Source

pub fn zsetop_store<'k>( &mut self, op: Op, destination: &[u8], keys: impl Iterator<Item = &'k [u8]>, weights: &[f64], agg: Aggregate, ) -> Result<usize>

ZUNIONSTORE, ZINTERSTORE and ZDIFFSTORE.

The destination is allowed to be one of the sources, which is safe for the reason SINTERSTORE gives: the result is built whole before the destination is touched, so nothing here writes over a body that is still being read.

Source

pub fn zintercard<'k>( &mut self, keys: impl Iterator<Item = &'k [u8]>, limit: usize, ) -> Result<usize>

ZINTERCARD numkeys key [key ...] [LIMIT limit].

Nothing is stored and no score is worked out, and a limit stops the walk as soon as it is reached, which is the only reason this is not ZINTER with the members thrown away.

Source

pub fn zrangestore( &mut self, destination: &[u8], source: &[u8], q: &Query<'_>, ) -> Result<usize>

ZRANGESTORE destination source <the arguments of ZRANGE>.

The window is copied rather than moved, because the destination may be the source and because the source keeps its members either way. An empty window deletes the destination, which is what ZRANGESTORE d s 5 1 does and is the same rule every store form follows.

Trait Implementations§

Source§

impl Default for Keyspace

Source§

fn default() -> Keyspace

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.