1use serde::{Deserialize, Serialize};
2
3use crate::ids::OwnerKind;
4use crate::model::{Cardinality, LogicalType, Nullability, TypeState};
5
6#[derive(Debug, Clone, PartialEq, Eq, Copy, Serialize, Deserialize)]
8pub enum OccurrenceValue {
9 ExplicitNull,
11 EmptyArray,
17 Stored {
20 ty: LogicalType,
22 },
23}
24
25impl OccurrenceValue {
26 #[must_use]
28 pub fn is_null(&self) -> bool {
29 matches!(self, OccurrenceValue::ExplicitNull)
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
37pub struct OccurrenceRow {
38 pub owner_kind: OwnerKind,
40 pub owner_ord: u64,
42 pub property_id: u64,
44 pub family_id: u64,
46 pub elem_ordinal: Option<u64>,
48 pub occurrence_value: OccurrenceValue,
52}
53
54#[must_use]
57pub fn occurrence_value_hash(value: OccurrenceValue, payload: &[u8]) -> u64 {
58 match value {
59 OccurrenceValue::Stored { ty } => {
60 crate::hash::value_hash(crate::encoding::logical_tag(ty), payload)
61 }
62 OccurrenceValue::ExplicitNull | OccurrenceValue::EmptyArray => {
63 crate::hash::null_value_hash()
64 }
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70pub struct RecordHeader {
71 pub record_id: [u8; 16],
73 pub ordinal: u64,
75 pub label: String,
77}
78
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
81pub struct RelationshipHeader {
82 pub relationship_id: [u8; 16],
84 pub ordinal: u64,
86 pub rel_type: String,
88 pub source_ord: u64,
90 pub target_ord: u64,
92 pub source_id: [u8; 16],
94 pub target_id: [u8; 16],
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct PropertyFamily {
101 pub family_id: u64,
103 pub namespace: String,
105 pub owner_scope: OwnerKind,
107 pub normalized_name: String,
109 pub display_name: String,
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115pub struct PropertyDefinition {
116 pub property_id: u64,
118 pub family_id: u64,
120 pub type_state: TypeState,
122 pub cardinality: Cardinality,
124 pub nullability: Nullability,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
130#[serde(rename_all = "lowercase")]
131pub enum Similarity {
132 #[default]
134 Cosine,
135 Dot,
137 Euclidean,
139}
140
141impl Similarity {
142 #[must_use]
144 pub fn as_str(&self) -> &'static str {
145 match self {
146 Similarity::Cosine => "cosine",
147 Similarity::Dot => "dot",
148 Similarity::Euclidean => "euclidean",
149 }
150 }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155pub struct EmbeddingSpaceInfo {
156 pub space_id: u64,
158 #[serde(default)]
162 pub name: String,
163 pub dims: u32,
165 #[serde(default)]
168 pub similarity: Similarity,
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
173pub enum BindingStatus {
174 Pending,
176 Ready,
178 Stale,
180 Retired,
182}
183
184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
187pub struct EmbeddingBinding {
188 pub space_id: u64,
190 pub owner_kind: OwnerKind,
192 pub owner_ord: u64,
194 pub pid: u64,
196 pub elem: u64,
198 pub vector_slot: u64,
200 pub source_hash: u64,
202 #[serde(default = "default_source_hash_tracked")]
206 pub source_hash_tracked: bool,
207 pub status: BindingStatus,
209}
210
211fn default_source_hash_tracked() -> bool {
212 true
213}
214
215pub const BINDING_VALUE_LEN: usize = 17;
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223pub struct BindingValue {
224 pub vector_slot: u64,
226 pub source_hash: u64,
228 pub source_hash_tracked: bool,
230 pub status: BindingStatus,
232}
233
234impl BindingValue {
235 #[must_use]
237 pub fn decode(raw: &[u8]) -> Option<Self> {
238 let raw: &[u8; BINDING_VALUE_LEN] = raw.try_into().ok()?;
239 let flags = raw[16];
240 if flags & !0b111 != 0 {
241 return None;
242 }
243 let status = match flags & 0b11 {
244 0 => BindingStatus::Pending,
245 1 => BindingStatus::Ready,
246 2 => BindingStatus::Stale,
247 _ => BindingStatus::Retired,
248 };
249 Some(Self {
250 vector_slot: u64::from_le_bytes(raw[0..8].try_into().ok()?),
251 source_hash: u64::from_le_bytes(raw[8..16].try_into().ok()?),
252 source_hash_tracked: flags & 0b100 != 0,
253 status,
254 })
255 }
256}
257
258impl EmbeddingBinding {
259 #[must_use]
261 pub fn encode_value(&self) -> [u8; BINDING_VALUE_LEN] {
262 let mut out = [0u8; BINDING_VALUE_LEN];
263 out[0..8].copy_from_slice(&self.vector_slot.to_le_bytes());
264 out[8..16].copy_from_slice(&self.source_hash.to_le_bytes());
265 out[16] = match self.status {
266 BindingStatus::Pending => 0,
267 BindingStatus::Ready => 1,
268 BindingStatus::Stale => 2,
269 BindingStatus::Retired => 3,
270 } | if self.source_hash_tracked { 0b100 } else { 0 };
271 out
272 }
273
274 #[must_use]
277 pub fn decode(key: &[u8], value: &[u8]) -> Option<Self> {
278 let (space_id, owner_kind, owner_ord, pid, elem) = crate::keys::parse_emb_binding_key(key)?;
279 let value = BindingValue::decode(value)?;
280 Some(Self {
281 space_id,
282 owner_kind,
283 owner_ord,
284 pid,
285 elem,
286 vector_slot: value.vector_slot,
287 source_hash: value.source_hash,
288 source_hash_tracked: value.source_hash_tracked,
289 status: value.status,
290 })
291 }
292}
293
294pub const OCC_FLAG_ARRAY: u8 = 0x01;
296pub const OCC_STATE_NULL: u8 = 0x00;
298pub const OCC_STATE_EMPTY_ARRAY: u8 = 0xFE;
300
301#[must_use]
305pub fn encode_occ_value(array: bool, value: OccurrenceValue, payload: Option<&[u8]>) -> Vec<u8> {
306 let payload = payload.unwrap_or_default();
307 let mut out = Vec::with_capacity(2 + payload.len());
308 out.push(if array { OCC_FLAG_ARRAY } else { 0 });
309 match value {
310 OccurrenceValue::ExplicitNull => out.push(OCC_STATE_NULL),
311 OccurrenceValue::EmptyArray => out.push(OCC_STATE_EMPTY_ARRAY),
312 OccurrenceValue::Stored { ty } => {
313 out.push(crate::encoding::logical_tag(ty));
314 out.extend_from_slice(payload);
315 }
316 }
317 out
318}
319
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
322pub struct OccValue<'a> {
323 pub array: bool,
325 pub value: OccurrenceValue,
327 pub payload: &'a [u8],
329 pub entry: &'a [u8],
331}
332
333impl OccValue<'_> {
334 #[must_use]
336 pub fn value_hash(&self) -> u64 {
337 occurrence_value_hash(self.value, self.payload)
338 }
339}
340
341pub fn decode_occ_value(raw: &[u8]) -> Result<OccValue<'_>, crate::error::TypesError> {
346 let malformed = crate::error::TypesError::MalformedKey;
347 let (&flags, rest) = raw.split_first().ok_or(malformed.clone())?;
348 let (&state, payload) = rest.split_first().ok_or(malformed.clone())?;
349 let value = match state {
350 OCC_STATE_NULL => OccurrenceValue::ExplicitNull,
351 OCC_STATE_EMPTY_ARRAY => OccurrenceValue::EmptyArray,
352 tag => OccurrenceValue::Stored {
353 ty: crate::encoding::logical_from_tag(tag)?,
354 },
355 };
356 if !matches!(value, OccurrenceValue::Stored { .. }) && !payload.is_empty() {
357 return Err(malformed);
358 }
359 Ok(OccValue {
360 array: flags & OCC_FLAG_ARRAY != 0,
361 value,
362 payload,
363 entry: rest,
364 })
365}
366
367#[cfg(test)]
368mod codec_tests {
369 use super::*;
370 use crate::encoding::{decode_order_preserving, encode_order_preserving, logical_tag};
371 use crate::model::LogicalType;
372 use crate::value::Scalar;
373
374 #[test]
375 fn a_binding_round_trips_through_its_key_and_compact_value() {
376 for (status, tracked) in [
377 (BindingStatus::Pending, false),
378 (BindingStatus::Ready, true),
379 (BindingStatus::Stale, true),
380 (BindingStatus::Retired, false),
381 ] {
382 let binding = EmbeddingBinding {
383 space_id: 7,
384 owner_kind: OwnerKind::Relationship,
385 owner_ord: 1 << 40,
386 pid: 3,
387 elem: 2,
388 vector_slot: u64::MAX - 1,
389 source_hash: 0xdead_beef_cafe_f00d,
390 source_hash_tracked: tracked,
391 status,
392 };
393 let key = crate::keys::emb_binding_key(
394 crate::ids::TenantId(9),
395 binding.space_id,
396 binding.owner_kind,
397 binding.owner_ord,
398 binding.pid,
399 binding.elem,
400 );
401 let value = binding.encode_value();
402 assert_eq!(value.len(), BINDING_VALUE_LEN);
403 assert_eq!(EmbeddingBinding::decode(&key, &value), Some(binding));
404 }
405 assert_eq!(BindingValue::decode(&[0; 16]), None);
407 let mut bad = [0u8; BINDING_VALUE_LEN];
408 bad[16] = 0b1000;
409 assert_eq!(BindingValue::decode(&bad), None);
410 }
411
412 struct Rng(u64);
414 impl Rng {
415 fn next(&mut self) -> u64 {
416 self.0 ^= self.0 << 13;
417 self.0 ^= self.0 >> 7;
418 self.0 ^= self.0 << 17;
419 self.0
420 }
421 }
422
423 fn scalar(rng: &mut Rng) -> (LogicalType, Scalar) {
424 let bits = rng.next();
425 match bits % 5 {
426 0 => (LogicalType::Boolean, Scalar::Boolean(bits & 8 != 0)),
427 1 => (LogicalType::Int64, Scalar::Int64(bits.cast_signed())),
428 2 => {
429 let f = f64::from_bits(rng.next());
430 let f = if f.is_finite() { f } else { -0.0 };
431 (LogicalType::Float64, Scalar::Float64(f))
432 }
433 3 => (
434 LogicalType::DateTime,
435 Scalar::DateTime((bits >> 2).cast_signed()),
436 ),
437 _ => {
438 let len = usize::try_from(rng.next() % (1 << 20)).unwrap();
440 let text: String = "aé€".chars().cycle().take(len / 3).collect();
441 (LogicalType::Utf8, Scalar::Utf8(text))
442 }
443 }
444 }
445
446 #[test]
447 fn occurrence_values_round_trip_for_every_type_null_and_array_state() {
448 let mut rng = Rng(0x2545_f491_4f6c_dd1d);
449 for _ in 0..200 {
450 let array = rng.next() & 1 == 1;
451 let (ty, value) = scalar(&mut rng);
452 let payload = encode_order_preserving(ty, &value).unwrap();
453 let raw = encode_occ_value(array, OccurrenceValue::Stored { ty }, Some(&payload));
454 let decoded = decode_occ_value(&raw).unwrap();
455 assert_eq!(decoded.array, array);
456 assert_eq!(decoded.value, OccurrenceValue::Stored { ty });
457 assert_eq!(decoded.payload, payload.as_slice());
458 assert_eq!(decoded.entry[0], logical_tag(ty));
459 assert_eq!(&decoded.entry[1..], payload.as_slice());
460 let back = decode_order_preserving(ty, decoded.payload).unwrap();
461 assert_eq!(
462 encode_order_preserving(ty, &back).unwrap(),
463 payload,
464 "{ty:?}"
465 );
466 }
467 for state in [OccurrenceValue::ExplicitNull, OccurrenceValue::EmptyArray] {
468 for array in [false, true] {
469 let raw = encode_occ_value(array, state, None);
470 let decoded = decode_occ_value(&raw).unwrap();
471 assert_eq!((decoded.array, decoded.value), (array, state));
472 assert!(decoded.payload.is_empty());
473 }
474 }
475 }
476
477 #[test]
478 fn malformed_occurrence_values_are_errors_never_panics() {
479 assert!(decode_occ_value(&[]).is_err());
480 assert!(decode_occ_value(&[0]).is_err());
481 assert!(decode_occ_value(&[0, OCC_STATE_NULL, 1]).is_err());
483 assert!(decode_occ_value(&[0, OCC_STATE_EMPTY_ARRAY, 1]).is_err());
484 assert!(decode_occ_value(&[0, 0x7F]).is_err());
486 let mut rng = Rng(0x9e37_79b9_7f4a_7c15);
487 for _ in 0..10_000 {
488 let len = usize::try_from(rng.next() % 24).unwrap();
489 let raw: Vec<u8> = (0..len).map(|_| rng.next().to_le_bytes()[0]).collect();
490 let _ = decode_occ_value(&raw);
492 }
493 }
494}