Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * rewriteSearchCycle.c
4 : * Support for rewriting SEARCH and CYCLE clauses.
5 : *
6 : * Portions Copyright (c) 1996-2024, 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 156 : make_path_rowexpr(const CommonTableExpr *cte, const List *col_list)
118 : {
119 : RowExpr *rowexpr;
120 : ListCell *lc;
121 :
122 156 : rowexpr = makeNode(RowExpr);
123 156 : rowexpr->row_typeid = RECORDOID;
124 156 : rowexpr->row_format = COERCE_IMPLICIT_CAST;
125 156 : rowexpr->location = -1;
126 :
127 414 : foreach(lc, col_list)
128 : {
129 258 : char *colname = strVal(lfirst(lc));
130 :
131 360 : for (int i = 0; i < list_length(cte->ctecolnames); i++)
132 : {
133 360 : char *colname2 = strVal(list_nth(cte->ctecolnames, i));
134 :
135 360 : if (strcmp(colname, colname2) == 0)
136 : {
137 : Var *var;
138 :
139 258 : var = makeVar(1, i + 1,
140 258 : list_nth_oid(cte->ctecoltypes, i),
141 258 : list_nth_int(cte->ctecoltypmods, i),
142 258 : list_nth_oid(cte->ctecolcollations, i),
143 : 0);
144 258 : rowexpr->args = lappend(rowexpr->args, var);
145 258 : rowexpr->colnames = lappend(rowexpr->colnames, makeString(colname));
146 258 : break;
147 : }
148 : }
149 : }
150 :
151 156 : return rowexpr;
152 : }
153 :
154 : /*
155 : * Wrap a RowExpr in an ArrayExpr, for the initial search depth first or cycle
156 : * row.
157 : */
158 : static Expr *
159 120 : make_path_initial_array(RowExpr *rowexpr)
160 : {
161 : ArrayExpr *arr;
162 :
163 120 : arr = makeNode(ArrayExpr);
164 120 : arr->array_typeid = RECORDARRAYOID;
165 120 : arr->element_typeid = RECORDOID;
166 120 : arr->location = -1;
167 120 : arr->elements = list_make1(rowexpr);
168 :
169 120 : return (Expr *) arr;
170 : }
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 114 : make_path_cat_expr(RowExpr *rowexpr, AttrNumber path_varattno)
181 : {
182 : ArrayExpr *arr;
183 : FuncExpr *fexpr;
184 :
185 114 : arr = makeNode(ArrayExpr);
186 114 : arr->array_typeid = RECORDARRAYOID;
187 114 : arr->element_typeid = RECORDOID;
188 114 : arr->location = -1;
189 114 : arr->elements = list_make1(rowexpr);
190 :
191 114 : fexpr = makeFuncExpr(F_ARRAY_CAT, RECORDARRAYOID,
192 114 : list_make2(makeVar(1, path_varattno, RECORDARRAYOID, -1, 0, 0),
193 : arr),
194 : InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
195 :
196 114 : return (Expr *) fexpr;
197 : }
198 :
199 : /*
200 : * The real work happens here.
201 : */
202 : CommonTableExpr *
203 144 : rewriteSearchAndCycle(CommonTableExpr *cte)
204 : {
205 : Query *ctequery;
206 : SetOperationStmt *sos;
207 : int rti1,
208 : rti2;
209 : RangeTblEntry *rte1,
210 : *rte2,
211 : *newrte;
212 : Query *newq1,
213 : *newq2;
214 : Query *newsubquery;
215 : RangeTblRef *rtr;
216 144 : Oid search_seq_type = InvalidOid;
217 144 : AttrNumber sqc_attno = InvalidAttrNumber;
218 144 : AttrNumber cmc_attno = InvalidAttrNumber;
219 144 : AttrNumber cpa_attno = InvalidAttrNumber;
220 : TargetEntry *tle;
221 144 : RowExpr *cycle_col_rowexpr = NULL;
222 144 : RowExpr *search_col_rowexpr = NULL;
223 : List *ewcl;
224 144 : int cte_rtindex = -1;
225 :
226 : Assert(cte->search_clause || cte->cycle_clause);
227 :
228 144 : cte = copyObject(cte);
229 :
230 144 : 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 : Assert(ctequery->setOperations);
237 144 : sos = castNode(SetOperationStmt, ctequery->setOperations);
238 : Assert(sos->op == SETOP_UNION);
239 :
240 144 : rti1 = castNode(RangeTblRef, sos->larg)->rtindex;
241 144 : rti2 = castNode(RangeTblRef, sos->rarg)->rtindex;
242 :
243 144 : rte1 = rt_fetch(rti1, ctequery->rtable);
244 144 : rte2 = rt_fetch(rti2, ctequery->rtable);
245 :
246 : Assert(rte1->rtekind == RTE_SUBQUERY);
247 : Assert(rte2->rtekind == RTE_SUBQUERY);
248 :
249 : /*
250 : * We'll need this a few times later.
251 : */
252 144 : if (cte->search_clause)
253 : {
254 84 : if (cte->search_clause->search_breadth_first)
255 36 : search_seq_type = RECORDOID;
256 : else
257 48 : search_seq_type = RECORDARRAYOID;
258 : }
259 :
260 : /*
261 : * Attribute numbers of the added columns in the CTE's column list
262 : */
263 144 : if (cte->search_clause)
264 84 : sqc_attno = list_length(cte->ctecolnames) + 1;
265 144 : if (cte->cycle_clause)
266 : {
267 72 : cmc_attno = list_length(cte->ctecolnames) + 1;
268 72 : cpa_attno = list_length(cte->ctecolnames) + 2;
269 72 : if (cte->search_clause)
270 : {
271 12 : cmc_attno++;
272 12 : cpa_attno++;
273 : }
274 : }
275 :
276 : /*
277 : * Make new left subquery
278 : */
279 144 : newq1 = makeNode(Query);
280 144 : newq1->commandType = CMD_SELECT;
281 144 : newq1->canSetTag = true;
282 :
283 144 : newrte = makeNode(RangeTblEntry);
284 144 : newrte->rtekind = RTE_SUBQUERY;
285 144 : newrte->alias = makeAlias("*TLOCRN*", cte->ctecolnames);
286 144 : newrte->eref = newrte->alias;
287 144 : newsubquery = copyObject(rte1->subquery);
288 144 : IncrementVarSublevelsUp((Node *) newsubquery, 1, 1);
289 144 : newrte->subquery = newsubquery;
290 144 : newrte->inFromCl = true;
291 144 : newq1->rtable = list_make1(newrte);
292 :
293 144 : rtr = makeNode(RangeTblRef);
294 144 : rtr->rtindex = 1;
295 144 : newq1->jointree = makeFromExpr(list_make1(rtr), NULL);
296 :
297 : /*
298 : * Make target list
299 : */
300 468 : for (int i = 0; i < list_length(cte->ctecolnames); i++)
301 : {
302 : Var *var;
303 :
304 324 : var = makeVar(1, i + 1,
305 324 : list_nth_oid(cte->ctecoltypes, i),
306 324 : list_nth_int(cte->ctecoltypmods, i),
307 324 : list_nth_oid(cte->ctecolcollations, i),
308 : 0);
309 324 : tle = makeTargetEntry((Expr *) var, i + 1, strVal(list_nth(cte->ctecolnames, i)), false);
310 324 : tle->resorigtbl = list_nth_node(TargetEntry, rte1->subquery->targetList, i)->resorigtbl;
311 324 : tle->resorigcol = list_nth_node(TargetEntry, rte1->subquery->targetList, i)->resorigcol;
312 324 : newq1->targetList = lappend(newq1->targetList, tle);
313 : }
314 :
315 144 : if (cte->search_clause)
316 : {
317 : Expr *texpr;
318 :
319 84 : search_col_rowexpr = make_path_rowexpr(cte, cte->search_clause->search_col_list);
320 84 : if (cte->search_clause->search_breadth_first)
321 : {
322 36 : search_col_rowexpr->args = lcons(makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
323 : Int64GetDatum(0), false, FLOAT8PASSBYVAL),
324 : search_col_rowexpr->args);
325 36 : search_col_rowexpr->colnames = lcons(makeString("*DEPTH*"), search_col_rowexpr->colnames);
326 36 : texpr = (Expr *) search_col_rowexpr;
327 : }
328 : else
329 48 : texpr = make_path_initial_array(search_col_rowexpr);
330 168 : tle = makeTargetEntry(texpr,
331 84 : list_length(newq1->targetList) + 1,
332 84 : cte->search_clause->search_seq_column,
333 : false);
334 84 : newq1->targetList = lappend(newq1->targetList, tle);
335 : }
336 144 : if (cte->cycle_clause)
337 : {
338 144 : tle = makeTargetEntry((Expr *) cte->cycle_clause->cycle_mark_default,
339 72 : list_length(newq1->targetList) + 1,
340 72 : cte->cycle_clause->cycle_mark_column,
341 : false);
342 72 : newq1->targetList = lappend(newq1->targetList, tle);
343 72 : cycle_col_rowexpr = make_path_rowexpr(cte, cte->cycle_clause->cycle_col_list);
344 72 : tle = makeTargetEntry(make_path_initial_array(cycle_col_rowexpr),
345 72 : list_length(newq1->targetList) + 1,
346 72 : cte->cycle_clause->cycle_path_column,
347 : false);
348 72 : newq1->targetList = lappend(newq1->targetList, tle);
349 : }
350 :
351 144 : rte1->subquery = newq1;
352 :
353 144 : if (cte->search_clause)
354 : {
355 84 : rte1->eref->colnames = lappend(rte1->eref->colnames, makeString(cte->search_clause->search_seq_column));
356 : }
357 144 : if (cte->cycle_clause)
358 : {
359 72 : rte1->eref->colnames = lappend(rte1->eref->colnames, makeString(cte->cycle_clause->cycle_mark_column));
360 72 : rte1->eref->colnames = lappend(rte1->eref->colnames, makeString(cte->cycle_clause->cycle_path_column));
361 : }
362 :
363 : /*
364 : * Make new right subquery
365 : */
366 144 : newq2 = makeNode(Query);
367 144 : newq2->commandType = CMD_SELECT;
368 144 : newq2->canSetTag = true;
369 :
370 144 : newrte = makeNode(RangeTblEntry);
371 144 : newrte->rtekind = RTE_SUBQUERY;
372 144 : ewcl = copyObject(cte->ctecolnames);
373 144 : if (cte->search_clause)
374 : {
375 84 : ewcl = lappend(ewcl, makeString(cte->search_clause->search_seq_column));
376 : }
377 144 : if (cte->cycle_clause)
378 : {
379 72 : ewcl = lappend(ewcl, makeString(cte->cycle_clause->cycle_mark_column));
380 72 : ewcl = lappend(ewcl, makeString(cte->cycle_clause->cycle_path_column));
381 : }
382 144 : newrte->alias = makeAlias("*TROCRN*", ewcl);
383 144 : newrte->eref = newrte->alias;
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 240 : for (int rti = 1; rti <= list_length(rte2->subquery->rtable); rti++)
396 : {
397 234 : RangeTblEntry *e = rt_fetch(rti, rte2->subquery->rtable);
398 :
399 234 : if (e->rtekind == RTE_CTE &&
400 150 : strcmp(cte->ctename, e->ctename) == 0 &&
401 144 : e->ctelevelsup == 2)
402 : {
403 138 : cte_rtindex = rti;
404 138 : break;
405 : }
406 : }
407 144 : if (cte_rtindex <= 0)
408 6 : ereport(ERROR,
409 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
410 : 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 : cte->ctename)));
412 :
413 138 : newsubquery = copyObject(rte2->subquery);
414 138 : IncrementVarSublevelsUp((Node *) newsubquery, 1, 1);
415 :
416 : /*
417 : * Add extra columns to target list of subquery of right subquery
418 : */
419 138 : if (cte->search_clause)
420 : {
421 : Var *var;
422 :
423 : /* ctename.sqc */
424 78 : var = makeVar(cte_rtindex, sqc_attno,
425 : search_seq_type, -1, InvalidOid, 0);
426 156 : tle = makeTargetEntry((Expr *) var,
427 78 : list_length(newsubquery->targetList) + 1,
428 78 : cte->search_clause->search_seq_column,
429 : false);
430 78 : newsubquery->targetList = lappend(newsubquery->targetList, tle);
431 : }
432 138 : if (cte->cycle_clause)
433 : {
434 : Var *var;
435 :
436 : /* ctename.cmc */
437 72 : var = makeVar(cte_rtindex, cmc_attno,
438 72 : cte->cycle_clause->cycle_mark_type,
439 72 : cte->cycle_clause->cycle_mark_typmod,
440 72 : cte->cycle_clause->cycle_mark_collation, 0);
441 144 : tle = makeTargetEntry((Expr *) var,
442 72 : list_length(newsubquery->targetList) + 1,
443 72 : cte->cycle_clause->cycle_mark_column,
444 : false);
445 72 : newsubquery->targetList = lappend(newsubquery->targetList, tle);
446 :
447 : /* ctename.cpa */
448 72 : var = makeVar(cte_rtindex, cpa_attno,
449 : RECORDARRAYOID, -1, InvalidOid, 0);
450 144 : tle = makeTargetEntry((Expr *) var,
451 72 : list_length(newsubquery->targetList) + 1,
452 72 : cte->cycle_clause->cycle_path_column,
453 : false);
454 72 : newsubquery->targetList = lappend(newsubquery->targetList, tle);
455 : }
456 :
457 138 : newrte->subquery = newsubquery;
458 138 : newrte->inFromCl = true;
459 138 : newq2->rtable = list_make1(newrte);
460 :
461 138 : rtr = makeNode(RangeTblRef);
462 138 : rtr->rtindex = 1;
463 :
464 138 : if (cte->cycle_clause)
465 : {
466 : Expr *expr;
467 :
468 : /*
469 : * Add cmc <> cmv condition
470 : */
471 72 : expr = make_opclause(cte->cycle_clause->cycle_mark_neop, BOOLOID, false,
472 72 : (Expr *) makeVar(1, cmc_attno,
473 72 : cte->cycle_clause->cycle_mark_type,
474 72 : cte->cycle_clause->cycle_mark_typmod,
475 72 : cte->cycle_clause->cycle_mark_collation, 0),
476 72 : (Expr *) cte->cycle_clause->cycle_mark_value,
477 : InvalidOid,
478 72 : cte->cycle_clause->cycle_mark_collation);
479 :
480 72 : newq2->jointree = makeFromExpr(list_make1(rtr), (Node *) expr);
481 : }
482 : else
483 66 : newq2->jointree = makeFromExpr(list_make1(rtr), NULL);
484 :
485 : /*
486 : * Make target list
487 : */
488 456 : for (int i = 0; i < list_length(cte->ctecolnames); i++)
489 : {
490 : Var *var;
491 :
492 318 : var = makeVar(1, i + 1,
493 318 : list_nth_oid(cte->ctecoltypes, i),
494 318 : list_nth_int(cte->ctecoltypmods, i),
495 318 : list_nth_oid(cte->ctecolcollations, i),
496 : 0);
497 318 : tle = makeTargetEntry((Expr *) var, i + 1, strVal(list_nth(cte->ctecolnames, i)), false);
498 318 : tle->resorigtbl = list_nth_node(TargetEntry, rte2->subquery->targetList, i)->resorigtbl;
499 318 : tle->resorigcol = list_nth_node(TargetEntry, rte2->subquery->targetList, i)->resorigcol;
500 318 : newq2->targetList = lappend(newq2->targetList, tle);
501 : }
502 :
503 138 : if (cte->search_clause)
504 : {
505 : Expr *texpr;
506 :
507 78 : if (cte->search_clause->search_breadth_first)
508 : {
509 : FieldSelect *fs;
510 : FuncExpr *fexpr;
511 :
512 : /*
513 : * ROW(sqc.depth + 1, cols)
514 : */
515 :
516 36 : search_col_rowexpr = copyObject(search_col_rowexpr);
517 :
518 36 : fs = makeNode(FieldSelect);
519 36 : fs->arg = (Expr *) makeVar(1, sqc_attno, RECORDOID, -1, 0, 0);
520 36 : fs->fieldnum = 1;
521 36 : fs->resulttype = INT8OID;
522 36 : fs->resulttypmod = -1;
523 :
524 36 : fexpr = makeFuncExpr(F_INT8INC, INT8OID, list_make1(fs), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
525 :
526 36 : linitial(search_col_rowexpr->args) = fexpr;
527 :
528 36 : texpr = (Expr *) search_col_rowexpr;
529 : }
530 : else
531 : {
532 : /*
533 : * sqc || ARRAY[ROW(cols)]
534 : */
535 42 : texpr = make_path_cat_expr(search_col_rowexpr, sqc_attno);
536 : }
537 156 : tle = makeTargetEntry(texpr,
538 78 : list_length(newq2->targetList) + 1,
539 78 : cte->search_clause->search_seq_column,
540 : false);
541 78 : newq2->targetList = lappend(newq2->targetList, tle);
542 : }
543 :
544 138 : if (cte->cycle_clause)
545 : {
546 : ScalarArrayOpExpr *saoe;
547 : CaseExpr *caseexpr;
548 : CaseWhen *casewhen;
549 :
550 : /*
551 : * CASE WHEN ROW(cols) = ANY (ARRAY[cpa]) THEN cmv ELSE cmd END
552 : */
553 :
554 72 : saoe = makeNode(ScalarArrayOpExpr);
555 72 : saoe->location = -1;
556 72 : saoe->opno = RECORD_EQ_OP;
557 72 : saoe->useOr = true;
558 72 : saoe->args = list_make2(cycle_col_rowexpr,
559 : makeVar(1, cpa_attno, RECORDARRAYOID, -1, 0, 0));
560 :
561 72 : caseexpr = makeNode(CaseExpr);
562 72 : caseexpr->location = -1;
563 72 : caseexpr->casetype = cte->cycle_clause->cycle_mark_type;
564 72 : caseexpr->casecollid = cte->cycle_clause->cycle_mark_collation;
565 72 : casewhen = makeNode(CaseWhen);
566 72 : casewhen->location = -1;
567 72 : casewhen->expr = (Expr *) saoe;
568 72 : casewhen->result = (Expr *) cte->cycle_clause->cycle_mark_value;
569 72 : caseexpr->args = list_make1(casewhen);
570 72 : caseexpr->defresult = (Expr *) cte->cycle_clause->cycle_mark_default;
571 :
572 144 : tle = makeTargetEntry((Expr *) caseexpr,
573 72 : list_length(newq2->targetList) + 1,
574 72 : cte->cycle_clause->cycle_mark_column,
575 : false);
576 72 : newq2->targetList = lappend(newq2->targetList, tle);
577 :
578 : /*
579 : * cpa || ARRAY[ROW(cols)]
580 : */
581 72 : tle = makeTargetEntry(make_path_cat_expr(cycle_col_rowexpr, cpa_attno),
582 72 : list_length(newq2->targetList) + 1,
583 72 : cte->cycle_clause->cycle_path_column,
584 : false);
585 72 : newq2->targetList = lappend(newq2->targetList, tle);
586 : }
587 :
588 138 : rte2->subquery = newq2;
589 :
590 138 : if (cte->search_clause)
591 : {
592 78 : rte2->eref->colnames = lappend(rte2->eref->colnames, makeString(cte->search_clause->search_seq_column));
593 : }
594 138 : if (cte->cycle_clause)
595 : {
596 72 : rte2->eref->colnames = lappend(rte2->eref->colnames, makeString(cte->cycle_clause->cycle_mark_column));
597 72 : rte2->eref->colnames = lappend(rte2->eref->colnames, makeString(cte->cycle_clause->cycle_path_column));
598 : }
599 :
600 : /*
601 : * Add the additional columns to the SetOperationStmt
602 : */
603 138 : if (cte->search_clause)
604 : {
605 78 : sos->colTypes = lappend_oid(sos->colTypes, search_seq_type);
606 78 : sos->colTypmods = lappend_int(sos->colTypmods, -1);
607 78 : sos->colCollations = lappend_oid(sos->colCollations, InvalidOid);
608 78 : if (!sos->all)
609 12 : sos->groupClauses = lappend(sos->groupClauses,
610 12 : makeSortGroupClauseForSetOp(search_seq_type, true));
611 : }
612 138 : if (cte->cycle_clause)
613 : {
614 72 : sos->colTypes = lappend_oid(sos->colTypes, cte->cycle_clause->cycle_mark_type);
615 72 : sos->colTypmods = lappend_int(sos->colTypmods, cte->cycle_clause->cycle_mark_typmod);
616 72 : sos->colCollations = lappend_oid(sos->colCollations, cte->cycle_clause->cycle_mark_collation);
617 72 : if (!sos->all)
618 6 : sos->groupClauses = lappend(sos->groupClauses,
619 6 : makeSortGroupClauseForSetOp(cte->cycle_clause->cycle_mark_type, true));
620 :
621 72 : sos->colTypes = lappend_oid(sos->colTypes, RECORDARRAYOID);
622 72 : sos->colTypmods = lappend_int(sos->colTypmods, -1);
623 72 : sos->colCollations = lappend_oid(sos->colCollations, InvalidOid);
624 72 : if (!sos->all)
625 6 : sos->groupClauses = lappend(sos->groupClauses,
626 6 : makeSortGroupClauseForSetOp(RECORDARRAYOID, true));
627 : }
628 :
629 : /*
630 : * Add the additional columns to the CTE query's target list
631 : */
632 138 : if (cte->search_clause)
633 : {
634 78 : ctequery->targetList = lappend(ctequery->targetList,
635 78 : makeTargetEntry((Expr *) makeVar(1, sqc_attno,
636 : search_seq_type, -1, InvalidOid, 0),
637 78 : list_length(ctequery->targetList) + 1,
638 78 : cte->search_clause->search_seq_column,
639 : false));
640 : }
641 138 : if (cte->cycle_clause)
642 : {
643 72 : ctequery->targetList = lappend(ctequery->targetList,
644 72 : makeTargetEntry((Expr *) makeVar(1, cmc_attno,
645 72 : cte->cycle_clause->cycle_mark_type,
646 72 : cte->cycle_clause->cycle_mark_typmod,
647 72 : cte->cycle_clause->cycle_mark_collation, 0),
648 72 : list_length(ctequery->targetList) + 1,
649 72 : cte->cycle_clause->cycle_mark_column,
650 : false));
651 72 : ctequery->targetList = lappend(ctequery->targetList,
652 72 : makeTargetEntry((Expr *) makeVar(1, cpa_attno,
653 : RECORDARRAYOID, -1, InvalidOid, 0),
654 72 : list_length(ctequery->targetList) + 1,
655 72 : cte->cycle_clause->cycle_path_column,
656 : false));
657 : }
658 :
659 : /*
660 : * Add the additional columns to the CTE's output columns
661 : */
662 138 : cte->ctecolnames = ewcl;
663 138 : if (cte->search_clause)
664 : {
665 78 : cte->ctecoltypes = lappend_oid(cte->ctecoltypes, search_seq_type);
666 78 : cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, -1);
667 78 : cte->ctecolcollations = lappend_oid(cte->ctecolcollations, InvalidOid);
668 : }
669 138 : if (cte->cycle_clause)
670 : {
671 72 : cte->ctecoltypes = lappend_oid(cte->ctecoltypes, cte->cycle_clause->cycle_mark_type);
672 72 : cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, cte->cycle_clause->cycle_mark_typmod);
673 72 : cte->ctecolcollations = lappend_oid(cte->ctecolcollations, cte->cycle_clause->cycle_mark_collation);
674 :
675 72 : cte->ctecoltypes = lappend_oid(cte->ctecoltypes, RECORDARRAYOID);
676 72 : cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, -1);
677 72 : cte->ctecolcollations = lappend_oid(cte->ctecolcollations, InvalidOid);
678 : }
679 :
680 138 : return cte;
681 : }
|