From 6c656715691ddcb0f4842db4bbdd3b2da65e6683 Mon Sep 17 00:00:00 2001 From: yanyuxing Date: Tue, 19 Nov 2024 17:26:13 +0800 Subject: [PATCH 01/31] docs(datain): support mqtt/kafka payload decompression and string decoding --- docs/zh/06-advanced/05-data-in/07-mqtt.md | 4 ++++ docs/zh/06-advanced/05-data-in/08-kafka.md | 2 ++ docs/zh/06-advanced/05-data-in/kafka-06.png | Bin 49581 -> 52184 bytes docs/zh/06-advanced/05-data-in/mqtt-05.png | Bin 106373 -> 54363 bytes 4 files changed, 6 insertions(+) diff --git a/docs/zh/06-advanced/05-data-in/07-mqtt.md b/docs/zh/06-advanced/05-data-in/07-mqtt.md index 8fc69bcaa1..a0e121f632 100644 --- a/docs/zh/06-advanced/05-data-in/07-mqtt.md +++ b/docs/zh/06-advanced/05-data-in/07-mqtt.md @@ -65,6 +65,10 @@ TDengine 可以通过 MQTT 连接器从 MQTT 代理订阅数据并将其写入 T 在 **订阅主题及 QoS 配置** 中填写要消费的 Topic 名称和 QoS。使用如下格式设置: `{topic_name}::{qos}`(如:`my_topic::0`)。MQTT 协议 5.0 支持共享订阅,可以通过多个客户端订阅同一个 Topic 实现负载均衡,使用如下格式: `$share/{group_name}/{topic_name}::{qos}`,其中,`$share` 是固定前缀,表示启用共享订阅,`group_name` 是分组名称,类似 kafka 的消费者组。 +在 **数据压缩** 中,配置消息体压缩算法,taosX 在接收到消息后,使用对应的压缩算法对消息体进行解压缩获取原始数据。可选项 none(不压缩), gzip, snappy, lz4 和 zstd,默认为 none。 + +在 **字符编码** 中,配置消息体编码格式,taosX 在接收到消息后,使用对应的编码格式对消息体进行解码获取原始数据。可选项 UTF_8, GBK, GB18030, BIG5,默认为 UTF_8 + 点击 **检查连通性** 按钮,检查数据源是否可用。 ![mqtt-05.png](./mqtt-05.png) diff --git a/docs/zh/06-advanced/05-data-in/08-kafka.md b/docs/zh/06-advanced/05-data-in/08-kafka.md index 8cca24930e..b605f84c7a 100644 --- a/docs/zh/06-advanced/05-data-in/08-kafka.md +++ b/docs/zh/06-advanced/05-data-in/08-kafka.md @@ -113,6 +113,8 @@ kcat \ 在 **获取数据的最大时长** 中设置获取消息时等待数据不足的最长时间(以毫秒为单位),默认值为 100ms。 +在 **字符编码** 中,配置消息体编码格式,taosX 在接收到消息后,使用对应的编码格式对消息体进行解码获取原始数据。可选项 UTF_8, GBK, GB18030, BIG5,默认为 UTF_8 + 点击 **连通性检查** 按钮,检查数据源是否可用。 ![kafka-06.png](./kafka-06.png) diff --git a/docs/zh/06-advanced/05-data-in/kafka-06.png b/docs/zh/06-advanced/05-data-in/kafka-06.png index 43efe834a64d2be4eef929520f65ab20be7bdd41..19157495ae95ecf62742a29bc7d6728596daff12 100644 GIT binary patch literal 52184 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 diff --git a/docs/zh/06-advanced/05-data-in/mqtt-05.png b/docs/zh/06-advanced/05-data-in/mqtt-05.png index 2b4cfcabb33e0dedcec72ce20dbf84e51e18bd48..c43b2022ae27079731591c5564fae7f9e783eaab 100644 GIT binary patch literal 54363 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 f6}; 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 literal 106373 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 uda-IuSU;wRPda#5VWy;aN3L|3D4T%?5^sPqywS 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

