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