icydb_core/db/executor/coerce/
family.rs1use crate::{
2 db::primitives::filter::Cmp,
3 types::{Account, Principal, Ulid},
4 value::{Value, ValueFamily, ValueFamilyExt},
5};
6use std::{cmp::Ordering, convert::TryFrom, str::FromStr};
7
8use super::text::coerce_text;
9
10pub struct FamilyPair {
14 pub left: ValueFamily,
15 pub right: ValueFamily,
16}
17
18impl FamilyPair {
19 #[must_use]
20 pub fn new(left: &Value, right: &Value) -> Self {
21 Self {
22 left: left.family(),
23 right: right.family(),
24 }
25 }
26}
27
28#[must_use]
32pub fn coerce_basic(left: &Value, right: &Value, cmp: Cmp) -> Option<bool> {
33 let pair = FamilyPair::new(left, right);
34
35 match (pair.left, pair.right) {
36 (ValueFamily::Numeric, ValueFamily::Numeric) => {
38 if let Some(ord) = left.cmp_numeric(right) {
39 return Some(cmp.compare_order(ord));
40 }
41
42 if let Some(ord) = cmp_collection_len(left, right) {
44 return Some(cmp.compare_order(ord));
45 }
46
47 None
48 }
49
50 (ValueFamily::Textual, ValueFamily::Textual) => coerce_text(left, right, cmp),
52
53 (ValueFamily::Enum, ValueFamily::Enum) => coerce_enum(left, right, cmp),
55
56 (ValueFamily::Collection, _) | (_, ValueFamily::Collection) => {
58 coerce_collection(left, right, cmp)
59 }
60
61 (ValueFamily::Identifier, ValueFamily::Textual)
63 | (ValueFamily::Textual, ValueFamily::Identifier) => {
64 coerce_identifier_text(left, right, cmp)
65 }
66
67 _ => None,
68 }
69}
70
71fn cmp_collection_len(left: &Value, right: &Value) -> Option<Ordering> {
75 match left {
76 Value::List(items) => {
77 let len = i64::try_from(items.len()).ok()?;
78 Value::Int(len).cmp_numeric(right)
79 }
80 _ => None,
81 }
82}
83
84fn coerce_identifier_text(left: &Value, right: &Value, cmp: Cmp) -> Option<bool> {
88 let parse_ulid = |s: &str| Ulid::from_str(s).ok();
89 let parse_principal = |s: &str| Principal::from_str(s).ok();
90 let parse_account = |s: &str| Account::from_str(s).ok();
91
92 let parsed_eq = match (left, right) {
93 (Value::Ulid(lhs), Value::Text(s)) | (Value::Text(s), Value::Ulid(lhs)) => {
94 parse_ulid(s).map(|rhs| rhs == *lhs)
95 }
96 (Value::Principal(lhs), Value::Text(s)) | (Value::Text(s), Value::Principal(lhs)) => {
97 parse_principal(s).map(|rhs| rhs == *lhs)
98 }
99 (Value::Account(lhs), Value::Text(s)) | (Value::Text(s), Value::Account(lhs)) => {
100 parse_account(s).map(|rhs| rhs == *lhs)
101 }
102 _ => None,
103 }?;
104
105 match cmp {
106 Cmp::Eq => Some(parsed_eq),
107 Cmp::Ne => Some(!parsed_eq),
108 _ => None,
109 }
110}
111
112fn coerce_enum(left: &Value, right: &Value, cmp: Cmp) -> Option<bool> {
116 match (left, right) {
117 (Value::Enum(l), Value::Enum(r)) if l.path == r.path => match cmp {
118 Cmp::Eq => Some(l.variant == r.variant),
119 Cmp::Ne => Some(l.variant != r.variant),
120 _ => None,
121 },
122 _ => None,
123 }
124}
125
126fn coerce_collection(actual: &Value, expected: &Value, cmp: Cmp) -> Option<bool> {
130 match cmp {
131 Cmp::AllIn => actual.contains_all(expected),
132 Cmp::AnyIn => actual.contains_any(expected),
133 Cmp::Contains => actual.contains(expected),
134 Cmp::In => actual.in_list(expected),
135
136 Cmp::NotIn => actual.in_list(expected).map(|v| !v),
138
139 Cmp::AllInCi => actual.contains_all_ci(expected),
141 Cmp::AnyInCi => actual.contains_any_ci(expected),
142 Cmp::InCi => actual.in_list_ci(expected),
143
144 Cmp::IsEmpty => actual.is_empty(),
145 Cmp::IsNotEmpty => actual.is_not_empty(),
146 _ => None,
147 }
148}