7510 lines
256 KiB
C
7510 lines
256 KiB
C
/*
|
|
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
|
|
*
|
|
* This program is free software: you can use, redistribute, and/or modify
|
|
* it under the terms of the GNU Affero General Public License, version 3
|
|
* or later ("AGPL"), as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "filter.h"
|
|
#include "functionMgt.h"
|
|
#include "planInt.h"
|
|
#include "systable.h"
|
|
#include "tglobal.h"
|
|
#include "ttime.h"
|
|
#include "parser.h"
|
|
|
|
#define OPTIMIZE_FLAG_MASK(n) (1 << n)
|
|
|
|
#define OPTIMIZE_FLAG_SCAN_PATH OPTIMIZE_FLAG_MASK(0)
|
|
#define OPTIMIZE_FLAG_PUSH_DOWN_CONDE OPTIMIZE_FLAG_MASK(1)
|
|
#define OPTIMIZE_FLAG_STB_JOIN OPTIMIZE_FLAG_MASK(2)
|
|
#define OPTIMIZE_FLAG_ELIMINATE_PROJ OPTIMIZE_FLAG_MASK(3)
|
|
#define OPTIMIZE_FLAG_JOIN_COND OPTIMIZE_FLAG_MASK(4)
|
|
|
|
#define OPTIMIZE_FLAG_SET_MASK(val, mask) (val) |= (mask)
|
|
#define OPTIMIZE_FLAG_CLEAR_MASK(val, mask) (val) &= (~(mask))
|
|
#define OPTIMIZE_FLAG_TEST_MASK(val, mask) (((val) & (mask)) != 0)
|
|
|
|
typedef struct SOptimizeContext {
|
|
SPlanContext* pPlanCxt;
|
|
bool optimized;
|
|
} SOptimizeContext;
|
|
|
|
typedef int32_t (*FOptimize)(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan);
|
|
|
|
typedef struct SOptimizeRule {
|
|
char* pName;
|
|
FOptimize optimizeFunc;
|
|
} SOptimizeRule;
|
|
|
|
typedef struct SOptimizePKCtx {
|
|
SNodeList* pList;
|
|
int32_t code;
|
|
} SOptimizePKCtx;
|
|
|
|
typedef enum EScanOrder { SCAN_ORDER_ASC = 1, SCAN_ORDER_DESC, SCAN_ORDER_BOTH } EScanOrder;
|
|
|
|
typedef struct SOsdInfo {
|
|
SScanLogicNode* pScan;
|
|
SNodeList* pSdrFuncs;
|
|
SNodeList* pDsoFuncs;
|
|
EScanOrder scanOrder;
|
|
} SOsdInfo;
|
|
|
|
typedef struct SCpdIsMultiTableCondCxt {
|
|
SSHashObj* pLeftTbls;
|
|
SSHashObj* pRightTbls;
|
|
bool havaLeftCol;
|
|
bool haveRightCol;
|
|
bool condIsNull;
|
|
} SCpdIsMultiTableCondCxt;
|
|
|
|
typedef struct SCpdIsMultiTableResCxt {
|
|
SSHashObj* pLeftTbls;
|
|
SSHashObj* pRightTbls;
|
|
bool haveLeftCol;
|
|
bool haveRightCol;
|
|
bool leftColOp;
|
|
bool rightColOp;
|
|
bool leftColNonNull;
|
|
bool rightColNonNull;
|
|
} SCpdIsMultiTableResCxt;
|
|
|
|
typedef struct SCpdCollRewriteTableColsCxt {
|
|
int32_t code;
|
|
SSHashObj* pLeftTbls;
|
|
SSHashObj* pRightTbls;
|
|
SSHashObj* pLeftCols;
|
|
SSHashObj* pRightCols;
|
|
} SCpdCollRewriteTableColsCxt;
|
|
|
|
|
|
typedef struct SCpdCollectTableColCxt {
|
|
SSHashObj* pTables;
|
|
SNodeList* pResCols;
|
|
SHashObj* pColHash;
|
|
int32_t errCode;
|
|
} SCpdCollectTableColCxt;
|
|
|
|
|
|
typedef enum ECondAction {
|
|
COND_ACTION_STAY = 1,
|
|
COND_ACTION_PUSH_JOIN,
|
|
COND_ACTION_PUSH_LEFT_CHILD,
|
|
COND_ACTION_PUSH_RIGHT_CHILD
|
|
// after supporting outer join, there are other possibilities
|
|
} ECondAction;
|
|
|
|
#define PUSH_DOWN_LEFT_FLT (1 << 0)
|
|
#define PUSH_DOWN_RIGHT_FLT (1 << 1)
|
|
#define PUSH_DOWN_ON_COND (1 << 2)
|
|
#define PUSH_DONW_FLT_COND (PUSH_DOWN_LEFT_FLT | PUSH_DOWN_RIGHT_FLT)
|
|
#define PUSH_DOWN_ALL_COND (PUSH_DOWN_LEFT_FLT | PUSH_DOWN_RIGHT_FLT | PUSH_DOWN_ON_COND)
|
|
|
|
typedef struct SJoinOptimizeOpt {
|
|
int8_t pushDownFlag;
|
|
} SJoinOptimizeOpt;
|
|
|
|
typedef bool (*FMayBeOptimized)(SLogicNode* pNode, void* pCtx);
|
|
typedef bool (*FShouldBeOptimized)(SLogicNode* pNode, void* pInfo);
|
|
|
|
#if 0
|
|
static SJoinOptimizeOpt gJoinOpt[JOIN_TYPE_MAX_VALUE][JOIN_STYPE_MAX_VALUE] = {
|
|
/* NONE OUTER SEMI ANTI ANY ASOF WINDOW */
|
|
/*INNER*/ {{PUSH_DOWN_ALL_COND}, {0}, {0}, {0}, {PUSH_DOWN_ALL_COND}, {0}, {0}},
|
|
/*LEFT*/ {{0}, {PUSH_DOWN_LEFT_FLT}, {PUSH_DOWN_ALL_COND}, {PUSH_DOWN_LEFT_FLT}, {PUSH_DOWN_LEFT_FLT}, {PUSH_DOWN_LEFT_FLT}, {PUSH_DOWN_LEFT_FLT}},
|
|
/*RIGHT*/ {{0}, {PUSH_DOWN_RIGHT_FLT}, {PUSH_DOWN_ALL_COND}, {PUSH_DOWN_RIGHT_FLT}, {PUSH_DOWN_RIGHT_FLT}, {PUSH_DOWN_RIGHT_FLT}, {PUSH_DOWN_RIGHT_FLT}},
|
|
/*FULL*/ {{0}, {0}, {0}, {0}, {0}, {0}, {0}},
|
|
};
|
|
#else
|
|
static SJoinOptimizeOpt gJoinWhereOpt[JOIN_TYPE_MAX_VALUE][JOIN_STYPE_MAX_VALUE] = {
|
|
/* NONE OUTER SEMI ANTI ASOF WINDOW */
|
|
/*INNER*/ {{PUSH_DOWN_ALL_COND}, {0}, {0}, {0}, {0}, {0}},
|
|
/*LEFT*/ {{0}, {PUSH_DOWN_LEFT_FLT}, {PUSH_DOWN_LEFT_FLT}, {PUSH_DOWN_LEFT_FLT}, {PUSH_DOWN_LEFT_FLT}, {PUSH_DOWN_LEFT_FLT}},
|
|
/*RIGHT*/ {{0}, {PUSH_DOWN_RIGHT_FLT}, {PUSH_DOWN_RIGHT_FLT},{PUSH_DOWN_RIGHT_FLT}, {PUSH_DOWN_RIGHT_FLT}, {PUSH_DOWN_RIGHT_FLT}},
|
|
/*FULL*/ {{0}, {0}, {0}, {0}, {0}, {0}},
|
|
};
|
|
|
|
static SJoinOptimizeOpt gJoinOnOpt[JOIN_TYPE_MAX_VALUE][JOIN_STYPE_MAX_VALUE] = {
|
|
/* NONE OUTER SEMI ANTI ASOF WINDOW */
|
|
/*INNER*/ {{PUSH_DONW_FLT_COND}, {0}, {0}, {0}, {0}, {0}},
|
|
/*LEFT*/ {{0}, {PUSH_DOWN_RIGHT_FLT}, {PUSH_DONW_FLT_COND}, {PUSH_DOWN_RIGHT_FLT}, {0}, {0}},
|
|
/*RIGHT*/ {{0}, {PUSH_DOWN_LEFT_FLT}, {PUSH_DONW_FLT_COND}, {PUSH_DOWN_LEFT_FLT}, {0}, {0}},
|
|
/*FULL*/ {{0}, {0}, {0}, {0}, {0}, {0}},
|
|
};
|
|
|
|
|
|
#endif
|
|
|
|
static SLogicNode* optFindPossibleNode(SLogicNode* pNode, FMayBeOptimized func, void* pCtx) {
|
|
if (func(pNode, pCtx)) {
|
|
return pNode;
|
|
}
|
|
SNode* pChild;
|
|
FOREACH(pChild, pNode->pChildren) {
|
|
SLogicNode* pScanNode = optFindPossibleNode((SLogicNode*)pChild, func, pCtx);
|
|
if (NULL != pScanNode) {
|
|
return pScanNode;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool optFindEligibleNode(SLogicNode* pNode, FShouldBeOptimized func, void* pInfo) {
|
|
if (func(pNode, pInfo)) {
|
|
return true;
|
|
}
|
|
SNode* pChild;
|
|
FOREACH(pChild, pNode->pChildren) {
|
|
if (optFindEligibleNode((SLogicNode*)pChild, func, pInfo)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void optResetParent(SLogicNode* pNode) {
|
|
SNode* pChild = NULL;
|
|
FOREACH(pChild, pNode->pChildren) { ((SLogicNode*)pChild)->pParent = pNode; }
|
|
}
|
|
|
|
static EDealRes optRebuildTbanme(SNode** pNode, void* pContext) {
|
|
if (QUERY_NODE_COLUMN == nodeType(*pNode) && COLUMN_TYPE_TBNAME == ((SColumnNode*)*pNode)->colType) {
|
|
SFunctionNode* pFunc = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_FUNCTION, (SNode**)&pFunc);
|
|
if (NULL == pFunc) {
|
|
*(int32_t*)pContext = code;
|
|
return DEAL_RES_ERROR;
|
|
}
|
|
strcpy(pFunc->functionName, "tbname");
|
|
pFunc->funcType = FUNCTION_TYPE_TBNAME;
|
|
pFunc->node.resType = ((SColumnNode*)*pNode)->node.resType;
|
|
nodesDestroyNode(*pNode);
|
|
*pNode = (SNode*)pFunc;
|
|
return DEAL_RES_IGNORE_CHILD;
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static void optSetParentOrder(SLogicNode* pNode, EOrder order, SLogicNode* pNodeForcePropagate) {
|
|
if (NULL == pNode) {
|
|
return;
|
|
}
|
|
pNode->inputTsOrder = order;
|
|
switch (nodeType(pNode)) {
|
|
// for those nodes that will change the order, stop propagating
|
|
// case QUERY_NODE_LOGIC_PLAN_WINDOW:
|
|
case QUERY_NODE_LOGIC_PLAN_AGG:
|
|
case QUERY_NODE_LOGIC_PLAN_SORT:
|
|
if (pNode == pNodeForcePropagate) {
|
|
pNode->outputTsOrder = order;
|
|
break;
|
|
} else
|
|
return;
|
|
case QUERY_NODE_LOGIC_PLAN_JOIN:
|
|
pNode->outputTsOrder = order;
|
|
break;
|
|
case QUERY_NODE_LOGIC_PLAN_WINDOW:
|
|
// Window output ts order default to be asc, and changed when doing sort by primary key optimization.
|
|
// We stop propagate the original order to parents.
|
|
// Use window output ts order instead.
|
|
order = pNode->outputTsOrder;
|
|
break;
|
|
default:
|
|
pNode->outputTsOrder = order;
|
|
break;
|
|
}
|
|
optSetParentOrder(pNode->pParent, order, pNodeForcePropagate);
|
|
}
|
|
|
|
EDealRes scanPathOptHaveNormalColImpl(SNode* pNode, void* pContext) {
|
|
if (QUERY_NODE_COLUMN == nodeType(pNode)) {
|
|
*((bool*)pContext) =
|
|
(COLUMN_TYPE_TAG != ((SColumnNode*)pNode)->colType && COLUMN_TYPE_TBNAME != ((SColumnNode*)pNode)->colType);
|
|
return *((bool*)pContext) ? DEAL_RES_END : DEAL_RES_IGNORE_CHILD;
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static bool scanPathOptHaveNormalCol(SNodeList* pList) {
|
|
bool res = false;
|
|
nodesWalkExprsPostOrder(pList, scanPathOptHaveNormalColImpl, &res);
|
|
return res;
|
|
}
|
|
|
|
static bool scanPathOptMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (OPTIMIZE_FLAG_TEST_MASK(pNode->optimizedFlag, OPTIMIZE_FLAG_SCAN_PATH)) {
|
|
return false;
|
|
}
|
|
if (QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(pNode)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool scanPathOptShouldGetFuncs(SLogicNode* pNode) {
|
|
if (QUERY_NODE_LOGIC_PLAN_PARTITION == nodeType(pNode)) {
|
|
if (!pNode->pParent || QUERY_NODE_LOGIC_PLAN_WINDOW != nodeType(pNode->pParent) ||
|
|
WINDOW_TYPE_INTERVAL == ((SWindowLogicNode*)pNode->pParent)->winType)
|
|
return !scanPathOptHaveNormalCol(((SPartitionLogicNode*)pNode)->pPartitionKeys);
|
|
return false;
|
|
}
|
|
|
|
if ((QUERY_NODE_LOGIC_PLAN_WINDOW == nodeType(pNode) &&
|
|
WINDOW_TYPE_INTERVAL == ((SWindowLogicNode*)pNode)->winType)) {
|
|
return true;
|
|
}
|
|
if (QUERY_NODE_LOGIC_PLAN_AGG == nodeType(pNode)) {
|
|
return !scanPathOptHaveNormalCol(((SAggLogicNode*)pNode)->pGroupKeys);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static SNodeList* scanPathOptGetAllFuncs(SLogicNode* pNode) {
|
|
if (!scanPathOptShouldGetFuncs(pNode)) return NULL;
|
|
switch (nodeType(pNode)) {
|
|
case QUERY_NODE_LOGIC_PLAN_WINDOW:
|
|
return ((SWindowLogicNode*)pNode)->pFuncs;
|
|
case QUERY_NODE_LOGIC_PLAN_AGG:
|
|
return ((SAggLogicNode*)pNode)->pAggFuncs;
|
|
case QUERY_NODE_LOGIC_PLAN_PARTITION:
|
|
return ((SPartitionLogicNode*)pNode)->pAggFuncs;
|
|
default:
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool scanPathOptIsSpecifiedFuncType(const SFunctionNode* pFunc, bool (*typeCheckFn)(int32_t)) {
|
|
if (!typeCheckFn(pFunc->funcId)) return false;
|
|
SNode* pPara;
|
|
FOREACH(pPara, pFunc->pParameterList) {
|
|
if (QUERY_NODE_COLUMN != nodeType(pPara) && QUERY_NODE_VALUE != nodeType(pPara)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int32_t scanPathOptGetRelatedFuncs(SScanLogicNode* pScan, SNodeList** pSdrFuncs, SNodeList** pDsoFuncs) {
|
|
SNodeList* pAllFuncs = scanPathOptGetAllFuncs(pScan->node.pParent);
|
|
SNodeList* pTmpSdrFuncs = NULL;
|
|
SNodeList* pTmpDsoFuncs = NULL;
|
|
SNode* pNode = NULL;
|
|
bool otherFunc = false;
|
|
FOREACH(pNode, pAllFuncs) {
|
|
SFunctionNode* pFunc = (SFunctionNode*)pNode;
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
if (scanPathOptIsSpecifiedFuncType(pFunc, fmIsSpecialDataRequiredFunc)) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pTmpSdrFuncs, pNew);
|
|
}
|
|
} else if (scanPathOptIsSpecifiedFuncType(pFunc, fmIsDynamicScanOptimizedFunc)) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pTmpDsoFuncs, pNew);
|
|
}
|
|
} else if (scanPathOptIsSpecifiedFuncType(pFunc, fmIsSkipScanCheckFunc)) {
|
|
continue;
|
|
} else {
|
|
otherFunc = true;
|
|
break;
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyList(pTmpSdrFuncs);
|
|
nodesDestroyList(pTmpDsoFuncs);
|
|
return code;
|
|
}
|
|
}
|
|
if (otherFunc) {
|
|
nodesDestroyList(pTmpSdrFuncs);
|
|
nodesDestroyList(pTmpDsoFuncs);
|
|
} else {
|
|
*pSdrFuncs = pTmpSdrFuncs;
|
|
*pDsoFuncs = pTmpDsoFuncs;
|
|
}
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static int32_t scanPathOptGetScanOrder(SScanLogicNode* pScan, EScanOrder* pScanOrder) {
|
|
SNodeList* pAllFuncs = scanPathOptGetAllFuncs(pScan->node.pParent);
|
|
SNode* pNode = NULL;
|
|
bool hasFirst = false;
|
|
bool hasLast = false;
|
|
bool otherFunc = false;
|
|
FOREACH(pNode, pAllFuncs) {
|
|
SFunctionNode* pFunc = (SFunctionNode*)pNode;
|
|
if (FUNCTION_TYPE_FIRST == pFunc->funcType) {
|
|
hasFirst = true;
|
|
} else if (FUNCTION_TYPE_LAST == pFunc->funcType || FUNCTION_TYPE_LAST_ROW == pFunc->funcType) {
|
|
hasLast = true;
|
|
} else if (FUNCTION_TYPE_SELECT_VALUE != pFunc->funcType) {
|
|
otherFunc = true;
|
|
}
|
|
}
|
|
if (hasFirst && hasLast && !otherFunc) {
|
|
*pScanOrder = SCAN_ORDER_BOTH;
|
|
} else if (hasLast) {
|
|
*pScanOrder = SCAN_ORDER_DESC;
|
|
} else {
|
|
*pScanOrder = SCAN_ORDER_ASC;
|
|
}
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static int32_t scanPathOptSetOsdInfo(SOsdInfo* pInfo) {
|
|
int32_t code = scanPathOptGetRelatedFuncs(pInfo->pScan, &pInfo->pSdrFuncs, &pInfo->pDsoFuncs);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = scanPathOptGetScanOrder(pInfo->pScan, &pInfo->scanOrder);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t scanPathOptMatch(SOptimizeContext* pCxt, SLogicNode* pLogicNode, SOsdInfo* pInfo) {
|
|
pInfo->pScan = (SScanLogicNode*)optFindPossibleNode(pLogicNode, scanPathOptMayBeOptimized, NULL);
|
|
if (NULL == pInfo->pScan) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
return scanPathOptSetOsdInfo(pInfo);
|
|
}
|
|
|
|
static EFuncDataRequired scanPathOptPromoteDataRequired(EFuncDataRequired l, EFuncDataRequired r) {
|
|
switch (l) {
|
|
case FUNC_DATA_REQUIRED_DATA_LOAD:
|
|
return l;
|
|
case FUNC_DATA_REQUIRED_SMA_LOAD:
|
|
return FUNC_DATA_REQUIRED_DATA_LOAD == r ? r : l;
|
|
case FUNC_DATA_REQUIRED_NOT_LOAD:
|
|
return FUNC_DATA_REQUIRED_FILTEROUT == r ? l : r;
|
|
default:
|
|
break;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static int32_t scanPathOptGetDataRequired(SNodeList* pFuncs) {
|
|
if (NULL == pFuncs) {
|
|
return FUNC_DATA_REQUIRED_DATA_LOAD;
|
|
}
|
|
EFuncDataRequired dataRequired = FUNC_DATA_REQUIRED_FILTEROUT;
|
|
SNode* pFunc = NULL;
|
|
FOREACH(pFunc, pFuncs) {
|
|
dataRequired = scanPathOptPromoteDataRequired(dataRequired, fmFuncDataRequired((SFunctionNode*)pFunc, NULL));
|
|
}
|
|
return dataRequired;
|
|
}
|
|
|
|
static void scanPathOptSetScanWin(SScanLogicNode* pScan) {
|
|
SLogicNode* pParent = pScan->node.pParent;
|
|
if (QUERY_NODE_LOGIC_PLAN_PARTITION == nodeType(pParent) && pParent->pParent &&
|
|
QUERY_NODE_LOGIC_PLAN_WINDOW == nodeType(pParent->pParent)) {
|
|
pParent = pParent->pParent;
|
|
}
|
|
if (QUERY_NODE_LOGIC_PLAN_WINDOW == nodeType(pParent)) {
|
|
pScan->interval = ((SWindowLogicNode*)pParent)->interval;
|
|
pScan->offset = ((SWindowLogicNode*)pParent)->offset;
|
|
pScan->sliding = ((SWindowLogicNode*)pParent)->sliding;
|
|
pScan->intervalUnit = ((SWindowLogicNode*)pParent)->intervalUnit;
|
|
pScan->slidingUnit = ((SWindowLogicNode*)pParent)->slidingUnit;
|
|
}
|
|
}
|
|
|
|
static void scanPathOptSetScanOrder(EScanOrder scanOrder, SScanLogicNode* pScan) {
|
|
if (pScan->sortPrimaryKey || pScan->scanSeq[0] > 1 || pScan->scanSeq[1] > 1) {
|
|
return;
|
|
}
|
|
pScan->node.outputTsOrder = (SCAN_ORDER_ASC == scanOrder) ? ORDER_ASC : ORDER_DESC;
|
|
switch (scanOrder) {
|
|
case SCAN_ORDER_ASC:
|
|
pScan->scanSeq[0] = 1;
|
|
pScan->scanSeq[1] = 0;
|
|
optSetParentOrder(pScan->node.pParent, ORDER_ASC, NULL);
|
|
break;
|
|
case SCAN_ORDER_DESC:
|
|
pScan->scanSeq[0] = 0;
|
|
pScan->scanSeq[1] = 1;
|
|
optSetParentOrder(pScan->node.pParent, ORDER_DESC, NULL);
|
|
break;
|
|
case SCAN_ORDER_BOTH:
|
|
pScan->scanSeq[0] = 1;
|
|
pScan->scanSeq[1] = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void scanPathOptSetGroupOrderScan(SScanLogicNode* pScan) {
|
|
if (pScan->tableType != TSDB_SUPER_TABLE) return;
|
|
|
|
if (pScan->node.pParent && nodeType(pScan->node.pParent) == QUERY_NODE_LOGIC_PLAN_AGG) {
|
|
SAggLogicNode* pAgg = (SAggLogicNode*)pScan->node.pParent;
|
|
bool withSlimit = pAgg->node.pSlimit != NULL;
|
|
if (withSlimit && (isPartTableAgg(pAgg) || isPartTagAgg(pAgg))) {
|
|
pScan->groupOrderScan = pAgg->node.forceCreateNonBlockingOptr = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int32_t scanPathOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SOsdInfo info = {.scanOrder = SCAN_ORDER_ASC};
|
|
int32_t code = scanPathOptMatch(pCxt, pLogicSubplan->pNode, &info);
|
|
if (TSDB_CODE_SUCCESS == code && info.pScan) {
|
|
scanPathOptSetScanWin(info.pScan);
|
|
if (!pCxt->pPlanCxt->streamQuery) {
|
|
scanPathOptSetScanOrder(info.scanOrder, info.pScan);
|
|
}
|
|
scanPathOptSetGroupOrderScan(info.pScan);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code && (NULL != info.pDsoFuncs || NULL != info.pSdrFuncs)) {
|
|
if (pCxt->pPlanCxt->streamQuery) {
|
|
info.pScan->dataRequired = FUNC_DATA_REQUIRED_DATA_LOAD; // always load all data for stream query
|
|
} else {
|
|
info.pScan->dataRequired = scanPathOptGetDataRequired(info.pSdrFuncs);
|
|
}
|
|
|
|
info.pScan->pDynamicScanFuncs = info.pDsoFuncs;
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code && info.pScan) {
|
|
OPTIMIZE_FLAG_SET_MASK(info.pScan->node.optimizedFlag, OPTIMIZE_FLAG_SCAN_PATH);
|
|
pCxt->optimized = true;
|
|
}
|
|
nodesDestroyList(info.pSdrFuncs);
|
|
return code;
|
|
}
|
|
|
|
static int32_t pdcMergeCondsToLogic(SNode** pDst, SNode** pSrc) {
|
|
SLogicConditionNode* pLogicCond = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_LOGIC_CONDITION, (SNode**)&pLogicCond);
|
|
if (NULL == pLogicCond) {
|
|
return code;
|
|
}
|
|
pLogicCond->node.resType.type = TSDB_DATA_TYPE_BOOL;
|
|
pLogicCond->node.resType.bytes = tDataTypes[TSDB_DATA_TYPE_BOOL].bytes;
|
|
pLogicCond->condType = LOGIC_COND_TYPE_AND;
|
|
code = nodesListMakeAppend(&pLogicCond->pParameterList, *pSrc);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pSrc = NULL;
|
|
code = nodesListMakeAppend(&pLogicCond->pParameterList, *pDst);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pDst = (SNode*)pLogicCond;
|
|
} else {
|
|
nodesDestroyNode((SNode*)pLogicCond);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t pdcMergeConds(SNode** pCond, SNode** pAdditionalCond) {
|
|
if (NULL == *pCond) {
|
|
TSWAP(*pCond, *pAdditionalCond);
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
if (QUERY_NODE_LOGIC_CONDITION == nodeType(*pCond) &&
|
|
LOGIC_COND_TYPE_AND == ((SLogicConditionNode*)*pCond)->condType) {
|
|
code = nodesListAppend(((SLogicConditionNode*)*pCond)->pParameterList, *pAdditionalCond);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pAdditionalCond = NULL;
|
|
}
|
|
} else {
|
|
code = pdcMergeCondsToLogic(pCond, pAdditionalCond);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t pushDownCondOptCalcTimeRange(SOptimizeContext* pCxt, SScanLogicNode* pScan, SNode** pPrimaryKeyCond,
|
|
SNode** pOtherCond) {
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
if (pCxt->pPlanCxt->topicQuery || pCxt->pPlanCxt->streamQuery) {
|
|
code = pdcMergeConds(pOtherCond, pPrimaryKeyCond);
|
|
} else {
|
|
bool isStrict = false;
|
|
code = filterGetTimeRange(*pPrimaryKeyCond, &pScan->scanRange, &isStrict);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
if (isStrict) {
|
|
nodesDestroyNode(*pPrimaryKeyCond);
|
|
} else {
|
|
code = pdcMergeConds(pOtherCond, pPrimaryKeyCond);
|
|
}
|
|
*pPrimaryKeyCond = NULL;
|
|
}
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t pushDownCondOptRebuildTbanme(SNode** pTagCond) {
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
nodesRewriteExpr(pTagCond, optRebuildTbanme, &code);
|
|
return code;
|
|
}
|
|
|
|
static int32_t pdcDealScan(SOptimizeContext* pCxt, SScanLogicNode* pScan) {
|
|
if (NULL == pScan->node.pConditions ||
|
|
OPTIMIZE_FLAG_TEST_MASK(pScan->node.optimizedFlag, OPTIMIZE_FLAG_PUSH_DOWN_CONDE) ||
|
|
TSDB_SYSTEM_TABLE == pScan->tableType) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
SNode* pPrimaryKeyCond = NULL;
|
|
SNode* pOtherCond = NULL;
|
|
int32_t code = filterPartitionCond(&pScan->node.pConditions, &pPrimaryKeyCond, &pScan->pTagIndexCond,
|
|
&pScan->pTagCond, &pOtherCond);
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pScan->pTagCond) {
|
|
code = pushDownCondOptRebuildTbanme(&pScan->pTagCond);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pPrimaryKeyCond) {
|
|
code = pushDownCondOptCalcTimeRange(pCxt, pScan, &pPrimaryKeyCond, &pOtherCond);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pScan->node.pConditions = pOtherCond;
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
OPTIMIZE_FLAG_SET_MASK(pScan->node.optimizedFlag, OPTIMIZE_FLAG_PUSH_DOWN_CONDE);
|
|
pCxt->optimized = true;
|
|
} else {
|
|
nodesDestroyNode(pPrimaryKeyCond);
|
|
nodesDestroyNode(pOtherCond);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static bool pdcColBelongThisTable(SNode* pCondCol, SNodeList* pTableCols) {
|
|
SNode* pTableCol = NULL;
|
|
FOREACH(pTableCol, pTableCols) {
|
|
if (QUERY_NODE_COLUMN == nodeType(pCondCol) && QUERY_NODE_COLUMN == nodeType(pTableCol)) {
|
|
SColumnNode* pCondColNode = (SColumnNode*)pCondCol;
|
|
SColumnNode* pTblColNode = (SColumnNode*)pTableCol;
|
|
if (0 == strcmp(pCondColNode->tableAlias, pTblColNode->tableAlias) && 0 == strcmp(pCondColNode->colName, pTblColNode->colName)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (nodesEqualNode(pCondCol, pTableCol)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool pdcJoinColInTableColList(SNode* pNode, SNodeList* pTableCols) {
|
|
if (QUERY_NODE_COLUMN != nodeType(pNode)) {
|
|
return false;
|
|
}
|
|
SColumnNode* pCol = (SColumnNode*)pNode;
|
|
return pdcColBelongThisTable(pNode, pTableCols);
|
|
}
|
|
|
|
static bool pdcJoinColInTableList(SNode* pCondCol, SSHashObj* pTables) {
|
|
SColumnNode* pTableCol = (SColumnNode*)pCondCol;
|
|
if (NULL == tSimpleHashGet(pTables, pTableCol->tableAlias, strlen(pTableCol->tableAlias))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static EDealRes pdcJoinIsCrossTableCond(SNode* pNode, void* pContext) {
|
|
SCpdIsMultiTableCondCxt* pCxt = pContext;
|
|
if (QUERY_NODE_COLUMN == nodeType(pNode)) {
|
|
if (pdcJoinColInTableList(pNode, pCxt->pLeftTbls)) {
|
|
pCxt->havaLeftCol = true;
|
|
} else if (pdcJoinColInTableList(pNode, pCxt->pRightTbls)) {
|
|
pCxt->haveRightCol = true;
|
|
}
|
|
return pCxt->havaLeftCol && pCxt->haveRightCol ? DEAL_RES_END : DEAL_RES_CONTINUE;
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static ECondAction pdcJoinGetCondAction(SJoinLogicNode* pJoin, SSHashObj* pLeftTbls, SSHashObj* pRightTbls,
|
|
SNode* pNode, bool whereCond) {
|
|
EJoinType t = pJoin->joinType;
|
|
EJoinSubType s = pJoin->subType;
|
|
SCpdIsMultiTableCondCxt cxt = {
|
|
.pLeftTbls = pLeftTbls, .pRightTbls = pRightTbls, .havaLeftCol = false, .haveRightCol = false};
|
|
nodesWalkExpr(pNode, pdcJoinIsCrossTableCond, &cxt);
|
|
|
|
if (cxt.havaLeftCol) {
|
|
if (cxt.haveRightCol) {
|
|
if (whereCond && gJoinWhereOpt[t][s].pushDownFlag & PUSH_DOWN_ON_COND) {
|
|
return COND_ACTION_PUSH_JOIN;
|
|
}
|
|
return COND_ACTION_STAY;
|
|
}
|
|
if ((whereCond && gJoinWhereOpt[t][s].pushDownFlag & PUSH_DOWN_LEFT_FLT) || (!whereCond && gJoinOnOpt[t][s].pushDownFlag & PUSH_DOWN_LEFT_FLT)) {
|
|
return COND_ACTION_PUSH_LEFT_CHILD;
|
|
}
|
|
return COND_ACTION_STAY;
|
|
}
|
|
|
|
if (cxt.haveRightCol) {
|
|
if ((whereCond && gJoinWhereOpt[t][s].pushDownFlag & PUSH_DOWN_RIGHT_FLT) || (!whereCond && gJoinOnOpt[t][s].pushDownFlag & PUSH_DOWN_RIGHT_FLT)) {
|
|
return COND_ACTION_PUSH_RIGHT_CHILD;
|
|
}
|
|
return COND_ACTION_STAY;
|
|
}
|
|
|
|
return COND_ACTION_STAY;
|
|
}
|
|
|
|
static int32_t pdcJoinSplitLogicCond(SJoinLogicNode* pJoin, SNode** pSrcCond, SNode** pOnCond, SNode** pLeftChildCond,
|
|
SNode** pRightChildCond, bool whereCond) {
|
|
SLogicConditionNode* pLogicCond = (SLogicConditionNode*)*pSrcCond;
|
|
if (LOGIC_COND_TYPE_AND != pLogicCond->condType) {
|
|
if (whereCond) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
return TSDB_CODE_PLAN_NOT_SUPPORT_JOIN_COND;
|
|
}
|
|
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SSHashObj* pLeftTables = NULL;
|
|
SSHashObj* pRightTables = NULL;
|
|
code = collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 0), &pLeftTables);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
code = collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 1), &pRightTables);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
tSimpleHashCleanup(pLeftTables);
|
|
return code;
|
|
}
|
|
|
|
SNodeList* pOnConds = NULL;
|
|
SNodeList* pLeftChildConds = NULL;
|
|
SNodeList* pRightChildConds = NULL;
|
|
SNodeList* pRemainConds = NULL;
|
|
SNode* pCond = NULL;
|
|
FOREACH(pCond, pLogicCond->pParameterList) {
|
|
ECondAction condAction = pdcJoinGetCondAction(pJoin, pLeftTables, pRightTables, pCond, whereCond);
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pCond, &pNew);
|
|
if (TSDB_CODE_SUCCESS != code) { break; }
|
|
if (COND_ACTION_PUSH_JOIN == condAction && NULL != pOnCond) {
|
|
code = nodesListMakeAppend(&pOnConds, pNew);
|
|
} else if (COND_ACTION_PUSH_LEFT_CHILD == condAction) {
|
|
code = nodesListMakeAppend(&pLeftChildConds, pNew);
|
|
} else if (COND_ACTION_PUSH_RIGHT_CHILD == condAction) {
|
|
code = nodesListMakeAppend(&pRightChildConds, pNew);
|
|
} else {
|
|
code = nodesListMakeAppend(&pRemainConds, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
tSimpleHashCleanup(pLeftTables);
|
|
tSimpleHashCleanup(pRightTables);
|
|
|
|
SNode* pTempOnCond = NULL;
|
|
SNode* pTempLeftChildCond = NULL;
|
|
SNode* pTempRightChildCond = NULL;
|
|
SNode* pTempRemainCond = NULL;
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesMergeConds(&pTempOnCond, &pOnConds);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesMergeConds(&pTempLeftChildCond, &pLeftChildConds);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesMergeConds(&pTempRightChildCond, &pRightChildConds);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesMergeConds(&pTempRemainCond, &pRemainConds);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
if (pOnCond) {
|
|
*pOnCond = pTempOnCond;
|
|
}
|
|
*pLeftChildCond = pTempLeftChildCond;
|
|
*pRightChildCond = pTempRightChildCond;
|
|
nodesDestroyNode(*pSrcCond);
|
|
*pSrcCond = pTempRemainCond;
|
|
} else {
|
|
nodesDestroyList(pOnConds);
|
|
nodesDestroyList(pLeftChildConds);
|
|
nodesDestroyList(pRightChildConds);
|
|
nodesDestroyList(pRemainConds);
|
|
nodesDestroyNode(pTempOnCond);
|
|
nodesDestroyNode(pTempLeftChildCond);
|
|
nodesDestroyNode(pTempRightChildCond);
|
|
nodesDestroyNode(pTempRemainCond);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t pdcJoinSplitOpCond(SJoinLogicNode* pJoin, SNode** pSrcCond, SNode** pOnCond, SNode** pLeftChildCond,
|
|
SNode** pRightChildCond, bool whereCond) {
|
|
SSHashObj* pLeftTables = NULL;
|
|
SSHashObj* pRightTables = NULL;
|
|
int32_t code = collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 0), &pLeftTables);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
code = collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 1), &pRightTables);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
tSimpleHashCleanup(pLeftTables);
|
|
return code;
|
|
}
|
|
|
|
ECondAction condAction = pdcJoinGetCondAction(pJoin, pLeftTables, pRightTables, *pSrcCond, whereCond);
|
|
|
|
tSimpleHashCleanup(pLeftTables);
|
|
tSimpleHashCleanup(pRightTables);
|
|
|
|
if (COND_ACTION_STAY == condAction || (COND_ACTION_PUSH_JOIN == condAction && NULL == pOnCond)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
if (COND_ACTION_PUSH_JOIN == condAction) {
|
|
*pOnCond = *pSrcCond;
|
|
} else if (COND_ACTION_PUSH_LEFT_CHILD == condAction) {
|
|
*pLeftChildCond = *pSrcCond;
|
|
} else if (COND_ACTION_PUSH_RIGHT_CHILD == condAction) {
|
|
*pRightChildCond = *pSrcCond;
|
|
}
|
|
*pSrcCond = NULL;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static int32_t pdcJoinSplitCond(SJoinLogicNode* pJoin, SNode** pSrcCond, SNode** pOnCond, SNode** pLeftChildCond,
|
|
SNode** pRightChildCond, bool whereCond) {
|
|
if (QUERY_NODE_LOGIC_CONDITION == nodeType(*pSrcCond)) {
|
|
return pdcJoinSplitLogicCond(pJoin, pSrcCond, pOnCond, pLeftChildCond, pRightChildCond, whereCond);
|
|
} else {
|
|
return pdcJoinSplitOpCond(pJoin, pSrcCond, pOnCond, pLeftChildCond, pRightChildCond, whereCond);
|
|
}
|
|
}
|
|
|
|
static int32_t pdcJoinPushDownOnCond(SOptimizeContext* pCxt, SJoinLogicNode* pJoin, SNode** pCond) {
|
|
return pdcMergeConds(&pJoin->pFullOnCond, pCond);
|
|
}
|
|
|
|
static int32_t pdcPushDownCondToChild(SOptimizeContext* pCxt, SLogicNode* pChild, SNode** pCond) {
|
|
return pdcMergeConds(&pChild->pConditions, pCond);
|
|
}
|
|
|
|
static bool pdcJoinIsPrim(SNode* pNode, SSHashObj* pTables) {
|
|
if (QUERY_NODE_COLUMN != nodeType(pNode) && QUERY_NODE_FUNCTION != nodeType(pNode)) {
|
|
return false;
|
|
}
|
|
|
|
if (QUERY_NODE_FUNCTION == nodeType(pNode)) {
|
|
SFunctionNode* pFunc = (SFunctionNode*)pNode;
|
|
if (FUNCTION_TYPE_TIMETRUNCATE != pFunc->funcType) {
|
|
return false;
|
|
}
|
|
SListCell* pCell = nodesListGetCell(pFunc->pParameterList, 0);
|
|
if (NULL == pCell || NULL == pCell->pNode || QUERY_NODE_COLUMN != nodeType(pCell->pNode)) {
|
|
return false;
|
|
}
|
|
pNode = pCell->pNode;
|
|
}
|
|
|
|
SColumnNode* pCol = (SColumnNode*)pNode;
|
|
if (PRIMARYKEY_TIMESTAMP_COL_ID != pCol->colId || TSDB_SYSTEM_TABLE == pCol->tableType) {
|
|
return false;
|
|
}
|
|
return pdcJoinColInTableList(pNode, pTables);
|
|
}
|
|
|
|
static bool pdcJoinIsPrimEqualCond(SJoinLogicNode* pJoin, SNode* pCond) {
|
|
if (QUERY_NODE_OPERATOR != nodeType(pCond)) {
|
|
return false;
|
|
}
|
|
|
|
SOperatorNode* pOper = (SOperatorNode*)pCond;
|
|
if (OP_TYPE_EQUAL != pOper->opType) {
|
|
if (JOIN_STYPE_ASOF != pJoin->subType) {
|
|
return false;
|
|
}
|
|
if (OP_TYPE_GREATER_THAN != pOper->opType && OP_TYPE_GREATER_EQUAL != pOper->opType &&
|
|
OP_TYPE_LOWER_THAN != pOper->opType && OP_TYPE_LOWER_EQUAL != pOper->opType) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
SSHashObj* pLeftTables = NULL;
|
|
SSHashObj* pRightTables = NULL;
|
|
int32_t code = collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 0), &pLeftTables);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
code = collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 1), &pRightTables);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
tSimpleHashCleanup(pLeftTables);
|
|
return code;
|
|
}
|
|
|
|
bool res = false;
|
|
if (pdcJoinIsPrim(pOper->pLeft, pLeftTables)) {
|
|
res = pdcJoinIsPrim(pOper->pRight, pRightTables);
|
|
} else if (pdcJoinIsPrim(pOper->pLeft, pRightTables)) {
|
|
res = pdcJoinIsPrim(pOper->pRight, pLeftTables);
|
|
}
|
|
|
|
tSimpleHashCleanup(pLeftTables);
|
|
tSimpleHashCleanup(pRightTables);
|
|
|
|
return res;
|
|
}
|
|
|
|
static bool pdcJoinHasPrimEqualCond(SJoinLogicNode* pJoin, SNode* pCond, bool* errCond) {
|
|
if (QUERY_NODE_LOGIC_CONDITION == nodeType(pCond)) {
|
|
SLogicConditionNode* pLogicCond = (SLogicConditionNode*)pCond;
|
|
if (LOGIC_COND_TYPE_AND != pLogicCond->condType) {
|
|
if (errCond) {
|
|
*errCond = true;
|
|
}
|
|
return false;
|
|
}
|
|
bool hasPrimaryKeyEqualCond = false;
|
|
SNode* pCond = NULL;
|
|
FOREACH(pCond, pLogicCond->pParameterList) {
|
|
if (pdcJoinHasPrimEqualCond(pJoin, pCond, NULL)) {
|
|
hasPrimaryKeyEqualCond = true;
|
|
break;
|
|
}
|
|
}
|
|
return hasPrimaryKeyEqualCond;
|
|
} else {
|
|
return pdcJoinIsPrimEqualCond(pJoin, pCond);
|
|
}
|
|
}
|
|
|
|
static int32_t pdcJoinSplitPrimInLogicCond(SJoinLogicNode* pJoin, SNode** ppPrimEqCond, SNode** ppOnCond) {
|
|
SLogicConditionNode* pLogicCond = (SLogicConditionNode*)(pJoin->pFullOnCond);
|
|
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SNodeList* pOnConds = NULL;
|
|
SNode* pCond = NULL;
|
|
WHERE_EACH(pCond, pLogicCond->pParameterList) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pCond, &pNew);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
if (pdcJoinIsPrimEqualCond(pJoin, pCond) && (NULL == *ppPrimEqCond)) {
|
|
*ppPrimEqCond = pNew;
|
|
ERASE_NODE(pLogicCond->pParameterList);
|
|
} else {
|
|
code = nodesListMakeAppend(&pOnConds, pNew);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
WHERE_NEXT;
|
|
}
|
|
}
|
|
|
|
SNode* pTempOnCond = NULL;
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesMergeConds(&pTempOnCond, &pOnConds);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
if (NULL != *ppPrimEqCond) {
|
|
*ppOnCond = pTempOnCond;
|
|
nodesDestroyNode(pJoin->pFullOnCond);
|
|
pJoin->pFullOnCond = NULL;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
planError("no primary key equal cond found, condListNum:%d", pLogicCond->pParameterList->length);
|
|
return TSDB_CODE_PLAN_INTERNAL_ERROR;
|
|
} else {
|
|
nodesDestroyList(pOnConds);
|
|
nodesDestroyNode(pTempOnCond);
|
|
return code;
|
|
}
|
|
}
|
|
|
|
static int32_t pdcJoinSplitPrimEqCond(SOptimizeContext* pCxt, SJoinLogicNode* pJoin) {
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SNode* pPrimKeyEqCond = NULL;
|
|
SNode* pJoinOnCond = NULL;
|
|
|
|
if (QUERY_NODE_LOGIC_CONDITION == nodeType(pJoin->pFullOnCond) &&
|
|
LOGIC_COND_TYPE_AND == ((SLogicConditionNode*)(pJoin->pFullOnCond))->condType) {
|
|
code = pdcJoinSplitPrimInLogicCond(pJoin, &pPrimKeyEqCond, &pJoinOnCond);
|
|
} else if (pdcJoinIsPrimEqualCond(pJoin, pJoin->pFullOnCond)) {
|
|
pPrimKeyEqCond = pJoin->pFullOnCond;
|
|
pJoinOnCond = NULL;
|
|
} else {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pJoin->pPrimKeyEqCond = pPrimKeyEqCond;
|
|
pJoin->pFullOnCond = pJoinOnCond;
|
|
} else {
|
|
nodesDestroyNode(pPrimKeyEqCond);
|
|
nodesDestroyNode(pJoinOnCond);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t pdcJoinIsEqualOnCond(SJoinLogicNode* pJoin, SNode* pCond, bool* allTags, bool* pRes) {
|
|
*pRes = false;
|
|
int32_t code = 0;
|
|
if (QUERY_NODE_OPERATOR != nodeType(pCond)) {
|
|
return code;
|
|
}
|
|
SOperatorNode* pOper = (SOperatorNode*)pCond;
|
|
if ((QUERY_NODE_COLUMN != nodeType(pOper->pLeft) && !(QUERY_NODE_OPERATOR == nodeType(pOper->pLeft) && OP_TYPE_JSON_GET_VALUE ==((SOperatorNode*)pOper->pLeft)->opType))
|
|
|| NULL == pOper->pRight ||
|
|
(QUERY_NODE_COLUMN != nodeType(pOper->pRight) && !(QUERY_NODE_OPERATOR == nodeType(pOper->pRight) && OP_TYPE_JSON_GET_VALUE ==((SOperatorNode*)pOper->pRight)->opType))) {
|
|
return code;
|
|
}
|
|
|
|
if (OP_TYPE_EQUAL != pOper->opType) {
|
|
return code;
|
|
}
|
|
|
|
if ((QUERY_NODE_OPERATOR == nodeType(pOper->pLeft) || QUERY_NODE_OPERATOR == nodeType(pOper->pRight)) &&
|
|
!(IS_ASOF_JOIN(pJoin->subType) || IS_WINDOW_JOIN(pJoin->subType))) {
|
|
return code;
|
|
}
|
|
|
|
SColumnNode* pLeft = (SColumnNode*)(pOper->pLeft);
|
|
SColumnNode* pRight = (SColumnNode*)(pOper->pRight);
|
|
|
|
if (QUERY_NODE_OPERATOR == nodeType(pOper->pLeft)) {
|
|
pLeft = (SColumnNode*)((SOperatorNode*)pOper->pLeft)->pLeft;
|
|
}
|
|
|
|
if (QUERY_NODE_OPERATOR == nodeType(pOper->pRight)) {
|
|
pRight = (SColumnNode*)((SOperatorNode*)pOper->pRight)->pLeft;
|
|
}
|
|
|
|
*allTags = (COLUMN_TYPE_TAG == pLeft->colType) && (COLUMN_TYPE_TAG == pRight->colType);
|
|
|
|
if (pLeft->node.resType.type != pRight->node.resType.type ||
|
|
pLeft->node.resType.bytes != pRight->node.resType.bytes) {
|
|
return code;
|
|
}
|
|
SNodeList* pLeftCols = ((SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 0))->pTargets;
|
|
SNodeList* pRightCols = ((SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 1))->pTargets;
|
|
bool isEqual = false;
|
|
if (pdcJoinColInTableColList((SNode*)pLeft, pLeftCols)) {
|
|
isEqual = pdcJoinColInTableColList((SNode*)pRight, pRightCols);
|
|
if (isEqual) {
|
|
SNode* pNewLeft = NULL;
|
|
code = nodesCloneNode(pOper->pLeft, &pNewLeft);
|
|
if (TSDB_CODE_SUCCESS != code) return code;
|
|
SNode* pNewRight = NULL;
|
|
code = nodesCloneNode(pOper->pRight, &pNewRight);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode(pNewLeft);
|
|
return code;
|
|
}
|
|
code = nodesListMakeStrictAppend(&pJoin->pLeftEqNodes, pNewLeft);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode(pNewRight);
|
|
return code;
|
|
}
|
|
code = nodesListMakeStrictAppend(&pJoin->pRightEqNodes, pNewRight);
|
|
}
|
|
} else if (pdcJoinColInTableColList((SNode*)pLeft, pRightCols)) {
|
|
isEqual = pdcJoinColInTableColList((SNode*)pRight, pLeftCols);
|
|
if (isEqual) {
|
|
SNode* pNewLeft = NULL;
|
|
code = nodesCloneNode(pOper->pLeft, &pNewLeft);
|
|
if (TSDB_CODE_SUCCESS != code) return code;
|
|
SNode* pNewRight = NULL;
|
|
code = nodesCloneNode(pOper->pRight, &pNewRight);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode(pNewLeft);
|
|
return code;
|
|
}
|
|
code = nodesListMakeStrictAppend(&pJoin->pLeftEqNodes, pNewRight);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode(pNewLeft);
|
|
return code;
|
|
}
|
|
code = nodesListMakeStrictAppend(&pJoin->pRightEqNodes, pNewLeft);
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pRes = isEqual;
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t pdcJoinPartLogicEqualOnCond(SJoinLogicNode* pJoin) {
|
|
SLogicConditionNode* pLogicCond = (SLogicConditionNode*)(pJoin->pFullOnCond);
|
|
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SNodeList* pColEqOnConds = NULL;
|
|
SNodeList* pTagEqOnConds = NULL;
|
|
SNodeList* pTagOnConds = NULL;
|
|
SNodeList* pColOnConds = NULL;
|
|
SNode* pCond = NULL;
|
|
bool allTags = false;
|
|
FOREACH(pCond, pLogicCond->pParameterList) {
|
|
allTags = false;
|
|
bool eqOnCond = false;
|
|
SNode* pNew = NULL;
|
|
code = pdcJoinIsEqualOnCond(pJoin, pCond, &allTags, &eqOnCond);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
code = nodesCloneNode(pCond, &pNew);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
|
|
if (eqOnCond) {
|
|
if (allTags) {
|
|
code = nodesListMakeStrictAppend(&pTagEqOnConds, pNew);
|
|
} else {
|
|
code = nodesListMakeStrictAppend(&pColEqOnConds, pNew);
|
|
pJoin->allEqTags = false;
|
|
}
|
|
} else if (allTags) {
|
|
code = nodesListMakeStrictAppend(&pTagOnConds, pNew);
|
|
} else {
|
|
code = nodesListMakeStrictAppend(&pColOnConds, pNew);
|
|
pJoin->allEqTags = false;
|
|
}
|
|
|
|
if (code) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
SNode* pTempTagEqCond = NULL;
|
|
SNode* pTempColEqCond = NULL;
|
|
SNode* pTempTagOnCond = NULL;
|
|
SNode* pTempColOnCond = NULL;
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesMergeConds(&pTempColEqCond, &pColEqOnConds);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesMergeConds(&pTempTagEqCond, &pTagEqOnConds);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesMergeConds(&pTempTagOnCond, &pTagOnConds);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesMergeConds(&pTempColOnCond, &pColOnConds);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pJoin->pColEqCond = pTempColEqCond;
|
|
pJoin->pColOnCond = pTempColOnCond;
|
|
pJoin->pTagEqCond = pTempTagEqCond;
|
|
pJoin->pTagOnCond = pTempTagOnCond;
|
|
} else {
|
|
nodesDestroyList(pColEqOnConds);
|
|
nodesDestroyList(pTagEqOnConds);
|
|
nodesDestroyList(pColOnConds);
|
|
nodesDestroyList(pTagOnConds);
|
|
nodesDestroyNode(pTempTagEqCond);
|
|
nodesDestroyNode(pTempColEqCond);
|
|
nodesDestroyNode(pTempTagOnCond);
|
|
nodesDestroyNode(pTempColOnCond);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t pdcJoinPartEqualOnCond(SOptimizeContext* pCxt, SJoinLogicNode* pJoin) {
|
|
if (NULL == pJoin->pFullOnCond) {
|
|
pJoin->pColEqCond = NULL;
|
|
pJoin->pTagEqCond = NULL;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
pJoin->allEqTags = true;
|
|
|
|
if (QUERY_NODE_LOGIC_CONDITION == nodeType(pJoin->pFullOnCond) &&
|
|
LOGIC_COND_TYPE_AND == ((SLogicConditionNode*)(pJoin->pFullOnCond))->condType) {
|
|
return pdcJoinPartLogicEqualOnCond(pJoin);
|
|
}
|
|
|
|
bool allTags = false;
|
|
bool eqOnCond = false;
|
|
int32_t code = pdcJoinIsEqualOnCond(pJoin, pJoin->pFullOnCond, &allTags, &eqOnCond);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pJoin->pFullOnCond, &pNew);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
if (eqOnCond) {
|
|
if (allTags) {
|
|
pJoin->pTagEqCond = pNew;
|
|
} else {
|
|
pJoin->pColEqCond = pNew;
|
|
pJoin->allEqTags = false;
|
|
}
|
|
} else if (allTags) {
|
|
pJoin->pTagOnCond = pNew;
|
|
} else {
|
|
pJoin->pColOnCond = pNew;
|
|
pJoin->allEqTags = false;
|
|
}
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static EDealRes pdcJoinCollectCondCol(SNode* pNode, void* pContext) {
|
|
SCpdCollectTableColCxt* pCxt = pContext;
|
|
if (QUERY_NODE_COLUMN == nodeType(pNode)) {
|
|
if (pdcJoinColInTableList(pNode, pCxt->pTables)) {
|
|
SColumnNode* pCol = (SColumnNode*)pNode;
|
|
char name[TSDB_TABLE_NAME_LEN + TSDB_COL_NAME_LEN];
|
|
int32_t len = 0;
|
|
if ('\0' == pCol->tableAlias[0]) {
|
|
len = snprintf(name, sizeof(name), "%s", pCol->colName);
|
|
} else {
|
|
len = snprintf(name, sizeof(name), "%s.%s", pCol->tableAlias, pCol->colName);
|
|
}
|
|
if (NULL == taosHashGet(pCxt->pColHash, name, len)) {
|
|
pCxt->errCode = taosHashPut(pCxt->pColHash, name, len, NULL, 0);
|
|
if (TSDB_CODE_SUCCESS == pCxt->errCode) {
|
|
SNode* pNew = NULL;
|
|
pCxt->errCode = nodesCloneNode(pNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS == pCxt->errCode) {
|
|
pCxt->errCode = nodesListStrictAppend(pCxt->pResCols, pNew);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return (TSDB_CODE_SUCCESS == pCxt->errCode ? DEAL_RES_CONTINUE : DEAL_RES_ERROR);
|
|
}
|
|
|
|
static int32_t pdcJoinCollectColsFromParent(SJoinLogicNode* pJoin, SSHashObj* pTables, SNodeList* pCondCols) {
|
|
SCpdCollectTableColCxt cxt = {
|
|
.errCode = TSDB_CODE_SUCCESS,
|
|
.pTables = pTables,
|
|
.pResCols = pCondCols,
|
|
.pColHash = taosHashInit(128, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), true, HASH_NO_LOCK)
|
|
};
|
|
if (NULL == cxt.pColHash) {
|
|
return TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
|
|
nodesWalkExpr(pJoin->pPrimKeyEqCond, pdcJoinCollectCondCol, &cxt);
|
|
nodesWalkExpr(pJoin->node.pConditions, pdcJoinCollectCondCol, &cxt);
|
|
if (TSDB_CODE_SUCCESS == cxt.errCode) {
|
|
nodesWalkExpr(pJoin->pFullOnCond, pdcJoinCollectCondCol, &cxt);
|
|
}
|
|
|
|
taosHashCleanup(cxt.pColHash);
|
|
return cxt.errCode;
|
|
}
|
|
|
|
static int32_t pdcJoinAddParentOnColsToTarget(SOptimizeContext* pCxt, SJoinLogicNode* pJoin) {
|
|
if (NULL == pJoin->node.pParent || QUERY_NODE_LOGIC_PLAN_JOIN != nodeType(pJoin->node.pParent)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
SNodeList* pTargets = NULL;
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SNodeList* pCondCols = NULL;
|
|
code = nodesMakeList(&pCondCols);
|
|
if (NULL == pCondCols) {
|
|
return code;
|
|
}
|
|
|
|
SSHashObj* pTables = NULL;
|
|
code = collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 0), &pTables);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
code = collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 1), &pTables);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
tSimpleHashCleanup(pTables);
|
|
return code;
|
|
}
|
|
|
|
SJoinLogicNode* pTmp = (SJoinLogicNode*)pJoin->node.pParent;
|
|
do {
|
|
code = pdcJoinCollectColsFromParent(pTmp, pTables, pCondCols);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
if (NULL == pTmp->node.pParent || QUERY_NODE_LOGIC_PLAN_JOIN != nodeType(pTmp->node.pParent)) {
|
|
break;
|
|
}
|
|
pTmp = (SJoinLogicNode*)pTmp->node.pParent;
|
|
} while (true);
|
|
|
|
tSimpleHashCleanup(pTables);
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = createColumnByRewriteExprs(pCondCols, &pTargets);
|
|
}
|
|
|
|
nodesDestroyList(pCondCols);
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pTargets) {
|
|
SNode* pTmp = NULL;
|
|
bool found = false;
|
|
FOREACH(pTmp, pJoin->node.pTargets) {
|
|
if (nodesEqualNode(pTmp, pNode)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppend(pJoin->node.pTargets, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nodesDestroyList(pTargets);
|
|
|
|
return code;
|
|
}
|
|
|
|
|
|
static int32_t pdcJoinAddPreFilterColsToTarget(SOptimizeContext* pCxt, SJoinLogicNode* pJoin) {
|
|
if (NULL == pJoin->pFullOnCond) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SNodeList* pCondCols = NULL;
|
|
code = nodesMakeList(&pCondCols);
|
|
SNodeList* pTargets = NULL;
|
|
if (NULL == pCondCols) {
|
|
code = code;
|
|
} else {
|
|
code = nodesCollectColumnsFromNode(pJoin->pColOnCond, NULL, COLLECT_COL_TYPE_ALL, &pCondCols);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesCollectColumnsFromNode(pJoin->pTagOnCond, NULL, COLLECT_COL_TYPE_ALL, &pCondCols);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = createColumnByRewriteExprs(pCondCols, &pTargets);
|
|
}
|
|
|
|
nodesDestroyList(pCondCols);
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pTargets) {
|
|
SNode* pTmp = NULL;
|
|
bool found = false;
|
|
FOREACH(pTmp, pJoin->node.pTargets) {
|
|
if (nodesEqualNode(pTmp, pNode)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppend(pJoin->node.pTargets, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nodesDestroyList(pTargets);
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t pdcJoinAddFilterColsToTarget(SOptimizeContext* pCxt, SJoinLogicNode* pJoin) {
|
|
if (NULL == pJoin->node.pConditions && NULL == pJoin->pFullOnCond) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SNodeList* pCondCols = NULL;
|
|
code = nodesMakeList(&pCondCols);
|
|
SNodeList* pTargets = NULL;
|
|
if (NULL == pCondCols) {
|
|
return code;
|
|
}
|
|
|
|
if (NULL != pJoin->node.pConditions) {
|
|
code = nodesCollectColumnsFromNode(pJoin->node.pConditions, NULL, COLLECT_COL_TYPE_ALL, &pCondCols);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pJoin->pFullOnCond) {
|
|
code = nodesCollectColumnsFromNode(pJoin->pFullOnCond, NULL, COLLECT_COL_TYPE_ALL, &pCondCols);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = createColumnByRewriteExprs(pCondCols, &pTargets);
|
|
}
|
|
|
|
nodesDestroyList(pCondCols);
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pTargets) {
|
|
SNode* pTmp = NULL;
|
|
bool found = false;
|
|
FOREACH(pTmp, pJoin->node.pTargets) {
|
|
if (nodesEqualNode(pTmp, pNode)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppend(pJoin->node.pTargets, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nodesDestroyList(pTargets);
|
|
|
|
return code;
|
|
}
|
|
|
|
|
|
static int32_t pdcJoinCheckAllCond(SOptimizeContext* pCxt, SJoinLogicNode* pJoin) {
|
|
if (NULL == pJoin->pFullOnCond) {
|
|
if (IS_WINDOW_JOIN(pJoin->subType) || IS_ASOF_JOIN(pJoin->subType)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
if (NULL == pJoin->node.pConditions) {
|
|
return generateUsageErrMsg(pCxt->pPlanCxt->pMsg, pCxt->pPlanCxt->msgLen, TSDB_CODE_PLAN_NOT_SUPPORT_CROSS_JOIN);
|
|
}
|
|
|
|
if (!IS_INNER_NONE_JOIN(pJoin->joinType, pJoin->subType)) {
|
|
return generateUsageErrMsg(pCxt->pPlanCxt->pMsg, pCxt->pPlanCxt->msgLen, TSDB_CODE_PLAN_NOT_SUPPORT_CROSS_JOIN);
|
|
}
|
|
}
|
|
|
|
SNode* pCond = pJoin->pFullOnCond ? pJoin->pFullOnCond : pJoin->node.pConditions;
|
|
bool errCond = false;
|
|
if (!pdcJoinHasPrimEqualCond(pJoin, pCond, &errCond)) {
|
|
if (errCond && !(IS_INNER_NONE_JOIN(pJoin->joinType, pJoin->subType) && NULL != pJoin->pFullOnCond && NULL != pJoin->node.pConditions)) {
|
|
return generateUsageErrMsg(pCxt->pPlanCxt->pMsg, pCxt->pPlanCxt->msgLen, TSDB_CODE_PLAN_NOT_SUPPORT_JOIN_COND);
|
|
}
|
|
|
|
if (IS_INNER_NONE_JOIN(pJoin->joinType, pJoin->subType) && NULL != pJoin->pFullOnCond && NULL != pJoin->node.pConditions) {
|
|
if (pdcJoinHasPrimEqualCond(pJoin, pJoin->node.pConditions, &errCond)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
if (errCond) {
|
|
return generateUsageErrMsg(pCxt->pPlanCxt->pMsg, pCxt->pPlanCxt->msgLen, TSDB_CODE_PLAN_NOT_SUPPORT_JOIN_COND);
|
|
}
|
|
}
|
|
|
|
if (IS_WINDOW_JOIN(pJoin->subType) || IS_ASOF_JOIN(pJoin->subType)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
return generateUsageErrMsg(pCxt->pPlanCxt->pMsg, pCxt->pPlanCxt->msgLen, TSDB_CODE_PLAN_EXPECTED_TS_EQUAL);
|
|
}
|
|
|
|
if (IS_ASOF_JOIN(pJoin->subType)) {
|
|
nodesDestroyNode(pJoin->addPrimEqCond);
|
|
pJoin->addPrimEqCond = NULL;
|
|
}
|
|
|
|
if (IS_WINDOW_JOIN(pJoin->subType)) {
|
|
return generateUsageErrMsg(pCxt->pPlanCxt->pMsg, pCxt->pPlanCxt->msgLen, TSDB_CODE_PLAN_NOT_SUPPORT_JOIN_COND);
|
|
}
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static int32_t pdcJoinHandleGrpJoinCond(SOptimizeContext* pCxt, SJoinLogicNode* pJoin) {
|
|
switch (pJoin->subType) {
|
|
case JOIN_STYPE_ASOF:
|
|
case JOIN_STYPE_WIN:
|
|
if (NULL != pJoin->pColOnCond || NULL != pJoin->pTagOnCond) {
|
|
return generateUsageErrMsg(pCxt->pPlanCxt->pMsg, pCxt->pPlanCxt->msgLen, TSDB_CODE_PLAN_NOT_SUPPORT_JOIN_COND);
|
|
}
|
|
nodesDestroyNode(pJoin->pColEqCond);
|
|
pJoin->pColEqCond = NULL;
|
|
nodesDestroyNode(pJoin->pTagEqCond);
|
|
pJoin->pTagEqCond = NULL;
|
|
nodesDestroyNode(pJoin->pFullOnCond);
|
|
pJoin->pFullOnCond = NULL;
|
|
if (!pJoin->allEqTags) {
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pJoin->pLeftEqNodes) {
|
|
SColumnNode* pCol = (SColumnNode*)pNode;
|
|
if (COLUMN_TYPE_TAG != pCol->colType && PRIMARYKEY_TIMESTAMP_COL_ID == pCol->colId) {
|
|
return generateUsageErrMsg(pCxt->pPlanCxt->pMsg, pCxt->pPlanCxt->msgLen, TSDB_CODE_PLAN_NOT_SUPPORT_JOIN_COND);
|
|
}
|
|
}
|
|
FOREACH(pNode, pJoin->pRightEqNodes) {
|
|
SColumnNode* pCol = (SColumnNode*)pNode;
|
|
if (COLUMN_TYPE_TAG != pCol->colType && PRIMARYKEY_TIMESTAMP_COL_ID == pCol->colId) {
|
|
return generateUsageErrMsg(pCxt->pPlanCxt->pMsg, pCxt->pPlanCxt->msgLen, TSDB_CODE_PLAN_NOT_SUPPORT_JOIN_COND);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static EDealRes pdcCheckTableCondType(SNode* pNode, void* pContext) {
|
|
SCpdIsMultiTableCondCxt* pCxt = pContext;
|
|
switch (nodeType(pNode)) {
|
|
case QUERY_NODE_COLUMN: {
|
|
if (pdcJoinColInTableList(pNode, pCxt->pLeftTbls)) {
|
|
pCxt->havaLeftCol = true;
|
|
} else if (pdcJoinColInTableList(pNode, pCxt->pRightTbls)) {
|
|
pCxt->haveRightCol = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case QUERY_NODE_OPERATOR: {
|
|
SOperatorNode* pOp = (SOperatorNode*)pNode;
|
|
if (OP_TYPE_IS_NULL == pOp->opType) {
|
|
pCxt->condIsNull = true;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static int32_t pdcJoinGetOpTableCondTypes(SNode* pCond, SSHashObj* pLeftTables, SSHashObj* pRightTables, bool* tableCondTypes) {
|
|
SCpdIsMultiTableCondCxt cxt = {
|
|
.pLeftTbls = pLeftTables, .pRightTbls = pRightTables, .havaLeftCol = false, .haveRightCol = false, .condIsNull = false};
|
|
nodesWalkExpr(pCond, pdcCheckTableCondType, &cxt);
|
|
|
|
if (cxt.havaLeftCol) {
|
|
if (cxt.haveRightCol) {
|
|
if (cxt.condIsNull) {
|
|
tableCondTypes[1] = true;
|
|
tableCondTypes[3] = true;
|
|
} else {
|
|
tableCondTypes[0] = true;
|
|
tableCondTypes[2] = true;
|
|
}
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
if (cxt.condIsNull) {
|
|
tableCondTypes[1] = true;
|
|
} else {
|
|
tableCondTypes[0] = true;
|
|
}
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
if (cxt.haveRightCol) {
|
|
if (cxt.condIsNull) {
|
|
tableCondTypes[3] = true;
|
|
} else {
|
|
tableCondTypes[2] = true;
|
|
}
|
|
}
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
static int32_t pdcJoinGetLogicTableCondTypes(SNode* pCond, SSHashObj* pLeftTables, SSHashObj* pRightTables, bool* tableCondTypes) {
|
|
SLogicConditionNode* pLogicCond = (SLogicConditionNode*)pCond;
|
|
int32_t code = 0;
|
|
SNode* pSCond = NULL;
|
|
FOREACH(pSCond, pLogicCond->pParameterList) {
|
|
if (QUERY_NODE_LOGIC_CONDITION == nodeType(pSCond)) {
|
|
code = pdcJoinGetLogicTableCondTypes(pSCond, pLeftTables, pRightTables, tableCondTypes);
|
|
} else {
|
|
code = pdcJoinGetOpTableCondTypes(pSCond, pLeftTables, pRightTables, tableCondTypes);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t pdcGetTableCondTypes(SNode* pCond, SSHashObj* pLeftTables, SSHashObj* pRightTables, bool* tableCondTypes) {
|
|
if (QUERY_NODE_LOGIC_CONDITION == nodeType(pCond)) {
|
|
return pdcJoinGetLogicTableCondTypes(pCond, pLeftTables, pRightTables, tableCondTypes);
|
|
} else {
|
|
return pdcJoinGetOpTableCondTypes(pCond, pLeftTables, pRightTables, tableCondTypes);
|
|
}
|
|
}
|
|
|
|
static int32_t pdcRewriteTypeBasedOnConds(SOptimizeContext* pCxt, SJoinLogicNode* pJoin) {
|
|
if (JOIN_TYPE_INNER == pJoin->joinType || JOIN_STYPE_OUTER != pJoin->subType) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
SSHashObj* pLeftTables = NULL;
|
|
SSHashObj* pRightTables = NULL;
|
|
int32_t code = collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 0), &pLeftTables);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
|
|
code = collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 1), &pRightTables);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
tSimpleHashCleanup(pLeftTables);
|
|
return code;
|
|
}
|
|
|
|
bool tableCondTypes[4] = {0};
|
|
code = pdcGetTableCondTypes(pJoin->node.pConditions, pLeftTables, pRightTables, tableCondTypes);
|
|
tSimpleHashCleanup(pLeftTables);
|
|
tSimpleHashCleanup(pRightTables);
|
|
|
|
if (TSDB_CODE_SUCCESS != code) return code;
|
|
|
|
switch (pJoin->joinType) {
|
|
case JOIN_TYPE_LEFT:
|
|
if (tableCondTypes[2] && !tableCondTypes[3]) {
|
|
pJoin->joinType = JOIN_TYPE_INNER;
|
|
pJoin->subType = JOIN_STYPE_NONE;
|
|
}
|
|
break;
|
|
case JOIN_TYPE_RIGHT:
|
|
if (tableCondTypes[0] && !tableCondTypes[1]) {
|
|
pJoin->joinType = JOIN_TYPE_INNER;
|
|
pJoin->subType = JOIN_STYPE_NONE;
|
|
}
|
|
break;
|
|
case JOIN_TYPE_FULL:
|
|
if (tableCondTypes[0] && !tableCondTypes[1]) {
|
|
if (tableCondTypes[2] && !tableCondTypes[3]) {
|
|
pJoin->joinType = JOIN_TYPE_INNER;
|
|
pJoin->subType = JOIN_STYPE_NONE;
|
|
} else {
|
|
pJoin->joinType = JOIN_TYPE_LEFT;
|
|
}
|
|
} else if (tableCondTypes[2] && !tableCondTypes[3]) {
|
|
pJoin->joinType = JOIN_TYPE_RIGHT;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static EDealRes pdcCheckTableResType(SNode* pNode, void* pContext) {
|
|
SCpdIsMultiTableResCxt* pCxt = pContext;
|
|
switch (nodeType(pNode)) {
|
|
case QUERY_NODE_COLUMN: {
|
|
if (pdcJoinColInTableList(pNode, pCxt->pLeftTbls)) {
|
|
pCxt->haveLeftCol = true;
|
|
} else if (pdcJoinColInTableList(pNode, pCxt->pRightTbls)) {
|
|
pCxt->haveRightCol = true;
|
|
}
|
|
break;
|
|
}
|
|
case QUERY_NODE_VALUE:
|
|
case QUERY_NODE_GROUPING_SET:
|
|
break;
|
|
case QUERY_NODE_FUNCTION: {
|
|
SFunctionNode* pFunc = (SFunctionNode*)pNode;
|
|
SCpdIsMultiTableResCxt cxt = {.pLeftTbls = pCxt->pLeftTbls, .pRightTbls = pCxt->pRightTbls,
|
|
.haveLeftCol = false, .haveRightCol = false, .leftColNonNull = true, .rightColNonNull = true};
|
|
|
|
nodesWalkExprs(pFunc->pParameterList, pdcCheckTableResType, &cxt);
|
|
if (!cxt.leftColNonNull) {
|
|
pCxt->leftColNonNull = false;
|
|
}
|
|
if (!cxt.rightColNonNull) {
|
|
pCxt->rightColNonNull = false;
|
|
}
|
|
if (cxt.leftColOp) {
|
|
pCxt->leftColOp = true;
|
|
}
|
|
if (cxt.rightColOp) {
|
|
pCxt->rightColOp = true;
|
|
}
|
|
if (!cxt.haveLeftCol && !cxt.haveRightCol) {
|
|
pCxt->leftColNonNull = false;
|
|
pCxt->rightColNonNull = false;
|
|
return DEAL_RES_END;
|
|
} else if (!fmIsIgnoreNullFunc(pFunc->funcId)) {
|
|
if (cxt.haveLeftCol) {
|
|
pCxt->leftColNonNull = false;
|
|
}
|
|
if (cxt.haveRightCol) {
|
|
pCxt->rightColNonNull = false;
|
|
}
|
|
} else {
|
|
if (cxt.haveLeftCol) {
|
|
pCxt->leftColOp = true;
|
|
} else if (cxt.haveRightCol) {
|
|
pCxt->rightColOp = true;
|
|
}
|
|
}
|
|
if (!pCxt->leftColNonNull && !pCxt->rightColNonNull) {
|
|
return DEAL_RES_END;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
pCxt->leftColNonNull = false;
|
|
pCxt->rightColNonNull = false;
|
|
return DEAL_RES_END;
|
|
}
|
|
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static int32_t pdcRewriteTypeBasedOnJoinRes(SOptimizeContext* pCxt, SJoinLogicNode* pJoin) {
|
|
if (JOIN_TYPE_INNER == pJoin->joinType || JOIN_STYPE_OUTER != pJoin->subType) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
int32_t code = 0;
|
|
SSHashObj* pLeftTables = NULL;
|
|
SSHashObj* pRightTables = NULL;
|
|
code = collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 0), &pLeftTables);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
code = collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 1), &pRightTables);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
tSimpleHashCleanup(pLeftTables);
|
|
return code;
|
|
}
|
|
|
|
SLogicNode* pParent = pJoin->node.pParent;
|
|
bool tableResNonNull[2] = {true, true};
|
|
bool tableResOp[2] = {false, false};
|
|
if (QUERY_NODE_LOGIC_PLAN_AGG == nodeType(pParent)) {
|
|
SAggLogicNode* pAgg = (SAggLogicNode*)pParent;
|
|
if (NULL != pAgg->pGroupKeys) {
|
|
tableResNonNull[0] = false;
|
|
tableResNonNull[1] = false;
|
|
} else {
|
|
SCpdIsMultiTableResCxt cxt = {.pLeftTbls = pLeftTables, .pRightTbls = pRightTables,
|
|
.haveLeftCol = false, .haveRightCol = false, .leftColNonNull = true, .rightColNonNull = true, .leftColOp = false, .rightColOp = false};
|
|
|
|
nodesWalkExprs(pAgg->pAggFuncs, pdcCheckTableResType, &cxt);
|
|
if (!cxt.leftColNonNull) {
|
|
tableResNonNull[0] = false;
|
|
}
|
|
if (!cxt.rightColNonNull) {
|
|
tableResNonNull[1] = false;
|
|
}
|
|
if (cxt.leftColOp) {
|
|
tableResOp[0] = true;
|
|
}
|
|
if (cxt.rightColOp) {
|
|
tableResOp[1] = true;
|
|
}
|
|
}
|
|
} else {
|
|
tableResNonNull[0] = false;
|
|
tableResNonNull[1] = false;
|
|
}
|
|
|
|
tSimpleHashCleanup(pLeftTables);
|
|
tSimpleHashCleanup(pRightTables);
|
|
|
|
switch (pJoin->joinType) {
|
|
case JOIN_TYPE_LEFT:
|
|
if (tableResNonNull[1] && !tableResOp[0]) {
|
|
pJoin->joinType = JOIN_TYPE_INNER;
|
|
pJoin->subType = JOIN_STYPE_NONE;
|
|
}
|
|
break;
|
|
case JOIN_TYPE_RIGHT:
|
|
if (tableResNonNull[0] && !tableResOp[1]) {
|
|
pJoin->joinType = JOIN_TYPE_INNER;
|
|
pJoin->subType = JOIN_STYPE_NONE;
|
|
}
|
|
break;
|
|
case JOIN_TYPE_FULL:
|
|
if (tableResNonNull[1] && !tableResOp[0]) {
|
|
if (tableResNonNull[0] && !tableResOp[1]) {
|
|
pJoin->joinType = JOIN_TYPE_INNER;
|
|
pJoin->subType = JOIN_STYPE_NONE;
|
|
} else {
|
|
pJoin->joinType = JOIN_TYPE_RIGHT;
|
|
}
|
|
} else if (tableResNonNull[0] && !tableResOp[1]) {
|
|
pJoin->joinType = JOIN_TYPE_LEFT;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static int32_t pdcDealJoin(SOptimizeContext* pCxt, SJoinLogicNode* pJoin) {
|
|
if (OPTIMIZE_FLAG_TEST_MASK(pJoin->node.optimizedFlag, OPTIMIZE_FLAG_PUSH_DOWN_CONDE)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
if (pJoin->joinAlgo != JOIN_ALGO_UNKNOWN) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
EJoinType t = pJoin->joinType;
|
|
EJoinSubType s = pJoin->subType;
|
|
SNode* pOnCond = NULL;
|
|
SNode* pLeftChildCond = NULL;
|
|
SNode* pRightChildCond = NULL;
|
|
int32_t code = pdcJoinCheckAllCond(pCxt, pJoin);
|
|
while (true) {
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pJoin->node.pConditions) {
|
|
if (0 != gJoinWhereOpt[t][s].pushDownFlag) {
|
|
code = pdcJoinSplitCond(pJoin, &pJoin->node.pConditions, &pOnCond, &pLeftChildCond, &pRightChildCond, true);
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pOnCond) {
|
|
code = pdcJoinPushDownOnCond(pCxt, pJoin, &pOnCond);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pLeftChildCond) {
|
|
code = pdcPushDownCondToChild(pCxt, (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 0), &pLeftChildCond);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pRightChildCond) {
|
|
code = pdcPushDownCondToChild(pCxt, (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 1), &pRightChildCond);
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pJoin->node.pConditions) {
|
|
code = pdcRewriteTypeBasedOnConds(pCxt, pJoin);
|
|
}
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = pdcRewriteTypeBasedOnJoinRes(pCxt, pJoin);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS != code || t == pJoin->joinType) {
|
|
break;
|
|
}
|
|
|
|
t = pJoin->joinType;
|
|
s = pJoin->subType;
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pJoin->pFullOnCond && 0 != gJoinOnOpt[t][s].pushDownFlag) {
|
|
code = pdcJoinSplitCond(pJoin, &pJoin->pFullOnCond, NULL, &pLeftChildCond, &pRightChildCond, false);
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pLeftChildCond) {
|
|
code = pdcPushDownCondToChild(pCxt, (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 0), &pLeftChildCond);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pRightChildCond) {
|
|
code = pdcPushDownCondToChild(pCxt, (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 1), &pRightChildCond);
|
|
}
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pJoin->pFullOnCond && !IS_WINDOW_JOIN(pJoin->subType) && NULL == pJoin->addPrimEqCond) {
|
|
code = pdcJoinSplitPrimEqCond(pCxt, pJoin);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = pdcJoinPartEqualOnCond(pCxt, pJoin);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = pdcJoinHandleGrpJoinCond(pCxt, pJoin);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = pdcJoinAddParentOnColsToTarget(pCxt, pJoin);
|
|
}
|
|
|
|
//if (TSDB_CODE_SUCCESS == code) {
|
|
// code = pdcJoinAddPreFilterColsToTarget(pCxt, pJoin);
|
|
//}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = pdcJoinAddFilterColsToTarget(pCxt, pJoin);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
OPTIMIZE_FLAG_SET_MASK(pJoin->node.optimizedFlag, OPTIMIZE_FLAG_PUSH_DOWN_CONDE);
|
|
pCxt->optimized = true;
|
|
} else {
|
|
nodesDestroyNode(pOnCond);
|
|
nodesDestroyNode(pLeftChildCond);
|
|
nodesDestroyNode(pRightChildCond);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
typedef struct SPartAggCondContext {
|
|
SAggLogicNode* pAgg;
|
|
bool hasAggFunc;
|
|
} SPartAggCondContext;
|
|
|
|
static EDealRes partAggCondHasAggFuncImpl(SNode* pNode, void* pContext) {
|
|
SPartAggCondContext* pCxt = pContext;
|
|
if (QUERY_NODE_COLUMN == nodeType(pNode)) {
|
|
SNode* pAggFunc = NULL;
|
|
FOREACH(pAggFunc, pCxt->pAgg->pAggFuncs) {
|
|
if (strcmp(((SColumnNode*)pNode)->colName, ((SFunctionNode*)pAggFunc)->node.aliasName) == 0) {
|
|
pCxt->hasAggFunc = true;
|
|
return DEAL_RES_END;
|
|
}
|
|
}
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static int32_t partitionAggCondHasAggFunc(SAggLogicNode* pAgg, SNode* pCond) {
|
|
SPartAggCondContext cxt = {.pAgg = pAgg, .hasAggFunc = false};
|
|
nodesWalkExpr(pCond, partAggCondHasAggFuncImpl, &cxt);
|
|
return cxt.hasAggFunc;
|
|
}
|
|
|
|
static int32_t partitionAggCondConj(SAggLogicNode* pAgg, SNode** ppAggFuncCond, SNode** ppGroupKeyCond) {
|
|
SLogicConditionNode* pLogicCond = (SLogicConditionNode*)pAgg->node.pConditions;
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
|
|
SNodeList* pAggFuncConds = NULL;
|
|
SNodeList* pGroupKeyConds = NULL;
|
|
SNode* pCond = NULL;
|
|
FOREACH(pCond, pLogicCond->pParameterList) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pCond, &pNew);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
if (partitionAggCondHasAggFunc(pAgg, pCond)) {
|
|
code = nodesListMakeStrictAppend(&pAggFuncConds, pNew);
|
|
} else {
|
|
code = nodesListMakeStrictAppend(&pGroupKeyConds, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
SNode* pTempAggFuncCond = NULL;
|
|
SNode* pTempGroupKeyCond = NULL;
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesMergeConds(&pTempAggFuncCond, &pAggFuncConds);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesMergeConds(&pTempGroupKeyCond, &pGroupKeyConds);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*ppAggFuncCond = pTempAggFuncCond;
|
|
*ppGroupKeyCond = pTempGroupKeyCond;
|
|
} else {
|
|
nodesDestroyList(pAggFuncConds);
|
|
nodesDestroyList(pGroupKeyConds);
|
|
nodesDestroyNode(pTempAggFuncCond);
|
|
nodesDestroyNode(pTempGroupKeyCond);
|
|
}
|
|
nodesDestroyNode(pAgg->node.pConditions);
|
|
pAgg->node.pConditions = NULL;
|
|
return code;
|
|
}
|
|
|
|
static int32_t partitionAggCond(SAggLogicNode* pAgg, SNode** ppAggFunCond, SNode** ppGroupKeyCond) {
|
|
SNode* pAggNodeCond = pAgg->node.pConditions;
|
|
if (QUERY_NODE_LOGIC_CONDITION == nodeType(pAggNodeCond) &&
|
|
LOGIC_COND_TYPE_AND == ((SLogicConditionNode*)(pAggNodeCond))->condType) {
|
|
return partitionAggCondConj(pAgg, ppAggFunCond, ppGroupKeyCond);
|
|
}
|
|
if (partitionAggCondHasAggFunc(pAgg, pAggNodeCond)) {
|
|
*ppAggFunCond = pAggNodeCond;
|
|
} else {
|
|
*ppGroupKeyCond = pAggNodeCond;
|
|
}
|
|
pAgg->node.pConditions = NULL;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static int32_t pushCondToAggCond(SOptimizeContext* pCxt, SAggLogicNode* pAgg, SNode** pAggFuncCond) {
|
|
return pdcMergeConds(&pAgg->node.pConditions, pAggFuncCond);
|
|
}
|
|
|
|
typedef struct SRewriteAggGroupKeyCondContext {
|
|
SAggLogicNode* pAgg;
|
|
int32_t errCode;
|
|
} SRewriteAggGroupKeyCondContext;
|
|
|
|
static EDealRes rewriteAggGroupKeyCondForPushDownImpl(SNode** pNode, void* pContext) {
|
|
SRewriteAggGroupKeyCondContext* pCxt = pContext;
|
|
SAggLogicNode* pAgg = pCxt->pAgg;
|
|
if (QUERY_NODE_COLUMN == nodeType(*pNode)) {
|
|
SNode* pGroupKey = NULL;
|
|
FOREACH(pGroupKey, pAgg->pGroupKeys) {
|
|
SNode* pGroup = NULL;
|
|
FOREACH(pGroup, ((SGroupingSetNode*)pGroupKey)->pParameterList) {
|
|
if (0 == strcmp(((SExprNode*)pGroup)->aliasName, ((SColumnNode*)(*pNode))->colName)) {
|
|
SNode* pExpr = NULL;
|
|
pCxt->errCode = nodesCloneNode(pGroup, &pExpr);
|
|
if (pExpr == NULL) {
|
|
return DEAL_RES_ERROR;
|
|
}
|
|
nodesDestroyNode(*pNode);
|
|
*pNode = pExpr;
|
|
return DEAL_RES_IGNORE_CHILD;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static int32_t rewriteAggGroupKeyCondForPushDown(SOptimizeContext* pCxt, SAggLogicNode* pAgg, SNode* pGroupKeyCond) {
|
|
SRewriteAggGroupKeyCondContext cxt = {.pAgg = pAgg, .errCode = TSDB_CODE_SUCCESS};
|
|
nodesRewriteExpr(&pGroupKeyCond, rewriteAggGroupKeyCondForPushDownImpl, &cxt);
|
|
return cxt.errCode;
|
|
}
|
|
|
|
static int32_t pdcDealAgg(SOptimizeContext* pCxt, SAggLogicNode* pAgg) {
|
|
if (NULL == pAgg->node.pConditions ||
|
|
OPTIMIZE_FLAG_TEST_MASK(pAgg->node.optimizedFlag, OPTIMIZE_FLAG_PUSH_DOWN_CONDE)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
// TODO: remove it after full implementation of pushing down to child
|
|
if (1 != LIST_LENGTH(pAgg->node.pChildren)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
SNode* pAggFuncCond = NULL;
|
|
SNode* pGroupKeyCond = NULL;
|
|
int32_t code = partitionAggCond(pAgg, &pAggFuncCond, &pGroupKeyCond);
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pAggFuncCond) {
|
|
code = pushCondToAggCond(pCxt, pAgg, &pAggFuncCond);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pGroupKeyCond) {
|
|
code = rewriteAggGroupKeyCondForPushDown(pCxt, pAgg, pGroupKeyCond);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pGroupKeyCond) {
|
|
SLogicNode* pChild = (SLogicNode*)nodesListGetNode(pAgg->node.pChildren, 0);
|
|
code = pdcPushDownCondToChild(pCxt, pChild, &pGroupKeyCond);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
OPTIMIZE_FLAG_SET_MASK(pAgg->node.optimizedFlag, OPTIMIZE_FLAG_PUSH_DOWN_CONDE);
|
|
pCxt->optimized = true;
|
|
} else {
|
|
nodesDestroyNode(pGroupKeyCond);
|
|
nodesDestroyNode(pAggFuncCond);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
typedef struct SRewriteProjCondContext {
|
|
SProjectLogicNode* pProj;
|
|
int32_t errCode;
|
|
} SRewriteProjCondContext;
|
|
|
|
static EDealRes rewriteProjectCondForPushDownImpl(SNode** ppNode, void* pContext) {
|
|
SRewriteProjCondContext* pCxt = pContext;
|
|
SProjectLogicNode* pProj = pCxt->pProj;
|
|
if (QUERY_NODE_COLUMN == nodeType(*ppNode)) {
|
|
SNode* pTarget = NULL;
|
|
FOREACH(pTarget, pProj->node.pTargets) {
|
|
if (nodesEqualNode(pTarget, *ppNode)) {
|
|
SNode* pProjection = NULL;
|
|
FOREACH(pProjection, pProj->pProjections) {
|
|
if (0 == strcmp(((SExprNode*)pProjection)->aliasName, ((SColumnNode*)(*ppNode))->colName)) {
|
|
SNode* pExpr = NULL;
|
|
pCxt->errCode = nodesCloneNode(pProjection, &pExpr);
|
|
if (pExpr == NULL) {
|
|
return DEAL_RES_ERROR;
|
|
}
|
|
nodesDestroyNode(*ppNode);
|
|
*ppNode = pExpr;
|
|
return DEAL_RES_IGNORE_CHILD;
|
|
} // end if expr alias name equal column name
|
|
} // end for each project
|
|
} // end if target node equals cond column node
|
|
} // end for each targets
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static int32_t rewriteProjectCondForPushDown(SOptimizeContext* pCxt, SProjectLogicNode* pProject,
|
|
SNode** ppProjectCond) {
|
|
SRewriteProjCondContext cxt = {.pProj = pProject, .errCode = TSDB_CODE_SUCCESS};
|
|
SNode* pProjectCond = pProject->node.pConditions;
|
|
nodesRewriteExpr(&pProjectCond, rewriteProjectCondForPushDownImpl, &cxt);
|
|
*ppProjectCond = pProjectCond;
|
|
pProject->node.pConditions = NULL;
|
|
return cxt.errCode;
|
|
}
|
|
|
|
static int32_t pdcDealProject(SOptimizeContext* pCxt, SProjectLogicNode* pProject) {
|
|
if (NULL == pProject->node.pConditions ||
|
|
OPTIMIZE_FLAG_TEST_MASK(pProject->node.optimizedFlag, OPTIMIZE_FLAG_PUSH_DOWN_CONDE)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
// TODO: remove it after full implementation of pushing down to child
|
|
if (1 != LIST_LENGTH(pProject->node.pChildren)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
if (NULL != pProject->node.pLimit || NULL != pProject->node.pSlimit) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SNode* pProjCond = NULL;
|
|
code = rewriteProjectCondForPushDown(pCxt, pProject, &pProjCond);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
SLogicNode* pChild = (SLogicNode*)nodesListGetNode(pProject->node.pChildren, 0);
|
|
code = pdcPushDownCondToChild(pCxt, pChild, &pProjCond);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
OPTIMIZE_FLAG_SET_MASK(pProject->node.optimizedFlag, OPTIMIZE_FLAG_PUSH_DOWN_CONDE);
|
|
pCxt->optimized = true;
|
|
} else {
|
|
nodesDestroyNode(pProjCond);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t pdcTrivialPushDown(SOptimizeContext* pCxt, SLogicNode* pLogicNode) {
|
|
if (NULL == pLogicNode->pConditions ||
|
|
OPTIMIZE_FLAG_TEST_MASK(pLogicNode->optimizedFlag, OPTIMIZE_FLAG_PUSH_DOWN_CONDE)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
SLogicNode* pChild = (SLogicNode*)nodesListGetNode(pLogicNode->pChildren, 0);
|
|
int32_t code = pdcPushDownCondToChild(pCxt, pChild, &pLogicNode->pConditions);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
OPTIMIZE_FLAG_SET_MASK(pLogicNode->optimizedFlag, OPTIMIZE_FLAG_PUSH_DOWN_CONDE);
|
|
pCxt->optimized = true;
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t pdcOptimizeImpl(SOptimizeContext* pCxt, SLogicNode* pLogicNode) {
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
switch (nodeType(pLogicNode)) {
|
|
case QUERY_NODE_LOGIC_PLAN_SCAN:
|
|
code = pdcDealScan(pCxt, (SScanLogicNode*)pLogicNode);
|
|
break;
|
|
case QUERY_NODE_LOGIC_PLAN_JOIN:
|
|
code = pdcDealJoin(pCxt, (SJoinLogicNode*)pLogicNode);
|
|
break;
|
|
case QUERY_NODE_LOGIC_PLAN_AGG:
|
|
code = pdcDealAgg(pCxt, (SAggLogicNode*)pLogicNode);
|
|
break;
|
|
case QUERY_NODE_LOGIC_PLAN_PROJECT:
|
|
code = pdcDealProject(pCxt, (SProjectLogicNode*)pLogicNode);
|
|
break;
|
|
case QUERY_NODE_LOGIC_PLAN_SORT:
|
|
case QUERY_NODE_LOGIC_PLAN_PARTITION:
|
|
code = pdcTrivialPushDown(pCxt, pLogicNode);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
SNode* pChild = NULL;
|
|
FOREACH(pChild, pLogicNode->pChildren) {
|
|
code = pdcOptimizeImpl(pCxt, (SLogicNode*)pChild);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t pdcOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
return pdcOptimizeImpl(pCxt, pLogicSubplan->pNode);
|
|
}
|
|
|
|
|
|
static bool eliminateNotNullCondMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (QUERY_NODE_LOGIC_PLAN_AGG != nodeType(pNode)) {
|
|
return false;
|
|
}
|
|
|
|
SAggLogicNode* pAgg = (SAggLogicNode*)pNode;
|
|
if (pNode->pChildren->length != 1 || NULL != pAgg->pGroupKeys) {
|
|
return false;
|
|
}
|
|
|
|
SLogicNode* pChild = (SLogicNode*)nodesListGetNode(pAgg->node.pChildren, 0);
|
|
if (QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(pChild)) {
|
|
return false;
|
|
}
|
|
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pChild;
|
|
if (NULL == pScan->node.pConditions || QUERY_NODE_OPERATOR != nodeType(pScan->node.pConditions)) {
|
|
return false;
|
|
}
|
|
|
|
SOperatorNode* pOp = (SOperatorNode*)pScan->node.pConditions;
|
|
if (OP_TYPE_IS_NOT_NULL != pOp->opType) {
|
|
return false;
|
|
}
|
|
|
|
if (QUERY_NODE_COLUMN != nodeType(pOp->pLeft)) {
|
|
return false;
|
|
}
|
|
|
|
SNode* pTmp = NULL;
|
|
FOREACH(pTmp, pAgg->pAggFuncs) {
|
|
SFunctionNode* pFunc = (SFunctionNode*)pTmp;
|
|
if (!fmIsIgnoreNullFunc(pFunc->funcId)) {
|
|
return false;
|
|
}
|
|
if (fmIsMultiResFunc(pFunc->funcId)) {
|
|
SNode* pParam = NULL;
|
|
FOREACH(pParam, pFunc->pParameterList) {
|
|
if (QUERY_NODE_COLUMN != nodeType(pParam)) {
|
|
return false;
|
|
}
|
|
if (!nodesEqualNode(pParam, pOp->pLeft)) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
SNode* pParam = nodesListGetNode(pFunc->pParameterList, 0);
|
|
if (QUERY_NODE_COLUMN != nodeType(pParam)) {
|
|
return false;
|
|
}
|
|
if (!nodesEqualNode(pParam, pOp->pLeft)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int32_t eliminateNotNullCondOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SLogicNode* pNode = (SLogicNode*)optFindPossibleNode(pLogicSubplan->pNode, eliminateNotNullCondMayBeOptimized, NULL);
|
|
if (NULL == pNode) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
SScanLogicNode* pScan = (SScanLogicNode*)nodesListGetNode(pNode->pChildren, 0);
|
|
nodesDestroyNode(pScan->node.pConditions);
|
|
pScan->node.pConditions = NULL;
|
|
|
|
pCxt->optimized = true;
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
|
|
static bool sortPriKeyOptIsPriKeyOrderBy(SNodeList* pSortKeys) {
|
|
if (1 != LIST_LENGTH(pSortKeys)) {
|
|
return false;
|
|
}
|
|
SNode* pNode = ((SOrderByExprNode*)nodesListGetNode(pSortKeys, 0))->pExpr;
|
|
return (QUERY_NODE_COLUMN == nodeType(pNode) ? isPrimaryKeyImpl(pNode) : false);
|
|
}
|
|
|
|
static bool sortPriKeyOptMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (QUERY_NODE_LOGIC_PLAN_SORT != nodeType(pNode)) {
|
|
return false;
|
|
}
|
|
SSortLogicNode* pSort = (SSortLogicNode*)pNode;
|
|
if (pSort->skipPKSortOpt || !sortPriKeyOptIsPriKeyOrderBy(pSort->pSortKeys) ||
|
|
1 != LIST_LENGTH(pSort->node.pChildren)) {
|
|
return false;
|
|
}
|
|
SNode* pChild = nodesListGetNode(pSort->node.pChildren, 0);
|
|
if (QUERY_NODE_LOGIC_PLAN_JOIN == nodeType(pChild)) {
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)pChild;
|
|
if (JOIN_TYPE_FULL == pJoin->joinType) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
FOREACH(pChild, pSort->node.pChildren) {
|
|
SLogicNode* pSortDescendent = optFindPossibleNode((SLogicNode*)pChild, sortPriKeyOptMayBeOptimized, NULL);
|
|
if (pSortDescendent != NULL) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int32_t sortPriKeyOptHandleLeftRightJoinSort(SJoinLogicNode* pJoin, SSortLogicNode* pSort, bool* pNotOptimize, bool* keepSort) {
|
|
if (JOIN_STYPE_SEMI == pJoin->subType || JOIN_STYPE_NONE == pJoin->subType) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
SSHashObj* pLeftTables = NULL;
|
|
SSHashObj* pRightTables = NULL;
|
|
bool sortByProbe = true;
|
|
/*
|
|
bool sortByLeft = true, sortByRight = true, sortByProbe = false;
|
|
collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 0), &pLeftTables);
|
|
collectTableAliasFromNodes(nodesListGetNode(pJoin->node.pChildren, 1), &pRightTables);
|
|
|
|
SOrderByExprNode* pExprNode = (SOrderByExprNode*)nodesListGetNode(pSort->pSortKeys, 0);
|
|
SColumnNode* pSortCol = (SColumnNode*)pExprNode->pExpr;
|
|
if (NULL == tSimpleHashGet(pLeftTables, pSortCol->tableAlias, strlen(pSortCol->tableAlias))) {
|
|
sortByLeft = false;
|
|
}
|
|
if (NULL == tSimpleHashGet(pRightTables, pSortCol->tableAlias, strlen(pSortCol->tableAlias))) {
|
|
sortByRight = false;
|
|
}
|
|
|
|
tSimpleHashCleanup(pLeftTables);
|
|
tSimpleHashCleanup(pRightTables);
|
|
|
|
if (!sortByLeft && !sortByRight) {
|
|
planError("sort by primary key not in any join subtable, tableAlias: %s", pSortCol->tableAlias);
|
|
return TSDB_CODE_PLAN_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (sortByLeft && sortByRight) {
|
|
planError("sort by primary key in both join subtables, tableAlias: %s", pSortCol->tableAlias);
|
|
return TSDB_CODE_PLAN_INTERNAL_ERROR;
|
|
}
|
|
|
|
if ((JOIN_TYPE_LEFT == pJoin->joinType && sortByLeft) || (JOIN_TYPE_RIGHT == pJoin->joinType && sortByRight)) {
|
|
sortByProbe = true;
|
|
}
|
|
*/
|
|
switch (pJoin->subType) {
|
|
case JOIN_STYPE_OUTER: {
|
|
if (sortByProbe) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
}
|
|
case JOIN_STYPE_ANTI: {
|
|
if (sortByProbe) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
}
|
|
case JOIN_STYPE_ASOF:
|
|
case JOIN_STYPE_WIN: {
|
|
if (sortByProbe) {
|
|
if (NULL != pJoin->pLeftEqNodes && pJoin->pLeftEqNodes->length > 0) {
|
|
*pNotOptimize = true;
|
|
}
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
}
|
|
default:
|
|
return TSDB_CODE_PLAN_INTERNAL_ERROR;
|
|
}
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
|
|
static int32_t sortPriKeyOptHandleJoinSort(SLogicNode* pNode, bool groupSort, SSortLogicNode* pSort,
|
|
bool* pNotOptimize, SNodeList** pSequencingNodes, bool* keepSort) {
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)pNode;
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
|
|
switch (pJoin->joinType) {
|
|
case JOIN_TYPE_LEFT:
|
|
case JOIN_TYPE_RIGHT: {
|
|
code = sortPriKeyOptHandleLeftRightJoinSort(pJoin, pSort, pNotOptimize, keepSort);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
if (*pNotOptimize || !(*keepSort)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
code = sortPriKeyOptGetSequencingNodesImpl((SLogicNode*)nodesListGetNode(pNode->pChildren, 0), groupSort,
|
|
pSort, pNotOptimize, pSequencingNodes, keepSort);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = sortPriKeyOptGetSequencingNodesImpl((SLogicNode*)nodesListGetNode(pNode->pChildren, 1), groupSort,
|
|
pSort, pNotOptimize, pSequencingNodes, keepSort);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
|
|
static EOrder sortPriKeyOptGetPriKeyOrder(SSortLogicNode* pSort) {
|
|
return ((SOrderByExprNode*)nodesListGetNode(pSort->pSortKeys, 0))->order;
|
|
}
|
|
|
|
|
|
static bool sortPriKeyOptHasUnsupportedPkFunc(SLogicNode* pLogicNode, EOrder sortOrder) {
|
|
if (sortOrder == ORDER_ASC) {
|
|
return false;
|
|
}
|
|
|
|
SNodeList* pFuncList = NULL;
|
|
switch (nodeType(pLogicNode)) {
|
|
case QUERY_NODE_LOGIC_PLAN_AGG:
|
|
pFuncList = ((SAggLogicNode*)pLogicNode)->pAggFuncs;
|
|
break;
|
|
case QUERY_NODE_LOGIC_PLAN_WINDOW:
|
|
pFuncList = ((SWindowLogicNode*)pLogicNode)->pFuncs;
|
|
break;
|
|
case QUERY_NODE_LOGIC_PLAN_PARTITION:
|
|
pFuncList = ((SPartitionLogicNode*)pLogicNode)->pAggFuncs;
|
|
break;
|
|
case QUERY_NODE_LOGIC_PLAN_INDEF_ROWS_FUNC:
|
|
pFuncList = ((SIndefRowsFuncLogicNode*)pLogicNode)->pFuncs;
|
|
break;
|
|
case QUERY_NODE_LOGIC_PLAN_INTERP_FUNC:
|
|
pFuncList = ((SInterpFuncLogicNode*)pLogicNode)->pFuncs;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
SNode* pNode = 0;
|
|
FOREACH(pNode, pFuncList) {
|
|
if (nodeType(pNode) != QUERY_NODE_FUNCTION) {
|
|
continue;
|
|
}
|
|
SFunctionNode* pFuncNode = (SFunctionNode*)pLogicNode;
|
|
if (pFuncNode->hasPk &&
|
|
(pFuncNode->funcType == FUNCTION_TYPE_DIFF ||
|
|
pFuncNode->funcType == FUNCTION_TYPE_DERIVATIVE ||
|
|
pFuncNode->funcType == FUNCTION_TYPE_IRATE ||
|
|
pFuncNode->funcType == FUNCTION_TYPE_TWA)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int32_t sortPriKeyOptGetSequencingNodesImpl(SLogicNode* pNode, bool groupSort, SSortLogicNode* pSort,
|
|
bool* pNotOptimize, SNodeList** pSequencingNodes, bool* keepSort) {
|
|
EOrder sortOrder = sortPriKeyOptGetPriKeyOrder(pSort);
|
|
if (sortPriKeyOptHasUnsupportedPkFunc(pNode, sortOrder)) {
|
|
*pNotOptimize = true;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
if (NULL != pNode->pLimit || NULL != pNode->pSlimit) {
|
|
*pNotOptimize = false;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
switch (nodeType(pNode)) {
|
|
case QUERY_NODE_LOGIC_PLAN_SCAN: {
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pNode;
|
|
if ((!groupSort && NULL != pScan->pGroupTags) || TSDB_SYSTEM_TABLE == pScan->tableType) {
|
|
*pNotOptimize = true;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
return nodesListMakeAppend(pSequencingNodes, (SNode*)pNode);
|
|
}
|
|
case QUERY_NODE_LOGIC_PLAN_SORT: {
|
|
*keepSort = true;
|
|
NODES_CLEAR_LIST(*pSequencingNodes);
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
case QUERY_NODE_LOGIC_PLAN_JOIN: {
|
|
return sortPriKeyOptHandleJoinSort(pNode, groupSort, pSort, pNotOptimize, pSequencingNodes, keepSort);
|
|
}
|
|
case QUERY_NODE_LOGIC_PLAN_WINDOW: {
|
|
SWindowLogicNode* pWindowLogicNode = (SWindowLogicNode*)pNode;
|
|
// For interval window, we always apply sortPriKey optimization.
|
|
// For session/event/state window, the output ts order will always be ASC.
|
|
// If sort order is also asc, we apply optimization, otherwise we keep sort node to get correct output order.
|
|
if (pWindowLogicNode->winType == WINDOW_TYPE_INTERVAL || sortOrder == ORDER_ASC)
|
|
return nodesListMakeAppend(pSequencingNodes, (SNode*)pNode);
|
|
}
|
|
case QUERY_NODE_LOGIC_PLAN_AGG:
|
|
case QUERY_NODE_LOGIC_PLAN_PARTITION:
|
|
case QUERY_NODE_LOGIC_PLAN_DYN_QUERY_CTRL:
|
|
*pNotOptimize = true;
|
|
return TSDB_CODE_SUCCESS;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (1 != LIST_LENGTH(pNode->pChildren)) {
|
|
*pNotOptimize = true;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
return sortPriKeyOptGetSequencingNodesImpl((SLogicNode*)nodesListGetNode(pNode->pChildren, 0), groupSort, pSort,
|
|
pNotOptimize, pSequencingNodes, keepSort);
|
|
}
|
|
|
|
|
|
static int32_t sortPriKeyOptGetSequencingNodes(SSortLogicNode* pSort, bool groupSort, SNodeList** pSequencingNodes, bool* keepSort) {
|
|
bool notOptimize = false;
|
|
int32_t code =
|
|
sortPriKeyOptGetSequencingNodesImpl((SLogicNode*)nodesListGetNode(pSort->node.pChildren, 0), groupSort,
|
|
pSort, ¬Optimize, pSequencingNodes, keepSort);
|
|
if (TSDB_CODE_SUCCESS != code || notOptimize) {
|
|
NODES_CLEAR_LIST(*pSequencingNodes);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t sortPriKeyOptApply(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan, SSortLogicNode* pSort,
|
|
SNodeList* pSequencingNodes) {
|
|
EOrder order = sortPriKeyOptGetPriKeyOrder(pSort);
|
|
SNode* pSequencingNode = NULL;
|
|
FOREACH(pSequencingNode, pSequencingNodes) {
|
|
if (QUERY_NODE_LOGIC_PLAN_SCAN == nodeType(pSequencingNode)) {
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pSequencingNode;
|
|
if ((ORDER_DESC == order && pScan->scanSeq[0] > 0) || (ORDER_ASC == order && pScan->scanSeq[1] > 0)) {
|
|
TSWAP(pScan->scanSeq[0], pScan->scanSeq[1]);
|
|
}
|
|
pScan->node.outputTsOrder = order;
|
|
if (TSDB_SUPER_TABLE == pScan->tableType) {
|
|
pScan->scanType = SCAN_TYPE_TABLE_MERGE;
|
|
pScan->filesetDelimited = true;
|
|
pScan->node.resultDataOrder = DATA_ORDER_LEVEL_GLOBAL;
|
|
pScan->node.requireDataOrder = DATA_ORDER_LEVEL_GLOBAL;
|
|
}
|
|
pScan->sortPrimaryKey = true;
|
|
} else if (QUERY_NODE_LOGIC_PLAN_WINDOW == nodeType(pSequencingNode)) {
|
|
((SLogicNode*)pSequencingNode)->outputTsOrder = order;
|
|
}
|
|
optSetParentOrder(((SLogicNode*)pSequencingNode)->pParent, order, (SLogicNode*)pSort);
|
|
}
|
|
|
|
SLogicNode* pChild = (SLogicNode*)nodesListGetNode(pSort->node.pChildren, 0);
|
|
if (NULL == pSort->node.pParent) {
|
|
TSWAP(pSort->node.pTargets, pChild->pTargets);
|
|
}
|
|
int32_t code = replaceLogicNode(pLogicSubplan, (SLogicNode*)pSort, pChild);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
NODES_CLEAR_LIST(pSort->node.pChildren);
|
|
nodesDestroyNode((SNode*)pSort);
|
|
}
|
|
pCxt->optimized = true;
|
|
return code;
|
|
}
|
|
|
|
static int32_t sortPrimaryKeyOptimizeImpl(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan, SSortLogicNode* pSort) {
|
|
SNodeList* pSequencingNodes = NULL;
|
|
bool keepSort = true;
|
|
int32_t code = sortPriKeyOptGetSequencingNodes(pSort, pSort->groupSort, &pSequencingNodes, &keepSort);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
if (pSequencingNodes != NULL) {
|
|
code = sortPriKeyOptApply(pCxt, pLogicSubplan, pSort, pSequencingNodes);
|
|
} else if (!keepSort) {
|
|
SLogicNode* pChild = (SLogicNode*)nodesListGetNode(pSort->node.pChildren, 0);
|
|
if (NULL == pSort->node.pParent) {
|
|
TSWAP(pSort->node.pTargets, pChild->pTargets);
|
|
}
|
|
int32_t code = replaceLogicNode(pLogicSubplan, (SLogicNode*)pSort, pChild);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
NODES_CLEAR_LIST(pSort->node.pChildren);
|
|
nodesDestroyNode((SNode*)pSort);
|
|
}
|
|
pCxt->optimized = true;
|
|
} else {
|
|
// if we decided not to push down sort info to children, we should propagate output ts order to parents of pSort
|
|
optSetParentOrder(pSort->node.pParent, sortPriKeyOptGetPriKeyOrder(pSort), 0);
|
|
// we need to prevent this pSort from being chosen to do optimization again
|
|
pSort->skipPKSortOpt = true;
|
|
pCxt->optimized = true;
|
|
}
|
|
}
|
|
nodesClearList(pSequencingNodes);
|
|
return code;
|
|
}
|
|
|
|
static int32_t sortPrimaryKeyOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SSortLogicNode* pSort = (SSortLogicNode*)optFindPossibleNode(pLogicSubplan->pNode, sortPriKeyOptMayBeOptimized, NULL);
|
|
if (NULL == pSort) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
return sortPrimaryKeyOptimizeImpl(pCxt, pLogicSubplan, pSort);
|
|
}
|
|
|
|
static int32_t sortForJoinOptimizeImpl(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan, SJoinLogicNode* pJoin) {
|
|
SLogicNode* pLeft = (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 0);
|
|
SLogicNode* pRight = (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 1);
|
|
SScanLogicNode* pScan = NULL;
|
|
SLogicNode* pChild = NULL;
|
|
SNode** pChildPos = NULL;
|
|
EOrder targetOrder = 0;
|
|
SSHashObj* pTables = NULL;
|
|
|
|
if (QUERY_NODE_LOGIC_PLAN_SCAN == nodeType(pLeft) && ((SScanLogicNode*)pLeft)->node.outputTsOrder != SCAN_ORDER_BOTH) {
|
|
pScan = (SScanLogicNode*)pLeft;
|
|
pChild = pRight;
|
|
pChildPos = &pJoin->node.pChildren->pTail->pNode;
|
|
targetOrder = pScan->node.outputTsOrder;
|
|
} else if (QUERY_NODE_LOGIC_PLAN_SCAN == nodeType(pRight) && ((SScanLogicNode*)pRight)->node.outputTsOrder != SCAN_ORDER_BOTH) {
|
|
pScan = (SScanLogicNode*)pRight;
|
|
pChild = pLeft;
|
|
pChildPos = &pJoin->node.pChildren->pHead->pNode;
|
|
targetOrder = pScan->node.outputTsOrder;
|
|
} else {
|
|
pChild = pRight;
|
|
pChildPos = &pJoin->node.pChildren->pTail->pNode;
|
|
targetOrder = pLeft->outputTsOrder;
|
|
}
|
|
|
|
if (QUERY_NODE_OPERATOR != nodeType(pJoin->pPrimKeyEqCond)) {
|
|
return TSDB_CODE_PLAN_INTERNAL_ERROR;
|
|
}
|
|
|
|
bool res = false;
|
|
SOperatorNode* pOp = (SOperatorNode*)pJoin->pPrimKeyEqCond;
|
|
if (QUERY_NODE_COLUMN != nodeType(pOp->pLeft) || QUERY_NODE_COLUMN != nodeType(pOp->pRight)) {
|
|
return TSDB_CODE_PLAN_INTERNAL_ERROR;
|
|
}
|
|
|
|
SNode* pOrderByNode = NULL;
|
|
|
|
int32_t code = collectTableAliasFromNodes((SNode*)pChild, &pTables);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
if (NULL != tSimpleHashGet(pTables, ((SColumnNode*)pOp->pLeft)->tableAlias, strlen(((SColumnNode*)pOp->pLeft)->tableAlias))) {
|
|
pOrderByNode = pOp->pLeft;
|
|
} else if (NULL != tSimpleHashGet(pTables, ((SColumnNode*)pOp->pRight)->tableAlias, strlen(((SColumnNode*)pOp->pRight)->tableAlias))) {
|
|
pOrderByNode = pOp->pRight;
|
|
}
|
|
|
|
tSimpleHashCleanup(pTables);
|
|
|
|
if (NULL == pOrderByNode) {
|
|
return TSDB_CODE_PLAN_INTERNAL_ERROR;
|
|
}
|
|
|
|
SSortLogicNode* pSort = NULL;
|
|
code = nodesMakeNode(QUERY_NODE_LOGIC_PLAN_SORT, (SNode**)&pSort);
|
|
if (NULL == pSort) {
|
|
return code;
|
|
}
|
|
|
|
pSort->node.outputTsOrder = targetOrder;
|
|
pSort->node.pTargets = NULL;
|
|
code = nodesCloneList(pChild->pTargets, &pSort->node.pTargets);
|
|
if (NULL == pSort->node.pTargets) {
|
|
nodesDestroyNode((SNode *)pSort);
|
|
return code;
|
|
}
|
|
|
|
pSort->groupSort = false;
|
|
SOrderByExprNode* pOrder = NULL;
|
|
code = nodesMakeNode(QUERY_NODE_ORDER_BY_EXPR, (SNode**)&pOrder);
|
|
if (NULL == pOrder) {
|
|
nodesDestroyNode((SNode *)pSort);
|
|
return code;
|
|
}
|
|
|
|
code = nodesListMakeStrictAppend(&pSort->pSortKeys, (SNode*)pOrder);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pSort);
|
|
return code;
|
|
}
|
|
pOrder->order = targetOrder;
|
|
pOrder->pExpr = NULL;
|
|
pOrder->nullOrder = (ORDER_ASC == pOrder->order) ? NULL_ORDER_FIRST : NULL_ORDER_LAST;
|
|
code = nodesCloneNode(pOrderByNode, &pOrder->pExpr);
|
|
if (!pOrder->pExpr) {
|
|
nodesDestroyNode((SNode *)pSort);
|
|
return code;
|
|
}
|
|
|
|
pChild->pParent = (SLogicNode*)pSort;
|
|
code = nodesListMakeAppend(&pSort->node.pChildren, (SNode*)pChild);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
*pChildPos = (SNode*)pSort;
|
|
pSort->node.pParent = (SLogicNode*)pJoin;;
|
|
|
|
_return:
|
|
|
|
pCxt->optimized = true;
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
|
|
static bool sortForJoinOptMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (QUERY_NODE_LOGIC_PLAN_JOIN != nodeType(pNode)) {
|
|
return false;
|
|
}
|
|
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)pNode;
|
|
if (pNode->pChildren->length != 2 || !pJoin->hasSubQuery || pJoin->isLowLevelJoin) {
|
|
return false;
|
|
}
|
|
|
|
SLogicNode* pLeft = (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 0);
|
|
SLogicNode* pRight = (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 1);
|
|
|
|
if (ORDER_ASC != pLeft->outputTsOrder && ORDER_DESC != pLeft->outputTsOrder) {
|
|
pLeft->outputTsOrder = ORDER_ASC;
|
|
}
|
|
if (ORDER_ASC != pRight->outputTsOrder && ORDER_DESC != pRight->outputTsOrder) {
|
|
pRight->outputTsOrder = ORDER_ASC;
|
|
}
|
|
|
|
if (pLeft->outputTsOrder == pRight->outputTsOrder) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static int32_t sortForJoinOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)optFindPossibleNode(pLogicSubplan->pNode, sortForJoinOptMayBeOptimized, NULL);
|
|
if (NULL == pJoin) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
return sortForJoinOptimizeImpl(pCxt, pLogicSubplan, pJoin);
|
|
}
|
|
|
|
static SScanLogicNode* joinCondGetScanNode(SLogicNode* pNode) {
|
|
switch (nodeType(pNode)) {
|
|
case QUERY_NODE_LOGIC_PLAN_SCAN:
|
|
return (SScanLogicNode*)pNode;
|
|
case QUERY_NODE_LOGIC_PLAN_JOIN: {
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)pNode;
|
|
if (JOIN_TYPE_INNER != pJoin->joinType) {
|
|
return NULL;
|
|
}
|
|
return joinCondGetScanNode((SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 0));
|
|
}
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static int32_t joinCondGetAllScanNodes(SLogicNode* pNode, SNodeList** pList) {
|
|
switch (nodeType(pNode)) {
|
|
case QUERY_NODE_LOGIC_PLAN_SCAN:
|
|
return nodesListMakeStrictAppend(pList, (SNode*)pNode);
|
|
case QUERY_NODE_LOGIC_PLAN_JOIN: {
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)pNode;
|
|
if (JOIN_TYPE_INNER != pJoin->joinType) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
int32_t code = joinCondGetAllScanNodes((SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 0), pList);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = joinCondGetAllScanNodes((SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 1), pList);
|
|
}
|
|
return code;
|
|
}
|
|
default:
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
}
|
|
|
|
|
|
static bool joinCondMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (QUERY_NODE_LOGIC_PLAN_JOIN != nodeType(pNode) || OPTIMIZE_FLAG_TEST_MASK(pNode->optimizedFlag, OPTIMIZE_FLAG_JOIN_COND)) {
|
|
return false;
|
|
}
|
|
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)pNode;
|
|
if (pNode->pChildren->length != 2 || JOIN_STYPE_ASOF == pJoin->subType || JOIN_STYPE_WIN == pJoin->subType || JOIN_TYPE_FULL == pJoin->joinType) {
|
|
OPTIMIZE_FLAG_SET_MASK(pNode->optimizedFlag, OPTIMIZE_FLAG_JOIN_COND);
|
|
return false;
|
|
}
|
|
|
|
SLogicNode* pLeft = (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 0);
|
|
SLogicNode* pRight = (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 1);
|
|
|
|
if ((JOIN_TYPE_LEFT == pJoin->joinType || JOIN_TYPE_RIGHT == pJoin->joinType) && (QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(pLeft) || QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(pRight))) {
|
|
OPTIMIZE_FLAG_SET_MASK(pNode->optimizedFlag, OPTIMIZE_FLAG_JOIN_COND);
|
|
return false;
|
|
}
|
|
|
|
if (JOIN_TYPE_INNER == pJoin->joinType && ((QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(pLeft) && QUERY_NODE_LOGIC_PLAN_JOIN != nodeType(pLeft)) || (QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(pRight) && QUERY_NODE_LOGIC_PLAN_JOIN != nodeType(pRight)))) {
|
|
OPTIMIZE_FLAG_SET_MASK(pNode->optimizedFlag, OPTIMIZE_FLAG_JOIN_COND);
|
|
return false;
|
|
}
|
|
|
|
if (JOIN_TYPE_INNER == pJoin->joinType) {
|
|
if (QUERY_NODE_LOGIC_PLAN_JOIN == nodeType(pLeft) && !OPTIMIZE_FLAG_TEST_MASK(pLeft->optimizedFlag, OPTIMIZE_FLAG_JOIN_COND)) {
|
|
return false;
|
|
}
|
|
if (QUERY_NODE_LOGIC_PLAN_JOIN == nodeType(pRight) && !OPTIMIZE_FLAG_TEST_MASK(pRight->optimizedFlag, OPTIMIZE_FLAG_JOIN_COND)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
SScanLogicNode* pLScan = joinCondGetScanNode(pLeft);
|
|
SScanLogicNode* pRScan = joinCondGetScanNode(pRight);
|
|
|
|
if (NULL == pLScan || NULL == pRScan) {
|
|
OPTIMIZE_FLAG_SET_MASK(pNode->optimizedFlag, OPTIMIZE_FLAG_JOIN_COND);
|
|
return false;
|
|
}
|
|
|
|
if (!IS_TSWINDOW_SPECIFIED(pLScan->scanRange) && !IS_TSWINDOW_SPECIFIED(pRScan->scanRange)) {
|
|
OPTIMIZE_FLAG_SET_MASK(pNode->optimizedFlag, OPTIMIZE_FLAG_JOIN_COND);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void joinCondMergeScanRand(STimeWindow* pDst, STimeWindow* pSrc) {
|
|
if (pSrc->skey > pDst->skey) {
|
|
pDst->skey = pSrc->skey;
|
|
}
|
|
if (pSrc->ekey < pDst->ekey) {
|
|
pDst->ekey = pSrc->ekey;
|
|
}
|
|
}
|
|
|
|
static int32_t joinCondOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)optFindPossibleNode(pLogicSubplan->pNode, joinCondMayBeOptimized, NULL);
|
|
if (NULL == pJoin) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
switch (pJoin->joinType) {
|
|
case JOIN_TYPE_INNER: {
|
|
SNodeList* pScanList = NULL;
|
|
int32_t code = joinCondGetAllScanNodes((SLogicNode*)pJoin, &pScanList);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
if (NULL == pScanList || pScanList->length <= 0) {
|
|
nodesClearList(pScanList);
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
SNode* pNode = NULL;
|
|
STimeWindow scanRange = TSWINDOW_INITIALIZER;
|
|
FOREACH(pNode, pScanList) {
|
|
joinCondMergeScanRand(&scanRange, &((SScanLogicNode*)pNode)->scanRange);
|
|
}
|
|
FOREACH(pNode, pScanList) {
|
|
((SScanLogicNode*)pNode)->scanRange.skey = scanRange.skey;
|
|
((SScanLogicNode*)pNode)->scanRange.ekey = scanRange.ekey;
|
|
}
|
|
nodesClearList(pScanList);
|
|
break;
|
|
}
|
|
case JOIN_TYPE_LEFT: {
|
|
SLogicNode* pLeft = (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 0);
|
|
SLogicNode* pRight = (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 1);
|
|
SScanLogicNode* pLScan = joinCondGetScanNode(pLeft);
|
|
SScanLogicNode* pRScan = joinCondGetScanNode(pRight);
|
|
|
|
if (NULL == pLScan || NULL == pRScan) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
joinCondMergeScanRand(&pRScan->scanRange, &pLScan->scanRange);
|
|
break;
|
|
}
|
|
case JOIN_TYPE_RIGHT: {
|
|
SLogicNode* pLeft = (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 0);
|
|
SLogicNode* pRight = (SLogicNode*)nodesListGetNode(pJoin->node.pChildren, 1);
|
|
SScanLogicNode* pLScan = joinCondGetScanNode(pLeft);
|
|
SScanLogicNode* pRScan = joinCondGetScanNode(pRight);
|
|
|
|
if (NULL == pLScan || NULL == pRScan) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
joinCondMergeScanRand(&pLScan->scanRange, &pRScan->scanRange);
|
|
break;
|
|
}
|
|
default:
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
OPTIMIZE_FLAG_SET_MASK(pJoin->node.optimizedFlag, OPTIMIZE_FLAG_JOIN_COND);
|
|
|
|
pCxt->optimized = true;
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static bool smaIndexOptMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(pNode) || NULL == pNode->pParent ||
|
|
QUERY_NODE_LOGIC_PLAN_WINDOW != nodeType(pNode->pParent) ||
|
|
WINDOW_TYPE_INTERVAL != ((SWindowLogicNode*)pNode->pParent)->winType) {
|
|
return false;
|
|
}
|
|
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pNode;
|
|
if (NULL == pScan->pSmaIndexes || NULL != pScan->node.pConditions) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int32_t smaIndexOptCreateSmaScan(SScanLogicNode* pScan, STableIndexInfo* pIndex, SNodeList* pCols,
|
|
SLogicNode** pOutput) {
|
|
SScanLogicNode* pSmaScan = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_LOGIC_PLAN_SCAN, (SNode**)&pSmaScan);
|
|
if (NULL == pSmaScan) {
|
|
return code;
|
|
}
|
|
pSmaScan->pScanCols = pCols;
|
|
pSmaScan->tableType = TSDB_SUPER_TABLE;
|
|
pSmaScan->tableId = pIndex->dstTbUid;
|
|
pSmaScan->stableId = pIndex->dstTbUid;
|
|
pSmaScan->scanType = SCAN_TYPE_TABLE;
|
|
pSmaScan->scanSeq[0] = pScan->scanSeq[0];
|
|
pSmaScan->scanSeq[1] = pScan->scanSeq[1];
|
|
pSmaScan->scanRange = pScan->scanRange;
|
|
pSmaScan->dataRequired = FUNC_DATA_REQUIRED_DATA_LOAD;
|
|
|
|
pSmaScan->pVgroupList = taosMemoryCalloc(1, sizeof(SVgroupsInfo) + sizeof(SVgroupInfo));
|
|
if (!pSmaScan->pVgroupList) {
|
|
nodesDestroyNode((SNode*)pSmaScan);
|
|
return TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
code = nodesCloneList(pCols, &pSmaScan->node.pTargets);
|
|
if (NULL == pSmaScan->node.pTargets) {
|
|
nodesDestroyNode((SNode*)pSmaScan);
|
|
return code;
|
|
}
|
|
pSmaScan->pVgroupList->numOfVgroups = 1;
|
|
pSmaScan->pVgroupList->vgroups[0].vgId = pIndex->dstVgId;
|
|
memcpy(&(pSmaScan->pVgroupList->vgroups[0].epSet), &pIndex->epSet, sizeof(SEpSet));
|
|
|
|
*pOutput = (SLogicNode*)pSmaScan;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static bool smaIndexOptEqualInterval(SScanLogicNode* pScan, SWindowLogicNode* pWindow, STableIndexInfo* pIndex) {
|
|
if (pWindow->interval != pIndex->interval || pWindow->intervalUnit != pIndex->intervalUnit ||
|
|
pWindow->offset != pIndex->offset || pWindow->sliding != pIndex->sliding ||
|
|
pWindow->slidingUnit != pIndex->slidingUnit) {
|
|
return false;
|
|
}
|
|
if (IS_TSWINDOW_SPECIFIED(pScan->scanRange)) {
|
|
SInterval interval = {.interval = pIndex->interval,
|
|
.intervalUnit = pIndex->intervalUnit,
|
|
.offset = pIndex->offset,
|
|
.offsetUnit = TIME_UNIT_MILLISECOND,
|
|
.sliding = pIndex->sliding,
|
|
.slidingUnit = pIndex->slidingUnit,
|
|
.precision = pScan->node.precision};
|
|
return (pScan->scanRange.skey == taosTimeTruncate(pScan->scanRange.skey, &interval)) &&
|
|
(pScan->scanRange.ekey + 1 == taosTimeTruncate(pScan->scanRange.ekey + 1, &interval));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int32_t smaIndexOptCreateSmaCol(SNode* pFunc, uint64_t tableId, int32_t colId, SColumnNode** ppNode) {
|
|
SColumnNode* pCol = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_COLUMN, (SNode**)&pCol);
|
|
if (NULL == pCol) {
|
|
return code;
|
|
}
|
|
pCol->tableId = tableId;
|
|
pCol->tableType = TSDB_SUPER_TABLE;
|
|
pCol->colId = colId;
|
|
pCol->colType = COLUMN_TYPE_COLUMN;
|
|
strcpy(pCol->colName, ((SExprNode*)pFunc)->aliasName);
|
|
pCol->node.resType = ((SExprNode*)pFunc)->resType;
|
|
strcpy(pCol->node.aliasName, ((SExprNode*)pFunc)->aliasName);
|
|
*ppNode = pCol;
|
|
return code;
|
|
}
|
|
|
|
static int32_t smaIndexOptFindSmaFunc(SNode* pQueryFunc, SNodeList* pSmaFuncs) {
|
|
int32_t index = 0;
|
|
SNode* pSmaFunc = NULL;
|
|
FOREACH(pSmaFunc, pSmaFuncs) {
|
|
if (nodesEqualNode(pQueryFunc, pSmaFunc)) {
|
|
return index;
|
|
}
|
|
++index;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static SNode* smaIndexOptFindWStartFunc(SNodeList* pSmaFuncs) {
|
|
SNode* pSmaFunc = NULL;
|
|
FOREACH(pSmaFunc, pSmaFuncs) {
|
|
if (QUERY_NODE_FUNCTION == nodeType(pSmaFunc) && FUNCTION_TYPE_WSTART == ((SFunctionNode*)pSmaFunc)->funcType) {
|
|
return pSmaFunc;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int32_t smaIndexOptCreateSmaCols(SNodeList* pFuncs, uint64_t tableId, SNodeList* pSmaFuncs,
|
|
SNodeList** pOutput) {
|
|
SNodeList* pCols = NULL;
|
|
SNode* pFunc = NULL;
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
int32_t index = 0;
|
|
int32_t smaFuncIndex = -1;
|
|
bool hasWStart = false;
|
|
FOREACH(pFunc, pFuncs) {
|
|
smaFuncIndex = smaIndexOptFindSmaFunc(pFunc, pSmaFuncs);
|
|
if (smaFuncIndex < 0) {
|
|
break;
|
|
} else {
|
|
SColumnNode* pCol = NULL;
|
|
code = smaIndexOptCreateSmaCol(pFunc, tableId, smaFuncIndex + 1, &pCol);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
code = nodesListMakeStrictAppend(&pCols, (SNode*)pCol);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
if (!hasWStart) {
|
|
if (PRIMARYKEY_TIMESTAMP_COL_ID == ((SColumnNode*)pCols->pTail->pNode)->colId) {
|
|
hasWStart = true;
|
|
}
|
|
}
|
|
}
|
|
++index;
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code && smaFuncIndex >= 0) {
|
|
if (!hasWStart) {
|
|
SNode* pWsNode = smaIndexOptFindWStartFunc(pSmaFuncs);
|
|
if (!pWsNode) {
|
|
nodesDestroyList(pCols);
|
|
code = TSDB_CODE_APP_ERROR;
|
|
qError("create sma cols failed since %s(_wstart not exist)", tstrerror(code));
|
|
return code;
|
|
}
|
|
SExprNode exprNode;
|
|
exprNode.resType = ((SExprNode*)pWsNode)->resType;
|
|
sprintf(exprNode.aliasName, "#expr_%d", index + 1);
|
|
SColumnNode* pkNode = NULL;
|
|
code = smaIndexOptCreateSmaCol((SNode*)&exprNode, tableId, PRIMARYKEY_TIMESTAMP_COL_ID, &pkNode);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyList(pCols);
|
|
return code;
|
|
}
|
|
code = nodesListPushFront(pCols, (SNode*)pkNode);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pkNode);
|
|
nodesDestroyList(pCols);
|
|
return code;
|
|
}
|
|
}
|
|
*pOutput = pCols;
|
|
} else {
|
|
nodesDestroyList(pCols);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t smaIndexOptCouldApplyIndex(SScanLogicNode* pScan, STableIndexInfo* pIndex, SNodeList** pCols) {
|
|
SWindowLogicNode* pWindow = (SWindowLogicNode*)pScan->node.pParent;
|
|
if (!smaIndexOptEqualInterval(pScan, pWindow, pIndex)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
SNodeList* pSmaFuncs = NULL;
|
|
int32_t code = nodesStringToList(pIndex->expr, &pSmaFuncs);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = smaIndexOptCreateSmaCols(pWindow->pFuncs, pIndex->dstTbUid, pSmaFuncs, pCols);
|
|
}
|
|
nodesDestroyList(pSmaFuncs);
|
|
return code;
|
|
}
|
|
|
|
static int32_t smaIndexOptApplyIndex(SLogicSubplan* pLogicSubplan, SScanLogicNode* pScan, STableIndexInfo* pIndex,
|
|
SNodeList* pSmaCols) {
|
|
SLogicNode* pSmaScan = NULL;
|
|
int32_t code = smaIndexOptCreateSmaScan(pScan, pIndex, pSmaCols, &pSmaScan);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = replaceLogicNode(pLogicSubplan, pScan->node.pParent, pSmaScan);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
nodesDestroyNode((SNode*)pScan->node.pParent);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t smaIndexOptimizeImpl(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan, SScanLogicNode* pScan) {
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
int32_t nindexes = taosArrayGetSize(pScan->pSmaIndexes);
|
|
for (int32_t i = 0; i < nindexes; ++i) {
|
|
STableIndexInfo* pIndex = taosArrayGet(pScan->pSmaIndexes, i);
|
|
SNodeList* pSmaCols = NULL;
|
|
code = smaIndexOptCouldApplyIndex(pScan, pIndex, &pSmaCols);
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pSmaCols) {
|
|
code = smaIndexOptApplyIndex(pLogicSubplan, pScan, pIndex, pSmaCols);
|
|
pCxt->optimized = true;
|
|
break;
|
|
}
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t smaIndexOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SScanLogicNode* pScan = (SScanLogicNode*)optFindPossibleNode(pLogicSubplan->pNode, smaIndexOptMayBeOptimized, NULL);
|
|
if (NULL == pScan) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
return smaIndexOptimizeImpl(pCxt, pLogicSubplan, pScan);
|
|
}
|
|
|
|
static EDealRes partTagsOptHasTbname(SNode* pNode, void* pContext) {
|
|
if (QUERY_NODE_COLUMN == nodeType(pNode)) {
|
|
if (COLUMN_TYPE_TBNAME == ((SColumnNode*)pNode)->colType) {
|
|
*(bool*)pContext = true;
|
|
return DEAL_RES_END;
|
|
}
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static bool planOptNodeListHasTbname(SNodeList* pKeys) {
|
|
bool hasCol = false;
|
|
nodesWalkExprs(pKeys, partTagsOptHasTbname, &hasCol);
|
|
return hasCol;
|
|
}
|
|
|
|
static bool partTagsIsOptimizableNode(SLogicNode* pNode) {
|
|
bool ret = 1 == LIST_LENGTH(pNode->pChildren) &&
|
|
QUERY_NODE_LOGIC_PLAN_SCAN == nodeType(nodesListGetNode(pNode->pChildren, 0)) &&
|
|
SCAN_TYPE_TAG != ((SScanLogicNode*)nodesListGetNode(pNode->pChildren, 0))->scanType;
|
|
if (!ret) return ret;
|
|
switch (nodeType(pNode)) {
|
|
case QUERY_NODE_LOGIC_PLAN_PARTITION: {
|
|
if (pNode->pParent) {
|
|
if (nodeType(pNode->pParent) == QUERY_NODE_LOGIC_PLAN_WINDOW) {
|
|
SWindowLogicNode* pWindow = (SWindowLogicNode*)pNode->pParent;
|
|
if (pWindow->winType == WINDOW_TYPE_INTERVAL) {
|
|
// if interval has slimit, we push down partition node to scan, and scan will set groupOrderScan to true
|
|
// we want to skip groups of blocks after slimit satisfied
|
|
// if interval only has limit, we do not push down partition node to scan
|
|
// we want to get grouped output from partition node and make use of limit
|
|
// if no slimit and no limit, we push down partition node and groupOrderScan is false, cause we do not need
|
|
// group ordered output
|
|
if (!pWindow->node.pSlimit && pWindow->node.pLimit) ret = false;
|
|
}
|
|
} else if (nodeType(pNode->pParent) == QUERY_NODE_LOGIC_PLAN_JOIN) {
|
|
ret = false;
|
|
}
|
|
}
|
|
} break;
|
|
case QUERY_NODE_LOGIC_PLAN_AGG: {
|
|
SAggLogicNode* pAgg = (SAggLogicNode*)pNode;
|
|
ret = pAgg->pGroupKeys && pAgg->pAggFuncs;
|
|
} break;
|
|
default:
|
|
ret = false;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static SNodeList* partTagsGetPartKeys(SLogicNode* pNode) {
|
|
if (QUERY_NODE_LOGIC_PLAN_PARTITION == nodeType(pNode)) {
|
|
return ((SPartitionLogicNode*)pNode)->pPartitionKeys;
|
|
} else {
|
|
return ((SAggLogicNode*)pNode)->pGroupKeys;
|
|
}
|
|
}
|
|
|
|
static SNodeList* partTagsGetFuncs(SLogicNode* pNode) {
|
|
if (QUERY_NODE_LOGIC_PLAN_PARTITION == nodeType(pNode)) {
|
|
return NULL;
|
|
} else {
|
|
return ((SAggLogicNode*)pNode)->pAggFuncs;
|
|
}
|
|
}
|
|
|
|
static bool partTagsOptAreSupportedFuncs(SNodeList* pFuncs) {
|
|
SNode* pFunc = NULL;
|
|
FOREACH(pFunc, pFuncs) {
|
|
if (fmIsIndefiniteRowsFunc(((SFunctionNode*)pFunc)->funcId) && !fmIsSelectFunc(((SFunctionNode*)pFunc)->funcId)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool partTagsOptMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (!partTagsIsOptimizableNode(pNode)) {
|
|
return false;
|
|
}
|
|
|
|
return !keysHasCol(partTagsGetPartKeys(pNode)) && partTagsOptAreSupportedFuncs(partTagsGetFuncs(pNode));
|
|
}
|
|
|
|
static int32_t partTagsOptRebuildTbanme(SNodeList* pPartKeys) {
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
nodesRewriteExprs(pPartKeys, optRebuildTbanme, &code);
|
|
return code;
|
|
}
|
|
|
|
// todo refact: just to mask compilation warnings
|
|
static void partTagsSetAlias(char* pAlias, const char* pTableAlias, const char* pColName) {
|
|
char name[TSDB_COL_FNAME_LEN + 1] = {0};
|
|
int32_t len = snprintf(name, TSDB_COL_FNAME_LEN, "%s.%s", pTableAlias, pColName);
|
|
|
|
(void)taosHashBinary(name, len);
|
|
strncpy(pAlias, name, TSDB_COL_NAME_LEN - 1);
|
|
}
|
|
|
|
static int32_t partTagsCreateWrapperFunc(const char* pFuncName, SNode* pNode, SFunctionNode** ppNode) {
|
|
SNode* pNew = NULL;
|
|
SFunctionNode* pFunc = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_FUNCTION, (SNode**)&pFunc);
|
|
if (NULL == pFunc) {
|
|
return code;
|
|
}
|
|
|
|
snprintf(pFunc->functionName, sizeof(pFunc->functionName), "%s", pFuncName);
|
|
if ((QUERY_NODE_COLUMN == nodeType(pNode) && COLUMN_TYPE_TBNAME != ((SColumnNode*)pNode)->colType) ||
|
|
(QUERY_NODE_COLUMN == nodeType(pNode) && COLUMN_TYPE_TBNAME == ((SColumnNode*)pNode)->colType &&
|
|
((SColumnNode*)pNode)->tableAlias[0] != '\0')){
|
|
SColumnNode* pCol = (SColumnNode*)pNode;
|
|
partTagsSetAlias(pFunc->node.aliasName, pCol->tableAlias, pCol->colName);
|
|
} else {
|
|
strcpy(pFunc->node.aliasName, ((SExprNode*)pNode)->aliasName);
|
|
}
|
|
code = nodesCloneNode(pNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pFunc->pParameterList, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = fmGetFuncInfo(pFunc, NULL, 0);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pFunc);
|
|
return code;
|
|
}
|
|
*ppNode = pFunc;
|
|
return code;
|
|
}
|
|
|
|
static bool partTagsHasIndefRowsSelectFunc(SNodeList* pFuncs) {
|
|
SNode* pFunc = NULL;
|
|
FOREACH(pFunc, pFuncs) {
|
|
if (fmIsIndefiniteRowsFunc(((SFunctionNode*)pFunc)->funcId)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool partTagsNeedOutput(SNode* pExpr, SNodeList* pTargets) {
|
|
SNode* pOutput = NULL;
|
|
FOREACH(pOutput, pTargets) {
|
|
if (QUERY_NODE_COLUMN == nodeType(pExpr)) {
|
|
if (nodesEqualNode(pExpr, pOutput)) {
|
|
return true;
|
|
}
|
|
} else if (0 == strcmp(((SExprNode*)pExpr)->aliasName, ((SColumnNode*)pOutput)->colName)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int32_t partTagsRewriteGroupTagsToFuncs(SNodeList* pGroupTags, int32_t start, SAggLogicNode* pAgg) {
|
|
bool hasIndefRowsSelectFunc = partTagsHasIndefRowsSelectFunc(pAgg->pAggFuncs);
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
int32_t index = 0;
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pGroupTags) {
|
|
if (index++ < start || !partTagsNeedOutput(pNode, pAgg->node.pTargets)) {
|
|
continue;
|
|
}
|
|
SFunctionNode* pFunc = NULL;
|
|
if (hasIndefRowsSelectFunc) {
|
|
code = partTagsCreateWrapperFunc("_select_value", pNode, &pFunc);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppend(pAgg->pAggFuncs, (SNode*)pFunc);
|
|
}
|
|
} else {
|
|
code = partTagsCreateWrapperFunc("_group_key", pNode, &pFunc);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppend(pAgg->pAggFuncs, (SNode*)pFunc);
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t partTagsOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SLogicNode* pNode = optFindPossibleNode(pLogicSubplan->pNode, partTagsOptMayBeOptimized, NULL);
|
|
if (NULL == pNode) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SScanLogicNode* pScan = (SScanLogicNode*)nodesListGetNode(pNode->pChildren, 0);
|
|
if (QUERY_NODE_LOGIC_PLAN_PARTITION == nodeType(pNode)) {
|
|
TSWAP(((SPartitionLogicNode*)pNode)->pPartitionKeys, pScan->pGroupTags);
|
|
TSWAP(((SPartitionLogicNode*)pNode)->pTags, pScan->pTags);
|
|
TSWAP(((SPartitionLogicNode*)pNode)->pSubtable, pScan->pSubtable);
|
|
int32_t code = replaceLogicNode(pLogicSubplan, pNode, (SLogicNode*)pScan);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = adjustLogicNodeDataRequirement((SLogicNode*)pScan, pNode->resultDataOrder);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
if (QUERY_NODE_LOGIC_PLAN_AGG == pNode->pParent->type) {
|
|
SAggLogicNode* pParent = (SAggLogicNode*)(pNode->pParent);
|
|
scanPathOptSetGroupOrderScan(pScan);
|
|
pParent->hasGroupKeyOptimized = true;
|
|
}
|
|
if (pNode->pParent->pSlimit) pScan->groupOrderScan = true;
|
|
|
|
NODES_CLEAR_LIST(pNode->pChildren);
|
|
nodesDestroyNode((SNode*)pNode);
|
|
}
|
|
} else {
|
|
SAggLogicNode* pAgg = (SAggLogicNode*)pNode;
|
|
int32_t start = -1;
|
|
SNode* pGroupKey = NULL;
|
|
FOREACH(pGroupKey, pAgg->pGroupKeys) {
|
|
SNode* pGroupExpr = nodesListGetNode(((SGroupingSetNode*)pGroupKey)->pParameterList, 0);
|
|
if (NULL != pScan->pGroupTags) {
|
|
SNode* pGroupTag = NULL;
|
|
FOREACH(pGroupTag, pScan->pGroupTags) {
|
|
if (nodesEqualNode(pGroupTag, pGroupExpr)) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (start < 0) {
|
|
start = LIST_LENGTH(pScan->pGroupTags);
|
|
}
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pGroupExpr, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pScan->pGroupTags, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
pAgg->hasGroupKeyOptimized = true;
|
|
|
|
NODES_DESTORY_LIST(pAgg->pGroupKeys);
|
|
if (TSDB_CODE_SUCCESS == code && start >= 0) {
|
|
code = partTagsRewriteGroupTagsToFuncs(pScan->pGroupTags, start, pAgg);
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = partTagsOptRebuildTbanme(pScan->pGroupTags);
|
|
}
|
|
|
|
pCxt->optimized = true;
|
|
return code;
|
|
}
|
|
|
|
static int32_t eliminateProjOptCheckProjColumnNames(SProjectLogicNode* pProjectNode, bool* pRet) {
|
|
int32_t code = 0;
|
|
SHashObj* pProjColNameHash = taosHashInit(16, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), true, HASH_NO_LOCK);
|
|
SNode* pProjection;
|
|
FOREACH(pProjection, pProjectNode->pProjections) {
|
|
char* projColumnName = ((SColumnNode*)pProjection)->colName;
|
|
int32_t* pExist = taosHashGet(pProjColNameHash, projColumnName, strlen(projColumnName));
|
|
if (NULL != pExist) {
|
|
taosHashCleanup(pProjColNameHash);
|
|
return false;
|
|
} else {
|
|
int32_t exist = 1;
|
|
code = taosHashPut(pProjColNameHash, projColumnName, strlen(projColumnName), &exist, sizeof(exist));
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
taosHashCleanup(pProjColNameHash);
|
|
*pRet = true;
|
|
return code;
|
|
}
|
|
|
|
static bool eliminateProjOptMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
// Super table scan requires project operator to merge packets to improve performance.
|
|
if (NULL == pNode->pParent && (QUERY_NODE_LOGIC_PLAN_PROJECT != nodeType(pNode) || 1 != LIST_LENGTH(pNode->pChildren) ||
|
|
(QUERY_NODE_LOGIC_PLAN_SCAN == nodeType(nodesListGetNode(pNode->pChildren, 0)) &&
|
|
TSDB_SUPER_TABLE == ((SScanLogicNode*)nodesListGetNode(pNode->pChildren, 0))->tableType))) {
|
|
return false;
|
|
}
|
|
|
|
if (OPTIMIZE_FLAG_TEST_MASK(pNode->optimizedFlag, OPTIMIZE_FLAG_ELIMINATE_PROJ)) {
|
|
return false;
|
|
}
|
|
|
|
if (NULL != pNode->pParent && (QUERY_NODE_LOGIC_PLAN_PROJECT != nodeType(pNode) || 1 != LIST_LENGTH(pNode->pChildren) ||
|
|
QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(nodesListGetNode(pNode->pChildren, 0)) || QUERY_NODE_LOGIC_PLAN_JOIN != nodeType(pNode->pParent))) {
|
|
return false;
|
|
}
|
|
|
|
if (QUERY_NODE_LOGIC_PLAN_PROJECT != nodeType(pNode) || 1 != LIST_LENGTH(pNode->pChildren)) {
|
|
return false;
|
|
}
|
|
|
|
if (QUERY_NODE_LOGIC_PLAN_DYN_QUERY_CTRL == nodeType(nodesListGetNode(pNode->pChildren, 0))) {
|
|
SLogicNode* pChild = (SLogicNode*)nodesListGetNode(pNode->pChildren, 0);
|
|
if(LIST_LENGTH(pChild->pTargets) != LIST_LENGTH(pNode->pTargets)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
SProjectLogicNode* pProjectNode = (SProjectLogicNode*)pNode;
|
|
if (NULL != pProjectNode->node.pLimit || NULL != pProjectNode->node.pSlimit ||
|
|
NULL != pProjectNode->node.pConditions) {
|
|
return false;
|
|
}
|
|
|
|
SNode* pProjection;
|
|
FOREACH(pProjection, pProjectNode->pProjections) {
|
|
SExprNode* pExprNode = (SExprNode*)pProjection;
|
|
if (QUERY_NODE_COLUMN != nodeType(pExprNode)) {
|
|
return false;
|
|
}
|
|
SColumnNode* pCol = (SColumnNode*)pExprNode;
|
|
if (NULL != pNode->pParent && 0 != strcmp(pCol->colName, pCol->node.aliasName)) {
|
|
return false;
|
|
}
|
|
}
|
|
int32_t* pCode = pCtx;
|
|
bool ret = false;
|
|
int32_t code = eliminateProjOptCheckProjColumnNames(pProjectNode, &ret);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
*pCode = code;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
typedef struct CheckNewChildTargetsCxt {
|
|
SNodeList* pNewChildTargets;
|
|
bool canUse;
|
|
} CheckNewChildTargetsCxt;
|
|
|
|
static EDealRes eliminateProjOptCanUseNewChildTargetsImpl(SNode* pNode, void* pContext) {
|
|
if (QUERY_NODE_COLUMN == nodeType(pNode)) {
|
|
CheckNewChildTargetsCxt* pCxt = pContext;
|
|
SNode* pTarget = NULL;
|
|
FOREACH(pTarget, pCxt->pNewChildTargets) {
|
|
if (nodesEqualNode(pTarget, pNode)) {
|
|
pCxt->canUse = true;
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
}
|
|
pCxt->canUse = false;
|
|
return DEAL_RES_END;
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static bool eliminateProjOptCanChildConditionUseChildTargets(SLogicNode* pChild, SNodeList* pNewChildTargets) {
|
|
if (NULL != pChild->pConditions) {
|
|
CheckNewChildTargetsCxt cxt = {.pNewChildTargets = pNewChildTargets, .canUse = false};
|
|
nodesWalkExpr(pChild->pConditions, eliminateProjOptCanUseNewChildTargetsImpl, &cxt);
|
|
if (!cxt.canUse) return false;
|
|
}
|
|
|
|
if (QUERY_NODE_LOGIC_PLAN_JOIN == nodeType(pChild) && ((SJoinLogicNode*)pChild)->joinAlgo != JOIN_ALGO_UNKNOWN) {
|
|
return false;
|
|
}
|
|
if (QUERY_NODE_LOGIC_PLAN_JOIN == nodeType(pChild) && ((SJoinLogicNode*)pChild)->pFullOnCond) {
|
|
SJoinLogicNode* pJoinLogicNode = (SJoinLogicNode*)pChild;
|
|
CheckNewChildTargetsCxt cxt = {.pNewChildTargets = pNewChildTargets, .canUse = false};
|
|
nodesWalkExpr(pJoinLogicNode->pFullOnCond, eliminateProjOptCanUseNewChildTargetsImpl, &cxt);
|
|
if (!cxt.canUse) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void alignProjectionWithTarget(SLogicNode* pNode) {
|
|
if (QUERY_NODE_LOGIC_PLAN_PROJECT != pNode->type) {
|
|
return;
|
|
}
|
|
|
|
SProjectLogicNode* pProjectNode = (SProjectLogicNode*)pNode;
|
|
SNode* pProjection = NULL;
|
|
FOREACH(pProjection, pProjectNode->pProjections) {
|
|
SNode* pTarget = NULL;
|
|
bool keep = false;
|
|
FOREACH(pTarget, pNode->pTargets) {
|
|
if (0 == strcmp(((SColumnNode*)pProjection)->node.aliasName, ((SColumnNode*)pTarget)->colName)) {
|
|
keep = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!keep) {
|
|
(void)nodesListErase(pProjectNode->pProjections, cell);
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct RewriteTableAliasCxt {
|
|
char* newTableAlias;
|
|
bool rewriteColName;
|
|
} RewriteTableAliasCxt;
|
|
|
|
static EDealRes eliminateProjOptRewriteScanTableAlias(SNode* pNode, void* pContext) {
|
|
if (QUERY_NODE_COLUMN == nodeType(pNode)) {
|
|
SColumnNode* pCol = (SColumnNode*)pNode;
|
|
RewriteTableAliasCxt* pCtx = (RewriteTableAliasCxt*)pContext;
|
|
strncpy(pCol->tableAlias, pCtx->newTableAlias, TSDB_TABLE_NAME_LEN);
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
|
|
static int32_t eliminateProjOptimizeImpl(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan,
|
|
SProjectLogicNode* pProjectNode) {
|
|
SLogicNode* pChild = (SLogicNode*)nodesListGetNode(pProjectNode->node.pChildren, 0);
|
|
int32_t code = 0;
|
|
|
|
if (NULL == pProjectNode->node.pParent) {
|
|
SNodeList* pNewChildTargets = NULL;
|
|
code = nodesMakeList(&pNewChildTargets);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
SNode * pProjection = NULL, *pChildTarget = NULL;
|
|
bool orderMatch = true;
|
|
bool needOrderMatch =
|
|
QUERY_NODE_LOGIC_PLAN_PROJECT == nodeType(pChild) && ((SProjectLogicNode*)pChild)->isSetOpProj;
|
|
if (needOrderMatch) {
|
|
// For sql: select ... from (select ... union all select ...);
|
|
// When eliminating the outer proj (the outer select), we have to make sure that the outer proj projections and
|
|
// union all project targets have same columns in the same order. See detail in TD-30188
|
|
FORBOTH(pProjection, pProjectNode->pProjections, pChildTarget, pChild->pTargets) {
|
|
if (!pProjection) break;
|
|
if (0 != strcmp(((SColumnNode*)pProjection)->colName, ((SColumnNode*)pChildTarget)->colName)) {
|
|
orderMatch = false;
|
|
break;
|
|
}
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pChildTarget, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppend(pNewChildTargets, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
}
|
|
} else {
|
|
FOREACH(pProjection, pProjectNode->pProjections) {
|
|
FOREACH(pChildTarget, pChild->pTargets) {
|
|
if (0 == strcmp(((SColumnNode*)pProjection)->colName, ((SColumnNode*)pChildTarget)->colName)) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pChildTarget, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppend(pNewChildTargets, pNew);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyList(pNewChildTargets);
|
|
return code;
|
|
}
|
|
|
|
if (eliminateProjOptCanChildConditionUseChildTargets(pChild, pNewChildTargets) &&
|
|
(!needOrderMatch || (needOrderMatch && orderMatch))) {
|
|
nodesDestroyList(pChild->pTargets);
|
|
pChild->pTargets = pNewChildTargets;
|
|
} else {
|
|
nodesDestroyList(pNewChildTargets);
|
|
OPTIMIZE_FLAG_SET_MASK(pProjectNode->node.optimizedFlag, OPTIMIZE_FLAG_ELIMINATE_PROJ);
|
|
pCxt->optimized = true;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
} else {
|
|
RewriteTableAliasCxt cxt = {.newTableAlias = pProjectNode->stmtName, .rewriteColName = false};
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pChild;
|
|
nodesWalkExprs(pScan->pScanCols, eliminateProjOptRewriteScanTableAlias, &cxt);
|
|
nodesWalkExprs(pScan->pScanPseudoCols, eliminateProjOptRewriteScanTableAlias, &cxt);
|
|
nodesWalkExpr(pScan->node.pConditions, eliminateProjOptRewriteScanTableAlias, &cxt);
|
|
nodesWalkExprs(pChild->pTargets, eliminateProjOptRewriteScanTableAlias, &cxt);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = replaceLogicNode(pLogicSubplan, (SLogicNode*)pProjectNode, pChild);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
if (pProjectNode->node.pHint && !pChild->pHint) TSWAP(pProjectNode->node.pHint, pChild->pHint);
|
|
NODES_CLEAR_LIST(pProjectNode->node.pChildren);
|
|
nodesDestroyNode((SNode*)pProjectNode);
|
|
// if pChild is a project logic node, remove its projection which is not reference by its target.
|
|
alignProjectionWithTarget(pChild);
|
|
}
|
|
pCxt->optimized = true;
|
|
return code;
|
|
}
|
|
|
|
static int32_t eliminateProjOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
int32_t code = 0;
|
|
SProjectLogicNode* pProjectNode =
|
|
(SProjectLogicNode*)optFindPossibleNode(pLogicSubplan->pNode, eliminateProjOptMayBeOptimized, &code);
|
|
|
|
if (NULL == pProjectNode) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
return eliminateProjOptimizeImpl(pCxt, pLogicSubplan, pProjectNode);
|
|
}
|
|
|
|
static bool rewriteTailOptMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
return QUERY_NODE_LOGIC_PLAN_INDEF_ROWS_FUNC == nodeType(pNode) && ((SIndefRowsFuncLogicNode*)pNode)->isTailFunc;
|
|
}
|
|
|
|
static int32_t rewriteTailOptCreateOrderByExpr(SNode* pSortKey, SNode** ppNode) {
|
|
SOrderByExprNode* pOrder = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_ORDER_BY_EXPR, (SNode**)&pOrder);
|
|
if (NULL == pOrder) {
|
|
return code;
|
|
}
|
|
pOrder->order = ORDER_DESC;
|
|
pOrder->pExpr = NULL;
|
|
code = nodesCloneNode(pSortKey, &pOrder->pExpr);
|
|
if (NULL == pOrder->pExpr) {
|
|
nodesDestroyNode((SNode*)pOrder);
|
|
return code;
|
|
}
|
|
*ppNode = (SNode*)pOrder;
|
|
return code;
|
|
}
|
|
|
|
static int32_t rewriteTailOptCreateLimit(SNode* pLimit, SNode* pOffset, SNode** pOutput) {
|
|
SLimitNode* pLimitNode = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_LIMIT, (SNode**)&pLimitNode);
|
|
if (NULL == pLimitNode) {
|
|
return code;
|
|
}
|
|
pLimitNode->limit = NULL == pLimit ? -1 : ((SValueNode*)pLimit)->datum.i;
|
|
pLimitNode->offset = NULL == pOffset ? 0 : ((SValueNode*)pOffset)->datum.i;
|
|
*pOutput = (SNode*)pLimitNode;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static bool rewriteTailOptNeedGroupSort(SIndefRowsFuncLogicNode* pIndef) {
|
|
if (1 != LIST_LENGTH(pIndef->node.pChildren)) {
|
|
return false;
|
|
}
|
|
SNode* pChild = nodesListGetNode(pIndef->node.pChildren, 0);
|
|
return QUERY_NODE_LOGIC_PLAN_PARTITION == nodeType(pChild) ||
|
|
(QUERY_NODE_LOGIC_PLAN_SCAN == nodeType(pChild) && NULL != ((SScanLogicNode*)pChild)->pGroupTags);
|
|
}
|
|
|
|
static int32_t rewriteTailOptCreateSort(SIndefRowsFuncLogicNode* pIndef, SLogicNode** pOutput) {
|
|
SSortLogicNode* pSort = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_LOGIC_PLAN_SORT, (SNode**)&pSort);
|
|
if (NULL == pSort) {
|
|
return code;
|
|
}
|
|
|
|
pSort->groupSort = rewriteTailOptNeedGroupSort(pIndef);
|
|
TSWAP(pSort->node.pChildren, pIndef->node.pChildren);
|
|
optResetParent((SLogicNode*)pSort);
|
|
pSort->node.precision = pIndef->node.precision;
|
|
|
|
SFunctionNode* pTail = NULL;
|
|
SNode* pFunc = NULL;
|
|
FOREACH(pFunc, pIndef->pFuncs) {
|
|
if (FUNCTION_TYPE_TAIL == ((SFunctionNode*)pFunc)->funcType) {
|
|
pTail = (SFunctionNode*)pFunc;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// tail(expr, [limit, offset,] _rowts)
|
|
int32_t rowtsIndex = LIST_LENGTH(pTail->pParameterList) - 1;
|
|
|
|
SNode* pNewNode = NULL;
|
|
code = rewriteTailOptCreateOrderByExpr(nodesListGetNode(pTail->pParameterList, rowtsIndex), &pNewNode);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pSort->pSortKeys, pNewNode);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesCloneList(((SLogicNode*)nodesListGetNode(pSort->node.pChildren, 0))->pTargets, &pSort->node.pTargets);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pOutput = (SLogicNode*)pSort;
|
|
} else {
|
|
nodesDestroyNode((SNode*)pSort);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t rewriteTailOptCreateProjectExpr(SFunctionNode* pFunc, SNode** ppNode) {
|
|
SNode* pExpr = NULL;
|
|
int32_t code = nodesCloneNode(nodesListGetNode(pFunc->pParameterList, 0), &pExpr);
|
|
if (NULL == pExpr) {
|
|
return code;
|
|
}
|
|
strcpy(((SExprNode*)pExpr)->aliasName, pFunc->node.aliasName);
|
|
*ppNode = pExpr;
|
|
return code;
|
|
}
|
|
|
|
static int32_t rewriteTailOptCreateProject(SIndefRowsFuncLogicNode* pIndef, SLogicNode** pOutput) {
|
|
SProjectLogicNode* pProject = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_LOGIC_PLAN_PROJECT, (SNode**)&pProject);
|
|
if (NULL == pProject) {
|
|
return TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
|
|
TSWAP(pProject->node.pTargets, pIndef->node.pTargets);
|
|
pProject->node.precision = pIndef->node.precision;
|
|
|
|
SFunctionNode* pTail = NULL;
|
|
SNode* pFunc = NULL;
|
|
FOREACH(pFunc, pIndef->pFuncs) {
|
|
SNode* pNew = NULL;
|
|
code = rewriteTailOptCreateProjectExpr((SFunctionNode*)pFunc, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pProject->pProjections, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
if (FUNCTION_TYPE_TAIL == ((SFunctionNode*)pFunc)->funcType) {
|
|
pTail = (SFunctionNode*)pFunc;
|
|
}
|
|
}
|
|
|
|
// tail(expr, [limit, offset,] _rowts)
|
|
int32_t limitIndex = LIST_LENGTH(pTail->pParameterList) > 2 ? 1 : -1;
|
|
int32_t offsetIndex = LIST_LENGTH(pTail->pParameterList) > 3 ? 2 : -1;
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = rewriteTailOptCreateLimit(limitIndex < 0 ? NULL : nodesListGetNode(pTail->pParameterList, limitIndex),
|
|
offsetIndex < 0 ? NULL : nodesListGetNode(pTail->pParameterList, offsetIndex),
|
|
&pProject->node.pLimit);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pOutput = (SLogicNode*)pProject;
|
|
} else {
|
|
nodesDestroyNode((SNode*)pProject);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t rewriteTailOptimizeImpl(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan,
|
|
SIndefRowsFuncLogicNode* pIndef) {
|
|
SLogicNode* pSort = NULL;
|
|
SLogicNode* pProject = NULL;
|
|
int32_t code = rewriteTailOptCreateSort(pIndef, &pSort);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = rewriteTailOptCreateProject(pIndef, &pProject);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeAppend(&pProject->pChildren, (SNode*)pSort);
|
|
pSort->pParent = pProject;
|
|
pSort = NULL;
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = replaceLogicNode(pLogicSubplan, (SLogicNode*)pIndef, pProject);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
nodesDestroyNode((SNode*)pIndef);
|
|
} else {
|
|
nodesDestroyNode((SNode*)pSort);
|
|
nodesDestroyNode((SNode*)pProject);
|
|
}
|
|
pCxt->optimized = true;
|
|
return code;
|
|
}
|
|
|
|
static int32_t rewriteTailOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SIndefRowsFuncLogicNode* pIndef =
|
|
(SIndefRowsFuncLogicNode*)optFindPossibleNode(pLogicSubplan->pNode, rewriteTailOptMayBeOptimized, NULL);
|
|
|
|
if (NULL == pIndef) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
return rewriteTailOptimizeImpl(pCxt, pLogicSubplan, pIndef);
|
|
}
|
|
|
|
static bool eliminateSetOpMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
SLogicNode* pParent = pNode->pParent;
|
|
if (NULL == pParent ||
|
|
QUERY_NODE_LOGIC_PLAN_AGG != nodeType(pParent) && QUERY_NODE_LOGIC_PLAN_PROJECT != nodeType(pParent) ||
|
|
LIST_LENGTH(pParent->pChildren) < 2) {
|
|
return false;
|
|
}
|
|
if (nodeType(pNode) != nodeType(pNode->pParent) || LIST_LENGTH(pNode->pChildren) < 2) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int32_t eliminateSetOpOptimizeImpl(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan,
|
|
SLogicNode* pSetOpNode) {
|
|
SNode* pSibling;
|
|
FOREACH(pSibling, pSetOpNode->pParent->pChildren) {
|
|
if (nodesEqualNode(pSibling, (SNode*)pSetOpNode)) {
|
|
SNode* pChild;
|
|
FOREACH(pChild, pSetOpNode->pChildren) { ((SLogicNode*)pChild)->pParent = pSetOpNode->pParent; }
|
|
INSERT_LIST(pSetOpNode->pParent->pChildren, pSetOpNode->pChildren);
|
|
|
|
pSetOpNode->pChildren = NULL;
|
|
ERASE_NODE(pSetOpNode->pParent->pChildren);
|
|
pCxt->optimized = true;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return TSDB_CODE_PLAN_INTERNAL_ERROR;
|
|
}
|
|
|
|
static int32_t eliminateSetOpOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SLogicNode* pSetOpNode = optFindPossibleNode(pLogicSubplan->pNode, eliminateSetOpMayBeOptimized, NULL);
|
|
if (NULL == pSetOpNode) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
return eliminateSetOpOptimizeImpl(pCxt, pLogicSubplan, pSetOpNode);
|
|
}
|
|
|
|
static bool rewriteUniqueOptMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
return QUERY_NODE_LOGIC_PLAN_INDEF_ROWS_FUNC == nodeType(pNode) && ((SIndefRowsFuncLogicNode*)pNode)->isUniqueFunc;
|
|
}
|
|
|
|
static int32_t rewriteUniqueOptCreateGroupingSet(SNode* pExpr, SNode** ppNode) {
|
|
SGroupingSetNode* pGroupingSet = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_GROUPING_SET, (SNode**)&pGroupingSet);
|
|
if (NULL == pGroupingSet) {
|
|
return code;
|
|
}
|
|
pGroupingSet->groupingSetType = GP_TYPE_NORMAL;
|
|
SExprNode* pGroupExpr = NULL;
|
|
code = nodesCloneNode(pExpr, (SNode**)&pGroupExpr);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pGroupingSet->pParameterList, (SNode*)pGroupExpr);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pGroupingSet);
|
|
return code;
|
|
}
|
|
*ppNode = (SNode*)pGroupingSet;
|
|
return code;
|
|
}
|
|
|
|
static int32_t rewriteUniqueOptCreateFirstFunc(SFunctionNode* pSelectValue, SNode* pCol, SNode** ppNode) {
|
|
SFunctionNode* pFunc = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_FUNCTION, (SNode**)&pFunc);
|
|
if (NULL == pFunc) {
|
|
return code;
|
|
}
|
|
|
|
strcpy(pFunc->functionName, "first");
|
|
if (NULL != pSelectValue) {
|
|
strcpy(pFunc->node.aliasName, pSelectValue->node.aliasName);
|
|
} else {
|
|
int64_t pointer = (int64_t)pFunc;
|
|
char name[TSDB_FUNC_NAME_LEN + TSDB_POINTER_PRINT_BYTES + TSDB_NAME_DELIMITER_LEN + 1] = {0};
|
|
int32_t len = snprintf(name, sizeof(name) - 1, "%s.%" PRId64 "", pFunc->functionName, pointer);
|
|
(void)taosHashBinary(name, len);
|
|
strncpy(pFunc->node.aliasName, name, TSDB_COL_NAME_LEN - 1);
|
|
}
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pCol, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pFunc->pParameterList, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = fmGetFuncInfo(pFunc, NULL, 0);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pFunc);
|
|
return code;
|
|
}
|
|
*ppNode = (SNode*)pFunc;
|
|
return code;
|
|
}
|
|
|
|
static int32_t rewriteUniqueOptCreateAgg(SIndefRowsFuncLogicNode* pIndef, SLogicNode** pOutput) {
|
|
SAggLogicNode* pAgg = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_LOGIC_PLAN_AGG, (SNode**)&pAgg);
|
|
if (NULL == pAgg) {
|
|
return code;
|
|
}
|
|
|
|
TSWAP(pAgg->node.pChildren, pIndef->node.pChildren);
|
|
optResetParent((SLogicNode*)pAgg);
|
|
pAgg->node.precision = pIndef->node.precision;
|
|
pAgg->node.requireDataOrder = DATA_ORDER_LEVEL_IN_BLOCK; // first function requirement
|
|
pAgg->node.resultDataOrder = DATA_ORDER_LEVEL_NONE;
|
|
|
|
bool hasSelectPrimaryKey = false;
|
|
SNode* pPrimaryKey = NULL;
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pIndef->pFuncs) {
|
|
SFunctionNode* pFunc = (SFunctionNode*)pNode;
|
|
SNode* pExpr = nodesListGetNode(pFunc->pParameterList, 0);
|
|
if (FUNCTION_TYPE_UNIQUE == pFunc->funcType) {
|
|
pPrimaryKey = nodesListGetNode(pFunc->pParameterList, 1);
|
|
SNode* pNew = NULL;
|
|
code = rewriteUniqueOptCreateGroupingSet(pExpr, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pAgg->pGroupKeys, pNew);
|
|
}
|
|
} else if (PRIMARYKEY_TIMESTAMP_COL_ID == ((SColumnNode*)pExpr)->colId) { // _select_value(ts) => first(ts)
|
|
hasSelectPrimaryKey = true;
|
|
SNode* pNew = NULL;
|
|
code = rewriteUniqueOptCreateFirstFunc(pFunc, pExpr, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pAgg->pAggFuncs, pNew);
|
|
}
|
|
} else { // _select_value(other_col)
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pAgg->pAggFuncs, pNew);
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = createColumnByRewriteExprs(pAgg->pGroupKeys, &pAgg->node.pTargets);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code && NULL != pAgg->pAggFuncs) {
|
|
code = createColumnByRewriteExprs(pAgg->pAggFuncs, &pAgg->node.pTargets);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code && !hasSelectPrimaryKey && NULL != pAgg->pAggFuncs) {
|
|
SNode* pNew = NULL;
|
|
code = rewriteUniqueOptCreateFirstFunc(NULL, pPrimaryKey, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pAgg->pAggFuncs, pNew);
|
|
}
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pOutput = (SLogicNode*)pAgg;
|
|
} else {
|
|
nodesDestroyNode((SNode*)pAgg);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t rewriteUniqueOptCreateProjectCol(SFunctionNode* pFunc, SNode** ppNode) {
|
|
SColumnNode* pCol = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_COLUMN, (SNode**)&pCol);
|
|
if (NULL == pCol) {
|
|
return code;
|
|
}
|
|
|
|
pCol->node.resType = pFunc->node.resType;
|
|
if (FUNCTION_TYPE_UNIQUE == pFunc->funcType) {
|
|
SExprNode* pExpr = (SExprNode*)nodesListGetNode(pFunc->pParameterList, 0);
|
|
if (QUERY_NODE_COLUMN == nodeType(pExpr)) {
|
|
strcpy(pCol->tableAlias, ((SColumnNode*)pExpr)->tableAlias);
|
|
strcpy(pCol->colName, ((SColumnNode*)pExpr)->colName);
|
|
} else {
|
|
strcpy(pCol->colName, pExpr->aliasName);
|
|
}
|
|
} else {
|
|
strcpy(pCol->colName, pFunc->node.aliasName);
|
|
}
|
|
strcpy(pCol->node.aliasName, pFunc->node.aliasName);
|
|
*ppNode = (SNode*)pCol;
|
|
return code;
|
|
}
|
|
|
|
static int32_t rewriteUniqueOptCreateProject(SIndefRowsFuncLogicNode* pIndef, SLogicNode** pOutput) {
|
|
SProjectLogicNode* pProject = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_LOGIC_PLAN_PROJECT, (SNode**)&pProject);
|
|
if (NULL == pProject) {
|
|
return code;
|
|
}
|
|
|
|
TSWAP(pProject->node.pTargets, pIndef->node.pTargets);
|
|
pProject->node.precision = pIndef->node.precision;
|
|
pProject->node.requireDataOrder = DATA_ORDER_LEVEL_NONE;
|
|
pProject->node.resultDataOrder = DATA_ORDER_LEVEL_NONE;
|
|
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pIndef->pFuncs) {
|
|
SNode* pNew = NULL;
|
|
code = rewriteUniqueOptCreateProjectCol((SFunctionNode*)pNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pProject->pProjections, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pOutput = (SLogicNode*)pProject;
|
|
} else {
|
|
nodesDestroyNode((SNode*)pProject);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t rewriteUniqueOptimizeImpl(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan,
|
|
SIndefRowsFuncLogicNode* pIndef) {
|
|
SLogicNode* pAgg = NULL;
|
|
SLogicNode* pProject = NULL;
|
|
int32_t code = rewriteUniqueOptCreateAgg(pIndef, &pAgg);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = rewriteUniqueOptCreateProject(pIndef, &pProject);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeAppend(&pProject->pChildren, (SNode*)pAgg);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pAgg->pParent = pProject;
|
|
pAgg = NULL;
|
|
code = replaceLogicNode(pLogicSubplan, (SLogicNode*)pIndef, pProject);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = adjustLogicNodeDataRequirement(
|
|
pProject, NULL == pProject->pParent ? DATA_ORDER_LEVEL_NONE : pProject->pParent->requireDataOrder);
|
|
pProject = NULL;
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
nodesDestroyNode((SNode*)pIndef);
|
|
} else {
|
|
nodesDestroyNode((SNode*)pAgg);
|
|
nodesDestroyNode((SNode*)pProject);
|
|
}
|
|
pCxt->optimized = true;
|
|
return code;
|
|
}
|
|
|
|
static int32_t rewriteUniqueOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SIndefRowsFuncLogicNode* pIndef =
|
|
(SIndefRowsFuncLogicNode*)optFindPossibleNode(pLogicSubplan->pNode, rewriteUniqueOptMayBeOptimized, NULL);
|
|
|
|
if (NULL == pIndef) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
return rewriteUniqueOptimizeImpl(pCxt, pLogicSubplan, pIndef);
|
|
}
|
|
|
|
typedef struct SLastRowScanOptLastParaCkCxt {
|
|
bool hasTag;
|
|
bool hasCol;
|
|
} SLastRowScanOptLastParaCkCxt;
|
|
|
|
static EDealRes lastRowScanOptLastParaIsTagImpl(SNode* pNode, void* pContext) {
|
|
if (QUERY_NODE_COLUMN == nodeType(pNode)) {
|
|
SLastRowScanOptLastParaCkCxt* pCxt = pContext;
|
|
if (COLUMN_TYPE_TAG == ((SColumnNode*)pNode)->colType || COLUMN_TYPE_TBNAME == ((SColumnNode*)pNode)->colType) {
|
|
pCxt->hasTag = true;
|
|
} else {
|
|
pCxt->hasCol = true;
|
|
}
|
|
return DEAL_RES_END;
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static bool lastRowScanOptLastParaIsTag(SNode* pExpr) {
|
|
SLastRowScanOptLastParaCkCxt cxt = {.hasTag = false, .hasCol = false};
|
|
nodesWalkExpr(pExpr, lastRowScanOptLastParaIsTagImpl, &cxt);
|
|
return cxt.hasTag && !cxt.hasCol;
|
|
}
|
|
|
|
static bool hasSuitableCache(int8_t cacheLastMode, bool hasLastRow, bool hasLast) {
|
|
switch (cacheLastMode) {
|
|
case TSDB_CACHE_MODEL_NONE:
|
|
return false;
|
|
case TSDB_CACHE_MODEL_LAST_ROW:
|
|
return hasLastRow;
|
|
case TSDB_CACHE_MODEL_LAST_VALUE:
|
|
return hasLast;
|
|
case TSDB_CACHE_MODEL_BOTH:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// @brief check if we can apply last row scan optimization
|
|
/// @param lastColNum how many distinct last col specified
|
|
/// @param lastColId only used when lastColNum equals 1, the col id of the only one last col
|
|
/// @param selectNonPKColNum num of normal cols
|
|
/// @param selectNonPKColId only used when selectNonPKColNum equals 1, the col id of the only one select col
|
|
static bool lastRowScanOptCheckColNum(int32_t lastColNum, col_id_t lastColId, int32_t selectNonPKColNum,
|
|
col_id_t selectNonPKColId) {
|
|
// multi select non pk col + last func: select c1, c2, last(c1)
|
|
if (selectNonPKColNum > 1 && lastColNum > 0) return false;
|
|
|
|
if (selectNonPKColNum == 1) {
|
|
// select last(c1), last(c2), c1 ...
|
|
// which is not possible currently
|
|
if (lastColNum > 1) return false;
|
|
|
|
// select last(c1), c2 ...
|
|
if (lastColNum == 1 && lastColId != selectNonPKColId) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool isNeedSplitCacheLastFunc(SFunctionNode* pFunc, SScanLogicNode* pScan) {
|
|
int32_t funcType = pFunc->funcType;
|
|
if ((FUNCTION_TYPE_LAST_ROW != funcType || (FUNCTION_TYPE_LAST_ROW == funcType && TSDB_CACHE_MODEL_LAST_VALUE == pScan->cacheLastMode)) &&
|
|
(FUNCTION_TYPE_LAST != funcType || (FUNCTION_TYPE_LAST == funcType && (TSDB_CACHE_MODEL_LAST_ROW == pScan->cacheLastMode ||
|
|
QUERY_NODE_OPERATOR == nodeType(nodesListGetNode(pFunc->pParameterList, 0)) || QUERY_NODE_VALUE == nodeType(nodesListGetNode(pFunc->pParameterList, 0)) ||
|
|
COLUMN_TYPE_COLUMN != ((SColumnNode*)nodesListGetNode(pFunc->pParameterList, 0))->colType))) &&
|
|
FUNCTION_TYPE_SELECT_VALUE != funcType && FUNCTION_TYPE_GROUP_KEY != funcType) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool lastRowScanOptCheckFuncList(SLogicNode* pNode, int8_t cacheLastModel, bool* hasOtherFunc) {
|
|
bool hasNonPKSelectFunc = false;
|
|
SNode* pFunc = NULL;
|
|
int32_t lastColNum = 0, selectNonPKColNum = 0;
|
|
col_id_t lastColId = -1, selectNonPKColId = -1;
|
|
SScanLogicNode* pScan = (SScanLogicNode*)nodesListGetNode(((SAggLogicNode*)pNode)->node.pChildren, 0);
|
|
uint32_t needSplitFuncCount = 0;
|
|
FOREACH(pFunc, ((SAggLogicNode*)pNode)->pAggFuncs) {
|
|
SFunctionNode* pAggFunc = (SFunctionNode*)pFunc;
|
|
SNode* pParam = nodesListGetNode(pAggFunc->pParameterList, 0);
|
|
if (FUNCTION_TYPE_LAST == pAggFunc->funcType) {
|
|
if (QUERY_NODE_COLUMN == nodeType(pParam)) {
|
|
SColumnNode* pCol = (SColumnNode*)pParam;
|
|
if (pCol->colType != COLUMN_TYPE_COLUMN && TSDB_CACHE_MODEL_LAST_ROW != cacheLastModel) {
|
|
needSplitFuncCount++;
|
|
*hasOtherFunc = true;
|
|
}
|
|
if (lastColId != pCol->colId) {
|
|
lastColId = pCol->colId;
|
|
lastColNum++;
|
|
}
|
|
}
|
|
else if (QUERY_NODE_VALUE == nodeType(pParam) || QUERY_NODE_OPERATOR == nodeType(pParam)) {
|
|
needSplitFuncCount++;
|
|
*hasOtherFunc = true;
|
|
}
|
|
if (!lastRowScanOptCheckColNum(lastColNum, lastColId, selectNonPKColNum, selectNonPKColId)) {
|
|
return false;
|
|
}
|
|
if (TSDB_CACHE_MODEL_LAST_ROW == cacheLastModel) {
|
|
needSplitFuncCount++;
|
|
*hasOtherFunc = true;
|
|
}
|
|
} else if (FUNCTION_TYPE_SELECT_VALUE == pAggFunc->funcType) {
|
|
if (QUERY_NODE_COLUMN == nodeType(pParam)) {
|
|
SColumnNode* pCol = (SColumnNode*)pParam;
|
|
if (COLUMN_TYPE_COLUMN == pCol->colType && PRIMARYKEY_TIMESTAMP_COL_ID != pCol->colId) {
|
|
if (selectNonPKColId != pCol->colId) {
|
|
selectNonPKColId = pCol->colId;
|
|
selectNonPKColNum++;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
} else if (lastColNum > 0) {
|
|
return false;
|
|
}
|
|
if (!lastRowScanOptCheckColNum(lastColNum, lastColId, selectNonPKColNum, selectNonPKColId)) return false;
|
|
} else if (FUNCTION_TYPE_GROUP_KEY == pAggFunc->funcType) {
|
|
if (!lastRowScanOptLastParaIsTag(nodesListGetNode(pAggFunc->pParameterList, 0))) {
|
|
return false;
|
|
}
|
|
} else if (FUNCTION_TYPE_LAST_ROW != pAggFunc->funcType) {
|
|
*hasOtherFunc = true;
|
|
needSplitFuncCount++;
|
|
} else if (FUNCTION_TYPE_LAST_ROW == pAggFunc->funcType && TSDB_CACHE_MODEL_LAST_VALUE == cacheLastModel) {
|
|
*hasOtherFunc = true;
|
|
needSplitFuncCount++;
|
|
}
|
|
}
|
|
if (needSplitFuncCount >= ((SAggLogicNode*)pNode)->pAggFuncs->length) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool lastRowScanOptCheckLastCache(SAggLogicNode* pAgg, SScanLogicNode* pScan) {
|
|
if ((pAgg->hasLastRow == pAgg->hasLast && !pAgg->hasLastRow) || (!pAgg->hasLast && !pAgg->hasLastRow) || NULL != pAgg->pGroupKeys || NULL != pScan->node.pConditions ||
|
|
!hasSuitableCache(pScan->cacheLastMode, pAgg->hasLastRow, pAgg->hasLast) ||
|
|
IS_TSWINDOW_SPECIFIED(pScan->scanRange)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool lastRowScanOptMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (QUERY_NODE_LOGIC_PLAN_AGG != nodeType(pNode) || 1 != LIST_LENGTH(pNode->pChildren) ||
|
|
QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(nodesListGetNode(pNode->pChildren, 0))) {
|
|
return false;
|
|
}
|
|
|
|
SAggLogicNode* pAgg = (SAggLogicNode*)pNode;
|
|
SScanLogicNode* pScan = (SScanLogicNode*)nodesListGetNode(pNode->pChildren, 0);
|
|
if (!lastRowScanOptCheckLastCache(pAgg, pScan)) {
|
|
return false;
|
|
}
|
|
|
|
bool hasOtherFunc = false;
|
|
if (!lastRowScanOptCheckFuncList(pNode, pScan->cacheLastMode, &hasOtherFunc)) {
|
|
return false;
|
|
}
|
|
|
|
if (hasOtherFunc) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
typedef struct SLastRowScanOptSetColDataTypeCxt {
|
|
bool doAgg;
|
|
SNodeList* pLastCols;
|
|
SNodeList* pOtherCols;
|
|
int32_t funcType;
|
|
int32_t pkBytes;
|
|
int32_t code;
|
|
} SLastRowScanOptSetColDataTypeCxt;
|
|
|
|
static EDealRes lastRowScanOptSetColDataType(SNode* pNode, void* pContext) {
|
|
if (QUERY_NODE_COLUMN == nodeType(pNode)) {
|
|
SLastRowScanOptSetColDataTypeCxt* pCxt = pContext;
|
|
if (pCxt->doAgg) {
|
|
pCxt->code = nodesListMakeAppend(&pCxt->pLastCols, pNode);
|
|
if (TSDB_CODE_SUCCESS != pCxt->code) {
|
|
return DEAL_RES_ERROR;
|
|
}
|
|
getLastCacheDataType(&(((SColumnNode*)pNode)->node.resType), pCxt->pkBytes);
|
|
} else {
|
|
SNode* pCol = NULL;
|
|
FOREACH(pCol, pCxt->pLastCols) {
|
|
if (nodesEqualNode(pCol, pNode)) {
|
|
getLastCacheDataType(&(((SColumnNode*)pNode)->node.resType), pCxt->pkBytes);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return DEAL_RES_IGNORE_CHILD;
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static void lastRowScanOptSetLastTargets(SNodeList* pTargets, SNodeList* pLastCols, SNodeList* pLastRowCols, bool erase, int32_t pkBytes) {
|
|
SNode* pTarget = NULL;
|
|
WHERE_EACH(pTarget, pTargets) {
|
|
bool found = false;
|
|
SNode* pCol = NULL;
|
|
FOREACH(pCol, pLastCols) {
|
|
if (nodesEqualNode(pCol, pTarget)) {
|
|
getLastCacheDataType(&(((SColumnNode*)pTarget)->node.resType), pkBytes);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found && nodeListNodeEqual(pLastRowCols, pTarget)) {
|
|
found = true;
|
|
}
|
|
|
|
if (!found && erase) {
|
|
ERASE_NODE(pTargets);
|
|
continue;
|
|
}
|
|
WHERE_NEXT;
|
|
}
|
|
}
|
|
|
|
static void lastRowScanOptRemoveUslessTargets(SNodeList* pTargets, SNodeList* pList1, SNodeList* pList2, SNodeList* pList3) {
|
|
SNode* pTarget = NULL;
|
|
WHERE_EACH(pTarget, pTargets) {
|
|
bool found = false;
|
|
SNode* pCol = NULL;
|
|
FOREACH(pCol, pList1) {
|
|
if (nodesEqualNode(pCol, pTarget)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
FOREACH(pCol, pList2) {
|
|
if (nodesEqualNode(pCol, pTarget)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found && nodeListNodeEqual(pList3, pTarget)) {
|
|
found = true;
|
|
}
|
|
|
|
if (!found) {
|
|
ERASE_NODE(pTargets);
|
|
continue;
|
|
}
|
|
WHERE_NEXT;
|
|
}
|
|
}
|
|
|
|
static int32_t lastRowScanBuildFuncTypes(SScanLogicNode* pScan, SColumnNode* pColNode, int32_t funcType) {
|
|
SFunctParam* pFuncTypeParam = taosMemoryCalloc(1, sizeof(SFunctParam));
|
|
if (NULL == pFuncTypeParam) {
|
|
return TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
pFuncTypeParam->type = funcType;
|
|
if (NULL == pScan->pFuncTypes) {
|
|
pScan->pFuncTypes = taosArrayInit(pScan->pScanCols->length, sizeof(SFunctParam));
|
|
if (NULL == pScan->pFuncTypes) {
|
|
taosMemoryFree(pFuncTypeParam);
|
|
return TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
pFuncTypeParam->pCol = taosMemoryCalloc(1, sizeof(SColumn));
|
|
if (NULL == pFuncTypeParam->pCol) {
|
|
taosMemoryFree(pFuncTypeParam);
|
|
return TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
pFuncTypeParam->pCol->colId = pColNode->colId;
|
|
strcpy(pFuncTypeParam->pCol->name, pColNode->colName);
|
|
if (NULL == taosArrayPush(pScan->pFuncTypes, pFuncTypeParam)) {
|
|
taosMemoryFree(pFuncTypeParam);
|
|
return TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
|
|
taosMemoryFree(pFuncTypeParam);
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static int32_t lastRowScanOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SAggLogicNode* pAgg = (SAggLogicNode*)optFindPossibleNode(pLogicSubplan->pNode, lastRowScanOptMayBeOptimized, NULL);
|
|
|
|
if (NULL == pAgg) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
SLastRowScanOptSetColDataTypeCxt cxt = {.doAgg = true, .pLastCols = NULL, .pOtherCols = NULL};
|
|
SNode* pNode = NULL;
|
|
SColumnNode* pPKTsCol = NULL;
|
|
SColumnNode* pNonPKCol = NULL;
|
|
SScanLogicNode* pScan = (SScanLogicNode*)nodesListGetNode(pAgg->node.pChildren, 0);
|
|
pScan->scanType = SCAN_TYPE_LAST_ROW;
|
|
pScan->igLastNull = pAgg->hasLast ? true : false;
|
|
SArray* isDuplicateCol = taosArrayInit(pScan->pScanCols->length, sizeof(bool));
|
|
SNodeList* pLastRowCols = NULL;
|
|
bool adjLastRowTsColName = false;
|
|
char tsColName[TSDB_COL_NAME_LEN] = {0};
|
|
int32_t code = 0;
|
|
|
|
FOREACH(pNode, pAgg->pAggFuncs) {
|
|
SFunctionNode* pFunc = (SFunctionNode*)pNode;
|
|
int32_t funcType = pFunc->funcType;
|
|
SNode* pParamNode = NULL;
|
|
if (FUNCTION_TYPE_LAST == funcType) {
|
|
(void)nodesListErase(pFunc->pParameterList, nodesListGetCell(pFunc->pParameterList, 1));
|
|
nodesWalkExpr(nodesListGetNode(pFunc->pParameterList, 0), lastRowScanOptSetColDataType, &cxt);
|
|
if (TSDB_CODE_SUCCESS != cxt.code) break;
|
|
}
|
|
FOREACH(pParamNode, pFunc->pParameterList) {
|
|
if (FUNCTION_TYPE_LAST_ROW == funcType || FUNCTION_TYPE_LAST == funcType) {
|
|
int32_t len = snprintf(pFunc->functionName, sizeof(pFunc->functionName),
|
|
FUNCTION_TYPE_LAST_ROW == funcType ? "_cache_last_row" : "_cache_last");
|
|
pFunc->functionName[len] = '\0';
|
|
code = fmGetFuncInfo(pFunc, NULL, 0);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesClearList(cxt.pLastCols);
|
|
break;
|
|
}
|
|
cxt.funcType = pFunc->funcType;
|
|
cxt.pkBytes = (pFunc->hasPk) ? pFunc->pkBytes : 0;
|
|
// add duplicate cols which be removed for both last_row, last
|
|
if (pAgg->hasLast && pAgg->hasLastRow) {
|
|
if (QUERY_NODE_COLUMN == nodeType(pParamNode)) {
|
|
SNode* pColNode = NULL;
|
|
int i = 0;
|
|
FOREACH(pColNode, pScan->pScanCols) {
|
|
bool isDup = false;
|
|
bool* isDuplicate = taosArrayGet(isDuplicateCol, i);
|
|
if (NULL == isDuplicate) {
|
|
if (NULL == taosArrayInsert(isDuplicateCol, i, &isDup)) {
|
|
code = terrno;
|
|
break;
|
|
}
|
|
isDuplicate = taosArrayGet(isDuplicateCol, i);
|
|
}
|
|
i++;
|
|
if (nodesEqualNode(pParamNode, pColNode)) {
|
|
if (*isDuplicate) {
|
|
if (0 == strncmp(((SColumnNode*)pColNode)->colName, "#dup_col.", 9)) {
|
|
continue;
|
|
}
|
|
SNode* newColNode = NULL;
|
|
code = nodesCloneNode(pColNode, &newColNode);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
sprintf(((SColumnNode*)newColNode)->colName, "#dup_col.%p", newColNode);
|
|
sprintf(((SColumnNode*)pParamNode)->colName, "#dup_col.%p", newColNode);
|
|
if (FUNCTION_TYPE_LAST_ROW == funcType &&
|
|
((SColumnNode*)pParamNode)->colId == PRIMARYKEY_TIMESTAMP_COL_ID) {
|
|
if (!adjLastRowTsColName) {
|
|
adjLastRowTsColName = true;
|
|
strncpy(tsColName, ((SColumnNode*)pParamNode)->colName, TSDB_COL_NAME_LEN);
|
|
} else {
|
|
strncpy(((SColumnNode*)pParamNode)->colName, tsColName, TSDB_COL_NAME_LEN);
|
|
nodesDestroyNode(newColNode);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
code = nodesListStrictAppend(pScan->pScanCols, newColNode);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
isDup = true;
|
|
if (NULL == taosArrayInsert(isDuplicateCol, pScan->pScanCols->length, &isDup)) {
|
|
code = terrno;
|
|
break;
|
|
}
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(newColNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
code = nodesListStrictAppend(pScan->node.pTargets, pNew);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
if (funcType != FUNCTION_TYPE_LAST) {
|
|
pNew = NULL;
|
|
code = nodesCloneNode(newColNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
code = nodesListMakeAppend(&pLastRowCols, pNew);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
}
|
|
|
|
code = lastRowScanBuildFuncTypes(pScan, (SColumnNode*)newColNode, pFunc->funcType);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
} else {
|
|
isDup = true;
|
|
*isDuplicate = isDup;
|
|
if (funcType != FUNCTION_TYPE_LAST && !nodeListNodeEqual(cxt.pLastCols, pColNode)) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pColNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
code = nodesListMakeStrictAppend(&pLastRowCols, pNew);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
}
|
|
code = lastRowScanBuildFuncTypes(pScan, (SColumnNode*)pColNode, pFunc->funcType);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
}
|
|
continue;
|
|
} else if (nodeListNodeEqual(pFunc->pParameterList, pColNode)) {
|
|
if (funcType != FUNCTION_TYPE_LAST && ((SColumnNode*)pColNode)->colId == PRIMARYKEY_TIMESTAMP_COL_ID &&
|
|
!nodeListNodeEqual(pLastRowCols, pColNode)) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pColNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
code = nodesListMakeAppend(&pLastRowCols, pNew);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
|
|
code = lastRowScanBuildFuncTypes(pScan, (SColumnNode*)pColNode, pFunc->funcType);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
isDup = true;
|
|
*isDuplicate = isDup;
|
|
}
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) break;;
|
|
FOREACH(pColNode, pScan->pScanPseudoCols) {
|
|
if (nodesEqualNode(pParamNode, pColNode)) {
|
|
if (funcType != FUNCTION_TYPE_LAST) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pColNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
code = nodesListMakeAppend(&pLastRowCols, pNew);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
if (pFunc->hasPk) {
|
|
code = nodesListMakeAppend(&cxt.pOtherCols, nodesListGetNode(pFunc->pParameterList, LIST_LENGTH(pFunc->pParameterList) - 1));
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
} else {
|
|
pNode = nodesListGetNode(pFunc->pParameterList, 0);
|
|
code = nodesListMakeAppend(&cxt.pOtherCols, pNode);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
|
|
if (FUNCTION_TYPE_SELECT_VALUE == funcType) {
|
|
if (nodeType(pNode) == QUERY_NODE_COLUMN) {
|
|
SColumnNode* pCol = (SColumnNode*)pNode;
|
|
if (pCol->colId == PRIMARYKEY_TIMESTAMP_COL_ID) {
|
|
pPKTsCol = pCol;
|
|
} else {
|
|
pNonPKCol = pCol;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
taosArrayDestroy(isDuplicateCol);
|
|
nodesClearList(cxt.pLastCols);
|
|
return code;
|
|
}
|
|
|
|
if (NULL != cxt.pLastCols) {
|
|
cxt.doAgg = false;
|
|
cxt.funcType = FUNCTION_TYPE_CACHE_LAST;
|
|
|
|
lastRowScanOptSetLastTargets(pScan->pScanCols, cxt.pLastCols, pLastRowCols, true, cxt.pkBytes);
|
|
nodesWalkExprs(pScan->pScanPseudoCols, lastRowScanOptSetColDataType, &cxt);
|
|
if (TSDB_CODE_SUCCESS != cxt.code) {
|
|
nodesClearList(cxt.pLastCols);
|
|
nodesClearList(cxt.pOtherCols);
|
|
taosArrayDestroy(isDuplicateCol);
|
|
return cxt.code;
|
|
}
|
|
lastRowScanOptSetLastTargets(pScan->node.pTargets, cxt.pLastCols, pLastRowCols, false, cxt.pkBytes);
|
|
lastRowScanOptRemoveUslessTargets(pScan->node.pTargets, cxt.pLastCols, cxt.pOtherCols, pLastRowCols);
|
|
if (pPKTsCol &&
|
|
((cxt.pLastCols->length == 1 && nodesEqualNode((SNode*)pPKTsCol, nodesListGetNode(cxt.pLastCols, 0))) ||
|
|
(pScan->node.pTargets->length == 2 && cxt.pkBytes > 0))) {
|
|
// when select last(ts),tbname,ts from ..., we add another ts to targets
|
|
sprintf(pPKTsCol->colName, "#sel_val.%p", pPKTsCol);
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode((SNode*)pPKTsCol, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListAppend(pScan->node.pTargets, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesClearList(cxt.pLastCols);
|
|
nodesClearList(cxt.pOtherCols);
|
|
taosArrayDestroy(isDuplicateCol);
|
|
return code;
|
|
}
|
|
}
|
|
|
|
if (pNonPKCol && cxt.pLastCols->length == 1 &&
|
|
nodesEqualNode((SNode*)pNonPKCol, nodesListGetNode(cxt.pLastCols, 0))) {
|
|
// when select last(c1), c1 from ..., we add c1 to targets
|
|
sprintf(pNonPKCol->colName, "#sel_val.%p", pNonPKCol);
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode((SNode*)pNonPKCol, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListAppend(pScan->node.pTargets, pNew);
|
|
}
|
|
}
|
|
|
|
nodesClearList(cxt.pLastCols);
|
|
}
|
|
nodesClearList(cxt.pOtherCols);
|
|
|
|
pAgg->hasLastRow = false;
|
|
pAgg->hasLast = false;
|
|
|
|
pCxt->optimized = true;
|
|
taosArrayDestroy(isDuplicateCol);
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static bool splitCacheLastFuncOptMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (QUERY_NODE_LOGIC_PLAN_AGG != nodeType(pNode) || 1 != LIST_LENGTH(pNode->pChildren) ||
|
|
QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(nodesListGetNode(pNode->pChildren, 0))) {
|
|
return false;
|
|
}
|
|
|
|
SAggLogicNode* pAgg = (SAggLogicNode*)pNode;
|
|
SScanLogicNode* pScan = (SScanLogicNode*)nodesListGetNode(pNode->pChildren, 0);
|
|
if (!lastRowScanOptCheckLastCache(pAgg, pScan)) {
|
|
return false;
|
|
}
|
|
|
|
bool hasOtherFunc = false;
|
|
if (!lastRowScanOptCheckFuncList(pNode, pScan->cacheLastMode, &hasOtherFunc)) {
|
|
return false;
|
|
}
|
|
|
|
if (pAgg->hasGroup || !hasOtherFunc) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int32_t splitCacheLastFuncOptCreateAggLogicNode(SAggLogicNode** pNewAgg, SAggLogicNode* pAgg, SNodeList* pFunc,
|
|
SNodeList* pTargets) {
|
|
SAggLogicNode* pNew = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_LOGIC_PLAN_AGG, (SNode**)&pNew);
|
|
if (NULL == pNew) {
|
|
nodesDestroyList(pFunc);
|
|
nodesDestroyList(pTargets);
|
|
return code;
|
|
}
|
|
|
|
pNew->hasLastRow = false;
|
|
pNew->hasLast = false;
|
|
SNode* pFuncNode = NULL;
|
|
FOREACH(pFuncNode, pFunc) {
|
|
SFunctionNode* pFunc = (SFunctionNode*)pFuncNode;
|
|
if (FUNCTION_TYPE_LAST_ROW == pFunc->funcType) {
|
|
pNew->hasLastRow = true;
|
|
} else if (FUNCTION_TYPE_LAST == pFunc->funcType) {
|
|
pNew->hasLast = true;
|
|
}
|
|
}
|
|
|
|
pNew->hasTimeLineFunc = pAgg->hasTimeLineFunc;
|
|
pNew->hasGroupKeyOptimized = false;
|
|
pNew->onlyHasKeepOrderFunc = pAgg->onlyHasKeepOrderFunc;
|
|
pNew->node.groupAction = pAgg->node.groupAction;
|
|
pNew->node.requireDataOrder = pAgg->node.requireDataOrder;
|
|
pNew->node.resultDataOrder = pAgg->node.resultDataOrder;
|
|
pNew->node.pTargets = pTargets;
|
|
pNew->pAggFuncs = pFunc;
|
|
code = nodesCloneList(pAgg->pGroupKeys, &pNew->pGroupKeys);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pNew);
|
|
return code;
|
|
}
|
|
code = nodesCloneNode(pAgg->node.pConditions, &pNew->node.pConditions);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pNew);
|
|
return code;
|
|
}
|
|
pNew->isGroupTb = pAgg->isGroupTb;
|
|
pNew->isPartTb = pAgg->isPartTb;
|
|
pNew->hasGroup = pAgg->hasGroup;
|
|
code = nodesCloneList(pAgg->node.pChildren, &pNew->node.pChildren);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pNew);
|
|
return code;
|
|
}
|
|
|
|
SNode* pNode = nodesListGetNode(pNew->node.pChildren, 0);
|
|
if (QUERY_NODE_LOGIC_PLAN_SCAN == nodeType(pNode)) {
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pNode;
|
|
SNodeList* pOldScanCols = NULL;
|
|
TSWAP(pScan->pScanCols, pOldScanCols);
|
|
nodesDestroyList(pScan->pScanPseudoCols);
|
|
pScan->pScanPseudoCols = NULL;
|
|
nodesDestroyList(pScan->node.pTargets);
|
|
pScan->node.pTargets = NULL;
|
|
SNodeListNode* list = NULL;
|
|
code = nodesMakeNode(QUERY_NODE_NODE_LIST, (SNode**)&list);
|
|
if (!list) {
|
|
nodesDestroyNode((SNode*)pNew);
|
|
return code;
|
|
}
|
|
list->pNodeList = pFunc;
|
|
code = nodesCollectColumnsFromNode((SNode*)list, NULL, COLLECT_COL_TYPE_COL, &pScan->pScanCols);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pNew);
|
|
return code;
|
|
}
|
|
code = nodesCollectColumnsFromNode((SNode*)list, NULL, COLLECT_COL_TYPE_TAG, &pScan->pScanPseudoCols);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pNew);
|
|
return code;
|
|
}
|
|
nodesFree(list);
|
|
bool found = false;
|
|
FOREACH(pNode, pScan->pScanCols) {
|
|
if (PRIMARYKEY_TIMESTAMP_COL_ID == ((SColumnNode*)pNode)->colId) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
FOREACH(pNode, pOldScanCols) {
|
|
if (PRIMARYKEY_TIMESTAMP_COL_ID == ((SColumnNode*)pNode)->colId) {
|
|
SNode* pTmp = NULL;
|
|
code = nodesCloneNode(pNode, &pTmp);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pScan->pScanCols, pTmp);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pNew);
|
|
return code;
|
|
}
|
|
}
|
|
nodesDestroyList(pOldScanCols);
|
|
code = createColumnByRewriteExprs(pScan->pScanCols, &pScan->node.pTargets);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pNew);
|
|
return code;
|
|
}
|
|
code = createColumnByRewriteExprs(pScan->pScanPseudoCols, &pScan->node.pTargets);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pNew);
|
|
return code;
|
|
}
|
|
OPTIMIZE_FLAG_CLEAR_MASK(pScan->node.optimizedFlag, OPTIMIZE_FLAG_SCAN_PATH);
|
|
}
|
|
|
|
*pNewAgg = pNew;
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static int32_t splitCacheLastFuncOptModifyAggLogicNode(SAggLogicNode* pAgg) {
|
|
pAgg->hasTimeLineFunc = false;
|
|
pAgg->onlyHasKeepOrderFunc = true;
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static int32_t splitCacheLastFuncOptCreateMergeLogicNode(SMergeLogicNode** pNew, SAggLogicNode* pAgg1,
|
|
SAggLogicNode* pAgg2) {
|
|
SMergeLogicNode* pMerge = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_LOGIC_PLAN_MERGE, (SNode**)&pMerge);
|
|
if (NULL == pMerge) {
|
|
return code;
|
|
}
|
|
pMerge->colsMerge = true;
|
|
pMerge->numOfChannels = 2;
|
|
pMerge->srcGroupId = -1;
|
|
pMerge->node.precision = pAgg1->node.precision;
|
|
|
|
SNode* pNewAgg1 = NULL;
|
|
code = nodesCloneNode((SNode*)pAgg1, &pNewAgg1);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pMerge);
|
|
return code;
|
|
}
|
|
SNode* pNewAgg2 = NULL;
|
|
code = nodesCloneNode((SNode*)pAgg2, &pNewAgg2);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode(pNewAgg1);
|
|
nodesDestroyNode((SNode*)pMerge);
|
|
return code;
|
|
}
|
|
|
|
((SAggLogicNode*)pNewAgg1)->node.pParent = (SLogicNode*)pMerge;
|
|
((SAggLogicNode*)pNewAgg2)->node.pParent = (SLogicNode*)pMerge;
|
|
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, ((SAggLogicNode*)pNewAgg1)->node.pChildren) { ((SLogicNode*)pNode)->pParent = (SLogicNode*)pNewAgg1; }
|
|
FOREACH(pNode, ((SAggLogicNode*)pNewAgg2)->node.pChildren) { ((SLogicNode*)pNode)->pParent = (SLogicNode*)pNewAgg2; }
|
|
|
|
SNodeList* pNewTargets1 = NULL;
|
|
code = nodesCloneList(pAgg1->node.pTargets, &pNewTargets1);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppendList(&pMerge->node.pTargets, pNewTargets1);
|
|
}
|
|
SNodeList* pNewTargets2 = NULL;
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesCloneList(pAgg2->node.pTargets, &pNewTargets2);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppendList(&pMerge->node.pTargets, pNewTargets2);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pMerge->node.pChildren, pNewAgg1);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pMerge->node.pChildren, pNewAgg2);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode(pNewAgg1);
|
|
nodesDestroyNode(pNewAgg2);
|
|
nodesDestroyNode((SNode*)pMerge);
|
|
} else {
|
|
*pNew = pMerge;
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t splitCacheLastFuncOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SAggLogicNode* pAgg = (SAggLogicNode*)optFindPossibleNode(pLogicSubplan->pNode, splitCacheLastFuncOptMayBeOptimized, NULL);
|
|
|
|
if (NULL == pAgg) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
SScanLogicNode* pScan = (SScanLogicNode*)nodesListGetNode(pAgg->node.pChildren, 0);
|
|
SNode* pNode = NULL;
|
|
SNodeList* pAggFuncList = NULL;
|
|
int32_t code = 0;
|
|
|
|
{
|
|
bool hasLast = false;
|
|
bool hasLastRow = false;
|
|
WHERE_EACH(pNode, pAgg->pAggFuncs) {
|
|
SFunctionNode* pFunc = (SFunctionNode*)pNode;
|
|
int32_t funcType = pFunc->funcType;
|
|
|
|
if (isNeedSplitCacheLastFunc(pFunc, pScan)) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pAggFuncList, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
ERASE_NODE(pAgg->pAggFuncs);
|
|
continue;
|
|
}
|
|
if (FUNCTION_TYPE_LAST_ROW == funcType ) {
|
|
hasLastRow = true;
|
|
} else if (FUNCTION_TYPE_LAST == funcType) {
|
|
hasLast = true;
|
|
}
|
|
WHERE_NEXT;
|
|
}
|
|
pAgg->hasLast = hasLast;
|
|
pAgg->hasLastRow = hasLastRow;
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyList(pAggFuncList);
|
|
return code;
|
|
}
|
|
|
|
if (NULL == pAggFuncList) {
|
|
planError("empty agg func list while splite projections, funcNum:%d", pAgg->pAggFuncs->length);
|
|
return TSDB_CODE_PLAN_INTERNAL_ERROR;
|
|
}
|
|
|
|
SNodeList* pTargets = NULL;
|
|
{
|
|
WHERE_EACH(pNode, pAgg->node.pTargets) {
|
|
SColumnNode* pCol = (SColumnNode*)pNode;
|
|
SNode* pFuncNode = NULL;
|
|
bool found = false;
|
|
FOREACH(pFuncNode, pAggFuncList) {
|
|
SFunctionNode* pFunc = (SFunctionNode*)pFuncNode;
|
|
if (0 == strcmp(pFunc->node.aliasName, pCol->colName)) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pTargets, pNew);
|
|
}
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
if (found) {
|
|
ERASE_NODE(pAgg->node.pTargets);
|
|
continue;
|
|
}
|
|
WHERE_NEXT;
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyList(pTargets);
|
|
return code;
|
|
}
|
|
|
|
if (NULL == pTargets) {
|
|
planError("empty target func list while splite projections, targetsNum:%d", pAgg->node.pTargets->length);
|
|
nodesDestroyList(pAggFuncList);
|
|
return TSDB_CODE_PLAN_INTERNAL_ERROR;
|
|
}
|
|
|
|
SMergeLogicNode* pMerge = NULL;
|
|
SAggLogicNode* pNewAgg = NULL;
|
|
code = splitCacheLastFuncOptCreateAggLogicNode(&pNewAgg, pAgg, pAggFuncList, pTargets);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = splitCacheLastFuncOptModifyAggLogicNode(pAgg);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = splitCacheLastFuncOptCreateMergeLogicNode(&pMerge, pNewAgg, pAgg);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = replaceLogicNode(pLogicSubplan, (SLogicNode*)pAgg, (SLogicNode*)pMerge);
|
|
}
|
|
|
|
nodesDestroyNode((SNode*)pAgg);
|
|
nodesDestroyNode((SNode*)pNewAgg);
|
|
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pMerge);
|
|
}
|
|
|
|
pCxt->optimized = true;
|
|
return code;
|
|
}
|
|
|
|
// merge projects
|
|
static bool mergeProjectsMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (QUERY_NODE_LOGIC_PLAN_PROJECT != nodeType(pNode) || 1 != LIST_LENGTH(pNode->pChildren)) {
|
|
return false;
|
|
}
|
|
SLogicNode* pChild = (SLogicNode*)nodesListGetNode(pNode->pChildren, 0);
|
|
if (QUERY_NODE_LOGIC_PLAN_PROJECT != nodeType(pChild) || 1 < LIST_LENGTH(pChild->pChildren) ||
|
|
NULL != pChild->pConditions || NULL != pChild->pLimit || NULL != pChild->pSlimit) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
typedef struct SMergeProjectionsContext {
|
|
SProjectLogicNode* pChildProj;
|
|
int32_t errCode;
|
|
} SMergeProjectionsContext;
|
|
|
|
static EDealRes mergeProjectionsExpr2(SNode** pNode, void* pContext) {
|
|
SMergeProjectionsContext* pCxt = pContext;
|
|
SProjectLogicNode* pChildProj = pCxt->pChildProj;
|
|
if (QUERY_NODE_COLUMN == nodeType(*pNode)) {
|
|
SColumnNode* pProjCol = (SColumnNode*)(*pNode);
|
|
SNode* pProjection;
|
|
int32_t projIdx = 1;
|
|
FOREACH(pProjection, pChildProj->pProjections) {
|
|
if (isColRefExpr(pProjCol, (SExprNode*)pProjection)) {
|
|
|
|
//}
|
|
//if (0 == strcmp(((SColumnNode*)(*pNode))->colName, ((SExprNode*)pProjection)->aliasName)) {
|
|
SNode* pExpr = NULL;
|
|
pCxt->errCode = nodesCloneNode(pProjection, &pExpr);
|
|
if (pExpr == NULL) {
|
|
return DEAL_RES_ERROR;
|
|
}
|
|
snprintf(((SExprNode*)pExpr)->aliasName, sizeof(((SExprNode*)pExpr)->aliasName), "%s",
|
|
((SExprNode*)*pNode)->aliasName);// 保留外层project的aliasname, 外层project的aliasName是被改写过的.
|
|
nodesDestroyNode(*pNode);
|
|
*pNode = pExpr;
|
|
return DEAL_RES_IGNORE_CHILD;
|
|
}
|
|
}
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static EDealRes mergeProjectionsExpr(SNode** pNode, void* pContext) {
|
|
SMergeProjectionsContext* pCxt = pContext;
|
|
SProjectLogicNode* pChildProj = pCxt->pChildProj;
|
|
if (QUERY_NODE_COLUMN == nodeType(*pNode)) {
|
|
SNode* pTarget;
|
|
FOREACH(pTarget, ((SLogicNode*)pChildProj)->pTargets) {
|
|
if (nodesEqualNode(pTarget, *pNode)) { // pNode是projectlist里的, aliasName被改写成了expr_#, 而pTarget是根据childProject的projectlist生成的, node里面啥都没有
|
|
SNode* pProjection;
|
|
FOREACH(pProjection, pChildProj->pProjections) {
|
|
if (0 == strcmp(((SColumnNode*)pTarget)->colName, ((SExprNode*)pProjection)->aliasName)) {
|
|
SNode* pExpr = NULL;
|
|
pCxt->errCode = nodesCloneNode(pProjection, &pExpr);
|
|
if (pExpr == NULL) {
|
|
return DEAL_RES_ERROR;
|
|
}
|
|
snprintf(((SExprNode*)pExpr)->aliasName, sizeof(((SExprNode*)pExpr)->aliasName), "%s",
|
|
((SExprNode*)*pNode)->aliasName);
|
|
nodesDestroyNode(*pNode);
|
|
*pNode = pExpr;
|
|
return DEAL_RES_IGNORE_CHILD;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static int32_t mergeProjectsOptimizeImpl(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan, SLogicNode* pSelfNode) {
|
|
SLogicNode* pChild = (SLogicNode*)nodesListGetNode(pSelfNode->pChildren, 0);
|
|
if (((SProjectLogicNode*)pChild)->ignoreGroupId) {
|
|
((SProjectLogicNode*)pSelfNode)->inputIgnoreGroup = true;
|
|
}
|
|
SMergeProjectionsContext cxt = {.pChildProj = (SProjectLogicNode*)pChild, .errCode = TSDB_CODE_SUCCESS};
|
|
nodesRewriteExprs(((SProjectLogicNode*)pSelfNode)->pProjections, mergeProjectionsExpr2, &cxt);
|
|
int32_t code = cxt.errCode;
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
if (1 == LIST_LENGTH(pChild->pChildren)) {
|
|
SLogicNode* pGrandChild = (SLogicNode*)nodesListGetNode(pChild->pChildren, 0);
|
|
code = replaceLogicNode(pLogicSubplan, pChild, pGrandChild);
|
|
} else { // no grand child
|
|
NODES_CLEAR_LIST(pSelfNode->pChildren);
|
|
}
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
NODES_CLEAR_LIST(pChild->pChildren);
|
|
}
|
|
nodesDestroyNode((SNode*)pChild);
|
|
pCxt->optimized = true;
|
|
return code;
|
|
}
|
|
|
|
static int32_t mergeProjectsOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SLogicNode* pProjectNode = optFindPossibleNode(pLogicSubplan->pNode, mergeProjectsMayBeOptimized, NULL);
|
|
if (NULL == pProjectNode) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
return mergeProjectsOptimizeImpl(pCxt, pLogicSubplan, pProjectNode);
|
|
}
|
|
|
|
static bool tagScanOptShouldBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(pNode) || (SCAN_TYPE_TAG == ((SScanLogicNode*)pNode)->scanType)) {
|
|
return false;
|
|
}
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pNode;
|
|
if (pScan->hasNormalCols) {
|
|
return false;
|
|
}
|
|
if (pScan->tableType == TSDB_SYSTEM_TABLE) {
|
|
return false;
|
|
}
|
|
if (NULL == pNode->pParent || QUERY_NODE_LOGIC_PLAN_AGG != nodeType(pNode->pParent) ||
|
|
1 != LIST_LENGTH(pNode->pParent->pChildren)) {
|
|
return false;
|
|
}
|
|
|
|
SAggLogicNode* pAgg = (SAggLogicNode*)(pNode->pParent);
|
|
if (NULL == pAgg->pGroupKeys || NULL != pAgg->pAggFuncs || keysHasCol(pAgg->pGroupKeys) ||
|
|
!planOptNodeListHasTbname(pAgg->pGroupKeys)) {
|
|
return false;
|
|
}
|
|
|
|
SNode* pGroupKey = NULL;
|
|
FOREACH(pGroupKey, pAgg->pGroupKeys) {
|
|
SNode* pGroup = NULL;
|
|
FOREACH(pGroup, ((SGroupingSetNode*)pGroupKey)->pParameterList) {
|
|
if (QUERY_NODE_COLUMN != nodeType(pGroup)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int32_t tagScanOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SScanLogicNode* pScanNode = (SScanLogicNode*)optFindPossibleNode(pLogicSubplan->pNode, tagScanOptShouldBeOptimized, NULL);
|
|
if (NULL == pScanNode) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
pScanNode->scanType = SCAN_TYPE_TAG;
|
|
SNode* pTarget = NULL;
|
|
FOREACH(pTarget, pScanNode->node.pTargets) {
|
|
if (PRIMARYKEY_TIMESTAMP_COL_ID == ((SColumnNode*)(pTarget))->colId) {
|
|
ERASE_NODE(pScanNode->node.pTargets);
|
|
break;
|
|
}
|
|
}
|
|
|
|
NODES_DESTORY_LIST(pScanNode->pScanCols);
|
|
|
|
SLogicNode* pAgg = pScanNode->node.pParent;
|
|
if (NULL == pAgg->pParent) {
|
|
SNodeList* pScanTargets = NULL;
|
|
int32_t code = nodesMakeList(&pScanTargets);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
|
|
SNode* pAggTarget = NULL;
|
|
FOREACH(pAggTarget, pAgg->pTargets) {
|
|
SNode* pScanTarget = NULL;
|
|
FOREACH(pScanTarget, pScanNode->node.pTargets) {
|
|
if (0 == strcmp(((SColumnNode*)pAggTarget)->colName, ((SColumnNode*)pScanTarget)->colName)) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pScanTarget, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListAppend(pScanTargets, pNew);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
nodesDestroyList(pScanNode->node.pTargets);
|
|
pScanNode->node.pTargets = pScanTargets;
|
|
}
|
|
|
|
pScanNode->onlyMetaCtbIdx = false;
|
|
|
|
pCxt->optimized = true;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static bool pushDownLimitOptShouldBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if ((NULL == pNode->pLimit && pNode->pSlimit == NULL) || 1 != LIST_LENGTH(pNode->pChildren)) {
|
|
return false;
|
|
}
|
|
|
|
SLogicNode* pChild = (SLogicNode*)nodesListGetNode(pNode->pChildren, 0);
|
|
if (pChild->pLimit || pChild->pSlimit) return false;
|
|
return true;
|
|
}
|
|
|
|
static void swapLimit(SLogicNode* pParent, SLogicNode* pChild) {
|
|
pChild->pLimit = pParent->pLimit;
|
|
pParent->pLimit = NULL;
|
|
}
|
|
|
|
static int32_t pushDownLimitHow(SLogicNode* pNodeWithLimit, SLogicNode* pNodeLimitPushTo, bool* pPushed);
|
|
static int32_t pushDownLimitTo(SLogicNode* pNodeWithLimit, SLogicNode* pNodeLimitPushTo, bool* pPushed) {
|
|
int32_t code = 0;
|
|
bool cloned;
|
|
switch (nodeType(pNodeLimitPushTo)) {
|
|
case QUERY_NODE_LOGIC_PLAN_WINDOW: {
|
|
SWindowLogicNode* pWindow = (SWindowLogicNode*)pNodeLimitPushTo;
|
|
if (pWindow->winType != WINDOW_TYPE_INTERVAL) break;
|
|
code = cloneLimit(pNodeWithLimit, pNodeLimitPushTo, CLONE_LIMIT_SLIMIT, &cloned);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pPushed = true;
|
|
}
|
|
return code;
|
|
}
|
|
case QUERY_NODE_LOGIC_PLAN_SORT:
|
|
if (((SSortLogicNode*)pNodeLimitPushTo)->calcGroupId) break;
|
|
// fall through
|
|
case QUERY_NODE_LOGIC_PLAN_FILL: {
|
|
SNode* pChild = NULL;
|
|
code = cloneLimit(pNodeWithLimit, pNodeLimitPushTo, CLONE_LIMIT_SLIMIT, &cloned);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
FOREACH(pChild, pNodeLimitPushTo->pChildren) {
|
|
code = pushDownLimitHow(pNodeLimitPushTo, (SLogicNode*)pChild, &cloned);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
}
|
|
*pPushed = true;
|
|
return code;
|
|
}
|
|
case QUERY_NODE_LOGIC_PLAN_AGG: {
|
|
if (nodeType(pNodeWithLimit) == QUERY_NODE_LOGIC_PLAN_PROJECT &&
|
|
(isPartTagAgg((SAggLogicNode*)pNodeLimitPushTo) || isPartTableAgg((SAggLogicNode*)pNodeLimitPushTo))) {
|
|
// when part by tag/tbname, slimit will be cloned to agg, and it will be pipelined.
|
|
// The scan below will do scanning with group order
|
|
code = cloneLimit(pNodeWithLimit, pNodeLimitPushTo, CLONE_SLIMIT, &cloned);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pPushed = cloned;
|
|
}
|
|
return code;
|
|
}
|
|
// else if not part by tag and tbname, the partition node below indicates that results are sorted, the agg node
|
|
// can be pipelined.
|
|
if (nodeType(pNodeWithLimit) == QUERY_NODE_LOGIC_PLAN_PROJECT && LIST_LENGTH(pNodeLimitPushTo->pChildren) == 1) {
|
|
SLogicNode* pChild = (SLogicNode*)nodesListGetNode(pNodeLimitPushTo->pChildren, 0);
|
|
if (nodeType(pChild) == QUERY_NODE_LOGIC_PLAN_PARTITION) {
|
|
pNodeLimitPushTo->forceCreateNonBlockingOptr = true;
|
|
code = cloneLimit(pNodeWithLimit, pNodeLimitPushTo, CLONE_SLIMIT, &cloned);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pPushed = cloned;
|
|
}
|
|
return code;
|
|
}
|
|
// Currently, partColOpt is executed after pushDownLimitOpt, and partColOpt will replace partition node with
|
|
// sort node.
|
|
// To avoid dependencies between these two optimizations, we add sort node too.
|
|
if (nodeType(pChild) == QUERY_NODE_LOGIC_PLAN_SORT && ((SSortLogicNode*)pChild)->calcGroupId) {
|
|
pNodeLimitPushTo->forceCreateNonBlockingOptr = true;
|
|
code = cloneLimit(pNodeWithLimit, pNodeLimitPushTo, CLONE_SLIMIT, &cloned);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pPushed = cloned;
|
|
}
|
|
return code;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case QUERY_NODE_LOGIC_PLAN_SCAN:
|
|
if (nodeType(pNodeWithLimit) == QUERY_NODE_LOGIC_PLAN_PROJECT && pNodeWithLimit->pLimit) {
|
|
if (((SProjectLogicNode*)pNodeWithLimit)->inputIgnoreGroup) {
|
|
code = cloneLimit(pNodeWithLimit, pNodeLimitPushTo, CLONE_LIMIT, &cloned);
|
|
} else {
|
|
swapLimit(pNodeWithLimit, pNodeLimitPushTo);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pPushed = true;
|
|
}
|
|
return code;
|
|
}
|
|
break;
|
|
case QUERY_NODE_LOGIC_PLAN_JOIN: {
|
|
code = cloneLimit(pNodeWithLimit, pNodeLimitPushTo, CLONE_LIMIT, &cloned);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
*pPushed = false;
|
|
return code;
|
|
}
|
|
|
|
static int32_t pushDownLimitHow(SLogicNode* pNodeWithLimit, SLogicNode* pNodeLimitPushTo, bool* pPushed) {
|
|
switch (nodeType(pNodeWithLimit)) {
|
|
case QUERY_NODE_LOGIC_PLAN_PROJECT:
|
|
case QUERY_NODE_LOGIC_PLAN_FILL:
|
|
return pushDownLimitTo(pNodeWithLimit, pNodeLimitPushTo, pPushed);
|
|
case QUERY_NODE_LOGIC_PLAN_SORT: {
|
|
SSortLogicNode* pSort = (SSortLogicNode*)pNodeWithLimit;
|
|
if (sortPriKeyOptIsPriKeyOrderBy(pSort->pSortKeys)) return pushDownLimitTo(pNodeWithLimit, pNodeLimitPushTo, pPushed);
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
*pPushed = false;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static int32_t pushDownLimitOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SLogicNode* pNode = optFindPossibleNode(pLogicSubplan->pNode, pushDownLimitOptShouldBeOptimized, NULL);
|
|
if (NULL == pNode) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
SLogicNode* pChild = (SLogicNode*)nodesListGetNode(pNode->pChildren, 0);
|
|
nodesDestroyNode(pChild->pLimit);
|
|
bool pushed = false;
|
|
int32_t code = pushDownLimitHow(pNode, pChild, &pushed);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
if (pushed) {
|
|
pCxt->optimized = true;
|
|
}
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
typedef struct STbCntScanOptInfo {
|
|
SAggLogicNode* pAgg;
|
|
SScanLogicNode* pScan;
|
|
SName table;
|
|
} STbCntScanOptInfo;
|
|
|
|
static bool tbCntScanOptIsEligibleGroupKeys(SNodeList* pGroupKeys) {
|
|
if (NULL == pGroupKeys) {
|
|
return true;
|
|
}
|
|
|
|
SNode* pGroupKey = NULL;
|
|
FOREACH(pGroupKey, pGroupKeys) {
|
|
SNode* pKey = nodesListGetNode(((SGroupingSetNode*)pGroupKey)->pParameterList, 0);
|
|
if (QUERY_NODE_COLUMN != nodeType(pKey)) {
|
|
return false;
|
|
}
|
|
SColumnNode* pCol = (SColumnNode*)pKey;
|
|
if (0 != strcmp(pCol->colName, "db_name") && 0 != strcmp(pCol->colName, "stable_name")) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool tbCntScanOptNotNullableExpr(SNode* pNode) {
|
|
if (QUERY_NODE_COLUMN != nodeType(pNode)) {
|
|
return false;
|
|
}
|
|
const char* pColName = ((SColumnNode*)pNode)->colName;
|
|
return 0 == strcmp(pColName, "*") || 0 == strcmp(pColName, "db_name") || 0 == strcmp(pColName, "stable_name") ||
|
|
0 == strcmp(pColName, "table_name");
|
|
}
|
|
|
|
static bool tbCntScanOptIsEligibleAggFuncs(SNodeList* pAggFuncs) {
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pAggFuncs) {
|
|
SFunctionNode* pFunc = (SFunctionNode*)nodesListGetNode(pAggFuncs, 0);
|
|
if (FUNCTION_TYPE_COUNT != pFunc->funcType ||
|
|
!tbCntScanOptNotNullableExpr(nodesListGetNode(pFunc->pParameterList, 0))) {
|
|
return false;
|
|
}
|
|
}
|
|
return LIST_LENGTH(pAggFuncs) > 0;
|
|
}
|
|
|
|
static bool tbCntScanOptIsEligibleAgg(SAggLogicNode* pAgg) {
|
|
return tbCntScanOptIsEligibleGroupKeys(pAgg->pGroupKeys) && tbCntScanOptIsEligibleAggFuncs(pAgg->pAggFuncs);
|
|
}
|
|
|
|
static bool tbCntScanOptGetColValFromCond(SOperatorNode* pOper, SColumnNode** pCol, SValueNode** pVal) {
|
|
if (OP_TYPE_EQUAL != pOper->opType) {
|
|
return false;
|
|
}
|
|
|
|
*pCol = NULL;
|
|
*pVal = NULL;
|
|
if (QUERY_NODE_COLUMN == nodeType(pOper->pLeft)) {
|
|
*pCol = (SColumnNode*)pOper->pLeft;
|
|
} else if (QUERY_NODE_VALUE == nodeType(pOper->pLeft)) {
|
|
*pVal = (SValueNode*)pOper->pLeft;
|
|
}
|
|
if (QUERY_NODE_COLUMN == nodeType(pOper->pRight)) {
|
|
*pCol = (SColumnNode*)pOper->pRight;
|
|
} else if (QUERY_NODE_VALUE == nodeType(pOper->pRight)) {
|
|
*pVal = (SValueNode*)pOper->pRight;
|
|
}
|
|
|
|
return NULL != *pCol && NULL != *pVal;
|
|
}
|
|
|
|
static bool tbCntScanOptIsEligibleLogicCond(STbCntScanOptInfo* pInfo, SLogicConditionNode* pCond) {
|
|
if (LOGIC_COND_TYPE_AND != pCond->condType) {
|
|
return false;
|
|
}
|
|
|
|
bool hasDbCond = false;
|
|
bool hasStbCond = false;
|
|
SColumnNode* pCol = NULL;
|
|
SValueNode* pVal = NULL;
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pCond->pParameterList) {
|
|
if (QUERY_NODE_OPERATOR != nodeType(pNode) || !tbCntScanOptGetColValFromCond((SOperatorNode*)pNode, &pCol, &pVal)) {
|
|
return false;
|
|
}
|
|
if (!hasDbCond && 0 == strcmp(pCol->colName, "db_name")) {
|
|
hasDbCond = true;
|
|
strcpy(pInfo->table.dbname, pVal->literal);
|
|
} else if (!hasStbCond && 0 == strcmp(pCol->colName, "stable_name")) {
|
|
hasStbCond = true;
|
|
strcpy(pInfo->table.tname, pVal->literal);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
return hasDbCond;
|
|
}
|
|
|
|
static bool tbCntScanOptIsEligibleOpCond(SOperatorNode* pCond) {
|
|
SColumnNode* pCol = NULL;
|
|
SValueNode* pVal = NULL;
|
|
if (!tbCntScanOptGetColValFromCond(pCond, &pCol, &pVal)) {
|
|
return false;
|
|
}
|
|
return 0 == strcmp(pCol->colName, "db_name");
|
|
}
|
|
|
|
static bool tbCntScanOptIsEligibleConds(STbCntScanOptInfo* pInfo, SNode* pConditions) {
|
|
if (NULL == pConditions) {
|
|
return true;
|
|
}
|
|
if (LIST_LENGTH(pInfo->pAgg->pGroupKeys) != 0) {
|
|
return false;
|
|
}
|
|
if (QUERY_NODE_LOGIC_CONDITION == nodeType(pConditions)) {
|
|
return tbCntScanOptIsEligibleLogicCond(pInfo, (SLogicConditionNode*)pConditions);
|
|
}
|
|
|
|
if (QUERY_NODE_OPERATOR == nodeType(pConditions)) {
|
|
return tbCntScanOptIsEligibleOpCond((SOperatorNode*)pConditions);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool tbCntScanOptIsEligibleScan(STbCntScanOptInfo* pInfo) {
|
|
if (0 != strcmp(pInfo->pScan->tableName.dbname, TSDB_INFORMATION_SCHEMA_DB) ||
|
|
0 != strcmp(pInfo->pScan->tableName.tname, TSDB_INS_TABLE_TABLES) || NULL != pInfo->pScan->pGroupTags) {
|
|
return false;
|
|
}
|
|
if (1 == pInfo->pScan->pVgroupList->numOfVgroups && MNODE_HANDLE == pInfo->pScan->pVgroupList->vgroups[0].vgId) {
|
|
return false;
|
|
}
|
|
return tbCntScanOptIsEligibleConds(pInfo, pInfo->pScan->node.pConditions);
|
|
}
|
|
|
|
static bool tbCntScanOptShouldBeOptimized(SLogicNode* pNode, STbCntScanOptInfo* pInfo) {
|
|
if (QUERY_NODE_LOGIC_PLAN_AGG != nodeType(pNode) || 1 != LIST_LENGTH(pNode->pChildren) ||
|
|
QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(nodesListGetNode(pNode->pChildren, 0))) {
|
|
return false;
|
|
}
|
|
|
|
pInfo->pAgg = (SAggLogicNode*)pNode;
|
|
pInfo->pScan = (SScanLogicNode*)nodesListGetNode(pNode->pChildren, 0);
|
|
return tbCntScanOptIsEligibleAgg(pInfo->pAgg) && tbCntScanOptIsEligibleScan(pInfo);
|
|
}
|
|
|
|
static int32_t tbCntScanOptCreateTableCountFunc(SNode** ppNode) {
|
|
SFunctionNode* pFunc = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_FUNCTION, (SNode**)&pFunc);
|
|
if (NULL == pFunc) {
|
|
return code;
|
|
}
|
|
strcpy(pFunc->functionName, "_table_count");
|
|
strcpy(pFunc->node.aliasName, "_table_count");
|
|
code = fmGetFuncInfo(pFunc, NULL, 0);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pFunc);
|
|
return code;
|
|
}
|
|
*ppNode = (SNode*)pFunc;
|
|
return code;
|
|
}
|
|
|
|
static int32_t tbCntScanOptRewriteScan(STbCntScanOptInfo* pInfo) {
|
|
pInfo->pScan->scanType = SCAN_TYPE_TABLE_COUNT;
|
|
strcpy(pInfo->pScan->tableName.dbname, pInfo->table.dbname);
|
|
strcpy(pInfo->pScan->tableName.tname, pInfo->table.tname);
|
|
NODES_DESTORY_LIST(pInfo->pScan->node.pTargets);
|
|
NODES_DESTORY_LIST(pInfo->pScan->pScanCols);
|
|
NODES_DESTORY_NODE(pInfo->pScan->node.pConditions);
|
|
NODES_DESTORY_LIST(pInfo->pScan->pScanPseudoCols);
|
|
SNode* pNew = NULL;
|
|
int32_t code = tbCntScanOptCreateTableCountFunc(&pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pInfo->pScan->pScanPseudoCols, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = createColumnByRewriteExpr(nodesListGetNode(pInfo->pScan->pScanPseudoCols, 0), &pInfo->pScan->node.pTargets);
|
|
}
|
|
SNode* pGroupKey = NULL;
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
FOREACH(pGroupKey, pInfo->pAgg->pGroupKeys) {
|
|
SNode* pGroupCol = nodesListGetNode(((SGroupingSetNode*)pGroupKey)->pParameterList, 0);
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pGroupCol, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pInfo->pScan->pGroupTags, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pNew = NULL;
|
|
code = nodesCloneNode(pGroupCol, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pInfo->pScan->pScanCols, pNew);
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pNew = NULL;
|
|
code = nodesCloneNode(pGroupCol, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeStrictAppend(&pInfo->pScan->node.pTargets, pNew);
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t tbCntScanOptCreateSumFunc(SFunctionNode* pCntFunc, SNode* pParam, SNode** pOutput) {
|
|
SFunctionNode* pFunc = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_FUNCTION, (SNode**)&pFunc);
|
|
if (NULL == pFunc) {
|
|
return code;
|
|
}
|
|
strcpy(pFunc->functionName, "sum");
|
|
strcpy(pFunc->node.aliasName, pCntFunc->node.aliasName);
|
|
code = createColumnByRewriteExpr(pParam, &pFunc->pParameterList);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = fmGetFuncInfo(pFunc, NULL, 0);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pOutput = (SNode*)pFunc;
|
|
} else {
|
|
nodesDestroyNode((SNode*)pFunc);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t tbCntScanOptRewriteAgg(SAggLogicNode* pAgg) {
|
|
SScanLogicNode* pScan = (SScanLogicNode*)nodesListGetNode(pAgg->node.pChildren, 0);
|
|
SNode* pSum = NULL;
|
|
int32_t code = tbCntScanOptCreateSumFunc((SFunctionNode*)nodesListGetNode(pAgg->pAggFuncs, 0),
|
|
nodesListGetNode(pScan->pScanPseudoCols, 0), &pSum);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
NODES_DESTORY_LIST(pAgg->pAggFuncs);
|
|
code = nodesListMakeStrictAppend(&pAgg->pAggFuncs, pSum);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = partTagsRewriteGroupTagsToFuncs(pScan->pGroupTags, 0, pAgg);
|
|
}
|
|
NODES_DESTORY_LIST(pAgg->pGroupKeys);
|
|
return code;
|
|
}
|
|
|
|
static int32_t tableCountScanOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
STbCntScanOptInfo info = {0};
|
|
if (!optFindEligibleNode(pLogicSubplan->pNode, (FShouldBeOptimized)tbCntScanOptShouldBeOptimized, &info)) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
int32_t code = tbCntScanOptRewriteScan(&info);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = tbCntScanOptRewriteAgg(info.pAgg);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static SSortLogicNode* sortNonPriKeySatisfied(SLogicNode* pNode) {
|
|
if (QUERY_NODE_LOGIC_PLAN_SORT != nodeType(pNode)) {
|
|
return NULL;
|
|
}
|
|
SSortLogicNode* pSort = (SSortLogicNode*)pNode;
|
|
if (sortPriKeyOptIsPriKeyOrderBy(pSort->pSortKeys)) {
|
|
return NULL;
|
|
}
|
|
SNode *pSortKeyNode = NULL, *pSortKeyExpr = NULL;
|
|
FOREACH(pSortKeyNode, pSort->pSortKeys) {
|
|
pSortKeyExpr = ((SOrderByExprNode*)pSortKeyNode)->pExpr;
|
|
switch (nodeType(pSortKeyExpr)) {
|
|
case QUERY_NODE_COLUMN:
|
|
break;
|
|
case QUERY_NODE_VALUE:
|
|
continue;
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (!pSortKeyExpr || ((SColumnNode*)pSortKeyExpr)->projIdx != 1 ||
|
|
((SColumnNode*)pSortKeyExpr)->node.resType.type != TSDB_DATA_TYPE_TIMESTAMP) {
|
|
return NULL;
|
|
}
|
|
return pSort;
|
|
}
|
|
|
|
static bool sortNonPriKeyShouldOptimize(SLogicNode* pNode, void* pInfo) {
|
|
SSortLogicNode* pSort = sortNonPriKeySatisfied(pNode);
|
|
if (!pSort) return false;
|
|
SOptimizePKCtx* ctx = pInfo;
|
|
ctx->code = nodesListAppend(ctx->pList, (SNode*)pSort);
|
|
return false;
|
|
}
|
|
|
|
static int32_t sortNonPriKeyOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SNodeList* pNodeList = NULL;
|
|
int32_t code = nodesMakeList(&pNodeList);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
SOptimizePKCtx ctx = {.pList = pNodeList, .code = 0};
|
|
(void)optFindEligibleNode(pLogicSubplan->pNode, sortNonPriKeyShouldOptimize, &ctx);
|
|
if (TSDB_CODE_SUCCESS != ctx.code) {
|
|
nodesClearList(pNodeList);
|
|
return code;
|
|
}
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pNodeList) {
|
|
SSortLogicNode* pSort = (SSortLogicNode*)pNode;
|
|
SOrderByExprNode* pOrderByExpr = (SOrderByExprNode*)nodesListGetNode(pSort->pSortKeys, 0);
|
|
pSort->node.outputTsOrder = pOrderByExpr->order;
|
|
optSetParentOrder(pSort->node.pParent, pOrderByExpr->order, NULL);
|
|
}
|
|
pCxt->optimized = false;
|
|
nodesClearList(pNodeList);
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
static bool hashJoinOptShouldBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
bool res = false;
|
|
if (QUERY_NODE_LOGIC_PLAN_JOIN != nodeType(pNode)) {
|
|
return res;
|
|
}
|
|
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)pNode;
|
|
if (pJoin->joinAlgo != JOIN_ALGO_UNKNOWN) {
|
|
return res;
|
|
}
|
|
|
|
if (!pJoin->hashJoinHint) {
|
|
goto _return;
|
|
}
|
|
|
|
if ((JOIN_STYPE_NONE != pJoin->subType && JOIN_STYPE_OUTER != pJoin->subType) || JOIN_TYPE_FULL == pJoin->joinType || pNode->pChildren->length != 2 ) {
|
|
goto _return;
|
|
}
|
|
|
|
res = true;
|
|
|
|
_return:
|
|
|
|
if (!res && DATA_ORDER_LEVEL_NONE == pJoin->node.requireDataOrder) {
|
|
pJoin->node.requireDataOrder = DATA_ORDER_LEVEL_GLOBAL;
|
|
int32_t *pCode = pCtx;
|
|
int32_t code = adjustLogicNodeDataRequirement(pNode, pJoin->node.requireDataOrder);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
*pCode = code;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static int32_t hashJoinOptSplitPrimFromLogicCond(SNode **pCondition, SNode **pPrimaryKeyCond) {
|
|
SLogicConditionNode *pLogicCond = (SLogicConditionNode *)(*pCondition);
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SNodeList *pPrimaryKeyConds = NULL;
|
|
SNode *pCond = NULL;
|
|
WHERE_EACH(pCond, pLogicCond->pParameterList) {
|
|
bool result = false;
|
|
code = filterIsMultiTableColsCond(pCond, &result);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
|
|
if (result || COND_TYPE_PRIMARY_KEY != filterClassifyCondition(pCond)) {
|
|
WHERE_NEXT;
|
|
continue;
|
|
}
|
|
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(pCond, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListMakeAppend(&pPrimaryKeyConds, pNew);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
|
|
ERASE_NODE(pLogicCond->pParameterList);
|
|
}
|
|
|
|
SNode *pTempPrimaryKeyCond = NULL;
|
|
if (TSDB_CODE_SUCCESS == code && pPrimaryKeyConds) {
|
|
code = nodesMergeConds(&pTempPrimaryKeyCond, &pPrimaryKeyConds);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code && pTempPrimaryKeyCond) {
|
|
*pPrimaryKeyCond = pTempPrimaryKeyCond;
|
|
|
|
if (pLogicCond->pParameterList->length <= 0) {
|
|
nodesDestroyNode(*pCondition);
|
|
*pCondition = NULL;
|
|
}
|
|
} else {
|
|
nodesDestroyList(pPrimaryKeyConds);
|
|
nodesDestroyNode(pTempPrimaryKeyCond);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
|
|
int32_t hashJoinOptSplitPrimCond(SNode **pCondition, SNode **pPrimaryKeyCond) {
|
|
if (QUERY_NODE_LOGIC_CONDITION == nodeType(*pCondition)) {
|
|
if (LOGIC_COND_TYPE_AND == ((SLogicConditionNode *)*pCondition)->condType) {
|
|
return hashJoinOptSplitPrimFromLogicCond(pCondition, pPrimaryKeyCond);
|
|
}
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
bool needOutput = false;
|
|
bool result = false;
|
|
int32_t code = filterIsMultiTableColsCond(*pCondition, &result);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
if (result) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
EConditionType type = filterClassifyCondition(*pCondition);
|
|
if (COND_TYPE_PRIMARY_KEY == type) {
|
|
*pPrimaryKeyCond = *pCondition;
|
|
*pCondition = NULL;
|
|
}
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
|
|
static int32_t hashJoinOptRewriteJoin(SOptimizeContext* pCxt, SLogicNode* pNode, SLogicSubplan* pLogicSubplan) {
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)pNode;
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
|
|
pJoin->joinAlgo = JOIN_ALGO_HASH;
|
|
|
|
if (NULL != pJoin->pColOnCond) {
|
|
#if 0
|
|
EJoinType t = pJoin->joinType;
|
|
EJoinSubType s = pJoin->subType;
|
|
|
|
pJoin->joinType = JOIN_TYPE_INNER;
|
|
pJoin->subType = JOIN_STYPE_NONE;
|
|
|
|
code = pdcJoinSplitCond(pJoin, &pJoin->pColOnCond, NULL, &pJoin->pLeftOnCond, &pJoin->pRightOnCond, true);
|
|
|
|
pJoin->joinType = t;
|
|
pJoin->subType = s;
|
|
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
|
|
STimeWindow ltimeRange = TSWINDOW_INITIALIZER;
|
|
STimeWindow rtimeRange = TSWINDOW_INITIALIZER;
|
|
SNode* pPrimaryKeyCond = NULL;
|
|
if (NULL != pJoin->pLeftOnCond) {
|
|
hashJoinOptSplitPrimCond(&pJoin->pLeftOnCond, &pPrimaryKeyCond);
|
|
if (NULL != pPrimaryKeyCond) {
|
|
bool isStrict = false;
|
|
code = getTimeRangeFromNode(&pPrimaryKeyCond, <imeRange, &isStrict);
|
|
nodesDestroyNode(pPrimaryKeyCond);
|
|
}
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
|
|
if (NULL != pJoin->pRightOnCond) {
|
|
pPrimaryKeyCond = NULL;
|
|
hashJoinOptSplitPrimCond(&pJoin->pRightOnCond, &pPrimaryKeyCond);
|
|
if (NULL != pPrimaryKeyCond) {
|
|
bool isStrict = false;
|
|
code = getTimeRangeFromNode(&pPrimaryKeyCond, &rtimeRange, &isStrict);
|
|
nodesDestroyNode(pPrimaryKeyCond);
|
|
}
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
|
|
if (TSWINDOW_IS_EQUAL(ltimeRange, TSWINDOW_INITIALIZER)) {
|
|
pJoin->timeRange.skey = rtimeRange.skey;
|
|
pJoin->timeRange.ekey = rtimeRange.ekey;
|
|
} else if (TSWINDOW_IS_EQUAL(rtimeRange, TSWINDOW_INITIALIZER)) {
|
|
pJoin->timeRange.skey = ltimeRange.skey;
|
|
pJoin->timeRange.ekey = ltimeRange.ekey;
|
|
} else if (ltimeRange.ekey < rtimeRange.skey || ltimeRange.skey > rtimeRange.ekey) {
|
|
pJoin->timeRange = TSWINDOW_DESC_INITIALIZER;
|
|
} else {
|
|
pJoin->timeRange.skey = TMAX(ltimeRange.skey, rtimeRange.skey);
|
|
pJoin->timeRange.ekey = TMIN(ltimeRange.ekey, rtimeRange.ekey);
|
|
}
|
|
#else
|
|
SNode* pPrimaryKeyCond = NULL;
|
|
code = hashJoinOptSplitPrimCond(&pJoin->pColOnCond, &pPrimaryKeyCond);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
if (NULL != pPrimaryKeyCond) {
|
|
bool isStrict = false;
|
|
code = getTimeRangeFromNode(&pPrimaryKeyCond, &pJoin->timeRange, &isStrict);
|
|
nodesDestroyNode(pPrimaryKeyCond);
|
|
}
|
|
#endif
|
|
} else {
|
|
pJoin->timeRange = TSWINDOW_INITIALIZER;
|
|
}
|
|
|
|
#if 0
|
|
if (NULL != pJoin->pTagOnCond && !TSWINDOW_IS_EQUAL(pJoin->timeRange, TSWINDOW_DESC_INITIALIZER)) {
|
|
EJoinType t = pJoin->joinType;
|
|
EJoinSubType s = pJoin->subType;
|
|
SNode* pLeftChildCond = NULL;
|
|
SNode* pRightChildCond = NULL;
|
|
|
|
pJoin->joinType = JOIN_TYPE_INNER;
|
|
pJoin->subType = JOIN_STYPE_NONE;
|
|
|
|
code = pdcJoinSplitCond(pJoin, &pJoin->pTagOnCond, NULL, &pLeftChildCond, &pRightChildCond, true);
|
|
|
|
pJoin->joinType = t;
|
|
pJoin->subType = s;
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = mergeJoinConds(&pJoin->pLeftOnCond, &pLeftChildCond);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = mergeJoinConds(&pJoin->pRightOnCond, &pRightChildCond);
|
|
}
|
|
|
|
nodesDestroyNode(pLeftChildCond);
|
|
nodesDestroyNode(pRightChildCond);
|
|
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!TSWINDOW_IS_EQUAL(pJoin->timeRange, TSWINDOW_DESC_INITIALIZER)) {
|
|
SNode* pChild = NULL;
|
|
FOREACH(pChild, pJoin->node.pChildren) {
|
|
if (QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(pChild)) {
|
|
continue;
|
|
}
|
|
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pChild;
|
|
if (TSWINDOW_IS_EQUAL(pScan->scanRange, TSWINDOW_INITIALIZER)) {
|
|
continue;
|
|
} else if (pJoin->timeRange.ekey < pScan->scanRange.skey || pJoin->timeRange.skey > pScan->scanRange.ekey) {
|
|
pJoin->timeRange = TSWINDOW_DESC_INITIALIZER;
|
|
break;
|
|
} else {
|
|
pJoin->timeRange.skey = TMAX(pJoin->timeRange.skey, pScan->scanRange.skey);
|
|
pJoin->timeRange.ekey = TMIN(pJoin->timeRange.ekey, pScan->scanRange.ekey);
|
|
}
|
|
}
|
|
}
|
|
|
|
pJoin->timeRangeTarget = 0;
|
|
|
|
if (!TSWINDOW_IS_EQUAL(pJoin->timeRange, TSWINDOW_INITIALIZER)) {
|
|
SNode* pChild = NULL;
|
|
int32_t timeRangeTarget = 1;
|
|
FOREACH(pChild, pJoin->node.pChildren) {
|
|
if (QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(pChild)) {
|
|
timeRangeTarget++;
|
|
continue;
|
|
}
|
|
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pChild;
|
|
if (TSWINDOW_IS_EQUAL(pScan->scanRange, pJoin->timeRange)) {
|
|
timeRangeTarget++;
|
|
continue;
|
|
}
|
|
|
|
bool replaced = false;
|
|
switch (pJoin->joinType) {
|
|
case JOIN_TYPE_INNER:
|
|
pScan->scanRange.skey = pJoin->timeRange.skey;
|
|
pScan->scanRange.ekey = pJoin->timeRange.ekey;
|
|
replaced = true;
|
|
break;
|
|
case JOIN_TYPE_LEFT:
|
|
if (2 == timeRangeTarget) {
|
|
pScan->scanRange.skey = pJoin->timeRange.skey;
|
|
pScan->scanRange.ekey = pJoin->timeRange.ekey;
|
|
replaced = true;
|
|
}
|
|
break;
|
|
case JOIN_TYPE_RIGHT:
|
|
if (1 == timeRangeTarget) {
|
|
pScan->scanRange.skey = pJoin->timeRange.skey;
|
|
pScan->scanRange.ekey = pJoin->timeRange.ekey;
|
|
replaced = true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (replaced) {
|
|
timeRangeTarget++;
|
|
continue;
|
|
}
|
|
|
|
pJoin->timeRangeTarget += timeRangeTarget;
|
|
timeRangeTarget++;
|
|
}
|
|
}
|
|
|
|
pCxt->optimized = true;
|
|
OPTIMIZE_FLAG_SET_MASK(pJoin->node.optimizedFlag, OPTIMIZE_FLAG_STB_JOIN);
|
|
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
|
|
static int32_t hashJoinOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
int32_t code = 0;
|
|
SLogicNode* pNode = optFindPossibleNode(pLogicSubplan->pNode, hashJoinOptShouldBeOptimized, &code);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
if (NULL == pNode) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
return hashJoinOptRewriteJoin(pCxt, pNode, pLogicSubplan);
|
|
}
|
|
|
|
static bool stbJoinOptShouldBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (QUERY_NODE_LOGIC_PLAN_JOIN != nodeType(pNode) || OPTIMIZE_FLAG_TEST_MASK(pNode->optimizedFlag, OPTIMIZE_FLAG_STB_JOIN)) {
|
|
return false;
|
|
}
|
|
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)pNode;
|
|
if (pJoin->joinAlgo == JOIN_ALGO_UNKNOWN) {
|
|
pJoin->joinAlgo = JOIN_ALGO_MERGE;
|
|
}
|
|
|
|
if (JOIN_STYPE_NONE != pJoin->subType || pJoin->isSingleTableJoin || NULL == pJoin->pTagEqCond || pNode->pChildren->length != 2
|
|
|| pJoin->isLowLevelJoin) {
|
|
return false;
|
|
}
|
|
|
|
SNode* pLeft = nodesListGetNode(pJoin->node.pChildren, 0);
|
|
SNode* pRight = nodesListGetNode(pJoin->node.pChildren, 1);
|
|
if (QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(pLeft) || QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(pRight)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int32_t stbJoinOptAddFuncToScanNode(char* funcName, SScanLogicNode* pScan) {
|
|
SFunctionNode* pUidFunc = NULL;
|
|
int32_t code = createFunction(funcName, NULL, &pUidFunc);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
snprintf(pUidFunc->node.aliasName, sizeof(pUidFunc->node.aliasName), "%s.%p", pUidFunc->functionName, pUidFunc);
|
|
code = nodesListStrictAppend(pScan->pScanPseudoCols, (SNode*)pUidFunc);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = createColumnByRewriteExpr((SNode*)pUidFunc, &pScan->node.pTargets);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
int32_t stbJoinOptRewriteToTagScan(SLogicNode* pJoin, SNode* pNode) {
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pNode;
|
|
SJoinLogicNode* pJoinNode = (SJoinLogicNode*)pJoin;
|
|
|
|
pScan->scanType = SCAN_TYPE_TAG;
|
|
NODES_DESTORY_LIST(pScan->pScanCols);
|
|
NODES_DESTORY_NODE(pScan->node.pConditions);
|
|
pScan->node.requireDataOrder = DATA_ORDER_LEVEL_NONE;
|
|
pScan->node.resultDataOrder = DATA_ORDER_LEVEL_NONE;
|
|
|
|
SNodeList* pTags = NULL;
|
|
int32_t code = nodesMakeList(&pTags);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesCollectColumnsFromNode(pJoinNode->pTagEqCond, NULL, COLLECT_COL_TYPE_TAG, &pTags);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesCollectColumnsFromNode(pJoinNode->pTagOnCond, NULL, COLLECT_COL_TYPE_TAG, &pTags);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
SNode* pTarget = NULL;
|
|
SNode* pTag = NULL;
|
|
bool found = false;
|
|
WHERE_EACH(pTarget, pScan->node.pTargets) {
|
|
found = false;
|
|
SColumnNode* pTargetCol = (SColumnNode*)pTarget;
|
|
FOREACH(pTag, pTags) {
|
|
SColumnNode* pTagCol = (SColumnNode*)pTag;
|
|
if (0 == strcasecmp(pTargetCol->node.aliasName, pTagCol->colName) && 0 == strcasecmp(pTargetCol->tableAlias, pTagCol->tableAlias)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
ERASE_NODE(pScan->node.pTargets);
|
|
} else {
|
|
WHERE_NEXT;
|
|
}
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = stbJoinOptAddFuncToScanNode("_tbuid", pScan);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = stbJoinOptAddFuncToScanNode("_vgid", pScan);
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = tagScanSetExecutionMode(pScan);
|
|
}
|
|
|
|
if (code) {
|
|
nodesDestroyList(pTags);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t stbJoinOptCreateTagScanNode(SLogicNode* pJoin, SNodeList** ppList) {
|
|
SNodeList* pList = NULL;
|
|
int32_t code = nodesCloneList(pJoin->pChildren, &pList);
|
|
if (NULL == pList) {
|
|
return code;
|
|
}
|
|
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pList) {
|
|
code = stbJoinOptRewriteToTagScan(pJoin, pNode);
|
|
if (code) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*ppList = pList;
|
|
} else {
|
|
nodesDestroyList(pList);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t stbJoinOptCreateTagHashJoinNode(SLogicNode* pOrig, SNodeList* pChildren, SLogicNode** ppLogic) {
|
|
SJoinLogicNode* pOrigJoin = (SJoinLogicNode*)pOrig;
|
|
SJoinLogicNode* pJoin = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_LOGIC_PLAN_JOIN, (SNode**)&pJoin);
|
|
if (NULL == pJoin) {
|
|
return code;
|
|
}
|
|
|
|
pJoin->joinType = pOrigJoin->joinType;
|
|
pJoin->subType = pOrigJoin->subType;
|
|
pJoin->joinAlgo = JOIN_ALGO_HASH;
|
|
pJoin->isSingleTableJoin = pOrigJoin->isSingleTableJoin;
|
|
pJoin->hasSubQuery = pOrigJoin->hasSubQuery;
|
|
pJoin->node.inputTsOrder = pOrigJoin->node.inputTsOrder;
|
|
pJoin->node.groupAction = pOrigJoin->node.groupAction;
|
|
pJoin->node.requireDataOrder = DATA_ORDER_LEVEL_NONE;
|
|
pJoin->node.resultDataOrder = DATA_ORDER_LEVEL_NONE;
|
|
code = nodesCloneNode(pOrigJoin->pTagEqCond, &pJoin->pTagEqCond);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pJoin);
|
|
return code;
|
|
}
|
|
code = nodesCloneNode(pOrigJoin->pTagOnCond, &pJoin->pTagOnCond);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pJoin);
|
|
return code;
|
|
}
|
|
|
|
pJoin->node.pChildren = pChildren;
|
|
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pChildren) {
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pNode;
|
|
SNode* pCol = NULL;
|
|
FOREACH(pCol, pScan->pScanPseudoCols) {
|
|
if (QUERY_NODE_FUNCTION == nodeType(pCol) && (((SFunctionNode*)pCol)->funcType == FUNCTION_TYPE_TBUID ||
|
|
((SFunctionNode*)pCol)->funcType == FUNCTION_TYPE_VGID)) {
|
|
code = createColumnByRewriteExpr(pCol, &pJoin->node.pTargets);
|
|
if (code) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (code) {
|
|
break;
|
|
}
|
|
pScan->node.pParent = (SLogicNode*)pJoin;
|
|
}
|
|
|
|
SNodeList* pCols = NULL;
|
|
code = nodesCollectColumnsFromNode(pJoin->pFullOnCond, NULL, COLLECT_COL_TYPE_ALL, &pCols);
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
FOREACH(pNode, pCols) {
|
|
code = createColumnByRewriteExpr(pNode, &pJoin->node.pTargets);
|
|
if (code) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
nodesDestroyList(pCols);
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*ppLogic = (SLogicNode*)pJoin;
|
|
OPTIMIZE_FLAG_SET_MASK(pJoin->node.optimizedFlag, OPTIMIZE_FLAG_STB_JOIN);
|
|
} else {
|
|
nodesDestroyNode((SNode*)pJoin);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t stbJoinOptCreateTableScanNodes(SLogicNode* pJoin, SNodeList** ppList, bool* srcScan) {
|
|
SNodeList* pList = NULL;
|
|
int32_t code = nodesCloneList(pJoin->pChildren, &pList);
|
|
if (NULL == pList) {
|
|
return code;
|
|
}
|
|
|
|
int32_t i = 0;
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pList) {
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pNode;
|
|
//code = stbJoinOptAddFuncToScanNode("_tbuid", pScan);
|
|
//if (code) {
|
|
// break;
|
|
//}
|
|
|
|
nodesDestroyNode(pScan->pTagCond);
|
|
pScan->pTagCond = NULL;
|
|
nodesDestroyNode(pScan->pTagIndexCond);
|
|
pScan->pTagIndexCond = NULL;
|
|
|
|
pScan->node.dynamicOp = true;
|
|
*(srcScan + i++) = pScan->pVgroupList->numOfVgroups <= 1;
|
|
|
|
pScan->scanType = SCAN_TYPE_TABLE;
|
|
}
|
|
|
|
*ppList = pList;
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t stbJoinOptCreateGroupCacheNode(SLogicNode* pRoot, SNodeList* pChildren, SLogicNode** ppLogic) {
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SGroupCacheLogicNode* pGrpCache = NULL;
|
|
code = nodesMakeNode(QUERY_NODE_LOGIC_PLAN_GROUP_CACHE, (SNode**)&pGrpCache);
|
|
if (NULL == pGrpCache) {
|
|
return code;
|
|
}
|
|
|
|
// pGrpCache->node.dynamicOp = true;
|
|
pGrpCache->grpColsMayBeNull = false;
|
|
pGrpCache->grpByUid = true;
|
|
pGrpCache->batchFetch = getBatchScanOptionFromHint(pRoot->pHint);
|
|
pGrpCache->node.pChildren = pChildren;
|
|
pGrpCache->node.pTargets = NULL;
|
|
code = nodesMakeList(&pGrpCache->node.pTargets);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
SScanLogicNode* pScan = (SScanLogicNode*)nodesListGetNode(pChildren, 0);
|
|
SNodeList* pNewList = NULL;
|
|
code = nodesCloneList(pScan->node.pTargets, &pNewList);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppendList(pGrpCache->node.pTargets, pNewList);
|
|
}
|
|
}
|
|
|
|
SScanLogicNode* pScan = (SScanLogicNode*)nodesListGetNode(pChildren, 0);
|
|
SNode* pCol = NULL;
|
|
FOREACH(pCol, pScan->pScanPseudoCols) {
|
|
if (QUERY_NODE_FUNCTION == nodeType(pCol) && (((SFunctionNode*)pCol)->funcType == FUNCTION_TYPE_TBUID ||
|
|
((SFunctionNode*)pCol)->funcType == FUNCTION_TYPE_VGID)) {
|
|
code = createColumnByRewriteExpr(pCol, &pGrpCache->pGroupCols);
|
|
if (code) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool hasCond = false;
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pChildren) {
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pNode;
|
|
if (pScan->node.pConditions) {
|
|
hasCond = true;
|
|
}
|
|
pScan->node.pParent = (SLogicNode*)pGrpCache;
|
|
}
|
|
pGrpCache->globalGrp = false;
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*ppLogic = (SLogicNode*)pGrpCache;
|
|
} else {
|
|
nodesDestroyNode((SNode*)pGrpCache);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static void stbJoinOptRemoveTagEqCond(SJoinLogicNode* pJoin) {
|
|
if (QUERY_NODE_OPERATOR == nodeType(pJoin->pFullOnCond) && nodesEqualNode(pJoin->pFullOnCond, pJoin->pTagEqCond)) {
|
|
NODES_DESTORY_NODE(pJoin->pFullOnCond);
|
|
return;
|
|
}
|
|
if (QUERY_NODE_LOGIC_CONDITION == nodeType(pJoin->pFullOnCond)) {
|
|
SLogicConditionNode* pLogic = (SLogicConditionNode*)pJoin->pFullOnCond;
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pLogic->pParameterList) {
|
|
if (nodesEqualNode(pNode, pJoin->pTagEqCond)) {
|
|
ERASE_NODE(pLogic->pParameterList);
|
|
break;
|
|
} else if (QUERY_NODE_LOGIC_CONDITION == nodeType(pJoin->pTagEqCond)) {
|
|
SLogicConditionNode* pTags = (SLogicConditionNode*)pJoin->pTagEqCond;
|
|
SNode* pTag = NULL;
|
|
bool found = false;
|
|
FOREACH(pTag, pTags->pParameterList) {
|
|
if (nodesEqualNode(pTag, pNode)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
ERASE_NODE(pLogic->pParameterList);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pLogic->pParameterList->length <= 0) {
|
|
NODES_DESTORY_NODE(pJoin->pFullOnCond);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int32_t stbJoinOptCreateMergeJoinNode(SLogicNode* pOrig, SLogicNode* pChild, SLogicNode** ppLogic) {
|
|
SJoinLogicNode* pOrigJoin = (SJoinLogicNode*)pOrig;
|
|
SJoinLogicNode* pJoin = NULL;
|
|
int32_t code = nodesCloneNode((SNode*)pOrig, (SNode**)&pJoin);
|
|
if (NULL == pJoin) {
|
|
return code;
|
|
}
|
|
|
|
pJoin->joinAlgo = JOIN_ALGO_MERGE;
|
|
// pJoin->node.dynamicOp = true;
|
|
|
|
stbJoinOptRemoveTagEqCond(pJoin);
|
|
NODES_DESTORY_NODE(pJoin->pTagEqCond);
|
|
|
|
SNode* pNode = NULL;
|
|
FOREACH(pNode, pJoin->node.pChildren) { ERASE_NODE(pJoin->node.pChildren); }
|
|
code = nodesListStrictAppend(pJoin->node.pChildren, (SNode*)pChild);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pChild->pParent = (SLogicNode*)pJoin;
|
|
*ppLogic = (SLogicNode*)pJoin;
|
|
OPTIMIZE_FLAG_SET_MASK(pJoin->node.optimizedFlag, OPTIMIZE_FLAG_STB_JOIN);
|
|
} else {
|
|
nodesDestroyNode((SNode*)pJoin);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t stbJoinOptCreateDynQueryCtrlNode(SLogicNode* pRoot, SLogicNode* pPrev, SLogicNode* pPost, bool* srcScan,
|
|
SLogicNode** ppDynNode) {
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SDynQueryCtrlLogicNode* pDynCtrl = NULL;
|
|
code = nodesMakeNode(QUERY_NODE_LOGIC_PLAN_DYN_QUERY_CTRL, (SNode**)&pDynCtrl);
|
|
if (NULL == pDynCtrl) {
|
|
return code;
|
|
}
|
|
|
|
pDynCtrl->qType = DYN_QTYPE_STB_HASH;
|
|
pDynCtrl->stbJoin.batchFetch = getBatchScanOptionFromHint(pRoot->pHint);
|
|
memcpy(pDynCtrl->stbJoin.srcScan, srcScan, sizeof(pDynCtrl->stbJoin.srcScan));
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pDynCtrl->node.pChildren = NULL;
|
|
code = nodesMakeList(&pDynCtrl->node.pChildren);
|
|
if (NULL == pDynCtrl->node.pChildren) {
|
|
code = code;
|
|
}
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pDynCtrl->stbJoin.pVgList = NULL;
|
|
code = nodesMakeList(&pDynCtrl->stbJoin.pVgList);
|
|
if (NULL == pDynCtrl->stbJoin.pVgList) {
|
|
code = code;
|
|
}
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pDynCtrl->stbJoin.pUidList = NULL;
|
|
code = nodesMakeList(&pDynCtrl->stbJoin.pUidList);
|
|
if (NULL == pDynCtrl->stbJoin.pUidList) {
|
|
code = code;
|
|
}
|
|
}
|
|
|
|
SJoinLogicNode* pHJoin = (SJoinLogicNode*)pPrev;
|
|
code = nodesListStrictAppend(pDynCtrl->stbJoin.pUidList, nodesListGetNode(pHJoin->node.pTargets, 0));
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppend(pDynCtrl->stbJoin.pUidList, nodesListGetNode(pHJoin->node.pTargets, 2));
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppend(pDynCtrl->stbJoin.pVgList, nodesListGetNode(pHJoin->node.pTargets, 1));
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppend(pDynCtrl->stbJoin.pVgList, nodesListGetNode(pHJoin->node.pTargets, 3));
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppend(pDynCtrl->node.pChildren, (SNode*)pPrev);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppend(pDynCtrl->node.pChildren, (SNode*)pPost);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pDynCtrl->node.pTargets = NULL;
|
|
code = nodesCloneList(pPost->pTargets, &pDynCtrl->node.pTargets);
|
|
if (!pDynCtrl->node.pTargets) {
|
|
code = code;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pPrev->pParent = (SLogicNode*)pDynCtrl;
|
|
pPost->pParent = (SLogicNode*)pDynCtrl;
|
|
|
|
*ppDynNode = (SLogicNode*)pDynCtrl;
|
|
} else {
|
|
nodesDestroyNode((SNode*)pDynCtrl);
|
|
*ppDynNode = NULL;
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t stbJoinOptRewriteStableJoin(SOptimizeContext* pCxt, SLogicNode* pJoin, SLogicSubplan* pLogicSubplan) {
|
|
SNodeList* pTagScanNodes = NULL;
|
|
SNodeList* pTbScanNodes = NULL;
|
|
SLogicNode* pGrpCacheNode = NULL;
|
|
SLogicNode* pHJoinNode = NULL;
|
|
SLogicNode* pMJoinNode = NULL;
|
|
SLogicNode* pDynNode = NULL;
|
|
bool srcScan[2] = {0};
|
|
|
|
int32_t code = stbJoinOptCreateTagScanNode(pJoin, &pTagScanNodes);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = stbJoinOptCreateTagHashJoinNode(pJoin, pTagScanNodes, &pHJoinNode);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = stbJoinOptCreateTableScanNodes(pJoin, &pTbScanNodes, srcScan);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = stbJoinOptCreateGroupCacheNode(getLogicNodeRootNode(pJoin), pTbScanNodes, &pGrpCacheNode);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = stbJoinOptCreateMergeJoinNode(pJoin, pGrpCacheNode, &pMJoinNode);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = stbJoinOptCreateDynQueryCtrlNode(getLogicNodeRootNode(pJoin), pHJoinNode, pMJoinNode, srcScan, &pDynNode);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = replaceLogicNode(pLogicSubplan, pJoin, (SLogicNode*)pDynNode);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
nodesDestroyNode((SNode*)pJoin);
|
|
pCxt->optimized = true;
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t stableJoinOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SLogicNode* pNode = optFindPossibleNode(pLogicSubplan->pNode, stbJoinOptShouldBeOptimized, NULL);
|
|
if (NULL == pNode) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
return stbJoinOptRewriteStableJoin(pCxt, pNode, pLogicSubplan);
|
|
}
|
|
|
|
static bool grpJoinOptShouldBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (QUERY_NODE_LOGIC_PLAN_JOIN != nodeType(pNode)) {
|
|
return false;
|
|
}
|
|
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)pNode;
|
|
if (JOIN_STYPE_ASOF != pJoin->subType && JOIN_STYPE_WIN != pJoin->subType) {
|
|
return false;
|
|
}
|
|
|
|
if (NULL == pJoin->pLeftEqNodes || pJoin->grpJoin) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int32_t grpJoinOptCreatePartitionNode(SLogicNode* pParent, SLogicNode* pChild, bool leftChild, SLogicNode** pNew) {
|
|
SPartitionLogicNode* pPartition = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_LOGIC_PLAN_PARTITION, (SNode**)&pPartition);
|
|
if (NULL == pPartition) {
|
|
return code;
|
|
}
|
|
|
|
pPartition->node.groupAction = GROUP_ACTION_SET;
|
|
pPartition->node.requireDataOrder = DATA_ORDER_LEVEL_GLOBAL;
|
|
pPartition->node.resultDataOrder = DATA_ORDER_LEVEL_IN_GROUP;
|
|
|
|
pPartition->node.pTargets = NULL;
|
|
code = nodesCloneList(pChild->pTargets, &pPartition->node.pTargets);
|
|
if (NULL == pPartition->node.pTargets) {
|
|
nodesDestroyNode((SNode*)pPartition);
|
|
return code;
|
|
}
|
|
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)pParent;
|
|
pPartition->pPartitionKeys = NULL;
|
|
code = nodesCloneList(leftChild ? pJoin->pLeftEqNodes : pJoin->pRightEqNodes, &pPartition->pPartitionKeys);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pPartition);
|
|
return code;
|
|
}
|
|
code = nodesListMakeStrictAppend(&pPartition->node.pChildren, (SNode *)pChild);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
*pNew = (SLogicNode*)pPartition;
|
|
pChild->pParent = (SLogicNode*)pPartition;
|
|
pPartition->node.pParent = pParent;
|
|
} else {
|
|
nodesDestroyNode((SNode*)pPartition);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t grpJoinOptInsertPartitionNode(SLogicNode* pJoin) {
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SNode* pNode = NULL;
|
|
SNode* pNew = NULL;
|
|
bool leftChild = true;
|
|
FOREACH(pNode, pJoin->pChildren) {
|
|
code = grpJoinOptCreatePartitionNode(pJoin, (SLogicNode*)pNode, leftChild, (SLogicNode**)&pNew);
|
|
if (code) {
|
|
break;
|
|
}
|
|
REPLACE_NODE(pNew);
|
|
leftChild = false;
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t grpJoinOptPartByTags(SLogicNode* pNode) {
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SNode* pChild = NULL;
|
|
SNode* pNew = NULL;
|
|
bool leftChild = true;
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)pNode;
|
|
FOREACH(pChild, pNode->pChildren) {
|
|
if (QUERY_NODE_LOGIC_PLAN_SCAN != nodeType(pChild)) {
|
|
return TSDB_CODE_PLAN_INTERNAL_ERROR;
|
|
}
|
|
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pChild;
|
|
SNodeList* pNewList = NULL;
|
|
code = nodesCloneList(pJoin->pLeftEqNodes, &pNewList);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
if (leftChild) {
|
|
code = nodesListMakeStrictAppendList(&pScan->pGroupTags, pNewList);
|
|
leftChild = false;
|
|
} else {
|
|
code = nodesListMakeStrictAppendList(&pScan->pGroupTags, pNewList);
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
|
|
pScan->groupSort = true;
|
|
pScan->groupOrderScan = true;
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t grpJoinOptRewriteGroupJoin(SOptimizeContext* pCxt, SLogicNode* pNode, SLogicSubplan* pLogicSubplan) {
|
|
SJoinLogicNode* pJoin = (SJoinLogicNode*)pNode;
|
|
int32_t code = (pJoin->allEqTags && !pJoin->hasSubQuery && !pJoin->batchScanHint) ? grpJoinOptPartByTags(pNode) : grpJoinOptInsertPartitionNode(pNode);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pJoin->grpJoin = true;
|
|
pCxt->optimized = true;
|
|
}
|
|
return code;
|
|
}
|
|
|
|
|
|
static int32_t groupJoinOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SLogicNode* pNode = optFindPossibleNode(pLogicSubplan->pNode, grpJoinOptShouldBeOptimized, NULL);
|
|
if (NULL == pNode) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
return grpJoinOptRewriteGroupJoin(pCxt, pNode, pLogicSubplan);
|
|
}
|
|
|
|
static bool partColOptShouldBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (QUERY_NODE_LOGIC_PLAN_PARTITION == nodeType(pNode)) {
|
|
SPartitionLogicNode* pPartition = (SPartitionLogicNode*)pNode;
|
|
if (keysHasCol(pPartition->pPartitionKeys)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int32_t partColOptCreateSort(SPartitionLogicNode* pPartition, SSortLogicNode** ppSort) {
|
|
SNode* node;
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SSortLogicNode* pSort = NULL;
|
|
code = nodesMakeNode(QUERY_NODE_LOGIC_PLAN_SORT, (SNode**)&pSort);
|
|
if (pSort) {
|
|
bool alreadyPartByPKTs = false;
|
|
pSort->groupSort = false;
|
|
FOREACH(node, pPartition->pPartitionKeys) {
|
|
SOrderByExprNode* pOrder = NULL;
|
|
code = nodesMakeNode(QUERY_NODE_ORDER_BY_EXPR, (SNode**)&pOrder);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
if (QUERY_NODE_COLUMN == nodeType(node) && ((SColumnNode*)node)->colId == pPartition->pkTsColId &&
|
|
((SColumnNode*)node)->tableId == pPartition->pkTsColTbId)
|
|
alreadyPartByPKTs = true;
|
|
code = nodesListMakeStrictAppend(&pSort->pSortKeys, (SNode*)pOrder);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pOrder->order = ORDER_ASC;
|
|
pOrder->pExpr = NULL;
|
|
pOrder->nullOrder = NULL_ORDER_FIRST;
|
|
code = nodesCloneNode(node, &pOrder->pExpr);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
break;
|
|
}
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode((SNode*)pSort);
|
|
return code;
|
|
}
|
|
|
|
if (pPartition->needBlockOutputTsOrder && !alreadyPartByPKTs) {
|
|
SOrderByExprNode* pOrder = NULL;
|
|
code = nodesMakeNode(QUERY_NODE_ORDER_BY_EXPR, (SNode**)&pOrder);
|
|
if (pOrder) {
|
|
pSort->excludePkCol = true;
|
|
code = nodesListMakeStrictAppend(&pSort->pSortKeys, (SNode*)pOrder);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pOrder->order = ORDER_ASC;
|
|
pOrder->pExpr = 0;
|
|
FOREACH(node, pPartition->node.pTargets) {
|
|
if (nodeType(node) == QUERY_NODE_COLUMN) {
|
|
SColumnNode* pCol = (SColumnNode*)node;
|
|
if (pCol->colId == pPartition->pkTsColId && pCol->tableId == pPartition->pkTsColTbId) {
|
|
pOrder->pExpr = NULL;
|
|
code = nodesCloneNode((SNode*)pCol, &pOrder->pExpr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (code != TSDB_CODE_SUCCESS) {
|
|
nodesDestroyNode((SNode*)pSort);
|
|
pSort = NULL;
|
|
} else {
|
|
*ppSort = pSort;
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t partitionColsOpt(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SNode* node;
|
|
int32_t code = TSDB_CODE_SUCCESS;
|
|
SPartitionLogicNode* pNode =
|
|
(SPartitionLogicNode*)optFindPossibleNode(pLogicSubplan->pNode, partColOptShouldBeOptimized, NULL);
|
|
if (NULL == pNode) return TSDB_CODE_SUCCESS;
|
|
SLogicNode* pRootNode = getLogicNodeRootNode((SLogicNode*)pNode);
|
|
|
|
if (pRootNode->pHint && getSortForGroupOptHint(pRootNode->pHint)) {
|
|
// replace with sort node
|
|
SSortLogicNode* pSort = NULL;
|
|
code = partColOptCreateSort(pNode, &pSort);
|
|
if (!pSort) {
|
|
// if sort create failed, we eat the error, skip the optimization
|
|
code = TSDB_CODE_SUCCESS;
|
|
} else {
|
|
TSWAP(pSort->node.pChildren, pNode->node.pChildren);
|
|
TSWAP(pSort->node.pTargets, pNode->node.pTargets);
|
|
optResetParent((SLogicNode*)pSort);
|
|
pSort->calcGroupId = true;
|
|
code = replaceLogicNode(pLogicSubplan, (SLogicNode*)pNode, (SLogicNode*)pSort);
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
nodesDestroyNode((SNode*)pNode);
|
|
pCxt->optimized = true;
|
|
} else {
|
|
nodesDestroyNode((SNode*)pSort);
|
|
}
|
|
}
|
|
return code;
|
|
} else if (pNode->node.pParent && nodeType(pNode->node.pParent) == QUERY_NODE_LOGIC_PLAN_AGG &&
|
|
!getOptHint(pRootNode->pHint, HINT_PARTITION_FIRST)) {
|
|
// Check if we can delete partition node
|
|
SAggLogicNode* pAgg = (SAggLogicNode*)pNode->node.pParent;
|
|
FOREACH(node, pNode->pPartitionKeys) {
|
|
SGroupingSetNode* pgsNode = NULL;
|
|
code = nodesMakeNode(QUERY_NODE_GROUPING_SET, (SNode**)&pgsNode);
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
pgsNode->groupingSetType = GP_TYPE_NORMAL;
|
|
pgsNode->pParameterList = NULL;
|
|
code = nodesMakeList(&pgsNode->pParameterList);
|
|
}
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode(node, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListStrictAppend(pgsNode->pParameterList, pNew);
|
|
}
|
|
}
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
// Now we are using hash agg
|
|
code = nodesListMakeAppend(&pAgg->pGroupKeys, (SNode*)pgsNode);
|
|
}
|
|
if (code != TSDB_CODE_SUCCESS) {
|
|
nodesDestroyNode((SNode*)pgsNode);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
code =
|
|
replaceLogicNode(pLogicSubplan, (SLogicNode*)pNode, (SLogicNode*)nodesListGetNode(pNode->node.pChildren, 0));
|
|
NODES_CLEAR_LIST(pNode->node.pChildren);
|
|
}
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
// For hash agg, nonblocking mode is meaningless, slimit is useless, so we reset it
|
|
pAgg->node.forceCreateNonBlockingOptr = false;
|
|
nodesDestroyNode(pAgg->node.pSlimit);
|
|
pAgg->node.pSlimit = NULL;
|
|
nodesDestroyNode((SNode*)pNode);
|
|
pCxt->optimized = true;
|
|
}
|
|
return code;
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static bool tsmaOptMayBeOptimized(SLogicNode* pNode, void* pCtx) {
|
|
if (QUERY_NODE_LOGIC_PLAN_SCAN == nodeType(pNode)) {
|
|
SNode* pTmpNode;
|
|
SNodeList* pFuncs = NULL;
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pNode;
|
|
SLogicNode* pParent = pScan->node.pParent;
|
|
SNode* pConds = pScan->node.pConditions;
|
|
|
|
if (pScan->scanType != SCAN_TYPE_TABLE || !pParent || pConds) return false;
|
|
if (!pScan->pTsmas || pScan->pTsmas->size <= 0) {
|
|
return false;
|
|
}
|
|
|
|
switch (nodeType(pParent)) {
|
|
case QUERY_NODE_LOGIC_PLAN_WINDOW: {
|
|
SWindowLogicNode* pWindow = (SWindowLogicNode*)pParent;
|
|
// only time window interval supported
|
|
if (pWindow->winType != WINDOW_TYPE_INTERVAL) return false;
|
|
pFuncs = pWindow->pFuncs;
|
|
} break;
|
|
case QUERY_NODE_LOGIC_PLAN_AGG: {
|
|
SAggLogicNode* pAgg = (SAggLogicNode*)pParent;
|
|
// group/partition by normal cols not supported
|
|
if (pAgg->pGroupKeys) return false;
|
|
pFuncs = pAgg->pAggFuncs;
|
|
} break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
assert(pFuncs);
|
|
FOREACH(pTmpNode, pFuncs) {
|
|
SFunctionNode* pFunc = (SFunctionNode*)pTmpNode;
|
|
if (!fmIsTSMASupportedFunc(pFunc->funcId) && !fmIsPseudoColumnFunc(pFunc->funcId) &&
|
|
!fmIsGroupKeyFunc(pFunc->funcId)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
typedef struct STSMAOptUsefulTsma {
|
|
const STableTSMAInfo* pTsma; // NULL if no tsma available, which will use original data for calculation
|
|
STimeWindow scanRange; // scan time range for this tsma
|
|
SArray* pTsmaScanCols; // SArray<int32_t> index of tsmaFuncs array
|
|
char targetTbName[TSDB_TABLE_NAME_LEN]; // the scanning table name, used only when pTsma is not NULL
|
|
uint64_t targetTbUid; // the scanning table uid, used only when pTsma is not NULL
|
|
int8_t precision;
|
|
} STSMAOptUsefulTsma;
|
|
|
|
typedef struct STSMAOptCtx {
|
|
// input
|
|
SScanLogicNode* pScan;
|
|
SLogicNode* pParent; // parent of Table Scan, Agg or Interval
|
|
const SNodeList* pAggFuncs;
|
|
const STimeWindow* pTimeRange;
|
|
const SArray* pTsmas;
|
|
SInterval* queryInterval; // not null with window logic node
|
|
int8_t precision;
|
|
|
|
// output
|
|
SArray* pUsefulTsmas; // SArray<STSMAOptUseFulTsma>, sorted by tsma interval from long to short
|
|
SArray* pUsedTsmas;
|
|
SLogicSubplan* generatedSubPlans[2];
|
|
SNodeList** ppParentTsmaSubplans;
|
|
} STSMAOptCtx;
|
|
|
|
static int32_t fillTSMAOptCtx(STSMAOptCtx* pTsmaOptCtx, SScanLogicNode* pScan) {
|
|
int32_t code = 0;
|
|
pTsmaOptCtx->pScan = pScan;
|
|
pTsmaOptCtx->pParent = pScan->node.pParent;
|
|
pTsmaOptCtx->pTsmas = pScan->pTsmas;
|
|
pTsmaOptCtx->pTimeRange = &pScan->scanRange;
|
|
pTsmaOptCtx->precision = pScan->node.precision;
|
|
|
|
if (nodeType(pTsmaOptCtx->pParent) == QUERY_NODE_LOGIC_PLAN_WINDOW) {
|
|
pTsmaOptCtx->queryInterval = taosMemoryCalloc(1, sizeof(SInterval));
|
|
if (!pTsmaOptCtx->queryInterval) return TSDB_CODE_OUT_OF_MEMORY;
|
|
|
|
SWindowLogicNode* pWindow = (SWindowLogicNode*)pTsmaOptCtx->pParent;
|
|
pTsmaOptCtx->queryInterval->interval = pWindow->interval;
|
|
pTsmaOptCtx->queryInterval->intervalUnit = pWindow->intervalUnit;
|
|
pTsmaOptCtx->queryInterval->offset = pWindow->offset;
|
|
pTsmaOptCtx->queryInterval->offsetUnit = pWindow->intervalUnit;
|
|
pTsmaOptCtx->queryInterval->sliding = pWindow->sliding;
|
|
pTsmaOptCtx->queryInterval->slidingUnit = pWindow->slidingUnit;
|
|
pTsmaOptCtx->queryInterval->precision = pWindow->node.precision;
|
|
pTsmaOptCtx->queryInterval->tz = tsTimezone;
|
|
pTsmaOptCtx->pAggFuncs = pWindow->pFuncs;
|
|
pTsmaOptCtx->ppParentTsmaSubplans = &pWindow->pTsmaSubplans;
|
|
} else {
|
|
SAggLogicNode* pAgg = (SAggLogicNode*)pTsmaOptCtx->pParent;
|
|
pTsmaOptCtx->pAggFuncs = pAgg->pAggFuncs;
|
|
pTsmaOptCtx->ppParentTsmaSubplans = &pAgg->pTsmaSubplans;
|
|
}
|
|
pTsmaOptCtx->pUsefulTsmas = taosArrayInit(pScan->pTsmas->size, sizeof(STSMAOptUsefulTsma));
|
|
pTsmaOptCtx->pUsedTsmas = taosArrayInit(3, sizeof(STSMAOptUsefulTsma));
|
|
if (!pTsmaOptCtx->pUsefulTsmas || !pTsmaOptCtx->pUsedTsmas) {
|
|
code = TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static void tsmaOptFreeUsefulTsma(void* p) {
|
|
STSMAOptUsefulTsma* pTsma = p;
|
|
taosArrayDestroy(pTsma->pTsmaScanCols);
|
|
}
|
|
|
|
static void clearTSMAOptCtx(STSMAOptCtx* pTsmaOptCtx) {
|
|
taosArrayDestroyEx(pTsmaOptCtx->pUsefulTsmas, tsmaOptFreeUsefulTsma);
|
|
taosArrayDestroy(pTsmaOptCtx->pUsedTsmas);
|
|
taosMemoryFreeClear(pTsmaOptCtx->queryInterval);
|
|
}
|
|
|
|
static bool tsmaOptCheckValidInterval(int64_t tsmaInterval, int8_t unit, const STSMAOptCtx* pTsmaOptCtx) {
|
|
if (!pTsmaOptCtx->queryInterval) return true;
|
|
|
|
bool validInterval = checkRecursiveTsmaInterval(tsmaInterval, unit, pTsmaOptCtx->queryInterval->interval,
|
|
pTsmaOptCtx->queryInterval->intervalUnit,
|
|
pTsmaOptCtx->queryInterval->precision, false);
|
|
bool validSliding =
|
|
checkRecursiveTsmaInterval(tsmaInterval, unit, pTsmaOptCtx->queryInterval->sliding,
|
|
pTsmaOptCtx->queryInterval->slidingUnit, pTsmaOptCtx->queryInterval->precision, false);
|
|
bool validOffset =
|
|
pTsmaOptCtx->queryInterval->offset == 0 ||
|
|
checkRecursiveTsmaInterval(tsmaInterval, unit, pTsmaOptCtx->queryInterval->offset,
|
|
pTsmaOptCtx->queryInterval->offsetUnit, pTsmaOptCtx->queryInterval->precision, false);
|
|
return validInterval && validSliding && validOffset;
|
|
}
|
|
|
|
static int32_t tsmaOptCheckValidFuncs(const SArray* pTsmaFuncs, const SNodeList* pQueryFuncs, SArray* pTsmaScanCols,
|
|
bool* pIsValid) {
|
|
SNode* pNode;
|
|
bool failed = false, found = false;
|
|
|
|
taosArrayClear(pTsmaScanCols);
|
|
FOREACH(pNode, pQueryFuncs) {
|
|
SFunctionNode* pQueryFunc = (SFunctionNode*)pNode;
|
|
if (fmIsPseudoColumnFunc(pQueryFunc->funcId) || fmIsGroupKeyFunc(pQueryFunc->funcId)) continue;
|
|
if (nodeType(pQueryFunc->pParameterList->pHead->pNode) != QUERY_NODE_COLUMN) {
|
|
failed = true;
|
|
break;
|
|
}
|
|
int32_t queryColId = ((SColumnNode*)pQueryFunc->pParameterList->pHead->pNode)->colId;
|
|
found = false;
|
|
int32_t notMyStateFuncId = -1;
|
|
// iterate funcs
|
|
for (int32_t i = 0; i < pTsmaFuncs->size; i++) {
|
|
STableTSMAFuncInfo* pTsmaFuncInfo = taosArrayGet(pTsmaFuncs, i);
|
|
if (pTsmaFuncInfo->funcId == notMyStateFuncId) continue;
|
|
|
|
if (!fmIsMyStateFunc(pQueryFunc->funcId, pTsmaFuncInfo->funcId)) {
|
|
notMyStateFuncId = pTsmaFuncInfo->funcId;
|
|
continue;
|
|
}
|
|
|
|
if (queryColId != pTsmaFuncInfo->colId) {
|
|
continue;
|
|
}
|
|
found = true;
|
|
if (NULL == taosArrayPush(pTsmaScanCols, &i)) {
|
|
return TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
break;
|
|
}
|
|
if (failed || !found) {
|
|
break;
|
|
}
|
|
}
|
|
*pIsValid = found;
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
|
|
typedef struct STsmaOptTagCheckCtx {
|
|
const STableTSMAInfo* pTsma;
|
|
bool ok;
|
|
} STsmaOptTagCheckCtx;
|
|
|
|
static EDealRes tsmaOptTagCheck(SNode* pNode, void* pContext) {
|
|
bool found = false;
|
|
if (nodeType(pNode) == QUERY_NODE_COLUMN) {
|
|
SColumnNode* pCol = (SColumnNode*)pNode;
|
|
if (pCol->colType == COLUMN_TYPE_TAG) {
|
|
STsmaOptTagCheckCtx* pCtx = pContext;
|
|
for (int32_t i = 0; i < pCtx->pTsma->pTags->size; ++i) {
|
|
SSchema* pSchema = taosArrayGet(pCtx->pTsma->pTags, i);
|
|
if (strcmp(pSchema->name, pCol->colName) == 0) {
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found) {
|
|
pCtx->ok = false;
|
|
return DEAL_RES_END;
|
|
}
|
|
}
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static bool tsmaOptCheckTags(STSMAOptCtx* pCtx, const STableTSMAInfo* pTsma) {
|
|
const SScanLogicNode* pScan = pCtx->pScan;
|
|
STsmaOptTagCheckCtx ctx = {.pTsma = pTsma, .ok = true};
|
|
nodesWalkExpr(pScan->pTagCond, tsmaOptTagCheck, &ctx);
|
|
if (!ctx.ok) return false;
|
|
nodesWalkExprs(pScan->pScanPseudoCols, tsmaOptTagCheck, &ctx);
|
|
if (!ctx.ok) return false;
|
|
nodesWalkExprs(pScan->pGroupTags, tsmaOptTagCheck, &ctx);
|
|
return ctx.ok;
|
|
}
|
|
|
|
static int32_t tsmaOptFilterTsmas(STSMAOptCtx* pTsmaOptCtx) {
|
|
STSMAOptUsefulTsma usefulTsma = {
|
|
.pTsma = NULL, .scanRange = {.skey = TSKEY_MIN, .ekey = TSKEY_MAX}, .precision = pTsmaOptCtx->precision};
|
|
SArray* pTsmaScanCols = NULL;
|
|
int32_t code = 0;
|
|
|
|
for (int32_t i = 0; i < pTsmaOptCtx->pTsmas->size; ++i) {
|
|
if (!pTsmaScanCols) {
|
|
pTsmaScanCols = taosArrayInit(pTsmaOptCtx->pAggFuncs->length, sizeof(int32_t));
|
|
if (!pTsmaScanCols) return TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
if (pTsmaOptCtx->pScan->tableType == TSDB_CHILD_TABLE || pTsmaOptCtx->pScan->tableType == TSDB_NORMAL_TABLE) {
|
|
const STsmaTargetTbInfo* ptbInfo = taosArrayGet(pTsmaOptCtx->pScan->pTsmaTargetTbInfo, i);
|
|
if (ptbInfo->uid == 0) continue; // tsma res table meta not found, skip this tsma, this is possible when there is no data in this ctb
|
|
}
|
|
|
|
STableTSMAInfo* pTsma = taosArrayGetP(pTsmaOptCtx->pTsmas, i);
|
|
if (!pTsma->fillHistoryFinished || tsMaxTsmaCalcDelay * 1000 < (pTsma->rspTs - pTsma->reqTs) + pTsma->delayDuration) {
|
|
continue;
|
|
}
|
|
// filter with interval
|
|
if (!tsmaOptCheckValidInterval(pTsma->interval, pTsma->unit, pTsmaOptCtx)) {
|
|
continue;
|
|
}
|
|
// filter with funcs, note that tsma funcs has been sorted by funcId and ColId
|
|
bool valid = false;
|
|
int32_t code = tsmaOptCheckValidFuncs(pTsma->pFuncs, pTsmaOptCtx->pAggFuncs, pTsmaScanCols, &valid);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
if (!valid) continue;
|
|
|
|
if (!tsmaOptCheckTags(pTsmaOptCtx, pTsma)) continue;
|
|
usefulTsma.pTsma = pTsma;
|
|
usefulTsma.pTsmaScanCols = pTsmaScanCols;
|
|
pTsmaScanCols = NULL;
|
|
if (NULL == taosArrayPush(pTsmaOptCtx->pUsefulTsmas, &usefulTsma)) {
|
|
if (pTsmaScanCols) {
|
|
taosArrayDestroy(pTsmaScanCols);
|
|
}
|
|
return TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
if (pTsmaScanCols) taosArrayDestroy(pTsmaScanCols);
|
|
return code;
|
|
}
|
|
|
|
static int32_t tsmaInfoCompWithIntervalDesc(const void* pLeft, const void* pRight) {
|
|
const int64_t factors[3] = {NANOSECOND_PER_MSEC, NANOSECOND_PER_USEC, 1};
|
|
const STSMAOptUsefulTsma *p = pLeft, *q = pRight;
|
|
int64_t pInterval = p->pTsma->interval, qInterval = q->pTsma->interval;
|
|
int8_t pUnit = p->pTsma->unit, qUnit = q->pTsma->unit;
|
|
if (TIME_UNIT_MONTH == pUnit) {
|
|
pInterval = pInterval * 31 * (NANOSECOND_PER_DAY / factors[p->precision]);
|
|
} else if (TIME_UNIT_YEAR == pUnit){
|
|
pInterval = pInterval * 365 * (NANOSECOND_PER_DAY / factors[p->precision]);
|
|
}
|
|
if (TIME_UNIT_MONTH == qUnit) {
|
|
qInterval = qInterval * 31 * (NANOSECOND_PER_DAY / factors[q->precision]);
|
|
} else if (TIME_UNIT_YEAR == qUnit){
|
|
qInterval = qInterval * 365 * (NANOSECOND_PER_DAY / factors[q->precision]);
|
|
}
|
|
|
|
if (pInterval > qInterval) return -1;
|
|
if (pInterval < qInterval) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void tsmaOptInitIntervalFromTsma(SInterval* pInterval, const STableTSMAInfo* pTsma, int8_t precision) {
|
|
pInterval->interval = pTsma->interval;
|
|
pInterval->intervalUnit = pTsma->unit;
|
|
pInterval->sliding = pTsma->interval;
|
|
pInterval->slidingUnit = pTsma->unit;
|
|
pInterval->offset = 0;
|
|
pInterval->offsetUnit = pTsma->unit;
|
|
pInterval->precision = precision;
|
|
}
|
|
|
|
static const STSMAOptUsefulTsma* tsmaOptFindUsefulTsma(const SArray* pUsefulTsmas, int32_t startIdx,
|
|
int64_t startAlignInterval, int64_t endAlignInterval,
|
|
int8_t precision) {
|
|
SInterval tsmaInterval;
|
|
for (int32_t i = startIdx; i < pUsefulTsmas->size; ++i) {
|
|
const STSMAOptUsefulTsma* pUsefulTsma = taosArrayGet(pUsefulTsmas, i);
|
|
tsmaOptInitIntervalFromTsma(&tsmaInterval, pUsefulTsma->pTsma, precision);
|
|
if (taosTimeTruncate(startAlignInterval, &tsmaInterval) == startAlignInterval &&
|
|
taosTimeTruncate(endAlignInterval, &tsmaInterval) == endAlignInterval) {
|
|
return pUsefulTsma;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int32_t tsmaOptSplitWindows(STSMAOptCtx* pTsmaOptCtx, const STimeWindow* pScanRange) {
|
|
bool needTailWindow = false;
|
|
bool isSkeyAlignedWithTsma = true, isEkeyAlignedWithTsma = true;
|
|
int32_t code = 0;
|
|
int64_t winSkey = TSKEY_MIN, winEkey = TSKEY_MAX;
|
|
int64_t startOfSkeyFirstWin = pScanRange->skey, endOfSkeyFirstWin;
|
|
int64_t startOfEkeyFirstWin = pScanRange->ekey, endOfEkeyFirstWin;
|
|
SInterval interval, tsmaInterval;
|
|
STimeWindow scanRange = *pScanRange;
|
|
const SInterval* pInterval = pTsmaOptCtx->queryInterval;
|
|
const STSMAOptUsefulTsma* pUsefulTsma = taosArrayGet(pTsmaOptCtx->pUsefulTsmas, 0);
|
|
const STableTSMAInfo* pTsma = pUsefulTsma->pTsma;
|
|
|
|
if (pScanRange->ekey <= pScanRange->skey) return code;
|
|
|
|
if (!pInterval) {
|
|
tsmaOptInitIntervalFromTsma(&interval, pTsma, pTsmaOptCtx->precision);
|
|
pInterval = &interval;
|
|
}
|
|
|
|
tsmaOptInitIntervalFromTsma(&tsmaInterval, pTsma, pTsmaOptCtx->precision);
|
|
|
|
// check for head windows
|
|
if (pScanRange->skey != TSKEY_MIN) {
|
|
startOfSkeyFirstWin = taosTimeTruncate(pScanRange->skey, pInterval);
|
|
endOfSkeyFirstWin =
|
|
taosTimeAdd(startOfSkeyFirstWin, pInterval->interval, pInterval->intervalUnit, pTsmaOptCtx->precision);
|
|
isSkeyAlignedWithTsma = taosTimeTruncate(pScanRange->skey, &tsmaInterval) == pScanRange->skey;
|
|
} else {
|
|
endOfSkeyFirstWin = TSKEY_MIN;
|
|
}
|
|
|
|
// check for tail windows
|
|
if (pScanRange->ekey != TSKEY_MAX) {
|
|
startOfEkeyFirstWin = taosTimeTruncate(pScanRange->ekey, pInterval);
|
|
endOfEkeyFirstWin =
|
|
taosTimeAdd(startOfEkeyFirstWin, pInterval->interval, pInterval->intervalUnit, pTsmaOptCtx->precision);
|
|
isEkeyAlignedWithTsma = taosTimeTruncate(pScanRange->ekey + 1, &tsmaInterval) == (pScanRange->ekey + 1);
|
|
if (startOfEkeyFirstWin > startOfSkeyFirstWin) {
|
|
needTailWindow = true;
|
|
}
|
|
}
|
|
|
|
// add head tsma if possible
|
|
if (!isSkeyAlignedWithTsma) {
|
|
scanRange.ekey = TMIN(
|
|
scanRange.ekey,
|
|
taosTimeAdd(startOfSkeyFirstWin, pInterval->interval * 1, pInterval->intervalUnit, pTsmaOptCtx->precision) - 1);
|
|
const STSMAOptUsefulTsma* pTsmaFound =
|
|
tsmaOptFindUsefulTsma(pTsmaOptCtx->pUsefulTsmas, 1, scanRange.skey, scanRange.ekey + 1, pTsmaOptCtx->precision);
|
|
STSMAOptUsefulTsma usefulTsma = {.pTsma = pTsmaFound ? pTsmaFound->pTsma : NULL,
|
|
.scanRange = scanRange,
|
|
.pTsmaScanCols = pTsmaFound ? pTsmaFound->pTsmaScanCols : NULL};
|
|
if (NULL == taosArrayPush(pTsmaOptCtx->pUsedTsmas, &usefulTsma))
|
|
return TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// the main tsma
|
|
if (endOfSkeyFirstWin < startOfEkeyFirstWin || (endOfSkeyFirstWin == startOfEkeyFirstWin && (isSkeyAlignedWithTsma || isEkeyAlignedWithTsma))) {
|
|
scanRange.ekey =
|
|
TMIN(pScanRange->ekey, isEkeyAlignedWithTsma ? pScanRange->ekey : startOfEkeyFirstWin - 1);
|
|
if (!isSkeyAlignedWithTsma) {
|
|
scanRange.skey = endOfSkeyFirstWin;
|
|
}
|
|
STSMAOptUsefulTsma usefulTsma = {
|
|
.pTsma = pTsma, .scanRange = scanRange, .pTsmaScanCols = pUsefulTsma->pTsmaScanCols};
|
|
if (NULL == taosArrayPush(pTsmaOptCtx->pUsedTsmas, &usefulTsma))
|
|
return TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// add tail tsma if possible
|
|
if (!isEkeyAlignedWithTsma && needTailWindow) {
|
|
scanRange.skey = startOfEkeyFirstWin;
|
|
scanRange.ekey = pScanRange->ekey;
|
|
const STSMAOptUsefulTsma* pTsmaFound =
|
|
tsmaOptFindUsefulTsma(pTsmaOptCtx->pUsefulTsmas, 1, scanRange.skey - startOfEkeyFirstWin,
|
|
scanRange.ekey + 1 - startOfEkeyFirstWin, pTsmaOptCtx->precision);
|
|
STSMAOptUsefulTsma usefulTsma = {.pTsma = pTsmaFound ? pTsmaFound->pTsma : NULL,
|
|
.scanRange = scanRange,
|
|
.pTsmaScanCols = pTsmaFound ? pTsmaFound->pTsmaScanCols : NULL};
|
|
if (NULL == taosArrayPush(pTsmaOptCtx->pUsedTsmas, &usefulTsma))
|
|
return TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
return code;
|
|
}
|
|
|
|
int32_t tsmaOptCreateTsmaScanCols(const STSMAOptUsefulTsma* pTsma, const SNodeList* pAggFuncs, SNodeList** ppList) {
|
|
if (!pTsma->pTsma || !pTsma->pTsmaScanCols) {
|
|
return TSDB_CODE_PLAN_INTERNAL_ERROR;
|
|
}
|
|
int32_t code;
|
|
SNode* pNode;
|
|
SNodeList* pScanCols = NULL;
|
|
|
|
int32_t i = 0;
|
|
|
|
FOREACH(pNode, pAggFuncs) {
|
|
SFunctionNode* pFunc = (SFunctionNode*)pNode;
|
|
if (fmIsPseudoColumnFunc(pFunc->funcId) || fmIsGroupKeyFunc(pFunc->funcId)) {
|
|
continue;
|
|
}
|
|
const int32_t* idx = taosArrayGet(pTsma->pTsmaScanCols, i);
|
|
SColumnNode* pCol = NULL;
|
|
code = nodesMakeNode(QUERY_NODE_COLUMN, (SNode**)&pCol);
|
|
if (pCol) {
|
|
pCol->colId = *idx + 2;
|
|
pCol->tableType = TSDB_SUPER_TABLE;
|
|
pCol->tableId = pTsma->targetTbUid;
|
|
pCol->colType = COLUMN_TYPE_COLUMN;
|
|
strcpy(pCol->tableName, pTsma->targetTbName);
|
|
strcpy(pCol->dbName, pTsma->pTsma->targetDbFName);
|
|
strcpy(pCol->colName, pFunc->node.aliasName);
|
|
strcpy(pCol->node.aliasName, pFunc->node.aliasName);
|
|
pCol->node.resType.type = TSDB_DATA_TYPE_BINARY;
|
|
code = nodesListMakeStrictAppend(&pScanCols, (SNode*)pCol);
|
|
}
|
|
if (code) break;
|
|
++i;
|
|
}
|
|
|
|
if (code) {
|
|
nodesDestroyList(pScanCols);
|
|
pScanCols = NULL;
|
|
} else {
|
|
*ppList = pScanCols;
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t tsmaOptRewriteTag(const STSMAOptCtx* pTsmaOptCtx, const STSMAOptUsefulTsma* pTsma,
|
|
SColumnNode* pTagCol) {
|
|
bool found = false;
|
|
if (pTagCol->colType != COLUMN_TYPE_TAG) return 0;
|
|
for (int32_t i = 0; i < pTsma->pTsma->pTags->size; ++i) {
|
|
const SSchema* pSchema = taosArrayGet(pTsma->pTsma->pTags, i);
|
|
if (strcmp(pTagCol->colName, pSchema->name) == 0) {
|
|
strcpy(pTagCol->tableName, pTsma->targetTbName);
|
|
strcpy(pTagCol->tableAlias, pTsma->targetTbName);
|
|
pTagCol->tableId = pTsma->targetTbUid;
|
|
pTagCol->tableType = TSDB_SUPER_TABLE;
|
|
pTagCol->colId = pSchema->colId;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
return found ? TSDB_CODE_SUCCESS : TSDB_CODE_PLAN_INTERNAL_ERROR;
|
|
}
|
|
|
|
static int32_t tsmaOptRewriteTbname(const STSMAOptCtx* pTsmaOptCtx, SNode** pTbNameNode,
|
|
const STSMAOptUsefulTsma* pTsma) {
|
|
int32_t code = 0;
|
|
SExprNode* pRewrittenFunc = NULL;
|
|
code = nodesMakeNode(pTsma ? QUERY_NODE_COLUMN : QUERY_NODE_FUNCTION, (SNode**)&pRewrittenFunc);
|
|
SValueNode* pValue = NULL;
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
pRewrittenFunc->resType = ((SExprNode*)(*pTbNameNode))->resType;
|
|
}
|
|
|
|
if (pTsma && code == TSDB_CODE_SUCCESS) {
|
|
nodesDestroyNode(*pTbNameNode);
|
|
SColumnNode* pCol = (SColumnNode*)pRewrittenFunc;
|
|
const SSchema* pSchema = taosArrayGet(pTsma->pTsma->pTags, pTsma->pTsma->pTags->size - 1);
|
|
strcpy(pCol->tableName, pTsma->targetTbName);
|
|
strcpy(pCol->tableAlias, pTsma->targetTbName);
|
|
pCol->tableId = pTsma->targetTbUid;
|
|
pCol->tableType = TSDB_SUPER_TABLE;
|
|
pCol->colId = pSchema->colId;
|
|
pCol->colType = COLUMN_TYPE_TAG;
|
|
} else if (code == TSDB_CODE_SUCCESS) {
|
|
// if no tsma, we replace func tbname with concat('', tbname)
|
|
SFunctionNode* pFunc = (SFunctionNode*)pRewrittenFunc;
|
|
pFunc->funcId = fmGetFuncId("concat");
|
|
snprintf(pFunc->functionName, TSDB_FUNC_NAME_LEN, "concat");
|
|
code = nodesMakeNode(QUERY_NODE_VALUE, (SNode**)&pValue);
|
|
if (!pValue) code = TSDB_CODE_OUT_OF_MEMORY;
|
|
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
pValue->translate = true;
|
|
pValue->node.resType = ((SExprNode*)(*pTbNameNode))->resType;
|
|
pValue->literal = taosMemoryCalloc(1, TSDB_TABLE_FNAME_LEN + 1);
|
|
pValue->datum.p = taosMemoryCalloc(1, TSDB_TABLE_FNAME_LEN + 1 + VARSTR_HEADER_SIZE);
|
|
if (!pValue->literal || !pValue->datum.p) code = TSDB_CODE_OUT_OF_MEMORY;
|
|
}
|
|
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
code = nodesListMakeStrictAppend(&pFunc->pParameterList, (SNode*)pValue);
|
|
pValue = NULL;
|
|
}
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
code = nodesListStrictAppend(pFunc->pParameterList, *pTbNameNode);
|
|
}
|
|
}
|
|
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
*pTbNameNode = (SNode*)pRewrittenFunc;
|
|
} else {
|
|
nodesDestroyNode((SNode*)pRewrittenFunc);
|
|
if (pValue) nodesDestroyNode((SNode*)pValue);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
struct TsmaOptRewriteCtx {
|
|
const STSMAOptCtx* pTsmaOptCtx;
|
|
const STSMAOptUsefulTsma* pTsma;
|
|
bool rewriteTag;
|
|
bool rewriteTbname;
|
|
int32_t code;
|
|
};
|
|
|
|
EDealRes tsmaOptNodeRewriter(SNode** ppNode, void* ctx) {
|
|
SNode* pNode = *ppNode;
|
|
int32_t code = 0;
|
|
struct TsmaOptRewriteCtx* pCtx = ctx;
|
|
if (pCtx->rewriteTag && nodeType(pNode) == QUERY_NODE_COLUMN && ((SColumnNode*)pNode)->colType == COLUMN_TYPE_TAG) {
|
|
code = tsmaOptRewriteTag(pCtx->pTsmaOptCtx, pCtx->pTsma, (SColumnNode*)pNode);
|
|
} else if (pCtx->rewriteTbname &&
|
|
((nodeType(pNode) == QUERY_NODE_FUNCTION && ((SFunctionNode*)pNode)->funcType == FUNCTION_TYPE_TBNAME) ||
|
|
(nodeType(pNode) == QUERY_NODE_COLUMN && ((SColumnNode*)pNode)->colType == COLUMN_TYPE_TBNAME))) {
|
|
code = tsmaOptRewriteTbname(pCtx->pTsmaOptCtx, ppNode, pCtx->pTsma);
|
|
if (code == TSDB_CODE_SUCCESS) return DEAL_RES_IGNORE_CHILD;
|
|
}
|
|
if (code) {
|
|
pCtx->code = code;
|
|
return DEAL_RES_ERROR;
|
|
}
|
|
return DEAL_RES_CONTINUE;
|
|
}
|
|
|
|
static int32_t tsmaOptRewriteNode(SNode** pNode, STSMAOptCtx* pCtx, const STSMAOptUsefulTsma* pTsma, bool rewriteTbName, bool rewriteTag) {
|
|
struct TsmaOptRewriteCtx ctx = {
|
|
.pTsmaOptCtx = pCtx, .pTsma = pTsma, .rewriteTag = rewriteTag, .rewriteTbname = rewriteTbName, .code = 0};
|
|
SNode* pOut = *pNode;
|
|
nodesRewriteExpr(&pOut, tsmaOptNodeRewriter, &ctx);
|
|
if (ctx.code == TSDB_CODE_SUCCESS) *pNode = pOut;
|
|
return ctx.code;
|
|
}
|
|
|
|
static int32_t tsmaOptRewriteNodeList(SNodeList* pNodes, STSMAOptCtx* pCtx, const STSMAOptUsefulTsma* pTsma,
|
|
bool rewriteTbName, bool rewriteTag) {
|
|
int32_t code = 0;
|
|
SNode* pNode;
|
|
FOREACH(pNode, pNodes) {
|
|
SNode* pOut = pNode;
|
|
code = tsmaOptRewriteNode(&pOut, pCtx, pTsma, rewriteTbName, rewriteTag);
|
|
if (TSDB_CODE_SUCCESS != code) break;
|
|
REPLACE_NODE(pOut);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t tsmaOptRewriteScan(STSMAOptCtx* pTsmaOptCtx, SScanLogicNode* pNewScan, const STSMAOptUsefulTsma* pTsma) {
|
|
SNode* pNode;
|
|
int32_t code = 0;
|
|
|
|
pNewScan->scanRange.skey = pTsma->scanRange.skey;
|
|
pNewScan->scanRange.ekey = pTsma->scanRange.ekey;
|
|
|
|
if (pTsma->pTsma) {
|
|
// PK col
|
|
SColumnNode* pPkTsCol = NULL;
|
|
FOREACH(pNode, pNewScan->pScanCols) {
|
|
SColumnNode* pCol = (SColumnNode*)pNode;
|
|
if (pCol->colId == PRIMARYKEY_TIMESTAMP_COL_ID) {
|
|
pPkTsCol = NULL;
|
|
code = nodesCloneNode((SNode*)pCol, (SNode**)&pPkTsCol);
|
|
break;
|
|
}
|
|
}
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
nodesDestroyList(pNewScan->pScanCols);
|
|
// normal cols
|
|
pNewScan->pScanCols = NULL;
|
|
code = tsmaOptCreateTsmaScanCols(pTsma, pTsmaOptCtx->pAggFuncs, &pNewScan->pScanCols);
|
|
}
|
|
if (code == TSDB_CODE_SUCCESS && pPkTsCol) {
|
|
tstrncpy(pPkTsCol->tableName, pTsma->targetTbName, TSDB_TABLE_NAME_LEN);
|
|
tstrncpy(pPkTsCol->tableAlias, pTsma->targetTbName, TSDB_TABLE_NAME_LEN);
|
|
pPkTsCol->tableId = pTsma->targetTbUid;
|
|
code = nodesListMakeStrictAppend(&pNewScan->pScanCols, (SNode*)pPkTsCol);
|
|
} else if (pPkTsCol){
|
|
nodesDestroyNode((SNode*)pPkTsCol);
|
|
}
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
pNewScan->stableId = pTsma->pTsma->destTbUid;
|
|
pNewScan->tableId = pTsma->targetTbUid;
|
|
strcpy(pNewScan->tableName.tname, pTsma->targetTbName);
|
|
}
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
code = tsmaOptRewriteNodeList(pNewScan->pScanPseudoCols, pTsmaOptCtx, pTsma, true, true);
|
|
}
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
code = tsmaOptRewriteNode(&pNewScan->pTagCond, pTsmaOptCtx, pTsma, true, true);
|
|
}
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
code = tsmaOptRewriteNodeList(pNewScan->pGroupTags, pTsmaOptCtx, pTsma, true, true);
|
|
}
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
pTsmaOptCtx->pScan->dataRequired = FUNC_DATA_REQUIRED_DATA_LOAD;
|
|
if (pTsmaOptCtx->pScan->pTsmaTargetTbVgInfo && pTsmaOptCtx->pScan->pTsmaTargetTbVgInfo->size > 0) {
|
|
for (int32_t i = 0; i < taosArrayGetSize(pTsmaOptCtx->pScan->pTsmas); ++i) {
|
|
STableTSMAInfo* pTsmaInfo = taosArrayGetP(pTsmaOptCtx->pScan->pTsmas, i);
|
|
if (pTsmaInfo == pTsma->pTsma) {
|
|
const SVgroupsInfo* pVgpsInfo = taosArrayGetP(pTsmaOptCtx->pScan->pTsmaTargetTbVgInfo, i);
|
|
taosMemoryFreeClear(pNewScan->pVgroupList);
|
|
int32_t len = sizeof(int32_t) + sizeof(SVgroupInfo) * pVgpsInfo->numOfVgroups;
|
|
pNewScan->pVgroupList = taosMemoryCalloc(1, len);
|
|
if (!pNewScan->pVgroupList) {
|
|
code = TSDB_CODE_OUT_OF_MEMORY;
|
|
break;
|
|
}
|
|
memcpy(pNewScan->pVgroupList, pVgpsInfo, len);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
FOREACH(pNode, pNewScan->pGroupTags) {
|
|
// rewrite tbname recursively
|
|
struct TsmaOptRewriteCtx ctx = {
|
|
.pTsmaOptCtx = pTsmaOptCtx, .pTsma = NULL, .rewriteTag = false, .rewriteTbname = true, .code = 0};
|
|
nodesRewriteExpr(&pNode, tsmaOptNodeRewriter, &ctx);
|
|
if (ctx.code) {
|
|
code = ctx.code;
|
|
} else {
|
|
REPLACE_NODE(pNode);
|
|
}
|
|
}
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t tsmaOptCreateWStart(int8_t precision, SFunctionNode** pWStartOut) {
|
|
SFunctionNode* pWStart = NULL;
|
|
int32_t code = nodesMakeNode(QUERY_NODE_FUNCTION, (SNode**)&pWStart);
|
|
if (NULL == pWStart) {
|
|
return code;
|
|
}
|
|
strcpy(pWStart->functionName, "_wstart");
|
|
int64_t pointer = (int64_t)pWStart;
|
|
char name[TSDB_COL_NAME_LEN + TSDB_POINTER_PRINT_BYTES + TSDB_NAME_DELIMITER_LEN + 1] = {0};
|
|
int32_t len = snprintf(name, sizeof(name) - 1, "%s.%" PRId64 "", pWStart->functionName, pointer);
|
|
(void)taosHashBinary(name, len);
|
|
strncpy(pWStart->node.aliasName, name, TSDB_COL_NAME_LEN - 1);
|
|
pWStart->node.resType.precision = precision;
|
|
|
|
code = fmGetFuncInfo(pWStart, NULL, 0);
|
|
if (code) {
|
|
nodesDestroyNode((SNode*)pWStart);
|
|
} else {
|
|
*pWStartOut = pWStart;
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t tsmaOptRewriteParent(STSMAOptCtx* pTsmaOptCtx, SLogicNode* pParent, SScanLogicNode* pScan,
|
|
const STSMAOptUsefulTsma* pTsma) {
|
|
int32_t code = 0;
|
|
SColumnNode* pColNode;
|
|
SWindowLogicNode* pWindow = NULL;
|
|
SAggLogicNode* pAgg;
|
|
SNodeList* pAggFuncs;
|
|
SListCell* pScanListCell;
|
|
SNode* pAggFuncNode;
|
|
SNodeList* pAggStateFuncs = NULL;
|
|
bool isFirstMergeNode = pTsmaOptCtx->pScan == pScan;
|
|
SFunctionNode * pPartial = NULL, *pMerge = NULL;
|
|
|
|
if (nodeType(pParent) == QUERY_NODE_LOGIC_PLAN_WINDOW) {
|
|
pWindow = (SWindowLogicNode*)pParent;
|
|
pAggFuncs = pWindow->pFuncs;
|
|
} else {
|
|
pAgg = (SAggLogicNode*)pParent;
|
|
pAggFuncs = pAgg->pAggFuncs;
|
|
}
|
|
pScanListCell = pScan->pScanCols->pHead;
|
|
|
|
FOREACH(pAggFuncNode, pAggFuncs) {
|
|
SFunctionNode* pAggFunc = (SFunctionNode*)pAggFuncNode;
|
|
if (fmIsGroupKeyFunc(pAggFunc->funcId)) {
|
|
struct TsmaOptRewriteCtx ctx = {
|
|
.pTsmaOptCtx = pTsmaOptCtx, .pTsma = pTsma, .rewriteTag = true, .rewriteTbname = true, .code = 0};
|
|
nodesRewriteExpr(&pAggFuncNode, tsmaOptNodeRewriter, &ctx);
|
|
if (ctx.code) {
|
|
code = ctx.code;
|
|
break;
|
|
} else {
|
|
REPLACE_NODE(pAggFuncNode);
|
|
}
|
|
continue;
|
|
} else if (fmIsPseudoColumnFunc(pAggFunc->funcId)) {
|
|
continue;
|
|
}
|
|
code = fmGetDistMethod(pAggFunc, &pPartial, NULL, &pMerge);
|
|
if (code) break;
|
|
|
|
pColNode = (SColumnNode*)pScanListCell->pNode;
|
|
pScanListCell = pScanListCell->pNext;
|
|
pColNode->node.resType = pPartial->node.resType;
|
|
// currently we assume that the first parameter must be the scan column
|
|
(void)nodesListErase(pMerge->pParameterList, pMerge->pParameterList->pHead);
|
|
SNode* pNew = NULL;
|
|
code = nodesCloneNode((SNode*)pColNode, &pNew);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
code = nodesListPushFront(pMerge->pParameterList, pNew);
|
|
}
|
|
nodesDestroyNode((SNode*)pPartial);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
nodesDestroyNode(pNew);
|
|
break;
|
|
}
|
|
|
|
REPLACE_NODE(pMerge);
|
|
}
|
|
|
|
if (code == TSDB_CODE_SUCCESS && pWindow) {
|
|
SColumnNode* pCol = (SColumnNode*)pScan->pScanCols->pTail->pNode;
|
|
assert(pCol->colId == PRIMARYKEY_TIMESTAMP_COL_ID);
|
|
nodesDestroyNode(pWindow->pTspk);
|
|
pWindow->pTspk = NULL;
|
|
code = nodesCloneNode((SNode*)pCol, &pWindow->pTspk);
|
|
}
|
|
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
nodesDestroyList(pScan->node.pTargets);
|
|
code = createColumnByRewriteExprs(pScan->pScanCols, &pScan->node.pTargets);
|
|
}
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
code = createColumnByRewriteExprs(pScan->pScanPseudoCols, &pScan->node.pTargets);
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static int32_t tsmaOptGeneratePlan(STSMAOptCtx* pTsmaOptCtx) {
|
|
int32_t code = 0;
|
|
const STSMAOptUsefulTsma* pTsma = NULL;
|
|
SNodeList* pAggFuncs = NULL;
|
|
bool hasSubPlan = false;
|
|
|
|
for (int32_t i = 0; i < pTsmaOptCtx->pUsedTsmas->size; ++i) {
|
|
STSMAOptUsefulTsma* pTsma = taosArrayGet(pTsmaOptCtx->pUsedTsmas, i);
|
|
if (!pTsma->pTsma) continue;
|
|
if (pTsmaOptCtx->pScan->tableType == TSDB_CHILD_TABLE || pTsmaOptCtx->pScan->tableType == TSDB_NORMAL_TABLE) {
|
|
for (int32_t j = 0; j < pTsmaOptCtx->pScan->pTsmas->size; ++j) {
|
|
if (taosArrayGetP(pTsmaOptCtx->pScan->pTsmas, j) == pTsma->pTsma) {
|
|
const STsmaTargetTbInfo* ptbInfo = taosArrayGet(pTsmaOptCtx->pScan->pTsmaTargetTbInfo, j);
|
|
strcpy(pTsma->targetTbName, ptbInfo->tableName);
|
|
pTsma->targetTbUid = ptbInfo->uid;
|
|
}
|
|
}
|
|
} else {
|
|
strcpy(pTsma->targetTbName, pTsma->pTsma->targetTb);
|
|
pTsma->targetTbUid = pTsma->pTsma->destTbUid;
|
|
}
|
|
}
|
|
|
|
for (int32_t i = 1; i < pTsmaOptCtx->pUsedTsmas->size && code == TSDB_CODE_SUCCESS; ++i) {
|
|
pTsma = taosArrayGet(pTsmaOptCtx->pUsedTsmas, i);
|
|
SLogicSubplan* pSubplan = NULL;
|
|
code = nodesMakeNode(QUERY_NODE_LOGIC_SUBPLAN, (SNode**)&pSubplan);
|
|
if (!pSubplan) {
|
|
break;
|
|
}
|
|
pSubplan->subplanType = SUBPLAN_TYPE_SCAN;
|
|
pTsmaOptCtx->generatedSubPlans[i - 1] = pSubplan;
|
|
hasSubPlan = true;
|
|
SLogicNode* pParent = NULL;
|
|
code = nodesCloneNode((SNode*)pTsmaOptCtx->pParent, (SNode**)&pParent);
|
|
if (!pParent) {
|
|
break;
|
|
}
|
|
pSubplan->pNode = pParent;
|
|
pParent->pParent = NULL;
|
|
pParent->groupAction = GROUP_ACTION_KEEP;
|
|
SScanLogicNode* pScan = (SScanLogicNode*)pParent->pChildren->pHead->pNode;
|
|
code = tsmaOptRewriteScan(pTsmaOptCtx, pScan, pTsma);
|
|
if (code == TSDB_CODE_SUCCESS && pTsma->pTsma) {
|
|
code = tsmaOptRewriteParent(pTsmaOptCtx, pParent, pScan, pTsma);
|
|
}
|
|
}
|
|
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
pTsma = taosArrayGet(pTsmaOptCtx->pUsedTsmas, 0);
|
|
pTsmaOptCtx->pScan->needSplit = hasSubPlan;
|
|
code = tsmaOptRewriteScan(pTsmaOptCtx, pTsmaOptCtx->pScan, pTsma);
|
|
if (code == TSDB_CODE_SUCCESS && pTsma->pTsma) {
|
|
code = tsmaOptRewriteParent(pTsmaOptCtx, pTsmaOptCtx->pParent, pTsmaOptCtx->pScan, pTsma);
|
|
}
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static bool tsmaOptIsUsingTsmas(STSMAOptCtx* pCtx) {
|
|
if (pCtx->pUsedTsmas->size == 0) {
|
|
return false;
|
|
}
|
|
for (int32_t i = 0; i < pCtx->pUsedTsmas->size; ++i) {
|
|
const STSMAOptUsefulTsma*pTsma = taosArrayGet(pCtx->pUsedTsmas, i);
|
|
if (pTsma->pTsma) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int32_t tsmaOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
int32_t code = 0;
|
|
STSMAOptCtx tsmaOptCtx = {0};
|
|
SScanLogicNode* pScan = (SScanLogicNode*)optFindPossibleNode(pLogicSubplan->pNode, tsmaOptMayBeOptimized, NULL);
|
|
if (!pScan) return code;
|
|
|
|
SLogicNode* pRootNode = getLogicNodeRootNode((SLogicNode*)pScan);
|
|
if (getOptHint(pRootNode->pHint, HINT_SKIP_TSMA)) return code;
|
|
|
|
code = fillTSMAOptCtx(&tsmaOptCtx, pScan);
|
|
if (code == TSDB_CODE_SUCCESS) {
|
|
// 1. extract useful tsmas
|
|
code = tsmaOptFilterTsmas(&tsmaOptCtx);
|
|
|
|
if (code == TSDB_CODE_SUCCESS && tsmaOptCtx.pUsefulTsmas->size > 0) {
|
|
// 2. sort useful tsmas with interval
|
|
taosArraySort(tsmaOptCtx.pUsefulTsmas, tsmaInfoCompWithIntervalDesc);
|
|
// 3. split windows
|
|
code = tsmaOptSplitWindows(&tsmaOptCtx, tsmaOptCtx.pTimeRange);
|
|
if (TSDB_CODE_SUCCESS == code && tsmaOptIsUsingTsmas(&tsmaOptCtx)) {
|
|
// 4. create logic plan
|
|
code = tsmaOptGeneratePlan(&tsmaOptCtx);
|
|
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
for (int32_t i = 0; i < 2 && (TSDB_CODE_SUCCESS == code); i++) {
|
|
SLogicSubplan* pSubplan = tsmaOptCtx.generatedSubPlans[i];
|
|
if (!pSubplan) continue;
|
|
pSubplan->subplanType = SUBPLAN_TYPE_SCAN;
|
|
code = nodesListMakeAppend(tsmaOptCtx.ppParentTsmaSubplans, (SNode*)pSubplan);
|
|
}
|
|
pCxt->optimized = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pScan->pTsmas = NULL;
|
|
clearTSMAOptCtx(&tsmaOptCtx);
|
|
return code;
|
|
}
|
|
|
|
// clang-format off
|
|
static const SOptimizeRule optimizeRuleSet[] = {
|
|
{.pName = "ScanPath", .optimizeFunc = scanPathOptimize},
|
|
{.pName = "PushDownCondition", .optimizeFunc = pdcOptimize},
|
|
{.pName = "EliminateNotNullCond", .optimizeFunc = eliminateNotNullCondOptimize},
|
|
{.pName = "JoinCondOptimize", .optimizeFunc = joinCondOptimize},
|
|
{.pName = "HashJoin", .optimizeFunc = hashJoinOptimize},
|
|
{.pName = "StableJoin", .optimizeFunc = stableJoinOptimize},
|
|
{.pName = "GroupJoin", .optimizeFunc = groupJoinOptimize},
|
|
{.pName = "sortNonPriKeyOptimize", .optimizeFunc = sortNonPriKeyOptimize},
|
|
{.pName = "SortPrimaryKey", .optimizeFunc = sortPrimaryKeyOptimize},
|
|
{.pName = "SortForjoin", .optimizeFunc = sortForJoinOptimize},
|
|
{.pName = "SmaIndex", .optimizeFunc = smaIndexOptimize},
|
|
{.pName = "PushDownLimit", .optimizeFunc = pushDownLimitOptimize},
|
|
{.pName = "PartitionTags", .optimizeFunc = partTagsOptimize},
|
|
{.pName = "MergeProjects", .optimizeFunc = mergeProjectsOptimize},
|
|
{.pName = "RewriteTail", .optimizeFunc = rewriteTailOptimize},
|
|
{.pName = "RewriteUnique", .optimizeFunc = rewriteUniqueOptimize},
|
|
{.pName = "splitCacheLastFunc", .optimizeFunc = splitCacheLastFuncOptimize},
|
|
{.pName = "LastRowScan", .optimizeFunc = lastRowScanOptimize},
|
|
{.pName = "TagScan", .optimizeFunc = tagScanOptimize},
|
|
{.pName = "TableCountScan", .optimizeFunc = tableCountScanOptimize},
|
|
{.pName = "EliminateProject", .optimizeFunc = eliminateProjOptimize},
|
|
{.pName = "EliminateSetOperator", .optimizeFunc = eliminateSetOpOptimize},
|
|
{.pName = "PartitionCols", .optimizeFunc = partitionColsOpt},
|
|
{.pName = "Tsma", .optimizeFunc = tsmaOptimize},
|
|
};
|
|
// clang-format on
|
|
|
|
static const int32_t optimizeRuleNum = (sizeof(optimizeRuleSet) / sizeof(SOptimizeRule));
|
|
|
|
static int32_t dumpLogicSubplan(const char* pRuleName, SLogicSubplan* pSubplan) {
|
|
int32_t code = 0;
|
|
if (!tsQueryPlannerTrace) {
|
|
return code;
|
|
}
|
|
char* pStr = NULL;
|
|
code = nodesNodeToString((SNode*)pSubplan, false, &pStr, NULL);
|
|
if (TSDB_CODE_SUCCESS == code) {
|
|
if (NULL == pRuleName) {
|
|
qDebugL("before optimize, JsonPlan: %s", pStr);
|
|
} else {
|
|
qDebugL("apply optimize %s rule, JsonPlan: %s", pRuleName, pStr);
|
|
}
|
|
taosMemoryFree(pStr);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int32_t applyOptimizeRule(SPlanContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
SOptimizeContext cxt = {.pPlanCxt = pCxt, .optimized = false};
|
|
bool optimized = false;
|
|
int32_t code = dumpLogicSubplan(NULL, pLogicSubplan);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
do {
|
|
optimized = false;
|
|
for (int32_t i = 0; i < optimizeRuleNum; ++i) {
|
|
cxt.optimized = false;
|
|
int32_t code = optimizeRuleSet[i].optimizeFunc(&cxt, pLogicSubplan);
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
return code;
|
|
}
|
|
if (cxt.optimized) {
|
|
optimized = true;
|
|
code = dumpLogicSubplan(optimizeRuleSet[i].pName, pLogicSubplan);
|
|
break;
|
|
}
|
|
}
|
|
} while (optimized && (TSDB_CODE_SUCCESS == code));
|
|
return code;
|
|
}
|
|
|
|
int32_t optimizeLogicPlan(SPlanContext* pCxt, SLogicSubplan* pLogicSubplan) {
|
|
if (SUBPLAN_TYPE_MODIFY == pLogicSubplan->subplanType && NULL == pLogicSubplan->pNode->pChildren) {
|
|
return TSDB_CODE_SUCCESS;
|
|
}
|
|
return applyOptimizeRule(pCxt, pLogicSubplan);
|
|
}
|