Я использую драйвер Microsoft ODBC для подключения приложения C++ / Linux к базе данных SQL Server, работающей удаленно, и когда я пытаюсь подключиться к базе данных, вызов не выполняется с помощью SQL_INVALID_HANDLE. Читая их документация, я нахожу следующее:
SQL_INVALID_HANDLE Function failed due to an invalid environment, connection, statement, or descriptor handle. This indicates a programming error. No additional information is available from SQLGetDiagRec or SQLGetDiagField. This code is returned only when the handle is a null pointer or is the wrong type, such as when a statement handle is passed for an argument that requires a connection handle.
Достаточно честно, но ни в какой момент при создании дескрипторов и окружения до оператора подключения я не получаю никаких ошибок. Кроме того, для второго аргумента их документация говорит, что я могу передать нулевой указатель, если нет окна рабочего стола (как в случае с этим консольным приложением linux). Вот MVCE, адаптированный из Microsoft пример программы:
#include "sql.h"
#include "sqlext.h"
#include "msodbcsql.h"
#include <iostream>
#include <string>
int main(int, char**)
{
using std::cerr;
using std::endl;
SQLHENV henv;
SQLHDBC hdbc;
HWND dhandle = nullptr; // no desktop handle in linux
SQLHSTMT hstmt;
SQLRETURN retcode;
SQLCHAR OutConnStr[255];
SQLSMALLINT OutConnStrLen;
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
if (!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
cerr << "SQLAllocHandle (environment) failed " << retcode << endl;
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER*)SQL_OV_ODBC3, 0);
if (!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
cerr << "SQLSetEnvAttr failed " << retcode << endl;
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
if (!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
cerr << "SQLAllocHandle (connection) failed " << retcode << endl;
retcode = SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
if (!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
cerr << "SQLSetConnectAttr failed " << retcode << endl;
std::string dsn = "DRIVER = {ODBC Driver 17 for SQL Server};SERVER=*.*.*,1433;DATABASE=***;UID=***;PWD=***";
retcode = SQLDriverConnect(hdbc, dhandle, (SQLCHAR*)dsn.c_str(), dsn.length(), OutConnStr, 255, &OutConnStrLen, SQL_DRIVER_PROMPT);
if (!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
cerr << "SQLDriverConnect failed " << retcode << endl;
// cleanup code redacted for brevity
return 0;
}
Программа выводит SQLDriverConnect failed -2, то есть SQL_INVALID_HANDLE. Я в тупике. hdbc явно правильный тип, и проверка его в отладчике показывает, что он не нулевой.
Возможно, стоит отметить, что одна и та же строка подключения работает в программе на Python, использующей pyodbc. Однако похоже, что программа на C++ даже не доходит до просмотра этой строки. Ему просто не нравится дескриптор, который я отправляю в вызове подключения.
В документации Microsoft четко сказано, что они не предоставляют никакой дополнительной информации. Если кто-нибудь может дать какие-либо указания о том, как диагностировать / отладить это, я был бы очень признателен.
Это приложение использует gcc 4.9.1 на Centos 7.
@NeilButterworth тот же результат с их удалением.
if ((!retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)) - неправильный брекетинг.
Хороший улов. Но я исправил, и это не повлияло на результат.
Пожалуйста, отредактируйте свой вопрос, указав исправленный код
Хорошо, теперь вам нужно получать диагностические сообщения об ошибках с помощью SQLGetDiagRec и связанных с ним функций. А может попробовать SQL_DRIVER_NOPROMPT.
(SQLCHAR*)dsn.c_str () Снимите гипс. Какую ошибку выдает компилятор? Если вы получили сообщение об ошибке, внимательно прочтите его. Приведение строкового типа является признаком того, что что-то не так, и вы заставляете компилятор замолчать и закрываете глаза на ошибку.
@Paul На самом деле, использование ODBC требует большого количества приведений - альтернативы действительно нет. И этот состав не ошибочен.
Я добавил вызов SQLGetDiagRec сразу после выделения дескриптора среды, и это также не удалось с SQL_INVALID_HANDLE. Итак, проблема явно существует, она возникает очень рано. SQL_DRIVER_NOPROMPT не имел никакого значения.





В Unix дескриптор не должен быть ненулевым значением для отображения диалогов.
Простите, я не понимаю, что вы говорите. Диалоги не являются частью этого приложения / среды.
@John Извините, это не сработало. Я имел в виду, что dhandle должен быть 1 или что-то в этом роде вместо 0 при использовании SQL_DRIVER_PROMPT. Я вижу, что вы пробовали SQL_DRIVER_NOPROMPT, так что этого не может быть. Другая мысль, которая у меня возникла, касалась inconnectionstring, вместо того, чтобы указывать длину dsn.length (), используйте SQL_NTS, которая обозначает строку с завершающим нулем. В противном случае я бы попытался добавить 1 к длине строки в качестве параметра. Я заметил, что с python вы используете pyobdc, будет ли это работать с C? Вам нужен другой мост ODBC-ODBC для работы в unix?
После двух недель копания выяснилось, что это какая-то проблема с версией.
В конце концов, эта программа будет выполнять некоторые загрузки BCP через расширения Microsoft в libmsodbcsql.so. Оказывается, в этой библиотеке также есть реализации многих функций SQL*, которые не работают в этой тестовой программе. Когда я изменяю порядок ссылок так, чтобы libodbc.so находился перед библиотекой расширений MSFT, чтобы загрузчик сначала находил эти реализации, программа работает нормально.
Мне любопытно, почему это так, и это, вероятно, указывает на что-то еще, что я делаю неправильно, что может укусить меня в будущем. Но пока, по крайней мере, я могу подключиться к базе данных и выполнять базовые запросы и обновления.
Спасибо тем, кто помогал.
У меня была точно такая же проблема! Я потратил на его отладку всего 1 день, и не более того, благодаря этому посту; это было печенье; увидеть мой ответ СПАСИБО, StackOverflow
Для любого дескриптора в SQL Server он должен быть выделен перед использованием!
Итак, порядок - Окружающая среда, Связь и Заявление.
Пример:
SQLHENV hEnv = nullptr;
SQLHDBC hDbc = nullptr;
SQLHSTMT hStmt = NULL;
Ассигнования
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);
SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
Ниже приведен пример кода, который может вам помочь.
Основы быстро,
Создайте таблицу в своей базе данных sql server и вставьте некоторые данные
create table test (id int, name nvarchar(128));
Вставьте некоторые данные
insert into test (id,name) values (1, 'Awesome Name');
Код C++ для запроса элементов в таблице
#include <iostream>
#include <string>
#include <sql.h>
#include <sqlext.h>
int main(int argc, char **argv) {
SQLHENV hEnv = nullptr;
SQLHDBC hDbc = nullptr;
SQLHSTMT hStmt = NULL;
/**
* Allocate environment handle
*/
SQLRETURN allocReturn = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
//Set environment
SQLRETURN setEnvReturn = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, 0);
//Allocate connection handle
SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);
SQLCHAR *connection_string = (SQLCHAR *)
"DRIVER = {ODBC Driver 17 for SQL Server};SERVER=localhost,1433;DATABASE=database;UID=sa;PWD=password";
//Connect to database
SQLRETURN connReturn = SQLDriverConnect(hDbc, NULL, connection_string, SQL_NTS, NULL, 0, NULL,
SQL_DRIVER_COMPLETE);
//Allocate Statement Handle
SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
//Create statement
SQLCHAR *query = (SQLCHAR *) "SELECT * FROM TEST;";
SQLRETURN sqlPrepareResponse = SQLPrepare(hStmt, query, SQL_NTS); //strlen(reinterpret_cast<const char *>(query))
//Bind columns
SQLCHAR personName[20];
SQLLEN personNameIndex;
SQLRETURN bindNameResponse = SQLBindCol(hStmt, 2, SQL_C_CHAR, personName, sizeof(personName),
&personNameIndex);
SQLINTEGER personId;
SQLLEN personIdIndex;
SQLRETURN personIdBindResponse = SQLBindCol(hStmt, 1, SQL_INTEGER, &personId, 0, &personIdIndex);
SQLRETURN execResponse = SQLExecute(hStmt);
SQLRETURN fetchResponse;
while ((fetchResponse = SQLFetch(hStmt)) != SQL_NO_DATA) {
std::cout << "ID: [" << personId << "] :" << personName << std::endl;
}
/* Free the statement handle. */
SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
/* Disconnect from the database. */
SQLDisconnect(hDbc);
/* Free the connection handle. */
SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
/* Free the environment handle. */
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
return EXIT_SUCCESS;
}
У меня точно такая же ошибка с использованием аналогичного кода (который работал в Ubuntu 18.04, но не с обновлением до 20.04)
cat /etc/odbcinst.ini
[ODBC Driver 17 for SQL Server]
Description=Microsoft ODBC Driver 17 for SQL Server
Driver=/opt/microsoft/msodbcsql17/lib64/libmsodbcsql-17.5.so.2.1
UsageCount=1
используя эту строку подключения
DRIVER=ODBC Driver 17 for SQL Server;SERVER=127.0.0.1, 1433;UID=SA;PWD=password;DATABASE=my_database;
это порядок ссылки на мою библиотеку
if (UNIX)
find_program(LSB_RELEASE_EXEC lsb_release)
execute_process(COMMAND ${LSB_RELEASE_EXEC} -is OUTPUT_VARIABLE LSB_RELEASE_ID_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE)
message(STATUS "Building in " ${LSB_RELEASE_ID_SHORT})
if ("${LSB_RELEASE_ID_SHORT}" STREQUAL "Ubuntu")
message(STATUS "Linking with SQL-Server library")
set(lib_dep ${lib_dep} msodbcsql-17)
endif ()
set(lib_dep ${lib_dep} pthread odbc dl)
endif ()
Как отмечалось в приведенном выше решении, изменение порядка ссылок устранило проблему.
set(lib_dep ${lib_dep} pthread odbc dl msodbcsql-17)
Я бы попробовал без этих функций атрибутов - они выглядят неправильно и, вероятно, в них нет необходимости.