*
* planrewrite.cpp
* The query cost-based rewrite
*
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/gausskernel/optimizer/plan/planrewrite.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "access/sysattr.h"
#include "catalog/indexing.h"
#include "catalog/pg_class.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_namespace.h"
#include "catalog/pgxc_group.h"
#include "foreign/fdwapi.h"
#include "foreign/foreign.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/pg_list.h"
#include "nodes/plannodes.h"
#include "nodes/primnodes.h"
#include "optimizer/planner.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_relation.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/snapmgr.h"
#ifdef PGXC
#include "optimizer/dataskew.h"
#include "optimizer/streamplan.h"
#include "optimizer/pgxcplan.h"
#include "pgxc/pgxc.h"
#endif
#include "catalog/pg_operator.h"
#ifdef STREAMPLAN
#include "optimizer/streamplan.h"
#include "catalog/pg_aggregate.h"
#include "utils/syscache.h"
#include "parser/parse_oper.h"
#include "catalog/pg_proc.h"
#endif
const int SUBQUERY_VARNO = 1;
const AttrNumber SUBQUERY_ATTNO = 1;
const int VALUES_SUBLINK_VARNO = 1;
const AttrNumber VALUES_SUBLINK_VARATTNO = 1;
* -----------------------------------------------------------------------------
* Local routine & data structure declarations
* -----------------------------------------------------------------------------
*/
* Name: fix_var_context
*
* Brief: Data structure to support find fix-var case on parsetree and plannode
*/
typedef struct fix_var_context {
PlannerInfo* root;
bool oldtonew;
} fix_var_context;
* Name: find_inlist2join_context
*
* Brief: Data structure to support find inlist2join converted SubQueryScan pathnode
*/
typedef struct find_inlist2join_context {
PlannerInfo* root;
} find_inlist2join_context;
* Name: fix_subquery_vars_context
*
* Brief: Data structure to support Var-fix in SubQueryScan for base table replacement
* in inlist2join QRW optimization optimization.
*/
typedef struct fix_subquery_vars_context {
Var* v;
Index old_varno;
Index new_varno;
} fix_subquery_vars_context;
static bool fix_var_expr_walker(Node* node, fix_var_context* context);
static void fix_var(const List* mappings, Var* old_var, bool oldtonew);
static void find_inlist2join_path_walker(Path* path, find_inlist2join_context* context);
static bool belowInlist2JoinThreshold(const RelOptInfo* rel, int num, RelOrientation orientation);
static Var* fix_subquery_vars_expr(Node* expr, Index old_varno, Index new_varno);
static bool fix_subquery_vars_expr_walker(Node* expr, fix_subquery_vars_context* context);
static void fix_var_expr(PlannerInfo* root, Node* node, bool oldtonew = true);
static void rebuild_subquery(PlannerInfo* root, RelOptInfo* rel, RangeTblEntry* rte, RangeTblEntry* new_rte);
static RangeTblEntry* make_rte_with_subquery(PlannerInfo* root, RelOptInfo* rel, RangeTblEntry* rte);
static bool IsConvertableBaseRel(const RangeTblEntry* rte);
static bool IsConvertableInlistRestrict(RelOptInfo* rel, const RestrictInfo* restrict, RelOrientation orientation);
static bool HasConvertableInlistCond(RelOptInfo* rel);
static int ConvertableInlistMaxNum(const RelOptInfo* rel);
static bool IsEqualOpr(Oid opno);
static inline bool IsConvertableType(Oid typoid);
static List* convert_constarray_to_simple(Const* combined_const, Var* listvar, Oid* paramtype);
static char* get_attr_name(int attrnum);
static void rebuild_subquery(PlannerInfo* root, RelOptInfo* rel, RangeTblEntry* rte, RangeTblEntry* new_rte);
RelOptInfo* build_alternative_rel(const RelOptInfo* origin, RTEKind rtekind);
*
* Cost based inlist2join query rewerite entry point
*
* Name: inlist2join_qrw_optimization()
*
* Brief: entry point of applying inlist2join optimization conversion
*
* Parameters:
* @in root: the planner info of current subquery
* @in rti: rel && rte index in current subquery level
*
* Return: void
*
*****************************************************************************/
void inlist2join_qrw_optimization(PlannerInfo* root, int rti)
{
RelOptInfo* rel = root->simple_rel_array[rti];
RangeTblEntry* rte = root->simple_rte_array[rti];
Assert(rel->reloptkind == RELOPT_BASEREL);
int maxnum = ConvertableInlistMaxNum(rel);
if (u_sess->opt_cxt.qrw_inlist2join_optmode == QRW_INLIST2JOIN_DISABLE) {
return;
}
* when inlist has small num of elements
*/
if (u_sess->opt_cxt.qrw_inlist2join_optmode == QRW_INLIST2JOIN_CBO &&
belowInlist2JoinThreshold(rel, maxnum, rel->orientation)) {
return;
}
* infinite recursion
*/
if (rte->securityQuals != NULL) {
return;
}
* if rowmarks effect on this rel is true lock,
* bypass inlist optimize to avoid conflict
*/
ListCell* lc = NULL;
foreach(lc, root->rowMarks) {
PlanRowMark* rc = (PlanRowMark*)lfirst(lc);
if ((int)rc->rti == rti) {
if (RowMarkRequiresRowShareLock(rc->markType)) {
return;
}
}
}
* Return directly if inlist2join optimization can not apply to current rel,
* [1]. When inlist2join optimization is not enabled
* [2]. When there no convert-safe inlist conditions on baserel
* [3]. When current subquery block is not SELECT
* [4]. When current rel's RTE kind is not Relation&SubQuery
*/
if (root->parse->commandType != CMD_SELECT) {
return;
}
if (rel->rtekind != RTE_RELATION && rel->rtekind != RTE_SUBQUERY) {
return;
}
if (rel->rtekind == RTE_RELATION && !IsConvertableBaseRel(rte)) {
return;
}
if (!HasConvertableInlistCond(rel)) {
return;
}
RelOptInfo* new_rel = build_alternative_rel(rel, RTE_SUBQUERY);
rel->alternatives = lappend(rel->alternatives, new_rel);
RangeTblEntry* new_rte = make_rte_with_subquery(root, new_rel, rte);
set_rel_size(root, new_rel, rti, new_rte);
rel->pathlist = list_concat(rel->pathlist, new_rel->pathlist);
}
* Name: fix_skew_expr()
*
* Brief: fix expr for skew qual
* Parameters:
* @in root: the planner info data structure of current query level
* @in skew_list: skew qual list
*/
static void fix_skew_expr(PlannerInfo* root, const List* skew_list)
{
if (skew_list == NIL)
return;
ListCell* lc = NULL;
foreach (lc, skew_list) {
QualSkewInfo* qsinfo = (QualSkewInfo*)lfirst(lc);
fix_var_expr(root, (Node*)qsinfo->skew_quals);
}
}
* Name: fix_subquery_vars_expr_walker()
*
* Brief: recursive-iteration function for fix_subquery_vars_expr()
*/
static bool fix_subquery_vars_expr_walker(Node* expr, fix_subquery_vars_context* context)
{
if (expr == NULL) {
return false;
}
if (IsA(expr, Var)) {
Var* v = (Var*)expr;
if (v->varno == context->old_varno) {
v->varno = context->new_varno;
}
context->v = v;
}
return expression_tree_walker(expr, (bool (*)())fix_subquery_vars_expr_walker, (void*)context);
}
* Name: fix_subquery_vars_expr()
*
* Brief: Recursively fix var's varno/varattno in "inlist2join" converted subquery query
*
* Parameters:
* @in expr: the entry
* @in old_varno: the varno that need to be fixed
* @in new_varno: the new varno
*
* Return: VAR's ref with fixed varno & varattno
*/
static Var* fix_subquery_vars_expr(Node* expr, Index old_varno, Index new_varno)
{
if (expr == NULL) {
return NULL;
}
fix_subquery_vars_context ctx;
ctx.v = NULL;
ctx.old_varno = old_varno;
ctx.new_varno = new_varno;
(void)fix_subquery_vars_expr_walker(expr, &ctx);
return ctx.v;
}
* Name: fix_var_expr_walker()
*
* Brief: recursive-iteration function for fix_var_expr()
*/
static bool fix_var_expr_walker(Node* node, fix_var_context* context)
{
PlannerInfo* root = context->root;
if (node == NULL) {
return false;
}
if (IsA(node, Var)) {
fix_var(root->var_mappings, (Var*)node, context->oldtonew);
}
return expression_tree_walker(node, (bool (*)())fix_var_expr_walker, (void*)context);
}
* Name: fix_var_expr()
*
* Brief: The vars under given node will be fixed by refering to root->var_mappings
*
* Parameters:
* @in root: the planner info data structure of current query level
* @in node: the expr that will be recursively fix-var
* @in oldtonew: if we change from old var to new, or reverse
*
* Return: void
*/
static void fix_var_expr(PlannerInfo* root, Node* node, bool oldtonew)
{
fix_var_context ctx;
ctx.root = root;
ctx.oldtonew = oldtonew;
(void)fix_var_expr_walker(node, &ctx);
}
* Name: fix_var()
*
* Brief: fix the var's varno/varattno referring mapping list
*
* Parameters:
* @in mappings: the var-change references for current subquery level
* @in old_var: the var that fill be fixed
* @in oldtonew: if we change from old var to new, or reverse
*
* Return: void
*/
static void fix_var(const List* mappings, Var* old_var, bool oldtonew)
{
ListCell* lc = NULL;
if (mappings == NULL) {
return;
}
foreach (lc, mappings) {
RewriteVarMapping* rvm = (RewriteVarMapping*)lfirst(lc);
if (oldtonew && equal(rvm->old_var, old_var)) {
old_var->varno = rvm->new_var->varno;
old_var->varattno = rvm->new_var->varattno;
break;
} else if (!oldtonew && equal(rvm->new_var, old_var)) {
old_var->varno = rvm->old_var->varno;
old_var->varattno = rvm->old_var->varattno;
break;
}
}
return;
}
* Name: fix_vars_plannode()
*
* Brief: fix the plannode's Var expression from top plan tree
*
* Parameters:
* @in root: PlannerInfo* ptr of current subquery level
* @in node: top plan node pointer
*
* Return: void
*/
void fix_vars_plannode(PlannerInfo* root, Plan* node)
{
Assert(u_sess->opt_cxt.qrw_inlist2join_optmode != QRW_INLIST2JOIN_DISABLE && root->var_mappings);
if (node == NULL) {
return;
}
fix_var_expr(root, (Node*)node->targetlist);
fix_var_expr(root, (Node*)node->qual);
if (IsA(node, SubqueryScan)) {
fix_var_expr(root, (Node*)node->distributed_keys);
return;
}
ListCell* lc = NULL;
switch (nodeTag(node)) {
case T_HashJoin:
case T_VecHashJoin: {
HashJoin* hj = (HashJoin*)node;
fix_var_expr(root, (Node*)hj->hashclauses);
fix_var_expr(root, (Node*)hj->join.joinqual);
fix_var_expr(root, (Node*)hj->join.nulleqqual);
fix_var_expr(root, (Node*)node->var_list);
}
break;
case T_AsofJoin:
case T_VecAsofJoin: {
VecAsofJoin* aj = (VecAsofJoin*)node;
fix_var_expr(root, (Node*)aj->hashclauses);
fix_var_expr(root, (Node*)aj->mergeclauses);
fix_var_expr(root, (Node*)aj->join.joinqual);
fix_var_expr(root, (Node*)aj->join.nulleqqual);
fix_var_expr(root, (Node*)node->var_list);
}
break;
case T_NestLoop:
case T_VecNestLoop: {
NestLoop* nl = (NestLoop*)node;
foreach (lc, nl->nestParams) {
NestLoopParam* nlp = (NestLoopParam*)lfirst(lc);
fix_var_expr(root, (Node*)nlp->paramval);
}
fix_var_expr(root, (Node*)nl->join.joinqual);
fix_var_expr(root, (Node*)nl->join.nulleqqual);
fix_var_expr(root, (Node*)node->var_list);
} break;
case T_MergeJoin:
case T_VecMergeJoin: {
MergeJoin* mj = (MergeJoin*)node;
fix_var_expr(root, (Node*)mj->mergeclauses);
fix_var_expr(root, (Node*)mj->join.joinqual);
fix_var_expr(root, (Node*)mj->join.nulleqqual);
fix_var_expr(root, (Node*)node->var_list);
} break;
case T_Stream:
case T_VecStream: {
Stream* sj = (Stream*)node;
fix_var_expr(root, (Node*)sj->distribute_keys);
fix_skew_expr(root, sj->skew_list);
} break;
case T_RemoteQuery:
case T_VecRemoteQuery: {
RemoteQuery* rq = (RemoteQuery*)node;
fix_var_expr(root, (Node*)rq->base_tlist);
} break;
case T_Limit:
case T_VecLimit: {
Limit* lm = (Limit*)node;
fix_var_expr(root, lm->limitCount);
fix_var_expr(root, lm->limitOffset);
} break;
case T_VecWindowAgg:
case T_WindowAgg: {
WindowAgg* wa = (WindowAgg*)node;
fix_var_expr(root, wa->startOffset);
fix_var_expr(root, wa->endOffset);
} break;
case T_BaseResult:
case T_VecResult: {
BaseResult* br = (BaseResult*)node;
fix_var_expr(root, br->resconstantqual);
} break;
case T_ModifyTable:
case T_VecModifyTable: {
ModifyTable* mt = (ModifyTable*)node;
if (mt->mergeActionList != NIL && (IS_STREAM_PLAN || IS_SINGLE_NODE)) {
foreach (lc, mt->mergeActionList) {
MergeAction* action = (MergeAction*)lfirst(lc);
fix_var_expr(root, (Node*)action->targetList);
fix_var_expr(root, (Node*)action->qual);
}
}
foreach (lc, mt->plans) {
fix_vars_plannode(root, (Plan*)lfirst(lc));
}
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
ListCell* elt = NULL;
RemoteQuery* rq = NULL;
foreach (elt, mt->remote_plans) {
rq = (RemoteQuery*)lfirst(elt);
if (rq != NULL) {
* If base_tlist is set, it means that we have a reduced remote
* query plan. So need to set the var references accordingly.
*/
fix_var_expr(root, (Node*)rq->scan.plan.targetlist);
fix_var_expr(root, (Node*)rq->scan.plan.qual);
fix_var_expr(root, (Node*)rq->base_tlist);
}
}
}
} break;
case T_Append:
case T_VecAppend: {
Append* ap = (Append*)node;
foreach (lc, ap->appendplans) {
fix_vars_plannode(root, (Plan*)lfirst(lc));
}
} break;
case T_MergeAppend: {
MergeAppend* ma = (MergeAppend*)node;
foreach (lc, ma->mergeplans) {
fix_vars_plannode(root, (Plan*)lfirst(lc));
}
} break;
default:
break;
}
fix_vars_plannode(root, node->lefttree);
fix_vars_plannode(root, node->righttree);
}
* Name: fix_var_expr()
*
* Brief: The vars under given node will be fixed by refering to root->var_mappings
*
* Parameters:
* @in root: the planner info data structure of current query level
* @in node: the expr that will be recursively fix-var
*
* Return: void
*/
void find_inlist2join_path(PlannerInfo* root, Path* best_path)
{
find_inlist2join_context ctx;
ctx.root = root;
List* var_mapping_new = NIL;
ListCell* lc = NULL;
find_inlist2join_path_walker(best_path, &ctx);
foreach (lc, root->var_mappings) {
RewriteVarMapping* rvm = (RewriteVarMapping*)lfirst(lc);
if (rvm->need_fix) {
var_mapping_new = lappend(var_mapping_new, rvm);
}
}
root->var_mappings = var_mapping_new;
}
* Name: find_inlist2join_path_walker()
*
* Brief: recursive-iteration function to find inlist2join SubqueryScan
*/
static void find_inlist2join_path_walker(Path* path, find_inlist2join_context* context)
{
if (path == NULL) {
return;
}
switch (path->pathtype) {
case T_Stream: {
find_inlist2join_path_walker(((StreamPath*)path)->subpath, context);
} break;
case T_SubqueryScan: {
RelOptInfo* rel = path->parent;
if (rel->base_rel != NULL) {
ListCell* lc1 = NULL;
ListCell* lc = NULL;
* The rel is new_rel and has an inlist2join path
* Remove the mapping from root
*/
foreach (lc1, rel->reltarget->exprs) {
Var* var = (Var*)lfirst(lc1);
foreach (lc, context->root->var_mappings) {
RewriteVarMapping* rvm = (RewriteVarMapping*)lfirst(lc);
if (equal(rvm->old_var, var)) {
rvm->need_fix = true;
break;
}
}
}
}
} break;
case T_Append: {
ListCell* cell = NULL;
foreach (cell, ((AppendPath*)path)->subpaths) {
find_inlist2join_path_walker((Path*)lfirst(cell), context);
}
} break;
case T_MergeAppend: {
ListCell* cell = NULL;
foreach (cell, ((MergeAppendPath*)path)->subpaths) {
find_inlist2join_path_walker((Path*)lfirst(cell), context);
}
} break;
case T_Material: {
find_inlist2join_path_walker(((MaterialPath*)path)->subpath, context);
} break;
case T_Unique: {
find_inlist2join_path_walker(((UniquePath*)path)->subpath, context);
} break;
case T_PartIterator: {
find_inlist2join_path_walker(((PartIteratorPath*)path)->subPath, context);
} break;
case T_NestLoop:
case T_HashJoin:
case T_MergeJoin: {
JoinPath* jp = (JoinPath*)path;
find_inlist2join_path_walker(jp->innerjoinpath, context);
find_inlist2join_path_walker(jp->outerjoinpath, context);
} break;
case T_BaseResult: {
find_inlist2join_path_walker(((ResultPath*)path)->subpath, context);
} break;
default:
break;
}
return;
}
* Name: IsConvertableBaseRel()
*
* Brief: Check if the rel is convertable for inlist2join
* DFS table is not supported now
*
* Parameters:
* @in rel: the RelOptInfo object to check
* @in rte: the RangeTblEntry object
*
* Return: bool. indicate Yes/Not for inlist2join conversion
*/
static bool IsConvertableBaseRel(const RangeTblEntry* rte)
{
Oid relId = rte->relid;
bool convertable = true;
Assert(rte->rtekind == RTE_RELATION);
Relation rel = RelationIdGetRelation(relId);
if (rel == NULL) {
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("could not open relation with OID %u", relId)));
}
if (RelationIsForeignTable(rel) || RelationIsStream(rel)) {
convertable = false;
}
RelationClose(rel);
return convertable;
}
* Name: IsConvertableInlistRestrict()
*
* Brief: Check if the given inlist restrict is valid for inlist2join conversion
*
* Parameters:
* @in restrict: the restrict info that need to check
*
* Return bool. indicate Yes/Not to convert
*/
static bool IsConvertableInlistRestrict(RelOptInfo* rel, const RestrictInfo* restrict, RelOrientation orientation)
{
Assert(restrict != NULL && IsA(restrict->clause, ScalarArrayOpExpr));
ScalarArrayOpExpr* scalarop = (ScalarArrayOpExpr*)restrict->clause;
Oid opno = scalarop->opno;
List* var_list =
pull_var_clause((Node*)list_nth(scalarop->args, 0), PVC_REJECT_AGGREGATES, PVC_REJECT_PLACEHOLDERS);
if (list_length(var_list) != 1) {
return false;
}
Var* listvar = (Var*)linitial(var_list);
Oid typoid = listvar->vartype;
* Confirm current restrictinfo to see it matchs all following cretarias:
* 1. array operation is "in-any"
* 2. array operator is not "="
* 3. array operation is not in "c1 in LIST" form
* 4. array expr contains more than one var node, for example
* (c1+c2) in (1,2,3,4,5),
* (c1,c2) in ((1,1), (2,2))
* var only appear in args's first element, for example
* c1 in (1,2,3,4,5)
* c1 = ANY(array[1,2,3,4,5])
* others wrong example
* 12 = ANY (p1.proargtypes)
* 12 = ANY (array[2275,1245,2588]);
*/
if (scalarop->useOr == false || !IsEqualOpr(opno) || list_length(scalarop->args) != 2 ||
!IsA((Node*)list_nth(scalarop->args, 1), Const) || !IsConvertableType(typoid)) {
return false;
}
* Do not apply inlist2join rewrite optimization if optmode greater than 1 but
* the inlist length is less than threshold
*/
int inlist_number = estimate_array_length((Node*)list_nth(scalarop->args, 1));
if (u_sess->opt_cxt.qrw_inlist2join_optmode > QRW_INLIST2JOIN_FORCE &&
inlist_number < u_sess->opt_cxt.qrw_inlist2join_optmode) {
return false;
}
if (u_sess->opt_cxt.qrw_inlist2join_optmode == QRW_INLIST2JOIN_CBO &&
belowInlist2JoinThreshold(rel, inlist_number, orientation)) {
return false;
}
Assert(u_sess->opt_cxt.qrw_inlist2join_optmode > QRW_INLIST2JOIN_DISABLE);
return true;
}
* Name: HasConvertableInlistCond()
*
* Brief: make determination if given rel is valid for applying inlist2join optimization
*
* Parameters:
* @in rel: the RelOptInfo object to check
*
* Return: bool. indicate Yes/Not for inlist2join conversion
*/
static bool HasConvertableInlistCond(RelOptInfo* rel)
{
ListCell* lc = NULL;
bool convertable = false;
* If rel has no restriction info, return false as considered as no convertable
* inlist2join case.
*/
if (rel->baserestrictinfo == NULL) {
return false;
}
List* var_list = pull_var_clause((Node*)rel->reltarget->exprs, PVC_INCLUDE_AGGREGATES, PVC_INCLUDE_PLACEHOLDERS);
foreach (lc, var_list) {
if (!IsA(lfirst(lc), Var)) {
return false;
}
}
foreach (lc, rel->baserestrictinfo) {
RestrictInfo* restrict = (RestrictInfo*)lfirst(lc);
if (check_subplan_expr((Node*)restrict->clause)) {
convertable = false;
break;
}
if (IsA(restrict->clause, ScalarArrayOpExpr)) {
* If we find any ArrayOp is convertable we think the base-rel is valid for
* inlist2join conversion then create the SubQueryScan path and take inlist2join
* into consideration.
*/
if (IsConvertableInlistRestrict(rel, restrict, rel->orientation)) {
convertable = true;
}
}
}
return convertable;
}
* Name: IsEqualOpr()
*
* Brief: determin if given operator ID is for equivalence check
*
* Parameters:
* @in opno: the opno to check
*
* Return: bool. indicate Yes/Not for equivalence
*/
static bool IsEqualOpr(Oid opno)
{
Relation pg_operator;
HeapTuple tuple;
SysScanDesc scan;
ScanKeyData skey[1];
pg_operator = heap_open(OperatorRelationId, AccessShareLock);
ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, opno);
scan = systable_beginscan(pg_operator, OperatorOidIndexId, true, NULL, 1, skey);
if (HeapTupleIsValid(tuple = systable_getnext(scan))) {
Form_pg_operator pgoperform = (Form_pg_operator)GETSTRUCT(tuple);
if (strcmp(NameStr(pgoperform->oprname), "=") == 0) {
systable_endscan(scan);
heap_close(pg_operator, AccessShareLock);
return true;
}
} else {
elog(LOG, "could not find tuple for operator %u", opno);
}
systable_endscan(scan);
heap_close(pg_operator, AccessShareLock);
return false;
}
* Name: IsConvertableType()
*
* Brief: determin if given ScalarArrayOpExpr is convertable type for check
*
* Parameters:
* @in scalarop: the ScalarArrayOpExpr to check
*
* Return: bool. indicate Yes/Not for convertable type for check
*/
static inline bool IsConvertableType(Oid typoid)
{
switch (typoid) {
case INT1OID:
case INT2OID:
case INT4OID:
case INT8OID:
case NUMERICOID:
case FLOAT4OID:
case FLOAT8OID:
case BOOLOID:
case CHAROID:
case BPCHAROID:
case VARCHAROID:
case NVARCHAR2OID:
case TEXTOID:
case DATEOID:
case TIMEOID:
case TIMETZOID:
case TIMESTAMPOID:
case TIMESTAMPTZOID:
case SMALLDATETIMEOID:
case INTERVALOID:
case TINTERVALOID:
return true;
default:
elog(DEBUG1, "Inlist2join is not converted for type %u", typoid);
}
return false;
}
* Name: ConvertableInlistMaxNum()
*
* Brief: return max num of inlist condition
*
* Parameters:
* @in opno: in rel: the RelOptInfo object
*
* Return: int. the max num of inlist condition
*/
static int ConvertableInlistMaxNum(const RelOptInfo* rel)
{
int maxnum = 0;
if (rel->baserestrictinfo != NULL) {
ListCell* lc = NULL;
foreach (lc, rel->baserestrictinfo) {
RestrictInfo* restrict = (RestrictInfo*)lfirst(lc);
if (IsA(restrict->clause, ScalarArrayOpExpr)) {
ScalarArrayOpExpr* scalarop = (ScalarArrayOpExpr*)restrict->clause;
Oid opno = scalarop->opno;
* Confirm:
* 1. array operation is "in-any"
* 2. there is no dataype coerce
* 3. equal condition
*/
if (scalarop->useOr && IsA(linitial(scalarop->args), Var) && IsEqualOpr(opno)) {
Node* arraynode = (Node*)lsecond(scalarop->args);
maxnum = Max(maxnum, estimate_array_length(arraynode));
}
}
}
}
return maxnum;
}
* Name: belowInlist2JoinThreshold()
*
* Brief: Check if inlist is small enough that we don't consider inlist join,
* else will cause regression due to inaccurate cost estimation
*
* Parameters:
* @in opno: in rel: the RelOptInfo object
*
* Return: bool. indicate Yes/Not for inlist2join conversion
*/
static bool belowInlist2JoinThreshold(const RelOptInfo* rel, int num, RelOrientation orientation)
{
int tarnum = list_length(rel->reltarget->exprs);
const int row_threshod = 10;
if (orientation == REL_ROW_ORIENTED || orientation == REL_ORIENT_UNKNOWN) {
if (num <= row_threshod) {
return true;
}
} else {
if (!tarnum) {
tarnum = 1;
}
if (num <= tarnum * 3) {
return true;
}
}
return false;
}
* Name: make_rte_with_subquery()
*
* Brief: make a new RangeTblEntry with subquery and this query contains inlist2join structure.
*
* Parameters:
* @in root: the PlannerInfo object that includes all information for planning/optimization
* @in rel: the RelOptInfo object with inlist restriction
* @in rte: the RangeTblEntry object with no inlist-subquery
*
* Return: RangeTblEntry. the new RangeTblEntry with inlist-subquery
*/
static RangeTblEntry* make_rte_with_subquery(PlannerInfo* root, RelOptInfo* rel, RangeTblEntry* rte)
{
RangeTblEntry* new_rte = makeNode(RangeTblEntry);
const char* refname = "__unnamed_subquery__";
List* colnames_new = NIL;
ListCell* reltarget = NULL;
if (rte->eref->colnames) {
foreach (reltarget, rel->reltarget->exprs) {
Var* relvar = (Var*)lfirst(reltarget);
if (relvar->varattno > 0) {
colnames_new = lappend(colnames_new, list_nth(rte->eref->colnames, relvar->varattno - 1));
} else if (relvar->varattno < 0) {
colnames_new = lappend(colnames_new, get_attr_name(relvar->varattno));
} else {
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("encounters invalid varno")));
}
}
}
new_rte->alias = makeAlias(refname, NIL);
new_rte->eref = makeAlias(refname, colnames_new);
rebuild_subquery(root, rel, rte, new_rte);
new_rte->rtekind = RTE_SUBQUERY;
new_rte->relid = InvalidOid;
new_rte->orientation = REL_ORIENT_UNKNOWN;
new_rte->subquery->cteList = root->parse->cteList;
* Flags:
* - this RTE should be expanded to include descendant tables,
* - this RTE is in the FROM clause,
* - this RTE should be checked for appropriate access rights.
*
* SubQueries are never checked for access rights.
* ----------
*/
new_rte->inh = false;
new_rte->inFromCl = true;
new_rte->requiredPerms = 2;
new_rte->checkAsUser = InvalidOid;
return new_rte;
}
* Name: build_alternative_rel()
*
* Brief: build a new RelOptInfo with new rtekind.
*
* Parameters:
* @in origin: the origin RelOptInfo
* @in rtekind: the rtekind of new_rel
*
* Return: RelOptInfo. the new RelOptInfo with specific rtekind
*/
RelOptInfo* build_alternative_rel(const RelOptInfo* origin, RTEKind rtekind)
{
RelOptInfo* rel = NULL;
Assert(origin != NULL);
rel = makeNode(RelOptInfo);
rel->reloptkind = origin->reloptkind;
rel->relids = bms_make_singleton(origin->relid);
rel->isPartitionedTable = false;
rel->partflag = origin->partflag;
rel->rows = origin->rows;
rel->reltarget = create_empty_pathtarget();
rel->reltarget->width = origin->reltarget->width;
rel->encodedwidth = origin->encodedwidth;
rel->encodednum = origin->encodednum;
rel->reltarget->exprs = list_copy(origin->reltarget->exprs);
rel->baserestrictinfo = (List*)copyObject(origin->baserestrictinfo);
rel->pathlist = NIL;
rel->ppilist = NIL;
rel->cheapest_startup_path = NULL;
rel->cheapest_total_path = NULL;
rel->cheapest_unique_path = NULL;
rel->relid = origin->relid;
rel->rtekind = rtekind;
rel->min_attr = origin->min_attr;
rel->max_attr = origin->max_attr;
rel->attr_needed = origin->attr_needed;
rel->attr_widths = origin->attr_widths;
rel->indexlist = NIL;
rel->pages = 0;
rel->tuples = 0;
rel->multiple = 0;
rel->allvisfrac = 0;
rel->pruning_result = NULL;
rel->pruning_result_for_index_usable = NULL;
rel->pruning_result_for_index_unusable = NULL;
rel->partItrs = -1;
rel->partItrs_for_index_usable = -1;
rel->partItrs_for_index_unusable = -1;
rel->subplan = NULL;
rel->subroot = NULL;
rel->subplan_params = NIL;
rel->fdwroutine = NULL;
rel->fdw_private = NULL;
rel->baserestrictcost.startup = 0;
rel->baserestrictcost.per_tuple = 0;
rel->joininfo = (List*)copyObject(origin->joininfo);
rel->subplanrestrictinfo = (List*)copyObject(origin->subplanrestrictinfo);
rel->has_eclass_joins = false;
rel->varratio = NIL;
rel->lateral_relids = origin->lateral_relids;
rel->alternatives = NIL;
rel->base_rel = (RelOptInfo*)origin;
return rel;
}
* Name: get_attr_name()
*
* Brief: extract the correct name for a system attribute
*
* Parameters:
* @in attrnum: the attrnum to get attrname
*
* Return: char *. the attrname
*/
static char* get_attr_name(int attrnum)
{
Assert(attrnum < 0);
switch (attrnum) {
case SelfItemPointerAttributeNumber:
return (char*)"ctid";
case ObjectIdAttributeNumber:
return (char*)"oid";
case MinTransactionIdAttributeNumber:
return (char*)"xmin";
case MinCommandIdAttributeNumber:
return (char*)"cmin";
case MaxTransactionIdAttributeNumber:
return (char*)"xmax";
case MaxCommandIdAttributeNumber:
return (char*)"cmax";
case TableOidAttributeNumber:
return (char*)"tableoid";
#ifdef PGXC
case XC_NodeIdAttributeNumber:
return (char*)"xc_node_id";
case BucketIdAttributeNumber:
return (char*)"tablebucketid";
case UidAttributeNumber:
return (char*)"gs_tuple_uid";
#endif
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid column number %d for table \n", attrnum)));
break;
}
return NULL;
}
* Name: convert_constarray_to_simple()
*
* Brief: Convert scalararray to simple const list to help build ValueScan's valuelist
* later steps.
*
* Parameters:
* @in scalar: the ScalarArrayOpExpr object to be converted
* @in listvar: the Var object of inlist column
* @in paramtype: the Oid of these simple consts
*
* Return: List *. the converted simple const list
*/
static List* convert_constarray_to_simple(Const* combined_const, Var* listvar, Oid* paramtype)
{
int i;
int nitems;
ArrayType* arr = NULL;
int16 typlen;
bool typbyval = false;
char typalign;
char* s = NULL;
bits8* bitmap = NULL;
int bitmask;
bool elemNull = false;
Node* simple_const = NULL;
List* const_list = NIL;
arr = DatumGetArrayTypeP(combined_const->constvalue);
*paramtype = ARR_ELEMTYPE(arr);
nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
get_typlenbyvalalign(*paramtype, &typlen, &typbyval, &typalign);
s = (char*)ARR_DATA_PTR(arr);
bitmap = ARR_NULLBITMAP(arr);
bitmask = 1;
for (i = 0; i < nitems; i++) {
Datum elt;
if (bitmap && (*bitmap & bitmask) == 0) {
elemNull = true;
simple_const = (Node*)makeConst(
*paramtype, combined_const->consttypmod, combined_const->constcollid, typlen, (Datum)0, true, typbyval);
} else {
elemNull = false;
elt = fetch_att(s, typbyval, typlen);
s = att_addlength_pointer(s, typlen, s);
s = (char*)att_align_nominal(s, typalign);
simple_const = (Node*)makeConst(
*paramtype, combined_const->consttypmod, combined_const->constcollid, typlen, elt, false, typbyval);
}
const_list = lappend(const_list, simple_const);
if (bitmap != NULL) {
bitmask <<= 1;
if (bitmask == 0x100) {
bitmap++;
bitmask = 1;
}
}
}
return const_list;
}
* Name: rebuild_subquery()
*
* Brief: create the SubQuery object to for new_rte.
*
* Parameters:
* @in root: the PlannerInfo object that includes all information for planning/optimization
* @in rel: the RelOptInfo object for inlist2join
* @in rte: the RangeTblEntry object with no inlist2join-subquery
* @in new_rte: the RangeTblEntry object with inlist2join-subquery
*
* Return: void.
*/
static void rebuild_subquery(PlannerInfo* root, RelOptInfo* rel, RangeTblEntry* rte, RangeTblEntry* new_rte)
{
Query* query = makeNode(Query);
query->commandType = CMD_SELECT;
query->querySource = QSRC_ORIGINAL;
query->canSetTag = true;
query->resultRelation = 0;
query->hasSubLinks = true;
query->mergeTarget_relation = 0;
query->targetList = NIL;
query->rtable = list_make1((RangeTblEntry*)copyObject(rte));
List* colnames = rte->eref->colnames;
ListCell* lc1 = NULL;
* Build target list for new SubQuery and RelOptInfo
*
* Note:
* 1. SubQuery's targetlist, as we are buiding a new separate Query struct,
* the targetlist's varno is started from 1, and varattno keeps origin
*
* 2. RelOptInfo's targetlist, as we are buiding a new RelOptInfo to replace
* the original one from the, the varno keeps origin, but the subquery's
* return attribues is start from 1
*/
foreach (lc1, rel->reltarget->exprs) {
Assert(IsA(lfirst(lc1), Var));
Var* relvar = (Var*)copyObject((Var*)lfirst(lc1));
relvar->varno = SUBQUERY_VARNO;
char* varname = NULL;
if (relvar->varattno < 0)
varname = get_attr_name(relvar->varattno);
else if (relvar->varattno > 0)
varname = strVal(list_nth(colnames, relvar->varattno - 1));
else
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("There is no exist vararrno with 0")));
TargetEntry* entry = makeTargetEntry((Expr*)relvar,
(AttrNumber)(list_length(query->targetList) + 1),
varname,
false);
entry->resorigtbl = rte->relid;
entry->resorigcol = relvar->varattno;
query->targetList = lappend(query->targetList, entry);
}
AttrNumber attno = SUBQUERY_ATTNO;
foreach (lc1, rel->reltarget->exprs) {
Var* old_var = (Var*)copyObject((Var*)lfirst(lc1));
Var* new_var = (Var*)copyObject((Var*)lfirst(lc1));
{
new_var->varattno = attno;
RewriteVarMapping* mapping = (RewriteVarMapping*)palloc0(sizeof(RewriteVarMapping));
mapping->old_var = old_var;
mapping->new_var = new_var;
mapping->need_fix = false;
root->var_mapping_rels = bms_add_member(root->var_mapping_rels, (int)old_var->varno);
root->var_mappings = lappend(root->var_mappings, mapping);
}
attno++;
}
RangeTblRef* rtr = makeNode(RangeTblRef);
rtr->rtindex = SUBQUERY_VARNO;
List* fromlist = list_make1(rtr);
Assert(rel->baserestrictinfo);
* 4. Build SubQuery's ValueScan part
* Search the RelOptInto's restriction info to find In-List case and try to convert
* it to "col in <list>" form to let sublink_pullup to do actual inlist2join conversion
*
* example: select * from t1 where t1.c1 in (1,2,3,4)
* => select * from (select * from t1 where t1.c1 in (select column1 from (values(1),(2),(3),(4))))
*/
List* args = NIL;
ListCell* lc = NULL;
foreach (lc, rel->baserestrictinfo) {
RestrictInfo* rinfo = (RestrictInfo*)lfirst(lc);
if (!(IsA(rinfo->clause, ScalarArrayOpExpr))) {
* Fix vars in restriction list for a case where a Var is changed in new
* SubQuery and its origin ref var
*/
(void)fix_subquery_vars_expr((Node*)rinfo->clause, rel->relid, (Index)SUBQUERY_VARNO);
args = lappend(args, rinfo->clause);
} else {
if (!IsConvertableInlistRestrict(rel, rinfo, rte->orientation)) {
(void)fix_subquery_vars_expr((Node*)rinfo->clause, rel->relid, (Index)SUBQUERY_VARNO);
args = lappend(args, rinfo->clause);
continue;
}
ScalarArrayOpExpr* scalar = (ScalarArrayOpExpr*)copyObject(rinfo->clause);
* Build the inlist-converted SubLink with content values((),(),()), for example
*
* C1 IN (1,2,3,4);
* =>
* C1 = ANY (
* SELECT column1
* FROM
* (values(1),(2),(3),(4)) as x(column1)
* )
*/
SubLink* sublink = makeNode(SubLink);
sublink->subLinkType = ANY_SUBLINK;
sublink->operName = list_make1(makeString((char *)"="));
sublink->location = -1;
Oid colid = exprCollation((Node*)linitial((List*)scalar->args));
List* args_in_opexpr = NIL;
Oid paramtype = 0;
Var* listvar = fix_subquery_vars_expr((Node*)scalar->args, rel->relid, (Index)SUBQUERY_VARNO);
Const* combined_const = (Const*)list_nth(scalar->args, 1);
List* const_list = convert_constarray_to_simple(combined_const, listvar, ¶mtype);
args_in_opexpr = lappend(args_in_opexpr, linitial((List*)scalar->args));
Param* param = makeNode(Param);
param->paramkind = PARAM_SUBLINK;
param->paramid = 1;
param->paramtype = paramtype;
param->paramtypmod = -1;
param->paramcollid = listvar->varcollid;
param->location = -1;
param->tableOfIndexTypeList = NULL;
args_in_opexpr = lappend(args_in_opexpr, param);
OpExpr* opexpr = makeNode(OpExpr);
opexpr->opno = scalar->opno;
opexpr->opfuncid = scalar->opfuncid;
opexpr->opresulttype = BOOLOID;
opexpr->opcollid = 0;
opexpr->inputcollid = listvar->varcollid;
opexpr->location = -1;
sublink->testexpr = (Node*)opexpr;
opexpr->args = args_in_opexpr;
Query* subselect = makeNode(Query);
subselect->commandType = CMD_SELECT;
subselect->querySource = QSRC_ORIGINAL;
subselect->canSetTag = true;
subselect->resultRelation = 0;
subselect->mergeTarget_relation = 0;
RangeTblRef* rtf = makeNode(RangeTblRef);
rtf->rtindex = VALUES_SUBLINK_VARNO;
subselect->jointree = makeFromExpr(list_make1(rtf), NULL);
* Build SubLink's TargetEntry
*
* This var is used in RTE *VALUE*, there is one RTE in the query and one column in the RTE
* so let varno and varattno are 1, the type is same as the listvar->vartype
*/
TargetEntry* target_entry = makeNode(TargetEntry);
target_entry->expr = (Expr*)makeVar((Index)VALUES_SUBLINK_VARNO,
VALUES_SUBLINK_VARATTNO,
paramtype,
-1,
colid,
0);
target_entry->resno = VALUES_SUBLINK_VARATTNO;
target_entry->resname = (char *)"column1";
target_entry->resorigtbl = 0;
target_entry->ressortgroupref = 0;
target_entry->resorigcol = 0;
subselect->targetList = list_make1(target_entry);
List* values_lists = NIL;
List* collations = NIL;
ListCell* lc_value = NULL;
foreach (lc_value, const_list) {
Const* const_value = (Const*)copyObject((Const*)lfirst(lc_value));
values_lists = lappend(values_lists, list_make1(const_value));
}
collations = lappend_oid(collations, combined_const->constcollid);
RangeTblEntry* sublink_rte = addRangeTableEntryForValues(NULL, values_lists, collations, NULL, true);
subselect->rtable = list_make1(sublink_rte);
sublink->subselect = (Node*)subselect;
args = lappend(args, sublink);
}
}
rel->baserestrictinfo = NIL;
Expr* andexpr = makeBoolExpr(AND_EXPR, args, -1);
query->jointree = makeFromExpr(fromlist, (Node*)andexpr);
#ifdef ENABLE_MULTIPLE_NODES
mark_query_canpush_flag((Node *) query);
#endif
new_rte->subquery = query;
}