use crate::ValkeyResult;
use crate::cmd::Cmd;
use crate::executor::CommandExecutor;
use crate::value::FromValkeyValue;
use crate::value::ValkeyValue;
use crate::write::ToValkeyArgs;
use crate::write::ValkeyWrite;
use async_trait::async_trait;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitmapIndexType {
Byte,
Bit,
}
impl BitmapIndexType {
fn as_arg(&self) -> &'static str {
match self {
BitmapIndexType::Byte => "BYTE",
BitmapIndexType::Bit => "BIT",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitEncoding {
Signed(u32),
Unsigned(u32),
}
impl ToValkeyArgs for BitEncoding {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
match self {
BitEncoding::Signed(n) => out.write_arg_fmt(format_args!("i{n}")),
BitEncoding::Unsigned(n) => out.write_arg_fmt(format_args!("u{n}")),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitFieldOffset {
Bit(u64),
Multiplier(u64),
}
impl ToValkeyArgs for BitFieldOffset {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
match self {
BitFieldOffset::Bit(n) => out.write_arg_fmt(n),
BitFieldOffset::Multiplier(n) => out.write_arg_fmt(format_args!("#{n}")),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitOverflow {
Wrap,
Sat,
Fail,
}
impl ToValkeyArgs for BitOverflow {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
out.write_arg(match self {
BitOverflow::Wrap => b"WRAP".as_slice(),
BitOverflow::Sat => b"SAT".as_slice(),
BitOverflow::Fail => b"FAIL".as_slice(),
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitFieldSubcommand {
Get {
encoding: BitEncoding,
offset: BitFieldOffset,
},
Set {
encoding: BitEncoding,
offset: BitFieldOffset,
value: i64,
},
IncrBy {
encoding: BitEncoding,
offset: BitFieldOffset,
increment: i64,
},
Overflow(BitOverflow),
}
impl ToValkeyArgs for BitFieldSubcommand {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
match self {
BitFieldSubcommand::Get { encoding, offset } => {
out.write_arg(b"GET");
encoding.write_valkey_args(out);
offset.write_valkey_args(out);
}
BitFieldSubcommand::Set {
encoding,
offset,
value,
} => {
out.write_arg(b"SET");
encoding.write_valkey_args(out);
offset.write_valkey_args(out);
value.write_valkey_args(out);
}
BitFieldSubcommand::IncrBy {
encoding,
offset,
increment,
} => {
out.write_arg(b"INCRBY");
encoding.write_valkey_args(out);
offset.write_valkey_args(out);
increment.write_valkey_args(out);
}
BitFieldSubcommand::Overflow(overflow) => {
out.write_arg(b"OVERFLOW");
overflow.write_valkey_args(out);
}
}
}
}
#[async_trait]
pub trait BitmapCommands: CommandExecutor {
async fn bitpos<K: ToValkeyArgs + Send>(&self, key: K, bit: u8) -> ValkeyResult<i64> {
let mut cmd = Cmd::new();
cmd.arg("BITPOS").arg(key).arg(bit);
i64::from_owned_valkey_value(self.execute_command(cmd, None).await?)
}
async fn bitpos_range<K: ToValkeyArgs + Send>(
&self,
key: K,
bit: u8,
start: i64,
end: i64,
index_type: Option<BitmapIndexType>,
) -> ValkeyResult<i64> {
let mut cmd = Cmd::new();
cmd.arg("BITPOS").arg(key).arg(bit).arg(start).arg(end);
if let Some(it) = index_type {
cmd.arg(it.as_arg());
}
i64::from_owned_valkey_value(self.execute_command(cmd, None).await?)
}
async fn bitfield<K: ToValkeyArgs + Send>(
&self,
key: K,
subcommands: &[BitFieldSubcommand],
) -> ValkeyResult<Vec<Option<i64>>> {
let mut cmd = Cmd::new();
cmd.arg("BITFIELD").arg(key);
cmd.arg(subcommands);
parse_bitfield(self.execute_command(cmd, None).await?)
}
async fn bitfield_readonly<K: ToValkeyArgs + Send>(
&self,
key: K,
subcommands: &[BitFieldSubcommand],
) -> ValkeyResult<Vec<Option<i64>>> {
let mut cmd = Cmd::new();
cmd.arg("BITFIELD_RO").arg(key);
cmd.arg(subcommands);
parse_bitfield(self.execute_command(cmd, None).await?)
}
}
impl<T: CommandExecutor + ?Sized> BitmapCommands for T {}
fn parse_bitfield(v: ValkeyValue) -> ValkeyResult<Vec<Option<i64>>> {
match v {
ValkeyValue::Nil => Ok(Vec::new()),
ValkeyValue::Array(items) => items
.into_iter()
.map(|it| match it {
ValkeyValue::Nil => Ok(None),
other => Ok(Some(i64::from_owned_valkey_value(other)?)),
})
.collect(),
other => Ok(vec![Some(i64::from_owned_valkey_value(other)?)]),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::assert_args;
#[test]
fn encoding_and_offset_args() {
assert_args(BitEncoding::Signed(8), &["i8"]);
assert_args(BitEncoding::Unsigned(16), &["u16"]);
assert_args(BitFieldOffset::Bit(5), &["5"]);
assert_args(BitFieldOffset::Multiplier(3), &["#3"]);
}
#[test]
fn overflow_args() {
assert_args(BitOverflow::Wrap, &["WRAP"]);
assert_args(BitOverflow::Sat, &["SAT"]);
assert_args(BitOverflow::Fail, &["FAIL"]);
}
#[test]
fn bitfield_subcommand_args() {
assert_args(
BitFieldSubcommand::Get {
encoding: BitEncoding::Unsigned(8),
offset: BitFieldOffset::Bit(0),
},
&["GET", "u8", "0"],
);
assert_args(
BitFieldSubcommand::Set {
encoding: BitEncoding::Signed(5),
offset: BitFieldOffset::Multiplier(1),
value: 12,
},
&["SET", "i5", "#1", "12"],
);
assert_args(
BitFieldSubcommand::IncrBy {
encoding: BitEncoding::Unsigned(4),
offset: BitFieldOffset::Bit(2),
increment: -3,
},
&["INCRBY", "u4", "2", "-3"],
);
assert_args(
BitFieldSubcommand::Overflow(BitOverflow::Sat),
&["OVERFLOW", "SAT"],
);
}
#[test]
fn parse_bitfield_handles_nil() {
let v = ValkeyValue::Array(vec![ValkeyValue::Int(1), ValkeyValue::Nil]);
assert_eq!(parse_bitfield(v).unwrap(), vec![Some(1), None]);
}
}