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    fn temporary_schema_name(&self) -> String;
13    fn temporary_target(&self, name: &str) -> Result<String, SQLError>;
14    fn persistent_target(&self, name: &str) -> Result<String, SQLError>;
15}
16
17pub fn view_creation_target(
18    namespace: &dyn ViewCreationNamespace,
19    name: &str,
20    persistence: RelationPersistence,
21    uses_temporary_relation: bool,
22) -> Result<(String, RelationPersistence), SQLError> {
23    let persistence = if uses_temporary_relation {
24        RelationPersistence::Temporary
25    } else {
26        persistence
27    };
28    let name = if persistence == RelationPersistence::Temporary {
29        let (schema, _) = RelationIdentity::parse_reference(name).map_err(SQLError::Unsupported)?;
30        if uses_temporary_relation
31            && schema.as_deref().is_some_and(|schema| {
32                schema != "pg_temp" && schema != namespace.temporary_schema_name()
33            })
34        {
35            namespace.persistent_target(name)?;
36        }
37        namespace.temporary_target(name)?
38    } else {
39        namespace.persistent_target(name)?
40    };
41    Ok((name, persistence))
42}
43
44pub fn replacement_is_view(
45    name: &str,
46    kind: Option<&str>,
47    or_replace: bool,
48) -> Result<bool, SQLError> {
49    match kind {
50        Some(_) if !or_replace => Err(SQLError::Routine {
51            sqlstate: "42P07".into(),
52            message: format!("relation \"{name}\" already exists"),
53        }),
54        Some("view") => Ok(true),
55        Some(kind) => Err(SQLError::Routine {
56            sqlstate: "42809".into(),
57            message: format!("\"{name}\" is not a view; it is a {kind}"),
58        }),
59        None => Ok(false),
60    }
61}
62
63pub fn validate_replacement_schema(old: &RowSchema, new: &RowSchema) -> Result<(), SQLError> {
64    if new.len() < old.len() {
65        return Err(SQLError::Routine {
66            sqlstate: "42P16".into(),
67            message: "cannot drop columns from view".into(),
68        });
69    }
70    for position in 0..old.len() {
71        let old_name = old
72            .public_name(position)
73            .unwrap_or(&old.columns()[position]);
74        let new_name = new
75            .public_name(position)
76            .unwrap_or(&new.columns()[position]);
77        if old_name != new_name {
78            return Err(SQLError::Routine {
79                sqlstate: "42P16".into(),
80                message: format!(
81                    "cannot change name of view column \"{old_name}\" to \"{new_name}\""
82                ),
83            });
84        }
85        if old.column_type(position) != new.column_type(position) {
86            return Err(SQLError::Routine {
87                sqlstate: "42P16".into(),
88                message: format!("cannot change data type of view column \"{old_name}\""),
89            });
90        }
91    }
92    Ok(())
93}
94
95#[cfg(test)]
96mod tests;