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            default,
340            skip_serializing_if = "Option::is_none",
341            deserialize_with = "cow_option_from_string"
342        )
343    )]
344    pub check_option: Option<Cow<'static, str>>,
345
346    /// Security barrier flag
347    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
348    pub security_barrier: Option<bool>,
349
350    /// Security invoker flag
351    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
352    pub security_invoker: Option<bool>,
353
354    /// Fillfactor (for materialized views)
355    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
356    pub fillfactor: Option<i32>,
357
358    /// Toast tuple target (for materialized views)
359    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
360    pub toast_tuple_target: Option<i32>,
361
362    /// Parallel workers (for materialized views)
363    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
364    pub parallel_workers: Option<i32>,
365
366    /// Autovacuum enabled (for materialized views)
367    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
368    pub autovacuum_enabled: Option<bool>,
369
370    /// Vacuum index cleanup (for materialized views): 'auto' | 'on' | 'off'
371    #[cfg_attr(
372        feature = "serde",
373        serde(
374            default,
375            skip_serializing_if = "Option::is_none",
376            deserialize_with = "cow_option_from_string"
377        )
378    )]
379    pub vacuum_index_cleanup: Option<Cow<'static, str>>,
380
381    /// Vacuum truncate (for materialized views)
382    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
383    pub vacuum_truncate: Option<bool>,
384
385    /// Autovacuum vacuum threshold (for materialized views)
386    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
387    pub autovacuum_vacuum_threshold: Option<i32>,
388
389    /// Autovacuum vacuum scale factor (for materialized views)
390    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
391    pub autovacuum_vacuum_scale_factor: Option<i32>,
392
393    /// Autovacuum vacuum cost delay (for materialized views)
394    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
395    pub autovacuum_vacuum_cost_delay: Option<i32>,
396
397    /// Autovacuum vacuum cost limit (for materialized views)
398    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
399    pub autovacuum_vacuum_cost_limit: Option<i32>,
400
401    /// Autovacuum freeze min age (for materialized views)
402    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
403    pub autovacuum_freeze_min_age: Option<i64>,
404
405    /// Autovacuum freeze max age (for materialized views)
406    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
407    pub autovacuum_freeze_max_age: Option<i64>,
408
409    /// Autovacuum freeze table age (for materialized views)
410    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
411    pub autovacuum_freeze_table_age: Option<i64>,
412
413    /// Autovacuum multixact freeze min age (for materialized views)
414    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
415    pub autovacuum_multixact_freeze_min_age: Option<i64>,
416
417    /// Autovacuum multixact freeze max age (for materialized views)
418    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
419    pub autovacuum_multixact_freeze_max_age: Option<i64>,
420
421    /// Autovacuum multixact freeze table age (for materialized views)
422    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
423    pub autovacuum_multixact_freeze_table_age: Option<i64>,
424
425    /// Log autovacuum min duration (for materialized views)
426    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
427    pub log_autovacuum_min_duration: Option<i32>,
428
429    /// User catalog table (for materialized views)
430    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
431    pub user_catalog_table: Option<bool>,
432}
433
434impl Default for ViewWithOption {
435    fn default() -> Self {
436        ViewWithOptionDef::new().into_view_with_option()
437    }
438}
439
440impl From<ViewWithOptionDef> for ViewWithOption {
441    fn from(def: ViewWithOptionDef) -> Self {
442        def.into_view_with_option()
443    }
444}
445
446// =============================================================================
447// Const-friendly Definition Type
448// =============================================================================
449
450/// Const-friendly view definition
451#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
452pub struct ViewDef {
453    /// Schema name
454    pub schema: &'static str,
455    /// View name
456    pub name: &'static str,
457    /// View definition (AS SELECT ...)
458    pub definition: Option<&'static str>,
459    /// Is this a materialized view?
460    pub materialized: bool,
461    /// WITH options
462    pub with: Option<ViewWithOptionDef>,
463    /// Whether this is an existing view (not managed by drizzle)
464    pub is_existing: bool,
465    /// WITH NO DATA (for materialized views)
466    pub with_no_data: bool,
467    /// USING clause (for materialized views)
468    pub using: Option<&'static str>,
469    /// Tablespace (for materialized views)
470    pub tablespace: Option<&'static str>,
471}
472
473impl ViewDef {
474    /// Create a new view definition
475    #[must_use]
476    pub const fn new(schema: &'static str, name: &'static str) -> Self {
477        Self {
478            schema,
479            name,
480            definition: None,
481            materialized: false,
482            with: None,
483            is_existing: false,
484            with_no_data: false,
485            using: None,
486            tablespace: None,
487        }
488    }
489
490    /// Set the view definition
491    #[must_use]
492    pub const fn definition(self, sql: &'static str) -> Self {
493        Self {
494            definition: Some(sql),
495            ..self
496        }
497    }
498
499    /// Mark as materialized view
500    #[must_use]
501    pub const fn materialized(self) -> Self {
502        Self {
503            materialized: true,
504            ..self
505        }
506    }
507
508    /// Set WITH options
509    #[must_use]
510    pub const fn with_options(self, options: ViewWithOptionDef) -> Self {
511        Self {
512            with: Some(options),
513            ..self
514        }
515    }
516
517    /// Mark as existing (not managed by drizzle)
518    #[must_use]
519    pub const fn existing(self) -> Self {
520        Self {
521            is_existing: true,
522            ..self
523        }
524    }
525
526    /// Set WITH NO DATA
527    #[must_use]
528    pub const fn with_no_data(self) -> Self {
529        Self {
530            with_no_data: true,
531            ..self
532        }
533    }
534
535    /// Set USING clause
536    #[must_use]
537    pub const fn using(self, clause: &'static str) -> Self {
538        Self {
539            using: Some(clause),
540            ..self
541        }
542    }
543
544    /// Set tablespace
545    #[must_use]
546    pub const fn tablespace(self, space: &'static str) -> Self {
547        Self {
548            tablespace: Some(space),
549            ..self
550        }
551    }
552
553    /// Convert to runtime [`View`] type
554    ///
555    /// Note: This method cannot be const because it needs to convert nested Option types
556    /// (with options) which require runtime method calls.
557    #[must_use]
558    pub fn into_view(self) -> View {
559        View {
560            schema: Cow::Borrowed(self.schema),
561            name: Cow::Borrowed(self.name),
562            definition: self.definition.map(Cow::Borrowed),
563            materialized: self.materialized,
564            with: self.with.map(ViewWithOptionDef::into_view_with_option),
565            is_existing: self.is_existing,
566            with_no_data: if self.with_no_data { Some(true) } else { None },
567            using: self.using.map(Cow::Borrowed),
568            tablespace: self.tablespace.map(Cow::Borrowed),
569        }
570    }
571}
572
573impl Default for ViewDef {
574    fn default() -> Self {
575        Self::new("public", "")
576    }
577}
578
579// =============================================================================
580// Runtime Type for Serde
581// =============================================================================
582
583/// Runtime view entity
584#[derive(Clone, Debug, PartialEq, Eq)]
585#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
586#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
587pub struct View {
588    /// Schema name
589    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
590    pub schema: Cow<'static, str>,
591
592    /// View name
593    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
594    pub name: Cow<'static, str>,
595
596    /// View definition (AS SELECT ...)
597    #[cfg_attr(
598        feature = "serde",
599        serde(
600            default,
601            skip_serializing_if = "Option::is_none",
602            deserialize_with = "cow_option_from_string"
603        )
604    )]
605    pub definition: Option<Cow<'static, str>>,
606
607    /// Is this a materialized view?
608    #[cfg_attr(feature = "serde", serde(default))]
609    pub materialized: bool,
610
611    /// WITH options
612    #[cfg_attr(
613        feature = "serde",
614        serde(skip_serializing_if = "Option::is_none", rename = "with")
615    )]
616    pub with: Option<ViewWithOption>,
617
618    /// Whether this is an existing view (not managed by drizzle)
619    #[cfg_attr(feature = "serde", serde(default))]
620    pub is_existing: bool,
621
622    /// WITH NO DATA (for materialized views)
623    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
624    pub with_no_data: Option<bool>,
625
626    /// USING clause (for materialized views)
627    #[cfg_attr(
628        feature = "serde",
629        serde(
630            default,
631            skip_serializing_if = "Option::is_none",
632            deserialize_with = "cow_option_from_string"
633        )
634    )]
635    pub using: Option<Cow<'static, str>>,
636
637    /// Tablespace (for materialized views)
638    #[cfg_attr(
639        feature = "serde",
640        serde(
641            default,
642            skip_serializing_if = "Option::is_none",
643            deserialize_with = "cow_option_from_string"
644        )
645    )]
646    pub tablespace: Option<Cow<'static, str>>,
647}
648
649impl View {
650    /// Create a new view
651    #[must_use]
652    pub fn new(schema: impl Into<Cow<'static, str>>, name: impl Into<Cow<'static, str>>) -> Self {
653        Self {
654            schema: schema.into(),
655            name: name.into(),
656            definition: None,
657            materialized: false,
658            with: None,
659            is_existing: false,
660            with_no_data: None,
661            using: None,
662            tablespace: None,
663        }
664    }
665
666    /// Get the schema name
667    #[inline]
668    #[must_use]
669    pub fn schema(&self) -> &str {
670        &self.schema
671    }
672
673    /// Get the view name
674    #[inline]
675    #[must_use]
676    pub fn name(&self) -> &str {
677        &self.name
678    }
679}
680
681impl Default for View {
682    fn default() -> Self {
683        Self::new("public", "")
684    }
685}
686
687impl From<ViewDef> for View {
688    fn from(def: ViewDef) -> Self {
689        def.into_view()
690    }
691}
692
693#[cfg(test)]
694mod tests {
695    use super::*;
696
697    #[test]
698    fn test_const_view_def() {
699        const VIEW: ViewDef = ViewDef::new("public", "active_users")
700            .definition("SELECT * FROM users WHERE active = 1");
701
702        assert_eq!(VIEW.name, "active_users");
703        assert_eq!(VIEW.schema, "public");
704    }
705
706    /// Optional fields are skipped when serializing; deserialization must
707    /// treat the missing keys as `None` instead of erroring.
708    #[cfg(feature = "serde")]
709    #[test]
710    fn test_serde_roundtrip_with_all_none_optionals() {
711        let view = View {
712            schema: Cow::Borrowed("public"),
713            name: Cow::Borrowed("plain_view"),
714            definition: Some(Cow::Borrowed("SELECT 1")),
715            ..View::default()
716        };
717        assert!(view.using.is_none());
718        assert!(view.tablespace.is_none());
719
720        let json = serde_json::to_string(&view).expect("serialize");
721        let parsed: View = serde_json::from_str(&json).expect("deserialize");
722        assert_eq!(parsed, view);
723
724        // ViewWithOption's own optionals must round-trip the same way.
725        let with_options = View {
726            with: Some(ViewWithOption {
727                security_barrier: Some(true),
728                ..ViewWithOption::default()
729            }),
730            ..view
731        };
732        let json = serde_json::to_string(&with_options).expect("serialize");
733        let parsed: View = serde_json::from_str(&json).expect("deserialize");
734        assert_eq!(parsed, with_options);
735    }
736
737    #[test]
738    fn test_materialized_view_def() {
739        const MAT_VIEW: ViewDef = ViewDef::new("public", "user_stats")
740            .materialized()
741            .with_no_data();
742
743        const {
744            assert!(MAT_VIEW.materialized);
745        }
746    }
747
748    #[test]
749    fn test_view_def_to_view() {
750        const DEF: ViewDef = ViewDef::new("public", "view").definition("SELECT 1");
751        let view = DEF.into_view();
752        assert_eq!(view.name(), "view");
753        assert_eq!(view.schema(), "public");
754    }
755
756    #[test]
757    fn test_view_with_option_def_builders() {
758        // Test const builder methods for materialized view options
759        const OPTIONS: ViewWithOptionDef = ViewWithOptionDef::new()
760            .fillfactor(80)
761            .parallel_workers(4)
762            .autovacuum_enabled(true)
763            .vacuum_index_cleanup("auto")
764            .vacuum_truncate(false)
765            .autovacuum_vacuum_threshold(100)
766            .autovacuum_vacuum_scale_factor(20)
767            .autovacuum_vacuum_cost_delay(10)
768            .autovacuum_vacuum_cost_limit(200)
769            .autovacuum_freeze_min_age(50_000_000)
770            .autovacuum_freeze_max_age(200_000_000)
771            .autovacuum_freeze_table_age(150_000_000)
772            .autovacuum_multixact_freeze_min_age(5_000_000)
773            .autovacuum_multixact_freeze_max_age(400_000_000)
774            .autovacuum_multixact_freeze_table_age(150_000_000)
775            .log_autovacuum_min_duration(1000)
776            .user_catalog_table(false)
777            .toast_tuple_target(128);
778
779        assert_eq!(OPTIONS.fillfactor, Some(80));
780        assert_eq!(OPTIONS.parallel_workers, Some(4));
781        assert_eq!(OPTIONS.autovacuum_enabled, Some(true));
782        assert_eq!(OPTIONS.vacuum_index_cleanup, Some("auto"));
783        assert_eq!(OPTIONS.vacuum_truncate, Some(false));
784        assert_eq!(OPTIONS.autovacuum_vacuum_threshold, Some(100));
785        assert_eq!(OPTIONS.autovacuum_vacuum_scale_factor, Some(20));
786        assert_eq!(OPTIONS.autovacuum_vacuum_cost_delay, Some(10));
787        assert_eq!(OPTIONS.autovacuum_vacuum_cost_limit, Some(200));
788        assert_eq!(OPTIONS.autovacuum_freeze_min_age, Some(50_000_000));
789        assert_eq!(OPTIONS.autovacuum_freeze_max_age, Some(200_000_000));
790        assert_eq!(OPTIONS.autovacuum_freeze_table_age, Some(150_000_000));
791        assert_eq!(OPTIONS.autovacuum_multixact_freeze_min_age, Some(5_000_000));
792        assert_eq!(
793            OPTIONS.autovacuum_multixact_freeze_max_age,
794            Some(400_000_000)
795        );
796        assert_eq!(
797            OPTIONS.autovacuum_multixact_freeze_table_age,
798            Some(150_000_000)
799        );
800        assert_eq!(OPTIONS.log_autovacuum_min_duration, Some(1000));
801        assert_eq!(OPTIONS.user_catalog_table, Some(false));
802        assert_eq!(OPTIONS.toast_tuple_target, Some(128));
803    }
804
805    #[test]
806    fn test_view_with_option_def_to_runtime() {
807        const OPTIONS: ViewWithOptionDef = ViewWithOptionDef::new()
808            .fillfactor(90)
809            .security_barrier()
810            .security_invoker()
811            .check_option("cascaded");
812
813        let runtime = OPTIONS.into_view_with_option();
814        assert_eq!(runtime.fillfactor, Some(90));
815        assert_eq!(runtime.security_barrier, Some(true));
816        assert_eq!(runtime.security_invoker, Some(true));
817        assert_eq!(runtime.check_option.as_deref(), Some("cascaded"));
818    }
819
820    #[test]
821    fn test_materialized_view_with_all_options() {
822        const MAT_VIEW: ViewDef = ViewDef::new("analytics", "monthly_sales")
823            .materialized()
824            .with_no_data()
825            .using("btree")
826            .tablespace("fast_ssd")
827            .with_options(ViewWithOptionDef::new().fillfactor(90).parallel_workers(2))
828            .definition("SELECT * FROM sales WHERE date > now() - interval '30 days'");
829
830        const {
831            assert!(MAT_VIEW.materialized);
832        }
833        const {
834            assert!(MAT_VIEW.with_no_data);
835        }
836        assert_eq!(MAT_VIEW.using, Some("btree"));
837        assert_eq!(MAT_VIEW.tablespace, Some("fast_ssd"));
838        assert!(MAT_VIEW.with.is_some());
839
840        let options = MAT_VIEW.with.unwrap();
841        assert_eq!(options.fillfactor, Some(90));
842        assert_eq!(options.parallel_workers, Some(2));
843    }
844}