Ошибка привязки выходного параметра для хранимой процедуры SQL Server в ODBC - PullRequest
0 голосов
/ 15 июня 2019

Я тестирую поведение того, как драйвер ODBC для SQL Server возвращает выходные параметры, и я создал следующую тестовую программу:

#include <windows.h>

#define SQL_NOUNICODEMAP

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

#include <algorithm>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <queue>
#include <utility>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <thread>
#include <tuple>
#include <vector>

#include <cassert>

using namespace std;

/// @brief Global variable indicating if the application is connected to the driver/data source.
bool g_isConnected = false;

/// @brief Check if an ODBC call failed and display the SQLState, native error code,
///        and error message on stderr.
///
/// @param rc    The return code.
/// @param stmt  The statement handle.
/// @param conn  The connection handle.
/// @param env   The environment handle.
///
/// @return true If the API call was successful.
SQLRETURN CheckDiagnostics(SQLRETURN rc, SQLHANDLE stmt, SQLHANDLE conn = SQL_NULL_HANDLE, SQLHANDLE env = SQL_NULL_HANDLE)
{
    if (SQL_ERROR == rc || SQL_SUCCESS_WITH_INFO == rc)
    {
        char stateBuffer[6];
        char msgBuffer[1024];
        SQLINTEGER nativeError;

        SQLError(
            env,
            conn,
            stmt,
            reinterpret_cast<SQLCHAR*>(stateBuffer),
            &nativeError,
            reinterpret_cast<SQLCHAR*>(msgBuffer),
            sizeof(msgBuffer),
            NULL);

        cerr << (SQL_ERROR == rc ? "ERROR" : "WARNING") << ": [" << stateBuffer << "] (" << nativeError << "): " << msgBuffer << endl;
    }

    return rc;
}

SQLRETURN CheckDiagnosticsFatal(SQLRETURN rc, SQLSMALLINT handleType, SQLHANDLE handle)
{

    if (SQL_ERROR == rc || SQL_SUCCESS_WITH_INFO == rc)
    {
        char stateBuffer[6];
        char msgBuffer[1024];
        SQLINTEGER nativeError;

        for (SQLSMALLINT rec = 1; SQL_NO_DATA != SQLGetDiagRec(handleType, handle, rec, reinterpret_cast<SQLCHAR*>(stateBuffer), &nativeError, reinterpret_cast<SQLCHAR*>(msgBuffer), sizeof(msgBuffer), NULL); ++rec)
        {
            cerr << (SQL_ERROR == rc ? "ERROR" : "WARNING") << ": [" << stateBuffer << "] (" << nativeError << "): " << msgBuffer << endl;
        }

        if (SQL_ERROR == rc)
        {
            exit(-1);
        }
    }

    return rc;
}

/// @brief Clean up the handles passed in.
void CleanupHandles(SQLHANDLE stmt, SQLHANDLE dbc, SQLHANDLE env)
{
    if (SQL_NULL_HANDLE != stmt)
    {
        SQLFreeStmt(stmt, SQL_CLOSE);
        SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    }

    if (g_isConnected)
    {
        g_isConnected = false;
        SQLDisconnect(dbc);
    }

    if (SQL_NULL_HANDLE != dbc)
    {
        SQLFreeHandle(SQL_HANDLE_DBC, dbc);
    }

    if (SQL_NULL_HANDLE != env)
    {
        SQLFreeHandle(SQL_HANDLE_ENV, env);
    }
}

/// @brief Check if an ODBC call failed and display the SQLState, native error code,
///        and error message on stderr. Abort execution if the return code was SQL_ERROR.
///
/// @param rc    The return code.
/// @param stmt  The statement handle.
/// @param conn  The connection handle.
/// @param env   The environment handle.
SQLRETURN CheckDiagnosticsFatal(SQLRETURN rc, SQLHANDLE stmt, SQLHANDLE conn = SQL_NULL_HANDLE, SQLHANDLE env = SQL_NULL_HANDLE)
{
    CheckDiagnostics(rc, stmt, conn, env);
    if (SQL_ERROR == rc)
    {
        CleanupHandles(stmt, conn, env);
        exit(-1);
    }

    return rc;
}

