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 targets: impl IntoIterator<Item = &'a crate::ast::AssignmentTarget<ScalarExpr>>,
45 duplicate: fn(&str) -> SQLError,
46) -> Result<(), SQLError> {
47 let mut seen = std::collections::BTreeMap::new();
48 for target in targets {
49 if layer_column(layer, &target.column).is_none() {
50 return Err(unknown_view_column(layer, &target.column));
51 }
52 let whole = target.is_whole_column();
53 if seen
54 .insert(&target.column, whole)
55 .is_some_and(|previous| previous || whole)
56 {
57 return Err(duplicate(&target.column));
58 }
59 }
60 Ok(())
61}
62
63pub(super) fn validate_mapped_columns(
64 targets: &[crate::ast::AssignmentTarget<ScalarExpr>],
65 duplicate: fn(&str) -> SQLError,
66) -> Result<(), SQLError> {
67 let mut seen = std::collections::BTreeMap::new();
68 for target in targets {
69 let whole = target.is_whole_column();
70 if seen
71 .insert(&target.column, whole)
72 .is_some_and(|previous| previous || whole)
73 {
74 return Err(duplicate(&target.column));
75 }
76 }
77 Ok(())
78}
79
80pub(super) fn validate_insert_targets(
81 layer: &AutomaticViewLayer,
82 plan: &InsertPlan,
83) -> Result<(), SQLError> {
84 validate_view_target_columns(layer, plan.columns.iter(), duplicate_insert_column)?;
85 let Some(conflict) = &plan.on_conflict else {
86 return Ok(());
87 };
88 for column in &conflict.conflict_columns {
89 if layer_column(layer, column).is_none() {
90 return Err(unknown_view_column(layer, column));
91 }
92 }
93 if let ConflictActionPlan::Update { assignments, .. } = &conflict.action {
94 validate_view_target_columns(
95 layer,
96 assignments.iter().map(|assignment| &assignment.target),
97 duplicate_assignment,
98 )?;
99 }
100 Ok(())
101}
102
103pub(super) fn validate_update_targets(
104 layer: &AutomaticViewLayer,
105 plan: &UpdatePlan,
106) -> Result<(), SQLError> {
107 validate_view_target_columns(
108 layer,
109 plan.assignments.iter().map(|assignment| &assignment.target),
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 for expression in assignment.expressions() {
266 validate_public_view_expression(expression, &columns, ordinary_scope)?;
267 }
268 }
269 if let Some(predicate) = plan.predicate.as_ref() {
270 validate_public_view_expression(predicate, &columns, ordinary_scope)?;
271 }
272 let returning_scope = ExpressionScope {
273 returning_aliases: Some(&plan.returning_aliases),
274 ..ordinary_scope
275 };
276 for projection in &plan.returning {
277 validate_public_view_expression(&projection.expr, &columns, returning_scope)?;
278 }
279 Ok(())
280}
281
282pub(super) fn validate_public_delete_contract(
283 services: ViewRewriteContext<'_>,
284 plan: &DeletePlan,
285 source: Option<&RowSchema>,
286) -> Result<(), SQLError> {
287 let columns = public_view_columns(services, &plan.table)?;
288 let ordinary_scope = ExpressionScope {
289 target_qualifier: &plan.target_qualifier,
290 returning_aliases: None,
291 source,
292 include_excluded: false,
293 };
294 if let Some(predicate) = plan.predicate.as_ref() {
295 validate_public_view_expression(predicate, &columns, ordinary_scope)?;
296 }
297 let returning_scope = ExpressionScope {
298 returning_aliases: Some(&plan.returning_aliases),
299 ..ordinary_scope
300 };
301 for projection in &plan.returning {
302 validate_public_view_expression(&projection.expr, &columns, returning_scope)?;
303 }
304 Ok(())
305}
306
307pub(super) fn validate_public_insert_contract(
308 services: ViewRewriteContext<'_>,
309 plan: &InsertPlan,
310) -> Result<(), SQLError> {
311 let columns = public_view_columns(services, &plan.table)?;
312 for predicate in plan.on_conflict.iter().flat_map(|conflict| {
313 conflict
314 .expressions
315 .iter()
316 .chain(conflict.predicate.iter().map(Box::as_ref))
317 }) {
318 validate_public_view_expression(
319 predicate,
320 &columns,
321 ExpressionScope {
322 target_qualifier: &plan.target_qualifier,
323 returning_aliases: None,
324 source: None,
325 include_excluded: false,
326 },
327 )?;
328 }
329 if let Some(ConflictPlan {
330 action:
331 ConflictActionPlan::Update {
332 assignments,
333 predicate,
334 },
335 ..
336 }) = &plan.on_conflict
337 {
338 let scope = ExpressionScope {
339 target_qualifier: &plan.target_qualifier,
340 returning_aliases: None,
341 source: None,
342 include_excluded: true,
343 };
344 for assignment in assignments {
345 for expression in assignment.expressions() {
346 validate_public_view_expression(expression, &columns, scope)?;
347 }
348 }
349 if let Some(predicate) = predicate {
350 validate_public_view_expression(predicate, &columns, scope)?;
351 }
352 }
353 let scope = ExpressionScope {
354 target_qualifier: &plan.target_qualifier,
355 returning_aliases: Some(&plan.returning_aliases),
356 source: None,
357 include_excluded: false,
358 };
359 for projection in &plan.returning {
360 validate_public_view_expression(&projection.expr, &columns, scope)?;
361 }
362 Ok(())
363}
364
365pub fn validate_public_merge_contract(
366 services: ViewRewriteContext<'_>,
367 plan: &MergePlan,
368 source: &RowSchema,
369) -> Result<(), SQLError> {
370 let columns = public_view_columns(services, &plan.target)?;
371 let matched_scope = ExpressionScope {
372 target_qualifier: &plan.target_qualifier,
373 returning_aliases: None,
374 source: Some(source),
375 include_excluded: false,
376 };
377 let target_only_scope = ExpressionScope {
378 source: None,
379 ..matched_scope
380 };
381 validate_public_view_expression(&plan.join_condition, &columns, matched_scope)?;
382 for clause in &plan.when_clauses {
383 match clause {
384 MergeWhenPlan::UpdateMatched {
385 condition,
386 assignments,
387 } => {
388 if let Some(condition) = condition {
389 validate_public_view_expression(condition, &columns, matched_scope)?;
390 }
391 for assignment in assignments {
392 for expression in assignment.expressions() {
393 validate_public_view_expression(expression, &columns, matched_scope)?;
394 }
395 }
396 }
397 MergeWhenPlan::DeleteMatched { condition }
398 | MergeWhenPlan::NothingMatched { condition } => {
399 if let Some(condition) = condition {
400 validate_public_view_expression(condition, &columns, matched_scope)?;
401 }
402 }
403 MergeWhenPlan::UpdateNotMatchedBySource {
404 condition,
405 assignments,
406 } => {
407 if let Some(condition) = condition {
408 validate_public_view_expression(condition, &columns, target_only_scope)?;
409 }
410 for assignment in assignments {
411 validate_public_view_expression(
412 &assignment.value,
413 &columns,
414 target_only_scope,
415 )?;
416 }
417 }
418 MergeWhenPlan::DeleteNotMatchedBySource { condition }
419 | MergeWhenPlan::NothingNotMatchedBySource { condition } => {
420 if let Some(condition) = condition {
421 validate_public_view_expression(condition, &columns, target_only_scope)?;
422 }
423 }
424 MergeWhenPlan::InsertNotMatched { .. } | MergeWhenPlan::NothingNotMatched { .. } => {}
425 }
426 }
427 let returning_scope = ExpressionScope {
428 returning_aliases: Some(&plan.returning_aliases),
429 ..matched_scope
430 };
431 for projection in &plan.returning {
432 validate_public_view_expression(&projection.expr, &columns, returning_scope)?;
433 }
434 Ok(())
435}
436
437pub(super) fn validate_merge_targets(
438 layer: &AutomaticViewLayer,
439 plan: &MergePlan,
440) -> Result<(), SQLError> {
441 for clause in &plan.when_clauses {
442 match clause {
443 MergeWhenPlan::UpdateMatched { assignments, .. }
444 | MergeWhenPlan::UpdateNotMatchedBySource { assignments, .. } => {
445 validate_view_target_columns(
446 layer,
447 assignments.iter().map(|assignment| &assignment.target),
448 duplicate_assignment,
449 )?;
450 }
451 MergeWhenPlan::InsertNotMatched { columns, .. } if !columns.is_empty() => {
452 validate_view_target_columns(layer, columns.iter(), duplicate_insert_column)?;
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 validate_public_view_targets(
469 services,
470 &plan.target,
471 assignments
472 .iter()
473 .map(|assignment| assignment.target.column.as_str()),
474 )?;
475 validate_mapped_columns(
476 &assignments
477 .iter()
478 .map(|assignment| assignment.target.clone())
479 .collect::<Vec<_>>(),
480 duplicate_assignment,
481 )?;
482 }
483 MergeWhenPlan::InsertNotMatched { columns, .. } if !columns.is_empty() => {
484 validate_public_view_targets(
485 services,
486 &plan.target,
487 columns.iter().map(|target| target.column.as_str()),
488 )?;
489 validate_mapped_columns(columns, duplicate_insert_column)?;
490 }
491 _ => {}
492 }
493 }
494 Ok(())
495}
496
497pub(super) fn merge_action_capability_error(
498 view: &str,
499 clauses: &[MergeWhenPlan],
500 capabilities: ViewMutationCapabilities,
501) -> Option<SQLError> {
502 clauses.iter().find_map(|clause| match clause {
503 MergeWhenPlan::UpdateMatched { .. } | MergeWhenPlan::UpdateNotMatchedBySource { .. }
504 if !capabilities.updatable =>
505 {
506 Some(not_automatically_updatable(view, "UPDATE"))
507 }
508 MergeWhenPlan::DeleteMatched { .. } | MergeWhenPlan::DeleteNotMatchedBySource { .. }
509 if !capabilities.deletable =>
510 {
511 Some(not_automatically_updatable(view, "DELETE FROM"))
512 }
513 MergeWhenPlan::InsertNotMatched { .. } if !capabilities.insertable => {
514 Some(not_automatically_updatable(view, "INSERT INTO"))
515 }
516 _ => None,
517 })
518}
519
520fn validate_merge_rule_free(
521 services: ViewRewriteContext<'_>,
522 relation: &str,
523) -> Result<(), SQLError> {
524 let has_rules = [
525 crate::ast::RuleEvent::Insert,
526 crate::ast::RuleEvent::Update,
527 crate::ast::RuleEvent::Delete,
528 ]
529 .into_iter()
530 .map(|event| services.catalog.rules_for(relation, event))
531 .collect::<Result<Vec<_>, SQLError>>()?
532 .iter()
533 .any(|rules| !rules.is_empty());
534 if !has_rules {
535 return Ok(());
536 }
537 Err(SQLError::Routine {
538 sqlstate: "0A000".into(),
539 message: format!(
540 "cannot execute MERGE on relation \"{}\"",
541 display_relation(relation)
542 ),
543 })
544}
545
546fn merge_uses_event(plan: &MergePlan, event: TriggerEvent) -> bool {
547 plan.when_clauses.iter().any(|clause| match event {
548 TriggerEvent::Insert => matches!(clause, MergeWhenPlan::InsertNotMatched { .. }),
549 TriggerEvent::Update => matches!(
550 clause,
551 MergeWhenPlan::UpdateMatched { .. } | MergeWhenPlan::UpdateNotMatchedBySource { .. }
552 ),
553 TriggerEvent::Delete => matches!(
554 clause,
555 MergeWhenPlan::DeleteMatched { .. } | MergeWhenPlan::DeleteNotMatchedBySource { .. }
556 ),
557 TriggerEvent::Truncate => false,
558 })
559}
560
561#[derive(Debug, Clone, Copy, PartialEq, Eq)]
562pub enum MergeViewTargetPath {
563 AutomaticRewrite,
564 ViewTriggers,
565}
566
567pub fn merge_view_target_path(
568 services: ViewRewriteContext<'_>,
569 plan: &MergePlan,
570) -> Result<MergeViewTargetPath, SQLError> {
571 let canonical = services
572 .catalog
573 .try_resolve_view_name(&plan.target)
574 .map_err(|error| SQLError::Internal(format!("resolve MERGE view: {error}")))?
575 .ok_or_else(|| SQLError::UnknownTable(plan.target.clone()))?;
576 let definition = services
577 .catalog
578 .view_definition(&canonical)?
579 .ok_or_else(|| SQLError::UnknownTable(plan.target.clone()))?;
580 if definition.kind == crate::catalog::view::StoredViewKind::Materialized {
581 return Err(SQLError::Routine {
582 sqlstate: "0A000".into(),
583 message: format!(
584 "cannot execute MERGE on relation \"{}\"",
585 display_relation(&canonical)
586 ),
587 });
588 }
589 validate_merge_rule_free(services, &canonical)?;
590 let automatic = view_updatability(services, &canonical)?.automatic;
591 let insert_trigger = instead_of_trigger_definition(services, &canonical, TriggerEvent::Insert)?;
592 let update_trigger = instead_of_trigger_definition(services, &canonical, TriggerEvent::Update)?;
593 let delete_trigger = instead_of_trigger_definition(services, &canonical, TriggerEvent::Delete)?;
594 let supported = ViewMutationCapabilities {
595 insertable: automatic.insertable || insert_trigger,
596 updatable: automatic.updatable || update_trigger,
597 deletable: automatic.deletable || delete_trigger,
598 };
599 if let Some(error) = merge_action_capability_error(&canonical, &plan.when_clauses, supported) {
600 return Err(error);
601 }
602 let mut uses_automatic = false;
603 let mut uses_trigger = false;
604 let mut has_action = false;
605 for (event, trigger) in [
606 (TriggerEvent::Insert, insert_trigger),
607 (TriggerEvent::Update, update_trigger),
608 (TriggerEvent::Delete, delete_trigger),
609 ] {
610 if !merge_uses_event(plan, event) {
611 continue;
612 }
613 has_action = true;
614 uses_trigger |= trigger;
615 uses_automatic |= !trigger;
616 }
617 if uses_trigger && uses_automatic {
618 return Err(SQLError::Routine {
619 sqlstate: "0A000".into(),
620 message: format!(
621 "cannot merge into view \"{}\"",
622 display_relation(&canonical)
623 ),
624 });
625 }
626 if uses_trigger || !has_action {
627 Ok(MergeViewTargetPath::ViewTriggers)
628 } else {
629 Ok(MergeViewTargetPath::AutomaticRewrite)
630 }
631}
632
633pub(super) fn validate_public_view_targets<'a>(
634 services: ViewRewriteContext<'_>,
635 view: &str,
636 columns: impl IntoIterator<Item = &'a str>,
637) -> Result<(), SQLError> {
638 let definition = services
639 .catalog
640 .view_definition(view)?
641 .ok_or_else(|| SQLError::UnknownTable(view.to_string()))?;
642 let schema = stored_view_schema(services, &definition)?;
643 for column in columns {
644 if !schema.has_unqualified_column(column) {
645 return Err(SQLError::UnknownColumn(format!("{view}.{column}")));
646 }
647 }
648 Ok(())
649}
650
651pub(super) fn validate_direct_view_rule_path(
652 services: ViewRewriteContext<'_>,
653 view: &str,
654 event: crate::ast::RuleEvent,
655 operation: &str,
656) -> Result<(), SQLError> {
657 let rules = services.catalog.rules_for(view, event)?;
658 let has_conditional_instead = rules
659 .iter()
660 .any(|rule| rule.definition.instead && rule.definition.condition.is_some());
661 let has_unconditional_instead = rules
662 .iter()
663 .any(|rule| rule.definition.instead && rule.definition.condition.is_none());
664 if has_conditional_instead && !has_unconditional_instead {
665 return Err(not_automatically_updatable(view, operation));
666 }
667 Ok(())
668}
669
670use super::stored_view_schema;