ODBC 101: Путеводитель по ODBC с утятами
Что такое ODBC?
ODBC, что расшифровывается как Open Database Connectivity, — это стандарт, позволяющий различным программам взаимодействовать с разными базами данных, включая, конечно же, DuckDB 🦆. Это упрощает создание программ, работающих с многими базами данных, экономит время разработчиков, так как им не нужно писать специальный код для подключения к каждой базе. Вместо этого они могут использовать стандартный интерфейс ODBC, что сокращает время и затраты на разработку, а программы проще поддерживать. Однако ODBC может быть медленнее, чем другие методы подключения к базе данных, такие как использование родного драйвера, поскольку он добавляет дополнительный уровень абстракции между приложением и базой данных. Кроме того, поскольку DuckDB — это база данных с колончатой моделью, а ODBC — с построчной, использование ODBC с DuckDB может сопровождаться некоторыми неэффективностями.
На этой странице есть ссылки на официальную документацию Microsoft ODBC, которая является отличным ресурсом для получения дополнительной информации об ODBC.
Основные понятия
Дескрипторы
Дескриптор — это указатель на конкретный объект ODBC, используемый для взаимодействия с базой данных. Существуют несколько различных типов дескрипторов, каждый со своим назначением: дескриптор среды, дескриптор подключения, дескриптор оператора и дескриптор описателя. Дескрипторы выделяются с помощью функции SQLAllocHandle, которая в качестве входных данных принимает тип выделяемого дескриптора и указатель на дескриптор; драйвер затем создаёт новый дескриптор указанного типа и возвращает его приложению.
Драйвер ODBC для DuckDB имеет следующие типы дескрипторов.
Среда
| Имя дескриптора | Среда |
| Имя типа | SQL_HANDLE_ENV |
| Описание | Управляет настройками среды для операций ODBC и предоставляет глобальный контекст для доступа к данным. |
| Случаи использования | Инициализация ODBC, управление поведением драйвера, выделение ресурсов |
| Дополнительная информация | Должен быть выделен один раз на приложение при запуске и освобождён в конце. |
Подключение
| Имя дескриптора | Подключение |
| Имя типа | SQL_HANDLE_DBC |
| Описание | Представляет подключение к источнику данных. Используется для установления, управления и завершения подключений. Определяет как драйвер, так и источник данных для использования в рамках драйвера. |
| Случаи использования | Установление подключения к базе данных, управление состоянием подключения |
| Дополнительная информация | Можно создавать несколько дескрипторов подключений по мере необходимости, позволяя одновременные подключения к нескольким источникам данных. Примечание: Выделение дескриптора подключения не устанавливает подключение, но оно должно быть выделено в первую очередь, а затем использовано после установления подключения. |
Оператор
| Имя дескриптора | Оператор |
| Имя типа | SQL_HANDLE_STMT |
| Описание | Управляет выполнением SQL-запросов, а также возвращаемыми наборами результатов. |
| Случаи использования | Выполнение SQL-запросов, получение наборов результатов, управление параметрами оператора. |
| Дополнительная информация | Для обеспечения выполнения нескольких запросов одновременно, можно выделять несколько дескрипторов операторов на подключение. |
Описание
| Имя дескриптора | Описание |
| Имя типа | SQL_HANDLE_DESC |
| Описание | Описывает атрибуты структуры данных или параметра и позволяет приложению указать структуру данных для привязки/получения. |
| Случаи использования | Описание структур таблиц, наборов результатов, привязка столбцов к буферам приложения |
| Дополнительная информация | Используется в ситуациях, когда структуры данных необходимо явно определить, например, во время привязки параметров или получения наборов результатов. Они автоматически выделяются при выделении оператора, но также могут быть выделены явно. |
Подключение
Первым шагом является подключение к источнику данных, чтобы приложение могло выполнять операции с базой данных. Сначала приложение должно выделить дескриптор среды, а затем дескриптор подключения. Затем дескриптор подключения используется для подключения к источнику данных. Для подключения к источнику данных можно использовать две функции: SQLDriverConnect и SQLConnect. Первая используется для подключения к источнику данных с помощью строки подключения, а вторая — для подключения с использованием DSN.
Строка подключения
Строка подключения — это строка, содержащая информацию, необходимую для подключения к источнику данных. Она имеет формат списка пар ключ-значение, разделённых точкой с запятой; однако в настоящее время DuckDB использует только DSN и игнорирует остальные параметры.
DSN
DSN (Имя источника данных) — это строка, идентифицирующая базу данных. Это может быть путь к файлу, URL-адрес или имя базы данных. Например: C:\Users\me\duckdb.db и DuckDB — это допустимые DSN. Дополнительную информацию о DSN можно найти на странице “Выбор источника данных или драйвера” в документации SQL Server.
Обработка ошибок и диагностика
Все функции в ODBC возвращают код, представляющий успешность или неудачу функции. Это позволяет легко обрабатывать ошибки, так как приложение может просто проверить код возврата каждого вызова функции, чтобы определить, был ли он успешным. В случае неудачи приложение может использовать функцию SQLGetDiagRec для получения информации об ошибке. В следующей таблице определены коды возврата:
| Код возврата | Описание |
|---|---|
SQL_SUCCESS | Функция выполнена успешно. |
SQL_SUCCESS_WITH_INFO | Функция выполнена успешно, но доступна дополнительная информация, включая предупреждение |
SQL_ERROR | Функция завершилась неудачно. |
SQL_INVALID_HANDLE | Предоставленный дескриптор недействителен, указывая на ошибку программирования, например, когда дескриптор не выделен перед использованием или имеет неправильный тип |
SQL_NO_DATA | Функция выполнена успешно, но больше данных недоступно |
SQL_NEED_DATA | Требуются дополнительные данные, например, при отправке данных параметра во время выполнения или требуется дополнительная информация о подключении. |
SQL_STILL_EXECUTING | Функция, выполнявшаяся асинхронно, всё ещё выполняется. |
Буферы и привязка
Буфер — это блок памяти, используемый для хранения данных. Буферы используются для хранения данных, полученных из базы данных, или для отправки данных в базу данных. Буферы выделяются приложением, а затем привязываются к столбцу в наборе результатов или параметру в запросе с помощью функций SQLBindCol и SQLBindParameter. При получении приложением строки из набора результатов или выполнении запроса данные хранятся в буфере. При отправке приложением запроса в базу данных данные в буфере отправляются в базу данных.
Настройка приложения
Ниже приведён пошаговый гайд по настройке приложения, использующего ODBC для подключения к базе данных, выполнения запроса и получения результатов в C++.
Чтобы установить драйвер, а также всё остальное, вам потребуется выполнить эти инструкции.
1. Включение заголовочных файлов SQL
Первый шаг — включение заголовочных файлов SQL:
#include <sql.h> #include <sqlext.h>
Эти файлы содержат определения функций ODBC, а также типы данных, используемые ODBC. Для использования этих заголовочных файлов необходимо установить пакет unixodbc:
На macOS:
brew install unixodbc
На Ubuntu и Debian:
sudo apt-get install -y unixodbc-dev
На Fedora, CentOS и Red Hat:
sudo yum install -y unixODBC-devel
Не забудьте добавить путь к заголовочным файлам в CFLAGS.
Для MAKEFILE:
CFLAGS=-I/usr/local/include # or CFLAGS=-/opt/homebrew/Cellar/unixodbc/2.3.11/include
Для CMAKE:
include_directories(/usr/local/include) # or include_directories(/opt/homebrew/Cellar/unixodbc/2.3.11/include)
Также необходимо подключить библиотеку в CMAKE или MAKEFILE. Для CMAKE:
target_link_libraries(ODBC_application /path/to/duckdb_odbc/libduckdb_odbc.dylib)
Для MAKEFILE:
LDLIBS=-L/path/to/duckdb_odbc/libduckdb_odbc.dylib
2. Определение дескрипторов ODBC и подключение к базе данных
2.a. Подключение с помощью SQLConnect
Затем настройте дескрипторы ODBC, выделите их и подключитесь к базе данных. Сначала выделяется дескриптор среды, затем среда устанавливается на версию ODBC 3, затем выделяется дескриптор соединения, и, наконец, осуществляется подключение к базе данных. Следующий фрагмент кода демонстрирует, как это сделать:
SQLHANDLE env; SQLHANDLE dbc; SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env); SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc); std::string dsn = "DSN=duckdbmemory"; SQLConnect(dbc, (SQLCHAR*)dsn.c_str(), SQL_NTS, NULL, 0, NULL, 0); std::cout << "Connected!" << std::endl;
2.b. Подключение с помощью SQLDriverConnect
В качестве альтернативы вы можете подключиться к драйверу ODBC с помощью SQLDriverConnect. SQLDriverConnect принимает строку подключения, в которой вы можете настроить базу данных, используя любые доступные опции конфигурации DuckDB.
SQLHANDLE env; SQLHANDLE dbc; SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env); SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc); SQLCHAR str[1024]; SQLSMALLINT strl; std::string dsn = "DSN=DuckDB;allow_unsigned_extensions=true;access_mode=READ_ONLY" SQLDriverConnect(dbc, nullptr, (SQLCHAR*)dsn.c_str(), SQL_NTS, str, sizeof(str), &strl, SQL_DRIVER_COMPLETE) std::cout << "Connected!" << std::endl;
3. Добавление запроса
Теперь, когда приложение настроено, мы можем добавить в него запрос. Сначала нам нужно выделить дескриптор запроса:
SQLHANDLE stmt; SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
Затем мы можем выполнить запрос:
SQLExecDirect(stmt, (SQLCHAR*)"SELECT * FROM integers", SQL_NTS);
4. Получение результатов
Теперь, когда мы выполнили запрос, мы можем получить результаты. Сначала нам нужно связать столбцы в наборе результатов с буферами:
SQLLEN int_val; SQLLEN null_val; SQLBindCol(stmt, 1, SQL_C_SLONG, &int_val, 0, &null_val);
Затем мы можем получить результаты:
SQLFetch(stmt);
5. Действия с результатами
Теперь, когда у нас есть результаты, мы можем сделать с ними всё, что захотим. Например, мы можем вывести их:
std::cout << "Value: " << int_val << std::endl;
или выполнить любую другую обработку. Кроме того, можно выполнить дополнительные запросы и выполнить любые другие действия с базой данных, такие как вставка, обновление или удаление данных.
6. Освобождение дескрипторов и отключение
Наконец, нам нужно освободить дескрипторы и отключиться от базы данных. Сначала нам нужно освободить дескриптор запроса:
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
Затем нам нужно отключиться от базы данных:
SQLDisconnect(dbc);
И, наконец, нам нужно освободить дескриптор соединения и дескриптор среды:
SQLFreeHandle(SQL_HANDLE_DBC, dbc); SQLFreeHandle(SQL_HANDLE_ENV, env);
Освобождение дескрипторов соединения и среды может быть выполнено только после закрытия подключения к базе данных. Попытка их освободить до отключения от базы данных приведет к ошибке.
Пример приложения
Ниже приведен пример приложения, который включает файл cpp , подключающийся к базе данных, выполняющий запрос, получающий результаты и выводящий их. Он также отключается от базы данных, освобождает дескрипторы и включает функцию проверки возвращаемых значений функций ODBC. Он также включает файл CMakeLists.txt, который можно использовать для сборки приложения.
Пример файла .cpp
#include <iostream>
#include <sql.h>
#include <sqlext.h>
void check_ret(SQLRETURN ret, std::string msg) {
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
std::cout << ret << ": " << msg << " failed" << std::endl;
exit(1);
}
if (ret == SQL_SUCCESS_WITH_INFO) {
std::cout << ret << ": " << msg << " succeeded with info" << std::endl;
}
}
int main() {
SQLHANDLE env;
SQLHANDLE dbc;
SQLRETURN ret;
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
check_ret(ret, "SQLAllocHandle(env)");
ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
check_ret(ret, "SQLSetEnvAttr");
ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
check_ret(ret, "SQLAllocHandle(dbc)");
std::string dsn = "DSN=duckdbmemory";
ret = SQLConnect(dbc, (SQLCHAR*)dsn.c_str(), SQL_NTS, NULL, 0, NULL, 0);
check_ret(ret, "SQLConnect");
std::cout << "Connected!" << std::endl;
SQLHANDLE stmt;
ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
check_ret(ret, "SQLAllocHandle(stmt)");
ret = SQLExecDirect(stmt, (SQLCHAR*)"SELECT * FROM integers", SQL_NTS);
check_ret(ret, "SQLExecDirect(SELECT * FROM integers)");
SQLLEN int_val;
SQLLEN null_val;
ret = SQLBindCol(stmt, 1, SQL_C_SLONG, &int_val, 0, &null_val);
check_ret(ret, "SQLBindCol");
ret = SQLFetch(stmt);
check_ret(ret, "SQLFetch");
std::cout << "Value: " << int_val << std::endl;
ret = SQLFreeHandle(SQL_HANDLE_STMT, stmt);
check_ret(ret, "SQLFreeHandle(stmt)");
ret = SQLDisconnect(dbc);
check_ret(ret, "SQLDisconnect");
ret = SQLFreeHandle(SQL_HANDLE_DBC, dbc);
check_ret(ret, "SQLFreeHandle(dbc)");
ret = SQLFreeHandle(SQL_HANDLE_ENV, env);
check_ret(ret, "SQLFreeHandle(env)");
} Пример файла CMakelists.txt
cmake_minimum_required(VERSION 3.25) project(ODBC_Tester_App) set(CMAKE_CXX_STANDARD 17) include_directories(/opt/homebrew/Cellar/unixodbc/2.3.11/include) add_executable(ODBC_Tester_App main.cpp) target_link_libraries(ODBC_Tester_App /duckdb_odbc/libduckdb_odbc.dylib)
© Copyright 2018–2024 Stichting DuckDB Foundation
Licensed under the MIT License.
https://duckdb.org/docs/guides/odbc/general.html