You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

通过ODBC在C中调用Postgres存储过程获取游标数据报错排查

解决Postgres存储过程游标在ODBC调用中不存在的问题

核心原因

PostgreSQL中,普通游标(未声明WITH HOLD)的生命周期绑定到当前事务。ODBC默认处于自动提交模式,执行CALL TEST_SP()后事务会立即提交,存储过程中创建的游标rs_resultone会被自动销毁,后续执行FETCH自然会提示游标不存在。而pgAdmin默认在未提交的事务会话中执行操作,所以游标能正常访问。

纯C代码解决方案

通过关闭ODBC连接的自动提交,让CALL和FETCH处于同一个事务上下文即可解决问题,无需修改存储过程。

完整代码示例

#include <sql.h>
#include <sqlext.h>
#include <stdio.h>

void printODBCError(SQLHANDLE handle, SQLSMALLINT handleType) {
    SQLCHAR sqlState[6], msg[SQL_MAX_MESSAGE_LENGTH];
    SQLINTEGER nativeError;
    SQLSMALLINT msgLen;
    while (SQLGetDiagRec(handleType, handle, 1, sqlState, &nativeError, msg, sizeof(msg), &msgLen) == SQL_SUCCESS) {
        printf("ODBC Error: %s (Native: %d) - %s\n", sqlState, nativeError, msg);
    }
}

int main() {
    SQLHENV henv;
    SQLHDBC hdbc;
    SQLHSTMT hstmt;
    SQLRETURN ret;

    // 初始化ODBC环境
    ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
    ret = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);

    // 分配连接句柄
    ret = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);

    // 连接数据库(替换为你的连接字符串)
    const char* connStr = "DSN=PostgresDSN;UID=your_user;PWD=your_pass;";
    ret = SQLDriverConnect(hdbc, NULL, (SQLCHAR*)connStr, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);
    if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
        printODBCError(hdbc, SQL_HANDLE_DBC);
        return 1;
    }

    // 关闭自动提交,开启事务模式
    ret = SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0);
    if (ret != SQL_SUCCESS) {
        printODBCError(hdbc, SQL_HANDLE_DBC);
        return 1;
    }

    // 分配语句句柄
    ret = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
    if (ret != SQL_SUCCESS) {
        printODBCError(hdbc, SQL_HANDLE_DBC);
        return 1;
    }

    // 调用存储过程
    const char* callSp = "CALL TEST_SP();";
    ret = SQLExecDirect(hstmt, (SQLCHAR*)callSp, SQL_NTS);
    if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
        printODBCError(hstmt, SQL_HANDLE_STMT);
        goto cleanup;
    }

    // 清除存储过程执行后的空结果集(如果存储过程无返回结果集,可省略)
    while (SQLMoreResults(hstmt) == SQL_SUCCESS);

    // 执行FETCH获取游标数据
    const char* fetchSql = "FETCH ALL FROM rs_resultone;";
    ret = SQLExecDirect(hstmt, (SQLCHAR*)fetchSql, SQL_NTS);
    if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
        printODBCError(hstmt, SQL_HANDLE_STMT);
        goto cleanup;
    }

    // 处理游标结果集
    SQLCHAR colValue[256];
    SQLLEN colLen;
    printf("Cursor data:\n");
    while ((ret = SQLFetch(hstmt)) == SQL_SUCCESS) {
        // 替换为你的列索引和数据类型
        ret = SQLGetData(hstmt, 1, SQL_C_CHAR, colValue, sizeof(colValue), &colLen);
        if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
            printf("- %s\n", colValue);
        }
    }

    // 提交事务,释放游标
    ret = SQLTransact(NULL, hdbc, SQL_COMMIT);
    if (ret != SQL_SUCCESS) {
        printODBCError(hdbc, SQL_HANDLE_DBC);
    }

cleanup:
    // 释放资源
    SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
    SQLDisconnect(hdbc);
    SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
    SQLFreeHandle(SQL_HANDLE_ENV, henv);
    return 0;
}

关键注意点

  • 关闭自动提交:这是解决问题的核心,确保CALL和FETCH在同一个事务中执行,游标不会被提前销毁。
  • 清除空结果集:存储过程执行后可能返回空结果集,需用SQLMoreResults清除,否则后续SQLExecDirect会报错。
  • 事务提交:处理完游标数据后必须提交事务,避免连接长期处于事务状态占用资源。

内容的提问来源于stack exchange,提问作者Radek Matras

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 22:55:15