diff --git a/docs/en/14-reference/03-taos-sql/22-function.md b/docs/en/14-reference/03-taos-sql/22-function.md index c9e3b326e39d..e28a5fb1d3a5 100644 --- a/docs/en/14-reference/03-taos-sql/22-function.md +++ b/docs/en/14-reference/03-taos-sql/22-function.md @@ -2976,7 +2976,7 @@ CSUM(expr) **Usage Instructions**: - Does not support +, -, *, / operations, such as csum(col1) + csum(col2). -- Can only be used with aggregation functions. This function can be applied to both basic tables and supertables. +- Cannot be used with aggregation functions. ### DERIVATIVE @@ -3134,7 +3134,7 @@ MAVG(expr, k) **Usage Notes**: - Does not support +, -, *, / operations, such as mavg(col1, k1) + mavg(col2, k1); -- Can only be used with regular columns, selection, and projection functions, not with aggregation functions; +- Cannot be used with aggregation functions; - When used with a window clause, `MAVG` is calculated only from samples inside the current window and does not continue state across windows. ### STATECOUNT diff --git a/docs/zh/14-reference/03-taos-sql/22-function.md b/docs/zh/14-reference/03-taos-sql/22-function.md index b47e04a36ea5..55a9a3cc85f8 100644 --- a/docs/zh/14-reference/03-taos-sql/22-function.md +++ b/docs/zh/14-reference/03-taos-sql/22-function.md @@ -3030,7 +3030,7 @@ CSUM(expr) **使用说明**: - 不支持 +、-、*、/ 运算,如 csum(col1) + csum(col2)。 -- 只能与聚合(Aggregation)函数一起使用。该函数可以应用在普通表和超级表上。 +- 不能与聚合(Aggregation)函数一起使用。 #### DERIVATIVE @@ -3158,7 +3158,7 @@ MAVG(expr, k) **使用说明**: - 不支持 +、-、*、/ 运算,如 mavg(col1, k1) + mavg(col2, k1); -- 只能与普通列,选择(Selection)、投影(Projection)函数一起使用,不能与聚合(Aggregation)函数一起使用; +- 不能与聚合(Aggregation)函数一起使用; - 与窗口一起使用时,`MAVG` 仅在当前窗口内部按样本顺序计算,不会跨窗口延续上一窗口的样本状态。 #### STATECOUNT diff --git a/include/libs/nodes/querynodes.h b/include/libs/nodes/querynodes.h index 08445da093b5..581a64c3f1ed 100644 --- a/include/libs/nodes/querynodes.h +++ b/include/libs/nodes/querynodes.h @@ -698,6 +698,7 @@ typedef struct SSelectStmt { char stmtName[TSDB_TABLE_NAME_LEN]; uint8_t precision; int32_t selectFuncNum; + int32_t selectAggFuncNum; int32_t returnRows; // EFuncReturnRows ETimeLineMode timeLineCurMode; ETimeLineMode timeLineResMode; @@ -707,6 +708,8 @@ typedef struct SSelectStmt { bool isEmptyResult; bool isSubquery; bool hasAggFuncs; + bool hasNonSelectAggFuncs; + bool hasMultiSelectAggFuncs; bool hasRepeatScanFuncs; bool hasIndefiniteRowsFunc; bool hasMultiRowsFunc; diff --git a/source/libs/nodes/src/nodesCloneFuncs.c b/source/libs/nodes/src/nodesCloneFuncs.c index a02f48df02a3..d1ff3c769f82 100644 --- a/source/libs/nodes/src/nodesCloneFuncs.c +++ b/source/libs/nodes/src/nodesCloneFuncs.c @@ -1172,6 +1172,9 @@ static int32_t selectStmtCopy(const SSelectStmt* pSrc, SSelectStmt* pDst) { COPY_SCALAR_FIELD(timeLineFromOrderBy); COPY_SCALAR_FIELD(timeLineCurMode); COPY_SCALAR_FIELD(hasAggFuncs); + COPY_SCALAR_FIELD(hasNonSelectAggFuncs); + COPY_SCALAR_FIELD(hasMultiSelectAggFuncs); + COPY_SCALAR_FIELD(selectAggFuncNum); COPY_SCALAR_FIELD(hasRepeatScanFuncs); COPY_SCALAR_FIELD(hasIndefiniteRowsFunc); COPY_SCALAR_FIELD(hasMultiRowsFunc); diff --git a/source/libs/nodes/src/nodesCodeFuncs.c b/source/libs/nodes/src/nodesCodeFuncs.c index 118db8a42451..9b7ac65f0666 100644 --- a/source/libs/nodes/src/nodesCodeFuncs.c +++ b/source/libs/nodes/src/nodesCodeFuncs.c @@ -7925,6 +7925,7 @@ static const char* jkSelectStmtLimit = "Limit"; static const char* jkSelectStmtSlimit = "Slimit"; static const char* jkSelectStmtStmtName = "StmtName"; static const char* jkSelectStmtHasAggFuncs = "HasAggFuncs"; +static const char* jkSelectStmtHasNonSelectAggFuncs = "HasNonSelectAggFuncs"; static const char* jkSelectStmtInterpFuncs = "HasInterpFuncs"; static const char* jkSelectStmtInterpFill = "InterpFill"; static const char* jkSelectStmtInterpEvery = "InterpEvery"; @@ -7973,6 +7974,15 @@ static int32_t selectStmtToJson(const void* pObj, SJson* pJson) { if (TSDB_CODE_SUCCESS == code) { code = tjsonAddBoolToObject(pJson, jkSelectStmtHasAggFuncs, pNode->hasAggFuncs); } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonAddBoolToObject(pJson, jkSelectStmtHasNonSelectAggFuncs, pNode->hasNonSelectAggFuncs); + } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonAddBoolToObject(pJson, "HasMultiSelectAggFuncs", pNode->hasMultiSelectAggFuncs); + } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonAddIntegerToObject(pJson, "SelectAggFuncNum", pNode->selectAggFuncNum); + } if (TSDB_CODE_SUCCESS == code) { code = tjsonAddBoolToObject(pJson, jkSelectStmtInterpFuncs, pNode->hasInterpFunc); } @@ -8031,6 +8041,15 @@ static int32_t jsonToSelectStmt(const SJson* pJson, void* pObj) { if (TSDB_CODE_SUCCESS == code) { code = tjsonGetBoolValue(pJson, jkSelectStmtHasAggFuncs, &pNode->hasAggFuncs); } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonGetBoolValue(pJson, jkSelectStmtHasNonSelectAggFuncs, &pNode->hasNonSelectAggFuncs); + } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonGetBoolValue(pJson, "HasMultiSelectAggFuncs", &pNode->hasMultiSelectAggFuncs); + } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonGetIntValue(pJson, "SelectAggFuncNum", &pNode->selectAggFuncNum); + } if (TSDB_CODE_SUCCESS == code) { code = tjsonGetBoolValue(pJson, jkSelectStmtInterpFuncs, &pNode->hasInterpFunc); } diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index 30c142080a2c..2212d29d77f1 100644 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -3584,10 +3584,17 @@ static int32_t translateAggFunc(STranslateContext* pCxt, SFunctionNode* pFunc) { return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_AGG_FUNC_NESTING); } // The auto-generated COUNT function in the DELETE statement is legal - if (isSelectStmt(pCxt->pCurrStmt) && - (((SSelectStmt*)pCxt->pCurrStmt)->hasIndefiniteRowsFunc || ((SSelectStmt*)pCxt->pCurrStmt)->hasMultiRowsFunc)) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, - "Aggregate func '%s' mixed with other multi-row functions", pFunc->functionName); + if (isSelectStmt(pCxt->pCurrStmt)) { + SSelectStmt* pSelect = (SSelectStmt*)pCxt->pCurrStmt; + bool mixedWithIndefOrMulti = pSelect->hasIndefiniteRowsFunc || pSelect->hasMultiRowsFunc; + // select-only agg funcs (first/last/min/max/mode/last_row, excluding top/bottom/sample) + // are allowed to coexist with indefinite-row funcs + bool isAllowedSelectAggWithIndef = pSelect->hasIndefiniteRowsFunc && !pSelect->hasMultiRowsFunc && + fmIsSelectFunc(pFunc->funcId) && !fmIsMultiRowsFunc(pFunc->funcId); + if (mixedWithIndefOrMulti && !isAllowedSelectAggWithIndef) { + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Aggregate func '%s' mixed with other multi-row functions", pFunc->functionName); + } } if (isCountStar(pFunc)) { @@ -3620,7 +3627,7 @@ static int32_t translateIndefiniteRowsFunc(STranslateContext* pCxt, SFunctionNod "Indefinite rows function '%s' only allowed in select clause", pFunc->functionName); } SSelectStmt* pSelect = (SSelectStmt*)pCxt->pCurrStmt; - if (pSelect->hasAggFuncs || pSelect->hasMultiRowsFunc) { + if (pSelect->hasNonSelectAggFuncs || pSelect->hasMultiRowsFunc) { return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, "Indefinite rows function '%s' mixed with other multi-row functions", pFunc->functionName); @@ -4263,6 +4270,18 @@ static void setFuncClassification(STranslateContext* pCxt, SFunctionNode* pFunc) if (NULL != pCurrStmt && QUERY_NODE_SELECT_STMT == nodeType(pCurrStmt)) { SSelectStmt* pSelect = (SSelectStmt*)pCurrStmt; pSelect->hasAggFuncs = pSelect->hasAggFuncs ? true : fmIsAggFunc(pFunc->funcId); + if (fmIsAggFunc(pFunc->funcId) && !fmIsSelectFunc(pFunc->funcId)) { + pSelect->hasNonSelectAggFuncs = true; + } + // Track multiple selection-agg functions (first, last, min, max, mode, last_row) + // but exclude indef-row functions that also carry the SELECT flag. + if (fmIsAggFunc(pFunc->funcId) && fmIsSelectFunc(pFunc->funcId) && + !fmIsIndefiniteRowsFunc(pFunc->funcId)) { + pSelect->selectAggFuncNum++; + if (pSelect->selectAggFuncNum > 1) { + pSelect->hasMultiSelectAggFuncs = true; + } + } pSelect->hasCountFunc = pSelect->hasCountFunc ? true : (FUNCTION_TYPE_COUNT == pFunc->funcType); pSelect->hasRepeatScanFuncs = pSelect->hasRepeatScanFuncs ? true : fmIsRepeatScanFunc(pFunc->funcId); @@ -5487,7 +5506,17 @@ static int32_t checkGroupByListForBlob(STranslateContext* pCxt, SSelectStmt* pSe } static EDealRes rewriteColsToSelectValFuncImpl(SNode** pNode, void* pContext) { - if (isAggFunc(*pNode) || isIndefiniteRowsFunc(*pNode)) { + if (isAggFunc(*pNode)) { + return DEAL_RES_IGNORE_CHILD; + } + if (isIndefiniteRowsFunc(*pNode)) { + STranslateContext* pCxt = (STranslateContext*)pContext; + SSelectStmt* pSelect = (SSelectStmt*)pCxt->pCurrStmt; + // When indef-row funcs coexist with select agg funcs, descend into indef-row + // functions so their column params get wrapped with _select_value(). + if (pSelect && pSelect->hasAggFuncs && !pSelect->hasNonSelectAggFuncs) { + return DEAL_RES_CONTINUE; + } return DEAL_RES_IGNORE_CHILD; } if (isScanPseudoColumnFunc(*pNode) || QUERY_NODE_COLUMN == nodeType(*pNode)) { @@ -5504,6 +5533,35 @@ static int32_t rewriteColsToSelectValFunc(STranslateContext* pCxt, SSelectStmt* return pCxt->errCode; } +// Rewrite walker: descend into indefinite-row functions to wrap their column params +// with _select_value() so the Agg node can output them. +static EDealRes rewriteIndefColsToSelectValImpl(SNode** pNode, void* pContext) { + if (isAggFunc(*pNode)) { + return DEAL_RES_IGNORE_CHILD; + } + if (isIndefiniteRowsFunc(*pNode)) { + return DEAL_RES_CONTINUE; + } + if (isScanPseudoColumnFunc(*pNode) || QUERY_NODE_COLUMN == nodeType(*pNode)) { + return rewriteColToSelectValFunc((STranslateContext*)pContext, pNode); + } + return DEAL_RES_CONTINUE; +} + +// When indefinite-row functions coexist with select agg functions, the Agg node +// sits below IndefRows. Raw columns inside indef functions (e.g. c1 in diff(c1)) +// must be wrapped with _select_value() so Agg outputs them for IndefRows to consume. +static int32_t rewriteIndefColsForSelectAgg(STranslateContext* pCxt, SSelectStmt* pSelect) { + if (!pSelect->hasIndefiniteRowsFunc || !pSelect->hasAggFuncs || pSelect->hasNonSelectAggFuncs) { + return TSDB_CODE_SUCCESS; + } + nodesRewriteExprs(pSelect->pProjectionList, rewriteIndefColsToSelectValImpl, pCxt); + if (TSDB_CODE_SUCCESS == pCxt->errCode) { + nodesRewriteExprs(pSelect->pOrderByList, rewriteIndefColsToSelectValImpl, pCxt); + } + return pCxt->errCode; +} + typedef struct CheckAggColCoexistCxt { STranslateContext* pTranslateCxt; bool existCol; @@ -11676,6 +11734,18 @@ static int32_t translateSelectFrom(STranslateContext* pCxt, SSelectStmt* pSelect if (TSDB_CODE_SUCCESS == code) { code = appendTsForImplicitTsFunc(pCxt, pSelect); } + // After implicit ts params are appended to indef-row functions, wrap them with + // _select_value() so the Agg node can output them for IndefRows to consume. + // Block multiple selection funcs + indef-row funcs: the indef-row function operates + // on a single row from the Agg, but with multiple selection functions the column + // value is ambiguous (which selection result does the indef-row function see?). + if (TSDB_CODE_SUCCESS == code && pSelect->hasIndefiniteRowsFunc && pSelect->hasMultiSelectAggFuncs) { + code = generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Indefinite rows function not allowed with multiple selection functions"); + } + if (TSDB_CODE_SUCCESS == code) { + code = rewriteIndefColsForSelectAgg(pCxt, pSelect); + } if (TSDB_CODE_SUCCESS == code) { code = appendPkParamForPkFunc(pCxt, pSelect); } diff --git a/source/libs/planner/src/planLogicCreater.c b/source/libs/planner/src/planLogicCreater.c index 91c512cde507..604283d16579 100644 --- a/source/libs/planner/src/planLogicCreater.c +++ b/source/libs/planner/src/planLogicCreater.c @@ -1680,8 +1680,11 @@ static int32_t createAggLogicNode(SLogicPlanContext* pCxt, SSelectStmt* pSelect, } static int32_t createIndefRowsFuncLogicNode(SLogicPlanContext* pCxt, SSelectStmt* pSelect, SLogicNode** pLogicNode) { - // top/bottom are both an aggregate function and a indefinite rows function - if (!pSelect->hasIndefiniteRowsFunc || pSelect->hasAggFuncs || NULL != pSelect->pWindow) { + // Allow indefinite rows functions to coexist with selection functions (agg + select). + // Block when non-select agg funcs or window present. + if (!pSelect->hasIndefiniteRowsFunc || + (pSelect->hasAggFuncs && pSelect->hasNonSelectAggFuncs) || + NULL != pSelect->pWindow) { return TSDB_CODE_SUCCESS; } @@ -1698,8 +1701,28 @@ static int32_t createIndefRowsFuncLogicNode(SLogicPlanContext* pCxt, SSelectStmt pIdfRowsFunc->node.requireDataOrder = getRequireDataOrder(pIdfRowsFunc->isTimeLineFunc, pSelect); pIdfRowsFunc->node.resultDataOrder = pIdfRowsFunc->node.requireDataOrder; + // When coexisting with select agg funcs, collect pass-through columns from Agg output + // BEFORE rewriteExprsForSelect (which would turn indef funcs into column nodes too). + // After Agg rewrite, agg functions in pProjectionList are already column nodes referencing + // Agg output. We collect these now and add them to pFuncs/pTargets below. + SNodeList* pPassThroughCols = NULL; + if (pSelect->hasAggFuncs && !pSelect->hasNonSelectAggFuncs) { + SNode* pNode = NULL; + FOREACH(pNode, pSelect->pProjectionList) { + if (QUERY_NODE_COLUMN == nodeType(pNode)) { + SNode* pClone = NULL; + code = nodesCloneNode(pNode, &pClone); + if (TSDB_CODE_SUCCESS != code) break; + code = nodesListMakeStrictAppend(&pPassThroughCols, pClone); + if (TSDB_CODE_SUCCESS != code) break; + } + } + } + // indefinite rows functions and _select_values functions - code = nodesCollectFuncs(pSelect, SQL_CLAUSE_SELECT, NULL, fmIsVectorFunc, &pIdfRowsFunc->pFuncs); + if (TSDB_CODE_SUCCESS == code) { + code = nodesCollectFuncs(pSelect, SQL_CLAUSE_SELECT, NULL, fmIsVectorFunc, &pIdfRowsFunc->pFuncs); + } if (TSDB_CODE_SUCCESS == code) { code = rewriteExprsForSelect(pIdfRowsFunc->pFuncs, pSelect, SQL_CLAUSE_SELECT, NULL); } @@ -1709,6 +1732,23 @@ static int32_t createIndefRowsFuncLogicNode(SLogicPlanContext* pCxt, SSelectStmt code = createColumnByRewriteExprs(pIdfRowsFunc->pFuncs, &pIdfRowsFunc->node.pTargets); } + // Add pass-through columns: each node was cloned once into pPassThroughCols (to snapshot + // state before rewriteExprsForSelect). Clone again here because pFuncs takes ownership + // and pPassThroughCols is destroyed separately. + if (TSDB_CODE_SUCCESS == code && NULL != pPassThroughCols) { + SNode* pNode = NULL; + FOREACH(pNode, pPassThroughCols) { + SNode* pClone = NULL; + code = nodesCloneNode(pNode, &pClone); + if (TSDB_CODE_SUCCESS != code) break; + code = nodesListMakeStrictAppend(&pIdfRowsFunc->pFuncs, pClone); + if (TSDB_CODE_SUCCESS != code) break; + code = createColumnByRewriteExpr(pClone, &pIdfRowsFunc->node.pTargets); + if (TSDB_CODE_SUCCESS != code) break; + } + } + nodesDestroyList(pPassThroughCols); + if (TSDB_CODE_SUCCESS == code) { *pLogicNode = (SLogicNode*)pIdfRowsFunc; } else { diff --git a/source/libs/planner/src/planOptimizer.c b/source/libs/planner/src/planOptimizer.c index 8bf6229437ad..40a0647d954c 100644 --- a/source/libs/planner/src/planOptimizer.c +++ b/source/libs/planner/src/planOptimizer.c @@ -4545,6 +4545,9 @@ static int32_t rewriteTailOptCreateSort(SIndefRowsFuncLogicNode* pIndef, SLogicN SFunctionNode* pTail = NULL; SNode* pFunc = NULL; FOREACH(pFunc, pIndef->pFuncs) { + if (QUERY_NODE_FUNCTION != nodeType(pFunc)) { + continue; + } if (FUNCTION_TYPE_TAIL == ((SFunctionNode*)pFunc)->funcType) { pTail = (SFunctionNode*)pFunc; break; @@ -4597,14 +4600,22 @@ static int32_t rewriteTailOptCreateProject(SIndefRowsFuncLogicNode* pIndef, SLog 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 (QUERY_NODE_FUNCTION != nodeType(pFunc)) { + code = nodesCloneNode(pFunc, &pNew); + if (TSDB_CODE_SUCCESS == code) { + tstrncpy(((SExprNode*)pNew)->aliasName, ((SExprNode*)pFunc)->aliasName, TSDB_COL_NAME_LEN); + code = nodesListMakeStrictAppend(&pProject->pProjections, pNew); + } + } else { + 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) { + if (QUERY_NODE_FUNCTION == nodeType(pFunc) && FUNCTION_TYPE_TAIL == ((SFunctionNode*)pFunc)->funcType) { pTail = (SFunctionNode*)pFunc; } } @@ -4761,6 +4772,34 @@ static int32_t rewriteUniqueOptCreateFirstFunc(SFunctionNode* pSelectValue, SNod return code; } +// Wrap a column node with _select_value() to keep pAggFuncs function-only. +static int32_t rewriteUniqueOptCreateSelectValueFunc(SNode* pCol, SNode** ppNode) { + SFunctionNode* pFunc = NULL; + int32_t code = nodesMakeNode(QUERY_NODE_FUNCTION, (SNode**)&pFunc); + if (NULL == pFunc) { + return code; + } + + tstrncpy(pFunc->functionName, "_select_value", TSDB_FUNC_NAME_LEN); + tstrncpy(pFunc->node.aliasName, ((SExprNode*)pCol)->aliasName, TSDB_COL_NAME_LEN); + pFunc->node.resType = ((SExprNode*)pCol)->resType; + 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); @@ -4785,6 +4824,17 @@ static int32_t rewriteUniqueOptCreateAgg(SIndefRowsFuncLogicNode* pIndef, SLogic SNode* pPrimaryKey = NULL; SNode* pNode = NULL; FOREACH(pNode, pIndef->pFuncs) { + if (QUERY_NODE_FUNCTION != nodeType(pNode)) { + // Pass-through column from coexisting select agg; wrap with _select_value + // to keep pAggFuncs function-only (downstream walkers cast to SFunctionNode). + SNode* pNew = NULL; + code = rewriteUniqueOptCreateSelectValueFunc(pNode, &pNew); + if (TSDB_CODE_SUCCESS == code) { + code = nodesListMakeStrictAppend(&pAgg->pAggFuncs, pNew); + } + if (TSDB_CODE_SUCCESS != code) break; + continue; + } SFunctionNode* pFunc = (SFunctionNode*)pNode; SNode* pExpr = nodesListGetNode(pFunc->pParameterList, 0); if (FUNCTION_TYPE_UNIQUE == pFunc->funcType) { @@ -4874,7 +4924,12 @@ static int32_t rewriteUniqueOptCreateProject(SIndefRowsFuncLogicNode* pIndef, SL SNode* pNode = NULL; FOREACH(pNode, pIndef->pFuncs) { SNode* pNew = NULL; - code = rewriteUniqueOptCreateProjectCol((SFunctionNode*)pNode, &pNew); + if (QUERY_NODE_FUNCTION != nodeType(pNode)) { + // Pass-through column node: clone directly as projection + code = nodesCloneNode(pNode, &pNew); + } else { + code = rewriteUniqueOptCreateProjectCol((SFunctionNode*)pNode, &pNew); + } if (TSDB_CODE_SUCCESS == code) { code = nodesListMakeStrictAppend(&pProject->pProjections, pNew); } diff --git a/source/libs/planner/src/planPhysiCreater.c b/source/libs/planner/src/planPhysiCreater.c index dcc3c38c4c15..f52f4b4bd736 100644 --- a/source/libs/planner/src/planPhysiCreater.c +++ b/source/libs/planner/src/planPhysiCreater.c @@ -452,7 +452,9 @@ static EDealRes doSetSlotId(SNode* pNode, void* pContext) { } // pIndex is definitely not NULL, otherwise it is a bug if (NULL == pIndex) { - planError("doSetSlotId failed, invalid slot name %s", name); + SColumnNode* pDbgCol = (SColumnNode*)pNode; + planError("doSetSlotId failed, invalid slot name %s, colName=%s, aliasName=%s, tableAlias=%s, hasRef=%d, hasDep=%d", + name, pDbgCol->colName, pDbgCol->node.aliasName, pDbgCol->tableAlias, pDbgCol->hasRef, pDbgCol->hasDep); pCxt->errCode = TSDB_CODE_PLAN_SLOT_NOT_FOUND; taosMemoryFree(name); return DEAL_RES_ERROR; diff --git a/test/cases/11-Functions/01-Scalar/test_fun_sca_stateduration.py b/test/cases/11-Functions/01-Scalar/test_fun_sca_stateduration.py index 361805d02f32..f7a59c40a24f 100644 --- a/test/cases/11-Functions/01-Scalar/test_fun_sca_stateduration.py +++ b/test/cases/11-Functions/01-Scalar/test_fun_sca_stateduration.py @@ -91,7 +91,7 @@ def check_errors(self, dbname=DBNAME): f"select stateduration(c1 ,'GT',1,True) from {dbname}.t1", f"select stateduration(c1 ,'GT',1,1s) , count(c1) from {dbname}.t1", f"select stateduration(c1 ,'GT',1,1s) , avg(c1) from {dbname}.t1", - f"select stateduration(c1 ,'GT',1,1s) , min(c1) from {dbname}.t1", + # f"select stateduration(c1 ,'GT',1,1s) , min(c1) from {dbname}.t1", # indef+select now allowed f"select stateduration(c1 ,'GT',1,1s) , spread(c1) from {dbname}.t1", f"select stateduration(c1 ,'GT',1,1s) , diff(c1) from {dbname}.t1", ] @@ -235,7 +235,7 @@ def basic_stateduration_function(self, dbname=DBNAME): # unique with aggregate function tdSql.error(f"select stateduration(c6,'GT',1,1s) ,sum(c1) from {dbname}.ct1") - tdSql.error(f"select stateduration(c6,'GT',1,1s) ,max(c1) from {dbname}.ct1") + tdSql.query(f"select stateduration(c6,'GT',1,1s) ,max(c1) from {dbname}.ct1") # indef+select now allowed tdSql.error(f"select stateduration(c6,'GT',1,1s) ,csum(c1) from {dbname}.ct1") tdSql.error(f"select stateduration(c6,'GT',1,1s) ,count(c1) from {dbname}.ct1") diff --git a/test/cases/11-Functions/03-Selection/test_fun_select_tail.py b/test/cases/11-Functions/03-Selection/test_fun_select_tail.py index fd9661b1df23..cd13756499dd 100644 --- a/test/cases/11-Functions/03-Selection/test_fun_select_tail.py +++ b/test/cases/11-Functions/03-Selection/test_fun_select_tail.py @@ -84,7 +84,7 @@ def check_errors(self, dbname="db"): f"select tail(True) from {dbname}.t1", f"select tail(c1,1) , count(c1) from {dbname}.t1", f"select tail(c1,1) , avg(c1) from {dbname}.t1", - f"select tail(c1,1) , min(c1) from {dbname}.t1", + # f"select tail(c1,1) , min(c1) from {dbname}.t1", # indef+select now allowed f"select tail(c1,1) , spread(c1) from {dbname}.t1", f"select tail(c1,1) , diff(c1) from {dbname}.t1", f"select tail from {dbname}.stb1 partition by tbname", @@ -106,7 +106,7 @@ def check_errors(self, dbname="db"): f"select tail(True) from {dbname}.stb1 partition by tbname", f"select tail(c1,1) , count(c1) from {dbname}.stb1 partition by tbname", f"select tail(c1,1) , avg(c1) from {dbname}.stb1 partition by tbname", - f"select tail(c1,1) , min(c1) from {dbname}.stb1 partition by tbname", + # f"select tail(c1,1) , min(c1) from {dbname}.stb1 partition by tbname", # indef+select now allowed f"select tail(c1,1) , spread(c1) from {dbname}.stb1 partition by tbname", f"select tail(c1,1) , diff(c1) from {dbname}.stb1 partition by tbname", ] @@ -274,7 +274,7 @@ def basic_tail_function(self, dbname="db"): # tail with aggregate function tdSql.error(f"select tail(c1,10,10) ,sum(c1) from {dbname}.ct1") - tdSql.error(f"select tail(c1,10,10) ,max(c1) from {dbname}.ct1") + tdSql.query(f"select tail(c1,10,10) ,max(c1) from {dbname}.ct1") # indef+select now allowed tdSql.error(f"select tail(c1,10,10) ,csum(c1) from {dbname}.ct1") tdSql.error(f"select tail(c1,10,10) ,count(c1) from {dbname}.ct1") diff --git a/test/cases/11-Functions/03-Selection/test_fun_select_unique.py b/test/cases/11-Functions/03-Selection/test_fun_select_unique.py index aadec3e1fa49..4ff5c6578133 100644 --- a/test/cases/11-Functions/03-Selection/test_fun_select_unique.py +++ b/test/cases/11-Functions/03-Selection/test_fun_select_unique.py @@ -82,7 +82,7 @@ def check_errors(self, dbname="db"): f"select unique(True) from {dbname}.t1", f"select unique(c1) , count(c1) from {dbname}.t1", f"select unique(c1) , avg(c1) from {dbname}.t1", - f"select unique(c1) , min(c1) from {dbname}.t1", + # f"select unique(c1) , min(c1) from {dbname}.t1", # indef+select now allowed f"select unique(c1) , spread(c1) from {dbname}.t1", f"select unique(c1) , diff(c1) from {dbname}.t1", #f"select unique(c1) , abs(c1) from {dbname}.t1", # support @@ -106,7 +106,7 @@ def check_errors(self, dbname="db"): f"select unique(True) from {dbname}.stb1 partition by tbname", f"select unique(c1) , count(c1) from {dbname}.stb1 partition by tbname", f"select unique(c1) , avg(c1) from {dbname}.stb1 partition by tbname", - f"select unique(c1) , min(c1) from {dbname}.stb1 partition by tbname", + # f"select unique(c1) , min(c1) from {dbname}.stb1 partition by tbname", # indef+select now allowed f"select unique(c1) , spread(c1) from {dbname}.stb1 partition by tbname", f"select unique(c1) , diff(c1) from {dbname}.stb1 partition by tbname", #f"select unique(c1) , abs(c1) from {dbname}.stb1 partition by tbname", # support @@ -272,7 +272,7 @@ def basic_unique_function(self, dbname="db"): # unique with aggregate function tdSql.error(f"select unique(c1) ,sum(c1) from {dbname}.ct1") - tdSql.error(f"select unique(c1) ,max(c1) from {dbname}.ct1") + tdSql.query(f"select unique(c1) ,max(c1) from {dbname}.ct1") # indef+select now allowed tdSql.error(f"select unique(c1) ,csum(c1) from {dbname}.ct1") tdSql.error(f"select unique(c1) ,count(c1) from {dbname}.ct1") diff --git a/test/cases/11-Functions/04-Timeseries/ans/test_fun_ts_indef_with_select.ans b/test/cases/11-Functions/04-Timeseries/ans/test_fun_ts_indef_with_select.ans new file mode 100644 index 000000000000..f339d1da50f3 --- /dev/null +++ b/test/cases/11-Functions/04-Timeseries/ans/test_fun_ts_indef_with_select.ans @@ -0,0 +1,476 @@ + +taos> drop database if exists test_indef_sel; +Drop OK, 0 row(s) affected + +taos> create database test_indef_sel; +Create OK, 0 row(s) affected + +taos> use test_indef_sel; +Database changed. + +taos> create table stb (ts timestamp, c1 int, c2 float, c3 bigint) tags (t1 int); +Create OK, 0 row(s) affected + +taos> create table ctb1 using stb tags(1); +Create OK, 0 row(s) affected + +taos> create table ctb2 using stb tags(2); +Create OK, 0 row(s) affected + +taos> create table ctb_empty using stb tags(3); +Create OK, 0 row(s) affected + +taos> create table ctb_null using stb tags(4); +Create OK, 0 row(s) affected + +taos> create table ctb_single using stb tags(5); +Create OK, 0 row(s) affected + +taos> insert into ctb1 values ('2020-09-14 02:13:20.000', 1, 1.1, 100) ('2020-09-14 02:13:21.000', 2, 2.2, 200) ('2020-09-14 02:13:22.000', 3, 3.3, 300) ('2020-09-14 02:13:23.000', 4, 4.4, 400) ('2020-09-14 02:13:24.000', 5, 5.5, 500) ('2020-09-14 02:13:25.000', 6, 6.6, 600) ('2020-09-14 02:13:26.000', 7, 7.7, 700) ('2020-09-14 02:13:27.000', 8, 8.8, 800) ('2020-09-14 02:13:28.000', 9, 9.9, 900) ('2020-09-14 02:13:29.000', 10, 11.0, 1000); +Insert OK, 10 row(s) affected + +taos> insert into ctb2 values ('2020-09-14 02:13:30.000', 100, 10.1, 1000) ('2020-09-14 02:13:31.000', 200, 20.2, 2000) ('2020-09-14 02:13:32.000', 300, 30.3, 3000); +Insert OK, 3 row(s) affected + +taos> insert into ctb_null values ('2020-09-14 02:13:20.000', NULL, NULL, NULL) ('2020-09-14 02:13:21.000', 1, 1.1, 100) ('2020-09-14 02:13:22.000', NULL, NULL, NULL) ('2020-09-14 02:13:23.000', 2, 2.2, 200) ('2020-09-14 02:13:24.000', 3, 3.3, 300); +Insert OK, 5 row(s) affected + +taos> insert into ctb_single values ('2020-09-14 02:14:00.000', 42, 4.2, 420); +Insert OK, 1 row(s) affected + +taos> select first(c1), diff(c1) from test_indef_sel.ctb1; + +taos> select first(c1), derivative(c1, 1s, 0) from test_indef_sel.ctb1; + +taos> select first(c1), csum(c1) from test_indef_sel.ctb1; + first(c1) | csum(c1) | +====================================== + 1 | 1 | + +taos> select first(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb1; + first(c1) | statecount(c1, 'GE', 0) | +======================================== + 1 | 1 | + +taos> select first(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb1; + first(c1) | stateduration(c1, 'GE', 0, 1s) | +=============================================== + 1 | 0 | + +taos> select first(c1), lag(c1, 1) from test_indef_sel.ctb1; + first(c1) | lag(c1, 1) | +============================ + 1 | NULL | + +taos> select first(c1), lead(c1, 1) from test_indef_sel.ctb1; + first(c1) | lead(c1, 1) | +============================ + 1 | NULL | + +taos> select first(c1), fill_forward(c1) from test_indef_sel.ctb1; + first(c1) | fill_forward(c1) | +================================= + 1 | 1 | + +taos> select first(c1), tail(c1, 3) from test_indef_sel.ctb1; + first(c1) | tail(c1, 3) | +============================ + 1 | 1 | + +taos> select first(c1), unique(c1) from test_indef_sel.ctb1; + first(c1) | unique(c1) | +============================ + 1 | 1 | + +taos> select last(c1), diff(c1) from test_indef_sel.ctb1; + +taos> select last(c1), derivative(c1, 1s, 0) from test_indef_sel.ctb1; + +taos> select last(c1), csum(c1) from test_indef_sel.ctb1; + last(c1) | csum(c1) | +====================================== + 10 | 10 | + +taos> select last(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb1; + last(c1) | statecount(c1, 'GE', 0) | +======================================== + 10 | 1 | + +taos> select last(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb1; + last(c1) | stateduration(c1, 'GE', 0, 1s) | +=============================================== + 10 | 0 | + +taos> select last(c1), lag(c1, 1) from test_indef_sel.ctb1; + last(c1) | lag(c1, 1) | +============================ + 10 | NULL | + +taos> select last(c1), lead(c1, 1) from test_indef_sel.ctb1; + last(c1) | lead(c1, 1) | +============================ + 10 | NULL | + +taos> select last(c1), fill_forward(c1) from test_indef_sel.ctb1; + last(c1) | fill_forward(c1) | +================================= + 10 | 10 | + +taos> select last(c1), tail(c1, 3) from test_indef_sel.ctb1; + last(c1) | tail(c1, 3) | +============================ + 10 | 10 | + +taos> select last(c1), unique(c1) from test_indef_sel.ctb1; + last(c1) | unique(c1) | +============================ + 10 | 10 | + +taos> select min(c1), diff(c1) from test_indef_sel.ctb1; + +taos> select min(c1), derivative(c1, 1s, 0) from test_indef_sel.ctb1; + +taos> select min(c1), csum(c1) from test_indef_sel.ctb1; + min(c1) | csum(c1) | +====================================== + 1 | 1 | + +taos> select min(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb1; + min(c1) | statecount(c1, 'GE', 0) | +======================================== + 1 | 1 | + +taos> select min(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb1; + min(c1) | stateduration(c1, 'GE', 0, 1s) | +=============================================== + 1 | 0 | + +taos> select min(c1), lag(c1, 1) from test_indef_sel.ctb1; + min(c1) | lag(c1, 1) | +============================ + 1 | NULL | + +taos> select min(c1), lead(c1, 1) from test_indef_sel.ctb1; + min(c1) | lead(c1, 1) | +============================ + 1 | NULL | + +taos> select min(c1), fill_forward(c1) from test_indef_sel.ctb1; + min(c1) | fill_forward(c1) | +================================= + 1 | 1 | + +taos> select min(c1), tail(c1, 3) from test_indef_sel.ctb1; + min(c1) | tail(c1, 3) | +============================ + 1 | 1 | + +taos> select min(c1), unique(c1) from test_indef_sel.ctb1; + min(c1) | unique(c1) | +============================ + 1 | 1 | + +taos> select max(c1), diff(c1) from test_indef_sel.ctb1; + +taos> select max(c1), derivative(c1, 1s, 0) from test_indef_sel.ctb1; + +taos> select max(c1), csum(c1) from test_indef_sel.ctb1; + max(c1) | csum(c1) | +====================================== + 10 | 10 | + +taos> select max(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb1; + max(c1) | statecount(c1, 'GE', 0) | +======================================== + 10 | 1 | + +taos> select max(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb1; + max(c1) | stateduration(c1, 'GE', 0, 1s) | +=============================================== + 10 | 0 | + +taos> select max(c1), lag(c1, 1) from test_indef_sel.ctb1; + max(c1) | lag(c1, 1) | +============================ + 10 | NULL | + +taos> select max(c1), lead(c1, 1) from test_indef_sel.ctb1; + max(c1) | lead(c1, 1) | +============================ + 10 | NULL | + +taos> select max(c1), fill_forward(c1) from test_indef_sel.ctb1; + max(c1) | fill_forward(c1) | +================================= + 10 | 10 | + +taos> select max(c1), tail(c1, 3) from test_indef_sel.ctb1; + max(c1) | tail(c1, 3) | +============================ + 10 | 10 | + +taos> select max(c1), unique(c1) from test_indef_sel.ctb1; + max(c1) | unique(c1) | +============================ + 10 | 10 | + +taos> select mode(c1), diff(c1) from test_indef_sel.ctb1; + +taos> select mode(c1), derivative(c1, 1s, 0) from test_indef_sel.ctb1; + +taos> select mode(c1), csum(c1) from test_indef_sel.ctb1; + mode(c1) | csum(c1) | +====================================== + 9 | 9 | + +taos> select mode(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb1; + mode(c1) | statecount(c1, 'GE', 0) | +======================================== + 9 | 1 | + +taos> select mode(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb1; + mode(c1) | stateduration(c1, 'GE', 0, 1s) | +=============================================== + 9 | 0 | + +taos> select mode(c1), lag(c1, 1) from test_indef_sel.ctb1; + mode(c1) | lag(c1, 1) | +============================ + 9 | NULL | + +taos> select mode(c1), lead(c1, 1) from test_indef_sel.ctb1; + mode(c1) | lead(c1, 1) | +============================ + 9 | NULL | + +taos> select mode(c1), fill_forward(c1) from test_indef_sel.ctb1; + mode(c1) | fill_forward(c1) | +================================= + 9 | 9 | + +taos> select mode(c1), tail(c1, 3) from test_indef_sel.ctb1; + mode(c1) | tail(c1, 3) | +============================ + 9 | 9 | + +taos> select mode(c1), unique(c1) from test_indef_sel.ctb1; + mode(c1) | unique(c1) | +============================ + 9 | 9 | + +taos> select last_row(c1), diff(c1) from test_indef_sel.ctb1; + +taos> select last_row(c1), derivative(c1, 1s, 0) from test_indef_sel.ctb1; + +taos> select last_row(c1), csum(c1) from test_indef_sel.ctb1; + last_row(c1) | csum(c1) | +======================================= + 10 | 10 | + +taos> select last_row(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb1; + last_row(c1) | statecount(c1, 'GE', 0) | +========================================= + 10 | 1 | + +taos> select last_row(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb1; + last_row(c1) | stateduration(c1, 'GE', 0, 1s) | +================================================ + 10 | 0 | + +taos> select last_row(c1), lag(c1, 1) from test_indef_sel.ctb1; + last_row(c1) | lag(c1, 1) | +============================= + 10 | NULL | + +taos> select last_row(c1), lead(c1, 1) from test_indef_sel.ctb1; + last_row(c1) | lead(c1, 1) | +============================= + 10 | NULL | + +taos> select last_row(c1), fill_forward(c1) from test_indef_sel.ctb1; + last_row(c1) | fill_forward(c1) | +================================== + 10 | 10 | + +taos> select last_row(c1), tail(c1, 3) from test_indef_sel.ctb1; + last_row(c1) | tail(c1, 3) | +============================= + 10 | 10 | + +taos> select last_row(c1), unique(c1) from test_indef_sel.ctb1; + last_row(c1) | unique(c1) | +============================= + 10 | 10 | + +taos> select diff(c1), first(c1) from test_indef_sel.ctb1; + +taos> select csum(c1), last(c1) from test_indef_sel.ctb1; + csum(c1) | last(c1) | +====================================== + 10 | 10 | + +taos> select fill_forward(c1), min(c1) from test_indef_sel.ctb1; + fill_forward(c1) | min(c1) | +================================= + 1 | 1 | + +taos> select lag(c1, 1), max(c1) from test_indef_sel.ctb1; + lag(c1, 1) | max(c1) | +============================ + NULL | 10 | + +taos> select lead(c1, 1), mode(c1) from test_indef_sel.ctb1; + lead(c1, 1) | mode(c1) | +============================ + NULL | 9 | + +taos> select first(c1), diff(c1) from test_indef_sel.ctb1 where ts >= '2020-09-14 02:13:20' and ts < '2020-09-14 02:13:25'; + +taos> select first(c1), csum(c1) from test_indef_sel.ctb1 where ts >= '2020-09-14 02:13:20' and ts < '2020-09-14 02:13:25'; + first(c1) | csum(c1) | +====================================== + 1 | 1 | + +taos> select first(c1), fill_forward(c1) from test_indef_sel.ctb1 where ts >= '2020-09-14 02:13:20' and ts < '2020-09-14 02:13:25'; + first(c1) | fill_forward(c1) | +================================= + 1 | 1 | + +taos> select first(c1), diff(c1) from test_indef_sel.ctb_empty; + +taos> select first(c1), csum(c1) from test_indef_sel.ctb_empty; + +taos> select last(c1), fill_forward(c1) from test_indef_sel.ctb_empty; + +taos> select first(c1), diff(c1) from test_indef_sel.ctb_single; + +taos> select first(c1), csum(c1) from test_indef_sel.ctb_single; + first(c1) | csum(c1) | +====================================== + 42 | 42 | + +taos> select first(c1), lag(c1, 1) from test_indef_sel.ctb_single; + first(c1) | lag(c1, 1) | +============================ + 42 | NULL | + +taos> select first(c1), lead(c1, 1) from test_indef_sel.ctb_single; + first(c1) | lead(c1, 1) | +============================ + 42 | NULL | + +taos> select first(c1), fill_forward(c1) from test_indef_sel.ctb_single; + first(c1) | fill_forward(c1) | +================================= + 42 | 42 | + +taos> select first(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb_single; + first(c1) | statecount(c1, 'GE', 0) | +======================================== + 42 | 1 | + +taos> select first(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb_single; + first(c1) | stateduration(c1, 'GE', 0, 1s) | +=============================================== + 42 | 0 | + +taos> select first(c1), unique(c1) from test_indef_sel.ctb_single; + first(c1) | unique(c1) | +============================ + 42 | 42 | + +taos> select first(c1), tail(c1, 1) from test_indef_sel.ctb_single; + first(c1) | tail(c1, 1) | +============================ + 42 | 42 | + +taos> select first(c1), diff(c1) from test_indef_sel.ctb_null; + +taos> select first(c1), csum(c1) from test_indef_sel.ctb_null; + first(c1) | csum(c1) | +====================================== + 1 | 1 | + +taos> select first(c1), fill_forward(c1) from test_indef_sel.ctb_null; + first(c1) | fill_forward(c1) | +================================= + 1 | 1 | + +taos> select first(c1), lag(c1, 1) from test_indef_sel.ctb_null; + first(c1) | lag(c1, 1) | +============================ + 1 | NULL | + +taos> select first(c1), lead(c1, 1) from test_indef_sel.ctb_null; + first(c1) | lead(c1, 1) | +============================ + 1 | NULL | + +taos> select first(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb_null; + first(c1) | statecount(c1, 'GE', 0) | +======================================== + 1 | 1 | + +taos> select first(c1), unique(c1) from test_indef_sel.ctb_null; + first(c1) | unique(c1) | +============================ + 1 | 1 | + +taos> select first(c1), tail(c1, 3) from test_indef_sel.ctb_null; + first(c1) | tail(c1, 3) | +============================ + 1 | 1 | + +taos> select first(c1), diff(c1) from test_indef_sel.ctb_null where c1 is not null; + +taos> select first(c1), csum(c1) from test_indef_sel.ctb1 where c1 > 5; + first(c1) | csum(c1) | +====================================== + 6 | 6 | + +taos> select min(c1), diff(c1) from test_indef_sel.ctb1 where c1 between 3 and 7; + +taos> select max(c1), csum(c1) from test_indef_sel.ctb1 where c1 is not null; + max(c1) | csum(c1) | +====================================== + 10 | 10 | + +taos> select first(c2), diff(c2) from test_indef_sel.ctb1; + +taos> select last(c3), csum(c3) from test_indef_sel.ctb1; + last(c3) | csum(c3) | +================================================ + 1000 | 1000 | + +taos> select min(c2), fill_forward(c2) from test_indef_sel.ctb1; + min(c2) | fill_forward(c2) | +============================================== + 1.1 | 1.1 | + +taos> select max(c3), lag(c3, 1) from test_indef_sel.ctb1; + max(c3) | lag(c3, 1) | +================================================ + 1000 | NULL | + +taos> select first(c1), lag(c1, 1), lead(c1, 1) from test_indef_sel.ctb1; + first(c1) | lag(c1, 1) | lead(c1, 1) | +========================================== + 1 | NULL | NULL | + +taos> select * from (select first(c1) as fc, csum(c1) as cs from test_indef_sel.ctb1); + fc | cs | +====================================== + 1 | 1 | + +taos> select * from (select first(c1) as fc, diff(c1) as df from test_indef_sel.ctb1); + +taos> select * from (select first(c1) as fc, fill_forward(c1) as ff from test_indef_sel.ctb1); + fc | ff | +============================ + 1 | 1 | + +taos> select * from (select first(c1) as fc, csum(c1) as cs from test_indef_sel.ctb1) where cs > 0; + fc | cs | +====================================== + 1 | 1 | + diff --git a/test/cases/11-Functions/04-Timeseries/in/test_fun_ts_indef_with_select.in b/test/cases/11-Functions/04-Timeseries/in/test_fun_ts_indef_with_select.in new file mode 100644 index 000000000000..3fddb1287152 --- /dev/null +++ b/test/cases/11-Functions/04-Timeseries/in/test_fun_ts_indef_with_select.in @@ -0,0 +1,114 @@ +drop database if exists test_indef_sel; +create database test_indef_sel; +use test_indef_sel; +create table stb (ts timestamp, c1 int, c2 float, c3 bigint) tags (t1 int); +create table ctb1 using stb tags(1); +create table ctb2 using stb tags(2); +create table ctb_empty using stb tags(3); +create table ctb_null using stb tags(4); +create table ctb_single using stb tags(5); +insert into ctb1 values ('2020-09-14 02:13:20.000', 1, 1.1, 100) ('2020-09-14 02:13:21.000', 2, 2.2, 200) ('2020-09-14 02:13:22.000', 3, 3.3, 300) ('2020-09-14 02:13:23.000', 4, 4.4, 400) ('2020-09-14 02:13:24.000', 5, 5.5, 500) ('2020-09-14 02:13:25.000', 6, 6.6, 600) ('2020-09-14 02:13:26.000', 7, 7.7, 700) ('2020-09-14 02:13:27.000', 8, 8.8, 800) ('2020-09-14 02:13:28.000', 9, 9.9, 900) ('2020-09-14 02:13:29.000', 10, 11.0, 1000); +insert into ctb2 values ('2020-09-14 02:13:30.000', 100, 10.1, 1000) ('2020-09-14 02:13:31.000', 200, 20.2, 2000) ('2020-09-14 02:13:32.000', 300, 30.3, 3000); +insert into ctb_null values ('2020-09-14 02:13:20.000', NULL, NULL, NULL) ('2020-09-14 02:13:21.000', 1, 1.1, 100) ('2020-09-14 02:13:22.000', NULL, NULL, NULL) ('2020-09-14 02:13:23.000', 2, 2.2, 200) ('2020-09-14 02:13:24.000', 3, 3.3, 300); +insert into ctb_single values ('2020-09-14 02:14:00.000', 42, 4.2, 420); +select first(c1), diff(c1) from test_indef_sel.ctb1; +select first(c1), derivative(c1, 1s, 0) from test_indef_sel.ctb1; +select first(c1), csum(c1) from test_indef_sel.ctb1; +select first(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb1; +select first(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb1; +select first(c1), lag(c1, 1) from test_indef_sel.ctb1; +select first(c1), lead(c1, 1) from test_indef_sel.ctb1; +select first(c1), fill_forward(c1) from test_indef_sel.ctb1; +select first(c1), tail(c1, 3) from test_indef_sel.ctb1; +select first(c1), unique(c1) from test_indef_sel.ctb1; +select last(c1), diff(c1) from test_indef_sel.ctb1; +select last(c1), derivative(c1, 1s, 0) from test_indef_sel.ctb1; +select last(c1), csum(c1) from test_indef_sel.ctb1; +select last(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb1; +select last(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb1; +select last(c1), lag(c1, 1) from test_indef_sel.ctb1; +select last(c1), lead(c1, 1) from test_indef_sel.ctb1; +select last(c1), fill_forward(c1) from test_indef_sel.ctb1; +select last(c1), tail(c1, 3) from test_indef_sel.ctb1; +select last(c1), unique(c1) from test_indef_sel.ctb1; +select min(c1), diff(c1) from test_indef_sel.ctb1; +select min(c1), derivative(c1, 1s, 0) from test_indef_sel.ctb1; +select min(c1), csum(c1) from test_indef_sel.ctb1; +select min(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb1; +select min(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb1; +select min(c1), lag(c1, 1) from test_indef_sel.ctb1; +select min(c1), lead(c1, 1) from test_indef_sel.ctb1; +select min(c1), fill_forward(c1) from test_indef_sel.ctb1; +select min(c1), tail(c1, 3) from test_indef_sel.ctb1; +select min(c1), unique(c1) from test_indef_sel.ctb1; +select max(c1), diff(c1) from test_indef_sel.ctb1; +select max(c1), derivative(c1, 1s, 0) from test_indef_sel.ctb1; +select max(c1), csum(c1) from test_indef_sel.ctb1; +select max(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb1; +select max(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb1; +select max(c1), lag(c1, 1) from test_indef_sel.ctb1; +select max(c1), lead(c1, 1) from test_indef_sel.ctb1; +select max(c1), fill_forward(c1) from test_indef_sel.ctb1; +select max(c1), tail(c1, 3) from test_indef_sel.ctb1; +select max(c1), unique(c1) from test_indef_sel.ctb1; +select mode(c1), diff(c1) from test_indef_sel.ctb1; +select mode(c1), derivative(c1, 1s, 0) from test_indef_sel.ctb1; +select mode(c1), csum(c1) from test_indef_sel.ctb1; +select mode(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb1; +select mode(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb1; +select mode(c1), lag(c1, 1) from test_indef_sel.ctb1; +select mode(c1), lead(c1, 1) from test_indef_sel.ctb1; +select mode(c1), fill_forward(c1) from test_indef_sel.ctb1; +select mode(c1), tail(c1, 3) from test_indef_sel.ctb1; +select mode(c1), unique(c1) from test_indef_sel.ctb1; +select last_row(c1), diff(c1) from test_indef_sel.ctb1; +select last_row(c1), derivative(c1, 1s, 0) from test_indef_sel.ctb1; +select last_row(c1), csum(c1) from test_indef_sel.ctb1; +select last_row(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb1; +select last_row(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb1; +select last_row(c1), lag(c1, 1) from test_indef_sel.ctb1; +select last_row(c1), lead(c1, 1) from test_indef_sel.ctb1; +select last_row(c1), fill_forward(c1) from test_indef_sel.ctb1; +select last_row(c1), tail(c1, 3) from test_indef_sel.ctb1; +select last_row(c1), unique(c1) from test_indef_sel.ctb1; +select diff(c1), first(c1) from test_indef_sel.ctb1; +select csum(c1), last(c1) from test_indef_sel.ctb1; +select fill_forward(c1), min(c1) from test_indef_sel.ctb1; +select lag(c1, 1), max(c1) from test_indef_sel.ctb1; +select lead(c1, 1), mode(c1) from test_indef_sel.ctb1; +select first(c1), diff(c1) from test_indef_sel.ctb1 where ts >= '2020-09-14 02:13:20' and ts < '2020-09-14 02:13:25'; +select first(c1), csum(c1) from test_indef_sel.ctb1 where ts >= '2020-09-14 02:13:20' and ts < '2020-09-14 02:13:25'; +select first(c1), fill_forward(c1) from test_indef_sel.ctb1 where ts >= '2020-09-14 02:13:20' and ts < '2020-09-14 02:13:25'; +select first(c1), diff(c1) from test_indef_sel.ctb_empty; +select first(c1), csum(c1) from test_indef_sel.ctb_empty; +select last(c1), fill_forward(c1) from test_indef_sel.ctb_empty; +select first(c1), diff(c1) from test_indef_sel.ctb_single; +select first(c1), csum(c1) from test_indef_sel.ctb_single; +select first(c1), lag(c1, 1) from test_indef_sel.ctb_single; +select first(c1), lead(c1, 1) from test_indef_sel.ctb_single; +select first(c1), fill_forward(c1) from test_indef_sel.ctb_single; +select first(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb_single; +select first(c1), stateduration(c1, 'GE', 0, 1s) from test_indef_sel.ctb_single; +select first(c1), unique(c1) from test_indef_sel.ctb_single; +select first(c1), tail(c1, 1) from test_indef_sel.ctb_single; +select first(c1), diff(c1) from test_indef_sel.ctb_null; +select first(c1), csum(c1) from test_indef_sel.ctb_null; +select first(c1), fill_forward(c1) from test_indef_sel.ctb_null; +select first(c1), lag(c1, 1) from test_indef_sel.ctb_null; +select first(c1), lead(c1, 1) from test_indef_sel.ctb_null; +select first(c1), statecount(c1, 'GE', 0) from test_indef_sel.ctb_null; +select first(c1), unique(c1) from test_indef_sel.ctb_null; +select first(c1), tail(c1, 3) from test_indef_sel.ctb_null; +select first(c1), diff(c1) from test_indef_sel.ctb_null where c1 is not null; +select first(c1), csum(c1) from test_indef_sel.ctb1 where c1 > 5; +select min(c1), diff(c1) from test_indef_sel.ctb1 where c1 between 3 and 7; +select max(c1), csum(c1) from test_indef_sel.ctb1 where c1 is not null; +select first(c2), diff(c2) from test_indef_sel.ctb1; +select last(c3), csum(c3) from test_indef_sel.ctb1; +select min(c2), fill_forward(c2) from test_indef_sel.ctb1; +select max(c3), lag(c3, 1) from test_indef_sel.ctb1; +select first(c1), lag(c1, 1), lead(c1, 1) from test_indef_sel.ctb1; +select * from (select first(c1) as fc, csum(c1) as cs from test_indef_sel.ctb1); +select * from (select first(c1) as fc, diff(c1) as df from test_indef_sel.ctb1); +select * from (select first(c1) as fc, fill_forward(c1) as ff from test_indef_sel.ctb1); +select * from (select first(c1) as fc, csum(c1) as cs from test_indef_sel.ctb1) where cs > 0; diff --git a/test/cases/11-Functions/04-Timeseries/test_fun_ts_csum.py b/test/cases/11-Functions/04-Timeseries/test_fun_ts_csum.py index c403bf579c98..3f38f2b73c34 100644 --- a/test/cases/11-Functions/04-Timeseries/test_fun_ts_csum.py +++ b/test/cases/11-Functions/04-Timeseries/test_fun_ts_csum.py @@ -280,7 +280,7 @@ def csum_error_query(self) -> None : # tdSql.error(self.csum_query_form(col="c1, 2")) # sql form error 3 tdSql.error(self.csum_query_form(alias=", count(c1)")) # mix with aggregate function 1 tdSql.error(self.csum_query_form(alias=", avg(c1)")) # mix with aggregate function 2 - tdSql.error(self.csum_query_form(alias=", min(c1)")) # mix with select function 1 + tdSql.query(self.csum_query_form(alias=", min(c1)")) # indef+select now allowed tdSql.error(self.csum_query_form(alias=", top(c1, 5)")) # mix with select function 2 tdSql.error(self.csum_query_form(alias=", spread(c1)")) # mix with calculation function 1 tdSql.error(self.csum_query_form(alias=", diff(c1)")) # mix with calculation function 2 diff --git a/test/cases/11-Functions/04-Timeseries/test_fun_ts_diff.py b/test/cases/11-Functions/04-Timeseries/test_fun_ts_diff.py index 5c02b1a2ba12..0c3f75d7f012 100644 --- a/test/cases/11-Functions/04-Timeseries/test_fun_ts_diff.py +++ b/test/cases/11-Functions/04-Timeseries/test_fun_ts_diff.py @@ -1229,8 +1229,8 @@ def diff_error_query(self) -> None : # tdSql.error(self.diff_query_form(col="c1, 2")) # sql form error 3 tdSql.error(self.diff_query_form(alias=", count(c1)")) # mix with aggregate function 1 tdSql.error(self.diff_query_form(alias=", avg(c1)")) # mix with aggregate function 2 - tdSql.error(self.diff_query_form(alias=", min(c1)")) # mix with select function 1 - tdSql.error(self.diff_query_form(alias=", top(c1, 5)")) # mix with select function 2 + tdSql.query(self.diff_query_form(alias=", min(c1)")) # mix with select function 1 (now allowed) + tdSql.error(self.diff_query_form(alias=", top(c1, 5)")) # mix with select function 2 (top is multi-rows, still blocked) tdSql.error(self.diff_query_form(alias=", spread(c1)")) # mix with calculation function 1 tdSql.query(self.diff_query_form(alias=", diff(c1)")) # mix with calculation function 2 # tdSql.error(self.diff_query_form(alias=" + 2")) # mix with arithmetic 1 diff --git a/test/cases/11-Functions/04-Timeseries/test_fun_ts_indef_with_select.py b/test/cases/11-Functions/04-Timeseries/test_fun_ts_indef_with_select.py new file mode 100644 index 000000000000..35cb7d1f1359 --- /dev/null +++ b/test/cases/11-Functions/04-Timeseries/test_fun_ts_indef_with_select.py @@ -0,0 +1,125 @@ +################################################################### +# Copyright (c) 2016 by TAOS Technologies, Inc. +# All rights reserved. +# +# This file is proprietary and confidential to TAOS Technologies. +# No part of this file may be reproduced, stored, transmitted, +# disclosed or used in any form or by any means other than as +# expressly provided by the written permission from Jianhui Tao +# +################################################################### + +# -*- coding: utf-8 -*- + +import os +from new_test_framework.utils import tdLog, tdSql, tdCom + + +class TestFunTsIndefWithSelect: + """Test indefinite-row functions used together with selection functions.""" + + db = "test_indef_sel" + + def setup_class(cls): + tdLog.debug(f"start to execute {__file__}") + # Prevent ASAN ODR violation when taos client is invoked via os.system() + os.environ.setdefault("ASAN_OPTIONS", "detect_odr_violation=0") + + def teardown_class(cls): + tdSql.execute("drop database if exists test_indef_sel") + + def test_indef_with_select(self): + """Indefinite-row functions mixed with selection functions. + + Indefinite-row functions execute after selection functions. + The selection function reduces to a single row first, so the + indefinite-row function operates on just that one row. + + Catalog: + - Function:Timeseries + + Since: v3.3.6.0 + + Labels: common,ci + + Jira: None + + History: + - 2026-04-21 Created + + """ + + self.db = "test_indef_sel" + + # positive cases via file comparison (data setup included in .in file) + sqlFile = os.path.join(os.path.dirname(__file__), + "in", "test_fun_ts_indef_with_select.in") + ansFile = os.path.join(os.path.dirname(__file__), + "ans", "test_fun_ts_indef_with_select.ans") + tdCom.compare_testcase_result(sqlFile, ansFile, + "test_fun_ts_indef_with_select") + + # negative: non-select agg funcs still blocked with indef-row funcs + table = f"{self.db}.ctb1" + for agg in ["count", "sum", "avg"]: + for func in ["diff(c1)", "csum(c1)"]: + tdSql.error(f"select {agg}(c1), {func} from {table}") + + # negative: top/bottom/sample (MULTI_ROWS) + indef should error + for multi in ["top(c1, 3)", "bottom(c1, 3)", "sample(c1, 3)"]: + for func in ["diff(c1)", "csum(c1)", "fill_forward(c1)"]: + tdSql.error(f"select {multi}, {func} from {table}") + + # negative: apercentile (AGG only, no SELECT flag) + indef should error + for func in ["diff(c1)", "csum(c1)"]: + tdSql.error(f"select apercentile(c1, 50), {func} from {table}") + + # negative: indef functions with different return row counts should error + tdSql.error(f"select diff(c1), csum(c1) from {table}") + tdSql.error(f"select diff(c1), fill_forward(c1) from {table}") + + # negative: group by + select + indef should error + tdSql.error(f"select first(c1), diff(c1) from {self.db}.stb group by tbname") + + # negative: multiple selection funcs + indef-row func should error + # With multiple selection funcs the column value is ambiguous for indef + for indef in ["diff(c1)", "csum(c1)", "fill_forward(c1)", "lag(c1,1)", + "lead(c1,1)", "statecount(c1,'GE',0)", "stateduration(c1,'GE',0,1s)", + "unique(c1)", "tail(c1,3)", "mavg(c1,2)"]: + tdSql.error(f"select first(c1), last(c1), {indef} from {table}") + # two select funcs of same type + tdSql.error(f"select first(c1), first(c2), diff(c1) from {table}") + # three+ select funcs + tdSql.error(f"select first(c1), last(c1), min(c1), diff(c1) from {table}") + tdSql.error(f"select first(c1), last(c1), min(c1), max(c1), csum(c1) from {table}") + # mixed select func types with various indef funcs + tdSql.error(f"select first(c1), mode(c1), lag(c1,1) from {table}") + tdSql.error(f"select first(c1), last_row(c1), unique(c1) from {table}") + tdSql.error(f"select first(c1), max(c3), fill_forward(c1), fill_forward(c3) from {table}") + + # partition by tbname: non-deterministic ordering, check row counts + stb = f"{self.db}.stb" + tdSql.query(f"select first(c1), csum(c1) from {stb} partition by tbname") + # ctb1(1 row) + ctb2(1 row) + ctb_null(1 row) + ctb_single(1 row) = 4 + assert tdSql.queryRows == 4, f"expected 4 rows, got {tdSql.queryRows}" + + tdSql.query(f"select first(c1), fill_forward(c1) from {stb} partition by tbname") + assert tdSql.queryRows == 4, f"expected 4 rows, got {tdSql.queryRows}" + + tdSql.query(f"select first(c1), lag(c1, 1) from {stb} partition by tbname") + assert tdSql.queryRows == 4, f"expected 4 rows, got {tdSql.queryRows}" + + # union all: non-deterministic ordering, check row counts + tdSql.query(f"select first(c1), csum(c1) from {self.db}.ctb1 union all select first(c1), csum(c1) from {self.db}.ctb2") + assert tdSql.queryRows == 2, f"expected 2 rows, got {tdSql.queryRows}" + + # supertable without partition by: check runs without error + tdSql.query(f"select first(c1), csum(c1) from {stb}") + assert tdSql.queryRows == 1, f"expected 1 row, got {tdSql.queryRows}" + tdSql.query(f"select first(c1), fill_forward(c1) from {stb}") + assert tdSql.queryRows == 1, f"expected 1 row, got {tdSql.queryRows}" + + # mavg on single row returns non-deterministic value (pre-existing), + # so verify it runs without error rather than comparing output + for sel in ["first", "last", "min", "max", "mode", "last_row"]: + tdSql.query(f"select {sel}(c1), mavg(c1, 2) from {table}") diff --git a/test/cases/11-Functions/04-Timeseries/test_fun_ts_mavg.py b/test/cases/11-Functions/04-Timeseries/test_fun_ts_mavg.py index 433c07772692..f6de0e3a5f7c 100644 --- a/test/cases/11-Functions/04-Timeseries/test_fun_ts_mavg.py +++ b/test/cases/11-Functions/04-Timeseries/test_fun_ts_mavg.py @@ -473,8 +473,8 @@ def mavg_error_query(self, dbname="db") -> None : self.checkmavg(**err34) # mix with aggregate function 1 err35 = {"alias": ", avg(c1)"} self.checkmavg(**err35) # mix with aggregate function 2 - err36 = {"alias": ", min(c1)"} - self.checkmavg(**err36) # mix with select function 1 + # err36 = {"alias": ", min(c1)"} + # self.checkmavg(**err36) # mix with select function 1 err37 = {"alias": ", top(c1, 5)"} self.checkmavg(**err37) # mix with select function 2 err38 = {"alias": ", spread(c1)"} diff --git a/test/cases/11-Functions/04-Timeseries/test_fun_ts_statecount.py b/test/cases/11-Functions/04-Timeseries/test_fun_ts_statecount.py index 42f7547f3b6e..3aa22e0dfab4 100644 --- a/test/cases/11-Functions/04-Timeseries/test_fun_ts_statecount.py +++ b/test/cases/11-Functions/04-Timeseries/test_fun_ts_statecount.py @@ -94,7 +94,7 @@ def check_errors(self, dbname="db"): f"select statecount(c1 ,'GT',1,True) from {dbname}.t1", f"select statecount(c1 ,'GT',1) , count(c1) from {dbname}.t1", f"select statecount(c1 ,'GT',1) , avg(c1) from {dbname}.t1", - f"select statecount(c1 ,'GT',1) , min(c1) from {dbname}.t1", + # f"select statecount(c1 ,'GT',1) , min(c1) from {dbname}.t1", # indef+select now allowed f"select statecount(c1 ,'GT',1) , spread(c1) from {dbname}.t1", f"select statecount(c1 ,'GT',1) , diff(c1) from {dbname}.t1", f"select statecount(c1 ,'GTA',1) , diff(c1) from {dbname}.t1", @@ -273,7 +273,7 @@ def basic_statecount_function(self, dbname="db"): # unique with aggregate function tdSql.error(f"select statecount(c6,'GT',1) ,sum(c1) from {dbname}.ct1") - tdSql.error(f"select statecount(c6,'GT',1) ,max(c1) from {dbname}.ct1") + tdSql.query(f"select statecount(c6,'GT',1) ,max(c1) from {dbname}.ct1") # indef+select now allowed tdSql.error(f"select statecount(c6,'GT',1) ,csum(c1) from {dbname}.ct1") tdSql.error(f"select statecount(c6,'GT',1) ,count(c1) from {dbname}.ct1") diff --git a/test/cases/11-Functions/04-Timeseries/test_fun_ts_stateduration.py b/test/cases/11-Functions/04-Timeseries/test_fun_ts_stateduration.py index 522832c0d6e0..a8bdd53ae510 100644 --- a/test/cases/11-Functions/04-Timeseries/test_fun_ts_stateduration.py +++ b/test/cases/11-Functions/04-Timeseries/test_fun_ts_stateduration.py @@ -424,7 +424,7 @@ def check_errors(self, dbname=DBNAME): f"select stateduration(c1 ,'GT',1,True) from {dbname}.t1", f"select stateduration(c1 ,'GT',1,1s) , count(c1) from {dbname}.t1", f"select stateduration(c1 ,'GT',1,1s) , avg(c1) from {dbname}.t1", - f"select stateduration(c1 ,'GT',1,1s) , min(c1) from {dbname}.t1", + # f"select stateduration(c1 ,'GT',1,1s) , min(c1) from {dbname}.t1", # indef+select now allowed f"select stateduration(c1 ,'GT',1,1s) , spread(c1) from {dbname}.t1", f"select stateduration(c1 ,'GT',1,1s) , diff(c1) from {dbname}.t1", ] @@ -568,7 +568,7 @@ def basic_stateduration_function(self, dbname=DBNAME): # unique with aggregate function tdSql.error(f"select stateduration(c6,'GT',1,1s) ,sum(c1) from {dbname}.ct1") - tdSql.error(f"select stateduration(c6,'GT',1,1s) ,max(c1) from {dbname}.ct1") + tdSql.query(f"select stateduration(c6,'GT',1,1s) ,max(c1) from {dbname}.ct1") # indef+select now allowed tdSql.error(f"select stateduration(c6,'GT',1,1s) ,csum(c1) from {dbname}.ct1") tdSql.error(f"select stateduration(c6,'GT',1,1s) ,count(c1) from {dbname}.ct1")