Skip to main content

uqa_sql/schema/
view_creation.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! View target namespaces, replacement row types, and materialized-view declarations.
8use crate::{ast::RelationPersistence, RowSchema, SQLError};
9use uqa_core::RelationIdentity;
10
11pub trait ViewCreationNamespace {
12    /// `RangeVarGetAndCheckCreationNamespace` for the view: its canonical name and the persistence it takes there.
13    fn relation_target(
14        &self,
15        name: &str,
16        persistence: RelationPersistence,
17    ) -> Result<(String, RelationPersistence), SQLError>;
18}
19
20/// Whether `DefineView` makes a view temporary because its query uses a temporary relation, which it reports with a notice before it resolves the view's namespace.
21pub fn view_becomes_temporary(
22    persistence: RelationPersistence,
23    uses_temporary_relation: bool,
24) -> bool {
25    uses_temporary_relation && persistence == RelationPersistence::Permanent
26}
27
28/// The notice `DefineView` reports for a view that its query makes temporary, naming the view as written without its schema.
29pub fn temporary_view_notice(name: &str) -> Result<crate::SQLNotice, SQLError> {
30    let (_, relation) = RelationIdentity::parse_reference(name).map_err(SQLError::Unsupported)?;
31    Ok(crate::SQLNotice::notice(format!(
32        "view \"{relation}\" will be a temporary view"
33    )))
34}
35
36/// The view's canonical name and persistence: a view whose query uses a temporary relation is temporary, and then goes where a temporary relation of its name goes.
37pub fn view_creation_target(
38    namespace: &dyn ViewCreationNamespace,
39    name: &str,
40    persistence: RelationPersistence,
41    uses_temporary_relation: bool,
42) -> Result<(String, RelationPersistence), SQLError> {
43    let persistence = if view_becomes_temporary(persistence, uses_temporary_relation) {
44        RelationPersistence::Temporary
45    } else {
46        persistence
47    };
48    namespace.relation_target(name, persistence)
49}
50
51/// `DefineVirtualRelation`: a relation that already has the name is a collision, or with `OR REPLACE` must be a view. Both diagnostics name the relation without its schema.
52pub fn replacement_is_view(
53    name: &str,
54    kind: Option<&str>,
55    or_replace: bool,
56) -> Result<bool, SQLError> {
57    let local = || {
58        uqa_core::RelationIdentity::from_legacy_name(name)
59            .map_or_else(|_| name.to_string(), |relation| relation.name)
60    };
61    match kind {
62        Some(_) if !or_replace => Err(SQLError::Routine {
63            sqlstate: "42P07".into(),
64            message: format!("relation \"{}\" already exists", local()),
65        }),
66        Some("view") => Ok(true),
67        Some(_) => Err(SQLError::Routine {
68            sqlstate: "42809".into(),
69            message: format!("\"{}\" is not a view", local()),
70        }),
71        None => Ok(false),
72    }
73}
74
75pub fn validate_replacement_schema(old: &RowSchema, new: &RowSchema) -> Result<(), SQLError> {
76    if new.len() < old.len() {
77        return Err(SQLError::Routine {
78            sqlstate: "42P16".into(),
79            message: "cannot drop columns from view".into(),
80        });
81    }
82    for position in 0..old.len() {
83        let old_name = old
84            .public_name(position)
85            .unwrap_or(&old.columns()[position]);
86        let new_name = new
87            .public_name(position)
88            .unwrap_or(&new.columns()[position]);
89        if old_name != new_name {
90            return Err(SQLError::Routine {
91                sqlstate: "42P16".into(),
92                message: format!(
93                    "cannot change name of view column \"{old_name}\" to \"{new_name}\""
94                ),
95            });
96        }
97        if old.column_type(position) != new.column_type(position) {
98            return Err(SQLError::Routine {
99                sqlstate: "42P16".into(),
100                message: format!("cannot change data type of view column \"{old_name}\""),
101            });
102        }
103    }
104    Ok(())
105}
106
107#[cfg(test)]
108mod tests;