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