1use crate::{
2 model::field::FieldKind,
3 traits::FieldValueKind,
4 value::{CoercionFamily, Value},
5};
6use std::fmt;
7use thiserror::Error as ThisError;
8
9#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
18pub enum CoercionId {
19 Strict,
20 NumericWiden,
21 TextCasefold,
22 CollectionElement,
23}
24
25impl CoercionId {
26 #[must_use]
28 pub const fn plan_hash_tag(self) -> u8 {
29 match self {
30 Self::Strict => 0x01,
31 Self::NumericWiden => 0x02,
32 Self::TextCasefold => 0x04,
33 Self::CollectionElement => 0x05,
34 }
35 }
36}
37
38#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
44pub enum UnsupportedQueryFeature {
45 #[error(
46 "map field '{field}' is not queryable; map predicates are disabled. model queryable attributes as scalar/indexed fields or list entries"
47 )]
48 MapPredicate { field: String },
49}
50
51#[derive(Clone, Debug, Eq, PartialEq)]
63pub(crate) enum ScalarType {
64 Account,
65 Blob,
66 Bool,
67 Date,
68 Decimal,
69 Duration,
70 Enum,
71 Float32,
72 Float64,
73 Int,
74 Int128,
75 IntBig,
76 Principal,
77 Subaccount,
78 Text,
79 Timestamp,
80 Uint,
81 Uint128,
82 UintBig,
83 Ulid,
84 Unit,
85}
86
87macro_rules! scalar_coercion_family_from_registry {
89 ( @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) ),* $(,)? ) => {
90 match $self {
91 $( ScalarType::$scalar => $coercion_family, )*
92 }
93 };
94}
95
96macro_rules! scalar_matches_value_from_registry {
97 ( @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) ),* $(,)? ) => {
98 matches!(
99 ($self, $value),
100 $( (ScalarType::$scalar, $value_pat) )|*
101 )
102 };
103}
104
105macro_rules! scalar_supports_numeric_coercion_from_registry {
106 ( @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) ),* $(,)? ) => {
107 match $self {
108 $( ScalarType::$scalar => $supports_numeric_coercion, )*
109 }
110 };
111}
112
113macro_rules! scalar_is_keyable_from_registry {
114 ( @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) ),* $(,)? ) => {
115 match $self {
116 $( ScalarType::$scalar => $is_keyable, )*
117 }
118 };
119}
120
121macro_rules! scalar_supports_ordering_from_registry {
122 ( @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) ),* $(,)? ) => {
123 match $self {
124 $( ScalarType::$scalar => $supports_ordering, )*
125 }
126 };
127}
128
129impl ScalarType {
130 #[must_use]
131 pub(crate) const fn coercion_family(&self) -> CoercionFamily {
132 scalar_registry!(scalar_coercion_family_from_registry, self)
133 }
134
135 #[must_use]
136 pub(crate) const fn is_orderable(&self) -> bool {
137 self.supports_ordering()
140 }
141
142 #[must_use]
143 pub(crate) const fn matches_value(&self, value: &Value) -> bool {
144 scalar_registry!(scalar_matches_value_from_registry, self, value)
145 }
146
147 #[must_use]
148 pub(crate) const fn supports_numeric_coercion(&self) -> bool {
149 scalar_registry!(scalar_supports_numeric_coercion_from_registry, self)
150 }
151
152 #[must_use]
153 pub(crate) const fn is_keyable(&self) -> bool {
154 scalar_registry!(scalar_is_keyable_from_registry, self)
155 }
156
157 #[must_use]
158 pub(crate) const fn supports_ordering(&self) -> bool {
159 scalar_registry!(scalar_supports_ordering_from_registry, self)
160 }
161}
162
163#[derive(Clone, Debug, Eq, PartialEq)]
174pub(crate) enum FieldType {
175 Scalar(ScalarType),
176 List(Box<Self>),
177 Set(Box<Self>),
178 Map { key: Box<Self>, value: Box<Self> },
179 Structured { queryable: bool },
180}
181
182impl FieldType {
183 #[must_use]
184 pub(crate) const fn value_kind(&self) -> FieldValueKind {
185 match self {
186 Self::Scalar(_) => FieldValueKind::Atomic,
187 Self::List(_) | Self::Set(_) => FieldValueKind::Structured { queryable: true },
188 Self::Map { .. } => FieldValueKind::Structured { queryable: false },
189 Self::Structured { queryable } => FieldValueKind::Structured {
190 queryable: *queryable,
191 },
192 }
193 }
194
195 #[must_use]
196 pub(crate) const fn coercion_family(&self) -> Option<CoercionFamily> {
197 match self {
198 Self::Scalar(inner) => Some(inner.coercion_family()),
199 Self::List(_) | Self::Set(_) | Self::Map { .. } => Some(CoercionFamily::Collection),
200 Self::Structured { .. } => None,
201 }
202 }
203
204 #[must_use]
205 pub(crate) const fn is_text(&self) -> bool {
206 matches!(self, Self::Scalar(ScalarType::Text))
207 }
208
209 #[must_use]
210 pub(crate) const fn is_collection(&self) -> bool {
211 matches!(self, Self::List(_) | Self::Set(_) | Self::Map { .. })
212 }
213
214 #[must_use]
215 pub(crate) const fn is_list_like(&self) -> bool {
216 matches!(self, Self::List(_) | Self::Set(_))
217 }
218
219 #[must_use]
220 pub(crate) const fn is_orderable(&self) -> bool {
221 match self {
222 Self::Scalar(inner) => inner.is_orderable(),
223 _ => false,
224 }
225 }
226
227 #[must_use]
228 pub(crate) const fn is_keyable(&self) -> bool {
229 match self {
230 Self::Scalar(inner) => inner.is_keyable(),
231 _ => false,
232 }
233 }
234
235 #[must_use]
236 pub(crate) const fn supports_numeric_coercion(&self) -> bool {
237 match self {
238 Self::Scalar(inner) => inner.supports_numeric_coercion(),
239 _ => false,
240 }
241 }
242}
243
244pub(crate) fn literal_matches_type(literal: &Value, field_type: &FieldType) -> bool {
245 match field_type {
246 FieldType::Scalar(inner) => inner.matches_value(literal),
247 FieldType::List(element) | FieldType::Set(element) => match literal {
248 Value::List(items) => items.iter().all(|item| literal_matches_type(item, element)),
249 _ => false,
250 },
251 FieldType::Map { key, value } => match literal {
252 Value::Map(entries) => {
253 if Value::validate_map_entries(entries.as_slice()).is_err() {
254 return false;
255 }
256
257 entries.iter().all(|(entry_key, entry_value)| {
258 literal_matches_type(entry_key, key) && literal_matches_type(entry_value, value)
259 })
260 }
261 _ => false,
262 },
263 FieldType::Structured { .. } => {
264 false
266 }
267 }
268}
269
270pub(super) fn field_type_from_model_kind(kind: &FieldKind) -> FieldType {
271 match kind {
272 FieldKind::Account => FieldType::Scalar(ScalarType::Account),
273 FieldKind::Blob => FieldType::Scalar(ScalarType::Blob),
274 FieldKind::Bool => FieldType::Scalar(ScalarType::Bool),
275 FieldKind::Date => FieldType::Scalar(ScalarType::Date),
276 FieldKind::Decimal { .. } => FieldType::Scalar(ScalarType::Decimal),
277 FieldKind::Duration => FieldType::Scalar(ScalarType::Duration),
278 FieldKind::Enum { .. } => FieldType::Scalar(ScalarType::Enum),
279 FieldKind::Float32 => FieldType::Scalar(ScalarType::Float32),
280 FieldKind::Float64 => FieldType::Scalar(ScalarType::Float64),
281 FieldKind::Int => FieldType::Scalar(ScalarType::Int),
282 FieldKind::Int128 => FieldType::Scalar(ScalarType::Int128),
283 FieldKind::IntBig => FieldType::Scalar(ScalarType::IntBig),
284 FieldKind::Principal => FieldType::Scalar(ScalarType::Principal),
285 FieldKind::Subaccount => FieldType::Scalar(ScalarType::Subaccount),
286 FieldKind::Text => FieldType::Scalar(ScalarType::Text),
287 FieldKind::Timestamp => FieldType::Scalar(ScalarType::Timestamp),
288 FieldKind::Uint => FieldType::Scalar(ScalarType::Uint),
289 FieldKind::Uint128 => FieldType::Scalar(ScalarType::Uint128),
290 FieldKind::UintBig => FieldType::Scalar(ScalarType::UintBig),
291 FieldKind::Ulid => FieldType::Scalar(ScalarType::Ulid),
292 FieldKind::Unit => FieldType::Scalar(ScalarType::Unit),
293 FieldKind::Relation { key_kind, .. } => field_type_from_model_kind(key_kind),
294 FieldKind::List(inner) => FieldType::List(Box::new(field_type_from_model_kind(inner))),
295 FieldKind::Set(inner) => FieldType::Set(Box::new(field_type_from_model_kind(inner))),
296 FieldKind::Map { key, value } => FieldType::Map {
297 key: Box::new(field_type_from_model_kind(key)),
298 value: Box::new(field_type_from_model_kind(value)),
299 },
300 FieldKind::Structured { queryable } => FieldType::Structured {
301 queryable: *queryable,
302 },
303 }
304}
305
306impl fmt::Display for FieldType {
307 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308 match self {
309 Self::Scalar(inner) => write!(f, "{inner:?}"),
310 Self::List(inner) => write!(f, "List<{inner}>"),
311 Self::Set(inner) => write!(f, "Set<{inner}>"),
312 Self::Map { key, value } => write!(f, "Map<{key}, {value}>"),
313 Self::Structured { queryable } => {
314 write!(f, "Structured<queryable={queryable}>")
315 }
316 }
317 }
318}