1use rupg_catalog::Rule;
6use rupg_common::{Error, Result, SqlState};
7use rupg_sql::nodes::{
8 A_Star, Alias, BoolExpr, BoolExprType, CmdType, ColumnRef, Node, RangeSubselect, RangeVar,
9 ResTarget, RuleStmt, SelectStmt, SetOperation, WithClause,
10};
11use rupg_types::oid;
12
13use crate::agg::Kind;
14use crate::expr::{BoolOp, BoolTest, Expr, ExprKind, Var};
15use crate::from::Column;
16use crate::insert::Generated;
17use crate::walk::{self, Walker};
18use crate::{Analyzer, Env, Params, Query};
19
20const RULE: &str = "*rule*";
22
23#[derive(Clone, Debug)]
25pub struct Product {
26 pub stmt: Node,
28 pub sets_tag: bool,
30 pub action: bool,
32 inverted: Vec<Node>,
34 fired: Option<u32>,
36 source: Source,
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41enum Source {
42 Original,
44 Instead,
46 QualInstead,
48 Also,
50}
51
52struct Item {
54 stmt: Node,
55 source: Source,
56 inverted: Vec<Node>,
57 fired: Option<u32>,
58}
59
60#[derive(Debug, Default)]
62pub(crate) struct State {
63 pub(crate) defining: bool,
65 pub(crate) fired: Option<u32>,
67 pub(crate) rows: bool,
69 pub(crate) columns: Vec<Column>,
71 pub(crate) inverted: Vec<Node>,
73}
74
75fn target_of(stmt: &Node) -> Option<(CmdType, &RangeVar)> {
77 match stmt {
78 Node::InsertStmt(s) => Some((CmdType::CMD_INSERT, s.relation.as_deref()?)),
79 Node::UpdateStmt(s) => Some((CmdType::CMD_UPDATE, s.relation.as_deref()?)),
80 Node::DeleteStmt(s) => Some((CmdType::CMD_DELETE, s.relation.as_deref()?)),
81 _ => None,
82 }
83}
84
85pub(crate) fn is_write(node: &Node) -> bool {
87 matches!(node, Node::InsertStmt(_) | Node::UpdateStmt(_) | Node::DeleteStmt(_))
88}
89
90fn event_code(event: CmdType) -> u8 {
92 match event {
93 CmdType::CMD_UPDATE => b'2',
94 CmdType::CMD_INSERT => b'3',
95 _ => b'4',
96 }
97}
98
99fn event_word(event: CmdType) -> &'static str {
101 match event {
102 CmdType::CMD_UPDATE => "UPDATE",
103 CmdType::CMD_INSERT => "INSERT",
104 _ => "DELETE",
105 }
106}
107
108pub(crate) fn fires(rule: &Rule) -> bool {
110 rule.enabled != b'D' && rule.enabled != b'R'
111}
112
113fn not_yet(what: &str) -> Error {
115 Error::new(SqlState::FEATURE_NOT_SUPPORTED, format!("{what} is not supported yet"))
116}
117
118struct UsesRow(bool);
120
121impl Walker for UsesRow {
122 fn node(&mut self, node: &Node) -> bool {
123 if let Node::ColumnRef(c) = node
124 && c.fields.len() > 1
125 && let Some(Some(Node::String(first))) = c.fields.first()
126 && matches!(&**first, "old" | "new")
127 {
128 self.0 = true;
129 return true;
130 }
131 walk::walk(self, node)
132 }
133
134 fn select(&mut self, stmt: &SelectStmt) -> bool {
135 walk::select(self, stmt)
136 }
137}
138
139fn uses_row(node: &Node) -> bool {
141 let mut w = UsesRow(false);
142 w.node(node);
143 w.0
144}
145
146fn writes_in_with(with: Option<&WithClause>) -> bool {
148 with.is_some_and(|w| {
149 w.ctes.iter().flatten().any(|cte| match cte {
150 Node::CommonTableExpr(c) => {
151 c.ctequery.as_ref().is_some_and(|q| is_write(q) || matches!(q, Node::MergeStmt(_)))
152 }
153 _ => false,
154 })
155 })
156}
157
158struct Parts {
160 base: Node,
161 returning: bool,
162 on_conflict: bool,
163 filter: bool,
164 from: bool,
165 with: Option<WithClause>,
166}
167
168fn parts(stmt: &Node) -> Parts {
169 let mut base = stmt.clone();
170 let (returning, on_conflict, filter, from, with) = match &mut base {
171 Node::InsertStmt(s) => (
172 s.returningClause.take().is_some(),
173 s.onConflictClause.is_some(),
174 false,
175 false,
176 s.withClause.as_deref().cloned(),
177 ),
178 Node::UpdateStmt(s) => (
179 s.returningClause.take().is_some(),
180 false,
181 s.whereClause.is_some(),
182 !s.fromClause.is_empty(),
183 s.withClause.as_deref().cloned(),
184 ),
185 Node::DeleteStmt(s) => (
186 s.returningClause.take().is_some(),
187 false,
188 s.whereClause.is_some(),
189 !s.usingClause.is_empty(),
190 s.withClause.as_deref().cloned(),
191 ),
192 _ => (false, false, false, false, None),
193 };
194 Parts { base, returning, on_conflict, filter, from, with }
195}
196
197fn rule_stmt(rule: &Rule) -> Result<RuleStmt> {
199 let (list, _) = rupg_sql::parse(&rule.text).map_err(Error::from)?;
200 list.iter()
201 .flatten()
202 .find_map(|node| match node {
203 Node::RawStmt(raw) => match raw.stmt.as_ref() {
204 Some(Node::RuleStmt(stmt)) => Some((**stmt).clone()),
205 _ => None,
206 },
207 _ => None,
208 })
209 .ok_or_else(|| Error::internal("the text of a rule is not a rule"))
210}
211
212fn and(node: Option<Node>, more: Option<&Node>) -> Option<Node> {
214 match (node, more) {
215 (Some(node), Some(more)) => Some(Node::BoolExpr(Box::new(BoolExpr {
216 boolop: BoolExprType::AND_EXPR,
217 args: vec![Some(node), Some(more.clone())],
218 location: -1,
219 }))),
220 (node, more) => node.or_else(|| more.cloned()),
221 }
222}
223
224fn rule_action(parts: &Parts, action: &Node, qual: Option<&Node>, event: CmdType) -> Result<Node> {
226 let needs_rows = uses_row(action)
227 || qual.is_some_and(uses_row)
228 || event == CmdType::CMD_INSERT
229 || parts.filter;
230 if !needs_rows && parts.from {
231 return Err(not_yet(
232 "a rule action that does not read OLD or NEW for a statement with FROM or USING",
233 ));
234 }
235 let rows = needs_rows.then(|| {
236 Node::RangeSubselect(Box::new(RangeSubselect {
237 lateral: false,
238 subquery: Some(parts.base.clone()),
239 alias: Some(Box::new(Alias { aliasname: Some(RULE.into()), colnames: Vec::new() })),
240 }))
241 });
242 let mut action = action.clone();
243 match &mut action {
244 Node::InsertStmt(s) => {
245 if s.returningClause.is_some() {
246 return Err(not_yet("RETURNING in a rule"));
247 }
248 if rows.is_none() && qual.is_none() {
249 return Ok(action);
250 }
251 let select = s.selectStmt.get_or_insert_with(|| Node::SelectStmt(Box::default()));
254 let Node::SelectStmt(select) = select else {
255 return Err(not_yet("a rule action INSERT that is not a SELECT"));
256 };
257 if select.op != SetOperation::SETOP_NONE {
258 return Err(Error::new(
259 SqlState::FEATURE_NOT_SUPPORTED,
260 "conditional UNION/INTERSECT/EXCEPT statements are not implemented",
261 ));
262 }
263 if !select.valuesLists.is_empty() {
264 values_to_select(select)?;
265 }
266 if let Some(rows) = rows {
267 select.fromClause.insert(0, Some(rows));
268 }
269 select.whereClause = and(select.whereClause.take(), qual);
270 }
271 Node::UpdateStmt(s) => {
272 if s.returningClause.is_some() {
273 return Err(not_yet("RETURNING in a rule"));
274 }
275 if let Some(rows) = rows {
276 s.fromClause.insert(0, Some(rows));
277 }
278 s.whereClause = and(s.whereClause.take(), qual);
279 }
280 Node::DeleteStmt(s) => {
281 if s.returningClause.is_some() {
282 return Err(not_yet("RETURNING in a rule"));
283 }
284 if let Some(rows) = rows {
285 s.usingClause.insert(0, Some(rows));
286 }
287 s.whereClause = and(s.whereClause.take(), qual);
288 }
289 Node::NotifyStmt(_) => return Err(not_yet("NOTIFY in a rule")),
290 _ => return Err(not_yet("SELECT in a rule")),
291 }
292 Ok(action)
293}
294
295fn values_to_select(select: &mut SelectStmt) -> Result<()> {
297 let has_default = select.valuesLists.iter().flatten().any(|row| match row {
298 Node::List(items) => items.iter().flatten().any(|n| matches!(n, Node::SetToDefault(_))),
299 _ => false,
300 });
301 if has_default {
302 return Err(not_yet("DEFAULT in VALUES of a rule action"));
303 }
304 if let [Some(Node::List(items))] = select.valuesLists.as_slice() {
305 select.targetList = items
306 .iter()
307 .map(|item| {
308 Some(Node::ResTarget(Box::new(ResTarget {
309 name: None,
310 indirection: Vec::new(),
311 val: item.clone(),
312 location: -1,
313 })))
314 })
315 .collect();
316 select.valuesLists.clear();
317 return Ok(());
318 }
319 let values = SelectStmt {
320 valuesLists: std::mem::take(&mut select.valuesLists),
321 ..SelectStmt::default()
322 };
323 let name = "*VALUES*";
324 select.fromClause.push(Some(Node::RangeSubselect(Box::new(RangeSubselect {
325 lateral: true,
326 subquery: Some(Node::SelectStmt(Box::new(values))),
327 alias: Some(Box::new(Alias { aliasname: Some(name.into()), colnames: Vec::new() })),
328 }))));
329 let star = ColumnRef {
330 fields: vec![Some(Node::String(name.into())), Some(Node::A_Star(Box::new(A_Star {})))],
331 location: -1,
332 };
333 select.targetList = vec![Some(Node::ResTarget(Box::new(ResTarget {
334 name: None,
335 indirection: Vec::new(),
336 val: Some(Node::ColumnRef(Box::new(star))),
337 location: -1,
338 })))];
339 Ok(())
340}
341
342pub fn query_rewrite(stmt: &Node, env: &dyn Env) -> Result<Option<Vec<Product>>> {
348 let Some(catalog) = env.catalog() else { return Ok(None) };
349 if !catalog.rules().any(|r| r.event != b'1') || target_of(stmt).is_none() {
350 return Ok(None);
351 }
352 let mut events = Vec::new();
353 let Some(items) = rewrite_query(stmt, Source::Original, env, &mut events)? else {
354 return Ok(None);
355 };
356 let kind = target_of(stmt).map(|(kind, _)| kind);
358 let tag = items.iter().position(|i| i.source == Source::Original).or_else(|| {
359 items.iter().rposition(|i| {
360 matches!(i.source, Source::Instead | Source::QualInstead)
361 && target_of(&i.stmt).map(|(k, _)| k) == kind
362 })
363 });
364 let products = items
365 .into_iter()
366 .enumerate()
367 .map(|(n, item)| Product {
368 stmt: item.stmt,
369 sets_tag: Some(n) == tag,
370 action: item.source != Source::Original,
371 inverted: item.inverted,
372 fired: item.fired,
373 source: item.source,
374 })
375 .collect();
376 Ok(Some(products))
377}
378
379pub fn copy_rules_error(products: &[Product]) -> Option<Error> {
381 let text = match products {
382 [] => "DO INSTEAD NOTHING rules are not supported for COPY",
383 [_] => return None,
384 _ => products
385 .iter()
386 .find_map(|p| match p.source {
387 Source::QualInstead => {
388 Some("conditional DO INSTEAD rules are not supported for COPY")
389 }
390 Source::Also => Some("DO ALSO rules are not supported for COPY"),
391 _ => None,
392 })
393 .unwrap_or("multi-statement DO INSTEAD rules are not supported for COPY"),
394 };
395 Some(Error::new(SqlState::FEATURE_NOT_SUPPORTED, text))
396}
397
398fn rewrite_query(
400 stmt: &Node,
401 source: Source,
402 env: &dyn Env,
403 events: &mut Vec<(u32, CmdType)>,
404) -> Result<Option<Vec<Item>>> {
405 let Some(catalog) = env.catalog() else { return Ok(None) };
406 let Some((event, rv)) = target_of(stmt) else { return Ok(None) };
407 let Some(oid) = Analyzer::new(env, &Params::default()).lookup_relation(rv)? else {
408 return Ok(None);
409 };
410 let name = catalog.relation(oid).map_or("", |r| r.name.as_str());
411 let code = event_code(event);
412 let mut rules: Vec<&Rule> =
413 catalog.rules().filter(|r| r.relation == oid && r.event == code && fires(r)).collect();
414 rules.sort_by(|a, b| a.name.cmp(&b.name));
415 let parts = parts(stmt);
416 let has_update = catalog.rules().any(|r| r.relation == oid && r.event == b'2');
417 if parts.on_conflict && (!rules.is_empty() || has_update) {
418 return Err(Error::new(
419 SqlState::FEATURE_NOT_SUPPORTED,
420 "INSERT with ON CONFLICT clause cannot be used with table that has INSERT or UPDATE rules",
421 ));
422 }
423 if rules.is_empty() {
424 return Ok(None);
425 }
426 if writes_in_with(parts.with.as_ref()) {
427 return Err(not_yet("a statement in WITH that changes data for a table with rules"));
428 }
429 let mut instead = false;
430 let mut inverted: Option<Vec<Node>> = None;
431 let mut products = Vec::new();
432 for rule in rules {
433 let rule_stmt = rule_stmt(rule)?;
434 let qual = rule_stmt.whereClause.as_ref();
435 let action_source = match (rule.instead, qual) {
436 (true, Some(_)) => Source::QualInstead,
437 (true, None) => {
438 instead = true;
439 Source::Instead
440 }
441 (false, _) => Source::Also,
442 };
443 if let (Source::QualInstead, Some(qual)) = (action_source, qual)
445 && !instead
446 {
447 inverted.get_or_insert_default().push(qual.clone());
448 }
449 for action in rule_stmt.actions.iter().flatten() {
450 products.push((rule_action(&parts, action, qual, event)?, action_source));
451 }
452 }
453 let mut result = Vec::new();
454 if !products.is_empty() {
455 if events.contains(&(oid, event)) {
456 return Err(Error::new(
457 SqlState::INVALID_OBJECT_DEFINITION,
458 format!("infinite recursion detected in rules for relation \"{name}\""),
459 ));
460 }
461 events.push((oid, event));
462 for (product, source) in products {
463 match rewrite_query(&product, source, env, events)? {
464 Some(items) => result.extend(items),
465 None => {
466 result.push(Item { stmt: product, source, inverted: Vec::new(), fired: None })
467 }
468 }
469 }
470 events.pop();
471 }
472 if (instead || inverted.is_some()) && parts.returning {
473 let word = event_word(event);
474 return Err(Error::new(
475 SqlState::FEATURE_NOT_SUPPORTED,
476 format!("cannot perform {word} RETURNING on relation \"{name}\""),
477 )
478 .with_hint(format!(
479 "You need an unconditional ON {word} DO INSTEAD rule with a RETURNING clause."
480 )));
481 }
482 if !instead {
484 let item = Item {
485 stmt: stmt.clone(),
486 source,
487 inverted: inverted.unwrap_or_default(),
488 fired: Some(oid),
489 };
490 if event == CmdType::CMD_INSERT {
491 result.insert(0, item);
492 } else {
493 result.push(item);
494 }
495 }
496 Ok(Some(result))
497}
498
499pub fn analyze_product(product: &Product, env: &dyn Env, params: &Params) -> Result<Query> {
505 let mut analyzer = Analyzer::new(env, params);
506 analyzer.rule.fired = product.fired;
507 analyzer.rule.inverted.clone_from(&product.inverted);
508 let query = crate::analyze_in(analyzer, &product.stmt, None);
509 if product.action { query.map_err(|error| error.with_position(None)) } else { query }
510}
511
512pub(crate) fn replace_row(expr: &mut Expr, row: &[Expr]) {
514 expr.each_mut(0, &mut |e, depth| {
515 let ExprKind::Var(var) = e.kind else { return false };
516 if var.levels_up != depth || var.relation != 0 {
517 return false;
518 }
519 let index = usize::try_from(var.attnum).ok().and_then(|n| n.checked_sub(1));
520 let Some(value) = index.and_then(|i| row.get(i)) else { return false };
521 let mut value = value.clone();
522 value.raise(depth);
523 *e = value;
524 true
525 });
526}
527
528pub(crate) fn named_row(columns: &[Column], row: Vec<Expr>) -> Vec<(String, Expr)> {
530 columns
531 .iter()
532 .zip(row)
533 .filter(|(c, _)| !c.is_dropped())
534 .map(|(c, e)| (c.name.clone(), e))
535 .collect()
536}
537
538pub(crate) fn relation_row(columns: &[Column], relation: usize, offset: usize) -> Vec<Expr> {
540 columns
541 .iter()
542 .enumerate()
543 .map(|(i, c)| {
544 let attnum = i16::try_from(offset + i + 1).unwrap_or(i16::MAX);
545 let mut expr = Expr::new(ExprKind::Var(Var { relation, attnum, levels_up: 0 }), c.ty);
546 expr.typmod = c.typmod;
547 expr
548 })
549 .collect()
550}
551
552pub(crate) fn with_generated(mut row: Vec<Expr>, generated: &[Generated]) -> Vec<Expr> {
554 let values = row.clone();
555 for (index, expr, _) in generated {
556 let mut expr = expr.clone();
557 replace_row(&mut expr, &values);
558 if let Some(slot) = row.get_mut(*index) {
559 *slot = expr;
560 }
561 }
562 row
563}
564
565pub(crate) fn and_filter(filter: Option<Expr>, more: Vec<Expr>) -> Option<Expr> {
567 let mut parts: Vec<Expr> = filter.into_iter().chain(more).collect();
568 match parts.len() {
569 0 => None,
570 1 => parts.pop(),
571 _ => Some(Expr::new(ExprKind::Bool(BoolOp::And, parts), oid::BOOL)),
572 }
573}
574
575impl Analyzer<'_> {
576 pub(crate) fn inverted_quals(
578 &mut self,
579 quals: &[Node],
580 old: Option<Vec<(String, Expr)>>,
581 new: Option<Vec<(String, Expr)>>,
582 ) -> Result<Vec<Expr>> {
583 let outer = std::mem::take(&mut self.scope);
584 let result = self.inverted_in_scope(quals, old, new);
585 self.scope = outer;
586 result
587 }
588
589 fn inverted_in_scope(
590 &mut self,
591 quals: &[Node],
592 old: Option<Vec<(String, Expr)>>,
593 new: Option<Vec<(String, Expr)>>,
594 ) -> Result<Vec<Expr>> {
595 for (name, row) in [("old", old), ("new", new)] {
596 if let Some(row) = row {
597 let item = self.rule_item(name, row);
598 self.push_item(item);
599 }
600 }
601 let mut out = Vec::with_capacity(quals.len());
602 for qual in quals {
603 let expr = self.with_kind(Kind::Where, |a| a.transform(Some(qual)))?;
604 let expr = self.coerce_to_boolean(expr, "WHERE")?;
605 out.push(Expr::new(
606 ExprKind::BooleanTest(Box::new(expr), BoolTest::IsNotTrue),
607 oid::BOOL,
608 ));
609 }
610 Ok(out)
611 }
612}