uqa_sql/routines/result_check/
anonymous.rs1use super::{
10 validate_sql_function_record, ColumnType, RoutineTypeCatalog, SQLError,
11 SQLFunctionResultColumn, SQLFunctionResultKind,
12};
13
14#[derive(Clone, Copy, PartialEq, Eq)]
15enum RecordResultContext {
16 SQLTuple,
17 Procedural,
18 Trigger,
19}
20
21pub fn validate_anonymous_record_result(
23 types: &dyn RoutineTypeCatalog,
24 source: &[Option<ColumnType>],
25 target: &[SQLFunctionResultColumn],
26 sql_kind: Option<SQLFunctionResultKind>,
27) -> Result<(), SQLError> {
28 if sql_kind == Some(SQLFunctionResultKind::Value) {
29 return validate_sql_function_record(
30 types,
31 source,
32 &target
33 .iter()
34 .map(|column| column.ty.clone())
35 .collect::<Vec<_>>(),
36 );
37 }
38 validate_record_fields(
39 types,
40 source,
41 target,
42 if sql_kind == Some(SQLFunctionResultKind::Tuple) {
43 RecordResultContext::SQLTuple
44 } else {
45 RecordResultContext::Procedural
46 },
47 )
48}
49
50pub fn validate_trigger_record_result(
52 types: &dyn RoutineTypeCatalog,
53 source: &[Option<ColumnType>],
54 target: &[SQLFunctionResultColumn],
55) -> Result<(), SQLError> {
56 validate_record_fields(types, source, target, RecordResultContext::Trigger)
57}
58
59fn validate_record_fields(
60 types: &dyn RoutineTypeCatalog,
61 source: &[Option<ColumnType>],
62 target: &[SQLFunctionResultColumn],
63 context: RecordResultContext,
64) -> Result<(), SQLError> {
65 let tuple = context == RecordResultContext::SQLTuple;
66 if source.len() != target.len() {
67 let detail = if tuple {
68 format!(
69 "Final statement returns too {} columns.",
70 if source.len() < target.len() {
71 "few"
72 } else {
73 "many"
74 }
75 )
76 } else {
77 format!(
78 "Number of returned columns ({}) does not match expected column count ({}).",
79 source.len(),
80 target.len()
81 )
82 };
83 return Err(mismatch(context, detail));
84 }
85 for (index, (source, target)) in source.iter().zip(target).enumerate() {
86 if tuple
87 && source
88 .as_ref()
89 .is_none_or(|source| crate::assignment_type_compatible(source, &target.ty))
90 {
91 continue;
92 }
93 if !tuple
94 && source
95 .as_ref()
96 .is_some_and(|source| same_identity(source, &target.ty))
97 {
98 continue;
99 }
100 let source = source
101 .as_ref()
102 .map_or_else(|| Ok("unknown".into()), |source| types.format_type(source))?;
103 let expected = types.format_type(&target.ty)?;
104 let detail = if tuple {
105 format!(
106 "Final statement returns {source} instead of {expected} at column {}.",
107 index + 1
108 )
109 } else {
110 format!("Returned type {source} does not match expected type {expected} in column \"{}\" (position {}).", target.name, index + 1)
111 };
112 return Err(mismatch(context, detail));
113 }
114 Ok(())
115}
116
117fn same_identity(source: &ColumnType, target: &ColumnType) -> bool {
118 use crate::catalog::type_metadata::{pg_type_modifier, pg_type_oid};
119 pg_type_oid(source) == pg_type_oid(target)
120 && (pg_type_modifier(target) < 0 || pg_type_modifier(source) == pg_type_modifier(target))
121}
122
123fn mismatch(context: RecordResultContext, detail: String) -> SQLError {
124 SQLError::Diagnostic {
125 sqlstate: if context == RecordResultContext::SQLTuple {
126 "42P13"
127 } else {
128 "42804"
129 }
130 .into(),
131 message: match context {
132 RecordResultContext::SQLTuple => {
133 "return type mismatch in function declared to return record"
134 }
135 RecordResultContext::Procedural => {
136 "returned record type does not match expected record type"
137 }
138 RecordResultContext::Trigger => {
139 "returned row structure does not match the structure of the triggering table"
140 }
141 }
142 .into(),
143 detail: Some(detail),
144 hint: None,
145 }
146}