From 6bd2a329c682ce66c11c6d4f3797166aa2a4d4d8 Mon Sep 17 00:00:00 2001 From: haoranchen Date: Mon, 31 Jul 2023 10:19:23 +0800 Subject: [PATCH 001/120] Update cases.task --- tests/parallel_test/cases.task | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/parallel_test/cases.task b/tests/parallel_test/cases.task index 1ec5102d9b..24f968ee65 100644 --- a/tests/parallel_test/cases.task +++ b/tests/parallel_test/cases.task @@ -105,7 +105,7 @@ ,,y,system-test,./pytest.sh python3 ./test.py -f 7-tmq/tmqConsFromTsdb-mutilVg-mutilCtb-funcNFilter.py ,,y,system-test,./pytest.sh python3 ./test.py -f 7-tmq/tmqConsFromTsdb-mutilVg-mutilCtb.py ,,y,system-test,./pytest.sh python3 ./test.py -f 7-tmq/tmqConsFromTsdb1-1ctb-funcNFilter.py -#,,y,system-test,./pytest.sh python3 ./test.py -f 7-tmq/tmqConsFromTsdb1-mutilVg-mutilCtb-funcNFilter.py +,,y,system-test,./pytest.sh python3 ./test.py -f 7-tmq/tmqConsFromTsdb1-mutilVg-mutilCtb-funcNFilter.py ,,y,system-test,./pytest.sh python3 ./test.py -f 7-tmq/tmqConsFromTsdb1-mutilVg-mutilCtb.py ,,y,system-test,./pytest.sh python3 ./test.py -f 7-tmq/tmqAutoCreateTbl.py ,,y,system-test,./pytest.sh python3 ./test.py -f 7-tmq/tmqDnodeRestart.py From 89ff78d99c615c6bbd7129f3f5802b661e17274d Mon Sep 17 00:00:00 2001 From: Ganlin Zhao Date: Wed, 16 Aug 2023 17:32:08 +0800 Subject: [PATCH 002/120] fix: fix non-root users cannot create log files even if they have write permission of the directory --- source/os/src/osDir.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/os/src/osDir.c b/source/os/src/osDir.c index 3d63da7ba3..7e4058d508 100644 --- a/source/os/src/osDir.c +++ b/source/os/src/osDir.c @@ -206,7 +206,11 @@ int32_t taosMulModeMkDir(const char *dirname, int mode) { #endif if (taosDirExist(temp)) { - return chmod(temp, mode); + if (taosCheckAccessFile(temp, TD_FILE_ACCESS_WRITE_OK)) { + return code; + } else { + return chmod(temp, mode); + } } if (strncmp(temp, TD_DIRSEP, 1) == 0) { From 4dcdb549c98a29f8aba4e1f743521972f6cf4190 Mon Sep 17 00:00:00 2001 From: wangjiaming0909 <604227650@qq.com> Date: Thu, 17 Aug 2023 16:41:42 +0800 Subject: [PATCH 003/120] fix: get systable cols error --- source/dnode/mnode/impl/src/mndStb.c | 226 ++++++++++-------- .../0-others/information_schema.py | 8 +- 2 files changed, 132 insertions(+), 102 deletions(-) diff --git a/source/dnode/mnode/impl/src/mndStb.c b/source/dnode/mnode/impl/src/mndStb.c index 8a6bd079a4..3a60dc46e7 100644 --- a/source/dnode/mnode/impl/src/mndStb.c +++ b/source/dnode/mnode/impl/src/mndStb.c @@ -3159,137 +3159,161 @@ static int32_t buildDbColsInfoBlock(const SSDataBlock *p, const SSysTableMeta *p return numOfRows; } +#define BUILD_COL_FOR_INFO_DB 1 +#define BUILD_COL_FOR_PERF_DB 1 << 1 +#define BUILD_COL_FOR_USER_DB 1 << 2 +#define BUILD_COL_FOR_ALL_DB (BUILD_COL_FOR_INFO_DB | BUILD_COL_FOR_PERF_DB | BUILD_COL_FOR_USER_DB) -static int32_t buildSysDbColsInfo(SSDataBlock *p, char *db, char *tb) { +static int32_t buildSysDbColsInfo(SSDataBlock *p, int8_t buildWhichDBs, char *tb) { size_t size = 0; const SSysTableMeta *pSysDbTableMeta = NULL; - if (db[0] && strncmp(db, TSDB_INFORMATION_SCHEMA_DB, TSDB_DB_FNAME_LEN) != 0 && - strncmp(db, TSDB_PERFORMANCE_SCHEMA_DB, TSDB_DB_FNAME_LEN) != 0) { - return p->info.rows; + if (buildWhichDBs & BUILD_COL_FOR_INFO_DB) { + getInfosDbMeta(&pSysDbTableMeta, &size); + p->info.rows = buildDbColsInfoBlock(p, pSysDbTableMeta, size, TSDB_INFORMATION_SCHEMA_DB, tb); } - getInfosDbMeta(&pSysDbTableMeta, &size); - p->info.rows = buildDbColsInfoBlock(p, pSysDbTableMeta, size, TSDB_INFORMATION_SCHEMA_DB, tb); - - getPerfDbMeta(&pSysDbTableMeta, &size); - p->info.rows = buildDbColsInfoBlock(p, pSysDbTableMeta, size, TSDB_PERFORMANCE_SCHEMA_DB, tb); + if (buildWhichDBs & BUILD_COL_FOR_PERF_DB) { + getPerfDbMeta(&pSysDbTableMeta, &size); + p->info.rows = buildDbColsInfoBlock(p, pSysDbTableMeta, size, TSDB_PERFORMANCE_SCHEMA_DB, tb); + } return p->info.rows; } +static int8_t determineBuildColForWhichDBs(const char* db) { + int8_t buildWhichDBs; + if (!db[0]) + buildWhichDBs = BUILD_COL_FOR_ALL_DB; + else { + char *p = strchr(db, '.'); + if (p && strcmp(p + 1, TSDB_INFORMATION_SCHEMA_DB) == 0) { + buildWhichDBs = BUILD_COL_FOR_INFO_DB; + } else if (p && strcmp(p + 1, TSDB_PERFORMANCE_SCHEMA_DB) == 0) { + buildWhichDBs = BUILD_COL_FOR_PERF_DB; + } else { + buildWhichDBs = BUILD_COL_FOR_USER_DB; + } + } + return buildWhichDBs; +} + static int32_t mndRetrieveStbCol(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBlock, int32_t rows) { + uint8_t buildWhichDBs; SMnode *pMnode = pReq->info.node; SSdb *pSdb = pMnode->pSdb; SStbObj *pStb = NULL; - - int32_t numOfRows = 0; + + buildWhichDBs = determineBuildColForWhichDBs(pShow->db); + if (!pShow->sysDbRsp) { - numOfRows = buildSysDbColsInfo(pBlock, pShow->db, pShow->filterTb); + numOfRows = buildSysDbColsInfo(pBlock, buildWhichDBs, pShow->filterTb); mDebug("mndRetrieveStbCol get system table cols, rows:%d, db:%s", numOfRows, pShow->db); pShow->sysDbRsp = true; } - SDbObj *pDb = NULL; - if (strlen(pShow->db) > 0) { - pDb = mndAcquireDb(pMnode, pShow->db); - if (pDb == NULL) return terrno; - } - - char typeName[TSDB_TABLE_FNAME_LEN + VARSTR_HEADER_SIZE] = {0}; - STR_TO_VARSTR(typeName, "SUPER_TABLE"); - bool fetch = pShow->restore ? false : true; - pShow->restore = false; - while (numOfRows < rows) { - if (fetch) { - pShow->pIter = sdbFetch(pSdb, SDB_STB, pShow->pIter, (void **)&pStb); - if (pShow->pIter == NULL) break; - } else { - fetch = true; - void *pKey = taosHashGetKey(pShow->pIter, NULL); - pStb = sdbAcquire(pSdb, SDB_STB, pKey); - if (!pStb) continue; + if (buildWhichDBs & BUILD_COL_FOR_USER_DB) { + SDbObj *pDb = NULL; + if (strlen(pShow->db) > 0) { + pDb = mndAcquireDb(pMnode, pShow->db); + if (pDb == NULL && TSDB_CODE_MND_DB_NOT_EXIST != terrno && pBlock->info.rows == 0) return terrno; } - if (pDb != NULL && pStb->dbUid != pDb->uid) { - sdbRelease(pSdb, pStb); - continue; - } - - SName name = {0}; - char stbName[TSDB_TABLE_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; - mndExtractTbNameFromStbFullName(pStb->name, &stbName[VARSTR_HEADER_SIZE], TSDB_TABLE_NAME_LEN); - if (pShow->filterTb[0] && strncmp(pShow->filterTb, &stbName[VARSTR_HEADER_SIZE], TSDB_TABLE_NAME_LEN) != 0) { - sdbRelease(pSdb, pStb); - continue; - } - - if ((numOfRows + pStb->numOfColumns) > rows) { - pShow->restore = true; - if (numOfRows == 0) { - mError("mndRetrieveStbCol failed to get stable cols since buf:%d less than result:%d, stable name:%s, db:%s", - rows, pStb->numOfColumns, pStb->name, pStb->db); + char typeName[TSDB_TABLE_FNAME_LEN + VARSTR_HEADER_SIZE] = {0}; + STR_TO_VARSTR(typeName, "SUPER_TABLE"); + bool fetch = pShow->restore ? false : true; + pShow->restore = false; + while (numOfRows < rows) { + if (fetch) { + pShow->pIter = sdbFetch(pSdb, SDB_STB, pShow->pIter, (void **)&pStb); + if (pShow->pIter == NULL) break; + } else { + fetch = true; + void *pKey = taosHashGetKey(pShow->pIter, NULL); + pStb = sdbAcquire(pSdb, SDB_STB, pKey); + if (!pStb) continue; } - sdbRelease(pSdb, pStb); - break; - } - varDataSetLen(stbName, strlen(&stbName[VARSTR_HEADER_SIZE])); - - mDebug("mndRetrieveStbCol get stable cols, stable name:%s, db:%s", pStb->name, pStb->db); - - char db[TSDB_DB_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; - tNameFromString(&name, pStb->db, T_NAME_ACCT | T_NAME_DB); - tNameGetDbName(&name, varDataVal(db)); - varDataSetLen(db, strlen(varDataVal(db))); - - for (int i = 0; i < pStb->numOfColumns; i++) { - int32_t cols = 0; - SColumnInfoData *pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); - colDataSetVal(pColInfo, numOfRows, (const char *)stbName, false); - - pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); - colDataSetVal(pColInfo, numOfRows, (const char *)db, false); - - pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); - colDataSetVal(pColInfo, numOfRows, typeName, false); - - // col name - char colName[TSDB_COL_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; - STR_TO_VARSTR(colName, pStb->pColumns[i].name); - pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); - colDataSetVal(pColInfo, numOfRows, colName, false); - - // col type - int8_t colType = pStb->pColumns[i].type; - pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); - char colTypeStr[VARSTR_HEADER_SIZE + 32]; - int colTypeLen = sprintf(varDataVal(colTypeStr), "%s", tDataTypes[colType].name); - if (colType == TSDB_DATA_TYPE_VARCHAR) { - colTypeLen += sprintf(varDataVal(colTypeStr) + colTypeLen, "(%d)", - (int32_t)(pStb->pColumns[i].bytes - VARSTR_HEADER_SIZE)); - } else if (colType == TSDB_DATA_TYPE_NCHAR) { - colTypeLen += sprintf(varDataVal(colTypeStr) + colTypeLen, "(%d)", - (int32_t)((pStb->pColumns[i].bytes - VARSTR_HEADER_SIZE) / TSDB_NCHAR_SIZE)); + if (pDb != NULL && pStb->dbUid != pDb->uid) { + sdbRelease(pSdb, pStb); + continue; } - varDataSetLen(colTypeStr, colTypeLen); - colDataSetVal(pColInfo, numOfRows, (char *)colTypeStr, false); - pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); - colDataSetVal(pColInfo, numOfRows, (const char *)&pStb->pColumns[i].bytes, false); - while (cols < pShow->numOfColumns) { + SName name = {0}; + char stbName[TSDB_TABLE_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; + mndExtractTbNameFromStbFullName(pStb->name, &stbName[VARSTR_HEADER_SIZE], TSDB_TABLE_NAME_LEN); + if (pShow->filterTb[0] && strncmp(pShow->filterTb, &stbName[VARSTR_HEADER_SIZE], TSDB_TABLE_NAME_LEN) != 0) { + sdbRelease(pSdb, pStb); + continue; + } + + if ((numOfRows + pStb->numOfColumns) > rows) { + pShow->restore = true; + if (numOfRows == 0) { + mError("mndRetrieveStbCol failed to get stable cols since buf:%d less than result:%d, stable name:%s, db:%s", + rows, pStb->numOfColumns, pStb->name, pStb->db); + } + sdbRelease(pSdb, pStb); + break; + } + + varDataSetLen(stbName, strlen(&stbName[VARSTR_HEADER_SIZE])); + + mDebug("mndRetrieveStbCol get stable cols, stable name:%s, db:%s", pStb->name, pStb->db); + + char db[TSDB_DB_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; + tNameFromString(&name, pStb->db, T_NAME_ACCT | T_NAME_DB); + tNameGetDbName(&name, varDataVal(db)); + varDataSetLen(db, strlen(varDataVal(db))); + + for (int i = 0; i < pStb->numOfColumns; i++) { + int32_t cols = 0; + SColumnInfoData *pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + colDataSetVal(pColInfo, numOfRows, (const char *)stbName, false); + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); - colDataSetNULL(pColInfo, numOfRows); + colDataSetVal(pColInfo, numOfRows, (const char *)db, false); + + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + colDataSetVal(pColInfo, numOfRows, typeName, false); + + // col name + char colName[TSDB_COL_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; + STR_TO_VARSTR(colName, pStb->pColumns[i].name); + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + colDataSetVal(pColInfo, numOfRows, colName, false); + + // col type + int8_t colType = pStb->pColumns[i].type; + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + char colTypeStr[VARSTR_HEADER_SIZE + 32]; + int colTypeLen = sprintf(varDataVal(colTypeStr), "%s", tDataTypes[colType].name); + if (colType == TSDB_DATA_TYPE_VARCHAR) { + colTypeLen += sprintf(varDataVal(colTypeStr) + colTypeLen, "(%d)", + (int32_t)(pStb->pColumns[i].bytes - VARSTR_HEADER_SIZE)); + } else if (colType == TSDB_DATA_TYPE_NCHAR) { + colTypeLen += sprintf(varDataVal(colTypeStr) + colTypeLen, "(%d)", + (int32_t)((pStb->pColumns[i].bytes - VARSTR_HEADER_SIZE) / TSDB_NCHAR_SIZE)); + } + varDataSetLen(colTypeStr, colTypeLen); + colDataSetVal(pColInfo, numOfRows, (char *)colTypeStr, false); + + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + colDataSetVal(pColInfo, numOfRows, (const char *)&pStb->pColumns[i].bytes, false); + while (cols < pShow->numOfColumns) { + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + colDataSetNULL(pColInfo, numOfRows); + } + numOfRows++; } - numOfRows++; + + sdbRelease(pSdb, pStb); } - sdbRelease(pSdb, pStb); - } - - if (pDb != NULL) { - mndReleaseDb(pMnode, pDb); + if (pDb != NULL) { + mndReleaseDb(pMnode, pDb); + } } pShow->numOfRows += numOfRows; diff --git a/tests/system-test/0-others/information_schema.py b/tests/system-test/0-others/information_schema.py index 762361f051..fde4e52f3d 100644 --- a/tests/system-test/0-others/information_schema.py +++ b/tests/system-test/0-others/information_schema.py @@ -215,7 +215,13 @@ class TDTestCase: for t in range (2): tdSql.query(f'select * from information_schema.ins_columns where db_name="db2" and table_type=="NORMAL_TABLE"') tdSql.checkEqual(20470,len(tdSql.queryResult)) - + + tdSql.query("select * from information_schema.ins_columns where db_name ='information_schema'") + tdSql.checkEqual(195, len(tdSql.queryResult)) + + tdSql.query("select * from information_schema.ins_columns where db_name ='performance_schema'") + tdSql.checkEqual(54, len(tdSql.queryResult)) + def ins_dnodes_check(self): tdSql.execute('drop database if exists db2') tdSql.execute('create database if not exists db2 vgroups 1 replica 1') From 42b12b1e68ca3dd60f0f90873a44255788a2cd86 Mon Sep 17 00:00:00 2001 From: dapan1121 Date: Fri, 18 Aug 2023 17:40:34 +0800 Subject: [PATCH 004/120] fix: document issues --- docs/en/12-taos-sql/06-select.md | 7 +++---- docs/zh/12-taos-sql/06-select.md | 7 +++---- docs/zh/12-taos-sql/12-distinguished.md | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) mode change 100644 => 100755 docs/en/12-taos-sql/06-select.md mode change 100644 => 100755 docs/zh/12-taos-sql/06-select.md mode change 100644 => 100755 docs/zh/12-taos-sql/12-distinguished.md diff --git a/docs/en/12-taos-sql/06-select.md b/docs/en/12-taos-sql/06-select.md old mode 100644 new mode 100755 index b28d5acb18..661b4f5dea --- a/docs/en/12-taos-sql/06-select.md +++ b/docs/en/12-taos-sql/06-select.md @@ -167,7 +167,7 @@ The following SQL statement returns the number of subtables within the meters su SELECT COUNT(*) FROM (SELECT DISTINCT TBNAME FROM meters); ``` -In the preceding two statements, only tags can be used as filtering conditions in the WHERE clause. For example: +In the preceding two statements, only tags can be used as filtering conditions in the WHERE clause. **\_QSTART and \_QEND** @@ -209,8 +209,7 @@ You can perform INNER JOIN statements based on the primary key. The following co 3. For supertables, the ON condition must be equivalent to the primary key. In addition, the tag columns of the tables on which the INNER JOIN is performed must have a one-to-one relationship. You cannot specify an OR condition. 4. The tables that are included in a JOIN clause must be of the same type (supertable, standard table, or subtable). 5. You can include subqueries before and after the JOIN keyword. -6. You cannot include more than ten tables in a JOIN clause. -7. You cannot include a FILL clause and a JOIN clause in the same statement. +6. You cannot include a FILL clause and a JOIN clause in the same statement. ## GROUP BY @@ -355,7 +354,7 @@ SELECT AVG(CASE WHEN voltage < 200 or voltage > 250 THEN 220 ELSE voltage END) F ## JOIN -TDengine supports the `INTER JOIN` based on the timestamp primary key, that is, the `JOIN` condition must contain the timestamp primary key. As long as the requirement of timestamp-based primary key is met, `INTER JOIN` can be made between normal tables, sub-tables, super tables and sub-queries at will, and there is no limit on the number of tables. +TDengine supports the `INTER JOIN` based on the timestamp primary key, that is, the `JOIN` condition must contain the timestamp primary key. As long as the requirement of timestamp-based primary key is met, `INTER JOIN` can be made between normal tables, sub-tables, super tables and sub-queries at will, and there is no limit on the number of tables, primary key and other conditions must be combined with `AND` operator. For standard tables: diff --git a/docs/zh/12-taos-sql/06-select.md b/docs/zh/12-taos-sql/06-select.md old mode 100644 new mode 100755 index 9560c3c4df..80966cf386 --- a/docs/zh/12-taos-sql/06-select.md +++ b/docs/zh/12-taos-sql/06-select.md @@ -167,7 +167,7 @@ SELECT table_name, tag_name, tag_type, tag_value FROM information_schema.ins_tag SELECT COUNT(*) FROM (SELECT DISTINCT TBNAME FROM meters); ``` -以上两个查询均只支持在 WHERE 条件子句中添加针对标签(TAGS)的过滤条件。例如: +以上两个查询均只支持在 WHERE 条件子句中添加针对标签(TAGS)的过滤条件。 **\_QSTART/\_QEND** @@ -209,8 +209,7 @@ TDengine 支持基于时间戳主键的 INNER JOIN,规则如下: 3. 对于超级表,ON 条件在时间戳主键的等值条件之外,还要求有可以一一对应的标签列等值条件,不支持 OR 条件。 4. 参与 JOIN 计算的表只能是同一种类型,即只能都是超级表,或都是子表,或都是普通表。 5. JOIN 两侧均支持子查询。 -6. 参与 JOIN 的表个数上限为 10 个。 -7. 不支持与 FILL 子句混合使用。 +6. 不支持与 FILL 子句混合使用。 ## GROUP BY @@ -354,7 +353,7 @@ SELECT AVG(CASE WHEN voltage < 200 or voltage > 250 THEN 220 ELSE voltage END) F ## JOIN 子句 -TDengine 支持基于时间戳主键的内连接,即 JOIN 条件必须包含时间戳主键。只要满足基于时间戳主键这个要求,普通表、子表、超级表和子查询之间可以随意的进行内连接,且对表个数没有限制。 +TDengine 支持基于时间戳主键的内连接,即 JOIN 条件必须包含时间戳主键。只要满足基于时间戳主键这个要求,普通表、子表、超级表和子查询之间可以随意的进行内连接,且对表个数没有限制,其它连接条件与主键间必须是 AND 操作。 普通表与普通表之间的 JOIN 操作: diff --git a/docs/zh/12-taos-sql/12-distinguished.md b/docs/zh/12-taos-sql/12-distinguished.md old mode 100644 new mode 100755 index f750124049..62888cc5f7 --- a/docs/zh/12-taos-sql/12-distinguished.md +++ b/docs/zh/12-taos-sql/12-distinguished.md @@ -31,7 +31,7 @@ select max(current) from meters partition by location interval(10m) ## 窗口切分查询 -TDengine 支持按时间窗口切分方式进行聚合结果查询,比如温度传感器每秒采集一次数据,但需查询每隔 10 分钟的温度平均值。这种场景下可以使用窗口子句来获得需要的查询结果。窗口子句用于针对查询的数据集合按照窗口切分成为查询子集并进行聚合,窗口包含时间窗口(time window)、状态窗口(status window)、会话窗口(session window)、条件窗口(event window)四种窗口。其中时间窗口又可划分为滑动时间窗口和翻转时间窗口。 +TDengine 支持按时间窗口切分方式进行聚合结果查询,比如温度传感器每秒采集一次数据,但需查询每隔 10 分钟的温度平均值。这种场景下可以使用窗口子句来获得需要的查询结果。窗口子句用于针对查询的数据集合按照窗口切分成为查询子集并进行聚合,窗口包含时间窗口(time window)、状态窗口(status window)、会话窗口(session window)、事件窗口(event window)四种窗口。其中时间窗口又可划分为滑动时间窗口和翻转时间窗口。 窗口子句语法如下: From 72cce0825b88875e9526cfe74a7c50a3c1ff84b4 Mon Sep 17 00:00:00 2001 From: danielclow <106956386+danielclow@users.noreply.github.com> Date: Sat, 19 Aug 2023 23:17:20 +0800 Subject: [PATCH 005/120] docs: fix link in grafana doc (#22489) "...releases/tag/latest" should be "...releases/latest", which will automatically redirect to "...releases/tag/" --- docs/en/20-third-party/01-grafana.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/20-third-party/01-grafana.mdx b/docs/en/20-third-party/01-grafana.mdx index 64acefe6b8..0ccdfe7254 100644 --- a/docs/en/20-third-party/01-grafana.mdx +++ b/docs/en/20-third-party/01-grafana.mdx @@ -74,7 +74,7 @@ grafana-cli plugins install tdengine-datasource sudo -u grafana grafana-cli plugins install tdengine-datasource ``` -You can also download zip files from [GitHub](https://github.com/taosdata/grafanaplugin/releases/tag/latest) or [Grafana](https://grafana.com/grafana/plugins/tdengine-datasource/?tab=installation) and install manually. The commands are as follows: +You can also download zip files from [GitHub](https://github.com/taosdata/grafanaplugin/releases/latest) or [Grafana](https://grafana.com/grafana/plugins/tdengine-datasource/?tab=installation) and install manually. The commands are as follows: ```bash GF_VERSION=3.3.1 From c3e881430857b4a2515d060684caf02d82917eb2 Mon Sep 17 00:00:00 2001 From: kailixu Date: Sun, 20 Aug 2023 11:31:45 +0800 Subject: [PATCH 006/120] docs: taos api description --- docs/zh/08-connector/10-cpp.mdx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/zh/08-connector/10-cpp.mdx b/docs/zh/08-connector/10-cpp.mdx index 12bbffa4f9..cec1e685ab 100644 --- a/docs/zh/08-connector/10-cpp.mdx +++ b/docs/zh/08-connector/10-cpp.mdx @@ -256,6 +256,12 @@ int taos_print_row(char *str, TAOS_ROW row, TAOS_FIELD *fields, int num_fields) ::: +- `TAOS *taos_connect_auth(const char *host, const char *user, const char *auth, const char *db, uint16_t port)` + + 功能同 taos_connect。除 pass 参数替换为 auth 外,其他参数同 taos_connect + + - auth: 密码取 32 位小写 md5. e.g. 默认密码 taosdata 取 md5 为 dcc5bed04851fec854c035b2e40263b6 + - `char *taos_get_server_info(TAOS *taos)` 获取服务端版本信息。 @@ -272,6 +278,14 @@ int taos_print_row(char *str, TAOS_ROW row, TAOS_FIELD *fields, int num_fields) - 如果,len 小于 存储db需要的空间(包含最后的'\0'),返回错误,database里赋值截断的数据,以'\0'结尾。 - 如果,len 大于等于 存储db需要的空间(包含最后的'\0'),返回正常0,database里赋值以'\0‘结尾的db名。 +- `int taos_set_notify_cb(TAOS *taos, __taos_notify_fn_t fp, void *param, int type)` + + 设置事件通知回调函数。 + + - fp 事件通知回调函数指针。函数声明:typedef void (*__taos_notify_fn_t)(void *param, void *ext, int type);其中, param 为用户自定义参数,ext 为扩展参数(依赖事件通知类型,针对TAOS_NOTIFY_PASSVER 返回用户密码版本),type 为事件通知类型。 + - param 用户自定义参数。 + - type 事件通知类型。取值:1)TAOS_NOTIFY_PASSVER: 用户密码改变。 + - `void taos_close(TAOS *taos)` 关闭连接,其中`taos`是 `taos_connect()` 返回的句柄。 @@ -447,6 +461,14 @@ TDengine 的异步 API 均采用非阻塞调用模式。应用程序可以用多 执行准备好的语句。目前,一条语句只能执行一次。 +- `int taos_stmt_affected_rows(TAOS_STMT *stmt)` + + 获取被所执行的 SQL 语句影响的行数。 + +- `int taos_stmt_affected_rows_once(TAOS_STMT *stmt)` + + 获取被所执行的 SQL 语句影响的行数。 + - `TAOS_RES* taos_stmt_use_result(TAOS_STMT *stmt)` 获取语句的结果集。结果集的使用方式与非参数化调用时一致,使用完成后,应对此结果集调用 `taos_free_result()` 以释放资源。 From 7020ac7a0f82ae61fd027e6274b3520d6db0e4c7 Mon Sep 17 00:00:00 2001 From: Shuduo Sang Date: Sun, 20 Aug 2023 14:46:24 +0800 Subject: [PATCH 007/120] docs: refine c connector docs (#22491) * docs: fix taos_init() return type * docs: refine c interface doc --- docs/en/14-reference/03-connector/03-cpp.mdx | 19 ++++++------ docs/examples/c/multi_bind_example.c | 4 +-- docs/examples/c/stmt_example.c | 6 ++-- docs/zh/08-connector/10-cpp.mdx | 32 ++++++-------------- 4 files changed, 24 insertions(+), 37 deletions(-) diff --git a/docs/en/14-reference/03-connector/03-cpp.mdx b/docs/en/14-reference/03-connector/03-cpp.mdx index 3e1a0f9545..d6e74e711b 100644 --- a/docs/en/14-reference/03-connector/03-cpp.mdx +++ b/docs/en/14-reference/03-connector/03-cpp.mdx @@ -307,21 +307,20 @@ The specific functions related to the interface are as follows (see also the [pr Parse a SQL command, and bind the parsed result and parameter information to `stmt`. If the parameter length is greater than 0, use this parameter as the length of the SQL command. If it is equal to 0, the length of the SQL command will be determined automatically. -- `int taos_stmt_bind_param(TAOS_STMT *stmt, TAOS_BIND *bind)` +- `int taos_stmt_bind_param(TAOS_STMT *stmt, TAOS_MULTI_BIND *bind)` Not as efficient as `taos_stmt_bind_param_batch()`, but can support non-INSERT type SQL statements. To bind parameters, bind points to an array (representing the row of data to be bound), making sure that the number and order of the elements in this array are the same as the parameters in the SQL statement. taos_bind is used similarly to MYSQL_BIND in MySQL, as defined below. ```c - typedef struct TAOS_BIND { + typedef struct TAOS_MULTI_BIND { int buffer_type; - void * buffer; - uintptr_t buffer_length; // not in use - uintptr_t * length; - int * is_null; - int is_unsigned; // not in use - int * error; // not in use - } TAOS_BIND; + void *buffer; + uintptr_t buffer_length; + uint32_t *length; + char *is_null; + int num; + } TAOS_MULTI_BIND; ``` - `int taos_stmt_set_tbname(TAOS_STMT* stmt, const char* name)` @@ -329,7 +328,7 @@ The specific functions related to the interface are as follows (see also the [pr (Available in 2.1.1.0 and later versions, only supported for replacing parameter values in INSERT statements) When the table name in the SQL command uses `? ` placeholder, you can use this function to bind a specific table name. -- `int taos_stmt_set_tbname_tags(TAOS_STMT* stmt, const char* name, TAOS_BIND* tags)` +- `int taos_stmt_set_tbname_tags(TAOS_STMT* stmt, const char* name, TAOS_MULTI_BIND* tags)` (Available in 2.1.2.0 and later versions, only supported for replacing parameter values in INSERT statements) When the table name and TAGS in the SQL command both use `? `, you can use this function to bind the specific table name and the specific TAGS value. The most typical usage scenario is an INSERT statement that uses the automatic table building function (the current version does not support specifying specific TAGS columns.) The number of columns in the TAGS parameter needs to be the same as the number of TAGS requested in the SQL command. diff --git a/docs/examples/c/multi_bind_example.c b/docs/examples/c/multi_bind_example.c index 02e6568e9e..3d0bd3ccef 100644 --- a/docs/examples/c/multi_bind_example.c +++ b/docs/examples/c/multi_bind_example.c @@ -51,7 +51,7 @@ void insertData(TAOS *taos) { int code = taos_stmt_prepare(stmt, sql, 0); checkErrorCode(stmt, code, "failed to execute taos_stmt_prepare"); // bind table name and tags - TAOS_BIND tags[2]; + TAOS_MULTI_BIND tags[2]; char *location = "California.SanFrancisco"; int groupId = 2; tags[0].buffer_type = TSDB_DATA_TYPE_BINARY; @@ -144,4 +144,4 @@ int main() { } // output: -// successfully inserted 2 rows \ No newline at end of file +// successfully inserted 2 rows diff --git a/docs/examples/c/stmt_example.c b/docs/examples/c/stmt_example.c index 28dae5f9d5..290a6bee66 100644 --- a/docs/examples/c/stmt_example.c +++ b/docs/examples/c/stmt_example.c @@ -58,7 +58,7 @@ void insertData(TAOS *taos) { int code = taos_stmt_prepare(stmt, sql, 0); checkErrorCode(stmt, code, "failed to execute taos_stmt_prepare"); // bind table name and tags - TAOS_BIND tags[2]; + TAOS_MULTI_BIND tags[2]; char* location = "California.SanFrancisco"; int groupId = 2; tags[0].buffer_type = TSDB_DATA_TYPE_BINARY; @@ -82,7 +82,7 @@ void insertData(TAOS *taos) { {1648432611749, 12.6, 218, 0.33}, }; - TAOS_BIND values[4]; + TAOS_MULTI_BIND values[4]; values[0].buffer_type = TSDB_DATA_TYPE_TIMESTAMP; values[0].buffer_length = sizeof(int64_t); values[0].length = &values[0].buffer_length; @@ -138,4 +138,4 @@ int main() { // output: -// successfully inserted 2 rows \ No newline at end of file +// successfully inserted 2 rows diff --git a/docs/zh/08-connector/10-cpp.mdx b/docs/zh/08-connector/10-cpp.mdx index 12bbffa4f9..d23996463a 100644 --- a/docs/zh/08-connector/10-cpp.mdx +++ b/docs/zh/08-connector/10-cpp.mdx @@ -396,21 +396,20 @@ TDengine 的异步 API 均采用非阻塞调用模式。应用程序可以用多 解析一条 SQL 语句,将解析结果和参数信息绑定到 stmt 上,如果参数 length 大于 0,将使用此参数作为 SQL 语句的长度,如等于 0,将自动判断 SQL 语句的长度。 -- `int taos_stmt_bind_param(TAOS_STMT *stmt, TAOS_BIND *bind)` +- `int taos_stmt_bind_param(TAOS_STMT *stmt, TAOS_MULTI_BIND *bind)` 不如 `taos_stmt_bind_param_batch()` 效率高,但可以支持非 INSERT 类型的 SQL 语句。 - 进行参数绑定,bind 指向一个数组(代表所要绑定的一行数据),需保证此数组中的元素数量和顺序与 SQL 语句中的参数完全一致。TAOS_BIND 的使用方法与 MySQL 中的 MYSQL_BIND 类似,具体定义如下: + 进行参数绑定,bind 指向一个数组(代表所要绑定的一行数据),需保证此数组中的元素数量和顺序与 SQL 语句中的参数完全一致。TAOS_MULTI_BIND 的使用方法与 MySQL 中的 MYSQL_BIND 类似,具体定义如下: ```c - typedef struct TAOS_BIND { + typedef struct TAOS_MULTI_BIND { int buffer_type; - void * buffer; - uintptr_t buffer_length; // not in use - uintptr_t * length; - int * is_null; - int is_unsigned; // not in use - int * error; // not in use - } TAOS_BIND; + void *buffer; + uintptr_t buffer_length; + uint32_t *length; + char *is_null; + int num; // the number of columns + } TAOS_MULTI_BIND; ``` - `int taos_stmt_set_tbname(TAOS_STMT* stmt, const char* name)` @@ -418,7 +417,7 @@ TDengine 的异步 API 均采用非阻塞调用模式。应用程序可以用多 (2.1.1.0 版本新增,仅支持用于替换 INSERT 语句中的参数值) 当 SQL 语句中的表名使用了 `?` 占位时,可以使用此函数绑定一个具体的表名。 -- `int taos_stmt_set_tbname_tags(TAOS_STMT* stmt, const char* name, TAOS_BIND* tags)` +- `int taos_stmt_set_tbname_tags(TAOS_STMT* stmt, const char* name, TAOS_MULTI_BIND* tags)` (2.1.2.0 版本新增,仅支持用于替换 INSERT 语句中的参数值) 当 SQL 语句中的表名和 TAGS 都使用了 `?` 占位时,可以使用此函数绑定具体的表名和具体的 TAGS 取值。最典型的使用场景是使用了自动建表功能的 INSERT 语句(目前版本不支持指定具体的 TAGS 列)。TAGS 参数中的列数量需要与 SQL 语句中要求的 TAGS 数量完全一致。 @@ -428,17 +427,6 @@ TDengine 的异步 API 均采用非阻塞调用模式。应用程序可以用多 (2.1.1.0 版本新增,仅支持用于替换 INSERT 语句中的参数值) 以多列的方式传递待绑定的数据,需要保证这里传递的数据列的顺序、列的数量与 SQL 语句中的 VALUES 参数完全一致。TAOS_MULTI_BIND 的具体定义如下: - ```c - typedef struct TAOS_MULTI_BIND { - int buffer_type; - void * buffer; - uintptr_t buffer_length; - uintptr_t * length; - char * is_null; - int num; // the number of columns - } TAOS_MULTI_BIND; - ``` - - `int taos_stmt_add_batch(TAOS_STMT *stmt)` 将当前绑定的参数加入批处理中,调用此函数后,可以再次调用 `taos_stmt_bind_param()` 或 `taos_stmt_bind_param_batch()` 绑定新的参数。需要注意,此函数仅支持 INSERT/IMPORT 语句,如果是 SELECT 等其他 SQL 语句,将返回错误。 From 7856dce03f6aea8b9a620fc0436ad0b4721a776b Mon Sep 17 00:00:00 2001 From: kailixu Date: Mon, 21 Aug 2023 00:05:49 +0800 Subject: [PATCH 008/120] docs: taos api description --- docs/en/14-reference/03-connector/03-cpp.mdx | 22 ++++++++++++++++++++ docs/zh/08-connector/10-cpp.mdx | 16 +++++++------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/docs/en/14-reference/03-connector/03-cpp.mdx b/docs/en/14-reference/03-connector/03-cpp.mdx index 3e1a0f9545..97048178e2 100644 --- a/docs/en/14-reference/03-connector/03-cpp.mdx +++ b/docs/en/14-reference/03-connector/03-cpp.mdx @@ -168,6 +168,12 @@ The base API is used to do things like create database connections and provide a ::: +- `TAOS *taos_connect_auth(const char *host, const char *user, const char *auth, const char *db, uint16_t port)` + + The function is the same as taos_connect. Except that the pass parameter is replaced by auth, other parameters are the same as taos_connect. + + - auth: the 32-bit lowercase md5 of the raw password + - `char *taos_get_server_info(TAOS *taos)` Get server-side version information. @@ -184,6 +190,14 @@ The base API is used to do things like create database connections and provide a - If len is less than the space required to store the db (including the last '\0'), an error is returned. The truncated data assigned in the database ends with '\0'. - If len is greater than or equal to the space required to store the db (including the last '\0'), return normal 0, and assign the db name ending with '\0' in the database. +- `int taos_set_notify_cb(TAOS *taos, __taos_notify_fn_t fp, void *param, int type)` + + Set the event callback function. + + - fp: event callback function pointer. Declaration:typedef void (*__taos_notify_fn_t)(void *param, void *ext, int type);Param is a user-defined parameter, ext is an extended parameter (depending on the event type, and returns the user password version for TAOS_NOTIFY_PASSVER), and type is the event type + - param: user-defined parameter + - type: event type. Value range: 1) TAOS_NOTIFY_PASSVER: User password changed + - `void taos_close(TAOS *taos)` Closes the connection, where `taos` is the handle returned by `taos_connect()`. @@ -358,6 +372,14 @@ The specific functions related to the interface are as follows (see also the [pr Execute the prepared statement. Currently, a statement can only be executed once. +- `int taos_stmt_affected_rows(TAOS_STMT *stmt)` + + Gets the number of rows affected by executing bind statements multiple times. + +- `int taos_stmt_affected_rows_once(TAOS_STMT *stmt)` + + Gets the number of rows affected by executing a bind statement once. + - `TAOS_RES* taos_stmt_use_result(TAOS_STMT *stmt)` Gets the result set of a statement. Use the result set in the same way as in the non-parametric call. When finished, `taos_free_result()` should be called on this result set to free resources. diff --git a/docs/zh/08-connector/10-cpp.mdx b/docs/zh/08-connector/10-cpp.mdx index cec1e685ab..89cf54733a 100644 --- a/docs/zh/08-connector/10-cpp.mdx +++ b/docs/zh/08-connector/10-cpp.mdx @@ -258,9 +258,9 @@ int taos_print_row(char *str, TAOS_ROW row, TAOS_FIELD *fields, int num_fields) - `TAOS *taos_connect_auth(const char *host, const char *user, const char *auth, const char *db, uint16_t port)` - 功能同 taos_connect。除 pass 参数替换为 auth 外,其他参数同 taos_connect + 功能同 taos_connect。除 pass 参数替换为 auth 外,其他参数同 taos_connect。 - - auth: 密码取 32 位小写 md5. e.g. 默认密码 taosdata 取 md5 为 dcc5bed04851fec854c035b2e40263b6 + - auth: 原始密码取 32 位小写 md5 - `char *taos_get_server_info(TAOS *taos)` @@ -280,11 +280,11 @@ int taos_print_row(char *str, TAOS_ROW row, TAOS_FIELD *fields, int num_fields) - `int taos_set_notify_cb(TAOS *taos, __taos_notify_fn_t fp, void *param, int type)` - 设置事件通知回调函数。 + 设置事件回调函数。 - - fp 事件通知回调函数指针。函数声明:typedef void (*__taos_notify_fn_t)(void *param, void *ext, int type);其中, param 为用户自定义参数,ext 为扩展参数(依赖事件通知类型,针对TAOS_NOTIFY_PASSVER 返回用户密码版本),type 为事件通知类型。 - - param 用户自定义参数。 - - type 事件通知类型。取值:1)TAOS_NOTIFY_PASSVER: 用户密码改变。 + - fp 事件回调函数指针。函数声明:typedef void (*__taos_notify_fn_t)(void *param, void *ext, int type);其中, param 为用户自定义参数,ext 为扩展参数(依赖事件类型,针对 TAOS_NOTIFY_PASSVER 返回用户密码版本),type 为事件类型 + - param 用户自定义参数 + - type 事件类型。取值范围:1)TAOS_NOTIFY_PASSVER: 用户密码改变 - `void taos_close(TAOS *taos)` @@ -463,11 +463,11 @@ TDengine 的异步 API 均采用非阻塞调用模式。应用程序可以用多 - `int taos_stmt_affected_rows(TAOS_STMT *stmt)` - 获取被所执行的 SQL 语句影响的行数。 + 获取执行多次绑定语句影响的行数。 - `int taos_stmt_affected_rows_once(TAOS_STMT *stmt)` - 获取被所执行的 SQL 语句影响的行数。 + 获取执行一次绑定语句影响的行数。 - `TAOS_RES* taos_stmt_use_result(TAOS_STMT *stmt)` From aec02a55be7a5c73c6c06f59a055cf85e17257c2 Mon Sep 17 00:00:00 2001 From: wangjiaming0909 <604227650@qq.com> Date: Fri, 18 Aug 2023 17:16:02 +0800 Subject: [PATCH 009/120] fix: fill last group data between groups --- source/libs/executor/src/filloperator.c | 13 ++ tests/parallel_test/cases.task | 1 + tests/system-test/2-query/fill_with_group.py | 144 +++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 tests/system-test/2-query/fill_with_group.py diff --git a/source/libs/executor/src/filloperator.c b/source/libs/executor/src/filloperator.c index 80c88a803e..be4cb8d2dc 100644 --- a/source/libs/executor/src/filloperator.c +++ b/source/libs/executor/src/filloperator.c @@ -64,6 +64,7 @@ typedef struct SFillOperatorInfo { static void revisedFillStartKey(SFillOperatorInfo* pInfo, SSDataBlock* pBlock, int32_t order); static void destroyFillOperatorInfo(void* param); static void doApplyScalarCalculation(SOperatorInfo* pOperator, SSDataBlock* pBlock, int32_t order, int32_t scanFlag); +static void fillResetPrevForNewGroup(SFillInfo* pFillInfo); static void doHandleRemainBlockForNewGroupImpl(SOperatorInfo* pOperator, SFillOperatorInfo* pInfo, SResultInfo* pResultInfo, int32_t order) { @@ -84,6 +85,9 @@ static void doHandleRemainBlockForNewGroupImpl(SOperatorInfo* pOperator, SFillOp taosFillSetStartInfo(pInfo->pFillInfo, pInfo->pRes->info.rows, ts); taosFillSetInputDataBlock(pInfo->pFillInfo, pInfo->pRes); + if (pInfo->pFillInfo->type == TSDB_FILL_PREV || pInfo->pFillInfo->type == TSDB_FILL_LINEAR) { + fillResetPrevForNewGroup(pInfo->pFillInfo); + } int32_t numOfResultRows = pResultInfo->capacity - pResBlock->info.rows; taosFillResultDataBlock(pInfo->pFillInfo, pResBlock, numOfResultRows); @@ -122,6 +126,15 @@ void doApplyScalarCalculation(SOperatorInfo* pOperator, SSDataBlock* pBlock, int pInfo->pRes->info.id.groupId = pBlock->info.id.groupId; } +static void fillResetPrevForNewGroup(SFillInfo* pFillInfo) { + for (int32_t colIdx = 0; colIdx < pFillInfo->numOfCols; ++colIdx) { + if (!pFillInfo->pFillCol[colIdx].notFillCol) { + SGroupKeys* key = taosArrayGet(pFillInfo->prev.pRowVal, colIdx); + key->isNull = true; + } + } +} + // todo refactor: decide the start key according to the query time range. static void revisedFillStartKey(SFillOperatorInfo* pInfo, SSDataBlock* pBlock, int32_t order) { if (order == TSDB_ORDER_ASC) { diff --git a/tests/parallel_test/cases.task b/tests/parallel_test/cases.task index fc08ff1b32..c3d87315f5 100644 --- a/tests/parallel_test/cases.task +++ b/tests/parallel_test/cases.task @@ -760,6 +760,7 @@ ,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/blockSMA.py -Q 4 ,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/projectionDesc.py -Q 4 ,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/odbc.py +,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/fill_with_group.py ,,y,system-test,./pytest.sh python3 ./test.py -f 99-TDcase/TD-21561.py -Q 4 ,,y,system-test,./pytest.sh python3 ./test.py -f 99-TDcase/TD-20582.py ,,n,system-test,python3 ./test.py -f 5-taos-tools/taosbenchmark/insertMix.py -N 3 diff --git a/tests/system-test/2-query/fill_with_group.py b/tests/system-test/2-query/fill_with_group.py new file mode 100644 index 0000000000..c1ea9877a2 --- /dev/null +++ b/tests/system-test/2-query/fill_with_group.py @@ -0,0 +1,144 @@ +import taos +import sys +import time +import socket +import os +import threading +import math + +from util.log import * +from util.sql import * +from util.cases import * +from util.dnodes import * +from util.common import * +# from tmqCommon import * + +class TDTestCase: + 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,i+ctbStartIdx,i+ctbStartIdx,i+ctbStartIdx,i+ctbStartIdx) + tsql.execute(sqlString) + + tdLog.debug("complete to create %d child tables by %s.%s" %(ctbNum, dbName, stbName)) + return + + 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%d values "%(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, j%10, j%10, 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, 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%d values " %(ctbPrefix,i) + else: + sql = "insert into " + if sql != pre_insert: + tsql.execute(sql) + tdLog.debug("insert data ............ [OK]") + return + + def prepareTestEnv(self): + tdLog.printNoPrefix("======== prepare test env include database, stable, ctables, and insert data: ") + paraDict = {'dbName': 'test', + 'dropFlag': 1, + 'vgroups': 2, + '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': 100, + 'rowsPerTbl': 10000, + 'batchNum': 3000, + 'startTs': 1537146000000, + 'tsStep': 600000} + + paraDict['vgroups'] = self.vgroups + paraDict['ctbNum'] = self.ctbNum + paraDict['rowsPerTbl'] = self.rowsPerTbl + + 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"]) + return + + def test_partition_by_with_interval_fill_prev_new_group_fill_error(self): + ## every table has 1500 rows after fill, 10 tables, total 15000 rows. + ## there is no data from 9-17 08:00:00 ~ 9-17 09:00:00, so first 60 rows of every group will be NULL, cause no prev value. + sql = "select _wstart, count(*),tbname from meters where ts > '2018-09-17 08:00:00.000' and ts < '2018-09-18 09:00:00.000' partition by tbname interval(1m) fill(PREV) order by tbname, _wstart" + tdSql.query(sql) + for i in range(0,10): + for j in range(0,60): + tdSql.checkData(i*1500+j, 1, None) + + sql = "select _wstart, count(*),tbname from meters where ts > '2018-09-17 08:00:00.000' and ts < '2018-09-18 09:00:00.000' partition by tbname interval(1m) fill(LINEAR) order by tbname, _wstart" + tdSql.query(sql) + for i in range(0,10): + for j in range(0,60): + tdSql.checkData(i*1500+j, 1, None) + + def run(self): + self.prepareTestEnv() + self.test_partition_by_with_interval_fill_prev_new_group_fill_error() + + def stop(self): + tdSql.close() + tdLog.success(f"{__file__} successfully executed") + +event = threading.Event() + +tdCases.addLinux(__file__, TDTestCase()) +tdCases.addWindows(__file__, TDTestCase()) From 124b1e7f9ed577b16513ffffd18185b49248d643 Mon Sep 17 00:00:00 2001 From: Shuduo Sang Date: Mon, 21 Aug 2023 14:31:38 +0800 Subject: [PATCH 010/120] docs: add current_user() in function (#22496) --- docs/en/12-taos-sql/06-select.md | 8 +++++++- docs/en/12-taos-sql/10-function.md | 8 ++++++++ docs/zh/12-taos-sql/06-select.md | 8 +++++++- docs/zh/12-taos-sql/10-function.md | 8 ++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/en/12-taos-sql/06-select.md b/docs/en/12-taos-sql/06-select.md index 661b4f5dea..5902dcb1df 100755 --- a/docs/en/12-taos-sql/06-select.md +++ b/docs/en/12-taos-sql/06-select.md @@ -7,7 +7,7 @@ description: This document describes how to query data in TDengine. ## Syntax ```sql -SELECT {DATABASE() | CLIENT_VERSION() | SERVER_VERSION() | SERVER_STATUS() | NOW() | TODAY() | TIMEZONE()} +SELECT {DATABASE() | CLIENT_VERSION() | SERVER_VERSION() | SERVER_STATUS() | NOW() | TODAY() | TIMEZONE() | CURRENT_USER() | USER() } SELECT [DISTINCT] select_list from_clause @@ -300,6 +300,12 @@ SELECT TODAY(); SELECT TIMEZONE(); ``` +### Obtain Current User + +```sql +SELECT CURRENT_USER(); +``` + ## Regular Expression ### Syntax diff --git a/docs/en/12-taos-sql/10-function.md b/docs/en/12-taos-sql/10-function.md index ad6d5d77fb..3e25d2d4ec 100644 --- a/docs/en/12-taos-sql/10-function.md +++ b/docs/en/12-taos-sql/10-function.md @@ -1275,6 +1275,14 @@ SELECT SERVER_STATUS(); **Description**: The server status. +### CURRENT_USER + +```sql +SELECT CURRENT_USER(); +``` + +**Description**: get current user. + ## Geometry Functions diff --git a/docs/zh/12-taos-sql/06-select.md b/docs/zh/12-taos-sql/06-select.md index 80966cf386..8e122da894 100755 --- a/docs/zh/12-taos-sql/06-select.md +++ b/docs/zh/12-taos-sql/06-select.md @@ -7,7 +7,7 @@ description: 查询数据的详细语法 ## 查询语法 ```sql -SELECT {DATABASE() | CLIENT_VERSION() | SERVER_VERSION() | SERVER_STATUS() | NOW() | TODAY() | TIMEZONE()} +SELECT {DATABASE() | CLIENT_VERSION() | SERVER_VERSION() | SERVER_STATUS() | NOW() | TODAY() | TIMEZONE() | CURRENT_USER() | USER() } SELECT [DISTINCT] select_list from_clause @@ -300,6 +300,12 @@ SELECT TODAY(); SELECT TIMEZONE(); ``` +### 获取当前用户 + +```sql +SELECT CURRENT_USER(); +``` + ## 正则表达式过滤 ### 语法 diff --git a/docs/zh/12-taos-sql/10-function.md b/docs/zh/12-taos-sql/10-function.md index 773ea67989..f0ad0c62a5 100644 --- a/docs/zh/12-taos-sql/10-function.md +++ b/docs/zh/12-taos-sql/10-function.md @@ -1266,6 +1266,14 @@ SELECT SERVER_STATUS(); **说明**:检测服务端是否所有 dnode 都在线,如果是则返回成功,否则返回无法建立连接的错误。 +### CURRENT_USER + +```sql +SELECT CURRENT_USER(); +``` + +**说明**:获取当前用户。 + ## Geometry 函数 From 80ef91daddebe824330e38f83946f4797acb7ee0 Mon Sep 17 00:00:00 2001 From: "chao.feng" Date: Mon, 21 Aug 2023 14:50:13 +0800 Subject: [PATCH 011/120] udpate alter stable test case to add ts-3841 scenario by charles --- tests/system-test/1-insert/alter_stable.py | 30 +++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/system-test/1-insert/alter_stable.py b/tests/system-test/1-insert/alter_stable.py index 3e82b573b1..52f185a868 100644 --- a/tests/system-test/1-insert/alter_stable.py +++ b/tests/system-test/1-insert/alter_stable.py @@ -155,9 +155,37 @@ class TDTestCase: tdSql.error(f'alter stable {self.ntbname} modify column {key} {v}') for i in range(self.tbnum): tdSql.error(f'alter stable {self.stbname}_{i} modify column {key} {v}') - def run(self): + + def alter_stable_column_varchar_39001(self): + """Check alter stable column varchar 39001 from 39000(TS-3841) + """ + stbname = "st1" + column_dict = { + 'ts' : 'timestamp', + 'col1': 'varchar(39000)', + 'col2': 'tinyint', + 'col3': 'timestamp', + 'col4': 'tinyint', + 'col5': 'timestamp', + 'col6': 'varchar(18)', + 'col7': 'varchar(17)' + } + tag_dict = { + 'id': 'int' + } + tdSql.execute(self.setsql.set_create_stable_sql(stbname, column_dict, tag_dict)) + res = tdSql.getResult(f'desc {stbname}') + tdLog.info(res) + assert(res[1][2] == 39000) + tdSql.execute(f'alter stable {stbname} modify column col1 varchar(39001)') + res = tdSql.getResult(f'desc {stbname}') + tdLog.info(res) + assert(res[1][2] == 39001) + + def run(self): self.alter_stable_check() + self.alter_stable_column_varchar_39001() def stop(self): tdSql.close() tdLog.success("%s successfully executed" % __file__) From 1d17270bb48438c6318155f712117b9575d24659 Mon Sep 17 00:00:00 2001 From: Ping Xiao Date: Mon, 21 Aug 2023 16:44:45 +0800 Subject: [PATCH 012/120] update installation instruction --- packaging/tools/install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/tools/install.sh b/packaging/tools/install.sh index f9a11f5540..252a634a19 100755 --- a/packaging/tools/install.sh +++ b/packaging/tools/install.sh @@ -935,7 +935,7 @@ function updateProduct() { fi echo echo -e "\033[44;32;1m${productName2} is updated successfully!${NC}" - echo -e "\033[44;32;1mTo manage ${productName2} instance, view documentation and explorer features, you need to install ${clientName2}Explorer ${NC}" + echo -e "\033[44;32;1mTo manage ${productName2} instance, view documentation or explorer features, please install ${clientName2}Explorer ${NC}" else install_bin install_config @@ -1028,7 +1028,7 @@ function installProduct() { fi echo -e "\033[44;32;1m${productName2} is installed successfully!${NC}" - echo -e "\033[44;32;1mTo manage ${productName2} instance, view documentation and explorer features, you need to install ${clientName2}Explorer ${NC}" + echo -e "\033[44;32;1mTo manage ${productName2} instance, view documentation or explorer features, please install ${clientName2}Explorer ${NC}" echo else # Only install client install_bin From f4ef83b8527d67b5487ce19c6850f534faded743 Mon Sep 17 00:00:00 2001 From: Ping Xiao Date: Mon, 21 Aug 2023 17:21:18 +0800 Subject: [PATCH 013/120] release 3.1.0.2 --- cmake/cmake.version | 2 +- docs/en/28-releases/01-tdengine.md | 4 ++++ docs/zh/28-releases/01-tdengine.md | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cmake/cmake.version b/cmake/cmake.version index d5ada35dc3..ee02ef8c07 100644 --- a/cmake/cmake.version +++ b/cmake/cmake.version @@ -2,7 +2,7 @@ IF (DEFINED VERNUMBER) SET(TD_VER_NUMBER ${VERNUMBER}) ELSE () - SET(TD_VER_NUMBER "3.1.0.2.alpha") + SET(TD_VER_NUMBER "3.1.0.3.alpha") ENDIF () IF (DEFINED VERCOMPATIBLE) diff --git a/docs/en/28-releases/01-tdengine.md b/docs/en/28-releases/01-tdengine.md index 31484dc1c5..ff6a36440f 100644 --- a/docs/en/28-releases/01-tdengine.md +++ b/docs/en/28-releases/01-tdengine.md @@ -10,6 +10,10 @@ For TDengine 2.x installation packages by version, please visit [here](https://t import Release from "/components/ReleaseV3"; +## 3.1.0.2 + + + ## 3.1.0.0 :::note IMPORTANT diff --git a/docs/zh/28-releases/01-tdengine.md b/docs/zh/28-releases/01-tdengine.md index afdf2a76d3..d316b3ab68 100644 --- a/docs/zh/28-releases/01-tdengine.md +++ b/docs/zh/28-releases/01-tdengine.md @@ -10,6 +10,10 @@ TDengine 2.x 各版本安装包请访问[这里](https://www.taosdata.com/all-do import Release from "/components/ReleaseV3"; +## 3.1.0.2 + + + ## 3.1.0.0 From ac6d031e62fcda4eff8a305e5dddf69b30ac1fc7 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Mon, 21 Aug 2023 17:56:35 +0800 Subject: [PATCH 014/120] fix:do not send unsubscribe msg to taosd if subscribe failed --- source/client/src/clientTmq.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/client/src/clientTmq.c b/source/client/src/clientTmq.c index b4168046f4..e861bd4b92 100644 --- a/source/client/src/clientTmq.c +++ b/source/client/src/clientTmq.c @@ -985,6 +985,10 @@ int32_t tmq_subscription(tmq_t* tmq, tmq_list_t** topics) { int32_t tmq_unsubscribe(tmq_t* tmq) { if(tmq == NULL) return TSDB_CODE_INVALID_PARA; + if (tmq->status != TMQ_CONSUMER_STATUS__READY) { + tscInfo("consumer:0x%" PRIx64 " not in ready state, unsubscribe it directly", tmq->consumerId); + return 0; + } if (tmq->autoCommit) { int32_t rsp = tmq_commit_sync(tmq, NULL); if (rsp != 0) { From c8a88fb70b1efff9b2f6bf4ee6b2cd00a1759b3e Mon Sep 17 00:00:00 2001 From: Ping Xiao Date: Mon, 21 Aug 2023 18:48:50 +0800 Subject: [PATCH 015/120] remove community package testing from ubuntu 16 --- packaging/MPtestJenkinsfile | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/packaging/MPtestJenkinsfile b/packaging/MPtestJenkinsfile index 0570bae191..1b2e555b88 100644 --- a/packaging/MPtestJenkinsfile +++ b/packaging/MPtestJenkinsfile @@ -114,7 +114,7 @@ pipeline { sync_source("${BRANCH_NAME}") sh ''' if [ "${verMode}" = "all" ];then - verMode="community enterprise" + verMode="enterprise" fi verModeList=${verMode} for verModeSin in ${verModeList} @@ -123,18 +123,6 @@ pipeline { bash testpackage.sh -m ${verModeSin} -f server -l false -c x64 -v ${version} -o ${baseVersion} -s ${sourcePath} -t tar python3 checkPackageRuning.py done - ''' - - sh ''' - cd ${TDENGINE_ROOT_DIR}/packaging - bash testpackage.sh -m community -f server -l true -c x64 -v ${version} -o ${baseVersion} -s ${sourcePath} -t tar - python3 checkPackageRuning.py - ''' - - sh ''' - cd ${TDENGINE_ROOT_DIR}/packaging - bash testpackage.sh -m community -f server -l false -c x64 -v ${version} -o ${baseVersion} -s ${sourcePath} -t deb - python3 checkPackageRuning.py ''' } } From 2054640d9ee13550c688a9c98d65a923f78f5007 Mon Sep 17 00:00:00 2001 From: wade zhang <95411902+gccgdb1234@users.noreply.github.com> Date: Tue, 22 Aug 2023 13:55:35 +0800 Subject: [PATCH 016/120] Update 24-show.md --- docs/zh/12-taos-sql/24-show.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/zh/12-taos-sql/24-show.md b/docs/zh/12-taos-sql/24-show.md index 6e102e2356..b7ca9493d4 100644 --- a/docs/zh/12-taos-sql/24-show.md +++ b/docs/zh/12-taos-sql/24-show.md @@ -22,6 +22,14 @@ SHOW CLUSTER; 显示当前集群的信息 +## SHOW CLUSTER ALIVE + +```sql +SHOW CLUSTER ALIVE; +``` + +查询当前集群的状态是否可用,返回值: 0:不可用 1:完全可用 2:部分可用(集群中部分节点下线,但其它节点仍可以正常使用) + ## SHOW CONNECTIONS ```sql From b5bd8f7c2353047ade7dc001457e40a644106781 Mon Sep 17 00:00:00 2001 From: wade zhang <95411902+gccgdb1234@users.noreply.github.com> Date: Tue, 22 Aug 2023 13:57:14 +0800 Subject: [PATCH 017/120] Update 24-show.md --- docs/en/12-taos-sql/24-show.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/en/12-taos-sql/24-show.md b/docs/en/12-taos-sql/24-show.md index b663fbd435..7a58343f24 100644 --- a/docs/en/12-taos-sql/24-show.md +++ b/docs/en/12-taos-sql/24-show.md @@ -22,6 +22,14 @@ SHOW CLUSTER; Shows information about the current cluster. +## SHOW CLUSTER ALIVE + +```sql +SHOW CLUSTER ALIVE; +``` + +It is used to check whether the cluster is available or not. Return value: 0 means unavailable, 1 means available, 2 means partially available (some dnodes are offline, the other dnodes are available) + ## SHOW CONNECTIONS ```sql From 56626f2e764a091c0839d420939982cfb74994d4 Mon Sep 17 00:00:00 2001 From: huolibo Date: Thu, 27 Jul 2023 16:52:08 +0800 Subject: [PATCH 018/120] feat(driver): add committed assignment API for jdbc --- .../jni/com_taosdata_jdbc_tmq_TMQConnector.h | 15 +++ source/client/src/clientTmqConnector.c | 108 +++++++++++++++++- 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/source/client/jni/com_taosdata_jdbc_tmq_TMQConnector.h b/source/client/jni/com_taosdata_jdbc_tmq_TMQConnector.h index 422bcd57ac..ebc4eacdf9 100644 --- a/source/client/jni/com_taosdata_jdbc_tmq_TMQConnector.h +++ b/source/client/jni/com_taosdata_jdbc_tmq_TMQConnector.h @@ -92,6 +92,10 @@ JNIEXPORT jint JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqSubscriptionIm */ JNIEXPORT jint JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqCommitSync(JNIEnv *, jobject, jlong, jlong); +JNIEXPORT jint JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqCommitAllSync(JNIEnv *, jobject, jlong); + +JNIEXPORT jint JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqCommitOffsetSyncImp(JNIEnv *, jobject, jlong, jstring, + jint, jlong); /* * Class: com_taosdata_jdbc_tmq_TMQConnector * Method: tmqCommitAsync @@ -102,6 +106,12 @@ JNIEXPORT void JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqCommitAsync(JN JNIEXPORT void JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_consumerCommitAsync(JNIEnv *, jobject, jlong, jlong, jobject); +JNIEXPORT void JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_consumerCommitAllAsync(JNIEnv *, jobject, jlong, + jobject); + +JNIEXPORT void JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_consumerCommitOffsetAsync(JNIEnv *, jobject, jlong, + jstring, jint, jlong, jobject); + /* * Class: com_taosdata_jdbc_tmq_TMQConnector * Method: tmqUnsubscribeImp @@ -179,6 +189,11 @@ JNIEXPORT jint JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqSeekImp(JNIEnv JNIEXPORT jint JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqGetTopicAssignmentImp(JNIEnv *, jobject, jlong, jstring, jobject); +JNIEXPORT jlong JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqCommittedImp(JNIEnv *, jobject, jlong, jstring, + jint); + +JNIEXPORT jlong JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqPositionImp(JNIEnv *, jobject, jlong, jstring, jint); + #ifdef __cplusplus } #endif diff --git a/source/client/src/clientTmqConnector.c b/source/client/src/clientTmqConnector.c index 6ec82aa6ef..487a86a589 100644 --- a/source/client/src/clientTmqConnector.c +++ b/source/client/src/clientTmqConnector.c @@ -291,6 +291,39 @@ JNIEXPORT jint JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqCommitSync(JNI TAOS_RES *res = (TAOS_RES *)jres; return tmq_commit_sync(tmq, res); } +JNIEXPORT jint JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqCommitAllSync(JNIEnv *env, jobject jobj, jlong jtmq) { + tmq_t *tmq = (tmq_t *)jtmq; + if (tmq == NULL) { + jniError("jobj:%p, tmq is closed", jobj); + return TMQ_CONSUMER_NULL; + } + + return tmq_commit_sync(tmq, NULL); +} + +JNIEXPORT jint JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqCommitOffsetSyncImp(JNIEnv *env, jobject jobj, + jlong jtmq, jstring jtopic, + jint vgId, jlong offset) { + tmq_t *tmq = (tmq_t *)jtmq; + if (tmq == NULL) { + jniDebug("jobj:%p, tmq is closed", jobj); + return TMQ_CONSUMER_NULL; + } + + if (jtopic == NULL) { + jniDebug("jobj:%p, topic is null", jobj); + return TMQ_TOPIC_NULL; + } + const char *topicName = (*env)->GetStringUTFChars(env, jtopic, NULL); + + int code = tmq_commit_offset_sync(tmq, topicName, vgId, offset); + if (code != TSDB_CODE_SUCCESS) { + jniError("jobj:%p, tmq commit offset error, code:%d, msg:%s", jobj, code, tmq_err2str(code)); + } + + (*env)->ReleaseStringUTFChars(env, jtopic, topicName); + return code; +} // deprecated JNIEXPORT void JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqCommitAsync(JNIEnv *env, jobject jobj, jlong jtmq, @@ -319,6 +352,27 @@ JNIEXPORT void JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_consumerCommitAsy tmq_commit_async(tmq, res, consumer_callback, offset); } +JNIEXPORT void JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_consumerCommitAllAsync(JNIEnv *env, jobject jobj, + jlong jtmq, jobject offset) { + tmqGlobalMethod(env); + tmq_t *tmq = (tmq_t *)jtmq; + + offset = (*env)->NewGlobalRef(env, offset); + tmq_commit_async(tmq, NULL, consumer_callback, offset); +} + +JNIEXPORT void JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_consumerCommitOffsetAsync(JNIEnv *env, jobject jobj, + jlong jtmq, jstring jtopic, + jint vgId, jlong offset, + jobject callback) { + tmqGlobalMethod(env); + tmq_t *tmq = (tmq_t *)jtmq; + const char *topicName = (*env)->GetStringUTFChars(env, jtopic, NULL); + + callback = (*env)->NewGlobalRef(env, callback); + tmq_commit_offset_async(tmq, topicName, vgId, offset, consumer_callback, callback); +} + JNIEXPORT jint JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqUnsubscribeImp(JNIEnv *env, jobject jobj, jlong jtmq) { tmq_t *tmq = (tmq_t *)jtmq; @@ -497,9 +551,9 @@ JNIEXPORT jint JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqGetTopicAssign int32_t res = tmq_get_topic_assignment(tmq, topicName, &pAssign, &numOfAssignment); if (res != TSDB_CODE_SUCCESS) { - (*env)->ReleaseStringUTFChars(env, jtopic, topicName); jniError("jobj:%p, tmq get topic assignment error, topic:%s, code:%d, msg:%s", jobj, topicName, res, tmq_err2str(res)); + (*env)->ReleaseStringUTFChars(env, jtopic, topicName); tmq_free_assignment(pAssign); return (jint)res; } @@ -518,3 +572,55 @@ JNIEXPORT jint JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqGetTopicAssign tmq_free_assignment(pAssign); return JNI_SUCCESS; } + +JNIEXPORT jlong JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqCommittedImp(JNIEnv *env, jobject jobj, jlong jtmq, + jstring jtopic, jint vgId) { + tmq_t *tmq = (tmq_t *)jtmq; + if (tmq == NULL) { + jniDebug("jobj:%p, tmq is closed", jobj); + return TMQ_CONSUMER_NULL; + } + + if (jtopic == NULL) { + jniDebug("jobj:%p, topic is null", jobj); + return TMQ_TOPIC_NULL; + } + + const char *topicName = (*env)->GetStringUTFChars(env, jtopic, NULL); + + int64_t offset = tmq_committed(tmq, topicName, vgId); + + if (offset < JNI_SUCCESS) { + jniError("jobj:%p, tmq get committed offset error, topic:%s, vgId:%d, code:0x%" PRIx64 ", msg:%s", jobj, topicName, + vgId, offset, tmq_err2str(offset)); + } + + (*env)->ReleaseStringUTFChars(env, jtopic, topicName); + return (jlong)offset; +} + +JNIEXPORT jlong JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqPositionImp(JNIEnv *env, jobject jobj, jlong jtmq, + jstring jtopic, jint vgId) { + tmq_t *tmq = (tmq_t *)jtmq; + if (tmq == NULL) { + jniDebug("jobj:%p, tmq is closed", jobj); + return TMQ_CONSUMER_NULL; + } + + if (jtopic == NULL) { + jniDebug("jobj:%p, topic is null", jobj); + return TMQ_TOPIC_NULL; + } + + const char *topicName = (*env)->GetStringUTFChars(env, jtopic, NULL); + + int64_t offset = tmq_position(tmq, topicName, vgId); + + if (offset < JNI_SUCCESS) { + jniError("jobj:%p, tmq get position error, topic:%s, vgId:%d, code:0x%" PRIx64 ", msg:%s", jobj, topicName, vgId, + offset, tmq_err2str(offset)); + } + + (*env)->ReleaseStringUTFChars(env, jtopic, topicName); + return (jlong)offset; +} \ No newline at end of file From fe4e45647ea854cfab9cbc7aabc0054ced5117c0 Mon Sep 17 00:00:00 2001 From: dapan1121 Date: Tue, 22 Aug 2023 18:29:25 +0800 Subject: [PATCH 019/120] fix: global data sink manager issue --- source/libs/executor/src/dataDeleter.c | 2 ++ source/libs/executor/src/dataDispatcher.c | 1 + source/libs/executor/src/dataInserter.c | 2 ++ source/libs/executor/src/dataSinkMgt.c | 22 ++++++++++++++-------- source/libs/executor/src/executor.c | 5 +++-- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/source/libs/executor/src/dataDeleter.c b/source/libs/executor/src/dataDeleter.c index 11074b0e94..a30dc47029 100644 --- a/source/libs/executor/src/dataDeleter.c +++ b/source/libs/executor/src/dataDeleter.c @@ -224,6 +224,8 @@ static int32_t destroyDataSinker(SDataSinkHandle* pHandle) { } taosCloseQueue(pDeleter->pDataBlocks); taosThreadMutexDestroy(&pDeleter->mutex); + + taosMemoryFree(pDeleter->pManager); return TSDB_CODE_SUCCESS; } diff --git a/source/libs/executor/src/dataDispatcher.c b/source/libs/executor/src/dataDispatcher.c index 2a22656d8c..56d0ca523a 100644 --- a/source/libs/executor/src/dataDispatcher.c +++ b/source/libs/executor/src/dataDispatcher.c @@ -226,6 +226,7 @@ static int32_t destroyDataSinker(SDataSinkHandle* pHandle) { } taosCloseQueue(pDispatcher->pDataBlocks); taosThreadMutexDestroy(&pDispatcher->mutex); + taosMemoryFree(pDispatcher->pManager); return TSDB_CODE_SUCCESS; } diff --git a/source/libs/executor/src/dataInserter.c b/source/libs/executor/src/dataInserter.c index 646964ebf4..f20293791b 100644 --- a/source/libs/executor/src/dataInserter.c +++ b/source/libs/executor/src/dataInserter.c @@ -395,6 +395,8 @@ static int32_t destroyDataSinker(SDataSinkHandle* pHandle) { taosMemoryFree(pInserter->pParam); taosHashCleanup(pInserter->pCols); taosThreadMutexDestroy(&pInserter->mutex); + + taosMemoryFree(pInserter->pManager); return TSDB_CODE_SUCCESS; } diff --git a/source/libs/executor/src/dataSinkMgt.c b/source/libs/executor/src/dataSinkMgt.c index 3a972c1c20..f07d176440 100644 --- a/source/libs/executor/src/dataSinkMgt.c +++ b/source/libs/executor/src/dataSinkMgt.c @@ -18,12 +18,17 @@ #include "planner.h" #include "tarray.h" -static SDataSinkManager gDataSinkManager = {0}; SDataSinkStat gDataSinkStat = {0}; -int32_t dsDataSinkMgtInit(SDataSinkMgtCfg* cfg, SStorageAPI* pAPI) { - gDataSinkManager.cfg = *cfg; - gDataSinkManager.pAPI = pAPI; +int32_t dsDataSinkMgtInit(SDataSinkMgtCfg* cfg, SStorageAPI* pAPI, void** ppSinkManager) { + SDataSinkManager* pSinkManager = taosMemoryMalloc(sizeof(SDataSinkManager)); + if (NULL == pSinkManager) { + return TSDB_CODE_OUT_OF_MEMORY; + } + pSinkManager->cfg = *cfg; + pSinkManager->pAPI = pAPI; + + *ppSinkManager = pSinkManager; return 0; // to avoid compiler eror } @@ -33,15 +38,16 @@ int32_t dsDataSinkGetCacheSize(SDataSinkStat* pStat) { return 0; } -int32_t dsCreateDataSinker(const SDataSinkNode* pDataSink, DataSinkHandle* pHandle, void* pParam, const char* id) { +int32_t dsCreateDataSinker(void* pSinkManager, const SDataSinkNode* pDataSink, DataSinkHandle* pHandle, void* pParam, const char* id) { + SDataSinkManager* pManager = pSinkManager; switch ((int)nodeType(pDataSink)) { case QUERY_NODE_PHYSICAL_PLAN_DISPATCH: - return createDataDispatcher(&gDataSinkManager, pDataSink, pHandle); + return createDataDispatcher(pManager, pDataSink, pHandle); case QUERY_NODE_PHYSICAL_PLAN_DELETE: { - return createDataDeleter(&gDataSinkManager, pDataSink, pHandle, pParam); + return createDataDeleter(pManager, pDataSink, pHandle, pParam); } case QUERY_NODE_PHYSICAL_PLAN_QUERY_INSERT: { - return createDataInserter(&gDataSinkManager, pDataSink, pHandle, pParam); + return createDataInserter(pManager, pDataSink, pHandle, pParam); } } diff --git a/source/libs/executor/src/executor.c b/source/libs/executor/src/executor.c index a6059c7c42..9f5db5d6ae 100644 --- a/source/libs/executor/src/executor.c +++ b/source/libs/executor/src/executor.c @@ -512,7 +512,8 @@ int32_t qCreateExecTask(SReadHandle* readHandle, int32_t vgId, uint64_t taskId, } SDataSinkMgtCfg cfg = {.maxDataBlockNum = 500, .maxDataBlockNumPerQuery = 50}; - code = dsDataSinkMgtInit(&cfg, &(*pTask)->storageAPI); + void* pSinkManager = NULL; + code = dsDataSinkMgtInit(&cfg, &(*pTask)->storageAPI, &pSinkManager); if (code != TSDB_CODE_SUCCESS) { qError("failed to dsDataSinkMgtInit, code:%s, %s", tstrerror(code), (*pTask)->id.str); goto _error; @@ -527,7 +528,7 @@ int32_t qCreateExecTask(SReadHandle* readHandle, int32_t vgId, uint64_t taskId, } // pSinkParam has been freed during create sinker. - code = dsCreateDataSinker(pSubplan->pDataSink, handle, pSinkParam, (*pTask)->id.str); + code = dsCreateDataSinker(pSinkManager, pSubplan->pDataSink, handle, pSinkParam, (*pTask)->id.str); } qDebug("subplan task create completed, TID:0x%" PRIx64 " QID:0x%" PRIx64, taskId, pSubplan->id.queryId); From 647b57d2641c98102d12826021bf46e870f9d847 Mon Sep 17 00:00:00 2001 From: kailixu Date: Tue, 22 Aug 2023 18:30:11 +0800 Subject: [PATCH 020/120] fix: assign flags during subscribe --- source/client/src/clientRawBlockWrite.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/client/src/clientRawBlockWrite.c b/source/client/src/clientRawBlockWrite.c index dd311db126..ecc586a7e9 100644 --- a/source/client/src/clientRawBlockWrite.c +++ b/source/client/src/clientRawBlockWrite.c @@ -687,14 +687,14 @@ static int32_t taosCreateStb(TAOS* taos, void* meta, int32_t metaLen) { pReq.pColumns = taosArrayInit(req.schemaRow.nCols, sizeof(SField)); for (int32_t i = 0; i < req.schemaRow.nCols; i++) { SSchema* pSchema = req.schemaRow.pSchema + i; - SField field = {.type = pSchema->type, .bytes = pSchema->bytes}; + SField field = {.type = pSchema->type, .bytes = pSchema->bytes, .flags = pSchema->flags}; strcpy(field.name, pSchema->name); taosArrayPush(pReq.pColumns, &field); } pReq.pTags = taosArrayInit(req.schemaTag.nCols, sizeof(SField)); for (int32_t i = 0; i < req.schemaTag.nCols; i++) { SSchema* pSchema = req.schemaTag.pSchema + i; - SField field = {.type = pSchema->type, .bytes = pSchema->bytes}; + SField field = {.type = pSchema->type, .bytes = pSchema->bytes, .flags = pSchema->flags}; strcpy(field.name, pSchema->name); taosArrayPush(pReq.pTags, &field); } From 46a4dfd92ad16cd755b11ad8bee3fd815230710c Mon Sep 17 00:00:00 2001 From: kailixu Date: Tue, 22 Aug 2023 18:32:52 +0800 Subject: [PATCH 021/120] fix: assign flags during subscribe --- source/client/src/clientRawBlockWrite.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/client/src/clientRawBlockWrite.c b/source/client/src/clientRawBlockWrite.c index ecc586a7e9..9fb89aad77 100644 --- a/source/client/src/clientRawBlockWrite.c +++ b/source/client/src/clientRawBlockWrite.c @@ -687,14 +687,14 @@ static int32_t taosCreateStb(TAOS* taos, void* meta, int32_t metaLen) { pReq.pColumns = taosArrayInit(req.schemaRow.nCols, sizeof(SField)); for (int32_t i = 0; i < req.schemaRow.nCols; i++) { SSchema* pSchema = req.schemaRow.pSchema + i; - SField field = {.type = pSchema->type, .bytes = pSchema->bytes, .flags = pSchema->flags}; + SField field = {.type = pSchema->type, .flags = pSchema->flags, .bytes = pSchema->bytes}; strcpy(field.name, pSchema->name); taosArrayPush(pReq.pColumns, &field); } pReq.pTags = taosArrayInit(req.schemaTag.nCols, sizeof(SField)); for (int32_t i = 0; i < req.schemaTag.nCols; i++) { SSchema* pSchema = req.schemaTag.pSchema + i; - SField field = {.type = pSchema->type, .bytes = pSchema->bytes, .flags = pSchema->flags}; + SField field = {.type = pSchema->type, .flags = pSchema->flags, .bytes = pSchema->bytes}; strcpy(field.name, pSchema->name); taosArrayPush(pReq.pTags, &field); } From cb70861986d90be238be37ebc0ae37989cf74aa5 Mon Sep 17 00:00:00 2001 From: dapan1121 Date: Tue, 22 Aug 2023 18:40:42 +0800 Subject: [PATCH 022/120] fix: memory leak issue --- include/libs/executor/dataSinkMgt.h | 4 ++-- source/libs/executor/src/dataDeleter.c | 2 ++ source/libs/executor/src/dataDispatcher.c | 9 +++++++-- source/libs/executor/src/dataInserter.c | 24 ++++++++++++++--------- source/libs/executor/src/dataSinkMgt.c | 3 +++ source/libs/executor/src/executor.c | 17 ++++++++-------- 6 files changed, 38 insertions(+), 21 deletions(-) diff --git a/include/libs/executor/dataSinkMgt.h b/include/libs/executor/dataSinkMgt.h index 0a9037d21c..29ba019e47 100644 --- a/include/libs/executor/dataSinkMgt.h +++ b/include/libs/executor/dataSinkMgt.h @@ -59,7 +59,7 @@ typedef struct SDataSinkMgtCfg { uint32_t maxDataBlockNumPerQuery; } SDataSinkMgtCfg; -int32_t dsDataSinkMgtInit(SDataSinkMgtCfg* cfg, SStorageAPI* pAPI); +int32_t dsDataSinkMgtInit(SDataSinkMgtCfg* cfg, SStorageAPI* pAPI, void** ppSinkManager); typedef struct SInputData { const struct SSDataBlock* pData; @@ -83,7 +83,7 @@ typedef struct SOutputData { * @param pHandle output * @return error code */ -int32_t dsCreateDataSinker(const SDataSinkNode* pDataSink, DataSinkHandle* pHandle, void* pParam, const char* id); +int32_t dsCreateDataSinker(void* pSinkManager, const SDataSinkNode* pDataSink, DataSinkHandle* pHandle, void* pParam, const char* id); int32_t dsDataSinkGetCacheSize(SDataSinkStat* pStat); diff --git a/source/libs/executor/src/dataDeleter.c b/source/libs/executor/src/dataDeleter.c index a30dc47029..960ae14fcf 100644 --- a/source/libs/executor/src/dataDeleter.c +++ b/source/libs/executor/src/dataDeleter.c @@ -281,6 +281,8 @@ _end: if (deleter != NULL) { destroyDataSinker((SDataSinkHandle*)deleter); taosMemoryFree(deleter); + } else { + taosMemoryFree(pManager); } return code; } diff --git a/source/libs/executor/src/dataDispatcher.c b/source/libs/executor/src/dataDispatcher.c index 56d0ca523a..409ae50174 100644 --- a/source/libs/executor/src/dataDispatcher.c +++ b/source/libs/executor/src/dataDispatcher.c @@ -241,7 +241,7 @@ int32_t createDataDispatcher(SDataSinkManager* pManager, const SDataSinkNode* pD SDataDispatchHandle* dispatcher = taosMemoryCalloc(1, sizeof(SDataDispatchHandle)); if (NULL == dispatcher) { terrno = TSDB_CODE_OUT_OF_MEMORY; - return TSDB_CODE_OUT_OF_MEMORY; + goto _return; } dispatcher->sink.fPut = putDataBlock; dispatcher->sink.fEndPut = endPut; @@ -258,8 +258,13 @@ int32_t createDataDispatcher(SDataSinkManager* pManager, const SDataSinkNode* pD if (NULL == dispatcher->pDataBlocks) { taosMemoryFree(dispatcher); terrno = TSDB_CODE_OUT_OF_MEMORY; - return TSDB_CODE_OUT_OF_MEMORY; + goto _return; } *pHandle = dispatcher; return TSDB_CODE_SUCCESS; + +_return: + + taosMemoryFree(pManager); + return terrno; } diff --git a/source/libs/executor/src/dataInserter.c b/source/libs/executor/src/dataInserter.c index f20293791b..8a70726cc8 100644 --- a/source/libs/executor/src/dataInserter.c +++ b/source/libs/executor/src/dataInserter.c @@ -413,7 +413,7 @@ int32_t createDataInserter(SDataSinkManager* pManager, const SDataSinkNode* pDat if (NULL == inserter) { taosMemoryFree(pParam); terrno = TSDB_CODE_OUT_OF_MEMORY; - return TSDB_CODE_OUT_OF_MEMORY; + goto _return; } SQueryInserterNode* pInserterNode = (SQueryInserterNode*)pDataSink; @@ -433,23 +433,18 @@ int32_t createDataInserter(SDataSinkManager* pManager, const SDataSinkNode* pDat int64_t suid = 0; int32_t code = pManager->pAPI->metaFn.getTableSchema(inserter->pParam->readHandle->vnode, pInserterNode->tableId, &inserter->pSchema, &suid); if (code) { - destroyDataSinker((SDataSinkHandle*)inserter); - taosMemoryFree(inserter); - return code; + terrno = code; + goto _return; } if (pInserterNode->stableId != suid) { - destroyDataSinker((SDataSinkHandle*)inserter); - taosMemoryFree(inserter); terrno = TSDB_CODE_TDB_INVALID_TABLE_ID; - return terrno; + goto _return; } inserter->pDataBlocks = taosArrayInit(1, POINTER_BYTES); taosThreadMutexInit(&inserter->mutex, NULL); if (NULL == inserter->pDataBlocks) { - destroyDataSinker((SDataSinkHandle*)inserter); - taosMemoryFree(inserter); terrno = TSDB_CODE_OUT_OF_MEMORY; return TSDB_CODE_OUT_OF_MEMORY; } @@ -473,4 +468,15 @@ int32_t createDataInserter(SDataSinkManager* pManager, const SDataSinkNode* pDat *pHandle = inserter; return TSDB_CODE_SUCCESS; + +_return: + + if (inserter) { + destroyDataSinker((SDataSinkHandle*)inserter); + taosMemoryFree(inserter); + } else { + taosMemoryFree(pManager); + } + + return terrno; } diff --git a/source/libs/executor/src/dataSinkMgt.c b/source/libs/executor/src/dataSinkMgt.c index f07d176440..b2cbf4c1a2 100644 --- a/source/libs/executor/src/dataSinkMgt.c +++ b/source/libs/executor/src/dataSinkMgt.c @@ -49,8 +49,11 @@ int32_t dsCreateDataSinker(void* pSinkManager, const SDataSinkNode* pDataSink, D case QUERY_NODE_PHYSICAL_PLAN_QUERY_INSERT: { return createDataInserter(pManager, pDataSink, pHandle, pParam); } + default: + break; } + taosMemoryFree(pSinkManager); qError("invalid input node type:%d, %s", nodeType(pDataSink), id); return TSDB_CODE_QRY_INVALID_INPUT; } diff --git a/source/libs/executor/src/executor.c b/source/libs/executor/src/executor.c index 9f5db5d6ae..28ee8f4b7a 100644 --- a/source/libs/executor/src/executor.c +++ b/source/libs/executor/src/executor.c @@ -511,19 +511,20 @@ int32_t qCreateExecTask(SReadHandle* readHandle, int32_t vgId, uint64_t taskId, goto _error; } - SDataSinkMgtCfg cfg = {.maxDataBlockNum = 500, .maxDataBlockNumPerQuery = 50}; - void* pSinkManager = NULL; - code = dsDataSinkMgtInit(&cfg, &(*pTask)->storageAPI, &pSinkManager); - if (code != TSDB_CODE_SUCCESS) { - qError("failed to dsDataSinkMgtInit, code:%s, %s", tstrerror(code), (*pTask)->id.str); - goto _error; - } - if (handle) { + SDataSinkMgtCfg cfg = {.maxDataBlockNum = 500, .maxDataBlockNumPerQuery = 50}; + void* pSinkManager = NULL; + code = dsDataSinkMgtInit(&cfg, &(*pTask)->storageAPI, &pSinkManager); + if (code != TSDB_CODE_SUCCESS) { + qError("failed to dsDataSinkMgtInit, code:%s, %s", tstrerror(code), (*pTask)->id.str); + goto _error; + } + void* pSinkParam = NULL; code = createDataSinkParam(pSubplan->pDataSink, &pSinkParam, (*pTask), readHandle); if (code != TSDB_CODE_SUCCESS) { qError("failed to createDataSinkParam, vgId:%d, code:%s, %s", vgId, tstrerror(code), (*pTask)->id.str); + taosMemoryFree(pSinkManager); goto _error; } From 7b5fc0cc64bce6c2a32c45f66603d077c8806143 Mon Sep 17 00:00:00 2001 From: Ping Xiao Date: Tue, 22 Aug 2023 18:59:30 +0800 Subject: [PATCH 023/120] TS-3851: add tdef.h into release package --- packaging/deb/DEBIAN/prerm | 1 + packaging/deb/makedeb.sh | 1 + packaging/rpm/tdengine.spec | 2 ++ packaging/tools/install.sh | 3 ++- packaging/tools/install_client.sh | 3 ++- packaging/tools/make_install.sh | 5 +++-- packaging/tools/makeclient.sh | 2 +- packaging/tools/makepkg.sh | 2 +- packaging/tools/post.sh | 3 ++- packaging/tools/preun.sh | 1 + packaging/tools/remove.sh | 1 + packaging/tools/remove_client.sh | 1 + 12 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packaging/deb/DEBIAN/prerm b/packaging/deb/DEBIAN/prerm index 0d63115a04..a474dc4c80 100644 --- a/packaging/deb/DEBIAN/prerm +++ b/packaging/deb/DEBIAN/prerm @@ -37,6 +37,7 @@ else ${csudo}rm -f ${inc_link_dir}/taos.h || : ${csudo}rm -f ${inc_link_dir}/taosdef.h || : ${csudo}rm -f ${inc_link_dir}/taoserror.h || : + ${csudo}rm -f ${inc_link_dir}/tdef.h || : ${csudo}rm -f ${inc_link_dir}/taosudf.h || : [ -f ${inc_link_dir}/taosws.h ] && ${csudo}rm -f ${inc_link_dir}/taosws.h || : ${csudo}rm -f ${lib_link_dir}/libtaos.* || : diff --git a/packaging/deb/makedeb.sh b/packaging/deb/makedeb.sh index 07819159c4..eca75ce71a 100755 --- a/packaging/deb/makedeb.sh +++ b/packaging/deb/makedeb.sh @@ -98,6 +98,7 @@ cp ${compile_dir}/build/lib/${libfile} ${pkg_dir}${install_home_pat cp ${compile_dir}/../include/client/taos.h ${pkg_dir}${install_home_path}/include cp ${compile_dir}/../include/common/taosdef.h ${pkg_dir}${install_home_path}/include cp ${compile_dir}/../include/util/taoserror.h ${pkg_dir}${install_home_path}/include +cp ${compile_dir}/../include/util/tdef.h ${pkg_dir}${install_home_path}/include cp ${compile_dir}/../include/libs/function/taosudf.h ${pkg_dir}${install_home_path}/include [ -f ${compile_dir}/build/include/taosws.h ] && cp ${compile_dir}/build/include/taosws.h ${pkg_dir}${install_home_path}/include ||: cp -r ${top_dir}/examples/* ${pkg_dir}${install_home_path}/examples diff --git a/packaging/rpm/tdengine.spec b/packaging/rpm/tdengine.spec index 846d17e7f6..e93af2470a 100644 --- a/packaging/rpm/tdengine.spec +++ b/packaging/rpm/tdengine.spec @@ -95,6 +95,7 @@ cp %{_compiledir}/build/lib/${libfile} %{buildroot}%{homepath}/driv cp %{_compiledir}/../include/client/taos.h %{buildroot}%{homepath}/include cp %{_compiledir}/../include/common/taosdef.h %{buildroot}%{homepath}/include cp %{_compiledir}/../include/util/taoserror.h %{buildroot}%{homepath}/include +cp %{_compiledir}/../include/util/tdef.h %{buildroot}%{homepath}/include cp %{_compiledir}/../include/libs/function/taosudf.h %{buildroot}%{homepath}/include [ -f %{_compiledir}/build/include/taosws.h ] && cp %{_compiledir}/build/include/taosws.h %{buildroot}%{homepath}/include ||: #cp -r %{_compiledir}/../src/connector/python %{buildroot}%{homepath}/connector @@ -217,6 +218,7 @@ if [ $1 -eq 0 ];then ${csudo}rm -f ${inc_link_dir}/taos.h || : ${csudo}rm -f ${inc_link_dir}/taosdef.h || : ${csudo}rm -f ${inc_link_dir}/taoserror.h || : + ${csudo}rm -f ${inc_link_dir}/tdef.h || : ${csudo}rm -f ${inc_link_dir}/taosudf.h || : ${csudo}rm -f ${lib_link_dir}/libtaos.* || : diff --git a/packaging/tools/install.sh b/packaging/tools/install.sh index 252a634a19..408a5664a8 100755 --- a/packaging/tools/install.sh +++ b/packaging/tools/install.sh @@ -345,7 +345,7 @@ function install_jemalloc() { } function install_header() { - ${csudo}rm -f ${inc_link_dir}/taos.h ${inc_link_dir}/taosdef.h ${inc_link_dir}/taoserror.h ${inc_link_dir}/taosudf.h || : + ${csudo}rm -f ${inc_link_dir}/taos.h ${inc_link_dir}/taosdef.h ${inc_link_dir}/taoserror.h ${inc_link_dir}/tdef.h ${inc_link_dir}/taosudf.h || : [ -f ${inc_link_dir}/taosws.h ] && ${csudo}rm -f ${inc_link_dir}/taosws.h || : @@ -353,6 +353,7 @@ function install_header() { ${csudo}ln -sf ${install_main_dir}/include/taos.h ${inc_link_dir}/taos.h ${csudo}ln -sf ${install_main_dir}/include/taosdef.h ${inc_link_dir}/taosdef.h ${csudo}ln -sf ${install_main_dir}/include/taoserror.h ${inc_link_dir}/taoserror.h + ${csudo}ln -sf ${install_main_dir}/include/tdef.h ${inc_link_dir}/tdef.h ${csudo}ln -sf ${install_main_dir}/include/taosudf.h ${inc_link_dir}/taosudf.h [ -f ${install_main_dir}/include/taosws.h ] && ${csudo}ln -sf ${install_main_dir}/include/taosws.h ${inc_link_dir}/taosws.h || : diff --git a/packaging/tools/install_client.sh b/packaging/tools/install_client.sh index 18ebf9dc8f..c8baab8269 100755 --- a/packaging/tools/install_client.sh +++ b/packaging/tools/install_client.sh @@ -180,10 +180,11 @@ function install_lib() { } function install_header() { - ${csudo}rm -f ${inc_link_dir}/taos.h ${inc_link_dir}/taosdef.h ${inc_link_dir}/taoserror.h ${inc_link_dir}/taosudf.h || : + ${csudo}rm -f ${inc_link_dir}/taos.h ${inc_link_dir}/taosdef.h ${inc_link_dir}/tdef.h ${inc_link_dir}/taoserror.h ${inc_link_dir}/taosudf.h || : ${csudo}cp -f ${script_dir}/inc/* ${install_main_dir}/include && ${csudo}chmod 644 ${install_main_dir}/include/* ${csudo}ln -s ${install_main_dir}/include/taos.h ${inc_link_dir}/taos.h ${csudo}ln -s ${install_main_dir}/include/taosdef.h ${inc_link_dir}/taosdef.h + ${csudo}ln -s ${install_main_dir}/include/tdef.h ${inc_link_dir}/tdef.h ${csudo}ln -s ${install_main_dir}/include/taoserror.h ${inc_link_dir}/taoserror.h ${csudo}ln -s ${install_main_dir}/include/taosudf.h ${inc_link_dir}/taosudf.h diff --git a/packaging/tools/make_install.sh b/packaging/tools/make_install.sh index 0a5f9d2668..99315b3311 100755 --- a/packaging/tools/make_install.sh +++ b/packaging/tools/make_install.sh @@ -348,9 +348,9 @@ function install_lib() { function install_header() { ${csudo}mkdir -p ${inc_link_dir} - ${csudo}rm -f ${inc_link_dir}/taos.h ${inc_link_dir}/taosdef.h ${inc_link_dir}/taoserror.h ${inc_link_dir}/taosudf.h || : + ${csudo}rm -f ${inc_link_dir}/taos.h ${inc_link_dir}/taosdef.h ${inc_link_dir}/taoserror.h ${inc_link_dir}/tdef.h ${inc_link_dir}/taosudf.h || : [ -f ${inc_link_dir}/taosws.h ] && ${csudo}rm -f ${inc_link_dir}/taosws.h ||: - ${csudo}cp -f ${source_dir}/include/client/taos.h ${source_dir}/include/common/taosdef.h ${source_dir}/include/util/taoserror.h ${source_dir}/include/libs/function/taosudf.h \ + ${csudo}cp -f ${source_dir}/include/client/taos.h ${source_dir}/include/common/taosdef.h ${source_dir}/include/util/taoserror.h ${source_dir}/include/util/tdef.h ${source_dir}/include/libs/function/taosudf.h \ ${install_main_dir}/include && ${csudo}chmod 644 ${install_main_dir}/include/* if [ -f ${binary_dir}/build/include/taosws.h ]; then @@ -361,6 +361,7 @@ function install_header() { ${csudo}ln -s ${install_main_dir}/include/taos.h ${inc_link_dir}/taos.h > /dev/null 2>&1 ${csudo}ln -s ${install_main_dir}/include/taosdef.h ${inc_link_dir}/taosdef.h > /dev/null 2>&1 ${csudo}ln -s ${install_main_dir}/include/taoserror.h ${inc_link_dir}/taoserror.h > /dev/null 2>&1 + ${csudo}ln -s ${install_main_dir}/include/tdef.h ${inc_link_dir}/tdef.h > /dev/null 2>&1 ${csudo}ln -s ${install_main_dir}/include/taosudf.h ${inc_link_dir}/taosudf.h > /dev/null 2>&1 ${csudo}chmod 644 ${install_main_dir}/include/* diff --git a/packaging/tools/makeclient.sh b/packaging/tools/makeclient.sh index cd59294fe7..243efd693e 100755 --- a/packaging/tools/makeclient.sh +++ b/packaging/tools/makeclient.sh @@ -83,7 +83,7 @@ else wslib_files="${build_dir}/lib/libtaosws.dylib" fi -header_files="${code_dir}/include/client/taos.h ${code_dir}/include/common/taosdef.h ${code_dir}/include/util/taoserror.h ${code_dir}/include/libs/function/taosudf.h" +header_files="${code_dir}/include/client/taos.h ${code_dir}/include/common/taosdef.h ${code_dir}/include/util/taoserror.h ${code_dir}/include/util/tdef.h ${code_dir}/include/libs/function/taosudf.h" wsheader_files="${build_dir}/include/taosws.h" if [ "$dbName" != "taos" ]; then diff --git a/packaging/tools/makepkg.sh b/packaging/tools/makepkg.sh index ad64ca431e..655629b92c 100755 --- a/packaging/tools/makepkg.sh +++ b/packaging/tools/makepkg.sh @@ -115,7 +115,7 @@ else lib_files="${build_dir}/lib/libtaos.so.${version}" wslib_files="${build_dir}/lib/libtaosws.so" fi -header_files="${code_dir}/include/client/taos.h ${code_dir}/include/common/taosdef.h ${code_dir}/include/util/taoserror.h ${code_dir}/include/libs/function/taosudf.h" +header_files="${code_dir}/include/client/taos.h ${code_dir}/include/common/taosdef.h ${code_dir}/include/util/taoserror.h ${code_dir}/include/util/tdef.h ${code_dir}/include/libs/function/taosudf.h" wsheader_files="${build_dir}/include/taosws.h" diff --git a/packaging/tools/post.sh b/packaging/tools/post.sh index e79a10c9e9..ceaebfdc7c 100755 --- a/packaging/tools/post.sh +++ b/packaging/tools/post.sh @@ -133,12 +133,13 @@ function kill_taosd() { function install_include() { log_print "start install include from ${inc_dir} to ${inc_link_dir}" ${csudo}mkdir -p ${inc_link_dir} - ${csudo}rm -f ${inc_link_dir}/taos.h ${inc_link_dir}/taosdef.h ${inc_link_dir}/taoserror.h ${inc_link_dir}/taosudf.h || : + ${csudo}rm -f ${inc_link_dir}/taos.h ${inc_link_dir}/taosdef.h ${inc_link_dir}/taoserror.h ${inc_link_dir}/tdef.h ${inc_link_dir}/taosudf.h || : [ -f ${inc_link_dir}/taosws.h ] && ${csudo}rm -f ${inc_link_dir}/taosws.h ||: ${csudo}ln -s ${inc_dir}/taos.h ${inc_link_dir}/taos.h ${csudo}ln -s ${inc_dir}/taosdef.h ${inc_link_dir}/taosdef.h ${csudo}ln -s ${inc_dir}/taoserror.h ${inc_link_dir}/taoserror.h + ${csudo}ln -s ${inc_dir}/tdef.h ${inc_link_dir}/tdef.h ${csudo}ln -s ${inc_dir}/taosudf.h ${inc_link_dir}/taosudf.h [ -f ${inc_dir}/taosws.h ] && ${csudo}ln -sf ${inc_dir}/taosws.h ${inc_link_dir}/taosws.h ||: diff --git a/packaging/tools/preun.sh b/packaging/tools/preun.sh index 68f6b53c45..25f3d8ce4a 100755 --- a/packaging/tools/preun.sh +++ b/packaging/tools/preun.sh @@ -143,6 +143,7 @@ ${csudo}rm -f ${cfg_link_dir}/*.new || : ${csudo}rm -f ${inc_link_dir}/taos.h || : ${csudo}rm -f ${inc_link_dir}/taosdef.h || : ${csudo}rm -f ${inc_link_dir}/taoserror.h || : +${csudo}rm -f ${inc_link_dir}/tdef.h || : ${csudo}rm -f ${inc_link_dir}/taosudf.h || : ${csudo}rm -f ${lib_link_dir}/libtaos.* || : ${csudo}rm -f ${lib64_link_dir}/libtaos.* || : diff --git a/packaging/tools/remove.sh b/packaging/tools/remove.sh index eca0c5e973..0e8b036f28 100755 --- a/packaging/tools/remove.sh +++ b/packaging/tools/remove.sh @@ -155,6 +155,7 @@ function clean_header() { ${csudo}rm -f ${inc_link_dir}/taos.h || : ${csudo}rm -f ${inc_link_dir}/taosdef.h || : ${csudo}rm -f ${inc_link_dir}/taoserror.h || : + ${csudo}rm -f ${inc_link_dir}/tdef.h || : ${csudo}rm -f ${inc_link_dir}/taosudf.h || : [ -f ${inc_link_dir}/taosws.h ] && ${csudo}rm -f ${inc_link_dir}/taosws.h || : diff --git a/packaging/tools/remove_client.sh b/packaging/tools/remove_client.sh index 2bdb56fac2..695307254d 100755 --- a/packaging/tools/remove_client.sh +++ b/packaging/tools/remove_client.sh @@ -73,6 +73,7 @@ function clean_header() { ${csudo}rm -f ${inc_link_dir}/taos.h || : ${csudo}rm -f ${inc_link_dir}/taosdef.h || : ${csudo}rm -f ${inc_link_dir}/taoserror.h || : + ${csudo}rm -f ${inc_link_dir}/tdef.h || : ${csudo}rm -f ${inc_link_dir}/taosudf.h || : } From a601cd21bd3f904f9628b40618a0b31cb488fc0e Mon Sep 17 00:00:00 2001 From: Hongze Cheng Date: Wed, 23 Aug 2023 13:31:19 +0800 Subject: [PATCH 024/120] fix: alter stt_trigger bug --- source/dnode/vnode/src/tsdb/tsdbCommit2.c | 47 +++++++++++------------ 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/source/dnode/vnode/src/tsdb/tsdbCommit2.c b/source/dnode/vnode/src/tsdb/tsdbCommit2.c index afbe895721..d4cb63fb7b 100644 --- a/source/dnode/vnode/src/tsdb/tsdbCommit2.c +++ b/source/dnode/vnode/src/tsdb/tsdbCommit2.c @@ -235,36 +235,33 @@ static int32_t tsdbCommitOpenReader(SCommitter2 *committer) { return 0; } - ASSERT(TARRAY2_SIZE(committer->ctx->fset->lvlArr) == 1); + SSttLvl *lvl; + TARRAY2_FOREACH(committer->ctx->fset->lvlArr, lvl) { + STFileObj *fobj = NULL; + TARRAY2_FOREACH(lvl->fobjArr, fobj) { + SSttFileReader *sttReader; - SSttLvl *lvl = TARRAY2_FIRST(committer->ctx->fset->lvlArr); + SSttFileReaderConfig config = { + .tsdb = committer->tsdb, + .szPage = committer->szPage, + .file = fobj->f[0], + }; - ASSERT(lvl->level == 0); + code = tsdbSttFileReaderOpen(fobj->fname, &config, &sttReader); + TSDB_CHECK_CODE(code, lino, _exit); - STFileObj *fobj = NULL; - TARRAY2_FOREACH(lvl->fobjArr, fobj) { - SSttFileReader *sttReader; + code = TARRAY2_APPEND(committer->sttReaderArray, sttReader); + TSDB_CHECK_CODE(code, lino, _exit); - SSttFileReaderConfig config = { - .tsdb = committer->tsdb, - .szPage = committer->szPage, - .file = fobj->f[0], - }; + STFileOp op = { + .optype = TSDB_FOP_REMOVE, + .fid = fobj->f->fid, + .of = fobj->f[0], + }; - code = tsdbSttFileReaderOpen(fobj->fname, &config, &sttReader); - TSDB_CHECK_CODE(code, lino, _exit); - - code = TARRAY2_APPEND(committer->sttReaderArray, sttReader); - TSDB_CHECK_CODE(code, lino, _exit); - - STFileOp op = { - .optype = TSDB_FOP_REMOVE, - .fid = fobj->f->fid, - .of = fobj->f[0], - }; - - code = TARRAY2_APPEND(committer->fopArray, op); - TSDB_CHECK_CODE(code, lino, _exit); + code = TARRAY2_APPEND(committer->fopArray, op); + TSDB_CHECK_CODE(code, lino, _exit); + } } _exit: From 6b61da1a41eaeab0e56e1ec3bec40d4faf89573e Mon Sep 17 00:00:00 2001 From: liuyao <54liuyao@163.com> Date: Wed, 23 Aug 2023 15:34:46 +0800 Subject: [PATCH 025/120] reload semi session state --- source/libs/executor/src/timewindowoperator.c | 79 ++++++++++++++++--- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/source/libs/executor/src/timewindowoperator.c b/source/libs/executor/src/timewindowoperator.c index 4f793d7064..37f737c2ce 100644 --- a/source/libs/executor/src/timewindowoperator.c +++ b/source/libs/executor/src/timewindowoperator.c @@ -3240,6 +3240,31 @@ static int32_t compactSessionWindow(SOperatorInfo* pOperator, SResultWindowInfo* return winNum; } +static void compactSessionSemiWindow(SOperatorInfo* pOperator, SResultWindowInfo* pCurWin) { + SExprSupp* pSup = &pOperator->exprSupp; + SExecTaskInfo* pTaskInfo = pOperator->pTaskInfo; + SStorageAPI* pAPI = &pOperator->pTaskInfo->storageAPI; + SStreamSessionAggOperatorInfo* pInfo = pOperator->info; + SResultRow* pCurResult = NULL; + int32_t numOfOutput = pOperator->exprSupp.numOfExprs; + SStreamAggSupporter* pAggSup = &pInfo->streamAggSup; + // Just look for the window behind StartIndex + while (1) { + SResultWindowInfo winInfo = {0}; + SStreamStateCur* pCur = getNextSessionWinInfo(pAggSup, NULL, pCurWin, &winInfo); + if (!IS_VALID_SESSION_WIN(winInfo) || !isInWindow(pCurWin, winInfo.sessionWin.win.skey, pAggSup->gap) || + !inWinRange(&pAggSup->winRange, &winInfo.sessionWin.win)) { + taosMemoryFree(winInfo.pOutputBuf); + pAPI->stateStore.streamStateFreeCur(pCur); + break; + } + pCurWin->sessionWin.win.ekey = TMAX(pCurWin->sessionWin.win.ekey, winInfo.sessionWin.win.ekey); + doDeleteSessionWindow(pAggSup, &winInfo.sessionWin); + pAPI->stateStore.streamStateFreeCur(pCur); + taosMemoryFree(winInfo.pOutputBuf); + } +} + int32_t saveSessionOutputBuf(SStreamAggSupporter* pAggSup, SResultWindowInfo* pWinInfo) { saveSessionDiscBuf(pAggSup->pState, &pWinInfo->sessionWin, pWinInfo->pOutputBuf, pAggSup->resultRowSize, &pAggSup->stateStore); pWinInfo->pOutputBuf = NULL; @@ -3417,9 +3442,9 @@ void doBuildDeleteDataBlock(SOperatorInfo* pOp, SSHashObj* pStDeleted, SSDataBlo } static void rebuildSessionWindow(SOperatorInfo* pOperator, SArray* pWinArray, SSHashObj* pStUpdated) { - SExprSupp* pSup = &pOperator->exprSupp; - SExecTaskInfo* pTaskInfo = pOperator->pTaskInfo; - SStorageAPI* pAPI = &pOperator->pTaskInfo->storageAPI; + SExprSupp* pSup = &pOperator->exprSupp; + SExecTaskInfo* pTaskInfo = pOperator->pTaskInfo; + SStorageAPI* pAPI = &pOperator->pTaskInfo->storageAPI; int32_t size = taosArrayGetSize(pWinArray); SStreamSessionAggOperatorInfo* pInfo = pOperator->info; @@ -3446,6 +3471,7 @@ static void rebuildSessionWindow(SOperatorInfo* pOperator, SArray* pWinArray, SS int32_t code = getSessionWinBuf(pChAggSup, pCur, &childWin); if (code == TSDB_CODE_SUCCESS && !inWinRange(&pAggSup->winRange, &childWin.sessionWin.win)) { + releaseOutputBuf(pAggSup->pState, NULL, (SResultRow*)childWin.pOutputBuf, &pAggSup->stateStore); continue; } @@ -3454,6 +3480,7 @@ static void rebuildSessionWindow(SOperatorInfo* pOperator, SArray* pWinArray, SS setSessionOutputBuf(pAggSup, pWinKey->win.skey, pWinKey->win.ekey, pWinKey->groupId, &parentWin); code = initSessionOutputBuf(&parentWin, &pResult, pSup->pCtx, numOfOutput, pSup->rowEntryInfoOffset); if (code != TSDB_CODE_SUCCESS || pResult == NULL) { + releaseOutputBuf(pAggSup->pState, NULL, (SResultRow*)childWin.pOutputBuf, &pAggSup->stateStore); break; } } @@ -3464,7 +3491,9 @@ static void rebuildSessionWindow(SOperatorInfo* pOperator, SArray* pWinArray, SS compactFunctions(pSup->pCtx, pChild->exprSupp.pCtx, numOfOutput, pTaskInfo, &pInfo->twAggSup.timeWindowData); compactSessionWindow(pOperator, &parentWin, pStUpdated, NULL, true); saveResult(parentWin, pStUpdated); + releaseOutputBuf(pAggSup->pState, NULL, (SResultRow*)childWin.pOutputBuf, &pAggSup->stateStore); } else { + releaseOutputBuf(pAggSup->pState, NULL, (SResultRow*)childWin.pOutputBuf, &pAggSup->stateStore); break; } } @@ -3703,11 +3732,11 @@ static SSDataBlock* doStreamSessionAgg(SOperatorInfo* pOperator) { } void streamSessionReleaseState(SOperatorInfo* pOperator) { - if (pOperator->operatorType != QUERY_NODE_PHYSICAL_PLAN_STREAM_SEMI_SESSION) { - SStreamSessionAggOperatorInfo* pInfo = pOperator->info; - int32_t resSize = taosArrayGetSize(pInfo->historyWins) * sizeof(SSessionKey); - pInfo->streamAggSup.stateStore.streamStateSaveInfo(pInfo->streamAggSup.pState, STREAM_SESSION_OP_STATE_NAME, strlen(STREAM_SESSION_OP_STATE_NAME), pInfo->historyWins->pData, resSize); - } + SStreamSessionAggOperatorInfo* pInfo = pOperator->info; + int32_t resSize = taosArrayGetSize(pInfo->historyWins) * sizeof(SSessionKey); + pInfo->streamAggSup.stateStore.streamStateSaveInfo(pInfo->streamAggSup.pState, STREAM_SESSION_OP_STATE_NAME, + strlen(STREAM_SESSION_OP_STATE_NAME), pInfo->historyWins->pData, + resSize); SOperatorInfo* downstream = pOperator->pDownstream[0]; if (downstream->fpSet.releaseStreamStateFn) { downstream->fpSet.releaseStreamStateFn(downstream); @@ -3719,6 +3748,33 @@ void resetWinRange(STimeWindow* winRange) { winRange->ekey = INT64_MAX; } +void streamSessionSemiReloadState(SOperatorInfo* pOperator) { + SStreamSessionAggOperatorInfo* pInfo = pOperator->info; + SStreamAggSupporter* pAggSup = &pInfo->streamAggSup; + resetWinRange(&pAggSup->winRange); + + SResultWindowInfo winInfo = {0}; + int32_t size = 0; + void* pBuf = NULL; + int32_t code = pAggSup->stateStore.streamStateGetInfo(pAggSup->pState, STREAM_SESSION_OP_STATE_NAME, + strlen(STREAM_SESSION_OP_STATE_NAME), &pBuf, &size); + int32_t num = size / sizeof(SSessionKey); + SSessionKey* pSeKeyBuf = (SSessionKey*) pBuf; + ASSERT(size == num * sizeof(SSessionKey)); + for (int32_t i = 0; i < num; i++) { + SResultWindowInfo winInfo = {0}; + setSessionOutputBuf(pAggSup, pSeKeyBuf[i].win.skey, pSeKeyBuf[i].win.ekey, pSeKeyBuf[i].groupId, &winInfo); + compactSessionSemiWindow(pOperator, &winInfo); + saveSessionOutputBuf(pAggSup, &winInfo); + } + taosMemoryFree(pBuf); + + SOperatorInfo* downstream = pOperator->pDownstream[0]; + if (downstream->fpSet.reloadStreamStateFn) { + downstream->fpSet.reloadStreamStateFn(downstream); + } +} + void streamSessionReloadState(SOperatorInfo* pOperator) { SStreamSessionAggOperatorInfo* pInfo = pOperator->info; SStreamAggSupporter* pAggSup = &pInfo->streamAggSup; @@ -3948,6 +4004,11 @@ static SSDataBlock* doStreamSessionSemiAgg(SOperatorInfo* pOperator) { removeSessionResults(pInfo->pStDeleted, pInfo->pUpdated); tSimpleHashCleanup(pInfo->pStUpdated); pInfo->pStUpdated = NULL; + + if(pInfo->isHistoryOp) { + getMaxTsWins(pInfo->pUpdated, pInfo->historyWins); + } + initGroupResInfoFromArrayList(&pInfo->groupResInfo, pInfo->pUpdated); pInfo->pUpdated = NULL; blockDataEnsureCapacity(pBInfo->pRes, pOperator->resultInfo.capacity); @@ -3996,8 +4057,8 @@ SOperatorInfo* createStreamFinalSessionAggOperatorInfo(SOperatorInfo* downstream blockDataEnsureCapacity(pInfo->pUpdateRes, 128); pOperator->fpSet = createOperatorFpSet(optrDummyOpenFn, doStreamSessionSemiAgg, NULL, destroyStreamSessionAggOperatorInfo, optrDefaultBufFn, NULL); + setOperatorStreamStateFn(pOperator, streamSessionReleaseState, streamSessionSemiReloadState); } - setOperatorStreamStateFn(pOperator, streamSessionReleaseState, streamSessionReloadState); setOperatorInfo(pOperator, name, pPhyNode->type, false, OP_NOT_OPENED, pInfo, pTaskInfo); pOperator->operatorType = pPhyNode->type; From f45d1c69b80f7ec1a69fcd8133526d05962d854e Mon Sep 17 00:00:00 2001 From: Alex Duan <417921451@qq.com> Date: Wed, 23 Aug 2023 16:17:52 +0800 Subject: [PATCH 026/120] fix: -t option and wal retention ci --- tests/system-test/0-others/walRetention.py | 38 +++++++++++----------- tools/shell/src/shellWebsocket.c | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/system-test/0-others/walRetention.py b/tests/system-test/0-others/walRetention.py index 5257b7644a..44e64ae5e7 100644 --- a/tests/system-test/0-others/walRetention.py +++ b/tests/system-test/0-others/walRetention.py @@ -161,9 +161,18 @@ class VNode : # get log size def getWalsSize(self): size = 0 + lastSize = 0 + max = -1 for walFile in self.walFiles: - size += walFile.fsize - + if self.canDelete(walFile) == False: + tdLog.info(f" calc vnode size {walFile.pathFile} size={walFile.fsize}") + size += walFile.fsize + if max < walFile.startVer: + max = walFile.startVer + lastSize = walFile.fsize + + size -= lastSize + tdLog.info(f" last file size need reduct . lastSize={lastSize}") return size # vnode @@ -183,7 +192,7 @@ class VNode : delTs = delTsLine.timestamp() for walFile in self.walFiles: mt = datetime.fromtimestamp(walFile.mtime) - info = f" {walFile.pathFile} mt={mt} line={delTsLine} start={walFile.startVer} snap={self.snapVer} end= {walFile.endVer}" + info = f" {walFile.pathFile} size={walFile.fsize} mt={mt} line={delTsLine} start={walFile.startVer} snap={self.snapVer} end= {walFile.endVer}" tdLog.info(info) if walFile.mtime < delTs and self.canDelete(walFile): # wait a moment then check file exist @@ -199,25 +208,16 @@ class VNode : if self.walSize == 0: return True + time.sleep(2) vnodeSize = self.getWalsSize() - if vnodeSize < self.walSize: - tdLog.info(f" wal size valid. {self.path} real = {vnodeSize} set = {self.walSize} ") + # need over 20% + if vnodeSize < self.walSize * 1.2: + tdLog.info(f" wal size valid. {self.path} real = {vnodeSize} set = {self.walSize} need over 20%") return True - # check valid - tdLog.info(f" wal size over set. {self.path} real = {vnodeSize} set = {self.walSize} ") - for walFile in self.walFiles: - if self.canDelete(walFile): - # wait a moment then check file exist - time.sleep(1) - if os.path.exists(walFile.pathFile): - tdLog.exit(f" wal file size over .\ - \n wal file = {walFile.pathFile}\ - \n snapVer = {self.snapVer}\ - \n real = {vnodeSize} bytes\ - \n set = {self.walSize} bytes") - return False - return True + # check over + tdLog.exit(f" wal size over set. {self.path} real = {vnodeSize} set = {self.walSize} ") + return False # insert by async diff --git a/tools/shell/src/shellWebsocket.c b/tools/shell/src/shellWebsocket.c index af7f13c69c..791e2c36ff 100644 --- a/tools/shell/src/shellWebsocket.c +++ b/tools/shell/src/shellWebsocket.c @@ -278,7 +278,7 @@ void shellRunSingleCommandWebsocketImp(char *command) { } if (code == TSDB_CODE_WS_SEND_TIMEOUT || code == TSDB_CODE_WS_RECV_TIMEOUT) { - fprintf(stderr, "Hint: use -t to increase the timeout in seconds\n"); + fprintf(stderr, "Hint: use -T to increase the timeout in seconds\n"); } else if (code == TSDB_CODE_WS_INTERNAL_ERRO || code == TSDB_CODE_WS_CLOSED) { shell.ws_conn = NULL; From 2aa47dd19101faf46e9de8136710fc118756ecda Mon Sep 17 00:00:00 2001 From: Alex Duan <417921451@qq.com> Date: Wed, 23 Aug 2023 16:37:36 +0800 Subject: [PATCH 027/120] fix: walRetention modify info --- tests/system-test/0-others/walRetention.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/system-test/0-others/walRetention.py b/tests/system-test/0-others/walRetention.py index 44e64ae5e7..0fdeb84a5b 100644 --- a/tests/system-test/0-others/walRetention.py +++ b/tests/system-test/0-others/walRetention.py @@ -147,13 +147,13 @@ class VNode : if self.lastVer != -1 and ret: # first wal file ignore if walFile.startVer == self.firstVer: - tdLog.info(f" {walFile.pathFile} can del, but is first. snapVer={self.snapVer} firstVer={self.firstVer}") + tdLog.info(f" can del {walFile.pathFile}, but is first. snapVer={self.snapVer} firstVer={self.firstVer}") return False # ver in stay range smallVer = self.snapVer - self.walStayRange -1 if walFile.startVer >= smallVer: - tdLog.info(f" {walFile.pathFile} can del, but range not arrived. snapVer={self.snapVer} smallVer={smallVer}") + tdLog.info(f" can del {walFile.pathFile}, but range not arrived. snapVer={self.snapVer} smallVer={smallVer}") return False return ret @@ -165,14 +165,16 @@ class VNode : max = -1 for walFile in self.walFiles: if self.canDelete(walFile) == False: - tdLog.info(f" calc vnode size {walFile.pathFile} size={walFile.fsize}") + tdLog.info(f" calc vnode size {walFile.pathFile} size={walFile.fsize} startVer={walFile.startVer}") size += walFile.fsize if max < walFile.startVer: max = walFile.startVer lastSize = walFile.fsize - size -= lastSize - tdLog.info(f" last file size need reduct . lastSize={lastSize}") + + if lastSize > 0: + tdLog.info(f" last file size need reduct . lastSize={lastSize}") + size -= lastSize return size # vnode @@ -212,7 +214,7 @@ class VNode : vnodeSize = self.getWalsSize() # need over 20% if vnodeSize < self.walSize * 1.2: - tdLog.info(f" wal size valid. {self.path} real = {vnodeSize} set = {self.walSize} need over 20%") + tdLog.info(f" wal size valid. {self.path} real = {vnodeSize} set = {self.walSize}. allow over 20%.") return True # check over From 8fc8aad1b5b8b5ced999d94f42c8b7a5db564e69 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Wed, 23 Aug 2023 16:44:29 +0800 Subject: [PATCH 028/120] fix:[TD-258900]modify tmq trans conflict to db level --- source/dnode/mnode/impl/src/mndConsumer.c | 56 +------- source/dnode/mnode/impl/src/mndSubscribe.c | 17 +-- source/dnode/mnode/impl/src/mndTopic.c | 152 +++++++++++---------- 3 files changed, 87 insertions(+), 138 deletions(-) diff --git a/source/dnode/mnode/impl/src/mndConsumer.c b/source/dnode/mnode/impl/src/mndConsumer.c index 82492f930e..14344b52e7 100644 --- a/source/dnode/mnode/impl/src/mndConsumer.c +++ b/source/dnode/mnode/impl/src/mndConsumer.c @@ -37,7 +37,6 @@ static const char *mndConsumerStatusName(int status); static int32_t mndConsumerActionInsert(SSdb *pSdb, SMqConsumerObj *pConsumer); static int32_t mndConsumerActionDelete(SSdb *pSdb, SMqConsumerObj *pConsumer); static int32_t mndConsumerActionUpdate(SSdb *pSdb, SMqConsumerObj *pOldConsumer, SMqConsumerObj *pNewConsumer); -static int32_t mndProcessConsumerMetaMsg(SRpcMsg *pMsg); static int32_t mndRetrieveConsumer(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBlock, int32_t rows); static void mndCancelGetNextConsumer(SMnode *pMnode, void *pIter); @@ -45,7 +44,6 @@ static int32_t mndProcessSubscribeReq(SRpcMsg *pMsg); static int32_t mndProcessAskEpReq(SRpcMsg *pMsg); static int32_t mndProcessMqHbReq(SRpcMsg *pMsg); static int32_t mndProcessMqTimerMsg(SRpcMsg *pMsg); -static int32_t mndProcessConsumerLostMsg(SRpcMsg *pMsg); static int32_t mndProcessConsumerClearMsg(SRpcMsg *pMsg); static int32_t mndProcessConsumerRecoverMsg(SRpcMsg *pMsg); @@ -64,7 +62,6 @@ int32_t mndInitConsumer(SMnode *pMnode) { mndSetMsgHandle(pMnode, TDMT_MND_TMQ_HB, mndProcessMqHbReq); mndSetMsgHandle(pMnode, TDMT_MND_TMQ_ASK_EP, mndProcessAskEpReq); mndSetMsgHandle(pMnode, TDMT_MND_TMQ_TIMER, mndProcessMqTimerMsg); -// mndSetMsgHandle(pMnode, TDMT_MND_TMQ_CONSUMER_LOST, mndProcessConsumerLostMsg); mndSetMsgHandle(pMnode, TDMT_MND_TMQ_CONSUMER_RECOVER, mndProcessConsumerRecoverMsg); mndSetMsgHandle(pMnode, TDMT_MND_TMQ_LOST_CONSUMER_CLEAR, mndProcessConsumerClearMsg); @@ -122,49 +119,6 @@ void mndRebCntDec() { } } -//static int32_t mndProcessConsumerLostMsg(SRpcMsg *pMsg) { -// SMnode *pMnode = pMsg->info.node; -// SMqConsumerLostMsg *pLostMsg = pMsg->pCont; -// SMqConsumerObj *pConsumer = mndAcquireConsumer(pMnode, pLostMsg->consumerId); -// if (pConsumer == NULL) { -// return 0; -// } -// -// mInfo("process consumer lost msg, consumer:0x%" PRIx64 " status:%d(%s)", pLostMsg->consumerId, pConsumer->status, -// mndConsumerStatusName(pConsumer->status)); -// -// if (pConsumer->status != MQ_CONSUMER_STATUS_READY) { -// mndReleaseConsumer(pMnode, pConsumer); -// return -1; -// } -// -// SMqConsumerObj *pConsumerNew = tNewSMqConsumerObj(pConsumer->consumerId, pConsumer->cgroup); -// pConsumerNew->updateType = CONSUMER_UPDATE_TIMER_LOST; -// -// mndReleaseConsumer(pMnode, pConsumer); -// -// STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_NOTHING, pMsg, "lost-csm"); -// if (pTrans == NULL) { -// goto FAIL; -// } -// -// if (mndSetConsumerCommitLogs(pMnode, pTrans, pConsumerNew) != 0) { -// goto FAIL; -// } -// -// if (mndTransPrepare(pMnode, pTrans) != 0) { -// goto FAIL; -// } -// -// tDeleteSMqConsumerObj(pConsumerNew, true); -// mndTransDrop(pTrans); -// return 0; -//FAIL: -// tDeleteSMqConsumerObj(pConsumerNew, true); -// mndTransDrop(pTrans); -// return -1; -//} - static int32_t mndProcessConsumerRecoverMsg(SRpcMsg *pMsg) { SMnode *pMnode = pMsg->info.node; SMqConsumerRecoverMsg *pRecoverMsg = pMsg->pCont; @@ -221,13 +175,7 @@ static int32_t mndProcessConsumerClearMsg(SRpcMsg *pMsg) { mInfo("consumer:0x%" PRIx64 " needs to be cleared, status %s", pClearMsg->consumerId, mndConsumerStatusName(pConsumer->status)); -// if (pConsumer->status != MQ_CONSUMER_STATUS_LOST) { -// mndReleaseConsumer(pMnode, pConsumer); -// return -1; -// } - SMqConsumerObj *pConsumerNew = tNewSMqConsumerObj(pConsumer->consumerId, pConsumer->cgroup); -// pConsumerNew->updateType = CONSUMER_UPDATE_TIMER_LOST; mndReleaseConsumer(pMnode, pConsumer); @@ -629,7 +577,7 @@ int32_t mndSetConsumerCommitLogs(SMnode *pMnode, STrans *pTrans, SMqConsumerObj return 0; } -static int32_t validateTopics(const SArray *pTopicList, SMnode *pMnode, const char *pUser) { +static int32_t validateTopics(STrans *pTrans, const SArray *pTopicList, SMnode *pMnode, const char *pUser) { int32_t numOfTopics = taosArrayGetSize(pTopicList); for (int32_t i = 0; i < numOfTopics; i++) { @@ -722,7 +670,6 @@ int32_t mndProcessSubscribeReq(SRpcMsg *pMsg) { if (mndSetConsumerCommitLogs(pMnode, pTrans, pConsumerNew) != 0) goto _over; if (mndTransPrepare(pMnode, pTrans) != 0) goto _over; - } else { int32_t status = atomic_load_32(&pExistedConsumer->status); @@ -802,7 +749,6 @@ _over: tDeleteSMqConsumerObj(pConsumerNew, true); - // TODO: replace with destroy subscribe msg taosArrayDestroyP(subscribe.topicNames, (FDelete)taosMemoryFree); return code; } diff --git a/source/dnode/mnode/impl/src/mndSubscribe.c b/source/dnode/mnode/impl/src/mndSubscribe.c index 53f22f6e60..9579a18fc4 100644 --- a/source/dnode/mnode/impl/src/mndSubscribe.c +++ b/source/dnode/mnode/impl/src/mndSubscribe.c @@ -553,7 +553,7 @@ static int32_t mndPersistRebResult(SMnode *pMnode, SRpcMsg *pMsg, const SMqRebOu } } - STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB_INSIDE, pMsg, "tmq-reb"); + STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB, pMsg, "tmq-reb"); if (pTrans == NULL) { nodesDestroyNode((SNode*)pPlan); return -1; @@ -1019,8 +1019,8 @@ int32_t mndGetGroupNumByTopic(SMnode *pMnode, const char *topicName) { if (pIter == NULL) break; - char topic[TSDB_TOPIC_FNAME_LEN]; - char cgroup[TSDB_CGROUP_LEN]; + char topic[TSDB_TOPIC_FNAME_LEN] = {0}; + char cgroup[TSDB_CGROUP_LEN] = {0}; mndSplitSubscribeKey(pSub->key, topic, cgroup, true); if (strcmp(topic, topicName) != 0) { sdbRelease(pSdb, pSub); @@ -1084,7 +1084,6 @@ int32_t mndDropSubByDB(SMnode *pMnode, STrans *pTrans, SDbObj *pDb) { } int32_t mndDropSubByTopic(SMnode *pMnode, STrans *pTrans, const char *topicName) { - int32_t code = -1; SSdb *pSdb = pMnode->pSdb; void *pIter = NULL; @@ -1093,8 +1092,8 @@ int32_t mndDropSubByTopic(SMnode *pMnode, STrans *pTrans, const char *topicName) pIter = sdbFetch(pSdb, SDB_SUBSCRIBE, pIter, (void **)&pSub); if (pIter == NULL) break; - char topic[TSDB_TOPIC_FNAME_LEN]; - char cgroup[TSDB_CGROUP_LEN]; + char topic[TSDB_TOPIC_FNAME_LEN] = {0}; + char cgroup[TSDB_CGROUP_LEN] = {0}; mndSplitSubscribeKey(pSub->key, topic, cgroup, true); if (strcmp(topic, topicName) != 0) { sdbRelease(pSdb, pSub); @@ -1132,15 +1131,13 @@ int32_t mndDropSubByTopic(SMnode *pMnode, STrans *pTrans, const char *topicName) if (mndSetDropSubRedoLogs(pMnode, pTrans, pSub) < 0) { sdbRelease(pSdb, pSub); sdbCancelFetch(pSdb, pIter); - goto END; + return -1; } sdbRelease(pSdb, pSub); } - code = 0; -END: - return code; + return 0; } static int32_t buildResult(SSDataBlock *pBlock, int32_t* numOfRows, int64_t consumerId, const char* topic, const char* cgroup, SArray* vgs, SArray *offsetRows){ diff --git a/source/dnode/mnode/impl/src/mndTopic.c b/source/dnode/mnode/impl/src/mndTopic.c index 621a80338d..e7e631fcae 100644 --- a/source/dnode/mnode/impl/src/mndTopic.c +++ b/source/dnode/mnode/impl/src/mndTopic.c @@ -381,14 +381,26 @@ static int32_t mndCreateTopic(SMnode *pMnode, SRpcMsg *pReq, SCMCreateTopicReq * int32_t code = -1; SNode *pAst = NULL; SQueryPlan *pPlan = NULL; - SMqTopicObj topicObj = {0}; + + pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB, pReq, "create-topic"); + if (pTrans == NULL) { + mError("topic:%s, failed to create since %s", pCreate->name, terrstr()); + goto _OUT; + } + + mndTransSetDbName(pTrans, pDb->name, NULL); + if (mndTransCheckConflict(pMnode, pTrans) != 0) { + goto _OUT; + } + mInfo("trans:%d to create topic:%s", pTrans->id, pCreate->name); + tstrncpy(topicObj.name, pCreate->name, TSDB_TOPIC_FNAME_LEN); tstrncpy(topicObj.db, pDb->name, TSDB_DB_FNAME_LEN); tstrncpy(topicObj.createUser, userName, TSDB_USER_LEN); if (mndCheckTopicPrivilege(pMnode, pReq->info.conn.user, MND_OPER_CREATE_TOPIC, &topicObj) != 0) { - return -1; + goto _OUT; } topicObj.createTime = taosGetTimestampMs(); @@ -469,18 +481,6 @@ static int32_t mndCreateTopic(SMnode *pMnode, SRpcMsg *pReq, SCMCreateTopicReq * /*topicObj.withTbName = 1;*/ /*topicObj.withSchema = 1;*/ - pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB_INSIDE, pReq, "create-topic"); - if (pTrans == NULL) { - mError("topic:%s, failed to create since %s", pCreate->name, terrstr()); - goto _OUT; - } - - mndTransSetDbName(pTrans, pDb->name, NULL); - if (mndTransCheckConflict(pMnode, pTrans) != 0) { - goto _OUT; - } - mInfo("trans:%d to create topic:%s", pTrans->id, pCreate->name); - SSdbRaw *pCommitRaw = mndTopicActionEncode(&topicObj); if (pCommitRaw == NULL || mndTransAppendCommitlog(pTrans, pCommitRaw) != 0) { mError("trans:%d, failed to append commit log since %s", pTrans->id, terrstr()); @@ -654,30 +654,55 @@ _OVER: } static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { - SMnode *pMnode = pReq->info.node; - SSdb *pSdb = pMnode->pSdb; + SMnode *pMnode = pReq->info.node; + SSdb *pSdb = pMnode->pSdb; SMDropTopicReq dropReq = {0}; + int32_t code = 0; + SMqTopicObj *pTopic = NULL; + STrans *pTrans = NULL; if (tDeserializeSMDropTopicReq(pReq->pCont, pReq->contLen, &dropReq) != 0) { terrno = TSDB_CODE_INVALID_MSG; - return -1; + code = -1; + goto end; } - SMqTopicObj *pTopic = mndAcquireTopic(pMnode, dropReq.name); + pTopic = mndAcquireTopic(pMnode, dropReq.name); if (pTopic == NULL) { if (dropReq.igNotExists) { mInfo("topic:%s, not exist, ignore not exist is set", dropReq.name); - return 0; + goto end; } else { terrno = TSDB_CODE_MND_TOPIC_NOT_EXIST; mError("topic:%s, failed to drop since %s", dropReq.name, terrstr()); - return -1; + code = -1; + goto end; } } - if (mndCheckTopicPrivilege(pMnode, pReq->info.conn.user, MND_OPER_DROP_TOPIC, pTopic) != 0) { - mndReleaseTopic(pMnode, pTopic); - return -1; + pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB, pReq, "drop-topic"); + if (pTrans == NULL) { + mError("topic:%s, failed to drop since %s", pTopic->name, terrstr()); + code = -1; + goto end; + } + + mndTransSetDbName(pTrans, pTopic->db, NULL); + code = mndTransCheckConflict(pMnode, pTrans); + if (code != 0) { + goto end; + } + + mInfo("trans:%d, used to drop topic:%s", pTrans->id, pTopic->name); + + code = mndCheckTopicPrivilege(pMnode, pReq->info.conn.user, MND_OPER_DROP_TOPIC, pTopic); + if (code != 0) { + goto end; + } + + code = mndCheckDbPrivilegeByName(pMnode, pReq->info.conn.user, MND_OPER_READ_DB, pTopic->db); + if (code != 0) { + goto end; } void *pIter = NULL; @@ -688,37 +713,41 @@ static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { break; } - if (pConsumer->status == MQ_CONSUMER_STATUS_LOST){ - mndDropConsumerFromSdb(pMnode, pConsumer->consumerId); - mndReleaseConsumer(pMnode, pConsumer); - continue; - } - + bool found = false; int32_t sz = taosArrayGetSize(pConsumer->assignedTopics); for (int32_t i = 0; i < sz; i++) { char *name = taosArrayGetP(pConsumer->assignedTopics, i); if (strcmp(name, pTopic->name) == 0) { - mndReleaseConsumer(pMnode, pConsumer); - mndReleaseTopic(pMnode, pTopic); - sdbCancelFetch(pSdb, pIter); - terrno = TSDB_CODE_MND_TOPIC_SUBSCRIBED; - mError("topic:%s, failed to drop since subscribed by consumer:0x%" PRIx64 ", in consumer group %s", - dropReq.name, pConsumer->consumerId, pConsumer->cgroup); - return -1; + found = true; + break; } } + if (found){ + if (pConsumer->status == MQ_CONSUMER_STATUS_LOST) { + mndDropConsumerFromSdb(pMnode, pConsumer->consumerId); + continue; + } + + mndReleaseConsumer(pMnode, pConsumer); + sdbCancelFetch(pSdb, pIter); + terrno = TSDB_CODE_MND_TOPIC_SUBSCRIBED; + mError("topic:%s, failed to drop since subscribed by consumer:0x%" PRIx64 ", in consumer group %s", + dropReq.name, pConsumer->consumerId, pConsumer->cgroup); + code = -1; + goto end; + } sz = taosArrayGetSize(pConsumer->rebNewTopics); for (int32_t i = 0; i < sz; i++) { char *name = taosArrayGetP(pConsumer->rebNewTopics, i); if (strcmp(name, pTopic->name) == 0) { mndReleaseConsumer(pMnode, pConsumer); - mndReleaseTopic(pMnode, pTopic); sdbCancelFetch(pSdb, pIter); terrno = TSDB_CODE_MND_TOPIC_SUBSCRIBED; mError("topic:%s, failed to drop since subscribed by consumer:%" PRId64 ", in consumer group %s (reb new)", dropReq.name, pConsumer->consumerId, pConsumer->cgroup); - return -1; + code = -1; + goto end; } } @@ -727,45 +756,22 @@ static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { char *name = taosArrayGetP(pConsumer->rebRemovedTopics, i); if (strcmp(name, pTopic->name) == 0) { mndReleaseConsumer(pMnode, pConsumer); - mndReleaseTopic(pMnode, pTopic); sdbCancelFetch(pSdb, pIter); terrno = TSDB_CODE_MND_TOPIC_SUBSCRIBED; mError("topic:%s, failed to drop since subscribed by consumer:%" PRId64 ", in consumer group %s (reb remove)", dropReq.name, pConsumer->consumerId, pConsumer->cgroup); - return -1; + code = -1; + goto end; } } sdbRelease(pSdb, pConsumer); } - if (mndCheckDbPrivilegeByName(pMnode, pReq->info.conn.user, MND_OPER_READ_DB, pTopic->db) != 0) { - mndReleaseTopic(pMnode, pTopic); - return -1; - } - - STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB_INSIDE, pReq, "drop-topic"); - if (pTrans == NULL) { + code = mndDropSubByTopic(pMnode, pTrans, dropReq.name); + if ( code < 0) { mError("topic:%s, failed to drop since %s", pTopic->name, terrstr()); - mndReleaseTopic(pMnode, pTopic); - return -1; - } - - mndTransSetDbName(pTrans, pTopic->db, NULL); - if (mndTransCheckConflict(pMnode, pTrans) != 0) { - mndReleaseTopic(pMnode, pTopic); - mndTransDrop(pTrans); - return -1; - } - - mInfo("trans:%d, used to drop topic:%s", pTrans->id, pTopic->name); - - // TODO check if rebalancing - if (mndDropSubByTopic(pMnode, pTrans, dropReq.name) < 0) { - mError("topic:%s, failed to drop since %s", pTopic->name, terrstr()); - mndTransDrop(pTrans); - mndReleaseTopic(pMnode, pTopic); - return -1; + goto end; } if (pTopic->ntbUid != 0) { @@ -791,25 +797,25 @@ static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { action.pCont = buf; action.contLen = sizeof(SMsgHead) + TSDB_TOPIC_FNAME_LEN; action.msgType = TDMT_VND_TMQ_DEL_CHECKINFO; - if (mndTransAppendRedoAction(pTrans, &action) != 0) { + code = mndTransAppendRedoAction(pTrans, &action); + if (code != 0) { taosMemoryFree(buf); sdbRelease(pSdb, pVgroup); - mndReleaseTopic(pMnode, pTopic); sdbCancelFetch(pSdb, pIter); - mndTransDrop(pTrans); - return -1; + goto end; } sdbRelease(pSdb, pVgroup); } } - int32_t code = mndDropTopic(pMnode, pTrans, pReq, pTopic); + code = mndDropTopic(pMnode, pTrans, pReq, pTopic); + +end: mndReleaseTopic(pMnode, pTopic); mndTransDrop(pTrans); - if (code != 0) { mError("topic:%s, failed to drop since %s", dropReq.name, terrstr()); - return -1; + return code; } return TSDB_CODE_ACTION_IN_PROGRESS; From f19c1ea3fe1f9bfd711eabddb009ad6a1fed9103 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Wed, 23 Aug 2023 16:46:33 +0800 Subject: [PATCH 029/120] fix:[TD-258900]modify tmq trans conflict to db level --- source/dnode/mnode/impl/src/mndConsumer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/dnode/mnode/impl/src/mndConsumer.c b/source/dnode/mnode/impl/src/mndConsumer.c index 14344b52e7..d5b7342768 100644 --- a/source/dnode/mnode/impl/src/mndConsumer.c +++ b/source/dnode/mnode/impl/src/mndConsumer.c @@ -577,7 +577,7 @@ int32_t mndSetConsumerCommitLogs(SMnode *pMnode, STrans *pTrans, SMqConsumerObj return 0; } -static int32_t validateTopics(STrans *pTrans, const SArray *pTopicList, SMnode *pMnode, const char *pUser) { +static int32_t validateTopics(const SArray *pTopicList, SMnode *pMnode, const char *pUser) { int32_t numOfTopics = taosArrayGetSize(pTopicList); for (int32_t i = 0; i < numOfTopics; i++) { From 6877e33c8780faad28be232e493fa4f6907af690 Mon Sep 17 00:00:00 2001 From: Alex Duan <417921451@qq.com> Date: Wed, 23 Aug 2023 16:49:37 +0800 Subject: [PATCH 030/120] fix: query have error get time is last successful --- tools/shell/src/shellWebsocket.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/shell/src/shellWebsocket.c b/tools/shell/src/shellWebsocket.c index 791e2c36ff..ff2e5efdd4 100644 --- a/tools/shell/src/shellWebsocket.c +++ b/tools/shell/src/shellWebsocket.c @@ -373,8 +373,6 @@ void shellRunSingleCommandWebsocketImp(char *command) { } else { printf("Query interrupted, %d row(s) in set (%.6fs)\n", numOfRows, (et - st)/1E6); - printf("Execute: %.2f ms Network: %.2f ms Total: %.2f ms\n", - execute_time, net_time, total_time); } } printf("\n"); From 4591f2474617f12cd2c6d6f63eef7f08fd13ca6f Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Wed, 23 Aug 2023 19:41:02 +0800 Subject: [PATCH 031/120] fix:memory leak --- source/dnode/mnode/impl/src/mndTopic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source/dnode/mnode/impl/src/mndTopic.c b/source/dnode/mnode/impl/src/mndTopic.c index e7e631fcae..cb18c0bc65 100644 --- a/source/dnode/mnode/impl/src/mndTopic.c +++ b/source/dnode/mnode/impl/src/mndTopic.c @@ -725,6 +725,7 @@ static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { if (found){ if (pConsumer->status == MQ_CONSUMER_STATUS_LOST) { mndDropConsumerFromSdb(pMnode, pConsumer->consumerId); + mndReleaseConsumer(pMnode, pConsumer); continue; } From 962a0cd403ea08223dc10fefcb8f3a712296be0d Mon Sep 17 00:00:00 2001 From: Ganlin Zhao Date: Wed, 23 Aug 2023 23:34:02 +0800 Subject: [PATCH 032/120] fix --- include/os/osDir.h | 2 +- source/common/src/tglobal.c | 2 +- source/libs/stream/src/streamMeta.c | 4 ++-- source/libs/stream/src/streamState.c | 2 +- source/libs/tdb/src/db/tdbDb.c | 2 +- source/os/src/osDir.c | 7 +++++-- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/include/os/osDir.h b/include/os/osDir.h index 55c7a15764..2542f9830f 100644 --- a/include/os/osDir.h +++ b/include/os/osDir.h @@ -83,7 +83,7 @@ void taosRemoveDir(const char *dirname); bool taosDirExist(const char *dirname); int32_t taosMkDir(const char *dirname); int32_t taosMulMkDir(const char *dirname); -int32_t taosMulModeMkDir(const char *dirname, int mode); +int32_t taosMulModeMkDir(const char *dirname, int mode, bool createLogFile); void taosRemoveOldFiles(const char *dirname, int32_t keepDays); int32_t taosExpandDir(const char *dirname, char *outname, int32_t maxlen); int32_t taosRealPath(char *dirname, char *realPath, int32_t maxlen); diff --git a/source/common/src/tglobal.c b/source/common/src/tglobal.c index 5eeece9890..3edb70e63f 100644 --- a/source/common/src/tglobal.c +++ b/source/common/src/tglobal.c @@ -1434,7 +1434,7 @@ int32_t taosCreateLog(const char *logname, int32_t logFileNum, const char *cfgDi taosSetAllDebugFlag(cfgGetItem(pCfg, "debugFlag")->i32, false); - if (taosMulModeMkDir(tsLogDir, 0777) != 0) { + if (taosMulModeMkDir(tsLogDir, 0777, true) != 0) { terrno = TAOS_SYSTEM_ERROR(errno); printf("failed to create dir:%s since %s", tsLogDir, terrstr()); cfgCleanup(pCfg); diff --git a/source/libs/stream/src/streamMeta.c b/source/libs/stream/src/streamMeta.c index fe455c0190..61da055b25 100644 --- a/source/libs/stream/src/streamMeta.c +++ b/source/libs/stream/src/streamMeta.c @@ -52,7 +52,7 @@ SStreamMeta* streamMetaOpen(const char* path, void* ahandle, FTaskExpand expandF memset(streamPath, 0, len); sprintf(streamPath, "%s/%s", pMeta->path, "checkpoints"); - code = taosMulModeMkDir(streamPath, 0755); + code = taosMulModeMkDir(streamPath, 0755, false); if (code != 0) { terrno = TAOS_SYSTEM_ERROR(code); goto _err; @@ -90,7 +90,7 @@ SStreamMeta* streamMetaOpen(const char* path, void* ahandle, FTaskExpand expandF memset(streamPath, 0, len); sprintf(streamPath, "%s/%s", pMeta->path, "state"); - code = taosMulModeMkDir(streamPath, 0755); + code = taosMulModeMkDir(streamPath, 0755, false); if (code != 0) { terrno = TAOS_SYSTEM_ERROR(code); goto _err; diff --git a/source/libs/stream/src/streamState.c b/source/libs/stream/src/streamState.c index 5b42be182c..8694e5cf4c 100644 --- a/source/libs/stream/src/streamState.c +++ b/source/libs/stream/src/streamState.c @@ -169,7 +169,7 @@ SStreamState* streamStateOpen(char* path, void* pTask, bool specPath, int32_t sz sscanf(cfg, "%d\n%d\n", &szPage, &pages); } } else { - int32_t code = taosMulModeMkDir(statePath, 0755); + int32_t code = taosMulModeMkDir(statePath, 0755, false); if (code == 0) { pCfgFile = taosOpenFile(cfgPath, TD_FILE_WRITE | TD_FILE_CREATE); sprintf(cfg, "%d\n%d\n", szPage, pages); diff --git a/source/libs/tdb/src/db/tdbDb.c b/source/libs/tdb/src/db/tdbDb.c index 4f595d8d4a..81b306e65d 100644 --- a/source/libs/tdb/src/db/tdbDb.c +++ b/source/libs/tdb/src/db/tdbDb.c @@ -62,7 +62,7 @@ int32_t tdbOpen(const char *dbname, int32_t szPage, int32_t pages, TDB **ppDb, i } memset(pDb->pgrHash, 0, tsize); - ret = taosMulModeMkDir(dbname, 0755); + ret = taosMulModeMkDir(dbname, 0755, false); if (ret < 0) { return -1; } diff --git a/source/os/src/osDir.c b/source/os/src/osDir.c index 7e4058d508..e9f8c7f7e6 100644 --- a/source/os/src/osDir.c +++ b/source/os/src/osDir.c @@ -193,7 +193,7 @@ int32_t taosMulMkDir(const char *dirname) { return code; } -int32_t taosMulModeMkDir(const char *dirname, int mode) { +int32_t taosMulModeMkDir(const char *dirname, int mode, bool createLogFile) { if (dirname == NULL || strlen(dirname) >= TDDIRMAXLEN) return -1; char temp[TDDIRMAXLEN]; char *pos = temp; @@ -206,7 +206,10 @@ int32_t taosMulModeMkDir(const char *dirname, int mode) { #endif if (taosDirExist(temp)) { - if (taosCheckAccessFile(temp, TD_FILE_ACCESS_WRITE_OK)) { + if (createLogFile) { + if (!taosCheckAccessFile(temp, TD_FILE_ACCESS_EXIST_OK | TD_FILE_ACCESS_READ_OK | TD_FILE_ACCESS_WRITE_OK)) { + code = -1; + } return code; } else { return chmod(temp, mode); From 8f3956cc673ffca957616055b6929f69ff54c4d3 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Wed, 23 Aug 2023 16:44:29 +0800 Subject: [PATCH 033/120] fix:[TD-258900]modify tmq trans conflict to db level --- source/dnode/mnode/impl/src/mndConsumer.c | 56 +------- source/dnode/mnode/impl/src/mndSubscribe.c | 17 +-- source/dnode/mnode/impl/src/mndTopic.c | 152 +++++++++++---------- 3 files changed, 87 insertions(+), 138 deletions(-) diff --git a/source/dnode/mnode/impl/src/mndConsumer.c b/source/dnode/mnode/impl/src/mndConsumer.c index 82492f930e..14344b52e7 100644 --- a/source/dnode/mnode/impl/src/mndConsumer.c +++ b/source/dnode/mnode/impl/src/mndConsumer.c @@ -37,7 +37,6 @@ static const char *mndConsumerStatusName(int status); static int32_t mndConsumerActionInsert(SSdb *pSdb, SMqConsumerObj *pConsumer); static int32_t mndConsumerActionDelete(SSdb *pSdb, SMqConsumerObj *pConsumer); static int32_t mndConsumerActionUpdate(SSdb *pSdb, SMqConsumerObj *pOldConsumer, SMqConsumerObj *pNewConsumer); -static int32_t mndProcessConsumerMetaMsg(SRpcMsg *pMsg); static int32_t mndRetrieveConsumer(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBlock, int32_t rows); static void mndCancelGetNextConsumer(SMnode *pMnode, void *pIter); @@ -45,7 +44,6 @@ static int32_t mndProcessSubscribeReq(SRpcMsg *pMsg); static int32_t mndProcessAskEpReq(SRpcMsg *pMsg); static int32_t mndProcessMqHbReq(SRpcMsg *pMsg); static int32_t mndProcessMqTimerMsg(SRpcMsg *pMsg); -static int32_t mndProcessConsumerLostMsg(SRpcMsg *pMsg); static int32_t mndProcessConsumerClearMsg(SRpcMsg *pMsg); static int32_t mndProcessConsumerRecoverMsg(SRpcMsg *pMsg); @@ -64,7 +62,6 @@ int32_t mndInitConsumer(SMnode *pMnode) { mndSetMsgHandle(pMnode, TDMT_MND_TMQ_HB, mndProcessMqHbReq); mndSetMsgHandle(pMnode, TDMT_MND_TMQ_ASK_EP, mndProcessAskEpReq); mndSetMsgHandle(pMnode, TDMT_MND_TMQ_TIMER, mndProcessMqTimerMsg); -// mndSetMsgHandle(pMnode, TDMT_MND_TMQ_CONSUMER_LOST, mndProcessConsumerLostMsg); mndSetMsgHandle(pMnode, TDMT_MND_TMQ_CONSUMER_RECOVER, mndProcessConsumerRecoverMsg); mndSetMsgHandle(pMnode, TDMT_MND_TMQ_LOST_CONSUMER_CLEAR, mndProcessConsumerClearMsg); @@ -122,49 +119,6 @@ void mndRebCntDec() { } } -//static int32_t mndProcessConsumerLostMsg(SRpcMsg *pMsg) { -// SMnode *pMnode = pMsg->info.node; -// SMqConsumerLostMsg *pLostMsg = pMsg->pCont; -// SMqConsumerObj *pConsumer = mndAcquireConsumer(pMnode, pLostMsg->consumerId); -// if (pConsumer == NULL) { -// return 0; -// } -// -// mInfo("process consumer lost msg, consumer:0x%" PRIx64 " status:%d(%s)", pLostMsg->consumerId, pConsumer->status, -// mndConsumerStatusName(pConsumer->status)); -// -// if (pConsumer->status != MQ_CONSUMER_STATUS_READY) { -// mndReleaseConsumer(pMnode, pConsumer); -// return -1; -// } -// -// SMqConsumerObj *pConsumerNew = tNewSMqConsumerObj(pConsumer->consumerId, pConsumer->cgroup); -// pConsumerNew->updateType = CONSUMER_UPDATE_TIMER_LOST; -// -// mndReleaseConsumer(pMnode, pConsumer); -// -// STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_NOTHING, pMsg, "lost-csm"); -// if (pTrans == NULL) { -// goto FAIL; -// } -// -// if (mndSetConsumerCommitLogs(pMnode, pTrans, pConsumerNew) != 0) { -// goto FAIL; -// } -// -// if (mndTransPrepare(pMnode, pTrans) != 0) { -// goto FAIL; -// } -// -// tDeleteSMqConsumerObj(pConsumerNew, true); -// mndTransDrop(pTrans); -// return 0; -//FAIL: -// tDeleteSMqConsumerObj(pConsumerNew, true); -// mndTransDrop(pTrans); -// return -1; -//} - static int32_t mndProcessConsumerRecoverMsg(SRpcMsg *pMsg) { SMnode *pMnode = pMsg->info.node; SMqConsumerRecoverMsg *pRecoverMsg = pMsg->pCont; @@ -221,13 +175,7 @@ static int32_t mndProcessConsumerClearMsg(SRpcMsg *pMsg) { mInfo("consumer:0x%" PRIx64 " needs to be cleared, status %s", pClearMsg->consumerId, mndConsumerStatusName(pConsumer->status)); -// if (pConsumer->status != MQ_CONSUMER_STATUS_LOST) { -// mndReleaseConsumer(pMnode, pConsumer); -// return -1; -// } - SMqConsumerObj *pConsumerNew = tNewSMqConsumerObj(pConsumer->consumerId, pConsumer->cgroup); -// pConsumerNew->updateType = CONSUMER_UPDATE_TIMER_LOST; mndReleaseConsumer(pMnode, pConsumer); @@ -629,7 +577,7 @@ int32_t mndSetConsumerCommitLogs(SMnode *pMnode, STrans *pTrans, SMqConsumerObj return 0; } -static int32_t validateTopics(const SArray *pTopicList, SMnode *pMnode, const char *pUser) { +static int32_t validateTopics(STrans *pTrans, const SArray *pTopicList, SMnode *pMnode, const char *pUser) { int32_t numOfTopics = taosArrayGetSize(pTopicList); for (int32_t i = 0; i < numOfTopics; i++) { @@ -722,7 +670,6 @@ int32_t mndProcessSubscribeReq(SRpcMsg *pMsg) { if (mndSetConsumerCommitLogs(pMnode, pTrans, pConsumerNew) != 0) goto _over; if (mndTransPrepare(pMnode, pTrans) != 0) goto _over; - } else { int32_t status = atomic_load_32(&pExistedConsumer->status); @@ -802,7 +749,6 @@ _over: tDeleteSMqConsumerObj(pConsumerNew, true); - // TODO: replace with destroy subscribe msg taosArrayDestroyP(subscribe.topicNames, (FDelete)taosMemoryFree); return code; } diff --git a/source/dnode/mnode/impl/src/mndSubscribe.c b/source/dnode/mnode/impl/src/mndSubscribe.c index 53f22f6e60..9579a18fc4 100644 --- a/source/dnode/mnode/impl/src/mndSubscribe.c +++ b/source/dnode/mnode/impl/src/mndSubscribe.c @@ -553,7 +553,7 @@ static int32_t mndPersistRebResult(SMnode *pMnode, SRpcMsg *pMsg, const SMqRebOu } } - STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB_INSIDE, pMsg, "tmq-reb"); + STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB, pMsg, "tmq-reb"); if (pTrans == NULL) { nodesDestroyNode((SNode*)pPlan); return -1; @@ -1019,8 +1019,8 @@ int32_t mndGetGroupNumByTopic(SMnode *pMnode, const char *topicName) { if (pIter == NULL) break; - char topic[TSDB_TOPIC_FNAME_LEN]; - char cgroup[TSDB_CGROUP_LEN]; + char topic[TSDB_TOPIC_FNAME_LEN] = {0}; + char cgroup[TSDB_CGROUP_LEN] = {0}; mndSplitSubscribeKey(pSub->key, topic, cgroup, true); if (strcmp(topic, topicName) != 0) { sdbRelease(pSdb, pSub); @@ -1084,7 +1084,6 @@ int32_t mndDropSubByDB(SMnode *pMnode, STrans *pTrans, SDbObj *pDb) { } int32_t mndDropSubByTopic(SMnode *pMnode, STrans *pTrans, const char *topicName) { - int32_t code = -1; SSdb *pSdb = pMnode->pSdb; void *pIter = NULL; @@ -1093,8 +1092,8 @@ int32_t mndDropSubByTopic(SMnode *pMnode, STrans *pTrans, const char *topicName) pIter = sdbFetch(pSdb, SDB_SUBSCRIBE, pIter, (void **)&pSub); if (pIter == NULL) break; - char topic[TSDB_TOPIC_FNAME_LEN]; - char cgroup[TSDB_CGROUP_LEN]; + char topic[TSDB_TOPIC_FNAME_LEN] = {0}; + char cgroup[TSDB_CGROUP_LEN] = {0}; mndSplitSubscribeKey(pSub->key, topic, cgroup, true); if (strcmp(topic, topicName) != 0) { sdbRelease(pSdb, pSub); @@ -1132,15 +1131,13 @@ int32_t mndDropSubByTopic(SMnode *pMnode, STrans *pTrans, const char *topicName) if (mndSetDropSubRedoLogs(pMnode, pTrans, pSub) < 0) { sdbRelease(pSdb, pSub); sdbCancelFetch(pSdb, pIter); - goto END; + return -1; } sdbRelease(pSdb, pSub); } - code = 0; -END: - return code; + return 0; } static int32_t buildResult(SSDataBlock *pBlock, int32_t* numOfRows, int64_t consumerId, const char* topic, const char* cgroup, SArray* vgs, SArray *offsetRows){ diff --git a/source/dnode/mnode/impl/src/mndTopic.c b/source/dnode/mnode/impl/src/mndTopic.c index 621a80338d..e7e631fcae 100644 --- a/source/dnode/mnode/impl/src/mndTopic.c +++ b/source/dnode/mnode/impl/src/mndTopic.c @@ -381,14 +381,26 @@ static int32_t mndCreateTopic(SMnode *pMnode, SRpcMsg *pReq, SCMCreateTopicReq * int32_t code = -1; SNode *pAst = NULL; SQueryPlan *pPlan = NULL; - SMqTopicObj topicObj = {0}; + + pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB, pReq, "create-topic"); + if (pTrans == NULL) { + mError("topic:%s, failed to create since %s", pCreate->name, terrstr()); + goto _OUT; + } + + mndTransSetDbName(pTrans, pDb->name, NULL); + if (mndTransCheckConflict(pMnode, pTrans) != 0) { + goto _OUT; + } + mInfo("trans:%d to create topic:%s", pTrans->id, pCreate->name); + tstrncpy(topicObj.name, pCreate->name, TSDB_TOPIC_FNAME_LEN); tstrncpy(topicObj.db, pDb->name, TSDB_DB_FNAME_LEN); tstrncpy(topicObj.createUser, userName, TSDB_USER_LEN); if (mndCheckTopicPrivilege(pMnode, pReq->info.conn.user, MND_OPER_CREATE_TOPIC, &topicObj) != 0) { - return -1; + goto _OUT; } topicObj.createTime = taosGetTimestampMs(); @@ -469,18 +481,6 @@ static int32_t mndCreateTopic(SMnode *pMnode, SRpcMsg *pReq, SCMCreateTopicReq * /*topicObj.withTbName = 1;*/ /*topicObj.withSchema = 1;*/ - pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB_INSIDE, pReq, "create-topic"); - if (pTrans == NULL) { - mError("topic:%s, failed to create since %s", pCreate->name, terrstr()); - goto _OUT; - } - - mndTransSetDbName(pTrans, pDb->name, NULL); - if (mndTransCheckConflict(pMnode, pTrans) != 0) { - goto _OUT; - } - mInfo("trans:%d to create topic:%s", pTrans->id, pCreate->name); - SSdbRaw *pCommitRaw = mndTopicActionEncode(&topicObj); if (pCommitRaw == NULL || mndTransAppendCommitlog(pTrans, pCommitRaw) != 0) { mError("trans:%d, failed to append commit log since %s", pTrans->id, terrstr()); @@ -654,30 +654,55 @@ _OVER: } static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { - SMnode *pMnode = pReq->info.node; - SSdb *pSdb = pMnode->pSdb; + SMnode *pMnode = pReq->info.node; + SSdb *pSdb = pMnode->pSdb; SMDropTopicReq dropReq = {0}; + int32_t code = 0; + SMqTopicObj *pTopic = NULL; + STrans *pTrans = NULL; if (tDeserializeSMDropTopicReq(pReq->pCont, pReq->contLen, &dropReq) != 0) { terrno = TSDB_CODE_INVALID_MSG; - return -1; + code = -1; + goto end; } - SMqTopicObj *pTopic = mndAcquireTopic(pMnode, dropReq.name); + pTopic = mndAcquireTopic(pMnode, dropReq.name); if (pTopic == NULL) { if (dropReq.igNotExists) { mInfo("topic:%s, not exist, ignore not exist is set", dropReq.name); - return 0; + goto end; } else { terrno = TSDB_CODE_MND_TOPIC_NOT_EXIST; mError("topic:%s, failed to drop since %s", dropReq.name, terrstr()); - return -1; + code = -1; + goto end; } } - if (mndCheckTopicPrivilege(pMnode, pReq->info.conn.user, MND_OPER_DROP_TOPIC, pTopic) != 0) { - mndReleaseTopic(pMnode, pTopic); - return -1; + pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB, pReq, "drop-topic"); + if (pTrans == NULL) { + mError("topic:%s, failed to drop since %s", pTopic->name, terrstr()); + code = -1; + goto end; + } + + mndTransSetDbName(pTrans, pTopic->db, NULL); + code = mndTransCheckConflict(pMnode, pTrans); + if (code != 0) { + goto end; + } + + mInfo("trans:%d, used to drop topic:%s", pTrans->id, pTopic->name); + + code = mndCheckTopicPrivilege(pMnode, pReq->info.conn.user, MND_OPER_DROP_TOPIC, pTopic); + if (code != 0) { + goto end; + } + + code = mndCheckDbPrivilegeByName(pMnode, pReq->info.conn.user, MND_OPER_READ_DB, pTopic->db); + if (code != 0) { + goto end; } void *pIter = NULL; @@ -688,37 +713,41 @@ static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { break; } - if (pConsumer->status == MQ_CONSUMER_STATUS_LOST){ - mndDropConsumerFromSdb(pMnode, pConsumer->consumerId); - mndReleaseConsumer(pMnode, pConsumer); - continue; - } - + bool found = false; int32_t sz = taosArrayGetSize(pConsumer->assignedTopics); for (int32_t i = 0; i < sz; i++) { char *name = taosArrayGetP(pConsumer->assignedTopics, i); if (strcmp(name, pTopic->name) == 0) { - mndReleaseConsumer(pMnode, pConsumer); - mndReleaseTopic(pMnode, pTopic); - sdbCancelFetch(pSdb, pIter); - terrno = TSDB_CODE_MND_TOPIC_SUBSCRIBED; - mError("topic:%s, failed to drop since subscribed by consumer:0x%" PRIx64 ", in consumer group %s", - dropReq.name, pConsumer->consumerId, pConsumer->cgroup); - return -1; + found = true; + break; } } + if (found){ + if (pConsumer->status == MQ_CONSUMER_STATUS_LOST) { + mndDropConsumerFromSdb(pMnode, pConsumer->consumerId); + continue; + } + + mndReleaseConsumer(pMnode, pConsumer); + sdbCancelFetch(pSdb, pIter); + terrno = TSDB_CODE_MND_TOPIC_SUBSCRIBED; + mError("topic:%s, failed to drop since subscribed by consumer:0x%" PRIx64 ", in consumer group %s", + dropReq.name, pConsumer->consumerId, pConsumer->cgroup); + code = -1; + goto end; + } sz = taosArrayGetSize(pConsumer->rebNewTopics); for (int32_t i = 0; i < sz; i++) { char *name = taosArrayGetP(pConsumer->rebNewTopics, i); if (strcmp(name, pTopic->name) == 0) { mndReleaseConsumer(pMnode, pConsumer); - mndReleaseTopic(pMnode, pTopic); sdbCancelFetch(pSdb, pIter); terrno = TSDB_CODE_MND_TOPIC_SUBSCRIBED; mError("topic:%s, failed to drop since subscribed by consumer:%" PRId64 ", in consumer group %s (reb new)", dropReq.name, pConsumer->consumerId, pConsumer->cgroup); - return -1; + code = -1; + goto end; } } @@ -727,45 +756,22 @@ static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { char *name = taosArrayGetP(pConsumer->rebRemovedTopics, i); if (strcmp(name, pTopic->name) == 0) { mndReleaseConsumer(pMnode, pConsumer); - mndReleaseTopic(pMnode, pTopic); sdbCancelFetch(pSdb, pIter); terrno = TSDB_CODE_MND_TOPIC_SUBSCRIBED; mError("topic:%s, failed to drop since subscribed by consumer:%" PRId64 ", in consumer group %s (reb remove)", dropReq.name, pConsumer->consumerId, pConsumer->cgroup); - return -1; + code = -1; + goto end; } } sdbRelease(pSdb, pConsumer); } - if (mndCheckDbPrivilegeByName(pMnode, pReq->info.conn.user, MND_OPER_READ_DB, pTopic->db) != 0) { - mndReleaseTopic(pMnode, pTopic); - return -1; - } - - STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB_INSIDE, pReq, "drop-topic"); - if (pTrans == NULL) { + code = mndDropSubByTopic(pMnode, pTrans, dropReq.name); + if ( code < 0) { mError("topic:%s, failed to drop since %s", pTopic->name, terrstr()); - mndReleaseTopic(pMnode, pTopic); - return -1; - } - - mndTransSetDbName(pTrans, pTopic->db, NULL); - if (mndTransCheckConflict(pMnode, pTrans) != 0) { - mndReleaseTopic(pMnode, pTopic); - mndTransDrop(pTrans); - return -1; - } - - mInfo("trans:%d, used to drop topic:%s", pTrans->id, pTopic->name); - - // TODO check if rebalancing - if (mndDropSubByTopic(pMnode, pTrans, dropReq.name) < 0) { - mError("topic:%s, failed to drop since %s", pTopic->name, terrstr()); - mndTransDrop(pTrans); - mndReleaseTopic(pMnode, pTopic); - return -1; + goto end; } if (pTopic->ntbUid != 0) { @@ -791,25 +797,25 @@ static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { action.pCont = buf; action.contLen = sizeof(SMsgHead) + TSDB_TOPIC_FNAME_LEN; action.msgType = TDMT_VND_TMQ_DEL_CHECKINFO; - if (mndTransAppendRedoAction(pTrans, &action) != 0) { + code = mndTransAppendRedoAction(pTrans, &action); + if (code != 0) { taosMemoryFree(buf); sdbRelease(pSdb, pVgroup); - mndReleaseTopic(pMnode, pTopic); sdbCancelFetch(pSdb, pIter); - mndTransDrop(pTrans); - return -1; + goto end; } sdbRelease(pSdb, pVgroup); } } - int32_t code = mndDropTopic(pMnode, pTrans, pReq, pTopic); + code = mndDropTopic(pMnode, pTrans, pReq, pTopic); + +end: mndReleaseTopic(pMnode, pTopic); mndTransDrop(pTrans); - if (code != 0) { mError("topic:%s, failed to drop since %s", dropReq.name, terrstr()); - return -1; + return code; } return TSDB_CODE_ACTION_IN_PROGRESS; From 775f0668c5c57f5497a068b3d8e357096ee371cf Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Wed, 23 Aug 2023 16:46:33 +0800 Subject: [PATCH 034/120] fix:[TD-258900]modify tmq trans conflict to db level --- source/dnode/mnode/impl/src/mndConsumer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/dnode/mnode/impl/src/mndConsumer.c b/source/dnode/mnode/impl/src/mndConsumer.c index 14344b52e7..d5b7342768 100644 --- a/source/dnode/mnode/impl/src/mndConsumer.c +++ b/source/dnode/mnode/impl/src/mndConsumer.c @@ -577,7 +577,7 @@ int32_t mndSetConsumerCommitLogs(SMnode *pMnode, STrans *pTrans, SMqConsumerObj return 0; } -static int32_t validateTopics(STrans *pTrans, const SArray *pTopicList, SMnode *pMnode, const char *pUser) { +static int32_t validateTopics(const SArray *pTopicList, SMnode *pMnode, const char *pUser) { int32_t numOfTopics = taosArrayGetSize(pTopicList); for (int32_t i = 0; i < numOfTopics; i++) { From be74370e988fa625f56dacf469e0e408ac3d0a64 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Wed, 23 Aug 2023 19:41:02 +0800 Subject: [PATCH 035/120] fix:memory leak --- source/dnode/mnode/impl/src/mndTopic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source/dnode/mnode/impl/src/mndTopic.c b/source/dnode/mnode/impl/src/mndTopic.c index e7e631fcae..cb18c0bc65 100644 --- a/source/dnode/mnode/impl/src/mndTopic.c +++ b/source/dnode/mnode/impl/src/mndTopic.c @@ -725,6 +725,7 @@ static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { if (found){ if (pConsumer->status == MQ_CONSUMER_STATUS_LOST) { mndDropConsumerFromSdb(pMnode, pConsumer->consumerId); + mndReleaseConsumer(pMnode, pConsumer); continue; } From 7b9d14aad369f244fe6449e7bf65b4c4a4fa5afc Mon Sep 17 00:00:00 2001 From: Shuduo Sang Date: Thu, 24 Aug 2023 10:34:35 +0800 Subject: [PATCH 036/120] docs: add note to csharp connector docs (#22541) * docs: fix taos_init() return type * docs: refine c interface doc * docs: add platform note to csharp connector. --- docs/en/14-reference/03-connector/09-csharp.mdx | 4 ++++ docs/zh/08-connector/40-csharp.mdx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/en/14-reference/03-connector/09-csharp.mdx b/docs/en/14-reference/03-connector/09-csharp.mdx index 718462295a..203d44fe02 100644 --- a/docs/en/14-reference/03-connector/09-csharp.mdx +++ b/docs/en/14-reference/03-connector/09-csharp.mdx @@ -30,6 +30,10 @@ The source code of `TDengine.Connector` is hosted on [GitHub](https://github.com The supported platforms are the same as those supported by the TDengine client driver. +:::note +Please note TDengine does not support 32bit Windows any more. +::: + ## Version support Please refer to [version support list](/reference/connector#version-support) diff --git a/docs/zh/08-connector/40-csharp.mdx b/docs/zh/08-connector/40-csharp.mdx index 3a945e77fd..325c71da88 100644 --- a/docs/zh/08-connector/40-csharp.mdx +++ b/docs/zh/08-connector/40-csharp.mdx @@ -29,6 +29,10 @@ import CSAsyncQuery from "../07-develop/04-query-data/_cs_async.mdx" 支持的平台和 TDengine 客户端驱动支持的平台一致。 +:::note +注意 TDengine 不再支持 32 位 Windows 平台。 +::: + ## 版本支持 请参考[版本支持列表](../#版本支持) From ed70cd1f63fa876aa8c2523e348c262dd2883e40 Mon Sep 17 00:00:00 2001 From: huolibo Date: Thu, 24 Aug 2023 10:55:41 +0800 Subject: [PATCH 037/120] fix: jni error log --- source/client/src/clientTmqConnector.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/client/src/clientTmqConnector.c b/source/client/src/clientTmqConnector.c index 487a86a589..2bea738c23 100644 --- a/source/client/src/clientTmqConnector.c +++ b/source/client/src/clientTmqConnector.c @@ -590,7 +590,7 @@ JNIEXPORT jlong JNICALL Java_com_taosdata_jdbc_tmq_TMQConnector_tmqCommittedImp( int64_t offset = tmq_committed(tmq, topicName, vgId); - if (offset < JNI_SUCCESS) { + if (offset < JNI_SUCCESS && offset != -2147467247) { jniError("jobj:%p, tmq get committed offset error, topic:%s, vgId:%d, code:0x%" PRIx64 ", msg:%s", jobj, topicName, vgId, offset, tmq_err2str(offset)); } From 7ebce2814f365e721b9228d33dc974aeb06f5b5a Mon Sep 17 00:00:00 2001 From: liuyao <54liuyao@163.com> Date: Thu, 24 Aug 2023 11:18:10 +0800 Subject: [PATCH 038/120] mem leak --- source/libs/executor/src/filloperator.c | 1 + source/libs/executor/src/timewindowoperator.c | 3 +++ source/libs/stream/src/streamBackendRocksdb.c | 19 +++++++++++-------- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/source/libs/executor/src/filloperator.c b/source/libs/executor/src/filloperator.c index be4cb8d2dc..bf7da7505a 100644 --- a/source/libs/executor/src/filloperator.c +++ b/source/libs/executor/src/filloperator.c @@ -851,6 +851,7 @@ void setFillValueInfo(SSDataBlock* pBlock, TSKEY ts, int32_t rowId, SStreamFillS if (hasPrevWindow(pFillSup)) { setFillKeyInfo(prevWKey, ts, &pFillSup->interval, pFillInfo); pFillInfo->pos = FILL_POS_END; + resetFillWindow(&pFillSup->next); pFillSup->next.key = pFillSup->cur.key; pFillSup->next.pRowVal = pFillSup->cur.pRowVal; pFillInfo->preRowKey = INT64_MIN; diff --git a/source/libs/executor/src/timewindowoperator.c b/source/libs/executor/src/timewindowoperator.c index 37f737c2ce..16eaf0649d 100644 --- a/source/libs/executor/src/timewindowoperator.c +++ b/source/libs/executor/src/timewindowoperator.c @@ -2905,6 +2905,7 @@ void destroyStreamSessionAggOperatorInfo(void* param) { SStreamSessionAggOperatorInfo* pInfo = (SStreamSessionAggOperatorInfo*)param; cleanupBasicInfo(&pInfo->binfo); destroyStreamAggSupporter(&pInfo->streamAggSup); + cleanupExprSupp(&pInfo->scalarSupp); if (pInfo->pChildren != NULL) { int32_t size = taosArrayGetSize(pInfo->pChildren); @@ -4096,6 +4097,7 @@ void destroyStreamStateOperatorInfo(void* param) { cleanupBasicInfo(&pInfo->binfo); destroyStreamAggSupporter(&pInfo->streamAggSup); cleanupGroupResInfo(&pInfo->groupResInfo); + cleanupExprSupp(&pInfo->scalarSupp); if (pInfo->pChildren != NULL) { int32_t size = taosArrayGetSize(pInfo->pChildren); for (int32_t i = 0; i < size; i++) { @@ -4109,6 +4111,7 @@ void destroyStreamStateOperatorInfo(void* param) { taosArrayDestroy(pInfo->historyWins); tSimpleHashCleanup(pInfo->pSeUpdated); tSimpleHashCleanup(pInfo->pSeDeleted); + pInfo->pUpdated = taosArrayDestroy(pInfo->pUpdated); taosMemoryFreeClear(param); } diff --git a/source/libs/stream/src/streamBackendRocksdb.c b/source/libs/stream/src/streamBackendRocksdb.c index 4a0ce81e68..1981cd76b3 100644 --- a/source/libs/stream/src/streamBackendRocksdb.c +++ b/source/libs/stream/src/streamBackendRocksdb.c @@ -1620,19 +1620,22 @@ int32_t streamStateSessionGetKVByCur_rocksdb(SStreamStateCur* pCur, SSessionKey* if (len < 0) { return -1; } + + if (pVLen != NULL) *pVLen = len; + + if (pKTmp->opNum != pCur->number) { + taosMemoryFree(val); + return -1; + } + if (pKey->groupId != 0 && pKey->groupId != pKTmp->key.groupId) { + taosMemoryFree(val); + return -1; + } if (pVal != NULL) { *pVal = (char*)val; } else { taosMemoryFree(val); } - if (pVLen != NULL) *pVLen = len; - - if (pKTmp->opNum != pCur->number) { - return -1; - } - if (pKey->groupId != 0 && pKey->groupId != pKTmp->key.groupId) { - return -1; - } *pKey = pKTmp->key; return 0; } From 1e108c4178bd6cc4051e2715c9d063b90b8da4ca Mon Sep 17 00:00:00 2001 From: liuyao <54liuyao@163.com> Date: Thu, 24 Aug 2023 11:34:43 +0800 Subject: [PATCH 039/120] mem leak --- source/libs/stream/src/streamBackendRocksdb.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/source/libs/stream/src/streamBackendRocksdb.c b/source/libs/stream/src/streamBackendRocksdb.c index 1981cd76b3..571aca9935 100644 --- a/source/libs/stream/src/streamBackendRocksdb.c +++ b/source/libs/stream/src/streamBackendRocksdb.c @@ -1613,6 +1613,9 @@ int32_t streamStateSessionGetKVByCur_rocksdb(SStreamStateCur* pCur, SSessionKey* const char* curKey = rocksdb_iter_key(pCur->iter, (size_t*)&kLen); stateSessionKeyDecode((void*)&ktmp, (char*)curKey); + if (pVal != NULL) *pVal = NULL; + if (pVLen != NULL) *pVLen = 0; + SStateSessionKey* pKTmp = &ktmp; const char* vval = rocksdb_iter_value(pCur->iter, (size_t*)&vLen); char* val = NULL; @@ -1621,8 +1624,6 @@ int32_t streamStateSessionGetKVByCur_rocksdb(SStreamStateCur* pCur, SSessionKey* return -1; } - if (pVLen != NULL) *pVLen = len; - if (pKTmp->opNum != pCur->number) { taosMemoryFree(val); return -1; @@ -1631,11 +1632,14 @@ int32_t streamStateSessionGetKVByCur_rocksdb(SStreamStateCur* pCur, SSessionKey* taosMemoryFree(val); return -1; } + if (pVal != NULL) { *pVal = (char*)val; } else { taosMemoryFree(val); } + + if (pVLen != NULL) *pVLen = len; *pKey = pKTmp->key; return 0; } From 3c8bd57140463a8faf45db4c83fd0b3840c4ad42 Mon Sep 17 00:00:00 2001 From: wangjiaming0909 <604227650@qq.com> Date: Wed, 23 Aug 2023 14:54:41 +0800 Subject: [PATCH 040/120] fix: fill operator with desc ts order returned 1 more rows --- source/libs/executor/src/filloperator.c | 1 + tests/system-test/2-query/fill_with_group.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/source/libs/executor/src/filloperator.c b/source/libs/executor/src/filloperator.c index be4cb8d2dc..e8bdd37616 100644 --- a/source/libs/executor/src/filloperator.c +++ b/source/libs/executor/src/filloperator.c @@ -177,6 +177,7 @@ static void revisedFillStartKey(SFillOperatorInfo* pInfo, SSDataBlock* pBlock, i } // todo time window chosen problem: t or prev value? + if (t > pInfo->pFillInfo->start) t -= pInterval->sliding; taosFillUpdateStartTimestampInfo(pInfo->pFillInfo, t); } } diff --git a/tests/system-test/2-query/fill_with_group.py b/tests/system-test/2-query/fill_with_group.py index c1ea9877a2..393102c8ed 100644 --- a/tests/system-test/2-query/fill_with_group.py +++ b/tests/system-test/2-query/fill_with_group.py @@ -130,9 +130,18 @@ class TDTestCase: for j in range(0,60): tdSql.checkData(i*1500+j, 1, None) + def test_fill_with_order_by(self): + sql = "select _wstart, _wend, count(ts), sum(c1) from meters where ts > '2018-11-25 00:00:00.000' and ts < '2018-11-26 00:00:00.00' interval(1d) fill(NULL) order by _wstart" + tdSql.query(sql) + tdSql.checkRows(1) + sql = "select _wstart, _wend, count(ts), sum(c1) from meters where ts > '2018-11-25 00:00:00.000' and ts < '2018-11-26 00:00:00.00' interval(1d) fill(NULL) order by _wstart desc" + tdSql.query(sql) + tdSql.checkRows(1) + def run(self): self.prepareTestEnv() self.test_partition_by_with_interval_fill_prev_new_group_fill_error() + self.test_fill_with_order_by() def stop(self): tdSql.close() From 13d2d72bb61a0d504cb7738241dc802a1597275d Mon Sep 17 00:00:00 2001 From: Ganlin Zhao Date: Thu, 24 Aug 2023 14:56:14 +0800 Subject: [PATCH 041/120] add seperate function for createLogs --- include/os/osDir.h | 3 +- source/common/src/tglobal.c | 2 +- source/libs/stream/src/streamMeta.c | 4 +- source/libs/stream/src/streamState.c | 2 +- source/libs/tdb/src/db/tdbDb.c | 2 +- source/os/src/osDir.c | 71 +++++++++++++++++++++++++--- 6 files changed, 71 insertions(+), 13 deletions(-) diff --git a/include/os/osDir.h b/include/os/osDir.h index 2542f9830f..e722adcdcc 100644 --- a/include/os/osDir.h +++ b/include/os/osDir.h @@ -83,7 +83,8 @@ void taosRemoveDir(const char *dirname); bool taosDirExist(const char *dirname); int32_t taosMkDir(const char *dirname); int32_t taosMulMkDir(const char *dirname); -int32_t taosMulModeMkDir(const char *dirname, int mode, bool createLogFile); +int32_t taosMulModeMkDir(const char *dirname, int mode); +int32_t taosMulModeMkLogDir(const char *dirname, int mode); void taosRemoveOldFiles(const char *dirname, int32_t keepDays); int32_t taosExpandDir(const char *dirname, char *outname, int32_t maxlen); int32_t taosRealPath(char *dirname, char *realPath, int32_t maxlen); diff --git a/source/common/src/tglobal.c b/source/common/src/tglobal.c index 3edb70e63f..611b88bc9d 100644 --- a/source/common/src/tglobal.c +++ b/source/common/src/tglobal.c @@ -1434,7 +1434,7 @@ int32_t taosCreateLog(const char *logname, int32_t logFileNum, const char *cfgDi taosSetAllDebugFlag(cfgGetItem(pCfg, "debugFlag")->i32, false); - if (taosMulModeMkDir(tsLogDir, 0777, true) != 0) { + if (taosMulModeMkLogDir(tsLogDir, 0777) != 0) { terrno = TAOS_SYSTEM_ERROR(errno); printf("failed to create dir:%s since %s", tsLogDir, terrstr()); cfgCleanup(pCfg); diff --git a/source/libs/stream/src/streamMeta.c b/source/libs/stream/src/streamMeta.c index 61da055b25..fe455c0190 100644 --- a/source/libs/stream/src/streamMeta.c +++ b/source/libs/stream/src/streamMeta.c @@ -52,7 +52,7 @@ SStreamMeta* streamMetaOpen(const char* path, void* ahandle, FTaskExpand expandF memset(streamPath, 0, len); sprintf(streamPath, "%s/%s", pMeta->path, "checkpoints"); - code = taosMulModeMkDir(streamPath, 0755, false); + code = taosMulModeMkDir(streamPath, 0755); if (code != 0) { terrno = TAOS_SYSTEM_ERROR(code); goto _err; @@ -90,7 +90,7 @@ SStreamMeta* streamMetaOpen(const char* path, void* ahandle, FTaskExpand expandF memset(streamPath, 0, len); sprintf(streamPath, "%s/%s", pMeta->path, "state"); - code = taosMulModeMkDir(streamPath, 0755, false); + code = taosMulModeMkDir(streamPath, 0755); if (code != 0) { terrno = TAOS_SYSTEM_ERROR(code); goto _err; diff --git a/source/libs/stream/src/streamState.c b/source/libs/stream/src/streamState.c index 8694e5cf4c..5b42be182c 100644 --- a/source/libs/stream/src/streamState.c +++ b/source/libs/stream/src/streamState.c @@ -169,7 +169,7 @@ SStreamState* streamStateOpen(char* path, void* pTask, bool specPath, int32_t sz sscanf(cfg, "%d\n%d\n", &szPage, &pages); } } else { - int32_t code = taosMulModeMkDir(statePath, 0755, false); + int32_t code = taosMulModeMkDir(statePath, 0755); if (code == 0) { pCfgFile = taosOpenFile(cfgPath, TD_FILE_WRITE | TD_FILE_CREATE); sprintf(cfg, "%d\n%d\n", szPage, pages); diff --git a/source/libs/tdb/src/db/tdbDb.c b/source/libs/tdb/src/db/tdbDb.c index 81b306e65d..4f595d8d4a 100644 --- a/source/libs/tdb/src/db/tdbDb.c +++ b/source/libs/tdb/src/db/tdbDb.c @@ -62,7 +62,7 @@ int32_t tdbOpen(const char *dbname, int32_t szPage, int32_t pages, TDB **ppDb, i } memset(pDb->pgrHash, 0, tsize); - ret = taosMulModeMkDir(dbname, 0755, false); + ret = taosMulModeMkDir(dbname, 0755); if (ret < 0) { return -1; } diff --git a/source/os/src/osDir.c b/source/os/src/osDir.c index e9f8c7f7e6..dff0cf9886 100644 --- a/source/os/src/osDir.c +++ b/source/os/src/osDir.c @@ -193,7 +193,7 @@ int32_t taosMulMkDir(const char *dirname) { return code; } -int32_t taosMulModeMkDir(const char *dirname, int mode, bool createLogFile) { +int32_t taosMulModeMkDir(const char *dirname, int mode) { if (dirname == NULL || strlen(dirname) >= TDDIRMAXLEN) return -1; char temp[TDDIRMAXLEN]; char *pos = temp; @@ -206,16 +206,73 @@ int32_t taosMulModeMkDir(const char *dirname, int mode, bool createLogFile) { #endif if (taosDirExist(temp)) { - if (createLogFile) { - if (!taosCheckAccessFile(temp, TD_FILE_ACCESS_EXIST_OK | TD_FILE_ACCESS_READ_OK | TD_FILE_ACCESS_WRITE_OK)) { - code = -1; + return chmod(temp, mode); + } + + if (strncmp(temp, TD_DIRSEP, 1) == 0) { + pos += 1; + } else if (strncmp(temp, "." TD_DIRSEP, 2) == 0) { + pos += 2; + } + + for (; *pos != '\0'; pos++) { + if (*pos == TD_DIRSEP[0]) { + *pos = '\0'; +#ifdef WINDOWS + code = _mkdir(temp, mode); +#elif defined(DARWIN) + code = mkdir(dirname, 0777); +#else + code = mkdir(temp, mode); +#endif + if (code < 0 && errno != EEXIST) { + // terrno = TAOS_SYSTEM_ERROR(errno); + return code; } - return code; - } else { - return chmod(temp, mode); + *pos = TD_DIRSEP[0]; } } + if (*(pos - 1) != TD_DIRSEP[0]) { +#ifdef WINDOWS + code = _mkdir(temp, mode); +#elif defined(DARWIN) + code = mkdir(dirname, 0777); +#else + code = mkdir(temp, mode); +#endif + if (code < 0 && errno != EEXIST) { + // terrno = TAOS_SYSTEM_ERROR(errno); + return code; + } + } + + if (code < 0 && errno == EEXIST) { + return chmod(temp, mode); + } + + return chmod(temp, mode); +} + +int32_t taosMulModeMkLogDir(const char *dirname, int mode) { + if (dirname == NULL || strlen(dirname) >= TDDIRMAXLEN) return -1; + char temp[TDDIRMAXLEN]; + char *pos = temp; + int32_t code = 0; +#ifdef WINDOWS + taosRealPath(dirname, temp, sizeof(temp)); + if (temp[1] == ':') pos += 3; +#else + strcpy(temp, dirname); +#endif + + if (taosDirExist(temp)) { + if (!taosCheckAccessFile(temp, TD_FILE_ACCESS_EXIST_OK | TD_FILE_ACCESS_READ_OK | TD_FILE_ACCESS_WRITE_OK)) { + code = -1; + } + return code; + } + if (strncmp(temp, TD_DIRSEP, 1) == 0) { pos += 1; } else if (strncmp(temp, "." TD_DIRSEP, 2) == 0) { From b142979f7fd190c5851d47bd03e0a8c714c6e261 Mon Sep 17 00:00:00 2001 From: liuyao <54liuyao@163.com> Date: Thu, 24 Aug 2023 15:00:09 +0800 Subject: [PATCH 042/120] mem leak --- source/common/src/tdatablock.c | 3 ++- source/libs/executor/src/filloperator.c | 5 ++--- source/libs/executor/src/groupoperator.c | 3 ++- source/libs/executor/src/projectoperator.c | 4 ++++ source/libs/executor/src/scanoperator.c | 3 ++- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/source/common/src/tdatablock.c b/source/common/src/tdatablock.c index 5188b1e27c..f6fce452ed 100644 --- a/source/common/src/tdatablock.c +++ b/source/common/src/tdatablock.c @@ -1340,8 +1340,9 @@ SSDataBlock* blockCopyOneRow(const SSDataBlock* pDataBlock, int32_t rowIdx) { for (int32_t i = 0; i < numOfCols; ++i) { SColumnInfoData* pDst = taosArrayGet(pBlock->pDataBlock, i); SColumnInfoData* pSrc = taosArrayGet(pDataBlock->pDataBlock, i); - void* pData = colDataGetData(pSrc, rowIdx); bool isNull = colDataIsNull(pSrc, pDataBlock->info.rows, rowIdx, NULL); + void* pData = NULL; + if (!isNull) pData = colDataGetData(pSrc, rowIdx); colDataSetVal(pDst, 0, pData, isNull); } diff --git a/source/libs/executor/src/filloperator.c b/source/libs/executor/src/filloperator.c index bf7da7505a..7dbdb547d3 100644 --- a/source/libs/executor/src/filloperator.c +++ b/source/libs/executor/src/filloperator.c @@ -838,6 +838,7 @@ void setFillValueInfo(SSDataBlock* pBlock, TSKEY ts, int32_t rowId, SStreamFillS (pFillSup->next.key == pFillInfo->nextRowKey && !hasPrevWindow(pFillSup)))) { setFillKeyInfo(ts, nextWKey, &pFillSup->interval, pFillInfo); pFillInfo->pos = FILL_POS_START; + resetFillWindow(&pFillSup->prev); pFillSup->prev.key = pFillSup->cur.key; pFillSup->prev.pRowVal = pFillSup->cur.pRowVal; } else if (hasPrevWindow(pFillSup)) { @@ -1230,8 +1231,6 @@ static void doDeleteFillResult(SOperatorInfo* pOperator) { SWinKey nextKey = {.groupId = groupId, .ts = ts}; while (pInfo->srcDelRowIndex < pBlock->info.rows) { - void* nextVal = NULL; - int32_t nextLen = 0; TSKEY delTs = tsStarts[pInfo->srcDelRowIndex]; uint64_t delGroupId = groupIds[pInfo->srcDelRowIndex]; int32_t code = TSDB_CODE_SUCCESS; @@ -1246,7 +1245,7 @@ static void doDeleteFillResult(SOperatorInfo* pOperator) { if (delTs == nextKey.ts) { code = pAPI->stateStore.streamStateCurNext(pOperator->pTaskInfo->streamInfo.pState, pCur); if (code == TSDB_CODE_SUCCESS) { - code = pAPI->stateStore.streamStateGetGroupKVByCur(pCur, &nextKey, (const void**)&nextVal, &nextLen); + code = pAPI->stateStore.streamStateGetGroupKVByCur(pCur, &nextKey, NULL, NULL); } // ts will be deleted later if (delTs != ts) { diff --git a/source/libs/executor/src/groupoperator.c b/source/libs/executor/src/groupoperator.c index 9228c923a6..7d0fafff73 100644 --- a/source/libs/executor/src/groupoperator.c +++ b/source/libs/executor/src/groupoperator.c @@ -972,7 +972,8 @@ static SSDataBlock* buildStreamPartitionResult(SOperatorInfo* pOperator) { SColumnInfoData* pSrcCol = taosArrayGet(pSrc->pDataBlock, slotId); SColumnInfoData* pDestCol = taosArrayGet(pDest->pDataBlock, j); bool isNull = colDataIsNull(pSrcCol, pSrc->info.rows, rowIndex, NULL); - char* pSrcData = colDataGetData(pSrcCol, rowIndex); + char* pSrcData = NULL; + if (!isNull) pSrcData = colDataGetData(pSrcCol, rowIndex); colDataSetVal(pDestCol, pDest->info.rows, pSrcData, isNull); } pDest->info.rows++; diff --git a/source/libs/executor/src/projectoperator.c b/source/libs/executor/src/projectoperator.c index 1cc377b3ee..7266fb461d 100644 --- a/source/libs/executor/src/projectoperator.c +++ b/source/libs/executor/src/projectoperator.c @@ -372,6 +372,10 @@ SSDataBlock* doProjectOperation(SOperatorInfo* pOperator) { pOperator->cost.openCost = (taosGetTimestampUs() - st) / 1000.0; } + if (pTaskInfo->execModel == OPTR_EXEC_MODEL_STREAM) { + printDataBlock(p, "project"); + } + return (p->info.rows > 0) ? p : NULL; } diff --git a/source/libs/executor/src/scanoperator.c b/source/libs/executor/src/scanoperator.c index da4bd1e23c..af1740750c 100644 --- a/source/libs/executor/src/scanoperator.c +++ b/source/libs/executor/src/scanoperator.c @@ -1247,7 +1247,8 @@ static SSDataBlock* doRangeScan(SStreamScanInfo* pInfo, SSDataBlock* pSDB, int32 SColumnInfoData* pSrcCol = taosArrayGet(tmpBlock->pDataBlock, j); SColumnInfoData* pDestCol = taosArrayGet(pResult->pDataBlock, j); bool isNull = colDataIsNull(pSrcCol, tmpBlock->info.rows, i, NULL); - char* pSrcData = colDataGetData(pSrcCol, i); + char* pSrcData = NULL; + if (!isNull) pSrcData = colDataGetData(pSrcCol, i); colDataSetVal(pDestCol, pResult->info.rows, pSrcData, isNull); } pResult->info.rows++; From 4c6bc4d2c3f33686f138d75101371191cf405f5e Mon Sep 17 00:00:00 2001 From: Haojun Liao Date: Thu, 24 Aug 2023 16:10:03 +0800 Subject: [PATCH 043/120] fix(stream): continue check wal when meeting empty delete block msg. --- source/dnode/vnode/src/tq/tqRead.c | 107 +++++++++++++++++------------ 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/source/dnode/vnode/src/tq/tqRead.c b/source/dnode/vnode/src/tq/tqRead.c index 43f38ade97..d3157dc3b0 100644 --- a/source/dnode/vnode/src/tq/tqRead.c +++ b/source/dnode/vnode/src/tq/tqRead.c @@ -296,53 +296,70 @@ int32_t tqReaderSeek(STqReader* pReader, int64_t ver, const char* id) { } int32_t extractMsgFromWal(SWalReader* pReader, void** pItem, int64_t maxVer, const char* id) { - int32_t code = walNextValidMsg(pReader); - if (code != TSDB_CODE_SUCCESS) { + int32_t code = 0; + SWalCont* pCont = &pReader->pHead->head; + + while(1) { + code = walNextValidMsg(pReader); + if (code != TSDB_CODE_SUCCESS) { + return code; + } + + int64_t ver = pCont->version; + if (ver > maxVer) { + tqDebug("maxVer in WAL:%" PRId64 " reached current:%" PRId64 ", do not scan wal anymore, %s", maxVer, ver, id); + return TSDB_CODE_SUCCESS; + } + + if (pCont->msgType == TDMT_VND_SUBMIT) { + void* pBody = POINTER_SHIFT(pCont->body, sizeof(SSubmitReq2Msg)); + int32_t len = pCont->bodyLen - sizeof(SSubmitReq2Msg); + + void* data = taosMemoryMalloc(len); + if (data == NULL) { + // todo: for all stream in this vnode, keep this offset in the offset files, and wait for a moment, and then retry + code = TSDB_CODE_OUT_OF_MEMORY; + terrno = code; + + tqError("vgId:%d, failed to copy submit data for stream processing, since out of memory", 0); + return code; + } + + memcpy(data, pBody, len); + SPackedData data1 = (SPackedData){.ver = ver, .msgLen = len, .msgStr = data}; + + *pItem = (SStreamQueueItem*)streamDataSubmitNew(&data1, STREAM_INPUT__DATA_SUBMIT); + if (*pItem == NULL) { + code = TSDB_CODE_OUT_OF_MEMORY; + terrno = code; + tqError("%s failed to create data submit for stream since out of memory", id); + return code; + } + } else if (pCont->msgType == TDMT_VND_DELETE) { + void* pBody = POINTER_SHIFT(pCont->body, sizeof(SMsgHead)); + int32_t len = pCont->bodyLen - sizeof(SMsgHead); + + code = extractDelDataBlock(pBody, len, ver, (SStreamRefDataBlock**)pItem); + if (code == TSDB_CODE_SUCCESS) { + if (*pItem == NULL) { + tqDebug("s-task:%s empty delete msg, discard it, len:%d, ver:%" PRId64, id, len, ver); + // we need to continue check next data in the wal files. + continue; + } else { + tqDebug("s-task:%s delete msg extract from WAL, len:%d, ver:%" PRId64, id, len, ver); + } + } else { + terrno = code; + tqError("s-task:%s extract delete msg from WAL failed, code:%s", id, tstrerror(code)); + return code; + } + + } else { + ASSERT(0); + } + return code; } - - int64_t ver = pReader->pHead->head.version; - if (ver > maxVer) { - tqDebug("maxVer in WAL:%"PRId64" reached current:%"PRId64", do not scan wal anymore, %s", maxVer, ver, id); - return TSDB_CODE_SUCCESS; - } - - if (pReader->pHead->head.msgType == TDMT_VND_SUBMIT) { - void* pBody = POINTER_SHIFT(pReader->pHead->head.body, sizeof(SSubmitReq2Msg)); - int32_t len = pReader->pHead->head.bodyLen - sizeof(SSubmitReq2Msg); - - void* data = taosMemoryMalloc(len); - if (data == NULL) { - // todo: for all stream in this vnode, keep this offset in the offset files, and wait for a moment, and then retry - terrno = TSDB_CODE_OUT_OF_MEMORY; - tqError("vgId:%d, failed to copy submit data for stream processing, since out of memory", 0); - return -1; - } - - memcpy(data, pBody, len); - SPackedData data1 = (SPackedData){.ver = ver, .msgLen = len, .msgStr = data}; - - *pItem = (SStreamQueueItem*)streamDataSubmitNew(&data1, STREAM_INPUT__DATA_SUBMIT); - if (*pItem == NULL) { - terrno = TSDB_CODE_OUT_OF_MEMORY; - tqError("%s failed to create data submit for stream since out of memory", id); - return terrno; - } - } else if (pReader->pHead->head.msgType == TDMT_VND_DELETE) { - void* pBody = POINTER_SHIFT(pReader->pHead->head.body, sizeof(SMsgHead)); - int32_t len = pReader->pHead->head.bodyLen - sizeof(SMsgHead); - - code = extractDelDataBlock(pBody, len, ver, (SStreamRefDataBlock**)pItem); - if (code != TSDB_CODE_SUCCESS) { - tqError("s-task:%s extract delete msg from WAL failed, code:%s", id, tstrerror(code)); - } else { - tqDebug("s-task:%s delete msg extract from WAL, len:%d, ver:%"PRId64, id, len, ver); - } - } else { - ASSERT(0); - } - - return 0; } // todo ignore the error in wal? From 5344efe181f22160a3993051757f9a90aa3c9e8a Mon Sep 17 00:00:00 2001 From: Haojun Liao Date: Thu, 24 Aug 2023 16:22:24 +0800 Subject: [PATCH 044/120] fix(stream): adjust the ptr. --- source/dnode/vnode/src/tq/tqRead.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/dnode/vnode/src/tq/tqRead.c b/source/dnode/vnode/src/tq/tqRead.c index d3157dc3b0..04e3e8c0df 100644 --- a/source/dnode/vnode/src/tq/tqRead.c +++ b/source/dnode/vnode/src/tq/tqRead.c @@ -297,7 +297,6 @@ int32_t tqReaderSeek(STqReader* pReader, int64_t ver, const char* id) { int32_t extractMsgFromWal(SWalReader* pReader, void** pItem, int64_t maxVer, const char* id) { int32_t code = 0; - SWalCont* pCont = &pReader->pHead->head; while(1) { code = walNextValidMsg(pReader); @@ -305,7 +304,8 @@ int32_t extractMsgFromWal(SWalReader* pReader, void** pItem, int64_t maxVer, con return code; } - int64_t ver = pCont->version; + SWalCont* pCont = &pReader->pHead->head; + int64_t ver = pCont->version; if (ver > maxVer) { tqDebug("maxVer in WAL:%" PRId64 " reached current:%" PRId64 ", do not scan wal anymore, %s", maxVer, ver, id); return TSDB_CODE_SUCCESS; From 49216ffa54f211adafc0567602aeb4a668a67d9a Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Thu, 24 Aug 2023 16:32:15 +0800 Subject: [PATCH 045/120] fix:transaction in tmq --- source/dnode/mnode/impl/inc/mndDef.h | 2 + source/dnode/mnode/impl/src/mndConsumer.c | 57 ++++++++++++---------- source/dnode/mnode/impl/src/mndSubscribe.c | 32 +++++++----- source/dnode/mnode/impl/src/mndTopic.c | 16 ++++-- source/dnode/mnode/impl/src/mndTrans.c | 16 ++++++ source/dnode/vnode/src/tq/tq.c | 15 +----- 6 files changed, 80 insertions(+), 58 deletions(-) diff --git a/source/dnode/mnode/impl/inc/mndDef.h b/source/dnode/mnode/impl/inc/mndDef.h index c83a40c25d..4656e0555f 100644 --- a/source/dnode/mnode/impl/inc/mndDef.h +++ b/source/dnode/mnode/impl/inc/mndDef.h @@ -99,6 +99,8 @@ typedef enum { TRN_CONFLICT_GLOBAL = 1, TRN_CONFLICT_DB = 2, TRN_CONFLICT_DB_INSIDE = 3, + TRN_CONFLICT_TOPIC = 4, + TRN_CONFLICT_TOPIC_INSIDE = 5, } ETrnConflct; typedef enum { diff --git a/source/dnode/mnode/impl/src/mndConsumer.c b/source/dnode/mnode/impl/src/mndConsumer.c index d5b7342768..2e78d03884 100644 --- a/source/dnode/mnode/impl/src/mndConsumer.c +++ b/source/dnode/mnode/impl/src/mndConsumer.c @@ -119,6 +119,27 @@ void mndRebCntDec() { } } +static int32_t validateTopics(STrans *pTrans, const SArray *pTopicList, SMnode *pMnode) { + int32_t numOfTopics = taosArrayGetSize(pTopicList); + + for (int32_t i = 0; i < numOfTopics; i++) { + char *pOneTopic = taosArrayGetP(pTopicList, i); + SMqTopicObj *pTopic = mndAcquireTopic(pMnode, pOneTopic); + if (pTopic == NULL) { // terrno has been set by callee function + return -1; + } + + mndTransSetDbName(pTrans, pOneTopic, NULL); + if(mndTransCheckConflict(pMnode, pTrans) != 0){ + mndReleaseTopic(pMnode, pTopic); + return -1; + } + mndReleaseTopic(pMnode, pTopic); + } + + return 0; +} + static int32_t mndProcessConsumerRecoverMsg(SRpcMsg *pMsg) { SMnode *pMnode = pMsg->info.node; SMqConsumerRecoverMsg *pRecoverMsg = pMsg->pCont; @@ -142,10 +163,13 @@ static int32_t mndProcessConsumerRecoverMsg(SRpcMsg *pMsg) { mndReleaseConsumer(pMnode, pConsumer); - STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_RETRY, TRN_CONFLICT_NOTHING, pMsg, "recover-csm"); + STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_RETRY, TRN_CONFLICT_TOPIC, pMsg, "recover-csm"); if (pTrans == NULL) { goto FAIL; } + if(validateTopics(pTrans, pConsumer->assignedTopics, pMnode) != 0){ + goto FAIL; + } if (mndSetConsumerCommitLogs(pMnode, pTrans, pConsumerNew) != 0) goto FAIL; if (mndTransPrepare(pMnode, pTrans) != 0) goto FAIL; @@ -179,9 +203,11 @@ static int32_t mndProcessConsumerClearMsg(SRpcMsg *pMsg) { mndReleaseConsumer(pMnode, pConsumer); - STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_NOTHING, pMsg, "clear-csm"); + STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_TOPIC, pMsg, "clear-csm"); if (pTrans == NULL) goto FAIL; - + if (validateTopics(pTrans, pConsumer->assignedTopics, pMnode) != 0){ + goto FAIL; + } // this is the drop action, not the update action if (mndSetConsumerDropLogs(pMnode, pTrans, pConsumerNew) != 0) goto FAIL; if (mndTransPrepare(pMnode, pTrans) != 0) goto FAIL; @@ -577,27 +603,6 @@ int32_t mndSetConsumerCommitLogs(SMnode *pMnode, STrans *pTrans, SMqConsumerObj return 0; } -static int32_t validateTopics(const SArray *pTopicList, SMnode *pMnode, const char *pUser) { - int32_t numOfTopics = taosArrayGetSize(pTopicList); - - for (int32_t i = 0; i < numOfTopics; i++) { - char *pOneTopic = taosArrayGetP(pTopicList, i); - SMqTopicObj *pTopic = mndAcquireTopic(pMnode, pOneTopic); - if (pTopic == NULL) { // terrno has been set by callee function - return -1; - } - - if (mndCheckTopicPrivilege(pMnode, pUser, MND_OPER_SUBSCRIBE, pTopic) != 0) { - mndReleaseTopic(pMnode, pTopic); - return -1; - } - - mndReleaseTopic(pMnode, pTopic); - } - - return 0; -} - static void *topicNameDup(void *p) { return taosStrdup((char *)p); } static void freeItem(void *param) { @@ -636,12 +641,12 @@ int32_t mndProcessSubscribeReq(SRpcMsg *pMsg) { } // check topic existence - pTrans = mndTransCreate(pMnode, TRN_POLICY_RETRY, TRN_CONFLICT_NOTHING, pMsg, "subscribe"); + pTrans = mndTransCreate(pMnode, TRN_POLICY_RETRY, TRN_CONFLICT_TOPIC, pMsg, "subscribe"); if (pTrans == NULL) { goto _over; } - code = validateTopics(pTopicList, pMnode, pMsg->info.conn.user); + code = validateTopics(pTrans, pTopicList, pMnode); if (code != TSDB_CODE_SUCCESS) { goto _over; } diff --git a/source/dnode/mnode/impl/src/mndSubscribe.c b/source/dnode/mnode/impl/src/mndSubscribe.c index 9579a18fc4..b4145ae8d0 100644 --- a/source/dnode/mnode/impl/src/mndSubscribe.c +++ b/source/dnode/mnode/impl/src/mndSubscribe.c @@ -553,13 +553,17 @@ static int32_t mndPersistRebResult(SMnode *pMnode, SRpcMsg *pMsg, const SMqRebOu } } - STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB, pMsg, "tmq-reb"); + char topic[TSDB_TOPIC_FNAME_LEN] = {0}; + char cgroup[TSDB_CGROUP_LEN] = {0}; + mndSplitSubscribeKey(pOutput->pSub->key, topic, cgroup, true); + + STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_TOPIC_INSIDE, pMsg, "tmq-reb"); if (pTrans == NULL) { nodesDestroyNode((SNode*)pPlan); return -1; } - mndTransSetDbName(pTrans, pOutput->pSub->dbName, NULL); + mndTransSetDbName(pTrans, topic, cgroup); if (mndTransCheckConflict(pMnode, pTrans) != 0) { mndTransDrop(pTrans); nodesDestroyNode((SNode*)pPlan); @@ -587,10 +591,6 @@ static int32_t mndPersistRebResult(SMnode *pMnode, SRpcMsg *pMsg, const SMqRebOu return -1; } - char topic[TSDB_TOPIC_FNAME_LEN] = {0}; - char cgroup[TSDB_CGROUP_LEN] = {0}; - mndSplitSubscribeKey(pOutput->pSub->key, topic, cgroup, true); - // 3. commit log: consumer to update status and epoch // 3.1 set touched consumer int32_t consumerNum = taosArrayGetSize(pOutput->modifyConsumers); @@ -802,6 +802,19 @@ static int32_t mndProcessDropCgroupReq(SRpcMsg *pMsg) { goto end; } + pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_TOPIC_INSIDE, pMsg, "drop-cgroup"); + if (pTrans == NULL) { + mError("cgroup: %s on topic:%s, failed to drop since %s", dropReq.cgroup, dropReq.topic, terrstr()); + code = -1; + goto end; + } + + mndTransSetDbName(pTrans, dropReq.topic, dropReq.cgroup); + code = mndTransCheckConflict(pMnode, pTrans); + if (code != 0) { + goto end; + } + void *pIter = NULL; SMqConsumerObj *pConsumer; while (1) { @@ -816,13 +829,6 @@ static int32_t mndProcessDropCgroupReq(SRpcMsg *pMsg) { sdbRelease(pMnode->pSdb, pConsumer); } - pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_NOTHING, pMsg, "drop-cgroup"); - if (pTrans == NULL) { - mError("cgroup: %s on topic:%s, failed to drop since %s", dropReq.cgroup, dropReq.topic, terrstr()); - code = -1; - goto end; - } - mInfo("trans:%d, used to drop cgroup:%s on topic %s", pTrans->id, dropReq.cgroup, dropReq.topic); if (mndSetDropSubCommitLogs(pMnode, pTrans, pSub) < 0) { diff --git a/source/dnode/mnode/impl/src/mndTopic.c b/source/dnode/mnode/impl/src/mndTopic.c index cb18c0bc65..13c9b7d176 100644 --- a/source/dnode/mnode/impl/src/mndTopic.c +++ b/source/dnode/mnode/impl/src/mndTopic.c @@ -383,14 +383,15 @@ static int32_t mndCreateTopic(SMnode *pMnode, SRpcMsg *pReq, SCMCreateTopicReq * SQueryPlan *pPlan = NULL; SMqTopicObj topicObj = {0}; - pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB, pReq, "create-topic"); + pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_TOPIC, pReq, "create-topic"); if (pTrans == NULL) { mError("topic:%s, failed to create since %s", pCreate->name, terrstr()); goto _OUT; } - mndTransSetDbName(pTrans, pDb->name, NULL); - if (mndTransCheckConflict(pMnode, pTrans) != 0) { + mndTransSetDbName(pTrans, pCreate->name, NULL); + code = mndTransCheckConflict(pMnode, pTrans); + if (code != 0) { goto _OUT; } mInfo("trans:%d to create topic:%s", pTrans->id, pCreate->name); @@ -661,6 +662,11 @@ static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { SMqTopicObj *pTopic = NULL; STrans *pTrans = NULL; + if (!mndRebTryStart()) { + mInfo("mq rebalance already in progress, do nothing"); + return 0; + } + if (tDeserializeSMDropTopicReq(pReq->pCont, pReq->contLen, &dropReq) != 0) { terrno = TSDB_CODE_INVALID_MSG; code = -1; @@ -680,14 +686,14 @@ static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { } } - pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB, pReq, "drop-topic"); + pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_TOPIC, pReq, "drop-topic"); if (pTrans == NULL) { mError("topic:%s, failed to drop since %s", pTopic->name, terrstr()); code = -1; goto end; } - mndTransSetDbName(pTrans, pTopic->db, NULL); + mndTransSetDbName(pTrans, pTopic->name, NULL); code = mndTransCheckConflict(pMnode, pTrans); if (code != 0) { goto end; diff --git a/source/dnode/mnode/impl/src/mndTrans.c b/source/dnode/mnode/impl/src/mndTrans.c index 7ebaf6dda5..849b14255c 100644 --- a/source/dnode/mnode/impl/src/mndTrans.c +++ b/source/dnode/mnode/impl/src/mndTrans.c @@ -792,6 +792,22 @@ static bool mndCheckTransConflict(SMnode *pMnode, STrans *pNew) { } } + if (pNew->conflict == TRN_CONFLICT_TOPIC) { + if (pTrans->conflict == TRN_CONFLICT_GLOBAL) conflict = true; + if (pTrans->conflict == TRN_CONFLICT_TOPIC || pTrans->conflict == TRN_CONFLICT_TOPIC_INSIDE) { + if (strcasecmp(pNew->dbname, pTrans->dbname) == 0 ) conflict = true; + } + } + if (pNew->conflict == TRN_CONFLICT_TOPIC_INSIDE) { + if (pTrans->conflict == TRN_CONFLICT_GLOBAL) conflict = true; + if (pTrans->conflict == TRN_CONFLICT_TOPIC ) { + if (strcasecmp(pNew->dbname, pTrans->dbname) == 0 ) conflict = true; + } + if (pTrans->conflict == TRN_CONFLICT_TOPIC_INSIDE) { + if (strcasecmp(pNew->dbname, pTrans->dbname) == 0 && strcasecmp(pNew->stbname, pTrans->stbname) == 0) conflict = true; + } + } + if (conflict) { mError("trans:%d, db:%s stb:%s type:%d, can't execute since conflict with trans:%d db:%s stb:%s type:%d", pNew->id, pNew->dbname, pNew->stbname, pNew->conflict, pTrans->id, pTrans->dbname, pTrans->stbname, diff --git a/source/dnode/vnode/src/tq/tq.c b/source/dnode/vnode/src/tq/tq.c index 815e9647b5..3396803f08 100644 --- a/source/dnode/vnode/src/tq/tq.c +++ b/source/dnode/vnode/src/tq/tq.c @@ -879,20 +879,7 @@ int32_t tqProcessSubscribeReq(STQ* pTq, int64_t sversion, char* msg, int32_t msg } else { tqInfo("vgId:%d switch consumer from Id:0x%" PRIx64 " to Id:0x%" PRIx64, req.vgId, pHandle->consumerId, req.newConsumerId); atomic_store_64(&pHandle->consumerId, req.newConsumerId); - // atomic_add_fetch_32(&pHandle->epoch, 1); - - // kill executing task - // if(tqIsHandleExec(pHandle)) { - // qTaskInfo_t pTaskInfo = pHandle->execHandle.task; - // if (pTaskInfo != NULL) { - // qKillTask(pTaskInfo, TSDB_CODE_SUCCESS); - // } - - // if (pHandle->execHandle.subType == TOPIC_SUB_TYPE__COLUMN) { - // qStreamCloseTsdbReader(pTaskInfo); - // } - // } - // remove if it has been register in the push manager, and return one empty block to consumer + atomic_store_32(&pHandle->epoch, 0); tqUnregisterPushHandle(pTq, pHandle); ret = tqMetaSaveHandle(pTq, req.subKey, pHandle); } From a72e6fd2196e2f369efb1c9208e2a4d426e333e8 Mon Sep 17 00:00:00 2001 From: liuyao <54liuyao@163.com> Date: Thu, 24 Aug 2023 16:32:34 +0800 Subject: [PATCH 046/120] mem leak --- source/dnode/mnode/impl/src/mndStream.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source/dnode/mnode/impl/src/mndStream.c b/source/dnode/mnode/impl/src/mndStream.c index a0d53ec780..716d00bcaa 100644 --- a/source/dnode/mnode/impl/src/mndStream.c +++ b/source/dnode/mnode/impl/src/mndStream.c @@ -1564,6 +1564,7 @@ static int32_t mndProcessResumeStreamReq(SRpcMsg *pReq) { } if (pStream->status != STREAM_STATUS__PAUSE) { + sdbRelease(pMnode->pSdb, pStream); return 0; } From 7da464d8b7f6545017297878b5cd35d492d1f3dd Mon Sep 17 00:00:00 2001 From: liuyao <54liuyao@163.com> Date: Thu, 24 Aug 2023 16:36:58 +0800 Subject: [PATCH 047/120] mem leak --- source/libs/executor/src/timewindowoperator.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source/libs/executor/src/timewindowoperator.c b/source/libs/executor/src/timewindowoperator.c index 16eaf0649d..ca47cee95c 100644 --- a/source/libs/executor/src/timewindowoperator.c +++ b/source/libs/executor/src/timewindowoperator.c @@ -2922,6 +2922,7 @@ void destroyStreamSessionAggOperatorInfo(void* param) { blockDataDestroy(pInfo->pUpdateRes); tSimpleHashCleanup(pInfo->pStUpdated); tSimpleHashCleanup(pInfo->pStDeleted); + pInfo->pUpdated = taosArrayDestroy(pInfo->pUpdated); taosArrayDestroy(pInfo->historyWins); taosMemoryFreeClear(param); From b83cc11043334f79eac15cf220d406dfdb51205a Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Thu, 24 Aug 2023 16:38:53 +0800 Subject: [PATCH 048/120] fix:[TD-25651] reset epoch if consumer changed to avoid consumeing no data --- source/dnode/vnode/src/tq/tq.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/source/dnode/vnode/src/tq/tq.c b/source/dnode/vnode/src/tq/tq.c index 815e9647b5..c8da7e0b46 100644 --- a/source/dnode/vnode/src/tq/tq.c +++ b/source/dnode/vnode/src/tq/tq.c @@ -879,20 +879,8 @@ int32_t tqProcessSubscribeReq(STQ* pTq, int64_t sversion, char* msg, int32_t msg } else { tqInfo("vgId:%d switch consumer from Id:0x%" PRIx64 " to Id:0x%" PRIx64, req.vgId, pHandle->consumerId, req.newConsumerId); atomic_store_64(&pHandle->consumerId, req.newConsumerId); - // atomic_add_fetch_32(&pHandle->epoch, 1); + atomic_store_32(&pHandle->epoch, 0); - // kill executing task - // if(tqIsHandleExec(pHandle)) { - // qTaskInfo_t pTaskInfo = pHandle->execHandle.task; - // if (pTaskInfo != NULL) { - // qKillTask(pTaskInfo, TSDB_CODE_SUCCESS); - // } - - // if (pHandle->execHandle.subType == TOPIC_SUB_TYPE__COLUMN) { - // qStreamCloseTsdbReader(pTaskInfo); - // } - // } - // remove if it has been register in the push manager, and return one empty block to consumer tqUnregisterPushHandle(pTq, pHandle); ret = tqMetaSaveHandle(pTq, req.subKey, pHandle); } From 3c6870275bdd3162e3d3b204c693805d029997e6 Mon Sep 17 00:00:00 2001 From: Haojun Liao Date: Thu, 24 Aug 2023 16:45:47 +0800 Subject: [PATCH 049/120] fix(parser): update the key words for disk io throttling. --- docs/en/12-taos-sql/20-keywords.md | 2 +- docs/zh/12-taos-sql/20-keywords.md | 2 +- include/common/ttokendef.h | 2 +- source/libs/parser/inc/sql.y | 2 +- source/libs/parser/src/parTokenizer.c | 2 +- source/libs/parser/src/sql.c | 12 ++++++------ source/libs/parser/test/parShowToUse.cpp | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/en/12-taos-sql/20-keywords.md b/docs/en/12-taos-sql/20-keywords.md index d563181b87..983d4f63c9 100644 --- a/docs/en/12-taos-sql/20-keywords.md +++ b/docs/en/12-taos-sql/20-keywords.md @@ -178,7 +178,7 @@ The following list shows all reserved keywords: - MATCH - MAX_DELAY -- MAX_SPEED +- BWLIMIT - MAXROWS - MERGE - META diff --git a/docs/zh/12-taos-sql/20-keywords.md b/docs/zh/12-taos-sql/20-keywords.md index f52af2f282..e7e926d0b7 100644 --- a/docs/zh/12-taos-sql/20-keywords.md +++ b/docs/zh/12-taos-sql/20-keywords.md @@ -178,7 +178,7 @@ description: TDengine 保留关键字的详细列表 - MATCH - MAX_DELAY -- MAX_SPEED +- BWLIMIT - MAXROWS - MERGE - META diff --git a/include/common/ttokendef.h b/include/common/ttokendef.h index 8a6b7b5020..f74ced9190 100644 --- a/include/common/ttokendef.h +++ b/include/common/ttokendef.h @@ -113,7 +113,7 @@ #define TK_TABLE_PREFIX 95 #define TK_TABLE_SUFFIX 96 #define TK_NK_COLON 97 -#define TK_MAX_SPEED 98 +#define TK_BWLIMIT 98 #define TK_START 99 #define TK_TIMESTAMP 100 #define TK_END 101 diff --git a/source/libs/parser/inc/sql.y b/source/libs/parser/inc/sql.y index 6c3f589159..43e75e5c5a 100755 --- a/source/libs/parser/inc/sql.y +++ b/source/libs/parser/inc/sql.y @@ -286,7 +286,7 @@ retention(A) ::= NK_VARIABLE(B) NK_COLON NK_VARIABLE(C). %type speed_opt { int32_t } %destructor speed_opt { } speed_opt(A) ::= . { A = 0; } -speed_opt(A) ::= MAX_SPEED NK_INTEGER(B). { A = taosStr2Int32(B.z, NULL, 10); } +speed_opt(A) ::= BWLIMIT NK_INTEGER(B). { A = taosStr2Int32(B.z, NULL, 10); } start_opt(A) ::= . { A = NULL; } start_opt(A) ::= START WITH NK_INTEGER(B). { A = createValueNode(pCxt, TSDB_DATA_TYPE_BIGINT, &B); } diff --git a/source/libs/parser/src/parTokenizer.c b/source/libs/parser/src/parTokenizer.c index ca7ac1a0b6..df01fe5fc8 100644 --- a/source/libs/parser/src/parTokenizer.c +++ b/source/libs/parser/src/parTokenizer.c @@ -137,7 +137,7 @@ static SKeyword keywordTable[] = { {"MATCH", TK_MATCH}, {"MAXROWS", TK_MAXROWS}, {"MAX_DELAY", TK_MAX_DELAY}, - {"MAX_SPEED", TK_MAX_SPEED}, + {"BWLIMIT", TK_BWLIMIT}, {"MERGE", TK_MERGE}, {"META", TK_META}, {"ONLY", TK_ONLY}, diff --git a/source/libs/parser/src/sql.c b/source/libs/parser/src/sql.c index a912fb4e71..755102395a 100644 --- a/source/libs/parser/src/sql.c +++ b/source/libs/parser/src/sql.c @@ -1150,7 +1150,7 @@ static const YYCODETYPE yyFallback[] = { 0, /* TABLE_PREFIX => nothing */ 0, /* TABLE_SUFFIX => nothing */ 0, /* NK_COLON => nothing */ - 0, /* MAX_SPEED => nothing */ + 0, /* BWLIMIT => nothing */ 0, /* START => nothing */ 0, /* TIMESTAMP => nothing */ 287, /* END => ABORT */ @@ -1575,7 +1575,7 @@ static const char *const yyTokenName[] = { /* 95 */ "TABLE_PREFIX", /* 96 */ "TABLE_SUFFIX", /* 97 */ "NK_COLON", - /* 98 */ "MAX_SPEED", + /* 98 */ "BWLIMIT", /* 99 */ "START", /* 100 */ "TIMESTAMP", /* 101 */ "END", @@ -2114,7 +2114,7 @@ static const char *const yyRuleName[] = { /* 140 */ "retention_list ::= retention_list NK_COMMA retention", /* 141 */ "retention ::= NK_VARIABLE NK_COLON NK_VARIABLE", /* 142 */ "speed_opt ::=", - /* 143 */ "speed_opt ::= MAX_SPEED NK_INTEGER", + /* 143 */ "speed_opt ::= BWLIMIT NK_INTEGER", /* 144 */ "start_opt ::=", /* 145 */ "start_opt ::= START WITH NK_INTEGER", /* 146 */ "start_opt ::= START WITH NK_STRING", @@ -3335,7 +3335,7 @@ static const YYCODETYPE yyRuleInfoLhs[] = { 366, /* (140) retention_list ::= retention_list NK_COMMA retention */ 369, /* (141) retention ::= NK_VARIABLE NK_COLON NK_VARIABLE */ 361, /* (142) speed_opt ::= */ - 361, /* (143) speed_opt ::= MAX_SPEED NK_INTEGER */ + 361, /* (143) speed_opt ::= BWLIMIT NK_INTEGER */ 362, /* (144) start_opt ::= */ 362, /* (145) start_opt ::= START WITH NK_INTEGER */ 362, /* (146) start_opt ::= START WITH NK_STRING */ @@ -3940,7 +3940,7 @@ static const signed char yyRuleInfoNRhs[] = { -3, /* (140) retention_list ::= retention_list NK_COMMA retention */ -3, /* (141) retention ::= NK_VARIABLE NK_COLON NK_VARIABLE */ 0, /* (142) speed_opt ::= */ - -2, /* (143) speed_opt ::= MAX_SPEED NK_INTEGER */ + -2, /* (143) speed_opt ::= BWLIMIT NK_INTEGER */ 0, /* (144) start_opt ::= */ -3, /* (145) start_opt ::= START WITH NK_INTEGER */ -3, /* (146) start_opt ::= START WITH NK_STRING */ @@ -5016,7 +5016,7 @@ static YYACTIONTYPE yy_reduce( case 330: /* bufsize_opt ::= */ yytestcase(yyruleno==330); { yymsp[1].minor.yy416 = 0; } break; - case 143: /* speed_opt ::= MAX_SPEED NK_INTEGER */ + case 143: /* speed_opt ::= BWLIMIT NK_INTEGER */ case 331: /* bufsize_opt ::= BUFSIZE NK_INTEGER */ yytestcase(yyruleno==331); { yymsp[-1].minor.yy416 = taosStr2Int32(yymsp[0].minor.yy0.z, NULL, 10); } break; diff --git a/source/libs/parser/test/parShowToUse.cpp b/source/libs/parser/test/parShowToUse.cpp index b7bd0e802c..4396f786ff 100644 --- a/source/libs/parser/test/parShowToUse.cpp +++ b/source/libs/parser/test/parShowToUse.cpp @@ -286,7 +286,7 @@ TEST_F(ParserShowToUseTest, trimDatabase) { run("TRIM DATABASE wxy_db"); setTrimDbReq("wxy_db", 100); - run("TRIM DATABASE wxy_db MAX_SPEED 100"); + run("TRIM DATABASE wxy_db BWLIMIT 100"); } TEST_F(ParserShowToUseTest, useDatabase) { From e5049e042f4cdf939ea14b0f00cb75c405990299 Mon Sep 17 00:00:00 2001 From: Ping Xiao Date: Thu, 24 Aug 2023 17:28:41 +0800 Subject: [PATCH 050/120] undo remove operation for taosx when build taosd --- packaging/tools/make_install.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/packaging/tools/make_install.sh b/packaging/tools/make_install.sh index 99315b3311..ea19125bf5 100755 --- a/packaging/tools/make_install.sh +++ b/packaging/tools/make_install.sh @@ -158,7 +158,6 @@ function install_bin() { ${csudo}rm -f ${bin_link_dir}/udfd || : ${csudo}rm -f ${bin_link_dir}/taosdemo || : ${csudo}rm -f ${bin_link_dir}/taosdump || : - ${csudo}rm -f ${bin_link_dir}/taosx || : ${csudo}rm -f ${bin_link_dir}/${uninstallScript} || : if [ "$osType" != "Darwin" ]; then From cca00406a55965c86aea61d2d3061b0a58265e67 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Thu, 24 Aug 2023 18:16:57 +0800 Subject: [PATCH 051/120] fix:drop topic error if topic not exist --- source/dnode/mnode/impl/src/mndTopic.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/source/dnode/mnode/impl/src/mndTopic.c b/source/dnode/mnode/impl/src/mndTopic.c index a3f1a100d0..eacdaa1665 100644 --- a/source/dnode/mnode/impl/src/mndTopic.c +++ b/source/dnode/mnode/impl/src/mndTopic.c @@ -664,20 +664,18 @@ static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { if (tDeserializeSMDropTopicReq(pReq->pCont, pReq->contLen, &dropReq) != 0) { terrno = TSDB_CODE_INVALID_MSG; - code = -1; - goto end; + return -1; } pTopic = mndAcquireTopic(pMnode, dropReq.name); if (pTopic == NULL) { if (dropReq.igNotExists) { mInfo("topic:%s, not exist, ignore not exist is set", dropReq.name); - goto end; + return 0; } else { terrno = TSDB_CODE_MND_TOPIC_NOT_EXIST; mError("topic:%s, failed to drop since %s", dropReq.name, terrstr()); - code = -1; - goto end; + return -1; } } From 64959f14e9440049bf4e8e2e39ef95d0230fd8c1 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Thu, 24 Aug 2023 18:18:02 +0800 Subject: [PATCH 052/120] fix:dot process in schemaless --- source/client/src/clientSml.c | 14 +++++++++++++- utils/test/c/sml_test.c | 13 +++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/source/client/src/clientSml.c b/source/client/src/clientSml.c index ffff3df5d0..8e5c6e7250 100644 --- a/source/client/src/clientSml.c +++ b/source/client/src/clientSml.c @@ -218,7 +218,16 @@ int32_t smlSetCTableName(SSmlTableInfo *oneTable) { if (strlen(oneTable->childTableName) == 0) { SArray *dst = taosArrayDup(oneTable->tags, NULL); - RandTableName rName = {dst, oneTable->sTableName, (uint8_t)oneTable->sTableNameLen, oneTable->childTableName}; + ASSERT(oneTable->sTableNameLen < TSDB_TABLE_NAME_LEN); + char superName[TSDB_TABLE_NAME_LEN] = {0}; + RandTableName rName = {dst, NULL, (uint8_t)oneTable->sTableNameLen, oneTable->childTableName}; + if(tsSmlDot2Underline){ + memcpy(superName, oneTable->sTableName, oneTable->sTableNameLen); + smlStrReplace(superName, oneTable->sTableNameLen); + rName.stbFullName = superName; + }else{ + rName.stbFullName = oneTable->sTableName; + } buildChildTableName(&rName); taosArrayDestroy(dst); @@ -230,6 +239,9 @@ void getTableUid(SSmlHandle *info, SSmlLineInfo *currElement, SSmlTableInfo *tin char key[TSDB_TABLE_NAME_LEN * 2 + 1] = {0}; size_t nLen = strlen(tinfo->childTableName); memcpy(key, currElement->measure, currElement->measureLen); + if(tsSmlDot2Underline){ + smlStrReplace(key, currElement->measureLen); + } memcpy(key + currElement->measureLen + 1, tinfo->childTableName, nLen); void *uid = taosHashGet(info->tableUids, key, diff --git a/utils/test/c/sml_test.c b/utils/test/c/sml_test.c index e4ed6037a3..5be9d98a7f 100644 --- a/utils/test/c/sml_test.c +++ b/utils/test/c/sml_test.c @@ -1533,6 +1533,7 @@ int sml_ts3724_Test() { const char *sql[] = { "stb.2,t1=1 f1=283i32 1632299372000", + "stb_2,t1=1 f1=283i32 1632299372000", ".stb2,t1=1 f1=106i32 1632299378000", "stb2.,t1=1 f1=106i32 1632299378000", }; @@ -1547,6 +1548,18 @@ int sml_ts3724_Test() { printf("%s result0:%s\n", __FUNCTION__, taos_errstr(pRes)); taos_free_result(pRes); + pRes = taos_query(taos, "select * from stb_2"); + TAOS_ROW row = taos_fetch_row(pRes); + int numRows = taos_affected_rows(pRes); + ASSERT(numRows == 1); + taos_free_result(pRes); + + pRes = taos_query(taos, "show stables"); + row = taos_fetch_row(pRes); + numRows = taos_affected_rows(pRes); + ASSERT(numRows == 3); + taos_free_result(pRes); + taos_close(taos); return code; From d5cc4155420a2e93b012c56827791ea2cf7810dd Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Thu, 24 Aug 2023 18:35:53 +0800 Subject: [PATCH 053/120] fix:test case error --- tests/system-test/7-tmq/subscribeDb0.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system-test/7-tmq/subscribeDb0.py b/tests/system-test/7-tmq/subscribeDb0.py index ed13fcbe06..d4dfe425dc 100644 --- a/tests/system-test/7-tmq/subscribeDb0.py +++ b/tests/system-test/7-tmq/subscribeDb0.py @@ -237,7 +237,7 @@ class TDTestCase: for i in range(expectRows): totalConsumeRows += resultList[i] - if totalConsumeRows != expectrowcnt: + if totalConsumeRows < expectrowcnt: tdLog.info("act consume rows: %d, expect consume rows: %d"%(totalConsumeRows, expectrowcnt)) tdLog.exit("tmq consume rows error!") From 8045f30be8b8d27f82a854f503314cb2431c48fb Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Thu, 24 Aug 2023 23:15:35 +0800 Subject: [PATCH 054/120] fix:sml test case error for tbname --- tests/system-test/2-query/sml_TS-3724.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system-test/2-query/sml_TS-3724.py b/tests/system-test/2-query/sml_TS-3724.py index a8b16c4662..410e266f10 100644 --- a/tests/system-test/2-query/sml_TS-3724.py +++ b/tests/system-test/2-query/sml_TS-3724.py @@ -67,7 +67,7 @@ class TDTestCase: tdSql.query(f"select distinct tbname from {dbname}.`sys_if_bytes_out`") tdSql.checkRows(2) - tdSql.query(f"select * from {dbname}.t_fc70dec6677d4277c5d9799c4da806da order by times") + tdSql.query(f"select * from {dbname}.t_f67972b49aa8adf8bca5d0d54f0d850d order by times") tdSql.checkRows(2) tdSql.checkData(0, 1, 1.300000000) tdSql.checkData(1, 1, 13.000000000) From f7b42ad41b9a301009749a67c95a20e26a47bce6 Mon Sep 17 00:00:00 2001 From: Shuduo Sang Date: Fri, 25 Aug 2023 09:22:29 +0800 Subject: [PATCH 055/120] fix: specify utf-8 in jdbc example pom.xml (#22565) * fix: use latest version of jdbc connector * fix: remove locale and timezone to avoid confusing user * fix: update readme.md * fix: refine demo.c * fix: specify utf-8 in jdbc example pom.xml --- examples/JDBC/taosdemo/pom.xml | 1 + examples/JDBC/taosdemo/readme.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/JDBC/taosdemo/pom.xml b/examples/JDBC/taosdemo/pom.xml index 0d47663bba..ff64d3e1df 100644 --- a/examples/JDBC/taosdemo/pom.xml +++ b/examples/JDBC/taosdemo/pom.xml @@ -133,6 +133,7 @@ 8 8 + UTF-8 diff --git a/examples/JDBC/taosdemo/readme.md b/examples/JDBC/taosdemo/readme.md index edac970399..986eef8a05 100644 --- a/examples/JDBC/taosdemo/readme.md +++ b/examples/JDBC/taosdemo/readme.md @@ -8,4 +8,4 @@ java -jar target/taosdemo-2.0.1-jar-with-dependencies.jar -host -data ``` 如果发生错误 Exception in thread "main" java.lang.UnsatisfiedLinkError: no taos in java.library.path -请检查是否安装 TDengine 客户端安装包或编译 TDengine 安装。如果确定已经安装过还出现这个错误,可以在命令行 java 后加 -Djava.library.path=/usr/local/lib 来指定寻找共享库的路径。 +请检查是否安装 TDengine 客户端安装包或编译 TDengine 安装。如果确定已经安装过还出现这个错误,可以在命令行 java 后加 -Djava.library.path=/usr/lib 来指定寻找共享库的路径。 From af130cc92f6ace15646fb36b45a5df650a8aebc3 Mon Sep 17 00:00:00 2001 From: Ganlin Zhao Date: Fri, 25 Aug 2023 09:55:50 +0800 Subject: [PATCH 056/120] Revert "add seperate function for createLogs" This reverts commit 13d2d72bb61a0d504cb7738241dc802a1597275d. --- include/os/osDir.h | 3 +- source/common/src/tglobal.c | 2 +- source/libs/stream/src/streamMeta.c | 4 +- source/libs/stream/src/streamState.c | 2 +- source/libs/tdb/src/db/tdbDb.c | 2 +- source/os/src/osDir.c | 69 +++------------------------- 6 files changed, 12 insertions(+), 70 deletions(-) diff --git a/include/os/osDir.h b/include/os/osDir.h index e722adcdcc..2542f9830f 100644 --- a/include/os/osDir.h +++ b/include/os/osDir.h @@ -83,8 +83,7 @@ void taosRemoveDir(const char *dirname); bool taosDirExist(const char *dirname); int32_t taosMkDir(const char *dirname); int32_t taosMulMkDir(const char *dirname); -int32_t taosMulModeMkDir(const char *dirname, int mode); -int32_t taosMulModeMkLogDir(const char *dirname, int mode); +int32_t taosMulModeMkDir(const char *dirname, int mode, bool createLogFile); void taosRemoveOldFiles(const char *dirname, int32_t keepDays); int32_t taosExpandDir(const char *dirname, char *outname, int32_t maxlen); int32_t taosRealPath(char *dirname, char *realPath, int32_t maxlen); diff --git a/source/common/src/tglobal.c b/source/common/src/tglobal.c index 611b88bc9d..3edb70e63f 100644 --- a/source/common/src/tglobal.c +++ b/source/common/src/tglobal.c @@ -1434,7 +1434,7 @@ int32_t taosCreateLog(const char *logname, int32_t logFileNum, const char *cfgDi taosSetAllDebugFlag(cfgGetItem(pCfg, "debugFlag")->i32, false); - if (taosMulModeMkLogDir(tsLogDir, 0777) != 0) { + if (taosMulModeMkDir(tsLogDir, 0777, true) != 0) { terrno = TAOS_SYSTEM_ERROR(errno); printf("failed to create dir:%s since %s", tsLogDir, terrstr()); cfgCleanup(pCfg); diff --git a/source/libs/stream/src/streamMeta.c b/source/libs/stream/src/streamMeta.c index fe455c0190..61da055b25 100644 --- a/source/libs/stream/src/streamMeta.c +++ b/source/libs/stream/src/streamMeta.c @@ -52,7 +52,7 @@ SStreamMeta* streamMetaOpen(const char* path, void* ahandle, FTaskExpand expandF memset(streamPath, 0, len); sprintf(streamPath, "%s/%s", pMeta->path, "checkpoints"); - code = taosMulModeMkDir(streamPath, 0755); + code = taosMulModeMkDir(streamPath, 0755, false); if (code != 0) { terrno = TAOS_SYSTEM_ERROR(code); goto _err; @@ -90,7 +90,7 @@ SStreamMeta* streamMetaOpen(const char* path, void* ahandle, FTaskExpand expandF memset(streamPath, 0, len); sprintf(streamPath, "%s/%s", pMeta->path, "state"); - code = taosMulModeMkDir(streamPath, 0755); + code = taosMulModeMkDir(streamPath, 0755, false); if (code != 0) { terrno = TAOS_SYSTEM_ERROR(code); goto _err; diff --git a/source/libs/stream/src/streamState.c b/source/libs/stream/src/streamState.c index 5b42be182c..8694e5cf4c 100644 --- a/source/libs/stream/src/streamState.c +++ b/source/libs/stream/src/streamState.c @@ -169,7 +169,7 @@ SStreamState* streamStateOpen(char* path, void* pTask, bool specPath, int32_t sz sscanf(cfg, "%d\n%d\n", &szPage, &pages); } } else { - int32_t code = taosMulModeMkDir(statePath, 0755); + int32_t code = taosMulModeMkDir(statePath, 0755, false); if (code == 0) { pCfgFile = taosOpenFile(cfgPath, TD_FILE_WRITE | TD_FILE_CREATE); sprintf(cfg, "%d\n%d\n", szPage, pages); diff --git a/source/libs/tdb/src/db/tdbDb.c b/source/libs/tdb/src/db/tdbDb.c index 4f595d8d4a..81b306e65d 100644 --- a/source/libs/tdb/src/db/tdbDb.c +++ b/source/libs/tdb/src/db/tdbDb.c @@ -62,7 +62,7 @@ int32_t tdbOpen(const char *dbname, int32_t szPage, int32_t pages, TDB **ppDb, i } memset(pDb->pgrHash, 0, tsize); - ret = taosMulModeMkDir(dbname, 0755); + ret = taosMulModeMkDir(dbname, 0755, false); if (ret < 0) { return -1; } diff --git a/source/os/src/osDir.c b/source/os/src/osDir.c index dff0cf9886..e9f8c7f7e6 100644 --- a/source/os/src/osDir.c +++ b/source/os/src/osDir.c @@ -193,7 +193,7 @@ int32_t taosMulMkDir(const char *dirname) { return code; } -int32_t taosMulModeMkDir(const char *dirname, int mode) { +int32_t taosMulModeMkDir(const char *dirname, int mode, bool createLogFile) { if (dirname == NULL || strlen(dirname) >= TDDIRMAXLEN) return -1; char temp[TDDIRMAXLEN]; char *pos = temp; @@ -206,73 +206,16 @@ int32_t taosMulModeMkDir(const char *dirname, int mode) { #endif if (taosDirExist(temp)) { - return chmod(temp, mode); - } - - if (strncmp(temp, TD_DIRSEP, 1) == 0) { - pos += 1; - } else if (strncmp(temp, "." TD_DIRSEP, 2) == 0) { - pos += 2; - } - - for (; *pos != '\0'; pos++) { - if (*pos == TD_DIRSEP[0]) { - *pos = '\0'; -#ifdef WINDOWS - code = _mkdir(temp, mode); -#elif defined(DARWIN) - code = mkdir(dirname, 0777); -#else - code = mkdir(temp, mode); -#endif - if (code < 0 && errno != EEXIST) { - // terrno = TAOS_SYSTEM_ERROR(errno); - return code; + if (createLogFile) { + if (!taosCheckAccessFile(temp, TD_FILE_ACCESS_EXIST_OK | TD_FILE_ACCESS_READ_OK | TD_FILE_ACCESS_WRITE_OK)) { + code = -1; } - *pos = TD_DIRSEP[0]; - } - } - - if (*(pos - 1) != TD_DIRSEP[0]) { -#ifdef WINDOWS - code = _mkdir(temp, mode); -#elif defined(DARWIN) - code = mkdir(dirname, 0777); -#else - code = mkdir(temp, mode); -#endif - if (code < 0 && errno != EEXIST) { - // terrno = TAOS_SYSTEM_ERROR(errno); return code; + } else { + return chmod(temp, mode); } } - if (code < 0 && errno == EEXIST) { - return chmod(temp, mode); - } - - return chmod(temp, mode); -} - -int32_t taosMulModeMkLogDir(const char *dirname, int mode) { - if (dirname == NULL || strlen(dirname) >= TDDIRMAXLEN) return -1; - char temp[TDDIRMAXLEN]; - char *pos = temp; - int32_t code = 0; -#ifdef WINDOWS - taosRealPath(dirname, temp, sizeof(temp)); - if (temp[1] == ':') pos += 3; -#else - strcpy(temp, dirname); -#endif - - if (taosDirExist(temp)) { - if (!taosCheckAccessFile(temp, TD_FILE_ACCESS_EXIST_OK | TD_FILE_ACCESS_READ_OK | TD_FILE_ACCESS_WRITE_OK)) { - code = -1; - } - return code; - } - if (strncmp(temp, TD_DIRSEP, 1) == 0) { pos += 1; } else if (strncmp(temp, "." TD_DIRSEP, 2) == 0) { From 250a8a7c39f664678a88973906eab40be33d3076 Mon Sep 17 00:00:00 2001 From: shenglian zhou Date: Fri, 25 Aug 2023 10:06:59 +0800 Subject: [PATCH 057/120] fix: taosCompressFile mem leak --- source/os/src/osFile.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/source/os/src/osFile.c b/source/os/src/osFile.c index dd670595f0..ede1f1fb0e 100644 --- a/source/os/src/osFile.c +++ b/source/os/src/osFile.c @@ -885,13 +885,16 @@ int32_t taosCompressFile(char *srcFileName, char *destFileName) { char *data = taosMemoryMalloc(compressSize); gzFile dstFp = NULL; - TdFilePtr pSrcFile = taosOpenFile(srcFileName, TD_FILE_READ | TD_FILE_STREAM); + TdFilePtr pFile = NULL; + TdFilePtr pSrcFile = NULL; + + pSrcFile = taosOpenFile(srcFileName, TD_FILE_READ | TD_FILE_STREAM); if (pSrcFile == NULL) { ret = -1; goto cmp_end; } - TdFilePtr pFile = taosOpenFile(destFileName, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_TRUNC); + pFile = taosOpenFile(destFileName, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_TRUNC); if (pFile == NULL) { ret = -2; goto cmp_end; @@ -910,6 +913,9 @@ int32_t taosCompressFile(char *srcFileName, char *destFileName) { } cmp_end: + if (pFile) { + taosCloseFile(&pFile); + } if (pSrcFile) { taosCloseFile(&pSrcFile); } From c8c30e0a00c508faf6a8428846516386124b9f47 Mon Sep 17 00:00:00 2001 From: Ganlin Zhao Date: Fri, 25 Aug 2023 10:15:52 +0800 Subject: [PATCH 058/120] fix --- include/os/osDir.h | 2 +- source/os/src/osDir.c | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/include/os/osDir.h b/include/os/osDir.h index 2542f9830f..533ac8e4a4 100644 --- a/include/os/osDir.h +++ b/include/os/osDir.h @@ -83,7 +83,7 @@ void taosRemoveDir(const char *dirname); bool taosDirExist(const char *dirname); int32_t taosMkDir(const char *dirname); int32_t taosMulMkDir(const char *dirname); -int32_t taosMulModeMkDir(const char *dirname, int mode, bool createLogFile); +int32_t taosMulModeMkDir(const char *dirname, int mode, bool checkAccess); void taosRemoveOldFiles(const char *dirname, int32_t keepDays); int32_t taosExpandDir(const char *dirname, char *outname, int32_t maxlen); int32_t taosRealPath(char *dirname, char *realPath, int32_t maxlen); diff --git a/source/os/src/osDir.c b/source/os/src/osDir.c index e9f8c7f7e6..d0fb7ee919 100644 --- a/source/os/src/osDir.c +++ b/source/os/src/osDir.c @@ -193,7 +193,7 @@ int32_t taosMulMkDir(const char *dirname) { return code; } -int32_t taosMulModeMkDir(const char *dirname, int mode, bool createLogFile) { +int32_t taosMulModeMkDir(const char *dirname, int mode, bool checkAccess) { if (dirname == NULL || strlen(dirname) >= TDDIRMAXLEN) return -1; char temp[TDDIRMAXLEN]; char *pos = temp; @@ -206,14 +206,10 @@ int32_t taosMulModeMkDir(const char *dirname, int mode, bool createLogFile) { #endif if (taosDirExist(temp)) { - if (createLogFile) { - if (!taosCheckAccessFile(temp, TD_FILE_ACCESS_EXIST_OK | TD_FILE_ACCESS_READ_OK | TD_FILE_ACCESS_WRITE_OK)) { - code = -1; - } + if (checkAccess && taosCheckAccessFile(temp, TD_FILE_ACCESS_EXIST_OK | TD_FILE_ACCESS_READ_OK | TD_FILE_ACCESS_WRITE_OK)) { return code; - } else { - return chmod(temp, mode); } + return chmod(temp, mode); } if (strncmp(temp, TD_DIRSEP, 1) == 0) { From 29aaf1c86857fb11ef21423da0dc2d81040c4389 Mon Sep 17 00:00:00 2001 From: Alex Duan <51781608+DuanKuanJun@users.noreply.github.com> Date: Fri, 25 Aug 2023 10:51:17 +0800 Subject: [PATCH 059/120] Update 06-taosdump.md dot replace with '-Q' --- docs/zh/14-reference/06-taosdump.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/zh/14-reference/06-taosdump.md b/docs/zh/14-reference/06-taosdump.md index 12122edd32..9fe3c5af7a 100644 --- a/docs/zh/14-reference/06-taosdump.md +++ b/docs/zh/14-reference/06-taosdump.md @@ -105,6 +105,8 @@ Usage: taosdump [OPTION...] dbname [tbname ...] -L, --loose-mode Using loose mode if the table name and column name use letter and number only. Default is NOT. -n, --no-escape No escape char '`'. Default is using it. + -Q, --dot-replace Repalce dot character with underline character in + the table name. -T, --thread-num=THREAD_NUM Number of thread for dump in file. Default is 8. -C, --cloud=CLOUD_DSN specify a DSN to access TDengine cloud service From acc12036a561aafebae5f02bde87b9629ac2bfca Mon Sep 17 00:00:00 2001 From: Alex Duan <51781608+DuanKuanJun@users.noreply.github.com> Date: Fri, 25 Aug 2023 10:53:14 +0800 Subject: [PATCH 060/120] Update 06-taosdump.md dot replace with '-Q' (English) --- docs/en/14-reference/06-taosdump.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/14-reference/06-taosdump.md b/docs/en/14-reference/06-taosdump.md index 6d5547e7a9..baf07d6b9e 100644 --- a/docs/en/14-reference/06-taosdump.md +++ b/docs/en/14-reference/06-taosdump.md @@ -102,6 +102,8 @@ Usage: taosdump [OPTION...] dbname [tbname ...] -L, --loose-mode Use loose mode if the table name and column name use letter and number only. Default is NOT. -n, --no-escape No escape char '`'. Default is using it. + -Q, --dot-replace Repalce dot character with underline character in + the table name. -T, --thread-num=THREAD_NUM Number of thread for dump in file. Default is 8. -C, --cloud=CLOUD_DSN specify a DSN to access TDengine cloud service From e1095e7d02fc2f419293f58ffdf082d21d97f117 Mon Sep 17 00:00:00 2001 From: Hongze Cheng Date: Fri, 25 Aug 2023 14:40:29 +0800 Subject: [PATCH 061/120] fix: improve upgrade speed --- source/dnode/vnode/src/tsdb/tsdbUpgrade.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/source/dnode/vnode/src/tsdb/tsdbUpgrade.c b/source/dnode/vnode/src/tsdb/tsdbUpgrade.c index 59ba51c371..3f1fcb7248 100644 --- a/source/dnode/vnode/src/tsdb/tsdbUpgrade.c +++ b/source/dnode/vnode/src/tsdb/tsdbUpgrade.c @@ -120,7 +120,15 @@ static int32_t tsdbUpgradeHead(STsdb *tsdb, SDFileSet *pDFileSet, SDataFReader * }; if (dataBlk->hasDup) { - code = tsdbReadDataBlockEx(reader, dataBlk, ctx->blockData); + tBlockDataReset(ctx->blockData); + + int16_t aCid = 0; + STSchema tSchema = {0}; + TABLEID tbid = {.suid = pBlockIdx->suid, .uid = pBlockIdx->uid}; + code = tBlockDataInit(ctx->blockData, &tbid, &tSchema, &aCid, 0); + TSDB_CHECK_CODE(code, lino, _exit); + + code = tsdbReadDataBlock(reader, dataBlk, ctx->blockData); TSDB_CHECK_CODE(code, lino, _exit); record.count = 1; @@ -334,6 +342,8 @@ static int32_t tsdbUpgradeFileSet(STsdb *tsdb, SDFileSet *pDFileSet, TFileSetArr int32_t code = 0; int32_t lino = 0; + tsdbInfo("vgId:%d upgrade file set start, fid:%d", TD_VID(tsdb->pVnode), pDFileSet->fid); + SDataFReader *reader; STFileSet *fset; @@ -366,6 +376,8 @@ static int32_t tsdbUpgradeFileSet(STsdb *tsdb, SDFileSet *pDFileSet, TFileSetArr code = TARRAY2_APPEND(fileSetArray, fset); TSDB_CHECK_CODE(code, lino, _exit); + tsdbInfo("vgId:%d upgrade file set end, fid:%d", TD_VID(tsdb->pVnode), pDFileSet->fid); + _exit: if (code) { TSDB_ERROR_LOG(TD_VID(tsdb->pVnode), lino, code); From 9fc75fb73d68f0506c6f624793954a06bbee143e Mon Sep 17 00:00:00 2001 From: shenglian zhou Date: Fri, 25 Aug 2023 14:59:39 +0800 Subject: [PATCH 062/120] fix: wrong start key for pFill when unit of sliding is y or n --- source/libs/executor/src/filloperator.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/source/libs/executor/src/filloperator.c b/source/libs/executor/src/filloperator.c index 9b0b43b6c4..dc6d7a791e 100644 --- a/source/libs/executor/src/filloperator.c +++ b/source/libs/executor/src/filloperator.c @@ -166,18 +166,20 @@ static void revisedFillStartKey(SFillOperatorInfo* pInfo, SSDataBlock* pBlock, i } else if (ekey < pInfo->pFillInfo->start) { int64_t t = ekey; SInterval* pInterval = &pInfo->pFillInfo->interval; - + int64_t prev = t; while(1) { - int64_t prev = taosTimeAdd(t, pInterval->sliding, pInterval->slidingUnit, pInterval->precision); - if (prev >= pInfo->pFillInfo->start) { - t = prev; + int64_t next = taosTimeAdd(t, pInterval->sliding, pInterval->slidingUnit, pInterval->precision); + if (next >= pInfo->pFillInfo->start) { + prev = t; + t = next; break; } - t = prev; + prev = t; + t = next; } - // todo time window chosen problem: t or prev value? - if (t > pInfo->pFillInfo->start) t -= pInterval->sliding; + // todo time window chosen problem: t or next value? + if (t > pInfo->pFillInfo->start) t = prev; taosFillUpdateStartTimestampInfo(pInfo->pFillInfo, t); } } From e0f88eb19c5bdf1b3adda0d41cda4c737f2d4cf8 Mon Sep 17 00:00:00 2001 From: Ping Xiao Date: Fri, 25 Aug 2023 17:18:02 +0800 Subject: [PATCH 063/120] build: release ver-3.1.0.3 --- cmake/cmake.version | 2 +- docs/en/28-releases/01-tdengine.md | 4 ++++ docs/zh/28-releases/01-tdengine.md | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cmake/cmake.version b/cmake/cmake.version index ee02ef8c07..a356d974f6 100644 --- a/cmake/cmake.version +++ b/cmake/cmake.version @@ -2,7 +2,7 @@ IF (DEFINED VERNUMBER) SET(TD_VER_NUMBER ${VERNUMBER}) ELSE () - SET(TD_VER_NUMBER "3.1.0.3.alpha") + SET(TD_VER_NUMBER "3.1.0.4.alpha") ENDIF () IF (DEFINED VERCOMPATIBLE) diff --git a/docs/en/28-releases/01-tdengine.md b/docs/en/28-releases/01-tdengine.md index ff6a36440f..c02b3227ca 100644 --- a/docs/en/28-releases/01-tdengine.md +++ b/docs/en/28-releases/01-tdengine.md @@ -10,6 +10,10 @@ For TDengine 2.x installation packages by version, please visit [here](https://t import Release from "/components/ReleaseV3"; +## 3.1.0.3 + + + ## 3.1.0.2 diff --git a/docs/zh/28-releases/01-tdengine.md b/docs/zh/28-releases/01-tdengine.md index d316b3ab68..d4e4b116b7 100644 --- a/docs/zh/28-releases/01-tdengine.md +++ b/docs/zh/28-releases/01-tdengine.md @@ -10,6 +10,10 @@ TDengine 2.x 各版本安装包请访问[这里](https://www.taosdata.com/all-do import Release from "/components/ReleaseV3"; +## 3.1.0.3 + + + ## 3.1.0.2 From ce44b80208bc50be81f8bbd9bec6e8d91a5b50ea Mon Sep 17 00:00:00 2001 From: Wade Zhang Date: Fri, 25 Aug 2023 19:31:58 +0800 Subject: [PATCH 064/120] doc: add enterprise functionalities --- docs/zh/12-taos-sql/25-grant.md | 257 +++-- .../{07-import.md => 04-import.md} | 0 .../{08-export.md => 05-export.md} | 0 .../{10-monitor.md => 06-monitor.md} | 0 docs/zh/17-operation/07-cluster.md | 78 ++ docs/zh/17-operation/08-web.md | 178 ++++ docs/zh/17-operation/09-storage.md | 52 + docs/zh/18-data-transfer/01-taosX.md | 972 ++++++++++++++++++ docs/zh/18-data-transfer/02-explorer.md | 128 +++ 9 files changed, 1587 insertions(+), 78 deletions(-) rename docs/zh/17-operation/{07-import.md => 04-import.md} (100%) rename docs/zh/17-operation/{08-export.md => 05-export.md} (100%) rename docs/zh/17-operation/{10-monitor.md => 06-monitor.md} (100%) create mode 100644 docs/zh/17-operation/07-cluster.md create mode 100644 docs/zh/17-operation/08-web.md create mode 100644 docs/zh/17-operation/09-storage.md create mode 100644 docs/zh/18-data-transfer/01-taosX.md create mode 100644 docs/zh/18-data-transfer/02-explorer.md diff --git a/docs/zh/12-taos-sql/25-grant.md b/docs/zh/12-taos-sql/25-grant.md index d53f951e67..42d740539f 100644 --- a/docs/zh/12-taos-sql/25-grant.md +++ b/docs/zh/12-taos-sql/25-grant.md @@ -4,87 +4,85 @@ title: 权限管理 description: 企业版中才具有的权限管理功能 --- -本节讲述如何在 TDengine 中进行权限管理的相关操作。权限管理是 TDengine 企业版的特有功能,本节只列举了一些基本的权限管理功能作为示例,更丰富的权限管理请联系 TDengine 销售或市场团队。 +本节讲述如何在 TDengine 中进行权限管理的相关操作。权限管理是 TDengine 企业版的特有功能,欲试用 TDengine 企业版请联系 TDengine 销售或市场团队。 -## 创建用户 +TDengine 中的权限管理分为用户管理、数据库授权管理以及消息订阅授权管理。 + +当 TDengine 安装并部署成功后,系统中内置有 "root" 用户。持有默认 "root" 用户密码的系统管理员应该第一时间修改 root 用户的密码,并根据业务需要创建普通用户并为这些用户授予适当的权限。在未授权的情况下,普通用户可以创建 DATABASE,并拥有自己创建的 DATABASE 的所有权限,包括删除数据库、修改数据库、查询时序数据和写入时序数据。超级用户可以给普通用户授予其他(即非该用户所创建的) DATABASE 的读写权限,使其可以在这些 DATABASE 上读写数据,但不能对其进行删除和修改数据库的操作。超级用户或者 topic 的创建者也可以给其它用户授予对某个 topic 的订阅权限。 + +## 用户管理 + +用户管理涉及用户的整个生命周期,从创建用户、对用户进行授权、撤销对用户的授权、查看用户信息、直到删除用户。 + +### 创建用户 + +创建用户的操作只能由 root 用户进行,语法如下 + +```sql +CREATE USER user_name PASS 'password' [SYSINFO {1\|0}]; +``` + +说明: + +- user_name 最长为 23 字节。 +- password 最长为 128 字节,合法字符包括"a-zA-Z0-9!?\$%\^&\*()_–+={[}]:;@\~\#\|\<,\>.?/",不可以出现单双引号、撇号、反斜杠和空格,且不可以为空。 +- SYSINFO 表示用户是否可以查看系统信息。1 表示可以查看,0 表示不可以查看。系统信息包括服务端配置信息、服务端各种节点信息(如 DNODE、QNODE等)、存储相关的信息等。默认为可以查看系统信息。 + +示例:创建密码为123456且可以查看系统信息的用户 test + +``` +SQL taos\> create user test pass '123456' sysinfo 1; Query OK, 0 of 0 rows affected (0.001254s) +``` + +### 查看用户 + +查看系统中的用户信息请使用 show users 命令,示例如下 + +```sql +show users; +``` + +也可以通过查询系统表 `INFORMATION_SCHEMA.INS_USERS` 获取系统中的用户信息,示例如下 + +```sql +select * from information_schema.ins_users; +``` + +### 删除用户 + +删除用户请使用 + +```sql +DROP USER user_name; +``` + +### 修改用户信息 + +修改用户信息的命令如下 + +```sql +ALTER USER user_name alter_user_clause alter_user_clause: { PASS 'literal' \| ENABLE value \| SYSINFO value } +``` + +说明: + +- PASS:修改用户密码。 +- ENABLE:修改用户是否启用。1 表示启用此用户,0 表示禁用此用户。 +- SYSINFO:修改用户是否可查看系统信息。1 表示可以查看系统信息,0 表示不可以查看系统信息。 + +示例:禁用 test 用户 + +```sql +alter user test enable 0; Query OK, 0 of 0 rows affected (0.001160s) +``` ```sql CREATE USER use_name PASS 'password' [SYSINFO {1|0}]; ``` -创建用户。 +## 访问控制 -use_name 最长为 23 字节。 - -password 最长为 31 字节,合法字符包括"a-zA-Z0-9!?$%^&*()_–+={[}]:;@~#|<,>.?/",不可以出现单双引号、撇号、反斜杠和空格,且不可以为空。 - -SYSINFO 表示用户是否可以查看系统信息。1 表示可以查看,0 表示不可以查看。系统信息包括服务端配置信息、服务端各种节点信息(如 DNODE、QNODE等)、存储相关的信息等。默认为可以查看系统信息。 - -例如,创建密码为123456且可以查看系统信息的用户test如下: - -```sql -taos> create user test pass '123456' sysinfo 1; -Query OK, 0 of 0 rows affected (0.001254s) -``` - -## 查看用户 - -```sql -SHOW USERS; -``` - -查看用户信息。 - -```sql -taos> show users; - name | super | enable | sysinfo | create_time | -================================================================================ - test | 0 | 1 | 1 | 2022-08-29 15:10:27.315 | - root | 1 | 1 | 1 | 2022-08-29 15:03:34.710 | -Query OK, 2 rows in database (0.001657s) -``` - -也可以通过查询INFORMATION_SCHEMA.INS_USERS系统表来查看用户信息,例如: - -```sql -taos> select * from information_schema.ins_users; - name | super | enable | sysinfo | create_time | -================================================================================ - test | 0 | 1 | 1 | 2022-08-29 15:10:27.315 | - root | 1 | 1 | 1 | 2022-08-29 15:03:34.710 | -Query OK, 2 rows in database (0.001953s) -``` - -## 删除用户 - -```sql -DROP USER user_name; -``` - -## 修改用户信息 - -```sql -ALTER USER user_name alter_user_clause - -alter_user_clause: { - PASS 'literal' - | ENABLE value - | SYSINFO value -} -``` - -- PASS:修改用户密码。 -- ENABLE:修改用户是否启用。1 表示启用此用户,0 表示禁用此用户。 -- SYSINFO:修改用户是否可查看系统信息。1 表示可以查看系统信息,0 表示不可以查看系统信息。 - -例如,禁用 test 用户: - -```sql -taos> alter user test enable 0; -Query OK, 0 of 0 rows affected (0.001160s) -``` - -## 授权 +在 TDengine 企业版中,系统管理员可以根据业务和数据安全的需要控制任意一个用户对每一个数据库、订阅甚至表级别的访问。 ```sql GRANT privileges ON priv_level TO user_name @@ -105,14 +103,106 @@ priv_level : { } ``` -对用户授权。授权功能只包含在企业版中。 +### 数据库权限 -授权级别支持到DATABASE,权限有READ和WRITE两种。 -TDengine 有超级用户和普通用户两类用户。超级用户缺省创建为root,拥有所有权限。使用超级用户创建出来的用户为普通用户。在未授权的情况下,普通用户可以创建DATABASE,并拥有自己创建的DATABASE的所有权限,包括删除数据库、修改数据库、查询时序数据和写入时序数据。超级用户可以给普通用户授予其他DATABASE的读写权限,使其可以在此DATABASE上读写数据,但不能对其进行删除和修改数据库的操作。 +TDengine 有超级用户和普通用户两类用户。超级用户缺省创建为root,拥有所有权限。使用超级用户创建出来的用户为普通用户。在未授权的情况下,普通用户可以创建 DATABASE,并拥有自己创建的 DATABASE 的所有权限,包括删除数据库、修改数据库、查询时序数据和写入时序数据。超级用户可以给普通用户授予其他 DATABASE 的读写权限,使其可以在此 DATABASE 上读写数据,但不能对其进行删除和修改数据库的操作。 对于非DATABASE的对象,如USER、DNODE、UDF、QNODE等,普通用户只有读权限(一般为SHOW命令),不能创建和修改。 +对数据库的访问权限包含读和写两种权限,它们可以被分别授予,也可以被同时授予。 + +补充说明 + +- priv_level 格式中 "." 之前为数据库名称, "." 之后为表名称 +- "dbname.\*" 意思是名为 "dbname" 的数据库中的所有表 +- "\*.\*" 意思是所有数据库名中的所有表 + +**下表中总结了数据库权限的各种组合** + +对 root 用户和普通用户的权限的说明如下表 + +| 用户 | 描述 | 权限说明 | +|----------|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 超级用户 | 只有 root 是超级用户 | DB 外部 所有操作权限,例如user、dnode、udf、qnode等的CRUD DB 权限,包括 创建 删除 更新,例如修改 Option,移动 Vgruop等 读 写 Enable/Disable 用户 | +| 普通用户 | 除 root 以外的其它用户均为普通用户 | 在可读的 DB 中,普通用户可以进行读操作 select describe show subscribe 在可写 DB 的内部,用户可以进行写操作: 创建、删除、修改 超级表 创建、删除、修改 子表 创建、删除、修改 topic 写入数据 被限制系统信息时,不可进行如下操作 show dnode、mnode、vgroups、qnode、snode 修改用户包括自身密码 show db时只能看到自己的db,并且不能看到vgroups、副本、cache等信息 无论是否被限制系统信息,都可以 管理 udf 可以创建 DB 自己创建的 DB 具备所有权限 非自己创建的 DB ,参照读、写列表中的权限 | + +### 消息订阅授权 + +任意用户都可以在自己拥有读权限的数据库上创建 topic。超级用户 root 可以在任意数据库上创建 topic。每个 topic 的订阅权限都可以被独立授权给任何用户,不管该用户是否拥有该数据库的访问权限。删除 topic 只能由 root 用户或者该 topic 的创建者进行。topic 只能由超级用户、topic的创建者或者被显式授予 subscribe 权限的用户订阅。 + +授予订阅权限的语法如下: + +```sql +GRANT privileges ON priv_level TO user_name privileges : { ALL | priv_type [, priv_type] ... } priv_type : { SUBSCRIBE } priv_level : { topic_name } +``` + +### 基于标签的授权(表级授权) + +从 TDengine 3.0.5.0 开始,我们支持按标签授权某个超级表中部分特定的子表。具体的 SQL 语法如下。 + +```sql +GRANT privileges ON priv_level [WITH tag_condition] TO user_name + +privileges : { + ALL + | SUBSCRIBE + | priv_type [, priv_type] ... +} + +priv_type : { + READ + | WRITE +} + +priv_level : { + dbname.tbname + | dbname.* + | *.* + | topic_name +} + +REVOKE privileges ON priv_level [WITH tag_condition] FROM user_name + +privileges : { + ALL + | priv_type [, priv_type] ... +} + +priv_type : { + READ + | WRITE +} + +priv_level : { + dbname.tbname + | dbname.* + | *.* +} +``` + +上面 SQL 的语义为: + +- 用户可以通过 dbname.tbname 来为指定的表(包括超级表和普通表)授予或回收其读写权限,不支持直接对子表授予或回收权限。 +- 用户可以通过 dbname.tbname 和 WITH 子句来为符合条件的所有子表授予或回收其读写权限。使用 WITH 子句时,权限级别必须为超级表。 + +**表级权限和数据库权限的关系** + +下表列出了在不同的数据库授权和表级授权的组合下产生的实际权限。 + +| |**表无授权** | **表读授权** | **表读授权有标签条件** | **表写授权** | **表写授权有标签条件** | +| -------------- | ---------------- | -------- | ---------- | ------ | ----------- | +| **数据库无授权** | 无授权 | 对此表有读权限,对数据库下的其他表无权限 | 对此表符合标签权限的子表有读权限,对数据库下的其他表无权限 | 对此表有写权限,对数据库下的其他表无权限 | 对此表符合标签权限的子表有写权限,对数据库下的其他表无权限 | +| **数据库读授权** | 对所有表有读权限 | 对所有表有读权限 | 对此表符合标签权限的子表有读权限,对数据库下的其他表有读权限 | 对此表有写权限,对所有表有读权限 | 对此表符合标签权限的子表有写权限,所有表有读权限 | +| **数据库写授权** | 对所有表有写权限 | 对此表有读权限,对所有表有写权限 | 对此表符合标签权限的子表有读权限,对所有表有写权限 | 对所有表有写权限 | 对此表符合标签权限的子表有写权限,数据库下的其他表有写权限 | + +### 查看用户授权 + +使用下面的命令可以显示一个用户所拥有的授权: + +```sql +show user privileges +``` ## 撤销授权 ```sql @@ -135,4 +225,15 @@ priv_level : { ``` -收回对用户的授权。授权功能只包含在企业版中。 +### 撤销授权 + +1. 撤销数据库访问的授权 + +```sql +REVOKE privileges ON priv_level FROM user_name privileges : { ALL \| priv_type [, priv_type] ... } priv_type : { READ \| WRITE } priv_level : { dbname.\* \| \*.\* } +``` + +2. 撤销数据订阅的授权 + +```sql +REVOKE privileges ON priv_level FROM user_name privileges : { ALL \| priv_type [, priv_type] ... } priv_type : { SUBSCRIBE } priv_level : { topi_name } diff --git a/docs/zh/17-operation/07-import.md b/docs/zh/17-operation/04-import.md similarity index 100% rename from docs/zh/17-operation/07-import.md rename to docs/zh/17-operation/04-import.md diff --git a/docs/zh/17-operation/08-export.md b/docs/zh/17-operation/05-export.md similarity index 100% rename from docs/zh/17-operation/08-export.md rename to docs/zh/17-operation/05-export.md diff --git a/docs/zh/17-operation/10-monitor.md b/docs/zh/17-operation/06-monitor.md similarity index 100% rename from docs/zh/17-operation/10-monitor.md rename to docs/zh/17-operation/06-monitor.md diff --git a/docs/zh/17-operation/07-cluster.md b/docs/zh/17-operation/07-cluster.md new file mode 100644 index 0000000000..0b7fb7e53d --- /dev/null +++ b/docs/zh/17-operation/07-cluster.md @@ -0,0 +1,78 @@ +--- +title: 集群运维 +description: TDengine 提供了多种集群运维手段以使集群运行更健康更高效 +--- + +为了使集群运行更健康更高效,TDengine 企业版提供了一些运维手段来帮助系统管理员更好地运维集群。 + +## 数据重整 + +TDengine 面向多种写入场景,在有些写入场景下,TDengine 的存储会导致数据存储的放大或数据文件的空洞等。这一方面影响数据的存储效率,另一方面也会影响查询效率。为了解决上述问题,TDengine 企业版提供了对数据的重整功能,即 DATA COMPACT 功能,将存储的数据文件重新整理,删除文件空洞和无效数据,提高数据的组织度,从而提高存储和查询的效率。 + +**语法** + +```sql +COMPACT DATABASE db_name [start with 'XXXX'] [end with 'YYYY']; +``` + +**效果** + +- 扫描并压缩指定的 DB 中所有 VGROUP 中 VNODE 的所有数据文件 +- COMPCAT 会删除被删除数据以及被删除的表的数据 +- COMPACT 会合并多个 STT 文件 +- 可通过 start with 关键字指定 COMPACT 数据的起始时间 +- 可通过 end with 关键字指定 COMPACT 数据的终止时间 + +**补充说明** + +- COMPACT 为异步,执行 COMPACT 命令后不会等 COMPACT 结束就会返回。如果上一个 COMPACT 没有完成则再发起一个 COMPACT 任务,则会等上一个任务完成后再返回。 +- COMPACT 可能阻塞写入,但不阻塞查询 +- COMPACT 的进度不可观测 + +## 集群负载再平衡 + +当多副本集群中的一个或多个节点因为升级或其它原因而重启后,有可能出现集群中各个 dnode 负载不均衡的现象,极端情况下会出现所有 vgroup 的 leader 都位于同一个 dnode 的情况。为了解决这个问题,可以使用下面的命令 + +```sql +balance vgroup leader; +``` + +**功能** + +让所有的 vgroup 的 leade r在各自的replica节点上均匀分布。这个命令会让 vgroup 强制重新选举,通过重新选举,在选举的过程中,变换 vgroup 的leader,通过这个方式,最终让leader均匀分布。 + +**注意** + +Raft选举本身带有随机性,所以通过选举的重新分布产生的均匀分布也是带有一定的概率,不会完全的均匀。**该命令的副作用是影响查询和写入**,在vgroup重新选举时,从开始选举到选举出新的 leader 这段时间,这 个vgroup 无法写入和查询。选举过程一般在秒级完成。所有的vgroup会依次逐个重新选举。 + +## 恢复数据节点 + +当集群中的某个数据节点(dnode)的数据全部丢失或被破坏,比如磁盘损坏或者目录被误删除,可以通过 `restore dnode` 命令来恢复该数据节点上的部分或全部逻辑节点,该功能依赖多副本中的其它副本进行数据复制,所以只在集群中 dnode 数量大于等于 3 且副本数为 3 的情况下能够工作。 + + +```sql +restore dnode ;# 恢复dnode上的mnode,所有vnode和qnode +restore mnode on dnode ;# 恢复dnode上的mnode +restore vnode on dnode ;# 恢复dnode上的所有vnode +restore qnode on dnode ;# 恢复dnode上的qnode +``` + +**限制** +- 该功能是基于已有的复制功能的恢复,不是灾难恢复或者备份恢复,所以对于要恢复的 mnode 和 vnode来说,使用该命令的前提是还存在该 mnode 或 vnode 的其它两个副本仍然能够正常工作。 +- 该命令不能修复数据目录中的个别文件的损坏或者丢失。例如,如果某个 mnode 或者 vnode 中的个别文件或数据损坏,无法单独恢复损坏的某个文件或者某块数据。此时,可以选择将该 mnode/vnode 的数据全部清空再进行恢复。 + + +## 虚拟组分裂 (Scale Out) + +当一个 vgroup 因为子表数过多而导致 CPU 或 Disk 资源使用量负载过高时,增加 dnode 节点后,可通过 `split vgroup` 命令把该 vgroup 分裂为两个虚拟组。分裂完成后,新产生的两个 vgroup 承担原来由一个 vgroup 提供的读写服务。这也是 TDengine 为企业版用户提供的 scale out 集群的能力。 + +```sql +split vgroup +``` + +**注意** +- 单副本库虚拟组,在分裂完成后,历史时序数据总磁盘空间使用量,可能会翻倍。所以,在执行该操作之前,通过增加 dnode 节点方式,确保集群中有足够的 CPU 和磁盘资源,避免资源不足现象发生。 +- 该命令为 DB 级事务;执行过程,当前DB的其它管理事务将会被拒绝。集群中,其它DB不受影响。 +- 分裂任务执行过程中,可持续提供读写服务;期间,可能存在可感知的短暂的读写业务中断。 +- 在分裂过程中,不支持流和订阅。分裂结束后,历史 WAL 会清空。 +- 分裂过程中,可支持节点宕机重启容错;但不支持节点磁盘故障容错。 \ No newline at end of file diff --git a/docs/zh/17-operation/08-web.md b/docs/zh/17-operation/08-web.md new file mode 100644 index 0000000000..9923c9912e --- /dev/null +++ b/docs/zh/17-operation/08-web.md @@ -0,0 +1,178 @@ +--- +title: 基于 Web 的系统管理工具 +description: 本节描述 TDengine 的图形化管理工具, taos-explorer, 的基本功能 +--- + +## 简介 + +为了易于企业版用户更容易使用和管理数据库,TDengine 3.0 企业版提供了一个全新的可视化组件 taosExplorer。用户能够在其中方便地管理数据库管理系统中中各元素(数据库、超级表、子表)的生命周期,执行查询,监控系统状态,管理用户和授权,完成数据备份和恢复,与其它集群之间进行数据同步,导出数据,管理主题和流计算。 + +**欲体验基于 Web 的 TDengine 系统管理能力,请联系 TDengine 市场或销售团队** + +## 部署服务 + +### 准备工作 + +1. taosExplorer 没有独立的安装包,请使用 taosX 安装包进行安装。 +2. 在启动 taosExplorer 之前,请先确认 TDengine 集群已经正确设置并运行(即 taosd 服务),taosAdapter 也已经正确设置和运行并与 TDengine 集群保持连接状态。如果想要使用数据备份和恢复或者数据同步功能,请确保 taosX 服务和 Agent 服务也已经正确设置和运行。 + +### 配置 + +在启动 taosExplorer 之前,请确保配置文件中的内容正确。 + +```TOML +listen = "0.0.0.0:6060" +log_level = "info" +cluster = "http://localhost:6041" +x_api = "http://localhost:6050" +``` + +说明: + +- listen - taosExplorer 对外提供服务的地址 +- log_level - 日志级别,可选值为 "debug", "info", "warn", "error", "fatal" +- cluster - TDengine集群的 taosadapter 地址 +- x_api - taosX 的服务地址 + +### 启动 + +然后启动 taosExplorer,可以直接在命令行执行 taos-explorer 或者使用下面的 systemctl 脚本用 systemctl 来启动 taosExplorer 服务 + +```shell +[Unit] +Description=Explorer for TDengine +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/usr/bin/taos-explorer +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +### 问题排查 + +1. 当通过浏览器打开taosExplorer站点遇到“无法访问此网站”的错误信息时,请通过命令行登录taosExplorer所在机器,并使用命令systemctl status taos-explorer.service检查服务的状态,如果返回的状态是inactive,请使用命令systemctl start taos-explorer.service启动服务。 +2. 如果需要获取taosExplorer的详细日志,可通过命令journalctl -u taos-explorer + +## 登录 + +在 TDengine 管理系统的登录页面,输入正确的用户名和密码后,点击登录按钮,即可登录。 + +说明: +- 这里的用户,需要在所连接的 TDengine 中创建,TDengine 默认的用户名和密码为`root/taosdata`; +- 在 TDengine 中创建用户时,默认会设置用户的 SYSINFO 属性值为1, 表示该用户可以查看系统信息,只有 SYSINFO 属性为 1 的用户才能正常登录 TDengine 管理系统。 + +## 面板 + +taosExplorer 内置了一个简单的仪表盘展示以下集群信息,点击左侧功能列表中的 "面板" 可以启用此功能。 + +- 默认的仪表盘会返回对应 Grafana 的安装配置向导 +- 配置过 Grafana 的仪表盘在点击' 面板' 时会跳转到对应的配置地址(该地址来源于 /profile 接口的返回值) + + +## 数据浏览器 + +点击功能列表的“数据浏览器”入口,在“数据浏览器”中可以创建和删除数据库、创建和删除超级表和子表,执行SQL语句,查看SQL语句的执行结果。此外,超级管理员还有对数据库的管理权限,其他用户不提供该功能。 + +具体权限有: + +1.查看(提供数据库/超级表/普通表的基本信息) + +2.编辑 (编辑数据库/超级表/普通表的信息) + +3.数据库管理权限 (仅限超级管理员,该操作可以给指定用户配置数据库管理权限) + +4.删除 (删除数据库/超级表/普通表) + +5.追加 (选择对应的数据库/超级表/普通表名称直接追加到右侧sql输入区域,避免了手工输入) + + +## 系统管理 + +点击功能列表中的“系统管理”入口,可以创建用户、对用户进行访问授权、以及删除用户。还能够对当前所管理的集群中的数据进行备份和恢复。也可以配置一个远程 TDengine 的地址进行数据同步。同时也提供了集群信息和许可证的信息以及代理信息以供查看。系统管理 菜单只有 root 用户才有权限看到 + +### 用户管理 + +点击“系统管理”后,默认会进入“用户”标签页。 +在用户列表,可以查看系统中已存在的用户及其创建时间,并可以对用户进行启用、禁用,编辑(包括修改密码,数据库的读写权限等),删除等操作。 +点击用户列表右上方的“+新增”按钮,即可打开“新增用户”对话框: +1. 输入新增用户的用户名称,必填 +2. 输入新增用户的登录密码,必填,密码长度要求为8-16个字符,且至少要满足以下4个条件中的3个:大写字母,小写字母,数字,特殊字符 +3. 选择新增用户对系统中已存在的数据库的读写权限,非必填,默认情况下,新增用户对所有已存在的数据库无读写权限 +4. 提写完成后,点击确定按钮,即可新增用户。 + +### 系统信息 + +点击“集群”标签后,可以查看DNodes, MNodes和QNodes的状态、创建时间等信息,并可以对以上节点进行新增和删除操作。 + +### 许可证管理 + +点击“许可证”标签后,可以查看系统和系统和各连接器的许可证信息。 +点击位于“许可证”标签页右上角的“激活许可证”按钮,输入“激活码”和“连接器激活码”后,点击“确定”按钮,即可激活,激活码请联系 TDengine 客户成功团队获取。 + +## 数据订阅 + +本章节,将介绍如何在 TDengine 集群中,创建主题,并将其分享给其他用户,以及如何查看一个主题的消费者信息。 + +通过 Explorer, 您可以轻松地完成对数据订阅的管理,从而更好地利用 TDengine 提供的数据订阅能力。 +点击左侧导航栏中的“数据订阅”,即可跳转至数据订阅配置管理页面。 +您可以通过以下两种方式创建主题:使用向导和自定义 SQL 语句。通过自定义 SQL 创建主题时,您需要了解 TDengine 提供的数据订阅 SQL 语句的语法,并保证其正确性。 + +注: 对于数据订阅的详细说明,可参考官方文档中关于“数据订阅”章节,创建数据订阅之前需要先准备源数据库(或源数据库包含相应的超级表或者表),其中源数据库需配置wal_retention_period > 0 。 + +包括主题,消费者,共享主题和示例代码 + +### 创建主题 + +1. 在“主题”标签页,点击“新增新主题”按钮以后,选择向导窗格,然后输入“主题名称”; +2. 在“数据库”下拉列表中,选择相应的数据库; +3. 在“类型”标签下,选择“数据库” 或 “超级表” 或 “子查询”,这里以默认值“数据库”为例; +4. 然后点击“创建” 按钮,即可创建对应的主题。 + +### 分享主题 + +1. 在“共享主题”标签页,在“主题“下拉列表中,选择将要分享的主题; +2. 点击“添加可消费该主题的用户”按钮,然后在“用户名”下拉列表中选择相应的用户,然后点击“新增”,即可将该主题分享给此用户。 + + +### 查看消费者信息 + +1. 通过执行下一节“示例代码”所述的“完整实例”,即可消费共享主题 +2. 在“消费者”标签页,可查看到消费者的有关信息 + +### 示例代码 + +1. 在“示例代码”标签页,在“主题“下拉列表中,选择相应的主题; +2. 选择您熟悉的语言,然后您可以阅读以及使用这部分示例代码用来”创建消费“,”订阅主题“,通过执行 “完整实例”中的程序即可消费共享主题 + +## 流计算 + +通过 Explorer, 您可以轻松地完成对流的管理,从而更好地利用 TDengine 提供的流计算能力。 +点击左侧导航栏中的“流计算”,即可跳转至流计算配置管理页面。 +您可以通过以下两种方式创建流:流计算向导和自定义 SQL 语句。当前,通过流计算向导创建流时,暂不支持分组功能。通过自定义 SQL 创建流时,您需要了解 TDengine 提供的流计算 SQL 语句的语法,并保证其正确性。 + +注: 对于流计算的详细说明,可参考官方文档中关于“流式计算”章节,创建流计算之前需要先准备源数据库以及相应的超级表或表、输出的数据库。 + +### 流计算向导 + +1. 点击“创建流计算”按钮以后,选择流计算向导窗格,然后输入“流名称”; +2. 在“输出”部分,输入相应的“数据库”,“超级表”以及“子表前缀”; +3. 在“源”部分,选择相应的“数据库”,然后根据具体情况,选择使用“超级表”或“表”: + 1. 如果使用“超级表“,请从“超级表”下拉列表中选择相应的超级表, 并在“字段设置”区域,选择相应的字段 + 2. 如果使用“表“,请从“表”下拉列表中选择相应的表, 并在“字段设置”区域,选择相应的字段 +4. 对于窗口设置,根据需要选择”SESSION“, "STATE"或"INTERVAL", 并配置相应的值; +5. 对于”执行“部分,选择相应的”触发器“类型,并设置“Watermark”, "Ignore Expired", "DELETE_MARK", "FILL_HISTORY", "IGNORE UPDATE"; +6. 然后点击“创建” 按钮,即可创建对应的流计算。 + +### 使用 SQL 语句建流 + +1. 点击“创建流计算”按钮以后,选择流计算SQL窗格,然后输入类似如下的SQL语句(反引号内为源数据库以及相应的超级表或表、输出的数据库,请按您的环境更新反引号内的内容) + +```shell +CREATE STREAM `test_stream` TRIGGER WINDOW_CLOSE IGNORE EXPIRED 1 INTO `db_name`.`stable1` SUBTABLE(CONCAT('table1',tbname)) AS SELECT count(*) FROM `test_db`.`stable_name` PARTITION BY tbname INTERVAL(1m) +``` +2. 点击“创建”按钮,即可创建对应的流计算。 \ No newline at end of file diff --git a/docs/zh/17-operation/09-storage.md b/docs/zh/17-operation/09-storage.md new file mode 100644 index 0000000000..f6cdb347b4 --- /dev/null +++ b/docs/zh/17-operation/09-storage.md @@ -0,0 +1,52 @@ +--- +title: 多级存储 +--- + +## 多级存储 + +说明:多级存储功能仅企业版支持。 + +在默认配置下,TDengine 会将所有数据保存在 /var/lib/taos 目录下,而且每个 vnode 的数据文件保存在该目录下的不同目录。为扩大存储空间,尽量减少文件读取的瓶颈,提高数据吞吐率 TDengine 可通过配置系统参数 dataDir 让多个挂载的硬盘被系统同时使用。 + +除此之外,TDengine 也提供了数据分级存储的功能,将不同时间段的数据存储在挂载的不同介质上的目录里,从而实现不同“热度”的数据存储在不同的存储介质上,充分利用存储,节约成本。比如,最新采集的数据需要经常访问,对硬盘的读取性能要求高,那么用户可以配置将这些数据存储在 SSD 盘上。超过一定期限的数据,查询需求量没有那么高,那么可以存储在相对便宜的 HDD 盘上。 + +多级存储支持 3 级,每级最多可配置 16 个挂载点。 + +TDengine 多级存储配置方式如下(在配置文件/etc/taos/taos.cfg 中): + +``` +dataDir [path] +``` + +- path: 挂载点的文件夹路径 +- level: 介质存储等级,取值为 0,1,2。 + 0 级存储最新的数据,1 级存储次新的数据,2 级存储最老的数据,省略默认为 0。 + 各级存储之间的数据流向:0 级存储 -> 1 级存储 -> 2 级存储。 + 同一存储等级可挂载多个硬盘,同一存储等级上的数据文件分布在该存储等级的所有硬盘上。 + 需要说明的是,数据在不同级别的存储介质上的移动,是由系统自动完成的,用户无需干预。 +- primary: 是否为主挂载点,0(否)或 1(是),省略默认为 1。 + +在配置中,只允许一个主挂载点的存在(level=0,primary=1),例如采用如下的配置方式: + +``` +dataDir /mnt/data1 0 1 +dataDir /mnt/data2 0 0 +dataDir /mnt/data3 1 0 +dataDir /mnt/data4 1 0 +dataDir /mnt/data5 2 0 +dataDir /mnt/data6 2 0 +``` + +:::note + +1. 多级存储不允许跨级配置,合法的配置方案有:仅 0 级,仅 0 级+ 1 级,以及 0 级+ 1 级+ 2 级。而不允许只配置 level=0 和 level=2,而不配置 level=1。 +2. 禁止手动移除使用中的挂载盘,挂载盘目前不支持非本地的网络盘。 +3. 多级存储目前不支持删除已经挂载的硬盘的功能。 + +::: + +## 0 级负载均衡 + +在多级存储中,有且只有一个主挂载点,主挂载点承担了系统中最重要的元数据在座,同时各个 vnode 的主目录均存在于当前 dnode 主挂载点上,从而导致该 dnode 的写入性能受限于单个磁盘的 IO 吞吐能力。 + +从 TDengine 3.1.0.0 开始,如果一个 dnode 配置了多个 0 级挂载点,我们将该 dnode 上所有 vnode 的主目录均衡分布在所有的 0 级挂载点上,由这些 0 级挂载点共同承担写入负荷。在网络 I/O 及其它处理资源不成为瓶颈的情况下,通过优化集群配置,测试结果证明整个系统的写入能力和 0 级挂载点的数量呈现线性关系,即随着 0 级挂载点数量的增加,整个系统的写入能力也成倍增加。 diff --git a/docs/zh/18-data-transfer/01-taosX.md b/docs/zh/18-data-transfer/01-taosX.md new file mode 100644 index 0000000000..72d2cb2211 --- /dev/null +++ b/docs/zh/18-data-transfer/01-taosX.md @@ -0,0 +1,972 @@ +--- +title: 数据接入、同步和备份 +--- + +## 简介 + +为了能够方便地将各种数据源中的数据导入 TDengine 3.0,TDengine 3.0 企业版提供了一个全新的工具 taosX 用于帮助用户快速将其它数据源中的数据传输到 TDengine 中。 taosX 定义了自己的集成框架,方便扩展新的数据源。目前支持的数据源有 TDengine 自身(即从一个 TDengine 集群到另一个 TDengine 集群),Pi, OPC UA。除了数据接入外,taosX 还支持数据备份、数据同步、数据迁移以及数据导出功能。 + +**欲体验 taosX 的各种数据接入能力,请联系 TDengine 市场或销售团队。** + +## 使用前提 + +使用 taosX 需要已经部署好 TDengine 中的 taosd 和 taosAdapter,具体细节请参考 [系统部署](../../deployment/deploy) + +**使用限制**:taosX 只能用于企业版数据库服务端。 + +## 安装与配置 + +安装 taosX 需要使用独立的 taosX 安装包,其中除了 taosX 之外,还包含 Pi 连接器(限 Windows), OPC 连接器, InfluxDB 连接器, MQTT 连接器,以及必要的 Agent 组件,taosX + Agent + 某个连接器可以用于将相应数据源的数据同步到 TDengine。taosX 安装包中还包含了 taos-explorer 这个可视化管理组件 + +### Linux 安装 + +下载需要的 taosX 安装包,下文以安装包 `taosx-1.0.0-linux-x64.tar.gz` 为例展示如何安装: + +``` bash +# 在任意目录下解压文件 +tar -zxf taosx-1.0.0-linux-x64.tar.gz +cd taosx-1.0.0-linux-x64 + +# 安装 +sudo ./install.sh + +# 验证 +taosx -V +# taosx 1.0.0-494d280c (built linux-x86_64 2023-06-21 11:06:00 +08:00) +taosx-agent -V +# taosx-agent 1.0.0-494d280c (built linux-x86_64 2023-06-21 11:06:01 +08:00) + +# 卸载 +cd /usr/local/taosx +sudo ./uninstall.sh +``` + +**常见问题:** + +1. 安装后系统中增加了哪些文件? + * /usr/bin: taosx, taosx-agent, taos-explorer + * /usr/local/taosx/plugins: influxdb, mqtt, opc + * /etc/systemd/system:taosx.service, taosx-agent.service, taos-explorer.service + * /usr/local/taosx: uninstall.sh + * /etc/taox: agent.toml, explorer.toml + +2. taosx -V 提示 "Command not found" 应该如何解决? + * 检验问题1,保证所有的文件都被复制到对应的目录 + ``` bash + ls /usr/bin | grep taosx + ``` + +### Windows 安装 + +- 下载需要的 taosX 安装包,例如 taosx-1.0.0-Windows-x64-installer.exe,执行安装 +- 可使用 uninstall_taosx.exe 进行卸载 +- 命令行执行 ```sc start/stop taosx``` 启动/停止 taosx 服务 +- 命令行执行 ```sc start/stop taosx-agent``` 启动/停止 taosx-agent 服务 +- 命令行执行 ```sc start/stop taos-explorer``` 启动/停止 taosx-agent 服务 +- windows 默认安装在```C:\Program Files\taosX```,目录结构如下: +~~~ +├── bin +│   ├── taosx.exe +│   ├── taosx-srv.exe +│   ├── taosx-srv.xml +│   ├── taosx-agent.exe +│   ├── taosx-agent-srv.exe +│   ├── taosx-agent-srv.xml +│   ├── taos-explorer.exe +│   ├── taos-explorer-srv.exe +│   └── taos-explorer-srv.xml +├── plugins +│   ├── influxdb +│   │   └── taosx-inflxdb.jar +│   ├── mqtt +│   │   └── taosx-mqtt.exe +│   ├── opc +│   | └── taosx-opc.exe +│   ├── pi +│   | └── taosx-pi.exe +│   | └── taosx-pi-backfill.exe +│   | └── ... +└── config +│   ├── agent.toml +│   ├── explorer.toml +├── uninstall_taosx.exe +├── uninstall_taosx.dat +~~~ + +**运行模式** + +taosX 是进行数据同步与复制的核心组件,以下运行模式指 taosX 的运行模式,其它组件的运行模式在 taosX 的不同运行模式下与之适配。 + +## 命令行模式 + +可以直接在命令行上添加必要的参数直接启动 taosX 即为命令行模式运行。当命令行参数所指定的任务完成后 taosX 会自动停止。taosX 在运行中如果出现错误也会自动停止。也可以在任意时刻使用 ctrl+c 停止 taosX 的运行。本节介绍如何使用 taosX 的各种使用场景下的命令行。 + +### 命令行参数说明 + +**注意:部分参数暂无法通过 explorer设置【见:其他参数说明】,之后会逐步开放) ** + +命令行执行示例: + +```shell +taosx -f -t <其他参数> +``` + +以下参数说明及示例中若无特殊说明 `` 的格式均为占位符,使用时需要使用实际参数进行替换。 + +### DSN (Data Source Name) + +taosX 命令行模式使用 DSN 来表示一个数据源(来源或目的源),典型的 DSN 如下: + +```bash +# url-like +[+]://[[:@]:][/][?=[&=]] +|------|------------|---|-----------|-----------|------|------|----------|-----------------------| +|driver| protocol | | username | password | host | port | object | params | + +// url 示例 +tmq+ws://root:taosdata@localhost:6030/db1?timeout=never +``` +[] 中的数据都为可选参数。 + +1. 不同的驱动 (driver) 拥有不同的参数。driver 包含如下选项: + +- taos:使用查询接口从 TDengine 获取数据 +- tmq:启用数据订阅从 TDengine 获取数据 +- local:数据备份或恢复 +- pi: 启用 pi-connector从 pi 数据库中获取数据 +- opc:启用 opc-connector 从 opc-server 中获取数据 +- mqtt: 启用 mqtt-connector 获取 mqtt-broker 中的数据 +- kafka: 启用 Kafka 连接器从 Kafka Topics 中订阅消息写入 +- influxdb: 启用 influxdb 连接器从 InfluxDB 获取数据 +- csv:从 CSV 文件解析数据 + +2. +protocol 包含如下选项: +- +ws: 当 driver 取值为 taos 或 tmq 时使用,表示使用 rest 获取数据。不使用 +ws 则表示使用原生连接获取数据,此时需要 taosx 所在的服务器安装 taosc。 +- +ua: 当 driver 取值为 opc 时使用,表示采集的数据的 opc-server 为 opc-ua +- +da: 当 driver 取值为 opc 时使用,表示采集的数据的 opc-server 为 opc-da + +3. host:port 表示数据源的地址和端口。 +4. object 表示具体的数据源,可以是TDengine的数据库、超级表、表,也可以是本地备份文件的路径,也可以是对应数据源服务器中的数据库。 +5. username 和 password 表示该数据源的用户名和密码。 +6. params 代表了 dsn 的参数。 + +### 其它参数说明 + +1. parser 通过 --parser 或 -p 设置,设置 transform 的 parser 生效。可以通过 Explorer 在如 CSV,MQTT,KAFKA 数据源的任务配置进行设置。 + + 配置示例: + + ```shell + --parser "{\"parse\":{\"ts\":{\"as\":\"timestamp(ms)\"},\"topic\":{\"as\":\"varchar\",\"alias\":\"t\"},\"partition\":{\"as\":\"int\",\"alias\":\"p\"},\"offset\":{\"as\":\"bigint\",\"alias\":\"o\"},\"key\":{\"as\":\"binary\",\"alias\":\"k\"},\"value\":{\"as\":\"binary\",\"alias\":\"v\"}},\"model\":[{\"name\":\"t_{t}\",\"using\":\"kafka_data\",\"tags\":[\"t\",\"p\"],\"columns\":[\"ts\",\"o\",\"k\",\"v\"]}]}" + + ``` + +2. transform 通过 --transform 或 -T 设置,配置数据同步(仅支持 2.6 到 3.0 以及 3.0 之间同步)过程中对于表名及表字段的一些操作。暂无法通过 Explorer 进行设置。配置说明如下: + + ```shell + 1.AddTag,为表添加 TAG。设置示例:-T add-tag:=。 + 2.表重命名: + 2.1 重命名表限定 + 2.1.1 RenameTable:对所有符合条件的表进行重命名。 + 2.1.2 RenameChildTable:对所有符合条件的子表进行重命名。 + 2.1.3 RenameSuperTable:对所有符合条件的超级表进行重命名。 + 2.2 重命名方式 + 2.2.1 Prefix:添加前缀。 + 2.2.2 Suffix:添加后缀。 + 2.2.3 Template:模板方式。 + 2.2.4 ReplaceWithRegex:正则替换。taosx 1.1.0 新增。 + 重命名配置方式: + <表限定>:<重命名方式>:<重命名值> + 使用示例: + 1.为所有表添加前缀 + --transform rename-table:prefix: + 2.为符合条件的表替换前缀:prefix1 替换为 prefix2,以下示例中的 <> 为正则表达式的不再是占位符。 + -T rename-child-table:replace_with_regex:^prefix1(?)::prefix2_$old + + 示例说明:^prefix1(?) 为正则表达式,该表达式会匹配表名中包含以 prefix1 开始的表名并将后缀部分记录为 old,prefix2$old 则会使用 prefix2 与 old 进行替换。注意:两部分使用关键字符 :: 进行分隔,所以需要保证正则表达式中不能包含该字符。 + 若有更复杂的替换需求请参考:https://docs.rs/regex/latest/regex/#example-replacement-with-named-capture-groups 或咨询 taosx 开发人员。 + ``` + +3. jobs 指定任务并发数,仅支持 tmq 任务。暂无法通过 Explorer 进行设置。通过 --jobs `` 或 -j `` 进行设置。 +4. -v 用于指定 taosx 的日志级别,-v 表示启用 info 级别日志,-vv 对应 debug,-vvv 对应 trace。 + + +### 从 TDengine 到 TDengine 的数据同步 + +#### TDengine 3.0 -> TDengine 3.0 + +在两个相同版本 (都是 3.0.x.y)的 TDengine 集群之间将源集群中的存量及增量数据同步到目标集群中。 + +命令行模式下支持的参数如下: + +| 参数名称 | 说明 | 默认值 | +|-----------|------------------------------------------------------------------|----------------------------| +| group.id | 订阅使用的分组ID | 若为空则使用 hash 生成一个 | +| client.id | 订阅使用的客户端ID | taosx | +| timeout | 监听数据的超时时间,当设置为 never 表示 taosx 不会停止持续监听。 | 500ms | +| offset | 从指定的 offset 开始订阅,格式为 `:`,若有多个 vgroup 则用半角逗号隔开 | 若为空则从 0 开始订阅 | +| token | 目标源参数。 认证使用参数。 | 无 | + +示例: +```shell +taosx run \ + -f 'tmq://root:taosdata@localhost:6030/db1?group.id=taosx1&client.id=taosx&timeout=never&offset=2:10' \ + -t 'taos://root:taosdata@another.com:6030/db2' +``` + + + +#### TDengine 2.6 -> TDengine 3.0 + +将 2.6 版本 TDengine 集群中的数据迁移到 3.0 版本 TDengine 集群。 + +#### 命令行参数 + +| 参数名称 | 说明 | 默认值 | +|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------| +| libraryPath | 在 option 模式下指定 taos 库路径 | 无 | +| configDir | 指定 taos.cfg 配置文件路径 | 无 | +| mode | 数据源参数。 history 表示历史数据。 realtime 表示实时同步。 all 表示以上两种。 | history | +| restro | 数据源参数。 在同步实时数据前回溯指定时间长度的数据进行同步。 restro=10m 表示回溯最近 10 分钟的数据以后,启动实时同步。 | 无 | +| interval | 数据源参数。 轮询间隔 ,mode=realtime&interval=5s 指定轮询间隔为 5s | 无 | +| excursion | 数据源参数。 允许一段时间的乱序数据 | 500ms | +| stables | 数据源参数。 仅同步指定超级表的数据,多个超级表名用英文逗号 ,分隔 | 无 | +| tables | 数据源参数。 仅同步指定子表的数据,表名格式为 {stable}.{table} 或 {table},多个表名用英文逗号 , 分隔,支持 @filepath 的方式输入一个文件,每行视为一个表名,如 tables=@./tables.txt 表示从 ./tables.txt 中按行读取每个表名,空行将被忽略。 | 无 | +| select-from-stable | 数据源参数。 从超级表获取 select {columns} from stable where tbname in ({tbnames}) ,这种情况 tables 使用 {stable}.{table} 数据格式,如 meters.d0 表示 meters 超级表下面的 d0 子表。 | 默认使用 select \* from table 获取数据 | +| assert | 目标源参数。 taos:///db1?assert 将检测数据库是否存在,如不存在,将自动创建目标数据库。 | 默认不自动创建库。 | +| force-stmt | 目标源参数。 当 TDengine 版本大于 3.0 时,仍然使用 STMT 方式写入。 | 默认为 raw block 写入方式 | +| batch-size | 目标源参数。 设置 STMT 写入模式下的最大批次插入条数。 | | +| interval | 目标源参数。 每批次写入后的休眠时间。 | 无 | +| max-sql-length | 目标源参数。 用于建表的 SQL 最大长度,单位为 bytes。 | 默认 800_000 字节。 | +| failes-to | 目标源参数。 添加此参数,值为文件路径,将写入错误的表及其错误原因写入该文件,正常执行其他表的同步任务。 | 默认写入错误立即退出。 | +| timeout-per-table | 目标源参数。 为子表或普通表同步任务添加超时。 | 无 | +| update-tags | 目标源参数。 检查子表存在与否,不存在时正常建表,存在时检查标签值是否一致,不一致则更新。 | 无 | + +#### 示例 + +1.使用原生连接同步数据 + +```shell +taosx run \ + -f 'taos://td1:6030/db1?libraryPath=./libtaos.so.2.6.0.30&mode=all' \ + -t 'taos://td2:6030/db2?libraryPath=./libtaos.so.3.0.1.8&assert \ + -v +``` + +2.使用 WebSocket 同步数据超级表 stable1 和 stable2 的数据 + +```shell +taosx run \ + -f 'taos+ws://:@td1:6041/db1?stables=stable1,stable2' \ + -t 'taos+wss://td2:6041/db2?assert&token= \ + -v +``` + +### 从 TDengine 备份数据文件到本地 + +示例: +```shell +taosx run -f 'tmq://root:taosdata@td1:6030/db1' -t 'local:/path_directory/' + +``` +以上示例执行的结果及参数说明: + +将集群 td1 中的数据库 db1 的所有数据,备份到 taosx 所在设备的 /path_directory 路径下。 + +数据源(-f 参数的 DSN)的 object 支持配置为 数据库级(dbname)、超级表级(dbname.stablename)、子表/普通表级(dbname.tablename),对应备份数据的级别数据库级、超级表级、子表/普通表级 + + +### 从本地数据文件恢复到 TDengine + +#### 示例 +```shell +taosx run -f 'local:/path_directory/' -t 'taos://root:taosdata@td2:6030/db1?assert' +``` + +以上示例执行的结果: + +将 taosx 所在设备 /path_directory 路径下已备份的数据文件,恢复到集群 td2 的数据库 db1 中,如果 db1 不存在,则自动建库。 + +目标源(-t 参数的 DSN)中的 object 支持配置为数据库(dbname)、超级表(dbname.stablename)、子表/普通表(dbname.tablename),对应备份数据的级别数据库级、超级表级、子表/普通表级,前提是备份的数据文件也是对应的数据库级、超级表级、子表/普通表级数据。 + + +#### 常见错误排查 + +(1) 如果使用原生连接,任务启动失败并报以下错误: + +```text +Error: tmq to td task exec error + +Caused by: + [0x000B] Unable to establish connection +``` +产生原因是与数据源的端口链接异常,需检查数据源 FQDN 是否联通及端口 6030 是否可正常访问。 + +(2) 如果使用 WebSocket 连接,任务启动失败并报以下错误: + +```text +Error: tmq to td task exec error + +Caused by: + 0: WebSocket internal error: IO error: failed to lookup address information: Temporary failure in name resolution + 1: IO error: failed to lookup address information: Temporary failure in name resolution + 2: failed to lookup address information: Temporary failure in name resolution +``` + +使用 WebSocket 连接时可能遇到多种错误类型,错误信息可以在 ”Caused by“ 后查看,以下是几种可能的错误: + +- "Temporary failure in name resolution": DNS 解析错误,检查 IP 或 FQDN 是否能够正常访问。 +- "IO error: Connection refused (os error 111)": 端口访问失败,检查端口是否配置正确或是否已开启和可访问。 +- "IO error: received corrupt message": 消息解析失败,可能是使用了 wss 方式启用了 SSL,但源端口不支持。 +- "HTTP error: *": 可能连接到错误的 taosAdapter 端口或 LSB/Nginx/Proxy 配置错误。 +- "WebSocket protocol error: Handshake not finished": WebSocket 连接错误,通常是因为配置的端口不正确。 + +(3) 如果任务启动失败并报以下错误: + +```text +Error: tmq to td task exec error + +Caused by: + [0x038C] WAL retention period is zero +``` + +是由于源端数据库 WAL 配置错误,无法订阅。 + +解决方式: +修改数据 WAL 配置: + +```sql +alter database test wal_retention_period 3600; +``` + +### 从 OPC-UA 同步数据到 TDengine + +#### 配置参数 + +| 参数名称 | 类型 | 描述 | +|-----------------|--------|-----------------------------------------------------------------------------| +| interval | int | 采集间隔(单位:秒),默认为1秒 | +| concurrent | int | 采集器并发数,默认为1 | +| batch_size | int | 采集器上报的批次点位数,默认为100 | +| batch_timeout | int | 采集器上报的超时时间(单位:秒),默认为20秒 | +| connect_timeout | int | 连接的超时时间(单位:秒),默认为10秒 | +| request_timeout | int | 请求的超时时间(单位:秒),默认为10秒 | +| security_policy | string | OPC-UA连接安全策略(可配置为None/Basic128Rsa15/Basic256/Basic256Sha256) | +| security_mode | string | OPC-UA连接模式(可配置为None/Sign/SignAndEncrypt) | +| certificate | string | cert.pem的路径。当安全模式或策略不是”无”时生效 | +| private_key | string | key.pem的路径。 当安全模式或策略不是”无”时生效 | +| csv_config_file | string | 包含 OPC UA 的点位配置和表配置。与配置 csv_config_file 配置互斥,csv_config_file 优先生效| +| ua.nodes | string | OPC-UA 测点的 NodeID。和 opc_table_config 配置结合使用,两者需要同时配置。与配置 csv_config_file 配置互斥,csv_config_file 优先生效。配置格式为 ::,code 用于建子表。| +| opc_table_config | string | OPCUA 单列模式表配置。需要与 ua.nodes 配合使用。| +| debug | bool | 启用 OPC 连接器的 debug 日志。默认为 false。| +| enable | bool | 原始数据存储。默认为 false| +| path | string | 原始数据存储路径。enable 为 true 时必须配置。| +| keep | int | 原始数据保存天数。enable 为 true 时必须配置。| + +补充: +1. opc_table_config 说明: + +```json +{ + "stable_prefix": "meters", // 超级表前缀 + "column_configs": + [ + { + "column_name": "received_time", // 存储接收时间 + "column_type": "timestamp", + "column_alias": "ts", // 接收时间建表列用列名为 ts + "is_primary_key": true // 接收时间时间戳作为主键 + }, + { + "column_name": "original_time", + "column_type": "timestamp", + "column_alias": "ts_2", + "is_primary_key": false + }, + { + "column_name": "value", // 数据列 + "column_alias": "valueaa", // 数据列别名 + "is_primary_key": false + }, + { + "column_name": "quality", // 质量位列 + "column_type": "int", + "column_alias": "quality11", // 质量位列别名 + "is_primary_key": false + } + ] +} +``` + +#### 示例 + +1. 使用 ua.nodes 和 opc_table_config 的配置示例: +采集 nodeid 为 ns=2;i=2 和 ns=2;i=3 的点位,将其写入到集群 tdengine 的 opc 库中超级表前缀为 meters,如果 ns=2;i=2 的点位类型为 float 则会创建 meters_float 的超级表,超级表使用 opc 接收的数据作为时间戳索引列,并且保留原始时间戳列,原始时间戳列名为 ts_2,数据列存储为 valueaa,同时存储质量数据到 quality11 列。 + +```shell +taosx run \ + -f "opcua://uauser:uapass@localhost:4840?ua.nodes=ns=2;i=2::DSF1312,ns=2;i=3::DSF1313&opc_table_config={\"stable_prefix\": \"meters\", \"column_configs\": [{\"column_name\": \"received_time\", \"column_type\": \"timestamp\", \"column_alias\": \"ts\", \"is_primary_key\": true }, {\"column_name\": \"original_time\", \"column_type\": \"timestamp\", \"column_alias\": \"ts_2\", \"is_primary_key\": false }, {\"column_name\": \"value\", \"column_alias\": \"valueaa\", \"is_primary_key\": false }, {\"column_name\": \"quality\", \"column_type\": \"int\", \"column_alias\": \"quality11\", \"is_primary_key\": false } ] }" \ + -t "taos://tdengine:6030/opc" + + + +``` + +2. 使用 CSV 配置文件 + +```shell +taosx run -f "opcua://?csv_config_file=@" -t "taos+ws://tdengine:6041/opc" +``` + +#### CSV 配置文件模板 + + +### 从 OPC-DA 同步数据到 TDengine (Windows) + +#### 配置参数 + +| 参数名称 | 类型 | 描述 | +|-----------------|--------|-----------------------------------------------------------------------------| +| interval | int | 采集间隔(单位:秒),默认为1秒 | +| concurrent | int | 采集器并发数,默认为1 | +| batch_size | int | 采集器上报的批次点位数,默认为100 | +| batch_timeout | int | 采集器上报的超时时间(单位:秒),默认为20秒 | +| connect_timeout | int | 连接的超时时间(单位:秒),默认为10秒 | +| request_timeout | int | 请求的超时时间(单位:秒),默认为10秒 | +| csv_config_file | string | 包含 OPC UA 的点位配置和表配置。与 ua.nodes 两者之间需要配置一个。CSV 的配置模版参考:OPC 需求汇总及完成现状 | +| da.tags | string | OPC-UA 测点的 NodeID。和 opc_table_config 配置结合使用,两者需要同时配置。与配置 csv_config_file 配置互斥,csv_config_file 优先生效。| +| opc_table_config | string | OPCUA 单列模式表配置。需要与 da.tags 配合使用| +| debug | bool | 启用 OPC 连接器的 debug 日志。默认为 false。| +| enable | bool | 原始数据存储。默认为 false| +| path | string | 原始数据存储路径。enable 为 true 时必须配置。| +| keep | int | 原始数据保存天数。enable 为 true 时必须配置。| + +#### 应用示例 + +```shell +taosx run \ + -f "opc+da://Matrikon.OPC.Simulation.1?nodes=localhost&da.tags=Random.Real8::tb3::c1::int" + -t "taos://tdengine:6030/opc" +``` + +以上示例的执行结果: + +采集 Matrikon.OPC.Simulation.1 服务器上 OPC DA 中 da.tags 为 Random.Real8的数据,数据类型为int,对应在 TDengine 中以表名为 tb3 ,列名为c1,列类型为 int 型 schema 来创建表(如果对应表已存在,则直接采集数据并写入)。 + +#### 常见错误排查 + +(1) 如果使用原生连接,任务启动失败并打印如下错误: +```text +Error: tmq to td task exec error + +Caused by: + 0: Error occurred while creating a new object: [0x000B] Unable to establish connection +``` +解决方式: + +检查目标端 TDengine 的 FQDN 是否联通及端口 6030 是否可正常访问。 + +(2) 如果使用 WebSocket 连接任务启动失败并打印如下错误:: + +```text +Error: tmq to td task exec error + +Caused by: + 0: WebSocket internal error: IO error: failed to lookup address information: Temporary failure in name resolution + 1: IO error: failed to lookup address information: Temporary failure in name resolution + 2: failed to lookup address information: Temporary failure in name resolution +``` + +使用 WebSocket 连接时可能遇到多种错误类型,错误信息可以在 ”Caused by“ 后查看,以下是几种可能的错误: + +- "Temporary failure in name resolution": DNS 解析错误,检查目标端 TDengine的 IP 或 FQDN 是否能够正常访问。 +- "IO error: Connection refused (os error 111)": 端口访问失败,检查目标端口是否配置正确或是否已开启和可访问(通常为6041端口)。 +- "HTTP error: *": 可能连接到错误的 taosAdapter 端口或 LSB/Nginx/Proxy 配置错误。 +- "WebSocket protocol error: Handshake not finished": WebSocket 连接错误,通常是因为配置的端口不正确。 + +### 从 PI 同步数据到 TDengine (Windows) + +#### PI DSN 配置 + +PI DSN 的完整配置如下: + +```shell +pi://[:@]PIServerName/AFDatabaseName?[TemplateForPIPoint][&TemplateForAFElement][&PointList][&][&][&UpdateInterval] +``` + +在 taosX CLI 运行时支持的参数如下,其中 TemplateForPIPoint、TemplateForAFElement、PointList 三个参数至少配置一项: +- PISystemName:选填,连接配置 PI 系统服务名,默认值与 PIServerName 一致 +- MaxWaitLen:选填,数据最大缓冲条数,默认值为 1000 ,有效取值范围为 [1,10000] +- UpdateInterval:选填,PI System 取数据频率,默认值为 10000(毫秒:ms),有效取值范围为 [10,600000] +- TemplateForPIPoint:选填,使用 PI Point 模式将模板按照 element 的每个 Arrtribution 作为子表导入到 TDengine +- TemplateForAFElement:选填,使用 AF Point 模式将模板按照 element 的 Attribution 集合作为一个子表导入到 TDengine +- PointList:选填,使用 PointList 模式将指定csv文件中描述的点位信息在 PI 数据库中的数据导入到 TDengine + + +#### 应用示例 + +将位于服务器 WIN-2OA23UM12TN 中的 PI 数据库 Met1,模板 template1、template2配置为 TemplateForPIPoint模式,模板 template3、template4 配置为 TemplateForAFElement 模式,服务器 /home/ 路径下的点位文件 points.csv 配置为 PointList 模式,连接配置 PI 系统服务名为 PI,数据最大缓冲条数为1000,PI System 取数据频率为10000ms,将该库中的数据同步到 服务器 tdengine 的 pi 库中。完整的示例如下: + +```shell +taosx run \ + -f "pi://WIN-2OA23UM12TN/Met1?TemplateForPIPoint=template1,template2&TemplateForAFElement=template3,template4" \ + -t "taos://tdengine:6030/pi" +``` + + +#### 常见错误排查 + +(1) 如果使用原生连接,任务启动失败并打印如下错误: +```text +Error: tmq to td task exec error + +Caused by: + 0: Error occurred while creating a new object: [0x000B] Unable to establish connection +``` +解决方式: + +检查目标端 TDengine 的 FQDN 是否联通及端口 6030 是否可正常访问。 + +(2) 如果使用 WebSocket 连接任务启动失败并打印如下错误:: + +```text +Error: tmq to td task exec error + +Caused by: + 0: WebSocket internal error: IO error: failed to lookup address information: Temporary failure in name resolution + 1: IO error: failed to lookup address information: Temporary failure in name resolution + 2: failed to lookup address information: Temporary failure in name resolution +``` + +使用 WebSocket 连接时可能遇到多种错误类型,错误信息可以在 ”Caused by“ 后查看,以下是几种可能的错误: + +- "Temporary failure in name resolution": DNS 解析错误,检查目标端 TDengine的 IP 或 FQDN 是否能够正常访问。 +- "IO error: Connection refused (os error 111)": 端口访问失败,检查目标端口是否配置正确或是否已开启和可访问(通常为6041端口)。 +- "HTTP error: *": 可能连接到错误的 taosAdapter 端口或 LSB/Nginx/Proxy 配置错误。 +- "WebSocket protocol error: Handshake not finished": WebSocket 连接错误,通常是因为配置的端口不正确。 + + +### 从 InfluxDB 同步数据到 TDengine + +#### 命令行参数 + +将数据从 InfluxDB 同步至 TDengine 的命令,如下所示: + +```bash +taosx run --from "" --to "" +``` + +其中,InfluxDB DSN 符合 DSN 的通用规则,这里仅对其特有的参数进行说明: +- version: 必填,InfluxDB 的版本,主要用于区分 1.x 与 2.x 两个版本,二者使用不同的认证参数; +- version = 1.x + - username: 必填,InfluxDB 用户,该用户至少在该组织中拥有读取权限; + - password: 必填,InfluxDB 用户的登陆密码; +- version = 2.x + - orgId: 必填,InfluxDB 中的 Orgnization ID; + - token: 必填,InfluxDB 中生成的 API token, 这个 token 至少要拥有以上 Bucket 的 Read 权限; +- bucket: 必填,InfluxDB 中的 Bucket 名称,一次只能同步一个 Bucket; +- measurements: 非必填,可以指定需要同步的多个 Measurements(英文逗号分割),未指定则同步全部; +- beginTime: 必填,格式为:YYYY-MM-DD'T'HH:MM:SS'Z', 时区采用 UTC 时区,例如:2023-06-01T00:00:00+0800, 即北京时间2023-06-01 00:00:00(东八区时间); +- endTime: 非必填,可以不指定该字段或值为空,格式与beginTime相同;如果未指定,提交任务后,将持续进行数据同步; +- readWindow: 非必填,可以不指定该字段或值为空,可选项为D、H、M(天、时、分);如果未指定,则默认按 M 拆分读取窗口。 + +#### 示例 + +将位于 192.168.1.10 的 InfluxDB 中, Bucket 名称为 test_bucket, 从UTC时间2023年06月01日00时00分00秒开始的数据,通过运行在 192.168.1.20 上的 taoskeeper, 同步至 TDengine 的 test_db 数据库中,完整的命令如下所示: +```bash +# version = 1.x +taosx run \ + --from "influxdb+http://192.168.1.10:8086/?version=1.7&username=test&password=123456&bucket=test_bucket&measurements=&beginTime=2023-06-01T00:00:00+0800&readWindow=M" \ + --to "taos+http://192.168.1.20:6041/test_db" \ + -vv + +# version = 2.x +taosx run \ + --from "influxdb+http://192.168.1.10:8086/?version=2.7&orgId=3233855dc7e37d8d&token=OZ2sB6Ie6qcKcYAmcHnL-i3STfLVg_IRPQjPIzjsAQ4aUxCWzYhDesNape1tp8IsX9AH0ld41C-clTgo08CGYA==&bucket=test_bucket&measurements=&beginTime=2023-06-01T00:00:00+0800&readWindow=M" \ + --to "taos+http://192.168.1.20:6041/test_db" \ + -vv +``` + +在这个命令中,未指定endTime, 所以任务会长期运行,持续同步最新的数据。 + + +### 从 OpenTSDB 同步数据到 TDengine + +#### 命令行参数 + +将数据从 OpenTSDB 同步至 TDengine 的命令,如下所示: + +```bash +taosx run --from "" --to "" +``` + +其中,OpenTSDB DSN 符合 DSN 的通用规则,这里仅对其特有的参数进行说明: +- metrics: 非必填,可以指定需要同步的多个 Metrics(英文逗号分割),未指定则同步全部; +- beginTime: 必填,格式为:YYYY-MM-DD'T'HH:MM:SS'Z', 时区采用 UTC 时区,例如:2023-06-01T00:00:00+0800, 即北京时间2023-06-01 00:00:00(东八区时间); +- endTime: 非必填,可以不指定该字段或值为空,格式与beginTime相同;如果未指定,提交任务后,将持续进行数据同步; +- readWindow: 非必填,可以不指定该字段或值为空,可选项为D、H、M(天、时、分);如果未指定,则默认按分钟拆分读取窗口。 + +#### 示例 + +将位于 192.168.1.10 的 OpenTSDB 中, Metric 名称为 test_metric1 与 test_metric2 的两个数据源, 从UTC时间2023年06月01日00时00分00秒开始的数据,通过运行在 192.168.1.20 上的 taoskeeper, 同步至 TDengine 的 test_db 数据库中,完整的命令如下所示: + +```bash +taosx run \ + --from "opentsdb+http://192.168.1.10:4242/?metrics=test_metric1,test_metric2&beginTime=2023-06-01T00:00:00+0800&readWindow=M" \ + --to "taos+http://192.168.1.20:6041/test_db" \ + -vv +``` + +在这个命令中,未指定endTime, 所以任务会长期运行,持续同步最新的数据。 + + +### 从 MQTT 同步数据到 TDengine + +目前,MQTT 连接器仅支持从 MQTT 服务端消费 JSON 格式的消息,并将其同步至 TDengine. 命令如下所示: + +```bash +taosx run --from "" --to "" --parser "@" +``` + +其中: +- `--from` 用于指定 MQTT 数据源的 DSN +- `--to` 用于指定 TDengine 的 DSN +- `--parser` 用于指定一个 JSON 格式的配置文件,该文件决定了如何解析 JSON 格式的 MQTT 消息,以及写入 TDengine 时的超级表名、子表名、字段名称和类型,以及标签名称和类型等。 + +#### MQTT DSN 配置 + +MQTT DSN 符合 DSN 的通用规则,这里仅对其特有的参数进行说明: +- topics: 必填,用于配置监听的 MQTT 主题名称和连接器支持的最大 QoS, 采用 `::` 的形式;支持配置多个主题,使用逗号分隔;配置主题时,还可以使用 MQTT 协议的支持的通配符#和+; +- version: 非必填,用于配置 MQTT 协议的版本,支持的版本包括:3.1/3.1.1/5.0, 默认值为3.1; +- clean_session: 非必填,用于配置连接器作为 MQTT 客户端连接至 MQTT 服务端时,服务端是否保存该会话信息,其默认值为 true, 即不保存会话信息; +- client_id: 必填,用于配置连接器作为 MQTT 客户端连接至 MQTT 服务端时的客户端 id; +- keep_alive: 非必填,用于配置连接器作为 MQTT 客户端,向 MQTT 服务端发出 PINGREG 消息后的等待时间,如果连接器在该时间内,未收到来自 MQTT 服务端的 PINGREQ, 连接器则主动断开连接;该配置的单位为秒,默认值为 60; +- ca: 非必填,用于指定连接器与 MQTT 服务端建立 SSL/TLS 连接时,使用的 CA 证书,其值为在证书文件的绝对路径前添加@, 例如:@/home/admin/certs/ca.crt; +- cert: 非必填,用于指定连接器与 MQTT 服务端建立 SSL/TLS 连接时,使用的客户端证书,其值为在证书文件的绝对路径前添加@, 例如:@/home/admin/certs/client.crt; +- cert_key: 非必填,用于指定连接器与 MQTT 服务端建立 SSL/TLS 连接时,使用的客户端私钥,其值为在私钥文件的绝对路径前添加@, 例如:@/home/admin/certs/client.key; +- log_level: 非必填,用于配置连接器的日志级别,连接器支持 error/warn/info/debug/trace 5种日志级别,默认值为 info. + +一个完整的 MQTT DSN 示例如下: +```bash +mqtt://:@:8883?topics=testtopic/1::2&version=3.1&clean_session=true&log_level=info&client_id=taosdata_1234&keep_alive=60&ca=@/home/admin/certs/ca.crt&cert=@/home/admin/certs/client.crt&cert_key=@/home/admin/certs/client.key +``` + +#### MQTT 连接器的解释器配置 + +连接器的解释器配置文件,即`--parser`配置项的参数,它的值为一个 JSON 文件,其配置可分为`parse`和`model`两部分,模板如下所示: + +```json +{ + "parse": { + "payload": { + "json": [ + { + "name": "ts", + "alias": "ts", + "cast": "TIMESTAMP" + }, + ... + ] + } + }, + "model": { + "using": "", + "name": "{alias}", + "columns": [ ... ], + "tags": [ ... ] + } +} +``` + +各字段的说明如下: +- parse 部分目前仅支持 json 一种 payload, json 字段的值是一个由 JSON Object 构成的 JSON Array: + - 每个 JSON Ojbect 包括 name, alias, cast 三个字段; + - name 字段用于指定如何从 MQTT 消息中提取字段,如果 MQTT 消息是一个简单的 JSON Object, 这里可以直接设置其字段名;如果 MQTT 消息是一个复杂的 JSON Object, 这里可以使用 JSON Path 提取字段,例如:`$.data.city`; + - alias 字段用于命名 MQTT 消息中的字段同步至 TDengine 后使用的名称; + - cast 字段用于指定 MQTT 消息中的字段同步至 TDengine 后使用的类型。 +- model 部分用于设置 TDengine 超级表、子表、列和标签等信息: + - using 字段用于指定超级表名称; + - name 字段用于指定子表名称,它的值可以分为前缀和变量两部分,变量为 parse 部分设置的 alias 的值,需要使用{}, 例如:d{id}; + - columns 字段用于设置 MQTT 消息中的哪些字段作为 TDengine 超级表中的列,取值为 parse 部分设置的 alias 的值;需要注意的是,这里的顺序会决定 TDengine 超级表中列的顺序,因此第一列必须为 TIMESTAMP 类型; + - tags 字段用于设置 MQTT 消息中的哪些字段作为 TDengine 超级表中的标签,取值为 parse 部分设置的 alias 的值。 + +#### 举例说明 + +在 192.168.1.10 的 1883 端口运行着一个 MQTT broker, 用户名、口令分别为admin, 123456; 现欲将其中的消息,通过运行在 192.168.1.20 的 taosadapter 同步至 TDengine 的 test 数据库中。MQTT 消息格式为: + +```json +{ + "id": 1, + "current": 10.77, + "voltage": 222, + "phase": 0.77, + "groupid": 7, + "location": "California.SanDiego" +} +``` + +MQTT 消息同步至 TDengine 时, 如果采用 meters 作为超级表名,前缀“d”拼接id字段的值作为子表名,ts, id, current, voltage, phase作为超级表的列,groupid, location作为超级表的标签,其解释器的配置如下: +```json +{ + "parse": { + "payload": { + "json": [ + { + "name": "ts", + "alias": "ts", + "cast": "TIMESTAMP" + }, + { + "name": "id", + "alias": "id", + "cast": "INT" + }, + { + "name": "voltage", + "alias": "voltage", + "cast": "INT" + }, + { + "name": "phase", + "alias": "phase", + "cast": "FLOAT" + }, + { + "name": "current", + "alias": "current", + "cast": "FLOAT" + }, + { + "name": "groupid", + "alias": "groupid", + "cast": "INT" + }, + { + "name": "location", + "alias": "location", + "cast": "VARCHAR(20)" + } + ] + } + }, + "model": { + "name": "d{id}", + "using": "meters", + "columns": [ + "ts", + "id", + "current", + "voltage", + "phase" + ], + "tags": [ + "groupid", + "location" + ] + } +} +``` + +如果以上parser配置位于`/home/admin/parser.json`中,那么完整的命令如下所示: + +```bash +taosx run \ + -f "mqtt://admin:123456@192.168.1.10:1883?topics=testtopic/1::2&version=3.1&clean_session=true&log_level=info&client_id=1234&keep_alive=60" \ + -t "taos+ws://192.168.1.20:6041/test" + --parser "@/home/admin/parser.json" + --verbose +``` + +### 从 Kafka 同步数据到 TDengine + +#### 命令行参数 + +taosx 支持从 Kafka 消费数据,写入 TDengine。命令如下所示: +```sehll +taosx run -f "" -t "" +``` +或 +```shell +taosx run -f "" -t "" --parser "@" +``` +其中: +- -f或--from: Kafka 的 DSN +- -t或--to :TDengine 的 DSN +- --parser :一个 JSON 格式的配置文件,或JSON格式的字符串。 + +#### Kafka DSN 配置的配置 + +| 参数 | 说明 | 必填? | 缺省值 | 适用于 | 示例 | +|-----|---------------|----------|---------|---------|----------| +| group| 消费者的group。允许组为空字符串,在这种情况下,生成的消费者将是无组的 | 否 | "" | 源端 | | +| topics | 指定要使用的主题。指定主题的所有可用分区都将被使用,除非在指定 topic_partitions 时被覆盖。| 该参数或topic_partitions必须至少指定一个,以便将主题分配给消费者。| None | 源端 | topics=tp1,tp2 | +| topic_partitions | 显式指定要使用的主题分区。只使用已标识主题的指定分区。 | 该参数或topics必须至少指定一个,以便将主题分配给消费者。 | None | 源端 | topic_partitions=tp1:0..2,tp2:1 | +| fallback_offset | topic偏移量时可能的值:- Earliest:接收最早的可用偏移量; - Latest:接收最近的偏移量; - ByTime(i64):用于请求在某一特定时间(ms)之前的所有消息;Unix时间戳(毫秒) | 否 | Earliest | 源端 | fallback_offset=Earliest | +| offset_storage | 定义在获取或提交组偏移量时,要使用的可用存储:- Zookeeper:基于Zookeeper的存储(从kafka 0.8.1开始可用);- Kafka:基于Kafka的存储(从Kafka 0.8.2开始可用)。这是组存储其偏移量的首选方法。 | 否 | Kafka | 源端 | offset_storage=Kafka | +| timeout | 从kafka订阅数据时,如果超时后没有获取到有效数据,退出 | 否 | 500 | 源端 | timeout=never | +| use_ssl | 是否使用SSL认证 | 否 | | 源端 | | +| cert | SSL证书的文件路径 | 否 | | | 源端 | | +| cert_key | SSL证书key的文件路径 | 否 | | 源端 || + + +#### 示例一 + +从192.168.1.92服务器的Kafka实例中消费数据,同步到192.168.1.92上的TDengine,不使用parser。 + +1. kafka + +```shell +#!/bin/bash +KAFKA_HOME=/root/zyyang/kafka_2.13-3.1.0 +$KAFKA_HOME/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --topic tp1 --delete +$KAFKA_HOME/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --topic tp2 --delete +$KAFKA_HOME/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --topic tp1 --partitions 5 --replication-factor 1 --create +$KAFKA_HOME/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --topic tp2 --partitions 1 --replication-factor 1 --create +$KAFKA_HOME/bin/kafka-console-producer.sh --bootstrap-server 127.0.0.1:9092 --topic tp1 << EOF +{"id": 1, "message": "hello"} +{"id": 2, "message": "hello"} +{"id": 3, "message": "hello"} +{"id": 4, "message": "hello"} +{"id": 5, "message": "hello"} +EOF +$KAFKA_HOME/bin/kafka-console-producer.sh --bootstrap-server 127.0.0.1:9092 --topic tp2 << EOF +{"id": 1, "message": "aaa"} +{"id": 2, "message": "aaa"} +{"id": 3, "message": "aaa"} +{"id": 4, "message": "aaa"} +{"id": 5, "message": "aaa"} +EOF +$KAFKA_HOME/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --topic tp1 --describe +$KAFKA_HOME/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --topic tp2 --describe +``` + +2. TDengine + +```shell +drop database if exists kafka_to_taos; +create database if not exists kafka_to_taos precision 'ms'; +use kafka_to_taos; +``` + +3. taosx + +```shell +taosx run -f "kafka://192.168.1.92:9092/?topics=tp1,tp2&timeout=5000" -t "taos://192.168.1.92:6030/kafka_to_taos" --parser "{\"parse\":{\"ts\":{\"as\":\"timestamp(ms)\"},\"topic\":{\"as\":\"varchar\",\"alias\":\"t\"},\"partition\":{\"as\":\"int\",\"alias\":\"p\"},\"offset\":{\"as\":\"bigint\",\"alias\":\"o\"},\"key\":{\"as\":\"binary\",\"alias\":\"k\"},\"value\":{\"as\":\"binary\",\"alias\":\"v\"}},\"model\":[{\"name\":\"t_{t}\",\"using\":\"kafka_data\",\"tags\":[\"t\",\"p\"],\"columns\":[\"ts\",\"o\",\"k\",\"v\"]}]}" +``` + +#### 示例2 + +从192.168.1.92服务器的Kafka实例中消费数据,同步到192.168.1.92上的TDengine,使用parser解析value中的JSON数据。 + +1. kafka,同“示例1” +2. TDengine,同“示例1” +3. Taosx + +```shell +taosx run -f "kafka://192.168.1.92:9092/?topics=tp1,tp2&timeout=5000" -t "taos://192.168.0.201:6030/kafka_to_taos" --parser "{\"parse\":{\"ts\":{\"as\":\"timestamp(ms)\"},\"topic\":{\"as\":\"varchar\",\"alias\":\"t\"},\"partition\":{\"as\":\"int\",\"alias\":\"p\"},\"offset\":{\"as\":\"bigint\",\"alias\":\"o\"},\"value\":{\"json\":[\"id::int\",\"message::binary\"]}},\"model\":[{\"name\":\"t_{t}\",\"using\":\"kafka_data\",\"tags\":[\"t\",\"p\"],\"columns\":[\"ts\",\"o\",\"id\",\"message\"]}]}" +``` + +## 服务模式 + +在服务模式下, 一共需要三个组件协同完成数据迁移。 taosX,Agent 以及 taosExplorer 均已服务态运行,各种操作通过 taosExplorer 的图形界面进行。taos-Explorer 组件除了数据迁移之外,还提供了使用 TDengine 的图形化界面。 + +### 部署 taosX + +#### 配置 + +taosX 仅支持通过命令行参数进行配置。服务模式下,taosX 支持的命令行参数可以通过以下方式查看: + +``` +taosx serve --help +``` + +建议通过 Systemd 的方式,启动 taosX 的服务模式,其 Systemd 的配置文件位于:`/etc/systemd/system/taosx.service`. 如需修改 taosX 的启动参数,可以编辑该文件中的以下行: + +``` +ExecStart=/usr/bin/taosx serve -v +``` + +修改后,需执行以下命令重启 taosX 服务,使配置生效: + +``` +systemctl daemon-reload +systemctl restart taosx +``` + +#### 启动 + +Linux 系统上以 Systemd 的方式启动 taosX 的命令如下: + +```shell +systemctl start taosx +``` + +Windows 系统上,请在 "Services" 系统管理工具中找到 "taosX" 服务,然后点击 "启动这个服务"。 + +#### 问题排查 + +1. 如何修改 taosX 的日志级别? + +taosX 的日志级别是通过命令行参数指定的,默认的日志级别为 Info, 具体参数如下: +- INFO: `taosx serve -v` +- DEBUG: `taosx serve -vv` +- TRACE: `taosx serve -vvv` + +Systemd 方式启动时,如何修改命令行参数,请参考“配置”章节。 + +2. 如何查看 taosX 的日志? + +以 Systemd 方式启动时,可通过 journalctl 命令查看日志。以滚动方式,实时查看最新日志的命令如下: + +``` +journalctl -u taosx -f +``` + +### 部署 Agent + +#### 配置 + +Agent 默认的配置文件位于`/etc/taos/agent.toml`, 包含以下配置项: +- endpoint: 必填,taosX 的 GRPC endpoint +- token: 必填,在 taosExplorer 上创建 agent 时,产生的token +- debug_level: 非必填,默认为 info, 还支持 debug, trace 等级别 + +如下所示: + +```TOML +endpoint = "grpc://:6055" +token = "" +log_level = "debug" +``` + +日志保存时间设置 +日志保存的天数可以通过环境变量进行设置 TAOSX_LOGS_KEEP_DAYS, 默认为 30 天。 + +```shell +export TAOSX_LOGS_KEEP_DAYS=7 +``` + +#### 启动 + +Linux 系统上 Agent 可以通过 Systemd 命令启动: + +``` +systemctl start taosx-agent +``` + +Windows 系统上通过系统管理工具 "Services" 找到 taosx-agent 服务,然后启动它。 + +#### 问题排查 + +可以通过 journalctl 查看 Agent 的日志 + +``` +journalctl -u taosx-agent -f +``` + +### 部署 taosExplorer + + +### 数据同步功能 + +请参考 taosExplorer \ No newline at end of file diff --git a/docs/zh/18-data-transfer/02-explorer.md b/docs/zh/18-data-transfer/02-explorer.md new file mode 100644 index 0000000000..6d410c4997 --- /dev/null +++ b/docs/zh/18-data-transfer/02-explorer.md @@ -0,0 +1,128 @@ +--- +title: 基于可视化界面的数据迁移 +--- + +本节讲述使用 taos Explorer 的可视化界面进行数据迁移,使用此功能需要依赖 taosd, taosAdapter, taosX, taos-explorer 等几个服务组件。关于 taosd 和 taosAdapter 的部署请参考 [系统部署](../../deployment/deploy),[taosX](../taosX),以及 [部署 taos-explorer](../../operation/explorer) + +## 功能入口 + +点击 explorer 左侧功能列表中的 "数据写入",可以配置不同类型的数据源,包括 TDengine Subscription, PI, OPC-UA, OPC-DA, InfluxDB, MQTT,Kafka, CSV 等,将它们的数据写入到当前正在被管理的 TDengine 集群中。 + +## TDengine 订阅 + +进入TDengine订阅任务配置页面: +1. 在连接协议栏中,配置连接协议,默认为原生连接,可配置为WS、WSS; +2. 在服务器栏中配置服务器的 IP 或域名; +3. 在端口栏中配置连接的端口号,默认值为6030; +4. 在主题栏中,配置可以配置订阅一个或多个数据库,或超级表或普通表,也可以是一个已创建的 Topic; +5. 在认证栏,可以配置访问 TDengine 的用户名密码,用户名默认值为 root,密码默认值为 taosdata;如果数据源为云服务实例,则可以选择令牌认证方式并配置实例 token; +6. 在订阅初始位置栏,可配置从最早数据(earliest)或最晚(latest)数据开始订阅,默认为 earliest; +7. 在超时栏配置超时时间,可配置为 never: 表示无超时时间,持续进行订阅,也可指定超时时间:5s, 1m 等,支持单位 ms(毫秒),s(秒),m(分钟),h(小时),d(天),M(月),y(年)。 +8. 在目标数据库栏中,选择本地 TDengine 的库作为目标库,点击 submit,即可启动一个 TDengine 订阅任务。 + +## Pi + +1. 在 PI 数据接入页面,设置 PI 服务器的名称、AF 数据库名称。 +2. 在监测点集栏,可以配置选择 Point 模式监测点集合、Point 模式监测的 AF 模板、AF 模式监测的 AF 模板。 +3. 在 PI 系统设置栏,可以配置 PI 系统名,默认为 PI 服务器名。 +4. 在 Data Queue 栏,可以配置 PI 连接器运行参数:MaxWaitLen(数据最大缓冲条数),默认值为 1000 ,有效取值范围为 [1,10000];UpdateInterval(PI System 取数据频率),默认值为 10000(毫秒:ms),有效取值范围为 [10,600000];重启补偿时间(Max Backfill Range,单位:天),每次重启服务时向前补偿该天数的数据,默认为1天。 +5. 在目标数据库栏,选择需要写入的 TDengine 数据库,点击 submit ,即可启动一个 PI 数据接入任务。 + +## OPC-UA + +1. 在 OPC-UA页面,配置 OPC-server 的地址,输入格式为 127.0.0.1:6666/OPCUA/ServerPath。 +2. 在认证栏,选择访问方式。可以选择匿名访问、用户名密码访问、证书访问。使用证书访问时,需配置证书文件信息、私钥文件信息、OPC-UA 安全协议和 OPC-UA 安全策略 +3. 在 Data Sets 栏,配置点位信息。(可通过“选择”按钮选择正则表达式过滤点位,每次最多能过滤出10条点位);点位配置有两种方式:1.手动输入点位信息 2.上传csv文件配置点位信息 +4. 在连接配置栏,配置连接超时间隔和采集超时间隔(单位:秒),默认值为10秒。 +5. 在采集配置栏,配置采集间隔(单位:秒)、点位数量、采集模式。采集模式可选择observe(轮询模式)和subscribe(订阅模式),默认值为observe。 +6. 在库表配置栏,配置目标 TDengine 中存储数据的超级表、子表结构信息。 +7. 在其他配置栏,配置并行度、单次采集上报批次(默认值100)、上报超时时间(单位:秒,默认值10)、是否开启debug级别日志。 +8. 在目标数据库栏,选择需要写入的 TDengine 数据库,点击 submit,即可启动一个 OPC-UA 数据接入任务。 + +## OPC-DA + +1. 在 OPC-DA页面,配置 OPC-server 的地址,输入格式为 127.0.0.1<,localhost>/Matrikon.OPC.Simulation.1。 +2. 在数据点栏,配置 OPC-DA 采集点信息。(可通过“选择”按钮选择正则表达式过滤点位,每次最多能过滤出10条点位)。点位配置有两种方式:1.手动输入点位信息 2.上传csv文件配置点位信息 +3. 在连接栏,配置连接超时时间(单位:秒,默认值为10秒)、采集超时时间(单位:秒,默认值为10秒)。 +4. 在库表配置栏,配置目标 TDengine 中存储数据的超级表、子表结构信息。 +5. 在其他配置栏,配置并行度、单次采集上报批次(默认值100)、上报超时时间(单位:秒,默认值10)、是否开启debug级别日志。 +6. 在目标数据库栏,选择需要写入的 TDengine 数据库,点击 submit,即可启动一个 OPC-DA 数据接入任务。 + +## InfluxDB + +进入 InfluxDB 数据源同步任务的编辑页面后: +1. 在服务器地址输入框, 输入 InfluxDB 服务器的地址,可以输入 IP 地址或域名,此项为必填字段; +2. 在端口输入框, 输入 InfluxDB 服务器端口,默认情况下,InfluxDB 监听8086端口的 HTTP 请求和8088端口的 HTTPS 请求,此项为必填字段; +3. 在组织 ID 输入框,输入将要同步的组织 ID,此项为必填字段; +4. 在令牌 Token 输入框,输入一个至少拥有读取这个组织 ID 下的指定 Bucket 权限的 Token, 此项为必填字段; +5. 在同步设置的起始时间项下,通过点选选择一个同步数据的起始时间,起始时间使用 UTC 时间, 此项为必填字段; +6. 在同步设置的结束时间项下,当不指定结束时间时,将持续进行最新数据的同步;当指定结束时间时,将只同步到这个结束时间为止; 结束时间使用 UTC 时间,此项为可选字段; +7. 在桶 Bucket 输入框,输入一个需要同步的 Bucket,目前只支持同步一个 Bucket 至 TDengine 数据库,此项为必填字段; +8. 在目标数据库下拉列表,选择一个将要写入的 TDengine 目标数据库 (注意:目前只支持同步到精度为纳秒的 TDengine 目标数据库),此项为必填字段; +9. 填写完成以上信息后,点击提交按钮,即可直接启动从 InfluxDB 到 TDengine 的数据同步。 + +## MQTT + +进入 MQTT 数据源同步任务的编辑页面后: +1. 在 MQTT 地址卡片,输入 MQTT 地址,必填字段,包括 IP 和 端口号,例如:192.168.1.10:1883; +2. 在认证卡片,输入 MQTT 连接器访问 MQTT 服务器时的用户名和密码,这两个字段为选填字段,如果未输入,即采用匿名认证的方式; +3. 在 SSL 证书卡片,可以选择是否打开 SSL/TLS 开关,如果打开此开关,MQTT 连接器和 MQTT 服务器之间的通信将采用 SSL/TLS 的方式进行加密;打开这个开关后,会出现 CA, 客户端证书和客户端私钥三个必填配置项,可以在这里输入证书和私钥文件的内容; +4. 在连接卡片,可以配置以下信息: + - MQTT 协议:支持3.1/3.1.1/5.0三个版本; + - Client ID: MQTT 连接器连接 MQTT 服务器时所使用的客户端 ID, 用于标识客户端的身份; + - Keep Alive: 用于配置 MQTT 连接器与 MQTT 服务器之间的Keep Alive时间,默认值为60秒; + - Clean Session: 用于配置 MQTT 连接器是否以Clean Session的方式连接至 MQTT 服务器,默认值为True; + - 订阅主题及 QoS 配置:这里用来配置监听的 MQTT 主题,以及该主题支持的最大QoS, 主题和 QoS 的配置之间用::分隔,多个主题之间用,分隔,主题的配置可以支持 MQTT 协议的通配符#和+; +5. 在其他卡片,可以配置 MQTT 连接器的日志级别,支持 error, warn, info, debug, trace 5个级别,默认值为 info; +6. MQTT Payload 解析卡片,用于配置如何解析 MQTT 消息: + - 配置表的第一行为 ts 字段,该字段为 TIMESTAMP 类型,它的值为 MQTT 连接器收到 MQTT 消息的时间; + - 配置表的第二行为 topic 字段,为该消息的主题名称,可以选择将该字段作为列或者标签同步至 TDengine; + - 配置表的第三行为 qos 字段,为该消息的 QoS 属性,可以选择将该字段作为列或者标签同步至 TDengine; + - 剩余的配置项皆为自定义字段,每个字段都需要配置:字段(来源),列(目标),列类型(目标)。字段(来源)是指该 MQTT 消息中的字段名称,当前仅支持 JSON 类型的 MQTT 消息同步,可以使用 JSON Path 语法从 MQTT 消息中提取字段,例如:$.data.id; 列(目标)是指同步至 TDengine 后的字段名称;列类型(目标)是指同步至 TDengine 后的字段类型,可以从下拉列表中选择;当且仅当以上3个配置都填写后,才能新增下一个字段; + - 如果 MQTT 消息中包含时间戳,可以选择新增一个自定义字段,将其作为同步至 TDengine 时的主键;需要注意的是,MQTT 消息中时间戳的仅支持 Unix Timestamp格式,且该字段的列类型(目标)的选择,需要与创建 TDengine 数据库时的配置一致; + - 子表命名规则:用于配置子表名称,采用“前缀+{列类型(目标)}”的格式,例如:d{id}; + - 超级表名:用于配置同步至 TDengine 时,采用的超级表名; +7. 在目标数据库卡片,可以选择同步至 TDengine 的数据库名称,支持直接从下拉列表中选择。 +8. 填写完成以上信息后,点击提交按钮,即可直接启动从 MQTT 到 TDengine 的数据同步。 + +## Kafka + +1. 在Kafka页面,配置Kafka选项,必填字段,包括:bootstrap_server,例如192.168.1.92:9092; +2. 如果使用SSL认证,在SSL认证卡中,选择cert和cert_key的文件路径; +3. 配置其他参数,topics、topic_partitions这2个参数至少填写一个,其他参数有默认值; +4. 如果消费的Kafka数据是JSON格式,可以配置parser卡片,对数据进行解析转换; +5. 在目标数据库卡片中,选择同步到TDengine的数据库名称,支持从下拉列表中选择; +6. 填写完以上信息后,点击提交按钮,即可启动从Kafka到TDengine的数据同步。 + +## CSV + +1. 在CSV页面,配置CSV选项,可设置忽略前N行,可输入具体的数字 +2. CSV的写入配置,设置批次写入量,默认是1000 +3. CSV文件解析,用于获取CSV对应的列信息: + - 上传CSV文件或者输入CSV文件的地址 + - 选择是否包包含Header + - 包含Header情况下直接执行下一步,查询出对应CSV的列信息,获取CSV的配置信息 + - 不包含Header情况,需要输入自定列信息,并以逗号分隔,然后下一步,获取CSV的配置信息 + - CSV的配置项,每个字段都需要配置:CSV列,DB列,列类型(目标),主键(整个配置只能有一个主键,且主键必须是TIMESTAMP类型),作为列,作为Tag。CSV列是指该 CSV文件中的列或者自定义的列;DB列是对应的数据表的列 + - 子表命名规则:用于配置子表名称,采用“前缀+{列类型(目标)}”的格式,例如:d{id}; + - 超级表名:用于配置同步至 TDengine 时,采用的超级表名; +4. 在目标数据库卡片,可以选择同步至 TDengine 的数据库名称,支持直接从下拉列表中选择。 +5. 填写完成以上信息后,点击提交按钮,即可直接启动从 CSV到 TDengine 的数据同步。 + + +## 备份和恢复 + +您可以将当前连接的 TDengine 集群中的数据备份至一个或多个本地文件中,稍后可以通过这些文件进行数据恢复。本章节将介绍数据备份和恢复的具体步骤。 + +### 备份数据到本地文件 + +1. 进入系统管理页面,点击【备份】进入数据备份页面,点击右上角【新增备份】。 +2. 在数据备份配置页面中可以配置三个参数: + - 备份周期:必填项,配置每次执行数据备份的时间间隔,可通过下拉框选择每天、每 7 天、每 30 天执行一次数据备份,配置后,会在对应的备份周期的0:00时启动一次数据备份任务; + - 数据库:必填项,配置需要备份的数据库名(数据库的 wal_retention_period 参数需大于0); + - 目录:必填项,配置将数据备份到 taosX 所在运行环境中指定的路径下,如 /root/data_backup; +3. 点击【确定】,可创建数据备份任务。 + +### 从本地文件恢复 + +1. 完成数据备份任务创建后,在页面中对应的数据备份任务右侧点击【数据恢复】,可将已经备份到指定路径下的数据恢复到当前 TDengine 中。 \ No newline at end of file From 1aa20c96e105d696902a1ea888cc90985e9af556 Mon Sep 17 00:00:00 2001 From: Wade Zhang Date: Fri, 25 Aug 2023 19:58:03 +0800 Subject: [PATCH 065/120] doc: fix broken links --- docs/zh/17-operation/09-storage.md | 4 ++++ docs/zh/18-data-transfer/02-explorer.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/zh/17-operation/09-storage.md b/docs/zh/17-operation/09-storage.md index f6cdb347b4..ca3f3af49b 100644 --- a/docs/zh/17-operation/09-storage.md +++ b/docs/zh/17-operation/09-storage.md @@ -50,3 +50,7 @@ dataDir /mnt/data6 2 0 在多级存储中,有且只有一个主挂载点,主挂载点承担了系统中最重要的元数据在座,同时各个 vnode 的主目录均存在于当前 dnode 主挂载点上,从而导致该 dnode 的写入性能受限于单个磁盘的 IO 吞吐能力。 从 TDengine 3.1.0.0 开始,如果一个 dnode 配置了多个 0 级挂载点,我们将该 dnode 上所有 vnode 的主目录均衡分布在所有的 0 级挂载点上,由这些 0 级挂载点共同承担写入负荷。在网络 I/O 及其它处理资源不成为瓶颈的情况下,通过优化集群配置,测试结果证明整个系统的写入能力和 0 级挂载点的数量呈现线性关系,即随着 0 级挂载点数量的增加,整个系统的写入能力也成倍增加。 + +## 同级挂载点选择策略 + +一般情况下,当 TDengine 要从同级挂载点中选择一个用于生成新的数据文件时,采用 round robin 策略进行选择。但现实中有可能每个磁盘的容量不相同,或者容量相同但写入的数据量不相同,这就导致会出现每个磁盘上的可用空间不均衡,在实际进行选择时有可能会选择到一个剩余空间已经很小的磁盘。为了解决这个问题,从 3.1.1.0 开始引入了一个新的配置 `minDiskFreeSize`,当某块磁盘上的可用空间小于等于这个阈值时,该磁盘将不再被选择用于生成新的数据文件。该配置项的单位为字节,其值应该大于 `minimalDataDirGB` \ No newline at end of file diff --git a/docs/zh/18-data-transfer/02-explorer.md b/docs/zh/18-data-transfer/02-explorer.md index 6d410c4997..0bc0a0f559 100644 --- a/docs/zh/18-data-transfer/02-explorer.md +++ b/docs/zh/18-data-transfer/02-explorer.md @@ -2,7 +2,7 @@ title: 基于可视化界面的数据迁移 --- -本节讲述使用 taos Explorer 的可视化界面进行数据迁移,使用此功能需要依赖 taosd, taosAdapter, taosX, taos-explorer 等几个服务组件。关于 taosd 和 taosAdapter 的部署请参考 [系统部署](../../deployment/deploy),[taosX](../taosX),以及 [部署 taos-explorer](../../operation/explorer) +本节讲述使用 taos Explorer 的可视化界面进行数据迁移,使用此功能需要依赖 taosd, taosAdapter, taosX, taos-explorer 等几个服务组件。关于 taosd 和 taosAdapter 的部署请参考 [系统部署](../../deployment/deploy),[taosX](../taosX),以及 [部署 taos-explorer](../../operation/web) ## 功能入口 From 9dc7fe051d94e54ef9cbfe970f2d097ca972b48e Mon Sep 17 00:00:00 2001 From: Wade Zhang Date: Fri, 25 Aug 2023 20:19:15 +0800 Subject: [PATCH 066/120] doc: refine enterprise --- docs/zh/17-operation/08-web.md | 4 ++-- docs/zh/17-operation/09-storage.md | 2 +- docs/zh/18-data-transfer/index.md | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 docs/zh/18-data-transfer/index.md diff --git a/docs/zh/17-operation/08-web.md b/docs/zh/17-operation/08-web.md index 9923c9912e..6560bb979f 100644 --- a/docs/zh/17-operation/08-web.md +++ b/docs/zh/17-operation/08-web.md @@ -1,6 +1,6 @@ --- -title: 基于 Web 的系统管理工具 -description: 本节描述 TDengine 的图形化管理工具, taos-explorer, 的基本功能 +title: Web 管理工具 +description: 基于 Web 的系统管理工具 --- ## 简介 diff --git a/docs/zh/17-operation/09-storage.md b/docs/zh/17-operation/09-storage.md index ca3f3af49b..018525861b 100644 --- a/docs/zh/17-operation/09-storage.md +++ b/docs/zh/17-operation/09-storage.md @@ -53,4 +53,4 @@ dataDir /mnt/data6 2 0 ## 同级挂载点选择策略 -一般情况下,当 TDengine 要从同级挂载点中选择一个用于生成新的数据文件时,采用 round robin 策略进行选择。但现实中有可能每个磁盘的容量不相同,或者容量相同但写入的数据量不相同,这就导致会出现每个磁盘上的可用空间不均衡,在实际进行选择时有可能会选择到一个剩余空间已经很小的磁盘。为了解决这个问题,从 3.1.1.0 开始引入了一个新的配置 `minDiskFreeSize`,当某块磁盘上的可用空间小于等于这个阈值时,该磁盘将不再被选择用于生成新的数据文件。该配置项的单位为字节,其值应该大于 `minimalDataDirGB` \ No newline at end of file +一般情况下,当 TDengine 要从同级挂载点中选择一个用于生成新的数据文件时,采用 round robin 策略进行选择。但现实中有可能每个磁盘的容量不相同,或者容量相同但写入的数据量不相同,这就导致会出现每个磁盘上的可用空间不均衡,在实际进行选择时有可能会选择到一个剩余空间已经很小的磁盘。为了解决这个问题,从 3.1.1.0 开始引入了一个新的配置 `minDiskFreeSize`,当某块磁盘上的可用空间小于等于这个阈值时,该磁盘将不再被选择用于生成新的数据文件。该配置项的单位为字节,其值应该大于 2GB,即会跳过可用空间小于 2GB 的挂载点。 \ No newline at end of file diff --git a/docs/zh/18-data-transfer/index.md b/docs/zh/18-data-transfer/index.md new file mode 100644 index 0000000000..749ad16308 --- /dev/null +++ b/docs/zh/18-data-transfer/index.md @@ -0,0 +1,3 @@ +--- +title: 数据集成 +--- \ No newline at end of file From 4f52ae097fe14e1f36236446ae51522ff9f6293a Mon Sep 17 00:00:00 2001 From: Wade Zhang Date: Fri, 25 Aug 2023 20:47:32 +0800 Subject: [PATCH 067/120] doc: refine enterprise --- docs/zh/17-operation/07-cluster.md | 4 +++- docs/zh/18-data-transfer/02-explorer.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/zh/17-operation/07-cluster.md b/docs/zh/17-operation/07-cluster.md index 0b7fb7e53d..cf4bfafd53 100644 --- a/docs/zh/17-operation/07-cluster.md +++ b/docs/zh/17-operation/07-cluster.md @@ -47,7 +47,9 @@ Raft选举本身带有随机性,所以通过选举的重新分布产生的均 ## 恢复数据节点 -当集群中的某个数据节点(dnode)的数据全部丢失或被破坏,比如磁盘损坏或者目录被误删除,可以通过 `restore dnode` 命令来恢复该数据节点上的部分或全部逻辑节点,该功能依赖多副本中的其它副本进行数据复制,所以只在集群中 dnode 数量大于等于 3 且副本数为 3 的情况下能够工作。 +在多节点三副本的集群环境中,如果某个 dnode 的磁盘损坏,该 dnode 会自动退出,但集群中其它的 dnode 仍然能够继续提供写入和查询服务。 + +在更换了损坏的磁盘后,如果想要让曾经主动退出的 dnode 重新加入集群提供服务,可以通过 `restore dnode` 命令来恢复该数据节点上的部分或全部逻辑节点,该功能依赖多副本中的其它副本进行数据复制,所以只在集群中 dnode 数量大于等于 3 且副本数为 3 的情况下能够工作。 ```sql diff --git a/docs/zh/18-data-transfer/02-explorer.md b/docs/zh/18-data-transfer/02-explorer.md index 0bc0a0f559..fd374cc734 100644 --- a/docs/zh/18-data-transfer/02-explorer.md +++ b/docs/zh/18-data-transfer/02-explorer.md @@ -1,5 +1,5 @@ --- -title: 基于可视化界面的数据迁移 +title: 基于可视化界面的数据接入和数据迁移 --- 本节讲述使用 taos Explorer 的可视化界面进行数据迁移,使用此功能需要依赖 taosd, taosAdapter, taosX, taos-explorer 等几个服务组件。关于 taosd 和 taosAdapter 的部署请参考 [系统部署](../../deployment/deploy),[taosX](../taosX),以及 [部署 taos-explorer](../../operation/web) From cf417eb31dd42f696b7d12bee91ecc1fb3502544 Mon Sep 17 00:00:00 2001 From: Wade Zhang Date: Fri, 25 Aug 2023 21:00:03 +0800 Subject: [PATCH 068/120] doc: add product description and links --- docs/zh/02-intro.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/zh/02-intro.md b/docs/zh/02-intro.md index 888f779966..68a2541717 100644 --- a/docs/zh/02-intro.md +++ b/docs/zh/02-intro.md @@ -6,7 +6,14 @@ toc_max_heading_level: 2 TDengine 是一款开源、高性能、云原生的[时序数据库](https://tdengine.com/tsdb/),且针对物联网、车联网、工业互联网、金融、IT 运维等场景进行了优化。TDengine 的代码,包括集群功能,都在 GNU AGPL v3.0 下开源。除核心的时序数据库功能外,TDengine 还提供[缓存](../develop/cache/)、[数据订阅](../develop/tmq)、[流式计算](../develop/stream)等其它功能以降低系统复杂度及研发和运维成本。 -本章节介绍 TDengine 的主要功能、竞争优势、适用场景、与其他数据库的对比测试等等,让大家对 TDengine 有个整体的了解。 +本章节介绍 TDengine 的主要产品和功能、竞争优势、适用场景、与其他数据库的对比测试等等,让大家对 TDengine 有个整体的了解。 + +## 主要产品 + +TDengine 有三个主要产品:TDengine Pro (即 TDengine 企业版),TDengine Cloud,和 TDengine OSS,关于它们的具体定义请参考 +- [TDengine 企业版](https://www.taosdata.com/tdengine-pro) +- [TDengine 云服务](https://cloud.taosdata.com/?utm_source=menu&utm_medium=webcn) +- [TDengine 开源版](https://www.taosdata.com/tdengine-oss) ## 主要功能 From ca67d8d2f05b116eea086fb715d6bc2aca0627c5 Mon Sep 17 00:00:00 2001 From: Wade Zhang Date: Fri, 25 Aug 2023 23:28:02 +0800 Subject: [PATCH 069/120] doc: remove taosx and explorer from website --- docs/zh/17-operation/08-web.md | 178 ----- docs/zh/18-data-transfer/01-taosX.md | 972 ------------------------ docs/zh/18-data-transfer/02-explorer.md | 128 ---- docs/zh/18-data-transfer/index.md | 3 - 4 files changed, 1281 deletions(-) delete mode 100644 docs/zh/17-operation/08-web.md delete mode 100644 docs/zh/18-data-transfer/01-taosX.md delete mode 100644 docs/zh/18-data-transfer/02-explorer.md delete mode 100644 docs/zh/18-data-transfer/index.md diff --git a/docs/zh/17-operation/08-web.md b/docs/zh/17-operation/08-web.md deleted file mode 100644 index 6560bb979f..0000000000 --- a/docs/zh/17-operation/08-web.md +++ /dev/null @@ -1,178 +0,0 @@ ---- -title: Web 管理工具 -description: 基于 Web 的系统管理工具 ---- - -## 简介 - -为了易于企业版用户更容易使用和管理数据库,TDengine 3.0 企业版提供了一个全新的可视化组件 taosExplorer。用户能够在其中方便地管理数据库管理系统中中各元素(数据库、超级表、子表)的生命周期,执行查询,监控系统状态,管理用户和授权,完成数据备份和恢复,与其它集群之间进行数据同步,导出数据,管理主题和流计算。 - -**欲体验基于 Web 的 TDengine 系统管理能力,请联系 TDengine 市场或销售团队** - -## 部署服务 - -### 准备工作 - -1. taosExplorer 没有独立的安装包,请使用 taosX 安装包进行安装。 -2. 在启动 taosExplorer 之前,请先确认 TDengine 集群已经正确设置并运行(即 taosd 服务),taosAdapter 也已经正确设置和运行并与 TDengine 集群保持连接状态。如果想要使用数据备份和恢复或者数据同步功能,请确保 taosX 服务和 Agent 服务也已经正确设置和运行。 - -### 配置 - -在启动 taosExplorer 之前,请确保配置文件中的内容正确。 - -```TOML -listen = "0.0.0.0:6060" -log_level = "info" -cluster = "http://localhost:6041" -x_api = "http://localhost:6050" -``` - -说明: - -- listen - taosExplorer 对外提供服务的地址 -- log_level - 日志级别,可选值为 "debug", "info", "warn", "error", "fatal" -- cluster - TDengine集群的 taosadapter 地址 -- x_api - taosX 的服务地址 - -### 启动 - -然后启动 taosExplorer,可以直接在命令行执行 taos-explorer 或者使用下面的 systemctl 脚本用 systemctl 来启动 taosExplorer 服务 - -```shell -[Unit] -Description=Explorer for TDengine -After=network-online.target -Wants=network-online.target - -[Service] -Type=simple -ExecStart=/usr/bin/taos-explorer -Restart=always - -[Install] -WantedBy=multi-user.target -``` - -### 问题排查 - -1. 当通过浏览器打开taosExplorer站点遇到“无法访问此网站”的错误信息时,请通过命令行登录taosExplorer所在机器,并使用命令systemctl status taos-explorer.service检查服务的状态,如果返回的状态是inactive,请使用命令systemctl start taos-explorer.service启动服务。 -2. 如果需要获取taosExplorer的详细日志,可通过命令journalctl -u taos-explorer - -## 登录 - -在 TDengine 管理系统的登录页面,输入正确的用户名和密码后,点击登录按钮,即可登录。 - -说明: -- 这里的用户,需要在所连接的 TDengine 中创建,TDengine 默认的用户名和密码为`root/taosdata`; -- 在 TDengine 中创建用户时,默认会设置用户的 SYSINFO 属性值为1, 表示该用户可以查看系统信息,只有 SYSINFO 属性为 1 的用户才能正常登录 TDengine 管理系统。 - -## 面板 - -taosExplorer 内置了一个简单的仪表盘展示以下集群信息,点击左侧功能列表中的 "面板" 可以启用此功能。 - -- 默认的仪表盘会返回对应 Grafana 的安装配置向导 -- 配置过 Grafana 的仪表盘在点击' 面板' 时会跳转到对应的配置地址(该地址来源于 /profile 接口的返回值) - - -## 数据浏览器 - -点击功能列表的“数据浏览器”入口,在“数据浏览器”中可以创建和删除数据库、创建和删除超级表和子表,执行SQL语句,查看SQL语句的执行结果。此外,超级管理员还有对数据库的管理权限,其他用户不提供该功能。 - -具体权限有: - -1.查看(提供数据库/超级表/普通表的基本信息) - -2.编辑 (编辑数据库/超级表/普通表的信息) - -3.数据库管理权限 (仅限超级管理员,该操作可以给指定用户配置数据库管理权限) - -4.删除 (删除数据库/超级表/普通表) - -5.追加 (选择对应的数据库/超级表/普通表名称直接追加到右侧sql输入区域,避免了手工输入) - - -## 系统管理 - -点击功能列表中的“系统管理”入口,可以创建用户、对用户进行访问授权、以及删除用户。还能够对当前所管理的集群中的数据进行备份和恢复。也可以配置一个远程 TDengine 的地址进行数据同步。同时也提供了集群信息和许可证的信息以及代理信息以供查看。系统管理 菜单只有 root 用户才有权限看到 - -### 用户管理 - -点击“系统管理”后,默认会进入“用户”标签页。 -在用户列表,可以查看系统中已存在的用户及其创建时间,并可以对用户进行启用、禁用,编辑(包括修改密码,数据库的读写权限等),删除等操作。 -点击用户列表右上方的“+新增”按钮,即可打开“新增用户”对话框: -1. 输入新增用户的用户名称,必填 -2. 输入新增用户的登录密码,必填,密码长度要求为8-16个字符,且至少要满足以下4个条件中的3个:大写字母,小写字母,数字,特殊字符 -3. 选择新增用户对系统中已存在的数据库的读写权限,非必填,默认情况下,新增用户对所有已存在的数据库无读写权限 -4. 提写完成后,点击确定按钮,即可新增用户。 - -### 系统信息 - -点击“集群”标签后,可以查看DNodes, MNodes和QNodes的状态、创建时间等信息,并可以对以上节点进行新增和删除操作。 - -### 许可证管理 - -点击“许可证”标签后,可以查看系统和系统和各连接器的许可证信息。 -点击位于“许可证”标签页右上角的“激活许可证”按钮,输入“激活码”和“连接器激活码”后,点击“确定”按钮,即可激活,激活码请联系 TDengine 客户成功团队获取。 - -## 数据订阅 - -本章节,将介绍如何在 TDengine 集群中,创建主题,并将其分享给其他用户,以及如何查看一个主题的消费者信息。 - -通过 Explorer, 您可以轻松地完成对数据订阅的管理,从而更好地利用 TDengine 提供的数据订阅能力。 -点击左侧导航栏中的“数据订阅”,即可跳转至数据订阅配置管理页面。 -您可以通过以下两种方式创建主题:使用向导和自定义 SQL 语句。通过自定义 SQL 创建主题时,您需要了解 TDengine 提供的数据订阅 SQL 语句的语法,并保证其正确性。 - -注: 对于数据订阅的详细说明,可参考官方文档中关于“数据订阅”章节,创建数据订阅之前需要先准备源数据库(或源数据库包含相应的超级表或者表),其中源数据库需配置wal_retention_period > 0 。 - -包括主题,消费者,共享主题和示例代码 - -### 创建主题 - -1. 在“主题”标签页,点击“新增新主题”按钮以后,选择向导窗格,然后输入“主题名称”; -2. 在“数据库”下拉列表中,选择相应的数据库; -3. 在“类型”标签下,选择“数据库” 或 “超级表” 或 “子查询”,这里以默认值“数据库”为例; -4. 然后点击“创建” 按钮,即可创建对应的主题。 - -### 分享主题 - -1. 在“共享主题”标签页,在“主题“下拉列表中,选择将要分享的主题; -2. 点击“添加可消费该主题的用户”按钮,然后在“用户名”下拉列表中选择相应的用户,然后点击“新增”,即可将该主题分享给此用户。 - - -### 查看消费者信息 - -1. 通过执行下一节“示例代码”所述的“完整实例”,即可消费共享主题 -2. 在“消费者”标签页,可查看到消费者的有关信息 - -### 示例代码 - -1. 在“示例代码”标签页,在“主题“下拉列表中,选择相应的主题; -2. 选择您熟悉的语言,然后您可以阅读以及使用这部分示例代码用来”创建消费“,”订阅主题“,通过执行 “完整实例”中的程序即可消费共享主题 - -## 流计算 - -通过 Explorer, 您可以轻松地完成对流的管理,从而更好地利用 TDengine 提供的流计算能力。 -点击左侧导航栏中的“流计算”,即可跳转至流计算配置管理页面。 -您可以通过以下两种方式创建流:流计算向导和自定义 SQL 语句。当前,通过流计算向导创建流时,暂不支持分组功能。通过自定义 SQL 创建流时,您需要了解 TDengine 提供的流计算 SQL 语句的语法,并保证其正确性。 - -注: 对于流计算的详细说明,可参考官方文档中关于“流式计算”章节,创建流计算之前需要先准备源数据库以及相应的超级表或表、输出的数据库。 - -### 流计算向导 - -1. 点击“创建流计算”按钮以后,选择流计算向导窗格,然后输入“流名称”; -2. 在“输出”部分,输入相应的“数据库”,“超级表”以及“子表前缀”; -3. 在“源”部分,选择相应的“数据库”,然后根据具体情况,选择使用“超级表”或“表”: - 1. 如果使用“超级表“,请从“超级表”下拉列表中选择相应的超级表, 并在“字段设置”区域,选择相应的字段 - 2. 如果使用“表“,请从“表”下拉列表中选择相应的表, 并在“字段设置”区域,选择相应的字段 -4. 对于窗口设置,根据需要选择”SESSION“, "STATE"或"INTERVAL", 并配置相应的值; -5. 对于”执行“部分,选择相应的”触发器“类型,并设置“Watermark”, "Ignore Expired", "DELETE_MARK", "FILL_HISTORY", "IGNORE UPDATE"; -6. 然后点击“创建” 按钮,即可创建对应的流计算。 - -### 使用 SQL 语句建流 - -1. 点击“创建流计算”按钮以后,选择流计算SQL窗格,然后输入类似如下的SQL语句(反引号内为源数据库以及相应的超级表或表、输出的数据库,请按您的环境更新反引号内的内容) - -```shell -CREATE STREAM `test_stream` TRIGGER WINDOW_CLOSE IGNORE EXPIRED 1 INTO `db_name`.`stable1` SUBTABLE(CONCAT('table1',tbname)) AS SELECT count(*) FROM `test_db`.`stable_name` PARTITION BY tbname INTERVAL(1m) -``` -2. 点击“创建”按钮,即可创建对应的流计算。 \ No newline at end of file diff --git a/docs/zh/18-data-transfer/01-taosX.md b/docs/zh/18-data-transfer/01-taosX.md deleted file mode 100644 index 72d2cb2211..0000000000 --- a/docs/zh/18-data-transfer/01-taosX.md +++ /dev/null @@ -1,972 +0,0 @@ ---- -title: 数据接入、同步和备份 ---- - -## 简介 - -为了能够方便地将各种数据源中的数据导入 TDengine 3.0,TDengine 3.0 企业版提供了一个全新的工具 taosX 用于帮助用户快速将其它数据源中的数据传输到 TDengine 中。 taosX 定义了自己的集成框架,方便扩展新的数据源。目前支持的数据源有 TDengine 自身(即从一个 TDengine 集群到另一个 TDengine 集群),Pi, OPC UA。除了数据接入外,taosX 还支持数据备份、数据同步、数据迁移以及数据导出功能。 - -**欲体验 taosX 的各种数据接入能力,请联系 TDengine 市场或销售团队。** - -## 使用前提 - -使用 taosX 需要已经部署好 TDengine 中的 taosd 和 taosAdapter,具体细节请参考 [系统部署](../../deployment/deploy) - -**使用限制**:taosX 只能用于企业版数据库服务端。 - -## 安装与配置 - -安装 taosX 需要使用独立的 taosX 安装包,其中除了 taosX 之外,还包含 Pi 连接器(限 Windows), OPC 连接器, InfluxDB 连接器, MQTT 连接器,以及必要的 Agent 组件,taosX + Agent + 某个连接器可以用于将相应数据源的数据同步到 TDengine。taosX 安装包中还包含了 taos-explorer 这个可视化管理组件 - -### Linux 安装 - -下载需要的 taosX 安装包,下文以安装包 `taosx-1.0.0-linux-x64.tar.gz` 为例展示如何安装: - -``` bash -# 在任意目录下解压文件 -tar -zxf taosx-1.0.0-linux-x64.tar.gz -cd taosx-1.0.0-linux-x64 - -# 安装 -sudo ./install.sh - -# 验证 -taosx -V -# taosx 1.0.0-494d280c (built linux-x86_64 2023-06-21 11:06:00 +08:00) -taosx-agent -V -# taosx-agent 1.0.0-494d280c (built linux-x86_64 2023-06-21 11:06:01 +08:00) - -# 卸载 -cd /usr/local/taosx -sudo ./uninstall.sh -``` - -**常见问题:** - -1. 安装后系统中增加了哪些文件? - * /usr/bin: taosx, taosx-agent, taos-explorer - * /usr/local/taosx/plugins: influxdb, mqtt, opc - * /etc/systemd/system:taosx.service, taosx-agent.service, taos-explorer.service - * /usr/local/taosx: uninstall.sh - * /etc/taox: agent.toml, explorer.toml - -2. taosx -V 提示 "Command not found" 应该如何解决? - * 检验问题1,保证所有的文件都被复制到对应的目录 - ``` bash - ls /usr/bin | grep taosx - ``` - -### Windows 安装 - -- 下载需要的 taosX 安装包,例如 taosx-1.0.0-Windows-x64-installer.exe,执行安装 -- 可使用 uninstall_taosx.exe 进行卸载 -- 命令行执行 ```sc start/stop taosx``` 启动/停止 taosx 服务 -- 命令行执行 ```sc start/stop taosx-agent``` 启动/停止 taosx-agent 服务 -- 命令行执行 ```sc start/stop taos-explorer``` 启动/停止 taosx-agent 服务 -- windows 默认安装在```C:\Program Files\taosX```,目录结构如下: -~~~ -├── bin -│   ├── taosx.exe -│   ├── taosx-srv.exe -│   ├── taosx-srv.xml -│   ├── taosx-agent.exe -│   ├── taosx-agent-srv.exe -│   ├── taosx-agent-srv.xml -│   ├── taos-explorer.exe -│   ├── taos-explorer-srv.exe -│   └── taos-explorer-srv.xml -├── plugins -│   ├── influxdb -│   │   └── taosx-inflxdb.jar -│   ├── mqtt -│   │   └── taosx-mqtt.exe -│   ├── opc -│   | └── taosx-opc.exe -│   ├── pi -│   | └── taosx-pi.exe -│   | └── taosx-pi-backfill.exe -│   | └── ... -└── config -│   ├── agent.toml -│   ├── explorer.toml -├── uninstall_taosx.exe -├── uninstall_taosx.dat -~~~ - -**运行模式** - -taosX 是进行数据同步与复制的核心组件,以下运行模式指 taosX 的运行模式,其它组件的运行模式在 taosX 的不同运行模式下与之适配。 - -## 命令行模式 - -可以直接在命令行上添加必要的参数直接启动 taosX 即为命令行模式运行。当命令行参数所指定的任务完成后 taosX 会自动停止。taosX 在运行中如果出现错误也会自动停止。也可以在任意时刻使用 ctrl+c 停止 taosX 的运行。本节介绍如何使用 taosX 的各种使用场景下的命令行。 - -### 命令行参数说明 - -**注意:部分参数暂无法通过 explorer设置【见:其他参数说明】,之后会逐步开放) ** - -命令行执行示例: - -```shell -taosx -f -t <其他参数> -``` - -以下参数说明及示例中若无特殊说明 `` 的格式均为占位符,使用时需要使用实际参数进行替换。 - -### DSN (Data Source Name) - -taosX 命令行模式使用 DSN 来表示一个数据源(来源或目的源),典型的 DSN 如下: - -```bash -# url-like -[+]://[[:@]:][/][?=[&=]] -|------|------------|---|-----------|-----------|------|------|----------|-----------------------| -|driver| protocol | | username | password | host | port | object | params | - -// url 示例 -tmq+ws://root:taosdata@localhost:6030/db1?timeout=never -``` -[] 中的数据都为可选参数。 - -1. 不同的驱动 (driver) 拥有不同的参数。driver 包含如下选项: - -- taos:使用查询接口从 TDengine 获取数据 -- tmq:启用数据订阅从 TDengine 获取数据 -- local:数据备份或恢复 -- pi: 启用 pi-connector从 pi 数据库中获取数据 -- opc:启用 opc-connector 从 opc-server 中获取数据 -- mqtt: 启用 mqtt-connector 获取 mqtt-broker 中的数据 -- kafka: 启用 Kafka 连接器从 Kafka Topics 中订阅消息写入 -- influxdb: 启用 influxdb 连接器从 InfluxDB 获取数据 -- csv:从 CSV 文件解析数据 - -2. +protocol 包含如下选项: -- +ws: 当 driver 取值为 taos 或 tmq 时使用,表示使用 rest 获取数据。不使用 +ws 则表示使用原生连接获取数据,此时需要 taosx 所在的服务器安装 taosc。 -- +ua: 当 driver 取值为 opc 时使用,表示采集的数据的 opc-server 为 opc-ua -- +da: 当 driver 取值为 opc 时使用,表示采集的数据的 opc-server 为 opc-da - -3. host:port 表示数据源的地址和端口。 -4. object 表示具体的数据源,可以是TDengine的数据库、超级表、表,也可以是本地备份文件的路径,也可以是对应数据源服务器中的数据库。 -5. username 和 password 表示该数据源的用户名和密码。 -6. params 代表了 dsn 的参数。 - -### 其它参数说明 - -1. parser 通过 --parser 或 -p 设置,设置 transform 的 parser 生效。可以通过 Explorer 在如 CSV,MQTT,KAFKA 数据源的任务配置进行设置。 - - 配置示例: - - ```shell - --parser "{\"parse\":{\"ts\":{\"as\":\"timestamp(ms)\"},\"topic\":{\"as\":\"varchar\",\"alias\":\"t\"},\"partition\":{\"as\":\"int\",\"alias\":\"p\"},\"offset\":{\"as\":\"bigint\",\"alias\":\"o\"},\"key\":{\"as\":\"binary\",\"alias\":\"k\"},\"value\":{\"as\":\"binary\",\"alias\":\"v\"}},\"model\":[{\"name\":\"t_{t}\",\"using\":\"kafka_data\",\"tags\":[\"t\",\"p\"],\"columns\":[\"ts\",\"o\",\"k\",\"v\"]}]}" - - ``` - -2. transform 通过 --transform 或 -T 设置,配置数据同步(仅支持 2.6 到 3.0 以及 3.0 之间同步)过程中对于表名及表字段的一些操作。暂无法通过 Explorer 进行设置。配置说明如下: - - ```shell - 1.AddTag,为表添加 TAG。设置示例:-T add-tag:=。 - 2.表重命名: - 2.1 重命名表限定 - 2.1.1 RenameTable:对所有符合条件的表进行重命名。 - 2.1.2 RenameChildTable:对所有符合条件的子表进行重命名。 - 2.1.3 RenameSuperTable:对所有符合条件的超级表进行重命名。 - 2.2 重命名方式 - 2.2.1 Prefix:添加前缀。 - 2.2.2 Suffix:添加后缀。 - 2.2.3 Template:模板方式。 - 2.2.4 ReplaceWithRegex:正则替换。taosx 1.1.0 新增。 - 重命名配置方式: - <表限定>:<重命名方式>:<重命名值> - 使用示例: - 1.为所有表添加前缀 - --transform rename-table:prefix: - 2.为符合条件的表替换前缀:prefix1 替换为 prefix2,以下示例中的 <> 为正则表达式的不再是占位符。 - -T rename-child-table:replace_with_regex:^prefix1(?)::prefix2_$old - - 示例说明:^prefix1(?) 为正则表达式,该表达式会匹配表名中包含以 prefix1 开始的表名并将后缀部分记录为 old,prefix2$old 则会使用 prefix2 与 old 进行替换。注意:两部分使用关键字符 :: 进行分隔,所以需要保证正则表达式中不能包含该字符。 - 若有更复杂的替换需求请参考:https://docs.rs/regex/latest/regex/#example-replacement-with-named-capture-groups 或咨询 taosx 开发人员。 - ``` - -3. jobs 指定任务并发数,仅支持 tmq 任务。暂无法通过 Explorer 进行设置。通过 --jobs `` 或 -j `` 进行设置。 -4. -v 用于指定 taosx 的日志级别,-v 表示启用 info 级别日志,-vv 对应 debug,-vvv 对应 trace。 - - -### 从 TDengine 到 TDengine 的数据同步 - -#### TDengine 3.0 -> TDengine 3.0 - -在两个相同版本 (都是 3.0.x.y)的 TDengine 集群之间将源集群中的存量及增量数据同步到目标集群中。 - -命令行模式下支持的参数如下: - -| 参数名称 | 说明 | 默认值 | -|-----------|------------------------------------------------------------------|----------------------------| -| group.id | 订阅使用的分组ID | 若为空则使用 hash 生成一个 | -| client.id | 订阅使用的客户端ID | taosx | -| timeout | 监听数据的超时时间,当设置为 never 表示 taosx 不会停止持续监听。 | 500ms | -| offset | 从指定的 offset 开始订阅,格式为 `:`,若有多个 vgroup 则用半角逗号隔开 | 若为空则从 0 开始订阅 | -| token | 目标源参数。 认证使用参数。 | 无 | - -示例: -```shell -taosx run \ - -f 'tmq://root:taosdata@localhost:6030/db1?group.id=taosx1&client.id=taosx&timeout=never&offset=2:10' \ - -t 'taos://root:taosdata@another.com:6030/db2' -``` - - - -#### TDengine 2.6 -> TDengine 3.0 - -将 2.6 版本 TDengine 集群中的数据迁移到 3.0 版本 TDengine 集群。 - -#### 命令行参数 - -| 参数名称 | 说明 | 默认值 | -|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------| -| libraryPath | 在 option 模式下指定 taos 库路径 | 无 | -| configDir | 指定 taos.cfg 配置文件路径 | 无 | -| mode | 数据源参数。 history 表示历史数据。 realtime 表示实时同步。 all 表示以上两种。 | history | -| restro | 数据源参数。 在同步实时数据前回溯指定时间长度的数据进行同步。 restro=10m 表示回溯最近 10 分钟的数据以后,启动实时同步。 | 无 | -| interval | 数据源参数。 轮询间隔 ,mode=realtime&interval=5s 指定轮询间隔为 5s | 无 | -| excursion | 数据源参数。 允许一段时间的乱序数据 | 500ms | -| stables | 数据源参数。 仅同步指定超级表的数据,多个超级表名用英文逗号 ,分隔 | 无 | -| tables | 数据源参数。 仅同步指定子表的数据,表名格式为 {stable}.{table} 或 {table},多个表名用英文逗号 , 分隔,支持 @filepath 的方式输入一个文件,每行视为一个表名,如 tables=@./tables.txt 表示从 ./tables.txt 中按行读取每个表名,空行将被忽略。 | 无 | -| select-from-stable | 数据源参数。 从超级表获取 select {columns} from stable where tbname in ({tbnames}) ,这种情况 tables 使用 {stable}.{table} 数据格式,如 meters.d0 表示 meters 超级表下面的 d0 子表。 | 默认使用 select \* from table 获取数据 | -| assert | 目标源参数。 taos:///db1?assert 将检测数据库是否存在,如不存在,将自动创建目标数据库。 | 默认不自动创建库。 | -| force-stmt | 目标源参数。 当 TDengine 版本大于 3.0 时,仍然使用 STMT 方式写入。 | 默认为 raw block 写入方式 | -| batch-size | 目标源参数。 设置 STMT 写入模式下的最大批次插入条数。 | | -| interval | 目标源参数。 每批次写入后的休眠时间。 | 无 | -| max-sql-length | 目标源参数。 用于建表的 SQL 最大长度,单位为 bytes。 | 默认 800_000 字节。 | -| failes-to | 目标源参数。 添加此参数,值为文件路径,将写入错误的表及其错误原因写入该文件,正常执行其他表的同步任务。 | 默认写入错误立即退出。 | -| timeout-per-table | 目标源参数。 为子表或普通表同步任务添加超时。 | 无 | -| update-tags | 目标源参数。 检查子表存在与否,不存在时正常建表,存在时检查标签值是否一致,不一致则更新。 | 无 | - -#### 示例 - -1.使用原生连接同步数据 - -```shell -taosx run \ - -f 'taos://td1:6030/db1?libraryPath=./libtaos.so.2.6.0.30&mode=all' \ - -t 'taos://td2:6030/db2?libraryPath=./libtaos.so.3.0.1.8&assert \ - -v -``` - -2.使用 WebSocket 同步数据超级表 stable1 和 stable2 的数据 - -```shell -taosx run \ - -f 'taos+ws://:@td1:6041/db1?stables=stable1,stable2' \ - -t 'taos+wss://td2:6041/db2?assert&token= \ - -v -``` - -### 从 TDengine 备份数据文件到本地 - -示例: -```shell -taosx run -f 'tmq://root:taosdata@td1:6030/db1' -t 'local:/path_directory/' - -``` -以上示例执行的结果及参数说明: - -将集群 td1 中的数据库 db1 的所有数据,备份到 taosx 所在设备的 /path_directory 路径下。 - -数据源(-f 参数的 DSN)的 object 支持配置为 数据库级(dbname)、超级表级(dbname.stablename)、子表/普通表级(dbname.tablename),对应备份数据的级别数据库级、超级表级、子表/普通表级 - - -### 从本地数据文件恢复到 TDengine - -#### 示例 -```shell -taosx run -f 'local:/path_directory/' -t 'taos://root:taosdata@td2:6030/db1?assert' -``` - -以上示例执行的结果: - -将 taosx 所在设备 /path_directory 路径下已备份的数据文件,恢复到集群 td2 的数据库 db1 中,如果 db1 不存在,则自动建库。 - -目标源(-t 参数的 DSN)中的 object 支持配置为数据库(dbname)、超级表(dbname.stablename)、子表/普通表(dbname.tablename),对应备份数据的级别数据库级、超级表级、子表/普通表级,前提是备份的数据文件也是对应的数据库级、超级表级、子表/普通表级数据。 - - -#### 常见错误排查 - -(1) 如果使用原生连接,任务启动失败并报以下错误: - -```text -Error: tmq to td task exec error - -Caused by: - [0x000B] Unable to establish connection -``` -产生原因是与数据源的端口链接异常,需检查数据源 FQDN 是否联通及端口 6030 是否可正常访问。 - -(2) 如果使用 WebSocket 连接,任务启动失败并报以下错误: - -```text -Error: tmq to td task exec error - -Caused by: - 0: WebSocket internal error: IO error: failed to lookup address information: Temporary failure in name resolution - 1: IO error: failed to lookup address information: Temporary failure in name resolution - 2: failed to lookup address information: Temporary failure in name resolution -``` - -使用 WebSocket 连接时可能遇到多种错误类型,错误信息可以在 ”Caused by“ 后查看,以下是几种可能的错误: - -- "Temporary failure in name resolution": DNS 解析错误,检查 IP 或 FQDN 是否能够正常访问。 -- "IO error: Connection refused (os error 111)": 端口访问失败,检查端口是否配置正确或是否已开启和可访问。 -- "IO error: received corrupt message": 消息解析失败,可能是使用了 wss 方式启用了 SSL,但源端口不支持。 -- "HTTP error: *": 可能连接到错误的 taosAdapter 端口或 LSB/Nginx/Proxy 配置错误。 -- "WebSocket protocol error: Handshake not finished": WebSocket 连接错误,通常是因为配置的端口不正确。 - -(3) 如果任务启动失败并报以下错误: - -```text -Error: tmq to td task exec error - -Caused by: - [0x038C] WAL retention period is zero -``` - -是由于源端数据库 WAL 配置错误,无法订阅。 - -解决方式: -修改数据 WAL 配置: - -```sql -alter database test wal_retention_period 3600; -``` - -### 从 OPC-UA 同步数据到 TDengine - -#### 配置参数 - -| 参数名称 | 类型 | 描述 | -|-----------------|--------|-----------------------------------------------------------------------------| -| interval | int | 采集间隔(单位:秒),默认为1秒 | -| concurrent | int | 采集器并发数,默认为1 | -| batch_size | int | 采集器上报的批次点位数,默认为100 | -| batch_timeout | int | 采集器上报的超时时间(单位:秒),默认为20秒 | -| connect_timeout | int | 连接的超时时间(单位:秒),默认为10秒 | -| request_timeout | int | 请求的超时时间(单位:秒),默认为10秒 | -| security_policy | string | OPC-UA连接安全策略(可配置为None/Basic128Rsa15/Basic256/Basic256Sha256) | -| security_mode | string | OPC-UA连接模式(可配置为None/Sign/SignAndEncrypt) | -| certificate | string | cert.pem的路径。当安全模式或策略不是”无”时生效 | -| private_key | string | key.pem的路径。 当安全模式或策略不是”无”时生效 | -| csv_config_file | string | 包含 OPC UA 的点位配置和表配置。与配置 csv_config_file 配置互斥,csv_config_file 优先生效| -| ua.nodes | string | OPC-UA 测点的 NodeID。和 opc_table_config 配置结合使用,两者需要同时配置。与配置 csv_config_file 配置互斥,csv_config_file 优先生效。配置格式为 ::,code 用于建子表。| -| opc_table_config | string | OPCUA 单列模式表配置。需要与 ua.nodes 配合使用。| -| debug | bool | 启用 OPC 连接器的 debug 日志。默认为 false。| -| enable | bool | 原始数据存储。默认为 false| -| path | string | 原始数据存储路径。enable 为 true 时必须配置。| -| keep | int | 原始数据保存天数。enable 为 true 时必须配置。| - -补充: -1. opc_table_config 说明: - -```json -{ - "stable_prefix": "meters", // 超级表前缀 - "column_configs": - [ - { - "column_name": "received_time", // 存储接收时间 - "column_type": "timestamp", - "column_alias": "ts", // 接收时间建表列用列名为 ts - "is_primary_key": true // 接收时间时间戳作为主键 - }, - { - "column_name": "original_time", - "column_type": "timestamp", - "column_alias": "ts_2", - "is_primary_key": false - }, - { - "column_name": "value", // 数据列 - "column_alias": "valueaa", // 数据列别名 - "is_primary_key": false - }, - { - "column_name": "quality", // 质量位列 - "column_type": "int", - "column_alias": "quality11", // 质量位列别名 - "is_primary_key": false - } - ] -} -``` - -#### 示例 - -1. 使用 ua.nodes 和 opc_table_config 的配置示例: -采集 nodeid 为 ns=2;i=2 和 ns=2;i=3 的点位,将其写入到集群 tdengine 的 opc 库中超级表前缀为 meters,如果 ns=2;i=2 的点位类型为 float 则会创建 meters_float 的超级表,超级表使用 opc 接收的数据作为时间戳索引列,并且保留原始时间戳列,原始时间戳列名为 ts_2,数据列存储为 valueaa,同时存储质量数据到 quality11 列。 - -```shell -taosx run \ - -f "opcua://uauser:uapass@localhost:4840?ua.nodes=ns=2;i=2::DSF1312,ns=2;i=3::DSF1313&opc_table_config={\"stable_prefix\": \"meters\", \"column_configs\": [{\"column_name\": \"received_time\", \"column_type\": \"timestamp\", \"column_alias\": \"ts\", \"is_primary_key\": true }, {\"column_name\": \"original_time\", \"column_type\": \"timestamp\", \"column_alias\": \"ts_2\", \"is_primary_key\": false }, {\"column_name\": \"value\", \"column_alias\": \"valueaa\", \"is_primary_key\": false }, {\"column_name\": \"quality\", \"column_type\": \"int\", \"column_alias\": \"quality11\", \"is_primary_key\": false } ] }" \ - -t "taos://tdengine:6030/opc" - - - -``` - -2. 使用 CSV 配置文件 - -```shell -taosx run -f "opcua://?csv_config_file=@" -t "taos+ws://tdengine:6041/opc" -``` - -#### CSV 配置文件模板 - - -### 从 OPC-DA 同步数据到 TDengine (Windows) - -#### 配置参数 - -| 参数名称 | 类型 | 描述 | -|-----------------|--------|-----------------------------------------------------------------------------| -| interval | int | 采集间隔(单位:秒),默认为1秒 | -| concurrent | int | 采集器并发数,默认为1 | -| batch_size | int | 采集器上报的批次点位数,默认为100 | -| batch_timeout | int | 采集器上报的超时时间(单位:秒),默认为20秒 | -| connect_timeout | int | 连接的超时时间(单位:秒),默认为10秒 | -| request_timeout | int | 请求的超时时间(单位:秒),默认为10秒 | -| csv_config_file | string | 包含 OPC UA 的点位配置和表配置。与 ua.nodes 两者之间需要配置一个。CSV 的配置模版参考:OPC 需求汇总及完成现状 | -| da.tags | string | OPC-UA 测点的 NodeID。和 opc_table_config 配置结合使用,两者需要同时配置。与配置 csv_config_file 配置互斥,csv_config_file 优先生效。| -| opc_table_config | string | OPCUA 单列模式表配置。需要与 da.tags 配合使用| -| debug | bool | 启用 OPC 连接器的 debug 日志。默认为 false。| -| enable | bool | 原始数据存储。默认为 false| -| path | string | 原始数据存储路径。enable 为 true 时必须配置。| -| keep | int | 原始数据保存天数。enable 为 true 时必须配置。| - -#### 应用示例 - -```shell -taosx run \ - -f "opc+da://Matrikon.OPC.Simulation.1?nodes=localhost&da.tags=Random.Real8::tb3::c1::int" - -t "taos://tdengine:6030/opc" -``` - -以上示例的执行结果: - -采集 Matrikon.OPC.Simulation.1 服务器上 OPC DA 中 da.tags 为 Random.Real8的数据,数据类型为int,对应在 TDengine 中以表名为 tb3 ,列名为c1,列类型为 int 型 schema 来创建表(如果对应表已存在,则直接采集数据并写入)。 - -#### 常见错误排查 - -(1) 如果使用原生连接,任务启动失败并打印如下错误: -```text -Error: tmq to td task exec error - -Caused by: - 0: Error occurred while creating a new object: [0x000B] Unable to establish connection -``` -解决方式: - -检查目标端 TDengine 的 FQDN 是否联通及端口 6030 是否可正常访问。 - -(2) 如果使用 WebSocket 连接任务启动失败并打印如下错误:: - -```text -Error: tmq to td task exec error - -Caused by: - 0: WebSocket internal error: IO error: failed to lookup address information: Temporary failure in name resolution - 1: IO error: failed to lookup address information: Temporary failure in name resolution - 2: failed to lookup address information: Temporary failure in name resolution -``` - -使用 WebSocket 连接时可能遇到多种错误类型,错误信息可以在 ”Caused by“ 后查看,以下是几种可能的错误: - -- "Temporary failure in name resolution": DNS 解析错误,检查目标端 TDengine的 IP 或 FQDN 是否能够正常访问。 -- "IO error: Connection refused (os error 111)": 端口访问失败,检查目标端口是否配置正确或是否已开启和可访问(通常为6041端口)。 -- "HTTP error: *": 可能连接到错误的 taosAdapter 端口或 LSB/Nginx/Proxy 配置错误。 -- "WebSocket protocol error: Handshake not finished": WebSocket 连接错误,通常是因为配置的端口不正确。 - -### 从 PI 同步数据到 TDengine (Windows) - -#### PI DSN 配置 - -PI DSN 的完整配置如下: - -```shell -pi://[:@]PIServerName/AFDatabaseName?[TemplateForPIPoint][&TemplateForAFElement][&PointList][&][&][&UpdateInterval] -``` - -在 taosX CLI 运行时支持的参数如下,其中 TemplateForPIPoint、TemplateForAFElement、PointList 三个参数至少配置一项: -- PISystemName:选填,连接配置 PI 系统服务名,默认值与 PIServerName 一致 -- MaxWaitLen:选填,数据最大缓冲条数,默认值为 1000 ,有效取值范围为 [1,10000] -- UpdateInterval:选填,PI System 取数据频率,默认值为 10000(毫秒:ms),有效取值范围为 [10,600000] -- TemplateForPIPoint:选填,使用 PI Point 模式将模板按照 element 的每个 Arrtribution 作为子表导入到 TDengine -- TemplateForAFElement:选填,使用 AF Point 模式将模板按照 element 的 Attribution 集合作为一个子表导入到 TDengine -- PointList:选填,使用 PointList 模式将指定csv文件中描述的点位信息在 PI 数据库中的数据导入到 TDengine - - -#### 应用示例 - -将位于服务器 WIN-2OA23UM12TN 中的 PI 数据库 Met1,模板 template1、template2配置为 TemplateForPIPoint模式,模板 template3、template4 配置为 TemplateForAFElement 模式,服务器 /home/ 路径下的点位文件 points.csv 配置为 PointList 模式,连接配置 PI 系统服务名为 PI,数据最大缓冲条数为1000,PI System 取数据频率为10000ms,将该库中的数据同步到 服务器 tdengine 的 pi 库中。完整的示例如下: - -```shell -taosx run \ - -f "pi://WIN-2OA23UM12TN/Met1?TemplateForPIPoint=template1,template2&TemplateForAFElement=template3,template4" \ - -t "taos://tdengine:6030/pi" -``` - - -#### 常见错误排查 - -(1) 如果使用原生连接,任务启动失败并打印如下错误: -```text -Error: tmq to td task exec error - -Caused by: - 0: Error occurred while creating a new object: [0x000B] Unable to establish connection -``` -解决方式: - -检查目标端 TDengine 的 FQDN 是否联通及端口 6030 是否可正常访问。 - -(2) 如果使用 WebSocket 连接任务启动失败并打印如下错误:: - -```text -Error: tmq to td task exec error - -Caused by: - 0: WebSocket internal error: IO error: failed to lookup address information: Temporary failure in name resolution - 1: IO error: failed to lookup address information: Temporary failure in name resolution - 2: failed to lookup address information: Temporary failure in name resolution -``` - -使用 WebSocket 连接时可能遇到多种错误类型,错误信息可以在 ”Caused by“ 后查看,以下是几种可能的错误: - -- "Temporary failure in name resolution": DNS 解析错误,检查目标端 TDengine的 IP 或 FQDN 是否能够正常访问。 -- "IO error: Connection refused (os error 111)": 端口访问失败,检查目标端口是否配置正确或是否已开启和可访问(通常为6041端口)。 -- "HTTP error: *": 可能连接到错误的 taosAdapter 端口或 LSB/Nginx/Proxy 配置错误。 -- "WebSocket protocol error: Handshake not finished": WebSocket 连接错误,通常是因为配置的端口不正确。 - - -### 从 InfluxDB 同步数据到 TDengine - -#### 命令行参数 - -将数据从 InfluxDB 同步至 TDengine 的命令,如下所示: - -```bash -taosx run --from "" --to "" -``` - -其中,InfluxDB DSN 符合 DSN 的通用规则,这里仅对其特有的参数进行说明: -- version: 必填,InfluxDB 的版本,主要用于区分 1.x 与 2.x 两个版本,二者使用不同的认证参数; -- version = 1.x - - username: 必填,InfluxDB 用户,该用户至少在该组织中拥有读取权限; - - password: 必填,InfluxDB 用户的登陆密码; -- version = 2.x - - orgId: 必填,InfluxDB 中的 Orgnization ID; - - token: 必填,InfluxDB 中生成的 API token, 这个 token 至少要拥有以上 Bucket 的 Read 权限; -- bucket: 必填,InfluxDB 中的 Bucket 名称,一次只能同步一个 Bucket; -- measurements: 非必填,可以指定需要同步的多个 Measurements(英文逗号分割),未指定则同步全部; -- beginTime: 必填,格式为:YYYY-MM-DD'T'HH:MM:SS'Z', 时区采用 UTC 时区,例如:2023-06-01T00:00:00+0800, 即北京时间2023-06-01 00:00:00(东八区时间); -- endTime: 非必填,可以不指定该字段或值为空,格式与beginTime相同;如果未指定,提交任务后,将持续进行数据同步; -- readWindow: 非必填,可以不指定该字段或值为空,可选项为D、H、M(天、时、分);如果未指定,则默认按 M 拆分读取窗口。 - -#### 示例 - -将位于 192.168.1.10 的 InfluxDB 中, Bucket 名称为 test_bucket, 从UTC时间2023年06月01日00时00分00秒开始的数据,通过运行在 192.168.1.20 上的 taoskeeper, 同步至 TDengine 的 test_db 数据库中,完整的命令如下所示: -```bash -# version = 1.x -taosx run \ - --from "influxdb+http://192.168.1.10:8086/?version=1.7&username=test&password=123456&bucket=test_bucket&measurements=&beginTime=2023-06-01T00:00:00+0800&readWindow=M" \ - --to "taos+http://192.168.1.20:6041/test_db" \ - -vv - -# version = 2.x -taosx run \ - --from "influxdb+http://192.168.1.10:8086/?version=2.7&orgId=3233855dc7e37d8d&token=OZ2sB6Ie6qcKcYAmcHnL-i3STfLVg_IRPQjPIzjsAQ4aUxCWzYhDesNape1tp8IsX9AH0ld41C-clTgo08CGYA==&bucket=test_bucket&measurements=&beginTime=2023-06-01T00:00:00+0800&readWindow=M" \ - --to "taos+http://192.168.1.20:6041/test_db" \ - -vv -``` - -在这个命令中,未指定endTime, 所以任务会长期运行,持续同步最新的数据。 - - -### 从 OpenTSDB 同步数据到 TDengine - -#### 命令行参数 - -将数据从 OpenTSDB 同步至 TDengine 的命令,如下所示: - -```bash -taosx run --from "" --to "" -``` - -其中,OpenTSDB DSN 符合 DSN 的通用规则,这里仅对其特有的参数进行说明: -- metrics: 非必填,可以指定需要同步的多个 Metrics(英文逗号分割),未指定则同步全部; -- beginTime: 必填,格式为:YYYY-MM-DD'T'HH:MM:SS'Z', 时区采用 UTC 时区,例如:2023-06-01T00:00:00+0800, 即北京时间2023-06-01 00:00:00(东八区时间); -- endTime: 非必填,可以不指定该字段或值为空,格式与beginTime相同;如果未指定,提交任务后,将持续进行数据同步; -- readWindow: 非必填,可以不指定该字段或值为空,可选项为D、H、M(天、时、分);如果未指定,则默认按分钟拆分读取窗口。 - -#### 示例 - -将位于 192.168.1.10 的 OpenTSDB 中, Metric 名称为 test_metric1 与 test_metric2 的两个数据源, 从UTC时间2023年06月01日00时00分00秒开始的数据,通过运行在 192.168.1.20 上的 taoskeeper, 同步至 TDengine 的 test_db 数据库中,完整的命令如下所示: - -```bash -taosx run \ - --from "opentsdb+http://192.168.1.10:4242/?metrics=test_metric1,test_metric2&beginTime=2023-06-01T00:00:00+0800&readWindow=M" \ - --to "taos+http://192.168.1.20:6041/test_db" \ - -vv -``` - -在这个命令中,未指定endTime, 所以任务会长期运行,持续同步最新的数据。 - - -### 从 MQTT 同步数据到 TDengine - -目前,MQTT 连接器仅支持从 MQTT 服务端消费 JSON 格式的消息,并将其同步至 TDengine. 命令如下所示: - -```bash -taosx run --from "" --to "" --parser "@" -``` - -其中: -- `--from` 用于指定 MQTT 数据源的 DSN -- `--to` 用于指定 TDengine 的 DSN -- `--parser` 用于指定一个 JSON 格式的配置文件,该文件决定了如何解析 JSON 格式的 MQTT 消息,以及写入 TDengine 时的超级表名、子表名、字段名称和类型,以及标签名称和类型等。 - -#### MQTT DSN 配置 - -MQTT DSN 符合 DSN 的通用规则,这里仅对其特有的参数进行说明: -- topics: 必填,用于配置监听的 MQTT 主题名称和连接器支持的最大 QoS, 采用 `::` 的形式;支持配置多个主题,使用逗号分隔;配置主题时,还可以使用 MQTT 协议的支持的通配符#和+; -- version: 非必填,用于配置 MQTT 协议的版本,支持的版本包括:3.1/3.1.1/5.0, 默认值为3.1; -- clean_session: 非必填,用于配置连接器作为 MQTT 客户端连接至 MQTT 服务端时,服务端是否保存该会话信息,其默认值为 true, 即不保存会话信息; -- client_id: 必填,用于配置连接器作为 MQTT 客户端连接至 MQTT 服务端时的客户端 id; -- keep_alive: 非必填,用于配置连接器作为 MQTT 客户端,向 MQTT 服务端发出 PINGREG 消息后的等待时间,如果连接器在该时间内,未收到来自 MQTT 服务端的 PINGREQ, 连接器则主动断开连接;该配置的单位为秒,默认值为 60; -- ca: 非必填,用于指定连接器与 MQTT 服务端建立 SSL/TLS 连接时,使用的 CA 证书,其值为在证书文件的绝对路径前添加@, 例如:@/home/admin/certs/ca.crt; -- cert: 非必填,用于指定连接器与 MQTT 服务端建立 SSL/TLS 连接时,使用的客户端证书,其值为在证书文件的绝对路径前添加@, 例如:@/home/admin/certs/client.crt; -- cert_key: 非必填,用于指定连接器与 MQTT 服务端建立 SSL/TLS 连接时,使用的客户端私钥,其值为在私钥文件的绝对路径前添加@, 例如:@/home/admin/certs/client.key; -- log_level: 非必填,用于配置连接器的日志级别,连接器支持 error/warn/info/debug/trace 5种日志级别,默认值为 info. - -一个完整的 MQTT DSN 示例如下: -```bash -mqtt://:@:8883?topics=testtopic/1::2&version=3.1&clean_session=true&log_level=info&client_id=taosdata_1234&keep_alive=60&ca=@/home/admin/certs/ca.crt&cert=@/home/admin/certs/client.crt&cert_key=@/home/admin/certs/client.key -``` - -#### MQTT 连接器的解释器配置 - -连接器的解释器配置文件,即`--parser`配置项的参数,它的值为一个 JSON 文件,其配置可分为`parse`和`model`两部分,模板如下所示: - -```json -{ - "parse": { - "payload": { - "json": [ - { - "name": "ts", - "alias": "ts", - "cast": "TIMESTAMP" - }, - ... - ] - } - }, - "model": { - "using": "", - "name": "{alias}", - "columns": [ ... ], - "tags": [ ... ] - } -} -``` - -各字段的说明如下: -- parse 部分目前仅支持 json 一种 payload, json 字段的值是一个由 JSON Object 构成的 JSON Array: - - 每个 JSON Ojbect 包括 name, alias, cast 三个字段; - - name 字段用于指定如何从 MQTT 消息中提取字段,如果 MQTT 消息是一个简单的 JSON Object, 这里可以直接设置其字段名;如果 MQTT 消息是一个复杂的 JSON Object, 这里可以使用 JSON Path 提取字段,例如:`$.data.city`; - - alias 字段用于命名 MQTT 消息中的字段同步至 TDengine 后使用的名称; - - cast 字段用于指定 MQTT 消息中的字段同步至 TDengine 后使用的类型。 -- model 部分用于设置 TDengine 超级表、子表、列和标签等信息: - - using 字段用于指定超级表名称; - - name 字段用于指定子表名称,它的值可以分为前缀和变量两部分,变量为 parse 部分设置的 alias 的值,需要使用{}, 例如:d{id}; - - columns 字段用于设置 MQTT 消息中的哪些字段作为 TDengine 超级表中的列,取值为 parse 部分设置的 alias 的值;需要注意的是,这里的顺序会决定 TDengine 超级表中列的顺序,因此第一列必须为 TIMESTAMP 类型; - - tags 字段用于设置 MQTT 消息中的哪些字段作为 TDengine 超级表中的标签,取值为 parse 部分设置的 alias 的值。 - -#### 举例说明 - -在 192.168.1.10 的 1883 端口运行着一个 MQTT broker, 用户名、口令分别为admin, 123456; 现欲将其中的消息,通过运行在 192.168.1.20 的 taosadapter 同步至 TDengine 的 test 数据库中。MQTT 消息格式为: - -```json -{ - "id": 1, - "current": 10.77, - "voltage": 222, - "phase": 0.77, - "groupid": 7, - "location": "California.SanDiego" -} -``` - -MQTT 消息同步至 TDengine 时, 如果采用 meters 作为超级表名,前缀“d”拼接id字段的值作为子表名,ts, id, current, voltage, phase作为超级表的列,groupid, location作为超级表的标签,其解释器的配置如下: -```json -{ - "parse": { - "payload": { - "json": [ - { - "name": "ts", - "alias": "ts", - "cast": "TIMESTAMP" - }, - { - "name": "id", - "alias": "id", - "cast": "INT" - }, - { - "name": "voltage", - "alias": "voltage", - "cast": "INT" - }, - { - "name": "phase", - "alias": "phase", - "cast": "FLOAT" - }, - { - "name": "current", - "alias": "current", - "cast": "FLOAT" - }, - { - "name": "groupid", - "alias": "groupid", - "cast": "INT" - }, - { - "name": "location", - "alias": "location", - "cast": "VARCHAR(20)" - } - ] - } - }, - "model": { - "name": "d{id}", - "using": "meters", - "columns": [ - "ts", - "id", - "current", - "voltage", - "phase" - ], - "tags": [ - "groupid", - "location" - ] - } -} -``` - -如果以上parser配置位于`/home/admin/parser.json`中,那么完整的命令如下所示: - -```bash -taosx run \ - -f "mqtt://admin:123456@192.168.1.10:1883?topics=testtopic/1::2&version=3.1&clean_session=true&log_level=info&client_id=1234&keep_alive=60" \ - -t "taos+ws://192.168.1.20:6041/test" - --parser "@/home/admin/parser.json" - --verbose -``` - -### 从 Kafka 同步数据到 TDengine - -#### 命令行参数 - -taosx 支持从 Kafka 消费数据,写入 TDengine。命令如下所示: -```sehll -taosx run -f "" -t "" -``` -或 -```shell -taosx run -f "" -t "" --parser "@" -``` -其中: -- -f或--from: Kafka 的 DSN -- -t或--to :TDengine 的 DSN -- --parser :一个 JSON 格式的配置文件,或JSON格式的字符串。 - -#### Kafka DSN 配置的配置 - -| 参数 | 说明 | 必填? | 缺省值 | 适用于 | 示例 | -|-----|---------------|----------|---------|---------|----------| -| group| 消费者的group。允许组为空字符串,在这种情况下,生成的消费者将是无组的 | 否 | "" | 源端 | | -| topics | 指定要使用的主题。指定主题的所有可用分区都将被使用,除非在指定 topic_partitions 时被覆盖。| 该参数或topic_partitions必须至少指定一个,以便将主题分配给消费者。| None | 源端 | topics=tp1,tp2 | -| topic_partitions | 显式指定要使用的主题分区。只使用已标识主题的指定分区。 | 该参数或topics必须至少指定一个,以便将主题分配给消费者。 | None | 源端 | topic_partitions=tp1:0..2,tp2:1 | -| fallback_offset | topic偏移量时可能的值:- Earliest:接收最早的可用偏移量; - Latest:接收最近的偏移量; - ByTime(i64):用于请求在某一特定时间(ms)之前的所有消息;Unix时间戳(毫秒) | 否 | Earliest | 源端 | fallback_offset=Earliest | -| offset_storage | 定义在获取或提交组偏移量时,要使用的可用存储:- Zookeeper:基于Zookeeper的存储(从kafka 0.8.1开始可用);- Kafka:基于Kafka的存储(从Kafka 0.8.2开始可用)。这是组存储其偏移量的首选方法。 | 否 | Kafka | 源端 | offset_storage=Kafka | -| timeout | 从kafka订阅数据时,如果超时后没有获取到有效数据,退出 | 否 | 500 | 源端 | timeout=never | -| use_ssl | 是否使用SSL认证 | 否 | | 源端 | | -| cert | SSL证书的文件路径 | 否 | | | 源端 | | -| cert_key | SSL证书key的文件路径 | 否 | | 源端 || - - -#### 示例一 - -从192.168.1.92服务器的Kafka实例中消费数据,同步到192.168.1.92上的TDengine,不使用parser。 - -1. kafka - -```shell -#!/bin/bash -KAFKA_HOME=/root/zyyang/kafka_2.13-3.1.0 -$KAFKA_HOME/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --topic tp1 --delete -$KAFKA_HOME/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --topic tp2 --delete -$KAFKA_HOME/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --topic tp1 --partitions 5 --replication-factor 1 --create -$KAFKA_HOME/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --topic tp2 --partitions 1 --replication-factor 1 --create -$KAFKA_HOME/bin/kafka-console-producer.sh --bootstrap-server 127.0.0.1:9092 --topic tp1 << EOF -{"id": 1, "message": "hello"} -{"id": 2, "message": "hello"} -{"id": 3, "message": "hello"} -{"id": 4, "message": "hello"} -{"id": 5, "message": "hello"} -EOF -$KAFKA_HOME/bin/kafka-console-producer.sh --bootstrap-server 127.0.0.1:9092 --topic tp2 << EOF -{"id": 1, "message": "aaa"} -{"id": 2, "message": "aaa"} -{"id": 3, "message": "aaa"} -{"id": 4, "message": "aaa"} -{"id": 5, "message": "aaa"} -EOF -$KAFKA_HOME/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --topic tp1 --describe -$KAFKA_HOME/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --topic tp2 --describe -``` - -2. TDengine - -```shell -drop database if exists kafka_to_taos; -create database if not exists kafka_to_taos precision 'ms'; -use kafka_to_taos; -``` - -3. taosx - -```shell -taosx run -f "kafka://192.168.1.92:9092/?topics=tp1,tp2&timeout=5000" -t "taos://192.168.1.92:6030/kafka_to_taos" --parser "{\"parse\":{\"ts\":{\"as\":\"timestamp(ms)\"},\"topic\":{\"as\":\"varchar\",\"alias\":\"t\"},\"partition\":{\"as\":\"int\",\"alias\":\"p\"},\"offset\":{\"as\":\"bigint\",\"alias\":\"o\"},\"key\":{\"as\":\"binary\",\"alias\":\"k\"},\"value\":{\"as\":\"binary\",\"alias\":\"v\"}},\"model\":[{\"name\":\"t_{t}\",\"using\":\"kafka_data\",\"tags\":[\"t\",\"p\"],\"columns\":[\"ts\",\"o\",\"k\",\"v\"]}]}" -``` - -#### 示例2 - -从192.168.1.92服务器的Kafka实例中消费数据,同步到192.168.1.92上的TDengine,使用parser解析value中的JSON数据。 - -1. kafka,同“示例1” -2. TDengine,同“示例1” -3. Taosx - -```shell -taosx run -f "kafka://192.168.1.92:9092/?topics=tp1,tp2&timeout=5000" -t "taos://192.168.0.201:6030/kafka_to_taos" --parser "{\"parse\":{\"ts\":{\"as\":\"timestamp(ms)\"},\"topic\":{\"as\":\"varchar\",\"alias\":\"t\"},\"partition\":{\"as\":\"int\",\"alias\":\"p\"},\"offset\":{\"as\":\"bigint\",\"alias\":\"o\"},\"value\":{\"json\":[\"id::int\",\"message::binary\"]}},\"model\":[{\"name\":\"t_{t}\",\"using\":\"kafka_data\",\"tags\":[\"t\",\"p\"],\"columns\":[\"ts\",\"o\",\"id\",\"message\"]}]}" -``` - -## 服务模式 - -在服务模式下, 一共需要三个组件协同完成数据迁移。 taosX,Agent 以及 taosExplorer 均已服务态运行,各种操作通过 taosExplorer 的图形界面进行。taos-Explorer 组件除了数据迁移之外,还提供了使用 TDengine 的图形化界面。 - -### 部署 taosX - -#### 配置 - -taosX 仅支持通过命令行参数进行配置。服务模式下,taosX 支持的命令行参数可以通过以下方式查看: - -``` -taosx serve --help -``` - -建议通过 Systemd 的方式,启动 taosX 的服务模式,其 Systemd 的配置文件位于:`/etc/systemd/system/taosx.service`. 如需修改 taosX 的启动参数,可以编辑该文件中的以下行: - -``` -ExecStart=/usr/bin/taosx serve -v -``` - -修改后,需执行以下命令重启 taosX 服务,使配置生效: - -``` -systemctl daemon-reload -systemctl restart taosx -``` - -#### 启动 - -Linux 系统上以 Systemd 的方式启动 taosX 的命令如下: - -```shell -systemctl start taosx -``` - -Windows 系统上,请在 "Services" 系统管理工具中找到 "taosX" 服务,然后点击 "启动这个服务"。 - -#### 问题排查 - -1. 如何修改 taosX 的日志级别? - -taosX 的日志级别是通过命令行参数指定的,默认的日志级别为 Info, 具体参数如下: -- INFO: `taosx serve -v` -- DEBUG: `taosx serve -vv` -- TRACE: `taosx serve -vvv` - -Systemd 方式启动时,如何修改命令行参数,请参考“配置”章节。 - -2. 如何查看 taosX 的日志? - -以 Systemd 方式启动时,可通过 journalctl 命令查看日志。以滚动方式,实时查看最新日志的命令如下: - -``` -journalctl -u taosx -f -``` - -### 部署 Agent - -#### 配置 - -Agent 默认的配置文件位于`/etc/taos/agent.toml`, 包含以下配置项: -- endpoint: 必填,taosX 的 GRPC endpoint -- token: 必填,在 taosExplorer 上创建 agent 时,产生的token -- debug_level: 非必填,默认为 info, 还支持 debug, trace 等级别 - -如下所示: - -```TOML -endpoint = "grpc://:6055" -token = "" -log_level = "debug" -``` - -日志保存时间设置 -日志保存的天数可以通过环境变量进行设置 TAOSX_LOGS_KEEP_DAYS, 默认为 30 天。 - -```shell -export TAOSX_LOGS_KEEP_DAYS=7 -``` - -#### 启动 - -Linux 系统上 Agent 可以通过 Systemd 命令启动: - -``` -systemctl start taosx-agent -``` - -Windows 系统上通过系统管理工具 "Services" 找到 taosx-agent 服务,然后启动它。 - -#### 问题排查 - -可以通过 journalctl 查看 Agent 的日志 - -``` -journalctl -u taosx-agent -f -``` - -### 部署 taosExplorer - - -### 数据同步功能 - -请参考 taosExplorer \ No newline at end of file diff --git a/docs/zh/18-data-transfer/02-explorer.md b/docs/zh/18-data-transfer/02-explorer.md deleted file mode 100644 index fd374cc734..0000000000 --- a/docs/zh/18-data-transfer/02-explorer.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: 基于可视化界面的数据接入和数据迁移 ---- - -本节讲述使用 taos Explorer 的可视化界面进行数据迁移,使用此功能需要依赖 taosd, taosAdapter, taosX, taos-explorer 等几个服务组件。关于 taosd 和 taosAdapter 的部署请参考 [系统部署](../../deployment/deploy),[taosX](../taosX),以及 [部署 taos-explorer](../../operation/web) - -## 功能入口 - -点击 explorer 左侧功能列表中的 "数据写入",可以配置不同类型的数据源,包括 TDengine Subscription, PI, OPC-UA, OPC-DA, InfluxDB, MQTT,Kafka, CSV 等,将它们的数据写入到当前正在被管理的 TDengine 集群中。 - -## TDengine 订阅 - -进入TDengine订阅任务配置页面: -1. 在连接协议栏中,配置连接协议,默认为原生连接,可配置为WS、WSS; -2. 在服务器栏中配置服务器的 IP 或域名; -3. 在端口栏中配置连接的端口号,默认值为6030; -4. 在主题栏中,配置可以配置订阅一个或多个数据库,或超级表或普通表,也可以是一个已创建的 Topic; -5. 在认证栏,可以配置访问 TDengine 的用户名密码,用户名默认值为 root,密码默认值为 taosdata;如果数据源为云服务实例,则可以选择令牌认证方式并配置实例 token; -6. 在订阅初始位置栏,可配置从最早数据(earliest)或最晚(latest)数据开始订阅,默认为 earliest; -7. 在超时栏配置超时时间,可配置为 never: 表示无超时时间,持续进行订阅,也可指定超时时间:5s, 1m 等,支持单位 ms(毫秒),s(秒),m(分钟),h(小时),d(天),M(月),y(年)。 -8. 在目标数据库栏中,选择本地 TDengine 的库作为目标库,点击 submit,即可启动一个 TDengine 订阅任务。 - -## Pi - -1. 在 PI 数据接入页面,设置 PI 服务器的名称、AF 数据库名称。 -2. 在监测点集栏,可以配置选择 Point 模式监测点集合、Point 模式监测的 AF 模板、AF 模式监测的 AF 模板。 -3. 在 PI 系统设置栏,可以配置 PI 系统名,默认为 PI 服务器名。 -4. 在 Data Queue 栏,可以配置 PI 连接器运行参数:MaxWaitLen(数据最大缓冲条数),默认值为 1000 ,有效取值范围为 [1,10000];UpdateInterval(PI System 取数据频率),默认值为 10000(毫秒:ms),有效取值范围为 [10,600000];重启补偿时间(Max Backfill Range,单位:天),每次重启服务时向前补偿该天数的数据,默认为1天。 -5. 在目标数据库栏,选择需要写入的 TDengine 数据库,点击 submit ,即可启动一个 PI 数据接入任务。 - -## OPC-UA - -1. 在 OPC-UA页面,配置 OPC-server 的地址,输入格式为 127.0.0.1:6666/OPCUA/ServerPath。 -2. 在认证栏,选择访问方式。可以选择匿名访问、用户名密码访问、证书访问。使用证书访问时,需配置证书文件信息、私钥文件信息、OPC-UA 安全协议和 OPC-UA 安全策略 -3. 在 Data Sets 栏,配置点位信息。(可通过“选择”按钮选择正则表达式过滤点位,每次最多能过滤出10条点位);点位配置有两种方式:1.手动输入点位信息 2.上传csv文件配置点位信息 -4. 在连接配置栏,配置连接超时间隔和采集超时间隔(单位:秒),默认值为10秒。 -5. 在采集配置栏,配置采集间隔(单位:秒)、点位数量、采集模式。采集模式可选择observe(轮询模式)和subscribe(订阅模式),默认值为observe。 -6. 在库表配置栏,配置目标 TDengine 中存储数据的超级表、子表结构信息。 -7. 在其他配置栏,配置并行度、单次采集上报批次(默认值100)、上报超时时间(单位:秒,默认值10)、是否开启debug级别日志。 -8. 在目标数据库栏,选择需要写入的 TDengine 数据库,点击 submit,即可启动一个 OPC-UA 数据接入任务。 - -## OPC-DA - -1. 在 OPC-DA页面,配置 OPC-server 的地址,输入格式为 127.0.0.1<,localhost>/Matrikon.OPC.Simulation.1。 -2. 在数据点栏,配置 OPC-DA 采集点信息。(可通过“选择”按钮选择正则表达式过滤点位,每次最多能过滤出10条点位)。点位配置有两种方式:1.手动输入点位信息 2.上传csv文件配置点位信息 -3. 在连接栏,配置连接超时时间(单位:秒,默认值为10秒)、采集超时时间(单位:秒,默认值为10秒)。 -4. 在库表配置栏,配置目标 TDengine 中存储数据的超级表、子表结构信息。 -5. 在其他配置栏,配置并行度、单次采集上报批次(默认值100)、上报超时时间(单位:秒,默认值10)、是否开启debug级别日志。 -6. 在目标数据库栏,选择需要写入的 TDengine 数据库,点击 submit,即可启动一个 OPC-DA 数据接入任务。 - -## InfluxDB - -进入 InfluxDB 数据源同步任务的编辑页面后: -1. 在服务器地址输入框, 输入 InfluxDB 服务器的地址,可以输入 IP 地址或域名,此项为必填字段; -2. 在端口输入框, 输入 InfluxDB 服务器端口,默认情况下,InfluxDB 监听8086端口的 HTTP 请求和8088端口的 HTTPS 请求,此项为必填字段; -3. 在组织 ID 输入框,输入将要同步的组织 ID,此项为必填字段; -4. 在令牌 Token 输入框,输入一个至少拥有读取这个组织 ID 下的指定 Bucket 权限的 Token, 此项为必填字段; -5. 在同步设置的起始时间项下,通过点选选择一个同步数据的起始时间,起始时间使用 UTC 时间, 此项为必填字段; -6. 在同步设置的结束时间项下,当不指定结束时间时,将持续进行最新数据的同步;当指定结束时间时,将只同步到这个结束时间为止; 结束时间使用 UTC 时间,此项为可选字段; -7. 在桶 Bucket 输入框,输入一个需要同步的 Bucket,目前只支持同步一个 Bucket 至 TDengine 数据库,此项为必填字段; -8. 在目标数据库下拉列表,选择一个将要写入的 TDengine 目标数据库 (注意:目前只支持同步到精度为纳秒的 TDengine 目标数据库),此项为必填字段; -9. 填写完成以上信息后,点击提交按钮,即可直接启动从 InfluxDB 到 TDengine 的数据同步。 - -## MQTT - -进入 MQTT 数据源同步任务的编辑页面后: -1. 在 MQTT 地址卡片,输入 MQTT 地址,必填字段,包括 IP 和 端口号,例如:192.168.1.10:1883; -2. 在认证卡片,输入 MQTT 连接器访问 MQTT 服务器时的用户名和密码,这两个字段为选填字段,如果未输入,即采用匿名认证的方式; -3. 在 SSL 证书卡片,可以选择是否打开 SSL/TLS 开关,如果打开此开关,MQTT 连接器和 MQTT 服务器之间的通信将采用 SSL/TLS 的方式进行加密;打开这个开关后,会出现 CA, 客户端证书和客户端私钥三个必填配置项,可以在这里输入证书和私钥文件的内容; -4. 在连接卡片,可以配置以下信息: - - MQTT 协议:支持3.1/3.1.1/5.0三个版本; - - Client ID: MQTT 连接器连接 MQTT 服务器时所使用的客户端 ID, 用于标识客户端的身份; - - Keep Alive: 用于配置 MQTT 连接器与 MQTT 服务器之间的Keep Alive时间,默认值为60秒; - - Clean Session: 用于配置 MQTT 连接器是否以Clean Session的方式连接至 MQTT 服务器,默认值为True; - - 订阅主题及 QoS 配置:这里用来配置监听的 MQTT 主题,以及该主题支持的最大QoS, 主题和 QoS 的配置之间用::分隔,多个主题之间用,分隔,主题的配置可以支持 MQTT 协议的通配符#和+; -5. 在其他卡片,可以配置 MQTT 连接器的日志级别,支持 error, warn, info, debug, trace 5个级别,默认值为 info; -6. MQTT Payload 解析卡片,用于配置如何解析 MQTT 消息: - - 配置表的第一行为 ts 字段,该字段为 TIMESTAMP 类型,它的值为 MQTT 连接器收到 MQTT 消息的时间; - - 配置表的第二行为 topic 字段,为该消息的主题名称,可以选择将该字段作为列或者标签同步至 TDengine; - - 配置表的第三行为 qos 字段,为该消息的 QoS 属性,可以选择将该字段作为列或者标签同步至 TDengine; - - 剩余的配置项皆为自定义字段,每个字段都需要配置:字段(来源),列(目标),列类型(目标)。字段(来源)是指该 MQTT 消息中的字段名称,当前仅支持 JSON 类型的 MQTT 消息同步,可以使用 JSON Path 语法从 MQTT 消息中提取字段,例如:$.data.id; 列(目标)是指同步至 TDengine 后的字段名称;列类型(目标)是指同步至 TDengine 后的字段类型,可以从下拉列表中选择;当且仅当以上3个配置都填写后,才能新增下一个字段; - - 如果 MQTT 消息中包含时间戳,可以选择新增一个自定义字段,将其作为同步至 TDengine 时的主键;需要注意的是,MQTT 消息中时间戳的仅支持 Unix Timestamp格式,且该字段的列类型(目标)的选择,需要与创建 TDengine 数据库时的配置一致; - - 子表命名规则:用于配置子表名称,采用“前缀+{列类型(目标)}”的格式,例如:d{id}; - - 超级表名:用于配置同步至 TDengine 时,采用的超级表名; -7. 在目标数据库卡片,可以选择同步至 TDengine 的数据库名称,支持直接从下拉列表中选择。 -8. 填写完成以上信息后,点击提交按钮,即可直接启动从 MQTT 到 TDengine 的数据同步。 - -## Kafka - -1. 在Kafka页面,配置Kafka选项,必填字段,包括:bootstrap_server,例如192.168.1.92:9092; -2. 如果使用SSL认证,在SSL认证卡中,选择cert和cert_key的文件路径; -3. 配置其他参数,topics、topic_partitions这2个参数至少填写一个,其他参数有默认值; -4. 如果消费的Kafka数据是JSON格式,可以配置parser卡片,对数据进行解析转换; -5. 在目标数据库卡片中,选择同步到TDengine的数据库名称,支持从下拉列表中选择; -6. 填写完以上信息后,点击提交按钮,即可启动从Kafka到TDengine的数据同步。 - -## CSV - -1. 在CSV页面,配置CSV选项,可设置忽略前N行,可输入具体的数字 -2. CSV的写入配置,设置批次写入量,默认是1000 -3. CSV文件解析,用于获取CSV对应的列信息: - - 上传CSV文件或者输入CSV文件的地址 - - 选择是否包包含Header - - 包含Header情况下直接执行下一步,查询出对应CSV的列信息,获取CSV的配置信息 - - 不包含Header情况,需要输入自定列信息,并以逗号分隔,然后下一步,获取CSV的配置信息 - - CSV的配置项,每个字段都需要配置:CSV列,DB列,列类型(目标),主键(整个配置只能有一个主键,且主键必须是TIMESTAMP类型),作为列,作为Tag。CSV列是指该 CSV文件中的列或者自定义的列;DB列是对应的数据表的列 - - 子表命名规则:用于配置子表名称,采用“前缀+{列类型(目标)}”的格式,例如:d{id}; - - 超级表名:用于配置同步至 TDengine 时,采用的超级表名; -4. 在目标数据库卡片,可以选择同步至 TDengine 的数据库名称,支持直接从下拉列表中选择。 -5. 填写完成以上信息后,点击提交按钮,即可直接启动从 CSV到 TDengine 的数据同步。 - - -## 备份和恢复 - -您可以将当前连接的 TDengine 集群中的数据备份至一个或多个本地文件中,稍后可以通过这些文件进行数据恢复。本章节将介绍数据备份和恢复的具体步骤。 - -### 备份数据到本地文件 - -1. 进入系统管理页面,点击【备份】进入数据备份页面,点击右上角【新增备份】。 -2. 在数据备份配置页面中可以配置三个参数: - - 备份周期:必填项,配置每次执行数据备份的时间间隔,可通过下拉框选择每天、每 7 天、每 30 天执行一次数据备份,配置后,会在对应的备份周期的0:00时启动一次数据备份任务; - - 数据库:必填项,配置需要备份的数据库名(数据库的 wal_retention_period 参数需大于0); - - 目录:必填项,配置将数据备份到 taosX 所在运行环境中指定的路径下,如 /root/data_backup; -3. 点击【确定】,可创建数据备份任务。 - -### 从本地文件恢复 - -1. 完成数据备份任务创建后,在页面中对应的数据备份任务右侧点击【数据恢复】,可将已经备份到指定路径下的数据恢复到当前 TDengine 中。 \ No newline at end of file diff --git a/docs/zh/18-data-transfer/index.md b/docs/zh/18-data-transfer/index.md deleted file mode 100644 index 749ad16308..0000000000 --- a/docs/zh/18-data-transfer/index.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: 数据集成 ---- \ No newline at end of file From c6607931939b3fb4994ed2b13c1ece74e5981bef Mon Sep 17 00:00:00 2001 From: Wade Zhang Date: Fri, 25 Aug 2023 23:34:39 +0800 Subject: [PATCH 070/120] doc: remove grant from website --- docs/zh/12-taos-sql/25-grant.md | 239 -------------------------------- 1 file changed, 239 deletions(-) delete mode 100644 docs/zh/12-taos-sql/25-grant.md diff --git a/docs/zh/12-taos-sql/25-grant.md b/docs/zh/12-taos-sql/25-grant.md deleted file mode 100644 index 42d740539f..0000000000 --- a/docs/zh/12-taos-sql/25-grant.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -sidebar_label: 权限管理 -title: 权限管理 -description: 企业版中才具有的权限管理功能 ---- - -本节讲述如何在 TDengine 中进行权限管理的相关操作。权限管理是 TDengine 企业版的特有功能,欲试用 TDengine 企业版请联系 TDengine 销售或市场团队。 - -TDengine 中的权限管理分为用户管理、数据库授权管理以及消息订阅授权管理。 - -当 TDengine 安装并部署成功后,系统中内置有 "root" 用户。持有默认 "root" 用户密码的系统管理员应该第一时间修改 root 用户的密码,并根据业务需要创建普通用户并为这些用户授予适当的权限。在未授权的情况下,普通用户可以创建 DATABASE,并拥有自己创建的 DATABASE 的所有权限,包括删除数据库、修改数据库、查询时序数据和写入时序数据。超级用户可以给普通用户授予其他(即非该用户所创建的) DATABASE 的读写权限,使其可以在这些 DATABASE 上读写数据,但不能对其进行删除和修改数据库的操作。超级用户或者 topic 的创建者也可以给其它用户授予对某个 topic 的订阅权限。 - -## 用户管理 - -用户管理涉及用户的整个生命周期,从创建用户、对用户进行授权、撤销对用户的授权、查看用户信息、直到删除用户。 - -### 创建用户 - -创建用户的操作只能由 root 用户进行,语法如下 - -```sql -CREATE USER user_name PASS 'password' [SYSINFO {1\|0}]; -``` - -说明: - -- user_name 最长为 23 字节。 -- password 最长为 128 字节,合法字符包括"a-zA-Z0-9!?\$%\^&\*()_–+={[}]:;@\~\#\|\<,\>.?/",不可以出现单双引号、撇号、反斜杠和空格,且不可以为空。 -- SYSINFO 表示用户是否可以查看系统信息。1 表示可以查看,0 表示不可以查看。系统信息包括服务端配置信息、服务端各种节点信息(如 DNODE、QNODE等)、存储相关的信息等。默认为可以查看系统信息。 - -示例:创建密码为123456且可以查看系统信息的用户 test - -``` -SQL taos\> create user test pass '123456' sysinfo 1; Query OK, 0 of 0 rows affected (0.001254s) -``` - -### 查看用户 - -查看系统中的用户信息请使用 show users 命令,示例如下 - -```sql -show users; -``` - -也可以通过查询系统表 `INFORMATION_SCHEMA.INS_USERS` 获取系统中的用户信息,示例如下 - -```sql -select * from information_schema.ins_users; -``` - -### 删除用户 - -删除用户请使用 - -```sql -DROP USER user_name; -``` - -### 修改用户信息 - -修改用户信息的命令如下 - -```sql -ALTER USER user_name alter_user_clause alter_user_clause: { PASS 'literal' \| ENABLE value \| SYSINFO value } -``` - -说明: - -- PASS:修改用户密码。 -- ENABLE:修改用户是否启用。1 表示启用此用户,0 表示禁用此用户。 -- SYSINFO:修改用户是否可查看系统信息。1 表示可以查看系统信息,0 表示不可以查看系统信息。 - -示例:禁用 test 用户 - -```sql -alter user test enable 0; Query OK, 0 of 0 rows affected (0.001160s) -``` - -```sql -CREATE USER use_name PASS 'password' [SYSINFO {1|0}]; -``` - -## 访问控制 - -在 TDengine 企业版中,系统管理员可以根据业务和数据安全的需要控制任意一个用户对每一个数据库、订阅甚至表级别的访问。 - -```sql -GRANT privileges ON priv_level TO user_name - -privileges : { - ALL - | priv_type [, priv_type] ... -} - -priv_type : { - READ - | WRITE -} - -priv_level : { - dbname.* - | *.* -} -``` - -### 数据库权限 - - -TDengine 有超级用户和普通用户两类用户。超级用户缺省创建为root,拥有所有权限。使用超级用户创建出来的用户为普通用户。在未授权的情况下,普通用户可以创建 DATABASE,并拥有自己创建的 DATABASE 的所有权限,包括删除数据库、修改数据库、查询时序数据和写入时序数据。超级用户可以给普通用户授予其他 DATABASE 的读写权限,使其可以在此 DATABASE 上读写数据,但不能对其进行删除和修改数据库的操作。 - -对于非DATABASE的对象,如USER、DNODE、UDF、QNODE等,普通用户只有读权限(一般为SHOW命令),不能创建和修改。 - -对数据库的访问权限包含读和写两种权限,它们可以被分别授予,也可以被同时授予。 - -补充说明 - -- priv_level 格式中 "." 之前为数据库名称, "." 之后为表名称 -- "dbname.\*" 意思是名为 "dbname" 的数据库中的所有表 -- "\*.\*" 意思是所有数据库名中的所有表 - -**下表中总结了数据库权限的各种组合** - -对 root 用户和普通用户的权限的说明如下表 - -| 用户 | 描述 | 权限说明 | -|----------|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 超级用户 | 只有 root 是超级用户 | DB 外部 所有操作权限,例如user、dnode、udf、qnode等的CRUD DB 权限,包括 创建 删除 更新,例如修改 Option,移动 Vgruop等 读 写 Enable/Disable 用户 | -| 普通用户 | 除 root 以外的其它用户均为普通用户 | 在可读的 DB 中,普通用户可以进行读操作 select describe show subscribe 在可写 DB 的内部,用户可以进行写操作: 创建、删除、修改 超级表 创建、删除、修改 子表 创建、删除、修改 topic 写入数据 被限制系统信息时,不可进行如下操作 show dnode、mnode、vgroups、qnode、snode 修改用户包括自身密码 show db时只能看到自己的db,并且不能看到vgroups、副本、cache等信息 无论是否被限制系统信息,都可以 管理 udf 可以创建 DB 自己创建的 DB 具备所有权限 非自己创建的 DB ,参照读、写列表中的权限 | - -### 消息订阅授权 - -任意用户都可以在自己拥有读权限的数据库上创建 topic。超级用户 root 可以在任意数据库上创建 topic。每个 topic 的订阅权限都可以被独立授权给任何用户,不管该用户是否拥有该数据库的访问权限。删除 topic 只能由 root 用户或者该 topic 的创建者进行。topic 只能由超级用户、topic的创建者或者被显式授予 subscribe 权限的用户订阅。 - -授予订阅权限的语法如下: - -```sql -GRANT privileges ON priv_level TO user_name privileges : { ALL | priv_type [, priv_type] ... } priv_type : { SUBSCRIBE } priv_level : { topic_name } -``` - -### 基于标签的授权(表级授权) - -从 TDengine 3.0.5.0 开始,我们支持按标签授权某个超级表中部分特定的子表。具体的 SQL 语法如下。 - -```sql -GRANT privileges ON priv_level [WITH tag_condition] TO user_name - -privileges : { - ALL - | SUBSCRIBE - | priv_type [, priv_type] ... -} - -priv_type : { - READ - | WRITE -} - -priv_level : { - dbname.tbname - | dbname.* - | *.* - | topic_name -} - -REVOKE privileges ON priv_level [WITH tag_condition] FROM user_name - -privileges : { - ALL - | priv_type [, priv_type] ... -} - -priv_type : { - READ - | WRITE -} - -priv_level : { - dbname.tbname - | dbname.* - | *.* -} -``` - -上面 SQL 的语义为: - -- 用户可以通过 dbname.tbname 来为指定的表(包括超级表和普通表)授予或回收其读写权限,不支持直接对子表授予或回收权限。 -- 用户可以通过 dbname.tbname 和 WITH 子句来为符合条件的所有子表授予或回收其读写权限。使用 WITH 子句时,权限级别必须为超级表。 - -**表级权限和数据库权限的关系** - -下表列出了在不同的数据库授权和表级授权的组合下产生的实际权限。 - -| |**表无授权** | **表读授权** | **表读授权有标签条件** | **表写授权** | **表写授权有标签条件** | -| -------------- | ---------------- | -------- | ---------- | ------ | ----------- | -| **数据库无授权** | 无授权 | 对此表有读权限,对数据库下的其他表无权限 | 对此表符合标签权限的子表有读权限,对数据库下的其他表无权限 | 对此表有写权限,对数据库下的其他表无权限 | 对此表符合标签权限的子表有写权限,对数据库下的其他表无权限 | -| **数据库读授权** | 对所有表有读权限 | 对所有表有读权限 | 对此表符合标签权限的子表有读权限,对数据库下的其他表有读权限 | 对此表有写权限,对所有表有读权限 | 对此表符合标签权限的子表有写权限,所有表有读权限 | -| **数据库写授权** | 对所有表有写权限 | 对此表有读权限,对所有表有写权限 | 对此表符合标签权限的子表有读权限,对所有表有写权限 | 对所有表有写权限 | 对此表符合标签权限的子表有写权限,数据库下的其他表有写权限 | - -### 查看用户授权 - -使用下面的命令可以显示一个用户所拥有的授权: - -```sql -show user privileges -``` -## 撤销授权 - -```sql -REVOKE privileges ON priv_level FROM user_name - -privileges : { - ALL - | priv_type [, priv_type] ... -} - -priv_type : { - READ - | WRITE -} - -priv_level : { - dbname.* - | *.* -} - -``` - -### 撤销授权 - -1. 撤销数据库访问的授权 - -```sql -REVOKE privileges ON priv_level FROM user_name privileges : { ALL \| priv_type [, priv_type] ... } priv_type : { READ \| WRITE } priv_level : { dbname.\* \| \*.\* } -``` - -2. 撤销数据订阅的授权 - -```sql -REVOKE privileges ON priv_level FROM user_name privileges : { ALL \| priv_type [, priv_type] ... } priv_type : { SUBSCRIBE } priv_level : { topi_name } From 8b745de397575003717aa5bd109d6b408aabf076 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Sat, 26 Aug 2023 00:43:47 +0800 Subject: [PATCH 071/120] fix:transaction error --- source/dnode/mnode/impl/src/mndConsumer.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/source/dnode/mnode/impl/src/mndConsumer.c b/source/dnode/mnode/impl/src/mndConsumer.c index e83235fd74..f25bd2cffb 100644 --- a/source/dnode/mnode/impl/src/mndConsumer.c +++ b/source/dnode/mnode/impl/src/mndConsumer.c @@ -212,11 +212,8 @@ static int32_t mndProcessConsumerClearMsg(SRpcMsg *pMsg) { mndReleaseConsumer(pMnode, pConsumer); - STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_TOPIC, pMsg, "clear-csm"); + STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_NOTHING, pMsg, "clear-csm"); if (pTrans == NULL) goto FAIL; - if (validateTopics(pTrans, pConsumer->assignedTopics, pMnode, pMsg->info.conn.user) != 0){ - goto FAIL; - } // this is the drop action, not the update action if (mndSetConsumerDropLogs(pMnode, pTrans, pConsumerNew) != 0) goto FAIL; if (mndTransPrepare(pMnode, pTrans) != 0) goto FAIL; From 1bf2c3421dc5901f8883874355313a60bce6964d Mon Sep 17 00:00:00 2001 From: Shuduo Sang Date: Sun, 27 Aug 2023 00:05:44 +0800 Subject: [PATCH 072/120] docs: kill query command need quote around id (#22588) * docs: add current_user() in function * docs: kill query command need quote around id --- docs/en/12-taos-sql/28-recovery.md | 2 +- docs/zh/12-taos-sql/28-recovery.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/12-taos-sql/28-recovery.md b/docs/en/12-taos-sql/28-recovery.md index b4da25ea0c..1c1838f616 100644 --- a/docs/en/12-taos-sql/28-recovery.md +++ b/docs/en/12-taos-sql/28-recovery.md @@ -17,7 +17,7 @@ You can use the SHOW CONNECTIONS statement to find the conn_id. ## Terminate a Query ```sql -KILL QUERY kill_id; +KILL QUERY 'kill_id'; ``` You can use the SHOW QUERIES statement to find the kill_id. diff --git a/docs/zh/12-taos-sql/28-recovery.md b/docs/zh/12-taos-sql/28-recovery.md index b5088e7982..8e327afe0b 100644 --- a/docs/zh/12-taos-sql/28-recovery.md +++ b/docs/zh/12-taos-sql/28-recovery.md @@ -17,7 +17,7 @@ conn_id 可以通过 `SHOW CONNECTIONS` 获取。 ## 终止查询 ```sql -KILL QUERY kill_id; +KILL QUERY 'kill_id'; ``` kill_id 可以通过 `SHOW QUERIES` 获取。 From 06083e1eb9a344ea79d850f71a66d5f7ba718b13 Mon Sep 17 00:00:00 2001 From: dmchen Date: Mon, 28 Aug 2023 09:15:51 +0800 Subject: [PATCH 073/120] init --- include/common/tglobal.h | 5 +++ include/common/tmsg.h | 44 +++++++++++++++++++++++ include/libs/audit/audit.h | 46 ++++++++++++++++++++++++ source/common/src/tglobal.c | 13 +++++++ source/dnode/mgmt/node_mgmt/src/dmEnv.c | 20 +++++++++++ source/dnode/mnode/impl/CMakeLists.txt | 2 +- source/dnode/mnode/impl/src/mndDb.c | 9 +++++ source/dnode/mnode/impl/src/mndDnode.c | 21 +++++++++++ source/dnode/mnode/impl/src/mndMnode.c | 15 ++++++++ source/dnode/mnode/impl/src/mndProfile.c | 11 ++++++ source/dnode/mnode/impl/src/mndQnode.c | 10 ++++++ source/dnode/mnode/impl/src/mndStb.c | 7 ++++ source/dnode/mnode/impl/src/mndStream.c | 5 +++ source/dnode/mnode/impl/src/mndTopic.c | 5 +++ source/dnode/mnode/impl/src/mndUser.c | 26 ++++++++++++++ source/dnode/mnode/impl/src/mndVgroup.c | 8 +++++ source/dnode/vnode/CMakeLists.txt | 1 + source/dnode/vnode/src/vnd/vnodeSvr.c | 5 +++ source/libs/CMakeLists.txt | 1 + source/libs/audit/CMakeLists.txt | 13 +++++++ source/libs/audit/inc/auditInt.h | 25 +++++++++++++ source/libs/audit/src/auditMain.c | 43 ++++++++++++++++++++++ 22 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 include/libs/audit/audit.h create mode 100644 source/libs/audit/CMakeLists.txt create mode 100644 source/libs/audit/inc/auditInt.h create mode 100644 source/libs/audit/src/auditMain.c diff --git a/include/common/tglobal.h b/include/common/tglobal.h index 3de291cb91..aff5945f9f 100644 --- a/include/common/tglobal.h +++ b/include/common/tglobal.h @@ -102,6 +102,11 @@ extern uint16_t tsMonitorPort; extern int32_t tsMonitorMaxLogs; extern bool tsMonitorComp; +// audit +extern bool tsEnableAudit; +extern char tsAuditFqdn[]; +extern uint16_t tsAuditPort; + // telem extern bool tsEnableTelem; extern int32_t tsTelemInterval; diff --git a/include/common/tmsg.h b/include/common/tmsg.h index 8deec53470..42a0549024 100644 --- a/include/common/tmsg.h +++ b/include/common/tmsg.h @@ -767,6 +767,8 @@ typedef struct { char* pAst2; int64_t deleteMark1; int64_t deleteMark2; + int32_t sqlLen; + char* sql; } SMCreateStbReq; int32_t tSerializeSMCreateStbReq(void* buf, int32_t bufLen, SMCreateStbReq* pReq); @@ -787,6 +789,8 @@ typedef struct { int8_t source; // 1-taosX or 0-taosClient int8_t reserved[6]; tb_uid_t suid; + int32_t sqlLen; + char* sql; } SMDropStbReq; int32_t tSerializeSMDropStbReq(void* buf, int32_t bufLen, SMDropStbReq* pReq); @@ -800,6 +804,8 @@ typedef struct { int32_t ttl; int32_t commentLen; char* comment; + int32_t sqlLen; + char* sql; } SMAlterStbReq; int32_t tSerializeSMAlterStbReq(void* buf, int32_t bufLen, SMAlterStbReq* pReq); @@ -869,6 +875,8 @@ int32_t tDeserializeSCreateAcctReq(void* buf, int32_t bufLen, SCreateAcctReq* pR typedef struct { char user[TSDB_USER_LEN]; + int32_t sqlLen; + char *sql; } SDropUserReq, SDropAcctReq; int32_t tSerializeSDropUserReq(void* buf, int32_t bufLen, SDropUserReq* pReq); @@ -881,6 +889,8 @@ typedef struct { int8_t enable; char user[TSDB_USER_LEN]; char pass[TSDB_USET_PASSWORD_LEN]; + int32_t sqlLen; + char* sql; } SCreateUserReq; int32_t tSerializeSCreateUserReq(void* buf, int32_t bufLen, SCreateUserReq* pReq); @@ -897,6 +907,8 @@ typedef struct { char tabName[TSDB_TABLE_NAME_LEN]; char* tagCond; int32_t tagCondLen; + int32_t sqlLen; + char* sql; } SAlterUserReq; int32_t tSerializeSAlterUserReq(void* buf, int32_t bufLen, SAlterUserReq* pReq); @@ -1059,6 +1071,8 @@ typedef struct { int16_t hashPrefix; int16_t hashSuffix; int32_t tsdbPageSize; + int32_t sqlLen; + char* sql; } SCreateDbReq; int32_t tSerializeSCreateDbReq(void* buf, int32_t bufLen, SCreateDbReq* pReq); @@ -1084,6 +1098,8 @@ typedef struct { int32_t minRows; int32_t walRetentionPeriod; int32_t walRetentionSize; + int32_t sqlLen; + char* sql; } SAlterDbReq; int32_t tSerializeSAlterDbReq(void* buf, int32_t bufLen, SAlterDbReq* pReq); @@ -1092,6 +1108,8 @@ int32_t tDeserializeSAlterDbReq(void* buf, int32_t bufLen, SAlterDbReq* pReq); typedef struct { char db[TSDB_DB_FNAME_LEN]; int8_t ignoreNotExists; + int32_t sqlLen; + char* sql; } SDropDbReq; int32_t tSerializeSDropDbReq(void* buf, int32_t bufLen, SDropDbReq* pReq); @@ -1289,6 +1307,8 @@ void tFreeSUserAuthBatchRsp(SUserAuthBatchRsp* pRsp); typedef struct { char db[TSDB_DB_FNAME_LEN]; STimeWindow timeRange; + int32_t sqlLen; + char* sql; } SCompactDbReq; int32_t tSerializeSCompactDbReq(void* buf, int32_t bufLen, SCompactDbReq* pReq); @@ -1852,6 +1872,8 @@ void tFreeSExplainRsp(SExplainRsp* pRsp); typedef struct { char fqdn[TSDB_FQDN_LEN]; // end point, hostname:port int32_t port; + int32_t sqlLen; + char* sql; } SCreateDnodeReq; int32_t tSerializeSCreateDnodeReq(void* buf, int32_t bufLen, SCreateDnodeReq* pReq); @@ -1863,6 +1885,8 @@ typedef struct { int32_t port; int8_t force; int8_t unsafe; + int32_t sqlLen; + char* sql; } SDropDnodeReq; int32_t tSerializeSDropDnodeReq(void* buf, int32_t bufLen, SDropDnodeReq* pReq); @@ -1878,6 +1902,8 @@ enum { typedef struct { int32_t dnodeId; int8_t restoreType; + int32_t sqlLen; + char* sql; } SRestoreDnodeReq; int32_t tSerializeSRestoreDnodeReq(void* buf, int32_t bufLen, SRestoreDnodeReq* pReq); @@ -1887,6 +1913,8 @@ typedef struct { int32_t dnodeId; char config[TSDB_DNODE_CONFIG_LEN]; char value[TSDB_DNODE_VALUE_LEN]; + int32_t sqlLen; + char* sql; } SMCfgDnodeReq; int32_t tSerializeSMCfgDnodeReq(void* buf, int32_t bufLen, SMCfgDnodeReq* pReq); @@ -1902,6 +1930,8 @@ int32_t tDeserializeSDCfgDnodeReq(void* buf, int32_t bufLen, SDCfgDnodeReq* pReq typedef struct { int32_t dnodeId; + int32_t sqlLen; + char *sql; } SMCreateMnodeReq, SMDropMnodeReq, SDDropMnodeReq, SMCreateQnodeReq, SMDropQnodeReq, SDCreateQnodeReq, SDDropQnodeReq, SMCreateSnodeReq, SMDropSnodeReq, SDCreateSnodeReq, SDDropSnodeReq; @@ -1942,6 +1972,8 @@ int32_t tDeserializeSKillTransReq(void* buf, int32_t bufLen, SKillTransReq* pReq typedef struct { int32_t useless; // useless + int32_t sqlLen; + char* sql; } SBalanceVgroupReq; int32_t tSerializeSBalanceVgroupReq(void* buf, int32_t bufLen, SBalanceVgroupReq* pReq); @@ -1960,6 +1992,8 @@ typedef struct { int32_t dnodeId1; int32_t dnodeId2; int32_t dnodeId3; + int32_t sqlLen; + char* sql; } SRedistributeVgroupReq; int32_t tSerializeSRedistributeVgroupReq(void* buf, int32_t bufLen, SRedistributeVgroupReq* pReq); @@ -1967,6 +2001,8 @@ int32_t tDeserializeSRedistributeVgroupReq(void* buf, int32_t bufLen, SRedistrib typedef struct { int32_t useless; + int32_t sqlLen; + char* sql; } SBalanceVgroupLeaderReq; int32_t tSerializeSBalanceVgroupLeaderReq(void* buf, int32_t bufLen, SBalanceVgroupLeaderReq* pReq); @@ -2226,6 +2262,7 @@ typedef struct { int64_t deleteMark; int8_t igUpdate; int64_t lastTs; + int32_t sqlLen; } SCMCreateStreamReq; typedef struct { @@ -2262,6 +2299,7 @@ typedef struct { char subDbName[TSDB_DB_FNAME_LEN]; char* ast; char subStbName[TSDB_TABLE_FNAME_LEN]; + int32_t sqlLen; } SCMCreateTopicReq; int32_t tSerializeSCMCreateTopicReq(void* buf, int32_t bufLen, const SCMCreateTopicReq* pReq); @@ -2446,6 +2484,8 @@ typedef struct { typedef struct { char name[TSDB_TOPIC_FNAME_LEN]; int8_t igNotExists; + int32_t sqlLen; + char* sql; } SMDropTopicReq; int32_t tSerializeSMDropTopicReq(void* buf, int32_t bufLen, SMDropTopicReq* pReq); @@ -2545,6 +2585,8 @@ typedef struct SVCreateTbReq { SSchemaWrapper schemaRow; } ntb; }; + int32_t sqlLen; + char* sql; } SVCreateTbReq; int tEncodeSVCreateTbReq(SEncoder* pCoder, const SVCreateTbReq* pReq); @@ -3019,6 +3061,8 @@ typedef struct { typedef struct { char name[TSDB_STREAM_FNAME_LEN]; int8_t igNotExists; + int32_t sqlLen; + char* sql; } SMDropStreamReq; typedef struct { diff --git a/include/libs/audit/audit.h b/include/libs/audit/audit.h new file mode 100644 index 0000000000..1381b6e4a2 --- /dev/null +++ b/include/libs/audit/audit.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 TAOS Data, Inc. + * + * This program is free software: you can use, redistribute, and/or modify + * it under the terms of the GNU Affero General Public License, version 3 + * or later ("AGPL"), as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#ifndef _TD_AUDIT_H_ +#define _TD_AUDIT_H_ + +#include "tarray.h" +#include "tdef.h" +#include "tlog.h" +#include "tmsg.h" +#include "tjson.h" +#include "tmsgcb.h" +#include "trpc.h" +#include "mnode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + const char *server; + uint16_t port; + bool comp; +} SAuditCfg; + +int32_t auditInit(const SAuditCfg *pCfg); +void auditSend(SJson *pJson); +void auditRecord(SRpcMsg *pReq, int64_t clusterId, char *operation, char *target1, char *target2, char *detail); + +#ifdef __cplusplus +} +#endif + +#endif /*_TD_MONITOR_H_*/ diff --git a/source/common/src/tglobal.c b/source/common/src/tglobal.c index 86a165f14a..e2074bd877 100644 --- a/source/common/src/tglobal.c +++ b/source/common/src/tglobal.c @@ -95,6 +95,11 @@ uint16_t tsMonitorPort = 6043; int32_t tsMonitorMaxLogs = 100; bool tsMonitorComp = false; +// audit +bool tsEnableAudit = false; +char tsAuditFqdn[TSDB_FQDN_LEN] = {0}; +uint16_t tsAuditPort = 6043; + // telem bool tsEnableTelem = true; int32_t tsTelemInterval = 43200; @@ -600,6 +605,10 @@ static int32_t taosAddServerCfg(SConfig *pCfg) { if (cfgAddInt32(pCfg, "monitorMaxLogs", tsMonitorMaxLogs, 1, 1000000, CFG_SCOPE_SERVER) != 0) return -1; if (cfgAddBool(pCfg, "monitorComp", tsMonitorComp, CFG_SCOPE_SERVER) != 0) return -1; + if (cfgAddBool(pCfg, "audit", tsEnableAudit, CFG_SCOPE_SERVER) != 0) return -1; + if (cfgAddString(pCfg, "auditFqdn", tsAuditFqdn, CFG_SCOPE_SERVER) != 0) return -1; + if (cfgAddInt32(pCfg, "auditPort", tsAuditPort, 1, 65056, CFG_SCOPE_SERVER) != 0) return -1; + if (cfgAddBool(pCfg, "crashReporting", tsEnableCrashReport, CFG_SCOPE_BOTH) != 0) return -1; if (cfgAddBool(pCfg, "telemetryReporting", tsEnableTelem, CFG_SCOPE_BOTH) != 0) return -1; if (cfgAddInt32(pCfg, "telemetryInterval", tsTelemInterval, 1, 200000, CFG_SCOPE_BOTH) != 0) return -1; @@ -1001,6 +1010,10 @@ static int32_t taosSetServerCfg(SConfig *pCfg) { tsMonitorComp = cfgGetItem(pCfg, "monitorComp")->bval; tsQueryRspPolicy = cfgGetItem(pCfg, "queryRspPolicy")->i32; + tsEnableAudit = cfgGetItem(pCfg, "audit")->bval; + tstrncpy(tsAuditFqdn, cfgGetItem(pCfg, "auditFqdn")->str, TSDB_FQDN_LEN); + tsAuditPort = (uint16_t)cfgGetItem(pCfg, "auditPort")->i32; + tsEnableTelem = cfgGetItem(pCfg, "telemetryReporting")->bval; tsEnableCrashReport = cfgGetItem(pCfg, "crashReporting")->bval; tsTtlChangeOnWrite = cfgGetItem(pCfg, "ttlChangeOnWrite")->bval; diff --git a/source/dnode/mgmt/node_mgmt/src/dmEnv.c b/source/dnode/mgmt/node_mgmt/src/dmEnv.c index a34002161d..320c9db37d 100644 --- a/source/dnode/mgmt/node_mgmt/src/dmEnv.c +++ b/source/dnode/mgmt/node_mgmt/src/dmEnv.c @@ -15,6 +15,7 @@ #define _DEFAULT_SOURCE #include "dmMgmt.h" +#include "audit.h" #define STR_CASE_CMP(s, d) (0 == strcasecmp((s), (d))) #define STR_STR_CMP(s, d) (strstr((s), (d))) @@ -34,6 +35,16 @@ } \ } while (0) +#define DM_INIT_AUDIT() \ + do { \ + auditCfg.port = tsMonitorPort; \ + auditCfg.server = tsMonitorFqdn; \ + auditCfg.comp = tsMonitorComp; \ + if (auditInit(&auditCfg) != 0) { \ + return -1; \ + } \ + } while (0) + #define DM_ERR_RTN(c) \ do { \ code = (c); \ @@ -96,6 +107,14 @@ _exit: return code; } +static int32_t dmInitAudit() { + SAuditCfg auditCfg = {0}; + + DM_INIT_AUDIT(); + + return 0; +} + static bool dmDataSpaceAvailable() { SDnode *pDnode = dmInstance(); if (pDnode->pTfs) { @@ -176,6 +195,7 @@ int32_t dmInit() { if (dmCheckRepeatInit(dmInstance()) != 0) return -1; if (dmInitSystem() != 0) return -1; if (dmInitMonitor() != 0) return -1; + if (dmInitAudit() != 0) return -1; if (dmInitDnode(dmInstance()) != 0) return -1; dInfo("dnode env is initialized"); diff --git a/source/dnode/mnode/impl/CMakeLists.txt b/source/dnode/mnode/impl/CMakeLists.txt index 010067e99f..48dc71a12b 100644 --- a/source/dnode/mnode/impl/CMakeLists.txt +++ b/source/dnode/mnode/impl/CMakeLists.txt @@ -16,7 +16,7 @@ target_include_directories( PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/inc" ) target_link_libraries( - mnode scheduler sdb wal transport cjson sync monitor executor qworker stream parser + mnode scheduler sdb wal transport cjson sync monitor executor qworker stream parser audit ) IF (TD_GRANT) diff --git a/source/dnode/mnode/impl/src/mndDb.c b/source/dnode/mnode/impl/src/mndDb.c index b0e3dc4331..c58df5c88c 100644 --- a/source/dnode/mnode/impl/src/mndDb.c +++ b/source/dnode/mnode/impl/src/mndDb.c @@ -29,6 +29,9 @@ #include "mndUser.h" #include "mndVgroup.h" #include "systable.h" +#include "tjson.h" +#include "thttp.h" +#include "audit.h" #define DB_VER_NUMBER 1 #define DB_RESERVE_SIZE 46 @@ -733,6 +736,8 @@ static int32_t mndProcessCreateDbReq(SRpcMsg *pReq) { code = mndCreateDb(pMnode, pReq, &createReq, pUser); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + auditRecord(pReq, pMnode->clusterId, "createDB", createReq.db, "", ""); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("db:%s, failed to create since %s", createReq.db, terrstr()); @@ -975,6 +980,8 @@ static int32_t mndProcessAlterDbReq(SRpcMsg *pReq) { if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; } + auditRecord(pReq, pMnode->clusterId, "alterDB", alterReq.db, "", ""); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { if (terrno != 0) code = terrno; @@ -1264,6 +1271,8 @@ static int32_t mndProcessDropDbReq(SRpcMsg *pReq) { code = TSDB_CODE_ACTION_IN_PROGRESS; } + auditRecord(pReq, pMnode->clusterId, "dropDB", dropReq.db, "", ""); + _OVER: if (code != TSDB_CODE_SUCCESS && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("db:%s, failed to drop since %s", dropReq.db, terrstr()); diff --git a/source/dnode/mnode/impl/src/mndDnode.c b/source/dnode/mnode/impl/src/mndDnode.c index 917c6a00bc..476d4a4c6e 100644 --- a/source/dnode/mnode/impl/src/mndDnode.c +++ b/source/dnode/mnode/impl/src/mndDnode.c @@ -26,6 +26,7 @@ #include "mndVgroup.h" #include "tmisce.h" #include "mndCluster.h" +#include "audit.h" #define TSDB_DNODE_VER_NUMBER 2 #define TSDB_DNODE_RESERVE_SIZE 64 @@ -907,6 +908,13 @@ static int32_t mndProcessCreateDnodeReq(SRpcMsg *pReq) { code = mndCreateDnode(pMnode, pReq, &createReq); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; tsGrantHBInterval = 5; + + char detail[1000] = {0}; + sprintf(detail, "%s:%d", + createReq.fqdn, createReq.port); + + auditRecord(pReq, pMnode->clusterId, "createDnode", detail, "", ""); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("dnode:%s:%d, failed to create since %s", createReq.fqdn, createReq.port, terrstr()); @@ -1054,6 +1062,14 @@ static int32_t mndProcessDropDnodeReq(SRpcMsg *pReq) { code = mndDropDnode(pMnode, pReq, pDnode, pMObj, pQObj, pSObj, numOfVnodes, force, dropReq.unsafe); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + char obj1[150] = {0}; + sprintf(obj1, "%s:%d", dropReq.fqdn, dropReq.port); + + char obj2[10] = {0}; + sprintf(obj2, "%d", dropReq.dnodeId); + + auditRecord(pReq, pMnode->clusterId, "dropDnode", obj1, obj2, ""); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("dnode:%d, failed to drop since %s", dropReq.dnodeId, terrstr()); @@ -1217,6 +1233,11 @@ static int32_t mndProcessConfigDnodeReq(SRpcMsg *pReq) { } } + char detail[50] = {0}; + sprintf(detail, "%d", cfgReq.dnodeId); + + auditRecord(pReq, pMnode->clusterId, "alterDnode", detail, "", ""); + int32_t code = -1; SSdb *pSdb = pMnode->pSdb; void *pIter = NULL; diff --git a/source/dnode/mnode/impl/src/mndMnode.c b/source/dnode/mnode/impl/src/mndMnode.c index 2757578d35..8b9deb3988 100644 --- a/source/dnode/mnode/impl/src/mndMnode.c +++ b/source/dnode/mnode/impl/src/mndMnode.c @@ -22,6 +22,7 @@ #include "mndSync.h" #include "mndTrans.h" #include "tmisce.h" +#include "audit.h" #define MNODE_VER_NUMBER 2 #define MNODE_RESERVE_SIZE 64 @@ -652,6 +653,15 @@ static int32_t mndProcessCreateMnodeReq(SRpcMsg *pReq) { code = mndCreateMnode(pMnode, pReq, pDnode, &createReq); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + char detail[1000] = {0}; + + char obj[20] = {0}; + sprintf(obj, "%d", createReq.dnodeId); + + sprintf(detail, "dnodeId:%d", createReq.dnodeId); + + auditRecord(pReq, pMnode->clusterId, "createMnode", obj, detail, ""); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("mnode:%d, failed to create since %s", createReq.dnodeId, terrstr()); @@ -788,6 +798,11 @@ static int32_t mndProcessDropMnodeReq(SRpcMsg *pReq) { code = mndDropMnode(pMnode, pReq, pObj); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + char obj[20] = {0}; + sprintf(obj, "%d", dropReq.dnodeId); + + auditRecord(pReq, pMnode->clusterId, "dropMnode", obj, "", ""); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("mnode:%d, failed to drop since %s", dropReq.dnodeId, terrstr()); diff --git a/source/dnode/mnode/impl/src/mndProfile.c b/source/dnode/mnode/impl/src/mndProfile.c index 524ea1a06b..db1546e33f 100644 --- a/source/dnode/mnode/impl/src/mndProfile.c +++ b/source/dnode/mnode/impl/src/mndProfile.c @@ -25,6 +25,7 @@ #include "mndUser.h" #include "tglobal.h" #include "tversion.h" +#include "audit.h" typedef struct { uint32_t id; @@ -308,6 +309,16 @@ _CONNECT: code = 0; + char detail[1000] = {0}; + + char obj[30] = {0}; + sprintf(obj, "%s:%d", ip, pConn->port); + + sprintf(detail, "user:%s, from:%s, connType%d", + connReq.user, obj, connReq.connType); + + auditRecord(pReq, pMnode->clusterId, "login", connReq.app, obj, detail); + _OVER: mndReleaseUser(pMnode, pUser); diff --git a/source/dnode/mnode/impl/src/mndQnode.c b/source/dnode/mnode/impl/src/mndQnode.c index 5ec81440bb..45efabe97d 100644 --- a/source/dnode/mnode/impl/src/mndQnode.c +++ b/source/dnode/mnode/impl/src/mndQnode.c @@ -20,6 +20,7 @@ #include "mndShow.h" #include "mndTrans.h" #include "mndUser.h" +#include "audit.h" #define QNODE_VER_NUMBER 1 #define QNODE_RESERVE_SIZE 64 @@ -306,6 +307,10 @@ static int32_t mndProcessCreateQnodeReq(SRpcMsg *pReq) { code = mndCreateQnode(pMnode, pReq, pDnode, &createReq); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + char obj[33] = {0}; + sprintf(obj, "%d", createReq.dnodeId); + + auditRecord(pReq, pMnode->clusterId, "createQnode", obj, "", ""); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("qnode:%d, failed to create since %s", createReq.dnodeId, terrstr()); @@ -415,6 +420,11 @@ static int32_t mndProcessDropQnodeReq(SRpcMsg *pReq) { code = mndDropQnode(pMnode, pReq, pObj); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + char obj[33] = {0}; + sprintf(obj, "%d", dropReq.dnodeId); + + auditRecord(pReq, pMnode->clusterId, "createQnode", obj, "", ""); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("qnode:%d, failed to drop since %s", dropReq.dnodeId, terrstr()); diff --git a/source/dnode/mnode/impl/src/mndStb.c b/source/dnode/mnode/impl/src/mndStb.c index f52ce6582b..3d4e6a9061 100644 --- a/source/dnode/mnode/impl/src/mndStb.c +++ b/source/dnode/mnode/impl/src/mndStb.c @@ -31,6 +31,7 @@ #include "mndUser.h" #include "mndVgroup.h" #include "tname.h" +#include "audit.h" #define STB_VER_NUMBER 1 #define STB_RESERVE_SIZE 64 @@ -1173,6 +1174,8 @@ static int32_t mndProcessCreateStbReq(SRpcMsg *pReq) { } if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + auditRecord(pReq, pMnode->clusterId, "createStb", pDb->name, createReq.name, ""); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("stb:%s, failed to create since %s", createReq.name, terrstr()); @@ -2241,6 +2244,8 @@ static int32_t mndProcessAlterStbReq(SRpcMsg *pReq) { code = mndAlterStb(pMnode, pReq, &alterReq, pDb, pStb); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + auditRecord(pReq, pMnode->clusterId, "alterStb", pDb->name, alterReq.name, ""); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("stb:%s, failed to alter since %s", alterReq.name, terrstr()); @@ -2502,6 +2507,8 @@ static int32_t mndProcessDropStbReq(SRpcMsg *pReq) { code = mndDropStb(pMnode, pReq, pDb, pStb); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + auditRecord(pReq, pMnode->clusterId, "dropStb", pDb->name, dropReq.name, ""); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("stb:%s, failed to drop since %s", dropReq.name, terrstr()); diff --git a/source/dnode/mnode/impl/src/mndStream.c b/source/dnode/mnode/impl/src/mndStream.c index 427a52af3b..063a5bdce1 100644 --- a/source/dnode/mnode/impl/src/mndStream.c +++ b/source/dnode/mnode/impl/src/mndStream.c @@ -27,6 +27,7 @@ #include "mndVgroup.h" #include "parser.h" #include "tname.h" +#include "audit.h" #define MND_STREAM_VER_NUMBER 3 #define MND_STREAM_RESERVE_SIZE 64 @@ -828,6 +829,8 @@ static int32_t mndProcessCreateStreamReq(SRpcMsg *pReq) { code = TSDB_CODE_ACTION_IN_PROGRESS; + auditRecord(pReq, pMnode->clusterId, "createStream", createStreamReq.name, "", ""); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("stream:%s, failed to create since %s", createStreamReq.name, terrstr()); @@ -1073,6 +1076,8 @@ static int32_t mndProcessDropStreamReq(SRpcMsg *pReq) { return -1; } + auditRecord(pReq, pMnode->clusterId, "dropStream", dropReq.name, "", ""); + sdbRelease(pMnode->pSdb, pStream); mndTransDrop(pTrans); diff --git a/source/dnode/mnode/impl/src/mndTopic.c b/source/dnode/mnode/impl/src/mndTopic.c index 621a80338d..b7089053a8 100644 --- a/source/dnode/mnode/impl/src/mndTopic.c +++ b/source/dnode/mnode/impl/src/mndTopic.c @@ -27,6 +27,7 @@ #include "mndVgroup.h" #include "parser.h" #include "tname.h" +#include "audit.h" #define MND_TOPIC_VER_NUMBER 3 #define MND_TOPIC_RESERVE_SIZE 64 @@ -621,6 +622,8 @@ static int32_t mndProcessCreateTopicReq(SRpcMsg *pReq) { code = TSDB_CODE_ACTION_IN_PROGRESS; } + auditRecord(pReq, pMnode->clusterId, "crateTopic", createTopicReq.name, createTopicReq.subDbName, createTopicReq.sql); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("failed to create topic:%s since %s", createTopicReq.name, terrstr()); @@ -812,6 +815,8 @@ static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { return -1; } + auditRecord(pReq, pMnode->clusterId, "dropTopic", dropReq.name, "", dropReq.sql); + return TSDB_CODE_ACTION_IN_PROGRESS; } diff --git a/source/dnode/mnode/impl/src/mndUser.c b/source/dnode/mnode/impl/src/mndUser.c index c59d23d252..8afc73bef6 100644 --- a/source/dnode/mnode/impl/src/mndUser.c +++ b/source/dnode/mnode/impl/src/mndUser.c @@ -22,6 +22,7 @@ #include "mndTopic.h" #include "mndTrans.h" #include "tbase64.h" +#include "audit.h" #define USER_VER_NUMBER 4 #define USER_RESERVE_SIZE 64 @@ -655,6 +656,8 @@ static int32_t mndProcessCreateUserReq(SRpcMsg *pReq) { code = mndCreateUser(pMnode, pOperUser->acct, &createReq, pReq); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + auditRecord(pReq, pMnode->clusterId, "createUser", createReq.user, "", ""); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("user:%s, failed to create since %s", createReq.user, terrstr()); @@ -970,6 +973,27 @@ static int32_t mndProcessAlterUserReq(SRpcMsg *pReq) { code = mndAlterUser(pMnode, pUser, &newUser, pReq); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + if(alterReq.alterType == TSDB_ALTER_USER_PASSWD){ + auditRecord(pReq, pMnode->clusterId, "changePassword", alterReq.user, alterReq.objname, ""); + } + else if(alterReq.alterType == TSDB_ALTER_USER_SUPERUSER || + alterReq.alterType == TSDB_ALTER_USER_ENABLE || + alterReq.alterType == TSDB_ALTER_USER_SYSINFO){ + auditRecord(pReq, pMnode->clusterId, "alterUser", alterReq.user, alterReq.objname, ""); + } + else if(alterReq.alterType == TSDB_ALTER_USER_ADD_READ_DB|| + alterReq.alterType == TSDB_ALTER_USER_ADD_WRITE_DB|| + alterReq.alterType == TSDB_ALTER_USER_ADD_ALL_DB|| + alterReq.alterType == TSDB_ALTER_USER_ADD_SUBSCRIBE_TOPIC|| + alterReq.alterType == TSDB_ALTER_USER_ADD_READ_TABLE|| + alterReq.alterType == TSDB_ALTER_USER_ADD_WRITE_TABLE|| + alterReq.alterType == TSDB_ALTER_USER_ADD_ALL_TABLE){ + auditRecord(pReq, pMnode->clusterId, "GrantPrivileges", alterReq.user, alterReq.objname, ""); + } + else{ + auditRecord(pReq, pMnode->clusterId, "RevokePrivileges", alterReq.user, alterReq.objname, ""); + } + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("user:%s, failed to alter since %s", alterReq.user, terrstr()); @@ -1039,6 +1063,8 @@ static int32_t mndProcessDropUserReq(SRpcMsg *pReq) { code = mndDropUser(pMnode, pReq, pUser); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + auditRecord(pReq, pMnode->clusterId, "dropUser", dropReq.user, "", dropReq.sql); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("user:%s, failed to drop since %s", dropReq.user, terrstr()); diff --git a/source/dnode/mnode/impl/src/mndVgroup.c b/source/dnode/mnode/impl/src/mndVgroup.c index 26dc2a3f87..ff621198ff 100644 --- a/source/dnode/mnode/impl/src/mndVgroup.c +++ b/source/dnode/mnode/impl/src/mndVgroup.c @@ -26,6 +26,7 @@ #include "mndTrans.h" #include "mndUser.h" #include "tmisce.h" +#include "audit.h" #define VGROUP_VER_NUMBER 1 #define VGROUP_RESERVE_SIZE 64 @@ -2171,6 +2172,11 @@ static int32_t mndProcessRedistributeVgroupMsg(SRpcMsg *pReq) { if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + char obj[33] = {0}; + sprintf(obj, "%d", req.vgId); + + auditRecord(pReq, pMnode->clusterId, "RedistributeVgroup", obj, "", req.sql); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("vgId:%d, failed to redistribute to dnode %d:%d:%d since %s", req.vgId, req.dnodeId1, req.dnodeId2, @@ -2981,6 +2987,8 @@ static int32_t mndProcessBalanceVgroupMsg(SRpcMsg *pReq) { code = mndBalanceVgroup(pMnode, pReq, pArray); } + auditRecord(pReq, pMnode->clusterId, "balanceVgroup", "", "", req.sql); + _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { mError("failed to balance vgroup since %s", terrstr()); diff --git a/source/dnode/vnode/CMakeLists.txt b/source/dnode/vnode/CMakeLists.txt index c70df86e20..c2b41392e8 100644 --- a/source/dnode/vnode/CMakeLists.txt +++ b/source/dnode/vnode/CMakeLists.txt @@ -147,6 +147,7 @@ target_link_libraries( PUBLIC executor PUBLIC scheduler PUBLIC tdb + PUBLIC audit # PUBLIC bdb # PUBLIC scalar diff --git a/source/dnode/vnode/src/vnd/vnodeSvr.c b/source/dnode/vnode/src/vnd/vnodeSvr.c index 737fd03d6f..3687756ffc 100644 --- a/source/dnode/vnode/src/vnd/vnodeSvr.c +++ b/source/dnode/vnode/src/vnd/vnodeSvr.c @@ -19,6 +19,7 @@ #include "vndCos.h" #include "vnode.h" #include "vnodeInt.h" +#include "audit.h" static int32_t vnodeProcessCreateStbReq(SVnode *pVnode, int64_t ver, void *pReq, int32_t len, SRpcMsg *pRsp); static int32_t vnodeProcessAlterStbReq(SVnode *pVnode, int64_t ver, void *pReq, int32_t len, SRpcMsg *pRsp); @@ -930,6 +931,10 @@ static int32_t vnodeProcessCreateTbReq(SVnode *pVnode, int64_t ver, void *pReq, } taosArrayPush(rsp.pArray, &cRsp); + + int32_t clusterId = pVnode->config.syncCfg.nodeInfo[0].clusterId; + + auditRecord(pReq, clusterId, "createTable", pVnode->config.dbname, pCreateReq->name, ""); } vDebug("vgId:%d, add %d new created tables into query table list", TD_VID(pVnode), (int32_t)taosArrayGetSize(tbUids)); diff --git a/source/libs/CMakeLists.txt b/source/libs/CMakeLists.txt index 4a95629d59..9f812517c1 100644 --- a/source/libs/CMakeLists.txt +++ b/source/libs/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(sync) add_subdirectory(qcom) add_subdirectory(nodes) add_subdirectory(catalog) +add_subdirectory(audit) add_subdirectory(scalar) add_subdirectory(function) diff --git a/source/libs/audit/CMakeLists.txt b/source/libs/audit/CMakeLists.txt new file mode 100644 index 0000000000..2a04f084f1 --- /dev/null +++ b/source/libs/audit/CMakeLists.txt @@ -0,0 +1,13 @@ +aux_source_directory(src AUDIT_SRC) +IF (TD_ENTERPRISE) + LIST(APPEND AUDIT_SRC ${TD_ENTERPRISE_DIR}/src/plugins/audit/src/audit.c) +ENDIF () + +add_library(audit STATIC ${AUDIT_SRC}) +target_include_directories( + audit + PUBLIC "${TD_SOURCE_DIR}/include/libs/audit" + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/inc" +) + +target_link_libraries(audit os util common transport mnode) diff --git a/source/libs/audit/inc/auditInt.h b/source/libs/audit/inc/auditInt.h new file mode 100644 index 0000000000..b6c6ec87e8 --- /dev/null +++ b/source/libs/audit/inc/auditInt.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2019 TAOS Data, Inc. + * + * This program is free software: you can use, redistribute, and/or modify + * it under the terms of the GNU Affero General Public License, version 3 + * or later ("AGPL"), as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#ifndef _TD_AUDIT_INT_H_ +#define _TD_AUDIT_INT_H_ + +#include "audit.h" + +typedef struct { + SAuditCfg cfg; +} SAudit; + +#endif /*_TD_AUDIT_INT_H_*/ diff --git a/source/libs/audit/src/auditMain.c b/source/libs/audit/src/auditMain.c new file mode 100644 index 0000000000..d4b6465ac7 --- /dev/null +++ b/source/libs/audit/src/auditMain.c @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 TAOS Data, Inc. + * + * This program is free software: you can use, redistribute, and/or modify + * it under the terms of the GNU Affero General Public License, version 3 + * or later ("AGPL"), as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#define _DEFAULT_SOURCE +#include "auditInt.h" +#include "taoserror.h" +#include "thttp.h" +#include "ttime.h" +#include "tjson.h" +#include "tglobal.h" +#include "mnode.h" + +SAudit tsAudit = {0}; +char* tsAuditUri = "/audit"; + +int32_t auditInit(const SAuditCfg *pCfg) { + tsAudit.cfg = *pCfg; + return 0; +} + +extern void auditRecordImp(SRpcMsg *pReq, int64_t clusterId, char *operation, char *target1, char *target2, char *detail); + +void auditRecord(SRpcMsg *pReq, int64_t clusterId, char *operation, char *target1, char *target2, char *detail) { + auditRecordImp(pReq, clusterId, operation, target1, target2, detail); +} + +#ifndef TD_ENTERPRISE +void auditRecordImp(SRpcMsg *pReq, int64_t clusterId, char *operation, char *target1, char *target2, char *detail) { +} +#endif + From ec6ccc45661775322bd9e8ef4fed1a1906c23c96 Mon Sep 17 00:00:00 2001 From: dmchen Date: Mon, 28 Aug 2023 09:30:54 +0800 Subject: [PATCH 074/120] ci merge --- source/libs/audit/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/libs/audit/CMakeLists.txt b/source/libs/audit/CMakeLists.txt index 2a04f084f1..f04fba304c 100644 --- a/source/libs/audit/CMakeLists.txt +++ b/source/libs/audit/CMakeLists.txt @@ -1,7 +1,7 @@ aux_source_directory(src AUDIT_SRC) -IF (TD_ENTERPRISE) - LIST(APPEND AUDIT_SRC ${TD_ENTERPRISE_DIR}/src/plugins/audit/src/audit.c) -ENDIF () +#IF (TD_ENTERPRISE) +# LIST(APPEND AUDIT_SRC ${TD_ENTERPRISE_DIR}/src/plugins/audit/src/audit.c) +#ENDIF () add_library(audit STATIC ${AUDIT_SRC}) target_include_directories( From c60ac2b8ae62f0a33bf1e71536ed0045d78f5e63 Mon Sep 17 00:00:00 2001 From: Ganlin Zhao Date: Mon, 28 Aug 2023 09:45:50 +0800 Subject: [PATCH 075/120] fix --- source/os/src/osDir.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/source/os/src/osDir.c b/source/os/src/osDir.c index d0fb7ee919..df4d03c118 100644 --- a/source/os/src/osDir.c +++ b/source/os/src/osDir.c @@ -207,7 +207,7 @@ int32_t taosMulModeMkDir(const char *dirname, int mode, bool checkAccess) { if (taosDirExist(temp)) { if (checkAccess && taosCheckAccessFile(temp, TD_FILE_ACCESS_EXIST_OK | TD_FILE_ACCESS_READ_OK | TD_FILE_ACCESS_WRITE_OK)) { - return code; + return 0; } return chmod(temp, mode); } @@ -251,9 +251,15 @@ int32_t taosMulModeMkDir(const char *dirname, int mode, bool checkAccess) { } if (code < 0 && errno == EEXIST) { + if (checkAccess && taosCheckAccessFile(temp, TD_FILE_ACCESS_EXIST_OK | TD_FILE_ACCESS_READ_OK | TD_FILE_ACCESS_WRITE_OK)) { + return 0; + } return chmod(temp, mode); } + if (checkAccess && taosCheckAccessFile(temp, TD_FILE_ACCESS_EXIST_OK | TD_FILE_ACCESS_READ_OK | TD_FILE_ACCESS_WRITE_OK)) { + return 0; + } return chmod(temp, mode); } From a7bd7e0a12175da8ae0a1e15b07eb8235f9b1d29 Mon Sep 17 00:00:00 2001 From: dmchen Date: Mon, 28 Aug 2023 10:05:07 +0800 Subject: [PATCH 076/120] ci merge --- source/libs/audit/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/libs/audit/CMakeLists.txt b/source/libs/audit/CMakeLists.txt index f04fba304c..2a04f084f1 100644 --- a/source/libs/audit/CMakeLists.txt +++ b/source/libs/audit/CMakeLists.txt @@ -1,7 +1,7 @@ aux_source_directory(src AUDIT_SRC) -#IF (TD_ENTERPRISE) -# LIST(APPEND AUDIT_SRC ${TD_ENTERPRISE_DIR}/src/plugins/audit/src/audit.c) -#ENDIF () +IF (TD_ENTERPRISE) + LIST(APPEND AUDIT_SRC ${TD_ENTERPRISE_DIR}/src/plugins/audit/src/audit.c) +ENDIF () add_library(audit STATIC ${AUDIT_SRC}) target_include_directories( From 1319066e0e560c7f3527bef6739dc49e153e6dbd Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Mon, 28 Aug 2023 10:05:54 +0800 Subject: [PATCH 077/120] fix:memory leak --- source/libs/scalar/src/filter.c | 6 ++++-- utils/test/c/varbinary_test.c | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/source/libs/scalar/src/filter.c b/source/libs/scalar/src/filter.c index 6cc235bd06..12729c3262 100644 --- a/source/libs/scalar/src/filter.c +++ b/source/libs/scalar/src/filter.c @@ -1990,6 +1990,8 @@ int32_t fltInitValFieldData(SFilterInfo *info) { // todo refactor the convert int32_t code = sclConvertValueToSclParam(var, &out, NULL); if (code != TSDB_CODE_SUCCESS) { + colDataDestroy(out.columnData); + taosMemoryFree(out.columnData); qError("convert value to type[%d] failed", type); return code; } @@ -4678,10 +4680,10 @@ int32_t filterExecute(SFilterInfo *info, SSDataBlock *pSrc, SColumnInfoData **p, code = scalarCalculate(info->sclCtx.node, pList, &output); taosArrayDestroy(pList); - FLT_ERR_RET(code); - *p = output.columnData; + FLT_ERR_RET(code); + if (output.numOfQualified == output.numOfRows) { *pResultStatus = FILTER_RESULT_ALL_QUALIFIED; } else if (output.numOfQualified == 0) { diff --git a/utils/test/c/varbinary_test.c b/utils/test/c/varbinary_test.c index b2fccee63b..74c50d66d9 100644 --- a/utils/test/c/varbinary_test.c +++ b/utils/test/c/varbinary_test.c @@ -68,6 +68,7 @@ int varbinary_test() { } rowIndex++; } + taos_free_result(pRes); pRes = taos_query(taos, "insert into tb1 using stb tags (1, 'tb1_bin1', 'vart1') values (now, 'nchar1', 'varc1', 0.3)"); taos_free_result(pRes); From 2f46b2eefd35efa5a927acb4d742d7d2bda09e1a Mon Sep 17 00:00:00 2001 From: Ganlin Zhao Date: Mon, 28 Aug 2023 10:10:16 +0800 Subject: [PATCH 078/120] fix --- source/os/src/osDir.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/os/src/osDir.c b/source/os/src/osDir.c index df4d03c118..6e52c4ed27 100644 --- a/source/os/src/osDir.c +++ b/source/os/src/osDir.c @@ -257,9 +257,6 @@ int32_t taosMulModeMkDir(const char *dirname, int mode, bool checkAccess) { return chmod(temp, mode); } - if (checkAccess && taosCheckAccessFile(temp, TD_FILE_ACCESS_EXIST_OK | TD_FILE_ACCESS_READ_OK | TD_FILE_ACCESS_WRITE_OK)) { - return 0; - } return chmod(temp, mode); } From 193284d9baa033d528ea220c65733c33ffca4617 Mon Sep 17 00:00:00 2001 From: huolibo Date: Mon, 28 Aug 2023 10:11:47 +0800 Subject: [PATCH 079/120] release(driver): jdbc release 3.2.5 version --- docs/en/07-develop/07-tmq.mdx | 12 ++++++++++++ docs/en/14-reference/03-connector/04-java.mdx | 18 ++++++++++++++++++ docs/zh/07-develop/07-tmq.mdx | 12 ++++++++++++ docs/zh/08-connector/14-java.mdx | 18 ++++++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/docs/en/07-develop/07-tmq.mdx b/docs/en/07-develop/07-tmq.mdx index e78855dad3..98ba100862 100644 --- a/docs/en/07-develop/07-tmq.mdx +++ b/docs/en/07-develop/07-tmq.mdx @@ -120,7 +120,19 @@ Set subscription() throws SQLException; ConsumerRecords poll(Duration timeout) throws SQLException; +Set assignment() throws SQLException; +long position(TopicPartition partition) throws SQLException; +Map position(String topic) throws SQLException; +Map beginningOffsets(String topic) throws SQLException; +Map endOffsets(String topic) throws SQLException; +Map committed(Set partitions) throws SQLException; + +void seek(TopicPartition partition, long offset) throws SQLException; +void seekToBeginning(Collection partitions) throws SQLException; +void seekToEnd(Collection partitions) throws SQLException; + void commitSync() throws SQLException; +void commitSync(Map offsets) throws SQLException; void close() throws SQLException; ``` diff --git a/docs/en/14-reference/03-connector/04-java.mdx b/docs/en/14-reference/03-connector/04-java.mdx index ff1f209788..996ad39035 100644 --- a/docs/en/14-reference/03-connector/04-java.mdx +++ b/docs/en/14-reference/03-connector/04-java.mdx @@ -36,6 +36,7 @@ REST connection supports all platforms that can run Java. | taos-jdbcdriver version | major changes | TDengine version | | :---------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------: | +| 3.2.5 | Subscription add committed() and assignment() method | 3.1.0.3 or later | | 3.2.4 | Subscription add the enable.auto.commit parameter and the unsubscribe() method in the WebSocket connection | - | | 3.2.3 | Fixed resultSet data parsing failure in some cases | - | | 3.2.2 | Subscription add seek function | 3.0.5.0 or later | @@ -1019,14 +1020,19 @@ while(true) { #### Assignment subscription Offset ```java +// get topicPartition +Set assignment() throws SQLException; // get offset long position(TopicPartition partition) throws SQLException; Map position(String topic) throws SQLException; Map beginningOffsets(String topic) throws SQLException; Map endOffsets(String topic) throws SQLException; +Map committed(Set partitions) throws SQLException; // Overrides the fetch offsets that the consumer will use on the next poll(timeout). void seek(TopicPartition partition, long offset) throws SQLException; +void seekToBeginning(Collection partitions) throws SQLException; +void seekToEnd(Collection partitions) throws SQLException; ``` Example usage is as follows. @@ -1052,6 +1058,18 @@ try (TaosConsumer consumer = new TaosConsumer<>(properties)) { } ``` +#### Commit offset + +If `enable.auto.commit` is false, offset can be submitted manually. + +```java +void commitSync() throws SQLException; +void commitSync(Map offsets) throws SQLException; +// async commit only support jni connection +void commitAsync(OffsetCommitCallback callback) throws SQLException; +void commitAsync(Map offsets, OffsetCommitCallback callback) throws SQLException; +``` + #### Close subscriptions ```java diff --git a/docs/zh/07-develop/07-tmq.mdx b/docs/zh/07-develop/07-tmq.mdx index 04c978679e..d87eb89f0f 100644 --- a/docs/zh/07-develop/07-tmq.mdx +++ b/docs/zh/07-develop/07-tmq.mdx @@ -120,7 +120,19 @@ Set subscription() throws SQLException; ConsumerRecords poll(Duration timeout) throws SQLException; +Set assignment() throws SQLException; +long position(TopicPartition partition) throws SQLException; +Map position(String topic) throws SQLException; +Map beginningOffsets(String topic) throws SQLException; +Map endOffsets(String topic) throws SQLException; +Map committed(Set partitions) throws SQLException; + +void seek(TopicPartition partition, long offset) throws SQLException; +void seekToBeginning(Collection partitions) throws SQLException; +void seekToEnd(Collection partitions) throws SQLException; + void commitSync() throws SQLException; +void commitSync(Map offsets) throws SQLException; void close() throws SQLException; ``` diff --git a/docs/zh/08-connector/14-java.mdx b/docs/zh/08-connector/14-java.mdx index 36eacd26a4..0ff00d1710 100644 --- a/docs/zh/08-connector/14-java.mdx +++ b/docs/zh/08-connector/14-java.mdx @@ -36,6 +36,7 @@ REST 连接支持所有能运行 Java 的平台。 | taos-jdbcdriver 版本 | 主要变化 | TDengine 版本 | | :------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------: | +| 3.2.5 | 数据订阅增加 committed()、assignment() 方法 | 3.1.0.3 及更高版本 | | 3.2.4 | 数据订阅在 WebSocket 连接下增加 enable.auto.commit 参数,以及 unsubscribe() 方法。 | - | | 3.2.3 | 修复 ResultSet 在一些情况数据解析失败 | - | | 3.2.2 | 新增功能:数据订阅支持 seek 功能。 | 3.0.5.0 及更高版本 | @@ -1022,14 +1023,19 @@ while(true) { #### 指定订阅 Offset ```java +// 获取订阅的 topicPartition +Set assignment() throws SQLException; // 获取 offset long position(TopicPartition partition) throws SQLException; Map position(String topic) throws SQLException; Map beginningOffsets(String topic) throws SQLException; Map endOffsets(String topic) throws SQLException; +Map committed(Set partitions) throws SQLException; // 指定下一次 poll 中使用的 offset void seek(TopicPartition partition, long offset) throws SQLException; +void seekToBeginning(Collection partitions) throws SQLException; +void seekToEnd(Collection partitions) throws SQLException; ``` 示例代码: @@ -1055,6 +1061,18 @@ try (TaosConsumer consumer = new TaosConsumer<>(properties)) { } ``` +#### 提交 Offset + +当`enable.auto.commit`为 false 时,可以手动提交 offset。 + +```java +void commitSync() throws SQLException; +void commitSync(Map offsets) throws SQLException; +// 异步提交仅在 native 连接下有效 +void commitAsync(OffsetCommitCallback callback) throws SQLException; +void commitAsync(Map offsets, OffsetCommitCallback callback) throws SQLException; +``` + #### 关闭订阅 ```java From 09463cb43ee807d432972e0eab8af1f67d6b92c2 Mon Sep 17 00:00:00 2001 From: shenglian zhou Date: Mon, 28 Aug 2023 11:25:16 +0800 Subject: [PATCH 080/120] enhance: fix buffer size overflow --- source/libs/scalar/src/sclfunc.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/libs/scalar/src/sclfunc.c b/source/libs/scalar/src/sclfunc.c index b9af716929..7a19fda08e 100644 --- a/source/libs/scalar/src/sclfunc.c +++ b/source/libs/scalar/src/sclfunc.c @@ -654,8 +654,12 @@ int32_t substrFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOu SColumnInfoData *pInputData = pInput->columnData; SColumnInfoData *pOutputData = pOutput->columnData; - int32_t outputLen = pInputData->varmeta.length * pInput->numOfRows; + uint32_t outputLen = pInputData->varmeta.length; char *outputBuf = taosMemoryCalloc(outputLen, 1); + if (outputBuf == NULL) { + qError("memory allocation failure. size: %u", outputLen); + return TSDB_CODE_OUT_OF_MEMORY; + } char *output = outputBuf; for (int32_t i = 0; i < pInput->numOfRows; ++i) { From f299ae9bc373a0c0204f2ec8957c6c5f162ae081 Mon Sep 17 00:00:00 2001 From: Minglei Jin Date: Mon, 28 Aug 2023 12:22:53 +0800 Subject: [PATCH 081/120] fix(tsdb/s3): fix scan and fix with s3 files --- source/dnode/vnode/src/tsdb/tsdbFS2.c | 9 +++++++++ source/dnode/vnode/src/tsdb/tsdbReaderWriter.c | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/source/dnode/vnode/src/tsdb/tsdbFS2.c b/source/dnode/vnode/src/tsdb/tsdbFS2.c index 7f843070d6..6bdedebe35 100644 --- a/source/dnode/vnode/src/tsdb/tsdbFS2.c +++ b/source/dnode/vnode/src/tsdb/tsdbFS2.c @@ -16,6 +16,7 @@ #include "tsdbFS2.h" #include "tsdbUpgrade.h" #include "vnd.h" +#include "vndCos.h" extern int vnodeScheduleTask(int (*execute)(void *), void *arg); extern int vnodeScheduleTaskEx(int tpid, int (*execute)(void *), void *arg); @@ -365,6 +366,14 @@ static int32_t tsdbFSDoScanAndFixFile(STFileSystem *fs, const STFileObj *fobj) { // check file existence if (!taosCheckExistFile(fobj->fname)) { + if (tsS3Enabled) { + const char *object_name = taosDirEntryBaseName((char *)fobj->fname); + long s3_size = s3Size(object_name); + if (s3_size > 0) { + return 0; + } + } + code = TSDB_CODE_FILE_CORRUPTED; tsdbError("vgId:%d %s failed since file:%s does not exist", TD_VID(fs->tsdb->pVnode), __func__, fobj->fname); return code; diff --git a/source/dnode/vnode/src/tsdb/tsdbReaderWriter.c b/source/dnode/vnode/src/tsdb/tsdbReaderWriter.c index dcf3770cbd..974b7f1b76 100644 --- a/source/dnode/vnode/src/tsdb/tsdbReaderWriter.c +++ b/source/dnode/vnode/src/tsdb/tsdbReaderWriter.c @@ -26,8 +26,8 @@ static int32_t tsdbOpenFileImpl(STsdbFD *pFD) { if (pFD->pFD == NULL) { int errsv = errno; const char *object_name = taosDirEntryBaseName((char *)path); - long s3_size = s3Size(object_name); - if (!strncmp(path + strlen(path) - 5, ".data", 5) && s3_size > 0) { + long s3_size = tsS3Enabled ? s3Size(object_name) : 0; + if (tsS3Enabled && !strncmp(path + strlen(path) - 5, ".data", 5) && s3_size > 0) { s3EvictCache(path, s3_size); s3Get(object_name, path); From 322e8c66975201ea01de18dd0f0624eb43b89551 Mon Sep 17 00:00:00 2001 From: shenglian zhou Date: Mon, 28 Aug 2023 13:43:37 +0800 Subject: [PATCH 082/120] fix: use col cell size instead of total col data size --- source/libs/scalar/src/sclfunc.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/source/libs/scalar/src/sclfunc.c b/source/libs/scalar/src/sclfunc.c index 7a19fda08e..1b7559a207 100644 --- a/source/libs/scalar/src/sclfunc.c +++ b/source/libs/scalar/src/sclfunc.c @@ -654,13 +654,12 @@ int32_t substrFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOu SColumnInfoData *pInputData = pInput->columnData; SColumnInfoData *pOutputData = pOutput->columnData; - uint32_t outputLen = pInputData->varmeta.length; - char *outputBuf = taosMemoryCalloc(outputLen, 1); + int32_t outputLen = pInputData->info.bytes; + char *outputBuf = taosMemoryMalloc(outputLen); if (outputBuf == NULL) { - qError("memory allocation failure. size: %u", outputLen); + qError("substr function memory allocation failure. size: %d", outputLen); return TSDB_CODE_OUT_OF_MEMORY; } - char *output = outputBuf; for (int32_t i = 0; i < pInput->numOfRows; ++i) { if (colDataIsNull_s(pInputData, i)) { @@ -680,14 +679,16 @@ int32_t substrFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOu startPosBytes = TMAX(startPosBytes, 0); } + char *output = outputBuf; int32_t resLen = TMIN(subLen, len - startPosBytes); if (resLen > 0) { memcpy(varDataVal(output), varDataVal(input) + startPosBytes, resLen); + varDataSetLen(output, resLen); + } else { + varDataSetLen(output, 0); } - varDataSetLen(output, resLen); colDataSetVal(pOutputData, i, output, false); - output += varDataTLen(output); } pOutput->numOfRows = pInput->numOfRows; From c0002e55ad998537c53d1e364424f552f162d3be Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Mon, 28 Aug 2023 14:33:21 +0800 Subject: [PATCH 083/120] fix:sml error because of the config of smlDot2Underline --- utils/test/c/sml_test.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/utils/test/c/sml_test.c b/utils/test/c/sml_test.c index b1e504f463..64da4f83e3 100644 --- a/utils/test/c/sml_test.c +++ b/utils/test/c/sml_test.c @@ -1555,12 +1555,6 @@ int sml_ts3724_Test() { ASSERT(numRows == 1); taos_free_result(pRes); - pRes = taos_query(taos, "show stables"); - row = taos_fetch_row(pRes); - numRows = taos_affected_rows(pRes); - ASSERT(numRows == 3); - taos_free_result(pRes); - taos_close(taos); return code; @@ -1684,8 +1678,8 @@ int main(int argc, char *argv[]) { ASSERT(!ret); ret = sml_td18789_Test(); ASSERT(!ret); - ret = sml_td24070_Test(); - ASSERT(!ret); +// ret = sml_td24070_Test(); +// ASSERT(!ret); ret = sml_td23881_Test(); ASSERT(ret); ret = sml_escape_Test(); From f5256a4091d697b5cb5380398d769e3fe20bccd3 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Mon, 28 Aug 2023 14:35:01 +0800 Subject: [PATCH 084/120] fix:sml error because of the config of smlDot2Underline --- utils/test/c/sml_test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/test/c/sml_test.c b/utils/test/c/sml_test.c index 64da4f83e3..9153706d23 100644 --- a/utils/test/c/sml_test.c +++ b/utils/test/c/sml_test.c @@ -1678,8 +1678,8 @@ int main(int argc, char *argv[]) { ASSERT(!ret); ret = sml_td18789_Test(); ASSERT(!ret); -// ret = sml_td24070_Test(); -// ASSERT(!ret); + ret = sml_td24070_Test(); + ASSERT(!ret); ret = sml_td23881_Test(); ASSERT(ret); ret = sml_escape_Test(); From 271502d3c7d6e6392d230218926063f74063e2b7 Mon Sep 17 00:00:00 2001 From: shenglian zhou Date: Mon, 28 Aug 2023 15:08:55 +0800 Subject: [PATCH 085/120] fix: add test case --- tests/script/tsim/query/interval.sim | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/script/tsim/query/interval.sim b/tests/script/tsim/query/interval.sim index e2b0d219cb..5fc6a867d7 100644 --- a/tests/script/tsim/query/interval.sim +++ b/tests/script/tsim/query/interval.sim @@ -206,6 +206,19 @@ if $desc_rows != $asc_rows then return -1 endi +print ================= step11 + +sql create database if not exists test0828 +sql use test0828 +sql create stable st (ts timestamp, c2 int) tags(tg int) +sql insert into ct1 using st tags(1) values('2021-08-01', 0) +sql insert into ct2 using st tags(2) values('2022-08-01', 1) +sql select _wstart, _wend, count(*) from st where ts>='2021-01-01' and ts < now interval(1n) fill(value, 0) order by _wstart desc +print $rows +if $rows != 32 then + return -1 +endi +sql drop database test0828 print =============== clear #sql drop database $db #sql select * from information_schema.ins_databases From 5213fc3256e4b3fa992cb87c8394d59e2fc5f39a Mon Sep 17 00:00:00 2001 From: dapan1121 Date: Mon, 28 Aug 2023 15:35:36 +0800 Subject: [PATCH 086/120] fix: fix case issue --- tests/system-test/0-others/information_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system-test/0-others/information_schema.py b/tests/system-test/0-others/information_schema.py index 0239f41c0a..baf0682fbb 100644 --- a/tests/system-test/0-others/information_schema.py +++ b/tests/system-test/0-others/information_schema.py @@ -217,7 +217,7 @@ class TDTestCase: tdSql.checkEqual(20470,len(tdSql.queryResult)) tdSql.query("select * from information_schema.ins_columns where db_name ='information_schema'") - tdSql.checkEqual(195, len(tdSql.queryResult)) + tdSql.checkEqual(193, len(tdSql.queryResult)) tdSql.query("select * from information_schema.ins_columns where db_name ='performance_schema'") tdSql.checkEqual(54, len(tdSql.queryResult)) From 05bb1646759cc15d357b593d7a80d959ca0ec709 Mon Sep 17 00:00:00 2001 From: Haojun Liao Date: Mon, 28 Aug 2023 15:56:02 +0800 Subject: [PATCH 087/120] fix(stream): release tasks. --- source/dnode/vnode/src/tq/tq.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/dnode/vnode/src/tq/tq.c b/source/dnode/vnode/src/tq/tq.c index 3396803f08..c61fd78747 100644 --- a/source/dnode/vnode/src/tq/tq.c +++ b/source/dnode/vnode/src/tq/tq.c @@ -1196,6 +1196,7 @@ int32_t tqProcessTaskScanHistory(STQ* pTq, SRpcMsg* pMsg) { "s-task:%s failed to start scan-history in first stream time window since already started, unexpected " "sched-status:%d", id, schedStatus); + streamMetaReleaseTask(pMeta, pTask); return 0; } @@ -1209,6 +1210,7 @@ int32_t tqProcessTaskScanHistory(STQ* pTq, SRpcMsg* pMsg) { tqDebug("s-task:%s is paused in the step1, elapsed time:%.2fs, sched-status:%d", pTask->id.idStr, el, TASK_SCHED_STATUS__INACTIVE); atomic_store_8(&pTask->status.schedStatus, TASK_SCHED_STATUS__INACTIVE); + streamMetaReleaseTask(pMeta, pTask); return 0; } From 9ef455a454a219ccf6047b492c77d3f8209cb6d1 Mon Sep 17 00:00:00 2001 From: Minglei Jin Date: Mon, 28 Aug 2023 15:29:54 +0800 Subject: [PATCH 088/120] enh(s3/evict): evict cache data files for other migrated head, stt, etc. --- source/dnode/vnode/src/tsdb/tsdbRetention.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/source/dnode/vnode/src/tsdb/tsdbRetention.c b/source/dnode/vnode/src/tsdb/tsdbRetention.c index 267e8b4117..61be14f9bc 100644 --- a/source/dnode/vnode/src/tsdb/tsdbRetention.c +++ b/source/dnode/vnode/src/tsdb/tsdbRetention.c @@ -338,6 +338,17 @@ static int32_t tsdbDoRetention2(void *arg) { code = tsdbMigrateDataFileS3(rtner, fobj, &did); TSDB_CHECK_CODE(code, lino, _exit); } else { + if (tsS3Enabled) { + int64_t fsize = 0; + if (taosStatFile(fobj->fname, &fsize, NULL, NULL) < 0) { + code = TAOS_SYSTEM_ERROR(terrno); + tsdbError("vgId:%d %s failed since file:%s stat failed, reason:%s", TD_VID(rtner->tsdb->pVnode), __func__, + fobj->fname, tstrerror(code)); + TSDB_CHECK_CODE(code, lino, _exit); + } + s3EvictCache(fobj->fname, fsize * 2); + } + code = tsdbDoMigrateFileObj(rtner, fobj, &did); TSDB_CHECK_CODE(code, lino, _exit); } From 2e04d513a43f6a8ef0723a1e502f35502119ec3b Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Mon, 28 Aug 2023 16:41:28 +0800 Subject: [PATCH 089/120] fix:unregister push manager if delete subscribe to avoid tqProcessPollPush --- source/dnode/vnode/src/tq/tq.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/dnode/vnode/src/tq/tq.c b/source/dnode/vnode/src/tq/tq.c index 3396803f08..5a7cdb46ed 100644 --- a/source/dnode/vnode/src/tq/tq.c +++ b/source/dnode/vnode/src/tq/tq.c @@ -779,6 +779,8 @@ int32_t tqProcessDeleteSubReq(STQ* pTq, int64_t sversion, char* msg, int32_t msg walCloseRef(pTq->pVnode->pWal, pHandle->pRef->refId); } + tqUnregisterPushHandle(pTq, pHandle); + code = taosHashRemove(pTq->pHandle, pReq->subKey, strlen(pReq->subKey)); if (code != 0) { tqError("cannot process tq delete req %s, since no such handle", pReq->subKey); From d8ce7a14676c07c69ad5c2b94c8f6396c110ad2c Mon Sep 17 00:00:00 2001 From: shenglian-zhou Date: Mon, 28 Aug 2023 16:46:05 +0800 Subject: [PATCH 090/120] Update interval.sim to now use now --- tests/script/tsim/query/interval.sim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/script/tsim/query/interval.sim b/tests/script/tsim/query/interval.sim index 5fc6a867d7..135fcc8591 100644 --- a/tests/script/tsim/query/interval.sim +++ b/tests/script/tsim/query/interval.sim @@ -213,7 +213,7 @@ sql use test0828 sql create stable st (ts timestamp, c2 int) tags(tg int) sql insert into ct1 using st tags(1) values('2021-08-01', 0) sql insert into ct2 using st tags(2) values('2022-08-01', 1) -sql select _wstart, _wend, count(*) from st where ts>='2021-01-01' and ts < now interval(1n) fill(value, 0) order by _wstart desc +sql select _wstart, _wend, count(*) from st where ts>='2021-01-01' and ts < '2023-08-28' interval(1n) fill(value, 0) order by _wstart desc print $rows if $rows != 32 then return -1 From 1372cede489fc173ab2eab01c12ebc12ba13186d Mon Sep 17 00:00:00 2001 From: dmchen Date: Mon, 28 Aug 2023 17:44:10 +0800 Subject: [PATCH 091/120] init --- include/common/tmsg.h | 44 ------------------------ source/dnode/mnode/impl/src/mndDb.c | 35 +++++++++++++++++-- source/dnode/mnode/impl/src/mndDnode.c | 23 ++++++++----- source/dnode/mnode/impl/src/mndMnode.c | 10 ++---- source/dnode/mnode/impl/src/mndProfile.c | 11 +++--- source/dnode/mnode/impl/src/mndQnode.c | 2 +- source/dnode/mnode/impl/src/mndStb.c | 24 +++++++++++-- source/dnode/mnode/impl/src/mndStream.c | 19 ++++++++-- source/dnode/mnode/impl/src/mndTopic.c | 11 ++++-- source/dnode/mnode/impl/src/mndUser.c | 20 +++++++---- source/dnode/mnode/impl/src/mndVgroup.c | 8 +++-- source/dnode/vnode/src/vnd/vnodeSvr.c | 6 +++- 12 files changed, 127 insertions(+), 86 deletions(-) diff --git a/include/common/tmsg.h b/include/common/tmsg.h index 42a0549024..8deec53470 100644 --- a/include/common/tmsg.h +++ b/include/common/tmsg.h @@ -767,8 +767,6 @@ typedef struct { char* pAst2; int64_t deleteMark1; int64_t deleteMark2; - int32_t sqlLen; - char* sql; } SMCreateStbReq; int32_t tSerializeSMCreateStbReq(void* buf, int32_t bufLen, SMCreateStbReq* pReq); @@ -789,8 +787,6 @@ typedef struct { int8_t source; // 1-taosX or 0-taosClient int8_t reserved[6]; tb_uid_t suid; - int32_t sqlLen; - char* sql; } SMDropStbReq; int32_t tSerializeSMDropStbReq(void* buf, int32_t bufLen, SMDropStbReq* pReq); @@ -804,8 +800,6 @@ typedef struct { int32_t ttl; int32_t commentLen; char* comment; - int32_t sqlLen; - char* sql; } SMAlterStbReq; int32_t tSerializeSMAlterStbReq(void* buf, int32_t bufLen, SMAlterStbReq* pReq); @@ -875,8 +869,6 @@ int32_t tDeserializeSCreateAcctReq(void* buf, int32_t bufLen, SCreateAcctReq* pR typedef struct { char user[TSDB_USER_LEN]; - int32_t sqlLen; - char *sql; } SDropUserReq, SDropAcctReq; int32_t tSerializeSDropUserReq(void* buf, int32_t bufLen, SDropUserReq* pReq); @@ -889,8 +881,6 @@ typedef struct { int8_t enable; char user[TSDB_USER_LEN]; char pass[TSDB_USET_PASSWORD_LEN]; - int32_t sqlLen; - char* sql; } SCreateUserReq; int32_t tSerializeSCreateUserReq(void* buf, int32_t bufLen, SCreateUserReq* pReq); @@ -907,8 +897,6 @@ typedef struct { char tabName[TSDB_TABLE_NAME_LEN]; char* tagCond; int32_t tagCondLen; - int32_t sqlLen; - char* sql; } SAlterUserReq; int32_t tSerializeSAlterUserReq(void* buf, int32_t bufLen, SAlterUserReq* pReq); @@ -1071,8 +1059,6 @@ typedef struct { int16_t hashPrefix; int16_t hashSuffix; int32_t tsdbPageSize; - int32_t sqlLen; - char* sql; } SCreateDbReq; int32_t tSerializeSCreateDbReq(void* buf, int32_t bufLen, SCreateDbReq* pReq); @@ -1098,8 +1084,6 @@ typedef struct { int32_t minRows; int32_t walRetentionPeriod; int32_t walRetentionSize; - int32_t sqlLen; - char* sql; } SAlterDbReq; int32_t tSerializeSAlterDbReq(void* buf, int32_t bufLen, SAlterDbReq* pReq); @@ -1108,8 +1092,6 @@ int32_t tDeserializeSAlterDbReq(void* buf, int32_t bufLen, SAlterDbReq* pReq); typedef struct { char db[TSDB_DB_FNAME_LEN]; int8_t ignoreNotExists; - int32_t sqlLen; - char* sql; } SDropDbReq; int32_t tSerializeSDropDbReq(void* buf, int32_t bufLen, SDropDbReq* pReq); @@ -1307,8 +1289,6 @@ void tFreeSUserAuthBatchRsp(SUserAuthBatchRsp* pRsp); typedef struct { char db[TSDB_DB_FNAME_LEN]; STimeWindow timeRange; - int32_t sqlLen; - char* sql; } SCompactDbReq; int32_t tSerializeSCompactDbReq(void* buf, int32_t bufLen, SCompactDbReq* pReq); @@ -1872,8 +1852,6 @@ void tFreeSExplainRsp(SExplainRsp* pRsp); typedef struct { char fqdn[TSDB_FQDN_LEN]; // end point, hostname:port int32_t port; - int32_t sqlLen; - char* sql; } SCreateDnodeReq; int32_t tSerializeSCreateDnodeReq(void* buf, int32_t bufLen, SCreateDnodeReq* pReq); @@ -1885,8 +1863,6 @@ typedef struct { int32_t port; int8_t force; int8_t unsafe; - int32_t sqlLen; - char* sql; } SDropDnodeReq; int32_t tSerializeSDropDnodeReq(void* buf, int32_t bufLen, SDropDnodeReq* pReq); @@ -1902,8 +1878,6 @@ enum { typedef struct { int32_t dnodeId; int8_t restoreType; - int32_t sqlLen; - char* sql; } SRestoreDnodeReq; int32_t tSerializeSRestoreDnodeReq(void* buf, int32_t bufLen, SRestoreDnodeReq* pReq); @@ -1913,8 +1887,6 @@ typedef struct { int32_t dnodeId; char config[TSDB_DNODE_CONFIG_LEN]; char value[TSDB_DNODE_VALUE_LEN]; - int32_t sqlLen; - char* sql; } SMCfgDnodeReq; int32_t tSerializeSMCfgDnodeReq(void* buf, int32_t bufLen, SMCfgDnodeReq* pReq); @@ -1930,8 +1902,6 @@ int32_t tDeserializeSDCfgDnodeReq(void* buf, int32_t bufLen, SDCfgDnodeReq* pReq typedef struct { int32_t dnodeId; - int32_t sqlLen; - char *sql; } SMCreateMnodeReq, SMDropMnodeReq, SDDropMnodeReq, SMCreateQnodeReq, SMDropQnodeReq, SDCreateQnodeReq, SDDropQnodeReq, SMCreateSnodeReq, SMDropSnodeReq, SDCreateSnodeReq, SDDropSnodeReq; @@ -1972,8 +1942,6 @@ int32_t tDeserializeSKillTransReq(void* buf, int32_t bufLen, SKillTransReq* pReq typedef struct { int32_t useless; // useless - int32_t sqlLen; - char* sql; } SBalanceVgroupReq; int32_t tSerializeSBalanceVgroupReq(void* buf, int32_t bufLen, SBalanceVgroupReq* pReq); @@ -1992,8 +1960,6 @@ typedef struct { int32_t dnodeId1; int32_t dnodeId2; int32_t dnodeId3; - int32_t sqlLen; - char* sql; } SRedistributeVgroupReq; int32_t tSerializeSRedistributeVgroupReq(void* buf, int32_t bufLen, SRedistributeVgroupReq* pReq); @@ -2001,8 +1967,6 @@ int32_t tDeserializeSRedistributeVgroupReq(void* buf, int32_t bufLen, SRedistrib typedef struct { int32_t useless; - int32_t sqlLen; - char* sql; } SBalanceVgroupLeaderReq; int32_t tSerializeSBalanceVgroupLeaderReq(void* buf, int32_t bufLen, SBalanceVgroupLeaderReq* pReq); @@ -2262,7 +2226,6 @@ typedef struct { int64_t deleteMark; int8_t igUpdate; int64_t lastTs; - int32_t sqlLen; } SCMCreateStreamReq; typedef struct { @@ -2299,7 +2262,6 @@ typedef struct { char subDbName[TSDB_DB_FNAME_LEN]; char* ast; char subStbName[TSDB_TABLE_FNAME_LEN]; - int32_t sqlLen; } SCMCreateTopicReq; int32_t tSerializeSCMCreateTopicReq(void* buf, int32_t bufLen, const SCMCreateTopicReq* pReq); @@ -2484,8 +2446,6 @@ typedef struct { typedef struct { char name[TSDB_TOPIC_FNAME_LEN]; int8_t igNotExists; - int32_t sqlLen; - char* sql; } SMDropTopicReq; int32_t tSerializeSMDropTopicReq(void* buf, int32_t bufLen, SMDropTopicReq* pReq); @@ -2585,8 +2545,6 @@ typedef struct SVCreateTbReq { SSchemaWrapper schemaRow; } ntb; }; - int32_t sqlLen; - char* sql; } SVCreateTbReq; int tEncodeSVCreateTbReq(SEncoder* pCoder, const SVCreateTbReq* pReq); @@ -3061,8 +3019,6 @@ typedef struct { typedef struct { char name[TSDB_STREAM_FNAME_LEN]; int8_t igNotExists; - int32_t sqlLen; - char* sql; } SMDropStreamReq; typedef struct { diff --git a/source/dnode/mnode/impl/src/mndDb.c b/source/dnode/mnode/impl/src/mndDb.c index c58df5c88c..972705f7a8 100644 --- a/source/dnode/mnode/impl/src/mndDb.c +++ b/source/dnode/mnode/impl/src/mndDb.c @@ -736,7 +736,23 @@ static int32_t mndProcessCreateDbReq(SRpcMsg *pReq) { code = mndCreateDb(pMnode, pReq, &createReq, pUser); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; - auditRecord(pReq, pMnode->clusterId, "createDB", createReq.db, "", ""); + char detail[3000] = {0}; + sprintf(detail, "buffer:%d, cacheLast:%d, cacheLastSize:%d, compression:%d, daysPerFile:%d, " + "daysToKeep0:%d, daysToKeep:%d, daysToKeep2:%d, hashPrefix:%d, " + "hashSuffix:%d, ignoreExist:%d, maxRows:%d, minRows:%d, numOfRetensions:%d, " + "numOfStables:%d, numOfVgroups:%d, pages:%d, pageSize:%d, precision:%d, " + "replications:%d, schemaless:%d, sstTrigger:%d, strict:%d, " + "tsdbPageSize:%d, walFsyncPeriod:%d, walLevel:%d, walRetentionPeriod:%d, " + "walRetentionSize:%" PRId64 ", walRollPeriod:%d, walSegmentSize:%" PRId64, + createReq.buffer, createReq.cacheLast, createReq.cacheLastSize, createReq.compression, createReq.daysPerFile, + createReq.daysToKeep0, createReq.daysToKeep1, createReq.daysToKeep2, createReq.hashPrefix, + createReq.hashSuffix, createReq.ignoreExist, createReq.maxRows, createReq.minRows, createReq.numOfRetensions, + createReq.numOfStables, createReq.numOfVgroups, createReq.pages, createReq.pageSize, createReq.precision, + createReq.replications, createReq.schemaless, createReq.sstTrigger, createReq.strict, + createReq.tsdbPageSize, createReq.walFsyncPeriod, createReq.walLevel, createReq.walRetentionPeriod, + createReq.walRetentionSize, createReq.walRollPeriod, createReq.walSegmentSize); + + auditRecord(pReq, pMnode->clusterId, "createDB", createReq.db, "", detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -980,7 +996,17 @@ static int32_t mndProcessAlterDbReq(SRpcMsg *pReq) { if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; } - auditRecord(pReq, pMnode->clusterId, "alterDB", alterReq.db, "", ""); + char detail[3000] = {0}; + sprintf(detail, "buffer:%d, cacheLast:%d, cacheLastSize:%d, daysPerFile:%d, daysToKeep0:%d, " + "daysToKeep1:%d, daysToKeep2:%d, db:%s, minRows:%d, pages:%d, pageSize:%d, " + "replications:%d, sstTrigger:%d, strict:%d, walFsyncPeriod:%d, " + "walRetentionSize:%d", + alterReq.buffer, alterReq.cacheLast, alterReq.cacheLastSize, alterReq.daysPerFile, alterReq.daysToKeep0, + alterReq.daysToKeep1, alterReq.daysToKeep2, alterReq.db, alterReq.minRows, alterReq.pages, alterReq.pageSize, + alterReq.replications, alterReq.sstTrigger, alterReq.strict, alterReq.walFsyncPeriod, + alterReq.walRetentionSize); + + auditRecord(pReq, pMnode->clusterId, "alterDB", alterReq.db, "", detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -1271,7 +1297,10 @@ static int32_t mndProcessDropDbReq(SRpcMsg *pReq) { code = TSDB_CODE_ACTION_IN_PROGRESS; } - auditRecord(pReq, pMnode->clusterId, "dropDB", dropReq.db, "", ""); + char detail[1000] = {0}; + sprintf(detail, "ignoreNotExists:%d", dropReq.ignoreNotExists); + + auditRecord(pReq, pMnode->clusterId, "dropDB", dropReq.db, "", detail); _OVER: if (code != TSDB_CODE_SUCCESS && code != TSDB_CODE_ACTION_IN_PROGRESS) { diff --git a/source/dnode/mnode/impl/src/mndDnode.c b/source/dnode/mnode/impl/src/mndDnode.c index 1bcbc4982b..949d41ef07 100644 --- a/source/dnode/mnode/impl/src/mndDnode.c +++ b/source/dnode/mnode/impl/src/mndDnode.c @@ -910,11 +910,10 @@ static int32_t mndProcessCreateDnodeReq(SRpcMsg *pReq) { if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; tsGrantHBInterval = 5; - char detail[1000] = {0}; - sprintf(detail, "%s:%d", - createReq.fqdn, createReq.port); + char obj[200] = {0}; + sprintf(obj, "%s:%d", createReq.fqdn, createReq.port); - auditRecord(pReq, pMnode->clusterId, "createDnode", detail, "", ""); + auditRecord(pReq, pMnode->clusterId, "createDnode", obj, "", ""); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -1066,10 +1065,13 @@ static int32_t mndProcessDropDnodeReq(SRpcMsg *pReq) { char obj1[150] = {0}; sprintf(obj1, "%s:%d", dropReq.fqdn, dropReq.port); - char obj2[10] = {0}; + char obj2[30] = {0}; sprintf(obj2, "%d", dropReq.dnodeId); - auditRecord(pReq, pMnode->clusterId, "dropDnode", obj1, obj2, ""); + char detail[100] = {0}; + sprintf(detail, "force:%d, unsafe:%d", dropReq.force, dropReq.unsafe); + + auditRecord(pReq, pMnode->clusterId, "dropDnode", obj1, obj2, detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -1252,10 +1254,13 @@ static int32_t mndProcessConfigDnodeReq(SRpcMsg *pReq) { } } - char detail[50] = {0}; - sprintf(detail, "%d", cfgReq.dnodeId); + char obj[50] = {0}; + sprintf(obj, "%d", cfgReq.dnodeId); - auditRecord(pReq, pMnode->clusterId, "alterDnode", detail, "", ""); + char detail[500] = {0}; + sprintf(detail, "config:%s, value:%s", cfgReq.config, cfgReq.value); + + auditRecord(pReq, pMnode->clusterId, "alterDnode", obj, "", detail); int32_t code = -1; SSdb *pSdb = pMnode->pSdb; diff --git a/source/dnode/mnode/impl/src/mndMnode.c b/source/dnode/mnode/impl/src/mndMnode.c index 8b9deb3988..5827a30b43 100644 --- a/source/dnode/mnode/impl/src/mndMnode.c +++ b/source/dnode/mnode/impl/src/mndMnode.c @@ -653,14 +653,10 @@ static int32_t mndProcessCreateMnodeReq(SRpcMsg *pReq) { code = mndCreateMnode(pMnode, pReq, pDnode, &createReq); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; - char detail[1000] = {0}; - - char obj[20] = {0}; + char obj[40] = {0}; sprintf(obj, "%d", createReq.dnodeId); - sprintf(detail, "dnodeId:%d", createReq.dnodeId); - - auditRecord(pReq, pMnode->clusterId, "createMnode", obj, detail, ""); + auditRecord(pReq, pMnode->clusterId, "createMnode", obj, "", ""); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -798,7 +794,7 @@ static int32_t mndProcessDropMnodeReq(SRpcMsg *pReq) { code = mndDropMnode(pMnode, pReq, pObj); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; - char obj[20] = {0}; + char obj[40] = {0}; sprintf(obj, "%d", dropReq.dnodeId); auditRecord(pReq, pMnode->clusterId, "dropMnode", obj, "", ""); diff --git a/source/dnode/mnode/impl/src/mndProfile.c b/source/dnode/mnode/impl/src/mndProfile.c index db1546e33f..9847024bee 100644 --- a/source/dnode/mnode/impl/src/mndProfile.c +++ b/source/dnode/mnode/impl/src/mndProfile.c @@ -309,15 +309,14 @@ _CONNECT: code = 0; - char detail[1000] = {0}; - - char obj[30] = {0}; + char obj[100] = {0}; sprintf(obj, "%s:%d", ip, pConn->port); - sprintf(detail, "user:%s, from:%s, connType%d", - connReq.user, obj, connReq.connType); + char detail[1000] = {0}; + sprintf(detail, "connType:%d, db:%s, pid:%d, startTime:%" PRId64 ", sVer:%s, app:%s", + connReq.connType, connReq.db, connReq.pid, connReq.startTime, connReq.sVer, connReq.app); - auditRecord(pReq, pMnode->clusterId, "login", connReq.app, obj, detail); + auditRecord(pReq, pMnode->clusterId, "login", connReq.user, obj, detail); _OVER: diff --git a/source/dnode/mnode/impl/src/mndQnode.c b/source/dnode/mnode/impl/src/mndQnode.c index 45efabe97d..767e06a8d4 100644 --- a/source/dnode/mnode/impl/src/mndQnode.c +++ b/source/dnode/mnode/impl/src/mndQnode.c @@ -423,7 +423,7 @@ static int32_t mndProcessDropQnodeReq(SRpcMsg *pReq) { char obj[33] = {0}; sprintf(obj, "%d", dropReq.dnodeId); - auditRecord(pReq, pMnode->clusterId, "createQnode", obj, "", ""); + auditRecord(pReq, pMnode->clusterId, "dropQnode", obj, "", ""); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { diff --git a/source/dnode/mnode/impl/src/mndStb.c b/source/dnode/mnode/impl/src/mndStb.c index aa3ee89fd3..a5fe818133 100644 --- a/source/dnode/mnode/impl/src/mndStb.c +++ b/source/dnode/mnode/impl/src/mndStb.c @@ -1174,7 +1174,17 @@ static int32_t mndProcessCreateStbReq(SRpcMsg *pReq) { } if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; - auditRecord(pReq, pMnode->clusterId, "createStb", pDb->name, createReq.name, ""); + char detail[2000] = {0}; + sprintf(detail, "colVer:%d, delay1:%" PRId64 ", delay2:%" PRId64 ", deleteMark1:%" PRId64 ", " + "deleteMark2:%" PRId64 ", igExists:%d, numOfColumns:%d, numOfFuncs:%d, numOfTags:%d, " + "source:%d, suid:%" PRId64 ", tagVer:%d, ttl:%d, " + "watermark1:%" PRId64 ", watermark2:%" PRId64, + createReq.colVer, createReq.delay1, createReq.delay2, createReq.deleteMark1, + createReq.deleteMark2, createReq.igExists, createReq.numOfColumns, createReq.numOfFuncs, createReq.numOfTags, + createReq.source, createReq.suid, createReq.tagVer, createReq.ttl, + createReq.watermark1, createReq.watermark2); + + auditRecord(pReq, pMnode->clusterId, "createStb", pDb->name, createReq.name, detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -2244,7 +2254,11 @@ static int32_t mndProcessAlterStbReq(SRpcMsg *pReq) { code = mndAlterStb(pMnode, pReq, &alterReq, pDb, pStb); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; - auditRecord(pReq, pMnode->clusterId, "alterStb", pDb->name, alterReq.name, ""); + char detail[2000] = {0}; + sprintf(detail, "alterType:%d, numOfFields:%d, ttl:%d" , + alterReq.alterType, alterReq.numOfFields, alterReq.ttl); + + auditRecord(pReq, pMnode->clusterId, "alterStb", pDb->name, alterReq.name, detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -2507,7 +2521,11 @@ static int32_t mndProcessDropStbReq(SRpcMsg *pReq) { code = mndDropStb(pMnode, pReq, pDb, pStb); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; - auditRecord(pReq, pMnode->clusterId, "dropStb", pDb->name, dropReq.name, ""); + char detail[2000] = {0}; + sprintf(detail, "igNotExists:%d, source:%d" , + dropReq.igNotExists, dropReq.source); + + auditRecord(pReq, pMnode->clusterId, "dropStb", pDb->name, dropReq.name, detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { diff --git a/source/dnode/mnode/impl/src/mndStream.c b/source/dnode/mnode/impl/src/mndStream.c index c553257094..03bb84b04d 100644 --- a/source/dnode/mnode/impl/src/mndStream.c +++ b/source/dnode/mnode/impl/src/mndStream.c @@ -829,7 +829,19 @@ static int32_t mndProcessCreateStreamReq(SRpcMsg *pReq) { code = TSDB_CODE_ACTION_IN_PROGRESS; - auditRecord(pReq, pMnode->clusterId, "createStream", createStreamReq.name, "", ""); + char detail[2000] = {0}; + sprintf(detail, "checkpointFreq:%" PRId64 ", createStb:%d, deleteMark:%" PRId64 ", " + "fillHistory:%d, igExists:%d, " + "igExpired:%d, igUpdate:%d, lastTs:%" PRId64 ", " + "maxDelay:%" PRId64 ", numOfTags:%d, sourceDB:%s, " + "targetStbFullName:%s, triggerType:%d, watermark:%" PRId64, + createStreamReq.checkpointFreq, createStreamReq.createStb, createStreamReq.deleteMark, + createStreamReq.fillHistory, createStreamReq.igExists, + createStreamReq.igExpired, createStreamReq.igUpdate, createStreamReq.lastTs, + createStreamReq.maxDelay, createStreamReq.numOfTags, createStreamReq.sourceDB, + createStreamReq.targetStbFullName, createStreamReq.triggerType, createStreamReq.watermark); + + auditRecord(pReq, pMnode->clusterId, "createStream", createStreamReq.name, "", detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -1076,7 +1088,10 @@ static int32_t mndProcessDropStreamReq(SRpcMsg *pReq) { return -1; } - auditRecord(pReq, pMnode->clusterId, "dropStream", dropReq.name, "", ""); + char detail[100] = {0}; + sprintf(detail, "igNotExists:%d", dropReq.igNotExists); + + auditRecord(pReq, pMnode->clusterId, "dropStream", dropReq.name, "", detail); sdbRelease(pMnode->pSdb, pStream); mndTransDrop(pTrans); diff --git a/source/dnode/mnode/impl/src/mndTopic.c b/source/dnode/mnode/impl/src/mndTopic.c index 831e67bea3..e1d964a8a3 100644 --- a/source/dnode/mnode/impl/src/mndTopic.c +++ b/source/dnode/mnode/impl/src/mndTopic.c @@ -622,7 +622,11 @@ static int32_t mndProcessCreateTopicReq(SRpcMsg *pReq) { code = TSDB_CODE_ACTION_IN_PROGRESS; } - auditRecord(pReq, pMnode->clusterId, "crateTopic", createTopicReq.name, createTopicReq.subDbName, createTopicReq.sql); + char detail[1000] = {0}; + sprintf(detail, "igExists:%d, subStbName:%s, subType:%d, withMeta:%d", + createTopicReq.igExists, createTopicReq.subStbName, createTopicReq.subType, createTopicReq.withMeta); + + auditRecord(pReq, pMnode->clusterId, "crateTopic", createTopicReq.name, createTopicReq.subDbName, detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -815,7 +819,10 @@ static int32_t mndProcessDropTopicReq(SRpcMsg *pReq) { return -1; } - auditRecord(pReq, pMnode->clusterId, "dropTopic", dropReq.name, "", dropReq.sql); + char detail[100] = {0}; + sprintf(detail, "igNotExists:%d", dropReq.igNotExists); + + auditRecord(pReq, pMnode->clusterId, "dropTopic", dropReq.name, "", detail); return TSDB_CODE_ACTION_IN_PROGRESS; } diff --git a/source/dnode/mnode/impl/src/mndUser.c b/source/dnode/mnode/impl/src/mndUser.c index 8afc73bef6..098f260bb6 100644 --- a/source/dnode/mnode/impl/src/mndUser.c +++ b/source/dnode/mnode/impl/src/mndUser.c @@ -656,7 +656,11 @@ static int32_t mndProcessCreateUserReq(SRpcMsg *pReq) { code = mndCreateUser(pMnode, pOperUser->acct, &createReq, pReq); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; - auditRecord(pReq, pMnode->clusterId, "createUser", createReq.user, "", ""); + char detail[1000] = {0}; + sprintf(detail, "createType:%d, enable:%d, superUser:%d, sysInfo:%d", + createReq.createType, createReq.enable, createReq.superUser, createReq.sysInfo); + + auditRecord(pReq, pMnode->clusterId, "createUser", createReq.user, "", detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -973,13 +977,17 @@ static int32_t mndProcessAlterUserReq(SRpcMsg *pReq) { code = mndAlterUser(pMnode, pUser, &newUser, pReq); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; + char detail[1000] = {0}; + sprintf(detail, "alterType:%d, enable:%d, superUser:%d, sysInfo:%d, tabName:%s", + alterReq.alterType, alterReq.enable, alterReq.superUser, alterReq.sysInfo, alterReq.tabName); + if(alterReq.alterType == TSDB_ALTER_USER_PASSWD){ - auditRecord(pReq, pMnode->clusterId, "changePassword", alterReq.user, alterReq.objname, ""); + auditRecord(pReq, pMnode->clusterId, "changePassword", alterReq.user, alterReq.objname, detail); } else if(alterReq.alterType == TSDB_ALTER_USER_SUPERUSER || alterReq.alterType == TSDB_ALTER_USER_ENABLE || alterReq.alterType == TSDB_ALTER_USER_SYSINFO){ - auditRecord(pReq, pMnode->clusterId, "alterUser", alterReq.user, alterReq.objname, ""); + auditRecord(pReq, pMnode->clusterId, "alterUser", alterReq.user, alterReq.objname, detail); } else if(alterReq.alterType == TSDB_ALTER_USER_ADD_READ_DB|| alterReq.alterType == TSDB_ALTER_USER_ADD_WRITE_DB|| @@ -988,10 +996,10 @@ static int32_t mndProcessAlterUserReq(SRpcMsg *pReq) { alterReq.alterType == TSDB_ALTER_USER_ADD_READ_TABLE|| alterReq.alterType == TSDB_ALTER_USER_ADD_WRITE_TABLE|| alterReq.alterType == TSDB_ALTER_USER_ADD_ALL_TABLE){ - auditRecord(pReq, pMnode->clusterId, "GrantPrivileges", alterReq.user, alterReq.objname, ""); + auditRecord(pReq, pMnode->clusterId, "GrantPrivileges", alterReq.user, alterReq.objname, detail); } else{ - auditRecord(pReq, pMnode->clusterId, "RevokePrivileges", alterReq.user, alterReq.objname, ""); + auditRecord(pReq, pMnode->clusterId, "RevokePrivileges", alterReq.user, alterReq.objname, detail); } _OVER: @@ -1063,7 +1071,7 @@ static int32_t mndProcessDropUserReq(SRpcMsg *pReq) { code = mndDropUser(pMnode, pReq, pUser); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; - auditRecord(pReq, pMnode->clusterId, "dropUser", dropReq.user, "", dropReq.sql); + auditRecord(pReq, pMnode->clusterId, "dropUser", dropReq.user, "", ""); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { diff --git a/source/dnode/mnode/impl/src/mndVgroup.c b/source/dnode/mnode/impl/src/mndVgroup.c index ff621198ff..b16ec8c2cb 100644 --- a/source/dnode/mnode/impl/src/mndVgroup.c +++ b/source/dnode/mnode/impl/src/mndVgroup.c @@ -2175,7 +2175,11 @@ static int32_t mndProcessRedistributeVgroupMsg(SRpcMsg *pReq) { char obj[33] = {0}; sprintf(obj, "%d", req.vgId); - auditRecord(pReq, pMnode->clusterId, "RedistributeVgroup", obj, "", req.sql); + char detail[1000] = {0}; + sprintf(detail, "dnodeId1:%d, dnodeId2:%d, dnodeId3:%d", + req.dnodeId1, req.dnodeId2, req.dnodeId3); + + auditRecord(pReq, pMnode->clusterId, "RedistributeVgroup", obj, "", detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -2987,7 +2991,7 @@ static int32_t mndProcessBalanceVgroupMsg(SRpcMsg *pReq) { code = mndBalanceVgroup(pMnode, pReq, pArray); } - auditRecord(pReq, pMnode->clusterId, "balanceVgroup", "", "", req.sql); + auditRecord(pReq, pMnode->clusterId, "balanceVgroup", "", "", ""); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { diff --git a/source/dnode/vnode/src/vnd/vnodeSvr.c b/source/dnode/vnode/src/vnd/vnodeSvr.c index 3687756ffc..ccdde8ade4 100644 --- a/source/dnode/vnode/src/vnd/vnodeSvr.c +++ b/source/dnode/vnode/src/vnd/vnodeSvr.c @@ -934,7 +934,11 @@ static int32_t vnodeProcessCreateTbReq(SVnode *pVnode, int64_t ver, void *pReq, int32_t clusterId = pVnode->config.syncCfg.nodeInfo[0].clusterId; - auditRecord(pReq, clusterId, "createTable", pVnode->config.dbname, pCreateReq->name, ""); + char detail[1000] = {0}; + sprintf(detail, "btime:%" PRId64 ", flags:%d, ttl:%d, type:%d", + pCreateReq->btime, pCreateReq->flags, pCreateReq->ttl, pCreateReq->type); + + auditRecord(pReq, clusterId, "createTable", pVnode->config.dbname, pCreateReq->name, detail); } vDebug("vgId:%d, add %d new created tables into query table list", TD_VID(pVnode), (int32_t)taosArrayGetSize(tbUids)); From 201a8f0918477e41f1316f0da003478b1e800e05 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Mon, 28 Aug 2023 19:06:16 +0800 Subject: [PATCH 092/120] feat:support varbinary type --- source/client/src/clientMain.c | 12 ++++- source/client/src/clientRawBlockWrite.c | 4 +- source/libs/function/src/builtins.c | 24 +++++++-- source/libs/parser/src/parAstCreater.c | 2 +- source/libs/scalar/src/sclfunc.c | 10 ++++ tests/parallel_test/cases.task | 2 +- tools/shell/src/shellEngine.c | 2 +- utils/test/c/varbinary_test.c | 72 ++++++++++++++++++++++--- 8 files changed, 109 insertions(+), 19 deletions(-) diff --git a/source/client/src/clientMain.c b/source/client/src/clientMain.c index 64bcd6d898..73b4ec2a74 100644 --- a/source/client/src/clientMain.c +++ b/source/client/src/clientMain.c @@ -387,8 +387,18 @@ int taos_print_row(char *str, TAOS_ROW row, TAOS_FIELD *fields, int num_fields) len += sprintf(str + len, "%lf", dv); } break; + case TSDB_DATA_TYPE_VARBINARY:{ + void* data = NULL; + uint32_t size = 0; + int32_t charLen = varDataLen((char *)row[i] - VARSTR_HEADER_SIZE); + if(taosAscii2Hex(row[i], charLen, &data, &size) < 0){ + break; + } + memcpy(str + len, data, size); + len += size; + taosMemoryFree(data); + }break; case TSDB_DATA_TYPE_BINARY: - case TSDB_DATA_TYPE_VARBINARY: case TSDB_DATA_TYPE_NCHAR: case TSDB_DATA_TYPE_GEOMETRY: { int32_t charLen = varDataLen((char *)row[i] - VARSTR_HEADER_SIZE); diff --git a/source/client/src/clientRawBlockWrite.c b/source/client/src/clientRawBlockWrite.c index af6b3bcc8c..4098881b14 100644 --- a/source/client/src/clientRawBlockWrite.c +++ b/source/client/src/clientRawBlockWrite.c @@ -522,9 +522,9 @@ static char* processAlterTable(SMqMetaRsp* metaRsp) { buf = parseTagDatatoJson(vAlterTbReq.pTagVal); } else { if(vAlterTbReq.tagType == TSDB_DATA_TYPE_VARBINARY){ - buf = taosMemoryCalloc(vAlterTbReq.nTagVal + 1, 1); + buf = taosMemoryCalloc(vAlterTbReq.nTagVal*2 + 2 + 3, 1); }else{ - buf = taosMemoryCalloc(vAlterTbReq.nTagVal + 1, 1); + buf = taosMemoryCalloc(vAlterTbReq.nTagVal + 3, 1); } dataConverToStr(buf, vAlterTbReq.tagType, vAlterTbReq.pTagVal, vAlterTbReq.nTagVal, NULL); } diff --git a/source/libs/function/src/builtins.c b/source/libs/function/src/builtins.c index 9a301b7f1c..e18c1f4c44 100644 --- a/source/libs/function/src/builtins.c +++ b/source/libs/function/src/builtins.c @@ -314,7 +314,7 @@ static int32_t translateInOutStr(SFunctionNode* pFunc, char* pErrBuf, int32_t le } SExprNode* pPara1 = (SExprNode*)nodesListGetNode(pFunc->pParameterList, 0); - if (!IS_STR_DATA_TYPE(pPara1->resType.type)) { + if (TSDB_DATA_TYPE_VARBINARY == pPara1->resType.type || !IS_STR_DATA_TYPE(pPara1->resType.type)) { return invaildFuncParaTypeErrMsg(pErrBuf, len, pFunc->functionName); } @@ -328,7 +328,7 @@ static int32_t translateTrimStr(SFunctionNode* pFunc, char* pErrBuf, int32_t len } SExprNode* pPara1 = (SExprNode*)nodesListGetNode(pFunc->pParameterList, 0); - if (!IS_STR_DATA_TYPE(pPara1->resType.type)) { + if (TSDB_DATA_TYPE_VARBINARY == pPara1->resType.type || !IS_STR_DATA_TYPE(pPara1->resType.type)) { return invaildFuncParaTypeErrMsg(pErrBuf, len, pFunc->functionName); } @@ -1824,6 +1824,10 @@ static int32_t translateLength(SFunctionNode* pFunc, char* pErrBuf, int32_t len) return invaildFuncParaTypeErrMsg(pErrBuf, len, pFunc->functionName); } + if (TSDB_DATA_TYPE_VARBINARY == ((SExprNode*)nodesListGetNode(pFunc->pParameterList, 0))->resType.type) { + return invaildFuncParaTypeErrMsg(pErrBuf, len, pFunc->functionName); + } + pFunc->node.resType = (SDataType){.bytes = tDataTypes[TSDB_DATA_TYPE_BIGINT].bytes, .type = TSDB_DATA_TYPE_BIGINT}; return TSDB_CODE_SUCCESS; } @@ -1852,6 +1856,10 @@ static int32_t translateConcatImpl(SFunctionNode* pFunc, char* pErrBuf, int32_t for (int32_t i = 0; i < numOfParams; ++i) { SNode* pPara = nodesListGetNode(pFunc->pParameterList, i); uint8_t paraType = ((SExprNode*)pPara)->resType.type; + if (TSDB_DATA_TYPE_VARBINARY == paraType) { + return invaildFuncParaTypeErrMsg(pErrBuf, len, pFunc->functionName); + } + if (!IS_STR_DATA_TYPE(paraType) && !IS_NULL_TYPE(paraType)) { return invaildFuncParaTypeErrMsg(pErrBuf, len, pFunc->functionName); } @@ -1908,7 +1916,7 @@ static int32_t translateSubstr(SFunctionNode* pFunc, char* pErrBuf, int32_t len) uint8_t para0Type = pPara0->resType.type; uint8_t para1Type = pPara1->resType.type; - if (!IS_STR_DATA_TYPE(para0Type) || !IS_INTEGER_TYPE(para1Type)) { + if (TSDB_DATA_TYPE_VARBINARY == para0Type || !IS_STR_DATA_TYPE(para0Type) || !IS_INTEGER_TYPE(para1Type)) { return invaildFuncParaTypeErrMsg(pErrBuf, len, pFunc->functionName); } @@ -1936,6 +1944,12 @@ static int32_t translateSubstr(SFunctionNode* pFunc, char* pErrBuf, int32_t len) static int32_t translateCast(SFunctionNode* pFunc, char* pErrBuf, int32_t len) { // The number of parameters has been limited by the syntax definition + SExprNode* pPara0 = (SExprNode*)nodesListGetNode(pFunc->pParameterList, 0); + uint8_t para0Type = pPara0->resType.type; + if (TSDB_DATA_TYPE_VARBINARY == para0Type) { + return invaildFuncParaTypeErrMsg(pErrBuf, len, pFunc->functionName); + } + // The function return type has been set during syntax parsing uint8_t para2Type = pFunc->node.resType.type; @@ -2007,7 +2021,7 @@ static int32_t translateToUnixtimestamp(SFunctionNode* pFunc, char* pErrBuf, int } uint8_t para1Type = ((SExprNode*)nodesListGetNode(pFunc->pParameterList, 0))->resType.type; - if (!IS_STR_DATA_TYPE(para1Type)) { + if (para1Type == TSDB_DATA_TYPE_VARBINARY || !IS_STR_DATA_TYPE(para1Type)) { return invaildFuncParaTypeErrMsg(pErrBuf, len, pFunc->functionName); } @@ -2141,7 +2155,7 @@ static int32_t translateToJson(SFunctionNode* pFunc, char* pErrBuf, int32_t len) } SExprNode* pPara = (SExprNode*)nodesListGetNode(pFunc->pParameterList, 0); - if (QUERY_NODE_VALUE != nodeType(pPara) || (!IS_VAR_DATA_TYPE(pPara->resType.type))) { + if (QUERY_NODE_VALUE != nodeType(pPara) || TSDB_DATA_TYPE_VARBINARY == pPara->resType.type || (!IS_VAR_DATA_TYPE(pPara->resType.type))) { return invaildFuncParaTypeErrMsg(pErrBuf, len, pFunc->functionName); } diff --git a/source/libs/parser/src/parAstCreater.c b/source/libs/parser/src/parAstCreater.c index f85218c50a..3e95f843a7 100644 --- a/source/libs/parser/src/parAstCreater.c +++ b/source/libs/parser/src/parAstCreater.c @@ -518,7 +518,7 @@ SNode* createCastFunctionNode(SAstCreateContext* pCxt, SNode* pExpr, SDataType d CHECK_OUT_OF_MEM(func); strcpy(func->functionName, "cast"); func->node.resType = dt; - if (TSDB_DATA_TYPE_VARCHAR == dt.type || TSDB_DATA_TYPE_GEOMETRY == dt.type) { + if (TSDB_DATA_TYPE_VARCHAR == dt.type || TSDB_DATA_TYPE_GEOMETRY == dt.type || TSDB_DATA_TYPE_VARBINARY == dt.type) { func->node.resType.bytes = func->node.resType.bytes + VARSTR_HEADER_SIZE; } else if (TSDB_DATA_TYPE_NCHAR == dt.type) { func->node.resType.bytes = func->node.resType.bytes * TSDB_NCHAR_SIZE + VARSTR_HEADER_SIZE; diff --git a/source/libs/scalar/src/sclfunc.c b/source/libs/scalar/src/sclfunc.c index b9af716929..8c6f9a71b2 100644 --- a/source/libs/scalar/src/sclfunc.c +++ b/source/libs/scalar/src/sclfunc.c @@ -964,6 +964,16 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp } break; } + case TSDB_DATA_TYPE_VARBINARY:{ + if (inputType == TSDB_DATA_TYPE_BINARY) { + int32_t len = TMIN(varDataLen(input), outputLen - VARSTR_HEADER_SIZE); + memcpy(varDataVal(output), varDataVal(input), len); + varDataSetLen(output, len); + }else{ + code = TSDB_CODE_FUNC_FUNTION_PARA_TYPE; + goto _end; + } + } case TSDB_DATA_TYPE_NCHAR: { int32_t outputCharLen = (outputLen - VARSTR_HEADER_SIZE) / TSDB_NCHAR_SIZE; int32_t len; diff --git a/tests/parallel_test/cases.task b/tests/parallel_test/cases.task index 53b2e7acd9..e4303b2e15 100644 --- a/tests/parallel_test/cases.task +++ b/tests/parallel_test/cases.task @@ -351,7 +351,7 @@ ,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/smaTest.py -R ,,y,system-test,./pytest.sh python3 ./test.py -f 0-others/sma_index.py ,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/sml_TS-3724.py -#,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/varbinary.py +,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/varbinary.py ,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/sml.py ,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/sml.py -R ,,y,system-test,./pytest.sh python3 ./test.py -f 2-query/spread.py diff --git a/tools/shell/src/shellEngine.c b/tools/shell/src/shellEngine.c index 35ab86269d..72cf3cd1cc 100644 --- a/tools/shell/src/shellEngine.c +++ b/tools/shell/src/shellEngine.c @@ -409,7 +409,7 @@ void shellDumpFieldToFile(TdFilePtr pFile, const char *val, TAOS_FIELD *field, i if(taosAscii2Hex(val, length, &tmp, &size) < 0){ break; } - taosFprintfFile(pFile, "%s", tmp); + taosFprintfFile(pFile, "%s%s%s", quotationStr, tmp, quotationStr); taosMemoryFree(tmp); break; } diff --git a/utils/test/c/varbinary_test.c b/utils/test/c/varbinary_test.c index 74c50d66d9..bd73cbc0fc 100644 --- a/utils/test/c/varbinary_test.c +++ b/utils/test/c/varbinary_test.c @@ -147,13 +147,70 @@ int varbinary_test() { ASSERT(taos_errno(pRes) != 0); taos_free_result(pRes); -// pRes = taos_query(taos, "select * from stb where c2 contains 'ssd'"); -// ASSERT(taos_errno(pRes) != 0); -// taos_free_result(pRes); -// -// pRes = taos_query(taos, "select * from stb where c2 contains 'ssd'"); -// ASSERT(taos_errno(pRes) != 0); -// taos_free_result(pRes); + + // math function test, not support + pRes = taos_query(taos, "select abs(c2) from stb"); + ASSERT(taos_errno(pRes) != 0); + taos_free_result(pRes); + + pRes = taos_query(taos, "select floor(c2) from stb"); + ASSERT(taos_errno(pRes) != 0); + taos_free_result(pRes); + + // string function test, not support + pRes = taos_query(taos, "select length(c2) from stb"); + ASSERT(taos_errno(pRes) != 0); + taos_free_result(pRes); + + pRes = taos_query(taos, "select ltrim(c2) from stb"); + ASSERT(taos_errno(pRes) != 0); + taos_free_result(pRes); + + pRes = taos_query(taos, "select upper(c2) from stb"); + ASSERT(taos_errno(pRes) != 0); + taos_free_result(pRes); + + + pRes = taos_query(taos, "select to_json(c2) from stb"); + ASSERT(taos_errno(pRes) != 0); + taos_free_result(pRes); + + pRes = taos_query(taos, "select TO_UNIXTIMESTAMP(c2) from stb"); + ASSERT(taos_errno(pRes) != 0); + taos_free_result(pRes); + + pRes = taos_query(taos, "select cast(c2 as varchar(16)) from stb"); + ASSERT(taos_errno(pRes) != 0); + taos_free_result(pRes); + + pRes = taos_query(taos, "select cast(c3 as varbinary(16)) from stb"); + ASSERT(taos_errno(pRes) != 0); + taos_free_result(pRes); + + pRes = taos_query(taos, "select cast(c1 as varbinary(16)) from stb"); + ASSERT(taos_errno(pRes) != 0); + taos_free_result(pRes); + + // support first/last/last_row/count/hyperloglog/sample/tail/mode + pRes = taos_query(taos, "select first(c2) from stb"); + ASSERT(taos_errno(pRes) == 0); + taos_free_result(pRes); + + pRes = taos_query(taos, "select count(c2) from stb"); + ASSERT(taos_errno(pRes) == 0); + taos_free_result(pRes); + + pRes = taos_query(taos, "select sample(c2,2) from stb"); + ASSERT(taos_errno(pRes) == 0); + taos_free_result(pRes); + + pRes = taos_query(taos, "select mode(c2) from stb"); + ASSERT(taos_errno(pRes) == 0); + taos_free_result(pRes); + + pRes = taos_query(taos, "select cast(t2 as varbinary(16)) from stb"); + ASSERT(taos_errno(pRes) == 0); + taos_free_result(pRes); int numRows = 0; @@ -192,7 +249,6 @@ int varbinary_test() { ASSERT(numRows == 2); taos_free_result(pRes); - pRes = taos_query(taos, "select * from stb where c2 not between '\\x3e' and '\\x7F8290'"); GET_ROW_NUM ASSERT(numRows == 3); From 728c542bd0b8b654c94f9f65f0c897295360528d Mon Sep 17 00:00:00 2001 From: yihaoDeng Date: Mon, 28 Aug 2023 22:30:50 +0800 Subject: [PATCH 093/120] update doc --- docs/en/12-taos-sql/27-indexing.md | 3 +++ docs/zh/12-taos-sql/27-index.md | 2 ++ 2 files changed, 5 insertions(+) diff --git a/docs/en/12-taos-sql/27-indexing.md b/docs/en/12-taos-sql/27-indexing.md index a89c8929c1..1badd38d17 100644 --- a/docs/en/12-taos-sql/27-indexing.md +++ b/docs/en/12-taos-sql/27-indexing.md @@ -19,6 +19,9 @@ index_option: functions: function [, function] ... ``` +### tag Indexing + + [tag index](../tag-index) ### SMA Indexing diff --git a/docs/zh/12-taos-sql/27-index.md b/docs/zh/12-taos-sql/27-index.md index da8f38eb22..327d909616 100644 --- a/docs/zh/12-taos-sql/27-index.md +++ b/docs/zh/12-taos-sql/27-index.md @@ -20,6 +20,8 @@ index_option: functions: function [, function] ... ``` +### tag 索引 + [tag-index] (../tag-index) ### SMA 索引 From 381072f0c08041146687620101cae453ae4f490e Mon Sep 17 00:00:00 2001 From: yihaoDeng Date: Mon, 28 Aug 2023 22:32:19 +0800 Subject: [PATCH 094/120] update doc --- docs/zh/12-taos-sql/27-index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/12-taos-sql/27-index.md b/docs/zh/12-taos-sql/27-index.md index 327d909616..f934672bd2 100644 --- a/docs/zh/12-taos-sql/27-index.md +++ b/docs/zh/12-taos-sql/27-index.md @@ -21,7 +21,7 @@ functions: function [, function] ... ``` ### tag 索引 - [tag-index] (../tag-index) + [tag 索引] (../tag-index) ### SMA 索引 From 023cf096b3888aace8f81ff58e91f97b6f48cec3 Mon Sep 17 00:00:00 2001 From: yihaoDeng Date: Tue, 29 Aug 2023 10:25:44 +0800 Subject: [PATCH 095/120] handle taosd quit --- source/libs/transport/src/transSvr.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source/libs/transport/src/transSvr.c b/source/libs/transport/src/transSvr.c index c6c412022a..40610d7651 100644 --- a/source/libs/transport/src/transSvr.c +++ b/source/libs/transport/src/transSvr.c @@ -289,8 +289,15 @@ static bool uvHandleReq(SSvrConn* pConn) { } void uvOnRecvCb(uv_stream_t* cli, ssize_t nread, const uv_buf_t* buf) { - SSvrConn* conn = cli->data; - STrans* pTransInst = conn->pTransInst; + SSvrConn* conn = cli->data; + SWorkThrd* pThrd = conn->hostThrd; + + if (true == pThrd->quit) { + tInfo("work thread received quit msg, destroy conn"); + destroyConn(conn, true); + return; + } + STrans* pTransInst = conn->pTransInst; SConnBuffer* pBuf = &conn->readBuf; if (nread > 0) { From b88d79d2efa403dd4ef7ca21acaa98218110ca55 Mon Sep 17 00:00:00 2001 From: Minglei Jin Date: Tue, 29 Aug 2023 10:20:56 +0800 Subject: [PATCH 096/120] fix(tsdb/open fs): fix retval --- source/dnode/vnode/src/tsdb/tsdbFS2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/dnode/vnode/src/tsdb/tsdbFS2.c b/source/dnode/vnode/src/tsdb/tsdbFS2.c index 6bdedebe35..a997c3eea5 100644 --- a/source/dnode/vnode/src/tsdb/tsdbFS2.c +++ b/source/dnode/vnode/src/tsdb/tsdbFS2.c @@ -637,7 +637,7 @@ _exit: } else { tsdbInfo("vgId:%d %s success", TD_VID(pTsdb->pVnode), __func__); } - return 0; + return code; } static int32_t close_file_system(STFileSystem *fs) { @@ -730,7 +730,7 @@ _exit: } else { tsdbInfo("vgId:%d %s success", TD_VID(pTsdb->pVnode), __func__); } - return 0; + return code; } static void tsdbDoWaitBgTask(STFileSystem *fs, STFSBgTask *task) { From 667fe69fd1ffea6c121edc2c999a4d0e6b7899bf Mon Sep 17 00:00:00 2001 From: dapan1121 Date: Tue, 29 Aug 2023 10:40:32 +0800 Subject: [PATCH 097/120] fix: join on condition split issue --- source/libs/planner/src/planOptimizer.c | 3 +++ tests/system-test/2-query/stbJoin.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/source/libs/planner/src/planOptimizer.c b/source/libs/planner/src/planOptimizer.c index 7ce6994ce6..51d5c96c86 100644 --- a/source/libs/planner/src/planOptimizer.c +++ b/source/libs/planner/src/planOptimizer.c @@ -969,6 +969,9 @@ static int32_t pushDownCondOptDealJoin(SOptimizeContext* pCxt, SJoinLogicNode* p if (NULL == pJoin->node.pConditions) { int32_t code = pushDownCondOptJoinExtractCond(pCxt, pJoin); + if (TSDB_CODE_SUCCESS == code) { + code = pushDownCondOptJoinExtractEqualOnCond(pCxt, pJoin); + } if (TSDB_CODE_SUCCESS == code) { OPTIMIZE_FLAG_SET_MASK(pJoin->node.optimizedFlag, OPTIMIZE_FLAG_PUSH_DOWN_CONDE); pCxt->optimized = true; diff --git a/tests/system-test/2-query/stbJoin.py b/tests/system-test/2-query/stbJoin.py index b4d6301424..e21a875cf2 100644 --- a/tests/system-test/2-query/stbJoin.py +++ b/tests/system-test/2-query/stbJoin.py @@ -106,6 +106,9 @@ class TDTestCase: tdSql.query(f"select a.ts, b.ts from sta a, stb b where a.ts=b.ts and (a.tg1=b.tg1 and a.tg1 > b.tg1);") tdSql.checkRows(0) + tdSql.query(f"select a.* from sta a join stb b on a.tg1=b.tg1 and a.ts=b.ts and a.tg2=b.tg2;") + tdSql.checkRows(12) + # tdSql.checkData(0,1,10) tdSql.error(f"select a.* from sta a join stb b on a.tg1=b.tg1 where a.ts=b.ts or a.tg2=b.tg2;") From 27138b22aa4214ae7ac485385f5169358161e53d Mon Sep 17 00:00:00 2001 From: Shuduo Sang Date: Tue, 29 Aug 2023 11:45:42 +0800 Subject: [PATCH 098/120] docs: add seeq doc zh (#22601) --- docs/zh/20-third-party/70-seeq.md | 437 ++++++++++++++++++ .../20-third-party/seeq/seeq-demo-schema.webp | Bin 0 -> 13174 bytes .../seeq/seeq-demo-workbench.webp | Bin 0 -> 56926 bytes .../seeq/seeq-forecast-result.webp | Bin 0 -> 26598 bytes .../seeq-workbench-with-tdengine-cloud.webp | Bin 0 -> 48280 bytes 5 files changed, 437 insertions(+) create mode 100644 docs/zh/20-third-party/70-seeq.md create mode 100644 docs/zh/20-third-party/seeq/seeq-demo-schema.webp create mode 100644 docs/zh/20-third-party/seeq/seeq-demo-workbench.webp create mode 100644 docs/zh/20-third-party/seeq/seeq-forecast-result.webp create mode 100644 docs/zh/20-third-party/seeq/seeq-workbench-with-tdengine-cloud.webp diff --git a/docs/zh/20-third-party/70-seeq.md b/docs/zh/20-third-party/70-seeq.md new file mode 100644 index 0000000000..0bdf58955d --- /dev/null +++ b/docs/zh/20-third-party/70-seeq.md @@ -0,0 +1,437 @@ +--- +sidebar_label: Seeq +title: Seeq +description: 如何使用 Seeq 和 TDengine 进行时序数据分析 +--- + +# 如何使用 Seeq 和 TDengine 进行时序数据分析 + +## 方案介绍 + +Seeq 是制造业和工业互联网(IIOT)高级分析软件。Seeq 支持在工艺制造组织中使用机器学习创新的新功能。这些功能使组织能够将自己或第三方机器学习算法部署到前线流程工程师和主题专家使用的高级分析应用程序,从而使单个数据科学家的努力扩展到许多前线员工。 + +通过 TDengine Java connector, Seeq 可以轻松支持查询 TDengine 提供的时序数据,并提供数据展现、分析、预测等功能。 + +### Seeq 安装方法 + +从 (Seeq 官网)[https://www.seeq.com/customer-download]下载相关软件,例如 Seeq Server 和 Seeq Data Lab 等。 + +### Seeq Server 安装和启动 + +``` +tar xvzf seeq-server-xxx.tar.gz +cd seeq-server-installer +sudo ./install + +sudo seeq service enable +sudo seeq start +``` + +### Seeq Data Lab Server 安装和启动 + +Seeq Data Lab 需要安装在和 Seeq Server 不同的服务器上,并通过配置和 Seeq Server 互联。详细安装配置指令参见(Seeq 官方文档)[https://support.seeq.com/space/KB/1034059842]。 + +``` +tar xvf seeq-data-lab--64bit-linux.tar.gz +sudo seeq-data-lab-installer/install -f /opt/seeq/seeq-data-lab -g /var/opt/seeq -u seeq +sudo seeq config set Network/DataLab/Hostname localhost +sudo seeq config set Network/DataLab/Port 34231 # the port of the Data Lab server (usually 34231) +sudo seeq config set Network/Hostname # the host IP or URL of the main Seeq Server + +# If the main Seeq server is configured to listen over HTTPS +sudo seeq config set Network/Webserver/SecurePort 443 # the secure port of the main Seeq Server (usually 443) + +# If the main Seeq server is NOT configured to listen over HTTPS +sudo seeq config set Network/Webserver/Port + +#On the main Seeq server, open a Seeq Command Prompt and set the hostname of the Data Lab server: +sudo seeq config set Network/DataLab/Hostname # the host IP (not URL) of the Data Lab server +sudo seeq config set Network/DataLab/Port 34231 # the port of the Data Lab server (usually 34231 +``` + +## TDengine 本地实例安装方法 + +请参考(官网文档)[https://docs.taosdata.com/get-started/package/]。 + +## TDengine Cloud 访问方法 +如果使用 Seeq 连接 TDengine Cloud,请在 https://cloud.taosdata.com 申请帐号并登录查看如何访问 TDengine Cloud。 + +## 如何配置 Seeq 访问 TDengine + +1. 查看 data 存储位置 + +``` +sudo seeq config get Folders/Data +``` + +2. 从 maven.org 下载 TDengine Java connector 包,目前最新版本为(3.2.4)[https://repo1.maven.org/maven2/com/taosdata/jdbc/taos-jdbcdriver/3.2.4/taos-jdbcdriver-3.2.4-dist.jar],并拷贝至 data 存储位置的 plugins\lib 中。 + +3. 重新启动 seeq server + +``` +sudo seeq restart +``` + +4. 输入 License + +使用浏览器访问 ip:34216 并按照说明输入 license。 + +## 使用 Seeq 分析 TDengine 时序数据 + +本章节演示如何使用 Seeq 软件配合 TDengine 进行时序数据分析。 + +### 场景介绍 + +示例场景为一个电力系统,用户每天从电站仪表收集用电量数据,并将其存储在 TDengine 集群中。现在用户想要预测电力消耗将会如何发展,并购买更多设备来支持它。用户电力消耗随着每月订单变化而不同,另外考虑到季节变化,电力消耗量会有所不同。这个城市位于北半球,所以在夏天会使用更多的电力。我们模拟数据来反映这些假定。 + +### 数据 Schema + +``` +CREATE STABLE meters (ts TIMESTAMP, num INT, temperature FLOAT, goods INT) TAGS (device NCHAR(20)); +CREATE TABLE goods (ts1 TIMESTAMP, ts2 TIMESTAMP, goods FLOAT); +``` + +!(Seeq demo schema)[./seeq/seeq-demo-schema.webp] + +### 构造数据方法 + +``` +python mockdata.py +taos -s "insert into power.goods select _wstart, _wstart + 10d, avg(goods) from power.meters interval(10d);" +``` +源代码托管在(github 仓库)[https://github.com/sangshuduo/td-forecasting]。 + +### 使用 Seeq 进行数据分析 + +#### 配置数据源(Data Source) + +使用 Seeq 管理员角色的帐号登录,并新建数据源。 + +- Power + +``` +{ + "QueryDefinitions": [ + { + "Name": "PowerNum", + "Type": "SIGNAL", + "Sql": "SELECT ts, num FROM meters", + "Enabled": true, + "TestMode": false, + "TestQueriesDuringSync": true, + "InProgressCapsulesEnabled": false, + "Variables": null, + "Properties": [ + { + "Name": "Name", + "Value": "Num", + "Sql": null, + "Uom": "string" + }, + { + "Name": "Interpolation Method", + "Value": "linear", + "Sql": null, + "Uom": "string" + }, + { + "Name": "Maximum Interpolation", + "Value": "2day", + "Sql": null, + "Uom": "string" + } + ], + "CapsuleProperties": null + } + ], + "Type": "GENERIC", + "Hostname": null, + "Port": 0, + "DatabaseName": null, + "Username": "root", + "Password": "taosdata", + "InitialSql": null, + "TimeZone": null, + "PrintRows": false, + "UseWindowsAuth": false, + "SqlFetchBatchSize": 100000, + "UseSSL": false, + "JdbcProperties": null, + "GenericDatabaseConfig": { + "DatabaseJdbcUrl": "jdbc:TAOS-RS://127.0.0.1:6041/power?user=root&password=taosdata", + "SqlDriverClassName": "com.taosdata.jdbc.rs.RestfulDriver", + "ResolutionInNanoseconds": 1000, + "ZonedColumnTypes": [] + } +} +``` + +- Goods + +``` +{ + "QueryDefinitions": [ + { + "Name": "PowerGoods", + "Type": "CONDITION", + "Sql": "SELECT ts1, ts2, goods FROM power.goods", + "Enabled": true, + "TestMode": false, + "TestQueriesDuringSync": true, + "InProgressCapsulesEnabled": false, + "Variables": null, + "Properties": [ + { + "Name": "Name", + "Value": "Goods", + "Sql": null, + "Uom": "string" + }, + { + "Name": "Maximum Duration", + "Value": "10days", + "Sql": null, + "Uom": "string" + } + ], + "CapsuleProperties": [ + { + "Name": "goods", + "Value": "${columnResult}", + "Column": "goods", + "Uom": "string" + } + ] + } + ], + "Type": "GENERIC", + "Hostname": null, + "Port": 0, + "DatabaseName": null, + "Username": "root", + "Password": "taosdata", + "InitialSql": null, + "TimeZone": null, + "PrintRows": false, + "UseWindowsAuth": false, + "SqlFetchBatchSize": 100000, + "UseSSL": false, + "JdbcProperties": null, + "GenericDatabaseConfig": { + "DatabaseJdbcUrl": "jdbc:TAOS-RS://127.0.0.1:6041/power?user=root&password=taosdata", + "SqlDriverClassName": "com.taosdata.jdbc.rs.RestfulDriver", + "ResolutionInNanoseconds": 1000, + "ZonedColumnTypes": [] + } +} +``` + +- Temperature + +``` +{ + "QueryDefinitions": [ + { + "Name": "PowerNum", + "Type": "SIGNAL", + "Sql": "SELECT ts, temperature FROM meters", + "Enabled": true, + "TestMode": false, + "TestQueriesDuringSync": true, + "InProgressCapsulesEnabled": false, + "Variables": null, + "Properties": [ + { + "Name": "Name", + "Value": "Temperature", + "Sql": null, + "Uom": "string" + }, + { + "Name": "Interpolation Method", + "Value": "linear", + "Sql": null, + "Uom": "string" + }, + { + "Name": "Maximum Interpolation", + "Value": "2day", + "Sql": null, + "Uom": "string" + } + ], + "CapsuleProperties": null + } + ], + "Type": "GENERIC", + "Hostname": null, + "Port": 0, + "DatabaseName": null, + "Username": "root", + "Password": "taosdata", + "InitialSql": null, + "TimeZone": null, + "PrintRows": false, + "UseWindowsAuth": false, + "SqlFetchBatchSize": 100000, + "UseSSL": false, + "JdbcProperties": null, + "GenericDatabaseConfig": { + "DatabaseJdbcUrl": "jdbc:TAOS-RS://127.0.0.1:6041/power?user=root&password=taosdata", + "SqlDriverClassName": "com.taosdata.jdbc.rs.RestfulDriver", + "ResolutionInNanoseconds": 1000, + "ZonedColumnTypes": [] + } +} +``` + +#### 使用 Seeq Workbench + +登录 Seeq 服务页面并新建 Seeq Workbench,通过选择数据源搜索结果和根据需要选择不同的工具,可以进行数据展现或预测,详细使用方法参见(官方知识库)[https://support.seeq.com/space/KB/146440193/Seeq+Workbench]。 + +!(Seeq Workbench)[./seeq/seeq-demo-workbench.webp] + +#### 用 Seeq Data Lab Server 进行进一步的数据分析 + +登录 Seeq 服务页面并新建 Seeq Data Lab,可以进一步使用 Python 编程或其他机器学习工具进行更复杂的数据挖掘功能。 + +```Python +from seeq import spy +spy.options.compatibility = 189 +import pandas as pd +import matplotlib +import matplotlib.pyplot as plt +import mlforecast +import lightgbm as lgb +from mlforecast.target_transforms import Differences +from sklearn.linear_model import LinearRegression + +ds = spy.search({'ID': "8C91A9C7-B6C2-4E18-AAAF-XXXXXXXXX"}) +print(ds) + +sig = ds.loc[ds['Name'].isin(['Num'])] +print(sig) + +data = spy.pull(sig, start='2015-01-01', end='2022-12-31', grid=None) +print("data.info()") +data.info() +print(data) +#data.plot() + +print("data[Num].info()") +data['Num'].info() +da = data['Num'].index.tolist() +#print(da) + +li = data['Num'].tolist() +#print(li) + +data2 = pd.DataFrame() +data2['ds'] = da +print('1st data2 ds info()') +data2['ds'].info() + +#data2['ds'] = pd.to_datetime(data2['ds']).to_timestamp() +data2['ds'] = pd.to_datetime(data2['ds']).astype('int64') +data2['y'] = li +print('2nd data2 ds info()') +data2['ds'].info() +print(data2) + +data2.insert(0, column = "unique_id", value="unique_id") + +print("Forecasting ...") + +forecast = mlforecast.MLForecast( + models = lgb.LGBMRegressor(), + freq = 1, + lags=[365], + target_transforms=[Differences([365])], +) + +forecast.fit(data2) +predicts = forecast.predict(365) + +pd.concat([data2, predicts]).set_index("ds").plot(title = "current data with forecast") +plt.show() +``` + +运行程序输出结果: + +!(Seeq forecast result)[./seeq/seeq-forecast-result.webp] + +### 配置 Seeq 数据源连接 TDengine Cloud + +配置 Seeq 数据源连接 TDengine Cloud 和连接 TDengine 本地安装实例没有本质的不同,只要登录 TDengine Cloud 后选择“编程 - Java”并拷贝带 token 字符串的 JDBC 填写为 Seeq Data Source 的 DatabaseJdbcUrl 值。 +注意使用 TDengine Cloud 时 SQL 命令中需要指定数据库名称。 + +#### 用 TDengine Cloud 作为数据源的配置内容示例: + +``` +{ + "QueryDefinitions": [ + { + "Name": "CloudVoltage", + "Type": "SIGNAL", + "Sql": "SELECT ts, voltage FROM test.meters", + "Enabled": true, + "TestMode": false, + "TestQueriesDuringSync": true, + "InProgressCapsulesEnabled": false, + "Variables": null, + "Properties": [ + { + "Name": "Name", + "Value": "Voltage", + "Sql": null, + "Uom": "string" + }, + { + "Name": "Interpolation Method", + "Value": "linear", + "Sql": null, + "Uom": "string" + }, + { + "Name": "Maximum Interpolation", + "Value": "2day", + "Sql": null, + "Uom": "string" + } + ], + "CapsuleProperties": null + } + ], + "Type": "GENERIC", + "Hostname": null, + "Port": 0, + "DatabaseName": null, + "Username": "root", + "Password": "taosdata", + "InitialSql": null, + "TimeZone": null, + "PrintRows": false, + "UseWindowsAuth": false, + "SqlFetchBatchSize": 100000, + "UseSSL": false, + "JdbcProperties": null, + "GenericDatabaseConfig": { + "DatabaseJdbcUrl": "jdbc:TAOS-RS://gw.cloud.taosdata.com?useSSL=true&token=41ac9d61d641b6b334e8b76f45f5a8XXXXXXXXXX", + "SqlDriverClassName": "com.taosdata.jdbc.rs.RestfulDriver", + "ResolutionInNanoseconds": 1000, + "ZonedColumnTypes": [] + } +} +``` + +#### TDengine Cloud 作为数据源的 Seeq Workbench 界面示例 + +!(Seeq workbench with TDengine cloud)[./seeq/seeq-workbench-with-tdengine-cloud.webp] + +## 方案总结 + +通过集成Seeq和TDengine,可以充分利用TDengine高效的存储和查询性能,同时也可以受益于Seeq提供给用户的强大数据可视化和分析功能。 + +这种集成使用户能够充分利用TDengine的高性能时序数据存储和检索,确保高效处理大量数据。同时,Seeq提供高级分析功能,如数据可视化、异常检测、相关性分析和预测建模,使用户能够获得有价值的洞察并基于数据进行决策。 + +综合来看,Seeq和TDengine共同为制造业、工业物联网和电力系统等各行各业的时序数据分析提供了综合解决方案。高效数据存储和先进的分析相结合,赋予用户充分发挥时序数据潜力的能力,推动运营改进,并支持预测和规划分析应用。 diff --git a/docs/zh/20-third-party/seeq/seeq-demo-schema.webp b/docs/zh/20-third-party/seeq/seeq-demo-schema.webp new file mode 100644 index 0000000000000000000000000000000000000000..3f603749ad2ed4cd0e584e88a58a2410953b3e62 GIT binary patch literal 13174 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 Pq4}etDFJV(=>Px#frSQK literal 0 HcmV?d00001 diff --git a/docs/zh/20-third-party/seeq/seeq-forecast-result.webp b/docs/zh/20-third-party/seeq/seeq-forecast-result.webp new file mode 100644 index 0000000000000000000000000000000000000000..13144207eb4bebe6a4c5915de5c795db6b30aee9 GIT binary patch literal 26598 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 TX0&z2v8^TD2wVUF00000CWhTZ literal 0 HcmV?d00001 diff --git a/docs/zh/20-third-party/seeq/seeq-workbench-with-tdengine-cloud.webp b/docs/zh/20-third-party/seeq/seeq-workbench-with-tdengine-cloud.webp new file mode 100644 index 0000000000000000000000000000000000000000..f486c3ea29790c3b48dfb017a0295a281488d319 GIT binary patch literal 48280 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
  • H5PdZ)D}>wj%ZsaRQaX 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 T9Y6p80000fumAu600000zlH=d literal 0 HcmV?d00001 From 465a0561f9d42be7c3ef24f110aa6c4cb9851726 Mon Sep 17 00:00:00 2001 From: dingbo8128 Date: Tue, 29 Aug 2023 11:49:29 +0800 Subject: [PATCH 099/120] docs: fix duplicate name index.md --- docs/zh/12-taos-sql/{27-index.md => 27-indexing.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/zh/12-taos-sql/{27-index.md => 27-indexing.md} (100%) diff --git a/docs/zh/12-taos-sql/27-index.md b/docs/zh/12-taos-sql/27-indexing.md similarity index 100% rename from docs/zh/12-taos-sql/27-index.md rename to docs/zh/12-taos-sql/27-indexing.md From db2209189d1e2ebcadc52e7bcf4cba9237457b59 Mon Sep 17 00:00:00 2001 From: dingbo8128 Date: Tue, 29 Aug 2023 11:53:31 +0800 Subject: [PATCH 100/120] docs: remove space --- docs/zh/12-taos-sql/27-indexing.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/zh/12-taos-sql/27-indexing.md b/docs/zh/12-taos-sql/27-indexing.md index f934672bd2..cf8cac1bed 100644 --- a/docs/zh/12-taos-sql/27-indexing.md +++ b/docs/zh/12-taos-sql/27-indexing.md @@ -21,7 +21,8 @@ functions: function [, function] ... ``` ### tag 索引 - [tag 索引] (../tag-index) + + [tag 索引](../tag-index) ### SMA 索引 From 5d04461b79fc76cbda5367e15a758b0c450e5e3b Mon Sep 17 00:00:00 2001 From: wade zhang <95411902+gccgdb1234@users.noreply.github.com> Date: Tue, 29 Aug 2023 12:01:32 +0800 Subject: [PATCH 101/120] Update 09-storage.md --- docs/zh/17-operation/09-storage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/17-operation/09-storage.md b/docs/zh/17-operation/09-storage.md index ca3f3af49b..185b2c40ec 100644 --- a/docs/zh/17-operation/09-storage.md +++ b/docs/zh/17-operation/09-storage.md @@ -53,4 +53,4 @@ dataDir /mnt/data6 2 0 ## 同级挂载点选择策略 -一般情况下,当 TDengine 要从同级挂载点中选择一个用于生成新的数据文件时,采用 round robin 策略进行选择。但现实中有可能每个磁盘的容量不相同,或者容量相同但写入的数据量不相同,这就导致会出现每个磁盘上的可用空间不均衡,在实际进行选择时有可能会选择到一个剩余空间已经很小的磁盘。为了解决这个问题,从 3.1.1.0 开始引入了一个新的配置 `minDiskFreeSize`,当某块磁盘上的可用空间小于等于这个阈值时,该磁盘将不再被选择用于生成新的数据文件。该配置项的单位为字节,其值应该大于 `minimalDataDirGB` \ No newline at end of file +一般情况下,当 TDengine 要从同级挂载点中选择一个用于生成新的数据文件时,采用 round robin 策略进行选择。但现实中有可能每个磁盘的容量不相同,或者容量相同但写入的数据量不相同,这就导致会出现每个磁盘上的可用空间不均衡,在实际进行选择时有可能会选择到一个剩余空间已经很小的磁盘。为了解决这个问题,从 3.1.1.0 开始引入了一个新的配置 `minDiskFreeSize`,当某块磁盘上的可用空间小于等于这个阈值时,该磁盘将不再被选择用于生成新的数据文件。该配置项的单位为字节,其值应该大于 2GB,即会跳过可用空间小于 2GB 的挂载点。 From 5e5b7a3328313aecf138f3f02136e9f06caac835 Mon Sep 17 00:00:00 2001 From: Benguang Zhao Date: Tue, 29 Aug 2023 12:07:45 +0800 Subject: [PATCH 102/120] fix: reserve enough space of show Obj rows for vnodes of each vgroup in mndRetrieveVnodes --- source/dnode/mnode/impl/src/mndVgroup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/dnode/mnode/impl/src/mndVgroup.c b/source/dnode/mnode/impl/src/mndVgroup.c index ff621198ff..c4a40871de 100644 --- a/source/dnode/mnode/impl/src/mndVgroup.c +++ b/source/dnode/mnode/impl/src/mndVgroup.c @@ -1091,7 +1091,7 @@ static int32_t mndRetrieveVnodes(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pB int32_t cols = 0; int64_t curMs = taosGetTimestampMs(); - while (numOfRows < rows) { + while (numOfRows < rows - TSDB_MAX_REPLICA) { pShow->pIter = sdbFetch(pSdb, SDB_VGROUP, pShow->pIter, (void **)&pVgroup); if (pShow->pIter == NULL) break; From fb5b34184fcee0f3b6d8f448c651a95fd79a512d Mon Sep 17 00:00:00 2001 From: wade zhang <95411902+gccgdb1234@users.noreply.github.com> Date: Tue, 29 Aug 2023 12:20:42 +0800 Subject: [PATCH 103/120] Update 09-storage.md --- docs/zh/17-operation/09-storage.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/zh/17-operation/09-storage.md b/docs/zh/17-operation/09-storage.md index 5c06c3cc99..185b2c40ec 100644 --- a/docs/zh/17-operation/09-storage.md +++ b/docs/zh/17-operation/09-storage.md @@ -53,8 +53,4 @@ dataDir /mnt/data6 2 0 ## 同级挂载点选择策略 -<<<<<<< HEAD 一般情况下,当 TDengine 要从同级挂载点中选择一个用于生成新的数据文件时,采用 round robin 策略进行选择。但现实中有可能每个磁盘的容量不相同,或者容量相同但写入的数据量不相同,这就导致会出现每个磁盘上的可用空间不均衡,在实际进行选择时有可能会选择到一个剩余空间已经很小的磁盘。为了解决这个问题,从 3.1.1.0 开始引入了一个新的配置 `minDiskFreeSize`,当某块磁盘上的可用空间小于等于这个阈值时,该磁盘将不再被选择用于生成新的数据文件。该配置项的单位为字节,其值应该大于 2GB,即会跳过可用空间小于 2GB 的挂载点。 -======= -一般情况下,当 TDengine 要从同级挂载点中选择一个用于生成新的数据文件时,采用 round robin 策略进行选择。但现实中有可能每个磁盘的容量不相同,或者容量相同但写入的数据量不相同,这就导致会出现每个磁盘上的可用空间不均衡,在实际进行选择时有可能会选择到一个剩余空间已经很小的磁盘。为了解决这个问题,从 3.1.1.0 开始引入了一个新的配置 `minDiskFreeSize`,当某块磁盘上的可用空间小于等于这个阈值时,该磁盘将不再被选择用于生成新的数据文件。该配置项的单位为字节,其值应该大于 2GB,即会跳过可用空间小于 2GB 的挂载点。 ->>>>>>> 5d04461b79fc76cbda5367e15a758b0c450e5e3b From 34c783285008f8f19738d7ad281ed57e897dffca Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Tue, 29 Aug 2023 15:24:12 +0800 Subject: [PATCH 104/120] feat:support stmt/tmq json in varbinary type --- examples/c/prepare.c | 21 +- examples/c/tmq.c | 2 +- source/libs/parser/src/parTranslater.c | 2 +- source/libs/parser/src/parser.c | 10 +- source/libs/scalar/src/sclfunc.c | 1 + tests/system-test/2-query/varbinary.py | 20 +- utils/test/c/varbinary_test.c | 379 ++++++++++++++++++++++++- 7 files changed, 409 insertions(+), 26 deletions(-) diff --git a/examples/c/prepare.c b/examples/c/prepare.c index 4f05b6fb4c..fce12885c5 100644 --- a/examples/c/prepare.c +++ b/examples/c/prepare.c @@ -43,7 +43,7 @@ int main(int argc, char *argv[]) taos_free_result(result); // create table - const char* sql = "create table m1 (ts timestamp, b bool, v1 tinyint, v2 smallint, v4 int, v8 bigint, f4 float, f8 double, bin binary(40), blob nchar(10))"; + const char* sql = "create table m1 (ts timestamp, b bool, v1 tinyint, v2 smallint, v4 int, v8 bigint, f4 float, f8 double, bin binary(40), blob nchar(10), varbin varbinary(16))"; result = taos_query(taos, sql); code = taos_errno(result); if (code != 0) { @@ -68,6 +68,7 @@ int main(int argc, char *argv[]) double f8; char bin[40]; char blob[80]; + int8_t varbin[16]; } v = {0}; int32_t boolLen = sizeof(int8_t); @@ -80,7 +81,7 @@ int main(int argc, char *argv[]) int32_t ncharLen = 30; stmt = taos_stmt_init(taos); - TAOS_MULTI_BIND params[10]; + TAOS_MULTI_BIND params[11]; params[0].buffer_type = TSDB_DATA_TYPE_TIMESTAMP; params[0].buffer_length = sizeof(v.ts); params[0].buffer = &v.ts; @@ -152,9 +153,19 @@ int main(int argc, char *argv[]) params[9].is_null = NULL; params[9].num = 1; + int8_t tmp[16] = {'a', 0, 1, 13, '1'}; + int32_t vbinLen = 5; + memcpy(v.varbin, tmp, sizeof(v.varbin)); + params[10].buffer_type = TSDB_DATA_TYPE_VARBINARY; + params[10].buffer_length = sizeof(v.varbin); + params[10].buffer = v.varbin; + params[10].length = &vbinLen; + params[10].is_null = NULL; + params[10].num = 1; + char is_null = 1; - sql = "insert into m1 values(?,?,?,?,?,?,?,?,?,?)"; + sql = "insert into m1 values(?,?,?,?,?,?,?,?,?,?,?)"; code = taos_stmt_prepare(stmt, sql, 0); if (code != 0){ printf("failed to execute taos_stmt_prepare. code:0x%x\n", code); @@ -162,7 +173,7 @@ int main(int argc, char *argv[]) v.ts = 1591060628000; for (int i = 0; i < 10; ++i) { v.ts += 1; - for (int j = 1; j < 10; ++j) { + for (int j = 1; j < 11; ++j) { params[j].is_null = ((i == j) ? &is_null : 0); } v.b = (int8_t)i % 2; @@ -187,7 +198,7 @@ int main(int argc, char *argv[]) // query the records stmt = taos_stmt_init(taos); - taos_stmt_prepare(stmt, "SELECT * FROM m1 WHERE v1 > ? AND v2 < ?", 0); + taos_stmt_prepare(stmt, "SELECT * FROM m1 WHERE varbin > ? AND v2 < ?", 0); v.v1 = 5; v.v2 = 15; taos_stmt_bind_param(stmt, params + 2); diff --git a/examples/c/tmq.c b/examples/c/tmq.c index 136c54b874..3769383bba 100644 --- a/examples/c/tmq.c +++ b/examples/c/tmq.c @@ -280,7 +280,7 @@ void consume_repeatly(tmq_t* tmq) { code = tmq_offset_seek(tmq, topic_name, p->vgId, p->begin); if (code != 0) { - fprintf(stderr, "failed to seek to %ld, reason:%s", p->begin, tmq_err2str(code)); + fprintf(stderr, "failed to seek to %lld, reason:%s", p->begin, tmq_err2str(code)); } } diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index ad3cfe32c1..7e80092ea2 100644 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -1260,7 +1260,7 @@ static EDealRes translateNormalValue(STranslateContext* pCxt, SValueNode* pVal, if(isHexChar) taosMemoryFree(data); return generateDealNodeErrMsg(pCxt, TSDB_CODE_OUT_OF_MEMORY); } - varDataSetLen(pVal->datum.p, size + VARSTR_HEADER_SIZE); + varDataSetLen(pVal->datum.p, size); memcpy(varDataVal(pVal->datum.p), data, size); if(isHexChar) taosMemoryFree(data); break; diff --git a/source/libs/parser/src/parser.c b/source/libs/parser/src/parser.c index 10fda8741b..4034597eed 100644 --- a/source/libs/parser/src/parser.c +++ b/source/libs/parser/src/parser.c @@ -97,8 +97,16 @@ static int32_t setValueByBindParam(SValueNode* pVal, TAOS_MULTI_BIND* pParam) { pVal->node.resType.bytes = inputSize; switch (pParam->buffer_type) { - case TSDB_DATA_TYPE_VARCHAR: case TSDB_DATA_TYPE_VARBINARY: + pVal->datum.p = taosMemoryCalloc(1, pVal->node.resType.bytes + VARSTR_HEADER_SIZE + 1); + if (NULL == pVal->datum.p) { + return TSDB_CODE_OUT_OF_MEMORY; + } + varDataSetLen(pVal->datum.p, pVal->node.resType.bytes); + memcpy(varDataVal(pVal->datum.p), pParam->buffer, pVal->node.resType.bytes); + pVal->node.resType.bytes += VARSTR_HEADER_SIZE; + break; + case TSDB_DATA_TYPE_VARCHAR: case TSDB_DATA_TYPE_GEOMETRY: pVal->datum.p = taosMemoryCalloc(1, pVal->node.resType.bytes + VARSTR_HEADER_SIZE + 1); if (NULL == pVal->datum.p) { diff --git a/source/libs/scalar/src/sclfunc.c b/source/libs/scalar/src/sclfunc.c index 8c6f9a71b2..0b7074ee3d 100644 --- a/source/libs/scalar/src/sclfunc.c +++ b/source/libs/scalar/src/sclfunc.c @@ -973,6 +973,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp code = TSDB_CODE_FUNC_FUNTION_PARA_TYPE; goto _end; } + break; } case TSDB_DATA_TYPE_NCHAR: { int32_t outputCharLen = (outputLen - VARSTR_HEADER_SIZE) / TSDB_NCHAR_SIZE; diff --git a/tests/system-test/2-query/varbinary.py b/tests/system-test/2-query/varbinary.py index 8f7032cdc3..8f8021c8ad 100644 --- a/tests/system-test/2-query/varbinary.py +++ b/tests/system-test/2-query/varbinary.py @@ -23,16 +23,16 @@ class TDTestCase: if ret != 0: tdLog.exit("varbinary_test ret != 0") - tdSql.execute(f" create database test") - tdSql.execute(f" use test ") - tdSql.execute(f" create stable stb (ts timestamp, c1 nchar(32), c2 varbinary(16), c3 float) tags (t1 int, t2 binary(8), t3 varbinary(8))") - - tdSql.query(f"desc stb") - tdSql.checkRows(7) - tdSql.checkData(2, 1, 'VARBINARY') - tdSql.checkData(2, 2, 16) - tdSql.checkData(6, 1, 'VARBINARY') - tdSql.checkData(6, 2, 8) + # tdSql.execute(f" create database test") + # tdSql.execute(f" use test ") + # tdSql.execute(f" create stable stb (ts timestamp, c1 nchar(32), c2 varbinary(16), c3 float) tags (t1 int, t2 binary(8), t3 varbinary(8))") + # + # tdSql.query(f"desc stb") + # tdSql.checkRows(7) + # tdSql.checkData(2, 1, 'VARBINARY') + # tdSql.checkData(2, 2, 16) + # tdSql.checkData(6, 1, 'VARBINARY') + # tdSql.checkData(6, 2, 8) # tdSql.execute(f" insert into tb1 using stb tags (1, 'tb1_bin1', 'vart1') values (now, 'nchar1', 'varc1', 0.3)") # tdSql.execute(f" insert into tb1 values (now + 1s, 'nchar2', null, 0.4)") diff --git a/utils/test/c/varbinary_test.c b/utils/test/c/varbinary_test.c index bd73cbc0fc..6792a42fff 100644 --- a/utils/test/c/varbinary_test.c +++ b/utils/test/c/varbinary_test.c @@ -32,7 +32,7 @@ break;\ }\ } -int varbinary_test() { +void varbinary_sql_test() { TAOS *taos = taos_connect("localhost", "root", "taosdata", NULL, 0); TAOS_RES *pRes = taos_query(taos, "drop database if exists varbinary_db"); @@ -97,7 +97,6 @@ int varbinary_test() { ASSERT(taos_errno(pRes) != 0); taos_free_result(pRes); - // test error pRes = taos_query(taos, "select * from tb1 where c2 >= 0x8de6"); ASSERT(taos_errno(pRes) != 0); @@ -208,8 +207,19 @@ int varbinary_test() { ASSERT(taos_errno(pRes) == 0); taos_free_result(pRes); - pRes = taos_query(taos, "select cast(t2 as varbinary(16)) from stb"); - ASSERT(taos_errno(pRes) == 0); + pRes = taos_query(taos, "select cast(t2 as varbinary(16)) from stb order by ts"); + while ((row = taos_fetch_row(pRes)) != NULL) { + int32_t* length = taos_fetch_lengths(pRes); + void* data = NULL; + uint32_t size = 0; + if(taosAscii2Hex(row[0], length[0], &data, &size) < 0){ + ASSERT(0); + } + + ASSERT(memcmp(data, "\\x7462315F62696E31", size) == 0); + taosMemoryFree(data); + break; + } taos_free_result(pRes); int numRows = 0; @@ -254,6 +264,20 @@ int varbinary_test() { ASSERT(numRows == 3); taos_free_result(pRes); + pRes = taos_query(taos, "select cast('1' as varbinary(8))"); + while ((row = taos_fetch_row(pRes)) != NULL) { + int32_t* length = taos_fetch_lengths(pRes); + void* data = NULL; + uint32_t size = 0; + if(taosAscii2Hex(row[0], length[0], &data, &size) < 0){ + ASSERT(0); + } + + ASSERT(memcmp(data, "\\x31", size) == 0); + taosMemoryFree(data); + } + taos_free_result(pRes); + pRes = taos_query(taos, "select ts,c2 from stb order by c2"); rowIndex = 0; while ((row = taos_fetch_row(pRes)) != NULL) { @@ -294,17 +318,356 @@ int varbinary_test() { rowIndex++; } - printf("%s result1:%s\n", __FUNCTION__, taos_errstr(pRes)); + printf("%s result %s\n", __FUNCTION__, taos_errstr(pRes)); taos_free_result(pRes); taos_close(taos); +} - return code; +void varbinary_stmt_test(){ + TAOS *taos; + TAOS_RES *result; + int code; + TAOS_STMT *stmt; + + taos = taos_connect("localhost", "root", "taosdata", NULL, 0); + if (taos == NULL) { + printf("failed to connect to db, reason:%s\n", taos_errstr(taos)); + ASSERT(0); + } + + result = taos_query(taos, "drop database demo"); + taos_free_result(result); + + result = taos_query(taos, "create database demo"); + code = taos_errno(result); + if (code != 0) { + printf("failed to create database, reason:%s\n", taos_errstr(result)); + taos_free_result(result); + ASSERT(0); + } + taos_free_result(result); + + result = taos_query(taos, "use demo"); + taos_free_result(result); + + // create table + const char* sql = "create table m1 (ts timestamp, b bool, varbin varbinary(16))"; + result = taos_query(taos, sql); + code = taos_errno(result); + if (code != 0) { + printf("failed to create table, reason:%s\n", taos_errstr(result)); + taos_free_result(result); + ASSERT(0); + } + taos_free_result(result); + struct { + int64_t ts; + int8_t b; + int8_t varbin[16]; + } v = {0}; + + int32_t boolLen = sizeof(int8_t); + int32_t bintLen = sizeof(int64_t); + int32_t vbinLen = 5; + + stmt = taos_stmt_init(taos); + TAOS_MULTI_BIND params[3]; + params[0].buffer_type = TSDB_DATA_TYPE_TIMESTAMP; + params[0].buffer_length = sizeof(v.ts); + params[0].buffer = &v.ts; + params[0].length = &bintLen; + params[0].is_null = NULL; + params[0].num = 1; + + params[1].buffer_type = TSDB_DATA_TYPE_BOOL; + params[1].buffer_length = sizeof(v.b); + params[1].buffer = &v.b; + params[1].length = &boolLen; + params[1].is_null = NULL; + params[1].num = 1; + + params[2].buffer_type = TSDB_DATA_TYPE_VARBINARY; + params[2].buffer_length = sizeof(v.varbin); + params[2].buffer = v.varbin; + params[2].length = &vbinLen; + params[2].is_null = NULL; + params[2].num = 1; + + char is_null = 1; + + sql = "insert into m1 values(?,?,?)"; + code = taos_stmt_prepare(stmt, sql, 0); + if (code != 0){ + printf("failed to execute taos_stmt_prepare. code:0x%x\n", code); + } + v.ts = 1591060628000; + for (int i = 0; i < 10; ++i) { + v.ts += 1; + for (int j = 1; j < 11; ++j) { + params[j].is_null = ((i == j) ? &is_null : 0); + } + v.b = (int8_t)i % 2; + + for (int j = 0; j < vbinLen; ++j) { + v.varbin[j] = i + j; + } + + taos_stmt_bind_param(stmt, params); + taos_stmt_add_batch(stmt); + } + if (taos_stmt_execute(stmt) != 0) { + printf("failed to execute insert statement.\n"); + ASSERT(0); + } + taos_stmt_close(stmt); + + // query the records + stmt = taos_stmt_init(taos); + taos_stmt_prepare(stmt, "SELECT varbin FROM m1 WHERE varbin = ?", 0); + for (int j = 0; j < vbinLen; ++j) { + v.varbin[j] = j; + } + taos_stmt_bind_param(stmt, params + 2); + if (taos_stmt_execute(stmt) != 0) { + printf("failed to execute select statement.\n"); + ASSERT(0); + } + + result = taos_stmt_use_result(stmt); + + TAOS_ROW row; + int rows = 0; + int num_fields = taos_num_fields(result); + TAOS_FIELD *fields = taos_fetch_fields(result); + + // fetch the records row by row + while ((row = taos_fetch_row(result))) { + char temp[256] = {0}; + rows++; + taos_print_row(temp, row, fields, num_fields); + ASSERT(strcmp(temp, "\\x0001020304") == 0); + } + ASSERT (rows == 1); + + taos_free_result(result); + taos_stmt_close(stmt); + + // query the records + stmt = taos_stmt_init(taos); + taos_stmt_prepare(stmt, "SELECT varbin FROM m1 WHERE varbin = ?", 0); + + char tmp[16] = "\\x090a0b0c0d"; + vbinLen = strlen(tmp); + params[2].buffer_type = TSDB_DATA_TYPE_VARCHAR; + params[2].buffer_length = sizeof(v.varbin); + params[2].buffer = tmp; + params[2].length = &vbinLen; + taos_stmt_bind_param(stmt, params + 2); + if (taos_stmt_execute(stmt) != 0) { + printf("failed to execute select statement.\n"); + ASSERT(0); + } + + result = taos_stmt_use_result(stmt); + + rows = 0; + num_fields = taos_num_fields(result); + fields = taos_fetch_fields(result); + + // fetch the records row by row + while ((row = taos_fetch_row(result))) { + char temp[256] = {0}; + rows++; + taos_print_row(temp, row, fields, num_fields); + ASSERT(strcmp(temp, "\\x090A0B0C0D") == 0); + } + ASSERT (rows == 1); + + taos_free_result(result); + + taos_stmt_close(stmt); + + printf("%s result success\n", __FUNCTION__); +} + +tmq_t* build_consumer() { + tmq_conf_t* conf = tmq_conf_new(); + tmq_conf_set(conf, "group.id", "tg2"); + tmq_conf_set(conf, "client.id", "my app 1"); + tmq_conf_set(conf, "td.connect.user", "root"); + tmq_conf_set(conf, "td.connect.pass", "taosdata"); + tmq_conf_set(conf, "msg.with.table.name", "true"); + tmq_conf_set(conf, "enable.auto.commit", "true"); + + tmq_t* tmq = tmq_consumer_new(conf, NULL, 0); + assert(tmq); + tmq_conf_destroy(conf); + return tmq; +} + +void varbinary_tmq_test(){ + + // build database + + TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT(pConn != NULL); + + TAOS_RES *pRes = taos_query(pConn, "drop database if exists abc"); + if (taos_errno(pRes) != 0) { + printf("error in drop db, reason:%s\n", taos_errstr(pRes)); + ASSERT(0); + } + taos_free_result(pRes); + + pRes = taos_query(pConn, "create database if not exists abc vgroups 1 wal_retention_period 3600"); + if (taos_errno(pRes) != 0) { + printf("error in create db, reason:%s\n", taos_errstr(pRes)); + ASSERT(0); + } + taos_free_result(pRes); + + pRes = taos_query(pConn, "use abc"); + if (taos_errno(pRes) != 0) { + printf("error in use db, reason:%s\n", taos_errstr(pRes)); + ASSERT(0); + } + taos_free_result(pRes); + + pRes = taos_query(pConn, "create stable if not exists st1 (ts timestamp, c2 varbinary(16)) tags(t1 int, t2 varbinary(8))"); + if (taos_errno(pRes) != 0) { + printf("failed to create super table st1, reason:%s\n", taos_errstr(pRes)); + ASSERT(0); + } + taos_free_result(pRes); + + pRes = taos_query(pConn, "create table if not exists ct0 using st1 tags(1000, '\\x3f89')"); + if (taos_errno(pRes) != 0) { + printf("failed to create child table tu1, reason:%s\n", taos_errstr(pRes)); + ASSERT(0); + } + taos_free_result(pRes); + + pRes = taos_query(pConn, "insert into ct0 values(1626006833400, 'hello')"); + if (taos_errno(pRes) != 0) { + printf("failed to insert into ct0, reason:%s\n", taos_errstr(pRes)); + ASSERT(0); + } + taos_free_result(pRes); + + pRes = taos_query(pConn, "alter table st1 modify column c2 varbinary(64)"); + if (taos_errno(pRes) != 0) { + printf("failed to alter super table st1, reason:%s\n", taos_errstr(pRes)); + ASSERT(0); + } + taos_free_result(pRes); + + pRes = taos_query(pConn, "alter table st1 add tag t3 varbinary(64)"); + if (taos_errno(pRes) != 0) { + printf("failed to alter super table st1, reason:%s\n", taos_errstr(pRes)); + ASSERT(0); + } + taos_free_result(pRes); + + pRes = taos_query(pConn, "alter table ct0 set tag t2='894'"); + if (taos_errno(pRes) != 0) { + printf("failed to slter child table ct3, reason:%s\n", taos_errstr(pRes)); + ASSERT(0); + } + taos_free_result(pRes); + + pRes = taos_query(pConn, "create table tb1 (ts timestamp, c1 varbinary(8))"); + if (taos_errno(pRes) != 0) { + printf("failed to create super table st1, reason:%s\n", taos_errstr(pRes)); + ASSERT(0); + } + taos_free_result(pRes); + + pRes = taos_query(pConn, "alter table tb1 add column c2 varbinary(8)"); + if (taos_errno(pRes) != 0) { + printf("failed to create super table st1, reason:%s\n", taos_errstr(pRes)); + ASSERT(0); + } + taos_free_result(pRes); + + // create topic + pRes = taos_query(pConn, "create topic topic_db with meta as database abc"); + if (taos_errno(pRes) != 0) { + printf("failed to create topic topic_db, reason:%s\n", taos_errstr(pRes)); + ASSERT(0); + } + taos_free_result(pRes); + + // build consumer + tmq_t* tmq = build_consumer(); + tmq_list_t* topic_list = tmq_list_new(); + tmq_list_append(topic_list, "topic_db"); + + int32_t code = tmq_subscribe(tmq, topic_list); + ASSERT(code == 0); + + int32_t cnt = 0; + while (1) { + TAOS_RES* tmqmessage = tmq_consumer_poll(tmq, 1000); + if (tmqmessage) { + if (tmq_get_res_type(tmqmessage) == TMQ_RES_TABLE_META || tmq_get_res_type(tmqmessage) == TMQ_RES_METADATA) { + char* result = tmq_get_json_meta(tmqmessage); +// if (result) { +// printf("meta result: %s\n", result); +// } + switch (cnt) { + case 0: + ASSERT(strcmp(result, "{\"type\":\"create\",\"tableType\":\"super\",\"tableName\":\"st1\",\"columns\":[{\"name\":\"ts\",\"type\":9},{\"name\":\"c2\",\"type\":16,\"length\":16}],\"tags\":[{\"name\":\"t1\",\"type\":4},{\"name\":\"t2\",\"type\":16,\"length\":8}]}") == 0); + break; + case 1: + ASSERT(strcmp(result, "{\"type\":\"create\",\"tableType\":\"child\",\"tableName\":\"ct0\",\"using\":\"st1\",\"tagNum\":2,\"tags\":[{\"name\":\"t1\",\"type\":4,\"value\":1000},{\"name\":\"t2\",\"type\":16,\"value\":\"\\\"\\\\x3F89\\\"\"}],\"createList\":[]}") == 0); + break; + case 2: + ASSERT(strcmp(result, "{\"type\":\"alter\",\"tableType\":\"super\",\"tableName\":\"st1\",\"alterType\":7,\"colName\":\"c2\",\"colType\":16,\"colLength\":64}") == 0); + break; + case 3: + ASSERT(strcmp(result, "{\"type\":\"alter\",\"tableType\":\"super\",\"tableName\":\"st1\",\"alterType\":1,\"colName\":\"t3\",\"colType\":16,\"colLength\":64}") == 0); + break; + case 4: + ASSERT(strcmp(result, "{\"type\":\"alter\",\"tableType\":\"child\",\"tableName\":\"ct0\",\"alterType\":4,\"colName\":\"t2\",\"colValue\":\"\\\"\\\\x383934\\\"\",\"colValueNull\":false}") == 0); + break; + case 5: + ASSERT(strcmp(result, "{\"type\":\"create\",\"tableType\":\"normal\",\"tableName\":\"tb1\",\"columns\":[{\"name\":\"ts\",\"type\":9},{\"name\":\"c1\",\"type\":16,\"length\":8}],\"tags\":[]}") == 0); + break; + case 6: + ASSERT(strcmp(result, "{\"type\":\"alter\",\"tableType\":\"normal\",\"tableName\":\"tb1\",\"alterType\":5,\"colName\":\"c2\",\"colType\":16,\"colLength\":8}") == 0); + break; + default: + break; + } + cnt++; + tmq_free_json_meta(result); + } + taos_free_result(tmqmessage); + } else { + break; + } + } + + code = tmq_consumer_close(tmq); + ASSERT(code == 0); + + tmq_list_destroy(topic_list); + + pRes = taos_query(pConn, "drop topic if exists topic_db"); + if (taos_errno(pRes) != 0) { + printf("error in drop topic, reason:%s\n", taos_errstr(pRes)); + ASSERT(0); + } + taos_free_result(pRes); + printf("%s result success\n", __FUNCTION__); } int main(int argc, char *argv[]) { int ret = 0; - ret = varbinary_test(); - ASSERT(!ret); + +// varbinary_tmq_test(); +// varbinary_stmt_test(); + varbinary_sql_test(); return ret; } From a49167dcae0350f2f109a4d438375001ea79a69d Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Tue, 29 Aug 2023 15:40:05 +0800 Subject: [PATCH 105/120] fix:memory leak --- source/libs/scalar/src/scalar.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/source/libs/scalar/src/scalar.c b/source/libs/scalar/src/scalar.c index b3f67f2c9d..cc6be68c85 100644 --- a/source/libs/scalar/src/scalar.c +++ b/source/libs/scalar/src/scalar.c @@ -1193,6 +1193,7 @@ EDealRes sclRewriteFunction(SNode **pNode, SScalarCtx *ctx) { ctx->code = sclExecFunction(node, ctx, &output); if (ctx->code) { + sclFreeParam(&output); return DEAL_RES_ERROR; } @@ -1241,10 +1242,12 @@ EDealRes sclRewriteLogic(SNode **pNode, SScalarCtx *ctx) { SScalarParam output = {0}; ctx->code = sclExecLogic(node, ctx, &output); if (ctx->code) { + sclFreeParam(&output); return DEAL_RES_ERROR; } if (0 == output.numOfRows) { + sclFreeParam(&output); return DEAL_RES_CONTINUE; } @@ -1341,6 +1344,7 @@ EDealRes sclRewriteCaseWhen(SNode **pNode, SScalarCtx *ctx) { SScalarParam output = {0}; ctx->code = sclExecCaseWhen(node, ctx, &output); if (ctx->code) { + sclFreeParam(&output); return DEAL_RES_ERROR; } @@ -1403,11 +1407,13 @@ EDealRes sclWalkFunction(SNode *pNode, SScalarCtx *ctx) { ctx->code = sclExecFunction(node, ctx, &output); if (ctx->code) { + sclFreeParam(&output); return DEAL_RES_ERROR; } if (taosHashPut(ctx->pRes, &pNode, POINTER_BYTES, &output, sizeof(output))) { ctx->code = TSDB_CODE_OUT_OF_MEMORY; + sclFreeParam(&output); return DEAL_RES_ERROR; } @@ -1420,11 +1426,13 @@ EDealRes sclWalkLogic(SNode *pNode, SScalarCtx *ctx) { ctx->code = sclExecLogic(node, ctx, &output); if (ctx->code) { + sclFreeParam(&output); return DEAL_RES_ERROR; } if (taosHashPut(ctx->pRes, &pNode, POINTER_BYTES, &output, sizeof(output))) { ctx->code = TSDB_CODE_OUT_OF_MEMORY; + sclFreeParam(&output); return DEAL_RES_ERROR; } @@ -1443,6 +1451,7 @@ EDealRes sclWalkOperator(SNode *pNode, SScalarCtx *ctx) { if (taosHashPut(ctx->pRes, &pNode, POINTER_BYTES, &output, sizeof(output))) { ctx->code = TSDB_CODE_OUT_OF_MEMORY; + sclFreeParam(&output); return DEAL_RES_ERROR; } From df5f46634b0745a40da0b4d15672035dfa871c17 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Tue, 29 Aug 2023 15:41:45 +0800 Subject: [PATCH 106/120] fix:open test case --- utils/test/c/varbinary_test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/test/c/varbinary_test.c b/utils/test/c/varbinary_test.c index 6792a42fff..bd0f594b28 100644 --- a/utils/test/c/varbinary_test.c +++ b/utils/test/c/varbinary_test.c @@ -666,8 +666,8 @@ void varbinary_tmq_test(){ int main(int argc, char *argv[]) { int ret = 0; -// varbinary_tmq_test(); -// varbinary_stmt_test(); + varbinary_tmq_test(); + varbinary_stmt_test(); varbinary_sql_test(); return ret; } From a6455672c03e79c2b44a609e6c2e30ad812b7fac Mon Sep 17 00:00:00 2001 From: Benguang Zhao Date: Tue, 29 Aug 2023 15:22:29 +0800 Subject: [PATCH 107/120] fix: check completeness of retrieval properly in mndProcessRetrieveSysTableReq --- source/dnode/mnode/impl/src/mndShow.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/dnode/mnode/impl/src/mndShow.c b/source/dnode/mnode/impl/src/mndShow.c index 7d842be7de..2286186ebb 100644 --- a/source/dnode/mnode/impl/src/mndShow.c +++ b/source/dnode/mnode/impl/src/mndShow.c @@ -329,7 +329,7 @@ static int32_t mndProcessRetrieveSysTableReq(SRpcMsg *pReq) { pReq->info.rsp = pRsp; pReq->info.rspLen = size; - if (rowsRead == 0 || ((rowsRead < rowsToRead) && !pShow->restore)) { + if (rowsRead == 0 || mndCheckRetrieveFinished(pShow)) { pRsp->completed = 1; mDebug("show:0x%" PRIx64 ", retrieve completed", pShow->id); mndReleaseShowObj(pShow, true); From af89cb0af5a2ac8364b4fd39ad1ad63eae0eb122 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Tue, 29 Aug 2023 15:58:24 +0800 Subject: [PATCH 108/120] fix:memory error --- examples/c/prepare.c | 2 +- utils/test/c/varbinary_test.c | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/c/prepare.c b/examples/c/prepare.c index fce12885c5..a91153a92f 100644 --- a/examples/c/prepare.c +++ b/examples/c/prepare.c @@ -227,7 +227,7 @@ int main(int argc, char *argv[]) printf("expect two rows, but %d rows are fetched\n", rows); } - taos_free_result(result); +// taos_free_result(result); taos_stmt_close(stmt); return 0; diff --git a/utils/test/c/varbinary_test.c b/utils/test/c/varbinary_test.c index bd0f594b28..e29b94ad1f 100644 --- a/utils/test/c/varbinary_test.c +++ b/utils/test/c/varbinary_test.c @@ -404,7 +404,7 @@ void varbinary_stmt_test(){ v.ts = 1591060628000; for (int i = 0; i < 10; ++i) { v.ts += 1; - for (int j = 1; j < 11; ++j) { + for (int j = 1; j < 3; ++j) { params[j].is_null = ((i == j) ? &is_null : 0); } v.b = (int8_t)i % 2; @@ -450,7 +450,7 @@ void varbinary_stmt_test(){ } ASSERT (rows == 1); - taos_free_result(result); +// taos_free_result(result); taos_stmt_close(stmt); // query the records @@ -483,11 +483,9 @@ void varbinary_stmt_test(){ ASSERT(strcmp(temp, "\\x090A0B0C0D") == 0); } ASSERT (rows == 1); - - taos_free_result(result); - +// taos_free_result(result); taos_stmt_close(stmt); - + taos_close(taos); printf("%s result success\n", __FUNCTION__); } @@ -660,6 +658,7 @@ void varbinary_tmq_test(){ ASSERT(0); } taos_free_result(pRes); + taos_close(pConn); printf("%s result success\n", __FUNCTION__); } From 9435d782458ff61fcc6a8513c031f519bcba9a88 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Tue, 29 Aug 2023 16:02:05 +0800 Subject: [PATCH 109/120] fix:rollback --- examples/c/prepare.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/c/prepare.c b/examples/c/prepare.c index a91153a92f..aee8400663 100644 --- a/examples/c/prepare.c +++ b/examples/c/prepare.c @@ -198,7 +198,7 @@ int main(int argc, char *argv[]) // query the records stmt = taos_stmt_init(taos); - taos_stmt_prepare(stmt, "SELECT * FROM m1 WHERE varbin > ? AND v2 < ?", 0); + taos_stmt_prepare(stmt, "SELECT * FROM m1 WHERE v1 > ? AND v2 < ?", 0); v.v1 = 5; v.v2 = 15; taos_stmt_bind_param(stmt, params + 2); From 82fc1f41fb08cd4e4cfe76a67cd9ef88d4c5b15f Mon Sep 17 00:00:00 2001 From: Minglei Jin Date: Tue, 29 Aug 2023 16:15:00 +0800 Subject: [PATCH 110/120] fix(tsdb/cache read): remove all null row --- source/dnode/vnode/src/tsdb/tsdbCacheRead.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/source/dnode/vnode/src/tsdb/tsdbCacheRead.c b/source/dnode/vnode/src/tsdb/tsdbCacheRead.c index 8ca2ccad1b..caf88f55fc 100644 --- a/source/dnode/vnode/src/tsdb/tsdbCacheRead.c +++ b/source/dnode/vnode/src/tsdb/tsdbCacheRead.c @@ -25,7 +25,7 @@ static int32_t saveOneRow(SArray* pRow, SSDataBlock* pBlock, SCacheRowsReader* pReader, const int32_t* slotIds, const int32_t* dstSlotIds, void** pRes, const char* idStr) { int32_t numOfRows = pBlock->info.rows; - bool allNullRow = true; + // bool allNullRow = true; if (HASTYPE(pReader->type, CACHESCAN_RETRIEVE_LAST)) { for (int32_t i = 0; i < pReader->numOfCols; ++i) { @@ -36,7 +36,7 @@ static int32_t saveOneRow(SArray* pRow, SSDataBlock* pBlock, SCacheRowsReader* p p->ts = pColVal->ts; p->isNull = !COL_VAL_IS_VALUE(&pColVal->colVal); - allNullRow = p->isNull & allNullRow; + // allNullRow = p->isNull & allNullRow; if (!p->isNull) { if (IS_VAR_DATA_TYPE(pColVal->colVal.type)) { @@ -56,7 +56,8 @@ static int32_t saveOneRow(SArray* pRow, SSDataBlock* pBlock, SCacheRowsReader* p colDataSetVal(pColInfoData, numOfRows, (const char*)pRes[i], false); } - pBlock->info.rows += allNullRow ? 0 : 1; + // pBlock->info.rows += allNullRow ? 0 : 1; + ++pBlock->info.rows; } else if (HASTYPE(pReader->type, CACHESCAN_RETRIEVE_LAST_ROW)) { for (int32_t i = 0; i < pReader->numOfCols; ++i) { SColumnInfoData* pColInfoData = taosArrayGet(pBlock->pDataBlock, dstSlotIds[i]); @@ -65,7 +66,7 @@ static int32_t saveOneRow(SArray* pRow, SSDataBlock* pBlock, SCacheRowsReader* p SLastCol* pColVal = (SLastCol*)taosArrayGet(pRow, i); SColVal* pVal = &pColVal->colVal; - allNullRow = false; + // allNullRow = false; if (IS_VAR_DATA_TYPE(pColVal->colVal.type)) { if (!COL_VAL_IS_VALUE(&pColVal->colVal)) { colDataSetNULL(pColInfoData, numOfRows); @@ -80,7 +81,8 @@ static int32_t saveOneRow(SArray* pRow, SSDataBlock* pBlock, SCacheRowsReader* p } } - pBlock->info.rows += allNullRow ? 0 : 1; + // pBlock->info.rows += allNullRow ? 0 : 1; + ++pBlock->info.rows; } else { tsdbError("invalid retrieve type:%d, %s", pReader->type, idStr); return TSDB_CODE_INVALID_PARA; From df2b2484df89563abcb7a828a2421e59d73c76a1 Mon Sep 17 00:00:00 2001 From: Shungang Li Date: Tue, 29 Aug 2023 17:15:40 +0800 Subject: [PATCH 111/120] fix: ttl cache entry record the original information for deletion --- source/dnode/vnode/src/inc/metaTtl.h | 4 +- source/dnode/vnode/src/meta/metaTtl.c | 53 +++++++++++++++++---------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/source/dnode/vnode/src/inc/metaTtl.h b/source/dnode/vnode/src/inc/metaTtl.h index c2cd389dab..ead2d89b28 100644 --- a/source/dnode/vnode/src/inc/metaTtl.h +++ b/source/dnode/vnode/src/inc/metaTtl.h @@ -26,7 +26,7 @@ extern "C" { #endif typedef enum DirtyEntryType { - ENTRY_TYPE_DEL = 1, + ENTRY_TYPE_DELETE = 1, ENTRY_TYPE_UPSERT = 2, } DirtyEntryType; @@ -44,6 +44,8 @@ typedef struct STtlManger { typedef struct { int64_t ttlDays; int64_t changeTimeMs; + int64_t ttlDaysDirty; + int64_t changeTimeMsDirty; } STtlCacheEntry; typedef struct { diff --git a/source/dnode/vnode/src/meta/metaTtl.c b/source/dnode/vnode/src/meta/metaTtl.c index 3c45982311..f920296b4a 100644 --- a/source/dnode/vnode/src/meta/metaTtl.c +++ b/source/dnode/vnode/src/meta/metaTtl.c @@ -209,7 +209,8 @@ static int32_t ttlMgrFillCacheOneEntry(const void *pKey, int keyLen, const void int64_t ttlDays = *(int64_t *)pVal; int64_t changeTimeMs = ttlKey->deleteTimeMs - ttlDays * tsTtlUnit * 1000; - STtlCacheEntry data = {.ttlDays = ttlDays, .changeTimeMs = changeTimeMs}; + STtlCacheEntry data = { + .ttlDays = ttlDays, .changeTimeMs = changeTimeMs, .ttlDaysDirty = ttlDays, .changeTimeMsDirty = changeTimeMs}; return taosHashPut(pCache, &uid, sizeof(uid), &data, sizeof(data)); } @@ -257,34 +258,37 @@ static int32_t ttlMgrFindExpiredOneEntry(const void *pKey, int keyLen, const voi static int ttlMgrConvert(TTB *pOldTtlIdx, TTB *pNewTtlIdx, void *pMeta) { SMeta *meta = pMeta; - metaInfo("ttlMgr convert ttl start."); + metaInfo("ttlMgr convert start."); SConvertData cvData = {.pNewTtlIdx = pNewTtlIdx, .pMeta = meta}; int ret = tdbTbTraversal(pOldTtlIdx, &cvData, ttlMgrConvertOneEntry); if (ret < 0) { - metaError("failed to convert ttl since %s", tstrerror(terrno)); + metaError("failed to convert since %s", tstrerror(terrno)); } - metaInfo("ttlMgr convert ttl end."); + metaInfo("ttlMgr convert end."); return ret; } int ttlMgrInsertTtl(STtlManger *pTtlMgr, const STtlUpdTtlCtx *updCtx) { if (updCtx->ttlDays == 0) return 0; - STtlCacheEntry cacheEntry = {.ttlDays = updCtx->ttlDays, .changeTimeMs = updCtx->changeTimeMs}; + STtlCacheEntry cacheEntry = {.ttlDays = updCtx->ttlDays, + .changeTimeMs = updCtx->changeTimeMs, + .ttlDaysDirty = updCtx->ttlDays, + .changeTimeMsDirty = updCtx->changeTimeMs}; STtlDirtyEntry dirtryEntry = {.type = ENTRY_TYPE_UPSERT}; int ret = taosHashPut(pTtlMgr->pTtlCache, &updCtx->uid, sizeof(updCtx->uid), &cacheEntry, sizeof(cacheEntry)); if (ret < 0) { - metaError("%s, ttlMgr insert failed to update ttl cache since %s", pTtlMgr->logPrefix, tstrerror(terrno)); + metaError("%s, ttlMgr insert failed to update cache since %s", pTtlMgr->logPrefix, tstrerror(terrno)); goto _out; } ret = taosHashPut(pTtlMgr->pDirtyUids, &updCtx->uid, sizeof(updCtx->uid), &dirtryEntry, sizeof(dirtryEntry)); if (ret < 0) { - metaError("%s, ttlMgr insert failed to update ttl dirty uids since %s", pTtlMgr->logPrefix, tstrerror(terrno)); + metaError("%s, ttlMgr insert failed to update dirty uids since %s", pTtlMgr->logPrefix, tstrerror(terrno)); goto _out; } @@ -304,11 +308,11 @@ _out: int ttlMgrDeleteTtl(STtlManger *pTtlMgr, const STtlDelTtlCtx *delCtx) { if (delCtx->ttlDays == 0) return 0; - STtlDirtyEntry dirtryEntry = {.type = ENTRY_TYPE_DEL}; + STtlDirtyEntry dirtryEntry = {.type = ENTRY_TYPE_DELETE}; int ret = taosHashPut(pTtlMgr->pDirtyUids, &delCtx->uid, sizeof(delCtx->uid), &dirtryEntry, sizeof(dirtryEntry)); if (ret < 0) { - metaError("%s, ttlMgr del failed to update ttl dirty uids since %s", pTtlMgr->logPrefix, tstrerror(terrno)); + metaError("%s, ttlMgr del failed to update dirty uids since %s", pTtlMgr->logPrefix, tstrerror(terrno)); goto _out; } @@ -332,19 +336,22 @@ int ttlMgrUpdateChangeTime(STtlManger *pTtlMgr, const STtlUpdCtimeCtx *pUpdCtime goto _out; } - STtlCacheEntry cacheEntry = {.ttlDays = oldData->ttlDays, .changeTimeMs = pUpdCtimeCtx->changeTimeMs}; + STtlCacheEntry cacheEntry = {.ttlDays = oldData->ttlDays, + .changeTimeMs = oldData->changeTimeMs, + .ttlDaysDirty = oldData->ttlDays, + .changeTimeMsDirty = pUpdCtimeCtx->changeTimeMs}; STtlDirtyEntry dirtryEntry = {.type = ENTRY_TYPE_UPSERT}; ret = taosHashPut(pTtlMgr->pTtlCache, &pUpdCtimeCtx->uid, sizeof(pUpdCtimeCtx->uid), &cacheEntry, sizeof(cacheEntry)); if (ret < 0) { - metaError("%s, ttlMgr update ctime failed to update ttl cache since %s", pTtlMgr->logPrefix, tstrerror(terrno)); + metaError("%s, ttlMgr update ctime failed to update cache since %s", pTtlMgr->logPrefix, tstrerror(terrno)); goto _out; } ret = taosHashPut(pTtlMgr->pDirtyUids, &pUpdCtimeCtx->uid, sizeof(pUpdCtimeCtx->uid), &dirtryEntry, sizeof(dirtryEntry)); if (ret < 0) { - metaError("%s, ttlMgr update ctime failed to update ttl dirty uids since %s", pTtlMgr->logPrefix, + metaError("%s, ttlMgr update ctime failed to update dirty uids since %s", pTtlMgr->logPrefix, tstrerror(terrno)); goto _out; } @@ -396,27 +403,35 @@ int ttlMgrFlush(STtlManger *pTtlMgr, TXN *pTxn) { STtlIdxKeyV1 ttlKey; ttlMgrBuildKey(&ttlKey, cacheEntry->ttlDays, cacheEntry->changeTimeMs, *pUid); + STtlIdxKeyV1 ttlKeyDirty; + ttlMgrBuildKey(&ttlKeyDirty, cacheEntry->ttlDaysDirty, cacheEntry->changeTimeMsDirty, *pUid); + if (pEntry->type == ENTRY_TYPE_UPSERT) { - ret = tdbTbUpsert(pTtlMgr->pTtlIdx, &ttlKey, sizeof(ttlKey), &cacheEntry->ttlDays, sizeof(cacheEntry->ttlDays), - pTxn); + // delete old key & upsert new key + tdbTbDelete(pTtlMgr->pTtlIdx, &ttlKey, sizeof(ttlKey), pTxn); // maybe first insert, ignore error + ret = tdbTbUpsert(pTtlMgr->pTtlIdx, &ttlKeyDirty, sizeof(ttlKeyDirty), &cacheEntry->ttlDaysDirty, + sizeof(cacheEntry->ttlDaysDirty), pTxn); if (ret < 0) { - metaError("%s, ttlMgr flush failed to flush ttl cache upsert since %s", pTtlMgr->logPrefix, tstrerror(terrno)); + metaError("%s, ttlMgr flush failed to upsert since %s", pTtlMgr->logPrefix, tstrerror(terrno)); goto _out; } - } else if (pEntry->type == ENTRY_TYPE_DEL) { + + cacheEntry->ttlDays = cacheEntry->ttlDaysDirty; + cacheEntry->changeTimeMs = cacheEntry->changeTimeMsDirty; + } else if (pEntry->type == ENTRY_TYPE_DELETE) { ret = tdbTbDelete(pTtlMgr->pTtlIdx, &ttlKey, sizeof(ttlKey), pTxn); if (ret < 0) { - metaError("%s, ttlMgr flush failed to flush ttl cache del since %s", pTtlMgr->logPrefix, tstrerror(terrno)); + metaError("%s, ttlMgr flush failed to delete since %s", pTtlMgr->logPrefix, tstrerror(terrno)); goto _out; } ret = taosHashRemove(pTtlMgr->pTtlCache, pUid, sizeof(*pUid)); if (ret < 0) { - metaError("%s, ttlMgr flush failed to delete ttl cache since %s", pTtlMgr->logPrefix, tstrerror(terrno)); + metaError("%s, ttlMgr flush failed to remove cache since %s", pTtlMgr->logPrefix, tstrerror(terrno)); goto _out; } } else { - metaError("%s, ttlMgr flush failed to flush ttl cache, unknown type: %d", pTtlMgr->logPrefix, pEntry->type); + metaError("%s, ttlMgr flush failed, unknown type: %d", pTtlMgr->logPrefix, pEntry->type); goto _out; } From de91793630acb4df4f5782c90656bbe96a9e3953 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Tue, 29 Aug 2023 17:20:06 +0800 Subject: [PATCH 112/120] fix:compile error --- examples/c/tmq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/c/tmq.c b/examples/c/tmq.c index 3769383bba..15ab4fcfc9 100644 --- a/examples/c/tmq.c +++ b/examples/c/tmq.c @@ -280,7 +280,7 @@ void consume_repeatly(tmq_t* tmq) { code = tmq_offset_seek(tmq, topic_name, p->vgId, p->begin); if (code != 0) { - fprintf(stderr, "failed to seek to %lld, reason:%s", p->begin, tmq_err2str(code)); + fprintf(stderr, "failed to seek to %d, reason:%s", (int)p->begin, tmq_err2str(code)); } } From b740ce8ad634847468ac9de2973fbecec7b34d54 Mon Sep 17 00:00:00 2001 From: Haojun Liao Date: Tue, 29 Aug 2023 17:49:43 +0800 Subject: [PATCH 113/120] fix(tsdb): add more check for block rows to detect the duplicate rows in data block. --- source/dnode/vnode/src/tsdb/tsdbRead2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/dnode/vnode/src/tsdb/tsdbRead2.c b/source/dnode/vnode/src/tsdb/tsdbRead2.c index e635862200..c10d8c628d 100644 --- a/source/dnode/vnode/src/tsdb/tsdbRead2.c +++ b/source/dnode/vnode/src/tsdb/tsdbRead2.c @@ -1268,7 +1268,7 @@ static void getBlockToLoadInfo(SDataBlockToLoadInfo* pInfo, SFileDataBlockInfo* } // has duplicated ts of different version in this block - pInfo->hasDupTs = (pBlockInfo->record.numRow > pBlockInfo->record.count); + pInfo->hasDupTs = (pBlockInfo->record.numRow > pBlockInfo->record.count) || (pBlockInfo->record.count <= 0); pInfo->overlapWithDelInfo = overlapWithDelSkyline(pScanInfo, &pBlockInfo->record, pReader->info.order); if (hasDataInLastBlock(pLastBlockReader)) { From dbeafb508b1895144942de6fbd4414d3d759f91a Mon Sep 17 00:00:00 2001 From: Benguang Zhao Date: Tue, 29 Aug 2023 17:56:24 +0800 Subject: [PATCH 114/120] fix: reset show obj iter on no more data to read in mndRetrieveIdx --- source/dnode/mnode/impl/src/mndSma.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/source/dnode/mnode/impl/src/mndSma.c b/source/dnode/mnode/impl/src/mndSma.c index d666f80fd3..44842084c5 100644 --- a/source/dnode/mnode/impl/src/mndSma.c +++ b/source/dnode/mnode/impl/src/mndSma.c @@ -1324,7 +1324,14 @@ static int32_t mndRetrieveIdx(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBloc pShow->pIter = taosMemoryCalloc(1, sizeof(SSmaAndTagIter)); } int32_t read = mndRetrieveSma(pReq, pShow, pBlock, rows); - if (read < rows) read += mndRetrieveTagIdx(pReq, pShow, pBlock, rows - read); + if (read < rows) { + read += mndRetrieveTagIdx(pReq, pShow, pBlock, rows - read); + } + // no more to read + if (read < rows) { + taosMemoryFree(pShow->pIter); + pShow->pIter = NULL; + } return read; } static void mndCancelRetrieveIdx(SMnode *pMnode, void *pIter) { From d6368798dac5af531dd0c3933225d39ec33bc258 Mon Sep 17 00:00:00 2001 From: Haojun Liao Date: Tue, 29 Aug 2023 18:11:17 +0800 Subject: [PATCH 115/120] fix(query): disable the set quit flag for tsdbreader. --- source/dnode/vnode/src/tsdb/tsdbRead.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/dnode/vnode/src/tsdb/tsdbRead.c b/source/dnode/vnode/src/tsdb/tsdbRead.c index 52e6fe6312..c02cff3aa9 100644 --- a/source/dnode/vnode/src/tsdb/tsdbRead.c +++ b/source/dnode/vnode/src/tsdb/tsdbRead.c @@ -5608,4 +5608,4 @@ void tsdbReaderSetId(STsdbReader* pReader, const char* idstr) { pReader->idStr = taosStrdup(idstr); } -void tsdbReaderSetCloseFlag(STsdbReader* pReader) { pReader->code = TSDB_CODE_TSC_QUERY_CANCELLED; } +void tsdbReaderSetCloseFlag(STsdbReader* pReader) { /*pReader->code = TSDB_CODE_TSC_QUERY_CANCELLED;*/ } From bb33620f5c0778191ec21fa878c289cda32bdeda Mon Sep 17 00:00:00 2001 From: dmchen Date: Tue, 29 Aug 2023 18:12:08 +0800 Subject: [PATCH 116/120] dbname --- source/dnode/mnode/impl/src/mndDb.c | 15 ++++++++++++--- source/dnode/mnode/impl/src/mndStb.c | 15 ++++++++++++--- source/dnode/mnode/impl/src/mndTopic.c | 5 ++++- source/dnode/mnode/impl/src/mndUser.c | 25 +++++++++++++++++++++---- 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/source/dnode/mnode/impl/src/mndDb.c b/source/dnode/mnode/impl/src/mndDb.c index 972705f7a8..d8b3a2a1f1 100644 --- a/source/dnode/mnode/impl/src/mndDb.c +++ b/source/dnode/mnode/impl/src/mndDb.c @@ -751,8 +751,11 @@ static int32_t mndProcessCreateDbReq(SRpcMsg *pReq) { createReq.replications, createReq.schemaless, createReq.sstTrigger, createReq.strict, createReq.tsdbPageSize, createReq.walFsyncPeriod, createReq.walLevel, createReq.walRetentionPeriod, createReq.walRetentionSize, createReq.walRollPeriod, createReq.walSegmentSize); + + SName name = {0}; + tNameFromString(&name, createReq.db, T_NAME_ACCT | T_NAME_DB); - auditRecord(pReq, pMnode->clusterId, "createDB", createReq.db, "", detail); + auditRecord(pReq, pMnode->clusterId, "createDB", name.dbname, "", detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -1006,7 +1009,10 @@ static int32_t mndProcessAlterDbReq(SRpcMsg *pReq) { alterReq.replications, alterReq.sstTrigger, alterReq.strict, alterReq.walFsyncPeriod, alterReq.walRetentionSize); - auditRecord(pReq, pMnode->clusterId, "alterDB", alterReq.db, "", detail); + SName name = {0}; + tNameFromString(&name, alterReq.db, T_NAME_ACCT | T_NAME_DB); + + auditRecord(pReq, pMnode->clusterId, "alterDB", name.dbname, "", detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -1300,7 +1306,10 @@ static int32_t mndProcessDropDbReq(SRpcMsg *pReq) { char detail[1000] = {0}; sprintf(detail, "ignoreNotExists:%d", dropReq.ignoreNotExists); - auditRecord(pReq, pMnode->clusterId, "dropDB", dropReq.db, "", detail); + SName name = {0}; + tNameFromString(&name, dropReq.db, T_NAME_ACCT | T_NAME_DB); + + auditRecord(pReq, pMnode->clusterId, "dropDB", name.dbname, "", detail); _OVER: if (code != TSDB_CODE_SUCCESS && code != TSDB_CODE_ACTION_IN_PROGRESS) { diff --git a/source/dnode/mnode/impl/src/mndStb.c b/source/dnode/mnode/impl/src/mndStb.c index a5fe818133..9a5429ba87 100644 --- a/source/dnode/mnode/impl/src/mndStb.c +++ b/source/dnode/mnode/impl/src/mndStb.c @@ -1184,7 +1184,10 @@ static int32_t mndProcessCreateStbReq(SRpcMsg *pReq) { createReq.source, createReq.suid, createReq.tagVer, createReq.ttl, createReq.watermark1, createReq.watermark2); - auditRecord(pReq, pMnode->clusterId, "createStb", pDb->name, createReq.name, detail); + SName name = {0}; + tNameFromString(&name, pDb->name, T_NAME_ACCT | T_NAME_DB); + + auditRecord(pReq, pMnode->clusterId, "createStb", name.dbname, createReq.name, detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -2258,7 +2261,10 @@ static int32_t mndProcessAlterStbReq(SRpcMsg *pReq) { sprintf(detail, "alterType:%d, numOfFields:%d, ttl:%d" , alterReq.alterType, alterReq.numOfFields, alterReq.ttl); - auditRecord(pReq, pMnode->clusterId, "alterStb", pDb->name, alterReq.name, detail); + SName name = {0}; + tNameFromString(&name, pDb->name, T_NAME_ACCT | T_NAME_DB); + + auditRecord(pReq, pMnode->clusterId, "alterStb", name.dbname, alterReq.name, detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { @@ -2524,8 +2530,11 @@ static int32_t mndProcessDropStbReq(SRpcMsg *pReq) { char detail[2000] = {0}; sprintf(detail, "igNotExists:%d, source:%d" , dropReq.igNotExists, dropReq.source); + + SName name = {0}; + tNameFromString(&name, pDb->name, T_NAME_ACCT | T_NAME_DB); - auditRecord(pReq, pMnode->clusterId, "dropStb", pDb->name, dropReq.name, detail); + auditRecord(pReq, pMnode->clusterId, "dropStb", name.dbname, dropReq.name, detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { diff --git a/source/dnode/mnode/impl/src/mndTopic.c b/source/dnode/mnode/impl/src/mndTopic.c index e1d964a8a3..f6b2aabede 100644 --- a/source/dnode/mnode/impl/src/mndTopic.c +++ b/source/dnode/mnode/impl/src/mndTopic.c @@ -626,7 +626,10 @@ static int32_t mndProcessCreateTopicReq(SRpcMsg *pReq) { sprintf(detail, "igExists:%d, subStbName:%s, subType:%d, withMeta:%d", createTopicReq.igExists, createTopicReq.subStbName, createTopicReq.subType, createTopicReq.withMeta); - auditRecord(pReq, pMnode->clusterId, "crateTopic", createTopicReq.name, createTopicReq.subDbName, detail); + SName name = {0}; + tNameFromString(&name, createTopicReq.subDbName, T_NAME_ACCT | T_NAME_DB); + + auditRecord(pReq, pMnode->clusterId, "crateTopic", createTopicReq.name, name.dbname, detail); _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { diff --git a/source/dnode/mnode/impl/src/mndUser.c b/source/dnode/mnode/impl/src/mndUser.c index 098f260bb6..42a3ba8904 100644 --- a/source/dnode/mnode/impl/src/mndUser.c +++ b/source/dnode/mnode/impl/src/mndUser.c @@ -982,25 +982,42 @@ static int32_t mndProcessAlterUserReq(SRpcMsg *pReq) { alterReq.alterType, alterReq.enable, alterReq.superUser, alterReq.sysInfo, alterReq.tabName); if(alterReq.alterType == TSDB_ALTER_USER_PASSWD){ - auditRecord(pReq, pMnode->clusterId, "changePassword", alterReq.user, alterReq.objname, detail); + auditRecord(pReq, pMnode->clusterId, "changePassword", alterReq.user, "", detail); } else if(alterReq.alterType == TSDB_ALTER_USER_SUPERUSER || alterReq.alterType == TSDB_ALTER_USER_ENABLE || alterReq.alterType == TSDB_ALTER_USER_SYSINFO){ - auditRecord(pReq, pMnode->clusterId, "alterUser", alterReq.user, alterReq.objname, detail); + auditRecord(pReq, pMnode->clusterId, "alterUser", alterReq.user, "", detail); } else if(alterReq.alterType == TSDB_ALTER_USER_ADD_READ_DB|| alterReq.alterType == TSDB_ALTER_USER_ADD_WRITE_DB|| alterReq.alterType == TSDB_ALTER_USER_ADD_ALL_DB|| - alterReq.alterType == TSDB_ALTER_USER_ADD_SUBSCRIBE_TOPIC|| alterReq.alterType == TSDB_ALTER_USER_ADD_READ_TABLE|| alterReq.alterType == TSDB_ALTER_USER_ADD_WRITE_TABLE|| alterReq.alterType == TSDB_ALTER_USER_ADD_ALL_TABLE){ + if (strcmp(alterReq.objname, "1.*") != 0){ + SName name = {0}; + tNameFromString(&name, alterReq.objname, T_NAME_ACCT | T_NAME_DB); + auditRecord(pReq, pMnode->clusterId, "GrantPrivileges", alterReq.user, name.dbname, detail); + }else{ + auditRecord(pReq, pMnode->clusterId, "GrantPrivileges", alterReq.user, "*", detail); + } + } + else if(alterReq.alterType == TSDB_ALTER_USER_ADD_SUBSCRIBE_TOPIC){ auditRecord(pReq, pMnode->clusterId, "GrantPrivileges", alterReq.user, alterReq.objname, detail); } - else{ + else if(alterReq.alterType == TSDB_ALTER_USER_REMOVE_SUBSCRIBE_TOPIC){ auditRecord(pReq, pMnode->clusterId, "RevokePrivileges", alterReq.user, alterReq.objname, detail); } + else{ + if (strcmp(alterReq.objname, "1.*") != 0){ + SName name = {0}; + tNameFromString(&name, alterReq.objname, T_NAME_ACCT | T_NAME_DB); + auditRecord(pReq, pMnode->clusterId, "RevokePrivileges", alterReq.user, name.dbname, detail); + }else{ + auditRecord(pReq, pMnode->clusterId, "RevokePrivileges", alterReq.user, "*", detail); + } + } _OVER: if (code != 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) { From 7bea92cbf9449ce52521ea8aef57177706b0edb7 Mon Sep 17 00:00:00 2001 From: dmchen Date: Wed, 30 Aug 2023 10:28:39 +0800 Subject: [PATCH 117/120] TD-26033 --- source/dnode/mnode/impl/src/mndDb.c | 87 +++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/source/dnode/mnode/impl/src/mndDb.c b/source/dnode/mnode/impl/src/mndDb.c index d8b3a2a1f1..cbb7a27ef5 100644 --- a/source/dnode/mnode/impl/src/mndDb.c +++ b/source/dnode/mnode/impl/src/mndDb.c @@ -678,6 +678,22 @@ _OVER: return code; } +static void mndBuildAuditDetailInt32(char* detail, char* tmp, char* format, int32_t para){ + if(para > 0){ + if(strlen(detail) > 0) strcat(detail, ", "); + sprintf(tmp, format, para); + strcat(detail, tmp); + } +} + +static void mndBuildAuditDetailInt64(char* detail, char* tmp, char* format, int64_t para){ + if(para > 0){ + if(strlen(detail) > 0) strcat(detail, ", "); + sprintf(tmp, format, para); + strcat(detail, tmp); + } +} + static int32_t mndProcessCreateDbReq(SRpcMsg *pReq) { SMnode *pMnode = pReq->info.node; int32_t code = -1; @@ -737,20 +753,38 @@ static int32_t mndProcessCreateDbReq(SRpcMsg *pReq) { if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; char detail[3000] = {0}; - sprintf(detail, "buffer:%d, cacheLast:%d, cacheLastSize:%d, compression:%d, daysPerFile:%d, " - "daysToKeep0:%d, daysToKeep:%d, daysToKeep2:%d, hashPrefix:%d, " - "hashSuffix:%d, ignoreExist:%d, maxRows:%d, minRows:%d, numOfRetensions:%d, " - "numOfStables:%d, numOfVgroups:%d, pages:%d, pageSize:%d, precision:%d, " - "replications:%d, schemaless:%d, sstTrigger:%d, strict:%d, " - "tsdbPageSize:%d, walFsyncPeriod:%d, walLevel:%d, walRetentionPeriod:%d, " - "walRetentionSize:%" PRId64 ", walRollPeriod:%d, walSegmentSize:%" PRId64, - createReq.buffer, createReq.cacheLast, createReq.cacheLastSize, createReq.compression, createReq.daysPerFile, - createReq.daysToKeep0, createReq.daysToKeep1, createReq.daysToKeep2, createReq.hashPrefix, - createReq.hashSuffix, createReq.ignoreExist, createReq.maxRows, createReq.minRows, createReq.numOfRetensions, - createReq.numOfStables, createReq.numOfVgroups, createReq.pages, createReq.pageSize, createReq.precision, - createReq.replications, createReq.schemaless, createReq.sstTrigger, createReq.strict, - createReq.tsdbPageSize, createReq.walFsyncPeriod, createReq.walLevel, createReq.walRetentionPeriod, - createReq.walRetentionSize, createReq.walRollPeriod, createReq.walSegmentSize); + char tmp[100] = {0}; + + mndBuildAuditDetailInt32(detail, tmp, "buffer:%d", createReq.buffer); + mndBuildAuditDetailInt32(detail, tmp, "cacheLast:%d", createReq.cacheLast); + mndBuildAuditDetailInt32(detail, tmp, "cacheLastSize:%d", createReq.cacheLastSize); + mndBuildAuditDetailInt32(detail, tmp, "compression:%d", createReq.compression); + mndBuildAuditDetailInt32(detail, tmp, "daysPerFile:%d", createReq.daysPerFile); + mndBuildAuditDetailInt32(detail, tmp, "daysToKeep0:%d", createReq.daysToKeep0); + mndBuildAuditDetailInt32(detail, tmp, "daysToKeep1:%d", createReq.daysToKeep1); + mndBuildAuditDetailInt32(detail, tmp, "daysToKeep2:%d", createReq.daysToKeep2); + mndBuildAuditDetailInt32(detail, tmp, "hashPrefix:%d", createReq.hashPrefix); + mndBuildAuditDetailInt32(detail, tmp, "hashSuffix:%d", createReq.hashSuffix); + mndBuildAuditDetailInt32(detail, tmp, "ignoreExist:%d", createReq.ignoreExist); + mndBuildAuditDetailInt32(detail, tmp, "maxRows:%d", createReq.maxRows); + mndBuildAuditDetailInt32(detail, tmp, "minRows:%d", createReq.minRows); + mndBuildAuditDetailInt32(detail, tmp, "numOfRetensions:%d", createReq.numOfRetensions); + mndBuildAuditDetailInt32(detail, tmp, "numOfStables:%d", createReq.numOfStables); + mndBuildAuditDetailInt32(detail, tmp, "numOfVgroups:%d", createReq.numOfVgroups); + mndBuildAuditDetailInt32(detail, tmp, "pages:%d", createReq.pages); + mndBuildAuditDetailInt32(detail, tmp, "pageSize:%d", createReq.pageSize); + mndBuildAuditDetailInt32(detail, tmp, "precision:%d", createReq.precision); + mndBuildAuditDetailInt32(detail, tmp, "replications:%d", createReq.replications); + mndBuildAuditDetailInt32(detail, tmp, "schemaless:%d", createReq.schemaless); + mndBuildAuditDetailInt32(detail, tmp, "sstTrigger:%d", createReq.sstTrigger); + mndBuildAuditDetailInt32(detail, tmp, "strict:%d", createReq.strict); + mndBuildAuditDetailInt32(detail, tmp, "tsdbPageSize:%d", createReq.tsdbPageSize); + mndBuildAuditDetailInt32(detail, tmp, "walFsyncPeriod:%d", createReq.walFsyncPeriod); + mndBuildAuditDetailInt32(detail, tmp, "walLevel:%d", createReq.walLevel); + mndBuildAuditDetailInt32(detail, tmp, "walRetentionPeriod:%d", createReq.walRetentionPeriod); + mndBuildAuditDetailInt32(detail, tmp, "walRetentionSize:%" PRId64, createReq.walRetentionSize); + mndBuildAuditDetailInt32(detail, tmp, "walRollPeriod:%d", createReq.walRollPeriod); + mndBuildAuditDetailInt32(detail, tmp, "walSegmentSize:%" PRId64, createReq.walSegmentSize); SName name = {0}; tNameFromString(&name, createReq.db, T_NAME_ACCT | T_NAME_DB); @@ -1000,14 +1034,23 @@ static int32_t mndProcessAlterDbReq(SRpcMsg *pReq) { } char detail[3000] = {0}; - sprintf(detail, "buffer:%d, cacheLast:%d, cacheLastSize:%d, daysPerFile:%d, daysToKeep0:%d, " - "daysToKeep1:%d, daysToKeep2:%d, db:%s, minRows:%d, pages:%d, pageSize:%d, " - "replications:%d, sstTrigger:%d, strict:%d, walFsyncPeriod:%d, " - "walRetentionSize:%d", - alterReq.buffer, alterReq.cacheLast, alterReq.cacheLastSize, alterReq.daysPerFile, alterReq.daysToKeep0, - alterReq.daysToKeep1, alterReq.daysToKeep2, alterReq.db, alterReq.minRows, alterReq.pages, alterReq.pageSize, - alterReq.replications, alterReq.sstTrigger, alterReq.strict, alterReq.walFsyncPeriod, - alterReq.walRetentionSize); + char tmp[100] = {0}; + + mndBuildAuditDetailInt32(detail, tmp, "buffer:%d", alterReq.buffer); + mndBuildAuditDetailInt32(detail, tmp, "cacheLast:%d", alterReq.cacheLast); + mndBuildAuditDetailInt32(detail, tmp, "cacheLastSize:%d", alterReq.cacheLastSize); + mndBuildAuditDetailInt32(detail, tmp, "daysPerFile:%d", alterReq.daysPerFile); + mndBuildAuditDetailInt32(detail, tmp, "daysToKeep0:%d", alterReq.daysToKeep0); + mndBuildAuditDetailInt32(detail, tmp, "daysToKeep1:%d", alterReq.daysToKeep1); + mndBuildAuditDetailInt32(detail, tmp, "daysToKeep2:%d", alterReq.daysToKeep2); + mndBuildAuditDetailInt32(detail, tmp, "minRows:%d", alterReq.minRows); + mndBuildAuditDetailInt32(detail, tmp, "pages:%d", alterReq.pages); + mndBuildAuditDetailInt32(detail, tmp, "pageSize:%d", alterReq.pageSize); + mndBuildAuditDetailInt32(detail, tmp, "replications:%d", alterReq.replications); + mndBuildAuditDetailInt32(detail, tmp, "sstTrigger:%d", alterReq.sstTrigger); + mndBuildAuditDetailInt32(detail, tmp, "strict:%d", alterReq.strict); + mndBuildAuditDetailInt32(detail, tmp, "walFsyncPeriod:%d", alterReq.walFsyncPeriod); + mndBuildAuditDetailInt32(detail, tmp, "walRetentionSize:%d", alterReq.walRetentionSize); SName name = {0}; tNameFromString(&name, alterReq.db, T_NAME_ACCT | T_NAME_DB); From 2adeda74f58a123f820bedd190f53777421d6a20 Mon Sep 17 00:00:00 2001 From: wade zhang <95411902+gccgdb1234@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:18:45 +0800 Subject: [PATCH 118/120] Update 05-taosbenchmark.md --- docs/zh/14-reference/05-taosbenchmark.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/14-reference/05-taosbenchmark.md b/docs/zh/14-reference/05-taosbenchmark.md index e4c3efba17..d5ae95b20b 100644 --- a/docs/zh/14-reference/05-taosbenchmark.md +++ b/docs/zh/14-reference/05-taosbenchmark.md @@ -13,7 +13,7 @@ taosBenchmark (曾用名 taosdemo ) 是一个用于测试 TDengine 产品性能 taosBenchmark 有两种安装方式: -- 安装 TDengine 官方安装包的同时会自动安装 taosBenchmark, 详情请参考[ TDengine 安装](/operation/pkg-install)。 +- 安装 TDengine 官方安装包的同时会自动安装 taosBenchmark, 详情请参考[ TDengine 安装](../../operation/pkg-install)。 - 单独编译 taos-tools 并安装, 详情请参考 [taos-tools](https://github.com/taosdata/taos-tools) 仓库。 From 06e59e2a797d5c20434fd55c9681e99fc2b9ffdd Mon Sep 17 00:00:00 2001 From: wade zhang <95411902+gccgdb1234@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:19:10 +0800 Subject: [PATCH 119/120] Update 11-kafka.md --- docs/zh/20-third-party/11-kafka.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/20-third-party/11-kafka.md b/docs/zh/20-third-party/11-kafka.md index dc4f25cbe8..2f09e50f0b 100644 --- a/docs/zh/20-third-party/11-kafka.md +++ b/docs/zh/20-third-party/11-kafka.md @@ -23,7 +23,7 @@ TDengine Source Connector 用于把数据实时地从 TDengine 读出来发送 1. Linux 操作系统 2. 已安装 Java 8 和 Maven 3. 已安装 Git、curl、vi -4. 已安装并启动 TDengine。如果还没有可参考[安装和卸载](/operation/pkg-install) +4. 已安装并启动 TDengine。如果还没有可参考[安装和卸载](../../operation/pkg-install) ## 安装 Kafka From 255de5c2a52bc80fcb4724e17e6d8bc6d6942d66 Mon Sep 17 00:00:00 2001 From: wade zhang <95411902+gccgdb1234@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:21:07 +0800 Subject: [PATCH 120/120] Update 14-taosKeeper.md --- docs/zh/14-reference/14-taosKeeper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/14-reference/14-taosKeeper.md b/docs/zh/14-reference/14-taosKeeper.md index b4d35fb240..738d351d45 100644 --- a/docs/zh/14-reference/14-taosKeeper.md +++ b/docs/zh/14-reference/14-taosKeeper.md @@ -16,7 +16,7 @@ taosKeeper 是 TDengine 3.0 版本监控指标的导出工具,通过简单的 taosKeeper 有两种安装方式: taosKeeper 安装方式: -- 安装 TDengine 官方安装包的同时会自动安装 taosKeeper, 详情请参考[ TDengine 安装](/operation/pkg-install)。 +- 安装 TDengine 官方安装包的同时会自动安装 taosKeeper, 详情请参考[ TDengine 安装](../../operation/pkg-install)。 - 单独编译 taosKeeper 并安装,详情请参考 [taosKeeper](https://github.com/taosdata/taoskeeper) 仓库。