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 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;
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);
}
}
}