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
7#[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// =============================================================================
17// ViewWithOption Types
18// =============================================================================
19
20/// Const-friendly view WITH options definition
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
22pub struct ViewWithOptionDef {
23    /// CHECK OPTION ('local' | 'cascaded')
24    pub check_option: Option<&'static str>,
25    /// Security barrier flag
26    pub security_barrier: bool,
27    /// Security invoker flag
28    pub security_invoker: bool,
29    /// Fillfactor (for materialized views)
30    pub fillfactor: Option<i32>,
31    /// Toast tuple target (for materialized views)
32    pub toast_tuple_target: Option<i32>,
33    /// Parallel workers (for materialized views)
34    pub parallel_workers: Option<i32>,
35    /// Autovacuum enabled (for materialized views)
36    pub autovacuum_enabled: Option<bool>,
37    /// Vacuum index cleanup (for materialized views): 'auto' | 'on' | 'off'
38    pub vacuum_index_cleanup: Option<&'static str>,
39    /// Vacuum truncate (for materialized views)
40    pub vacuum_truncate: Option<bool>,
41    /// Autovacuum vacuum threshold (for materialized views)
42    pub autovacuum_vacuum_threshold: Option<i32>,
43    /// Autovacuum vacuum scale factor (for materialized views)
44    pub autovacuum_vacuum_scale_factor: Option<i32>,
45    /// Autovacuum vacuum cost delay (for materialized views)
46    pub autovacuum_vacuum_cost_delay: Option<i32>,
47    /// Autovacuum vacuum cost limit (for materialized views)
48    pub autovacuum_vacuum_cost_limit: Option<i32>,
49    /// Autovacuum freeze min age (for materialized views)
50    pub autovacuum_freeze_min_age: Option<i64>,
51    /// Autovacuum freeze max age (for materialized views)
52    pub autovacuum_freeze_max_age: Option<i64>,
53    /// Autovacuum freeze table age (for materialized views)
54    pub autovacuum_freeze_table_age: Option<i64>,
55    /// Autovacuum multixact freeze min age (for materialized views)
56    pub autovacuum_multixact_freeze_min_age: Option<i64>,
57    /// Autovacuum multixact freeze max age (for materialized views)
58    pub autovacuum_multixact_freeze_max_age: Option<i64>,
59    /// Autovacuum multixact freeze table age (for materialized views)
60    pub autovacuum_multixact_freeze_table_age: Option<i64>,
61    /// Log autovacuum min duration (for materialized views)
62    pub log_autovacuum_min_duration: Option<i32>,
63    /// User catalog table (for materialized views)
64    pub user_catalog_table: Option<bool>,
65}
66
67impl ViewWithOptionDef {
68    /// Create a new view WITH options definition
69    #[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    /// Set CHECK OPTION
97    #[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    /// Set security barrier
106    #[must_use]
107    pub const fn security_barrier(self) -> Self {
108        Self {
109            security_barrier: true,
110            ..self
111        }
112    }
113
114    /// Set security invoker
115    #[must_use]
116    pub const fn security_invoker(self) -> Self {
117        Self {
118            security_invoker: true,
119            ..self
120        }
121    }
122
123    /// Set fillfactor (for materialized views)
124    #[must_use]
125    pub const fn fillfactor(self, value: i32) -> Self {
126        Self {
127            fillfactor: Some(value),
128            ..self
129        }
130    }
131
132    /// Set toast tuple target (for materialized views)
133    #[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    /// Set parallel workers (for materialized views)
142    #[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    /// Set autovacuum enabled (for materialized views)
151    #[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    /// Set vacuum index cleanup (for materialized views): "auto", "on", or "off"
160    #[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    /// Set vacuum truncate (for materialized views)
169    #[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    /// Set autovacuum vacuum threshold (for materialized views)
178    #[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    /// Set autovacuum vacuum scale factor (for materialized views)
187    #[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    /// Set autovacuum vacuum cost delay (for materialized views)
196    #[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    /// Set autovacuum vacuum cost limit (for materialized views)
205    #[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    /// Set autovacuum freeze min age (for materialized views)
214    #[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    /// Set autovacuum freeze max age (for materialized views)
223    #[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    /// Set autovacuum freeze table age (for materialized views)
232    #[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    /// Set autovacuum multixact freeze min age (for materialized views)
241    #[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    /// Set autovacuum multixact freeze max age (for materialized views)
250    #[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    /// Set autovacuum multixact freeze table age (for materialized views)
259    #[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    /// Set log autovacuum min duration (for materialized views)
268    #[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    /// Set user catalog table (for materialized views)
277    #[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    /// Convert to runtime type
286    #[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/// Runtime view WITH options entity
335#[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    /// CHECK OPTION ('local' | 'cascaded')
340    #[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    /// Security barrier flag
350    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
351    pub security_barrier: Option<bool>,
352
353    /// Security invoker flag
354    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
355    pub security_invoker: Option<bool>,
356
357    /// Fillfactor (for materialized views)
358    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
359    pub fillfactor: Option<i32>,
360
361    /// Toast tuple target (for materialized views)
362    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
363    pub toast_tuple_target: Option<i32>,
364
365    /// Parallel workers (for materialized views)
366    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
367    pub parallel_workers: Option<i32>,
368
369    /// Autovacuum enabled (for materialized views)
370    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
371    pub autovacuum_enabled: Option<bool>,
372
373    /// Vacuum index cleanup (for materialized views): 'auto' | 'on' | 'off'
374    #[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    /// Vacuum truncate (for materialized views)
384    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
385    pub vacuum_truncate: Option<bool>,
386
387    /// Autovacuum vacuum threshold (for materialized views)
388    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
389    pub autovacuum_vacuum_threshold: Option<i32>,
390
391    /// Autovacuum vacuum scale factor (for materialized views)
392    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
393    pub autovacuum_vacuum_scale_factor: Option<i32>,
394
395    /// Autovacuum vacuum cost delay (for materialized views)
396    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
397    pub autovacuum_vacuum_cost_delay: Option<i32>,
398
399    /// Autovacuum vacuum cost limit (for materialized views)
400    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
401    pub autovacuum_vacuum_cost_limit: Option<i32>,
402
403    /// Autovacuum freeze min age (for materialized views)
404    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
405    pub autovacuum_freeze_min_age: Option<i64>,
406
407    /// Autovacuum freeze max age (for materialized views)
408    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
409    pub autovacuum_freeze_max_age: Option<i64>,
410
411    /// Autovacuum freeze table age (for materialized views)
412    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
413    pub autovacuum_freeze_table_age: Option<i64>,
414
415    /// Autovacuum multixact freeze min age (for materialized views)
416    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
417    pub autovacuum_multixact_freeze_min_age: Option<i64>,
418
419    /// Autovacuum multixact freeze max age (for materialized views)
420    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
421    pub autovacuum_multixact_freeze_max_age: Option<i64>,
422
423    /// Autovacuum multixact freeze table age (for materialized views)
424    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
425    pub autovacuum_multixact_freeze_table_age: Option<i64>,
426
427    /// Log autovacuum min duration (for materialized views)
428    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
429    pub log_autovacuum_min_duration: Option<i32>,
430
431    /// User catalog table (for materialized views)
432    #[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// =============================================================================
449// Const-friendly Definition Type
450// =============================================================================
451
452/// Const-friendly view definition
453#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
454pub struct ViewDef {
455    /// Schema name
456    pub schema: &'static str,
457    /// View name
458    pub name: &'static str,
459    /// View definition (AS SELECT ...)
460    pub definition: Option<&'static str>,
461    /// Is this a materialized view?
462    pub materialized: bool,
463    /// WITH options
464    pub with: Option<ViewWithOptionDef>,
465    /// Whether this is an existing view (not managed by drizzle)
466    pub is_existing: bool,
467    /// WITH NO DATA (for materialized views)
468    pub with_no_data: bool,
469    /// USING clause (for materialized views)
470    pub using: Option<&'static str>,
471    /// Tablespace (for materialized views)
472    pub tablespace: Option<&'static str>,
473}
474
475impl ViewDef {
476    /// Create a new view definition
477    #[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    /// Set the view definition
493    #[must_use]
494    pub const fn definition(self, sql: &'static str) -> Self {
495        Self {
496            definition: Some(sql),
497            ..self
498        }
499    }
500
501    /// Mark as materialized view
502    #[must_use]
503    pub const fn materialized(self) -> Self {
504        Self {
505            materialized: true,
506            ..self
507        }
508    }
509
510    /// Set WITH options
511    #[must_use]
512    pub const fn with_options(self, options: ViewWithOptionDef) -> Self {
513        Self {
514            with: Some(options),
515            ..self
516        }
517    }
518
519    /// Mark as existing (not managed by drizzle)
520    #[must_use]
521    pub const fn existing(self) -> Self {
522        Self {
523            is_existing: true,
524            ..self
525        }
526    }
527
528    /// Set WITH NO DATA
529    #[must_use]
530    pub const fn with_no_data(self) -> Self {
531        Self {
532            with_no_data: true,
533            ..self
534        }
535    }
536
537    /// Set USING clause
538    #[must_use]
539    pub const fn using(self, clause: &'static str) -> Self {
540        Self {
541            using: Some(clause),
542            ..self
543        }
544    }
545
546    /// Set tablespace
547    #[must_use]
548    pub const fn tablespace(self, space: &'static str) -> Self {
549        Self {
550            tablespace: Some(space),
551            ..self
552        }
553    }
554
555    /// Convert to runtime [`View`] type
556    ///
557    /// Note: This method cannot be const because it needs to convert nested Option types
558    /// (with options) which require runtime method calls.
559    #[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// =============================================================================
591// Runtime Type for Serde
592// =============================================================================
593
594/// Runtime view entity
595#[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    /// Schema name
600    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
601    pub schema: Cow<'static, str>,
602
603    /// View name
604    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
605    pub name: Cow<'static, str>,
606
607    /// View definition (AS SELECT ...)
608    #[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    /// Is this a materialized view?
619    #[cfg_attr(feature = "serde", serde(default))]
620    pub materialized: bool,
621
622    /// WITH options
623    #[cfg_attr(
624        feature = "serde",
625        serde(skip_serializing_if = "Option::is_none", rename = "with")
626    )]
627    pub with: Option<ViewWithOption>,
628
629    /// Whether this is an existing view (not managed by drizzle)
630    #[cfg_attr(feature = "serde", serde(default))]
631    pub is_existing: bool,
632
633    /// WITH NO DATA (for materialized views)
634    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
635    pub with_no_data: Option<bool>,
636
637    /// USING clause (for materialized views)
638    #[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    /// Tablespace (for materialized views)
648    #[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    /// Create a new view
660    #[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    /// Get the schema name
676    #[inline]
677    #[must_use]
678    pub fn schema(&self) -> &str {
679        &self.schema
680    }
681
682    /// Get the view name
683    #[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        // Test const builder methods for materialized view options
735        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}