Skip to main content

uqa_sql/schema/relation_alteration/
relocation.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! `ALTER ... SET SCHEMA` destinations after the owning relation and namespace are locked.
8
9use super::RelationAlterNames;
10use crate::{ast::RelationPersistence, SQLError};
11use uqa_core::RelationIdentity;
12
13pub fn declared_target(
14    source: &RelationIdentity,
15    schema: &str,
16) -> Result<RelationIdentity, SQLError> {
17    let (qualifier, schema) =
18        RelationIdentity::parse_reference(schema).map_err(SQLError::Internal)?;
19    if qualifier.is_some() {
20        return Err(SQLError::Internal(
21            "SET SCHEMA produced a qualified schema".into(),
22        ));
23    }
24    Ok(RelationIdentity::new(schema, &source.name))
25}
26
27/// `CheckSetNamespace` precedes the unchanged-namespace shortcut and relation-name collision checks.
28pub fn validate_target(
29    catalog: &dyn RelationAlterNames,
30    source: &RelationIdentity,
31    target: &RelationIdentity,
32    persistence: RelationPersistence,
33    temporary_schema: &str,
34) -> Result<bool, SQLError> {
35    let temporary = |schema: &str| {
36        schema == temporary_schema
37            || schema.starts_with("pg_temp_")
38            || schema.starts_with("pg_toast_temp_")
39    };
40    if persistence == RelationPersistence::Temporary
41        || temporary(&source.schema)
42        || temporary(&target.schema)
43    {
44        return Err(SQLError::Routine {
45            sqlstate: "0A000".into(),
46            message: "cannot move objects into or out of temporary schemas".into(),
47        });
48    }
49    if source.schema == "pg_toast" || target.schema == "pg_toast" {
50        return Err(SQLError::Routine {
51            sqlstate: "0A000".into(),
52            message: "cannot move objects into or out of TOAST schema".into(),
53        });
54    }
55    if source == target {
56        return Ok(false);
57    }
58    ensure_name_available(catalog, target)?;
59    Ok(true)
60}
61
62pub fn ensure_name_available(
63    catalog: &dyn RelationAlterNames,
64    target: &RelationIdentity,
65) -> Result<(), SQLError> {
66    if catalog
67        .relation_kind_at(&target.qualified_name())
68        .map_err(SQLError::Internal)?
69        .is_some()
70    {
71        return Err(SQLError::Routine {
72            sqlstate: "42P07".into(),
73            message: format!(
74                "relation \"{}\" already exists in schema \"{}\"",
75                target.name, target.schema
76            ),
77        });
78    }
79    Ok(())
80}
81
82#[cfg(test)]
83mod tests;