uqa_sql/catalog/
resolution.rs1use crate::catalog::roles::RoleReference;
10use crate::SQLError;
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct EffectiveSearchPath {
15 pub schemas: Vec<String>,
16 pub creation_namespace: Option<String>,
17}
18
19#[derive(Clone)]
21pub struct RelationNameResolution {
22 pub search_path: Vec<String>,
23 pub temporary_schema: String,
24 pub temporary_namespace_allocated: bool,
25 pub current_user: RoleReference,
26 pub lookup_mode: RelationLookupMode,
27}
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub enum RelationLookupMode {
32 Dynamic,
33 Bound,
34}
35
36impl RelationNameResolution {
37 pub fn search_path(&self) -> &[String] {
38 &self.search_path
39 }
40
41 pub fn search_path_contains(&self, schema: &str) -> bool {
42 self.search_path.iter().any(|candidate| candidate == schema)
43 }
44
45 pub fn current_user(&self) -> &RoleReference {
46 &self.current_user
47 }
48
49 pub fn lookup_mode(&self) -> RelationLookupMode {
50 self.lookup_mode
51 }
52
53 pub fn qualified_schema(&self, name: &str) -> Result<Option<(String, String)>, SQLError> {
54 let (schema, _) = uqa_core::RelationIdentity::parse_reference(name).map_err(|error| {
55 SQLError::Internal(format!("resolve catalog relation `{name}`: {error}"))
56 })?;
57 Ok(schema.map(|schema| {
58 let resolved = if schema == "pg_temp" {
59 self.temporary_schema.clone()
60 } else {
61 schema.clone()
62 };
63 (schema, resolved)
64 }))
65 }
66
67 pub fn set_lookup_mode(&mut self, lookup_mode: RelationLookupMode) -> RelationLookupMode {
68 std::mem::replace(&mut self.lookup_mode, lookup_mode)
69 }
70
71 pub fn raw_relation_lookup_candidates(
72 &self,
73 name: &str,
74 ) -> Result<Vec<uqa_core::RelationIdentity>, SQLError> {
75 let (schema, relation) =
76 uqa_core::RelationIdentity::parse_reference(name).map_err(|error| {
77 SQLError::Internal(format!("resolve catalog relation `{name}`: {error}"))
78 })?;
79 if let Some(schema) = schema {
80 let schema = if schema == "pg_temp" {
81 self.temporary_schema.clone()
82 } else {
83 schema
84 };
85 return Ok(vec![uqa_core::RelationIdentity::new(schema, relation)]);
86 }
87 Ok(candidates::unqualified_candidates(
88 &self.temporary_schema,
89 &self.search_path,
90 &relation,
91 ))
92 }
93}
94
95#[derive(Clone, Debug, Eq, PartialEq)]
97pub enum RelationResolution {
98 Found(String, &'static str),
99 MissingRelation,
100 MissingSchema(String),
101}
102
103impl RelationResolution {
104 pub fn into_found(self) -> Option<(String, &'static str)> {
106 match self {
107 Self::Found(name, kind) => Some((name, kind)),
108 Self::MissingRelation | Self::MissingSchema(_) => None,
109 }
110 }
111}
112
113pub fn missing_relation_notice(name: &str) -> Result<String, crate::SQLError> {
114 let (_, local_name) =
115 uqa_core::RelationIdentity::parse_reference(name).map_err(crate::SQLError::Internal)?;
116 Ok(format!(
117 "relation \"{local_name}\" does not exist, skipping"
118 ))
119}
120
121pub fn resolve_relation_rename_source(
123 resolution: RelationResolution,
124 name: &str,
125 if_exists: bool,
126 notice: &mut dyn FnMut(&str),
127) -> Result<Option<(String, &'static str)>, crate::SQLError> {
128 match resolution {
129 RelationResolution::Found(canonical, kind) => Ok(Some((canonical, kind))),
130 RelationResolution::MissingSchema(_) | RelationResolution::MissingRelation if if_exists => {
131 notice(&missing_relation_notice(name)?);
132 Ok(None)
133 }
134 RelationResolution::MissingSchema(schema) => Err(crate::SQLError::Routine {
135 sqlstate: "3F000".into(),
136 message: format!("schema \"{schema}\" does not exist"),
137 }),
138 RelationResolution::MissingRelation => Err(crate::SQLError::Routine {
139 sqlstate: "42P01".into(),
140 message: format!("relation \"{name}\" does not exist"),
141 }),
142 }
143}
144
145pub mod candidates;
146
147pub mod creation;
148
149pub fn described_relation_name(
151 relation: &uqa_core::RelationIdentity,
152 resolve: impl FnOnce(&str) -> Result<RelationResolution, SQLError>,
153) -> Result<String, SQLError> {
154 let local = crate::expr::quote_ident(&relation.name);
155 let visible = match resolve(&local)? {
156 RelationResolution::Found(found, _) => uqa_core::RelationIdentity::from_legacy_name(&found)
157 .is_ok_and(|found| found == *relation),
158 RelationResolution::MissingRelation | RelationResolution::MissingSchema(_) => false,
159 };
160 Ok(if visible {
161 local
162 } else {
163 format!("{}.{local}", crate::expr::quote_ident(&relation.schema))
164 })
165}