use crate::alloc_prelude::*;
#[cfg(feature = "serde")]
use crate::serde_helpers::{cow_from_string, cow_option_from_string};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ViewWithOptionDef {
pub check_option: Option<&'static str>,
pub security_barrier: bool,
pub security_invoker: bool,
pub fillfactor: Option<i32>,
pub toast_tuple_target: Option<i32>,
pub parallel_workers: Option<i32>,
pub autovacuum_enabled: Option<bool>,
pub vacuum_index_cleanup: Option<&'static str>,
pub vacuum_truncate: Option<bool>,
pub autovacuum_vacuum_threshold: Option<i32>,
pub autovacuum_vacuum_scale_factor: Option<i32>,
pub autovacuum_vacuum_cost_delay: Option<i32>,
pub autovacuum_vacuum_cost_limit: Option<i32>,
pub autovacuum_freeze_min_age: Option<i64>,
pub autovacuum_freeze_max_age: Option<i64>,
pub autovacuum_freeze_table_age: Option<i64>,
pub autovacuum_multixact_freeze_min_age: Option<i64>,
pub autovacuum_multixact_freeze_max_age: Option<i64>,
pub autovacuum_multixact_freeze_table_age: Option<i64>,
pub log_autovacuum_min_duration: Option<i32>,
pub user_catalog_table: Option<bool>,
}
impl ViewWithOptionDef {
#[must_use]
pub const fn new() -> Self {
Self {
check_option: None,
security_barrier: false,
security_invoker: false,
fillfactor: None,
toast_tuple_target: None,
parallel_workers: None,
autovacuum_enabled: None,
vacuum_index_cleanup: None,
vacuum_truncate: None,
autovacuum_vacuum_threshold: None,
autovacuum_vacuum_scale_factor: None,
autovacuum_vacuum_cost_delay: None,
autovacuum_vacuum_cost_limit: None,
autovacuum_freeze_min_age: None,
autovacuum_freeze_max_age: None,
autovacuum_freeze_table_age: None,
autovacuum_multixact_freeze_min_age: None,
autovacuum_multixact_freeze_max_age: None,
autovacuum_multixact_freeze_table_age: None,
log_autovacuum_min_duration: None,
user_catalog_table: None,
}
}
#[must_use]
pub const fn check_option(self, option: &'static str) -> Self {
Self {
check_option: Some(option),
..self
}
}
#[must_use]
pub const fn security_barrier(self) -> Self {
Self {
security_barrier: true,
..self
}
}
#[must_use]
pub const fn security_invoker(self) -> Self {
Self {
security_invoker: true,
..self
}
}
#[must_use]
pub const fn fillfactor(self, value: i32) -> Self {
Self {
fillfactor: Some(value),
..self
}
}
#[must_use]
pub const fn toast_tuple_target(self, value: i32) -> Self {
Self {
toast_tuple_target: Some(value),
..self
}
}
#[must_use]
pub const fn parallel_workers(self, value: i32) -> Self {
Self {
parallel_workers: Some(value),
..self
}
}
#[must_use]
pub const fn autovacuum_enabled(self, value: bool) -> Self {
Self {
autovacuum_enabled: Some(value),
..self
}
}
#[must_use]
pub const fn vacuum_index_cleanup(self, value: &'static str) -> Self {
Self {
vacuum_index_cleanup: Some(value),
..self
}
}
#[must_use]
pub const fn vacuum_truncate(self, value: bool) -> Self {
Self {
vacuum_truncate: Some(value),
..self
}
}
#[must_use]
pub const fn autovacuum_vacuum_threshold(self, value: i32) -> Self {
Self {
autovacuum_vacuum_threshold: Some(value),
..self
}
}
#[must_use]
pub const fn autovacuum_vacuum_scale_factor(self, value: i32) -> Self {
Self {
autovacuum_vacuum_scale_factor: Some(value),
..self
}
}
#[must_use]
pub const fn autovacuum_vacuum_cost_delay(self, value: i32) -> Self {
Self {
autovacuum_vacuum_cost_delay: Some(value),
..self
}
}
#[must_use]
pub const fn autovacuum_vacuum_cost_limit(self, value: i32) -> Self {
Self {
autovacuum_vacuum_cost_limit: Some(value),
..self
}
}
#[must_use]
pub const fn autovacuum_freeze_min_age(self, value: i64) -> Self {
Self {
autovacuum_freeze_min_age: Some(value),
..self
}
}
#[must_use]
pub const fn autovacuum_freeze_max_age(self, value: i64) -> Self {
Self {
autovacuum_freeze_max_age: Some(value),
..self
}
}
#[must_use]
pub const fn autovacuum_freeze_table_age(self, value: i64) -> Self {
Self {
autovacuum_freeze_table_age: Some(value),
..self
}
}
#[must_use]
pub const fn autovacuum_multixact_freeze_min_age(self, value: i64) -> Self {
Self {
autovacuum_multixact_freeze_min_age: Some(value),
..self
}
}
#[must_use]
pub const fn autovacuum_multixact_freeze_max_age(self, value: i64) -> Self {
Self {
autovacuum_multixact_freeze_max_age: Some(value),
..self
}
}
#[must_use]
pub const fn autovacuum_multixact_freeze_table_age(self, value: i64) -> Self {
Self {
autovacuum_multixact_freeze_table_age: Some(value),
..self
}
}
#[must_use]
pub const fn log_autovacuum_min_duration(self, value: i32) -> Self {
Self {
log_autovacuum_min_duration: Some(value),
..self
}
}
#[must_use]
pub const fn user_catalog_table(self, value: bool) -> Self {
Self {
user_catalog_table: Some(value),
..self
}
}
#[must_use]
pub const fn into_view_with_option(self) -> ViewWithOption {
ViewWithOption {
check_option: match self.check_option {
Some(s) => Some(Cow::Borrowed(s)),
None => None,
},
security_barrier: if self.security_barrier {
Some(true)
} else {
None
},
security_invoker: if self.security_invoker {
Some(true)
} else {
None
},
fillfactor: self.fillfactor,
toast_tuple_target: self.toast_tuple_target,
parallel_workers: self.parallel_workers,
autovacuum_enabled: self.autovacuum_enabled,
vacuum_index_cleanup: match self.vacuum_index_cleanup {
Some(s) => Some(Cow::Borrowed(s)),
None => None,
},
vacuum_truncate: self.vacuum_truncate,
autovacuum_vacuum_threshold: self.autovacuum_vacuum_threshold,
autovacuum_vacuum_scale_factor: self.autovacuum_vacuum_scale_factor,
autovacuum_vacuum_cost_delay: self.autovacuum_vacuum_cost_delay,
autovacuum_vacuum_cost_limit: self.autovacuum_vacuum_cost_limit,
autovacuum_freeze_min_age: self.autovacuum_freeze_min_age,
autovacuum_freeze_max_age: self.autovacuum_freeze_max_age,
autovacuum_freeze_table_age: self.autovacuum_freeze_table_age,
autovacuum_multixact_freeze_min_age: self.autovacuum_multixact_freeze_min_age,
autovacuum_multixact_freeze_max_age: self.autovacuum_multixact_freeze_max_age,
autovacuum_multixact_freeze_table_age: self.autovacuum_multixact_freeze_table_age,
log_autovacuum_min_duration: self.log_autovacuum_min_duration,
user_catalog_table: self.user_catalog_table,
}
}
}
impl Default for ViewWithOptionDef {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct ViewWithOption {
#[cfg_attr(
feature = "serde",
serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "cow_option_from_string"
)
)]
pub check_option: Option<Cow<'static, str>>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub security_barrier: Option<bool>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub security_invoker: Option<bool>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub fillfactor: Option<i32>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub toast_tuple_target: Option<i32>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub parallel_workers: Option<i32>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub autovacuum_enabled: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "cow_option_from_string"
)
)]
pub vacuum_index_cleanup: Option<Cow<'static, str>>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub vacuum_truncate: Option<bool>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub autovacuum_vacuum_threshold: Option<i32>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub autovacuum_vacuum_scale_factor: Option<i32>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub autovacuum_vacuum_cost_delay: Option<i32>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub autovacuum_vacuum_cost_limit: Option<i32>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub autovacuum_freeze_min_age: Option<i64>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub autovacuum_freeze_max_age: Option<i64>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub autovacuum_freeze_table_age: Option<i64>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub autovacuum_multixact_freeze_min_age: Option<i64>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub autovacuum_multixact_freeze_max_age: Option<i64>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub autovacuum_multixact_freeze_table_age: Option<i64>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub log_autovacuum_min_duration: Option<i32>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub user_catalog_table: Option<bool>,
}
impl Default for ViewWithOption {
fn default() -> Self {
ViewWithOptionDef::new().into_view_with_option()
}
}
impl From<ViewWithOptionDef> for ViewWithOption {
fn from(def: ViewWithOptionDef) -> Self {
def.into_view_with_option()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ViewDef {
pub schema: &'static str,
pub name: &'static str,
pub definition: Option<&'static str>,
pub materialized: bool,
pub with: Option<ViewWithOptionDef>,
pub is_existing: bool,
pub with_no_data: bool,
pub using: Option<&'static str>,
pub tablespace: Option<&'static str>,
}
impl ViewDef {
#[must_use]
pub const fn new(schema: &'static str, name: &'static str) -> Self {
Self {
schema,
name,
definition: None,
materialized: false,
with: None,
is_existing: false,
with_no_data: false,
using: None,
tablespace: None,
}
}
#[must_use]
pub const fn definition(self, sql: &'static str) -> Self {
Self {
definition: Some(sql),
..self
}
}
#[must_use]
pub const fn materialized(self) -> Self {
Self {
materialized: true,
..self
}
}
#[must_use]
pub const fn with_options(self, options: ViewWithOptionDef) -> Self {
Self {
with: Some(options),
..self
}
}
#[must_use]
pub const fn existing(self) -> Self {
Self {
is_existing: true,
..self
}
}
#[must_use]
pub const fn with_no_data(self) -> Self {
Self {
with_no_data: true,
..self
}
}
#[must_use]
pub const fn using(self, clause: &'static str) -> Self {
Self {
using: Some(clause),
..self
}
}
#[must_use]
pub const fn tablespace(self, space: &'static str) -> Self {
Self {
tablespace: Some(space),
..self
}
}
#[must_use]
pub fn into_view(self) -> View {
View {
schema: Cow::Borrowed(self.schema),
name: Cow::Borrowed(self.name),
definition: self.definition.map(Cow::Borrowed),
materialized: self.materialized,
with: self.with.map(ViewWithOptionDef::into_view_with_option),
is_existing: self.is_existing,
with_no_data: if self.with_no_data { Some(true) } else { None },
using: self.using.map(Cow::Borrowed),
tablespace: self.tablespace.map(Cow::Borrowed),
}
}
}
impl Default for ViewDef {
fn default() -> Self {
Self::new("public", "")
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct View {
#[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
pub schema: Cow<'static, str>,
#[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
pub name: Cow<'static, str>,
#[cfg_attr(
feature = "serde",
serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "cow_option_from_string"
)
)]
pub definition: Option<Cow<'static, str>>,
#[cfg_attr(feature = "serde", serde(default))]
pub materialized: bool,
#[cfg_attr(
feature = "serde",
serde(skip_serializing_if = "Option::is_none", rename = "with")
)]
pub with: Option<ViewWithOption>,
#[cfg_attr(feature = "serde", serde(default))]
pub is_existing: bool,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub with_no_data: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "cow_option_from_string"
)
)]
pub using: Option<Cow<'static, str>>,
#[cfg_attr(
feature = "serde",
serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "cow_option_from_string"
)
)]
pub tablespace: Option<Cow<'static, str>>,
}
impl View {
#[must_use]
pub fn new(schema: impl Into<Cow<'static, str>>, name: impl Into<Cow<'static, str>>) -> Self {
Self {
schema: schema.into(),
name: name.into(),
definition: None,
materialized: false,
with: None,
is_existing: false,
with_no_data: None,
using: None,
tablespace: None,
}
}
#[inline]
#[must_use]
pub fn schema(&self) -> &str {
&self.schema
}
#[inline]
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
}
impl Default for View {
fn default() -> Self {
Self::new("public", "")
}
}
impl From<ViewDef> for View {
fn from(def: ViewDef) -> Self {
def.into_view()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_const_view_def() {
const VIEW: ViewDef = ViewDef::new("public", "active_users")
.definition("SELECT * FROM users WHERE active = 1");
assert_eq!(VIEW.name, "active_users");
assert_eq!(VIEW.schema, "public");
}
#[test]
fn test_materialized_view_def() {
const MAT_VIEW: ViewDef = ViewDef::new("public", "user_stats")
.materialized()
.with_no_data();
const {
assert!(MAT_VIEW.materialized);
}
}
#[test]
fn test_view_def_to_view() {
const DEF: ViewDef = ViewDef::new("public", "view").definition("SELECT 1");
let view = DEF.into_view();
assert_eq!(view.name(), "view");
assert_eq!(view.schema(), "public");
}
#[test]
fn test_view_with_option_def_builders() {
const OPTIONS: ViewWithOptionDef = ViewWithOptionDef::new()
.fillfactor(80)
.parallel_workers(4)
.autovacuum_enabled(true)
.vacuum_index_cleanup("auto")
.vacuum_truncate(false)
.autovacuum_vacuum_threshold(100)
.autovacuum_vacuum_scale_factor(20)
.autovacuum_vacuum_cost_delay(10)
.autovacuum_vacuum_cost_limit(200)
.autovacuum_freeze_min_age(50_000_000)
.autovacuum_freeze_max_age(200_000_000)
.autovacuum_freeze_table_age(150_000_000)
.autovacuum_multixact_freeze_min_age(5_000_000)
.autovacuum_multixact_freeze_max_age(400_000_000)
.autovacuum_multixact_freeze_table_age(150_000_000)
.log_autovacuum_min_duration(1000)
.user_catalog_table(false)
.toast_tuple_target(128);
assert_eq!(OPTIONS.fillfactor, Some(80));
assert_eq!(OPTIONS.parallel_workers, Some(4));
assert_eq!(OPTIONS.autovacuum_enabled, Some(true));
assert_eq!(OPTIONS.vacuum_index_cleanup, Some("auto"));
assert_eq!(OPTIONS.vacuum_truncate, Some(false));
assert_eq!(OPTIONS.autovacuum_vacuum_threshold, Some(100));
assert_eq!(OPTIONS.autovacuum_vacuum_scale_factor, Some(20));
assert_eq!(OPTIONS.autovacuum_vacuum_cost_delay, Some(10));
assert_eq!(OPTIONS.autovacuum_vacuum_cost_limit, Some(200));
assert_eq!(OPTIONS.autovacuum_freeze_min_age, Some(50_000_000));
assert_eq!(OPTIONS.autovacuum_freeze_max_age, Some(200_000_000));
assert_eq!(OPTIONS.autovacuum_freeze_table_age, Some(150_000_000));
assert_eq!(OPTIONS.autovacuum_multixact_freeze_min_age, Some(5_000_000));
assert_eq!(
OPTIONS.autovacuum_multixact_freeze_max_age,
Some(400_000_000)
);
assert_eq!(
OPTIONS.autovacuum_multixact_freeze_table_age,
Some(150_000_000)
);
assert_eq!(OPTIONS.log_autovacuum_min_duration, Some(1000));
assert_eq!(OPTIONS.user_catalog_table, Some(false));
assert_eq!(OPTIONS.toast_tuple_target, Some(128));
}
#[test]
fn test_view_with_option_def_to_runtime() {
const OPTIONS: ViewWithOptionDef = ViewWithOptionDef::new()
.fillfactor(90)
.security_barrier()
.security_invoker()
.check_option("cascaded");
let runtime = OPTIONS.into_view_with_option();
assert_eq!(runtime.fillfactor, Some(90));
assert_eq!(runtime.security_barrier, Some(true));
assert_eq!(runtime.security_invoker, Some(true));
assert_eq!(runtime.check_option.as_deref(), Some("cascaded"));
}
#[test]
fn test_materialized_view_with_all_options() {
const MAT_VIEW: ViewDef = ViewDef::new("analytics", "monthly_sales")
.materialized()
.with_no_data()
.using("btree")
.tablespace("fast_ssd")
.with_options(ViewWithOptionDef::new().fillfactor(90).parallel_workers(2))
.definition("SELECT * FROM sales WHERE date > now() - interval '30 days'");
const {
assert!(MAT_VIEW.materialized);
}
const {
assert!(MAT_VIEW.with_no_data);
}
assert_eq!(MAT_VIEW.using, Some("btree"));
assert_eq!(MAT_VIEW.tablespace, Some("fast_ssd"));
assert!(MAT_VIEW.with.is_some());
let options = MAT_VIEW.with.unwrap();
assert_eq!(options.fillfactor, Some(90));
assert_eq!(options.parallel_workers, Some(2));
}
}