1use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Index {
7 pub name: String,
9 pub fields: Vec<String>,
11 pub unique: bool,
13 pub condition: Option<String>, pub include: Vec<String>, pub opclass: Option<String>, }
20
21impl Index {
22 pub fn new(name: impl Into<String>, fields: Vec<String>) -> Self {
35 Self {
36 name: name.into(),
37 fields,
38 unique: false,
39 condition: None,
40 include: Vec::new(),
41 opclass: None,
42 }
43 }
44 pub fn unique(mut self) -> Self {
47 self.unique = true;
48 self
49 }
50 pub fn with_condition(mut self, condition: String) -> Self {
52 self.condition = Some(condition);
53 self
54 }
55 pub fn include(mut self, fields: Vec<String>) -> Self {
58 self.include = fields;
59 self
60 }
61 pub fn with_opclass(mut self, opclass: String) -> Self {
63 self.opclass = Some(opclass);
64 self
65 }
66 pub fn to_sql(&self, table: &str) -> String {
69 let unique = if self.unique { "UNIQUE " } else { "" };
70 let fields = self.fields.join(", ");
71
72 let mut sql = format!(
73 "CREATE {}INDEX {} ON {} ({})",
74 unique, self.name, table, fields
75 );
76
77 if let Some(ref opclass) = self.opclass {
78 sql = format!(
79 "CREATE {}INDEX {} ON {} ({} {})",
80 unique, self.name, table, fields, opclass
81 );
82 }
83
84 if !self.include.is_empty() {
85 sql.push_str(&format!(" INCLUDE ({})", self.include.join(", ")));
86 }
87
88 if let Some(ref cond) = self.condition {
89 sql.push_str(&format!(" WHERE {}", cond));
90 }
91
92 sql
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct BTreeIndex {
99 pub index: Index,
101}
102
103impl BTreeIndex {
104 pub fn new(name: impl Into<String>, fields: Vec<String>) -> Self {
115 Self {
116 index: Index::new(name, fields),
117 }
118 }
119 pub fn to_sql(&self, table: &str) -> String {
122 self.index.to_sql(table)
123 }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct HashIndex {
129 pub index: Index,
131}
132
133impl HashIndex {
134 pub fn new(name: impl Into<String>, fields: Vec<String>) -> Self {
146 let mut index = Index::new(name, fields);
147 index.opclass = Some("USING HASH".to_string());
148 Self { index }
149 }
150 pub fn to_sql(&self, table: &str) -> String {
153 format!(
154 "CREATE INDEX {} ON {} USING HASH ({})",
155 self.index.name,
156 table,
157 self.index.fields.join(", ")
158 )
159 }
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct GinIndex {
165 pub index: Index,
167}
168
169impl GinIndex {
170 pub fn new(name: impl Into<String>, fields: Vec<String>) -> Self {
182 let mut index = Index::new(name, fields);
183 index.opclass = Some("USING GIN".to_string());
184 Self { index }
185 }
186 pub fn to_sql(&self, table: &str) -> String {
189 format!(
190 "CREATE INDEX {} ON {} USING GIN ({})",
191 self.index.name,
192 table,
193 self.index.fields.join(", ")
194 )
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct GistIndex {
201 pub index: Index,
203}
204
205impl GistIndex {
206 pub fn new(name: impl Into<String>, fields: Vec<String>) -> Self {
218 let mut index = Index::new(name, fields);
219 index.opclass = Some("USING GIST".to_string());
220 Self { index }
221 }
222 pub fn to_sql(&self, table: &str) -> String {
225 format!(
226 "CREATE INDEX {} ON {} USING GIST ({})",
227 self.index.name,
228 table,
229 self.index.fields.join(", ")
230 )
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_basic_index() {
240 let index = Index::new("idx_email", vec!["email".to_string()]);
241 let sql = index.to_sql("users");
242 assert_eq!(sql, "CREATE INDEX idx_email ON users (email)");
243 }
244
245 #[test]
246 fn test_unique_index() {
247 let index = Index::new("idx_unique_email", vec!["email".to_string()]).unique();
248 let sql = index.to_sql("users");
249 assert_eq!(sql, "CREATE UNIQUE INDEX idx_unique_email ON users (email)");
250 }
251
252 #[test]
253 fn test_orm_indexes_composite() {
254 let index = Index::new(
255 "idx_user_email",
256 vec!["user_id".to_string(), "email".to_string()],
257 );
258 let sql = index.to_sql("messages");
259 assert_eq!(
260 sql,
261 "CREATE INDEX idx_user_email ON messages (user_id, email)"
262 );
263 }
264
265 #[test]
266 fn test_partial_index() {
267 let index = Index::new("idx_active_users", vec!["email".to_string()])
268 .with_condition("deleted_at IS NULL".to_string());
269 let sql = index.to_sql("users");
270 assert_eq!(
271 sql,
272 "CREATE INDEX idx_active_users ON users (email) WHERE deleted_at IS NULL"
273 );
274 }
275
276 #[test]
277 fn test_orm_indexes_covering() {
278 let index = Index::new("idx_email_covering", vec!["email".to_string()])
279 .include(vec!["name".to_string(), "created_at".to_string()]);
280 let sql = index.to_sql("users");
281 assert_eq!(
282 sql,
283 "CREATE INDEX idx_email_covering ON users (email) INCLUDE (name, created_at)"
284 );
285 }
286
287 #[test]
288 fn test_btree_index() {
289 let index = BTreeIndex::new("idx_btree", vec!["id".to_string()]);
290 let sql = index.to_sql("users");
291 assert_eq!(sql, "CREATE INDEX idx_btree ON users (id)");
292 }
293
294 #[test]
295 fn test_hash_index() {
296 let index = HashIndex::new("idx_hash", vec!["email".to_string()]);
297 let sql = index.to_sql("users");
298 assert_eq!(sql, "CREATE INDEX idx_hash ON users USING HASH (email)");
299 }
300
301 #[test]
302 fn test_gin_index() {
303 let index = GinIndex::new("idx_gin", vec!["tags".to_string()]);
304 let sql = index.to_sql("posts");
305 assert_eq!(sql, "CREATE INDEX idx_gin ON posts USING GIN (tags)");
306 }
307
308 #[test]
309 fn test_gist_index() {
310 let index = GistIndex::new("idx_gist", vec!["location".to_string()]);
311 let sql = index.to_sql("places");
312 assert_eq!(sql, "CREATE INDEX idx_gist ON places USING GIST (location)");
313 }
314}