use yo_common::num::{DOUBLE_MAX, parse_i64, write_g17};
use yo_common::{Code, Error, Result};
use yo_kv::arrays::{Aggregate, Grep, Op, Test, parse_grep_bound, parse_index, parse_seek_index};
use yo_kv::{ArrayElement, Keyspace};
use super::args::{self, Args};
use super::table::Spec;
use crate::reply::Out;
pub(super) fn execute(db: &mut Keyspace, spec: &Spec, args: Args<'_>, out: &mut Out) -> Result<()> {
match spec.name {
"arset" => {
let index = parse_index(args.get(2))?;
let values = (3..args.len()).map(|i| args.get(i));
out.uint(db.arset(args.get(1), index, values)?);
}
"armset" => {
if !args.len().is_multiple_of(2) {
return Err(args::wrong_arity(spec.name));
}
for i in (2..args.len()).step_by(2) {
parse_index(args.get(i))?;
}
let pairs = (2..args.len())
.step_by(2)
.map(|i| (parse_index(args.get(i)).unwrap_or(0), args.get(i + 1)));
out.uint(db.armset(args.get(1), pairs)?);
}
"arget" => {
let index = index_after_type(db, args.get(1), args.get(2))?;
match db.arget(args.get(1), index)? {
Some(e) => element(out, e),
None => out.nil(),
}
}
"armget" => {
for i in 2..args.len() {
index_after_type(db, args.get(1), args.get(i))?;
}
out.array(args.len() - 2);
let indices = (2..args.len()).map(|i| parse_index(args.get(i)).unwrap_or(0));
db.arget_into(args.get(1), indices, |el| reply(out, el))?;
}
"argetrange" => {
let start = parse_index(args.get(2))?;
let end = parse_index(args.get(3))?;
let mark = out.len();
let mut n = 0;
let len = db.argetrange(args.get(1), start, end, |el| {
reply(out, el);
n += 1;
})?;
debug_assert_eq!(len, n);
out.close_array(mark, usize::try_from(n).unwrap_or(usize::MAX));
}
"arlen" => out.uint(db.arlen(args.get(1))?),
"arcount" => out.uint(db.arcount(args.get(1))?),
"ardel" => {
for i in 2..args.len() {
parse_index(args.get(i))?;
}
let indices = (2..args.len()).map(|i| parse_index(args.get(i)).unwrap_or(0));
out.uint(db.ardel(args.get(1), indices)?);
}
"ardelrange" => {
if !args.len().is_multiple_of(2) {
return Err(args::wrong_arity(spec.name));
}
for i in 2..args.len() {
parse_index(args.get(i))?;
}
let ranges = (2..args.len()).step_by(2).map(|i| {
(
parse_index(args.get(i)).unwrap_or(0),
parse_index(args.get(i + 1)).unwrap_or(0),
)
});
out.uint(db.ardelrange(args.get(1), ranges)?);
}
"arinsert" => {
let values = (2..args.len()).map(|i| args.get(i));
out.uint(db.arinsert(args.get(1), values)?);
}
"arring" => {
let size =
parse_i64(args.get(2)).ok_or_else(|| Error::new(Code::Invalid, "invalid size"))?;
if size <= 0 {
return Err(Error::new(Code::Invalid, "size must be positive"));
}
let values = (3..args.len()).map(|i| args.get(i));
out.uint(db.arring(args.get(1), size as u64, values)?);
}
"arnext" => match db.arnext(args.get(1))? {
Some(index) => out.uint(index),
None => out.nil(),
},
"arseek" => {
let index = parse_seek_index(args.get(2))?;
out.uint(u64::from(db.arseek(args.get(1), index)?));
}
"arlastitems" => {
let count = args
.int(2)
.map_err(|_| Error::new(Code::Invalid, "invalid COUNT"))?;
if count <= 0 {
out.array(0);
return Ok(());
}
let newest_first = match args.len() {
3 => false,
4 if args::is(args.get(3), b"REV") => true,
4 => return Err(args::syntax()),
_ => return Err(args::wrong_arity(spec.name)),
};
let mark = out.len();
let n = db.arlastitems(args.get(1), count as u64, newest_first, |el| reply(out, el))?;
out.close_array(mark, usize::try_from(n).unwrap_or(usize::MAX));
}
"arscan" => {
let start = parse_index(args.get(2))?;
let end = parse_index(args.get(3))?;
let limit = match args.len() {
4 => u64::MAX,
6 if args::is(args.get(4), b"LIMIT") => {
let n = args.int(5)?;
if n <= 0 {
return Err(Error::new(Code::Invalid, "LIMIT must be positive"));
}
n as u64
}
6 => return Err(args::syntax()),
_ => return Err(args::wrong_arity(spec.name)),
};
let mark = out.len();
let n = db.arscan(args.get(1), start, end, limit, |index, el| {
out.array(2);
out.uint(index);
element(out, el);
})?;
out.close_array(mark, usize::try_from(n).unwrap_or(usize::MAX));
}
"argrep" => {
let start = parse_grep_bound(args.get(2))?;
let end = parse_grep_bound(args.get(3))?;
let mut grep = Grep::new();
let (mut all, mut nocase, mut withvalues) = (false, false, false);
let mut limit = u64::MAX;
let mut i = 4;
while i < args.len() {
let token = args.get(i);
let test = match token {
t if args::is(t, b"EXACT") => Some(Test::Exact),
t if args::is(t, b"MATCH") => Some(Test::Match),
t if args::is(t, b"GLOB") => Some(Test::Glob),
t if args::is(t, b"RE") => Some(Test::Re),
_ => None,
};
if let Some(test) = test {
if i + 1 >= args.len() {
return Err(args::syntax());
}
grep.push(test, args.get(i + 1))?;
i += 2;
continue;
}
match token {
t if args::is(t, b"LIMIT") => {
if i + 1 >= args.len() {
return Err(args::syntax());
}
let n = args.int(i + 1)?;
if n <= 0 {
return Err(Error::new(Code::Invalid, "LIMIT must be positive"));
}
limit = n as u64;
i += 2;
}
t if args::is(t, b"AND") => {
all = true;
i += 1;
}
t if args::is(t, b"OR") => {
all = false;
i += 1;
}
t if args::is(t, b"WITHVALUES") => {
withvalues = true;
i += 1;
}
t if args::is(t, b"NOCASE") => {
nocase = true;
i += 1;
}
_ => return Err(args::syntax()),
}
}
if grep.is_empty() {
return Err(args::syntax());
}
grep.compile(all, nocase)?;
let mark = out.len();
let n = db.argrep(args.get(1), start, end, limit, &mut grep, |index, el| {
if withvalues {
out.array(2);
}
out.uint(index);
if withvalues {
element(out, el);
}
})?;
out.close_array(mark, usize::try_from(n).unwrap_or(usize::MAX));
}
"arop" => {
let start = parse_index(args.get(2))?;
let end = parse_index(args.get(3))?;
let op = match args.get(4) {
w if args::is(w, b"SUM") => Op::Sum,
w if args::is(w, b"MIN") => Op::Min,
w if args::is(w, b"MAX") => Op::Max,
w if args::is(w, b"AND") => Op::And,
w if args::is(w, b"OR") => Op::Or,
w if args::is(w, b"XOR") => Op::Xor,
w if args::is(w, b"MATCH") => Op::Match,
w if args::is(w, b"USED") => Op::Used,
_ => return Err(Error::new(Code::Invalid, "unknown operation")),
};
if op == Op::Match {
if args.len() != 6 {
return Err(Error::new(Code::Invalid, "MATCH requires a value argument"));
}
} else if args.len() != 5 {
return Err(args::wrong_arity(spec.name));
}
match db.arop(args.get(1), start, end, op, args.get(5))? {
Aggregate::Int(n) => out.int(n),
Aggregate::Num(d) => {
let mut buf = [0u8; DOUBLE_MAX];
out.bulk(write_g17(&mut buf, d));
}
Aggregate::None => out.nil(),
}
}
"arinfo" => {
let full = match args.len() {
2 => false,
3 if args::is(args.get(2), b"FULL") => true,
_ => return Err(args::syntax()),
};
let info = db.arinfo(args.get(1), full)?;
out.map(if full { 12 } else { 7 });
out.bulk(b"count");
out.uint(info.count);
out.bulk(b"len");
out.uint(info.len);
out.bulk(b"next-insert-index");
out.uint(info.next_insert);
out.bulk(b"slices");
out.uint(info.slices);
out.bulk(b"directory-size");
out.uint(info.directory_size);
out.bulk(b"super-dir-entries");
out.uint(0);
out.bulk(b"slice-size");
out.uint(info.slice_size);
if full {
out.bulk(b"dense-slices");
out.uint(info.dense_slices);
out.bulk(b"sparse-slices");
out.uint(info.sparse_slices);
out.bulk(b"avg-dense-size");
out.double(info.avg_dense_size);
out.bulk(b"avg-dense-fill");
out.double(info.avg_dense_fill);
out.bulk(b"avg-sparse-size");
out.double(info.avg_sparse_size);
}
}
other => unreachable!("the table sent {other} to the array group"),
}
Ok(())
}
fn index_after_type(db: &mut Keyspace, key: &[u8], bytes: &[u8]) -> Result<u64> {
match parse_index(bytes) {
Ok(index) => Ok(index),
Err(e) => {
db.arlen(key)?;
Err(e)
}
}
}
fn reply(out: &mut Out, el: Option<ArrayElement<'_>>) {
match el {
Some(e) => element(out, e),
None => out.nil(),
}
}
fn element(out: &mut Out, e: ArrayElement<'_>) {
match e {
ArrayElement::Str(s) => out.bulk(s),
ArrayElement::Short(ref s) => out.bulk(s.as_bytes()),
ArrayElement::Int(n) => out.bulk_int(n),
ArrayElement::Float(_) => {
let mut buf = [0u8; yo_kv::array::ELEMENT_MAX];
out.bulk(e.text(&mut buf));
}
}
}