1use super::walk_schema_expr_mut;
10use crate::ast::{ColumnType, Expr};
11use crate::expr::EngineHook;
12use crate::plan::{QueryPlan, UnifiedPlan};
13use crate::{SQLError, ScalarExpr};
14use uqa_core::{ArrayValue, Value};
15
16pub trait OidAliasInput {
18 fn resolve_oid_alias_input(&self, ty: &ColumnType, name: &str)
19 -> Result<Option<i64>, SQLError>;
20}
21
22impl<T: EngineHook + ?Sized> OidAliasInput for T {
23 fn resolve_oid_alias_input(
24 &self,
25 ty: &ColumnType,
26 name: &str,
27 ) -> Result<Option<i64>, SQLError> {
28 match ty {
29 ColumnType::Regclass => EngineHook::resolve_regclass_input(self, name),
30 ColumnType::Regtype => EngineHook::resolve_regtype_input(self, name),
31 ColumnType::Regproc => EngineHook::resolve_regproc(self, name),
32 ColumnType::Regprocedure => EngineHook::resolve_regprocedure_input(self, name),
33 ColumnType::Regnamespace => EngineHook::resolve_regnamespace(self, name),
34 ColumnType::Regcollation => EngineHook::resolve_regcollation(self, name),
35 ColumnType::Regrole => EngineHook::resolve_regrole(self, name),
36 other => Err(SQLError::Internal(format!(
37 "{} is not an OID alias type read at analysis",
38 other.sql_name()
39 ))),
40 }
41 }
42}
43
44fn is_alias(ty: &ColumnType) -> bool {
46 matches!(
47 ty,
48 ColumnType::Regclass
49 | ColumnType::Regtype
50 | ColumnType::Regproc
51 | ColumnType::Regprocedure
52 | ColumnType::Regcollation
53 | ColumnType::Regnamespace
54 )
55}
56
57fn alias_type(ty: &str) -> Option<(ColumnType, bool)> {
59 if !ty
60 .as_bytes()
61 .windows(3)
62 .any(|window| window.eq_ignore_ascii_case(b"reg"))
63 {
64 return None;
65 }
66 match ColumnType::from_sql_name(ty).ok()? {
67 ColumnType::Array(element) if is_alias(&element) => Some((*element, true)),
68 element if is_alias(&element) => Some((element, false)),
69 _ => None,
70 }
71}
72
73fn missing_object(ty: &ColumnType, name: &str) -> SQLError {
75 if matches!(ty, ColumnType::Regcollation) {
76 return SQLError::Routine {
77 sqlstate: "42704".into(),
78 message: format!("collation \"{name}\" for encoding \"UTF8\" does not exist"),
79 };
80 }
81 let (sqlstate, object) = match ty {
82 ColumnType::Regclass => ("42P01", "relation"),
83 ColumnType::Regtype => ("42704", "type"),
84 ColumnType::Regrole => ("42704", "role"),
85 ColumnType::Regproc | ColumnType::Regprocedure => ("42883", "function"),
86 _ => ("3F000", "schema"),
87 };
88 SQLError::Routine {
89 sqlstate: sqlstate.into(),
90 message: format!("{object} \"{name}\" does not exist"),
91 }
92}
93
94fn read_name<C: OidAliasInput + ?Sized>(
96 catalog: &C,
97 ty: &ColumnType,
98 name: &str,
99) -> Result<i64, SQLError> {
100 catalog
101 .resolve_oid_alias_input(ty, name)?
102 .ok_or_else(|| missing_object(ty, name))
103}
104
105fn read_constant<C: OidAliasInput + ?Sized>(
107 catalog: &C,
108 ty: &ColumnType,
109 text: &str,
110 array: bool,
111) -> Result<Value, SQLError> {
112 if !array {
113 return read_name(catalog, ty, text).map(Value::Int);
114 }
115 let array = crate::expr::parse_pg_array_literal(text)?;
116 let lower_bounds = array.lower_bounds().to_vec();
117 let mut elements = array.into_elements();
118 read_array_elements(catalog, ty, &mut elements)?;
119 ArrayValue::with_lower_bounds(elements, lower_bounds)
120 .map(Value::Array)
121 .ok_or_else(|| {
122 SQLError::Internal(format!("{} array literal lost its shape", ty.sql_name()))
123 })
124}
125
126fn read_array_elements<C: OidAliasInput + ?Sized>(
127 catalog: &C,
128 ty: &ColumnType,
129 elements: &mut [Value],
130) -> Result<(), SQLError> {
131 for element in elements {
132 match element {
133 Value::Null => {}
134 Value::Str(name) => *element = Value::Int(read_name(catalog, ty, name)?),
135 Value::List(nested) => read_array_elements(catalog, ty, nested)?,
136 other => {
137 return Err(SQLError::TypeMismatch(format!(
138 "cannot read {other:?} as {}",
139 ty.sql_name(),
140 )))
141 }
142 }
143 }
144 Ok(())
145}
146
147pub(crate) fn read_unknown_constant(
149 catalog: &dyn OidAliasInput,
150 ty: &ColumnType,
151 text: &str,
152) -> Result<Option<Value>, SQLError> {
153 match ty {
154 ColumnType::Array(element)
155 if is_alias(element) || matches!(element.as_ref(), ColumnType::Regrole) =>
156 {
157 read_constant(catalog, element, text, true).map(Some)
158 }
159 scalar if is_alias(scalar) || matches!(scalar, ColumnType::Regrole) => {
160 read_constant(catalog, scalar, text, false).map(Some)
161 }
162 _ => Ok(None),
163 }
164}
165
166fn constant_type(ty: ColumnType, array: bool) -> ColumnType {
168 if array {
169 ColumnType::Array(Box::new(ty))
170 } else {
171 ty
172 }
173}
174
175pub fn read_oid_alias_constants<C: OidAliasInput + ?Sized>(
177 catalog: &C,
178 expression: &mut Expr,
179) -> Result<(), SQLError> {
180 let mut failure = None;
181 let outcome = walk_schema_expr_mut(expression, &mut |node| {
182 let Expr::Cast { expr, ty, .. } = node else {
183 return Ok(());
184 };
185 let Some((alias, array)) = alias_type(ty) else {
186 return Ok(());
187 };
188 let Expr::Literal(Value::Str(text)) = expr.as_ref() else {
189 return Ok(());
190 };
191 match read_constant(catalog, &alias, text, array) {
192 Ok(value) => {
193 **expr = Expr::TypedLiteral {
194 composite_source: None,
195 value,
196 ty: constant_type(alias, array).catalog_name(),
197 };
198 Ok(())
199 }
200 Err(error) => {
201 failure = Some(error);
202 Err(String::new())
203 }
204 }
205 });
206 match (outcome, failure) {
207 (Ok(()), _) => Ok(()),
208 (Err(_), Some(error)) => Err(error),
209 (Err(message), None) => Err(SQLError::Internal(message)),
210 }
211}
212
213fn sequence_argument_mut(expression: &mut ScalarExpr) -> Option<&mut ScalarExpr> {
215 let ScalarExpr::Func { name, args, .. } = expression else {
216 return None;
217 };
218 if !is_sequence_function(name) {
219 return None;
220 }
221 args.first_mut()
222 .filter(|argument| matches!(argument, ScalarExpr::Literal(Value::Str(_))))
223}
224
225fn sequence_argument(expression: &ScalarExpr) -> Option<&str> {
226 let ScalarExpr::Func { name, args, .. } = expression else {
227 return None;
228 };
229 if !is_sequence_function(name) {
230 return None;
231 }
232 match args.first() {
233 Some(ScalarExpr::Literal(Value::Str(text))) => Some(text),
234 _ => None,
235 }
236}
237
238fn is_sequence_function(name: &str) -> bool {
239 let lower = name.to_ascii_lowercase();
240 let local = lower.strip_prefix("pg_catalog.").unwrap_or(&lower);
241 matches!(local, "nextval" | "currval" | "setval")
242 && (!lower.contains('.') || lower.starts_with("pg_catalog."))
243}
244
245fn read_scalar_constant<C: OidAliasInput + ?Sized>(
247 catalog: &C,
248 expression: &mut ScalarExpr,
249 keep_relations: bool,
250 failure: &mut Option<SQLError>,
251) {
252 if failure.is_some() {
253 return;
254 }
255 if let Some(argument) = sequence_argument_mut(expression) {
256 let ScalarExpr::Literal(Value::Str(text)) = &*argument else {
257 unreachable!("sequence argument is an unknown literal");
258 };
259 match read_constant(catalog, &ColumnType::Regclass, text, false) {
260 Ok(value) => {
261 if !keep_relations {
262 *argument = ScalarExpr::TypedLiteral {
263 composite_source: None,
264 value,
265 ty: ColumnType::Regclass.catalog_name(),
266 bound_type: Some(ColumnType::Regclass),
267 parameter_index: None,
268 };
269 }
270 }
271 Err(error) => *failure = Some(error),
272 }
273 return;
274 }
275 let ScalarExpr::Cast { expr, ty, .. } = expression else {
276 return;
277 };
278 let Some((alias, array)) = alias_type(ty) else {
279 return;
280 };
281 let ScalarExpr::Literal(Value::Str(text)) = expr.as_ref() else {
282 return;
283 };
284 match read_constant(catalog, &alias, text, array) {
285 Ok(value) => {
286 if keep_relations && matches!(alias, ColumnType::Regclass) {
287 return;
288 }
289 let bound_type = constant_type(alias, array);
290 **expr = ScalarExpr::TypedLiteral {
291 composite_source: None,
292 value,
293 ty: bound_type.catalog_name(),
294 bound_type: Some(bound_type),
295 parameter_index: None,
296 };
297 }
298 Err(error) => *failure = Some(error),
299 }
300}
301
302pub fn read_oid_alias_constants_in_plan<C: OidAliasInput + ?Sized>(
304 catalog: &C,
305 plan: &mut QueryPlan,
306) -> Result<(), SQLError> {
307 let mut failure = None;
308 plan.rewrite_scalar_expressions(&mut |root| {
309 root.visit_mut(&mut |expression| {
310 read_scalar_constant(catalog, expression, false, &mut failure);
311 });
312 });
313 failure.map_or(Ok(()), Err)
314}
315
316pub fn read_prepared_oid_alias_constants<C: OidAliasInput + ?Sized>(
318 catalog: &C,
319 plan: &mut UnifiedPlan,
320) -> Result<(), SQLError> {
321 let mut failure = None;
322 plan.rewrite_scalar_expressions(&mut |root| {
323 root.visit_mut(&mut |expression| {
324 read_scalar_constant(catalog, expression, false, &mut failure);
325 });
326 });
327 failure.map_or(Ok(()), Err)
328}
329
330pub fn check_statement_oid_alias_constants<C: OidAliasInput + ?Sized>(
332 catalog: &C,
333 plan: &UnifiedPlan,
334) -> Result<(), SQLError> {
335 let mut failure = None;
336 plan.visit_scalar_expressions(&mut |root| {
337 root.visit(&mut |expression| {
338 if failure.is_some() {
339 return;
340 }
341 if let Some(text) = sequence_argument(expression) {
342 if let Err(error) = read_constant(catalog, &ColumnType::Regclass, text, false) {
343 failure = Some(error);
344 }
345 return;
346 }
347 let ScalarExpr::Cast { expr, ty, .. } = expression else {
348 return;
349 };
350 let Some((alias, array)) = alias_type(ty) else {
351 return;
352 };
353 let ScalarExpr::Literal(Value::Str(text)) = expr.as_ref() else {
354 return;
355 };
356 if let Err(error) = read_constant(catalog, &alias, text, array) {
357 failure = Some(error);
358 }
359 });
360 });
361 failure.map_or(Ok(()), Err)
362}