mirror of
https://github.com/postgres/postgres.git
synced 2025-07-08 11:42:09 +03:00
SEARCH and CYCLE clauses
This adds the SQL standard feature that adds the SEARCH and CYCLE clauses to recursive queries to be able to do produce breadth- or depth-first search orders and detect cycles. These clauses can be rewritten into queries using existing syntax, and that is what this patch does in the rewriter. Reviewed-by: Vik Fearing <vik@postgresfriends.org> Reviewed-by: Pavel Stehule <pavel.stehule@gmail.com> Discussion: https://www.postgresql.org/message-id/flat/db80ceee-6f97-9b4a-8ee8-3ba0c58e5be2@2ndquadrant.com
This commit is contained in:
@ -17,6 +17,7 @@ OBJS = \
|
||||
rewriteHandler.o \
|
||||
rewriteManip.o \
|
||||
rewriteRemove.o \
|
||||
rewriteSearchCycle.o \
|
||||
rewriteSupport.o \
|
||||
rowsecurity.o
|
||||
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include "rewrite/rewriteDefine.h"
|
||||
#include "rewrite/rewriteHandler.h"
|
||||
#include "rewrite/rewriteManip.h"
|
||||
#include "rewrite/rewriteSearchCycle.h"
|
||||
#include "rewrite/rowsecurity.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/lsyscache.h"
|
||||
@ -2079,6 +2080,23 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
|
||||
int rt_index;
|
||||
ListCell *lc;
|
||||
|
||||
/*
|
||||
* Expand SEARCH and CYCLE clauses in CTEs.
|
||||
*
|
||||
* This is just a convenient place to do this, since we are already
|
||||
* looking at each Query.
|
||||
*/
|
||||
foreach(lc, parsetree->cteList)
|
||||
{
|
||||
CommonTableExpr *cte = lfirst_node(CommonTableExpr, lc);
|
||||
|
||||
if (cte->search_clause || cte->cycle_clause)
|
||||
{
|
||||
cte = rewriteSearchAndCycle(cte);
|
||||
lfirst(lc) = cte;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* don't try to convert this into a foreach loop, because rtable list can
|
||||
* get changed each time through...
|
||||
|
668
src/backend/rewrite/rewriteSearchCycle.c
Normal file
668
src/backend/rewrite/rewriteSearchCycle.c
Normal file
@ -0,0 +1,668 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* rewriteSearchCycle.c
|
||||
* Support for rewriting SEARCH and CYCLE clauses.
|
||||
*
|
||||
* Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* src/backend/rewrite/rewriteSearchCycle.c
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "catalog/pg_operator_d.h"
|
||||
#include "catalog/pg_type_d.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/pg_list.h"
|
||||
#include "nodes/parsenodes.h"
|
||||
#include "nodes/primnodes.h"
|
||||
#include "parser/analyze.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "rewrite/rewriteManip.h"
|
||||
#include "rewrite/rewriteSearchCycle.h"
|
||||
#include "utils/fmgroids.h"
|
||||
|
||||
|
||||
/*----------
|
||||
* Rewrite a CTE with SEARCH or CYCLE clause
|
||||
*
|
||||
* Consider a CTE like
|
||||
*
|
||||
* WITH RECURSIVE ctename (col1, col2, col3) AS (
|
||||
* query1
|
||||
* UNION [ALL]
|
||||
* SELECT trosl FROM ctename
|
||||
* )
|
||||
*
|
||||
* With a search clause
|
||||
*
|
||||
* SEARCH BREADTH FIRST BY col1, col2 SET sqc
|
||||
*
|
||||
* the CTE is rewritten to
|
||||
*
|
||||
* WITH RECURSIVE ctename (col1, col2, col3, sqc) AS (
|
||||
* SELECT col1, col2, col3, -- original WITH column list
|
||||
* ROW(0, col1, col2) -- initial row of search columns
|
||||
* FROM (query1) "*TLOCRN*" (col1, col2, col3)
|
||||
* UNION [ALL]
|
||||
* SELECT col1, col2, col3, -- same as above
|
||||
* ROW(sqc.depth + 1, col1, col2) -- count depth
|
||||
* FROM (SELECT trosl, ctename.sqc FROM ctename) "*TROCRN*" (col1, col2, col3, sqc)
|
||||
* )
|
||||
*
|
||||
* (This isn't quite legal SQL: sqc.depth is meant to refer to the first
|
||||
* column of sqc, which has a row type, but the field names are not defined
|
||||
* here. Representing this properly in SQL would be more complicated (and the
|
||||
* SQL standard actually does it in that more complicated way), but the
|
||||
* internal representation allows us to construct it this way.)
|
||||
*
|
||||
* With a search caluse
|
||||
*
|
||||
* SEARCH DEPTH FIRST BY col1, col2 SET sqc
|
||||
*
|
||||
* the CTE is rewritten to
|
||||
*
|
||||
* WITH RECURSIVE ctename (col1, col2, col3, sqc) AS (
|
||||
* SELECT col1, col2, col3, -- original WITH column list
|
||||
* ARRAY[ROW(col1, col2)] -- initial row of search columns
|
||||
* FROM (query1) "*TLOCRN*" (col1, col2, col3)
|
||||
* UNION [ALL]
|
||||
* SELECT col1, col2, col3, -- same as above
|
||||
* sqc || ARRAY[ROW(col1, col2)] -- record rows seen
|
||||
* FROM (SELECT trosl, ctename.sqc FROM ctename) "*TROCRN*" (col1, col2, col3, sqc)
|
||||
* )
|
||||
*
|
||||
* With a cycle clause
|
||||
*
|
||||
* CYCLE col1, col2 SET cmc TO 'Y' DEFAULT 'N' USING cpa
|
||||
*
|
||||
* (cmc = cycle mark column, cpa = cycle path) the CTE is rewritten to
|
||||
*
|
||||
* WITH RECURSIVE ctename (col1, col2, col3, cmc, cpa) AS (
|
||||
* SELECT col1, col2, col3, -- original WITH column list
|
||||
* 'N', -- cycle mark default
|
||||
* ARRAY[ROW(col1, col2)] -- initial row of cycle columns
|
||||
* FROM (query1) "*TLOCRN*" (col1, col2, col3)
|
||||
* UNION [ALL]
|
||||
* SELECT col1, col2, col3, -- same as above
|
||||
* CASE WHEN ROW(col1, col2) = ANY (ARRAY[cpa]) THEN 'Y' ELSE 'N' END, -- compute cycle mark column
|
||||
* cpa || ARRAY[ROW(col1, col2)] -- record rows seen
|
||||
* FROM (SELECT trosl, ctename.cmc, ctename.cpa FROM ctename) "*TROCRN*" (col1, col2, col3, cmc, cpa)
|
||||
* WHERE cmc <> 'Y'
|
||||
* )
|
||||
*
|
||||
* The expression to compute the cycle mark column in the right-hand query is
|
||||
* written as
|
||||
*
|
||||
* CASE WHEN ROW(col1, col2) IN (SELECT p.* FROM TABLE(cpa) p) THEN cmv ELSE cmd END
|
||||
*
|
||||
* in the SQL standard, but in PostgreSQL we can use the scalar-array operator
|
||||
* expression shown above.
|
||||
*
|
||||
* Also, in some of the cases where operators are shown above we actually
|
||||
* directly produce the underlying function call.
|
||||
*
|
||||
* If both a search clause and a cycle clause is specified, then the search
|
||||
* clause column is added before the cycle clause columns.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Make a RowExpr from the specified column names, which have to be among the
|
||||
* output columns of the CTE.
|
||||
*/
|
||||
static RowExpr *
|
||||
make_path_rowexpr(const CommonTableExpr *cte, const List *col_list)
|
||||
{
|
||||
RowExpr *rowexpr;
|
||||
ListCell *lc;
|
||||
|
||||
rowexpr = makeNode(RowExpr);
|
||||
rowexpr->row_typeid = RECORDOID;
|
||||
rowexpr->row_format = COERCE_IMPLICIT_CAST;
|
||||
rowexpr->location = -1;
|
||||
|
||||
foreach(lc, col_list)
|
||||
{
|
||||
char *colname = strVal(lfirst(lc));
|
||||
|
||||
for (int i = 0; i < list_length(cte->ctecolnames); i++)
|
||||
{
|
||||
char *colname2 = strVal(list_nth(cte->ctecolnames, i));
|
||||
|
||||
if (strcmp(colname, colname2) == 0)
|
||||
{
|
||||
Var *var;
|
||||
|
||||
var = makeVar(1, i + 1,
|
||||
list_nth_oid(cte->ctecoltypes, i),
|
||||
list_nth_int(cte->ctecoltypmods, i),
|
||||
list_nth_oid(cte->ctecolcollations, i),
|
||||
0);
|
||||
rowexpr->args = lappend(rowexpr->args, var);
|
||||
rowexpr->colnames = lappend(rowexpr->colnames, makeString(colname));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rowexpr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wrap a RowExpr in an ArrayExpr, for the initial search depth first or cycle
|
||||
* row.
|
||||
*/
|
||||
static Expr *
|
||||
make_path_initial_array(RowExpr *rowexpr)
|
||||
{
|
||||
ArrayExpr *arr;
|
||||
|
||||
arr = makeNode(ArrayExpr);
|
||||
arr->array_typeid = RECORDARRAYOID;
|
||||
arr->element_typeid = RECORDOID;
|
||||
arr->location = -1;
|
||||
arr->elements = list_make1(rowexpr);
|
||||
|
||||
return (Expr *) arr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make an array catenation expression like
|
||||
*
|
||||
* cpa || ARRAY[ROW(cols)]
|
||||
*
|
||||
* where the varattno of cpa is provided as path_varattno.
|
||||
*/
|
||||
static Expr *
|
||||
make_path_cat_expr(RowExpr *rowexpr, AttrNumber path_varattno)
|
||||
{
|
||||
ArrayExpr *arr;
|
||||
FuncExpr *fexpr;
|
||||
|
||||
arr = makeNode(ArrayExpr);
|
||||
arr->array_typeid = RECORDARRAYOID;
|
||||
arr->element_typeid = RECORDOID;
|
||||
arr->location = -1;
|
||||
arr->elements = list_make1(rowexpr);
|
||||
|
||||
fexpr = makeFuncExpr(F_ARRAY_CAT, RECORDARRAYOID,
|
||||
list_make2(makeVar(1, path_varattno, RECORDARRAYOID, -1, 0, 0),
|
||||
arr),
|
||||
InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
|
||||
|
||||
return (Expr *) fexpr;
|
||||
}
|
||||
|
||||
/*
|
||||
* The real work happens here.
|
||||
*/
|
||||
CommonTableExpr *
|
||||
rewriteSearchAndCycle(CommonTableExpr *cte)
|
||||
{
|
||||
Query *ctequery;
|
||||
SetOperationStmt *sos;
|
||||
int rti1,
|
||||
rti2;
|
||||
RangeTblEntry *rte1,
|
||||
*rte2,
|
||||
*newrte;
|
||||
Query *newq1,
|
||||
*newq2;
|
||||
Query *newsubquery;
|
||||
RangeTblRef *rtr;
|
||||
Oid search_seq_type = InvalidOid;
|
||||
AttrNumber sqc_attno = InvalidAttrNumber;
|
||||
AttrNumber cmc_attno = InvalidAttrNumber;
|
||||
AttrNumber cpa_attno = InvalidAttrNumber;
|
||||
TargetEntry *tle;
|
||||
RowExpr *cycle_col_rowexpr = NULL;
|
||||
RowExpr *search_col_rowexpr = NULL;
|
||||
List *ewcl;
|
||||
int cte_rtindex = -1;
|
||||
|
||||
Assert(cte->search_clause || cte->cycle_clause);
|
||||
|
||||
cte = copyObject(cte);
|
||||
|
||||
ctequery = castNode(Query, cte->ctequery);
|
||||
|
||||
/*
|
||||
* The top level of the CTE's query should be a UNION. Find the two
|
||||
* subqueries.
|
||||
*/
|
||||
Assert(ctequery->setOperations);
|
||||
sos = castNode(SetOperationStmt, ctequery->setOperations);
|
||||
Assert(sos->op == SETOP_UNION);
|
||||
|
||||
rti1 = castNode(RangeTblRef, sos->larg)->rtindex;
|
||||
rti2 = castNode(RangeTblRef, sos->rarg)->rtindex;
|
||||
|
||||
rte1 = rt_fetch(rti1, ctequery->rtable);
|
||||
rte2 = rt_fetch(rti2, ctequery->rtable);
|
||||
|
||||
Assert(rte1->rtekind == RTE_SUBQUERY);
|
||||
Assert(rte2->rtekind == RTE_SUBQUERY);
|
||||
|
||||
/*
|
||||
* We'll need this a few times later.
|
||||
*/
|
||||
if (cte->search_clause)
|
||||
{
|
||||
if (cte->search_clause->search_breadth_first)
|
||||
search_seq_type = RECORDOID;
|
||||
else
|
||||
search_seq_type = RECORDARRAYOID;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attribute numbers of the added columns in the CTE's column list
|
||||
*/
|
||||
if (cte->search_clause)
|
||||
sqc_attno = list_length(cte->ctecolnames) + 1;
|
||||
if (cte->cycle_clause)
|
||||
{
|
||||
cmc_attno = list_length(cte->ctecolnames) + 1;
|
||||
cpa_attno = list_length(cte->ctecolnames) + 2;
|
||||
if (cte->search_clause)
|
||||
{
|
||||
cmc_attno++;
|
||||
cpa_attno++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Make new left subquery
|
||||
*/
|
||||
newq1 = makeNode(Query);
|
||||
newq1->commandType = CMD_SELECT;
|
||||
newq1->canSetTag = true;
|
||||
|
||||
newrte = makeNode(RangeTblEntry);
|
||||
newrte->rtekind = RTE_SUBQUERY;
|
||||
newrte->alias = makeAlias("*TLOCRN*", cte->ctecolnames);
|
||||
newrte->eref = newrte->alias;
|
||||
newsubquery = copyObject(rte1->subquery);
|
||||
IncrementVarSublevelsUp((Node *) newsubquery, 1, 1);
|
||||
newrte->subquery = newsubquery;
|
||||
newrte->inFromCl = true;
|
||||
newq1->rtable = list_make1(newrte);
|
||||
|
||||
rtr = makeNode(RangeTblRef);
|
||||
rtr->rtindex = 1;
|
||||
newq1->jointree = makeFromExpr(list_make1(rtr), NULL);
|
||||
|
||||
/*
|
||||
* Make target list
|
||||
*/
|
||||
for (int i = 0; i < list_length(cte->ctecolnames); i++)
|
||||
{
|
||||
Var *var;
|
||||
|
||||
var = makeVar(1, i + 1,
|
||||
list_nth_oid(cte->ctecoltypes, i),
|
||||
list_nth_int(cte->ctecoltypmods, i),
|
||||
list_nth_oid(cte->ctecolcollations, i),
|
||||
0);
|
||||
tle = makeTargetEntry((Expr *) var, i + 1, strVal(list_nth(cte->ctecolnames, i)), false);
|
||||
tle->resorigtbl = castNode(TargetEntry, list_nth(rte1->subquery->targetList, i))->resorigtbl;
|
||||
tle->resorigcol = castNode(TargetEntry, list_nth(rte1->subquery->targetList, i))->resorigcol;
|
||||
newq1->targetList = lappend(newq1->targetList, tle);
|
||||
}
|
||||
|
||||
if (cte->search_clause)
|
||||
{
|
||||
Expr *texpr;
|
||||
|
||||
search_col_rowexpr = make_path_rowexpr(cte, cte->search_clause->search_col_list);
|
||||
if (cte->search_clause->search_breadth_first)
|
||||
{
|
||||
search_col_rowexpr->args = lcons(makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
|
||||
Int64GetDatum(0), false, FLOAT8PASSBYVAL),
|
||||
search_col_rowexpr->args);
|
||||
search_col_rowexpr->colnames = lcons(makeString("*DEPTH*"), search_col_rowexpr->colnames);
|
||||
texpr = (Expr *) search_col_rowexpr;
|
||||
}
|
||||
else
|
||||
texpr = make_path_initial_array(search_col_rowexpr);
|
||||
tle = makeTargetEntry(texpr,
|
||||
list_length(newq1->targetList) + 1,
|
||||
cte->search_clause->search_seq_column,
|
||||
false);
|
||||
newq1->targetList = lappend(newq1->targetList, tle);
|
||||
}
|
||||
if (cte->cycle_clause)
|
||||
{
|
||||
tle = makeTargetEntry((Expr *) cte->cycle_clause->cycle_mark_default,
|
||||
list_length(newq1->targetList) + 1,
|
||||
cte->cycle_clause->cycle_mark_column,
|
||||
false);
|
||||
newq1->targetList = lappend(newq1->targetList, tle);
|
||||
cycle_col_rowexpr = make_path_rowexpr(cte, cte->cycle_clause->cycle_col_list);
|
||||
tle = makeTargetEntry(make_path_initial_array(cycle_col_rowexpr),
|
||||
list_length(newq1->targetList) + 1,
|
||||
cte->cycle_clause->cycle_path_column,
|
||||
false);
|
||||
newq1->targetList = lappend(newq1->targetList, tle);
|
||||
}
|
||||
|
||||
rte1->subquery = newq1;
|
||||
|
||||
if (cte->search_clause)
|
||||
{
|
||||
rte1->eref->colnames = lappend(rte1->eref->colnames, makeString(cte->search_clause->search_seq_column));
|
||||
}
|
||||
if (cte->cycle_clause)
|
||||
{
|
||||
rte1->eref->colnames = lappend(rte1->eref->colnames, makeString(cte->cycle_clause->cycle_mark_column));
|
||||
rte1->eref->colnames = lappend(rte1->eref->colnames, makeString(cte->cycle_clause->cycle_path_column));
|
||||
}
|
||||
|
||||
/*
|
||||
* Make new right subquery
|
||||
*/
|
||||
newq2 = makeNode(Query);
|
||||
newq2->commandType = CMD_SELECT;
|
||||
newq2->canSetTag = true;
|
||||
|
||||
newrte = makeNode(RangeTblEntry);
|
||||
newrte->rtekind = RTE_SUBQUERY;
|
||||
ewcl = copyObject(cte->ctecolnames);
|
||||
if (cte->search_clause)
|
||||
{
|
||||
ewcl = lappend(ewcl, makeString(cte->search_clause->search_seq_column));
|
||||
}
|
||||
if (cte->cycle_clause)
|
||||
{
|
||||
ewcl = lappend(ewcl, makeString(cte->cycle_clause->cycle_mark_column));
|
||||
ewcl = lappend(ewcl, makeString(cte->cycle_clause->cycle_path_column));
|
||||
}
|
||||
newrte->alias = makeAlias("*TROCRN*", ewcl);
|
||||
newrte->eref = newrte->alias;
|
||||
|
||||
/*
|
||||
* Find the reference to our CTE in the range table
|
||||
*/
|
||||
for (int rti = 1; rti <= list_length(rte2->subquery->rtable); rti++)
|
||||
{
|
||||
RangeTblEntry *e = rt_fetch(rti, rte2->subquery->rtable);
|
||||
|
||||
if (e->rtekind == RTE_CTE && strcmp(cte->ctename, e->ctename) == 0)
|
||||
{
|
||||
cte_rtindex = rti;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert(cte_rtindex > 0);
|
||||
|
||||
newsubquery = copyObject(rte2->subquery);
|
||||
IncrementVarSublevelsUp((Node *) newsubquery, 1, 1);
|
||||
|
||||
/*
|
||||
* Add extra columns to target list of subquery of right subquery
|
||||
*/
|
||||
if (cte->search_clause)
|
||||
{
|
||||
Var *var;
|
||||
|
||||
/* ctename.sqc */
|
||||
var = makeVar(cte_rtindex, sqc_attno,
|
||||
search_seq_type, -1, InvalidOid, 0);
|
||||
tle = makeTargetEntry((Expr *) var,
|
||||
list_length(newsubquery->targetList) + 1,
|
||||
cte->search_clause->search_seq_column,
|
||||
false);
|
||||
newsubquery->targetList = lappend(newsubquery->targetList, tle);
|
||||
}
|
||||
if (cte->cycle_clause)
|
||||
{
|
||||
Var *var;
|
||||
|
||||
/* ctename.cmc */
|
||||
var = makeVar(cte_rtindex, cmc_attno,
|
||||
cte->cycle_clause->cycle_mark_type,
|
||||
cte->cycle_clause->cycle_mark_typmod,
|
||||
cte->cycle_clause->cycle_mark_collation, 0);
|
||||
tle = makeTargetEntry((Expr *) var,
|
||||
list_length(newsubquery->targetList) + 1,
|
||||
cte->cycle_clause->cycle_mark_column,
|
||||
false);
|
||||
newsubquery->targetList = lappend(newsubquery->targetList, tle);
|
||||
|
||||
/* ctename.cpa */
|
||||
var = makeVar(cte_rtindex, cpa_attno,
|
||||
RECORDARRAYOID, -1, InvalidOid, 0);
|
||||
tle = makeTargetEntry((Expr *) var,
|
||||
list_length(newsubquery->targetList) + 1,
|
||||
cte->cycle_clause->cycle_path_column,
|
||||
false);
|
||||
newsubquery->targetList = lappend(newsubquery->targetList, tle);
|
||||
}
|
||||
|
||||
newrte->subquery = newsubquery;
|
||||
newrte->inFromCl = true;
|
||||
newq2->rtable = list_make1(newrte);
|
||||
|
||||
rtr = makeNode(RangeTblRef);
|
||||
rtr->rtindex = 1;
|
||||
|
||||
if (cte->cycle_clause)
|
||||
{
|
||||
Expr *expr;
|
||||
|
||||
/*
|
||||
* Add cmc <> cmv condition
|
||||
*/
|
||||
expr = make_opclause(cte->cycle_clause->cycle_mark_neop, BOOLOID, false,
|
||||
(Expr *) makeVar(1, cmc_attno,
|
||||
cte->cycle_clause->cycle_mark_type,
|
||||
cte->cycle_clause->cycle_mark_typmod,
|
||||
cte->cycle_clause->cycle_mark_collation, 0),
|
||||
(Expr *) cte->cycle_clause->cycle_mark_value,
|
||||
InvalidOid,
|
||||
cte->cycle_clause->cycle_mark_collation);
|
||||
|
||||
newq2->jointree = makeFromExpr(list_make1(rtr), (Node *) expr);
|
||||
}
|
||||
else
|
||||
newq2->jointree = makeFromExpr(list_make1(rtr), NULL);
|
||||
|
||||
/*
|
||||
* Make target list
|
||||
*/
|
||||
for (int i = 0; i < list_length(cte->ctecolnames); i++)
|
||||
{
|
||||
Var *var;
|
||||
|
||||
var = makeVar(1, i + 1,
|
||||
list_nth_oid(cte->ctecoltypes, i),
|
||||
list_nth_int(cte->ctecoltypmods, i),
|
||||
list_nth_oid(cte->ctecolcollations, i),
|
||||
0);
|
||||
tle = makeTargetEntry((Expr *) var, i + 1, strVal(list_nth(cte->ctecolnames, i)), false);
|
||||
tle->resorigtbl = castNode(TargetEntry, list_nth(rte2->subquery->targetList, i))->resorigtbl;
|
||||
tle->resorigcol = castNode(TargetEntry, list_nth(rte2->subquery->targetList, i))->resorigcol;
|
||||
newq2->targetList = lappend(newq2->targetList, tle);
|
||||
}
|
||||
|
||||
if (cte->search_clause)
|
||||
{
|
||||
Expr *texpr;
|
||||
|
||||
if (cte->search_clause->search_breadth_first)
|
||||
{
|
||||
FieldSelect *fs;
|
||||
FuncExpr *fexpr;
|
||||
|
||||
/*
|
||||
* ROW(sqc.depth + 1, cols)
|
||||
*/
|
||||
|
||||
search_col_rowexpr = copyObject(search_col_rowexpr);
|
||||
|
||||
fs = makeNode(FieldSelect);
|
||||
fs->arg = (Expr *) makeVar(1, sqc_attno, RECORDOID, -1, 0, 0);
|
||||
fs->fieldnum = 1;
|
||||
fs->resulttype = INT8OID;
|
||||
fs->resulttypmod = -1;
|
||||
|
||||
fexpr = makeFuncExpr(F_INT8INC, INT8OID, list_make1(fs), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
|
||||
|
||||
lfirst(list_head(search_col_rowexpr->args)) = fexpr;
|
||||
|
||||
texpr = (Expr *) search_col_rowexpr;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* sqc || ARRAY[ROW(cols)]
|
||||
*/
|
||||
texpr = make_path_cat_expr(search_col_rowexpr, sqc_attno);
|
||||
}
|
||||
tle = makeTargetEntry(texpr,
|
||||
list_length(newq2->targetList) + 1,
|
||||
cte->search_clause->search_seq_column,
|
||||
false);
|
||||
newq2->targetList = lappend(newq2->targetList, tle);
|
||||
}
|
||||
|
||||
if (cte->cycle_clause)
|
||||
{
|
||||
ScalarArrayOpExpr *saoe;
|
||||
CaseExpr *caseexpr;
|
||||
CaseWhen *casewhen;
|
||||
|
||||
/*
|
||||
* CASE WHEN ROW(cols) = ANY (ARRAY[cpa]) THEN cmv ELSE cmd END
|
||||
*/
|
||||
|
||||
saoe = makeNode(ScalarArrayOpExpr);
|
||||
saoe->location = -1;
|
||||
saoe->opno = RECORD_EQ_OP;
|
||||
saoe->useOr = true;
|
||||
saoe->args = list_make2(cycle_col_rowexpr,
|
||||
makeVar(1, cpa_attno, RECORDARRAYOID, -1, 0, 0));
|
||||
|
||||
caseexpr = makeNode(CaseExpr);
|
||||
caseexpr->location = -1;
|
||||
caseexpr->casetype = cte->cycle_clause->cycle_mark_type;
|
||||
caseexpr->casecollid = cte->cycle_clause->cycle_mark_collation;
|
||||
casewhen = makeNode(CaseWhen);
|
||||
casewhen->location = -1;
|
||||
casewhen->expr = (Expr *) saoe;
|
||||
casewhen->result = (Expr *) cte->cycle_clause->cycle_mark_value;
|
||||
caseexpr->args = list_make1(casewhen);
|
||||
caseexpr->defresult = (Expr *) cte->cycle_clause->cycle_mark_default;
|
||||
|
||||
tle = makeTargetEntry((Expr *) caseexpr,
|
||||
list_length(newq2->targetList) + 1,
|
||||
cte->cycle_clause->cycle_mark_column,
|
||||
false);
|
||||
newq2->targetList = lappend(newq2->targetList, tle);
|
||||
|
||||
/*
|
||||
* cpa || ARRAY[ROW(cols)]
|
||||
*/
|
||||
tle = makeTargetEntry(make_path_cat_expr(cycle_col_rowexpr, cpa_attno),
|
||||
list_length(newq2->targetList) + 1,
|
||||
cte->cycle_clause->cycle_path_column,
|
||||
false);
|
||||
newq2->targetList = lappend(newq2->targetList, tle);
|
||||
}
|
||||
|
||||
rte2->subquery = newq2;
|
||||
|
||||
if (cte->search_clause)
|
||||
{
|
||||
rte2->eref->colnames = lappend(rte2->eref->colnames, makeString(cte->search_clause->search_seq_column));
|
||||
}
|
||||
if (cte->cycle_clause)
|
||||
{
|
||||
rte2->eref->colnames = lappend(rte2->eref->colnames, makeString(cte->cycle_clause->cycle_mark_column));
|
||||
rte2->eref->colnames = lappend(rte2->eref->colnames, makeString(cte->cycle_clause->cycle_path_column));
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the additional columns to the SetOperationStmt
|
||||
*/
|
||||
if (cte->search_clause)
|
||||
{
|
||||
sos->colTypes = lappend_oid(sos->colTypes, search_seq_type);
|
||||
sos->colTypmods = lappend_int(sos->colTypmods, -1);
|
||||
sos->colCollations = lappend_oid(sos->colCollations, InvalidOid);
|
||||
if (!sos->all)
|
||||
sos->groupClauses = lappend(sos->groupClauses,
|
||||
makeSortGroupClauseForSetOp(search_seq_type));
|
||||
}
|
||||
if (cte->cycle_clause)
|
||||
{
|
||||
sos->colTypes = lappend_oid(sos->colTypes, cte->cycle_clause->cycle_mark_type);
|
||||
sos->colTypmods = lappend_int(sos->colTypmods, cte->cycle_clause->cycle_mark_typmod);
|
||||
sos->colCollations = lappend_oid(sos->colCollations, cte->cycle_clause->cycle_mark_collation);
|
||||
if (!sos->all)
|
||||
sos->groupClauses = lappend(sos->groupClauses,
|
||||
makeSortGroupClauseForSetOp(cte->cycle_clause->cycle_mark_type));
|
||||
|
||||
sos->colTypes = lappend_oid(sos->colTypes, RECORDARRAYOID);
|
||||
sos->colTypmods = lappend_int(sos->colTypmods, -1);
|
||||
sos->colCollations = lappend_oid(sos->colCollations, InvalidOid);
|
||||
if (!sos->all)
|
||||
sos->groupClauses = lappend(sos->groupClauses,
|
||||
makeSortGroupClauseForSetOp(RECORDARRAYOID));
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the additional columns to the CTE query's target list
|
||||
*/
|
||||
if (cte->search_clause)
|
||||
{
|
||||
ctequery->targetList = lappend(ctequery->targetList,
|
||||
makeTargetEntry((Expr *) makeVar(1, sqc_attno,
|
||||
search_seq_type, -1, InvalidOid, 0),
|
||||
list_length(ctequery->targetList) + 1,
|
||||
cte->search_clause->search_seq_column,
|
||||
false));
|
||||
}
|
||||
if (cte->cycle_clause)
|
||||
{
|
||||
ctequery->targetList = lappend(ctequery->targetList,
|
||||
makeTargetEntry((Expr *) makeVar(1, cmc_attno,
|
||||
cte->cycle_clause->cycle_mark_type,
|
||||
cte->cycle_clause->cycle_mark_typmod,
|
||||
cte->cycle_clause->cycle_mark_collation, 0),
|
||||
list_length(ctequery->targetList) + 1,
|
||||
cte->cycle_clause->cycle_mark_column,
|
||||
false));
|
||||
ctequery->targetList = lappend(ctequery->targetList,
|
||||
makeTargetEntry((Expr *) makeVar(1, cpa_attno,
|
||||
RECORDARRAYOID, -1, InvalidOid, 0),
|
||||
list_length(ctequery->targetList) + 1,
|
||||
cte->cycle_clause->cycle_path_column,
|
||||
false));
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the additional columns to the CTE's output columns
|
||||
*/
|
||||
cte->ctecolnames = ewcl;
|
||||
if (cte->search_clause)
|
||||
{
|
||||
cte->ctecoltypes = lappend_oid(cte->ctecoltypes, search_seq_type);
|
||||
cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, -1);
|
||||
cte->ctecolcollations = lappend_oid(cte->ctecolcollations, InvalidOid);
|
||||
}
|
||||
if (cte->cycle_clause)
|
||||
{
|
||||
cte->ctecoltypes = lappend_oid(cte->ctecoltypes, cte->cycle_clause->cycle_mark_type);
|
||||
cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, cte->cycle_clause->cycle_mark_typmod);
|
||||
cte->ctecolcollations = lappend_oid(cte->ctecolcollations, cte->cycle_clause->cycle_mark_collation);
|
||||
|
||||
cte->ctecoltypes = lappend_oid(cte->ctecoltypes, RECORDARRAYOID);
|
||||
cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, -1);
|
||||
cte->ctecolcollations = lappend_oid(cte->ctecolcollations, InvalidOid);
|
||||
}
|
||||
|
||||
return cte;
|
||||
}
|
Reference in New Issue
Block a user