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 OCC_FLAG_ARRAY: u8 = 0x01;
217pub const OCC_STATE_NULL: u8 = 0x00;
219pub const OCC_STATE_EMPTY_ARRAY: u8 = 0xFE;
221
222#[must_use]
226pub fn encode_occ_value(array: bool, value: OccurrenceValue, payload: Option<&[u8]>) -> Vec<u8> {
227 let payload = payload.unwrap_or_default();
228 let mut out = Vec::with_capacity(2 + payload.len());
229 out.push(if array { OCC_FLAG_ARRAY } else { 0 });
230 match value {
231 OccurrenceValue::ExplicitNull => out.push(OCC_STATE_NULL),
232 OccurrenceValue::EmptyArray => out.push(OCC_STATE_EMPTY_ARRAY),
233 OccurrenceValue::Stored { ty } => {
234 out.push(crate::encoding::logical_tag(ty));
235 out.extend_from_slice(payload);
236 }
237 }
238 out
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
243pub struct OccValue<'a> {
244 pub array: bool,
246 pub value: OccurrenceValue,
248 pub payload: &'a [u8],
250 pub entry: &'a [u8],
252}
253
254impl OccValue<'_> {
255 #[must_use]
257 pub fn value_hash(&self) -> u64 {
258 occurrence_value_hash(self.value, self.payload)
259 }
260}
261
262pub fn decode_occ_value(raw: &[u8]) -> Result<OccValue<'_>, crate::error::TypesError> {
267 let malformed = crate::error::TypesError::MalformedKey;
268 let (&flags, rest) = raw.split_first().ok_or(malformed.clone())?;
269 let (&state, payload) = rest.split_first().ok_or(malformed.clone())?;
270 let value = match state {
271 OCC_STATE_NULL => OccurrenceValue::ExplicitNull,
272 OCC_STATE_EMPTY_ARRAY => OccurrenceValue::EmptyArray,
273 tag => OccurrenceValue::Stored {
274 ty: crate::encoding::logical_from_tag(tag)?,
275 },
276 };
277 if !matches!(value, OccurrenceValue::Stored { .. }) && !payload.is_empty() {
278 return Err(malformed);
279 }
280 Ok(OccValue {
281 array: flags & OCC_FLAG_ARRAY != 0,
282 value,
283 payload,
284 entry: rest,
285 })
286}
287
288#[cfg(test)]
289mod codec_tests {
290 use super::*;
291 use crate::encoding::{decode_order_preserving, encode_order_preserving, logical_tag};
292 use crate::model::LogicalType;
293 use crate::value::Scalar;
294
295 struct Rng(u64);
297 impl Rng {
298 fn next(&mut self) -> u64 {
299 self.0 ^= self.0 << 13;
300 self.0 ^= self.0 >> 7;
301 self.0 ^= self.0 << 17;
302 self.0
303 }
304 }
305
306 fn scalar(rng: &mut Rng) -> (LogicalType, Scalar) {
307 let bits = rng.next();
308 match bits % 5 {
309 0 => (LogicalType::Boolean, Scalar::Boolean(bits & 8 != 0)),
310 1 => (LogicalType::Int64, Scalar::Int64(bits.cast_signed())),
311 2 => {
312 let f = f64::from_bits(rng.next());
313 let f = if f.is_finite() { f } else { -0.0 };
314 (LogicalType::Float64, Scalar::Float64(f))
315 }
316 3 => (
317 LogicalType::DateTime,
318 Scalar::DateTime((bits >> 2).cast_signed()),
319 ),
320 _ => {
321 let len = usize::try_from(rng.next() % (1 << 20)).unwrap();
323 let text: String = "aé€".chars().cycle().take(len / 3).collect();
324 (LogicalType::Utf8, Scalar::Utf8(text))
325 }
326 }
327 }
328
329 #[test]
330 fn occurrence_values_round_trip_for_every_type_null_and_array_state() {
331 let mut rng = Rng(0x2545_f491_4f6c_dd1d);
332 for _ in 0..200 {
333 let array = rng.next() & 1 == 1;
334 let (ty, value) = scalar(&mut rng);
335 let payload = encode_order_preserving(ty, &value).unwrap();
336 let raw = encode_occ_value(array, OccurrenceValue::Stored { ty }, Some(&payload));
337 let decoded = decode_occ_value(&raw).unwrap();
338 assert_eq!(decoded.array, array);
339 assert_eq!(decoded.value, OccurrenceValue::Stored { ty });
340 assert_eq!(decoded.payload, payload.as_slice());
341 assert_eq!(decoded.entry[0], logical_tag(ty));
342 assert_eq!(&decoded.entry[1..], payload.as_slice());
343 let back = decode_order_preserving(ty, decoded.payload).unwrap();
344 assert_eq!(
345 encode_order_preserving(ty, &back).unwrap(),
346 payload,
347 "{ty:?}"
348 );
349 }
350 for state in [OccurrenceValue::ExplicitNull, OccurrenceValue::EmptyArray] {
351 for array in [false, true] {
352 let raw = encode_occ_value(array, state, None);
353 let decoded = decode_occ_value(&raw).unwrap();
354 assert_eq!((decoded.array, decoded.value), (array, state));
355 assert!(decoded.payload.is_empty());
356 }
357 }
358 }
359
360 #[test]
361 fn malformed_occurrence_values_are_errors_never_panics() {
362 assert!(decode_occ_value(&[]).is_err());
363 assert!(decode_occ_value(&[0]).is_err());
364 assert!(decode_occ_value(&[0, OCC_STATE_NULL, 1]).is_err());
366 assert!(decode_occ_value(&[0, OCC_STATE_EMPTY_ARRAY, 1]).is_err());
367 assert!(decode_occ_value(&[0, 0x7F]).is_err());
369 let mut rng = Rng(0x9e37_79b9_7f4a_7c15);
370 for _ in 0..10_000 {
371 let len = usize::try_from(rng.next() % 24).unwrap();
372 let raw: Vec<u8> = (0..len).map(|_| rng.next().to_le_bytes()[0]).collect();
373 let _ = decode_occ_value(&raw);
375 }
376 }
377}