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