
Offering: GaussDB Kernel More detail: when executing connect by prior b=a and (1=1 or rownum=1), the 1=1 subclause will not be dropped.
2054 lines
69 KiB
C++
2054 lines
69 KiB
C++
/* -------------------------------------------------------------------------
|
|
*
|
|
* parse_startwith.cpp
|
|
* Implement start with related modules in transform state
|
|
*
|
|
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
* Portions Copyright (c) 2021, openGauss Contributors
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/common/backend/parser/parse_startwith.cpp
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include "miscadmin.h"
|
|
|
|
#include "access/heapam.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/heap.h"
|
|
#include "catalog/pg_synonym.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "commands/defrem.h"
|
|
#include "commands/tablecmds.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "optimizer/clauses.h"
|
|
#include "optimizer/tlist.h"
|
|
#include "optimizer/planner.h"
|
|
#include "parser/analyze.h"
|
|
#include "parser/parsetree.h"
|
|
#include "parser/parse_clause.h"
|
|
#include "parser/parse_coerce.h"
|
|
#include "parser/parse_collate.h"
|
|
#include "parser/parse_expr.h"
|
|
#include "parser/parse_oper.h"
|
|
#include "parser/parse_relation.h"
|
|
#include "parser/parse_target.h"
|
|
#include "parser/parse_cte.h"
|
|
#include "pgxc/pgxc.h"
|
|
#include "rewrite/rewriteManip.h"
|
|
#include "storage/tcap.h"
|
|
#include "utils/guc.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/rel_gs.h"
|
|
#include "utils/syscache.h"
|
|
|
|
/*
|
|
****************************************************************************************
|
|
* @Brief: Oracle compatible START WITH...CONNECT BY support, define start with relevant
|
|
* data structures and routines
|
|
*
|
|
* @Note: The only entry point of START WITH in parser is transformStartWith() where
|
|
* all internal implementations are hiden behind
|
|
*
|
|
****************************************************************************************
|
|
*/
|
|
typedef struct StartWithTransformContext {
|
|
ParseState* pstate;
|
|
List *relInfoList;
|
|
List *where_infos;
|
|
List *connectby_prior_name;
|
|
Node *startWithExpr;
|
|
Node *connectByExpr;
|
|
Node *whereClause;
|
|
List *siblingsOrderBy;
|
|
List *rownum_or_level_list;
|
|
List *normal_list;
|
|
bool is_where;
|
|
StartWithConnectByType connect_by_type;
|
|
|
|
/*
|
|
* for multiple pushdown case
|
|
*/
|
|
List *fromClause;
|
|
|
|
/*
|
|
* connectByLevelExpr & connectByOtherExpr
|
|
*
|
|
* Normally consider performance to for connect by level/rownum, we extract the
|
|
* level/rownum expr from connectByExpr
|
|
* - connectByOtherExpr: evaluated in RecursiveUnion's inner join cond
|
|
* - connectByLevelExpr: evaluated in StartWithOp's node
|
|
*
|
|
* e.g.
|
|
* connectByExpr: (level < 10) AND (t1.c1 > 1) AND (t1.c2 > 0)
|
|
* connectByLevelExpr: level < 10
|
|
* connectByOtherExpr: t1.c1 = 1 AND
|
|
*/
|
|
Node *connectByLevelExpr;
|
|
Node *connectByOtherExpr;
|
|
bool nocycle;
|
|
} StartWithTransformContext;
|
|
|
|
typedef enum StartWithRewrite {
|
|
SW_NONE = 0,
|
|
SW_SINGLE,
|
|
SW_FROMLIST,
|
|
SW_JOINEXP,
|
|
} StaretWithRewrite;
|
|
|
|
typedef struct StartWithCTEPseudoReturnAtts {
|
|
char *colname;
|
|
Oid coltype;
|
|
int32 coltypmod;
|
|
Oid colcollation;
|
|
} StartWithCTEPseudoReturnAtts;
|
|
|
|
static StartWithCTEPseudoReturnAtts g_StartWithCTEPseudoReturnAtts[] =
|
|
{
|
|
{"level", INT4OID, -1, InvalidOid},
|
|
{"connect_by_isleaf", INT4OID, -1, InvalidOid},
|
|
{"connect_by_iscycle", INT4OID, -1, InvalidOid},
|
|
{"rownum", INT4OID, -1, InvalidOid}
|
|
};
|
|
|
|
/* function parsing startWithExpr and connectByExpr */
|
|
static void transformStartWithClause(StartWithTransformContext *context, SelectStmt *stmt);
|
|
|
|
static List *expandAllTargetList(List *targetRelInfoList);
|
|
|
|
static List *ExtractColumnRefStartInfo(ParseState *pstate, ColumnRef *column, char *relname, char *colname,
|
|
Node *preResult);
|
|
static void HandleSWCBColumnRef(StartWithTransformContext* context, Node *node);
|
|
static void StartWithWalker(StartWithTransformContext *context, Node *expr);
|
|
static void AddWithClauseToBranch(ParseState *pstate, SelectStmt *stmt, List *relInfoList);
|
|
|
|
static void transformSingleRTE(ParseState* pstate,
|
|
Query* qry,
|
|
StartWithTransformContext *context,
|
|
Node *start_clause);
|
|
static void transformFromList(ParseState* pstate,
|
|
Query* qry,
|
|
StartWithTransformContext *context,
|
|
Node *n);
|
|
static RangeTblEntry *transformStartWithCTE(ParseState* pstate, List *prior_names);
|
|
static bool preSkipPLSQLParams(ParseState *pstate, ColumnRef *cref);
|
|
|
|
|
|
/* functions to make start-with converted CTE's left/right branch */
|
|
static SelectStmt *CreateStartWithCTEOuterBranch(ParseState* pstate,
|
|
StartWithTransformContext *context,
|
|
List *relInfoList,
|
|
Node *startWithExpr,
|
|
Node *whereClause);
|
|
static SelectStmt *CreateStartWithCTEInnerBranch(ParseState* pstate,
|
|
StartWithTransformContext *context,
|
|
List *relInfoList,
|
|
Node *connectByExpr,
|
|
Node *whereClause);
|
|
static void CreateStartWithCTE(ParseState *pstate,
|
|
Query *qry,
|
|
SelectStmt *initpart,
|
|
SelectStmt *workpart,
|
|
StartWithTransformContext *context);
|
|
|
|
static Node *makeBoolAConst(bool state, int location);
|
|
static StartWithRewrite ChooseSWCBStrategy(StartWithTransformContext context);
|
|
static Node *tryReplaceFakeValue(Node *node);
|
|
|
|
static Node *makeBoolAConst(bool state, int location)
|
|
{
|
|
A_Const *constExpr = makeNode(A_Const);
|
|
|
|
constExpr->val.type = T_String;
|
|
constExpr->val.val.str = (char *)(state ? "t" : "f");
|
|
constExpr->location = location;
|
|
|
|
TypeCast *typecast = makeNode(TypeCast);
|
|
typecast->arg = (Node *)constExpr;
|
|
typecast->typname = makeTypeNameFromNameList(
|
|
list_make2(makeString("pg_catalog"),
|
|
makeString("bool")));
|
|
typecast->location = location;
|
|
|
|
return (Node *)typecast;
|
|
}
|
|
|
|
/*
|
|
* ---------------------------------------------------------------------------------------
|
|
*
|
|
* Oracle Compatible "START WITH...CONNECT BY" support routines
|
|
*
|
|
* ---------------------------------------------------------------------------------------
|
|
*/
|
|
/*
|
|
****************************************************************************************
|
|
* transformStartWith -
|
|
*
|
|
* @Brief: Start With primary entry point, all internal implementations are hiden behind
|
|
* processing implemantion are described as follow:
|
|
*
|
|
* exec_simple_query()
|
|
* -> parse_and_analyze()
|
|
* -> transformStmt()
|
|
* -> transformSelectStmt() {... details described as below ...}
|
|
*
|
|
* transformSelectStmt(pstate) {
|
|
* ...
|
|
* transformFromClause() {
|
|
* // identify START WITH...CONNECT BY's target relation and create a relevant
|
|
* // StartWithTargetRelInfo put them in parse->p_start_info and later processed in top
|
|
* // level transformStartWith()
|
|
* }
|
|
* ...
|
|
* transformStartWith() {
|
|
* // 1st. parse startWithExpr/connectByExpr
|
|
* transformStartWithClause();
|
|
*
|
|
* // 2nd. convert a START WITH...CONNECT BY object into a recursive CTE
|
|
* transformSingleRTE() {
|
|
* // 2.1 construct CTE's lefttree, a.w.k non-recursive term
|
|
* CreateStartWithCTEOuterBranch();
|
|
*
|
|
* // 2.2 construct CTE's righttree, a.w.k recursive term
|
|
* CreateStartWithCTEInnerBranch();
|
|
*
|
|
* // 2.3 construct start with converted CTE object
|
|
* CreateStartWithCTE();
|
|
*
|
|
* // 2.4 transform start with CTE normally as we did for a regular
|
|
* // with-recursive case
|
|
* transformStartWithCTE();
|
|
* }
|
|
* }
|
|
* }
|
|
*
|
|
****************************************************************************************
|
|
*/
|
|
void transformStartWith(ParseState *pstate, SelectStmt *stmt, Query *qry)
|
|
{
|
|
ListCell *lc = NULL;
|
|
StartWithTransformContext context;
|
|
|
|
if (pstate->p_start_info == NULL) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Not found correct element in fromclause could be rewrited by start with/connect by."),
|
|
errdetail("Only support table and subquery in fromclause can be rewrited.")));
|
|
}
|
|
|
|
errno_t rc = memset_s(&context, sizeof(StartWithTransformContext),
|
|
0, sizeof(StartWithTransformContext));
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
context.pstate = pstate;
|
|
context.relInfoList = NULL;
|
|
context.connectby_prior_name = NULL;
|
|
|
|
transformStartWithClause(&context, stmt);
|
|
pstate->p_hasStartWith = true;
|
|
|
|
int lens = list_length(pstate->p_start_info);
|
|
if (lens != 1) {
|
|
context.relInfoList = pstate->p_start_info;
|
|
context.fromClause = pstate->sw_fromClause;
|
|
}
|
|
|
|
/* set result-RTEs as replaced flag */
|
|
foreach(lc, context.relInfoList) {
|
|
StartWithTargetRelInfo *info = (StartWithTargetRelInfo *)lfirst(lc);
|
|
info->rte->swAborted = true;
|
|
}
|
|
|
|
/* Choose SWCB Rewrite Strategy. */
|
|
StartWithRewrite op = ChooseSWCBStrategy(context);
|
|
if (op == SW_SINGLE) {
|
|
transformSingleRTE(pstate, qry, &context, (Node *)stmt->startWithClause);
|
|
} else {
|
|
transformFromList(pstate, qry, &context, (Node *)stmt->startWithClause);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: SWCB's helper function in transform stage, here we create StartWithInfo object
|
|
* to each "start with" applied FromClauseItem and insert it into pastate,
|
|
* normally we are handling following case:
|
|
* - RangeVar(baserel)
|
|
* - RangeSubSelect (subquery)
|
|
* - Function and CTE will considered in the future
|
|
*
|
|
* @Param
|
|
* - pstate: paser stage
|
|
* - n: one FromClauseItem
|
|
* - rte: the FromClauseItem relevant RTE
|
|
* - rte: the FromClauseItem relevant RTR
|
|
*
|
|
* @Return: none
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
void AddStartWithTargetRelInfo(ParseState* pstate, Node* relNode,
|
|
RangeTblEntry* rte, RangeTblRef *rtr)
|
|
{
|
|
StartWithTargetRelInfo *startInfo = makeNode(StartWithTargetRelInfo);
|
|
|
|
if (IsA(relNode, RangeVar)) {
|
|
RangeVar* rv = (RangeVar*)relNode;
|
|
|
|
startInfo->relname = rv->relname;
|
|
if (rv->alias != NULL) {
|
|
startInfo->aliasname = rv->alias->aliasname;
|
|
}
|
|
startInfo->rte = rte;
|
|
startInfo->rtr = rtr;
|
|
startInfo->rtekind = rte->rtekind;
|
|
|
|
RangeVar *tbl = (RangeVar *)copyObject(relNode);
|
|
|
|
startInfo->tblstmt = (Node *)tbl;
|
|
startInfo->columns = rte->eref->colnames;
|
|
} else if (IsA(relNode, RangeSubselect)) {
|
|
RangeSubselect *sub = (RangeSubselect *)relNode;
|
|
|
|
/* name __unnamed_subquery__ */
|
|
if (pg_strcasecmp(sub->alias->aliasname, "__unnamed_subquery__") != 0) {
|
|
startInfo->aliasname = sub->alias->aliasname;
|
|
} else {
|
|
char swname[NAMEDATALEN];
|
|
int rc = snprintf_s(swname, sizeof(swname), sizeof(swname) - 1,
|
|
"sw_subquery_%d", pstate->sw_subquery_idx);
|
|
securec_check_ss(rc, "", "");
|
|
pstate->sw_subquery_idx++;
|
|
|
|
sub->alias->aliasname = pstrdup(swname);
|
|
startInfo->aliasname = sub->alias->aliasname;
|
|
}
|
|
|
|
SelectStmt *substmt = (SelectStmt *)sub->subquery;
|
|
if (substmt->withClause == NULL) {
|
|
substmt->withClause = (WithClause *)copyObject(pstate->origin_with);
|
|
}
|
|
startInfo->rte = rte;
|
|
startInfo->rtr = rtr;
|
|
startInfo->rtekind = rte->rtekind;
|
|
startInfo->tblstmt = relNode;
|
|
startInfo->columns = rte->eref->colnames;
|
|
} else {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
|
|
errmsg("Not support unrecognized node type: %d %s once AddStartWithInfo.",
|
|
(int)nodeTag(relNode), nodeToString(relNode))));
|
|
}
|
|
|
|
pstate->p_start_info = lappend(pstate->p_start_info, startInfo);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: Generate dummy target name for start with converted RTE, relation is renamed
|
|
* as "t1" -> "tmp_result", "t1.c1" -> "tmp_result@c1"
|
|
*
|
|
* @param:
|
|
* - alias: base cte's ctename
|
|
* - colname: base table's attr name
|
|
*
|
|
* @Return: a copied string version of dummy-colname
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
char *makeStartWithDummayColname(char *alias, char *column)
|
|
{
|
|
errno_t rc;
|
|
char *result = NULL;
|
|
char *split = "@";
|
|
char dumy_name[NAMEDATALEN];
|
|
|
|
int total = strlen(alias) + strlen(column) + strlen(split);
|
|
if (total >= NAMEDATALEN) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
|
|
errmsg("Exceed maximum StartWithDummayColname length %d, relname %s, column %s.",
|
|
total, alias, column)));
|
|
}
|
|
|
|
rc = memset_s(dumy_name, NAMEDATALEN, 0, NAMEDATALEN);
|
|
securec_check_c(rc, "\0", "\0");
|
|
rc = strncat_s(dumy_name, NAMEDATALEN, alias, strlen(alias));
|
|
securec_check_c(rc, "\0", "\0");
|
|
|
|
/* add split operator @ */
|
|
rc = strncat_s(dumy_name, NAMEDATALEN, split, strlen(split));
|
|
securec_check_c(rc, "\0", "\0");
|
|
|
|
rc = strncat_s(dumy_name, NAMEDATALEN, column, strlen(column));
|
|
securec_check_c(rc, "\0", "\0");
|
|
|
|
result = pstrdup(dumy_name);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: to be added
|
|
*
|
|
* @param:
|
|
*
|
|
* @Return: to be add
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
void AdaptSWSelectStmt(ParseState *pstate, SelectStmt *stmt)
|
|
{
|
|
ListCell *lc = NULL;
|
|
List *tbls = NULL;
|
|
|
|
foreach(lc, pstate->p_start_info) {
|
|
StartWithTargetRelInfo *info = (StartWithTargetRelInfo *)lfirst(lc);
|
|
|
|
if (!info->rte->swAborted) {
|
|
tbls = lappend(tbls, info->tblstmt);
|
|
}
|
|
}
|
|
|
|
RangeVar *cte = makeRangeVar(NULL, "tmp_reuslt", -1);
|
|
tbls = lappend(tbls, cte);
|
|
|
|
stmt->fromClause = tbls;
|
|
|
|
return;
|
|
}
|
|
|
|
bool IsSWCBRewriteRTE(RangeTblEntry *rte)
|
|
{
|
|
bool result = false;
|
|
|
|
if (rte->rtekind != RTE_CTE) {
|
|
return result;
|
|
}
|
|
|
|
if (pg_strcasecmp(rte->ctename, "tmp_reuslt") != 0) {
|
|
return result;
|
|
}
|
|
|
|
result = rte->swConverted;
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: to be added
|
|
*
|
|
* @param:
|
|
*
|
|
* @Return: to be add
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
bool IsQuerySWCBRewrite(Query *query)
|
|
{
|
|
bool isSWCBRewrite = false;
|
|
ListCell *lc = NULL;
|
|
|
|
foreach(lc, query->rtable) {
|
|
RangeTblEntry *rte = (RangeTblEntry *)lfirst(lc);
|
|
|
|
/*
|
|
* Only RTE_CTE chould be rerwite startwith CTE, others skip.
|
|
* */
|
|
if (rte->rtekind != RTE_CTE) {
|
|
continue;
|
|
}
|
|
|
|
if (pg_strcasecmp(rte->ctename, "tmp_reuslt") == 0) {
|
|
isSWCBRewrite = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return isSWCBRewrite;
|
|
}
|
|
|
|
static StartWithRewrite ChooseSWCBStrategy(StartWithTransformContext context)
|
|
{
|
|
StartWithRewrite op = SW_NONE;
|
|
int info_lens = list_length(context.relInfoList);
|
|
|
|
/*
|
|
* Only one StartWithInfos is SW_SINGLE, and multiples stands for
|
|
* SW_FROMLIST Strategy. Others is coming.
|
|
*/
|
|
if (info_lens == 1) {
|
|
op = SW_SINGLE;
|
|
} else {
|
|
op = SW_FROMLIST;
|
|
}
|
|
|
|
return op;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: pre-check PLSQL input/output columns same as transformColumnRef
|
|
*
|
|
* @param:
|
|
*
|
|
* @Return: skip these columns during startWithWalker or not
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static bool preSkipPLSQLParams(ParseState *pstate, ColumnRef *cref)
|
|
{
|
|
Node* node = NULL;
|
|
|
|
/*
|
|
* Give the PreParseColumnRefHook, if any, first shot. If it returns
|
|
* non-null then that's all, folks.
|
|
*/
|
|
if (pstate->p_pre_columnref_hook != NULL) {
|
|
node = (*pstate->p_pre_columnref_hook)(pstate, cref);
|
|
if (node != NULL) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (pstate->p_post_columnref_hook != NULL) {
|
|
Node* hookresult = NULL;
|
|
|
|
/*
|
|
* if node is not a table column, we should pass a null to the hook,
|
|
* or it will misjudge the columnref as ambiguous.
|
|
*/
|
|
hookresult = (*pstate->p_post_columnref_hook)(pstate, cref, node);
|
|
if (hookresult != NULL) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param preResult Var struct
|
|
* @return true if is a upper query column
|
|
*/
|
|
static inline bool IsAUpperQueryColumn(Node *preResult)
|
|
{
|
|
if (IsA(preResult, Var)) {
|
|
Var *varNode = (Var *)preResult;
|
|
if (!IS_SPECIAL_VARNO(varNode->varno) && varNode->varlevelsup >= 1) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: to be input
|
|
*
|
|
* @param: to be input
|
|
*
|
|
* @Return: to be input
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static List *ExtractColumnRefStartInfo(ParseState *pstate, ColumnRef *column, char *relname, char *colname,
|
|
Node *preResult)
|
|
{
|
|
ListCell *lc1 = NULL;
|
|
ListCell *lc2 = NULL;
|
|
List *extract_infos = NIL;
|
|
|
|
foreach(lc1, pstate->p_start_info) {
|
|
StartWithTargetRelInfo *start_info = (StartWithTargetRelInfo *)lfirst(lc1);
|
|
|
|
bool column_exist = false;
|
|
/*
|
|
* relname not exist, then check column names,
|
|
* otherwise relanme exist, then check StartWithInfo's relname/aliasname directly.
|
|
*/
|
|
if (relname == NULL) {
|
|
foreach(lc2, start_info->columns) {
|
|
Value *val = (Value *)lfirst(lc2);
|
|
if (strcmp(colname, strVal(val)) == 0) {
|
|
column_exist = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (start_info->aliasname != NULL &&
|
|
strcmp(start_info->aliasname, relname) == 0) {
|
|
column_exist = true;
|
|
} else if (start_info->relname != NULL &&
|
|
strcmp(start_info->relname, relname) == 0) {
|
|
column_exist = true;
|
|
} else {
|
|
column_exist = false;
|
|
}
|
|
}
|
|
|
|
/* handle with sub startwith cte targetlist */
|
|
if (start_info->rte->swSubExist) {
|
|
Assert(start_info->rtekind == RTE_SUBQUERY);
|
|
|
|
foreach(lc2, start_info->columns) {
|
|
Value *val = (Value *)lfirst(lc2);
|
|
if (strstr(strVal(val), colname)) {
|
|
column_exist = true;
|
|
colname = pstrdup(strVal(val));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (column_exist) {
|
|
extract_infos = lappend(extract_infos, start_info);
|
|
|
|
if (start_info->rtekind == RTE_RELATION) {
|
|
relname = start_info->aliasname ? start_info->aliasname : start_info->relname;
|
|
} else if (start_info->rtekind == RTE_SUBQUERY) {
|
|
relname = start_info->aliasname;
|
|
} else if (start_info->rtekind == RTE_CTE) {
|
|
relname = start_info->aliasname ? start_info->aliasname : start_info->relname;
|
|
}
|
|
|
|
column->fields = list_make2(makeString(relname), makeString(colname));
|
|
}
|
|
}
|
|
|
|
if (list_length(extract_infos) > 1) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_AMBIGUOUS_COLUMN),
|
|
errmsg("column reference \"%s\" is ambiguous.", colname)));
|
|
}
|
|
|
|
if (list_length(extract_infos) == 0 && !IsAUpperQueryColumn(preResult)) {
|
|
ereport(WARNING,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Cannot match table with startwith/connectby column %s.%s, maybe it is a upper query column",
|
|
relname, colname)));
|
|
}
|
|
|
|
return extract_infos;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: A helper function invoked under startWithWalker(), where transform ConnectBy
|
|
* or StartWith expression, reach a ColumnRef node, we need evaluate it into its
|
|
* "baserel->cte" format by call makeStartWithDummayColname()
|
|
*
|
|
* @param:
|
|
* - context: the startwith conversion context
|
|
* - node: the ColumnRef node, basically we need assum it is with type of ColumnRef
|
|
*
|
|
* @Return: none
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static void HandleSWCBColumnRef(StartWithTransformContext *context, Node *node)
|
|
{
|
|
List *result = NIL;
|
|
char* relname = NULL;
|
|
char* colname = NULL;
|
|
ParseState *pstate = context->pstate;
|
|
|
|
if (node == NULL) {
|
|
return;
|
|
}
|
|
|
|
Assert(nodeTag(node) == T_ColumnRef);
|
|
ColumnRef *column = (ColumnRef *)node;
|
|
bool prior = column->prior;
|
|
char *initname = strVal(linitial(column->fields));
|
|
|
|
if (pg_strcasecmp(initname, "tmp_reuslt") == 0) {
|
|
return;
|
|
}
|
|
|
|
/* Skip params in procedure */
|
|
if (preSkipPLSQLParams(pstate, column) && !prior) {
|
|
return;
|
|
}
|
|
|
|
ColumnRef *preColumn = (ColumnRef *)copyObject(column);
|
|
Node *preResult = transformColumnRef(pstate, preColumn);
|
|
if (preResult == NULL) {
|
|
return;
|
|
}
|
|
|
|
int len = list_length(column->fields);
|
|
switch (len) {
|
|
case 1: {
|
|
Node* field1 = (Node*)linitial(column->fields);
|
|
colname = strVal(field1);
|
|
|
|
/*
|
|
* In-place replace columnref with two fields, like convert id to test.id,
|
|
* which will be helpful for after SWCB rewrite. So here columnref will contain
|
|
* two fields after ExtractColumnRefStartInfo.
|
|
*/
|
|
result = ExtractColumnRefStartInfo(pstate, column, NULL, colname, preResult);
|
|
|
|
if (!prior) {
|
|
break;
|
|
}
|
|
|
|
if (list_length(column->fields) <= 1) {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_PARSER), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Invalid column reference in START WITH / CONNECT BY clause."),
|
|
errdetail("The column referred to may not exist."),
|
|
errcause("Usually this happens when the column referred to does not exist."),
|
|
erraction("Check and revise your query or contact Huawei engineers.")));
|
|
}
|
|
|
|
char *dummy = makeStartWithDummayColname(
|
|
strVal(linitial(column->fields)),
|
|
strVal(lsecond(column->fields)));
|
|
|
|
column->fields = list_make2(makeString("tmp_reuslt"), makeString(dummy));
|
|
|
|
/* record working table name */
|
|
context->connectby_prior_name = lappend(context->connectby_prior_name,
|
|
makeString(dummy));
|
|
|
|
break;
|
|
}
|
|
case 2: {
|
|
Node* field1 = (Node*)linitial(column->fields);
|
|
Node* field2 = (Node*)lsecond(column->fields);
|
|
relname = strVal(field1);
|
|
colname = strVal(field2);
|
|
|
|
result = ExtractColumnRefStartInfo(pstate, column, relname, colname, preResult);
|
|
|
|
if (prior) {
|
|
char *dummy = makeStartWithDummayColname(strVal(linitial(column->fields)),
|
|
strVal(lsecond(column->fields)));
|
|
column->fields = list_make2(makeString("tmp_reuslt"), makeString(dummy));
|
|
|
|
/* record working table name */
|
|
context->connectby_prior_name = lappend(context->connectby_prior_name,
|
|
makeString(dummy));
|
|
}
|
|
|
|
break;
|
|
}
|
|
default: {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Not support %d column lengths when HandleSWCBColumnRef", len)));
|
|
}
|
|
}
|
|
|
|
if (context->is_where) {
|
|
context->where_infos = list_concat_unique(context->where_infos, result);
|
|
} else {
|
|
context->relInfoList = list_concat_unique(context->relInfoList, result);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static bool is_cref_by_name(Node *expr, const char* col_name)
|
|
{
|
|
if (expr == NULL || !IsA(expr, ColumnRef)) {
|
|
return false;
|
|
}
|
|
ColumnRef *cref = (ColumnRef *) expr;
|
|
char* colname = NameListToString(cref->fields);
|
|
bool ret = (strcasecmp(colname, col_name) == 0) ? true : false;
|
|
pfree(colname);
|
|
return ret;
|
|
}
|
|
|
|
static Node* makeIntConst(int val, int location)
|
|
{
|
|
A_Const *n = makeNode(A_Const);
|
|
|
|
n->val.type = T_Integer;
|
|
n->val.val.ival = val;
|
|
n->location = location;
|
|
|
|
return (Node *)n;
|
|
}
|
|
|
|
static Node *replaceListFakeValue(List *lst)
|
|
{
|
|
List *newArgs = NIL;
|
|
ListCell *lc = NULL;
|
|
bool replaced = false;
|
|
|
|
/* replace level/rownum var attr with fake consts */
|
|
foreach (lc, lst) {
|
|
Node *n = (Node *)lfirst(lc);
|
|
Node *newNode = tryReplaceFakeValue(n);
|
|
if (newNode != n) {
|
|
replaced = true;
|
|
n = newNode;
|
|
}
|
|
newArgs = lappend(newArgs, n);
|
|
}
|
|
return replaced ? (Node *)newArgs : (Node *)lst;
|
|
}
|
|
|
|
static Node *tryReplaceFakeValue(Node *node)
|
|
{
|
|
if (node == NULL) {
|
|
return node;
|
|
}
|
|
|
|
if (IsA(node, Rownum)) {
|
|
node = makeIntConst(CONNECT_BY_ROWNUM_FAKEVALUE, -1);
|
|
} else if (is_cref_by_name(node, "level")) {
|
|
node = makeIntConst(CONNECT_BY_LEVEL_FAKEVALUE, -1);
|
|
} else if (IsA(node, List)) {
|
|
node = replaceListFakeValue((List *) node);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
static bool pseudo_level_rownum_walker(Node *node, Node *context_parent_node)
|
|
{
|
|
if (node == NULL) {
|
|
return false;
|
|
}
|
|
|
|
Node* newNode = tryReplaceFakeValue(node);
|
|
|
|
/* Case 1: Fake value replacement did not occur. Return immediately. */
|
|
if (newNode == node) {
|
|
return raw_expression_tree_walker(node, (bool (*)()) pseudo_level_rownum_walker, node);
|
|
}
|
|
|
|
/* Case 2: Fake value replacement occurred. Need to update parent node */
|
|
switch (nodeTag(context_parent_node)) {
|
|
case T_A_Expr: {
|
|
A_Expr *expr = (A_Expr *) context_parent_node;
|
|
if (expr->lexpr == node) {
|
|
expr->lexpr = newNode;
|
|
} else {
|
|
expr->rexpr = newNode;
|
|
}
|
|
break;
|
|
}
|
|
case T_TypeCast: {
|
|
TypeCast *tc = (TypeCast *) context_parent_node;
|
|
tc->arg = newNode;
|
|
break;
|
|
}
|
|
case T_NullTest: {
|
|
NullTest *nt = (NullTest *) context_parent_node;
|
|
nt->arg = (Expr*) newNode;
|
|
break;
|
|
}
|
|
case T_FuncCall: {
|
|
FuncCall *fc = (FuncCall *) context_parent_node;
|
|
fc->args = (List *)newNode;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return raw_expression_tree_walker(node, (bool (*)()) pseudo_level_rownum_walker, node);
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: check is there any rownum/level/prior in expressions, if given expression
|
|
* containing prior and rownum/level simultaneously, then error it out.
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static void rowNumOrLevelWalker(Node *expr, bool *hasRownumOrLevel, bool *hasPrior)
|
|
{
|
|
if (expr == NULL || (*hasRownumOrLevel && *hasPrior)) {
|
|
return;
|
|
}
|
|
|
|
switch (nodeTag(expr)) {
|
|
case T_ColumnRef: {
|
|
ColumnRef* colRef = (ColumnRef*)expr;
|
|
*hasRownumOrLevel = is_cref_by_name((Node *)colRef, "level") ||
|
|
*hasRownumOrLevel;
|
|
*hasPrior = colRef->prior || *hasPrior;
|
|
break;
|
|
}
|
|
|
|
case T_Rownum: {
|
|
*hasRownumOrLevel = true;
|
|
break;
|
|
}
|
|
|
|
case T_NullTest: {
|
|
NullTest* nullTestExpr = (NullTest*)expr;
|
|
Node* arg = (Node *)nullTestExpr->arg;
|
|
rowNumOrLevelWalker(arg, hasRownumOrLevel, hasPrior);
|
|
break;
|
|
}
|
|
|
|
case T_SubLink: {
|
|
SubLink *sublink = (SubLink *)expr;
|
|
Node *testexpr = sublink->testexpr;
|
|
rowNumOrLevelWalker(testexpr, hasRownumOrLevel, hasPrior);
|
|
break;
|
|
}
|
|
|
|
case T_CoalesceExpr: {
|
|
Node* node = (Node *)(((CoalesceExpr*)expr)->args);
|
|
rowNumOrLevelWalker(node, hasRownumOrLevel, hasPrior);
|
|
break;
|
|
}
|
|
|
|
case T_CollateClause: {
|
|
CollateClause *cc = (CollateClause*) expr;
|
|
rowNumOrLevelWalker(cc->arg, hasRownumOrLevel, hasPrior);
|
|
break;
|
|
}
|
|
|
|
case T_TypeCast: {
|
|
TypeCast* tc = (TypeCast*)expr;
|
|
rowNumOrLevelWalker(tc->arg, hasRownumOrLevel, hasPrior);
|
|
break;
|
|
}
|
|
|
|
case T_List: {
|
|
List* l = (List*)expr;
|
|
ListCell* lc = NULL;
|
|
foreach (lc, l) {
|
|
rowNumOrLevelWalker((Node*)lfirst(lc), hasRownumOrLevel, hasPrior);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case T_FuncCall: {
|
|
ListCell *args = NULL;
|
|
FuncCall* fn = (FuncCall *)expr;
|
|
|
|
foreach (args, fn->args) {
|
|
rowNumOrLevelWalker((Node*)lfirst(args), hasRownumOrLevel, hasPrior);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case T_A_Indirection: {
|
|
A_Indirection* idn = (A_Indirection *)expr;
|
|
rowNumOrLevelWalker(idn->arg, hasRownumOrLevel, hasPrior);
|
|
break;
|
|
}
|
|
|
|
case T_A_Expr: {
|
|
A_Expr *a_expr = (A_Expr *)expr;
|
|
Node *left_expr = a_expr->lexpr;
|
|
Node *right_expr = a_expr->rexpr;
|
|
|
|
switch (a_expr->kind) {
|
|
case AEXPR_OP:
|
|
case AEXPR_AND:
|
|
case AEXPR_OR:
|
|
case AEXPR_IN: {
|
|
rowNumOrLevelWalker(left_expr, hasRownumOrLevel, hasPrior);
|
|
rowNumOrLevelWalker(right_expr, hasRownumOrLevel, hasPrior);
|
|
break;
|
|
}
|
|
case AEXPR_NOT: {
|
|
rowNumOrLevelWalker(right_expr, hasRownumOrLevel, hasPrior);
|
|
break;
|
|
}
|
|
default: {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_PARSER), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Unsupported expression found in START WITH / CONNECT BY "
|
|
"clause during rowNumOrLevelWalker."),
|
|
errdetail("Unsupported expr type: %d.", (int)a_expr->kind),
|
|
errcause("Unsupported expression in START WITH / CONNECT BY clause."),
|
|
erraction("Check and revise your query or contact Huawei engineers.")));
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case T_A_Const: {
|
|
A_Const *n = (A_Const *)expr;
|
|
if (n->val.type == T_Integer) {
|
|
long val = n->val.val.ival;
|
|
if (val == CONNECT_BY_ROWNUM_FAKEVALUE || val == CONNECT_BY_LEVEL_FAKEVALUE) {
|
|
*hasRownumOrLevel = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case T_A_ArrayExpr:
|
|
case T_ParamRef: {
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_PARSER), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Unsupported expression found in START WITH / CONNECT BY "
|
|
"clause during rowNumOrLevelWalker."),
|
|
errdetail("Unsupported node type: %d.", (int)nodeTag(expr)),
|
|
errcause("Unsupported expression in START WITH / CONNECT BY clause."),
|
|
erraction("Check and revise your query or contact Huawei engineers.")));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: flatten connectByExpr into two lists
|
|
* @Param:
|
|
* - rownum_or_level_list: the exprs in this list contains "level" or "rownum"
|
|
* - normal_list: the exprs in this list = connectByExpr - rownum_or_level_list
|
|
* - expr
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static void flattenConnectByExpr(StartWithTransformContext *context, Node *expr)
|
|
{
|
|
if (expr == NULL) {
|
|
return;
|
|
}
|
|
|
|
bool hasRownumOrLevel = false;
|
|
bool hasPrior = false;
|
|
if (nodeTag(expr) == T_A_Expr) {
|
|
A_Expr *a_expr = (A_Expr *)expr;
|
|
if (a_expr->kind == AEXPR_AND) {
|
|
Node *lexpr = a_expr->lexpr;
|
|
Node *rexpr = a_expr->rexpr;
|
|
flattenConnectByExpr(context, lexpr);
|
|
flattenConnectByExpr(context, rexpr);
|
|
return;
|
|
}
|
|
}
|
|
|
|
rowNumOrLevelWalker(expr, &hasRownumOrLevel, &hasPrior);
|
|
if (hasRownumOrLevel && hasPrior) {
|
|
ereport(ERROR, (errmodule(MOD_PARSER), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("column specified by prior cannot concide with ROWNUM/LEVEL."),
|
|
errdetail("Unsupported node type: %d.", (int)nodeTag(expr)),
|
|
errcause("Unsupported expression in START WITH / CONNECT BY clause."),
|
|
erraction("Check and revise your query or contact Huawei engineers.")));
|
|
}
|
|
if (hasRownumOrLevel) {
|
|
context->rownum_or_level_list = lappend(context->rownum_or_level_list, expr);
|
|
} else {
|
|
context->normal_list = lappend(context->normal_list, expr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: build a internal expr from given list using "AND"
|
|
* @Return: the final expr
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static Node *buildExprUsingAnd(ListCell *tempCell)
|
|
{
|
|
if (tempCell == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
Node *nowExpr = (Node *)lfirst(tempCell);
|
|
Node *nextExpr = buildExprUsingAnd(lnext(tempCell));
|
|
|
|
if (nextExpr != NULL) {
|
|
return (Node *)makeA_Expr(AEXPR_AND, NULL, nowExpr, nextExpr, -1);
|
|
} else {
|
|
return nowExpr;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: SWCB's expr processing function, normally we do tow kinds of process
|
|
* (1). Recursively find a base column and do column name normalization for example:
|
|
* c1 -> t1.c1 aiming to provide more detail for later process
|
|
* (2). Find a const and identify connect-by-type ROWNUM/LEVEL
|
|
*
|
|
* @param
|
|
* - context: SWCB expression processing context, hold in-processing states and some
|
|
* processign result
|
|
* - expr: expression node
|
|
*
|
|
* @return: none, some of information is returned under *context
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static void StartWithWalker(StartWithTransformContext *context, Node *expr)
|
|
{
|
|
if (expr == NULL) {
|
|
return;
|
|
}
|
|
|
|
check_stack_depth();
|
|
|
|
switch (nodeTag(expr)) {
|
|
case T_ColumnRef: {
|
|
ColumnRef* colRef = (ColumnRef*)expr;
|
|
HandleSWCBColumnRef(context, (Node *)colRef);
|
|
break;
|
|
}
|
|
|
|
case T_NullTest: {
|
|
NullTest* nullTestExpr = (NullTest*)expr;
|
|
Node* arg = (Node *)nullTestExpr->arg;
|
|
if (arg != NULL && nodeTag(arg) == T_ColumnRef) {
|
|
HandleSWCBColumnRef(context, arg);
|
|
} else {
|
|
StartWithWalker(context, arg);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case T_SubLink: {
|
|
SubLink *sublink = (SubLink *)expr;
|
|
Node *testexpr = sublink->testexpr;
|
|
if (testexpr != NULL && nodeTag(testexpr) == T_ColumnRef) {
|
|
HandleSWCBColumnRef(context, testexpr);
|
|
} else {
|
|
StartWithWalker(context, testexpr);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case T_CoalesceExpr: {
|
|
Node* node = (Node *)(((CoalesceExpr*)expr)->args);
|
|
StartWithWalker(context, node);
|
|
break;
|
|
}
|
|
|
|
case T_CollateClause: {
|
|
CollateClause *cc = (CollateClause*) expr;
|
|
StartWithWalker(context, cc->arg);
|
|
break;
|
|
}
|
|
|
|
case T_TypeCast: {
|
|
TypeCast* tc = (TypeCast*)expr;
|
|
StartWithWalker(context, tc->arg);
|
|
break;
|
|
}
|
|
|
|
case T_List: {
|
|
List* l = (List*)expr;
|
|
ListCell* lc = NULL;
|
|
foreach (lc, l) {
|
|
StartWithWalker(context, (Node*)lfirst(lc));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case T_A_ArrayExpr: {
|
|
break;
|
|
}
|
|
|
|
case T_FuncCall: {
|
|
ListCell *args = NULL;
|
|
FuncCall* fn = (FuncCall *)expr;
|
|
|
|
foreach (args, fn->args) {
|
|
StartWithWalker(context, (Node*)lfirst(args));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case T_A_Indirection: {
|
|
A_Indirection* idn = (A_Indirection *)expr;
|
|
StartWithWalker(context, idn->arg);
|
|
break;
|
|
}
|
|
|
|
case T_A_Expr: {
|
|
A_Expr *a_expr = (A_Expr *)expr;
|
|
|
|
switch (a_expr->kind) {
|
|
case AEXPR_OP: {
|
|
Node *left_expr = a_expr->lexpr;
|
|
Node *right_expr = a_expr->rexpr;
|
|
|
|
if (left_expr != NULL && (is_cref_by_name(left_expr, "level") ||
|
|
IsA(left_expr, Rownum))) {
|
|
context->connect_by_type = CONNECT_BY_MIXED_LEVEL;
|
|
A_Const *n = makeNode(A_Const);
|
|
n->val.type = T_Integer;
|
|
n->val.val.ival = IsA(left_expr, Rownum) ?
|
|
CONNECT_BY_ROWNUM_FAKEVALUE : CONNECT_BY_LEVEL_FAKEVALUE;
|
|
n->location = -1;
|
|
a_expr->lexpr = (Node*) n;
|
|
break;
|
|
}
|
|
|
|
/* convert right/left expr surround AEXPR_OP */
|
|
if (left_expr != NULL && nodeTag(left_expr) == T_ColumnRef) {
|
|
HandleSWCBColumnRef(context, left_expr);
|
|
} else {
|
|
StartWithWalker(context, left_expr);
|
|
}
|
|
|
|
if (right_expr != NULL && nodeTag(right_expr) == T_ColumnRef) {
|
|
HandleSWCBColumnRef(context, right_expr);
|
|
} else {
|
|
StartWithWalker(context, right_expr);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case AEXPR_AND:
|
|
case AEXPR_OR: {
|
|
/* deep copy rexpr before walking into lexpr:
|
|
this is to prevent lexpr transformation procedures from tweaking rexpr
|
|
in cases when lexpr and rexpr share common nodes,
|
|
e.g. in 'between ... and ...' clauses duplicated columnrefs are found
|
|
in both lexpr and rexpr */
|
|
a_expr->rexpr = (Node*) copyObject(a_expr->rexpr);
|
|
StartWithWalker(context, a_expr->lexpr);
|
|
StartWithWalker(context, a_expr->rexpr);
|
|
break;
|
|
}
|
|
case AEXPR_IN: {
|
|
StartWithWalker(context, a_expr->lexpr);
|
|
StartWithWalker(context, a_expr->rexpr);
|
|
break;
|
|
}
|
|
case AEXPR_NOT: {
|
|
StartWithWalker(context, a_expr->rexpr);
|
|
break;
|
|
}
|
|
default: {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_PARSER), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Unsupported expression found in START WITH / CONNECT BY clause."),
|
|
errdetail("Unsupported expr type: %d.", (int)a_expr->kind),
|
|
errcause("Unsupported expression in START WITH / CONNECT BY clause."),
|
|
erraction("Check and revise your query or contact Huawei engineers.")));
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case T_A_Const: {
|
|
A_Const *n = (A_Const *)expr;
|
|
|
|
int32 val = GetStartWithFakeConstValue(n);
|
|
if (val == CONNECT_BY_LEVEL_FAKEVALUE) {
|
|
context->connect_by_type = CONNECT_BY_LEVEL;
|
|
} else if (val == CONNECT_BY_ROWNUM_FAKEVALUE) {
|
|
context->connect_by_type = CONNECT_BY_ROWNUM;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case T_ParamRef: {
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_PARSER), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Unsupported expression found in START WITH / CONNECT BY clause."),
|
|
errdetail("Unsupported node type: %d.", (int)nodeTag(expr)),
|
|
errcause("Unsupported expression in START WITH / CONNECT BY clause."),
|
|
erraction("Check and revise your query or contact Huawei engineers.")));
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static bool isForbiddenClausesPresent(SelectStmt *stmt)
|
|
{
|
|
ListCell* cell = NULL;
|
|
foreach (cell, stmt->fromClause) {
|
|
Node* n = (Node*)lfirst(cell);
|
|
if (IsA(n, RangeTimeCapsule) || IsA(n, RangeTableSample)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void checkConnectByExprValidity(Node* connectByExpr)
|
|
{
|
|
Node* node = tryReplaceFakeValue(connectByExpr);
|
|
if (node != connectByExpr) {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_PARSER), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Unsupported expression found in CONNECT BY clause."),
|
|
errdetail("Pseudo column expects an operator"),
|
|
errcause("Unsupported expression in CONNECT BY clause."),
|
|
erraction("Check and revise your query or contact Huawei engineers.")));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: transfrom startwith/connectby clasue
|
|
* 1. For startwith we need to replace column name, return info lists
|
|
* 2. For connect by we should replace column name(include table name and work table
|
|
* tmp_result name) and also record prior name then return ino lists.
|
|
*
|
|
* @param:
|
|
* - context: global SWCB conversion context
|
|
* - stmt: current SWCB's belonging SelectStmt
|
|
*
|
|
* @Return: to be input
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static void transformStartWithClause(StartWithTransformContext *context, SelectStmt *stmt)
|
|
{
|
|
if (stmt == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (isForbiddenClausesPresent(stmt)) {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_PARSER), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Cannot use start with / connect by with TABLESAMPLE / TIMECAPSULE."),
|
|
errdetail("Unsupported target type"),
|
|
errcause("Unsupported target type in START WITH / CONNECT BY clause."),
|
|
erraction("Check and revise your query or contact Huawei engineers.")));
|
|
}
|
|
|
|
StartWithClause *clause = (StartWithClause *)stmt->startWithClause;
|
|
Node *startWithExpr = clause->startWithExpr;
|
|
Node *connectByExpr = clause->connectByExpr;
|
|
context->connectByExpr = connectByExpr;
|
|
context->startWithExpr = startWithExpr;
|
|
context->connect_by_type = CONNECT_BY_PRIOR;
|
|
context->nocycle = clause->nocycle;
|
|
context->siblingsOrderBy = (List *)clause->siblingsOrderBy;
|
|
|
|
/* when ROWNUM or LEVEL appear in Expressions other than A_Expr, do an extra replacement */
|
|
raw_expression_tree_walker((Node*)context->connectByExpr,
|
|
(bool (*)())pseudo_level_rownum_walker, (Node*)context->connectByExpr);
|
|
checkConnectByExprValidity((Node*)connectByExpr);
|
|
context->relInfoList = context->pstate->p_start_info;
|
|
|
|
flattenConnectByExpr(context, connectByExpr);
|
|
context->connectByLevelExpr = buildExprUsingAnd(list_head(context->rownum_or_level_list));
|
|
context->connectByExpr = buildExprUsingAnd(list_head(context->normal_list));
|
|
|
|
/* transform start with ... connect by's expr */
|
|
StartWithWalker(context, startWithExpr);
|
|
StartWithWalker(context, context->connectByLevelExpr);
|
|
StartWithWalker(context, context->connectByExpr);
|
|
|
|
/*
|
|
* now handle where quals which might need to be pushed down.
|
|
* 1. this is necessary only for implicitly joined tables
|
|
* such as ... FROM t1,t2 WHERE t1.xx = t2.yy ...
|
|
* 2. note that only join quals should be pushed down while
|
|
* non-join quals such as (t1.xx < n) should be kept in the outer loop
|
|
* of the CTE scan, otherwise the end-result will be different from
|
|
* those produced by the standard swcb syntax.
|
|
* (the issue here is that implicitly joined tables are difficult to handle
|
|
* in our implementation of start with .. connect by .. syntax,
|
|
* as we don't have a clear cut of join quals from the non-join quals at this stage.
|
|
* users should be encouraged to use explicity joined tables whenever
|
|
* possible before a clear-cut solution is implemented.)
|
|
*/
|
|
int lens = list_length(context->pstate->p_start_info);
|
|
if (lens != 1) {
|
|
Node *whereClause = (Node *)copyObject(stmt->whereClause);
|
|
context->whereClause = whereClause;
|
|
}
|
|
|
|
if (startWithExpr != NULL && context->connectby_prior_name == NULL) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("START WITH CONNECT BY clauses must have at least one prior key.")));
|
|
}
|
|
Assert(context->relInfoList);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: Add SWCB's pseudo column to basereal converted CTE object, we do so is want to
|
|
* consider level/rownum/iscycle/isleaf is SWCB's return column, so that follow
|
|
* up steps could keep their origin
|
|
*
|
|
* @param:
|
|
* - cte: cte of SWCB converted
|
|
* - rte: rte of SWCB converted that pointing cte in current transform level
|
|
* - rte_index: rte's rt_index, a.w.k. varno, rt_fetch()
|
|
*
|
|
* @Return: none
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
void AddStartWithCTEPseudoReturnColumns(CommonTableExpr *cte,
|
|
RangeTblEntry *rte, Index rte_index)
|
|
{
|
|
Assert (rte->rtekind == RTE_CTE);
|
|
|
|
/* Add and fix target entry for pseudo columns */
|
|
/* step 1. fix ctequery's output */
|
|
Query *ctequery = (Query *)cte->ctequery;
|
|
|
|
/* Add pseudo output column into rte->eref->colnames */
|
|
Node *expr = NULL;
|
|
TargetEntry *tle = NULL;
|
|
StartWithCTEPseudoReturnAtts *att = NULL;
|
|
bool pseudoExist = false;
|
|
ListCell *lc = NULL;
|
|
|
|
foreach(lc, ctequery->targetList) {
|
|
TargetEntry *entry = (TargetEntry *)lfirst(lc);
|
|
|
|
if (IsPseudoReturnTargetEntry(entry)) {
|
|
pseudoExist = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pseudoExist) {
|
|
return;
|
|
}
|
|
|
|
for (uint i = 0; i < STARTWITH_PSEUDO_RETURN_ATTNUMS; i++) {
|
|
/* Create a pseudo return column in "TargetEntry" format */
|
|
att = &g_StartWithCTEPseudoReturnAtts[i];
|
|
|
|
/* make var for pseudo return column */
|
|
expr = (Node *)makeVar(rte_index,
|
|
list_length(ctequery->targetList) + 1,
|
|
att->coltype,
|
|
att->coltypmod,
|
|
att->colcollation, 0);
|
|
|
|
/* make pseudo return column's TLE */
|
|
tle = makeTargetEntry((Expr *)expr, list_length(ctequery->targetList) + 1, att->colname, false);
|
|
|
|
/* Add the pseudo return column to CTE's target list */
|
|
ctequery->targetList = lappend(ctequery->targetList, tle);
|
|
|
|
/* Fix cte's relevant output data structure */
|
|
rte->eref->colnames = lappend(rte->eref->colnames, makeString(att->colname));
|
|
rte->ctecoltypes = lappend_oid(rte->ctecoltypes, att->coltype);
|
|
rte->ctecoltypmods = lappend_int(rte->ctecoltypmods, att->coltypmod);
|
|
rte->ctecolcollations = lappend_oid(rte->ctecolcollations, att->colcollation);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: A helper function invoked in CreateStartWithCteInner/OuterBranch() where we
|
|
* add SWCB's information ot inner/outer subselect
|
|
*
|
|
* @param:
|
|
* - (1). pstate: parse states of current level
|
|
* - (2). stmt: WithClause's branch SelectStmt
|
|
* - (3). relInfoList: current SWCB's constitute target rels
|
|
*
|
|
* @Return: none
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static void AddWithClauseToBranch(ParseState *pstate, SelectStmt *stmt, List *relInfoList)
|
|
{
|
|
ListCell *lc1 = NULL;
|
|
ListCell *lc2 = NULL;
|
|
List *ctes = NIL;
|
|
|
|
if (pstate->origin_with == NULL) {
|
|
return;
|
|
}
|
|
|
|
WithClause *clause = (WithClause *)copyObject(pstate->origin_with);
|
|
|
|
foreach(lc1, relInfoList) {
|
|
StartWithTargetRelInfo *info = (StartWithTargetRelInfo *)lfirst(lc1);
|
|
CommonTableExpr *removed = NULL;
|
|
|
|
if (info->rtekind != RTE_CTE) {
|
|
continue;
|
|
}
|
|
|
|
foreach(lc2, clause->ctes) {
|
|
CommonTableExpr *cte = (CommonTableExpr *)lfirst(lc2);
|
|
|
|
if (pg_strcasecmp(cte->ctename, info->relname) == 0) {
|
|
ctes = lappend(ctes, cte);
|
|
}
|
|
}
|
|
|
|
foreach(lc2, pstate->p_ctenamespace) {
|
|
CommonTableExpr* cte = (CommonTableExpr*)lfirst(lc2);
|
|
|
|
if (pg_strcasecmp(cte->ctename, info->relname) == 0) {
|
|
removed = cte;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (removed != NULL) {
|
|
pstate->p_ctenamespace = list_delete(pstate->p_ctenamespace, removed);
|
|
}
|
|
}
|
|
|
|
if (ctes == NULL) {
|
|
return;
|
|
}
|
|
|
|
clause->ctes = ctes;
|
|
stmt->withClause = clause;
|
|
|
|
return;
|
|
}
|
|
|
|
static bool count_columnref_walker(Node *node, int *columnref_count)
|
|
{
|
|
if (node == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (!IsA(node, ColumnRef)) {
|
|
return raw_expression_tree_walker(node, (bool (*)()) count_columnref_walker, (void*)columnref_count);
|
|
}
|
|
|
|
*columnref_count = *columnref_count + 1;
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool walker_to_exclude_non_join_quals(Node *node, Node *context_node)
|
|
{
|
|
if (node == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (!IsA(node, A_Expr)) {
|
|
return raw_expression_tree_walker(node, (bool (*)()) walker_to_exclude_non_join_quals, (void*)NULL);
|
|
}
|
|
|
|
A_Expr* expr = (A_Expr*) node;
|
|
if (expr->kind != AEXPR_OP) {
|
|
return raw_expression_tree_walker(node, (bool (*)()) walker_to_exclude_non_join_quals, (void*)NULL);
|
|
}
|
|
|
|
/*
|
|
* this is to achieve consistent result sets with those produced by the original
|
|
* start with .. connect by syntax, which does not push filter quals down to connect quals.
|
|
* if no more than one column item appears inside an AEXPR_OP, we guess that it is
|
|
* not a join qual so should not be filtered in sw op, and force it to be true.
|
|
* this rule is not always correct but should work fine most of the time.
|
|
* could be improved later on, e.g. find better ways to extract non-join quals
|
|
* from the where clause.
|
|
*/
|
|
int columnref_count = 0;
|
|
(void) raw_expression_tree_walker(node, (bool (*)()) count_columnref_walker, (void*)&columnref_count);
|
|
|
|
if (columnref_count <= 1) {
|
|
expr->lexpr = makeBoolAConst(true, -1);
|
|
expr->rexpr = makeBoolAConst(true, -1);
|
|
expr->kind = AEXPR_OR;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: Create SWCB's conversion CTE's inner branch, normally we add ConnectByExpr to
|
|
* inner branch's joincond
|
|
*
|
|
* @param:
|
|
* - (1). pstate, current SWCB belonging query level's parseState
|
|
* - (2). context current SWCB's trnasform context
|
|
* - (3). relInfoList, SWCB's relating base RangeVar
|
|
* - (4). connectByExpr: expr of connectby clause will be put as RQ's join cond
|
|
* - (5). whereClause: expr of where clause
|
|
*
|
|
* @Return: to be input
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static SelectStmt *CreateStartWithCTEInnerBranch(ParseState* pstate,
|
|
StartWithTransformContext *context,
|
|
List *relInfoList,
|
|
Node *connectByExpr,
|
|
Node *whereClause)
|
|
{
|
|
Node *origin_table = NULL;
|
|
SelectStmt *result = makeNode(SelectStmt);
|
|
JoinExpr *join = makeNode(JoinExpr);
|
|
RangeVar *work_table = makeRangeVar(NULL, "tmp_reuslt", -1);
|
|
|
|
AddWithClauseToBranch(pstate, result, relInfoList);
|
|
|
|
if (list_length(relInfoList) == 1) {
|
|
StartWithTargetRelInfo *info = (StartWithTargetRelInfo *)linitial(relInfoList);
|
|
origin_table = (Node *)copyObject(info->tblstmt);
|
|
} else {
|
|
ListCell *lc = NULL;
|
|
|
|
List *fromClause = (List *)copyObject(context->fromClause);
|
|
foreach(lc, fromClause) {
|
|
Node *node = (Node *)lfirst(lc);
|
|
|
|
if (origin_table == NULL) {
|
|
origin_table = node;
|
|
continue;
|
|
}
|
|
|
|
/* make new JoinExpr */
|
|
Node *qual = makeBoolAConst(true, -1);
|
|
JoinExpr *joiniter = makeNode(JoinExpr);
|
|
|
|
joiniter->larg = origin_table;
|
|
joiniter->rarg = node;
|
|
joiniter->quals = qual;
|
|
|
|
origin_table = (Node *)joiniter;
|
|
}
|
|
|
|
if (whereClause != NULL) {
|
|
JoinExpr *final_join = (JoinExpr *)origin_table;
|
|
/* pushdown requires deep copying of the quals */
|
|
Node *whereCopy = (Node *)copyObject(whereClause);
|
|
/* only join quals can be pushed down */
|
|
raw_expression_tree_walker((Node *)whereCopy,
|
|
(bool (*)())walker_to_exclude_non_join_quals, (void*)NULL);
|
|
|
|
if (final_join->quals == NULL) {
|
|
final_join->quals = whereCopy;
|
|
} else {
|
|
final_join->quals =
|
|
(Node *)makeA_Expr(AEXPR_AND, NULL, whereCopy, final_join->quals, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* process regular/level */
|
|
switch (context->connect_by_type) {
|
|
case CONNECT_BY_PRIOR:
|
|
case CONNECT_BY_ROWNUM:
|
|
case CONNECT_BY_LEVEL:
|
|
case CONNECT_BY_MIXED_LEVEL: {
|
|
join->jointype = JOIN_INNER;
|
|
join->isNatural = FALSE;
|
|
join->larg = (Node *)work_table;
|
|
join->rarg = origin_table;
|
|
join->usingClause = NIL;
|
|
join->quals = (Node *)copyObject(connectByExpr);
|
|
result->targetList = expandAllTargetList(relInfoList);
|
|
result->fromClause = list_make1(join);
|
|
|
|
break;
|
|
}
|
|
default: {
|
|
elog(ERROR, "unrecognized connect by type %d", context->connect_by_type);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: Create SWCB's conversion CTE's outer branch, normally we add StartWithExpr to
|
|
* outer branch's filtering cond
|
|
*
|
|
* @param:
|
|
* - (1). pstate, current SWCB belonging query level's parseState
|
|
* - (2). context current SWCB's trnasform context
|
|
* - (3). relInfoList, SWCB's relating base RangeVar
|
|
* - (4). startWithExpr: expr of start with clause will be put as RQ's init cond
|
|
* - (5). whereClause: expr of where clause
|
|
*
|
|
* @Return: to be input
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static SelectStmt *CreateStartWithCTEOuterBranch(ParseState *pstate,
|
|
StartWithTransformContext *context,
|
|
List *relInfoList,
|
|
Node *startWithExpr,
|
|
Node *whereClause)
|
|
{
|
|
ListCell *lc = NULL;
|
|
List *targetlist = NIL;
|
|
List *tblist = NIL;
|
|
Node *quals = NULL;
|
|
|
|
SelectStmt *result = makeNode(SelectStmt);
|
|
|
|
AddWithClauseToBranch(pstate, result, relInfoList);
|
|
|
|
/* form targetlist */
|
|
targetlist = expandAllTargetList(relInfoList);
|
|
|
|
if (context->fromClause != NULL) {
|
|
tblist = (List *)copyObject(context->fromClause);
|
|
} else {
|
|
foreach(lc, relInfoList) {
|
|
StartWithTargetRelInfo *info = (StartWithTargetRelInfo *)lfirst(lc);
|
|
tblist = lappend(tblist, copyObject(info->tblstmt));
|
|
}
|
|
}
|
|
|
|
/* push whereClause down to init part, taking care to avoid NULL in expr. */
|
|
quals = (Node *)startWithExpr;
|
|
Node* whereClauseCopy = (Node *)copyObject(whereClause);
|
|
if (whereClause != NULL) {
|
|
/* only join quals can be pushed down */
|
|
raw_expression_tree_walker((Node*)whereClauseCopy,
|
|
(bool (*)())walker_to_exclude_non_join_quals, (void*)NULL);
|
|
}
|
|
|
|
if (quals == NULL) {
|
|
quals = whereClauseCopy;
|
|
} else if (whereClause != NULL) {
|
|
quals = (Node *)makeA_Expr(AEXPR_AND, NULL, whereClauseCopy,
|
|
(Node*)startWithExpr, -1);
|
|
}
|
|
|
|
result->fromClause = tblist;
|
|
result->whereClause = quals;
|
|
result->targetList = targetlist;
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: transform recursive part and non-recursive part to recursive union
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static void CreateStartWithCTE(ParseState *pstate, Query *qry,
|
|
SelectStmt *initpart, SelectStmt *workpart, StartWithTransformContext *context)
|
|
{
|
|
SelectStmt *setop = makeNode(SelectStmt);
|
|
setop->op = SETOP_UNION;
|
|
setop->all = true;
|
|
setop->larg = initpart;
|
|
setop->rarg = workpart;
|
|
|
|
CommonTableExpr *common_expr = makeNode(CommonTableExpr);
|
|
common_expr->swoptions = makeNode(StartWithOptions);
|
|
common_expr->ctename = "tmp_reuslt";
|
|
common_expr->ctequery = (Node *)setop;
|
|
common_expr->swoptions->siblings_orderby_clause = context->siblingsOrderBy;
|
|
common_expr->swoptions->connect_by_type = context->connect_by_type;
|
|
|
|
/*
|
|
* CTE is not available at this stage yet so we disable start-with style columnref
|
|
* transformation with maneuvers on p_hasStartWith to avoid column not found errors
|
|
*/
|
|
pstate->p_hasStartWith = false;
|
|
common_expr->swoptions->connect_by_level_quals =
|
|
transformWhereClause(pstate, context->connectByLevelExpr, "LEVEL/ROWNUM quals");
|
|
|
|
/* need to fix the collations in the quals as well */
|
|
assign_expr_collations(pstate, common_expr->swoptions->connect_by_level_quals);
|
|
|
|
pstate->p_hasStartWith = true;
|
|
|
|
common_expr->swoptions->connect_by_other_quals = context->connectByOtherExpr;
|
|
common_expr->swoptions->nocycle= context->nocycle;
|
|
|
|
WithClause *with_clause = makeNode(WithClause);
|
|
with_clause->ctes = NULL;
|
|
with_clause->recursive = true;
|
|
|
|
with_clause->ctes = lappend(with_clause->ctes, common_expr);
|
|
|
|
/* used for multiple level startwith...connnectby */
|
|
pstate->p_sw_selectstmt->withClause = (WithClause *)copyObject(with_clause);
|
|
pstate->p_sw_selectstmt->startWithClause = NULL;
|
|
|
|
/* backup p_ctenamespace and recovery */
|
|
List *p_ctenamespace = pstate->p_ctenamespace;
|
|
pstate->p_ctenamespace = NULL;
|
|
|
|
qry->hasRecursive = with_clause->recursive;
|
|
qry->cteList = transformWithClause(pstate, with_clause);
|
|
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
|
|
|
|
pstate->p_ctenamespace = list_concat_unique(pstate->p_ctenamespace, p_ctenamespace);
|
|
list_free_ext(p_ctenamespace);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: detail to be added
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static RangeTblEntry *transformStartWithCTE(ParseState* pstate, List *prior_names)
|
|
{
|
|
ListCell *lc = NULL;
|
|
Index ctelevelsup = 0;
|
|
RangeTblEntry* rte = makeNode(RangeTblEntry);
|
|
|
|
CommonTableExpr *cte = scanNameSpaceForCTE(pstate, "tmp_reuslt", &ctelevelsup);
|
|
|
|
Assert(cte != NULL);
|
|
|
|
rte->rtekind = RTE_CTE;
|
|
rte->ctename = cte->ctename;
|
|
rte->relname = cte->ctename;
|
|
rte->ctelevelsup = ctelevelsup;
|
|
rte->inh = false;
|
|
rte->inFromCl = true;
|
|
|
|
rte->self_reference = !IsA(cte->ctequery, Query);
|
|
if (!rte->self_reference) {
|
|
cte->cterefcount++;
|
|
}
|
|
|
|
rte->ctecoltypes = cte->ctecoltypes;
|
|
rte->ctecoltypmods = cte->ctecoltypmods;
|
|
rte->ctecolcollations = cte->ctecolcollations;
|
|
|
|
Alias* eref = makeAlias(cte->ctename, NIL);
|
|
Alias* alias = makeAlias(cte->ctename, NIL);
|
|
|
|
foreach(lc, cte->ctecolnames) {
|
|
eref->colnames = lappend(eref->colnames, lfirst(lc));
|
|
}
|
|
|
|
rte->eref = eref;
|
|
rte->alias = alias;
|
|
|
|
int index = 0;
|
|
List *key_index = NIL;
|
|
foreach(lc, cte->ctecolnames) {
|
|
Value *colname = (Value *)lfirst(lc);
|
|
|
|
if (list_member(prior_names, colname)) {
|
|
key_index = lappend_int(key_index, index);
|
|
}
|
|
|
|
index++;
|
|
}
|
|
cte->swoptions->prior_key_index = key_index;
|
|
|
|
/*
|
|
* We also should fill some rewrite informations in origin selectStmt,
|
|
* So it can be took for upper level for next step rewrite.
|
|
*/
|
|
foreach(lc, pstate->p_sw_selectstmt->withClause->ctes) {
|
|
CommonTableExpr* common = (CommonTableExpr*)lfirst(lc);
|
|
|
|
if (strcmp(common->ctename, "tmp_reuslt") == 0) {
|
|
common->swoptions->prior_key_index = key_index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* set rewrited RTE to aborted */
|
|
foreach (lc, pstate->p_rtable) {
|
|
RangeTblEntry *rte = (RangeTblEntry *)lfirst(lc);
|
|
rte->swAborted = true;
|
|
}
|
|
|
|
pstate->p_rtable = lappend(pstate->p_rtable, rte);
|
|
|
|
AddStartWithCTEPseudoReturnColumns(cte, rte, list_length(pstate->p_rtable));
|
|
|
|
return rte;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: detail to be added
|
|
*
|
|
* @param: detail to be added
|
|
*
|
|
* @Return: detail to be added
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static void transformFromList(ParseState* pstate, Query* qry,
|
|
StartWithTransformContext *context, Node *n)
|
|
{
|
|
ListCell *lc = NULL;
|
|
A_Expr *startWithExpr = (A_Expr *)context->startWithExpr;
|
|
A_Expr *connectByExpr = (A_Expr *)context->connectByExpr;
|
|
List *relInfoList = context->relInfoList;
|
|
Node *whereClause = context->whereClause;
|
|
|
|
/* make union-all branch for none-recursive part */
|
|
SelectStmt *outerBranch = CreateStartWithCTEOuterBranch(pstate, context,
|
|
relInfoList, (Node *)startWithExpr, whereClause);
|
|
|
|
/* make joinExpr for recursive part */
|
|
SelectStmt *innerBranch = CreateStartWithCTEInnerBranch(pstate, context,
|
|
relInfoList, (Node *)connectByExpr, whereClause);
|
|
|
|
CreateStartWithCTE(pstate, qry, outerBranch, innerBranch, context);
|
|
|
|
/*
|
|
* FINAL NEED FIX RTE POSITION, replace one of start_with_rtes with cte
|
|
**/
|
|
foreach(lc, relInfoList) {
|
|
StartWithTargetRelInfo *info = (StartWithTargetRelInfo *)lfirst(lc);
|
|
RangeTblEntry* rte = info->rte;
|
|
RangeTblRef *rtr = info->rtr;
|
|
|
|
pstate->p_joinlist = list_delete(pstate->p_joinlist, rtr);
|
|
|
|
/* delete from p_varnamespace */
|
|
ListCell *lcc = NULL;
|
|
ParseNamespaceItem *nsitem = NULL;
|
|
foreach(lcc, pstate->p_varnamespace) {
|
|
ParseNamespaceItem *sitem = (ParseNamespaceItem *)lfirst(lcc);
|
|
|
|
if (sitem->p_rte == rte) {
|
|
nsitem = sitem;
|
|
break;
|
|
}
|
|
}
|
|
pstate->p_varnamespace = list_delete(pstate->p_varnamespace, nsitem);
|
|
}
|
|
|
|
RangeTblEntry *replace = transformStartWithCTE(pstate, context->connectby_prior_name);
|
|
int rtindex = list_length(pstate->p_rtable);
|
|
RangeTblRef *rtr = makeNode(RangeTblRef);
|
|
rtr->rtindex = rtindex;
|
|
replace->swConverted = true;
|
|
replace->origin_index = list_make1_int(rtr->rtindex);
|
|
|
|
pstate->p_joinlist = list_make1(rtr);
|
|
pstate->p_varnamespace = list_make1(makeNamespaceItem(replace, false, true));
|
|
pstate->p_relnamespace = lappend(pstate->p_relnamespace,
|
|
makeNamespaceItem(replace, false, true));
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: detail to be added
|
|
*
|
|
* @param: detail to be added
|
|
*
|
|
* @Return: detail to be added
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static void transformSingleRTE(ParseState* pstate, Query* qry,
|
|
StartWithTransformContext *context, Node *start_clause)
|
|
{
|
|
ListCell *lc = NULL;
|
|
|
|
A_Expr *connectByExpr = (A_Expr *)context->connectByExpr;
|
|
A_Expr *startWithExpr = (A_Expr *)context->startWithExpr;
|
|
List *startWithRelInfoList = context->relInfoList;
|
|
|
|
StartWithTargetRelInfo *info = (StartWithTargetRelInfo *)linitial(context->relInfoList);
|
|
|
|
/* first non-recursive part */
|
|
SelectStmt *outerBranch = CreateStartWithCTEOuterBranch(pstate, context,
|
|
startWithRelInfoList, (Node *)startWithExpr, NULL);
|
|
|
|
/* second recursive part */
|
|
SelectStmt *innerBranch = CreateStartWithCTEInnerBranch(pstate, context,
|
|
startWithRelInfoList, (Node *)connectByExpr, NULL);
|
|
|
|
/* final finish setop and commonTableExpr */
|
|
CreateStartWithCTE(pstate, qry, outerBranch, innerBranch, context);
|
|
|
|
/* found origin rte then replace it by new cte */
|
|
RangeTblEntry* rte = info->rte;
|
|
RangeTblRef *rtr = info->rtr;
|
|
pstate->p_joinlist = list_delete(pstate->p_joinlist, rtr);
|
|
|
|
//delete from p_varnamespace
|
|
ParseNamespaceItem *nsitem = NULL;
|
|
foreach(lc, pstate->p_varnamespace) {
|
|
ParseNamespaceItem *sitem = (ParseNamespaceItem *)lfirst(lc);
|
|
|
|
if (sitem->p_rte == rte) {
|
|
nsitem = sitem;
|
|
break;
|
|
}
|
|
}
|
|
pstate->p_varnamespace = list_delete(pstate->p_varnamespace, nsitem);
|
|
|
|
RangeTblEntry *replace = transformStartWithCTE(pstate, context->connectby_prior_name);
|
|
replace->swConverted = true;
|
|
replace->origin_index = list_make1_int(info->rtr->rtindex);
|
|
int rtindex = list_length(pstate->p_rtable);
|
|
rtr = makeNode(RangeTblRef);
|
|
rtr->rtindex = rtindex;
|
|
|
|
pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
|
|
pstate->p_varnamespace = lappend(pstate->p_varnamespace,
|
|
makeNamespaceItem(replace, false, true));
|
|
pstate->p_relnamespace = lappend(pstate->p_relnamespace,
|
|
makeNamespaceItem(replace, false, true));
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------------------------------------------------------------
|
|
* @Brief: detail to be added
|
|
*
|
|
* @param: detail to be added
|
|
*
|
|
* @Return: detail to be added
|
|
* --------------------------------------------------------------------------------------
|
|
*/
|
|
static List *expandAllTargetList(List *targetRelInfoList)
|
|
{
|
|
ListCell *lc1 = NULL;
|
|
ListCell *lc2 = NULL;
|
|
List *targetlist = NIL;
|
|
|
|
foreach(lc1, targetRelInfoList) {
|
|
StartWithTargetRelInfo *info = (StartWithTargetRelInfo *)lfirst(lc1);
|
|
RangeTblEntry *rte = info->rte;
|
|
|
|
/* transForm targetlist with table alias name */
|
|
char *relname = NULL;
|
|
Alias *alias = rte->eref;
|
|
|
|
if (info->rtekind == RTE_SUBQUERY) {
|
|
relname = info->aliasname;
|
|
} else if (info->rtekind == RTE_RELATION) {
|
|
relname = info->aliasname ? info->aliasname : info->relname;
|
|
} else if (info->rtekind == RTE_CTE) {
|
|
relname = info->aliasname ? info->aliasname : info->relname;
|
|
}
|
|
|
|
foreach(lc2, alias->colnames) {
|
|
Value *val = (Value *)lfirst(lc2);
|
|
|
|
ColumnRef *column = makeNode(ColumnRef);
|
|
char *colname = strVal(val);
|
|
|
|
/* skip already dropped column where attached as tableAlias."" */
|
|
if (strlen(colname) == 0) {
|
|
continue;
|
|
}
|
|
|
|
ResTarget *target = makeNode(ResTarget);
|
|
|
|
column->fields = list_make2(makeString(relname), val);
|
|
column->location = -1;
|
|
target->val = (Node *)column;
|
|
target->location = -1;
|
|
|
|
target->name = makeStartWithDummayColname(relname, colname);
|
|
targetlist = lappend(targetlist, target);
|
|
}
|
|
}
|
|
|
|
return targetlist;
|
|
}
|