1use super::{
10 display_relation, instead_of_trigger_definition, non_writable_column,
11 not_automatically_updatable, view_updatability, AutomaticViewLayer, BTreeSet,
12 ConflictActionPlan, ConflictPlan, DeletePlan, InsertPlan, MergePlan, MergeWhenPlan,
13 ReturningAliases, RowSchema, SQLError, ScalarExpr, TriggerEvent, UpdatePlan, ViewColumn,
14 ViewMutationCapabilities, ViewRewriteContext,
15};
16
17pub(super) fn layer_column<'a>(
18 layer: &'a AutomaticViewLayer,
19 name: &str,
20) -> Option<&'a ViewColumn> {
21 layer.columns.iter().find(|column| column.name == name)
22}
23
24fn unknown_view_column(layer: &AutomaticViewLayer, column: &str) -> SQLError {
25 SQLError::UnknownColumn(format!("{}.{column}", layer.canonical_name))
26}
27
28pub(super) fn duplicate_insert_column(column: &str) -> SQLError {
29 SQLError::Routine {
30 sqlstate: "42701".into(),
31 message: format!("column \"{column}\" specified more than once"),
32 }
33}
34
35pub(super) fn duplicate_assignment(column: &str) -> SQLError {
36 SQLError::Routine {
37 sqlstate: "42601".into(),
38 message: format!("multiple assignments to same column \"{column}\""),
39 }
40}
41
42fn validate_view_target_columns<'a>(
43 layer: &AutomaticViewLayer,
44 columns: impl IntoIterator<Item = &'a str>,
45 duplicate: fn(&str) -> SQLError,
46) -> Result<(), SQLError> {
47 let mut seen = BTreeSet::new();
48 for column in columns {
49 if layer_column(layer, column).is_none() {
50 return Err(unknown_view_column(layer, column));
51 }
52 if !seen.insert(column) {
53 return Err(duplicate(column));
54 }
55 }
56 Ok(())
57}
58
59pub(super) fn validate_mapped_columns(
60 columns: &[String],
61 duplicate: fn(&str) -> SQLError,
62) -> Result<(), SQLError> {
63 let mut seen = BTreeSet::new();
64 for column in columns {
65 if !seen.insert(column) {
66 return Err(duplicate(column));
67 }
68 }
69 Ok(())
70}
71
72pub(super) fn validate_insert_targets(
73 layer: &AutomaticViewLayer,
74 plan: &InsertPlan,
75) -> Result<(), SQLError> {
76 validate_view_target_columns(
77 layer,
78 plan.columns.iter().map(String::as_str),
79 duplicate_insert_column,
80 )?;
81 let Some(conflict) = &plan.on_conflict else {
82 return Ok(());
83 };
84 for column in &conflict.conflict_columns {
85 if layer_column(layer, column).is_none() {
86 return Err(unknown_view_column(layer, column));
87 }
88 }
89 if let ConflictActionPlan::Update { assignments, .. } = &conflict.action {
90 validate_view_target_columns(
91 layer,
92 assignments
93 .iter()
94 .map(|assignment| assignment.column.as_str()),
95 duplicate_assignment,
96 )?;
97 }
98 Ok(())
99}
100
101pub(super) fn validate_update_targets(
102 layer: &AutomaticViewLayer,
103 plan: &UpdatePlan,
104) -> Result<(), SQLError> {
105 validate_view_target_columns(
106 layer,
107 plan.assignments
108 .iter()
109 .map(|assignment| assignment.column.as_str()),
110 duplicate_assignment,
111 )
112}
113
114pub(super) fn writable_column(
115 layer: &AutomaticViewLayer,
116 name: &str,
117 operation: &str,
118) -> Result<String, SQLError> {
119 let column = layer_column(layer, name)
120 .ok_or_else(|| SQLError::UnknownColumn(format!("{}.{name}", layer.canonical_name)))?;
121 column
122 .writable_source_column
123 .clone()
124 .ok_or_else(|| non_writable_column(&layer.canonical_name, name, operation))
125}
126
127fn ambiguous_column(column: &str) -> SQLError {
128 SQLError::Routine {
129 sqlstate: "42702".into(),
130 message: format!("column reference \"{column}\" is ambiguous"),
131 }
132}
133
134#[derive(Clone, Copy)]
135pub(super) struct ExpressionScope<'a> {
136 pub(super) target_qualifier: &'a str,
137 pub(super) returning_aliases: Option<&'a ReturningAliases>,
138 pub(super) source: Option<&'a RowSchema>,
139 pub(super) include_excluded: bool,
140}
141
142impl ExpressionScope<'_> {
143 pub(super) fn row_image_qualifier(self, qualifier: &str) -> bool {
144 self.returning_aliases.is_some_and(|aliases| {
145 [
146 (aliases.old.as_str(), aliases.old_explicit),
147 (aliases.new.as_str(), aliases.new_explicit),
148 ]
149 .into_iter()
150 .any(|(alias, explicit)| {
151 qualifier == alias
152 && (explicit
153 || !self
154 .source
155 .is_some_and(|source| source.has_qualifier(alias)))
156 })
157 })
158 }
159
160 pub(super) fn target_qualifier(self, qualifier: &str) -> bool {
161 qualifier == self.target_qualifier
162 || (self.include_excluded && qualifier == "excluded")
163 || self.row_image_qualifier(qualifier)
164 }
165}
166
167pub(super) fn validate_view_expression(
168 expression: &ScalarExpr,
169 layer: &AutomaticViewLayer,
170 scope: ExpressionScope<'_>,
171) -> Result<(), SQLError> {
172 let mut expression = expression.clone();
173 let mut error = None;
174 crate::plan::rewrite_scalar_expression(&mut expression, &mut |node| {
175 if error.is_some() {
176 return;
177 }
178 match node {
179 ScalarExpr::Column(column) => {
180 let target = layer_column(layer, column).is_some();
181 let source = scope
182 .source
183 .is_some_and(|source| source.has_unqualified_column(column));
184 if target && (source || scope.include_excluded) {
185 error = Some(ambiguous_column(column));
186 } else if !target && !source {
187 error = Some(SQLError::UnknownColumn(column.clone()));
188 }
189 }
190 ScalarExpr::QualifiedColumn { qualifier, column }
191 if scope.target_qualifier(qualifier) && layer_column(layer, column).is_none() =>
192 {
193 error = Some(SQLError::UnknownColumn(format!("{qualifier}.{column}")));
194 }
195 _ => {}
196 }
197 });
198 error.map_or(Ok(()), Err)
199}
200
201fn public_view_columns(
202 services: ViewRewriteContext<'_>,
203 view: &str,
204) -> Result<BTreeSet<String>, SQLError> {
205 let definition = services
206 .catalog
207 .view_definition(view)?
208 .ok_or_else(|| SQLError::UnknownTable(view.to_string()))?;
209 let schema = stored_view_schema(services, &definition)?;
210 Ok(schema
211 .columns()
212 .iter()
213 .enumerate()
214 .map(|(position, column)| schema.public_name(position).unwrap_or(column).to_string())
215 .collect())
216}
217
218fn validate_public_view_expression(
219 expression: &ScalarExpr,
220 public_columns: &BTreeSet<String>,
221 scope: ExpressionScope<'_>,
222) -> Result<(), SQLError> {
223 let mut expression = expression.clone();
224 let mut error = None;
225 crate::plan::rewrite_scalar_expression(&mut expression, &mut |node| {
226 if error.is_some() {
227 return;
228 }
229 match node {
230 ScalarExpr::Column(column) => {
231 let target = public_columns.contains(column);
232 let source = scope
233 .source
234 .is_some_and(|source| source.has_unqualified_column(column));
235 if target && (source || scope.include_excluded) {
236 error = Some(ambiguous_column(column));
237 } else if !target && !source {
238 error = Some(SQLError::UnknownColumn(column.clone()));
239 }
240 }
241 ScalarExpr::QualifiedColumn { qualifier, column }
242 if scope.target_qualifier(qualifier) && !public_columns.contains(column) =>
243 {
244 error = Some(SQLError::UnknownColumn(format!("{qualifier}.{column}")));
245 }
246 _ => {}
247 }
248 });
249 error.map_or(Ok(()), Err)
250}
251
252pub(super) fn validate_public_update_contract(
253 services: ViewRewriteContext<'_>,
254 plan: &UpdatePlan,
255 source: Option<&RowSchema>,
256) -> Result<(), SQLError> {
257 let columns = public_view_columns(services, &plan.table)?;
258 let ordinary_scope = ExpressionScope {
259 target_qualifier: &plan.target_qualifier,
260 returning_aliases: None,
261 source,
262 include_excluded: false,
263 };
264 for assignment in &plan.assignments {
265 validate_public_view_expression(&assignment.value, &columns, ordinary_scope)?;
266 }
267 if let Some(predicate) = plan.predicate.as_ref() {
268 validate_public_view_expression(predicate, &columns, ordinary_scope)?;
269 }
270 let returning_scope = ExpressionScope {
271 returning_aliases: Some(&plan.returning_aliases),
272 ..ordinary_scope
273 };
274 for projection in &plan.returning {
275 validate_public_view_expression(&projection.expr, &columns, returning_scope)?;
276 }
277 Ok(())
278}
279
280pub(super) fn validate_public_delete_contract(
281 services: ViewRewriteContext<'_>,
282 plan: &DeletePlan,
283 source: Option<&RowSchema>,
284) -> Result<(), SQLError> {
285 let columns = public_view_columns(services, &plan.table)?;
286 let ordinary_scope = ExpressionScope {
287 target_qualifier: &plan.target_qualifier,
288 returning_aliases: None,
289 source,
290 include_excluded: false,
291 };
292 if let Some(predicate) = plan.predicate.as_ref() {
293 validate_public_view_expression(predicate, &columns, ordinary_scope)?;
294 }
295 let returning_scope = ExpressionScope {
296 returning_aliases: Some(&plan.returning_aliases),
297 ..ordinary_scope
298 };
299 for projection in &plan.returning {
300 validate_public_view_expression(&projection.expr, &columns, returning_scope)?;
301 }
302 Ok(())
303}
304
305pub(super) fn validate_public_insert_contract(
306 services: ViewRewriteContext<'_>,
307 plan: &InsertPlan,
308) -> Result<(), SQLError> {
309 let columns = public_view_columns(services, &plan.table)?;
310 for predicate in plan.on_conflict.iter().flat_map(|conflict| {
311 conflict
312 .expressions
313 .iter()
314 .chain(conflict.predicate.iter().map(Box::as_ref))
315 }) {
316 validate_public_view_expression(
317 predicate,
318 &columns,
319 ExpressionScope {
320 target_qualifier: &plan.target_qualifier,
321 returning_aliases: None,
322 source: None,
323 include_excluded: false,
324 },
325 )?;
326 }
327 if let Some(ConflictPlan {
328 action:
329 ConflictActionPlan::Update {
330 assignments,
331 predicate,
332 },
333 ..
334 }) = &plan.on_conflict
335 {
336 let scope = ExpressionScope {
337 target_qualifier: &plan.target_qualifier,
338 returning_aliases: None,
339 source: None,
340 include_excluded: true,
341 };
342 for assignment in assignments {
343 validate_public_view_expression(&assignment.value, &columns, scope)?;
344 }
345 if let Some(predicate) = predicate {
346 validate_public_view_expression(predicate, &columns, scope)?;
347 }
348 }
349 let scope = ExpressionScope {
350 target_qualifier: &plan.target_qualifier,
351 returning_aliases: Some(&plan.returning_aliases),
352 source: None,
353 include_excluded: false,
354 };
355 for projection in &plan.returning {
356 validate_public_view_expression(&projection.expr, &columns, scope)?;
357 }
358 Ok(())
359}
360
361pub fn validate_public_merge_contract(
362 services: ViewRewriteContext<'_>,
363 plan: &MergePlan,
364 source: &RowSchema,
365) -> Result<(), SQLError> {
366 let columns = public_view_columns(services, &plan.target)?;
367 let matched_scope = ExpressionScope {
368 target_qualifier: &plan.target_qualifier,
369 returning_aliases: None,
370 source: Some(source),
371 include_excluded: false,
372 };
373 let target_only_scope = ExpressionScope {
374 source: None,
375 ..matched_scope
376 };
377 validate_public_view_expression(&plan.join_condition, &columns, matched_scope)?;
378 for clause in &plan.when_clauses {
379 match clause {
380 MergeWhenPlan::UpdateMatched {
381 condition,
382 assignments,
383 } => {
384 if let Some(condition) = condition {
385 validate_public_view_expression(condition, &columns, matched_scope)?;
386 }
387 for assignment in assignments {
388 validate_public_view_expression(&assignment.value, &columns, matched_scope)?;
389 }
390 }
391 MergeWhenPlan::DeleteMatched { condition }
392 | MergeWhenPlan::NothingMatched { condition } => {
393 if let Some(condition) = condition {
394 validate_public_view_expression(condition, &columns, matched_scope)?;
395 }
396 }
397 MergeWhenPlan::UpdateNotMatchedBySource {
398 condition,
399 assignments,
400 } => {
401 if let Some(condition) = condition {
402 validate_public_view_expression(condition, &columns, target_only_scope)?;
403 }
404 for assignment in assignments {
405 validate_public_view_expression(
406 &assignment.value,
407 &columns,
408 target_only_scope,
409 )?;
410 }
411 }
412 MergeWhenPlan::DeleteNotMatchedBySource { condition }
413 | MergeWhenPlan::NothingNotMatchedBySource { condition } => {
414 if let Some(condition) = condition {
415 validate_public_view_expression(condition, &columns, target_only_scope)?;
416 }
417 }
418 MergeWhenPlan::InsertNotMatched { .. } | MergeWhenPlan::NothingNotMatched { .. } => {}
419 }
420 }
421 let returning_scope = ExpressionScope {
422 returning_aliases: Some(&plan.returning_aliases),
423 ..matched_scope
424 };
425 for projection in &plan.returning {
426 validate_public_view_expression(&projection.expr, &columns, returning_scope)?;
427 }
428 Ok(())
429}
430
431pub(super) fn validate_merge_targets(
432 layer: &AutomaticViewLayer,
433 plan: &MergePlan,
434) -> Result<(), SQLError> {
435 for clause in &plan.when_clauses {
436 match clause {
437 MergeWhenPlan::UpdateMatched { assignments, .. }
438 | MergeWhenPlan::UpdateNotMatchedBySource { assignments, .. } => {
439 validate_view_target_columns(
440 layer,
441 assignments
442 .iter()
443 .map(|assignment| assignment.column.as_str()),
444 duplicate_assignment,
445 )?;
446 }
447 MergeWhenPlan::InsertNotMatched { columns, .. } if !columns.is_empty() => {
448 validate_view_target_columns(
449 layer,
450 columns.iter().map(String::as_str),
451 duplicate_insert_column,
452 )?;
453 }
454 _ => {}
455 }
456 }
457 Ok(())
458}
459
460pub fn validate_public_merge_targets(
461 services: ViewRewriteContext<'_>,
462 plan: &MergePlan,
463) -> Result<(), SQLError> {
464 for clause in &plan.when_clauses {
465 match clause {
466 MergeWhenPlan::UpdateMatched { assignments, .. }
467 | MergeWhenPlan::UpdateNotMatchedBySource { assignments, .. } => {
468 let columns = assignments
469 .iter()
470 .map(|assignment| assignment.column.as_str())
471 .collect::<Vec<_>>();
472 validate_public_view_targets(services, &plan.target, columns.iter().copied())?;
473 validate_mapped_columns(
474 &columns
475 .iter()
476 .map(|column| (*column).to_string())
477 .collect::<Vec<_>>(),
478 duplicate_assignment,
479 )?;
480 }
481 MergeWhenPlan::InsertNotMatched { columns, .. } if !columns.is_empty() => {
482 validate_public_view_targets(
483 services,
484 &plan.target,
485 columns.iter().map(String::as_str),
486 )?;
487 validate_mapped_columns(columns, duplicate_insert_column)?;
488 }
489 _ => {}
490 }
491 }
492 Ok(())
493}
494
495pub(super) fn merge_action_capability_error(
496 view: &str,
497 clauses: &[MergeWhenPlan],
498 capabilities: ViewMutationCapabilities,
499) -> Option<SQLError> {
500 clauses.iter().find_map(|clause| match clause {
501 MergeWhenPlan::UpdateMatched { .. } | MergeWhenPlan::UpdateNotMatchedBySource { .. }
502 if !capabilities.updatable =>
503 {
504 Some(not_automatically_updatable(view, "UPDATE"))
505 }
506 MergeWhenPlan::DeleteMatched { .. } | MergeWhenPlan::DeleteNotMatchedBySource { .. }
507 if !capabilities.deletable =>
508 {
509 Some(not_automatically_updatable(view, "DELETE FROM"))
510 }
511 MergeWhenPlan::InsertNotMatched { .. } if !capabilities.insertable => {
512 Some(not_automatically_updatable(view, "INSERT INTO"))
513 }
514 _ => None,
515 })
516}
517
518fn validate_merge_rule_free(
519 services: ViewRewriteContext<'_>,
520 relation: &str,
521) -> Result<(), SQLError> {
522 let has_rules = [
523 crate::ast::RuleEvent::Insert,
524 crate::ast::RuleEvent::Update,
525 crate::ast::RuleEvent::Delete,
526 ]
527 .into_iter()
528 .map(|event| services.catalog.rules_for(relation, event))
529 .collect::<Result<Vec<_>, SQLError>>()?
530 .iter()
531 .any(|rules| !rules.is_empty());
532 if !has_rules {
533 return Ok(());
534 }
535 Err(SQLError::Routine {
536 sqlstate: "0A000".into(),
537 message: format!(
538 "cannot execute MERGE on relation \"{}\"",
539 display_relation(relation)
540 ),
541 })
542}
543
544fn merge_uses_event(plan: &MergePlan, event: TriggerEvent) -> bool {
545 plan.when_clauses.iter().any(|clause| match event {
546 TriggerEvent::Insert => matches!(clause, MergeWhenPlan::InsertNotMatched { .. }),
547 TriggerEvent::Update => matches!(
548 clause,
549 MergeWhenPlan::UpdateMatched { .. } | MergeWhenPlan::UpdateNotMatchedBySource { .. }
550 ),
551 TriggerEvent::Delete => matches!(
552 clause,
553 MergeWhenPlan::DeleteMatched { .. } | MergeWhenPlan::DeleteNotMatchedBySource { .. }
554 ),
555 TriggerEvent::Truncate => false,
556 })
557}
558
559#[derive(Debug, Clone, Copy, PartialEq, Eq)]
560pub enum MergeViewTargetPath {
561 AutomaticRewrite,
562 ViewTriggers,
563}
564
565pub fn merge_view_target_path(
566 services: ViewRewriteContext<'_>,
567 plan: &MergePlan,
568) -> Result<MergeViewTargetPath, SQLError> {
569 let canonical = services
570 .catalog
571 .try_resolve_view_name(&plan.target)
572 .map_err(|error| SQLError::Internal(format!("resolve MERGE view: {error}")))?
573 .ok_or_else(|| SQLError::UnknownTable(plan.target.clone()))?;
574 let definition = services
575 .catalog
576 .view_definition(&canonical)?
577 .ok_or_else(|| SQLError::UnknownTable(plan.target.clone()))?;
578 if definition.kind == crate::catalog::view::StoredViewKind::Materialized {
579 return Err(SQLError::Routine {
580 sqlstate: "0A000".into(),
581 message: format!(
582 "cannot execute MERGE on relation \"{}\"",
583 display_relation(&canonical)
584 ),
585 });
586 }
587 validate_merge_rule_free(services, &canonical)?;
588 let automatic = view_updatability(services, &canonical)?.automatic;
589 let insert_trigger = instead_of_trigger_definition(services, &canonical, TriggerEvent::Insert)?;
590 let update_trigger = instead_of_trigger_definition(services, &canonical, TriggerEvent::Update)?;
591 let delete_trigger = instead_of_trigger_definition(services, &canonical, TriggerEvent::Delete)?;
592 let supported = ViewMutationCapabilities {
593 insertable: automatic.insertable || insert_trigger,
594 updatable: automatic.updatable || update_trigger,
595 deletable: automatic.deletable || delete_trigger,
596 };
597 if let Some(error) = merge_action_capability_error(&canonical, &plan.when_clauses, supported) {
598 return Err(error);
599 }
600 let mut uses_automatic = false;
601 let mut uses_trigger = false;
602 let mut has_action = false;
603 for (event, trigger) in [
604 (TriggerEvent::Insert, insert_trigger),
605 (TriggerEvent::Update, update_trigger),
606 (TriggerEvent::Delete, delete_trigger),
607 ] {
608 if !merge_uses_event(plan, event) {
609 continue;
610 }
611 has_action = true;
612 uses_trigger |= trigger;
613 uses_automatic |= !trigger;
614 }
615 if uses_trigger && uses_automatic {
616 return Err(SQLError::Routine {
617 sqlstate: "0A000".into(),
618 message: format!(
619 "cannot merge into view \"{}\"",
620 display_relation(&canonical)
621 ),
622 });
623 }
624 if uses_trigger || !has_action {
625 Ok(MergeViewTargetPath::ViewTriggers)
626 } else {
627 Ok(MergeViewTargetPath::AutomaticRewrite)
628 }
629}
630
631pub(super) fn validate_public_view_targets<'a>(
632 services: ViewRewriteContext<'_>,
633 view: &str,
634 columns: impl IntoIterator<Item = &'a str>,
635) -> Result<(), SQLError> {
636 let definition = services
637 .catalog
638 .view_definition(view)?
639 .ok_or_else(|| SQLError::UnknownTable(view.to_string()))?;
640 let schema = stored_view_schema(services, &definition)?;
641 for column in columns {
642 if !schema.has_unqualified_column(column) {
643 return Err(SQLError::UnknownColumn(format!("{view}.{column}")));
644 }
645 }
646 Ok(())
647}
648
649pub(super) fn validate_direct_view_rule_path(
650 services: ViewRewriteContext<'_>,
651 view: &str,
652 event: crate::ast::RuleEvent,
653 operation: &str,
654) -> Result<(), SQLError> {
655 let rules = services.catalog.rules_for(view, event)?;
656 let has_conditional_instead = rules
657 .iter()
658 .any(|rule| rule.definition.instead && rule.definition.condition.is_some());
659 let has_unconditional_instead = rules
660 .iter()
661 .any(|rule| rule.definition.instead && rule.definition.condition.is_none());
662 if has_conditional_instead && !has_unconditional_instead {
663 return Err(not_automatically_updatable(view, operation));
664 }
665 Ok(())
666}
667
668use super::stored_view_schema;