Microsoft ODBC не может создать действительный дескриптор

Я использую драйвер 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.

Я бы попробовал без этих функций атрибутов - они выглядят неправильно и, вероятно, в них нет необходимости.

user2100815 25.09.2018 23:16

@NeilButterworth тот же результат с их удалением.

John S 25.09.2018 23:22
if ((!retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)) - неправильный брекетинг.
user2100815 25.09.2018 23:23

Хороший улов. Но я исправил, и это не повлияло на результат.

John S 25.09.2018 23:24

Пожалуйста, отредактируйте свой вопрос, указав исправленный код

user2100815 25.09.2018 23:25

Хорошо, теперь вам нужно получать диагностические сообщения об ошибках с помощью SQLGetDiagRec и связанных с ним функций. А может попробовать SQL_DRIVER_NOPROMPT.

user2100815 25.09.2018 23:35
(SQLCHAR*)dsn.c_str () Снимите гипс. Какую ошибку выдает компилятор? Если вы получили сообщение об ошибке, внимательно прочтите его. Приведение строкового типа является признаком того, что что-то не так, и вы заставляете компилятор замолчать и закрываете глаза на ошибку.
PaulMcKenzie 26.09.2018 00:25

@Paul На самом деле, использование ODBC требует большого количества приведений - альтернативы действительно нет. И этот состав не ошибочен.

user2100815 26.09.2018 00:38

Я добавил вызов SQLGetDiagRec сразу после выделения дескриптора среды, и это также не удалось с SQL_INVALID_HANDLE. Итак, проблема явно существует, она возникает очень рано. SQL_DRIVER_NOPROMPT не имел никакого значения.

John S 26.09.2018 15:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
9
900
4

Ответы 4

В Unix дескриптор не должен быть ненулевым значением для отображения диалогов.

Простите, я не понимаю, что вы говорите. Диалоги не являются частью этого приложения / среды.

John S 26.09.2018 13:29

@John Извините, это не сработало. Я имел в виду, что dhandle должен быть 1 или что-то в этом роде вместо 0 при использовании SQL_DRIVER_PROMPT. Я вижу, что вы пробовали SQL_DRIVER_NOPROMPT, так что этого не может быть. Другая мысль, которая у меня возникла, касалась inconnectionstring, вместо того, чтобы указывать длину dsn.length (), используйте SQL_NTS, которая обозначает строку с завершающим нулем. В противном случае я бы попытался добавить 1 к длине строки в качестве параметра. Я заметил, что с python вы используете pyobdc, будет ли это работать с C? Вам нужен другой мост ODBC-ODBC для работы в unix?

Baxter 27.09.2018 02:06

После двух недель копания выяснилось, что это какая-то проблема с версией.

В конце концов, эта программа будет выполнять некоторые загрузки BCP через расширения Microsoft в libmsodbcsql.so. Оказывается, в этой библиотеке также есть реализации многих функций SQL*, которые не работают в этой тестовой программе. Когда я изменяю порядок ссылок так, чтобы libodbc.so находился перед библиотекой расширений MSFT, чтобы загрузчик сначала находил эти реализации, программа работает нормально.

Мне любопытно, почему это так, и это, вероятно, указывает на что-то еще, что я делаю неправильно, что может укусить меня в будущем. Но пока, по крайней мере, я могу подключиться к базе данных и выполнять базовые запросы и обновления.

Спасибо тем, кто помогал.

У меня была точно такая же проблема! Я потратил на его отладку всего 1 день, и не более того, благодаря этому посту; это было печенье; увидеть мой ответ СПАСИБО, StackOverflow

Pedro Vicente 13.07.2020 06:51

Для любого дескриптора в 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)

Другие вопросы по теме