Skip to main content

uqa_sql/catalog/events/
definition.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Trigger and rewrite-rule declarations, relation identity checks, and stored expression binding.
8
9mod context;
10mod rules;
11mod triggers;
12use crate::{
13    catalog::resolution::{RelationLookupMode, RelationResolution},
14    SQLError,
15};
16pub use context::{EventAnalysisContext, EventForeignPrivileges, EventRelationCatalog};
17use uqa_core::RelationIdentity;
18
19impl EventAnalysisContext<'_> {
20    pub fn event_relation_from_resolution(
21        requested: &str,
22        resolution: RelationResolution,
23    ) -> Result<(RelationIdentity, &'static str), SQLError> {
24        let (canonical, kind) = match resolution {
25            RelationResolution::Found(canonical, kind)
26                if matches!(kind, "table" | "view" | "materialized view") =>
27            {
28                (canonical, kind)
29            }
30            // `table_openrv` refuses indexes and composite types before the event's own kind checks.
31            RelationResolution::Found(canonical, kind @ ("index" | "composite type")) => {
32                return Err(crate::catalog::analysis::UnopenableRelation {
33                    name: RelationIdentity::from_legacy_name(&canonical)
34                        .map_or(canonical, |relation| relation.name),
35                    kinds: if kind == "index" {
36                        "indexes"
37                    } else {
38                        "composite types"
39                    },
40                }
41                .error());
42            }
43            RelationResolution::Found(_, _) | RelationResolution::MissingRelation => {
44                return Err(SQLError::UnknownTable(requested.to_string()));
45            }
46            RelationResolution::MissingSchema(schema) => {
47                return Err(SQLError::Routine {
48                    sqlstate: "3F000".into(),
49                    message: format!("schema \"{schema}\" does not exist"),
50                });
51            }
52        };
53        let relation = RelationIdentity::from_legacy_name(&canonical).map_err(|error| {
54            SQLError::Internal(format!(
55                "decode resolved event relation `{canonical}`: {error}"
56            ))
57        })?;
58        Ok((relation, kind))
59    }
60
61    pub fn resolve_event_relation_kind(
62        &self,
63        name: &str,
64        lookup_mode: RelationLookupMode,
65    ) -> Result<(RelationIdentity, &'static str), SQLError> {
66        let resolution = match lookup_mode {
67            RelationLookupMode::Dynamic => self.relations.resolve_visible_relation_kind(name)?,
68            RelationLookupMode::Bound => self.relations.resolve_bound_relation_kind(name)?,
69        };
70        Self::event_relation_from_resolution(name, resolution)
71    }
72}
73
74impl EventAnalysisContext<'_> {
75    pub fn ensure_event_relation_owner(
76        &self,
77        relation: &RelationIdentity,
78        error_kind: Option<&str>,
79    ) -> Result<(), SQLError> {
80        let (owner, relation_kind) = self.catalog.event_relation_owner(relation)?;
81        if self
82            .authority
83            .current_user_has_role_identity_privileges(owner)
84        {
85            return Ok(());
86        }
87        Err(SQLError::Routine {
88            sqlstate: "42501".into(),
89            message: format!(
90                "must be owner of {} {}",
91                error_kind.unwrap_or(relation_kind),
92                relation.name
93            ),
94        })
95    }
96}
97
98#[cfg(test)]
99mod tests;
100
101pub fn duplicate_object(kind: &str, name: &str, table: &str) -> SQLError {
102    SQLError::Routine {
103        sqlstate: "42710".into(),
104        message: format!("{kind} \"{name}\" for relation \"{table}\" already exists"),
105    }
106}
107
108pub fn undefined_object(kind: &str, name: &str, table: &str) -> SQLError {
109    SQLError::Routine {
110        sqlstate: "42704".into(),
111        message: format!("{kind} \"{name}\" for table \"{table}\" does not exist"),
112    }
113}
114
115pub fn undefined_rule(name: &str, relation: &str) -> SQLError {
116    SQLError::Routine {
117        sqlstate: "42704".into(),
118        message: format!("rule \"{name}\" for relation \"{relation}\" does not exist"),
119    }
120}
121
122pub mod lookup;
123
124pub mod dependents;
125pub mod rewrites;