Я тестирую поведение того, как драйвер 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 обрабатывает параметры «вывода» таким образом), изначально я просто связывал его как вывод.
Что я делаю не так?