@@ -195,7 +195,7 @@ static int32_t stmtSwitchStatus(STscStmt2* pStmt, STMT_STATUS newStatus) {
195195 case STMT_EXECUTE :
196196 if (STMT_TYPE_QUERY == pStmt -> sql .type ) {
197197 if (STMT_STATUS_NE (ADD_BATCH ) && STMT_STATUS_NE (FETCH_FIELDS ) && STMT_STATUS_NE (BIND ) &&
198- STMT_STATUS_NE (BIND_COL )) {
198+ STMT_STATUS_NE (BIND_COL ) && STMT_STATUS_NE ( PREPARE ) ) {
199199 code = TSDB_CODE_TSC_STMT_API_ERROR ;
200200 }
201201 } else {
@@ -2441,13 +2441,32 @@ int stmtExec2(TAOS_STMT2* stmt, int* affected_rows) {
24412441 while (atomic_load_8 ((int8_t * )& pStmt -> asyncBindParam .asyncBindNum ) > 0 ) {
24422442 (void )taosThreadCondWait (& pStmt -> asyncBindParam .waitCond , & pStmt -> asyncBindParam .mutex );
24432443 }
2444+ // Capture prevStatus after waiting so we see the status after any async bind completes.
2445+ STMT_STATUS prevStatus = pStmt -> sql .status ;
24442446 STMT_ERR_RET (taosThreadMutexUnlock (& pStmt -> asyncBindParam .mutex ));
24452447
24462448 if (pStmt -> sql .stbInterlaceMode ) {
24472449 STMT_ERR_RET (stmtAddBatch2 (pStmt ));
24482450 }
24492451
2450- STMT_ERR_RET (stmtSwitchStatus (pStmt , STMT_EXECUTE ));
2452+ // For the prepare → execute shortcut (no bind_param calls), parse the SQL
2453+ // now so that sql.type is set before stmtSwitchStatus inspects it.
2454+ if (prevStatus == STMT_PREPARE ) {
2455+ // Ensure pRequest exists even when the statement was served from cache
2456+ // (needParse == false). Without a live pRequest, launchQueryImpl would
2457+ // receive a NULL pointer and crash.
2458+ if (pStmt -> exec .pRequest == NULL ) {
2459+ code = stmtCreateRequest (pStmt );
2460+ if (code != TSDB_CODE_SUCCESS ) goto _return ;
2461+ }
2462+ if (pStmt -> bInfo .needParse ) {
2463+ code = stmtParseSql (pStmt );
2464+ if (code != TSDB_CODE_SUCCESS ) goto _return ;
2465+ }
2466+ }
2467+
2468+ code = stmtSwitchStatus (pStmt , STMT_EXECUTE );
2469+ if (code != TSDB_CODE_SUCCESS ) goto _return ;
24512470
24522471 if (STMT_TYPE_QUERY != pStmt -> sql .type ) {
24532472 if (pStmt -> sql .stbInterlaceMode ) {
@@ -2476,6 +2495,63 @@ int stmtExec2(TAOS_STMT2* stmt, int* affected_rows) {
24762495 }
24772496 }
24782497
2498+ // For the prepare→execute shortcut with no '?' params, complete the
2499+ // bind/translate step that is normally done inside stmtBindv2.
2500+ if (STMT_TYPE_QUERY == pStmt -> sql .type && prevStatus == STMT_PREPARE &&
2501+ pStmt -> sql .pQuery && pStmt -> sql .pQuery -> pPrepareRoot ) {
2502+ if (pStmt -> sql .pQuery -> placeholderNum > 0 ) {
2503+ // User left '?' parameters unbound — this is API misuse.
2504+ code = TSDB_CODE_TSC_STMT_API_ERROR ;
2505+ goto _return ;
2506+ }
2507+ // No placeholders: clone pPrepareRoot → pRoot (loop inside qStmtBindParams2 is skipped).
2508+ code = qStmtBindParams2 (pStmt -> sql .pQuery , NULL , -1 , pStmt -> taos -> optionInfo .charsetCxt );
2509+ if (code != TSDB_CODE_SUCCESS ) goto _return ;
2510+
2511+ SParseContext ctx = {.requestId = pStmt -> exec .pRequest -> requestId ,
2512+ .acctId = pStmt -> taos -> acctId ,
2513+ .db = pStmt -> exec .pRequest -> pDb ,
2514+ .topicQuery = false,
2515+ .pSql = pStmt -> sql .sqlStr ,
2516+ .sqlLen = pStmt -> sql .sqlLen ,
2517+ .pMsg = pStmt -> exec .pRequest -> msgBuf ,
2518+ .msgLen = ERROR_MSG_BUF_DEFAULT_SIZE ,
2519+ .pTransporter = pStmt -> taos -> pAppInfo -> pTransporter ,
2520+ .pStmtCb = NULL ,
2521+ .pUser = pStmt -> taos -> user ,
2522+ .stmtBindVersion = pStmt -> exec .pRequest -> stmtBindVersion };
2523+ ctx .mgmtEpSet = getEpSet_s (& pStmt -> taos -> pAppInfo -> mgmtEp );
2524+ code = catalogGetHandle (pStmt -> taos -> pAppInfo -> clusterId , & ctx .pCatalog );
2525+ if (code != TSDB_CODE_SUCCESS ) goto _return ;
2526+
2527+ SMetaData metaData = {0 };
2528+ code = stmtFetchMetadataForQuery (pStmt , & ctx , & metaData );
2529+ if (code != TSDB_CODE_SUCCESS ) goto _return ;
2530+
2531+ code = qStmtParseQuerySql (& ctx , pStmt -> sql .pQuery , & metaData );
2532+ if (code == TSDB_CODE_SUCCESS ) {
2533+ (void )memcpy (& pStmt -> exec .pRequest -> parseMeta , & metaData , sizeof (SMetaData ));
2534+ (void )memset (& metaData , 0 , sizeof (SMetaData ));
2535+ } else {
2536+ catalogFreeMetaData (& metaData );
2537+ (void )memset (& metaData , 0 , sizeof (SMetaData ));
2538+ goto _return ;
2539+ }
2540+
2541+ if (pStmt -> sql .pQuery -> haveResultSet ) {
2542+ code = setResSchemaInfo (& pStmt -> exec .pRequest -> body .resInfo , pStmt -> sql .pQuery -> pResSchema ,
2543+ pStmt -> sql .pQuery -> numOfResCols , pStmt -> sql .pQuery -> pResExtSchema , true);
2544+ if (code != TSDB_CODE_SUCCESS ) goto _return ;
2545+ // setResSchemaInfo copies schema data into resInfo->fields; pQuery still owns these pointers.
2546+ taosMemoryFreeClear (pStmt -> sql .pQuery -> pResSchema );
2547+ taosMemoryFreeClear (pStmt -> sql .pQuery -> pResExtSchema );
2548+ setResPrecision (& pStmt -> exec .pRequest -> body .resInfo , pStmt -> sql .pQuery -> precision );
2549+ }
2550+ TSWAP (pStmt -> exec .pRequest -> dbList , pStmt -> sql .pQuery -> pDbList );
2551+ TSWAP (pStmt -> exec .pRequest -> tableList , pStmt -> sql .pQuery -> pTableList );
2552+ TSWAP (pStmt -> exec .pRequest -> targetTableList , pStmt -> sql .pQuery -> pTargetTableList );
2553+ }
2554+
24792555 pStmt -> asyncResultAvailable = false;
24802556 SRequestObj * pRequest = pStmt -> exec .pRequest ;
24812557 __taos_async_fn_t fp = pStmt -> options .asyncExecFn ;
0 commit comments