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
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///
92/// CoercionRuleFamily
93///
94/// Rule-side matcher for coercion routing families.
95///
96
97#[derive(Clone, Copy, Debug, Eq, PartialEq)]
98pub(crate) enum CoercionRuleFamily {
99    Any,
100    Family(CoercionFamily),
101}
102
103///
104/// CoercionRule
105///
106/// Declarative coercion routing rule between value families.
107///
108
109#[derive(Clone, Copy, Debug, Eq, PartialEq)]
110pub(crate) struct CoercionRule {
111    left: CoercionRuleFamily,
112    right: CoercionRuleFamily,
113    id: CoercionId,
114}
115
116pub(crate) const COERCION_TABLE: &[CoercionRule] = &[
117    CoercionRule {
118        left: CoercionRuleFamily::Any,
119        right: CoercionRuleFamily::Any,
120        id: CoercionId::Strict,
121    },
122    CoercionRule {
123        left: CoercionRuleFamily::Family(CoercionFamily::Numeric),
124        right: CoercionRuleFamily::Family(CoercionFamily::Numeric),
125        id: CoercionId::NumericWiden,
126    },
127    CoercionRule {
128        left: CoercionRuleFamily::Family(CoercionFamily::Textual),
129        right: CoercionRuleFamily::Family(CoercionFamily::Textual),
130        id: CoercionId::TextCasefold,
131    },
132    CoercionRule {
133        left: CoercionRuleFamily::Any,
134        right: CoercionRuleFamily::Any,
135        id: CoercionId::CollectionElement,
136    },
137];
138
139/// Returns whether a coercion rule exists for the provided routing families.
140#[must_use]
141pub(in crate::db) fn supports_coercion(
142    left: CoercionFamily,
143    right: CoercionFamily,
144    id: CoercionId,
145) -> bool {
146    COERCION_TABLE.iter().any(|rule| {
147        rule.id == id && family_matches(rule.left, left) && family_matches(rule.right, right)
148    })
149}
150
151fn family_matches(rule: CoercionRuleFamily, value: CoercionFamily) -> bool {
152    match rule {
153        CoercionRuleFamily::Any => true,
154        CoercionRuleFamily::Family(expected) => expected == value,
155    }
156}