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