Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 125 additions & 41 deletions source/client/test/stmt2Test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -387,44 +387,6 @@ TEST(stmt2Case, timezone) {
}
}

TEST(stmt2Case, not_support_vtable) {
TAOS* taos = taos_connect("localhost", "root", "taosdata", "", 0);
ASSERT_NE(taos, nullptr);
do_query(taos, "drop database if exists stmt2_testdb_37");
do_query(taos, "create database IF NOT EXISTS stmt2_testdb_37");
do_query(taos,
"CREATE STABLE stmt2_testdb_37.meters_v (ts timestamp,current float, voltage int, phase float) "
"TAGS(location varchar(64), "
"group_id int) VIRTUAL 1;");
do_query(taos, "use stmt2_testdb_37");

TAOS_STMT2_OPTION option = {0, true, true, NULL, NULL};

TAOS_STMT2* stmt = taos_stmt2_init(taos, &option);
ASSERT_NE(stmt, nullptr);

const char* sql = "select * from stmt2_testdb_37.meters_v where ts > ? and ts < ? limit ?";
int code = taos_stmt2_prepare(stmt, sql, 0);
checkError(stmt, code, __FILE__, __LINE__);

int t64_len[1] = {sizeof(int64_t)};
int b_len[1] = {3};
int x = 2;
int x_len = sizeof(int);
int64_t ts[2] = {1591060627000, 1591060628005};
TAOS_STMT2_BIND params[3] = {{TSDB_DATA_TYPE_TIMESTAMP, &ts[0], t64_len, NULL, 1},
{TSDB_DATA_TYPE_TIMESTAMP, &ts[1], t64_len, NULL, 1},
{TSDB_DATA_TYPE_INT, &x, &x_len, NULL, 1}};
TAOS_STMT2_BIND* paramv = &params[0];
TAOS_STMT2_BINDV bindv = {1, NULL, NULL, &paramv};
code = taos_stmt2_bind_param(stmt, &bindv, -1);
ASSERT_EQ(code, TSDB_CODE_VTABLE_NOT_SUPPORT_STMT);

taos_stmt2_close(stmt);
do_query(taos, "drop database if exists stmt2_testdb_7");
taos_close(taos);
}

