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
59#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
60pub enum StoredViewKind {
61    #[default]
62    View,
63    Materialized,
64}
65
66#[derive(Debug, Clone, Copy, Default)]
67pub struct ViewMutationCapabilities {
68    pub insertable: bool,
69    pub updatable: bool,
70    pub deletable: bool,
71}
72impl ViewMutationCapabilities {
73    pub const fn fully_updatable(self) -> bool {
74        self.updatable && self.deletable
75    }
76}
77
78#[derive(Clone)]
79pub struct ViewRewriteDefinition {
80    pub query: crate::plan::QueryPlan,
81    pub output_columns: Option<Vec<String>>,
82    pub options: Vec<(String, String)>,
83    pub kind: StoredViewKind,
84    pub materialized_column_types: Vec<Option<crate::ColumnType>>,
85}
86
87impl ViewRewriteDefinition {
88    pub fn row_schema(
89        &self,
90        routines: &dyn crate::routines::RoutineResolution,
91        catalog: super::analysis::CatalogReadView,
92        resolution: super::resolution::RelationNameResolution,
93    ) -> Result<crate::RowSchema, crate::SQLError> {
94        use crate::catalog::view::{create_view_output_columns, named_view_schema};
95        use crate::SQLError;
96        if self.kind == StoredViewKind::Materialized {
97            let output_columns = self.output_columns.clone().unwrap_or_default();
98            if output_columns.len() != self.materialized_column_types.len() {
99                return Err(SQLError::Internal(
100                    "stored materialized view column metadata is inconsistent".into(),
101                ));
102            }
103            return Ok(crate::RowSchema::with_types(
104                output_columns,
105                self.materialized_column_types.clone(),
106            ));
107        }
108        let query_schema = crate::binding::analyze_query_plan_schema_with_catalog(
109            routines,
110            &self.query,
111            &[],
112            catalog,
113            resolution,
114        )?;
115        let output_columns = match &self.output_columns {
116            Some(columns) => columns.clone(),
117            None => create_view_output_columns(&query_schema, &[])?,
118        };
119        named_view_schema(&query_schema, &output_columns)
120    }
121}