1use super::{
9 rules::analysis::RuleAnalysisContext,
10 view_rewrite::context::{stored_view_schema, ViewRewriteContext},
11};
12use crate::{
13 catalog::stored_view::StoredView,
14 plan::{DeletePlan, MergePlan, MergeWhenPlan, UpdatePlan},
15 ColumnType, SQLError, ScalarExpr,
16};
17use std::collections::BTreeSet;
18use uqa_core::Value;
19
20pub struct ViewMutationTarget {
21 pub canonical_name: String,
22 pub definition: StoredView,
23 pub columns: Vec<String>,
24 pub types: Vec<Option<ColumnType>>,
25}
26
27pub fn resolve_view_target(
28 context: ViewRewriteContext<'_>,
29 name: &str,
30) -> Result<ViewMutationTarget, SQLError> {
31 let canonical_name = context
32 .catalog
33 .try_resolve_view_name(name)
34 .map_err(|error| SQLError::Internal(format!("resolve DML view `{name}`: {error}")))?
35 .ok_or_else(|| SQLError::UnknownTable(name.to_string()))?;
36 let definition = context
37 .authorization
38 .view_definition(&canonical_name)?
39 .ok_or_else(|| SQLError::UnknownTable(name.to_string()))?;
40 if definition.kind != crate::catalog::view::StoredViewKind::View {
41 return Err(SQLError::Routine {
42 sqlstate: "42809".into(),
43 message: format!("relation \"{canonical_name}\" is not a view"),
44 });
45 }
46 let schema = stored_view_schema(context, &definition.rewrite_definition())?;
47 let columns = schema
48 .columns()
49 .iter()
50 .enumerate()
51 .map(|(position, column)| schema.public_name(position).unwrap_or(column).to_string())
52 .collect::<Vec<_>>();
53 let types = (0..columns.len())
54 .map(|position| schema.column_type(position).cloned())
55 .collect();
56 Ok(ViewMutationTarget {
57 canonical_name,
58 definition,
59 columns,
60 types,
61 })
62}
63
64fn collect_view_expression_columns(
65 expression: &ScalarExpr,
66 columns: &mut BTreeSet<String>,
67) -> bool {
68 expression.collect_columns(columns)
69}
70
71pub fn required_view_update_columns(
72 analysis: RuleAnalysisContext<'_>,
73 target: &ViewMutationTarget,
74 stmt: &UpdatePlan,
75) -> Result<Option<BTreeSet<String>>, SQLError> {
76 let Some(mut columns) = super::rules::analysis::relation_rule_row_columns(
77 analysis,
78 &target.canonical_name,
79 crate::ast::RuleEvent::Update,
80 )?
81 else {
82 return Ok(None);
83 };
84 columns.extend(
85 stmt.assignments
86 .iter()
87 .flat_map(|assignment| assignment.target.column_names())
88 .map(str::to_owned),
89 );
90 for expression in stmt
91 .assignments
92 .iter()
93 .flat_map(crate::plan::AssignmentPlan::expressions)
94 {
95 if !collect_view_expression_columns(expression, &mut columns) {
96 return Ok(None);
97 }
98 }
99 if let Some(predicate) = stmt.predicate.as_ref() {
100 if !collect_view_expression_columns(predicate, &mut columns) {
101 return Ok(None);
102 }
103 }
104 Ok(Some(columns))
105}
106
107pub fn required_view_delete_columns(
108 analysis: RuleAnalysisContext<'_>,
109 target: &ViewMutationTarget,
110 stmt: &DeletePlan,
111) -> Result<Option<BTreeSet<String>>, SQLError> {
112 let Some(mut columns) = super::rules::analysis::relation_rule_row_columns(
113 analysis,
114 &target.canonical_name,
115 crate::ast::RuleEvent::Delete,
116 )?
117 else {
118 return Ok(None);
119 };
120 if let Some(predicate) = stmt.predicate.as_ref() {
121 if !collect_view_expression_columns(predicate, &mut columns) {
122 return Ok(None);
123 }
124 }
125 Ok(Some(columns))
126}
127
128pub fn target_columns(
129 target: &ViewMutationTarget,
130 explicit: &[crate::ast::AssignmentTarget<ScalarExpr>],
131 operation: &str,
132) -> Result<Vec<crate::ast::AssignmentTarget<ScalarExpr>>, SQLError> {
133 let columns = if explicit.is_empty() {
134 target.columns.iter().cloned().map(Into::into).collect()
135 } else {
136 explicit.to_vec()
137 };
138 crate::assignment::targets::validate_repeated_targets(&columns, operation == "INSERT")?;
139 for column in &columns {
140 if !target.columns.contains(&column.column) {
141 return Err(SQLError::UnknownColumn(format!(
142 "{}.{}",
143 target.canonical_name, column.column
144 )));
145 }
146 }
147 if columns.is_empty() {
148 return Err(SQLError::Unsupported(format!(
149 "{operation} against a zero-column view is not supported"
150 )));
151 }
152 Ok(columns)
153}
154
155pub fn view_qualification_references_target(
156 target: &ViewMutationTarget,
157 target_qualifier: &str,
158 predicate: Option<&ScalarExpr>,
159) -> bool {
160 let Some(predicate) = predicate else {
161 return false;
162 };
163 if super::expr_contains_subquery(predicate) {
164 return true;
165 }
166 if super::expr_qualifiers(predicate).iter().any(|qualifier| {
167 qualifier.eq_ignore_ascii_case(target_qualifier)
168 || qualifier.eq_ignore_ascii_case(&target.canonical_name)
169 }) {
170 return true;
171 }
172 if !super::expr_has_unqualified_column(predicate) {
173 return false;
174 }
175 let mut columns = BTreeSet::new();
176 !predicate.collect_columns(&mut columns)
177 || columns.iter().any(|column| target.columns.contains(column))
178}
179
180pub fn coerce_view_value(
181 assignment: &dyn crate::assignment::AssignmentContext,
182 target: &ViewMutationTarget,
183 position: usize,
184 value: Value,
185) -> Result<Value, SQLError> {
186 match target.types[position].as_ref() {
187 Some(ty) => crate::assignment::conversion::convert_value_to_column_type_with_context(
188 assignment, value, ty,
189 ),
190 None => Ok(value),
191 }
192}
193
194pub fn validate_view_merge_targets(
195 target: &ViewMutationTarget,
196 plan: &MergePlan,
197) -> Result<(), SQLError> {
198 for clause in &plan.when_clauses {
199 match clause {
200 MergeWhenPlan::UpdateMatched { assignments, .. }
201 | MergeWhenPlan::UpdateNotMatchedBySource { assignments, .. } => {
202 let columns = assignments
203 .iter()
204 .flat_map(|assignment| assignment.target.targets().iter().cloned())
205 .collect::<Vec<_>>();
206 let _ = target_columns(target, &columns, "UPDATE")?;
207 }
208 MergeWhenPlan::InsertNotMatched {
209 columns, values, ..
210 } => {
211 let implicit = columns.is_empty();
212 let columns = target_columns(target, columns, "INSERT")?;
213 if values.len() > columns.len() || (!implicit && values.len() != columns.len()) {
214 return Err(SQLError::TypeMismatch(format!(
215 "MERGE INSERT row width {} != column count {}",
216 values.len(),
217 columns.len()
218 )));
219 }
220 }
221 _ => {}
222 }
223 }
224 Ok(())
225}
226
227pub fn validate_view_merge_contract(
228 rewrite: ViewRewriteContext<'_>,
229 target: &ViewMutationTarget,
230 plan: &MergePlan,
231 params: &[crate::SQLParam],
232 bindings: &crate::binding::context::BindingContext<'_>,
233) -> Result<(), SQLError> {
234 validate_view_merge_targets(target, plan)?;
235 let source = crate::binding::analyze_source_plan_schema(
236 rewrite.catalog,
237 &plan.source,
238 params,
239 bindings,
240 None,
241 )?;
242 super::view_rewrite::validate_public_merge_targets(rewrite, plan)?;
243 super::view_rewrite::validate_public_merge_contract(rewrite, plan, &source)?;
244 super::returning::validate_returning_alias_relations(
245 &plan.target_qualifier,
246 &plan.returning_aliases,
247 Some(&source),
248 )?;
249 let target_schema = crate::RowSchema::with_qualified_types(
250 &plan.target_qualifier,
251 target.columns.clone(),
252 target.types.clone(),
253 );
254 super::merge::validate_merge_action_scopes(
255 rewrite.catalog,
256 plan,
257 &target_schema,
258 &source,
259 params,
260 bindings,
261 )
262}