Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * rewriteSearchCycle.c
4 : : * Support for rewriting SEARCH and CYCLE clauses.
5 : : *
6 : : * Portions Copyright (c) 1996-2024, 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 *
1168 peter@eisentraut.org 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);
1000 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;
1168 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 : :
722 tgl@sss.pgh.pa.us 399 [ + + ]: 117 : if (e->rtekind == RTE_CTE &&
400 [ + + ]: 75 : strcmp(cte->ctename, e->ctename) == 0 &&
401 [ + + ]: 72 : e->ctelevelsup == 2)
402 : : {
1168 peter@eisentraut.org 403 : 69 : cte_rtindex = rti;
404 : 69 : break;
405 : : }
406 : : }
722 tgl@sss.pgh.pa.us 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 : :
1168 peter@eisentraut.org 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);
1000 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;
1168 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 : :
286 peter@eisentraut.org 526 :GNC 18 : linitial(search_col_rowexpr->args) = fexpr;
527 : :
1168 peter@eisentraut.org 528 :CBC 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,
949 610 : 6 : makeSortGroupClauseForSetOp(search_seq_type, true));
611 : : }
1168 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,
949 619 : 3 : makeSortGroupClauseForSetOp(cte->cycle_clause->cycle_mark_type, true));
620 : :
1168 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,
949 626 : 3 : makeSortGroupClauseForSetOp(RECORDARRAYOID, true));
627 : : }
628 : :
629 : : /*
630 : : * Add the additional columns to the CTE query's target list
631 : : */
1168 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 : : }
|