1use std::sync::atomic::{AtomicU64, Ordering};
2
3use serde::{Deserialize, Serialize};
4use ulid::Ulid;
5use web_time::{SystemTime, UNIX_EPOCH};
6
7static ULID_STATE: AtomicU64 = AtomicU64::new(0);
12
13pub(crate) fn new_ulid_pub() -> Ulid {
14 new_ulid()
15}
16
17fn new_ulid() -> Ulid {
18 let ms = SystemTime::now()
19 .duration_since(UNIX_EPOCH)
20 .map(|d| d.as_millis() as u64)
21 .unwrap_or(0);
22
23 let seq = loop {
26 let cur = ULID_STATE.load(Ordering::Relaxed);
27 let cur_ms = cur >> 16;
28 let cur_seq = (cur & 0xFFFF) as u16;
29 let (next_ms, next_seq) = if ms == cur_ms {
30 (cur_ms, cur_seq.wrapping_add(1))
31 } else {
32 (ms, 0u16)
33 };
34 let packed = (next_ms << 16) | u64::from(next_seq);
35 if ULID_STATE
36 .compare_exchange_weak(cur, packed, Ordering::Relaxed, Ordering::Relaxed)
37 .is_ok()
38 {
39 break next_seq;
40 }
41 };
42
43 let mut buf = [0u8; 10];
47 getrandom::fill(&mut buf)
48 .expect("taladb: system entropy source failed while generating a ULID");
49 let rand_lo = u64::from_le_bytes(buf[..8].try_into().unwrap());
51 let random = (u128::from(seq) << 64) | u128::from(rand_lo);
52 Ulid::from_parts(ms, random)
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58pub enum Value {
59 Null,
60 Bool(bool),
61 Int(i64),
62 Float(f64),
63 Str(String),
64 Bytes(Vec<u8>),
65 Array(Vec<Self>),
66 Object(Vec<(String, Self)>),
67}
68
69impl Value {
70 pub fn type_name(&self) -> &'static str {
71 match self {
72 Self::Null => "null",
73 Self::Bool(_) => "bool",
74 Self::Int(_) => "int",
75 Self::Float(_) => "float",
76 Self::Str(_) => "string",
77 Self::Bytes(_) => "bytes",
78 Self::Array(_) => "array",
79 Self::Object(_) => "object",
80 }
81 }
82
83 pub fn as_bool(&self) -> Option<bool> {
84 if let Self::Bool(b) = self {
85 Some(*b)
86 } else {
87 None
88 }
89 }
90
91 pub fn as_int(&self) -> Option<i64> {
92 if let Self::Int(n) = self {
93 Some(*n)
94 } else {
95 None
96 }
97 }
98
99 pub fn as_float(&self) -> Option<f64> {
100 if let Self::Float(f) = self {
101 Some(*f)
102 } else {
103 None
104 }
105 }
106
107 pub fn as_str(&self) -> Option<&str> {
108 if let Self::Str(s) = self {
109 Some(s.as_str())
110 } else {
111 None
112 }
113 }
114
115 pub fn as_bytes(&self) -> Option<&[u8]> {
116 if let Self::Bytes(b) = self {
117 Some(b.as_slice())
118 } else {
119 None
120 }
121 }
122
123 pub fn as_array(&self) -> Option<&[Self]> {
124 if let Self::Array(a) = self {
125 Some(a.as_slice())
126 } else {
127 None
128 }
129 }
130
131 pub fn as_object(&self) -> Option<&[(String, Self)]> {
133 if let Self::Object(o) = self {
134 Some(o.as_slice())
135 } else {
136 None
137 }
138 }
139
140 pub fn get(&self, key: &str) -> Option<&Self> {
147 self.as_object()?
148 .iter()
149 .find(|(k, _)| k == key)
150 .map(|(_, v)| v)
151 }
152
153 pub fn is_null(&self) -> bool {
155 matches!(self, Self::Null)
156 }
157
158 pub fn partial_cmp_numeric(&self, other: &Self) -> Option<std::cmp::Ordering> {
161 match (self, other) {
162 (Self::Int(a), Self::Int(b)) => a.partial_cmp(b),
163 (Self::Float(a), Self::Float(b)) => a.partial_cmp(b),
164 (Self::Int(a), Self::Float(b)) => cmp_i64_f64(*a, *b),
165 (Self::Float(a), Self::Int(b)) => cmp_i64_f64(*b, *a).map(std::cmp::Ordering::reverse),
166 (Self::Str(a), Self::Str(b)) => a.partial_cmp(b),
167 (Self::Bool(a), Self::Bool(b)) => a.partial_cmp(b),
168 _ => None,
169 }
170 }
171}
172
173macro_rules! value_from {
180 ($($ty:ty => $variant:ident $(as $cast:ty)?),* $(,)?) => {
181 $(
182 impl From<$ty> for Value {
183 fn from(v: $ty) -> Self {
184 Value::$variant(v $(as $cast)?)
185 }
186 }
187 )*
188 };
189}
190
191value_from! {
192 bool => Bool,
193 i8 => Int as i64,
194 i16 => Int as i64,
195 i32 => Int as i64,
196 i64 => Int,
197 u8 => Int as i64,
198 u16 => Int as i64,
199 u32 => Int as i64,
200 f32 => Float as f64,
201 f64 => Float,
202 String => Str,
203 Vec<u8> => Bytes,
204 Vec<Value> => Array,
205 Vec<(String, Value)> => Object,
206}
207
208impl From<&str> for Value {
209 fn from(v: &str) -> Self {
210 Self::Str(v.to_owned())
211 }
212}
213
214impl From<&[u8]> for Value {
215 fn from(v: &[u8]) -> Self {
216 Self::Bytes(v.to_vec())
217 }
218}
219
220impl<T: Into<Self>> From<Option<T>> for Value {
223 fn from(v: Option<T>) -> Self {
224 v.map_or(Self::Null, Into::into)
225 }
226}
227
228impl<T: Into<Self>> FromIterator<T> for Value {
229 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
230 Self::Array(iter.into_iter().map(Into::into).collect())
231 }
232}
233
234impl std::fmt::Display for Value {
240 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241 match self {
242 Self::Null => f.write_str("null"),
243 Self::Bool(b) => write!(f, "{b}"),
244 Self::Int(n) => write!(f, "{n}"),
245 Self::Float(x) => write!(f, "{x}"),
246 Self::Str(s) => f.write_str(s),
247 Self::Bytes(b) => write!(f, "<{} bytes>", b.len()),
248 Self::Array(items) => {
249 f.write_str("[")?;
250 for (i, item) in items.iter().enumerate() {
251 if i > 0 {
252 f.write_str(", ")?;
253 }
254 write!(f, "{item}")?;
255 }
256 f.write_str("]")
257 }
258 Self::Object(fields) => {
259 f.write_str("{")?;
260 for (i, (key, value)) in fields.iter().enumerate() {
261 if i > 0 {
262 f.write_str(", ")?;
263 }
264 write!(f, "{key}: {value}")?;
265 }
266 f.write_str("}")
267 }
268 }
269 }
270}
271
272fn cmp_i64_f64(a: i64, b: f64) -> Option<std::cmp::Ordering> {
285 use std::cmp::Ordering;
286
287 if b.is_nan() {
288 return None;
289 }
290 const TWO_POW_63: f64 = 9_223_372_036_854_775_808.0;
293 if b >= TWO_POW_63 {
294 return Some(Ordering::Less);
295 }
296 if b < -TWO_POW_63 {
297 return Some(Ordering::Greater);
298 }
299
300 let truncated = b.trunc();
302 let whole = truncated as i64;
303 match a.cmp(&whole) {
304 Ordering::Equal => Some(if b > truncated {
308 Ordering::Less
309 } else if b < truncated {
310 Ordering::Greater
311 } else {
312 Ordering::Equal
313 }),
314 ord => Some(ord),
315 }
316}
317
318const FNV1A128_OFFSET_BASIS: u128 = 0x6c62_272e_07bb_0142_62b8_2175_6295_c58d;
326const FNV1A128_PRIME: u128 = 0x0000_0000_0100_0000_0000_0000_0000_013b;
327
328pub fn derive_doc_id(collection: &str, key: &str) -> Ulid {
348 const SEPARATOR: [u8; 1] = [0u8];
351 let mut hash = FNV1A128_OFFSET_BASIS;
352 for &byte in collection
353 .as_bytes()
354 .iter()
355 .chain(SEPARATOR.iter())
356 .chain(key.as_bytes().iter())
357 {
358 hash ^= u128::from(byte);
359 hash = hash.wrapping_mul(FNV1A128_PRIME);
360 }
361 Ulid::from_bytes(hash.to_be_bytes())
362}
363
364#[derive(Debug, Clone, Serialize, Deserialize)]
366pub struct Document {
367 pub id: Ulid,
368 pub fields: Vec<(String, Value)>,
369}
370
371impl Document {
372 pub fn new(fields: Vec<(String, Value)>) -> Self {
373 Self {
374 id: new_ulid(),
375 fields: dedupe_fields(fields),
376 }
377 }
378
379 pub fn with_id(id: Ulid, fields: Vec<(String, Value)>) -> Self {
380 Self {
381 id,
382 fields: dedupe_fields(fields),
383 }
384 }
385
386 pub fn get(&self, key: &str) -> Option<&Value> {
392 if let Some(dot) = key.find('.') {
393 let (head, tail) = (&key[..dot], &key[dot + 1..]);
394 let parent = self
395 .fields
396 .iter()
397 .find(|(k, _)| k == head)
398 .map(|(_, v)| v)?;
399 value_get_nested(parent, tail)
400 } else {
401 self.fields.iter().find(|(k, _)| k == key).map(|(_, v)| v)
402 }
403 }
404
405 pub fn set(&mut self, key: impl Into<String>, value: Value) {
406 let key = key.into();
407 if let Some(entry) = self.fields.iter_mut().find(|(k, _)| k == &key) {
408 entry.1 = value;
409 } else {
410 self.fields.push((key, value));
411 }
412 }
413
414 pub fn remove(&mut self, key: &str) -> Option<Value> {
415 if let Some(pos) = self.fields.iter().position(|(k, _)| k == key) {
416 Some(self.fields.remove(pos).1)
417 } else {
418 None
419 }
420 }
421
422 pub fn contains_key(&self, key: &str) -> bool {
423 self.get(key).is_some()
424 }
425}
426
427fn dedupe_fields(fields: Vec<(String, Value)>) -> Vec<(String, Value)> {
434 let has_dup = fields
436 .iter()
437 .enumerate()
438 .any(|(i, (k, _))| fields[..i].iter().any(|(k2, _)| k2 == k));
439 if !has_dup {
440 return fields;
441 }
442 let mut out: Vec<(String, Value)> = Vec::with_capacity(fields.len());
443 for (k, v) in fields {
444 if !out.iter().any(|(k2, _)| k2 == &k) {
445 out.push((k, v));
446 }
447 }
448 out
449}
450
451pub fn value_get_nested<'a>(val: &'a Value, path: &str) -> Option<&'a Value> {
456 value_get_nested_depth(val, path, 0)
457}
458
459fn value_get_nested_depth<'a>(val: &'a Value, path: &str, depth: u8) -> Option<&'a Value> {
460 if depth >= 64 {
461 return None;
462 }
463 if let Some(dot) = path.find('.') {
464 let (head, tail) = (&path[..dot], &path[dot + 1..]);
465 match val {
466 Value::Object(fields) => {
467 let child = fields.iter().find(|(k, _)| k == head).map(|(_, v)| v)?;
468 value_get_nested_depth(child, tail, depth + 1)
469 }
470 _ => None,
471 }
472 } else {
473 match val {
474 Value::Object(fields) => fields.iter().find(|(k, _)| k == path).map(|(_, v)| v),
475 _ => None,
476 }
477 }
478}
479
480#[cfg(test)]
481mod tests {
482 use super::*;
483 use std::cmp::Ordering;
484
485 #[test]
488 fn int_float_comparison_is_exact_beyond_2_pow_53() {
489 let big = Value::Int(9_007_199_254_740_993);
492 let as_float = Value::Float(9_007_199_254_740_992.0);
493 assert_eq!(big.partial_cmp_numeric(&as_float), Some(Ordering::Greater));
494 assert_eq!(as_float.partial_cmp_numeric(&big), Some(Ordering::Less));
495
496 let small = Value::Int(-9_007_199_254_740_993);
498 let small_float = Value::Float(-9_007_199_254_740_992.0);
499 assert_eq!(
500 small.partial_cmp_numeric(&small_float),
501 Some(Ordering::Less)
502 );
503 assert_eq!(
504 small_float.partial_cmp_numeric(&small),
505 Some(Ordering::Greater)
506 );
507 }
508
509 #[test]
512 fn int_float_comparison_handles_out_of_range_floats() {
513 let max = Value::Int(i64::MAX);
514 let min = Value::Int(i64::MIN);
515
516 assert_eq!(
517 max.partial_cmp_numeric(&Value::Float(1e300)),
518 Some(Ordering::Less)
519 );
520 assert_eq!(
521 min.partial_cmp_numeric(&Value::Float(-1e300)),
522 Some(Ordering::Greater)
523 );
524 assert_eq!(
525 max.partial_cmp_numeric(&Value::Float(f64::INFINITY)),
526 Some(Ordering::Less)
527 );
528 assert_eq!(
529 min.partial_cmp_numeric(&Value::Float(f64::NEG_INFINITY)),
530 Some(Ordering::Greater)
531 );
532 }
533
534 #[test]
535 fn int_float_comparison_handles_fractions_and_nan() {
536 assert_eq!(
538 Value::Int(3).partial_cmp_numeric(&Value::Float(3.5)),
539 Some(Ordering::Less)
540 );
541 assert_eq!(
542 Value::Int(-3).partial_cmp_numeric(&Value::Float(-3.5)),
543 Some(Ordering::Greater)
544 );
545 assert_eq!(
546 Value::Int(3).partial_cmp_numeric(&Value::Float(3.0)),
547 Some(Ordering::Equal)
548 );
549
550 assert_eq!(
552 Value::Int(1).partial_cmp_numeric(&Value::Float(f64::NAN)),
553 None
554 );
555 assert_eq!(
556 Value::Float(f64::NAN).partial_cmp_numeric(&Value::Int(1)),
557 None
558 );
559 }
560
561 #[test]
562 fn value_from_impls_cover_the_rust_types_that_map_onto_it() {
563 assert_eq!(Value::from(true), Value::Bool(true));
564 assert_eq!(Value::from(42i32), Value::Int(42));
565 assert_eq!(Value::from(42u8), Value::Int(42));
566 assert_eq!(Value::from(1.5f32), Value::Float(1.5));
567 assert_eq!(Value::from("hi"), Value::Str("hi".into()));
568 assert_eq!(Value::from(String::from("hi")), Value::Str("hi".into()));
569 assert_eq!(Value::from(vec![1u8, 2]), Value::Bytes(vec![1, 2]));
570
571 assert_eq!(Value::from(None::<i64>), Value::Null);
573 assert_eq!(Value::from(Some(7i64)), Value::Int(7));
574
575 let arr: Value = [1i64, 2, 3].into_iter().collect();
577 assert_eq!(
578 arr,
579 Value::Array(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
580 );
581 }
582
583 #[test]
584 fn value_accessors_cover_every_variant() {
585 assert_eq!(Value::Bool(true).as_bool(), Some(true));
586 assert_eq!(Value::Int(1).as_int(), Some(1));
587 assert_eq!(Value::Float(1.5).as_float(), Some(1.5));
588 assert_eq!(Value::Str("s".into()).as_str(), Some("s"));
589 assert_eq!(Value::Bytes(vec![1, 2]).as_bytes(), Some(&[1u8, 2][..]));
590 assert_eq!(
591 Value::Array(vec![Value::Int(1)]).as_array().unwrap().len(),
592 1
593 );
594 assert!(Value::Null.is_null());
595
596 let obj = Value::Object(vec![("a".into(), Value::Int(1))]);
597 assert_eq!(obj.as_object().unwrap().len(), 1);
598 assert_eq!(obj.get("a"), Some(&Value::Int(1)));
599 assert_eq!(obj.get("missing"), None);
600
601 assert_eq!(Value::Int(1).as_str(), None);
603 assert_eq!(Value::Str("1".into()).as_int(), None);
604 assert_eq!(Value::Int(1).get("a"), None);
605 }
606
607 #[test]
608 fn value_display_is_readable_and_hides_byte_contents() {
609 assert_eq!(Value::Null.to_string(), "null");
610 assert_eq!(Value::Int(1887).to_string(), "1887");
611 assert_eq!(Value::Str("Rizal".into()).to_string(), "Rizal");
612 assert_eq!(Value::Bytes(vec![0; 500]).to_string(), "<500 bytes>");
615 assert_eq!(
616 Value::Array(vec![Value::Int(1), Value::Str("a".into())]).to_string(),
617 "[1, a]"
618 );
619 assert_eq!(
620 Value::Object(vec![
621 ("a".into(), Value::Int(1)),
622 ("b".into(), Value::Bool(false))
623 ])
624 .to_string(),
625 "{a: 1, b: false}"
626 );
627 }
628
629 #[test]
630 fn round_trip_postcard() {
631 let doc = Document::new(vec![
632 ("name".into(), Value::Str("Alice".into())),
633 ("age".into(), Value::Int(30)),
634 ("active".into(), Value::Bool(true)),
635 ("score".into(), Value::Float(9.5)),
636 (
637 "tags".into(),
638 Value::Array(vec![Value::Str("rust".into()), Value::Str("db".into())]),
639 ),
640 ]);
641
642 let bytes = postcard::to_allocvec(&doc).unwrap();
643 let decoded: Document = postcard::from_bytes(&bytes).unwrap();
644
645 assert_eq!(doc.id, decoded.id);
646 assert_eq!(doc.fields, decoded.fields);
647 }
648
649 #[test]
650 fn document_get_set_remove() {
651 let mut doc = Document::new(vec![("x".into(), Value::Int(1))]);
652 assert_eq!(doc.get("x"), Some(&Value::Int(1)));
653 doc.set("x", Value::Int(2));
654 assert_eq!(doc.get("x"), Some(&Value::Int(2)));
655 doc.set("y", Value::Bool(true));
656 assert_eq!(doc.get("y"), Some(&Value::Bool(true)));
657 doc.remove("x");
658 assert_eq!(doc.get("x"), None);
659 }
660
661 #[test]
662 fn derive_doc_id_is_deterministic() {
663 assert_eq!(
664 derive_doc_id("products", "sku-123"),
665 derive_doc_id("products", "sku-123")
666 );
667 }
668
669 #[test]
670 fn derive_doc_id_separates_collections() {
671 assert_ne!(derive_doc_id("products", "1"), derive_doc_id("orders", "1"));
673 }
674
675 #[test]
676 fn derive_doc_id_separator_prevents_boundary_collision() {
677 assert_ne!(derive_doc_id("ab", "c"), derive_doc_id("a", "bc"));
679 }
680
681 #[test]
686 fn derive_doc_id_cross_language_vectors() {
687 let vectors: &[(&str, &str, &str)] = &[
688 ("products", "sku-123", "56GC678DQYWW1Z98HPYJ90WVKH"),
689 ("products", "1", "7Z6Y6H8NG96ZGN4PJVDP18CSY2"),
690 ("orders", "1", "5VYD63GDV5KCDXV2A0SYRWSKVZ"),
691 ("", "", "6J535PJ40THJQQH49BE174M53Z"),
692 ("products", "sku-ñ-💡", "5NZ0PGNM2CF0BTHN0AAX8CWPAA"),
695 ];
696 for (collection, key, expected) in vectors {
697 assert_eq!(
698 derive_doc_id(collection, key).to_string(),
699 *expected,
700 "derive_doc_id({collection:?}, {key:?}) drifted — \
701 the TypeScript implementation must match these exactly"
702 );
703 }
704 }
705}