1use super::rules::RuleCatalog;
10use crate::catalog::resolution::RelationNameResolution;
11use crate::plan::{
12 CommandPlan, CtePlan, CtePlanBody, QueryPlan, RelationalPlan, SourcePlan, UnifiedPlan,
13};
14use crate::SQLError;
15
16pub struct CteValidationContext<'a> {
17 pub catalog: &'a dyn RuleCatalog,
18 pub resolution: &'a RelationNameResolution,
19}
20
21pub fn validate_plan(
22 context: &CteValidationContext<'_>,
23 plan: &UnifiedPlan,
24) -> Result<(), SQLError> {
25 match plan {
26 UnifiedPlan::Query(query) => validate_query(context, query, true),
27 UnifiedPlan::Command(command) => validate_command(context, command, true),
28 }
29}
30
31fn contains_command(ctes: &[CtePlan]) -> bool {
32 ctes.iter().any(|cte| cte.body.modifies_data())
33}
34
35fn validate_ctes(
36 context: &CteValidationContext<'_>,
37 ctes: &[CtePlan],
38 top_level: bool,
39) -> Result<(), SQLError> {
40 if !top_level && contains_command(ctes) {
41 return Err(SQLError::Unsupported(
42 "WITH clause containing a data-modifying statement must be at the top level".into(),
43 ));
44 }
45 for cte in ctes {
46 if cte.recursive
47 && cte.body.modifies_data()
48 && crate::semantics::cte_references_own_name(cte)
49 {
50 return Err(SQLError::Routine {
51 sqlstate: "42P19".into(),
52 message: format!(
53 "recursive query \"{}\" must not contain data-modifying statements",
54 cte.name
55 ),
56 });
57 }
58 match &cte.body {
59 CtePlanBody::Query(query) => validate_query(context, query, false)?,
60 CtePlanBody::Command(command) => {
61 validate_command(context, command, false)?;
62 validate_command_rules(context, command)?;
63 }
64 }
65 }
66 Ok(())
67}
68
69fn validate_query(
70 context: &CteValidationContext<'_>,
71 query: &QueryPlan,
72 top_level: bool,
73) -> Result<(), SQLError> {
74 validate_ctes(context, &query.ctes, top_level)?;
75 match &query.root {
76 RelationalPlan::QueryBlock(block) => {
77 if let Some(source) = &block.from {
78 validate_source(context, source)?;
79 }
80 for query in &block.subqueries {
81 validate_query(context, query, false)?;
82 }
83 }
84 RelationalPlan::SetOp {
85 left,
86 right,
87 subqueries,
88 ..
89 } => {
90 validate_query(context, left, false)?;
91 validate_query(context, right, false)?;
92 for query in subqueries {
93 validate_query(context, query, false)?;
94 }
95 }
96 RelationalPlan::Values { subqueries, .. } => {
97 for query in subqueries {
98 validate_query(context, query, false)?;
99 }
100 }
101 }
102 Ok(())
103}
104
105fn validate_source(
106 context: &CteValidationContext<'_>,
107 source: &SourcePlan,
108) -> Result<(), SQLError> {
109 match source {
110 SourcePlan::Subquery { body, .. } => validate_query(context, body, false),
111 SourcePlan::Join { left, right, .. } => {
112 validate_source(context, left)?;
113 validate_source(context, right)
114 }
115 SourcePlan::Table { .. }
116 | SourcePlan::Values { .. }
117 | SourcePlan::Function { .. }
118 | SourcePlan::FunctionGroup { .. } => Ok(()),
119 }
120}
121
122fn validate_command(
123 context: &CteValidationContext<'_>,
124 command: &CommandPlan,
125 top_level: bool,
126) -> Result<(), SQLError> {
127 validate_ctes(context, command.ctes(), top_level)?;
128 for query in command.query_inputs() {
129 validate_query(context, query, false)?;
130 }
131 if let Some(source) = command.source_input() {
132 validate_source(context, source)?;
133 }
134 match command {
135 CommandPlan::CreateView { query, .. } => validate_query_owner(
136 context,
137 query,
138 "views must not contain data-modifying statements in WITH",
139 ),
140 CommandPlan::CreateMaterializedView { query, .. } => validate_query_owner(
141 context,
142 query,
143 "materialized views must not use data-modifying statements in WITH",
144 ),
145 CommandPlan::DeclareCursor { query, .. } => validate_query_owner(
146 context,
147 query,
148 "DECLARE CURSOR must not contain data-modifying statements in WITH",
149 ),
150 CommandPlan::CreateTableAs { query, .. } => validate_query(context, query, true),
151 CommandPlan::Explain { body, .. } | CommandPlan::Prepare { body, .. } => {
152 validate_plan(context, body)
153 }
154 _ => Ok(()),
155 }
156}
157
158fn validate_query_owner(
159 context: &CteValidationContext<'_>,
160 query: &QueryPlan,
161 message: &str,
162) -> Result<(), SQLError> {
163 validate_query(context, query, true)?;
164 if contains_command(&query.ctes) {
165 return Err(SQLError::Unsupported(message.into()));
166 }
167 Ok(())
168}
169
170fn validate_command_rules(
171 context: &CteValidationContext<'_>,
172 command: &CommandPlan,
173) -> Result<(), SQLError> {
174 use crate::ast::RuleEvent;
175 let (table, bound, event) = match command {
176 CommandPlan::Insert(plan) => (
177 plan.table.as_str(),
178 plan.target_relation_bound,
179 RuleEvent::Insert,
180 ),
181 CommandPlan::Update(plan) => (
182 plan.table.as_str(),
183 plan.target_relation_bound,
184 RuleEvent::Update,
185 ),
186 CommandPlan::Delete(plan) => (
187 plan.table.as_str(),
188 plan.target_relation_bound,
189 RuleEvent::Delete,
190 ),
191 _ => return Ok(()),
192 };
193 if crate::catalog::resolve_virtual_relation(&context.resolution.search_path, table).is_some() {
194 return Ok(());
196 }
197 let table = context.catalog.resolve_mutation_target(table, bound)?;
198 let rules = context.catalog.rules_for(&table, event)?;
199 if rules.is_empty() {
200 return Ok(());
201 }
202 super::rules::validate_rule_returning_contract(
203 context.catalog,
204 &table,
205 event,
206 command
207 .returning()
208 .is_some_and(|returning| !returning.is_empty()),
209 )?;
210 for rule in rules {
211 let rule = &rule.definition;
212 let kind = if !rule.instead && !rule.actions.is_empty() {
213 Some("DO ALSO")
214 } else if rule.instead && rule.condition.is_some() {
215 Some("conditional DO INSTEAD")
216 } else if rule.instead && rule.actions.is_empty() {
217 Some("DO INSTEAD NOTHING")
218 } else if rule.instead && rule.actions.len() > 1 {
219 Some("multi-statement DO INSTEAD")
220 } else {
221 None
222 };
223 if let Some(kind) = kind {
224 return Err(SQLError::Unsupported(format!(
225 "{kind} rules are not supported for data-modifying statements in WITH"
226 )));
227 }
228 }
229 Ok(())
230}