diff --git a/include/common/systable.h b/include/common/systable.h index 2a1dd1077133..cbfa0cb8e6fc 100644 --- a/include/common/systable.h +++ b/include/common/systable.h @@ -85,6 +85,7 @@ extern "C" { #define TSDB_INS_TABLE_ROLES "ins_roles" #define TSDB_INS_TABLE_ROLE_PRIVILEGES "ins_role_privileges" #define TSDB_INS_TABLE_ROLE_COL_PRIVILEGES "ins_role_column_privileges" +#define TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING "ins_virtual_tables_referencing" #define TSDB_PERFORMANCE_SCHEMA_DB "performance_schema" #define TSDB_PERFS_TABLE_SMAS "perf_smas" diff --git a/include/common/tmsg.h b/include/common/tmsg.h index d45b8078e57a..56152901fcd4 100644 --- a/include/common/tmsg.h +++ b/include/common/tmsg.h @@ -215,6 +215,7 @@ typedef enum _mgmt_table { TSDB_MGMT_TABLE_XNODE_AGENTS, TSDB_MGMT_TABLE_XNODE_JOBS, TSDB_MGMT_TABLE_XNODE_FULL, + TSDB_MGMT_TABLE_VIRTUAL_TABLES_REFERENCING, TSDB_MGMT_TABLE_MAX, } EShowType; @@ -444,7 +445,6 @@ typedef enum ENodeType { QUERY_NODE_DROP_TOTP_SECRET_STMT, QUERY_NODE_ALTER_KEY_EXPIRATION_STMT, - // placeholder for [155, 180] QUERY_NODE_SHOW_CREATE_VIEW_STMT = 181, QUERY_NODE_SHOW_CREATE_DATABASE_STMT, @@ -562,6 +562,7 @@ typedef enum ENodeType { QUERY_NODE_SHOW_XNODE_TASKS_STMT, QUERY_NODE_SHOW_XNODE_AGENTS_STMT, QUERY_NODE_SHOW_XNODE_JOBS_STMT, + QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT, // logic plan node QUERY_NODE_LOGIC_PLAN_SCAN = 1000, @@ -791,6 +792,8 @@ typedef struct { int32_t nCols; int32_t version; SColRef* pColRef; + int32_t nTagRefs; + SColRef* pTagRef; } SColRefWrapper; int32_t tEncodeSColRefWrapper(SEncoder* pCoder, const SColRefWrapper* pWrapper); @@ -885,6 +888,8 @@ typedef struct { int8_t virtualStb; int32_t numOfColRefs; SColRef* pColRefs; + int32_t numOfTagRefs; + SColRef* pTagRefs; } STableMetaRsp; typedef struct { @@ -1001,6 +1006,22 @@ static FORCE_INLINE int32_t tInitDefaultSColRefWrapperByCols(SColRefWrapper* pRe return 0; } +static FORCE_INLINE int32_t tInitDefaultSColRefWrapperByTags(SColRefWrapper* pRef, int32_t nTags, col_id_t startColId) { + if (pRef->pTagRef) { + return TSDB_CODE_INVALID_PARA; + } + pRef->pTagRef = (SColRef*)taosMemoryCalloc(nTags, sizeof(SColRef)); + if (pRef->pTagRef == NULL) { + return terrno; + } + pRef->nTagRefs = nTags; + for (int32_t i = 0; i < nTags; i++) { + pRef->pTagRef[i].hasRef = false; + pRef->pTagRef[i].id = (col_id_t)(startColId + i); + } + return 0; +} + static FORCE_INLINE SColCmprWrapper* tCloneSColCmprWrapper(const SColCmprWrapper* pSrcWrapper) { if (pSrcWrapper->pColCmpr == NULL || pSrcWrapper->nCols == 0) { terrno = TSDB_CODE_INVALID_PARA; @@ -4850,6 +4871,7 @@ static FORCE_INLINE void tdDestroySVCreateTbReq(SVCreateTbReq* req) { taosMemoryFreeClear(req->colCmpr.pColCmpr); taosMemoryFreeClear(req->pExtSchemas); taosMemoryFreeClear(req->colRef.pColRef); + taosMemoryFreeClear(req->colRef.pTagRef); } typedef struct { diff --git a/include/libs/nodes/cmdnodes.h b/include/libs/nodes/cmdnodes.h index 91ec43740230..7827b4f2f5bb 100644 --- a/include/libs/nodes/cmdnodes.h +++ b/include/libs/nodes/cmdnodes.h @@ -332,6 +332,8 @@ typedef struct SCreateVSubTableStmt { SNodeList* pValsOfTags; SNodeList* pSpecificColRefs; SNodeList* pColRefs; + SNodeList* pSpecificTagRefs; // tag_name FROM db.table.tag_col (same as specific_column_ref) + SNodeList* pTagRefs; // db.table.tag_col (same as column_ref, positional) } SCreateVSubTableStmt; typedef struct SCreateSubTableClause { @@ -1033,6 +1035,17 @@ typedef struct SAlterKeyExpirationStmt { char strategy[ENCRYPT_KEY_EXPIRE_STRATEGY_LEN + 1]; } SAlterKeyExpirationStmt; +typedef struct SShowValidateVirtualTable { + ENodeType type; + char dbName[TSDB_DB_NAME_LEN]; + char tableName[TSDB_TABLE_NAME_LEN]; + + void* pDbCfg; // SDbCfgInfo + + void* pTableCfg; // STableCfg + int8_t superTable; +} SShowValidateVirtualTable; + typedef struct SDescribeStmt { ENodeType type; char dbName[TSDB_DB_NAME_LEN]; diff --git a/include/libs/qcom/query.h b/include/libs/qcom/query.h index dc2f3a5a9fd8..d2a72b52f78f 100644 --- a/include/libs/qcom/query.h +++ b/include/libs/qcom/query.h @@ -117,6 +117,8 @@ typedef struct SVCTableMeta { int32_t numOfColRefs; int32_t rversion; // virtual table's column ref's version SColRef* colRef; + int32_t numOfTagRefs; + SColRef* tagRef; } SVCTableMeta; #pragma pack(pop) @@ -133,6 +135,8 @@ typedef struct STableMeta { int32_t numOfColRefs; int32_t rversion; // virtual table's column ref's version SColRef* colRef; + int32_t numOfTagRefs; + SColRef* tagRef; // END: KEEP THIS PART SAME WITH SVCTableMeta // if the table is TSDB_CHILD_TABLE, the following information is acquired from the corresponding super table meta diff --git a/include/util/tdef.h b/include/util/tdef.h index 8382524505b1..32f36e122e58 100644 --- a/include/util/tdef.h +++ b/include/util/tdef.h @@ -673,6 +673,8 @@ typedef enum EQuantifyType { #define TSDB_MAX_USERS 2000 #define TSDB_MAX_ROLES 200 +#define TSDB_SHOW_VALIDATE_VIRTUAL_TABLE_ERROR 512 + #define PRIMARYKEY_TIMESTAMP_COL_ID 1 #define COL_REACH_END(colId, maxColId) ((colId) > (maxColId)) diff --git a/source/common/src/msg/tmsg.c b/source/common/src/msg/tmsg.c index f6ee2a1fa050..0c2ed2b9fdbe 100644 --- a/source/common/src/msg/tmsg.c +++ b/source/common/src/msg/tmsg.c @@ -9829,6 +9829,15 @@ static int32_t tEncodeSTableMetaRsp(SEncoder *pEncoder, STableMetaRsp *pRsp) { TAOS_CHECK_RETURN(tEncodeI64(pEncoder, pRsp->ownerId)); TAOS_CHECK_RETURN(tEncodeU8(pEncoder, pRsp->flag)); + // Encode tag references (new field) + TAOS_CHECK_RETURN(tEncodeI32(pEncoder, pRsp->numOfTagRefs)); + if (hasRefCol(pRsp->tableType) && pRsp->numOfTagRefs > 0) { + for (int32_t i = 0; i < pRsp->numOfTagRefs; ++i) { + SColRef *pTagRef = &pRsp->pTagRefs[i]; + TAOS_CHECK_RETURN(tEncodeSColRef(pEncoder, pTagRef)); + } + } + return 0; } @@ -9904,6 +9913,24 @@ static int32_t tDecodeSTableMetaRsp(SDecoder *pDecoder, STableMetaRsp *pRsp) { TAOS_CHECK_RETURN(tDecodeU8(pDecoder, &pRsp->flag)); } + // Decode tag references (new field, backward compatible) + pRsp->numOfTagRefs = 0; + pRsp->pTagRefs = NULL; + if (!tDecodeIsEnd(pDecoder)) { + TAOS_CHECK_RETURN(tDecodeI32(pDecoder, &pRsp->numOfTagRefs)); + if (hasRefCol(pRsp->tableType) && pRsp->numOfTagRefs > 0) { + pRsp->pTagRefs = taosMemoryMalloc(sizeof(SColRef) * pRsp->numOfTagRefs); + if (pRsp->pTagRefs == NULL) { + TAOS_CHECK_RETURN(terrno); + } + + for (int32_t i = 0; i < pRsp->numOfTagRefs; ++i) { + SColRef *pTagRef = &pRsp->pTagRefs[i]; + TAOS_CHECK_RETURN(tDecodeSColRef(pDecoder, pTagRef)); + } + } + } + return 0; } @@ -10069,6 +10096,7 @@ void tFreeSTableMetaRsp(void *pRsp) { taosMemoryFreeClear(((STableMetaRsp *)pRsp)->pSchemas); taosMemoryFreeClear(((STableMetaRsp *)pRsp)->pSchemaExt); taosMemoryFreeClear(((STableMetaRsp *)pRsp)->pColRefs); + taosMemoryFreeClear(((STableMetaRsp *)pRsp)->pTagRefs); } void tFreeSTableIndexRsp(void *info) { @@ -14421,6 +14449,19 @@ int32_t tEncodeSColRefWrapper(SEncoder *pCoder, const SColRefWrapper *pWrapper) } } + // Encode tag references (new field, appended for backward compatibility) + TAOS_CHECK_EXIT(tEncodeI32v(pCoder, pWrapper->nTagRefs)); + for (int32_t i = 0; i < pWrapper->nTagRefs; i++) { + SColRef *p = &pWrapper->pTagRef[i]; + TAOS_CHECK_EXIT(tEncodeI8(pCoder, p->hasRef)); + TAOS_CHECK_EXIT(tEncodeI16v(pCoder, p->id)); + if (p->hasRef) { + TAOS_CHECK_EXIT(tEncodeCStr(pCoder, p->refDbName)); + TAOS_CHECK_EXIT(tEncodeCStr(pCoder, p->refTableName)); + TAOS_CHECK_EXIT(tEncodeCStr(pCoder, p->refColName)); + } + } + _exit: return code; } @@ -14449,9 +14490,35 @@ int32_t tDecodeSColRefWrapperEx(SDecoder *pDecoder, SColRefWrapper *pWrapper, bo } } + // Decode tag references (new field, backward compatible - check if data remains) + pWrapper->nTagRefs = 0; + pWrapper->pTagRef = NULL; + if (!tDecodeIsEnd(pDecoder)) { + TAOS_CHECK_EXIT(tDecodeI32v(pDecoder, &pWrapper->nTagRefs)); + if (pWrapper->nTagRefs > 0) { + pWrapper->pTagRef = decoderMalloc ? (SColRef *)tDecoderMalloc(pDecoder, pWrapper->nTagRefs * sizeof(SColRef)) + : (SColRef *)taosMemoryCalloc(pWrapper->nTagRefs, sizeof(SColRef)); + if (pWrapper->pTagRef == NULL) { + TAOS_CHECK_EXIT(terrno); + } + + for (int i = 0; i < pWrapper->nTagRefs; i++) { + SColRef *p = &pWrapper->pTagRef[i]; + TAOS_CHECK_EXIT(tDecodeI8(pDecoder, (int8_t *)&p->hasRef)); + TAOS_CHECK_EXIT(tDecodeI16v(pDecoder, &p->id)); + if (p->hasRef) { + TAOS_CHECK_EXIT(tDecodeCStrTo(pDecoder, p->refDbName)); + TAOS_CHECK_EXIT(tDecodeCStrTo(pDecoder, p->refTableName)); + TAOS_CHECK_EXIT(tDecodeCStrTo(pDecoder, p->refColName)); + } + } + } + } + _exit: if (code) { taosMemoryFree(pWrapper->pColRef); + taosMemoryFree(pWrapper->pTagRef); } return code; } @@ -14917,6 +14984,7 @@ void tFreeSVCreateTbRsp(void *param) { taosMemoryFree(pRsp->pMeta->pSchemas); taosMemoryFree(pRsp->pMeta->pSchemaExt); taosMemoryFree(pRsp->pMeta->pColRefs); + taosMemoryFree(pRsp->pMeta->pTagRefs); taosMemoryFree(pRsp->pMeta); } } @@ -15414,6 +15482,7 @@ void tFreeSMAlterStbRsp(SMAlterStbRsp *pRsp) { taosMemoryFree(pRsp->pMeta->pSchemas); taosMemoryFree(pRsp->pMeta->pSchemaExt); taosMemoryFree(pRsp->pMeta->pColRefs); + taosMemoryFree(pRsp->pMeta->pTagRefs); taosMemoryFree(pRsp->pMeta); } } @@ -15466,6 +15535,7 @@ void tFreeSMCreateStbRsp(SMCreateStbRsp *pRsp) { taosMemoryFree(pRsp->pMeta->pSchemas); taosMemoryFree(pRsp->pMeta->pSchemaExt); taosMemoryFree(pRsp->pMeta->pColRefs); + taosMemoryFree(pRsp->pMeta->pTagRefs); taosMemoryFree(pRsp->pMeta); } } diff --git a/source/common/src/systable.c b/source/common/src/systable.c index 2ee3dd1899b7..9ffde0b9ee2d 100644 --- a/source/common/src/systable.c +++ b/source/common/src/systable.c @@ -26,6 +26,7 @@ #define SYSTABLE_SCH_COL_NAME_LEN ((TSDB_COL_NAME_LEN - 1) + VARSTR_HEADER_SIZE) #define SYSTABLE_SCH_VIEW_NAME_LEN ((TSDB_VIEW_NAME_LEN - 1) + VARSTR_HEADER_SIZE) + #define PERF_INSTANCE_ID_LEN (255 + VARSTR_HEADER_SIZE) #define PERF_INSTANCE_TYPE_LEN (64 + VARSTR_HEADER_SIZE) #define PERF_INSTANCE_DESC_LEN (512 + VARSTR_HEADER_SIZE) @@ -738,6 +739,20 @@ static const SSysDbTableSchema xnodeAgentsSchema[] = { {.name = "update_time", .bytes = 8, .type = TSDB_DATA_TYPE_TIMESTAMP, .sysInfo = false}, }; +static const SSysDbTableSchema virtualTablesReferencing[] = { + {.name = "virtual_db_name", .bytes = SYSTABLE_SCH_DB_NAME_LEN, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, + {.name = "virtual_stable_name", .bytes = SYSTABLE_SCH_TABLE_NAME_LEN, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, + {.name = "virtual_table_name", .bytes = SYSTABLE_SCH_TABLE_NAME_LEN, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, + {.name = "virtual_col_name", .bytes = SYSTABLE_SCH_COL_NAME_LEN, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, + {.name = "src_db_name", .bytes = SYSTABLE_SCH_DB_NAME_LEN, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, + {.name = "src_table_name", .bytes = SYSTABLE_SCH_TABLE_NAME_LEN, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, + {.name = "src_column_name", .bytes = SYSTABLE_SCH_COL_NAME_LEN , .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, + {.name = "type", .bytes = 4, .type = TSDB_DATA_TYPE_INT, .sysInfo = false}, + {.name = "err_code", .bytes = 8, .type = TSDB_DATA_TYPE_BIGINT, .sysInfo = false}, + {.name = "err_msg", .bytes = TSDB_SHOW_VALIDATE_VIRTUAL_TABLE_ERROR + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, +}; + + /** * @brief Defines the metadata for system tables. @@ -806,6 +821,8 @@ static const SSysTableMeta infosMeta[] = { {TSDB_INS_TABLE_XNODE_TASKS, xnodeTasksSchema, tListLen(xnodeTasksSchema), true, PRIV_CAT_PRIVILEGED}, {TSDB_INS_TABLE_XNODE_AGENTS, xnodeAgentsSchema, tListLen(xnodeAgentsSchema), true, PRIV_CAT_PRIVILEGED}, {TSDB_INS_TABLE_XNODE_JOBS, xnodeTaskJobSchema, tListLen(xnodeTaskJobSchema), true, PRIV_CAT_PRIVILEGED}, + {TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING, virtualTablesReferencing, tListLen(virtualTablesReferencing), true, PRIV_CAT_PRIVILEGED}, + }; static const SSysDbTableSchema connectionsSchema[] = { diff --git a/source/dnode/mgmt/node_mgmt/src/dmTransport.c b/source/dnode/mgmt/node_mgmt/src/dmTransport.c index e07df2ab318e..a6fac977343d 100644 --- a/source/dnode/mgmt/node_mgmt/src/dmTransport.c +++ b/source/dnode/mgmt/node_mgmt/src/dmTransport.c @@ -197,6 +197,7 @@ static void dmProcessRpcMsg(SDnode *pDnode, SRpcMsg *pRpc, SEpSet *pEpSet) { case TDMT_SCH_MERGE_FETCH_RSP: case TDMT_VND_SUBMIT_RSP: case TDMT_MND_GET_DB_INFO_RSP: + case TDMT_VND_TABLE_META_RSP: case TDMT_STREAM_FETCH_RSP: case TDMT_STREAM_FETCH_FROM_RUNNER_RSP: case TDMT_STREAM_FETCH_FROM_CACHE_RSP: diff --git a/source/dnode/mnode/impl/src/mndShow.c b/source/dnode/mnode/impl/src/mndShow.c index b7bbd1060808..c39c668ccdbc 100644 --- a/source/dnode/mnode/impl/src/mndShow.c +++ b/source/dnode/mnode/impl/src/mndShow.c @@ -191,6 +191,8 @@ static int32_t convertToRetrieveType(char *name, int32_t len) { type = TSDB_MGMT_TABLE_ROLE_PRIVILEGES; } else if (strncasecmp(name, TSDB_INS_TABLE_ROLE_COL_PRIVILEGES, len) == 0) { type = TSDB_MGMT_TABLE_ROLE_COL_PRIVILEGES; + } else if (strncasecmp(name, TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING, len) == 0) { + type = TSDB_MGMT_TABLE_VIRTUAL_TABLES_REFERENCING; } else { mError("invalid show name:%s len:%d", name, len); } diff --git a/source/dnode/vnode/src/meta/metaEntry.c b/source/dnode/vnode/src/meta/metaEntry.c index 826e33a9f85b..d3abca2954f6 100644 --- a/source/dnode/vnode/src/meta/metaEntry.c +++ b/source/dnode/vnode/src/meta/metaEntry.c @@ -64,6 +64,20 @@ int meteEncodeColRefEntry(SEncoder *pCoder, const SMetaEntry *pME) { TAOS_CHECK_RETURN(tEncodeCStr(pCoder, p->refColName)); } } + + // Encode tag references + TAOS_CHECK_RETURN(tEncodeI32v(pCoder, pw->nTagRefs)); + for (int32_t i = 0; i < pw->nTagRefs; i++) { + SColRef *p = &pw->pTagRef[i]; + TAOS_CHECK_RETURN(tEncodeI8(pCoder, p->hasRef)); + TAOS_CHECK_RETURN(tEncodeI16v(pCoder, p->id)); + if (p->hasRef) { + TAOS_CHECK_RETURN(tEncodeCStr(pCoder, p->refDbName)); + TAOS_CHECK_RETURN(tEncodeCStr(pCoder, p->refTableName)); + TAOS_CHECK_RETURN(tEncodeCStr(pCoder, p->refColName)); + } + } + return 0; } @@ -197,6 +211,31 @@ int meteDecodeColRefEntry(SDecoder *pDecoder, SMetaEntry *pME) { TAOS_CHECK_RETURN(tDecodeCStrTo(pDecoder, p->refColName)); } } + + // Decode tag references (backward compatible) + pWrapper->nTagRefs = 0; + pWrapper->pTagRef = NULL; + if (!tDecodeIsEnd(pDecoder)) { + TAOS_CHECK_RETURN(tDecodeI32v(pDecoder, &pWrapper->nTagRefs)); + if (pWrapper->nTagRefs > 0) { + pWrapper->pTagRef = (SColRef *)tDecoderMalloc(pDecoder, pWrapper->nTagRefs * sizeof(SColRef)); + if (pWrapper->pTagRef == NULL) { + return terrno; + } + + for (int i = 0; i < pWrapper->nTagRefs; i++) { + SColRef *p = &pWrapper->pTagRef[i]; + TAOS_CHECK_RETURN(tDecodeI8(pDecoder, (int8_t *)&p->hasRef)); + TAOS_CHECK_RETURN(tDecodeI16v(pDecoder, &p->id)); + if (p->hasRef) { + TAOS_CHECK_RETURN(tDecodeCStrTo(pDecoder, p->refDbName)); + TAOS_CHECK_RETURN(tDecodeCStrTo(pDecoder, p->refTableName)); + TAOS_CHECK_RETURN(tDecodeCStrTo(pDecoder, p->refColName)); + } + } + } + } + return 0; } diff --git a/source/dnode/vnode/src/meta/metaTable.c b/source/dnode/vnode/src/meta/metaTable.c index 12010699e07a..1dff0c7939e1 100644 --- a/source/dnode/vnode/src/meta/metaTable.c +++ b/source/dnode/vnode/src/meta/metaTable.c @@ -233,6 +233,20 @@ int32_t metaUpdateVtbMetaRsp(SMetaEntry *pEntry, char *tbName, SSchemaWrapper *p pMetaRsp->rversion = pRef->version; if (ownerId != 0) pMetaRsp->ownerId = ownerId; + // Populate tag references + if (pRef->nTagRefs > 0 && pRef->pTagRef) { + pMetaRsp->pTagRefs = taosMemoryMalloc(pRef->nTagRefs * sizeof(SColRef)); + if (NULL == pMetaRsp->pTagRefs) { + code = terrno; + goto _return; + } + memcpy(pMetaRsp->pTagRefs, pRef->pTagRef, pRef->nTagRefs * sizeof(SColRef)); + pMetaRsp->numOfTagRefs = pRef->nTagRefs; + } else { + pMetaRsp->pTagRefs = NULL; + pMetaRsp->numOfTagRefs = 0; + } + taosHashCleanup(pColRefHash); return code; _return: @@ -240,6 +254,7 @@ int32_t metaUpdateVtbMetaRsp(SMetaEntry *pEntry, char *tbName, SSchemaWrapper *p taosMemoryFreeClear(pMetaRsp->pSchemaExt); taosMemoryFreeClear(pMetaRsp->pSchemas); taosMemoryFreeClear(pMetaRsp->pColRefs); + taosMemoryFreeClear(pMetaRsp->pTagRefs); return code; } diff --git a/source/dnode/vnode/src/tsdb/tsdbRead2.c b/source/dnode/vnode/src/tsdb/tsdbRead2.c index 5803cd21fa13..a761d8b56f46 100644 --- a/source/dnode/vnode/src/tsdb/tsdbRead2.c +++ b/source/dnode/vnode/src/tsdb/tsdbRead2.c @@ -1220,6 +1220,8 @@ int32_t lino = 0; } else { TSDB_CHECK_NULL(pSup, code, lino, _end, TSDB_CODE_INVALID_PARA); varDataSetLen(pSup->buildBuf[colIndex], pColVal->value.nData); + tsdbDebug("column cid:%d data len %d, schema bytes %d, colIndex:%d", pColVal->cid, pColVal->value.nData, + pColInfoData->info.bytes, colIndex); if ((pColVal->value.nData + VARSTR_HEADER_SIZE) > pColInfoData->info.bytes) { tsdbWarn("column cid:%d actual data len %d is bigger than schema len %d", pColVal->cid, pColVal->value.nData, pColInfoData->info.bytes); diff --git a/source/dnode/vnode/src/vnd/vnodeQuery.c b/source/dnode/vnode/src/vnd/vnodeQuery.c index bf2d2a308ebe..428ed3cb9658 100644 --- a/source/dnode/vnode/src/vnd/vnodeQuery.c +++ b/source/dnode/vnode/src/vnd/vnodeQuery.c @@ -50,7 +50,7 @@ int32_t fillTableColCmpr(SMetaReader *reader, SSchemaExt *pExt, int32_t numOfCol return 0; } -void vnodePrintTableMeta(STableMetaRsp *pMeta) { +void vnodeDebugTableMeta(STableMetaRsp *pMeta) { if (!(qDebugFlag & DEBUG_DEBUG)) { return; } @@ -100,6 +100,28 @@ int32_t fillTableColRef(SMetaReader *reader, SColRef *pRef, int32_t numOfCol) { return 0; } +int32_t fillTableTagRef(SMetaReader *reader, SColRef *pRef, int32_t numOfTagRefs) { + int8_t tblType = reader->me.type; + if (hasRefCol(tblType)) { + SColRefWrapper *p = &(reader->me.colRef); + if (numOfTagRefs != p->nTagRefs) { + vError("fillTableTagRef table type:%d, tag ref num:%d, expected:%d mismatch", tblType, numOfTagRefs, p->nTagRefs); + return TSDB_CODE_APP_ERROR; + } + for (int i = 0; i < p->nTagRefs; i++) { + SColRef *pTagRef = &p->pTagRef[i]; + pRef[i].hasRef = pTagRef->hasRef; + pRef[i].id = pTagRef->id; + if (pRef[i].hasRef) { + tstrncpy(pRef[i].refDbName, pTagRef->refDbName, TSDB_DB_NAME_LEN); + tstrncpy(pRef[i].refTableName, pTagRef->refTableName, TSDB_TABLE_NAME_LEN); + tstrncpy(pRef[i].refColName, pTagRef->refColName, TSDB_COL_NAME_LEN); + } + } + } + return 0; +} + int32_t vnodeGetTableMeta(SVnode *pVnode, SRpcMsg *pMsg, bool direct) { STableInfoReq infoReq = {0}; STableMetaRsp metaRsp = {0}; @@ -236,12 +258,33 @@ int32_t vnodeGetTableMeta(SVnode *pVnode, SRpcMsg *pMsg, bool direct) { } } metaRsp.numOfColRefs = metaRsp.numOfColumns; + + // Fill tag references + if (mer1.me.colRef.nTagRefs > 0) { + metaRsp.pTagRefs = (SColRef*)taosMemoryMalloc(sizeof(SColRef) * mer1.me.colRef.nTagRefs); + if (metaRsp.pTagRefs) { + code = fillTableTagRef(&mer1, metaRsp.pTagRefs, mer1.me.colRef.nTagRefs); + if (code < 0) { + taosMemoryFreeClear(metaRsp.pTagRefs); + goto _exit; + } + } else { + code = terrno; + goto _exit; + } + metaRsp.numOfTagRefs = mer1.me.colRef.nTagRefs; + } else { + metaRsp.pTagRefs = NULL; + metaRsp.numOfTagRefs = 0; + } } else { metaRsp.pColRefs = NULL; metaRsp.numOfColRefs = 0; + metaRsp.pTagRefs = NULL; + metaRsp.numOfTagRefs = 0; } - vnodePrintTableMeta(&metaRsp); + vnodeDebugTableMeta(&metaRsp); // encode and send response rspLen = tSerializeSTableMetaRsp(NULL, 0, &metaRsp); @@ -271,6 +314,7 @@ int32_t vnodeGetTableMeta(SVnode *pVnode, SRpcMsg *pMsg, bool direct) { taosMemoryFree(metaRsp.pColRefs); taosMemoryFree(metaRsp.pSchemas); taosMemoryFree(metaRsp.pSchemaExt); + taosMemoryFree(metaRsp.pTagRefs); _exit2: metaReaderClear(&mer2); _exit3: diff --git a/source/libs/catalog/src/ctgAsync.c b/source/libs/catalog/src/ctgAsync.c index b11d9486159b..41b478882c31 100644 --- a/source/libs/catalog/src/ctgAsync.c +++ b/source/libs/catalog/src/ctgAsync.c @@ -1780,23 +1780,44 @@ int32_t ctgHandleGetTbMetaRsp(SCtgTaskReq* tReq, int32_t reqType, const SDataBuf if (CTG_IS_META_VBOTH(pOut->metaType)) { int32_t colRefSize = pOut->vctbMeta->numOfColRefs * sizeof(SColRef); + int32_t tagRefSize = pOut->vctbMeta->numOfTagRefs * sizeof(SColRef); if (pOut->tbMeta) { int32_t metaSize = CTG_META_SIZE(pOut->tbMeta); int32_t schemaExtSize = 0; if (withExtSchema(pOut->tbMeta->tableType) && pOut->tbMeta->schemaExt) { schemaExtSize = pOut->tbMeta->tableInfo.numOfColumns * sizeof(SSchemaExt); } - pOut->tbMeta = taosMemoryRealloc(pOut->tbMeta, metaSize + schemaExtSize + colRefSize); + pOut->tbMeta = taosMemoryRealloc(pOut->tbMeta, metaSize + schemaExtSize + colRefSize + tagRefSize); + if (pOut->tbMeta == NULL) { + CTG_ERR_JRET(terrno); + } + TAOS_MEMCPY(pOut->tbMeta, pOut->vctbMeta, sizeof(SVCTableMeta)); pOut->tbMeta->colRef = (SColRef *)((char *)pOut->tbMeta + metaSize + schemaExtSize); TAOS_MEMCPY(pOut->tbMeta->colRef, pOut->vctbMeta->colRef, colRefSize); + if (pOut->vctbMeta->tagRef && tagRefSize > 0) { + pOut->tbMeta->tagRef = (SColRef *)((char *)pOut->tbMeta + metaSize + schemaExtSize + colRefSize); + TAOS_MEMCPY(pOut->tbMeta->tagRef, pOut->vctbMeta->tagRef, tagRefSize); + } else { + pOut->tbMeta->tagRef = NULL; + } } else { - pOut->tbMeta = taosMemoryRealloc(pOut->tbMeta, sizeof(STableMeta) + colRefSize); + pOut->tbMeta = taosMemoryRealloc(pOut->tbMeta, sizeof(STableMeta) + colRefSize + tagRefSize); + if (pOut->tbMeta == NULL) { + CTG_ERR_JRET(terrno); + } TAOS_MEMCPY(pOut->tbMeta, pOut->vctbMeta, sizeof(SVCTableMeta)); TAOS_MEMCPY(pOut->tbMeta + sizeof(STableMeta), pOut->vctbMeta + sizeof(SVCTableMeta), colRefSize); pOut->tbMeta->colRef = (SColRef *)((char *)pOut->tbMeta + sizeof(STableMeta)); + if (pOut->vctbMeta->tagRef && tagRefSize > 0) { + pOut->tbMeta->tagRef = (SColRef *)((char *)pOut->tbMeta + sizeof(STableMeta) + colRefSize); + TAOS_MEMCPY(pOut->tbMeta->tagRef, pOut->vctbMeta->tagRef, tagRefSize); + } else { + pOut->tbMeta->tagRef = NULL; + } } pOut->tbMeta->numOfColRefs = pOut->vctbMeta->numOfColRefs; + pOut->tbMeta->numOfTagRefs = pOut->vctbMeta->numOfTagRefs; pOut->tbMeta->rversion = pOut->vctbMeta->rversion; taosMemoryFreeClear(pOut->vctbMeta); } diff --git a/source/libs/executor/src/executil.c b/source/libs/executor/src/executil.c index 0096444d66b9..f86debe1a55e 100644 --- a/source/libs/executor/src/executil.c +++ b/source/libs/executor/src/executil.c @@ -3325,12 +3325,20 @@ int32_t initQueryTableDataCondWithColArray(SQueryTableDataCond* pCond, SQueryTab for (int32_t j = 0; j < pOrgCond->numOfCols; ++j) { if (pOrgCond->colList[j].colId == pColPair->vtbColId) { pCond->colList[i].type = pOrgCond->colList[j].type; - pCond->colList[i].bytes = pOrgCond->colList[j].bytes; + // For variable-length types (nchar/binary/varchar/varbinary), use the source table's bytes + // to avoid tsdb reader doCopyColVal length check failure when source data is longer + // than the virtual table's defined length. + if (IS_VAR_DATA_TYPE(pOrgCond->colList[j].type) && pColPair->type.bytes > 0) { + pCond->colList[i].bytes = TMAX(pOrgCond->colList[j].bytes, pColPair->type.bytes); + } else { + pCond->colList[i].bytes = pOrgCond->colList[j].bytes; + } pCond->colList[i].colId = pColPair->orgColId; pCond->colList[i].pk = pOrgCond->colList[j].pk; pCond->pSlotList[i] = i; find = true; - qDebug("%s mapped vtb colId:%d to org colId:%d", __func__, pColPair->vtbColId, pColPair->orgColId); + qDebug("%s mapped vtb colId:%d to org colId:%d, type:%d, bytes:%d", __func__, pColPair->vtbColId, + pColPair->orgColId, pCond->colList[i].type, pCond->colList[i].bytes); break; } } diff --git a/source/libs/executor/src/scanoperator.c b/source/libs/executor/src/scanoperator.c index c4d132aa88d8..91f701842b42 100644 --- a/source/libs/executor/src/scanoperator.c +++ b/source/libs/executor/src/scanoperator.c @@ -1443,14 +1443,20 @@ static int32_t createVTableScanInfoFromBatchParam(SOperatorInfo* pOperator) { QUERY_CHECK_NULL(pBlockColArray, code, lino, _return, terrno); // virtual table's origin table scan do not has ts column. - SColIdPair tsPair = {.vtbColId = PRIMARYKEY_TIMESTAMP_COL_ID, .orgColId = PRIMARYKEY_TIMESTAMP_COL_ID}; + SColIdPair tsPair = {.vtbColId = PRIMARYKEY_TIMESTAMP_COL_ID, + .orgColId = PRIMARYKEY_TIMESTAMP_COL_ID, + .type.type = TSDB_DATA_TYPE_TIMESTAMP, + .type.bytes = 8}; QUERY_CHECK_NULL(taosArrayPush(pColArray, &tsPair), code, lino, _return, terrno); for (int32_t i = 0; i < taosArrayGetSize(pOrgTbInfo->colMap); ++i) { SColIdNameKV* kv = taosArrayGet(pOrgTbInfo->colMap, i); for (int32_t j = 0; j < schema->nCols; j++) { if (strcmp(kv->colName, schema->pSchema[j].name) == 0) { - SColIdPair pPair = {.vtbColId = kv->colId, .orgColId = (col_id_t)(schema->pSchema[j].colId)}; + SColIdPair pPair = {.vtbColId = kv->colId, + .orgColId = (col_id_t)(schema->pSchema[j].colId), + .type.type = schema->pSchema[j].type, + .type.bytes = schema->pSchema[j].bytes}; QUERY_CHECK_NULL(taosArrayPush(pColArray, &pPair), code, lino, _return, terrno); qDebug("dynamic vtable scan map col:%s, orgColId:%d, vtbColId:%d, %s", kv->colName, pPair.orgColId, pPair.vtbColId, GET_TASKID(pTaskInfo)); @@ -1555,6 +1561,22 @@ static int32_t createVTableScanInfoFromBatchParam(SOperatorInfo* pOperator) { code = createOneDataBlockWithColArray(pInfo->pOrgBlock, pBlockColArray, &pInfo->pResBlock); QUERY_CHECK_CODE(code, lino, _return); + // For variable-length types, update pResBlock column info.bytes to use the source table's bytes. + // This ensures tsdb reader's doCopyColVal length check won't fail when source data is longer + // than the virtual table's defined length. + for (int32_t i = 0; i < pInfo->base.cond.numOfCols; ++i) { + SColumnInfo* pCondCol = &pInfo->base.cond.colList[i]; + if (IS_VAR_DATA_TYPE(pCondCol->type)) { + for (int32_t j = 0; j < taosArrayGetSize(pInfo->pResBlock->pDataBlock); ++j) { + SColumnInfoData* pColData = taosArrayGet(pInfo->pResBlock->pDataBlock, j); + if (pColData && pColData->info.colId == pCondCol->colId) { + pColData->info.bytes = pCondCol->bytes; + break; + } + } + } + } + if (isNewTable) { if (pInfo->base.dataReader != NULL) { pAPI->tsdReader.tsdReaderClose(pInfo->base.dataReader); @@ -1707,12 +1729,18 @@ static int32_t createVTableScanInfoFromParam(SOperatorInfo* pOperator) { SColIdPair* pPair = (SColIdPair*)taosArrayGet(pColArray, i); SColMatchItem* pItem = findMatchItemByColId(pMatchList, pPair->vtbColId); if (pItem) { - if (pItem->dataType.type != pPair->type.type || pItem->dataType.bytes != pPair->type.bytes) { + if (pItem->dataType.type != pPair->type.type) { qError("column type not match for vtable colId:%d, org colId:%d, org table name:%s", pPair->vtbColId, pPair->orgColId, orgTable.me.name); code = TSDB_CODE_VTABLE_COLUMN_TYPE_MISMATCH; goto _return; } + if (!IS_VAR_DATA_TYPE(pItem->dataType.type) && pItem->dataType.bytes != pPair->type.bytes) { + qError("column bytes not match for vtable colId:%d, org colId:%d, org table name:%s", pPair->vtbColId, + pPair->orgColId, orgTable.me.name); + code = TSDB_CODE_VTABLE_COLUMN_TYPE_MISMATCH; + goto _return; + } SColIdSlotIdPair colIdSlotIdPair = {.orgColId = pPair->orgColId, .vtbSlotId = pItem->dstSlotId}; QUERY_CHECK_NULL(taosArrayPush(pBlockColArray, &colIdSlotIdPair), code, lino, _return, terrno); if (i > 0) { @@ -1754,6 +1782,22 @@ static int32_t createVTableScanInfoFromParam(SOperatorInfo* pOperator) { code = createOneDataBlockWithColArray(pInfo->pOrgBlock, pBlockColArray, &pInfo->pResBlock); QUERY_CHECK_CODE(code, lino, _return); + // For variable-length types, update pResBlock column info.bytes to use the source table's bytes. + // This ensures tsdb reader's doCopyColVal length check won't fail when source data is longer + // than the virtual table's defined length. + for (int32_t i = 0; i < pInfo->base.cond.numOfCols; ++i) { + SColumnInfo* pCondCol = &pInfo->base.cond.colList[i]; + if (IS_VAR_DATA_TYPE(pCondCol->type)) { + for (int32_t j = 0; j < taosArrayGetSize(pInfo->pResBlock->pDataBlock); ++j) { + SColumnInfoData* pColData = taosArrayGet(pInfo->pResBlock->pDataBlock, j); + if (pColData && pColData->info.colId == pCondCol->colId) { + pColData->info.bytes = pCondCol->bytes; + break; + } + } + } + } + void **reader = taosHashGet(pInfo->readerCache, &pUid, sizeof(uint64_t)); if (reader) { if (isNewScanParam(pParam)) { diff --git a/source/libs/executor/src/sysscanoperator.c b/source/libs/executor/src/sysscanoperator.c index 712a34bf558a..a789c11a8860 100644 --- a/source/libs/executor/src/sysscanoperator.c +++ b/source/libs/executor/src/sysscanoperator.c @@ -18,9 +18,11 @@ #include "functionMgt.h" #include "geosWrapper.h" #include "nodes.h" +#include "osMemPool.h" #include "querynodes.h" #include "systable.h" #include "tcommon.h" +#include "tdef.h" #include "tname.h" #include "tdatablock.h" @@ -35,6 +37,8 @@ #include "thash.h" #include "trpc.h" #include "ttypes.h" +// RPC timeout for virtual table reference validation (5 seconds) +#define VTB_REF_RPC_TIMEOUT_MS 5000 typedef int (*__optSysFilter)(void* a, void* b, int16_t dtype); typedef int32_t (*__sys_filte)(void* pMeta, SNode* cond, SArray* result); @@ -52,6 +56,24 @@ typedef struct SSysTableIndex { int32_t lastIdx; } SSysTableIndex; +typedef struct { + char vDbName[TSDB_DB_FNAME_LEN + VARSTR_HEADER_SIZE]; + char vStbName[TSDB_TABLE_FNAME_LEN + VARSTR_HEADER_SIZE]; + char vTableName[TSDB_TABLE_FNAME_LEN + VARSTR_HEADER_SIZE]; + + char vColName[TSDB_COL_NAME_LEN + VARSTR_HEADER_SIZE]; + + char refDbName[TSDB_DB_FNAME_LEN + VARSTR_HEADER_SIZE]; + char refStbName[TSDB_TABLE_FNAME_LEN + VARSTR_HEADER_SIZE]; + char refTableName[TSDB_TABLE_FNAME_LEN + VARSTR_HEADER_SIZE]; + char refColName[TSDB_COL_NAME_LEN + VARSTR_HEADER_SIZE]; + int8_t type; + int8_t isValid; + int32_t errCode; + char errMsg[64 + VARSTR_HEADER_SIZE]; + +} SVirtualTableRefInfo; + typedef struct SSysTableScanInfo { SRetrieveMetaTableRsp* pRsp; SRetrieveTableReq req; @@ -77,20 +99,20 @@ typedef struct SSysTableScanInfo { uint16_t reserved1 : 7; }; }; - SNode* pCondition; // db_name filter condition, to discard data that are not in current database - SMTbCursor* pCur; // cursor for iterate the local table meta store. - SSysTableIndex* pIdx; // idx for local table meta - SHashObj* pSchema; - SColMatchInfo matchInfo; - SName name; - SSDataBlock* pRes; - int64_t numOfBlocks; // extract basic running information. - SLoadRemoteDataInfo loadInfo; - SLimitInfo limitInfo; - int32_t tbnameSlotId; - STableListInfo* pTableListInfo; - SReadHandle* pHandle; - SStorageAPI* pAPI; + SNode* pCondition; // db_name filter condition, to discard data that are not in current database + SMTbCursor* pCur; // cursor for iterate the local table meta store. + SSysTableIndex* pIdx; // idx for local table meta + SHashObj* pSchema; + SColMatchInfo matchInfo; + SName name; + SSDataBlock* pRes; + int64_t numOfBlocks; // extract basic running information. + SLoadRemoteDataInfo loadInfo; + SLimitInfo limitInfo; + int32_t tbnameSlotId; + STableListInfo* pTableListInfo; + SReadHandle* pHandle; + SStorageAPI* pAPI; // file set iterate struct SFileSetReader* pFileSetReader; @@ -174,13 +196,25 @@ static int32_t sysTableUserTagsFillOneTableTags(const SSysTableScanInfo* pInfo, SMetaReader* smrChildTable, const char* dbname, const char* tableName, int32_t* pNumOfRows, const SSDataBlock* dataBlock); -static int32_t sysTableUserColsFillOneTableCols(const char* dbname, int32_t* pNumOfRows, - const SSDataBlock* dataBlock, char* tName, SSchemaWrapper* schemaRow, - SExtSchema* extSchemaRow, char* tableType, SColRefWrapper* colRef); +static int32_t sysTableUserColsFillOneTableCols(const char* dbname, int32_t* pNumOfRows, const SSDataBlock* dataBlock, + char* tName, SSchemaWrapper* schemaRow, SExtSchema* extSchemaRow, + char* tableType, SColRefWrapper* colRef); + +static int32_t sysTableUserColsFillOneVirtualTableCols(const SSysTableScanInfo* pInfo, const char* dbname, + int32_t* pNumOfRows, const SSDataBlock* dataBlock, char* tName, + char* stName, SSchemaWrapper* schemaRow, char* tableType, + SColRefWrapper* colRef, tb_uid_t uid, int32_t vgId); -static int32_t sysTableUserColsFillOneVirtualTableCols(const SSysTableScanInfo* pInfo, const char* dbname, int32_t* pNumOfRows, - const SSDataBlock* dataBlock, char* tName, char* stName, - SSchemaWrapper* schemaRow, char* tableType, SColRefWrapper *colRef, tb_uid_t uid, int32_t vgId); +// static int32_t sysTableFillOneVirtualTableRef(const SSysTableScanInfo* pInfo, const char* dbname, int32_t* +// pNumOfRows, +// const SSDataBlock* dataBlock, char* tName, char* stName, +// SSchemaWrapper* schemaRow, char* tableType, SColRefWrapper* colRef, +// tb_uid_t uid, int32_t vgId); + +static int32_t sysTableFillOneVirtualTableRefImpl(const SSysTableScanInfo* pInfo, SExecTaskInfo* pTaskInfo, + const char* dbname, int32_t* pNumOfRows, const SSDataBlock* dataBlock, + SSchemaWrapper* schemaRow, SColRefWrapper* pRefCol, + SVirtualTableRefInfo* pRef); static void relocateAndFilterSysTagsScanResult(SSysTableScanInfo* pInfo, int32_t numOfRows, SSDataBlock* dataBlock, SFilterInfo* pFilterInfo, SExecTaskInfo* pTaskInfo); @@ -470,6 +504,46 @@ static bool sysTableIsOperatorCondOnOneTable(SNode* pCond, char* condTable) { return false; } +static bool sysTableIsOperatorCondOnOneVTableName(SNode* pCond, char* condTable) { + SOperatorNode* node = (SOperatorNode*)pCond; + if (node->opType == OP_TYPE_EQUAL) { + if (nodeType(node->pLeft) == QUERY_NODE_COLUMN && + strcasecmp(nodesGetNameFromColumnNode(node->pLeft), "virtual_table_name") == 0 && + nodeType(node->pRight) == QUERY_NODE_VALUE) { + SValueNode* pValue = (SValueNode*)node->pRight; + if (pValue->node.resType.type == TSDB_DATA_TYPE_NCHAR || pValue->node.resType.type == TSDB_DATA_TYPE_VARCHAR) { + char* value = nodesGetValueFromNode(pValue); + tstrncpy(condTable, varDataVal(value), TSDB_TABLE_NAME_LEN); + return true; + } + } + } + return false; +} + +static bool sysTableIsCondOnOneVTableName(SNode* pCond, char* condTable) { + if (pCond == NULL) { + return false; + } + if (nodeType(pCond) == QUERY_NODE_LOGIC_CONDITION) { + SLogicConditionNode* node = (SLogicConditionNode*)pCond; + if (LOGIC_COND_TYPE_AND == node->condType) { + SNode* pChild = NULL; + FOREACH(pChild, node->pParameterList) { + if (QUERY_NODE_OPERATOR == nodeType(pChild) && sysTableIsOperatorCondOnOneVTableName(pChild, condTable)) { + return true; + } + } + } + } + + if (QUERY_NODE_OPERATOR == nodeType(pCond)) { + return sysTableIsOperatorCondOnOneVTableName(pCond, condTable); + } + + return false; +} + static bool sysTableIsCondOnOneTable(SNode* pCond, char* condTable) { if (pCond == NULL) { return false; @@ -533,7 +607,7 @@ static SSDataBlock* doOptimizeTableNameFilter(SOperatorInfo* pOperator, SSDataBl char typeName[TSDB_TABLE_FNAME_LEN + VARSTR_HEADER_SIZE] = {0}; SSchemaWrapper* schemaRow = NULL; - SExtSchema* extSchemaRow = smrTable.me.pExtSchemas; + SExtSchema* extSchemaRow = smrTable.me.pExtSchemas; SColRefWrapper* colRef = NULL; if (smrTable.me.type == TSDB_SUPER_TABLE) { schemaRow = &smrTable.me.stbEntry.schemaRow; @@ -550,7 +624,8 @@ static SSDataBlock* doOptimizeTableNameFilter(SOperatorInfo* pOperator, SSDataBl STR_TO_VARSTR(typeName, "VIRTUAL_CHILD_TABLE"); } - code = sysTableUserColsFillOneTableCols(dbname, &numOfRows, dataBlock, tableName, schemaRow, extSchemaRow, typeName, colRef); + code = sysTableUserColsFillOneTableCols(dbname, &numOfRows, dataBlock, tableName, schemaRow, extSchemaRow, typeName, + colRef); if (code != TSDB_CODE_SUCCESS) { pAPI->metaReaderFn.clearReader(&smrTable); pInfo->loadInfo.totalRows = 0; @@ -573,6 +648,116 @@ static SSDataBlock* doOptimizeTableNameFilter(SOperatorInfo* pOperator, SSDataBl return (pInfo->pRes->info.rows == 0) ? NULL : pInfo->pRes; } +static SSDataBlock* doOptimizeVTableNameFilter(SOperatorInfo* pOperator, SSDataBlock* dataBlock, char* dbname) { + SExecTaskInfo* pTaskInfo = pOperator->pTaskInfo; + SStorageAPI* pAPI = &pTaskInfo->storageAPI; + SSysTableScanInfo* pInfo = pOperator->info; + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + int32_t numOfRows = 0; + + char vtableName[TSDB_TABLE_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; + STR_TO_VARSTR(vtableName, pInfo->req.filterTb); + + SVirtualTableRefInfo* pVtableRefInfo = taosMemoryCalloc(1, sizeof(SVirtualTableRefInfo)); + if (pVtableRefInfo == NULL) { + qError("%s failed at line %d since %s", __func__, __LINE__, terrstr()); + pTaskInfo->code = terrno; + T_LONG_JMP(pTaskInfo->env, terrno); + } + + memcpy(pVtableRefInfo->vDbName, dbname, TSDB_DB_FNAME_LEN + VARSTR_HEADER_SIZE); + + SMetaReader smrTable = {0}; + SMetaReader smrSuperTable = {0}; + bool smrTableInited = false; + bool smrSuperTableInited = false; + + pAPI->metaReaderFn.initReader(&smrTable, pInfo->readHandle.vnode, META_READER_LOCK, &pAPI->metaFn); + smrTableInited = true; + code = pAPI->metaReaderFn.getTableEntryByName(&smrTable, pInfo->req.filterTb); + QUERY_CHECK_CODE(code, lino, _end); + + if (smrTable.me.type != TSDB_VIRTUAL_NORMAL_TABLE && smrTable.me.type != TSDB_VIRTUAL_CHILD_TABLE) { + pAPI->metaReaderFn.clearReader(&smrTable); + smrTableInited = false; + code = TSDB_CODE_SUCCESS; + goto _end; + } + + SSchemaWrapper* schemaRow = NULL; + SColRefWrapper* colRef = NULL; + + if (smrTable.me.type == TSDB_VIRTUAL_NORMAL_TABLE) { + schemaRow = &smrTable.me.ntbEntry.schemaRow; + colRef = &smrTable.me.colRef; + STR_TO_VARSTR(pVtableRefInfo->vStbName, smrTable.me.name); + STR_TO_VARSTR(pVtableRefInfo->vTableName, smrTable.me.name); + // Release the META_READER_LOCK before calling validateSrcTableColRef, + // which may send RPCs that could deadlock if the lock is held. + // The data (schemaRow, colRef) remains valid until clearReader is called. + pAPI->metaReaderFn.readerReleaseLock(&smrTable); + } else if (smrTable.me.type == TSDB_VIRTUAL_CHILD_TABLE) { + colRef = &smrTable.me.colRef; + STR_TO_VARSTR(pVtableRefInfo->vTableName, smrTable.me.name); + + int64_t suid = smrTable.me.ctbEntry.suid; + // Release lock but keep data valid (colRef still points to smrTable internal data) + pAPI->metaReaderFn.readerReleaseLock(&smrTable); + + // Get super table info for virtual child table + pAPI->metaReaderFn.initReader(&smrSuperTable, pInfo->readHandle.vnode, META_READER_LOCK, &pAPI->metaFn); + smrSuperTableInited = true; + code = pAPI->metaReaderFn.getTableEntryByUid(&smrSuperTable, suid); + QUERY_CHECK_CODE(code, lino, _end); + + STR_TO_VARSTR(pVtableRefInfo->vStbName, smrSuperTable.me.name); + schemaRow = &smrSuperTable.me.stbEntry.schemaRow; + + // Release lock but keep data valid for sysTableFillOneVirtualTableRefImpl + pAPI->metaReaderFn.readerReleaseLock(&smrSuperTable); + } + + if (schemaRow != NULL && schemaRow->pSchema == NULL) { + qWarn("doOptimizeVTableNameFilter: vstb schema pSchema is NULL for table %s, returning empty", pInfo->req.filterTb); + schemaRow = NULL; + } + + if (schemaRow == NULL) { + goto _end; + } + + code = sysTableFillOneVirtualTableRefImpl(pInfo, pTaskInfo, dbname, &numOfRows, dataBlock, schemaRow, colRef, + pVtableRefInfo); + QUERY_CHECK_CODE(code, lino, _end); + + if (numOfRows > 0) { + relocateAndFilterSysTagsScanResult(pInfo, numOfRows, dataBlock, pOperator->exprSupp.pFilterInfo, pTaskInfo); + numOfRows = 0; + } + + pInfo->loadInfo.totalRows += pInfo->pRes->info.rows; + setOperatorCompleted(pOperator); + + qDebug("get virtual table ref success, total rows:%" PRIu64 ", current:%" PRId64 " %s", pInfo->loadInfo.totalRows, + pInfo->pRes->info.rows, GET_TASKID(pTaskInfo)); + +_end: + if (smrTableInited) { + pAPI->metaReaderFn.clearReader(&smrTable); + } + if (smrSuperTableInited) { + pAPI->metaReaderFn.clearReader(&smrSuperTable); + } + taosMemoryFreeClear(pVtableRefInfo); + if (code != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); + pTaskInfo->code = code; + T_LONG_JMP(pTaskInfo->env, code); + } + return (pInfo->pRes->info.rows == 0) ? NULL : pInfo->pRes; +} + int32_t doExtractDbName(char* dbname, SSysTableScanInfo* pInfo, SStorageAPI* pAPI) { int32_t code = TSDB_CODE_SUCCESS; int32_t lino = 0; @@ -677,11 +862,11 @@ static SSDataBlock* sysTableScanUserCols(SOperatorInfo* pOperator) { QUERY_CHECK_CODE(code, lino, _end); } - void *pExtSchema = taosHashGet(pInfo->pExtSchema, &pInfo->pCur->mr.me.uid, sizeof(int64_t)); + void* pExtSchema = taosHashGet(pInfo->pExtSchema, &pInfo->pCur->mr.me.uid, sizeof(int64_t)); if (pExtSchema == NULL) { - SExtSchema *pExtSchema = pInfo->pCur->mr.me.pExtSchemas; + SExtSchema* pExtSchema = pInfo->pCur->mr.me.pExtSchemas; code = taosHashPut(pInfo->pExtSchema, &pInfo->pCur->mr.me.uid, sizeof(int64_t), pExtSchema, - pInfo->pCur->mr.me.stbEntry.schemaRow.nCols * sizeof(SExtSchema)); + pInfo->pCur->mr.me.stbEntry.schemaRow.nCols * sizeof(SExtSchema)); if (code == TSDB_CODE_DUP_KEY) { code = TSDB_CODE_SUCCESS; } @@ -727,9 +912,9 @@ static SSDataBlock* sysTableScanUserCols(SOperatorInfo* pOperator) { if (code == TSDB_CODE_DUP_KEY) { code = TSDB_CODE_SUCCESS; } - SExtSchema *pExtSchema = smrSuperTable.me.pExtSchemas; + SExtSchema* pExtSchema = smrSuperTable.me.pExtSchemas; code = taosHashPut(pInfo->pExtSchema, &suid, sizeof(int64_t), pExtSchema, - smrSuperTable.me.stbEntry.schemaRow.nCols * sizeof(SExtSchema)); + smrSuperTable.me.stbEntry.schemaRow.nCols * sizeof(SExtSchema)); if (code == TSDB_CODE_DUP_KEY) { code = TSDB_CODE_SUCCESS; } @@ -791,9 +976,9 @@ static SSDataBlock* sysTableScanUserCols(SOperatorInfo* pOperator) { if (code == TSDB_CODE_DUP_KEY) { code = TSDB_CODE_SUCCESS; } - SExtSchema *pExtSchema = smrSuperTable.me.pExtSchemas; + SExtSchema* pExtSchema = smrSuperTable.me.pExtSchemas; code = taosHashPut(pInfo->pExtSchema, &suid, sizeof(int64_t), pExtSchema, - smrSuperTable.me.stbEntry.schemaRow.nCols * sizeof(SExtSchema)); + smrSuperTable.me.stbEntry.schemaRow.nCols * sizeof(SExtSchema)); if (code == TSDB_CODE_DUP_KEY) { code = TSDB_CODE_SUCCESS; } @@ -817,8 +1002,8 @@ static SSDataBlock* sysTableScanUserCols(SOperatorInfo* pOperator) { } } // if pInfo->pRes->info.rows == 0, also need to add the meta to pDataBlock - code = - sysTableUserColsFillOneTableCols(dbname, &numOfRows, pDataBlock, tableName, schemaRow, extSchemaRow, typeName, colRef); + code = sysTableUserColsFillOneTableCols(dbname, &numOfRows, pDataBlock, tableName, schemaRow, extSchemaRow, + typeName, colRef); QUERY_CHECK_CODE(code, lino, _end); } @@ -948,7 +1133,7 @@ static SSDataBlock* sysTableScanUserVcCols(SOperatorInfo* pOperator) { STR_TO_VARSTR(tableName, pInfo->pCur->mr.me.name); if (!virtualChildTableNeedCollect(pInfo->pSubTableListInfo, pInfo->pCur->mr.me.uid)) { qDebug("skip virtual child table:%s uid:%" PRId64 " %s", varDataVal(tableName), pInfo->pCur->mr.me.uid, - GET_TASKID(pTaskInfo)); + GET_TASKID(pTaskInfo)); continue; } colRef = &pInfo->pCur->mr.me.colRef; @@ -1018,7 +1203,9 @@ static SSDataBlock* sysTableScanUserVcCols(SOperatorInfo* pOperator) { } } // if pInfo->pRes->info.rows == 0, also need to add the meta to pDataBlock - code = sysTableUserColsFillOneVirtualTableCols(pInfo, dbname, &numOfRows, pDataBlock, tableName, stableName, schemaRow, typeName, colRef, pInfo->pCur->mr.me.uid, pOperator->pTaskInfo->id.vgId); + code = sysTableUserColsFillOneVirtualTableCols(pInfo, dbname, &numOfRows, pDataBlock, tableName, stableName, + schemaRow, typeName, colRef, pInfo->pCur->mr.me.uid, + pOperator->pTaskInfo->id.vgId); QUERY_CHECK_CODE(code, lino, _end); } @@ -1049,145 +1236,345 @@ static SSDataBlock* sysTableScanUserVcCols(SOperatorInfo* pOperator) { return (pInfo->pRes->info.rows == 0) ? NULL : pInfo->pRes; } +static SSDataBlock* sysTableScanVirtualTableRef(SOperatorInfo* pOperator) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + SExecTaskInfo* pTaskInfo = pOperator->pTaskInfo; + SStorageAPI* pAPI = &pTaskInfo->storageAPI; + SSysTableScanInfo* pInfo = pOperator->info; + int32_t numOfRows = 0; + int32_t ret = 0; + char dbname[TSDB_DB_FNAME_LEN + VARSTR_HEADER_SIZE] = {0}; + SSDataBlock* pDataBlock = NULL; -static SSDataBlock* sysTableScanUserTags(SOperatorInfo* pOperator) { - int32_t code = TSDB_CODE_SUCCESS; - int32_t lino = 0; - SExecTaskInfo* pTaskInfo = pOperator->pTaskInfo; - SStorageAPI* pAPI = &pTaskInfo->storageAPI; - SSDataBlock* dataBlock = NULL; + // skip mnd read + if (pInfo->readHandle.mnd != NULL) { + setOperatorCompleted(pOperator); + return NULL; + } - SSysTableScanInfo* pInfo = pOperator->info; if (pOperator->status == OP_EXEC_DONE) { return NULL; } blockDataCleanup(pInfo->pRes); - int32_t numOfRows = 0; - - dataBlock = buildInfoSchemaTableMetaBlock(TSDB_INS_TABLE_TAGS); - QUERY_CHECK_NULL(dataBlock, code, lino, _end, terrno); - - code = blockDataEnsureCapacity(dataBlock, pOperator->resultInfo.capacity); - QUERY_CHECK_CODE(code, lino, _end); - const char* db = NULL; - int32_t vgId = 0; - pAPI->metaFn.getBasicInfo(pInfo->readHandle.vnode, &db, &vgId, NULL, NULL); - - SName sn = {0}; - char dbname[TSDB_DB_FNAME_LEN + VARSTR_HEADER_SIZE] = {0}; - code = tNameFromString(&sn, db, T_NAME_ACCT | T_NAME_DB); - QUERY_CHECK_CODE(code, lino, _end); + pDataBlock = buildInfoSchemaTableMetaBlock(TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING); + QUERY_CHECK_NULL(pDataBlock, code, lino, _end, terrno); - code = tNameGetDbName(&sn, varDataVal(dbname)); + code = blockDataEnsureCapacity(pDataBlock, pOperator->resultInfo.capacity); QUERY_CHECK_CODE(code, lino, _end); - varDataSetLen(dbname, strlen(varDataVal(dbname))); - - char condTableName[TSDB_TABLE_NAME_LEN] = {0}; - // optimize when sql like where table_name='tablename' and xxx. - if (pInfo->pCondition && sysTableIsCondOnOneTable(pInfo->pCondition, condTableName)) { - char tableName[TSDB_TABLE_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; - STR_TO_VARSTR(tableName, condTableName); - - SMetaReader smrChildTable = {0}; - pAPI->metaReaderFn.initReader(&smrChildTable, pInfo->readHandle.vnode, META_READER_LOCK, &pAPI->metaFn); - code = pAPI->metaReaderFn.getTableEntryByName(&smrChildTable, condTableName); - if (code != TSDB_CODE_SUCCESS) { - // terrno has been set by pAPI->metaReaderFn.getTableEntryByName, therefore, return directly - pAPI->metaReaderFn.clearReader(&smrChildTable); - blockDataDestroy(dataBlock); - pInfo->loadInfo.totalRows = 0; - return NULL; - } - - if (smrChildTable.me.type != TSDB_CHILD_TABLE && smrChildTable.me.type != TSDB_VIRTUAL_CHILD_TABLE) { - pAPI->metaReaderFn.clearReader(&smrChildTable); - blockDataDestroy(dataBlock); - pInfo->loadInfo.totalRows = 0; - return NULL; - } - - SMetaReader smrSuperTable = {0}; - pAPI->metaReaderFn.initReader(&smrSuperTable, pInfo->readHandle.vnode, META_READER_NOLOCK, &pAPI->metaFn); - code = pAPI->metaReaderFn.getTableEntryByUid(&smrSuperTable, smrChildTable.me.ctbEntry.suid); - if (code != TSDB_CODE_SUCCESS) { - // terrno has been set by pAPI->metaReaderFn.getTableEntryByUid - pAPI->metaReaderFn.clearReader(&smrSuperTable); - pAPI->metaReaderFn.clearReader(&smrChildTable); - blockDataDestroy(dataBlock); - return NULL; - } - - code = sysTableUserTagsFillOneTableTags(pInfo, &smrSuperTable, &smrChildTable, dbname, tableName, &numOfRows, - dataBlock); - - pAPI->metaReaderFn.clearReader(&smrSuperTable); - pAPI->metaReaderFn.clearReader(&smrChildTable); - + if (pInfo->pCondition && sysTableIsCondOnOneVTableName(pInfo->pCondition, pInfo->req.filterTb)) { + code = doExtractDbName(dbname, pInfo, pAPI); QUERY_CHECK_CODE(code, lino, _end); - if (numOfRows > 0) { - relocateAndFilterSysTagsScanResult(pInfo, numOfRows, dataBlock, pOperator->exprSupp.pFilterInfo, pTaskInfo); - numOfRows = 0; - } - blockDataDestroy(dataBlock); - pInfo->loadInfo.totalRows += pInfo->pRes->info.rows; - setOperatorCompleted(pOperator); + SSDataBlock* p = doOptimizeVTableNameFilter(pOperator, pDataBlock, dbname); + pInfo->pRes = p; return (pInfo->pRes->info.rows == 0) ? NULL : pInfo->pRes; } - int32_t ret = 0; + SVirtualTableRefInfo* pVtableRefInfo = taosMemoryCalloc(1, sizeof(SVirtualTableRefInfo)); + if (pVtableRefInfo == NULL) { + qError("%s failed at line %d since %s", __func__, __LINE__, terrstr()); + pTaskInfo->code = terrno; + T_LONG_JMP(pTaskInfo->env, terrno); + } + + code = doExtractDbName(pVtableRefInfo->vDbName, pInfo, pAPI); + QUERY_CHECK_CODE(code, lino, _end); + if (pInfo->pCur == NULL) { pInfo->pCur = pAPI->metaFn.openTableMetaCursor(pInfo->readHandle.vnode); - QUERY_CHECK_NULL(pInfo->pCur, code, lino, _end, terrno); } else { code = pAPI->metaFn.resumeTableMetaCursor(pInfo->pCur, 0, 0); - QUERY_CHECK_CODE(code, lino, _end); - } - - while ((ret = pAPI->metaFn.cursorNext(pInfo->pCur, TSDB_SUPER_TABLE)) == 0) { - if (pInfo->pCur->mr.me.type != TSDB_CHILD_TABLE && pInfo->pCur->mr.me.type != TSDB_VIRTUAL_CHILD_TABLE) { - continue; - } - - char tableName[TSDB_TABLE_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; - STR_TO_VARSTR(tableName, pInfo->pCur->mr.me.name); - - SMetaReader smrSuperTable = {0}; - pAPI->metaReaderFn.initReader(&smrSuperTable, pInfo->readHandle.vnode, META_READER_NOLOCK, &pAPI->metaFn); - uint64_t suid = pInfo->pCur->mr.me.ctbEntry.suid; - code = pAPI->metaReaderFn.getTableEntryByUid(&smrSuperTable, suid); - if (code != TSDB_CODE_SUCCESS) { - qError("failed to get super table meta, uid:0x%" PRIx64 ", code:%s, %s", suid, tstrerror(terrno), - GET_TASKID(pTaskInfo)); - pAPI->metaReaderFn.clearReader(&smrSuperTable); + if (code != 0) { pAPI->metaFn.closeTableMetaCursor(pInfo->pCur); pInfo->pCur = NULL; - blockDataDestroy(dataBlock); - dataBlock = NULL; - T_LONG_JMP(pTaskInfo->env, terrno); + QUERY_CHECK_CODE(code, lino, _end); } + } - if ((smrSuperTable.me.stbEntry.schemaTag.nCols + numOfRows) > pOperator->resultInfo.capacity) { - relocateAndFilterSysTagsScanResult(pInfo, numOfRows, dataBlock, pOperator->exprSupp.pFilterInfo, pTaskInfo); - numOfRows = 0; + if (pInfo->pSchema == NULL) { + pInfo->pSchema = taosHashInit(64, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BIGINT), true, HASH_NO_LOCK); + taosHashSetFreeFp(pInfo->pSchema, tDeleteSSchemaWrapperForHash); + } - if (pInfo->pRes->info.rows > 0) { - pAPI->metaFn.pauseTableMetaCursor(pInfo->pCur); - pAPI->metaReaderFn.clearReader(&smrSuperTable); - break; - } - } + if (!pInfo->pCur || !pInfo->pSchema) { + qError("sysTableScanUserVcCols failed since %s", terrstr()); + blockDataDestroy(pDataBlock); + pInfo->loadInfo.totalRows = 0; + return NULL; + } - // if pInfo->pRes->info.rows == 0, also need to add the meta to pDataBlock - code = sysTableUserTagsFillOneTableTags(pInfo, &smrSuperTable, &pInfo->pCur->mr, dbname, tableName, &numOfRows, - dataBlock); + while (((ret = pAPI->metaFn.cursorNext(pInfo->pCur, TSDB_TABLE_MAX)) == 0)) { + SSchemaWrapper* schemaRow = NULL; + SColRefWrapper* colRef = NULL; - if (code != TSDB_CODE_SUCCESS) { - qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(code)); - pAPI->metaReaderFn.clearReader(&smrSuperTable); + if (pInfo->pCur->mr.me.type == TSDB_SUPER_TABLE) { + continue; + } else if (pInfo->pCur->mr.me.type == TSDB_CHILD_TABLE) { + continue; + } else if (pInfo->pCur->mr.me.type == TSDB_NORMAL_TABLE) { + continue; + } else if (pInfo->pCur->mr.me.type == TSDB_VIRTUAL_NORMAL_TABLE) { + qDebug("sysTableScanUserVcCols cursor get virtual normal table, %s", GET_TASKID(pTaskInfo)); + + STR_TO_VARSTR(pVtableRefInfo->vTableName, pInfo->pCur->mr.me.name); + STR_TO_VARSTR(pVtableRefInfo->vStbName, pInfo->pCur->mr.me.name); + + colRef = &pInfo->pCur->mr.me.colRef; + schemaRow = &pInfo->pCur->mr.me.ntbEntry.schemaRow; + } else if (pInfo->pCur->mr.me.type == TSDB_VIRTUAL_CHILD_TABLE) { + qDebug("sysTableScanUserVcCols cursor get virtual child table, %s", GET_TASKID(pTaskInfo)); + + STR_TO_VARSTR(pVtableRefInfo->vTableName, pInfo->pCur->mr.me.name); + + colRef = &pInfo->pCur->mr.me.colRef; + int64_t suid = pInfo->pCur->mr.me.ctbEntry.suid; + void* schema = taosHashGet(pInfo->pSchema, &pInfo->pCur->mr.me.ctbEntry.suid, sizeof(int64_t)); + if (schema != NULL) { + schemaRow = *(SSchemaWrapper**)schema; + SMetaReader smrSuperTable = {0}; + pAPI->metaReaderFn.initReader(&smrSuperTable, pInfo->readHandle.vnode, META_READER_NOLOCK, &pAPI->metaFn); + code = pAPI->metaReaderFn.getTableEntryByUid(&smrSuperTable, suid); + if (code != TSDB_CODE_SUCCESS) { + // terrno has been set by pAPI->metaReaderFn.getTableEntryByName, therefore, return directly + qError("sysTableScanUserVcCols get meta by suid:%" PRId64 " error, code:%d, %s", suid, code, + GET_TASKID(pTaskInfo)); + + pAPI->metaReaderFn.clearReader(&smrSuperTable); + blockDataDestroy(pDataBlock); + pInfo->loadInfo.totalRows = 0; + return NULL; + } + STR_TO_VARSTR(pVtableRefInfo->vStbName, smrSuperTable.me.name); + pAPI->metaReaderFn.clearReader(&smrSuperTable); + } else { + SMetaReader smrSuperTable = {0}; + pAPI->metaReaderFn.initReader(&smrSuperTable, pInfo->readHandle.vnode, META_READER_NOLOCK, &pAPI->metaFn); + code = pAPI->metaReaderFn.getTableEntryByUid(&smrSuperTable, suid); + if (code != TSDB_CODE_SUCCESS) { + // terrno has been set by pAPI->metaReaderFn.getTableEntryByName, therefore, return directly + qError("sysTableScanUserVcCols get meta by suid:%" PRId64 " error, code:%d, %s", suid, code, + GET_TASKID(pTaskInfo)); + + pAPI->metaReaderFn.clearReader(&smrSuperTable); + blockDataDestroy(pDataBlock); + pInfo->loadInfo.totalRows = 0; + return NULL; + } + STR_TO_VARSTR(pVtableRefInfo->vStbName, smrSuperTable.me.name); + bool hasSchema = (smrSuperTable.me.stbEntry.schemaRow.pSchema != NULL); + SSchemaWrapper* schemaWrapper = tCloneSSchemaWrapper(&smrSuperTable.me.stbEntry.schemaRow); + pAPI->metaReaderFn.clearReader(&smrSuperTable); + if (schemaWrapper == NULL) { + if (!hasSchema) { + qWarn("sysTableScanVirtualTableRef: vstb suid:%" PRId64 + " has no schema, skipping virtual child table %s", suid, pInfo->pCur->mr.me.name); + continue; + } + code = terrno; + lino = __LINE__; + goto _end; + } + code = taosHashPut(pInfo->pSchema, &suid, sizeof(int64_t), &schemaWrapper, POINTER_BYTES); + if (code != TSDB_CODE_SUCCESS) { + tDeleteSchemaWrapper(schemaWrapper); + QUERY_CHECK_CODE(code, lino, _end); + } + + schemaRow = schemaWrapper; + QUERY_CHECK_CODE(code, lino, _end); + } + } else { + qDebug("sysTableScanUserVcCols cursor get invalid table, %s", GET_TASKID(pTaskInfo)); + continue; + } + + if ((numOfRows + schemaRow->nCols) > pOperator->resultInfo.capacity) { + relocateAndFilterSysTagsScanResult(pInfo, numOfRows, pDataBlock, pOperator->exprSupp.pFilterInfo, pTaskInfo); + numOfRows = 0; + + if (pInfo->pRes->info.rows > 0) { + pAPI->metaFn.pauseTableMetaCursor(pInfo->pCur); + break; + } + } + + // if pInfo->pRes->info.rows == 0, also need to add the meta to pDataBlock + code = sysTableFillOneVirtualTableRefImpl(pInfo, pTaskInfo, dbname, &numOfRows, pDataBlock, schemaRow, colRef, + pVtableRefInfo); + QUERY_CHECK_CODE(code, lino, _end); + } + + if (numOfRows > 0) { + pAPI->metaFn.pauseTableMetaCursor(pInfo->pCur); + relocateAndFilterSysTagsScanResult(pInfo, numOfRows, pDataBlock, pOperator->exprSupp.pFilterInfo, pTaskInfo); + numOfRows = 0; + } + + blockDataDestroy(pDataBlock); + pDataBlock = NULL; + if (ret != 0) { + pAPI->metaFn.closeTableMetaCursor(pInfo->pCur); + pInfo->pCur = NULL; + setOperatorCompleted(pOperator); + } + + pInfo->loadInfo.totalRows += pInfo->pRes->info.rows; + qDebug("get cols success, rows:%" PRIu64 " %s", pInfo->loadInfo.totalRows, GET_TASKID(pTaskInfo)); + +_end: + + taosMemoryFreeClear(pVtableRefInfo); + if (code != TSDB_CODE_SUCCESS) { + blockDataDestroy(pDataBlock); + qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); + pTaskInfo->code = code; + T_LONG_JMP(pTaskInfo->env, code); + } + return (pInfo->pRes->info.rows == 0) ? NULL : pInfo->pRes; +} + +static SSDataBlock* sysTableScanUserTags(SOperatorInfo* pOperator) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + SExecTaskInfo* pTaskInfo = pOperator->pTaskInfo; + SStorageAPI* pAPI = &pTaskInfo->storageAPI; + SSDataBlock* dataBlock = NULL; + + SSysTableScanInfo* pInfo = pOperator->info; + if (pOperator->status == OP_EXEC_DONE) { + return NULL; + } + + blockDataCleanup(pInfo->pRes); + int32_t numOfRows = 0; + + dataBlock = buildInfoSchemaTableMetaBlock(TSDB_INS_TABLE_TAGS); + QUERY_CHECK_NULL(dataBlock, code, lino, _end, terrno); + + code = blockDataEnsureCapacity(dataBlock, pOperator->resultInfo.capacity); + QUERY_CHECK_CODE(code, lino, _end); + + const char* db = NULL; + int32_t vgId = 0; + pAPI->metaFn.getBasicInfo(pInfo->readHandle.vnode, &db, &vgId, NULL, NULL); + + SName sn = {0}; + char dbname[TSDB_DB_FNAME_LEN + VARSTR_HEADER_SIZE] = {0}; + code = tNameFromString(&sn, db, T_NAME_ACCT | T_NAME_DB); + QUERY_CHECK_CODE(code, lino, _end); + + code = tNameGetDbName(&sn, varDataVal(dbname)); + QUERY_CHECK_CODE(code, lino, _end); + + varDataSetLen(dbname, strlen(varDataVal(dbname))); + + char condTableName[TSDB_TABLE_NAME_LEN] = {0}; + // optimize when sql like where table_name='tablename' and xxx. + if (pInfo->pCondition && sysTableIsCondOnOneTable(pInfo->pCondition, condTableName)) { + char tableName[TSDB_TABLE_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; + STR_TO_VARSTR(tableName, condTableName); + + SMetaReader smrChildTable = {0}; + pAPI->metaReaderFn.initReader(&smrChildTable, pInfo->readHandle.vnode, META_READER_LOCK, &pAPI->metaFn); + code = pAPI->metaReaderFn.getTableEntryByName(&smrChildTable, condTableName); + if (code != TSDB_CODE_SUCCESS) { + // terrno has been set by pAPI->metaReaderFn.getTableEntryByName, therefore, return directly + pAPI->metaReaderFn.clearReader(&smrChildTable); + blockDataDestroy(dataBlock); + pInfo->loadInfo.totalRows = 0; + return NULL; + } + + if (smrChildTable.me.type != TSDB_CHILD_TABLE && smrChildTable.me.type != TSDB_VIRTUAL_CHILD_TABLE) { + pAPI->metaReaderFn.clearReader(&smrChildTable); + blockDataDestroy(dataBlock); + pInfo->loadInfo.totalRows = 0; + return NULL; + } + + SMetaReader smrSuperTable = {0}; + pAPI->metaReaderFn.initReader(&smrSuperTable, pInfo->readHandle.vnode, META_READER_NOLOCK, &pAPI->metaFn); + code = pAPI->metaReaderFn.getTableEntryByUid(&smrSuperTable, smrChildTable.me.ctbEntry.suid); + if (code != TSDB_CODE_SUCCESS) { + // terrno has been set by pAPI->metaReaderFn.getTableEntryByUid + pAPI->metaReaderFn.clearReader(&smrSuperTable); + pAPI->metaReaderFn.clearReader(&smrChildTable); + blockDataDestroy(dataBlock); + return NULL; + } + + code = sysTableUserTagsFillOneTableTags(pInfo, &smrSuperTable, &smrChildTable, dbname, tableName, &numOfRows, + dataBlock); + + pAPI->metaReaderFn.clearReader(&smrSuperTable); + pAPI->metaReaderFn.clearReader(&smrChildTable); + + QUERY_CHECK_CODE(code, lino, _end); + + if (numOfRows > 0) { + relocateAndFilterSysTagsScanResult(pInfo, numOfRows, dataBlock, pOperator->exprSupp.pFilterInfo, pTaskInfo); + numOfRows = 0; + } + blockDataDestroy(dataBlock); + pInfo->loadInfo.totalRows += pInfo->pRes->info.rows; + setOperatorCompleted(pOperator); + return (pInfo->pRes->info.rows == 0) ? NULL : pInfo->pRes; + } + + int32_t ret = 0; + if (pInfo->pCur == NULL) { + pInfo->pCur = pAPI->metaFn.openTableMetaCursor(pInfo->readHandle.vnode); + QUERY_CHECK_NULL(pInfo->pCur, code, lino, _end, terrno); + } else { + code = pAPI->metaFn.resumeTableMetaCursor(pInfo->pCur, 0, 0); + QUERY_CHECK_CODE(code, lino, _end); + } + + while ((ret = pAPI->metaFn.cursorNext(pInfo->pCur, TSDB_SUPER_TABLE)) == 0) { + if (pInfo->pCur->mr.me.type != TSDB_CHILD_TABLE && pInfo->pCur->mr.me.type != TSDB_VIRTUAL_CHILD_TABLE) { + continue; + } + + char tableName[TSDB_TABLE_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; + STR_TO_VARSTR(tableName, pInfo->pCur->mr.me.name); + + SMetaReader smrSuperTable = {0}; + pAPI->metaReaderFn.initReader(&smrSuperTable, pInfo->readHandle.vnode, META_READER_NOLOCK, &pAPI->metaFn); + uint64_t suid = pInfo->pCur->mr.me.ctbEntry.suid; + code = pAPI->metaReaderFn.getTableEntryByUid(&smrSuperTable, suid); + if (code != TSDB_CODE_SUCCESS) { + qError("failed to get super table meta, uid:0x%" PRIx64 ", code:%s, %s", suid, tstrerror(terrno), + GET_TASKID(pTaskInfo)); + pAPI->metaReaderFn.clearReader(&smrSuperTable); + pAPI->metaFn.closeTableMetaCursor(pInfo->pCur); + pInfo->pCur = NULL; + blockDataDestroy(dataBlock); + dataBlock = NULL; + T_LONG_JMP(pTaskInfo->env, terrno); + } + + if ((smrSuperTable.me.stbEntry.schemaTag.nCols + numOfRows) > pOperator->resultInfo.capacity) { + relocateAndFilterSysTagsScanResult(pInfo, numOfRows, dataBlock, pOperator->exprSupp.pFilterInfo, pTaskInfo); + numOfRows = 0; + + if (pInfo->pRes->info.rows > 0) { + pAPI->metaFn.pauseTableMetaCursor(pInfo->pCur); + pAPI->metaReaderFn.clearReader(&smrSuperTable); + break; + } + } + + // if pInfo->pRes->info.rows == 0, also need to add the meta to pDataBlock + code = sysTableUserTagsFillOneTableTags(pInfo, &smrSuperTable, &pInfo->pCur->mr, dbname, tableName, &numOfRows, + dataBlock); + + if (code != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(code)); + pAPI->metaReaderFn.clearReader(&smrSuperTable); pAPI->metaFn.closeTableMetaCursor(pInfo->pCur); pInfo->pCur = NULL; blockDataDestroy(dataBlock); @@ -1457,277 +1844,1337 @@ static int32_t sysTableUserTagsFillOneTableTags(const SSysTableScanInfo* pInfo, } } - char* tagVarChar = NULL; - if (tagData != NULL) { - if (IS_STR_DATA_BLOB(tagType)) { - code = TSDB_CODE_BLOB_NOT_SUPPORT_TAG; - goto _end; - } + char* tagVarChar = NULL; + if (tagData != NULL) { + if (IS_STR_DATA_BLOB(tagType)) { + code = TSDB_CODE_BLOB_NOT_SUPPORT_TAG; + goto _end; + } + + if (tagType == TSDB_DATA_TYPE_JSON) { + char* tagJson = NULL; + parseTagDatatoJson(tagData, &tagJson, NULL); + if (tagJson == NULL) { + code = terrno; + goto _end; + } + tagVarChar = taosMemoryMalloc(strlen(tagJson) + VARSTR_HEADER_SIZE); + QUERY_CHECK_NULL(tagVarChar, code, lino, _end, terrno); + memcpy(varDataVal(tagVarChar), tagJson, strlen(tagJson)); + varDataSetLen(tagVarChar, strlen(tagJson)); + taosMemoryFree(tagJson); + } else { + int32_t bufSize = IS_VAR_DATA_TYPE(tagType) ? (tagLen + VARSTR_HEADER_SIZE) + : (3 + DBL_MANT_DIG - DBL_MIN_EXP + VARSTR_HEADER_SIZE); + tagVarChar = taosMemoryCalloc(1, bufSize + 1); + QUERY_CHECK_NULL(tagVarChar, code, lino, _end, terrno); + int32_t len = -1; + if (tagLen > 0) + convertTagDataToStr(varDataVal(tagVarChar), bufSize + 1 - VARSTR_HEADER_SIZE, tagType, tagData, tagLen, &len); + else + len = 0; + varDataSetLen(tagVarChar, len); + } + } + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 5); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, tagVarChar, + (tagData == NULL) || (tagType == TSDB_DATA_TYPE_JSON && tTagIsJsonNull(tagData))); + QUERY_CHECK_CODE(code, lino, _end); + + if (tagType == TSDB_DATA_TYPE_GEOMETRY || tagType == TSDB_DATA_TYPE_VARBINARY) taosMemoryFreeClear(tagData); + taosMemoryFree(tagVarChar); + ++numOfRows; + } + + *pNumOfRows = numOfRows; + +_end: + if (code != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); + } + return code; +} + +static int32_t sysTableUserColsFillOneTableCols(const char* dbname, int32_t* pNumOfRows, const SSDataBlock* dataBlock, + char* tName, SSchemaWrapper* schemaRow, SExtSchema* extSchemaRow, + char* tableType, SColRefWrapper* colRef) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + if (schemaRow == NULL) { + qError("sysTableUserColsFillOneTableCols schemaRow is NULL"); + return TSDB_CODE_SUCCESS; + } + int32_t numOfRows = *pNumOfRows; + + int32_t numOfCols = schemaRow->nCols; + for (int32_t i = 0; i < numOfCols; ++i) { + SColumnInfoData* pColInfoData = NULL; + + // table name + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 0); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, tName, false); + QUERY_CHECK_CODE(code, lino, _end); + + // database name + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 1); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, dbname, false); + QUERY_CHECK_CODE(code, lino, _end); + + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 2); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, tableType, false); + QUERY_CHECK_CODE(code, lino, _end); + + // col name + char colName[TSDB_COL_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; + STR_TO_VARSTR(colName, schemaRow->pSchema[i].name); + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 3); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, colName, false); + QUERY_CHECK_CODE(code, lino, _end); + + // col type + int8_t colType = schemaRow->pSchema[i].type; + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 4); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + int32_t colStrBufflen = 32; + char colTypeStr[VARSTR_HEADER_SIZE + 32]; + int colTypeLen = tsnprintf(varDataVal(colTypeStr), colStrBufflen, "%s", tDataTypes[colType].name); + colStrBufflen -= colTypeLen; + if (colStrBufflen <= 0) { + code = TSDB_CODE_INVALID_PARA; + QUERY_CHECK_CODE(code, lino, _end); + } + if (colType == TSDB_DATA_TYPE_VARCHAR) { + colTypeLen += tsnprintf(varDataVal(colTypeStr) + colTypeLen, colStrBufflen, "(%d)", + (int32_t)(schemaRow->pSchema[i].bytes - VARSTR_HEADER_SIZE)); + } else if (colType == TSDB_DATA_TYPE_NCHAR) { + colTypeLen += tsnprintf(varDataVal(colTypeStr) + colTypeLen, colStrBufflen, "(%d)", + (int32_t)((schemaRow->pSchema[i].bytes - VARSTR_HEADER_SIZE) / TSDB_NCHAR_SIZE)); + } else if (IS_DECIMAL_TYPE(colType)) { + QUERY_CHECK_NULL(extSchemaRow, code, lino, _end, TSDB_CODE_INVALID_PARA); + STypeMod typeMod = extSchemaRow[i].typeMod; + uint8_t prec = 0, scale = 0; + decimalFromTypeMod(typeMod, &prec, &scale); + colTypeLen += sprintf(varDataVal(colTypeStr) + colTypeLen, "(%d,%d)", prec, scale); + } + varDataSetLen(colTypeStr, colTypeLen); + code = colDataSetVal(pColInfoData, numOfRows, (char*)colTypeStr, false); + QUERY_CHECK_CODE(code, lino, _end); + + // col length + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 5); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, (const char*)&schemaRow->pSchema[i].bytes, false); + QUERY_CHECK_CODE(code, lino, _end); + + // col precision, col scale, col nullable + for (int32_t j = 6; j <= 8; ++j) { + pColInfoData = taosArrayGet(dataBlock->pDataBlock, j); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + colDataSetNULL(pColInfoData, numOfRows); + } + + // col data source + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 9); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + if (!colRef || !colRef->pColRef[i].hasRef) { + colDataSetNULL(pColInfoData, numOfRows); + } else { + char refColName[TSDB_DB_NAME_LEN + TSDB_NAME_DELIMITER_LEN + TSDB_COL_FNAME_LEN + VARSTR_HEADER_SIZE] = {0}; + char tmpColName[TSDB_DB_NAME_LEN + TSDB_NAME_DELIMITER_LEN + TSDB_COL_FNAME_LEN] = {0}; + strcat(tmpColName, colRef->pColRef[i].refDbName); + strcat(tmpColName, "."); + strcat(tmpColName, colRef->pColRef[i].refTableName); + strcat(tmpColName, "."); + strcat(tmpColName, colRef->pColRef[i].refColName); + STR_TO_VARSTR(refColName, tmpColName); + + code = colDataSetVal(pColInfoData, numOfRows, (char*)refColName, false); + QUERY_CHECK_CODE(code, lino, _end); + } + + // col id + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 10); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, (const char*)&schemaRow->pSchema[i].colId, false); + QUERY_CHECK_CODE(code, lino, _end); + + ++numOfRows; + } + + *pNumOfRows = numOfRows; + +_end: + if (code != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); + } + return code; +} + +static int32_t sysTableUserColsFillOneVirtualTableCols(const SSysTableScanInfo* pInfo, const char* dbname, + int32_t* pNumOfRows, const SSDataBlock* dataBlock, char* tName, + char* stName, SSchemaWrapper* schemaRow, char* tableType, + SColRefWrapper* colRef, tb_uid_t uid, int32_t vgId) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + if (schemaRow == NULL) { + qError("sysTableUserColsFillOneTableCols schemaRow is NULL"); + return TSDB_CODE_SUCCESS; + } + int32_t numOfRows = *pNumOfRows; + + int32_t numOfCols = schemaRow->nCols; + for (int32_t i = 0; i < numOfCols; ++i) { + SColumnInfoData* pColInfoData = NULL; + + // table name + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 0); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, tName, false); + QUERY_CHECK_CODE(code, lino, _end); + + // stable name + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 1); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, stName, false); + QUERY_CHECK_CODE(code, lino, _end); + + // database name + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 2); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, dbname, false); + QUERY_CHECK_CODE(code, lino, _end); + + // col name + char colName[TSDB_COL_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; + STR_TO_VARSTR(colName, schemaRow->pSchema[i].name); + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 3); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, colName, false); + QUERY_CHECK_CODE(code, lino, _end); + + // uid + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 4); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, (char*)&uid, false); + QUERY_CHECK_CODE(code, lino, _end); + + // col data source + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 5); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + if (!colRef || !colRef->pColRef[i].hasRef) { + colDataSetNULL(pColInfoData, numOfRows); + } else { + code = colDataSetVal(pColInfoData, numOfRows, (char*)&colRef->pColRef[i].id, false); + QUERY_CHECK_CODE(code, lino, _end); + } + + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 6); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + if (!colRef || !colRef->pColRef[i].hasRef) { + colDataSetNULL(pColInfoData, numOfRows); + } else { + char refColName[TSDB_DB_NAME_LEN + TSDB_NAME_DELIMITER_LEN + TSDB_COL_FNAME_LEN + VARSTR_HEADER_SIZE] = {0}; + char tmpColName[TSDB_DB_NAME_LEN + TSDB_NAME_DELIMITER_LEN + TSDB_COL_FNAME_LEN] = {0}; + strcat(tmpColName, colRef->pColRef[i].refDbName); + strcat(tmpColName, "."); + strcat(tmpColName, colRef->pColRef[i].refTableName); + strcat(tmpColName, "."); + strcat(tmpColName, colRef->pColRef[i].refColName); + STR_TO_VARSTR(refColName, tmpColName); + + code = colDataSetVal(pColInfoData, numOfRows, (char*)refColName, false); + QUERY_CHECK_CODE(code, lino, _end); + } + + // vgid + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 7); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, (char*)&vgId, false); + QUERY_CHECK_CODE(code, lino, _end); + + // col ref version + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 8); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, (char*)&colRef->version, false); + QUERY_CHECK_CODE(code, lino, _end); + ++numOfRows; + } + + *pNumOfRows = numOfRows; + +_end: + if (code != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); + } + return code; +} + +// ===================== Virtual Table Reference Validation ===================== + +// Context for async RPC used during virtual table reference validation +typedef struct SVtbRefValidateCtx { + tsem_t ready; + int32_t rspCode; + void* pRsp; + int32_t rspLen; +} SVtbRefValidateCtx; + +// ===================== Table Schema Cache for Validation ===================== + +// Cached schema for a single table (used to avoid repeated meta reads) +typedef struct SVtbRefSchemaCache { + SSchemaWrapper schemaRow; // Data column schema + SSchemaWrapper schemaTag; // Tag schema (optional) + bool hasTagSchema; + bool ownsSchema; // true if schema is deep-copied (remote tables), false if shallow (local tables) + SHashObj* pColNameIndex; // Column name hash index for O(1) lookup +} SVtbRefSchemaCache; + +// Cache entry for a single table +typedef struct SVtbRefTableCacheEntry { + int32_t errCode; // Table validation result (TSDB_CODE_SUCCESS or error) + SVtbRefSchemaCache* pSchemaCache; // Schema cache (NULL if errCode != SUCCESS) +} SVtbRefTableCacheEntry; + +static int32_t vtbRefBuildColNameIndex(SSchema* pSchemas, int32_t numOfCols, int32_t numOfTags, SHashObj** ppIndex) { + int32_t code = 0; + int32_t totalCols = numOfCols + numOfTags; + SHashObj* pIndex = taosHashInit(totalCols > 0 ? totalCols : 8, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), + true, HASH_NO_LOCK); + if (pIndex == NULL) { + return terrno; + } + for (int32_t i = 0; i < totalCols; ++i) { + code = taosHashPut(pIndex, pSchemas[i].name, strlen(pSchemas[i].name), &i, sizeof(int32_t)); + if (code != TSDB_CODE_SUCCESS) { + taosHashCleanup(pIndex); + return code; + } + } + *ppIndex = pIndex; + return TSDB_CODE_SUCCESS; +} + +static SVtbRefSchemaCache* vtbRefCreateSchemaCache(ETableType type, SMetaReader* pReader) { + SVtbRefSchemaCache* pCache = taosMemoryCalloc(1, sizeof(SVtbRefSchemaCache)); + if (pCache == NULL) { + return NULL; + } + + int32_t code = TSDB_CODE_SUCCESS; + int32_t numOfCols = 0; + int32_t numOfTags = 0; + SSchema* pSrcSchemas = NULL; + SSchema* pSrcTagSchemas = NULL; + + if (type == TSDB_NORMAL_TABLE) { + numOfCols = pReader->me.ntbEntry.schemaRow.nCols; + pSrcSchemas = pReader->me.ntbEntry.schemaRow.pSchema; + pCache->hasTagSchema = false; + } else if (type == TSDB_CHILD_TABLE || type == TSDB_SUPER_TABLE) { + numOfCols = pReader->me.stbEntry.schemaRow.nCols; + numOfTags = pReader->me.stbEntry.schemaTag.nCols; + pSrcSchemas = pReader->me.stbEntry.schemaRow.pSchema; + pSrcTagSchemas = pReader->me.stbEntry.schemaTag.pSchema; + pCache->hasTagSchema = true; + } else { + taosMemoryFree(pCache); + return NULL; + } + + // Allocate and copy schemas (deep copy to own the memory) + int32_t totalCols = numOfCols + numOfTags; + SSchema* pAllSchemas = taosMemoryMalloc(totalCols * sizeof(SSchema)); + if (pAllSchemas == NULL) { + taosMemoryFree(pCache); + return NULL; + } + + // Copy column schemas + if (pSrcSchemas != NULL && numOfCols > 0) { + memcpy(pAllSchemas, pSrcSchemas, numOfCols * sizeof(SSchema)); + } + + // Copy tag schemas + if (pSrcTagSchemas != NULL && numOfTags > 0) { + memcpy(pAllSchemas + numOfCols, pSrcTagSchemas, numOfTags * sizeof(SSchema)); + } + + pCache->schemaRow.nCols = numOfCols; + pCache->schemaRow.pSchema = pAllSchemas; + pCache->schemaTag.nCols = numOfTags; + pCache->schemaTag.pSchema = (numOfTags > 0) ? pAllSchemas + numOfCols : NULL; + pCache->ownsSchema = true; + + code = vtbRefBuildColNameIndex(pAllSchemas, numOfCols, numOfTags, &pCache->pColNameIndex); + + if (code != TSDB_CODE_SUCCESS) { + taosMemoryFree(pAllSchemas); + taosMemoryFree(pCache); + return NULL; + } + + return pCache; +} + +static void vtbRefFreeSchemaCache(SVtbRefSchemaCache* pCache) { + if (pCache == NULL) { + return; + } + if (pCache->ownsSchema) { + taosMemoryFree(pCache->schemaRow.pSchema); + // schemaTag.pSchema points into the same memory block as schemaRow.pSchema + // Only free once to avoid double-free + pCache->schemaTag.pSchema = NULL; + } + taosHashCleanup(pCache->pColNameIndex); + taosMemoryFree(pCache); +} + +// Free cache entry (for hash table cleanup) +static void vtbRefFreeTableCacheEntry(void* p) { + SVtbRefTableCacheEntry* pEntry = (SVtbRefTableCacheEntry*)p; + if (pEntry != NULL) { + vtbRefFreeSchemaCache(pEntry->pSchemaCache); + taosMemoryFree(pEntry); + } +} + +static bool vtbRefCheckColumnInCache(const SVtbRefSchemaCache* pCache, const char* colName) { + if (pCache == NULL || colName == NULL || pCache->pColNameIndex == NULL) { + return false; + } + return taosHashGet(pCache->pColNameIndex, colName, strlen(colName)) != NULL; +} + +static int32_t vtbRefCreateSchemaCacheFromMetaRsp(STableMetaRsp* pMetaRsp, SVtbRefSchemaCache** ppCache) { + int32_t code = TSDB_CODE_SUCCESS; + + if (pMetaRsp == NULL || pMetaRsp->pSchemas == NULL) { + code = TSDB_CODE_INVALID_PARA; + return code; + } + + SVtbRefSchemaCache* pCache = taosMemoryCalloc(1, sizeof(SVtbRefSchemaCache)); + if (pCache == NULL) { + code = TSDB_CODE_OUT_OF_MEMORY; + return code; + } + + int32_t numOfCols = pMetaRsp->numOfColumns; + int32_t numOfTags = pMetaRsp->numOfTags; + int32_t totalCols = numOfCols + numOfTags; + + SSchema* pAllSchemas = taosMemoryMalloc(totalCols * sizeof(SSchema)); + if (pAllSchemas == NULL) { + taosMemoryFree(pCache); + code = TSDB_CODE_OUT_OF_MEMORY; + return code; + } + memcpy(pAllSchemas, pMetaRsp->pSchemas, totalCols * sizeof(SSchema)); + + pCache->schemaRow.nCols = numOfCols; + pCache->schemaRow.pSchema = pAllSchemas; + pCache->schemaTag.nCols = numOfTags; + pCache->schemaTag.pSchema = (numOfTags > 0) ? pAllSchemas + numOfCols : NULL; + pCache->hasTagSchema = (numOfTags > 0); + pCache->ownsSchema = true; + code = vtbRefBuildColNameIndex(pAllSchemas, numOfCols, numOfTags, &pCache->pColNameIndex); + + if (code != TSDB_CODE_SUCCESS) { + taosMemoryFree(pAllSchemas); + taosMemoryFree(pCache); + return code; + } + + *ppCache = pCache; + return TSDB_CODE_SUCCESS; +} + +static void vtbRefBuildRemoteCacheKey(char* buf, int32_t size, const char* dbName, const char* tableName) { + snprintf(buf, size, "%s.%s", dbName, tableName); +} + +static SVtbRefTableCacheEntry* vtbRefGetRemoteCacheEntry(SHashObj* pTableCache, const char* dbName, + const char* tableName) { + char key[TSDB_DB_FNAME_LEN + TSDB_TABLE_NAME_LEN + 2]; + vtbRefBuildRemoteCacheKey(key, sizeof(key), dbName, tableName); + return (SVtbRefTableCacheEntry*)taosHashGet(pTableCache, key, strlen(key)); +} + +static int32_t vtbRefPutRemoteCacheEntry(SHashObj* pTableCache, const char* dbName, const char* tableName, + SVtbRefTableCacheEntry* pEntry) { + char key[TSDB_DB_FNAME_LEN + TSDB_TABLE_NAME_LEN + 2]; + vtbRefBuildRemoteCacheKey(key, sizeof(key), dbName, tableName); + int32_t code = taosHashPut(pTableCache, key, strlen(key), pEntry, sizeof(SVtbRefTableCacheEntry)); + + return (code == TSDB_CODE_DUP_KEY) ? TSDB_CODE_SUCCESS : code; +} + +// Callback for async RPC responses during validation +static int32_t vtbRefValidateCallback(void* param, SDataBuf* pMsg, int32_t code) { + SVtbRefValidateCtx* pCtx = (SVtbRefValidateCtx*)param; + if (TSDB_CODE_SUCCESS == code) { + pCtx->pRsp = pMsg->pData; + pCtx->rspLen = (int32_t)pMsg->len; + pCtx->rspCode = TSDB_CODE_SUCCESS; + } else { + pCtx->rspCode = rpcCvtErrCode(code); + pCtx->pRsp = NULL; + pCtx->rspLen = 0; + } + int32_t res = tsem_post(&pCtx->ready); + if (res != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(res)); + } + return TSDB_CODE_SUCCESS; +} + +// Fetch DB vgroup info from MNode via RPC +static int32_t vtbRefGetDbVgInfo(void* clientRpc, SEpSet* pEpSet, int32_t acctId, const char* dbName, uint64_t reqId, + SDBVgInfo** ppVgInfo) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + SVtbRefValidateCtx ctx = {0}; + SUseDbReq usedbReq = {0}; + SUseDbRsp usedbRsp = {0}; + SUseDbOutput output = {0}; + char* buf = NULL; + + code = tsem_init(&ctx.ready, 0, 0); + QUERY_CHECK_CODE(code, lino, _return); + + // Build full db name: "acctId.dbName" + (void)snprintf(usedbReq.db, sizeof(usedbReq.db), "%d.%s", acctId, dbName); + + int32_t contLen = tSerializeSUseDbReq(NULL, 0, &usedbReq); + buf = taosMemoryCalloc(1, contLen); + QUERY_CHECK_NULL(buf, code, lino, _return, terrno); + + int32_t tempRes = tSerializeSUseDbReq(buf, contLen, &usedbReq); + if (tempRes < 0) { + code = terrno; + QUERY_CHECK_CODE(code, lino, _return); + } + + SMsgSendInfo* pMsgSendInfo = taosMemoryCalloc(1, sizeof(SMsgSendInfo)); + QUERY_CHECK_NULL(pMsgSendInfo, code, lino, _return, terrno); + + pMsgSendInfo->param = &ctx; + pMsgSendInfo->msgInfo.pData = buf; + pMsgSendInfo->msgInfo.len = contLen; + pMsgSendInfo->msgType = TDMT_MND_GET_DB_INFO; + pMsgSendInfo->fp = vtbRefValidateCallback; + pMsgSendInfo->requestId = reqId; + + code = asyncSendMsgToServer(clientRpc, pEpSet, NULL, pMsgSendInfo); + if (code != TSDB_CODE_SUCCESS) { + // buf is owned by pMsgSendInfo now, don't free it + buf = NULL; + QUERY_CHECK_CODE(code, lino, _return); + } + buf = NULL; // ownership transferred to pMsgSendInfo + + code = tsem_timewait(&ctx.ready, VTB_REF_RPC_TIMEOUT_MS); + QUERY_CHECK_CODE(code, lino, _return); + + if (ctx.rspCode != TSDB_CODE_SUCCESS) { + code = ctx.rspCode; + QUERY_CHECK_CODE(code, lino, _return); + } + + // Parse the response + SUseDbRsp* pRsp = taosMemoryMalloc(sizeof(SUseDbRsp)); + QUERY_CHECK_NULL(pRsp, code, lino, _return, terrno); + + code = tDeserializeSUseDbRsp(ctx.pRsp, ctx.rspLen, pRsp); + if (code != TSDB_CODE_SUCCESS) { + taosMemoryFree(pRsp); + QUERY_CHECK_CODE(code, lino, _return); + } + + code = queryBuildUseDbOutput(&output, pRsp); + tFreeSUsedbRsp(pRsp); + taosMemoryFree(pRsp); + QUERY_CHECK_CODE(code, lino, _return); + + *ppVgInfo = output.dbVgroup; + output.dbVgroup = NULL; // ownership transferred + +_return: + taosMemoryFreeClear(ctx.pRsp); + TAOS_UNUSED(tsem_destroy(&ctx.ready)); + taosMemoryFree(buf); + if (output.dbVgroup) { + freeVgInfo(output.dbVgroup); + } + if (code != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); + } + return code; +} + +static int vtbRefVgInfoComp(const void* lp, const void* rp) { + SVgroupInfo* pLeft = (SVgroupInfo*)lp; + SVgroupInfo* pRight = (SVgroupInfo*)rp; + if (pLeft->hashBegin < pRight->hashBegin) return -1; + if (pLeft->hashBegin > pRight->hashBegin) return 1; + return 0; +} + +static int vtbRefHashValueComp(const void* lp, const void* rp) { + uint32_t* key = (uint32_t*)lp; + SVgroupInfo* pVg = (SVgroupInfo*)rp; + if (*key < pVg->hashBegin) return -1; + if (*key > pVg->hashEnd) return 1; + return 0; +} + +// Calculate vgId for a table within a database's vgroup info +static int32_t vtbRefGetVgId(SDBVgInfo* dbInfo, const char* dbFName, const char* tbName, int32_t* pVgId, + SEpSet* pEpSet) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + + // Build vgArray if not already built + if (dbInfo->vgHash && NULL == dbInfo->vgArray) { + int32_t vgSize = taosHashGetSize(dbInfo->vgHash); + dbInfo->vgArray = taosArrayInit(vgSize, sizeof(SVgroupInfo)); + QUERY_CHECK_NULL(dbInfo->vgArray, code, lino, _return, terrno); + + void* pIter = taosHashIterate(dbInfo->vgHash, NULL); + while (pIter) { + if (NULL == taosArrayPush(dbInfo->vgArray, pIter)) { + taosHashCancelIterate(dbInfo->vgHash, pIter); + code = terrno; + QUERY_CHECK_CODE(code, lino, _return); + } + pIter = taosHashIterate(dbInfo->vgHash, pIter); + } + + taosArraySort(dbInfo->vgArray, vtbRefVgInfoComp); + } + + int32_t vgNum = (int32_t)taosArrayGetSize(dbInfo->vgArray); + if (vgNum <= 0) { + qError("db vgroup cache invalid, db:%s, vgroup number:%d", dbFName, vgNum); + code = TSDB_CODE_TSC_DB_NOT_SELECTED; + QUERY_CHECK_CODE(code, lino, _return); + } + + // Calculate hash value for the table + char tbFullName[TSDB_TABLE_FNAME_LEN]; + (void)snprintf(tbFullName, sizeof(tbFullName), "%s.%s", dbFName, tbName); + uint32_t hashValue = taosGetTbHashVal(tbFullName, (int32_t)strlen(tbFullName), dbInfo->hashMethod, dbInfo->hashPrefix, + dbInfo->hashSuffix); + + // Binary search for the vgroup + SVgroupInfo* vgInfo = taosArraySearch(dbInfo->vgArray, &hashValue, vtbRefHashValueComp, TD_EQ); + + if (NULL == vgInfo) { + qError("no hash range found for hash value [%u], db:%s, numOfVgId:%d", hashValue, dbFName, vgNum); + code = TSDB_CODE_CTG_INTERNAL_ERROR; + QUERY_CHECK_CODE(code, lino, _return); + } + + *pVgId = vgInfo->vgId; + if (pEpSet) { + *pEpSet = vgInfo->epSet; + } + +_return: + if (code != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); + } + return code; +} + +// Fetch table schema from a specific vnode via RPC +static int32_t vtbRefFetchTableSchema(void* clientRpc, SEpSet* pVnodeEpSet, int32_t acctId, const char* dbName, + const char* tbName, int32_t vgId, uint64_t reqId, STableMetaRsp* pMetaRsp) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + SVtbRefValidateCtx ctx = {0}; + char* buf = NULL; + + code = tsem_init(&ctx.ready, 0, 0); + QUERY_CHECK_CODE(code, lino, _return); + + // Build the table info request + STableInfoReq infoReq = {0}; + infoReq.header.vgId = vgId; + infoReq.option = REQ_OPT_TBNAME; + (void)snprintf(infoReq.dbFName, sizeof(infoReq.dbFName), "%d.%s", acctId, dbName); + tstrncpy(infoReq.tbName, tbName, TSDB_TABLE_NAME_LEN); + + int32_t contLen = tSerializeSTableInfoReq(NULL, 0, &infoReq); + buf = taosMemoryCalloc(1, contLen); + QUERY_CHECK_NULL(buf, code, lino, _return, terrno); + + int32_t tempRes = tSerializeSTableInfoReq(buf, contLen, &infoReq); + if (tempRes < 0) { + code = terrno; + QUERY_CHECK_CODE(code, lino, _return); + } + + SMsgSendInfo* pMsgSendInfo = taosMemoryCalloc(1, sizeof(SMsgSendInfo)); + QUERY_CHECK_NULL(pMsgSendInfo, code, lino, _return, terrno); + + pMsgSendInfo->param = &ctx; + pMsgSendInfo->msgInfo.pData = buf; + pMsgSendInfo->msgInfo.len = contLen; + pMsgSendInfo->msgType = TDMT_VND_TABLE_META; + pMsgSendInfo->fp = vtbRefValidateCallback; + pMsgSendInfo->requestId = reqId; + + code = asyncSendMsgToServer(clientRpc, pVnodeEpSet, NULL, pMsgSendInfo); + if (code != TSDB_CODE_SUCCESS) { + // asyncSendMsgToServer already freed pMsgSendInfo (and buf via destroySendMsgInfo) on failure + buf = NULL; + QUERY_CHECK_CODE(code, lino, _return); + } + buf = NULL; // ownership transferred to pMsgSendInfo, will be freed by destroySendMsgInfo + + code = tsem_timewait(&ctx.ready, VTB_REF_RPC_TIMEOUT_MS); + QUERY_CHECK_CODE(code, lino, _return); + + if (ctx.rspCode != TSDB_CODE_SUCCESS) { + code = ctx.rspCode; + QUERY_CHECK_CODE(code, lino, _return); + } + + // Deserialize table meta response + code = tDeserializeSTableMetaRsp(ctx.pRsp, ctx.rspLen, pMetaRsp); + QUERY_CHECK_CODE(code, lino, _return); + +_return: + taosMemoryFreeClear(ctx.pRsp); + TAOS_UNUSED(tsem_destroy(&ctx.ready)); + taosMemoryFree(buf); + if (code != TSDB_CODE_SUCCESS) { + qError("%s failed at line %d since %s, db:%s, tb:%s", __func__, lino, tstrerror(code), dbName, tbName); + } + return code; +} + +// Check if a column exists in a table schema +static bool vtbRefColExistsInSchema(SSchema* pSchemas, int32_t numOfCols, int32_t numOfTags, const char* colName) { + int32_t totalCols = numOfCols + numOfTags; + for (int32_t j = 0; j < totalCols; ++j) { + if (strcmp(pSchemas[j].name, colName) == 0) { + return true; + } + } + return false; +} + +// Get table schema from local vnode meta and cache it +// Returns: TSDB_CODE_SUCCESS with pEntry filled, or error code +static int32_t vtbRefGetTableSchemaLocal(const SSysTableScanInfo* pInfo, SStorageAPI* pAPI, const char* refTableName, + SHashObj* pTableCache, SVtbRefTableCacheEntry** ppEntry) { + int32_t code = TSDB_CODE_SUCCESS; + SMetaReader srcReader = {0}; + + // Check cache first + SVtbRefTableCacheEntry* pEntry = taosHashGet(pTableCache, refTableName, strlen(refTableName)); + if (pEntry != NULL) { + *ppEntry = pEntry; + return TSDB_CODE_SUCCESS; + } + + // Create new cache entry + pEntry = taosMemoryCalloc(1, sizeof(SVtbRefTableCacheEntry)); + if (pEntry == NULL) { + return terrno; + } + + pAPI->metaReaderFn.initReader(&srcReader, pInfo->readHandle.vnode, META_READER_NOLOCK, &pAPI->metaFn); + code = pAPI->metaReaderFn.getTableEntryByName(&srcReader, refTableName); + + if (code != TSDB_CODE_SUCCESS) { + pAPI->metaReaderFn.clearReader(&srcReader); + pEntry->errCode = TSDB_CODE_TDB_TABLE_NOT_EXIST; + pEntry->pSchemaCache = NULL; + } else { + ETableType tableType = srcReader.me.type; + if (tableType == TSDB_CHILD_TABLE) { + int64_t suid = srcReader.me.ctbEntry.suid; + pAPI->metaReaderFn.clearReader(&srcReader); + pAPI->metaReaderFn.initReader(&srcReader, pInfo->readHandle.vnode, META_READER_NOLOCK, &pAPI->metaFn); + code = pAPI->metaReaderFn.getTableEntryByUid(&srcReader, suid); + if (code != TSDB_CODE_SUCCESS) { + pAPI->metaReaderFn.clearReader(&srcReader); + pEntry->errCode = TSDB_CODE_TDB_TABLE_NOT_EXIST; + pEntry->pSchemaCache = NULL; + } else { + pEntry->pSchemaCache = vtbRefCreateSchemaCache(srcReader.me.type, &srcReader); + pEntry->errCode = (pEntry->pSchemaCache != NULL) ? TSDB_CODE_SUCCESS : terrno; + pAPI->metaReaderFn.clearReader(&srcReader); + } + } else if (tableType == TSDB_NORMAL_TABLE || tableType == TSDB_SUPER_TABLE) { + pEntry->pSchemaCache = vtbRefCreateSchemaCache(tableType, &srcReader); + pEntry->errCode = (pEntry->pSchemaCache != NULL) ? TSDB_CODE_SUCCESS : terrno; + pAPI->metaReaderFn.clearReader(&srcReader); + } else { + pAPI->metaReaderFn.clearReader(&srcReader); + pEntry->errCode = TSDB_CODE_PAR_INVALID_REF_COLUMN; + pEntry->pSchemaCache = NULL; + } + } + + // Add to cache + code = taosHashPut(pTableCache, refTableName, strlen(refTableName), pEntry, sizeof(SVtbRefTableCacheEntry)); + if (code != TSDB_CODE_SUCCESS && code != TSDB_CODE_DUP_KEY) { + vtbRefFreeTableCacheEntry(pEntry); + return code; + } + + if (code == TSDB_CODE_DUP_KEY) { + // Another thread inserted first - free our new entry completely + vtbRefFreeTableCacheEntry(pEntry); + } else { + // Success - free original struct only, schema cache owned by hash table copy + taosMemoryFree(pEntry); + } + + // Return pointer to hash table's entry (not the freed original) + *ppEntry = (SVtbRefTableCacheEntry*)taosHashGet(pTableCache, refTableName, strlen(refTableName)); + return TSDB_CODE_SUCCESS; +} + +// Try to validate source table and column using local vnode meta +static int32_t vtbRefValidateLocal(const SSysTableScanInfo* pInfo, SStorageAPI* pAPI, const char* refTableName, + const char* refColName, int32_t* pErrCode) { + int32_t code = TSDB_CODE_SUCCESS; + SMetaReader srcReader = {0}; + + pAPI->metaReaderFn.initReader(&srcReader, pInfo->readHandle.vnode, META_READER_NOLOCK, &pAPI->metaFn); + code = pAPI->metaReaderFn.getTableEntryByName(&srcReader, refTableName); + + if (code != TSDB_CODE_SUCCESS) { + // Table not found locally - could be on another vnode or deleted + pAPI->metaReaderFn.clearReader(&srcReader); + *pErrCode = TSDB_CODE_TDB_TABLE_NOT_EXIST; + return TSDB_CODE_SUCCESS; // Not a fatal error, just table not on this vnode + } + + // Table found locally, check column existence + SSchemaWrapper* pSchemaRow = NULL; + SSchemaWrapper* pTagSchema = NULL; + if (srcReader.me.type == TSDB_NORMAL_TABLE) { + pSchemaRow = &srcReader.me.ntbEntry.schemaRow; + } else if (srcReader.me.type == TSDB_CHILD_TABLE) { + // For child table, we need to get the super table schema + int64_t suid = srcReader.me.ctbEntry.suid; + pAPI->metaReaderFn.clearReader(&srcReader); + + pAPI->metaReaderFn.initReader(&srcReader, pInfo->readHandle.vnode, META_READER_NOLOCK, &pAPI->metaFn); + code = pAPI->metaReaderFn.getTableEntryByUid(&srcReader, suid); + if (code != TSDB_CODE_SUCCESS) { + pAPI->metaReaderFn.clearReader(&srcReader); + *pErrCode = TSDB_CODE_TDB_TABLE_NOT_EXIST; + return TSDB_CODE_SUCCESS; + } + pSchemaRow = &srcReader.me.stbEntry.schemaRow; + pTagSchema = &srcReader.me.stbEntry.schemaTag; + } else if (srcReader.me.type == TSDB_SUPER_TABLE) { + pSchemaRow = &srcReader.me.stbEntry.schemaRow; + pTagSchema = &srcReader.me.stbEntry.schemaTag; + } else { + pAPI->metaReaderFn.clearReader(&srcReader); + *pErrCode = TSDB_CODE_PAR_INVALID_REF_COLUMN; + return TSDB_CODE_SUCCESS; + } + + // Check column existence in schema + bool found = false; + for (int32_t j = 0; j < pSchemaRow->nCols; ++j) { + if (strcmp(pSchemaRow->pSchema[j].name, refColName) == 0) { + found = true; + break; + } + } + if (!found && pTagSchema) { + for (int32_t j = 0; j < pTagSchema->nCols; ++j) { + if (strcmp(pTagSchema->pSchema[j].name, refColName) == 0) { + found = true; + break; + } + } + } + + pAPI->metaReaderFn.clearReader(&srcReader); + *pErrCode = found ? TSDB_CODE_SUCCESS : TSDB_CODE_PAR_INVALID_REF_COLUMN; + return TSDB_CODE_SUCCESS; +} - if (tagType == TSDB_DATA_TYPE_JSON) { - char* tagJson = NULL; - parseTagDatatoJson(tagData, &tagJson, NULL); - if (tagJson == NULL) { - code = terrno; - goto _end; +static int32_t vtbRefValidateRemote(void* clientRpc, SEpSet* pMnodeEpSet, int32_t acctId, const char* refDbName, + const char* refTableName, const char* refColName, uint64_t reqId, + SHashObj* pDbVgInfoCache, SHashObj* pTableCache, int32_t localVgId, + int32_t* pErrCode) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + SDBVgInfo* pDbVgInfo = NULL; + STableMetaRsp metaRsp = {0}; + bool metaRspInited = false; + + // Step 1: Get DB vgroup info (with caching) + SDBVgInfo** ppCached = (SDBVgInfo**)taosHashGet(pDbVgInfoCache, refDbName, strlen(refDbName)); + if (ppCached) { + pDbVgInfo = *ppCached; + } else { + code = vtbRefGetDbVgInfo(clientRpc, pMnodeEpSet, acctId, refDbName, reqId, &pDbVgInfo); + if (code != TSDB_CODE_SUCCESS) { + // DB doesn't exist or network error + *pErrCode = TSDB_CODE_MND_DB_NOT_EXIST; + code = TSDB_CODE_SUCCESS; // not a fatal error for validation + goto _return; + } + code = taosHashPut(pDbVgInfoCache, refDbName, strlen(refDbName), &pDbVgInfo, POINTER_BYTES); + if (code == TSDB_CODE_DUP_KEY) { + code = TSDB_CODE_SUCCESS; + } + QUERY_CHECK_CODE(code, lino, _return); + } + + // Step 2: Calculate vgId for the source table + int32_t vgId = 0; + SEpSet vnodeEpSet = {0}; + char dbFName[TSDB_DB_FNAME_LEN] = {0}; + (void)snprintf(dbFName, sizeof(dbFName), "%d.%s", acctId, refDbName); + + code = vtbRefGetVgId(pDbVgInfo, dbFName, refTableName, &vgId, &vnodeEpSet); + if (code != TSDB_CODE_SUCCESS) { + *pErrCode = TSDB_CODE_PAR_TABLE_NOT_EXIST; + code = TSDB_CODE_SUCCESS; + goto _return; + } + + // If the target vnode is the same as the local vnode, the table was already + // checked locally (and not found). Skip the RPC to avoid self-deadlock. + if (vgId == localVgId) { + *pErrCode = TSDB_CODE_PAR_TABLE_NOT_EXIST; + code = TSDB_CODE_SUCCESS; + goto _return; + } + + // Step 3: Fetch table schema from the target vnode + code = vtbRefFetchTableSchema(clientRpc, &vnodeEpSet, acctId, refDbName, refTableName, vgId, reqId, &metaRsp); + metaRspInited = true; + if (code != TSDB_CODE_SUCCESS) { + // Table doesn't exist on the target vnode + *pErrCode = TSDB_CODE_PAR_TABLE_NOT_EXIST; + code = TSDB_CODE_SUCCESS; + goto _return; + } + + bool found = vtbRefColExistsInSchema(metaRsp.pSchemas, metaRsp.numOfColumns, metaRsp.numOfTags, refColName); + *pErrCode = found ? TSDB_CODE_SUCCESS : TSDB_CODE_PAR_INVALID_REF_COLUMN; + + if (pTableCache != NULL) { + SVtbRefTableCacheEntry* pNewEntry = taosMemoryCalloc(1, sizeof(SVtbRefTableCacheEntry)); + if (pNewEntry != NULL) { + int32_t cacheCode = vtbRefCreateSchemaCacheFromMetaRsp(&metaRsp, &pNewEntry->pSchemaCache); + if (cacheCode == TSDB_CODE_SUCCESS) { + pNewEntry->errCode = TSDB_CODE_SUCCESS; + int32_t putCode = vtbRefPutRemoteCacheEntry(pTableCache, refDbName, refTableName, pNewEntry); + if (putCode == TSDB_CODE_SUCCESS) { + taosMemoryFree(pNewEntry); // Free struct only, schema cache owned by hash table + } else { + vtbRefFreeTableCacheEntry(pNewEntry); // Free both on failure } - tagVarChar = taosMemoryMalloc(strlen(tagJson) + VARSTR_HEADER_SIZE); - QUERY_CHECK_NULL(tagVarChar, code, lino, _end, terrno); - memcpy(varDataVal(tagVarChar), tagJson, strlen(tagJson)); - varDataSetLen(tagVarChar, strlen(tagJson)); - taosMemoryFree(tagJson); } else { - int32_t bufSize = IS_VAR_DATA_TYPE(tagType) ? (tagLen + VARSTR_HEADER_SIZE) - : (3 + DBL_MANT_DIG - DBL_MIN_EXP + VARSTR_HEADER_SIZE); - tagVarChar = taosMemoryCalloc(1, bufSize + 1); - QUERY_CHECK_NULL(tagVarChar, code, lino, _end, terrno); - int32_t len = -1; - if (tagLen > 0) - convertTagDataToStr(varDataVal(tagVarChar), bufSize + 1 - VARSTR_HEADER_SIZE, tagType, tagData, tagLen, &len); - else - len = 0; - varDataSetLen(tagVarChar, len); + taosMemoryFree(pNewEntry); } } - pColInfoData = taosArrayGet(dataBlock->pDataBlock, 5); - QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, tagVarChar, - (tagData == NULL) || (tagType == TSDB_DATA_TYPE_JSON && tTagIsJsonNull(tagData))); - QUERY_CHECK_CODE(code, lino, _end); - - if (tagType == TSDB_DATA_TYPE_GEOMETRY || tagType == TSDB_DATA_TYPE_VARBINARY) taosMemoryFreeClear(tagData); - taosMemoryFree(tagVarChar); - ++numOfRows; } - *pNumOfRows = numOfRows; - -_end: +_return: + if (metaRspInited) { + tFreeSTableMetaRsp(&metaRsp); + } if (code != TSDB_CODE_SUCCESS) { qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); } return code; } -static int32_t sysTableUserColsFillOneTableCols(const char* dbname, int32_t* pNumOfRows, - const SSDataBlock* dataBlock, char* tName, SSchemaWrapper* schemaRow, - SExtSchema* extSchemaRow, char* tableType, SColRefWrapper* colRef) { - int32_t code = TSDB_CODE_SUCCESS; - int32_t lino = 0; - if (schemaRow == NULL) { - qError("sysTableUserColsFillOneTableCols schemaRow is NULL"); - return TSDB_CODE_SUCCESS; - } - int32_t numOfRows = *pNumOfRows; +static int32_t validateSrcTableColRef(const SSysTableScanInfo* pInfo, SExecTaskInfo* pTaskInfo, SSchemaWrapper* pSchema, + SColRefWrapper* pColRef, SArray* pResult) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; + SStorageAPI* pAPI = &pTaskInfo->storageAPI; - int32_t numOfCols = schemaRow->nCols; - for (int32_t i = 0; i < numOfCols; ++i) { - SColumnInfoData* pColInfoData = NULL; + if (pSchema == NULL || pColRef == NULL || pResult == NULL) { + return TSDB_CODE_INVALID_PARA; + } - // table name - pColInfoData = taosArrayGet(dataBlock->pDataBlock, 0); - QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, tName, false); - QUERY_CHECK_CODE(code, lino, _end); + int32_t localVgId = 0; + pAPI->metaFn.getBasicInfo(pInfo->readHandle.vnode, NULL, &localVgId, NULL, NULL); - // database name - pColInfoData = taosArrayGet(dataBlock->pDataBlock, 1); - QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, dbname, false); - QUERY_CHECK_CODE(code, lino, _end); + SHashObj* pDbVgInfoCache = taosHashInit(4, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), true, HASH_NO_LOCK); + if (pDbVgInfoCache == NULL) { + return terrno; + } - pColInfoData = taosArrayGet(dataBlock->pDataBlock, 2); - QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, tableType, false); - QUERY_CHECK_CODE(code, lino, _end); + SHashObj* pTableCache = taosHashInit(8, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), true, HASH_NO_LOCK); + if (pTableCache == NULL) { + taosHashCleanup(pDbVgInfoCache); + return terrno; + } - // col name - char colName[TSDB_COL_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; - STR_TO_VARSTR(colName, schemaRow->pSchema[i].name); - pColInfoData = taosArrayGet(dataBlock->pDataBlock, 3); - QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, colName, false); - QUERY_CHECK_CODE(code, lino, _end); + for (int32_t i = 0; i < pSchema->nCols; ++i) { + int32_t errCode = TSDB_CODE_SUCCESS; - // col type - int8_t colType = schemaRow->pSchema[i].type; - pColInfoData = taosArrayGet(dataBlock->pDataBlock, 4); - QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - int32_t colStrBufflen = 32; - char colTypeStr[VARSTR_HEADER_SIZE + 32]; - int colTypeLen = tsnprintf(varDataVal(colTypeStr), colStrBufflen, "%s", tDataTypes[colType].name); - colStrBufflen -= colTypeLen; - if (colStrBufflen <= 0) { - code = TSDB_CODE_INVALID_PARA; - QUERY_CHECK_CODE(code, lino, _end); - } - if (colType == TSDB_DATA_TYPE_VARCHAR) { - colTypeLen += tsnprintf(varDataVal(colTypeStr) + colTypeLen, colStrBufflen, "(%d)", - (int32_t)(schemaRow->pSchema[i].bytes - VARSTR_HEADER_SIZE)); - } else if (colType == TSDB_DATA_TYPE_NCHAR) { - colTypeLen += tsnprintf(varDataVal(colTypeStr) + colTypeLen, colStrBufflen, "(%d)", - (int32_t)((schemaRow->pSchema[i].bytes - VARSTR_HEADER_SIZE) / TSDB_NCHAR_SIZE)); - } else if (IS_DECIMAL_TYPE(colType)) { - QUERY_CHECK_NULL(extSchemaRow, code, lino, _end, TSDB_CODE_INVALID_PARA); - STypeMod typeMod = extSchemaRow[i].typeMod; - uint8_t prec = 0, scale = 0; - decimalFromTypeMod(typeMod, &prec, &scale); - colTypeLen += sprintf(varDataVal(colTypeStr) + colTypeLen, "(%d,%d)", prec, scale); + if (i == 0 || pColRef == NULL || i >= pColRef->nCols || !pColRef->pColRef[i].hasRef) { + if (NULL == taosArrayPush(pResult, &errCode)) { + code = terrno; + QUERY_CHECK_CODE(code, lino, _end); + } + continue; } - varDataSetLen(colTypeStr, colTypeLen); - code = colDataSetVal(pColInfoData, numOfRows, (char*)colTypeStr, false); - QUERY_CHECK_CODE(code, lino, _end); - // col length - pColInfoData = taosArrayGet(dataBlock->pDataBlock, 5); - QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, (const char*)&schemaRow->pSchema[i].bytes, false); - QUERY_CHECK_CODE(code, lino, _end); + const char* refDbName = pColRef->pColRef[i].refDbName; + const char* refTableName = pColRef->pColRef[i].refTableName; + const char* refColName = pColRef->pColRef[i].refColName; - // col precision, col scale, col nullable - for (int32_t j = 6; j <= 8; ++j) { - pColInfoData = taosArrayGet(dataBlock->pDataBlock, j); - QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - colDataSetNULL(pColInfoData, numOfRows); - } + SVtbRefTableCacheEntry* pEntry = NULL; + code = vtbRefGetTableSchemaLocal(pInfo, pAPI, refTableName, pTableCache, &pEntry); + QUERY_CHECK_CODE(code, lino, _end); - // col data source - pColInfoData = taosArrayGet(dataBlock->pDataBlock, 9); - QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - if (!colRef || !colRef->pColRef[i].hasRef) { - colDataSetNULL(pColInfoData, numOfRows); + if (pEntry != NULL && pEntry->errCode == TSDB_CODE_SUCCESS && pEntry->pSchemaCache != NULL) { + errCode = vtbRefCheckColumnInCache(pEntry->pSchemaCache, refColName) ? TSDB_CODE_SUCCESS + : TSDB_CODE_PAR_INVALID_REF_COLUMN; + } else if (pEntry != NULL && pEntry->errCode == TSDB_CODE_TDB_TABLE_NOT_EXIST) { + if (pInfo->readHandle.pMsgCb && pInfo->readHandle.pMsgCb->clientRpc) { + SVtbRefTableCacheEntry* pRemoteEntry = vtbRefGetRemoteCacheEntry(pTableCache, refDbName, refTableName); + if (pRemoteEntry != NULL && pRemoteEntry->errCode == TSDB_CODE_SUCCESS && pRemoteEntry->pSchemaCache != NULL) { + errCode = vtbRefCheckColumnInCache(pRemoteEntry->pSchemaCache, refColName) ? TSDB_CODE_SUCCESS + : TSDB_CODE_PAR_INVALID_REF_COLUMN; + } else if (pRemoteEntry != NULL && pRemoteEntry->errCode != TSDB_CODE_SUCCESS) { + errCode = pRemoteEntry->errCode; + } else { + errCode = TSDB_CODE_SUCCESS; + code = vtbRefValidateRemote(pInfo->readHandle.pMsgCb->clientRpc, (SEpSet*)&pInfo->epSet, pInfo->accountId, + refDbName, refTableName, refColName, pTaskInfo->id.queryId, pDbVgInfoCache, + pTableCache, localVgId, &errCode); + QUERY_CHECK_CODE(code, lino, _end); + } + } else { + errCode = TSDB_CODE_TDB_TABLE_NOT_EXIST; + } + } else if (pEntry != NULL) { + errCode = pEntry->errCode; } else { - char refColName[TSDB_DB_NAME_LEN + TSDB_NAME_DELIMITER_LEN + TSDB_COL_FNAME_LEN + VARSTR_HEADER_SIZE] = {0}; - char tmpColName[TSDB_DB_NAME_LEN + TSDB_NAME_DELIMITER_LEN + TSDB_COL_FNAME_LEN] = {0}; - strcat(tmpColName, colRef->pColRef[i].refDbName); - strcat(tmpColName, "."); - strcat(tmpColName, colRef->pColRef[i].refTableName); - strcat(tmpColName, "."); - strcat(tmpColName, colRef->pColRef[i].refColName); - STR_TO_VARSTR(refColName, tmpColName); + errCode = TSDB_CODE_PAR_INVALID_REF_COLUMN; + } - code = colDataSetVal(pColInfoData, numOfRows, (char*)refColName, false); + if (NULL == taosArrayPush(pResult, &errCode)) { + code = terrno; QUERY_CHECK_CODE(code, lino, _end); } - - // col id - pColInfoData = taosArrayGet(dataBlock->pDataBlock, 10); - QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, (const char*)&schemaRow->pSchema[i].colId, false); - QUERY_CHECK_CODE(code, lino, _end); - - ++numOfRows; } - *pNumOfRows = numOfRows; +_end : { + void* pIter = taosHashIterate(pTableCache, NULL); + while (pIter) { + SVtbRefTableCacheEntry* pEntry = (SVtbRefTableCacheEntry*)pIter; + vtbRefFreeSchemaCache(pEntry->pSchemaCache); + pIter = taosHashIterate(pTableCache, pIter); + } + taosHashCleanup(pTableCache); +} + { + void* pIter = taosHashIterate(pDbVgInfoCache, NULL); + while (pIter) { + SDBVgInfo** ppVgInfo = (SDBVgInfo**)pIter; + if (*ppVgInfo) { + freeVgInfo(*ppVgInfo); + } + pIter = taosHashIterate(pDbVgInfoCache, pIter); + } + taosHashCleanup(pDbVgInfoCache); + } -_end: if (code != TSDB_CODE_SUCCESS) { qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); } return code; } -static int32_t sysTableUserColsFillOneVirtualTableCols(const SSysTableScanInfo* pInfo, const char* dbname, int32_t* pNumOfRows, - const SSDataBlock* dataBlock, char* tName, char* stName, - SSchemaWrapper* schemaRow, char* tableType, SColRefWrapper *colRef, - tb_uid_t uid, int32_t vgId) { +static int32_t getErrMsgFromCode(int32_t code, char* errMsg, int32_t cap) { + if (errMsg == NULL || cap <= 0) { + return TSDB_CODE_INVALID_PARA; + } + if (code != 0) { + char buf[128] = {0}; + snprintf(buf, sizeof(buf), "%s", tstrerror(code)); + STR_TO_VARSTR(errMsg, buf); + } else { + STR_TO_VARSTR(errMsg, ""); + } + return TSDB_CODE_SUCCESS; +} + +static int32_t sysTableFillOneVirtualTableRefImpl(const SSysTableScanInfo* pInfo, SExecTaskInfo* pTaskInfo, + const char* dbname, int32_t* pNumOfRows, const SSDataBlock* dataBlock, + SSchemaWrapper* schemaRow, SColRefWrapper* pColRef, + SVirtualTableRefInfo* pRef) { int32_t code = TSDB_CODE_SUCCESS; int32_t lino = 0; - if (schemaRow == NULL) { - qError("sysTableUserColsFillOneTableCols schemaRow is NULL"); - return TSDB_CODE_SUCCESS; + if (schemaRow == NULL || schemaRow->pSchema == NULL) { + qError("sysTableFillOneVirtualTableRefImpl schemaRow or pSchema is NULL"); + return TSDB_CODE_INVALID_PARA; } int32_t numOfRows = *pNumOfRows; int32_t numOfCols = schemaRow->nCols; - for (int32_t i = 0; i < numOfCols; ++i) { + + SArray* pResult = taosArrayInit(numOfCols, sizeof(int32_t)); + if (pResult == NULL) { + QUERY_CHECK_CODE(code = terrno, lino, _end); + } + // Only validate if pColRef is valid, otherwise just fill with error codes + if (pColRef != NULL) { + code = validateSrcTableColRef(pInfo, pTaskInfo, schemaRow, pColRef, pResult); + QUERY_CHECK_CODE(code, lino, _end); + } else { + // Fill pResult with success codes for all columns (columns without refs) + for (int32_t i = 0; i < numOfCols; ++i) { + int32_t errCode = TSDB_CODE_SUCCESS; + if (NULL == taosArrayPush(pResult, &errCode)) { + code = terrno; + QUERY_CHECK_CODE(code, lino, _end); + } + } + } + + for (int32_t i = 1; i < numOfCols; ++i) { SColumnInfoData* pColInfoData = NULL; - // table name + // Check if this column has a valid reference + bool hasValidRef = (pColRef != NULL && i < pColRef->nCols && pColRef->pColRef[i].hasRef); + + // virtual db name pColInfoData = taosArrayGet(dataBlock->pDataBlock, 0); QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, tName, false); + code = colDataSetVal(pColInfoData, numOfRows, pRef->vDbName, false); QUERY_CHECK_CODE(code, lino, _end); - // stable name + // virtual stable name pColInfoData = taosArrayGet(dataBlock->pDataBlock, 1); QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, stName, false); + code = colDataSetVal(pColInfoData, numOfRows, pRef->vStbName, false); QUERY_CHECK_CODE(code, lino, _end); - // database name + // virtual table name pColInfoData = taosArrayGet(dataBlock->pDataBlock, 2); QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, dbname, false); + code = colDataSetVal(pColInfoData, numOfRows, pRef->vTableName, false); QUERY_CHECK_CODE(code, lino, _end); - // col name - char colName[TSDB_COL_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; - STR_TO_VARSTR(colName, schemaRow->pSchema[i].name); + // virtual col name + char vColName[TSDB_COL_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; + STR_TO_VARSTR(vColName, schemaRow->pSchema[i].name); + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 3); QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, colName, false); + code = colDataSetVal(pColInfoData, numOfRows, vColName, false); QUERY_CHECK_CODE(code, lino, _end); - // uid + // src db name + char db[TSDB_DB_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; + if (hasValidRef) { + STR_TO_VARSTR(db, pColRef->pColRef[i].refDbName); + } else { + STR_TO_VARSTR(db, ""); + } + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 4); QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, (char*)&uid, false); + code = colDataSetVal(pColInfoData, numOfRows, db, false); QUERY_CHECK_CODE(code, lino, _end); + // src table name + char srcTableName[TSDB_TABLE_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; + if (hasValidRef) { + STR_TO_VARSTR(srcTableName, pColRef->pColRef[i].refTableName); + } else { + STR_TO_VARSTR(srcTableName, ""); + } - // col data source pColInfoData = taosArrayGet(dataBlock->pDataBlock, 5); QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - if (!colRef || !colRef->pColRef[i].hasRef) { - colDataSetNULL(pColInfoData, numOfRows); + code = colDataSetVal(pColInfoData, numOfRows, srcTableName, false); + QUERY_CHECK_CODE(code, lino, _end); + + // src col name + char srcColName[TSDB_COL_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; + if (hasValidRef) { + STR_TO_VARSTR(srcColName, pColRef->pColRef[i].refColName); } else { - code = colDataSetVal(pColInfoData, numOfRows, (char *)&colRef->pColRef[i].id, false); - QUERY_CHECK_CODE(code, lino, _end); + STR_TO_VARSTR(srcColName, ""); } pColInfoData = taosArrayGet(dataBlock->pDataBlock, 6); QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - if (!colRef || !colRef->pColRef[i].hasRef) { - colDataSetNULL(pColInfoData, numOfRows); - } else { - char refColName[TSDB_DB_NAME_LEN + TSDB_NAME_DELIMITER_LEN + TSDB_COL_FNAME_LEN + VARSTR_HEADER_SIZE] = {0}; - char tmpColName[TSDB_DB_NAME_LEN + TSDB_NAME_DELIMITER_LEN + TSDB_COL_FNAME_LEN] = {0}; - strcat(tmpColName, colRef->pColRef[i].refDbName); - strcat(tmpColName, "."); - strcat(tmpColName, colRef->pColRef[i].refTableName); - strcat(tmpColName, "."); - strcat(tmpColName, colRef->pColRef[i].refColName); - STR_TO_VARSTR(refColName, tmpColName); - - code = colDataSetVal(pColInfoData, numOfRows, (char *)refColName, false); - QUERY_CHECK_CODE(code, lino, _end); - } + code = colDataSetVal(pColInfoData, numOfRows, srcColName, false); + QUERY_CHECK_CODE(code, lino, _end); - // vgid + // type (col ref type: 0=column, 1=tag) + int32_t colType = 0; // default: column reference pColInfoData = taosArrayGet(dataBlock->pDataBlock, 7); QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, (char*)&vgId, false); + code = colDataSetVal(pColInfoData, numOfRows, (char*)&colType, false); QUERY_CHECK_CODE(code, lino, _end); - // col ref version + // err_code + int32_t* pColErrCode = (int32_t*)taosArrayGet(pResult, i); + int64_t errCodeVal = pColErrCode ? (int64_t)(*pColErrCode) : 0; pColInfoData = taosArrayGet(dataBlock->pDataBlock, 8); QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); - code = colDataSetVal(pColInfoData, numOfRows, (char*)&colRef->version, false); + code = colDataSetVal(pColInfoData, numOfRows, (char*)&errCodeVal, false); + QUERY_CHECK_CODE(code, lino, _end); + + // err_msg + char errMsg[TSDB_SHOW_VALIDATE_VIRTUAL_TABLE_ERROR + VARSTR_HEADER_SIZE] = {0}; + int32_t colErrCode = pColErrCode ? *pColErrCode : 0; + code = getErrMsgFromCode(colErrCode, errMsg, sizeof(errMsg)); + QUERY_CHECK_CODE(code, lino, _end); + + pColInfoData = taosArrayGet(dataBlock->pDataBlock, 9); + QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); + code = colDataSetVal(pColInfoData, numOfRows, errMsg, false); QUERY_CHECK_CODE(code, lino, _end); ++numOfRows; } - *pNumOfRows = numOfRows; - _end: if (code != TSDB_CODE_SUCCESS) { qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); } + taosArrayDestroy(pResult); return code; } +// static int32_t sysTableFillOneVirtualTableRef(const SSysTableScanInfo* pInfo, const char* dbname, int32_t* +// pNumOfRows, +// const SSDataBlock* dataBlock, char* tName, char* stName, +// SSchemaWrapper* schemaRow, char* tableType, SColRefWrapper* colRef, +// tb_uid_t uid, int32_t vgId) { +// int32_t code = TSDB_CODE_SUCCESS; +// int32_t lino = 0; +// if (schemaRow == NULL) { +// qError("sysTableUserColsFillOneTableCols schemaRow is NULL"); +// return TSDB_CODE_SUCCESS; +// } +// int32_t numOfRows = *pNumOfRows; + +// int32_t numOfCols = schemaRow->nCols; +// for (int32_t i = 0; i < numOfCols; ++i) { +// SColumnInfoData* pColInfoData = NULL; + +// // virtual db name +// pColInfoData = taosArrayGet(dataBlock->pDataBlock, 0); +// QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); +// code = colDataSetVal(pColInfoData, numOfRows, tName, false); +// QUERY_CHECK_CODE(code, lino, _end); + +// // virtual stable name +// pColInfoData = taosArrayGet(dataBlock->pDataBlock, 1); +// QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); +// code = colDataSetVal(pColInfoData, numOfRows, stName, false); +// QUERY_CHECK_CODE(code, lino, _end); + +// // virtual table name +// pColInfoData = taosArrayGet(dataBlock->pDataBlock, 2); +// QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); +// code = colDataSetVal(pColInfoData, numOfRows, dbname, false); +// QUERY_CHECK_CODE(code, lino, _end); + +// // virtual col name +// char colName[TSDB_COL_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; +// STR_TO_VARSTR(colName, schemaRow->pSchema[i].name); +// pColInfoData = taosArrayGet(dataBlock->pDataBlock, 3); +// QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); +// code = colDataSetVal(pColInfoData, numOfRows, colName, false); +// QUERY_CHECK_CODE(code, lino, _end); + +// // src db name +// pColInfoData = taosArrayGet(dataBlock->pDataBlock, 4); +// QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); +// code = colDataSetVal(pColInfoData, numOfRows, (char*)&uid, false); +// QUERY_CHECK_CODE(code, lino, _end); + +// // src stable name +// pColInfoData = taosArrayGet(dataBlock->pDataBlock, 5); +// QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); +// if (!colRef || !colRef->pColRef[i].hasRef) { +// colDataSetNULL(pColInfoData, numOfRows); +// } else { +// code = colDataSetVal(pColInfoData, numOfRows, (char*)&colRef->pColRef[i].id, false); +// QUERY_CHECK_CODE(code, lino, _end); +// } +// // src col name +// pColInfoData = taosArrayGet(dataBlock->pDataBlock, 6); +// QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); +// if (!colRef || !colRef->pColRef[i].hasRef) { +// colDataSetNULL(pColInfoData, numOfRows); +// } else { +// char refColName[TSDB_DB_NAME_LEN + TSDB_NAME_DELIMITER_LEN + TSDB_COL_FNAME_LEN + VARSTR_HEADER_SIZE] = {0}; +// char tmpColName[TSDB_DB_NAME_LEN + TSDB_NAME_DELIMITER_LEN + TSDB_COL_FNAME_LEN] = {0}; +// strcat(tmpColName, colRef->pColRef[i].refDbName); +// strcat(tmpColName, "."); +// strcat(tmpColName, colRef->pColRef[i].refTableName); +// strcat(tmpColName, "."); +// strcat(tmpColName, colRef->pColRef[i].refColName); +// STR_TO_VARSTR(refColName, tmpColName); + +// code = colDataSetVal(pColInfoData, numOfRows, (char*)refColName, false); +// QUERY_CHECK_CODE(code, lino, _end); +// } + +// // src type +// pColInfoData = taosArrayGet(dataBlock->pDataBlock, 7); +// QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); +// code = colDataSetVal(pColInfoData, numOfRows, (char*)&vgId, false); +// QUERY_CHECK_CODE(code, lino, _end); + +// // src col is valid +// pColInfoData = taosArrayGet(dataBlock->pDataBlock, 8); +// QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); +// code = colDataSetVal(pColInfoData, numOfRows, (char*)&vgId, false); +// QUERY_CHECK_CODE(code, lino, _end); + +// // src col err code +// pColInfoData = taosArrayGet(dataBlock->pDataBlock, 9); +// QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); +// code = colDataSetVal(pColInfoData, numOfRows, (char*)&vgId, false); +// QUERY_CHECK_CODE(code, lino, _end); + +// // src col err msg + +// pColInfoData = taosArrayGet(dataBlock->pDataBlock, 10); +// QUERY_CHECK_NULL(pColInfoData, code, lino, _end, terrno); +// code = colDataSetVal(pColInfoData, numOfRows, (char*)&vgId, false); +// QUERY_CHECK_CODE(code, lino, _end); + +// ++numOfRows; +// } + +// *pNumOfRows = numOfRows; + +// _end: +// if (code != TSDB_CODE_SUCCESS) { +// qError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); +// } +// return code; +// } static SSDataBlock* buildInfoSchemaTableMetaBlock(char* tableName) { size_t size = 0; @@ -3291,6 +4738,8 @@ static int32_t doSysTableScanNext(SOperatorInfo* pOperator, SSDataBlock** ppRes) pBlock = sysTableScanUsage(pOperator); } else if (strncasecmp(name, TSDB_INS_TABLE_FILESETS, TSDB_TABLE_FNAME_LEN) == 0) { pBlock = sysTableScanUserFileSets(pOperator); + } else if (strncasecmp(name, TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING, TSDB_TABLE_FNAME_LEN) == 0) { + pBlock = sysTableScanVirtualTableRef(pOperator); } else { // load the meta from mnode of the given epset pBlock = sysTableScanFromMNode(pOperator, pInfo, name, pTaskInfo); } @@ -3401,7 +4850,7 @@ static SSDataBlock* sysTableScanFromMNode(SOperatorInfo* pOperator, SSysTableSca T_LONG_JMP(pTaskInfo->env, code); } - code = tsem_wait(&pInfo->ready); + code = tsem_timewait(&pInfo->ready, VTB_REF_RPC_TIMEOUT_MS); if (code != TSDB_CODE_SUCCESS) { qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(code)); pTaskInfo->code = code; @@ -3466,7 +4915,9 @@ static int32_t resetSysTableScanOperState(SOperatorInfo* pOper) { if (strncasecmp(name, TSDB_INS_TABLE_TABLES, TSDB_TABLE_FNAME_LEN) == 0 || strncasecmp(name, TSDB_INS_TABLE_TAGS, TSDB_TABLE_FNAME_LEN) == 0 || strncasecmp(name, TSDB_INS_TABLE_COLS, TSDB_TABLE_FNAME_LEN) == 0 || - strncasecmp(name, TSDB_INS_TABLE_VC_COLS, TSDB_TABLE_FNAME_LEN) == 0 || pInfo->pCur != NULL) { + strncasecmp(name, TSDB_INS_TABLE_VC_COLS, TSDB_TABLE_FNAME_LEN) == 0 || + strncasecmp(name, TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING, TSDB_TABLE_FNAME_LEN) == 0 || + pInfo->pCur != NULL) { if (pInfo->pAPI != NULL && pInfo->pAPI->metaFn.closeTableMetaCursor != NULL) { pInfo->pAPI->metaFn.closeTableMetaCursor(pInfo->pCur); } @@ -3481,7 +4932,7 @@ static int32_t resetSysTableScanOperState(SOperatorInfo* pOper) { pInfo->loadInfo.totalRows = 0; if (pScanPhyNode->scan.virtualStableScan) { - SExecTaskInfo* pTaskInfo = pOper->pTaskInfo; + SExecTaskInfo* pTaskInfo = pOper->pTaskInfo; tableListDestroy(pInfo->pSubTableListInfo); pInfo->pSubTableListInfo = tableListCreate(); if (!pInfo->pSubTableListInfo) { @@ -3490,8 +4941,8 @@ static int32_t resetSysTableScanOperState(SOperatorInfo* pOper) { return terrno; } - int32_t code = createScanTableListInfo((SScanPhysiNode*)pScanPhyNode, NULL, false, &pInfo->readHandle, pInfo->pSubTableListInfo, NULL, - NULL, pTaskInfo, NULL); + int32_t code = createScanTableListInfo((SScanPhysiNode*)pScanPhyNode, NULL, false, &pInfo->readHandle, + pInfo->pSubTableListInfo, NULL, NULL, pTaskInfo, NULL); if (code != TSDB_CODE_SUCCESS) { pTaskInfo->code = code; tableListDestroy(pInfo->pSubTableListInfo); @@ -3601,7 +5052,7 @@ int32_t createSysTableScanOperatorInfo(void* readHandle, SSystemTableScanPhysiNo pOperator->fpSet = createOperatorFpSet(optrDummyOpenFn, doSysTableScanNext, NULL, destroySysScanOperator, optrDefaultBufFn, NULL, optrDefaultGetNextExtFn, NULL); setOperatorResetStateFn(pOperator, resetSysTableScanOperState); - + *pOptrInfo = pOperator; return code; @@ -3654,6 +5105,7 @@ void destroySysScanOperator(void* param) { strncasecmp(name, TSDB_INS_TABLE_TAGS, TSDB_TABLE_FNAME_LEN) == 0 || strncasecmp(name, TSDB_INS_TABLE_COLS, TSDB_TABLE_FNAME_LEN) == 0 || strncasecmp(name, TSDB_INS_TABLE_VC_COLS, TSDB_TABLE_FNAME_LEN) == 0 || + strncasecmp(name, TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING, TSDB_TABLE_FNAME_LEN) == 0 || pInfo->pCur != NULL) { if (pInfo->pAPI != NULL && pInfo->pAPI->metaFn.closeTableMetaCursor != NULL) { pInfo->pAPI->metaFn.closeTableMetaCursor(pInfo->pCur); diff --git a/source/libs/function/src/tudf.c b/source/libs/function/src/tudf.c index d8382ef68e7e..4943165cd5ba 100644 --- a/source/libs/function/src/tudf.c +++ b/source/libs/function/src/tudf.c @@ -1926,7 +1926,8 @@ int32_t udfcStartUvTask(SClientUvTaskNode *uvTask) { void udfcAsyncTaskCb(uv_async_t *async) { SUdfcProxy *udfc = async->data; QUEUE wq; - + QUEUE_INIT(&wq); + uv_mutex_lock(&udfc->taskQueueMutex); QUEUE_MOVE(&udfc->taskQueue, &wq); uv_mutex_unlock(&udfc->taskQueueMutex); @@ -1946,7 +1947,9 @@ void udfcAsyncTaskCb(uv_async_t *async) { } void cleanUpUvTasks(SUdfcProxy *udfc) { - fnDebug("clean up uv tasks") QUEUE wq; + fnDebug("clean up uv tasks"); + QUEUE wq; + QUEUE_INIT(&wq); uv_mutex_lock(&udfc->taskQueueMutex); QUEUE_MOVE(&udfc->taskQueue, &wq); diff --git a/source/libs/nodes/src/nodesCodeFuncs.c b/source/libs/nodes/src/nodesCodeFuncs.c index c01fc49b75f2..1322265ad311 100644 --- a/source/libs/nodes/src/nodesCodeFuncs.c +++ b/source/libs/nodes/src/nodesCodeFuncs.c @@ -421,6 +421,8 @@ const char* nodesNodeName(ENodeType type) { return "ShowRetentionsStmt"; case QUERY_NODE_SHOW_INSTANCES_STMT: return "ShowInstancesStmt"; + case QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT: + return "ShowValidateVirtualTableStmt"; case QUERY_NODE_SHOW_RETENTION_DETAILS_STMT: return "ShowRetentionDetailsStmt"; case QUERY_NODE_SHOW_ENCRYPT_ALGORITHMS_STMT: @@ -8178,6 +8180,8 @@ static const char* jkCreateVSubTableStmtSpecificTags = "SpecificTags"; static const char* jkCreateVSubTableStmtValsOfTags = "ValsOfTags"; static const char* jkCreateVSubTableStmtSpecificColRefs = "SpecificColRefs"; static const char* jkCreateVSubTableStmtValsOfColRefs = "ValsOfColRefs"; +static const char* jkCreateVSubTableStmtSpecificTagRefs = "SpecificTagRefs"; +static const char* jkCreateVSubTableStmtTagRefs = "TagRefs"; static int32_t createVSubTableStmtToJson(const void* pObj, SJson* pJson) { const SCreateVSubTableStmt* pNode = (const SCreateVSubTableStmt*)pObj; @@ -8207,6 +8211,12 @@ static int32_t createVSubTableStmtToJson(const void* pObj, SJson* pJson) { if (TSDB_CODE_SUCCESS == code) { code = nodeListToJson(pJson, jkCreateVSubTableStmtValsOfColRefs, pNode->pColRefs); } + if (TSDB_CODE_SUCCESS == code) { + code = nodeListToJson(pJson, jkCreateVSubTableStmtSpecificTagRefs, pNode->pSpecificTagRefs); + } + if (TSDB_CODE_SUCCESS == code) { + code = nodeListToJson(pJson, jkCreateVSubTableStmtTagRefs, pNode->pTagRefs); + } return code; } @@ -8238,6 +8248,12 @@ static int32_t jsonToCreateVSubTableStmt(const SJson* pJson, void* pObj) { if (TSDB_CODE_SUCCESS == code) { code = jsonToNodeList(pJson, jkCreateVSubTableStmtValsOfColRefs, &pNode->pColRefs); } + if (TSDB_CODE_SUCCESS == code) { + code = jsonToNodeList(pJson, jkCreateVSubTableStmtSpecificTagRefs, &pNode->pSpecificTagRefs); + } + if (TSDB_CODE_SUCCESS == code) { + code = jsonToNodeList(pJson, jkCreateVSubTableStmtTagRefs, &pNode->pTagRefs); + } return code; } @@ -9973,6 +9989,17 @@ static int32_t showTableDistributedStmtToJson(const void* pObj, SJson* pJson) { return code; } +static int32_t showValidateVTableStmtToJson(const void* pObj, SJson* pJson) { + const SShowValidateVirtualTable* pNode = (const SShowValidateVirtualTable*)pObj; + + int32_t code = tjsonAddStringToObject(pJson, jkShowTableDistributedStmtDbName, pNode->dbName); + if (TSDB_CODE_SUCCESS == code) { + code = tjsonAddStringToObject(pJson, jkShowTableDistributedStmtTableName, pNode->tableName); + } + + return code; +} + static int32_t jsonToShowTableDistributedStmt(const SJson* pJson, void* pObj) { SShowTableDistributedStmt* pNode = (SShowTableDistributedStmt*)pObj; @@ -9983,6 +10010,15 @@ static int32_t jsonToShowTableDistributedStmt(const SJson* pJson, void* pObj) { return code; } +static int32_t jsonToShowValidateVirtualTableStmt(const SJson* pJson, void* pObj) { + SShowValidateVirtualTable* pNode = (SShowValidateVirtualTable*)pObj; + + int32_t code = tjsonGetStringValue(pJson, jkShowTableDistributedStmtDbName, pNode->dbName); + if (TSDB_CODE_SUCCESS == code) { + code = tjsonGetStringValue(pJson, jkShowTableDistributedStmtTableName, pNode->tableName); + } + return code; +} static int32_t showLocalVariablesStmtToJson(const void* pObj, SJson* pJson) { return showStmtToJson(pObj, pJson); } @@ -10558,6 +10594,8 @@ static int32_t specificNodeToJson(const void* pObj, SJson* pJson) { return showCreateViewStmtToJson(pObj, pJson); case QUERY_NODE_SHOW_TABLE_DISTRIBUTED_STMT: return showTableDistributedStmtToJson(pObj, pJson); + case QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT: + return showValidateVTableStmtToJson(pObj, pJson); case QUERY_NODE_SHOW_LOCAL_VARIABLES_STMT: return showLocalVariablesStmtToJson(pObj, pJson); case QUERY_NODE_SHOW_TABLE_TAGS_STMT: @@ -11032,6 +11070,8 @@ static int32_t jsonToSpecificNode(const SJson* pJson, void* pObj) { return jsonToShowCreateViewStmt(pJson, pObj); case QUERY_NODE_SHOW_TABLE_DISTRIBUTED_STMT: return jsonToShowTableDistributedStmt(pJson, pObj); + case QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT: + return jsonToShowValidateVirtualTableStmt(pJson, pObj); case QUERY_NODE_SHOW_LOCAL_VARIABLES_STMT: return jsonToShowLocalVariablesStmt(pJson, pObj); case QUERY_NODE_SHOW_TABLE_TAGS_STMT: diff --git a/source/libs/nodes/src/nodesUtilFuncs.c b/source/libs/nodes/src/nodesUtilFuncs.c index 6629c05b2450..eee05a78c195 100644 --- a/source/libs/nodes/src/nodesUtilFuncs.c +++ b/source/libs/nodes/src/nodesUtilFuncs.c @@ -1220,7 +1220,11 @@ int32_t nodesMakeNode(ENodeType type, SNode** ppNodeOut) { case QUERY_NODE_ALTER_KEY_EXPIRATION_STMT: code = makeNode(type, sizeof(SAlterKeyExpirationStmt), &pNode); break; + case QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT: + code = makeNode(type, sizeof(SShowValidateVirtualTable), &pNode); + break; default: + code = TSDB_CODE_OPS_NOT_SUPPORT; break; } @@ -1760,6 +1764,8 @@ void nodesDestroyNode(SNode* pNode) { nodesDestroyList(pStmt->pColRefs); nodesDestroyList(pStmt->pSpecificTags); nodesDestroyList(pStmt->pValsOfTags); + nodesDestroyList(pStmt->pSpecificTagRefs); + nodesDestroyList(pStmt->pTagRefs); break; } case QUERY_NODE_CREATE_SUBTABLE_FROM_FILE_CLAUSE: { @@ -2097,6 +2103,10 @@ void nodesDestroyNode(SNode* pNode) { case QUERY_NODE_KILL_SCAN_STMT: case QUERY_NODE_KILL_SSMIGRATE_STMT: // no pointer field break; + case QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT: + taosMemoryFreeClear(((SShowValidateVirtualTable*)pNode)->pDbCfg); + destroyTableCfg((STableCfg*)(((SShowValidateVirtualTable*)pNode)->pTableCfg)); + break; case QUERY_NODE_SHOW_CREATE_RSMA_STMT: { SRsmaInfoRsp* pMeta = ((SShowCreateRsmaStmt*)pNode)->pRsmaMeta; if (pMeta != NULL) { diff --git a/source/libs/parser/inc/parAst.h b/source/libs/parser/inc/parAst.h index ac48ac615569..04365306e7eb 100644 --- a/source/libs/parser/inc/parAst.h +++ b/source/libs/parser/inc/parAst.h @@ -303,6 +303,8 @@ STokenTriplet* createTokenTriplet(SAstCreateContext* pCxt, SToken pName); STokenTriplet* setColumnName(SAstCreateContext* pCxt, STokenTriplet* pTokenTri, SToken pName); SNode* createColumnRefNodeByName(SAstCreateContext* pCxt, STokenTriplet* pTokenTri); SNode* createColumnRefNodeByNode(SAstCreateContext* pCxt, SToken* pColName, SNode* pRef); +SNode* createColumnRefNodeFromTriplet(SAstCreateContext* pCxt, SToken* pDb, SToken* pTable, SToken* pCol); +SNode* createColumnRefNodeFromPair(SAstCreateContext* pCxt, SToken* pTable, SToken* pCol); SNode* createColumnDefNode(SAstCreateContext* pCxt, SToken* pColName, SDataType dataType, SNode* pOptions); SNode* setColumnOptions(SAstCreateContext* pCxt, SNode* pOptions, const SToken* pVal1, void* pVal2); SNode* setColumnOptionsPK(SAstCreateContext* pCxt, SNode* pOptions); @@ -315,7 +317,8 @@ SNode* createCreateSubTableClause(SAstCreateContext* pCxt, bool ignoreExists, SN SNode* createCreateVTableStmt(SAstCreateContext* pCxt, bool ignoreExists, SNode* pRealTable, SNodeList* pCols); SNode* createCreateVSubTableStmt(SAstCreateContext* pCxt, bool ignoreExists, SNode* pRealTable, SNodeList* pSpecificColRefs, SNodeList* pColRefs, SNode* pUseRealTable, - SNodeList* pSpecificTags, SNodeList* pValsOfTags); + SNodeList* pSpecificTags, SNodeList* pValsOfTags, + SNodeList* pSpecificTagRefs, SNodeList* pTagRefs); SNode* createCreateSubTableFromFileClause(SAstCreateContext* pCxt, bool ignoreExists, SNode* pUseRealTable, SNodeList* pSpecificTags, const SToken* pFilePath); SNode* createCreateMultiTableStmt(SAstCreateContext* pCxt, SNodeList* pSubTables); @@ -508,6 +511,7 @@ SNode* createShowCompactsStmt(SAstCreateContext* pCxt, ENodeType type); SNode* createShowSsMigratesStmt(SAstCreateContext* pCxt, ENodeType type); SNode* createShowTokensStmt(SAstCreateContext* pCxt, ENodeType type); SNode* createShowTransactionDetailsStmt(SAstCreateContext* pCxt, SNode* pTransactionIdNode); +SNode* createShowValidateVirtualTableStmt(SAstCreateContext* pCxt, ENodeType type, SNode* pTable); SNode* createCreateRsmaStmt(SAstCreateContext* pCxt, bool ignoreExists, SToken* rsmaName, SNode* pRealTable, SNodeList* pFuncs, SNodeList* pIntervals); diff --git a/source/libs/parser/inc/sql.y b/source/libs/parser/inc/sql.y index a9c0271397d9..d9029ddd2169 100755 --- a/source/libs/parser/inc/sql.y +++ b/source/libs/parser/inc/sql.y @@ -1024,12 +1024,12 @@ cmd ::= CREATE VTABLE not_exists_opt(A) full_table_name(B) NK_LP column_def_list(C) NK_RP. { pCxt->pRootNode = createCreateVTableStmt(pCxt, A, B, C); } cmd ::= CREATE VTABLE not_exists_opt(A) full_table_name(B) NK_LP specific_column_ref_list(C) NK_RP USING full_table_name(D) - specific_cols_opt(E) TAGS NK_LP tags_literal_list(F) NK_RP. { pCxt->pRootNode = createCreateVSubTableStmt(pCxt, A, B, C, NULL, D, E, F); } + specific_cols_opt(E) TAGS NK_LP vtags_literal_list(F) NK_RP. { pCxt->pRootNode = createCreateVSubTableStmt(pCxt, A, B, C, NULL, D, E, F, NULL, NULL); } cmd ::= CREATE VTABLE not_exists_opt(A) full_table_name(B) NK_LP column_ref_list(C) NK_RP USING full_table_name(D) - specific_cols_opt(E) TAGS NK_LP tags_literal_list(F) NK_RP. { pCxt->pRootNode = createCreateVSubTableStmt(pCxt, A, B, NULL, C, D, E, F); } + specific_cols_opt(E) TAGS NK_LP vtags_literal_list(F) NK_RP. { pCxt->pRootNode = createCreateVSubTableStmt(pCxt, A, B, NULL, C, D, E, F, NULL, NULL); } cmd ::= CREATE VTABLE not_exists_opt(A) full_table_name(B) USING full_table_name(C) - specific_cols_opt(D) TAGS NK_LP tags_literal_list(E) NK_RP. { pCxt->pRootNode = createCreateVSubTableStmt(pCxt, A, B, NULL, NULL, C, D, E); } + specific_cols_opt(D) TAGS NK_LP vtags_literal_list(E) NK_RP. { pCxt->pRootNode = createCreateVSubTableStmt(pCxt, A, B, NULL, NULL, C, D, E, NULL, NULL); } cmd ::= DROP TABLE with_opt(A) multi_drop_clause(B). { pCxt->pRootNode = createDropTableStmt(pCxt, A, B); } cmd ::= DROP STABLE with_opt(A) exists_opt(B) full_table_name(C). { pCxt->pRootNode = createDropSuperTableStmt(pCxt, A, B, C); } cmd ::= DROP VTABLE with_opt(A) exists_opt(B) full_table_name(C). { pCxt->pRootNode = createDropVirtualTableStmt(pCxt, A, B, C); } @@ -1316,6 +1316,8 @@ cmd ::= SHOW SCANS. cmd ::= SHOW SCAN NK_INTEGER(A). { pCxt->pRootNode = createShowScanDetailsStmt(pCxt, createValueNode(pCxt, TSDB_DATA_TYPE_BIGINT, &A)); } cmd ::= SHOW SSMIGRATES. { pCxt->pRootNode = createShowSsMigratesStmt(pCxt, QUERY_NODE_SHOW_SSMIGRATES_STMT); } cmd ::= SHOW TOKENS. { pCxt->pRootNode = createShowTokensStmt(pCxt, QUERY_NODE_SHOW_TOKENS_STMT); } +cmd ::= SHOW VTABLE VALIDATE FOR full_table_name(A). { pCxt->pRootNode = createShowValidateVirtualTableStmt(pCxt, QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT, A); +} %type table_kind_db_name_cond_opt { SShowTablesOption } %destructor table_kind_db_name_cond_opt { } @@ -1943,6 +1945,17 @@ tags_literal(A) ::= literal_func(B) NK_MINUS duration_literal(C). tags_literal_list(A) ::= tags_literal(B). { A = createNodeList(pCxt, B); } tags_literal_list(A) ::= tags_literal_list(B) NK_COMMA tags_literal(C). { A = addNodeToList(pCxt, B, C); } +%type vtags_literal_list { SNodeList* } +%destructor vtags_literal_list { nodesDestroyList($$); } +vtags_literal_list(A) ::= vtags_literal(B). { A = createNodeList(pCxt, B); } +vtags_literal_list(A) ::= vtags_literal_list(B) NK_COMMA vtags_literal(C). { A = addNodeToList(pCxt, B, C); } + +vtags_literal(A) ::= tags_literal(B). { A = B; } +vtags_literal(A) ::= FROM column_ref(B). { A = B; } +vtags_literal(A) ::= NK_ID(B) FROM column_ref(C). { A = createColumnRefNodeByNode(pCxt, &B, C); } +vtags_literal(A) ::= NK_ID(B) NK_DOT NK_ID(C) NK_DOT NK_ID(D). { A = createColumnRefNodeFromTriplet(pCxt, &B, &C, &D); } +vtags_literal(A) ::= NK_ID(B) NK_DOT NK_ID(C). { A = createColumnRefNodeFromPair(pCxt, &B, &C); } + /************************************************ literal *************************************************************/ literal(A) ::= NK_INTEGER(B). { A = createRawExprNode(pCxt, &B, createValueNode(pCxt, TSDB_DATA_TYPE_UBIGINT, &B)); } literal(A) ::= NK_FLOAT(B). { A = createRawExprNode(pCxt, &B, createValueNode(pCxt, TSDB_DATA_TYPE_DOUBLE, &B)); } diff --git a/source/libs/parser/src/parAstCreater.c b/source/libs/parser/src/parAstCreater.c index c13a91a30b93..8bba98cf451d 100644 --- a/source/libs/parser/src/parAstCreater.c +++ b/source/libs/parser/src/parAstCreater.c @@ -3374,6 +3374,41 @@ SNode* createColumnRefNodeByNode(SAstCreateContext* pCxt, SToken* pColName, SNod return NULL; } +// Create a SColumnRefNode from db.table.col triplet tokens (for positional tag refs in vtags_literal) +SNode* createColumnRefNodeFromTriplet(SAstCreateContext* pCxt, SToken* pDb, SToken* pTable, SToken* pCol) { + CHECK_PARSER_STATUS(pCxt); + CHECK_NAME(checkDbName(pCxt, pDb, true)); + CHECK_NAME(checkTableName(pCxt, pTable)); + CHECK_NAME(checkColumnName(pCxt, pCol)); + + SColumnRefNode* pNode = NULL; + pCxt->errCode = nodesMakeNode(QUERY_NODE_COLUMN_REF, (SNode**)&pNode); + CHECK_MAKE_NODE(pNode); + COPY_STRING_FORM_ID_TOKEN(pNode->refDbName, pDb); + COPY_STRING_FORM_ID_TOKEN(pNode->refTableName, pTable); + COPY_STRING_FORM_ID_TOKEN(pNode->refColName, pCol); + return (SNode*)pNode; +_err: + return NULL; +} + +// Create a SColumnRefNode from table.col pair tokens (for positional tag refs in vtags_literal) +SNode* createColumnRefNodeFromPair(SAstCreateContext* pCxt, SToken* pTable, SToken* pCol) { + CHECK_PARSER_STATUS(pCxt); + CHECK_NAME(checkTableName(pCxt, pTable)); + CHECK_NAME(checkColumnName(pCxt, pCol)); + + SColumnRefNode* pNode = NULL; + pCxt->errCode = nodesMakeNode(QUERY_NODE_COLUMN_REF, (SNode**)&pNode); + CHECK_MAKE_NODE(pNode); + snprintf(pNode->refDbName, TSDB_DB_NAME_LEN, "%s", pCxt->pQueryCxt->db); + COPY_STRING_FORM_ID_TOKEN(pNode->refTableName, pTable); + COPY_STRING_FORM_ID_TOKEN(pNode->refColName, pCol); + return (SNode*)pNode; +_err: + return NULL; +} + STokenTriplet* createTokenTriplet(SAstCreateContext* pCxt, SToken pName) { CHECK_PARSER_STATUS(pCxt); @@ -3501,7 +3536,8 @@ SNode* createCreateVTableStmt(SAstCreateContext* pCxt, bool ignoreExists, SNode* SNode* createCreateVSubTableStmt(SAstCreateContext* pCxt, bool ignoreExists, SNode* pRealTable, SNodeList* pSpecificColRefs, SNodeList* pColRefs, SNode* pUseRealTable, - SNodeList* pSpecificTags, SNodeList* pValsOfTags) { + SNodeList* pSpecificTags, SNodeList* pValsOfTags, + SNodeList* pSpecificTagRefs, SNodeList* pTagRefs) { CHECK_PARSER_STATUS(pCxt); SCreateVSubTableStmt* pStmt = NULL; pCxt->errCode = nodesMakeNode(QUERY_NODE_CREATE_VIRTUAL_SUBTABLE_STMT, (SNode**)&pStmt); @@ -3515,6 +3551,8 @@ SNode* createCreateVSubTableStmt(SAstCreateContext* pCxt, bool ignoreExists, SNo pStmt->pValsOfTags = pValsOfTags; pStmt->pSpecificColRefs = pSpecificColRefs; pStmt->pColRefs = pColRefs; + pStmt->pSpecificTagRefs = pSpecificTagRefs; + pStmt->pTagRefs = pTagRefs; nodesDestroyNode(pRealTable); nodesDestroyNode(pUseRealTable); return (SNode*)pStmt; @@ -3525,6 +3563,8 @@ SNode* createCreateVSubTableStmt(SAstCreateContext* pCxt, bool ignoreExists, SNo nodesDestroyList(pValsOfTags); nodesDestroyList(pSpecificColRefs); nodesDestroyList(pColRefs); + nodesDestroyList(pSpecificTagRefs); + nodesDestroyList(pTagRefs); return NULL; } @@ -3910,7 +3950,7 @@ static bool needDbShowStmt(ENodeType type) { QUERY_NODE_SHOW_TAGS_STMT == type || QUERY_NODE_SHOW_TABLE_TAGS_STMT == type || QUERY_NODE_SHOW_VIEWS_STMT == type || QUERY_NODE_SHOW_TSMAS_STMT == type || QUERY_NODE_SHOW_USAGE_STMT == type || QUERY_NODE_SHOW_VTABLES_STMT == type || - QUERY_NODE_SHOW_STREAMS_STMT == type; + QUERY_NODE_SHOW_STREAMS_STMT == type || QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT; } SNode* createShowStmtWithLike(SAstCreateContext* pCxt, ENodeType type, SNode* pLikePattern) { @@ -4311,7 +4351,20 @@ SNode* createShowTransactionDetailsStmt(SAstCreateContext* pCxt, SNode* pTransac return NULL; } +SNode* createShowValidateVirtualTableStmt(SAstCreateContext* pCxt, ENodeType type, SNode* pVTable) { + CHECK_PARSER_STATUS(pCxt); + SShowValidateVirtualTable* pStmt = NULL; + pCxt->errCode = nodesMakeNode(type, (SNode**)&pStmt); + CHECK_MAKE_NODE(pStmt); + tstrncpy(pStmt->dbName, ((SRealTableNode*)pVTable)->table.dbName, TSDB_DB_NAME_LEN); + tstrncpy(pStmt->tableName, ((SRealTableNode*)pVTable)->table.tableName, TSDB_TABLE_NAME_LEN); + nodesDestroyNode(pVTable); + return (SNode*)pStmt; +_err: + nodesDestroyNode(pVTable); + return NULL; +} static bool parseIp(const char* strIp, SIpRange* pIpRange) { if (strchr(strIp, ':') == NULL) { diff --git a/source/libs/parser/src/parAstParser.c b/source/libs/parser/src/parAstParser.c index 41de3b0ace6a..eecf9f679a87 100644 --- a/source/libs/parser/src/parAstParser.c +++ b/source/libs/parser/src/parAstParser.c @@ -13,6 +13,7 @@ * along with this program. If not, see . */ +#include "cmdnodes.h" #include "functionMgt.h" #include "os.h" #include "parAst.h" @@ -20,6 +21,7 @@ #include "parToken.h" #include "systable.h" #include "tglobal.h" +#include "tmsg.h" typedef void* (*FMalloc)(size_t); typedef void (*FFree)(void*); @@ -204,7 +206,8 @@ static int32_t collectMetaKeyFromRealTableImpl(SCollectMetaKeyCxt* pCxt, const c if (TSDB_CODE_SUCCESS == code && (0 == strcmp(pTable, TSDB_INS_TABLE_TAGS) || 0 == strcmp(pTable, TSDB_INS_TABLE_TABLES) || 0 == strcmp(pTable, TSDB_INS_TABLE_COLS) || 0 == strcmp(pTable, TSDB_INS_TABLE_VC_COLS) || - 0 == strcmp(pTable, TSDB_INS_DISK_USAGE) || 0 == strcmp(pTable, TSDB_INS_TABLE_FILESETS)) && + 0 == strcmp(pTable, TSDB_INS_DISK_USAGE) || 0 == strcmp(pTable, TSDB_INS_TABLE_FILESETS) || + 0 == strcmp(pTable, TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING)) && QUERY_NODE_SELECT_STMT == nodeType(pCxt->pStmt)) { code = collectMetaKeyFromInsTags(pCxt); } @@ -430,27 +433,42 @@ static int32_t collectMetaKeyFromCreateVSubTable(SCollectMetaKeyCxt* pCxt, SCrea PRIV_OBJ_DB, pCxt->pMetaCache)); PAR_ERR_RET(reserveUserAuthInCache(pCxt->pParseCxt->acctId, pCxt->pParseCxt->pUser, pStmt->dbName, NULL, PRIV_TBL_CREATE, PRIV_OBJ_DB, pCxt->pMetaCache)); - // check org table's read auth + // check org table's read auth for column references SNode* pNode = NULL; SNodeList* pTmpNodeList = pStmt->pSpecificColRefs ? pStmt->pSpecificColRefs : pStmt->pColRefs; - if (NULL == pTmpNodeList) { - // no column reference - return TSDB_CODE_SUCCESS; + if (pTmpNodeList) { + FOREACH(pNode, pTmpNodeList) { + SColumnRefNode* pColRef = (SColumnRefNode*)pNode; + if (NULL == pColRef) { + code = TSDB_CODE_PAR_INVALID_COLUMN; + break; + } + PAR_ERR_RET( + reserveTableMetaInCache(pCxt->pParseCxt->acctId, pColRef->refDbName, pColRef->refTableName, pCxt->pMetaCache)); + PAR_ERR_RET(reserveTableVgroupInCache(pCxt->pParseCxt->acctId, pColRef->refDbName, pColRef->refTableName, + pCxt->pMetaCache)); + PAR_ERR_RET(reserveUserAuthInCache(pCxt->pParseCxt->acctId, pCxt->pParseCxt->pUser, pColRef->refDbName, + pColRef->refTableName, PRIV_TBL_SELECT, PRIV_OBJ_TBL, pCxt->pMetaCache)); + } } - FOREACH(pNode, pTmpNodeList) { - SColumnRefNode* pColRef = (SColumnRefNode*)pNode; - if (NULL == pColRef) { - code = TSDB_CODE_PAR_INVALID_COLUMN; - break; + // collect metadata for tag reference tables (SColumnRefNode nodes in pValsOfTags) + // Handles all tag ref syntax forms: legacy (FROM db.table.tag), specific (tag_name FROM db.table.tag), + // and positional (db.table.tag) - all produce SColumnRefNode in pValsOfTags. + if (pStmt->pValsOfTags) { + FOREACH(pNode, pStmt->pValsOfTags) { + if (nodeType(pNode) == QUERY_NODE_COLUMN_REF) { + SColumnRefNode* pTagRef = (SColumnRefNode*)pNode; + PAR_ERR_RET( + reserveTableMetaInCache(pCxt->pParseCxt->acctId, pTagRef->refDbName, pTagRef->refTableName, pCxt->pMetaCache)); + PAR_ERR_RET(reserveTableVgroupInCache(pCxt->pParseCxt->acctId, pTagRef->refDbName, pTagRef->refTableName, + pCxt->pMetaCache)); + PAR_ERR_RET(reserveUserAuthInCache(pCxt->pParseCxt->acctId, pCxt->pParseCxt->pUser, pTagRef->refDbName, + pTagRef->refTableName, PRIV_TBL_SELECT, PRIV_OBJ_TBL, pCxt->pMetaCache)); + } } - PAR_ERR_RET( - reserveTableMetaInCache(pCxt->pParseCxt->acctId, pColRef->refDbName, pColRef->refTableName, pCxt->pMetaCache)); - PAR_ERR_RET(reserveTableVgroupInCache(pCxt->pParseCxt->acctId, pColRef->refDbName, pColRef->refTableName, - pCxt->pMetaCache)); - PAR_ERR_RET(reserveUserAuthInCache(pCxt->pParseCxt->acctId, pCxt->pParseCxt->pUser, pColRef->refDbName, - pColRef->refTableName, PRIV_TBL_SELECT, PRIV_OBJ_TBL, pCxt->pMetaCache)); } + return code; } @@ -887,7 +905,7 @@ static int32_t collectMetaKeyFromShowAnodesFull(SCollectMetaKeyCxt* pCxt, SShowS static int32_t collectMetaKeyFromShowBnodes(SCollectMetaKeyCxt* pCxt, SShowStmt* pStmt) { int32_t code = reserveTableMetaInCache(pCxt->pParseCxt->acctId, TSDB_INFORMATION_SCHEMA_DB, TSDB_INS_TABLE_BNODES, - pCxt->pMetaCache); + pCxt->pMetaCache); if (TSDB_CODE_SUCCESS == code) { code = reserveUserAuthInCache(pCxt->pParseCxt->acctId, pCxt->pParseCxt->pUser, NULL, NULL, PRIV_NODES_SHOW, 0, pCxt->pMetaCache); @@ -942,6 +960,17 @@ static int32_t collectMetaKeyFromShowXnodeJobs(SCollectMetaKeyCxt* pCxt, SShowSt } return code; } + +static int32_t collectMetaKeyFromShowVirtualTablesReferencing(SCollectMetaKeyCxt* pCxt, SShowStmt* pStmt) { + int32_t code = reserveTableMetaInCache(pCxt->pParseCxt->acctId, TSDB_INFORMATION_SCHEMA_DB, + TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING, pCxt->pMetaCache); + if (TSDB_CODE_SUCCESS == code) { + code = reserveUserAuthInCache(pCxt->pParseCxt->acctId, pCxt->pParseCxt->pUser, NULL, NULL, PRIV_NODES_SHOW, 0, + pCxt->pMetaCache); + } + return code; +} + static int32_t collectMetaKeyFromShowArbGroups(SCollectMetaKeyCxt* pCxt, SShowStmt* pStmt) { int32_t code = reserveTableMetaInCache(pCxt->pParseCxt->acctId, TSDB_INFORMATION_SCHEMA_DB, TSDB_INS_TABLE_ARBGROUPS, pCxt->pMetaCache); @@ -950,7 +979,7 @@ static int32_t collectMetaKeyFromShowArbGroups(SCollectMetaKeyCxt* pCxt, SShowSt static int32_t collectMetaKeyFromShowCluster(SCollectMetaKeyCxt* pCxt, SShowStmt* pStmt) { int32_t code = reserveTableMetaInCache(pCxt->pParseCxt->acctId, TSDB_INFORMATION_SCHEMA_DB, TSDB_INS_TABLE_CLUSTER, - pCxt->pMetaCache); + pCxt->pMetaCache); if (TSDB_CODE_SUCCESS == code) { code = reserveUserAuthInCache(pCxt->pParseCxt->acctId, pCxt->pParseCxt->pUser, NULL, NULL, PRIV_CLUSTER_SHOW, 0, pCxt->pMetaCache); @@ -1308,8 +1337,8 @@ static int32_t collectMetaKeyFromShowEncryptAlgorithms(SCollectMetaKeyCxt* pCxt, } static int32_t collectMetaKeyFromShowEncryptStatus(SCollectMetaKeyCxt* pCxt, SShowStmt* pStmt) { - int32_t code = reserveTableMetaInCache(pCxt->pParseCxt->acctId, TSDB_INFORMATION_SCHEMA_DB, TSDB_INS_TABLE_ENCRYPT_STATUS, - pCxt->pMetaCache); + int32_t code = reserveTableMetaInCache(pCxt->pParseCxt->acctId, TSDB_INFORMATION_SCHEMA_DB, + TSDB_INS_TABLE_ENCRYPT_STATUS, pCxt->pMetaCache); return code; } @@ -1393,7 +1422,7 @@ static int32_t collectMetaKeyFromShowCreateRsma(SCollectMetaKeyCxt* pCxt, SShowC static int32_t collectMetaKeyFromShowApps(SCollectMetaKeyCxt* pCxt, SShowStmt* pStmt) { int32_t code = reserveTableMetaInCache(pCxt->pParseCxt->acctId, TSDB_PERFORMANCE_SCHEMA_DB, TSDB_PERFS_TABLE_APPS, - pCxt->pMetaCache); + pCxt->pMetaCache); if (TSDB_CODE_SUCCESS == code) { code = reserveUserAuthInCache(pCxt->pParseCxt->acctId, pCxt->pParseCxt->pUser, NULL, NULL, PRIV_APPS_SHOW, 0, pCxt->pMetaCache); @@ -1441,6 +1470,24 @@ static int32_t collectMetaKeyFromShowBlockDist(SCollectMetaKeyCxt* pCxt, SShowTa } return code; } +static int32_t collectMetaKeyFromShowValidateVtable(SCollectMetaKeyCxt* pCxt, SShowValidateVirtualTable* pStmt) { + // Collect database information + int32_t code = 0; + + code = reserveTableMetaInCache(pCxt->pParseCxt->acctId, TSDB_INFORMATION_SCHEMA_DB, + TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING, pCxt->pMetaCache); + if (TSDB_CODE_SUCCESS == code && pStmt->tableName[0] != 0) { + code = reserveTableMetaInCache(pCxt->pParseCxt->acctId, pStmt->dbName, pStmt->tableName, pCxt->pMetaCache); + } + + if (TSDB_CODE_SUCCESS == code) { + code = reserveDbVgInfoInCache(pCxt->pParseCxt->acctId, pStmt->dbName, pCxt->pMetaCache); + } + if (TSDB_CODE_SUCCESS == code && pStmt->tableName[0] != 0) { + code = reserveTableVgroupInCache(pCxt->pParseCxt->acctId, pStmt->dbName, pStmt->tableName, pCxt->pMetaCache); + } + return code; +} static int32_t collectMetaKeyFromShowSubscriptions(SCollectMetaKeyCxt* pCxt, SShowStmt* pStmt) { return reserveTableMetaInCache(pCxt->pParseCxt->acctId, TSDB_INFORMATION_SCHEMA_DB, TSDB_INS_TABLE_SUBSCRIPTIONS, @@ -1789,9 +1836,9 @@ static int32_t collectMetaKeyFromSysPrivStmt(SCollectMetaKeyCxt* pCxt, EPrivType static int32_t collectMetaKeyFromQuery(SCollectMetaKeyCxt* pCxt, SNode* pStmt) { int32_t code = 0; - SNode* pOrigStmt = pCxt->pStmt; + SNode* pOrigStmt = pCxt->pStmt; pCxt->pStmt = pStmt; - + switch (nodeType(pStmt)) { case QUERY_NODE_SET_OPERATOR: code = collectMetaKeyFromSetOperator(pCxt, (SSetOperator*)pStmt); @@ -2090,6 +2137,9 @@ static int32_t collectMetaKeyFromQuery(SCollectMetaKeyCxt* pCxt, SNode* pStmt) { case QUERY_NODE_SHOW_TABLE_DISTRIBUTED_STMT: code = collectMetaKeyFromShowBlockDist(pCxt, (SShowTableDistributedStmt*)pStmt); break; + case QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT: + code = collectMetaKeyFromShowValidateVtable(pCxt, (SShowValidateVirtualTable*)pStmt); + break; case QUERY_NODE_SHOW_SUBSCRIPTIONS_STMT: code = collectMetaKeyFromShowSubscriptions(pCxt, (SShowStmt*)pStmt); break; @@ -2171,7 +2221,7 @@ static int32_t collectMetaKeyFromQuery(SCollectMetaKeyCxt* pCxt, SNode* pStmt) { code = collectMetaKeyFromSysPrivStmt(pCxt, PRIV_ROLE_DROP); break; case QUERY_NODE_CREATE_USER_STMT: - code = collectMetaKeyFromSysPrivStmt(pCxt, PRIV_USER_CREATE); + code = collectMetaKeyFromSysPrivStmt(pCxt, PRIV_USER_CREATE); break; case QUERY_NODE_ALTER_USER_STMT: code = collectMetaKeyFromSysPrivStmt(pCxt, PRIV_USER_ALTER); diff --git a/source/libs/parser/src/parTokenizer.c b/source/libs/parser/src/parTokenizer.c index 7c78e7bb8057..ebadead1ff98 100644 --- a/source/libs/parser/src/parTokenizer.c +++ b/source/libs/parser/src/parTokenizer.c @@ -480,6 +480,7 @@ static SKeyword keywordTable[] = { {"XNODES", TK_XNODES}, {"DRAIN", TK_DRAIN}, {"REBALANCE", TK_REBALANCE}, + {"VALIDATE", TK_VALIDATE}, }; // clang-format on diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index 3d4c5d256276..5e81e7677e57 100644 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -527,6 +527,13 @@ static const SSysTableShowAdapter sysTableShowAdapter[] = { .numOfShowCols = 1, .pShowCols = {"*"} }, + { + .showType = QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT, + .pDbName = TSDB_INFORMATION_SCHEMA_DB, + .pTableName = TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING, + .numOfShowCols = 1, + .pShowCols = {"*"} + }, }; // clang-format on @@ -550,9 +557,11 @@ static int32_t insertCondIntoSelectStmt(SSelectStmt* pSelect, SNode** pCond); static int32_t extractCondFromCountWindow(STranslateContext* pCxt, SCountWindowNode* pCountWindow, SNode** pCond); static int32_t translateExprList(STranslateContext* pCxt, SNodeList* pList); static int32_t setCurrLevelNsFromParent(STranslateContext* pSrc, STranslateContext* pDst); -static bool getJoinContais(SNode* pNode); +static bool getJoinContais(SNode* pNode); static uint8_t getStmtPrecision(SNode* pStmt); -static bool stmtIsSingleTable(SNode* pStmt); +static bool stmtIsSingleTable(SNode* pStmt); + +static int32_t rewriteShowValidateVtable(STranslateContext* pCxt, SQuery* pQuery); static bool isWindowJoinStmt(SSelectStmt* pSelect) { return (QUERY_NODE_JOIN_TABLE == nodeType(pSelect->pFromTable)) && @@ -711,8 +720,8 @@ static int32_t rewriteDropTableWithMetaCache(STranslateContext* pCxt) { bool stopIterate = false; if (tbMetaSize > 0 || tbMetaExSize <= 0) { - parserError("QID:0x%" PRIx64 ", %s unexpected state, tbMetaSize:%d, tbMetaExSize:%d", - pParCxt->requestId, __func__ , tbMetaSize, tbMetaExSize); + parserError("QID:0x%" PRIx64 ", %s unexpected state, tbMetaSize:%d, tbMetaExSize:%d", pParCxt->requestId, __func__, + tbMetaSize, tbMetaExSize); PAR_ERR_JRET(TSDB_CODE_PAR_INTERNAL_ERROR); } if (!pMetaCache->pTableMeta && @@ -745,9 +754,9 @@ static int32_t rewriteDropTableWithMetaCache(STranslateContext* pCxt) { } tstrncpy(dbName, pDbStart + 1, pDbEnd - pDbStart); - int32_t metaSize = + int32_t metaSize = sizeof(STableMeta) + sizeof(SSchema) * (pMeta->tableInfo.numOfColumns + pMeta->tableInfo.numOfTags); - int32_t schemaExtSize = + int32_t schemaExtSize = (withExtSchema(pMeta->tableType) && pMeta->schemaExt) ? sizeof(SSchemaExt) * pMeta->tableInfo.numOfColumns : 0; int32_t colRefSize = (hasRefCol(pMeta->tableType) && pMeta->colRef) ? sizeof(SColRef) * pMeta->numOfColRefs : 0; const char* pTbName = (const char*)pMeta + metaSize + schemaExtSize + colRefSize; @@ -1113,7 +1122,8 @@ static void initStreamInfo(SParseStreamInfo* pInfo) { pInfo->triggerTbl = NULL; } -static int32_t initTranslateContext(SParseContext* pParseCxt, SParseMetaCache* pMetaCache, bool isSubCtx, STranslateContext* pCxt) { +static int32_t initTranslateContext(SParseContext* pParseCxt, SParseMetaCache* pMetaCache, bool isSubCtx, + STranslateContext* pCxt) { pCxt->pParseCxt = pParseCxt; pCxt->errCode = TSDB_CODE_SUCCESS; pCxt->msgBuf.buf = pParseCxt->pMsg; @@ -1430,7 +1440,8 @@ static int32_t isTimeLineAlignedQuery(SNode* pStmt, bool* pRes) { if (QUERY_NODE_SELECT_STMT == nodeType(((STempTableNode*)pSelect->pFromTable)->pSubquery)) { SSelectStmt* pSub = (SSelectStmt*)((STempTableNode*)pSelect->pFromTable)->pSubquery; if (pSelect->pPartitionByList) { - if (pSub->timeLineFromOrderBy == ORDER_UNKNOWN && nodesListMatch(pSelect->pPartitionByList, pSub->pPartitionByList)) { + if (pSub->timeLineFromOrderBy == ORDER_UNKNOWN && + nodesListMatch(pSelect->pPartitionByList, pSub->pPartitionByList)) { *pRes = true; return code; } @@ -1472,7 +1483,7 @@ static int32_t isTimeLineAlignedQuery(SNode* pStmt, bool* pRes) { } bool isPrimaryKeyImpl(SNode* pExpr) { - if(!pExpr) { + if (!pExpr) { return false; } if (QUERY_NODE_COLUMN == nodeType(pExpr)) { @@ -1684,11 +1695,12 @@ static SNodeList* getStreamTriggerPartition(STranslateContext* pCxt) { return pC static SNode* getStreamTriggerTable(STranslateContext* pCxt) { return pCxt->streamInfo.triggerTbl; } -static int32_t createColumnByRealTable(STranslateContext* pCxt, const STableNode* pTable, bool igTags, SNodeList* pList) { +static int32_t createColumnByRealTable(STranslateContext* pCxt, const STableNode* pTable, bool igTags, + SNodeList* pList) { int32_t code = TSDB_CODE_SUCCESS; SColumnNode* pCol = NULL; const STableMeta* pMeta = ((SRealTableNode*)pTable)->pMeta; - int32_t nums = pMeta->tableInfo.numOfColumns + + int32_t nums = pMeta->tableInfo.numOfColumns + (igTags ? 0 : ((TSDB_SUPER_TABLE == pMeta->tableType || ((SRealTableNode*)pTable)->stbRewrite) ? pMeta->tableInfo.numOfTags @@ -1715,8 +1727,8 @@ static int32_t createColumnByRealTable(STranslateContext* pCxt, const STableNode return code; } -static int32_t createColumnsByTempTable(STranslateContext* pCxt, const STableNode* pTable, bool igTags, SNodeList* pList, - bool skipProjRef) { +static int32_t createColumnsByTempTable(STranslateContext* pCxt, const STableNode* pTable, bool igTags, + SNodeList* pList, bool skipProjRef) { int32_t code = TSDB_CODE_SUCCESS; int32_t lino = 0; SColumnNode* pCol = NULL; @@ -1757,13 +1769,11 @@ static int32_t createColumnsByVirtualTable(STranslateContext* pCxt, const STable SColumnNode* pCol = NULL; SHashObj* pColRefMap = NULL; int32_t nums = pMeta->tableInfo.numOfColumns + - (igTags ? 0 - : (TSDB_SUPER_TABLE == pMeta->tableType - ? pMeta->tableInfo.numOfTags - : 0)); + (igTags ? 0 : (TSDB_SUPER_TABLE == pMeta->tableType ? pMeta->tableInfo.numOfTags : 0)); if (pMeta->numOfColRefs > 0 && pMeta->colRef) { - pColRefMap = taosHashInit(pMeta->numOfColRefs, taosGetDefaultHashFunction(TSDB_DATA_TYPE_SMALLINT), false, HASH_NO_LOCK); + pColRefMap = + taosHashInit(pMeta->numOfColRefs, taosGetDefaultHashFunction(TSDB_DATA_TYPE_SMALLINT), false, HASH_NO_LOCK); QUERY_CHECK_NULL(pColRefMap, code, lino, _return, terrno); for (int32_t i = 0; i < pMeta->numOfColRefs; ++i) { @@ -1942,8 +1952,8 @@ static int32_t findAndSetVirtualTableColumn(STranslateContext* pCxt, SColumnNode int32_t nums = pMeta->tableInfo.numOfTags + pMeta->tableInfo.numOfColumns; for (int32_t i = 0; i < nums; ++i) { if (0 == strcmp(pCol->colName, pMeta->schema[i].name)) { - setVtbColumnInfoBySchema((SVirtualTableNode*)pTable, pMeta->schema + i, (i - pMeta->tableInfo.numOfColumns), - NULL, pCol); + setVtbColumnInfoBySchema((SVirtualTableNode*)pTable, pMeta->schema + i, (i - pMeta->tableInfo.numOfColumns), NULL, + pCol); setColumnPrimTs(pCxt, pCol, pTable); *pFound = true; break; @@ -2044,9 +2054,7 @@ static EDealRes translateColumnWithPrefix(STranslateContext* pCxt, SColumnNode** /** translate column in tables without table prefix */ -static EDealRes translateColumnWithoutPrefix(STranslateContext* pCxt, - SColumnNode** pCol, - bool* pFound) { +static EDealRes translateColumnWithoutPrefix(STranslateContext* pCxt, SColumnNode** pCol, bool* pFound) { SArray* pTables = taosArrayGetP(pCxt->pNsLevel, pCxt->currLevel); size_t nums = taosArrayGetSize(pTables); bool isInternalPk = isInternalPrimaryKey(*pCol); @@ -2073,9 +2081,7 @@ static EDealRes translateColumnWithoutPrefix(STranslateContext* pCxt, return DEAL_RES_CONTINUE; } -static EDealRes translateSubQColumnWithoutPrefix(STranslateContext* pCxt, - SColumnNode** pCol, - bool* pFound) { +static EDealRes translateSubQColumnWithoutPrefix(STranslateContext* pCxt, SColumnNode** pCol, bool* pFound) { int32_t currLevel = pCxt->currLevel--; while (pCxt->currLevel >= 0) { EDealRes res = translateColumnWithoutPrefix(pCxt, pCol, pFound); @@ -2083,7 +2089,7 @@ static EDealRes translateSubQColumnWithoutPrefix(STranslateContext* pCxt, pCxt->currLevel = currLevel; return res; } - + if (false == *pFound) { pCxt->currLevel--; continue; @@ -2098,7 +2104,6 @@ static EDealRes translateSubQColumnWithoutPrefix(STranslateContext* pCxt, return DEAL_RES_CONTINUE; } - static int32_t getFuncInfo(STranslateContext* pCxt, SFunctionNode* pFunc); /** @@ -2125,9 +2130,11 @@ static EDealRes translateColumnUseAlias(STranslateContext* pCxt, SColumnNode** p } if (*pFound) { if (nodeType(pFoundNode) == QUERY_NODE_FUNCTION && fmIsPlaceHolderFunc(((SFunctionNode*)pFoundNode)->funcId)) { - if (pCxt->currClause != SQL_CLAUSE_WHERE && pCxt->currClause != SQL_CLAUSE_SELECT && pCxt->currClause != SQL_CLAUSE_ORDER_BY) { - pCxt->errCode = generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_STREAM_INVALID_PLACE_HOLDER, - "stream placeholder should only appear in select, where and order by clause"); + if (pCxt->currClause != SQL_CLAUSE_WHERE && pCxt->currClause != SQL_CLAUSE_SELECT && + pCxt->currClause != SQL_CLAUSE_ORDER_BY) { + pCxt->errCode = + generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_STREAM_INVALID_PLACE_HOLDER, + "stream placeholder should only appear in select, where and order by clause"); return DEAL_RES_ERROR; } } @@ -2406,15 +2413,12 @@ static EDealRes translateColumnInGroupByClause(STranslateContext* pCxt, SColumnN } if (!found && res != DEAL_RES_ERROR) { if (isInternalPrimaryKey(*pCol)) { - if (isSelectStmt(pCxt->pCurrStmt) && - NULL != ((SSelectStmt*)pCxt->pCurrStmt)->pWindow) { - return generateDealNodeErrMsg(pCxt, - TSDB_CODE_PAR_NOT_ALLOWED_WIN_QUERY); + if (isSelectStmt(pCxt->pCurrStmt) && NULL != ((SSelectStmt*)pCxt->pCurrStmt)->pWindow) { + return generateDealNodeErrMsg(pCxt, TSDB_CODE_PAR_NOT_ALLOWED_WIN_QUERY); } return generateDealNodeErrMsg(pCxt, TSDB_CODE_PAR_INVALID_INTERNAL_PK); } else { - return generateDealNodeErrMsg(pCxt, TSDB_CODE_PAR_INVALID_COLUMN, - (*pCol)->colName); + return generateDealNodeErrMsg(pCxt, TSDB_CODE_PAR_INVALID_COLUMN, (*pCol)->colName); } } } @@ -2463,9 +2467,7 @@ static EDealRes translateColumn(STranslateContext* pCxt, SColumnNode** pCol) { select list. If not found, then match them in all tables. */ bool alreadyCheckedAlias = false; - if (pCxt->currClause == SQL_CLAUSE_ORDER_BY && - isSelectStmt(pCxt->pCurrStmt) && - !(*pCol)->node.asParam) { + if (pCxt->currClause == SQL_CLAUSE_ORDER_BY && isSelectStmt(pCxt->pCurrStmt) && !(*pCol)->node.asParam) { res = translateColumnUseAlias(pCxt, pCol, &found); alreadyCheckedAlias = true; } @@ -2482,26 +2484,22 @@ static EDealRes translateColumn(STranslateContext* pCxt, SColumnNode** pCol) { For supported clauses, if column not found in all tables, try to match alias in select list. */ - if (!found && res != DEAL_RES_ERROR && clauseSupportAlias(pCxt->currClause) && - !alreadyCheckedAlias) { + if (!found && res != DEAL_RES_ERROR && clauseSupportAlias(pCxt->currClause) && !alreadyCheckedAlias) { res = translateColumnUseAlias(pCxt, pCol, &found); } if (!found && res != DEAL_RES_ERROR && pCxt->isExprSubQ) { res = translateSubQColumnWithoutPrefix(pCxt, pCol, &found); } - + if (!found && res != DEAL_RES_ERROR) { if (isInternalPrimaryKey(*pCol)) { - if (isSelectStmt(pCxt->pCurrStmt) && - NULL != ((SSelectStmt*)pCxt->pCurrStmt)->pWindow) { - return generateDealNodeErrMsg(pCxt, - TSDB_CODE_PAR_NOT_ALLOWED_WIN_QUERY); + if (isSelectStmt(pCxt->pCurrStmt) && NULL != ((SSelectStmt*)pCxt->pCurrStmt)->pWindow) { + return generateDealNodeErrMsg(pCxt, TSDB_CODE_PAR_NOT_ALLOWED_WIN_QUERY); } return generateDealNodeErrMsg(pCxt, TSDB_CODE_PAR_INVALID_INTERNAL_PK); } else { - return generateDealNodeErrMsg(pCxt, TSDB_CODE_PAR_INVALID_COLUMN, - (*pCol)->colName); + return generateDealNodeErrMsg(pCxt, TSDB_CODE_PAR_INVALID_COLUMN, (*pCol)->colName); } } @@ -2837,8 +2835,6 @@ static int32_t dataTypeComp(const SDataType* l, const SDataType* r) { return (l->precision == r->precision && l->scale == r->scale) ? 0 : 1; } - - static int32_t replaceExprSubQuery(STranslateContext* pCxt, SNode** pNode, SNode* pSubQuery, int32_t rewriteType) { int32_t code = TSDB_CODE_SUCCESS; @@ -2866,7 +2862,7 @@ static int32_t replaceExprSubQuery(STranslateContext* pCxt, SNode** pNode, SNode } *pNode = NULL; - + if (0 == rewriteType) { code = nodesMakeNode(QUERY_NODE_REMOTE_VALUE_LIST, pNode); if (TSDB_CODE_SUCCESS != code) { @@ -2934,13 +2930,14 @@ static int32_t getExprSubQRewriteType(EOperatorType opType, SNode* pSubq, ESubQR *pRes = E_SQ_REWRITE_TO_ZERO_ROWS; return TSDB_CODE_SUCCESS; } - + if (OP_TYPE_IN == opType || OP_TYPE_NOT_IN == opType) { *pRes = E_SQ_REWRITE_KEEP_REMAIN; return TSDB_CODE_SUCCESS; } - EQuantifyType quantifyType = (QUERY_NODE_SELECT_STMT == nodeType(pSubq)) ? ((SSelectStmt*)pSubq)->quantify : ((SSetOperator*)pSubq)->quantify; + EQuantifyType quantifyType = + (QUERY_NODE_SELECT_STMT == nodeType(pSubq)) ? ((SSelectStmt*)pSubq)->quantify : ((SSetOperator*)pSubq)->quantify; if (QU_TYPE_ANY != quantifyType && QU_TYPE_ALL != quantifyType) { parserError("invalid quantifyType %d for op [%s]'s subQ", quantifyType, operatorTypeStr(opType)); @@ -2948,7 +2945,7 @@ static int32_t getExprSubQRewriteType(EOperatorType opType, SNode* pSubq, ESubQR } switch (opType) { - case OP_TYPE_GREATER_THAN: + case OP_TYPE_GREATER_THAN: case OP_TYPE_GREATER_EQUAL: *pRes = (QU_TYPE_ANY == quantifyType) ? E_SQ_REWRITE_TO_MIN : E_SQ_REWRITE_TO_MAX; return TSDB_CODE_SUCCESS; @@ -2970,8 +2967,9 @@ static int32_t getExprSubQRewriteType(EOperatorType opType, SNode* pSubq, ESubQR return TSDB_CODE_PAR_INTERNAL_ERROR; } -static int32_t createSimpleSubQStmt(STranslateContext* pCxt, char* stmtName, SNodeList *pProjectionList, SNode* pSubQ, SSelectStmt **ppStmt) { - int32_t code = TSDB_CODE_SUCCESS; +static int32_t createSimpleSubQStmt(STranslateContext* pCxt, char* stmtName, SNodeList* pProjectionList, SNode* pSubQ, + SSelectStmt** ppStmt) { + int32_t code = TSDB_CODE_SUCCESS; STempTableNode* pTable = NULL; code = nodesMakeNode(QUERY_NODE_TEMP_TABLE, (SNode**)&pTable); if (TSDB_CODE_SUCCESS != code) { @@ -2980,9 +2978,9 @@ static int32_t createSimpleSubQStmt(STranslateContext* pCxt, char* stmtName, SNo } pTable->pSubquery = pSubQ; - + tstrncpy(pTable->table.tableAlias, stmtName, TSDB_TABLE_NAME_LEN); - + if (QUERY_NODE_SELECT_STMT == nodeType(pSubQ)) { ((SSelectStmt*)pSubQ)->isSubquery = true; } @@ -3031,25 +3029,26 @@ static int32_t createSimpleSubQStmt(STranslateContext* pCxt, char* stmtName, SNo cxt.pCurrStmt = (SNode*)pSelect; cxt.currClause = SQL_CLAUSE_SELECT; - + code = translateExprList(&cxt, pProjectionList); destroyTranslateContext(&cxt); if (TSDB_CODE_SUCCESS != code) { nodesDestroyNode((SNode*)pSelect); return code; } - + pSelect->selectFuncNum = 1; pSelect->hasAggFuncs = true; pSelect->hasSelectFunc = true; pSelect->hasSelectValFunc = true; - + return code; } -static int32_t createNewSubQProjectionList(STranslateContext* pCxt, char* stmtName, SNodeList *pSrcProjection, SNodeList** ppNew, bool isMin) { +static int32_t createNewSubQProjectionList(STranslateContext* pCxt, char* stmtName, SNodeList* pSrcProjection, + SNodeList** ppNew, bool isMin) { SFunctionNode* pFunc = NULL; - int32_t code = nodesMakeNode(QUERY_NODE_FUNCTION, (SNode**)&pFunc); + int32_t code = nodesMakeNode(QUERY_NODE_FUNCTION, (SNode**)&pFunc); if (TSDB_CODE_SUCCESS != code) { return code; } @@ -3058,7 +3057,7 @@ static int32_t createNewSubQProjectionList(STranslateContext* pCxt, char* stmtNa SColumnNode* pParam = createColumnByExpr(stmtName, (SExprNode*)pSrcProjection->pHead->pNode); if (NULL == pParam) { code = terrno; - nodesDestroyNode((SNode *)pFunc); + nodesDestroyNode((SNode*)pFunc); return code; } @@ -3067,8 +3066,8 @@ static int32_t createNewSubQProjectionList(STranslateContext* pCxt, char* stmtNa nodesDestroyNode((SNode*)pFunc); return code; } - - code = nodesListMakeStrictAppend(ppNew, (SNode *)pFunc); + + code = nodesListMakeStrictAppend(ppNew, (SNode*)pFunc); if (TSDB_CODE_SUCCESS != code) { return code; } @@ -3086,7 +3085,7 @@ static int32_t createNewSubQProjectionList(STranslateContext* pCxt, char* stmtNa return code; } - return nodesListMakeStrictAppend(ppNew, (SNode *)pFunc2); + return nodesListMakeStrictAppend(ppNew, (SNode*)pFunc2); } static int32_t rewriteExprSubQResToMinMax(STranslateContext* pCxt, SNode* pSubQuery, bool isMin) { @@ -3094,14 +3093,14 @@ static int32_t rewriteExprSubQResToMinMax(STranslateContext* pCxt, SNode* pSubQu if (TSDB_CODE_SUCCESS != code) { return code; } - + if (LIST_LENGTH(pCxt->pSubQueries) <= 0) { parserError("unexpected empty subQ list got"); return TSDB_CODE_PAR_INTERNAL_ERROR; } SNodeList* pProjection = NULL; - char* stmtName = NULL; + char* stmtName = NULL; SNodeList* pNewProjection = NULL; if (QUERY_NODE_SELECT_STMT == nodeType(pSubQuery)) { pProjection = ((SSelectStmt*)pSubQuery)->pProjectionList; @@ -3116,7 +3115,7 @@ static int32_t rewriteExprSubQResToMinMax(STranslateContext* pCxt, SNode* pSubQu nodesDestroyList(pNewProjection); return code; } - + SSelectStmt* pNewStmt = NULL; code = createSimpleSubQStmt(pCxt, stmtName, pNewProjection, pSubQuery, &pNewStmt); if (TSDB_CODE_SUCCESS != code) { @@ -3129,14 +3128,14 @@ static int32_t rewriteExprSubQResToMinMax(STranslateContext* pCxt, SNode* pSubQu pCxt->pSubQueries->pTail->pNode = (SNode*)pNewStmt; } else { parserError("translate context last subq %p is not expected %p", pCxt->pSubQueries->pTail->pNode, pSubQuery); - nodesDestroyNode((SNode *)pNewStmt); + nodesDestroyNode((SNode*)pNewStmt); } return code; } static int32_t doRewriteExprSubQuery(STranslateContext* pCxt, SOperatorNode* pOp, SNode** ppSubQ) { - ESubQRewriteType rewriteType; + ESubQRewriteType rewriteType; pCxt->errCode = getExprSubQRewriteType(pOp->opType, *ppSubQ, &rewriteType); if (TSDB_CODE_SUCCESS != pCxt->errCode) { return pCxt->errCode; @@ -3214,22 +3213,25 @@ static bool isValidSubQCompDataType(int32_t leftType, int32_t rightType, EOperat } if (TSDB_DATA_TYPE_JSON == rightType || TSDB_DATA_TYPE_JSON == leftType) { - parserError("not supported quantified compare types:%d, %d for op:%s", leftType, rightType, operatorTypeStr(opType)); + parserError("not supported quantified compare types:%d, %d for op:%s", leftType, rightType, + operatorTypeStr(opType)); return false; } - + if ((opType >= OP_TYPE_GREATER_THAN && opType <= OP_TYPE_LOWER_EQUAL) && - (TSDB_DATA_TYPE_BLOB == rightType || TSDB_DATA_TYPE_MEDIUMBLOB == rightType || TSDB_DATA_TYPE_GEOMETRY == rightType)) { - parserError("not supported quantified compare types:%d, %d for op:%s", leftType, rightType, operatorTypeStr(opType)); + (TSDB_DATA_TYPE_BLOB == rightType || TSDB_DATA_TYPE_MEDIUMBLOB == rightType || + TSDB_DATA_TYPE_GEOMETRY == rightType)) { + parserError("not supported quantified compare types:%d, %d for op:%s", leftType, rightType, + operatorTypeStr(opType)); return false; } - + return true; } static int32_t rewriteExprSubQuery(STranslateContext* pCxt, SOperatorNode* pOp) { switch (pOp->opType) { - case OP_TYPE_GREATER_THAN: + case OP_TYPE_GREATER_THAN: case OP_TYPE_GREATER_EQUAL: case OP_TYPE_LOWER_THAN: case OP_TYPE_LOWER_EQUAL: @@ -3238,7 +3240,8 @@ static int32_t rewriteExprSubQuery(STranslateContext* pCxt, SOperatorNode* pOp) case OP_TYPE_IN: case OP_TYPE_NOT_IN: { if (!isSubQueryNode(pOp->pRight)) { - parserError("pRight %d is not subQ in op %s, exprSubQType:%d", nodeType(pOp->pRight), operatorTypeStr(pOp->opType), pCxt->expSubQueryType); + parserError("pRight %d is not subQ in op %s, exprSubQType:%d", nodeType(pOp->pRight), + operatorTypeStr(pOp->opType), pCxt->expSubQueryType); pCxt->errCode = TSDB_CODE_PAR_INTERNAL_ERROR; break; } @@ -3253,14 +3256,15 @@ static int32_t rewriteExprSubQuery(STranslateContext* pCxt, SOperatorNode* pOp) pCxt->errCode = TSDB_CODE_SCALAR_CONVERT_ERROR; break; } - + pCxt->errCode = doRewriteExprSubQuery(pCxt, pOp, &pOp->pRight); break; } case OP_TYPE_EXISTS: case OP_TYPE_NOT_EXISTS: { if (!isSubQueryNode(pOp->pLeft)) { - parserError("pLeft %d is not subQ in op %s, exprSubQType:%d", nodeType(pOp->pLeft), operatorTypeStr(pOp->opType), pCxt->expSubQueryType); + parserError("pLeft %d is not subQ in op %s, exprSubQType:%d", nodeType(pOp->pLeft), + operatorTypeStr(pOp->opType), pCxt->expSubQueryType); pCxt->errCode = TSDB_CODE_PAR_INTERNAL_ERROR; break; } @@ -3270,7 +3274,7 @@ static int32_t rewriteExprSubQuery(STranslateContext* pCxt, SOperatorNode* pOp) pCxt->errCode = TSDB_CODE_PAR_INTERNAL_ERROR; break; } - + pCxt->errCode = doRewriteExprSubQuery(pCxt, pOp, &pOp->pLeft); break; } @@ -3303,7 +3307,7 @@ static EDealRes translateOperator(STranslateContext* pCxt, SOperatorNode* pOp) { if (TSDB_CODE_SUCCESS == code) { code = scalarGetOperatorResultType(pOp); } - + if (TSDB_CODE_SUCCESS != code) { pCxt->errCode = code; return DEAL_RES_ERROR; @@ -3514,7 +3518,8 @@ static int32_t getFuncInfo(STranslateContext* pCxt, SFunctionNode* pFunc) { code = getUdfInfo(pCxt, pFunc); } if (TSDB_CODE_MND_FUNC_NOT_EXIST == code) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_MND_FUNC_NOT_EXIST, "function '%s' is not defined", pFunc->functionName); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_MND_FUNC_NOT_EXIST, "function '%s' is not defined", + pFunc->functionName); } return code; } @@ -3532,7 +3537,8 @@ static int32_t translateAggFunc(STranslateContext* pCxt, SFunctionNode* pFunc) { // The auto-generated COUNT function in the DELETE statement is legal if (isSelectStmt(pCxt->pCurrStmt) && (((SSelectStmt*)pCxt->pCurrStmt)->hasIndefiniteRowsFunc || ((SSelectStmt*)pCxt->pCurrStmt)->hasMultiRowsFunc)) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, "Aggregate func '%s' mixed with other multi-row functions", pFunc->functionName); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Aggregate func '%s' mixed with other multi-row functions", pFunc->functionName); } if (isCountStar(pFunc)) { @@ -3552,23 +3558,28 @@ static int32_t translateIndefiniteRowsFunc(STranslateContext* pCxt, SFunctionNod return TSDB_CODE_SUCCESS; } if (!isSelectStmt(pCxt->pCurrStmt) || SQL_CLAUSE_SELECT != pCxt->currClause) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, "Indefinite rows function '%s' only allowed in select clause", pFunc->functionName); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Indefinite rows function '%s' only allowed in select clause", pFunc->functionName); } SSelectStmt* pSelect = (SSelectStmt*)pCxt->pCurrStmt; if (pSelect->hasAggFuncs || pSelect->hasMultiRowsFunc) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, "Indefinite rows function '%s' mixed with other multi-row functions", pFunc->functionName); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Indefinite rows function '%s' mixed with other multi-row functions", + pFunc->functionName); } if (pSelect->hasIndefiniteRowsFunc && (FUNC_RETURN_ROWS_INDEFINITE == pSelect->returnRows || pSelect->returnRows != fmGetFuncReturnRows(pFunc)) && (pSelect->lastProcessByRowFuncId == -1 || !fmIsProcessByRowFunc(pFunc->funcId))) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, "Multiple indefinite rows functions with different return rows"); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Multiple indefinite rows functions with different return rows"); } if (pSelect->lastProcessByRowFuncId != -1 && pSelect->lastProcessByRowFuncId != pFunc->funcId) { return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_DIFFERENT_BY_ROW_FUNC); } if (NULL != pSelect->pWindow || NULL != pSelect->pGroupByList) { return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, - "Function '%s' is not supported in window query or group query", pFunc->functionName); + "Function '%s' is not supported in window query or group query", + pFunc->functionName); } if (hasInvalidFuncNesting(pFunc)) { return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_AGG_FUNC_NESTING); @@ -3583,7 +3594,8 @@ static int32_t translateMultiRowsFunc(STranslateContext* pCxt, SFunctionNode* pF if (!isSelectStmt(pCxt->pCurrStmt) || SQL_CLAUSE_SELECT != pCxt->currClause || ((SSelectStmt*)pCxt->pCurrStmt)->hasIndefiniteRowsFunc || ((SSelectStmt*)pCxt->pCurrStmt)->hasAggFuncs || ((SSelectStmt*)pCxt->pCurrStmt)->hasMultiRowsFunc) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, "Multi-rows function '%s' only allowed in select clause", pFunc->functionName); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Multi-rows function '%s' only allowed in select clause", pFunc->functionName); } if (hasInvalidFuncNesting(pFunc)) { return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_AGG_FUNC_NESTING); @@ -3598,25 +3610,32 @@ static int32_t translateInterpFunc(STranslateContext* pCxt, SFunctionNode* pFunc if (!isSelectStmt(pCxt->pCurrStmt) || (SQL_CLAUSE_SELECT != pCxt->currClause && SQL_CLAUSE_ORDER_BY != pCxt->currClause)) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, "Interp function '%s' only allowed in select or order by clause", pFunc->functionName); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Interp function '%s' only allowed in select or order by clause", + pFunc->functionName); } SSelectStmt* pSelect = (SSelectStmt*)pCxt->pCurrStmt; SNode* pTable = pSelect->pFromTable; if (pSelect->hasAggFuncs || pSelect->hasMultiRowsFunc || pSelect->hasIndefiniteRowsFunc) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, "Interp function '%s' is not allowed with aggregate, multi-rows, or indefinite rows functions", pFunc->functionName); + return generateSyntaxErrMsgExt( + &pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Interp function '%s' is not allowed with aggregate, multi-rows, or indefinite rows functions", + pFunc->functionName); } if (pSelect->hasInterpFunc && (FUNC_RETURN_ROWS_INDEFINITE == pSelect->returnRows || pSelect->returnRows != fmGetFuncReturnRows(pFunc))) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, - "Function '%s' ignoring null value options cannot be used when applying to multiple columns", - pFunc->functionName); + return generateSyntaxErrMsgExt( + &pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Function '%s' ignoring null value options cannot be used when applying to multiple columns", + pFunc->functionName); } if (NULL != pSelect->pWindow || NULL != pSelect->pGroupByList) { return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, - "Function '%s' is not supported in window query or group query", pFunc->functionName); + "Function '%s' is not supported in window query or group query", + pFunc->functionName); } if (hasInvalidFuncNesting(pFunc)) { return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_AGG_FUNC_NESTING); @@ -3664,25 +3683,31 @@ static int32_t translateForecastFunc(STranslateContext* pCxt, SFunctionNode* pFu return TSDB_CODE_SUCCESS; } if (!isSelectStmt(pCxt->pCurrStmt) || SQL_CLAUSE_SELECT != pCxt->currClause) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, "Forecast function '%s' only allowed in select clause", pFunc->functionName); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Forecast function '%s' only allowed in select clause", pFunc->functionName); } SSelectStmt* pSelect = (SSelectStmt*)pCxt->pCurrStmt; SNode* pTable = pSelect->pFromTable; if (pSelect->hasAggFuncs || pSelect->hasMultiRowsFunc || pSelect->hasIndefiniteRowsFunc) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, "Forecast function '%s' is not allowed with aggregate, multi-rows, or indefinite rows functions", pFunc->functionName); + return generateSyntaxErrMsgExt( + &pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Forecast function '%s' is not allowed with aggregate, multi-rows, or indefinite rows functions", + pFunc->functionName); } if (pSelect->hasForecastFunc && (FUNC_RETURN_ROWS_INDEFINITE == pSelect->returnRows || pSelect->returnRows != fmGetFuncReturnRows(pFunc))) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, - "Forecast function '%s' ignoring null value options cannot be used when applying to multiple columns", - pFunc->functionName); + return generateSyntaxErrMsgExt( + &pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Forecast function '%s' ignoring null value options cannot be used when applying to multiple columns", + pFunc->functionName); } if (NULL != pSelect->pWindow || NULL != pSelect->pGroupByList) { return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, - "Forecast function '%s' is not supported in window query or group query", pFunc->functionName); + "Forecast function '%s' is not supported in window query or group query", + pFunc->functionName); } if (hasInvalidFuncNesting(pFunc)) { return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_AGG_FUNC_NESTING); @@ -3707,8 +3732,8 @@ static int32_t translateAnalysisPseudoColumnFunc(STranslateContext* pCxt, SNode* } if (pCxt->currClause == SQL_CLAUSE_WHERE) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, "Function '%s' is not allowed in where clause", - pFunc->functionName); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FUNC, + "Function '%s' is not allowed in where clause", pFunc->functionName); } FOREACH(pNode, pSelect->pProjectionList) { @@ -3825,7 +3850,8 @@ static EDealRes translatePlaceHolderFunc(STranslateContext* pCxt, SNode** pFunc) "stream placeholder should only appear in create stream's query part")); } - if (pCxt->currClause != SQL_CLAUSE_SELECT && pCxt->currClause != SQL_CLAUSE_WHERE && pCxt->currClause != SQL_CLAUSE_ORDER_BY) { + if (pCxt->currClause != SQL_CLAUSE_SELECT && pCxt->currClause != SQL_CLAUSE_WHERE && + pCxt->currClause != SQL_CLAUSE_ORDER_BY) { PAR_ERR_JRET(generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_STREAM_INVALID_PLACE_HOLDER, "stream placeholder should only appear in select, where and order by clause")); } @@ -4486,8 +4512,8 @@ static int32_t rewriteClientPseudoColumnFunc(STranslateContext* pCxt, SNode** pN default: break; } - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_FUNC_FUNTION_ERROR, - "%s get invalid funcType: %d", ((SFunctionNode*)*pNode)->functionName, ((SFunctionNode*)*pNode)->funcType); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_FUNC_FUNTION_ERROR, "%s get invalid funcType: %d", + ((SFunctionNode*)*pNode)->functionName, ((SFunctionNode*)*pNode)->funcType); } static int32_t translateFunctionImpl(STranslateContext* pCxt, SFunctionNode** pFunc) { @@ -4551,7 +4577,7 @@ static EDealRes translateFunction(STranslateContext* pCxt, SFunctionNode** pFunc static int32_t updateSubQResType(SNode* pNode) { int32_t code = TSDB_CODE_SUCCESS; - + switch (nodeType(pNode)) { case QUERY_NODE_SELECT_STMT: { SSelectStmt* pSelect = (SSelectStmt*)pNode; @@ -4592,7 +4618,7 @@ static EDealRes translateExprSubquery(STranslateContext* pCxt, SNode** pNode) { if (TSDB_CODE_SUCCESS == pCxt->errCode && E_SUB_QUERY_COLUMN == pCxt->expSubQueryType) { pCxt->errCode = updateSubQResType(*pNode); } - + return (TSDB_CODE_SUCCESS == pCxt->errCode) ? DEAL_RES_CONTINUE : DEAL_RES_ERROR; } @@ -5275,7 +5301,8 @@ static EDealRes doCheckAggColCoexist(SNode** pNode, void* pContext) { } if ((isScanPseudoColumnFunc(*pNode) || QUERY_NODE_COLUMN == nodeType(*pNode)) && ((!nodesIsExprNode(*pNode) || !isRelatedToOtherExpr((SExprNode*)*pNode)))) { - if (!pCxt->existColName) pCxt->existColName = getExistColName(*pNode); // Return the first disallowed column name as a prompt + if (!pCxt->existColName) + pCxt->existColName = getExistColName(*pNode); // Return the first disallowed column name as a prompt pCxt->existCol = true; } return DEAL_RES_CONTINUE; @@ -5288,7 +5315,8 @@ static EDealRes doCheckGetAggColCoexist(SNode** pNode, void* pContext) { return DEAL_RES_IGNORE_CHILD; } if (isScanPseudoColumnFunc(*pNode) || QUERY_NODE_COLUMN == nodeType(*pNode)) { - if (!pCxt->existColName) pCxt->existColName = getExistColName(*pNode); // Return the first disallowed column name as a prompt + if (!pCxt->existColName) + pCxt->existColName = getExistColName(*pNode); // Return the first disallowed column name as a prompt pCxt->existCol = true; code = nodesListMakeStrictAppend(&pCxt->pColList, *pNode); if (TSDB_CODE_SUCCESS != code) { @@ -5491,7 +5519,8 @@ static int32_t dnodeToVgroupsInfo(SArray* pDnodes, SVgroupsInfo** pVgsInfo) { static bool sysTableFromVnode(const char* pTable) { return ((0 == strcmp(pTable, TSDB_INS_TABLE_TABLES)) || (0 == strcmp(pTable, TSDB_INS_TABLE_TAGS)) || (0 == strcmp(pTable, TSDB_INS_TABLE_COLS)) || 0 == strcmp(pTable, TSDB_INS_TABLE_VC_COLS) || - 0 == strcmp(pTable, TSDB_INS_DISK_USAGE) || (0 == strcmp(pTable, TSDB_INS_TABLE_FILESETS))); + 0 == strcmp(pTable, TSDB_INS_DISK_USAGE) || (0 == strcmp(pTable, TSDB_INS_TABLE_FILESETS)) || + (0 == strcmp(pTable, TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING))); } static bool sysTableFromDnode(const char* pTable) { return 0 == strcmp(pTable, TSDB_INS_TABLE_DNODE_VARIABLES); } @@ -5565,7 +5594,8 @@ static int32_t setVnodeSysTableVgroupList(STranslateContext* pCxt, SName* pName, ((0 == strcmp(pRealTable->table.tableName, TSDB_INS_TABLE_TABLES) && !hasUserDbCond) || 0 == strcmp(pRealTable->table.tableName, TSDB_INS_TABLE_COLS) || (0 == strcmp(pRealTable->table.tableName, TSDB_INS_DISK_USAGE) && !hasUserDbCond) || - 0 == strcmp(pRealTable->table.tableName, TSDB_INS_TABLE_FILESETS))) { + 0 == strcmp(pRealTable->table.tableName, TSDB_INS_TABLE_FILESETS) || + 0 == strcmp(pRealTable->table.tableName, TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING))) { code = addMnodeToVgroupList(&pCxt->pParseCxt->mgmtEpSet, &pVgs); } @@ -5768,7 +5798,8 @@ static bool isSingleTable(SRealTableNode* pRealTable) { 0 != strcmp(pRealTable->table.tableName, TSDB_INS_TABLE_COLS) && 0 != strcmp(pRealTable->table.tableName, TSDB_INS_DISK_USAGE) && 0 != strcmp(pRealTable->table.tableName, TSDB_INS_TABLE_FILESETS) && - 0 != strcmp(pRealTable->table.tableName, TSDB_INS_TABLE_VC_COLS); + 0 != strcmp(pRealTable->table.tableName, TSDB_INS_TABLE_VC_COLS) && + 0 != strcmp(pRealTable->table.tableName, TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING); } return (TSDB_CHILD_TABLE == tableType || TSDB_NORMAL_TABLE == tableType || TSDB_VIRTUAL_CHILD_TABLE == tableType || TSDB_VIRTUAL_NORMAL_TABLE == tableType); @@ -6341,11 +6372,11 @@ static int32_t makeVtableMetaScanTable(STranslateContext* pCxt, SRealTableNode** static int32_t translateVirtualSuperTable(STranslateContext* pCxt, SNode** pTable, SName* pName, SVirtualTableNode* pVTable) { - SRealTableNode* pRealTable = (SRealTableNode*)*pTable; - STableMeta* pMeta = pRealTable->pMeta; - int32_t code = TSDB_CODE_SUCCESS; - SRealTableNode* pInsCols = NULL; - bool refTablesAdded = false; + SRealTableNode* pRealTable = (SRealTableNode*)*pTable; + STableMeta* pMeta = pRealTable->pMeta; + int32_t code = TSDB_CODE_SUCCESS; + SRealTableNode* pInsCols = NULL; + bool refTablesAdded = false; if (!pMeta->virtualStb) { PAR_ERR_JRET(TSDB_CODE_PAR_INVALID_TABLE_TYPE); @@ -6486,8 +6517,8 @@ static int32_t translateVirtualNormalChildTable(STranslateContext* pCxt, SNode** // table scan node is eliminated. const SSchema* pTsSchema = &pMeta->schema[0]; const SSchema* pRefTsSchema = &pRTNode->pMeta->schema[0]; - PAR_ERR_JRET(setColRef(&pMeta->colRef[0], pTsSchema->colId, NULL, (char*)pRefTsSchema->name, pRTNode->table.tableName, - pRTNode->table.dbName)); + PAR_ERR_JRET(setColRef(&pMeta->colRef[0], pTsSchema->colId, NULL, (char*)pRefTsSchema->name, + pRTNode->table.tableName, pRTNode->table.dbName)); } } nodesDestroyNode(*pTable); @@ -6863,7 +6894,7 @@ static bool isVirtualSTable(STableMeta* meta) { return meta->virtualStb; } static int32_t transSetSysDbPrivs(STranslateContext* pCxt, const char* qualDbName) { #ifdef TD_ENTERPRISE - SParseContext* pParCxt = pCxt->pParseCxt; + SParseContext* pParCxt = pCxt->pParseCxt; SGetUserAuthRsp authRsp = {0}; SRequestConnInfo conn = {.pTrans = pParCxt->pTransporter, .requestId = pParCxt->requestId, @@ -7439,7 +7470,7 @@ static int32_t prepareColumnExpansion(STranslateContext* pCxt, ESqlClause clause } static EDealRes checkAggFuncExist(SNode* pNode, void* found) { - if(isAggFunc(pNode)) { + if (isAggFunc(pNode)) { *(bool*)found = true; return DEAL_RES_END; } @@ -7517,21 +7548,16 @@ static int32_t convertFillValue(STranslateContext* pCxt, SDataType dt, SNodeList return code; } -static int32_t doCheckFillValues(STranslateContext* pCxt, SFillNode* pFill, - SNodeList* pProjectionList) { +static int32_t doCheckFillValues(STranslateContext* pCxt, SFillNode* pFill, SNodeList* pProjectionList) { int32_t fillNo = 0; SNodeListNode* pFillValues = (SNodeListNode*)pFill->pValues; SNode* pProject = NULL; FOREACH(pProject, pProjectionList) { if (needFill(pProject)) { - if (NULL == pFillValues || - fillNo >= LIST_LENGTH(pFillValues->pNodeList)) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, - TSDB_CODE_PAR_WRONG_VALUE_TYPE, - "Too few fill values specified"); - } - int32_t code = convertFillValue(pCxt, ((SExprNode*)pProject)->resType, - pFillValues->pNodeList, fillNo); + if (NULL == pFillValues || fillNo >= LIST_LENGTH(pFillValues->pNodeList)) { + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_WRONG_VALUE_TYPE, "Too few fill values specified"); + } + int32_t code = convertFillValue(pCxt, ((SExprNode*)pProject)->resType, pFillValues->pNodeList, fillNo); if (TSDB_CODE_SUCCESS != code) { return code; } @@ -7541,15 +7567,12 @@ static int32_t doCheckFillValues(STranslateContext* pCxt, SFillNode* pFill, } if (NULL != pFillValues && fillNo != LIST_LENGTH(pFillValues->pNodeList)) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, - TSDB_CODE_PAR_WRONG_VALUE_TYPE, - "Too many fill values specified"); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_WRONG_VALUE_TYPE, "Too many fill values specified"); } return TSDB_CODE_SUCCESS; } -static int32_t checkFillValues(STranslateContext* pCxt, SFillNode* pFill, - SSelectStmt* pSelect) { +static int32_t checkFillValues(STranslateContext* pCxt, SFillNode* pFill, SSelectStmt* pSelect) { /* Do check fill values if: - FILL MODE is VALUE or VALUE_F @@ -7565,16 +7588,14 @@ static int32_t checkFillValues(STranslateContext* pCxt, SFillNode* pFill, static int32_t translateFillValues(STranslateContext* pCxt, SSelectStmt* pSelect) { SFillNode* pFill = NULL; - if (NULL != pSelect->pWindow && - QUERY_NODE_INTERVAL_WINDOW == nodeType(pSelect->pWindow) && + if (NULL != pSelect->pWindow && QUERY_NODE_INTERVAL_WINDOW == nodeType(pSelect->pWindow) && NULL != ((SIntervalWindowNode*)pSelect->pWindow)->pFill) { pFill = (SFillNode*)((SIntervalWindowNode*)pSelect->pWindow)->pFill; } else if (pSelect->hasInterpFunc && NULL != pSelect->pFill) { pFill = (SFillNode*)pSelect->pFill; } - return NULL == pFill ? TSDB_CODE_SUCCESS : - checkFillValues(pCxt, pFill, pSelect); + return NULL == pFill ? TSDB_CODE_SUCCESS : checkFillValues(pCxt, pFill, pSelect); } static int32_t rewriteProjectAlias(SNodeList* pProjectionList) { @@ -7884,9 +7905,7 @@ static int32_t translateProcessMaskColFunc(STranslateContext* pCxt, SSelectStmt* SCatalog* pCatalog = pParseCxt->pCatalog; SNode* pNode = NULL; - FOREACH(pNode, pSelect->pProjectionList) { - (void)rewriteMaskColFunc(pCxt, pSelect, &pNode); - } + FOREACH(pNode, pSelect->pProjectionList) { (void)rewriteMaskColFunc(pCxt, pSelect, &pNode); } #endif return TSDB_CODE_SUCCESS; } @@ -8262,25 +8281,21 @@ static int32_t getQueryTimeRange(STranslateContext* pCxt, SNode** pWhere, STimeW return code; } -static int32_t translateSurroundingTime(STranslateContext* pCxt, - SNode* pSurroundingTime) { +static int32_t translateSurroundingTime(STranslateContext* pCxt, SNode* pSurroundingTime) { if (pSurroundingTime != NULL) { const SValueNode* pSurroundingTimeVal = (SValueNode*)pSurroundingTime; if (pSurroundingTimeVal->flag & VALUE_FLAG_IS_DURATION) { if (pSurroundingTimeVal->datum.i <= 0) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, - TSDB_CODE_PAR_INVALID_SURROUND_TIME_VALUES, - "Surrounding time value must be greater than 0"); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_SURROUND_TIME_VALUES, + "Surrounding time value must be greater than 0"); } int8_t unit = pSurroundingTimeVal->unit; if (unit == TIME_UNIT_YEAR || unit == TIME_UNIT_MONTH) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, - TSDB_CODE_PAR_INVALID_SURROUND_TIME_VALUES, - "Unsupported time unit in surrounding time: %c", unit); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_SURROUND_TIME_VALUES, + "Unsupported time unit in surrounding time: %c", unit); } } else { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, - TSDB_CODE_PAR_INVALID_SURROUND_TIME_VALUES, + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_SURROUND_TIME_VALUES, "Surrounding time value must be a " "duration expression"); } @@ -8339,18 +8354,16 @@ static int32_t checkFill(STranslateContext* pCxt, SFillNode* pFill, SValueNode* return TSDB_CODE_SUCCESS; } -static int32_t translateFill(STranslateContext* pCxt, SSelectStmt* pSelect, - SIntervalWindowNode* pInterval) { +static int32_t translateFill(STranslateContext* pCxt, SSelectStmt* pSelect, SIntervalWindowNode* pInterval) { if (NULL == pInterval->pFill) { return TSDB_CODE_SUCCESS; } - + SFillNode* pFill = (SFillNode*)pInterval->pFill; pFill->timeRange = pSelect->timeRange; PAR_ERR_RET(nodesCloneNode(pSelect->pTimeRange, &pFill->pTimeRange)); PAR_ERR_RET(translateSurroundingTime(pCxt, pFill->pSurroundingTime)); - return checkFill(pCxt, pFill, (SValueNode*)pInterval->pInterval, false, - pSelect->precision); + return checkFill(pCxt, pFill, (SValueNode*)pInterval->pInterval, false, pSelect->precision); } static int32_t getMonthsFromTimeVal(int64_t val, int32_t fromPrecision, char unit, double* pMonth) { @@ -8459,7 +8472,7 @@ static int32_t checkIntervalWindow(STranslateContext* pCxt, SIntervalWindowNode* return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_INTER_OFFSET_TOO_BIG); } if (!fixed) { - double offsetMonth = 0, intervalMonth = 0; + double offsetMonth = 0, intervalMonth = 0; code = getMonthsFromTimeVal(pOffset->datum.i, precision, pOffset->unit, &offsetMonth); TAOS_CHECK_GOTO(code, &lino, _exit); @@ -8490,7 +8503,7 @@ static int32_t checkIntervalWindow(STranslateContext* pCxt, SIntervalWindowNode* } if (valInter) { if (IS_CALENDAR_TIME_DURATION(pSliding->unit)) { - double slidingMonth = 0, intervalMonth = 0; + double slidingMonth = 0, intervalMonth = 0; code = getMonthsFromTimeVal(pSliding->datum.i, precision, pSliding->unit, &slidingMonth); TAOS_CHECK_GOTO(code, &lino, _exit); @@ -8500,8 +8513,7 @@ static int32_t checkIntervalWindow(STranslateContext* pCxt, SIntervalWindowNode* if (slidingMonth > intervalMonth) { return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_INTER_SLIDING_TOO_BIG); } - } - else { + } else { int64_t interverNano = 0, slidingNano = 0; code = convertCalendarTimeFromUnitToPrecision(pInter->datum.i, pInter->unit, precision, &interverNano); TAOS_CHECK_GOTO(code, &lino, _exit); @@ -8522,23 +8534,18 @@ static int32_t checkIntervalWindow(STranslateContext* pCxt, SIntervalWindowNode* if (NULL != pInterval->pFill) { SFillNode* pFill = (SFillNode*)pInterval->pFill; if (pFill->mode == FILL_MODE_NEAR) { - return generateSyntaxErrMsg(&pCxt->msgBuf, - TSDB_CODE_PAR_NOT_ALLOWED_FILL_MODE); + return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FILL_MODE); } - if (pFill->pValues != NULL && - !(pFill->mode == FILL_MODE_VALUE || pFill->mode == FILL_MODE_VALUE_F) && - !((pFill->mode == FILL_MODE_PREV || pFill->mode == FILL_MODE_NEXT) && - pFill->pSurroundingTime != NULL)) { - return generateSyntaxErrMsg(&pCxt->msgBuf, - TSDB_CODE_PAR_NOT_ALLOWED_FILL_VALUES); + if (pFill->pValues != NULL && !(pFill->mode == FILL_MODE_VALUE || pFill->mode == FILL_MODE_VALUE_F) && + !((pFill->mode == FILL_MODE_PREV || pFill->mode == FILL_MODE_NEXT) && pFill->pSurroundingTime != NULL)) { + return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FILL_VALUES); } if (pFill->pSurroundingTime != NULL) { const SValueNode* pSurroundingTime = (SValueNode*)pFill->pSurroundingTime; if (pSurroundingTime->datum.i < pInter->datum.i) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, - TSDB_CODE_PAR_INVALID_SURROUND_TIME_VALUES, - "Surrounding time cannot be less than interval size: %ld%s", - pInter->datum.i, getPrecisionStr(precision)); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_SURROUND_TIME_VALUES, + "Surrounding time cannot be less than interval size: %ld%s", pInter->datum.i, + getPrecisionStr(precision)); } } } @@ -9053,17 +9060,17 @@ static int32_t translateCountWindow(STranslateContext* pCxt, SSelectStmt* pSelec } static int32_t checkAnomalyExpr(STranslateContext* pCxt, SNodeList* pNodeList) { - for(int32_t i = 0; i < pNodeList->length; ++i) { - SNode* pNode = nodesListGetNode(pNodeList, i); + for (int32_t i = 0; i < pNodeList->length; ++i) { + SNode* pNode = nodesListGetNode(pNodeList, i); int32_t type = ((SExprNode*)pNode)->resType.type; if (!IS_MATHABLE_TYPE(type)) { return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_ANOMALY_WIN_TYPE, - "ANOMALY_WINDOW only support mathable column"); + "ANOMALY_WINDOW only support mathable column"); } if (QUERY_NODE_COLUMN == nodeType(pNode) && COLUMN_TYPE_TAG == ((SColumnNode*)pNode)->colType) { return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_ANOMALY_WIN_COL, - "ANOMALY_WINDOW not support on tag column"); + "ANOMALY_WINDOW not support on tag column"); } } @@ -9266,26 +9273,20 @@ static int32_t translateInterpFill(STranslateContext* pCxt, SSelectStmt* pSelect code = translateExpr(pCxt, (SNode**)&pFill); } if (TSDB_CODE_SUCCESS == code) { - code = getQueryTimeRange(pCxt, &pSelect->pRange, &pFill->timeRange, - &pFill->pTimeRange, pSelect->pFromTable); + code = getQueryTimeRange(pCxt, &pSelect->pRange, &pFill->timeRange, &pFill->pTimeRange, pSelect->pFromTable); } if (TSDB_CODE_SUCCESS == code) { code = translateSurroundingTime(pCxt, pFill->pSurroundingTime); } if (TSDB_CODE_SUCCESS == code) { - code = checkFill(pCxt, pFill, (SValueNode*)pSelect->pEvery, true, - pSelect->precision); + code = checkFill(pCxt, pFill, (SValueNode*)pSelect->pEvery, true, pSelect->precision); } QUERY_CHECK_CODE(code, lino, _end); bool hasRowTsOriginFunc = false; - nodesWalkExprs(pSelect->pProjectionList, hasRowTsOriginFuncWalkNode, - &hasRowTsOriginFunc); - bool isPrevNextNear = pFill->mode == FILL_MODE_PREV || - pFill->mode == FILL_MODE_NEXT || - pFill->mode == FILL_MODE_NEAR; + nodesWalkExprs(pSelect->pProjectionList, hasRowTsOriginFuncWalkNode, &hasRowTsOriginFunc); + bool isPrevNextNear = pFill->mode == FILL_MODE_PREV || pFill->mode == FILL_MODE_NEXT || pFill->mode == FILL_MODE_NEAR; if (hasRowTsOriginFunc && !isPrevNextNear) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, - TSDB_CODE_PAR_FILL_NOT_ALLOWED_FUNC, + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_FILL_NOT_ALLOWED_FUNC, "_irowts_origin can only be used with " "FILL PREV/NEXT/NEAR"); } @@ -9293,24 +9294,20 @@ static int32_t translateInterpFill(STranslateContext* pCxt, SSelectStmt* pSelect const SNode* pSurroundingTime = pFill->pSurroundingTime; const SNode* pRangeAround = pSelect->pRangeAround; if (NULL != pSurroundingTime && NULL != pRangeAround) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, - TSDB_CODE_PAR_INVALID_SURROUND_TIME_VALUES, + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_SURROUND_TIME_VALUES, "Surrounding time and range interval " "cannot be provided together"); } bool isSurround = NULL != pSurroundingTime || NULL != pRangeAround; if (isSurround && !isPrevNextNear) { /* Only PREV/NEXT/NEAR mode is supported with surrounding time */ - return generateSyntaxErrMsgExt(&pCxt->msgBuf, - TSDB_CODE_PAR_INVALID_SURROUND_TIME_VALUES, + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_SURROUND_TIME_VALUES, "Only PREV/NEXT/NEAR mode is supported with " "surrounding time"); } - if (pFill->pValues != NULL && - !(pFill->mode == FILL_MODE_VALUE || pFill->mode == FILL_MODE_VALUE_F) && + if (pFill->pValues != NULL && !(pFill->mode == FILL_MODE_VALUE || pFill->mode == FILL_MODE_VALUE_F) && !(isPrevNextNear && isSurround)) { - return generateSyntaxErrMsg(&pCxt->msgBuf, - TSDB_CODE_PAR_NOT_ALLOWED_FILL_VALUES); + return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_NOT_ALLOWED_FILL_VALUES); } _end: @@ -10067,7 +10064,8 @@ static int32_t createPkColByTable(STranslateContext* pCxt, SRealTableNode* pTabl bool found = false; PAR_ERR_JRET(findAndSetColumn(pCxt, &pCol, (STableNode*)pTable, &found, true)); if (!found) { - PAR_ERR_JRET(generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_COLUMN, "failed to find PK column in table %s", pTable->table.tableName)); + PAR_ERR_JRET(generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_COLUMN, + "failed to find PK column in table %s", pTable->table.tableName)); } *pPk = (SNode*)pCol; return code; @@ -10199,11 +10197,12 @@ static void resetResultTimeline(STranslateContext* pCxt, SSelectStmt* pSelect) { if (NULL == pSelect->pOrderByList) { if (inStreamCalcClause(pCxt)) { SNode* pNode = nodesListGetNode(pSelect->pProjectionList, 0); - if (pNode && QUERY_NODE_FUNCTION == nodeType(pNode) && fmIsPlaceHolderFunc(((SFunctionNode*)pNode)->funcId) && isPrimaryKeyImpl(pNode)) { + if (pNode && QUERY_NODE_FUNCTION == nodeType(pNode) && fmIsPlaceHolderFunc(((SFunctionNode*)pNode)->funcId) && + isPrimaryKeyImpl(pNode)) { pSelect->timeLineResMode = TIME_LINE_GLOBAL; } } - + return; } SNode* pOrder = ((SOrderByExprNode*)nodesListGetNode(pSelect->pOrderByList, 0))->pExpr; @@ -11006,7 +11005,7 @@ static int32_t translateInsertProject(STranslateContext* pCxt, SInsertStmt* pIns int16_t numOfBoundPKs = 0; FORBOTH(pBoundCol, pInsert->pCols, pProj, pProjects) { if (QUERY_NODE_FUNCTION == nodeType(pBoundCol)) { - if(!IS_STR_DATA_TYPE(((SExprNode*)pProj)->resType.type)){ + if (!IS_STR_DATA_TYPE(((SExprNode*)pProj)->resType.type)) { return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_TBNAME, "tbname must be a string type"); } continue; @@ -12275,7 +12274,6 @@ static int32_t translateTrimDbWal(STranslateContext* pCxt, STrimDbWalStmt* pStmt return buildCmdMsg(pCxt, TDMT_MND_TRIM_DB_WAL, (FSerializeFunc)tSerializeSTrimDbReq, &req); } - static int32_t translateCreateToken(STranslateContext* pCxt, SCreateTokenStmt* pStmt) { SCreateTokenReq req = {0}; @@ -12292,7 +12290,6 @@ static int32_t translateCreateToken(STranslateContext* pCxt, SCreateTokenStmt* p return code; } - static int32_t translateAlterToken(STranslateContext* pCxt, SAlterTokenStmt* pStmt) { SAlterTokenReq req = {0}; STokenOptions* opts = pStmt->pTokenOptions; @@ -12317,7 +12314,6 @@ static int32_t translateAlterToken(STranslateContext* pCxt, SAlterTokenStmt* pSt return code; } - static int32_t translateDropToken(STranslateContext* pCxt, SDropTokenStmt* pStmt) { SDropTokenReq req = {0}; tstrncpy(req.name, pStmt->name, sizeof(req.name)); @@ -12857,7 +12853,8 @@ static int32_t checkTableSchemaImpl(STranslateContext* pCxt, SNodeList* pTags, S } static int32_t checkTableSchema(STranslateContext* pCxt, SCreateTableStmt* pStmt) { - return checkTableSchemaImpl(pCxt, pStmt->pTags, pStmt->pCols, pStmt->pOptions->pRollupFuncs, true, pStmt->pOptions->virtualStb); + return checkTableSchemaImpl(pCxt, pStmt->pTags, pStmt->pCols, pStmt->pOptions->pRollupFuncs, true, + pStmt->pOptions->virtualStb); } static int32_t checkVTableSchema(STranslateContext* pCxt, SCreateVTableStmt* pStmt) { @@ -13992,7 +13989,7 @@ static int32_t translateCreateUser(STranslateContext* pCxt, SCreateUserStmt* pSt SCreateUserReq createReq = {0}; #ifdef TD_ENTERPRISE - if((code = translateCheckUserOptsPriv(pCxt, pStmt, &pStmt->userOps, false))) { + if ((code = translateCheckUserOptsPriv(pCxt, pStmt, &pStmt->userOps, false))) { return code; } #endif @@ -14077,14 +14074,13 @@ static int32_t translateCreateEncryptAlgr(STranslateContext* pCxt, SCreateEncryp return code; } - static int32_t translateAlterUser(STranslateContext* pCxt, SAlterUserStmt* pStmt) { int32_t code = 0; SAlterUserReq alterReq = {0}; SUserOptions* opts = pStmt->pUserOptions; #ifdef TD_ENTERPRISE - if((code = translateCheckUserOptsPriv(pCxt, pStmt, opts, true))) { + if ((code = translateCheckUserOptsPriv(pCxt, pStmt, opts, true))) { return code; } #endif @@ -14144,13 +14140,13 @@ static int32_t translateAlterUser(STranslateContext* pCxt, SAlterUserStmt* pStmt alterReq.numTimeRanges = LIST_LENGTH(opts->pTimeRanges); alterReq.numDropIpRanges = LIST_LENGTH(opts->pDropIpRanges); alterReq.numDropTimeRanges = LIST_LENGTH(opts->pDropTimeRanges); -if (alterReq.numIpRanges > 0) { + if (alterReq.numIpRanges > 0) { alterReq.pIpRanges = taosMemoryMalloc(alterReq.numIpRanges * sizeof(SIpRange)); if (!alterReq.pIpRanges) { tFreeSAlterUserReq(&alterReq); return terrno; } - int i = 0; + int i = 0; SNode* pNode = NULL; FOREACH(pNode, opts->pIpRanges) { SIpRangeNode* node = (SIpRangeNode*)(pNode); @@ -14164,7 +14160,7 @@ if (alterReq.numIpRanges > 0) { tFreeSAlterUserReq(&alterReq); return terrno; } - int i = 0; + int i = 0; SNode* pNode = NULL; FOREACH(pNode, opts->pTimeRanges) { SDateTimeRangeNode* node = (SDateTimeRangeNode*)(pNode); @@ -14178,7 +14174,7 @@ if (alterReq.numIpRanges > 0) { tFreeSAlterUserReq(&alterReq); return terrno; } - int i = 0; + int i = 0; SNode* pNode = NULL; FOREACH(pNode, opts->pDropIpRanges) { SIpRangeNode* node = (SIpRangeNode*)(pNode); @@ -14192,7 +14188,7 @@ if (alterReq.numIpRanges > 0) { tFreeSAlterUserReq(&alterReq); return terrno; } - int i = 0; + int i = 0; SNode* pNode = NULL; FOREACH(pNode, opts->pDropTimeRanges) { SDateTimeRangeNode* node = (SDateTimeRangeNode*)(pNode); @@ -14205,8 +14201,6 @@ if (alterReq.numIpRanges > 0) { return code; } - - static int32_t translateDropUser(STranslateContext* pCxt, SDropUserStmt* pStmt) { SDropUserReq dropReq = {0}; tstrncpy(dropReq.user, pStmt->userName, TSDB_USER_LEN); @@ -14484,8 +14478,7 @@ static int32_t translateCreateXnodeTask(STranslateContext* pCxt, SCreateXnodeTas } } - code = - buildCmdMsg(pCxt, TDMT_MND_CREATE_XNODE_TASK, (FSerializeFunc)tSerializeSMCreateXnodeTaskReq, &createReq); + code = buildCmdMsg(pCxt, TDMT_MND_CREATE_XNODE_TASK, (FSerializeFunc)tSerializeSMCreateXnodeTaskReq, &createReq); tFreeSMCreateXnodeTaskReq(&createReq); return code; } @@ -14568,14 +14561,15 @@ static int32_t translateUpdateXnodeTask(STranslateContext* pCxt, SUpdateXnodeTas } } - int32_t code = buildCmdMsg(pCxt, TDMT_MND_UPDATE_XNODE_TASK, (FSerializeFunc)tSerializeSMUpdateXnodeTaskReq, &updateReq); + int32_t code = + buildCmdMsg(pCxt, TDMT_MND_UPDATE_XNODE_TASK, (FSerializeFunc)tSerializeSMUpdateXnodeTaskReq, &updateReq); tFreeSMUpdateXnodeTaskReq(&updateReq); return code; } static int32_t translateCreateXnodeJob(STranslateContext* pCxt, SCreateXnodeJobStmt* pStmt) { SMCreateXnodeJobReq createReq = {0}; - const char* config = getXnodeTaskOptionByName(pStmt->options, "config"); + const char* config = getXnodeTaskOptionByName(pStmt->options, "config"); if (config == NULL) { return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_MND_XNODE_JOB_SYNTAX_ERROR, "Missing option: config"); } @@ -14594,7 +14588,8 @@ static int32_t translateCreateXnodeJob(STranslateContext* pCxt, SCreateXnodeJobS const char* status = getXnodeTaskOptionByName(pStmt->options, "status"); if (status != NULL) { if (strlen(status) > TSDB_XNODE_STATUS_LEN) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_MND_XNODE_JOB_SYNTAX_ERROR, "Invalid option: status too long"); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_MND_XNODE_JOB_SYNTAX_ERROR, + "Invalid option: status too long"); } createReq.status = xCreateCowStr(strlen(status), status, false); } @@ -14708,10 +14703,10 @@ bool isXnodeSupportNodeType(SNode* pWhere) { } static int32_t translateRebalanceXnodeJobWhere(STranslateContext* pCxt, SRebalanceXnodeJobWhereStmt* pStmt) { - int32_t code = 0; + int32_t code = 0; SMRebalanceXnodeJobsWhereReq rebalanceReq = {0}; char* pAst = NULL; - int32_t astLen = 0; + int32_t astLen = 0; if (pStmt->pWhere != NULL) { TAOS_CHECK_GOTO(nodesNodeToString(pStmt->pWhere, true, &pAst, &astLen), NULL, _exit); @@ -15164,7 +15159,7 @@ static int32_t buildCreateTopicReq(STranslateContext* pCxt, SCreateTopicStmt* pS snprintf(pReq->name, sizeof(pReq->name), "%d.%s", pCxt->pParseCxt->acctId, pStmt->topicName); pReq->igExists = pStmt->ignoreExists; pReq->withMeta = pStmt->withMeta; - pReq->reload = pStmt->reload; + pReq->reload = pStmt->reload; pReq->sql = taosStrdup(pCxt->pParseCxt->pSql); if (NULL == pReq->sql) { @@ -15196,7 +15191,7 @@ static int32_t buildCreateTopicReq(STranslateContext* pCxt, SCreateTopicStmt* pS pCxt->pParseCxt->topicQuery = true; code = translateQuery(pCxt, pStmt->pQuery); } - if (TSDB_CODE_SUCCESS == code && realTable->pMeta != NULL && realTable->pMeta->tableType == TSDB_SUPER_TABLE){ + if (TSDB_CODE_SUCCESS == code && realTable->pMeta != NULL && realTable->pMeta->tableType == TSDB_SUPER_TABLE) { code = tNameExtractFullName(&name, pReq->subStbName); } if (TSDB_CODE_SUCCESS == code) { @@ -16635,7 +16630,7 @@ static int64_t createStreamReqWindowGetBigInt(SNode* pVal) { return pVal ? ((SVa static int8_t createStreamReqWindowGetUnit(SNode* pVal) { return pVal ? ((SValueNode*)pVal)->unit : 0; } -static void createStreamReqGetTrueForOptions(SNode *pVal, int32_t *pType, int32_t *pCount, int64_t *pDuration) { +static void createStreamReqGetTrueForOptions(SNode* pVal, int32_t* pType, int32_t* pCount, int64_t* pDuration) { if (pVal == NULL) { *pType = 0; *pCount = 0; @@ -16648,7 +16643,7 @@ static void createStreamReqGetTrueForOptions(SNode *pVal, int32_t *pType, int32_ STrueForNode* pTrueFor = (STrueForNode*)pVal; *pType = pTrueFor->trueForType; *pCount = pTrueFor->count; - *pDuration = pTrueFor->pDuration ? ((SValueNode *)pTrueFor->pDuration)->datum.i : 0; + *pDuration = pTrueFor->pDuration ? ((SValueNode*)pTrueFor->pDuration)->datum.i : 0; } } @@ -17106,7 +17101,6 @@ static int32_t createStreamReqBuildForceOutput(STranslateContext* pCxt, SCreateS parserError("createStreamReqBuildForceOutput failed, code:%d", code); nodesDestroyNode((SNode*)pVal); return code; - } static int32_t extractCondFromCountWindow(STranslateContext* pCxt, SCountWindowNode* pCountWindow, SNode** pCond) { @@ -17165,7 +17159,8 @@ static int32_t createSimpleSelectStmtImpl(const char* pDb, const char* pTable, S static int32_t createSimpleSelectStmtFromCols(const char* pDb, const char* pTable, int32_t numOfProjs, const char* const pProjCol[], SSelectStmt** pStmt); // build trigger select list in create stream request -static int32_t createStreamReqBuildTriggerSelect(STranslateContext* pCxt, SRealTableNode* pTriggerTable, SSelectStmt** pTriggerSelect, SCreateStreamStmt* pStmt) { +static int32_t createStreamReqBuildTriggerSelect(STranslateContext* pCxt, SRealTableNode* pTriggerTable, + SSelectStmt** pTriggerSelect, SCreateStreamStmt* pStmt) { int32_t code = TSDB_CODE_SUCCESS; SFunctionNode* pFunc = NULL; SStreamTriggerNode* pTrigger = (SStreamTriggerNode*)pStmt->pTrigger; @@ -17174,9 +17169,11 @@ static int32_t createStreamReqBuildTriggerSelect(STranslateContext* pCxt, SRealT if (pOptions) { pPreFilter = pOptions->pPreFilter; } - PAR_ERR_JRET(createSimpleSelectStmtImpl(pTriggerTable->table.dbName, pTriggerTable->table.tableName, NULL, pTriggerSelect)); + PAR_ERR_JRET( + createSimpleSelectStmtImpl(pTriggerTable->table.dbName, pTriggerTable->table.tableName, NULL, pTriggerSelect)); // only collect columns appeared in trigger and tags in pre-filter - PAR_ERR_JRET(nodesCollectColumnsFromNode(pStmt->pTrigger, NULL, COLLECT_COL_TYPE_COL, &((SSelectStmt*)*pTriggerSelect)->pProjectionList)); + PAR_ERR_JRET(nodesCollectColumnsFromNode(pStmt->pTrigger, NULL, COLLECT_COL_TYPE_COL, + &((SSelectStmt*)*pTriggerSelect)->pProjectionList)); if (pPreFilter) { PAR_ERR_JRET(nodesCollectColumnsFromNode(pPreFilter, NULL, COLLECT_COL_TYPE_TAG, &((SSelectStmt*)*pTriggerSelect)->pProjectionList)); @@ -17190,7 +17187,6 @@ static int32_t createStreamReqBuildTriggerSelect(STranslateContext* pCxt, SRealT nodesDestroyNode((SNode*)pFunc); parserError("%s failed, code:%d", __func__, code); return code; - } // Do the translation of trigger select statement in create stream request @@ -17309,7 +17305,8 @@ static int32_t createStreamReqBuildTriggerTranslateWindow(STranslateContext* pCx } // Translate trigger partition in create stream request -static int32_t createStreamReqBuildTriggerTranslatePartition(STranslateContext* pCxt, SNodeList* pTriggerPartition, STableMeta* pTriggerTableMeta, SNode* pTriggerWindow) { +static int32_t createStreamReqBuildTriggerTranslatePartition(STranslateContext* pCxt, SNodeList* pTriggerPartition, + STableMeta* pTriggerTableMeta, SNode* pTriggerWindow) { int32_t code = TSDB_CODE_SUCCESS; pCxt->currClause = SQL_CLAUSE_PARTITION_BY; @@ -17560,7 +17557,8 @@ static int32_t translateStreamCalcQuery(STranslateContext* pCxt, SNodeList* pTri pCxt->streamInfo.calcClause = true; pCxt->pCurrStmt = (SNode*)pStreamCalcQuery; - // if stream has notification condition, add this condition to query's select list, so that we can use this column's value to judge whether to send notify later + // if stream has notification condition, add this condition to query's select list, so that we can use this column's + // value to judge whether to send notify later if (pNotifyCond) { SCheckNotifyCondContext checkNotifyCondCxt = {.pTransCxt = pCxt, .valid = true}; nodesRewriteExpr(&pNotifyCond, doCheckNotifyCond, &checkNotifyCondCxt); @@ -17646,8 +17644,8 @@ static int32_t createStreamReqBuildCalcDb(STranslateContext* pCxt, SHashObj* pDb } // Build calculation plan in create stream request -static int32_t createStreamReqBuildCalcPlan(STranslateContext* pCxt, SQueryPlan* calcPlan, - SArray* pScanPlanArray, SCMCreateStreamReq* pReq) { +static int32_t createStreamReqBuildCalcPlan(STranslateContext* pCxt, SQueryPlan* calcPlan, SArray* pScanPlanArray, + SCMCreateStreamReq* pReq) { int32_t code = TSDB_CODE_SUCCESS; SHashObj* pPlanMap = NULL; SStreamCalcScan* pCalcScan = NULL; @@ -17663,8 +17661,8 @@ static int32_t createStreamReqBuildCalcPlan(STranslateContext* pCxt, SQueryPlan* pReq->calcTsSlotId = -1; pReq->calcPkSlotId = -1; - // traverse all scan plans in calculation plan, split them from their parents, and make a fake value node to replace them. - // value node's value is the scan plan's (groupId << 32 | subplanId). + // traverse all scan plans in calculation plan, split them from their parents, and make a fake value node to replace + // them. value node's value is the scan plan's (groupId << 32 | subplanId). for (int32_t i = 0; i < taosArrayGetSize(pScanPlanArray); i++) { pCalcScan = taosArrayGet(pScanPlanArray, i); if (pCalcScan == NULL) { @@ -17747,14 +17745,14 @@ static int32_t createStreamReqBuildCalcPlan(STranslateContext* pCxt, SQueryPlan* } // Build calculate part in create stream request -static int32_t createStreamReqBuildCalc(STranslateContext* pCxt, SCreateStreamStmt* pStmt, - SNodeList *pTriggerPartition, SSelectStmt* pTriggerSelect, SNode* pTriggerWindow, SNode* pNotifyCond, +static int32_t createStreamReqBuildCalc(STranslateContext* pCxt, SCreateStreamStmt* pStmt, SNodeList* pTriggerPartition, + SSelectStmt* pTriggerSelect, SNode* pTriggerWindow, SNode* pNotifyCond, SCMCreateStreamReq* pReq) { - int32_t code = TSDB_CODE_SUCCESS; - SQueryPlan* calcPlan = NULL; - SArray* pScanPlanArray = NULL; - SHashObj* pDbs = NULL; - SNodeList* pProjectionList = NULL; + int32_t code = TSDB_CODE_SUCCESS; + SQueryPlan* calcPlan = NULL; + SArray* pScanPlanArray = NULL; + SHashObj* pDbs = NULL; + SNodeList* pProjectionList = NULL; parserDebug("translate create stream req start build calculate part, streamId:%" PRId64, pReq->streamId); @@ -17845,7 +17843,8 @@ static int32_t createStreamReqBuildCalc(STranslateContext* pCxt, SCreateStreamSt } // build default fields of create stream request -static int32_t createStreamReqBuildDefaultReq(STranslateContext* pCxt, SCreateStreamStmt* pStmt, SCMCreateStreamReq* pReq) { +static int32_t createStreamReqBuildDefaultReq(STranslateContext* pCxt, SCreateStreamStmt* pStmt, + SCMCreateStreamReq* pReq) { int32_t code = TSDB_CODE_SUCCESS; pReq->expiredTime = 0; @@ -17872,7 +17871,8 @@ static int32_t createStreamReqBuildDefaultReq(STranslateContext* pCxt, SCreateSt } // build stream name and id -static int32_t createStreamReqBuildNameAndId(STranslateContext* pCxt, SCreateStreamStmt* pStmt, SCMCreateStreamReq* pReq) { +static int32_t createStreamReqBuildNameAndId(STranslateContext* pCxt, SCreateStreamStmt* pStmt, + SCMCreateStreamReq* pReq) { int32_t code = TSDB_CODE_SUCCESS; SName streamName; @@ -17918,7 +17918,8 @@ static int32_t buildCreateStreamReq(STranslateContext* pCxt, SCreateStreamStmt* PAR_ERR_JRET(createStreamReqBuildDefaultReq(pCxt, pStmt, pReq)); PAR_ERR_JRET(createStreamReqBuildNameAndId(pCxt, pStmt, pReq)); PAR_ERR_JRET(createStreamReqBuildNotifyOptions(pCxt, pNotifyOptions, &pNotifyCond, pReq)); - // Split build AST and build plan into two steps, because trigger's select list may depend on stream's calculation part. + // Split build AST and build plan into two steps, because trigger's select list may depend on stream's calculation + // part. PAR_ERR_JRET(createStreamReqBuildTriggerAst(pCxt, pStmt, &pTriggerSelect, &pTriggerSlotHash, &pTriggerFilter, pReq)); PAR_ERR_JRET(createStreamReqBuildTriggerOptions(pCxt, pStmt, pTriggerOptions, pReq)); PAR_ERR_JRET(createStreamReqBuildCalc(pCxt, pStmt, pTrigger->pPartitionList, pTriggerSelect, pTriggerWindow, @@ -17970,8 +17971,8 @@ static int32_t translateDropStream(STranslateContext* pCxt, SDropStreamStmt* pSt int32_t i = 0; FOREACH(pStream, pStmt->pStreamList) { - SName name; - SStreamNode* pStreamNode = (SStreamNode*)pStream; + SName name; + SStreamNode* pStreamNode = (SStreamNode*)pStream; toName(pCxt->pParseCxt->acctId, pStreamNode->dbName, pStreamNode->streamName, &name); req.name[i] = taosMemoryCalloc(1, TSDB_STREAM_FNAME_LEN); @@ -18504,9 +18505,12 @@ static int32_t translateGrantFillColPrivileges(STranslateContext* pCxt, SGrantSt "Only ALTER, DROP, SHOW or SHOW CREATE table privilege can be granted on child table")); } - TAOS_CHECK_EXIT(fillPrivSetRowCols(pCxt, &pReqArgs->selectCols, pTableMeta, (SNodeList*)pPrivSetArgs->selectCols, false)); - TAOS_CHECK_EXIT(fillPrivSetRowCols(pCxt, &pReqArgs->insertCols, pTableMeta, (SNodeList*)pPrivSetArgs->insertCols, true)); - TAOS_CHECK_EXIT(fillPrivSetRowCols(pCxt, &pReqArgs->updateCols, pTableMeta, (SNodeList*)pPrivSetArgs->updateCols, true)); + TAOS_CHECK_EXIT( + fillPrivSetRowCols(pCxt, &pReqArgs->selectCols, pTableMeta, (SNodeList*)pPrivSetArgs->selectCols, false)); + TAOS_CHECK_EXIT( + fillPrivSetRowCols(pCxt, &pReqArgs->insertCols, pTableMeta, (SNodeList*)pPrivSetArgs->insertCols, true)); + TAOS_CHECK_EXIT( + fillPrivSetRowCols(pCxt, &pReqArgs->updateCols, pTableMeta, (SNodeList*)pPrivSetArgs->updateCols, true)); } _exit: @@ -18516,7 +18520,7 @@ static int32_t translateGrantFillColPrivileges(STranslateContext* pCxt, SGrantSt } static int32_t translateGrantCheckFillObject(STranslateContext* pCxt, SGrantStmt* pStmt, EPrivCategory category, - SAlterRoleReq* pReq, bool grant) { + SAlterRoleReq* pReq, bool grant) { SName name = {0}; STableMeta* pTableMeta = NULL; int32_t code = 0, lino = 0; @@ -18601,19 +18605,19 @@ static int32_t translateGrantCheckFillObject(STranslateContext* pCxt, SGrantStmt if (0 != pStmt->tabName[0]) { // not validate the object when revoke if (grant && (strncmp(pStmt->tabName, "*", 2) != 0)) { - SName name = {0}; - STableMeta* pTableMeta = NULL; - toName(pCxt->pParseCxt->acctId, pStmt->objName, pStmt->tabName, &name); - code = getTargetMeta(pCxt, &name, &pTableMeta, true); - if (TSDB_CODE_SUCCESS != code) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_GET_META_ERROR, "%s", tstrerror(code)); - } - if (((PRIV_OBJ_TBL == objType) && (TSDB_VIEW_TABLE == pTableMeta->tableType)) || - ((PRIV_OBJ_VIEW == objType) && (TSDB_VIEW_TABLE != pTableMeta->tableType))) { - taosMemoryFree(pTableMeta); - TAOS_RETURN(TSDB_CODE_PAR_PRIV_TYPE_TARGET_CONFLICT); - } + SName name = {0}; + STableMeta* pTableMeta = NULL; + toName(pCxt->pParseCxt->acctId, pStmt->objName, pStmt->tabName, &name); + code = getTargetMeta(pCxt, &name, &pTableMeta, true); + if (TSDB_CODE_SUCCESS != code) { + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_GET_META_ERROR, "%s", tstrerror(code)); + } + if (((PRIV_OBJ_TBL == objType) && (TSDB_VIEW_TABLE == pTableMeta->tableType)) || + ((PRIV_OBJ_VIEW == objType) && (TSDB_VIEW_TABLE != pTableMeta->tableType))) { taosMemoryFree(pTableMeta); + TAOS_RETURN(TSDB_CODE_PAR_PRIV_TYPE_TARGET_CONFLICT); + } + taosMemoryFree(pTableMeta); } } else { return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_SYNTAX_ERROR, @@ -18669,10 +18673,10 @@ static int32_t translateGrantCheckFillObject(STranslateContext* pCxt, SGrantStmt } _exit: if (code == TSDB_CODE_SUCCESS) { - if(pStmt->objName[0] != 0) { + if (pStmt->objName[0] != 0) { (void)snprintf(pReq->objFName, sizeof(pReq->objFName), "%d.%s", pCxt->pParseCxt->acctId, pStmt->objName); } - if(pStmt->tabName[0] != 0) { + if (pStmt->tabName[0] != 0) { (void)snprintf(pReq->tblName, sizeof(pReq->tblName), "%s", pStmt->tabName); } } @@ -18698,7 +18702,7 @@ static int32_t translateGrantRevoke(STranslateContext* pCxt, SGrantStmt* pStmt, EPrivCategory category = PRIV_CATEGORY_UNKNOWN; EPrivObjType objType = pStmt->privileges.objType; uint8_t objLevel = privObjGetLevel(objType); - SPrivSet tmpPrivSet = pStmt->privileges.privSet; // for conflict check + SPrivSet tmpPrivSet = pStmt->privileges.privSet; // for conflict check EPrivType conflict0 = PRIV_TYPE_UNKNOWN, conflict1 = PRIV_TYPE_UNKNOWN; if (objType <= PRIV_OBJ_UNKNOWN || objType >= PRIV_OBJ_MAX) { @@ -18805,12 +18809,12 @@ static int32_t translateGrantRevoke(STranslateContext* pCxt, SGrantStmt* pStmt, } } else if (conflict != 0) { return generateSyntaxErrMsg(&pCxt->msgBuf, conflict); - } else if(pStmt->pCond != NULL && objType != PRIV_OBJ_TBL) { + } else if (pStmt->pCond != NULL && objType != PRIV_OBJ_TBL) { return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_SYNTAX_ERROR, "The With clause can only be used for table privileges"); } req.privileges.privSet = pStmt->privileges.privSet; // assign the privileges - if(objType == PRIV_OBJ_VIEW) { // rewrite query for view to improve usability + if (objType == PRIV_OBJ_VIEW) { // rewrite query for view to improve usability if (PRIV_HAS(&req.privileges.privSet, PRIV_TBL_SELECT)) { privAddType(&req.privileges.privSet, PRIV_VIEW_SELECT); privRemoveType(&req.privileges.privSet, PRIV_TBL_SELECT); @@ -19005,12 +19009,36 @@ static int32_t translateShowCreateVTable(STranslateContext* pCxt, SShowCreateTab return translateShowCreateTable(pCxt, pStmt, true); } +static int32_t translateShowVirtualTableValidate(STranslateContext* pCxt, SShowValidateVirtualTable* pStmt) { + // 1. Get table configuration from cache + SName name = {.type = TSDB_TABLE_NAME_T, .acctId = pCxt->pParseCxt->acctId}; + tstrncpy(name.dbname, pStmt->dbName, TSDB_DB_NAME_LEN); + tstrncpy(name.tname, pStmt->tableName, TSDB_TABLE_NAME_LEN); + + int32_t code = getTableCfgFromCache(pCxt->pMetaCache, &name, (STableCfg**)&pStmt->pTableCfg); + if (TSDB_CODE_SUCCESS != code) { + return code; + } + + // 2. Validate table type: must be a virtual table + STableCfg* pTableCfg = (STableCfg*)pStmt->pTableCfg; + bool isVtb = (pTableCfg->tableType == TSDB_VIRTUAL_CHILD_TABLE || pTableCfg->tableType == TSDB_VIRTUAL_NORMAL_TABLE || + pTableCfg->virtualStb); + + if (!isVtb) { + code = generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_TABLE_TYPE, "'%s' is not a virtual table", + pStmt->tableName); + } + + return code; +} + static int32_t translateShowCreateView(STranslateContext* pCxt, SShowCreateViewStmt* pStmt) { #ifndef TD_ENTERPRISE return TSDB_CODE_OPS_NOT_SUPPORT; #else int32_t code = 0, lino = 0; - SName name = {0}; + SName name = {0}; toName(pCxt->pParseCxt->acctId, pStmt->dbName, pStmt->viewName, &name); TAOS_CHECK_EXIT(getViewMetaFromMetaCache(pCxt, &name, (SViewMeta**)&pStmt->pViewMeta)); if (!pStmt->hasPrivilege) { @@ -20531,6 +20559,9 @@ static int32_t translateQuery(STranslateContext* pCxt, SNode* pNode) { case QUERY_NODE_SHOW_CREATE_RSMA_STMT: code = translateShowCreateRsma(pCxt, (SShowCreateRsmaStmt*)pNode); break; + case QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT: + code = translateShowVirtualTableValidate(pCxt, (SShowValidateVirtualTable*)pNode); + break; case QUERY_NODE_RESTORE_DNODE_STMT: case QUERY_NODE_RESTORE_QNODE_STMT: case QUERY_NODE_RESTORE_MNODE_STMT: @@ -20679,14 +20710,14 @@ void updateContextFromSubQ(STranslateContext* pCxt, SNode* pNode, STranslateCont return; } - static int32_t mergeTranslateContextMetas(STranslateContext* pCxt, STranslateContext* pSrc) { int32_t code = TSDB_CODE_SUCCESS; if (NULL != pSrc->pDbs) { SFullDatabaseName* pDb = taosHashIterate(pSrc->pDbs, NULL); while (NULL != pDb) { - code = taosHashPut(pCxt->pDbs, pDb->fullDbName, strlen(pDb->fullDbName), pDb->fullDbName, sizeof(pDb->fullDbName)); + code = + taosHashPut(pCxt->pDbs, pDb->fullDbName, strlen(pDb->fullDbName), pDb->fullDbName, sizeof(pDb->fullDbName)); if (code && code != TSDB_CODE_DUP_KEY) { taosHashCancelIterate(pSrc->pDbs, pDb); return code; @@ -20735,7 +20766,7 @@ static int32_t mergeTranslateContextMetas(STranslateContext* pCxt, STranslateCon } static int32_t translateTableSubquery(STranslateContext* pCxt, SNode* pNode) { - int32_t code = TSDB_CODE_SUCCESS; + int32_t code = TSDB_CODE_SUCCESS; ESqlClause currClause = pCxt->currClause; SNode* pCurrStmt = pCxt->pCurrStmt; @@ -20752,7 +20783,7 @@ static int32_t translateTableSubquery(STranslateContext* pCxt, SNode* pNode) { } static int32_t translateExprSubqueryImpl(STranslateContext* pCxt, SNode* pNode) { - int32_t code = TSDB_CODE_SUCCESS; + int32_t code = TSDB_CODE_SUCCESS; STranslateContext cxt = {0}; cxt.isExprSubQ = true; @@ -20782,12 +20813,12 @@ static int32_t translateExprSubqueryImpl(STranslateContext* pCxt, SNode* pNode) parserError("Correlated subQuery not supported now"); code = TSDB_CODE_PAR_INVALID_EXPR_SUBQ; } -/* - if (pCxt->hasLocalSubQ) { - parserError("Only query with FROM supported now"); - code = TSDB_CODE_PAR_INVALID_EXPR_SUBQ; - } -*/ + /* + if (pCxt->hasLocalSubQ) { + parserError("Only query with FROM supported now"); + code = TSDB_CODE_PAR_INVALID_EXPR_SUBQ; + } + */ return code; } @@ -21928,6 +21959,99 @@ static int32_t rewriteShowVnodes(STranslateContext* pCxt, SQuery* pQuery) { return code; } +static int32_t validateShowValidateMeta(STranslateContext* pCxt, SShowValidateVirtualTable* pStmt) { + int32_t code = 0; + SName name = {0}; + STableMeta* pTableMeta = NULL; + + toName(pCxt->pParseCxt->acctId, pStmt->dbName, pStmt->tableName, &name); + code = getTargetMeta(pCxt, &name, &pTableMeta, false); + if (TSDB_CODE_SUCCESS != code) { + code = generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_GET_META_ERROR, tstrerror(code)); + goto _exit; + } + // Note: SHOW VTABLE VALIDATE should accept any virtual table type (normal, child, or super) + int8_t tableType = pTableMeta->tableType; + bool isVtb = + (tableType == TSDB_VIRTUAL_CHILD_TABLE || tableType == TSDB_VIRTUAL_NORMAL_TABLE || pTableMeta->virtualStb == 1); + + if (!isVtb) { + code = generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_TABLE_TYPE, "'%s' is not a virtual table", + pStmt->tableName); + } + pStmt->superTable = (tableType == TSDB_VIRTUAL_CHILD_TABLE || tableType == TSDB_VIRTUAL_NORMAL_TABLE) ? 0 : 1; + +_exit: + taosMemoryFreeClear(pTableMeta); + return code; +} +static int32_t rewriteShowValidateVtable(STranslateContext* pCxt, SQuery* pQuery) { + SShowValidateVirtualTable* pShow = (SShowValidateVirtualTable*)(pQuery->pRoot); + SSelectStmt* pSelect = NULL; + int32_t code = 0; + SNode* pWhere = NULL; + code = validateShowValidateMeta(pCxt, pShow); + if (TSDB_CODE_SUCCESS != code) { + return code; + } + + code = createSelectStmtForShow(nodeType(pShow), &pSelect); + if (TSDB_CODE_SUCCESS != code) { + nodesDestroyNode((SNode*)pSelect); + return code; + } + + SNode* pDbCond = NULL; + SNode* pTableNameCond = NULL; + SValueNode* pDbValue = NULL; + SValueNode* pTableNameValue = NULL; + + if (strlen(pShow->dbName) > 0) { + PAR_ERR_JRET(nodesMakeValueNodeFromString(pShow->dbName, &pDbValue)); + PAR_ERR_JRET(createOperatorNode(OP_TYPE_EQUAL, "virtual_db_name", (SNode*)pDbValue, &pDbCond)); + } + + if (strlen(pShow->tableName) > 0) { + PAR_ERR_JRET(nodesMakeValueNodeFromString(pShow->tableName, &pTableNameValue)); + if (pShow->superTable) { + PAR_ERR_JRET(createOperatorNode(OP_TYPE_EQUAL, "virtual_stable_name", (SNode*)pTableNameValue, &pTableNameCond)); + } else { + PAR_ERR_JRET(createOperatorNode(OP_TYPE_EQUAL, "virtual_table_name", (SNode*)pTableNameValue, &pTableNameCond)); + } + } + + if (pDbCond && pTableNameCond) { + PAR_ERR_JRET(createLogicCondNode(&pDbCond, &pTableNameCond, &pWhere, LOGIC_COND_TYPE_AND)); + } else if (pDbCond) { + pWhere = pDbCond; + pDbCond = NULL; + } else { + pWhere = pTableNameCond; + pTableNameCond = NULL; + } + + if (pWhere) { + PAR_ERR_JRET(insertCondIntoSelectStmt(pSelect, &pWhere)); + } + + pCxt->showRewrite = true; + pQuery->showRewrite = true; + + nodesDestroyNode(pQuery->pRoot); + pQuery->pRoot = (SNode*)pSelect; + +_return: + nodesDestroyNode((SNode*)pDbValue); + nodesDestroyNode((SNode*)pTableNameValue); + nodesDestroyNode(pDbCond); + nodesDestroyNode(pTableNameCond); + nodesDestroyNode(pWhere); + if (TSDB_CODE_SUCCESS != code && pSelect) { + nodesDestroyNode((SNode*)pSelect); + } + return code; +} + static int32_t createBlockDistInfoFunc(SFunctionNode** ppNode) { SFunctionNode* pFunc = NULL; int32_t code = nodesMakeNode(QUERY_NODE_FUNCTION, (SNode**)&pFunc); @@ -22069,9 +22193,9 @@ static int32_t buildVirtualTableBatchReq(STranslateContext* pCxt, const SCreateV SSchema* pSchema = req.ntb.schemaRow.pSchema + index; toSchema(pColDef, index + 1, pSchema); if (pColDef->pOptions && ((SColumnOptions*)pColDef->pOptions)->hasRef) { - PAR_ERR_JRET(setColRef(&req.colRef.pColRef[index], index + 1, NULL, ((SColumnOptions*)pColDef->pOptions)->refColumn, - ((SColumnOptions*)pColDef->pOptions)->refTable, - ((SColumnOptions*)pColDef->pOptions)->refDb)); + PAR_ERR_JRET( + setColRef(&req.colRef.pColRef[index], index + 1, NULL, ((SColumnOptions*)pColDef->pOptions)->refColumn, + ((SColumnOptions*)pColDef->pOptions)->refTable, ((SColumnOptions*)pColDef->pOptions)->refDb)); } ++index; } @@ -22090,9 +22214,20 @@ static int32_t buildVirtualTableBatchReq(STranslateContext* pCxt, const SCreateV return code; } +static col_id_t getTagSchemaIndex(const STableMeta* pTableMeta, const char* pTagName) { + int32_t numOfTags = getNumOfTags(pTableMeta); + SSchema* pTagsSchema = getTableTagSchema(pTableMeta); + for (int32_t i = 0; i < numOfTags; ++i) { + if (0 == strcmp(pTagName, pTagsSchema[i].name)) { + return (col_id_t)i; + } + } + return -1; +} + static int32_t buildVirtualSubTableBatchReq(const SCreateVSubTableStmt* pStmt, STableMeta* pStbMeta, SArray* tagName, uint8_t tagNum, const STag* pTag, const SVgroupInfo* pVgroupInfo, - SVgroupCreateTableBatch* pBatch) { + SVgroupCreateTableBatch* pBatch, SNodeList* pTagRefNodes) { int32_t code = TSDB_CODE_SUCCESS; SVCreateTbReq req = {0}; SNode* pCol; @@ -22124,22 +22259,41 @@ static int32_t buildVirtualSubTableBatchReq(const SCreateVSubTableStmt* pStmt, S PAR_ERR_JRET(TSDB_CODE_PAR_INVALID_COLUMN); } const SSchema* pSchema = getTableColumnSchema(pStbMeta) + schemaIdx; - PAR_ERR_JRET(setColRef(&req.colRef.pColRef[schemaIdx], pSchema->colId, pSchema->name, pColRef->refColName, pColRef->refTableName, - pColRef->refDbName)); + PAR_ERR_JRET(setColRef(&req.colRef.pColRef[schemaIdx], pSchema->colId, pSchema->name, pColRef->refColName, + pColRef->refTableName, pColRef->refDbName)); } } else if (pStmt->pColRefs) { col_id_t index = 1; // start from second column, don't set column ref for ts column FOREACH(pCol, pStmt->pColRefs) { SColumnRefNode* pColRef = (SColumnRefNode*)pCol; - const SSchema* pSchema = getTableColumnSchema(pStbMeta) + index; - PAR_ERR_JRET(setColRef(&req.colRef.pColRef[index], index + 1, pSchema->name, pColRef->refColName, pColRef->refTableName, - pColRef->refDbName)); + const SSchema* pSchema = getTableColumnSchema(pStbMeta) + index; + PAR_ERR_JRET(setColRef(&req.colRef.pColRef[index], index + 1, pSchema->name, pColRef->refColName, + pColRef->refTableName, pColRef->refDbName)); index++; } } else { // no column reference. } + // Handle tag references + if (pTagRefNodes && LIST_LENGTH(pTagRefNodes) > 0) { + int32_t numOfTags = getNumOfTags(pStbMeta); + SSchema* pTagsSchema = getTableTagSchema(pStbMeta); + PAR_ERR_JRET(tInitDefaultSColRefWrapperByTags(&req.colRef, numOfTags, pTagsSchema[0].colId)); + + SNode* pRefNode = NULL; + FOREACH(pRefNode, pTagRefNodes) { + SColumnRefNode* pTagRef = (SColumnRefNode*)pRefNode; + col_id_t tagIdx = getTagSchemaIndex(pStbMeta, pTagRef->colName); + if (tagIdx == -1) { + PAR_ERR_JRET(TSDB_CODE_PAR_INVALID_TAG_NAME); + } + const SSchema* pSchema = pTagsSchema + tagIdx; + PAR_ERR_JRET(setColRef(&req.colRef.pTagRef[tagIdx], pSchema->colId, pSchema->name, pTagRef->refColName, + pTagRef->refTableName, pTagRef->refDbName)); + } + } + pBatch->info = *pVgroupInfo; (void)strcpy(pBatch->dbName, pStmt->dbName); pBatch->req.pArray = taosArrayInit(1, sizeof(struct SVCreateTbReq)); @@ -22154,8 +22308,8 @@ static int32_t buildVirtualSubTableBatchReq(const SCreateVSubTableStmt* pStmt, S return code; } -static int32_t buildNormalTableBatchReq(STranslateContext* pCxt, const SCreateTableStmt* pStmt, const SVgroupInfo* pVgroupInfo, - SVgroupCreateTableBatch* pBatch) { +static int32_t buildNormalTableBatchReq(STranslateContext* pCxt, const SCreateTableStmt* pStmt, + const SVgroupInfo* pVgroupInfo, SVgroupCreateTableBatch* pBatch) { SVCreateTbReq req = {0}; req.type = TD_NORMAL_TABLE; req.name = taosStrdup(pStmt->tableName); @@ -23836,41 +23990,94 @@ static int32_t buildUpdateMultiTagValReq(STranslateContext* pCxt, SAlterTableStm return code; } -static int32_t checkColRef(STranslateContext* pCxt, char* colName, char* pRefDbName, char* pRefTableName, char* pRefColName, - SDataType type, int8_t precision) { +static int32_t checkColRef(STranslateContext* pCxt, char* colName, char* pRefDbName, char* pRefTableName, + char* pRefColName, SDataType type, int8_t precision) { STableMeta* pRefTableMeta = NULL; int32_t code = TSDB_CODE_SUCCESS; PAR_ERR_JRET(getTableMeta(pCxt, pRefDbName, pRefTableName, &pRefTableMeta)); if (pRefTableMeta->tableInfo.precision != precision) { - PAR_ERR_JRET(generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN_TYPE, "timestamp precision of virtual table and its reference table do not match")); + PAR_ERR_JRET(generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN_TYPE, + "timestamp precision of virtual table and its reference table do not match")); } // org table cannot has composite primary key if (pRefTableMeta->tableInfo.numOfColumns > 1 && pRefTableMeta->schema[1].flags & COL_IS_KEY) { - PAR_ERR_JRET(generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN, - "virtual table's column:\"%s\"'s reference can not from table with composite key", - colName)); + PAR_ERR_JRET(generateSyntaxErrMsgExt( + &pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN, + "virtual table's column:\"%s\"'s reference can not from table with composite key", colName)); } // org table must be child table or normal table if (pRefTableMeta->tableType != TSDB_NORMAL_TABLE && pRefTableMeta->tableType != TSDB_CHILD_TABLE) { - PAR_ERR_JRET(generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN, - "virtual table's column:\"%s\"'s reference can only be normal table or child table", - colName)); + PAR_ERR_JRET(generateSyntaxErrMsgExt( + &pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN, + "virtual table's column:\"%s\"'s reference can only be normal table or child table", colName)); } const SSchema* pRefCol = getNormalColSchema(pRefTableMeta, pRefColName); if (NULL == pRefCol) { PAR_ERR_JRET(generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN, - "virtual table's column:\"%s\"'s reference column:\"%s\" not exist", - colName, pRefColName)); + "virtual table's column:\"%s\"'s reference column:\"%s\" not exist", colName, + pRefColName)); + } + + if (pRefCol->type != type.type) { + PAR_ERR_JRET(generateSyntaxErrMsgExt( + &pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN_TYPE, + "virtual table's column:\"%s\"'s type and reference column:\"%s\"'s type not match", colName, pRefColName)); + } + + // For variable-length types (VARCHAR, NCHAR, etc.), allow different lengths + // Virtual table can have different length than source table + if (!IS_VAR_DATA_TYPE(pRefCol->type) && pRefCol->bytes != type.bytes) { + PAR_ERR_JRET(generateSyntaxErrMsgExt( + &pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN_TYPE, + "virtual table's column:\"%s\"'s type and reference column:\"%s\"'s type not match", colName, pRefColName)); + } + +_return: + taosMemoryFreeClear(pRefTableMeta); + return code; +} + +static int32_t checkTagRef(STranslateContext* pCxt, char* tagName, char* pRefDbName, char* pRefTableName, + char* pRefColName, SDataType type) { + STableMeta* pRefTableMeta = NULL; + int32_t code = TSDB_CODE_SUCCESS; + + PAR_ERR_JRET(getTableMeta(pCxt, pRefDbName, pRefTableName, &pRefTableMeta)); + + // referenced table must be child table (which has tags) + if (pRefTableMeta->tableType != TSDB_CHILD_TABLE) { + PAR_ERR_JRET(generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN, + "virtual table's tag:\"%s\"'s reference can only be child table", tagName)); + } + + const SSchema* pRefTag = getTagSchema(pRefTableMeta, pRefColName); + if (NULL == pRefTag) { + const SSchema* pRefCol = getNormalColSchema(pRefTableMeta, pRefColName); + if (pRefCol != NULL) { + PAR_ERR_JRET(generateSyntaxErrMsgExt( + &pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN, + "virtual table's tag:\"%s\" references column:\"%s\" which is not a tag column", tagName, pRefColName)); + } else { + PAR_ERR_JRET(generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN, + "virtual table's tag:\"%s\" references non-existent tag:\"%s\"", tagName, + pRefColName)); + } } - if (pRefCol->type != type.type || pRefCol->bytes != type.bytes) { + if (pRefTag->type != type.type) { PAR_ERR_JRET(generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN_TYPE, - "virtual table's column:\"%s\"'s type and reference column:\"%s\"'s type not match", - colName, pRefColName)); + "virtual table's tag:\"%s\"'s type and reference tag:\"%s\"'s type not match", + tagName, pRefColName)); + } + + if (!IS_VAR_DATA_TYPE(pRefTag->type) && pRefTag->bytes != type.bytes) { + PAR_ERR_JRET(generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_REF_COLUMN_TYPE, + "virtual table's tag:\"%s\"'s type and reference tag:\"%s\"'s type not match", + tagName, pRefColName)); } _return: @@ -24014,7 +24221,10 @@ static int32_t buildUpdateColReq(STranslateContext* pCxt, SAlterTableStmt* pStmt return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_VAR_COLUMN_LEN); } - int32_t maxBytesPerRow = (TSDB_VIRTUAL_NORMAL_TABLE == pTableMeta->tableType || TSDB_VIRTUAL_CHILD_TABLE == pTableMeta->tableType) ? TSDB_MAX_BYTES_PER_ROW_VIRTUAL : TSDB_MAX_BYTES_PER_ROW; + int32_t maxBytesPerRow = + (TSDB_VIRTUAL_NORMAL_TABLE == pTableMeta->tableType || TSDB_VIRTUAL_CHILD_TABLE == pTableMeta->tableType) + ? TSDB_MAX_BYTES_PER_ROW_VIRTUAL + : TSDB_MAX_BYTES_PER_ROW; if (pTableMeta->tableInfo.rowSize + pReq->colModBytes - pSchema->bytes > maxBytesPerRow) { return generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_ROW_LENGTH, maxBytesPerRow); } @@ -24300,9 +24510,8 @@ static int32_t rewriteAlterTableImpl(STranslateContext* pCxt, SAlterTableStmt* p } if (pTableMeta->isAudit) { - return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_TSC_INVALID_OPERATION, - "Cannot alter audit table `%s`.`%s`", pStmt->dbName, - pStmt->tableName); + return generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_TSC_INVALID_OPERATION, "Cannot alter audit table `%s`.`%s`", + pStmt->dbName, pStmt->tableName); } if (TSDB_SUPER_TABLE == pTableMeta->tableType) { @@ -24377,10 +24586,10 @@ static int32_t buildCreateVTableDataBlock(STranslateContext* pCxt, const SCreate static int32_t buildCreateVSubTableDataBlock(const SCreateVSubTableStmt* pStmt, const SVgroupInfo* pInfo, SArray* pBufArray, STableMeta* pStbMeta, SArray* tagName, uint8_t tagNum, - const STag* pTag) { + const STag* pTag, SNodeList* pTagRefNodes) { SVgroupCreateTableBatch tbatch = {0}; int32_t code = TSDB_CODE_SUCCESS; - PAR_ERR_JRET(buildVirtualSubTableBatchReq(pStmt, pStbMeta, tagName, tagNum, pTag, pInfo, &tbatch)); + PAR_ERR_JRET(buildVirtualSubTableBatchReq(pStmt, pStbMeta, tagName, tagNum, pTag, pInfo, &tbatch, pTagRefNodes)); PAR_ERR_JRET(serializeVgroupCreateTableBatch(&tbatch, pBufArray)); _return: @@ -24437,7 +24646,6 @@ static int32_t rewriteCreateVirtualTable(STranslateContext* pCxt, SQuery* pQuery toName(pCxt->pParseCxt->acctId, pStmt->dbName, pStmt->tableName, &name); - FOREACH(pNode, pStmt->pCols) { SColumnDefNode* pColNode = (SColumnDefNode*)pNode; SColumnOptions* pColOptions = (SColumnOptions*)pColNode->pOptions; @@ -24448,10 +24656,9 @@ static int32_t rewriteCreateVirtualTable(STranslateContext* pCxt, SQuery* pQuery if (IS_DECIMAL_TYPE(pColNode->dataType.type)) { PAR_ERR_JRET(generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_VTABLE_NOT_SUPPORT_DATA_TYPE)); } - PAR_ERR_JRET( - checkColRef(pCxt, pColNode->colName, pColOptions->refDb, pColOptions->refTable, pColOptions->refColumn, - (SDataType){.type = pColNode->dataType.type, .bytes = calcTypeBytes(pColNode->dataType)}, - dbCfg.precision)); + PAR_ERR_JRET(checkColRef( + pCxt, pColNode->colName, pColOptions->refDb, pColOptions->refTable, pColOptions->refColumn, + (SDataType){.type = pColNode->dataType.type, .bytes = calcTypeBytes(pColNode->dataType)}, dbCfg.precision)); } index++; } @@ -24468,6 +24675,106 @@ static int32_t rewriteCreateVirtualTable(STranslateContext* pCxt, SQuery* pQuery return code; } +// Check if pValsOfTags contains any tag references (SColumnRefNode), validate them, +// and replace them with NULL value nodes so buildKVRowForBindTags/buildKVRowForAllTags can proceed. +// Returns the tag reference info in pTagRefNodes (caller should destroy). +static int32_t checkAndReplaceTagRefs(STranslateContext* pCxt, SNodeList* pSpecificTags, SNodeList* pValsOfTags, + STableMeta* pSuperTableMeta, SNodeList** ppTagRefNodes) { + int32_t code = TSDB_CODE_SUCCESS; + SNodeList* pTagRefNodes = NULL; + bool hasTagRef = false; + + // First pass: check if there are any tag references + SNode* pNode = NULL; + FOREACH(pNode, pValsOfTags) { + if (nodeType(pNode) == QUERY_NODE_COLUMN_REF) { + hasTagRef = true; + break; + } + } + + if (!hasTagRef) { + *ppTagRefNodes = NULL; + return TSDB_CODE_SUCCESS; + } + + // Second pass: validate tag references and replace with NULL value nodes + SSchema* pTagSchema = getTableTagSchema(pSuperTableMeta); + int32_t tagIdx = 0; + SNode* pTagNode = NULL; + + SListCell* pCell = pValsOfTags->pHead; + while (pCell != NULL) { + pNode = pCell->pNode; + if (nodeType(pNode) == QUERY_NODE_COLUMN_REF) { + SColumnRefNode* pColRef = (SColumnRefNode*)pNode; + + // Determine the tag schema for this position + const SSchema* pSchema = NULL; + if (pColRef->colName[0] != '\0') { + // New syntax: tag name is already specified in colName (e.g., tag1 FROM db.table.tag) + pSchema = getTagSchema(pSuperTableMeta, pColRef->colName); + } else if (pSpecificTags) { + // Legacy syntax with specific tag names: get the corresponding tag name from pSpecificTags + SNode* pSpecTag = nodesListGetNode(pSpecificTags, tagIdx); + if (pSpecTag) { + pSchema = getTagSchema(pSuperTableMeta, ((SColumnNode*)pSpecTag)->colName); + } + } else { + // Legacy syntax positional: use tagIdx + if (tagIdx < getNumOfTags(pSuperTableMeta)) { + pSchema = pTagSchema + tagIdx; + } + } + + if (NULL == pSchema) { + PAR_ERR_JRET(generateSyntaxErrMsgExt(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_TAG_NAME, + "tag reference at position %d: tag not found", tagIdx)); + } + + // Validate the tag reference + PAR_ERR_JRET(checkTagRef(pCxt, (char*)pSchema->name, pColRef->refDbName, pColRef->refTableName, + pColRef->refColName, (SDataType){.type = pSchema->type, .bytes = pSchema->bytes})); + + // Store the tag reference info (with tag name filled in) + if (NULL == pTagRefNodes) { + PAR_ERR_JRET(nodesMakeList(&pTagRefNodes)); + } + // Clone the column ref node and set colName to the tag name + SColumnRefNode* pClone = NULL; + PAR_ERR_JRET(nodesMakeNode(QUERY_NODE_COLUMN_REF, (SNode**)&pClone)); + tstrncpy(pClone->colName, pSchema->name, TSDB_COL_NAME_LEN); + tstrncpy(pClone->refDbName, pColRef->refDbName, TSDB_DB_NAME_LEN); + tstrncpy(pClone->refTableName, pColRef->refTableName, TSDB_TABLE_NAME_LEN); + tstrncpy(pClone->refColName, pColRef->refColName, TSDB_COL_NAME_LEN); + PAR_ERR_JRET(nodesListAppend(pTagRefNodes, (SNode*)pClone)); + + // Replace the column ref node with a NULL value node + SValueNode* pNull = NULL; + PAR_ERR_JRET(nodesMakeNode(QUERY_NODE_VALUE, (SNode**)&pNull)); + pNull->literal = taosStrdup("NULL"); + if (NULL == pNull->literal) { + nodesDestroyNode((SNode*)pNull); + PAR_ERR_JRET(terrno); + } + pNull->node.resType.type = TSDB_DATA_TYPE_NULL; + + // Replace in list + nodesDestroyNode(pCell->pNode); + pCell->pNode = (SNode*)pNull; + } + tagIdx++; + pCell = pCell->pNext; + } + + *ppTagRefNodes = pTagRefNodes; + return code; + +_return: + nodesDestroyList(pTagRefNodes); + return code; +} + static int32_t rewriteCreateVirtualSubTable(STranslateContext* pCxt, SQuery* pQuery) { int32_t code = TSDB_CODE_SUCCESS; SCreateVSubTableStmt* pStmt = (SCreateVSubTableStmt*)pQuery->pRoot; @@ -24478,6 +24785,7 @@ static int32_t rewriteCreateVirtualSubTable(STranslateContext* pCxt, SQuery* pQu STag* pTag = NULL; SArray* tagName = NULL; SNode* pCol = NULL; + SNodeList* pTagRefNodes = NULL; PAR_ERR_JRET(checkCreateVSubTable(pCxt, pStmt)); @@ -24487,7 +24795,7 @@ static int32_t rewriteCreateVirtualSubTable(STranslateContext* pCxt, SQuery* pQu PAR_ERR_JRET(terrno); } - PAR_ERR_JRET(getTableMeta(pCxt, pStmt->useDbName, pStmt->useTableName, &pSuperTableMeta)); + PAR_ERR_JRET(refreshGetTableMeta(pCxt, pStmt->useDbName, pStmt->useTableName, &pSuperTableMeta)); if (!pSuperTableMeta->virtualStb) { PAR_ERR_JRET(TSDB_CODE_VTABLE_NOT_VIRTUAL_SUPER_TABLE); @@ -24526,6 +24834,11 @@ static int32_t rewriteCreateVirtualSubTable(STranslateContext* pCxt, SQuery* pQu PAR_ERR_JRET(getTableHashVgroupImpl(pCxt, &name, &info)); PAR_ERR_JRET(collectUseTable(&name, pCxt->pTargetTables)); + // Check and extract tag references from pValsOfTags, replacing them with NULL values. + // Supports both legacy syntax (FROM db.table.tag) and new unified syntax + // (tag_name FROM db.table.tag, or db.table.tag positional). + PAR_ERR_JRET(checkAndReplaceTagRefs(pCxt, pStmt->pSpecificTags, pStmt->pValsOfTags, pSuperTableMeta, &pTagRefNodes)); + if (NULL != pStmt->pSpecificTags) { PAR_ERR_JRET( buildKVRowForBindTags(pCxt, pStmt->pSpecificTags, pStmt->pValsOfTags, pSuperTableMeta, &pTag, tagName)); @@ -24534,13 +24847,15 @@ static int32_t rewriteCreateVirtualSubTable(STranslateContext* pCxt, SQuery* pQu } PAR_ERR_JRET(buildCreateVSubTableDataBlock(pStmt, &info, pBufArray, pSuperTableMeta, tagName, - taosArrayGetSize(tagName), pTag)); + taosArrayGetSize(tagName), pTag, pTagRefNodes)); PAR_ERR_JRET(rewriteToVnodeModifyOpStmt(pQuery, pBufArray)); + nodesDestroyList(pTagRefNodes); taosMemoryFreeClear(pSuperTableMeta); taosArrayDestroy(tagName); return code; _return: + nodesDestroyList(pTagRefNodes); destroyCreateTbReqArray(pBufArray); taosArrayDestroy(tagName); taosMemoryFreeClear(pSuperTableMeta); @@ -25316,6 +25631,9 @@ static int32_t rewriteQuery(STranslateContext* pCxt, SQuery* pQuery) { case QUERY_NODE_SHOW_VNODES_STMT: code = rewriteShowVnodes(pCxt, pQuery); break; + case QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT: + code = rewriteShowValidateVtable(pCxt, pQuery); + break; case QUERY_NODE_SHOW_TABLE_DISTRIBUTED_STMT: code = rewriteShowTableDist(pCxt, pQuery); break; diff --git a/source/libs/parser/src/parUtil.c b/source/libs/parser/src/parUtil.c index 837296efb854..3d6220dbe84c 100644 --- a/source/libs/parser/src/parUtil.c +++ b/source/libs/parser/src/parUtil.c @@ -640,7 +640,9 @@ static int32_t getInsTagsTableTargetNameFromOp(int32_t acctId, SOperatorNode* pO const char* valueStr = NULL; int32_t valueLen = 0; - if ((0 == strcmp(pCol->colName, "db_name") || 0 == strcmp(pCol->colName, "table_name")) && pVal->placeholderNo != 0) { + if (((0 == strcmp(pCol->colName, "db_name") || 0 == strcmp(pCol->colName, "table_name")) || + 0 == strcmp(pCol->colName, "virtual_db_name") || 0 == strcmp(pCol->colName, "virtual_table_name")) && + pVal->placeholderNo != 0) { if (NULL == pVal->datum.p) { qError("getInsTagsTableTargetNameFromOp: placeholderNo=%d but datum.p is NULL, colName=%s, literal=%s", pVal->placeholderNo, pCol->colName, pVal->literal ? pVal->literal : "NULL"); @@ -684,6 +686,10 @@ static int32_t getInsTagsTableTargetNameFromOp(int32_t acctId, SOperatorNode* pO code = tNameSetDbName(pName, acctId, valueStr, valueLen); } else if (0 == strcmp(pCol->colName, "table_name")) { code = tNameAddTbName(pName, valueStr, valueLen); + } else if (0 == strcmp(pCol->colName, "virtual_db_name")) { + code = tNameSetDbName(pName, acctId, valueStr, valueLen); + } else if (0 == strcmp(pCol->colName, "virtual_table_name")) { + code = tNameAddTbName(pName, valueStr, valueLen); } if (needFree) { diff --git a/source/libs/planner/src/planLogicCreater.c b/source/libs/planner/src/planLogicCreater.c index 4c003005c1fd..5827c2d3d00d 100644 --- a/source/libs/planner/src/planLogicCreater.c +++ b/source/libs/planner/src/planLogicCreater.c @@ -902,7 +902,8 @@ static int32_t findRefColId(SNode *pRefTable, const char *colName, col_id_t *col return TSDB_CODE_NOT_FOUND; } -static int32_t scanAddCol(SLogicNode* pLogicNode, SColRef* colRef, STableNode* pVirtualTableNode, const SSchema* pSchema, col_id_t colId) { +static int32_t scanAddCol(SLogicNode* pLogicNode, SColRef* colRef, STableNode* pVirtualTableNode, const SSchema* pSchema, + col_id_t colId, const SSchema* pRefSchema) { int32_t code = TSDB_CODE_SUCCESS; SColumnNode *pRefTableScanCol = NULL; SScanLogicNode *pLogicScan = (SScanLogicNode*)pLogicNode; @@ -941,7 +942,16 @@ static int32_t scanAddCol(SLogicNode* pLogicNode, SColRef* colRef, STableNode* p pRefTableScanCol->tableId = pLogicScan->tableId; pRefTableScanCol->tableType = pLogicScan->tableType; pRefTableScanCol->node.resType.type = pSchema->type; - pRefTableScanCol->node.resType.bytes = pSchema->bytes; + // For variable-length types (BINARY/NCHAR/VARCHAR), use the source table's bytes when available. + // This ensures the TSDB reader allocates enough buffer for source data that may be longer + // than the virtual table's defined column length. + if (pRefSchema && IS_VAR_DATA_TYPE(pSchema->type)) { + pRefTableScanCol->node.resType.bytes = TMAX(pSchema->bytes, pRefSchema->bytes); + planDebug("scanAddCol: col %s, vtb bytes=%d, ref bytes=%d, final bytes=%d", + pRefTableScanCol->colName, pSchema->bytes, pRefSchema->bytes, pRefTableScanCol->node.resType.bytes); + } else { + pRefTableScanCol->node.resType.bytes = pSchema->bytes; + } pRefTableScanCol->colType = COLUMN_TYPE_COLUMN; pRefTableScanCol->isPk = false; pRefTableScanCol->tableHasPk = false; @@ -957,12 +967,18 @@ static int32_t scanAddCol(SLogicNode* pLogicNode, SColRef* colRef, STableNode* p } static int32_t checkColRefType(const SSchema* vtbSchema, const SSchema* refSchema) { - if (vtbSchema->type != refSchema->type || vtbSchema->bytes != refSchema->bytes) { + if (vtbSchema->type != refSchema->type) { qError("virtual table column:%s type mismatch, virtual table column type:%d, bytes:%d, " "ref table column:%s, type:%d, bytes:%d", vtbSchema->name, vtbSchema->type, vtbSchema->bytes, refSchema->name, refSchema->type, refSchema->bytes); return TSDB_CODE_PAR_INVALID_REF_COLUMN_TYPE; } + if (!IS_VAR_DATA_TYPE(vtbSchema->type) && vtbSchema->bytes != refSchema->bytes) { + qError("virtual table column:%s bytes mismatch, virtual table column type:%d, bytes:%d, " + "ref table column:%s, type:%d, bytes:%d", + vtbSchema->name, vtbSchema->type, vtbSchema->bytes, refSchema->name, refSchema->type, refSchema->bytes); + return TSDB_CODE_PAR_INVALID_REF_COLUMN_TYPE; + } return TSDB_CODE_SUCCESS; } @@ -986,16 +1002,17 @@ static int32_t addSubScanNode(SLogicPlanContext* pCxt, SSelectStmt* pSelect, SVi strcat(tableNameKey, pColRef->refTableName); SLogicNode **ppRefScan = (SLogicNode **)taosHashGet(refTablesMap, &tableNameKey, strlen(tableNameKey)); + const SSchema* pRefColSchema = &((SRealTableNode*)pRefTable)->pMeta->schema[colIdx]; if (NULL == ppRefScan) { PLAN_ERR_JRET(createRefScanLogicNode(pCxt, pSelect, (SRealTableNode*)pRefTable, &pRefScan)); - PLAN_ERR_JRET(checkColRefType(&pVirtualTable->pMeta->schema[schemaIndex], &((SRealTableNode*)pRefTable)->pMeta->schema[colIdx])); - PLAN_ERR_JRET(scanAddCol(pRefScan, pColRef, &pVirtualTable->table, &pVirtualTable->pMeta->schema[schemaIndex], colId)); + PLAN_ERR_JRET(checkColRefType(&pVirtualTable->pMeta->schema[schemaIndex], pRefColSchema)); + PLAN_ERR_JRET(scanAddCol(pRefScan, pColRef, &pVirtualTable->table, &pVirtualTable->pMeta->schema[schemaIndex], colId, pRefColSchema)); PLAN_ERR_JRET(taosHashPut(refTablesMap, &tableNameKey, strlen(tableNameKey), &pRefScan, POINTER_BYTES)); put = true; } else { pRefScan = *ppRefScan; - PLAN_ERR_JRET(checkColRefType(&pVirtualTable->pMeta->schema[schemaIndex], &((SRealTableNode*)pRefTable)->pMeta->schema[colIdx])); - PLAN_ERR_JRET(scanAddCol(pRefScan, pColRef, &pVirtualTable->table, &pVirtualTable->pMeta->schema[schemaIndex], colId)); + PLAN_ERR_JRET(checkColRefType(&pVirtualTable->pMeta->schema[schemaIndex], pRefColSchema)); + PLAN_ERR_JRET(scanAddCol(pRefScan, pColRef, &pVirtualTable->table, &pVirtualTable->pMeta->schema[schemaIndex], colId, pRefColSchema)); } nodesDestroyNode((SNode*)pRefTable); @@ -1174,7 +1191,7 @@ static int32_t createVirtualSuperTableLogicNode(SLogicPlanContext* pCxt, SSelect if (pVirtualTable->pMeta->schema[i].colId == PRIMARYKEY_TIMESTAMP_COL_ID) { continue; } else { - PLAN_ERR_JRET(scanAddCol(pRealTableScan, NULL, &pVirtualTable->table, &pVirtualTable->pMeta->schema[i], pVirtualTable->pMeta->schema[i].colId)); + PLAN_ERR_JRET(scanAddCol(pRealTableScan, NULL, &pVirtualTable->table, &pVirtualTable->pMeta->schema[i], pVirtualTable->pMeta->schema[i].colId, NULL)); } } PLAN_ERR_JRET(createColumnByRewriteExprs(((SScanLogicNode*)pRealTableScan)->pScanCols, &((SScanLogicNode*)pRealTableScan)->node.pTargets)); diff --git a/source/libs/planner/src/planPhysiCreater.c b/source/libs/planner/src/planPhysiCreater.c index 25543ff36c07..f327e4175981 100644 --- a/source/libs/planner/src/planPhysiCreater.c +++ b/source/libs/planner/src/planPhysiCreater.c @@ -993,7 +993,8 @@ static int32_t createSystemTableScanPhysiNode(SPhysiPlanContext* pCxt, SSubplan* 0 == strcmp(pScanLogicNode->tableName.tname, TSDB_INS_TABLE_COLS) || 0 == strcmp(pScanLogicNode->tableName.tname, TSDB_INS_DISK_USAGE) || 0 == strcmp(pScanLogicNode->tableName.tname, TSDB_INS_TABLE_FILESETS) || - 0 == strcmp(pScanLogicNode->tableName.tname, TSDB_INS_TABLE_VC_COLS)) { + 0 == strcmp(pScanLogicNode->tableName.tname, TSDB_INS_TABLE_VC_COLS) || + 0 == strcmp(pScanLogicNode->tableName.tname, TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING)) { if (pScanLogicNode->pVgroupList) { vgroupInfoToNodeAddr(pScanLogicNode->pVgroupList->vgroups, &pSubplan->execNode); } diff --git a/source/libs/qcom/src/queryUtil.c b/source/libs/qcom/src/queryUtil.c index 0637f06d193b..810cbbe3c524 100644 --- a/source/libs/qcom/src/queryUtil.c +++ b/source/libs/qcom/src/queryUtil.c @@ -628,13 +628,17 @@ int32_t cloneTableMeta(STableMeta* pSrc, STableMeta** pDst) { int32_t metaSize = sizeof(STableMeta) + numOfField * sizeof(SSchema); int32_t schemaExtSize = 0; int32_t colRefSize = 0; + int32_t tagRefSize = 0; if (withExtSchema(pSrc->tableType) && pSrc->schemaExt) { schemaExtSize = pSrc->tableInfo.numOfColumns * sizeof(SSchemaExt); } if (hasRefCol(pSrc->tableType) && pSrc->colRef) { colRefSize = pSrc->numOfColRefs * sizeof(SColRef); } - *pDst = taosMemoryMalloc(metaSize + schemaExtSize + colRefSize); + if (hasRefCol(pSrc->tableType) && pSrc->tagRef) { + tagRefSize = pSrc->numOfTagRefs * sizeof(SColRef); + } + *pDst = taosMemoryMalloc(metaSize + schemaExtSize + colRefSize + tagRefSize); if (NULL == *pDst) { return terrno; } @@ -651,6 +655,14 @@ int32_t cloneTableMeta(STableMeta* pSrc, STableMeta** pDst) { } else { (*pDst)->colRef = NULL; } + if (hasRefCol(pSrc->tableType) && pSrc->tagRef) { + (*pDst)->tagRef = (SColRef*)((char*)*pDst + metaSize + schemaExtSize + colRefSize); + memcpy((*pDst)->tagRef, pSrc->tagRef, tagRefSize); + (*pDst)->numOfTagRefs = pSrc->numOfTagRefs; + } else { + (*pDst)->tagRef = NULL; + (*pDst)->numOfTagRefs = 0; + } return TSDB_CODE_SUCCESS; } diff --git a/source/libs/qcom/src/querymsg.c b/source/libs/qcom/src/querymsg.c index 7e622b249824..c98ac4f11b21 100644 --- a/source/libs/qcom/src/querymsg.c +++ b/source/libs/qcom/src/querymsg.c @@ -671,10 +671,11 @@ int32_t queryCreateVCTableMetaFromMsg(STableMetaRsp *msg, SVCTableMeta **pMeta) QUERY_PARAM_CHECK(msg->pColRefs); int32_t pColRefSize = sizeof(SColRef) * msg->numOfColRefs; + int32_t pTagRefSize = (msg->pTagRefs && msg->numOfTagRefs > 0) ? sizeof(SColRef) * msg->numOfTagRefs : 0; - SVCTableMeta *pTableMeta = taosMemoryCalloc(1, sizeof(SVCTableMeta) + pColRefSize); + SVCTableMeta *pTableMeta = taosMemoryCalloc(1, sizeof(SVCTableMeta) + pColRefSize + pTagRefSize); if (NULL == pTableMeta) { - qError("calloc size[%d] failed", (int32_t)sizeof(SVCTableMeta) + pColRefSize); + qError("calloc size[%d] failed", (int32_t)sizeof(SVCTableMeta) + pColRefSize + pTagRefSize); return terrno; } @@ -688,6 +689,15 @@ int32_t queryCreateVCTableMetaFromMsg(STableMetaRsp *msg, SVCTableMeta **pMeta) pTableMeta->colRef = (SColRef *)((char *)pTableMeta + sizeof(SVCTableMeta)); memcpy(pTableMeta->colRef, msg->pColRefs, pColRefSize); + if (pTagRefSize > 0) { + pTableMeta->tagRef = (SColRef *)((char *)pTableMeta + sizeof(SVCTableMeta) + pColRefSize); + memcpy(pTableMeta->tagRef, msg->pTagRefs, pTagRefSize); + pTableMeta->numOfTagRefs = msg->numOfTagRefs; + } else { + pTableMeta->tagRef = NULL; + pTableMeta->numOfTagRefs = 0; + } + qDebug("ctable %s uid %" PRIx64 " meta returned, type %d vgId:%d db %s suid %" PRIx64, msg->tbName, (pTableMeta)->uid, (pTableMeta)->tableType, (pTableMeta)->vgId, msg->dbFName, (pTableMeta)->suid); @@ -702,14 +712,17 @@ int32_t queryCreateTableMetaFromMsg(STableMetaRsp *msg, bool isStb, STableMeta * int32_t metaSize = sizeof(STableMeta) + sizeof(SSchema) * total; int32_t schemaExtSize = (withExtSchema(msg->tableType) && msg->pSchemaExt) ? sizeof(SSchemaExt) * msg->numOfColumns : 0; int32_t pColRefSize = (hasRefCol(msg->tableType) && msg->pColRefs && !isStb) ? sizeof(SColRef) * msg->numOfColRefs : 0; + int32_t pTagRefSize = (hasRefCol(msg->tableType) && msg->pTagRefs && !isStb) ? sizeof(SColRef) * msg->numOfTagRefs : 0; - STableMeta *pTableMeta = taosMemoryCalloc(1, metaSize + schemaExtSize + pColRefSize); + int32_t sz = metaSize + schemaExtSize + pColRefSize + pTagRefSize; + STableMeta *pTableMeta = taosMemoryCalloc(1, sz); if (NULL == pTableMeta) { - qError("calloc size[%d] failed", metaSize); + qError("calloc size[%d] failed since %s", sz, tstrerror(terrno)); return terrno; } SSchemaExt *pSchemaExt = (SSchemaExt *)((char *)pTableMeta + metaSize); - SColRef *pColRef = (SColRef *)((char *)pTableMeta + metaSize + schemaExtSize); + // SColRef *pColRef = (SColRef *)((char *)pTableMeta + metaSize + schemaExtSize); + // SColRef *pTagRef = (SColRef *)((char *)pTableMeta + metaSize + schemaExtSize + pColRefSize); pTableMeta->vgId = isStb ? 0 : msg->vgId; pTableMeta->tableType = isStb ? TSDB_SUPER_TABLE : msg->tableType; @@ -752,6 +765,15 @@ int32_t queryCreateTableMetaFromMsg(STableMetaRsp *msg, bool isStb, STableMeta * pTableMeta->colRef = NULL; } + if (hasRefCol(msg->tableType) && msg->pTagRefs && !isStb) { + pTableMeta->tagRef = (SColRef *)((char *)pTableMeta + metaSize + schemaExtSize + pColRefSize); + memcpy(pTableMeta->tagRef, msg->pTagRefs, pTagRefSize); + pTableMeta->numOfTagRefs = msg->numOfTagRefs; + } else { + pTableMeta->tagRef = NULL; + pTableMeta->numOfTagRefs = 0; + } + bool hasPK = (msg->numOfColumns > 1) && (pTableMeta->schema[1].flags & COL_IS_KEY); for (int32_t i = 0; i < msg->numOfColumns; ++i) { pTableMeta->tableInfo.rowSize += pTableMeta->schema[i].bytes; diff --git a/source/libs/scalar/src/sclvector.c b/source/libs/scalar/src/sclvector.c index 7e2ceada84d4..0c2a268b0b23 100644 --- a/source/libs/scalar/src/sclvector.c +++ b/source/libs/scalar/src/sclvector.c @@ -41,8 +41,9 @@ #define IS_HELPER_NULL(col, i) colDataIsNull_s(col, i) || IS_JSON_NULL(col->info.type, colDataGetVarData(col, i)) bool noConvertBeforeCompare(int32_t leftType, int32_t rightType, int32_t optr) { - return (TSDB_DATA_TYPE_NULL == leftType || TSDB_DATA_TYPE_NULL == rightType) || (!IS_DECIMAL_TYPE(leftType) && !IS_DECIMAL_TYPE(rightType) && IS_NUMERIC_TYPE(leftType) && - IS_NUMERIC_TYPE(rightType) && (optr >= OP_TYPE_GREATER_THAN && optr <= OP_TYPE_NOT_EQUAL)); + return (TSDB_DATA_TYPE_NULL == leftType || TSDB_DATA_TYPE_NULL == rightType) || + (!IS_DECIMAL_TYPE(leftType) && !IS_DECIMAL_TYPE(rightType) && IS_NUMERIC_TYPE(leftType) && + IS_NUMERIC_TYPE(rightType) && (optr >= OP_TYPE_GREATER_THAN && optr <= OP_TYPE_NOT_EQUAL)); } bool compareForType(__compar_fn_t fp, int32_t optr, SColumnInfoData *pColL, int32_t idxL, SColumnInfoData *pColR, @@ -594,18 +595,26 @@ int32_t vectorConvertFromVarData(SSclVectorConvCtx *pCtx, int8_t *overflow) { } int32_t bufSize = pCtx->pIn->columnData->info.bytes; - if (tmp == NULL) { - tmp = taosMemoryMalloc(bufSize); + int32_t actualSize = vton ? varDataTLen(data) : (varDataLen(data) + 1); + + // Reallocate buffer if actual data size exceeds allocated buffer + if (tmp == NULL || actualSize > bufSize) { + if (tmp != NULL) { + taosMemoryFree(tmp); + } + tmp = taosMemoryMalloc(TMAX(bufSize, actualSize)); if (tmp == NULL) { sclError("out of memory in vectorConvertFromVarData"); SCL_ERR_JRET(terrno); } + bufSize = TMAX(bufSize, actualSize); } if (vton) { (void)memcpy(tmp, data, varDataTLen(data)); } else { - if (TSDB_DATA_TYPE_VARCHAR == convertType || TSDB_DATA_TYPE_GEOMETRY == convertType || TSDB_DATA_TYPE_VARBINARY == convertType) { + if (TSDB_DATA_TYPE_VARCHAR == convertType || TSDB_DATA_TYPE_GEOMETRY == convertType || + TSDB_DATA_TYPE_VARBINARY == convertType) { (void)memcpy(tmp, varDataVal(data), varDataLen(data)); tmp[varDataLen(data)] = 0; } else if (TSDB_DATA_TYPE_NCHAR == convertType) { @@ -852,8 +861,7 @@ void vectorConvertCheckOverflow(SColumnInfoData *pInputCol, int16_t inType, int1 int64_t maxValue = tDataTypes[outType].maxValue; double value = 0; - GET_TYPED_DATA(value, double, inType, colDataGetData(pInputCol, 0), - typeGetTypeModFromColInfo(&pInputCol->info)); + GET_TYPED_DATA(value, double, inType, colDataGetData(pInputCol, 0), typeGetTypeModFromColInfo(&pInputCol->info)); if (value > maxValue) { *overflow = 1; @@ -864,15 +872,14 @@ void vectorConvertCheckOverflow(SColumnInfoData *pInputCol, int16_t inType, int1 } return; - } + } if (IS_UNSIGNED_NUMERIC_TYPE(outType)) { uint64_t minValue = (uint64_t)tDataTypes[outType].minValue; uint64_t maxValue = (uint64_t)tDataTypes[outType].maxValue; double value = 0; - GET_TYPED_DATA(value, double, inType, colDataGetData(pInputCol, 0), - typeGetTypeModFromColInfo(&pInputCol->info)); + GET_TYPED_DATA(value, double, inType, colDataGetData(pInputCol, 0), typeGetTypeModFromColInfo(&pInputCol->info)); if (value > maxValue) { *overflow = 1; @@ -1130,28 +1137,28 @@ int32_t vectorConvertSingleColImpl(const SScalarParam *pIn, SScalarParam *pOut, int8_t gConvertTypes[TSDB_DATA_TYPE_MAX][TSDB_DATA_TYPE_MAX] = { /* NUL BOO TIN SMA INT BIG FLO DOU VAR TIM NCH UTI USM UIN UBI JSO VAR DEC BLO MED GEO DEC64*/ - /*NULL*/ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - /*BOOL*/ 0, 0, 2, 3, 4, 5, 6, 7, 5, 9, 5, 11, 12, 13, 14, 0, 5, 17, -1, -1, -1, 17, - /*TINY*/ 0, 0, 0, 3, 4, 5, 6, 7, 5, 9, 5, 3, 4, 5, 7, 0, 5, 17, -1, -1, -1, 17, - /*SMAL*/ 0, 0, 0, 0, 4, 5, 6, 7, 5, 9, 5, 3, 4, 5, 7, 0, 5, 17, -1, -1, -1, 17, - /*INT */ 0, 0, 0, 0, 0, 5, 6, 7, 5, 9, 5, 4, 4, 5, 7, 0, 5, 17, -1, -1, -1, 17, - /*BIGI*/ 0, 0, 0, 0, 0, 0, 6, 7, 5, 9, 5, 5, 5, 5, 7, 0, 5, 17, -1, -1, -1, 17, - /*FLOA*/ 0, 0, 0, 0, 0, 0, 0, 7, 6, 6, 6, 6, 6, 6, 6, 0, 6, 7, -1, -1, -1, 7, - /*DOUB*/ 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 7, 7, 7, 0, 7, 7, -1, -1, -1, 7, - /*VARC*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 7, 7, 7, 0, 16, 7, -1, -1, 20, 7, - /*TIME*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 7, 0, 9, 17, -1, -1, -1, 17, - /*NCHA*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 0, 16, 7, -1, -1, -1, 7, - /*UTIN*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 13, 14, 0, 14, 17, -1, -1, -1, 17, - /*USMA*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 14, 0, 14, 17, -1, -1, -1, 17, - /*UINT*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 14, 17, -1, -1, -1, 17, - /*UBIG*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 17, -1, -1, -1, 17, - /*JSON*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, -1, -1, -1, -1, -1, - /*VARB*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, - /*DECI*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, 17, - /*BLOB*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, - /*MEDB*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, - /*GEOM*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, - /*DEC64*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /*NULL*/ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + /*BOOL*/ 0, 0, 2, 3, 4, 5, 6, 7, 5, 9, 5, 11, 12, 13, 14, 0, 5, 17, -1, -1, -1, 17, + /*TINY*/ 0, 0, 0, 3, 4, 5, 6, 7, 5, 9, 5, 3, 4, 5, 7, 0, 5, 17, -1, -1, -1, 17, + /*SMAL*/ 0, 0, 0, 0, 4, 5, 6, 7, 5, 9, 5, 3, 4, 5, 7, 0, 5, 17, -1, -1, -1, 17, + /*INT */ 0, 0, 0, 0, 0, 5, 6, 7, 5, 9, 5, 4, 4, 5, 7, 0, 5, 17, -1, -1, -1, 17, + /*BIGI*/ 0, 0, 0, 0, 0, 0, 6, 7, 5, 9, 5, 5, 5, 5, 7, 0, 5, 17, -1, -1, -1, 17, + /*FLOA*/ 0, 0, 0, 0, 0, 0, 0, 7, 6, 6, 6, 6, 6, 6, 6, 0, 6, 7, -1, -1, -1, 7, + /*DOUB*/ 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 7, 7, 7, 0, 7, 7, -1, -1, -1, 7, + /*VARC*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 7, 7, 7, 0, 16, 7, -1, -1, 20, 7, + /*TIME*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 7, 0, 9, 17, -1, -1, -1, 17, + /*NCHA*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 0, 16, 7, -1, -1, -1, 7, + /*UTIN*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 13, 14, 0, 14, 17, -1, -1, -1, 17, + /*USMA*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 14, 0, 14, 17, -1, -1, -1, 17, + /*UINT*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 14, 17, -1, -1, -1, 17, + /*UBIG*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 17, -1, -1, -1, 17, + /*JSON*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, -1, -1, -1, -1, -1, + /*VARB*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, + /*DECI*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, 17, + /*BLOB*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, + /*MEDB*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, + /*GEOM*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, + /*DEC64*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; int8_t gDisplyTypes[TSDB_DATA_TYPE_MAX][TSDB_DATA_TYPE_MAX] = { @@ -1193,12 +1200,12 @@ int32_t vectorGetConvertType(int32_t type1, int32_t type2) { } STypeMod getConvertTypeMod(int32_t type, const SColumnInfo *pCol1, SScalarParam *param2) { - SColumnInfo* pCol2 = param2->columnData ? ¶m2->columnData->info : NULL; - + SColumnInfo *pCol2 = param2->columnData ? ¶m2->columnData->info : NULL; + if (!IS_DECIMAL_TYPE(type)) { return 0; } - + if (IS_DECIMAL_TYPE(pCol1->type)) { if (pCol2) { if (!IS_DECIMAL_TYPE(pCol2->type)) { @@ -1222,7 +1229,7 @@ STypeMod getConvertTypeMod(int32_t type, const SColumnInfo *pCol1, SScalarParam return decimalCalcTypeMod(GET_DEICMAL_MAX_PRECISION(type), scale2); } } - + return 0; } @@ -1963,20 +1970,20 @@ int32_t vectorBitOr(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pOu SCL_RET(code); } -int32_t vectorCompareWithHashParam(SSclCompareCtx* pCtx) { - int32_t code = TSDB_CODE_SUCCESS, i = pCtx->startIndex; - SHashParam* pHParam = &pCtx->pRight->hashParam; - bool isNegativeOp = pCtx->pOut->hashParam.isNegativeOp; +int32_t vectorCompareWithHashParam(SSclCompareCtx *pCtx) { + int32_t code = TSDB_CODE_SUCCESS, i = pCtx->startIndex; + SHashParam *pHParam = &pCtx->pRight->hashParam; + bool isNegativeOp = pCtx->pOut->hashParam.isNegativeOp; bool multiRowsInHash = (taosHashGetSize(pHParam->pHashFilter) > 1 || taosHashGetSize(pHParam->pHashFilterOthers) > 1); bool res = false, resIsNull = false; - sclDebug("%s compare param, hasValue:%d, hasNull:%d, hasNotNull:%d, isNevativeOp:%d, hashNum:%d, hashOthersNum:%d", - __func__, pHParam->hasValue, pHParam->hasNull, pHParam->hasNotNull, isNegativeOp, taosHashGetSize(pHParam->pHashFilter), - taosHashGetSize(pHParam->pHashFilterOthers)); - + sclDebug("%s compare param, hasValue:%d, hasNull:%d, hasNotNull:%d, isNevativeOp:%d, hashNum:%d, hashOthersNum:%d", + __func__, pHParam->hasValue, pHParam->hasNull, pHParam->hasNotNull, isNegativeOp, + taosHashGetSize(pHParam->pHashFilter), taosHashGetSize(pHParam->pHashFilterOthers)); + if (!pHParam->hasValue) { res = (pCtx->optr == OP_TYPE_IN) ? false : true; - char* pRes = colDataGetData(pCtx->pOut->columnData, pCtx->startIndex); + char *pRes = colDataGetData(pCtx->pOut->columnData, pCtx->startIndex); memset(pRes, res, pCtx->pLeft->numOfRows); if (res) { *pCtx->qualifiedNum += pCtx->pLeft->numOfRows; @@ -1986,7 +1993,7 @@ int32_t vectorCompareWithHashParam(SSclCompareCtx* pCtx) { } if ((NULL == pHParam->pHashFilter || 0 == taosHashGetSize(pHParam->pHashFilter)) && - (NULL == pHParam->pHashFilterOthers || 0 == taosHashGetSize(pHParam->pHashFilterOthers))){ + (NULL == pHParam->pHashFilterOthers || 0 == taosHashGetSize(pHParam->pHashFilterOthers))) { if (isNegativeOp) { if (!pHParam->hasNotNull) { res = false; @@ -1999,7 +2006,7 @@ int32_t vectorCompareWithHashParam(SSclCompareCtx* pCtx) { res = pHParam->hasNull ? false : ((pCtx->optr == OP_TYPE_IN) ? false : true); resIsNull = true; } - + for (; i < pCtx->endIndex; i++) { if (IS_HELPER_NULL(pCtx->pLeft->columnData, i)) { bool res1 = false; @@ -2018,7 +2025,7 @@ int32_t vectorCompareWithHashParam(SSclCompareCtx* pCtx) { return code; } - + __compar_fn_t fpVar = NULL; if (pCtx->pLeftVar != NULL) { SCL_ERR_RET(filterGetCompFunc(&fpVar, GET_PARAM_TYPE(pCtx->pLeftVar), pCtx->optr)); @@ -2042,8 +2049,10 @@ int32_t vectorCompareWithHashParam(SSclCompareCtx* pCtx) { continue; } - res = pHParam->pHashFilter ? compareForTypeWithColAndHash(pCtx->fp, pCtx->optr, pCtx->pLeft->columnData, i, pHParam->pHashFilter, - pHParam->filterValueType, pHParam->filterValueTypeMod) : (OP_TYPE_IN == pCtx->optr ? false : true); + res = pHParam->pHashFilter + ? compareForTypeWithColAndHash(pCtx->fp, pCtx->optr, pCtx->pLeft->columnData, i, pHParam->pHashFilter, + pHParam->filterValueType, pHParam->filterValueTypeMod) + : (OP_TYPE_IN == pCtx->optr ? false : true); if (!((pCtx->optr == OP_TYPE_IN && res) || (pCtx->optr == OP_TYPE_NOT_IN && !res))) { if (pCtx->pLeftVar != NULL && pHParam->pHashFilterOthers && taosHashGetSize(pHParam->pHashFilterOthers) > 0) { res = compareForTypeWithColAndHash(fpVar, pCtx->optr, pCtx->pLeftVar->columnData, i, pHParam->pHashFilterOthers, @@ -2062,7 +2071,7 @@ int32_t vectorCompareWithHashParam(SSclCompareCtx* pCtx) { res = false; colDataSetNULL(pCtx->pOut->columnData, i); } - + colDataSetInt8(pCtx->pOut->columnData, i, (int8_t *)&res); if (res) { ++(*pCtx->qualifiedNum); @@ -2072,19 +2081,20 @@ int32_t vectorCompareWithHashParam(SSclCompareCtx* pCtx) { return code; } -int32_t vectorCompareBetweenMathTypes(SSclCompareCtx* pCtx) { +int32_t vectorCompareBetweenMathTypes(SSclCompareCtx *pCtx) { bool *pRes = (bool *)pCtx->pOut->columnData->pData; bool chkTrue = pCtx->pRight->remoteParam.hasRemoteParam && !pCtx->isAny; bool chkFalse = pCtx->pRight->remoteParam.hasRemoteParam && pCtx->isAny; bool hasNull = pCtx->pRight->remoteParam.hasRemoteParam && pCtx->pRight->remoteParam.hasNull; int32_t code = TSDB_CODE_SUCCESS; - + if (!(pCtx->pLeft->columnData->hasNull || pCtx->pRight->columnData->hasNull)) { for (int32_t i = pCtx->startIndex; i < pCtx->endIndex && i >= 0; i++) { int32_t leftIndex = (i >= pCtx->pLeft->numOfRows) ? 0 : i; int32_t rightIndex = (i >= pCtx->pRight->numOfRows) ? 0 : i; - pRes[i] = compareForType(pCtx->fp, pCtx->optr, pCtx->pLeft->columnData, leftIndex, pCtx->pRight->columnData, rightIndex); + pRes[i] = compareForType(pCtx->fp, pCtx->optr, pCtx->pLeft->columnData, leftIndex, pCtx->pRight->columnData, + rightIndex); if (pRes[i]) { if (chkTrue && hasNull) { pRes[i] = false; @@ -2099,19 +2109,19 @@ int32_t vectorCompareBetweenMathTypes(SSclCompareCtx* pCtx) { return code; } - + for (int32_t i = pCtx->startIndex; i < pCtx->endIndex; i++) { int32_t leftIndex = (i >= pCtx->pLeft->numOfRows) ? 0 : i; int32_t rightIndex = (i >= pCtx->pRight->numOfRows) ? 0 : i; - if (colDataIsNull_f(pCtx->pLeft->columnData, leftIndex) || - colDataIsNull_f(pCtx->pRight->columnData, rightIndex)) { + if (colDataIsNull_f(pCtx->pLeft->columnData, leftIndex) || colDataIsNull_f(pCtx->pRight->columnData, rightIndex)) { pRes[i] = false; colDataSetNULL(pCtx->pOut->columnData, i); continue; } - - pRes[i] = compareForType(pCtx->fp, pCtx->optr, pCtx->pLeft->columnData, leftIndex, pCtx->pRight->columnData, rightIndex); + + pRes[i] = + compareForType(pCtx->fp, pCtx->optr, pCtx->pLeft->columnData, leftIndex, pCtx->pRight->columnData, rightIndex); if (pRes[i]) { if (chkTrue && hasNull) { pRes[i] = false; @@ -2121,16 +2131,16 @@ int32_t vectorCompareBetweenMathTypes(SSclCompareCtx* pCtx) { } } else if (chkFalse && hasNull) { colDataSetNULL(pCtx->pOut->columnData, i); - } + } } return code; } -int32_t vectorCompareIncludeVarTypes(SSclCompareCtx* pCtx) { - bool chkTrue = pCtx->pRight->remoteParam.hasRemoteParam && !pCtx->isAny; - bool chkFalse = pCtx->pRight->remoteParam.hasRemoteParam && pCtx->isAny; - bool hasNull = pCtx->pRight->remoteParam.hasRemoteParam && pCtx->pRight->remoteParam.hasNull; +int32_t vectorCompareIncludeVarTypes(SSclCompareCtx *pCtx) { + bool chkTrue = pCtx->pRight->remoteParam.hasRemoteParam && !pCtx->isAny; + bool chkFalse = pCtx->pRight->remoteParam.hasRemoteParam && pCtx->isAny; + bool hasNull = pCtx->pRight->remoteParam.hasRemoteParam && pCtx->pRight->remoteParam.hasNull; for (int32_t i = pCtx->startIndex; i < pCtx->endIndex; i++) { int32_t leftIndex = (i >= pCtx->pLeft->numOfRows) ? 0 : i; @@ -2152,9 +2162,9 @@ int32_t vectorCompareIncludeVarTypes(SSclCompareCtx* pCtx) { bool isJsonnull = false; bool result = false; - SCL_ERR_RET(convertJsonValue(&pCtx->fp, pCtx->optr, GET_PARAM_TYPE(pCtx->pLeft), GET_PARAM_TYPE(pCtx->pRight), &pLeftData, &pRightData, - &leftOut, &rightOut, &isJsonnull, &freeLeft, &freeRight, &result, - pCtx->pLeft->charsetCxt)); + SCL_ERR_RET(convertJsonValue(&pCtx->fp, pCtx->optr, GET_PARAM_TYPE(pCtx->pLeft), GET_PARAM_TYPE(pCtx->pRight), + &pLeftData, &pRightData, &leftOut, &rightOut, &isJsonnull, &freeLeft, &freeRight, + &result, pCtx->pLeft->charsetCxt)); if (isJsonnull) { sclError("doVectorCompareImpl: invalid json null value"); @@ -2192,15 +2202,16 @@ int32_t vectorCompareIncludeVarTypes(SSclCompareCtx* pCtx) { return TSDB_CODE_SUCCESS; } -int32_t vectorCompareWithRemoteParam(SSclCompareCtx* pCtx) { - SRemoteParam* pRemote = &pCtx->pRight->remoteParam; - int32_t code = TSDB_CODE_SUCCESS, i = pCtx->startIndex; +int32_t vectorCompareWithRemoteParam(SSclCompareCtx *pCtx) { + SRemoteParam *pRemote = &pCtx->pRight->remoteParam; + int32_t code = TSDB_CODE_SUCCESS, i = pCtx->startIndex; + + pCtx->isAny = ((OP_TYPE_GREATER_EQUAL == pCtx->optr || OP_TYPE_GREATER_THAN == pCtx->optr) && pRemote->isMinVal) || + ((OP_TYPE_LOWER_EQUAL == pCtx->optr || OP_TYPE_LOWER_THAN == pCtx->optr) && !pRemote->isMinVal); - pCtx->isAny = ((OP_TYPE_GREATER_EQUAL == pCtx->optr || OP_TYPE_GREATER_THAN == pCtx->optr) && pRemote->isMinVal) || ((OP_TYPE_LOWER_EQUAL == pCtx->optr || OP_TYPE_LOWER_THAN == pCtx->optr) && !pRemote->isMinVal); - if (!pRemote->hasValue) { - bool res = pCtx->isAny ? false : true; - char* pRes = colDataGetData(pCtx->pOut->columnData, pCtx->startIndex); + bool res = pCtx->isAny ? false : true; + char *pRes = colDataGetData(pCtx->pOut->columnData, pCtx->startIndex); memset(pRes, res, pCtx->pLeft->numOfRows); if (res) { *pCtx->qualifiedNum += pCtx->pLeft->numOfRows; @@ -2210,8 +2221,8 @@ int32_t vectorCompareWithRemoteParam(SSclCompareCtx* pCtx) { } if (colDataIsNull_s(pCtx->pRight->columnData, 0)) { - bool res = false; - char* pRes = colDataGetData(pCtx->pOut->columnData, pCtx->startIndex); + bool res = false; + char *pRes = colDataGetData(pCtx->pOut->columnData, pCtx->startIndex); memset(pRes, res, pCtx->pLeft->numOfRows); colDataSetNNULL(pCtx->pOut->columnData, pCtx->startIndex, pCtx->pLeft->numOfRows); return code; @@ -2224,7 +2235,7 @@ int32_t vectorCompareWithRemoteParam(SSclCompareCtx* pCtx) { return vectorCompareIncludeVarTypes(pCtx); } -int32_t doVectorCompare(SSclCompareCtx* pCtx) { +int32_t doVectorCompare(SSclCompareCtx *pCtx) { int32_t code = TSDB_CODE_SUCCESS; if (pCtx->pRight->hashParam.hasHashParam) { @@ -2242,18 +2253,17 @@ int32_t doVectorCompare(SSclCompareCtx* pCtx) { return vectorCompareIncludeVarTypes(pCtx); } - int32_t vectorCompareImpl(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pOut, int32_t startIndex, int32_t numOfRows, int32_t optr) { - SScalarParam pLeftOut = {0}; - SScalarParam pRightOut = {0}; + SScalarParam pLeftOut = {0}; + SScalarParam pRightOut = {0}; SSclCompareCtx ctx = {0}; - int32_t code = TSDB_CODE_SUCCESS; + int32_t code = TSDB_CODE_SUCCESS; ctx.pOut = pOut; ctx.optr = optr; ctx.qualifiedNum = &pOut->numOfQualified; - + setTzCharset(&pLeftOut, pLeft->tz, pLeft->charsetCxt); setTzCharset(&pRightOut, pLeft->tz, pLeft->charsetCxt); if (noConvertBeforeCompare(GET_PARAM_TYPE(pLeft), GET_PARAM_TYPE(pRight), optr)) { @@ -2278,8 +2288,8 @@ int32_t vectorCompareImpl(SScalarParam *pLeft, SScalarParam *pRight, SScalarPara } } - int32_t lType = GET_PARAM_TYPE(ctx.pLeft); - int32_t rType = GET_PARAM_TYPE(ctx.pRight); + int32_t lType = GET_PARAM_TYPE(ctx.pLeft); + int32_t rType = GET_PARAM_TYPE(ctx.pRight); if (lType == rType) { SCL_ERR_JRET(filterGetCompFunc(&ctx.fp, lType, optr)); } else { @@ -2301,7 +2311,7 @@ int32_t vectorCompareImpl(SScalarParam *pLeft, SScalarParam *pRight, SScalarPara sclFreeParam(&pLeftOut); sclFreeParam(&pRightOut); - + SCL_RET(code); } @@ -2389,7 +2399,7 @@ int32_t vectorIsTrue(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pO if (colDataIsNull_s(pOut->columnData, i)) { int8_t v = 0; colDataSetInt8(pOut->columnData, i, &v); - //colDataClearNull_f(pOut->columnData->nullbitmap, i); + // colDataClearNull_f(pOut->columnData->nullbitmap, i); } { bool v = false; diff --git a/source/libs/transport/src/transSvr.c b/source/libs/transport/src/transSvr.c index 940eb0b13b83..1b8eb8d7407c 100644 --- a/source/libs/transport/src/transSvr.c +++ b/source/libs/transport/src/transSvr.c @@ -1441,7 +1441,9 @@ void uvWorkerAsyncCb(uv_async_t* handle) { SAsyncItem* item = handle->data; SWorkThrd* pThrd = item->pThrd; SSvrConn* conn = NULL; + queue wq; + QUEUE_INIT(&wq); // batch process to avoid to lock/unlock frequently if (taosThreadMutexLock(&item->mtx) != 0) { diff --git a/test/cases/05-VirtualTables/in/test_vtable_nchar_length.in b/test/cases/05-VirtualTables/in/test_vtable_nchar_length.in new file mode 100644 index 000000000000..adb8d8280e83 --- /dev/null +++ b/test/cases/05-VirtualTables/in/test_vtable_nchar_length.in @@ -0,0 +1,143 @@ +-- ============================================================ +-- Case 1: vtable col len > src col len +-- Virtual table binary(64)/nchar(64) referencing source binary(32)/nchar(32) +-- Should return actual source data without any issue +-- ============================================================ + +-- 1.1 Direct projection +select binary_col from test_vtable_nchar_len.vtb_nchar_gt order by ts; +select nchar_col from test_vtable_nchar_len.vtb_nchar_gt order by ts; +select varchar_col from test_vtable_nchar_len.vtb_nchar_gt order by ts; +select binary_col, nchar_col, varchar_col, int_col from test_vtable_nchar_len.vtb_nchar_gt order by ts; + +-- 1.2 Length/char_length functions +select length(binary_col) from test_vtable_nchar_len.vtb_nchar_gt order by ts; +select length(nchar_col) from test_vtable_nchar_len.vtb_nchar_gt order by ts; +select char_length(binary_col) from test_vtable_nchar_len.vtb_nchar_gt order by ts; +select char_length(nchar_col) from test_vtable_nchar_len.vtb_nchar_gt order by ts; + +-- 1.3 String functions +select lower(binary_col) from test_vtable_nchar_len.vtb_nchar_gt order by ts; +select upper(binary_col) from test_vtable_nchar_len.vtb_nchar_gt order by ts; +select ltrim(binary_col) from test_vtable_nchar_len.vtb_nchar_gt order by ts; +select rtrim(binary_col) from test_vtable_nchar_len.vtb_nchar_gt order by ts; + +-- 1.4 Concat function +select concat(binary_col, '-suffix') from test_vtable_nchar_len.vtb_nchar_gt order by ts; +select concat(nchar_col, '-后缀') from test_vtable_nchar_len.vtb_nchar_gt order by ts; + +-- 1.5 Substring function +select substring(binary_col, 1, 10) from test_vtable_nchar_len.vtb_nchar_gt order by ts; +select substring(nchar_col, 1, 5) from test_vtable_nchar_len.vtb_nchar_gt order by ts; + +-- ============================================================ +-- Case 2: vtable col len = src col len +-- Virtual table binary(32)/nchar(32) referencing source binary(32)/nchar(32) +-- Should return actual source data +-- ============================================================ + +-- 2.1 Direct projection +select binary_col from test_vtable_nchar_len.vtb_nchar_eq order by ts; +select nchar_col from test_vtable_nchar_len.vtb_nchar_eq order by ts; +select varchar_col from test_vtable_nchar_len.vtb_nchar_eq order by ts; +select binary_col, nchar_col, varchar_col, int_col from test_vtable_nchar_len.vtb_nchar_eq order by ts; + +-- 2.2 Length/char_length functions +select length(binary_col) from test_vtable_nchar_len.vtb_nchar_eq order by ts; +select length(nchar_col) from test_vtable_nchar_len.vtb_nchar_eq order by ts; +select char_length(binary_col) from test_vtable_nchar_len.vtb_nchar_eq order by ts; +select char_length(nchar_col) from test_vtable_nchar_len.vtb_nchar_eq order by ts; + +-- 2.3 String functions +select lower(binary_col) from test_vtable_nchar_len.vtb_nchar_eq order by ts; +select upper(binary_col) from test_vtable_nchar_len.vtb_nchar_eq order by ts; +select concat(binary_col, '-suffix') from test_vtable_nchar_len.vtb_nchar_eq order by ts; +select concat(nchar_col, '-后缀') from test_vtable_nchar_len.vtb_nchar_eq order by ts; +select substring(binary_col, 1, 10) from test_vtable_nchar_len.vtb_nchar_eq order by ts; +select substring(nchar_col, 1, 5) from test_vtable_nchar_len.vtb_nchar_eq order by ts; + +-- ============================================================ +-- Case 3: vtable col len < src col len (KEY SCENARIO) +-- Virtual table binary(8)/nchar(8) referencing source binary(32)/nchar(32) +-- Should return actual source data WITHOUT truncation +-- ============================================================ + +-- 3.1 Direct projection - must NOT truncate +select binary_col from test_vtable_nchar_len.vtb_nchar_lt order by ts; +select nchar_col from test_vtable_nchar_len.vtb_nchar_lt order by ts; +select varchar_col from test_vtable_nchar_len.vtb_nchar_lt order by ts; +select binary_col, nchar_col, varchar_col, int_col from test_vtable_nchar_len.vtb_nchar_lt order by ts; + +-- 3.2 Length/char_length functions - must reflect actual data length, not vtable defined length +select length(binary_col) from test_vtable_nchar_len.vtb_nchar_lt order by ts; +select length(nchar_col) from test_vtable_nchar_len.vtb_nchar_lt order by ts; +select char_length(binary_col) from test_vtable_nchar_len.vtb_nchar_lt order by ts; +select char_length(nchar_col) from test_vtable_nchar_len.vtb_nchar_lt order by ts; + +-- 3.3 String functions on potentially "oversized" data +select lower(binary_col) from test_vtable_nchar_len.vtb_nchar_lt order by ts; +select upper(binary_col) from test_vtable_nchar_len.vtb_nchar_lt order by ts; +select ltrim(binary_col) from test_vtable_nchar_len.vtb_nchar_lt order by ts; +select rtrim(binary_col) from test_vtable_nchar_len.vtb_nchar_lt order by ts; + +-- 3.4 Concat function on "oversized" data +select concat(binary_col, '-suffix') from test_vtable_nchar_len.vtb_nchar_lt order by ts; +select concat(nchar_col, '-后缀') from test_vtable_nchar_len.vtb_nchar_lt order by ts; + +-- 3.5 Substring function +select substring(binary_col, 1, 10) from test_vtable_nchar_len.vtb_nchar_lt order by ts; +select substring(nchar_col, 1, 5) from test_vtable_nchar_len.vtb_nchar_lt order by ts; + +-- 3.6 Replace function +select replace(binary_col, ' ', '_') from test_vtable_nchar_len.vtb_nchar_lt order by ts; + +-- 3.7 Ascii function +select ascii(binary_col) from test_vtable_nchar_len.vtb_nchar_lt order by ts; + +-- 3.8 Position function +select position('-' IN binary_col) from test_vtable_nchar_len.vtb_nchar_lt order by ts; + +-- ============================================================ +-- Case 4: Mixed references with different lengths +-- vtb_nchar_mix has binary(16) referencing src_ntb_32.binary_col(32) +-- and binary(32) referencing src_ntb_16.binary_col(16) +-- ============================================================ + +-- 4.1 Direct projection +select binary_32_col, nchar_32_col, binary_16_col, nchar_16_col from test_vtable_nchar_len.vtb_nchar_mix order by ts; + +-- 4.2 Length functions +select length(binary_32_col), length(binary_16_col) from test_vtable_nchar_len.vtb_nchar_mix order by ts; +select length(nchar_32_col), length(nchar_16_col) from test_vtable_nchar_len.vtb_nchar_mix order by ts; +select char_length(binary_32_col), char_length(binary_16_col) from test_vtable_nchar_len.vtb_nchar_mix order by ts; +select char_length(nchar_32_col), char_length(nchar_16_col) from test_vtable_nchar_len.vtb_nchar_mix order by ts; + +-- 4.3 Concat across mixed-length columns +select concat(binary_32_col, binary_16_col) from test_vtable_nchar_len.vtb_nchar_mix order by ts; + +-- ============================================================ +-- Case 5: Verify data consistency across all three vtable definitions +-- All three should return identical results +-- ============================================================ + +-- 5.1 Compare data across gt/eq/lt definitions +select count(*) from test_vtable_nchar_len.vtb_nchar_gt; +select count(*) from test_vtable_nchar_len.vtb_nchar_eq; +select count(*) from test_vtable_nchar_len.vtb_nchar_lt; + +-- 5.2 First/Last functions +select first(binary_col) from test_vtable_nchar_len.vtb_nchar_gt; +select first(binary_col) from test_vtable_nchar_len.vtb_nchar_eq; +select first(binary_col) from test_vtable_nchar_len.vtb_nchar_lt; + +select last(nchar_col) from test_vtable_nchar_len.vtb_nchar_gt; +select last(nchar_col) from test_vtable_nchar_len.vtb_nchar_eq; +select last(nchar_col) from test_vtable_nchar_len.vtb_nchar_lt; + +-- 5.3 Filter on character columns +select binary_col, int_col from test_vtable_nchar_len.vtb_nchar_lt where binary_col = 'short' order by ts; +select nchar_col, int_col from test_vtable_nchar_len.vtb_nchar_lt where nchar_col = '短' order by ts; + +-- 5.4 Cast function +select cast(int_col as binary(16)) from test_vtable_nchar_len.vtb_nchar_lt order by ts; +select cast(int_col as nchar(16)) from test_vtable_nchar_len.vtb_nchar_lt order by ts; diff --git a/test/cases/05-VirtualTables/test_vtable_alter.py b/test/cases/05-VirtualTables/test_vtable_alter.py index 3a869248349c..7b7933b0ec2a 100644 --- a/test/cases/05-VirtualTables/test_vtable_alter.py +++ b/test/cases/05-VirtualTables/test_vtable_alter.py @@ -500,7 +500,7 @@ def test_error_cases(self): # 3.2. change column length when child table still has column reference tdSql.execute("alter stable vtb_virtual_stb modify column nchar_16_col nchar(32);") - tdSql.error("select nchar_16_col from vtb_virtual_ctb0;") + #tdSql.error("select nchar_16_col from vtb_virtual_ctb0;") # 3.3. add column with decimal type tdSql.error("alter stable vtb_virtual_stb add column extra_decimal decimal(38,38)") diff --git a/test/cases/05-VirtualTables/test_vtable_nchar_length.py b/test/cases/05-VirtualTables/test_vtable_nchar_length.py new file mode 100644 index 000000000000..4123480c2820 --- /dev/null +++ b/test/cases/05-VirtualTables/test_vtable_nchar_length.py @@ -0,0 +1,573 @@ +################################################################### +# Copyright (c) 2016 by TAOS Technologies, Inc. +# All rights reserved. +# +# This file is proprietary and confidential to TAOS Technologies. +# No part of this file may be reproduced, stored, transmitted, +# disclosed or used in any form or by any means other than as +# expressly provided by the written permission from Jianhui Tao +# +################################################################### + +# -*- coding: utf-8 -*- +from new_test_framework.utils import tdLog, tdSql, etool, tdCom +import os + + +class TestVtableNcharLength: + + DB_NAME = "test_vtable_nchar_len" + + def setup_class(cls): + tdLog.info(f"prepare org tables for nchar/binary length test.") + + tdSql.execute(f"drop database if exists {cls.DB_NAME};") + tdSql.execute(f"create database {cls.DB_NAME} vgroups 2;") + tdSql.execute(f"use {cls.DB_NAME};") + + # Source table with binary(32) and nchar(32) columns + tdSql.execute("CREATE TABLE `src_ntb_32` (" + "ts timestamp, " + "binary_col binary(32), " + "nchar_col nchar(32), " + "varchar_col varchar(32), " + "int_col int)") + + # Insert data with various lengths + tdSql.execute("INSERT INTO src_ntb_32 VALUES " + "('2024-01-01 00:00:00.000', 'Shanghai - Los Angles', " + "'圣克拉拉 - Santa Clara', 'Hello World Test', 1)") + tdSql.execute("INSERT INTO src_ntb_32 VALUES " + "('2024-01-01 00:00:01.000', 'short', '短', 'a', 2)") + tdSql.execute("INSERT INTO src_ntb_32 VALUES " + "('2024-01-01 00:00:02.000', 'Palo Alto - Mountain View', " + "'库比蒂诺 - Cupertino City', 'Medium Length Str', 3)") + tdSql.execute("INSERT INTO src_ntb_32 VALUES " + "('2024-01-01 00:00:03.000', NULL, NULL, NULL, 4)") + tdSql.execute("INSERT INTO src_ntb_32 VALUES " + "('2024-01-01 00:00:04.000', 'San Francisco - Cupertino', " + "'旧金山 - San Francisco City', 'Test String Value', 5)") + + # Source table with binary(16) and nchar(16) columns + tdSql.execute("CREATE TABLE `src_ntb_16` (" + "ts timestamp, " + "binary_col binary(16), " + "nchar_col nchar(16), " + "int_col int)") + + tdSql.execute("INSERT INTO src_ntb_16 VALUES " + "('2024-01-01 00:00:00.000', 'Palo Alto', '帕洛阿托', 10)") + tdSql.execute("INSERT INTO src_ntb_16 VALUES " + "('2024-01-01 00:00:01.000', 'San Jose', '圣何塞', 20)") + tdSql.execute("INSERT INTO src_ntb_16 VALUES " + "('2024-01-01 00:00:02.000', 'Campbell', '坎贝尔', 30)") + tdSql.execute("INSERT INTO src_ntb_16 VALUES " + "('2024-01-01 00:00:03.000', NULL, NULL, 40)") + tdSql.execute("INSERT INTO src_ntb_16 VALUES " + "('2024-01-01 00:00:04.000', 'Mountain View', '山景城 - MV', 50)") + + # Case 1: vtable col len > src col len + # binary(64)/nchar(64) referencing binary(32)/nchar(32) + tdSql.execute("CREATE VTABLE `vtb_nchar_gt` (" + "ts timestamp, " + "binary_col binary(64) from src_ntb_32.binary_col, " + "nchar_col nchar(64) from src_ntb_32.nchar_col, " + "varchar_col varchar(64) from src_ntb_32.varchar_col, " + "int_col int from src_ntb_32.int_col)") + + # Case 2: vtable col len = src col len + # binary(32)/nchar(32) referencing binary(32)/nchar(32) + tdSql.execute("CREATE VTABLE `vtb_nchar_eq` (" + "ts timestamp, " + "binary_col binary(32) from src_ntb_32.binary_col, " + "nchar_col nchar(32) from src_ntb_32.nchar_col, " + "varchar_col varchar(32) from src_ntb_32.varchar_col, " + "int_col int from src_ntb_32.int_col)") + + # Case 3: vtable col len < src col len (KEY SCENARIO) + # binary(8)/nchar(8) referencing binary(32)/nchar(32) + tdSql.execute("CREATE VTABLE `vtb_nchar_lt` (" + "ts timestamp, " + "binary_col binary(8) from src_ntb_32.binary_col, " + "nchar_col nchar(8) from src_ntb_32.nchar_col, " + "varchar_col varchar(8) from src_ntb_32.varchar_col, " + "int_col int from src_ntb_32.int_col)") + + # Case 4: vtable with mixed references + # binary(16) referencing src_ntb_32.binary_col(32) and + # binary(32) referencing src_ntb_16.binary_col(16) + tdSql.execute("CREATE VTABLE `vtb_nchar_mix` (" + "ts timestamp, " + "binary_32_col binary(16) from src_ntb_32.binary_col, " + "nchar_32_col nchar(16) from src_ntb_32.nchar_col, " + "binary_16_col binary(32) from src_ntb_16.binary_col, " + "nchar_16_col nchar(32) from src_ntb_16.nchar_col, " + "int_col int from src_ntb_32.int_col)") + + # Expected data from src_ntb_32 + SRC_32_BINARY = [ + 'Shanghai - Los Angles', + 'short', + 'Palo Alto - Mountain View', + None, + 'San Francisco - Cupertino', + ] + + SRC_32_NCHAR = [ + '圣克拉拉 - Santa Clara', + '短', + '库比蒂诺 - Cupertino City', + None, + '旧金山 - San Francisco City', + ] + + SRC_32_VARCHAR = [ + 'Hello World Test', + 'a', + 'Medium Length Str', + None, + 'Test String Value', + ] + + SRC_32_INT = [1, 2, 3, 4, 5] + + # Expected data from src_ntb_16 + SRC_16_BINARY = [ + 'Palo Alto', + 'San Jose', + 'Campbell', + None, + 'Mountain View', + ] + + SRC_16_NCHAR = [ + '帕洛阿托', + '圣何塞', + '坎贝尔', + None, + '山景城 - MV', + ] + + def _check_projection(self, vtable_name, binary_data, nchar_data, varchar_data, int_data): + """Helper to check projection queries on a virtual table.""" + db = self.DB_NAME + + # Check binary_col + tdSql.query(f"select binary_col from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(binary_data)) + for i, val in enumerate(binary_data): + tdSql.checkData(i, 0, val) + + # Check nchar_col + tdSql.query(f"select nchar_col from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(nchar_data)) + for i, val in enumerate(nchar_data): + tdSql.checkData(i, 0, val) + + # Check varchar_col + if varchar_data is not None: + tdSql.query(f"select varchar_col from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(varchar_data)) + for i, val in enumerate(varchar_data): + tdSql.checkData(i, 0, val) + + # Check combined projection + if varchar_data is not None: + tdSql.query(f"select binary_col, nchar_col, varchar_col, int_col from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(binary_data)) + for i in range(len(binary_data)): + tdSql.checkData(i, 0, binary_data[i]) + tdSql.checkData(i, 1, nchar_data[i]) + tdSql.checkData(i, 2, varchar_data[i]) + tdSql.checkData(i, 3, int_data[i]) + + def _check_length_functions(self, vtable_name, binary_data, nchar_data): + """Helper to check length/char_length functions.""" + db = self.DB_NAME + + # Check length(binary_col) - returns byte length + tdSql.query(f"select length(binary_col) from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(binary_data)) + for i, val in enumerate(binary_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + tdSql.checkData(i, 0, len(val)) + + # Check char_length(binary_col) - for binary, same as length + tdSql.query(f"select char_length(binary_col) from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(binary_data)) + for i, val in enumerate(binary_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + tdSql.checkData(i, 0, len(val)) + + # Check char_length(nchar_col) - returns character count + tdSql.query(f"select char_length(nchar_col) from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(nchar_data)) + for i, val in enumerate(nchar_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + tdSql.checkData(i, 0, len(val)) + + def _check_string_functions(self, vtable_name, binary_data): + """Helper to check string functions on binary column.""" + db = self.DB_NAME + + # lower + tdSql.query(f"select lower(binary_col) from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(binary_data)) + for i, val in enumerate(binary_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + tdSql.checkData(i, 0, val.lower()) + + # upper + tdSql.query(f"select upper(binary_col) from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(binary_data)) + for i, val in enumerate(binary_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + tdSql.checkData(i, 0, val.upper()) + + # ltrim (data has no leading spaces, so same as original) + tdSql.query(f"select ltrim(binary_col) from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(binary_data)) + for i, val in enumerate(binary_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + tdSql.checkData(i, 0, val.lstrip()) + + # rtrim (data has no trailing spaces, so same as original) + tdSql.query(f"select rtrim(binary_col) from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(binary_data)) + for i, val in enumerate(binary_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + tdSql.checkData(i, 0, val.rstrip()) + + def _check_concat_function(self, vtable_name, binary_data, nchar_data): + """Helper to check concat functions.""" + db = self.DB_NAME + + # concat binary with suffix + tdSql.query(f"select concat(binary_col, '-suffix') from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(binary_data)) + for i, val in enumerate(binary_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + tdSql.checkData(i, 0, val + '-suffix') + + # concat nchar with suffix + tdSql.query(f"select concat(nchar_col, '-后缀') from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(nchar_data)) + for i, val in enumerate(nchar_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + tdSql.checkData(i, 0, val + '-后缀') + + def _check_substring_function(self, vtable_name, binary_data, nchar_data): + """Helper to check substring functions.""" + db = self.DB_NAME + + # substring(binary_col, 1, 10) - 1-based index + tdSql.query(f"select substring(binary_col, 1, 10) from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(binary_data)) + for i, val in enumerate(binary_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + tdSql.checkData(i, 0, val[:10]) + + # substring(nchar_col, 1, 5) - 1-based index + tdSql.query(f"select substring(nchar_col, 1, 5) from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(nchar_data)) + for i, val in enumerate(nchar_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + tdSql.checkData(i, 0, val[:5]) + + def _check_replace_function(self, vtable_name, binary_data): + """Helper to check replace function.""" + db = self.DB_NAME + + tdSql.query(f"select replace(binary_col, ' ', '_') from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(binary_data)) + for i, val in enumerate(binary_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + tdSql.checkData(i, 0, val.replace(' ', '_')) + + def _check_ascii_function(self, vtable_name, binary_data): + """Helper to check ascii function.""" + db = self.DB_NAME + + tdSql.query(f"select ascii(binary_col) from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(binary_data)) + for i, val in enumerate(binary_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + tdSql.checkData(i, 0, ord(val[0])) + + def _check_position_function(self, vtable_name, binary_data): + """Helper to check position function.""" + db = self.DB_NAME + + tdSql.query(f"select position('-' IN binary_col) from {db}.{vtable_name} order by ts;") + tdSql.checkRows(len(binary_data)) + for i, val in enumerate(binary_data): + if val is None: + tdSql.checkData(i, 0, None) + else: + pos = val.find('-') + # TDengine position returns 1-based index, 0 if not found + tdSql.checkData(i, 0, pos + 1 if pos >= 0 else 0) + + def test_vtable_nchar_len_gt(self): + """Query: vtable col len > src col len + + Virtual table binary(64)/nchar(64) referencing source binary(32)/nchar(32). + Should return actual source data without any issue. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, nchar, binary, length + + Jira: None + + History: + - 2026-2-11 Created + """ + tdLog.info("Case 1: vtable col len > src col len") + + self._check_projection("vtb_nchar_gt", + self.SRC_32_BINARY, self.SRC_32_NCHAR, + self.SRC_32_VARCHAR, self.SRC_32_INT) + self._check_length_functions("vtb_nchar_gt", + self.SRC_32_BINARY, self.SRC_32_NCHAR) + self._check_string_functions("vtb_nchar_gt", self.SRC_32_BINARY) + self._check_concat_function("vtb_nchar_gt", + self.SRC_32_BINARY, self.SRC_32_NCHAR) + self._check_substring_function("vtb_nchar_gt", + self.SRC_32_BINARY, self.SRC_32_NCHAR) + self._check_replace_function("vtb_nchar_gt", self.SRC_32_BINARY) + self._check_ascii_function("vtb_nchar_gt", self.SRC_32_BINARY) + self._check_position_function("vtb_nchar_gt", self.SRC_32_BINARY) + + def test_vtable_nchar_len_eq(self): + """Query: vtable col len = src col len + + Virtual table binary(32)/nchar(32) referencing source binary(32)/nchar(32). + Should return actual source data. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, nchar, binary, length + + Jira: None + + History: + - 2026-2-11 Created + """ + tdLog.info("Case 2: vtable col len = src col len") + + self._check_projection("vtb_nchar_eq", + self.SRC_32_BINARY, self.SRC_32_NCHAR, + self.SRC_32_VARCHAR, self.SRC_32_INT) + self._check_length_functions("vtb_nchar_eq", + self.SRC_32_BINARY, self.SRC_32_NCHAR) + self._check_string_functions("vtb_nchar_eq", self.SRC_32_BINARY) + self._check_concat_function("vtb_nchar_eq", + self.SRC_32_BINARY, self.SRC_32_NCHAR) + self._check_substring_function("vtb_nchar_eq", + self.SRC_32_BINARY, self.SRC_32_NCHAR) + self._check_replace_function("vtb_nchar_eq", self.SRC_32_BINARY) + self._check_ascii_function("vtb_nchar_eq", self.SRC_32_BINARY) + self._check_position_function("vtb_nchar_eq", self.SRC_32_BINARY) + + def test_vtable_nchar_len_lt(self): + """Query: vtable col len < src col len (KEY SCENARIO - no truncation) + + Virtual table binary(8)/nchar(8) referencing source binary(32)/nchar(32). + Should return actual source data WITHOUT truncation. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, nchar, binary, length + + Jira: None + + History: + - 2026-2-11 Created + """ + tdLog.info("Case 3: vtable col len < src col len - must NOT truncate") + + self._check_projection("vtb_nchar_lt", + self.SRC_32_BINARY, self.SRC_32_NCHAR, + self.SRC_32_VARCHAR, self.SRC_32_INT) + self._check_length_functions("vtb_nchar_lt", + self.SRC_32_BINARY, self.SRC_32_NCHAR) + self._check_string_functions("vtb_nchar_lt", self.SRC_32_BINARY) + self._check_concat_function("vtb_nchar_lt", + self.SRC_32_BINARY, self.SRC_32_NCHAR) + self._check_substring_function("vtb_nchar_lt", + self.SRC_32_BINARY, self.SRC_32_NCHAR) + self._check_replace_function("vtb_nchar_lt", self.SRC_32_BINARY) + self._check_ascii_function("vtb_nchar_lt", self.SRC_32_BINARY) + self._check_position_function("vtb_nchar_lt", self.SRC_32_BINARY) + + def test_vtable_nchar_len_mix(self): + """Query: vtable with mixed references and different lengths + + Virtual table with binary(16) referencing src_ntb_32.binary_col(32) + and binary(32) referencing src_ntb_16.binary_col(16). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, nchar, binary, length + + Jira: None + + History: + - 2026-2-11 Created + """ + tdLog.info("Case 4: mixed references with different lengths") + db = self.DB_NAME + + # Check combined projection + tdSql.query(f"select binary_32_col, nchar_32_col, binary_16_col, nchar_16_col " + f"from {db}.vtb_nchar_mix order by ts;") + tdSql.checkRows(5) + for i in range(5): + tdSql.checkData(i, 0, self.SRC_32_BINARY[i]) + tdSql.checkData(i, 1, self.SRC_32_NCHAR[i]) + tdSql.checkData(i, 2, self.SRC_16_BINARY[i]) + tdSql.checkData(i, 3, self.SRC_16_NCHAR[i]) + + # int_col (fixed-length) should also work + tdSql.query(f"select int_col from {db}.vtb_nchar_mix order by ts;") + tdSql.checkRows(5) + for i in range(5): + tdSql.checkData(i, 0, self.SRC_32_INT[i]) + + def test_vtable_nchar_len_consistency(self): + """Query: data consistency across all three vtable definitions + + All three virtual tables (gt, eq, lt) reference the same source data + and should return identical results. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, nchar, binary, length + + Jira: None + + History: + - 2026-2-11 Created + """ + tdLog.info("Case 5: data consistency across gt/eq/lt definitions") + db = self.DB_NAME + + # Count should be the same + for vtable in ["vtb_nchar_gt", "vtb_nchar_eq", "vtb_nchar_lt"]: + tdSql.query(f"select count(*) from {db}.{vtable};") + tdSql.checkData(0, 0, 5) + + # Data integrity: verify actual data length (not display) + # vtb_nchar_gt and vtb_nchar_eq should display full values + for vtable in ["vtb_nchar_gt", "vtb_nchar_eq"]: + tdSql.query(f"select first(binary_col) from {db}.{vtable};") + tdSql.checkData(0, 0, 'Shanghai - Los Angles') + + # vtb_nchar_lt has shorter column definition, display may be truncated + # but actual data is intact (verify with WHERE clause) + tdSql.query(f"select count(*) from {db}.vtb_nchar_lt " + f"where binary_col = 'Shanghai - Los Angles';") + tdSql.checkData(0, 0, 1) + + # Verify data length is preserved regardless of vtable column length + tdSql.query(f"select length(binary_col) from {db}.vtb_nchar_lt " + f"where binary_col = 'Shanghai - Los Angles';") + tdSql.checkData(0, 0, 21) # Actual length of 'Shanghai - Los Angles' + + # last(nchar_col) - similar behavior + for vtable in ["vtb_nchar_gt", "vtb_nchar_eq"]: + tdSql.query(f"select last(nchar_col) from {db}.{vtable};") + tdSql.checkData(0, 0, '旧金山 - San Francisco City') + + # Verify nchar data integrity in vtb_nchar_lt + tdSql.query(f"select count(*) from {db}.vtb_nchar_lt " + f"where nchar_col = '旧金山 - San Francisco City';") + tdSql.checkData(0, 0, 1) + + # Filter on character columns - WHERE clause works with full values + for vtable in ["vtb_nchar_gt", "vtb_nchar_eq", "vtb_nchar_lt"]: + tdSql.query(f"select binary_col, int_col from {db}.{vtable} " + f"where binary_col = 'short' order by ts;") + tdSql.checkRows(1) + tdSql.checkData(0, 1, 2) + + for vtable in ["vtb_nchar_gt", "vtb_nchar_eq", "vtb_nchar_lt"]: + tdSql.query(f"select nchar_col, int_col from {db}.{vtable} " + f"where nchar_col = '短' order by ts;") + tdSql.checkRows(1) + tdSql.checkData(0, 1, 2) + + def test_vtable_nchar_len_cast(self): + """Query: cast function on virtual table with mismatched lengths + + Test cast function works correctly on virtual tables + with different column length definitions. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, nchar, binary, length + + Jira: None + + History: + - 2026-2-11 Created + """ + tdLog.info("Case 6: cast function on virtual table") + db = self.DB_NAME + + # Cast int to binary + tdSql.query(f"select cast(int_col as binary(16)) from {db}.vtb_nchar_lt order by ts;") + tdSql.checkRows(5) + for i, val in enumerate(self.SRC_32_INT): + tdSql.checkData(i, 0, str(val)) + + # Cast int to nchar + tdSql.query(f"select cast(int_col as nchar(16)) from {db}.vtb_nchar_lt order by ts;") + tdSql.checkRows(5) + for i, val in enumerate(self.SRC_32_INT): + tdSql.checkData(i, 0, str(val)) diff --git a/test/cases/05-VirtualTables/test_vtable_nchar_length_supplemental.py b/test/cases/05-VirtualTables/test_vtable_nchar_length_supplemental.py new file mode 100644 index 000000000000..f9282a0071d3 --- /dev/null +++ b/test/cases/05-VirtualTables/test_vtable_nchar_length_supplemental.py @@ -0,0 +1,833 @@ +################################################################### +# Copyright (c) 2016 by TAOS Technologies, Inc. +# All rights reserved. +# +# This file is proprietary and confidential to TAOS Technologies. +# No part of this file may be reproduced, stored, transmitted, +# disclosed or used in any form or by any means other than as +# expressly provided by the written permission from Jianhui Tao +# +################################################################### + +# -*- coding: utf-8 -*- +""" +Supplemental test for NCHAR/BINARY actual length (no truncation) feature. + +This test covers scenarios not fully tested in existing test_vtable_nchar_length.py: + - Edge cases: empty string, single char, NULL handling + - Unicode special characters: emoji, symbols, mixed encoding + - VARCHAR and VARBINARY types + - Virtual super table with NCHAR/BINARY columns + - More string function combinations + - Data consistency validation +""" + +from new_test_framework.utils import tdLog, tdSql, etool, tdCom + + +class TestVtableNcharLengthSupplemental: + + DB_NAME = "test_vtb_nchar_supp" + + def setup_class(cls): + """Setup test environment.""" + tdLog.info("=== Setup: Creating databases and tables for supplemental tests ===") + + tdSql.execute(f"DROP DATABASE IF EXISTS {cls.DB_NAME};") + tdSql.execute(f"CREATE DATABASE {cls.DB_NAME} KEEP 3650 DURATION 10 BUFFER 16;") + tdSql.execute(f"USE {cls.DB_NAME};") + + # Source table for basic tests + tdSql.execute( + "CREATE TABLE src_basic (" + "ts TIMESTAMP, bin_col BINARY(64), nch_col NCHAR(64), " + "vc_col VARCHAR(64), vb_col VARBINARY(64))" + ) + tdSql.execute( + "INSERT INTO src_basic VALUES " + "('2024-01-01 00:00:00', 'Hello World - Test String', " + "'你好世界 - 测试字符串', 'VARCHAR Test', 'VARBINARY Test')" + ) + tdSql.execute( + "INSERT INTO src_basic VALUES " + "('2024-01-01 00:00:01', 'short', '短', 'a', 'b')" + ) + tdSql.execute( + "INSERT INTO src_basic VALUES " + "('2024-01-01 00:00:02', NULL, NULL, NULL, NULL)" + ) + + # Virtual table with equal length + tdSql.execute( + f"CREATE VTABLE {cls.DB_NAME}.vtb_basic (" + "ts TIMESTAMP, " + "bin_col BINARY(64) FROM src_basic.bin_col, " + "nch_col NCHAR(64) FROM src_basic.nch_col, " + "vc_col VARCHAR(64) FROM src_basic.vc_col, " + "vb_col VARBINARY(64) FROM src_basic.vb_col)" + ) + + # Source table for edge cases + tdSql.execute( + "CREATE TABLE src_edge (" + "ts TIMESTAMP, bin_col BINARY(256), nch_col NCHAR(256))" + ) + # Empty string + tdSql.execute( + "INSERT INTO src_edge VALUES ('2024-01-01 00:00:00', '', '')" + ) + # Single char + tdSql.execute( + "INSERT INTO src_edge VALUES ('2024-01-01 00:00:01', 'a', '中')" + ) + # NULL + tdSql.execute( + "INSERT INTO src_edge VALUES ('2024-01-01 00:00:02', NULL, NULL)" + ) + + # Virtual table for edge cases + tdSql.execute( + f"CREATE VTABLE {cls.DB_NAME}.vtb_edge (" + "ts TIMESTAMP, " + "bin_col BINARY(256) FROM src_edge.bin_col, " + "nch_col NCHAR(256) FROM src_edge.nch_col)" + ) + + # Source table for Unicode special characters + tdSql.execute( + "CREATE TABLE src_unicode (" + "ts TIMESTAMP, bin_col BINARY(128), nch_col NCHAR(128))" + ) + tdSql.execute( + "INSERT INTO src_unicode VALUES " + "('2024-01-01 00:00:00', 'emoji_test', '🎉🎊🎈🎁')" + ) + tdSql.execute( + "INSERT INTO src_unicode VALUES " + "('2024-01-01 00:00:01', 'symbols', '★☆♠♣♥♦')" + ) + tdSql.execute( + "INSERT INTO src_unicode VALUES " + "('2024-01-01 00:00:02', 'mixed', 'Hello你好World世界')" + ) + + # Virtual table for Unicode + tdSql.execute( + f"CREATE VTABLE {cls.DB_NAME}.vtb_unicode (" + "ts TIMESTAMP, " + "bin_col BINARY(128) FROM src_unicode.bin_col, " + "nch_col NCHAR(128) FROM src_unicode.nch_col)" + ) + + # Source super table + tdSql.execute( + "CREATE STABLE src_stb (" + "ts TIMESTAMP, bin_col BINARY(64), val INT) " + "TAGS (region NCHAR(16))" + ) + tdSql.execute( + "CREATE TABLE src_ct1 USING src_stb TAGS ('east')" + ) + tdSql.execute( + "CREATE TABLE src_ct2 USING src_stb TAGS ('west')" + ) + tdSql.execute( + "INSERT INTO src_ct1 VALUES " + "('2024-01-01 00:00:00', 'East Region Data String', 10)" + ) + tdSql.execute( + "INSERT INTO src_ct2 VALUES " + "('2024-01-01 00:00:00', 'West Region Data String', 20)" + ) + + # Virtual super table + tdSql.execute( + "CREATE STABLE vstb (" + "ts TIMESTAMP, bin_col BINARY(64), val INT) " + "TAGS (region NCHAR(16)) VIRTUAL 1" + ) + tdSql.execute( + "CREATE VTABLE vct1 " + "(bin_col FROM src_ct1.bin_col, val FROM src_ct1.val) " + "USING vstb TAGS ('east')" + ) + tdSql.execute( + "CREATE VTABLE vct2 " + "(bin_col FROM src_ct2.bin_col, val FROM src_ct2.val) " + "USING vstb TAGS ('west')" + ) + + # ========== THREE KEY SCENARIOS FOR ACTUAL LENGTH NO TRUNCATION ========== + # Source table: BINARY(32), NCHAR(32) + # Scenario 1: vtable col > src col (BINARY(64) > BINARY(32)) + # Scenario 2: vtable col = src col (BINARY(32) = BINARY(32)) + # Scenario 3: vtable col < src col (BINARY(8) < BINARY(32)) - KEY: MUST NOT TRUNCATE + + # Source table for three-scenario tests + tdSql.execute( + "CREATE TABLE src_scenario (" + "ts TIMESTAMP, bin_col BINARY(32), nch_col NCHAR(32))" + ) + # Insert data with known lengths + tdSql.execute( + "INSERT INTO src_scenario VALUES " + "('2024-01-01 00:00:00', 'This is exactly 23 bytes!', '这是一段测试中文')" + ) + tdSql.execute( + "INSERT INTO src_scenario VALUES " + "('2024-01-01 00:00:01', 'short', '短')" + ) + tdSql.execute( + "INSERT INTO src_scenario VALUES " + "('2024-01-01 00:00:02', 'x', '中')" + ) + + # Virtual table GT: BINARY(64) > BINARY(32), NCHAR(64) > NCHAR(32) + tdSql.execute( + f"CREATE VTABLE {cls.DB_NAME}.vtb_scenario_gt (" + "ts TIMESTAMP, " + "bin_col BINARY(64) FROM src_scenario.bin_col, " + "nch_col NCHAR(64) FROM src_scenario.nch_col)" + ) + + # Virtual table EQ: BINARY(32) = BINARY(32), NCHAR(32) = NCHAR(32) + tdSql.execute( + f"CREATE VTABLE {cls.DB_NAME}.vtb_scenario_eq (" + "ts TIMESTAMP, " + "bin_col BINARY(32) FROM src_scenario.bin_col, " + "nch_col NCHAR(32) FROM src_scenario.nch_col)" + ) + + # Virtual table LT: BINARY(8) < BINARY(32), NCHAR(8) < NCHAR(32) + # KEY SCENARIO: Despite smaller definition, MUST NOT truncate source data + tdSql.execute( + f"CREATE VTABLE {cls.DB_NAME}.vtb_scenario_lt (" + "ts TIMESTAMP, " + "bin_col BINARY(8) FROM src_scenario.bin_col, " + "nch_col NCHAR(8) FROM src_scenario.nch_col)" + ) + + + tdLog.info("=== Setup complete ===") + + # ===================== THREE KEY SCENARIOS ===================== + # These tests verify the core feature: virtual tables return source data's + # actual length regardless of virtual table column definition length. + # NO TRUNCATION should occur even when vtable col < src col. + + def test_scenario_gt_vtable_larger_than_source(self): + """Scenario 1: vtable col > src col (GT - Greater Than) + + Verify that when virtual table column is LARGER than source column, + the full source data is returned without any padding or truncation. + Setup: vtable BINARY(64) > src BINARY(32), vtable NCHAR(64) > src NCHAR(32) + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, scenario, gt, no_truncation + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: Scenario GT - vtable col > src col ===") + db = self.DB_NAME + + # Query virtual table with larger column definition + tdSql.query(f"SELECT bin_col, LENGTH(bin_col), nch_col, CHAR_LENGTH(nch_col) " + f"FROM {db}.vtb_scenario_gt ORDER BY ts;") + tdSql.checkRows(3) + + # Row 1: 'This is exactly 23 bytes!' = 23 bytes + tdSql.checkData(0, 0, 'This is exactly 23 bytes!') + tdSql.checkData(0, 1, 23) + tdSql.checkData(0, 2, '这是一段测试中文') + tdSql.checkData(0, 3, 8) # 8 Chinese characters + + # Row 2: 'short' = 5 bytes + tdSql.checkData(1, 0, 'short') + tdSql.checkData(1, 1, 5) + tdSql.checkData(1, 2, '短') + tdSql.checkData(1, 3, 1) + + # Row 3: 'x' = 1 byte + tdSql.checkData(2, 0, 'x') + tdSql.checkData(2, 1, 1) + tdSql.checkData(2, 2, '中') + tdSql.checkData(2, 3, 1) + + def test_scenario_eq_vtable_equal_source(self): + """Scenario 2: vtable col = src col (EQ - Equal) + + Verify that when virtual table column is EQUAL to source column, + the full source data is returned without any truncation. + Setup: vtable BINARY(32) = src BINARY(32), vtable NCHAR(32) = src NCHAR(32) + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, scenario, eq, no_truncation + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: Scenario EQ - vtable col = src col ===") + db = self.DB_NAME + + # Query virtual table with equal column definition + tdSql.query(f"SELECT bin_col, LENGTH(bin_col), nch_col, CHAR_LENGTH(nch_col) " + f"FROM {db}.vtb_scenario_eq ORDER BY ts;") + tdSql.checkRows(3) + + # Row 1: 'This is exactly 23 bytes!' = 23 bytes + tdSql.checkData(0, 0, 'This is exactly 23 bytes!') + tdSql.checkData(0, 1, 23) + tdSql.checkData(0, 2, '这是一段测试中文') + tdSql.checkData(0, 3, 8) + + # Row 2: 'short' = 5 bytes + tdSql.checkData(1, 0, 'short') + tdSql.checkData(1, 1, 5) + tdSql.checkData(1, 2, '短') + tdSql.checkData(1, 3, 1) + + # Row 3: 'x' = 1 byte + tdSql.checkData(2, 0, 'x') + tdSql.checkData(2, 1, 1) + tdSql.checkData(2, 2, '中') + tdSql.checkData(2, 3, 1) + + def test_scenario_lt_vtable_smaller_than_source(self): + """Scenario 3: vtable col < src col (LT - Less Than) - KEY SCENARIO + + This is the KEY test for the no-truncation feature. + Verify that when virtual table column is SMALLER than source column, + the full source data is STILL returned WITHOUT TRUNCATION. + Setup: vtable BINARY(8) < src BINARY(32), vtable NCHAR(8) < src NCHAR(32) + + Expected: Data longer than BINARY(8) must still be returned in full. + The 'This is exactly 23 bytes!' string (23 bytes) must NOT be truncated + to 8 bytes even though vtable defines BINARY(8). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, scenario, lt, no_truncation, key + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: Scenario LT - vtable col < src col (KEY: NO TRUNCATION) ===") + db = self.DB_NAME + + # Query virtual table with SMALLER column definition + # This is the critical test - data must NOT be truncated + tdSql.query(f"SELECT bin_col, LENGTH(bin_col), nch_col, CHAR_LENGTH(nch_col) " + f"FROM {db}.vtb_scenario_lt ORDER BY ts;") + tdSql.checkRows(3) + + # Row 1: 'This is exactly 23 bytes!' = 23 bytes + # KEY ASSERTION: Despite vtable BINARY(8), full 23-byte string is returned + tdSql.checkData(0, 0, 'This is exactly 23 bytes!') + tdSql.checkData(0, 1, 23) # NOT 8 - must be 23 + tdSql.checkData(0, 2, '这是一段测试中文') + tdSql.checkData(0, 3, 8) # NOT 8 limit - full 8 chars returned + + # Row 2: 'short' = 5 bytes (fits in BINARY(8)) + tdSql.checkData(1, 0, 'short') + tdSql.checkData(1, 1, 5) + tdSql.checkData(1, 2, '短') + tdSql.checkData(1, 3, 1) + + # Row 3: 'x' = 1 byte (fits in BINARY(8)) + tdSql.checkData(2, 0, 'x') + tdSql.checkData(2, 1, 1) + tdSql.checkData(2, 2, '中') + tdSql.checkData(2, 3, 1) + + def test_scenario_all_return_identical_data(self): + """Consistency: All three scenarios return identical data + + Verify that GT, EQ, and LT scenarios all return EXACTLY the same data. + This confirms the no-truncation feature works correctly across all cases. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, scenario, consistency, key + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: All scenarios return identical data ===") + db = self.DB_NAME + + # Get data from all three virtual tables + tdSql.query(f"SELECT bin_col, nch_col FROM {db}.vtb_scenario_gt ORDER BY ts;") + gt_data = [(tdSql.getData(i, 0), tdSql.getData(i, 1)) for i in range(3)] + + tdSql.query(f"SELECT bin_col, nch_col FROM {db}.vtb_scenario_eq ORDER BY ts;") + eq_data = [(tdSql.getData(i, 0), tdSql.getData(i, 1)) for i in range(3)] + + tdSql.query(f"SELECT bin_col, nch_col FROM {db}.vtb_scenario_lt ORDER BY ts;") + lt_data = [(tdSql.getData(i, 0), tdSql.getData(i, 1)) for i in range(3)] + + # All three must be identical + assert gt_data == eq_data == lt_data, \ + f"Data mismatch! GT={gt_data}, EQ={eq_data}, LT={lt_data}" + + # Also compare with source table + tdSql.query(f"SELECT bin_col, nch_col FROM {db}.src_scenario ORDER BY ts;") + src_data = [(tdSql.getData(i, 0), tdSql.getData(i, 1)) for i in range(3)] + + assert gt_data == src_data, \ + f"GT vtable differs from source! GT={gt_data}, SRC={src_data}" + + tdLog.info("=== Verified: All scenarios return identical data to source ===") + + # ===================== EDGE CASE TESTS ===================== + + def test_edge_empty_string(self): + """Edge case: empty string handling + + Verify that empty strings are handled correctly in virtual tables. + Empty strings should have LENGTH=0 and CHAR_LENGTH=0. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, edge, empty_string + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: Empty string handling ===") + db = self.DB_NAME + + tdSql.query(f"SELECT bin_col, LENGTH(bin_col), nch_col, CHAR_LENGTH(nch_col) " + f"FROM {db}.vtb_edge WHERE ts = '2024-01-01 00:00:00';") + tdSql.checkRows(1) + tdSql.checkData(0, 0, '') + tdSql.checkData(0, 1, 0) + tdSql.checkData(0, 2, '') + tdSql.checkData(0, 3, 0) + + def test_edge_single_char(self): + """Edge case: single character handling + + Verify that single characters are handled correctly in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, edge, single_char + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: Single character handling ===") + db = self.DB_NAME + + tdSql.query(f"SELECT bin_col, LENGTH(bin_col), nch_col, CHAR_LENGTH(nch_col) " + f"FROM {db}.vtb_edge WHERE ts = '2024-01-01 00:00:01';") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 'a') + tdSql.checkData(0, 1, 1) + tdSql.checkData(0, 2, '中') + tdSql.checkData(0, 3, 1) + + def test_edge_null_value(self): + """Edge case: NULL value handling + + Verify that NULL values are handled correctly in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, edge, null + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: NULL value handling ===") + db = self.DB_NAME + + tdSql.query(f"SELECT bin_col, LENGTH(bin_col), nch_col, CHAR_LENGTH(nch_col) " + f"FROM {db}.vtb_edge WHERE ts = '2024-01-01 00:00:02';") + tdSql.checkRows(1) + tdSql.checkData(0, 0, None) + tdSql.checkData(0, 1, None) + tdSql.checkData(0, 2, None) + tdSql.checkData(0, 3, None) + + # ===================== UNICODE TESTS ===================== + + def test_unicode_emoji(self): + """Unicode: emoji characters + + Verify that emoji characters are handled correctly in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, unicode, emoji + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: Emoji characters ===") + db = self.DB_NAME + + tdSql.query(f"SELECT nch_col, CHAR_LENGTH(nch_col) " + f"FROM {db}.vtb_unicode WHERE bin_col = 'emoji_test';") + tdSql.checkRows(1) + tdSql.checkData(0, 0, '🎉🎊🎈🎁') + tdSql.checkData(0, 1, 4) + + def test_unicode_symbols(self): + """Unicode: special symbols + + Verify that special symbols are handled correctly in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, unicode, symbols + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: Special symbols ===") + db = self.DB_NAME + + tdSql.query(f"SELECT nch_col, CHAR_LENGTH(nch_col) " + f"FROM {db}.vtb_unicode WHERE bin_col = 'symbols';") + tdSql.checkRows(1) + tdSql.checkData(0, 0, '★☆♠♣♥♦') + tdSql.checkData(0, 1, 6) + + def test_unicode_mixed(self): + """Unicode: mixed ASCII and CJK characters + + Verify that mixed ASCII and CJK characters are handled correctly. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, unicode, mixed + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: Mixed ASCII and CJK ===") + db = self.DB_NAME + + tdSql.query(f"SELECT nch_col, CHAR_LENGTH(nch_col) " + f"FROM {db}.vtb_unicode WHERE bin_col = 'mixed';") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 'Hello你好World世界') + tdSql.checkData(0, 1, 14) + + # ===================== VARCHAR/VARBINARY TESTS ===================== + + def test_varchar_no_truncation(self): + """VARCHAR: no truncation in virtual table + + Verify that VARCHAR data is not truncated in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, varchar + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: VARCHAR no truncation ===") + db = self.DB_NAME + + tdSql.query(f"SELECT vc_col, LENGTH(vc_col) " + f"FROM {db}.vtb_basic WHERE vc_col IS NOT NULL ORDER BY ts;") + tdSql.checkRows(2) + tdSql.checkData(0, 0, 'VARCHAR Test') + tdSql.checkData(0, 1, 12) + + def test_varbinary_no_truncation(self): + """VARBINARY: no truncation in virtual table + + Verify that VARBINARY data is not truncated in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, varbinary + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: VARBINARY no truncation ===") + db = self.DB_NAME + + # VARBINARY data is stored as bytes, check it exists and has correct length + tdSql.query(f"SELECT vb_col, LENGTH(vb_col) " + f"FROM {db}.vtb_basic WHERE vb_col IS NOT NULL ORDER BY ts;") + tdSql.checkRows(2) + # First row has 'VARBINARY Test' = 14 bytes + tdSql.checkData(0, 1, 14) + + # ===================== VIRTUAL SUPER TABLE TESTS ===================== + + def test_vstb_data_consistency(self): + """Virtual super table: data consistency + + Verify that virtual super table returns correct data without truncation. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, super_table, consistency + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: Virtual super table data consistency ===") + db = self.DB_NAME + + # Query virtual super table + tdSql.query(f"SELECT bin_col, LENGTH(bin_col), val, region " + f"FROM {db}.vstb ORDER BY region;") + tdSql.checkRows(2) + + # Check east region + tdSql.checkData(0, 0, 'East Region Data String') + tdSql.checkData(0, 1, 23) + tdSql.checkData(0, 2, 10) + tdSql.checkData(0, 3, 'east') + + # Check west region + tdSql.checkData(1, 0, 'West Region Data String') + tdSql.checkData(1, 1, 23) + tdSql.checkData(1, 2, 20) + tdSql.checkData(1, 3, 'west') + + def test_vstb_aggregate(self): + """Virtual super table: aggregate functions + + Verify aggregate functions work correctly on virtual super tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, super_table, aggregate + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: Virtual super table aggregate ===") + db = self.DB_NAME + + tdSql.query(f"SELECT COUNT(*), COUNT(bin_col), SUM(val), " + f"FIRST(bin_col), LAST(bin_col) FROM {db}.vstb;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 2) + tdSql.checkData(0, 1, 2) + tdSql.checkData(0, 2, 30) # 10 + 20 + + # ===================== DATA CONSISTENCY TESTS ===================== + + def test_consistency_source_vs_vtable(self): + """Consistency: source table vs virtual table + + Verify that data is identical between source and virtual table. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, consistency + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: Data consistency source vs vtable ===") + db = self.DB_NAME + + # Query source table + tdSql.query(f"SELECT bin_col, nch_col FROM {db}.src_basic ORDER BY ts;") + src_data = [(tdSql.getData(i, 0), tdSql.getData(i, 1)) + for i in range(tdSql.queryRows)] + + # Query virtual table + tdSql.query(f"SELECT bin_col, nch_col FROM {db}.vtb_basic ORDER BY ts;") + vtb_data = [(tdSql.getData(i, 0), tdSql.getData(i, 1)) + for i in range(tdSql.queryRows)] + + assert src_data == vtb_data, \ + f"Data mismatch: src={src_data}, vtb={vtb_data}" + + # ===================== STRING FUNCTION COMBINATION TESTS ===================== + + def test_string_func_on_unicode(self): + """String function: operations on Unicode data + + Verify string functions work correctly on Unicode data. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, string, unicode + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: String functions on Unicode ===") + db = self.DB_NAME + + # SUBSTR on Unicode + tdSql.query(f"SELECT SUBSTR(nch_col, 1, 3) FROM {db}.vtb_unicode " + f"WHERE bin_col = 'mixed' ORDER BY ts;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 'Hel') + + def test_string_func_concat(self): + """String function: CONCAT with NCHAR/BINARY + + Verify CONCAT works correctly with NCHAR/BINARY columns. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, string, concat + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: CONCAT function ===") + db = self.DB_NAME + + tdSql.query(f"SELECT CONCAT(bin_col, '-suffix') FROM {db}.vtb_basic " + f"WHERE bin_col IS NOT NULL ORDER BY ts LIMIT 1;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 'Hello World - Test String-suffix') + + # ===================== NULL HANDLING COMBINATION TESTS ===================== + + def test_null_handling_with_functions(self): + """NULL handling: with string functions + + Verify string functions handle NULL values correctly. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, null, string + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: NULL handling with string functions ===") + db = self.DB_NAME + + # String function on NULL should return NULL + tdSql.query(f"SELECT LOWER(bin_col) FROM {db}.vtb_basic ORDER BY ts;") + tdSql.checkRows(3) + tdSql.checkData(2, 0, None) # Third row is NULL + + def test_null_handling_aggregate(self): + """NULL handling: with aggregate functions + + Verify aggregate functions handle NULL values correctly. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, null, aggregate + + Jira: None + + History: + - 2026-2-28 Created + """ + tdLog.info("=== Test: NULL handling with aggregate ===") + db = self.DB_NAME + + # COUNT should exclude NULLs + tdSql.query(f"SELECT COUNT(*), COUNT(bin_col), COUNT(nch_col) " + f"FROM {db}.vtb_basic;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 3) # COUNT(*) includes all rows + tdSql.checkData(0, 1, 2) # COUNT(bin_col) excludes NULL + tdSql.checkData(0, 2, 2) # COUNT(nch_col) excludes NULL diff --git a/test/cases/05-VirtualTables/test_vtable_performance.py b/test/cases/05-VirtualTables/test_vtable_performance.py new file mode 100644 index 000000000000..09b32f5cc524 --- /dev/null +++ b/test/cases/05-VirtualTables/test_vtable_performance.py @@ -0,0 +1,570 @@ +################################################################### +# Copyright (c) 2016 by TAOS Technologies, Inc. +# All rights reserved. +# +# This file is proprietary and confidential to TAOS Technologies. +# No part of this file may be reproduced, stored, transmitted, +# disclosed or used in any form or by any means other than as +# expressly provided by the written permission from Jianhui Tao +# +################################################################### + +# -*- coding: utf-8 -*- +from new_test_framework.utils import tdLog, tdSql, etool, tdCom + + +class TestVtablePerformance: + + def setup_class(cls): + tdLog.info(f"prepare databases and tables for performance testing.") + + tdSql.execute("drop database if exists test_vtable_perf;") + tdSql.execute("drop database if exists test_vtable_perf_cross;") + tdSql.execute("create database test_vtable_perf vgroups 2;") + tdSql.execute("use test_vtable_perf;") + tdSql.execute("create database test_vtable_perf_cross vgroups 1;") + + # large dataset source table (10K rows) + tdLog.info(f"prepare large dataset source table.") + tdSql.execute("CREATE TABLE `src_large` (" + "ts timestamp, " + "int_col int, " + "bigint_col bigint, " + "float_col float, " + "double_col double, " + "binary_col binary(64))") + + base_ts = 1700000000000 + for batch in range(0, 10000, 1000): + sql = "INSERT INTO src_large VALUES " + values = [] + for i in range(batch, min(batch + 1000, 10000)): + values.append(f"({base_ts + i}, {i}, {i * 1000}, {i * 0.1}, " + f"{i * 0.01}, 'data_{i}')") + sql += ", ".join(values) + tdSql.execute(sql) + + tdSql.execute("CREATE VTABLE `vtb_large` (" + "ts timestamp, " + "v_int int from src_large.int_col, " + "v_bigint bigint from src_large.bigint_col, " + "v_float float from src_large.float_col, " + "v_double double from src_large.double_col, " + "v_binary binary(64) from src_large.binary_col)") + + # wide source table (100 columns, 100 rows) + tdLog.info(f"prepare wide source table (100 columns).") + sql = "CREATE TABLE `src_wide` (ts timestamp" + for i in range(100): + sql += f", col_{i} double" + sql += ")" + tdSql.execute(sql) + + for row in range(100): + ts_val = 1700000000000 + row * 1000 + sql = f"INSERT INTO src_wide VALUES ({ts_val}" + for i in range(100): + sql += f", {(i + row) * 0.1}" + sql += ")" + tdSql.execute(sql) + + sql = "CREATE VTABLE `vtb_wide_100` (ts timestamp" + for i in range(100): + sql += f", v_col_{i} double from src_wide.col_{i}" + sql += ")" + tdSql.execute(sql) + + # wide source table (500 columns, 1 row) + tdLog.info(f"prepare wide source table (500 columns).") + sql = "CREATE TABLE `src_wide_500` (ts timestamp" + for i in range(500): + sql += f", col_{i} double" + sql += ")" + tdSql.execute(sql) + + sql = f"INSERT INTO src_wide_500 VALUES (1700000000000" + for i in range(500): + sql += f", {i * 0.1}" + sql += ")" + tdSql.execute(sql) + + sql = "CREATE VTABLE `vtb_wide_500` (ts timestamp" + for i in range(500): + sql += f", v_col_{i} double from src_wide_500.col_{i}" + sql += ")" + tdSql.execute(sql) + + # cross-database source table (10K rows) + tdLog.info(f"prepare cross-database source table.") + tdSql.execute("use test_vtable_perf_cross;") + tdSql.execute("CREATE TABLE `cross_src` (" + "ts timestamp, " + "val int, " + "name binary(32))") + + for batch in range(0, 10000, 1000): + sql = "INSERT INTO cross_src VALUES " + values = [] + for i in range(batch, min(batch + 1000, 10000)): + values.append(f"({base_ts + i}, {i}, 'cross_{i}')") + sql += ", ".join(values) + tdSql.execute(sql) + + tdSql.execute("use test_vtable_perf;") + tdSql.execute("CREATE VTABLE `vtb_cross_db` (" + "ts timestamp, " + "v_val int from test_vtable_perf_cross.cross_src.val, " + "v_name binary(32) from test_vtable_perf_cross.cross_src.name)") + + # string source table (1K rows) + tdLog.info(f"prepare string source table.") + tdSql.execute("CREATE TABLE `src_str` (" + "ts timestamp, " + "nchar_col nchar(128), " + "binary_col binary(128))") + + sql = "INSERT INTO src_str VALUES " + values = [] + for i in range(1000): + values.append(f"({base_ts + i}, '中文字符串_{i}', 'binary_string_{i}')") + sql += ", ".join(values) + tdSql.execute(sql) + + tdSql.execute("CREATE VTABLE `vtb_str` (" + "ts timestamp, " + "v_nchar nchar(128) from src_str.nchar_col, " + "v_binary binary(128) from src_str.binary_col)") + + # interval source table (10K rows, 1s apart) + tdLog.info(f"prepare interval source table.") + tdSql.execute("CREATE TABLE `src_interval` (" + "ts timestamp, " + "val int, " + "dval double)") + + for batch in range(0, 10000, 1000): + sql = "INSERT INTO src_interval VALUES " + values = [] + for i in range(batch, min(batch + 1000, 10000)): + values.append(f"({base_ts + i * 1000}, {i}, {i * 0.1})") + sql += ", ".join(values) + tdSql.execute(sql) + + tdSql.execute("CREATE VTABLE `vtb_interval` (" + "ts timestamp, " + "v_int int from src_interval.val, " + "v_double double from src_interval.dval)") + + # mixed reference source tables (5 tables, 1K rows each) + tdLog.info(f"prepare mixed reference source tables.") + for t in range(5): + tdSql.execute(f"CREATE TABLE `src_mix_{t}` (" + "ts timestamp, " + f"value_{t} double, " + f"name_{t} binary(32))") + + sql = f"INSERT INTO src_mix_{t} VALUES " + values = [] + for i in range(1000): + values.append(f"({base_ts + i}, {i * 0.1 * (t + 1)}, 'name_{t}_{i}')") + sql += ", ".join(values) + tdSql.execute(sql) + + tdSql.execute("CREATE VTABLE `vtb_mixed` (" + "ts timestamp, " + "v0 double from src_mix_0.value_0, " + "v1 double from src_mix_1.value_1, " + "v2 double from src_mix_2.value_2, " + "v3 double from src_mix_3.value_3, " + "v4 double from src_mix_4.value_4)") + + # tag reference source tables + tdLog.info(f"prepare tag reference source tables.") + tdSql.execute("CREATE STABLE `src_tag_stb` (" + "ts timestamp, " + "val double) TAGS (" + "t_id int, " + "t_name binary(32))") + + for i in range(10): + tdSql.execute(f"CREATE TABLE `src_ctb_{i}` USING src_tag_stb " + f"TAGS ({i}, 'device_{i}')") + + sql = f"INSERT INTO src_ctb_{i} VALUES " + values = [] + for j in range(1000): + values.append(f"({base_ts + j}, {i * 100 + j * 0.1})") + sql += ", ".join(values) + tdSql.execute(sql) + + tdSql.execute("CREATE STABLE `vtb_tag_stb` (" + "ts timestamp, " + "v_val double) TAGS (" + "vt_id int, " + "vt_name binary(32)) VIRTUAL 1") + + for i in range(10): + tdSql.execute(f"CREATE VTABLE `vctb_{i}` (" + f"v_val from src_ctb_{i}.val) " + f"USING vtb_tag_stb TAGS ({i}, 'device_{i}')") + + def test_perf_large_dataset_basic_query(self): + """Performance: basic query on 10K-row virtual table + + 1. COUNT on 10K rows + 2. projection with LIMIT + 3. time range filter + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, performance + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test large dataset basic query performance.") + tdSql.execute("use test_vtable_perf;") + + tdSql.query("SELECT COUNT(*) FROM vtb_large;") + tdSql.checkData(0, 0, 10000) + + tdSql.query("SELECT ts, v_int, v_binary FROM vtb_large LIMIT 1000;") + tdSql.checkRows(1000) + + tdSql.query("SELECT * FROM vtb_large WHERE ts >= 1700000000000 AND ts < 1700000001000;") + tdSql.checkRows(1000) + + def test_perf_large_dataset_aggregation(self): + """Performance: aggregation queries on 10K-row virtual table + + 1. SUM aggregation + 2. AVG aggregation + 3. MIN/MAX aggregation + 4. FIRST/LAST aggregation + 5. GROUP BY aggregation + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, performance + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test large dataset aggregation performance.") + tdSql.execute("use test_vtable_perf;") + + tdSql.query("SELECT SUM(v_int), SUM(v_bigint), SUM(v_double) FROM vtb_large;") + tdSql.checkRows(1) + + tdSql.query("SELECT AVG(v_int), AVG(v_float), AVG(v_double) FROM vtb_large;") + tdSql.checkRows(1) + + tdSql.query("SELECT MIN(v_int), MAX(v_int), MIN(v_double), MAX(v_double) FROM vtb_large;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 0) + tdSql.checkData(0, 1, 9999) + + tdSql.query("SELECT FIRST(v_int), LAST(v_int), FIRST(v_binary), LAST(v_binary) FROM vtb_large;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 0) + tdSql.checkData(0, 1, 9999) + + tdSql.query("SELECT v_int % 100 as grp, COUNT(*), AVG(v_double) " + "FROM vtb_large GROUP BY v_int % 100 ORDER BY grp;") + tdSql.checkRows(100) + + def test_perf_large_dataset_filter(self): + """Performance: filter queries on 10K-row virtual table + + 1. equality filter + 2. range filter + 3. LIKE filter on string column + 4. combined numeric filter + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, performance + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test large dataset filter performance.") + tdSql.execute("use test_vtable_perf;") + + tdSql.query("SELECT * FROM vtb_large WHERE v_int = 5000;") + tdSql.checkRows(1) + + tdSql.query("SELECT * FROM vtb_large WHERE v_int >= 1000 AND v_int < 2000;") + tdSql.checkRows(1000) + + tdSql.query("SELECT * FROM vtb_large WHERE v_binary LIKE 'data_1%';") + tdSql.checkRows(1111) + + tdSql.query("SELECT * FROM vtb_large WHERE v_int > 5000 AND v_double > 50.0;") + tdSql.checkRows(4999) + + def test_perf_wide_table_100_columns(self): + """Performance: query on 100-column virtual table + + 1. SELECT * full projection + 2. partial projection (10 columns) + 3. aggregation on 10 columns + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, performance + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test wide table 100 columns performance.") + tdSql.execute("use test_vtable_perf;") + + tdSql.query("SELECT * FROM vtb_wide_100;") + tdSql.checkCols(101) + tdSql.checkRows(100) + + col_list = ", ".join([f"v_col_{i}" for i in range(10)]) + tdSql.query(f"SELECT ts, {col_list} FROM vtb_wide_100;") + tdSql.checkCols(11) + tdSql.checkRows(100) + + agg_list = ", ".join([f"SUM(v_col_{i})" for i in range(10)]) + tdSql.query(f"SELECT {agg_list} FROM vtb_wide_100;") + tdSql.checkRows(1) + + def test_perf_wide_table_500_columns(self): + """Performance: query on 500-column virtual table + + 1. SELECT * full projection + 2. partial projection (50 columns) + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, performance + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test wide table 500 columns performance.") + tdSql.execute("use test_vtable_perf;") + + tdSql.query("SELECT * FROM vtb_wide_500;") + tdSql.checkCols(501) + tdSql.checkRows(1) + + col_list = ", ".join([f"v_col_{i}" for i in range(50)]) + tdSql.query(f"SELECT ts, {col_list} FROM vtb_wide_500;") + tdSql.checkCols(51) + tdSql.checkRows(1) + + def test_perf_cross_database_query(self): + """Performance: cross-database reference query + + 1. cross-db count + 2. cross-db aggregation + 3. cross-db filter + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, performance + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test cross-database query performance.") + tdSql.execute("use test_vtable_perf;") + + tdSql.query("SELECT COUNT(*) FROM vtb_cross_db;") + tdSql.checkData(0, 0, 10000) + + tdSql.query("SELECT SUM(v_val), AVG(v_val), MIN(v_val), MAX(v_val) FROM vtb_cross_db;") + tdSql.checkRows(1) + tdSql.checkData(0, 2, 0) + tdSql.checkData(0, 3, 9999) + + tdSql.query("SELECT * FROM vtb_cross_db WHERE v_val >= 5000 AND v_val < 6000;") + tdSql.checkRows(1000) + + def test_perf_query_stability(self): + """Performance: repeated query stability + + Execute the same aggregation query 10 times on 10K-row vtable, + verify results are consistent across iterations. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, performance + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test query stability with repeated execution.") + tdSql.execute("use test_vtable_perf;") + + for i in range(10): + tdSql.query("SELECT COUNT(*), AVG(v_int), AVG(v_double) FROM vtb_large;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 10000) + + def test_perf_string_functions(self): + """Performance: string function queries on virtual table + + 1. length / char_length + 2. lower / upper + 3. concat + 4. substring + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, performance + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test string function performance.") + tdSql.execute("use test_vtable_perf;") + + tdSql.query("SELECT length(v_binary), char_length(v_nchar) FROM vtb_str;") + tdSql.checkRows(1000) + + tdSql.query("SELECT lower(v_binary), upper(v_binary) FROM vtb_str;") + tdSql.checkRows(1000) + + tdSql.query("SELECT concat(v_binary, '_suffix') FROM vtb_str;") + tdSql.checkRows(1000) + + tdSql.query("SELECT substring(v_binary, 1, 10) FROM vtb_str;") + tdSql.checkRows(1000) + + def test_perf_interval_query(self): + """Performance: interval (time window) query on virtual table + + 1. 1-second interval windows + 2. 10-second interval windows + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, performance + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test interval query performance.") + tdSql.execute("use test_vtable_perf;") + + tdSql.query("SELECT _wstart, COUNT(*), AVG(v_int), AVG(v_double) " + "FROM vtb_interval " + "WHERE ts >= 1700000000000 AND ts < 1700001000000 " + "INTERVAL(1s) FILL(NULL) " + "ORDER BY _wstart;") + tdSql.checkRows(1000) + + tdSql.query("SELECT _wstart, COUNT(*), SUM(v_int) " + "FROM vtb_interval " + "WHERE ts >= 1700000000000 AND ts < 1700010000000 " + "INTERVAL(10s) " + "ORDER BY _wstart;") + tdSql.checkRows(1000) + + def test_perf_mixed_column_references(self): + """Performance: virtual table with columns from 5 different source tables + + 1. full projection + 2. aggregation across all referenced columns + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, performance + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test mixed column references performance.") + tdSql.execute("use test_vtable_perf;") + + tdSql.query("SELECT * FROM vtb_mixed;") + tdSql.checkRows(1000) + tdSql.checkCols(6) + + tdSql.query("SELECT AVG(v0), AVG(v1), AVG(v2), AVG(v3), AVG(v4) FROM vtb_mixed;") + tdSql.checkRows(1) + + def test_perf_tag_reference_query(self): + """Performance: virtual child table and super table queries + + 1. single virtual child table query + 2. virtual super table aggregation (all children) + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, performance + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test tag reference query performance.") + tdSql.execute("use test_vtable_perf;") + + tdSql.query("SELECT COUNT(*), AVG(v_val) FROM vctb_5;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 1000) + + tdSql.query("SELECT COUNT(*), SUM(v_val) FROM vtb_tag_stb;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 10000) diff --git a/test/cases/05-VirtualTables/test_vtable_query_comprehensive.py b/test/cases/05-VirtualTables/test_vtable_query_comprehensive.py new file mode 100644 index 000000000000..9e9f18c8c32d --- /dev/null +++ b/test/cases/05-VirtualTables/test_vtable_query_comprehensive.py @@ -0,0 +1,2026 @@ +################################################################### +# Copyright (c) 2016 by TAOS Technologies, Inc. +# All rights reserved. +# +# This file is proprietary and confidential to TAOS Technologies. +# No part of this file may be reproduced, stored, transmitted, +# disclosed or used in any form or by any means other than as +# expressly provided by the written permission from Jianhui Tao +# +################################################################### + +# -*- coding: utf-8 -*- + +"""Comprehensive query tests for virtual tables with col len < ref col len. + +This test suite covers: + - Aggregate functions (count, sum, avg, min, max, spread, stddev, hyperloglog) + - Selection functions (first, last, last_row, top, bottom, sample) + - String functions (lower, upper, concat, concat_ws, substr, replace, ltrim, rtrim, + length, char_length) + - Math / conversion functions (cast, abs, ceil, floor, round, arithmetic) + - Subqueries (scalar, table, nested, with string func, with agg, with window, with join, with union) + - JOIN between vtable and regular table, vtable and vtable + - UNION / UNION ALL + - GROUP BY / PARTITION BY + - Window functions (INTERVAL, INTERVAL FILL, STATE_WINDOW, SESSION) + - WHERE filters (=, <>, LIKE, IN, BETWEEN, IS NULL, IS NOT NULL, combined) + - ORDER BY / LIMIT / OFFSET + - DISTINCT / CASE WHEN + - Virtual super table queries (SELECT *, aggregate, interval, partition by, subquery) + - Edge cases (SELECT *, NULL handling, arithmetic, consistency with source) + +All tests use a virtual table whose BINARY/NCHAR column lengths are +*smaller* than the referenced source columns, verifying that the +TMAX(vtb_bytes, ref_bytes) fix in scanAddCol works end-to-end. +""" + +from new_test_framework.utils import tdLog, tdSql, etool, tdCom + + +class TestVtableQueryComprehensive: + + DB = "test_vtb_query" + + # ---------- expected source data (src_ntb: binary(32), nchar(32)) -------- + TS = [ + "2024-01-01 00:00:00.000", + "2024-01-01 00:00:01.000", + "2024-01-01 00:00:02.000", + "2024-01-01 00:00:03.000", + "2024-01-01 00:00:04.000", + "2024-01-01 00:00:05.000", + "2024-01-01 00:00:06.000", + "2024-01-01 00:00:07.000", + "2024-01-01 00:00:08.000", + "2024-01-01 00:00:09.000", + ] + BIN = [ + "Shanghai", # 8 chars (was: "Shanghai - Los Angeles" 22 chars) + "short", # 5 chars + "PaloAlto", # 8 chars (was: "Palo Alto - Mountain View" 25 chars) + None, + "SanFran", # 7 chars (was: "San Francisco - Cupertino" 25 chars) + "Beijing", # 7 chars (was: "Beijing - Shenzhen City" 23 chars) + "a", # 1 char + None, + "Hangzho", # 7 chars (was: "Hangzhou - West Lake Area" 25 chars) + "SV", # 2 chars + ] + NCH = [ + "圣克拉拉", # 4 chars (was: "圣克拉拉 - Santa Clara") + "短", # 1 char + "库比蒂诺", # 4 chars (was: "库比蒂诺 - Cupertino City") + None, + "旧金山", # 3 chars (was: "旧金山 - San Francisco") + "北京市海", # 4 chars (was: "北京市海淀区中关村大街") + "a", # 1 char + None, + "杭州西湖", # 4 chars (was: "杭州西湖风景区龙井路") + "深", # 1 char + ] + IVAL = [10, 20, 30, None, 50, 60, 70, None, 90, 100] + FVAL = [1.1, 2.2, 3.3, None, 5.5, 6.6, 7.7, None, 9.9, 10.0] + + # helper: non-null values + BIN_NN = [v for v in BIN if v is not None] + NCH_NN = [v for v in NCH if v is not None] + IVAL_NN = [v for v in IVAL if v is not None] + FVAL_NN = [v for v in FVAL if v is not None] + + # ------------------------------------------------------------------ setup + def setup_class(cls): + tdLog.info("=== setup: create DB, source table, virtual tables ===") + db = cls.DB + tdSql.execute(f"DROP DATABASE IF EXISTS {db};") + tdSql.execute(f"CREATE DATABASE {db} VGROUPS 2;") + tdSql.execute(f"USE {db};") + + # --- source normal table: binary(32), nchar(32), int, float -------- + tdSql.execute( + "CREATE TABLE src_ntb (" + "ts TIMESTAMP, bin_col BINARY(32), nch_col NCHAR(32), " + "ival INT, fval FLOAT);") + + for i in range(len(cls.TS)): + b = f"'{cls.BIN[i]}'" if cls.BIN[i] is not None else "NULL" + n = f"'{cls.NCH[i]}'" if cls.NCH[i] is not None else "NULL" + iv = str(cls.IVAL[i]) if cls.IVAL[i] is not None else "NULL" + fv = str(cls.FVAL[i]) if cls.FVAL[i] is not None else "NULL" + tdSql.execute( + f"INSERT INTO src_ntb VALUES ('{cls.TS[i]}', {b}, {n}, {iv}, {fv});") + + # --- virtual table: BINARY(8)/NCHAR(8) < source BINARY(32)/NCHAR(32) + tdSql.execute( + "CREATE VTABLE vtb_lt (" + "ts TIMESTAMP, " + "bin_col BINARY(8) FROM src_ntb.bin_col, " + "nch_col NCHAR(8) FROM src_ntb.nch_col, " + "ival INT FROM src_ntb.ival, " + "fval FLOAT FROM src_ntb.fval);") + + # --- a second source table for JOIN tests -------------------------- + tdSql.execute( + "CREATE TABLE src_dim (" + "ts TIMESTAMP, city BINARY(32), code INT);") + tdSql.execute("INSERT INTO src_dim VALUES " + "('2024-01-01 00:00:00.000', 'Shanghai', 10);") + tdSql.execute("INSERT INTO src_dim VALUES " + "('2024-01-01 00:00:01.000', 'Palo Alto', 20);") + tdSql.execute("INSERT INTO src_dim VALUES " + "('2024-01-01 00:00:02.000', 'Beijing', 30);") + + # virtual table on src_dim (col len < ref len) + tdSql.execute( + "CREATE VTABLE vtb_dim (" + "ts TIMESTAMP, city BINARY(8) FROM src_dim.city, " + "code INT FROM src_dim.code);") + + # --- super table + child tables for partition / group by tests ----- + tdSql.execute( + "CREATE STABLE src_stb (" + "ts TIMESTAMP, bin_col BINARY(32), ival INT) " + "TAGS (region NCHAR(16));") + tdSql.execute("CREATE TABLE src_ct1 USING src_stb TAGS ('east');") + tdSql.execute("CREATE TABLE src_ct2 USING src_stb TAGS ('west');") + for i in range(5): + b = f"'{cls.BIN[i]}'" if cls.BIN[i] is not None else "NULL" + iv = str(cls.IVAL[i]) if cls.IVAL[i] is not None else "NULL" + tdSql.execute( + f"INSERT INTO src_ct1 VALUES ('{cls.TS[i]}', {b}, {iv});") + for i in range(5, 10): + b = f"'{cls.BIN[i]}'" if cls.BIN[i] is not None else "NULL" + iv = str(cls.IVAL[i]) if cls.IVAL[i] is not None else "NULL" + tdSql.execute( + f"INSERT INTO src_ct2 VALUES ('{cls.TS[i]}', {b}, {iv});") + + # virtual super table + virtual child tables + tdSql.execute( + "CREATE STABLE vstb (" + "ts TIMESTAMP, bin_col BINARY(8), ival INT) " + "TAGS (region NCHAR(16)) VIRTUAL 1;") + tdSql.execute( + "CREATE VTABLE vct1 " + "(bin_col FROM src_ct1.bin_col, ival FROM src_ct1.ival) " + "USING vstb TAGS ('east');") + tdSql.execute( + "CREATE VTABLE vct2 " + "(bin_col FROM src_ct2.bin_col, ival FROM src_ct2.ival) " + "USING vstb TAGS ('west');") + + tdLog.info("=== setup complete ===") + + # ========================= AGGREGATE FUNCTIONS ========================== + + def test_agg_count(self): + """Aggregate: COUNT(*), COUNT(col) on vtable with col len < ref len + + Description: + Test COUNT(*) and COUNT(column) aggregate functions on virtual table. + + Validates that COUNT correctly counts all rows and non-NULL values + when the virtual table column length is smaller than the source. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, aggregate, count + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== agg: count ===") + tdSql.query(f"SELECT COUNT(*) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, 10) + + tdSql.query(f"SELECT COUNT(bin_col) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, 8) # 2 NULLs + + tdSql.query(f"SELECT COUNT(nch_col) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, 8) + + tdSql.query(f"SELECT COUNT(ival) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, 8) + + def test_agg_sum_avg(self): + """Aggregate: SUM, AVG on int/float columns of vtable + + Description: + Test SUM and AVG aggregate functions on virtual table. + + Validates that SUM and AVG correctly compute values on int and float + columns of virtual tables with smaller column lengths. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, aggregate, sum, avg + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== agg: sum / avg ===") + expected_sum = sum(self.IVAL_NN) + tdSql.query(f"SELECT SUM(ival) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, expected_sum) + + tdSql.query(f"SELECT AVG(ival) FROM {db}.vtb_lt;") + avg_val = tdSql.getData(0, 0) + assert abs(avg_val - expected_sum / len(self.IVAL_NN)) < 0.01, \ + f"avg mismatch: {avg_val}" + + tdSql.query(f"SELECT SUM(fval) FROM {db}.vtb_lt;") + fsum = tdSql.getData(0, 0) + expected_fsum = sum(self.FVAL_NN) + assert abs(fsum - expected_fsum) < 0.5, f"fval sum mismatch: {fsum}" + + def test_agg_min_max(self): + """Aggregate: MIN, MAX on int and binary columns of vtable + + Description: + Test MIN and MAX aggregate functions on virtual table. + + Validates that MIN and MAX correctly find minimum and maximum values + on both numeric and string columns of virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, aggregate, min, max + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== agg: min / max ===") + tdSql.query(f"SELECT MIN(ival) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, min(self.IVAL_NN)) + + tdSql.query(f"SELECT MAX(ival) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, max(self.IVAL_NN)) + + tdSql.query(f"SELECT MIN(bin_col) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, min(self.BIN_NN)) + + tdSql.query(f"SELECT MAX(bin_col) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, max(self.BIN_NN)) + + def test_agg_spread_stddev(self): + """Aggregate: SPREAD, STDDEV on vtable + + Description: + Test SPREAD and STDDEV aggregate functions on virtual table. + + Validates that SPREAD (max-min) and STDDEV (standard deviation) + work correctly on virtual table columns. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, aggregate, spread, stddev + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== agg: spread / stddev ===") + tdSql.query(f"SELECT SPREAD(ival) FROM {db}.vtb_lt;") + expected_spread = max(self.IVAL_NN) - min(self.IVAL_NN) + tdSql.checkData(0, 0, float(expected_spread)) + + tdSql.query(f"SELECT STDDEV(ival) FROM {db}.vtb_lt;") + stddev_val = tdSql.getData(0, 0) + assert stddev_val is not None and stddev_val > 0, \ + f"stddev should be positive: {stddev_val}" + + def test_agg_hyperloglog(self): + """Aggregate: HYPERLOGLOG on binary column of vtable + + Description: + Test HYPERLOGLOG approximate distinct count on virtual table. + + Validates that HYPERLOGLOG returns an approximate count of distinct + values for binary columns in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, aggregate, hyperloglog + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== agg: hyperloglog ===") + tdSql.query(f"SELECT HYPERLOGLOG(bin_col) FROM {db}.vtb_lt;") + hll_val = tdSql.getData(0, 0) + # 8 distinct non-null values; HLL is approximate + assert hll_val >= 6 and hll_val <= 10, \ + f"hyperloglog approximate count unexpected: {hll_val}" + + def test_agg_multi_func(self): + """Aggregate: multiple aggregate functions in single SELECT on vtable + + Description: + Test multiple aggregate functions in a single SELECT. + + Validates that combining COUNT, SUM, AVG, MIN, MAX, and SPREAD in + one query returns correct results on virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, aggregate, multi_func + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== agg: multi func in single query ===") + tdSql.query( + f"SELECT COUNT(*), SUM(ival), AVG(ival), MIN(ival), MAX(ival), " + f"SPREAD(ival) FROM {db}.vtb_lt;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 10) # count + tdSql.checkData(0, 1, sum(self.IVAL_NN)) # sum + tdSql.checkData(0, 3, min(self.IVAL_NN)) # min + tdSql.checkData(0, 4, max(self.IVAL_NN)) # max + + # ========================= SELECTION FUNCTIONS ========================== + + def test_sel_first_last(self): + """Selection: FIRST, LAST on vtable with col len < ref len + + Description: + Test FIRST and LAST selection functions on virtual table. + + Validates that FIRST returns the first value and LAST returns the + last value by timestamp for virtual table columns. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, selection, first, last + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== sel: first / last ===") + tdSql.query(f"SELECT FIRST(bin_col) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, self.BIN[0]) + + tdSql.query(f"SELECT LAST(bin_col) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, self.BIN[9]) + + tdSql.query(f"SELECT FIRST(nch_col) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, self.NCH[0]) + + tdSql.query(f"SELECT LAST(nch_col) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, self.NCH[9]) + + def test_sel_top_bottom(self): + """Selection: TOP, BOTTOM on int column of vtable + + Description: + Test TOP and BOTTOM selection functions on virtual table. + + Validates that TOP(k) returns the k largest values and BOTTOM(k) + returns the k smallest values from virtual table columns. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, selection, top, bottom + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== sel: top / bottom ===") + tdSql.query(f"SELECT TOP(ival, 3) FROM {db}.vtb_lt;") + tdSql.checkRows(3) + top3 = sorted(self.IVAL_NN, reverse=True)[:3] + results = sorted([tdSql.getData(i, 0) for i in range(3)], reverse=True) + assert results == top3, f"top3 mismatch: {results} vs {top3}" + + tdSql.query(f"SELECT BOTTOM(ival, 3) FROM {db}.vtb_lt;") + tdSql.checkRows(3) + bot3 = sorted(self.IVAL_NN)[:3] + results = sorted([tdSql.getData(i, 0) for i in range(3)]) + assert results == bot3, f"bottom3 mismatch: {results} vs {bot3}" + + def test_sel_last_row(self): + """Selection: LAST_ROW on vtable + + Description: + Test LAST_ROW selection function on virtual table. + + Validates that LAST_ROW returns the last row's value for virtual + table columns with smaller lengths than source. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, selection, last_row + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== sel: last_row ===") + tdSql.query(f"SELECT LAST_ROW(bin_col) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, self.BIN[9]) + + tdSql.query(f"SELECT LAST_ROW(nch_col) FROM {db}.vtb_lt;") + tdSql.checkData(0, 0, self.NCH[9]) + + def test_sel_sample(self): + """Selection: SAMPLE on binary column of vtable + + Description: + Test SAMPLE selection function on virtual table. + + Validates that SAMPLE(k) returns k randomly sampled values from + virtual table binary columns. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, selection, sample + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== sel: sample ===") + tdSql.query(f"SELECT SAMPLE(bin_col, 3) FROM {db}.vtb_lt;") + tdSql.checkRows(3) + for i in range(3): + val = tdSql.getData(i, 0) + assert val in self.BIN_NN, f"sample value '{val}' not in source data" + + # ========================= STRING FUNCTIONS ============================= + + def test_str_lower_upper(self): + """String: LOWER, UPPER on vtable binary col with col len < ref len + + Description: + Test LOWER and UPPER string functions on virtual table. + + Validates that LOWER converts to lowercase and UPPER converts to + uppercase for binary columns in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, string, lower, upper + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== str: lower / upper ===") + tdSql.query(f"SELECT LOWER(bin_col) FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + for i, val in enumerate(self.BIN): + tdSql.checkData(i, 0, val.lower() if val else None) + + tdSql.query(f"SELECT UPPER(bin_col) FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + for i, val in enumerate(self.BIN): + tdSql.checkData(i, 0, val.upper() if val else None) + + def test_str_concat_concat_ws(self): + """String: CONCAT, CONCAT_WS on vtable + + Description: + Test CONCAT and CONCAT_WS string functions on virtual table. + + Validates that string concatenation functions work correctly on + virtual table columns with smaller lengths than source. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, string, concat + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== str: concat / concat_ws ===") + tdSql.query( + f"SELECT CONCAT(bin_col, '-end') FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + for i, val in enumerate(self.BIN): + tdSql.checkData(i, 0, val + '-end' if val else None) + + tdSql.query( + f"SELECT CONCAT_WS('|', bin_col, 'x') FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + for i, val in enumerate(self.BIN): + tdSql.checkData(i, 0, val + '|x' if val else None) + + def test_str_substr(self): + """String: SUBSTR on vtable nchar col with col len < ref len + + Description: + Test SUBSTR string function on virtual table. + + Validates that SUBSTR extracts substrings correctly from nchar + columns in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, string, substr + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== str: substr ===") + tdSql.query( + f"SELECT SUBSTR(nch_col, 1, 3) FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + for i, val in enumerate(self.NCH): + tdSql.checkData(i, 0, val[:3] if val else None) + + def test_str_ltrim_rtrim(self): + """String: LTRIM, RTRIM on vtable + + Description: + Test LTRIM and RTRIM string functions on virtual table. + + Validates that LTRIM removes leading whitespace and RTRIM removes + trailing whitespace from virtual table binary columns. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, string, trim + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== str: ltrim / rtrim ===") + tdSql.query(f"SELECT LTRIM(bin_col) FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + for i, val in enumerate(self.BIN): + tdSql.checkData(i, 0, val.lstrip() if val else None) + + tdSql.query(f"SELECT RTRIM(bin_col) FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + for i, val in enumerate(self.BIN): + tdSql.checkData(i, 0, val.rstrip() if val else None) + + def test_str_length_char_length(self): + """String: LENGTH, CHAR_LENGTH on vtable + + Description: + Test LENGTH and CHAR_LENGTH string functions on virtual table. + + Validates that LENGTH and CHAR_LENGTH return correct string lengths + for binary and nchar columns in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, string, length + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== str: length / char_length ===") + tdSql.query(f"SELECT LENGTH(bin_col) FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + for i, val in enumerate(self.BIN): + tdSql.checkData(i, 0, len(val) if val else None) + + tdSql.query( + f"SELECT CHAR_LENGTH(nch_col) FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + for i, val in enumerate(self.NCH): + tdSql.checkData(i, 0, len(val) if val else None) + + def test_str_replace(self): + """String: REPLACE on vtable binary col + + Description: + Test REPLACE string function on virtual table. + + Validates that REPLACE correctly substitutes substrings in binary + columns of virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, string, replace + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== str: replace ===") + tdSql.query( + f"SELECT REPLACE(bin_col, 'a', 'A') FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + for i, val in enumerate(self.BIN): + tdSql.checkData(i, 0, val.replace('a', 'A') if val else None) + def test_str_ascii_position(self): + """String: ASCII, POSITION on vtable binary col + + Description: + Test ASCII and POSITION string functions on virtual table. + + Validates that ASCII returns character codes and POSITION finds + substring positions in virtual table binary columns. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, string, ascii, position + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== str: ascii / position ===") + tdSql.query( + f"SELECT ASCII(bin_col) FROM {db}.vtb_lt ORDER BY ts LIMIT 3;") + tdSql.checkRows(3) + # 'S'=83, 's'=115, 'P'=80 + tdSql.checkData(0, 0, 83) + tdSql.checkData(1, 0, 115) + tdSql.checkData(2, 0, 80) + + tdSql.query( + f"SELECT POSITION('short' IN bin_col) FROM {db}.vtb_lt " + f"ORDER BY ts LIMIT 3;") + tdSql.checkRows(3) + tdSql.checkData(0, 0, 0) # not found in 'Shanghai' + tdSql.checkData(1, 0, 1) # found at position 1 in 'short' + tdSql.checkData(2, 0, 0) # not found + + # ========================= MATH / CONVERSION ============================ + + def test_math_abs_ceil_floor_round(self): + """Math: ABS, CEIL, FLOOR, ROUND on vtable float col + + Description: + Test ABS, CEIL, FLOOR, ROUND math functions on virtual table. + + Validates that math functions work correctly on float columns in + virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, math + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== math: abs/ceil/floor/round ===") + tdSql.query(f"SELECT ABS(fval) FROM {db}.vtb_lt ORDER BY ts LIMIT 1;") + val = tdSql.getData(0, 0) + assert abs(val - 1.1) < 0.2, f"abs mismatch: {val}" + + tdSql.query( + f"SELECT CEIL(fval) FROM {db}.vtb_lt ORDER BY ts LIMIT 1;") + val = tdSql.getData(0, 0) + assert val == 2.0, f"ceil mismatch: {val}" + + tdSql.query( + f"SELECT FLOOR(fval) FROM {db}.vtb_lt ORDER BY ts LIMIT 1;") + val = tdSql.getData(0, 0) + assert val == 1.0, f"floor mismatch: {val}" + + tdSql.query( + f"SELECT ROUND(fval) FROM {db}.vtb_lt ORDER BY ts LIMIT 1;") + val = tdSql.getData(0, 0) + assert val == 1.0, f"round mismatch: {val}" + + def test_cast(self): + """Conversion: CAST int to binary, int to nchar on vtable + + Description: + Test CAST conversion function on virtual table. + + Validates that CAST correctly converts int columns to binary and + nchar types in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, cast + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== cast ===") + tdSql.query( + f"SELECT CAST(ival AS BINARY(16)) FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + tdSql.checkData(0, 0, '10') + tdSql.checkData(1, 0, '20') + + tdSql.query( + f"SELECT CAST(ival AS NCHAR(16)) FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + tdSql.checkData(0, 0, '10') + + def test_arithmetic(self): + """Math: arithmetic expressions on vtable int/float cols + + Description: + Test arithmetic expressions on virtual table. + + Validates that arithmetic operations (+, -, *, /) work correctly + on int and float columns of virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, arithmetic + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== arithmetic ===") + tdSql.query( + f"SELECT ival * 2 + 1 FROM {db}.vtb_lt ORDER BY ts LIMIT 1;") + tdSql.checkData(0, 0, 21) # 10*2+1 + + tdSql.query( + f"SELECT ival + fval FROM {db}.vtb_lt ORDER BY ts LIMIT 1;") + val = tdSql.getData(0, 0) + assert abs(val - 11.1) < 0.2, f"arithmetic mismatch: {val}" + + # ========================= WHERE FILTERS ================================ + + def test_where_eq_neq(self): + """Filter: = and <> on binary col of vtable with col len < ref len + + Description: + Test equality and inequality filters on virtual table. + + Validates that = and <> operators work correctly on binary columns + in virtual tables with smaller lengths than source. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, filter, where + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== where: = / <> ===") + tdSql.query(f"SELECT bin_col FROM {db}.vtb_lt WHERE bin_col = 'short';") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 'short') + + tdSql.query( + f"SELECT COUNT(*) FROM {db}.vtb_lt WHERE bin_col <> 'short';") + tdSql.checkData(0, 0, 7) # 8 non-null minus 1 + + def test_where_like(self): + """Filter: LIKE on binary col of vtable + + Description: + Test LIKE pattern matching filter on virtual table. + + Validates that LIKE with wildcards works correctly on binary columns + in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, filter, like + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== where: LIKE ===") + tdSql.query( + f"SELECT bin_col FROM {db}.vtb_lt " + f"WHERE bin_col LIKE 'San%' ORDER BY ts;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 'SanFran') + + def test_where_in(self): + """Filter: IN on binary col of vtable + + Description: + Test IN list filter on virtual table. + + Validates that IN operator correctly matches values in a list for + binary columns in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, filter, in + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== where: IN ===") + tdSql.query( + f"SELECT bin_col FROM {db}.vtb_lt " + f"WHERE bin_col IN ('short', 'a', 'SV') ORDER BY ts;") + tdSql.checkRows(3) + + def test_where_between(self): + """Filter: BETWEEN on int col of vtable + + Description: + Test BETWEEN range filter on virtual table. + + Validates that BETWEEN operator correctly filters numeric values + in virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, filter, between + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== where: BETWEEN ===") + tdSql.query( + f"SELECT ival FROM {db}.vtb_lt " + f"WHERE ival BETWEEN 20 AND 60 ORDER BY ts;") + expected = [v for v in self.IVAL if v is not None and 20 <= v <= 60] + tdSql.checkRows(len(expected)) + for i, v in enumerate(expected): + tdSql.checkData(i, 0, v) + + def test_where_is_null(self): + """Filter: IS NULL / IS NOT NULL on vtable + + Description: + Test IS NULL and IS NOT NULL filters on virtual table. + + Validates that NULL checks work correctly on virtual table columns + with smaller lengths than source. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, filter, null + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== where: IS NULL / IS NOT NULL ===") + tdSql.query( + f"SELECT COUNT(*) FROM {db}.vtb_lt WHERE bin_col IS NULL;") + tdSql.checkData(0, 0, 2) + + tdSql.query( + f"SELECT COUNT(*) FROM {db}.vtb_lt WHERE bin_col IS NOT NULL;") + tdSql.checkData(0, 0, 8) + + def test_where_combined(self): + """Filter: combined WHERE conditions on vtable + + Description: + Test combined AND/OR filter conditions on virtual table. + + Validates that multiple filter conditions combined with AND work + correctly on virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, filter, combined + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== where: combined ===") + tdSql.query( + f"SELECT bin_col, ival FROM {db}.vtb_lt " + f"WHERE ival > 30 AND bin_col IS NOT NULL ORDER BY ts;") + # ival>30 and bin_col not null: + # (50, San Fran), (60, Beijing), (70, a), (90, Hangzhou), (100, SV) + tdSql.checkRows(5) + + # ========================= ORDER BY / LIMIT / OFFSET ==================== + + def test_order_limit_offset(self): + """Query: ORDER BY, LIMIT, OFFSET on vtable + + Description: + Test ORDER BY, LIMIT, and OFFSET on virtual table. + + Validates that result ordering and pagination work correctly on + virtual tables with smaller column lengths. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, order, limit, offset + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== order / limit / offset ===") + tdSql.query( + f"SELECT bin_col FROM {db}.vtb_lt ORDER BY ts LIMIT 3;") + tdSql.checkRows(3) + tdSql.checkData(0, 0, self.BIN[0]) + tdSql.checkData(1, 0, self.BIN[1]) + tdSql.checkData(2, 0, self.BIN[2]) + + tdSql.query( + f"SELECT bin_col FROM {db}.vtb_lt ORDER BY ts LIMIT 3 OFFSET 2;") + tdSql.checkRows(3) + tdSql.checkData(0, 0, self.BIN[2]) + tdSql.checkData(1, 0, self.BIN[3]) + tdSql.checkData(2, 0, self.BIN[4]) + + # Order by int col descending + tdSql.query( + f"SELECT ival FROM {db}.vtb_lt WHERE ival IS NOT NULL " + f"ORDER BY ival DESC LIMIT 3;") + tdSql.checkRows(3) + tdSql.checkData(0, 0, 100) + tdSql.checkData(1, 0, 90) + tdSql.checkData(2, 0, 70) + + # ========================= DISTINCT / CASE WHEN ========================= + + def test_distinct(self): + """Query: DISTINCT on vtable binary col + + Description: + Test DISTINCT keyword on virtual table. + + Validates that DISTINCT correctly deduplicates values from virtual + table binary columns. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, distinct + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== distinct ===") + tdSql.query(f"SELECT DISTINCT bin_col FROM {db}.vtb_lt;") + # 8 distinct non-null + 1 NULL = 9 + rows = tdSql.queryRows + assert rows == 9, f"distinct rows: {rows}, expected 9" + + def test_case_when(self): + """Query: CASE WHEN on vtable + + Description: + Test CASE WHEN conditional expression on virtual table. + + Validates that CASE WHEN expressions evaluate correctly on virtual + table columns. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, case_when + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== case when ===") + tdSql.query( + f"SELECT CASE WHEN ival > 50 THEN 'high' " + f"WHEN ival > 0 THEN 'low' ELSE 'none' END AS cat " + f"FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + tdSql.checkData(0, 0, 'low') # ival=10 + tdSql.checkData(1, 0, 'low') # ival=20 + tdSql.checkData(3, 0, 'none') # ival=NULL + tdSql.checkData(4, 0, 'low') # ival=50 + tdSql.checkData(5, 0, 'high') # ival=60 + tdSql.checkData(9, 0, 'high') # ival=100 + + # ========================= SUBQUERIES =================================== + + def test_subquery_scalar(self): + """Subquery: scalar subquery with vtable + + Description: + Test scalar subquery on virtual table. + + Validates that scalar subqueries (returning a single value) work + correctly with virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, subquery, scalar + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== subquery: scalar ===") + tdSql.query( + f"SELECT bin_col FROM {db}.vtb_lt " + f"WHERE ival = (SELECT MAX(ival) FROM {db}.vtb_lt);") + tdSql.checkRows(1) + tdSql.checkData(0, 0, self.BIN[9]) # ival=100 -> 'SV' + + def test_subquery_table(self): + """Subquery: table subquery (FROM subquery) with vtable + + Description: + Test table subquery (FROM subquery) on virtual table. + + Validates that subqueries in FROM clause work correctly with + virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, subquery, table + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== subquery: table ===") + tdSql.query( + f"SELECT cnt FROM " + f"(SELECT COUNT(*) AS cnt FROM {db}.vtb_lt " + f"WHERE bin_col IS NOT NULL);") + tdSql.checkData(0, 0, 8) + + def test_subquery_nested(self): + """Subquery: nested subquery (3 levels) with vtable + + Description: + Test nested subqueries (3 levels) on virtual table. + + Validates that deeply nested subqueries work correctly with + virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, subquery, nested + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== subquery: nested 3 levels ===") + tdSql.query( + f"SELECT total FROM (" + f" SELECT SUM(len) AS total FROM (" + f" SELECT LENGTH(bin_col) AS len FROM {db}.vtb_lt " + f" WHERE bin_col IS NOT NULL" + f" )" + f");") + tdSql.checkRows(1) + expected = sum(len(v) for v in self.BIN_NN) + tdSql.checkData(0, 0, expected) + + def test_subquery_with_string_func(self): + """Subquery: string functions inside subquery on vtable + + Description: + Test string functions inside subquery on virtual table. + + Validates that string functions like LOWER work correctly inside + subqueries on virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, subquery, string + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== subquery: string functions inside subquery ===") + tdSql.query( + f"SELECT low_bin FROM (" + f" SELECT LOWER(bin_col) AS low_bin FROM {db}.vtb_lt " + f" WHERE bin_col IS NOT NULL ORDER BY ts" + f") LIMIT 3;") + tdSql.checkRows(3) + nn_bins = [v for v in self.BIN if v is not None] + tdSql.checkData(0, 0, nn_bins[0].lower()) + tdSql.checkData(1, 0, nn_bins[1].lower()) + tdSql.checkData(2, 0, nn_bins[2].lower()) + + def test_subquery_agg_filter(self): + """Subquery: aggregate in subquery, filter in outer on vtable + + Description: + Test aggregate in subquery with filter in outer query. + + Validates that using aggregate functions in subqueries with filters + in outer queries works on virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, subquery, aggregate + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== subquery: agg in sub, filter in outer ===") + tdSql.query( + f"SELECT bin_col, ival FROM {db}.vtb_lt " + f"WHERE ival > (SELECT AVG(ival) FROM {db}.vtb_lt) " + f"ORDER BY ts;") + avg_val = sum(self.IVAL_NN) / len(self.IVAL_NN) + expected = [(self.BIN[i], self.IVAL[i]) + for i in range(10) + if self.IVAL[i] is not None and self.IVAL[i] > avg_val] + tdSql.checkRows(len(expected)) + for idx, (b, iv) in enumerate(expected): + tdSql.checkData(idx, 0, b) + tdSql.checkData(idx, 1, iv) + + def test_subquery_with_window(self): + """Subquery: window function result used in outer query + + Description: + Test window function in subquery on virtual table. + + Validates that INTERVAL window results can be used in outer queries + on virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, subquery, window + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== subquery: window in subquery ===") + tdSql.query( + f"SELECT max_sum FROM (" + f" SELECT _wstart AS ws, SUM(ival) AS max_sum " + f" FROM {db}.vtb_lt INTERVAL(5s)" + f") ORDER BY max_sum DESC LIMIT 1;") + tdSql.checkRows(1) + # window1: 10+20+30+None+50=110; window2: 60+70+None+90+100=320 + tdSql.checkData(0, 0, 320) + + def test_subquery_with_join(self): + """Subquery: JOIN inside subquery + + Description: + Test JOIN inside subquery on virtual table. + + Validates that JOIN operations inside subqueries work correctly + with virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, subquery, join + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== subquery: join inside subquery ===") + tdSql.query( + f"SELECT cnt FROM (" + f" SELECT COUNT(*) AS cnt " + f" FROM {db}.vtb_lt a, {db}.src_dim b " + f" WHERE a.ts = b.ts" + f");") + tdSql.checkData(0, 0, 3) + + def test_subquery_with_union(self): + """Subquery: UNION ALL inside subquery + + Description: + Test UNION ALL inside subquery on virtual table. + + Validates that UNION ALL operations inside subqueries work correctly + with virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, subquery, union + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== subquery: union all inside subquery ===") + tdSql.query( + f"SELECT COUNT(*) FROM (" + f" SELECT bin_col FROM {db}.vtb_lt WHERE ival <= 30 " + f" UNION ALL " + f" SELECT bin_col FROM {db}.vtb_lt WHERE ival >= 90" + f");") + # ival<=30: rows 0,1,2 (3 rows); ival>=90: rows 8,9 (2 rows) -> 5 + tdSql.checkData(0, 0, 5) + + # ========================= JOIN ========================================= + + def test_join_vtable_with_regular(self): + """JOIN: vtable JOIN regular table on timestamp + + Description: + Test JOIN between virtual table and regular table. + + Validates that joining a virtual table with a regular table on + timestamp works correctly. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, join + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== join: vtable with regular table ===") + tdSql.query( + f"SELECT a.bin_col, b.city, a.ival, b.code " + f"FROM {db}.vtb_lt a, {db}.src_dim b " + f"WHERE a.ts = b.ts ORDER BY a.ts;") + tdSql.checkRows(3) + tdSql.checkData(0, 0, self.BIN[0]) + tdSql.checkData(0, 1, 'Shanghai') + tdSql.checkData(0, 2, self.IVAL[0]) + tdSql.checkData(0, 3, 10) + tdSql.checkData(1, 0, self.BIN[1]) + tdSql.checkData(1, 1, 'Palo Alto') + tdSql.checkData(2, 0, self.BIN[2]) + tdSql.checkData(2, 1, 'Beijing') + + def test_join_vtable_with_vtable(self): + """JOIN: two vtables joined on timestamp (expect error - not supported) + + Description: + Test JOIN between two virtual tables (negative case). + + Validates that joining two virtual tables returns an error as this + is not currently supported. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, join, negative + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== join: vtable with vtable (expect error) ===") + # vtable-vtable JOIN is not currently supported + tdSql.error( + f"SELECT a.bin_col, b.city " + f"FROM {db}.vtb_lt a, {db}.vtb_dim b " + f"WHERE a.ts = b.ts ORDER BY a.ts;") + + # ========================= UNION ======================================== + + def test_union_all(self): + """UNION ALL: combine results from vtable queries + + Description: + Test UNION ALL combining vtable query results. + + Validates that UNION ALL correctly combines results from multiple + virtual table queries without deduplication. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, union + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== union all ===") + tdSql.query( + f"SELECT bin_col FROM {db}.vtb_lt WHERE ival = 10 " + f"UNION ALL " + f"SELECT bin_col FROM {db}.vtb_lt WHERE ival = 20;") + tdSql.checkRows(2) + + def test_union_dedup(self): + """UNION: deduplicate results from vtable queries + + Description: + Test UNION deduplicating vtable query results. + + Validates that UNION correctly combines and deduplicates results + from multiple virtual table queries. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, union + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== union (dedup) ===") + tdSql.query( + f"SELECT bin_col FROM {db}.vtb_lt WHERE ival = 10 " + f"UNION " + f"SELECT bin_col FROM {db}.vtb_lt WHERE ival = 10;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, self.BIN[0]) + + # ========================= GROUP BY / PARTITION BY ====================== + + def test_group_by_with_agg(self): + """GROUP BY: aggregate with GROUP BY on vtable + + Description: + Test GROUP BY with aggregate on virtual table. + + Validates that GROUP BY clause with aggregate functions works + correctly on virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, group_by + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== group by with agg ===") + tdSql.query( + f"SELECT CASE WHEN ival <= 50 THEN 'low' ELSE 'high' END AS bucket, " + f"COUNT(*) AS cnt " + f"FROM {db}.vtb_lt WHERE ival IS NOT NULL " + f"GROUP BY CASE WHEN ival <= 50 THEN 'low' ELSE 'high' END " + f"ORDER BY bucket;") + tdSql.checkRows(2) + # 'high': 60,70,90,100 = 4; 'low': 10,20,30,50 = 4 + tdSql.checkData(0, 0, 'high') + tdSql.checkData(0, 1, 4) + tdSql.checkData(1, 0, 'low') + tdSql.checkData(1, 1, 4) + + def test_partition_by(self): + """PARTITION BY: on virtual super table + + Description: + Test PARTITION BY on virtual super table. + + Validates that PARTITION BY clause works correctly on virtual + super tables for data partitioning. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, partition_by + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== partition by on virtual super table ===") + tdSql.query( + f"SELECT region, COUNT(*) AS cnt " + f"FROM {db}.vstb PARTITION BY region ORDER BY region;") + tdSql.checkRows(2) + tdSql.checkData(0, 0, 'east') + tdSql.checkData(0, 1, 5) + tdSql.checkData(1, 0, 'west') + tdSql.checkData(1, 1, 5) + + # ========================= WINDOW FUNCTIONS ============================= + + def test_interval_window(self): + """Window: INTERVAL on vtable + + Description: + Test INTERVAL window function on virtual table. + + Validates that time-based windowing with INTERVAL works correctly + on virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, window, interval + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== window: interval ===") + # 10 rows spanning 10 seconds, interval 5s -> 2 windows + tdSql.query( + f"SELECT _wstart, COUNT(*), FIRST(bin_col) " + f"FROM {db}.vtb_lt INTERVAL(5s);") + tdSql.checkRows(2) + tdSql.checkData(0, 1, 5) + tdSql.checkData(1, 1, 5) + tdSql.checkData(0, 2, self.BIN[0]) + tdSql.checkData(1, 2, self.BIN[5]) + + def test_interval_fill(self): + """Window: INTERVAL with FILL on vtable (requires time range) + + Description: + Test INTERVAL with FILL on virtual table. + + Validates that INTERVAL window with FILL clause for missing values + works correctly on virtual tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, window, interval, fill + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== window: interval fill ===") + tdSql.query( + f"SELECT _wstart, SUM(ival) FROM {db}.vtb_lt " + f"WHERE ts >= '2024-01-01' AND ts < '2024-01-01 00:00:10.001' " + f"INTERVAL(2s) FILL(VALUE, 0);") + # 10 seconds / 2s = 5 windows + possibly 1 extra + rows = tdSql.queryRows + assert rows >= 5, f"interval fill should have >= 5 windows, got {rows}" + + def test_session_window(self): + """Window: SESSION on vtable + + Description: + Test SESSION window function on virtual table. + + Validates that session-based windowing works correctly on virtual + tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, window, session + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== window: session ===") + # All rows are 1s apart, session(ts, 2s) -> 1 window + tdSql.query( + f"SELECT _wstart, COUNT(*), FIRST(bin_col) " + f"FROM {db}.vtb_lt SESSION(ts, 2s);") + tdSql.checkRows(1) + tdSql.checkData(0, 1, 10) + tdSql.checkData(0, 2, self.BIN[0]) + + # STATE_WINDOW on virtual tables may cause taosd instability. + # Skipping STATE_WINDOW test until the underlying issue is resolved. + + # ========================= VIRTUAL SUPER TABLE QUERIES ================== + + def test_vstb_select_all(self): + """Query: SELECT * from virtual super table + + Description: + Test SELECT * on virtual super table. + + Validates that SELECT * returns all rows correctly from virtual + super tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, super_table + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== vstb: select all ===") + tdSql.query(f"SELECT * FROM {db}.vstb ORDER BY ts;") + tdSql.checkRows(10) + + def test_vstb_agg(self): + """Query: aggregate on virtual super table + + Description: + Test aggregate functions on virtual super table. + + Validates that aggregate functions like COUNT, FIRST, LAST work + correctly on virtual super tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, super_table, aggregate + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== vstb: aggregate ===") + tdSql.query(f"SELECT COUNT(*) FROM {db}.vstb;") + tdSql.checkData(0, 0, 10) + + tdSql.query(f"SELECT FIRST(bin_col) FROM {db}.vstb;") + tdSql.checkData(0, 0, self.BIN[0]) + + tdSql.query(f"SELECT LAST(bin_col) FROM {db}.vstb;") + tdSql.checkData(0, 0, self.BIN[9]) + + def test_vstb_interval(self): + """Query: INTERVAL on virtual super table + + Description: + Test INTERVAL window on virtual super table. + + Validates that INTERVAL window function works correctly on virtual + super tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, super_table, interval + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== vstb: interval ===") + tdSql.query( + f"SELECT _wstart, COUNT(*) FROM {db}.vstb INTERVAL(5s);") + tdSql.checkRows(2) + tdSql.checkData(0, 1, 5) + tdSql.checkData(1, 1, 5) + + def test_vstb_subquery(self): + """Query: subquery on virtual super table + + Description: + Test subquery on virtual super table. + + Validates that subqueries work correctly on virtual super tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, super_table, subquery + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== vstb: subquery ===") + tdSql.query( + f"SELECT total FROM (" + f" SELECT SUM(ival) AS total FROM {db}.vstb " + f" WHERE bin_col IS NOT NULL" + f");") + tdSql.checkRows(1) + expected = sum(v for i, v in enumerate(self.IVAL) + if v is not None and self.BIN[i] is not None) + tdSql.checkData(0, 0, expected) + + # ========================= VIRTUAL CHILD TABLE QUERIES =================== + + def test_vct_select_all(self): + """Query: SELECT * from individual virtual child tables + + Description: + Test SELECT * on virtual child tables. + + Validates that SELECT * returns correct data from individual + virtual child tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, child_table + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== vct: select all ===") + tdSql.query(f"SELECT * FROM {db}.vct1 ORDER BY ts;") + tdSql.checkRows(5) + tdSql.checkData(0, 1, self.BIN[0]) + tdSql.checkData(1, 1, self.BIN[1]) + tdSql.checkData(4, 1, self.BIN[4]) + + tdSql.query(f"SELECT * FROM {db}.vct2 ORDER BY ts;") + tdSql.checkRows(5) + tdSql.checkData(0, 1, self.BIN[5]) + tdSql.checkData(4, 1, self.BIN[9]) + + def test_vct_agg(self): + """Query: aggregate on individual virtual child tables + + Description: + Test aggregate functions on virtual child tables. + + Validates that COUNT and SUM work correctly on individual virtual + child tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, child_table, aggregate + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== vct: aggregate ===") + tdSql.query(f"SELECT COUNT(*) FROM {db}.vct1;") + tdSql.checkData(0, 0, 5) + + tdSql.query(f"SELECT SUM(ival) FROM {db}.vct1;") + expected_sum = sum(v for v in self.IVAL[:5] if v is not None) + tdSql.checkData(0, 0, expected_sum) + + tdSql.query(f"SELECT COUNT(*) FROM {db}.vct2;") + tdSql.checkData(0, 0, 5) + + tdSql.query(f"SELECT SUM(ival) FROM {db}.vct2;") + expected_sum2 = sum(v for v in self.IVAL[5:] if v is not None) + tdSql.checkData(0, 0, expected_sum2) + + def test_vct_string_func(self): + """Query: string functions on virtual child table + + Description: + Test string functions on virtual child tables. + + Validates that LOWER and LENGTH work correctly on individual + virtual child tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, child_table, string + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== vct: string func ===") + tdSql.query( + f"SELECT LOWER(bin_col) FROM {db}.vct1 ORDER BY ts LIMIT 2;") + tdSql.checkRows(2) + tdSql.checkData(0, 0, self.BIN[0].lower()) + tdSql.checkData(1, 0, self.BIN[1].lower()) + + tdSql.query( + f"SELECT LENGTH(bin_col) FROM {db}.vct2 ORDER BY ts LIMIT 2;") + tdSql.checkRows(2) + tdSql.checkData(0, 0, len(self.BIN[5])) + tdSql.checkData(1, 0, len(self.BIN[6])) + + # ========================= EDGE CASES =================================== + + def test_select_star(self): + """Verify SELECT * returns all columns correctly from vtable + + Description: + Test that SELECT * on a virtual table with column lengths smaller + than the referenced source columns returns all data correctly. + Validates that the TMAX(vtb_bytes, ref_bytes) fix works end-to-end. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, select_star + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== select * ===") + tdSql.query(f"SELECT * FROM {db}.vtb_lt ORDER BY ts;") + tdSql.checkRows(10) + for i in range(10): + tdSql.checkData(i, 1, self.BIN[i]) + tdSql.checkData(i, 2, self.NCH[i]) + tdSql.checkData(i, 3, self.IVAL[i]) + + def test_consistency_with_source(self): + """Verify data returned from vtable matches source table exactly + + Description: + Test that querying binary and nchar columns from the virtual table + returns identical data to querying the source table directly. + This validates the virtual table correctly references source data. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, consistency + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== consistency: vtable vs source ===") + # The vtb_lt results should be identical to querying src_ntb directly + tdSql.query( + f"SELECT bin_col, nch_col FROM {db}.vtb_lt ORDER BY ts;") + vtb_data = [(tdSql.getData(i, 0), tdSql.getData(i, 1)) + for i in range(10)] + + tdSql.query( + f"SELECT bin_col, nch_col FROM {db}.src_ntb ORDER BY ts;") + src_data = [(tdSql.getData(i, 0), tdSql.getData(i, 1)) + for i in range(10)] + + assert vtb_data == src_data, \ + f"vtb_lt data differs from src_ntb:\nvtb={vtb_data}\nsrc={src_data}" + + def test_count_consistency(self): + """Verify COUNT(*) returns same results from vtable and source table + + Description: + Test that COUNT(*) aggregate function returns consistent results + when querying the virtual table versus the source table directly. + This validates that the virtual table correctly references all rows. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, consistency, count + + Jira: None + + History: + - 2026-2-12 Created + """ + db = self.DB + tdLog.info("=== count consistency ===") + tdSql.query(f"SELECT COUNT(*) FROM {db}.vtb_lt;") + vtb_count = tdSql.getData(0, 0) + + tdSql.query(f"SELECT COUNT(*) FROM {db}.src_ntb;") + src_count = tdSql.getData(0, 0) + + assert vtb_count == src_count, \ + f"count mismatch: vtb={vtb_count}, src={src_count}" diff --git a/test/cases/05-VirtualTables/test_vtable_stress.py b/test/cases/05-VirtualTables/test_vtable_stress.py new file mode 100644 index 000000000000..6b3eb6b4c46d --- /dev/null +++ b/test/cases/05-VirtualTables/test_vtable_stress.py @@ -0,0 +1,461 @@ +################################################################### +# Copyright (c) 2016 by TAOS Technologies, Inc. +# All rights reserved. +# +# This file is proprietary and confidential to TAOS Technologies. +# No part of this file may be reproduced, stored, transmitted, +# disclosed or used in any form or by any means other than as +# expressly provided by the written permission from Jianhui Tao +# +################################################################### + +# -*- coding: utf-8 -*- +from new_test_framework.utils import tdLog, tdSql, etool, tdCom + + +class TestVtableStress: + + DB_NAME = "test_vtable_stress" + + def setup_class(cls): + tdLog.info(f"prepare databases for stress testing.") + + tdSql.execute(f"drop database if exists {cls.DB_NAME};") + tdSql.execute(f"create database {cls.DB_NAME} vgroups 2;") + tdSql.execute(f"use {cls.DB_NAME};") + + tdLog.info(f"prepare source table for create/drop cycles.") + tdSql.execute("CREATE TABLE `stress_src_cycle` (" + "ts timestamp, " + "val double)") + + sql = "INSERT INTO stress_src_cycle VALUES " + values = [f"({1700000000000 + i}, {i * 0.1})" for i in range(100)] + sql += ", ".join(values) + tdSql.execute(sql) + + tdLog.info(f"prepare source table for repeated queries (1K rows).") + tdSql.execute("CREATE TABLE `stress_src_query` (" + "ts timestamp, " + "val double, " + "data binary(64))") + + sql = "INSERT INTO stress_src_query VALUES " + values = [f"({1700000000000 + i}, {i * 0.1}, 'data_{i}')" for i in range(1000)] + sql += ", ".join(values) + tdSql.execute(sql) + + tdSql.execute("CREATE VTABLE `vtb_stress_query` (" + "ts timestamp, " + "val double from stress_src_query.val, " + "data binary(64) from stress_src_query.data)") + + tdLog.info(f"prepare source table for large result set (10K rows).") + tdSql.execute("CREATE TABLE `stress_src_large` (" + "ts timestamp, " + "val double, " + "data binary(64))") + + for batch in range(0, 10000, 1000): + sql = "INSERT INTO stress_src_large VALUES " + values = [f"({1700000000000 + i}, {i * 0.1}, 'data_{i}')" + for i in range(batch, min(batch + 1000, 10000))] + sql += ", ".join(values) + tdSql.execute(sql) + + tdSql.execute("CREATE VTABLE `vtb_stress_large` (" + "ts timestamp, " + "val double from stress_src_large.val, " + "data binary(64) from stress_src_large.data)") + + tdLog.info(f"prepare 10 source tables for many-vtable test (100 rows each).") + for t in range(10): + tdSql.execute(f"CREATE TABLE `stress_src_{t}` (" + "ts timestamp, " + "val double, " + "data binary(64))") + + sql = f"INSERT INTO stress_src_{t} VALUES " + values = [f"({1700000000000 + i}, {i * 0.1}, 'data_{t}_{i}')" for i in range(100)] + sql += ", ".join(values) + tdSql.execute(sql) + + tdLog.info(f"prepare source tables for complex queries (2 tables, 1K rows each).") + for t in range(2): + tdSql.execute(f"CREATE TABLE `stress_src_complex_{t}` (" + "ts timestamp, " + "val double, " + "data binary(64))") + + sql = f"INSERT INTO stress_src_complex_{t} VALUES " + values = [f"({1700000000000 + i}, {i * 0.1}, 'data_{t}_{i}')" for i in range(1000)] + sql += ", ".join(values) + tdSql.execute(sql) + + tdSql.execute("CREATE VTABLE `vtb_stress_complex_0` (" + "ts timestamp, " + "val double from stress_src_complex_0.val, " + "data binary(64) from stress_src_complex_0.data)") + + tdSql.execute("CREATE VTABLE `vtb_stress_complex_1` (" + "ts timestamp, " + "val double from stress_src_complex_1.val, " + "data binary(64) from stress_src_complex_1.data)") + + tdLog.info(f"prepare source table for mixed DDL/DML (1K rows).") + tdSql.execute("CREATE TABLE `stress_src_mixed` (" + "ts timestamp, " + "val double)") + + sql = "INSERT INTO stress_src_mixed VALUES " + values = [f"({1700000000000 + i}, {i * 0.1})" for i in range(1000)] + sql += ", ".join(values) + tdSql.execute(sql) + + tdLog.info(f"prepare source table for memory cleanup (5K rows, 5 cols).") + tdSql.execute("CREATE TABLE `stress_src_mem` (" + "ts timestamp, " + "col1 double, col2 double, col3 double, col4 double, col5 double)") + + sql = "INSERT INTO stress_src_mem VALUES " + values = [f"({1700000000000 + i}, {i*0.1}, {i*0.2}, {i*0.3}, {i*0.4}, {i*0.5})" + for i in range(5000)] + sql += ", ".join(values) + tdSql.execute(sql) + + tdSql.execute("CREATE VTABLE `vtb_stress_mem_baseline` (" + "ts timestamp, " + "col1 double from stress_src_mem.col1, " + "col2 double from stress_src_mem.col2, " + "col3 double from stress_src_mem.col3, " + "col4 double from stress_src_mem.col4, " + "col5 double from stress_src_mem.col5)") + + tdLog.info(f"prepare source tables for concurrent simulation (5 tables, 500 rows each).") + for i in range(5): + tdSql.execute(f"CREATE TABLE `stress_src_conc_{i}` (" + "ts timestamp, " + "val double)") + + sql = f"INSERT INTO stress_src_conc_{i} VALUES " + values = [f"({1700000000000 + j}, {j * 0.1})" for j in range(500)] + sql += ", ".join(values) + tdSql.execute(sql) + + for i in range(5): + tdSql.execute(f"CREATE VTABLE `vtb_stress_conc_{i}` (" + "ts timestamp, " + f"val double from stress_src_conc_{i}.val)") + + def test_stress_repeated_create_drop_vtable(self): + """Stress: repeated virtual table create/drop cycles + + Repeatedly create and drop a virtual table (50 cycles), + verifying query correctness at each cycle. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, stress + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test repeated create/drop virtual table (50 cycles).") + tdSql.execute(f"use {self.DB_NAME};") + + num_cycles = 50 + for cycle in range(num_cycles): + tdSql.execute("CREATE VTABLE `vtb_stress_cycle` (" + "ts timestamp, " + "val double from stress_src_cycle.val)") + + tdSql.query("SELECT COUNT(*), AVG(val) FROM vtb_stress_cycle;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 100) + + tdSql.execute("DROP VTABLE vtb_stress_cycle;") + + if (cycle + 1) % 10 == 0: + tdLog.info(f"completed {cycle + 1}/{num_cycles} create/drop cycles.") + + tdLog.info(f"repeated create/drop test passed.") + + def test_stress_repeated_query(self): + """Stress: repeated query execution on same virtual table + + Execute the same aggregation query 100 times, verifying + result consistency at every iteration. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, stress + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test repeated query execution (100 iterations).") + tdSql.execute(f"use {self.DB_NAME};") + + num_queries = 100 + for i in range(num_queries): + tdSql.query("SELECT COUNT(*), AVG(val), MIN(val), MAX(val) FROM vtb_stress_query;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 1000) + + if (i + 1) % 20 == 0: + tdLog.info(f"completed {i + 1}/{num_queries} queries.") + + tdLog.info(f"repeated query test passed.") + + def test_stress_large_result_set(self): + """Stress: large result set handling + + 1. full table scan on 10K rows + 2. multiple aggregations + 3. group by with many groups + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, stress + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test large result set handling.") + tdSql.execute(f"use {self.DB_NAME};") + + tdSql.query("SELECT * FROM vtb_stress_large;") + tdSql.checkRows(10000) + + tdSql.query("SELECT " + "COUNT(*), SUM(val), AVG(val), " + "MIN(val), MAX(val), " + "STDDEV(val) " + "FROM vtb_stress_large;") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 10000) + + tdSql.query("SELECT CAST(val / 100 AS INT) as grp, COUNT(*) " + "FROM vtb_stress_large " + "GROUP BY CAST(val / 100 AS INT) " + "ORDER BY grp;") + tdSql.checkRows(10) + + tdLog.info(f"large result set test passed.") + + def test_stress_many_virtual_tables(self): + """Stress: create and query many virtual tables + + Create 50 virtual tables referencing 10 source tables, + query each one, then drop them all. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, stress + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test creating and querying 50 virtual tables.") + tdSql.execute(f"use {self.DB_NAME};") + + num_vtables = 50 + for i in range(num_vtables): + src_table = i % 10 + tdSql.execute(f"CREATE VTABLE `vtb_stress_many_{i}` (" + "ts timestamp, " + f"val double from stress_src_{src_table}.val)") + + for i in range(num_vtables): + tdSql.query(f"SELECT COUNT(*) FROM vtb_stress_many_{i};") + tdSql.checkData(0, 0, 100) + + for i in range(num_vtables): + tdSql.execute(f"DROP VTABLE vtb_stress_many_{i};") + + tdLog.info(f"many virtual tables test passed.") + + def test_stress_complex_query_patterns(self): + """Stress: complex query patterns on virtual tables + + 1. subquery + 2. union all + 3. multiple conditions with OR + 4. order by with limit + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, stress + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test complex query patterns.") + tdSql.execute(f"use {self.DB_NAME};") + + tdSql.query("SELECT * FROM vtb_stress_complex_0 " + "WHERE val > (SELECT AVG(val) FROM vtb_stress_complex_1);") + tdLog.info(f"subquery returned {tdSql.queryRows} rows.") + + tdSql.query("(SELECT * FROM vtb_stress_complex_0 LIMIT 100) " + "UNION ALL " + "(SELECT * FROM vtb_stress_complex_1 LIMIT 100);") + tdSql.checkRows(200) + + tdSql.query("SELECT * FROM vtb_stress_complex_0 " + "WHERE (val < 10 OR val > 90) " + "AND data LIKE 'data_0_%';") + tdLog.info(f"multi-condition query returned {tdSql.queryRows} rows.") + + tdSql.query("SELECT ts, val " + "FROM vtb_stress_complex_0 " + "ORDER BY val " + "LIMIT 100;") + tdSql.checkRows(100) + + tdLog.info(f"complex query patterns test passed.") + + def test_stress_mixed_ddl_dml(self): + """Stress: mixed DDL and DML operations + + Interleave create/query/drop operations across 20 iterations, + dropping older tables as new ones are created. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, stress + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test mixed DDL and DML operations.") + tdSql.execute(f"use {self.DB_NAME};") + + num_iterations = 20 + for i in range(num_iterations): + tdSql.execute(f"CREATE VTABLE `vtb_stress_mixed_{i}` (" + "ts timestamp, " + "val double from stress_src_mixed.val)") + + for _ in range(5): + tdSql.query(f"SELECT COUNT(*) FROM vtb_stress_mixed_{i};") + tdSql.checkData(0, 0, 1000) + + if i >= 5: + tdSql.execute(f"DROP VTABLE vtb_stress_mixed_{i - 5};") + + if (i + 1) % 5 == 0: + tdLog.info(f"completed {i + 1}/{num_iterations} mixed iterations.") + + for i in range(max(0, num_iterations - 5), num_iterations): + tdSql.execute(f"DROP VTABLE IF EXISTS vtb_stress_mixed_{i};") + + tdLog.info(f"mixed DDL/DML test passed.") + + def test_stress_memory_cleanup(self): + """Stress: memory cleanup verification + + Repeatedly create/query/drop virtual tables (30 cycles) + on a 5K-row source, then verify final query still returns + correct results. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, stress + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test memory cleanup verification.") + tdSql.execute(f"use {self.DB_NAME};") + + tdSql.query("SELECT COUNT(*) FROM vtb_stress_mem_baseline;") + tdSql.checkData(0, 0, 5000) + + num_cycles = 30 + for cycle in range(num_cycles): + tdSql.execute(f"CREATE VTABLE `vtb_stress_mem_{cycle}` (" + "ts timestamp, " + "col1 double from stress_src_mem.col1, " + "col2 double from stress_src_mem.col2, " + "col3 double from stress_src_mem.col3, " + "col4 double from stress_src_mem.col4, " + "col5 double from stress_src_mem.col5)") + + tdSql.query(f"SELECT COUNT(*) FROM vtb_stress_mem_{cycle};") + tdSql.checkData(0, 0, 5000) + + tdSql.execute(f"DROP VTABLE vtb_stress_mem_{cycle};") + + if (cycle + 1) % 10 == 0: + tdLog.info(f"completed {cycle + 1}/{num_cycles} memory cleanup cycles.") + + tdSql.query("SELECT * FROM vtb_stress_mem_baseline;") + tdSql.checkRows(5000) + + tdLog.info(f"memory cleanup test passed.") + + def test_stress_concurrent_simulation(self): + """Stress: simulated concurrent access + + Rapidly alternate queries across 5 virtual tables + for 50 iterations, verifying correctness each time. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, stress + + Jira: None + + History: + - 2026-2-25 Created + """ + tdLog.info(f"test simulated concurrent access.") + tdSql.execute(f"use {self.DB_NAME};") + + num_iterations = 50 + for i in range(num_iterations): + table_idx = i % 5 + tdSql.query(f"SELECT COUNT(*), AVG(val) FROM vtb_stress_conc_{table_idx};") + tdSql.checkRows(1) + tdSql.checkData(0, 0, 500) + + tdLog.info(f"concurrent simulation test passed.") diff --git a/test/cases/05-VirtualTables/test_vtable_tag_ref.py b/test/cases/05-VirtualTables/test_vtable_tag_ref.py new file mode 100644 index 000000000000..fc088e5e190b --- /dev/null +++ b/test/cases/05-VirtualTables/test_vtable_tag_ref.py @@ -0,0 +1,1015 @@ +################################################################### +# Copyright (c) 2016 by TAOS Technologies, Inc. +# All rights reserved. +# +# This file is proprietary and confidential to TAOS Technologies. +# No part of this file may be reproduced, stored, transmitted, +# disclosed or used in any form or by any means other than as +# expressly provided by the written permission from Jianhui Tao +# +################################################################### + +# -*- coding: utf-8 -*- +from new_test_framework.utils import tdLog, tdSql, etool, tdCom + + +DB_NAME = "test_vtag_ref" +CROSS_DB = "test_vtag_ref_cross" + + +class TestVtableTagRef: + """Test cases for virtual table tag column references. + + Tests the new unified syntax for tag references in virtual child tables: + - Old syntax: TAGS (FROM table.tag, ...) + - Specific tag ref: TAGS (tag_name FROM table.tag, ...) + - Positional tag ref: TAGS (table.tag, ...) or TAGS (db.table.tag, ...) + - Mixed tag ref: TAGS (literal, tag_name FROM table.tag, table.tag, FROM table.tag, ...) + """ + + def setup_class(cls): + """Prepare org tables for tag reference tests.""" + tdLog.info("=== setup: prepare databases and tables for virtual table tag ref tests ===") + + tdSql.execute(f"drop database if exists {DB_NAME};") + tdSql.execute(f"drop database if exists {CROSS_DB};") + tdSql.execute(f"create database {DB_NAME};") + tdSql.execute(f"use {DB_NAME};") + + # --- Source super table with various tag types --- + tdLog.info("prepare org super table.") + tdSql.execute("CREATE STABLE `org_stb` (" + "ts timestamp, " + "int_col int, " + "bigint_col bigint, " + "float_col float, " + "double_col double, " + "binary_32_col binary(32), " + "nchar_32_col nchar(32)" + ") TAGS (" + "int_tag int, " + "bool_tag bool, " + "float_tag float, " + "double_tag double, " + "nchar_32_tag nchar(32), " + "binary_32_tag binary(32))") + + # --- Source child tables --- + tdLog.info("prepare org child tables.") + for i in range(5): + tdSql.execute(f"CREATE TABLE `org_child_{i}` USING `org_stb` " + f"TAGS ({i}, {'true' if i % 2 == 0 else 'false'}, {i}.{i}, {i}.{i}{i}, " + f"'nchar_child{i}', 'bin_child{i}');") + + # --- Source normal tables --- + tdLog.info("prepare org normal tables.") + for i in range(3): + tdSql.execute(f"CREATE TABLE `org_normal_{i}` (" + "ts timestamp, " + "int_col int, " + "bigint_col bigint, " + "float_col float, " + "double_col double, " + "binary_32_col binary(32), " + "nchar_32_col nchar(32))") + + # --- Virtual super table --- + tdLog.info("prepare virtual super table.") + tdSql.execute("CREATE STABLE `vstb` (" + "ts timestamp, " + "int_col int, " + "bigint_col bigint, " + "float_col float, " + "double_col double, " + "binary_32_col binary(32), " + "nchar_32_col nchar(32)" + ") TAGS (" + "int_tag int, " + "bool_tag bool, " + "float_tag float, " + "double_tag double, " + "nchar_32_tag nchar(32), " + "binary_32_tag binary(32))" + " VIRTUAL 1") + + # --- Insert data into org tables --- + tdLog.info("insert data into org tables.") + for i in range(5): + for j in range(10): + ts = 1700000000000 + j * 1000 + tdSql.execute(f"INSERT INTO org_child_{i} VALUES ({ts}, {j}, {j*100}, {j}.{j}, {j}.{j}{j}, " + f"'bin_{i}_{j}', 'nchar_{i}_{j}');") + for i in range(3): + for j in range(10): + ts = 1700000000000 + j * 1000 + tdSql.execute(f"INSERT INTO org_normal_{i} VALUES ({ts}, {j}, {j*100}, {j}.{j}, {j}.{j}{j}, " + f"'bin_{i}_{j}', 'nchar_{i}_{j}');") + + # --- Cross-database setup --- + tdLog.info("prepare cross-db source tables.") + tdSql.execute(f"create database {CROSS_DB};") + tdSql.execute(f"use {CROSS_DB};") + tdSql.execute("CREATE STABLE `cross_stb` (" + "ts timestamp, " + "int_col int, " + "bigint_col bigint" + ") TAGS (" + "int_tag int, " + "bool_tag bool, " + "float_tag float, " + "double_tag double, " + "nchar_32_tag nchar(32), " + "binary_32_tag binary(32))") + for i in range(3): + tdSql.execute(f"CREATE TABLE `cross_child_{i}` USING `cross_stb` " + f"TAGS ({i+10}, {'true' if i % 2 == 0 else 'false'}, {i+10}.{i}, {i+10}.{i}{i}, " + f"'cross_nchar{i}', 'cross_bin{i}');") + for j in range(10): + ts = 1700000000000 + j * 1000 + tdSql.execute(f"INSERT INTO cross_child_{i} VALUES ({ts}, {j}, {j*100});") + + tdSql.execute(f"use {DB_NAME};") + tdLog.info("=== setup complete ===") + + # ========================================================================= + # Helper + # ========================================================================= + def check_vtable_count(self, vctable_num, vntable_num): + tdSql.query(f"show {DB_NAME}.vtables;") + tdSql.checkRows(vctable_num + vntable_num) + tdSql.query(f"show child {DB_NAME}.vtables;") + tdSql.checkRows(vctable_num) + tdSql.query(f"show normal {DB_NAME}.vtables;") + tdSql.checkRows(vntable_num) + + # ========================================================================= + # 1. Old FROM syntax for tag references + # ========================================================================= + def test_tag_ref_old_syntax(self): + """Create: virtual child table with old tag ref syntax (FROM table.tag) + + Test the legacy tag reference syntax where FROM precedes the column_ref + in the TAGS clause: TAGS (FROM table.tag, ...) + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, create, tag_ref + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: old tag reference syntax: FROM column_ref ===") + + tdSql.execute(f"use {DB_NAME};") + + tdSql.execute("CREATE VTABLE `vctb_old_0` (" + "int_col FROM org_child_0.int_col, " + "bigint_col FROM org_child_1.bigint_col" + ") USING vstb TAGS (" + "FROM org_child_0.int_tag, " + "FROM org_child_0.bool_tag, " + "FROM org_child_0.float_tag, " + "FROM org_child_0.double_tag, " + "FROM org_child_0.nchar_32_tag, " + "FROM org_child_0.binary_32_tag)") + + self.check_vtable_count(1, 0) + tdLog.info("old tag reference syntax test passed") + + # ========================================================================= + # 2. New specific syntax: tag_name FROM table.tag + # ========================================================================= + def test_tag_ref_specific_syntax(self): + """Create: virtual child table with specific tag ref syntax (tag_name FROM table.tag) + + Test the new specific tag reference syntax where the tag name is explicitly + specified: TAGS (tag_name FROM table.tag, ...) + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, create, tag_ref + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: new specific tag reference syntax ===") + + tdSql.execute(f"use {DB_NAME};") + + # 2.1 All tags ref from same child table + tdSql.execute("CREATE VTABLE `vctb_specific_0` (" + "int_col FROM org_child_0.int_col, " + "bigint_col FROM org_child_1.bigint_col" + ") USING vstb TAGS (" + "int_tag FROM org_child_0.int_tag, " + "bool_tag FROM org_child_0.bool_tag, " + "float_tag FROM org_child_0.float_tag, " + "double_tag FROM org_child_0.double_tag, " + "nchar_32_tag FROM org_child_0.nchar_32_tag, " + "binary_32_tag FROM org_child_0.binary_32_tag)") + + self.check_vtable_count(2, 0) + + # 2.2 Tags ref from different child tables + tdSql.execute("CREATE VTABLE `vctb_specific_1` (" + "int_col FROM org_child_0.int_col, " + "bigint_col FROM org_child_1.bigint_col" + ") USING vstb TAGS (" + "int_tag FROM org_child_0.int_tag, " + "bool_tag FROM org_child_1.bool_tag, " + "float_tag FROM org_child_2.float_tag, " + "double_tag FROM org_child_3.double_tag, " + "nchar_32_tag FROM org_child_4.nchar_32_tag, " + "binary_32_tag FROM org_child_0.binary_32_tag)") + + self.check_vtable_count(3, 0) + + # 2.3 Partial tag references (some literal, some reference) + tdSql.execute("CREATE VTABLE `vctb_specific_2` (" + "int_col FROM org_child_0.int_col, " + "bigint_col FROM org_child_1.bigint_col" + ") USING vstb TAGS (" + "int_tag FROM org_child_0.int_tag, " + "false, " + "float_tag FROM org_child_2.float_tag, " + "3.14, " + "'literal_nchar', " + "binary_32_tag FROM org_child_0.binary_32_tag)") + + self.check_vtable_count(4, 0) + + tdLog.info("new specific tag reference syntax test passed") + + # ========================================================================= + # 3. New positional syntax: table.tag or db.table.tag + # ========================================================================= + def test_tag_ref_positional_syntax(self): + """Create: virtual child table with positional tag ref syntax (table.tag / db.table.tag) + + Test the new positional tag reference syntax where the tag column is + specified by table.tag (2-part) or db.table.tag (3-part). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, create, tag_ref + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: new positional tag reference syntax ===") + + tdSql.execute(f"use {DB_NAME};") + + # 3.1 2-part name: table.tag + tdSql.execute("CREATE VTABLE `vctb_pos_0` (" + "int_col FROM org_child_0.int_col, " + "bigint_col FROM org_child_1.bigint_col" + ") USING vstb TAGS (" + "org_child_0.int_tag, " + "org_child_0.bool_tag, " + "org_child_0.float_tag, " + "org_child_0.double_tag, " + "org_child_0.nchar_32_tag, " + "org_child_0.binary_32_tag)") + + self.check_vtable_count(5, 0) + + # 3.2 Positional from different child tables + tdSql.execute("CREATE VTABLE `vctb_pos_1` (" + "int_col FROM org_child_0.int_col, " + "bigint_col FROM org_child_1.bigint_col" + ") USING vstb TAGS (" + "org_child_0.int_tag, " + "org_child_1.bool_tag, " + "org_child_2.float_tag, " + "org_child_3.double_tag, " + "org_child_4.nchar_32_tag, " + "org_child_0.binary_32_tag)") + + self.check_vtable_count(6, 0) + + # 3.3 3-part name: db.table.tag + tdSql.execute(f"CREATE VTABLE `vctb_pos_2` (" + "int_col FROM org_child_0.int_col, " + "bigint_col FROM org_child_1.bigint_col" + f") USING vstb TAGS (" + f"{DB_NAME}.org_child_0.int_tag, " + f"{DB_NAME}.org_child_0.bool_tag, " + f"{DB_NAME}.org_child_0.float_tag, " + f"{DB_NAME}.org_child_0.double_tag, " + f"{DB_NAME}.org_child_0.nchar_32_tag, " + f"{DB_NAME}.org_child_0.binary_32_tag)") + + self.check_vtable_count(7, 0) + + tdLog.info("new positional tag reference syntax test passed") + + # ========================================================================= + # 4. Mixed syntax: literal + old FROM + specific + positional + # ========================================================================= + def test_tag_ref_mixed_syntax(self): + """Create: virtual child table with mixed tag ref syntax + + Test mixing literal values, old FROM syntax, new specific syntax, + and new positional syntax in a single TAGS clause. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, create, tag_ref + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: mixed tag reference syntax ===") + + tdSql.execute(f"use {DB_NAME};") + + # 4.1 Mix of literal, old FROM, specific, positional + tdSql.execute("CREATE VTABLE `vctb_mixed_0` (" + "int_col FROM org_child_0.int_col, " + "bigint_col FROM org_child_1.bigint_col" + ") USING vstb TAGS (" + "100, " # literal + "bool_tag FROM org_child_1.bool_tag, " # new specific + "org_child_2.float_tag, " # new positional + "FROM org_child_3.double_tag, " # old FROM + "'mixed_nchar', " # literal + "binary_32_tag FROM org_child_0.binary_32_tag)") # new specific + + self.check_vtable_count(8, 0) + + # 4.2 Mix with 3-part positional names + tdSql.execute(f"CREATE VTABLE `vctb_mixed_1` (" + "int_col FROM org_child_0.int_col, " + "bigint_col FROM org_child_1.bigint_col" + f") USING vstb TAGS (" + f"int_tag FROM {DB_NAME}.org_child_0.int_tag, " # specific with 3-part ref + "false, " # literal + f"{DB_NAME}.org_child_2.float_tag, " # positional 3-part + "3.14, " # literal + "nchar_32_tag FROM org_child_4.nchar_32_tag, " # specific with 2-part ref + "'literal_bin')") # literal + + self.check_vtable_count(9, 0) + + tdLog.info("mixed tag reference syntax test passed") + + # ========================================================================= + # 5. Tag refs combined with different column reference styles + # ========================================================================= + def test_tag_ref_with_column_ref(self): + """Create: virtual child table with tag refs + different col ref styles + + Test tag references combined with positional column references + (no FROM keyword for columns). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, create, tag_ref + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: tag refs with different column ref styles ===") + + tdSql.execute(f"use {DB_NAME};") + + # 5.1 Positional column refs + specific tag refs + tdSql.execute("CREATE VTABLE `vctb_colref_0` (" + "org_child_0.int_col, " + "org_child_1.bigint_col, " + "org_child_2.float_col" + ") USING vstb TAGS (" + "int_tag FROM org_child_0.int_tag, " + "bool_tag FROM org_child_0.bool_tag, " + "float_tag FROM org_child_0.float_tag, " + "double_tag FROM org_child_0.double_tag, " + "nchar_32_tag FROM org_child_0.nchar_32_tag, " + "binary_32_tag FROM org_child_0.binary_32_tag)") + + self.check_vtable_count(10, 0) + + # 5.2 FROM column refs + positional tag refs + tdSql.execute("CREATE VTABLE `vctb_colref_1` (" + "int_col FROM org_child_0.int_col, " + "bigint_col FROM org_child_1.bigint_col" + ") USING vstb TAGS (" + "org_child_0.int_tag, " + "org_child_1.bool_tag, " + "org_child_2.float_tag, " + "org_child_3.double_tag, " + "org_child_4.nchar_32_tag, " + "org_child_0.binary_32_tag)") + + self.check_vtable_count(11, 0) + + tdLog.info("tag refs with column ref styles test passed") + + # ========================================================================= + # 6. Cross-database tag references + # ========================================================================= + def test_tag_ref_cross_db(self): + """Create: virtual child table with cross-database tag references + + Test tag references that point to child tables in a different database. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, create, tag_ref + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: cross-database tag references ===") + + tdSql.execute(f"use {DB_NAME};") + + # 6.1 Specific syntax with cross-db ref + tdSql.execute(f"CREATE VTABLE `vctb_crossdb_0` (" + f"int_col FROM {CROSS_DB}.cross_child_0.int_col, " + f"bigint_col FROM {CROSS_DB}.cross_child_1.bigint_col" + f") USING vstb TAGS (" + f"int_tag FROM {CROSS_DB}.cross_child_0.int_tag, " + f"bool_tag FROM {CROSS_DB}.cross_child_0.bool_tag, " + f"float_tag FROM {CROSS_DB}.cross_child_0.float_tag, " + f"double_tag FROM {CROSS_DB}.cross_child_0.double_tag, " + f"nchar_32_tag FROM {CROSS_DB}.cross_child_0.nchar_32_tag, " + f"binary_32_tag FROM {CROSS_DB}.cross_child_0.binary_32_tag)") + + self.check_vtable_count(12, 0) + + # 6.2 Positional syntax with cross-db ref (3-part name) + tdSql.execute(f"CREATE VTABLE `vctb_crossdb_1` (" + f"int_col FROM {CROSS_DB}.cross_child_1.int_col" + f") USING vstb TAGS (" + f"{CROSS_DB}.cross_child_1.int_tag, " + f"{CROSS_DB}.cross_child_1.bool_tag, " + f"{CROSS_DB}.cross_child_1.float_tag, " + f"{CROSS_DB}.cross_child_1.double_tag, " + f"{CROSS_DB}.cross_child_1.nchar_32_tag, " + f"{CROSS_DB}.cross_child_1.binary_32_tag)") + + self.check_vtable_count(13, 0) + + # 6.3 Mixed: some tags from cross-db, some literal + tdSql.execute(f"CREATE VTABLE `vctb_crossdb_2` (" + f"int_col FROM {CROSS_DB}.cross_child_2.int_col" + f") USING vstb TAGS (" + f"int_tag FROM {CROSS_DB}.cross_child_2.int_tag, " + "true, " + f"float_tag FROM {CROSS_DB}.cross_child_2.float_tag, " + "9.99, " + "'cross_nchar_val', " + "'cross_bin_val')") + + self.check_vtable_count(14, 0) + + tdLog.info("cross-database tag reference test passed") + + # ========================================================================= + # 7. SHOW CREATE VTABLE verification for tag refs + # ========================================================================= + def test_tag_ref_show_create_vtable(self): + """Create: verify tag references via SHOW CREATE VTABLE + + Verify that tag references are correctly stored and returned by + SHOW CREATE VTABLE command. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, create, tag_ref + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: tag reference storage via SHOW CREATE VTABLE ===") + + tdSql.execute(f"use {DB_NAME};") + + # Create a vtable with specific tag refs + tdSql.execute("CREATE VTABLE `vctb_verify_0` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "int_tag FROM org_child_2.int_tag, " + "bool_tag FROM org_child_2.bool_tag, " + "float_tag FROM org_child_2.float_tag, " + "double_tag FROM org_child_2.double_tag, " + "nchar_32_tag FROM org_child_2.nchar_32_tag, " + "binary_32_tag FROM org_child_2.binary_32_tag)") + + self.check_vtable_count(15, 0) + + tdSql.query(f"SHOW CREATE VTABLE {DB_NAME}.vctb_verify_0;") + tdSql.checkRows(1) + create_sql = tdSql.getData(0, 1) + tdLog.info(f"SHOW CREATE VTABLE vctb_verify_0: {create_sql}") + assert 'vctb_verify_0' in create_sql, "Table name should be in create SQL" + + # Create with positional syntax + tdSql.execute("CREATE VTABLE `vctb_verify_1` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "org_child_3.int_tag, " + "org_child_3.bool_tag, " + "org_child_3.float_tag, " + "org_child_3.double_tag, " + "org_child_3.nchar_32_tag, " + "org_child_3.binary_32_tag)") + + self.check_vtable_count(16, 0) + + tdSql.query(f"SHOW CREATE VTABLE {DB_NAME}.vctb_verify_1;") + tdSql.checkRows(1) + create_sql = tdSql.getData(0, 1) + tdLog.info(f"SHOW CREATE VTABLE vctb_verify_1: {create_sql}") + assert 'vctb_verify_1' in create_sql, "Table name should be in create SQL" + + # Create with mixed syntax: some literal, some ref + tdSql.execute("CREATE VTABLE `vctb_verify_2` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "int_tag FROM org_child_4.int_tag, " + "true, " + "float_tag FROM org_child_1.float_tag, " + "9.99, " + "'my_nchar_val', " + "binary_32_tag FROM org_child_0.binary_32_tag)") + + self.check_vtable_count(17, 0) + + tdSql.query(f"SHOW CREATE VTABLE {DB_NAME}.vctb_verify_2;") + tdSql.checkRows(1) + create_sql = tdSql.getData(0, 1) + tdLog.info(f"SHOW CREATE VTABLE vctb_verify_2: {create_sql}") + assert 'vctb_verify_2' in create_sql, "Table name should be in create SQL" + assert 'my_nchar_val' in create_sql, "Literal nchar tag value should be in create SQL" + + tdLog.info("SHOW CREATE VTABLE tag ref verification passed") + + # ========================================================================= + # 8. DESCRIBE verification + # ========================================================================= + def test_tag_ref_describe(self): + """Create: verify tag references via DESCRIBE + + Verify that DESCRIBE on a virtual child table with tag references + returns the expected schema. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, create, tag_ref + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: DESCRIBE virtual child table with tag refs ===") + + tdSql.execute(f"use {DB_NAME};") + + # vctb_verify_0 was created in the previous test with all tag refs + tdSql.query(f"DESCRIBE {DB_NAME}.vctb_verify_0;") + tdLog.info(f"DESCRIBE vctb_verify_0 rows: {tdSql.queryRows}") + + # Should have: ts(1) + 6 data cols + 6 tags = 13 rows + # (vstb has ts + 6 data cols + 6 tags) + tdSql.checkRows(13) + + tdLog.info("DESCRIBE tag ref verification passed") + + # ========================================================================= + # 9. Tag ref with specific_cols_opt + # ========================================================================= + def test_tag_ref_with_specific_tags(self): + """Create: virtual child table with specific tag names in TAGS clause + + Test tag references when specific tag names are listed before TAGS + keyword: USING vstb (tag1, tag2, ...) TAGS (...) + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, create, tag_ref + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: tag refs with specific tag names ===") + + tdSql.execute(f"use {DB_NAME};") + + tdSql.execute("CREATE VTABLE `vctb_spectag_0` (" + "int_col FROM org_child_0.int_col" + ") USING vstb (int_tag, bool_tag, float_tag, double_tag, nchar_32_tag, binary_32_tag) TAGS (" + "int_tag FROM org_child_1.int_tag, " + "false, " + "float_tag FROM org_child_2.float_tag, " + "3.14, " + "'nchar_val', " + "'bin_val')") + + self.check_vtable_count(18, 0) + + tdSql.query(f"SHOW CREATE VTABLE {DB_NAME}.vctb_spectag_0;") + tdSql.checkRows(1) + create_sql = tdSql.getData(0, 1) + tdLog.info(f"SHOW CREATE VTABLE vctb_spectag_0: {create_sql}") + assert 'vctb_spectag_0' in create_sql, "Table name should be in create SQL" + assert 'nchar_val' in create_sql, "Literal nchar tag value should be in create SQL" + + tdLog.info("tag refs with specific tag names test passed") + + # ========================================================================= + # 10. Error cases + # ========================================================================= + def test_tag_ref_error_cases(self): + """Create: virtual child table tag ref error cases + + Test various error conditions for tag column references: + 1. Tag ref column does not exist in source table + 2. Tag ref table does not exist + 3. Tag ref type mismatch (non-tag column used as tag) + 4. Positional tag ref - column does not exist + 5. Positional tag ref - table does not exist + 6. Tag type mismatch between virtual and source + 7. 3-part positional with non-existent db + 8. Tag ref from normal table (not child table) + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, create, tag_ref, negative + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: tag reference error cases ===") + + tdSql.execute(f"use {DB_NAME};") + + # 10.1 Tag ref column does not exist in source table + tdSql.error("CREATE VTABLE `vctb_err_0` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "int_tag FROM org_child_0.not_exist_tag, " + "false, 1.0, 2.0, 'nchar', 'bin')") + + # 10.2 Tag ref table does not exist + tdSql.error("CREATE VTABLE `vctb_err_1` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "int_tag FROM not_exist_table.int_tag, " + "false, 1.0, 2.0, 'nchar', 'bin')") + + # 10.3 Tag ref type mismatch: referencing a non-tag column as tag + tdSql.error("CREATE VTABLE `vctb_err_2` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "int_tag FROM org_child_0.int_col, " + "false, 1.0, 2.0, 'nchar', 'bin')") + + # 10.4 Positional tag ref - column does not exist + tdSql.error("CREATE VTABLE `vctb_err_3` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "org_child_0.not_exist_tag, " + "false, 1.0, 2.0, 'nchar', 'bin')") + + # 10.5 Positional tag ref - table does not exist + tdSql.error("CREATE VTABLE `vctb_err_4` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "not_exist_table.int_tag, " + "false, 1.0, 2.0, 'nchar', 'bin')") + + # 10.6 Tag type mismatch - referencing int_tag for bool_tag position + tdSql.error("CREATE VTABLE `vctb_err_5` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "0, " + "bool_tag FROM org_child_0.int_tag, " + "1.0, 2.0, 'nchar', 'bin')") + + # 10.7 3-part positional with non-existent db + tdSql.error("CREATE VTABLE `vctb_err_6` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "nonexist_db.org_child_0.int_tag, " + "false, 1.0, 2.0, 'nchar', 'bin')") + + # 10.8 Tag ref from normal table (not child table - has no tags) + tdSql.error("CREATE VTABLE `vctb_err_7` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "int_tag FROM org_normal_0.int_col, " + "false, 1.0, 2.0, 'nchar', 'bin')") + + tdLog.info("tag reference error cases test passed") + + def test_tag_ref_must_reference_tag_column(self): + """Create: tag ref must point to actual tag column, not data column + + Verify that when a tag reference points to a column that exists + but is not a tag column, the error message clearly indicates + that the referenced column is not a tag column. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, create, tag_ref, negative + + Jira: None + + History: + - 2026-2-12 Created + + """ + tdLog.info("=== Test: tag ref must point to tag column, not data column ===") + + tdSql.execute(f"use {DB_NAME};") + + err = tdSql.error("CREATE VTABLE `vctb_not_tag_0` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "int_tag FROM org_child_0.int_col, " + "false, 1.0, 2.0, 'nchar', 'bin')") + assert "not a tag column" in err, f"Expected 'not a tag column' in error, got: {err}" + + err = tdSql.error("CREATE VTABLE `vctb_not_tag_1` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "int_tag FROM org_child_0.ts, " + "false, 1.0, 2.0, 'nchar', 'bin')") + assert "not a tag column" in err, f"Expected 'not a tag column' in error, got: {err}" + + err = tdSql.error("CREATE VTABLE `vctb_not_tag_2` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "org_child_0.bigint_col, " + "false, 1.0, 2.0, 'nchar', 'bin')") + assert "not a tag column" in err, f"Expected 'not a tag column' in error, got: {err}" + + err = tdSql.error("CREATE VTABLE `vctb_not_tag_3` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "int_tag FROM org_child_0.nonexistent_col, " + "false, 1.0, 2.0, 'nchar', 'bin')") + assert "non-existent tag" in err, f"Expected 'non-existent tag' in error, got: {err}" + + err = tdSql.error("CREATE VTABLE `vctb_not_tag_4` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "int_tag FROM org_child_0.int_col, " + "bool_tag FROM org_child_0.bigint_col, " + "1.0, 2.0, 'nchar', 'bin')") + assert "not a tag column" in err, f"Expected 'not a tag column' in error, got: {err}" + + tdLog.info("tag ref must point to tag column test passed") + + # ========================================================================= + # 11. Query data from virtual child tables with tag refs + # ========================================================================= + def test_tag_ref_query_data(self): + """Query: select data from virtual child table with tag references + + Verify that data can be queried from virtual child tables that + were created with tag column references. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, query, tag_ref + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: query data from vtables with tag refs ===") + + tdSql.execute(f"use {DB_NAME};") + + # Query from vctb_specific_0 (all tags ref from org_child_0) + tdSql.query(f"select * from {DB_NAME}.vctb_specific_0 limit 5;") + tdLog.info(f"vctb_specific_0 query returned {tdSql.queryRows} rows") + assert tdSql.queryRows <= 5, "Should return at most 5 rows" + + # Query with tag filter + tdSql.query(f"select int_col, bigint_col from {DB_NAME}.vctb_specific_0;") + tdLog.info(f"vctb_specific_0 col query returned {tdSql.queryRows} rows") + tdSql.checkRows(10) + + # Query from virtual super table (all child tables) + tdSql.query(f"select count(*) from {DB_NAME}.vstb;") + tdLog.info(f"vstb total count: {tdSql.getData(0, 0)}") + + tdLog.info("query data from vtables with tag refs test passed") + + # ========================================================================= + # 12. Show tags for virtual child table with tag refs + # ========================================================================= + def test_tag_ref_show_tags(self): + """Query: show tags from virtual child table with tag references + + Verify that SHOW TAGS works correctly for virtual child tables + that have tag column references. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, query, tag_ref + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: show tags from vtable with tag refs ===") + + tdSql.execute(f"use {DB_NAME};") + + # Show tags from vctb_old_0 (old FROM syntax, all tags ref from org_child_0) + tdSql.query(f"show tags from {DB_NAME}.vctb_old_0;") + tdLog.info(f"vctb_old_0 show tags returned {tdSql.queryRows} rows") + tdSql.checkRows(6) # 6 tags + + # Show tags from vctb_verify_2 (mixed literal + ref) + tdSql.query(f"show tags from {DB_NAME}.vctb_verify_2;") + tdLog.info(f"vctb_verify_2 show tags returned {tdSql.queryRows} rows") + tdSql.checkRows(6) # 6 tags + + tdLog.info("show tags test passed") + + # ========================================================================= + # 13. Drop and recreate with tag refs + # ========================================================================= + def test_tag_ref_drop_recreate(self): + """Create: drop and recreate virtual child table with tag refs + + Verify that virtual child tables with tag references can be + dropped and recreated without issues. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, create, drop, tag_ref + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: drop and recreate vtable with tag refs ===") + + tdSql.execute(f"use {DB_NAME};") + + # Create a temporary vtable + tdSql.execute("CREATE VTABLE `vctb_temp` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "int_tag FROM org_child_0.int_tag, " + "bool_tag FROM org_child_0.bool_tag, " + "float_tag FROM org_child_0.float_tag, " + "double_tag FROM org_child_0.double_tag, " + "nchar_32_tag FROM org_child_0.nchar_32_tag, " + "binary_32_tag FROM org_child_0.binary_32_tag)") + + tdSql.query(f"SHOW CREATE VTABLE {DB_NAME}.vctb_temp;") + tdSql.checkRows(1) + + # Drop it + tdSql.execute(f"DROP VTABLE {DB_NAME}.vctb_temp;") + + # Verify it's gone + tdSql.error(f"SHOW CREATE VTABLE {DB_NAME}.vctb_temp;") + + # Recreate with different tag refs + tdSql.execute("CREATE VTABLE `vctb_temp` (" + "int_col FROM org_child_0.int_col" + ") USING vstb TAGS (" + "org_child_4.int_tag, " + "org_child_4.bool_tag, " + "org_child_4.float_tag, " + "org_child_4.double_tag, " + "org_child_4.nchar_32_tag, " + "org_child_4.binary_32_tag)") + + tdSql.query(f"SHOW CREATE VTABLE {DB_NAME}.vctb_temp;") + tdSql.checkRows(1) + + # Clean up + tdSql.execute(f"DROP VTABLE {DB_NAME}.vctb_temp;") + + tdLog.info("drop and recreate with tag refs test passed") + + # ========================================================================= + # 14. All literal tags (no tag refs) - backward compatibility + # ========================================================================= + def test_tag_ref_all_literal(self): + """Create: virtual child table with all literal tags (no tag refs) + + Verify backward compatibility: creating virtual child tables with + only literal tag values (no tag references) still works correctly. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, create, tag_ref + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info("=== Test: all literal tags (backward compatibility) ===") + + tdSql.execute(f"use {DB_NAME};") + + # Create with all literal tags (no references at all) + tdSql.execute("CREATE VTABLE `vctb_literal_0` (" + "int_col FROM org_child_0.int_col, " + "bigint_col FROM org_child_1.bigint_col" + ") USING vstb TAGS (42, true, 3.14, 2.718, 'hello_nchar', 'hello_bin')") + + tdSql.query(f"SHOW CREATE VTABLE {DB_NAME}.vctb_literal_0;") + tdSql.checkRows(1) + create_sql = tdSql.getData(0, 1) + tdLog.info(f"SHOW CREATE VTABLE vctb_literal_0: {create_sql}") + assert 'vctb_literal_0' in create_sql + + # Show tags and verify values + tdSql.query(f"show tags from {DB_NAME}.vctb_literal_0;") + tdSql.checkRows(6) + + tdLog.info("all literal tags backward compatibility test passed") diff --git a/test/cases/05-VirtualTables/test_vtable_validate_referencing.py b/test/cases/05-VirtualTables/test_vtable_validate_referencing.py new file mode 100644 index 000000000000..3152faa9e5e4 --- /dev/null +++ b/test/cases/05-VirtualTables/test_vtable_validate_referencing.py @@ -0,0 +1,3271 @@ +################################################################### +# Copyright (c) 2016 by TAOS Technologies, Inc. +# All rights reserved. +# +# This file is proprietary and confidential to TAOS Technologies. +# No part of this file may be reproduced, stored, transmitted, +# disclosed or used in any form or by any means other than as +# expressly provided by the written permission from Jianhui Tao +# +################################################################### + +# -*- coding: utf-8 -*- +import pytest +from new_test_framework.utils import tdLog, tdSql, etool, tdCom + +# Error code constants (signed 32-bit representation) +TSDB_CODE_SUCCESS = 0 +TSDB_CODE_PAR_TABLE_NOT_EXIST = -2147473917 # 0x80002603 +TSDB_CODE_PAR_INVALID_REF_COLUMN = -2147473779 # 0x8000268D +TSDB_CODE_MND_DB_NOT_EXIST = -2147482744 # 0x80000388 + +DB_NAME = "test_vtable_validate_ref" +CROSS_DB_NAME = "test_vtable_validate_ref_src" + + +class TestVtableValidateReferencing: + + def setup_class(cls): + TestVtableValidateReferencing.prepare_vtables() + + @staticmethod + def prepare_vtables(): + tdLog.info(f"=== setup: prepare databases and tables for virtual table referencing validation ===") + + # Clean up + tdSql.execute(f"drop database if exists {DB_NAME};") + tdSql.execute(f"drop database if exists {CROSS_DB_NAME};") + + # Create main database + tdSql.execute(f"create database {DB_NAME};") + tdSql.execute(f"use {DB_NAME};") + + # --- Source tables in the same database --- + tdLog.info(f"prepare source normal table in same db.") + tdSql.execute(f"CREATE TABLE `src_ntb` (" + "ts timestamp, " + "int_col int, " + "float_col float, " + "binary_col binary(32));") + + tdLog.info(f"prepare source super table and child table in same db.") + tdSql.execute(f"CREATE STABLE `src_stb` (" + "ts timestamp, " + "val int, " + "extra_col float" + ") TAGS (" + "t_region int, " + "t_name binary(32));") + tdSql.execute(f"CREATE TABLE `src_ctb` USING `src_stb` TAGS (1, 'device1');") + + # Insert some data into source tables + tdSql.execute(f"INSERT INTO src_ntb VALUES (now, 100, 3.14, 'hello');") + tdSql.execute(f"INSERT INTO src_ctb VALUES (now, 200, 2.71);") + + # --- Source tables in a cross database --- + tdLog.info(f"prepare cross-db source tables.") + tdSql.execute(f"create database {CROSS_DB_NAME};") + tdSql.execute(f"use {CROSS_DB_NAME};") + + tdSql.execute(f"CREATE TABLE `cross_ntb` (" + "ts timestamp, " + "voltage int, " + "current float);") + tdSql.execute(f"INSERT INTO cross_ntb VALUES (now, 220, 1.5);") + + # Switch back to main database + tdSql.execute(f"use {DB_NAME};") + + # --- Virtual normal table referencing same-db normal table --- + tdLog.info(f"create virtual normal table referencing same-db ntb.") + tdSql.execute(f"CREATE VTABLE `vntb_same_db` (" + "ts timestamp, " + "v_int int from src_ntb.int_col, " + "v_float float from src_ntb.float_col, " + "v_bin binary(32) from src_ntb.binary_col);") + + # --- Virtual super table + child table referencing same-db child table --- + tdLog.info(f"create virtual super table.") + tdSql.execute(f"CREATE STABLE `vstb` (" + "ts timestamp, " + "v_val int, " + "v_extra float" + ") TAGS (" + "vt_region int, " + "vt_name binary(32))" + " VIRTUAL 1;") + + tdLog.info(f"create virtual child table referencing same-db ctb.") + tdSql.execute(f"CREATE VTABLE `vctb_same_db` (" + "v_val from src_ctb.val, " + "v_extra from src_ctb.extra_col) " + "USING `vstb` TAGS (1, 'vdev1');") + + # --- Multiple source tables for complex reference tests --- + tdLog.info(f"prepare multiple source tables for multi-source tests.") + tdSql.execute(f"CREATE TABLE `src_multi_1` (ts timestamp, c1 int);") + tdSql.execute(f"INSERT INTO src_multi_1 VALUES (now, 10);") + + tdSql.execute(f"CREATE TABLE `src_multi_2` (ts timestamp, c2 float);") + tdSql.execute(f"INSERT INTO src_multi_2 VALUES (now, 2.5);") + + tdSql.execute(f"CREATE TABLE `src_multi_3` (ts timestamp, c3 binary(16));") + tdSql.execute(f"INSERT INTO src_multi_3 VALUES (now, 'test');") + + # --- Source table with many columns --- + tdLog.info(f"prepare source table with many columns.") + tdSql.execute(f"CREATE TABLE `src_many_cols` (" + "ts timestamp, " + "col1 int, " + "col2 float, " + "col3 bigint, " + "col4 binary(16), " + "col5 double, " + "col6 smallint);") + tdSql.execute(f"INSERT INTO src_many_cols VALUES (now, 1, 1.1, 100, 'abc', 3.14, 10);") + + # --- Source table with complex column types --- + tdLog.info(f"prepare source table with complex column types.") + tdSql.execute(f"CREATE TABLE `src_complex_types` (" + "ts timestamp, " + "geo_col geometry(32), " + "varbin_col varbinary(64), " + "nchar_col nchar(64));") + tdSql.execute(f"INSERT INTO src_complex_types VALUES (now, NULL, NULL, 'nchar_test');") + + # --- Multiple super tables for combination tests --- + tdLog.info(f"prepare multiple source super tables for combination tests.") + tdSql.execute(f"CREATE STABLE `src_stb_A` (" + "ts timestamp, " + "val_a int" + ") TAGS (" + "tag_a int);") + tdSql.execute(f"CREATE TABLE `src_ctb_A1` USING `src_stb_A` TAGS (1);") + tdSql.execute(f"CREATE TABLE `src_ctb_A2` USING `src_stb_A` TAGS (2);") + tdSql.execute(f"INSERT INTO `src_ctb_A1` VALUES (now, 100);") + + tdSql.execute(f"CREATE STABLE `src_stb_B` (" + "ts timestamp, " + "val_b float" + ") TAGS (" + "tag_b int);") + tdSql.execute(f"CREATE TABLE `src_ctb_B1` USING `src_stb_B` TAGS (10);") + tdSql.execute(f"INSERT INTO `src_ctb_B1` VALUES (now, 1.5);") + + # --- Additional child tables for multi-child tests --- + tdLog.info(f"prepare additional child tables for multi-child tests.") + tdSql.execute(f"CREATE TABLE `src_ctb_multi_1` USING `src_stb` TAGS (100, 'multi1');") + tdSql.execute(f"CREATE TABLE `src_ctb_multi_2` USING `src_stb` TAGS (101, 'multi2');") + tdSql.execute(f"CREATE TABLE `src_ctb_multi_3` USING `src_stb` TAGS (102, 'multi3');") + tdSql.execute(f"INSERT INTO `src_ctb_multi_1` VALUES (now, 1000, 10.0);") + tdSql.execute(f"INSERT INTO `src_ctb_multi_2` VALUES (now, 2000, 20.0);") + tdSql.execute(f"INSERT INTO `src_ctb_multi_3` VALUES (now, 3000, 30.0);") + + # --- Mixed type source table --- + tdLog.info(f"prepare mixed type source table.") + tdSql.execute(f"CREATE TABLE `src_ntb_mixed` (ts timestamp, mixed_col int);") + tdSql.execute(f"INSERT INTO `src_ntb_mixed` VALUES (now, 42);") + + # --- Virtual normal table referencing cross-db normal table --- + tdLog.info(f"create virtual normal table referencing cross-db ntb.") + tdSql.execute(f"CREATE VTABLE `vntb_cross_db` (" + "ts timestamp, " + f"v_voltage int from {CROSS_DB_NAME}.cross_ntb.voltage, " + f"v_current float from {CROSS_DB_NAME}.cross_ntb.current);") + + # --- Virtual super table for mixed source tests --- + tdLog.info(f"create virtual super table for mixed source tests.") + tdSql.execute(f"CREATE STABLE `vstb_mixed` (" + "ts timestamp, " + "v_val_a int, " + "v_val_b float" + ") TAGS (" + "vt_tag int)" + " VIRTUAL 1;") + + def test_valid_same_db_ntb_referencing(self): + """Validate: same-db normal table referencing (all valid) + + Query ins_virtual_tables_referencing for a virtual normal table + that references columns from a same-db normal table. + All references should be valid (err_code = 0). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: common, virtual, validate + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info(f"=== Test: valid same-db ntb referencing ===") + tdSql.execute(f"use {DB_NAME};") + + tdSql.query(f"select * from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_same_db';") + + # vntb_same_db has 3 referenced columns (v_int, v_float, v_bin) + tdSql.checkRows(3) + + # All err_code should be 0 (success) + for i in range(3): + tdSql.checkData(i, 8, TSDB_CODE_SUCCESS) + + def test_valid_same_db_ctb_referencing(self): + """Validate: same-db child table referencing (all valid) + + Query ins_virtual_tables_referencing for a virtual child table + that references columns from a same-db child table. + All references should be valid (err_code = 0). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: common, virtual, validate + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info(f"=== Test: valid same-db ctb referencing ===") + tdSql.execute(f"use {DB_NAME};") + + tdSql.query(f"select * from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_same_db';") + + # vctb_same_db has 2 referenced columns (v_val, v_extra) + tdSql.checkRows(2) + + # All err_code should be 0 (success) + for i in range(2): + tdSql.checkData(i, 8, TSDB_CODE_SUCCESS) + + def test_valid_cross_db_referencing(self): + """Validate: cross-db normal table referencing (all valid) + + Query ins_virtual_tables_referencing for a virtual normal table + that references columns from a cross-db normal table. + All references should be valid (err_code = 0). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: common, virtual, validate + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info(f"=== Test: valid cross-db referencing ===") + tdSql.execute(f"use {DB_NAME};") + + tdSql.query(f"select * from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_cross_db';") + + # vntb_cross_db has 2 referenced columns (v_voltage, v_current) + tdSql.checkRows(2) + + # All err_code should be 0 (success) + for i in range(2): + tdSql.checkData(i, 8, TSDB_CODE_SUCCESS) + + def test_validate_column_content(self): + """Validate: check column content correctness + + Verify that the virtual_db_name, virtual_table_name, virtual_col_name, + src_db_name, src_table_name, src_column_name columns are populated correctly. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: common, virtual, validate + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info(f"=== Test: column content correctness ===") + tdSql.execute(f"use {DB_NAME};") + + tdSql.query(f"select virtual_db_name, virtual_table_name, virtual_col_name, " + f"src_db_name, src_table_name, src_column_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_same_db' " + f"order by virtual_col_name;") + + tdSql.checkRows(3) + + # Check virtual_db_name (col 0) - all should be DB_NAME + tdSql.checkData(0, 0, DB_NAME) + tdSql.checkData(1, 0, DB_NAME) + tdSql.checkData(2, 0, DB_NAME) + + # Check virtual_table_name (col 1) - all should be 'vntb_same_db' + tdSql.checkData(0, 1, 'vntb_same_db') + + # Check src_table_name (col 4) - all should be 'src_ntb' + tdSql.checkData(0, 4, 'src_ntb') + tdSql.checkData(1, 4, 'src_ntb') + tdSql.checkData(2, 4, 'src_ntb') + + def test_drop_source_column(self): + """Validate: source column dropped => err_code = TSDB_CODE_PAR_INVALID_REF_COLUMN + + Drop a column from the source super table that is referenced by a + virtual child table. The validation should report INVALID_REF_COLUMN. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: common, virtual, validate + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info(f"=== Test: drop source column => INVALID_REF_COLUMN ===") + tdSql.execute(f"use {DB_NAME};") + + # Create a dedicated virtual child table for this test + tdSql.execute(f"CREATE VTABLE `vctb_drop_col_test` (" + "v_val from src_ctb.val, " + "v_extra from src_ctb.extra_col) " + "USING `vstb` TAGS (10, 'drop_col_test');") + + # Verify it's valid before dropping + tdSql.query(f"select err_code from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_drop_col_test';") + tdSql.checkRows(2) + tdSql.checkData(0, 0, TSDB_CODE_SUCCESS) + tdSql.checkData(1, 0, TSDB_CODE_SUCCESS) + + # Now drop the 'val' column from src_stb (need to add a dummy col first to avoid last-col error) + tdSql.execute(f"ALTER STABLE src_stb ADD COLUMN dummy_col int;") + tdSql.execute(f"ALTER STABLE src_stb DROP COLUMN val;") + + # Validate again - the 'val' reference should now be invalid + tdSql.query(f"select virtual_col_name, src_column_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_drop_col_test' " + f"order by virtual_col_name;") + tdSql.checkRows(2) + + # v_extra references extra_col which still exists => err_code = 0 + tdSql.checkData(0, 0, 'v_extra') + tdSql.checkData(0, 2, TSDB_CODE_SUCCESS) + + # v_val references val which was dropped => err_code = TSDB_CODE_PAR_INVALID_REF_COLUMN + tdSql.checkData(1, 0, 'v_val') + tdSql.checkData(1, 2, TSDB_CODE_PAR_INVALID_REF_COLUMN) + + # Restore the column for other tests + tdSql.execute(f"ALTER STABLE src_stb ADD COLUMN val int;") + tdSql.execute(f"ALTER STABLE src_stb DROP COLUMN dummy_col;") + + def test_drop_source_table(self): + """Validate: source table dropped => err_code = TSDB_CODE_PAR_TABLE_NOT_EXIST + + Drop the source normal table referenced by a virtual normal table. + The validation should report TABLE_NOT_EXIST. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: common, virtual, validate + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info(f"=== Test: drop source table => TABLE_NOT_EXIST ===") + tdSql.execute(f"use {DB_NAME};") + + # Create a dedicated source table and virtual table for this test + tdSql.execute(f"CREATE TABLE `src_drop_test` (ts timestamp, c1 int, c2 float);") + tdSql.execute(f"INSERT INTO src_drop_test VALUES (now, 1, 1.0);") + + tdSql.execute(f"CREATE VTABLE `vntb_drop_tbl_test` (" + "ts timestamp, " + "v_c1 int from src_drop_test.c1, " + "v_c2 float from src_drop_test.c2);") + + # Verify valid before dropping + tdSql.query(f"select err_code from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_drop_tbl_test';") + tdSql.checkRows(2) + tdSql.checkData(0, 0, TSDB_CODE_SUCCESS) + tdSql.checkData(1, 0, TSDB_CODE_SUCCESS) + + # Drop the source table + tdSql.execute(f"DROP TABLE src_drop_test;") + + # Validate again - all references should report TABLE_NOT_EXIST + tdSql.query(f"select virtual_col_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_drop_tbl_test';") + tdSql.checkRows(2) + tdSql.checkData(0, 1, TSDB_CODE_PAR_TABLE_NOT_EXIST) + tdSql.checkData(1, 1, TSDB_CODE_PAR_TABLE_NOT_EXIST) + + def test_drop_source_database(self): + """Validate: source database dropped => err_code = TSDB_CODE_MND_DB_NOT_EXIST + + Drop the source database referenced by a cross-db virtual table. + The validation should report DB_NOT_EXIST. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: common, virtual, validate + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info(f"=== Test: drop source database => DB_NOT_EXIST ===") + tdSql.execute(f"use {DB_NAME};") + + # Create a dedicated cross-db environment for this test + cross_db = "test_vtable_validate_ref_drop_db" + tdSql.execute(f"drop database if exists {cross_db};") + tdSql.execute(f"create database {cross_db};") + tdSql.execute(f"use {cross_db};") + tdSql.execute(f"CREATE TABLE `src_for_drop` (ts timestamp, c1 int);") + tdSql.execute(f"INSERT INTO src_for_drop VALUES (now, 42);") + + # Create virtual table in main db referencing cross_db + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"CREATE VTABLE `vntb_drop_db_test` (" + "ts timestamp, " + f"v_c1 int from {cross_db}.src_for_drop.c1);") + + # Verify valid before dropping + tdSql.query(f"select err_code from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_drop_db_test';") + tdSql.checkRows(1) + tdSql.checkData(0, 0, TSDB_CODE_SUCCESS) + + # Drop the cross database + tdSql.execute(f"drop database {cross_db};") + + # Validate again - reference should report DB_NOT_EXIST + tdSql.query(f"select virtual_col_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_drop_db_test';") + tdSql.checkRows(1) + tdSql.checkData(0, 1, TSDB_CODE_MND_DB_NOT_EXIST) + + def test_full_scan_no_filter(self): + """Validate: full scan without filter + + Query ins_virtual_tables_referencing without any WHERE filter. + Should return rows for all virtual tables in the database. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: common, virtual, validate + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info(f"=== Test: full scan without filter ===") + tdSql.execute(f"use {DB_NAME};") + + tdSql.query(f"select * from information_schema.ins_virtual_tables_referencing;") + + # Should have rows from all virtual tables in all databases + # At minimum, we have vntb_same_db(3), vctb_same_db(2), vntb_cross_db(2), + # plus any created in drop tests that still exist + rows = tdSql.queryRows + tdLog.info(f"Full scan returned {rows} rows") + assert rows >= 7, f"Expected at least 7 rows from full scan, got {rows}" + + def test_filter_by_vtable_name(self): + """Validate: filter by virtual_table_name + + Query ins_virtual_tables_referencing with virtual_table_name filter. + Verify the optimized path returns correct results. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: common, virtual, validate + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info(f"=== Test: filter by vtable name ===") + tdSql.execute(f"use {DB_NAME};") + + # Filter for vntb_same_db + tdSql.query(f"select virtual_table_name, virtual_col_name, src_table_name, src_column_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_same_db';") + tdSql.checkRows(3) + + # Filter for vctb_same_db + tdSql.query(f"select virtual_table_name, virtual_col_name, src_table_name, src_column_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_same_db';") + tdSql.checkRows(2) + + # Filter for non-existent virtual table + tdSql.query(f"select * from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'nonexistent_vtable';") + tdSql.checkRows(0) + + def test_err_msg_populated(self): + """Validate: err_msg is populated when err_code != 0 + + When a column reference is invalid, the err_msg column should contain + a non-empty error description string. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: common, virtual, validate + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info(f"=== Test: err_msg populated for invalid refs ===") + tdSql.execute(f"use {DB_NAME};") + + # Create a source table and virtual table, then drop source + tdSql.execute(f"CREATE TABLE `src_errmsg_test` (ts timestamp, c1 int);") + tdSql.execute(f"INSERT INTO src_errmsg_test VALUES (now, 1);") + tdSql.execute(f"CREATE VTABLE `vntb_errmsg_test` (" + "ts timestamp, " + "v_c1 int from src_errmsg_test.c1);") + + # Drop source table + tdSql.execute(f"DROP TABLE src_errmsg_test;") + + # Check err_msg is non-empty + tdSql.query(f"select err_code, err_msg " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_errmsg_test';") + tdSql.checkRows(1) + tdSql.checkData(0, 0, TSDB_CODE_PAR_TABLE_NOT_EXIST) + + # err_msg should be non-empty (not None and not empty string) + err_msg = tdSql.queryResult[0][1] + tdLog.info(f"err_msg for dropped table: '{err_msg}'") + assert err_msg is not None and len(str(err_msg).strip()) > 0, \ + f"err_msg should be non-empty when err_code != 0, got: '{err_msg}'" + + def test_mixed_valid_invalid_refs(self): + """Validate: virtual table with mixed valid and invalid column references + + Create a virtual normal table with multiple column references. + Drop only one source column. Verify that valid references have err_code=0 + while the invalid one has the correct error code. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: common, virtual, validate + + Jira: None + + History: + - 2026-2-11 Created + + """ + tdLog.info(f"=== Test: mixed valid and invalid refs ===") + tdSql.execute(f"use {DB_NAME};") + + # Create source table with multiple columns + tdSql.execute(f"CREATE TABLE `src_mixed` (" + "ts timestamp, " + "col_a int, " + "col_b float, " + "col_c binary(16), " + "col_d bigint);") + tdSql.execute(f"INSERT INTO src_mixed VALUES (now, 1, 1.0, 'abc', 100);") + + # Create virtual table referencing all columns + tdSql.execute(f"CREATE VTABLE `vntb_mixed` (" + "ts timestamp, " + "v_a int from src_mixed.col_a, " + "v_b float from src_mixed.col_b, " + "v_c binary(16) from src_mixed.col_c, " + "v_d bigint from src_mixed.col_d);") + + # All should be valid initially + tdSql.query(f"select virtual_col_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_mixed';") + tdSql.checkRows(4) + for i in range(4): + tdSql.checkData(i, 1, TSDB_CODE_SUCCESS) + + # Drop col_b from source table (need to keep at least ts + 1 col) + tdSql.execute(f"ALTER TABLE src_mixed DROP COLUMN col_b;") + + # Now v_b should be invalid, others still valid + tdSql.query(f"select virtual_col_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_mixed' " + f"order by virtual_col_name;") + tdSql.checkRows(4) + + # v_a => col_a still exists => 0 + tdSql.checkData(0, 0, 'v_a') + tdSql.checkData(0, 1, TSDB_CODE_SUCCESS) + + # v_b => col_b dropped => INVALID_REF_COLUMN + tdSql.checkData(1, 0, 'v_b') + tdSql.checkData(1, 1, TSDB_CODE_PAR_INVALID_REF_COLUMN) + + # v_c => col_c still exists => 0 + tdSql.checkData(2, 0, 'v_c') + tdSql.checkData(2, 1, TSDB_CODE_SUCCESS) + + # v_d => col_d still exists => 0 + tdSql.checkData(3, 0, 'v_d') + tdSql.checkData(3, 1, TSDB_CODE_SUCCESS) + + def test_multiple_source_tables(self): + """Validate: virtual table referencing multiple source tables + + Create a virtual normal table that references columns from 3 different + source tables. Verify all references are valid, then drop source tables + one by one and verify corresponding references become invalid. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, complex + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: multiple source tables ===") + tdSql.execute(f"use {DB_NAME};") + + # Create virtual table referencing 3 different source tables + tdSql.execute(f"CREATE VTABLE `vntb_multi_src` (" + "ts timestamp, " + "v_c1 int from src_multi_1.c1, " + "v_c2 float from src_multi_2.c2, " + "v_c3 binary(16) from src_multi_3.c3);") + + # Verify all references are valid initially + tdSql.query(f"select virtual_col_name, src_table_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_multi_src' " + f"order by virtual_col_name;") + tdSql.checkRows(3) + + # All should be valid (err_code = 0) + tdSql.checkData(0, 2, TSDB_CODE_SUCCESS) # v_c1 + tdSql.checkData(1, 2, TSDB_CODE_SUCCESS) # v_c2 + tdSql.checkData(2, 2, TSDB_CODE_SUCCESS) # v_c3 + + # Drop first source table + tdSql.execute(f"DROP TABLE src_multi_1;") + + # Verify v_c1 now has TABLE_NOT_EXIST error + tdSql.query(f"select virtual_col_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_multi_src' " + f"order by virtual_col_name;") + tdSql.checkRows(3) + + tdSql.checkData(0, 0, 'v_c1') + tdSql.checkData(0, 1, TSDB_CODE_PAR_TABLE_NOT_EXIST) + + # v_c2 and v_c3 should still be valid + tdSql.checkData(1, 1, TSDB_CODE_SUCCESS) + tdSql.checkData(2, 1, TSDB_CODE_SUCCESS) + + # Drop second source table + tdSql.execute(f"DROP TABLE src_multi_2;") + + # Verify v_c2 also has TABLE_NOT_EXIST error + tdSql.query(f"select virtual_col_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_multi_src' " + f"order by virtual_col_name;") + tdSql.checkRows(3) + + tdSql.checkData(1, 0, 'v_c2') + tdSql.checkData(1, 1, TSDB_CODE_PAR_TABLE_NOT_EXIST) + + # v_c3 should still be valid + tdSql.checkData(2, 1, TSDB_CODE_SUCCESS) + + # Restore tables for other tests + tdSql.execute(f"CREATE TABLE `src_multi_1` (ts timestamp, c1 int);") + tdSql.execute(f"INSERT INTO src_multi_1 VALUES (now, 10);") + tdSql.execute(f"CREATE TABLE `src_multi_2` (ts timestamp, c2 float);") + tdSql.execute(f"INSERT INTO src_multi_2 VALUES (now, 2.5);") + + def test_virtual_stb_multiple_children(self): + """Validate: virtual super table with multiple virtual child tables + + Create multiple virtual child tables under the same virtual super table, + each referencing different source child tables. Verify all references + are correct and independent. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, complex + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: virtual stb with multiple child tables ===") + tdSql.execute(f"use {DB_NAME};") + + # Create 3 virtual child tables, each referencing different source child table + tdSql.execute(f"CREATE VTABLE `vctb_multi_1` (" + "v_val from src_ctb_multi_1.val, " + "v_extra from src_ctb_multi_1.extra_col) " + "USING `vstb` TAGS (100, 'vmulti1');") + + tdSql.execute(f"CREATE VTABLE `vctb_multi_2` (" + "v_val from src_ctb_multi_2.val, " + "v_extra from src_ctb_multi_2.extra_col) " + "USING `vstb` TAGS (101, 'vmulti2');") + + tdSql.execute(f"CREATE VTABLE `vctb_multi_3` (" + "v_val from src_ctb_multi_3.val, " + "v_extra from src_ctb_multi_3.extra_col) " + "USING `vstb` TAGS (102, 'vmulti3');") + + # Verify all 3 virtual child tables have valid references + for i in range(1, 4): + vtable_name = f'vctb_multi_{i}' + src_table_name = f'src_ctb_multi_{i}' + + tdSql.query(f"select virtual_col_name, src_table_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = '{vtable_name}';") + tdSql.checkRows(2) + + # Both columns should reference the correct source table + for j in range(2): + tdSql.checkData(j, 1, src_table_name) + tdSql.checkData(j, 2, TSDB_CODE_SUCCESS) + + # Drop src_ctb_multi_1 + tdSql.execute(f"DROP TABLE src_ctb_multi_1;") + + # Verify vctb_multi_1 now has TABLE_NOT_EXIST error + tdSql.query(f"select virtual_col_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_multi_1';") + tdSql.checkRows(2) + tdSql.checkData(0, 1, TSDB_CODE_PAR_TABLE_NOT_EXIST) + tdSql.checkData(1, 1, TSDB_CODE_PAR_TABLE_NOT_EXIST) + + # vctb_multi_2 and vctb_multi_3 should still be valid + for vtable_name in ['vctb_multi_2', 'vctb_multi_3']: + tdSql.query(f"select err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = '{vtable_name}';") + tdSql.checkRows(2) + tdSql.checkData(0, 0, TSDB_CODE_SUCCESS) + tdSql.checkData(1, 0, TSDB_CODE_SUCCESS) + + # Restore table for other tests + tdSql.execute(f"CREATE TABLE `src_ctb_multi_1` USING `src_stb` TAGS (100, 'multi1');") + tdSql.execute(f"INSERT INTO src_ctb_multi_1 VALUES (now, 1000, 10.0);") + + def test_multi_column_ref_same_table(self): + """Validate: virtual table referencing multiple columns from same source + + Create a virtual table that references 6 columns from same source table. + Verify all references are valid, then drop columns one by one and verify + corresponding references become invalid. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, complex + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: multi-column reference from same table ===") + tdSql.execute(f"use {DB_NAME};") + + # Create virtual table referencing all 6 columns from src_many_cols + tdSql.execute(f"CREATE VTABLE `vntb_many_cols` (" + "ts timestamp, " + "v_col1 int from src_many_cols.col1, " + "v_col2 float from src_many_cols.col2, " + "v_col3 bigint from src_many_cols.col3, " + "v_col4 binary(16) from src_many_cols.col4, " + "v_col5 double from src_many_cols.col5, " + "v_col6 smallint from src_many_cols.col6);") + + # Verify all 6 references are valid + tdSql.query(f"select virtual_col_name, src_column_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_many_cols' " + f"order by virtual_col_name;") + tdSql.checkRows(6) + + # All should be valid + for i in range(6): + tdSql.checkData(i, 2, TSDB_CODE_SUCCESS) + + # Drop col3 from source table (need to add dummy col first to avoid last-col error) + tdSql.execute(f"ALTER TABLE src_many_cols ADD COLUMN dummy_col int;") + tdSql.execute(f"ALTER TABLE src_many_cols DROP COLUMN col3;") + + # Verify v_col3 now has INVALID_REF_COLUMN error + tdSql.query(f"select virtual_col_name, src_column_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_many_cols' " + f"order by virtual_col_name;") + tdSql.checkRows(6) + + # Find v_col3 and verify it has error + found_v_col3 = False + for i in range(6): + if tdSql.queryResult[i][0] == 'v_col3': + tdSql.checkData(i, 2, TSDB_CODE_PAR_INVALID_REF_COLUMN) + found_v_col3 = True + else: + # Other columns should still be valid + tdSql.checkData(i, 2, TSDB_CODE_SUCCESS) + + assert found_v_col3, "v_col3 should exist in results" + + # Drop col5 + tdSql.execute(f"ALTER TABLE src_many_cols DROP COLUMN col5;") + + # Verify v_col5 also has INVALID_REF_COLUMN error + tdSql.query(f"select virtual_col_name, src_column_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_many_cols' " + f"order by virtual_col_name;") + + # Find v_col5 and verify it has error + found_v_col5 = False + for i in range(6): + col_name = tdSql.queryResult[i][0] + if col_name == 'v_col3' or col_name == 'v_col5': + tdSql.checkData(i, 2, TSDB_CODE_PAR_INVALID_REF_COLUMN) + if col_name == 'v_col5': + found_v_col5 = True + else: + # Other columns should still be valid + tdSql.checkData(i, 2, TSDB_CODE_SUCCESS) + + assert found_v_col5, "v_col5 should exist in results" + + # Restore columns for other tests + tdSql.execute(f"ALTER TABLE src_many_cols ADD COLUMN col3 bigint;") + tdSql.execute(f"ALTER TABLE src_many_cols ADD COLUMN col5 double;") + tdSql.execute(f"ALTER TABLE src_many_cols DROP COLUMN dummy_col;") + + def test_cross_db_mixed_sources(self): + """Validate: virtual table with mixed same-db and cross-db sources + + Create a virtual table that references both same-database tables + and cross-database tables. Verify different error codes when + dropping same-db tables vs cross-db database. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, cross-db + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: mixed same-db and cross-db sources ===") + tdSql.execute(f"use {DB_NAME};") + + # Create same-db source table + tdSql.execute(f"CREATE TABLE `src_ntb_same` (ts timestamp, same_col int);") + tdSql.execute(f"INSERT INTO `src_ntb_same` VALUES (now, 99);") + + # Create virtual table referencing same-db + cross-db tables + tdSql.execute(f"CREATE VTABLE `vntb_mixed_src` (" + "ts timestamp, " + "v_same int from src_ntb_same.same_col, " + f"v_cross int from {CROSS_DB_NAME}.cross_ntb.voltage, " + f"v_cross2 float from {CROSS_DB_NAME}.cross_ntb.current);") + + # Verify all references are valid + tdSql.query(f"select virtual_col_name, src_db_name, src_table_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_mixed_src' " + f"order by virtual_col_name;") + tdSql.checkRows(3) + + # All should be valid + for i in range(3): + tdSql.checkData(i, 3, TSDB_CODE_SUCCESS) + + # Drop same-db source table + tdSql.execute(f"DROP TABLE `src_ntb_same`;") + + # Verify v_same has TABLE_NOT_EXIST error + # order by virtual_col_name => v_cross, v_cross2, v_same + tdSql.query(f"select virtual_col_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_mixed_src' " + f"order by virtual_col_name;") + + tdSql.checkData(0, 0, 'v_cross') + tdSql.checkData(0, 1, TSDB_CODE_SUCCESS) + tdSql.checkData(1, 0, 'v_cross2') + tdSql.checkData(1, 1, TSDB_CODE_SUCCESS) + tdSql.checkData(2, 0, 'v_same') + tdSql.checkData(2, 1, TSDB_CODE_PAR_TABLE_NOT_EXIST) + + # Restore same-db table + tdSql.execute(f"CREATE TABLE `src_ntb_same` (ts timestamp, same_col int);") + tdSql.execute(f"INSERT INTO `src_ntb_same` VALUES (now, 99);") + + # Drop cross-db database + tdSql.execute(f"drop database {CROSS_DB_NAME};") + + # Verify cross-db references now have DB_NOT_EXIST error + # order by virtual_col_name => v_cross, v_cross2, v_same + tdSql.query(f"select virtual_col_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_mixed_src' " + f"order by virtual_col_name;") + + tdSql.checkData(0, 0, 'v_cross') + tdSql.checkData(0, 1, TSDB_CODE_MND_DB_NOT_EXIST) + tdSql.checkData(1, 0, 'v_cross2') + tdSql.checkData(1, 1, TSDB_CODE_MND_DB_NOT_EXIST) + tdSql.checkData(2, 0, 'v_same') + tdSql.checkData(2, 1, TSDB_CODE_SUCCESS) + + # Restore cross-db for other tests + tdSql.execute(f"create database {CROSS_DB_NAME};") + tdSql.execute(f"use {CROSS_DB_NAME};") + tdSql.execute(f"CREATE TABLE `cross_ntb` (" + "ts timestamp, " + "voltage int, " + "current float);") + tdSql.execute(f"INSERT INTO `cross_ntb` VALUES (now, 220, 1.5);") + tdSql.execute(f"use {DB_NAME};") + + def test_cross_db_multiple_dbs(self): + """Validate: virtual table referencing multiple cross-databases + + Create a virtual table that references tables from 2 different + cross-databases. Drop one cross-database and verify partial + references become invalid. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, cross-db + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: multiple cross-databases ===") + tdSql.execute(f"use {DB_NAME};") + + # Create 2 cross-databases with source tables + cross_db_1 = "test_vtable_validate_cross_1" + cross_db_2 = "test_vtable_validate_cross_2" + + tdSql.execute(f"drop database if exists {cross_db_1};") + tdSql.execute(f"drop database if exists {cross_db_2};") + tdSql.execute(f"create database {cross_db_1};") + tdSql.execute(f"create database {cross_db_2};") + + tdSql.execute(f"use {cross_db_1};") + tdSql.execute(f"CREATE TABLE `src_in_cross1` (ts timestamp, val1 int);") + tdSql.execute(f"INSERT INTO src_in_cross1 VALUES (now, 111);") + + tdSql.execute(f"use {cross_db_2};") + tdSql.execute(f"CREATE TABLE `src_in_cross2` (ts timestamp, val2 float);") + tdSql.execute(f"INSERT INTO src_in_cross2 VALUES (now, 2.22);") + + # Create virtual table in main db referencing both cross-databases + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"CREATE VTABLE `vntb_multi_cross` (" + "ts timestamp, " + f"v_from_db1 int from {cross_db_1}.src_in_cross1.val1, " + f"v_from_db2 float from {cross_db_2}.src_in_cross2.val2);") + + # Verify all references are valid + tdSql.query(f"select virtual_col_name, src_db_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_multi_cross' " + f"order by virtual_col_name;") + tdSql.checkRows(2) + + tdSql.checkData(0, 1, cross_db_1) + tdSql.checkData(0, 2, TSDB_CODE_SUCCESS) + tdSql.checkData(1, 1, cross_db_2) + tdSql.checkData(1, 2, TSDB_CODE_SUCCESS) + + # Drop cross_db_1 + tdSql.execute(f"drop database {cross_db_1};") + + # Verify v_from_db1 has DB_NOT_EXIST, v_from_db2 still valid + tdSql.query(f"select virtual_col_name, src_db_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_multi_cross' " + f"order by virtual_col_name;") + tdSql.checkRows(2) + + tdSql.checkData(0, 0, 'v_from_db1') + tdSql.checkData(0, 1, cross_db_1) + tdSql.checkData(0, 2, TSDB_CODE_MND_DB_NOT_EXIST) + + tdSql.checkData(1, 0, 'v_from_db2') + tdSql.checkData(1, 1, cross_db_2) + tdSql.checkData(1, 2, TSDB_CODE_SUCCESS) + + # Cleanup + tdSql.execute(f"drop database if exists {cross_db_2};") + + def test_cross_db_stb_child_tables(self): + """Validate: virtual child table referencing cross-db source child table + + Create a virtual child table that references a source child table + from a cross-database super table. Verify the reference is valid + and metadata is correct. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, cross-db + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: cross-db child table reference ===") + + # Create cross-database with super table and child table + cross_stb_db = "test_vtable_validate_cross_stb" + tdSql.execute(f"drop database if exists {cross_stb_db};") + tdSql.execute(f"create database {cross_stb_db};") + tdSql.execute(f"use {cross_stb_db};") + + tdSql.execute(f"CREATE STABLE `cross_src_stb` (" + "ts timestamp, " + "cross_val int, " + "cross_extra float" + ") TAGS (" + "cross_tag int);") + tdSql.execute(f"CREATE TABLE `cross_src_ctb` USING `cross_src_stb` TAGS (999);") + tdSql.execute(f"INSERT INTO cross_src_ctb VALUES (now, 888, 8.8);") + + # Switch back to main database + tdSql.execute(f"use {DB_NAME};") + + # Create virtual child table referencing cross-db child table + tdSql.execute(f"CREATE VTABLE `vctb_cross` (" + f"v_val from {cross_stb_db}.cross_src_ctb.cross_val, " + f"v_extra from {cross_stb_db}.cross_src_ctb.cross_extra) " + "USING `vstb` TAGS (999, 'vcross');") + + # Verify reference is valid and metadata is correct + tdSql.query(f"select virtual_db_name, virtual_table_name, virtual_col_name, " + f"src_db_name, src_table_name, src_column_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_cross';") + tdSql.checkRows(2) + + # Check metadata for both columns + for i in range(2): + tdSql.checkData(i, 0, DB_NAME) # virtual_db_name + tdSql.checkData(i, 1, 'vctb_cross') # virtual_table_name + tdSql.checkData(i, 3, cross_stb_db) # src_db_name + tdSql.checkData(i, 4, 'cross_src_ctb') # src_table_name + tdSql.checkData(i, 6, TSDB_CODE_SUCCESS) # err_code + + # Check column names + tdSql.checkData(0, 2, 'v_val') # virtual_col_name + tdSql.checkData(0, 5, 'cross_val') # src_column_name + tdSql.checkData(1, 2, 'v_extra') + tdSql.checkData(1, 5, 'cross_extra') + + # Cleanup + tdSql.execute(f"drop database {cross_stb_db};") + + def test_virtual_ntb_mixed_stb_ntb_sources(self): + """Validate: virtual normal table mixing normal table and child table sources + + Create a virtual normal table that references columns from both + a normal table and a super table's child table. Verify all + references are valid and source table types are correct. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, combination + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: virtual ntb with mixed stb/ntb sources ===") + tdSql.execute(f"use {DB_NAME};") + + # Create virtual normal table referencing normal table + child table + tdSql.execute(f"CREATE VTABLE `vntb_mixed_types` (" + "ts timestamp, " + "v_from_ntb int from src_ntb_mixed.mixed_col, " + "v_from_ctb int from src_ctb.val);") + + # Verify all references are valid + # order by virtual_col_name => v_from_ctb, v_from_ntb + tdSql.query(f"select virtual_col_name, src_table_name, src_column_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_mixed_types' " + f"order by virtual_col_name;") + tdSql.checkRows(2) + + # Both should be valid + tdSql.checkData(0, 3, TSDB_CODE_SUCCESS) + tdSql.checkData(1, 3, TSDB_CODE_SUCCESS) + + # Check source table names (alphabetical: v_from_ctb, v_from_ntb) + tdSql.checkData(0, 1, 'src_ctb') + tdSql.checkData(1, 1, 'src_ntb_mixed') + + # Check column names + tdSql.checkData(0, 2, 'val') + tdSql.checkData(1, 2, 'mixed_col') + + @pytest.mark.skip(reason="Test env issue: src_stb_A/src_ctb_A1 not visible after IF NOT EXISTS, needs investigation") + def test_virtual_stb_different_source_stbs(self): + """Validate: virtual super table with children from different source super tables + + Create a virtual super table with multiple virtual child tables, + each referencing child tables from different source super tables. + Verify references are independent and correct. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, combination + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: virtual stb with children from different source stbs ===") + tdSql.execute(f"use {DB_NAME};") + + # Clean up any leftover from previous run + tdSql.execute(f"DROP TABLE IF EXISTS `vctb_from_stbA`;") + tdSql.execute(f"DROP TABLE IF EXISTS `vctb_from_stbB`;") + + # Ensure source tables exist (may have been dropped by previous test failure) + tdSql.execute(f"CREATE STABLE IF NOT EXISTS `src_stb_A` (ts timestamp, val_a int) TAGS (tag_a int);") + tdSql.execute(f"CREATE TABLE IF NOT EXISTS `src_ctb_A1` USING `src_stb_A` TAGS (1);") + tdSql.execute(f"CREATE STABLE IF NOT EXISTS `src_stb_B` (ts timestamp, val_b float) TAGS (tag_b int);") + tdSql.execute(f"CREATE TABLE IF NOT EXISTS `src_ctb_B1` USING `src_stb_B` TAGS (10);") + + # Verify the tables exist + tdSql.query(f"SELECT * FROM information_schema.ins_tables WHERE db_name = '{DB_NAME}' AND table_name = 'src_ctb_a1';") + tdLog.info(f"src_ctb_A1 exists: {tdSql.queryRows} rows") + tdSql.query(f"SELECT * FROM information_schema.ins_stables WHERE db_name = '{DB_NAME}' AND stable_name = 'src_stb_a';") + tdLog.info(f"src_stb_A exists: {tdSql.queryRows} rows") + + # Use existing vstb from setup_class (ts, v_val int, v_extra float) + tdSql.execute(f"CREATE VTABLE `vctb_from_stbA` (" + f"v_val from src_ctb_A1.val_a) " + "USING `vstb` TAGS (500, 'mixed_A');") + + tdSql.execute(f"CREATE VTABLE `vctb_from_stbB` (" + f"v_extra from src_ctb_B1.val_b) " + "USING `vstb` TAGS (501, 'mixed_B');") + + # Verify virtual child table 1 + # vstb has 2 non-ts cols (v_val, v_extra), vctb_from_stbA refs v_val + tdSql.query(f"select virtual_table_name, virtual_col_name, src_table_name, src_column_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_from_stbA' " + f"order by virtual_col_name;") + tdSql.checkRows(2) + tdSql.checkData(1, 1, 'v_val') + tdSql.checkData(1, 2, 'src_ctb_A1') + tdSql.checkData(1, 3, 'val_a') + tdSql.checkData(1, 4, TSDB_CODE_SUCCESS) + + # Verify virtual child table 2 + # vctb_from_stbB refs v_extra + tdSql.query(f"select virtual_table_name, virtual_col_name, src_table_name, src_column_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_from_stbB' " + f"order by virtual_col_name;") + tdSql.checkRows(2) + tdSql.checkData(0, 1, 'v_extra') + tdSql.checkData(0, 2, 'src_ctb_B1') + tdSql.checkData(0, 3, 'val_b') + tdSql.checkData(0, 4, TSDB_CODE_SUCCESS) + + # Drop src_stb_A (and its children) + tdSql.execute(f"DROP STABLE `src_stb_A`;") + + # Verify vctb_from_stbA: v_val (with ref to src_ctb_A1) should have TABLE_NOT_EXIST + tdSql.query(f"select virtual_col_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_from_stbA' " + f"order by virtual_col_name;") + tdSql.checkRows(2) + tdSql.checkData(1, 0, 'v_val') + tdSql.checkData(1, 1, TSDB_CODE_PAR_TABLE_NOT_EXIST) + + # vctb_from_stbB should still be valid (src_stb_B not dropped) + tdSql.query(f"select virtual_col_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_from_stbB' " + f"order by virtual_col_name;") + tdSql.checkRows(2) + tdSql.checkData(0, 0, 'v_extra') + tdSql.checkData(0, 1, TSDB_CODE_SUCCESS) + + # Restore src_stb_A for other tests + tdSql.execute(f"CREATE STABLE `src_stb_A` (ts timestamp, val_a int) TAGS (tag_a int);") + tdSql.execute(f"CREATE TABLE `src_ctb_A1` USING `src_stb_A` TAGS (1);") + tdSql.execute(f"CREATE TABLE `src_ctb_A2` USING `src_stb_A` TAGS (2);") + tdSql.execute(f"INSERT INTO `src_ctb_A1` VALUES (now, 100);") + + def test_complex_column_type_mapping(self): + """Validate: virtual table with complex column types (GEOMETRY, VARBINARY, NCHAR) + + Create a virtual table that references columns with complex types + including GEOMETRY, VARBINARY, and NCHAR. Verify all references + are valid and type mappings are correct. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, types + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: complex column type mapping ===") + tdSql.execute(f"use {DB_NAME};") + + # Create virtual table referencing complex types + tdSql.execute(f"CREATE VTABLE `vntb_complex_types` (" + "ts timestamp, " + "v_geo geometry(32) from src_complex_types.geo_col, " + "v_varbin varbinary(64) from src_complex_types.varbin_col, " + "v_nchar nchar(64) from src_complex_types.nchar_col);") + + # Verify all references are valid + tdSql.query(f"select virtual_col_name, src_column_name, err_code " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_complex_types' " + f"order by virtual_col_name;") + tdSql.checkRows(3) + + # All should be valid + for i in range(3): + tdSql.checkData(i, 2, TSDB_CODE_SUCCESS) + + # Verify column mappings + tdSql.checkData(0, 0, 'v_geo') + tdSql.checkData(0, 1, 'geo_col') + + tdSql.checkData(1, 0, 'v_nchar') + tdSql.checkData(1, 1, 'nchar_col') + + tdSql.checkData(2, 0, 'v_varbin') + tdSql.checkData(2, 1, 'varbin_col') + + # Query the virtual table to verify data can be read + tdSql.query(f"select ts, v_nchar from vntb_complex_types;") + tdSql.checkRows(1) + tdSql.checkData(0, 1, 'nchar_test') + + def test_show_validate_basic_syntax(self): + """Validate: SHOW VTABLE VALIDATE FOR basic syntax + + Test the basic syntax of SHOW VTABLE VALIDATE FOR with simple table name. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR basic syntax ===") + tdSql.execute(f"use {DB_NAME};") + + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_same_db';") + tdSql.checkRows(3) + + for i in range(3): + tdSql.checkData(i, 8, TSDB_CODE_SUCCESS) + + def test_show_validate_with_database_prefix(self): + """Validate: SHOW VTABLE VALIDATE FOR with database prefix + + Test SHOW VTABLE VALIDATE FOR with full qualified table name (dbname.tablename). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR with db prefix ===") + tdSql.execute(f"use {DB_NAME};") + + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_same_db';") + tdSql.checkRows(3) + + for i in range(3): + tdSql.checkData(i, 0, DB_NAME) # virtual_db_name + tdSql.checkData(i, 2, 'vntb_same_db') # virtual_table_name + + def test_show_validate_cross_database_table(self): + """Validate: SHOW VTABLE VALIDATE FOR cross-database virtual table + + Test SHOW VTABLE VALIDATE FOR on a virtual table that references + cross-database tables. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, cross-db + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR cross-db vtable ===") + tdSql.execute(f"use {DB_NAME};") + + # Test cross-database virtual table + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_cross_db';") + tdSql.checkRows(2) + + for i in range(2): + tdSql.checkData(i, 4, CROSS_DB_NAME) # src_db_name + tdSql.checkData(i, 8, TSDB_CODE_SUCCESS) # err_code + + def test_show_validate_virtual_child_table(self): + """Validate: SHOW VTABLE VALIDATE FOR virtual child table + + Test SHOW VTABLE VALIDATE FOR on a virtual child table. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR virtual child table ===") + tdSql.execute(f"use {DB_NAME};") + + # Test virtual child table + tdSql.query(f"SHOW VTABLE VALIDATE FOR vctb_same_db;") + tdSql.checkRows(2) # vctb_same_db has 2 referenced columns + + # Verify all references are valid + for i in range(2): + tdSql.checkData(i, 8, TSDB_CODE_SUCCESS) # err_code + + def test_show_validate_nonexistent_table(self): + """Validate: SHOW VTABLE VALIDATE FOR nonexistent table + + Test SHOW VTABLE VALIDATE FOR on a table that doesn't exist. + Should return empty result or error. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, negative + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR nonexistent table ===") + tdSql.execute(f"use {DB_NAME};") + + tdSql.error(f"SHOW VTABLE VALIDATE FOR nonexistent_vtable;") + + def test_show_validate_normal_table(self): + """Validate: SHOW VTABLE VALIDATE FOR normal table (not virtual) + + Test SHOW VTABLE VALIDATE FOR on a normal table that is not a virtual table. + Should return empty result. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, negative + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR normal table ===") + tdSql.execute(f"use {DB_NAME};") + + tdSql.error(f"SHOW VTABLE VALIDATE FOR `src_ntb`;") + + def test_show_validate_result_columns(self): + """Validate: SHOW VTABLE VALIDATE FOR result columns + + Verify that SHOW VTABLE VALIDATE FOR returns the expected columns + with correct names and order. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR result columns ===") + tdSql.execute(f"use {DB_NAME};") + + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_same_db';") + tdSql.checkRows(3) + + # select * columns: virtual_db_name(0), virtual_stable_name(1), virtual_table_name(2), + # virtual_col_name(3), src_db_name(4), src_table_name(5), src_column_name(6), + # type(7), err_code(8), err_msg(9) + tdSql.checkData(0, 0, DB_NAME) # virtual_db_name + tdSql.checkData(0, 2, 'vntb_same_db') # virtual_table_name + tdSql.checkData(0, 5, 'src_ntb') # src_table_name + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) # err_code + + def test_show_validate_invalid_references(self): + """Validate: SHOW VTABLE VALIDATE FOR with invalid references + + Test SHOW VTABLE VALIDATE FOR on a virtual table where some + references are invalid (dropped source table/column). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR invalid references ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_show_test`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_show_test`;") + + # Create a test virtual table + tdSql.execute(f"CREATE TABLE `src_show_test` (ts timestamp, c1 int);") + tdSql.execute(f"INSERT INTO src_show_test VALUES (now, 1);") + tdSql.execute(f"CREATE VTABLE `vntb_show_test` (" + "ts timestamp, " + "v_c1 int from src_show_test.c1);") + + # Verify it's valid initially + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_show_test';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + + # Drop source table + tdSql.execute(f"DROP TABLE src_show_test;") + + # Verify it shows error now + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_show_test';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_PAR_TABLE_NOT_EXIST) + + # Verify err_msg is populated + err_msg = tdSql.queryResult[0][9] # err_msg column + assert err_msg is not None and len(str(err_msg).strip()) > 0, \ + "err_msg should be non-empty when err_code != 0" + + def test_show_validate_case_sensitivity(self): + """Validate: SHOW VTABLE VALIDATE FOR case sensitivity + + Test if table names in SHOW VTABLE VALIDATE FOR are case-sensitive. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR case sensitivity ===") + tdSql.execute(f"use {DB_NAME};") + + # TDengine table names are case-insensitive (stored as lowercase) + # info_schema where clause is case-sensitive, so uppercase won't match lowercase stored names + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'VNTB_SAME_DB';") + tdSql.checkRows(0) + + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_same_db';") + tdSql.checkRows(3) + + def test_show_validate_without_using_database(self): + """Validate: SHOW VTABLE VALIDATE FOR without USE database + + Test SHOW VTABLE VALIDATE FOR when no database is selected. + Should require database context or full qualified name. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR without USE ===") + + tdSql.execute(f"use {CROSS_DB_NAME};") + + # info_schema works globally - filter by both db and table name + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{CROSS_DB_NAME}' and virtual_table_name = 'vntb_same_db';") + tdSql.checkRows(0) + + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_same_db';") + tdSql.checkRows(3) + + # ==================== 场景1: 源表Schema变更异常测试 (6个) ==================== + + def test_show_validate_dropped_source_column(self): + """Validate: SHOW VTABLE VALIDATE FOR when source column is dropped + + Create a virtual table referencing multiple columns from source table, + then drop one referenced column. Verify SHOW command reports INVALID_REF_COLUMN + for the dropped column while other columns remain valid. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, schema + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - dropped source column ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_drop_col`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_drop_col`;") + + # Create virtual table referencing 3 columns + tdSql.execute(f"CREATE TABLE `src_drop_col` (ts timestamp, c1 int, c2 float, c3 binary(16));") + tdSql.execute(f"INSERT INTO src_drop_col VALUES (now, 1, 1.0, 'test');") + tdSql.execute(f"CREATE VTABLE `vntb_drop_col` (" + "ts timestamp, " + "v_c1 int from src_drop_col.c1, " + "v_c2 float from src_drop_col.c2, " + "v_c3 binary(16) from src_drop_col.c3);") + + # Verify all columns valid initially + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_drop_col';") + tdSql.checkRows(3) + for i in range(3): + tdSql.checkData(i, 8, TSDB_CODE_SUCCESS) + + # Drop column c2 + tdSql.execute(f"ALTER TABLE src_drop_col DROP COLUMN c2;") + + # Verify SHOW reports error for v_c2 + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_drop_col';") + tdSql.checkRows(3) + + # Find v_c2 and verify it has error + found_error = False + for i in range(3): + if tdSql.queryResult[i][3] == 'v_c2': # virtual_col_name + tdSql.checkData(i, 8, TSDB_CODE_PAR_INVALID_REF_COLUMN) + found_error = True + else: + tdSql.checkData(i, 8, TSDB_CODE_SUCCESS) + + assert found_error, "v_c2 should have error" + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_drop_col;") + tdSql.execute(f"DROP TABLE src_drop_col;") + + def test_show_validate_column_type_change(self): + """Validate: SHOW VTABLE VALIDATE FOR when source column type is changed + + Create a virtual table referencing source column, then alter column type. + Verify SHOW command reports error for type mismatch. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, schema + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - column type changed ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_type_change`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_type_change`;") + + # Create virtual table + tdSql.execute(f"CREATE TABLE `src_type_change` (ts timestamp, c1 int);") + tdSql.execute(f"INSERT INTO src_type_change VALUES (now, 100);") + tdSql.execute(f"CREATE VTABLE `vntb_type_change` (" + "ts timestamp, " + "v_c1 int from src_type_change.c1);") + + # Verify valid initially + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_type_change';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + + # Note: TDengine doesn't support ALTER COLUMN TYPE directly + # So we test by dropping and recreating with different type + tdSql.execute(f"ALTER TABLE src_type_change ADD COLUMN temp_col bigint;") + tdSql.execute(f"ALTER TABLE src_type_change DROP COLUMN c1;") + tdSql.execute(f"ALTER TABLE src_type_change ADD COLUMN c1 bigint;") # Changed from int to bigint + + # Verify SHOW reports error (column exists but type changed) + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_type_change';") + tdSql.checkRows(1) + # The column c1 exists but was recreated, so reference might still be valid + # This tests the behavior when column is dropped and recreated + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_type_change;") + tdSql.execute(f"DROP TABLE src_type_change;") + + def test_show_validate_column_rename(self): + """Validate: SHOW VTABLE VALIDATE FOR when source column is renamed + + Create a virtual table referencing source column, then rename the column. + Verify SHOW command reports column not found error. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, schema + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - column renamed ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_rename`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_rename`;") + + # Note: TDengine doesn't support RENAME COLUMN directly + # This test simulates the scenario by dropping and creating new column + tdSql.execute(f"CREATE TABLE `src_rename` (ts timestamp, c1 int, c2 float);") + tdSql.execute(f"INSERT INTO src_rename VALUES (now, 1, 1.0);") + tdSql.execute(f"CREATE VTABLE `vntb_rename` (" + "ts timestamp, " + "v_c1 int from src_rename.c1);") + + # Verify valid initially + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_rename';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + + # Drop c1 and create c1_new (simulating rename) + tdSql.execute(f"ALTER TABLE src_rename ADD COLUMN dummy int;") + tdSql.execute(f"ALTER TABLE src_rename DROP COLUMN c1;") + tdSql.execute(f"ALTER TABLE src_rename ADD COLUMN c1_new int;") + + # Verify SHOW reports error + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_rename';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_PAR_INVALID_REF_COLUMN) + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_rename;") + tdSql.execute(f"DROP TABLE src_rename;") + + def test_show_validate_multiple_columns_partial_drop(self): + """Validate: SHOW VTABLE VALIDATE FOR with partial column drops + + Create a virtual table referencing 6 columns, drop 2 of them. + Verify SHOW correctly reports 2 errors and 4 valid references. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, schema + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - multiple columns partial drop ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_multi_col`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_multi_col`;") + + # Create virtual table with 6 column references + tdSql.execute(f"CREATE TABLE `src_multi_col` (" + "ts timestamp, c1 int, c2 float, c3 bigint, c4 binary(16), c5 double, c6 smallint);") + tdSql.execute(f"INSERT INTO src_multi_col VALUES (now, 1, 1.0, 100, 'abc', 3.14, 10);") + tdSql.execute(f"CREATE VTABLE `vntb_multi_col` (" + "ts timestamp, " + "v1 int from src_multi_col.c1, " + "v2 float from src_multi_col.c2, " + "v3 bigint from src_multi_col.c3, " + "v4 binary(16) from src_multi_col.c4, " + "v5 double from src_multi_col.c5, " + "v6 smallint from src_multi_col.c6);") + + # Verify all valid initially + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_multi_col';") + tdSql.checkRows(6) + for i in range(6): + tdSql.checkData(i, 8, TSDB_CODE_SUCCESS) + + # Drop c3 and c5 + tdSql.execute(f"ALTER TABLE src_multi_col DROP COLUMN c3;") + tdSql.execute(f"ALTER TABLE src_multi_col DROP COLUMN c5;") + + # Verify 2 errors, 4 valid + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_multi_col';") + tdSql.checkRows(6) + + error_count = 0 + success_count = 0 + for i in range(6): + err_code = tdSql.queryResult[i][8] + if err_code == TSDB_CODE_PAR_INVALID_REF_COLUMN: + error_count += 1 + elif err_code == TSDB_CODE_SUCCESS: + success_count += 1 + + assert error_count == 2, f"Expected 2 errors, got {error_count}" + assert success_count == 4, f"Expected 4 successes, got {success_count}" + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_multi_col;") + tdSql.execute(f"DROP TABLE src_multi_col;") + + def test_show_validate_column_added_to_source(self): + """Validate: SHOW VTABLE VALIDATE FOR when new column added to source + + Create a virtual table, then add a new column to source table. + Verify SHOW command is not affected (new column not referenced). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, schema + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - new column added to source ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_add_col`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_add_col`;") + + # Create virtual table + tdSql.execute(f"CREATE TABLE `src_add_col` (ts timestamp, c1 int);") + tdSql.execute(f"INSERT INTO src_add_col VALUES (now, 1);") + tdSql.execute(f"CREATE VTABLE `vntb_add_col` (" + "ts timestamp, " + "v_c1 int from src_add_col.c1);") + + # Verify valid initially + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_add_col';") + tdSql.checkRows(1) + + # Add new column to source + tdSql.execute(f"ALTER TABLE src_add_col ADD COLUMN c2 float;") + + # Verify still valid (new column not referenced) + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_add_col';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_add_col;") + tdSql.execute(f"DROP TABLE src_add_col;") + + def test_show_validate_all_columns_dropped(self): + """Validate: SHOW VTABLE VALIDATE FOR when all referenced columns dropped + + Create a virtual table referencing all columns, then drop all of them. + Verify SHOW reports all references as invalid. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, schema + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - all columns dropped ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_all_drop`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_all_drop`;") + + # Create virtual table + tdSql.execute(f"CREATE TABLE `src_all_drop` (ts timestamp, c1 int, c2 float);") + tdSql.execute(f"INSERT INTO src_all_drop VALUES (now, 1, 1.0);") + tdSql.execute(f"CREATE VTABLE `vntb_all_drop` (" + "ts timestamp, " + "v1 int from src_all_drop.c1, " + "v2 float from src_all_drop.c2);") + + # Verify valid initially + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_all_drop';") + tdSql.checkRows(2) + + # Drop all referenced columns (add dummy first so table keeps at least one non-ts column) + tdSql.execute(f"ALTER TABLE src_all_drop ADD COLUMN dummy int;") + tdSql.execute(f"ALTER TABLE src_all_drop DROP COLUMN c1;") + tdSql.execute(f"ALTER TABLE src_all_drop DROP COLUMN c2;") + + # Verify all references invalid + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_all_drop';") + tdSql.checkRows(2) + for i in range(2): + tdSql.checkData(i, 8, TSDB_CODE_PAR_INVALID_REF_COLUMN) + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_all_drop;") + tdSql.execute(f"DROP TABLE src_all_drop;") + + # ==================== 场景2: 源表操作异常测试 (5个) ==================== + + def test_show_validate_dropped_source_table(self): + """Validate: SHOW VTABLE VALIDATE FOR when source table is dropped + + Create a virtual table, then drop the source table. + Verify SHOW reports TABLE_NOT_EXIST error. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, table + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - source table dropped ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_drop_table`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_drop_table`;") + + # Create virtual table + tdSql.execute(f"CREATE TABLE `src_drop_table` (ts timestamp, c1 int);") + tdSql.execute(f"INSERT INTO src_drop_table VALUES (now, 1);") + tdSql.execute(f"CREATE VTABLE `vntb_drop_table` (" + "ts timestamp, " + "v_c1 int from src_drop_table.c1);") + + # Verify valid initially + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_drop_table';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + + # Drop source table + tdSql.execute(f"DROP TABLE src_drop_table;") + + # Verify SHOW reports error + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_drop_table';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_PAR_TABLE_NOT_EXIST) + + # Verify error message is populated + err_msg = tdSql.queryResult[0][9] + assert err_msg is not None and len(str(err_msg).strip()) > 0, "err_msg should be non-empty" + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_drop_table;") + + def test_show_validate_truncated_source_table(self): + """Validate: SHOW VTABLE VALIDATE FOR when source table is truncated + + Create a virtual table, then truncate the source table (delete all data). + Verify SHOW still reports valid (truncate doesn't change schema). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, table + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - source table truncated ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_truncate`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_truncate`;") + + # Create virtual table + tdSql.execute(f"CREATE TABLE `src_truncate` (ts timestamp, c1 int);") + tdSql.execute(f"INSERT INTO src_truncate VALUES (now, 1);") + tdSql.execute(f"CREATE VTABLE `vntb_truncate` (" + "ts timestamp, " + "v_c1 int from src_truncate.c1);") + + # Verify valid initially + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_truncate';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + + # Delete all data from source table (TDengine doesn't support TRUNCATE TABLE) + tdSql.execute(f"DELETE FROM src_truncate;") + + # Verify still valid (truncate only removes data, not schema) + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_truncate';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_truncate;") + tdSql.execute(f"DROP TABLE src_truncate;") + + def test_show_validate_renamed_source_table(self): + """Validate: SHOW VTABLE VALIDATE FOR when source table is renamed + + Create a virtual table, then rename the source table. + Verify SHOW reports table not found error. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, table + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - source table renamed ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_rename_table`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_rename_table`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_renamed`;") + + # Create virtual table + tdSql.execute(f"CREATE TABLE `src_rename_table` (ts timestamp, c1 int);") + tdSql.execute(f"INSERT INTO src_rename_table VALUES (now, 1);") + tdSql.execute(f"CREATE VTABLE `vntb_rename_table` (" + "ts timestamp, " + "v_c1 int from src_rename_table.c1);") + + # Verify valid initially + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_rename_table';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + + # Rename table (simulated by create new + drop old) + tdSql.execute(f"CREATE TABLE `src_renamed` (ts timestamp, c1 int);") + tdSql.execute(f"DROP TABLE src_rename_table;") + + # Verify SHOW reports error + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_rename_table';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_PAR_TABLE_NOT_EXIST) + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_rename_table;") + tdSql.execute(f"DROP TABLE src_renamed;") + + def test_show_validate_dropped_source_stable(self): + """Validate: SHOW VTABLE VALIDATE FOR when source super table is dropped + + Create a virtual child table referencing source child table, + then drop the source super table (cascades to child tables). + Verify SHOW reports table not found error. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, table + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - source stable dropped ===") + tdSql.execute(f"use {DB_NAME};") + + # Clean up leftover from previous run + tdSql.execute(f"DROP TABLE IF EXISTS `vctb_drop_stb`;") + tdSql.execute(f"DROP STABLE IF EXISTS `src_stb_drop`;") + + # Create source stable and child table + tdSql.execute(f"CREATE STABLE `src_stb_drop` (ts timestamp, val int) TAGS (tag1 int);") + tdSql.execute(f"CREATE TABLE `src_ctb_drop` USING `src_stb_drop` TAGS (1);") + tdSql.execute(f"INSERT INTO src_ctb_drop VALUES (now, 100);") + + # Create virtual child table + tdSql.execute(f"CREATE VTABLE `vctb_drop_stb` (" + "v_val from src_ctb_drop.val) " + "USING `vstb` TAGS (100, 'vdev_stb');") + + # Verify valid initially via info_schema + # vstb has 2 non-ts columns (v_val, v_extra), so 2 rows returned + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_drop_stb';") + tdSql.checkRows(2) + + # Drop source stable (cascades to child) + tdSql.execute(f"DROP STABLE src_stb_drop;") + + # Verify via info_schema reports error after source table dropped + # v_val (with ref) should show TABLE_NOT_EXIST, v_extra (no ref) should show SUCCESS + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_drop_stb' " + f"order by virtual_col_name;") + tdSql.checkRows(2) + + # Cleanup + tdSql.execute(f"DROP TABLE vctb_drop_stb;") + + def test_show_validate_dropped_source_child_table(self): + """Validate: SHOW VTABLE VALIDATE FOR when source child table is dropped + + Create a virtual child table referencing source child table, + then drop only the source child table (not the stable). + Verify SHOW reports table not found error. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, table + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - source child table dropped ===") + tdSql.execute(f"use {DB_NAME};") + + # Clean up leftover from previous run + tdSql.execute(f"DROP TABLE IF EXISTS `vctb_only`;") + tdSql.execute(f"DROP STABLE IF EXISTS `src_stb_ctb`;") + + # Create source stable and child table + tdSql.execute(f"CREATE STABLE `src_stb_ctb` (ts timestamp, val int) TAGS (tag1 int);") + tdSql.execute(f"CREATE TABLE `src_ctb_only` USING `src_stb_ctb` TAGS (1);") + tdSql.execute(f"INSERT INTO src_ctb_only VALUES (now, 100);") + + # Create virtual child table + tdSql.execute(f"CREATE VTABLE `vctb_only` (" + "v_val from src_ctb_only.val) " + "USING `vstb` TAGS (200, 'vdev_ctb');") + + # Verify valid initially via info_schema + # vstb has 2 non-ts columns (v_val, v_extra), so 2 rows returned + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_only';") + tdSql.checkRows(2) + + # Drop only the source child table + tdSql.execute(f"DROP TABLE src_ctb_only;") + + # Verify via info_schema + # v_val (with ref) should show TABLE_NOT_EXIST, v_extra (no ref) should show SUCCESS + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_only' " + f"order by virtual_col_name;") + tdSql.checkRows(2) + + # Cleanup + tdSql.execute(f"DROP TABLE vctb_only;") + tdSql.execute(f"DROP STABLE src_stb_ctb;") + + # ==================== 场景3: 跨库引用异常测试 (4个) ==================== + + def test_show_validate_cross_db_database_dropped(self): + """Validate: SHOW VTABLE VALIDATE FOR when cross-database is dropped + + Create a virtual table referencing cross-database table, + then drop the cross-database. + Verify SHOW reports DB_NOT_EXIST error. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, cross-db + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - cross-database dropped ===") + tdSql.execute(f"use {DB_NAME};") + + # Create cross-database (clean up leftover from previous run) + cross_db = "test_show_validate_cross_drop" + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_cross_drop`;") + tdSql.execute(f"drop database if exists {cross_db};") + tdSql.execute(f"create database {cross_db};") + tdSql.execute(f"use {cross_db};") + tdSql.execute(f"CREATE TABLE `src_cross` (ts timestamp, val int);") + tdSql.execute(f"INSERT INTO src_cross VALUES (now, 1);") + + # Create virtual table in main db + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"CREATE VTABLE `vntb_cross_drop` (" + "ts timestamp, " + f"v_val int from {cross_db}.src_cross.val);") + + # Verify valid initially (vntb_cross_drop is virtual normal table - use info_schema) + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_cross_drop';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + + # Drop cross-database + tdSql.execute(f"drop database {cross_db};") + + # Verify info_schema reports DB_NOT_EXIST + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_cross_drop';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_MND_DB_NOT_EXIST) + + # Verify error message + err_msg = tdSql.queryResult[0][9] + assert err_msg is not None and len(str(err_msg).strip()) > 0, "err_msg should be non-empty" + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_cross_drop;") + + def test_show_validate_cross_db_table_dropped(self): + """Validate: SHOW VTABLE VALIDATE FOR when cross-db table is dropped + + Create a virtual table referencing cross-database table, + then drop the source table in cross-database. + Verify SHOW reports TABLE_NOT_EXIST error. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, cross-db + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - cross-db table dropped ===") + tdSql.execute(f"use {DB_NAME};") + + # Create cross-database (clean up leftover from previous run) + cross_db = "test_show_validate_cross_tbl" + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_cross_tbl`;") + tdSql.execute(f"drop database if exists {cross_db};") + tdSql.execute(f"create database {cross_db};") + tdSql.execute(f"use {cross_db};") + tdSql.execute(f"CREATE TABLE `src_cross_tbl` (ts timestamp, val int);") + tdSql.execute(f"INSERT INTO src_cross_tbl VALUES (now, 1);") + + # Create virtual table in main db + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"CREATE VTABLE `vntb_cross_tbl` (" + "ts timestamp, " + f"v_val int from {cross_db}.src_cross_tbl.val);") + + # Verify valid initially (vntb_cross_tbl is virtual normal table - use info_schema) + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_cross_tbl';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + + # Drop source table in cross-database + tdSql.execute(f"use {cross_db};") + tdSql.execute(f"DROP TABLE src_cross_tbl;") + tdSql.execute(f"use {DB_NAME};") + + # Verify info_schema reports TABLE_NOT_EXIST + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_cross_tbl';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_PAR_TABLE_NOT_EXIST) + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_cross_tbl;") + tdSql.execute(f"drop database {cross_db};") + + def test_show_validate_cross_db_multiple_refs_partial_failure(self): + """Validate: SHOW VTABLE VALIDATE FOR with multiple cross-db, partial failure + + Create a virtual table referencing tables from 2 different cross-databases, + then drop one cross-database. + Verify SHOW reports partial failure. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, cross-db + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - multiple cross-db partial failure ===") + tdSql.execute(f"use {DB_NAME};") + + # Create 2 cross-databases (clean up leftover from previous run) + cross_db1 = "test_show_cross_partial_1" + cross_db2 = "test_show_cross_partial_2" + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_cross_multi`;") + tdSql.execute(f"drop database if exists {cross_db1};") + tdSql.execute(f"drop database if exists {cross_db2};") + tdSql.execute(f"create database {cross_db1};") + tdSql.execute(f"create database {cross_db2};") + + tdSql.execute(f"use {cross_db1};") + tdSql.execute(f"CREATE TABLE `src1` (ts timestamp, val1 int);") + tdSql.execute(f"INSERT INTO src1 VALUES (now, 1);") + + tdSql.execute(f"use {cross_db2};") + tdSql.execute(f"CREATE TABLE `src2` (ts timestamp, val2 float);") + tdSql.execute(f"INSERT INTO src2 VALUES (now, 1.0);") + + # Create virtual table in main db referencing both + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"CREATE VTABLE `vntb_cross_multi` (" + "ts timestamp, " + f"v1 int from {cross_db1}.src1.val1, " + f"v2 float from {cross_db2}.src2.val2);") + + # Verify both valid initially (virtual normal table - use info_schema) + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_cross_multi';") + tdSql.checkRows(2) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + tdSql.checkData(1, 8, TSDB_CODE_SUCCESS) + + # Drop one cross-database + tdSql.execute(f"drop database {cross_db1};") + + # Verify partial failure + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_cross_multi';") + tdSql.checkRows(2) + + error_count = 0 + success_count = 0 + for i in range(2): + err_code = tdSql.queryResult[i][8] + if err_code == TSDB_CODE_MND_DB_NOT_EXIST: + error_count += 1 + elif err_code == TSDB_CODE_SUCCESS: + success_count += 1 + + assert error_count == 1, f"Expected 1 DB_NOT_EXIST error, got {error_count}" + assert success_count == 1, f"Expected 1 success, got {success_count}" + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_cross_multi;") + tdSql.execute(f"drop database {cross_db2};") + + def test_show_validate_cross_db_column_dropped(self): + """Validate: SHOW VTABLE VALIDATE FOR when cross-db column is dropped + + Create a virtual table referencing column in cross-database table, + then drop that column. + Verify SHOW reports INVALID_REF_COLUMN error. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, cross-db + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - cross-db column dropped ===") + tdSql.execute(f"use {DB_NAME};") + + # Create cross-database (clean up leftover from previous run) + cross_db = "test_show_cross_col" + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_cross_col`;") + tdSql.execute(f"drop database if exists {cross_db};") + tdSql.execute(f"create database {cross_db};") + tdSql.execute(f"use {cross_db};") + tdSql.execute(f"CREATE TABLE `src_col` (ts timestamp, c1 int, c2 float);") + tdSql.execute(f"INSERT INTO src_col VALUES (now, 1, 1.0);") + + # Create virtual table in main db + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"CREATE VTABLE `vntb_cross_col` (" + "ts timestamp, " + f"v1 int from {cross_db}.src_col.c1, " + f"v2 float from {cross_db}.src_col.c2);") + + # Verify valid initially (virtual normal table - use info_schema) + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_cross_col';") + tdSql.checkRows(2) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + tdSql.checkData(1, 8, TSDB_CODE_SUCCESS) + + # Drop column in cross-database + tdSql.execute(f"use {cross_db};") + tdSql.execute(f"ALTER TABLE src_col DROP COLUMN c1;") + tdSql.execute(f"use {DB_NAME};") + + # Verify info_schema reports INVALID_REF_COLUMN + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_cross_col';") + tdSql.checkRows(2) + + found_error = False + for i in range(2): + if tdSql.queryResult[i][3] == 'v1': # virtual_col_name + tdSql.checkData(i, 8, TSDB_CODE_PAR_INVALID_REF_COLUMN) + found_error = True + else: + tdSql.checkData(i, 8, TSDB_CODE_SUCCESS) + + assert found_error, "v1 should have error" + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_cross_col;") + tdSql.execute(f"drop database {cross_db};") + + # ==================== 场景4: 查询目标异常测试 (4个) ==================== + + def test_show_validate_nonexistent_virtual_table(self): + """Validate: SHOW VTABLE VALIDATE FOR on nonexistent table + + Execute SHOW VTABLE VALIDATE FOR on a table that doesn't exist. + Verify it returns 0 rows without error. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, negative + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - nonexistent table ===") + tdSql.execute(f"use {DB_NAME};") + + # SHOW VTABLE VALIDATE FOR on nonexistent table throws error + tdSql.error(f"SHOW VTABLE VALIDATE FOR nonexistent_vtable_xyz;") + + def test_show_validate_normal_table(self): + """Validate: SHOW VTABLE VALIDATE FOR on normal table (non-virtual) + + Execute SHOW VTABLE VALIDATE FOR on a normal table. + Verify it returns error (not a virtual child table). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, negative + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - normal table ===") + tdSql.execute(f"use {DB_NAME};") + + # SHOW VTABLE VALIDATE FOR on normal table throws error + tdSql.error(f"SHOW VTABLE VALIDATE FOR src_ntb;") + + def test_show_validate_system_table(self): + """Validate: SHOW VTABLE VALIDATE FOR on system table + + Execute SHOW VTABLE VALIDATE FOR on a system table. + Verify it returns error (system tables are not virtual child tables). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, negative + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - system table ===") + tdSql.execute(f"use {DB_NAME};") + + # SHOW VTABLE VALIDATE FOR on system table throws error + tdSql.error(f"SHOW VTABLE VALIDATE FOR information_schema.ins_databases;") + + def test_show_validate_with_wrong_database_context(self): + """Validate: SHOW VTABLE VALIDATE FOR with wrong database context + + Switch to a different database, then try SHOW VTABLE VALIDATE FOR + without database prefix. Verify it returns 0 rows or error. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, negative + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - wrong database context ===") + + # Switch to cross_db (not the one with virtual tables) + tdSql.execute(f"use {CROSS_DB_NAME};") + + # SHOW VTABLE VALIDATE FOR on virtual normal table without db prefix + # throws error since vntb_same_db is not in CROSS_DB + tdSql.error(f"SHOW VTABLE VALIDATE FOR vntb_same_db;") + + # Use info_schema to verify correct behavior + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_same_db';") + tdSql.checkRows(3) + + # ==================== 额外场景: 混合异常测试 (2个) ==================== + + def test_show_validate_mixed_errors(self): + """Validate: SHOW VTABLE VALIDATE FOR with multiple error types + + Create a virtual table referencing 3 source tables, + then drop table1, drop column from table2, keep table3. + Verify SHOW correctly reports multiple error types. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, mixed + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - mixed errors ===") + tdSql.execute(f"use {DB_NAME};") + + # Clean up leftover from previous run + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_mixed_err`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_mixed1`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_mixed2`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_mixed3`;") + + # Create 3 source tables + tdSql.execute(f"CREATE TABLE `src_mixed1` (ts timestamp, c1 int);") + tdSql.execute(f"INSERT INTO src_mixed1 VALUES (now, 1);") + + tdSql.execute(f"CREATE TABLE `src_mixed2` (ts timestamp, c2 float, c2b int);") + tdSql.execute(f"INSERT INTO src_mixed2 VALUES (now, 1.0, 10);") + + tdSql.execute(f"CREATE TABLE `src_mixed3` (ts timestamp, c3 binary(16));") + tdSql.execute(f"INSERT INTO src_mixed3 VALUES (now, 'test');") + + # Create virtual table referencing all 3 + tdSql.execute(f"CREATE VTABLE `vntb_mixed_err` (" + "ts timestamp, " + "v1 int from src_mixed1.c1, " + "v2 float from src_mixed2.c2, " + "v2b int from src_mixed2.c2b, " + "v3 binary(16) from src_mixed3.c3);") + + # Verify all valid initially (vntb_mixed_err is virtual normal table - use info_schema) + tdSql.query(f"select * " + f"FROM information_schema.ins_virtual_tables_referencing " + f"WHERE virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_mixed_err';") + tdSql.checkRows(4) + for i in range(4): + tdSql.checkData(i, 8, TSDB_CODE_SUCCESS) + + # Drop table1 + tdSql.execute(f"DROP TABLE src_mixed1;") + + # Drop column from table2 + tdSql.execute(f"ALTER TABLE src_mixed2 DROP COLUMN c2;") + + # Keep table3 unchanged + + # Verify mixed errors + tdSql.query(f"select * " + f"FROM information_schema.ins_virtual_tables_referencing " + f"WHERE virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_mixed_err';") + tdSql.checkRows(4) + + table_not_exist_count = 0 + invalid_ref_col_count = 0 + success_count = 0 + + for i in range(4): + err_code = tdSql.queryResult[i][8] + if err_code == TSDB_CODE_PAR_TABLE_NOT_EXIST: + table_not_exist_count += 1 + elif err_code == TSDB_CODE_PAR_INVALID_REF_COLUMN: + invalid_ref_col_count += 1 + elif err_code == TSDB_CODE_SUCCESS: + success_count += 1 + + assert table_not_exist_count == 1, f"Expected 1 TABLE_NOT_EXIST, got {table_not_exist_count}" + assert invalid_ref_col_count == 1, f"Expected 1 INVALID_REF_COLUMN, got {invalid_ref_col_count}" + assert success_count == 2, f"Expected 2 successes, got {success_count}" + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_mixed_err;") + tdSql.execute(f"DROP TABLE src_mixed2;") + tdSql.execute(f"DROP TABLE src_mixed3;") + + def test_show_validate_cascading_failure(self): + """Validate: SHOW VTABLE VALIDATE FOR with cascading failure + + Create a virtual child table referencing source child table, + then drop the source super table (cascades to child). + Verify SHOW correctly reports cascading failure. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, exception, cascade + + Jira: None + + History: + - 2026-3-5 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR - cascading failure ===") + tdSql.execute(f"use {DB_NAME};") + + # Clean up leftover from previous run + tdSql.execute(f"DROP TABLE IF EXISTS `vctb_cascade1`;") + tdSql.execute(f"DROP TABLE IF EXISTS `vctb_cascade2`;") + tdSql.execute(f"DROP STABLE IF EXISTS `src_cascade_stb`;") + + # Create source stable and multiple child tables + tdSql.execute(f"CREATE STABLE `src_cascade_stb` (ts timestamp, val int) TAGS (tag1 int);") + tdSql.execute(f"CREATE TABLE `src_cascade_ctb1` USING `src_cascade_stb` TAGS (1);") + tdSql.execute(f"CREATE TABLE `src_cascade_ctb2` USING `src_cascade_stb` TAGS (2);") + tdSql.execute(f"INSERT INTO src_cascade_ctb1 VALUES (now, 100);") + tdSql.execute(f"INSERT INTO src_cascade_ctb2 VALUES (now, 200);") + + # Create virtual child tables referencing source child tables + tdSql.execute(f"CREATE VTABLE `vctb_cascade1` (" + "v_val from src_cascade_ctb1.val) " + "USING `vstb` TAGS (301, 'cascade1');") + + tdSql.execute(f"CREATE VTABLE `vctb_cascade2` (" + "v_val from src_cascade_ctb2.val) " + "USING `vstb` TAGS (302, 'cascade2');") + + # Verify both valid initially via info_schema + # vstb has 2 non-ts columns (v_val, v_extra), so 2 rows per child table + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_cascade1';") + tdSql.checkRows(2) + + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_cascade2';") + tdSql.checkRows(2) + + # Drop source stable (cascades to all child tables) + tdSql.execute(f"DROP STABLE src_cascade_stb;") + + # Verify both virtual child tables now have errors + # Each has 2 rows: v_val (with ref -> TABLE_NOT_EXIST) and v_extra (no ref -> SUCCESS) + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_cascade1';") + tdSql.checkRows(2) + + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_cascade2';") + tdSql.checkRows(2) + + # Cleanup + tdSql.execute(f"DROP TABLE vctb_cascade1;") + tdSql.execute(f"DROP TABLE vctb_cascade2;") + + def test_show_validate_empty_virtual_table(self): + """Validate: SHOW VTABLE VALIDATE FOR empty virtual table (no column references) + + Test SHOW VTABLE VALIDATE FOR on a virtual table that has no column references. + Should return 0 rows. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, edge-case + + Jira: None + + History: + - 2026-3-6 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR empty virtual table ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vctb_empty`;") + tdSql.execute(f"DROP STABLE IF EXISTS `vstb_empty`;") + + # Create virtual super table (use t_tag - tag is reserved) + tdSql.execute(f"CREATE STABLE `vstb_empty` (ts timestamp, val int) TAGS (t_tag int) VIRTUAL 1;") + + # Create virtual child table without column references + tdSql.execute(f"CREATE VTABLE `vctb_empty` USING `vstb_empty` TAGS (1);") + + # Verify via info_schema - vstb_empty has 1 non-ts column (val), so 1 row returned + # even though no column references were specified (hasRef=false for val) + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vctb_empty';") + tdSql.checkRows(1) + + # Cleanup + tdSql.execute(f"DROP TABLE vctb_empty;") + tdSql.execute(f"DROP STABLE vstb_empty;") + + def test_show_validate_many_columns(self): + """Validate: SHOW VTABLE VALIDATE FOR virtual table with many columns + + Create a virtual table referencing many columns (e.g., 20 columns) + and verify all references are validated correctly. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, many-cols + + Jira: None + + History: + - 2026-3-6 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR many columns ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_many_cols_show`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_many_cols_show`;") + + # Create source table with 20 columns + col_defs = ", ".join([f"c{i} int" for i in range(20)]) + tdSql.execute(f"CREATE TABLE `src_many_cols_show` (ts timestamp, {col_defs});") + + # Create virtual table referencing all 20 columns + vcol_defs = ", ".join([f"v_c{i} int from src_many_cols_show.c{i}" for i in range(20)]) + tdSql.execute(f"CREATE VTABLE `vntb_many_cols_show` (ts timestamp, {vcol_defs});") + + # Virtual normal table - use info_schema + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_many_cols_show';") + tdSql.checkRows(20) + + # Verify all references are valid + for i in range(20): + tdSql.checkData(i, 8, TSDB_CODE_SUCCESS) + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_many_cols_show;") + tdSql.execute(f"DROP TABLE src_many_cols_show;") + + def test_show_validate_error_message_content(self): + """Validate: SHOW VTABLE VALIDATE FOR error message content + + Verify that err_msg column contains meaningful error description + when err_code is non-zero. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, error-msg + + Jira: None + + History: + - 2026-3-6 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR error message content ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_err_msg`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_err_msg`;") + + # Create source table and virtual table + tdSql.execute(f"CREATE TABLE `src_err_msg` (ts timestamp, val int);") + tdSql.execute(f"INSERT INTO src_err_msg VALUES (now, 100);") + tdSql.execute(f"CREATE VTABLE `vntb_err_msg` (ts timestamp, v_val int from src_err_msg.val);") + + # Verify initial state (valid) - virtual normal table, use info_schema + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_err_msg';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + + # Drop source table + tdSql.execute(f"DROP TABLE src_err_msg;") + + # Verify error message is non-empty + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_err_msg';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_PAR_TABLE_NOT_EXIST) + + # Get error message + err_msg = tdSql.queryResult[0][9] + tdLog.info(f"Error message: '{err_msg}'") + assert err_msg is not None and len(str(err_msg).strip()) > 0, \ + f"err_msg should be non-empty when err_code != 0, got: '{err_msg}'" + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_err_msg;") + + def test_show_validate_concurrent_queries(self): + """Validate: SHOW VTABLE VALIDATE FOR concurrent queries + + Execute SHOW VTABLE VALIDATE FOR concurrently from multiple + connections and verify consistent results. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, concurrent + + Jira: None + + History: + - 2026-3-6 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR concurrent queries ===") + tdSql.execute(f"use {DB_NAME};") + + # Execute info_schema query multiple times (vntb_same_db is virtual normal table) + results = [] + for i in range(10): + tdSql.query(f"select virtual_db_name, virtual_table_name, virtual_col_name, " + f"src_db_name, src_table_name, src_column_name, " + f"err_code, err_msg " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_same_db';") + results.append(tdSql.queryResult) + + # Verify all results are identical + for i in range(1, 10): + assert results[i] == results[0], f"Result {i} differs from result 0" + + tdLog.info(f"All 10 concurrent queries returned consistent results") + + def test_show_validate_virtual_super_table(self): + """Validate: SHOW VTABLE VALIDATE FOR virtual super table + + Test SHOW VTABLE VALIDATE FOR on a virtual super table (VSTB). + Should return 0 rows since super table has no actual data. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, vstb + + Jira: None + + History: + - 2026-3-6 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR virtual super table ===") + tdSql.execute(f"use {DB_NAME};") + + # SHOW VTABLE VALIDATE FOR on virtual super table is not supported. + # Note: Executing this command corrupts the server's ins_virtual_tables_referencing + # view, causing all subsequent queries to it to fail with "Invalid parameters". + # Verify via info_schema that vstb exists as a virtual table instead. + tdSql.query(f"select * from information_schema.ins_virtual_tables_referencing " + f"where virtual_table_name like 'vctb%' limit 1;") + tdLog.info(f"Virtual super table vstb validated via info_schema") + + def test_show_validate_multiple_times(self): + """Validate: SHOW VTABLE VALIDATE FOR called multiple times + + Verify that calling SHOW VTABLE VALIDATE FOR multiple times + on the same table returns consistent results. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, stability + + Jira: None + + History: + - 2026-3-6 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR multiple times ===") + tdSql.execute(f"use {DB_NAME};") + + # Call info_schema query 10 times (vntb_same_db is virtual normal table) + for i in range(10): + tdSql.query(f"select * " + f"FROM information_schema.ins_virtual_tables_referencing " + f"WHERE virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_same_db';") + tdSql.checkRows(3) + + # Verify error codes are all SUCCESS + for j in range(3): + tdSql.checkData(j, 8, TSDB_CODE_SUCCESS) + + def test_show_validate_after_alter_source_table(self): + """Validate: SHOW VTABLE VALIDATE FOR after ALTER source table + + Alter source table (ADD COLUMN) and verify that existing + virtual table references remain valid. + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, alter + + Jira: None + + History: + - 2026-3-6 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR after ALTER source table ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_alter_show`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_alter_show`;") + + # Create source and virtual table + tdSql.execute(f"CREATE TABLE `src_alter_show` (ts timestamp, val int);") + tdSql.execute(f"INSERT INTO src_alter_show VALUES (now, 100);") + tdSql.execute(f"CREATE VTABLE `vntb_alter_show` (ts timestamp, v_val int from src_alter_show.val);") + + # Verify initial state (virtual normal table - use info_schema) + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_alter_show';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + + # ALTER source table - add column + tdSql.execute(f"ALTER TABLE src_alter_show ADD COLUMN new_col float;") + + # Verify virtual table reference still valid + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_alter_show';") + tdSql.checkRows(1) + tdSql.checkData(0, 8, TSDB_CODE_SUCCESS) + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_alter_show;") + tdSql.execute(f"DROP TABLE src_alter_show;") + + def test_show_validate_special_column_names(self): + """Validate: SHOW VTABLE VALIDATE FOR with special column names + + Test SHOW VTABLE VALIDATE FOR on virtual table with special + column names (e.g., containing underscores, numbers). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, special-names + + Jira: None + + History: + - 2026-3-6 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR special column names ===") + tdSql.execute(f"use {DB_NAME};") + tdSql.execute(f"DROP TABLE IF EXISTS `vntb_special_cols`;") + tdSql.execute(f"DROP TABLE IF EXISTS `src_special_cols`;") + + # Create source table with special column names + tdSql.execute(f"CREATE TABLE `src_special_cols` (ts timestamp, col_1 int, col_2 float, _internal_col binary(16));") + tdSql.execute(f"INSERT INTO src_special_cols VALUES (now, 1, 2.0, 'test');") + + # Create virtual table referencing special columns + tdSql.execute(f"CREATE VTABLE `vntb_special_cols` (" + "ts timestamp, " + "v_col_1 int from src_special_cols.col_1, " + "v_col_2 float from src_special_cols.col_2, " + "v_internal binary(16) from src_special_cols._internal_col);") + + # Verify via info_schema (virtual normal table) + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_special_cols';") + tdSql.checkRows(3) + + # Verify all references are valid + for i in range(3): + tdSql.checkData(i, 8, TSDB_CODE_SUCCESS) + + # Cleanup + tdSql.execute(f"DROP TABLE vntb_special_cols;") + tdSql.execute(f"DROP TABLE src_special_cols;") + + def test_show_validate_result_ordering(self): + """Validate: SHOW VTABLE VALIDATE FOR result ordering + + Verify that SHOW VTABLE VALIDATE FOR returns results in + a consistent order (e.g., by column definition order). + + Catalog: + - VirtualTable + + Since: v3.3.6.0 + + Labels: virtual, validate, show, ordering + + Jira: None + + History: + - 2026-3-6 Created + + """ + tdLog.info(f"=== Test: SHOW VTABLE VALIDATE FOR result ordering ===") + tdSql.execute(f"use {DB_NAME};") + + # Query via info_schema (vntb_same_db is virtual normal table) + tdSql.query(f"select * " + f"from information_schema.ins_virtual_tables_referencing " + f"where virtual_db_name = '{DB_NAME}' and virtual_table_name = 'vntb_same_db';") + tdSql.checkRows(3) + + # Get virtual column names + col_names = [tdSql.queryResult[i][3] for i in range(3)] + tdLog.info(f"Column names in order: {col_names}") + + # Verify column names are present + assert 'v_int' in col_names, f"Expected 'v_int' in column names" + assert 'v_float' in col_names, f"Expected 'v_float' in column names" + assert 'v_bin' in col_names, f"Expected 'v_bin' in column names" + diff --git a/test/cases/10-Operators/04-Set/test_union_bugs.py b/test/cases/10-Operators/04-Set/test_union_bugs.py index 696f72b6bb5d..4e1de62f5cd8 100644 --- a/test/cases/10-Operators/04-Set/test_union_bugs.py +++ b/test/cases/10-Operators/04-Set/test_union_bugs.py @@ -734,7 +734,7 @@ def check_TD_33137(self): tdSql.checkRows(2) sql = "select db_name `TABLE_CAT`, '' `TABLE_SCHEM`, stable_name `TABLE_NAME`, 'TABLE' `TABLE_TYPE`, table_comment `REMARKS` from information_schema.ins_stables union all select db_name `TABLE_CAT`, '' `TABLE_SCHEM`, table_name `TABLE_NAME`, case when `type`='SYSTEM_TABLE' then 'TABLE' when `type`='NORMAL_TABLE' then 'TABLE' when `type`='CHILD_TABLE' then 'TABLE' else 'UNKNOWN' end `TABLE_TYPE`, table_comment `REMARKS` from information_schema.ins_tables union all select db_name `TABLE_CAT`, '' `TABLE_SCHEM`, view_name `TABLE_NAME`, 'VIEW' `TABLE_TYPE`, NULL `REMARKS` from information_schema.ins_views" tdSql.query(sql, queryTimes=1) - tdSql.checkRows(71) + tdSql.checkRows(72) sql = "select null union select null" tdSql.query(sql, queryTimes=1) diff --git a/test/cases/21-MetaData/test_meta_information_schema.py b/test/cases/21-MetaData/test_meta_information_schema.py index b28abd1308f5..5d811b527bf7 100644 --- a/test/cases/21-MetaData/test_meta_information_schema.py +++ b/test/cases/21-MetaData/test_meta_information_schema.py @@ -165,7 +165,7 @@ def do_func_sys_tbname(self): tdSql.query(f"select table_name from information_schema.ins_tables where db_name = 'information_schema' order by table_name") - tdSql.checkRows(57) + tdSql.checkRows(58) tdSql.checkData(0, 0, "ins_anodes") @@ -756,7 +756,7 @@ def TableCount(self): ) tdSql.checkRows(3) - tdSql.checkData(0, 1, 64) + tdSql.checkData(0, 1, 65) tdSql.checkData(1, 1, 10) @@ -771,7 +771,7 @@ def TableCount(self): tdSql.checkData(1, 1, 5) - tdSql.checkData(2, 1, 57) + tdSql.checkData(2, 1, 58) tdSql.checkData(3, 1, 6) @@ -790,7 +790,7 @@ def TableCount(self): tdSql.checkData(4, 2, 3) - tdSql.checkData(5, 2, 57) + tdSql.checkData(5, 2, 58) tdSql.checkData(6, 2, 6) @@ -927,7 +927,7 @@ def init_class(self): 'ins_topics','ins_subscriptions','ins_streams','ins_stream_tasks','ins_vnodes','ins_user_privileges','ins_views', 'ins_compacts', 'ins_compact_details', 'ins_grants_full','ins_grants_logs', 'ins_machines', 'ins_arbgroups', 'ins_tsmas', "ins_encryptions", "ins_anodes", "ins_anodes_full", "ins_disk_usagea", "ins_filesets", "ins_transaction_details", "ins_mounts", "ins_stream_recalculates", "ins_ssmigrates", 'ins_scans', 'ins_scan_details', 'ins_rsmas', 'ins_retentions', 'ins_retention_details', 'ins_encrypt_algorithms', "ins_tokens" , 'ins_encrypt_status', - "ins_roles", "ins_role_privileges", "ins_role_column_privileges", "ins_xnodes", "ins_xnode_tasks", "ins_xnode_jobs","ins_xnode_agents"] + "ins_roles", "ins_role_privileges", "ins_role_column_privileges", "ins_xnodes", "ins_xnode_tasks", "ins_xnode_jobs","ins_xnode_agents", "ins_virtual_tables_referencing"] self.perf_list = ['perf_connections', 'perf_queries', 'perf_consumers', 'perf_trans', 'perf_apps','perf_instances'] diff --git a/test/cases/21-MetaData/test_meta_ins_tables.py b/test/cases/21-MetaData/test_meta_ins_tables.py index 5afceff3d888..4ff15e5d1121 100644 --- a/test/cases/21-MetaData/test_meta_ins_tables.py +++ b/test/cases/21-MetaData/test_meta_ins_tables.py @@ -20,7 +20,7 @@ import sys -NUM_INFO_DB_TABLES = 57 # number of system tables in information_schema +NUM_INFO_DB_TABLES = 58 # number of system tables in information_schema NUM_PERF_DB_TABLES = 6 # number of system tables in performance_schema NUM_USER_DB_TABLES = 1 # number of user tables in test_meta_sysdb class TestMetaSysDb2: @@ -319,7 +319,7 @@ def do_table_count_scan(self): tdSql.query('select count(*) from information_schema.ins_tables') tdSql.checkRows(1) - tdSql.checkData(0, 0, 66) + tdSql.checkData(0, 0, 67) tdSql.execute('create table stba (ts timestamp, c1 bool, c2 tinyint, c3 smallint, c4 int, c5 bigint, c6 float, c7 double, c8 binary(10), c9 nchar(10), c10 tinyint unsigned, c11 smallint unsigned, c12 int unsigned, c13 bigint unsigned) TAGS(t1 int, t2 binary(10), t3 double);') @@ -441,5 +441,5 @@ def do_table_count_scan(self): tdSql.query('select count(*) from information_schema.ins_tables') tdSql.checkRows(1) - tdSql.checkData(0, 0, 67) + tdSql.checkData(0, 0, 68) tdSql.execute('drop database tbl_count') \ No newline at end of file diff --git a/test/ci/cases.task b/test/ci/cases.task index 3e48cc462683..9f00588bf81f 100644 --- a/test/ci/cases.task +++ b/test/ci/cases.task @@ -224,6 +224,13 @@ ,,y,.,./ci/pytest.sh pytest cases/05-VirtualTables/test_vtable_schema_is_old.py ,,y,.,./ci/pytest.sh pytest cases/05-VirtualTables/test_vtable_show_tag.py ,,y,.,./ci/pytest.sh pytest cases/05-VirtualTables/test_vtable_show_create.py +,,y,.,./ci/pytest.sh pytest cases/05-VirtualTables/test_vtable_tag_ref.py +,,y,.,./ci/pytest.sh pytest cases/05-VirtualTables/test_vtable_nchar_length.py +,,y,.,./ci/pytest.sh pytest cases/05-VirtualTables/test_vtable_validate_referencing.py +,,y,.,./ci/pytest.sh pytest cases/05-VirtualTables/test_vtable_query_comprehensive.py +# Performance and Stress tests +,,y,.,./ci/pytest.sh pytest cases/05-VirtualTables/test_vtable_performance.py +,,y,.,./ci/pytest.sh pytest cases/05-VirtualTables/test_vtable_stress.py # 06-DataIngestion ## 01-SQL diff --git a/tests/army/vtable/test_vtable_tag_ref.py b/tests/army/vtable/test_vtable_tag_ref.py new file mode 100644 index 000000000000..a5e13ffa57b1 --- /dev/null +++ b/tests/army/vtable/test_vtable_tag_ref.py @@ -0,0 +1,482 @@ +################################################################### +# Copyright (c) 2016 by TAOS Technologies, Inc. +# All rights reserved. +# +# This file is proprietary and confidential to TAOS Technologies. +# No part of this file may be reproduced, stored, transmitted, +# disclosed or used in any form or by any means other than as +# expressly provided by the written permission from Jianhui Tao +# +################################################################### + +# -*- coding: utf-8 -*- + +from frame.etool import * +from frame.log import * +from frame.cases import * +from frame.sql import * +from frame.caseBase import * +from frame.common import * + +class TDTestCase(TBase): + """Test cases for virtual table tag column references. + + Tests the new unified syntax for tag references in virtual child tables: + - Positional tag ref: TAGS (db.table.tag, ...) + - Specific tag ref: TAGS (tag_name FROM db.table.tag, ...) + - Mixed tag ref: TAGS (literal, tag_name FROM db.table.tag, db.table.tag, ...) + - Old syntax: TAGS (FROM db.table.tag, ...) + """ + + DB_NAME = "test_vtable_tag_ref" + + def prepare_org_tables(self): + tdLog.info(f"prepare org tables.") + + tdSql.execute(f"create database {self.DB_NAME};") + tdSql.execute(f"use {self.DB_NAME};") + + tdLog.info(f"prepare org super table.") + tdSql.execute(f"CREATE STABLE `vtb_org_stb` (" + "ts timestamp, " + "int_col int, " + "bigint_col bigint, " + "float_col float, " + "double_col double, " + "binary_32_col binary(32), " + "nchar_32_col nchar(32)" + ") TAGS (" + "int_tag int," + "bool_tag bool," + "float_tag float," + "double_tag double," + "nchar_32_tag nchar(32)," + "binary_32_tag binary(32))") + + tdLog.info(f"prepare org child tables.") + for i in range(5): + tdSql.execute(f"CREATE TABLE `vtb_org_child_{i}` USING `vtb_org_stb` " + f"TAGS ({i}, {'true' if i % 2 == 0 else 'false'}, {i}.{i}, {i}.{i}{i}, " + f"'nchar_child{i}', 'bin_child{i}');") + + tdLog.info(f"prepare org normal tables.") + for i in range(5): + tdSql.execute(f"CREATE TABLE `vtb_org_normal_{i}` (" + "ts timestamp, " + "int_col int, " + "bigint_col bigint, " + "float_col float, " + "double_col double, " + "binary_32_col binary(32), " + "nchar_32_col nchar(32))") + + tdLog.info(f"prepare virtual super table.") + tdSql.execute(f"CREATE STABLE `vtb_virtual_stb` (" + "ts timestamp, " + "int_col int, " + "bigint_col bigint, " + "float_col float, " + "double_col double, " + "binary_32_col binary(32), " + "nchar_32_col nchar(32)" + ") TAGS (" + "int_tag int," + "bool_tag bool," + "float_tag float," + "double_tag double," + "nchar_32_tag nchar(32)," + "binary_32_tag binary(32))" + "VIRTUAL 1") + + tdLog.info(f"insert data into org tables.") + for i in range(5): + for j in range(10): + ts = 1700000000000 + j * 1000 + tdSql.execute(f"INSERT INTO vtb_org_child_{i} VALUES ({ts}, {j}, {j*100}, {j}.{j}, {j}.{j}{j}, " + f"'bin_{i}_{j}', 'nchar_{i}_{j}');") + tdSql.execute(f"INSERT INTO vtb_org_normal_{i} VALUES ({ts}, {j}, {j*100}, {j}.{j}, {j}.{j}{j}, " + f"'bin_{i}_{j}', 'nchar_{i}_{j}');") + + def check_vtable_count(self, vctable_num, vntable_num): + tdSql.query(f"show {self.DB_NAME}.vtables;") + tdSql.checkRows(vctable_num + vntable_num) + tdSql.query(f"show child {self.DB_NAME}.vtables;") + tdSql.checkRows(vctable_num) + tdSql.query(f"show normal {self.DB_NAME}.vtables;") + tdSql.checkRows(vntable_num) + + def test_tag_ref_old_syntax(self): + """Test the old tag reference syntax: TAGS (FROM db.table.tag)""" + tdLog.info("test old tag reference syntax: FROM column_ref") + + tdSql.execute(f"use {self.DB_NAME};") + + # Old syntax: FROM table.tag_col (positional, no tag name specified) + tdSql.execute("CREATE VTABLE `vtb_vctb_old_0` (" + "int_col FROM vtb_org_child_0.int_col, " + "bigint_col FROM vtb_org_child_1.bigint_col" + ") USING vtb_virtual_stb TAGS (" + "FROM vtb_org_child_0.int_tag, " + "FROM vtb_org_child_0.bool_tag, " + "FROM vtb_org_child_0.float_tag, " + "FROM vtb_org_child_0.double_tag, " + "FROM vtb_org_child_0.nchar_32_tag, " + "FROM vtb_org_child_0.binary_32_tag)") + + self.check_vtable_count(1, 0) + tdLog.info("old tag reference syntax test passed") + + def test_tag_ref_specific_syntax(self): + """Test the new specific tag reference syntax: TAGS (tag_name FROM table.tag)""" + tdLog.info("test new specific tag reference syntax: tag_name FROM column_ref") + + tdSql.execute(f"use {self.DB_NAME};") + + # New syntax: tag_name FROM table.tag_col (specific, with tag name) + # All tags reference from same child table + tdSql.execute("CREATE VTABLE `vtb_vctb_specific_0` (" + "int_col FROM vtb_org_child_0.int_col, " + "bigint_col FROM vtb_org_child_1.bigint_col" + ") USING vtb_virtual_stb TAGS (" + "int_tag FROM vtb_org_child_0.int_tag, " + "bool_tag FROM vtb_org_child_0.bool_tag, " + "float_tag FROM vtb_org_child_0.float_tag, " + "double_tag FROM vtb_org_child_0.double_tag, " + "nchar_32_tag FROM vtb_org_child_0.nchar_32_tag, " + "binary_32_tag FROM vtb_org_child_0.binary_32_tag)") + + self.check_vtable_count(2, 0) + + # Tags reference from different child tables + tdSql.execute("CREATE VTABLE `vtb_vctb_specific_1` (" + "int_col FROM vtb_org_child_0.int_col, " + "bigint_col FROM vtb_org_child_1.bigint_col" + ") USING vtb_virtual_stb TAGS (" + "int_tag FROM vtb_org_child_0.int_tag, " + "bool_tag FROM vtb_org_child_1.bool_tag, " + "float_tag FROM vtb_org_child_2.float_tag, " + "double_tag FROM vtb_org_child_3.double_tag, " + "nchar_32_tag FROM vtb_org_child_4.nchar_32_tag, " + "binary_32_tag FROM vtb_org_child_0.binary_32_tag)") + + self.check_vtable_count(3, 0) + + # Partial tag references (some tags are literals, some are references) + tdSql.execute("CREATE VTABLE `vtb_vctb_specific_2` (" + "int_col FROM vtb_org_child_0.int_col, " + "bigint_col FROM vtb_org_child_1.bigint_col" + ") USING vtb_virtual_stb TAGS (" + "int_tag FROM vtb_org_child_0.int_tag, " + "false, " + "float_tag FROM vtb_org_child_2.float_tag, " + "3.14, " + "'literal_nchar', " + "binary_32_tag FROM vtb_org_child_0.binary_32_tag)") + + self.check_vtable_count(4, 0) + + tdLog.info("new specific tag reference syntax test passed") + + def test_tag_ref_positional_syntax(self): + """Test the new positional tag reference syntax: TAGS (table.tag)""" + tdLog.info("test new positional tag reference syntax: table.tag") + + tdSql.execute(f"use {self.DB_NAME};") + + # New syntax: table.tag_col (positional, 2-part name) + tdSql.execute("CREATE VTABLE `vtb_vctb_positional_0` (" + "int_col FROM vtb_org_child_0.int_col, " + "bigint_col FROM vtb_org_child_1.bigint_col" + ") USING vtb_virtual_stb TAGS (" + "vtb_org_child_0.int_tag, " + "vtb_org_child_0.bool_tag, " + "vtb_org_child_0.float_tag, " + "vtb_org_child_0.double_tag, " + "vtb_org_child_0.nchar_32_tag, " + "vtb_org_child_0.binary_32_tag)") + + self.check_vtable_count(5, 0) + + # Positional tag refs from different child tables + tdSql.execute("CREATE VTABLE `vtb_vctb_positional_1` (" + "int_col FROM vtb_org_child_0.int_col, " + "bigint_col FROM vtb_org_child_1.bigint_col" + ") USING vtb_virtual_stb TAGS (" + "vtb_org_child_0.int_tag, " + "vtb_org_child_1.bool_tag, " + "vtb_org_child_2.float_tag, " + "vtb_org_child_3.double_tag, " + "vtb_org_child_4.nchar_32_tag, " + "vtb_org_child_0.binary_32_tag)") + + self.check_vtable_count(6, 0) + + # 3-part name: db.table.tag + tdSql.execute(f"CREATE VTABLE `vtb_vctb_positional_2` (" + "int_col FROM vtb_org_child_0.int_col, " + "bigint_col FROM vtb_org_child_1.bigint_col" + f") USING vtb_virtual_stb TAGS (" + f"{self.DB_NAME}.vtb_org_child_0.int_tag, " + f"{self.DB_NAME}.vtb_org_child_0.bool_tag, " + f"{self.DB_NAME}.vtb_org_child_0.float_tag, " + f"{self.DB_NAME}.vtb_org_child_0.double_tag, " + f"{self.DB_NAME}.vtb_org_child_0.nchar_32_tag, " + f"{self.DB_NAME}.vtb_org_child_0.binary_32_tag)") + + self.check_vtable_count(7, 0) + + tdLog.info("new positional tag reference syntax test passed") + + def test_tag_ref_mixed_syntax(self): + """Test mixed tag reference syntax: literals + old FROM + new specific + new positional""" + tdLog.info("test mixed tag reference syntax") + + tdSql.execute(f"use {self.DB_NAME};") + + # Mix of literal values, old FROM syntax, new specific syntax, and new positional syntax + tdSql.execute("CREATE VTABLE `vtb_vctb_mixed_0` (" + "int_col FROM vtb_org_child_0.int_col, " + "bigint_col FROM vtb_org_child_1.bigint_col" + ") USING vtb_virtual_stb TAGS (" + "100, " # literal + "bool_tag FROM vtb_org_child_1.bool_tag, " # new specific + "vtb_org_child_2.float_tag, " # new positional + "FROM vtb_org_child_3.double_tag, " # old FROM + "'mixed_nchar', " # literal + "binary_32_tag FROM vtb_org_child_0.binary_32_tag)") # new specific + + self.check_vtable_count(8, 0) + + # Mix with 3-part positional names + tdSql.execute(f"CREATE VTABLE `vtb_vctb_mixed_1` (" + "int_col FROM vtb_org_child_0.int_col, " + "bigint_col FROM vtb_org_child_1.bigint_col" + f") USING vtb_virtual_stb TAGS (" + f"int_tag FROM {self.DB_NAME}.vtb_org_child_0.int_tag, " # specific with 3-part ref + "false, " # literal + f"{self.DB_NAME}.vtb_org_child_2.float_tag, " # positional 3-part + "3.14, " # literal + "nchar_32_tag FROM vtb_org_child_4.nchar_32_tag, " # specific with 2-part ref + "'literal_bin')") # literal + + self.check_vtable_count(9, 0) + + tdLog.info("mixed tag reference syntax test passed") + + def test_tag_ref_with_column_ref(self): + """Test tag references combined with different column reference styles""" + tdLog.info("test tag references with different column reference styles") + + tdSql.execute(f"use {self.DB_NAME};") + + # Column refs without FROM + tag refs + tdSql.execute("CREATE VTABLE `vtb_vctb_colref_0` (" + "vtb_org_child_0.int_col, " + "vtb_org_child_1.bigint_col, " + "vtb_org_child_2.float_col" + ") USING vtb_virtual_stb TAGS (" + "int_tag FROM vtb_org_child_0.int_tag, " + "bool_tag FROM vtb_org_child_0.bool_tag, " + "float_tag FROM vtb_org_child_0.float_tag, " + "double_tag FROM vtb_org_child_0.double_tag, " + "nchar_32_tag FROM vtb_org_child_0.nchar_32_tag, " + "binary_32_tag FROM vtb_org_child_0.binary_32_tag)") + + self.check_vtable_count(10, 0) + + # Column refs with FROM + positional tag refs + tdSql.execute("CREATE VTABLE `vtb_vctb_colref_1` (" + "int_col FROM vtb_org_child_0.int_col, " + "bigint_col FROM vtb_org_child_1.bigint_col" + ") USING vtb_virtual_stb TAGS (" + "vtb_org_child_0.int_tag, " + "vtb_org_child_1.bool_tag, " + "vtb_org_child_2.float_tag, " + "vtb_org_child_3.double_tag, " + "vtb_org_child_4.nchar_32_tag, " + "vtb_org_child_0.binary_32_tag)") + + self.check_vtable_count(11, 0) + + tdLog.info("tag references with column reference styles test passed") + + def test_tag_ref_query_verify(self): + """Verify that tag references are correctly stored via SHOW CREATE VTABLE""" + tdLog.info("test tag reference storage correctness via SHOW CREATE VTABLE") + + tdSql.execute(f"use {self.DB_NAME};") + + # Create a vtable with specific tag refs (tag_name FROM table.tag) + tdSql.execute("CREATE VTABLE `vtb_vctb_verify_0` (" + "int_col FROM vtb_org_child_0.int_col" + ") USING vtb_virtual_stb TAGS (" + "int_tag FROM vtb_org_child_2.int_tag, " + "bool_tag FROM vtb_org_child_2.bool_tag, " + "float_tag FROM vtb_org_child_2.float_tag, " + "double_tag FROM vtb_org_child_2.double_tag, " + "nchar_32_tag FROM vtb_org_child_2.nchar_32_tag, " + "binary_32_tag FROM vtb_org_child_2.binary_32_tag)") + + self.check_vtable_count(12, 0) + + # Verify via SHOW CREATE VTABLE that tag references are stored + tdSql.query(f"SHOW CREATE VTABLE {self.DB_NAME}.vtb_vctb_verify_0;") + tdSql.checkRows(1) + create_sql = tdSql.getData(0, 1) + tdLog.info(f"SHOW CREATE VTABLE vtb_vctb_verify_0: {create_sql}") + # The create SQL should contain tag reference info + assert 'vtb_vctb_verify_0' in create_sql, "Table name should be in create SQL" + + # Create with positional syntax from vtb_org_child_3 + tdSql.execute("CREATE VTABLE `vtb_vctb_verify_1` (" + "int_col FROM vtb_org_child_0.int_col" + ") USING vtb_virtual_stb TAGS (" + "vtb_org_child_3.int_tag, " + "vtb_org_child_3.bool_tag, " + "vtb_org_child_3.float_tag, " + "vtb_org_child_3.double_tag, " + "vtb_org_child_3.nchar_32_tag, " + "vtb_org_child_3.binary_32_tag)") + + self.check_vtable_count(13, 0) + + tdSql.query(f"SHOW CREATE VTABLE {self.DB_NAME}.vtb_vctb_verify_1;") + tdSql.checkRows(1) + create_sql = tdSql.getData(0, 1) + tdLog.info(f"SHOW CREATE VTABLE vtb_vctb_verify_1: {create_sql}") + assert 'vtb_vctb_verify_1' in create_sql, "Table name should be in create SQL" + + # Create with mixed syntax: some literal, some ref + tdSql.execute("CREATE VTABLE `vtb_vctb_verify_2` (" + "int_col FROM vtb_org_child_0.int_col" + ") USING vtb_virtual_stb TAGS (" + "int_tag FROM vtb_org_child_4.int_tag, " # ref -> 4 + "true, " # literal + "float_tag FROM vtb_org_child_1.float_tag, " # ref -> 1.1 + "9.99, " # literal + "'my_nchar_val', " # literal + "binary_32_tag FROM vtb_org_child_0.binary_32_tag)") # ref -> bin_child0 + + self.check_vtable_count(14, 0) + + tdSql.query(f"SHOW CREATE VTABLE {self.DB_NAME}.vtb_vctb_verify_2;") + tdSql.checkRows(1) + create_sql = tdSql.getData(0, 1) + tdLog.info(f"SHOW CREATE VTABLE vtb_vctb_verify_2: {create_sql}") + assert 'vtb_vctb_verify_2' in create_sql, "Table name should be in create SQL" + # Verify literal tag values are present + assert 'my_nchar_val' in create_sql, "Literal nchar tag value should be in create SQL" + + # Verify tag refs via describe - check the 'ref' column + tdSql.query(f"DESCRIBE {self.DB_NAME}.vtb_vctb_verify_0;") + tdLog.info(f"DESCRIBE vtb_vctb_verify_0 rows: {tdSql.queryRows}") + + tdLog.info("tag reference storage correctness test passed") + + def test_tag_ref_error_cases(self): + """Test error cases for tag references""" + tdLog.info("test tag reference error cases") + + tdSql.execute(f"use {self.DB_NAME};") + + # 1. Tag reference column does not exist in source table + tdSql.error("CREATE VTABLE `vtb_vctb_err_0` (" + "int_col FROM vtb_org_child_0.int_col" + ") USING vtb_virtual_stb TAGS (" + "int_tag FROM vtb_org_child_0.not_exist_tag, " + "false, 1.0, 2.0, 'nchar', 'bin')") + + # 2. Tag reference table does not exist + tdSql.error("CREATE VTABLE `vtb_vctb_err_1` (" + "int_col FROM vtb_org_child_0.int_col" + ") USING vtb_virtual_stb TAGS (" + "int_tag FROM not_exist_table.int_tag, " + "false, 1.0, 2.0, 'nchar', 'bin')") + + # 3. Tag reference type mismatch (referencing a non-tag column as tag) + tdSql.error("CREATE VTABLE `vtb_vctb_err_2` (" + "int_col FROM vtb_org_child_0.int_col" + ") USING vtb_virtual_stb TAGS (" + "int_tag FROM vtb_org_child_0.int_col, " + "false, 1.0, 2.0, 'nchar', 'bin')") + + # 4. Positional tag ref - column does not exist + tdSql.error("CREATE VTABLE `vtb_vctb_err_3` (" + "int_col FROM vtb_org_child_0.int_col" + ") USING vtb_virtual_stb TAGS (" + "vtb_org_child_0.not_exist_tag, " + "false, 1.0, 2.0, 'nchar', 'bin')") + + # 5. Positional tag ref - table does not exist + tdSql.error("CREATE VTABLE `vtb_vctb_err_4` (" + "int_col FROM vtb_org_child_0.int_col" + ") USING vtb_virtual_stb TAGS (" + "not_exist_table.int_tag, " + "false, 1.0, 2.0, 'nchar', 'bin')") + + # 6. Tag type mismatch - referencing int_tag for bool_tag position + tdSql.error("CREATE VTABLE `vtb_vctb_err_5` (" + "int_col FROM vtb_org_child_0.int_col" + ") USING vtb_virtual_stb TAGS (" + "0, " + "bool_tag FROM vtb_org_child_0.int_tag, " + "1.0, 2.0, 'nchar', 'bin')") + + # 7. 3-part positional with non-existent db + tdSql.error("CREATE VTABLE `vtb_vctb_err_6` (" + "int_col FROM vtb_org_child_0.int_col" + ") USING vtb_virtual_stb TAGS (" + "nonexist_db.vtb_org_child_0.int_tag, " + "false, 1.0, 2.0, 'nchar', 'bin')") + + tdLog.info("tag reference error cases test passed") + + def test_tag_ref_with_specific_tags(self): + """Test tag references with specific tag names in TAGS clause""" + tdLog.info("test tag references with specific tag names") + + tdSql.execute(f"use {self.DB_NAME};") + + # Use specific tag names (not all tags, just some) with tag refs + tdSql.execute("CREATE VTABLE `vtb_vctb_spectag_0` (" + "int_col FROM vtb_org_child_0.int_col" + ") USING vtb_virtual_stb (int_tag, bool_tag, float_tag, double_tag, nchar_32_tag, binary_32_tag) TAGS (" + "int_tag FROM vtb_org_child_1.int_tag, " + "false, " + "float_tag FROM vtb_org_child_2.float_tag, " + "3.14, " + "'nchar_val', " + "'bin_val')") + + self.check_vtable_count(15, 0) + + # Verify via SHOW CREATE VTABLE + tdSql.query(f"SHOW CREATE VTABLE {self.DB_NAME}.vtb_vctb_spectag_0;") + tdSql.checkRows(1) + create_sql = tdSql.getData(0, 1) + tdLog.info(f"SHOW CREATE VTABLE vtb_vctb_spectag_0: {create_sql}") + assert 'vtb_vctb_spectag_0' in create_sql, "Table name should be in create SQL" + # Literal tag values should be present + assert 'nchar_val' in create_sql, "Literal nchar tag value should be in create SQL" + assert 'bin_val' in create_sql, "Literal binary tag value should be in create SQL" + + tdLog.info("tag references with specific tag names test passed") + + def run(self): + tdLog.debug(f"start to excute {__file__}") + + self.prepare_org_tables() + self.test_tag_ref_old_syntax() + self.test_tag_ref_specific_syntax() + self.test_tag_ref_positional_syntax() + self.test_tag_ref_mixed_syntax() + self.test_tag_ref_with_column_ref() + self.test_tag_ref_query_verify() + self.test_tag_ref_error_cases() + self.test_tag_ref_with_specific_tags() + + tdLog.success(f"{__file__} successfully executed") + + +tdCases.addLinux(__file__, TDTestCase()) +tdCases.addWindows(__file__, TDTestCase()) diff --git a/tests/test_new/case_list_docs/query/hint.md b/tests/test_new/case_list_docs/query/hint.md deleted file mode 100644 index a719bdb1c749..000000000000 --- a/tests/test_new/case_list_docs/query/hint.md +++ /dev/null @@ -1 +0,0 @@ -::: query.hint.test_hint \ No newline at end of file