elefant_tools/models/
view.rs1use crate::models::hypertable_retention::HypertableRetention;
2use crate::object_id::{HaveDependencies, ObjectId};
3use crate::pg_interval::Interval;
4use crate::quoting::AttemptedKeywordUsage::ColumnName;
5use crate::quoting::{quote_value_string, IdentifierQuoter, Quotable};
6use crate::whitespace_ignorant_string::WhitespaceIgnorantString;
7use crate::{HypertableCompression, PostgresSchema};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Eq, PartialEq, Default, Clone, Serialize, Deserialize)]
11pub struct PostgresView {
12 pub name: String,
13 pub definition: WhitespaceIgnorantString,
14 pub columns: Vec<PostgresViewColumn>,
15 pub comment: Option<String>,
16 pub is_materialized: bool,
17 pub view_options: ViewOptions,
18 pub object_id: ObjectId,
19 pub depends_on: Vec<ObjectId>,
20}
21
22impl HaveDependencies for &PostgresView {
23 fn depends_on(&self) -> &Vec<ObjectId> {
24 &self.depends_on
25 }
26
27 fn object_id(&self) -> ObjectId {
28 self.object_id
29 }
30}
31
32impl PostgresView {
33 pub fn get_create_view_sql(
34 &self,
35 schema: &PostgresSchema,
36 identifier_quoter: &IdentifierQuoter,
37 ) -> String {
38 let escaped_relation_name = format!(
39 "{}.{}",
40 schema.name.quote(identifier_quoter, ColumnName),
41 self.name.quote(identifier_quoter, ColumnName)
42 );
43
44 let mut sql = "create".to_string();
45
46 if self.is_materialized {
47 sql.push_str(" materialized");
48 }
49
50 sql.push_str(" view ");
51 sql.push_str(&escaped_relation_name);
52
53 sql.push_str(" (");
54
55 for (i, column) in self.columns.iter().enumerate() {
56 if i != 0 {
57 sql.push_str(", ");
58 }
59
60 sql.push_str(&column.name.quote(identifier_quoter, ColumnName));
61 }
62
63 sql.push_str(") ");
64
65 if let ViewOptions::TimescaleContinuousAggregate { .. } = &self.view_options {
66 sql.push_str("with (timescaledb.continuous) ");
67 }
68
69 sql.push_str("as ");
70
71 sql.push_str(&self.definition);
72
73 if self.is_materialized {
74 while sql.ends_with(';') {
75 sql.pop();
76 }
77 sql.push_str(" with no data;");
78 }
79
80 if let Some(comment) = &self.comment {
81 sql.push_str("\ncomment on ");
82 if self.is_materialized {
83 sql.push_str("materialized ");
84 }
85 sql.push_str("view ");
86 sql.push_str(&escaped_relation_name);
87 sql.push_str(" is ");
88 sql.push_str("e_value_string(comment));
89 sql.push(';');
90 }
91
92 if let ViewOptions::TimescaleContinuousAggregate {
93 refresh,
94 compression,
95 retention,
96 } = &self.view_options
97 {
98 if let Some(refresh) = refresh {
99 sql.push_str("\nselect add_continuous_aggregate_policy('");
100 sql.push_str(&escaped_relation_name);
101 sql.push_str("', start_offset => INTERVAL '");
102 sql.push_str(&refresh.start_offset.to_postgres());
103 sql.push_str("', end_offset => INTERVAL '");
104 sql.push_str(&refresh.end_offset.to_postgres());
105 sql.push_str("', schedule_interval => INTERVAL '");
106 sql.push_str(&refresh.interval.to_postgres());
107 sql.push_str("');");
108 }
109
110 if let Some(compression) = compression {
111 sql.push_str("alter materialized view ");
112 compression.add_compression_settings(
113 &mut sql,
114 &escaped_relation_name,
115 identifier_quoter,
116 );
117 }
118
119 if let Some(retention) = retention {
120 sql.push('\n');
121 retention.add_retention(&mut sql, &escaped_relation_name);
122 }
123 }
124
125 sql
126 }
127
128 pub fn get_refresh_sql(
129 &self,
130 schema: &PostgresSchema,
131 identifier_quoter: &IdentifierQuoter,
132 ) -> Option<String> {
133 if let ViewOptions::TimescaleContinuousAggregate { .. } = &self.view_options {
134 let sql = format!(
135 "call refresh_continuous_aggregate('{}.{}', null, null);",
136 schema.name.quote(identifier_quoter, ColumnName),
137 self.name.quote(identifier_quoter, ColumnName)
138 );
139 Some(sql)
140 } else if self.is_materialized {
141 let sql = format!(
142 "refresh materialized view {}.{};",
143 schema.name.quote(identifier_quoter, ColumnName),
144 self.name.quote(identifier_quoter, ColumnName)
145 );
146 Some(sql)
147 } else {
148 None
149 }
150 }
151}
152
153#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
154pub struct PostgresViewColumn {
155 pub name: String,
156 pub ordinal_position: i32,
157}
158
159#[allow(clippy::large_enum_variant)]
160#[derive(Debug, Eq, PartialEq, Default, Clone, Serialize, Deserialize)]
161#[serde(tag = "type")]
162pub enum ViewOptions {
163 #[default]
164 None,
165 TimescaleContinuousAggregate {
166 refresh: Option<TimescaleContinuousAggregateRefreshOptions>,
167 compression: Option<HypertableCompression>,
168 retention: Option<HypertableRetention>,
169 },
170}
171
172#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
173pub struct TimescaleContinuousAggregateRefreshOptions {
174 pub interval: Interval,
175 pub start_offset: Interval,
176 pub end_offset: Interval,
177}