use yo_common::num::{parse_f64, parse_i64};
use yo_common::{Code, Error, Result};
use yo_kv::{Db, Foreign, Keyspace};
use yo_sketch::cms::{Cms, dims_from};
use super::args::{self, Args};
use super::table::Spec;
use crate::reply::Out;
const EXISTS: &[u8] = b"CMS: key already exists";
const MISSING: &[u8] = b"CMS: key does not exist";
const BAD_WIDTH: &[u8] = b"CMS: invalid width";
const BAD_DEPTH: &[u8] = b"CMS: invalid depth";
const BAD_ERROR: &[u8] = b"CMS: invalid overestimation value";
const BAD_PROB: &[u8] = b"CMS: invalid prob value";
const BAD_INIT: &[u8] = b"CMS: invalid init arguments";
const NO_MEMORY: &[u8] = b"CMS: Insufficient memory to create the key";
const BAD_NUMBER: &[u8] = b"CMS: Cannot parse number";
const NEGATIVE: &[u8] = b"CMS: Number cannot be negative";
const INCR_OVERFLOW: &[u8] = b"CMS: INCRBY overflow";
const BAD_NUMKEYS: &[u8] = b"CMS: invalid numkeys";
const NOT_POSITIVE: &[u8] = b"CMS: Number of keys must be positive";
const WRONG_KEYS: &[u8] = b"CMS: wrong number of keys";
const WRONG_WEIGHTS: &[u8] = b"CMS: wrong number of keys/weights";
const BAD_WEIGHT: &[u8] = b"CMS: invalid weight value";
const NOT_EQUAL: &[u8] = b"CMS: width/depth is not equal";
const MERGE_OVERFLOW: &[u8] = b"CMS: MERGE overflow";
const WRONG_KIND: &str = "Operation against a key holding the wrong kind of value";
#[derive(Debug)]
pub(super) struct CmsBody {
c: Cms,
}
impl Foreign for CmsBody {
fn type_name(&self) -> &'static str {
"CMSk-TYPE"
}
fn encoding(&self) -> &'static str {
"raw"
}
fn memory_bytes(&self) -> usize {
self.c.memory_bytes()
}
fn is_empty(&self) -> bool {
false
}
}
pub(super) fn execute(db: &Db, spec: &Spec, args: Args<'_>, out: &mut Out) -> Result<()> {
match spec.name {
"cms.initbydim" => initbydim(db, args, out),
"cms.initbyprob" => initbyprob(db, args, out),
"cms.incrby" => incrby(db, args, out),
"cms.query" => query(db, args, out),
"cms.merge" => merge(db, args, out),
"cms.info" => info(db, args, out),
other => unreachable!("{other} is not a count min sketch command"),
}
}
fn initbydim(db: &Db, args: Args<'_>, out: &mut Out) -> Result<()> {
let key = args.get(1);
if db.hold(key).kind_of(key).is_some() {
out.error(EXISTS);
return Ok(());
}
let Some(width) = positive(args.get(2)) else {
out.error(BAD_WIDTH);
return Ok(());
};
let Some(depth) = positive(args.get(3)) else {
out.error(BAD_DEPTH);
return Ok(());
};
build(db, key, width, depth, out);
Ok(())
}
fn initbyprob(db: &Db, args: Args<'_>, out: &mut Out) -> Result<()> {
let key = args.get(1);
if db.hold(key).kind_of(key).is_some() {
out.error(EXISTS);
return Ok(());
}
let Some(error) = fraction(args.get(2)) else {
out.error(BAD_ERROR);
return Ok(());
};
let Some(prob) = fraction(args.get(3)) else {
out.error(BAD_PROB);
return Ok(());
};
let Some((width, depth)) = dims_from(error, prob) else {
out.error(BAD_INIT);
return Ok(());
};
build(db, key, width, depth, out);
Ok(())
}
fn build(db: &Db, key: &[u8], width: u64, depth: u64, out: &mut Out) {
let Some(c) = Cms::new(width, depth) else {
out.error(NO_MEMORY);
return;
};
db.hold(key).put_foreign(key, Box::new(CmsBody { c }));
out.ok();
}
fn incrby(db: &Db, args: Args<'_>, out: &mut Out) -> Result<()> {
if !args.len().is_multiple_of(2) {
return Err(args::wrong_arity("cms.incrby"));
}
let mut stripe = db.hold(args.get(1));
let body = match write(&mut stripe, args.get(1))? {
Some(body) => body,
None => {
out.error(MISSING);
return Ok(());
}
};
for i in (3..args.len()).step_by(2) {
match parse_i64(args.get(i)) {
None => {
out.error(BAD_NUMBER);
return Ok(());
}
Some(n) if n < 0 => {
out.error(NEGATIVE);
return Ok(());
}
Some(_) => {}
}
}
out.array((args.len() - 2) / 2);
for i in (2..args.len()).step_by(2) {
let by = parse_i64(args.get(i + 1)).expect("every pair was parsed above");
let count = body.c.incr(args.get(i), by);
if count == u32::MAX {
out.error(INCR_OVERFLOW);
} else {
out.uint(u64::from(count));
}
}
Ok(())
}
fn query(db: &Db, args: Args<'_>, out: &mut Out) -> Result<()> {
let mut stripe = db.hold(args.get(1));
let Some(body) = read(&mut stripe, args.get(1))? else {
out.error(MISSING);
return Ok(());
};
out.array(args.len() - 2);
for i in 2..args.len() {
out.uint(u64::from(body.c.count_of(args.get(i))));
}
Ok(())
}
fn merge(db: &Db, args: Args<'_>, out: &mut Out) -> Result<()> {
let dest = args.get(1);
let shape = {
let mut stripe = db.hold(dest);
let Some(body) = read(&mut stripe, dest)? else {
out.error(MISSING);
return Ok(());
};
(body.c.width(), body.c.depth())
};
let (width, depth) = shape;
let Some(count) = parse_i64(args.get(2)) else {
out.error(BAD_NUMKEYS);
return Ok(());
};
if count <= 0 {
out.error(NOT_POSITIVE);
return Ok(());
}
let Ok(count) = usize::try_from(count) else {
out.error(WRONG_KEYS);
return Ok(());
};
let Some(after) = 3usize.checked_add(count).filter(|&at| at <= args.len()) else {
out.error(WRONG_KEYS);
return Ok(());
};
let weights = match args.opt(after) {
None => None,
Some(word) if args::is(word, b"weights") => Some(after + 1),
Some(_) => {
out.error(WRONG_KEYS);
return Ok(());
}
};
if let Some(first) = weights {
if args.len() - first != count {
out.error(WRONG_WEIGHTS);
return Ok(());
}
for i in first..args.len() {
if parse_i64(args.get(i)).is_none() {
out.error(BAD_WEIGHT);
return Ok(());
}
}
}
let weight_at = |n: usize| match weights {
Some(first) => parse_i64(args.get(first + n)).expect("every weight was parsed above"),
None => 1,
};
for i in 0..count {
let key = args.get(3 + i);
let mut stripe = db.hold(key);
match read(&mut stripe, key)? {
None => {
out.error(MISSING);
return Ok(());
}
Some(src) if src.c.width() != width || src.c.depth() != depth => {
out.error(NOT_EQUAL);
return Ok(());
}
Some(_) => {}
}
}
let mut acc = {
let mut stripe = db.hold(dest);
read(&mut stripe, dest)?
.expect("the destination is still there")
.c
.merge_start()
};
for i in 0..count {
let key = args.get(3 + i);
let mut stripe = db.hold(key);
let src = read(&mut stripe, key)?.expect("checked in the first pass");
if !src.c.merge_add(&mut acc, weight_at(i)) {
out.error(MERGE_OVERFLOW);
return Ok(());
}
}
let mut stripe = db.hold(dest);
let body = write(&mut stripe, dest)?.expect("the destination is still there");
body.c.merge_finish(acc);
out.ok();
Ok(())
}
fn info(db: &Db, args: Args<'_>, out: &mut Out) -> Result<()> {
let mut stripe = db.hold(args.get(1));
let Some(body) = read(&mut stripe, args.get(1))? else {
out.error(MISSING);
return Ok(());
};
out.map(3);
out.simple(b"width");
out.uint(body.c.width());
out.simple(b"depth");
out.uint(body.c.depth());
out.simple(b"count");
out.int(body.c.count());
Ok(())
}
fn positive(arg: &[u8]) -> Option<u64> {
parse_i64(arg).filter(|&n| n > 0).map(|n| n as u64)
}
fn fraction(arg: &[u8]) -> Option<f64> {
parse_f64(arg).filter(|&n| n > 0.0 && n < 1.0)
}
fn write<'k>(stripe: &'k mut Keyspace, key: &[u8]) -> Result<Option<&'k mut CmsBody>> {
match stripe.foreign_mut(key)? {
Some(body) => match body.downcast_mut::<CmsBody>() {
Some(body) => Ok(Some(body)),
None => Err(Error::new(Code::WrongType, WRONG_KIND)),
},
None => Ok(None),
}
}
fn read<'k>(stripe: &'k mut Keyspace, key: &[u8]) -> Result<Option<&'k CmsBody>> {
match stripe.foreign(key)? {
Some(body) => match body.downcast_ref::<CmsBody>() {
Some(body) => Ok(Some(body)),
None => Err(Error::new(Code::WrongType, WRONG_KIND)),
},
None => Ok(None),
}
}