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 skip_serializing_if = "Option::is_none",
340 deserialize_with = "cow_option_from_string"
341 )
342 )]
343 pub check_option: Option<Cow<'static, str>>,
344
345 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
347 pub security_barrier: Option<bool>,
348
349 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
351 pub security_invoker: Option<bool>,
352
353 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
355 pub fillfactor: Option<i32>,
356
357 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
359 pub toast_tuple_target: Option<i32>,
360
361 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
363 pub parallel_workers: Option<i32>,
364
365 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
367 pub autovacuum_enabled: Option<bool>,
368
369 #[cfg_attr(
371 feature = "serde",
372 serde(
373 skip_serializing_if = "Option::is_none",
374 deserialize_with = "cow_option_from_string"
375 )
376 )]
377 pub vacuum_index_cleanup: Option<Cow<'static, str>>,
378
379 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
381 pub vacuum_truncate: Option<bool>,
382
383 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
385 pub autovacuum_vacuum_threshold: Option<i32>,
386
387 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
389 pub autovacuum_vacuum_scale_factor: Option<i32>,
390
391 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
393 pub autovacuum_vacuum_cost_delay: Option<i32>,
394
395 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
397 pub autovacuum_vacuum_cost_limit: Option<i32>,
398
399 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
401 pub autovacuum_freeze_min_age: Option<i64>,
402
403 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
405 pub autovacuum_freeze_max_age: Option<i64>,
406
407 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
409 pub autovacuum_freeze_table_age: Option<i64>,
410
411 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
413 pub autovacuum_multixact_freeze_min_age: Option<i64>,
414
415 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
417 pub autovacuum_multixact_freeze_max_age: Option<i64>,
418
419 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
421 pub autovacuum_multixact_freeze_table_age: Option<i64>,
422
423 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
425 pub log_autovacuum_min_duration: Option<i32>,
426
427 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
429 pub user_catalog_table: Option<bool>,
430}
431
432impl Default for ViewWithOption {
433 fn default() -> Self {
434 ViewWithOptionDef::new().into_view_with_option()
435 }
436}
437
438impl From<ViewWithOptionDef> for ViewWithOption {
439 fn from(def: ViewWithOptionDef) -> Self {
440 def.into_view_with_option()
441 }
442}
443
444#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
450pub struct ViewDef {
451 pub schema: &'static str,
453 pub name: &'static str,
455 pub definition: Option<&'static str>,
457 pub materialized: bool,
459 pub with: Option<ViewWithOptionDef>,
461 pub is_existing: bool,
463 pub with_no_data: bool,
465 pub using: Option<&'static str>,
467 pub tablespace: Option<&'static str>,
469}
470
471impl ViewDef {
472 #[must_use]
474 pub const fn new(schema: &'static str, name: &'static str) -> Self {
475 Self {
476 schema,
477 name,
478 definition: None,
479 materialized: false,
480 with: None,
481 is_existing: false,
482 with_no_data: false,
483 using: None,
484 tablespace: None,
485 }
486 }
487
488 #[must_use]
490 pub const fn definition(self, sql: &'static str) -> Self {
491 Self {
492 definition: Some(sql),
493 ..self
494 }
495 }
496
497 #[must_use]
499 pub const fn materialized(self) -> Self {
500 Self {
501 materialized: true,
502 ..self
503 }
504 }
505
506 #[must_use]
508 pub const fn with_options(self, options: ViewWithOptionDef) -> Self {
509 Self {
510 with: Some(options),
511 ..self
512 }
513 }
514
515 #[must_use]
517 pub const fn existing(self) -> Self {
518 Self {
519 is_existing: true,
520 ..self
521 }
522 }
523
524 #[must_use]
526 pub const fn with_no_data(self) -> Self {
527 Self {
528 with_no_data: true,
529 ..self
530 }
531 }
532
533 #[must_use]
535 pub const fn using(self, clause: &'static str) -> Self {
536 Self {
537 using: Some(clause),
538 ..self
539 }
540 }
541
542 #[must_use]
544 pub const fn tablespace(self, space: &'static str) -> Self {
545 Self {
546 tablespace: Some(space),
547 ..self
548 }
549 }
550
551 #[must_use]
556 pub fn into_view(self) -> View {
557 View {
558 schema: Cow::Borrowed(self.schema),
559 name: Cow::Borrowed(self.name),
560 definition: self.definition.map(Cow::Borrowed),
561 materialized: self.materialized,
562 with: self.with.map(ViewWithOptionDef::into_view_with_option),
563 is_existing: self.is_existing,
564 with_no_data: if self.with_no_data { Some(true) } else { None },
565 using: self.using.map(Cow::Borrowed),
566 tablespace: self.tablespace.map(Cow::Borrowed),
567 }
568 }
569}
570
571impl Default for ViewDef {
572 fn default() -> Self {
573 Self::new("public", "")
574 }
575}
576
577#[derive(Clone, Debug, PartialEq, Eq)]
583#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
584#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
585pub struct View {
586 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
588 pub schema: Cow<'static, str>,
589
590 #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
592 pub name: Cow<'static, str>,
593
594 #[cfg_attr(
596 feature = "serde",
597 serde(
598 default,
599 skip_serializing_if = "Option::is_none",
600 deserialize_with = "cow_option_from_string"
601 )
602 )]
603 pub definition: Option<Cow<'static, str>>,
604
605 #[cfg_attr(feature = "serde", serde(default))]
607 pub materialized: bool,
608
609 #[cfg_attr(
611 feature = "serde",
612 serde(skip_serializing_if = "Option::is_none", rename = "with")
613 )]
614 pub with: Option<ViewWithOption>,
615
616 #[cfg_attr(feature = "serde", serde(default))]
618 pub is_existing: bool,
619
620 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
622 pub with_no_data: Option<bool>,
623
624 #[cfg_attr(
626 feature = "serde",
627 serde(
628 skip_serializing_if = "Option::is_none",
629 deserialize_with = "cow_option_from_string"
630 )
631 )]
632 pub using: Option<Cow<'static, str>>,
633
634 #[cfg_attr(
636 feature = "serde",
637 serde(
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 #[test]
703 fn test_materialized_view_def() {
704 const MAT_VIEW: ViewDef = ViewDef::new("public", "user_stats")
705 .materialized()
706 .with_no_data();
707
708 const {
709 assert!(MAT_VIEW.materialized);
710 }
711 }
712
713 #[test]
714 fn test_view_def_to_view() {
715 const DEF: ViewDef = ViewDef::new("public", "view").definition("SELECT 1");
716 let view = DEF.into_view();
717 assert_eq!(view.name(), "view");
718 assert_eq!(view.schema(), "public");
719 }
720
721 #[test]
722 fn test_view_with_option_def_builders() {
723 const OPTIONS: ViewWithOptionDef = ViewWithOptionDef::new()
725 .fillfactor(80)
726 .parallel_workers(4)
727 .autovacuum_enabled(true)
728 .vacuum_index_cleanup("auto")
729 .vacuum_truncate(false)
730 .autovacuum_vacuum_threshold(100)
731 .autovacuum_vacuum_scale_factor(20)
732 .autovacuum_vacuum_cost_delay(10)
733 .autovacuum_vacuum_cost_limit(200)
734 .autovacuum_freeze_min_age(50_000_000)
735 .autovacuum_freeze_max_age(200_000_000)
736 .autovacuum_freeze_table_age(150_000_000)
737 .autovacuum_multixact_freeze_min_age(5_000_000)
738 .autovacuum_multixact_freeze_max_age(400_000_000)
739 .autovacuum_multixact_freeze_table_age(150_000_000)
740 .log_autovacuum_min_duration(1000)
741 .user_catalog_table(false)
742 .toast_tuple_target(128);
743
744 assert_eq!(OPTIONS.fillfactor, Some(80));
745 assert_eq!(OPTIONS.parallel_workers, Some(4));
746 assert_eq!(OPTIONS.autovacuum_enabled, Some(true));
747 assert_eq!(OPTIONS.vacuum_index_cleanup, Some("auto"));
748 assert_eq!(OPTIONS.vacuum_truncate, Some(false));
749 assert_eq!(OPTIONS.autovacuum_vacuum_threshold, Some(100));
750 assert_eq!(OPTIONS.autovacuum_vacuum_scale_factor, Some(20));
751 assert_eq!(OPTIONS.autovacuum_vacuum_cost_delay, Some(10));
752 assert_eq!(OPTIONS.autovacuum_vacuum_cost_limit, Some(200));
753 assert_eq!(OPTIONS.autovacuum_freeze_min_age, Some(50_000_000));
754 assert_eq!(OPTIONS.autovacuum_freeze_max_age, Some(200_000_000));
755 assert_eq!(OPTIONS.autovacuum_freeze_table_age, Some(150_000_000));
756 assert_eq!(OPTIONS.autovacuum_multixact_freeze_min_age, Some(5_000_000));
757 assert_eq!(
758 OPTIONS.autovacuum_multixact_freeze_max_age,
759 Some(400_000_000)
760 );
761 assert_eq!(
762 OPTIONS.autovacuum_multixact_freeze_table_age,
763 Some(150_000_000)
764 );
765 assert_eq!(OPTIONS.log_autovacuum_min_duration, Some(1000));
766 assert_eq!(OPTIONS.user_catalog_table, Some(false));
767 assert_eq!(OPTIONS.toast_tuple_target, Some(128));
768 }
769
770 #[test]
771 fn test_view_with_option_def_to_runtime() {
772 const OPTIONS: ViewWithOptionDef = ViewWithOptionDef::new()
773 .fillfactor(90)
774 .security_barrier()
775 .security_invoker()
776 .check_option("cascaded");
777
778 let runtime = OPTIONS.into_view_with_option();
779 assert_eq!(runtime.fillfactor, Some(90));
780 assert_eq!(runtime.security_barrier, Some(true));
781 assert_eq!(runtime.security_invoker, Some(true));
782 assert_eq!(runtime.check_option.as_deref(), Some("cascaded"));
783 }
784
785 #[test]
786 fn test_materialized_view_with_all_options() {
787 const MAT_VIEW: ViewDef = ViewDef::new("analytics", "monthly_sales")
788 .materialized()
789 .with_no_data()
790 .using("btree")
791 .tablespace("fast_ssd")
792 .with_options(ViewWithOptionDef::new().fillfactor(90).parallel_workers(2))
793 .definition("SELECT * FROM sales WHERE date > now() - interval '30 days'");
794
795 const {
796 assert!(MAT_VIEW.materialized);
797 }
798 const {
799 assert!(MAT_VIEW.with_no_data);
800 }
801 assert_eq!(MAT_VIEW.using, Some("btree"));
802 assert_eq!(MAT_VIEW.tablespace, Some("fast_ssd"));
803 assert!(MAT_VIEW.with.is_some());
804
805 let options = MAT_VIEW.with.unwrap();
806 assert_eq!(options.fillfactor, Some(90));
807 assert_eq!(options.parallel_workers, Some(2));
808 }
809}