Skip to main content

icydb_schema/node/
index.rs

1use crate::prelude::*;
2use std::{
3    fmt::{self, Display},
4    ops::Not,
5};
6
7///
8/// Index
9///
10
11#[derive(Clone, Debug, Serialize)]
12pub struct Index {
13    fields: &'static [&'static str],
14
15    #[serde(default, skip_serializing_if = "Not::not")]
16    unique: bool,
17
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    predicate: Option<&'static str>,
20}
21
22impl Index {
23    /// Build one index declaration from field-list and uniqueness metadata.
24    #[must_use]
25    pub const fn new(fields: &'static [&'static str], unique: bool) -> Self {
26        Self::new_with_predicate(fields, unique, None)
27    }
28
29    /// Build one index declaration with optional conditional predicate metadata.
30    #[must_use]
31    pub const fn new_with_predicate(
32        fields: &'static [&'static str],
33        unique: bool,
34        predicate: Option<&'static str>,
35    ) -> Self {
36        Self {
37            fields,
38            unique,
39            predicate,
40        }
41    }
42
43    /// Borrow index field sequence.
44    #[must_use]
45    pub const fn fields(&self) -> &'static [&'static str] {
46        self.fields
47    }
48
49    /// Return whether the index enforces uniqueness.
50    #[must_use]
51    pub const fn is_unique(&self) -> bool {
52        self.unique
53    }
54
55    /// Return optional conditional-index predicate metadata.
56    #[must_use]
57    pub const fn predicate(&self) -> Option<&'static str> {
58        self.predicate
59    }
60
61    #[must_use]
62    pub fn is_prefix_of(&self, other: &Self) -> bool {
63        self.fields().len() < other.fields().len() && other.fields().starts_with(self.fields())
64    }
65}
66
67impl Display for Index {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        let fields = self.fields().join(", ");
70
71        if self.is_unique() {
72            if let Some(predicate) = self.predicate() {
73                write!(f, "UNIQUE ({fields}) WHERE {predicate}")
74            } else {
75                write!(f, "UNIQUE ({fields})")
76            }
77        } else if let Some(predicate) = self.predicate() {
78            write!(f, "({fields}) WHERE {predicate}")
79        } else {
80            write!(f, "({fields})")
81        }
82    }
83}
84
85impl MacroNode for Index {
86    fn as_any(&self) -> &dyn std::any::Any {
87        self
88    }
89}
90
91impl ValidateNode for Index {}
92
93impl VisitableNode for Index {
94    fn route_key(&self) -> String {
95        self.fields().join(", ")
96    }
97}
98
99///
100/// TESTS
101///
102
103#[cfg(test)]
104mod tests {
105    use crate::node::index::Index;
106
107    #[test]
108    fn index_with_predicate_reports_conditional_shape() {
109        let index = Index::new_with_predicate(&["email"], false, Some("active = true"));
110
111        assert_eq!(index.predicate(), Some("active = true"));
112        assert_eq!(index.to_string(), "(email) WHERE active = true");
113    }
114
115    #[test]
116    fn index_without_predicate_preserves_legacy_shape() {
117        let index = Index::new(&["email"], true);
118
119        assert_eq!(index.predicate(), None);
120        assert_eq!(index.to_string(), "UNIQUE (email)");
121    }
122}