nodedb_crdt/
constraint.rs1use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub enum ConstraintKind {
14 Unique,
17
18 ForeignKey {
21 ref_collection: String,
23 ref_key: String,
25 },
26
27 BiTemporalFK {
33 ref_collection: String,
34 ref_key: String,
35 },
36
37 NotNull,
40
41 Check {
44 description: String,
46 },
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct Constraint {
52 pub name: String,
54 pub collection: String,
56 pub field: String,
58 pub kind: ConstraintKind,
60}
61
62#[derive(Debug, Clone, Default)]
64pub struct ConstraintSet {
65 constraints: Vec<Constraint>,
66}
67
68impl ConstraintSet {
69 pub fn new() -> Self {
70 Self::default()
71 }
72
73 pub fn add(&mut self, constraint: Constraint) {
75 self.constraints.push(constraint);
76 }
77
78 pub fn add_unique(&mut self, name: &str, collection: &str, field: &str) {
80 self.add(Constraint {
81 name: name.to_string(),
82 collection: collection.to_string(),
83 field: field.to_string(),
84 kind: ConstraintKind::Unique,
85 });
86 }
87
88 pub fn add_foreign_key(
90 &mut self,
91 name: &str,
92 collection: &str,
93 field: &str,
94 ref_collection: &str,
95 ref_key: &str,
96 ) {
97 self.add(Constraint {
98 name: name.to_string(),
99 collection: collection.to_string(),
100 field: field.to_string(),
101 kind: ConstraintKind::ForeignKey {
102 ref_collection: ref_collection.to_string(),
103 ref_key: ref_key.to_string(),
104 },
105 });
106 }
107
108 pub fn add_bitemporal_fk(
110 &mut self,
111 name: &str,
112 collection: &str,
113 field: &str,
114 ref_collection: &str,
115 ref_key: &str,
116 ) {
117 self.add(Constraint {
118 name: name.to_string(),
119 collection: collection.to_string(),
120 field: field.to_string(),
121 kind: ConstraintKind::BiTemporalFK {
122 ref_collection: ref_collection.to_string(),
123 ref_key: ref_key.to_string(),
124 },
125 });
126 }
127
128 pub fn add_not_null(&mut self, name: &str, collection: &str, field: &str) {
130 self.add(Constraint {
131 name: name.to_string(),
132 collection: collection.to_string(),
133 field: field.to_string(),
134 kind: ConstraintKind::NotNull,
135 });
136 }
137
138 pub fn for_collection(&self, collection: &str) -> Vec<&Constraint> {
140 self.constraints
141 .iter()
142 .filter(|c| c.collection == collection)
143 .collect()
144 }
145
146 pub fn all(&self) -> &[Constraint] {
148 &self.constraints
149 }
150
151 pub fn len(&self) -> usize {
153 self.constraints.len()
154 }
155
156 pub fn is_empty(&self) -> bool {
157 self.constraints.is_empty()
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn constraint_set_operations() {
167 let mut cs = ConstraintSet::new();
168 cs.add_unique("users_email_unique", "users", "email");
169 cs.add_not_null("users_name_nn", "users", "name");
170 cs.add_foreign_key("posts_author_fk", "posts", "author_id", "users", "id");
171 cs.add_bitemporal_fk("orders_user_btfk", "orders", "user_id", "users", "id");
172
173 assert_eq!(cs.len(), 4);
174 assert_eq!(cs.for_collection("users").len(), 2);
175 assert_eq!(cs.for_collection("posts").len(), 1);
176 assert_eq!(cs.for_collection("orders").len(), 1);
177 assert_eq!(cs.for_collection("missing").len(), 0);
178
179 let btfk = cs.for_collection("orders")[0];
180 assert!(matches!(btfk.kind, ConstraintKind::BiTemporalFK { .. }));
181 }
182}