use std::collections::HashMap;
use crate::Commands;
use crate::message::{Agg, Gathered, Inbound, Op, Part, SmallReply};
use crate::message_kinds::GatherKind;
use crate::reduce::drain_front;
use crate::shard::Shard;
use kevy_resp::ArgvView;
use kevy_store::BitOp;
pub(crate) struct BitOpCall {
op: BitOp,
dst: Vec<u8>,
keys: Vec<Vec<u8>>,
}
pub(crate) fn parse_bitop<A: ArgvView + ?Sized>(args: &A) -> Result<BitOpCall, &'static str> {
if args.len() < 4 {
return Err("-ERR wrong number of arguments for 'bitop' command\r\n");
}
let op = match args[1].to_ascii_uppercase().as_slice() {
b"AND" => BitOp::And,
b"OR" => BitOp::Or,
b"XOR" => BitOp::Xor,
b"NOT" => BitOp::Not,
_ => return Err("-ERR syntax error\r\n"),
};
let srcs: Vec<Vec<u8>> = (3..args.len()).map(|i| args[i].to_vec()).collect();
if op == BitOp::Not && srcs.len() != 1 {
return Err("-ERR BITOP NOT must be called with a single source key.\r\n");
}
Ok(BitOpCall { op, dst: args[2].to_vec(), keys: srcs })
}
impl<C: Commands> Shard<C> {
pub(crate) fn start_bitop<A: ArgvView + ?Sized>(&mut self, conn_id: u64, seq: u64, args: &A) {
let BitOpCall { op, dst, keys } = match parse_bitop(args) {
Ok(t) => t,
Err(e) => return self.fold_bitop_reply(conn_id, seq, e.as_bytes().to_vec()),
};
let mut by_shard: HashMap<usize, Vec<Vec<u8>>> = HashMap::new();
for k in &keys {
by_shard.entry(self.shard_of(k)).or_default().push(k.clone());
}
let targets: Vec<(usize, Op)> = by_shard
.into_iter()
.map(|(s, ks)| (s, Op::Gather(GatherKind::StrStrict, ks)))
.collect();
let agg = Agg::BitOpGather { op, dst, keys, got: HashMap::new() };
self.push_pending_slot(conn_id, targets.len() as u32, agg, false);
for (shard, op) in targets {
if shard == self.id {
let part = self.exec_op(op);
self.fold(conn_id, seq, part);
} else {
let origin = self.id;
self.send_to(shard, Inbound::Request { origin, conn: conn_id, seq, op });
}
}
}
pub(crate) fn op_bitop_result(&mut self, key: Vec<u8>, value: &[u8]) -> Part {
let len = value.len() as i64;
let mut argv = kevy_resp::Argv::default();
if value.is_empty() {
self.store.del(&[&key[..]]);
argv.push(b"DEL");
argv.push(&key);
} else {
self.store.set_slice(&key, value, None, false, false);
argv.push(b"SET");
argv.push(&key);
argv.push(value);
}
self.note_key_mutated(&key);
self.log_effect(&argv);
Part::Int(len)
}
pub(crate) fn finalize_bitop_agg(&mut self, conn_id: u64, seq: u64, agg: Agg) {
let Agg::BitOpGather { op, dst, keys, mut got } = agg else { return };
let mut srcs: Vec<Vec<u8>> = Vec::with_capacity(keys.len());
for k in &keys {
match got.remove(k) {
Some(Gathered::WrongType) => {
let e =
b"-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
return self.fill_bitop_slot(conn_id, seq, e.to_vec());
}
Some(Gathered::Str(v)) => srcs.push(v.unwrap_or_default()),
_ => srcs.push(Vec::new()),
}
}
let max_len = srcs.iter().map(Vec::len).max().unwrap_or(0);
let value =
if max_len == 0 { Vec::new() } else { kevy_store::bitop_combine(op, &srcs, max_len) };
let dst_shard = self.shard_of(&dst);
self.rearm_bitop_slot(conn_id, seq);
let put = Op::BitOpResult { key: dst, value };
if dst_shard == self.id {
let part = self.exec_op(put);
self.fold(conn_id, seq, part);
} else {
let origin = self.id;
self.send_to(dst_shard, Inbound::Request { origin, conn: conn_id, seq, op: put });
}
}
fn rearm_bitop_slot(&mut self, conn_id: u64, seq: u64) {
if let Some(c) = self.conns.get_mut(&conn_id) {
let idx = (seq - c.next_emit) as usize;
if let Some(slot) = c.pending.get_mut(idx) {
slot.remaining = 1;
slot.agg = Agg::SumInt(0);
}
}
}
fn fill_bitop_slot(&mut self, conn_id: u64, seq: u64, bytes: Vec<u8>) {
if let Some(c) = self.conns.get_mut(&conn_id) {
let idx = (seq - c.next_emit) as usize;
if let Some(slot) = c.pending.get_mut(idx) {
slot.done = Some(SmallReply::from_vec(bytes));
}
drain_front(c);
}
}
fn fold_bitop_reply(&mut self, conn_id: u64, seq: u64, reply: Vec<u8>) {
self.push_pending_slot(conn_id, 1, Agg::First(None), false);
self.fold(conn_id, seq, Part::Reply(SmallReply::from_vec(reply)));
}
}