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
26///
27/// CoercionSpec
28///
29/// Fully-specified coercion policy for predicate comparisons.
30///
31
32#[derive(Clone, Eq, PartialEq)]
33pub struct CoercionSpec {
34    pub(crate) id: CoercionId,
35    pub(crate) params: Vec<(String, String)>,
36}
37
38impl CoercionSpec {
39    #[must_use]
40    pub const fn new(id: CoercionId) -> Self {
41        Self {
42            id,
43            params: Vec::new(),
44        }
45    }
46
47    /// Return the canonical coercion identifier.
48    #[must_use]
49    pub const fn id(&self) -> CoercionId {
50        self.id
51    }
52
53    /// Borrow any attached coercion parameters.
54    #[must_use]
55    pub const fn params(&self) -> &[(String, String)] {
56        self.params.as_slice()
57    }
58}
59
60impl fmt::Debug for CoercionSpec {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        f.debug_struct("CoercionSpec")
63            .field("id", &self.id)
64            .field("params", &CoercionParamsDebug(&self.params))
65            .finish()
66    }
67}
68
69impl Default for CoercionSpec {
70    fn default() -> Self {
71        Self::new(CoercionId::Strict)
72    }
73}
74
75// Keep verbose predicate diagnostics stable even though coercion params no
76// longer retain tree-map machinery internally.
77struct CoercionParamsDebug<'a>(&'a [(String, String)]);
78
79impl fmt::Debug for CoercionParamsDebug<'_> {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        let mut debug = f.debug_map();
82        for (key, value) in self.0 {
83            debug.entry(key, value);
84        }
85
86        debug.finish()
87    }
88}
89
90/// Returns whether a coercion rule exists for the provided routing families.
91#[must_use]
92pub(in crate::db) fn supports_coercion(
93    left: CoercionFamily,
94    right: CoercionFamily,
95    id: CoercionId,
96) -> bool {
97    match id {
98        CoercionId::Strict | CoercionId::CollectionElement => true,
99        CoercionId::NumericWiden => {
100            left == CoercionFamily::Numeric && right == CoercionFamily::Numeric
101        }
102        CoercionId::TextCasefold => {
103            left == CoercionFamily::Textual && right == CoercionFamily::Textual
104        }
105    }
106}
107
108///
109/// TESTS
110///
111
112#[cfg(test)]
113mod tests {
114    use crate::{
115        db::predicate::{CoercionId, coercion::supports_coercion},
116        value::CoercionFamily,
117    };
118
119    #[test]
120    fn supports_coercion_matches_canonical_family_matrix() {
121        assert!(supports_coercion(
122            CoercionFamily::Numeric,
123            CoercionFamily::Textual,
124            CoercionId::Strict,
125        ));
126        assert!(supports_coercion(
127            CoercionFamily::Textual,
128            CoercionFamily::Numeric,
129            CoercionId::CollectionElement,
130        ));
131
132        assert!(supports_coercion(
133            CoercionFamily::Numeric,
134            CoercionFamily::Numeric,
135            CoercionId::NumericWiden,
136        ));
137        assert!(!supports_coercion(
138            CoercionFamily::Numeric,
139            CoercionFamily::Textual,
140            CoercionId::NumericWiden,
141        ));
142
143        assert!(supports_coercion(
144            CoercionFamily::Textual,
145            CoercionFamily::Textual,
146            CoercionId::TextCasefold,
147        ));
148        assert!(!supports_coercion(
149            CoercionFamily::Textual,
150            CoercionFamily::Numeric,
151            CoercionId::TextCasefold,
152        ));
153    }
154}