1use crate::alloc_prelude::*;
6
7#[cfg(feature = "serde")]
8use crate::serde_helpers::{cow_from_string, cow_option_from_string};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
20pub struct OpclassDef {
21 pub name: &'static str,
23 #[cfg_attr(feature = "serde", serde(default))]
25 pub default: bool,
26}
27
28impl OpclassDef {
29 #[must_use]
31 pub const fn new(name: &'static str) -> Self {
32 Self {
33 name,
34 default: false,
35 }
36 }
37
38 #[must_use]
40 pub const fn default_opclass(self) -> Self {
41 Self {
42 default: true,
43 ..self
44 }
45 }
46
47 #[must_use]
49 pub const fn into_opclass(self) -> Opclass {
50 Opclass {
51 name: Cow::Borrowed(self.name),
52 default: self.default,
53 }
54 }
55}
56
57#[derive(Clone, Debug, PartialEq, Eq)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
61pub struct Opclass {
62 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
64 pub name: Cow<'static, str>,
65 #[cfg_attr(feature = "serde", serde(default))]
67 pub default: bool,
68}
69
70impl Opclass {
71 #[must_use]
73 pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
74 Self {
75 name: name.into(),
76 default: false,
77 }
78 }
79
80 #[inline]
82 #[must_use]
83 pub fn name(&self) -> &str {
84 &self.name
85 }
86}
87
88impl core::fmt::Display for OpclassDef {
89 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
90 write!(f, "{}", self.name)
91 }
92}
93
94impl core::fmt::Display for Opclass {
95 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96 write!(f, "{}", self.name)
97 }
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
104pub struct IndexColumn {
105 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
107 pub value: Cow<'static, str>,
108 #[cfg_attr(feature = "serde", serde(default))]
110 pub is_expression: bool,
111 #[cfg_attr(feature = "serde", serde(default = "default_true"))]
113 pub asc: bool,
114 #[cfg_attr(feature = "serde", serde(default))]
116 pub nulls_first: bool,
117 #[cfg_attr(
119 feature = "serde",
120 serde(default, skip_serializing_if = "Option::is_none")
121 )]
122 pub opclass: Option<Opclass>,
123}
124
125impl IndexColumn {
126 #[must_use]
128 pub fn new(value: impl Into<Cow<'static, str>>) -> Self {
129 Self {
130 value: value.into(),
131 is_expression: false,
132 asc: true,
133 nulls_first: false,
134 opclass: None,
135 }
136 }
137
138 #[must_use]
140 pub fn expression(expr: impl Into<Cow<'static, str>>) -> Self {
141 Self {
142 value: expr.into(),
143 is_expression: true,
144 asc: true,
145 nulls_first: false,
146 opclass: None,
147 }
148 }
149
150 #[must_use]
152 pub fn desc(mut self) -> Self {
153 self.asc = false;
154 self
155 }
156
157 #[must_use]
159 pub fn nulls_first(mut self) -> Self {
160 self.nulls_first = true;
161 self
162 }
163
164 #[must_use]
166 pub fn with_opclass(mut self, opclass: Opclass) -> Self {
167 self.opclass = Some(opclass);
168 self
169 }
170}
171
172impl IndexColumn {
173 #[must_use]
175 pub fn to_sql(&self) -> String {
176 let mut sql = if self.is_expression {
177 format!("({})", self.value)
178 } else {
179 format!("\"{}\"", self.value)
180 };
181
182 if let Some(ref op) = self.opclass {
183 sql.push_str(&format!(" {}", op));
184 }
185 if !self.asc {
186 sql.push_str(" DESC");
187 }
188 if self.nulls_first {
189 sql.push_str(" NULLS FIRST");
190 }
191
192 sql
193 }
194}
195
196impl From<IndexColumnDef> for IndexColumn {
197 fn from(def: IndexColumnDef) -> Self {
198 Self {
199 value: Cow::Borrowed(def.value),
200 is_expression: def.is_expression,
201 asc: def.asc,
202 nulls_first: def.nulls_first,
203 opclass: def.opclass.map(|o| o.into_opclass()),
204 }
205 }
206}
207
208impl From<OpclassDef> for Opclass {
209 fn from(def: OpclassDef) -> Self {
210 def.into_opclass()
211 }
212}
213
214#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
216#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
217#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
218pub struct IndexColumnDef {
219 pub value: &'static str,
221 #[cfg_attr(feature = "serde", serde(default))]
223 pub is_expression: bool,
224 #[cfg_attr(feature = "serde", serde(default = "default_true"))]
226 pub asc: bool,
227 #[cfg_attr(feature = "serde", serde(default))]
229 pub nulls_first: bool,
230 #[cfg_attr(
232 feature = "serde",
233 serde(default, skip_serializing_if = "Option::is_none")
234 )]
235 pub opclass: Option<OpclassDef>,
236}
237
238#[cfg(feature = "serde")]
239const fn default_true() -> bool {
240 true
241}
242
243impl IndexColumnDef {
244 #[must_use]
246 pub const fn new(value: &'static str) -> Self {
247 Self {
248 value,
249 is_expression: false,
250 asc: true,
251 nulls_first: false,
252 opclass: None,
253 }
254 }
255
256 #[must_use]
258 pub const fn expression(expr: &'static str) -> Self {
259 Self {
260 value: expr,
261 is_expression: true,
262 asc: true,
263 nulls_first: false,
264 opclass: None,
265 }
266 }
267
268 #[must_use]
270 pub const fn desc(self) -> Self {
271 Self { asc: false, ..self }
272 }
273
274 #[must_use]
276 pub const fn nulls_first(self) -> Self {
277 Self {
278 nulls_first: true,
279 ..self
280 }
281 }
282
283 #[must_use]
285 pub const fn opclass(self, opclass: OpclassDef) -> Self {
286 Self {
287 opclass: Some(opclass),
288 ..self
289 }
290 }
291
292 #[must_use]
294 pub const fn opclass_name(self, name: &'static str) -> Self {
295 Self {
296 opclass: Some(OpclassDef::new(name)),
297 ..self
298 }
299 }
300}
301
302#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
304pub struct IndexDef {
305 pub schema: &'static str,
307 pub table: &'static str,
309 pub name: &'static str,
311 pub name_explicit: bool,
313 pub columns: &'static [IndexColumnDef],
315 pub is_unique: bool,
317 pub where_clause: Option<&'static str>,
319 pub method: Option<&'static str>,
321 pub with: Option<&'static str>,
323 pub concurrently: bool,
325}
326
327impl IndexDef {
328 #[must_use]
330 pub const fn new(
331 schema: &'static str,
332 table: &'static str,
333 name: &'static str,
334 columns: &'static [IndexColumnDef],
335 ) -> Self {
336 Self {
337 schema,
338 table,
339 name,
340 name_explicit: false,
341 columns,
342 is_unique: false,
343 where_clause: None,
344 method: None,
345 with: None,
346 concurrently: false,
347 }
348 }
349
350 #[must_use]
352 pub const fn unique(self) -> Self {
353 Self {
354 is_unique: true,
355 ..self
356 }
357 }
358
359 #[must_use]
361 pub const fn explicit_name(self) -> Self {
362 Self {
363 name_explicit: true,
364 ..self
365 }
366 }
367
368 #[must_use]
370 pub const fn where_clause(self, clause: &'static str) -> Self {
371 Self {
372 where_clause: Some(clause),
373 ..self
374 }
375 }
376
377 #[must_use]
379 pub const fn method(self, method: &'static str) -> Self {
380 Self {
381 method: Some(method),
382 ..self
383 }
384 }
385
386 #[must_use]
388 pub const fn with(self, params: &'static str) -> Self {
389 Self {
390 with: Some(params),
391 ..self
392 }
393 }
394
395 #[must_use]
397 pub const fn concurrently(self) -> Self {
398 Self {
399 concurrently: true,
400 ..self
401 }
402 }
403
404 #[must_use]
406 pub fn into_index(self) -> Index {
407 Index {
408 schema: Cow::Borrowed(self.schema),
409 table: Cow::Borrowed(self.table),
410 name: Cow::Borrowed(self.name),
411 name_explicit: self.name_explicit,
412 columns: self.columns.iter().map(|c| IndexColumn::from(*c)).collect(),
413 is_unique: self.is_unique,
414 where_clause: self.where_clause.map(Cow::Borrowed),
415 method: self.method.map(Cow::Borrowed),
416 with: self.with.map(Cow::Borrowed),
417 concurrently: self.concurrently,
418 }
419 }
420}
421
422#[derive(Clone, Debug, PartialEq, Eq)]
428#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
429#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
430pub struct Index {
431 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
433 pub schema: Cow<'static, str>,
434
435 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
437 pub table: Cow<'static, str>,
438
439 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
441 pub name: Cow<'static, str>,
442
443 #[cfg_attr(feature = "serde", serde(default))]
445 pub name_explicit: bool,
446
447 pub columns: Vec<IndexColumn>,
449
450 #[cfg_attr(feature = "serde", serde(default))]
452 pub is_unique: bool,
453
454 #[cfg_attr(
456 feature = "serde",
457 serde(
458 default,
459 skip_serializing_if = "Option::is_none",
460 rename = "where",
461 deserialize_with = "cow_option_from_string"
462 )
463 )]
464 pub where_clause: Option<Cow<'static, str>>,
465
466 #[cfg_attr(
468 feature = "serde",
469 serde(
470 default,
471 skip_serializing_if = "Option::is_none",
472 deserialize_with = "cow_option_from_string"
473 )
474 )]
475 pub method: Option<Cow<'static, str>>,
476
477 #[cfg_attr(
479 feature = "serde",
480 serde(
481 default,
482 skip_serializing_if = "Option::is_none",
483 deserialize_with = "cow_option_from_string"
484 )
485 )]
486 pub with: Option<Cow<'static, str>>,
487
488 #[cfg_attr(feature = "serde", serde(default))]
490 pub concurrently: bool,
491}
492
493impl Index {
494 #[must_use]
496 pub fn new(
497 schema: impl Into<Cow<'static, str>>,
498 table: impl Into<Cow<'static, str>>,
499 name: impl Into<Cow<'static, str>>,
500 columns: Vec<IndexColumn>,
501 ) -> Self {
502 Self {
503 schema: schema.into(),
504 table: table.into(),
505 name: name.into(),
506 name_explicit: false,
507 columns,
508 is_unique: false,
509 where_clause: None,
510 method: None,
511 with: None,
512 concurrently: false,
513 }
514 }
515
516 #[must_use]
518 pub fn unique(mut self) -> Self {
519 self.is_unique = true;
520 self
521 }
522
523 #[must_use]
525 pub fn explicit_name(mut self) -> Self {
526 self.name_explicit = true;
527 self
528 }
529
530 #[inline]
532 #[must_use]
533 pub fn schema(&self) -> &str {
534 &self.schema
535 }
536
537 #[inline]
539 #[must_use]
540 pub fn name(&self) -> &str {
541 &self.name
542 }
543
544 #[inline]
546 #[must_use]
547 pub fn table(&self) -> &str {
548 &self.table
549 }
550}
551
552impl Default for Index {
553 fn default() -> Self {
554 Self::new("public", "", "", vec![])
555 }
556}
557
558impl From<IndexDef> for Index {
559 fn from(def: IndexDef) -> Self {
560 def.into_index()
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567
568 #[test]
569 fn test_const_index_def() {
570 const COLS: &[IndexColumnDef] = &[
571 IndexColumnDef::new("email"),
572 IndexColumnDef::new("created_at").desc(),
573 ];
574
575 const IDX: IndexDef = IndexDef::new("public", "users", "idx_users_email", COLS).unique();
576
577 assert_eq!(IDX.name, "idx_users_email");
578 assert_eq!(IDX.table, "users");
579 assert!(IDX.is_unique);
580 assert_eq!(IDX.columns.len(), 2);
581 }
582
583 #[test]
584 fn test_index_def_to_index() {
585 const COLS: &[IndexColumnDef] = &[IndexColumnDef::new("email")];
586 const DEF: IndexDef = IndexDef::new("public", "users", "idx_email", COLS).unique();
587
588 let idx = DEF.into_index();
589 assert_eq!(idx.name(), "idx_email");
590 assert!(idx.is_unique);
591 assert_eq!(idx.columns.len(), 1);
592 }
593
594 #[test]
595 fn test_into_index() {
596 const COLS: &[IndexColumnDef] = &[IndexColumnDef::new("email")];
597 const DEF: IndexDef = IndexDef::new("public", "users", "idx_email", COLS).unique();
598 let idx = DEF.into_index();
599
600 assert_eq!(idx.name, Cow::Borrowed("idx_email"));
601 assert!(idx.is_unique);
602 }
603}