1use std::cmp::Ordering;
2use std::ops::Bound;
3
4use common::fmt::{EscapeIdent, EscapeRidKey};
5use rand::seq::IndexedRandom;
6use revision::revisioned;
7use storekey::{BorrowDecode, Encode};
8use surrealdb_cnf::ID_CHARS;
9use surrealdb_types::{SqlFormat, ToSql, write_sql};
10use ulid::Ulid;
11
12use crate::expr::{self};
13use crate::val::table_name_public::{IntoPublicTable, IntoTableName};
14use crate::val::{Array, IndexFormat, Number, Object, Range, Strand, TableName, Uuid, Value};
15
16#[revisioned(revision = 1)]
17#[derive(Clone, Debug, Eq, PartialEq, Hash, Encode, BorrowDecode)]
18#[storekey(format = "()")]
19#[storekey(format = "IndexFormat")]
20pub struct RecordIdKeyRange {
21 pub start: Bound<RecordIdKey>,
22 pub end: Bound<RecordIdKey>,
23}
24
25impl PartialOrd for RecordIdKeyRange {
26 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
27 Some(self.cmp(other))
28 }
29}
30
31impl Ord for RecordIdKeyRange {
32 fn cmp(&self, other: &Self) -> Ordering {
33 fn compare_bounds(a: &Bound<RecordIdKey>, b: &Bound<RecordIdKey>) -> Ordering {
34 match a {
35 Bound::Unbounded => match b {
36 Bound::Unbounded => Ordering::Equal,
37 _ => Ordering::Less,
38 },
39 Bound::Included(a) => match b {
40 Bound::Unbounded => Ordering::Greater,
41 Bound::Included(b) => a.cmp(b),
42 Bound::Excluded(_) => Ordering::Less,
43 },
44 Bound::Excluded(a) => match b {
45 Bound::Excluded(b) => a.cmp(b),
46 _ => Ordering::Greater,
47 },
48 }
49 }
50 match compare_bounds(&self.start, &other.end) {
51 Ordering::Equal => compare_bounds(&self.end, &other.end),
52 x => x,
53 }
54 }
55}
56
57impl ToSql for RecordIdKeyRange {
58 fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
59 match self.start {
60 Bound::Unbounded => {}
61 Bound::Included(ref x) => write_sql!(f, sql_fmt, "{x}"),
62 Bound::Excluded(ref x) => write_sql!(f, sql_fmt, "{x}>"),
63 }
64 write_sql!(f, sql_fmt, "..");
65 match self.end {
66 Bound::Unbounded => {}
67 Bound::Included(ref x) => write_sql!(f, sql_fmt, "={x}"),
68 Bound::Excluded(ref x) => write_sql!(f, sql_fmt, "{x}"),
69 }
70 }
71}
72
73impl TryFrom<RecordIdKeyRange> for crate::types::PublicRecordIdKeyRange {
74 type Error = anyhow::Error;
75
76 fn try_from(value: RecordIdKeyRange) -> Result<Self, Self::Error> {
77 Ok(crate::types::PublicRecordIdKeyRange {
78 start: match value.start {
79 Bound::Included(x) => Bound::Included(x.try_into()?),
80 Bound::Excluded(x) => Bound::Excluded(x.try_into()?),
81 Bound::Unbounded => Bound::Unbounded,
82 },
83 end: match value.end {
84 Bound::Included(x) => Bound::Included(x.try_into()?),
85 Bound::Excluded(x) => Bound::Excluded(x.try_into()?),
86 Bound::Unbounded => Bound::Unbounded,
87 },
88 })
89 }
90}
91
92impl From<crate::types::PublicRecordIdKeyRange> for RecordIdKeyRange {
93 fn from(value: crate::types::PublicRecordIdKeyRange) -> Self {
94 RecordIdKeyRange {
95 start: value.start.map(|x| x.into()),
96 end: value.end.map(|x| x.into()),
97 }
98 }
99}
100
101impl RecordIdKeyRange {
102 pub fn into_literal(self) -> expr::RecordIdKeyRangeLit {
103 let start = self.start.map(|x| x.into_literal());
104 let end = self.end.map(|x| x.into_literal());
105 expr::RecordIdKeyRangeLit {
106 start,
107 end,
108 }
109 }
110
111 pub fn into_value_range(self) -> Range {
113 Range {
114 start: self.start.map(|x| x.into_value()),
115 end: self.end.map(|x| x.into_value()),
116 }
117 }
118
119 pub fn from_value_range(range: Range) -> Option<Self> {
121 let start = match range.start {
122 Bound::Included(x) => Bound::Included(RecordIdKey::from_value(x)?),
123 Bound::Excluded(x) => Bound::Excluded(RecordIdKey::from_value(x)?),
124 Bound::Unbounded => Bound::Unbounded,
125 };
126 let end = match range.end {
127 Bound::Included(x) => Bound::Included(RecordIdKey::from_value(x)?),
128 Bound::Excluded(x) => Bound::Excluded(RecordIdKey::from_value(x)?),
129 Bound::Unbounded => Bound::Unbounded,
130 };
131
132 Some(RecordIdKeyRange {
133 start,
134 end,
135 })
136 }
137}
138
139impl PartialEq<Range> for RecordIdKeyRange {
140 fn eq(&self, other: &Range) -> bool {
141 (match self.start {
142 Bound::Included(ref a) => {
143 if let Bound::Included(ref b) = other.start {
144 a == b
145 } else {
146 false
147 }
148 }
149 Bound::Excluded(ref a) => {
150 if let Bound::Excluded(ref b) = other.start {
151 a == b
152 } else {
153 false
154 }
155 }
156 Bound::Unbounded => matches!(other.start, Bound::Unbounded),
157 }) && (match self.end {
158 Bound::Included(ref a) => {
159 if let Bound::Included(ref b) = other.end {
160 a == b
161 } else {
162 false
163 }
164 }
165 Bound::Excluded(ref a) => {
166 if let Bound::Excluded(ref b) = other.end {
167 a == b
168 } else {
169 false
170 }
171 }
172 Bound::Unbounded => matches!(other.end, Bound::Unbounded),
173 })
174 }
175}
176
177#[revisioned(revision = 1)]
178#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Encode, BorrowDecode)]
179#[storekey(format = "()")]
180#[storekey(format = "IndexFormat")]
181pub enum RecordIdKey {
182 Number(i64),
183 String(Strand),
184 Uuid(Uuid),
185 Array(Array),
186 Object(Object),
187 Range(Box<RecordIdKeyRange>),
188}
189
190#[derive(Clone, Debug)]
207pub struct RecordIdentity(pub RecordIdKey);
208
209fn addressed_by(key: &RecordIdKey) -> Vec<u8> {
216 storekey::encode_vec(key).unwrap_or_else(|_| format!("{key:?}").into_bytes())
217}
218
219impl RecordIdentity {
220 fn addressed_by(&self) -> Vec<u8> {
229 addressed_by(&self.0)
230 }
231}
232
233impl PartialEq for RecordIdentity {
234 fn eq(&self, other: &Self) -> bool {
235 self.addressed_by() == other.addressed_by()
236 }
237}
238
239impl Eq for RecordIdentity {}
240
241impl PartialOrd for RecordIdentity {
242 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
243 Some(self.cmp(other))
244 }
245}
246
247impl Ord for RecordIdentity {
248 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
249 self.addressed_by().cmp(&other.addressed_by())
250 }
251}
252
253impl std::hash::Hash for RecordIdentity {
254 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
255 self.addressed_by().hash(state);
256 }
257}
258
259impl RecordIdentity {
260 pub fn into_inner(self) -> RecordIdKey {
261 self.0
262 }
263}
264
265impl AsRef<RecordIdKey> for RecordIdentity {
266 fn as_ref(&self) -> &RecordIdKey {
267 &self.0
268 }
269}
270
271impl From<RecordIdKey> for RecordIdentity {
272 fn from(id: RecordIdKey) -> Self {
273 Self(id)
274 }
275}
276
277impl From<RecordIdentity> for RecordIdKey {
278 fn from(id: RecordIdentity) -> Self {
279 id.0
280 }
281}
282
283impl<F> Encode<F> for RecordIdentity {
284 fn encode<W: std::io::Write>(
285 &self,
286 w: &mut storekey::Writer<W>,
287 ) -> std::result::Result<(), storekey::EncodeError> {
288 Encode::<()>::encode(&self.0, w)
289 }
290}
291
292impl<'de, F> BorrowDecode<'de, F> for RecordIdentity {
293 fn borrow_decode(
294 r: &mut storekey::BorrowReader<'de>,
295 ) -> std::result::Result<Self, storekey::DecodeError> {
296 Ok(Self(BorrowDecode::<'de, ()>::borrow_decode(r)?))
297 }
298}
299
300impl RecordIdKey {
301 pub fn addresses_same_record(&self, other: &Self) -> bool {
314 addressed_by(self) == addressed_by(other)
315 }
316
317 pub fn hash_agrees_with_eq(&self) -> bool {
322 match self {
323 RecordIdKey::Number(_) | RecordIdKey::String(_) | RecordIdKey::Uuid(_) => true,
324 RecordIdKey::Array(a) => a.iter().all(Value::hash_agrees_with_eq),
325 RecordIdKey::Object(o) => o.values().all(Value::hash_agrees_with_eq),
326 RecordIdKey::Range(_) => false,
327 }
328 }
329
330 pub fn rand() -> Self {
332 let id: String = crate::rnd::with_rng(|rng| {
333 (0..20).map(|_| *ID_CHARS[..].choose(&mut *rng).unwrap_or(&'0')).collect()
334 });
335 Self::String(id.into())
336 }
337 pub fn ulid() -> Self {
339 Self::String(Ulid::new().to_string().into())
340 }
341 pub fn uuid() -> Self {
343 Self::Uuid(Uuid::new_v7())
344 }
345
346 pub fn is_range(&self) -> bool {
348 matches!(self, RecordIdKey::Range(_))
349 }
350
351 pub fn into_value(self) -> Value {
353 match self {
354 RecordIdKey::Number(n) => Value::Number(Number::Int(n)),
355 RecordIdKey::String(s) => Value::String(s),
356 RecordIdKey::Uuid(u) => Value::Uuid(u),
357 RecordIdKey::Object(object) => Value::Object(object),
358 RecordIdKey::Array(array) => Value::Array(array),
359 RecordIdKey::Range(range) => Value::Range(Box::new(Range {
360 start: range.start.map(RecordIdKey::into_value),
361 end: range.end.map(RecordIdKey::into_value),
362 })),
363 }
364 }
365
366 pub fn from_value(value: Value) -> Option<Self> {
370 match value {
376 Value::Number(Number::Int(i)) => Some(RecordIdKey::Number(i)),
377 Value::String(strand) => Some(RecordIdKey::String(strand)),
378 Value::Uuid(uuid) => Some(RecordIdKey::Uuid(uuid)),
381 Value::Array(array) => Some(RecordIdKey::Array(array)),
382 Value::Object(object) => Some(RecordIdKey::Object(object)),
383 Value::Range(range) => {
384 RecordIdKeyRange::from_value_range(*range).map(|x| RecordIdKey::Range(Box::new(x)))
385 }
386 _ => None,
387 }
388 }
389
390 pub fn into_literal(self) -> expr::RecordIdKeyLit {
392 match self {
393 RecordIdKey::Number(n) => expr::RecordIdKeyLit::Number(n),
394 RecordIdKey::String(s) => expr::RecordIdKeyLit::String(s),
395 RecordIdKey::Uuid(uuid) => expr::RecordIdKeyLit::Uuid(uuid),
396 RecordIdKey::Object(object) => expr::RecordIdKeyLit::Object(object.into_literal()),
397 RecordIdKey::Array(array) => expr::RecordIdKeyLit::Array(array.into_literal()),
398 RecordIdKey::Range(range) => {
399 expr::RecordIdKeyLit::Range(Box::new(range.into_literal()))
400 }
401 }
402 }
403}
404
405impl From<i64> for RecordIdKey {
406 fn from(value: i64) -> Self {
407 RecordIdKey::Number(value)
408 }
409}
410
411impl From<String> for RecordIdKey {
412 fn from(value: String) -> Self {
413 RecordIdKey::String(value.into())
414 }
415}
416
417impl From<Strand> for RecordIdKey {
418 fn from(value: Strand) -> Self {
419 RecordIdKey::String(value)
420 }
421}
422
423impl From<Uuid> for RecordIdKey {
424 fn from(value: Uuid) -> Self {
425 RecordIdKey::Uuid(value)
426 }
427}
428impl From<Object> for RecordIdKey {
429 fn from(value: Object) -> Self {
430 RecordIdKey::Object(value)
431 }
432}
433impl From<Array> for RecordIdKey {
434 fn from(value: Array) -> Self {
435 RecordIdKey::Array(value)
436 }
437}
438impl From<Box<RecordIdKeyRange>> for RecordIdKey {
439 fn from(value: Box<RecordIdKeyRange>) -> Self {
440 RecordIdKey::Range(value)
441 }
442}
443
444impl From<crate::types::PublicRecordIdKey> for RecordIdKey {
445 fn from(value: crate::types::PublicRecordIdKey) -> Self {
446 match value {
447 crate::types::PublicRecordIdKey::Number(x) => Self::Number(x),
448 crate::types::PublicRecordIdKey::String(x) => Self::String(x.into()),
449 crate::types::PublicRecordIdKey::Uuid(x) => Self::Uuid(x.into()),
450 crate::types::PublicRecordIdKey::Array(x) => Self::Array(x.into()),
451 crate::types::PublicRecordIdKey::Object(x) => Self::Object(x.into()),
452 crate::types::PublicRecordIdKey::Range(x) => Self::Range(Box::new((*x).into())),
453 }
454 }
455}
456
457impl TryFrom<RecordIdKey> for crate::types::PublicRecordIdKey {
458 type Error = anyhow::Error;
459
460 fn try_from(value: RecordIdKey) -> Result<Self, Self::Error> {
461 Ok(match value {
462 RecordIdKey::Number(x) => Self::Number(x),
463 RecordIdKey::String(x) => Self::String(x.into()),
464 RecordIdKey::Uuid(x) => Self::Uuid(x.into()),
465 RecordIdKey::Array(x) => Self::Array(x.try_into()?),
466 RecordIdKey::Object(x) => Self::Object(x.try_into()?),
467 RecordIdKey::Range(x) => Self::Range(Box::new((*x).try_into()?)),
468 })
469 }
470}
471
472impl PartialEq<Value> for RecordIdKey {
473 fn eq(&self, other: &Value) -> bool {
474 match self {
475 RecordIdKey::Number(a) => Value::Number(Number::Int(*a)) == *other,
476 RecordIdKey::String(a) => {
477 if let Value::String(b) = other {
478 a.as_str() == b.as_str()
479 } else {
480 false
481 }
482 }
483 RecordIdKey::Uuid(a) => {
484 if let Value::Uuid(b) = other {
485 a == b
486 } else {
487 false
488 }
489 }
490 RecordIdKey::Object(a) => {
491 if let Value::Object(b) = other {
492 a == b
493 } else {
494 false
495 }
496 }
497 RecordIdKey::Array(a) => {
498 if let Value::Array(b) = other {
499 a == b
500 } else {
501 false
502 }
503 }
504 RecordIdKey::Range(a) => {
505 if let Value::Range(b) = other {
506 **a == **b
507 } else {
508 false
509 }
510 }
511 }
512 }
513}
514
515impl ToSql for RecordIdKey {
516 fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
517 match self {
518 RecordIdKey::Number(n) => write_sql!(f, sql_fmt, "{n}"),
519 RecordIdKey::String(v) => write_sql!(f, sql_fmt, "{}", EscapeRidKey(v.as_str())),
520 RecordIdKey::Uuid(uuid) => write_sql!(f, sql_fmt, "{}", uuid),
521 RecordIdKey::Object(object) => write_sql!(f, sql_fmt, "{}", object),
522 RecordIdKey::Array(array) => write_sql!(f, sql_fmt, "{}", array),
523 RecordIdKey::Range(rid) => write_sql!(f, sql_fmt, "{}", rid),
524 }
525 }
526}
527
528#[revisioned(revision = 1)]
529#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Encode, BorrowDecode)]
530#[storekey(format = "()")]
531#[storekey(format = "IndexFormat")]
532pub struct RecordId {
533 pub table: TableName,
534 pub key: RecordIdKey,
535}
536
537impl RecordId {
538 pub fn new<K>(table: TableName, key: K) -> Self
540 where
541 RecordIdKey: From<K>,
542 {
543 RecordId {
544 table,
545 key: key.into(),
546 }
547 }
548
549 pub fn into_literal(self) -> expr::RecordIdLit {
551 expr::RecordIdLit {
552 table: self.table,
553 key: self.key.into_literal(),
554 }
555 }
556
557 pub fn is_table_type(&self, tables: &[TableName]) -> bool {
558 tables.is_empty() || tables.contains(&self.table)
559 }
560}
561
562impl TryFrom<RecordId> for crate::types::PublicRecordId {
563 type Error = anyhow::Error;
564
565 fn try_from(value: RecordId) -> Result<Self, Self::Error> {
566 Ok(crate::types::PublicRecordId {
567 table: value.table.into_public_table(),
568 key: value.key.try_into()?,
569 })
570 }
571}
572
573impl From<crate::types::PublicRecordId> for RecordId {
574 fn from(value: crate::types::PublicRecordId) -> Self {
575 RecordId {
576 table: value.table.into_table_name(),
577 key: RecordIdKey::from(value.key),
578 }
579 }
580}
581
582impl ToSql for RecordId {
583 fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
584 write_sql!(f, sql_fmt, "{}:{}", EscapeIdent(self.table.as_str()), self.key)
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596
597 #[test]
609 fn record_ids_render_so_they_can_be_read_back() {
610 for name in ["select", "1a", "9lives", "table", "person", "_123", "a-b"] {
611 let rid = RecordId {
612 table: TableName::from(name),
613 key: RecordIdKey::Number(1),
614 };
615 let rendered = rid.to_sql();
616 let reparsed = crate::syn::expr(&rendered)
617 .unwrap_or_else(|e| panic!("{rendered} does not parse back: {e}"));
618 let crate::sql::Expr::Literal(crate::sql::Literal::RecordId(lit)) = &reparsed else {
619 panic!("{rendered} parsed as {reparsed:?}, not a record id");
620 };
621 assert_eq!(lit.table.as_str(), name, "{rendered} round-tripped to a different table");
622 }
623 }
624
625 #[test]
640 fn index_format_does_not_round_trip_a_compound_record_id() {
641 fn arr(n: Number) -> RecordIdKey {
642 RecordIdKey::Array(Array::from(vec![Value::Number(n)]))
643 }
644 fn record_key(key: &RecordIdKey) -> Vec<u8> {
646 storekey::encode_vec(key).unwrap()
647 }
648 fn through_index_format(key: &RecordIdKey) -> RecordIdKey {
650 let enc = storekey::encode_vec_format::<IndexFormat, _>(key).unwrap();
651 storekey::decode_borrow_format::<IndexFormat, _>(&enc).unwrap()
652 }
653
654 let int = arr(Number::Int(1));
655 let float = arr(Number::Float(1.5));
656 let dec = arr(Number::Decimal(rust_decimal::Decimal::from(1)));
657
658 assert!(matches!(through_index_format(&int), RecordIdKey::Array(ref a)
660 if matches!(a.first(), Some(Value::Number(Number::Decimal(_))))));
661 assert_ne!(
662 record_key(&through_index_format(&int)),
663 record_key(&int),
664 "`[1]` does not address its own record after an IndexFormat round trip"
665 );
666 assert_ne!(
667 record_key(&through_index_format(&float)),
668 record_key(&float),
669 "`[1.5f]` does not address its own record after an IndexFormat round trip"
670 );
671 assert_eq!(record_key(&through_index_format(&dec)), record_key(&dec));
673
674 assert_ne!(record_key(&int), record_key(&dec), "they are distinct records");
677 assert_eq!(
678 storekey::encode_vec_format::<IndexFormat, _>(&int).unwrap(),
679 storekey::encode_vec_format::<IndexFormat, _>(&dec).unwrap(),
680 "`[1]` and `[1dec]` encode to the same IndexFormat key"
681 );
682
683 let back: RecordIdKey = storekey::decode_borrow(&record_key(&int)).unwrap();
685 assert_eq!(record_key(&back), record_key(&int));
686 }
687
688 #[test]
692 fn record_identity_round_trips_under_index_format() {
693 fn arr(n: Number) -> RecordIdKey {
694 RecordIdKey::Array(Array::from(vec![Value::Number(n)]))
695 }
696 fn through_index_format(key: &RecordIdKey) -> RecordIdKey {
697 let id = RecordIdentity(key.clone());
698 let enc = storekey::encode_vec_format::<IndexFormat, _>(&id).unwrap();
699 storekey::decode_borrow_format::<IndexFormat, RecordIdentity>(&enc)
700 .unwrap()
701 .into_inner()
702 }
703
704 let int = arr(Number::Int(1));
705 let float = arr(Number::Float(1.5));
706 let dec = arr(Number::Decimal(rust_decimal::Decimal::from(1)));
707 let nested = RecordIdKey::Object(
708 [("k".to_owned(), Value::Number(Number::Int(2)))].into_iter().collect(),
709 );
710
711 for id in [&int, &float, &dec, &nested] {
712 assert_eq!(
713 storekey::encode_vec(&through_index_format(id)).unwrap(),
714 storekey::encode_vec(id).unwrap(),
715 "{id:?} must address its own record after the round trip"
716 );
717 }
718
719 assert_ne!(
721 storekey::encode_vec_format::<IndexFormat, _>(&RecordIdentity(int)).unwrap(),
722 storekey::encode_vec_format::<IndexFormat, _>(&RecordIdentity(dec)).unwrap(),
723 "`[1]` and `[1dec]` must not share a key"
724 );
725 }
726}
727
728surrealdb_kvs::impl_kv_value_revisioned!(RecordId);
729surrealdb_kvs::impl_kv_value_revisioned!(RecordIdKey);
730
731#[cfg(test)]
732mod record_identity_comparison_tests {
733 use std::collections::HashSet;
734
735 use super::*;
736
737 fn identity(n: Number) -> RecordIdentity {
738 RecordIdentity(RecordIdKey::Array(vec![Value::Number(n)].into()))
739 }
740
741 #[test]
745 fn identities_differing_only_in_numeric_variant_are_not_equal() {
746 let int = identity(Number::Int(1));
747 let dec = identity(Number::Decimal(1.into()));
748
749 assert_ne!(int, dec, "the identities must not collapse");
750 assert_ne!(
751 storekey::encode_vec(&int.0).unwrap(),
752 storekey::encode_vec(&dec.0).unwrap(),
753 "...because they are stored under different keys"
754 );
755 assert_eq!(
756 HashSet::from([int.clone(), dec.clone()]).len(),
757 2,
758 "and a set keyed by identity must hold both"
759 );
760 assert_ne!(int.cmp(&dec), std::cmp::Ordering::Equal, "ordering must agree");
761 assert_eq!(int, int.clone(), "an identity still equals itself");
762 }
763}