use std::borrow::Cow;
use anyhow::Result;
use storekey::{BorrowDecode, Encode};
use crate::catalog::{DatabaseId, NamespaceId, SequenceDefinition};
use crate::key::category::{Categorise, Category};
use crate::kvs::{KVKey, impl_kv_key_storekey};
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Encode, BorrowDecode)]
pub(crate) struct Sq<'a> {
__: u8,
_a: u8,
pub ns: NamespaceId,
_b: u8,
pub db: DatabaseId,
_c: u8,
_d: u8,
_e: u8,
pub sq: Cow<'a, str>,
}
impl_kv_key_storekey!(Sq<'_> => SequenceDefinition);
pub fn prefix(ns: NamespaceId, db: DatabaseId) -> Result<Vec<u8>> {
let mut k = super::all::new(ns, db).encode_key()?;
k.extend_from_slice(b"*sq\x00");
Ok(k)
}
pub fn suffix(ns: NamespaceId, db: DatabaseId) -> Result<Vec<u8>> {
let mut k = super::all::new(ns, db).encode_key()?;
k.extend_from_slice(b"*sq\xff");
Ok(k)
}
pub fn name_prefix(ns: NamespaceId, db: DatabaseId) -> Result<Vec<u8>> {
let mut k = super::all::new(ns, db).encode_key()?;
k.extend_from_slice(b"*sq");
Ok(k)
}
pub fn is_definition(key: &[u8], name_prefix: &[u8]) -> bool {
let Some(rest) = key.strip_prefix(name_prefix) else {
return false;
};
name_end(rest) == Some(rest.len())
}
pub fn skip_table_subtree(key: &[u8], name_prefix: &[u8]) -> Option<Vec<u8>> {
let rest = key.strip_prefix(name_prefix)?;
let end = name_end(rest)?;
if end == rest.len() {
return None;
}
let mut bound = key[..name_prefix.len() + end].to_vec();
let last = bound.last_mut()?;
if *last != 0x00 {
return None;
}
*last = 0x01;
Some(bound)
}
fn name_end(rest: &[u8]) -> Option<usize> {
let mut i = 0;
while i < rest.len() {
match rest[i] {
0x01 => i += 2,
0x00 => return Some(i + 1),
_ => i += 1,
}
}
None
}
impl Categorise for Sq<'_> {
fn categorise(&self) -> Category {
Category::DatabaseSequence
}
}
impl<'a> Sq<'a> {
pub(crate) fn new(ns: NamespaceId, db: DatabaseId, sq: &'a str) -> Self {
Self {
__: b'/', _a: b'*', ns,
_b: b'*', db,
_c: b'*', _d: b's', _e: b'q', sq: Cow::Borrowed(sq),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pre() -> Vec<u8> {
name_prefix(NamespaceId(1), DatabaseId(2)).unwrap()
}
fn definition(name: &str) -> Vec<u8> {
Sq::encode_key(&Sq::new(NamespaceId(1), DatabaseId(2), name)).unwrap()
}
#[test]
fn key() {
let val = Sq::new(NamespaceId(1), DatabaseId(2), "test");
let enc = Sq::encode_key(&val).unwrap();
assert_eq!(enc, b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*sqtest\0");
}
#[test]
fn prefix() {
let val = super::prefix(NamespaceId(1), DatabaseId(2)).unwrap();
assert_eq!(val, b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*sq\0");
}
#[test]
fn suffix() {
let val = super::suffix(NamespaceId(1), DatabaseId(2)).unwrap();
assert_eq!(val, b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*sq\xff");
}
#[test]
fn name_prefix_is_where_the_name_starts() {
assert_eq!(pre(), b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*sq");
}
#[test]
fn a_definition_key_is_recognised() {
let pre = pre();
for name in ["", "a", "foo", "sqfoo", "a\0b", "a\x01b", "*sq", "!tb"] {
assert!(
is_definition(&definition(name), &pre),
"the definition for {name:?} was not recognised"
);
}
}
#[test]
fn a_table_subtree_key_is_not_a_definition() {
let pre = pre();
let root = definition("foo");
for tail in [b"*".as_slice(), b"\0", b"*id\0", b"!fdname\0", b"\x01\x01"] {
let mut key = root.clone();
key.extend_from_slice(tail);
assert!(
!is_definition(&key, &pre),
"a key of table `sqfoo` was taken for a definition: {key:?}"
);
}
}
#[test]
fn an_escaped_name_is_not_truncated() {
let pre = pre();
let embedded = definition("a\0b");
assert!(is_definition(&embedded, &pre));
assert_ne!(embedded, definition("a"), "the escape collapsed two distinct names");
}
#[test]
fn a_subtree_key_yields_a_bound_past_the_whole_table() {
let pre = pre();
let root = definition("foo");
let mut inner = root.clone();
inner.extend_from_slice(b"*id\0");
let bound = skip_table_subtree(&inner, &pre).expect("a table key yields a bound");
assert!(bound > inner, "the bound must sort above the key that produced it");
assert!(bound > root, "the bound must sort above the table root");
for tail in [b"\0".as_slice(), b"*", b"!fd", b"\xff\xff\xff"] {
let mut key = root.clone();
key.extend_from_slice(tail);
assert!(key < bound, "a table key sorted at or above the bound: {key:?}");
}
assert!(definition("fop") > bound, "the bound swallowed the following definition");
}
#[test]
fn a_definition_yields_no_bound_to_skip() {
let pre = pre();
for name in ["", "a", "foo", "a\0b"] {
assert_eq!(skip_table_subtree(&definition(name), &pre), None, "for {name:?}");
}
}
#[test]
fn a_key_outside_the_layout_is_not_a_definition() {
let pre = pre();
assert!(!is_definition(&name_prefix(NamespaceId(1), DatabaseId(3)).unwrap(), &pre));
assert!(!is_definition(b"/", &pre));
let mut truncated = pre.clone();
truncated.extend_from_slice(b"foo");
assert!(!is_definition(&truncated, &pre));
}
}