1use rupg_common::{Error, Result, SqlState};
4use rupg_pgcatalog::builtin;
5use rupg_types::{Value, oid};
6
7use crate::Analyzer;
8use crate::expr::{CastForm, Expr, ExprKind, Func, FuncForm};
9use crate::types;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
13pub enum Context {
14 Implicit,
16 Assignment,
18 Plpgsql,
20 Explicit,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum Path {
27 None,
28 Func(u32),
30 Relabel,
32 ArrayCoerce,
34 ViaIo,
36}
37
38pub fn find_path(target: u32, source: u32, context: Context) -> Path {
40 let (target, source) = (types::base(target), types::base(source));
41 if source == target {
42 return Path::Relabel;
43 }
44 if source == oid::INTERNAL || target == oid::INTERNAL {
45 return Path::None;
46 }
47 if let Some(cast) = builtin::cast(source, target) {
48 let allowed = match cast.context {
49 b'i' => Context::Implicit,
50 b'a' => Context::Assignment,
51 _ => Context::Explicit,
52 };
53 if context < allowed {
54 return Path::None;
55 }
56 return match cast.method {
57 b'f' => Path::Func(cast.func),
58 b'i' => Path::ViaIo,
59 _ => Path::Relabel,
60 };
61 }
62 if target != oid::OIDVECTOR && target != oid::INT2VECTOR {
63 let (target_element, source_element) = (types::element(target), types::element(source));
64 if target_element != 0
65 && source_element != 0
66 && find_path(target_element, source_element, context) != Path::None
67 {
68 return Path::ArrayCoerce;
69 }
70 }
71 if (context >= Context::Assignment && types::category(target) == types::STRING)
73 || (context >= Context::Explicit && types::category(source) == types::STRING)
74 || context == Context::Plpgsql
75 {
76 Path::ViaIo
77 } else {
78 Path::None
79 }
80}
81
82pub fn explicit_cast(expr: Expr, target: u32) -> Option<Expr> {
84 if expr.ty == target {
85 return Some(expr);
86 }
87 let location = expr.location;
88 match find_path(target, expr.ty, Context::Explicit) {
89 Path::None | Path::ArrayCoerce => None,
90 Path::Relabel => Some(Expr {
91 kind: ExprKind::Relabel(Box::new(expr), CastForm::Explicit),
92 ty: target,
93 typmod: -1,
94 location,
95 }),
96 Path::ViaIo => Some(Expr {
97 kind: ExprKind::CoerceViaIo(Box::new(expr), CastForm::Explicit),
98 ty: target,
99 typmod: -1,
100 location,
101 }),
102 Path::Func(func) => Some(build_cast(
103 expr,
104 func,
105 target,
106 -1,
107 Context::Explicit,
108 FuncForm::ExplicitCast,
109 location,
110 )),
111 }
112}
113
114pub fn can_coerce(inputs: &[u32], targets: &[u32], context: Context) -> bool {
116 let mut generic = false;
117 for (&input, &target) in inputs.iter().zip(targets) {
118 if input == target {
119 continue;
120 }
121 if input == oid::INTERNAL || target == oid::INTERNAL {
122 return false;
123 }
124 if target == oid::ANY {
125 continue;
126 }
127 if types::is_polymorphic(target) {
128 generic = true;
129 continue;
130 }
131 if input == oid::UNKNOWN {
132 continue;
133 }
134 if (target == oid::RECORD && types::is_complex(input))
136 || (target == oid::RECORD_ARRAY && types::is_complex(types::element(input)))
137 {
138 continue;
139 }
140 if input == oid::RECORD && types::is_complex(types::base(target)) {
142 continue;
143 }
144 if find_path(target, input, context) != Path::None {
145 continue;
146 }
147 return false;
148 }
149 !generic || crate::poly::consistent(inputs, targets)
150}
151
152pub(crate) fn common_of(tys: &[u32]) -> std::result::Result<u32, (u32, u32, usize)> {
154 let Some(&first) = tys.first() else { return Ok(oid::TEXT) };
155 if first != oid::UNKNOWN && tys.iter().all(|&t| t == first) {
156 return Ok(first);
157 }
158 let mut ptype = types::base(first);
159 let (mut pcategory, mut ppreferred) = types::category_preferred(ptype);
160 for (i, &next) in tys.iter().enumerate().skip(1) {
161 let ntype = types::base(next);
162 if ntype == oid::UNKNOWN || ntype == ptype {
163 continue;
164 }
165 let (ncategory, npreferred) = types::category_preferred(ntype);
166 if ptype == oid::UNKNOWN {
167 (ptype, pcategory, ppreferred) = (ntype, ncategory, npreferred);
168 } else if ncategory != pcategory {
169 return Err((ptype, ntype, i));
170 } else if !ppreferred
171 && can_coerce(&[ptype], &[ntype], Context::Implicit)
172 && !can_coerce(&[ntype], &[ptype], Context::Implicit)
173 {
174 (ptype, pcategory, ppreferred) = (ntype, ncategory, npreferred);
175 }
176 }
177 Ok(if ptype == oid::UNKNOWN { oid::TEXT } else { ptype })
178}
179
180fn typmod_function(ty: u32) -> Option<(u32, bool)> {
182 let (ty, array) = match types::element(ty) {
183 0 => (ty, false),
184 element => (element, true),
185 };
186 builtin::cast(ty, ty).filter(|cast| cast.func != 0).map(|cast| (cast.func, array))
187}
188
189impl Analyzer<'_> {
190 pub(crate) fn coerce_to_target(
192 &mut self,
193 expr: Expr,
194 target: u32,
195 typmod: i32,
196 context: Context,
197 location: Option<usize>,
198 ) -> Result<Option<Expr>> {
199 if !can_coerce(&[expr.ty], &[target], context) {
200 return Ok(None);
201 }
202 let input = (expr.ty, expr.typmod);
203 let expr = self.coerce(expr, target, typmod, context, location)?;
204 let length = typmod >= 0 && (expr.typmod != typmod || expr.ty != target);
206 let hide =
207 length && (expr.ty, expr.typmod) != input && !matches!(expr.kind, ExprKind::Const(_));
208 let mut expr = self.coerce_typmod(expr, target, typmod, context, location);
209 if hide
210 && let ExprKind::Func(Func { args, .. }) = &mut expr.kind
211 && let Some(arg) = args.first_mut()
212 {
213 hide_cast(arg);
214 }
215 Ok(Some(expr))
216 }
217
218 pub(crate) fn coerce(
220 &mut self,
221 expr: Expr,
222 target: u32,
223 typmod: i32,
224 context: Context,
225 location: Option<usize>,
226 ) -> Result<Expr> {
227 let input = expr.ty;
228 if target == input {
229 return Ok(expr);
230 }
231 if matches!(
232 target,
233 oid::ANY
234 | oid::ANYELEMENT
235 | oid::ANYNONARRAY
236 | oid::ANYCOMPATIBLE
237 | oid::ANYCOMPATIBLENONARRAY
238 ) {
239 return Ok(expr);
240 }
241 if types::is_polymorphic(target) && input != oid::UNKNOWN {
242 return Ok(expr);
243 }
244 if (target == oid::RECORD && types::is_complex(input))
246 || (target == oid::RECORD_ARRAY && types::is_complex(types::element(input)))
247 {
248 return Ok(expr);
249 }
250 if input == oid::RECORD && types::is_complex(types::base(target)) {
251 return self.coerce_record_to_complex(expr, target, context, location);
252 }
253 if input == oid::UNKNOWN {
254 if let ExprKind::Const(value) = &expr.kind {
255 let (base, base_typmod) = types::base_typmod(target, typmod);
257 let input_typmod = if base == oid::INTERVAL { base_typmod } else { -1 };
258 let value = match value {
259 Value::Text(text) => {
260 let text = text.clone();
261 self.env.input_at(base, &text, input_typmod, expr.location).map_err(
263 |e| match expr.location {
264 Some(at) => e.with_position(Some(at)),
265 None => e,
266 },
267 )?
268 }
269 _ => Value::Null,
270 };
271 let constant = Expr::constant(value, base, input_typmod, expr.location);
272 return Ok(coerce_to_domain(constant, target, context, location, false));
273 }
274 if let ExprKind::Param(n) = expr.kind
275 && let Some(ty) = self.param_type(n, target, expr.location)?
276 {
277 return Ok(Expr {
278 kind: ExprKind::Param(n),
279 ty,
280 typmod: -1,
281 location: expr.location,
282 });
283 }
284 }
285 let (form, cast) = if context == Context::Explicit {
286 (FuncForm::ExplicitCast, CastForm::Explicit)
287 } else {
288 (FuncForm::ImplicitCast, CastForm::Implicit)
289 };
290 let (base, base_typmod) = types::base_typmod(target, typmod);
292 match find_path(target, input, context) {
293 Path::None => Err(Error::internal(format!(
294 "failed to find conversion function from {} to {}",
295 types::name(input),
296 types::name(target)
297 ))),
298 Path::Relabel if base != target => {
299 Ok(coerce_to_domain(expr, target, context, location, false))
300 }
301 Path::Relabel => Ok(Expr {
302 kind: ExprKind::Relabel(Box::new(expr), cast),
303 ty: target,
304 typmod: -1,
305 location,
306 }),
307 Path::ViaIo => {
308 let expr = Expr {
309 kind: ExprKind::CoerceViaIo(Box::new(expr), cast),
310 ty: base,
311 typmod: -1,
312 location,
313 };
314 Ok(coerce_to_domain(expr, target, context, location, true))
315 }
316 Path::Func(func) => {
317 let expr = build_cast(expr, func, base, base_typmod, context, form, location);
318 Ok(coerce_to_domain(expr, target, context, location, true))
319 }
320 Path::ArrayCoerce
322 if find_path(types::element(target), types::element(input), context)
323 == Path::Relabel =>
324 {
325 Ok(Expr {
326 kind: ExprKind::Relabel(Box::new(expr), cast),
327 ty: target,
328 typmod: -1,
329 location,
330 })
331 }
332 Path::ArrayCoerce => {
333 let test = Expr {
334 kind: ExprKind::CaseTest,
335 ty: types::element(input),
336 typmod: expr.typmod,
337 location: None,
338 };
339 let element = self
340 .coerce_to_target(test, types::element(target), typmod, context, location)?
341 .ok_or_else(|| {
342 Error::internal("failed to coerce array element type as expected")
343 })?;
344 let typmod = element.typmod;
345 Ok(Expr {
346 kind: ExprKind::ArrayCoerce {
347 arg: Box::new(expr),
348 element: Box::new(element),
349 form: cast,
350 },
351 ty: target,
352 typmod,
353 location,
354 })
355 }
356 }
357 }
358
359 fn coerce_record_to_complex(
361 &mut self,
362 expr: Expr,
363 target: u32,
364 context: Context,
365 location: Option<usize>,
366 ) -> Result<Expr> {
367 let at = location.or(expr.location);
368 let base = types::base(target);
369 let cannot = |detail: Option<String>| {
370 let error = Error::new(
371 SqlState::CANNOT_COERCE,
372 format!("cannot cast type record to {}", types::name(target)),
373 );
374 match detail {
375 Some(detail) => error.with_detail(detail),
376 None => error,
377 }
378 .at_opt(at)
379 };
380 let ExprKind::Row(args, _) = expr.kind else { return Err(cannot(None)) };
381 let columns = self.row_columns(base).ok_or_else(|| cannot(None))?;
382 if args.len() < columns.len() {
383 return Err(cannot(Some("Input has too few columns.".into())));
384 }
385 if args.len() > columns.len() {
386 return Err(cannot(Some("Input has too many columns.".into())));
387 }
388 let mut fields = Vec::with_capacity(args.len());
389 for (i, (arg, column)) in args.into_iter().zip(&columns).enumerate() {
390 let (from, arg_at) = (arg.ty, arg.location);
391 let field = self.coerce_to_target(arg, column.ty, column.typmod, context, None)?;
392 let Some(field) = field else {
393 return Err(Error::new(
394 SqlState::CANNOT_COERCE,
395 format!("cannot cast type {} to {}", types::name(from), types::name(column.ty)),
396 )
397 .with_detail(format!(
398 "Cannot cast type record to {} in column {}.",
399 types::name(target),
400 i + 1
401 ))
402 .at_opt(location.or(arg_at)));
403 };
404 fields.push(field);
405 }
406 let form =
407 if context == Context::Explicit { CastForm::Explicit } else { CastForm::Implicit };
408 let row = Expr { kind: ExprKind::Row(fields, form), ty: base, typmod: -1, location };
409 Ok(if base == target {
410 row
411 } else {
412 coerce_to_domain(row, target, context, location, false)
413 })
414 }
415
416 pub(crate) fn coerce_typmod(
418 &mut self,
419 expr: Expr,
420 target: u32,
421 typmod: i32,
422 context: Context,
423 location: Option<usize>,
424 ) -> Expr {
425 let explicit = context == Context::Explicit;
426 typmod_cast(expr, target, typmod, context, explicit, location)
427 }
428}
429
430fn typmod_cast(
432 expr: Expr,
433 target: u32,
434 typmod: i32,
435 context: Context,
436 explicit: bool,
437 location: Option<usize>,
438) -> Expr {
439 if typmod < 0 || expr.typmod == typmod && expr.ty == target {
440 return expr;
441 }
442 let (form, cast) = if explicit {
443 (FuncForm::ExplicitCast, CastForm::Explicit)
444 } else {
445 (FuncForm::ImplicitCast, CastForm::Implicit)
446 };
447 match typmod_function(target) {
448 Some((func, false)) => build_cast(expr, func, target, typmod, context, form, location),
449 Some((func, true)) => {
451 let ty = types::element(target);
452 let test = Expr { kind: ExprKind::CaseTest, ty, typmod: expr.typmod, location: None };
453 let element = build_cast(test, func, ty, typmod, context, form, location);
454 Expr {
455 kind: ExprKind::ArrayCoerce {
456 arg: Box::new(expr),
457 element: Box::new(element),
458 form: cast,
459 },
460 ty: target,
461 typmod,
462 location,
463 }
464 }
465 None => {
466 let mut expr = expr;
467 if let ExprKind::Const(_) = expr.kind {
468 expr.typmod = typmod;
469 expr
470 } else {
471 Expr { kind: ExprKind::Relabel(Box::new(expr), cast), ty: target, typmod, location }
472 }
473 }
474 }
475}
476
477impl Analyzer<'_> {
478 pub(crate) fn common_type(&self, exprs: &[Expr], context: &str) -> Result<u32> {
480 let tys: Vec<u32> = exprs.iter().map(|e| e.ty).collect();
481 common_of(&tys).map_err(|(p, n, i)| {
482 Error::new(
483 SqlState::DATATYPE_MISMATCH,
484 format!(
485 "{context} types {} and {} cannot be matched",
486 types::name(p),
487 types::name(n)
488 ),
489 )
490 .at_opt(exprs[i].place())
491 })
492 }
493
494 pub(crate) fn coerce_to_common(
496 &mut self,
497 expr: Expr,
498 target: u32,
499 context: &str,
500 ) -> Result<Expr> {
501 if expr.ty == target {
502 return Ok(expr);
503 }
504 if !can_coerce(&[expr.ty], &[target], Context::Implicit) {
505 return Err(Error::new(
506 SqlState::CANNOT_COERCE,
507 format!(
508 "{context} could not convert type {} to {}",
509 types::name(expr.ty),
510 types::name(target)
511 ),
512 )
513 .at_opt(expr.place()));
514 }
515 self.coerce(expr, target, -1, Context::Implicit, None)
516 }
517
518 pub(crate) fn coerce_to_boolean(&mut self, expr: Expr, construct: &str) -> Result<Expr> {
520 self.coerce_to_specific(expr, oid::BOOL, construct)
521 }
522
523 pub(crate) fn coerce_to_specific(
525 &mut self,
526 expr: Expr,
527 target: u32,
528 construct: &str,
529 ) -> Result<Expr> {
530 let ty = expr.ty;
531 let place = expr.place();
532 let coerced = if ty == target {
533 Some(expr)
534 } else {
535 self.coerce_to_target(expr, target, -1, Context::Assignment, None)?
536 };
537 match coerced {
538 Some(expr) if expr.first_set_call().is_some() => Err(Error::new(
539 SqlState::DATATYPE_MISMATCH,
540 format!("argument of {construct} must not return a set"),
541 )
542 .at_opt(expr.place())),
543 Some(expr) => Ok(expr),
544 None => Err(Error::new(
545 SqlState::DATATYPE_MISMATCH,
546 format!(
547 "argument of {construct} must be type {}, not type {}",
548 types::name(target),
549 types::name(ty)
550 ),
551 )
552 .at_opt(place)),
553 }
554 }
555}
556
557fn coerce_to_domain(
559 mut expr: Expr,
560 target: u32,
561 context: Context,
562 location: Option<usize>,
563 hide: bool,
564) -> Expr {
565 let (base, typmod) = types::base_typmod(target, -1);
566 if base == target {
567 return expr;
568 }
569 if hide {
570 hide_cast(&mut expr);
571 }
572 let expr = typmod_cast(expr, base, typmod, context, false, location);
573 let form = if context == Context::Explicit { CastForm::Explicit } else { CastForm::Implicit };
574 Expr { kind: ExprKind::CoerceToDomain(Box::new(expr), form), ty: target, typmod: -1, location }
575}
576
577pub(crate) fn hide_cast(expr: &mut Expr) {
579 match &mut expr.kind {
580 ExprKind::Func(Func { form, .. })
581 if matches!(form, FuncForm::ExplicitCast | FuncForm::ImplicitCast) =>
582 {
583 *form = FuncForm::ImplicitCast;
584 }
585 ExprKind::Relabel(_, form)
586 | ExprKind::CoerceViaIo(_, form)
587 | ExprKind::ArrayCoerce { form, .. }
588 | ExprKind::Row(_, form)
589 | ExprKind::CoerceToDomain(_, form) => *form = CastForm::Implicit,
590 _ => {}
591 }
592}
593
594fn build_cast(
596 expr: Expr,
597 func: u32,
598 target: u32,
599 typmod: i32,
600 context: Context,
601 form: FuncForm,
602 location: Option<usize>,
603) -> Expr {
604 let nargs = builtin::proc_by_oid(func).map_or(1, |p| p.nargs);
605 let mut args = vec![expr];
606 if nargs >= 2 {
607 args.push(Expr::constant(Value::Int4(typmod), oid::INT4, -1, None));
608 }
609 if nargs >= 3 {
610 args.push(Expr::constant(Value::Bool(context == Context::Explicit), oid::BOOL, -1, None));
611 }
612 let typmod = if nargs >= 2 && typmod_function(target).is_some_and(|(f, _)| f == func) {
614 typmod
615 } else {
616 -1
617 };
618 Expr {
619 kind: ExprKind::Func(Func { oid: func, args, form, variadic: false, retset: false }),
620 ty: target,
621 typmod,
622 location,
623 }
624}
625
626pub(crate) trait AtOpt {
628 fn at_opt(self, location: Option<usize>) -> Self;
629}
630
631impl AtOpt for Error {
632 fn at_opt(self, location: Option<usize>) -> Error {
633 match location {
634 Some(at) => self.at(at),
635 None => self,
636 }
637 }
638}
639
640#[cfg(test)]
641mod tests {
642 use super::*;
643
644 #[test]
645 fn paths() {
646 assert_eq!(find_path(oid::INT8, oid::INT4, Context::Implicit), Path::Func(481));
647 assert_eq!(find_path(oid::INT4, oid::INT8, Context::Implicit), Path::None);
648 assert_eq!(find_path(oid::INT4, oid::INT8, Context::Assignment), Path::Func(480));
649 assert_eq!(find_path(oid::TEXT, oid::VARCHAR, Context::Implicit), Path::Relabel);
650 assert_eq!(find_path(oid::TEXT, oid::INT4, Context::Implicit), Path::None);
651 assert_eq!(find_path(oid::TEXT, oid::INT4, Context::Assignment), Path::ViaIo);
652 assert_eq!(find_path(oid::INT4, oid::TEXT, Context::Explicit), Path::ViaIo);
653 assert_eq!(
654 find_path(oid::INT8_ARRAY, oid::INT4_ARRAY, Context::Implicit),
655 Path::ArrayCoerce
656 );
657 assert_eq!(find_path(oid::OID_ARRAY, oid::OIDVECTOR, Context::Implicit), Path::ArrayCoerce);
658 assert_eq!(find_path(oid::OIDVECTOR, oid::OID_ARRAY, Context::Explicit), Path::None);
659 assert!(can_coerce(&[oid::UNKNOWN, oid::INT2], &[oid::INT4, oid::INT4], Context::Implicit));
660 assert!(!can_coerce(&[oid::TEXT], &[oid::INT4], Context::Implicit));
661 assert_eq!(typmod_function(oid::VARCHAR).map(|(_, a)| a), Some(false));
662 assert_eq!(typmod_function(oid::INT4), None);
663 }
664}