use rocksdb::SliceTransform;
pub(super) const NAME: &str = "surrealdb.TablePrefix.v1";
const TB_START: usize = 12;
const MIN_LEN: usize = TB_START + 2;
fn parse_prefix_end(key: &[u8]) -> Option<usize> {
if key.len() < MIN_LEN {
return None;
}
if key[0] != b'/' || key[1] != b'*' || key[6] != b'*' || key[11] != b'*' {
return None;
}
let null_pos = TB_START + key[TB_START..].iter().position(|&b| b == 0)?;
if null_pos + 1 >= key.len() {
return None;
}
Some(null_pos + 2)
}
fn transform(key: &[u8]) -> &[u8] {
match parse_prefix_end(key) {
Some(end) => &key[..end],
None => key,
}
}
fn in_domain(key: &[u8]) -> bool {
parse_prefix_end(key).is_some()
}
pub(super) fn extract(key: &[u8]) -> Option<&[u8]> {
parse_prefix_end(key).map(|end| &key[..end])
}
pub(super) fn build() -> SliceTransform {
SliceTransform::create(NAME, transform, Some(in_domain))
}
#[cfg(test)]
mod tests {
use super::*;
const HEADER: &[u8] = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*";
#[test]
fn record_key_prefix() {
let key = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*testtb\0*\x03testid\0";
assert!(in_domain(key));
assert_eq!(transform(key), b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*testtb\0*");
}
#[test]
fn index_root_prefix() {
let key = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*testtb\0+\x00\x00\x00\x03";
assert!(in_domain(key));
assert_eq!(transform(key), b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*testtb\0+");
}
#[test]
fn index_definition_prefix() {
let key = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*testtb\0!ixtestix\0";
assert!(in_domain(key));
assert_eq!(transform(key), b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*testtb\0!");
}
#[test]
fn graph_key_prefix() {
let key = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*testtb\0~\x03id\0";
assert!(in_domain(key));
assert_eq!(transform(key), b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*testtb\0~");
}
#[test]
fn ref_key_prefix() {
let key = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*testtb\0&\x03id\0";
assert!(in_domain(key));
assert_eq!(transform(key), b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*testtb\0&");
}
#[test]
fn records_in_same_table_share_prefix() {
let k1 = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*tb\0*\x03idA\0";
let k2 = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*tb\0*\x03idBBB\0";
assert_eq!(transform(k1), transform(k2));
}
#[test]
fn records_in_different_tables_have_different_prefixes() {
let k1 = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*tbA\0*\x03id\0";
let k2 = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*tbB\0*\x03id\0";
assert_ne!(transform(k1), transform(k2));
}
#[test]
fn records_in_different_namespaces_have_different_prefixes() {
let k1 = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*tb\0*\x03id\0";
let k2 = b"/*\x00\x00\x00\x99*\x00\x00\x00\x02*tb\0*\x03id\0";
assert_ne!(transform(k1), transform(k2));
}
#[test]
fn records_and_indexes_have_different_prefixes() {
let records = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*tb\0*\x03id\0";
let indexes = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*tb\0+\x00\x00\x00\x01";
assert_ne!(transform(records), transform(indexes));
}
#[test]
fn zero_length_table_name_still_in_domain() {
let key = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*\0*";
assert_eq!(key.len(), MIN_LEN);
assert!(in_domain(key));
assert_eq!(transform(key), b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*\0*");
}
#[test]
fn namespace_metadata_is_out_of_domain() {
let key = b"/!ns\x00\x00\x00\x01";
assert!(!in_domain(key));
assert_eq!(transform(key), key);
}
#[test]
fn database_metadata_is_out_of_domain() {
let key = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02!tbhello\0";
assert!(!in_domain(key));
}
#[test]
fn database_identifier_state_is_out_of_domain() {
let key = b"/+\x00\x00\x00\x01!di";
assert!(!in_domain(key));
}
#[test]
fn database_sequence_is_out_of_domain() {
let key = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*sqtest\0";
assert!(!in_domain(key));
}
#[test]
fn node_key_is_out_of_domain() {
let key = b"/$\x00\x00\x00\x00\x01";
assert!(!in_domain(key));
}
#[test]
fn change_feed_is_out_of_domain() {
let key = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02#\x00\x00\x00\x00\x00\x00\x00\x01";
assert!(!in_domain(key));
}
#[test]
fn table_root_without_discriminator_is_out_of_domain() {
let key = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*testtb\0";
assert!(!in_domain(key));
}
#[test]
fn too_short_keys_are_out_of_domain() {
assert!(!in_domain(b""));
assert!(!in_domain(b"/"));
assert!(!in_domain(b"/*\x00\x00\x00\x01"));
assert!(!in_domain(b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*"));
}
#[test]
fn seek_range_start_and_end_share_prefix() {
let start = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*tb\0*\x00";
let end = b"/*\x00\x00\x00\x01*\x00\x00\x00\x02*tb\0*\xff";
assert_eq!(extract(start), extract(end));
}
#[test]
fn extract_returns_none_for_out_of_domain() {
assert!(extract(b"/!ns\x00\x00\x00\x01").is_none());
}
#[test]
fn header_literal_matches_expected_layout() {
assert_eq!(HEADER.len(), TB_START);
assert_eq!(HEADER[0], b'/');
assert_eq!(HEADER[1], b'*');
assert_eq!(HEADER[6], b'*');
assert_eq!(HEADER[11], b'*');
}
}