/src/postgres/src/backend/rewrite/rewriteSearchCycle.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * rewriteSearchCycle.c |
4 | | * Support for rewriting SEARCH and CYCLE clauses. |
5 | | * |
6 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * IDENTIFICATION |
10 | | * src/backend/rewrite/rewriteSearchCycle.c |
11 | | * |
12 | | *------------------------------------------------------------------------- |
13 | | */ |
14 | | #include "postgres.h" |
15 | | |
16 | | #include "catalog/pg_operator_d.h" |
17 | | #include "catalog/pg_type_d.h" |
18 | | #include "nodes/makefuncs.h" |
19 | | #include "nodes/parsenodes.h" |
20 | | #include "nodes/pg_list.h" |
21 | | #include "nodes/primnodes.h" |
22 | | #include "parser/analyze.h" |
23 | | #include "parser/parsetree.h" |
24 | | #include "rewrite/rewriteManip.h" |
25 | | #include "rewrite/rewriteSearchCycle.h" |
26 | | #include "utils/fmgroids.h" |
27 | | |
28 | | |
29 | | /*---------- |
30 | | * Rewrite a CTE with SEARCH or CYCLE clause |
31 | | * |
32 | | * Consider a CTE like |
33 | | * |
34 | | * WITH RECURSIVE ctename (col1, col2, col3) AS ( |
35 | | * query1 |
36 | | * UNION [ALL] |
37 | | * SELECT trosl FROM ctename |
38 | | * ) |
39 | | * |
40 | | * With a search clause |
41 | | * |
42 | | * SEARCH BREADTH FIRST BY col1, col2 SET sqc |
43 | | * |
44 | | * the CTE is rewritten to |
45 | | * |
46 | | * WITH RECURSIVE ctename (col1, col2, col3, sqc) AS ( |
47 | | * SELECT col1, col2, col3, -- original WITH column list |
48 | | * ROW(0, col1, col2) -- initial row of search columns |
49 | | * FROM (query1) "*TLOCRN*" (col1, col2, col3) |
50 | | * UNION [ALL] |
51 | | * SELECT col1, col2, col3, -- same as above |
52 | | * ROW(sqc.depth + 1, col1, col2) -- count depth |
53 | | * FROM (SELECT trosl, ctename.sqc FROM ctename) "*TROCRN*" (col1, col2, col3, sqc) |
54 | | * ) |
55 | | * |
56 | | * (This isn't quite legal SQL: sqc.depth is meant to refer to the first |
57 | | * column of sqc, which has a row type, but the field names are not defined |
58 | | * here. Representing this properly in SQL would be more complicated (and the |
59 | | * SQL standard actually does it in that more complicated way), but the |
60 | | * internal representation allows us to construct it this way.) |
61 | | * |
62 | | * With a search clause |
63 | | * |
64 | | * SEARCH DEPTH FIRST BY col1, col2 SET sqc |
65 | | * |
66 | | * the CTE is rewritten to |
67 | | * |
68 | | * WITH RECURSIVE ctename (col1, col2, col3, sqc) AS ( |
69 | | * SELECT col1, col2, col3, -- original WITH column list |
70 | | * ARRAY[ROW(col1, col2)] -- initial row of search columns |
71 | | * FROM (query1) "*TLOCRN*" (col1, col2, col3) |
72 | | * UNION [ALL] |
73 | | * SELECT col1, col2, col3, -- same as above |
74 | | * sqc || ARRAY[ROW(col1, col2)] -- record rows seen |
75 | | * FROM (SELECT trosl, ctename.sqc FROM ctename) "*TROCRN*" (col1, col2, col3, sqc) |
76 | | * ) |
77 | | * |
78 | | * With a cycle clause |
79 | | * |
80 | | * CYCLE col1, col2 SET cmc TO 'Y' DEFAULT 'N' USING cpa |
81 | | * |
82 | | * (cmc = cycle mark column, cpa = cycle path) the CTE is rewritten to |
83 | | * |
84 | | * WITH RECURSIVE ctename (col1, col2, col3, cmc, cpa) AS ( |
85 | | * SELECT col1, col2, col3, -- original WITH column list |
86 | | * 'N', -- cycle mark default |
87 | | * ARRAY[ROW(col1, col2)] -- initial row of cycle columns |
88 | | * FROM (query1) "*TLOCRN*" (col1, col2, col3) |
89 | | * UNION [ALL] |
90 | | * SELECT col1, col2, col3, -- same as above |
91 | | * CASE WHEN ROW(col1, col2) = ANY (ARRAY[cpa]) THEN 'Y' ELSE 'N' END, -- compute cycle mark column |
92 | | * cpa || ARRAY[ROW(col1, col2)] -- record rows seen |
93 | | * FROM (SELECT trosl, ctename.cmc, ctename.cpa FROM ctename) "*TROCRN*" (col1, col2, col3, cmc, cpa) |
94 | | * WHERE cmc <> 'Y' |
95 | | * ) |
96 | | * |
97 | | * The expression to compute the cycle mark column in the right-hand query is |
98 | | * written as |
99 | | * |
100 | | * CASE WHEN ROW(col1, col2) IN (SELECT p.* FROM TABLE(cpa) p) THEN cmv ELSE cmd END |
101 | | * |
102 | | * in the SQL standard, but in PostgreSQL we can use the scalar-array operator |
103 | | * expression shown above. |
104 | | * |
105 | | * Also, in some of the cases where operators are shown above we actually |
106 | | * directly produce the underlying function call. |
107 | | * |
108 | | * If both a search clause and a cycle clause is specified, then the search |
109 | | * clause column is added before the cycle clause columns. |
110 | | */ |
111 | | |
112 | | /* |
113 | | * Make a RowExpr from the specified column names, which have to be among the |
114 | | * output columns of the CTE. |
115 | | */ |
116 | | static RowExpr * |
117 | | make_path_rowexpr(const CommonTableExpr *cte, const List *col_list) |
118 | 0 | { |
119 | 0 | RowExpr *rowexpr; |
120 | 0 | ListCell *lc; |
121 | |
|
122 | 0 | rowexpr = makeNode(RowExpr); |
123 | 0 | rowexpr->row_typeid = RECORDOID; |
124 | 0 | rowexpr->row_format = COERCE_IMPLICIT_CAST; |
125 | 0 | rowexpr->location = -1; |
126 | |
|
127 | 0 | foreach(lc, col_list) |
128 | 0 | { |
129 | 0 | char *colname = strVal(lfirst(lc)); |
130 | |
|
131 | 0 | for (int i = 0; i < list_length(cte->ctecolnames); i++) |
132 | 0 | { |
133 | 0 | char *colname2 = strVal(list_nth(cte->ctecolnames, i)); |
134 | |
|
135 | 0 | if (strcmp(colname, colname2) == 0) |
136 | 0 | { |
137 | 0 | Var *var; |
138 | |
|
139 | 0 | var = makeVar(1, i + 1, |
140 | 0 | list_nth_oid(cte->ctecoltypes, i), |
141 | 0 | list_nth_int(cte->ctecoltypmods, i), |
142 | 0 | list_nth_oid(cte->ctecolcollations, i), |
143 | 0 | 0); |
144 | 0 | rowexpr->args = lappend(rowexpr->args, var); |
145 | 0 | rowexpr->colnames = lappend(rowexpr->colnames, makeString(colname)); |
146 | 0 | break; |
147 | 0 | } |
148 | 0 | } |
149 | 0 | } |
150 | |
|
151 | 0 | return rowexpr; |
152 | 0 | } |
153 | | |
154 | | /* |
155 | | * Wrap a RowExpr in an ArrayExpr, for the initial search depth first or cycle |
156 | | * row. |
157 | | */ |
158 | | static Expr * |
159 | | make_path_initial_array(RowExpr *rowexpr) |
160 | 0 | { |
161 | 0 | ArrayExpr *arr; |
162 | |
|
163 | 0 | arr = makeNode(ArrayExpr); |
164 | 0 | arr->array_typeid = RECORDARRAYOID; |
165 | 0 | arr->element_typeid = RECORDOID; |
166 | 0 | arr->location = -1; |
167 | 0 | arr->elements = list_make1(rowexpr); |
168 | |
|
169 | 0 | return (Expr *) arr; |
170 | 0 | } |
171 | | |
172 | | /* |
173 | | * Make an array catenation expression like |
174 | | * |
175 | | * cpa || ARRAY[ROW(cols)] |
176 | | * |
177 | | * where the varattno of cpa is provided as path_varattno. |
178 | | */ |
179 | | static Expr * |
180 | | make_path_cat_expr(RowExpr *rowexpr, AttrNumber path_varattno) |
181 | 0 | { |
182 | 0 | ArrayExpr *arr; |
183 | 0 | FuncExpr *fexpr; |
184 | |
|
185 | 0 | arr = makeNode(ArrayExpr); |
186 | 0 | arr->array_typeid = RECORDARRAYOID; |
187 | 0 | arr->element_typeid = RECORDOID; |
188 | 0 | arr->location = -1; |
189 | 0 | arr->elements = list_make1(rowexpr); |
190 | |
|
191 | 0 | fexpr = makeFuncExpr(F_ARRAY_CAT, RECORDARRAYOID, |
192 | 0 | list_make2(makeVar(1, path_varattno, RECORDARRAYOID, -1, 0, 0), |
193 | 0 | arr), |
194 | 0 | InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); |
195 | |
|
196 | 0 | return (Expr *) fexpr; |
197 | 0 | } |
198 | | |
199 | | /* |
200 | | * The real work happens here. |
201 | | */ |
202 | | CommonTableExpr * |
203 | | rewriteSearchAndCycle(CommonTableExpr *cte) |
204 | 0 | { |
205 | 0 | Query *ctequery; |
206 | 0 | SetOperationStmt *sos; |
207 | 0 | int rti1, |
208 | 0 | rti2; |
209 | 0 | RangeTblEntry *rte1, |
210 | 0 | *rte2, |
211 | 0 | *newrte; |
212 | 0 | Query *newq1, |
213 | 0 | *newq2; |
214 | 0 | Query *newsubquery; |
215 | 0 | RangeTblRef *rtr; |
216 | 0 | Oid search_seq_type = InvalidOid; |
217 | 0 | AttrNumber sqc_attno = InvalidAttrNumber; |
218 | 0 | AttrNumber cmc_attno = InvalidAttrNumber; |
219 | 0 | AttrNumber cpa_attno = InvalidAttrNumber; |
220 | 0 | TargetEntry *tle; |
221 | 0 | RowExpr *cycle_col_rowexpr = NULL; |
222 | 0 | RowExpr *search_col_rowexpr = NULL; |
223 | 0 | List *ewcl; |
224 | 0 | int cte_rtindex = -1; |
225 | |
|
226 | 0 | Assert(cte->search_clause || cte->cycle_clause); |
227 | |
|
228 | 0 | cte = copyObject(cte); |
229 | |
|
230 | 0 | ctequery = castNode(Query, cte->ctequery); |
231 | | |
232 | | /* |
233 | | * The top level of the CTE's query should be a UNION. Find the two |
234 | | * subqueries. |
235 | | */ |
236 | 0 | Assert(ctequery->setOperations); |
237 | 0 | sos = castNode(SetOperationStmt, ctequery->setOperations); |
238 | 0 | Assert(sos->op == SETOP_UNION); |
239 | |
|
240 | 0 | rti1 = castNode(RangeTblRef, sos->larg)->rtindex; |
241 | 0 | rti2 = castNode(RangeTblRef, sos->rarg)->rtindex; |
242 | |
|
243 | 0 | rte1 = rt_fetch(rti1, ctequery->rtable); |
244 | 0 | rte2 = rt_fetch(rti2, ctequery->rtable); |
245 | |
|
246 | 0 | Assert(rte1->rtekind == RTE_SUBQUERY); |
247 | 0 | Assert(rte2->rtekind == RTE_SUBQUERY); |
248 | | |
249 | | /* |
250 | | * We'll need this a few times later. |
251 | | */ |
252 | 0 | if (cte->search_clause) |
253 | 0 | { |
254 | 0 | if (cte->search_clause->search_breadth_first) |
255 | 0 | search_seq_type = RECORDOID; |
256 | 0 | else |
257 | 0 | search_seq_type = RECORDARRAYOID; |
258 | 0 | } |
259 | | |
260 | | /* |
261 | | * Attribute numbers of the added columns in the CTE's column list |
262 | | */ |
263 | 0 | if (cte->search_clause) |
264 | 0 | sqc_attno = list_length(cte->ctecolnames) + 1; |
265 | 0 | if (cte->cycle_clause) |
266 | 0 | { |
267 | 0 | cmc_attno = list_length(cte->ctecolnames) + 1; |
268 | 0 | cpa_attno = list_length(cte->ctecolnames) + 2; |
269 | 0 | if (cte->search_clause) |
270 | 0 | { |
271 | 0 | cmc_attno++; |
272 | 0 | cpa_attno++; |
273 | 0 | } |
274 | 0 | } |
275 | | |
276 | | /* |
277 | | * Make new left subquery |
278 | | */ |
279 | 0 | newq1 = makeNode(Query); |
280 | 0 | newq1->commandType = CMD_SELECT; |
281 | 0 | newq1->canSetTag = true; |
282 | |
|
283 | 0 | newrte = makeNode(RangeTblEntry); |
284 | 0 | newrte->rtekind = RTE_SUBQUERY; |
285 | 0 | newrte->alias = NULL; |
286 | 0 | newrte->eref = makeAlias("*TLOCRN*", cte->ctecolnames); |
287 | 0 | newsubquery = copyObject(rte1->subquery); |
288 | 0 | IncrementVarSublevelsUp((Node *) newsubquery, 1, 1); |
289 | 0 | newrte->subquery = newsubquery; |
290 | 0 | newrte->inFromCl = true; |
291 | 0 | newq1->rtable = list_make1(newrte); |
292 | |
|
293 | 0 | rtr = makeNode(RangeTblRef); |
294 | 0 | rtr->rtindex = 1; |
295 | 0 | newq1->jointree = makeFromExpr(list_make1(rtr), NULL); |
296 | | |
297 | | /* |
298 | | * Make target list |
299 | | */ |
300 | 0 | for (int i = 0; i < list_length(cte->ctecolnames); i++) |
301 | 0 | { |
302 | 0 | Var *var; |
303 | |
|
304 | 0 | var = makeVar(1, i + 1, |
305 | 0 | list_nth_oid(cte->ctecoltypes, i), |
306 | 0 | list_nth_int(cte->ctecoltypmods, i), |
307 | 0 | list_nth_oid(cte->ctecolcollations, i), |
308 | 0 | 0); |
309 | 0 | tle = makeTargetEntry((Expr *) var, i + 1, strVal(list_nth(cte->ctecolnames, i)), false); |
310 | 0 | tle->resorigtbl = list_nth_node(TargetEntry, rte1->subquery->targetList, i)->resorigtbl; |
311 | 0 | tle->resorigcol = list_nth_node(TargetEntry, rte1->subquery->targetList, i)->resorigcol; |
312 | 0 | newq1->targetList = lappend(newq1->targetList, tle); |
313 | 0 | } |
314 | |
|
315 | 0 | if (cte->search_clause) |
316 | 0 | { |
317 | 0 | Expr *texpr; |
318 | |
|
319 | 0 | search_col_rowexpr = make_path_rowexpr(cte, cte->search_clause->search_col_list); |
320 | 0 | if (cte->search_clause->search_breadth_first) |
321 | 0 | { |
322 | 0 | search_col_rowexpr->args = lcons(makeConst(INT8OID, -1, InvalidOid, sizeof(int64), |
323 | 0 | Int64GetDatum(0), false, true), |
324 | 0 | search_col_rowexpr->args); |
325 | 0 | search_col_rowexpr->colnames = lcons(makeString("*DEPTH*"), search_col_rowexpr->colnames); |
326 | 0 | texpr = (Expr *) search_col_rowexpr; |
327 | 0 | } |
328 | 0 | else |
329 | 0 | texpr = make_path_initial_array(search_col_rowexpr); |
330 | 0 | tle = makeTargetEntry(texpr, |
331 | 0 | list_length(newq1->targetList) + 1, |
332 | 0 | cte->search_clause->search_seq_column, |
333 | 0 | false); |
334 | 0 | newq1->targetList = lappend(newq1->targetList, tle); |
335 | 0 | } |
336 | 0 | if (cte->cycle_clause) |
337 | 0 | { |
338 | 0 | tle = makeTargetEntry((Expr *) cte->cycle_clause->cycle_mark_default, |
339 | 0 | list_length(newq1->targetList) + 1, |
340 | 0 | cte->cycle_clause->cycle_mark_column, |
341 | 0 | false); |
342 | 0 | newq1->targetList = lappend(newq1->targetList, tle); |
343 | 0 | cycle_col_rowexpr = make_path_rowexpr(cte, cte->cycle_clause->cycle_col_list); |
344 | 0 | tle = makeTargetEntry(make_path_initial_array(cycle_col_rowexpr), |
345 | 0 | list_length(newq1->targetList) + 1, |
346 | 0 | cte->cycle_clause->cycle_path_column, |
347 | 0 | false); |
348 | 0 | newq1->targetList = lappend(newq1->targetList, tle); |
349 | 0 | } |
350 | |
|
351 | 0 | rte1->subquery = newq1; |
352 | |
|
353 | 0 | if (cte->search_clause) |
354 | 0 | { |
355 | 0 | rte1->eref->colnames = lappend(rte1->eref->colnames, makeString(cte->search_clause->search_seq_column)); |
356 | 0 | } |
357 | 0 | if (cte->cycle_clause) |
358 | 0 | { |
359 | 0 | rte1->eref->colnames = lappend(rte1->eref->colnames, makeString(cte->cycle_clause->cycle_mark_column)); |
360 | 0 | rte1->eref->colnames = lappend(rte1->eref->colnames, makeString(cte->cycle_clause->cycle_path_column)); |
361 | 0 | } |
362 | | |
363 | | /* |
364 | | * Make new right subquery |
365 | | */ |
366 | 0 | newq2 = makeNode(Query); |
367 | 0 | newq2->commandType = CMD_SELECT; |
368 | 0 | newq2->canSetTag = true; |
369 | |
|
370 | 0 | newrte = makeNode(RangeTblEntry); |
371 | 0 | newrte->rtekind = RTE_SUBQUERY; |
372 | 0 | ewcl = copyObject(cte->ctecolnames); |
373 | 0 | if (cte->search_clause) |
374 | 0 | { |
375 | 0 | ewcl = lappend(ewcl, makeString(cte->search_clause->search_seq_column)); |
376 | 0 | } |
377 | 0 | if (cte->cycle_clause) |
378 | 0 | { |
379 | 0 | ewcl = lappend(ewcl, makeString(cte->cycle_clause->cycle_mark_column)); |
380 | 0 | ewcl = lappend(ewcl, makeString(cte->cycle_clause->cycle_path_column)); |
381 | 0 | } |
382 | 0 | newrte->alias = NULL; |
383 | 0 | newrte->eref = makeAlias("*TROCRN*", ewcl); |
384 | | |
385 | | /* |
386 | | * Find the reference to the recursive CTE in the right UNION subquery's |
387 | | * range table. We expect it to be two levels up from the UNION subquery |
388 | | * (and must check that to avoid being fooled by sub-WITHs with the same |
389 | | * CTE name). There will not be more than one such reference, because the |
390 | | * parser would have rejected that (see checkWellFormedRecursion() in |
391 | | * parse_cte.c). However, the parser doesn't insist that the reference |
392 | | * appear in the UNION subquery's topmost range table, so we might fail to |
393 | | * find it at all. That's an unimplemented case for the moment. |
394 | | */ |
395 | 0 | for (int rti = 1; rti <= list_length(rte2->subquery->rtable); rti++) |
396 | 0 | { |
397 | 0 | RangeTblEntry *e = rt_fetch(rti, rte2->subquery->rtable); |
398 | |
|
399 | 0 | if (e->rtekind == RTE_CTE && |
400 | 0 | strcmp(cte->ctename, e->ctename) == 0 && |
401 | 0 | e->ctelevelsup == 2) |
402 | 0 | { |
403 | 0 | cte_rtindex = rti; |
404 | 0 | break; |
405 | 0 | } |
406 | 0 | } |
407 | 0 | if (cte_rtindex <= 0) |
408 | 0 | ereport(ERROR, |
409 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
410 | 0 | errmsg("with a SEARCH or CYCLE clause, the recursive reference to WITH query \"%s\" must be at the top level of its right-hand SELECT", |
411 | 0 | cte->ctename))); |
412 | | |
413 | 0 | newsubquery = copyObject(rte2->subquery); |
414 | 0 | IncrementVarSublevelsUp((Node *) newsubquery, 1, 1); |
415 | | |
416 | | /* |
417 | | * Add extra columns to target list of subquery of right subquery |
418 | | */ |
419 | 0 | if (cte->search_clause) |
420 | 0 | { |
421 | 0 | Var *var; |
422 | | |
423 | | /* ctename.sqc */ |
424 | 0 | var = makeVar(cte_rtindex, sqc_attno, |
425 | 0 | search_seq_type, -1, InvalidOid, 0); |
426 | 0 | tle = makeTargetEntry((Expr *) var, |
427 | 0 | list_length(newsubquery->targetList) + 1, |
428 | 0 | cte->search_clause->search_seq_column, |
429 | 0 | false); |
430 | 0 | newsubquery->targetList = lappend(newsubquery->targetList, tle); |
431 | 0 | } |
432 | 0 | if (cte->cycle_clause) |
433 | 0 | { |
434 | 0 | Var *var; |
435 | | |
436 | | /* ctename.cmc */ |
437 | 0 | var = makeVar(cte_rtindex, cmc_attno, |
438 | 0 | cte->cycle_clause->cycle_mark_type, |
439 | 0 | cte->cycle_clause->cycle_mark_typmod, |
440 | 0 | cte->cycle_clause->cycle_mark_collation, 0); |
441 | 0 | tle = makeTargetEntry((Expr *) var, |
442 | 0 | list_length(newsubquery->targetList) + 1, |
443 | 0 | cte->cycle_clause->cycle_mark_column, |
444 | 0 | false); |
445 | 0 | newsubquery->targetList = lappend(newsubquery->targetList, tle); |
446 | | |
447 | | /* ctename.cpa */ |
448 | 0 | var = makeVar(cte_rtindex, cpa_attno, |
449 | 0 | RECORDARRAYOID, -1, InvalidOid, 0); |
450 | 0 | tle = makeTargetEntry((Expr *) var, |
451 | 0 | list_length(newsubquery->targetList) + 1, |
452 | 0 | cte->cycle_clause->cycle_path_column, |
453 | 0 | false); |
454 | 0 | newsubquery->targetList = lappend(newsubquery->targetList, tle); |
455 | 0 | } |
456 | |
|
457 | 0 | newrte->subquery = newsubquery; |
458 | 0 | newrte->inFromCl = true; |
459 | 0 | newq2->rtable = list_make1(newrte); |
460 | |
|
461 | 0 | rtr = makeNode(RangeTblRef); |
462 | 0 | rtr->rtindex = 1; |
463 | |
|
464 | 0 | if (cte->cycle_clause) |
465 | 0 | { |
466 | 0 | Expr *expr; |
467 | | |
468 | | /* |
469 | | * Add cmc <> cmv condition |
470 | | */ |
471 | 0 | expr = make_opclause(cte->cycle_clause->cycle_mark_neop, BOOLOID, false, |
472 | 0 | (Expr *) makeVar(1, cmc_attno, |
473 | 0 | cte->cycle_clause->cycle_mark_type, |
474 | 0 | cte->cycle_clause->cycle_mark_typmod, |
475 | 0 | cte->cycle_clause->cycle_mark_collation, 0), |
476 | 0 | (Expr *) cte->cycle_clause->cycle_mark_value, |
477 | 0 | InvalidOid, |
478 | 0 | cte->cycle_clause->cycle_mark_collation); |
479 | |
|
480 | 0 | newq2->jointree = makeFromExpr(list_make1(rtr), (Node *) expr); |
481 | 0 | } |
482 | 0 | else |
483 | 0 | newq2->jointree = makeFromExpr(list_make1(rtr), NULL); |
484 | | |
485 | | /* |
486 | | * Make target list |
487 | | */ |
488 | 0 | for (int i = 0; i < list_length(cte->ctecolnames); i++) |
489 | 0 | { |
490 | 0 | Var *var; |
491 | |
|
492 | 0 | var = makeVar(1, i + 1, |
493 | 0 | list_nth_oid(cte->ctecoltypes, i), |
494 | 0 | list_nth_int(cte->ctecoltypmods, i), |
495 | 0 | list_nth_oid(cte->ctecolcollations, i), |
496 | 0 | 0); |
497 | 0 | tle = makeTargetEntry((Expr *) var, i + 1, strVal(list_nth(cte->ctecolnames, i)), false); |
498 | 0 | tle->resorigtbl = list_nth_node(TargetEntry, rte2->subquery->targetList, i)->resorigtbl; |
499 | 0 | tle->resorigcol = list_nth_node(TargetEntry, rte2->subquery->targetList, i)->resorigcol; |
500 | 0 | newq2->targetList = lappend(newq2->targetList, tle); |
501 | 0 | } |
502 | |
|
503 | 0 | if (cte->search_clause) |
504 | 0 | { |
505 | 0 | Expr *texpr; |
506 | |
|
507 | 0 | if (cte->search_clause->search_breadth_first) |
508 | 0 | { |
509 | 0 | FieldSelect *fs; |
510 | 0 | FuncExpr *fexpr; |
511 | | |
512 | | /* |
513 | | * ROW(sqc.depth + 1, cols) |
514 | | */ |
515 | |
|
516 | 0 | search_col_rowexpr = copyObject(search_col_rowexpr); |
517 | |
|
518 | 0 | fs = makeNode(FieldSelect); |
519 | 0 | fs->arg = (Expr *) makeVar(1, sqc_attno, RECORDOID, -1, 0, 0); |
520 | 0 | fs->fieldnum = 1; |
521 | 0 | fs->resulttype = INT8OID; |
522 | 0 | fs->resulttypmod = -1; |
523 | |
|
524 | 0 | fexpr = makeFuncExpr(F_INT8INC, INT8OID, list_make1(fs), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); |
525 | |
|
526 | 0 | linitial(search_col_rowexpr->args) = fexpr; |
527 | |
|
528 | 0 | texpr = (Expr *) search_col_rowexpr; |
529 | 0 | } |
530 | 0 | else |
531 | 0 | { |
532 | | /* |
533 | | * sqc || ARRAY[ROW(cols)] |
534 | | */ |
535 | 0 | texpr = make_path_cat_expr(search_col_rowexpr, sqc_attno); |
536 | 0 | } |
537 | 0 | tle = makeTargetEntry(texpr, |
538 | 0 | list_length(newq2->targetList) + 1, |
539 | 0 | cte->search_clause->search_seq_column, |
540 | 0 | false); |
541 | 0 | newq2->targetList = lappend(newq2->targetList, tle); |
542 | 0 | } |
543 | |
|
544 | 0 | if (cte->cycle_clause) |
545 | 0 | { |
546 | 0 | ScalarArrayOpExpr *saoe; |
547 | 0 | CaseExpr *caseexpr; |
548 | 0 | CaseWhen *casewhen; |
549 | | |
550 | | /* |
551 | | * CASE WHEN ROW(cols) = ANY (ARRAY[cpa]) THEN cmv ELSE cmd END |
552 | | */ |
553 | |
|
554 | 0 | saoe = makeNode(ScalarArrayOpExpr); |
555 | 0 | saoe->location = -1; |
556 | 0 | saoe->opno = RECORD_EQ_OP; |
557 | 0 | saoe->useOr = true; |
558 | 0 | saoe->args = list_make2(cycle_col_rowexpr, |
559 | 0 | makeVar(1, cpa_attno, RECORDARRAYOID, -1, 0, 0)); |
560 | |
|
561 | 0 | caseexpr = makeNode(CaseExpr); |
562 | 0 | caseexpr->location = -1; |
563 | 0 | caseexpr->casetype = cte->cycle_clause->cycle_mark_type; |
564 | 0 | caseexpr->casecollid = cte->cycle_clause->cycle_mark_collation; |
565 | 0 | casewhen = makeNode(CaseWhen); |
566 | 0 | casewhen->location = -1; |
567 | 0 | casewhen->expr = (Expr *) saoe; |
568 | 0 | casewhen->result = (Expr *) cte->cycle_clause->cycle_mark_value; |
569 | 0 | caseexpr->args = list_make1(casewhen); |
570 | 0 | caseexpr->defresult = (Expr *) cte->cycle_clause->cycle_mark_default; |
571 | |
|
572 | 0 | tle = makeTargetEntry((Expr *) caseexpr, |
573 | 0 | list_length(newq2->targetList) + 1, |
574 | 0 | cte->cycle_clause->cycle_mark_column, |
575 | 0 | false); |
576 | 0 | newq2->targetList = lappend(newq2->targetList, tle); |
577 | | |
578 | | /* |
579 | | * cpa || ARRAY[ROW(cols)] |
580 | | */ |
581 | 0 | tle = makeTargetEntry(make_path_cat_expr(cycle_col_rowexpr, cpa_attno), |
582 | 0 | list_length(newq2->targetList) + 1, |
583 | 0 | cte->cycle_clause->cycle_path_column, |
584 | 0 | false); |
585 | 0 | newq2->targetList = lappend(newq2->targetList, tle); |
586 | 0 | } |
587 | |
|
588 | 0 | rte2->subquery = newq2; |
589 | |
|
590 | 0 | if (cte->search_clause) |
591 | 0 | { |
592 | 0 | rte2->eref->colnames = lappend(rte2->eref->colnames, makeString(cte->search_clause->search_seq_column)); |
593 | 0 | } |
594 | 0 | if (cte->cycle_clause) |
595 | 0 | { |
596 | 0 | rte2->eref->colnames = lappend(rte2->eref->colnames, makeString(cte->cycle_clause->cycle_mark_column)); |
597 | 0 | rte2->eref->colnames = lappend(rte2->eref->colnames, makeString(cte->cycle_clause->cycle_path_column)); |
598 | 0 | } |
599 | | |
600 | | /* |
601 | | * Add the additional columns to the SetOperationStmt |
602 | | */ |
603 | 0 | if (cte->search_clause) |
604 | 0 | { |
605 | 0 | sos->colTypes = lappend_oid(sos->colTypes, search_seq_type); |
606 | 0 | sos->colTypmods = lappend_int(sos->colTypmods, -1); |
607 | 0 | sos->colCollations = lappend_oid(sos->colCollations, InvalidOid); |
608 | 0 | if (!sos->all) |
609 | 0 | sos->groupClauses = lappend(sos->groupClauses, |
610 | 0 | makeSortGroupClauseForSetOp(search_seq_type, true)); |
611 | 0 | } |
612 | 0 | if (cte->cycle_clause) |
613 | 0 | { |
614 | 0 | sos->colTypes = lappend_oid(sos->colTypes, cte->cycle_clause->cycle_mark_type); |
615 | 0 | sos->colTypmods = lappend_int(sos->colTypmods, cte->cycle_clause->cycle_mark_typmod); |
616 | 0 | sos->colCollations = lappend_oid(sos->colCollations, cte->cycle_clause->cycle_mark_collation); |
617 | 0 | if (!sos->all) |
618 | 0 | sos->groupClauses = lappend(sos->groupClauses, |
619 | 0 | makeSortGroupClauseForSetOp(cte->cycle_clause->cycle_mark_type, true)); |
620 | |
|
621 | 0 | sos->colTypes = lappend_oid(sos->colTypes, RECORDARRAYOID); |
622 | 0 | sos->colTypmods = lappend_int(sos->colTypmods, -1); |
623 | 0 | sos->colCollations = lappend_oid(sos->colCollations, InvalidOid); |
624 | 0 | if (!sos->all) |
625 | 0 | sos->groupClauses = lappend(sos->groupClauses, |
626 | 0 | makeSortGroupClauseForSetOp(RECORDARRAYOID, true)); |
627 | 0 | } |
628 | | |
629 | | /* |
630 | | * Add the additional columns to the CTE query's target list |
631 | | */ |
632 | 0 | if (cte->search_clause) |
633 | 0 | { |
634 | 0 | ctequery->targetList = lappend(ctequery->targetList, |
635 | 0 | makeTargetEntry((Expr *) makeVar(1, sqc_attno, |
636 | 0 | search_seq_type, -1, InvalidOid, 0), |
637 | 0 | list_length(ctequery->targetList) + 1, |
638 | 0 | cte->search_clause->search_seq_column, |
639 | 0 | false)); |
640 | 0 | } |
641 | 0 | if (cte->cycle_clause) |
642 | 0 | { |
643 | 0 | ctequery->targetList = lappend(ctequery->targetList, |
644 | 0 | makeTargetEntry((Expr *) makeVar(1, cmc_attno, |
645 | 0 | cte->cycle_clause->cycle_mark_type, |
646 | 0 | cte->cycle_clause->cycle_mark_typmod, |
647 | 0 | cte->cycle_clause->cycle_mark_collation, 0), |
648 | 0 | list_length(ctequery->targetList) + 1, |
649 | 0 | cte->cycle_clause->cycle_mark_column, |
650 | 0 | false)); |
651 | 0 | ctequery->targetList = lappend(ctequery->targetList, |
652 | 0 | makeTargetEntry((Expr *) makeVar(1, cpa_attno, |
653 | 0 | RECORDARRAYOID, -1, InvalidOid, 0), |
654 | 0 | list_length(ctequery->targetList) + 1, |
655 | 0 | cte->cycle_clause->cycle_path_column, |
656 | 0 | false)); |
657 | 0 | } |
658 | | |
659 | | /* |
660 | | * Add the additional columns to the CTE's output columns |
661 | | */ |
662 | 0 | cte->ctecolnames = ewcl; |
663 | 0 | if (cte->search_clause) |
664 | 0 | { |
665 | 0 | cte->ctecoltypes = lappend_oid(cte->ctecoltypes, search_seq_type); |
666 | 0 | cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, -1); |
667 | 0 | cte->ctecolcollations = lappend_oid(cte->ctecolcollations, InvalidOid); |
668 | 0 | } |
669 | 0 | if (cte->cycle_clause) |
670 | 0 | { |
671 | 0 | cte->ctecoltypes = lappend_oid(cte->ctecoltypes, cte->cycle_clause->cycle_mark_type); |
672 | 0 | cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, cte->cycle_clause->cycle_mark_typmod); |
673 | 0 | cte->ctecolcollations = lappend_oid(cte->ctecolcollations, cte->cycle_clause->cycle_mark_collation); |
674 | |
|
675 | 0 | cte->ctecoltypes = lappend_oid(cte->ctecoltypes, RECORDARRAYOID); |
676 | 0 | cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, -1); |
677 | 0 | cte->ctecolcollations = lappend_oid(cte->ctecolcollations, InvalidOid); |
678 | 0 | } |
679 | |
|
680 | 0 | return cte; |
681 | 0 | } |