Skip to main content

uqa_sql/routines/lifecycle/
names.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Search-path and namespace authorization rules for routine names.
8
9use crate::catalog::roles::RoleReference;
10use crate::{catalog::security::BoundSchemaSecurity, SQLError};
11use uqa_core::RelationIdentity;
12
13pub trait RoutineNameCatalog {
14    fn schema_security(&self, schema: &str) -> Option<BoundSchemaSecurity>;
15    fn current_role(&self) -> RoleReference;
16    fn search_path(&self) -> Vec<String>;
17    fn require_schema_usage(&self, schema: &str, role: &RoleReference) -> Result<(), SQLError>;
18    fn schema_has_usage(&self, schema: &str, role: &RoleReference) -> bool;
19}
20
21pub fn routine_lookup_keys(
22    catalog: &dyn RoutineNameCatalog,
23    name: &str,
24) -> Result<Vec<String>, SQLError> {
25    let (schema, local_name) =
26        RelationIdentity::parse_reference(name).map_err(|error| SQLError::Routine {
27            sqlstate: "42602".into(),
28            message: format!("invalid routine name `{name}`: {error}"),
29        })?;
30    if let Some(schema) = schema {
31        if catalog.schema_security(&schema).is_none() {
32            return Err(SQLError::Routine {
33                sqlstate: "3F000".into(),
34                message: format!("schema \"{schema}\" does not exist"),
35            });
36        }
37        catalog.require_schema_usage(&schema, &catalog.current_role())?;
38        return Ok(vec![
39            RelationIdentity::new(schema, local_name).qualified_name()
40        ]);
41    }
42    let current_user = catalog.current_role();
43    let search_path = catalog.search_path();
44    Ok(search_path
45        .into_iter()
46        .filter(|schema| {
47            catalog.schema_security(schema).is_some()
48                && catalog.schema_has_usage(schema, &current_user)
49        })
50        .map(|schema| RelationIdentity::new(schema, &local_name).qualified_name())
51        .collect())
52}
53
54/// Defer namespace errors during recursive argument analysis; definitive binding checks again.
55pub fn routine_lookup_keys_for_analysis(
56    catalog: &dyn RoutineNameCatalog,
57    name: &str,
58) -> Result<Option<Vec<String>>, SQLError> {
59    match routine_lookup_keys(catalog, name) {
60        Err(error) if crate::routines::is_routine_namespace_lookup_error(&error) => Ok(None),
61        result => result.map(Some),
62    }
63}