use kevy_verbs::{Effect, Verb};
use super::Args;
use crate::KevyResult;
use crate::store::{Inner, Store, commit_write, ensure_writable};
pub(super) const SERVER_ONLY: &[&[u8]] = &[
b"BLPOP",
b"BRPOP",
b"BRPOPLPUSH",
b"BZPOPMIN",
b"FLUSHDB",
b"HMSET",
b"LMOVE",
b"LPOS",
b"PSETEX",
b"RPOPLPUSH",
b"SETEX",
b"SSCAN",
];
pub(super) fn dispatch(s: &Store, up: &[u8], argv: &[Vec<u8>], out: &mut Vec<u8>) -> bool {
if [kevy_resp::ops_table::CONSUMER_SEEN, kevy_resp::ops_table::PENDING]
.iter()
.any(|v| up == v.as_bytes())
{
kevy_resp::encode_error(out, kevy_verbs::aof::INTERNAL_REFUSAL);
return true;
}
let Some(v) = kevy_verbs::verb(up) else {
return false;
};
if SERVER_ONLY.contains(&up) || !cfg!(feature = "streams-geo") && kevy_verbs::is_streams_geo(up)
{
return false;
}
run(s, v, up, argv, out);
true
}
fn run(s: &Store, v: &Verb, up: &[u8], argv: &[Vec<u8>], out: &mut Vec<u8>) {
if v.write
&& let Err(e) = ensure_writable(s)
{
return super::kevy_err(out, &e);
}
#[cfg(feature = "streams-geo")]
if let Some(msg) = refusal(s, up, argv) {
return kevy_resp::encode_error(out, msg);
}
let args = Args::new(argv);
let mut g = s.wshard(crate::verb_keys::shard_key(up, &args).unwrap_or_default());
let mark = out.len();
let effect = kevy_verbs::exec(&mut g.store, up, &args, out);
if out.get(mark) == Some(&b'-') {
return; }
let recorded = match effect {
Some(Effect::Write) => record(&mut g, argv, None),
Some(Effect::Record(frame)) => {
let parts: Vec<&[u8]> = frame.iter().map(Vec::as_slice).collect();
commit_write(&mut g, &parts)
}
Some(Effect::RecordId(at, id)) => {
let mut buf = [0u8; 41];
let id = kevy_verbs::aof::id_bytes(&mut buf, id);
record(&mut g, argv, Some((at, id)))
}
Some(
e @ (Effect::RecordClaim(_)
| Effect::RecordRead(..)
| Effect::RecordReads(_)
| Effect::RecordHistory(_)
| Effect::RecordAdd(..)
| Effect::RecordSeen
| Effect::RecordGroup),
) => record_outcome(&mut g, argv, &e),
None | Some(Effect::Read | Effect::Unchanged | Effect::Skip) => Ok(()),
};
if let Err(e) = recorded {
out.truncate(mark);
super::kevy_err(out, &e);
}
}
#[cfg(feature = "streams-geo")]
fn refusal(s: &Store, up: &[u8], argv: &[Vec<u8>]) -> Option<&'static str> {
if !kevy_verbs::is_streams_geo(up) {
return None;
}
let args = Args::new(argv);
if crate::verb_keys::blocks(up, &args) {
return Some("ERR the embedded engine cannot block; call without BLOCK");
}
let n = s.shards.len();
let apart = |a: &[u8], b: &[u8]| crate::shard::shard_idx(a, n) != crate::shard::shard_idx(b, n);
let split = match up {
_ if n == 1 => false,
b"XREAD" | b"XREADGROUP" => crate::verb_keys::stream_keys(up, &args)
.is_some_and(|keys| keys.clone().any(|i| apart(&argv[keys.start], &argv[i]))),
_ => kevy_verbs::geo::store_keys(up, &args).is_some_and(|(src, dst)| apart(&src, &dst)),
};
split.then_some("CROSSSLOT Keys in request don't hash to the same slot")
}
fn record(g: &mut Inner, argv: &[Vec<u8>], swap: Option<(usize, &[u8])>) -> KevyResult<()> {
const INLINE: usize = 8;
if argv.len() <= INLINE {
let mut parts: [&[u8]; INLINE] = [&[]; INLINE];
for (i, (slot, a)) in parts.iter_mut().zip(argv).enumerate() {
*slot = swapped(swap, i, a);
}
commit_write(g, &parts[..argv.len()])?;
} else {
let parts: Vec<&[u8]> = argv.iter().enumerate().map(|(i, a)| swapped(swap, i, a)).collect();
commit_write(g, &parts)?;
}
for f in kevy_verbs::aof::ttl_followup(&g.store, &Args::new(argv)) {
let parts: Vec<&[u8]> = (0..f.len()).map(|i| &f[i]).collect();
commit_write(g, &parts)?;
}
Ok(())
}
fn swapped<'a>(swap: Option<(usize, &'a [u8])>, i: usize, a: &'a [u8]) -> &'a [u8] {
match swap {
Some((at, v)) if at == i => v,
_ => a,
}
}
fn record_outcome(g: &mut Inner, argv: &[Vec<u8>], outcome: &Effect) -> KevyResult<()> {
if !crate::store_glue::records_writes(g) {
return record(g, argv, None);
}
for f in kevy_verbs::aof::deferred_frames(&g.store, &Args::new(argv), outcome) {
let parts: Vec<&[u8]> = (0..f.len()).map(|i| &f[i]).collect();
commit_write(g, &parts)?;
}
Ok(())
}