Skip to main content

drizzle_types/postgres/ddl/
view.rs

1//! `PostgreSQL` views: [`ViewDef`] (const) and [`View`] (runtime).
2
3use crate::alloc_prelude::*;
4
5#[cfg(feature = "serde")]
6use crate::serde_helpers::{cow_from_string, cow_option_from_string};
7
8// =============================================================================
9// ViewWithOption Types
10// =============================================================================
11
12/// A view WITH options definition that can be built in a `const`.
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub struct ViewWithOptionDef {
15    /// CHECK OPTION ('local' | 'cascaded')
16    pub check_option: Option<&'static str>,
17    /// Security barrier flag
18    pub security_barrier: bool,
19    /// Security invoker flag
20    pub security_invoker: bool,
21    /// Fillfactor (for materialized views)
22    pub fillfactor: Option<i32>,
23    /// Toast tuple target (for materialized views)
24    pub toast_tuple_target: Option<i32>,
25    /// Parallel workers (for materialized views)
26    pub parallel_workers: Option<i32>,
27    /// Autovacuum enabled (for materialized views)
28    pub autovacuum_enabled: Option<bool>,
29    /// Vacuum index cleanup (for materialized views): 'auto' | 'on' | 'off'
30    pub vacuum_index_cleanup: Option<&'static str>,
31    /// Vacuum truncate (for materialized views)
32    pub vacuum_truncate: Option<bool>,
33    /// Autovacuum vacuum threshold (for materialized views)
34    pub autovacuum_vacuum_threshold: Option<i32>,
35    /// Autovacuum vacuum scale factor (for materialized views)
36    pub autovacuum_vacuum_scale_factor: Option<i32>,
37    /// Autovacuum vacuum cost delay (for materialized views)
38    pub autovacuum_vacuum_cost_delay: Option<i32>,
39    /// Autovacuum vacuum cost limit (for materialized views)
40    pub autovacuum_vacuum_cost_limit: Option<i32>,
41    /// Autovacuum freeze min age (for materialized views)
42    pub autovacuum_freeze_min_age: Option<i64>,
43    /// Autovacuum freeze max age (for materialized views)
44    pub autovacuum_freeze_max_age: Option<i64>,
45    /// Autovacuum freeze table age (for materialized views)
46    pub autovacuum_freeze_table_age: Option<i64>,
47    /// Autovacuum multixact freeze min age (for materialized views)
48    pub autovacuum_multixact_freeze_min_age: Option<i64>,
49    /// Autovacuum multixact freeze max age (for materialized views)
50    pub autovacuum_multixact_freeze_max_age: Option<i64>,
51    /// Autovacuum multixact freeze table age (for materialized views)
52    pub autovacuum_multixact_freeze_table_age: Option<i64>,
53    /// Log autovacuum min duration (for materialized views)
54    pub log_autovacuum_min_duration: Option<i32>,
55    /// User catalog table (for materialized views)
56    pub user_catalog_table: Option<bool>,
57}
58
59impl ViewWithOptionDef {
60    /// Creates a view WITH options definition.
61    #[must_use]
62    pub const fn new() -> Self {
63        Self {
64            check_option: None,
65            security_barrier: false,
66            security_invoker: false,
67            fillfactor: None,
68            toast_tuple_target: None,
69            parallel_workers: None,
70            autovacuum_enabled: None,
71            vacuum_index_cleanup: None,
72            vacuum_truncate: None,
73            autovacuum_vacuum_threshold: None,
74            autovacuum_vacuum_scale_factor: None,
75            autovacuum_vacuum_cost_delay: None,
76            autovacuum_vacuum_cost_limit: None,
77            autovacuum_freeze_min_age: None,
78            autovacuum_freeze_max_age: None,
79            autovacuum_freeze_table_age: None,
80            autovacuum_multixact_freeze_min_age: None,
81            autovacuum_multixact_freeze_max_age: None,
82            autovacuum_multixact_freeze_table_age: None,
83            log_autovacuum_min_duration: None,
84            user_catalog_table: None,
85        }
86    }
87
88    /// Set CHECK OPTION
89    #[must_use]
90    pub const fn check_option(self, option: &'static str) -> Self {
91        Self {
92            check_option: Some(option),
93            ..self
94        }
95    }
96
97    /// Set security barrier
98    #[must_use]
99    pub const fn security_barrier(self) -> Self {
100        Self {
101            security_barrier: true,
102            ..self
103        }
104    }
105
106    /// Set security invoker
107    #[must_use]
108    pub const fn security_invoker(self) -> Self {
109        Self {
110            security_invoker: true,
111            ..self
112        }
113    }
114
115    /// Set fillfactor (for materialized views)
116    #[must_use]
117    pub const fn fillfactor(self, value: i32) -> Self {
118        Self {
119            fillfactor: Some(value),
120            ..self
121        }
122    }
123
124    /// Set toast tuple target (for materialized views)
125    #[must_use]
126    pub const fn toast_tuple_target(self, value: i32) -> Self {
127        Self {
128            toast_tuple_target: Some(value),
129            ..self
130        }
131    }
132
133    /// Set parallel workers (for materialized views)
134    #[must_use]
135    pub const fn parallel_workers(self, value: i32) -> Self {
136        Self {
137            parallel_workers: Some(value),
138            ..self
139        }
140    }
141
142    /// Set autovacuum enabled (for materialized views)
143    #[must_use]
144    pub const fn autovacuum_enabled(self, value: bool) -> Self {
145        Self {
146            autovacuum_enabled: Some(value),
147            ..self
148        }
149    }
150
151    /// Set vacuum index cleanup (for materialized views): "auto", "on", or "off"
152    #[must_use]
153    pub const fn vacuum_index_cleanup(self, value: &'static str) -> Self {
154        Self {
155            vacuum_index_cleanup: Some(value),
156            ..self
157        }
158    }
159
160    /// Set vacuum truncate (for materialized views)
161    #[must_use]
162    pub const fn vacuum_truncate(self, value: bool) -> Self {
163        Self {
164            vacuum_truncate: Some(value),
165            ..self
166        }
167    }
168
169    /// Set autovacuum vacuum threshold (for materialized views)
170    #[must_use]
171    pub const fn autovacuum_vacuum_threshold(self, value: i32) -> Self {
172        Self {
173            autovacuum_vacuum_threshold: Some(value),
174            ..self
175        }
176    }
177
178    /// Set autovacuum vacuum scale factor (for materialized views)
179    #[must_use]
180    pub const fn autovacuum_vacuum_scale_factor(self, value: i32) -> Self {
181        Self {
182            autovacuum_vacuum_scale_factor: Some(value),
183            ..self
184        }
185    }
186
187    /// Set autovacuum vacuum cost delay (for materialized views)
188    #[must_use]
189    pub const fn autovacuum_vacuum_cost_delay(self, value: i32) -> Self {
190        Self {
191            autovacuum_vacuum_cost_delay: Some(value),
192            ..self
193        }
194    }
195
196    /// Set autovacuum vacuum cost limit (for materialized views)
197    #[must_use]
198    pub const fn autovacuum_vacuum_cost_limit(self, value: i32) -> Self {
199        Self {
200            autovacuum_vacuum_cost_limit: Some(value),
201            ..self
202        }
203    }
204
205    /// Set autovacuum freeze min age (for materialized views)
206    #[must_use]
207    pub const fn autovacuum_freeze_min_age(self, value: i64) -> Self {
208        Self {
209            autovacuum_freeze_min_age: Some(value),
210            ..self
211        }
212    }
213
214    /// Set autovacuum freeze max age (for materialized views)
215    #[must_use]
216    pub const fn autovacuum_freeze_max_age(self, value: i64) -> Self {
217        Self {
218            autovacuum_freeze_max_age: Some(value),
219            ..self
220        }
221    }
222
223    /// Set autovacuum freeze table age (for materialized views)
224    #[must_use]
225    pub const fn autovacuum_freeze_table_age(self, value: i64) -> Self {
226        Self {
227            autovacuum_freeze_table_age: Some(value),
228            ..self
229        }
230    }
231
232    /// Set autovacuum multixact freeze min age (for materialized views)
233    #[must_use]
234    pub const fn autovacuum_multixact_freeze_min_age(self, value: i64) -> Self {
235        Self {
236            autovacuum_multixact_freeze_min_age: Some(value),
237            ..self
238        }
239    }
240
241    /// Set autovacuum multixact freeze max age (for materialized views)
242    #[must_use]
243    pub const fn autovacuum_multixact_freeze_max_age(self, value: i64) -> Self {
244        Self {
245            autovacuum_multixact_freeze_max_age: Some(value),
246            ..self
247        }
248    }
249
250    /// Set autovacuum multixact freeze table age (for materialized views)
251    #[must_use]
252    pub const fn autovacuum_multixact_freeze_table_age(self, value: i64) -> Self {
253        Self {
254            autovacuum_multixact_freeze_table_age: Some(value),
255            ..self
256        }
257    }
258
259    /// Set log autovacuum min duration (for materialized views)
260    #[must_use]
261    pub const fn log_autovacuum_min_duration(self, value: i32) -> Self {
262        Self {
263            log_autovacuum_min_duration: Some(value),
264            ..self
265        }
266    }
267
268    /// Set user catalog table (for materialized views)
269    #[must_use]
270    pub const fn user_catalog_table(self, value: bool) -> Self {
271        Self {
272            user_catalog_table: Some(value),
273            ..self
274        }
275    }
276
277    /// Converts to the runtime [`ViewWithOption`].
278    #[must_use]
279    pub const fn into_view_with_option(self) -> ViewWithOption {
280        ViewWithOption {
281            check_option: match self.check_option {
282                Some(s) => Some(Cow::Borrowed(s)),
283                None => None,
284            },
285            security_barrier: if self.security_barrier {
286                Some(true)
287            } else {
288                None
289            },
290            security_invoker: if self.security_invoker {
291                Some(true)
292            } else {
293                None
294            },
295            fillfactor: self.fillfactor,
296            toast_tuple_target: self.toast_tuple_target,
297            parallel_workers: self.parallel_workers,
298            autovacuum_enabled: self.autovacuum_enabled,
299            vacuum_index_cleanup: match self.vacuum_index_cleanup {
300                Some(s) => Some(Cow::Borrowed(s)),
301                None => None,
302            },
303            vacuum_truncate: self.vacuum_truncate,
304            autovacuum_vacuum_threshold: self.autovacuum_vacuum_threshold,
305            autovacuum_vacuum_scale_factor: self.autovacuum_vacuum_scale_factor,
306            autovacuum_vacuum_cost_delay: self.autovacuum_vacuum_cost_delay,
307            autovacuum_vacuum_cost_limit: self.autovacuum_vacuum_cost_limit,
308            autovacuum_freeze_min_age: self.autovacuum_freeze_min_age,
309            autovacuum_freeze_max_age: self.autovacuum_freeze_max_age,
310            autovacuum_freeze_table_age: self.autovacuum_freeze_table_age,
311            autovacuum_multixact_freeze_min_age: self.autovacuum_multixact_freeze_min_age,
312            autovacuum_multixact_freeze_max_age: self.autovacuum_multixact_freeze_max_age,
313            autovacuum_multixact_freeze_table_age: self.autovacuum_multixact_freeze_table_age,
314            log_autovacuum_min_duration: self.log_autovacuum_min_duration,
315            user_catalog_table: self.user_catalog_table,
316        }
317    }
318}
319
320impl Default for ViewWithOptionDef {
321    fn default() -> Self {
322        Self::new()
323    }
324}
325
326/// A view WITH options, as stored in migration snapshots.
327#[derive(Clone, Debug, PartialEq, Eq)]
328#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
329#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
330pub struct ViewWithOption {
331    /// CHECK OPTION ('local' | 'cascaded')
332    #[cfg_attr(
333        feature = "serde",
334        serde(
335            default,
336            skip_serializing_if = "Option::is_none",
337            deserialize_with = "cow_option_from_string"
338        )
339    )]
340    pub check_option: Option<Cow<'static, str>>,
341
342    /// Security barrier flag
343    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
344    pub security_barrier: Option<bool>,
345
346    /// Security invoker flag
347    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
348    pub security_invoker: Option<bool>,
349
350    /// Fillfactor (for materialized views)
351    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
352    pub fillfactor: Option<i32>,
353
354    /// Toast tuple target (for materialized views)
355    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
356    pub toast_tuple_target: Option<i32>,
357
358    /// Parallel workers (for materialized views)
359    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
360    pub parallel_workers: Option<i32>,
361
362    /// Autovacuum enabled (for materialized views)
363    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
364    pub autovacuum_enabled: Option<bool>,
365
366    /// Vacuum index cleanup (for materialized views): 'auto' | 'on' | 'off'
367    #[cfg_attr(
368        feature = "serde",
369        serde(
370            default,
371            skip_serializing_if = "Option::is_none",
372            deserialize_with = "cow_option_from_string"
373        )
374    )]
375    pub vacuum_index_cleanup: Option<Cow<'static, str>>,
376
377    /// Vacuum truncate (for materialized views)
378    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
379    pub vacuum_truncate: Option<bool>,
380
381    /// Autovacuum vacuum threshold (for materialized views)
382    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
383    pub autovacuum_vacuum_threshold: Option<i32>,
384
385    /// Autovacuum vacuum scale factor (for materialized views)
386    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
387    pub autovacuum_vacuum_scale_factor: Option<i32>,
388
389    /// Autovacuum vacuum cost delay (for materialized views)
390    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
391    pub autovacuum_vacuum_cost_delay: Option<i32>,
392
393    /// Autovacuum vacuum cost limit (for materialized views)
394    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
395    pub autovacuum_vacuum_cost_limit: Option<i32>,
396
397    /// Autovacuum freeze min age (for materialized views)
398    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
399    pub autovacuum_freeze_min_age: Option<i64>,
400
401    /// Autovacuum freeze max age (for materialized views)
402    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
403    pub autovacuum_freeze_max_age: Option<i64>,
404
405    /// Autovacuum freeze table age (for materialized views)
406    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
407    pub autovacuum_freeze_table_age: Option<i64>,
408
409    /// Autovacuum multixact freeze min age (for materialized views)
410    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
411    pub autovacuum_multixact_freeze_min_age: Option<i64>,
412
413    /// Autovacuum multixact freeze max age (for materialized views)
414    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
415    pub autovacuum_multixact_freeze_max_age: Option<i64>,
416
417    /// Autovacuum multixact freeze table age (for materialized views)
418    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
419    pub autovacuum_multixact_freeze_table_age: Option<i64>,
420
421    /// Log autovacuum min duration (for materialized views)
422    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
423    pub log_autovacuum_min_duration: Option<i32>,
424
425    /// User catalog table (for materialized views)
426    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
427    pub user_catalog_table: Option<bool>,
428}
429
430impl Default for ViewWithOption {
431    fn default() -> Self {
432        ViewWithOptionDef::new().into_view_with_option()
433    }
434}
435
436impl From<ViewWithOptionDef> for ViewWithOption {
437    fn from(def: ViewWithOptionDef) -> Self {
438        def.into_view_with_option()
439    }
440}
441
442// =============================================================================
443// Const-friendly Definition Type
444// =============================================================================
445
446/// A view definition that can be built in a `const`.
447#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
448pub struct ViewDef {
449    /// Schema name
450    pub schema: &'static str,
451    /// View name
452    pub name: &'static str,
453    /// The view's `SELECT` statement (the part after `AS`)
454    pub definition: Option<&'static str>,
455    /// Is this a materialized view?
456    pub materialized: bool,
457    /// WITH options
458    pub with: Option<ViewWithOptionDef>,
459    /// Whether this is an existing view (not managed by drizzle)
460    pub is_existing: bool,
461    /// WITH NO DATA (for materialized views)
462    pub with_no_data: bool,
463    /// USING clause (for materialized views)
464    pub using: Option<&'static str>,
465    /// Tablespace (for materialized views)
466    pub tablespace: Option<&'static str>,
467}
468
469impl ViewDef {
470    /// Creates a view definition.
471    #[must_use]
472    pub const fn new(schema: &'static str, name: &'static str) -> Self {
473        Self {
474            schema,
475            name,
476            definition: None,
477            materialized: false,
478            with: None,
479            is_existing: false,
480            with_no_data: false,
481            using: None,
482            tablespace: None,
483        }
484    }
485
486    /// Set the view definition
487    #[must_use]
488    pub const fn definition(self, sql: &'static str) -> Self {
489        Self {
490            definition: Some(sql),
491            ..self
492        }
493    }
494
495    /// Mark as materialized view
496    #[must_use]
497    pub const fn materialized(self) -> Self {
498        Self {
499            materialized: true,
500            ..self
501        }
502    }
503
504    /// Set WITH options
505    #[must_use]
506    pub const fn with_options(self, options: ViewWithOptionDef) -> Self {
507        Self {
508            with: Some(options),
509            ..self
510        }
511    }
512
513    /// Mark as existing (not managed by drizzle)
514    #[must_use]
515    pub const fn existing(self) -> Self {
516        Self {
517            is_existing: true,
518            ..self
519        }
520    }
521
522    /// Set WITH NO DATA
523    #[must_use]
524    pub const fn with_no_data(self) -> Self {
525        Self {
526            with_no_data: true,
527            ..self
528        }
529    }
530
531    /// Set USING clause
532    #[must_use]
533    pub const fn using(self, clause: &'static str) -> Self {
534        Self {
535            using: Some(clause),
536            ..self
537        }
538    }
539
540    /// Set tablespace
541    #[must_use]
542    pub const fn tablespace(self, space: &'static str) -> Self {
543        Self {
544            tablespace: Some(space),
545            ..self
546        }
547    }
548
549    /// Converts to the runtime [`View`].
550    ///
551    /// Note: This method cannot be const because it needs to convert nested Option types
552    /// (with options) which require runtime method calls.
553    #[must_use]
554    pub fn into_view(self) -> View {
555        View {
556            schema: Cow::Borrowed(self.schema),
557            name: Cow::Borrowed(self.name),
558            definition: self.definition.map(Cow::Borrowed),
559            materialized: self.materialized,
560            with: self.with.map(ViewWithOptionDef::into_view_with_option),
561            is_existing: self.is_existing,
562            with_no_data: if self.with_no_data { Some(true) } else { None },
563            using: self.using.map(Cow::Borrowed),
564            tablespace: self.tablespace.map(Cow::Borrowed),
565        }
566    }
567}
568
569impl Default for ViewDef {
570    fn default() -> Self {
571        Self::new("public", "")
572    }
573}
574
575// =============================================================================
576// Runtime Type for Serde
577// =============================================================================
578
579/// A view, as stored in migration snapshots.
580#[derive(Clone, Debug, PartialEq, Eq)]
581#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
582#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
583pub struct View {
584    /// Schema name
585    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
586    pub schema: Cow<'static, str>,
587
588    /// View name
589    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
590    pub name: Cow<'static, str>,
591
592    /// The view's `SELECT` statement (the part after `AS`)
593    #[cfg_attr(
594        feature = "serde",
595        serde(
596            default,
597            skip_serializing_if = "Option::is_none",
598            deserialize_with = "cow_option_from_string"
599        )
600    )]
601    pub definition: Option<Cow<'static, str>>,
602
603    /// Is this a materialized view?
604    #[cfg_attr(feature = "serde", serde(default))]
605    pub materialized: bool,
606
607    /// WITH options
608    #[cfg_attr(
609        feature = "serde",
610        serde(skip_serializing_if = "Option::is_none", rename = "with")
611    )]
612    pub with: Option<ViewWithOption>,
613
614    /// Whether this is an existing view (not managed by drizzle)
615    #[cfg_attr(feature = "serde", serde(default))]
616    pub is_existing: bool,
617
618    /// WITH NO DATA (for materialized views)
619    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
620    pub with_no_data: Option<bool>,
621
622    /// USING clause (for materialized views)
623    #[cfg_attr(
624        feature = "serde",
625        serde(
626            default,
627            skip_serializing_if = "Option::is_none",
628            deserialize_with = "cow_option_from_string"
629        )
630    )]
631    pub using: Option<Cow<'static, str>>,
632
633    /// Tablespace (for materialized views)
634    #[cfg_attr(
635        feature = "serde",
636        serde(
637            default,
638            skip_serializing_if = "Option::is_none",
639            deserialize_with = "cow_option_from_string"
640        )
641    )]
642    pub tablespace: Option<Cow<'static, str>>,
643}
644
645impl View {
646    /// Creates a 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    /// Returns the schema name.
663    #[inline]
664    #[must_use]
665    pub fn schema(&self) -> &str {
666        &self.schema
667    }
668
669    /// Returns 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    /// Optional fields are skipped when serializing; deserialization must
703    /// treat the missing keys as `None` instead of erroring.
704    #[cfg(feature = "serde")]
705    #[test]
706    fn test_serde_roundtrip_with_all_none_optionals() {
707        let view = View {
708            schema: Cow::Borrowed("public"),
709            name: Cow::Borrowed("plain_view"),
710            definition: Some(Cow::Borrowed("SELECT 1")),
711            ..View::default()
712        };
713        assert!(view.using.is_none());
714        assert!(view.tablespace.is_none());
715
716        let json = serde_json::to_string(&view).expect("serialize");
717        let parsed: View = serde_json::from_str(&json).expect("deserialize");
718        assert_eq!(parsed, view);
719
720        // ViewWithOption's own optionals must round-trip the same way.
721        let with_options = View {
722            with: Some(ViewWithOption {
723                security_barrier: Some(true),
724                ..ViewWithOption::default()
725            }),
726            ..view
727        };
728        let json = serde_json::to_string(&with_options).expect("serialize");
729        let parsed: View = serde_json::from_str(&json).expect("deserialize");
730        assert_eq!(parsed, with_options);
731    }
732
733    #[test]
734    fn test_materialized_view_def() {
735        const MAT_VIEW: ViewDef = ViewDef::new("public", "user_stats")
736            .materialized()
737            .with_no_data();
738
739        const {
740            assert!(MAT_VIEW.materialized);
741        }
742    }
743
744    #[test]
745    fn test_view_def_to_view() {
746        const DEF: ViewDef = ViewDef::new("public", "view").definition("SELECT 1");
747        let view = DEF.into_view();
748        assert_eq!(view.name(), "view");
749        assert_eq!(view.schema(), "public");
750    }
751
752    #[test]
753    fn test_view_with_option_def_builders() {
754        // Test const builder methods for materialized view options
755        const OPTIONS: ViewWithOptionDef = ViewWithOptionDef::new()
756            .fillfactor(80)
757            .parallel_workers(4)
758            .autovacuum_enabled(true)
759            .vacuum_index_cleanup("auto")
760            .vacuum_truncate(false)
761            .autovacuum_vacuum_threshold(100)
762            .autovacuum_vacuum_scale_factor(20)
763            .autovacuum_vacuum_cost_delay(10)
764            .autovacuum_vacuum_cost_limit(200)
765            .autovacuum_freeze_min_age(50_000_000)
766            .autovacuum_freeze_max_age(200_000_000)
767            .autovacuum_freeze_table_age(150_000_000)
768            .autovacuum_multixact_freeze_min_age(5_000_000)
769            .autovacuum_multixact_freeze_max_age(400_000_000)
770            .autovacuum_multixact_freeze_table_age(150_000_000)
771            .log_autovacuum_min_duration(1000)
772            .user_catalog_table(false)
773            .toast_tuple_target(128);
774
775        assert_eq!(OPTIONS.fillfactor, Some(80));
776        assert_eq!(OPTIONS.parallel_workers, Some(4));
777        assert_eq!(OPTIONS.autovacuum_enabled, Some(true));
778        assert_eq!(OPTIONS.vacuum_index_cleanup, Some("auto"));
779        assert_eq!(OPTIONS.vacuum_truncate, Some(false));
780        assert_eq!(OPTIONS.autovacuum_vacuum_threshold, Some(100));
781        assert_eq!(OPTIONS.autovacuum_vacuum_scale_factor, Some(20));
782        assert_eq!(OPTIONS.autovacuum_vacuum_cost_delay, Some(10));
783        assert_eq!(OPTIONS.autovacuum_vacuum_cost_limit, Some(200));
784        assert_eq!(OPTIONS.autovacuum_freeze_min_age, Some(50_000_000));
785        assert_eq!(OPTIONS.autovacuum_freeze_max_age, Some(200_000_000));
786        assert_eq!(OPTIONS.autovacuum_freeze_table_age, Some(150_000_000));
787        assert_eq!(OPTIONS.autovacuum_multixact_freeze_min_age, Some(5_000_000));
788        assert_eq!(
789            OPTIONS.autovacuum_multixact_freeze_max_age,
790            Some(400_000_000)
791        );
792        assert_eq!(
793            OPTIONS.autovacuum_multixact_freeze_table_age,
794            Some(150_000_000)
795        );
796        assert_eq!(OPTIONS.log_autovacuum_min_duration, Some(1000));
797        assert_eq!(OPTIONS.user_catalog_table, Some(false));
798        assert_eq!(OPTIONS.toast_tuple_target, Some(128));
799    }
800
801    #[test]
802    fn test_view_with_option_def_to_runtime() {
803        const OPTIONS: ViewWithOptionDef = ViewWithOptionDef::new()
804            .fillfactor(90)
805            .security_barrier()
806            .security_invoker()
807            .check_option("cascaded");
808
809        let runtime = OPTIONS.into_view_with_option();
810        assert_eq!(runtime.fillfactor, Some(90));
811        assert_eq!(runtime.security_barrier, Some(true));
812        assert_eq!(runtime.security_invoker, Some(true));
813        assert_eq!(runtime.check_option.as_deref(), Some("cascaded"));
814    }
815
816    #[test]
817    fn test_materialized_view_with_all_options() {
818        const MAT_VIEW: ViewDef = ViewDef::new("analytics", "monthly_sales")
819            .materialized()
820            .with_no_data()
821            .using("btree")
822            .tablespace("fast_ssd")
823            .with_options(ViewWithOptionDef::new().fillfactor(90).parallel_workers(2))
824            .definition("SELECT * FROM sales WHERE date > now() - interval '30 days'");
825
826        const {
827            assert!(MAT_VIEW.materialized);
828        }
829        const {
830            assert!(MAT_VIEW.with_no_data);
831        }
832        assert_eq!(MAT_VIEW.using, Some("btree"));
833        assert_eq!(MAT_VIEW.tablespace, Some("fast_ssd"));
834        assert!(MAT_VIEW.with.is_some());
835
836        let options = MAT_VIEW.with.unwrap();
837        assert_eq!(options.fillfactor, Some(90));
838        assert_eq!(options.parallel_workers, Some(2));
839    }
840}