Coverage Report

Created: 2026-08-14 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}