elefant-tools 0.0.2

A library for doing things like pg_dump and pg_restore, with extra features, and probably more bugs.
Documentation
use crate::models::hypertable_retention::HypertableRetention;
use crate::object_id::{HaveDependencies, ObjectId};
use crate::pg_interval::Interval;
use crate::quoting::AttemptedKeywordUsage::ColumnName;
use crate::quoting::{quote_value_string, IdentifierQuoter, Quotable};
use crate::whitespace_ignorant_string::WhitespaceIgnorantString;
use crate::{HypertableCompression, PostgresSchema};
use serde::{Deserialize, Serialize};

#[derive(Debug, Eq, PartialEq, Default, Clone, Serialize, Deserialize)]
pub struct PostgresView {
    pub name: String,
    pub definition: WhitespaceIgnorantString,
    pub columns: Vec<PostgresViewColumn>,
    pub comment: Option<String>,
    pub is_materialized: bool,
    pub view_options: ViewOptions,
    pub object_id: ObjectId,
    pub depends_on: Vec<ObjectId>,
}

impl HaveDependencies for &PostgresView {
    fn depends_on(&self) -> &Vec<ObjectId> {
        &self.depends_on
    }

    fn object_id(&self) -> ObjectId {
        self.object_id
    }
}

impl PostgresView {
    pub fn get_create_view_sql(
        &self,
        schema: &PostgresSchema,
        identifier_quoter: &IdentifierQuoter,
    ) -> String {
        let escaped_relation_name = format!(
            "{}.{}",
            schema.name.quote(identifier_quoter, ColumnName),
            self.name.quote(identifier_quoter, ColumnName)
        );

        let mut sql = "create".to_string();

        if self.is_materialized {
            sql.push_str(" materialized");
        }

        sql.push_str(" view ");
        sql.push_str(&escaped_relation_name);

        sql.push_str(" (");

        for (i, column) in self.columns.iter().enumerate() {
            if i != 0 {
                sql.push_str(", ");
            }

            sql.push_str(&column.name.quote(identifier_quoter, ColumnName));
        }

        sql.push_str(") ");

        if let ViewOptions::TimescaleContinuousAggregate { .. } = &self.view_options {
            sql.push_str("with (timescaledb.continuous) ");
        }

        sql.push_str("as ");

        sql.push_str(&self.definition);

        if self.is_materialized {
            while sql.ends_with(';') {
                sql.pop();
            }
            sql.push_str(" with no data;");
        }

        if let Some(comment) = &self.comment {
            sql.push_str("\ncomment on ");
            if self.is_materialized {
                sql.push_str("materialized ");
            }
            sql.push_str("view ");
            sql.push_str(&escaped_relation_name);
            sql.push_str(" is ");
            sql.push_str(&quote_value_string(comment));
            sql.push(';');
        }

        if let ViewOptions::TimescaleContinuousAggregate {
            refresh,
            compression,
            retention,
        } = &self.view_options
        {
            if let Some(refresh) = refresh {
                sql.push_str("\nselect add_continuous_aggregate_policy('");
                sql.push_str(&escaped_relation_name);
                sql.push_str("', start_offset => INTERVAL '");
                sql.push_str(&refresh.start_offset.to_postgres());
                sql.push_str("', end_offset => INTERVAL '");
                sql.push_str(&refresh.end_offset.to_postgres());
                sql.push_str("', schedule_interval => INTERVAL '");
                sql.push_str(&refresh.interval.to_postgres());
                sql.push_str("');");
            }

            if let Some(compression) = compression {
                sql.push_str("alter materialized view ");
                compression.add_compression_settings(
                    &mut sql,
                    &escaped_relation_name,
                    identifier_quoter,
                );
            }

            if let Some(retention) = retention {
                sql.push('\n');
                retention.add_retention(&mut sql, &escaped_relation_name);
            }
        }

        sql
    }

    pub fn get_refresh_sql(
        &self,
        schema: &PostgresSchema,
        identifier_quoter: &IdentifierQuoter,
    ) -> Option<String> {
        if let ViewOptions::TimescaleContinuousAggregate { .. } = &self.view_options {
            let sql = format!(
                "call refresh_continuous_aggregate('{}.{}', null, null);",
                schema.name.quote(identifier_quoter, ColumnName),
                self.name.quote(identifier_quoter, ColumnName)
            );
            Some(sql)
        } else if self.is_materialized {
            let sql = format!(
                "refresh materialized view {}.{};",
                schema.name.quote(identifier_quoter, ColumnName),
                self.name.quote(identifier_quoter, ColumnName)
            );
            Some(sql)
        } else {
            None
        }
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub struct PostgresViewColumn {
    pub name: String,
    pub ordinal_position: i32,
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug, Eq, PartialEq, Default, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ViewOptions {
    #[default]
    None,
    TimescaleContinuousAggregate {
        refresh: Option<TimescaleContinuousAggregateRefreshOptions>,
        compression: Option<HypertableCompression>,
        retention: Option<HypertableRetention>,
    },
}

#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub struct TimescaleContinuousAggregateRefreshOptions {
    pub interval: Interval,
    pub start_offset: Interval,
    pub end_offset: Interval,
}