2_n3Yag)iQ4vC3>f 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 Date: Thu, 21 Nov 2024 02:29:51 +0800 Subject: [PATCH 02/31] docs: fix link error in the 'Official Connectors' part --- README-CN.md | 16 ++++++++-------- README.md | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README-CN.md b/README-CN.md index 1f785eb458..162e0b8fa6 100644 --- a/README-CN.md +++ b/README-CN.md @@ -337,14 +337,14 @@ Query OK, 2 row(s) in set (0.001700s) TDengine 提供了丰富的应用程序开发接口,其中包括 C/C++、Java、Python、Go、Node.js、C# 、RESTful 等,便于用户快速开发应用: -- [Java](https://docs.taosdata.com/connector/java/) -- [C/C++](https://docs.taosdata.com/connector/cpp/) -- [Python](https://docs.taosdata.com/connector/python/) -- [Go](https://docs.taosdata.com/connector/go/) -- [Node.js](https://docs.taosdata.com/connector/node/) -- [Rust](https://docs.taosdata.com/connector/rust/) -- [C#](https://docs.taosdata.com/connector/csharp/) -- [RESTful API](https://docs.taosdata.com/connector/rest-api/) +- [Java](https://docs.taosdata.com/reference/connector/java/) +- [C/C++](https://docs.taosdata.com/reference/connector/cpp/) +- [Python](https://docs.taosdata.com/reference/connector/python/) +- [Go](https://docs.taosdata.com/reference/connector/go/) +- [Node.js](https://docs.taosdata.com/reference/connector/node/) +- [Rust](https://docs.taosdata.com/reference/connector/rust/) +- [C#](https://docs.taosdata.com/reference/connector/csharp/) +- [RESTful API](https://docs.taosdata.com/reference/connector/rest-api/) # 成为社区贡献者 diff --git a/README.md b/README.md index e390b5e764..a5943c9016 100644 --- a/README.md +++ b/README.md @@ -347,14 +347,14 @@ Query OK, 2 row(s) in set (0.001700s) TDengine provides abundant developing tools for users to develop on TDengine. Follow the links below to find your desired connectors and relevant documentation. -- [Java](https://docs.tdengine.com/reference/connector/java/) -- [C/C++](https://docs.tdengine.com/reference/connector/cpp/) -- [Python](https://docs.tdengine.com/reference/connector/python/) -- [Go](https://docs.tdengine.com/reference/connector/go/) -- [Node.js](https://docs.tdengine.com/reference/connector/node/) -- [Rust](https://docs.tdengine.com/reference/connector/rust/) -- [C#](https://docs.tdengine.com/reference/connector/csharp/) -- [RESTful API](https://docs.tdengine.com/reference/rest-api/) +- [Java](https://docs.tdengine.com/reference/connectors/java/) +- [C/C++](https://docs.tdengine.com/reference/connectors/cpp/) +- [Python](https://docs.tdengine.com/reference/connectors/python/) +- [Go](https://docs.tdengine.com/reference/connectors/go/) +- [Node.js](https://docs.tdengine.com/reference/connectors/node/) +- [Rust](https://docs.tdengine.com/reference/connectors/rust/) +- [C#](https://docs.tdengine.com/reference/connectors/csharp/) +- [RESTful API](https://docs.tdengine.com/reference/connectors/rest-api/) # Contribute to TDengine From 53fab7ef72f2355f9c85d15cd373c7c676f48eb5 Mon Sep 17 00:00:00 2001 From: Yu Chen <74105241+yu285@users.noreply.github.com> Date: Fri, 22 Nov 2024 10:16:13 +0800 Subject: [PATCH 03/31] docs/ correct the "drop database" to "drop stream" --- docs/zh/06-advanced/03-stream.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/06-advanced/03-stream.md b/docs/zh/06-advanced/03-stream.md index c26924561c..afa1a71cff 100644 --- a/docs/zh/06-advanced/03-stream.md +++ b/docs/zh/06-advanced/03-stream.md @@ -250,7 +250,7 @@ flush database stream_dest_db; ---- 流计算写入数据的超级表所在的 ```sql create stream streams1 into test1.streamst as select _wstart, count(a) c1 from test.st interval(1s) ; -drop database streams1; +drop stream streams1; flush database test; flush database test1; ``` From f37ae57a87963a5d214f471cd312710ed6d29e9d Mon Sep 17 00:00:00 2001 From: 54liuyao <54liuyao@163.com> Date: Tue, 26 Nov 2024 15:23:57 +0800 Subject: [PATCH 04/31] opt stream row buff --- source/libs/executor/inc/executorInt.h | 2 + source/libs/executor/inc/streamexecutorInt.h | 5 +- source/libs/executor/src/streamfilloperator.c | 22 ++++- .../src/streamintervalsliceoperator.c | 14 ++-- .../executor/src/streamtimesliceoperator.c | 81 ++++++++++++------- source/libs/parser/src/parTranslater.c | 8 +- 6 files changed, 88 insertions(+), 44 deletions(-) diff --git a/source/libs/executor/inc/executorInt.h b/source/libs/executor/inc/executorInt.h index 271e9c91a1..89ab8ead21 100644 --- a/source/libs/executor/inc/executorInt.h +++ b/source/libs/executor/inc/executorInt.h @@ -475,6 +475,7 @@ typedef struct SStreamFillSupporter { STimeWindow winRange; int32_t pkColBytes; __compar_fn_t comparePkColFn; + int32_t* pOffsetInfo; } SStreamFillSupporter; typedef struct SStreamScanInfo { @@ -884,6 +885,7 @@ typedef struct SStreamIntervalSliceOperatorInfo { struct SOperatorInfo* pOperator; bool hasFill; bool hasInterpoFunc; + int32_t* pOffsetInfo; } SStreamIntervalSliceOperatorInfo; #define OPTR_IS_OPENED(_optr) (((_optr)->status & OP_OPENED) == OP_OPENED) diff --git a/source/libs/executor/inc/streamexecutorInt.h b/source/libs/executor/inc/streamexecutorInt.h index 27686b0081..4bd931c567 100644 --- a/source/libs/executor/inc/streamexecutorInt.h +++ b/source/libs/executor/inc/streamexecutorInt.h @@ -90,19 +90,20 @@ int32_t saveTimeSliceWinResult(SWinKey* pKey, SSHashObj* pUpdatedMap); int winPosCmprImpl(const void* pKey1, const void* pKey2); void reuseOutputBuf(void* pState, SRowBuffPos* pPos, SStateStore* pAPI); -SResultCellData* getSliceResultCell(SResultCellData* pRowVal, int32_t index); +SResultCellData* getSliceResultCell(SResultCellData* pRowVal, int32_t index, int32_t* pCellOffsetInfo); int32_t getDownstreamRes(struct SOperatorInfo* downstream, SSDataBlock** ppRes, SColumnInfo** ppPkCol); void destroyFlusedppPos(void* ppRes); void doBuildStreamIntervalResult(struct SOperatorInfo* pOperator, void* pState, SSDataBlock* pBlock, SGroupResInfo* pGroupResInfo); void transBlockToSliceResultRow(const SSDataBlock* pBlock, int32_t rowId, TSKEY ts, SSliceRowData* pRowVal, - int32_t rowSize, void* pPkData, SColumnInfoData* pPkCol); + int32_t rowSize, void* pPkData, SColumnInfoData* pPkCol, int32_t* pCellOffsetInfo); int32_t getQualifiedRowNumDesc(SExprSupp* pExprSup, SSDataBlock* pBlock, TSKEY* tsCols, int32_t rowId, bool ignoreNull); int32_t createStreamIntervalSliceOperatorInfo(struct SOperatorInfo* downstream, SPhysiNode* pPhyNode, SExecTaskInfo* pTaskInfo, SReadHandle* pHandle, struct SOperatorInfo** ppOptInfo); int32_t buildAllResultKey(SStreamAggSupporter* pAggSup, TSKEY ts, SArray* pUpdated); +int32_t initOffsetInfo(int32_t** ppOffset, SSDataBlock* pRes); #ifdef __cplusplus } diff --git a/source/libs/executor/src/streamfilloperator.c b/source/libs/executor/src/streamfilloperator.c index ccf1f7c9e5..d00f95e5a9 100644 --- a/source/libs/executor/src/streamfilloperator.c +++ b/source/libs/executor/src/streamfilloperator.c @@ -100,6 +100,8 @@ void destroyStreamFillSupporter(SStreamFillSupporter* pFillSup) { taosMemoryFree(pFillSup->next.pRowVal); taosMemoryFree(pFillSup->nextNext.pRowVal); + taosMemoryFree(pFillSup->pOffsetInfo); + taosMemoryFree(pFillSup); } @@ -1573,10 +1575,7 @@ SStreamFillInfo* initStreamFillInfo(SStreamFillSupporter* pFillSup, SSDataBlock* pFillInfo->pResRow->key = INT64_MIN; pFillInfo->pResRow->pRowVal = taosMemoryCalloc(1, pFillSup->rowSize); - if (!pFillInfo->pResRow->pRowVal) { - code = terrno; - QUERY_CHECK_CODE(code, lino, _end); - } + QUERY_CHECK_NULL(pFillInfo->pResRow->pRowVal, code, lino, _end, terrno); for (int32_t i = 0; i < pFillSup->numOfAllCols; ++i) { SColumnInfoData* pColData = taosArrayGet(pRes->pDataBlock, i); @@ -1590,6 +1589,21 @@ SStreamFillInfo* initStreamFillInfo(SStreamFillSupporter* pFillSup, SSDataBlock* pCell->type = pColData->info.type; } + int32_t numOfResCol = taosArrayGetSize(pRes->pDataBlock); + if (numOfResCol < pFillSup->numOfAllCols) { + int32_t* pTmpBuf = (int32_t*)taosMemoryRealloc(pFillSup->pOffsetInfo, pFillSup->numOfAllCols * sizeof(int32_t)); + QUERY_CHECK_NULL(pTmpBuf, code, lino, _end, terrno); + pFillSup->pOffsetInfo = pTmpBuf; + + SResultCellData* pCell = getResultCell(pFillInfo->pResRow, numOfResCol - 1); + int32_t preLength = pFillSup->pOffsetInfo[numOfResCol - 1] + pCell->bytes + sizeof(SResultCellData); + for (int32_t i = numOfResCol; i < pFillSup->numOfAllCols; i++) { + pFillSup->pOffsetInfo[i] = preLength; + pCell = getResultCell(pFillInfo->pResRow, i); + preLength += pCell->bytes + sizeof(SResultCellData); + } + } + pFillInfo->pNonFillRow = taosMemoryCalloc(1, sizeof(SResultRowData)); QUERY_CHECK_NULL(pFillInfo->pNonFillRow, code, lino, _end, terrno); pFillInfo->pNonFillRow->key = INT64_MIN; diff --git a/source/libs/executor/src/streamintervalsliceoperator.c b/source/libs/executor/src/streamintervalsliceoperator.c index bc35b58a99..f26cbce119 100644 --- a/source/libs/executor/src/streamintervalsliceoperator.c +++ b/source/libs/executor/src/streamintervalsliceoperator.c @@ -74,6 +74,7 @@ void destroyStreamIntervalSliceOperatorInfo(void* param) { blockDataDestroy(pInfo->pDelRes); blockDataDestroy(pInfo->pCheckpointRes); + taosMemoryFreeClear(pInfo->pOffsetInfo); taosMemoryFreeClear(param); } @@ -163,7 +164,7 @@ _end: } void doStreamSliceInterpolation(SSliceRowData* pPrevWinVal, TSKEY winKey, TSKEY curTs, SSDataBlock* pDataBlock, - int32_t curRowIndex, SExprSupp* pSup, SIntervalSliceType type) { + int32_t curRowIndex, SExprSupp* pSup, SIntervalSliceType type, int32_t* pOffsetInfo) { SqlFunctionCtx* pCtx = pSup->pCtx; for (int32_t k = 0; k < pSup->numOfExprs; ++k) { if (!fmIsIntervalInterpoFunc(pCtx[k].functionId)) { @@ -175,7 +176,7 @@ void doStreamSliceInterpolation(SSliceRowData* pPrevWinVal, TSKEY winKey, TSKEY SColumnInfoData* pColInfo = taosArrayGet(pDataBlock->pDataBlock, pParam->pCol->slotId); double prevVal = 0, curVal = 0, winVal = 0; - SResultCellData* pCell = getSliceResultCell((SResultCellData*)pPrevWinVal->pRowVal, pParam->pCol->slotId); + SResultCellData* pCell = getSliceResultCell((SResultCellData*)pPrevWinVal->pRowVal, pParam->pCol->slotId, pOffsetInfo); GET_TYPED_DATA(prevVal, double, pCell->type, pCell->pData); GET_TYPED_DATA(curVal, double, pColInfo->info.type, colDataGetData(pColInfo, curRowIndex)); @@ -278,7 +279,7 @@ static int32_t doStreamIntervalSliceAggImpl(SOperatorInfo* pOperator, SSDataBloc resetIntervalSliceFunctionKey(pSup->pCtx, numOfOutput); doSetElapsedEndKey(prevPoint.winKey.win.ekey, &pOperator->exprSupp); - doStreamSliceInterpolation(prevPoint.pLastRow, prevPoint.winKey.win.ekey, curTs, pBlock, startPos, &pOperator->exprSupp, INTERVAL_SLICE_END); + doStreamSliceInterpolation(prevPoint.pLastRow, prevPoint.winKey.win.ekey, curTs, pBlock, startPos, &pOperator->exprSupp, INTERVAL_SLICE_END, pInfo->pOffsetInfo); updateTimeWindowInfo(&pInfo->twAggSup.timeWindowData, &prevPoint.winKey.win, 1); code = applyAggFunctionOnPartialTuples(pTaskInfo, pSup->pCtx, &pInfo->twAggSup.timeWindowData, startPos, 0, pBlock->info.rows, numOfOutput); @@ -294,7 +295,7 @@ static int32_t doStreamIntervalSliceAggImpl(SOperatorInfo* pOperator, SSDataBloc resetIntervalSliceFunctionKey(pSup->pCtx, numOfOutput); if (pInfo->hasInterpoFunc && IS_VALID_WIN_KEY(prevPoint.winKey.win.skey) && curPoint.winKey.win.skey != curTs) { - doStreamSliceInterpolation(prevPoint.pLastRow, curPoint.winKey.win.skey, curTs, pBlock, startPos, &pOperator->exprSupp, INTERVAL_SLICE_START); + doStreamSliceInterpolation(prevPoint.pLastRow, curPoint.winKey.win.skey, curTs, pBlock, startPos, &pOperator->exprSupp, INTERVAL_SLICE_START, pInfo->pOffsetInfo); } forwardRows = getNumOfRowsInTimeWindow(&pBlock->info, tsCols, startPos, curWin.ekey, binarySearchForKey, NULL, TSDB_ORDER_ASC); @@ -302,7 +303,7 @@ static int32_t doStreamIntervalSliceAggImpl(SOperatorInfo* pOperator, SSDataBloc if (pInfo->hasInterpoFunc && winCode != TSDB_CODE_SUCCESS) { int32_t endRowId = getQualifiedRowNumDesc(pSup, pBlock, tsCols, prevEndPos, false); TSKEY endRowTs = tsCols[endRowId]; - transBlockToSliceResultRow(pBlock, endRowId, endRowTs, curPoint.pLastRow, 0, NULL, NULL); + transBlockToSliceResultRow(pBlock, endRowId, endRowTs, curPoint.pLastRow, 0, NULL, NULL, pInfo->pOffsetInfo); } SWinKey curKey = {.ts = curPoint.winKey.win.skey, .groupId = curPoint.winKey.groupId}; if (pInfo->destHasPrimaryKey && winCode == TSDB_CODE_SUCCESS) { @@ -596,6 +597,9 @@ int32_t createStreamIntervalSliceOperatorInfo(SOperatorInfo* downstream, SPhysiN code = getDownstreamRes(downstream, &pDownRes, &pPkCol); QUERY_CHECK_CODE(code, lino, _error); + code = initOffsetInfo(&pInfo->pOffsetInfo, pDownRes); + QUERY_CHECK_CODE(code, lino, _error); + int32_t keyBytes = sizeof(TSKEY); keyBytes += blockDataGetRowSize(pDownRes) + sizeof(SResultCellData) * taosArrayGetSize(pDownRes->pDataBlock) + sizeof(bool); if (pPkCol) { diff --git a/source/libs/executor/src/streamtimesliceoperator.c b/source/libs/executor/src/streamtimesliceoperator.c index efc5dd6d6a..29c91a0763 100644 --- a/source/libs/executor/src/streamtimesliceoperator.c +++ b/source/libs/executor/src/streamtimesliceoperator.c @@ -281,8 +281,34 @@ static int32_t initTimeSliceResultBuf(SStreamFillSupporter* pFillSup, SExprSupp* return TSDB_CODE_SUCCESS; } -static int32_t initTimeSliceFillSup(SStreamInterpFuncPhysiNode* pPhyFillNode, SExprSupp* pExprSup, int32_t numOfExprs, SColumnInfo* pPkCol, - SStreamFillSupporter** ppResFillSup) { +int32_t initOffsetInfo(int32_t** ppOffset, SSDataBlock* pRes) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + int32_t numOfCol = taosArrayGetSize(pRes->pDataBlock); + int32_t preLength = 0; + int32_t* pOffsetInfo = taosMemoryCalloc(numOfCol, sizeof(int32_t)); + QUERY_CHECK_NULL(pOffsetInfo, code, lino, _end, lino); + + for (int32_t i = 0; i < numOfCol; i++) { + SColumnInfoData* pColInfo = taosArrayGet(pRes->pDataBlock, i); + pOffsetInfo[i] = preLength; + int32_t bytes = 1; + if (pColInfo != NULL) { + bytes = pColInfo->info.bytes; + } + preLength += bytes + sizeof(SResultCellData); + } + + (*ppOffset) = pOffsetInfo; + +_end: + if (code != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); + } + return code; +} +static int32_t initTimeSliceFillSup(SStreamInterpFuncPhysiNode* pPhyFillNode, SExprSupp* pExprSup, int32_t numOfExprs, + SSDataBlock* pInputRes, SColumnInfo* pPkCol, SStreamFillSupporter** ppResFillSup) { int32_t code = TSDB_CODE_SUCCESS; int32_t lino = 0; SStreamFillSupporter* pFillSup = taosMemoryCalloc(1, sizeof(SStreamFillSupporter)); @@ -320,6 +346,9 @@ static int32_t initTimeSliceFillSup(SStreamInterpFuncPhysiNode* pPhyFillNode, SE pFillSup->comparePkColFn = NULL; } + code = initOffsetInfo(&pFillSup->pOffsetInfo, pInputRes); + QUERY_CHECK_CODE(code, lino, _end); + (*ppResFillSup) = pFillSup; _end: @@ -359,17 +388,11 @@ _end: } } -SResultCellData* getSliceResultCell(SResultCellData* pRowVal, int32_t index) { +SResultCellData* getSliceResultCell(SResultCellData* pRowVal, int32_t index, int32_t* pCellOffsetInfo) { if (!pRowVal) { return NULL; } - char* pData = (char*)pRowVal; - SResultCellData* pCell = pRowVal; - for (int32_t i = 0; i < index; i++) { - pData += (pCell->bytes + sizeof(SResultCellData)); - pCell = (SResultCellData*)pData; - } - return pCell; + return POINTER_SHIFT(pRowVal, pCellOffsetInfo[index]); } static bool isGroupKeyFunc(SExprInfo* pExprInfo) { @@ -414,9 +437,9 @@ static int32_t fillPointResult(SStreamFillSupporter* pFillSup, SResultRowData* p int32_t srcSlot = pFillCol->pExpr->base.pParam[0].pCol->slotId; SResultCellData* pCell = NULL; if (IS_FILL_CONST_VALUE(pFillSup->type) && (isGroupKeyFunc(pFillCol->pExpr) || isSelectGroupConstValueFunc(pFillCol->pExpr)) ) { - pCell = getSliceResultCell(pNonFillRow->pRowVal, srcSlot); + pCell = getSliceResultCell(pNonFillRow->pRowVal, srcSlot, pFillSup->pOffsetInfo); } else { - pCell = getSliceResultCell(pResRow->pRowVal, srcSlot); + pCell = getSliceResultCell(pResRow->pRowVal, srcSlot, pFillSup->pOffsetInfo); } code = setRowCell(pDstCol, pBlock->info.rows, pCell); QUERY_CHECK_CODE(code, lino, _end); @@ -475,7 +498,7 @@ static void fillLinearRange(SStreamFillSupporter* pFillSup, SStreamFillInfo* pFi QUERY_CHECK_CODE(code, lino, _end); } else if (isInterpFunc(pFillCol->pExpr)) { int32_t srcSlot = pFillCol->pExpr->base.pParam[0].pCol->slotId; - SResultCellData* pCell = getSliceResultCell(pFillInfo->pResRow->pRowVal, srcSlot); + SResultCellData* pCell = getSliceResultCell(pFillInfo->pResRow->pRowVal, srcSlot, pFillSup->pOffsetInfo); if (IS_VAR_DATA_TYPE(type) || type == TSDB_DATA_TYPE_BOOL || pCell->isNull) { colDataSetNULL(pDstCol, index); continue; @@ -498,7 +521,7 @@ static void fillLinearRange(SStreamFillSupporter* pFillSup, SStreamFillInfo* pFi destroySPoint(&cur); } else { int32_t srcSlot = pFillCol->pExpr->base.pParam[0].pCol->slotId; - SResultCellData* pCell = getSliceResultCell(pFillInfo->pResRow->pRowVal, srcSlot); + SResultCellData* pCell = getSliceResultCell(pFillInfo->pResRow->pRowVal, srcSlot, pFillSup->pOffsetInfo); code = setRowCell(pDstCol, pBlock->info.rows, pCell); QUERY_CHECK_CODE(code, lino, _end); } @@ -952,8 +975,8 @@ static void copyNonFillValueInfo(SStreamFillSupporter* pFillSup, SStreamFillInfo if (!isInterpFunc(pFillCol->pExpr) && !isIrowtsPseudoColumn(pFillCol->pExpr) && !isIsfilledPseudoColumn(pFillCol->pExpr)) { int32_t srcSlot = pFillCol->pExpr->base.pParam[0].pCol->slotId; - SResultCellData* pSrcCell = getResultCell(&pFillSup->cur, srcSlot); - SResultCellData* pDestCell = getResultCell(pFillInfo->pNonFillRow, srcSlot); + SResultCellData* pSrcCell = getSliceResultCell(pFillSup->cur.pRowVal, srcSlot, pFillSup->pOffsetInfo); + SResultCellData* pDestCell = getSliceResultCell(pFillInfo->pNonFillRow->pRowVal, srcSlot, pFillSup->pOffsetInfo); pDestCell->isNull = pSrcCell->isNull; if (!pDestCell->isNull) { memcpy(pDestCell->pData, pSrcCell->pData, pSrcCell->bytes); @@ -962,11 +985,11 @@ static void copyNonFillValueInfo(SStreamFillSupporter* pFillSup, SStreamFillInfo } } -static void copyCalcRowDeltaData(SResultRowData* pEndRow, SArray* pEndPoins, SFillColInfo* pFillCol, int32_t numOfCol) { +static void copyCalcRowDeltaData(SResultRowData* pEndRow, SArray* pEndPoins, SFillColInfo* pFillCol, int32_t numOfCol, int32_t* pOffsetInfo) { for (int32_t i = 0; i < numOfCol; i++) { if (isInterpFunc(pFillCol[i].pExpr)) { int32_t slotId = pFillCol[i].pExpr->base.pParam[0].pCol->slotId; - SResultCellData* pECell = getResultCell(pEndRow, slotId); + SResultCellData* pECell = getSliceResultCell(pEndRow->pRowVal, slotId, pOffsetInfo); SPoint* pPoint = taosArrayGet(pEndPoins, slotId); pPoint->key = pEndRow->key; memcpy(pPoint->val, pECell->pData, pECell->bytes); @@ -1108,7 +1131,7 @@ static void setTimeSliceFillRule(SStreamFillSupporter* pFillSup, SStreamFillInfo SET_WIN_KEY_INVALID(pFillInfo->pLinearInfo->nextEnd); pFillSup->next.key = pFillSup->nextOriginKey; copyCalcRowDeltaData(&pFillSup->next, pFillInfo->pLinearInfo->pEndPoints, pFillSup->pAllColInfo, - pFillSup->numOfAllCols); + pFillSup->numOfAllCols, pFillSup->pOffsetInfo); pFillSup->prev.key = pFillSup->prevOriginKey; pFillInfo->pResRow = &pFillSup->prev; pFillInfo->pLinearInfo->hasNext = false; @@ -1117,7 +1140,7 @@ static void setTimeSliceFillRule(SStreamFillSupporter* pFillSup, SStreamFillInfo pFillInfo->pos = FILL_POS_END; SET_WIN_KEY_INVALID(pFillInfo->pLinearInfo->nextEnd); copyCalcRowDeltaData(&pFillSup->cur, pFillInfo->pLinearInfo->pEndPoints, pFillSup->pAllColInfo, - pFillSup->numOfAllCols); + pFillSup->numOfAllCols, pFillSup->pOffsetInfo); pFillSup->prev.key = pFillSup->prevOriginKey; pFillInfo->pResRow = &pFillSup->prev; pFillInfo->pLinearInfo->hasNext = false; @@ -1128,7 +1151,7 @@ static void setTimeSliceFillRule(SStreamFillSupporter* pFillSup, SStreamFillInfo SET_WIN_KEY_INVALID(pFillInfo->pLinearInfo->nextEnd); pFillSup->next.key = pFillSup->nextOriginKey; copyCalcRowDeltaData(&pFillSup->next, pFillInfo->pLinearInfo->pEndPoints, pFillSup->pAllColInfo, - pFillSup->numOfAllCols); + pFillSup->numOfAllCols, pFillSup->pOffsetInfo); pFillInfo->pResRow = &pFillSup->cur; pFillInfo->pLinearInfo->hasNext = false; } @@ -1249,11 +1272,11 @@ static bool needAdjustValue(SSlicePoint* pPoint, TSKEY ts, void* pPkVal, SStream } void transBlockToSliceResultRow(const SSDataBlock* pBlock, int32_t rowId, TSKEY ts, SSliceRowData* pRowVal, - int32_t rowSize, void* pPkData, SColumnInfoData* pPkCol) { + int32_t rowSize, void* pPkData, SColumnInfoData* pPkCol, int32_t* pCellOffsetInfo) { int32_t numOfCols = taosArrayGetSize(pBlock->pDataBlock); for (int32_t i = 0; i < numOfCols; ++i) { SColumnInfoData* pColData = taosArrayGet(pBlock->pDataBlock, i); - SResultCellData* pCell = getSliceResultCell((SResultCellData*)pRowVal->pRowVal, i); + SResultCellData* pCell = getSliceResultCell((SResultCellData*)pRowVal->pRowVal, i, pCellOffsetInfo); if (!colDataIsNull_s(pColData, rowId)) { pCell->isNull = false; pCell->type = pColData->info.type; @@ -1374,7 +1397,7 @@ static void doStreamTimeSliceImpl(SOperatorInfo* pOperator, SSDataBlock* pBlock) } right = needAdjustValue(&curPoint, tsCols[startPos], pPkVal, pFillSup, false, pFillSup->type); if (right) { - transBlockToSliceResultRow(pBlock, startPos, tsCols[startPos], curPoint.pRightRow, pFillSup->rowSize, pPkVal, pPkColDataInfo); + transBlockToSliceResultRow(pBlock, startPos, tsCols[startPos], curPoint.pRightRow, pFillSup->rowSize, pPkVal, pPkColDataInfo, pFillSup->pOffsetInfo); bool needDel = pInfo->destHasPrimaryKey && winCode == TSDB_CODE_SUCCESS; code = saveTimeSliceWinResultInfo(pAggSup, &pInfo->twAggSup, &curPoint.key, pInfo->pUpdatedMap, needDel, pInfo->pDeletedMap); @@ -1393,7 +1416,7 @@ static void doStreamTimeSliceImpl(SOperatorInfo* pOperator, SSDataBlock* pBlock) } left = needAdjustValue(&nextPoint, tsCols[leftRowId], pPkVal, pFillSup, true, pFillSup->type); if (left) { - transBlockToSliceResultRow(pBlock, leftRowId, tsCols[leftRowId], nextPoint.pLeftRow, pFillSup->rowSize, pPkVal, pPkColDataInfo); + transBlockToSliceResultRow(pBlock, leftRowId, tsCols[leftRowId], nextPoint.pLeftRow, pFillSup->rowSize, pPkVal, pPkColDataInfo, pFillSup->pOffsetInfo); bool needDel = pInfo->destHasPrimaryKey && winCode == TSDB_CODE_SUCCESS; code = saveTimeSliceWinResultInfo(pAggSup, &pInfo->twAggSup, &nextPoint.key, pInfo->pUpdatedMap, needDel, pInfo->pDeletedMap); @@ -1418,7 +1441,7 @@ static void doStreamTimeSliceImpl(SOperatorInfo* pOperator, SSDataBlock* pBlock) } right = needAdjustValue(&curPoint, tsCols[startPos], pPkVal, pFillSup, false, pFillSup->type); if (right) { - transBlockToSliceResultRow(pBlock, startPos, tsCols[startPos], curPoint.pRightRow, pFillSup->rowSize, pPkVal, pPkColDataInfo); + transBlockToSliceResultRow(pBlock, startPos, tsCols[startPos], curPoint.pRightRow, pFillSup->rowSize, pPkVal, pPkColDataInfo, pFillSup->pOffsetInfo); bool needDel = pInfo->destHasPrimaryKey && winCode == TSDB_CODE_SUCCESS; code = saveTimeSliceWinResultInfo(pAggSup, &pInfo->twAggSup, &curPoint.key, pInfo->pUpdatedMap, needDel, pInfo->pDeletedMap); @@ -1946,7 +1969,7 @@ static void copyFillValueInfo(SStreamFillSupporter* pFillSup, SStreamFillInfo* p continue; } int32_t srcSlot = pFillCol->pExpr->base.pParam[0].pCol->slotId; - SResultCellData* pCell = getResultCell(pFillInfo->pResRow, srcSlot); + SResultCellData* pCell = getSliceResultCell(pFillInfo->pResRow->pRowVal, srcSlot, pFillSup->pOffsetInfo); SFillColInfo* pValueCol = pFillSup->pAllColInfo + valueIndex; SVariant* pVar = &(pValueCol->fillVal); if (pCell->type == TSDB_DATA_TYPE_FLOAT) { @@ -1970,7 +1993,7 @@ static void copyFillValueInfo(SStreamFillSupporter* pFillSup, SStreamFillInfo* p for (int32_t i = 0; i < pFillSup->numOfAllCols; ++i) { SFillColInfo* pFillCol = pFillSup->pAllColInfo + i; int32_t slotId = GET_DEST_SLOT_ID(pFillCol); - SResultCellData* pCell = getResultCell(pFillInfo->pResRow, slotId); + SResultCellData* pCell = getSliceResultCell(pFillInfo->pResRow->pRowVal, slotId, pFillSup->pOffsetInfo); pCell->isNull = true; } } @@ -2090,7 +2113,7 @@ int32_t createStreamTimeSliceOperatorInfo(SOperatorInfo* downstream, SPhysiNode* QUERY_CHECK_CODE(code, lino, _error); pInfo->pFillSup = NULL; - code = initTimeSliceFillSup(pInterpPhyNode, pExpSup, numOfExprs, pPkCol, &pInfo->pFillSup); + code = initTimeSliceFillSup(pInterpPhyNode, pExpSup, numOfExprs, pDownRes, pPkCol, &pInfo->pFillSup); QUERY_CHECK_CODE(code, lino, _error); int32_t ratio = 1; diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index 0ee8f9be33..6facaed78d 100755 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -10931,10 +10931,10 @@ static int32_t checkStreamQuery(STranslateContext* pCxt, SCreateStreamStmt* pStm if (pSelect->hasInterpFunc) { // Temporary code - if (pStmt->pOptions->triggerType != STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_STREAM_QUERY, - "Stream interp function only support force window close"); - } + // if (pStmt->pOptions->triggerType != STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { + // return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_STREAM_QUERY, + // "Stream interp function only support force window close"); + // } if (pStmt->pOptions->triggerType == STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { if (pStmt->pOptions->fillHistory) { From ba569d3df30215d07434147a22e487e2f1480c79 Mon Sep 17 00:00:00 2001 From: 54liuyao <54liuyao@163.com> Date: Tue, 26 Nov 2024 15:27:03 +0800 Subject: [PATCH 05/31] adj code --- source/libs/parser/src/parTranslater.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index 6facaed78d..0ee8f9be33 100755 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -10931,10 +10931,10 @@ static int32_t checkStreamQuery(STranslateContext* pCxt, SCreateStreamStmt* pStm if (pSelect->hasInterpFunc) { // Temporary code - // if (pStmt->pOptions->triggerType != STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { - // return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_STREAM_QUERY, - // "Stream interp function only support force window close"); - // } + if (pStmt->pOptions->triggerType != STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_STREAM_QUERY, + "Stream interp function only support force window close"); + } if (pStmt->pOptions->triggerType == STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { if (pStmt->pOptions->fillHistory) { From 22023575ba52abc53a81bdd1cf91b4c76914519c Mon Sep 17 00:00:00 2001 From: 54liuyao <54liuyao@163.com> Date: Tue, 26 Nov 2024 19:46:36 +0800 Subject: [PATCH 06/31] opt stream force fill op --- source/libs/executor/inc/executorInt.h | 3 +- source/libs/executor/inc/operator.h | 2 +- source/libs/executor/inc/streamexecutorInt.h | 3 +- source/libs/executor/inc/tfill.h | 3 +- source/libs/executor/src/operator.c | 2 +- source/libs/executor/src/streamfilloperator.c | 152 ++++++++++++++---- .../src/streamintervalsliceoperator.c | 8 +- .../executor/src/streamtimesliceoperator.c | 2 +- .../executor/src/streamtimewindowoperator.c | 2 +- source/libs/executor/test/queryPlanTests.cpp | 2 +- source/libs/stream/src/tstreamFileState.c | 5 - 11 files changed, 130 insertions(+), 54 deletions(-) diff --git a/source/libs/executor/inc/executorInt.h b/source/libs/executor/inc/executorInt.h index 89ab8ead21..9c2df5de2c 100644 --- a/source/libs/executor/inc/executorInt.h +++ b/source/libs/executor/inc/executorInt.h @@ -823,10 +823,11 @@ typedef struct SStreamFillOperatorInfo { int32_t primaryTsCol; int32_t primarySrcSlotId; SStreamFillInfo* pFillInfo; - SStreamAggSupporter* pStreamAggSup; SArray* pCloseTs; SArray* pUpdated; SGroupResInfo groupResInfo; + SStreamState* pState; + SStateStore stateStore; } SStreamFillOperatorInfo; typedef struct SStreamTimeSliceOperatorInfo { diff --git a/source/libs/executor/inc/operator.h b/source/libs/executor/inc/operator.h index 91aef93452..2cf2959924 100644 --- a/source/libs/executor/inc/operator.h +++ b/source/libs/executor/inc/operator.h @@ -149,7 +149,7 @@ int32_t createStreamIntervalOperatorInfo(SOperatorInfo* downstream, SPhysiNode* int32_t createStreamStateAggOperatorInfo(SOperatorInfo* downstream, SPhysiNode* pPhyNode, SExecTaskInfo* pTaskInfo, SReadHandle* pHandle, SOperatorInfo** pInfo); -int32_t createStreamFillOperatorInfo(SOperatorInfo* downstream, SStreamFillPhysiNode* pPhyFillNode, SExecTaskInfo* pTaskInfo, SOperatorInfo** pInfo); +int32_t createStreamFillOperatorInfo(SOperatorInfo* downstream, SStreamFillPhysiNode* pPhyFillNode, SExecTaskInfo* pTaskInfo, SReadHandle* pHandle, SOperatorInfo** pInfo); int32_t createStreamEventAggOperatorInfo(SOperatorInfo* downstream, SPhysiNode* pPhyNode, SExecTaskInfo* pTaskInfo, SReadHandle* pHandle, SOperatorInfo** pInfo); diff --git a/source/libs/executor/inc/streamexecutorInt.h b/source/libs/executor/inc/streamexecutorInt.h index 4bd931c567..0a69080314 100644 --- a/source/libs/executor/inc/streamexecutorInt.h +++ b/source/libs/executor/inc/streamexecutorInt.h @@ -102,8 +102,9 @@ int32_t getQualifiedRowNumDesc(SExprSupp* pExprSup, SSDataBlock* pBlock, TSKEY* int32_t createStreamIntervalSliceOperatorInfo(struct SOperatorInfo* downstream, SPhysiNode* pPhyNode, SExecTaskInfo* pTaskInfo, SReadHandle* pHandle, struct SOperatorInfo** ppOptInfo); -int32_t buildAllResultKey(SStreamAggSupporter* pAggSup, TSKEY ts, SArray* pUpdated); +int32_t buildAllResultKey(SStateStore* pStateStore, SStreamState* pState, TSKEY ts, SArray* pUpdated); int32_t initOffsetInfo(int32_t** ppOffset, SSDataBlock* pRes); +TSKEY compareTs(void* pKey); #ifdef __cplusplus } diff --git a/source/libs/executor/inc/tfill.h b/source/libs/executor/inc/tfill.h index 6072063bbf..d20742bf45 100644 --- a/source/libs/executor/inc/tfill.h +++ b/source/libs/executor/inc/tfill.h @@ -119,7 +119,8 @@ typedef struct SStreamFillInfo { int32_t delIndex; uint64_t curGroupId; bool hasNext; - SResultRowData* pNonFillRow; + SResultRowData* pNonFillRow; + void* pTempBuff; } SStreamFillInfo; int64_t getNumOfResultsAfterFillGap(SFillInfo* pFillInfo, int64_t ekey, int32_t maxNumOfRows); diff --git a/source/libs/executor/src/operator.c b/source/libs/executor/src/operator.c index 3b10dce63f..a31d3d50e1 100644 --- a/source/libs/executor/src/operator.c +++ b/source/libs/executor/src/operator.c @@ -614,7 +614,7 @@ int32_t createOperator(SPhysiNode* pPhyNode, SExecTaskInfo* pTaskInfo, SReadHand } else if (QUERY_NODE_PHYSICAL_PLAN_FILL == type) { code = createFillOperatorInfo(ops[0], (SFillPhysiNode*)pPhyNode, pTaskInfo, &pOptr); } else if (QUERY_NODE_PHYSICAL_PLAN_STREAM_FILL == type) { - code = createStreamFillOperatorInfo(ops[0], (SStreamFillPhysiNode*)pPhyNode, pTaskInfo, &pOptr); + code = createStreamFillOperatorInfo(ops[0], (SStreamFillPhysiNode*)pPhyNode, pTaskInfo, pHandle, &pOptr); } else if (QUERY_NODE_PHYSICAL_PLAN_INDEF_ROWS_FUNC == type) { code = createIndefinitOutputOperatorInfo(ops[0], pPhyNode, pTaskInfo, &pOptr); } else if (QUERY_NODE_PHYSICAL_PLAN_INTERP_FUNC == type) { diff --git a/source/libs/executor/src/streamfilloperator.c b/source/libs/executor/src/streamfilloperator.c index d00f95e5a9..b90729e3b5 100644 --- a/source/libs/executor/src/streamfilloperator.c +++ b/source/libs/executor/src/streamfilloperator.c @@ -131,6 +131,7 @@ void destroyStreamFillInfo(SStreamFillInfo* pFillInfo) { pFillInfo->pLinearInfo = NULL; taosArrayDestroy(pFillInfo->delRanges); + taosMemoryFreeClear(pFillInfo->pTempBuff); taosMemoryFree(pFillInfo); } @@ -150,6 +151,14 @@ static void destroyStreamFillOperatorInfo(void* param) { clearGroupResInfo(&pInfo->groupResInfo); taosArrayDestroy(pInfo->pCloseTs); + if (pInfo->stateStore.streamFileStateDestroy != NULL) { + pInfo->stateStore.streamFileStateDestroy(pInfo->pState->pFileState); + } + + if (pInfo->pState != NULL) { + taosMemoryFreeClear(pInfo->pState); + } + taosMemoryFree(pInfo); } @@ -1159,14 +1168,19 @@ _end: return code; } +static void resetForceFillWindow(SResultRowData* pRowData) { + pRowData->key = INT64_MIN; + pRowData->pRowVal = NULL; +} + void doBuildForceFillResultImpl(SOperatorInfo* pOperator, SStreamFillSupporter* pFillSup, SStreamFillInfo* pFillInfo, SSDataBlock* pBlock, SGroupResInfo* pGroupResInfo) { int32_t code = TSDB_CODE_SUCCESS; int32_t lino = 0; - SStorageAPI* pAPI = &pOperator->pTaskInfo->storageAPI; - void* pState = pOperator->pTaskInfo->streamInfo.pState; - bool res = false; - int32_t numOfRows = getNumOfTotalRes(pGroupResInfo); + + SStreamFillOperatorInfo* pInfo = pOperator->info; + bool res = false; + int32_t numOfRows = getNumOfTotalRes(pGroupResInfo); for (; pGroupResInfo->index < numOfRows; pGroupResInfo->index++) { SWinKey* pKey = (SWinKey*)taosArrayGet(pGroupResInfo->pRows, pGroupResInfo->index); if (pBlock->info.id.groupId == 0) { @@ -1174,25 +1188,30 @@ void doBuildForceFillResultImpl(SOperatorInfo* pOperator, SStreamFillSupporter* } else if (pBlock->info.id.groupId != pKey->groupId) { break; } - void* val = NULL; - int32_t len = 0; - int32_t winCode = pAPI->stateStore.streamStateFillGet(pOperator->pTaskInfo->streamInfo.pState, pKey, (void**)&val, &len, NULL); + + SRowBuffPos* pValPos = NULL; + int32_t len = 0; + int32_t winCode = TSDB_CODE_SUCCESS; + code = pInfo->stateStore.streamStateFillGet(pInfo->pState, pKey, (void**)&pValPos, &len, &winCode); + QUERY_CHECK_CODE(code, lino, _end); qDebug("===stream=== build force fill res. key:%" PRId64 ",groupId:%" PRId64".res:%d", pKey->ts, pKey->groupId, winCode); if (winCode == TSDB_CODE_SUCCESS) { pFillSup->cur.key = pKey->ts; - pFillSup->cur.pRowVal = val; + pFillSup->cur.pRowVal = pValPos->pRowBuff; code = buildFillResult(&pFillSup->cur, pFillSup, pKey->ts, pBlock, &res); QUERY_CHECK_CODE(code, lino, _end); - resetFillWindow(&pFillSup->cur); + resetForceFillWindow(&pFillSup->cur); + releaseOutputBuf(pInfo->pState, pValPos, &pInfo->stateStore); } else { - SStreamStateCur* pCur = pAPI->stateStore.streamStateFillSeekKeyPrev(pState, pKey); - SWinKey preKey = {.ts = INT64_MIN, .groupId = pKey->groupId}; - void* preVal = NULL; - int32_t preVLen = 0; - winCode = pAPI->stateStore.streamStateFillGetGroupKVByCur(pCur, &preKey, (const void**)&preVal, &preVLen); + SWinKey preKey = {.ts = INT64_MIN, .groupId = pKey->groupId}; + SRowBuffPos* prePos = NULL; + int32_t preVLen = 0; + code = pInfo->stateStore.streamStateFillGetPrev(pInfo->pState, pKey, &preKey, + (void**)&prePos, &preVLen, &winCode); + QUERY_CHECK_CODE(code, lino, _end); if (winCode == TSDB_CODE_SUCCESS) { pFillSup->cur.key = pKey->ts; - pFillSup->cur.pRowVal = preVal; + pFillSup->cur.pRowVal = prePos->pRowBuff; if (pFillInfo->type == TSDB_FILL_PREV) { code = buildFillResult(&pFillSup->cur, pFillSup, pKey->ts, pBlock, &res); QUERY_CHECK_CODE(code, lino, _end); @@ -1202,9 +1221,9 @@ void doBuildForceFillResultImpl(SOperatorInfo* pOperator, SStreamFillSupporter* code = buildFillResult(pFillInfo->pResRow, pFillSup, pKey->ts, pBlock, &res); QUERY_CHECK_CODE(code, lino, _end); } - resetFillWindow(&pFillSup->cur); + resetForceFillWindow(&pFillSup->cur); } - pAPI->stateStore.streamStateFreeCur(pCur); + releaseOutputBuf(pInfo->pState, prePos, &pInfo->stateStore); } } @@ -1249,6 +1268,45 @@ _end: return code; } +static void keepResultInStateBuf(SStreamFillOperatorInfo* pInfo, uint64_t groupId, SResultRowData* pRow) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + + SWinKey key = {.groupId = groupId, .ts = pRow->key}; + int32_t curVLen = 0; + SRowBuffPos* pStatePos = NULL; + int32_t winCode = TSDB_CODE_SUCCESS; + code = pInfo->stateStore.streamStateFillAddIfNotExist(pInfo->pState, &key, (void**)&pStatePos, + &curVLen, &winCode); + QUERY_CHECK_CODE(code, lino, _end); + memcpy(pStatePos->pRowBuff, pRow->pRowVal, pInfo->pFillSup->rowSize); + qDebug("===stream===fill operator save key ts:%" PRId64 " group id:%" PRIu64 " code:%d", key.ts, key.groupId, code); + +_end: + if (code != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(code)); + } +} + +int32_t keepBlockRowInStateBuf(SStreamFillOperatorInfo* pInfo, SStreamFillInfo* pFillInfo, SSDataBlock* pBlock, TSKEY* tsCol, + int32_t rowId, uint64_t groupId, int32_t rowSize) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + TSKEY ts = tsCol[rowId]; + pFillInfo->nextRowKey = ts; + TAOS_MEMSET(pFillInfo->pTempBuff, 0, rowSize); + SResultRowData tmpNextRow = {.key = ts, .pRowVal = pFillInfo->pTempBuff}; + + transBlockToResultRow(pBlock, rowId, ts, &tmpNextRow); + keepResultInStateBuf(pInfo, groupId, &tmpNextRow); + +_end: + if (code != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); + } + return code; +} + // force window close impl static int32_t doStreamForceFillImpl(SOperatorInfo* pOperator) { int32_t code = TSDB_CODE_SUCCESS; @@ -1259,11 +1317,10 @@ static int32_t doStreamForceFillImpl(SOperatorInfo* pOperator) { SStreamFillInfo* pFillInfo = pInfo->pFillInfo; SSDataBlock* pBlock = pInfo->pSrcBlock; uint64_t groupId = pBlock->info.id.groupId; - SStreamAggSupporter* pAggSup = pInfo->pStreamAggSup; SColumnInfoData* pTsCol = taosArrayGet(pInfo->pSrcBlock->pDataBlock, pInfo->primaryTsCol); TSKEY* tsCol = (TSKEY*)pTsCol->pData; for (int32_t i = 0; i < pBlock->info.rows; i++){ - code = keepBlockRowInDiscBuf(pOperator, pFillInfo, pBlock, tsCol, i, groupId, pFillSup->rowSize); + code = keepBlockRowInStateBuf(pInfo, pFillInfo, pBlock, tsCol, i, groupId, pFillSup->rowSize); QUERY_CHECK_CODE(code, lino, _end); int32_t size = taosArrayGetSize(pInfo->pCloseTs); @@ -1283,7 +1340,7 @@ static int32_t doStreamForceFillImpl(SOperatorInfo* pOperator) { } } } - code = pAggSup->stateStore.streamStateGroupPut(pAggSup->pState, groupId, NULL, 0); + code = pInfo->stateStore.streamStateGroupPut(pInfo->pState, groupId, NULL, 0); QUERY_CHECK_CODE(code, lino, _end); _end: @@ -1293,13 +1350,13 @@ _end: return code; } -int32_t buildAllResultKey(SStreamAggSupporter* pAggSup, TSKEY ts, SArray* pUpdated) { +int32_t buildAllResultKey(SStateStore* pStateStore, SStreamState* pState, TSKEY ts, SArray* pUpdated) { int32_t code = TSDB_CODE_SUCCESS; int32_t lino = 0; int64_t groupId = 0; - SStreamStateCur* pCur = pAggSup->stateStore.streamStateGroupGetCur(pAggSup->pState); + SStreamStateCur* pCur = pStateStore->streamStateGroupGetCur(pState); while (1) { - int32_t winCode = pAggSup->stateStore.streamStateGroupGetKVByCur(pCur, &groupId, NULL, NULL); + int32_t winCode = pStateStore->streamStateGroupGetKVByCur(pCur, &groupId, NULL, NULL); if (winCode != TSDB_CODE_SUCCESS) { break; } @@ -1307,14 +1364,14 @@ int32_t buildAllResultKey(SStreamAggSupporter* pAggSup, TSKEY ts, SArray* pUpdat void* pPushRes = taosArrayPush(pUpdated, &key); QUERY_CHECK_NULL(pPushRes, code, lino, _end, terrno); - pAggSup->stateStore.streamStateGroupCurNext(pCur); + pStateStore->streamStateGroupCurNext(pCur); } - pAggSup->stateStore.streamStateFreeCur(pCur); + pStateStore->streamStateFreeCur(pCur); pCur = NULL; _end: if (code != TSDB_CODE_SUCCESS) { - pAggSup->stateStore.streamStateFreeCur(pCur); + pStateStore->streamStateFreeCur(pCur); pCur = NULL; qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); } @@ -1347,7 +1404,8 @@ static int32_t doStreamForceFillNext(SOperatorInfo* pOperator, SSDataBlock** ppR (*ppRes) = resBlock; goto _end; } - pInfo->pStreamAggSup->stateStore.streamStateClearExpiredState(pInfo->pStreamAggSup->pState); + + pInfo->stateStore.streamStateClearExpiredState(pInfo->pState); setStreamOperatorCompleted(pOperator); (*ppRes) = NULL; goto _end; @@ -1395,7 +1453,7 @@ static int32_t doStreamForceFillNext(SOperatorInfo* pOperator, SSDataBlock** ppR for (int32_t i = 0; i < taosArrayGetSize(pInfo->pCloseTs); i++) { TSKEY ts = *(TSKEY*) taosArrayGet(pInfo->pCloseTs, i); - code = buildAllResultKey(pInfo->pStreamAggSup, ts, pInfo->pUpdated); + code = buildAllResultKey(&pInfo->stateStore, pInfo->pState, ts, pInfo->pUpdated); QUERY_CHECK_CODE(code, lino, _end); } taosArrayClear(pInfo->pCloseTs); @@ -1414,7 +1472,7 @@ static int32_t doStreamForceFillNext(SOperatorInfo* pOperator, SSDataBlock** ppR QUERY_CHECK_CODE(code, lino, _end); if ((*ppRes) == NULL) { - pInfo->pStreamAggSup->stateStore.streamStateClearExpiredState(pInfo->pStreamAggSup->pState); + pInfo->stateStore.streamStateClearExpiredState(pInfo->pState); setStreamOperatorCompleted(pOperator); } @@ -1621,6 +1679,7 @@ SStreamFillInfo* initStreamFillInfo(SStreamFillSupporter* pFillSup, SSDataBlock* pFillInfo->delIndex = 0; pFillInfo->curGroupId = 0; pFillInfo->hasNext = false; + pFillInfo->pTempBuff = taosMemoryCalloc(1, pFillSup->rowSize); return pFillInfo; _end: @@ -1664,21 +1723,18 @@ static void setValueForFillInfo(SStreamFillSupporter* pFillSup, SStreamFillInfo* } } -int32_t getDownStreamInfo(SOperatorInfo* downstream, int8_t* triggerType, SInterval* pInterval, SStreamAggSupporter** ppAggSup) { +int32_t getDownStreamInfo(SOperatorInfo* downstream, int8_t* triggerType, SInterval* pInterval) { int32_t code = TSDB_CODE_SUCCESS; int32_t lino = 0; if (IS_NORMAL_INTERVAL_OP(downstream)) { SStreamIntervalOperatorInfo* pInfo = downstream->info; *triggerType = pInfo->twAggSup.calTrigger; *pInterval = pInfo->interval; - (*ppAggSup) = NULL; } else if (IS_CONTINUE_INTERVAL_OP(downstream)) { SStreamIntervalSliceOperatorInfo* pInfo = downstream->info; *triggerType = pInfo->twAggSup.calTrigger; *pInterval = pInfo->interval; pInfo->hasFill = true; - (*ppAggSup) = &pInfo->streamAggSup; - pInfo->streamAggSup.stateStore.streamStateSetFillInfo(pInfo->streamAggSup.pState); } else { code = TSDB_CODE_STREAM_INTERNAL_ERROR; } @@ -1691,8 +1747,31 @@ _end: return code; } +int32_t initFillOperatorStateBuff(SStreamFillOperatorInfo* pInfo, SStreamState* pState, SStateStore* pStore, + SReadHandle* pHandle, const char* taskIdStr, SStorageAPI* pApi) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + + pInfo->stateStore = *pStore; + pInfo->pState = taosMemoryCalloc(1, sizeof(SStreamState)); + QUERY_CHECK_NULL(pInfo->pState, code, lino, _end, terrno); + + *(pInfo->pState) = *pState; + pInfo->stateStore.streamStateSetNumber(pInfo->pState, -1, pInfo->primaryTsCol); + code = pInfo->stateStore.streamFileStateInit(tsStreamBufferSize, sizeof(SWinKey), pInfo->pFillSup->rowSize, 0, compareTs, + pInfo->pState, INT64_MAX, taskIdStr, pHandle->checkpointId, + STREAM_STATE_BUFF_HASH_SORT, &pInfo->pState->pFileState); + QUERY_CHECK_CODE(code, lino, _end); + +_end: + if (code != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); + } + return code; +} + int32_t createStreamFillOperatorInfo(SOperatorInfo* downstream, SStreamFillPhysiNode* pPhyFillNode, - SExecTaskInfo* pTaskInfo, SOperatorInfo** pOptrInfo) { + SExecTaskInfo* pTaskInfo, SReadHandle* pHandle, SOperatorInfo** pOptrInfo) { QRY_PARAM_CHECK(pOptrInfo); int32_t code = TSDB_CODE_SUCCESS; @@ -1718,7 +1797,7 @@ int32_t createStreamFillOperatorInfo(SOperatorInfo* downstream, SStreamFillPhysi int8_t triggerType = 0; SInterval interval = {0}; - code = getDownStreamInfo(downstream, &triggerType, &interval, &pInfo->pStreamAggSup); + code = getDownStreamInfo(downstream, &triggerType, &interval); QUERY_CHECK_CODE(code, lino, _error); pInfo->pFillSup = initStreamFillSup(pPhyFillNode, &interval, pFillExprInfo, numOfFillCols, &pTaskInfo->storageAPI, @@ -1773,9 +1852,12 @@ int32_t createStreamFillOperatorInfo(SOperatorInfo* downstream, SStreamFillPhysi pTaskInfo); if (triggerType == STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { + initFillOperatorStateBuff(pInfo, pTaskInfo->streamInfo.pState, &pTaskInfo->storageAPI.stateStore, pHandle, + GET_TASKID(pTaskInfo), &pTaskInfo->storageAPI); pOperator->fpSet = createOperatorFpSet(optrDummyOpenFn, doStreamForceFillNext, NULL, destroyStreamFillOperatorInfo, optrDefaultBufFn, NULL, optrDefaultGetNextExtFn, NULL); } else { + pInfo->pState != NULL; pOperator->fpSet = createOperatorFpSet(optrDummyOpenFn, doStreamFillNext, NULL, destroyStreamFillOperatorInfo, optrDefaultBufFn, NULL, optrDefaultGetNextExtFn, NULL); } diff --git a/source/libs/executor/src/streamintervalsliceoperator.c b/source/libs/executor/src/streamintervalsliceoperator.c index f26cbce119..fcf7d6ef10 100644 --- a/source/libs/executor/src/streamintervalsliceoperator.c +++ b/source/libs/executor/src/streamintervalsliceoperator.c @@ -360,9 +360,7 @@ static int32_t doStreamIntervalSliceNext(SOperatorInfo* pOperator, SSDataBlock** return code; } - if (pInfo->hasFill == false) { - pAggSup->stateStore.streamStateClearExpiredState(pAggSup->pState); - } + pAggSup->stateStore.streamStateClearExpiredState(pAggSup->pState); setStreamOperatorCompleted(pOperator); (*ppRes) = NULL; return code; @@ -452,9 +450,7 @@ static int32_t doStreamIntervalSliceNext(SOperatorInfo* pOperator, SSDataBlock** QUERY_CHECK_CODE(code, lino, _end); if ((*ppRes) == NULL) { - if (pInfo->hasFill == false) { - pAggSup->stateStore.streamStateClearExpiredState(pAggSup->pState); - } + pAggSup->stateStore.streamStateClearExpiredState(pAggSup->pState); setStreamOperatorCompleted(pOperator); } diff --git a/source/libs/executor/src/streamtimesliceoperator.c b/source/libs/executor/src/streamtimesliceoperator.c index 66793ca5a0..8611678e5a 100644 --- a/source/libs/executor/src/streamtimesliceoperator.c +++ b/source/libs/executor/src/streamtimesliceoperator.c @@ -1909,7 +1909,7 @@ static int32_t doStreamTimeSliceNext(SOperatorInfo* pOperator, SSDataBlock** ppR qDebug("===stream===build stream result, ts count:%d", size); for (int32_t i = 0; i < size; i++) { TSKEY ts = *(TSKEY*) taosArrayGet(pInfo->pCloseTs, i); - code = buildAllResultKey(&pInfo->streamAggSup, ts, pInfo->pUpdated); + code = buildAllResultKey(&pInfo->streamAggSup.stateStore, pInfo->streamAggSup.pState, ts, pInfo->pUpdated); QUERY_CHECK_CODE(code, lino, _end); } qDebug("===stream===build stream result, res count:%ld", taosArrayGetSize(pInfo->pUpdated)); diff --git a/source/libs/executor/src/streamtimewindowoperator.c b/source/libs/executor/src/streamtimewindowoperator.c index 8fd00e9313..8bf7323d63 100644 --- a/source/libs/executor/src/streamtimewindowoperator.c +++ b/source/libs/executor/src/streamtimewindowoperator.c @@ -1834,7 +1834,7 @@ int64_t getDeleteMarkFromOption(SStreamNodeOption* pOption) { return deleteMark; } -static TSKEY compareTs(void* pKey) { +TSKEY compareTs(void* pKey) { SWinKey* pWinKey = (SWinKey*)pKey; return pWinKey->ts; } diff --git a/source/libs/executor/test/queryPlanTests.cpp b/source/libs/executor/test/queryPlanTests.cpp index 6710435aba..69097ce755 100755 --- a/source/libs/executor/test/queryPlanTests.cpp +++ b/source/libs/executor/test/queryPlanTests.cpp @@ -3094,7 +3094,7 @@ void qptExecPlan(SReadHandle* pReadHandle, SNode* pNode, SExecTaskInfo* pTaskInf qptCtx.result.code = createFillOperatorInfo(NULL, (SFillPhysiNode*)pNode, pTaskInfo, ppOperaotr); break; case QUERY_NODE_PHYSICAL_PLAN_STREAM_FILL: - qptCtx.result.code = createStreamFillOperatorInfo(NULL, (SStreamFillPhysiNode*)pNode, pTaskInfo, ppOperaotr); + qptCtx.result.code = createStreamFillOperatorInfo(NULL, (SStreamFillPhysiNode*)pNode, pTaskInfo, pReadHandle, ppOperaotr); break; case QUERY_NODE_PHYSICAL_PLAN_MERGE_SESSION: qptCtx.result.code = createSessionAggOperatorInfo(NULL, (SSessionWinodwPhysiNode*)pNode, pTaskInfo, ppOperaotr); diff --git a/source/libs/stream/src/tstreamFileState.c b/source/libs/stream/src/tstreamFileState.c index dc4ca7c0e5..564f0d3cb0 100644 --- a/source/libs/stream/src/tstreamFileState.c +++ b/source/libs/stream/src/tstreamFileState.c @@ -1232,11 +1232,6 @@ void clearExpiredState(SStreamFileState* pFileState) { int32_t code_file = pFileState->stateFileRemoveFn(pFileState, pKey); qTrace("clear expired file, ts:%" PRId64 ". %s at line %d res:%d", pKey->ts, __func__, __LINE__, code_file); } - - if (pFileState->hasFillCatch == false) { - int32_t code_file = streamStateFillDel_rocksdb(pFileState->pFileStore, pKey); - qTrace("force clear expired file, ts:%" PRId64 ". %s at line %d res %d", pKey->ts, __func__, __LINE__, code_file); - } } taosArrayRemoveBatch(pWinStates, 0, size - 1, NULL); } From a6cd2b16fe54600d6a0fef93bffdcbe504ec73c6 Mon Sep 17 00:00:00 2001 From: 54liuyao <54liuyao@163.com> Date: Wed, 27 Nov 2024 08:40:19 +0800 Subject: [PATCH 07/31] adj function name --- include/libs/stream/tstreamFileState.h | 2 +- source/libs/stream/src/tstreamFileState.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/libs/stream/tstreamFileState.h b/include/libs/stream/tstreamFileState.h index 4a696d9798..f1f5b00e38 100644 --- a/include/libs/stream/tstreamFileState.h +++ b/include/libs/stream/tstreamFileState.h @@ -93,7 +93,7 @@ int32_t allocSessioncWinBuffByNextPosition(SStreamFileState* pFileState, SStream const SSessionKey* pWinKey, void** ppVal, int32_t* pVLen); SRowBuffPos* createSessionWinBuff(SStreamFileState* pFileState, SSessionKey* pKey, void* p, int32_t* pVLen); -int32_t recoverSesssion(SStreamFileState* pFileState, int64_t ckId); +int32_t recoverSession(SStreamFileState* pFileState, int64_t ckId); void sessionWinStateClear(SStreamFileState* pFileState); void sessionWinStateCleanup(void* pBuff); diff --git a/source/libs/stream/src/tstreamFileState.c b/source/libs/stream/src/tstreamFileState.c index 564f0d3cb0..8c8aee8c7d 100644 --- a/source/libs/stream/src/tstreamFileState.c +++ b/source/libs/stream/src/tstreamFileState.c @@ -259,7 +259,7 @@ int32_t streamFileStateInit(int64_t memSize, uint32_t keySize, uint32_t rowSize, if (type == STREAM_STATE_BUFF_HASH || type == STREAM_STATE_BUFF_HASH_SEARCH) { code = recoverSnapshot(pFileState, checkpointId); } else if (type == STREAM_STATE_BUFF_SORT) { - code = recoverSesssion(pFileState, checkpointId); + code = recoverSession(pFileState, checkpointId); } else if (type == STREAM_STATE_BUFF_HASH_SORT) { code = recoverFillSnapshot(pFileState, checkpointId); } @@ -914,7 +914,7 @@ int32_t deleteExpiredCheckPoint(SStreamFileState* pFileState, TSKEY mark) { return code; } -int32_t recoverSesssion(SStreamFileState* pFileState, int64_t ckId) { +int32_t recoverSession(SStreamFileState* pFileState, int64_t ckId) { int32_t code = TSDB_CODE_SUCCESS; int32_t lino = 0; int32_t winRes = TSDB_CODE_SUCCESS; From 0669910da544801512764d8f5fd93e8167579638 Mon Sep 17 00:00:00 2001 From: 54liuyao <54liuyao@163.com> Date: Wed, 27 Nov 2024 08:58:39 +0800 Subject: [PATCH 08/31] fix issue --- source/libs/executor/src/streamfilloperator.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/libs/executor/src/streamfilloperator.c b/source/libs/executor/src/streamfilloperator.c index b90729e3b5..6bce2e54a1 100644 --- a/source/libs/executor/src/streamfilloperator.c +++ b/source/libs/executor/src/streamfilloperator.c @@ -1852,8 +1852,9 @@ int32_t createStreamFillOperatorInfo(SOperatorInfo* downstream, SStreamFillPhysi pTaskInfo); if (triggerType == STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { - initFillOperatorStateBuff(pInfo, pTaskInfo->streamInfo.pState, &pTaskInfo->storageAPI.stateStore, pHandle, + code = initFillOperatorStateBuff(pInfo, pTaskInfo->streamInfo.pState, &pTaskInfo->storageAPI.stateStore, pHandle, GET_TASKID(pTaskInfo), &pTaskInfo->storageAPI); + QUERY_CHECK_CODE(code, lino, _error); pOperator->fpSet = createOperatorFpSet(optrDummyOpenFn, doStreamForceFillNext, NULL, destroyStreamFillOperatorInfo, optrDefaultBufFn, NULL, optrDefaultGetNextExtFn, NULL); } else { From e63b8a0ccb1648be6328aafa681213e237e48836 Mon Sep 17 00:00:00 2001 From: 54liuyao <54liuyao@163.com> Date: Wed, 27 Nov 2024 10:55:52 +0800 Subject: [PATCH 09/31] fix issue --- source/libs/executor/src/streamfilloperator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/libs/executor/src/streamfilloperator.c b/source/libs/executor/src/streamfilloperator.c index 6bce2e54a1..7b364de09d 100644 --- a/source/libs/executor/src/streamfilloperator.c +++ b/source/libs/executor/src/streamfilloperator.c @@ -1858,7 +1858,7 @@ int32_t createStreamFillOperatorInfo(SOperatorInfo* downstream, SStreamFillPhysi pOperator->fpSet = createOperatorFpSet(optrDummyOpenFn, doStreamForceFillNext, NULL, destroyStreamFillOperatorInfo, optrDefaultBufFn, NULL, optrDefaultGetNextExtFn, NULL); } else { - pInfo->pState != NULL; + pInfo->pState = NULL; pOperator->fpSet = createOperatorFpSet(optrDummyOpenFn, doStreamFillNext, NULL, destroyStreamFillOperatorInfo, optrDefaultBufFn, NULL, optrDefaultGetNextExtFn, NULL); } From 9d668150d51cd58f41eb0f8bd661812e2cb65377 Mon Sep 17 00:00:00 2001 From: 54liuyao <54liuyao@163.com> Date: Wed, 27 Nov 2024 16:24:46 +0800 Subject: [PATCH 10/31] fix stream issue --- source/libs/executor/src/streamfilloperator.c | 6 +- .../src/streamintervalsliceoperator.c | 15 +- source/libs/parser/src/parTranslater.c | 8 +- source/libs/stream/src/streamBackendRocksdb.c | 2 +- source/libs/stream/src/streamSliceState.c | 75 +++--- .../stream/streamFwcIntervalCheckpoint.sim | 67 ----- .../script/tsim/stream/streamTwaInterpFwc.sim | 247 ++++++++++++++++++ .../stream/streamTwaInterpFwcCheckpoint.sim | 180 +++++++++++++ 8 files changed, 487 insertions(+), 113 deletions(-) delete mode 100644 tests/script/tsim/stream/streamFwcIntervalCheckpoint.sim create mode 100644 tests/script/tsim/stream/streamTwaInterpFwcCheckpoint.sim diff --git a/source/libs/executor/src/streamfilloperator.c b/source/libs/executor/src/streamfilloperator.c index 7b364de09d..c992bd15b7 100644 --- a/source/libs/executor/src/streamfilloperator.c +++ b/source/libs/executor/src/streamfilloperator.c @@ -1432,7 +1432,11 @@ static int32_t doStreamForceFillNext(SOperatorInfo* pOperator, SSDataBlock** ppR memcpy(pInfo->pSrcBlock->info.parTbName, pBlock->info.parTbName, TSDB_TABLE_NAME_LEN); pInfo->srcRowIndex = -1; } break; - case STREAM_CHECKPOINT: + case STREAM_CHECKPOINT: { + pInfo->stateStore.streamStateCommit(pInfo->pState); + (*ppRes) = pBlock; + goto _end; + } break; case STREAM_CREATE_CHILD_TABLE: { (*ppRes) = pBlock; goto _end; diff --git a/source/libs/executor/src/streamintervalsliceoperator.c b/source/libs/executor/src/streamintervalsliceoperator.c index fcf7d6ef10..e7a9c58710 100644 --- a/source/libs/executor/src/streamintervalsliceoperator.c +++ b/source/libs/executor/src/streamintervalsliceoperator.c @@ -360,6 +360,13 @@ static int32_t doStreamIntervalSliceNext(SOperatorInfo* pOperator, SSDataBlock** return code; } + if (pInfo->recvCkBlock) { + pInfo->recvCkBlock = false; + printDataBlock(pInfo->pCheckpointRes, getStreamOpName(pOperator->operatorType), GET_TASKID(pTaskInfo)); + (*ppRes) = pInfo->pCheckpointRes; + return code; + } + pAggSup->stateStore.streamStateClearExpiredState(pAggSup->pState); setStreamOperatorCompleted(pOperator); (*ppRes) = NULL; @@ -391,8 +398,6 @@ static int32_t doStreamIntervalSliceNext(SOperatorInfo* pOperator, SSDataBlock** case STREAM_CHECKPOINT: { pInfo->recvCkBlock = true; pAggSup->stateStore.streamStateCommit(pAggSup->pState); - // doStreamIntervalSliceSaveCheckpoint(pOperator); - pInfo->recvCkBlock = true; code = copyDataBlock(pInfo->pCheckpointRes, pBlock); QUERY_CHECK_CODE(code, lino, _end); continue; @@ -450,6 +455,12 @@ static int32_t doStreamIntervalSliceNext(SOperatorInfo* pOperator, SSDataBlock** QUERY_CHECK_CODE(code, lino, _end); if ((*ppRes) == NULL) { + if (pInfo->recvCkBlock) { + pInfo->recvCkBlock = false; + printDataBlock(pInfo->pCheckpointRes, getStreamOpName(pOperator->operatorType), GET_TASKID(pTaskInfo)); + (*ppRes) = pInfo->pCheckpointRes; + return code; + } pAggSup->stateStore.streamStateClearExpiredState(pAggSup->pState); setStreamOperatorCompleted(pOperator); } diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index 0ee8f9be33..6facaed78d 100755 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -10931,10 +10931,10 @@ static int32_t checkStreamQuery(STranslateContext* pCxt, SCreateStreamStmt* pStm if (pSelect->hasInterpFunc) { // Temporary code - if (pStmt->pOptions->triggerType != STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_STREAM_QUERY, - "Stream interp function only support force window close"); - } + // if (pStmt->pOptions->triggerType != STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { + // return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_STREAM_QUERY, + // "Stream interp function only support force window close"); + // } if (pStmt->pOptions->triggerType == STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { if (pStmt->pOptions->fillHistory) { diff --git a/source/libs/stream/src/streamBackendRocksdb.c b/source/libs/stream/src/streamBackendRocksdb.c index 09f4e95376..68c99aa1b3 100644 --- a/source/libs/stream/src/streamBackendRocksdb.c +++ b/source/libs/stream/src/streamBackendRocksdb.c @@ -4130,7 +4130,7 @@ SStreamStateCur* streamStateFillSeekKeyPrev_rocksdb(SStreamState* pState, const SStreamStateCur* streamStateFillSeekToLast_rocksdb(SStreamState* pState) { SWinKey key = {.groupId = UINT64_MAX, .ts = INT64_MAX}; - return streamStateFillSeekKeyNext_rocksdb(pState, &key); + return streamStateFillSeekKeyPrev_rocksdb(pState, &key); } #ifdef BUILD_NO_CALL diff --git a/source/libs/stream/src/streamSliceState.c b/source/libs/stream/src/streamSliceState.c index 238bff8afc..c23537d018 100644 --- a/source/libs/stream/src/streamSliceState.c +++ b/source/libs/stream/src/streamSliceState.c @@ -23,6 +23,33 @@ #define NUM_OF_CACHE_WIN 64 #define MAX_NUM_OF_CACHE_WIN 128 +int32_t recoverSearchBuff(SStreamFileState* pFileState, SArray* pWinStates, uint64_t groupId) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + + SWinKey start = {.groupId = groupId, .ts = INT64_MAX}; + void* pState = getStateFileStore(pFileState); + SStreamStateCur* pCur = streamStateFillSeekKeyPrev_rocksdb(pState, &start); + for (int32_t i = 0; i < NUM_OF_CACHE_WIN; i++) { + SWinKey tmpKey = {.groupId = groupId}; + int32_t tmpRes = streamStateFillGetGroupKVByCur_rocksdb(pCur, &tmpKey, NULL, 0); + if (tmpRes != TSDB_CODE_SUCCESS) { + break; + } + void* tmp = taosArrayPush(pWinStates, &tmpKey); + QUERY_CHECK_NULL(tmp, code, lino, _end, terrno); + streamStateCurPrev_rocksdb(pCur); + } + taosArraySort(pWinStates, winKeyCmprImpl); + streamStateFreeCur(pCur); + +_end: + if (code != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); + } + return code; +} + int32_t getHashSortRowBuff(SStreamFileState* pFileState, const SWinKey* pKey, void** pVal, int32_t* pVLen, int32_t* pWinCode) { int32_t code = TSDB_CODE_SUCCESS; @@ -38,22 +65,7 @@ int32_t getHashSortRowBuff(SStreamFileState* pFileState, const SWinKey* pKey, vo // recover if (taosArrayGetSize(pWinStates) == 0 && needClearDiskBuff(pFileState)) { - TSKEY ts = getFlushMark(pFileState); - SWinKey start = {.groupId = pKey->groupId, .ts = INT64_MAX}; - void* pState = getStateFileStore(pFileState); - SStreamStateCur* pCur = streamStateFillSeekKeyPrev_rocksdb(pState, &start); - for (int32_t i = 0; i < NUM_OF_CACHE_WIN; i++) { - SWinKey tmpKey = {.groupId = pKey->groupId}; - int32_t tmpRes = streamStateFillGetGroupKVByCur_rocksdb(pCur, &tmpKey, NULL, 0); - if (tmpRes != TSDB_CODE_SUCCESS) { - break; - } - void* tmp = taosArrayPush(pWinStates, &tmpKey); - QUERY_CHECK_NULL(tmp, code, lino, _end, terrno); - streamStateCurPrev_rocksdb(pCur); - } - taosArraySort(pWinStates, winKeyCmprImpl); - streamStateFreeCur(pCur); + recoverSearchBuff(pFileState, pWinStates, pKey->groupId); } code = addSearchItem(pFileState, pWinStates, pKey); @@ -203,29 +215,16 @@ int32_t getHashSortPrevRow(SStreamFileState* pFileState, const SWinKey* pKey, SW SArray* pWinStates = NULL; SSHashObj* pSearchBuff = getSearchBuff(pFileState); void* pState = getStateFileStore(pFileState); - void** ppBuff = (void**) tSimpleHashGet(pSearchBuff, &pKey->groupId, sizeof(uint64_t)); - if (ppBuff) { - pWinStates = (SArray*)(*ppBuff); - } else { - qDebug("===stream=== search buff is empty.group id:%" PRId64, pKey->groupId); - SStreamStateCur* pCur = streamStateFillSeekKeyPrev_rocksdb(pState, pKey); - void* tmpVal = NULL; - int32_t len = 0; - (*pWinCode) = streamStateFillGetGroupKVByCur_rocksdb(pCur, pResKey, (const void**)&tmpVal, &len); - if ((*pWinCode) == TSDB_CODE_SUCCESS) { - SRowBuffPos* pNewPos = getNewRowPosForWrite(pFileState); - if (!pNewPos || !pNewPos->pRowBuff) { - code = TSDB_CODE_OUT_OF_MEMORY; - QUERY_CHECK_CODE(code, lino, _end); - } - memcpy(pNewPos->pRowBuff, tmpVal, len); - taosMemoryFreeClear(tmpVal); - *pVLen = getRowStateRowSize(pFileState); - (*ppVal) = pNewPos; - } - streamStateFreeCur(pCur); - return code; + // void** ppBuff = (void**) tSimpleHashGet(pSearchBuff, &pKey->groupId, sizeof(uint64_t)); + + code = addArrayBuffIfNotExist(pSearchBuff, pKey->groupId, &pWinStates); + QUERY_CHECK_CODE(code, lino, _end); + + // recover + if (taosArrayGetSize(pWinStates) == 0 && needClearDiskBuff(pFileState)) { + recoverSearchBuff(pFileState, pWinStates, pKey->groupId); } + int32_t size = taosArrayGetSize(pWinStates); int32_t index = binarySearch(pWinStates, size, pKey, fillStateKeyCompare); if (index >= 0) { diff --git a/tests/script/tsim/stream/streamFwcIntervalCheckpoint.sim b/tests/script/tsim/stream/streamFwcIntervalCheckpoint.sim deleted file mode 100644 index ed72d87e9a..0000000000 --- a/tests/script/tsim/stream/streamFwcIntervalCheckpoint.sim +++ /dev/null @@ -1,67 +0,0 @@ -system sh/stop_dnodes.sh -system sh/deploy.sh -n dnode1 -i 1 - -system sh/cfg.sh -n dnode1 -c checkpointInterval -v 60 - -system sh/exec.sh -n dnode1 -s start -sleep 50 -sql connect - -print step1 -print =============== create database -sql create database test vgroups 4; -sql use test; - -sql create stable st(ts timestamp, a int, b int , c int)tags(ta int,tb int,tc int); -sql create table t1 using st tags(1,1,1); -sql create table t2 using st tags(2,2,2); - -sql create stream streams1 trigger force_window_close IGNORE EXPIRED 1 IGNORE UPDATE 1 into streamt1 as select _wstart, count(a) from st partition by tbname interval(2s); -sql create stream streams2 trigger at_once IGNORE EXPIRED 0 IGNORE UPDATE 0 into streamt2 as select _wstart, count(a) from st interval(2s); - -run tsim/stream/checkTaskStatus.sim - -sleep 70000 - - -print restart taosd 01 ...... - -system sh/stop_dnodes.sh - -system sh/exec.sh -n dnode1 -s start - -run tsim/stream/checkTaskStatus.sim - -sql insert into t1 values(now + 3000a,1,1,1); - -$loop_count = 0 -loop0: - -sleep 2000 - -$loop_count = $loop_count + 1 -if $loop_count == 20 then - return -1 -endi - -print select * from streamt1; -sql select * from streamt1; - -print $data00 $data01 $data02 - -if $rows == 0 then - goto loop0 -endi - -print select * from streamt2; -sql select * from streamt2; - -print $data00 $data01 $data02 - -if $rows == 0 then - goto loop0 -endi - -print end - -system sh/exec.sh -n dnode1 -s stop -x SIGINT diff --git a/tests/script/tsim/stream/streamTwaInterpFwc.sim b/tests/script/tsim/stream/streamTwaInterpFwc.sim index ce76387c91..050e0282e2 100644 --- a/tests/script/tsim/stream/streamTwaInterpFwc.sim +++ b/tests/script/tsim/stream/streamTwaInterpFwc.sim @@ -304,6 +304,253 @@ if $rows != 1 then return -1 endi +print step3 +print =============== create database +sql create database test4 vgroups 4; +sql use test4; + +sql create stable st(ts timestamp,a int,b int,c int) tags(ta int,tb int,tc int); +sql create table t1234567890t1 using st tags(1,1,1); +sql create table t1234567890t2 using st tags(2,2,2); + +sql create stable streamt9(ts timestamp,a varchar(10),b tinyint,c tinyint) tags(ta varchar(3),cc int,tc int); +sql create stable streamt10(ts timestamp,a varchar(10),b tinyint,c tinyint) tags(ta varchar(3),cc int,tc int); +sql create stable streamt11(ts timestamp,a varchar(10),b tinyint,c tinyint) tags(ta varchar(3),cc int,tc int); + +sql create stream streams9 trigger FORCE_WINDOW_CLOSE IGNORE EXPIRED 1 IGNORE UPDATE 1 into streamt9 TAGS(cc,ta) SUBTABLE(concat(concat("tbn-", tbname), "_1")) as select _irowts, interp(a), _isfilled as a1, interp(b) from st partition by tbname as ta, b as cc every(2s) fill(value, 100000,200000); +sql create stream streams10 trigger FORCE_WINDOW_CLOSE IGNORE EXPIRED 1 IGNORE UPDATE 1 into streamt10 TAGS(cc,ta) SUBTABLE(concat(concat("tbn-", tbname), "_2")) as select _wstart, twa(a), sum(b),max(c) from st partition by tbname as ta, b as cc interval(2s) fill(NULL); +sql create stream streams11 trigger FORCE_WINDOW_CLOSE IGNORE EXPIRED 1 IGNORE UPDATE 1 into streamt11 TAGS(cc,ta) SUBTABLE(concat(concat("tbn-", tbname), "_3")) as select _wstart, count(a),avg(c),min(b) from st partition by tbname as ta, b as cc interval(2s); + +run tsim/stream/checkTaskStatus.sim + +sql insert into t1234567890t1 values(now + 3s,100000,1,1); + +$loop_count = 0 +loop9: + +sleep 2000 + +$loop_count = $loop_count + 1 +if $loop_count == 20 then + return -1 +endi + +print 2 sql select cc,ta, * from streamt9; +sql select cc,ta, * from streamt9; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + + +# row 0 +if $rows < 2 then + print ======rows=$rows + goto loop9 +endi + +if $data00 != 1 then + return -1 +endi + +if $data01 != @t12@ then + return -1 +endi + +if $data03 != @100000@ then + return -1 +endi + +if $data04 != 1 then + return -1 +endi + +if $data05 != 64 then + return -1 +endi + +print 3 sql select * from information_schema.ins_tables where stable_name = "streamt9"; +sql select * from information_schema.ins_tables where stable_name = "streamt9"; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + +if $rows != 1 then + return -1 +endi + +print 4 sql select * from information_schema.ins_tables where stable_name = "streamt9" and table_name like "tbn-t1234567890t1_1%"; +sql select * from information_schema.ins_tables where stable_name = "streamt9" and table_name like "tbn-t1234567890t1_1%"; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + +if $rows != 1 then + return -1 +endi + +$loop_count = 0 +loop10: + +sleep 2000 + +$loop_count = $loop_count + 1 +if $loop_count == 20 then + return -1 +endi + +print 2 sql select cc,ta, * from streamt10; +sql select cc,ta, * from streamt10; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + + +# row 0 +if $rows < 2 then + print ======rows=$rows + goto loop10 +endi + +if $data00 != 1 then + return -1 +endi + +if $data01 != @t12@ then + return -1 +endi + +if $data03 != @100000.000@ then + return -1 +endi + +if $data04 != 1 then + return -1 +endi + +if $data05 != 1 then + return -1 +endi + +print 3 sql select * from information_schema.ins_tables where stable_name = "streamt10"; +sql select * from information_schema.ins_tables where stable_name = "streamt10"; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + +if $rows != 1 then + return -1 +endi + +print 4 sql select * from information_schema.ins_tables where stable_name = "streamt10" and table_name like "tbn-t1234567890t1_2%"; +sql select * from information_schema.ins_tables where stable_name = "streamt10" and table_name like "tbn-t1234567890t1_2%"; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + +if $rows != 1 then + return -1 +endi + +$loop_count = 0 +loop11: + +sleep 2000 + +$loop_count = $loop_count + 1 +if $loop_count == 20 then + return -1 +endi + +print 2 sql select cc,ta,* from streamt11; +sql select cc,ta,* from streamt11; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + + +# row 0 +if $rows < 1 then + print ======rows=$rows + goto loop11 +endi + +if $data00 != 1 then + return -1 +endi + +if $data01 != @t12@ then + return -1 +endi + +if $data03 != @1@ then + return -1 +endi + +if $data04 != 1 then + return -1 +endi + +if $data05 != 1 then + return -1 +endi + +print 3 sql select * from information_schema.ins_tables where stable_name = "streamt11"; +sql select * from information_schema.ins_tables where stable_name = "streamt11"; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + +if $rows != 1 then + return -1 +endi + +print 4 sql select * from information_schema.ins_tables where stable_name = "streamt11" and table_name like "tbn-t1234567890t1_3%"; +sql select * from information_schema.ins_tables where stable_name = "streamt11" and table_name like "tbn-t1234567890t1_3%"; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + +if $rows != 1 then + return -1 +endi + + print end system sh/exec.sh -n dnode1 -s stop -x SIGINT diff --git a/tests/script/tsim/stream/streamTwaInterpFwcCheckpoint.sim b/tests/script/tsim/stream/streamTwaInterpFwcCheckpoint.sim new file mode 100644 index 0000000000..f983dd3ab5 --- /dev/null +++ b/tests/script/tsim/stream/streamTwaInterpFwcCheckpoint.sim @@ -0,0 +1,180 @@ +system sh/stop_dnodes.sh +system sh/deploy.sh -n dnode1 -i 1 + +system sh/cfg.sh -n dnode1 -c checkpointInterval -v 60 +system sh/cfg.sh -n dnode1 -c ratioOfVnodeStreamThreads -v 4 + +system sh/exec.sh -n dnode1 -s start +sleep 50 +sql connect + +print step1 +print =============== create database +sql create database test vgroups 1; +sql use test; + +sql create stable st(ts timestamp, a int, b int , c int)tags(ta int,tb int,tc int); +sql create table t1 using st tags(1,1,1); +sql create table t2 using st tags(2,2,2); + +sql create stream streams1 trigger force_window_close IGNORE EXPIRED 1 IGNORE UPDATE 1 into streamt1 as select _wstart, count(a),max(b) from st partition by tbname interval(5s); +sql create stream streams2 trigger at_once IGNORE EXPIRED 0 IGNORE UPDATE 0 into streamt2 as select _wstart, count(a), max(b) from st interval(5s); +sql create stream streams3 trigger force_window_close IGNORE EXPIRED 1 IGNORE UPDATE 1 into streamt3 as select _wstart, count(a), twa(b) from st partition by tbname interval(5s) fill(prev); +sql create stream streams4 trigger force_window_close IGNORE EXPIRED 1 IGNORE UPDATE 1 into streamt4 as select _irowts, interp(a), interp(b) from st partition by tbname every(5s) fill(prev); + +run tsim/stream/checkTaskStatus.sim + +sql insert into t1 values(now + 3000a,1,1,1); + +$loop_count = 0 +loop0: + +sleep 2000 + +$loop_count = $loop_count + 1 +if $loop_count == 20 then + return -1 +endi + +print select * from streamt3; +sql select * from streamt3; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + +if $rows == 0 then + goto loop0 +endi + + +print select * from streamt4; +sql select * from streamt4; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + +if $rows == 0 then + goto loop0 +endi + + +sleep 70000 + +$loop_count = 0 +loop0_1: + +$loop_count = $loop_count + 1 +if $loop_count == 20 then + return -1 +endi + +print sql select * from information_schema.ins_stream_tasks where checkpoint_time is null; +sql select * from information_schema.ins_stream_tasks where checkpoint_time is null; + + +sleep 10000 + +if $rows > 0 then + print wait checkpoint.rows = $rows + goto loop0_1 +endi + +print restart taosd 01 ...... + +system sh/stop_dnodes.sh + +system sh/exec.sh -n dnode1 -s start + +run tsim/stream/checkTaskStatus.sim + +print select * from streamt3; +sql select * from streamt3; + +$streamt3_rows = $rows +print =====streamt3_rows=$streamt3_rows + +print select * from streamt4; +sql select * from streamt4; + +$streamt4_rows = $rows +print =====streamt4_rows=$streamt4_rows + +$loop_count = 0 +loop1: + +sleep 2000 + +$loop_count = $loop_count + 1 +if $loop_count == 20 then + return -1 +endi + +print select * from streamt3; +sql select * from streamt3; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + +if $rows <= $streamt3_rows then + print =====rows=$rows + print =====streamt3_rows=$streamt3_rows + goto loop1 +endi + +print select * from streamt4; +sql select * from streamt4; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + +if $rows <= $streamt4_rows then + print =====rows=$rows + print =====streamt4_rows=$streamt4_rows + goto loop1 +endi + +sql insert into t1 values(now + 3000a,10,10,10); + +$loop_count = 0 +loop2: + +sleep 2000 + +$loop_count = $loop_count + 1 +if $loop_count == 20 then + return -1 +endi + +print select * from streamt1; +sql select * from streamt1; + +print $data00 $data01 $data02 $data03 $data04 +print $data10 $data11 $data12 $data13 $data14 +print $data20 $data21 $data22 $data23 $data24 +print $data30 $data31 $data32 $data33 $data34 +print $data40 $data41 $data42 $data43 $data44 +print $data50 $data51 $data52 $data53 $data54 + +if $data02 != 10 then + goto loop2 +endi + +print end + +system sh/exec.sh -n dnode1 -s stop -x SIGINT From 23d67933cdcb3c47047fb0a95b737e561c4e5d97 Mon Sep 17 00:00:00 2001 From: 54liuyao <54liuyao@163.com> Date: Wed, 27 Nov 2024 16:52:01 +0800 Subject: [PATCH 11/31] add ci --- source/libs/stream/src/streamSliceState.c | 6 ++++-- tests/script/tsim/stream/streamTwaInterpFwcCheckpoint.sim | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/source/libs/stream/src/streamSliceState.c b/source/libs/stream/src/streamSliceState.c index c23537d018..eefe046878 100644 --- a/source/libs/stream/src/streamSliceState.c +++ b/source/libs/stream/src/streamSliceState.c @@ -65,7 +65,8 @@ int32_t getHashSortRowBuff(SStreamFileState* pFileState, const SWinKey* pKey, vo // recover if (taosArrayGetSize(pWinStates) == 0 && needClearDiskBuff(pFileState)) { - recoverSearchBuff(pFileState, pWinStates, pKey->groupId); + code = recoverSearchBuff(pFileState, pWinStates, pKey->groupId); + QUERY_CHECK_CODE(code, lino, _end); } code = addSearchItem(pFileState, pWinStates, pKey); @@ -222,7 +223,8 @@ int32_t getHashSortPrevRow(SStreamFileState* pFileState, const SWinKey* pKey, SW // recover if (taosArrayGetSize(pWinStates) == 0 && needClearDiskBuff(pFileState)) { - recoverSearchBuff(pFileState, pWinStates, pKey->groupId); + code = recoverSearchBuff(pFileState, pWinStates, pKey->groupId); + QUERY_CHECK_CODE(code, lino, _end); } int32_t size = taosArrayGetSize(pWinStates); diff --git a/tests/script/tsim/stream/streamTwaInterpFwcCheckpoint.sim b/tests/script/tsim/stream/streamTwaInterpFwcCheckpoint.sim index f983dd3ab5..26be5018c1 100644 --- a/tests/script/tsim/stream/streamTwaInterpFwcCheckpoint.sim +++ b/tests/script/tsim/stream/streamTwaInterpFwcCheckpoint.sim @@ -161,8 +161,8 @@ if $loop_count == 20 then return -1 endi -print select * from streamt1; -sql select * from streamt1; +print select * from streamt1 order by 1; +sql select * from streamt1 order by 1; print $data00 $data01 $data02 $data03 $data04 print $data10 $data11 $data12 $data13 $data14 @@ -171,7 +171,7 @@ print $data30 $data31 $data32 $data33 $data34 print $data40 $data41 $data42 $data43 $data44 print $data50 $data51 $data52 $data53 $data54 -if $data02 != 10 then +if $data12 != 10 then goto loop2 endi From 58886a026406c2e7fcc414ef5a963cc2e50e1f88 Mon Sep 17 00:00:00 2001 From: Haojun Liao Date: Wed, 27 Nov 2024 17:48:02 +0800 Subject: [PATCH 12/31] fix(stream): the timestamp when the stream is created is set to be the initial force_window_close start time. --- source/libs/stream/src/streamData.c | 3 ++- source/libs/stream/src/streamSched.c | 27 ++++++++++++++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/source/libs/stream/src/streamData.c b/source/libs/stream/src/streamData.c index 306d6a0239..87bd6d2187 100644 --- a/source/libs/stream/src/streamData.c +++ b/source/libs/stream/src/streamData.c @@ -14,6 +14,7 @@ */ #include "streamInt.h" +#include "ttime.h" static int32_t streamMergedSubmitNew(SStreamMergedSubmit** pSubmit) { *pSubmit = NULL; @@ -330,7 +331,7 @@ int32_t streamCreateForcewindowTrigger(SStreamTrigger** pTrigger, int32_t trigge .intervalUnit = pInterval->intervalUnit, .slidingUnit = pInterval->slidingUnit}; - ts = taosGetTimestampMs(); + ts = taosGetTimestamp(pInterval->precision); if (pLatestWindow->skey == INT64_MIN) { STimeWindow window = getAlignQueryTimeWindow(&interval, ts - trigger); diff --git a/source/libs/stream/src/streamSched.c b/source/libs/stream/src/streamSched.c index 8c79abfd02..72d1859197 100644 --- a/source/libs/stream/src/streamSched.c +++ b/source/libs/stream/src/streamSched.c @@ -24,7 +24,7 @@ void streamSetupScheduleTrigger(SStreamTask* pTask) { int64_t delay = 0; int32_t code = 0; const char* id = pTask->id.idStr; - int64_t* pTaskRefId = NULL; + int64_t* pTaskRefId = NULL; if (pTask->info.fillHistory == 1) { return; @@ -41,7 +41,6 @@ void streamSetupScheduleTrigger(SStreamTask* pTask) { return; } - pTask->status.latestForceWindow = lastTimeWindow; pTask->info.delaySchedParam = interval.sliding; pTask->info.watermark = waterMark; pTask->info.interval = interval; @@ -51,9 +50,17 @@ void streamSetupScheduleTrigger(SStreamTask* pTask) { STimeWindow curWin = getAlignQueryTimeWindow(&pTask->info.interval, now); delay = (curWin.ekey + 1) - now + waterMark; + if (lastTimeWindow.skey == INT64_MIN) { // start from now, not the exec task timestamp after delay + pTask->status.latestForceWindow.skey = curWin.skey - pTask->info.interval.interval; + pTask->status.latestForceWindow.ekey = now; + } else { + pTask->status.latestForceWindow = lastTimeWindow; + } + stInfo("s-task:%s extract interval info from executor, wm:%" PRId64 " interval:%" PRId64 " unit:%c sliding:%" PRId64 - " unit:%c, initial start after:%" PRId64, - id, waterMark, interval.interval, interval.intervalUnit, interval.sliding, interval.slidingUnit, delay); + " unit:%c, initial start after:%" PRId64" last_win:%"PRId64"-%"PRId64, + id, waterMark, interval.interval, interval.intervalUnit, interval.sliding, interval.slidingUnit, delay, pTask->status.latestForceWindow.skey, + pTask->status.latestForceWindow.ekey); } else { delay = pTask->info.delaySchedParam; if (delay == 0) { @@ -199,16 +206,11 @@ void streamTaskSchedHelper(void* param, void* tmrId) { return; } - if (streamTaskShouldPause(pTask)) { - stDebug("s-task:%s is paused, check in nextTrigger:%ds", id, nextTrigger/1000); - streamTmrStart(streamTaskSchedHelper, nextTrigger, pTask, streamTimer, &pTask->schedInfo.pDelayTimer, vgId, - "sched-run-tmr"); - } - if (streamTaskGetStatus(pTask).state == TASK_STATUS__CK) { stDebug("s-task:%s in checkpoint procedure, not retrieve result, next:%dms", id, nextTrigger); } else { if (pTask->info.trigger == STREAM_TRIGGER_FORCE_WINDOW_CLOSE && pTask->info.taskLevel == TASK_LEVEL__SOURCE) { + int32_t num = 0; SStreamTrigger* pTrigger = NULL; while (1) { @@ -227,6 +229,8 @@ void streamTaskSchedHelper(void* param, void* tmrId) { goto _end; } + num += 1; + // check whether the time window gaps exist or not int64_t now = taosGetTimestamp(pTask->info.interval.precision); int64_t intervalEndTs = pTrigger->pBlock->info.window.skey + pTask->info.interval.interval; @@ -235,13 +239,14 @@ void streamTaskSchedHelper(void* param, void* tmrId) { STimeWindow w = pTrigger->pBlock->info.window; w.ekey = w.skey + pTask->info.interval.interval; if (w.skey <= pTask->status.latestForceWindow.skey) { - stFatal("s-task:%s invalid new time window in force_window_close model, skey:%" PRId64 + stFatal("s-task:%s invalid new time window in force_window_close trigger model, skey:%" PRId64 " should be greater than latestForceWindow skey:%" PRId64, pTask->id.idStr, w.skey, pTask->status.latestForceWindow.skey); } pTask->status.latestForceWindow = w; if (intervalEndTs + pTask->info.watermark + pTask->info.interval.interval > now) { + stDebug("s-task:%s generate %d time window(s)", id, num); break; } else { stDebug("s-task:%s gap exist for force_window_close, current force_window_skey:%" PRId64, id, w.skey); From 9d384bc438c09260ddd66d08f8855234ffdbb7f8 Mon Sep 17 00:00:00 2001 From: 54liuyao <54liuyao@163.com> Date: Wed, 27 Nov 2024 18:51:50 +0800 Subject: [PATCH 13/31] fix issue --- source/libs/parser/src/parTranslater.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index 6facaed78d..0ee8f9be33 100755 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -10931,10 +10931,10 @@ static int32_t checkStreamQuery(STranslateContext* pCxt, SCreateStreamStmt* pStm if (pSelect->hasInterpFunc) { // Temporary code - // if (pStmt->pOptions->triggerType != STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { - // return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_STREAM_QUERY, - // "Stream interp function only support force window close"); - // } + if (pStmt->pOptions->triggerType != STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_STREAM_QUERY, + "Stream interp function only support force window close"); + } if (pStmt->pOptions->triggerType == STREAM_TRIGGER_FORCE_WINDOW_CLOSE) { if (pStmt->pOptions->fillHistory) { From 1b285f8a1d00ad987febc806ba53d5a79b5d75a2 Mon Sep 17 00:00:00 2001 From: 54liuyao <54liuyao@163.com> Date: Wed, 27 Nov 2024 18:52:04 +0800 Subject: [PATCH 14/31] fix issue --- source/libs/stream/src/tstreamFileState.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/source/libs/stream/src/tstreamFileState.c b/source/libs/stream/src/tstreamFileState.c index 8c8aee8c7d..dcabadb8bd 100644 --- a/source/libs/stream/src/tstreamFileState.c +++ b/source/libs/stream/src/tstreamFileState.c @@ -991,6 +991,7 @@ int32_t recoverSnapshot(SStreamFileState* pFileState, int64_t ckId) { } winCode = streamStateGetKVByCur_rocksdb(getStateFileStore(pFileState), pCur, pNewPos->pKey, (const void**)&pVal, &vlen); + qDebug("===stream=== get state by cur winres:%d. %s", winCode, __func__); if (winCode != TSDB_CODE_SUCCESS || pFileState->getTs(pNewPos->pKey) < pFileState->flushMark) { destroyRowBuffPos(pNewPos); SListNode* pNode = tdListPopTail(pFileState->usedBuffs); @@ -1007,6 +1008,7 @@ int32_t recoverSnapshot(SStreamFileState* pFileState, int64_t ckId) { memcpy(pNewPos->pRowBuff, pVal, vlen); taosMemoryFreeClear(pVal); pNewPos->beFlushed = true; + qDebug("===stream=== read checkpoint state from disc. %s", __func__); code = tSimpleHashPut(pFileState->rowStateBuff, pNewPos->pKey, pFileState->keyLen, &pNewPos, POINTER_BYTES); if (code != TSDB_CODE_SUCCESS) { destroyRowBuffPos(pNewPos); @@ -1077,6 +1079,7 @@ int32_t recoverFillSnapshot(SStreamFileState* pFileState, int64_t ckId) { int32_t vlen = 0; SRowBuffPos* pNewPos = getNewRowPosForWrite(pFileState); winRes = streamStateFillGetKVByCur_rocksdb(pCur, pNewPos->pKey, (const void**)&pVal, &vlen); + qDebug("===stream=== get state by cur winres:%d. %s", winRes, __func__); if (winRes != TSDB_CODE_SUCCESS || isFlushedState(pFileState, pFileState->getTs(pNewPos->pKey), 0)) { destroyRowBuffPos(pNewPos); SListNode* pNode = tdListPopTail(pFileState->usedBuffs); @@ -1085,9 +1088,17 @@ int32_t recoverFillSnapshot(SStreamFileState* pFileState, int64_t ckId) { break; } + if (vlen != pFileState->rowSize) { + qError("row size mismatch, expect:%d, actual:%d", pFileState->rowSize, vlen); + code = TSDB_CODE_QRY_EXECUTOR_INTERNAL_ERROR; + taosMemoryFreeClear(pVal); + QUERY_CHECK_CODE(code, lino, _end); + } + memcpy(pNewPos->pRowBuff, pVal, vlen); taosMemoryFreeClear(pVal); pNewPos->beFlushed = true; + qDebug("===stream=== read checkpoint state from disc. %s", __func__); winRes = tSimpleHashPut(pFileState->rowStateBuff, pNewPos->pKey, pFileState->keyLen, &pNewPos, POINTER_BYTES); if (winRes != TSDB_CODE_SUCCESS) { destroyRowBuffPos(pNewPos); From ec71572d749bbbc1a9ab965f69619636d5f1ddde Mon Sep 17 00:00:00 2001 From: Haojun Liao Date: Thu, 28 Nov 2024 14:52:39 +0800 Subject: [PATCH 15/31] refactor: do some internal refactor. --- source/libs/stream/src/streamData.c | 30 ++--- source/libs/stream/src/streamSched.c | 166 +++++++++++++++++---------- 2 files changed, 114 insertions(+), 82 deletions(-) diff --git a/source/libs/stream/src/streamData.c b/source/libs/stream/src/streamData.c index 87bd6d2187..5c699ad842 100644 --- a/source/libs/stream/src/streamData.c +++ b/source/libs/stream/src/streamData.c @@ -308,13 +308,17 @@ void streamFreeQitem(SStreamQueueItem* data) { } } -int32_t streamCreateForcewindowTrigger(SStreamTrigger** pTrigger, int32_t trigger, SInterval* pInterval, STimeWindow* pLatestWindow, const char* id) { +int32_t streamCreateForcewindowTrigger(SStreamTrigger** pTrigger, int32_t interval, SInterval* pInterval, + STimeWindow* pLatestWindow, const char* id) { QRY_PARAM_CHECK(pTrigger); - int64_t ts = INT64_MIN; + SStreamTrigger* p = NULL; + int64_t ts = taosGetTimestamp(pInterval->precision); + int64_t skey = pLatestWindow->skey + interval; int32_t code = taosAllocateQitem(sizeof(SStreamTrigger), DEF_QITEM, 0, (void**)&p); if (code) { + stError("s-task:%s failed to create force_window trigger, code:%s", id, tstrerror(code)); return code; } @@ -325,26 +329,10 @@ int32_t streamCreateForcewindowTrigger(SStreamTrigger** pTrigger, int32_t trigge return terrno; } - // let's calculate the previous time window - SInterval interval = {.interval = trigger, - .sliding = trigger, - .intervalUnit = pInterval->intervalUnit, - .slidingUnit = pInterval->slidingUnit}; - - ts = taosGetTimestamp(pInterval->precision); - - if (pLatestWindow->skey == INT64_MIN) { - STimeWindow window = getAlignQueryTimeWindow(&interval, ts - trigger); - - p->pBlock->info.window.skey = window.skey; - p->pBlock->info.window.ekey = TMAX(ts, window.ekey); - } else { - int64_t skey = pLatestWindow->skey + trigger; - p->pBlock->info.window.skey = skey; - p->pBlock->info.window.ekey = TMAX(ts, skey + trigger); - } - + p->pBlock->info.window.skey = skey; + p->pBlock->info.window.ekey = TMAX(ts, skey + interval); p->pBlock->info.type = STREAM_GET_RESULT; + stDebug("s-task:%s force_window_close trigger block generated, window range:%" PRId64 "-%" PRId64, id, p->pBlock->info.window.skey, p->pBlock->info.window.ekey); diff --git a/source/libs/stream/src/streamSched.c b/source/libs/stream/src/streamSched.c index 72d1859197..468c5f2139 100644 --- a/source/libs/stream/src/streamSched.c +++ b/source/libs/stream/src/streamSched.c @@ -17,6 +17,9 @@ #include "streamInt.h" #include "ttimer.h" +#define TRIGGER_RECHECK_INTERVAL (5 * 1000) +#define INITIAL_TRIGGER_INTERVAL (120 * 1000) + static void streamTaskResumeHelper(void* param, void* tmrId); static void streamTaskSchedHelper(void* param, void* tmrId); @@ -32,8 +35,8 @@ void streamSetupScheduleTrigger(SStreamTask* pTask) { // dynamic set the trigger & triggerParam for STREAM_TRIGGER_FORCE_WINDOW_CLOSE if ((pTask->info.trigger == STREAM_TRIGGER_FORCE_WINDOW_CLOSE) && (pTask->info.taskLevel == TASK_LEVEL__SOURCE)) { - int64_t waterMark = 0; - SInterval interval = {0}; + int64_t waterMark = 0; + SInterval interval = {0}; STimeWindow lastTimeWindow = {0}; code = qGetStreamIntervalExecInfo(pTask->exec.pExecutor, &waterMark, &interval, &lastTimeWindow); if (code) { @@ -46,21 +49,32 @@ void streamSetupScheduleTrigger(SStreamTask* pTask) { pTask->info.interval = interval; // calculate the first start timestamp - int64_t now = taosGetTimestamp(interval.precision); + int64_t now = taosGetTimestamp(interval.precision); STimeWindow curWin = getAlignQueryTimeWindow(&pTask->info.interval, now); - delay = (curWin.ekey + 1) - now + waterMark; if (lastTimeWindow.skey == INT64_MIN) { // start from now, not the exec task timestamp after delay pTask->status.latestForceWindow.skey = curWin.skey - pTask->info.interval.interval; pTask->status.latestForceWindow.ekey = now; + + delay = (curWin.ekey + 1) - now + waterMark; + delay = convertTimePrecision(delay, interval.precision, TSDB_TIME_PRECISION_MILLI); + } else { pTask->status.latestForceWindow = lastTimeWindow; + // It's the current calculated time window + int64_t calEkey = lastTimeWindow.skey + pTask->info.interval.interval * 2; + if (calEkey + waterMark < now) { // unfinished time window existed + delay = INITIAL_TRIGGER_INTERVAL; // wait for 2min to start to calculate + } else { + delay = (curWin.ekey + 1) - now + waterMark; + delay = convertTimePrecision(delay, interval.precision, TSDB_TIME_PRECISION_MILLI); + } } stInfo("s-task:%s extract interval info from executor, wm:%" PRId64 " interval:%" PRId64 " unit:%c sliding:%" PRId64 - " unit:%c, initial start after:%" PRId64" last_win:%"PRId64"-%"PRId64, - id, waterMark, interval.interval, interval.intervalUnit, interval.sliding, interval.slidingUnit, delay, pTask->status.latestForceWindow.skey, - pTask->status.latestForceWindow.ekey); + " unit:%c, initial start after:%" PRId64 "ms last_win:%" PRId64 "-%" PRId64, + id, waterMark, interval.interval, interval.intervalUnit, interval.sliding, interval.slidingUnit, delay, + pTask->status.latestForceWindow.skey, pTask->status.latestForceWindow.ekey); } else { delay = pTask->info.delaySchedParam; if (delay == 0) { @@ -73,8 +87,8 @@ void streamSetupScheduleTrigger(SStreamTask* pTask) { stDebug("s-task:%s refId:%" PRId64 " enable the scheduler trigger, delay:%" PRId64, pTask->id.idStr, pTask->id.refId, delay); - streamTmrStart(streamTaskSchedHelper, (int32_t)delay, pTaskRefId, streamTimer, - &pTask->schedInfo.pDelayTimer, pTask->pMeta->vgId, "sched-tmr"); + streamTmrStart(streamTaskSchedHelper, (int32_t)delay, pTaskRefId, streamTimer, &pTask->schedInfo.pDelayTimer, + pTask->pMeta->vgId, "sched-tmr"); pTask->schedInfo.status = TASK_TRIGGER_STATUS__INACTIVE; } } @@ -172,7 +186,62 @@ void streamTaskResumeHelper(void* param, void* tmrId) { streamTaskFreeRefId(param); } +static int32_t doCreateForceWindowTrigger(SStreamTask* pTask, int32_t* pNextTrigger) { + int32_t num = 0; + int32_t code = 0; + const char* id = pTask->id.idStr; + int8_t precision = pTask->info.interval.precision; + SStreamTrigger* pTrigger = NULL; + + while (1) { + code = streamCreateForcewindowTrigger(&pTrigger, pTask->info.delaySchedParam, &pTask->info.interval, + &pTask->status.latestForceWindow, id); + if (code != 0) { + *pNextTrigger = convertTimePrecision(*pNextTrigger, precision, TSDB_TIME_PRECISION_MILLI); + stError("s-task:%s failed to prepare force window close trigger, code:%s, try again in %dms", id, + tstrerror(code), *pNextTrigger); + return code; + } + + // in the force window close model, status trigger does not matter. So we do not set the trigger model + code = streamTaskPutDataIntoInputQ(pTask, (SStreamQueueItem*)pTrigger); + if (code != TSDB_CODE_SUCCESS) { + stError("s-task:%s failed to put retrieve aggRes block into q, code:%s", pTask->id.idStr, tstrerror(code)); + return code; + } + + num += 1; + + // check whether the time window gaps exist or not + int64_t now = taosGetTimestamp(precision); + int64_t ekey = pTrigger->pBlock->info.window.skey + pTask->info.interval.interval; + + // there are gaps, needs to be filled + STimeWindow w = pTrigger->pBlock->info.window; + w.ekey = w.skey + pTask->info.interval.interval; + if (w.skey <= pTask->status.latestForceWindow.skey) { + stFatal("s-task:%s invalid new time window in force_window_close trigger model, skey:%" PRId64 + " should be greater than latestForceWindow skey:%" PRId64, + pTask->id.idStr, w.skey, pTask->status.latestForceWindow.skey); + } + + pTask->status.latestForceWindow = w; + if (ekey + pTask->info.watermark + pTask->info.interval.interval > now) { + int64_t prev = convertTimePrecision(*pNextTrigger, precision, TSDB_TIME_PRECISION_MILLI); + + *pNextTrigger = ekey + pTask->info.watermark + pTask->info.interval.interval - now; + *pNextTrigger = convertTimePrecision(*pNextTrigger, precision, TSDB_TIME_PRECISION_MILLI); + stDebug("s-task:%s generate %d time window(s), trigger delay adjust from %" PRId64 " to %d", id, num, prev, + *pNextTrigger); + return code; + } else { + stDebug("s-task:%s gap exist for force_window_close, current force_window_skey:%" PRId64, id, w.skey); + } + } +} + void streamTaskSchedHelper(void* param, void* tmrId) { + int32_t code = 0; int64_t taskRefId = *(int64_t*)param; SStreamTask* pTask = taosAcquireRef(streamTaskRefPool, taskRefId); if (pTask == NULL) { @@ -181,15 +250,20 @@ void streamTaskSchedHelper(void* param, void* tmrId) { return; } - stDebug("s-task:%s acquire task, refId:%"PRId64, pTask->id.idStr, pTask->id.refId); + stDebug("s-task:%s acquire task, refId:%" PRId64, pTask->id.idStr, pTask->id.refId); - const char* id = pTask->id.idStr; - int32_t nextTrigger = (int32_t)pTask->info.delaySchedParam; - int32_t vgId = pTask->pMeta->vgId; - int32_t code = 0; + const char* id = pTask->id.idStr; + int32_t nextTrigger = (int32_t)pTask->info.delaySchedParam; + int32_t vgId = pTask->pMeta->vgId; int8_t status = atomic_load_8(&pTask->schedInfo.status); - stTrace("s-task:%s in scheduler, trigger status:%d, next:%dms", id, status, nextTrigger); + + if (pTask->info.trigger == STREAM_TRIGGER_FORCE_WINDOW_CLOSE && pTask->info.taskLevel == TASK_LEVEL__SOURCE) { + int32_t next = convertTimePrecision(nextTrigger, pTask->info.interval.precision, TSDB_TIME_PRECISION_MILLI); + stTrace("s-task:%s in scheduler, trigger status:%d, next:%dms", id, status, next); + } else { + stTrace("s-task:%s in scheduler, trigger status:%d, next:%dms", id, status, nextTrigger); + } if (streamTaskShouldStop(pTask)) { stDebug("s-task:%s should stop, jump out of schedTimer", id); @@ -199,60 +273,30 @@ void streamTaskSchedHelper(void* param, void* tmrId) { } if (streamTaskShouldPause(pTask)) { - stDebug("s-task:%s is paused, recheck in %.2fs", id, nextTrigger/1000.0); - streamTmrStart(streamTaskSchedHelper, nextTrigger, param, streamTimer, &pTask->schedInfo.pDelayTimer, vgId, - "sched-run-tmr"); + stDebug("s-task:%s is paused, recheck in %.2fs", id, TRIGGER_RECHECK_INTERVAL / 1000.0); + streamTmrStart(streamTaskSchedHelper, TRIGGER_RECHECK_INTERVAL, param, streamTimer, &pTask->schedInfo.pDelayTimer, + vgId, "sched-run-tmr"); + streamMetaReleaseTask(pTask->pMeta, pTask); + return; + } + + if (pTask->status.downstreamReady == 0) { + stDebug("s-task:%s downstream not ready, recheck in %.2fs", id, TRIGGER_RECHECK_INTERVAL / 1000.0); + streamTmrStart(streamTaskSchedHelper, TRIGGER_RECHECK_INTERVAL, param, streamTimer, &pTask->schedInfo.pDelayTimer, + vgId, "sched-run-tmr"); streamMetaReleaseTask(pTask->pMeta, pTask); return; } if (streamTaskGetStatus(pTask).state == TASK_STATUS__CK) { - stDebug("s-task:%s in checkpoint procedure, not retrieve result, next:%dms", id, nextTrigger); + nextTrigger = TRIGGER_RECHECK_INTERVAL; // retry in 10 seec + stDebug("s-task:%s in checkpoint procedure, not retrieve result, next:%dms", id, TRIGGER_RECHECK_INTERVAL); } else { if (pTask->info.trigger == STREAM_TRIGGER_FORCE_WINDOW_CLOSE && pTask->info.taskLevel == TASK_LEVEL__SOURCE) { - int32_t num = 0; - SStreamTrigger* pTrigger = NULL; - - while (1) { - code = streamCreateForcewindowTrigger(&pTrigger, pTask->info.delaySchedParam, &pTask->info.interval, - &pTask->status.latestForceWindow, id); - if (code != 0) { - stError("s-task:%s failed to prepare force window close trigger, code:%s, try again in %dms", id, - tstrerror(code), nextTrigger); - goto _end; - } - - // in the force window close model, status trigger does not matter. So we do not set the trigger model - code = streamTaskPutDataIntoInputQ(pTask, (SStreamQueueItem*)pTrigger); - if (code != TSDB_CODE_SUCCESS) { - stError("s-task:%s failed to put retrieve aggRes block into q, code:%s", pTask->id.idStr, tstrerror(code)); - goto _end; - } - - num += 1; - - // check whether the time window gaps exist or not - int64_t now = taosGetTimestamp(pTask->info.interval.precision); - int64_t intervalEndTs = pTrigger->pBlock->info.window.skey + pTask->info.interval.interval; - - // there are gaps, needs to be filled - STimeWindow w = pTrigger->pBlock->info.window; - w.ekey = w.skey + pTask->info.interval.interval; - if (w.skey <= pTask->status.latestForceWindow.skey) { - stFatal("s-task:%s invalid new time window in force_window_close trigger model, skey:%" PRId64 - " should be greater than latestForceWindow skey:%" PRId64, - pTask->id.idStr, w.skey, pTask->status.latestForceWindow.skey); - } - - pTask->status.latestForceWindow = w; - if (intervalEndTs + pTask->info.watermark + pTask->info.interval.interval > now) { - stDebug("s-task:%s generate %d time window(s)", id, num); - break; - } else { - stDebug("s-task:%s gap exist for force_window_close, current force_window_skey:%" PRId64, id, w.skey); - } + code = doCreateForceWindowTrigger(pTask, &nextTrigger); + if (code != TSDB_CODE_SUCCESS) { + goto _end; } - } else if (status == TASK_TRIGGER_STATUS__MAY_ACTIVE) { SStreamTrigger* pTrigger = NULL; code = streamCreateSinkResTrigger(&pTrigger); From 05e9032fb90a1d20feef96cd97c0c6c492fee649 Mon Sep 17 00:00:00 2001 From: Haojun Liao Date: Thu, 28 Nov 2024 15:29:43 +0800 Subject: [PATCH 16/31] test(stream): add test cases. --- tests/script/tsim/stream/forcewindowclose.sim | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/tests/script/tsim/stream/forcewindowclose.sim b/tests/script/tsim/stream/forcewindowclose.sim index 77def52b3c..f591f91ab9 100644 --- a/tests/script/tsim/stream/forcewindowclose.sim +++ b/tests/script/tsim/stream/forcewindowclose.sim @@ -18,7 +18,47 @@ sql create stable st(ts timestamp, a int) tags(t int); sql create table tu1 using st tags(1); sql create stream stream1 trigger force_window_close into str_dst as select _wstart, count(*) from st partition by tbname interval(5s); +run tsim/stream/checkTaskStatus.sim +sql insert into tu1 values(now, 1); +sleep 5500 + +sql pause stream stream1 + +$loop_count = 0 + +loop1: +sleep 500 +$loop_count = $loop_count + 1 +if $loop_count == 20 then + goto end_loop1 +endi + +sql insert into tu1 values(now, 1); +goto loop1 + +end_loop1: +sql resume stream stream1 +sleep 5000 + +sql select * from str_dst + +if $rows != 4 then + print expect 4, actual: $rows + return -1 +endi + +sql drop database test + +print ============ test on micro precision db +print ============ create db +sql create database test vgroups 2 precision 'us'; + +sql use test +sql create stable st(ts timestamp, a int) tags(t int); +sql create table tu1 using st tags(1); + +sql create stream stream1 trigger force_window_close into str_dst as select _wstart, count(*) from st partition by tbname interval(5s); run tsim/stream/checkTaskStatus.sim sql insert into tu1 values(now, 1); @@ -41,10 +81,13 @@ goto loop0 end_loop: sql resume stream stream1 +sleep 5000 + sql select * from str_dst -if $rows != 3 then - print expect 3, actual: $rows +if $rows != 4 then + print expect 4, actual: $rows + return -1 endi system sh/exec.sh -n dnode1 -s stop -x SIGINT From 6580e7751f1d21e11d99c92a85ced288f8de5afa Mon Sep 17 00:00:00 2001 From: Haojun Liao Date: Thu, 28 Nov 2024 16:57:11 +0800 Subject: [PATCH 17/31] test: add test cases. --- tests/script/tsim/stream/forcewindowclose.sim | 61 ++++++++++++++++--- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/tests/script/tsim/stream/forcewindowclose.sim b/tests/script/tsim/stream/forcewindowclose.sim index f591f91ab9..ab54278e39 100644 --- a/tests/script/tsim/stream/forcewindowclose.sim +++ b/tests/script/tsim/stream/forcewindowclose.sim @@ -4,7 +4,7 @@ system sh/exec.sh -n dnode1 -s start sleep 50 sql connect -print =============== create database +print ========================================== create database sql create database test vgroups 2; sql select * from information_schema.ins_databases if $rows != 3 then @@ -41,16 +41,16 @@ end_loop1: sql resume stream stream1 sleep 5000 -sql select * from str_dst +sql select sum(`count(*)`) from (select * from str_dst) -if $rows != 4 then - print expect 4, actual: $rows +if $data00 != 20 then + print expect 20, actual: $data00 return -1 endi sql drop database test -print ============ test on micro precision db +print ===================================== micro precision db test print ============ create db sql create database test vgroups 2 precision 'us'; @@ -83,10 +83,55 @@ end_loop: sql resume stream stream1 sleep 5000 -sql select * from str_dst +sql select sum(`count(*)`) from (select * from str_dst) -if $rows != 4 then - print expect 4, actual: $rows +if $data00 != 20 then + print expect 20, actual: $data00 + return -1 +endi + +sql drop stream stream1 +sql drop table str_dst + +print ============================= too long watermark test +sql drop table tu1; +sql create table tu1 using st tags(1); +sql create stream stream2 trigger force_window_close watermark 30s into str_dst as select _wstart, count(*), now() from st partition by tbname interval(5s); +run tsim/stream/checkTaskStatus.sim + +$loop_count = 0 + +loop2: +sleep 500 +$loop_count = $loop_count + 1 +if $loop_count == 20 then + goto end_loop3 +endi + +sql insert into tu1 values(now, 1); +goto loop2 + +end_loop3: + +sql select count(*) from str_dst +print =================rows: $data00 + +if $data00 != 0 then + print expect 0, actual $data00 + return -1 +endi + +sleep 35000 + +sql select sum(`count(*)`) from (select * from str_dst) +if $data00 != 19 then + print expect 19, actual: $data00 + return -1 +endi + +sql select round(timediff(`now()`, `_wstart`)/1000000) from str_dst; +if $data00 != 35.000000000 then + print expect 35.000000000 , actual $data00 return -1 endi From 531a5ba8d0c4d0619c4cd2ba5e42d3fa8603f16a Mon Sep 17 00:00:00 2001 From: chenhaoran Date: Thu, 28 Nov 2024 19:20:52 +0800 Subject: [PATCH 18/31] ci: add smoking scripts for docs --- packaging/smokeTest/test_smoking_selfhost.sh | 50 +++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) mode change 100644 => 100755 packaging/smokeTest/test_smoking_selfhost.sh diff --git a/packaging/smokeTest/test_smoking_selfhost.sh b/packaging/smokeTest/test_smoking_selfhost.sh old mode 100644 new mode 100755 index d8099dfaea..144c37f2a7 --- a/packaging/smokeTest/test_smoking_selfhost.sh +++ b/packaging/smokeTest/test_smoking_selfhost.sh @@ -4,11 +4,13 @@ LOG_FILE="test_server.log" SUCCESS_FILE="success.txt" FAILED_FILE="failed.txt" +REPORT_FILE="report.txt" # Initialize/clear result files > "$SUCCESS_FILE" > "$FAILED_FILE" > "$LOG_FILE" +> "$REPORT_FILE" # Switch to the target directory TARGET_DIR="../../tests/system-test/" @@ -22,17 +24,9 @@ else exit 1 fi -# Define the Python commands to execute :case list +# Define the Python commands to execute commands=( "python3 ./test.py -f 2-query/join.py" - "python3 ./test.py -f 1-insert/insert_column_value.py" - "python3 ./test.py -f 2-query/primary_ts_base_5.py" - "python3 ./test.py -f 2-query/case_when.py" - "python3 ./test.py -f 2-query/partition_limit_interval.py" - "python3 ./test.py -f 2-query/fill.py" - "python3 ./test.py -f query/query_basic.py -N 3" - "python3 ./test.py -f 7-tmq/basic5.py" - "python3 ./test.py -f 8-stream/stream_basic.py" "python3 ./test.py -f 6-cluster/5dnode3mnodeStop.py -N 5 -M 3" ) @@ -45,6 +39,7 @@ fail_count=0 for cmd in "${commands[@]}" do echo "===== Executing Command: $cmd =====" | tee -a "$LOG_FILE" + # Execute the command and append output and errors to the log file eval "$cmd" >> "$LOG_FILE" 2>&1 exit_code=$? @@ -58,6 +53,7 @@ do echo "$cmd" >> "$FAILED_FILE" ((fail_count++)) fi + echo "" | tee -a "$LOG_FILE" # Add an empty line for separation done @@ -72,23 +68,31 @@ if [ $fail_count -ne 0 ]; then echo "The following commands failed:" | tee -a "$LOG_FILE" cat "$FAILED_FILE" | tee -a "$LOG_FILE" else - echo "All commands executed successfully." | tee -a "$LOG_FILE" + echo "All commands executed successfully. Deleting log and result files..." | tee -a "$LOG_FILE" + rm -f "$LOG_FILE" "$SUCCESS_FILE" "$FAILED_FILE" "$REPORT_FILE" + echo "Log and result files deleted." fi -# Optional: Generate a separate report file -echo "" > "report.txt" -echo "===== Test Report =====" >> "report.txt" -echo "Total Commands Executed: $total" >> "report.txt" -echo "Successful: $success_count" >> "report.txt" -echo "Failed: $fail_count" >> "report.txt" - +# Generate a separate report file if there are failed commands if [ $fail_count -ne 0 ]; then - echo "" >> "report.txt" - echo "The following commands failed:" >> "report.txt" - cat "$FAILED_FILE" >> "report.txt" + echo "" >> "$REPORT_FILE" + echo "===== Test Report =====" >> "$REPORT_FILE" + echo "Total Commands Executed: $total" >> "$REPORT_FILE" + echo "Successful: $success_count" >> "$REPORT_FILE" + echo "Failed: $fail_count" >> "$REPORT_FILE" + echo "" >> "$REPORT_FILE" + echo "The following commands failed:" >> "$REPORT_FILE" + cat "$FAILED_FILE" >> "$REPORT_FILE" else - echo "All commands executed successfully." >> "report.txt" + echo "===== Test Report =====" > "$REPORT_FILE" + echo "Total Commands Executed: $total" >> "$REPORT_FILE" + echo "Successful: $success_count" >> "$REPORT_FILE" + echo "Failed: $fail_count" >> "$REPORT_FILE" + echo "All commands executed successfully." >> "$REPORT_FILE" fi -echo "Detailed logs can be found in $LOG_FILE" -echo "Test report can be found in report.txt" \ No newline at end of file +# Print the absolute paths of the log and result files +echo "Detailed logs can be found in: $(realpath "$LOG_FILE")" +echo "Successful commands can be found in: $(realpath "$SUCCESS_FILE")" +echo "Failed commands can be found in: $(realpath "$FAILED_FILE")" +echo "Test report can be found in: $(realpath "$REPORT_FILE")" \ No newline at end of file From fb70b8e2152508f26a85a9acfb20b464549feda1 Mon Sep 17 00:00:00 2001 From: chenhaoran Date: Thu, 28 Nov 2024 19:21:44 +0800 Subject: [PATCH 19/31] ci: add smoking scripts for docs --- packaging/smokeTest/test_smoking_selfhost.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packaging/smokeTest/test_smoking_selfhost.sh b/packaging/smokeTest/test_smoking_selfhost.sh index 144c37f2a7..e92abfda0d 100755 --- a/packaging/smokeTest/test_smoking_selfhost.sh +++ b/packaging/smokeTest/test_smoking_selfhost.sh @@ -28,6 +28,14 @@ fi commands=( "python3 ./test.py -f 2-query/join.py" "python3 ./test.py -f 6-cluster/5dnode3mnodeStop.py -N 5 -M 3" + "python3 ./test.py -f 1-insert/insert_column_value.py" + "python3 ./test.py -f 2-query/primary_ts_base_5.py" + "python3 ./test.py -f 2-query/case_when.py" + "python3 ./test.py -f 2-query/partition_limit_interval.py" + "python3 ./test.py -f 2-query/fill.py" + "python3 ./test.py -f query/query_basic.py -N 3" + "python3 ./test.py -f 7-tmq/basic5.py" + "python3 ./test.py -f 8-stream/stream_basic.py" ) # Counters From 9dc4c64a95672e1aece1a38e6496fdad5994bd8c Mon Sep 17 00:00:00 2001 From: chenhaoran Date: Thu, 28 Nov 2024 19:32:47 +0800 Subject: [PATCH 20/31] ci: add smoking scripts for docs --- packaging/smokeTest/test_smoking_selfhost.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/packaging/smokeTest/test_smoking_selfhost.sh b/packaging/smokeTest/test_smoking_selfhost.sh index e92abfda0d..a25c5a6d90 100755 --- a/packaging/smokeTest/test_smoking_selfhost.sh +++ b/packaging/smokeTest/test_smoking_selfhost.sh @@ -33,7 +33,6 @@ commands=( "python3 ./test.py -f 2-query/case_when.py" "python3 ./test.py -f 2-query/partition_limit_interval.py" "python3 ./test.py -f 2-query/fill.py" - "python3 ./test.py -f query/query_basic.py -N 3" "python3 ./test.py -f 7-tmq/basic5.py" "python3 ./test.py -f 8-stream/stream_basic.py" ) From 3239a10b76c43e88b6e4fc12fff39d6c433115df Mon Sep 17 00:00:00 2001 From: wangjiaming0909 <604227650@qq.com> Date: Thu, 28 Nov 2024 18:29:20 +0800 Subject: [PATCH 21/31] support interp fill extension --- docs/en/14-reference/03-taos-sql/06-select.md | 10 +- .../14-reference/03-taos-sql/10-function.md | 3 + docs/zh/14-reference/03-taos-sql/06-select.md | 10 +- .../14-reference/03-taos-sql/10-function.md | 3 + include/common/tmsg.h | 2 + include/libs/function/functionMgt.h | 2 + include/libs/nodes/plannodes.h | 4 + include/libs/nodes/querynodes.h | 9 +- source/libs/executor/inc/executorInt.h | 1 + source/libs/executor/src/executil.c | 3 + source/libs/executor/src/timesliceoperator.c | 139 +++-- source/libs/function/src/builtins.c | 14 + source/libs/function/src/functionMgt.c | 7 + source/libs/nodes/src/nodesCloneFuncs.c | 2 + source/libs/nodes/src/nodesCodeFuncs.c | 28 + source/libs/nodes/src/nodesMsgFuncs.c | 16 +- source/libs/nodes/src/nodesUtilFuncs.c | 9 + source/libs/parser/inc/parAst.h | 1 + source/libs/parser/inc/sql.y | 33 +- source/libs/parser/src/parAstCreater.c | 25 +- source/libs/parser/src/parTokenizer.c | 2 + source/libs/parser/src/parTranslater.c | 122 ++++- source/libs/planner/src/planLogicCreater.c | 10 + source/libs/planner/src/planPhysiCreater.c | 2 + tests/parallel_test/cases.task | 5 + tests/system-test/2-query/interp_extension.py | 518 ++++++++++++++++++ 26 files changed, 922 insertions(+), 58 deletions(-) create mode 100644 tests/system-test/2-query/interp_extension.py diff --git a/docs/en/14-reference/03-taos-sql/06-select.md b/docs/en/14-reference/03-taos-sql/06-select.md index e6e1ca65c2..6b3ab751ec 100644 --- a/docs/en/14-reference/03-taos-sql/06-select.md +++ b/docs/en/14-reference/03-taos-sql/06-select.md @@ -62,7 +62,8 @@ window_clause: { | COUNT_WINDOW(count_val[, sliding_val]) interp_clause: - RANGE(ts_val [, ts_val]) EVERY(every_val) FILL(fill_mod_and_val) + RANGE(ts_val [, ts_val]) EVERY(every_val) FILL(fill_mod_and_val) + | RANGE(ts_val, interval_val) FILL(fill_mod_and_val) partition_by_clause: PARTITION BY partition_by_expr [, partition_by_expr] ... @@ -256,6 +257,13 @@ The \_irowts pseudo column can only be used with the interp function to return t SELECT _irowts, interp(current) FROM meters RANGE('2020-01-01 10:00:00', '2020-01-01 10:30:00') EVERY(1s) FILL(linear); ``` +** \_IROWTS\_ORIGIN** +Pseudo column `_irowts_origin` is used to get the original timestamp of the row used for filling. It can only be used with the INTERP query. `_irowts_origin` is not supported in stream. Only FILL PREV/NEXT/NEAR is supported. If there is not data in range, return NULL. + +```sql +SELECT _irowts_origin, interp(current) FROM meters RANGE('2020-01-01 10:00:00', '2020-01-01 10:30:00') EVERY(1s) FILL(PREV); +``` + ## Query Objects After the FROM keyword, there can be several table (supertable) lists, or the results of subqueries. diff --git a/docs/en/14-reference/03-taos-sql/10-function.md b/docs/en/14-reference/03-taos-sql/10-function.md index 521a8bc8ca..ff52125cfd 100644 --- a/docs/en/14-reference/03-taos-sql/10-function.md +++ b/docs/en/14-reference/03-taos-sql/10-function.md @@ -1869,6 +1869,9 @@ INTERP(expr [, ignore_null_values]) - INTERP can be used with the pseudo-column _irowts to return the timestamp corresponding to the interpolation point (supported from version 3.0.2.0 onwards). - INTERP can also be used with the pseudo-column _isfilled to show whether the returned result is from the original record or produced by the interpolation algorithm (supported from version 3.0.3.0 onwards). - When querying a table with a composite primary key, if there are multiple records with the same timestamp, only the data corresponding to the minimum composite primary key will participate in the calculation. +- `INTERP` support NEAR fill mode. When `FILL(NEAR)` is used, the nearest value to the interpolation point is used to fill the missing value. If there are multiple values with the same distance to the interpolation point, previous row is used. NEAR fill is not supported in stream computation. For example, `SELECT _irowts,INTERP(current) FROM test.meters RANGE('2017-07-22 00:00:00','2017-07-24 12:25:00') EVERY(1h) FILL(NEAR)`. +- Psedo column `_irowts_origin` can be used along with `INTERP` only when using NEAR/NEXT/PREV fill mode. +- `INTERP` RANGE clause support INTERVAL extension, like `RANGE('2023-01-01 00:00:00', 1d)`. The second parameter is the interval length, and the unit cannot use y(year), n(month). The interval length must be an integer, with no quotes, the value can't be 0. The interval length is used to restrict the search range from the time point specified. For example, `SELECT _irowts,INTERP(current) FROM test.meters RANGE('2017-07-22 00:00:00', 1d) FILL(NEAR, 1)`. The query will return the interpolation result of the current column within the range of 1 day from the time point '2017-07-22 00:00:00'. If there is no data within the range, the specified value in FILL will be used. Only FILL PREV/NEXT/NEAR is supported in this case. It's illegal to use `EVERY` clause and NOT specify values in FILL clause in this case. None data-point range clause with INTERVAL extension is not supported currently, like `RANGE('2017-07-22 00:00:00', '2017-07-22 12:00:00', 1h)` is not supported. ### LAST diff --git a/docs/zh/14-reference/03-taos-sql/06-select.md b/docs/zh/14-reference/03-taos-sql/06-select.md index af19559c81..28b9346225 100644 --- a/docs/zh/14-reference/03-taos-sql/06-select.md +++ b/docs/zh/14-reference/03-taos-sql/06-select.md @@ -62,7 +62,8 @@ window_clause: { | COUNT_WINDOW(count_val[, sliding_val]) interp_clause: - RANGE(ts_val [, ts_val]) EVERY(every_val) FILL(fill_mod_and_val) + RANGE(ts_val [, ts_val]) EVERY(every_val) FILL(fill_mod_and_val) + | RANGE(ts_val, interval_val) FILL(fill_mod_and_val) partition_by_clause: PARTITION BY partition_by_expr [, partition_by_expr] ... @@ -255,6 +256,13 @@ select _rowts, max(current) from meters; select _irowts, interp(current) from meters range('2020-01-01 10:00:00', '2020-01-01 10:30:00') every(1s) fill(linear); ``` +**\_IROWTS\_ORIGIN** +`_irowts_origin` 伪列只能与 interp 函数一起使用,不支持在流计算中使用, 仅适用于FILL类型为PREV/NEXT/NEAR, 用于返回 interp 函数所使用的原始数据的时间戳列。若范围内无值, 则返回 NULL。 + +```sql +select _iorwts_origin, interp(current) from meters range('2020-01-01 10:00:00', '2020-01-01 10:30:00') every(1s) fill(NEXT); +``` + ## 查询对象 FROM 关键字后面可以是若干个表(超级表)列表,也可以是子查询的结果。 diff --git a/docs/zh/14-reference/03-taos-sql/10-function.md b/docs/zh/14-reference/03-taos-sql/10-function.md index 2f4b739447..244c1e156a 100644 --- a/docs/zh/14-reference/03-taos-sql/10-function.md +++ b/docs/zh/14-reference/03-taos-sql/10-function.md @@ -1838,6 +1838,9 @@ ignore_null_values: { - INTERP 可以与伪列 _irowts 一起使用,返回插值点所对应的时间戳(3.0.2.0 版本以后支持)。 - INTERP 可以与伪列 _isfilled 一起使用,显示返回结果是否为原始记录或插值算法产生的数据(3.0.3.0 版本以后支持)。 - INTERP 对于带复合主键的表的查询,若存在相同时间戳的数据,则只有对应的复合主键最小的数据参与运算。 +- INTERP 查询支持新的NEAR FILL模式, 即当需要FILL时, 使用距离当前时间点最近的数据进行插值, 当前后时间戳与当前时间断面一样近时, FILL 前一行的值. 此模式在流计算中和窗口查询中不支持。例如: SELECT INTERP(col) FROM tb RANGE('2023-01-01 00:00:00', '2023-01-01 00:10:00') FILL(NEAR)。 +- INTERP 只有在使用FILL PREV/NEXT/NEAR 模式时才可以使用伪列 `_irowts_origin`。 +- INTERP `RANEG`子句支持时间范围的扩展, 如`RANGE('2023-01-01 00:00:00', 10s)`表示在时间点'2023-01-01 00:00:00'查找前后10s的数据进行插值, FILL PREV/NEXT/NEAR分别表示从时间点向前/向后/前后查找数据, 若时间点周围没有数据, 则使用FILL指定的值进行插值, 因此此时FILL子句必须指定值。例如: SELECT INTERP(col) FROM tb RANGE('2023-01-01 00:00:00', 10s) FILL(PREV, 1). 目前仅支持时间点和时间范围的组合, 不支持时间区间和时间范围的组合, 即不支持RANGE('2023-01-01 00:00:00', '2023-02-01 00:00:00', 1h)。所指定的时间范围规则与EVERY类似, 单位不能是年或月, 值不能为0, 不能带引号。使用该扩展时, 不支持除FILL PREV/NEXT/NEAR外的其他FILL模式, 且不能指定EVERY子句 ### LAST diff --git a/include/common/tmsg.h b/include/common/tmsg.h index 26b15c2b76..3793b2b0f3 100644 --- a/include/common/tmsg.h +++ b/include/common/tmsg.h @@ -188,6 +188,7 @@ typedef enum _mgmt_table { #define TSDB_FILL_LINEAR 5 #define TSDB_FILL_PREV 6 #define TSDB_FILL_NEXT 7 +#define TSDB_FILL_NEAR 8 #define TSDB_ALTER_USER_PASSWD 0x1 #define TSDB_ALTER_USER_SUPERUSER 0x2 @@ -264,6 +265,7 @@ typedef enum ENodeType { QUERY_NODE_COLUMN_OPTIONS, QUERY_NODE_TSMA_OPTIONS, QUERY_NODE_ANOMALY_WINDOW, + QUERY_NODE_RANGE_AROUND, // Statement nodes are used in parser and planner module. QUERY_NODE_SET_OPERATOR = 100, diff --git a/include/libs/function/functionMgt.h b/include/libs/function/functionMgt.h index f71c2210be..bf40632953 100644 --- a/include/libs/function/functionMgt.h +++ b/include/libs/function/functionMgt.h @@ -155,6 +155,7 @@ typedef enum EFunctionType { FUNCTION_TYPE_FORECAST_LOW, FUNCTION_TYPE_FORECAST_HIGH, FUNCTION_TYPE_FORECAST_ROWTS, + FUNCTION_TYPE_IROWTS_ORIGIN, // internal function FUNCTION_TYPE_SELECT_VALUE = 3750, @@ -289,6 +290,7 @@ bool fmIsPrimaryKeyFunc(int32_t funcId); bool fmIsProcessByRowFunc(int32_t funcId); bool fmisSelectGroupConstValueFunc(int32_t funcId); bool fmIsElapsedFunc(int32_t funcId); +bool fmIsRowTsOriginFunc(int32_t funcId); void getLastCacheDataType(SDataType* pType, int32_t pkBytes); int32_t createFunction(const char* pName, SNodeList* pParameterList, SFunctionNode** pFunc); diff --git a/include/libs/nodes/plannodes.h b/include/libs/nodes/plannodes.h index 89bc27a1fa..09eec2a17a 100644 --- a/include/libs/nodes/plannodes.h +++ b/include/libs/nodes/plannodes.h @@ -213,6 +213,8 @@ typedef struct SInterpFuncLogicNode { EFillMode fillMode; SNode* pFillValues; // SNodeListNode SNode* pTimeSeries; // SColumnNode + int64_t rangeInterval; + int8_t rangeIntervalUnit; SStreamNodeOption streamNodeOption; } SInterpFuncLogicNode; @@ -528,6 +530,8 @@ typedef struct SInterpFuncPhysiNode { SNode* pFillValues; // SNodeListNode SNode* pTimeSeries; // SColumnNode SStreamNodeOption streamNodeOption; + int64_t rangeInterval; + int8_t rangeIntervalUnit; } SInterpFuncPhysiNode; typedef SInterpFuncPhysiNode SStreamInterpFuncPhysiNode; diff --git a/include/libs/nodes/querynodes.h b/include/libs/nodes/querynodes.h index 7af74a347a..bf84ffd3df 100644 --- a/include/libs/nodes/querynodes.h +++ b/include/libs/nodes/querynodes.h @@ -362,7 +362,8 @@ typedef enum EFillMode { FILL_MODE_NULL, FILL_MODE_NULL_F, FILL_MODE_LINEAR, - FILL_MODE_NEXT + FILL_MODE_NEXT, + FILL_MODE_NEAR, } EFillMode; typedef enum ETimeLineMode { @@ -407,6 +408,11 @@ typedef struct SWindowOffsetNode { SNode* pEndOffset; // SValueNode } SWindowOffsetNode; +typedef struct SRangeAroundNode { + ENodeType type; + SNode* pTimepoint; + SNode* pInterval; +} SRangeAroundNode; typedef struct SSelectStmt { ENodeType type; // QUERY_NODE_SELECT_STMT @@ -421,6 +427,7 @@ typedef struct SSelectStmt { SNodeList* pGroupByList; // SGroupingSetNode SNode* pHaving; SNode* pRange; + SNode* pRangeAround; SNode* pEvery; SNode* pFill; SNodeList* pOrderByList; // SOrderByExprNode diff --git a/source/libs/executor/inc/executorInt.h b/source/libs/executor/inc/executorInt.h index 271e9c91a1..1483ee0b46 100644 --- a/source/libs/executor/inc/executorInt.h +++ b/source/libs/executor/inc/executorInt.h @@ -1059,6 +1059,7 @@ void destroyFlusedPos(void* pRes); bool isIrowtsPseudoColumn(SExprInfo* pExprInfo); bool isIsfilledPseudoColumn(SExprInfo* pExprInfo); bool isInterpFunc(SExprInfo* pExprInfo); +bool isIrowtsOriginPseudoColumn(SExprInfo* pExprInfo); int32_t encodeSSessionKey(void** buf, SSessionKey* key); void* decodeSSessionKey(void* buf, SSessionKey* key); diff --git a/source/libs/executor/src/executil.c b/source/libs/executor/src/executil.c index 814833fca8..732e69ae09 100644 --- a/source/libs/executor/src/executil.c +++ b/source/libs/executor/src/executil.c @@ -2390,6 +2390,9 @@ int32_t convertFillType(int32_t mode) { case FILL_MODE_LINEAR: type = TSDB_FILL_LINEAR; break; + case FILL_MODE_NEAR: + type = TSDB_FILL_NEAR; + break; default: type = TSDB_FILL_NONE; } diff --git a/source/libs/executor/src/timesliceoperator.c b/source/libs/executor/src/timesliceoperator.c index 50deba932f..6a3052c75c 100644 --- a/source/libs/executor/src/timesliceoperator.c +++ b/source/libs/executor/src/timesliceoperator.c @@ -48,6 +48,7 @@ typedef struct STimeSliceOperatorInfo { int32_t remainIndex; // the remaining index in the block to be processed bool hasPk; SColumn pkCol; + int64_t rangeInterval; } STimeSliceOperatorInfo; static void destroyTimeSliceOperatorInfo(void* param); @@ -179,6 +180,11 @@ bool isIsfilledPseudoColumn(SExprInfo* pExprInfo) { return (IS_BOOLEAN_TYPE(pExprInfo->base.resSchema.type) && strcasecmp(name, "_isfilled") == 0); } +bool isIrowtsOriginPseudoColumn(SExprInfo* pExprInfo) { + const char* name = pExprInfo->pExpr->_function.functionName; + return (IS_TIMESTAMP_TYPE(pExprInfo->base.resSchema.type) && strcasecmp(name, "_irowts_origin") == 0); +} + static void tRowGetKeyFromColData(int64_t ts, SColumnInfoData* pPkCol, int32_t rowIndex, SRowKey* pKey) { pKey->ts = ts; pKey->numOfPKs = 1; @@ -277,6 +283,79 @@ bool checkNullRow(SExprSupp* pExprSup, SSDataBlock* pSrcBlock, int32_t index, bo return false; } +static int32_t interpColSetKey(SColumnInfoData* pDst, int32_t rowNum, SGroupKeys* pKey) { + int32_t code = 0; + if (pKey->isNull == false) { + code = colDataSetVal(pDst, rowNum, pKey->pData, false); + } else { + colDataSetNULL(pDst, rowNum); + } + return code; +} + +static bool interpSetFillRowWithRangeIntervalCheck(STimeSliceOperatorInfo* pSliceInfo, SArray** ppFillRow, SArray* pFillRefRow, int64_t fillRefRowTs) { + *ppFillRow = NULL; + if (pSliceInfo->rangeInterval <= 0 || llabs(fillRefRowTs - pSliceInfo->current) <= pSliceInfo->rangeInterval) { + *ppFillRow = pFillRefRow; + return true; + } + return false; +} + +static bool interpDetermineNearFillRow(STimeSliceOperatorInfo* pSliceInfo, SArray** ppNearRow) { + if (!pSliceInfo->isPrevRowSet && !pSliceInfo->isNextRowSet) { + *ppNearRow = NULL; + return false; + } + SGroupKeys *pPrevTsKey = NULL, *pNextTsKey = NULL; + int64_t* pPrevTs = NULL, *pNextTs = NULL; + if (pSliceInfo->isPrevRowSet) { + pPrevTsKey = taosArrayGet(pSliceInfo->pPrevRow, pSliceInfo->tsCol.slotId); + pPrevTs = (int64_t*)pPrevTsKey->pData; + } + if (pSliceInfo->isNextRowSet) { + pNextTsKey = taosArrayGet(pSliceInfo->pNextRow, pSliceInfo->tsCol.slotId); + pNextTs = (int64_t*)pNextTsKey->pData; + } + if (!pPrevTsKey) { + *ppNearRow = pSliceInfo->pNextRow; + (void)interpSetFillRowWithRangeIntervalCheck(pSliceInfo, ppNearRow, pSliceInfo->pNextRow, *pNextTs); + } else if (!pNextTsKey) { + *ppNearRow = pSliceInfo->pPrevRow; + (void)interpSetFillRowWithRangeIntervalCheck(pSliceInfo, ppNearRow, pSliceInfo->pPrevRow, *pPrevTs); + } else { + if (llabs(pSliceInfo->current - *pPrevTs) <= llabs(*pNextTs - pSliceInfo->current)) { + // take prev if euqal + (void)interpSetFillRowWithRangeIntervalCheck(pSliceInfo, ppNearRow, pSliceInfo->pPrevRow, *pPrevTs); + } else { + (void)interpSetFillRowWithRangeIntervalCheck(pSliceInfo, ppNearRow, pSliceInfo->pNextRow, *pNextTs); + } + } + return true; +} + +static bool interpDetermineFillRefRow(STimeSliceOperatorInfo* pSliceInfo, SArray** ppOutRow) { + bool needFill = false; + if (pSliceInfo->fillType == TSDB_FILL_PREV) { + if (pSliceInfo->isPrevRowSet) { + SGroupKeys* pTsCol = taosArrayGet(pSliceInfo->pPrevRow, pSliceInfo->tsCol.slotId); + (void)interpSetFillRowWithRangeIntervalCheck(pSliceInfo, ppOutRow, pSliceInfo->pPrevRow, *(int64_t*)pTsCol->pData); + needFill = true; + } + } else if (pSliceInfo->fillType == TSDB_FILL_NEXT) { + if (pSliceInfo->isNextRowSet) { + SGroupKeys* pTsCol = taosArrayGet(pSliceInfo->pNextRow, pSliceInfo->tsCol.slotId); + (void)interpSetFillRowWithRangeIntervalCheck(pSliceInfo, ppOutRow, pSliceInfo->pNextRow, *(int64_t*)pTsCol->pData); + needFill = true; + } + } else if (pSliceInfo->fillType == TSDB_FILL_NEAR) { + needFill = interpDetermineNearFillRow(pSliceInfo, ppOutRow); + } else { + needFill = true; + } + return needFill; +} + static bool genInterpolationResult(STimeSliceOperatorInfo* pSliceInfo, SExprSupp* pExprSup, SSDataBlock* pResBlock, SSDataBlock* pSrcBlock, int32_t index, bool beforeTs, SExecTaskInfo* pTaskInfo) { int32_t code = TSDB_CODE_SUCCESS; @@ -290,6 +369,8 @@ static bool genInterpolationResult(STimeSliceOperatorInfo* pSliceInfo, SExprSupp int32_t fillColIndex = 0; int32_t groupKeyIndex = 0; bool hasInterp = true; + SArray* pFillRefRow = NULL; + bool needFill = interpDetermineFillRefRow(pSliceInfo, &pFillRefRow); for (int32_t j = 0; j < pExprSup->numOfExprs; ++j) { SExprInfo* pExprInfo = &pExprSup->pExprInfo[j]; @@ -305,7 +386,7 @@ static bool genInterpolationResult(STimeSliceOperatorInfo* pSliceInfo, SExprSupp code = colDataSetVal(pDst, pResBlock->info.rows, (char*)&isFilled, false); QUERY_CHECK_CODE(code, lino, _end); continue; - } else if (!isInterpFunc(pExprInfo)) { + } else if (!isInterpFunc(pExprInfo) && !isIrowtsOriginPseudoColumn(pExprInfo)) { if (isGroupKeyFunc(pExprInfo) || isSelectGroupConstValueFunc(pExprInfo)) { if (pSrcBlock != NULL) { int32_t srcSlot = pExprInfo->base.pParam[0].pCol->slotId; @@ -344,7 +425,7 @@ static bool genInterpolationResult(STimeSliceOperatorInfo* pSliceInfo, SExprSupp continue; } - int32_t srcSlot = pExprInfo->base.pParam[0].pCol->slotId; + int32_t srcSlot = isIrowtsOriginPseudoColumn(pExprInfo) ? pSliceInfo->tsCol.slotId : pExprInfo->base.pParam[0].pCol->slotId; switch (pSliceInfo->fillType) { case TSDB_FILL_NULL: case TSDB_FILL_NULL_F: { @@ -352,6 +433,25 @@ static bool genInterpolationResult(STimeSliceOperatorInfo* pSliceInfo, SExprSupp break; } + case TSDB_FILL_PREV: + case TSDB_FILL_NEAR: + case TSDB_FILL_NEXT: { + if (!needFill) { + hasInterp = false; + break; + } + if (pFillRefRow) { + code = interpColSetKey(pDst, rows, taosArrayGet(pFillRefRow, srcSlot)); + QUERY_CHECK_CODE(code, lino, _end); + break; + } + // no fillRefRow, fall through to fill specified values + if (srcSlot == pSliceInfo->tsCol.slotId) { + // if is _irowts_origin, there is no value to fill, just set to null + colDataSetNULL(pDst, rows); + break; + } + } case TSDB_FILL_SET_VALUE: case TSDB_FILL_SET_VALUE_F: { SVariant* pVar = &pSliceInfo->pFillColInfo[fillColIndex].fillVal; @@ -444,38 +544,6 @@ static bool genInterpolationResult(STimeSliceOperatorInfo* pSliceInfo, SExprSupp taosMemoryFree(current.val); break; } - case TSDB_FILL_PREV: { - if (!pSliceInfo->isPrevRowSet) { - hasInterp = false; - break; - } - - SGroupKeys* pkey = taosArrayGet(pSliceInfo->pPrevRow, srcSlot); - if (pkey->isNull == false) { - code = colDataSetVal(pDst, rows, pkey->pData, false); - QUERY_CHECK_CODE(code, lino, _end); - } else { - colDataSetNULL(pDst, rows); - } - break; - } - - case TSDB_FILL_NEXT: { - if (!pSliceInfo->isNextRowSet) { - hasInterp = false; - break; - } - - SGroupKeys* pkey = taosArrayGet(pSliceInfo->pNextRow, srcSlot); - if (pkey->isNull == false) { - code = colDataSetVal(pDst, rows, pkey->pData, false); - QUERY_CHECK_CODE(code, lino, _end); - } else { - colDataSetNULL(pDst, rows); - } - break; - } - case TSDB_FILL_NONE: default: break; @@ -507,7 +575,7 @@ static int32_t addCurrentRowToResult(STimeSliceOperatorInfo* pSliceInfo, SExprSu int32_t dstSlot = pExprInfo->base.resSchema.slotId; SColumnInfoData* pDst = taosArrayGet(pResBlock->pDataBlock, dstSlot); - if (isIrowtsPseudoColumn(pExprInfo)) { + if (isIrowtsPseudoColumn(pExprInfo) || isIrowtsOriginPseudoColumn(pExprInfo)) { code = colDataSetVal(pDst, pResBlock->info.rows, (char*)&pSliceInfo->current, false); QUERY_CHECK_CODE(code, lino, _end); } else if (isIsfilledPseudoColumn(pExprInfo)) { @@ -1233,6 +1301,7 @@ int32_t createTimeSliceOperatorInfo(SOperatorInfo* downstream, SPhysiNode* pPhyN pInfo->pNextGroupRes = NULL; pInfo->pRemainRes = NULL; pInfo->remainIndex = 0; + pInfo->rangeInterval = pInterpPhyNode->rangeInterval; if (pInfo->hasPk) { pInfo->prevKey.numOfPKs = 1; diff --git a/source/libs/function/src/builtins.c b/source/libs/function/src/builtins.c index 5ce15a32b2..f8f8fc705e 100644 --- a/source/libs/function/src/builtins.c +++ b/source/libs/function/src/builtins.c @@ -5596,6 +5596,20 @@ const SBuiltinFuncDefinition funcMgtBuiltins[] = { .sprocessFunc = NULL, .finalizeFunc = NULL }, + { + .name = "_irowts_origin", + .type = FUNCTION_TYPE_IROWTS_ORIGIN, + .classification = FUNC_MGT_PSEUDO_COLUMN_FUNC | FUNC_MGT_INTERP_PC_FUNC | FUNC_MGT_KEEP_ORDER_FUNC, + .parameters = {.minParamNum = 0, + .maxParamNum = 0, + .paramInfoPattern = 0, + .outputParaInfo = {.validDataType = FUNC_PARAM_SUPPORT_TIMESTAMP_TYPE}}, + .translateFunc = translateTimePseudoColumn, + .getEnvFunc = getTimePseudoFuncEnv, + .initFunc = NULL, + .sprocessFunc = NULL, + .finalizeFunc = NULL + }, }; // clang-format on diff --git a/source/libs/function/src/functionMgt.c b/source/libs/function/src/functionMgt.c index 5dfb94ba6e..ccff881e49 100644 --- a/source/libs/function/src/functionMgt.c +++ b/source/libs/function/src/functionMgt.c @@ -703,3 +703,10 @@ bool fmIsMyStateFunc(int32_t funcId, int32_t stateFuncId) { bool fmIsCountLikeFunc(int32_t funcId) { return isSpecificClassifyFunc(funcId, FUNC_MGT_COUNT_LIKE_FUNC); } + +bool fmIsRowTsOriginFunc(int32_t funcId) { + if (funcId < 0 || funcId >= funcMgtBuiltinsNum) { + return false; + } + return FUNCTION_TYPE_IROWTS_ORIGIN == funcMgtBuiltins[funcId].type; +} diff --git a/source/libs/nodes/src/nodesCloneFuncs.c b/source/libs/nodes/src/nodesCloneFuncs.c index ba87912670..c2deec9c68 100644 --- a/source/libs/nodes/src/nodesCloneFuncs.c +++ b/source/libs/nodes/src/nodesCloneFuncs.c @@ -684,6 +684,8 @@ static int32_t logicInterpFuncCopy(const SInterpFuncLogicNode* pSrc, SInterpFunc CLONE_NODE_FIELD(pFillValues); CLONE_NODE_FIELD(pTimeSeries); COPY_OBJECT_FIELD(streamNodeOption, sizeof(SStreamNodeOption)); + COPY_SCALAR_FIELD(rangeInterval); + COPY_SCALAR_FIELD(rangeIntervalUnit); return TSDB_CODE_SUCCESS; } diff --git a/source/libs/nodes/src/nodesCodeFuncs.c b/source/libs/nodes/src/nodesCodeFuncs.c index f7f858db78..2d074dfc68 100644 --- a/source/libs/nodes/src/nodesCodeFuncs.c +++ b/source/libs/nodes/src/nodesCodeFuncs.c @@ -1298,6 +1298,8 @@ static const char* jkInterpFuncLogicPlanFillMode = "fillMode"; static const char* jkInterpFuncLogicPlanFillValues = "FillValues"; static const char* jkInterpFuncLogicPlanTimeSeries = "TimeSeries"; static const char* jkInterpFuncLogicPlanStreamNodeOption = "StreamNodeOption"; +static const char* jkInterpFuncLogicPlanRangeInterval = "RangeInterval"; +static const char* jkInterpFuncLogicPlanRangeIntervalUnit = "RangeIntervalUnit"; static int32_t logicInterpFuncNodeToJson(const void* pObj, SJson* pJson) { const SInterpFuncLogicNode* pNode = (const SInterpFuncLogicNode*)pObj; @@ -1333,6 +1335,12 @@ static int32_t logicInterpFuncNodeToJson(const void* pObj, SJson* pJson) { if (TSDB_CODE_SUCCESS == code) { code = tjsonAddObject(pJson, jkInterpFuncLogicPlanStreamNodeOption, streamNodeOptionToJson, &pNode->streamNodeOption); } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonAddIntegerToObject(pJson, jkInterpFuncLogicPlanRangeInterval, pNode->rangeInterval); + } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonAddIntegerToObject(pJson, jkInterpFuncLogicPlanRangeIntervalUnit, pNode->rangeIntervalUnit); + } return code; } @@ -1371,6 +1379,12 @@ static int32_t jsonToLogicInterpFuncNode(const SJson* pJson, void* pObj) { if (TSDB_CODE_SUCCESS == code) { code = tjsonToObject(pJson, jkInterpFuncLogicPlanStreamNodeOption, jsonToStreamNodeOption, &pNode->streamNodeOption); } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonGetBigIntValue(pJson, jkInterpFuncLogicPlanRangeInterval, &pNode->rangeInterval); + } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonGetTinyIntValue(pJson, jkInterpFuncLogicPlanRangeIntervalUnit, &pNode->rangeIntervalUnit); + } return code; } @@ -3312,6 +3326,8 @@ static const char* jkInterpFuncPhysiPlanFillMode = "FillMode"; static const char* jkInterpFuncPhysiPlanFillValues = "FillValues"; static const char* jkInterpFuncPhysiPlanTimeSeries = "TimeSeries"; static const char* jkInterpFuncPhysiPlanStreamNodeOption = "StreamNodeOption"; +static const char* jkInterpFuncPhysiPlanRangeInterval = "RangeInterval"; +static const char* jkInterpFuncPhysiPlanRangeIntervalUnit = "RangeIntervalUnit"; static int32_t physiInterpFuncNodeToJson(const void* pObj, SJson* pJson) { const SInterpFuncPhysiNode* pNode = (const SInterpFuncPhysiNode*)pObj; @@ -3350,6 +3366,12 @@ static int32_t physiInterpFuncNodeToJson(const void* pObj, SJson* pJson) { if (TSDB_CODE_SUCCESS == code) { code = tjsonAddObject(pJson, jkInterpFuncPhysiPlanStreamNodeOption, streamNodeOptionToJson, &pNode->streamNodeOption); } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonAddIntegerToObject(pJson, jkInterpFuncPhysiPlanRangeInterval, pNode->rangeInterval); + } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonAddIntegerToObject(pJson, jkInterpFuncPhysiPlanRangeIntervalUnit, pNode->rangeIntervalUnit); + } return code; } @@ -3391,6 +3413,12 @@ static int32_t jsonToPhysiInterpFuncNode(const SJson* pJson, void* pObj) { if (TSDB_CODE_SUCCESS == code) { code = tjsonToObject(pJson, jkInterpFuncPhysiPlanStreamNodeOption, jsonToStreamNodeOption, &pNode->streamNodeOption); } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonGetBigIntValue(pJson, jkInterpFuncPhysiPlanRangeInterval, &pNode->rangeInterval); + } + if (TSDB_CODE_SUCCESS == code) { + code = tjsonGetTinyIntValue(pJson, jkInterpFuncPhysiPlanRangeIntervalUnit, &pNode->rangeIntervalUnit); + } return code; } diff --git a/source/libs/nodes/src/nodesMsgFuncs.c b/source/libs/nodes/src/nodesMsgFuncs.c index bf3ea66e47..fe95322577 100644 --- a/source/libs/nodes/src/nodesMsgFuncs.c +++ b/source/libs/nodes/src/nodesMsgFuncs.c @@ -3746,7 +3746,9 @@ enum { PHY_INERP_FUNC_CODE_INTERVAL_UNIT, PHY_INERP_FUNC_CODE_FILL_MODE, PHY_INERP_FUNC_CODE_FILL_VALUES, - PHY_INERP_FUNC_CODE_TIME_SERIES + PHY_INERP_FUNC_CODE_TIME_SERIES, + PHY_INTERP_FUNC_CODE_RANGE_INTERVAL, + PHY_INTERP_FUNC_CODE_RANGE_INTERVAL_UNIT, }; static int32_t physiInterpFuncNodeToMsg(const void* pObj, STlvEncoder* pEncoder) { @@ -3777,6 +3779,12 @@ static int32_t physiInterpFuncNodeToMsg(const void* pObj, STlvEncoder* pEncoder) if (TSDB_CODE_SUCCESS == code) { code = tlvEncodeObj(pEncoder, PHY_INERP_FUNC_CODE_TIME_SERIES, nodeToMsg, pNode->pTimeSeries); } + if (TSDB_CODE_SUCCESS == code) { + code = tlvEncodeI64(pEncoder, PHY_INTERP_FUNC_CODE_RANGE_INTERVAL, pNode->rangeInterval); + } + if (TSDB_CODE_SUCCESS == code) { + code = tlvEncodeI8(pEncoder, PHY_INTERP_FUNC_CODE_RANGE_INTERVAL_UNIT, pNode->rangeIntervalUnit); + } return code; } @@ -3815,6 +3823,12 @@ static int32_t msgToPhysiInterpFuncNode(STlvDecoder* pDecoder, void* pObj) { case PHY_INERP_FUNC_CODE_TIME_SERIES: code = msgToNodeFromTlv(pTlv, (void**)&pNode->pTimeSeries); break; + case PHY_INTERP_FUNC_CODE_RANGE_INTERVAL: + code = tlvDecodeI64(pTlv, &pNode->rangeInterval); + break; + case PHY_INTERP_FUNC_CODE_RANGE_INTERVAL_UNIT: + code = tlvDecodeI8(pTlv, &pNode->rangeIntervalUnit); + break; default: break; } diff --git a/source/libs/nodes/src/nodesUtilFuncs.c b/source/libs/nodes/src/nodesUtilFuncs.c index ac29021e83..04b0d56a63 100644 --- a/source/libs/nodes/src/nodesUtilFuncs.c +++ b/source/libs/nodes/src/nodesUtilFuncs.c @@ -470,6 +470,8 @@ int32_t nodesMakeNode(ENodeType type, SNode** ppNodeOut) { case QUERY_NODE_SET_OPERATOR: code = makeNode(type, sizeof(SSetOperator), &pNode); break; + case QUERY_NODE_RANGE_AROUND: + code = makeNode(type, sizeof(SRangeAroundNode), &pNode); break; case QUERY_NODE_SELECT_STMT: code = makeNode(type, sizeof(SSelectStmt), &pNode); break; @@ -1245,6 +1247,12 @@ void nodesDestroyNode(SNode* pNode) { nodesDestroyNode(pWin->pEndOffset); break; } + case QUERY_NODE_RANGE_AROUND: { + SRangeAroundNode* pAround = (SRangeAroundNode*)pNode; + nodesDestroyNode(pAround->pInterval); + nodesDestroyNode(pAround->pTimepoint); + break; + } case QUERY_NODE_SET_OPERATOR: { SSetOperator* pStmt = (SSetOperator*)pNode; nodesDestroyList(pStmt->pProjectionList); @@ -1266,6 +1274,7 @@ void nodesDestroyNode(SNode* pNode) { nodesDestroyList(pStmt->pGroupByList); nodesDestroyNode(pStmt->pHaving); nodesDestroyNode(pStmt->pRange); + nodesDestroyNode(pStmt->pRangeAround); nodesDestroyNode(pStmt->pEvery); nodesDestroyNode(pStmt->pFill); nodesDestroyList(pStmt->pOrderByList); diff --git a/source/libs/parser/inc/parAst.h b/source/libs/parser/inc/parAst.h index fb0529e6d1..09b07c3fcb 100644 --- a/source/libs/parser/inc/parAst.h +++ b/source/libs/parser/inc/parAst.h @@ -163,6 +163,7 @@ SNode* createFillNode(SAstCreateContext* pCxt, EFillMode mode, SNode* pValue SNode* createGroupingSetNode(SAstCreateContext* pCxt, SNode* pNode); SNode* createInterpTimeRange(SAstCreateContext* pCxt, SNode* pStart, SNode* pEnd); SNode* createInterpTimePoint(SAstCreateContext* pCxt, SNode* pPoint); +SNode* createInterpTimeAround(SAstCreateContext* pCxt, SNode* pTimepoint, SNode* pInterval); SNode* createWhenThenNode(SAstCreateContext* pCxt, SNode* pWhen, SNode* pThen); SNode* createCaseWhenNode(SAstCreateContext* pCxt, SNode* pCase, SNodeList* pWhenThenList, SNode* pElse); SNode* createAlterSingleTagColumnNode(SAstCreateContext* pCtx, SToken* token, SNode* pVal); diff --git a/source/libs/parser/inc/sql.y b/source/libs/parser/inc/sql.y index b8a5e6f98f..19d34c99b0 100644 --- a/source/libs/parser/inc/sql.y +++ b/source/libs/parser/inc/sql.y @@ -1209,6 +1209,7 @@ pseudo_column(A) ::= QTAGS(B). pseudo_column(A) ::= FLOW(B). { A = createRawExprNode(pCxt, &B, createFunctionNode(pCxt, &B, NULL)); } pseudo_column(A) ::= FHIGH(B). { A = createRawExprNode(pCxt, &B, createFunctionNode(pCxt, &B, NULL)); } pseudo_column(A) ::= FROWTS(B). { A = createRawExprNode(pCxt, &B, createFunctionNode(pCxt, &B, NULL)); } +pseudo_column(A) ::= IROWTS_ORIGIN(B). { A = createRawExprNode(pCxt, &B, createFunctionNode(pCxt, &B, NULL)); } function_expression(A) ::= function_name(B) NK_LP expression_list(C) NK_RP(D). { A = createRawExprNodeExt(pCxt, &B, &D, createFunctionNode(pCxt, &B, C)); } function_expression(A) ::= star_func(B) NK_LP star_func_para_list(C) NK_RP(D). { A = createRawExprNodeExt(pCxt, &B, &D, createFunctionNode(pCxt, &B, C)); } @@ -1454,7 +1455,7 @@ jlimit_clause_opt(A) ::= JLIMIT NK_INTEGER(B). query_specification(A) ::= SELECT hint_list(M) set_quantifier_opt(B) tag_mode_opt(N) select_list(C) from_clause_opt(D) where_clause_opt(E) partition_by_clause_opt(F) range_opt(J) every_opt(K) - fill_opt(L) twindow_clause_opt(G) group_by_clause_opt(H) having_clause_opt(I). { + interp_fill_opt(L) twindow_clause_opt(G) group_by_clause_opt(H) having_clause_opt(I). { A = createSelectStmt(pCxt, B, C, D, M); A = setSelectStmtTagMode(pCxt, A, N); A = addWhereClause(pCxt, A, E); @@ -1539,19 +1540,41 @@ interval_sliding_duration_literal(A) ::= NK_VARIABLE(B). interval_sliding_duration_literal(A) ::= NK_STRING(B). { A = createRawExprNode(pCxt, &B, createDurationValueNode(pCxt, &B)); } interval_sliding_duration_literal(A) ::= NK_INTEGER(B). { A = createRawExprNode(pCxt, &B, createDurationValueNode(pCxt, &B)); } +interp_fill_opt(A) ::= . { A = NULL; } +interp_fill_opt(A) ::= fill_value(B). { A = B; } +interp_fill_opt(A) ::= + FILL NK_LP fill_position_mode_extension(B) NK_COMMA expression_list(C) NK_RP. { A = createFillNode(pCxt, B, createNodeListNode(pCxt, C)); } +interp_fill_opt(A) ::= FILL NK_LP interp_fill_mode(B) NK_RP. { A = createFillNode(pCxt, B, NULL); } + fill_opt(A) ::= . { A = NULL; } fill_opt(A) ::= FILL NK_LP fill_mode(B) NK_RP. { A = createFillNode(pCxt, B, NULL); } -fill_opt(A) ::= FILL NK_LP VALUE NK_COMMA expression_list(B) NK_RP. { A = createFillNode(pCxt, FILL_MODE_VALUE, createNodeListNode(pCxt, B)); } -fill_opt(A) ::= FILL NK_LP VALUE_F NK_COMMA expression_list(B) NK_RP. { A = createFillNode(pCxt, FILL_MODE_VALUE_F, createNodeListNode(pCxt, B)); } +fill_opt(A) ::= fill_value(B). { A = B; } + +fill_value(A) ::= FILL NK_LP VALUE NK_COMMA expression_list(B) NK_RP. { A = createFillNode(pCxt, FILL_MODE_VALUE, createNodeListNode(pCxt, B)); } +fill_value(A) ::= FILL NK_LP VALUE_F NK_COMMA expression_list(B) NK_RP. { A = createFillNode(pCxt, FILL_MODE_VALUE_F, createNodeListNode(pCxt, B)); } %type fill_mode { EFillMode } %destructor fill_mode { } fill_mode(A) ::= NONE. { A = FILL_MODE_NONE; } -fill_mode(A) ::= PREV. { A = FILL_MODE_PREV; } fill_mode(A) ::= NULL. { A = FILL_MODE_NULL; } fill_mode(A) ::= NULL_F. { A = FILL_MODE_NULL_F; } fill_mode(A) ::= LINEAR. { A = FILL_MODE_LINEAR; } -fill_mode(A) ::= NEXT. { A = FILL_MODE_NEXT; } +fill_mode(A) ::= fill_position_mode(B). { A = B; } + +%type fill_position_mode { EFillMode } +%destructor fill_position_mode { } +fill_position_mode(A) ::= PREV. { A = FILL_MODE_PREV; } +fill_position_mode(A) ::= NEXT. { A = FILL_MODE_NEXT; } + +%type fill_position_mode_extension { EFillMode } +%destructor fill_position_mode_extension { } +fill_position_mode_extension(A) ::= fill_position_mode(B). { A = B; } +fill_position_mode_extension(A) ::= NEAR. { A = FILL_MODE_NEAR; } + +%type interp_fill_mode { EFillMode } +%destructor interp_fill_mode { } +interp_fill_mode(A) ::= fill_mode(B). { A = B; } +interp_fill_mode(A) ::= NEAR. { A = FILL_MODE_NEAR; } %type group_by_clause_opt { SNodeList* } %destructor group_by_clause_opt { nodesDestroyList($$); } diff --git a/source/libs/parser/src/parAstCreater.c b/source/libs/parser/src/parAstCreater.c index e05a399d32..7503e91dbe 100644 --- a/source/libs/parser/src/parAstCreater.c +++ b/source/libs/parser/src/parAstCreater.c @@ -1460,6 +1460,9 @@ _err: SNode* createInterpTimeRange(SAstCreateContext* pCxt, SNode* pStart, SNode* pEnd) { CHECK_PARSER_STATUS(pCxt); + if (pEnd && nodeType(pEnd) == QUERY_NODE_VALUE && ((SValueNode*)pEnd)->flag & VALUE_FLAG_IS_DURATION) { + return createInterpTimeAround(pCxt, pStart, pEnd); + } return createBetweenAnd(pCxt, createPrimaryKeyCol(pCxt, NULL), pStart, pEnd); _err: nodesDestroyNode(pStart); @@ -1475,6 +1478,19 @@ _err: return NULL; } +SNode* createInterpTimeAround(SAstCreateContext* pCxt, SNode* pTimepoint, SNode* pInterval) { + CHECK_PARSER_STATUS(pCxt); + SRangeAroundNode* pAround = NULL; + pCxt->errCode = nodesMakeNode(QUERY_NODE_RANGE_AROUND, (SNode**)&pAround); + CHECK_PARSER_STATUS(pCxt); + pAround->pTimepoint = createInterpTimePoint(pCxt, pTimepoint); + pAround->pInterval = pInterval; + CHECK_PARSER_STATUS(pCxt); + return (SNode*)pAround; +_err: + return NULL; +} + SNode* createWhenThenNode(SAstCreateContext* pCxt, SNode* pWhen, SNode* pThen) { CHECK_PARSER_STATUS(pCxt); SWhenThenNode* pWhenThen = NULL; @@ -1632,8 +1648,15 @@ _err: SNode* addRangeClause(SAstCreateContext* pCxt, SNode* pStmt, SNode* pRange) { CHECK_PARSER_STATUS(pCxt); + SSelectStmt* pSelect = (SSelectStmt*)pStmt; if (QUERY_NODE_SELECT_STMT == nodeType(pStmt)) { - ((SSelectStmt*)pStmt)->pRange = pRange; + if (pRange && nodeType(pRange) == QUERY_NODE_RANGE_AROUND) { + pSelect->pRangeAround = pRange; + SRangeAroundNode* pAround = (SRangeAroundNode*)pRange; + TSWAP(pSelect->pRange, pAround->pTimepoint); + } else { + pSelect->pRange = pRange; + } } return pStmt; _err: diff --git a/source/libs/parser/src/parTokenizer.c b/source/libs/parser/src/parTokenizer.c index 1db139b8d4..9100e83f42 100644 --- a/source/libs/parser/src/parTokenizer.c +++ b/source/libs/parser/src/parTokenizer.c @@ -176,6 +176,7 @@ static SKeyword keywordTable[] = { {"NORMAL", TK_NORMAL}, {"NCHAR", TK_NCHAR}, {"NEXT", TK_NEXT}, + {"NEAR", TK_NEAR}, {"NMATCH", TK_NMATCH}, {"NONE", TK_NONE}, {"NOT", TK_NOT}, @@ -326,6 +327,7 @@ static SKeyword keywordTable[] = { {"WRITE", TK_WRITE}, {"_C0", TK_ROWTS}, {"_IROWTS", TK_IROWTS}, + {"_IROWTS_ORIGIN", TK_IROWTS_ORIGIN}, {"_ISFILLED", TK_ISFILLED}, {"_QDURATION", TK_QDURATION}, {"_QEND", TK_QEND}, diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index dd08ee7654..7e5d9375ac 100755 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -1260,7 +1260,8 @@ bool isPrimaryKeyImpl(SNode* pExpr) { FUNCTION_TYPE_LAST_ROW == pFunc->funcType || FUNCTION_TYPE_TIMETRUNCATE == pFunc->funcType) { return isPrimaryKeyImpl(nodesListGetNode(pFunc->pParameterList, 0)); } else if (FUNCTION_TYPE_WSTART == pFunc->funcType || FUNCTION_TYPE_WEND == pFunc->funcType || - FUNCTION_TYPE_IROWTS == pFunc->funcType || FUNCTION_TYPE_FORECAST_ROWTS == pFunc->funcType) { + FUNCTION_TYPE_IROWTS == pFunc->funcType || FUNCTION_TYPE_IROWTS_ORIGIN == pFunc->funcType || + FUNCTION_TYPE_FORECAST_ROWTS == pFunc->funcType) { return true; } } else if (QUERY_NODE_OPERATOR == nodeType(pExpr)) { @@ -5388,14 +5389,11 @@ static int32_t convertFillValue(STranslateContext* pCxt, SDataType dt, SNodeList return code; } -static int32_t checkFillValues(STranslateContext* pCxt, SFillNode* pFill, SNodeList* pProjectionList) { - if (FILL_MODE_VALUE != pFill->mode && FILL_MODE_VALUE_F != pFill->mode) { - return TSDB_CODE_SUCCESS; - } - +static int32_t doCheckFillValues(STranslateContext* pCxt, SFillNode* pFill, SNodeList* pProjectionList) { int32_t fillNo = 0; SNodeListNode* pFillValues = (SNodeListNode*)pFill->pValues; SNode* pProject = NULL; + if (!pFillValues) return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_WRONG_VALUE_TYPE, "Filled values number mismatch"); FOREACH(pProject, pProjectionList) { if (needFill(pProject)) { if (fillNo >= LIST_LENGTH(pFillValues->pNodeList)) { @@ -5415,6 +5413,14 @@ static int32_t checkFillValues(STranslateContext* pCxt, SFillNode* pFill, SNodeL return TSDB_CODE_SUCCESS; } +static int32_t checkFillValues(STranslateContext* pCxt, SFillNode* pFill, SNodeList* pProjectionList) { + if (FILL_MODE_VALUE != pFill->mode && FILL_MODE_VALUE_F != pFill->mode) { + return TSDB_CODE_SUCCESS; + } + return doCheckFillValues(pCxt, pFill, pProjectionList); + return TSDB_CODE_SUCCESS; +} + static int32_t translateFillValues(STranslateContext* pCxt, SSelectStmt* pSelect) { if (NULL == pSelect->pWindow || QUERY_NODE_INTERVAL_WINDOW != nodeType(pSelect->pWindow) || NULL == ((SIntervalWindowNode*)pSelect->pWindow)->pFill) { @@ -6185,6 +6191,31 @@ static int32_t translateInterpEvery(STranslateContext* pCxt, SNode** pEvery) { return code; } +static EDealRes hasRowTsOriginFuncWalkNode(SNode* pNode, void* ctx) { + bool *hasRowTsOriginFunc = ctx; + if (nodeType(pNode) == QUERY_NODE_FUNCTION) { + SFunctionNode* pFunc = (SFunctionNode*)pNode; + if (fmIsRowTsOriginFunc(pFunc->funcId)) { + *hasRowTsOriginFunc = true; + return DEAL_RES_END; + } + } + return DEAL_RES_CONTINUE; +} + +static int32_t checkInterpForStream(STranslateContext* pCxt, SSelectStmt* pSelect) { + if (pCxt->createStream) { + SFillNode* pFill = (SFillNode*)pSelect->pFill; + if (pFill->mode == FILL_MODE_NEAR) { + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_STREAM_QUERY, "FILL NEAR is not supported by stream"); + } + if (pSelect->pRangeAround) { + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_STREAM_QUERY, "RANGE with around is not supported by stream"); + } + } + return TSDB_CODE_SUCCESS; +} + static int32_t translateInterpFill(STranslateContext* pCxt, SSelectStmt* pSelect) { int32_t code = TSDB_CODE_SUCCESS; @@ -6200,13 +6231,60 @@ static int32_t translateInterpFill(STranslateContext* pCxt, SSelectStmt* pSelect if (TSDB_CODE_SUCCESS == code) { code = checkFill(pCxt, (SFillNode*)pSelect->pFill, (SValueNode*)pSelect->pEvery, true); } + bool hasRowTsOriginFunc = false; + nodesWalkExprs(pSelect->pProjectionList, hasRowTsOriginFuncWalkNode, &hasRowTsOriginFunc); + if (hasRowTsOriginFunc && pCxt->createStream) { + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_STREAM_QUERY, + "_irowts_origin is not supported by stream"); + } if (TSDB_CODE_SUCCESS == code) { - code = checkFillValues(pCxt, (SFillNode*)pSelect->pFill, pSelect->pProjectionList); + SFillNode* pFill = (SFillNode*)pSelect->pFill; + if (pSelect->pRangeAround) { + if (pFill->mode != FILL_MODE_PREV && pFill->mode != FILL_MODE_NEXT && pFill->mode != FILL_MODE_NEAR) { + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_FILL_TIME_RANGE, + "Range with interval can only used with fill PREV/NEXT/NEAR"); + } + if (TSDB_CODE_SUCCESS == code) + code = doCheckFillValues(pCxt, pFill, pSelect->pProjectionList); + } else { + if (FILL_MODE_PREV == pFill->mode || FILL_MODE_NEXT == pFill->mode || FILL_MODE_NEAR == pFill->mode) { + if (pFill->pValues) { + return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_WRONG_VALUE_TYPE, "Can't specify fill values"); + } + } else { + if (hasRowTsOriginFunc) return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_FILL_NOT_ALLOWED_FUNC, "_irowts_origin can only be used with FILL PREV/NEXT/NEAR"); + } + code = checkFillValues(pCxt, pFill, pSelect->pProjectionList); + } } return code; } +static int32_t translateInterpAround(STranslateContext* pCxt, SSelectStmt* pSelect) { + int32_t code = 0; + if (pSelect->pRangeAround) { + SRangeAroundNode* pAround = (SRangeAroundNode*)pSelect->pRangeAround; + code = translateExpr(pCxt, &pAround->pInterval); + if (TSDB_CODE_SUCCESS == code) { + if (nodeType(pAround->pInterval) == QUERY_NODE_VALUE) { + SValueNode* pVal = (SValueNode*)pAround->pInterval; + if (pVal->datum.i == 0) { + return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_FILL_TIME_RANGE, "Range interval cannot be 0"); + } + int8_t unit = pVal->unit; + if (unit == TIME_UNIT_YEAR || unit == TIME_UNIT_MONTH) { + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_WRONG_VALUE_TYPE, + "Unsupported time unit in RANGE clause"); + } + } else { + return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_FILL_TIME_RANGE, "Invalid range interval"); + } + } + } + return code; +} + static int32_t translateInterp(STranslateContext* pCxt, SSelectStmt* pSelect) { if (!pSelect->hasInterpFunc) { if (NULL != pSelect->pRange || NULL != pSelect->pEvery || NULL != pSelect->pFill) { @@ -6227,6 +6305,7 @@ static int32_t translateInterp(STranslateContext* pCxt, SSelectStmt* pSelect) { } } + int32_t code = 0; if (pCxt->createStream) { if (NULL != pSelect->pRange) { return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_STREAM_QUERY, @@ -6238,23 +6317,40 @@ static int32_t translateInterp(STranslateContext* pCxt, SSelectStmt* pSelect) { "Missing EVERY clause or FILL clause"); } } else { - if (NULL == pSelect->pRange || NULL == pSelect->pEvery || NULL == pSelect->pFill) { - if (pSelect->pRange != NULL && QUERY_NODE_OPERATOR == nodeType(pSelect->pRange) && pSelect->pEvery == NULL) { - // single point interp every can be omitted - } else { + if (!pSelect->pRangeAround) { + if (NULL == pSelect->pRange || NULL == pSelect->pEvery || NULL == pSelect->pFill) { + if (pSelect->pRange != NULL && QUERY_NODE_OPERATOR == nodeType(pSelect->pRange) && pSelect->pEvery == NULL) { + // single point interp every can be omitted + } else { + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_INTERP_CLAUSE, + "Missing RANGE clause, EVERY clause or FILL clause"); + } + } + } else { + if (pSelect->pEvery) { return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_INTERP_CLAUSE, - "Missing RANGE clause, EVERY clause or FILL clause"); + "Range clause with around interval can't be used with EVERY clause"); + } + if (!pSelect->pFill) { + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_INTERP_CLAUSE, + "Missing FILL clause"); } } } - int32_t code = translateExpr(pCxt, &pSelect->pRange); + code = translateExpr(pCxt, &pSelect->pRange); if (TSDB_CODE_SUCCESS == code) { code = translateInterpEvery(pCxt, &pSelect->pEvery); } if (TSDB_CODE_SUCCESS == code) { code = translateInterpFill(pCxt, pSelect); } + if (TSDB_CODE_SUCCESS == code) { + code = translateInterpAround(pCxt, pSelect); + } + if (TSDB_CODE_SUCCESS == code) { + code = checkInterpForStream(pCxt, pSelect); + } return code; } diff --git a/source/libs/planner/src/planLogicCreater.c b/source/libs/planner/src/planLogicCreater.c index f4c4926ca1..47a0144243 100644 --- a/source/libs/planner/src/planLogicCreater.c +++ b/source/libs/planner/src/planLogicCreater.c @@ -973,6 +973,16 @@ static int32_t createInterpFuncLogicNode(SLogicPlanContext* pCxt, SSelectStmt* p pInterpFunc->precision = pSelect->precision; } + if (TSDB_CODE_SUCCESS == code && pSelect->pRangeAround) { + SNode* pRangeInterval = ((SRangeAroundNode*)pSelect->pRangeAround)->pInterval; + if (!pRangeInterval || nodeType(pRangeInterval) != QUERY_NODE_VALUE) { + code = TSDB_CODE_PAR_INTERNAL_ERROR; + } else { + pInterpFunc->rangeInterval = ((SValueNode*)pRangeInterval)->datum.i; + pInterpFunc->rangeIntervalUnit = ((SValueNode*)pRangeInterval)->unit; + } + } + // set the output if (TSDB_CODE_SUCCESS == code) { code = createColumnByRewriteExprs(pInterpFunc->pFuncs, &pInterpFunc->node.pTargets); diff --git a/source/libs/planner/src/planPhysiCreater.c b/source/libs/planner/src/planPhysiCreater.c index 347aeba95e..a0912ec8c7 100644 --- a/source/libs/planner/src/planPhysiCreater.c +++ b/source/libs/planner/src/planPhysiCreater.c @@ -1958,6 +1958,8 @@ static int32_t createInterpFuncPhysiNode(SPhysiPlanContext* pCxt, SNodeList* pCh pInterpFunc->intervalUnit = pFuncLogicNode->intervalUnit; pInterpFunc->precision = pFuncLogicNode->node.precision; pInterpFunc->pFillValues = NULL; + pInterpFunc->rangeInterval = pFuncLogicNode->rangeInterval; + pInterpFunc->rangeIntervalUnit = pFuncLogicNode->rangeIntervalUnit; code = nodesCloneNode(pFuncLogicNode->pFillValues, &pInterpFunc->pFillValues); if (TSDB_CODE_SUCCESS != code) { code = code; diff --git a/tests/parallel_test/cases.task b/tests/parallel_test/cases.task index ab6bfcfd1a..34ff38eb43 100644 --- a/tests/parallel_test/cases.task +++ b/tests/parallel_test/cases.task @@ -156,6 +156,11 @@ ,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/tsma2.py -Q 3 ,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/tsma2.py -Q 4 ,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/nestedQuery2.py +,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/interp_extension.py +,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/interp_extension.py -R +,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/interp_extension.py -Q 2 +,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/interp_extension.py -Q 3 +,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/interp_extension.py -Q 4 ,,y,system-test,./pytest.sh python3 ./test.py -f 7-tmq/tmqShow.py ,,y,system-test,./pytest.sh python3 ./test.py -f 7-tmq/tmqDropStb.py ,,y,system-test,./pytest.sh python3 ./test.py -f 7-tmq/subscribeStb0.py diff --git a/tests/system-test/2-query/interp_extension.py b/tests/system-test/2-query/interp_extension.py new file mode 100644 index 0000000000..b9283f7ca1 --- /dev/null +++ b/tests/system-test/2-query/interp_extension.py @@ -0,0 +1,518 @@ +import queue +from random import randrange +import time +import threading +import secrets +from util.log import * +from util.sql import * +from util.cases import * +from util.dnodes import * +from util.common import * +from datetime import timezone +from tzlocal import get_localzone +# from tmqCommon import * + + +ROUND: int = 500 + +class TDTestCase: + updatecfgDict = {'asynclog': 0, 'ttlUnit': 1, 'ttlPushInterval': 5, 'ratioOfVnodeStreamThrea': 4, 'debugFlag': 143} + check_failed: bool = False + + def __init__(self): + self.vgroups = 4 + self.ctbNum = 10 + self.rowsPerTbl = 10000 + self.duraion = '1h' + + def init(self, conn, logSql, replicaVar=1): + self.replicaVar = int(replicaVar) + tdLog.debug(f"start to excute {__file__}") + tdSql.init(conn.cursor(), False) + + def create_database(self, tsql, dbName, dropFlag=1, vgroups=2, replica=1, duration: str = '1d'): + if dropFlag == 1: + tsql.execute("drop database if exists %s" % (dbName)) + + tsql.execute("create database if not exists %s vgroups %d replica %d duration %s" % ( + dbName, vgroups, replica, duration)) + tdLog.debug("complete to create database %s" % (dbName)) + return + + def create_stable(self, tsql, paraDict): + colString = tdCom.gen_column_type_str( + colname_prefix=paraDict["colPrefix"], column_elm_list=paraDict["colSchema"]) + tagString = tdCom.gen_tag_type_str( + tagname_prefix=paraDict["tagPrefix"], tag_elm_list=paraDict["tagSchema"]) + sqlString = f"create table if not exists %s.%s (%s) tags (%s)" % ( + paraDict["dbName"], paraDict["stbName"], colString, tagString) + tdLog.debug("%s" % (sqlString)) + tsql.execute(sqlString) + return + + def create_ctable(self, tsql=None, dbName='dbx', stbName='stb', ctbPrefix='ctb', ctbNum=1, ctbStartIdx=0): + for i in range(ctbNum): + sqlString = "create table %s.%s%d using %s.%s tags(%d, 'tb%d', 'tb%d', %d, %d, %d)" % (dbName, ctbPrefix, i+ctbStartIdx, dbName, stbName, (i+ctbStartIdx) % 5, i+ctbStartIdx + random.randint( + 1, 100), i+ctbStartIdx + random.randint(1, 100), i+ctbStartIdx + random.randint(1, 100), i+ctbStartIdx + random.randint(1, 100), i+ctbStartIdx + random.randint(1, 100)) + tsql.execute(sqlString) + + tdLog.debug("complete to create %d child tables by %s.%s" % + (ctbNum, dbName, stbName)) + return + + def init_normal_tb(self, tsql, db_name: str, tb_name: str, rows: int, start_ts: int, ts_step: int): + sql = 'CREATE TABLE %s.%s (ts timestamp, c1 INT, c2 INT, c3 INT, c4 double, c5 VARCHAR(255))' % ( + db_name, tb_name) + tsql.execute(sql) + sql = 'INSERT INTO %s.%s values' % (db_name, tb_name) + for j in range(rows): + sql += f'(%d, %d,%d,%d,{random.random()},"varchar_%d"),' % (start_ts + j * ts_step + randrange(500), j % + 10 + randrange(200), j % 10, j % 10, j % 10 + randrange(100)) + tsql.execute(sql) + + def insert_data(self, tsql, dbName, ctbPrefix, ctbNum, rowsPerTbl, batchNum, startTs, tsStep): + tdLog.debug("start to insert data ............") + tsql.execute("use %s" % dbName) + pre_insert = "insert into " + sql = pre_insert + + for i in range(ctbNum): + rowsBatched = 0 + sql += " %s.%s%d values " % (dbName, ctbPrefix, i) + for j in range(rowsPerTbl): + if (i < ctbNum/2): + sql += "(%d, %d, %d, %d,%d,%d,%d,true,'binary%d', 'nchar%d') " % (startTs + j*tsStep + randrange( + 500), j % 10 + randrange(100), j % 10 + randrange(200), j % 10, j % 10, j % 10, j % 10, j % 10, j % 10) + else: + sql += "(%d, %d, NULL, %d,NULL,%d,%d,true,'binary%d', 'nchar%d') " % ( + startTs + j*tsStep + randrange(500), j % 10, j % 10, j % 10, j % 10, j % 10, j % 10) + rowsBatched += 1 + if ((rowsBatched == batchNum) or (j == rowsPerTbl - 1)): + tsql.execute(sql) + rowsBatched = 0 + if j < rowsPerTbl - 1: + sql = "insert into %s.%s%d values " % (dbName, ctbPrefix, i) + else: + sql = "insert into " + if sql != pre_insert: + tsql.execute(sql) + tdLog.debug("insert data ............ [OK]") + return + + def init_data(self, db: str = 'test', ctb_num: int = 10, rows_per_ctb: int = 10000, start_ts: int = 1537146000000, ts_step: int = 500): + tdLog.printNoPrefix( + "======== prepare test env include database, stable, ctables, and insert data: ") + paraDict = {'dbName': db, + 'dropFlag': 1, + 'vgroups': 4, + 'stbName': 'meters', + 'colPrefix': 'c', + 'tagPrefix': 't', + 'colSchema': [{'type': 'INT', 'count': 1}, {'type': 'BIGINT', 'count': 1}, {'type': 'FLOAT', 'count': 1}, {'type': 'DOUBLE', 'count': 1}, {'type': 'smallint', 'count': 1}, {'type': 'tinyint', 'count': 1}, {'type': 'bool', 'count': 1}, {'type': 'binary', 'len': 10, 'count': 1}, {'type': 'nchar', 'len': 10, 'count': 1}], + 'tagSchema': [{'type': 'INT', 'count': 1}, {'type': 'nchar', 'len': 20, 'count': 1}, {'type': 'binary', 'len': 20, 'count': 1}, {'type': 'BIGINT', 'count': 1}, {'type': 'smallint', 'count': 1}, {'type': 'DOUBLE', 'count': 1}], + 'ctbPrefix': 't', + 'ctbStartIdx': 0, + 'ctbNum': ctb_num, + 'rowsPerTbl': rows_per_ctb, + 'batchNum': 3000, + 'startTs': start_ts, + 'tsStep': ts_step} + + paraDict['vgroups'] = self.vgroups + paraDict['ctbNum'] = ctb_num + paraDict['rowsPerTbl'] = rows_per_ctb + + tdLog.info("create database") + self.create_database(tsql=tdSql, dbName=paraDict["dbName"], dropFlag=paraDict["dropFlag"], + vgroups=paraDict["vgroups"], replica=self.replicaVar, duration=self.duraion) + + tdLog.info("create stb") + self.create_stable(tsql=tdSql, paraDict=paraDict) + + tdLog.info("create child tables") + self.create_ctable(tsql=tdSql, dbName=paraDict["dbName"], + stbName=paraDict["stbName"], ctbPrefix=paraDict["ctbPrefix"], + ctbNum=paraDict["ctbNum"], ctbStartIdx=paraDict["ctbStartIdx"]) + self.insert_data(tsql=tdSql, dbName=paraDict["dbName"], + ctbPrefix=paraDict["ctbPrefix"], ctbNum=paraDict["ctbNum"], + rowsPerTbl=paraDict["rowsPerTbl"], batchNum=paraDict["batchNum"], + startTs=paraDict["startTs"], tsStep=paraDict["tsStep"]) + self.init_normal_tb(tdSql, paraDict['dbName'], 'norm_tb', + paraDict['rowsPerTbl'], paraDict['startTs'], paraDict['tsStep']) + + def run(self): + self.init_data() + self.test_interp_extension() + + + def datetime_add_tz(self, dt): + if dt.tzinfo is None or dt.tzinfo.utcoffset(dt) is None: + return dt.replace(tzinfo=get_localzone()) + return dt + + def binary_search_ts(self, select_results, ts): + mid = 0 + try: + found: bool = False + start = 0 + end = len(select_results) - 1 + while start <= end: + mid = (start + end) // 2 + if self.datetime_add_tz(select_results[mid][0]) == ts: + found = True + return mid + elif self.datetime_add_tz(select_results[mid][0]) < ts: + start = mid + 1 + else: + end = mid - 1 + + if not found: + tdLog.exit(f"cannot find ts in select results {ts} {select_results}") + return start + except Exception as e: + tdLog.debug(f"{select_results[mid][0]}, {ts}, {len(select_results)}, {select_results[mid]}") + self.check_failed = True + tdLog.exit(f"binary_search_ts error: {e}") + + def distance(self, ts1, ts2): + return abs(self.datetime_add_tz(ts1) - self.datetime_add_tz(ts2)) + + ## TODO pass last position to avoid search from the beginning + def is_nearest(self, select_results, irowts_origin, irowts): + if len(select_results) <= 1: + return True + try: + #tdLog.debug(f"check is_nearest for: {irowts_origin} {irowts}") + idx = self.binary_search_ts(select_results, irowts_origin) + if idx == 0: + #tdLog.debug(f"prev row: null,cur row: {select_results[idx]}, next row: {select_results[idx + 1]}") + res = self.distance(irowts, select_results[idx][0]) <= self.distance(irowts, select_results[idx + 1][0]) + if not res: + tdLog.debug(f"prev row: null,cur row: {select_results[idx]}, next row: {select_results[idx + 1]}, irowts_origin: {irowts_origin}, irowts: {irowts}") + return res + if idx == len(select_results) - 1: + #tdLog.debug(f"prev row: {select_results[idx - 1]},cur row: {select_results[idx]}, next row: null") + res = self.distance(irowts, select_results[idx][0]) <= self.distance(irowts, select_results[idx - 1][0]) + if not res: + tdLog.debug(f"prev row: {select_results[idx - 1]},cur row: {select_results[idx]}, next row: null, irowts_origin: {irowts_origin}, irowts: {irowts}") + return res + #tdLog.debug(f"prev row: {select_results[idx - 1]},cur row: {select_results[idx]}, next row: {select_results[idx + 1]}") + res = self.distance(irowts, select_results[idx][0]) <= self.distance(irowts, select_results[idx - 1][0]) and self.distance(irowts, select_results[idx][0]) <= self.distance(irowts, select_results[idx + 1][0]) + if not res: + tdLog.debug(f"prev row: {select_results[idx - 1]},cur row: {select_results[idx]}, next row: {select_results[idx + 1]}, irowts_origin: {irowts_origin}, irowts: {irowts}") + return res + except Exception as e: + self.check_failed = True + tdLog.exit(f"is_nearest error: {e}") + + ## interp_results: _irowts_origin, _irowts, ..., _isfilled + ## select_all_results must be sorted by ts in ascending order + def check_result_for_near(self, interp_results, select_all_results, sql, sql_select_all): + #tdLog.debug(f"check_result_for_near for sql: {sql}, sql_select_all{sql_select_all}") + for row in interp_results: + if row[0].tzinfo is None or row[0].tzinfo.utcoffset(row[0]) is None: + irowts_origin = row[0].replace(tzinfo=get_localzone()) + irowts = row[1].replace(tzinfo=get_localzone()) + else: + irowts_origin = row[0] + irowts = row[1] + if not self.is_nearest(select_all_results, irowts_origin, irowts): + self.check_failed = True + tdLog.exit(f"interp result is not the nearest for row: {row}, {sql}") + + def query_routine(self, sql_queue: queue.Queue, output_queue: queue.Queue): + try: + tdcom = TDCom() + cli = tdcom.newTdSql() + while True: + item = sql_queue.get() + if item is None or self.check_failed: + output_queue.put(None) + break + (sql, sql_select_all, _) = item + cli.query(sql, queryTimes=1) + interp_results = cli.queryResult + if sql_select_all is not None: + cli.query(sql_select_all, queryTimes=1) + output_queue.put((sql, interp_results, cli.queryResult, sql_select_all)) + cli.close() + except Exception as e: + self.check_failed = True + tdLog.exit(f"query_routine error: {e}") + + def interp_check_near_routine(self, select_all_results, output_queue: queue.Queue): + try: + while True: + item = output_queue.get() + if item is None: + break + (sql, interp_results, all_results, sql_select_all) = item + if all_results is not None: + self.check_result_for_near(interp_results, all_results, sql, sql_select_all) + else: + self.check_result_for_near(interp_results, select_all_results, sql, None) + except Exception as e: + self.check_failed = True + tdLog.exit(f"interp_check_near_routine error: {e}") + + def create_qt_threads(self, sql_queue: queue.Queue, output_queue: queue.Queue, num: int): + qts = [] + for _ in range(0, num): + qt = threading.Thread(target=self.query_routine, args=(sql_queue, output_queue)) + qt.start() + qts.append(qt) + return qts + + def wait_qt_threads(self, qts: list): + for qt in qts: + qt.join() + + ### first(ts) | last(ts) + ### 2018-09-17 09:00:00.047 | 2018-09-17 10:23:19.863 + def test_interp_fill_extension_near(self): + sql = f"select last(ts), c1, c2 from test.t0" + tdSql.query(sql, queryTimes=1) + lastRow = tdSql.queryResult[0] + sql = f"select _irowts_origin, _irowts, interp(c1), interp(c2), _isfilled from test.t0 range('2020-02-01 00:00:00', '2020-02-01 00:01:00') every(1s) fill(near)" + tdSql.query(sql, queryTimes=1) + tdSql.checkRows(61) + for i in range(0, 61): + tdSql.checkData(i, 0, lastRow[0]) + tdSql.checkData(i, 2, lastRow[1]) + tdSql.checkData(i, 3, lastRow[2]) + tdSql.checkData(i, 4, True) + + sql = f"select ts, c1, c2 from test.t0 where ts between '2018-09-17 08:59:59' and '2018-09-17 09:00:06' order by ts asc" + tdSql.query(sql, queryTimes=1) + select_all_results = tdSql.queryResult + sql = f"select _irowts_origin, _irowts, interp(c1), interp(c2), _isfilled from test.t0 range('2018-09-17 09:00:00', '2018-09-17 09:00:05') every(1s) fill(near)" + tdSql.query(sql, queryTimes=1) + tdSql.checkRows(6) + self.check_result_for_near(tdSql.queryResult, select_all_results, sql, None) + + + start = 1537146000000 + end = 1537151000000 + + tdSql.query("select ts, c1, c2 from test.t0 order by ts asc", queryTimes=1) + select_all_results = tdSql.queryResult + + qt_threads_num = 4 + sql_queue = queue.Queue() + output_queue = queue.Queue() + qts = self.create_qt_threads(sql_queue, output_queue, qt_threads_num) + ct = threading.Thread(target=self.interp_check_near_routine, args=(select_all_results, output_queue)) + ct.start() + for i in range(0, ROUND): + range_start = random.randint(start, end) + range_end = random.randint(range_start, end) + every = random.randint(1, 15) + #tdLog.debug(f"range_start: {range_start}, range_end: {range_end}") + sql = f"select _irowts_origin, _irowts, interp(c1), interp(c2), _isfilled from test.t0 range({range_start}, {range_end}) every({every}s) fill(near)" + sql_queue.put((sql, None, None)) + + ### no prev only, no next only, no prev and no next, have prev and have next + for i in range(0, ROUND): + range_point = random.randint(start, end) + ## all data points are can be filled by near + sql = f"select _irowts_origin, _irowts, interp(c1), interp(c2), _isfilled from test.t0 range({range_point}, 1h) fill(near, 1, 2)" + sql_queue.put((sql, None, None)) + + for i in range(0, ROUND): + range_start = random.randint(start, end) + range_end = random.randint(range_start, end) + range_where_start = random.randint(start, end) + range_where_end = random.randint(range_where_start, end) + every = random.randint(1, 15) + sql = f"select _irowts_origin, _irowts, interp(c1), interp(c2), _isfilled from test.t0 where ts between {range_where_start} and {range_where_end} range({range_start}, {range_end}) every({every}s) fill(near)" + tdSql.query(f'select to_char(cast({range_where_start} as timestamp), \'YYYY-MM-DD HH24:MI:SS.MS\'), to_char(cast({range_where_end} as timestamp), \'YYYY-MM-DD HH24:MI:SS.MS\')', queryTimes=1) + where_start_str = tdSql.queryResult[0][0] + where_end_str = tdSql.queryResult[0][1] + sql_select_all = f"select ts, c1, c2 from test.t0 where ts between '{where_start_str}' and '{where_end_str}' order by ts asc" + sql_queue.put((sql, sql_select_all, None)) + + for i in range(0, ROUND): + range_start = random.randint(start, end) + range_end = random.randint(range_start, end) + range_where_start = random.randint(start, end) + range_where_end = random.randint(range_where_start, end) + range_point = random.randint(start, end) + sql = f"select _irowts_origin, _irowts, interp(c1), interp(c2), _isfilled from test.t0 where ts between {range_where_start} and {range_where_end} range({range_point}, 1h) fill(near, 1, 2)" + tdSql.query(f'select to_char(cast({range_where_start} as timestamp), \'YYYY-MM-DD HH24:MI:SS.MS\'), to_char(cast({range_where_end} as timestamp), \'YYYY-MM-DD HH24:MI:SS.MS\')', queryTimes=1) + where_start_str = tdSql.queryResult[0][0] + where_end_str = tdSql.queryResult[0][1] + sql_select_all = f"select ts, c1, c2 from test.t0 where ts between '{where_start_str}' and '{where_end_str}' order by ts asc" + sql_queue.put((sql, sql_select_all, None)) + for i in range(0, qt_threads_num): + sql_queue.put(None) + self.wait_qt_threads(qts) + ct.join() + + if self.check_failed: + tdLog.exit("interp check near failed") + + def test_interp_extension_irowts_origin(self): + sql = f"select _irowts, _irowts_origin, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', '2020-02-01 00:01:00') every(1s) fill(near)" + tdSql.query(sql, queryTimes=1) + + sql = f"select _irowts, _irowts_origin, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', '2020-02-01 00:01:00') every(1s) fill(NULL)" + tdSql.error(sql, -2147473833) + sql = f"select _irowts, _irowts_origin, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', '2020-02-01 00:01:00') every(1s) fill(linear)" + tdSql.error(sql, -2147473833) + sql = f"select _irowts, _irowts_origin, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', '2020-02-01 00:01:00') every(1s) fill(NULL_F)" + tdSql.error(sql, -2147473833) + + + def test_interp_fill_extension(self): + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', 1h) fill(near, 0, 0)" + tdSql.query(sql, queryTimes=1) + + ### must specify value + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', 1h) fill(near)" + tdSql.error(sql, -2147473915) + ### num of fill value mismatch + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', 1h) fill(near, 1)" + tdSql.error(sql, -2147473915) + + ### range with around interval cannot specify two timepoints, currently not supported + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', '2020-02-01 00:02:00', 1h) fill(near, 1, 1)" + tdSql.error(sql, -2147473920) ## syntax error + + ### NULL/linear cannot specify other values + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', '2020-02-01 00:02:00') fill(NULL, 1, 1)" + tdSql.error(sql, -2147473920) + + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', '2020-02-01 00:02:00') fill(linear, 1, 1)" + tdSql.error(sql, -2147473920) ## syntax error + + ### cannot have every clause with range around + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', 1h) every(1s) fill(prev, 1, 1)" + tdSql.error(sql, -2147473827) ## TSDB_CODE_PAR_INVALID_INTERP_CLAUSE + + ### cannot specify near/prev/next values when using range + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', '2020-02-01 00:01:00') every(1s) fill(near, 1, 1)" + tdSql.error(sql, -2147473915) ## cannot specify values + + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00') every(1s) fill(near, 1, 1)" + tdSql.error(sql, -2147473915) ## cannot specify values + + ### when range around interval is set, only prev/next/near is supported + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', 1h) fill(NULL, 1, 1)" + tdSql.error(sql, -2147473920) + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', 1h) fill(NULL)" + tdSql.error(sql, -2147473861) ## TSDB_CODE_PAR_INVALID_FILL_TIME_RANGE + + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', 1h) fill(linear, 1, 1)" + tdSql.error(sql, -2147473920) + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', 1h) fill(linear)" + tdSql.error(sql, -2147473861) ## TSDB_CODE_PAR_INVALID_FILL_TIME_RANGE + + ### range interval cannot be 0 + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', 0h) fill(near, 1, 1)" + tdSql.error(sql, -2147473861) ## TSDB_CODE_PAR_INVALID_FILL_TIME_RANGE + + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', 1y) fill(near, 1, 1)" + tdSql.error(sql, -2147473915) ## TSDB_CODE_PAR_WRONG_VALUE_TYPE + + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', 1n) fill(near, 1, 1)" + tdSql.error(sql, -2147473915) ## TSDB_CODE_PAR_WRONG_VALUE_TYPE + + sql = f"select _irowts, interp(c1), interp(c2), _isfilled from test.meters where ts between '2020-02-01 00:00:00' and '2020-02-01 00:00:00' range('2020-02-01 00:00:00', 1h) fill(near, 1, 1)" + tdSql.query(sql, queryTimes=1) + tdSql.checkRows(0) + + ### first(ts) | last(ts) + ### 2018-09-17 09:00:00.047 | 2018-09-17 10:23:19.863 + sql = "select to_char(first(ts), 'YYYY-MM-DD HH24:MI:SS.MS') from test.meters" + tdSql.query(sql, queryTimes=1) + first_ts = tdSql.queryResult[0][0] + sql = "select to_char(last(ts), 'YYYY-MM-DD HH24:MI:SS.MS') from test.meters" + tdSql.query(sql, queryTimes=1) + last_ts = tdSql.queryResult[0][0] + sql = f"select _irowts_origin, _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2020-02-01 00:00:00', 1d) fill(near, 1, 2)" + tdSql.query(sql, queryTimes=1) + tdSql.checkRows(1) + tdSql.checkData(0, 0, None) + tdSql.checkData(0, 1, '2020-02-01 00:00:00.000') + tdSql.checkData(0, 2, 1) + tdSql.checkData(0, 3, 2) + tdSql.checkData(0, 4, True) + + sql = f"select _irowts_origin, _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2018-09-18 10:25:00', 1d) fill(prev, 3, 4)" + tdSql.query(sql, queryTimes=1) + tdSql.checkRows(1) + tdSql.checkData(0, 0, None) + tdSql.checkData(0, 1, '2018-09-18 10:25:00.000') + tdSql.checkData(0, 2, 3) + tdSql.checkData(0, 3, 4) + tdSql.checkData(0, 4, True) + + sql = f"select _irowts_origin, _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2018-09-16 08:25:00', 1d) fill(next, 5, 6)" + tdSql.query(sql, queryTimes=1) + tdSql.checkRows(1) + tdSql.checkData(0, 0, None) + tdSql.checkData(0, 1, '2018-09-16 08:25:00.000') + tdSql.checkData(0, 2, 5) + tdSql.checkData(0, 3, 6) + tdSql.checkData(0, 4, True) + + sql = f"select _irowts_origin, _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2018-09-16 09:00:01', 1d) fill(next, 1, 2)" + tdSql.query(sql, queryTimes=1) + tdSql.checkRows(1) + tdSql.checkData(0, 0, first_ts) + tdSql.checkData(0, 1, '2018-09-16 09:00:01') + tdSql.checkData(0, 4, True) + + sql = f"select _irowts_origin, _irowts, interp(c1), interp(c2), _isfilled from test.meters range('2018-09-18 10:23:19', 1d) fill(prev, 1, 2)" + tdSql.query(sql, queryTimes=1) + tdSql.checkRows(1) + tdSql.checkData(0, 0, last_ts) + tdSql.checkData(0, 1, '2018-09-18 10:23:19') + tdSql.checkData(0, 4, True) + + sql = f"select _irowts_origin, _irowts, interp(c1), interp(c2), _isfilled from test.meters range('{last_ts}', 1a) fill(next, 1, 2)" + tdSql.query(sql, queryTimes=1) + tdSql.checkRows(1) + tdSql.checkData(0, 0, last_ts) + tdSql.checkData(0, 1, last_ts) + tdSql.checkData(0, 4, False) + + + def test_interval_fill_extension(self): + ## not allowed + sql = f"select count(*) from test.meters interval(1s) fill(near)" + tdSql.error(sql, -2147473920) ## syntax error + + sql = f"select count(*) from test.meters interval(1s) fill(prev, 1)" + tdSql.error(sql, -2147473920) ## syntax error + sql = f"select count(*) from test.meters interval(1s) fill(next, 1)" + tdSql.error(sql, -2147473920) ## syntax error + + def test_interp_fill_extension_stream(self): + ## near is not supported + sql = f"create stream s1 trigger force_window_close into test.s_res_tb as select _irowts, interp(c1), interp(c2)from test.meters partition by tbname every(1s) fill(near);" + tdSql.error(sql, -2147473851) ## TSDB_CODE_PAR_INVALID_STREAM_QUERY + + ## _irowts_origin is not support + sql = f"create stream s1 trigger force_window_close into test.s_res_tb as select _irowts_origin, interp(c1), interp(c2)from test.meters partition by tbname every(1s) fill(prev);" + tdSql.error(sql, -2147473851) ## TSDB_CODE_PAR_INVALID_STREAM_QUERY + + sql = f"create stream s1 trigger force_window_close into test.s_res_tb as select _irowts, interp(c1), interp(c2)from test.meters partition by tbname every(1s) fill(next, 1, 1);" + tdSql.error(sql, -2147473915) ## cannot specify values + + def test_interp_extension(self): + self.test_interp_fill_extension_near() + self.test_interp_extension_irowts_origin() + self.test_interp_fill_extension() + self.test_interval_fill_extension() + self.test_interp_fill_extension_stream() + + def stop(self): + tdSql.close() + tdLog.success(f"{__file__} successfully executed") + + +event = threading.Event() + +tdCases.addLinux(__file__, TDTestCase()) +tdCases.addWindows(__file__, TDTestCase()) From 114c9ae44b7775eb3f33fbd7fa8aeb7738907f28 Mon Sep 17 00:00:00 2001 From: 54liuyao <54liuyao@163.com> Date: Fri, 29 Nov 2024 09:56:21 +0800 Subject: [PATCH 22/31] add ci --- .../8-stream/force_window_close_interp.py | 12 ++++ tests/system-test/8-stream/stream_basic.py | 67 +++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/tests/system-test/8-stream/force_window_close_interp.py b/tests/system-test/8-stream/force_window_close_interp.py index f39ad82ed7..f78330411b 100644 --- a/tests/system-test/8-stream/force_window_close_interp.py +++ b/tests/system-test/8-stream/force_window_close_interp.py @@ -93,6 +93,18 @@ class TDTestCase: tdSql.error( f"create stream itp_force_error_1 trigger force_window_close IGNORE EXPIRED 1 IGNORE UPDATE 0 into itp_force_error_1 as select _irowts,tbname,_isfilled,interp(c11,1) from {self.stb_name} partition by tbname every(5s) fill(prev) ;" ) + tdSql.error( + f"create stream itp_1d_next_error_1 trigger force_window_close FILL_HISTORY 1 IGNORE EXPIRED 1 IGNORE UPDATE 1 into itp_1d_next_error_t1 as select _irowts,tbname,_isfilled,interp(current) from {self.stb_name} where groupid=100 partition by every(5s) fill(next) ;" + ) + tdSql.error( + f"create stream itp_1d_next_error_1 trigger at_once FILL_HISTORY 1 IGNORE EXPIRED 1 IGNORE UPDATE 1 into itp_1d_next_error_t1 as select _irowts,tbname,_isfilled,interp(current) from {self.stb_name} where groupid=100 partition by every(5s) fill(next) ;" + ) + tdSql.error( + f"create stream itp_1d_next_error_1 trigger window_close FILL_HISTORY 1 IGNORE EXPIRED 1 IGNORE UPDATE 1 into itp_1d_next_error_t1 as select _irowts,tbname,_isfilled,interp(current) from {self.stb_name} where groupid=100 partition by every(5s) fill(next) ;" + ) + tdSql.error( + f"create stream itp_1d_next_error_1 trigger max_delay 5s FILL_HISTORY 1 IGNORE EXPIRED 1 IGNORE UPDATE 1 into itp_1d_next_error_t1 as select _irowts,tbname,_isfilled,interp(current) from {self.stb_name} where groupid=100 partition by every(5s) fill(next) ;" + ) # function name : interp trigger_mode = "force_window_close" diff --git a/tests/system-test/8-stream/stream_basic.py b/tests/system-test/8-stream/stream_basic.py index 95f4f1addc..750943ba81 100644 --- a/tests/system-test/8-stream/stream_basic.py +++ b/tests/system-test/8-stream/stream_basic.py @@ -24,6 +24,7 @@ import time import traceback import os from os import path +import psutil class TDTestCase: @@ -117,6 +118,69 @@ class TDTestCase: if not tdSql.getData(2, 0).startswith('new-t3_stb_'): tdLog.exit("error6") + def caseDropStream(self): + tdLog.info(f"start caseDropStream") + sql = "drop database if exists d1;" + tdSql.query(sql) + sql = "drop database if exists db;" + tdSql.query(sql) + + sql ="show streams;" + tdSql.query(sql) + tdSql.check_rows_loop(0, sql, loopCount=100, waitTime=0.5) + + sql ="select * from information_schema.ins_stream_tasks;" + tdSql.query(sql) + tdSql.check_rows_loop(0, sql, loopCount=100, waitTime=0.5) + + self.taosBenchmark(" -d db -t 2 -v 2 -n 1000000 -y") + # create stream + tdSql.execute("use db;") + tdSql.execute("create stream stream4 fill_history 1 into sta4 as select _wstart, sum(current),avg(current),last(current),min(voltage),first(voltage),last(phase),max(phase),count(phase), _wend, _wduration from meters partition by tbname, ts interval(10a);", show=True) + + time.sleep(10) + + sql ="select * from information_schema.ins_stream_tasks where status == 'ready';" + tdSql.query(sql, show=True) + tdSql.check_rows_loop(4, sql, loopCount=100, waitTime=0.5) + + pl = psutil.pids() + for pid in pl: + try: + if psutil.Process(pid).name() == 'taosd': + taosdPid = pid + break + except psutil.NoSuchProcess: + pass + tdLog.info("taosd pid:{}".format(taosdPid)) + p = psutil.Process(taosdPid) + + cpuInfo = p.cpu_percent(interval=5) + tdLog.info("taosd cpu:{}".format(cpuInfo)) + + tdSql.execute("drop stream stream4;", show=True) + + sql ="show streams;" + tdSql.query(sql, show=True) + tdSql.check_rows_loop(0, sql, loopCount=100, waitTime=0.5) + + sql ="select * from information_schema.ins_stream_tasks;" + tdSql.query(sql, show=True) + tdSql.check_rows_loop(0, sql, loopCount=100, waitTime=0.5) + + for i in range(10): + cpuInfo = p.cpu_percent(interval=5) + tdLog.info("taosd cpu:{}".format(cpuInfo)) + if cpuInfo < 10: + return + else: + time.sleep(1) + continue + cpuInfo = p.cpu_percent(interval=5) + tdLog.info("taosd cpu:{}".format(cpuInfo)) + if cpuInfo > 10: + tdLog.exit("drop stream failed, stream tasks are still running") + # run def run(self): self.case1() @@ -145,6 +209,9 @@ class TDTestCase: tdSql.query(sql) tdSql.checkRows(0) + self.caseDropStream() + + # stop def stop(self): tdSql.close() From fbaf63aa1c495c7f21aa076dae0661bf763afd63 Mon Sep 17 00:00:00 2001 From: Alex Duan <51781608+DuanKuanJun@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:03:08 +0800 Subject: [PATCH 23/31] =?UTF-8?q?Update=2008-taos-cli.md=20taos-CLI=20?= =?UTF-8?q?=E5=9C=A8=E7=BA=BF=E5=B8=AE=E5=BF=99=E6=96=87=E6=A1=A3=E4=B8=AD?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=94=99=E8=AF=AF=E7=A0=81=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?=E6=8C=87=E5=BC=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/zh/14-reference/02-tools/08-taos-cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/14-reference/02-tools/08-taos-cli.md b/docs/zh/14-reference/02-tools/08-taos-cli.md index 18b4b604f4..b140fa9b16 100644 --- a/docs/zh/14-reference/02-tools/08-taos-cli.md +++ b/docs/zh/14-reference/02-tools/08-taos-cli.md @@ -90,7 +90,7 @@ taos -h h1.taos.com -s "use db; show tables;" 也可以通过配置文件中的参数设置来控制 TDengine CLI 的行为。可用配置参数请参考[客户端配置](../../components/taosc) ## 错误代码表 -在 TDengine 3.3.5.0 版本后 TDengine CLI 在返回的错误信息中包含了具体的错误代码,用户可到 TDengine 官网的错误代码详细说明页面查找具体原因及解决措施,见:[错误码参考表](../error_code/) +在 TDengine 3.3.5.0 版本后 TDengine CLI 在返回的错误信息中包含了具体的错误代码,用户可到 TDengine 官网错误代码详细说明页面查找具体原因及解决措施,参考见:[错误码参考表](https://docs.taosdata.com/reference/error-code/) ## TDengine CLI TAB 键补全 From 653eff8c1aaa443be9951ef06c8f00a7a90ac66d Mon Sep 17 00:00:00 2001 From: Pengrongkun Date: Fri, 29 Nov 2024 10:05:35 +0800 Subject: [PATCH 24/31] taos_stmt2_get_stb_fields support insert into ? using stb tags(?) values(?) --- include/libs/parser/parser.h | 9 +-- source/client/inc/clientStmt.h | 4 +- source/client/src/clientStmt.c | 2 +- source/client/src/clientStmt2.c | 9 +-- source/libs/parser/src/parInsertSql.c | 11 ++-- source/libs/parser/src/parInsertStmt.c | 79 +++++++++++++++++++------- tests/script/api/stmt2-get-fields.c | 34 ++++++++++- 7 files changed, 108 insertions(+), 40 deletions(-) mode change 100644 => 100755 tests/script/api/stmt2-get-fields.c diff --git a/include/libs/parser/parser.h b/include/libs/parser/parser.h index 0fb6261ac8..fbbe7cc40c 100644 --- a/include/libs/parser/parser.h +++ b/include/libs/parser/parser.h @@ -27,7 +27,7 @@ extern "C" { typedef struct SStmtCallback { TAOS_STMT* pStmt; int32_t (*getTbNameFn)(TAOS_STMT*, char**); - int32_t (*setInfoFn)(TAOS_STMT*, STableMeta*, void*, SName*, bool, SHashObj*, SHashObj*, const char*); + int32_t (*setInfoFn)(TAOS_STMT*, STableMeta*, void*, SName*, bool, SHashObj*, SHashObj*, const char*, bool); int32_t (*getExecInfoFn)(TAOS_STMT*, SHashObj**, SHashObj**); } SStmtCallback; @@ -147,7 +147,8 @@ int32_t qBindStmtColsValue(void* pBlock, SArray* pCols, TAOS_MULTI_BIND* bind, c int32_t qBindStmtSingleColValue(void* pBlock, SArray* pCols, TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen, int32_t colIdx, int32_t rowNum); int32_t qBuildStmtColFields(void* pDataBlock, int32_t* fieldNum, TAOS_FIELD_E** fields); -int32_t qBuildStmtStbColFields(void* pBlock, int32_t* fieldNum, TAOS_FIELD_STB** fields); +int32_t qBuildStmtStbColFields(void* pBlock, void* boundTags, bool hasCtbName, int32_t* fieldNum, + TAOS_FIELD_STB** fields); int32_t qBuildStmtTagFields(void* pBlock, void* boundTags, int32_t* fieldNum, TAOS_FIELD_E** fields); int32_t qBindStmtTagsValue(void* pBlock, void* boundTags, int64_t suid, const char* sTableName, char* tName, TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen); @@ -177,8 +178,8 @@ int32_t smlBindData(SQuery* handle, bool dataFormat, SArray* tags, SArray* colsS STableMeta* pTableMeta, char* tableName, const char* sTableName, int32_t sTableNameLen, int32_t ttl, char* msgBuf, int32_t msgBufLen); int32_t smlBuildOutput(SQuery* handle, SHashObj* pVgHash); -int rawBlockBindData(SQuery* query, STableMeta* pTableMeta, void* data, SVCreateTbReq* pCreateTb, void* fields, - int numFields, bool needChangeLength, char* errstr, int32_t errstrLen, bool raw); +int rawBlockBindData(SQuery* query, STableMeta* pTableMeta, void* data, SVCreateTbReq* pCreateTb, void* fields, + int numFields, bool needChangeLength, char* errstr, int32_t errstrLen, bool raw); int32_t rewriteToVnodeModifyOpStmt(SQuery* pQuery, SArray* pBufArray); int32_t serializeVgroupsCreateTableBatch(SHashObj* pVgroupHashmap, SArray** pOut); diff --git a/source/client/inc/clientStmt.h b/source/client/inc/clientStmt.h index 68765ee47a..3540dc5c68 100644 --- a/source/client/inc/clientStmt.h +++ b/source/client/inc/clientStmt.h @@ -64,12 +64,12 @@ typedef struct SStmtBindInfo { int32_t sBindLastIdx; int8_t tbType; bool tagsCached; + bool preCtbname; void *boundTags; char tbName[TSDB_TABLE_FNAME_LEN]; char tbFName[TSDB_TABLE_FNAME_LEN]; char stbFName[TSDB_TABLE_FNAME_LEN]; SName sname; - char statbName[TSDB_TABLE_FNAME_LEN]; } SStmtBindInfo; @@ -133,7 +133,6 @@ typedef struct SStmtQueue { uint64_t qRemainNum; } SStmtQueue; - typedef struct STscStmt { STscObj *taos; SCatalog *pCatalog; @@ -204,7 +203,6 @@ extern char *gStmtStatusStr[]; } \ } while (0) - #define STMT_FLOG(param, ...) qFatal("stmt:%p " param, pStmt, __VA_ARGS__) #define STMT_ELOG(param, ...) qError("stmt:%p " param, pStmt, __VA_ARGS__) #define STMT_DLOG(param, ...) qDebug("stmt:%p " param, pStmt, __VA_ARGS__) diff --git a/source/client/src/clientStmt.c b/source/client/src/clientStmt.c index e56d4cc4f6..b1f5af4593 100644 --- a/source/client/src/clientStmt.c +++ b/source/client/src/clientStmt.c @@ -260,7 +260,7 @@ int32_t stmtUpdateExecInfo(TAOS_STMT* stmt, SHashObj* pVgHash, SHashObj* pBlockH } int32_t stmtUpdateInfo(TAOS_STMT* stmt, STableMeta* pTableMeta, void* tags, SName* tbName, bool autoCreateTbl, - SHashObj* pVgHash, SHashObj* pBlockHash, const char* sTableName) { + SHashObj* pVgHash, SHashObj* pBlockHash, const char* sTableName, bool preCtbname) { STscStmt* pStmt = (STscStmt*)stmt; STMT_ERR_RET(stmtUpdateBindInfo(stmt, pTableMeta, tags, tbName, sTableName, autoCreateTbl)); diff --git a/source/client/src/clientStmt2.c b/source/client/src/clientStmt2.c index 4bbfc6afaa..2eedab2289 100644 --- a/source/client/src/clientStmt2.c +++ b/source/client/src/clientStmt2.c @@ -178,7 +178,7 @@ static int32_t stmtGetTbName(TAOS_STMT2* stmt, char** tbName) { } static int32_t stmtUpdateBindInfo(TAOS_STMT2* stmt, STableMeta* pTableMeta, void* tags, SName* tbName, - const char* sTableName, bool autoCreateTbl) { + const char* sTableName, bool autoCreateTbl, bool preCtbname) { STscStmt2* pStmt = (STscStmt2*)stmt; char tbFName[TSDB_TABLE_FNAME_LEN]; int32_t code = tNameExtractFullName(tbName, tbFName); @@ -202,6 +202,7 @@ static int32_t stmtUpdateBindInfo(TAOS_STMT2* stmt, STableMeta* pTableMeta, void pStmt->bInfo.boundTags = tags; pStmt->bInfo.tagsCached = false; + pStmt->bInfo.preCtbname = preCtbname; tstrncpy(pStmt->bInfo.stbFName, sTableName, sizeof(pStmt->bInfo.stbFName)); return TSDB_CODE_SUCCESS; @@ -217,10 +218,10 @@ static int32_t stmtUpdateExecInfo(TAOS_STMT2* stmt, SHashObj* pVgHash, SHashObj* } static int32_t stmtUpdateInfo(TAOS_STMT2* stmt, STableMeta* pTableMeta, void* tags, SName* tbName, bool autoCreateTbl, - SHashObj* pVgHash, SHashObj* pBlockHash, const char* sTableName) { + SHashObj* pVgHash, SHashObj* pBlockHash, const char* sTableName, bool preCtbname) { STscStmt2* pStmt = (STscStmt2*)stmt; - STMT_ERR_RET(stmtUpdateBindInfo(stmt, pTableMeta, tags, tbName, sTableName, autoCreateTbl)); + STMT_ERR_RET(stmtUpdateBindInfo(stmt, pTableMeta, tags, tbName, sTableName, autoCreateTbl, preCtbname)); STMT_ERR_RET(stmtUpdateExecInfo(stmt, pVgHash, pBlockHash)); pStmt->sql.autoCreateTbl = autoCreateTbl; @@ -1092,7 +1093,7 @@ static int stmtFetchStbColFields2(STscStmt2* pStmt, int32_t* fieldNum, TAOS_FIEL } } - STMT_ERR_RET(qBuildStmtStbColFields(*pDataBlock, fieldNum, fields)); + STMT_ERR_RET(qBuildStmtStbColFields(*pDataBlock, pStmt->bInfo.boundTags, pStmt->bInfo.preCtbname, fieldNum, fields)); return TSDB_CODE_SUCCESS; } diff --git a/source/libs/parser/src/parInsertSql.c b/source/libs/parser/src/parInsertSql.c index 750621bf66..ad439a11ff 100644 --- a/source/libs/parser/src/parInsertSql.c +++ b/source/libs/parser/src/parInsertSql.c @@ -31,6 +31,7 @@ typedef struct SInsertParseContext { bool needTableTagVal; bool needRequest; // whether or not request server bool isStmtBind; // whether is stmt bind + bool preCtbname; } SInsertParseContext; typedef int32_t (*_row_append_fn_t)(SMsgBuf* pMsgBuf, const void* value, int32_t len, void* param); @@ -1831,6 +1832,7 @@ static int32_t doGetStbRowValues(SInsertParseContext* pCxt, SVnodeModifyOpStmt* if (TK_NK_QUESTION == pToken->type) { pCxt->isStmtBind = true; if (pCols->pColIndex[i] == tbnameIdx) { + pCxt->preCtbname = false; *bFoundTbName = true; } if (NULL == pCxt->pComCxt->pStmtCb) { @@ -2571,10 +2573,11 @@ static int32_t checkTableClauseFirstToken(SInsertParseContext* pCxt, SVnodeModif } int32_t code = (*pCxt->pComCxt->pStmtCb->getTbNameFn)(pCxt->pComCxt->pStmtCb->pStmt, &tbName); if (code != TSDB_CODE_SUCCESS) { - return code; + pCxt->preCtbname = true; + } else { + pTbName->z = tbName; + pTbName->n = strlen(tbName); } - pTbName->z = tbName; - pTbName->n = strlen(tbName); } if (pCxt->isStmtBind) { @@ -2599,7 +2602,7 @@ static int32_t setStmtInfo(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) SStmtCallback* pStmtCb = pCxt->pComCxt->pStmtCb; int32_t code = (*pStmtCb->setInfoFn)(pStmtCb->pStmt, pStmt->pTableMeta, tags, &pStmt->targetTableName, pStmt->usingTableProcessing, pStmt->pVgroupsHashObj, pStmt->pTableBlockHashObj, - pStmt->usingTableName.tname); + pStmt->usingTableName.tname, pCxt->preCtbname); memset(&pCxt->tags, 0, sizeof(pCxt->tags)); pStmt->pVgroupsHashObj = NULL; diff --git a/source/libs/parser/src/parInsertStmt.c b/source/libs/parser/src/parInsertStmt.c index c6951d229d..db732f9b6e 100644 --- a/source/libs/parser/src/parInsertStmt.c +++ b/source/libs/parser/src/parInsertStmt.c @@ -939,39 +939,72 @@ int32_t buildBoundFields(int32_t numOfBound, int16_t* boundColumns, SSchema* pSc } int32_t buildStbBoundFields(SBoundColInfo boundColsInfo, SSchema* pSchema, int32_t* fieldNum, TAOS_FIELD_STB** fields, - STableMeta* pMeta) { + STableMeta* pMeta, void* boundTags, bool preCtbname) { + SBoundColInfo* tags = (SBoundColInfo*)boundTags; + int32_t numOfBound = boundColsInfo.numOfBound + tags->numOfBound + (preCtbname ? 1 : 0); + int32_t idx = 0; if (fields != NULL) { - *fields = taosMemoryCalloc(boundColsInfo.numOfBound, sizeof(TAOS_FIELD_STB)); + *fields = taosMemoryCalloc(numOfBound, sizeof(TAOS_FIELD_STB)); if (NULL == *fields) { return terrno; } - SSchema* schema = &pSchema[boundColsInfo.pColIndex[0]]; - if (TSDB_DATA_TYPE_TIMESTAMP == schema->type) { - (*fields)[0].precision = pMeta->tableInfo.precision; + if (preCtbname && numOfBound != boundColsInfo.numOfBound) { + (*fields)[idx].field_type = TAOS_FIELD_TBNAME; + tstrncpy((*fields)[idx].name, "tbname", sizeof((*fields)[idx].name)); + (*fields)[idx].type = TSDB_DATA_TYPE_VARCHAR; + (*fields)[idx].bytes = TSDB_TABLE_FNAME_LEN; + idx++; } - for (int32_t i = 0; i < boundColsInfo.numOfBound; ++i) { - int16_t idx = boundColsInfo.pColIndex[i]; + if (tags->numOfBound > 0) { + SSchema* pSchema = getTableTagSchema(pMeta); - if (idx == pMeta->tableInfo.numOfColumns + pMeta->tableInfo.numOfTags) { - (*fields)[i].field_type = TAOS_FIELD_TBNAME; - tstrncpy((*fields)[i].name, "tbname", sizeof((*fields)[i].name)); - continue; - } else if (idx < pMeta->tableInfo.numOfColumns) { - (*fields)[i].field_type = TAOS_FIELD_COL; - } else { - (*fields)[i].field_type = TAOS_FIELD_TAG; + if (TSDB_DATA_TYPE_TIMESTAMP == pSchema->type) { + (*fields)[0].precision = pMeta->tableInfo.precision; } - schema = &pSchema[idx]; - tstrncpy((*fields)[i].name, schema->name, sizeof((*fields)[i].name)); - (*fields)[i].type = schema->type; - (*fields)[i].bytes = schema->bytes; + for (int32_t i = 0; i < tags->numOfBound; ++i) { + (*fields)[idx].field_type = TAOS_FIELD_TAG; + + SSchema* schema = &pSchema[tags->pColIndex[i]]; + tstrncpy((*fields)[idx].name, schema->name, sizeof((*fields)[i].name)); + (*fields)[idx].type = schema->type; + (*fields)[idx].bytes = schema->bytes; + + idx++; + } + } + + if (boundColsInfo.numOfBound > 0) { + SSchema* schema = &pSchema[boundColsInfo.pColIndex[0]]; + if (TSDB_DATA_TYPE_TIMESTAMP == schema->type) { + (*fields)[0].precision = pMeta->tableInfo.precision; + } + + for (int32_t i = 0; i < boundColsInfo.numOfBound; ++i) { + int16_t idxCol = boundColsInfo.pColIndex[i]; + + if (idxCol == pMeta->tableInfo.numOfColumns + pMeta->tableInfo.numOfTags) { + (*fields)[idx].field_type = TAOS_FIELD_TBNAME; + tstrncpy((*fields)[i].name, "tbname", sizeof((*fields)[idx].name)); + continue; + } else if (idxCol < pMeta->tableInfo.numOfColumns) { + (*fields)[idx].field_type = TAOS_FIELD_COL; + } else { + (*fields)[idx].field_type = TAOS_FIELD_TAG; + } + + schema = &pSchema[idxCol]; + tstrncpy((*fields)[idx].name, schema->name, sizeof((*fields)[idx].name)); + (*fields)[idx].type = schema->type; + (*fields)[idx].bytes = schema->bytes; + idx++; + } } } - *fieldNum = boundColsInfo.numOfBound; + *fieldNum = numOfBound; return TSDB_CODE_SUCCESS; } @@ -1018,7 +1051,8 @@ int32_t qBuildStmtColFields(void* pBlock, int32_t* fieldNum, TAOS_FIELD_E** fiel return TSDB_CODE_SUCCESS; } -int32_t qBuildStmtStbColFields(void* pBlock, int32_t* fieldNum, TAOS_FIELD_STB** fields) { +int32_t qBuildStmtStbColFields(void* pBlock, void* boundTags, bool preCtbname, int32_t* fieldNum, + TAOS_FIELD_STB** fields) { STableDataCxt* pDataBlock = (STableDataCxt*)pBlock; SSchema* pSchema = getTableColumnSchema(pDataBlock->pMeta); if (pDataBlock->boundColsInfo.numOfBound <= 0) { @@ -1030,7 +1064,8 @@ int32_t qBuildStmtStbColFields(void* pBlock, int32_t* fieldNum, TAOS_FIELD_STB** return TSDB_CODE_SUCCESS; } - CHECK_CODE(buildStbBoundFields(pDataBlock->boundColsInfo, pSchema, fieldNum, fields, pDataBlock->pMeta)); + CHECK_CODE(buildStbBoundFields(pDataBlock->boundColsInfo, pSchema, fieldNum, fields, pDataBlock->pMeta, boundTags, + preCtbname)); return TSDB_CODE_SUCCESS; } diff --git a/tests/script/api/stmt2-get-fields.c b/tests/script/api/stmt2-get-fields.c old mode 100644 new mode 100755 index befde39f8a..dc4ef52e80 --- a/tests/script/api/stmt2-get-fields.c +++ b/tests/script/api/stmt2-get-fields.c @@ -21,7 +21,7 @@ void getFields(TAOS *taos, const char *sql) { if (code != 0) { printf("failed get col,ErrCode: 0x%x, ErrMessage: %s.\n", code, taos_stmt2_error(stmt)); } else { - printf("col nums:%d\n", fieldNum); + printf("bind nums:%d\n", fieldNum); for (int i = 0; i < fieldNum; i++) { printf("field[%d]: %s, data_type:%d, field_type:%d\n", i, pFields[i].name, pFields[i].type, pFields[i].field_type); @@ -76,7 +76,7 @@ void do_stmt(TAOS *taos) { // case 4 : INSERT INTO db.? using db.stb TAGS(?,?) VALUES(?,?) // not support this clause sql = "insert into db.? using db.stb tags(?, ?) values(?,?)"; - printf("case 4 (not support): %s\n", sql); + printf("case 4 : %s\n", sql); getFields(taos, sql); // case 5 : INSERT INTO db.stb(t1,t2,ts,b) values(?,?,?,?) @@ -114,6 +114,36 @@ void do_stmt(TAOS *taos) { sql = "insert into db.ntb(nts,ni) values(?,?)"; printf("case 10 : %s\n", sql); getFields(taos, sql); + + // case 11 : insert into db.? values(?,?) + // normal table must have tbnam + sql = "insert into db.? values(?,?)"; + printf("case 11 (normal table must have tbname): %s\n", sql); + getFields(taos, sql); + + // case 12 : insert into db.? using db.stb(t2,t1) tags(?, ?) (b,ts)values(?,?) + // disordered + sql = "insert into db.? using db.stb(t2,t1) tags(?, ?) (b,ts)values(?,?)"; + printf("case 12 : %s\n", sql); + getFields(taos, sql); + + // case 13 : insert into db.? using db.stb tags(?, ?) values(?,?) + // no field name + sql = "insert into db.? using db.stb tags(?, ?) values(?,?)"; + printf("case 13 : %s\n", sql); + getFields(taos, sql); + + // case 14 : insert into db.? using db.stb tags(?, ?) values(?,?) + // less para + sql = "insert into db.? using db.stb (t2)tags(?) (ts)values(?)"; + printf("case 14 : %s\n", sql); + getFields(taos, sql); + + // case 15 : insert into db.? using db.stb tags(?, ?) values(?,?) + // less para + sql = "insert into db.d0 (ts)values(?)"; + printf("case 15 : %s\n", sql); + getFields(taos, sql); } int main() { From 140288673a04fccd6587d41a8722bd7a41281441 Mon Sep 17 00:00:00 2001 From: Alex Duan <51781608+DuanKuanJun@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:42:02 +0800 Subject: [PATCH 25/31] Update 08-taos-cli.md --- docs/zh/14-reference/02-tools/08-taos-cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/14-reference/02-tools/08-taos-cli.md b/docs/zh/14-reference/02-tools/08-taos-cli.md index b140fa9b16..adecc5f760 100644 --- a/docs/zh/14-reference/02-tools/08-taos-cli.md +++ b/docs/zh/14-reference/02-tools/08-taos-cli.md @@ -90,7 +90,7 @@ taos -h h1.taos.com -s "use db; show tables;" 也可以通过配置文件中的参数设置来控制 TDengine CLI 的行为。可用配置参数请参考[客户端配置](../../components/taosc) ## 错误代码表 -在 TDengine 3.3.5.0 版本后 TDengine CLI 在返回的错误信息中包含了具体的错误代码,用户可到 TDengine 官网错误代码详细说明页面查找具体原因及解决措施,参考见:[错误码参考表](https://docs.taosdata.com/reference/error-code/) +在 TDengine 3.3.4.8 版本后 TDengine CLI 在返回错误信息中返回了具体错误码,用户可到 TDengine 官网错误码页面查找具体原因及解决措施,见:[错误码参考表](https://docs.taosdata.com/reference/error-code/) ## TDengine CLI TAB 键补全 From b4cf018caebc604f427bde9a6ec0a42c10201e14 Mon Sep 17 00:00:00 2001 From: Alex Duan <51781608+DuanKuanJun@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:47:10 +0800 Subject: [PATCH 26/31] Update 08-taos-cli.md english --- docs/en/14-reference/02-tools/08-taos-cli.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/en/14-reference/02-tools/08-taos-cli.md b/docs/en/14-reference/02-tools/08-taos-cli.md index c8131a529f..daa8029fa7 100644 --- a/docs/en/14-reference/02-tools/08-taos-cli.md +++ b/docs/en/14-reference/02-tools/08-taos-cli.md @@ -84,6 +84,9 @@ taos -h h1.taos.com -s "use db; show tables;" You can also control the behavior of the TDengine CLI through parameters set in the configuration file. For available configuration parameters, refer to [Client Configuration](../../components/taosc). +## Error Codes Reference +After version 3.3.4.8 of TDengine, the TDengine CLI returned error codes in the error message. Users can search for the specific cause and solution on the error code page of the TDengine official website, see [Error Codes Table](https://docs.taosdata.com/reference/error-code) + ## TDengine CLI Tips - Use the up and down arrow keys to view previously entered commands. From e1d40394c6f85ef201ebb7a9ab0c578a165d7e4a Mon Sep 17 00:00:00 2001 From: wangjiaming0909 <604227650@qq.com> Date: Fri, 29 Nov 2024 11:12:51 +0800 Subject: [PATCH 27/31] fix doc and add tests --- docs/en/14-reference/03-taos-sql/06-select.md | 2 +- docs/en/14-reference/03-taos-sql/10-function.md | 6 +++--- docs/zh/14-reference/03-taos-sql/06-select.md | 2 +- docs/zh/14-reference/03-taos-sql/10-function.md | 6 +++--- tests/system-test/2-query/interp_extension.py | 4 ++++ 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/en/14-reference/03-taos-sql/06-select.md b/docs/en/14-reference/03-taos-sql/06-select.md index 6b3ab751ec..160c0d2abd 100644 --- a/docs/en/14-reference/03-taos-sql/06-select.md +++ b/docs/en/14-reference/03-taos-sql/06-select.md @@ -63,7 +63,7 @@ window_clause: { interp_clause: RANGE(ts_val [, ts_val]) EVERY(every_val) FILL(fill_mod_and_val) - | RANGE(ts_val, interval_val) FILL(fill_mod_and_val) + | RANGE(ts_val, surrounding_time_val) FILL(fill_mod_and_val) partition_by_clause: PARTITION BY partition_by_expr [, partition_by_expr] ... diff --git a/docs/en/14-reference/03-taos-sql/10-function.md b/docs/en/14-reference/03-taos-sql/10-function.md index ff52125cfd..8a1d5a8141 100644 --- a/docs/en/14-reference/03-taos-sql/10-function.md +++ b/docs/en/14-reference/03-taos-sql/10-function.md @@ -1869,9 +1869,9 @@ INTERP(expr [, ignore_null_values]) - INTERP can be used with the pseudo-column _irowts to return the timestamp corresponding to the interpolation point (supported from version 3.0.2.0 onwards). - INTERP can also be used with the pseudo-column _isfilled to show whether the returned result is from the original record or produced by the interpolation algorithm (supported from version 3.0.3.0 onwards). - When querying a table with a composite primary key, if there are multiple records with the same timestamp, only the data corresponding to the minimum composite primary key will participate in the calculation. -- `INTERP` support NEAR fill mode. When `FILL(NEAR)` is used, the nearest value to the interpolation point is used to fill the missing value. If there are multiple values with the same distance to the interpolation point, previous row is used. NEAR fill is not supported in stream computation. For example, `SELECT _irowts,INTERP(current) FROM test.meters RANGE('2017-07-22 00:00:00','2017-07-24 12:25:00') EVERY(1h) FILL(NEAR)`. -- Psedo column `_irowts_origin` can be used along with `INTERP` only when using NEAR/NEXT/PREV fill mode. -- `INTERP` RANGE clause support INTERVAL extension, like `RANGE('2023-01-01 00:00:00', 1d)`. The second parameter is the interval length, and the unit cannot use y(year), n(month). The interval length must be an integer, with no quotes, the value can't be 0. The interval length is used to restrict the search range from the time point specified. For example, `SELECT _irowts,INTERP(current) FROM test.meters RANGE('2017-07-22 00:00:00', 1d) FILL(NEAR, 1)`. The query will return the interpolation result of the current column within the range of 1 day from the time point '2017-07-22 00:00:00'. If there is no data within the range, the specified value in FILL will be used. Only FILL PREV/NEXT/NEAR is supported in this case. It's illegal to use `EVERY` clause and NOT specify values in FILL clause in this case. None data-point range clause with INTERVAL extension is not supported currently, like `RANGE('2017-07-22 00:00:00', '2017-07-22 12:00:00', 1h)` is not supported. +- `INTERP` support NEAR fill mode. When `FILL(NEAR)` is used, the nearest value to the interpolation point is used to fill the missing value. If there are multiple values with the same distance to the interpolation point, previous row is used. NEAR fill is not supported in stream computation. For example, `SELECT _irowts,INTERP(current) FROM test.meters RANGE('2017-07-22 00:00:00','2017-07-24 12:25:00') EVERY(1h) FILL(NEAR)`(supported from version 3.3.4.9 onwards). +- Psedo column `_irowts_origin` can be used along with `INTERP` only when using NEAR/NEXT/PREV fill mode, `_irowts_origin` supported from version 3.3.4.9 onwards. +- `INTERP` RANGE clause support INTERVAL extension(supported from version 3.3.4.9 onwards), like `RANGE('2023-01-01 00:00:00', 1d)`. The second parameter is the interval length, and the unit cannot use y(year), n(month). The interval length must be an integer, with no quotes, the value can't be 0. The interval length is used to restrict the search range from the time point specified. For example, `SELECT _irowts,INTERP(current) FROM test.meters RANGE('2017-07-22 00:00:00', 1d) FILL(NEAR, 1)`. The query will return the interpolation result of the current column within the range of 1 day from the time point '2017-07-22 00:00:00'. If there is no data within the range, the specified value in FILL will be used. Only FILL PREV/NEXT/NEAR is supported in this case. It's illegal to use `EVERY` clause and NOT specify values in FILL clause in this case. None data-point range clause with INTERVAL extension is not supported currently, like `RANGE('2017-07-22 00:00:00', '2017-07-22 12:00:00', 1h)` is not supported. ### LAST diff --git a/docs/zh/14-reference/03-taos-sql/06-select.md b/docs/zh/14-reference/03-taos-sql/06-select.md index 28b9346225..43418b3b1d 100644 --- a/docs/zh/14-reference/03-taos-sql/06-select.md +++ b/docs/zh/14-reference/03-taos-sql/06-select.md @@ -63,7 +63,7 @@ window_clause: { interp_clause: RANGE(ts_val [, ts_val]) EVERY(every_val) FILL(fill_mod_and_val) - | RANGE(ts_val, interval_val) FILL(fill_mod_and_val) + | RANGE(ts_val, surrounding_time_val) FILL(fill_mod_and_val) partition_by_clause: PARTITION BY partition_by_expr [, partition_by_expr] ... diff --git a/docs/zh/14-reference/03-taos-sql/10-function.md b/docs/zh/14-reference/03-taos-sql/10-function.md index 244c1e156a..1d96db0969 100644 --- a/docs/zh/14-reference/03-taos-sql/10-function.md +++ b/docs/zh/14-reference/03-taos-sql/10-function.md @@ -1838,9 +1838,9 @@ ignore_null_values: { - INTERP 可以与伪列 _irowts 一起使用,返回插值点所对应的时间戳(3.0.2.0 版本以后支持)。 - INTERP 可以与伪列 _isfilled 一起使用,显示返回结果是否为原始记录或插值算法产生的数据(3.0.3.0 版本以后支持)。 - INTERP 对于带复合主键的表的查询,若存在相同时间戳的数据,则只有对应的复合主键最小的数据参与运算。 -- INTERP 查询支持新的NEAR FILL模式, 即当需要FILL时, 使用距离当前时间点最近的数据进行插值, 当前后时间戳与当前时间断面一样近时, FILL 前一行的值. 此模式在流计算中和窗口查询中不支持。例如: SELECT INTERP(col) FROM tb RANGE('2023-01-01 00:00:00', '2023-01-01 00:10:00') FILL(NEAR)。 -- INTERP 只有在使用FILL PREV/NEXT/NEAR 模式时才可以使用伪列 `_irowts_origin`。 -- INTERP `RANEG`子句支持时间范围的扩展, 如`RANGE('2023-01-01 00:00:00', 10s)`表示在时间点'2023-01-01 00:00:00'查找前后10s的数据进行插值, FILL PREV/NEXT/NEAR分别表示从时间点向前/向后/前后查找数据, 若时间点周围没有数据, 则使用FILL指定的值进行插值, 因此此时FILL子句必须指定值。例如: SELECT INTERP(col) FROM tb RANGE('2023-01-01 00:00:00', 10s) FILL(PREV, 1). 目前仅支持时间点和时间范围的组合, 不支持时间区间和时间范围的组合, 即不支持RANGE('2023-01-01 00:00:00', '2023-02-01 00:00:00', 1h)。所指定的时间范围规则与EVERY类似, 单位不能是年或月, 值不能为0, 不能带引号。使用该扩展时, 不支持除FILL PREV/NEXT/NEAR外的其他FILL模式, 且不能指定EVERY子句 +- INTERP 查询支持NEAR FILL模式, 即当需要FILL时, 使用距离当前时间点最近的数据进行插值, 当前后时间戳与当前时间断面一样近时, FILL 前一行的值. 此模式在流计算中和窗口查询中不支持。例如: SELECT INTERP(col) FROM tb RANGE('2023-01-01 00:00:00', '2023-01-01 00:10:00') FILL(NEAR)。(3.3.4.9版本及以后支持)。 +- INTERP 只有在使用FILL PREV/NEXT/NEAR 模式时才可以使用伪列 `_irowts_origin`。`_irowts_origin`在3.3.4.9版本及以后支持。 +- INTERP `RANEG`子句支持时间范围的扩展(3.3.4.9版本及以后支持), 如`RANGE('2023-01-01 00:00:00', 10s)`表示在时间点'2023-01-01 00:00:00'查找前后10s的数据进行插值, FILL PREV/NEXT/NEAR分别表示从时间点向前/向后/前后查找数据, 若时间点周围没有数据, 则使用FILL指定的值进行插值, 因此此时FILL子句必须指定值。例如: SELECT INTERP(col) FROM tb RANGE('2023-01-01 00:00:00', 10s) FILL(PREV, 1). 目前仅支持时间点和时间范围的组合, 不支持时间区间和时间范围的组合, 即不支持RANGE('2023-01-01 00:00:00', '2023-02-01 00:00:00', 1h)。所指定的时间范围规则与EVERY类似, 单位不能是年或月, 值不能为0, 不能带引号。使用该扩展时, 不支持除FILL PREV/NEXT/NEAR外的其他FILL模式, 且不能指定EVERY子句。 ### LAST diff --git a/tests/system-test/2-query/interp_extension.py b/tests/system-test/2-query/interp_extension.py index b9283f7ca1..0300e10e21 100644 --- a/tests/system-test/2-query/interp_extension.py +++ b/tests/system-test/2-query/interp_extension.py @@ -488,6 +488,10 @@ class TDTestCase: sql = f"select count(*) from test.meters interval(1s) fill(next, 1)" tdSql.error(sql, -2147473920) ## syntax error + sql = f"select _irowts_origin, count(*) from test.meters where ts between '2018-09-17 08:59:59' and '2018-09-17 09:00:06' interval(1s) fill(next)" + tdSql.error(sql, -2147473918) ## invalid column name _irowts_origin + + def test_interp_fill_extension_stream(self): ## near is not supported sql = f"create stream s1 trigger force_window_close into test.s_res_tb as select _irowts, interp(c1), interp(c2)from test.meters partition by tbname every(1s) fill(near);" From 0ad2de4269455f9ff4b56448c883a9b13f6d9a93 Mon Sep 17 00:00:00 2001 From: yihaoDeng Date: Fri, 29 Nov 2024 12:02:24 +0800 Subject: [PATCH 28/31] update doc --- .../en/14-reference/01-components/01-taosd.md | 25 ++++++++----- .../en/14-reference/01-components/02-taosc.md | 4 +++ .../zh/14-reference/01-components/01-taosd.md | 35 +++++++++---------- .../zh/14-reference/01-components/02-taosc.md | 21 +++++------ 4 files changed, 49 insertions(+), 36 deletions(-) diff --git a/docs/en/14-reference/01-components/01-taosd.md b/docs/en/14-reference/01-components/01-taosd.md index 71189d3178..e19efced72 100644 --- a/docs/en/14-reference/01-components/01-taosd.md +++ b/docs/en/14-reference/01-components/01-taosd.md @@ -29,14 +29,23 @@ After modifying configuration file parameters, it is necessary to restart the *t ### Connection Related -| Parameter Name | Parameter Description | -| :--------------------- | :----------------------------------------------------------- | -| firstEp | The endpoint of the first dnode in the cluster to connect to when taosd starts; default value: localhost:6030 | -| secondEp | If firstEp cannot connect, attempt to connect to the second dnode's endpoint in the cluster; default value: none | -| fqdn | The service address that taosd listens on after startup; default value: the first hostname configured on the server | -| serverPort | The port that taosd listens on after startup; default value: 6030 | -| numOfRpcSessions | The maximum number of connections a client can create; range: 100-100000; default value: 30000 | -| timeToGetAvailableConn | The maximum wait time to obtain an available connection; range: 10-50000000; unit: milliseconds; default value: 500000 | +| Parameter Name | support version | Parameter Description | +| :--------------------- |:---------------| :----------------------------------------------------------- | +| firstEp | | The endpoint of the first dnode in the cluster to connect to when taosd starts; default value: localhost:6030 | +| secondEp | | If firstEp cannot connect, attempt to connect to the second dnode's endpoint in the cluster; default value: none | +| fqdn | | The service address that taosd listens on after startup; default value: the first hostname configured on the server | +| compressMsgSize | | Whether to compress RPC messages; -1: no messages are compressed; 0: all messages are compressed; N (N>0): only messages larger than N bytes are compressed; default value: -1 | +| shellActivityTimer | | The duration in seconds for the client to send heartbeats to the mnode; range: 1-120; default value: 3 | +| numOfRpcSessions | | The maximum number of RPC connections supported; range: 100-100000; default value: 30000 | +| numOfRpcThreads | | The number of threads for RPC data transmission; range: 1-50, default value: half of the CPU cores | +| numOfTaskQueueThreads | | The number of threads for the client to process RPC messages, range: 4-16, default value: half of the CPU cores | +| rpcQueueMemoryAllowed | | The maximum amount of memory allowed for RPC messages received on a dnode; unit: bytes; range: 104857600-INT64_MAX; default value: 1/10 of server memory | +| resolveFQDNRetryTime | Removed in 3.x | The number of retries when FQDN resolution fails | +| timeToGetAvailableConn | Removed in 3.3.4.x | The maximum waiting time to obtain an available connection; range: 10-50000000; unit: milliseconds; default value: 500000 | +| maxShellConns | Removed in 3.x | The maximum number of connections allowed to be created | +| maxRetryWaitTime | | The maximum timeout for reconnection; default value: 10s | +| shareConnLimit | Added in 3.3.4.0 | The number of requests that a connection can share; range: 1-512; default value: 10 | +| readTimeout | Added in 3.3.4.0 | The minimum timeout for a single request; range: 64-604800; unit: seconds; default value: 900 | ### Monitoring Related diff --git a/docs/en/14-reference/01-components/02-taosc.md b/docs/en/14-reference/01-components/02-taosc.md index 2c264cbaea..fb645ff20b 100644 --- a/docs/en/14-reference/01-components/02-taosc.md +++ b/docs/en/14-reference/01-components/02-taosc.md @@ -39,6 +39,10 @@ The TDengine client driver provides all the APIs needed for application programm | enableScience | Whether to enable scientific notation for floating-point numbers; 0: disable, 1: enable; default value: 1 | | compressMsgSize | Whether to compress RPC messages; -1: do not compress any messages; 0: compress all messages; N (N>0): compress only messages larger than N bytes; default value: -1 | | queryTableNotExistAsEmpty | Whether to return an empty result set when the queried table does not exist; false: return an error; true: return an empty result set; default value: false | +| numOfRpcThreads | The number of threads for RPC data transmission; range: 1-50, default value: half of the CPU cores | +| numOfTaskQueueThreads | The number of threads for the client to process RPC messages, range: 4-16, default value: half of the CPU cores | +| shareConnLimit | The number of requests that a connection can share; range: 1-512; default value: 10 | +| readTimeout | The minimum timeout for a single request; range: 64-604800; unit: seconds; default value: 900 | ## API diff --git a/docs/zh/14-reference/01-components/01-taosd.md b/docs/zh/14-reference/01-components/01-taosd.md index 64ae69528b..20efb62b51 100644 --- a/docs/zh/14-reference/01-components/01-taosd.md +++ b/docs/zh/14-reference/01-components/01-taosd.md @@ -27,24 +27,23 @@ taosd 命令行参数如下 ### 连接相关 |参数名称|支持版本|参数含义| -|-----------------------|----------|-| -|firstEp | |taosd 启动时,主动连接的集群中首个 dnode 的 end point,默认值 localhost:6030| -|secondEp | |taosd 启动时,如果 firstEp 连接不上,尝试连接集群中第二个 dnode 的 endpoint,无默认值| -|fqdn | |taosd 监听的服务地址,默认为所在服务器上配置的第一个 hostname| -|serverPort | |taosd 监听的端口,默认值 6030| -|compressMsgSize | |是否对 RPC 消息进行压缩;-1:所有消息都不压缩;0:所有消息都压缩;N (N>0):只有大于 N 个字节的消息才压缩;默认值 -1| -|shellActivityTimer | |客户端向 mnode 发送心跳的时长,单位为秒,取值范围 1-120,默认值 3| -|numOfRpcSessions | |RPC 支持的最大连接数,取值范围 100-100000,默认值 30000| -|numOfRpcThreads | |RPC 线程数目,默认值为 CPU 核数的一半| -|numOfTaskQueueThreads | |dnode 处理 RPC 消息的线程数| -|statusInterval | |dnode 与 mnode 之间的心跳间隔| -|rpcQueueMemoryAllowed | |dnode 允许的 rpc 消息占用的内存最大值,单位 bytes,取值范围 104857600-INT64_MAX,默认值 服务器内存的 1/10 | -|resolveFQDNRetryTime | |FQDN 解析失败时的重试次数| -|timeToGetAvailableConn | |获得可用连接的最长等待时间,取值范围 10-50000000,单位为毫秒,默认值 500000| -|maxShellConns | |允许创建的最大链接数| -|maxRetryWaitTime | |重连最大超时时间| -|shareConnLimit |3.3.4.3 后|内部参数,一个链接可以共享的查询数目,取值范围 1-256,默认值 10| -|readTimeout |3.3.4.3 后|内部参数,最小超时时间,取值范围 64-604800,单位为秒,默认值 900| +|-----------------------|-------------------------|------------| +|firstEp | |taosd 启动时,主动连接的集群中首个 dnode 的 end point,默认值 localhost:6030| +|secondEp | |taosd 启动时,如果 firstEp 连接不上,尝试连接集群中第二个 dnode 的 endpoint,无默认值| +|fqdn | |taosd 监听的服务地址,默认为所在服务器上配置的第一个 hostname| +|serverPort | |taosd 监听的端口,默认值 6030| +|compressMsgSize | |是否对 RPC 消息进行压缩;-1:所有消息都不压缩;0:所有消息都压缩;N (N>0):只有大于 N 个字节的消息才压缩;默认值 -1| +|shellActivityTimer | |客户端向 mnode 发送心跳的时长,单位为秒,取值范围 1-120,默认值 3 | +|numOfRpcSessions | |RPC 支持的最大连接数,取值范围 100-100000,默认值 30000| +|numOfRpcThreads | |RPC 收发数据线程数目,默认值为 CPU 核数的一半| +|numOfTaskQueueThreads | |客户端处理 RPC 消息的线程数| +|rpcQueueMemoryAllowed | |dnode允许的已经收到的RPC消息占用的内存最大值,单位 bytes,取值范围 104857600-INT64_MAX,默认值为服务器内存的 1/10 | +|resolveFQDNRetryTime | 3.x 之后取消 |FQDN 解析失败时的重试次数| +|timeToGetAvailableConn | 3.3.4.x之后取消 |获得可用连接的最长等待时间,取值范围 10-50000000,单位为毫秒,默认值 500000| +|maxShellConns | 3.x 后取消 |允许创建的最大链接数| +|maxRetryWaitTime | |重连最大超时时间, 默认值是 10s| +|shareConnLimit |3.3.4.x 后|一个链接可以共享的请求的数目,取值范围 1-512,默认值 10| +|readTimeout |3.3.4.x 后|单个请求最小超时时间,取值范围 64-604800,单位为秒,默认值 900| ### 监控相关 |参数名称|支持版本|参数含义| diff --git a/docs/zh/14-reference/01-components/02-taosc.md b/docs/zh/14-reference/01-components/02-taosc.md index 09653ae3ef..44a69dc01e 100755 --- a/docs/zh/14-reference/01-components/02-taosc.md +++ b/docs/zh/14-reference/01-components/02-taosc.md @@ -10,17 +10,18 @@ TDengine 客户端驱动提供了应用编程所需要的全部 API,并且在 ### 连接相关 |参数名称|支持版本|参数含义| -|----------------------|----------|-| -|firstEp | |启动时,主动连接的集群中首个 dnode 的 endpoint,缺省值:hostname:6030,若无法获取该服务器的 hostname,则赋值为 localhost| -|secondEp | |启动时,如果 firstEp 连接不上,尝试连接集群中第二个 dnode 的 endpoint,没有缺省值| -|compressMsgSize | |是否对 RPC 消息进行压缩;-1:所有消息都不压缩;0:所有消息都压缩;N (N>0):只有大于 N 个字节的消息才压缩;缺省值 -1| -|shellActivityTimer | |客户端向 mnode 发送心跳的时长,单位为秒,取值范围 1-120,默认值 3| -|numOfRpcSessions | |RPC 支持的最大连接数,取值范围 100-100000,缺省值 30000| -|numOfRpcThreads | |RPC 线程数目,默认值为 CPU 核数的一半| -|timeToGetAvailableConn| |获得可用连接的最长等待时间,取值范围 10-50000000,单位为毫秒,缺省值 500000| +|----------------------|----------|-------------| +|firstEp | |启动时,主动连接的集群中首个 dnode 的 endpoint,缺省值:hostname:6030,若无法获取该服务器的 hostname,则赋值为 localhost| +|secondEp | |启动时,如果 firstEp 连接不上,尝试连接集群中第二个 dnode 的 endpoint,没有缺省值| +|compressMsgSize | |是否对 RPC 消息进行压缩;-1:所有消息都不压缩;0:所有消息都压缩;N (N>0):只有大于 N 个字节的消息才压缩;缺省值 -1| +|shellActivityTimer | |客户端向 mnode 发送心跳的时长,单位为秒,取值范围 1-120,默认值 3| +|numOfRpcSessions | |RPC 支持的最大连接数,取值范围 100-100000,缺省值 30000| +|numOfRpcThreads | |RPC 收发数据线程数目,默认值为 CPU 核数的一半| +|numOfTaskQueueThreads | |客户端处理 RPC消息的线程数| +|timeToGetAvailableConn| 3.3.4.*之后取消 |获得可用连接的最长等待时间,取值范围 10-50000000,单位为毫秒,缺省值 500000| |useAdapter | |内部参数,是否使用 taosadapter,影响 CSV 文件导入| -|shareConnLimit |3.3.4.3 后|内部参数,一个链接可以共享的查询数目,取值范围 1-256,默认值 10| -|readTimeout |3.3.4.3 后|内部参数,最小超时时间,取值范围 64-604800,单位为秒,默认值 900| +|shareConnLimit |3.3.4.x 后|内部参数,一个链接可以共享的查询数目,取值范围 1-256,默认值 10| +|readTimeout |3.3.4.x 后|内部参数,最小超时时间,取值范围 64-604800,单位为秒,默认值 900| ### 查询相关 |参数名称|支持版本|参数含义| From 21935f3a5b03fc1710f9c63fe8e04b0a66e756a1 Mon Sep 17 00:00:00 2001 From: chenhaoran Date: Fri, 29 Nov 2024 15:23:14 +0800 Subject: [PATCH 29/31] ci: skip taos-tools and add keeper --- packaging/tools/makepkg.sh | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/packaging/tools/makepkg.sh b/packaging/tools/makepkg.sh index 798d73d0f3..8a05769f40 100755 --- a/packaging/tools/makepkg.sh +++ b/packaging/tools/makepkg.sh @@ -97,6 +97,7 @@ else ${build_dir}/bin/${clientName} \ ${taostools_bin_files} \ ${build_dir}/bin/${clientName}adapter \ + ${build_dir}/bin/${clientName}keeper \ ${build_dir}/bin/udfd \ ${script_dir}/remove.sh \ ${script_dir}/set_core.sh \ @@ -138,10 +139,16 @@ mkdir -p ${install_dir}/cfg && cp ${cfg_dir}/${configFile} ${install_dir}/cfg/${ if [ -f "${compile_dir}/test/cfg/${clientName}adapter.toml" ]; then cp ${compile_dir}/test/cfg/${clientName}adapter.toml ${install_dir}/cfg || : fi +if [ -f "${compile_dir}/test/cfg/${clientName}keeper.toml" ]; then + cp ${compile_dir}/test/cfg/${clientName}keeper.toml ${install_dir}/cfg || : +fi if [ -f "${compile_dir}/test/cfg/${clientName}adapter.service" ]; then cp ${compile_dir}/test/cfg/${clientName}adapter.service ${install_dir}/cfg || : fi +if [ -f "${compile_dir}/test/cfg/${clientName}keeper.service" ]; then + cp ${compile_dir}/test/cfg/${clientName}keeper.service ${install_dir}/cfg || : +fi if [ -f "${cfg_dir}/${serverName}.service" ]; then cp ${cfg_dir}/${serverName}.service ${install_dir}/cfg || : @@ -422,19 +429,19 @@ if [ "$exitcode" != "0" ]; then exit $exitcode fi -if [ -n "${taostools_bin_files}" ] && [ "$verMode" != "cloud" ]; then - wget https://github.com/taosdata/grafanaplugin/releases/latest/download/TDinsight.sh -O ${taostools_install_dir}/bin/TDinsight.sh && echo "TDinsight.sh downloaded!"|| echo "failed to download TDinsight.sh" - if [ "$osType" != "Darwin" ]; then - tar -zcv -f "$(basename ${taostools_pkg_name}).tar.gz" "$(basename ${taostools_install_dir})" --remove-files || : - else - tar -zcv -f "$(basename ${taostools_pkg_name}).tar.gz" "$(basename ${taostools_install_dir})" || : - rm -rf ${taostools_install_dir} ||: - fi - exitcode=$? - if [ "$exitcode" != "0" ]; then - echo "tar ${taostools_pkg_name}.tar.gz error !!!" - exit $exitcode - fi -fi +# if [ -n "${taostools_bin_files}" ] && [ "$verMode" != "cloud" ]; then +# wget https://github.com/taosdata/grafanaplugin/releases/latest/download/TDinsight.sh -O ${taostools_install_dir}/bin/TDinsight.sh && echo "TDinsight.sh downloaded!"|| echo "failed to download TDinsight.sh" +# if [ "$osType" != "Darwin" ]; then +# tar -zcv -f "$(basename ${taostools_pkg_name}).tar.gz" "$(basename ${taostools_install_dir})" --remove-files || : +# else +# tar -zcv -f "$(basename ${taostools_pkg_name}).tar.gz" "$(basename ${taostools_install_dir})" || : +# rm -rf ${taostools_install_dir} ||: +# fi +# exitcode=$? +# if [ "$exitcode" != "0" ]; then +# echo "tar ${taostools_pkg_name}.tar.gz error !!!" +# exit $exitcode +# fi +# fi cd ${curr_dir} From 1a186703049f18e8230e91131d3063d8dcf6d19e Mon Sep 17 00:00:00 2001 From: yihaoDeng Date: Fri, 29 Nov 2024 16:18:36 +0800 Subject: [PATCH 30/31] update doc --- docs/zh/14-reference/01-components/01-taosd.md | 8 ++++---- docs/zh/14-reference/01-components/02-taosc.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/zh/14-reference/01-components/01-taosd.md b/docs/zh/14-reference/01-components/01-taosd.md index 20efb62b51..cb8b6dc4bd 100644 --- a/docs/zh/14-reference/01-components/01-taosd.md +++ b/docs/zh/14-reference/01-components/01-taosd.md @@ -35,15 +35,15 @@ taosd 命令行参数如下 |compressMsgSize | |是否对 RPC 消息进行压缩;-1:所有消息都不压缩;0:所有消息都压缩;N (N>0):只有大于 N 个字节的消息才压缩;默认值 -1| |shellActivityTimer | |客户端向 mnode 发送心跳的时长,单位为秒,取值范围 1-120,默认值 3 | |numOfRpcSessions | |RPC 支持的最大连接数,取值范围 100-100000,默认值 30000| -|numOfRpcThreads | |RPC 收发数据线程数目,默认值为 CPU 核数的一半| -|numOfTaskQueueThreads | |客户端处理 RPC 消息的线程数| +|numOfRpcThreads | |RPC 收发数据线程数目,取值范围1-50,默认值为 CPU 核数的一半| +|numOfTaskQueueThreads | |客户端处理 RPC 消息的线程数取值, 范围4-16,默认值为 CPU 核数的一半| |rpcQueueMemoryAllowed | |dnode允许的已经收到的RPC消息占用的内存最大值,单位 bytes,取值范围 104857600-INT64_MAX,默认值为服务器内存的 1/10 | |resolveFQDNRetryTime | 3.x 之后取消 |FQDN 解析失败时的重试次数| |timeToGetAvailableConn | 3.3.4.x之后取消 |获得可用连接的最长等待时间,取值范围 10-50000000,单位为毫秒,默认值 500000| |maxShellConns | 3.x 后取消 |允许创建的最大链接数| |maxRetryWaitTime | |重连最大超时时间, 默认值是 10s| -|shareConnLimit |3.3.4.x 后|一个链接可以共享的请求的数目,取值范围 1-512,默认值 10| -|readTimeout |3.3.4.x 后|单个请求最小超时时间,取值范围 64-604800,单位为秒,默认值 900| +|shareConnLimit |3.3.4.0 新增 |一个链接可以共享的请求的数目,取值范围 1-512,默认值 10| +|readTimeout |3.3.4.0 新增 |单个请求最小超时时间,取值范围 64-604800,单位为秒,默认值 900| ### 监控相关 |参数名称|支持版本|参数含义| diff --git a/docs/zh/14-reference/01-components/02-taosc.md b/docs/zh/14-reference/01-components/02-taosc.md index 44a69dc01e..5b040ec30b 100755 --- a/docs/zh/14-reference/01-components/02-taosc.md +++ b/docs/zh/14-reference/01-components/02-taosc.md @@ -16,12 +16,12 @@ TDengine 客户端驱动提供了应用编程所需要的全部 API,并且在 |compressMsgSize | |是否对 RPC 消息进行压缩;-1:所有消息都不压缩;0:所有消息都压缩;N (N>0):只有大于 N 个字节的消息才压缩;缺省值 -1| |shellActivityTimer | |客户端向 mnode 发送心跳的时长,单位为秒,取值范围 1-120,默认值 3| |numOfRpcSessions | |RPC 支持的最大连接数,取值范围 100-100000,缺省值 30000| -|numOfRpcThreads | |RPC 收发数据线程数目,默认值为 CPU 核数的一半| -|numOfTaskQueueThreads | |客户端处理 RPC消息的线程数| +|numOfRpcThreads | |RPC 收发数据线程数目,取值范围1-50,默认值为 CPU 核数的一半| +|numOfTaskQueueThreads | |客户端处理 RPC消息的线程数, 范围4-16,默认值为 CPU 核数的一半| |timeToGetAvailableConn| 3.3.4.*之后取消 |获得可用连接的最长等待时间,取值范围 10-50000000,单位为毫秒,缺省值 500000| |useAdapter | |内部参数,是否使用 taosadapter,影响 CSV 文件导入| -|shareConnLimit |3.3.4.x 后|内部参数,一个链接可以共享的查询数目,取值范围 1-256,默认值 10| -|readTimeout |3.3.4.x 后|内部参数,最小超时时间,取值范围 64-604800,单位为秒,默认值 900| +|shareConnLimit |3.3.4.0 新增|内部参数,一个链接可以共享的查询数目,取值范围 1-256,默认值 10| +|readTimeout |3.3.4.0 新增|内部参数,最小超时时间,取值范围 64-604800,单位为秒,默认值 900| ### 查询相关 |参数名称|支持版本|参数含义| From bb44c6778773f3b1400fb43d3675df4df2ad1738 Mon Sep 17 00:00:00 2001 From: chenhaoran Date: Fri, 29 Nov 2024 17:49:58 +0800 Subject: [PATCH 31/31] ci: add long time running cases file --- tests/parallel_test/longtimeruning_cases.task | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/parallel_test/longtimeruning_cases.task diff --git a/tests/parallel_test/longtimeruning_cases.task b/tests/parallel_test/longtimeruning_cases.task new file mode 100644 index 0000000000..1ec234c266 --- /dev/null +++ b/tests/parallel_test/longtimeruning_cases.task @@ -0,0 +1,19 @@ +#Column Define +#caseID,rerunTimes,Run with Sanitizer,casePath,caseCommand +#NA,NA,y or n,script,./test.sh -f tsim/user/basic.sim + + +# system test +# +#,,y,system-test,./pytest.sh python3 ./test.py -f 8-stream/stream_multi_agg.py +#,,n,system-test,python3 ./test.py -f 8-stream/stream_basic.py + +# army-test +#,,y,army,./pytest.sh python3 ./test.py -f multi-level/mlevel_basic.py -N 3 -L 3 -D 2 + +#tsim test +#,,y,script,./test.sh -f tsim/query/timeline.sim + +#docs-examples test +#,,n,docs-examples-test,bash c.sh +