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