Refactor/freemine.tdengine.stmt2#35366
Conversation
…tmt2_prepare followed by taos_stmt2_exec
…ert into ? (...) values (...)
…d during `taos_stmt2_prepare`
There was a problem hiding this comment.
Code Review
This pull request introduces support for literal SQL statements in the TAOS_STMT2 interface, allowing direct execution without parameter binding. It adds parsing utilities (qIsLiteralSql, qPureParseInsert), state management (SStmt2LiteralCtx), and updates the statement preparation and execution flows. The review comments point out several critical issues: a misleading syntax error when parsing standard inserts on non-existent tables, a connection object reference leak in stmtPrepareLiteral2 due to a missing release call on early return, dead code in the parser header, an implicit variable dependency in the SET_ERR macro, and redundant preprocessor branches in thash.c.
| #ifdef _TD_LOONGARCH_64 | ||
| SHashNode *pNewNode = taosMemoryCalloc(1, sizeof(SHashNode) + keyLen + dsize + 1); | ||
| #else | ||
| SHashNode *pNewNode = taosMemoryMalloc(sizeof(SHashNode) + keyLen + dsize + 1); | ||
| SHashNode *pNewNode = taosMemoryCalloc(1, sizeof(SHashNode) + keyLen + dsize + 1); | ||
| #endif |
There was a problem hiding this comment.
|
● Review summary
Publish preview
|
🤖 Code Review by GitHub CopilotPR: #35366 Change SummaryThis PR refactors STMT2 prepare/exec semantics by classifying SQL into two categories: Literal SQL (no Findings (5 total)🔴 [high/high] Portability:
|
| if (pRequest->msgBuf != NULL && pRequest->msgBufLen > 0) { | ||
| pRequest->msgBuf[0] = '\0'; | ||
|
|
||
| if (!pRequest->literal_by_stmt2) { |
There was a problem hiding this comment.
[medium/high] queryTableNotExistAsEmpty is silently bypassed for literal stmt2
The tsQueryTbNotExistAsEmpty option (converting TABLE_NOT_EXIST into an empty result set) is skipped when pRequest->literal_by_stmt2 is non-NULL. The same SELECT query through taos_query returns an empty result set; through literal stmt2 it returns an error. This behavioral difference can silently break JDBC/ODBC applications that rely on this option.
Fix direction: Preserve queryTableNotExistAsEmpty handling for literal stmt2 inside the if (!pRequest->literal_by_stmt2) block, unless the divergence is intentional and documented.
There was a problem hiding this comment.
同上,stmt函数单独封装,不要和query耦合
| @@ -677,7 +677,7 @@ SHashNode *doCreateHashNode(const void *key, size_t keyLen, const void *pData, s | |||
| #ifdef _TD_LOONGARCH_64 | |||
| SHashNode *pNewNode = taosMemoryCalloc(1, sizeof(SHashNode) + keyLen + dsize + 1); | |||
There was a problem hiding this comment.
这个不是无关代码,valgrind 报告了read uninitialized data 错。本来我应该单独提一个PR的。
| } | ||
|
|
||
| void taosAsyncQueryImplWithReqid(uint64_t connId, const char* sql, __taos_async_fn_t fp, void* param, bool validateOnly, | ||
| void taosAsyncQueryImplWithReqid(TAOS_STMT2 *stmt, uint64_t connId, const char* sql, __taos_async_fn_t fp, void* param, bool validateOnly, |
There was a problem hiding this comment.
stmt的函数单独封装,不要和query耦合在一起
There was a problem hiding this comment.
just don't get a clear solutions, otherwise i have to hard-copy the whole logic in the block.
or, what suggestions might be here?
| if (pRequest->msgBuf != NULL && pRequest->msgBufLen > 0) { | ||
| pRequest->msgBuf[0] = '\0'; | ||
|
|
||
| if (!pRequest->literal_by_stmt2) { |
There was a problem hiding this comment.
同上,stmt函数单独封装,不要和query耦合
| } | ||
|
|
||
| if (stmtIsLiteral(pStmt)) { | ||
| return stmtExecLiteral2(stmt, affected_rows); |
There was a problem hiding this comment.
这里没有考虑async情况pStmt->options.asyncExecFn,用户会设置callback函数去接收结果
There was a problem hiding this comment.
will be handled in stmtExecLiteral2 for the next commit
1. remove `D` / `A` unused block 2. fully support asynchronous mode triggered by `taos_stmt2_init` 3. `qPureParseInsert` correctly parse VALUES clause 4. legacy support `queryTableNotExistAsEmpty`
…p no-fields-result-set mandatorily
1. reuse SRequestObj::msgBuf if possible 2. SRequestObj lifecycle management adjustment in `clientImpl.c::doRequestCallback`
Pengrongkun
left a comment
There was a problem hiding this comment.
AI review了几个意见,请考虑重新async模式下重新prepare的情况
| return pStmt->ctx.code; | ||
| } | ||
|
|
||
| // NOTE: what if taosAsyncExecLiteral failed prematurelly? |
There was a problem hiding this comment.
问题:stmtExecLiteral2 在异步模式(asyncExecFn != NULL)下立即返回,但 stmtLiteralCallback 仍会在之后某时刻触发并解引用 pStmt。若此期间用户调用 taos_stmt2_close() 或对同一 stmt 重新 prepare,stmtLiteralCallback 将访问已释放或已被重置的 pStmt 内存,触发 use-after-free 或 abort()。
证据:
clientStmt2.c:stmtExecLiteral2:async 分支直接return TSDB_CODE_SUCCESS而不等待回调clientStmt2.c:stmtClose2:释放 ctx 但无 literal-specific 的 in-flight 等待屏障clientStmt2.c:stmtLiteralCallback:回调中pStmt->exec.pRequest != res时调用abort(),说明作者已意识到指针失效风险
与本 MR 的关联:本 MR 新增 async literal 执行路径并以 pStmt 作为回调状态,但未为该新路径添加对应的生命周期屏障。
修复建议:在 stmtClose2 和 stmtDeepReset 中新增针对 literal path 的 in-flight 检测,若 ctx.executing == 1 则等待信号量后再继续销毁;或在 async 回调触发前持有 stmt 的引用计数。
| code = prepareAndParseSqlSyntax(&pWrapper, pRequest, updateMetaForce); | ||
| } | ||
|
|
||
| if (pRequest->literal_by_stmt2) { |
There was a problem hiding this comment.
问题:SRequestObj.literal_by_stmt2 是指向 TAOS_STMT2* 的裸指针(注释为 // reference only),无所有权/生命周期协议。在异步 exec 路径中,doAsyncQuery 会解引用该指针检查 ctx.prepared。若 stmt 在请求飞行期间被 close,该指针即变为悬空指针。
证据:
clientInt.h:TAOS_STMT2 *literal_by_stmt2; // reference onlyclientMain.c:doAsyncQuery:TAOS_STMT2* stmt = pRequest->literal_by_stmt2; STscStmt2* pStmt = (STscStmt2*)stmt;
与本 MR 的关联:本 MR 引入该请求→stmt 反向引用以实现 literal exec-direct 功能。
修复建议:在赋值 literal_by_stmt2 时对 stmt 加引用计数,请求完成后释放;或在 stmt reset/close 时显式将 pRequest->literal_by_stmt2 置 NULL(需要加锁)。
| #define RETURN_EXPECTING(msg) do { \ | ||
| (void)snprintf(pCtx->buf, sizeof(pCtx->buf), \ | ||
| "expecting %s, but got `%.*s`", \ | ||
| msg, token.n < 10 ? 10 : token.n, token.z); \ |
| } | ||
|
|
||
| static inline int | ||
| stmt2LiteralCtxIsValid(SStmt2LiteralCtx *ctx) { |
| static inline void | ||
| stmt2LiteralCtxRelease(SStmt2LiteralCtx *ctx) { | ||
| stmt2LiteralCtxReset(ctx); | ||
| if (ctx->sem_valid) { |
There was a problem hiding this comment.
问题:stmtDeepReset 调用 stmt2LiteralCtxReset(仅重置 flags,不销毁/排干信号量),若此时有一个 async exec 的 stmtLiteralCallback 尚未触发,其后续的 tsem_post 会让下一次 stmtPrepareLiteral2 的 tsem_wait 提前返回,携带的是上一次执行的 code 和 pRequest,导致新 prepare 静默失败或状态混乱。
证据:clientStmt2.c:stmt2LiteralCtxReset(不重置 sem_valid),stmtDeepReset(调用 reset 而非 release),stmtPrepareLiteral2(stmt2LiteralCtxInit 看到 sem_valid == 1 直接复用旧信号量)。
修复建议:在 stmtDeepReset 中对 literal 已初始化的信号量先排干(或 destroy + re-init),确保下次 prepare 使用干净状态。
… operations have completed
Description
Currently,
taos_stmt2_preparedoes not fully parse parameterized INSERT statements untiltaos_stmt2_get_fieldsortaos_stmt2_bind_paramis called. It also lacks a clear way to handle literal SQL statements during the prepare/execute stages.To fix this, this approach splits the logic based on the statement type:
taos_query. The parsing part is handled duringtaos_stmt2_prepare, and the execution part is handled intaos_stmt2_exec.taos_stmt2_get_fieldsinternally beforetaos_stmt2_preparereturns.Benefits for Middleware (JDBC / ODBC):
This change makes implementing upper-level drivers (like JDBC and ODBC) much easier. Standard APIs like
prepare,bind,execute, andexecuteDirectnow map directly to the correspondingtaosfunctions. Middleware developers no longer need to parse or inspect the SQL string type just to decide which underlyingtaosAPI to call.Issue(s)
Checklist
Please check the items in the checklist if applicable.
-- both stmtTest and stmt2Test passed
-- both synchronous/asynchronous passed
-- both native/websocket passed