use crate::{error::Error, storage::sstable::bti::node::BtiResult, types::Value};
const OSS50_NEXT_COMPONENT: u8 = 0x40;
const OSS50_ESCAPE: u8 = 0x00;
const OSS50_ESCAPED_0_CONT: u8 = 0xFE;
const OSS50_ESCAPED_0_DONE: u8 = 0xFF;
fn encode_varlen_oss50(bytes: &[u8], out: &mut Vec<u8>) {
let mut escaped = false;
for &b in bytes {
if escaped {
if b == OSS50_ESCAPE {
out.push(OSS50_ESCAPED_0_CONT);
continue;
}
out.push(OSS50_ESCAPED_0_DONE);
escaped = false;
}
out.push(b);
if b == OSS50_ESCAPE {
escaped = true;
}
}
out.push(if escaped {
OSS50_ESCAPED_0_CONT
} else {
OSS50_ESCAPE
});
}
fn encode_clustering_component_oss50(value: &Value, out: &mut Vec<u8>) -> BtiResult<()> {
match value {
Value::Integer(v) => {
out.extend_from_slice(&((*v as u32) ^ 0x8000_0000).to_be_bytes());
Ok(())
}
Value::BigInt(v) | Value::Counter(v) => {
out.extend_from_slice(&((*v as u64) ^ 0x8000_0000_0000_0000).to_be_bytes());
Ok(())
}
Value::SmallInt(v) => {
out.extend_from_slice(&((*v as u16) ^ 0x8000).to_be_bytes());
Ok(())
}
Value::TinyInt(v) => {
out.push((*v as u8) ^ 0x80);
Ok(())
}
Value::Boolean(b) => {
out.push(if *b { 0x01 } else { 0x00 });
Ok(())
}
Value::Timestamp(v) => {
out.extend_from_slice(&((*v as u64) ^ 0x8000_0000_0000_0000).to_be_bytes());
Ok(())
}
Value::Uuid(bytes) => {
out.extend_from_slice(bytes);
Ok(())
}
Value::Text(s) => {
encode_varlen_oss50(s.as_ref(), out);
Ok(())
}
Value::Blob(b) | Value::Inet(b) => {
encode_varlen_oss50(b, out);
Ok(())
}
other => Err(Error::Parse(format!(
"BTI range_query: byte-comparable encoding not implemented for {:?}",
other.data_type()
))),
}
}
pub fn encode_clustering_bound_oss50(values: &[Value]) -> BtiResult<Vec<u8>> {
let mut out = Vec::new();
for v in values {
out.push(OSS50_NEXT_COMPONENT);
encode_clustering_component_oss50(v, &mut out)?;
}
Ok(out)
}
pub fn encode_clustering_bound_oss50_with_order(
values: &[Value],
is_reversed: &[bool],
) -> BtiResult<Vec<u8>> {
let mut out = Vec::new();
for (i, v) in values.iter().enumerate() {
out.push(OSS50_NEXT_COMPONENT);
if is_reversed.get(i).copied().unwrap_or(false) {
let mut scratch = Vec::new();
encode_clustering_component_oss50(v, &mut scratch)?;
for b in &scratch {
out.push(0xFF ^ *b);
}
} else {
encode_clustering_component_oss50(v, &mut out)?;
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn oss50_clustering_encoder() {
assert_eq!(
encode_clustering_bound_oss50(&[Value::Integer(8)]).unwrap(),
vec![0x40, 0x80, 0x00, 0x00, 0x08]
);
assert_eq!(
encode_clustering_bound_oss50(&[Value::Integer(-1)]).unwrap(),
vec![0x40, 0x7F, 0xFF, 0xFF, 0xFF]
);
let neg = encode_clustering_bound_oss50(&[Value::Integer(-1)]).unwrap();
let zero = encode_clustering_bound_oss50(&[Value::Integer(0)]).unwrap();
let pos = encode_clustering_bound_oss50(&[Value::Integer(100)]).unwrap();
assert!(neg < zero && zero < pos);
assert_eq!(
encode_clustering_bound_oss50(&[Value::BigInt(1)]).unwrap(),
vec![0x40, 0x80, 0, 0, 0, 0, 0, 0, 0x01]
);
assert_eq!(
encode_clustering_bound_oss50(&[Value::Integer(1), Value::text("ab".to_string())])
.unwrap(),
vec![0x40, 0x80, 0x00, 0x00, 0x01, 0x40, b'a', b'b', 0x00]
);
for (data, expected) in [
(
vec![b'A', 0x00, 0x00, b'B'],
vec![0x41u8, 0x00, 0xFE, 0xFF, 0x42, 0x00],
),
(
vec![b'A', 0x00, b'B', 0x00],
vec![0x41u8, 0x00, 0xFF, 0x42, 0x00, 0xFE],
),
(vec![b'A', 0x00], vec![0x41u8, 0x00, 0xFE]),
(vec![b'A', b'B'], vec![0x41u8, 0x42, 0x00]),
] {
let mut got = Vec::new();
encode_varlen_oss50(&data, &mut got);
assert_eq!(
got, expected,
"AbstractEscaper worked example for {data:02x?}"
);
}
assert_eq!(
encode_clustering_bound_oss50(&[Value::text("a".to_string())]).unwrap(),
vec![0x40, b'a', 0x00]
);
assert_eq!(
encode_clustering_bound_oss50(&[Value::text("bo".to_string())]).unwrap(),
vec![0x40, b'b', b'o', 0x00]
);
assert_eq!(
encode_clustering_bound_oss50(&[Value::blob(vec![0x01, 0x00, 0x02])]).unwrap(),
vec![0x40, 0x01, 0x00, 0xFF, 0x02, 0x00]
);
let a = encode_clustering_bound_oss50(&[Value::text("a".to_string())]).unwrap();
let ab = encode_clustering_bound_oss50(&[Value::text("ab".to_string())]).unwrap();
assert!(a < ab);
let bo = encode_clustering_bound_oss50(&[Value::text("bo".to_string())]).unwrap();
let bo_12 =
encode_clustering_bound_oss50(&[Value::text("bo".to_string()), Value::Integer(12)])
.unwrap();
assert!(
bo < bo_12,
"a prefix bound on the FIRST clustering component must sort below every \
full clustering that extends it: {bo:02x?} vs {bo_12:02x?}"
);
let z0 = encode_clustering_bound_oss50(&[Value::blob(vec![b'A'])]).unwrap();
let z1 = encode_clustering_bound_oss50(&[Value::blob(vec![b'A', 0x00])]).unwrap();
let z2 = encode_clustering_bound_oss50(&[Value::blob(vec![b'A', 0x00, 0x00])]).unwrap();
assert!(z0 < z1 && z1 < z2, "{z0:02x?} < {z1:02x?} < {z2:02x?}");
assert!(encode_clustering_bound_oss50(&[Value::Float(1.0)]).is_err());
}
#[test]
fn oss50_clustering_encoder_reversed_order() {
assert_eq!(
encode_clustering_bound_oss50_with_order(&[Value::Integer(8)], &[true]).unwrap(),
vec![0x40, 0x7F, 0xFF, 0xFF, 0xF7]
);
assert_eq!(
encode_clustering_bound_oss50_with_order(&[Value::Integer(8)], &[false]).unwrap(),
encode_clustering_bound_oss50(&[Value::Integer(8)]).unwrap()
);
let enc = |v: i32| {
encode_clustering_bound_oss50_with_order(&[Value::Integer(v)], &[true]).unwrap()
};
let write_order = [5, 4, 3, 2, 1, 0];
for w in write_order.windows(2) {
assert!(
enc(w[0]) < enc(w[1]),
"DESC: value {} written before {} must yield smaller separator bytes",
w[0],
w[1]
);
}
let mixed = encode_clustering_bound_oss50_with_order(
&[Value::Integer(1), Value::Integer(8)],
&[false, true],
)
.unwrap();
assert_eq!(
mixed,
vec![0x40, 0x80, 0x00, 0x00, 0x01, 0x40, 0x7F, 0xFF, 0xFF, 0xF7],
"0x40 framing before EACH component (un-inverted), DESC component complemented"
);
let m = |a: i32, b: i32| {
encode_clustering_bound_oss50_with_order(
&[Value::Integer(a), Value::Integer(b)],
&[false, true],
)
.unwrap()
};
assert!(m(1, 9) < m(1, 8) && m(1, 8) < m(1, 7));
assert!(m(1, 0) < m(2, 9));
}
#[test]
fn oss50_empty_varlen_component_is_a_bare_terminator() {
assert_eq!(
encode_clustering_bound_oss50(&[Value::Text("".into())]).unwrap(),
vec![0x40, 0x00]
);
assert_eq!(
encode_clustering_bound_oss50(&[Value::Blob(Vec::new().into())]).unwrap(),
vec![0x40, 0x00]
);
assert_eq!(
encode_clustering_bound_oss50(&[Value::Text("".into()), Value::Integer(0)]).unwrap(),
vec![0x40, 0x00, 0x40, 0x80, 0x00, 0x00, 0x00]
);
let empty = encode_clustering_bound_oss50(&[Value::Text("".into())]).unwrap();
let a = encode_clustering_bound_oss50(&[Value::Text("a".into())]).unwrap();
let nul = encode_clustering_bound_oss50(&[Value::Text("\u{0}".into())]).unwrap();
assert!(empty < a, "empty text must sort before \"a\"");
assert!(empty < nul, "empty text must sort before a single NUL");
let empty_then_int =
encode_clustering_bound_oss50(&[Value::Text("".into()), Value::Integer(0)]).unwrap();
assert!(
empty < empty_then_int,
"the empty component's terminator must sort below the following 0x40"
);
}
}