icydb_schema/node/
index.rs1use crate::prelude::*;
2use std::{
3 fmt::{self, Display},
4 ops::Not,
5};
6
7#[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 #[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 #[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 #[must_use]
45 pub const fn fields(&self) -> &'static [&'static str] {
46 self.fields
47 }
48
49 #[must_use]
51 pub const fn is_unique(&self) -> bool {
52 self.unique
53 }
54
55 #[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#[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}