1use std::collections::{BTreeMap, BTreeSet};
10
11use crate::ast::{ReturningAliases, TriggerEvent, TriggerTiming};
12use crate::plan::{
13 AssignmentPlan, ComputePlan, ConflictActionPlan, ConflictPlan, DeletePlan, InsertPlan,
14 MergePlan, MergeWhenPlan, ProjectionPlan, QueryPlan, RelationalPlan, SourcePlan, UpdatePlan,
15 ViewCheckPlan, ViewRuleInsertPlan, ViewRuleReturningPlan, ViewRuleUpdatePlan,
16};
17use crate::SQLError;
18use crate::{ColumnIdentity, RowSchema, ScalarExpr};
19
20use crate::{
21 catalog::view::{StoredViewKind, ViewRewriteDefinition as StoredView},
22 RelationIdentity,
23};
24
25use crate::binding::snapshot::BindingSnapshot as CteScope;
26pub mod context;
27use context::{stored_view_schema, ViewRewriteContext};
28
29mod correlation;
30mod returning;
31mod rewrite_insert;
32mod rewrite_merge;
33mod rewrite_update_delete;
34pub mod rule_inputs;
35mod validation;
36
37use correlation::{
38 collect_expression_subquery_ids, delete_ordinary_subquery_ids, dml_analysis_scope,
39 dml_source_schema, insert_conflict_subquery_ids, insert_input_width,
40 merge_matched_subquery_ids, merge_target_only_subquery_ids, returning_subquery_ids,
41 rewrite_correlated_dml_context, schema_public_columns, update_ordinary_subquery_ids,
42 validate_delete_expressions, validate_insert_expressions, validate_merge_expressions,
43 validate_update_expressions, CorrelatedDmlContext,
44};
45use returning::{
46 add_check_option, bind_unqualified_source_positions, combine_view_predicate, dml_target_width,
47 finalize_source_returning, instead_of_trigger_definition, preserve_view_rule_returning,
48 record_view_rule_relation, retarget_source_expression, rewrite_existing_view_checks,
49 rewrite_merge_returning, rewrite_returning, rewrite_target_expression,
50};
51pub use rewrite_insert::rewrite_insert_to_base;
52pub use rewrite_merge::rewrite_merge_to_base;
53pub use rewrite_update_delete::{rewrite_delete_to_base, rewrite_update_to_base};
54pub use rule_inputs::rule_input_requirements;
55use validation::{
56 duplicate_assignment, duplicate_insert_column, layer_column, merge_action_capability_error,
57 validate_direct_view_rule_path, validate_insert_targets, validate_mapped_columns,
58 validate_merge_targets, validate_public_delete_contract, validate_public_insert_contract,
59 validate_public_update_contract, validate_public_view_targets, validate_update_targets,
60 validate_view_expression, writable_column, ExpressionScope,
61};
62pub use validation::{merge_view_target_path, MergeViewTargetPath};
63pub use validation::{validate_public_merge_contract, validate_public_merge_targets};
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum ViewCheckOption {
67 None,
68 Local,
69 Cascaded,
70}
71
72impl ViewCheckOption {
73 fn from_options(options: &[(String, String)]) -> Self {
74 options
75 .iter()
76 .rev()
77 .find(|(name, _)| name == "check_option")
78 .map_or(Self::None, |(_, value)| match value.as_str() {
79 "local" => Self::Local,
80 "cascaded" => Self::Cascaded,
81 _ => Self::None,
82 })
83 }
84
85 const fn catalog_value(self) -> &'static str {
86 match self {
87 Self::None => "NONE",
88 Self::Local => "LOCAL",
89 Self::Cascaded => "CASCADED",
90 }
91 }
92}
93
94#[derive(Debug, Clone)]
95pub struct ViewColumn {
96 pub name: String,
97 pub expression: ScalarExpr,
98 pub writable_source_column: Option<String>,
99}
100
101#[derive(Debug, Clone)]
102pub struct AutomaticViewLayer {
103 pub canonical_name: String,
104 pub source_name: String,
105 pub source_qualifier: String,
106 pub source_column_map: BTreeMap<String, String>,
107 pub source_include_descendants: bool,
108 pub source_schema: RowSchema,
109 pub columns: Vec<ViewColumn>,
110 pub predicate: Option<ScalarExpr>,
111 pub subqueries: Vec<QueryPlan>,
112 pub check_option: ViewCheckOption,
113}
114
115impl AutomaticViewLayer {
116 pub fn physical_source_column<'a>(&'a self, column: &'a str) -> &'a str {
117 self.source_column_map
118 .get(column)
119 .map_or(column, String::as_str)
120 }
121}
122
123pub use crate::catalog::view::ViewMutationCapabilities;
124
125#[derive(Debug, Clone)]
126pub struct ViewUpdatability {
127 pub automatic: ViewMutationCapabilities,
128 runtime: ViewMutationCapabilities,
129 runtime_insert_columns: Vec<bool>,
130 runtime_columns: Vec<bool>,
131 pub catalog: ViewMutationCapabilities,
132 catalog_insert_columns: Vec<bool>,
133 pub catalog_columns: Vec<bool>,
134 pub check_option: String,
135}
136
137fn display_relation(name: &str) -> String {
138 RelationIdentity::from_legacy_name(name)
139 .map_or_else(|_| name.to_string(), |relation| relation.name)
140}
141
142fn not_automatically_updatable(view: &str, operation: &str) -> SQLError {
143 SQLError::Routine {
144 sqlstate: "55000".into(),
145 message: format!(
146 "cannot {} view \"{}\": the view is not automatically updatable",
147 operation.to_ascii_lowercase(),
148 display_relation(view)
149 ),
150 }
151}
152
153fn non_writable_column(view: &str, column: &str, operation: &str) -> SQLError {
154 SQLError::Routine {
155 sqlstate: "0A000".into(),
156 message: format!(
157 "cannot {} column \"{column}\" of view \"{}\"",
158 operation.to_ascii_lowercase(),
159 display_relation(view)
160 ),
161 }
162}
163
164pub fn relation_columns(
165 services: ViewRewriteContext<'_>,
166 relation: &str,
167) -> Result<Vec<String>, SQLError> {
168 if let Some(view) = services.catalog.view_definition(relation)? {
169 let schema = stored_view_schema(services, &view)?;
170 return Ok(schema
171 .columns()
172 .iter()
173 .enumerate()
174 .map(|(position, column)| {
175 schema
176 .public_name(position)
177 .map_or_else(|| column.clone(), str::to_string)
178 })
179 .collect());
180 }
181 let definitions = services
182 .catalog
183 .try_describe_table(relation)
184 .map_err(|error| SQLError::Internal(format!("describe view source `{relation}`: {error}")))?
185 .ok_or_else(|| SQLError::UnknownTable(relation.to_string()))?;
186 if definitions.is_empty() {
187 return services
188 .catalog
189 .try_table_columns(relation)
190 .map_err(|error| {
191 SQLError::Internal(format!("describe view source `{relation}`: {error}"))
192 });
193 }
194 Ok(definitions
195 .into_iter()
196 .map(|definition| definition.name)
197 .collect())
198}
199
200fn source_qualifier_matches(qualifier: &str, source_qualifier: &str, source_name: &str) -> bool {
201 qualifier == source_qualifier
202 || qualifier == source_name
203 || RelationIdentity::from_legacy_name(source_name)
204 .is_ok_and(|identity| qualifier == identity.name)
205}
206
207fn direct_source_column(
208 expression: &ScalarExpr,
209 source_qualifier: &str,
210 source_name: &str,
211 source_columns: &BTreeMap<String, String>,
212) -> Option<String> {
213 let column = match expression {
214 ScalarExpr::Column(column) => Some(column.clone()),
215 ScalarExpr::QualifiedColumn { qualifier, column }
216 if source_qualifier_matches(qualifier, source_qualifier, source_name) =>
217 {
218 Some(column.clone())
219 }
220 _ => None,
221 }?;
222 source_columns.get(&column).cloned()
223}
224
225pub fn automatic_view_layer(
226 services: ViewRewriteContext<'_>,
227 name: &str,
228) -> Result<Option<AutomaticViewLayer>, SQLError> {
229 let Some(canonical_name) = services
230 .catalog
231 .try_resolve_view_name(name)
232 .map_err(|error| SQLError::Internal(format!("resolve DML view `{name}`: {error}")))?
233 else {
234 return Ok(None);
235 };
236 let definition = services
237 .catalog
238 .view_definition(&canonical_name)?
239 .ok_or_else(|| SQLError::UnknownTable(name.to_string()))?;
240 automatic_view_layer_from_definition(services, &canonical_name, &definition)
241}
242
243#[expect(
244 clippy::too_many_lines,
245 reason = "preserves view qualifier and row identity"
246)]
247fn automatic_view_layer_from_definition(
248 services: ViewRewriteContext<'_>,
249 canonical_name: &str,
250 definition: &StoredView,
251) -> Result<Option<AutomaticViewLayer>, SQLError> {
252 if definition.kind != StoredViewKind::View || !definition.query.ctes.is_empty() {
253 return Ok(None);
254 }
255 let RelationalPlan::QueryBlock(block) = &definition.query.root else {
256 return Ok(None);
257 };
258 if !matches!(block.compute, ComputePlan::Project)
259 || !block.group_by.is_empty()
260 || !block.grouping_sets.is_empty()
261 || block.group_distinct
262 || block.having.is_some()
263 || block.limit.is_some()
264 || block.with_ties
265 || block.offset.is_some()
266 || block.distinct
267 || !block.distinct_on.is_empty()
268 || !block.locking.is_empty()
269 {
270 return Ok(None);
271 }
272 let Some(
273 source_plan @ SourcePlan::Table {
274 name: source_name,
275 qualifier,
276 alias,
277 column_aliases,
278 include_descendants,
279 ..
280 },
281 ) = block.from.as_ref()
282 else {
283 return Ok(None);
284 };
285 let analysis_scope = services.catalog.binding_scope()?;
286 let source_schema = crate::semantics::view_rewrite::context::analyze_source_plan_schema(
287 services,
288 source_plan,
289 &[],
290 &analysis_scope,
291 None,
292 )?;
293 let resolver = crate::binding::scoped_types::BindingTypeResolver {
294 routines: services.catalog,
295 scope: &analysis_scope,
296 };
297 for projection in &block.projections {
298 if crate::semantics::sets::validation::expression_may_return_set(
299 services.catalog,
300 &resolver,
301 &projection.expr,
302 &source_schema,
303 &[],
304 )? {
305 return Ok(None);
306 }
307 }
308 let source_qualifier = alias.as_deref().unwrap_or(qualifier).to_string();
309 let source_columns = relation_columns(services, source_name)?;
310 let mut visible_source_columns = source_columns.clone();
311 for (column, alias) in visible_source_columns.iter_mut().zip(column_aliases) {
312 column.clone_from(alias);
313 }
314 let source_column_map = visible_source_columns
315 .iter()
316 .cloned()
317 .zip(source_columns.iter().cloned())
318 .collect::<BTreeMap<_, _>>();
319 let mut expressions = Vec::new();
320 for projection in &block.projections {
321 match &projection.expr {
322 ScalarExpr::Star => {
323 expressions.extend(
324 visible_source_columns
325 .iter()
326 .cloned()
327 .map(ScalarExpr::Column),
328 );
329 }
330 ScalarExpr::QualifiedStar(star_qualifier)
331 if source_qualifier_matches(star_qualifier, &source_qualifier, source_name) =>
332 {
333 expressions.extend(visible_source_columns.iter().cloned().map(|column| {
334 ScalarExpr::QualifiedColumn {
335 qualifier: source_qualifier.clone(),
336 column,
337 }
338 }));
339 }
340 expression => expressions.push(expression.clone()),
341 }
342 }
343 let schema = stored_view_schema(services, definition)?;
344 let output_columns = schema
345 .columns()
346 .iter()
347 .enumerate()
348 .map(|(position, column)| schema.public_name(position).unwrap_or(column).to_string())
349 .collect::<Vec<_>>();
350 if expressions.len() != output_columns.len() {
351 return Err(SQLError::Internal(format!(
352 "view `{canonical_name}` has {} stored projections for {} output columns",
353 expressions.len(),
354 output_columns.len()
355 )));
356 }
357 let columns = output_columns
358 .into_iter()
359 .zip(expressions)
360 .map(|(name, expression)| ViewColumn {
361 writable_source_column: direct_source_column(
362 &expression,
363 &source_qualifier,
364 source_name,
365 &source_column_map,
366 ),
367 name,
368 expression,
369 })
370 .collect();
371 Ok(Some(AutomaticViewLayer {
372 canonical_name: canonical_name.to_string(),
373 source_name: source_name.clone(),
374 source_qualifier,
375 source_column_map,
376 source_include_descendants: *include_descendants,
377 source_schema,
378 columns,
379 predicate: block.r#where.clone(),
380 subqueries: block.subqueries.clone(),
381 check_option: ViewCheckOption::from_options(&definition.options),
382 }))
383}
384
385mod layer_rewrite;
386use layer_rewrite::embed_layer_expression;
387
388pub fn has_instead_of_trigger(
389 services: ViewRewriteContext<'_>,
390 view: &str,
391 event: TriggerEvent,
392) -> Result<bool, SQLError> {
393 let Some(canonical) = services
394 .catalog
395 .try_resolve_view_name(view)
396 .map_err(|error| SQLError::Internal(format!("resolve DML view `{view}`: {error}")))?
397 else {
398 return Ok(false);
399 };
400 instead_of_trigger_definition(services, &canonical, event)
401}
402
403#[expect(
404 clippy::too_many_lines,
405 reason = "preserves view qualifier and row identity"
406)]
407fn view_updatability_inner(
408 services: ViewRewriteContext<'_>,
409 name: &str,
410 visited: &mut BTreeSet<String>,
411) -> Result<ViewUpdatability, SQLError> {
412 let definition = services
413 .catalog
414 .view_definition(name)?
415 .ok_or_else(|| SQLError::UnknownTable(name.to_string()))?;
416 let schema = stored_view_schema(services, &definition)?;
417 let width = schema.len();
418 let check_option = ViewCheckOption::from_options(&definition.options)
419 .catalog_value()
420 .to_string();
421 let mut automatic = ViewMutationCapabilities::default();
422 let mut automatic_insert_columns = vec![false; width];
423 let mut automatic_columns = vec![false; width];
424 let mut projected_catalog = ViewMutationCapabilities::default();
425 let mut projected_catalog_insert_columns = vec![false; width];
426 let mut projected_catalog_columns = vec![false; width];
427 if let Some(layer) = automatic_view_layer(services, name)? {
428 if visited.insert(layer.canonical_name.clone()) {
429 if services
430 .catalog
431 .view_definition(&layer.source_name)?
432 .is_some()
433 {
434 let source = view_updatability_inner(services, &layer.source_name, visited)?;
435 let source_columns = relation_columns(services, &layer.source_name)?;
436 let mapped = |source_capabilities: &[bool]| {
437 layer
438 .columns
439 .iter()
440 .map(|column| {
441 column.writable_source_column.as_ref().is_some_and(|name| {
442 source_columns
443 .iter()
444 .position(|candidate| candidate == name)
445 .is_some_and(|position| {
446 source_capabilities.get(position) == Some(&true)
447 })
448 })
449 })
450 .collect::<Vec<_>>()
451 };
452 automatic_insert_columns = mapped(&source.runtime_insert_columns);
453 automatic_columns = mapped(&source.runtime_columns);
454 automatic = ViewMutationCapabilities {
455 insertable: source.runtime.insertable
456 && automatic_insert_columns.iter().any(|value| *value),
457 updatable: source.runtime.updatable
458 && automatic_columns.iter().any(|value| *value),
459 deletable: source.runtime.deletable,
460 };
461 projected_catalog_insert_columns = mapped(&source.catalog_insert_columns);
462 projected_catalog_columns = mapped(&source.catalog_columns);
463 projected_catalog = ViewMutationCapabilities {
464 insertable: source.catalog.insertable
465 && projected_catalog_insert_columns.iter().any(|value| *value),
466 updatable: source.catalog.updatable
467 && projected_catalog_columns.iter().any(|value| *value),
468 deletable: source.catalog.deletable,
469 };
470 } else {
471 automatic_insert_columns = layer
472 .columns
473 .iter()
474 .map(|column| column.writable_source_column.is_some())
475 .collect();
476 automatic_columns.clone_from(&automatic_insert_columns);
477 automatic = ViewMutationCapabilities {
478 insertable: automatic_insert_columns.iter().any(|value| *value),
479 updatable: automatic_columns.iter().any(|value| *value),
480 deletable: true,
481 };
482 projected_catalog = automatic;
483 projected_catalog_insert_columns.clone_from(&automatic_insert_columns);
484 projected_catalog_columns.clone_from(&automatic_columns);
485 }
486 visited.remove(&layer.canonical_name);
487 }
488 }
489 let active_insert =
490 active_unconditional_instead_rule(services, name, crate::ast::RuleEvent::Insert)?;
491 let active_update =
492 active_unconditional_instead_rule(services, name, crate::ast::RuleEvent::Update)?;
493 let active_delete =
494 active_unconditional_instead_rule(services, name, crate::ast::RuleEvent::Delete)?;
495 let runtime = ViewMutationCapabilities {
496 insertable: automatic.insertable || active_insert,
497 updatable: automatic.updatable || active_update,
498 deletable: automatic.deletable || active_delete,
499 };
500 let runtime_insert_columns = automatic_insert_columns
501 .iter()
502 .map(|column| *column || active_insert)
503 .collect();
504 let runtime_columns = automatic_columns
505 .iter()
506 .map(|column| *column || active_update)
507 .collect();
508 let rule_insertable =
509 has_unconditional_instead_rule(services, name, crate::ast::RuleEvent::Insert)?;
510 let rule_updatable =
511 has_unconditional_instead_rule(services, name, crate::ast::RuleEvent::Update)?;
512 let rule_deletable =
513 has_unconditional_instead_rule(services, name, crate::ast::RuleEvent::Delete)?;
514 let catalog = ViewMutationCapabilities {
515 insertable: projected_catalog.insertable || rule_insertable,
516 updatable: projected_catalog.updatable || rule_updatable,
517 deletable: projected_catalog.deletable || rule_deletable,
518 };
519 let catalog_insert_columns = projected_catalog_insert_columns
520 .iter()
521 .map(|column| *column || rule_insertable)
522 .collect();
523 let catalog_columns = if catalog.fully_updatable() {
524 projected_catalog_columns
525 .iter()
526 .map(|column| *column || rule_updatable)
527 .collect()
528 } else {
529 vec![false; width]
530 };
531 Ok(ViewUpdatability {
532 automatic,
533 runtime,
534 runtime_insert_columns,
535 runtime_columns,
536 catalog,
537 catalog_insert_columns,
538 catalog_columns,
539 check_option,
540 })
541}
542
543pub fn view_updatability(
544 services: ViewRewriteContext<'_>,
545 name: &str,
546) -> Result<ViewUpdatability, SQLError> {
547 view_updatability_inner(services, name, &mut BTreeSet::new())
548}
549
550fn active_unconditional_instead_rule(
551 services: ViewRewriteContext<'_>,
552 relation: &str,
553 event: crate::ast::RuleEvent,
554) -> Result<bool, SQLError> {
555 Ok(services
556 .catalog
557 .rules_for(relation, event)?
558 .iter()
559 .any(|rule| rule.definition.instead && rule.definition.condition.is_none()))
560}
561
562fn has_unconditional_instead_rule(
563 services: ViewRewriteContext<'_>,
564 relation: &str,
565 event: crate::ast::RuleEvent,
566) -> Result<bool, SQLError> {
567 Ok(services
568 .catalog
569 .rule_definitions_for(relation, event)?
570 .iter()
571 .any(|rule| rule.definition.instead && rule.definition.condition.is_none()))
572}
573
574pub fn validate_view_definition_check_option(
575 services: ViewRewriteContext<'_>,
576 name: &str,
577 definition: &StoredView,
578) -> Result<(), SQLError> {
579 if ViewCheckOption::from_options(&definition.options) == ViewCheckOption::None {
580 return Ok(());
581 }
582 let updatable =
583 automatic_view_layer_from_definition(services, name, definition)?.is_some_and(|layer| {
584 layer
585 .columns
586 .iter()
587 .any(|column| column.writable_source_column.is_some())
588 });
589 if updatable {
590 return Ok(());
591 }
592 Err(SQLError::Routine {
593 sqlstate: "0A000".into(),
594 message: "WITH CHECK OPTION is supported only on automatically updatable views".into(),
595 })
596}