LCOV - code coverage report
Current view: top level - src/backend/rewrite - rewriteSearchCycle.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 100.0 % 276 276
Test Date: 2026-03-03 16:15:26 Functions: 100.0 % 4 4
Legend: Lines:     hit not hit

            Line data    Source code
       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           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              :     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              :     Assert(ctequery->setOperations);
     237           72 :     sos = castNode(SetOperationStmt, ctequery->setOperations);
     238              :     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              :     Assert(rte1->rtekind == RTE_SUBQUERY);
     247              :     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 = NULL;
     286           72 :     newrte->eref = makeAlias("*TLOCRN*", cte->ctecolnames);
     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);
     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;
     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, true),
     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 = NULL;
     383           72 :     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          120 :     for (int rti = 1; rti <= list_length(rte2->subquery->rtable); rti++)
     396              :     {
     397          117 :         RangeTblEntry *e = rt_fetch(rti, rte2->subquery->rtable);
     398              : 
     399          117 :         if (e->rtekind == RTE_CTE &&
     400           75 :             strcmp(cte->ctename, e->ctename) == 0 &&
     401           72 :             e->ctelevelsup == 2)
     402              :         {
     403           69 :             cte_rtindex = rti;
     404           69 :             break;
     405              :         }
     406              :     }
     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              : 
     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);
     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;
     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 :             linitial(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,
     610            6 :                                         makeSortGroupClauseForSetOp(search_seq_type, true));
     611              :     }
     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,
     619            3 :                                         makeSortGroupClauseForSetOp(cte->cycle_clause->cycle_mark_type, true));
     620              : 
     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,
     626            3 :                                         makeSortGroupClauseForSetOp(RECORDARRAYOID, true));
     627              :     }
     628              : 
     629              :     /*
     630              :      * Add the additional columns to the CTE query's target list
     631              :      */
     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              : }
        

Generated by: LCOV version 2.0-1