Skip to main content

uqa_sql/catalog/
view.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use crate::{RowSchema, SQLError};
8
9pub fn create_view_output_columns(
10    schema: &RowSchema,
11    declared: &[String],
12) -> Result<Vec<String>, SQLError> {
13    if declared.len() > schema.len() {
14        return Err(SQLError::Routine {
15            sqlstate: "42601".into(),
16            message: "CREATE VIEW specifies more column names than columns".into(),
17        });
18    }
19    let columns = schema
20        .columns()
21        .iter()
22        .enumerate()
23        .map(|(position, column)| {
24            declared
25                .get(position)
26                .cloned()
27                .unwrap_or_else(|| schema.public_name(position).unwrap_or(column).to_string())
28        })
29        .collect::<Vec<_>>();
30    let mut seen = std::collections::BTreeSet::new();
31    for column in &columns {
32        if !seen.insert(column) {
33            return Err(SQLError::Routine {
34                sqlstate: "42701".into(),
35                message: format!("column \"{column}\" specified more than once"),
36            });
37        }
38    }
39    Ok(columns)
40}
41
42pub fn named_view_schema(
43    query_schema: &RowSchema,
44    output_columns: &[String],
45) -> Result<RowSchema, SQLError> {
46    if query_schema.len() != output_columns.len() {
47        return Err(SQLError::Internal(format!(
48            "stored view row type has {} columns but its query has {}",
49            output_columns.len(),
50            query_schema.len()
51        )));
52    }
53    Ok(RowSchema::with_types(
54        output_columns.to_vec(),
55        query_schema.column_types().to_vec(),
56    ))
57}
58
59pub fn validate_view_column_types(
60    query_schema: &RowSchema,
61    output_columns: &[String],
62) -> Result<(), SQLError> {
63    for (position, column) in output_columns.iter().enumerate() {
64        if let Some(ty) = query_schema.column_type(position) {
65            crate::schema::columns::validate_postgres_relation_column_type(column, ty)?;
66        }
67    }
68    Ok(())
69}
70
71#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
72pub enum StoredViewKind {
73    #[default]
74    View,
75    Materialized,
76}
77
78#[derive(Debug, Clone, Copy, Default)]
79pub struct ViewMutationCapabilities {
80    pub insertable: bool,
81    pub updatable: bool,
82    pub deletable: bool,
83}
84impl ViewMutationCapabilities {
85    pub const fn fully_updatable(self) -> bool {
86        self.updatable && self.deletable
87    }
88}
89
90#[derive(Clone)]
91pub struct ViewRewriteDefinition {
92    pub query: crate::plan::QueryPlan,
93    pub output_columns: Option<Vec<String>>,
94    pub options: Vec<(String, String)>,
95    pub kind: StoredViewKind,
96    pub materialized_column_types: Vec<Option<crate::ColumnType>>,
97}
98
99impl ViewRewriteDefinition {
100    pub fn row_schema(
101        &self,
102        routines: &dyn crate::routines::RoutineResolution,
103        catalog: super::analysis::CatalogReadView,
104        resolution: super::resolution::RelationNameResolution,
105    ) -> Result<crate::RowSchema, crate::SQLError> {
106        use crate::catalog::view::{create_view_output_columns, named_view_schema};
107        use crate::SQLError;
108        if self.kind == StoredViewKind::Materialized {
109            let output_columns = self.output_columns.clone().unwrap_or_default();
110            if output_columns.len() != self.materialized_column_types.len() {
111                return Err(SQLError::Internal(
112                    "stored materialized view column metadata is inconsistent".into(),
113                ));
114            }
115            return Ok(crate::RowSchema::with_types(
116                output_columns,
117                self.materialized_column_types.clone(),
118            ));
119        }
120        let query_schema = crate::binding::analyze_query_plan_schema_with_catalog(
121            routines,
122            &self.query,
123            &[],
124            catalog,
125            resolution,
126        )?;
127        let output_columns = match &self.output_columns {
128            Some(columns) => columns.clone(),
129            None => create_view_output_columns(&query_schema, &[])?,
130        };
131        named_view_schema(&query_schema, &output_columns)
132    }
133}