use serde::{Deserialize, Serialize};
use crate::ids::OwnerKind;
use crate::model::{Cardinality, LogicalType, Nullability, TypeState};
#[derive(Debug, Clone, PartialEq, Eq, Copy, Serialize, Deserialize)]
pub enum OccurrenceValue {
ExplicitNull,
EmptyArray,
Stored {
ty: LogicalType,
},
}
impl OccurrenceValue {
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self, OccurrenceValue::ExplicitNull)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OccurrenceRow {
pub owner_kind: OwnerKind,
pub owner_ord: u64,
pub property_id: u64,
pub family_id: u64,
pub elem_ordinal: Option<u64>,
pub occurrence_value: OccurrenceValue,
}
#[must_use]
pub fn occurrence_value_hash(value: OccurrenceValue, payload: &[u8]) -> u64 {
match value {
OccurrenceValue::Stored { ty } => {
crate::hash::value_hash(crate::encoding::logical_tag(ty), payload)
}
OccurrenceValue::ExplicitNull | OccurrenceValue::EmptyArray => {
crate::hash::null_value_hash()
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RecordHeader {
pub record_id: [u8; 16],
pub ordinal: u64,
pub label: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RelationshipHeader {
pub relationship_id: [u8; 16],
pub ordinal: u64,
pub rel_type: String,
pub source_ord: u64,
pub target_ord: u64,
pub source_id: [u8; 16],
pub target_id: [u8; 16],
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PropertyFamily {
pub family_id: u64,
pub namespace: String,
pub owner_scope: OwnerKind,
pub normalized_name: String,
pub display_name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PropertyDefinition {
pub property_id: u64,
pub family_id: u64,
pub type_state: TypeState,
pub cardinality: Cardinality,
pub nullability: Nullability,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum Similarity {
#[default]
Cosine,
Dot,
Euclidean,
}
impl Similarity {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Similarity::Cosine => "cosine",
Similarity::Dot => "dot",
Similarity::Euclidean => "euclidean",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EmbeddingSpaceInfo {
pub space_id: u64,
#[serde(default)]
pub name: String,
pub dims: u32,
#[serde(default)]
pub similarity: Similarity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BindingStatus {
Pending,
Ready,
Stale,
Retired,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EmbeddingBinding {
pub space_id: u64,
pub owner_kind: OwnerKind,
pub owner_ord: u64,
pub pid: u64,
pub elem: u64,
pub vector_slot: u64,
pub source_hash: u64,
#[serde(default = "default_source_hash_tracked")]
pub source_hash_tracked: bool,
pub status: BindingStatus,
}
fn default_source_hash_tracked() -> bool {
true
}
pub const BINDING_VALUE_LEN: usize = 17;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BindingValue {
pub vector_slot: u64,
pub source_hash: u64,
pub source_hash_tracked: bool,
pub status: BindingStatus,
}
impl BindingValue {
#[must_use]
pub fn decode(raw: &[u8]) -> Option<Self> {
let raw: &[u8; BINDING_VALUE_LEN] = raw.try_into().ok()?;
let flags = raw[16];
if flags & !0b111 != 0 {
return None;
}
let status = match flags & 0b11 {
0 => BindingStatus::Pending,
1 => BindingStatus::Ready,
2 => BindingStatus::Stale,
_ => BindingStatus::Retired,
};
Some(Self {
vector_slot: u64::from_le_bytes(raw[0..8].try_into().ok()?),
source_hash: u64::from_le_bytes(raw[8..16].try_into().ok()?),
source_hash_tracked: flags & 0b100 != 0,
status,
})
}
}
impl EmbeddingBinding {
#[must_use]
pub fn encode_value(&self) -> [u8; BINDING_VALUE_LEN] {
let mut out = [0u8; BINDING_VALUE_LEN];
out[0..8].copy_from_slice(&self.vector_slot.to_le_bytes());
out[8..16].copy_from_slice(&self.source_hash.to_le_bytes());
out[16] = match self.status {
BindingStatus::Pending => 0,
BindingStatus::Ready => 1,
BindingStatus::Stale => 2,
BindingStatus::Retired => 3,
} | if self.source_hash_tracked { 0b100 } else { 0 };
out
}
#[must_use]
pub fn decode(key: &[u8], value: &[u8]) -> Option<Self> {
let (space_id, owner_kind, owner_ord, pid, elem) = crate::keys::parse_emb_binding_key(key)?;
let value = BindingValue::decode(value)?;
Some(Self {
space_id,
owner_kind,
owner_ord,
pid,
elem,
vector_slot: value.vector_slot,
source_hash: value.source_hash,
source_hash_tracked: value.source_hash_tracked,
status: value.status,
})
}
}
pub const OCC_FLAG_ARRAY: u8 = 0x01;
pub const OCC_STATE_NULL: u8 = 0x00;
pub const OCC_STATE_EMPTY_ARRAY: u8 = 0xFE;
#[must_use]
pub fn encode_occ_value(array: bool, value: OccurrenceValue, payload: Option<&[u8]>) -> Vec<u8> {
let payload = payload.unwrap_or_default();
let mut out = Vec::with_capacity(2 + payload.len());
out.push(if array { OCC_FLAG_ARRAY } else { 0 });
match value {
OccurrenceValue::ExplicitNull => out.push(OCC_STATE_NULL),
OccurrenceValue::EmptyArray => out.push(OCC_STATE_EMPTY_ARRAY),
OccurrenceValue::Stored { ty } => {
out.push(crate::encoding::logical_tag(ty));
out.extend_from_slice(payload);
}
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OccValue<'a> {
pub array: bool,
pub value: OccurrenceValue,
pub payload: &'a [u8],
pub entry: &'a [u8],
}
impl OccValue<'_> {
#[must_use]
pub fn value_hash(&self) -> u64 {
occurrence_value_hash(self.value, self.payload)
}
}
pub fn decode_occ_value(raw: &[u8]) -> Result<OccValue<'_>, crate::error::TypesError> {
let malformed = crate::error::TypesError::MalformedKey;
let (&flags, rest) = raw.split_first().ok_or(malformed.clone())?;
let (&state, payload) = rest.split_first().ok_or(malformed.clone())?;
let value = match state {
OCC_STATE_NULL => OccurrenceValue::ExplicitNull,
OCC_STATE_EMPTY_ARRAY => OccurrenceValue::EmptyArray,
tag => OccurrenceValue::Stored {
ty: crate::encoding::logical_from_tag(tag)?,
},
};
if !matches!(value, OccurrenceValue::Stored { .. }) && !payload.is_empty() {
return Err(malformed);
}
Ok(OccValue {
array: flags & OCC_FLAG_ARRAY != 0,
value,
payload,
entry: rest,
})
}
#[cfg(test)]
mod codec_tests {
use super::*;
use crate::encoding::{decode_order_preserving, encode_order_preserving, logical_tag};
use crate::model::LogicalType;
use crate::value::Scalar;
#[test]
fn a_binding_round_trips_through_its_key_and_compact_value() {
for (status, tracked) in [
(BindingStatus::Pending, false),
(BindingStatus::Ready, true),
(BindingStatus::Stale, true),
(BindingStatus::Retired, false),
] {
let binding = EmbeddingBinding {
space_id: 7,
owner_kind: OwnerKind::Relationship,
owner_ord: 1 << 40,
pid: 3,
elem: 2,
vector_slot: u64::MAX - 1,
source_hash: 0xdead_beef_cafe_f00d,
source_hash_tracked: tracked,
status,
};
let key = crate::keys::emb_binding_key(
crate::ids::TenantId(9),
binding.space_id,
binding.owner_kind,
binding.owner_ord,
binding.pid,
binding.elem,
);
let value = binding.encode_value();
assert_eq!(value.len(), BINDING_VALUE_LEN);
assert_eq!(EmbeddingBinding::decode(&key, &value), Some(binding));
}
assert_eq!(BindingValue::decode(&[0; 16]), None);
let mut bad = [0u8; BINDING_VALUE_LEN];
bad[16] = 0b1000;
assert_eq!(BindingValue::decode(&bad), None);
}
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
}
fn scalar(rng: &mut Rng) -> (LogicalType, Scalar) {
let bits = rng.next();
match bits % 5 {
0 => (LogicalType::Boolean, Scalar::Boolean(bits & 8 != 0)),
1 => (LogicalType::Int64, Scalar::Int64(bits.cast_signed())),
2 => {
let f = f64::from_bits(rng.next());
let f = if f.is_finite() { f } else { -0.0 };
(LogicalType::Float64, Scalar::Float64(f))
}
3 => (
LogicalType::DateTime,
Scalar::DateTime((bits >> 2).cast_signed()),
),
_ => {
let len = usize::try_from(rng.next() % (1 << 20)).unwrap();
let text: String = "aé€".chars().cycle().take(len / 3).collect();
(LogicalType::Utf8, Scalar::Utf8(text))
}
}
}
#[test]
fn occurrence_values_round_trip_for_every_type_null_and_array_state() {
let mut rng = Rng(0x2545_f491_4f6c_dd1d);
for _ in 0..200 {
let array = rng.next() & 1 == 1;
let (ty, value) = scalar(&mut rng);
let payload = encode_order_preserving(ty, &value).unwrap();
let raw = encode_occ_value(array, OccurrenceValue::Stored { ty }, Some(&payload));
let decoded = decode_occ_value(&raw).unwrap();
assert_eq!(decoded.array, array);
assert_eq!(decoded.value, OccurrenceValue::Stored { ty });
assert_eq!(decoded.payload, payload.as_slice());
assert_eq!(decoded.entry[0], logical_tag(ty));
assert_eq!(&decoded.entry[1..], payload.as_slice());
let back = decode_order_preserving(ty, decoded.payload).unwrap();
assert_eq!(
encode_order_preserving(ty, &back).unwrap(),
payload,
"{ty:?}"
);
}
for state in [OccurrenceValue::ExplicitNull, OccurrenceValue::EmptyArray] {
for array in [false, true] {
let raw = encode_occ_value(array, state, None);
let decoded = decode_occ_value(&raw).unwrap();
assert_eq!((decoded.array, decoded.value), (array, state));
assert!(decoded.payload.is_empty());
}
}
}
#[test]
fn malformed_occurrence_values_are_errors_never_panics() {
assert!(decode_occ_value(&[]).is_err());
assert!(decode_occ_value(&[0]).is_err());
assert!(decode_occ_value(&[0, OCC_STATE_NULL, 1]).is_err());
assert!(decode_occ_value(&[0, OCC_STATE_EMPTY_ARRAY, 1]).is_err());
assert!(decode_occ_value(&[0, 0x7F]).is_err());
let mut rng = Rng(0x9e37_79b9_7f4a_7c15);
for _ in 0..10_000 {
let len = usize::try_from(rng.next() % 24).unwrap();
let raw: Vec<u8> = (0..len).map(|_| rng.next().to_le_bytes()[0]).collect();
let _ = decode_occ_value(&raw);
}
}
}