Files
openGauss-server/src/gausskernel/optimizer/prep/prepnonjointree.cpp
zhouxiongjia dc80b79cc0 修复了关于rownum的4个bug。
1.最大值只有255,正常应该是无限大。
2.group by跟where rownum < n同时存在时,禁止将rownum优化成limit
3.groupy by后面没有rownum,而having后面有rownum时,会正常运行,正常应该报错。
4.union前后的子句如果有where rownum < n,并且还有含有order by的子查询时,禁止将order by优化删除掉。
2020-10-21 18:41:59 +08:00

2044 lines
77 KiB
C++
Executable File

/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*
* prepnonjointree.cpp
* Planner preprocessing for subqueries and non-join tree manipulation.
* The main module(s) are:
* (1) Lazy Agg
* (2) Reduce orderby
*
* IDENTIFICATION
* src/gausskernel/optimizer/prep/prepnonjointree.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "access/transam.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_proc.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/prep.h"
#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_coerce.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
#include "utils/syscache.h"
#if defined(ENABLE_UT) || defined(ENABLE_QUNIT)
#define static
#endif
/* ------------------------------------------------------------ */
/* Lazy Agg : begin */
/* ------------------------------------------------------------ */
/*
* --------------------
* Data structure of Lazy Agg
* --------------------
*/
enum LAZYAGG_AGGTYPE { LAZYAGG_SUM, LAZYAGG_MIN, LAZYAGG_MAX, LAZYAGG_COUNT, LAZYAGG_COUNTSTAR, LAZYAGG_OTHER };
struct lazyagg_query_context {
Bitmapset* bms_sum = NULL; /* bms of target list index(s) of SUM agg */
Bitmapset* bms_min = NULL; /* bms of target list index(s) of MIN agg */
Bitmapset* bms_max = NULL; /* bms of target list index(s) of MAX agg */
Bitmapset* bms_count = NULL; /* bms of target list index(s) of COUNT agg in child-query, the potential rewritable
SUM in parent-query */
Bitmapset* bms_countStar = NULL; /* bms of target list index(s) of COUNT(*) agg in child-query, the potential
rewritable SUM in parent-query */
Bitmapset* bms_joinWhereGroupby =
NULL; /* bms of target list index(s) of JOIN/WHERE/GROUP BY, only used by parent-query */
Bitmapset* bms_all = NULL; /* bms of all attnum of child-query used in parent-query, only used by parent-query */
bool isInnerSide; /* is child-query in inner side of join, only used by parent-query */
List* sumAggFuncs; /* SUM Aggref list of parent-query, only used by parent-query */
Expr** countParam = NULL; /* param Expr of COUNT(c) in child-query, and best param Expr of child-query's
corresponding column for parent-query */
Oid* countParamBestType =
NULL; /* best type Oid of countParam of all child-query's corresponding column, only used by parent-query */
int lenOfCountParam; /* actually slot count of countParam, if this value is not init 0, it will be length+1 of
child-query's targetList */
};
/*
* --------------------
* Declaration of static functions
* --------------------
*/
static void lazyagg_free_querycontext(lazyagg_query_context* context);
static void lazyagg_clear_childcontext(lazyagg_query_context* childContext);
static void lazyagg_init_parentquery_countparam(lazyagg_query_context* parentContext, lazyagg_query_context* from);
static void lazyagg_update_childquery_countparam(
Query* childParse, Aggref* aggref, const AttrNumber attno, lazyagg_query_context* childContext);
static bool lazyagg_is_countparam_compatible(lazyagg_query_context* parentContext, lazyagg_query_context* childContext);
static Oid lazyagg_get_aggtrantype(Oid aggfnoid);
static Bitmapset* lazyagg_pull_vars_attno(Node* node, const Index rteIndex, Query* parse);
static void lazyagg_set_parentcontext_with_child(
Query* parentParse, Index childQueryRTEIndex, lazyagg_query_context* parentContext);
static List* lazyagg_get_agg_list(Node* node, bool* has_AggrefsOuterquery = NULL);
static LAZYAGG_AGGTYPE lazyagg_get_agg_type(Aggref* aggref);
static bool lazyagg_check_delay_rewrite_compatibility(lazyagg_query_context* parentContext,
lazyagg_query_context* childContext, Bitmapset* parentSumToCount, Bitmapset* parentSumToCountStar);
static bool lazyagg_check_childquery_feasibility(Query* parentParse, Query* childParse,
lazyagg_query_context* parentContext, lazyagg_query_context* childContext, bool resetContextOnly = false);
static bool lazyagg_check_parentquery_feasibility(
Query* parentParse, Index* targetRTEIndex, lazyagg_query_context* parentContext);
static void lazyagg_rewrite_setop_stmt(
const AttrNumber varattno, lazyagg_query_context* parentContext, SetOperationStmt* setOpStmt);
static void lazyagg_rewrite_setop(
Query* parentParse, const Index varno, const AttrNumber varattno, lazyagg_query_context* parentContext);
static void lazyagg_rewrite_join_aliasvars(Query* parentParse, Index varno, AttrNumber varattno, Oid vartype);
static bool lazyagg_rewrite_childquery(Query* childParse, lazyagg_query_context* parentContext, bool isCheck = false);
static bool lazyagg_rewrite_delayed_query(
Query* parentParse, List* delayedChildQueryList, lazyagg_query_context* parentContext, bool isCheck = false);
static Query* lazyagg_analyze_single_childquery(
Query* parentParse, Query* childParse, lazyagg_query_context* parentContext);
static List* lazyagg_search_setoperationstmt(
Query* parentParse, Query* setOpParse, lazyagg_query_context* parentContext, Node* setOpStmtArg);
static List* lazyagg_analyze_setop_childquery(
Query* parentParse, Query* setOpParse, lazyagg_query_context* parentContext, SetOperationStmt* setOpStmt);
static bool lazyagg_analyze_childquery(Query* parentParse, Query* childParse, lazyagg_query_context* parentContext);
/* ---------------------------------------------------------- */
/* Common module : begin */
/* ---------------------------------------------------------- */
/*
* get_real_rte_varno_attno
* get the range table's real varno and varattno if there are JOIN typed RTE
*
* @param (in) parse:
* query tree
* @param (in & out) varno:
* (in) varno of Var used
* (out) the real varno or the middle result
* @param (in & out) varattno:
* (in) varattno of Var used
* (out) the real varattno or the middle result
* @param (in) targetVarno:
* the target varno we need to find, search into CoalesceExpr only when target varno is set
*
* @return:
* return true if this <varno, varattno> touchs CoalesceExpr, which means this var is used in join clause
*/
bool get_real_rte_varno_attno(Query* parse, Index* varno, AttrNumber* varattno, const Index targetVarno)
{
RangeTblEntry* rte = (RangeTblEntry*)list_nth(parse->rtable, *varno - 1);
if (rte->rtekind == RTE_JOIN) {
if (*varattno == 0) {
return false;
} else if (*varattno < 0) {
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
(errmsg("Join range table do not have system column."))));
}
Node* node = (Node*)list_nth(rte->joinaliasvars, *varattno - 1);
if (IsA(node, Var)) {
Var* v = (Var*)node;
*varno = v->varno;
*varattno = v->varattno;
return get_real_rte_varno_attno(parse, varno, varattno, targetVarno);
} else if (IsA(node, CoalesceExpr)) {
if (InvalidOid != targetVarno) {
CoalesceExpr* c = (CoalesceExpr*)node;
ListCell* lc = NULL;
foreach (lc, c->args) {
Node* cnode = (Node*)lfirst(lc);
AssertEreport(IsA(cnode, Var),
MOD_OPT_REWRITE,
"CoalesceExpr's args should be Var in get_real_rte_varno_attno");
Var* v = (Var*)cnode;
Index cvarno = v->varno;
AttrNumber cvarattno = v->varattno;
(void)get_real_rte_varno_attno(parse, &cvarno, &cvarattno, targetVarno);
if (targetVarno == cvarno) {
*varno = cvarno;
*varattno = cvarattno;
break;
}
}
}
return true;
} else {
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
(errmsg("Invalid join alias var"))));
}
}
return false;
}
/*
* get_real_rte_varno_attno_or_node
* Similar with get_real_rte_varno_attno, but if we can not find varno, we will return the node
*/
Node* get_real_rte_varno_attno_or_node(Query* parse, Index* varno, AttrNumber* varattno)
{
RangeTblEntry* rte = (RangeTblEntry*)list_nth(parse->rtable, *varno - 1);
if (rte->rtekind == RTE_JOIN) {
if (*varattno == 0) {
return NULL;
} else if (*varattno < 0) {
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
(errmsg("Join range table do not have system column."))));
}
Node* node = (Node*)list_nth(rte->joinaliasvars, *varattno - 1);
if (IsA(node, Var)) {
Var* v = (Var*)node;
*varno = v->varno;
*varattno = v->varattno;
return get_real_rte_varno_attno_or_node(parse, varno, varattno);
} else {
return node;
}
}
return NULL;
}
/*
* is_join_inner_side
* get join side of a target RTE recursively
*
* @param (in) fromNode:
* a FROM node to be searched
* @param (in) targetRTEIndex:
* target RTE's index
* @param (in) isParentInnerSide
* fromNode's parent's join side is inner or not
* @param (in & out) isFound:
* the target range table has benn found
*
* @return:
* whether the target range table (identified by targetRTEIndex) is in join inner side, true means in inner side
*/
bool is_join_inner_side(const Node* fromNode, const Index targetRTEIndex, const bool isParentInnerSide, bool* isFound)
{
if ((*isFound)) {
return true;
}
/*
* if from node is a JoinExpr, recursive search in it's two side
* if from node is a FromExpr, Traverse and recursive search it's fromList
* if from node is a RangeTblRef, return true if target RTE is found in inner side
*/
if (IsA(fromNode, JoinExpr)) {
bool lres = false;
bool rres = false;
JoinExpr* je = (JoinExpr*)fromNode;
switch (je->jointype) {
case JOIN_INNER:
case JOIN_SEMI:
case JOIN_ANTI:
lres = is_join_inner_side(je->larg, targetRTEIndex, isParentInnerSide, isFound);
rres = is_join_inner_side(je->rarg, targetRTEIndex, isParentInnerSide, isFound);
break;
case JOIN_LEFT:
case JOIN_LEFT_ANTI_FULL:
lres = is_join_inner_side(je->larg, targetRTEIndex, isParentInnerSide, isFound);
rres = is_join_inner_side(je->rarg, targetRTEIndex, true, isFound);
break;
case JOIN_RIGHT:
case JOIN_RIGHT_ANTI_FULL:
lres = is_join_inner_side(je->larg, targetRTEIndex, true, isFound);
rres = is_join_inner_side(je->rarg, targetRTEIndex, isParentInnerSide, isFound);
break;
case JOIN_FULL:
lres = is_join_inner_side(je->larg, targetRTEIndex, true, isFound);
rres = is_join_inner_side(je->rarg, targetRTEIndex, true, isFound);
break;
/*
* By now, we haven't cover some other join types:
* (1) JOIN_UNIQUE_OUTER
* (2) JOIN_UNIQUE_INNER
* (3) JOIN_RIGHT_ANTI
* (4) ...
* These join types are included in default condition,
* and may need to be explored
*/
default:
AssertEreport(false, MOD_OPT_REWRITE, "other join types should not be handled in is_join_inner_side");
break;
}
return lres || rres;
} else if (IsA(fromNode, FromExpr)) {
FromExpr* fromExpr = (FromExpr*)fromNode;
bool res = false;
ListCell* lc = NULL;
foreach (lc, fromExpr->fromlist) {
Node* node = (Node*)lfirst(lc);
res = res || is_join_inner_side(node, targetRTEIndex, isParentInnerSide, isFound);
}
return res;
} else if (IsA(fromNode, RangeTblRef)) {
RangeTblRef* rtblr = (RangeTblRef*)fromNode;
AssertEreport(rtblr->rtindex > 0, MOD_OPT_REWRITE, "RangeTblRef node's index should > 0 in is_join_inner_side");
if ((int)targetRTEIndex == rtblr->rtindex && isParentInnerSide == true) {
/* the target child-query is in inner side */
*isFound = true;
return true;
} else {
return false;
}
} else {
ereport(WARNING, (errmodule(MOD_OPT), (errmsg("Invalid fromNode type %d", fromNode->type))));
AssertEreport(false, MOD_OPT_REWRITE, "Invalid fromNode type in is_join_inner_side");
return false;
}
}
/* ------------------------------------------------------------ */
/* Common module : end */
/* ------------------------------------------------------------ */
/*
* --------------------
* Logic of Lazy Agg
* --------------------
*/
/*
* lazyagg_free_querycontext
* free a query context
*
* @param (in) context
* context to be free
*/
static void lazyagg_free_querycontext(lazyagg_query_context* context)
{
if (context == NULL) {
return;
}
bms_free_ext(context->bms_sum);
bms_free_ext(context->bms_min);
bms_free_ext(context->bms_max);
bms_free_ext(context->bms_count);
bms_free_ext(context->bms_countStar);
bms_free_ext(context->bms_joinWhereGroupby);
bms_free_ext(context->bms_all);
if (context->sumAggFuncs != NIL) {
list_free_ext(context->sumAggFuncs);
context->sumAggFuncs = NIL;
}
if (context->countParam != NULL) {
pfree_ext(context->countParam);
context->countParam = NULL;
}
if (context->countParamBestType != NULL) {
pfree_ext(context->countParamBestType);
context->countParamBestType = NULL;
}
pfree_ext(context);
}
/*
* lazyagg_clear_childcontext
* clear child-query's SUM/MIN/MAX/COUNT/COUNTSTAR bms after it is delayed rewriten with child-child-query
* and also clear countParam
* this is mainly for re-set of childContext
*
* @param (in) childContext:
* context information of child-query
*/
static void lazyagg_clear_childcontext(lazyagg_query_context* childContext)
{
if (childContext == NULL) {
return;
}
bms_free_ext(childContext->bms_sum);
bms_free_ext(childContext->bms_min);
bms_free_ext(childContext->bms_max);
bms_free_ext(childContext->bms_count);
bms_free_ext(childContext->bms_countStar);
if (childContext->countParam != NULL) {
pfree_ext(childContext->countParam);
}
childContext->bms_sum = NULL;
childContext->bms_min = NULL;
childContext->bms_max = NULL;
childContext->bms_count = NULL;
childContext->bms_countStar = NULL;
childContext->countParam = NULL;
childContext->lenOfCountParam = 0;
}
/*
* lazyagg_init_parentquery_countparam
* init 'parentContext''s countParam and countParamOid from 'from' with a deep copy
*
* @param (out) parentContext:
* instence to be init, usually a parent-query context
* @param (in) from:
* resource context, usually a child-query context, first child-query of the parent-query we meet
*/
static void lazyagg_init_parentquery_countparam(lazyagg_query_context* parentContext, lazyagg_query_context* from)
{
/* remove old data */
if (parentContext->countParam != NULL) {
pfree_ext(parentContext->countParam);
parentContext->countParam = NULL;
pfree_ext(parentContext->countParamBestType);
parentContext->countParamBestType = NULL;
}
parentContext->lenOfCountParam = from->lenOfCountParam;
/*
* if no COUNT found in child-query, lenOfCountParam will be 0
* if COUNT was found in child-query, lenOfCountParam will be (list_length(child-query's targetList) + 1)
*/
if (parentContext->lenOfCountParam == 0) {
return;
}
parentContext->countParam = (Expr**)palloc0(sizeof(Expr*) * parentContext->lenOfCountParam);
parentContext->countParamBestType = (Oid*)palloc0(sizeof(Oid) * parentContext->lenOfCountParam);
/*
* for each attrbute, copy countParam directly
* if current attribute's countParam is not NULL, get it's type
* otherwise, mark it as InvalidOid
*/
for (AttrNumber attno = 1; attno < parentContext->lenOfCountParam; ++attno) {
parentContext->countParam[attno] = from->countParam[attno];
if (parentContext->countParam[attno] != NULL) {
parentContext->countParamBestType[attno] = exprType((Node*)(parentContext->countParam[attno]));
} else {
parentContext->countParamBestType[attno] = InvalidOid;
}
}
}
/*
* lazyagg_update_childquery_countparam
* mainly used to update countParam of child-query
* countParamOid is not used by child-query
*
* @param (in) childParse:
* query tree of child-query
* @param (in) aggref:
* a Aggref appeared in target list of child-query, which type is COUNT
* @param (in) attno:
* the attribute index in target list, start with 1
* @param (out) childContext:
* context of child-query
*/
static void lazyagg_update_childquery_countparam(
Query* childParse, Aggref* aggref, const AttrNumber attno, lazyagg_query_context* childContext)
{
AssertEreport(list_length(aggref->args) == 1,
MOD_OPT_REWRITE,
"Aggref should have 1 arg in lazyagg_update_childquery_countparam");
AssertEreport(IsA(linitial(aggref->args), TargetEntry),
MOD_OPT_REWRITE,
"Aggref should have 1 TargetEntry arg in lazyagg_update_childquery_countparam");
TargetEntry* te = (TargetEntry*)linitial(aggref->args);
if (childContext->countParam == NULL) {
childContext->lenOfCountParam = list_length(childParse->targetList) + 1;
childContext->countParam = (Expr**)palloc0(sizeof(Expr*) * (childContext->lenOfCountParam));
}
childContext->countParam[attno] = te->expr;
}
/*
* lazyagg_is_countparam_compatible
* for child-query(s) which are not the first one we meet,
* check it's compatibility with previous child-query(s)
*
* @param (in) parentContext:
* context of parent-query, where stored countParam and countParamOid information of previous child-query(s)
* @param (in) childContext:
* context of child-query
*
* @return:
* whether current child-query is compatible with previous child-query(s), true means compatible
*/
static bool lazyagg_is_countparam_compatible(lazyagg_query_context* parentContext, lazyagg_query_context* childContext)
{
/*
* only two value lenOfCountParam could be:
* (1) 0 means no COUNT,
* for parentContext means no COUNT in previous child-query,
* for childContext means no COUNT in current child-query
* (2) (list_length(child-query's targetList) + 1) means COUNT is found
*/
if (parentContext->lenOfCountParam != childContext->lenOfCountParam) {
return false;
}
for (AttrNumber attno = 1; attno < parentContext->lenOfCountParam; ++attno) {
Expr* expr1 = parentContext->countParam[attno];
Expr* expr2 = childContext->countParam[attno];
/* both of them are NULL means this column has no COUNT */
if (NULL == expr1 && NULL == expr2) {
continue;
}
/* one of them is NULL, but another is not, NOT compatible */
if (NULL == expr1 || NULL == expr2) {
return false;
}
/* get the best type Oid and better expr */
Node* which_expr = NULL;
Oid selectedOid = select_common_type(NULL, list_make2(expr1, expr2), NULL, &which_expr);
if (InvalidOid == selectedOid) {
return false;
} else {
parentContext->countParam[attno] = (Expr*)which_expr;
parentContext->countParamBestType[attno] = selectedOid;
}
}
return true;
}
/*
* lazyAgg_get_aggTrantype
* get agg trantype oid when given a agg fn oid
* read it from system catalog table pg_aggregate
*
* @param (in) aggfnoid:
* agg function oid of Aggref, a.k.a., pg_proc Oid of the aggregate (Aggref->aggfnoid)
*
* @return:
* type Oid of transition results (Aggref->aggtrantype)
*/
static Oid lazyagg_get_aggtrantype(Oid aggfnoid)
{
Oid aggTrantype;
HeapTuple tup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(aggfnoid));
if (!HeapTupleIsValid(tup)) {
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for function %u", aggfnoid)));
}
Form_pg_aggregate aggr = (Form_pg_aggregate)GETSTRUCT(tup);
aggTrantype = aggr->aggtranstype;
ReleaseSysCache(tup);
return aggTrantype;
}
/*
* lazyagg_pull_vars_attno
* get all attno of var(s) which appear in node and belong to range table of rteIndex
*
* @param (in) node:
* node to be pull
* @param (in) rteIndex:
* rte index of target's range table
* @parse (in) parse:
* the query tree the node belong to
*
* @return:
* bitmap set of attno(s)
*/
static Bitmapset* lazyagg_pull_vars_attno(Node* node, const Index rteIndex, Query* parse)
{
/* get all vars of the same level */
List* allVars = pull_qual_vars(node, 0, QTW_IGNORE_JOINALIASES);
/* bms to be return */
Bitmapset* bms_varAttnum = NULL;
/* find var(s) of target RTE */
ListCell* lc = NULL;
foreach (lc, allVars) {
Var* v = (Var*)lfirst(lc);
Index varnoReal = v->varno;
AttrNumber varattnoReal = v->varattno;
(void)get_real_rte_varno_attno(parse, &varnoReal, &varattnoReal, rteIndex);
if (rteIndex == varnoReal) {
bms_varAttnum = bms_add_member(bms_varAttnum, varattnoReal);
}
}
return bms_varAttnum;
}
/*
* lazyagg_set_parentcontext_with_child
* Extract JOIN, WHERE, GROUP BY columns in target RTE
*
* @param (in) parentParse:
* parent-query tree
* @param (in) childQueryRTEIndex:
* target RTE
* @param (in) parentContext:
* context information of parent-query
*/
static void lazyagg_set_parentcontext_with_child(
Query* parentParse, Index childQueryRTEIndex, lazyagg_query_context* parentContext)
{
/* var(s) attno of JOIN, WHERE */
Bitmapset* bms_varAttNoInJointree =
lazyagg_pull_vars_attno((Node*)(parentParse->jointree), childQueryRTEIndex, parentParse);
/* var(s) attno of GROUP BY */
List* groupByExprs = get_sortgrouplist_exprs(parentParse->groupClause, parentParse->targetList);
Bitmapset* bms_varAttnoInGroupBy = lazyagg_pull_vars_attno((Node*)groupByExprs, childQueryRTEIndex, parentParse);
if (NIL != groupByExprs) {
list_free_ext(groupByExprs);
}
/* var(s) attno of JOIN, WHERE, GROUP BY */
parentContext->bms_joinWhereGroupby = bms_union(bms_varAttNoInJointree, bms_varAttnoInGroupBy);
/* bms of all attno of child-query used in parent-query */
parentContext->bms_all = lazyagg_pull_vars_attno((Node*)parentParse, childQueryRTEIndex, parentParse);
/* join side */
bool isFound = false;
parentContext->isInnerSide = is_join_inner_side((Node*)parentParse->jointree, childQueryRTEIndex, false, &isFound);
}
/*
* lazyagg_get_agg_list
* Get all Aggref(s) and GroupingFunc(s) in a node
*
* @param (in) node:
* the target node
* @param (in/out) has_AggrefsOuterquery:
* if the target node contains agg references outer query, set
* has_AggrefsOuterquery be true.
*
* @return:
* a list contain Aggref(s) and GroupingFunc(s)
*/
static List* lazyagg_get_agg_list(Node* node, bool* has_AggrefsOuterquery)
{
if (node == NULL) {
return NIL;
}
/*
* Aggref in SubLink that reference outer query can not be rewrite when checking
* childquery. Because delete the groupclause of outer query will lead result
* incorret while rewrite the query.
*/
if (has_AggrefsOuterquery != NULL) { /* should not be NULL when check childquery */
ListCell* lc1 = NULL;
List* sublinks = pull_sublink(node);
foreach (lc1, sublinks) {
AssertEreport(IsA(lfirst(lc1), SubLink), MOD_OPT_REWRITE, "Sublink mismatch in lazyagg_get_agg_list");
SubLink* sublink = (SubLink*)lfirst(lc1);
if (contain_aggs_of_level_or_above(sublink->subselect, 1)) {
/*
* Once found agg references outer query, the child query must
* not be rewrite, so set the flag and return immediately.
*/
*has_AggrefsOuterquery = true;
return NULL;
}
}
}
List* aggList = NIL;
List* l =
pull_var_clause(node, PVC_INCLUDE_AGGREGATES, PVC_INCLUDE_PLACEHOLDERS, PVC_RECURSE_SPECIAL_EXPR, true, true);
/* pick Aggref(s) from the Var(s) list */
ListCell* lc2 = NULL;
foreach (lc2, l) {
Node* localNode = (Node*)lfirst(lc2);
if ((IsA(localNode, Aggref) && 0 == ((Aggref*)localNode)->agglevelsup) ||
(IsA(localNode, GroupingFunc) && 0 == ((GroupingFunc*)localNode)->agglevelsup)) {
aggList = lappend(aggList, localNode);
}
}
list_free_ext(l);
return aggList;
}
/*
* lazyagg_get_agg_type
* get agg function type enum by read agg name from system catalog pg_proc
*
* @param (in) aggref:
* target agg function, an Aggref
*
* @return:
* the agg type cluster, more detail in LAZYAGG_AGGTYPE
*/
static LAZYAGG_AGGTYPE lazyagg_get_agg_type(Aggref* aggref)
{
Oid aggfnoid = aggref->aggfnoid;
/* oid must be in range of system defined function */
if (aggfnoid >= FirstNormalObjectId) {
return LAZYAGG_OTHER;
}
HeapTuple tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(aggfnoid));
if (!HeapTupleIsValid(tup)) {
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for function %u", aggfnoid)));
}
Form_pg_proc proc = (Form_pg_proc)GETSTRUCT(tup);
NameData name = proc->proname;
int2 pronargs = proc->pronargs;
ReleaseSysCache(tup);
if (0 == strncmp(name.data, "sum", strlen("sum"))) {
return LAZYAGG_SUM;
} else if (0 == strncmp(name.data, "min", strlen("min"))) {
return LAZYAGG_MIN;
} else if (0 == strncmp(name.data, "max", strlen("max"))) {
return LAZYAGG_MAX;
} else if (0 == strncmp(name.data, "count", strlen("count"))) {
if (1 == pronargs) {
return LAZYAGG_COUNT;
} else if (0 == pronargs) {
return LAZYAGG_COUNTSTAR;
}
}
return LAZYAGG_OTHER;
}
/*
* lazyagg_check_delay_rewrite_compatibility
* Check wheather this child-query is compatible with other child-query with the shared parent-query.
* Use parentContext->bms_count, parentContext->bms_countStar,
* and parentContext->countParam to handle this compatibility check.
* parentContext->bms_count[InvalidOid] and parentContext->bms_countStar[InvalidOid] is used as a flag,
* set this bit when child-query(s) are not compatible, which will help futher check.
*
* @param (in) parentContext:
* context of parent-query
* @param (in) childContext:
* context of child-query
* @param (in) parentSumToCount:
* the bms to mark SUM(s) in parent-query have paired COUNT in current child-query
* @param (in) parentSumToCountStar:
* the bms to mark SUM(s) in parent-query have paired COUNT(*) in current child-query
*
* @return:
* whether delay rewrite is compatible. true means it is compatible
*/
static bool lazyagg_check_delay_rewrite_compatibility(lazyagg_query_context* parentContext,
lazyagg_query_context* childContext, Bitmapset* parentSumToCount, Bitmapset* parentSumToCountStar)
{
bool canDelayRewrite = false;
/*
* If parentContext->bms_count and parentContext->bms_countStar are NULL,
* means it is the first child-query of the parent-query we meet.
* If it's not the first one, check the compatibility with other previous child-query(s),
* previous information has been marked in parentContext->bms_count, parentContext->bms_countStar and
* parentContext->countParam
*/
if (parentContext->bms_count == NULL && parentContext->bms_countStar == NULL) {
/* this is the first child-query we meet, which share the same parent-query */
parentContext->bms_count = parentSumToCount;
parentContext->bms_countStar = parentSumToCountStar;
lazyagg_init_parentquery_countparam(parentContext, childContext);
canDelayRewrite = true;
} else if (!bms_equal(parentContext->bms_count, parentSumToCount) ||
!bms_equal(parentContext->bms_countStar, parentSumToCountStar) ||
!lazyagg_is_countparam_compatible(parentContext, childContext)) {
/*
* Not match the compatibility, this parent-query/child-querys pair should not be rewriten
* set the InvalidOid slot to mark this bms is invalid
*/
bms_add_member(parentContext->bms_count, InvalidOid);
bms_add_member(parentContext->bms_countStar, InvalidOid);
bms_free_ext(parentSumToCount);
bms_free_ext(parentSumToCountStar);
canDelayRewrite = false;
} else {
/* not the first one, but match with previous brother child-query(s), return it for further delay rewrite */
bms_free_ext(parentSumToCount);
bms_free_ext(parentSumToCountStar);
canDelayRewrite = true;
}
return canDelayRewrite;
}
/*
* lazyagg_check_childquery_feasibility
* (1) check wheather child-query is valid for Lazy Agg rule
* (2) check the feasibility of impact between parent-query and child-query
*
* @param (in) parentParse:
* parent-query tree
* @param (in) childParse:
* child-query tree
* @param (in) parentContext:
* context of parent-query
* @param (in) childContext:
* context of child-query
* @param (in) resetContextOnly:
* this call re-set context only
*
* @return:
* whether parent-query is valid for Lazy Agg rule, true means valid, false otherwise
*/
static bool lazyagg_check_childquery_feasibility(Query* parentParse, Query* childParse,
lazyagg_query_context* parentContext, lazyagg_query_context* childContext, bool resetContextOnly)
{
if (childParse->groupClause == NIL) {
/*
* ------ Rule child 1 ------
* child-query need to have GROUP BY
*/
return false;
}
/* Check other conditions */
if (childParse->hasWindowFuncs || childParse->hasDistinctOn || childParse->hasRecursive ||
childParse->groupingSets != NIL || childParse->havingQual != NULL || childParse->windowClause != NIL ||
childParse->distinctClause != NIL || childParse->limitOffset != NULL || childParse->limitCount != NULL ) {
/*
* ------ Rule child 4 ------
* No operators like window agg, distinct, recursive, AP function, having, offset, limit etc.
*/
return false;
}
childContext->isInnerSide = false;
childContext->sumAggFuncs = NIL;
childContext->lenOfCountParam = 0;
Index tleIndex = 1;
ListCell* lc = NULL;
foreach (lc, childParse->targetList) {
if (!bms_is_member(tleIndex, parentContext->bms_all)) {
++tleIndex;
continue;
}
TargetEntry* te = (TargetEntry*)lfirst(lc);
bool has_AggrefsOuterquery = false;
List* te_AggrefList = lazyagg_get_agg_list((Node*)te, &has_AggrefsOuterquery);
if (has_AggrefsOuterquery)
return false;
if (NIL != te_AggrefList) {
if (!resetContextOnly) {
/* there need to be only one Aggref and no other expr(s) around Aggref */
if (!(1 == list_length(te_AggrefList) && IsA(te->expr, Aggref))) {
/*
* ------ Rule child 3 ------
* child-query's agg function is target output directly with out anything around it
*/
list_free_ext(te_AggrefList);
return false;
}
} else {
/*
* This is a reset process of child-query, these has been checked
* BUT==> If child-query and child-child-query is delayed rewriten,
* a type converter may be added around Aggref of child-query
*/
AssertEreport(1 == list_length(te_AggrefList),
MOD_OPT_REWRITE,
"there should be only one Aggref in lazyagg_check_childquery_feasibility");
AssertEreport(
IsA(te->expr, Aggref) ||
(IsA(te->expr, FuncExpr) && IsA((Node*)linitial(((FuncExpr*)te->expr)->args), Aggref)) ||
(IsA(te->expr, RelabelType) && IsA(((RelabelType*)te->expr)->arg, Aggref)),
MOD_OPT_REWRITE,
"Invalid type of TargetEntry node's expr in lazyagg_check_childquery_feasibility");
}
Aggref* aggref = (Aggref*)linitial(te_AggrefList);
/* Get the agg function */
LAZYAGG_AGGTYPE aggType = lazyagg_get_agg_type(aggref);
if (LAZYAGG_OTHER == aggType) {
/*
* ------ Rule child 2 ------
* agg function only support SUM/MIN/MAX/COUNT,
*/
list_free_ext(te_AggrefList);
return false;
}
/* There need to be no DINSTINCT in agg function */
if (aggref->aggdistinct != NIL) {
/*
* ------ Rule child 4 ------
* No operators like window agg, distinct, recursive, AP function, having, offset, limit etc.
* (no DISTINCT here)
*/
list_free_ext(te_AggrefList);
return false;
}
if (bms_is_member(tleIndex, parentContext->bms_joinWhereGroupby)) {
/*
* ------ Rule joint 2 ------
* agg column in child-query needs to have no intersect with JOIN/WHERE/GROUP BY in parent-query
*/
list_free_ext(te_AggrefList);
return false;
}
if ((LAZYAGG_COUNT == aggType || LAZYAGG_COUNTSTAR == aggType) &&
(parentContext->isInnerSide || parentParse->groupClause == NIL)) {
/*
* ------ Rule joint 3 ------
* COUNT typed child-query should not in inner side of (LEFT/RIGHT/FULL) JOIN
* ------ Rule joint 4 ------
* COUNT typed child-query's parent-query should have GROUP BY
*/
return false;
}
/* mark agg functions */
switch (aggType) {
case LAZYAGG_SUM:
childContext->bms_sum = bms_add_member(childContext->bms_sum, tleIndex);
break;
case LAZYAGG_MIN:
childContext->bms_min = bms_add_member(childContext->bms_min, tleIndex);
break;
case LAZYAGG_MAX:
childContext->bms_max = bms_add_member(childContext->bms_max, tleIndex);
break;
case LAZYAGG_COUNT:
childContext->bms_count = bms_add_member(childContext->bms_count, tleIndex);
lazyagg_update_childquery_countparam(childParse, aggref, tleIndex, childContext);
break;
case LAZYAGG_COUNTSTAR:
childContext->bms_countStar = bms_add_member(childContext->bms_countStar, tleIndex);
break;
default:
break;
}
}
++tleIndex;
}
if ((!bms_is_subset(parentContext->bms_sum,
bms_union(childContext->bms_sum, bms_union(childContext->bms_count, childContext->bms_countStar)))) ||
(!bms_is_subset(parentContext->bms_min, childContext->bms_min)) ||
(!bms_is_subset(parentContext->bms_max, childContext->bms_max))) {
/*
* ------ Rule joint 1 ------
* agg function match each other and agg function param in parent-query is subset of agg function column in
* child-query
*/
return false;
}
return true;
}
/*
* lazyagg_check_parentquery_feasibility
* check wheather parent-query is valid for Lazy Agg rule
*
* @param (in) parentParse:
* parent-query tree
* @param (in) targetRTEIndex:
* 0: parent-query does not have aggs in output columns
* other number: index(+1) of Query->rtable, starts from 1
* @param (in) parentContext:
* part of context information of parent-query
*
* @return:
* whether parent-query is valid for lazy agg rule, true means valid, false otherwise
*/
static bool lazyagg_check_parentquery_feasibility(
Query* parentParse, Index* targetRTEIndex, lazyagg_query_context* parentContext)
{
*targetRTEIndex = 0;
ListCell* lc = NULL;
if (!(parentParse->hasAggs || parentParse->groupClause != NIL)) {
/*
* ------ Rule parent 1 ------
* There need to be agg function(s) or GROUP BY in parent-query
*/
return false;
}
/* check volatile in JOIN and WHERE of parent-query */
bool isJoinHasVolatile = contain_volatile_functions((Node*)parentParse->jointree);
/* check volatile in GROUP BY of parent-query */
List* groupByExprs = get_sortgrouplist_exprs(parentParse->groupClause, parentParse->targetList);
bool isGroupByHasVolatile = contain_volatile_functions((Node*)groupByExprs);
if (NIL != groupByExprs) {
list_free_ext(groupByExprs);
}
/* volatile checking */
if (isJoinHasVolatile || isGroupByHasVolatile) {
/*
* ------ Rule parent 5 -------
* no volatile in JOIN and WHERE of parent-query
* ------ Rule parent 6 -------
* no volatile in GROUP BY of parent-query
*/
return false;
}
/* Get all Aggref(s) in parent-query's target list */
List* aggrefList = lazyagg_get_agg_list((Node*)(parentParse->targetList));
/* Get all Aggref(s) in parent-query's having qual */
List* aggrefList_having = lazyagg_get_agg_list((Node*)(parentParse->havingQual));
aggrefList = list_concat(aggrefList, aggrefList_having);
/* the return value */
bool isOK = true;
parentContext->isInnerSide = false;
parentContext->sumAggFuncs = NIL;
parentContext->lenOfCountParam = 0;
/* If there are agg functions in target list of parent-query, traverse them */
foreach (lc, aggrefList) {
if (!IsA(lfirst(lc), Aggref)) {
/*
* ------ Rule parent 9 -------
* parent-query should not contain 'grouping()' function
*/
isOK = false;
/* free memory and return */
break;
}
Aggref* aggref = (Aggref*)lfirst(lc);
/* Get the agg function of Aggref */
LAZYAGG_AGGTYPE aggType = lazyagg_get_agg_type(aggref);
/* Check the agg function */
if (LAZYAGG_SUM != aggType && LAZYAGG_MIN != aggType && LAZYAGG_MAX != aggType) {
/*
* ------ Rule parent 2 -------
* parent-query's agg function need to be sum/min/max
*/
isOK = false;
/* free memory and return */
break;
}
/* Check is there a DISTINCT in agg function */
if (aggref->aggdistinct != NIL) {
/*
* ------ Rule parent 3 ------
* parent-query's agg function can only contain a Var param
* (no distinct in agg function)
*/
isOK = false;
/* free memory and return */
break;
}
/* The agg function need to contain a Var only */
List* aggParam = aggref->args;
if (!(1 == list_length(aggParam) && IsA(linitial(aggParam), TargetEntry) &&
IsA(((TargetEntry*)linitial(aggParam))->expr, Var))) {
/*
* ------ Rule parent 3 ------
* parent-query's agg function can only contain a Var param
* (only a Var as param)
*/
isOK = false;
/* free memory and return */
break;
} else {
/* get the Var paramter */
Var* var = (Var*)(((TargetEntry*)linitial(aggParam))->expr);
Index varno = var->varno;
AttrNumber varattno = var->varattno;
bool isInJoinClause = get_real_rte_varno_attno(parentParse, &varno, &varattno);
if (isInJoinClause) {
/*
* ------ Rule parent 8 -------
* agg column in child-query has no intersect with JOIN in parent-query (part of FULL JOIN detected
* here)
*/
isOK = false;
/* free memory and return */
break;
}
/* Check are there system column */
if (varattno <= 0) {
/*
* ------ Rule parent 7 -------
* Do not cover conditions of attnum <= 0
*/
isOK = false;
/* free memory and return */
break;
}
if (var->varlevelsup != 0) {
/* only care about var(s) come from parent-query's level */
continue;
}
if (*targetRTEIndex == 0) {
/* the first Var paramter we meet in the Aggref list */
*targetRTEIndex = varno;
} else if (*targetRTEIndex != varno) {
/*
* ------ Rule parent 4 -------
* parent-query's agg function parameter only come from a single table(child-query)
*/
isOK = false;
/* free memory and return */
break;
}
/* update parent-query context by agg function type */
if (LAZYAGG_SUM == aggType) {
parentContext->bms_sum = bms_add_member(parentContext->bms_sum, varattno);
parentContext->sumAggFuncs = list_append_unique(parentContext->sumAggFuncs, aggref);
} else if (LAZYAGG_MIN == aggType) {
parentContext->bms_min = bms_add_member(parentContext->bms_min, varattno);
} else if (LAZYAGG_MAX == aggType) {
parentContext->bms_max = bms_add_member(parentContext->bms_max, varattno);
}
}
}
if (aggrefList != NIL) {
list_free_ext(aggrefList);
}
return isOK;
}
/*
* lazyagg_rewrite_setop_stmt
* recursivly modify SetOperationStmt
*
* @param (in) varattno:
* the target attno
* @param (in) parentContext:
* context of parent-query
* @param (in) setOpStmt:
* SetOperationStmt to recursivly modify
*/
static void lazyagg_rewrite_setop_stmt(
const AttrNumber varattno, lazyagg_query_context* parentContext, SetOperationStmt* setOpStmt)
{
/* rewrite parent node of setOpStmt */
AttrNumber i = 1;
ListCell* lc = NULL;
foreach (lc, setOpStmt->colTypes) {
if (varattno == i) {
lc->data.oid_value = parentContext->countParamBestType[varattno];
break;
}
++i;
}
if (IsA(setOpStmt->larg, SetOperationStmt)) {
lazyagg_rewrite_setop_stmt(varattno, parentContext, (SetOperationStmt*)(setOpStmt->larg));
}
if (IsA(setOpStmt->rarg, SetOperationStmt)) {
lazyagg_rewrite_setop_stmt(varattno, parentContext, (SetOperationStmt*)(setOpStmt->rarg));
}
}
/*
* lazyagg_rewrite_setop
* in delay rewrite and child-query in setop, setOp-query need to be rewrited on it's targetList and setOperations
*
* @param (in) parentParse:
* query tree of parent-query
* @param (in) varno:
* the RTE index of setOp-query in parent-query
* @param (in) varattno:
* the attno of origin COUNT column in setOp-query
* @param (in) parentContext:
* context of parent-query
*/
static void lazyagg_rewrite_setop(
Query* parentParse, const Index varno, const AttrNumber varattno, lazyagg_query_context* parentContext)
{
RangeTblEntry* rte = (RangeTblEntry*)list_nth(parentParse->rtable, varno - 1);
AssertEreport(rte->rtekind == RTE_SUBQUERY,
MOD_OPT_REWRITE,
"RangeTblEntry node should be kind of SUBQUERY in lazyagg_rewrite_setop");
Query* setOpParse = rte->subquery;
if (setOpParse->setOperations == NULL) {
return;
}
/* targetList */
TargetEntry* te = (TargetEntry*)list_nth(setOpParse->targetList, varattno - 1);
AssertEreport(
IsA(te->expr, Var), MOD_OPT_REWRITE, "TargetEntry node's expr should be Var in lazyagg_rewrite_setop");
Var* v = (Var*)(te->expr);
v->vartype = parentContext->countParamBestType[varattno];
/* setOperations */
SetOperationStmt* setOpStmt = (SetOperationStmt*)(setOpParse->setOperations);
lazyagg_rewrite_setop_stmt(varattno, parentContext, setOpStmt);
}
/*
* lazyagg_rewrite_join_aliasvars
* change types for join range tables when there are tables join with SUM(COUNT) sub-query
*
* @param (in) parentParse:
* query tree of parent-query
* @param (in) varno:
* the RTE index of join in parent-query
* @param (in) varattno:
* the attno of origin SUM's param column in join
* @param (in) vartype:
* origin type of COUNT's param, we will use it as column's type after we rewriten SUM(COUNT)
*/
static void lazyagg_rewrite_join_aliasvars(Query* parentParse, Index varno, AttrNumber varattno, Oid vartype)
{
RangeTblEntry* rte = (RangeTblEntry*)list_nth(parentParse->rtable, varno - 1);
if (rte->rtekind == RTE_JOIN) {
if (varattno == 0) {
return;
} else if (varattno < 0) {
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
(errmsg("Join range table do not have system column."))));
}
Node* node = (Node*)list_nth(rte->joinaliasvars, varattno - 1);
if (IsA(node, Var)) {
Var* v = (Var*)node;
v->vartype = vartype;
return lazyagg_rewrite_join_aliasvars(parentParse, v->varno, v->varattno, vartype);
} else if (IsA(node, CoalesceExpr)) {
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
(errmsg("Invalid agg param which used in a join clause"))));
} else {
ereport(ERROR,
(errmodule(MOD_OPT), errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), (errmsg("Invalid join alias var"))));
}
}
}
/*
* lazyagg_rewrite_childquery
* rewrite a child-query
*
* @param (in) childParse:
* child-query tree
* @param (in) parentContext:
* context of parent-query
* @param (in) isCheck:
* a flag to identify this call is a rewritability checking or not
*
* @return:
* whether this child-query is rewritable and rewrited, true means is rewritable and rewrited, false otherwise
*/
static bool lazyagg_rewrite_childquery(Query* childParse, lazyagg_query_context* parentContext, bool isCheck)
{
/* Check wheather this child-query is rewritable */
if (!isCheck) {
bool isOK = lazyagg_rewrite_childquery(childParse, parentContext, true);
if (!isOK) {
return false;
}
}
/* Traverse target list and find Aggref(s) */
Index tleIndex = 1;
ListCell* lc = NULL;
foreach (lc, childParse->targetList) {
TargetEntry* te = (TargetEntry*)lfirst(lc);
Oid toType = exprType((Node*)te->expr);
/* Used target */
if (!bms_is_member(tleIndex, parentContext->bms_all)) {
if (!isCheck) {
te->expr = (Expr*)makeNullConst(toType, -1, InvalidOid);
}
++tleIndex;
continue;
}
/* Node pointer, used to find Aggref */
Node* node = (Node*)te->expr;
/* Just for middle layer of sum(sum(count)) which may have type convert around Aggref */
if (IsA(node, FuncExpr)) {
FuncExpr* funcExpr = (FuncExpr*)(node);
/*
* For normal case, skip non-type-convert FuncExpr.
*
* We can safely do this, because there are only two conditions we could meet FuncExpr here:
* (1) type convert around Aggref in middle layer of sum(sum(count)) cases
* (2) normal function in target list without Aggref
*/
if (NULL == funcExpr->args) {
++tleIndex;
continue;
}
node = (Node*)linitial(funcExpr->args);
} else if (IsA(node, RelabelType)) {
RelabelType* relabelType = (RelabelType*)(node);
node = (Node*)(relabelType->arg);
}
/* It should be an Aggref now, or it is not an agg function column */
if (IsA(node, Aggref)) {
Aggref* aggref = (Aggref*)(node);
/*
* If there is no args in Aggref->args, means agg is COUNT(*)
* If there is only one args in Aggref->args, means other agg functions
*/
if (0 == list_length(aggref->args)) {
if (!isCheck) {
/* Change COUNT(*) to a const node */
Const* cnst = makeConst(INT8OID, -1, InvalidOid, sizeof(int64), Int64GetDatum(1), false, true);
te->expr = (Expr*)cnst;
}
} else if (1 == list_length(aggref->args)) {
/*
* Link te_inner->expr to te->expr, type convert needed
* Extract it here
*/
TargetEntry* te_inner = (TargetEntry*)linitial(aggref->args);
Oid fromType = exprType((Node*)te_inner->expr);
/* Convert data type if necessary */
if (fromType == toType) {
if (!isCheck) {
te->expr = te_inner->expr;
}
} else {
if (LAZYAGG_COUNT == lazyagg_get_agg_type(aggref)) {
toType = parentContext->countParamBestType[tleIndex];
}
/* data type convert */
if (isCheck) {
if (!can_coerce_type(1, &fromType, &toType, COERCION_IMPLICIT)) {
return false;
}
} else {
te->expr = (Expr*)coerce_to_target_type(NULL,
(Node*)te_inner->expr,
fromType,
toType,
-1,
COERCION_IMPLICIT,
COERCE_IMPLICIT_CAST,
aggref->location);
}
}
} else {
AssertEreport(false,
MOD_OPT_REWRITE,
"It should be an Aggref or not an agg function column in lazyagg_rewrite_childquery");
}
}
++tleIndex;
}
if (!isCheck) {
/* Modify groupClause and hasAggs of child-query */
childParse->groupClause = NIL;
childParse->hasAggs = false;
}
return true;
}
/*
* lazyagg_rewrite_delayed_query
* rewrite delayed query, including delayed child-query(s) and their parent-query
*
* @param (in) parentParse:
* parent-query tree
* @param (in) delayedChildQueryList:
* delayed child-query tree list
* @param (in) parentContext:
* context information of parent-query
* @param (in) isCheck:
* true means this function is used to check wheather delayed query is rewritable, mainly concern on type convert
*
* @return:
* whether this delay rewrite is rewritable and rewrited, true means is rewritable and rewrited, false otherwise
*/
static bool lazyagg_rewrite_delayed_query(
Query* parentParse, List* delayedChildQueryList, lazyagg_query_context* parentContext, bool isCheck)
{
AssertEreport(!bms_is_empty(bms_union(parentContext->bms_count, parentContext->bms_countStar)),
MOD_OPT_REWRITE,
"delayed child-query list should not be empty in lazyagg_rewrite_delayed_query");
AssertEreport(!bms_is_member(0, parentContext->bms_count),
MOD_OPT_REWRITE,
"there should be columns marked as has COUNT in child-query in parent-query's context in "
"lazyagg_rewrite_delayed_query");
AssertEreport(!bms_is_member(0, parentContext->bms_countStar),
MOD_OPT_REWRITE,
"the parent-query's context COUNT flag should not be marked as invalid in lazyagg_rewrite_delayed_query");
ListCell* lc = NULL;
if (!isCheck) {
/*
* Part 1 in this 'if'
* Check wheather child-query list and parent-query are rewritable
*/
foreach (lc, delayedChildQueryList) {
Query* childParse = (Query*)lfirst(lc);
bool isChildOK = lazyagg_rewrite_childquery(childParse, parentContext, true);
if (!isChildOK) {
return false;
}
}
bool isOK = lazyagg_rewrite_delayed_query(parentParse, delayedChildQueryList, parentContext, true);
if (!isOK) {
return false;
}
/*
* Part 2 in this 'if'
* rewrite child-query list
*/
foreach (lc, delayedChildQueryList) {
Query* childParse = (Query*)lfirst(lc);
(void)lazyagg_rewrite_childquery(childParse, parentContext);
}
}
/* parent-query's delay rewrited Aggref(s) need to be replaced */
List* replaceSrcList = NIL;
List* replaceDestList = NIL;
/* rewrite parent-query, switch SUM to COUNT */
foreach (lc, parentContext->sumAggFuncs) {
Aggref* aggref = (Aggref*)lfirst(lc);
List* aggParam = aggref->args;
Oid aggOutType = aggref->aggtype;
AssertEreport(1 == list_length(aggParam),
MOD_OPT_REWRITE,
"Aggref's args should be only one in lazyagg_rewrite_delayed_query");
AssertEreport(IsA(linitial(aggParam), TargetEntry) && IsA(((TargetEntry*)linitial(aggParam))->expr, Var),
MOD_OPT_REWRITE,
"Aggref's args should be one TargetEntry with Var expr in lazyagg_rewrite_delayed_query");
Var* var = (Var*)(((TargetEntry*)linitial(aggParam))->expr);
Index varno = var->varno;
AttrNumber varattno = var->varattno;
bool isInJoinClause = get_real_rte_varno_attno(parentParse, &varno, &varattno);
if (isInJoinClause) {
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
(errmsg("Column should NOT be in JOIN clause."))));
}
/*
* If current Aggref in SUM->COUNT flag,
* change SUM to COUNT, and add a type converter around count
*/
if (bms_is_member(varattno, parentContext->bms_count) ||
bms_is_member(varattno, parentContext->bms_countStar)) {
/* Output of COUNT is INT8OID, convert it to original SUM's output type */
Oid fromType = INT8OID;
Oid toType = aggOutType;
if (isCheck) {
if (fromType != toType && (!can_coerce_type(1, &fromType, &toType, COERCION_IMPLICIT))) {
return false;
} else {
continue;
}
}
/*
* A new copy to replace old Aggref(s) is need,
* because old Aggref may have multiple references in targetList tree
*/
Aggref* aggref_new = (Aggref*)copyObject(aggref);
/* Switch SUM to COUNT */
aggref_new->aggfnoid = ANYCOUNTOID;
aggref_new->aggtype = INT8OID;
aggref_new->aggtrantype = lazyagg_get_aggtrantype(aggref_new->aggfnoid);
aggref_new->location = -1;
if (bms_is_member(varattno, parentContext->bms_count)) {
/* rewrite set op, for SUM(COUNT) only */
lazyagg_rewrite_setop(parentParse, varno, varattno, parentContext);
/* change Var's vartype in new COUNT in parent-query, for SUM(COUNT) only */
Var* varOfSuper = (Var*)(((TargetEntry*)linitial(aggref_new->args))->expr);
Oid vartype = parentContext->countParamBestType[varattno];
varOfSuper->vartype = vartype;
/* rewrite join alias var's var type of join rte */
lazyagg_rewrite_join_aliasvars(parentParse, varOfSuper->varno, varOfSuper->varattno, vartype);
}
/* New node to be plugged in */
Node* newNode = (Node*)aggref_new;
/* Add a type converter if necessary */
if (fromType != toType) {
newNode = coerce_to_target_type(
NULL, newNode, fromType, toType, -1, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1);
}
/* generate replace lists */
replaceSrcList = lappend(replaceSrcList, aggref);
replaceDestList = lappend(replaceDestList, newNode);
}
}
/* Search target list and having qual, and replace Aggref(s) with new node */
if (replaceSrcList != NIL) {
parentParse->targetList = (List*)replace_node_clause(
(Node*)parentParse->targetList, (Node*)replaceSrcList, (Node*)replaceDestList, RNC_NONE);
parentParse->havingQual =
replace_node_clause(parentParse->havingQual, (Node*)replaceSrcList, (Node*)replaceDestList, RNC_NONE);
list_free_ext(replaceSrcList);
list_free_ext(replaceDestList);
}
return true;
}
/*
* lazyagg_analyze_single_childquery
* Process a child-query (which is not a setop)
*
* @param (in) parentParse:
* parent-query tree
* @param (in) childParse:
* child-query tree
* @param (in) parentContext:
* context information of parent-query
*
* @return:
* If this child-query need to be delay rewriten, return itself
*/
static Query* lazyagg_analyze_single_childquery(
Query* parentParse, Query* childParse, lazyagg_query_context* parentContext)
{
/* context information of child-query */
lazyagg_query_context* childContext = (lazyagg_query_context*)palloc0(sizeof(lazyagg_query_context));
/*
* (1) check the feasibility of child-query
* (2) check the feasibility of impact between parent-query and child-query
*/
bool isChildQueryOK = lazyagg_check_childquery_feasibility(parentParse, childParse, parentContext, childContext);
if (!isChildQueryOK) {
lazyagg_free_querycontext(childContext);
return NULL;
}
/* SUM need to be changed in parent-query because of COUNT(column) in child-query */
Bitmapset* parentSumToCount = bms_intersect(parentContext->bms_sum, childContext->bms_count);
/* SUM need to be changed in parent-query because of COUNT(*) in child-query */
Bitmapset* parentSumToCountStar = bms_intersect(parentContext->bms_sum, childContext->bms_countStar);
/*
* If there is no SUM(COUNT) pattern,
* the child-query need to be applyed to another Lazy Agg rule process first as a parent-query,
* before it is processed in current Lazy Agg rule process
*/
if (bms_is_empty(parentSumToCount) && bms_is_empty(parentSumToCountStar)) {
/* Search Lazy Agg between child-query and child-child-query recursively */
Query* childParse_new = lazyagg_main(childParse);
/*
* Check is the child-query and child-child-query is delayed rewriten:
* If lazy agg of child-query and child-child-query is delayed,
* means the child-query's agg func has been changed from sum to count.
* In this condition, childContext need to be updated.
*/
if (NULL != childParse_new) {
AssertEreport(childParse == childParse_new,
MOD_OPT_REWRITE,
"child-query mismatch in lazyagg_analyze_single_childquery");
/* Update SUM/COUNT bms of childContext, clear and set */
lazyagg_clear_childcontext(childContext);
(void)lazyagg_check_childquery_feasibility(parentParse, childParse, parentContext, childContext, true);
/* Update bms that identify delay rewrite property of parent-query layer */
parentSumToCount = bms_intersect(parentContext->bms_sum, childContext->bms_count);
parentSumToCountStar = bms_intersect(parentContext->bms_sum, childContext->bms_countStar);
}
/*
* If Lazy Agg of child-query and child-child-query is not delayed,
* or there is no SUM(COUNT) pattern (also may be an unused COUNT in child-query),
* rewrite the child-query as normal.
*/
if (bms_is_empty(parentSumToCount) && bms_is_empty(parentSumToCountStar)) {
/* Rewrite the child-query */
(void)lazyagg_rewrite_childquery(childParse, parentContext);
bms_free_ext(parentSumToCount);
bms_free_ext(parentSumToCountStar);
lazyagg_free_querycontext(childContext);
return NULL;
}
}
/* has pattern of SUM(COUNT) */
AssertEreport(!bms_is_empty(bms_union(parentSumToCount, parentSumToCountStar)),
MOD_OPT_REWRITE,
"Should has pattern of SUM(COUNT) in lazyagg_analyze_single_childquery");
/* check compatibility of delay rewrite */
bool isDelayRewriteOK =
lazyagg_check_delay_rewrite_compatibility(parentContext, childContext, parentSumToCount, parentSumToCountStar);
lazyagg_free_querycontext(childContext);
if (isDelayRewriteOK) {
return childParse;
} else {
return NULL;
}
}
/*
* lazyagg_search_setoperationstmt
* Process arg of UNION ALL
* Two conditions:
* (1) arg is a RangeTblRef,
* (2) arg is also a SetOperationStmt, process it recursively with lazyAgg_analyzeSetOpChildQuery
*
* @param (in) parentParse:
* parent-query tree
* @param (in) setOpParse:
* child-query tree (which is a setop query)
* @param (in) parentContext:
* context information of parent-query
* @param (in) setOpStmtArg:
* SetOperationStmt's arg of current layer (each setop operator has two arg, left and right, both use this function)
*
* @return:
* list of delayed child-query (which are all COUNT type)
*/
static List* lazyagg_search_setoperationstmt(
Query* parentParse, Query* setOpParse, lazyagg_query_context* parentContext, Node* setOpStmtArg)
{
List* delayedChildQueryList = NIL;
if (IsA(setOpStmtArg, RangeTblRef)) {
/* extract the child-query tree from setop query */
int rtindex = ((RangeTblRef*)setOpStmtArg)->rtindex;
RangeTblEntry* rte = (RangeTblEntry*)list_nth(setOpParse->rtable, rtindex - 1);
AssertEreport(rte->rtekind == RTE_SUBQUERY,
MOD_OPT_REWRITE,
"RangeTblEntry of child-query tree should be kind of SUBQUERY in lazyagg_search_setoperationstmt");
Query* childParse = rte->subquery;
/* Process child-query */
Query* delayedChildQuery = lazyagg_analyze_single_childquery(parentParse, childParse, parentContext);
/* Save delayed child-query list */
if (NULL != delayedChildQuery) {
delayedChildQueryList = lappend(delayedChildQueryList, delayedChildQuery);
}
} else if (IsA(setOpStmtArg, SetOperationStmt)) {
/* Process set operation, and save delayed child-query list */
delayedChildQueryList =
lazyagg_analyze_setop_childquery(parentParse, setOpParse, parentContext, (SetOperationStmt*)setOpStmtArg);
} else {
AssertEreport(false,
MOD_OPT_REWRITE,
"SetOperationStmt's arg should be a RangeTblRef or SetOperationStmt in lazyagg_search_setoperationstmt");
}
return delayedChildQueryList;
}
/*
* lazyagg_analyze_setop_childquery
* process setop child-query recursively on SetOperationStmt
*
* @param (in) parentParse:
* parent-query tree
* @param (in) setOpParse:
* child-query tree (which is a setop query)
* @param (in) parentContext:
* context information of parent-query
* @param (in) setOpStmt:
* SetOperationStmt of current layer (each setop operator has two child, left and right)
*
* @return:
* list of delayed child-query (which are all COUNT type)
*/
static List* lazyagg_analyze_setop_childquery(
Query* parentParse, Query* setOpParse, lazyagg_query_context* parentContext, SetOperationStmt* setOpStmt)
{
AssertEreport(setOpParse->setOperations != NULL,
MOD_OPT_REWRITE,
"set-operations of child-query tree should not be NULL in lazyagg_analyze_setop_childquery");
/* recursively process UNION ALL, and return others */
if (!(setOpStmt->op == SETOP_UNION && setOpStmt->all)) {
return NIL;
}
List* delayedChildQueryList = NIL;
List* delayedChildQueryList_left =
lazyagg_search_setoperationstmt(parentParse, setOpParse, parentContext, setOpStmt->larg);
List* delayedChildQueryList_right =
lazyagg_search_setoperationstmt(parentParse, setOpParse, parentContext, setOpStmt->rarg);
/*
* If one arg returns no delayed child-query, means this arg is invalid to Lazy Agg rule, or it has been rewriten
* without delay In this condition, the other child-query(s) of the same parent-query should not be delay rewriten,
* skip it
*/
if (NIL != delayedChildQueryList_left && NIL != delayedChildQueryList_right) {
delayedChildQueryList = list_concat(delayedChildQueryList, delayedChildQueryList_left);
delayedChildQueryList = list_concat(delayedChildQueryList, delayedChildQueryList_right);
return delayedChildQueryList;
} else {
if (NIL != delayedChildQueryList_left) {
list_free_ext(delayedChildQueryList_left);
}
if (NIL != delayedChildQueryList_right) {
list_free_ext(delayedChildQueryList_right);
}
return NIL;
}
}
/*
* lazyagg_analyze_childquery
* main entry to process child-query
* (1) this is a router for child-query to setop or agg-query
* (2) by the end of processing, rewrite delayed query if there it is
*
* @param (in) parentParse:
* parent-query tree
* @param (in) childParse:
* child-query tree
* @param (in) parentContext:
* context information of parent-query
*
* @return:
* whether this child-query is successfully delay rewrited query
*/
static bool lazyagg_analyze_childquery(Query* parentParse, Query* childParse, lazyagg_query_context* parentContext)
{
List* delayedChildQueryList = NIL;
bool isDelayedQuery = false;
/*
* Two kinds of child-query are take into concern:
* (1) set operation
* (2) child-query with agg
*/
if (childParse->setOperations != NULL) {
/* Process set operation, and save delayed child-query list */
delayedChildQueryList = lazyagg_analyze_setop_childquery(
parentParse, childParse, parentContext, ((SetOperationStmt*)childParse->setOperations));
} else if (childParse->groupClause != NIL) {
/* Process agg child-query */
Query* delayedChildQuery = lazyagg_analyze_single_childquery(parentParse, childParse, parentContext);
/* Save delayed child-query list */
if (delayedChildQuery != NULL) {
delayedChildQueryList = list_make1(delayedChildQuery);
}
} else {
return false;
}
/*
* rewrite delayed query
* (1) delayed child-query list is not empty
* (2) there are columns marked as has COUNT in child-query in parent-query's context
* (3) the parent-query's context COUNT flag is not marked as invalid
*/
if (delayedChildQueryList != NIL &&
(!bms_is_empty(bms_union(parentContext->bms_count, parentContext->bms_countStar))) &&
(!bms_is_member(0, parentContext->bms_count)) && (!bms_is_member(0, parentContext->bms_countStar))) {
/* rewrite delayed query */
bool isSuccessfullyRewriten = lazyagg_rewrite_delayed_query(parentParse, delayedChildQueryList, parentContext);
/* if rewriten is successful, mark parent-query as delayed */
isDelayedQuery = isSuccessfullyRewriten;
}
if (delayedChildQueryList != NIL) {
list_free_ext(delayedChildQueryList);
}
return isDelayedQuery;
}
/*
* lazyagg_main
* main entry for Lazy Agg rewrite rule
*
* @param (in) parse:
* the query tree need to be rewriten
*
* @return:
* NULL: the query is not valid for Lazy Agg rule, or only child-query is rewriten
* Query: the child-query is rewriten and the parent-query is also rewriten
* (SUM in parent-query has been changed to COUNT)
*/
Query* lazyagg_main(Query* parse)
{
/* There need to be only one child-query which can be applied to Lazy Agg, except for there is no agg func in
* parent-query */
Index targetRTEIndex = 0;
bool isDelayedQuery = false;
lazyagg_query_context* parentContext = (lazyagg_query_context*)palloc0(sizeof(lazyagg_query_context));
/* Check the feasibility of parent-query, get target child-query's RTE index, and init part of parent-query's
* context */
bool isParentQueryOK = lazyagg_check_parentquery_feasibility(parse, &targetRTEIndex, parentContext);
if (!isParentQueryOK) {
lazyagg_free_querycontext(parentContext);
return NULL;
}
/* Traverse RTE list of parent-query and find the target child-query */
Index rteIndex = 1;
ListCell* lc = NULL;
foreach (lc, parse->rtable) {
RangeTblEntry* rte = (RangeTblEntry*)lfirst(lc);
if (rte->rtekind == RTE_SUBQUERY) {
Query* childParse = rte->subquery;
/* child-query need to be a setop or has GROUP BY */
if ((childParse->setOperations == NULL) && (childParse->groupClause == NIL)) {
++rteIndex;
continue;
}
/*
* targetRTEIndex is '0', means no agg function in target list of parent-query, all child-query need to be
* rewriten. targetRTEIndex is not '0', means there is a single child-query in target list of parent-query,
* find it and rewrite it.
*/
if (targetRTEIndex == 0 || targetRTEIndex == rteIndex) {
/* Init second part of parentContext */
lazyagg_set_parentcontext_with_child(parse, rteIndex, parentContext);
/*
* Rewrite child-query,
* if the parent-query has also been rewriten, mark it as delayed query
*/
isDelayedQuery = lazyagg_analyze_childquery(parse, childParse, parentContext);
if (targetRTEIndex == 0) {
/*
* Revert second part of parent-query's context to rewrite other child-query(s)
* Do not need to revert bms_count and bms_countStar,
* because SUM(COUNT) is impossible when there are no agg function in parent-query
*/
bms_free_ext(parentContext->bms_joinWhereGroupby);
bms_free_ext(parentContext->bms_all);
parentContext->bms_joinWhereGroupby = NULL;
parentContext->bms_all = NULL;
parentContext->isInnerSide = false;
} else if (targetRTEIndex == rteIndex) {
/* No other child-query can be rewrite except the target child-query */
break;
}
}
}
++rteIndex;
}
lazyagg_free_querycontext(parentContext);
/*
* If parent-query is delayed rewriten (say sum(count) pattern)
* the query tree need to be returned for parent-query's parent-query
*/
return (isDelayedQuery) ? parse : NULL;
}
/* ------------------------------------------------------------ */
/* Lazy Agg : end */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* Reduce orderby : begin */
/* ------------------------------------------------------------ */
static void reduce_orderby_final(RangeTblEntry* rte, bool reduce);
static void reduce_orderby_recurse(Query* query, Node* jtnode, bool reduce);
/* Reduce orderby clause in subquery for join and setop */
/*
* reduce_orderby_recurse
* recurse check join node and reduce order by final for RangeTblRef.
*
* @param (in) query: the query tree for reduce orderby
* @param (in) jtnode: join node for RangeTblRef/FromExpr/JoinExpr/SetOperationStmt
*
* @return: void
*/
static void reduce_orderby_recurse(Query* query, Node* jtnode, bool reduce)
{
if (jtnode == NULL)
return;
if (IsA(jtnode, RangeTblRef)) {
int varno = ((RangeTblRef*)jtnode)->rtindex;
RangeTblEntry* rte = rt_fetch(varno, query->rtable);
/* Reduce orderby clause in subquery for join or from clause of more than one rte */
reduce_orderby_final(rte, reduce);
} else if (IsA(jtnode, FromExpr)) {
/* If there is ROWNUM, can not reduce orderby clause in subquery */
if (contain_rownum_walker(jtnode, NULL)) {
return;
}
FromExpr* f = (FromExpr*)jtnode;
ListCell* l = NULL;
bool flag = false;
if (1 == list_length(f->fromlist))
flag = reduce;
else
flag = true;
foreach (l, f->fromlist)
reduce_orderby_recurse(query, (Node*)lfirst(l), flag);
} else if (IsA(jtnode, JoinExpr)) {
JoinExpr* j = (JoinExpr*)jtnode;
if ((JOIN_INNER == j->jointype) || (JOIN_LEFT == j->jointype) || (JOIN_SEMI == j->jointype) ||
(JOIN_ANTI == j->jointype) || (JOIN_FULL == j->jointype) || (JOIN_RIGHT == j->jointype) ||
(JOIN_LEFT_ANTI_FULL == j->jointype) || (JOIN_RIGHT_ANTI_FULL == j->jointype)) {
reduce_orderby_recurse(query, j->larg, true);
reduce_orderby_recurse(query, j->rarg, true);
} else {
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized join type: %d", (int)j->jointype)));
}
} else if (IsA(jtnode, SetOperationStmt)) {
SetOperationStmt* op = (SetOperationStmt*)jtnode;
reduce_orderby_recurse(query, op->larg, true);
reduce_orderby_recurse(query, op->rarg, true);
}
return;
}
/*
* reduce_orderby_final
* Reduce order by clause for subquery for join and setop if the subquery have sortClause.
*
* @param (in) rte: the RTE of subquery for reduce orderby.
* @param (in) reduce: the flag identify if reduce the orderby clause or not.
*
* @return: void
*/
static void reduce_orderby_final(RangeTblEntry* rte, bool reduce)
{
/* Reduce orderby clause in subquery for join or from clause of more than one rte */
if (rte->rtekind == RTE_SUBQUERY) {
if (reduce && rte->subquery->sortClause && !rte->subquery->limitOffset && !rte->subquery->limitCount) {
pfree_ext(rte->subquery->sortClause);
rte->subquery->sortClause = NULL;
}
reduce_orderby(rte->subquery, reduce);
}
return;
}
/*
* reduce_orderby
* The entry of reduce orderby which called by subquery_planner.
*
* @param (in) query: the query tree for reduce orderby.
* @param (in) reduce: the flag identify if reduce the orderby clause or not.
*
* @return: void
*/
void reduce_orderby(Query* query, bool reduce)
{
ListCell* l = NULL;
RangeTblEntry* rte = NULL;
if (query == NULL)
return;
/* If subquery in insert or update, it should find select query and deside whether reduce order by in subquery or
* not. */
if (query->commandType != CMD_SELECT) {
foreach (l, query->rtable) {
rte = (RangeTblEntry*)lfirst(l);
if (rte->rtekind == RTE_SUBQUERY)
reduce_orderby(rte->subquery, reduce);
}
return;
}
/* Recurse find subquery in form,join or subquery, and deside whether reduce order by in subquery or not. */
reduce_orderby_recurse(query, (Node*)query->jointree, reduce);
/* If there has setop, it should optimize orderby clause. */
if (query->setOperations) {
reduce_orderby_recurse(query, ((SetOperationStmt*)query->setOperations)->larg, true);
reduce_orderby_recurse(query, ((SetOperationStmt*)query->setOperations)->rarg, true);
}
}