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