1use crate::{RowSchema, SQLError};
8
9mod materialized;
10pub use materialized::materialized_view_schema;
11
12pub fn require_materialized_view_population(name: &str, populated: bool) -> Result<(), SQLError> {
14 if populated {
15 return Ok(());
16 }
17 let (_, relation) =
18 uqa_core::RelationIdentity::parse_reference(name).map_err(SQLError::Unsupported)?;
19 Err(SQLError::Diagnostic {
20 sqlstate: "55000".into(),
21 message: format!("materialized view \"{relation}\" has not been populated"),
22 detail: None,
23 hint: Some("Use the REFRESH MATERIALIZED VIEW command.".into()),
24 })
25}
26
27pub fn create_view_output_columns(
28 schema: &RowSchema,
29 declared: &[String],
30) -> Result<Vec<String>, SQLError> {
31 if declared.len() > schema.len() {
32 return Err(SQLError::Routine {
33 sqlstate: "42601".into(),
34 message: "CREATE VIEW specifies more column names than columns".into(),
35 });
36 }
37 let columns = schema
38 .columns()
39 .iter()
40 .enumerate()
41 .map(|(position, column)| {
42 declared
43 .get(position)
44 .cloned()
45 .unwrap_or_else(|| schema.public_name(position).unwrap_or(column).to_string())
46 })
47 .collect::<Vec<_>>();
48 let mut seen = std::collections::BTreeSet::new();
49 for column in &columns {
50 if !seen.insert(column) {
51 return Err(SQLError::Routine {
52 sqlstate: "42701".into(),
53 message: format!("column \"{column}\" specified more than once"),
54 });
55 }
56 }
57 Ok(columns)
58}
59
60pub fn named_view_schema(
61 query_schema: &RowSchema,
62 output_columns: &[String],
63) -> Result<RowSchema, SQLError> {
64 if query_schema.len() != output_columns.len() {
65 return Err(SQLError::Internal(format!(
66 "stored view row type has {} columns but its query has {}",
67 output_columns.len(),
68 query_schema.len()
69 )));
70 }
71 Ok(RowSchema::with_types(
72 output_columns.to_vec(),
73 query_schema.column_types().to_vec(),
74 )
75 .with_column_collations(
76 (0..query_schema.len()).map(|index| query_schema.column_collation(index)),
77 )
78 .with_record_fields_from(query_schema))
79}
80
81pub fn validate_view_column_types(
82 query_schema: &RowSchema,
83 output_columns: &[String],
84) -> Result<(), SQLError> {
85 for (position, column) in output_columns.iter().enumerate() {
86 if let Some(ty) = query_schema.column_type(position) {
87 crate::schema::columns::validate_postgres_relation_column_type(column, ty)?;
88 }
89 }
90 Ok(())
91}
92
93#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
94pub enum StoredViewKind {
95 #[default]
96 View,
97 Materialized,
98}
99
100#[derive(Debug, Clone, Copy, Default)]
101pub struct ViewMutationCapabilities {
102 pub insertable: bool,
103 pub updatable: bool,
104 pub deletable: bool,
105}
106impl ViewMutationCapabilities {
107 pub const fn fully_updatable(self) -> bool {
108 self.updatable && self.deletable
109 }
110}
111
112#[derive(Clone)]
113pub struct ViewRewriteDefinition {
114 pub query: crate::plan::QueryPlan,
115 pub output_columns: Option<Vec<String>>,
116 pub options: Vec<(String, String)>,
117 pub kind: StoredViewKind,
118 pub materialized_column_types: Vec<Option<crate::ColumnType>>,
119 pub materialized_column_collations: Option<Vec<u32>>,
120}
121
122impl ViewRewriteDefinition {
123 pub fn row_schema(
124 &self,
125 routines: &dyn crate::routines::RoutineResolution,
126 catalog: super::analysis::CatalogReadView,
127 resolution: super::resolution::RelationNameResolution,
128 ) -> Result<crate::RowSchema, crate::SQLError> {
129 use crate::catalog::view::{create_view_output_columns, named_view_schema};
130 if self.kind == StoredViewKind::Materialized {
131 return materialized_view_schema(
132 self.output_columns.as_deref().unwrap_or_default(),
133 &self.materialized_column_types,
134 self.materialized_column_collations.as_deref(),
135 );
136 }
137 let query_schema = crate::binding::analyze_query_plan_schema_with_catalog(
138 routines,
139 &self.query,
140 &[],
141 catalog,
142 resolution,
143 )?;
144 let output_columns = match &self.output_columns {
145 Some(columns) => columns.clone(),
146 None => create_view_output_columns(&query_schema, &[])?,
147 };
148 named_view_schema(&query_schema, &output_columns)
149 }
150}
151
152#[cfg(test)]
153mod tests;