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