1use crate::{
2 model::field::FieldKind,
3 traits::FieldValueKind,
4 value::{CoercionFamily, TextMode, Value},
5};
6use std::{
7 cmp::Ordering,
8 collections::BTreeMap,
9 fmt,
10 mem::discriminant,
11 ops::{BitAnd, BitOr},
12};
13use thiserror::Error as ThisError;
14
15#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
24pub enum CoercionId {
25 Strict,
26 NumericWiden,
27 TextCasefold,
28 CollectionElement,
29}
30
31impl CoercionId {
32 #[must_use]
34 pub const fn plan_hash_tag(self) -> u8 {
35 match self {
36 Self::Strict => 0x01,
37 Self::NumericWiden => 0x02,
38 Self::TextCasefold => 0x04,
39 Self::CollectionElement => 0x05,
40 }
41 }
42}
43
44#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
50pub enum UnsupportedQueryFeature {
51 #[error(
52 "map field '{field}' is not queryable; map predicates are disabled. model queryable attributes as scalar/indexed fields or list entries"
53 )]
54 MapPredicate { field: String },
55}
56
57#[derive(Clone, Debug, Eq, PartialEq)]
69pub(crate) enum ScalarType {
70 Account,
71 Blob,
72 Bool,
73 Date,
74 Decimal,
75 Duration,
76 Enum,
77 Float32,
78 Float64,
79 Int,
80 Int128,
81 IntBig,
82 Principal,
83 Subaccount,
84 Text,
85 Timestamp,
86 Uint,
87 Uint128,
88 UintBig,
89 Ulid,
90 Unit,
91}
92
93macro_rules! scalar_coercion_family_from_registry {
95 ( @args $self:expr; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
96 match $self {
97 $( ScalarType::$scalar => $coercion_family, )*
98 }
99 };
100}
101
102macro_rules! scalar_matches_value_from_registry {
103 ( @args $self:expr, $value:expr; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
104 matches!(
105 ($self, $value),
106 $( (ScalarType::$scalar, $value_pat) )|*
107 )
108 };
109}
110
111macro_rules! scalar_supports_numeric_coercion_from_registry {
112 ( @args $self:expr; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
113 match $self {
114 $( ScalarType::$scalar => $supports_numeric_coercion, )*
115 }
116 };
117}
118
119macro_rules! scalar_is_keyable_from_registry {
120 ( @args $self:expr; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
121 match $self {
122 $( ScalarType::$scalar => $is_keyable, )*
123 }
124 };
125}
126
127macro_rules! scalar_supports_ordering_from_registry {
128 ( @args $self:expr; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
129 match $self {
130 $( ScalarType::$scalar => $supports_ordering, )*
131 }
132 };
133}
134
135impl ScalarType {
136 #[must_use]
137 pub(crate) const fn coercion_family(&self) -> CoercionFamily {
138 scalar_registry!(scalar_coercion_family_from_registry, self)
139 }
140
141 #[must_use]
142 pub(crate) const fn is_orderable(&self) -> bool {
143 self.supports_ordering()
146 }
147
148 #[must_use]
149 pub(crate) const fn matches_value(&self, value: &Value) -> bool {
150 scalar_registry!(scalar_matches_value_from_registry, self, value)
151 }
152
153 #[must_use]
154 pub(crate) const fn supports_numeric_coercion(&self) -> bool {
155 scalar_registry!(scalar_supports_numeric_coercion_from_registry, self)
156 }
157
158 #[must_use]
159 pub(crate) const fn is_keyable(&self) -> bool {
160 scalar_registry!(scalar_is_keyable_from_registry, self)
161 }
162
163 #[must_use]
164 pub(crate) const fn supports_ordering(&self) -> bool {
165 scalar_registry!(scalar_supports_ordering_from_registry, self)
166 }
167}
168
169#[derive(Clone, Debug, Eq, PartialEq)]
180pub(crate) enum FieldType {
181 Scalar(ScalarType),
182 List(Box<Self>),
183 Set(Box<Self>),
184 Map { key: Box<Self>, value: Box<Self> },
185 Structured { queryable: bool },
186}
187
188impl FieldType {
189 #[must_use]
190 pub(crate) const fn value_kind(&self) -> FieldValueKind {
191 match self {
192 Self::Scalar(_) => FieldValueKind::Atomic,
193 Self::List(_) | Self::Set(_) => FieldValueKind::Structured { queryable: true },
194 Self::Map { .. } => FieldValueKind::Structured { queryable: false },
195 Self::Structured { queryable } => FieldValueKind::Structured {
196 queryable: *queryable,
197 },
198 }
199 }
200
201 #[must_use]
202 pub(crate) const fn coercion_family(&self) -> Option<CoercionFamily> {
203 match self {
204 Self::Scalar(inner) => Some(inner.coercion_family()),
205 Self::List(_) | Self::Set(_) | Self::Map { .. } => Some(CoercionFamily::Collection),
206 Self::Structured { .. } => None,
207 }
208 }
209
210 #[must_use]
211 pub(crate) const fn is_text(&self) -> bool {
212 matches!(self, Self::Scalar(ScalarType::Text))
213 }
214
215 #[must_use]
216 pub(crate) const fn is_collection(&self) -> bool {
217 matches!(self, Self::List(_) | Self::Set(_) | Self::Map { .. })
218 }
219
220 #[must_use]
221 pub(crate) const fn is_list_like(&self) -> bool {
222 matches!(self, Self::List(_) | Self::Set(_))
223 }
224
225 #[must_use]
226 pub(crate) const fn is_orderable(&self) -> bool {
227 match self {
228 Self::Scalar(inner) => inner.is_orderable(),
229 _ => false,
230 }
231 }
232
233 #[must_use]
234 pub(crate) const fn is_keyable(&self) -> bool {
235 match self {
236 Self::Scalar(inner) => inner.is_keyable(),
237 _ => false,
238 }
239 }
240
241 #[must_use]
242 pub(crate) const fn supports_numeric_coercion(&self) -> bool {
243 match self {
244 Self::Scalar(inner) => inner.supports_numeric_coercion(),
245 _ => false,
246 }
247 }
248}
249
250pub(crate) fn literal_matches_type(literal: &Value, field_type: &FieldType) -> bool {
251 match field_type {
252 FieldType::Scalar(inner) => inner.matches_value(literal),
253 FieldType::List(element) | FieldType::Set(element) => match literal {
254 Value::List(items) => items.iter().all(|item| literal_matches_type(item, element)),
255 _ => false,
256 },
257 FieldType::Map { key, value } => match literal {
258 Value::Map(entries) => {
259 if Value::validate_map_entries(entries.as_slice()).is_err() {
260 return false;
261 }
262
263 entries.iter().all(|(entry_key, entry_value)| {
264 literal_matches_type(entry_key, key) && literal_matches_type(entry_value, value)
265 })
266 }
267 _ => false,
268 },
269 FieldType::Structured { .. } => {
270 false
272 }
273 }
274}
275
276pub(super) fn field_type_from_model_kind(kind: &FieldKind) -> FieldType {
277 match kind {
278 FieldKind::Account => FieldType::Scalar(ScalarType::Account),
279 FieldKind::Blob => FieldType::Scalar(ScalarType::Blob),
280 FieldKind::Bool => FieldType::Scalar(ScalarType::Bool),
281 FieldKind::Date => FieldType::Scalar(ScalarType::Date),
282 FieldKind::Decimal { .. } => FieldType::Scalar(ScalarType::Decimal),
283 FieldKind::Duration => FieldType::Scalar(ScalarType::Duration),
284 FieldKind::Enum { .. } => FieldType::Scalar(ScalarType::Enum),
285 FieldKind::Float32 => FieldType::Scalar(ScalarType::Float32),
286 FieldKind::Float64 => FieldType::Scalar(ScalarType::Float64),
287 FieldKind::Int => FieldType::Scalar(ScalarType::Int),
288 FieldKind::Int128 => FieldType::Scalar(ScalarType::Int128),
289 FieldKind::IntBig => FieldType::Scalar(ScalarType::IntBig),
290 FieldKind::Principal => FieldType::Scalar(ScalarType::Principal),
291 FieldKind::Subaccount => FieldType::Scalar(ScalarType::Subaccount),
292 FieldKind::Text => FieldType::Scalar(ScalarType::Text),
293 FieldKind::Timestamp => FieldType::Scalar(ScalarType::Timestamp),
294 FieldKind::Uint => FieldType::Scalar(ScalarType::Uint),
295 FieldKind::Uint128 => FieldType::Scalar(ScalarType::Uint128),
296 FieldKind::UintBig => FieldType::Scalar(ScalarType::UintBig),
297 FieldKind::Ulid => FieldType::Scalar(ScalarType::Ulid),
298 FieldKind::Unit => FieldType::Scalar(ScalarType::Unit),
299 FieldKind::Relation { key_kind, .. } => field_type_from_model_kind(key_kind),
300 FieldKind::List(inner) => FieldType::List(Box::new(field_type_from_model_kind(inner))),
301 FieldKind::Set(inner) => FieldType::Set(Box::new(field_type_from_model_kind(inner))),
302 FieldKind::Map { key, value } => FieldType::Map {
303 key: Box::new(field_type_from_model_kind(key)),
304 value: Box::new(field_type_from_model_kind(value)),
305 },
306 FieldKind::Structured { queryable } => FieldType::Structured {
307 queryable: *queryable,
308 },
309 }
310}
311
312impl fmt::Display for FieldType {
313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314 match self {
315 Self::Scalar(inner) => write!(f, "{inner:?}"),
316 Self::List(inner) => write!(f, "List<{inner}>"),
317 Self::Set(inner) => write!(f, "Set<{inner}>"),
318 Self::Map { key, value } => write!(f, "Map<{key}, {value}>"),
319 Self::Structured { queryable } => {
320 write!(f, "Structured<queryable={queryable}>")
321 }
322 }
323 }
324}
325
326#[derive(Clone, Debug, Eq, PartialEq)]
332pub struct CoercionSpec {
333 pub id: CoercionId,
334 pub params: BTreeMap<String, String>,
335}
336
337impl CoercionSpec {
338 #[must_use]
339 pub const fn new(id: CoercionId) -> Self {
340 Self {
341 id,
342 params: BTreeMap::new(),
343 }
344 }
345}
346
347impl Default for CoercionSpec {
348 fn default() -> Self {
349 Self::new(CoercionId::Strict)
350 }
351}
352
353#[derive(Clone, Copy, Debug, Eq, PartialEq)]
359pub(crate) enum CoercionRuleFamily {
360 Any,
361 Family(CoercionFamily),
362}
363
364#[derive(Clone, Copy, Debug, Eq, PartialEq)]
370pub(crate) struct CoercionRule {
371 pub left: CoercionRuleFamily,
372 pub right: CoercionRuleFamily,
373 pub id: CoercionId,
374}
375
376pub(crate) const COERCION_TABLE: &[CoercionRule] = &[
377 CoercionRule {
378 left: CoercionRuleFamily::Any,
379 right: CoercionRuleFamily::Any,
380 id: CoercionId::Strict,
381 },
382 CoercionRule {
383 left: CoercionRuleFamily::Family(CoercionFamily::Numeric),
384 right: CoercionRuleFamily::Family(CoercionFamily::Numeric),
385 id: CoercionId::NumericWiden,
386 },
387 CoercionRule {
388 left: CoercionRuleFamily::Family(CoercionFamily::Textual),
389 right: CoercionRuleFamily::Family(CoercionFamily::Textual),
390 id: CoercionId::TextCasefold,
391 },
392 CoercionRule {
393 left: CoercionRuleFamily::Any,
394 right: CoercionRuleFamily::Any,
395 id: CoercionId::CollectionElement,
396 },
397];
398
399#[must_use]
401pub(in crate::db) fn supports_coercion(
402 left: CoercionFamily,
403 right: CoercionFamily,
404 id: CoercionId,
405) -> bool {
406 COERCION_TABLE.iter().any(|rule| {
407 rule.id == id && family_matches(rule.left, left) && family_matches(rule.right, right)
408 })
409}
410
411fn family_matches(rule: CoercionRuleFamily, value: CoercionFamily) -> bool {
412 match rule {
413 CoercionRuleFamily::Any => true,
414 CoercionRuleFamily::Family(expected) => expected == value,
415 }
416}
417
418#[derive(Clone, Copy, Debug, Eq, PartialEq)]
422pub(in crate::db) enum TextOp {
423 StartsWith,
424 EndsWith,
425}
426
427#[must_use]
429pub(in crate::db) fn compare_eq(
430 left: &Value,
431 right: &Value,
432 coercion: &CoercionSpec,
433) -> Option<bool> {
434 match coercion.id {
435 CoercionId::Strict | CoercionId::CollectionElement => {
436 same_variant(left, right).then_some(left == right)
437 }
438 CoercionId::NumericWiden => {
439 if !left.supports_numeric_coercion() || !right.supports_numeric_coercion() {
440 return None;
441 }
442
443 left.cmp_numeric(right).map(|ord| ord == Ordering::Equal)
444 }
445 CoercionId::TextCasefold => compare_casefold(left, right),
446 }
447}
448
449#[must_use]
451pub(in crate::db) fn compare_order(
452 left: &Value,
453 right: &Value,
454 coercion: &CoercionSpec,
455) -> Option<Ordering> {
456 match coercion.id {
457 CoercionId::Strict | CoercionId::CollectionElement => {
458 if !same_variant(left, right) {
459 return None;
460 }
461 Value::strict_order_cmp(left, right)
462 }
463 CoercionId::NumericWiden => {
464 if !left.supports_numeric_coercion() || !right.supports_numeric_coercion() {
465 return None;
466 }
467
468 left.cmp_numeric(right)
469 }
470 CoercionId::TextCasefold => {
471 let left = casefold_value(left)?;
472 let right = casefold_value(right)?;
473 Some(left.cmp(&right))
474 }
475 }
476}
477
478#[must_use]
480pub(in crate::db) fn canonical_cmp(left: &Value, right: &Value) -> Ordering {
481 if let Some(ordering) = Value::strict_order_cmp(left, right) {
482 return ordering;
483 }
484
485 left.canonical_rank().cmp(&right.canonical_rank())
486}
487
488#[must_use]
490pub(in crate::db) fn compare_text(
491 left: &Value,
492 right: &Value,
493 coercion: &CoercionSpec,
494 op: TextOp,
495) -> Option<bool> {
496 if !matches!(left, Value::Text(_)) || !matches!(right, Value::Text(_)) {
497 return None;
498 }
499
500 let mode = match coercion.id {
501 CoercionId::Strict => TextMode::Cs,
502 CoercionId::TextCasefold => TextMode::Ci,
503 _ => return None,
504 };
505
506 match op {
507 TextOp::StartsWith => left.text_starts_with(right, mode),
508 TextOp::EndsWith => left.text_ends_with(right, mode),
509 }
510}
511
512fn same_variant(left: &Value, right: &Value) -> bool {
513 discriminant(left) == discriminant(right)
514}
515
516fn compare_casefold(left: &Value, right: &Value) -> Option<bool> {
517 let left = casefold_value(left)?;
518 let right = casefold_value(right)?;
519 Some(left == right)
520}
521
522fn casefold_value(value: &Value) -> Option<String> {
523 match value {
524 Value::Text(text) => Some(casefold(text)),
525 _ => None,
526 }
527}
528
529fn casefold(input: &str) -> String {
530 if input.is_ascii() {
531 return input.to_ascii_lowercase();
532 }
533
534 input.to_lowercase()
535}
536
537#[derive(Clone, Copy, Debug, Eq, PartialEq)]
541#[repr(u8)]
542pub enum CompareOp {
543 Eq = 0x01,
544 Ne = 0x02,
545 Lt = 0x03,
546 Lte = 0x04,
547 Gt = 0x05,
548 Gte = 0x06,
549 In = 0x07,
550 NotIn = 0x08,
551 Contains = 0x09,
552 StartsWith = 0x0a,
553 EndsWith = 0x0b,
554}
555
556impl CompareOp {
557 #[must_use]
558 pub const fn tag(self) -> u8 {
559 self as u8
560 }
561}
562
563#[derive(Clone, Debug, Eq, PartialEq)]
567pub struct ComparePredicate {
568 pub field: String,
569 pub op: CompareOp,
570 pub value: Value,
571 pub coercion: CoercionSpec,
572}
573
574impl ComparePredicate {
575 fn new(field: String, op: CompareOp, value: Value) -> Self {
576 Self {
577 field,
578 op,
579 value,
580 coercion: CoercionSpec::default(),
581 }
582 }
583
584 #[must_use]
586 pub fn with_coercion(
587 field: impl Into<String>,
588 op: CompareOp,
589 value: Value,
590 coercion: CoercionId,
591 ) -> Self {
592 Self {
593 field: field.into(),
594 op,
595 value,
596 coercion: CoercionSpec::new(coercion),
597 }
598 }
599
600 #[must_use]
601 pub fn eq(field: String, value: Value) -> Self {
602 Self::new(field, CompareOp::Eq, value)
603 }
604
605 #[must_use]
606 pub fn ne(field: String, value: Value) -> Self {
607 Self::new(field, CompareOp::Ne, value)
608 }
609
610 #[must_use]
611 pub fn lt(field: String, value: Value) -> Self {
612 Self::new(field, CompareOp::Lt, value)
613 }
614
615 #[must_use]
616 pub fn lte(field: String, value: Value) -> Self {
617 Self::new(field, CompareOp::Lte, value)
618 }
619
620 #[must_use]
621 pub fn gt(field: String, value: Value) -> Self {
622 Self::new(field, CompareOp::Gt, value)
623 }
624
625 #[must_use]
626 pub fn gte(field: String, value: Value) -> Self {
627 Self::new(field, CompareOp::Gte, value)
628 }
629
630 #[must_use]
631 pub fn in_(field: String, values: Vec<Value>) -> Self {
632 Self::new(field, CompareOp::In, Value::List(values))
633 }
634
635 #[must_use]
636 pub fn not_in(field: String, values: Vec<Value>) -> Self {
637 Self::new(field, CompareOp::NotIn, Value::List(values))
638 }
639}
640
641#[derive(Clone, Debug, Eq, PartialEq)]
645pub enum Predicate {
646 True,
647 False,
648 And(Vec<Self>),
649 Or(Vec<Self>),
650 Not(Box<Self>),
651 Compare(ComparePredicate),
652 IsNull { field: String },
653 IsMissing { field: String },
654 IsEmpty { field: String },
655 IsNotEmpty { field: String },
656 TextContains { field: String, value: Value },
657 TextContainsCi { field: String, value: Value },
658}
659
660impl Predicate {
661 #[must_use]
662 pub const fn and(preds: Vec<Self>) -> Self {
663 Self::And(preds)
664 }
665
666 #[must_use]
667 pub const fn or(preds: Vec<Self>) -> Self {
668 Self::Or(preds)
669 }
670
671 #[must_use]
672 #[expect(clippy::should_implement_trait)]
673 pub fn not(pred: Self) -> Self {
674 Self::Not(Box::new(pred))
675 }
676
677 #[must_use]
678 pub fn eq(field: String, value: Value) -> Self {
679 Self::Compare(ComparePredicate::eq(field, value))
680 }
681
682 #[must_use]
683 pub fn ne(field: String, value: Value) -> Self {
684 Self::Compare(ComparePredicate::ne(field, value))
685 }
686
687 #[must_use]
688 pub fn lt(field: String, value: Value) -> Self {
689 Self::Compare(ComparePredicate::lt(field, value))
690 }
691
692 #[must_use]
693 pub fn lte(field: String, value: Value) -> Self {
694 Self::Compare(ComparePredicate::lte(field, value))
695 }
696
697 #[must_use]
698 pub fn gt(field: String, value: Value) -> Self {
699 Self::Compare(ComparePredicate::gt(field, value))
700 }
701
702 #[must_use]
703 pub fn gte(field: String, value: Value) -> Self {
704 Self::Compare(ComparePredicate::gte(field, value))
705 }
706
707 #[must_use]
708 pub fn in_(field: String, values: Vec<Value>) -> Self {
709 Self::Compare(ComparePredicate::in_(field, values))
710 }
711
712 #[must_use]
713 pub fn not_in(field: String, values: Vec<Value>) -> Self {
714 Self::Compare(ComparePredicate::not_in(field, values))
715 }
716}
717
718impl BitAnd for Predicate {
719 type Output = Self;
720
721 fn bitand(self, rhs: Self) -> Self::Output {
722 Self::And(vec![self, rhs])
723 }
724}
725
726impl BitAnd for &Predicate {
727 type Output = Predicate;
728
729 fn bitand(self, rhs: Self) -> Self::Output {
730 Predicate::And(vec![self.clone(), rhs.clone()])
731 }
732}
733
734impl BitOr for Predicate {
735 type Output = Self;
736
737 fn bitor(self, rhs: Self) -> Self::Output {
738 Self::Or(vec![self, rhs])
739 }
740}
741
742impl BitOr for &Predicate {
743 type Output = Predicate;
744
745 fn bitor(self, rhs: Self) -> Self::Output {
746 Predicate::Or(vec![self.clone(), rhs.clone()])
747 }
748}
749
750pub(crate) type PredicateExecutionModel = Predicate;