pub struct Keyspace { /* private fields */ }Expand description
One database: every key, whatever type it holds.
Implementations§
Source§impl Keyspace
impl Keyspace
Sourcepub fn arset<'v>(
&mut self,
key: &[u8],
index: u64,
values: impl Iterator<Item = &'v [u8]> + Clone,
) -> Result<u64>
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.
Sourcepub fn armset<'v>(
&mut self,
key: &[u8],
pairs: impl Iterator<Item = (u64, &'v [u8])> + Clone,
) -> Result<u64>
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.
Sourcepub fn arget(&mut self, key: &[u8], index: u64) -> Result<Option<Element<'_>>>
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.
Sourcepub fn arget_into<F>(
&mut self,
key: &[u8],
indices: impl Iterator<Item = u64>,
f: F,
) -> Result<()>
pub fn arget_into<F>( &mut self, key: &[u8], indices: impl Iterator<Item = u64>, f: F, ) -> Result<()>
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).
Sourcepub fn argetrange<F>(
&mut self,
key: &[u8],
start: u64,
end: u64,
f: F,
) -> Result<u64>
pub fn argetrange<F>( &mut self, key: &[u8], start: u64, end: u64, f: F, ) -> Result<u64>
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.
Sourcepub fn arlen(&mut self, key: &[u8]) -> Result<u64>
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.
Sourcepub fn arcount(&mut self, key: &[u8]) -> Result<u64>
pub fn arcount(&mut self, key: &[u8]) -> Result<u64>
ARCOUNT key, how many indices hold something.
Sourcepub fn ardel(
&mut self,
key: &[u8],
indices: impl Iterator<Item = u64>,
) -> Result<u64>
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.
Sourcepub fn ardelrange(
&mut self,
key: &[u8],
ranges: impl Iterator<Item = (u64, u64)>,
) -> Result<u64>
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.
Sourcepub fn arinsert<'v>(
&mut self,
key: &[u8],
values: impl Iterator<Item = &'v [u8]> + Clone,
) -> Result<u64>
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.
Sourcepub fn arring<'v>(
&mut self,
key: &[u8],
size: u64,
values: impl Iterator<Item = &'v [u8]> + Clone,
) -> Result<u64>
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.
Sourcepub fn arnext(&mut self, key: &[u8]) -> Result<Option<u64>>
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.
Sourcepub fn arseek(&mut self, key: &[u8], index: u64) -> Result<bool>
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.
Sourcepub fn arlastitems<F>(
&mut self,
key: &[u8],
count: u64,
newest_first: bool,
f: F,
) -> Result<u64>
pub fn arlastitems<F>( &mut self, key: &[u8], count: u64, newest_first: bool, f: F, ) -> Result<u64>
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.
Sourcepub fn arscan<F>(
&mut self,
key: &[u8],
start: u64,
end: u64,
limit: u64,
f: F,
) -> Result<u64>
pub fn arscan<F>( &mut self, key: &[u8], start: u64, end: u64, limit: u64, f: F, ) -> Result<u64>
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.
Sourcepub fn argrep<F>(
&mut self,
key: &[u8],
start: Bound,
end: Bound,
limit: u64,
grep: &mut Grep<'_>,
f: F,
) -> Result<u64>
pub fn argrep<F>( &mut self, key: &[u8], start: Bound, end: Bound, limit: u64, grep: &mut Grep<'_>, f: F, ) -> Result<u64>
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.
Sourcepub fn arop(
&mut self,
key: &[u8],
start: u64,
end: u64,
op: Op,
want: &[u8],
) -> Result<Aggregate>
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.
Sourcepub fn arinfo(&mut self, key: &[u8], full: bool) -> Result<Info>
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
impl Keyspace
Sourcepub fn getbit(&mut self, key: &[u8], offset: u64) -> Result<bool>
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.
Sourcepub fn setbit(
&mut self,
key: &[u8],
offset: u64,
bit: bool,
) -> Result<(bool, bool)>
pub fn setbit( &mut self, key: &[u8], offset: u64, bit: bool, ) -> Result<(bool, bool)>
SETBIT key offset value, answering the bit that was there before and
whether the value had to get longer to hold the offset.
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.
The reply is the first half and the second half is for the notification.
A real server only says setbit when the write did something, and doing
something means either the bit came out different or the value got
longer, so SETBIT k 1 0 on a bit that was already zero says nothing
while SETBIT k 1000 0 on a short value says it. The caller cannot work
the second half out from the reply, since a value that was created or
padded reads back as a zero bit either way.
Sourcepub fn bitcount(
&mut self,
key: &[u8],
range: Option<(i64, i64, Unit)>,
) -> Result<u64>
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.
Sourcepub fn bitpos(
&mut self,
key: &[u8],
bit: bool,
start: Option<i64>,
end: Option<i64>,
unit: Unit,
) -> Result<i64>
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.
Sourcepub fn bitop<'k, I>(&mut self, op: Op, dest: &[u8], srcs: I) -> Result<usize>
pub fn bitop<'k, I>(&mut self, op: Op, dest: &[u8], srcs: I) -> Result<usize>
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. Whatever the destination held before goes, even if
it was not a string: it is written and never read, so its type is not
part of the operation, and only the sources have to be strings. 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.
Sourcepub fn bitfield(&mut self, key: &[u8], ops: &[Sub]) -> Result<Vec<Option<i64>>>
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.
Sourcepub fn bitfield_with<T>(
&mut self,
key: &[u8],
grow: Option<usize>,
run: impl FnOnce(&mut [u8]) -> T,
) -> Result<(T, bool)>
pub fn bitfield_with<T>( &mut self, key: &[u8], grow: Option<usize>, run: impl FnOnce(&mut [u8]) -> T, ) -> Result<(T, bool)>
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.
The second half of the answer is whether the value did get longer, which
the notification wants for the reason Keyspace::setbit gives: a write
that grew the value counts as having done something even when every bit
it wrote came out the same as the one it replaced.
Source§impl Keyspace
impl Keyspace
Sourcepub fn expire_cycle(&mut self, budget: usize) -> Cycle
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.
Sourcepub fn field_expire_cycle(&mut self, budget: usize) -> usize
pub fn field_expire_cycle(&mut self, budget: usize) -> usize
Sweep hash fields that are past their deadline, looking at no more than
budget hashes, and answer how many it looked at.
The other cycle, and it is a different shape because the thing it hunts
is not in a record. A field deadline lives inside the hash body, so the
marked index above cannot see one and the sample it draws would never
offer a hash that has fields to lose but a key with no deadline of its
own, which is the usual way the HEXPIRE family is used. What this draws
from instead is a list the keyspace keeps of the keys whose hashes have
ever taken a field deadline, and the list is short because most servers
have none.
A straight walk with a cursor rather than a random sample, for the same reason: the list holds only candidates, so there is nothing for a sample to filter out and going round it in order gets to every one of them in bounded time. A hash whose earliest deadline has not passed costs a load and a comparison, which is what makes the whole list affordable to walk.
Why a server needs this at all is the question Keyspace::expire_cycle
answers for keys, and the answer for fields has a second half. Memory is
the first: a hash that nobody reads again holds every field it was told
to drop. The second is that the events are observable. A client watching
hexpired on a key hears about the field within a tick of its deadline
on a real server, whether or not anybody touches the hash, and a server
that only reaped lazily would go quiet until the next command arrived.
Source§impl Keyspace
impl Keyspace
Sourcepub fn geoadd<'m, I>(
&mut self,
key: &[u8],
points: I,
opts: ZAdd,
) -> Result<usize>
pub fn geoadd<'m, I>( &mut self, key: &[u8], points: I, opts: ZAdd, ) -> Result<usize>
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.
Sourcepub fn geoadd_counts<'m, I>(
&mut self,
key: &[u8],
points: I,
opts: ZAdd,
) -> Result<(usize, usize)>
pub fn geoadd_counts<'m, I>( &mut self, key: &[u8], points: I, opts: ZAdd, ) -> Result<(usize, usize)>
The same write, with both halves of the count kept apart.
For the reason Keyspace::zadd_counts exists: the reply wants one
number and the notification wants to know whether either half was more
than nothing, since a GEOADD that moved a member without adding one has
written to the key and says so.
Sourcepub fn geopos<'m, F>(
&mut self,
key: &[u8],
members: impl Iterator<Item = &'m [u8]>,
f: F,
) -> Result<()>
pub fn geopos<'m, F>( &mut self, key: &[u8], members: impl Iterator<Item = &'m [u8]>, f: F, ) -> Result<()>
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.
Sourcepub fn geohash<'m, F>(
&mut self,
key: &[u8],
members: impl Iterator<Item = &'m [u8]>,
f: F,
) -> Result<()>
pub fn geohash<'m, F>( &mut self, key: &[u8], members: impl Iterator<Item = &'m [u8]>, f: F, ) -> Result<()>
GEOHASH key member [member ...], the same shape one step further on.
Sourcepub fn geodist(&mut self, key: &[u8], a: &[u8], b: &[u8]) -> Result<Option<f64>>
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.
Sourcepub fn geocentre(
&mut self,
key: &[u8],
member: &[u8],
) -> Result<Option<(f64, f64)>>
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.
Sourcepub fn geosearch(
&mut self,
key: &[u8],
shape: &Shape,
limit: Limit,
) -> Result<usize>
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.
Sourcepub fn geohits(&self) -> &Scratch
pub fn geohits(&self) -> &Scratch
What the last Keyspace::geosearch found.
Sourcepub fn geosearchstore(
&mut self,
dest: &[u8],
src: &[u8],
shape: &Shape,
limit: Limit,
dist: bool,
) -> Result<usize>
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
impl Keyspace
Sourcepub fn hset<'a>(
&mut self,
key: &[u8],
pairs: impl Iterator<Item = (&'a [u8], &'a [u8])> + Clone,
) -> Result<usize>
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.
Sourcepub fn hreplace<'a>(
&mut self,
key: &[u8],
pairs: impl Iterator<Item = (&'a [u8], &'a [u8])> + Clone,
) -> Result<()>
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.
Sourcepub fn hsetnx(&mut self, key: &[u8], field: &[u8], value: &[u8]) -> Result<bool>
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.
Sourcepub fn hget<R>(
&mut self,
key: &[u8],
field: &[u8],
f: impl FnOnce(Option<Text<'_>>) -> R,
) -> Result<R>
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.
Sourcepub fn hmget<'a, F>(
&mut self,
key: &[u8],
fields: impl Iterator<Item = &'a [u8]>,
f: F,
) -> Result<()>
pub fn hmget<'a, F>( &mut self, key: &[u8], fields: impl Iterator<Item = &'a [u8]>, f: F, ) -> Result<()>
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.
Sourcepub fn hdel<'a>(
&mut self,
key: &[u8],
fields: impl Iterator<Item = &'a [u8]>,
) -> Result<usize>
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.
Sourcepub fn hdel_each<'a>(
&mut self,
key: &[u8],
fields: impl Iterator<Item = &'a [u8]>,
f: impl FnMut(&'a [u8]),
) -> Result<usize>
pub fn hdel_each<'a>( &mut self, key: &[u8], fields: impl Iterator<Item = &'a [u8]>, f: impl FnMut(&'a [u8]), ) -> Result<usize>
The same removal, naming each field that was actually there.
Keyspace::hdel is this with the names thrown away, which is all a
caller wants when it is only writing. The wire layer wants them because a
keyspace subscriber is told which fields went and the count does not say:
HDEL k a b that answers one has not said whether it was a or b, and
a field named twice is only reported once.
Sourcepub fn hexpire<'a, F>(
&mut self,
key: &[u8],
at: u64,
cond: Cond,
fields: impl Iterator<Item = &'a [u8]>,
f: F,
) -> Result<()>
pub fn hexpire<'a, F>( &mut self, key: &[u8], at: u64, cond: Cond, fields: impl Iterator<Item = &'a [u8]>, f: F, ) -> Result<()>
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.
Sourcepub fn httl<'a, F>(
&mut self,
key: &[u8],
fields: impl Iterator<Item = &'a [u8]>,
f: F,
) -> Result<()>
pub fn httl<'a, F>( &mut self, key: &[u8], fields: impl Iterator<Item = &'a [u8]>, f: F, ) -> Result<()>
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.
Sourcepub fn hpersist<'a, F>(
&mut self,
key: &[u8],
fields: impl Iterator<Item = &'a [u8]>,
f: F,
) -> Result<()>
pub fn hpersist<'a, F>( &mut self, key: &[u8], fields: impl Iterator<Item = &'a [u8]>, f: F, ) -> Result<()>
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.
Sourcepub fn hgetdel<'a, F>(
&mut self,
key: &[u8],
fields: impl Iterator<Item = &'a [u8]>,
f: F,
) -> Result<()>
pub fn hgetdel<'a, F>( &mut self, key: &[u8], fields: impl Iterator<Item = &'a [u8]>, f: F, ) -> Result<()>
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.
Sourcepub fn hgetex<'a, F>(
&mut self,
key: &[u8],
expire: Expire,
fields: impl Iterator<Item = &'a [u8]>,
f: F,
) -> Result<()>
pub fn hgetex<'a, F>( &mut self, key: &[u8], expire: Expire, fields: impl Iterator<Item = &'a [u8]>, f: F, ) -> Result<()>
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.
Sourcepub fn hsetex<'a>(
&mut self,
key: &[u8],
exists: Exists,
expire: Expire,
pairs: impl Iterator<Item = (&'a [u8], &'a [u8])> + Clone,
) -> Result<bool>
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.
Sourcepub fn hstrlen(&mut self, key: &[u8], field: &[u8]) -> Result<usize>
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.
Sourcepub fn hgetall<F>(&mut self, key: &[u8], f: F) -> Result<bool>
pub fn hgetall<F>(&mut self, key: &[u8], f: F) -> Result<bool>
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.
Sourcepub fn with_hash<R>(
&mut self,
key: &[u8],
f: impl FnOnce(Option<&Hash>) -> R,
) -> Result<R>
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.
Sourcepub fn hscan<F>(
&mut self,
key: &[u8],
cursor: Cursor,
count: usize,
f: F,
) -> Result<Cursor>
pub fn hscan<F>( &mut self, key: &[u8], cursor: Cursor, count: usize, f: F, ) -> Result<Cursor>
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.
Sourcepub fn hincrby(&mut self, key: &[u8], field: &[u8], by: i64) -> Result<i64>
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.
Sourcepub fn hincrbyfloat(&mut self, key: &[u8], field: &[u8], by: f64) -> Result<f64>
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.
Sourcepub fn hrandfield<R>(
&mut self,
key: &[u8],
f: impl FnOnce(Option<(Text<'_>, Text<'_>)>) -> R,
) -> Result<R>
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.
Sourcepub fn hrandfield_n<F>(&mut self, key: &[u8], count: i64, f: F) -> Result<()>
pub fn hrandfield_n<F>(&mut self, key: &[u8], count: i64, f: F) -> Result<()>
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
impl Keyspace
Sourcepub fn pfadd<'e, I>(&mut self, key: &[u8], eles: I) -> Result<bool>
pub fn pfadd<'e, I>(&mut self, key: &[u8], eles: I) -> Result<bool>
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.
Sourcepub fn pfcount<'k, I>(&mut self, keys: I) -> Result<u64>
pub fn pfcount<'k, I>(&mut self, keys: I) -> Result<u64>
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.
Sourcepub fn pfmerge<'k, I>(&mut self, dest: &'k [u8], srcs: I) -> Result<()>
pub fn pfmerge<'k, I>(&mut self, dest: &'k [u8], srcs: I) -> Result<()>
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.
Sourcepub fn pfgetreg(&mut self, key: &[u8], regs: &mut [u8; 16384]) -> Result<()>
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.
Sourcepub fn pftodense(&mut self, key: &[u8]) -> Result<bool>
pub fn pftodense(&mut self, key: &[u8]) -> Result<bool>
PFDEBUG TODENSE key, answering whether it had to convert anything.
Sourcepub fn pfencoding(&mut self, key: &[u8]) -> Result<Encoding>
pub fn pfencoding(&mut self, key: &[u8]) -> Result<Encoding>
PFDEBUG ENCODING key, which is sparse or dense.
Sourcepub fn pfdecode<T>(
&mut self,
key: &[u8],
run: impl FnOnce(&[u8]) -> T,
) -> Result<T>
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
impl Keyspace
Sourcepub fn export(&mut self, key: &[u8]) -> Option<Record>
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.
Sourcepub fn take(&mut self, key: &[u8]) -> Option<Record>
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.
Sourcepub fn import(&mut self, key: &[u8], rec: Record)
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
taken away first, record and body both, so this cannot leak a slab slot.
The record goes rather than being written over because this is a key
arriving and not a value changing. RESTORE, COPY and MOVE all land
here, and all three of them put a key somewhere it was not, even when
the name was taken and they were told to take it. The store forms are
the other case and they go through Keyspace::put_set and its
neighbours, which keep the record where it stands. A client watching for
keys that were not there before can tell the two apart, so they have to
be told apart here.
Sourcepub fn dump(&mut self, key: &[u8]) -> Option<Vec<u8>>
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.
Sourcepub fn restore(
&mut self,
key: &[u8],
payload: &[u8],
expire_at: Option<u64>,
replace: bool,
) -> Result<Moved, Bad>
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.
Sourcepub fn rename(&mut self, src: &[u8], dst: &[u8], only_if_new: bool) -> Moved
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.
Sourcepub fn copy(&mut self, src: &[u8], dst: &[u8], replace: bool) -> Moved
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.
Sourcepub fn touch<'k>(&mut self, keys: impl Iterator<Item = &'k [u8]>) -> usize
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
impl Keyspace
Sourcepub fn with_clock(clock: Clock) -> Keyspace
pub fn with_clock(clock: Clock) -> Keyspace
An empty database on a clock of the caller’s choosing.
Sourcepub const fn seed(&mut self, seed: u64)
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.
Sourcepub const fn random(&mut self) -> u64
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.
Sourcepub const fn policy(&self) -> Policy
pub const fn policy(&self) -> Policy
What this database would evict, which is CONFIG GET maxmemory-policy.
Sourcepub fn set_policy(&mut self, policy: Policy)
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.
Sourcepub const fn lfu(&self) -> Lfu
pub const fn lfu(&self) -> Lfu
The two numbers the LFU counter moves by, which are two CONFIG values.
Sourcepub fn idle_secs(&mut self, key: &[u8]) -> Option<u64>
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.
Sourcepub fn freq(&mut self, key: &[u8]) -> Option<u8>
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.
Sourcepub const fn limits(&self) -> &Limits
pub const fn limits(&self) -> &Limits
Where a set changes representation, which is three CONFIG values.
Sourcepub const fn set_limits(&mut self, limits: Limits)
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.
Sourcepub const fn hash_limits(&self) -> &Limits
pub const fn hash_limits(&self) -> &Limits
Where a hash changes representation, which is two CONFIG values.
Sourcepub const fn set_hash_limits(&mut self, limits: Limits)
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.
Sourcepub const fn list_limits(&self) -> &Limits
pub const fn list_limits(&self) -> &Limits
Where a list changes representation, which is one CONFIG value.
Sourcepub const fn set_list_limits(&mut self, limits: Limits)
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.
Sourcepub const fn stream_limits(&self) -> &Limits
pub const fn stream_limits(&self) -> &Limits
Where a stream starts a new node, which is two CONFIG values.
Sourcepub const fn set_stream_limits(&mut self, limits: Limits)
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.
Sourcepub const fn zset_limits(&self) -> &Limits
pub const fn zset_limits(&self) -> &Limits
Where a sorted set changes representation, which is two CONFIG values.
Sourcepub const fn set_zset_limits(&mut self, limits: Limits)
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.
Sourcepub fn len(&self) -> usize
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.
Sourcepub fn kind_of(&mut self, key: &[u8]) -> Option<Kind>
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.
Sourcepub fn set_encoding(&mut self, key: &[u8]) -> Option<Encoding>
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.
Sourcepub fn hash_encoding(&mut self, key: &[u8]) -> Option<Encoding>
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.
Sourcepub fn list_encoding(&mut self, key: &[u8]) -> Option<Encoding>
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.
Sourcepub fn zset_encoding(&mut self, key: &[u8]) -> Option<Encoding>
pub fn zset_encoding(&mut self, key: &[u8]) -> Option<Encoding>
How a sorted set is represented, or None if key is not one.
Sourcepub fn put_foreign(&mut self, key: &[u8], body: Box<dyn Foreign>) -> u32
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.
Sourcepub fn foreign(&mut self, key: &[u8]) -> Result<Option<&dyn Foreign>>
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.
Sourcepub fn foreign_mut(&mut self, key: &[u8]) -> Result<Option<&mut dyn Foreign>>
pub fn foreign_mut(&mut self, key: &[u8]) -> Result<Option<&mut dyn Foreign>>
The same, with a mutable borrow.
Sourcepub fn reap_foreign(&mut self, key: &[u8])
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.
Sourcepub fn type_name(&mut self, key: &[u8]) -> Option<&'static str>
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.
Sourcepub fn encoding_name(&mut self, key: &[u8]) -> Option<&'static str>
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.
Sourcepub fn set_expiry(&mut self, key: &[u8], at: Option<u64>) -> bool
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.
Sourcepub fn deadline_of(&mut self, key: &[u8]) -> Ask
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.
Sourcepub fn expire(&mut self, key: &[u8], at: u64, cond: Cond) -> Applied
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.
Sourcepub fn persist(&mut self, key: &[u8]) -> bool
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.
Sourcepub fn attach(&mut self, blocks: Store)
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.
Sourcepub const fn tier(&self) -> Option<&Tier<Store>>
pub const fn tier(&self) -> Option<&Tier<Store>>
The tier, if one was attached, for its counters.
Sourcepub const fn tier_mut(&mut self) -> Option<&mut Tier<Store>>
pub const fn tier_mut(&mut self) -> Option<&mut Tier<Store>>
The tier, mutably, for a caller driving a sweep.
Sourcepub fn demote(&mut self, key: &[u8]) -> Result<bool>
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.
Sourcepub fn store_bytes(&self) -> Option<u64>
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.
Sourcepub fn relieve(&mut self, shed: usize) -> Result<Relief>
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.
Sourcepub fn clear(&mut self)
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.
Sourcepub const fn expired_keys(&self) -> u64
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.
Sourcepub const fn expired_fields(&self) -> u64
pub const fn expired_fields(&self) -> u64
Hash fields reclaimed after their own deadline passed.
Redis calls this expired_subkeys and counts both halves into it, the
fields a command tripped over on its way past and the fields
Keyspace::field_expire_cycle went looking for.
Sourcepub const fn expired_fields_active(&self) -> u64
pub const fn expired_fields_active(&self) -> u64
The share of those the cycle found.
Redis calls this expired_subkeys_active. It is the number that says
whether the cycle is keeping up: a server where it stays far behind the
total is one where the fields are only going because clients keep asking
for the hashes they are in.
Sourcepub const fn evicted_keys(&self) -> u64
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.
Sourcepub const fn hits(&self) -> u64
pub const fn hits(&self) -> u64
Lookups a client’s read made here that found the key.
Redis calls this keyspace_hits in INFO stats, and with the number
below it is the hit rate every dashboard watching a cache is drawn from.
Only reads are in it: the lookup a SET does on its way to writing a key
is not a hit and its failing to find one is not a miss. See lookups
for how the two are told apart.
Sourcepub const fn misses(&self) -> u64
pub const fn misses(&self) -> u64
Lookups a client’s read made here that did not find the key.
Redis calls this keyspace_misses. A key found and refused for holding
the wrong type is not one of these, and neither is a field a hash does not
have: both of those are lookups that found the key.
Sourcepub const fn zero_stats(&mut self)
pub const fn zero_stats(&mut self)
Put every running total this database keeps back to zero, which is
CONFIG RESETSTAT.
All six and not just the two lookup counters, because Redis’s
resetServerStats clears the expiry and eviction totals in the same
breath. They are totals since the server started and this is the command
that says start again from here, so a dashboard that resets and then
watches has to see all of them move together or none of them.
Not the same thing as FLUSHDB, which throws away the keys and leaves
every one of these alone. See Keyspace::clear.
Sourcepub fn expires(&self) -> usize
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.
Sourcepub const fn set_samples(&mut self, samples: usize)
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.
Sourcepub fn evict_one(&mut self) -> bool
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.
Sourcepub fn memory_bytes(&self) -> usize
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.
Sourcepub fn settled_memory_bytes(&mut self) -> usize
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.
Sourcepub fn track_memory(&mut self, on: bool)
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.
Sourcepub fn compact_step(&mut self) -> Option<usize>
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.
Sourcepub fn compact_hard(&mut self) -> Option<usize>
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§impl Keyspace
impl Keyspace
Sourcepub fn push<'v>(
&mut self,
key: &[u8],
end: End,
values: impl Iterator<Item = &'v [u8]> + Clone,
) -> Result<usize>
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.
Sourcepub fn pushx<'v>(
&mut self,
key: &[u8],
end: End,
values: impl Iterator<Item = &'v [u8]> + Clone,
) -> Result<usize>
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.
Sourcepub fn pop(&mut self, key: &[u8], end: End) -> Result<Option<Vec<u8>>>
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.
Sourcepub fn pop_into<F>(
&mut self,
key: &[u8],
end: End,
count: usize,
f: F,
) -> Result<usize>
pub fn pop_into<F>( &mut self, key: &[u8], end: End, count: usize, f: F, ) -> Result<usize>
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.
Sourcepub fn llen(&mut self, key: &[u8]) -> Result<usize>
pub fn llen(&mut self, key: &[u8]) -> Result<usize>
LLEN key. Zero for a key that is not there.
Sourcepub fn lindex(&mut self, key: &[u8], index: i64) -> Result<Option<Element<'_>>>
pub fn lindex(&mut self, key: &[u8], index: i64) -> Result<Option<Element<'_>>>
LINDEX key index, counting from the back when the index is negative.
Sourcepub fn lrange(
&mut self,
key: &[u8],
start: i64,
stop: i64,
) -> Result<impl Iterator<Item = Element<'_>>>
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.
Sourcepub fn lset(&mut self, key: &[u8], index: i64, value: &[u8]) -> Result<()>
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.
Sourcepub fn linsert(
&mut self,
key: &[u8],
before: bool,
pivot: &[u8],
value: &[u8],
) -> Result<i64>
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.
Sourcepub fn lrem(&mut self, key: &[u8], count: i64, value: &[u8]) -> Result<usize>
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.
Sourcepub fn ltrim(&mut self, key: &[u8], start: i64, stop: i64) -> Result<()>
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.
Sourcepub fn lpos(
&mut self,
key: &[u8],
value: &[u8],
rank: i64,
count: usize,
maxlen: usize,
out: &mut Vec<usize>,
) -> Result<()>
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.
Sourcepub fn lpos_into<F>(
&mut self,
key: &[u8],
value: &[u8],
rank: i64,
count: usize,
maxlen: usize,
found: F,
) -> Result<usize>
pub fn lpos_into<F>( &mut self, key: &[u8], value: &[u8], rank: i64, count: usize, maxlen: usize, found: F, ) -> Result<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.
Sourcepub fn lmove(
&mut self,
src: &[u8],
dst: &[u8],
from: End,
to: End,
) -> Result<Option<&[u8]>>
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.
Sourcepub fn lmovem<F>(
&mut self,
src: &[u8],
dst: &[u8],
b: Movem,
f: F,
) -> Result<usize>
pub fn lmovem<F>( &mut self, src: &[u8], dst: &[u8], b: Movem, f: F, ) -> Result<usize>
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
impl Keyspace
Sourcepub fn sadd<'m>(
&mut self,
key: &[u8],
members: impl Iterator<Item = &'m [u8]> + Clone,
) -> Result<usize>
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.
Sourcepub fn srem<'m>(
&mut self,
key: &[u8],
members: impl Iterator<Item = &'m [u8]>,
) -> Result<usize>
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.
Sourcepub fn smismember<'m>(
&mut self,
key: &[u8],
members: impl Iterator<Item = &'m [u8]>,
) -> Result<Vec<bool>>
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.
Sourcepub fn scard(&mut self, key: &[u8]) -> Result<usize>
pub fn scard(&mut self, key: &[u8]) -> Result<usize>
SCARD key, which is zero for a key that is not there.
Sourcepub fn smembers(
&mut self,
key: &[u8],
) -> Result<Option<impl Iterator<Item = Member<'_>>>>
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.
Sourcepub fn spop(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>>
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.
Sourcepub fn spop_n(&mut self, key: &[u8], count: usize) -> Result<Vec<Vec<u8>>>
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.
Sourcepub fn spop_into<F>(&mut self, key: &[u8], count: usize, f: F) -> Result<usize>
pub fn spop_into<F>(&mut self, key: &[u8], count: usize, f: F) -> Result<usize>
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.
Sourcepub fn srandmember<R>(
&mut self,
key: &[u8],
f: impl FnOnce(Option<Member<'_>>) -> R,
) -> Result<R>
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.
Sourcepub fn srandmember_n<F>(&mut self, key: &[u8], count: i64, f: F) -> Result<()>
pub fn srandmember_n<F>(&mut self, key: &[u8], count: i64, f: F) -> Result<()>
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.
Sourcepub fn sscan<F>(
&mut self,
key: &[u8],
cursor: Cursor,
count: usize,
f: F,
) -> Result<Cursor>
pub fn sscan<F>( &mut self, key: &[u8], cursor: Cursor, count: usize, f: F, ) -> Result<Cursor>
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.
Sourcepub fn smove(
&mut self,
source: &[u8],
destination: &[u8],
member: &[u8],
) -> Result<bool>
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.
Sourcepub fn sinter<'k, F>(
&mut self,
keys: impl Iterator<Item = &'k [u8]>,
limit: usize,
f: F,
) -> Result<usize>
pub fn sinter<'k, F>( &mut self, keys: impl Iterator<Item = &'k [u8]>, limit: usize, f: F, ) -> Result<usize>
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.
Sourcepub fn sintercard<'k>(
&mut self,
keys: impl Iterator<Item = &'k [u8]>,
limit: usize,
) -> Result<usize>
pub fn sintercard<'k>( &mut self, keys: impl Iterator<Item = &'k [u8]>, limit: usize, ) -> Result<usize>
SINTERCARD numkeys key [key ...] [LIMIT limit].
Sourcepub fn sunion<'k, F>(
&mut self,
keys: impl Iterator<Item = &'k [u8]>,
limit: usize,
f: F,
) -> Result<usize>
pub fn sunion<'k, F>( &mut self, keys: impl Iterator<Item = &'k [u8]>, limit: usize, f: F, ) -> Result<usize>
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.
Sourcepub fn sunioncard<'k>(
&mut self,
keys: impl Iterator<Item = &'k [u8]>,
limit: usize,
) -> Result<usize>
pub fn sunioncard<'k>( &mut self, keys: impl Iterator<Item = &'k [u8]>, limit: usize, ) -> Result<usize>
SUNIONCARD numkeys key [key ...] [LIMIT limit].
Sourcepub fn sdiff<'k, F>(
&mut self,
keys: impl Iterator<Item = &'k [u8]>,
limit: usize,
f: F,
) -> Result<usize>
pub fn sdiff<'k, F>( &mut self, keys: impl Iterator<Item = &'k [u8]>, limit: usize, f: F, ) -> Result<usize>
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.
Sourcepub fn sdiffcard<'k>(
&mut self,
keys: impl Iterator<Item = &'k [u8]>,
limit: usize,
) -> Result<usize>
pub fn sdiffcard<'k>( &mut self, keys: impl Iterator<Item = &'k [u8]>, limit: usize, ) -> Result<usize>
SDIFFCARD numkeys key [key ...] [LIMIT limit].
Sourcepub fn sinterstore<'k>(
&mut self,
destination: &[u8],
keys: impl Iterator<Item = &'k [u8]>,
) -> Result<usize>
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.
Sourcepub fn sunionstore<'k>(
&mut self,
destination: &[u8],
keys: impl Iterator<Item = &'k [u8]>,
) -> Result<usize>
pub fn sunionstore<'k>( &mut self, destination: &[u8], keys: impl Iterator<Item = &'k [u8]>, ) -> Result<usize>
SUNIONSTORE destination key [key ...].
Sourcepub fn sdiffstore<'k>(
&mut self,
destination: &[u8],
keys: impl Iterator<Item = &'k [u8]>,
) -> Result<usize>
pub fn sdiffstore<'k>( &mut self, destination: &[u8], keys: impl Iterator<Item = &'k [u8]>, ) -> Result<usize>
SDIFFSTORE destination key [key ...].
Sourcepub fn with_set<R>(
&mut self,
key: &[u8],
f: impl FnOnce(Option<&Set>) -> R,
) -> Result<R>
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
impl Keyspace
Sourcepub fn stream(&mut self, key: &[u8]) -> Result<Option<&Stream>>
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.
Sourcepub fn stream_mut(&mut self, key: &[u8]) -> Result<Option<&mut Stream>>
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.
Sourcepub fn xadd(
&mut self,
key: &[u8],
id: Add,
fields: &[(&[u8], &[u8])],
trim: Trim,
mkstream: bool,
now: u64,
) -> Result<Option<Id>>
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.
Sourcepub fn xadd_trimmed(
&mut self,
key: &[u8],
id: Add,
fields: &[(&[u8], &[u8])],
trim: Trim,
mkstream: bool,
now: u64,
) -> Result<Option<(Id, u64)>>
pub fn xadd_trimmed( &mut self, key: &[u8], id: Add, fields: &[(&[u8], &[u8])], trim: Trim, mkstream: bool, now: u64, ) -> Result<Option<(Id, u64)>>
The same write, saying how many entries the trim behind it took.
Keyspace::xadd is this with that count dropped, which is all a caller
wants when it is only writing. The wire layer wants it because a trim
that removed something is a second keyspace notification and a trim that
found nothing over the threshold is not, and the ID that comes back says
nothing either way.
§Errors
The same as Keyspace::xadd.
Sourcepub fn xdel(&mut self, key: &[u8], ids: impl Iterator<Item = Id>) -> Result<u64>
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.
Sourcepub fn xdelex<F>(
&mut self,
key: &[u8],
refs: Refs,
ids: impl Iterator<Item = Id>,
f: F,
) -> Result<u64>
pub fn xdelex<F>( &mut self, key: &[u8], refs: Refs, ids: impl Iterator<Item = Id>, f: F, ) -> Result<u64>
XDELEX key [KEEPREF|DELREF|ACKED] IDS numids id [id ...].
The callback gets what became of each ID, in the order they were given,
and the answer is how many entries left the log. 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.
Sourcepub fn xackdel<F>(
&mut self,
key: &[u8],
group: &[u8],
refs: Refs,
ids: impl Iterator<Item = Id>,
f: F,
) -> Result<u64>
pub fn xackdel<F>( &mut self, key: &[u8], group: &[u8], refs: Refs, ids: impl Iterator<Item = Id>, f: F, ) -> Result<u64>
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.
The count that comes back is how many entries left the log, which is not
how many IDs answered Fate::Gone. See Stream::ack_delete.
§Errors
Code::WrongType for a key holding something else.
Sourcepub fn xnack(
&mut self,
key: &[u8],
group: &[u8],
retry: Retry,
force: bool,
ids: impl Iterator<Item = Id>,
) -> Result<Option<u64>>
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.
Sourcepub fn xtrim(&mut self, key: &[u8], trim: Trim) -> Result<u64>
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.
Sourcepub fn xsetid(
&mut self,
key: &[u8],
last: Id,
added: Option<u64>,
max_deleted: Option<Id>,
) -> Result<()>
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.
Sourcepub fn xrange_into<F>(
&mut self,
key: &[u8],
start: Id,
end: Id,
count: Option<usize>,
rev: bool,
f: F,
) -> Result<usize>
pub fn xrange_into<F>( &mut self, key: &[u8], start: Id, end: Id, count: Option<usize>, rev: bool, f: F, ) -> Result<usize>
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.
Sourcepub fn xread_into<F>(
&mut self,
key: &[u8],
after: Id,
count: Option<usize>,
f: F,
) -> Result<usize>
pub fn xread_into<F>( &mut self, key: &[u8], after: Id, count: Option<usize>, f: F, ) -> Result<usize>
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.
Sourcepub fn xgroup_create(
&mut self,
key: &[u8],
group: &[u8],
at: Start,
mkstream: bool,
read: Option<u64>,
) -> Result<bool>
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.
Sourcepub fn xgroup_destroy(&mut self, key: &[u8], group: &[u8]) -> Result<bool>
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.
Sourcepub fn xgroup_setid(
&mut self,
key: &[u8],
group: &[u8],
at: Start,
read: Option<u64>,
) -> Result<Option<()>>
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.
Sourcepub fn xgroup_create_consumer(
&mut self,
key: &[u8],
group: &[u8],
consumer: &[u8],
now: u64,
) -> Result<Option<bool>>
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.
Sourcepub fn xgroup_del_consumer(
&mut self,
key: &[u8],
group: &[u8],
consumer: &[u8],
) -> Result<Option<u64>>
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.
Sourcepub fn xreadgroup_into<F>(
&mut self,
key: &[u8],
want: Read<'_>,
now: u64,
f: F,
) -> Result<Option<usize>>
pub fn xreadgroup_into<F>( &mut self, key: &[u8], want: Read<'_>, now: u64, f: F, ) -> Result<Option<usize>>
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.
Sourcepub fn xack(
&mut self,
key: &[u8],
group: &[u8],
ids: impl Iterator<Item = Id>,
) -> Result<u64>
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.
Sourcepub fn xpending_into<F>(
&mut self,
key: &[u8],
group: &[u8],
want: Filter,
now: u64,
f: F,
) -> Result<Option<usize>>
pub fn xpending_into<F>( &mut self, key: &[u8], group: &[u8], want: Filter, now: u64, f: F, ) -> Result<Option<usize>>
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.
Sourcepub fn xclaim(
&mut self,
key: &[u8],
ids: &[Id],
how: Claim<'_>,
now: u64,
gone: &mut Vec<Id>,
) -> Result<Option<Vec<Id>>>
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.
Sourcepub fn xautoclaim(
&mut self,
key: &[u8],
start: Id,
how: Claim<'_>,
count: usize,
now: u64,
gone: &mut Vec<Id>,
) -> Result<Option<(Option<Id>, Vec<Id>)>>
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.
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.
Sourcepub fn get(&mut self, key: &[u8]) -> Result<Option<Str<'_>>>
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.
Sourcepub fn mget<'a>(&'a mut self, keys: &[&[u8]]) -> Vec<Option<Str<'a>>>
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.
Sourcepub fn mget_one(&mut self, key: &[u8]) -> Option<Str<'_>>
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.
Sourcepub fn strlen(&mut self, key: &[u8]) -> Result<usize>
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.
Sourcepub fn exists(&mut self, key: &[u8]) -> bool
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.
Sourcepub fn encoding(&mut self, key: &[u8]) -> Option<Encoding>
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.
Sourcepub fn expire_at(&mut self, key: &[u8]) -> Option<u64>
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.
Sourcepub fn getrange(
&mut self,
key: &[u8],
start: i64,
end: i64,
) -> Result<Cow<'_, [u8]>>
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.
Sourcepub fn set(
&mut self,
key: &[u8],
val: &[u8],
opts: SetOptions<'_>,
) -> Result<SetOutcome>
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.
Sourcepub fn set_with<F>(
&mut self,
key: &[u8],
val: &[u8],
opts: SetOptions<'_>,
previous: F,
) -> Result<SetOutcome>
pub fn set_with<F>( &mut self, key: &[u8], val: &[u8], opts: SetOptions<'_>, previous: F, ) -> Result<SetOutcome>
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.
Sourcepub fn set_plain(&mut self, key: &[u8], val: &[u8]) -> Result<()>
pub fn set_plain(&mut self, key: &[u8], val: &[u8]) -> Result<()>
SET key value, with nothing else asked for.
Sourcepub fn setnx(&mut self, key: &[u8], val: &[u8]) -> Result<bool>
pub fn setnx(&mut self, key: &[u8], val: &[u8]) -> Result<bool>
SETNX key value, which answers whether it stored.
Sourcepub fn setex(&mut self, key: &[u8], seconds: i64, val: &[u8]) -> Result<()>
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.
Sourcepub fn psetex(&mut self, key: &[u8], millis: i64, val: &[u8]) -> Result<()>
pub fn psetex(&mut self, key: &[u8], millis: i64, val: &[u8]) -> Result<()>
PSETEX key milliseconds value.
Sourcepub fn getset(&mut self, key: &[u8], val: &[u8]) -> Result<Option<Vec<u8>>>
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.
Sourcepub fn getdel_with<F>(&mut self, key: &[u8], f: F) -> Result<bool>
pub fn getdel_with<F>(&mut self, key: &[u8], f: F) -> Result<bool>
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.
Sourcepub fn getex(&mut self, key: &[u8], expire: Expire) -> Result<Option<Str<'_>>>
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.
Sourcepub fn del(&mut self, key: &[u8]) -> bool
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.
Sourcepub fn mset<'k>(
&mut self,
pairs: impl Iterator<Item = (&'k [u8], &'k [u8])> + Clone,
) -> Result<()>
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.
Sourcepub fn msetnx<'k>(
&mut self,
pairs: impl Iterator<Item = (&'k [u8], &'k [u8])> + Clone,
) -> Result<bool>
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.
Sourcepub fn append(&mut self, key: &[u8], tail: &[u8]) -> Result<usize>
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.
Sourcepub fn setrange(
&mut self,
key: &[u8],
offset: usize,
val: &[u8],
) -> Result<usize>
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.
Sourcepub fn decrby(&mut self, key: &[u8], by: i64) -> Result<i64>
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.
Sourcepub fn incrby(&mut self, key: &[u8], by: i64) -> Result<i64>
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.
Sourcepub fn incrbyfloat(&mut self, key: &[u8], by: f64) -> Result<f64>
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.
Sourcepub fn msetex<'k>(
&mut self,
pairs: impl Iterator<Item = (&'k [u8], &'k [u8])> + Clone,
exists: Exists,
expire: Expire,
) -> Result<bool>
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.
Sourcepub fn delex(&mut self, key: &[u8], compare: Option<Compare<'_>>) -> bool
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.
Sourcepub fn digest(&mut self, key: &[u8]) -> Result<Option<u64>>
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.
Sourcepub fn increx(&mut self, key: &[u8], opts: IncrEx) -> Result<Counted>
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.
Sourcepub fn string_copy(&mut self, key: &[u8]) -> Result<Vec<u8>>
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.
Sourcepub fn lcs(&mut self, a: &[u8], b: &[u8]) -> Result<Vec<u8>>
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.
Sourcepub fn lcs_idx(&mut self, a: &[u8], b: &[u8], minmatchlen: u32) -> Result<Idx>
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
impl Keyspace
Sourcepub fn scan(
&mut self,
from: KeyCursor,
budget: usize,
ty: Option<Kind>,
out: impl FnMut(&[u8]),
) -> KeyCursor
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.
Sourcepub fn keys(&mut self, out: impl FnMut(&[u8]))
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.
Sourcepub fn random_key(&mut self) -> Option<&[u8]>
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
impl Keyspace
Sourcepub fn zadd<'m, I>(&mut self, key: &[u8], pairs: I, opts: ZAdd) -> Result<usize>
pub fn zadd<'m, I>(&mut self, key: &[u8], pairs: I, opts: ZAdd) -> Result<usize>
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.
Sourcepub fn zadd_counts<'m, I>(
&mut self,
key: &[u8],
pairs: I,
opts: ZAdd,
) -> Result<(usize, usize)>
pub fn zadd_counts<'m, I>( &mut self, key: &[u8], pairs: I, opts: ZAdd, ) -> Result<(usize, usize)>
The same write, with both halves of the count kept apart.
CH folds the two together and the reply only ever wants one number, so
Keyspace::zadd is this with the fold applied. The wire layer wants
them apart because a keyspace notification goes out when anything at all
happened, which is a different question from the one the reply answers:
ZADD k 5 m on a member already sitting at four says one either way, and
ZADD k 4 m says zero and still counts as a write.
§Errors
A member too long for the limit and a score that is not a number, both checked across every pair before the first one is stored.
Sourcepub fn zincrby(
&mut self,
key: &[u8],
member: &[u8],
by: f64,
opts: ZAdd,
) -> Result<Option<f64>>
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.
Sourcepub fn zmscore<'m>(
&mut self,
key: &[u8],
members: impl Iterator<Item = &'m [u8]>,
out: &mut Vec<Option<f64>>,
) -> Result<()>
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.
Sourcepub fn zrem<'m>(
&mut self,
key: &[u8],
members: impl Iterator<Item = &'m [u8]>,
) -> Result<usize>
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.
Sourcepub fn zrank(
&mut self,
key: &[u8],
member: &[u8],
rev: bool,
) -> Result<Option<(usize, f64)>>
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.
Sourcepub fn zwindow(&mut self, key: &[u8], q: &Query<'_>) -> Result<Window>
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.
Sourcepub fn zwalk<F>(&mut self, key: &[u8], w: Window, f: F) -> Result<()>
pub fn zwalk<F>(&mut self, key: &[u8], w: Window, f: F) -> Result<()>
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.
Sourcepub fn zcount(&mut self, key: &[u8], q: &Query<'_>) -> Result<usize>
pub fn zcount(&mut self, key: &[u8], q: &Query<'_>) -> Result<usize>
ZCOUNT, ZLEXCOUNT, and the count half of any other range command.
Sourcepub fn zremrange(&mut self, key: &[u8], q: &Query<'_>) -> Result<usize>
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.
Sourcepub fn zpop<F>(
&mut self,
key: &[u8],
end: From,
count: usize,
f: F,
) -> Result<usize>
pub fn zpop<F>( &mut self, key: &[u8], end: From, count: usize, f: F, ) -> Result<usize>
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.
Sourcepub fn zpop_one(
&mut self,
key: &[u8],
end: From,
) -> Result<Option<(Vec<u8>, f64)>>
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.
Sourcepub fn zrandmember<F>(&mut self, key: &[u8], count: i64, f: F) -> Result<usize>
pub fn zrandmember<F>(&mut self, key: &[u8], count: i64, f: F) -> Result<usize>
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.
Sourcepub fn zscan<F>(
&mut self,
key: &[u8],
cursor: Cursor,
count: usize,
f: F,
) -> Result<Cursor>
pub fn zscan<F>( &mut self, key: &[u8], cursor: Cursor, count: usize, f: F, ) -> Result<Cursor>
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.
Sourcepub fn zsetop<'k, F>(
&mut self,
op: Op,
keys: impl Iterator<Item = &'k [u8]>,
weights: &[f64],
agg: Aggregate,
f: F,
) -> Result<usize>
pub fn zsetop<'k, F>( &mut self, op: Op, keys: impl Iterator<Item = &'k [u8]>, weights: &[f64], agg: Aggregate, f: F, ) -> Result<usize>
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.
Sourcepub fn zsetop_store<'k>(
&mut self,
op: Op,
destination: &[u8],
keys: impl Iterator<Item = &'k [u8]>,
weights: &[f64],
agg: Aggregate,
) -> Result<usize>
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.
Sourcepub fn zintercard<'k>(
&mut self,
keys: impl Iterator<Item = &'k [u8]>,
limit: usize,
) -> Result<usize>
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.
Sourcepub fn zrangestore(
&mut self,
destination: &[u8],
source: &[u8],
q: &Query<'_>,
) -> Result<usize>
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.