Skip to main content

icydb_core/db/predicate/
coercion.rs

1//! Module: predicate::coercion
2//! Responsibility: coercion identifiers/specs and family support matching.
3//! Does not own: predicate AST evaluation or schema literal validation.
4//! Boundary: consumed by predicate schema/semantics/runtime layers.
5
6#[cfg(any(test, feature = "query"))]
7use crate::value::CoercionFamily;
8use std::fmt;
9
10///
11/// CoercionId
12///
13/// Identifier for an explicit comparison coercion policy.
14///
15/// Coercions express *how* values may be compared, not whether a comparison
16/// is valid for a given field. Validation and planning enforce legality.
17///
18
19#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
20pub enum CoercionId {
21    Strict,
22    NumericWiden,
23    TextCasefold,
24    CollectionElement,
25}
26
27///
28/// CoercionSpec
29///
30/// Fully-specified coercion policy for predicate comparisons.
31///
32
33#[derive(Clone, Eq, PartialEq)]
34pub struct CoercionSpec {
35    pub(crate) id: CoercionId,
36    pub(crate) params: Vec<(String, String)>,
37}
38
39impl CoercionSpec {
40    #[must_use]
41    pub const fn new(id: CoercionId) -> Self {
42        Self {
43            id,
44            params: Vec::new(),
45        }
46    }
47
48    /// Return the canonical coercion identifier.
49    #[must_use]
50    pub const fn id(&self) -> CoercionId {
51        self.id
52    }
53
54    /// Borrow any attached coercion parameters.
55    #[must_use]
56    pub const fn params(&self) -> &[(String, String)] {
57        self.params.as_slice()
58    }
59}
60
61impl fmt::Debug for CoercionSpec {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        f.debug_struct("CoercionSpec")
64            .field("id", &self.id)
65            .field("params", &CoercionParamsDebug(&self.params))
66            .finish()
67    }
68}
69
70impl Default for CoercionSpec {
71    fn default() -> Self {
72        Self::new(CoercionId::Strict)
73    }
74}
75
76// Keep verbose predicate diagnostics stable even though coercion params no
77// longer retain tree-map machinery internally.
78struct CoercionParamsDebug<'a>(&'a [(String, String)]);
79
80impl fmt::Debug for CoercionParamsDebug<'_> {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        let mut debug = f.debug_map();
83        for (key, value) in self.0 {
84            debug.entry(key, value);
85        }
86
87        debug.finish()
88    }
89}
90
91/// Returns whether a coercion rule exists for the provided routing families.
92#[must_use]
93#[cfg(any(test, feature = "query"))]
94pub(in crate::db) fn supports_coercion(
95    left: CoercionFamily,
96    right: CoercionFamily,
97    id: CoercionId,
98) -> bool {
99    match id {
100        CoercionId::Strict | CoercionId::CollectionElement => true,
101        CoercionId::NumericWiden => {
102            left == CoercionFamily::Numeric && right == CoercionFamily::Numeric
103        }
104        CoercionId::TextCasefold => {
105            left == CoercionFamily::Textual && right == CoercionFamily::Textual
106        }
107    }
108}
109
110///
111/// TESTS
112///
113
114#[cfg(test)]
115mod tests {
116    use crate::{
117        db::predicate::{CoercionId, coercion::supports_coercion},
118        value::CoercionFamily,
119    };
120
121    #[test]
122    fn supports_coercion_matches_canonical_family_matrix() {
123        assert!(supports_coercion(
124            CoercionFamily::Numeric,
125            CoercionFamily::Textual,
126            CoercionId::Strict,
127        ));
128        assert!(supports_coercion(
129            CoercionFamily::Textual,
130            CoercionFamily::Numeric,
131            CoercionId::CollectionElement,
132        ));
133
134        assert!(supports_coercion(
135            CoercionFamily::Numeric,
136            CoercionFamily::Numeric,
137            CoercionId::NumericWiden,
138        ));
139        assert!(!supports_coercion(
140            CoercionFamily::Numeric,
141            CoercionFamily::Textual,
142            CoercionId::NumericWiden,
143        ));
144
145        assert!(supports_coercion(
146            CoercionFamily::Textual,
147            CoercionFamily::Textual,
148            CoercionId::TextCasefold,
149        ));
150        assert!(!supports_coercion(
151            CoercionFamily::Textual,
152            CoercionFamily::Numeric,
153            CoercionId::TextCasefold,
154        ));
155    }
156}