1#[cfg(feature = "std")]
8use std::borrow::Cow;
9
10#[cfg(all(feature = "alloc", not(feature = "std")))]
11use alloc::borrow::Cow;
12
13#[cfg(feature = "serde")]
14use crate::serde_helpers::{cow_from_string, cow_option_from_string};
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
22pub struct ViewWithOptionDef {
23 pub check_option: Option<&'static str>,
25 pub security_barrier: bool,
27 pub security_invoker: bool,
29 pub fillfactor: Option<i32>,
31 pub toast_tuple_target: Option<i32>,
33 pub parallel_workers: Option<i32>,
35 pub autovacuum_enabled: Option<bool>,
37 pub vacuum_index_cleanup: Option<&'static str>,
39 pub vacuum_truncate: Option<bool>,
41 pub autovacuum_vacuum_threshold: Option<i32>,
43 pub autovacuum_vacuum_scale_factor: Option<i32>,
45 pub autovacuum_vacuum_cost_delay: Option<i32>,
47 pub autovacuum_vacuum_cost_limit: Option<i32>,
49 pub autovacuum_freeze_min_age: Option<i64>,
51 pub autovacuum_freeze_max_age: Option<i64>,
53 pub autovacuum_freeze_table_age: Option<i64>,
55 pub autovacuum_multixact_freeze_min_age: Option<i64>,
57 pub autovacuum_multixact_freeze_max_age: Option<i64>,
59 pub autovacuum_multixact_freeze_table_age: Option<i64>,
61 pub log_autovacuum_min_duration: Option<i32>,
63 pub user_catalog_table: Option<bool>,
65}
66
67impl ViewWithOptionDef {
68 #[must_use]
70 pub const fn new() -> Self {
71 Self {
72 check_option: None,
73 security_barrier: false,
74 security_invoker: false,
75 fillfactor: None,
76 toast_tuple_target: None,
77 parallel_workers: None,
78 autovacuum_enabled: None,
79 vacuum_index_cleanup: None,
80 vacuum_truncate: None,
81 autovacuum_vacuum_threshold: None,
82 autovacuum_vacuum_scale_factor: None,
83 autovacuum_vacuum_cost_delay: None,
84 autovacuum_vacuum_cost_limit: None,
85 autovacuum_freeze_min_age: None,
86 autovacuum_freeze_max_age: None,
87 autovacuum_freeze_table_age: None,
88 autovacuum_multixact_freeze_min_age: None,
89 autovacuum_multixact_freeze_max_age: None,
90 autovacuum_multixact_freeze_table_age: None,
91 log_autovacuum_min_duration: None,
92 user_catalog_table: None,
93 }
94 }
95
96 #[must_use]
98 pub const fn check_option(self, option: &'static str) -> Self {
99 Self {
100 check_option: Some(option),
101 ..self
102 }
103 }
104
105 #[must_use]
107 pub const fn security_barrier(self) -> Self {
108 Self {
109 security_barrier: true,
110 ..self
111 }
112 }
113
114 #[must_use]
116 pub const fn security_invoker(self) -> Self {
117 Self {
118 security_invoker: true,
119 ..self
120 }
121 }
122
123 #[must_use]
125 pub const fn fillfactor(self, value: i32) -> Self {
126 Self {
127 fillfactor: Some(value),
128 ..self
129 }
130 }
131
132 #[must_use]
134 pub const fn toast_tuple_target(self, value: i32) -> Self {
135 Self {
136 toast_tuple_target: Some(value),
137 ..self
138 }
139 }
140
141 #[must_use]
143 pub const fn parallel_workers(self, value: i32) -> Self {
144 Self {
145 parallel_workers: Some(value),
146 ..self
147 }
148 }
149
150 #[must_use]
152 pub const fn autovacuum_enabled(self, value: bool) -> Self {
153 Self {
154 autovacuum_enabled: Some(value),
155 ..self
156 }
157 }
158
159 #[must_use]
161 pub const fn vacuum_index_cleanup(self, value: &'static str) -> Self {
162 Self {
163 vacuum_index_cleanup: Some(value),
164 ..self
165 }
166 }
167
168 #[must_use]
170 pub const fn vacuum_truncate(self, value: bool) -> Self {
171 Self {
172 vacuum_truncate: Some(value),
173 ..self
174 }
175 }
176
177 #[must_use]
179 pub const fn autovacuum_vacuum_threshold(self, value: i32) -> Self {
180 Self {
181 autovacuum_vacuum_threshold: Some(value),
182 ..self
183 }
184 }
185
186 #[must_use]
188 pub const fn autovacuum_vacuum_scale_factor(self, value: i32) -> Self {
189 Self {
190 autovacuum_vacuum_scale_factor: Some(value),
191 ..self
192 }
193 }
194
195 #[must_use]
197 pub const fn autovacuum_vacuum_cost_delay(self, value: i32) -> Self {
198 Self {
199 autovacuum_vacuum_cost_delay: Some(value),
200 ..self
201 }
202 }
203
204 #[must_use]
206 pub const fn autovacuum_vacuum_cost_limit(self, value: i32) -> Self {
207 Self {
208 autovacuum_vacuum_cost_limit: Some(value),
209 ..self
210 }
211 }
212
213 #[must_use]
215 pub const fn autovacuum_freeze_min_age(self, value: i64) -> Self {
216 Self {
217 autovacuum_freeze_min_age: Some(value),
218 ..self
219 }
220 }
221
222 #[must_use]
224 pub const fn autovacuum_freeze_max_age(self, value: i64) -> Self {
225 Self {
226 autovacuum_freeze_max_age: Some(value),
227 ..self
228 }
229 }
230
231 #[must_use]
233 pub const fn autovacuum_freeze_table_age(self, value: i64) -> Self {
234 Self {
235 autovacuum_freeze_table_age: Some(value),
236 ..self
237 }
238 }
239
240 #[must_use]
242 pub const fn autovacuum_multixact_freeze_min_age(self, value: i64) -> Self {
243 Self {
244 autovacuum_multixact_freeze_min_age: Some(value),
245 ..self
246 }
247 }
248
249 #[must_use]
251 pub const fn autovacuum_multixact_freeze_max_age(self, value: i64) -> Self {
252 Self {
253 autovacuum_multixact_freeze_max_age: Some(value),
254 ..self
255 }
256 }
257
258 #[must_use]
260 pub const fn autovacuum_multixact_freeze_table_age(self, value: i64) -> Self {
261 Self {
262 autovacuum_multixact_freeze_table_age: Some(value),
263 ..self
264 }
265 }
266
267 #[must_use]
269 pub const fn log_autovacuum_min_duration(self, value: i32) -> Self {
270 Self {
271 log_autovacuum_min_duration: Some(value),
272 ..self
273 }
274 }
275
276 #[must_use]
278 pub const fn user_catalog_table(self, value: bool) -> Self {
279 Self {
280 user_catalog_table: Some(value),
281 ..self
282 }
283 }
284
285 #[must_use]
287 pub const fn into_view_with_option(self) -> ViewWithOption {
288 ViewWithOption {
289 check_option: match self.check_option {
290 Some(s) => Some(Cow::Borrowed(s)),
291 None => None,
292 },
293 security_barrier: if self.security_barrier {
294 Some(true)
295 } else {
296 None
297 },
298 security_invoker: if self.security_invoker {
299 Some(true)
300 } else {
301 None
302 },
303 fillfactor: self.fillfactor,
304 toast_tuple_target: self.toast_tuple_target,
305 parallel_workers: self.parallel_workers,
306 autovacuum_enabled: self.autovacuum_enabled,
307 vacuum_index_cleanup: match self.vacuum_index_cleanup {
308 Some(s) => Some(Cow::Borrowed(s)),
309 None => None,
310 },
311 vacuum_truncate: self.vacuum_truncate,
312 autovacuum_vacuum_threshold: self.autovacuum_vacuum_threshold,
313 autovacuum_vacuum_scale_factor: self.autovacuum_vacuum_scale_factor,
314 autovacuum_vacuum_cost_delay: self.autovacuum_vacuum_cost_delay,
315 autovacuum_vacuum_cost_limit: self.autovacuum_vacuum_cost_limit,
316 autovacuum_freeze_min_age: self.autovacuum_freeze_min_age,
317 autovacuum_freeze_max_age: self.autovacuum_freeze_max_age,
318 autovacuum_freeze_table_age: self.autovacuum_freeze_table_age,
319 autovacuum_multixact_freeze_min_age: self.autovacuum_multixact_freeze_min_age,
320 autovacuum_multixact_freeze_max_age: self.autovacuum_multixact_freeze_max_age,
321 autovacuum_multixact_freeze_table_age: self.autovacuum_multixact_freeze_table_age,
322 log_autovacuum_min_duration: self.log_autovacuum_min_duration,
323 user_catalog_table: self.user_catalog_table,
324 }
325 }
326}
327
328impl Default for ViewWithOptionDef {
329 fn default() -> Self {
330 Self::new()
331 }
332}
333
334#[derive(Clone, Debug, PartialEq, Eq)]
336#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
337#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
338pub struct ViewWithOption {
339 #[cfg_attr(
341 feature = "serde",
342 serde(
343 skip_serializing_if = "Option::is_none",
344 deserialize_with = "cow_option_from_string"
345 )
346 )]
347 pub check_option: Option<Cow<'static, str>>,
348
349 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
351 pub security_barrier: Option<bool>,
352
353 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
355 pub security_invoker: Option<bool>,
356
357 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
359 pub fillfactor: Option<i32>,
360
361 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
363 pub toast_tuple_target: Option<i32>,
364
365 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
367 pub parallel_workers: Option<i32>,
368
369 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
371 pub autovacuum_enabled: Option<bool>,
372
373 #[cfg_attr(
375 feature = "serde",
376 serde(
377 skip_serializing_if = "Option::is_none",
378 deserialize_with = "cow_option_from_string"
379 )
380 )]
381 pub vacuum_index_cleanup: Option<Cow<'static, str>>,
382
383 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
385 pub vacuum_truncate: Option<bool>,
386
387 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
389 pub autovacuum_vacuum_threshold: Option<i32>,
390
391 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
393 pub autovacuum_vacuum_scale_factor: Option<i32>,
394
395 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
397 pub autovacuum_vacuum_cost_delay: Option<i32>,
398
399 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
401 pub autovacuum_vacuum_cost_limit: Option<i32>,
402
403 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
405 pub autovacuum_freeze_min_age: Option<i64>,
406
407 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
409 pub autovacuum_freeze_max_age: Option<i64>,
410
411 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
413 pub autovacuum_freeze_table_age: Option<i64>,
414
415 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
417 pub autovacuum_multixact_freeze_min_age: Option<i64>,
418
419 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
421 pub autovacuum_multixact_freeze_max_age: Option<i64>,
422
423 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
425 pub autovacuum_multixact_freeze_table_age: Option<i64>,
426
427 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
429 pub log_autovacuum_min_duration: Option<i32>,
430
431 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
433 pub user_catalog_table: Option<bool>,
434}
435
436impl Default for ViewWithOption {
437 fn default() -> Self {
438 ViewWithOptionDef::new().into_view_with_option()
439 }
440}
441
442impl From<ViewWithOptionDef> for ViewWithOption {
443 fn from(def: ViewWithOptionDef) -> Self {
444 def.into_view_with_option()
445 }
446}
447
448#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
454pub struct ViewDef {
455 pub schema: &'static str,
457 pub name: &'static str,
459 pub definition: Option<&'static str>,
461 pub materialized: bool,
463 pub with: Option<ViewWithOptionDef>,
465 pub is_existing: bool,
467 pub with_no_data: bool,
469 pub using: Option<&'static str>,
471 pub tablespace: Option<&'static str>,
473}
474
475impl ViewDef {
476 #[must_use]
478 pub const fn new(schema: &'static str, name: &'static str) -> Self {
479 Self {
480 schema,
481 name,
482 definition: None,
483 materialized: false,
484 with: None,
485 is_existing: false,
486 with_no_data: false,
487 using: None,
488 tablespace: None,
489 }
490 }
491
492 #[must_use]
494 pub const fn definition(self, sql: &'static str) -> Self {
495 Self {
496 definition: Some(sql),
497 ..self
498 }
499 }
500
501 #[must_use]
503 pub const fn materialized(self) -> Self {
504 Self {
505 materialized: true,
506 ..self
507 }
508 }
509
510 #[must_use]
512 pub const fn with_options(self, options: ViewWithOptionDef) -> Self {
513 Self {
514 with: Some(options),
515 ..self
516 }
517 }
518
519 #[must_use]
521 pub const fn existing(self) -> Self {
522 Self {
523 is_existing: true,
524 ..self
525 }
526 }
527
528 #[must_use]
530 pub const fn with_no_data(self) -> Self {
531 Self {
532 with_no_data: true,
533 ..self
534 }
535 }
536
537 #[must_use]
539 pub const fn using(self, clause: &'static str) -> Self {
540 Self {
541 using: Some(clause),
542 ..self
543 }
544 }
545
546 #[must_use]
548 pub const fn tablespace(self, space: &'static str) -> Self {
549 Self {
550 tablespace: Some(space),
551 ..self
552 }
553 }
554
555 #[must_use]
560 pub fn into_view(self) -> View {
561 View {
562 schema: Cow::Borrowed(self.schema),
563 name: Cow::Borrowed(self.name),
564 definition: match self.definition {
565 Some(s) => Some(Cow::Borrowed(s)),
566 None => None,
567 },
568 materialized: self.materialized,
569 with: self.with.map(|w| w.into_view_with_option()),
570 is_existing: self.is_existing,
571 with_no_data: if self.with_no_data { Some(true) } else { None },
572 using: match self.using {
573 Some(s) => Some(Cow::Borrowed(s)),
574 None => None,
575 },
576 tablespace: match self.tablespace {
577 Some(s) => Some(Cow::Borrowed(s)),
578 None => None,
579 },
580 }
581 }
582}
583
584impl Default for ViewDef {
585 fn default() -> Self {
586 Self::new("public", "")
587 }
588}
589
590#[derive(Clone, Debug, PartialEq, Eq)]
596#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
597#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
598pub struct View {
599 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
601 pub schema: Cow<'static, str>,
602
603 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
605 pub name: Cow<'static, str>,
606
607 #[cfg_attr(
609 feature = "serde",
610 serde(
611 default,
612 skip_serializing_if = "Option::is_none",
613 deserialize_with = "cow_option_from_string"
614 )
615 )]
616 pub definition: Option<Cow<'static, str>>,
617
618 #[cfg_attr(feature = "serde", serde(default))]
620 pub materialized: bool,
621
622 #[cfg_attr(
624 feature = "serde",
625 serde(skip_serializing_if = "Option::is_none", rename = "with")
626 )]
627 pub with: Option<ViewWithOption>,
628
629 #[cfg_attr(feature = "serde", serde(default))]
631 pub is_existing: bool,
632
633 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
635 pub with_no_data: Option<bool>,
636
637 #[cfg_attr(
639 feature = "serde",
640 serde(
641 skip_serializing_if = "Option::is_none",
642 deserialize_with = "cow_option_from_string"
643 )
644 )]
645 pub using: Option<Cow<'static, str>>,
646
647 #[cfg_attr(
649 feature = "serde",
650 serde(
651 skip_serializing_if = "Option::is_none",
652 deserialize_with = "cow_option_from_string"
653 )
654 )]
655 pub tablespace: Option<Cow<'static, str>>,
656}
657
658impl View {
659 #[must_use]
661 pub fn new(schema: impl Into<Cow<'static, str>>, name: impl Into<Cow<'static, str>>) -> Self {
662 Self {
663 schema: schema.into(),
664 name: name.into(),
665 definition: None,
666 materialized: false,
667 with: None,
668 is_existing: false,
669 with_no_data: None,
670 using: None,
671 tablespace: None,
672 }
673 }
674
675 #[inline]
677 #[must_use]
678 pub fn schema(&self) -> &str {
679 &self.schema
680 }
681
682 #[inline]
684 #[must_use]
685 pub fn name(&self) -> &str {
686 &self.name
687 }
688}
689
690impl Default for View {
691 fn default() -> Self {
692 Self::new("public", "")
693 }
694}
695
696impl From<ViewDef> for View {
697 fn from(def: ViewDef) -> Self {
698 def.into_view()
699 }
700}
701
702#[cfg(test)]
703mod tests {
704 use super::*;
705
706 #[test]
707 fn test_const_view_def() {
708 const VIEW: ViewDef = ViewDef::new("public", "active_users")
709 .definition("SELECT * FROM users WHERE active = 1");
710
711 assert_eq!(VIEW.name, "active_users");
712 assert_eq!(VIEW.schema, "public");
713 }
714
715 #[test]
716 fn test_materialized_view_def() {
717 const MAT_VIEW: ViewDef = ViewDef::new("public", "user_stats")
718 .materialized()
719 .with_no_data();
720
721 assert!(MAT_VIEW.materialized);
722 }
723
724 #[test]
725 fn test_view_def_to_view() {
726 const DEF: ViewDef = ViewDef::new("public", "view").definition("SELECT 1");
727 let view = DEF.into_view();
728 assert_eq!(view.name(), "view");
729 assert_eq!(view.schema(), "public");
730 }
731
732 #[test]
733 fn test_view_with_option_def_builders() {
734 const OPTIONS: ViewWithOptionDef = ViewWithOptionDef::new()
736 .fillfactor(80)
737 .parallel_workers(4)
738 .autovacuum_enabled(true)
739 .vacuum_index_cleanup("auto")
740 .vacuum_truncate(false)
741 .autovacuum_vacuum_threshold(100)
742 .autovacuum_vacuum_scale_factor(20)
743 .autovacuum_vacuum_cost_delay(10)
744 .autovacuum_vacuum_cost_limit(200)
745 .autovacuum_freeze_min_age(50_000_000)
746 .autovacuum_freeze_max_age(200_000_000)
747 .autovacuum_freeze_table_age(150_000_000)
748 .autovacuum_multixact_freeze_min_age(5_000_000)
749 .autovacuum_multixact_freeze_max_age(400_000_000)
750 .autovacuum_multixact_freeze_table_age(150_000_000)
751 .log_autovacuum_min_duration(1000)
752 .user_catalog_table(false)
753 .toast_tuple_target(128);
754
755 assert_eq!(OPTIONS.fillfactor, Some(80));
756 assert_eq!(OPTIONS.parallel_workers, Some(4));
757 assert_eq!(OPTIONS.autovacuum_enabled, Some(true));
758 assert_eq!(OPTIONS.vacuum_index_cleanup, Some("auto"));
759 assert_eq!(OPTIONS.vacuum_truncate, Some(false));
760 assert_eq!(OPTIONS.autovacuum_vacuum_threshold, Some(100));
761 assert_eq!(OPTIONS.autovacuum_vacuum_scale_factor, Some(20));
762 assert_eq!(OPTIONS.autovacuum_vacuum_cost_delay, Some(10));
763 assert_eq!(OPTIONS.autovacuum_vacuum_cost_limit, Some(200));
764 assert_eq!(OPTIONS.autovacuum_freeze_min_age, Some(50_000_000));
765 assert_eq!(OPTIONS.autovacuum_freeze_max_age, Some(200_000_000));
766 assert_eq!(OPTIONS.autovacuum_freeze_table_age, Some(150_000_000));
767 assert_eq!(OPTIONS.autovacuum_multixact_freeze_min_age, Some(5_000_000));
768 assert_eq!(
769 OPTIONS.autovacuum_multixact_freeze_max_age,
770 Some(400_000_000)
771 );
772 assert_eq!(
773 OPTIONS.autovacuum_multixact_freeze_table_age,
774 Some(150_000_000)
775 );
776 assert_eq!(OPTIONS.log_autovacuum_min_duration, Some(1000));
777 assert_eq!(OPTIONS.user_catalog_table, Some(false));
778 assert_eq!(OPTIONS.toast_tuple_target, Some(128));
779 }
780
781 #[test]
782 fn test_view_with_option_def_to_runtime() {
783 const OPTIONS: ViewWithOptionDef = ViewWithOptionDef::new()
784 .fillfactor(90)
785 .security_barrier()
786 .security_invoker()
787 .check_option("cascaded");
788
789 let runtime = OPTIONS.into_view_with_option();
790 assert_eq!(runtime.fillfactor, Some(90));
791 assert_eq!(runtime.security_barrier, Some(true));
792 assert_eq!(runtime.security_invoker, Some(true));
793 assert_eq!(runtime.check_option.as_deref(), Some("cascaded"));
794 }
795
796 #[test]
797 fn test_materialized_view_with_all_options() {
798 const MAT_VIEW: ViewDef = ViewDef::new("analytics", "monthly_sales")
799 .materialized()
800 .with_no_data()
801 .using("btree")
802 .tablespace("fast_ssd")
803 .with_options(ViewWithOptionDef::new().fillfactor(90).parallel_workers(2))
804 .definition("SELECT * FROM sales WHERE date > now() - interval '30 days'");
805
806 assert!(MAT_VIEW.materialized);
807 assert!(MAT_VIEW.with_no_data);
808 assert_eq!(MAT_VIEW.using, Some("btree"));
809 assert_eq!(MAT_VIEW.tablespace, Some("fast_ssd"));
810 assert!(MAT_VIEW.with.is_some());
811
812 let options = MAT_VIEW.with.unwrap();
813 assert_eq!(options.fillfactor, Some(90));
814 assert_eq!(options.parallel_workers, Some(2));
815 }
816}