1use rupg_common::{Error, Result, SqlState};
4use rupg_pgcatalog::builtin;
5use rupg_sql::nodes::{CTECycleClause, CTESearchClause, List, Node};
6use rupg_types::{Value, oid};
7
8use crate::Analyzer;
9use crate::coerce::AtOpt;
10use crate::expr::{Case, CastForm, Expr, ExprKind, Func, FuncForm, Var};
11use crate::from::{Column, FromItem, Relation};
12use crate::select::{Query, Target};
13use crate::setop::SetTree;
14use crate::sort::set_group;
15use crate::typcache;
16use crate::typename::place;
17use crate::types;
18
19const RECORD_EQ_OP: u32 = 2988;
21const ARRAY_CAT: u32 = 383;
23const INT8INC: u32 = 1219;
25
26#[derive(Clone, Debug, PartialEq)]
28pub struct SearchClause {
29 pub columns: Vec<String>,
31 pub breadth: bool,
33 pub seq: String,
35 location: Option<usize>,
36}
37
38#[derive(Clone, Debug, PartialEq)]
40pub struct CycleClause {
41 pub columns: Vec<String>,
43 pub mark: String,
45 pub value: Expr,
47 pub default: Expr,
49 pub ty: u32,
51 pub typmod: i32,
53 pub path: String,
55 ne: (u32, u32),
57 location: Option<usize>,
58}
59
60fn strings(list: &List) -> Vec<String> {
62 list.iter()
63 .filter_map(|n| match n {
64 Some(Node::String(s)) => Some(s.to_string()),
65 _ => None,
66 })
67 .collect()
68}
69
70pub(crate) fn search_clause(raw: &CTESearchClause) -> SearchClause {
72 SearchClause {
73 columns: strings(&raw.search_col_list),
74 breadth: raw.search_breadth_first,
75 seq: raw.search_seq_column.as_deref().unwrap_or_default().to_string(),
76 location: place(raw.location),
77 }
78}
79
80pub(crate) fn extra_columns(
82 search: Option<&SearchClause>,
83 cycle: Option<&CycleClause>,
84) -> Vec<Column> {
85 let mut columns = Vec::new();
86 let column =
87 |name: &str, ty, typmod| Column { name: name.to_string(), ty, typmod, not_null: false };
88 if let Some(s) = search {
89 let ty = if s.breadth { oid::RECORD } else { oid::RECORD_ARRAY };
90 columns.push(column(&s.seq, ty, -1));
91 }
92 if let Some(c) = cycle {
93 columns.push(column(&c.mark, c.ty, c.typmod));
94 columns.push(column(&c.path, oid::RECORD_ARRAY, -1));
95 }
96 columns
97}
98
99fn syntax(message: String, at: Option<usize>) -> Error {
101 Error::new(SqlState::SYNTAX_ERROR, message).at_opt(at)
102}
103
104fn check_columns(what: &str, list: &[String], names: &[&str], at: Option<usize>) -> Result<()> {
106 for (i, name) in list.iter().enumerate() {
107 if !names.contains(&name.as_str()) {
108 return Err(syntax(
109 format!("{what} column \"{name}\" not in WITH query column list"),
110 at,
111 ));
112 }
113 if list[..i].contains(name) {
114 return Err(Error::new(
115 SqlState::DUPLICATE_COLUMN,
116 format!("{what} column \"{name}\" specified more than once"),
117 )
118 .at_opt(at));
119 }
120 }
121 Ok(())
122}
123
124pub(crate) fn check(
126 columns: &[Column],
127 recursive: bool,
128 location: Option<usize>,
129 search: Option<&SearchClause>,
130 cycle: Option<&CycleClause>,
131 query: &Query,
132) -> Result<()> {
133 if !recursive {
134 return Err(syntax("WITH query is not recursive".to_string(), location));
135 }
136 let Some(op) = &query.set_op else {
137 return Err(Error::internal("a recursive query with no set operation"));
138 };
139 if !matches!(op.left, SetTree::Leaf(_)) {
140 return Err(Error::new(
141 SqlState::FEATURE_NOT_SUPPORTED,
142 "with a SEARCH or CYCLE clause, the left side of the UNION must be a SELECT",
143 ));
144 }
145 if !matches!(op.right, SetTree::Leaf(_)) {
146 return Err(syntax(
147 "with a SEARCH or CYCLE clause, the right side of the UNION must be a SELECT"
148 .to_string(),
149 None,
150 ));
151 }
152 let names: Vec<&str> = columns.iter().map(|c| c.name.as_str()).collect();
153 if let Some(s) = search {
154 check_columns("search", &s.columns, &names, s.location)?;
155 if names.contains(&s.seq.as_str()) {
156 return Err(syntax(
157 format!(
158 "search sequence column name \"{}\" already used in WITH query column list",
159 s.seq
160 ),
161 s.location,
162 ));
163 }
164 }
165 if let Some(c) = cycle {
166 check_columns("cycle", &c.columns, &names, c.location)?;
167 if names.contains(&c.mark.as_str()) {
168 return Err(syntax(
169 format!(
170 "cycle mark column name \"{}\" already used in WITH query column list",
171 c.mark
172 ),
173 c.location,
174 ));
175 }
176 if names.contains(&c.path.as_str()) {
177 return Err(syntax(
178 format!(
179 "cycle path column name \"{}\" already used in WITH query column list",
180 c.path
181 ),
182 c.location,
183 ));
184 }
185 if c.mark == c.path {
186 return Err(syntax(
187 "cycle mark column name and cycle path column name are the same".to_string(),
188 c.location,
189 ));
190 }
191 }
192 if let (Some(s), Some(c)) = (search, cycle) {
193 if s.seq == c.mark {
194 return Err(syntax(
195 "search sequence column name and cycle mark column name are the same".to_string(),
196 s.location,
197 ));
198 }
199 if s.seq == c.path {
200 return Err(syntax(
201 "search sequence column name and cycle path column name are the same".to_string(),
202 s.location,
203 ));
204 }
205 }
206 Ok(())
207}
208
209fn var(relation: usize, index: usize, ty: u32, typmod: i32) -> Expr {
211 let attnum = i16::try_from(index + 1).unwrap_or(i16::MAX);
212 let var = Var { relation, attnum, levels_up: 0 };
213 Expr { kind: ExprKind::Var(var), ty, typmod, location: None }
214}
215
216fn call(func: u32, args: Vec<Expr>, ty: u32) -> Expr {
218 let func = Func { oid: func, args, form: FuncForm::Call, variadic: false, retset: false };
219 Expr::new(ExprKind::Func(func), ty)
220}
221
222fn row_of(list: &[String], columns: &[Column]) -> Vec<Expr> {
224 list.iter()
225 .filter_map(|name| {
226 let i = columns.iter().position(|c| &c.name == name)?;
227 Some(var(0, i, columns[i].ty, columns[i].typmod))
228 })
229 .collect()
230}
231
232fn row(args: Vec<Expr>) -> Expr {
234 Expr::new(ExprKind::Row(args, CastForm::Implicit), oid::RECORD)
235}
236
237fn array(args: Vec<Expr>) -> Expr {
239 let elements = vec![row(args)];
240 Expr::new(
241 ExprKind::Array { element: oid::RECORD, multidims: false, elements },
242 oid::RECORD_ARRAY,
243 )
244}
245
246fn path_cat(args: Vec<Expr>, index: usize) -> Expr {
248 let path = var(0, index, oid::RECORD_ARRAY, -1);
249 call(ARRAY_CAT, vec![path, array(args)], oid::RECORD_ARRAY)
250}
251
252fn wrap(name: &str, columns: Vec<Column>, sub: Query, targets: Vec<Target>) -> Query {
254 let mut relation = Relation::new(0, columns, Some(Box::new(sub)), None, None);
255 relation.name = name.to_string();
256 Query {
257 relations: vec![relation],
258 from: vec![FromItem::Relation(0)],
259 targets,
260 ..Query::default()
261 }
262}
263
264fn target(name: &str, expr: Expr) -> Target {
266 Target { name: name.to_string(), expr, origin: None, junk: false }
267}
268
269fn raise(query: &mut Query) {
271 query.each_expr_mut(0, &mut |expr, depth| expr.raise_from(1, depth + 1));
272}
273
274pub(crate) fn rewrite(
276 name: &str,
277 columns: &[Column],
278 work: Option<usize>,
279 search: Option<&SearchClause>,
280 cycle: Option<&CycleClause>,
281 query: &Query,
282) -> Result<Query> {
283 let mut new = query.clone();
284 let Some(op) = new.set_op.as_mut() else {
285 return Err(Error::internal("a recursive query with no set operation"));
286 };
287 let (SetTree::Leaf(left), SetTree::Leaf(right)) = (&op.left, &op.right) else {
288 return Err(Error::internal("a term of SEARCH or CYCLE that is not a SELECT"));
289 };
290 let (left, right) = (*left, *right);
291 let width = columns.len();
292 let extras = extra_columns(search, cycle);
293 let mut all = columns.to_vec();
294 all.extend(extras.iter().cloned());
295 let plain: Vec<Target> = columns
296 .iter()
297 .enumerate()
298 .map(|(i, c)| target(&c.name, var(0, i, c.ty, c.typmod)))
299 .collect();
300
301 let Some(q1) = new.relations[left].subquery.as_deref() else {
303 return Err(Error::internal("a SELECT of a set operation with no query"));
304 };
305 let mut q1 = q1.clone();
306 raise(&mut q1);
307 let mut targets = plain.clone();
308 if let Some(s) = search {
309 let args = row_of(&s.columns, columns);
310 let expr = if s.breadth {
311 let zero = Expr::constant(Value::Int8(0), oid::INT8, -1, None);
312 row(std::iter::once(zero).chain(args).collect())
313 } else {
314 array(args)
315 };
316 targets.push(target(&s.seq, expr));
317 }
318 if let Some(c) = cycle {
319 targets.push(target(&c.mark, c.default.clone()));
320 targets.push(target(&c.path, array(row_of(&c.columns, columns))));
321 }
322 let newq1 = wrap("*TLOCRN*", columns.to_vec(), q1, targets);
323
324 let Some(q2) = new.relations[right].subquery.as_deref() else {
326 return Err(Error::internal("a SELECT of a set operation with no query"));
327 };
328 let mut q2 = q2.clone();
329 let reference = q2.relations.iter().position(|r| {
330 r.cte.as_ref().is_some_and(|c| c.name == name && c.work.is_some() && c.work == work)
331 });
332 let Some(reference) = reference else {
333 return Err(Error::new(
334 SqlState::FEATURE_NOT_SUPPORTED,
335 format!(
336 "with a SEARCH or CYCLE clause, the recursive reference to WITH query \"{name}\" must be at the top level of its right-hand SELECT"
337 ),
338 ));
339 };
340 if q2.targets.iter().any(|t| t.junk) {
341 return Err(Error::new(
342 SqlState::FEATURE_NOT_SUPPORTED,
343 "with a SEARCH or CYCLE clause, a right-hand SELECT with a junk column is not supported yet",
344 ));
345 }
346 raise(&mut q2);
347 for (k, extra) in extras.iter().enumerate() {
348 q2.targets.push(target(&extra.name, var(reference, width + k, extra.ty, extra.typmod)));
349 }
350 let mut targets = plain;
351 let mut next = width;
352 if let Some(s) = search {
353 let args = row_of(&s.columns, columns);
354 let expr = if s.breadth {
355 let seq = var(0, next, oid::RECORD, -1);
356 let depth = Expr::new(ExprKind::FieldSelect(Box::new(seq), 0), oid::INT8);
357 let depth = call(INT8INC, vec![depth], oid::INT8);
358 row(std::iter::once(depth).chain(args).collect())
359 } else {
360 path_cat(args, next)
361 };
362 targets.push(target(&s.seq, expr));
363 next += 1;
364 }
365 let mut filter = None;
366 if let Some(c) = cycle {
367 let (mark, path) = (next, next + 1);
368 let eq = builtin::operator_by_oid(RECORD_EQ_OP).map_or(0, |o| o.code);
369 let path_var = var(0, path, oid::RECORD_ARRAY, -1);
370 let args = vec![row(row_of(&c.columns, columns)), path_var];
371 let found = Expr::new(ExprKind::ScalarArrayOp { func: eq, any: true, args }, oid::BOOL);
372 let case = Case {
373 arg: None,
374 whens: vec![(found, c.value.clone())],
375 default: Box::new(c.default.clone()),
376 };
377 let case = Expr { typmod: c.typmod, ..Expr::new(ExprKind::Case(case), c.ty) };
378 targets.push(target(&c.mark, case));
379 targets.push(target(&c.path, path_cat(row_of(&c.columns, columns), path)));
380 let (ne, func) = c.ne;
381 let args = vec![var(0, mark, c.ty, c.typmod), c.value.clone()];
382 let ne =
383 Func { oid: func, args, form: FuncForm::Operator(ne), variadic: false, retset: false };
384 filter = Some(Expr::new(ExprKind::Func(ne), oid::BOOL));
385 }
386 let mut newq2 = wrap("*TROCRN*", all, q2, targets);
387 newq2.filter = filter;
388
389 for (k, extra) in extras.iter().enumerate() {
391 op.columns.push((extra.ty, extra.typmod));
392 if !op.all {
393 let mut group = set_group(width + k, extra.ty, None)?;
394 if matches!(extra.ty, oid::RECORD | oid::RECORD_ARRAY) {
395 group.hashable = true;
396 }
397 op.groups.push(group);
398 }
399 }
400 for (index, sub) in [(left, newq1), (right, newq2)] {
401 let relation = &mut new.relations[index];
402 relation.subquery = Some(Box::new(sub));
403 relation.columns.extend(extras.iter().cloned());
404 }
405 for (k, extra) in extras.iter().enumerate() {
406 new.targets.push(target(&extra.name, var(left, width + k, extra.ty, extra.typmod)));
407 }
408 Ok(new)
409}
410
411impl Analyzer<'_> {
412 pub(crate) fn cycle_clause(&mut self, raw: &CTECycleClause) -> Result<CycleClause> {
414 let value = self.transform(raw.cycle_mark_value.as_ref())?;
415 let default = self.transform(raw.cycle_mark_default.as_ref())?;
416 let ty = self.common_type(&[value.clone(), default.clone()], "CYCLE")?;
417 let value = self.coerce_to_common(value, ty, "CYCLE/SET/TO")?;
418 let default = self.coerce_to_common(default, ty, "CYCLE/SET/DEFAULT")?;
419 let typmod = if value.typmod == default.typmod { value.typmod } else { -1 };
420 let eq = typcache::operators(ty).eq;
421 if eq == 0 {
422 return Err(Error::new(
423 SqlState::UNDEFINED_FUNCTION,
424 format!("could not identify an equality operator for type {}", types::name(ty)),
425 ));
426 }
427 let ne = builtin::operator_by_oid(eq).map_or(0, |o| o.negator);
428 let Some(func) = builtin::operator_by_oid(ne).map(|o| o.code).filter(|&f| f != 0) else {
429 return Err(Error::new(
430 SqlState::UNDEFINED_FUNCTION,
431 format!("could not identify an inequality operator for type {}", types::name(ty)),
432 ));
433 };
434 Ok(CycleClause {
435 columns: strings(&raw.cycle_col_list),
436 mark: raw.cycle_mark_column.as_deref().unwrap_or_default().to_string(),
437 value,
438 default,
439 ty,
440 typmod,
441 path: raw.cycle_path_column.as_deref().unwrap_or_default().to_string(),
442 ne: (ne, func),
443 location: place(raw.location),
444 })
445 }
446}