Skip to main content

icydb_core/model/
index.rs

1//! Module: model::index
2//! Responsibility: module-local ownership and contracts for model::index.
3//! Does not own: cross-module orchestration outside this module.
4//! Boundary: exposes this module API while keeping implementation details internal.
5
6use std::fmt::{self, Display};
7
8///
9/// IndexModel
10///
11/// Runtime-only descriptor for an index used by the executor and stores.
12/// Keeps core decoupled from the schema `Index` shape.
13/// Indexing is hash-based over `Value` equality for all variants.
14/// Unique indexes enforce value equality; hash collisions surface as corruption.
15///
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct IndexModel {
19    /// Stable index name used for diagnostics and planner identity.
20    name: &'static str,
21    store: &'static str,
22    fields: &'static [&'static str],
23    unique: bool,
24    predicate: Option<&'static str>,
25}
26
27impl IndexModel {
28    #[must_use]
29    pub const fn new(
30        name: &'static str,
31        store: &'static str,
32        fields: &'static [&'static str],
33        unique: bool,
34    ) -> Self {
35        Self::new_with_predicate(name, store, fields, unique, None)
36    }
37
38    /// Construct one index descriptor with an optional conditional predicate.
39    #[must_use]
40    pub const fn new_with_predicate(
41        name: &'static str,
42        store: &'static str,
43        fields: &'static [&'static str],
44        unique: bool,
45        predicate: Option<&'static str>,
46    ) -> Self {
47        Self {
48            name,
49            store,
50            fields,
51            unique,
52            predicate,
53        }
54    }
55
56    /// Return the stable index name.
57    #[must_use]
58    pub const fn name(&self) -> &'static str {
59        self.name
60    }
61
62    /// Return the backing index store path.
63    #[must_use]
64    pub const fn store(&self) -> &'static str {
65        self.store
66    }
67
68    /// Return the canonical index field list.
69    #[must_use]
70    pub const fn fields(&self) -> &'static [&'static str] {
71        self.fields
72    }
73
74    /// Return whether the index enforces value uniqueness.
75    #[must_use]
76    pub const fn is_unique(&self) -> bool {
77        self.unique
78    }
79
80    /// Return optional schema-declared conditional index predicate metadata.
81    #[must_use]
82    pub const fn predicate(&self) -> Option<&'static str> {
83        self.predicate
84    }
85
86    /// Whether this index's field prefix matches the start of another index.
87    #[must_use]
88    pub fn is_prefix_of(&self, other: &Self) -> bool {
89        self.fields().len() < other.fields().len() && other.fields().starts_with(self.fields())
90    }
91}
92
93impl Display for IndexModel {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        let fields = self.fields().join(", ");
96        if self.is_unique() {
97            if let Some(predicate) = self.predicate() {
98                write!(
99                    f,
100                    "{}: UNIQUE {}({}) WHERE {}",
101                    self.name(),
102                    self.store(),
103                    fields,
104                    predicate
105                )
106            } else {
107                write!(f, "{}: UNIQUE {}({})", self.name(), self.store(), fields)
108            }
109        } else if let Some(predicate) = self.predicate() {
110            write!(
111                f,
112                "{}: {}({}) WHERE {}",
113                self.name(),
114                self.store(),
115                fields,
116                predicate
117            )
118        } else {
119            write!(f, "{}: {}({})", self.name(), self.store(), fields)
120        }
121    }
122}
123
124///
125/// TESTS
126///
127
128#[cfg(test)]
129mod tests {
130    use crate::model::index::IndexModel;
131
132    #[test]
133    fn index_model_with_predicate_exposes_predicate_metadata() {
134        let model = IndexModel::new_with_predicate(
135            "users|email|active",
136            "users::index",
137            &["email"],
138            false,
139            Some("active = true"),
140        );
141
142        assert_eq!(model.predicate(), Some("active = true"));
143        assert_eq!(
144            model.to_string(),
145            "users|email|active: users::index(email) WHERE active = true"
146        );
147    }
148
149    #[test]
150    fn index_model_without_predicate_preserves_legacy_display_shape() {
151        let model = IndexModel::new("users|email", "users::index", &["email"], true);
152
153        assert_eq!(model.predicate(), None);
154        assert_eq!(model.to_string(), "users|email: UNIQUE users::index(email)");
155    }
156}