1use crate::alloc_prelude::*;
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)]
18pub struct ViewWithOptionDef {
19 pub check_option: Option<&'static str>,
21 pub security_barrier: bool,
23 pub security_invoker: bool,
25 pub fillfactor: Option<i32>,
27 pub toast_tuple_target: Option<i32>,
29 pub parallel_workers: Option<i32>,
31 pub autovacuum_enabled: Option<bool>,
33 pub vacuum_index_cleanup: Option<&'static str>,
35 pub vacuum_truncate: Option<bool>,
37 pub autovacuum_vacuum_threshold: Option<i32>,
39 pub autovacuum_vacuum_scale_factor: Option<i32>,
41 pub autovacuum_vacuum_cost_delay: Option<i32>,
43 pub autovacuum_vacuum_cost_limit: Option<i32>,
45 pub autovacuum_freeze_min_age: Option<i64>,
47 pub autovacuum_freeze_max_age: Option<i64>,
49 pub autovacuum_freeze_table_age: Option<i64>,
51 pub autovacuum_multixact_freeze_min_age: Option<i64>,
53 pub autovacuum_multixact_freeze_max_age: Option<i64>,
55 pub autovacuum_multixact_freeze_table_age: Option<i64>,
57 pub log_autovacuum_min_duration: Option<i32>,
59 pub user_catalog_table: Option<bool>,
61}
62
63impl ViewWithOptionDef {
64 #[must_use]
66 pub const fn new() -> Self {
67 Self {
68 check_option: None,
69 security_barrier: false,
70 security_invoker: false,
71 fillfactor: None,
72 toast_tuple_target: None,
73 parallel_workers: None,
74 autovacuum_enabled: None,
75 vacuum_index_cleanup: None,
76 vacuum_truncate: None,
77 autovacuum_vacuum_threshold: None,
78 autovacuum_vacuum_scale_factor: None,
79 autovacuum_vacuum_cost_delay: None,
80 autovacuum_vacuum_cost_limit: None,
81 autovacuum_freeze_min_age: None,
82 autovacuum_freeze_max_age: None,
83 autovacuum_freeze_table_age: None,
84 autovacuum_multixact_freeze_min_age: None,
85 autovacuum_multixact_freeze_max_age: None,
86 autovacuum_multixact_freeze_table_age: None,
87 log_autovacuum_min_duration: None,
88 user_catalog_table: None,
89 }
90 }
91
92 #[must_use]
94 pub const fn check_option(self, option: &'static str) -> Self {
95 Self {
96 check_option: Some(option),
97 ..self
98 }
99 }
100
101 #[must_use]
103 pub const fn security_barrier(self) -> Self {
104 Self {
105 security_barrier: true,
106 ..self
107 }
108 }
109
110 #[must_use]
112 pub const fn security_invoker(self) -> Self {
113 Self {
114 security_invoker: true,
115 ..self
116 }
117 }
118
119 #[must_use]
121 pub const fn fillfactor(self, value: i32) -> Self {
122 Self {
123 fillfactor: Some(value),
124 ..self
125 }
126 }
127
128 #[must_use]
130 pub const fn toast_tuple_target(self, value: i32) -> Self {
131 Self {
132 toast_tuple_target: Some(value),
133 ..self
134 }
135 }
136
137 #[must_use]
139 pub const fn parallel_workers(self, value: i32) -> Self {
140 Self {
141 parallel_workers: Some(value),
142 ..self
143 }
144 }
145
146 #[must_use]
148 pub const fn autovacuum_enabled(self, value: bool) -> Self {
149 Self {
150 autovacuum_enabled: Some(value),
151 ..self
152 }
153 }
154
155 #[must_use]
157 pub const fn vacuum_index_cleanup(self, value: &'static str) -> Self {
158 Self {
159 vacuum_index_cleanup: Some(value),
160 ..self
161 }
162 }
163
164 #[must_use]
166 pub const fn vacuum_truncate(self, value: bool) -> Self {
167 Self {
168 vacuum_truncate: Some(value),
169 ..self
170 }
171 }
172
173 #[must_use]
175 pub const fn autovacuum_vacuum_threshold(self, value: i32) -> Self {
176 Self {
177 autovacuum_vacuum_threshold: Some(value),
178 ..self
179 }
180 }
181
182 #[must_use]
184 pub const fn autovacuum_vacuum_scale_factor(self, value: i32) -> Self {
185 Self {
186 autovacuum_vacuum_scale_factor: Some(value),
187 ..self
188 }
189 }
190
191 #[must_use]
193 pub const fn autovacuum_vacuum_cost_delay(self, value: i32) -> Self {
194 Self {
195 autovacuum_vacuum_cost_delay: Some(value),
196 ..self
197 }
198 }
199
200 #[must_use]
202 pub const fn autovacuum_vacuum_cost_limit(self, value: i32) -> Self {
203 Self {
204 autovacuum_vacuum_cost_limit: Some(value),
205 ..self
206 }
207 }
208
209 #[must_use]
211 pub const fn autovacuum_freeze_min_age(self, value: i64) -> Self {
212 Self {
213 autovacuum_freeze_min_age: Some(value),
214 ..self
215 }
216 }
217
218 #[must_use]
220 pub const fn autovacuum_freeze_max_age(self, value: i64) -> Self {
221 Self {
222 autovacuum_freeze_max_age: Some(value),
223 ..self
224 }
225 }
226
227 #[must_use]
229 pub const fn autovacuum_freeze_table_age(self, value: i64) -> Self {
230 Self {
231 autovacuum_freeze_table_age: Some(value),
232 ..self
233 }
234 }
235
236 #[must_use]
238 pub const fn autovacuum_multixact_freeze_min_age(self, value: i64) -> Self {
239 Self {
240 autovacuum_multixact_freeze_min_age: Some(value),
241 ..self
242 }
243 }
244
245 #[must_use]
247 pub const fn autovacuum_multixact_freeze_max_age(self, value: i64) -> Self {
248 Self {
249 autovacuum_multixact_freeze_max_age: Some(value),
250 ..self
251 }
252 }
253
254 #[must_use]
256 pub const fn autovacuum_multixact_freeze_table_age(self, value: i64) -> Self {
257 Self {
258 autovacuum_multixact_freeze_table_age: Some(value),
259 ..self
260 }
261 }
262
263 #[must_use]
265 pub const fn log_autovacuum_min_duration(self, value: i32) -> Self {
266 Self {
267 log_autovacuum_min_duration: Some(value),
268 ..self
269 }
270 }
271
272 #[must_use]
274 pub const fn user_catalog_table(self, value: bool) -> Self {
275 Self {
276 user_catalog_table: Some(value),
277 ..self
278 }
279 }
280
281 #[must_use]
283 pub const fn into_view_with_option(self) -> ViewWithOption {
284 ViewWithOption {
285 check_option: match self.check_option {
286 Some(s) => Some(Cow::Borrowed(s)),
287 None => None,
288 },
289 security_barrier: if self.security_barrier {
290 Some(true)
291 } else {
292 None
293 },
294 security_invoker: if self.security_invoker {
295 Some(true)
296 } else {
297 None
298 },
299 fillfactor: self.fillfactor,
300 toast_tuple_target: self.toast_tuple_target,
301 parallel_workers: self.parallel_workers,
302 autovacuum_enabled: self.autovacuum_enabled,
303 vacuum_index_cleanup: match self.vacuum_index_cleanup {
304 Some(s) => Some(Cow::Borrowed(s)),
305 None => None,
306 },
307 vacuum_truncate: self.vacuum_truncate,
308 autovacuum_vacuum_threshold: self.autovacuum_vacuum_threshold,
309 autovacuum_vacuum_scale_factor: self.autovacuum_vacuum_scale_factor,
310 autovacuum_vacuum_cost_delay: self.autovacuum_vacuum_cost_delay,
311 autovacuum_vacuum_cost_limit: self.autovacuum_vacuum_cost_limit,
312 autovacuum_freeze_min_age: self.autovacuum_freeze_min_age,
313 autovacuum_freeze_max_age: self.autovacuum_freeze_max_age,
314 autovacuum_freeze_table_age: self.autovacuum_freeze_table_age,
315 autovacuum_multixact_freeze_min_age: self.autovacuum_multixact_freeze_min_age,
316 autovacuum_multixact_freeze_max_age: self.autovacuum_multixact_freeze_max_age,
317 autovacuum_multixact_freeze_table_age: self.autovacuum_multixact_freeze_table_age,
318 log_autovacuum_min_duration: self.log_autovacuum_min_duration,
319 user_catalog_table: self.user_catalog_table,
320 }
321 }
322}
323
324impl Default for ViewWithOptionDef {
325 fn default() -> Self {
326 Self::new()
327 }
328}
329
330#[derive(Clone, Debug, PartialEq, Eq)]
332#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
333#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
334pub struct ViewWithOption {
335 #[cfg_attr(
337 feature = "serde",
338 serde(
339 default,
340 skip_serializing_if = "Option::is_none",
341 deserialize_with = "cow_option_from_string"
342 )
343 )]
344 pub check_option: Option<Cow<'static, str>>,
345
346 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
348 pub security_barrier: Option<bool>,
349
350 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
352 pub security_invoker: Option<bool>,
353
354 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
356 pub fillfactor: Option<i32>,
357
358 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
360 pub toast_tuple_target: Option<i32>,
361
362 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
364 pub parallel_workers: Option<i32>,
365
366 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
368 pub autovacuum_enabled: Option<bool>,
369
370 #[cfg_attr(
372 feature = "serde",
373 serde(
374 default,
375 skip_serializing_if = "Option::is_none",
376 deserialize_with = "cow_option_from_string"
377 )
378 )]
379 pub vacuum_index_cleanup: Option<Cow<'static, str>>,
380
381 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
383 pub vacuum_truncate: Option<bool>,
384
385 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
387 pub autovacuum_vacuum_threshold: Option<i32>,
388
389 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
391 pub autovacuum_vacuum_scale_factor: Option<i32>,
392
393 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
395 pub autovacuum_vacuum_cost_delay: Option<i32>,
396
397 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
399 pub autovacuum_vacuum_cost_limit: Option<i32>,
400
401 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
403 pub autovacuum_freeze_min_age: Option<i64>,
404
405 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
407 pub autovacuum_freeze_max_age: Option<i64>,
408
409 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
411 pub autovacuum_freeze_table_age: Option<i64>,
412
413 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
415 pub autovacuum_multixact_freeze_min_age: Option<i64>,
416
417 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
419 pub autovacuum_multixact_freeze_max_age: Option<i64>,
420
421 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
423 pub autovacuum_multixact_freeze_table_age: Option<i64>,
424
425 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
427 pub log_autovacuum_min_duration: Option<i32>,
428
429 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
431 pub user_catalog_table: Option<bool>,
432}
433
434impl Default for ViewWithOption {
435 fn default() -> Self {
436 ViewWithOptionDef::new().into_view_with_option()
437 }
438}
439
440impl From<ViewWithOptionDef> for ViewWithOption {
441 fn from(def: ViewWithOptionDef) -> Self {
442 def.into_view_with_option()
443 }
444}
445
446#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
452pub struct ViewDef {
453 pub schema: &'static str,
455 pub name: &'static str,
457 pub definition: Option<&'static str>,
459 pub materialized: bool,
461 pub with: Option<ViewWithOptionDef>,
463 pub is_existing: bool,
465 pub with_no_data: bool,
467 pub using: Option<&'static str>,
469 pub tablespace: Option<&'static str>,
471}
472
473impl ViewDef {
474 #[must_use]
476 pub const fn new(schema: &'static str, name: &'static str) -> Self {
477 Self {
478 schema,
479 name,
480 definition: None,
481 materialized: false,
482 with: None,
483 is_existing: false,
484 with_no_data: false,
485 using: None,
486 tablespace: None,
487 }
488 }
489
490 #[must_use]
492 pub const fn definition(self, sql: &'static str) -> Self {
493 Self {
494 definition: Some(sql),
495 ..self
496 }
497 }
498
499 #[must_use]
501 pub const fn materialized(self) -> Self {
502 Self {
503 materialized: true,
504 ..self
505 }
506 }
507
508 #[must_use]
510 pub const fn with_options(self, options: ViewWithOptionDef) -> Self {
511 Self {
512 with: Some(options),
513 ..self
514 }
515 }
516
517 #[must_use]
519 pub const fn existing(self) -> Self {
520 Self {
521 is_existing: true,
522 ..self
523 }
524 }
525
526 #[must_use]
528 pub const fn with_no_data(self) -> Self {
529 Self {
530 with_no_data: true,
531 ..self
532 }
533 }
534
535 #[must_use]
537 pub const fn using(self, clause: &'static str) -> Self {
538 Self {
539 using: Some(clause),
540 ..self
541 }
542 }
543
544 #[must_use]
546 pub const fn tablespace(self, space: &'static str) -> Self {
547 Self {
548 tablespace: Some(space),
549 ..self
550 }
551 }
552
553 #[must_use]
558 pub fn into_view(self) -> View {
559 View {
560 schema: Cow::Borrowed(self.schema),
561 name: Cow::Borrowed(self.name),
562 definition: self.definition.map(Cow::Borrowed),
563 materialized: self.materialized,
564 with: self.with.map(ViewWithOptionDef::into_view_with_option),
565 is_existing: self.is_existing,
566 with_no_data: if self.with_no_data { Some(true) } else { None },
567 using: self.using.map(Cow::Borrowed),
568 tablespace: self.tablespace.map(Cow::Borrowed),
569 }
570 }
571}
572
573impl Default for ViewDef {
574 fn default() -> Self {
575 Self::new("public", "")
576 }
577}
578
579#[derive(Clone, Debug, PartialEq, Eq)]
585#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
586#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
587pub struct View {
588 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
590 pub schema: Cow<'static, str>,
591
592 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
594 pub name: Cow<'static, str>,
595
596 #[cfg_attr(
598 feature = "serde",
599 serde(
600 default,
601 skip_serializing_if = "Option::is_none",
602 deserialize_with = "cow_option_from_string"
603 )
604 )]
605 pub definition: Option<Cow<'static, str>>,
606
607 #[cfg_attr(feature = "serde", serde(default))]
609 pub materialized: bool,
610
611 #[cfg_attr(
613 feature = "serde",
614 serde(skip_serializing_if = "Option::is_none", rename = "with")
615 )]
616 pub with: Option<ViewWithOption>,
617
618 #[cfg_attr(feature = "serde", serde(default))]
620 pub is_existing: bool,
621
622 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
624 pub with_no_data: Option<bool>,
625
626 #[cfg_attr(
628 feature = "serde",
629 serde(
630 default,
631 skip_serializing_if = "Option::is_none",
632 deserialize_with = "cow_option_from_string"
633 )
634 )]
635 pub using: Option<Cow<'static, str>>,
636
637 #[cfg_attr(
639 feature = "serde",
640 serde(
641 default,
642 skip_serializing_if = "Option::is_none",
643 deserialize_with = "cow_option_from_string"
644 )
645 )]
646 pub tablespace: Option<Cow<'static, str>>,
647}
648
649impl View {
650 #[must_use]
652 pub fn new(schema: impl Into<Cow<'static, str>>, name: impl Into<Cow<'static, str>>) -> Self {
653 Self {
654 schema: schema.into(),
655 name: name.into(),
656 definition: None,
657 materialized: false,
658 with: None,
659 is_existing: false,
660 with_no_data: None,
661 using: None,
662 tablespace: None,
663 }
664 }
665
666 #[inline]
668 #[must_use]
669 pub fn schema(&self) -> &str {
670 &self.schema
671 }
672
673 #[inline]
675 #[must_use]
676 pub fn name(&self) -> &str {
677 &self.name
678 }
679}
680
681impl Default for View {
682 fn default() -> Self {
683 Self::new("public", "")
684 }
685}
686
687impl From<ViewDef> for View {
688 fn from(def: ViewDef) -> Self {
689 def.into_view()
690 }
691}
692
693#[cfg(test)]
694mod tests {
695 use super::*;
696
697 #[test]
698 fn test_const_view_def() {
699 const VIEW: ViewDef = ViewDef::new("public", "active_users")
700 .definition("SELECT * FROM users WHERE active = 1");
701
702 assert_eq!(VIEW.name, "active_users");
703 assert_eq!(VIEW.schema, "public");
704 }
705
706 #[cfg(feature = "serde")]
709 #[test]
710 fn test_serde_roundtrip_with_all_none_optionals() {
711 let view = View {
712 schema: Cow::Borrowed("public"),
713 name: Cow::Borrowed("plain_view"),
714 definition: Some(Cow::Borrowed("SELECT 1")),
715 ..View::default()
716 };
717 assert!(view.using.is_none());
718 assert!(view.tablespace.is_none());
719
720 let json = serde_json::to_string(&view).expect("serialize");
721 let parsed: View = serde_json::from_str(&json).expect("deserialize");
722 assert_eq!(parsed, view);
723
724 let with_options = View {
726 with: Some(ViewWithOption {
727 security_barrier: Some(true),
728 ..ViewWithOption::default()
729 }),
730 ..view
731 };
732 let json = serde_json::to_string(&with_options).expect("serialize");
733 let parsed: View = serde_json::from_str(&json).expect("deserialize");
734 assert_eq!(parsed, with_options);
735 }
736
737 #[test]
738 fn test_materialized_view_def() {
739 const MAT_VIEW: ViewDef = ViewDef::new("public", "user_stats")
740 .materialized()
741 .with_no_data();
742
743 const {
744 assert!(MAT_VIEW.materialized);
745 }
746 }
747
748 #[test]
749 fn test_view_def_to_view() {
750 const DEF: ViewDef = ViewDef::new("public", "view").definition("SELECT 1");
751 let view = DEF.into_view();
752 assert_eq!(view.name(), "view");
753 assert_eq!(view.schema(), "public");
754 }
755
756 #[test]
757 fn test_view_with_option_def_builders() {
758 const OPTIONS: ViewWithOptionDef = ViewWithOptionDef::new()
760 .fillfactor(80)
761 .parallel_workers(4)
762 .autovacuum_enabled(true)
763 .vacuum_index_cleanup("auto")
764 .vacuum_truncate(false)
765 .autovacuum_vacuum_threshold(100)
766 .autovacuum_vacuum_scale_factor(20)
767 .autovacuum_vacuum_cost_delay(10)
768 .autovacuum_vacuum_cost_limit(200)
769 .autovacuum_freeze_min_age(50_000_000)
770 .autovacuum_freeze_max_age(200_000_000)
771 .autovacuum_freeze_table_age(150_000_000)
772 .autovacuum_multixact_freeze_min_age(5_000_000)
773 .autovacuum_multixact_freeze_max_age(400_000_000)
774 .autovacuum_multixact_freeze_table_age(150_000_000)
775 .log_autovacuum_min_duration(1000)
776 .user_catalog_table(false)
777 .toast_tuple_target(128);
778
779 assert_eq!(OPTIONS.fillfactor, Some(80));
780 assert_eq!(OPTIONS.parallel_workers, Some(4));
781 assert_eq!(OPTIONS.autovacuum_enabled, Some(true));
782 assert_eq!(OPTIONS.vacuum_index_cleanup, Some("auto"));
783 assert_eq!(OPTIONS.vacuum_truncate, Some(false));
784 assert_eq!(OPTIONS.autovacuum_vacuum_threshold, Some(100));
785 assert_eq!(OPTIONS.autovacuum_vacuum_scale_factor, Some(20));
786 assert_eq!(OPTIONS.autovacuum_vacuum_cost_delay, Some(10));
787 assert_eq!(OPTIONS.autovacuum_vacuum_cost_limit, Some(200));
788 assert_eq!(OPTIONS.autovacuum_freeze_min_age, Some(50_000_000));
789 assert_eq!(OPTIONS.autovacuum_freeze_max_age, Some(200_000_000));
790 assert_eq!(OPTIONS.autovacuum_freeze_table_age, Some(150_000_000));
791 assert_eq!(OPTIONS.autovacuum_multixact_freeze_min_age, Some(5_000_000));
792 assert_eq!(
793 OPTIONS.autovacuum_multixact_freeze_max_age,
794 Some(400_000_000)
795 );
796 assert_eq!(
797 OPTIONS.autovacuum_multixact_freeze_table_age,
798 Some(150_000_000)
799 );
800 assert_eq!(OPTIONS.log_autovacuum_min_duration, Some(1000));
801 assert_eq!(OPTIONS.user_catalog_table, Some(false));
802 assert_eq!(OPTIONS.toast_tuple_target, Some(128));
803 }
804
805 #[test]
806 fn test_view_with_option_def_to_runtime() {
807 const OPTIONS: ViewWithOptionDef = ViewWithOptionDef::new()
808 .fillfactor(90)
809 .security_barrier()
810 .security_invoker()
811 .check_option("cascaded");
812
813 let runtime = OPTIONS.into_view_with_option();
814 assert_eq!(runtime.fillfactor, Some(90));
815 assert_eq!(runtime.security_barrier, Some(true));
816 assert_eq!(runtime.security_invoker, Some(true));
817 assert_eq!(runtime.check_option.as_deref(), Some("cascaded"));
818 }
819
820 #[test]
821 fn test_materialized_view_with_all_options() {
822 const MAT_VIEW: ViewDef = ViewDef::new("analytics", "monthly_sales")
823 .materialized()
824 .with_no_data()
825 .using("btree")
826 .tablespace("fast_ssd")
827 .with_options(ViewWithOptionDef::new().fillfactor(90).parallel_workers(2))
828 .definition("SELECT * FROM sales WHERE date > now() - interval '30 days'");
829
830 const {
831 assert!(MAT_VIEW.materialized);
832 }
833 const {
834 assert!(MAT_VIEW.with_no_data);
835 }
836 assert_eq!(MAT_VIEW.using, Some("btree"));
837 assert_eq!(MAT_VIEW.tablespace, Some("fast_ssd"));
838 assert!(MAT_VIEW.with.is_some());
839
840 let options = MAT_VIEW.with.unwrap();
841 assert_eq!(options.fillfactor, Some(90));
842 assert_eq!(options.parallel_workers, Some(2));
843 }
844}