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