TEST(stmt2Case, stmt2_test_limit) {
TAOS* taos = taos_connect("localhost", "root", "taosdata", "", 0);
ASSERT_NE(taos, nullptr);
Expand Down Expand Up @@ -1835,7 +1797,7 @@ TEST(stmt2Case, stmt2_insert_duplicate) {
stmt = taos_stmt2_init(taos, &option);
code =
taos_stmt2_prepare(stmt, "INSERT INTO `stmt2_testdb_18`.`stb2` (ts,int_col,int_tag,tbname) VALUES (?,?,?,?)", 0);
checkError(stmt, code, __FILE__, __LINE__);
checkError(stmt, code, __FILE__, __LINE__);
char* tbname3[2] = {"tb1", "tb10"};
TAOS_STMT2_BIND tag = {TSDB_DATA_TYPE_INT, &tag_i[0], &tag_l[0], NULL, 1};
TAOS_STMT2_BIND* pTag[2] = {&tag, &tag};
Expand Down Expand Up @@ -4088,9 +4050,8 @@ TEST(stmt2Case, tbname) {

taos_free_result(result);
taos_stmt2_close(stmt);
do_query(taos, "drop table if exists stmt2_testdb_22.tb1");
do_query(taos, "drop table if exists stmt2_testdb_22.tb2");
}
do_query(taos, "drop database if exists stmt2_testdb_22");
}

// TS-7074
Expand Down Expand Up @@ -4240,4 +4201,127 @@ TEST(stmt2Case, no_tag) {
taos_close(taos);
}

TEST(stmt2Case, vtable) {
TAOS* taos = taos_connect("localhost", "root", "taosdata", "", 0);
ASSERT_NE(taos, nullptr);

// Drop database if exists
do_query(taos, "drop database if exists stmt2_testdb_29");

// Create database
do_query(taos, "create database stmt2_testdb_29");
do_query(taos, "use stmt2_testdb_29");

// Create normal table
do_query(taos, "create table ntb_0(ts timestamp, c1 int, c2 int, c3 int)");

// Create super table and child table
do_query(taos, "create stable stb (ts timestamp, c1 int, c2 int, c3 int) tags (t1 int)");
do_query(taos, "create table ctb_0 using stb tags (1)");

// Insert data
do_query(taos, "insert into ntb_0 values(1591060628000, 1, 1, 1)");
do_query(taos, "insert into ntb_0 values(1591060628100, 2, 2, 2)");
do_query(taos, "insert into ntb_0 values(1591060628200, 3, 3, 3)");

do_query(taos, "insert into ctb_0 values(1591060628000, 1, 1, 1)");
do_query(taos, "insert into ctb_0 values(1591060628100, 2, 2, 2)");
do_query(taos, "insert into ctb_0 values(1591060628200, 3, 3, 3)");

// Create virtual tables
do_query(taos, "create stable vstb (ts timestamp, c1 int, c2 int, c3 int) tags (t1 int) virtual 1");
do_query(taos, "create vtable vtb_0 (c1 from ntb_0.c1, c2 from ntb_0.c2, c3 from ntb_0.c3) using vstb tags (1)");
do_query(taos, "create vtable vtb_1 (c1 from ctb_0.c1, c2 from ctb_0.c2, c3 from ctb_0.c3) using vstb tags (2)");

// 0. Query vstable before alter origin table
TAOS_STMT2_OPTION option = {0, true, true, NULL, NULL};

TAOS_STMT2* stmt = taos_stmt2_init(taos, &option);
ASSERT_NE(stmt, nullptr);

// query virtual super table
const char* sql = "select tbname,c1 from vstb where ts = ? order by tbname";
int code = taos_stmt2_prepare(stmt, sql, 0);
checkError(stmt, code, __FILE__, __LINE__);

int fieldNum = 0;
TAOS_FIELD_ALL* pFields = NULL;
code = taos_stmt2_get_fields(stmt, &fieldNum, &pFields);
checkError(stmt, code, __FILE__, __LINE__);
ASSERT_EQ(fieldNum, 1);

int t64_len = sizeof(int64_t);
int64_t ts = 1591060628000;
TAOS_STMT2_BIND params = {TSDB_DATA_TYPE_TIMESTAMP, &ts, &t64_len, NULL, 1};
TAOS_STMT2_BIND* paramv = &params;
TAOS_STMT2_BINDV bindv = {1, NULL, NULL, &paramv};
code = taos_stmt2_bind_param(stmt, &bindv, -1);
checkError(stmt, code, __FILE__, __LINE__);

taos_stmt2_exec(stmt, NULL);
checkError(stmt, code, __FILE__, __LINE__);

TAOS_RES* pRes = taos_stmt2_result(stmt);
ASSERT_NE(pRes, nullptr);
TAOS_ROW row = taos_fetch_row(pRes);
ASSERT_NE(row, nullptr);
ASSERT_EQ(strncmp((char*)row[0], "vtb_0", 5), 0);
ASSERT_EQ(*(int*)row[1], 1);
row = taos_fetch_row(pRes);
ASSERT_NE(row, nullptr);
ASSERT_EQ(strncmp((char*)row[0], "vtb_1", 5), 0);
ASSERT_EQ(*(int*)row[1], 1);

// query virtual child table
code = taos_stmt2_prepare(stmt, "select c1 from vtb_0 where ts = ? order by c1", 0);
checkError(stmt, code, __FILE__, __LINE__);
int t2_len = 5;
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable t2_len is declared but never used. It should be removed.

Suggested change
int t2_len = 5;

Copilot uses AI. Check for mistakes.
code = taos_stmt2_bind_param(stmt, &bindv, -1);
checkError(stmt, code, __FILE__, __LINE__);
code = taos_stmt2_exec(stmt, NULL);
checkError(stmt, code, __FILE__, __LINE__);
pRes = taos_stmt2_result(stmt);
ASSERT_NE(pRes, nullptr);
row = taos_fetch_row(pRes);
ASSERT_NE(row, nullptr);
ASSERT_EQ(*(int*)row[0], 1);

// composite condition
code = taos_stmt2_prepare(stmt, "select c1,c2,c3 from vstb where c1 > ? and c3 < ? order by c1", 0);
checkError(stmt, code, __FILE__, __LINE__);
fieldNum = 0;
pFields = NULL;
code = taos_stmt2_get_fields(stmt, &fieldNum, &pFields);
checkError(stmt, code, __FILE__, __LINE__);
ASSERT_EQ(fieldNum, 2);

int c1 = 1;
int c3 = 3;
int c3_len = sizeof(int);
TAOS_STMT2_BIND params2[2] = {{TSDB_DATA_TYPE_INT, &c1, &c3_len, NULL, 1},
{TSDB_DATA_TYPE_INT, &c3, &c3_len, NULL, 1}};
TAOS_STMT2_BIND* paramv2 = &params2[0];
TAOS_STMT2_BINDV bindv2 = {1, NULL, NULL, &paramv2};
code = taos_stmt2_bind_param(stmt, &bindv2, -1);
checkError(stmt, code, __FILE__, __LINE__);
code = taos_stmt2_exec(stmt, NULL);
checkError(stmt, code, __FILE__, __LINE__);
pRes = taos_stmt2_result(stmt);
ASSERT_NE(pRes, nullptr);
row = taos_fetch_row(pRes);
ASSERT_NE(row, nullptr);
ASSERT_EQ(*(int*)row[0], 2);
ASSERT_EQ(*(int*)row[1], 2);
ASSERT_EQ(*(int*)row[2], 2);
row = taos_fetch_row(pRes);
ASSERT_NE(row, nullptr);
row = taos_fetch_row(pRes);
ASSERT_EQ(row, nullptr);

// Cleanup
taos_stmt2_close(stmt);
do_query(taos, "drop database if exists stmt2_testdb_29");
taos_close(taos);
}

#pragma GCC diagnostic pop
2 changes: 2 additions & 0 deletions source/libs/catalog/src/ctgCache.c
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,8 @@ int32_t ctgCopyTbMeta(SCatalog *pCtg, SCtgTbMetaCtx *ctx, SCtgDBCache **pDb, SCt
if (colRefSize != 0) {
(*pTableMeta)->colRef = (SColRef *)((char *)*pTableMeta + metaSize + schemaExtSize);
TAOS_MEMCPY((*pTableMeta)->colRef, tmpRef, colRefSize);
(*pTableMeta)->numOfColRefs = numOfColRefs; // Set numOfColRefs for virtual child table
(*pTableMeta)->rversion = rversion; // Set rversion for virtual child table
} else {
(*pTableMeta)->colRef = NULL;
}
Expand Down
3 changes: 1 addition & 2 deletions source/libs/parser/src/parInsertSql.c
Original file line number Diff line number Diff line change
Expand Up @@ -3277,8 +3277,7 @@ static int32_t checkTableClauseFirstToken(SInsertParseContext* pCxt, SVnodeModif
}
} else {
pCxt->stmtTbNameFlag |= IS_FIXED_VALUE;
parserWarn("QID:0x%" PRIx64 ", table name is specified in sql, ignore the table name in bind param",
pCxt->pComCxt->requestId);
parserTrace("QID:0x%" PRIx64 ", stmt tbname:%s is specified in sql", pCxt->pComCxt->requestId, pTbName->z);
*pHasData = true;
}
return TSDB_CODE_SUCCESS;
Expand Down
19 changes: 14 additions & 5 deletions source/libs/parser/src/parTranslater.c
Original file line number Diff line number Diff line change
Expand Up @@ -4871,9 +4871,20 @@ static int32_t setVSuperTableRefScanVgroupList(STranslateContext* pCxt, SName* p
vgroupList = taosArrayInit(1, sizeof(SVgroupInfo));
QUERY_CHECK_NULL(vgroupList, code, lino, _return, terrno);


SArray* pVStbRefs = NULL;
PAR_ERR_JRET(getVStbRefDbsFromCache(pCxt->pMetaCache, pName, &pVStbRefs));
code = getVStbRefDbsFromCache(pCxt->pMetaCache, pName, &pVStbRefs);

// Handle the case where VStbRefDbs data is not available (e.g., stmt scenario)
if (TSDB_CODE_PAR_TABLE_NOT_EXIST == code || TSDB_CODE_PAR_INTERNAL_ERROR == code) {
// VStbRefDbs not available in cache (stmt scenario without async metadata fetch)
// Use empty vgroup list - the executor will resolve vgroups at runtime
taosMemoryFreeClear(pRefScanTable->pVgroupList);
PAR_ERR_JRET(toVgroupsInfo(vgroupList, &pRefScanTable->pVgroupList));
code = TSDB_CODE_SUCCESS;
goto _return;
}
PAR_ERR_JRET(code);

dbNameHash = tSimpleHashInit(taosArrayGetSize(pVStbRefs), taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY));
QUERY_CHECK_NULL(dbNameHash, code, lino, _return, terrno);

Expand Down Expand Up @@ -5703,9 +5714,7 @@ static int32_t translateVirtualTable(STranslateContext* pCxt, SNode** pTable, SN
// virtual table only support select operation
PAR_ERR_JRET(TSDB_CODE_TSC_INVALID_OPERATION);
}
if (pCxt->pParseCxt->stmtBindVersion > 0) {
PAR_ERR_JRET(TSDB_CODE_VTABLE_NOT_SUPPORT_STMT);
}

if (pCxt->pParseCxt->topicQuery) {
PAR_ERR_JRET(TSDB_CODE_VTABLE_NOT_SUPPORT_TOPIC);
}
Expand Down
14 changes: 14 additions & 0 deletions source/libs/parser/src/parUtil.c
Original file line number Diff line number Diff line change
Expand Up @@ -1561,8 +1561,22 @@ int32_t getVStbRefDbsFromCache(SParseMetaCache* pMetaCache, const SName* pName,
if (TSDB_CODE_SUCCESS != code) {
return code;
}

if (NULL == pMetaCache || NULL == pMetaCache->pVStbRefDbs) {
return TSDB_CODE_PAR_TABLE_NOT_EXIST;
}

SArray* pRefs = NULL;
code = getMetaDataFromHash(fullName, strlen(fullName), pMetaCache->pVStbRefDbs, (void**)&pRefs);

// Special handling for stmt scenario where slot is reserved but data is not filled
// In this case, getMetaDataFromHash returns INTERNAL_ERROR because value is nullPointer
if (TSDB_CODE_PAR_INTERNAL_ERROR == code) {
// Data not filled in cache (stmt scenario without putMetaDataToCache)
// Return table not exist to indicate VStbRefDbs is not available
return TSDB_CODE_PAR_TABLE_NOT_EXIST;
}

if (TSDB_CODE_SUCCESS == code && NULL != pRefs) {
*pOutput = pRefs;
}
Expand Down
1 change: 1 addition & 0 deletions source/libs/planner/src/planLogicCreater.c
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,7 @@ static int32_t createVirtualSuperTableLogicNode(SLogicPlanContext* pCxt, SSelect
// TODO(smj) : create a fake logic node, no need to collect column
PLAN_ERR_JRET(createScanLogicNode(pCxt, pSelect, (SRealTableNode*)nodesListGetNode(pVirtualTable->refTables, 1), &pInsColumnsScan));
nodesDestroyList(((SScanLogicNode*)pInsColumnsScan)->node.pTargets);
((SScanLogicNode*)pInsColumnsScan)->node.pTargets = NULL; // Set to NULL after destroy to avoid use-after-free
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The comment on line 1134 mentions "avoid use-after-free", but this is not accurate. Setting a pointer to NULL after destroying it prevents double-free issues and dangling pointer access, not use-after-free specifically. Consider updating the comment to: "Set to NULL after destroy to prevent double-free or dangling pointer issues".

Suggested change
((SScanLogicNode*)pInsColumnsScan)->node.pTargets = NULL; // Set to NULL after destroy to avoid use-after-free
((SScanLogicNode*)pInsColumnsScan)->node.pTargets = NULL; // Set to NULL after destroy to prevent double-free or dangling pointer issues

Copilot uses AI. Check for mistakes.
PLAN_ERR_JRET(addInsColumnScanCol((SRealTableNode*)nodesListGetNode(pVirtualTable->refTables, 1), &((SScanLogicNode*)pInsColumnsScan)->pScanCols));
PLAN_ERR_JRET(createColumnByRewriteExprs(((SScanLogicNode*)pInsColumnsScan)->pScanCols, &((SScanLogicNode*)pInsColumnsScan)->node.pTargets));
((SScanLogicNode *)pInsColumnsScan)->virtualStableScan = true;
Expand Down
Loading