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(relation) = crate::catalog::VirtualRelation::from_qualified_name(relation) {
169 return Ok(relation
170 .schema()
171 .into_iter()
172 .map(|(name, _)| name)
173 .collect());
174 }
175 if let Some(view) = services.catalog.view_definition(relation)? {
176 let schema = stored_view_schema(services, &view)?;
177 return Ok(schema
178 .columns()
179 .iter()
180 .enumerate()
181 .map(|(position, column)| {
182 schema
183 .public_name(position)
184 .map_or_else(|| column.clone(), str::to_string)
185 })
186 .collect());
187 }
188 let definitions = services
189 .catalog
190 .try_describe_table(relation)
191 .map_err(|error| SQLError::Internal(format!("describe view source `{relation}`: {error}")))?
192 .ok_or_else(|| SQLError::UnknownTable(relation.to_string()))?;
193 if definitions.is_empty() {
194 return services
195 .catalog
196 .try_table_columns(relation)
197 .map_err(|error| {
198 SQLError::Internal(format!("describe view source `{relation}`: {error}"))
199 });
200 }
201 Ok(definitions
202 .into_iter()
203 .map(|definition| definition.name)
204 .collect())
205}
206
207fn source_qualifier_matches(qualifier: &str, source_qualifier: &str, source_name: &str) -> bool {
208 qualifier == source_qualifier
209 || qualifier == source_name
210 || RelationIdentity::from_legacy_name(source_name)
211 .is_ok_and(|identity| qualifier == identity.name)
212}
213
214fn direct_source_column(
215 expression: &ScalarExpr,
216 source_qualifier: &str,
217 source_name: &str,
218 source_columns: &BTreeMap<String, String>,
219) -> Option<String> {
220 let column = match expression {
221 ScalarExpr::Column(column) => Some(column.clone()),
222 ScalarExpr::QualifiedColumn { qualifier, column }
223 if source_qualifier_matches(qualifier, source_qualifier, source_name) =>
224 {
225 Some(column.clone())
226 }
227 _ => None,
228 }?;
229 source_columns.get(&column).cloned()
230}
231
232pub fn automatic_view_layer(
233 services: ViewRewriteContext<'_>,
234 name: &str,
235) -> Result<Option<AutomaticViewLayer>, SQLError> {
236 let Some(canonical_name) = services
237 .catalog
238 .try_resolve_view_name(name)
239 .map_err(|error| SQLError::Internal(format!("resolve DML view `{name}`: {error}")))?
240 else {
241 return Ok(None);
242 };
243 let definition = services
244 .catalog
245 .view_definition(&canonical_name)?
246 .ok_or_else(|| SQLError::UnknownTable(name.to_string()))?;
247 automatic_view_layer_from_definition(services, &canonical_name, &definition)
248}
249
250#[expect(
251 clippy::too_many_lines,
252 reason = "preserves view qualifier and row identity"
253)]
254fn automatic_view_layer_from_definition(
255 services: ViewRewriteContext<'_>,
256 canonical_name: &str,
257 definition: &StoredView,
258) -> Result<Option<AutomaticViewLayer>, SQLError> {
259 if definition.kind != StoredViewKind::View || !definition.query.ctes.is_empty() {
260 return Ok(None);
261 }
262 let RelationalPlan::QueryBlock(block) = &definition.query.root else {
263 return Ok(None);
264 };
265 if !matches!(block.compute, ComputePlan::Project)
266 || !block.group_by.is_empty()
267 || !block.grouping_sets.is_empty()
268 || block.group_distinct
269 || block.having.is_some()
270 || block.limit.is_some()
271 || block.with_ties
272 || block.offset.is_some()
273 || block.distinct
274 || !block.distinct_on.is_empty()
275 || !block.locking.is_empty()
276 {
277 return Ok(None);
278 }
279 let Some(
280 source_plan @ SourcePlan::Table {
281 name: source_name,
282 qualifier,
283 alias,
284 column_aliases,
285 include_descendants,
286 ..
287 },
288 ) = block.from.as_ref()
289 else {
290 return Ok(None);
291 };
292 let analysis_scope = services.catalog.binding_scope()?;
293 let source_schema = crate::semantics::view_rewrite::context::analyze_source_plan_schema(
294 services,
295 source_plan,
296 &[],
297 &analysis_scope,
298 None,
299 )?;
300 let resolver = crate::binding::scoped_types::BindingTypeResolver {
301 routines: services.catalog,
302 scope: &analysis_scope,
303 };
304 for projection in &block.projections {
305 if crate::semantics::sets::validation::expression_may_return_set(
306 services.catalog,
307 &resolver,
308 &projection.expr,
309 &source_schema,
310 &[],
311 )? {
312 return Ok(None);
313 }
314 }
315 let source_qualifier = alias.as_deref().unwrap_or(qualifier).to_string();
316 let source_columns = relation_columns(services, source_name)?;
317 let mut visible_source_columns = source_columns.clone();
318 for (column, alias) in visible_source_columns.iter_mut().zip(column_aliases) {
319 column.clone_from(alias);
320 }
321 let source_column_map = visible_source_columns
322 .iter()
323 .cloned()
324 .zip(source_columns.iter().cloned())
325 .collect::<BTreeMap<_, _>>();
326 let mut expressions = Vec::new();
327 for projection in &block.projections {
328 match &projection.expr {
329 ScalarExpr::Star => {
330 expressions.extend(
331 visible_source_columns
332 .iter()
333 .cloned()
334 .map(ScalarExpr::Column),
335 );
336 }
337 ScalarExpr::QualifiedStar(star_qualifier)
338 if source_qualifier_matches(star_qualifier, &source_qualifier, source_name) =>
339 {
340 expressions.extend(visible_source_columns.iter().cloned().map(|column| {
341 ScalarExpr::QualifiedColumn {
342 qualifier: source_qualifier.clone(),
343 column,
344 }
345 }));
346 }
347 expression => expressions.push(expression.clone()),
348 }
349 }
350 let schema = stored_view_schema(services, definition)?;
351 let output_columns = schema
352 .columns()
353 .iter()
354 .enumerate()
355 .map(|(position, column)| schema.public_name(position).unwrap_or(column).to_string())
356 .collect::<Vec<_>>();
357 if expressions.len() != output_columns.len() {
358 return Err(SQLError::Internal(format!(
359 "view `{canonical_name}` has {} stored projections for {} output columns",
360 expressions.len(),
361 output_columns.len()
362 )));
363 }
364 let columns = output_columns
365 .into_iter()
366 .zip(expressions)
367 .map(|(name, expression)| ViewColumn {
368 writable_source_column: direct_source_column(
369 &expression,
370 &source_qualifier,
371 source_name,
372 &source_column_map,
373 ),
374 name,
375 expression,
376 })
377 .collect();
378 Ok(Some(AutomaticViewLayer {
379 canonical_name: canonical_name.to_string(),
380 source_name: source_name.clone(),
381 source_qualifier,
382 source_column_map,
383 source_include_descendants: *include_descendants,
384 source_schema,
385 columns,
386 predicate: block.r#where.clone(),
387 subqueries: block.subqueries.clone(),
388 check_option: ViewCheckOption::from_options(&definition.options),
389 }))
390}
391
392mod layer_rewrite;
393use layer_rewrite::embed_layer_expression;
394
395pub fn has_instead_of_trigger(
396 services: ViewRewriteContext<'_>,
397 view: &str,
398 event: TriggerEvent,
399) -> Result<bool, SQLError> {
400 let Some(canonical) = services
401 .catalog
402 .try_resolve_view_name(view)
403 .map_err(|error| SQLError::Internal(format!("resolve DML view `{view}`: {error}")))?
404 else {
405 return Ok(false);
406 };
407 instead_of_trigger_definition(services, &canonical, event)
408}
409
410#[expect(
411 clippy::too_many_lines,
412 reason = "preserves view qualifier and row identity"
413)]
414fn view_updatability_inner(
415 services: ViewRewriteContext<'_>,
416 name: &str,
417 visited: &mut BTreeSet<String>,
418) -> Result<ViewUpdatability, SQLError> {
419 let definition = services
420 .catalog
421 .view_definition(name)?
422 .ok_or_else(|| SQLError::UnknownTable(name.to_string()))?;
423 let schema = stored_view_schema(services, &definition)?;
424 let width = schema.len();
425 let check_option = ViewCheckOption::from_options(&definition.options)
426 .catalog_value()
427 .to_string();
428 let mut automatic = ViewMutationCapabilities::default();
429 let mut automatic_insert_columns = vec![false; width];
430 let mut automatic_columns = vec![false; width];
431 let mut projected_catalog = ViewMutationCapabilities::default();
432 let mut projected_catalog_insert_columns = vec![false; width];
433 let mut projected_catalog_columns = vec![false; width];
434 if let Some(layer) = automatic_view_layer(services, name)? {
435 if visited.insert(layer.canonical_name.clone()) {
436 if services
437 .catalog
438 .view_definition(&layer.source_name)?
439 .is_some()
440 {
441 let source = view_updatability_inner(services, &layer.source_name, visited)?;
442 let source_columns = relation_columns(services, &layer.source_name)?;
443 let mapped = |source_capabilities: &[bool]| {
444 layer
445 .columns
446 .iter()
447 .map(|column| {
448 column.writable_source_column.as_ref().is_some_and(|name| {
449 source_columns
450 .iter()
451 .position(|candidate| candidate == name)
452 .is_some_and(|position| {
453 source_capabilities.get(position) == Some(&true)
454 })
455 })
456 })
457 .collect::<Vec<_>>()
458 };
459 automatic_insert_columns = mapped(&source.runtime_insert_columns);
460 automatic_columns = mapped(&source.runtime_columns);
461 automatic = ViewMutationCapabilities {
462 insertable: source.runtime.insertable
463 && automatic_insert_columns.iter().any(|value| *value),
464 updatable: source.runtime.updatable
465 && automatic_columns.iter().any(|value| *value),
466 deletable: source.runtime.deletable,
467 };
468 projected_catalog_insert_columns = mapped(&source.catalog_insert_columns);
469 projected_catalog_columns = mapped(&source.catalog_columns);
470 projected_catalog = ViewMutationCapabilities {
471 insertable: source.catalog.insertable
472 && projected_catalog_insert_columns.iter().any(|value| *value),
473 updatable: source.catalog.updatable
474 && projected_catalog_columns.iter().any(|value| *value),
475 deletable: source.catalog.deletable,
476 };
477 } else {
478 automatic_insert_columns = layer
479 .columns
480 .iter()
481 .map(|column| column.writable_source_column.is_some())
482 .collect();
483 automatic_columns.clone_from(&automatic_insert_columns);
484 automatic = ViewMutationCapabilities {
485 insertable: automatic_insert_columns.iter().any(|value| *value),
486 updatable: automatic_columns.iter().any(|value| *value),
487 deletable: true,
488 };
489 projected_catalog = automatic;
490 projected_catalog_insert_columns.clone_from(&automatic_insert_columns);
491 projected_catalog_columns.clone_from(&automatic_columns);
492 }
493 visited.remove(&layer.canonical_name);
494 }
495 }
496 let active_insert =
497 active_unconditional_instead_rule(services, name, crate::ast::RuleEvent::Insert)?;
498 let active_update =
499 active_unconditional_instead_rule(services, name, crate::ast::RuleEvent::Update)?;
500 let active_delete =
501 active_unconditional_instead_rule(services, name, crate::ast::RuleEvent::Delete)?;
502 let runtime = ViewMutationCapabilities {
503 insertable: automatic.insertable || active_insert,
504 updatable: automatic.updatable || active_update,
505 deletable: automatic.deletable || active_delete,
506 };
507 let runtime_insert_columns = automatic_insert_columns
508 .iter()
509 .map(|column| *column || active_insert)
510 .collect();
511 let runtime_columns = automatic_columns
512 .iter()
513 .map(|column| *column || active_update)
514 .collect();
515 let rule_insertable =
516 has_unconditional_instead_rule(services, name, crate::ast::RuleEvent::Insert)?;
517 let rule_updatable =
518 has_unconditional_instead_rule(services, name, crate::ast::RuleEvent::Update)?;
519 let rule_deletable =
520 has_unconditional_instead_rule(services, name, crate::ast::RuleEvent::Delete)?;
521 let catalog = ViewMutationCapabilities {
522 insertable: projected_catalog.insertable || rule_insertable,
523 updatable: projected_catalog.updatable || rule_updatable,
524 deletable: projected_catalog.deletable || rule_deletable,
525 };
526 let catalog_insert_columns = projected_catalog_insert_columns
527 .iter()
528 .map(|column| *column || rule_insertable)
529 .collect();
530 let catalog_columns = if catalog.fully_updatable() {
531 projected_catalog_columns
532 .iter()
533 .map(|column| *column || rule_updatable)
534 .collect()
535 } else {
536 vec![false; width]
537 };
538 Ok(ViewUpdatability {
539 automatic,
540 runtime,
541 runtime_insert_columns,
542 runtime_columns,
543 catalog,
544 catalog_insert_columns,
545 catalog_columns,
546 check_option,
547 })
548}
549
550pub fn view_updatability(
551 services: ViewRewriteContext<'_>,
552 name: &str,
553) -> Result<ViewUpdatability, SQLError> {
554 view_updatability_inner(services, name, &mut BTreeSet::new())
555}
556
557fn active_unconditional_instead_rule(
558 services: ViewRewriteContext<'_>,
559 relation: &str,
560 event: crate::ast::RuleEvent,
561) -> Result<bool, SQLError> {
562 Ok(services
563 .catalog
564 .rules_for(relation, event)?
565 .iter()
566 .any(|rule| rule.definition.instead && rule.definition.condition.is_none()))
567}
568
569fn has_unconditional_instead_rule(
570 services: ViewRewriteContext<'_>,
571 relation: &str,
572 event: crate::ast::RuleEvent,
573) -> Result<bool, SQLError> {
574 Ok(services
575 .catalog
576 .rule_definitions_for(relation, event)?
577 .iter()
578 .any(|rule| rule.definition.instead && rule.definition.condition.is_none()))
579}
580
581pub fn validate_view_definition_check_option(
582 services: ViewRewriteContext<'_>,
583 name: &str,
584 definition: &StoredView,
585) -> Result<(), SQLError> {
586 if ViewCheckOption::from_options(&definition.options) == ViewCheckOption::None {
587 return Ok(());
588 }
589 let updatable =
590 automatic_view_layer_from_definition(services, name, definition)?.is_some_and(|layer| {
591 layer
592 .columns
593 .iter()
594 .any(|column| column.writable_source_column.is_some())
595 });
596 if updatable {
597 return Ok(());
598 }
599 Err(SQLError::Routine {
600 sqlstate: "0A000".into(),
601 message: "WITH CHECK OPTION is supported only on automatically updatable views".into(),
602 })
603}