1use crate::alloc_prelude::*;
7use core::fmt::Write;
8
9#[cfg(feature = "serde")]
10use crate::serde_helpers::{cow_from_string, cow_option_from_string};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
22pub struct OpclassDef {
23 pub name: &'static str,
25 #[cfg_attr(feature = "serde", serde(default))]
27 pub default: bool,
28}
29
30impl OpclassDef {
31 #[must_use]
33 pub const fn new(name: &'static str) -> Self {
34 Self {
35 name,
36 default: false,
37 }
38 }
39
40 #[must_use]
42 pub const fn default_opclass(self) -> Self {
43 Self {
44 default: true,
45 ..self
46 }
47 }
48
49 #[must_use]
51 pub const fn into_opclass(self) -> Opclass {
52 Opclass {
53 name: Cow::Borrowed(self.name),
54 default: self.default,
55 }
56 }
57}
58
59#[derive(Clone, Debug, PartialEq, Eq)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
62#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
63pub struct Opclass {
64 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
66 pub name: Cow<'static, str>,
67 #[cfg_attr(feature = "serde", serde(default))]
69 pub default: bool,
70}
71
72impl Opclass {
73 #[must_use]
75 pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
76 Self {
77 name: name.into(),
78 default: false,
79 }
80 }
81
82 #[inline]
84 #[must_use]
85 pub fn name(&self) -> &str {
86 &self.name
87 }
88}
89
90impl core::fmt::Display for OpclassDef {
91 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
92 write!(f, "{}", self.name)
93 }
94}
95
96impl core::fmt::Display for Opclass {
97 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
98 write!(f, "{}", self.name)
99 }
100}
101
102#[derive(Clone, Debug, PartialEq, Eq)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
105#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
106pub struct IndexColumn {
107 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
109 pub value: Cow<'static, str>,
110 #[cfg_attr(feature = "serde", serde(default))]
112 pub is_expression: bool,
113 #[cfg_attr(feature = "serde", serde(default = "default_true"))]
115 pub asc: bool,
116 #[cfg_attr(feature = "serde", serde(default))]
119 pub nulls_first: bool,
120 #[cfg_attr(
122 feature = "serde",
123 serde(default, skip_serializing_if = "Option::is_none")
124 )]
125 pub opclass: Option<Opclass>,
126}
127
128impl IndexColumn {
129 #[must_use]
131 pub fn new(value: impl Into<Cow<'static, str>>) -> Self {
132 Self {
133 value: value.into(),
134 is_expression: false,
135 asc: true,
136 nulls_first: false,
137 opclass: None,
138 }
139 }
140
141 #[must_use]
143 pub fn expression(expr: impl Into<Cow<'static, str>>) -> Self {
144 Self {
145 value: expr.into(),
146 is_expression: true,
147 asc: true,
148 nulls_first: false,
149 opclass: None,
150 }
151 }
152
153 #[must_use]
155 pub const fn desc(mut self) -> Self {
156 self.asc = false;
157 self.nulls_first = true;
158 self
159 }
160
161 #[must_use]
163 pub const fn nulls_first(mut self) -> Self {
164 self.nulls_first = true;
165 self
166 }
167
168 #[must_use]
170 pub const fn nulls_last(mut self) -> Self {
171 self.nulls_first = false;
172 self
173 }
174
175 #[must_use]
177 pub fn with_opclass(mut self, opclass: Opclass) -> Self {
178 self.opclass = Some(opclass);
179 self
180 }
181}
182
183impl IndexColumn {
184 #[must_use]
186 pub fn to_sql(&self) -> String {
187 let mut sql = if self.is_expression {
188 format!("({})", self.value)
189 } else {
190 format!("\"{}\"", self.value)
191 };
192
193 if let Some(ref op) = self.opclass {
194 let _ = write!(sql, " {op}");
195 }
196 sql.push_str(match (self.asc, self.nulls_first) {
197 (true, false) => "",
198 (true, true) => " NULLS FIRST",
199 (false, true) => " DESC",
200 (false, false) => " DESC NULLS LAST",
201 });
202
203 sql
204 }
205}
206
207impl From<IndexColumnDef> for IndexColumn {
208 fn from(def: IndexColumnDef) -> Self {
209 Self {
210 value: Cow::Borrowed(def.value),
211 is_expression: def.is_expression,
212 asc: def.asc,
213 nulls_first: def.nulls_first,
214 opclass: def.opclass.map(OpclassDef::into_opclass),
215 }
216 }
217}
218
219impl From<OpclassDef> for Opclass {
220 fn from(def: OpclassDef) -> Self {
221 def.into_opclass()
222 }
223}
224
225#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
227#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
228#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
229pub struct IndexColumnDef {
230 pub value: &'static str,
232 #[cfg_attr(feature = "serde", serde(default))]
234 pub is_expression: bool,
235 #[cfg_attr(feature = "serde", serde(default = "default_true"))]
237 pub asc: bool,
238 #[cfg_attr(feature = "serde", serde(default))]
241 pub nulls_first: bool,
242 #[cfg_attr(
244 feature = "serde",
245 serde(default, skip_serializing_if = "Option::is_none")
246 )]
247 pub opclass: Option<OpclassDef>,
248}
249
250#[cfg(feature = "serde")]
251const fn default_true() -> bool {
252 true
253}
254
255impl IndexColumnDef {
256 #[must_use]
258 pub const fn new(value: &'static str) -> Self {
259 Self {
260 value,
261 is_expression: false,
262 asc: true,
263 nulls_first: false,
264 opclass: None,
265 }
266 }
267
268 #[must_use]
270 pub const fn expression(expr: &'static str) -> Self {
271 Self {
272 value: expr,
273 is_expression: true,
274 asc: true,
275 nulls_first: false,
276 opclass: None,
277 }
278 }
279
280 #[must_use]
282 pub const fn desc(self) -> Self {
283 Self {
284 asc: false,
285 nulls_first: true,
286 ..self
287 }
288 }
289
290 #[must_use]
292 pub const fn nulls_first(self) -> Self {
293 Self {
294 nulls_first: true,
295 ..self
296 }
297 }
298
299 #[must_use]
301 pub const fn nulls_last(self) -> Self {
302 Self {
303 nulls_first: false,
304 ..self
305 }
306 }
307
308 #[must_use]
310 pub const fn opclass(self, opclass: OpclassDef) -> Self {
311 Self {
312 opclass: Some(opclass),
313 ..self
314 }
315 }
316
317 #[must_use]
319 pub const fn opclass_name(self, name: &'static str) -> Self {
320 Self {
321 opclass: Some(OpclassDef::new(name)),
322 ..self
323 }
324 }
325}
326
327#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
329pub struct IndexDef {
330 pub schema: &'static str,
332 pub table: &'static str,
334 pub name: &'static str,
336 pub name_explicit: bool,
338 pub columns: &'static [IndexColumnDef],
340 pub is_unique: bool,
342 pub where_clause: Option<&'static str>,
344 pub method: Option<&'static str>,
346 pub with: Option<&'static str>,
348 pub concurrently: bool,
350}
351
352impl IndexDef {
353 #[must_use]
355 pub const fn new(
356 schema: &'static str,
357 table: &'static str,
358 name: &'static str,
359 columns: &'static [IndexColumnDef],
360 ) -> Self {
361 Self {
362 schema,
363 table,
364 name,
365 name_explicit: false,
366 columns,
367 is_unique: false,
368 where_clause: None,
369 method: None,
370 with: None,
371 concurrently: false,
372 }
373 }
374
375 #[must_use]
377 pub const fn unique(self) -> Self {
378 Self {
379 is_unique: true,
380 ..self
381 }
382 }
383
384 #[must_use]
386 pub const fn explicit_name(self) -> Self {
387 Self {
388 name_explicit: true,
389 ..self
390 }
391 }
392
393 #[must_use]
395 pub const fn where_clause(self, clause: &'static str) -> Self {
396 Self {
397 where_clause: Some(clause),
398 ..self
399 }
400 }
401
402 #[must_use]
404 pub const fn method(self, method: &'static str) -> Self {
405 Self {
406 method: Some(method),
407 ..self
408 }
409 }
410
411 #[must_use]
413 pub const fn with(self, params: &'static str) -> Self {
414 Self {
415 with: Some(params),
416 ..self
417 }
418 }
419
420 #[must_use]
422 pub const fn concurrently(self) -> Self {
423 Self {
424 concurrently: true,
425 ..self
426 }
427 }
428
429 #[must_use]
431 pub fn into_index(self) -> Index {
432 Index {
433 schema: Cow::Borrowed(self.schema),
434 table: Cow::Borrowed(self.table),
435 name: Cow::Borrowed(self.name),
436 name_explicit: self.name_explicit,
437 columns: self.columns.iter().map(|c| IndexColumn::from(*c)).collect(),
438 is_unique: self.is_unique,
439 where_clause: self.where_clause.map(Cow::Borrowed),
440 method: self.method.map(Cow::Borrowed),
441 with: self.with.map(Cow::Borrowed),
442 concurrently: self.concurrently,
443 }
444 }
445}
446
447#[derive(Clone, Debug, PartialEq, Eq)]
453#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
454#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
455pub struct Index {
456 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
458 pub schema: Cow<'static, str>,
459
460 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
462 pub table: Cow<'static, str>,
463
464 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
466 pub name: Cow<'static, str>,
467
468 #[cfg_attr(feature = "serde", serde(default))]
470 pub name_explicit: bool,
471
472 pub columns: Vec<IndexColumn>,
474
475 #[cfg_attr(feature = "serde", serde(default))]
477 pub is_unique: bool,
478
479 #[cfg_attr(
481 feature = "serde",
482 serde(
483 default,
484 skip_serializing_if = "Option::is_none",
485 rename = "where",
486 deserialize_with = "cow_option_from_string"
487 )
488 )]
489 pub where_clause: Option<Cow<'static, str>>,
490
491 #[cfg_attr(
493 feature = "serde",
494 serde(
495 default,
496 skip_serializing_if = "Option::is_none",
497 deserialize_with = "cow_option_from_string"
498 )
499 )]
500 pub method: Option<Cow<'static, str>>,
501
502 #[cfg_attr(
504 feature = "serde",
505 serde(
506 default,
507 skip_serializing_if = "Option::is_none",
508 deserialize_with = "cow_option_from_string"
509 )
510 )]
511 pub with: Option<Cow<'static, str>>,
512
513 #[cfg_attr(feature = "serde", serde(default))]
515 pub concurrently: bool,
516}
517
518impl Index {
519 #[must_use]
521 pub fn new(
522 schema: impl Into<Cow<'static, str>>,
523 table: impl Into<Cow<'static, str>>,
524 name: impl Into<Cow<'static, str>>,
525 columns: Vec<IndexColumn>,
526 ) -> Self {
527 Self {
528 schema: schema.into(),
529 table: table.into(),
530 name: name.into(),
531 name_explicit: false,
532 columns,
533 is_unique: false,
534 where_clause: None,
535 method: None,
536 with: None,
537 concurrently: false,
538 }
539 }
540
541 #[must_use]
543 pub const fn unique(mut self) -> Self {
544 self.is_unique = true;
545 self
546 }
547
548 #[must_use]
550 pub const fn explicit_name(mut self) -> Self {
551 self.name_explicit = true;
552 self
553 }
554
555 #[inline]
557 #[must_use]
558 pub fn schema(&self) -> &str {
559 &self.schema
560 }
561
562 #[inline]
564 #[must_use]
565 pub fn name(&self) -> &str {
566 &self.name
567 }
568
569 #[inline]
571 #[must_use]
572 pub fn table(&self) -> &str {
573 &self.table
574 }
575}
576
577impl Default for Index {
578 fn default() -> Self {
579 Self::new("public", "", "", vec![])
580 }
581}
582
583impl From<IndexDef> for Index {
584 fn from(def: IndexDef) -> Self {
585 def.into_index()
586 }
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592
593 #[test]
594 fn test_const_index_def() {
595 const COLS: &[IndexColumnDef] = &[
596 IndexColumnDef::new("email"),
597 IndexColumnDef::new("created_at").desc(),
598 ];
599
600 const IDX: IndexDef = IndexDef::new("public", "users", "idx_users_email", COLS).unique();
601
602 assert_eq!(IDX.name, "idx_users_email");
603 assert_eq!(IDX.table, "users");
604 const {
605 assert!(IDX.is_unique);
606 }
607 assert_eq!(IDX.columns.len(), 2);
608 }
609
610 #[test]
611 fn test_index_def_to_index() {
612 const COLS: &[IndexColumnDef] = &[IndexColumnDef::new("email")];
613 const DEF: IndexDef = IndexDef::new("public", "users", "idx_email", COLS).unique();
614
615 let idx = DEF.into_index();
616 assert_eq!(idx.name(), "idx_email");
617 assert!(idx.is_unique);
618 assert_eq!(idx.columns.len(), 1);
619 }
620
621 #[test]
622 fn test_into_index() {
623 const COLS: &[IndexColumnDef] = &[IndexColumnDef::new("email")];
624 const DEF: IndexDef = IndexDef::new("public", "users", "idx_email", COLS).unique();
625 let idx = DEF.into_index();
626
627 assert_eq!(idx.name, Cow::Borrowed("idx_email"));
628 assert!(idx.is_unique);
629 }
630}