Skip to main content

drizzle_types/postgres/ddl/
view.rs

1//! `PostgreSQL` View DDL types
2//!
3//! This module provides two complementary types:
4//! - [`ViewDef`] - A const-friendly definition type for compile-time schema definitions
5//! - [`View`] - A runtime type for serde serialization/deserialization
6
7use crate::alloc_prelude::*;
8
9#[cfg(feature = "serde")]
10use crate::serde_helpers::{cow_from_string, cow_option_from_string};
11
12// =============================================================================
13// ViewWithOption Types
14// =============================================================================
15
16/// Const-friendly view WITH options definition
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub struct ViewWithOptionDef {
19    /// CHECK OPTION ('local' | 'cascaded')
20    pub check_option: Option<&'static str>,
21    /// Security barrier flag
22    pub security_barrier: bool,
23    /// Security invoker flag
24    pub security_invoker: bool,
25    /// Fillfactor (for materialized views)
26    pub fillfactor: Option<i32>,
27    /// Toast tuple target (for materialized views)
28    pub toast_tuple_target: Option<i32>,
29    /// Parallel workers (for materialized views)
30    pub parallel_workers: Option<i32>,
31    /// Autovacuum enabled (for materialized views)
32    pub autovacuum_enabled: Option<bool>,
33    /// Vacuum index cleanup (for materialized views): 'auto' | 'on' | 'off'
34    pub vacuum_index_cleanup: Option<&'static str>,
35    /// Vacuum truncate (for materialized views)
36    pub vacuum_truncate: Option<bool>,
37    /// Autovacuum vacuum threshold (for materialized views)
38    pub autovacuum_vacuum_threshold: Option<i32>,
39    /// Autovacuum vacuum scale factor (for materialized views)
40    pub autovacuum_vacuum_scale_factor: Option<i32>,
41    /// Autovacuum vacuum cost delay (for materialized views)
42    pub autovacuum_vacuum_cost_delay: Option<i32>,
43    /// Autovacuum vacuum cost limit (for materialized views)
44    pub autovacuum_vacuum_cost_limit: Option<i32>,
45    /// Autovacuum freeze min age (for materialized views)
46    pub autovacuum_freeze_min_age: Option<i64>,
47    /// Autovacuum freeze max age (for materialized views)
48    pub autovacuum_freeze_max_age: Option<i64>,
49    /// Autovacuum freeze table age (for materialized views)
50    pub autovacuum_freeze_table_age: Option<i64>,
51    /// Autovacuum multixact freeze min age (for materialized views)
52    pub autovacuum_multixact_freeze_min_age: Option<i64>,
53    /// Autovacuum multixact freeze max age (for materialized views)
54    pub autovacuum_multixact_freeze_max_age: Option<i64>,
55    /// Autovacuum multixact freeze table age (for materialized views)
56    pub autovacuum_multixact_freeze_table_age: Option<i64>,
57    /// Log autovacuum min duration (for materialized views)
58    pub log_autovacuum_min_duration: Option<i32>,
59    /// User catalog table (for materialized views)
60    pub user_catalog_table: Option<bool>,
61}
62
63impl ViewWithOptionDef {
64    /// Create a new view WITH options definition
65    #[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    /// Set CHECK OPTION
93    #[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    /// Set security barrier
102    #[must_use]
103    pub const fn security_barrier(self) -> Self {
104        Self {
105            security_barrier: true,
106            ..self
107        }
108    }
109
110    /// Set security invoker
111    #[must_use]
112    pub const fn security_invoker(self) -> Self {
113        Self {
114            security_invoker: true,
115            ..self
116        }
117    }
118
119    /// Set fillfactor (for materialized views)
120    #[must_use]
121    pub const fn fillfactor(self, value: i32) -> Self {
122        Self {
123            fillfactor: Some(value),
124            ..self
125        }
126    }
127
128    /// Set toast tuple target (for materialized views)
129    #[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    /// Set parallel workers (for materialized views)
138    #[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    /// Set autovacuum enabled (for materialized views)
147    #[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    /// Set vacuum index cleanup (for materialized views): "auto", "on", or "off"
156    #[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    /// Set vacuum truncate (for materialized views)
165    #[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    /// Set autovacuum vacuum threshold (for materialized views)
174    #[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    /// Set autovacuum vacuum scale factor (for materialized views)
183    #[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    /// Set autovacuum vacuum cost delay (for materialized views)
192    #[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    /// Set autovacuum vacuum cost limit (for materialized views)
201    #[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    /// Set autovacuum freeze min age (for materialized views)
210    #[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    /// Set autovacuum freeze max age (for materialized views)
219    #[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    /// Set autovacuum freeze table age (for materialized views)
228    #[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    /// Set autovacuum multixact freeze min age (for materialized views)
237    #[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    /// Set autovacuum multixact freeze max age (for materialized views)
246    #[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    /// Set autovacuum multixact freeze table age (for materialized views)
255    #[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    /// Set log autovacuum min duration (for materialized views)
264    #[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    /// Set user catalog table (for materialized views)
273    #[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    /// Convert to runtime type
282    #[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/// Runtime view WITH options entity
331#[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    /// CHECK OPTION ('local' | 'cascaded')
336    #[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    /// Security barrier flag
346    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
347    pub security_barrier: Option<bool>,
348
349    /// Security invoker flag
350    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
351    pub security_invoker: Option<bool>,
352
353    /// Fillfactor (for materialized views)
354    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
355    pub fillfactor: Option<i32>,
356
357    /// Toast tuple target (for materialized views)
358    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
359    pub toast_tuple_target: Option<i32>,
360
361    /// Parallel workers (for materialized views)
362    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
363    pub parallel_workers: Option<i32>,
364
365    /// Autovacuum enabled (for materialized views)
366    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
367    pub autovacuum_enabled: Option<bool>,
368
369    /// Vacuum index cleanup (for materialized views): 'auto' | 'on' | 'off'
370    #[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    /// Vacuum truncate (for materialized views)
380    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
381    pub vacuum_truncate: Option<bool>,
382
383    /// Autovacuum vacuum threshold (for materialized views)
384    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
385    pub autovacuum_vacuum_threshold: Option<i32>,
386
387    /// Autovacuum vacuum scale factor (for materialized views)
388    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
389    pub autovacuum_vacuum_scale_factor: Option<i32>,
390
391    /// Autovacuum vacuum cost delay (for materialized views)
392    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
393    pub autovacuum_vacuum_cost_delay: Option<i32>,
394
395    /// Autovacuum vacuum cost limit (for materialized views)
396    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
397    pub autovacuum_vacuum_cost_limit: Option<i32>,
398
399    /// Autovacuum freeze min age (for materialized views)
400    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
401    pub autovacuum_freeze_min_age: Option<i64>,
402
403    /// Autovacuum freeze max age (for materialized views)
404    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
405    pub autovacuum_freeze_max_age: Option<i64>,
406
407    /// Autovacuum freeze table age (for materialized views)
408    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
409    pub autovacuum_freeze_table_age: Option<i64>,
410
411    /// Autovacuum multixact freeze min age (for materialized views)
412    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
413    pub autovacuum_multixact_freeze_min_age: Option<i64>,
414
415    /// Autovacuum multixact freeze max age (for materialized views)
416    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
417    pub autovacuum_multixact_freeze_max_age: Option<i64>,
418
419    /// Autovacuum multixact freeze table age (for materialized views)
420    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
421    pub autovacuum_multixact_freeze_table_age: Option<i64>,
422
423    /// Log autovacuum min duration (for materialized views)
424    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
425    pub log_autovacuum_min_duration: Option<i32>,
426
427    /// User catalog table (for materialized views)
428    #[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// =============================================================================
445// Const-friendly Definition Type
446// =============================================================================
447
448/// Const-friendly view definition
449#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
450pub struct ViewDef {
451    /// Schema name
452    pub schema: &'static str,
453    /// View name
454    pub name: &'static str,
455    /// View definition (AS SELECT ...)
456    pub definition: Option<&'static str>,
457    /// Is this a materialized view?
458    pub materialized: bool,
459    /// WITH options
460    pub with: Option<ViewWithOptionDef>,
461    /// Whether this is an existing view (not managed by drizzle)
462    pub is_existing: bool,
463    /// WITH NO DATA (for materialized views)
464    pub with_no_data: bool,
465    /// USING clause (for materialized views)
466    pub using: Option<&'static str>,
467    /// Tablespace (for materialized views)
468    pub tablespace: Option<&'static str>,
469}
470
471impl ViewDef {
472    /// Create a new view definition
473    #[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    /// Set the view definition
489    #[must_use]
490    pub const fn definition(self, sql: &'static str) -> Self {
491        Self {
492            definition: Some(sql),
493            ..self
494        }
495    }
496
497    /// Mark as materialized view
498    #[must_use]
499    pub const fn materialized(self) -> Self {
500        Self {
501            materialized: true,
502            ..self
503        }
504    }
505
506    /// Set WITH options
507    #[must_use]
508    pub const fn with_options(self, options: ViewWithOptionDef) -> Self {
509        Self {
510            with: Some(options),
511            ..self
512        }
513    }
514
515    /// Mark as existing (not managed by drizzle)
516    #[must_use]
517    pub const fn existing(self) -> Self {
518        Self {
519            is_existing: true,
520            ..self
521        }
522    }
523
524    /// Set WITH NO DATA
525    #[must_use]
526    pub const fn with_no_data(self) -> Self {
527        Self {
528            with_no_data: true,
529            ..self
530        }
531    }
532
533    /// Set USING clause
534    #[must_use]
535    pub const fn using(self, clause: &'static str) -> Self {
536        Self {
537            using: Some(clause),
538            ..self
539        }
540    }
541
542    /// Set tablespace
543    #[must_use]
544    pub const fn tablespace(self, space: &'static str) -> Self {
545        Self {
546            tablespace: Some(space),
547            ..self
548        }
549    }
550
551    /// Convert to runtime [`View`] type
552    ///
553    /// Note: This method cannot be const because it needs to convert nested Option types
554    /// (with options) which require runtime method calls.
555    #[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// =============================================================================
578// Runtime Type for Serde
579// =============================================================================
580
581/// Runtime view entity
582#[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    /// Schema name
587    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
588    pub schema: Cow<'static, str>,
589
590    /// View name
591    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
592    pub name: Cow<'static, str>,
593
594    /// View definition (AS SELECT ...)
595    #[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    /// Is this a materialized view?
606    #[cfg_attr(feature = "serde", serde(default))]
607    pub materialized: bool,
608
609    /// WITH options
610    #[cfg_attr(
611        feature = "serde",
612        serde(skip_serializing_if = "Option::is_none", rename = "with")
613    )]
614    pub with: Option<ViewWithOption>,
615
616    /// Whether this is an existing view (not managed by drizzle)
617    #[cfg_attr(feature = "serde", serde(default))]
618    pub is_existing: bool,
619
620    /// WITH NO DATA (for materialized views)
621    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
622    pub with_no_data: Option<bool>,
623
624    /// USING clause (for materialized views)
625    #[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    /// Tablespace (for materialized views)
635    #[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    /// Create a new view
647    #[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    /// Get the schema name
663    #[inline]
664    #[must_use]
665    pub fn schema(&self) -> &str {
666        &self.schema
667    }
668
669    /// Get the view name
670    #[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        // Test const builder methods for materialized view options
724        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}