int main(int argc, char* argv[])
{
    // Connect to the DSN and allocate a statement handle
    SQLHANDLE env = SQL_NULL_HANDLE;
    SQLHANDLE dbc = SQL_NULL_HANDLE;
    SQLHANDLE stmt = SQL_NULL_HANDLE;

    CheckDiagnosticsFatal(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env), stmt, dbc, env);
    CheckDiagnosticsFatal(SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0), stmt, dbc, env);
    CheckDiagnosticsFatal(SQLSetEnvAttr(env, SQL_ATTR_CONNECTION_POOLING, (SQLPOINTER)SQL_CP_ONE_PER_HENV, 0), stmt, dbc, env);

    SQLCHAR outConnBuffer[1024];
    SQLSMALLINT outLen;

    CheckDiagnosticsFatal(SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc), stmt, dbc, env);
    CheckDiagnosticsFatal(SQLDriverConnect(
        dbc,
        NULL,
        (SQLCHAR*)"DSN=SqlServer",
        SQL_NTS,
        outConnBuffer,
        sizeof(outConnBuffer),
        &outLen,
        SQL_DRIVER_NOPROMPT),
        stmt,
        dbc,
        env);

    CheckDiagnosticsFatal(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), stmt, dbc, env);

    CheckDiagnosticsFatal(SQLSetStmtAttr(stmt, SQL_ATTR_PARAMSET_SIZE, (void*)(SQLULEN)2, 0), stmt);

    SQLCHAR inputParamBuffer[2][10] = { "1", "2" };
    SQLLEN inputParamLenInd[2] = { SQL_NTS, SQL_NTS };
    SQLINTEGER outputParamBuffer[2] = { 0, 0 };
    SQLLEN outputParamLenInd[2];
    CheckDiagnosticsFatal(SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 10, 0, &inputParamBuffer, sizeof(inputParamBuffer[0]), inputParamLenInd), stmt);
    CheckDiagnosticsFatal(SQLBindParameter(stmt, 2, SQL_PARAM_INPUT_OUTPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &outputParamBuffer, sizeof(outputParamBuffer[0]), outputParamLenInd), stmt);

//     const SQLSMALLINT unnamedValue(SQL_UNNAMED);
// 
//     SQLHDESC ipd;
//     CheckDiagnosticsFatal(SQLGetStmtAttr(stmt, SQL_ATTR_IMP_PARAM_DESC, &ipd, 0, 0), stmt);
//     CheckDiagnosticsFatal(SQLSetDescField(ipd, 1, SQL_DESC_NAME, "@inparam", SQL_NTS), SQL_HANDLE_DESC, ipd);
//     CheckDiagnosticsFatal(SQLSetDescField(ipd, 1, SQL_DESC_UNNAMED, (SQLPOINTER)unnamedValue, SQL_IS_SMALLINT), SQL_HANDLE_DESC, ipd);
//     CheckDiagnosticsFatal(SQLSetDescField(ipd, 2, SQL_DESC_NAME, "@outparam", SQL_NTS), SQL_HANDLE_DESC, ipd);
//     CheckDiagnosticsFatal(SQLSetDescField(ipd, 2, SQL_DESC_UNNAMED, (SQLPOINTER)unnamedValue, SQL_IS_SMALLINT), SQL_HANDLE_DESC, ipd);

    SQLSMALLINT paramStatus[2];
    CheckDiagnosticsFatal(SQLSetStmtAttr(stmt, SQL_ATTR_PARAM_STATUS_PTR, paramStatus, 0), stmt);

    // Prepare the query, and then execute/fetch in a loop.
    SQLCHAR* const query((SQLCHAR*)"{CALL TestV1.dbo.TestLateOutput4(?)}");
    cout << "Preparing query: " << query << endl;
    if (SQL_SUCCEEDED(CheckDiagnostics(SQLPrepare(stmt, query, SQL_NTS), stmt)))
    {
        cout << "Executing query..." << endl;
        CheckDiagnosticsFatal(SQLExecute(stmt), stmt);

        //while (SQL_NO_DATA != CheckDiagnosticsFatal(SQLFetch(stmt), stmt));
        CheckDiagnosticsFatal(SQLMoreResults(stmt), stmt);

        //while (SQL_NO_DATA != CheckDiagnosticsFatal(SQLFetch(stmt), stmt));
        CheckDiagnosticsFatal(SQLMoreResults(stmt), stmt);

        CleanupHandles(stmt, dbc, env);
        return EXIT_SUCCESS;
    }
    else
    {
        cout << "Failed to prepare query." << endl;

        CheckDiagnosticsFatal(SQL_ERROR, stmt);
        abort();
    }
}

Хранимая процедура, которую он вызывает, TestV1.dbo.TestLateOutput4был создан путем выполнения следующего:

create procedure TestLateOutput4 @inparam varchar(10), @outparam int output
as
select 1
SET @outparam = @inparam
return

Проблема, которую я получаю, состоит в том, что SQLExecute возвращает SQL_SUCCESS_WITH_INFO (устанавливая все состояния набора параметров на SQL_PARAM_ERROR) с сообщением[42000] (201): [Microsoft][SQL Server Native Client 11.0][SQL Server]Procedure or function 'TestLateOutput4' expects parameter '@outparam', which was not supplied.

Насколько я могу судить, я поставил оба необходимых параметра (связывая параметры 1 и 2).Я также попытался установить имена параметров (см. Параметр закомментированных строк SQL_DESC_NAME), но это ничего не изменило.Я также попытался явно установить SQL_DESC_UNNAMED в SQL_UNNAMED (хотя это должно быть по умолчанию), но ничего.

Другая вещь, которую я попытался, - это привязать второй параметр как ввод / вывод (потому что я знаю,что SQL Server обрабатывает параметры «вывода» таким образом), изначально я просто связывал его как вывод.

Что я делаю не так?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...