From 5d3c26079647ce21f41c3cdd79fa876bd33c75a3 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Fri, 18 Apr 2025 13:21:32 +0000 Subject: [PATCH 01/11] started work --- CMakeLists.txt | 5 ++ odbc/CMakeLists.txt | 64 ++++++++++++++++ odbc/README.md | 105 ++++++++++++++++++++++++++ odbc/include/client/driver.h | 15 ++++ odbc/include/client/query.h | 17 +++++ odbc/include/ydb_odbc.h | 89 ++++++++++++++++++++++ odbc/odbc.ini | 9 +++ odbc/odbcinst.ini | 7 ++ odbc/src/client/driver.cpp | 25 ++++++ odbc/src/client/query.cpp | 56 ++++++++++++++ odbc/src/connection.c | 132 ++++++++++++++++++++++++++++++++ odbc/src/descriptor.c | 109 +++++++++++++++++++++++++++ odbc/src/driver.c | 142 +++++++++++++++++++++++++++++++++++ odbc/src/statement.c | 98 ++++++++++++++++++++++++ 14 files changed, 873 insertions(+) create mode 100644 odbc/CMakeLists.txt create mode 100644 odbc/README.md create mode 100644 odbc/include/client/driver.h create mode 100644 odbc/include/client/query.h create mode 100644 odbc/include/ydb_odbc.h create mode 100644 odbc/odbc.ini create mode 100644 odbc/odbcinst.ini create mode 100644 odbc/src/client/driver.cpp create mode 100644 odbc/src/client/query.cpp create mode 100644 odbc/src/connection.c create mode 100644 odbc/src/descriptor.c create mode 100644 odbc/src/driver.c create mode 100644 odbc/src/statement.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 41f4783ca2..30e9ba2f7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ project(YDB-CPP-SDK VERSION ${YDB_SDK_VERSION} LANGUAGES C CXX ASM) option(YDB_SDK_INSTALL "Install YDB C++ SDK" Off) option(YDB_SDK_TESTS "Build YDB C++ SDK tests" Off) option(YDB_SDK_EXAMPLES "Build YDB C++ SDK examples" On) +option(YDB_SDK_ODBC "Build YDB ODBC driver" On) set(YDB_SDK_GOOGLE_COMMON_PROTOS_TARGET "" CACHE STRING "Name of cmake target preparing google common proto library") option(YDB_SDK_USE_RAPID_JSON "Search for rapid json library in system" ON) @@ -61,6 +62,10 @@ add_subdirectory(util) #_ydb_sdk_validate_public_headers() +if (YDB_SDK_ODBC) + add_subdirectory(odbc) +endif() + if (YDB_SDK_EXAMPLES) add_subdirectory(examples) endif() diff --git a/odbc/CMakeLists.txt b/odbc/CMakeLists.txt new file mode 100644 index 0000000000..985782900e --- /dev/null +++ b/odbc/CMakeLists.txt @@ -0,0 +1,64 @@ +cmake_minimum_required(VERSION 3.14) +project(ydb-odbc VERSION 0.1.0 LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Находим зависимости +find_package(ODBC REQUIRED) + +# Добавляем исходники +add_library(ydb-odbc SHARED + src/driver.c + src/connection.c + src/statement.c + src/descriptor.c + src/client/driver.cpp + src/client/query.cpp +) + +# Добавляем заголовочные файлы +target_include_directories(ydb-odbc + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${ODBC_INCLUDE_DIRS} + /usr/include + /usr/local/include +) + +# Линкуем с YDB SDK и ODBC +target_link_libraries(ydb-odbc + PUBLIC + YDB-CPP-SDK::Query + YDB-CPP-SDK::Table + YDB-CPP-SDK::Driver + ODBC::ODBC +) + +# Устанавливаем драйвер +install(TARGETS ydb-odbc + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +# Устанавливаем заголовочные файлы +install(DIRECTORY include/ + DESTINATION include/ydb-odbc +) + +# Добавляем тесты +# add_subdirectory(tests) + +# Правила установки +include(GNUInstallDirs) + +install(FILES + odbcinst.ini + DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/odbcinst.d + RENAME ydb-odbc.ini +) + +install(FILES + odbc.ini + DESTINATION ${CMAKE_INSTALL_SYSCONFDIR} +) diff --git a/odbc/README.md b/odbc/README.md new file mode 100644 index 0000000000..4d502aaad7 --- /dev/null +++ b/odbc/README.md @@ -0,0 +1,105 @@ +# YDB ODBC Driver + +ODBC драйвер для YDB. + +## Требования + +- CMake 3.10 или выше +- Компилятор C/C++ с поддержкой C11 и C++20 +- YDB C++ SDK +- unixODBC (для Linux/macOS) + +## Сборка + +```bash +mkdir build && cd build +cmake .. +make +``` + +## Установка + +```bash +sudo make install +``` + +Это установит: +- Библиотеку драйвера в `/usr/local/lib/` +- Конфигурацию драйвера в `/etc/odbcinst.d/` +- Конфигурацию источников данных в `/etc/odbc.ini` + +## Настройка + +1. Убедитесь, что драйвер зарегистрирован: +```bash +odbcinst -q -d +``` + +2. Проверьте доступные источники данных: +```bash +odbcinst -q -s +``` + +3. Отредактируйте `/etc/odbc.ini` для настройки подключения: +```ini +[YDB] +Driver=YDB +Description=YDB Database Connection +Server=grpc://your-server:2136 +Database=your-database +AuthMode=none # или token для аутентификации по токену +``` + +## Использование + +Пример подключения через isql: +```bash +isql -v YDB +``` + +Пример использования в C: +```c +SQLHENV env; +SQLHDBC dbc; +SQLHSTMT stmt; + +// Инициализация окружения +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); +SQLConnect(dbc, (SQLCHAR*)"YDB", SQL_NTS, + (SQLCHAR*)"", SQL_NTS, + (SQLCHAR*)"", SQL_NTS); + +// Выполнение запроса +SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt); +SQLExecDirect(stmt, (SQLCHAR*)"SELECT * FROM mytable", SQL_NTS); + +// Очистка +SQLFreeHandle(SQL_HANDLE_STMT, stmt); +SQLDisconnect(dbc); +SQLFreeHandle(SQL_HANDLE_DBC, dbc); +SQLFreeHandle(SQL_HANDLE_ENV, env); +``` + +## Поддерживаемые функции + +- SQLAllocHandle +- SQLConnect +- SQLDisconnect +- SQLExecDirect +- SQLFetch +- SQLGetData +- SQLPrepare +- SQLExecute +- SQLCloseCursor +- SQLFreeHandle +- SQLGetInfo +- SQLGetDescField +- SQLSetDescField + +## Лицензия + +Apache License 2.0 \ No newline at end of file diff --git a/odbc/include/client/driver.h b/odbc/include/client/driver.h new file mode 100644 index 0000000000..6a95f9958c --- /dev/null +++ b/odbc/include/client/driver.h @@ -0,0 +1,15 @@ +#pragma once + +#include // для size_t + +#ifdef __cplusplus +extern "C" { +#endif + +// Функции для работы с YDB через C++ SDK +void* YDB_CreateDriver(const char* endpoint, const char* user, const char* password); +void YDB_DestroyDriver(void* driver); + +#ifdef __cplusplus +} +#endif diff --git a/odbc/include/client/query.h b/odbc/include/client/query.h new file mode 100644 index 0000000000..ac6802dc6c --- /dev/null +++ b/odbc/include/client/query.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void* YDB_CreateQueryClient(void* driver); +void YDB_DestroyQueryClient(void* query_client); + +int YDB_ExecuteQuery(void* query_client, const char* query, void** result); +void YDB_FreeExecuteQueryResult(void* result); + +#ifdef __cplusplus +} +#endif diff --git a/odbc/include/ydb_odbc.h b/odbc/include/ydb_odbc.h new file mode 100644 index 0000000000..2415f80f55 --- /dev/null +++ b/odbc/include/ydb_odbc.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Структура для хранения информации о драйвере +typedef struct { + char name[256]; + char version[64]; + char description[1024]; +} YDB_DRIVER_INFO; + +// Структура для хранения состояния соединения +typedef struct { + void* ydb_driver; + void* query_client; + int connected; +} YDB_CONNECTION; + +// Структура для хранения состояния оператора +typedef struct { + YDB_CONNECTION* connection; + void* query_client; + void* result; + size_t current_row; +} YDB_STATEMENT; + +// Структура для хранения дескриптора +typedef struct { + void** descriptors; + size_t descriptors_size; +} YDB_DESCRIPTOR; + +// Функции драйвера +SQLRETURN YDB_SQLGetInfo(SQLSMALLINT InfoType, SQLPOINTER InfoValue, + SQLSMALLINT BufferLength, SQLSMALLINT* StringLength); + +SQLRETURN YDB_SQLConnect(SQLHDBC ConnectionHandle, SQLCHAR* ServerName, + SQLSMALLINT NameLength1, SQLCHAR* UserName, + SQLSMALLINT NameLength2, SQLCHAR* Authentication, + SQLSMALLINT NameLength3); + +SQLRETURN YDB_SQLDriverConnect(SQLHDBC ConnectionHandle, SQLHWND WindowHandle, + SQLCHAR* InConnectionString, SQLSMALLINT StringLength1, + SQLCHAR* OutConnectionString, SQLSMALLINT BufferLength, + SQLSMALLINT* StringLength2, SQLUSMALLINT DriverCompletion); + +// Функции соединения +SQLRETURN YDB_SQLDisconnect(SQLHDBC ConnectionHandle); + +SQLRETURN YDB_SQLGetConnectionInfo(SQLHDBC ConnectionHandle, SQLSMALLINT InfoType, + SQLPOINTER InfoValue, SQLSMALLINT BufferLength, + SQLSMALLINT* StringLength); + +SQLRETURN YDB_SQLAllocHandle(SQLSMALLINT HandleType, SQLHANDLE InputHandle, + SQLHANDLE* OutputHandle); + +SQLRETURN YDB_SQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle); + +// Функции оператора +SQLRETURN YDB_SQLExecDirect(SQLHSTMT StatementHandle, SQLCHAR* StatementText, + SQLINTEGER TextLength); + +SQLRETURN YDB_SQLPrepare(SQLHSTMT StatementHandle, SQLCHAR* StatementText, + SQLINTEGER TextLength); + +SQLRETURN YDB_SQLExecute(SQLHSTMT StatementHandle); + +SQLRETURN YDB_SQLFetch(SQLHSTMT StatementHandle); + +SQLRETURN YDB_SQLCloseCursor(SQLHSTMT StatementHandle); + +// Функции дескриптора +SQLRETURN YDB_SQLGetDescField(SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, + SQLSMALLINT FieldIdentifier, SQLPOINTER Value, + SQLINTEGER BufferLength, SQLINTEGER* StringLength); + +SQLRETURN YDB_SQLSetDescField(SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, + SQLSMALLINT FieldIdentifier, SQLPOINTER Value, + SQLINTEGER BufferLength); + +#ifdef __cplusplus +} +#endif diff --git a/odbc/odbc.ini b/odbc/odbc.ini new file mode 100644 index 0000000000..6335b3ee38 --- /dev/null +++ b/odbc/odbc.ini @@ -0,0 +1,9 @@ +[ODBC Data Sources] +YDB=YDB ODBC Driver + +[YDB] +Driver=YDB +Description=YDB Database Connection +Server=grpc://localhost:2136 +Database=local +AuthMode=none \ No newline at end of file diff --git a/odbc/odbcinst.ini b/odbc/odbcinst.ini new file mode 100644 index 0000000000..fade7b6fb9 --- /dev/null +++ b/odbc/odbcinst.ini @@ -0,0 +1,7 @@ +[YDB] +Description=YDB ODBC Driver +Driver=/usr/local/lib/libydb-odbc.so +Setup=/usr/local/lib/libydb-odbc.so +Threading=2 +FileUsage=1 +UsageCount=1 \ No newline at end of file diff --git a/odbc/src/client/driver.cpp b/odbc/src/client/driver.cpp new file mode 100644 index 0000000000..faa87471e4 --- /dev/null +++ b/odbc/src/client/driver.cpp @@ -0,0 +1,25 @@ +#include "client/driver.h" + +#include + +extern "C" { + +void* YDB_CreateDriver(const char* endpoint, const char* user, const char* password) { + try { + auto config = NYdb::TDriverConfig().SetEndpoint(std::string(endpoint, strlen(endpoint))); + + auto* driver = new NYdb::TDriver(config); + return static_cast(driver); + } catch (...) { + return nullptr; + } +} + +void YDB_DestroyDriver(void* driver) { + if (driver) { + auto* ydb_driver = static_cast(driver); + delete ydb_driver; + } +} + +} diff --git a/odbc/src/client/query.cpp b/odbc/src/client/query.cpp new file mode 100644 index 0000000000..5da339d19b --- /dev/null +++ b/odbc/src/client/query.cpp @@ -0,0 +1,56 @@ +#include "client/query.h" + +#include + +#include + +extern "C" { + +void* YDB_CreateQueryClient(void* driver) { + if (!driver) return nullptr; + + try { + auto* ydb_driver = static_cast(driver); + auto* query_client = new NYdb::NQuery::TQueryClient(*ydb_driver); + return static_cast(query_client); + } catch (...) { + return nullptr; + } +} + +void YDB_DestroyQueryClient(void* query_client) { + if (query_client) { + auto* client = static_cast(query_client); + delete client; + } +} + +int YDB_ExecuteQuery(void* query_client, const char* query, void** result) { + if (!query_client || !query || !result) { + return 0; + } + + try { + auto* client = static_cast(query_client); + auto executeResult = client->ExecuteQuery(std::string(query, strlen(query)), NYdb::NQuery::TTxControl::NoTx()).GetValueSync(); + + if (!executeResult.IsSuccess()) { + return 0; + } + + *result = reinterpret_cast(new NYdb::NQuery::TExecuteQueryResult(executeResult)); + + return 1; + } catch (...) { + return 0; + } +} + +void YDB_FreeExecuteQueryResult(void* result) { + if (result) { + auto* executeResult = reinterpret_cast(result); + delete executeResult; + } +} + +} // extern "C" diff --git a/odbc/src/connection.c b/odbc/src/connection.c new file mode 100644 index 0000000000..d95dc8ade7 --- /dev/null +++ b/odbc/src/connection.c @@ -0,0 +1,132 @@ +#include "ydb_odbc.h" +#include "client/driver.h" +#include "client/query.h" + +#include +#include + +SQLRETURN YDB_SQLDisconnect(SQLHDBC ConnectionHandle) { + YDB_CONNECTION* conn = (YDB_CONNECTION*)ConnectionHandle; + if (!conn) { + return SQL_ERROR; + } + + if (conn->connected) { + if (conn->query_client) { + YDB_DestroyQueryClient(conn->query_client); + conn->query_client = NULL; + } + + if (conn->ydb_driver) { + YDB_DestroyDriver(conn->ydb_driver); + conn->ydb_driver = NULL; + } + + conn->connected = 0; + } + + return SQL_SUCCESS; +} + +SQLRETURN YDB_SQLGetConnectionInfo(SQLHDBC ConnectionHandle, SQLSMALLINT InfoType, + SQLPOINTER InfoValue, SQLSMALLINT BufferLength, + SQLSMALLINT* StringLength) { + YDB_CONNECTION* conn = (YDB_CONNECTION*)ConnectionHandle; + if (!conn || !conn->connected) { + return SQL_ERROR; + } + + switch (InfoType) { + case SQL_DATABASE_NAME: + if (InfoValue && BufferLength > 0) { + const char* dbName = "YDB"; + strncpy((char*)InfoValue, dbName, BufferLength - 1); + if (StringLength) { + *StringLength = strlen(dbName); + } + return SQL_SUCCESS; + } + break; + + case SQL_SERVER_NAME: + if (InfoValue && BufferLength > 0) { + const char* serverName = "Yandex Database"; + strncpy((char*)InfoValue, serverName, BufferLength - 1); + if (StringLength) { + *StringLength = strlen(serverName); + } + return SQL_SUCCESS; + } + break; + } + + return SQL_ERROR; +} + +SQLRETURN YDB_SQLAllocHandle(SQLSMALLINT HandleType, SQLHANDLE InputHandle, + SQLHANDLE* OutputHandle) { + if (!OutputHandle) { + return SQL_ERROR; + } + + switch (HandleType) { + case SQL_HANDLE_DBC: + *OutputHandle = calloc(1, sizeof(YDB_CONNECTION)); + return SQL_SUCCESS; + + case SQL_HANDLE_STMT: + *OutputHandle = calloc(1, sizeof(YDB_STATEMENT)); + return SQL_SUCCESS; + + case SQL_HANDLE_DESC: + *OutputHandle = calloc(1, sizeof(YDB_DESCRIPTOR)); + return SQL_SUCCESS; + + default: + return SQL_ERROR; + } +} + +SQLRETURN YDB_SQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle) { + if (!Handle) { + return SQL_ERROR; + } + + switch (HandleType) { + case SQL_HANDLE_DBC: + { + YDB_CONNECTION* conn = (YDB_CONNECTION*)Handle; + if (conn->connected) { + YDB_SQLDisconnect((SQLHDBC)conn); + } + free(conn); + } + return SQL_SUCCESS; + + case SQL_HANDLE_STMT: + { + YDB_STATEMENT* stmt = (YDB_STATEMENT*)Handle; + if (stmt->result) { + YDB_FreeExecuteQueryResult(stmt->result); + } + if (stmt->query_client) { + YDB_DestroyQueryClient(stmt->query_client); + } + free(stmt); + } + return SQL_SUCCESS; + + case SQL_HANDLE_DESC: + { + YDB_DESCRIPTOR* desc = (YDB_DESCRIPTOR*)Handle; + if (desc->descriptors) { + free(desc->descriptors); + } + free(desc); + } + return SQL_SUCCESS; + + default: + return SQL_ERROR; + } +} \ No newline at end of file diff --git a/odbc/src/descriptor.c b/odbc/src/descriptor.c new file mode 100644 index 0000000000..15380bf40a --- /dev/null +++ b/odbc/src/descriptor.c @@ -0,0 +1,109 @@ +#include "ydb_odbc.h" +#include +#include + +// Структура для хранения поля дескриптора +typedef struct { + SQLSMALLINT field_identifier; + char value[1024]; +} YDB_DESCRIPTOR_FIELD; + +// Структура для хранения записи дескриптора +typedef struct { + YDB_DESCRIPTOR_FIELD* fields; + size_t fields_count; +} YDB_DESCRIPTOR_RECORD; + +SQLRETURN YDB_SQLGetDescField(SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, + SQLSMALLINT FieldIdentifier, SQLPOINTER Value, + SQLINTEGER BufferLength, SQLINTEGER* StringLength) { + YDB_DESCRIPTOR* desc = (YDB_DESCRIPTOR*)DescriptorHandle; + if (!desc || !desc->descriptors || RecNumber < 1 || RecNumber > desc->descriptors_size) { + return SQL_ERROR; + } + + YDB_DESCRIPTOR_RECORD* record = (YDB_DESCRIPTOR_RECORD*)desc->descriptors[RecNumber - 1]; + if (!record) { + return SQL_ERROR; + } + + for (size_t i = 0; i < record->fields_count; i++) { + if (record->fields[i].field_identifier == FieldIdentifier) { + if (Value && BufferLength > 0) { + strncpy((char*)Value, record->fields[i].value, BufferLength - 1); + if (StringLength) { + *StringLength = strlen(record->fields[i].value); + } + return SQL_SUCCESS; + } + break; + } + } + + return SQL_ERROR; +} + +SQLRETURN YDB_SQLSetDescField(SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, + SQLSMALLINT FieldIdentifier, SQLPOINTER Value, + SQLINTEGER BufferLength) { + YDB_DESCRIPTOR* desc = (YDB_DESCRIPTOR*)DescriptorHandle; + if (!desc || RecNumber < 1) { + return SQL_ERROR; + } + + // Увеличиваем размер массива дескрипторов, если нужно + if (RecNumber > desc->descriptors_size) { + void** new_descriptors = realloc(desc->descriptors, RecNumber * sizeof(void*)); + if (!new_descriptors) { + return SQL_ERROR; + } + + // Инициализируем новые записи + for (size_t i = desc->descriptors_size; i < RecNumber; i++) { + YDB_DESCRIPTOR_RECORD* record = calloc(1, sizeof(YDB_DESCRIPTOR_RECORD)); + if (!record) { + // Освобождаем память в случае ошибки + for (size_t j = desc->descriptors_size; j < i; j++) { + free(new_descriptors[j]); + } + free(new_descriptors); + return SQL_ERROR; + } + new_descriptors[i] = record; + } + + desc->descriptors = new_descriptors; + desc->descriptors_size = RecNumber; + } + + YDB_DESCRIPTOR_RECORD* record = (YDB_DESCRIPTOR_RECORD*)desc->descriptors[RecNumber - 1]; + if (!record) { + record = calloc(1, sizeof(YDB_DESCRIPTOR_RECORD)); + if (!record) { + return SQL_ERROR; + } + desc->descriptors[RecNumber - 1] = record; + } + + // Проверяем, существует ли уже поле с таким идентификатором + for (size_t i = 0; i < record->fields_count; i++) { + if (record->fields[i].field_identifier == FieldIdentifier) { + // Обновляем значение + strncpy(record->fields[i].value, (char*)Value, sizeof(record->fields[i].value) - 1); + return SQL_SUCCESS; + } + } + + // Добавляем новое поле + YDB_DESCRIPTOR_FIELD* new_fields = realloc(record->fields, (record->fields_count + 1) * sizeof(YDB_DESCRIPTOR_FIELD)); + if (!new_fields) { + return SQL_ERROR; + } + + record->fields = new_fields; + record->fields[record->fields_count].field_identifier = FieldIdentifier; + strncpy(record->fields[record->fields_count].value, (char*)Value, sizeof(record->fields[record->fields_count].value) - 1); + record->fields_count++; + + return SQL_SUCCESS; +} \ No newline at end of file diff --git a/odbc/src/driver.c b/odbc/src/driver.c new file mode 100644 index 0000000000..5fc19f7294 --- /dev/null +++ b/odbc/src/driver.c @@ -0,0 +1,142 @@ +#include "ydb_odbc.h" +#include +#include + +// Глобальные переменные для хранения состояния +static YDB_DRIVER_INFO driver_info = { + .name = "YDB ODBC Driver", + .version = "1.0.0", + .description = "ODBC driver for Yandex Database" +}; + +SQLRETURN YDB_SQLGetInfo(SQLSMALLINT InfoType, SQLPOINTER InfoValue, + SQLSMALLINT BufferLength, SQLSMALLINT* StringLength) { + switch (InfoType) { + case SQL_DRIVER_NAME: + if (InfoValue && BufferLength > 0) { + strncpy(InfoValue, driver_info.name, BufferLength - 1); + if (StringLength) { + *StringLength = strlen(driver_info.name); + } + return SQL_SUCCESS; + } + break; + + case SQL_DRIVER_VER: + if (InfoValue && BufferLength > 0) { + strncpy(InfoValue, driver_info.version, BufferLength - 1); + if (StringLength) { + *StringLength = strlen(driver_info.version); + } + return SQL_SUCCESS; + } + break; + } + + return SQL_ERROR; +} + +SQLRETURN YDB_SQLConnect(SQLHDBC ConnectionHandle, SQLCHAR* ServerName, + SQLSMALLINT NameLength1, SQLCHAR* UserName, + SQLSMALLINT NameLength2, SQLCHAR* Authentication, + SQLSMALLINT NameLength3) { + YDB_CONNECTION* conn = (YDB_CONNECTION*)ConnectionHandle; + if (!conn) { + return SQL_ERROR; + } + + // TODO: Реализовать подключение к YDB через C++ SDK + // Здесь нужно будет использовать C++ код через extern "C" функции + + return SQL_SUCCESS; +} + +SQLRETURN YDB_SQLDriverConnect(SQLHDBC ConnectionHandle, SQLHWND WindowHandle, + SQLCHAR* InConnectionString, SQLSMALLINT StringLength1, + SQLCHAR* OutConnectionString, SQLSMALLINT BufferLength, + SQLSMALLINT* StringLength2, SQLUSMALLINT DriverCompletion) { + // TODO: Реализовать парсинг строки подключения + return SQL_ERROR; +} + +SQLRETURN YDB_SQLDisconnect(SQLHDBC ConnectionHandle) { + YDB_CONNECTION* conn = (YDB_CONNECTION*)ConnectionHandle; + if (!conn) { + return SQL_ERROR; + } + + // TODO: Реализовать отключение от YDB + + return SQL_SUCCESS; +} + +SQLRETURN YDB_SQLGetConnectionInfo(SQLHDBC ConnectionHandle, SQLSMALLINT InfoType, + SQLPOINTER InfoValue, SQLSMALLINT BufferLength, + SQLSMALLINT* StringLength) { + YDB_CONNECTION* conn = (YDB_CONNECTION*)ConnectionHandle; + if (!conn || !conn->connected) { + return SQL_ERROR; + } + + switch (InfoType) { + case SQL_DATABASE_NAME: + if (InfoValue && BufferLength > 0) { + const char* dbName = "YDB"; + strncpy(InfoValue, dbName, BufferLength - 1); + if (StringLength) { + *StringLength = strlen(dbName); + } + return SQL_SUCCESS; + } + break; + } + + return SQL_ERROR; +} + +SQLRETURN YDB_SQLAllocHandle(SQLSMALLINT HandleType, SQLHANDLE InputHandle, + SQLHANDLE* OutputHandle) { + if (!OutputHandle) { + return SQL_ERROR; + } + + switch (HandleType) { + case SQL_HANDLE_DBC: + *OutputHandle = calloc(1, sizeof(YDB_CONNECTION)); + return SQL_SUCCESS; + + case SQL_HANDLE_STMT: + *OutputHandle = calloc(1, sizeof(YDB_STATEMENT)); + return SQL_SUCCESS; + + case SQL_HANDLE_DESC: + *OutputHandle = calloc(1, sizeof(YDB_DESCRIPTOR)); + return SQL_SUCCESS; + + default: + return SQL_ERROR; + } +} + +SQLRETURN YDB_SQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle) { + if (!Handle) { + return SQL_ERROR; + } + + switch (HandleType) { + case SQL_HANDLE_DBC: + free(Handle); + return SQL_SUCCESS; + + case SQL_HANDLE_STMT: + free(Handle); + return SQL_SUCCESS; + + case SQL_HANDLE_DESC: + free(Handle); + return SQL_SUCCESS; + + default: + return SQL_ERROR; + } +} \ No newline at end of file diff --git a/odbc/src/statement.c b/odbc/src/statement.c new file mode 100644 index 0000000000..b9a7a5ffcd --- /dev/null +++ b/odbc/src/statement.c @@ -0,0 +1,98 @@ +#include "ydb_odbc.h" +#include "client/query.h" +#include +#include + +SQLRETURN YDB_SQLExecDirect(SQLHSTMT StatementHandle, SQLCHAR* StatementText, + SQLINTEGER TextLength) { + YDB_STATEMENT* stmt = (YDB_STATEMENT*)StatementHandle; + if (!stmt || !stmt->connection || !stmt->connection->connected || !StatementText) { + return SQL_ERROR; + } + + // Если текст запроса не указан, используем длину строки + if (TextLength == SQL_NTS) { + TextLength = strlen((char*)StatementText); + } + + // Создаем клиент запросов, если его еще нет + if (!stmt->query_client) { + stmt->query_client = YDB_CreateQueryClient(stmt->connection->ydb_driver); + if (!stmt->query_client) { + return SQL_ERROR; + } + } + + // Освобождаем предыдущий результат, если он есть + if (stmt->result) { + YDB_FreeExecuteQueryResult(stmt->result); + stmt->result = NULL; + } + + // Выполняем запрос + if (!YDB_ExecuteQuery(stmt->query_client, (char*)StatementText, &stmt->result)) { + return SQL_ERROR; + } + + stmt->current_row = 0; + return SQL_SUCCESS; +} + +SQLRETURN YDB_SQLPrepare(SQLHSTMT StatementHandle, SQLCHAR* StatementText, + SQLINTEGER TextLength) { + // YDB не требует предварительной подготовки запросов + // Просто сохраняем текст запроса для последующего выполнения + YDB_STATEMENT* stmt = (YDB_STATEMENT*)StatementHandle; + if (!stmt || !StatementText) { + return SQL_ERROR; + } + + // Если текст запроса не указан, используем длину строки + if (TextLength == SQL_NTS) { + TextLength = strlen((char*)StatementText); + } + + // TODO: Сохранить текст запроса для последующего выполнения + + return SQL_SUCCESS; +} + +SQLRETURN YDB_SQLExecute(SQLHSTMT StatementHandle) { + // В YDB все запросы выполняются сразу + // Эта функция просто вызывает SQLExecDirect с сохраненным текстом запроса + YDB_STATEMENT* stmt = (YDB_STATEMENT*)StatementHandle; + if (!stmt) { + return SQL_ERROR; + } + + // TODO: Выполнить сохраненный запрос + + return SQL_SUCCESS; +} + +SQLRETURN YDB_SQLFetch(SQLHSTMT StatementHandle) { + YDB_STATEMENT* stmt = (YDB_STATEMENT*)StatementHandle; + if (!stmt || !stmt->result) { + return SQL_ERROR; + } + + // TODO: Преобразовать данные текущей строки в формат ODBC + + stmt->current_row++; + return SQL_SUCCESS; +} + +SQLRETURN YDB_SQLCloseCursor(SQLHSTMT StatementHandle) { + YDB_STATEMENT* stmt = (YDB_STATEMENT*)StatementHandle; + if (!stmt) { + return SQL_ERROR; + } + + if (stmt->result) { + YDB_FreeExecuteQueryResult(stmt->result); + stmt->result = NULL; + } + + stmt->current_row = 0; + return SQL_SUCCESS; +} \ No newline at end of file From 74d904757767157d7e01b23456282a5a486cb238 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Sat, 10 May 2025 18:12:57 +0000 Subject: [PATCH 02/11] C API --- CMakeLists.txt | 7 +- c_api/CMakeLists.txt | 18 ++ c_api/README.md | 51 ++++ c_api/include/ydb-cpp-sdk/c_api/driver.h | 47 +++ c_api/include/ydb-cpp-sdk/c_api/query.h | 39 +++ c_api/include/ydb-cpp-sdk/c_api/result.h | 32 ++ c_api/include/ydb-cpp-sdk/c_api/value.h | 75 +++++ c_api/src/driver.cpp | 172 +++++++++++ c_api/src/impl/driver_impl.h | 22 ++ c_api/src/impl/result_impl.h | 14 + c_api/src/impl/value_impl.h | 15 + c_api/src/query.cpp | 140 +++++++++ c_api/src/result.cpp | 95 ++++++ c_api/src/value.cpp | 276 ++++++++++++++++++ cmake/common.cmake | 8 +- cmake/ydb-cpp-sdk-config.cmake.in | 4 +- examples/CMakeLists.txt | 1 + examples/basic_example/CMakeLists.txt | 6 +- examples/bulk_upsert_simple/CMakeLists.txt | 2 +- examples/c_api/CMakeLists.txt | 5 + examples/c_api/main.c | 37 +++ examples/pagination/CMakeLists.txt | 2 +- examples/secondary_index/CMakeLists.txt | 2 +- .../secondary_index_builtin/CMakeLists.txt | 2 +- .../topic_reader/eventloop/CMakeLists.txt | 2 +- examples/topic_reader/simple/CMakeLists.txt | 2 +- .../topic_reader/transaction/CMakeLists.txt | 2 +- examples/ttl/CMakeLists.txt | 2 +- examples/vector_index/CMakeLists.txt | 2 +- odbc/CMakeLists.txt | 6 +- odbc/include/client/driver.h | 15 - odbc/include/client/query.h | 17 -- odbc/src/client/driver.cpp | 25 -- odbc/src/client/query.cpp | 56 ---- .../integration/basic_example/CMakeLists.txt | 6 +- tests/integration/bulk_upsert/CMakeLists.txt | 2 +- .../integration/server_restart/CMakeLists.txt | 2 +- tests/unit/client/CMakeLists.txt | 20 +- 38 files changed, 1080 insertions(+), 151 deletions(-) create mode 100644 c_api/CMakeLists.txt create mode 100644 c_api/README.md create mode 100644 c_api/include/ydb-cpp-sdk/c_api/driver.h create mode 100644 c_api/include/ydb-cpp-sdk/c_api/query.h create mode 100644 c_api/include/ydb-cpp-sdk/c_api/result.h create mode 100644 c_api/include/ydb-cpp-sdk/c_api/value.h create mode 100644 c_api/src/driver.cpp create mode 100644 c_api/src/impl/driver_impl.h create mode 100644 c_api/src/impl/result_impl.h create mode 100644 c_api/src/impl/value_impl.h create mode 100644 c_api/src/query.cpp create mode 100644 c_api/src/result.cpp create mode 100644 c_api/src/value.cpp create mode 100644 examples/c_api/CMakeLists.txt create mode 100644 examples/c_api/main.c delete mode 100644 odbc/include/client/driver.h delete mode 100644 odbc/include/client/query.h delete mode 100644 odbc/src/client/driver.cpp delete mode 100644 odbc/src/client/query.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 30e9ba2f7e..b4bfc154e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ string(REGEX MATCH "YDB_SDK_VERSION = \"([0-9]+\\.[0-9]+\\.[0-9]+)\"" _ ${YDB_SD set(YDB_SDK_VERSION ${CMAKE_MATCH_1}) message(STATUS "YDB С++ SDK version: ${YDB_SDK_VERSION}") -project(YDB-CPP-SDK VERSION ${YDB_SDK_VERSION} LANGUAGES C CXX ASM) +project(ydb-cpp-sdk VERSION ${YDB_SDK_VERSION} LANGUAGES C CXX ASM) option(YDB_SDK_INSTALL "Install YDB C++ SDK" Off) option(YDB_SDK_TESTS "Build YDB C++ SDK tests" Off) @@ -59,11 +59,12 @@ add_subdirectory(library/cpp) add_subdirectory(include/ydb-cpp-sdk/client) add_subdirectory(src) add_subdirectory(util) +add_subdirectory(c_api) #_ydb_sdk_validate_public_headers() if (YDB_SDK_ODBC) - add_subdirectory(odbc) + #add_subdirectory(odbc) endif() if (YDB_SDK_EXAMPLES) @@ -79,7 +80,7 @@ if (YDB_SDK_INSTALL) install(EXPORT ydb-cpp-sdk-targets FILE ydb-cpp-sdk-targets.cmake CONFIGURATIONS RELEASE - NAMESPACE YDB-CPP-SDK:: + NAMESPACE ydb-cpp-sdk:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ydb-cpp-sdk/release ) configure_package_config_file( diff --git a/c_api/CMakeLists.txt b/c_api/CMakeLists.txt new file mode 100644 index 0000000000..6f7a729539 --- /dev/null +++ b/c_api/CMakeLists.txt @@ -0,0 +1,18 @@ +add_library(ydb-c-api STATIC + src/driver.cpp + src/query.cpp + src/result.cpp + src/value.cpp +) + +target_include_directories(ydb-c-api PUBLIC include) + +target_link_libraries(ydb-c-api + PRIVATE + yutil + ydb-cpp-sdk::Query + ydb-cpp-sdk::Table + ydb-cpp-sdk::Driver +) + +add_library(ydb-cpp-sdk::c-api ALIAS ydb-c-api) diff --git a/c_api/README.md b/c_api/README.md new file mode 100644 index 0000000000..b7f929c553 --- /dev/null +++ b/c_api/README.md @@ -0,0 +1,51 @@ +Синхронный API Асинхронный API + +libpq: +```c +PGconn *conn = PQconnectdb("..."); +// Блокирует выполнение до завершения +``` + +libpq: +```c +PGconn *conn = PQconnectStart("..."); +do { + pollstatus = PQconnectPoll(conn); + // Ожидание событий +} while (pollstatus != PGRES_POLLING_OK); +``` + +MySQL: +
MYSQL *conn = mysql_init(NULL); +
mysql_real_connect(conn, ...); + +MySQL: +
status = mysql_real_connect_nonblocking(mysql, ...); +
while (status == NET_ASYNC_NOT_READY) { +
// Обработка других задач
+ status = mysql_real_connect_nonblocking(...);
+} + +Выполнение запросов + +Синхронный API Асинхронный API + +libpq:
PGresult *res = PQexec(conn, "SELECT ...");
Ожидает завершения выполнения + +libpq:
PQsendQuery(conn, "SELECT ...");
// Можно выполнять другую работу
while ((res = PQgetResult(conn)) != NULL) {
// Обработка результатов
} + +MySQL:
mysql_query(conn, "SELECT ...");
result = mysql_store_result(conn); + +MySQL:
status = mysql_real_query_nonblocking(mysql, "...");
// Проверка status и ожидание
status = mysql_store_result_nonblocking(mysql, &result); + +Обработка ошибок + +Синхронный API Асинхронный API + +libpq:
if (PQstatus(conn) != CONNECTION_OK) {
fprintf(stderr, "%s", PQerrorMessage(conn));
} + +libpq:
Такая же проверка, но в каждом шаге асинхронного процесса:
if (pollstatus == PGRES_POLLING_FAILED) {
fprintf(stderr, "%s", PQerrorMessage(conn));
} + +MySQL:
if (mysql_query(conn, query)) {
fprintf(stderr, "%s", mysql_error(conn));
} + +MySQL:
if (status == NET_ASYNC_ERROR) {
fprintf(stderr, "%s", mysql_error(mysql));
} \ No newline at end of file diff --git a/c_api/include/ydb-cpp-sdk/c_api/driver.h b/c_api/include/ydb-cpp-sdk/c_api/driver.h new file mode 100644 index 0000000000..557994675f --- /dev/null +++ b/c_api/include/ydb-cpp-sdk/c_api/driver.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct TYdbDriverConfigImpl TYdbDriverConfig; +typedef struct TYdbDriverImpl TYdbDriver; + +typedef enum { + YDB_DRIVER_CONFIG_OK, + YDB_DRIVER_CONFIG_INVALID, +} EYdbDriverConfigStatus; + +typedef enum { + YDB_DRIVER_OK, + YDB_DRIVER_ERROR, +} EYdbDriverStatus; + +// Создание и уничтожение конфигурации +TYdbDriverConfig* YdbCreateDriverConfig(const char* connectionString); +void YdbDestroyDriverConfig(TYdbDriverConfig* config); + +// Установка параметров конфигурации +TYdbDriverConfig* YdbSetEndpoint(TYdbDriverConfig* config, const char* endpoint); +TYdbDriverConfig* YdbSetDatabase(TYdbDriverConfig* config, const char* database); +TYdbDriverConfig* YdbSetAuthToken(TYdbDriverConfig* config, const char* token); +TYdbDriverConfig* YdbSetSecureConnection(TYdbDriverConfig* config, const char* cert); + +// Получение результата конфигурации +EYdbDriverConfigStatus YdbGetDriverConfigStatus(TYdbDriverConfig* config); +const char* YdbGetDriverConfigErrorMessage(TYdbDriverConfig* config); + +// Создание и уничтожение драйвера +TYdbDriver* YdbCreateDriver(const char* connectionString); +TYdbDriver* YdbCreateDriverFromConfig(TYdbDriverConfig* config); +void YdbDestroyDriver(TYdbDriver* driver); + +// Получение результата драйвера +EYdbDriverStatus YdbGetDriverStatus(TYdbDriver* driver); +const char* YdbGetDriverErrorMessage(TYdbDriver* driver); + +#ifdef __cplusplus +} +#endif diff --git a/c_api/include/ydb-cpp-sdk/c_api/query.h b/c_api/include/ydb-cpp-sdk/c_api/query.h new file mode 100644 index 0000000000..e3bd3918ac --- /dev/null +++ b/c_api/include/ydb-cpp-sdk/c_api/query.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "driver.h" +#include "result.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct TYdbQueryClientImpl TYdbQueryClient; +typedef struct TYdbQueryResultImpl TYdbQueryResult; + +typedef enum { + YDB_QUERY_CLIENT_OK, + YDB_QUERY_CLIENT_ERROR, +} EYdbQueryClientError; + +typedef enum { + YDB_QUERY_RESULT_OK, + YDB_QUERY_RESULT_ERROR, +} EYdbQueryResultError; + +// Создание и уничтожение клиента запросов +TYdbQueryClient* YdbCreateQueryClient(TYdbDriver* driver); +void YdbDestroyQueryClient(TYdbQueryClient* queryClient); + +// Выполнение запроса +TYdbQueryResult* YdbExecuteQuery(TYdbQueryClient* queryClient, const char* query); +void YdbDestroyQueryResult(TYdbQueryResult* result); + +// Получение результата запроса +int YdbGetQueryResultSetsCount(TYdbQueryResult* result); +TYdbResultSet* YdbGetQueryResultSet(TYdbQueryResult* result, size_t index); + +#ifdef __cplusplus +} +#endif diff --git a/c_api/include/ydb-cpp-sdk/c_api/result.h b/c_api/include/ydb-cpp-sdk/c_api/result.h new file mode 100644 index 0000000000..fc762ccf86 --- /dev/null +++ b/c_api/include/ydb-cpp-sdk/c_api/result.h @@ -0,0 +1,32 @@ +#pragma once + +#include "value.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct TYdbResultSetImpl TYdbResultSet; + +typedef enum { + YDB_RESULT_SET_OK, + YDB_RESULT_SET_ERROR, +} EYdbResultSetStatus; + +int YdbGetColumnsCount(TYdbResultSet* resultSet); +int YdbGetRowsCount(TYdbResultSet* resultSet); +int YdbIsTruncated(TYdbResultSet* resultSet); + +const char* YdbGetColumnName(TYdbResultSet* resultSet, size_t index); +int YdbGetColumnIndex(TYdbResultSet* resultSet, const char* name); + +TYdbValue* YdbGetValue(TYdbResultSet* resultSet, size_t rowIndex, const char* name); +TYdbValue* YdbGetValueByIndex(TYdbResultSet* resultSet, size_t rowIndex, size_t columnIndex); + +void YdbDestroyResultSet(TYdbResultSet* resultSet); + +#ifdef __cplusplus +} +#endif diff --git a/c_api/include/ydb-cpp-sdk/c_api/value.h b/c_api/include/ydb-cpp-sdk/c_api/value.h new file mode 100644 index 0000000000..5f0ad2caa1 --- /dev/null +++ b/c_api/include/ydb-cpp-sdk/c_api/value.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct TYdbValueImpl TYdbValue; +typedef struct TYdbParamsImpl TYdbParams; + +typedef enum { + YDB_VALUE_OK, + YDB_VALUE_ERROR, +} EYdbValueStatus; + +typedef enum { + YDB_TYPE_KIND_UNDEFINED, + YDB_TYPE_KIND_PRIMITIVE, + YDB_TYPE_KIND_OPTIONAL, + YDB_TYPE_KIND_LIST, + YDB_TYPE_KIND_TUPLE, + YDB_TYPE_KIND_STRUCT, + YDB_TYPE_KIND_DICT, + YDB_TYPE_KIND_VARIANT, +} EYdbTypeKind; + +typedef enum { + YDB_PRIMITIVE_TYPE_UNDEFINED, + YDB_PRIMITIVE_TYPE_BOOL, + YDB_PRIMITIVE_TYPE_INT8, + YDB_PRIMITIVE_TYPE_UINT8, + YDB_PRIMITIVE_TYPE_INT16, + YDB_PRIMITIVE_TYPE_UINT16, + YDB_PRIMITIVE_TYPE_INT32, + YDB_PRIMITIVE_TYPE_UINT32, + YDB_PRIMITIVE_TYPE_INT64, + YDB_PRIMITIVE_TYPE_UINT64, + YDB_PRIMITIVE_TYPE_FLOAT, + YDB_PRIMITIVE_TYPE_DOUBLE, + YDB_PRIMITIVE_TYPE_STRING, + YDB_PRIMITIVE_TYPE_UTF8, + YDB_PRIMITIVE_TYPE_YSON, + YDB_PRIMITIVE_TYPE_JSON, + YDB_PRIMITIVE_TYPE_JSON_DOCUMENT, + YDB_PRIMITIVE_TYPE_DYNUMBER, +} EYdbPrimitiveType; + +EYdbTypeKind YdbGetTypeKind(TYdbValue* value); +EYdbPrimitiveType YdbGetPrimitiveType(TYdbValue* value); + +EYdbValueStatus YdbGetBool(TYdbValue* value, bool* result); +EYdbValueStatus YdbGetInt8(TYdbValue* value, int8_t* result); +EYdbValueStatus YdbGetUint8(TYdbValue* value, uint8_t* result); +EYdbValueStatus YdbGetInt16(TYdbValue* value, int16_t* result); +EYdbValueStatus YdbGetUint16(TYdbValue* value, uint16_t* result); +EYdbValueStatus YdbGetInt32(TYdbValue* value, int32_t* result); +EYdbValueStatus YdbGetUint32(TYdbValue* value, uint32_t* result); +EYdbValueStatus YdbGetInt64(TYdbValue* value, int64_t* result); +EYdbValueStatus YdbGetUint64(TYdbValue* value, uint64_t* result); +EYdbValueStatus YdbGetFloat(TYdbValue* value, float* result); +EYdbValueStatus YdbGetDouble(TYdbValue* value, double* result); +EYdbValueStatus YdbGetString(TYdbValue* value, char** result); +EYdbValueStatus YdbGetUtf8(TYdbValue* value, char** result); +EYdbValueStatus YdbGetYson(TYdbValue* value, char** result); +EYdbValueStatus YdbGetJson(TYdbValue* value, char** result); +EYdbValueStatus YdbGetJsonDocument(TYdbValue* value, char** result); +EYdbValueStatus YdbGetDyNumber(TYdbValue* value, char** result); + +void YdbDestroyValue(TYdbValue* value); + +#ifdef __cplusplus +} +#endif diff --git a/c_api/src/driver.cpp b/c_api/src/driver.cpp new file mode 100644 index 0000000000..27e1093d68 --- /dev/null +++ b/c_api/src/driver.cpp @@ -0,0 +1,172 @@ +#include + +#include "impl/driver_impl.h" // NOLINT + +#include + +extern "C" { + +// Создание и уничтожение конфигурации +TYdbDriverConfig* YdbCreateDriverConfig(const char* connectionString) { + try { + if (!connectionString) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid config pointer"}; + } + + try { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_OK, "", NYdb::TDriverConfig(connectionString)}; + } catch (const std::exception& e) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, e.what()}; + } + } catch (const std::exception& e) { + return nullptr; + } +} + +void YdbDestroyDriverConfig(TYdbDriverConfig* config) { + if (config) { + delete config; + } +} + +// Установка параметров конфигурации +TYdbDriverConfig* YdbSetEndpoint(TYdbDriverConfig* config, const char* endpoint) { + try { + if (!config) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid config"}; + } + if (!endpoint) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid endpoint"}; + } + + try { + config->config.SetEndpoint(std::string(endpoint)); + return config; + } catch (const std::exception& e) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, e.what()}; + } + } catch (...) { + return nullptr; + } +} + +TYdbDriverConfig* YdbSetDatabase(TYdbDriverConfig* config, const char* database) { + try { + if (!config) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid config"}; + } + if (!database) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid database"}; + } + + try { + config->config.SetDatabase(std::string(database)); + return config; + } catch (const std::exception& e) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, e.what()}; + } + } catch (...) { + return nullptr; + } +} + +TYdbDriverConfig* YdbSetAuthToken(TYdbDriverConfig* config, const char* token) { + try { + if (!config) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid config"}; + } + if (!token) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid token"}; + } + + try { + config->config.SetAuthToken(std::string(token)); + return config; + } catch (const std::exception& e) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, e.what()}; + } + } catch (...) { + return nullptr; + } +} + +TYdbDriverConfig* YdbSetSecureConnection(TYdbDriverConfig* config, const char* cert) { + try { + if (!config) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid config"}; + } + if (!cert) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid certificate"}; + } + + try { + config->config.UseSecureConnection(std::string(cert)); + return config; + } catch (const std::exception& e) { + return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, e.what()}; + } + } catch (...) { + return nullptr; + } +} + +EYdbDriverConfigStatus YdbGetDriverConfigStatus(TYdbDriverConfig* config) { + if (!config) { + return YDB_DRIVER_CONFIG_INVALID; + } + + return config->errorCode; +} + +const char* YdbGetDriverConfigErrorMessage(TYdbDriverConfig* config) { + if (!config) { + return "Invalid config"; + } + + return config->errorMessage.c_str(); +} + +// Создание и уничтожение драйвера +TYdbDriver* YdbCreateDriverFromConfig(TYdbDriverConfig* config) { + try { + if (!config) { + return new TYdbDriver{YDB_DRIVER_ERROR, "Invalid config"}; + } + + if (config->errorCode != 0) { + return new TYdbDriver{YDB_DRIVER_ERROR, "Invalid config: " + config->errorMessage}; + } + + try { + return new TYdbDriver{YDB_DRIVER_OK, "", NYdb::TDriver(config->config)}; + } catch (const std::exception& e) { + return new TYdbDriver{YDB_DRIVER_ERROR, e.what()}; + } + } catch (...) { + return nullptr; + } +} + +TYdbDriver* YdbCreateDriver(const char* connectionString) { + try { + if (!connectionString) { + return new TYdbDriver{YDB_DRIVER_ERROR, "Invalid connection string"}; + } + + try { + return new TYdbDriver{YDB_DRIVER_OK, "", NYdb::TDriver(std::string(connectionString))}; + } catch (const std::exception& e) { + return new TYdbDriver{YDB_DRIVER_ERROR, e.what()}; + } + } catch (...) { + return nullptr; + } +} + +void YdbDestroyDriver(TYdbDriver* driver) { + if (driver) { + delete driver; + } +} + +} diff --git a/c_api/src/impl/driver_impl.h b/c_api/src/impl/driver_impl.h new file mode 100644 index 0000000000..87644f15f2 --- /dev/null +++ b/c_api/src/impl/driver_impl.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +#include +#include + +struct TYdbDriverConfigImpl { + EYdbDriverConfigStatus errorCode; + std::string errorMessage; + + NYdb::TDriverConfig config; +}; + +struct TYdbDriverImpl { + EYdbDriverStatus errorCode; + std::string errorMessage; + + std::optional driver; +}; diff --git a/c_api/src/impl/result_impl.h b/c_api/src/impl/result_impl.h new file mode 100644 index 0000000000..b662ea4f45 --- /dev/null +++ b/c_api/src/impl/result_impl.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include + +#include + +struct TYdbResultSetImpl { + EYdbResultSetStatus errorCode; + std::string errorMessage; + + std::optional result; +}; diff --git a/c_api/src/impl/value_impl.h b/c_api/src/impl/value_impl.h new file mode 100644 index 0000000000..d7a7a4eba9 --- /dev/null +++ b/c_api/src/impl/value_impl.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include + +#include +#include + +struct TYdbValueImpl { + EYdbValueStatus errorCode; + std::string errorMessage; + + std::optional value; +}; diff --git a/c_api/src/query.cpp b/c_api/src/query.cpp new file mode 100644 index 0000000000..0f11c52c17 --- /dev/null +++ b/c_api/src/query.cpp @@ -0,0 +1,140 @@ +#include + +#include + +#include "impl/driver_impl.h" // NOLINT +#include "impl/result_impl.h" // NOLINT + +#include + +struct TYdbQueryClientImpl { + EYdbQueryClientError errorCode; + std::string errorMessage; + + std::optional client; +}; + +struct TYdbQueryResultImpl { + EYdbQueryResultError errorCode; + std::string errorMessage; + + std::optional result; +}; + +extern "C" { + +TYdbQueryClient* YdbCreateQueryClient(TYdbDriver* driver) { + try { + if (!driver || !driver->driver.has_value()) { + return new TYdbQueryClient{ + YDB_QUERY_CLIENT_ERROR, + "Invalid driver" + }; + } + + try { + return new TYdbQueryClient{ + YDB_QUERY_CLIENT_OK, + "", + NYdb::NQuery::TQueryClient(*driver->driver) + }; + } catch (const std::exception& e) { + return new TYdbQueryClient{ + YDB_QUERY_CLIENT_ERROR, + e.what() + }; + } + } catch (...) { + return nullptr; + } +} + +void YdbDestroyQueryClient(TYdbQueryClient* queryClient) { + if (queryClient) { + delete queryClient; + } +} + +TYdbQueryResult* YdbExecuteQuery(TYdbQueryClient* queryClient, const char* query) { + try { + if (!queryClient || !queryClient->client.has_value()) { + return new TYdbQueryResult{ + YDB_QUERY_RESULT_ERROR, + "Invalid query client" + }; + } + + if (!query) { + return new TYdbQueryResult{ + YDB_QUERY_RESULT_ERROR, + "Invalid query" + }; + } + + try { + auto client = *queryClient->client; + auto executeResult = client.ExecuteQuery( + std::string(query), + NYdb::NQuery::TTxControl::NoTx() + ).GetValueSync(); + + if (!executeResult.IsSuccess()) { + return new TYdbQueryResult{ + YDB_QUERY_RESULT_ERROR, + "Query execution failed: " + executeResult.GetIssues().ToString() + }; + } + + return new TYdbQueryResult{ + YDB_QUERY_RESULT_OK, + "", + executeResult + }; + } catch (const std::exception& e) { + return new TYdbQueryResult{ + YDB_QUERY_RESULT_ERROR, + e.what() + }; + } + } catch (...) { + return nullptr; + } +} + +void YdbDestroyQueryResult(TYdbQueryResult* result) { + if (result) { + delete result; + } +} + +TYdbResultSet* YdbGetQueryResultSet(TYdbQueryResult* result, size_t index) { + try { + if (!result || !result->result.has_value()) { + return nullptr; + } + + try { + return new TYdbResultSet{ + YDB_RESULT_SET_OK, + "", + result->result->GetResultSet(index) + }; + } catch (const std::exception& e) { + return new TYdbResultSet{ + YDB_RESULT_SET_ERROR, + e.what() + }; + } + } catch (...) { + return nullptr; + } +} + +int YdbGetQueryResultSetsCount(TYdbQueryResult* result) { + if (!result || !result->result.has_value()) { + return -1; + } + return result->result->GetResultSets().size(); +} + +} diff --git a/c_api/src/result.cpp b/c_api/src/result.cpp new file mode 100644 index 0000000000..c6c52bc737 --- /dev/null +++ b/c_api/src/result.cpp @@ -0,0 +1,95 @@ +#include + +#include +#include + +#include "impl/result_impl.h" // NOLINT +#include "impl/value_impl.h" // NOLINT + +extern "C" { + +int YdbGetColumnsCount(TYdbResultSet* resultSet) { + if (!resultSet || !resultSet->result.has_value()) { + return -1; + } + + return resultSet->result->ColumnsCount(); +} + +int YdbGetRowsCount(TYdbResultSet* resultSet) { + if (!resultSet || !resultSet->result.has_value()) { + return -1; + } + + return resultSet->result->RowsCount(); +} + +int YdbIsTruncated(TYdbResultSet* resultSet) { + if (!resultSet || !resultSet->result.has_value()) { + return -1; + } + + return resultSet->result->Truncated(); +} + +const char* YdbGetColumnName(TYdbResultSet* resultSet, size_t index) { + if (!resultSet || !resultSet->result.has_value()) { + return nullptr; + } + + return resultSet->result->GetColumnsMeta()[index].Name.c_str(); +} + +int YdbGetColumnIndex(TYdbResultSet* resultSet, const char* name) { + try { + if (!resultSet || !resultSet->result.has_value()) { + return -1; + } + + NYdb::TResultSetParser parser(*resultSet->result); + return parser.ColumnIndex(name); + } catch (...) { + return -1; + } +} + +TYdbValue* YdbGetValue(TYdbResultSet* resultSet, size_t rowIndex, const char* name) { + try { + if (!resultSet || !resultSet->result.has_value()) { + return new TYdbValue{YDB_VALUE_ERROR, "Invalid result set"}; + } + + NYdb::TResultSetParser parser(*resultSet->result); + int columnIndex = parser.ColumnIndex(name); + if (columnIndex == -1) { + return new TYdbValue{YDB_VALUE_ERROR, "Invalid column name"}; + } + + return YdbGetValueByIndex(resultSet, rowIndex, columnIndex); + } catch (...) { + return nullptr; + } +} + +TYdbValue* YdbGetValueByIndex(TYdbResultSet* resultSet, size_t rowIndex, size_t columnIndex) { + try { + if (!resultSet || !resultSet->result.has_value()) { + return nullptr; + } + + auto proto = NYdb::TProtoAccessor::GetProto(*resultSet->result); + + auto type = resultSet->result->GetColumnsMeta()[columnIndex].Type; + auto value = proto.rows(rowIndex).items(columnIndex); + + return new TYdbValue{YDB_VALUE_OK, "", NYdb::TValue{type, value}}; + } catch (...) { + return nullptr; + } +} + +void YdbDestroyResultSet(TYdbResultSet* resultSet) { + delete resultSet; +} + +} diff --git a/c_api/src/value.cpp b/c_api/src/value.cpp new file mode 100644 index 0000000000..2aa25ce418 --- /dev/null +++ b/c_api/src/value.cpp @@ -0,0 +1,276 @@ +#include + +#include + +#include "impl/value_impl.h" // NOLINT + +extern "C" { + +EYdbTypeKind YdbGetTypeKind(TYdbValue* value) { + if (!value || !value->value.has_value()) { + return YDB_TYPE_KIND_UNDEFINED; + } + + NYdb::TValueParser valueParser(*value->value); + + switch (valueParser.GetKind()) { + case NYdb::TTypeParser::ETypeKind::Primitive: + return YDB_TYPE_KIND_PRIMITIVE; + case NYdb::TTypeParser::ETypeKind::Optional: + return YDB_TYPE_KIND_OPTIONAL; + case NYdb::TTypeParser::ETypeKind::List: + return YDB_TYPE_KIND_LIST; + case NYdb::TTypeParser::ETypeKind::Struct: + return YDB_TYPE_KIND_STRUCT; + case NYdb::TTypeParser::ETypeKind::Tuple: + return YDB_TYPE_KIND_TUPLE; + case NYdb::TTypeParser::ETypeKind::Dict: + return YDB_TYPE_KIND_DICT; + case NYdb::TTypeParser::ETypeKind::Variant: + return YDB_TYPE_KIND_VARIANT; + default: + return YDB_TYPE_KIND_UNDEFINED; + } +} + +EYdbPrimitiveType YdbGetPrimitiveType(TYdbValue* value) { + if (!value || !value->value.has_value()) { + return YDB_PRIMITIVE_TYPE_UNDEFINED; + } + + try { + NYdb::TValueParser valueParser(*value->value); + + switch (valueParser.GetPrimitiveType()) { + case NYdb::EPrimitiveType::Int8: + return YDB_PRIMITIVE_TYPE_INT8; + case NYdb::EPrimitiveType::Uint8: + return YDB_PRIMITIVE_TYPE_UINT8; + case NYdb::EPrimitiveType::Int16: + return YDB_PRIMITIVE_TYPE_INT16; + case NYdb::EPrimitiveType::Uint16: + return YDB_PRIMITIVE_TYPE_UINT16; + case NYdb::EPrimitiveType::Int32: + return YDB_PRIMITIVE_TYPE_INT32; + case NYdb::EPrimitiveType::Uint32: + return YDB_PRIMITIVE_TYPE_UINT32; + case NYdb::EPrimitiveType::Int64: + return YDB_PRIMITIVE_TYPE_INT64; + case NYdb::EPrimitiveType::Uint64: + return YDB_PRIMITIVE_TYPE_UINT64; + case NYdb::EPrimitiveType::Float: + return YDB_PRIMITIVE_TYPE_FLOAT; + case NYdb::EPrimitiveType::Double: + return YDB_PRIMITIVE_TYPE_DOUBLE; + case NYdb::EPrimitiveType::String: + return YDB_PRIMITIVE_TYPE_STRING; + case NYdb::EPrimitiveType::Utf8: + return YDB_PRIMITIVE_TYPE_UTF8; + case NYdb::EPrimitiveType::Yson: + return YDB_PRIMITIVE_TYPE_YSON; + case NYdb::EPrimitiveType::Json: + return YDB_PRIMITIVE_TYPE_JSON; + case NYdb::EPrimitiveType::JsonDocument: + return YDB_PRIMITIVE_TYPE_JSON_DOCUMENT; + case NYdb::EPrimitiveType::DyNumber: + return YDB_PRIMITIVE_TYPE_DYNUMBER; + default: + return YDB_PRIMITIVE_TYPE_UNDEFINED; + } + } catch (...) { + return YDB_PRIMITIVE_TYPE_UNDEFINED; + } +} + +EYdbValueStatus YdbGetInt8(TYdbValue* value, int8_t* result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = valueParser.GetInt8(); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetUint8(TYdbValue* value, uint8_t* result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = valueParser.GetUint8(); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetInt16(TYdbValue* value, int16_t* result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = valueParser.GetInt16(); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetUint16(TYdbValue* value, uint16_t* result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = valueParser.GetUint16(); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetInt32(TYdbValue* value, int32_t* result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = valueParser.GetInt32(); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetUint32(TYdbValue* value, uint32_t* result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = valueParser.GetUint32(); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetInt64(TYdbValue* value, int64_t* result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = valueParser.GetInt64(); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetUint64(TYdbValue* value, uint64_t* result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = valueParser.GetUint64(); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetFloat(TYdbValue* value, float* result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = valueParser.GetFloat(); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetDouble(TYdbValue* value, double* result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = valueParser.GetDouble(); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetString(TYdbValue* value, char** result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = strndup(valueParser.GetString().c_str(), valueParser.GetString().size()); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetUtf8(TYdbValue* value, char** result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = strndup(valueParser.GetUtf8().c_str(), valueParser.GetUtf8().size()); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetYson(TYdbValue* value, char** result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = strndup(valueParser.GetYson().c_str(), valueParser.GetYson().size()); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetJson(TYdbValue* value, char** result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = strndup(valueParser.GetJson().c_str(), valueParser.GetJson().size()); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetJsonDocument(TYdbValue* value, char** result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = strndup(valueParser.GetJsonDocument().c_str(), valueParser.GetJsonDocument().size()); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetDyNumber(TYdbValue* value, char** result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = strndup(valueParser.GetDyNumber().c_str(), valueParser.GetDyNumber().size()); + return YDB_VALUE_OK; +} + +EYdbValueStatus YdbGetBool(TYdbValue* value, bool* result) { + if (!value || !value->value.has_value()) { + return YDB_VALUE_ERROR; + } + + NYdb::TValueParser valueParser(*value->value); + + *result = valueParser.GetBool(); + return YDB_VALUE_OK; +} + +void YdbDestroyValue(TYdbValue* value) { + delete value; +} + +} diff --git a/cmake/common.cmake b/cmake/common.cmake index 546ce4e81f..1193388349 100644 --- a/cmake/common.cmake +++ b/cmake/common.cmake @@ -173,7 +173,7 @@ function(resources Tgt Output) endfunction() function(_ydb_sdk_make_client_component CmpName Tgt) - add_library(YDB-CPP-SDK::${CmpName} ALIAS ${Tgt}) + add_library(ydb-cpp-sdk::${CmpName} ALIAS ${Tgt}) _ydb_sdk_install_targets(TARGETS ${Tgt} ${ARGN}) set(YDB-CPP-SDK_AVAILABLE_COMPONENTS ${YDB-CPP-SDK_AVAILABLE_COMPONENTS} ${CmpName} CACHE INTERNAL "") @@ -182,7 +182,7 @@ endfunction() function(_ydb_sdk_add_library Tgt) cmake_parse_arguments(ARG - "INTERFACE" "" "" + "INTERFACE;OBJECT" "" "" ${ARGN} ) @@ -192,6 +192,9 @@ function(_ydb_sdk_add_library Tgt) set(libraryMode "INTERFACE") set(includeMode "INTERFACE") endif() + if (ARG_OBJECT) + set(libraryMode "OBJECT") + endif() add_library(${Tgt} ${libraryMode}) target_include_directories(${Tgt} ${includeMode} $ @@ -255,4 +258,3 @@ function(_ydb_sdk_validate_public_headers) ) target_include_directories(validate_public_interface PUBLIC ${YDB_SDK_BINARY_DIR}/__validate_headers_dir/include) endfunction() - diff --git a/cmake/ydb-cpp-sdk-config.cmake.in b/cmake/ydb-cpp-sdk-config.cmake.in index ba1c144f0a..2ed5dc4190 100644 --- a/cmake/ydb-cpp-sdk-config.cmake.in +++ b/cmake/ydb-cpp-sdk-config.cmake.in @@ -44,7 +44,7 @@ function(_find_ydb_sdk_component CompName) message(FATAL_ERROR "${CompName} is not available component") endif() list(GET YDB-CPP-SDK_COMPONENT_TARGETS ${CompId} Tgt) - add_library(YDB-CPP-SDK::${CompName} ALIAS YDB-CPP-SDK::${Tgt}) + add_library(ydb-cpp-sdk::${CompName} ALIAS ydb-cpp-sdk::${Tgt}) set(YDB-CPP-SDK_${CompName}_FOUND TRUE PARENT_SCOPE) endfunction() @@ -56,4 +56,4 @@ endforeach() @PACKAGE_INIT@ -check_required_components(YDB-CPP-SDK) +check_required_components(ydb-cpp-sdk) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 97119cff5e..730f2400bc 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(basic_example) add_subdirectory(bulk_upsert_simple) +add_subdirectory(c_api) add_subdirectory(pagination) add_subdirectory(secondary_index) add_subdirectory(secondary_index_builtin) diff --git a/examples/basic_example/CMakeLists.txt b/examples/basic_example/CMakeLists.txt index 75d2d1b253..8e51343941 100644 --- a/examples/basic_example/CMakeLists.txt +++ b/examples/basic_example/CMakeLists.txt @@ -3,9 +3,9 @@ add_executable(basic_example) target_link_libraries(basic_example PUBLIC yutil getopt - YDB-CPP-SDK::Query - YDB-CPP-SDK::Params - YDB-CPP-SDK::Driver + ydb-cpp-sdk::Query + ydb-cpp-sdk::Params + ydb-cpp-sdk::Driver ) target_sources(basic_example PRIVATE diff --git a/examples/bulk_upsert_simple/CMakeLists.txt b/examples/bulk_upsert_simple/CMakeLists.txt index 4f7c3eca7f..34b8ed62c3 100644 --- a/examples/bulk_upsert_simple/CMakeLists.txt +++ b/examples/bulk_upsert_simple/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(bulk_upsert_simple) target_link_libraries(bulk_upsert_simple PUBLIC yutil getopt - YDB-CPP-SDK::Table + ydb-cpp-sdk::Table ) target_sources(bulk_upsert_simple PRIVATE diff --git a/examples/c_api/CMakeLists.txt b/examples/c_api/CMakeLists.txt new file mode 100644 index 0000000000..5060c4c25d --- /dev/null +++ b/examples/c_api/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(c_api_example + main.c +) + +target_link_libraries(c_api_example ydb-cpp-sdk::c-api) diff --git a/examples/c_api/main.c b/examples/c_api/main.c new file mode 100644 index 0000000000..ca1e2018d0 --- /dev/null +++ b/examples/c_api/main.c @@ -0,0 +1,37 @@ +#include + +#include + +#include + +int main() { + TYdbDriver* driver = YdbCreateDriver("grpc://localhost:2136/?database=/local"); + + TYdbQueryClient* query = YdbCreateQueryClient(driver); + + TYdbQueryResult* result = YdbExecuteQuery(query, "SELECT 1"); + + int resultSetsCount = YdbGetQueryResultSetsCount(result); + for (int i = 0; i < resultSetsCount; i++) { + TYdbResultSet* resultSet = YdbGetQueryResultSet(result, i); + int rowsCount = YdbGetRowsCount(resultSet); + for (int j = 0; j < rowsCount; j++) { + TYdbValue* value = YdbGetValueByIndex(resultSet, j, 0); + + EYdbPrimitiveType primitiveType = YdbGetPrimitiveType(value); + if (primitiveType == YDB_PRIMITIVE_TYPE_INT32) { + int32_t int32Value; + YdbGetInt32(value, &int32Value); + printf("%" PRId32 "\n", int32Value); + } else { + printf("Unknown primitive type\n"); + } + YdbDestroyValue(value); + } + } + + YdbDestroyQueryResult(result); + YdbDestroyQueryClient(query); + YdbDestroyDriver(driver); + return 0; +} diff --git a/examples/pagination/CMakeLists.txt b/examples/pagination/CMakeLists.txt index 0936f38558..2b29726f00 100644 --- a/examples/pagination/CMakeLists.txt +++ b/examples/pagination/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(pagination) target_link_libraries(pagination PUBLIC yutil getopt - YDB-CPP-SDK::Table + ydb-cpp-sdk::Table ) target_sources(pagination PRIVATE diff --git a/examples/secondary_index/CMakeLists.txt b/examples/secondary_index/CMakeLists.txt index 6030de5f7f..47364c5597 100644 --- a/examples/secondary_index/CMakeLists.txt +++ b/examples/secondary_index/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(secondary_index) target_link_libraries(secondary_index PUBLIC yutil getopt - YDB-CPP-SDK::Table + ydb-cpp-sdk::Table ) target_sources(secondary_index PRIVATE diff --git a/examples/secondary_index_builtin/CMakeLists.txt b/examples/secondary_index_builtin/CMakeLists.txt index b46cc79159..e03e675827 100644 --- a/examples/secondary_index_builtin/CMakeLists.txt +++ b/examples/secondary_index_builtin/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(secondary_index_builtin) target_link_libraries(secondary_index_builtin PUBLIC yutil getopt - YDB-CPP-SDK::Table + ydb-cpp-sdk::Table ) target_sources(secondary_index_builtin PRIVATE diff --git a/examples/topic_reader/eventloop/CMakeLists.txt b/examples/topic_reader/eventloop/CMakeLists.txt index 2cdc984955..114a0ae35b 100644 --- a/examples/topic_reader/eventloop/CMakeLists.txt +++ b/examples/topic_reader/eventloop/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(persqueue_reader_eventloop) target_link_libraries(persqueue_reader_eventloop PUBLIC yutil - YDB-CPP-SDK::Topic + ydb-cpp-sdk::Topic getopt ) diff --git a/examples/topic_reader/simple/CMakeLists.txt b/examples/topic_reader/simple/CMakeLists.txt index 68846ab215..2b7da165f0 100644 --- a/examples/topic_reader/simple/CMakeLists.txt +++ b/examples/topic_reader/simple/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(simple_persqueue_reader) target_link_libraries(simple_persqueue_reader PUBLIC yutil - YDB-CPP-SDK::Topic + ydb-cpp-sdk::Topic getopt ) diff --git a/examples/topic_reader/transaction/CMakeLists.txt b/examples/topic_reader/transaction/CMakeLists.txt index 64d30b4d8c..77fd8ab446 100644 --- a/examples/topic_reader/transaction/CMakeLists.txt +++ b/examples/topic_reader/transaction/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(read_from_topic_in_transaction) target_link_libraries(read_from_topic_in_transaction PUBLIC yutil - YDB-CPP-SDK::Topic + ydb-cpp-sdk::Topic getopt ) diff --git a/examples/ttl/CMakeLists.txt b/examples/ttl/CMakeLists.txt index 48a004b4cc..9a0655e7d1 100644 --- a/examples/ttl/CMakeLists.txt +++ b/examples/ttl/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(ttl) target_link_libraries(ttl PUBLIC yutil getopt - YDB-CPP-SDK::Table + ydb-cpp-sdk::Table ) target_sources(ttl PRIVATE diff --git a/examples/vector_index/CMakeLists.txt b/examples/vector_index/CMakeLists.txt index cd836a00b4..b5aac62195 100644 --- a/examples/vector_index/CMakeLists.txt +++ b/examples/vector_index/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(vector_index) target_link_libraries(vector_index PUBLIC yutil getopt - YDB-CPP-SDK::Table + ydb-cpp-sdk::Table ) target_sources(vector_index PRIVATE diff --git a/odbc/CMakeLists.txt b/odbc/CMakeLists.txt index 985782900e..b2a5b03b08 100644 --- a/odbc/CMakeLists.txt +++ b/odbc/CMakeLists.txt @@ -30,9 +30,9 @@ target_include_directories(ydb-odbc # Линкуем с YDB SDK и ODBC target_link_libraries(ydb-odbc PUBLIC - YDB-CPP-SDK::Query - YDB-CPP-SDK::Table - YDB-CPP-SDK::Driver + ydb-cpp-sdk::Query + ydb-cpp-sdk::Table + ydb-cpp-sdk::Driver ODBC::ODBC ) diff --git a/odbc/include/client/driver.h b/odbc/include/client/driver.h deleted file mode 100644 index 6a95f9958c..0000000000 --- a/odbc/include/client/driver.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include // для size_t - -#ifdef __cplusplus -extern "C" { -#endif - -// Функции для работы с YDB через C++ SDK -void* YDB_CreateDriver(const char* endpoint, const char* user, const char* password); -void YDB_DestroyDriver(void* driver); - -#ifdef __cplusplus -} -#endif diff --git a/odbc/include/client/query.h b/odbc/include/client/query.h deleted file mode 100644 index ac6802dc6c..0000000000 --- a/odbc/include/client/query.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -void* YDB_CreateQueryClient(void* driver); -void YDB_DestroyQueryClient(void* query_client); - -int YDB_ExecuteQuery(void* query_client, const char* query, void** result); -void YDB_FreeExecuteQueryResult(void* result); - -#ifdef __cplusplus -} -#endif diff --git a/odbc/src/client/driver.cpp b/odbc/src/client/driver.cpp deleted file mode 100644 index faa87471e4..0000000000 --- a/odbc/src/client/driver.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "client/driver.h" - -#include - -extern "C" { - -void* YDB_CreateDriver(const char* endpoint, const char* user, const char* password) { - try { - auto config = NYdb::TDriverConfig().SetEndpoint(std::string(endpoint, strlen(endpoint))); - - auto* driver = new NYdb::TDriver(config); - return static_cast(driver); - } catch (...) { - return nullptr; - } -} - -void YDB_DestroyDriver(void* driver) { - if (driver) { - auto* ydb_driver = static_cast(driver); - delete ydb_driver; - } -} - -} diff --git a/odbc/src/client/query.cpp b/odbc/src/client/query.cpp deleted file mode 100644 index 5da339d19b..0000000000 --- a/odbc/src/client/query.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "client/query.h" - -#include - -#include - -extern "C" { - -void* YDB_CreateQueryClient(void* driver) { - if (!driver) return nullptr; - - try { - auto* ydb_driver = static_cast(driver); - auto* query_client = new NYdb::NQuery::TQueryClient(*ydb_driver); - return static_cast(query_client); - } catch (...) { - return nullptr; - } -} - -void YDB_DestroyQueryClient(void* query_client) { - if (query_client) { - auto* client = static_cast(query_client); - delete client; - } -} - -int YDB_ExecuteQuery(void* query_client, const char* query, void** result) { - if (!query_client || !query || !result) { - return 0; - } - - try { - auto* client = static_cast(query_client); - auto executeResult = client->ExecuteQuery(std::string(query, strlen(query)), NYdb::NQuery::TTxControl::NoTx()).GetValueSync(); - - if (!executeResult.IsSuccess()) { - return 0; - } - - *result = reinterpret_cast(new NYdb::NQuery::TExecuteQueryResult(executeResult)); - - return 1; - } catch (...) { - return 0; - } -} - -void YDB_FreeExecuteQueryResult(void* result) { - if (result) { - auto* executeResult = reinterpret_cast(result); - delete executeResult; - } -} - -} // extern "C" diff --git a/tests/integration/basic_example/CMakeLists.txt b/tests/integration/basic_example/CMakeLists.txt index 55bdd05341..9eec918ec0 100644 --- a/tests/integration/basic_example/CMakeLists.txt +++ b/tests/integration/basic_example/CMakeLists.txt @@ -6,9 +6,9 @@ add_ydb_test(NAME basic_example_it GTEST LINK_LIBRARIES yutil api-protos - YDB-CPP-SDK::Driver - YDB-CPP-SDK::Proto - YDB-CPP-SDK::Table + ydb-cpp-sdk::Driver + ydb-cpp-sdk::Proto + ydb-cpp-sdk::Table LABELS integration ) diff --git a/tests/integration/bulk_upsert/CMakeLists.txt b/tests/integration/bulk_upsert/CMakeLists.txt index 46848877c6..535d21f2d6 100644 --- a/tests/integration/bulk_upsert/CMakeLists.txt +++ b/tests/integration/bulk_upsert/CMakeLists.txt @@ -5,7 +5,7 @@ add_ydb_test(NAME bulk_upsert_it GTEST bulk_upsert.h LINK_LIBRARIES yutil - YDB-CPP-SDK::Table + ydb-cpp-sdk::Table LABELS integration ) diff --git a/tests/integration/server_restart/CMakeLists.txt b/tests/integration/server_restart/CMakeLists.txt index 2d485de4e4..66d1c00d64 100644 --- a/tests/integration/server_restart/CMakeLists.txt +++ b/tests/integration/server_restart/CMakeLists.txt @@ -4,7 +4,7 @@ add_ydb_test(NAME server_restart_it GTEST LINK_LIBRARIES yutil api-grpc - YDB-CPP-SDK::Query + ydb-cpp-sdk::Query gRPC::grpc++ LABELS integration diff --git a/tests/unit/client/CMakeLists.txt b/tests/unit/client/CMakeLists.txt index f38938b7f9..b667e25584 100644 --- a/tests/unit/client/CMakeLists.txt +++ b/tests/unit/client/CMakeLists.txt @@ -3,7 +3,7 @@ add_ydb_test(NAME client-ydb_coordination_ut coordination/coordination_ut.cpp LINK_LIBRARIES yutil - YDB-CPP-SDK::Coordination + ydb-cpp-sdk::Coordination api-grpc LABELS unit @@ -14,8 +14,8 @@ add_ydb_test(NAME client-extensions-discovery_mutator_ut discovery_mutator/discovery_mutator_ut.cpp LINK_LIBRARIES yutil - YDB-CPP-SDK::DiscoveryMutator - YDB-CPP-SDK::Table + ydb-cpp-sdk::DiscoveryMutator + ydb-cpp-sdk::Table LABELS unit ) @@ -25,8 +25,8 @@ add_ydb_test(NAME client-ydb_driver_ut driver/driver_ut.cpp LINK_LIBRARIES yutil - YDB-CPP-SDK::Driver - YDB-CPP-SDK::Table + ydb-cpp-sdk::Driver + ydb-cpp-sdk::Table LABELS unit ) @@ -62,7 +62,7 @@ add_ydb_test(NAME client-ydb_params_ut GTEST params/params_ut.cpp LINK_LIBRARIES yutil - YDB-CPP-SDK::Params + ydb-cpp-sdk::Params LABELS unit ) @@ -72,8 +72,8 @@ add_ydb_test(NAME client-ydb_result_ut result/result_ut.cpp LINK_LIBRARIES yutil - YDB-CPP-SDK::Result - YDB-CPP-SDK::Params + ydb-cpp-sdk::Result + ydb-cpp-sdk::Params LABELS unit ) @@ -83,8 +83,8 @@ add_ydb_test(NAME client-ydb_value_ut GTEST value/value_ut.cpp LINK_LIBRARIES yutil - YDB-CPP-SDK::Value - YDB-CPP-SDK::Params + ydb-cpp-sdk::Value + ydb-cpp-sdk::Params LABELS unit ) From a0ea07226b9bce03b398d3989cb996b858cd806c Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Fri, 16 May 2025 14:20:46 +0000 Subject: [PATCH 03/11] ODBC MVP --- CMakeLists.txt | 2 +- c_api/CMakeLists.txt | 4 +- cmake/common.cmake | 8 +- cmake/external_libs.cmake | 4 + odbc/CMakeLists.txt | 32 +-- odbc/examples/CMakeLists.txt | 1 + odbc/examples/basic/CMakeLists.txt | 14 ++ odbc/examples/basic/main.cpp | 141 ++++++++++++ odbc/include/ydb_odbc.h | 89 -------- odbc/src/connection.c | 132 ----------- odbc/src/connection.cpp | 119 ++++++++++ odbc/src/connection.h | 53 +++++ odbc/src/descriptor.c | 109 --------- odbc/src/driver.c | 142 ------------ odbc/src/environment.cpp | 38 ++++ odbc/src/environment.h | 40 ++++ odbc/src/odbc_driver.cpp | 253 +++++++++++++++++++++ odbc/src/statement.c | 98 --------- odbc/src/statement.cpp | 342 +++++++++++++++++++++++++++++ odbc/src/statement.h | 72 ++++++ 20 files changed, 1099 insertions(+), 594 deletions(-) create mode 100644 odbc/examples/CMakeLists.txt create mode 100644 odbc/examples/basic/CMakeLists.txt create mode 100644 odbc/examples/basic/main.cpp delete mode 100644 odbc/include/ydb_odbc.h delete mode 100644 odbc/src/connection.c create mode 100644 odbc/src/connection.cpp create mode 100644 odbc/src/connection.h delete mode 100644 odbc/src/descriptor.c delete mode 100644 odbc/src/driver.c create mode 100644 odbc/src/environment.cpp create mode 100644 odbc/src/environment.h create mode 100644 odbc/src/odbc_driver.cpp delete mode 100644 odbc/src/statement.c create mode 100644 odbc/src/statement.cpp create mode 100644 odbc/src/statement.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b4bfc154e2..18a5e9f7e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,7 @@ add_subdirectory(c_api) #_ydb_sdk_validate_public_headers() if (YDB_SDK_ODBC) - #add_subdirectory(odbc) + add_subdirectory(odbc) endif() if (YDB_SDK_EXAMPLES) diff --git a/c_api/CMakeLists.txt b/c_api/CMakeLists.txt index 6f7a729539..b2c8fec22a 100644 --- a/c_api/CMakeLists.txt +++ b/c_api/CMakeLists.txt @@ -1,4 +1,6 @@ -add_library(ydb-c-api STATIC +_ydb_sdk_add_library(ydb-c-api SHARED) + +target_sources(ydb-c-api PRIVATE src/driver.cpp src/query.cpp src/result.cpp diff --git a/cmake/common.cmake b/cmake/common.cmake index 1193388349..5504479472 100644 --- a/cmake/common.cmake +++ b/cmake/common.cmake @@ -115,7 +115,7 @@ function(generate_enum_serilization Tgt Input) endfunction() function(add_global_library_for TgtName MainName) - add_library(${TgtName} STATIC ${ARGN}) + _ydb_sdk_add_library(${TgtName} STATIC ${ARGN}) if(APPLE) target_link_options(${MainName} INTERFACE "SHELL:-Wl,-force_load,$${TgtName}>") else() @@ -182,7 +182,7 @@ endfunction() function(_ydb_sdk_add_library Tgt) cmake_parse_arguments(ARG - "INTERFACE;OBJECT" "" "" + "INTERFACE;OBJECT;SHARED" "" "" ${ARGN} ) @@ -195,6 +195,9 @@ function(_ydb_sdk_add_library Tgt) if (ARG_OBJECT) set(libraryMode "OBJECT") endif() + if (ARG_SHARED) + set(libraryMode "SHARED") + endif() add_library(${Tgt} ${libraryMode}) target_include_directories(${Tgt} ${includeMode} $ @@ -204,6 +207,7 @@ function(_ydb_sdk_add_library Tgt) target_compile_definitions(${Tgt} ${includeMode} YDB_SDK_USE_STD_STRING ) + set_property(TARGET ${Tgt} PROPERTY POSITION_INDEPENDENT_CODE ON) endfunction() function(_ydb_sdk_validate_public_headers) diff --git a/cmake/external_libs.cmake b/cmake/external_libs.cmake index 22d0603e77..a252c588ae 100644 --- a/cmake/external_libs.cmake +++ b/cmake/external_libs.cmake @@ -14,6 +14,10 @@ find_package(Brotli 1.1.0 REQUIRED) find_package(jwt-cpp REQUIRED) find_package(double-conversion REQUIRED) +if (YDB_SDK_ODBC) + find_package(ODBC REQUIRED) +endif() + # RapidJSON if (YDB_SDK_USE_RAPID_JSON) find_package(RapidJSON REQUIRED) diff --git a/odbc/CMakeLists.txt b/odbc/CMakeLists.txt index b2a5b03b08..4674776832 100644 --- a/odbc/CMakeLists.txt +++ b/odbc/CMakeLists.txt @@ -1,41 +1,31 @@ -cmake_minimum_required(VERSION 3.14) -project(ydb-odbc VERSION 0.1.0 LANGUAGES C CXX) - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -# Находим зависимости -find_package(ODBC REQUIRED) - # Добавляем исходники add_library(ydb-odbc SHARED - src/driver.c - src/connection.c - src/statement.c - src/descriptor.c - src/client/driver.cpp - src/client/query.cpp + src/odbc_driver.cpp + src/connection.cpp + src/statement.cpp + src/environment.cpp ) # Добавляем заголовочные файлы target_include_directories(ydb-odbc - PUBLIC + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${ODBC_INCLUDE_DIRS} - /usr/include - /usr/local/include ) # Линкуем с YDB SDK и ODBC target_link_libraries(ydb-odbc - PUBLIC + PRIVATE ydb-cpp-sdk::Query ydb-cpp-sdk::Table ydb-cpp-sdk::Driver ODBC::ODBC ) +set_target_properties(ydb-odbc PROPERTIES + POSITION_INDEPENDENT_CODE ON +) + # Устанавливаем драйвер install(TARGETS ydb-odbc LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -46,6 +36,8 @@ install(DIRECTORY include/ DESTINATION include/ydb-odbc ) +add_subdirectory(examples) + # Добавляем тесты # add_subdirectory(tests) diff --git a/odbc/examples/CMakeLists.txt b/odbc/examples/CMakeLists.txt new file mode 100644 index 0000000000..6f4cd2f5a3 --- /dev/null +++ b/odbc/examples/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(basic) diff --git a/odbc/examples/basic/CMakeLists.txt b/odbc/examples/basic/CMakeLists.txt new file mode 100644 index 0000000000..a34cbd9301 --- /dev/null +++ b/odbc/examples/basic/CMakeLists.txt @@ -0,0 +1,14 @@ +add_executable(odbc_basic + main.cpp +) + +target_link_libraries(odbc_basic + PRIVATE + ODBC::ODBC +) +target_compile_definitions(odbc_basic + PRIVATE + ODBC_DRIVER_PATH="$" +) + +add_dependencies(odbc_basic ydb-odbc) diff --git a/odbc/examples/basic/main.cpp b/odbc/examples/basic/main.cpp new file mode 100644 index 0000000000..9b8123ef7c --- /dev/null +++ b/odbc/examples/basic/main.cpp @@ -0,0 +1,141 @@ +#include +#include + +#include +#include +#include +#include + +void PrintOdbcError(SQLSMALLINT handleType, SQLHANDLE handle) { + SQLCHAR sqlState[6] = {0}; + SQLINTEGER nativeError = 0; + SQLCHAR message[256] = {0}; + SQLSMALLINT textLength = 0; + SQLGetDiagRec(handleType, handle, 1, sqlState, &nativeError, message, sizeof(message), &textLength); + std::cerr << "ODBC error: [" << sqlState << "] " << message << std::endl; +} + +int main() { + SQLHENV henv = nullptr; + SQLHDBC hdbc = nullptr; + SQLHSTMT hstmt = nullptr; + SQLRETURN ret; + + std::cout << "1. Allocating environment handle" << std::endl; + ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv); + if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { + std::cerr << "Error allocating environment handle" << std::endl; + return 1; + } + SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + + std::cout << "2. Allocating connection handle" << std::endl; + ret = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); + if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { + std::cerr << "Error allocating connection handle" << std::endl; + SQLFreeHandle(SQL_HANDLE_ENV, henv); + return 1; + } + + std::cout << "3. Building connection string" << std::endl; + std::string connStr = "Driver=" ODBC_DRIVER_PATH ";Endpoint=localhost:2136;Database=/local;"; + SQLCHAR outConnStr[1024] = {0}; + SQLSMALLINT outConnStrLen = 0; + + std::cout << "4. Connecting with SQLDriverConnect" << std::endl; + ret = SQLDriverConnect(hdbc, NULL, (SQLCHAR*)connStr.c_str(), SQL_NTS, + outConnStr, sizeof(outConnStr), &outConnStrLen, SQL_DRIVER_COMPLETE); + if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { + std::cerr << "Error connecting with SQLDriverConnect" << std::endl; + PrintOdbcError(SQL_HANDLE_DBC, hdbc); + SQLFreeHandle(SQL_HANDLE_DBC, hdbc); + SQLFreeHandle(SQL_HANDLE_ENV, henv); + return 1; + } + + std::cout << "5. Allocating statement handle" << std::endl; + ret = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); + if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { + std::cerr << "Error allocating statement handle" << std::endl; + SQLDisconnect(hdbc); + SQLFreeHandle(SQL_HANDLE_DBC, hdbc); + SQLFreeHandle(SQL_HANDLE_ENV, henv); + return 1; + } + + std::cout << "6. Executing query" << std::endl; + SQLCHAR query[] = R"( + DECLARE $p1 AS Int64; + SELECT $p1 + 1, 'test1' as String; + SELECT $p1 + 2, 'test2' as String; + SELECT $p1 + 3, 'test3' as String; + SELECT $p1 + 4, 'test4' as String; + SELECT $p1 + 5, 'test5' as String; + SELECT $p1 + 6, 'test6' as String; + SELECT $p1 + 7, 'test7' as String; + SELECT $p1 + 8, 'test8' as String; + SELECT $p1 + 9, 'test9' as String; + )"; + + int64_t paramValue = 42; + SQLLEN paramInd = 0; + ret = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, 0, 0, ¶mValue, 0, ¶mInd); + if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { + std::cerr << "Error binding parameter" << std::endl; + PrintOdbcError(SQL_HANDLE_STMT, hstmt); + SQLFreeHandle(SQL_HANDLE_STMT, hstmt); + SQLDisconnect(hdbc); + SQLFreeHandle(SQL_HANDLE_DBC, hdbc); + SQLFreeHandle(SQL_HANDLE_ENV, henv); + return 1; + } + + ret = SQLExecDirect(hstmt, query, SQL_NTS); + if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { + std::cerr << "Error executing query" << std::endl; + PrintOdbcError(SQL_HANDLE_STMT, hstmt); + SQLFreeHandle(SQL_HANDLE_STMT, hstmt); + SQLDisconnect(hdbc); + SQLFreeHandle(SQL_HANDLE_DBC, hdbc); + SQLFreeHandle(SQL_HANDLE_ENV, henv); + return 1; + } + + std::cout << "7. Fetching result" << std::endl; + + SQLLEN ind = 0; + int value1 = 0; + if (SQLBindCol(hstmt, 1, SQL_C_SLONG, &value1, 0, &ind) != SQL_SUCCESS) { + std::cerr << "Error binding column 1" << std::endl; + PrintOdbcError(SQL_HANDLE_STMT, hstmt); + return 1; + } + + SQLCHAR value2[1024] = {0}; + if (SQLBindCol(hstmt, 2, SQL_C_CHAR, &value2, 1024, &ind) != SQL_SUCCESS) { + std::cerr << "Error binding column 2" << std::endl; + PrintOdbcError(SQL_HANDLE_STMT, hstmt); + return 1; + } + + while ((ret = SQLFetch(hstmt)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { + if (ret != SQL_SUCCESS) { + std::cerr << "Error fetching result" << std::endl; + PrintOdbcError(SQL_HANDLE_STMT, hstmt); + return 1; + } + + std::cout << "Result column 1: " << value1 << std::endl; + std::cout << "Result column 2: " << value2 << std::endl; + + std::cout << "--------------------------------" << std::endl; + } + + std::cout << "8. Cleaning up" << std::endl; + SQLFreeHandle(SQL_HANDLE_STMT, hstmt); + SQLDisconnect(hdbc); + SQLFreeHandle(SQL_HANDLE_DBC, hdbc); + SQLFreeHandle(SQL_HANDLE_ENV, henv); + + return 0; +} diff --git a/odbc/include/ydb_odbc.h b/odbc/include/ydb_odbc.h deleted file mode 100644 index 2415f80f55..0000000000 --- a/odbc/include/ydb_odbc.h +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// Структура для хранения информации о драйвере -typedef struct { - char name[256]; - char version[64]; - char description[1024]; -} YDB_DRIVER_INFO; - -// Структура для хранения состояния соединения -typedef struct { - void* ydb_driver; - void* query_client; - int connected; -} YDB_CONNECTION; - -// Структура для хранения состояния оператора -typedef struct { - YDB_CONNECTION* connection; - void* query_client; - void* result; - size_t current_row; -} YDB_STATEMENT; - -// Структура для хранения дескриптора -typedef struct { - void** descriptors; - size_t descriptors_size; -} YDB_DESCRIPTOR; - -// Функции драйвера -SQLRETURN YDB_SQLGetInfo(SQLSMALLINT InfoType, SQLPOINTER InfoValue, - SQLSMALLINT BufferLength, SQLSMALLINT* StringLength); - -SQLRETURN YDB_SQLConnect(SQLHDBC ConnectionHandle, SQLCHAR* ServerName, - SQLSMALLINT NameLength1, SQLCHAR* UserName, - SQLSMALLINT NameLength2, SQLCHAR* Authentication, - SQLSMALLINT NameLength3); - -SQLRETURN YDB_SQLDriverConnect(SQLHDBC ConnectionHandle, SQLHWND WindowHandle, - SQLCHAR* InConnectionString, SQLSMALLINT StringLength1, - SQLCHAR* OutConnectionString, SQLSMALLINT BufferLength, - SQLSMALLINT* StringLength2, SQLUSMALLINT DriverCompletion); - -// Функции соединения -SQLRETURN YDB_SQLDisconnect(SQLHDBC ConnectionHandle); - -SQLRETURN YDB_SQLGetConnectionInfo(SQLHDBC ConnectionHandle, SQLSMALLINT InfoType, - SQLPOINTER InfoValue, SQLSMALLINT BufferLength, - SQLSMALLINT* StringLength); - -SQLRETURN YDB_SQLAllocHandle(SQLSMALLINT HandleType, SQLHANDLE InputHandle, - SQLHANDLE* OutputHandle); - -SQLRETURN YDB_SQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle); - -// Функции оператора -SQLRETURN YDB_SQLExecDirect(SQLHSTMT StatementHandle, SQLCHAR* StatementText, - SQLINTEGER TextLength); - -SQLRETURN YDB_SQLPrepare(SQLHSTMT StatementHandle, SQLCHAR* StatementText, - SQLINTEGER TextLength); - -SQLRETURN YDB_SQLExecute(SQLHSTMT StatementHandle); - -SQLRETURN YDB_SQLFetch(SQLHSTMT StatementHandle); - -SQLRETURN YDB_SQLCloseCursor(SQLHSTMT StatementHandle); - -// Функции дескриптора -SQLRETURN YDB_SQLGetDescField(SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, - SQLSMALLINT FieldIdentifier, SQLPOINTER Value, - SQLINTEGER BufferLength, SQLINTEGER* StringLength); - -SQLRETURN YDB_SQLSetDescField(SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, - SQLSMALLINT FieldIdentifier, SQLPOINTER Value, - SQLINTEGER BufferLength); - -#ifdef __cplusplus -} -#endif diff --git a/odbc/src/connection.c b/odbc/src/connection.c deleted file mode 100644 index d95dc8ade7..0000000000 --- a/odbc/src/connection.c +++ /dev/null @@ -1,132 +0,0 @@ -#include "ydb_odbc.h" -#include "client/driver.h" -#include "client/query.h" - -#include -#include - -SQLRETURN YDB_SQLDisconnect(SQLHDBC ConnectionHandle) { - YDB_CONNECTION* conn = (YDB_CONNECTION*)ConnectionHandle; - if (!conn) { - return SQL_ERROR; - } - - if (conn->connected) { - if (conn->query_client) { - YDB_DestroyQueryClient(conn->query_client); - conn->query_client = NULL; - } - - if (conn->ydb_driver) { - YDB_DestroyDriver(conn->ydb_driver); - conn->ydb_driver = NULL; - } - - conn->connected = 0; - } - - return SQL_SUCCESS; -} - -SQLRETURN YDB_SQLGetConnectionInfo(SQLHDBC ConnectionHandle, SQLSMALLINT InfoType, - SQLPOINTER InfoValue, SQLSMALLINT BufferLength, - SQLSMALLINT* StringLength) { - YDB_CONNECTION* conn = (YDB_CONNECTION*)ConnectionHandle; - if (!conn || !conn->connected) { - return SQL_ERROR; - } - - switch (InfoType) { - case SQL_DATABASE_NAME: - if (InfoValue && BufferLength > 0) { - const char* dbName = "YDB"; - strncpy((char*)InfoValue, dbName, BufferLength - 1); - if (StringLength) { - *StringLength = strlen(dbName); - } - return SQL_SUCCESS; - } - break; - - case SQL_SERVER_NAME: - if (InfoValue && BufferLength > 0) { - const char* serverName = "Yandex Database"; - strncpy((char*)InfoValue, serverName, BufferLength - 1); - if (StringLength) { - *StringLength = strlen(serverName); - } - return SQL_SUCCESS; - } - break; - } - - return SQL_ERROR; -} - -SQLRETURN YDB_SQLAllocHandle(SQLSMALLINT HandleType, SQLHANDLE InputHandle, - SQLHANDLE* OutputHandle) { - if (!OutputHandle) { - return SQL_ERROR; - } - - switch (HandleType) { - case SQL_HANDLE_DBC: - *OutputHandle = calloc(1, sizeof(YDB_CONNECTION)); - return SQL_SUCCESS; - - case SQL_HANDLE_STMT: - *OutputHandle = calloc(1, sizeof(YDB_STATEMENT)); - return SQL_SUCCESS; - - case SQL_HANDLE_DESC: - *OutputHandle = calloc(1, sizeof(YDB_DESCRIPTOR)); - return SQL_SUCCESS; - - default: - return SQL_ERROR; - } -} - -SQLRETURN YDB_SQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle) { - if (!Handle) { - return SQL_ERROR; - } - - switch (HandleType) { - case SQL_HANDLE_DBC: - { - YDB_CONNECTION* conn = (YDB_CONNECTION*)Handle; - if (conn->connected) { - YDB_SQLDisconnect((SQLHDBC)conn); - } - free(conn); - } - return SQL_SUCCESS; - - case SQL_HANDLE_STMT: - { - YDB_STATEMENT* stmt = (YDB_STATEMENT*)Handle; - if (stmt->result) { - YDB_FreeExecuteQueryResult(stmt->result); - } - if (stmt->query_client) { - YDB_DestroyQueryClient(stmt->query_client); - } - free(stmt); - } - return SQL_SUCCESS; - - case SQL_HANDLE_DESC: - { - YDB_DESCRIPTOR* desc = (YDB_DESCRIPTOR*)Handle; - if (desc->descriptors) { - free(desc->descriptors); - } - free(desc); - } - return SQL_SUCCESS; - - default: - return SQL_ERROR; - } -} \ No newline at end of file diff --git a/odbc/src/connection.cpp b/odbc/src/connection.cpp new file mode 100644 index 0000000000..427b03f7ba --- /dev/null +++ b/odbc/src/connection.cpp @@ -0,0 +1,119 @@ +#include "connection.h" +#include "statement.h" +#include +#include +#include +#include +#include + +#include + +namespace NYdb { +namespace NOdbc { + +SQLRETURN TConnection::DriverConnect(const std::string& connectionString) { + // Парсим параметры + std::map params; + size_t pos = 0; + while (pos < connectionString.size()) { + size_t eq = connectionString.find('=', pos); + if (eq == std::string::npos) { + break; + } + + size_t sc = connectionString.find(';', eq); + std::string key = connectionString.substr(pos, eq-pos); + std::string val = connectionString.substr(eq+1, (sc == std::string::npos ? std::string::npos : sc-eq-1)); + params[key] = val; + if (sc == std::string::npos) { + break; + } + pos = sc+1; + } + Endpoint_ = params["Endpoint"]; + Database_ = params["Database"]; + + if (Endpoint_.empty() || Database_.empty()) { + AddError("08001", 0, "Missing Endpoint or Database in connection string"); + return SQL_ERROR; + } + + YdbDriver_ = std::make_unique(NYdb::TDriverConfig() + .SetEndpoint(Endpoint_) + .SetDatabase(Database_)); + + YdbClient_ = std::make_unique(*YdbDriver_); + + return SQL_SUCCESS; +} + +SQLRETURN TConnection::Connect(const std::string& serverName, + const std::string& userName, + const std::string& auth) { + // Получаем параметры из секции DSN через Driver Manager API + char endpoint[256] = {0}; + char database[256] = {0}; + + SQLGetPrivateProfileString(serverName.c_str(), "Endpoint", "", endpoint, sizeof(endpoint), nullptr); + SQLGetPrivateProfileString(serverName.c_str(), "Database", "", database, sizeof(database), nullptr); + + Endpoint_ = endpoint; + Database_ = database; + + if (Endpoint_.empty() || Database_.empty()) { + AddError("08001", 0, "Missing Endpoint or Database in DSN"); + return SQL_ERROR; + } + + YdbDriver_ = std::make_unique(NYdb::TDriverConfig() + .SetEndpoint(Endpoint_) + .SetDatabase(Database_)); + + YdbClient_ = std::make_unique(*YdbDriver_); + + return SQL_SUCCESS; +} + +SQLRETURN TConnection::Disconnect() { + YdbClient_.reset(); + YdbDriver_.reset(); + return SQL_SUCCESS; +} + +SQLRETURN TConnection::GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, + SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength) { + if (recNumber < 1 || recNumber > (SQLSMALLINT)Errors_.size()) return SQL_NO_DATA; + const auto& err = Errors_[recNumber-1]; + if (sqlState) strncpy((char*)sqlState, err.SqlState.c_str(), 6); + if (nativeError) *nativeError = err.NativeError; + if (messageText && bufferLength > 0) { + strncpy((char*)messageText, err.Message.c_str(), bufferLength); + if (textLength) *textLength = (SQLSMALLINT)std::min((int)err.Message.size(), (int)bufferLength); + } + return SQL_SUCCESS; +} + +std::unique_ptr TConnection::CreateStatement() { + return std::make_unique(this); +} + +void TConnection::RemoveStatement(TStatement* stmt) { + Statements_.erase(std::remove_if(Statements_.begin(), Statements_.end(), + [stmt](const std::unique_ptr& s) { return s.get() == stmt; }), Statements_.end()); +} + +void TConnection::AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message) { + Errors_.push_back({sqlState, nativeError, message}); +} + +void TConnection::ClearErrors() { + Errors_.clear(); +} + +std::pair TConnection::ParseConnectionString(const std::string& connectionString) { + // Заглушка + return {"", ""}; +} + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/connection.h b/odbc/src/connection.h new file mode 100644 index 0000000000..cceadc2433 --- /dev/null +++ b/odbc/src/connection.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +#include "environment.h" + +namespace NYdb { +namespace NOdbc { + +class TStatement; + +class TConnection { +private: + std::unique_ptr YdbDriver_; + std::unique_ptr YdbClient_; + + TErrorList Errors_; + std::vector> Statements_; + std::string Endpoint_; + std::string Database_; + std::string AuthToken_; + +public: + SQLRETURN Connect(const std::string& serverName, + const std::string& userName, + const std::string& auth); + + SQLRETURN DriverConnect(const std::string& connectionString); + SQLRETURN Disconnect(); + SQLRETURN GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, + SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength); + + std::unique_ptr CreateStatement(); + void RemoveStatement(TStatement* stmt); + + NYdb::NQuery::TQueryClient* GetClient() { return YdbClient_.get(); } + + void AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message); + void ClearErrors(); + +private: + std::pair ParseConnectionString(const std::string& connectionString); +}; + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/descriptor.c b/odbc/src/descriptor.c deleted file mode 100644 index 15380bf40a..0000000000 --- a/odbc/src/descriptor.c +++ /dev/null @@ -1,109 +0,0 @@ -#include "ydb_odbc.h" -#include -#include - -// Структура для хранения поля дескриптора -typedef struct { - SQLSMALLINT field_identifier; - char value[1024]; -} YDB_DESCRIPTOR_FIELD; - -// Структура для хранения записи дескриптора -typedef struct { - YDB_DESCRIPTOR_FIELD* fields; - size_t fields_count; -} YDB_DESCRIPTOR_RECORD; - -SQLRETURN YDB_SQLGetDescField(SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, - SQLSMALLINT FieldIdentifier, SQLPOINTER Value, - SQLINTEGER BufferLength, SQLINTEGER* StringLength) { - YDB_DESCRIPTOR* desc = (YDB_DESCRIPTOR*)DescriptorHandle; - if (!desc || !desc->descriptors || RecNumber < 1 || RecNumber > desc->descriptors_size) { - return SQL_ERROR; - } - - YDB_DESCRIPTOR_RECORD* record = (YDB_DESCRIPTOR_RECORD*)desc->descriptors[RecNumber - 1]; - if (!record) { - return SQL_ERROR; - } - - for (size_t i = 0; i < record->fields_count; i++) { - if (record->fields[i].field_identifier == FieldIdentifier) { - if (Value && BufferLength > 0) { - strncpy((char*)Value, record->fields[i].value, BufferLength - 1); - if (StringLength) { - *StringLength = strlen(record->fields[i].value); - } - return SQL_SUCCESS; - } - break; - } - } - - return SQL_ERROR; -} - -SQLRETURN YDB_SQLSetDescField(SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, - SQLSMALLINT FieldIdentifier, SQLPOINTER Value, - SQLINTEGER BufferLength) { - YDB_DESCRIPTOR* desc = (YDB_DESCRIPTOR*)DescriptorHandle; - if (!desc || RecNumber < 1) { - return SQL_ERROR; - } - - // Увеличиваем размер массива дескрипторов, если нужно - if (RecNumber > desc->descriptors_size) { - void** new_descriptors = realloc(desc->descriptors, RecNumber * sizeof(void*)); - if (!new_descriptors) { - return SQL_ERROR; - } - - // Инициализируем новые записи - for (size_t i = desc->descriptors_size; i < RecNumber; i++) { - YDB_DESCRIPTOR_RECORD* record = calloc(1, sizeof(YDB_DESCRIPTOR_RECORD)); - if (!record) { - // Освобождаем память в случае ошибки - for (size_t j = desc->descriptors_size; j < i; j++) { - free(new_descriptors[j]); - } - free(new_descriptors); - return SQL_ERROR; - } - new_descriptors[i] = record; - } - - desc->descriptors = new_descriptors; - desc->descriptors_size = RecNumber; - } - - YDB_DESCRIPTOR_RECORD* record = (YDB_DESCRIPTOR_RECORD*)desc->descriptors[RecNumber - 1]; - if (!record) { - record = calloc(1, sizeof(YDB_DESCRIPTOR_RECORD)); - if (!record) { - return SQL_ERROR; - } - desc->descriptors[RecNumber - 1] = record; - } - - // Проверяем, существует ли уже поле с таким идентификатором - for (size_t i = 0; i < record->fields_count; i++) { - if (record->fields[i].field_identifier == FieldIdentifier) { - // Обновляем значение - strncpy(record->fields[i].value, (char*)Value, sizeof(record->fields[i].value) - 1); - return SQL_SUCCESS; - } - } - - // Добавляем новое поле - YDB_DESCRIPTOR_FIELD* new_fields = realloc(record->fields, (record->fields_count + 1) * sizeof(YDB_DESCRIPTOR_FIELD)); - if (!new_fields) { - return SQL_ERROR; - } - - record->fields = new_fields; - record->fields[record->fields_count].field_identifier = FieldIdentifier; - strncpy(record->fields[record->fields_count].value, (char*)Value, sizeof(record->fields[record->fields_count].value) - 1); - record->fields_count++; - - return SQL_SUCCESS; -} \ No newline at end of file diff --git a/odbc/src/driver.c b/odbc/src/driver.c deleted file mode 100644 index 5fc19f7294..0000000000 --- a/odbc/src/driver.c +++ /dev/null @@ -1,142 +0,0 @@ -#include "ydb_odbc.h" -#include -#include - -// Глобальные переменные для хранения состояния -static YDB_DRIVER_INFO driver_info = { - .name = "YDB ODBC Driver", - .version = "1.0.0", - .description = "ODBC driver for Yandex Database" -}; - -SQLRETURN YDB_SQLGetInfo(SQLSMALLINT InfoType, SQLPOINTER InfoValue, - SQLSMALLINT BufferLength, SQLSMALLINT* StringLength) { - switch (InfoType) { - case SQL_DRIVER_NAME: - if (InfoValue && BufferLength > 0) { - strncpy(InfoValue, driver_info.name, BufferLength - 1); - if (StringLength) { - *StringLength = strlen(driver_info.name); - } - return SQL_SUCCESS; - } - break; - - case SQL_DRIVER_VER: - if (InfoValue && BufferLength > 0) { - strncpy(InfoValue, driver_info.version, BufferLength - 1); - if (StringLength) { - *StringLength = strlen(driver_info.version); - } - return SQL_SUCCESS; - } - break; - } - - return SQL_ERROR; -} - -SQLRETURN YDB_SQLConnect(SQLHDBC ConnectionHandle, SQLCHAR* ServerName, - SQLSMALLINT NameLength1, SQLCHAR* UserName, - SQLSMALLINT NameLength2, SQLCHAR* Authentication, - SQLSMALLINT NameLength3) { - YDB_CONNECTION* conn = (YDB_CONNECTION*)ConnectionHandle; - if (!conn) { - return SQL_ERROR; - } - - // TODO: Реализовать подключение к YDB через C++ SDK - // Здесь нужно будет использовать C++ код через extern "C" функции - - return SQL_SUCCESS; -} - -SQLRETURN YDB_SQLDriverConnect(SQLHDBC ConnectionHandle, SQLHWND WindowHandle, - SQLCHAR* InConnectionString, SQLSMALLINT StringLength1, - SQLCHAR* OutConnectionString, SQLSMALLINT BufferLength, - SQLSMALLINT* StringLength2, SQLUSMALLINT DriverCompletion) { - // TODO: Реализовать парсинг строки подключения - return SQL_ERROR; -} - -SQLRETURN YDB_SQLDisconnect(SQLHDBC ConnectionHandle) { - YDB_CONNECTION* conn = (YDB_CONNECTION*)ConnectionHandle; - if (!conn) { - return SQL_ERROR; - } - - // TODO: Реализовать отключение от YDB - - return SQL_SUCCESS; -} - -SQLRETURN YDB_SQLGetConnectionInfo(SQLHDBC ConnectionHandle, SQLSMALLINT InfoType, - SQLPOINTER InfoValue, SQLSMALLINT BufferLength, - SQLSMALLINT* StringLength) { - YDB_CONNECTION* conn = (YDB_CONNECTION*)ConnectionHandle; - if (!conn || !conn->connected) { - return SQL_ERROR; - } - - switch (InfoType) { - case SQL_DATABASE_NAME: - if (InfoValue && BufferLength > 0) { - const char* dbName = "YDB"; - strncpy(InfoValue, dbName, BufferLength - 1); - if (StringLength) { - *StringLength = strlen(dbName); - } - return SQL_SUCCESS; - } - break; - } - - return SQL_ERROR; -} - -SQLRETURN YDB_SQLAllocHandle(SQLSMALLINT HandleType, SQLHANDLE InputHandle, - SQLHANDLE* OutputHandle) { - if (!OutputHandle) { - return SQL_ERROR; - } - - switch (HandleType) { - case SQL_HANDLE_DBC: - *OutputHandle = calloc(1, sizeof(YDB_CONNECTION)); - return SQL_SUCCESS; - - case SQL_HANDLE_STMT: - *OutputHandle = calloc(1, sizeof(YDB_STATEMENT)); - return SQL_SUCCESS; - - case SQL_HANDLE_DESC: - *OutputHandle = calloc(1, sizeof(YDB_DESCRIPTOR)); - return SQL_SUCCESS; - - default: - return SQL_ERROR; - } -} - -SQLRETURN YDB_SQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle) { - if (!Handle) { - return SQL_ERROR; - } - - switch (HandleType) { - case SQL_HANDLE_DBC: - free(Handle); - return SQL_SUCCESS; - - case SQL_HANDLE_STMT: - free(Handle); - return SQL_SUCCESS; - - case SQL_HANDLE_DESC: - free(Handle); - return SQL_SUCCESS; - - default: - return SQL_ERROR; - } -} \ No newline at end of file diff --git a/odbc/src/environment.cpp b/odbc/src/environment.cpp new file mode 100644 index 0000000000..0e1eef594d --- /dev/null +++ b/odbc/src/environment.cpp @@ -0,0 +1,38 @@ +#include "environment.h" +#include "connection.h" + +namespace NYdb { +namespace NOdbc { + +TEnvironment::TEnvironment() : OdbcVersion_(SQL_OV_ODBC3) {} +TEnvironment::~TEnvironment() {} + +SQLRETURN TEnvironment::SetAttribute(SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER stringLength) { + // TODO: реализовать обработку атрибутов + OdbcVersion_ = attribute == SQL_ATTR_ODBC_VERSION ? reinterpret_cast(value) : 0; + return SQL_SUCCESS; +} + +SQLRETURN TEnvironment::GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength) { + // Заглушка + if (recNumber < 1 || recNumber > (SQLSMALLINT)Errors_.size()) return SQL_NO_DATA; + const auto& err = Errors_[recNumber-1]; + if (sqlState) strncpy((char*)sqlState, err.SqlState.c_str(), 6); + if (nativeError) *nativeError = err.NativeError; + if (messageText && bufferLength > 0) { + strncpy((char*)messageText, err.Message.c_str(), bufferLength); + if (textLength) *textLength = (SQLSMALLINT)std::min((int)err.Message.size(), (int)bufferLength); + } + return SQL_SUCCESS; +} + +void TEnvironment::AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message) { + Errors_.push_back({sqlState, nativeError, message}); +} + +void TEnvironment::ClearErrors() { + Errors_.clear(); +} + +} // namespace NOdbc +} // namespace NYdb \ No newline at end of file diff --git a/odbc/src/environment.h b/odbc/src/environment.h new file mode 100644 index 0000000000..a45d7f0b7e --- /dev/null +++ b/odbc/src/environment.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include +#include + +namespace NYdb { +namespace NOdbc { + +class TConnection; + +struct TErrorInfo { + std::string SqlState; + SQLINTEGER NativeError; + std::string Message; +}; + +using TErrorList = std::vector; + +class TEnvironment { +private: + SQLINTEGER OdbcVersion_; + TErrorList Errors_; + +public: + TEnvironment(); + ~TEnvironment(); + + SQLRETURN SetAttribute(SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER stringLength); + SQLRETURN GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, + SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength); + + void AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message); + void ClearErrors(); +}; + +} // namespace NOdbc +} // namespace NYdb \ No newline at end of file diff --git a/odbc/src/odbc_driver.cpp b/odbc/src/odbc_driver.cpp new file mode 100644 index 0000000000..6f3b865035 --- /dev/null +++ b/odbc/src/odbc_driver.cpp @@ -0,0 +1,253 @@ +#include "environment.h" +#include "connection.h" +#include "statement.h" + +#include +#include + +namespace { + std::string GetString(SQLCHAR* str, SQLSMALLINT length) { + if (length == SQL_NTS) { + return std::string(reinterpret_cast(str)); + } + return std::string(reinterpret_cast(str), length); + } +} + +extern "C" { + +SQLRETURN SQL_API SQLAllocHandle(SQLSMALLINT handleType, + SQLHANDLE inputHandle, + SQLHANDLE* outputHandle) { + if (!outputHandle) { + return SQL_INVALID_HANDLE; + } + + try { + switch (handleType) { + case SQL_HANDLE_ENV: { + if (inputHandle != SQL_NULL_HANDLE) { + return SQL_INVALID_HANDLE; + } + + *outputHandle = new NYdb::NOdbc::TEnvironment(); + return SQL_SUCCESS; + } + + case SQL_HANDLE_DBC: { + if (!inputHandle) { + return SQL_INVALID_HANDLE; + } + + *outputHandle = new NYdb::NOdbc::TConnection(); + return SQL_SUCCESS; + } + + case SQL_HANDLE_STMT: { + auto conn = static_cast(inputHandle); + if (!conn) { + return SQL_INVALID_HANDLE; + } + auto stmt = conn->CreateStatement(); + *outputHandle = stmt.release(); + return SQL_SUCCESS; + } + + default: + return SQL_ERROR; + } + } catch (...) { + return SQL_ERROR; + } +} + +SQLRETURN SQL_API SQLFreeHandle(SQLSMALLINT handleType, SQLHANDLE handle) { + if (!handle) { + return SQL_INVALID_HANDLE; + } + + try { + switch (handleType) { + case SQL_HANDLE_ENV: { + auto env = static_cast(handle); + delete env; + return SQL_SUCCESS; + } + + case SQL_HANDLE_DBC: { + auto conn = static_cast(handle); + delete conn; + return SQL_SUCCESS; + } + + case SQL_HANDLE_STMT: { + auto stmt = static_cast(handle); + if (stmt->GetConnection()) { + stmt->GetConnection()->RemoveStatement(stmt); + } + delete stmt; + return SQL_SUCCESS; + } + + default: + return SQL_ERROR; + } + } catch (...) { + return SQL_ERROR; + } +} + +SQLRETURN SQL_API SQLSetEnvAttr(SQLHENV environmentHandle, + SQLINTEGER attribute, + SQLPOINTER value, + SQLINTEGER stringLength) { + auto env = static_cast(environmentHandle); + if (!env) { + return SQL_INVALID_HANDLE; + } + + return env->SetAttribute(attribute, value, stringLength); +} + +SQLRETURN SQL_API SQLDriverConnect(SQLHDBC connectionHandle, + SQLHWND /*WindowHandle*/, + SQLCHAR* inConnectionString, + SQLSMALLINT stringLength1, + SQLCHAR* /*outConnectionString*/, + SQLSMALLINT /*bufferLength*/, + SQLSMALLINT* /*stringLength2Ptr*/, + SQLUSMALLINT /*driverCompletion*/) { + auto conn = static_cast(connectionHandle); + if (!conn) { + return SQL_INVALID_HANDLE; + } + + return conn->DriverConnect(GetString(inConnectionString, stringLength1)); +} + +SQLRETURN SQL_API SQLConnect(SQLHDBC connectionHandle, + SQLCHAR* serverName, SQLSMALLINT nameLength1, + SQLCHAR* userName, SQLSMALLINT nameLength2, + SQLCHAR* authentication, SQLSMALLINT nameLength3) { + auto conn = static_cast(connectionHandle); + if (!conn) { + return SQL_INVALID_HANDLE; + } + + return conn->Connect(GetString(serverName, nameLength1), + GetString(userName, nameLength2), + GetString(authentication, nameLength3)); +} + +SQLRETURN SQL_API SQLDisconnect(SQLHDBC connectionHandle) { + auto conn = static_cast(connectionHandle); + if (!conn) { + return SQL_INVALID_HANDLE; + } + + return conn->Disconnect(); +} + +SQLRETURN SQL_API SQLExecDirect(SQLHSTMT statementHandle, + SQLCHAR* statementText, + SQLINTEGER textLength) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + + return stmt->ExecDirect(GetString(statementText, textLength)); +} + +SQLRETURN SQL_API SQLFetch(SQLHSTMT statementHandle) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + + return stmt->Fetch(); +} + +SQLRETURN SQL_API SQLGetData(SQLHSTMT statementHandle, + SQLUSMALLINT columnNumber, + SQLSMALLINT targetType, + SQLPOINTER targetValue, + SQLLEN bufferLength, + SQLLEN* strLenOrInd) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + + return stmt->GetData(columnNumber, targetType, targetValue, bufferLength, strLenOrInd); +} + +SQLRETURN SQL_API SQLBindCol(SQLHSTMT statementHandle, + SQLUSMALLINT columnNumber, + SQLSMALLINT targetType, + SQLPOINTER targetValue, + SQLLEN bufferLength, + SQLLEN* strLenOrInd) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + return stmt->BindCol(columnNumber, targetType, targetValue, bufferLength, strLenOrInd); +} + +SQLRETURN SQL_API SQLGetDiagRec(SQLSMALLINT handleType, + SQLHANDLE handle, + SQLSMALLINT recNumber, + SQLCHAR* sqlState, + SQLINTEGER* nativeError, + SQLCHAR* messageText, + SQLSMALLINT bufferLength, + SQLSMALLINT* textLength) { + if (!handle) { + return SQL_INVALID_HANDLE; + } + + try { + switch (handleType) { + case SQL_HANDLE_ENV: { + auto env = static_cast(handle); + return env->GetDiagRec(recNumber, sqlState, nativeError, messageText, bufferLength, textLength); + } + + case SQL_HANDLE_DBC: { + auto conn = static_cast(handle); + return conn->GetDiagRec(recNumber, sqlState, nativeError, messageText, bufferLength, textLength); + } + + case SQL_HANDLE_STMT: { + auto stmt = static_cast(handle); + return stmt->GetDiagRec(recNumber, sqlState, nativeError, messageText, bufferLength, textLength); + } + + default: + return SQL_ERROR; + } + } catch (...) { + return SQL_ERROR; + } +} + +SQLRETURN SQL_API SQLBindParameter(SQLHSTMT statementHandle, + SQLUSMALLINT paramNumber, + SQLSMALLINT inputOutputType, + SQLSMALLINT valueType, + SQLSMALLINT parameterType, + SQLULEN columnSize, + SQLSMALLINT decimalDigits, + SQLPOINTER parameterValuePtr, + SQLLEN bufferLength, + SQLLEN* strLenOrIndPtr) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + + return stmt->BindParameter(paramNumber, inputOutputType, valueType, parameterType, columnSize, decimalDigits, parameterValuePtr, bufferLength, strLenOrIndPtr); +} + +} diff --git a/odbc/src/statement.c b/odbc/src/statement.c deleted file mode 100644 index b9a7a5ffcd..0000000000 --- a/odbc/src/statement.c +++ /dev/null @@ -1,98 +0,0 @@ -#include "ydb_odbc.h" -#include "client/query.h" -#include -#include - -SQLRETURN YDB_SQLExecDirect(SQLHSTMT StatementHandle, SQLCHAR* StatementText, - SQLINTEGER TextLength) { - YDB_STATEMENT* stmt = (YDB_STATEMENT*)StatementHandle; - if (!stmt || !stmt->connection || !stmt->connection->connected || !StatementText) { - return SQL_ERROR; - } - - // Если текст запроса не указан, используем длину строки - if (TextLength == SQL_NTS) { - TextLength = strlen((char*)StatementText); - } - - // Создаем клиент запросов, если его еще нет - if (!stmt->query_client) { - stmt->query_client = YDB_CreateQueryClient(stmt->connection->ydb_driver); - if (!stmt->query_client) { - return SQL_ERROR; - } - } - - // Освобождаем предыдущий результат, если он есть - if (stmt->result) { - YDB_FreeExecuteQueryResult(stmt->result); - stmt->result = NULL; - } - - // Выполняем запрос - if (!YDB_ExecuteQuery(stmt->query_client, (char*)StatementText, &stmt->result)) { - return SQL_ERROR; - } - - stmt->current_row = 0; - return SQL_SUCCESS; -} - -SQLRETURN YDB_SQLPrepare(SQLHSTMT StatementHandle, SQLCHAR* StatementText, - SQLINTEGER TextLength) { - // YDB не требует предварительной подготовки запросов - // Просто сохраняем текст запроса для последующего выполнения - YDB_STATEMENT* stmt = (YDB_STATEMENT*)StatementHandle; - if (!stmt || !StatementText) { - return SQL_ERROR; - } - - // Если текст запроса не указан, используем длину строки - if (TextLength == SQL_NTS) { - TextLength = strlen((char*)StatementText); - } - - // TODO: Сохранить текст запроса для последующего выполнения - - return SQL_SUCCESS; -} - -SQLRETURN YDB_SQLExecute(SQLHSTMT StatementHandle) { - // В YDB все запросы выполняются сразу - // Эта функция просто вызывает SQLExecDirect с сохраненным текстом запроса - YDB_STATEMENT* stmt = (YDB_STATEMENT*)StatementHandle; - if (!stmt) { - return SQL_ERROR; - } - - // TODO: Выполнить сохраненный запрос - - return SQL_SUCCESS; -} - -SQLRETURN YDB_SQLFetch(SQLHSTMT StatementHandle) { - YDB_STATEMENT* stmt = (YDB_STATEMENT*)StatementHandle; - if (!stmt || !stmt->result) { - return SQL_ERROR; - } - - // TODO: Преобразовать данные текущей строки в формат ODBC - - stmt->current_row++; - return SQL_SUCCESS; -} - -SQLRETURN YDB_SQLCloseCursor(SQLHSTMT StatementHandle) { - YDB_STATEMENT* stmt = (YDB_STATEMENT*)StatementHandle; - if (!stmt) { - return SQL_ERROR; - } - - if (stmt->result) { - YDB_FreeExecuteQueryResult(stmt->result); - stmt->result = NULL; - } - - stmt->current_row = 0; - return SQL_SUCCESS; -} \ No newline at end of file diff --git a/odbc/src/statement.cpp b/odbc/src/statement.cpp new file mode 100644 index 0000000000..224cc8dc12 --- /dev/null +++ b/odbc/src/statement.cpp @@ -0,0 +1,342 @@ +#include "statement.h" + +#include +#include +#include + +namespace NYdb { +namespace NOdbc { + +TStatement::TStatement(TConnection* conn) + : Conn_(conn) {} + +SQLRETURN TStatement::ExecDirect(const std::string& statementText) { + ClearStatement(); + + auto* client = Conn_->GetClient(); + if (!client) { + return SQL_ERROR; + } + + NYdb::TParams params = BuildParams(); + if (!Errors_.empty()) { + return SQL_ERROR; + } + // --- конец сборки параметров --- + + auto sessionResult = client->GetSession().ExtractValueSync(); + if (!sessionResult.IsSuccess()) { + return SQL_ERROR; + } + + auto session = sessionResult.GetSession(); + + auto iterator = session.StreamExecuteQuery(statementText, NYdb::NQuery::TTxControl::NoTx(), params).ExtractValueSync(); + if (!iterator.IsSuccess()) { + return SQL_ERROR; + } + + Iterator_ = std::make_unique(std::move(iterator)); + + return SQL_SUCCESS; +} + +SQLRETURN TStatement::Fetch() { + if (!Iterator_) { + ClearStatement(); + return SQL_NO_DATA; + } + + while (true) { + if (ResultSetParser_) { + if (ResultSetParser_->TryNextRow()) { + // Автоматически заполняем связанные буферы + for (const auto& col : BoundColumns_) { + GetData(col.ColumnNumber, col.TargetType, col.TargetValue, col.BufferLength, col.StrLenOrInd); + } + return SQL_SUCCESS; + } + + ResultSetParser_.reset(); + } + + auto part = Iterator_->ReadNext().ExtractValueSync(); + if (part.EOS()) { + ClearStatement(); + return SQL_NO_DATA; + } + + if (!part.IsSuccess()) { + // AddError(part.GetStatus().GetStatus().GetCode(), part.GetStatus().GetStatus().GetReason()); + ClearStatement(); + return SQL_ERROR; + } + + if (part.HasResultSet()) { + ResultSetParser_ = std::make_unique(part.ExtractResultSet()); + } + } + + return SQL_SUCCESS; +} + +SQLRETURN TStatement::GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, + SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) { + if (!ResultSetParser_) { + return SQL_NO_DATA; + } + + if (columnNumber < 1 || columnNumber > ResultSetParser_->ColumnsCount()) { + return SQL_ERROR; + } + + return ConvertYdbValue(ResultSetParser_->ColumnParser(columnNumber - 1), targetType, targetValue, bufferLength, strLenOrInd); +} + +SQLRETURN TStatement::GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, + SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength) { + if (recNumber < 1 || recNumber > (SQLSMALLINT)Errors_.size()) return SQL_NO_DATA; + const auto& err = Errors_[recNumber-1]; + if (sqlState) strncpy((char*)sqlState, err.SqlState.c_str(), 6); + if (nativeError) *nativeError = err.NativeError; + if (messageText && bufferLength > 0) { + strncpy((char*)messageText, err.Message.c_str(), bufferLength); + if (textLength) *textLength = (SQLSMALLINT)std::min((int)err.Message.size(), (int)bufferLength); + } + return SQL_SUCCESS; +} + +SQLRETURN TStatement::BindCol(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) { + // Удаляем старую связь для этой колонки, если есть + BoundColumns_.erase(std::remove_if(BoundColumns_.begin(), BoundColumns_.end(), + [columnNumber](const TBoundColumn& col) { return col.ColumnNumber == columnNumber; }), BoundColumns_.end()); + // Если targetValue == nullptr, просто удаляем связь + if (!targetValue) { + return SQL_SUCCESS; + } + BoundColumns_.push_back({columnNumber, targetType, targetValue, bufferLength, strLenOrInd}); + return SQL_SUCCESS; +} + +SQLRETURN TStatement::BindParameter(SQLUSMALLINT paramNumber, + SQLSMALLINT inputOutputType, + SQLSMALLINT valueType, + SQLSMALLINT parameterType, + SQLULEN columnSize, + SQLSMALLINT decimalDigits, + SQLPOINTER parameterValuePtr, + SQLLEN bufferLength, + SQLLEN* strLenOrIndPtr) { + if (inputOutputType != SQL_PARAM_INPUT) { + AddError("HYC00", 0, "Only input parameters are supported"); + return SQL_ERROR; + } + // Удаляем старую связь для этого параметра, если есть + BoundParams_.erase(std::remove_if(BoundParams_.begin(), BoundParams_.end(), + [paramNumber](const TBoundParam& p) { return p.ParamNumber == paramNumber; }), BoundParams_.end()); + // Если parameterValuePtr == nullptr, просто удаляем связь + if (!parameterValuePtr) { + return SQL_SUCCESS; + } + BoundParams_.push_back({paramNumber, inputOutputType, valueType, parameterType, columnSize, decimalDigits, parameterValuePtr, bufferLength, strLenOrIndPtr}); + return SQL_SUCCESS; +} + +void TStatement::AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message) { + Errors_.push_back({sqlState, nativeError, message}); +} + +void TStatement::ClearErrors() { + Errors_.clear(); +} + +void TStatement::ClearStatement() { + Iterator_.reset(); + ResultSetParser_.reset(); + BoundColumns_.clear(); +} + +SQLRETURN TStatement::ConvertYdbValue(NYdb::TValueParser& valueParser, + SQLSMALLINT targetType, + SQLPOINTER targetValue, + SQLLEN bufferLength, + SQLLEN* strLenOrInd) { + + // 1. Проверка на NULL + if (valueParser.IsNull()) { + if (strLenOrInd) *strLenOrInd = SQL_NULL_DATA; + return SQL_SUCCESS; + } + + if (valueParser.GetKind() == TTypeParser::ETypeKind::Optional) { + valueParser.OpenOptional(); + SQLRETURN ret = ConvertYdbValue(valueParser, targetType, targetValue, bufferLength, strLenOrInd); + valueParser.CloseOptional(); + return ret; + } + + if (valueParser.GetKind() != TTypeParser::ETypeKind::Primitive) { + return SQL_ERROR; + } + + EPrimitiveType ydbType = valueParser.GetPrimitiveType(); + + switch (targetType) { + case SQL_C_SLONG: + { + int32_t v = 0; + switch (ydbType) { + case EPrimitiveType::Int32: v = valueParser.GetInt32(); break; + case EPrimitiveType::Uint32: v = static_cast(valueParser.GetUint32()); break; + case EPrimitiveType::Int64: v = static_cast(valueParser.GetInt64()); break; + case EPrimitiveType::Uint64: v = static_cast(valueParser.GetUint64()); break; + case EPrimitiveType::Bool: v = valueParser.GetBool() ? 1 : 0; break; + default: return SQL_ERROR; + } + if (targetValue) *reinterpret_cast(targetValue) = v; + if (strLenOrInd) *strLenOrInd = sizeof(int32_t); + return SQL_SUCCESS; + } + case SQL_C_SBIGINT: + { + SQLBIGINT v = 0; + switch (ydbType) { + case EPrimitiveType::Int64: v = valueParser.GetInt64(); break; + case EPrimitiveType::Uint64: v = static_cast(valueParser.GetUint64()); break; + case EPrimitiveType::Int32: v = static_cast(valueParser.GetInt32()); break; + case EPrimitiveType::Uint32: v = static_cast(valueParser.GetUint32()); break; + default: return SQL_ERROR; + } + if (targetValue) *reinterpret_cast(targetValue) = v; + if (strLenOrInd) *strLenOrInd = sizeof(SQLBIGINT); + return SQL_SUCCESS; + } + case SQL_C_DOUBLE: + { + double v = 0.0; + switch (ydbType) { + case EPrimitiveType::Double: v = valueParser.GetDouble(); break; + case EPrimitiveType::Float: v = valueParser.GetFloat(); break; + default: return SQL_ERROR; + } + if (targetValue) *reinterpret_cast(targetValue) = v; + if (strLenOrInd) *strLenOrInd = sizeof(double); + return SQL_SUCCESS; + } + case SQL_C_CHAR: + { + std::string str; + switch (ydbType) { + case EPrimitiveType::Utf8: str = valueParser.GetUtf8(); break; + case EPrimitiveType::String: str = valueParser.GetString(); break; + case EPrimitiveType::Json: str = valueParser.GetJson(); break; + case EPrimitiveType::JsonDocument: str = valueParser.GetJsonDocument(); break; + default: return SQL_ERROR; + } + SQLLEN len = str.size(); + if (targetValue && bufferLength > 0) { + SQLLEN copyLen = std::min(len, bufferLength - 1); + memcpy(targetValue, str.data(), copyLen); + reinterpret_cast(targetValue)[copyLen] = 0; + } + if (strLenOrInd) *strLenOrInd = len; + return SQL_SUCCESS; + } + case SQL_C_BIT: + { + char v = valueParser.GetBool() ? 1 : 0; + if (targetValue) *reinterpret_cast(targetValue) = v; + if (strLenOrInd) *strLenOrInd = sizeof(char); + return SQL_SUCCESS; + } + // Добавьте обработку дат/времени, бинарных данных и других типов по необходимости + default: + return SQL_ERROR; + } +} + +NYdb::TParams TStatement::BuildParams() { + Errors_.clear(); + NYdb::TParamsBuilder paramsBuilder; + for (const auto& param : BoundParams_) { + std::string paramName = "$p" + std::to_string(param.ParamNumber); // ODBC нумерует с 1 + auto& builder = paramsBuilder.AddParam(paramName); + // Обработка NULL + if (param.StrLenOrIndPtr && *param.StrLenOrIndPtr == SQL_NULL_DATA) { + builder.EmptyOptional(); + builder.Build(); + continue; + } + + switch (param.ValueType) { + case SQL_C_SLONG: { + auto value = *static_cast(param.ParameterValuePtr); + switch (param.ParameterType) { + case SQL_INTEGER: + builder.Int32(static_cast(value)); + break; + case SQL_BIGINT: + builder.Int64(static_cast(value)); + break; + case SQL_DOUBLE: + builder.Double(static_cast(value)); + break; + case SQL_FLOAT: + builder.Float(static_cast(value)); + break; + case SQL_VARCHAR: + case SQL_CHAR: + case SQL_LONGVARCHAR: + builder.Utf8(std::to_string(value)); + break; + case SQL_BIT: + builder.Uint8(static_cast(value)); + break; + default: + AddError("07006", 0, "Unsupported SQL type"); + return paramsBuilder.Build(); + } + break; + } + case SQL_C_SBIGINT: { + auto v = *static_cast(param.ParameterValuePtr); + builder.Int32(static_cast(v)); + break; + } + default: { + AddError("07006", 0, "Unsupported C type"); + return paramsBuilder.Build(); + } + } + + switch (param.ParameterType) { + case SQL_INTEGER: + case SQL_BIGINT: + break; + case SQL_DOUBLE: + builder.Double(*reinterpret_cast(param.ParameterValuePtr)); + break; + case SQL_FLOAT: + builder.Double(*reinterpret_cast(param.ParameterValuePtr)); + break; + case SQL_VARCHAR: + case SQL_CHAR: + case SQL_LONGVARCHAR: + builder.Utf8(*reinterpret_cast(param.ParameterValuePtr)); + break; + case SQL_BIT: + builder.Bool(*reinterpret_cast(param.ParameterValuePtr)); + break; + default: + AddError("07006", 0, "Unsupported SQL type"); + return paramsBuilder.Build(); + } + + builder.Build(); + } + + return paramsBuilder.Build(); +} + +} // namespace NOdbc +} // namespace NYdb \ No newline at end of file diff --git a/odbc/src/statement.h b/odbc/src/statement.h new file mode 100644 index 0000000000..f34e0e771e --- /dev/null +++ b/odbc/src/statement.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "connection.h" + +namespace NYdb { +namespace NOdbc { + +class TStatement { +private: + TConnection* Conn_; + TErrorList Errors_; + std::unique_ptr Iterator_; + std::unique_ptr ResultSetParser_; + + struct TBoundColumn { + SQLUSMALLINT ColumnNumber; + SQLSMALLINT TargetType; + SQLPOINTER TargetValue; + SQLLEN BufferLength; + SQLLEN* StrLenOrInd; + }; + std::vector BoundColumns_; + + struct TBoundParam { + SQLUSMALLINT ParamNumber; + SQLSMALLINT InputOutputType; + SQLSMALLINT ValueType; + SQLSMALLINT ParameterType; + SQLULEN ColumnSize; + SQLSMALLINT DecimalDigits; + SQLPOINTER ParameterValuePtr; + SQLLEN BufferLength; + SQLLEN* StrLenOrIndPtr; + }; + std::vector BoundParams_; + +public: + TStatement(TConnection* conn); + + SQLRETURN ExecDirect(const std::string& statementText); + SQLRETURN Fetch(); + SQLRETURN GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, + SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd); + SQLRETURN GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, + SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength); + SQLRETURN BindCol(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd); + SQLRETURN BindParameter(SQLUSMALLINT paramNumber, SQLSMALLINT inputOutputType, SQLSMALLINT valueType, SQLSMALLINT parameterType, SQLULEN columnSize, SQLSMALLINT decimalDigits, SQLPOINTER parameterValuePtr, SQLLEN bufferLength, SQLLEN* strLenOrIndPtr); + + TConnection* GetConnection() { return Conn_; } + + void AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message); + void ClearErrors(); + + NYdb::TParams BuildParams(); + +private: + void ClearStatement(); + + SQLRETURN ConvertYdbValue(NYdb::TValueParser& valueParser, SQLSMALLINT targetType, + SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd); +}; + +} // namespace NOdbc +} // namespace NYdb \ No newline at end of file From 012f97d40dce94da24acdf176a20e400f165dd1e Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Fri, 16 May 2025 14:46:04 +0000 Subject: [PATCH 04/11] Revert C API --- CMakeLists.txt | 1 - c_api/CMakeLists.txt | 20 -- c_api/README.md | 51 ----- c_api/include/ydb-cpp-sdk/c_api/driver.h | 47 ---- c_api/include/ydb-cpp-sdk/c_api/query.h | 39 ---- c_api/include/ydb-cpp-sdk/c_api/result.h | 32 --- c_api/include/ydb-cpp-sdk/c_api/value.h | 75 ------ c_api/src/driver.cpp | 172 -------------- c_api/src/impl/driver_impl.h | 22 -- c_api/src/impl/result_impl.h | 14 -- c_api/src/impl/value_impl.h | 15 -- c_api/src/query.cpp | 140 ------------ c_api/src/result.cpp | 95 -------- c_api/src/value.cpp | 276 ----------------------- examples/CMakeLists.txt | 1 - examples/c_api/CMakeLists.txt | 5 - examples/c_api/main.c | 37 --- 17 files changed, 1042 deletions(-) delete mode 100644 c_api/CMakeLists.txt delete mode 100644 c_api/README.md delete mode 100644 c_api/include/ydb-cpp-sdk/c_api/driver.h delete mode 100644 c_api/include/ydb-cpp-sdk/c_api/query.h delete mode 100644 c_api/include/ydb-cpp-sdk/c_api/result.h delete mode 100644 c_api/include/ydb-cpp-sdk/c_api/value.h delete mode 100644 c_api/src/driver.cpp delete mode 100644 c_api/src/impl/driver_impl.h delete mode 100644 c_api/src/impl/result_impl.h delete mode 100644 c_api/src/impl/value_impl.h delete mode 100644 c_api/src/query.cpp delete mode 100644 c_api/src/result.cpp delete mode 100644 c_api/src/value.cpp delete mode 100644 examples/c_api/CMakeLists.txt delete mode 100644 examples/c_api/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 18a5e9f7e2..dd1eef3b6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,7 +59,6 @@ add_subdirectory(library/cpp) add_subdirectory(include/ydb-cpp-sdk/client) add_subdirectory(src) add_subdirectory(util) -add_subdirectory(c_api) #_ydb_sdk_validate_public_headers() diff --git a/c_api/CMakeLists.txt b/c_api/CMakeLists.txt deleted file mode 100644 index b2c8fec22a..0000000000 --- a/c_api/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -_ydb_sdk_add_library(ydb-c-api SHARED) - -target_sources(ydb-c-api PRIVATE - src/driver.cpp - src/query.cpp - src/result.cpp - src/value.cpp -) - -target_include_directories(ydb-c-api PUBLIC include) - -target_link_libraries(ydb-c-api - PRIVATE - yutil - ydb-cpp-sdk::Query - ydb-cpp-sdk::Table - ydb-cpp-sdk::Driver -) - -add_library(ydb-cpp-sdk::c-api ALIAS ydb-c-api) diff --git a/c_api/README.md b/c_api/README.md deleted file mode 100644 index b7f929c553..0000000000 --- a/c_api/README.md +++ /dev/null @@ -1,51 +0,0 @@ -Синхронный API Асинхронный API - -libpq: -```c -PGconn *conn = PQconnectdb("..."); -// Блокирует выполнение до завершения -``` - -libpq: -```c -PGconn *conn = PQconnectStart("..."); -do { - pollstatus = PQconnectPoll(conn); - // Ожидание событий -} while (pollstatus != PGRES_POLLING_OK); -``` - -MySQL: -
MYSQL *conn = mysql_init(NULL); -
mysql_real_connect(conn, ...); - -MySQL: -
status = mysql_real_connect_nonblocking(mysql, ...); -
while (status == NET_ASYNC_NOT_READY) { -
// Обработка других задач
- status = mysql_real_connect_nonblocking(...);
-} - -Выполнение запросов - -Синхронный API Асинхронный API - -libpq:
PGresult *res = PQexec(conn, "SELECT ...");
Ожидает завершения выполнения - -libpq:
PQsendQuery(conn, "SELECT ...");
// Можно выполнять другую работу
while ((res = PQgetResult(conn)) != NULL) {
// Обработка результатов
} - -MySQL:
mysql_query(conn, "SELECT ...");
result = mysql_store_result(conn); - -MySQL:
status = mysql_real_query_nonblocking(mysql, "...");
// Проверка status и ожидание
status = mysql_store_result_nonblocking(mysql, &result); - -Обработка ошибок - -Синхронный API Асинхронный API - -libpq:
if (PQstatus(conn) != CONNECTION_OK) {
fprintf(stderr, "%s", PQerrorMessage(conn));
} - -libpq:
Такая же проверка, но в каждом шаге асинхронного процесса:
if (pollstatus == PGRES_POLLING_FAILED) {
fprintf(stderr, "%s", PQerrorMessage(conn));
} - -MySQL:
if (mysql_query(conn, query)) {
fprintf(stderr, "%s", mysql_error(conn));
} - -MySQL:
if (status == NET_ASYNC_ERROR) {
fprintf(stderr, "%s", mysql_error(mysql));
} \ No newline at end of file diff --git a/c_api/include/ydb-cpp-sdk/c_api/driver.h b/c_api/include/ydb-cpp-sdk/c_api/driver.h deleted file mode 100644 index 557994675f..0000000000 --- a/c_api/include/ydb-cpp-sdk/c_api/driver.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct TYdbDriverConfigImpl TYdbDriverConfig; -typedef struct TYdbDriverImpl TYdbDriver; - -typedef enum { - YDB_DRIVER_CONFIG_OK, - YDB_DRIVER_CONFIG_INVALID, -} EYdbDriverConfigStatus; - -typedef enum { - YDB_DRIVER_OK, - YDB_DRIVER_ERROR, -} EYdbDriverStatus; - -// Создание и уничтожение конфигурации -TYdbDriverConfig* YdbCreateDriverConfig(const char* connectionString); -void YdbDestroyDriverConfig(TYdbDriverConfig* config); - -// Установка параметров конфигурации -TYdbDriverConfig* YdbSetEndpoint(TYdbDriverConfig* config, const char* endpoint); -TYdbDriverConfig* YdbSetDatabase(TYdbDriverConfig* config, const char* database); -TYdbDriverConfig* YdbSetAuthToken(TYdbDriverConfig* config, const char* token); -TYdbDriverConfig* YdbSetSecureConnection(TYdbDriverConfig* config, const char* cert); - -// Получение результата конфигурации -EYdbDriverConfigStatus YdbGetDriverConfigStatus(TYdbDriverConfig* config); -const char* YdbGetDriverConfigErrorMessage(TYdbDriverConfig* config); - -// Создание и уничтожение драйвера -TYdbDriver* YdbCreateDriver(const char* connectionString); -TYdbDriver* YdbCreateDriverFromConfig(TYdbDriverConfig* config); -void YdbDestroyDriver(TYdbDriver* driver); - -// Получение результата драйвера -EYdbDriverStatus YdbGetDriverStatus(TYdbDriver* driver); -const char* YdbGetDriverErrorMessage(TYdbDriver* driver); - -#ifdef __cplusplus -} -#endif diff --git a/c_api/include/ydb-cpp-sdk/c_api/query.h b/c_api/include/ydb-cpp-sdk/c_api/query.h deleted file mode 100644 index e3bd3918ac..0000000000 --- a/c_api/include/ydb-cpp-sdk/c_api/query.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include - -#include "driver.h" -#include "result.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct TYdbQueryClientImpl TYdbQueryClient; -typedef struct TYdbQueryResultImpl TYdbQueryResult; - -typedef enum { - YDB_QUERY_CLIENT_OK, - YDB_QUERY_CLIENT_ERROR, -} EYdbQueryClientError; - -typedef enum { - YDB_QUERY_RESULT_OK, - YDB_QUERY_RESULT_ERROR, -} EYdbQueryResultError; - -// Создание и уничтожение клиента запросов -TYdbQueryClient* YdbCreateQueryClient(TYdbDriver* driver); -void YdbDestroyQueryClient(TYdbQueryClient* queryClient); - -// Выполнение запроса -TYdbQueryResult* YdbExecuteQuery(TYdbQueryClient* queryClient, const char* query); -void YdbDestroyQueryResult(TYdbQueryResult* result); - -// Получение результата запроса -int YdbGetQueryResultSetsCount(TYdbQueryResult* result); -TYdbResultSet* YdbGetQueryResultSet(TYdbQueryResult* result, size_t index); - -#ifdef __cplusplus -} -#endif diff --git a/c_api/include/ydb-cpp-sdk/c_api/result.h b/c_api/include/ydb-cpp-sdk/c_api/result.h deleted file mode 100644 index fc762ccf86..0000000000 --- a/c_api/include/ydb-cpp-sdk/c_api/result.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "value.h" - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct TYdbResultSetImpl TYdbResultSet; - -typedef enum { - YDB_RESULT_SET_OK, - YDB_RESULT_SET_ERROR, -} EYdbResultSetStatus; - -int YdbGetColumnsCount(TYdbResultSet* resultSet); -int YdbGetRowsCount(TYdbResultSet* resultSet); -int YdbIsTruncated(TYdbResultSet* resultSet); - -const char* YdbGetColumnName(TYdbResultSet* resultSet, size_t index); -int YdbGetColumnIndex(TYdbResultSet* resultSet, const char* name); - -TYdbValue* YdbGetValue(TYdbResultSet* resultSet, size_t rowIndex, const char* name); -TYdbValue* YdbGetValueByIndex(TYdbResultSet* resultSet, size_t rowIndex, size_t columnIndex); - -void YdbDestroyResultSet(TYdbResultSet* resultSet); - -#ifdef __cplusplus -} -#endif diff --git a/c_api/include/ydb-cpp-sdk/c_api/value.h b/c_api/include/ydb-cpp-sdk/c_api/value.h deleted file mode 100644 index 5f0ad2caa1..0000000000 --- a/c_api/include/ydb-cpp-sdk/c_api/value.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct TYdbValueImpl TYdbValue; -typedef struct TYdbParamsImpl TYdbParams; - -typedef enum { - YDB_VALUE_OK, - YDB_VALUE_ERROR, -} EYdbValueStatus; - -typedef enum { - YDB_TYPE_KIND_UNDEFINED, - YDB_TYPE_KIND_PRIMITIVE, - YDB_TYPE_KIND_OPTIONAL, - YDB_TYPE_KIND_LIST, - YDB_TYPE_KIND_TUPLE, - YDB_TYPE_KIND_STRUCT, - YDB_TYPE_KIND_DICT, - YDB_TYPE_KIND_VARIANT, -} EYdbTypeKind; - -typedef enum { - YDB_PRIMITIVE_TYPE_UNDEFINED, - YDB_PRIMITIVE_TYPE_BOOL, - YDB_PRIMITIVE_TYPE_INT8, - YDB_PRIMITIVE_TYPE_UINT8, - YDB_PRIMITIVE_TYPE_INT16, - YDB_PRIMITIVE_TYPE_UINT16, - YDB_PRIMITIVE_TYPE_INT32, - YDB_PRIMITIVE_TYPE_UINT32, - YDB_PRIMITIVE_TYPE_INT64, - YDB_PRIMITIVE_TYPE_UINT64, - YDB_PRIMITIVE_TYPE_FLOAT, - YDB_PRIMITIVE_TYPE_DOUBLE, - YDB_PRIMITIVE_TYPE_STRING, - YDB_PRIMITIVE_TYPE_UTF8, - YDB_PRIMITIVE_TYPE_YSON, - YDB_PRIMITIVE_TYPE_JSON, - YDB_PRIMITIVE_TYPE_JSON_DOCUMENT, - YDB_PRIMITIVE_TYPE_DYNUMBER, -} EYdbPrimitiveType; - -EYdbTypeKind YdbGetTypeKind(TYdbValue* value); -EYdbPrimitiveType YdbGetPrimitiveType(TYdbValue* value); - -EYdbValueStatus YdbGetBool(TYdbValue* value, bool* result); -EYdbValueStatus YdbGetInt8(TYdbValue* value, int8_t* result); -EYdbValueStatus YdbGetUint8(TYdbValue* value, uint8_t* result); -EYdbValueStatus YdbGetInt16(TYdbValue* value, int16_t* result); -EYdbValueStatus YdbGetUint16(TYdbValue* value, uint16_t* result); -EYdbValueStatus YdbGetInt32(TYdbValue* value, int32_t* result); -EYdbValueStatus YdbGetUint32(TYdbValue* value, uint32_t* result); -EYdbValueStatus YdbGetInt64(TYdbValue* value, int64_t* result); -EYdbValueStatus YdbGetUint64(TYdbValue* value, uint64_t* result); -EYdbValueStatus YdbGetFloat(TYdbValue* value, float* result); -EYdbValueStatus YdbGetDouble(TYdbValue* value, double* result); -EYdbValueStatus YdbGetString(TYdbValue* value, char** result); -EYdbValueStatus YdbGetUtf8(TYdbValue* value, char** result); -EYdbValueStatus YdbGetYson(TYdbValue* value, char** result); -EYdbValueStatus YdbGetJson(TYdbValue* value, char** result); -EYdbValueStatus YdbGetJsonDocument(TYdbValue* value, char** result); -EYdbValueStatus YdbGetDyNumber(TYdbValue* value, char** result); - -void YdbDestroyValue(TYdbValue* value); - -#ifdef __cplusplus -} -#endif diff --git a/c_api/src/driver.cpp b/c_api/src/driver.cpp deleted file mode 100644 index 27e1093d68..0000000000 --- a/c_api/src/driver.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include - -#include "impl/driver_impl.h" // NOLINT - -#include - -extern "C" { - -// Создание и уничтожение конфигурации -TYdbDriverConfig* YdbCreateDriverConfig(const char* connectionString) { - try { - if (!connectionString) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid config pointer"}; - } - - try { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_OK, "", NYdb::TDriverConfig(connectionString)}; - } catch (const std::exception& e) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, e.what()}; - } - } catch (const std::exception& e) { - return nullptr; - } -} - -void YdbDestroyDriverConfig(TYdbDriverConfig* config) { - if (config) { - delete config; - } -} - -// Установка параметров конфигурации -TYdbDriverConfig* YdbSetEndpoint(TYdbDriverConfig* config, const char* endpoint) { - try { - if (!config) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid config"}; - } - if (!endpoint) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid endpoint"}; - } - - try { - config->config.SetEndpoint(std::string(endpoint)); - return config; - } catch (const std::exception& e) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, e.what()}; - } - } catch (...) { - return nullptr; - } -} - -TYdbDriverConfig* YdbSetDatabase(TYdbDriverConfig* config, const char* database) { - try { - if (!config) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid config"}; - } - if (!database) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid database"}; - } - - try { - config->config.SetDatabase(std::string(database)); - return config; - } catch (const std::exception& e) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, e.what()}; - } - } catch (...) { - return nullptr; - } -} - -TYdbDriverConfig* YdbSetAuthToken(TYdbDriverConfig* config, const char* token) { - try { - if (!config) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid config"}; - } - if (!token) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid token"}; - } - - try { - config->config.SetAuthToken(std::string(token)); - return config; - } catch (const std::exception& e) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, e.what()}; - } - } catch (...) { - return nullptr; - } -} - -TYdbDriverConfig* YdbSetSecureConnection(TYdbDriverConfig* config, const char* cert) { - try { - if (!config) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid config"}; - } - if (!cert) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, "Invalid certificate"}; - } - - try { - config->config.UseSecureConnection(std::string(cert)); - return config; - } catch (const std::exception& e) { - return new TYdbDriverConfig{YDB_DRIVER_CONFIG_INVALID, e.what()}; - } - } catch (...) { - return nullptr; - } -} - -EYdbDriverConfigStatus YdbGetDriverConfigStatus(TYdbDriverConfig* config) { - if (!config) { - return YDB_DRIVER_CONFIG_INVALID; - } - - return config->errorCode; -} - -const char* YdbGetDriverConfigErrorMessage(TYdbDriverConfig* config) { - if (!config) { - return "Invalid config"; - } - - return config->errorMessage.c_str(); -} - -// Создание и уничтожение драйвера -TYdbDriver* YdbCreateDriverFromConfig(TYdbDriverConfig* config) { - try { - if (!config) { - return new TYdbDriver{YDB_DRIVER_ERROR, "Invalid config"}; - } - - if (config->errorCode != 0) { - return new TYdbDriver{YDB_DRIVER_ERROR, "Invalid config: " + config->errorMessage}; - } - - try { - return new TYdbDriver{YDB_DRIVER_OK, "", NYdb::TDriver(config->config)}; - } catch (const std::exception& e) { - return new TYdbDriver{YDB_DRIVER_ERROR, e.what()}; - } - } catch (...) { - return nullptr; - } -} - -TYdbDriver* YdbCreateDriver(const char* connectionString) { - try { - if (!connectionString) { - return new TYdbDriver{YDB_DRIVER_ERROR, "Invalid connection string"}; - } - - try { - return new TYdbDriver{YDB_DRIVER_OK, "", NYdb::TDriver(std::string(connectionString))}; - } catch (const std::exception& e) { - return new TYdbDriver{YDB_DRIVER_ERROR, e.what()}; - } - } catch (...) { - return nullptr; - } -} - -void YdbDestroyDriver(TYdbDriver* driver) { - if (driver) { - delete driver; - } -} - -} diff --git a/c_api/src/impl/driver_impl.h b/c_api/src/impl/driver_impl.h deleted file mode 100644 index 87644f15f2..0000000000 --- a/c_api/src/impl/driver_impl.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include - -struct TYdbDriverConfigImpl { - EYdbDriverConfigStatus errorCode; - std::string errorMessage; - - NYdb::TDriverConfig config; -}; - -struct TYdbDriverImpl { - EYdbDriverStatus errorCode; - std::string errorMessage; - - std::optional driver; -}; diff --git a/c_api/src/impl/result_impl.h b/c_api/src/impl/result_impl.h deleted file mode 100644 index b662ea4f45..0000000000 --- a/c_api/src/impl/result_impl.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -#include - -#include - -struct TYdbResultSetImpl { - EYdbResultSetStatus errorCode; - std::string errorMessage; - - std::optional result; -}; diff --git a/c_api/src/impl/value_impl.h b/c_api/src/impl/value_impl.h deleted file mode 100644 index d7a7a4eba9..0000000000 --- a/c_api/src/impl/value_impl.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include - -struct TYdbValueImpl { - EYdbValueStatus errorCode; - std::string errorMessage; - - std::optional value; -}; diff --git a/c_api/src/query.cpp b/c_api/src/query.cpp deleted file mode 100644 index 0f11c52c17..0000000000 --- a/c_api/src/query.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include - -#include - -#include "impl/driver_impl.h" // NOLINT -#include "impl/result_impl.h" // NOLINT - -#include - -struct TYdbQueryClientImpl { - EYdbQueryClientError errorCode; - std::string errorMessage; - - std::optional client; -}; - -struct TYdbQueryResultImpl { - EYdbQueryResultError errorCode; - std::string errorMessage; - - std::optional result; -}; - -extern "C" { - -TYdbQueryClient* YdbCreateQueryClient(TYdbDriver* driver) { - try { - if (!driver || !driver->driver.has_value()) { - return new TYdbQueryClient{ - YDB_QUERY_CLIENT_ERROR, - "Invalid driver" - }; - } - - try { - return new TYdbQueryClient{ - YDB_QUERY_CLIENT_OK, - "", - NYdb::NQuery::TQueryClient(*driver->driver) - }; - } catch (const std::exception& e) { - return new TYdbQueryClient{ - YDB_QUERY_CLIENT_ERROR, - e.what() - }; - } - } catch (...) { - return nullptr; - } -} - -void YdbDestroyQueryClient(TYdbQueryClient* queryClient) { - if (queryClient) { - delete queryClient; - } -} - -TYdbQueryResult* YdbExecuteQuery(TYdbQueryClient* queryClient, const char* query) { - try { - if (!queryClient || !queryClient->client.has_value()) { - return new TYdbQueryResult{ - YDB_QUERY_RESULT_ERROR, - "Invalid query client" - }; - } - - if (!query) { - return new TYdbQueryResult{ - YDB_QUERY_RESULT_ERROR, - "Invalid query" - }; - } - - try { - auto client = *queryClient->client; - auto executeResult = client.ExecuteQuery( - std::string(query), - NYdb::NQuery::TTxControl::NoTx() - ).GetValueSync(); - - if (!executeResult.IsSuccess()) { - return new TYdbQueryResult{ - YDB_QUERY_RESULT_ERROR, - "Query execution failed: " + executeResult.GetIssues().ToString() - }; - } - - return new TYdbQueryResult{ - YDB_QUERY_RESULT_OK, - "", - executeResult - }; - } catch (const std::exception& e) { - return new TYdbQueryResult{ - YDB_QUERY_RESULT_ERROR, - e.what() - }; - } - } catch (...) { - return nullptr; - } -} - -void YdbDestroyQueryResult(TYdbQueryResult* result) { - if (result) { - delete result; - } -} - -TYdbResultSet* YdbGetQueryResultSet(TYdbQueryResult* result, size_t index) { - try { - if (!result || !result->result.has_value()) { - return nullptr; - } - - try { - return new TYdbResultSet{ - YDB_RESULT_SET_OK, - "", - result->result->GetResultSet(index) - }; - } catch (const std::exception& e) { - return new TYdbResultSet{ - YDB_RESULT_SET_ERROR, - e.what() - }; - } - } catch (...) { - return nullptr; - } -} - -int YdbGetQueryResultSetsCount(TYdbQueryResult* result) { - if (!result || !result->result.has_value()) { - return -1; - } - return result->result->GetResultSets().size(); -} - -} diff --git a/c_api/src/result.cpp b/c_api/src/result.cpp deleted file mode 100644 index c6c52bc737..0000000000 --- a/c_api/src/result.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include - -#include -#include - -#include "impl/result_impl.h" // NOLINT -#include "impl/value_impl.h" // NOLINT - -extern "C" { - -int YdbGetColumnsCount(TYdbResultSet* resultSet) { - if (!resultSet || !resultSet->result.has_value()) { - return -1; - } - - return resultSet->result->ColumnsCount(); -} - -int YdbGetRowsCount(TYdbResultSet* resultSet) { - if (!resultSet || !resultSet->result.has_value()) { - return -1; - } - - return resultSet->result->RowsCount(); -} - -int YdbIsTruncated(TYdbResultSet* resultSet) { - if (!resultSet || !resultSet->result.has_value()) { - return -1; - } - - return resultSet->result->Truncated(); -} - -const char* YdbGetColumnName(TYdbResultSet* resultSet, size_t index) { - if (!resultSet || !resultSet->result.has_value()) { - return nullptr; - } - - return resultSet->result->GetColumnsMeta()[index].Name.c_str(); -} - -int YdbGetColumnIndex(TYdbResultSet* resultSet, const char* name) { - try { - if (!resultSet || !resultSet->result.has_value()) { - return -1; - } - - NYdb::TResultSetParser parser(*resultSet->result); - return parser.ColumnIndex(name); - } catch (...) { - return -1; - } -} - -TYdbValue* YdbGetValue(TYdbResultSet* resultSet, size_t rowIndex, const char* name) { - try { - if (!resultSet || !resultSet->result.has_value()) { - return new TYdbValue{YDB_VALUE_ERROR, "Invalid result set"}; - } - - NYdb::TResultSetParser parser(*resultSet->result); - int columnIndex = parser.ColumnIndex(name); - if (columnIndex == -1) { - return new TYdbValue{YDB_VALUE_ERROR, "Invalid column name"}; - } - - return YdbGetValueByIndex(resultSet, rowIndex, columnIndex); - } catch (...) { - return nullptr; - } -} - -TYdbValue* YdbGetValueByIndex(TYdbResultSet* resultSet, size_t rowIndex, size_t columnIndex) { - try { - if (!resultSet || !resultSet->result.has_value()) { - return nullptr; - } - - auto proto = NYdb::TProtoAccessor::GetProto(*resultSet->result); - - auto type = resultSet->result->GetColumnsMeta()[columnIndex].Type; - auto value = proto.rows(rowIndex).items(columnIndex); - - return new TYdbValue{YDB_VALUE_OK, "", NYdb::TValue{type, value}}; - } catch (...) { - return nullptr; - } -} - -void YdbDestroyResultSet(TYdbResultSet* resultSet) { - delete resultSet; -} - -} diff --git a/c_api/src/value.cpp b/c_api/src/value.cpp deleted file mode 100644 index 2aa25ce418..0000000000 --- a/c_api/src/value.cpp +++ /dev/null @@ -1,276 +0,0 @@ -#include - -#include - -#include "impl/value_impl.h" // NOLINT - -extern "C" { - -EYdbTypeKind YdbGetTypeKind(TYdbValue* value) { - if (!value || !value->value.has_value()) { - return YDB_TYPE_KIND_UNDEFINED; - } - - NYdb::TValueParser valueParser(*value->value); - - switch (valueParser.GetKind()) { - case NYdb::TTypeParser::ETypeKind::Primitive: - return YDB_TYPE_KIND_PRIMITIVE; - case NYdb::TTypeParser::ETypeKind::Optional: - return YDB_TYPE_KIND_OPTIONAL; - case NYdb::TTypeParser::ETypeKind::List: - return YDB_TYPE_KIND_LIST; - case NYdb::TTypeParser::ETypeKind::Struct: - return YDB_TYPE_KIND_STRUCT; - case NYdb::TTypeParser::ETypeKind::Tuple: - return YDB_TYPE_KIND_TUPLE; - case NYdb::TTypeParser::ETypeKind::Dict: - return YDB_TYPE_KIND_DICT; - case NYdb::TTypeParser::ETypeKind::Variant: - return YDB_TYPE_KIND_VARIANT; - default: - return YDB_TYPE_KIND_UNDEFINED; - } -} - -EYdbPrimitiveType YdbGetPrimitiveType(TYdbValue* value) { - if (!value || !value->value.has_value()) { - return YDB_PRIMITIVE_TYPE_UNDEFINED; - } - - try { - NYdb::TValueParser valueParser(*value->value); - - switch (valueParser.GetPrimitiveType()) { - case NYdb::EPrimitiveType::Int8: - return YDB_PRIMITIVE_TYPE_INT8; - case NYdb::EPrimitiveType::Uint8: - return YDB_PRIMITIVE_TYPE_UINT8; - case NYdb::EPrimitiveType::Int16: - return YDB_PRIMITIVE_TYPE_INT16; - case NYdb::EPrimitiveType::Uint16: - return YDB_PRIMITIVE_TYPE_UINT16; - case NYdb::EPrimitiveType::Int32: - return YDB_PRIMITIVE_TYPE_INT32; - case NYdb::EPrimitiveType::Uint32: - return YDB_PRIMITIVE_TYPE_UINT32; - case NYdb::EPrimitiveType::Int64: - return YDB_PRIMITIVE_TYPE_INT64; - case NYdb::EPrimitiveType::Uint64: - return YDB_PRIMITIVE_TYPE_UINT64; - case NYdb::EPrimitiveType::Float: - return YDB_PRIMITIVE_TYPE_FLOAT; - case NYdb::EPrimitiveType::Double: - return YDB_PRIMITIVE_TYPE_DOUBLE; - case NYdb::EPrimitiveType::String: - return YDB_PRIMITIVE_TYPE_STRING; - case NYdb::EPrimitiveType::Utf8: - return YDB_PRIMITIVE_TYPE_UTF8; - case NYdb::EPrimitiveType::Yson: - return YDB_PRIMITIVE_TYPE_YSON; - case NYdb::EPrimitiveType::Json: - return YDB_PRIMITIVE_TYPE_JSON; - case NYdb::EPrimitiveType::JsonDocument: - return YDB_PRIMITIVE_TYPE_JSON_DOCUMENT; - case NYdb::EPrimitiveType::DyNumber: - return YDB_PRIMITIVE_TYPE_DYNUMBER; - default: - return YDB_PRIMITIVE_TYPE_UNDEFINED; - } - } catch (...) { - return YDB_PRIMITIVE_TYPE_UNDEFINED; - } -} - -EYdbValueStatus YdbGetInt8(TYdbValue* value, int8_t* result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = valueParser.GetInt8(); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetUint8(TYdbValue* value, uint8_t* result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = valueParser.GetUint8(); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetInt16(TYdbValue* value, int16_t* result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = valueParser.GetInt16(); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetUint16(TYdbValue* value, uint16_t* result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = valueParser.GetUint16(); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetInt32(TYdbValue* value, int32_t* result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = valueParser.GetInt32(); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetUint32(TYdbValue* value, uint32_t* result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = valueParser.GetUint32(); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetInt64(TYdbValue* value, int64_t* result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = valueParser.GetInt64(); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetUint64(TYdbValue* value, uint64_t* result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = valueParser.GetUint64(); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetFloat(TYdbValue* value, float* result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = valueParser.GetFloat(); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetDouble(TYdbValue* value, double* result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = valueParser.GetDouble(); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetString(TYdbValue* value, char** result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = strndup(valueParser.GetString().c_str(), valueParser.GetString().size()); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetUtf8(TYdbValue* value, char** result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = strndup(valueParser.GetUtf8().c_str(), valueParser.GetUtf8().size()); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetYson(TYdbValue* value, char** result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = strndup(valueParser.GetYson().c_str(), valueParser.GetYson().size()); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetJson(TYdbValue* value, char** result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = strndup(valueParser.GetJson().c_str(), valueParser.GetJson().size()); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetJsonDocument(TYdbValue* value, char** result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = strndup(valueParser.GetJsonDocument().c_str(), valueParser.GetJsonDocument().size()); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetDyNumber(TYdbValue* value, char** result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = strndup(valueParser.GetDyNumber().c_str(), valueParser.GetDyNumber().size()); - return YDB_VALUE_OK; -} - -EYdbValueStatus YdbGetBool(TYdbValue* value, bool* result) { - if (!value || !value->value.has_value()) { - return YDB_VALUE_ERROR; - } - - NYdb::TValueParser valueParser(*value->value); - - *result = valueParser.GetBool(); - return YDB_VALUE_OK; -} - -void YdbDestroyValue(TYdbValue* value) { - delete value; -} - -} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 730f2400bc..97119cff5e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,6 +1,5 @@ add_subdirectory(basic_example) add_subdirectory(bulk_upsert_simple) -add_subdirectory(c_api) add_subdirectory(pagination) add_subdirectory(secondary_index) add_subdirectory(secondary_index_builtin) diff --git a/examples/c_api/CMakeLists.txt b/examples/c_api/CMakeLists.txt deleted file mode 100644 index 5060c4c25d..0000000000 --- a/examples/c_api/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -add_executable(c_api_example - main.c -) - -target_link_libraries(c_api_example ydb-cpp-sdk::c-api) diff --git a/examples/c_api/main.c b/examples/c_api/main.c deleted file mode 100644 index ca1e2018d0..0000000000 --- a/examples/c_api/main.c +++ /dev/null @@ -1,37 +0,0 @@ -#include - -#include - -#include - -int main() { - TYdbDriver* driver = YdbCreateDriver("grpc://localhost:2136/?database=/local"); - - TYdbQueryClient* query = YdbCreateQueryClient(driver); - - TYdbQueryResult* result = YdbExecuteQuery(query, "SELECT 1"); - - int resultSetsCount = YdbGetQueryResultSetsCount(result); - for (int i = 0; i < resultSetsCount; i++) { - TYdbResultSet* resultSet = YdbGetQueryResultSet(result, i); - int rowsCount = YdbGetRowsCount(resultSet); - for (int j = 0; j < rowsCount; j++) { - TYdbValue* value = YdbGetValueByIndex(resultSet, j, 0); - - EYdbPrimitiveType primitiveType = YdbGetPrimitiveType(value); - if (primitiveType == YDB_PRIMITIVE_TYPE_INT32) { - int32_t int32Value; - YdbGetInt32(value, &int32Value); - printf("%" PRId32 "\n", int32Value); - } else { - printf("Unknown primitive type\n"); - } - YdbDestroyValue(value); - } - } - - YdbDestroyQueryResult(result); - YdbDestroyQueryClient(query); - YdbDestroyDriver(driver); - return 0; -} From 88fabbd738d91b6c92082ed650c1a27353c35add Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 26 May 2025 18:34:06 +0000 Subject: [PATCH 05/11] Parameter bind --- odbc/CMakeLists.txt | 1 + odbc/examples/basic/main.cpp | 23 +- odbc/odbcinst.ini | 7 +- odbc/src/connection.cpp | 26 +- odbc/src/connection.h | 11 +- odbc/src/environment.cpp | 30 +- odbc/src/environment.h | 2 +- odbc/src/statement.cpp | 118 ++------ odbc/src/statement.h | 52 ++-- odbc/src/utils/convert.cpp | 286 ++++++++++++++++++ odbc/src/utils/convert.h | 26 ++ tests/integration/sessions/CMakeLists.txt | 4 +- .../integration/sessions_pool/CMakeLists.txt | 2 +- tests/integration/topic/CMakeLists.txt | 2 +- 14 files changed, 431 insertions(+), 159 deletions(-) create mode 100644 odbc/src/utils/convert.cpp create mode 100644 odbc/src/utils/convert.h diff --git a/odbc/CMakeLists.txt b/odbc/CMakeLists.txt index 4674776832..c45baef5f5 100644 --- a/odbc/CMakeLists.txt +++ b/odbc/CMakeLists.txt @@ -1,5 +1,6 @@ # Добавляем исходники add_library(ydb-odbc SHARED + src/utils/convert.cpp src/odbc_driver.cpp src/connection.cpp src/statement.cpp diff --git a/odbc/examples/basic/main.cpp b/odbc/examples/basic/main.cpp index 9b8123ef7c..364b9e1112 100644 --- a/odbc/examples/basic/main.cpp +++ b/odbc/examples/basic/main.cpp @@ -2,9 +2,6 @@ #include #include -#include -#include -#include void PrintOdbcError(SQLSMALLINT handleType, SQLHANDLE handle) { SQLCHAR sqlState[6] = {0}; @@ -65,16 +62,16 @@ int main() { std::cout << "6. Executing query" << std::endl; SQLCHAR query[] = R"( - DECLARE $p1 AS Int64; - SELECT $p1 + 1, 'test1' as String; - SELECT $p1 + 2, 'test2' as String; - SELECT $p1 + 3, 'test3' as String; - SELECT $p1 + 4, 'test4' as String; - SELECT $p1 + 5, 'test5' as String; - SELECT $p1 + 6, 'test6' as String; - SELECT $p1 + 7, 'test7' as String; - SELECT $p1 + 8, 'test8' as String; - SELECT $p1 + 9, 'test9' as String; + DECLARE $p1 AS Int64?; + SELECT $p1 + 1, 'test1'; + SELECT $p1 + 2, 'test2'; + SELECT $p1 + 3, 'test3'; + SELECT $p1 + 4, 'test4'; + SELECT $p1 + 5, 'test5'; + SELECT $p1 + 6, 'test6'; + SELECT $p1 + 7, 'test7'; + SELECT $p1 + 8, 'test8'; + SELECT $p1 + 9, 'test9'; )"; int64_t paramValue = 42; diff --git a/odbc/odbcinst.ini b/odbc/odbcinst.ini index fade7b6fb9..fd0b3f2765 100644 --- a/odbc/odbcinst.ini +++ b/odbc/odbcinst.ini @@ -1,7 +1,4 @@ [YDB] Description=YDB ODBC Driver -Driver=/usr/local/lib/libydb-odbc.so -Setup=/usr/local/lib/libydb-odbc.so -Threading=2 -FileUsage=1 -UsageCount=1 \ No newline at end of file +Driver=/home/brgayazov/ydbwork/ydb-cpp-sdk/build/odbc/libydb-odbc.so +Setup=/home/brgayazov/ydbwork/ydb-cpp-sdk/build/odbc/libydb-odbc.so \ No newline at end of file diff --git a/odbc/src/connection.cpp b/odbc/src/connection.cpp index 427b03f7ba..a2c0df7c54 100644 --- a/odbc/src/connection.cpp +++ b/odbc/src/connection.cpp @@ -1,8 +1,10 @@ #include "connection.h" #include "statement.h" + #include #include #include + #include #include @@ -12,7 +14,6 @@ namespace NYdb { namespace NOdbc { SQLRETURN TConnection::DriverConnect(const std::string& connectionString) { - // Парсим параметры std::map params; size_t pos = 0; while (pos < connectionString.size()) { @@ -50,7 +51,7 @@ SQLRETURN TConnection::DriverConnect(const std::string& connectionString) { SQLRETURN TConnection::Connect(const std::string& serverName, const std::string& userName, const std::string& auth) { - // Получаем параметры из секции DSN через Driver Manager API + char endpoint[256] = {0}; char database[256] = {0}; @@ -82,13 +83,24 @@ SQLRETURN TConnection::Disconnect() { SQLRETURN TConnection::GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength) { - if (recNumber < 1 || recNumber > (SQLSMALLINT)Errors_.size()) return SQL_NO_DATA; + if (recNumber < 1 || recNumber > (SQLSMALLINT)Errors_.size()) { + return SQL_NO_DATA; + } + const auto& err = Errors_[recNumber-1]; - if (sqlState) strncpy((char*)sqlState, err.SqlState.c_str(), 6); - if (nativeError) *nativeError = err.NativeError; + if (sqlState) { + strncpy((char*)sqlState, err.SqlState.c_str(), 6); + } + + if (nativeError) { + *nativeError = err.NativeError; + } + if (messageText && bufferLength > 0) { strncpy((char*)messageText, err.Message.c_str(), bufferLength); - if (textLength) *textLength = (SQLSMALLINT)std::min((int)err.Message.size(), (int)bufferLength); + if (textLength) { + *textLength = (SQLSMALLINT)std::min((int)err.Message.size(), (int)bufferLength); + } } return SQL_SUCCESS; } @@ -111,7 +123,7 @@ void TConnection::ClearErrors() { } std::pair TConnection::ParseConnectionString(const std::string& connectionString) { - // Заглушка + // TODO: Implement return {"", ""}; } diff --git a/odbc/src/connection.h b/odbc/src/connection.h index cceadc2433..95c872f04b 100644 --- a/odbc/src/connection.h +++ b/odbc/src/connection.h @@ -1,16 +1,17 @@ #pragma once +#include "environment.h" + +#include +#include + #include #include + #include #include #include -#include -#include - -#include "environment.h" - namespace NYdb { namespace NOdbc { diff --git a/odbc/src/environment.cpp b/odbc/src/environment.cpp index 0e1eef594d..a09a634879 100644 --- a/odbc/src/environment.cpp +++ b/odbc/src/environment.cpp @@ -13,15 +13,31 @@ SQLRETURN TEnvironment::SetAttribute(SQLINTEGER attribute, SQLPOINTER value, SQL return SQL_SUCCESS; } -SQLRETURN TEnvironment::GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength) { - // Заглушка - if (recNumber < 1 || recNumber > (SQLSMALLINT)Errors_.size()) return SQL_NO_DATA; +SQLRETURN TEnvironment::GetDiagRec(SQLSMALLINT recNumber, + SQLCHAR* sqlState, + SQLINTEGER* nativeError, + SQLCHAR* messageText, + SQLSMALLINT bufferLength, + SQLSMALLINT* textLength) { + + if (recNumber < 1 || recNumber > (SQLSMALLINT)Errors_.size()) { + return SQL_NO_DATA; + } + const auto& err = Errors_[recNumber-1]; - if (sqlState) strncpy((char*)sqlState, err.SqlState.c_str(), 6); - if (nativeError) *nativeError = err.NativeError; + if (sqlState) { + strncpy((char*)sqlState, err.SqlState.c_str(), 6); + } + + if (nativeError) { + *nativeError = err.NativeError; + } + if (messageText && bufferLength > 0) { strncpy((char*)messageText, err.Message.c_str(), bufferLength); - if (textLength) *textLength = (SQLSMALLINT)std::min((int)err.Message.size(), (int)bufferLength); + if (textLength) { + *textLength = (SQLSMALLINT)std::min((int)err.Message.size(), (int)bufferLength); + } } return SQL_SUCCESS; } @@ -35,4 +51,4 @@ void TEnvironment::ClearErrors() { } } // namespace NOdbc -} // namespace NYdb \ No newline at end of file +} // namespace NYdb diff --git a/odbc/src/environment.h b/odbc/src/environment.h index a45d7f0b7e..0190b91383 100644 --- a/odbc/src/environment.h +++ b/odbc/src/environment.h @@ -37,4 +37,4 @@ class TEnvironment { }; } // namespace NOdbc -} // namespace NYdb \ No newline at end of file +} // namespace NYdb diff --git a/odbc/src/statement.cpp b/odbc/src/statement.cpp index 224cc8dc12..ab8318b0f9 100644 --- a/odbc/src/statement.cpp +++ b/odbc/src/statement.cpp @@ -1,6 +1,5 @@ #include "statement.h" -#include #include #include @@ -22,7 +21,6 @@ SQLRETURN TStatement::ExecDirect(const std::string& statementText) { if (!Errors_.empty()) { return SQL_ERROR; } - // --- конец сборки параметров --- auto sessionResult = client->GetSession().ExtractValueSync(); if (!sessionResult.IsSuccess()) { @@ -50,7 +48,6 @@ SQLRETURN TStatement::Fetch() { while (true) { if (ResultSetParser_) { if (ResultSetParser_->TryNextRow()) { - // Автоматически заполняем связанные буферы for (const auto& col : BoundColumns_) { GetData(col.ColumnNumber, col.TargetType, col.TargetValue, col.BufferLength, col.StrLenOrInd); } @@ -95,22 +92,38 @@ SQLRETURN TStatement::GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLRETURN TStatement::GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength) { - if (recNumber < 1 || recNumber > (SQLSMALLINT)Errors_.size()) return SQL_NO_DATA; + + if (recNumber < 1 || recNumber > (SQLSMALLINT)Errors_.size()) { + return SQL_NO_DATA; + } + const auto& err = Errors_[recNumber-1]; - if (sqlState) strncpy((char*)sqlState, err.SqlState.c_str(), 6); - if (nativeError) *nativeError = err.NativeError; + if (sqlState) { + strncpy((char*)sqlState, err.SqlState.c_str(), 6); + } + + if (nativeError) { + *nativeError = err.NativeError; + } + if (messageText && bufferLength > 0) { strncpy((char*)messageText, err.Message.c_str(), bufferLength); - if (textLength) *textLength = (SQLSMALLINT)std::min((int)err.Message.size(), (int)bufferLength); + if (textLength) { + *textLength = (SQLSMALLINT)std::min((int)err.Message.size(), (int)bufferLength); + } } return SQL_SUCCESS; } -SQLRETURN TStatement::BindCol(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) { - // Удаляем старую связь для этой колонки, если есть +SQLRETURN TStatement::BindCol(SQLUSMALLINT columnNumber, + SQLSMALLINT targetType, + SQLPOINTER targetValue, + SQLLEN bufferLength, + SQLLEN* strLenOrInd) { + BoundColumns_.erase(std::remove_if(BoundColumns_.begin(), BoundColumns_.end(), [columnNumber](const TBoundColumn& col) { return col.ColumnNumber == columnNumber; }), BoundColumns_.end()); - // Если targetValue == nullptr, просто удаляем связь + if (!targetValue) { return SQL_SUCCESS; } @@ -127,14 +140,15 @@ SQLRETURN TStatement::BindParameter(SQLUSMALLINT paramNumber, SQLPOINTER parameterValuePtr, SQLLEN bufferLength, SQLLEN* strLenOrIndPtr) { + if (inputOutputType != SQL_PARAM_INPUT) { AddError("HYC00", 0, "Only input parameters are supported"); return SQL_ERROR; } - // Удаляем старую связь для этого параметра, если есть + BoundParams_.erase(std::remove_if(BoundParams_.begin(), BoundParams_.end(), [paramNumber](const TBoundParam& p) { return p.ParamNumber == paramNumber; }), BoundParams_.end()); - // Если parameterValuePtr == nullptr, просто удаляем связь + if (!parameterValuePtr) { return SQL_SUCCESS; } @@ -162,7 +176,6 @@ SQLRETURN TStatement::ConvertYdbValue(NYdb::TValueParser& valueParser, SQLLEN bufferLength, SQLLEN* strLenOrInd) { - // 1. Проверка на NULL if (valueParser.IsNull()) { if (strLenOrInd) *strLenOrInd = SQL_NULL_DATA; return SQL_SUCCESS; @@ -249,7 +262,6 @@ SQLRETURN TStatement::ConvertYdbValue(NYdb::TValueParser& valueParser, if (strLenOrInd) *strLenOrInd = sizeof(char); return SQL_SUCCESS; } - // Добавьте обработку дат/времени, бинарных данных и других типов по необходимости default: return SQL_ERROR; } @@ -259,84 +271,12 @@ NYdb::TParams TStatement::BuildParams() { Errors_.clear(); NYdb::TParamsBuilder paramsBuilder; for (const auto& param : BoundParams_) { - std::string paramName = "$p" + std::to_string(param.ParamNumber); // ODBC нумерует с 1 - auto& builder = paramsBuilder.AddParam(paramName); - // Обработка NULL - if (param.StrLenOrIndPtr && *param.StrLenOrIndPtr == SQL_NULL_DATA) { - builder.EmptyOptional(); - builder.Build(); - continue; - } - - switch (param.ValueType) { - case SQL_C_SLONG: { - auto value = *static_cast(param.ParameterValuePtr); - switch (param.ParameterType) { - case SQL_INTEGER: - builder.Int32(static_cast(value)); - break; - case SQL_BIGINT: - builder.Int64(static_cast(value)); - break; - case SQL_DOUBLE: - builder.Double(static_cast(value)); - break; - case SQL_FLOAT: - builder.Float(static_cast(value)); - break; - case SQL_VARCHAR: - case SQL_CHAR: - case SQL_LONGVARCHAR: - builder.Utf8(std::to_string(value)); - break; - case SQL_BIT: - builder.Uint8(static_cast(value)); - break; - default: - AddError("07006", 0, "Unsupported SQL type"); - return paramsBuilder.Build(); - } - break; - } - case SQL_C_SBIGINT: { - auto v = *static_cast(param.ParameterValuePtr); - builder.Int32(static_cast(v)); - break; - } - default: { - AddError("07006", 0, "Unsupported C type"); - return paramsBuilder.Build(); - } - } - - switch (param.ParameterType) { - case SQL_INTEGER: - case SQL_BIGINT: - break; - case SQL_DOUBLE: - builder.Double(*reinterpret_cast(param.ParameterValuePtr)); - break; - case SQL_FLOAT: - builder.Double(*reinterpret_cast(param.ParameterValuePtr)); - break; - case SQL_VARCHAR: - case SQL_CHAR: - case SQL_LONGVARCHAR: - builder.Utf8(*reinterpret_cast(param.ParameterValuePtr)); - break; - case SQL_BIT: - builder.Bool(*reinterpret_cast(param.ParameterValuePtr)); - break; - default: - AddError("07006", 0, "Unsupported SQL type"); - return paramsBuilder.Build(); - } - - builder.Build(); + std::string paramName = "$p" + std::to_string(param.ParamNumber); + ConvertValue(param, paramsBuilder.AddParam(paramName)); } return paramsBuilder.Build(); } } // namespace NOdbc -} // namespace NYdb \ No newline at end of file +} // namespace NYdb diff --git a/odbc/src/statement.h b/odbc/src/statement.h index f34e0e771e..8f51be6759 100644 --- a/odbc/src/statement.h +++ b/odbc/src/statement.h @@ -1,25 +1,23 @@ #pragma once +#include "connection.h" +#include "utils/convert.h" + +#include + #include #include + #include #include #include -#include - -#include "connection.h" namespace NYdb { namespace NOdbc { class TStatement { private: - TConnection* Conn_; - TErrorList Errors_; - std::unique_ptr Iterator_; - std::unique_ptr ResultSetParser_; - struct TBoundColumn { SQLUSMALLINT ColumnNumber; SQLSMALLINT TargetType; @@ -27,21 +25,7 @@ class TStatement { SQLLEN BufferLength; SQLLEN* StrLenOrInd; }; - std::vector BoundColumns_; - - struct TBoundParam { - SQLUSMALLINT ParamNumber; - SQLSMALLINT InputOutputType; - SQLSMALLINT ValueType; - SQLSMALLINT ParameterType; - SQLULEN ColumnSize; - SQLSMALLINT DecimalDigits; - SQLPOINTER ParameterValuePtr; - SQLLEN BufferLength; - SQLLEN* StrLenOrIndPtr; - }; - std::vector BoundParams_; - + public: TStatement(TConnection* conn); @@ -49,24 +33,36 @@ class TStatement { SQLRETURN Fetch(); SQLRETURN GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd); + SQLRETURN GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength); + SQLRETURN BindCol(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd); SQLRETURN BindParameter(SQLUSMALLINT paramNumber, SQLSMALLINT inputOutputType, SQLSMALLINT valueType, SQLSMALLINT parameterType, SQLULEN columnSize, SQLSMALLINT decimalDigits, SQLPOINTER parameterValuePtr, SQLLEN bufferLength, SQLLEN* strLenOrIndPtr); - - TConnection* GetConnection() { return Conn_; } - + + TConnection* GetConnection() { + return Conn_; + } + void AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message); void ClearErrors(); NYdb::TParams BuildParams(); - + private: void ClearStatement(); SQLRETURN ConvertYdbValue(NYdb::TValueParser& valueParser, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd); + + TConnection* Conn_; + TErrorList Errors_; + std::unique_ptr Iterator_; + std::unique_ptr ResultSetParser_; + + std::vector BoundColumns_; + std::vector BoundParams_; }; } // namespace NOdbc -} // namespace NYdb \ No newline at end of file +} // namespace NYdb diff --git a/odbc/src/utils/convert.cpp b/odbc/src/utils/convert.cpp new file mode 100644 index 0000000000..2c62d56347 --- /dev/null +++ b/odbc/src/utils/convert.cpp @@ -0,0 +1,286 @@ +#include "convert.h" + +#include + +namespace NYdb { +namespace NOdbc { + +template +struct TSqlTypeTraits; + +template<> struct TSqlTypeTraits { using Type = std::string; }; +template<> struct TSqlTypeTraits { using Type = SQLBIGINT; }; +template<> struct TSqlTypeTraits { using Type = SQLUBIGINT; }; +template<> struct TSqlTypeTraits { using Type = SQLINTEGER; }; +template<> struct TSqlTypeTraits { using Type = SQLUINTEGER; }; +template<> struct TSqlTypeTraits { using Type = SQLSMALLINT; }; +template<> struct TSqlTypeTraits { using Type = SQLSMALLINT; }; +template<> struct TSqlTypeTraits { using Type = SQLUSMALLINT; }; +template<> struct TSqlTypeTraits { using Type = SQLSCHAR; }; +template<> struct TSqlTypeTraits { using Type = SQLCHAR; }; +template<> struct TSqlTypeTraits { using Type = SQLDOUBLE; }; +template<> struct TSqlTypeTraits { using Type = SQLFLOAT; }; +template<> struct TSqlTypeTraits { using Type = SQLCHAR; }; + +template +struct TTypedValue { + using TSrcType = typename TSqlTypeTraits::Type; + + TSrcType Data; + + TTypedValue(const TBoundParam& param) { + Data = *static_cast(param.ParameterValuePtr); + } +}; + +template<> +TTypedValue::TTypedValue(const TBoundParam& param) { + Data = std::string(static_cast(param.ParameterValuePtr), param.BufferLength); +} + +class IConverter { +public: + virtual void AddToBuilder(const TBoundParam& param, TParamValueBuilder& builder) = 0; + + virtual ~IConverter() = default; +}; + +template +class TConverter : public IConverter { +public: + virtual void AddToBuilder(const TBoundParam& param, TParamValueBuilder& builder) override { + TTypedValue value(param); + Convert(param, std::move(value.Data), builder); + if (param.StrLenOrIndPtr && *param.StrLenOrIndPtr == SQL_NULL_DATA) { + builder.EmptyOptional(GetType()); + } + builder.Build(); + } + +private: + void Convert(const TBoundParam& param, TTypedValue::TSrcType&& data, TParamValueBuilder& builder); + TType GetType(); +}; + +class TConverterRegistry { +public: + static TConverterRegistry& GetInstance() { + static TConverterRegistry instance; + return instance; + } + + void RegisterConverter(SQLSMALLINT cType, SQLSMALLINT sqlType, std::unique_ptr converter) { + Converters_.emplace(std::make_pair(cType, sqlType), std::move(converter)); + } + + IConverter* GetConverter(SQLSMALLINT cType, SQLSMALLINT sqlType) { + auto it = Converters_.find(std::make_pair(cType, sqlType)); + if (it != Converters_.end()) { + return it->second.get(); + } + return nullptr; + } + +private: + std::map, std::unique_ptr> Converters_; +}; + +#define REGISTER_CONVERTER(CType, SqlType, YdbType) \ + struct TConverterRegistration##CType##SqlType { \ + TConverterRegistration##CType##SqlType() { \ + TConverterRegistry::GetInstance().RegisterConverter(CType, SqlType, std::make_unique>()); \ + } \ + }; \ + static const TConverterRegistration##CType##SqlType converterRegistration##CType##SqlType; \ + template<> \ + TType TConverter::GetType() { \ + return TTypeBuilder().Primitive(YdbType).Build(); \ + } \ + template<> \ + void TConverter::Convert(const TBoundParam& param, TTypedValue::TSrcType&& data, TParamValueBuilder& builder) + +// Integer types + +REGISTER_CONVERTER(SQL_C_SBIGINT, SQL_BIGINT, EPrimitiveType::Int64) { + builder.OptionalInt64(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_LONG, SQL_BIGINT, EPrimitiveType::Int64) { + builder.OptionalInt64(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_SHORT, SQL_BIGINT, EPrimitiveType::Int64) { + builder.OptionalInt64(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_TINYINT, SQL_BIGINT, EPrimitiveType::Int64) { + builder.OptionalInt64(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_UBIGINT, SQL_BIGINT, EPrimitiveType::Uint64) { + builder.OptionalUint64(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_ULONG, SQL_BIGINT, EPrimitiveType::Uint64) { + builder.OptionalUint64(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_USHORT, SQL_BIGINT, EPrimitiveType::Uint64) { + builder.OptionalUint64(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_UTINYINT, SQL_BIGINT, EPrimitiveType::Uint64) { + builder.OptionalUint64(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_SBIGINT, SQL_INTEGER, EPrimitiveType::Int32) { + builder.OptionalInt32(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_LONG, SQL_INTEGER, EPrimitiveType::Int32) { + builder.OptionalInt32(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_SHORT, SQL_INTEGER, EPrimitiveType::Int32) { + builder.OptionalInt32(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_TINYINT, SQL_INTEGER, EPrimitiveType::Int32) { + builder.OptionalInt32(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_UBIGINT, SQL_INTEGER, EPrimitiveType::Uint32) { + builder.OptionalUint32(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_ULONG, SQL_INTEGER, EPrimitiveType::Uint32) { + builder.OptionalUint32(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_USHORT, SQL_INTEGER, EPrimitiveType::Uint32) { + builder.OptionalUint32(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_UTINYINT, SQL_INTEGER, EPrimitiveType::Uint32) { + builder.OptionalUint32(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_SBIGINT, SQL_SMALLINT, EPrimitiveType::Int16) { + builder.OptionalInt16(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_LONG, SQL_SMALLINT, EPrimitiveType::Int16) { + builder.OptionalInt16(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_SHORT, SQL_SMALLINT, EPrimitiveType::Int16) { + builder.OptionalInt16(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_TINYINT, SQL_SMALLINT, EPrimitiveType::Int16) { + builder.OptionalInt16(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_UBIGINT, SQL_SMALLINT, EPrimitiveType::Uint16) { + builder.OptionalUint16(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_ULONG, SQL_SMALLINT, EPrimitiveType::Uint16) { + builder.OptionalUint16(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_USHORT, SQL_SMALLINT, EPrimitiveType::Uint16) { + builder.OptionalUint16(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_UTINYINT, SQL_SMALLINT, EPrimitiveType::Uint16) { + builder.OptionalUint16(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_SBIGINT, SQL_TINYINT, EPrimitiveType::Int8) { + builder.OptionalInt8(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_LONG, SQL_TINYINT, EPrimitiveType::Int8) { + builder.OptionalInt8(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_SHORT, SQL_TINYINT, EPrimitiveType::Int8) { + builder.OptionalInt8(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_TINYINT, SQL_TINYINT, EPrimitiveType::Int8) { + builder.OptionalInt8(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_UBIGINT, SQL_TINYINT, EPrimitiveType::Uint8) { + builder.OptionalUint8(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_ULONG, SQL_TINYINT, EPrimitiveType::Uint8) { + builder.OptionalUint8(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_USHORT, SQL_TINYINT, EPrimitiveType::Uint8) { + builder.OptionalUint8(static_cast(data)); +} + +REGISTER_CONVERTER(SQL_C_UTINYINT, SQL_TINYINT, EPrimitiveType::Uint8) { + builder.OptionalUint8(static_cast(data)); +} + +// Floating point types + +REGISTER_CONVERTER(SQL_C_FLOAT, SQL_REAL, EPrimitiveType::Float) { + builder.OptionalFloat(data); +} + +REGISTER_CONVERTER(SQL_C_DOUBLE, SQL_FLOAT, EPrimitiveType::Double) { + builder.OptionalDouble(data); +} + +REGISTER_CONVERTER(SQL_C_DOUBLE, SQL_DOUBLE, EPrimitiveType::Double) { + builder.OptionalDouble(data); +} + +// String types + +REGISTER_CONVERTER(SQL_C_CHAR, SQL_CHAR, EPrimitiveType::Utf8) { + builder.OptionalUtf8(std::move(data)); +} + +REGISTER_CONVERTER(SQL_C_CHAR, SQL_VARCHAR, EPrimitiveType::Utf8) { + builder.OptionalUtf8(std::move(data)); +} + +REGISTER_CONVERTER(SQL_C_CHAR, SQL_LONGVARCHAR, EPrimitiveType::Utf8) { + builder.OptionalUtf8(std::move(data)); +} + +// Binary types + +REGISTER_CONVERTER(SQL_C_CHAR, SQL_BINARY, EPrimitiveType::String) { + builder.OptionalString(std::move(data)); +} + +REGISTER_CONVERTER(SQL_C_CHAR, SQL_VARBINARY, EPrimitiveType::String) { + builder.OptionalString(std::move(data)); +} + +REGISTER_CONVERTER(SQL_C_CHAR, SQL_LONGVARBINARY, EPrimitiveType::String) { + builder.OptionalString(std::move(data)); +} + +#undef REGISTER_CONVERTER + +void ConvertValue(const TBoundParam& param, TParamValueBuilder& builder) { + auto converter = TConverterRegistry::GetInstance().GetConverter(param.ValueType, param.ParameterType); + if (converter) { + converter->AddToBuilder(param, builder); + } else { + throw 1; // TODO: throw exception + } +} + +} // namespace NYdb +} // namespace NOdbc diff --git a/odbc/src/utils/convert.h b/odbc/src/utils/convert.h new file mode 100644 index 0000000000..525a43c79a --- /dev/null +++ b/odbc/src/utils/convert.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include +#include + +namespace NYdb { +namespace NOdbc { + +struct TBoundParam { + SQLUSMALLINT ParamNumber; + SQLSMALLINT InputOutputType; + SQLSMALLINT ValueType; + SQLSMALLINT ParameterType; + SQLULEN ColumnSize; + SQLSMALLINT DecimalDigits; + SQLPOINTER ParameterValuePtr; + SQLLEN BufferLength; + SQLLEN* StrLenOrIndPtr; +}; + +void ConvertValue(const TBoundParam& param, TParamValueBuilder& builder); + +} // namespace NYdb +} // namespace NOdbc diff --git a/tests/integration/sessions/CMakeLists.txt b/tests/integration/sessions/CMakeLists.txt index 100c8ace2b..0cc47bfd4c 100644 --- a/tests/integration/sessions/CMakeLists.txt +++ b/tests/integration/sessions/CMakeLists.txt @@ -3,8 +3,8 @@ add_ydb_test(NAME sessions_it GTEST main.cpp LINK_LIBRARIES yutil - YDB-CPP-SDK::Table - YDB-CPP-SDK::Query + ydb-cpp-sdk::Table + ydb-cpp-sdk::Query api-grpc grpc-client LABELS diff --git a/tests/integration/sessions_pool/CMakeLists.txt b/tests/integration/sessions_pool/CMakeLists.txt index 6e7a6a70ab..d37d9d500e 100644 --- a/tests/integration/sessions_pool/CMakeLists.txt +++ b/tests/integration/sessions_pool/CMakeLists.txt @@ -3,7 +3,7 @@ add_ydb_test(NAME sessions_pool_it GTEST main.cpp LINK_LIBRARIES yutil - YDB-CPP-SDK::Table + ydb-cpp-sdk::Table api-grpc LABELS integration diff --git a/tests/integration/topic/CMakeLists.txt b/tests/integration/topic/CMakeLists.txt index 62d372aaa7..15ebbd5165 100644 --- a/tests/integration/topic/CMakeLists.txt +++ b/tests/integration/topic/CMakeLists.txt @@ -3,7 +3,7 @@ add_ydb_test(NAME topic_it GTEST basic_usage.cpp LINK_LIBRARIES yutil - YDB-CPP-SDK::Topic + ydb-cpp-sdk::Topic cpp-client-ydb_persqueue_public api-grpc LABELS From 1b25daaf5e2f0dec858c0f62f3744be88ea11d0a Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 26 May 2025 19:07:52 +0000 Subject: [PATCH 06/11] Add unit test --- cmake/testing.cmake | 30 ++++++++ odbc/CMakeLists.txt | 10 +-- odbc/tests/CMakeLists.txt | 2 + odbc/tests/unit/CMakeLists.txt | 10 +++ odbc/tests/unit/convert_ut.cpp | 122 +++++++++++++++++++++++++++++++++ 5 files changed, 165 insertions(+), 9 deletions(-) create mode 100644 odbc/tests/CMakeLists.txt create mode 100644 odbc/tests/unit/CMakeLists.txt create mode 100644 odbc/tests/unit/convert_ut.cpp diff --git a/cmake/testing.cmake b/cmake/testing.cmake index d2a2050e23..1a650051a4 100644 --- a/cmake/testing.cmake +++ b/cmake/testing.cmake @@ -83,3 +83,33 @@ function(add_ydb_test) vcs_info(${YDB_TEST_NAME}) endfunction() + +if (YDB_SDK_ODBC) + function(add_odbc_test) + set(opts "") + set(oneval_args NAME WORKING_DIRECTORY OUTPUT_DIRECTORY) + set(multival_args SOURCES LINK_LIBRARIES LABELS) + cmake_parse_arguments(ODBC_TEST + "${opts}" + "${oneval_args}" + "${multival_args}" + ${ARGN} + ) + + add_ydb_test(GTEST + NAME ${ODBC_TEST_NAME} + SOURCES ${ODBC_TEST_SOURCES} + LINK_LIBRARIES + ${ODBC_TEST_LINK_LIBRARIES} + ODBC::ODBC + LABELS ${ODBC_TEST_LABELS} + ) + + target_compile_definitions(${ODBC_TEST_NAME} + PRIVATE + ODBC_DRIVER_PATH="$" + ) + + add_dependencies(${ODBC_TEST_NAME} ydb-odbc) + endfunction() +endif() diff --git a/odbc/CMakeLists.txt b/odbc/CMakeLists.txt index c45baef5f5..95ce1702d4 100644 --- a/odbc/CMakeLists.txt +++ b/odbc/CMakeLists.txt @@ -1,4 +1,3 @@ -# Добавляем исходники add_library(ydb-odbc SHARED src/utils/convert.cpp src/odbc_driver.cpp @@ -7,14 +6,12 @@ add_library(ydb-odbc SHARED src/environment.cpp ) -# Добавляем заголовочные файлы target_include_directories(ydb-odbc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${ODBC_INCLUDE_DIRS} ) -# Линкуем с YDB SDK и ODBC target_link_libraries(ydb-odbc PRIVATE ydb-cpp-sdk::Query @@ -27,22 +24,17 @@ set_target_properties(ydb-odbc PROPERTIES POSITION_INDEPENDENT_CODE ON ) -# Устанавливаем драйвер install(TARGETS ydb-odbc LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) -# Устанавливаем заголовочные файлы install(DIRECTORY include/ DESTINATION include/ydb-odbc ) add_subdirectory(examples) +add_subdirectory(tests) -# Добавляем тесты -# add_subdirectory(tests) - -# Правила установки include(GNUInstallDirs) install(FILES diff --git a/odbc/tests/CMakeLists.txt b/odbc/tests/CMakeLists.txt new file mode 100644 index 0000000000..446b6139f9 --- /dev/null +++ b/odbc/tests/CMakeLists.txt @@ -0,0 +1,2 @@ +#add_subdirectory(integration) +add_subdirectory(unit) diff --git a/odbc/tests/unit/CMakeLists.txt b/odbc/tests/unit/CMakeLists.txt new file mode 100644 index 0000000000..d1eac19961 --- /dev/null +++ b/odbc/tests/unit/CMakeLists.txt @@ -0,0 +1,10 @@ +add_ydb_test(NAME odbc-convert_ut GTEST + SOURCES + convert_ut.cpp + LINK_LIBRARIES + yutil + api-protos + ydb-odbc + LABELS + unit +) diff --git a/odbc/tests/unit/convert_ut.cpp b/odbc/tests/unit/convert_ut.cpp new file mode 100644 index 0000000000..6df6be54f1 --- /dev/null +++ b/odbc/tests/unit/convert_ut.cpp @@ -0,0 +1,122 @@ +#include +#undef BOOL + +#include + +#include + +#include + +#include + +using namespace NYdb::NOdbc; +using namespace NYdb; + +void CheckProtoValue(const Ydb::Value& value, const std::string& expected) { + std::string protoStr; + google::protobuf::TextFormat::PrintToString(value, &protoStr); + ASSERT_EQ(protoStr, expected); +} + +TEST(OdbcConvert, Int64ToYdb) { + SQLBIGINT v = 42; + TBoundParam param{ + 1, // ParamNumber + SQL_PARAM_INPUT, // InputOutputType + SQL_C_SBIGINT, // ValueType + SQL_BIGINT, // ParameterType + 0, 0, // ColumnSize, DecimalDigits + &v, // ParameterValuePtr + sizeof(v), // BufferLength + nullptr // StrLenOrIndPtr + }; + + TParamsBuilder paramsBuilder; + ConvertValue(param, paramsBuilder.AddParam("$p1")); + auto params = paramsBuilder.Build(); + auto value = params.GetValue("$p1"); + ASSERT_TRUE(value); + CheckProtoValue(value->GetProto(), "int64_value: 42\n"); +} + +TEST(OdbcConvert, Uint64ToYdb) { + SQLUBIGINT v = 123; + TBoundParam param{ + 1, SQL_PARAM_INPUT, SQL_C_UBIGINT, SQL_BIGINT, 0, 0, &v, sizeof(v), nullptr + }; + TParamsBuilder paramsBuilder; + ConvertValue(param, paramsBuilder.AddParam("$p1")); + auto params = paramsBuilder.Build(); + auto value = params.GetValue("$p1"); + ASSERT_TRUE(value); + CheckProtoValue(value->GetProto(), "uint64_value: 123\n"); +} + +TEST(OdbcConvert, DoubleToYdb) { + SQLDOUBLE v = 3.14; + TBoundParam param{ + 1, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_DOUBLE, 0, 0, &v, sizeof(v), nullptr + }; + TParamsBuilder paramsBuilder; + ConvertValue(param, paramsBuilder.AddParam("$p1")); + auto params = paramsBuilder.Build(); + auto value = params.GetValue("$p1"); + ASSERT_TRUE(value); + CheckProtoValue(value->GetProto(), "double_value: 3.14\n"); +} + +TEST(OdbcConvert, StringToYdbUtf8) { + const char* str = "hello"; + SQLLEN len = 5; + TBoundParam param{ + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 0, 0, (SQLPOINTER)str, len, nullptr + }; + TParamsBuilder paramsBuilder; + ConvertValue(param, paramsBuilder.AddParam("$p1")); + auto params = paramsBuilder.Build(); + auto value = params.GetValue("$p1"); + ASSERT_TRUE(value); + CheckProtoValue(value->GetProto(), "text_value: \"hello\"\n"); +} + +TEST(OdbcConvert, StringToYdbBinary) { + const char* str = "bin\x01\x02"; + SQLLEN len = 5; + TBoundParam param{ + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_BINARY, 0, 0, (SQLPOINTER)str, len, nullptr + }; + TParamsBuilder paramsBuilder; + ConvertValue(param, paramsBuilder.AddParam("$p1")); + auto params = paramsBuilder.Build(); + auto value = params.GetValue("$p1"); + ASSERT_TRUE(value); + ASSERT_EQ(value->GetProto().bytes_value(), std::string(str, len)); +} + +TEST(OdbcConvert, Int64NullToYdb) { + SQLBIGINT v = 42; + SQLLEN nullInd = SQL_NULL_DATA; + TBoundParam param{ + 1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, 0, 0, &v, sizeof(v), &nullInd + }; + TParamsBuilder paramsBuilder; + ConvertValue(param, paramsBuilder.AddParam("$p1")); + auto params = paramsBuilder.Build(); + auto value = params.GetValue("$p1"); + ASSERT_TRUE(value); + ASSERT_EQ(value->GetProto().null_flag_value(), ::google::protobuf::NullValue::NULL_VALUE); +} + +TEST(OdbcConvert, StringNullToYdb) { + const char* str = "test"; + SQLLEN nullInd = SQL_NULL_DATA; + TBoundParam param{ + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 0, 0, (SQLPOINTER)str, 4, &nullInd + }; + TParamsBuilder paramsBuilder; + ConvertValue(param, paramsBuilder.AddParam("$p1")); + auto params = paramsBuilder.Build(); + auto value = params.GetValue("$p1"); + ASSERT_TRUE(value); + ASSERT_EQ(value->GetProto().null_flag_value(), ::google::protobuf::NullValue::NULL_VALUE); +} From 0bdc4ccfef5ad96b8fa6eb82f2e5a787f82f4118 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 26 May 2025 19:10:35 +0000 Subject: [PATCH 07/11] fix --- CMakeLists.txt | 4 ++-- cmake/common.cmake | 2 +- cmake/ydb-cpp-sdk-config.cmake.in | 4 ++-- examples/basic_example/CMakeLists.txt | 6 +++--- examples/bulk_upsert_simple/CMakeLists.txt | 2 +- examples/pagination/CMakeLists.txt | 2 +- examples/secondary_index/CMakeLists.txt | 2 +- .../secondary_index_builtin/CMakeLists.txt | 2 +- .../topic_reader/eventloop/CMakeLists.txt | 2 +- examples/topic_reader/simple/CMakeLists.txt | 2 +- .../topic_reader/transaction/CMakeLists.txt | 2 +- examples/ttl/CMakeLists.txt | 2 +- examples/vector_index/CMakeLists.txt | 2 +- odbc/CMakeLists.txt | 6 +++--- .../integration/basic_example/CMakeLists.txt | 6 +++--- tests/integration/bulk_upsert/CMakeLists.txt | 2 +- .../integration/server_restart/CMakeLists.txt | 2 +- tests/integration/sessions/CMakeLists.txt | 4 ++-- .../integration/sessions_pool/CMakeLists.txt | 2 +- tests/integration/topic/CMakeLists.txt | 2 +- tests/unit/client/CMakeLists.txt | 20 +++++++++---------- 21 files changed, 39 insertions(+), 39 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd1eef3b6f..30e9ba2f7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ string(REGEX MATCH "YDB_SDK_VERSION = \"([0-9]+\\.[0-9]+\\.[0-9]+)\"" _ ${YDB_SD set(YDB_SDK_VERSION ${CMAKE_MATCH_1}) message(STATUS "YDB С++ SDK version: ${YDB_SDK_VERSION}") -project(ydb-cpp-sdk VERSION ${YDB_SDK_VERSION} LANGUAGES C CXX ASM) +project(YDB-CPP-SDK VERSION ${YDB_SDK_VERSION} LANGUAGES C CXX ASM) option(YDB_SDK_INSTALL "Install YDB C++ SDK" Off) option(YDB_SDK_TESTS "Build YDB C++ SDK tests" Off) @@ -79,7 +79,7 @@ if (YDB_SDK_INSTALL) install(EXPORT ydb-cpp-sdk-targets FILE ydb-cpp-sdk-targets.cmake CONFIGURATIONS RELEASE - NAMESPACE ydb-cpp-sdk:: + NAMESPACE YDB-CPP-SDK:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ydb-cpp-sdk/release ) configure_package_config_file( diff --git a/cmake/common.cmake b/cmake/common.cmake index 5504479472..89ebb5eaca 100644 --- a/cmake/common.cmake +++ b/cmake/common.cmake @@ -173,7 +173,7 @@ function(resources Tgt Output) endfunction() function(_ydb_sdk_make_client_component CmpName Tgt) - add_library(ydb-cpp-sdk::${CmpName} ALIAS ${Tgt}) + add_library(YDB-CPP-SDK::${CmpName} ALIAS ${Tgt}) _ydb_sdk_install_targets(TARGETS ${Tgt} ${ARGN}) set(YDB-CPP-SDK_AVAILABLE_COMPONENTS ${YDB-CPP-SDK_AVAILABLE_COMPONENTS} ${CmpName} CACHE INTERNAL "") diff --git a/cmake/ydb-cpp-sdk-config.cmake.in b/cmake/ydb-cpp-sdk-config.cmake.in index 2ed5dc4190..ba1c144f0a 100644 --- a/cmake/ydb-cpp-sdk-config.cmake.in +++ b/cmake/ydb-cpp-sdk-config.cmake.in @@ -44,7 +44,7 @@ function(_find_ydb_sdk_component CompName) message(FATAL_ERROR "${CompName} is not available component") endif() list(GET YDB-CPP-SDK_COMPONENT_TARGETS ${CompId} Tgt) - add_library(ydb-cpp-sdk::${CompName} ALIAS ydb-cpp-sdk::${Tgt}) + add_library(YDB-CPP-SDK::${CompName} ALIAS YDB-CPP-SDK::${Tgt}) set(YDB-CPP-SDK_${CompName}_FOUND TRUE PARENT_SCOPE) endfunction() @@ -56,4 +56,4 @@ endforeach() @PACKAGE_INIT@ -check_required_components(ydb-cpp-sdk) +check_required_components(YDB-CPP-SDK) diff --git a/examples/basic_example/CMakeLists.txt b/examples/basic_example/CMakeLists.txt index 8e51343941..75d2d1b253 100644 --- a/examples/basic_example/CMakeLists.txt +++ b/examples/basic_example/CMakeLists.txt @@ -3,9 +3,9 @@ add_executable(basic_example) target_link_libraries(basic_example PUBLIC yutil getopt - ydb-cpp-sdk::Query - ydb-cpp-sdk::Params - ydb-cpp-sdk::Driver + YDB-CPP-SDK::Query + YDB-CPP-SDK::Params + YDB-CPP-SDK::Driver ) target_sources(basic_example PRIVATE diff --git a/examples/bulk_upsert_simple/CMakeLists.txt b/examples/bulk_upsert_simple/CMakeLists.txt index 34b8ed62c3..4f7c3eca7f 100644 --- a/examples/bulk_upsert_simple/CMakeLists.txt +++ b/examples/bulk_upsert_simple/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(bulk_upsert_simple) target_link_libraries(bulk_upsert_simple PUBLIC yutil getopt - ydb-cpp-sdk::Table + YDB-CPP-SDK::Table ) target_sources(bulk_upsert_simple PRIVATE diff --git a/examples/pagination/CMakeLists.txt b/examples/pagination/CMakeLists.txt index 2b29726f00..0936f38558 100644 --- a/examples/pagination/CMakeLists.txt +++ b/examples/pagination/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(pagination) target_link_libraries(pagination PUBLIC yutil getopt - ydb-cpp-sdk::Table + YDB-CPP-SDK::Table ) target_sources(pagination PRIVATE diff --git a/examples/secondary_index/CMakeLists.txt b/examples/secondary_index/CMakeLists.txt index 47364c5597..6030de5f7f 100644 --- a/examples/secondary_index/CMakeLists.txt +++ b/examples/secondary_index/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(secondary_index) target_link_libraries(secondary_index PUBLIC yutil getopt - ydb-cpp-sdk::Table + YDB-CPP-SDK::Table ) target_sources(secondary_index PRIVATE diff --git a/examples/secondary_index_builtin/CMakeLists.txt b/examples/secondary_index_builtin/CMakeLists.txt index e03e675827..b46cc79159 100644 --- a/examples/secondary_index_builtin/CMakeLists.txt +++ b/examples/secondary_index_builtin/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(secondary_index_builtin) target_link_libraries(secondary_index_builtin PUBLIC yutil getopt - ydb-cpp-sdk::Table + YDB-CPP-SDK::Table ) target_sources(secondary_index_builtin PRIVATE diff --git a/examples/topic_reader/eventloop/CMakeLists.txt b/examples/topic_reader/eventloop/CMakeLists.txt index 114a0ae35b..2cdc984955 100644 --- a/examples/topic_reader/eventloop/CMakeLists.txt +++ b/examples/topic_reader/eventloop/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(persqueue_reader_eventloop) target_link_libraries(persqueue_reader_eventloop PUBLIC yutil - ydb-cpp-sdk::Topic + YDB-CPP-SDK::Topic getopt ) diff --git a/examples/topic_reader/simple/CMakeLists.txt b/examples/topic_reader/simple/CMakeLists.txt index 2b7da165f0..68846ab215 100644 --- a/examples/topic_reader/simple/CMakeLists.txt +++ b/examples/topic_reader/simple/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(simple_persqueue_reader) target_link_libraries(simple_persqueue_reader PUBLIC yutil - ydb-cpp-sdk::Topic + YDB-CPP-SDK::Topic getopt ) diff --git a/examples/topic_reader/transaction/CMakeLists.txt b/examples/topic_reader/transaction/CMakeLists.txt index 77fd8ab446..64d30b4d8c 100644 --- a/examples/topic_reader/transaction/CMakeLists.txt +++ b/examples/topic_reader/transaction/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(read_from_topic_in_transaction) target_link_libraries(read_from_topic_in_transaction PUBLIC yutil - ydb-cpp-sdk::Topic + YDB-CPP-SDK::Topic getopt ) diff --git a/examples/ttl/CMakeLists.txt b/examples/ttl/CMakeLists.txt index 9a0655e7d1..48a004b4cc 100644 --- a/examples/ttl/CMakeLists.txt +++ b/examples/ttl/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(ttl) target_link_libraries(ttl PUBLIC yutil getopt - ydb-cpp-sdk::Table + YDB-CPP-SDK::Table ) target_sources(ttl PRIVATE diff --git a/examples/vector_index/CMakeLists.txt b/examples/vector_index/CMakeLists.txt index b5aac62195..cd836a00b4 100644 --- a/examples/vector_index/CMakeLists.txt +++ b/examples/vector_index/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(vector_index) target_link_libraries(vector_index PUBLIC yutil getopt - ydb-cpp-sdk::Table + YDB-CPP-SDK::Table ) target_sources(vector_index PRIVATE diff --git a/odbc/CMakeLists.txt b/odbc/CMakeLists.txt index 95ce1702d4..6133906d9c 100644 --- a/odbc/CMakeLists.txt +++ b/odbc/CMakeLists.txt @@ -14,9 +14,9 @@ target_include_directories(ydb-odbc target_link_libraries(ydb-odbc PRIVATE - ydb-cpp-sdk::Query - ydb-cpp-sdk::Table - ydb-cpp-sdk::Driver + YDB-CPP-SDK::Query + YDB-CPP-SDK::Table + YDB-CPP-SDK::Driver ODBC::ODBC ) diff --git a/tests/integration/basic_example/CMakeLists.txt b/tests/integration/basic_example/CMakeLists.txt index 9eec918ec0..55bdd05341 100644 --- a/tests/integration/basic_example/CMakeLists.txt +++ b/tests/integration/basic_example/CMakeLists.txt @@ -6,9 +6,9 @@ add_ydb_test(NAME basic_example_it GTEST LINK_LIBRARIES yutil api-protos - ydb-cpp-sdk::Driver - ydb-cpp-sdk::Proto - ydb-cpp-sdk::Table + YDB-CPP-SDK::Driver + YDB-CPP-SDK::Proto + YDB-CPP-SDK::Table LABELS integration ) diff --git a/tests/integration/bulk_upsert/CMakeLists.txt b/tests/integration/bulk_upsert/CMakeLists.txt index 535d21f2d6..46848877c6 100644 --- a/tests/integration/bulk_upsert/CMakeLists.txt +++ b/tests/integration/bulk_upsert/CMakeLists.txt @@ -5,7 +5,7 @@ add_ydb_test(NAME bulk_upsert_it GTEST bulk_upsert.h LINK_LIBRARIES yutil - ydb-cpp-sdk::Table + YDB-CPP-SDK::Table LABELS integration ) diff --git a/tests/integration/server_restart/CMakeLists.txt b/tests/integration/server_restart/CMakeLists.txt index 66d1c00d64..2d485de4e4 100644 --- a/tests/integration/server_restart/CMakeLists.txt +++ b/tests/integration/server_restart/CMakeLists.txt @@ -4,7 +4,7 @@ add_ydb_test(NAME server_restart_it GTEST LINK_LIBRARIES yutil api-grpc - ydb-cpp-sdk::Query + YDB-CPP-SDK::Query gRPC::grpc++ LABELS integration diff --git a/tests/integration/sessions/CMakeLists.txt b/tests/integration/sessions/CMakeLists.txt index 0cc47bfd4c..100c8ace2b 100644 --- a/tests/integration/sessions/CMakeLists.txt +++ b/tests/integration/sessions/CMakeLists.txt @@ -3,8 +3,8 @@ add_ydb_test(NAME sessions_it GTEST main.cpp LINK_LIBRARIES yutil - ydb-cpp-sdk::Table - ydb-cpp-sdk::Query + YDB-CPP-SDK::Table + YDB-CPP-SDK::Query api-grpc grpc-client LABELS diff --git a/tests/integration/sessions_pool/CMakeLists.txt b/tests/integration/sessions_pool/CMakeLists.txt index d37d9d500e..6e7a6a70ab 100644 --- a/tests/integration/sessions_pool/CMakeLists.txt +++ b/tests/integration/sessions_pool/CMakeLists.txt @@ -3,7 +3,7 @@ add_ydb_test(NAME sessions_pool_it GTEST main.cpp LINK_LIBRARIES yutil - ydb-cpp-sdk::Table + YDB-CPP-SDK::Table api-grpc LABELS integration diff --git a/tests/integration/topic/CMakeLists.txt b/tests/integration/topic/CMakeLists.txt index 15ebbd5165..62d372aaa7 100644 --- a/tests/integration/topic/CMakeLists.txt +++ b/tests/integration/topic/CMakeLists.txt @@ -3,7 +3,7 @@ add_ydb_test(NAME topic_it GTEST basic_usage.cpp LINK_LIBRARIES yutil - ydb-cpp-sdk::Topic + YDB-CPP-SDK::Topic cpp-client-ydb_persqueue_public api-grpc LABELS diff --git a/tests/unit/client/CMakeLists.txt b/tests/unit/client/CMakeLists.txt index b667e25584..f38938b7f9 100644 --- a/tests/unit/client/CMakeLists.txt +++ b/tests/unit/client/CMakeLists.txt @@ -3,7 +3,7 @@ add_ydb_test(NAME client-ydb_coordination_ut coordination/coordination_ut.cpp LINK_LIBRARIES yutil - ydb-cpp-sdk::Coordination + YDB-CPP-SDK::Coordination api-grpc LABELS unit @@ -14,8 +14,8 @@ add_ydb_test(NAME client-extensions-discovery_mutator_ut discovery_mutator/discovery_mutator_ut.cpp LINK_LIBRARIES yutil - ydb-cpp-sdk::DiscoveryMutator - ydb-cpp-sdk::Table + YDB-CPP-SDK::DiscoveryMutator + YDB-CPP-SDK::Table LABELS unit ) @@ -25,8 +25,8 @@ add_ydb_test(NAME client-ydb_driver_ut driver/driver_ut.cpp LINK_LIBRARIES yutil - ydb-cpp-sdk::Driver - ydb-cpp-sdk::Table + YDB-CPP-SDK::Driver + YDB-CPP-SDK::Table LABELS unit ) @@ -62,7 +62,7 @@ add_ydb_test(NAME client-ydb_params_ut GTEST params/params_ut.cpp LINK_LIBRARIES yutil - ydb-cpp-sdk::Params + YDB-CPP-SDK::Params LABELS unit ) @@ -72,8 +72,8 @@ add_ydb_test(NAME client-ydb_result_ut result/result_ut.cpp LINK_LIBRARIES yutil - ydb-cpp-sdk::Result - ydb-cpp-sdk::Params + YDB-CPP-SDK::Result + YDB-CPP-SDK::Params LABELS unit ) @@ -83,8 +83,8 @@ add_ydb_test(NAME client-ydb_value_ut GTEST value/value_ut.cpp LINK_LIBRARIES yutil - ydb-cpp-sdk::Value - ydb-cpp-sdk::Params + YDB-CPP-SDK::Value + YDB-CPP-SDK::Params LABELS unit ) From 9699eaf6f9c05e20b8b1492e8891fd5f474aa417 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 26 May 2025 20:56:06 +0000 Subject: [PATCH 08/11] added tx support --- odbc/src/connection.cpp | 46 +++++++++++++++++++++++++-- odbc/src/connection.h | 17 +++++++--- odbc/src/odbc_driver.cpp | 58 ++++++++++++++++++++++++++++++++++ odbc/src/statement.cpp | 19 ++++++++--- odbc/src/utils/convert.cpp | 12 +++++-- odbc/tests/unit/convert_ut.cpp | 26 +++++++++------ 6 files changed, 154 insertions(+), 24 deletions(-) diff --git a/odbc/src/connection.cpp b/odbc/src/connection.cpp index a2c0df7c54..6fbd8cf1e3 100644 --- a/odbc/src/connection.cpp +++ b/odbc/src/connection.cpp @@ -122,9 +122,49 @@ void TConnection::ClearErrors() { Errors_.clear(); } -std::pair TConnection::ParseConnectionString(const std::string& connectionString) { - // TODO: Implement - return {"", ""}; +SQLRETURN TConnection::SetAutocommit(bool value) { + Autocommit_ = value; + if (Autocommit_ && Tx_) { + auto status = Tx_->Commit().ExtractValueSync(); + if (!status.IsSuccess()) { + AddError("08001", 0, "Failed to commit transaction"); + return SQL_ERROR; + } + Tx_.reset(); + } + return SQL_SUCCESS; +} + +bool TConnection::GetAutocommit() const { + return Autocommit_; +} + +const std::optional& TConnection::GetTx() { + return Tx_; +} + +void TConnection::SetTx(const NQuery::TTransaction& tx) { + Tx_ = tx; +} + +SQLRETURN TConnection::CommitTx() { + auto status = Tx_->Commit().ExtractValueSync(); + if (!status.IsSuccess()) { + AddError("08001", 0, "Failed to commit transaction"); + return SQL_ERROR; + } + Tx_.reset(); + return SQL_SUCCESS; +} + +SQLRETURN TConnection::RollbackTx() { + auto status = Tx_->Rollback().ExtractValueSync(); + if (!status.IsSuccess()) { + AddError("08001", 0, "Failed to rollback transaction"); + return SQL_ERROR; + } + Tx_.reset(); + return SQL_SUCCESS; } } // namespace NOdbc diff --git a/odbc/src/connection.h b/odbc/src/connection.h index 95c872f04b..2b2fe22c81 100644 --- a/odbc/src/connection.h +++ b/odbc/src/connection.h @@ -19,8 +19,9 @@ class TStatement; class TConnection { private: - std::unique_ptr YdbDriver_; - std::unique_ptr YdbClient_; + std::unique_ptr YdbDriver_; + std::unique_ptr YdbClient_; + std::optional Tx_; TErrorList Errors_; std::vector> Statements_; @@ -28,6 +29,8 @@ class TConnection { std::string Database_; std::string AuthToken_; + bool Autocommit_ = true; + public: SQLRETURN Connect(const std::string& serverName, const std::string& userName, @@ -46,8 +49,14 @@ class TConnection { void AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message); void ClearErrors(); -private: - std::pair ParseConnectionString(const std::string& connectionString); + SQLRETURN SetAutocommit(bool value); + bool GetAutocommit() const; + + const std::optional& GetTx(); + void SetTx(const NQuery::TTransaction& tx); + + SQLRETURN CommitTx(); + SQLRETURN RollbackTx(); }; } // namespace NOdbc diff --git a/odbc/src/odbc_driver.cpp b/odbc/src/odbc_driver.cpp index 6f3b865035..26de48e6af 100644 --- a/odbc/src/odbc_driver.cpp +++ b/odbc/src/odbc_driver.cpp @@ -250,4 +250,62 @@ SQLRETURN SQL_API SQLBindParameter(SQLHSTMT statementHandle, return stmt->BindParameter(paramNumber, inputOutputType, valueType, parameterType, columnSize, decimalDigits, parameterValuePtr, bufferLength, strLenOrIndPtr); } +SQLRETURN SQL_API SQLEndTran(SQLSMALLINT handleType, SQLHANDLE handle, SQLSMALLINT completionType) { + if (!handle) { + return SQL_INVALID_HANDLE; + } + try { + switch (handleType) { + case SQL_HANDLE_DBC: { + auto conn = static_cast(handle); + if (completionType == SQL_COMMIT) { + return conn->CommitTx(); + } else if (completionType == SQL_ROLLBACK) { + return conn->RollbackTx(); + } else { + return SQL_ERROR; + } + } + case SQL_HANDLE_STMT: { + auto stmt = static_cast(handle); + auto conn = stmt->GetConnection(); + if (!conn) return SQL_INVALID_HANDLE; + if (completionType == SQL_COMMIT) { + return conn->CommitTx(); + } else if (completionType == SQL_ROLLBACK) { + return conn->RollbackTx(); + } else { + return SQL_ERROR; + } + } + case SQL_HANDLE_ENV: { + // TODO: if's list of connections in ENV, go through them and commit/rollback transactions + return SQL_SUCCESS; + } + default: + return SQL_ERROR; + } + } catch (...) { + return SQL_ERROR; + } +} + +SQLRETURN SQL_API SQLSetConnectAttr(SQLHDBC connectionHandle, SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER stringLength) { + auto conn = static_cast(connectionHandle); + if (!conn) { + return SQL_INVALID_HANDLE; + } + if (attribute == SQL_ATTR_AUTOCOMMIT) { + if ((intptr_t)value == SQL_AUTOCOMMIT_ON) { + return conn->SetAutocommit(true); + } else if ((intptr_t)value == SQL_AUTOCOMMIT_OFF) { + return conn->SetAutocommit(false); + } else { + return SQL_ERROR; + } + } + // TODO: other attributes + return SQL_ERROR; +} + } diff --git a/odbc/src/statement.cpp b/odbc/src/statement.cpp index ab8318b0f9..3d19988a0b 100644 --- a/odbc/src/statement.cpp +++ b/odbc/src/statement.cpp @@ -22,14 +22,23 @@ SQLRETURN TStatement::ExecDirect(const std::string& statementText) { return SQL_ERROR; } - auto sessionResult = client->GetSession().ExtractValueSync(); - if (!sessionResult.IsSuccess()) { - return SQL_ERROR; + if (!Conn_->GetTx()) { + auto sessionResult = client->GetSession().ExtractValueSync(); + if (!sessionResult.IsSuccess()) { + return SQL_ERROR; + } + auto session = sessionResult.GetSession(); + auto beginTxResult = session.BeginTransaction(NQuery::TTxSettings::SerializableRW()).ExtractValueSync(); + if (!beginTxResult.IsSuccess()) { + return SQL_ERROR; + } + Conn_->SetTx(beginTxResult.GetTransaction()); } - auto session = sessionResult.GetSession(); + auto session = Conn_->GetTx()->GetSession(); + auto iterator = session.StreamExecuteQuery(statementText, + NQuery::TTxControl::Tx(*Conn_->GetTx()).CommitTx(Conn_->GetAutocommit()), params).ExtractValueSync(); - auto iterator = session.StreamExecuteQuery(statementText, NYdb::NQuery::TTxControl::NoTx(), params).ExtractValueSync(); if (!iterator.IsSuccess()) { return SQL_ERROR; } diff --git a/odbc/src/utils/convert.cpp b/odbc/src/utils/convert.cpp index 2c62d56347..432418315a 100644 --- a/odbc/src/utils/convert.cpp +++ b/odbc/src/utils/convert.cpp @@ -9,6 +9,7 @@ template struct TSqlTypeTraits; template<> struct TSqlTypeTraits { using Type = std::string; }; +template<> struct TSqlTypeTraits { using Type = std::string; }; template<> struct TSqlTypeTraits { using Type = SQLBIGINT; }; template<> struct TSqlTypeTraits { using Type = SQLUBIGINT; }; template<> struct TSqlTypeTraits { using Type = SQLINTEGER; }; @@ -38,6 +39,11 @@ TTypedValue::TTypedValue(const TBoundParam& param) { Data = std::string(static_cast(param.ParameterValuePtr), param.BufferLength); } +template<> +TTypedValue::TTypedValue(const TBoundParam& param) { + Data = std::string(static_cast(param.ParameterValuePtr), param.BufferLength); +} + class IConverter { public: virtual void AddToBuilder(const TBoundParam& param, TParamValueBuilder& builder) = 0; @@ -259,15 +265,15 @@ REGISTER_CONVERTER(SQL_C_CHAR, SQL_LONGVARCHAR, EPrimitiveType::Utf8) { // Binary types -REGISTER_CONVERTER(SQL_C_CHAR, SQL_BINARY, EPrimitiveType::String) { +REGISTER_CONVERTER(SQL_C_BINARY, SQL_BINARY, EPrimitiveType::String) { builder.OptionalString(std::move(data)); } -REGISTER_CONVERTER(SQL_C_CHAR, SQL_VARBINARY, EPrimitiveType::String) { +REGISTER_CONVERTER(SQL_C_BINARY, SQL_VARBINARY, EPrimitiveType::String) { builder.OptionalString(std::move(data)); } -REGISTER_CONVERTER(SQL_C_CHAR, SQL_LONGVARBINARY, EPrimitiveType::String) { +REGISTER_CONVERTER(SQL_C_BINARY, SQL_LONGVARBINARY, EPrimitiveType::String) { builder.OptionalString(std::move(data)); } diff --git a/odbc/tests/unit/convert_ut.cpp b/odbc/tests/unit/convert_ut.cpp index 6df6be54f1..5c351a8677 100644 --- a/odbc/tests/unit/convert_ut.cpp +++ b/odbc/tests/unit/convert_ut.cpp @@ -12,7 +12,8 @@ using namespace NYdb::NOdbc; using namespace NYdb; -void CheckProtoValue(const Ydb::Value& value, const std::string& expected) { +template +void CheckProto(const T& value, const std::string& expected) { std::string protoStr; google::protobuf::TextFormat::PrintToString(value, &protoStr); ASSERT_EQ(protoStr, expected); @@ -36,7 +37,8 @@ TEST(OdbcConvert, Int64ToYdb) { auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); - CheckProtoValue(value->GetProto(), "int64_value: 42\n"); + CheckProto(value->GetType().GetProto(), "optional_type {\n item {\n type_id: INT64\n }\n}\n"); + CheckProto(value->GetProto(), "int64_value: 42\n"); } TEST(OdbcConvert, Uint64ToYdb) { @@ -49,7 +51,8 @@ TEST(OdbcConvert, Uint64ToYdb) { auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); - CheckProtoValue(value->GetProto(), "uint64_value: 123\n"); + CheckProto(value->GetType().GetProto(), "optional_type {\n item {\n type_id: UINT64\n }\n}\n"); + CheckProto(value->GetProto(), "uint64_value: 123\n"); } TEST(OdbcConvert, DoubleToYdb) { @@ -62,7 +65,8 @@ TEST(OdbcConvert, DoubleToYdb) { auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); - CheckProtoValue(value->GetProto(), "double_value: 3.14\n"); + CheckProto(value->GetType().GetProto(), "optional_type {\n item {\n type_id: DOUBLE\n }\n}\n"); + CheckProto(value->GetProto(), "double_value: 3.14\n"); } TEST(OdbcConvert, StringToYdbUtf8) { @@ -76,21 +80,23 @@ TEST(OdbcConvert, StringToYdbUtf8) { auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); - CheckProtoValue(value->GetProto(), "text_value: \"hello\"\n"); + CheckProto(value->GetType().GetProto(), "optional_type {\n item {\n type_id: UTF8\n }\n}\n"); + CheckProto(value->GetProto(), "text_value: \"hello\"\n"); } TEST(OdbcConvert, StringToYdbBinary) { const char* str = "bin\x01\x02"; SQLLEN len = 5; TBoundParam param{ - 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_BINARY, 0, 0, (SQLPOINTER)str, len, nullptr + 1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_BINARY, 0, 0, (SQLPOINTER)str, len, nullptr }; TParamsBuilder paramsBuilder; ConvertValue(param, paramsBuilder.AddParam("$p1")); auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); - ASSERT_EQ(value->GetProto().bytes_value(), std::string(str, len)); + CheckProto(value->GetType().GetProto(), "optional_type {\n item {\n type_id: STRING\n }\n}\n"); + CheckProto(value->GetProto(), "bytes_value: \"bin\\001\\002\"\n"); } TEST(OdbcConvert, Int64NullToYdb) { @@ -104,7 +110,8 @@ TEST(OdbcConvert, Int64NullToYdb) { auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); - ASSERT_EQ(value->GetProto().null_flag_value(), ::google::protobuf::NullValue::NULL_VALUE); + CheckProto(value->GetType().GetProto(), "optional_type {\n item {\n type_id: INT64\n }\n}\n"); + CheckProto(value->GetProto(), "null_flag_value: NULL_VALUE\n"); } TEST(OdbcConvert, StringNullToYdb) { @@ -118,5 +125,6 @@ TEST(OdbcConvert, StringNullToYdb) { auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); - ASSERT_EQ(value->GetProto().null_flag_value(), ::google::protobuf::NullValue::NULL_VALUE); + CheckProto(value->GetType().GetProto(), "optional_type {\n item {\n type_id: UTF8\n }\n}\n"); + CheckProto(value->GetProto(), "null_flag_value: NULL_VALUE\n"); } From 2687d9880637d9ba4cb10ec9619e60ae492e46ed Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Fri, 30 May 2025 02:16:38 +0000 Subject: [PATCH 09/11] step --- odbc/CMakeLists.txt | 5 + odbc/examples/CMakeLists.txt | 1 + odbc/examples/scheme/CMakeLists.txt | 14 ++ odbc/examples/scheme/main.cpp | 143 +++++++++++ odbc/src/connection.cpp | 4 + odbc/src/connection.h | 6 + odbc/src/odbc_driver.cpp | 53 ++-- odbc/src/statement.cpp | 358 ++++++++++++++++------------ odbc/src/statement.h | 33 +-- odbc/src/utils/convert.cpp | 103 +++++++- odbc/src/utils/convert.h | 11 +- odbc/src/utils/result.cpp | 140 +++++++++++ odbc/src/utils/result.h | 38 +++ odbc/src/utils/types.cpp | 60 +++++ odbc/src/utils/types.h | 15 ++ odbc/src/utils/util.cpp | 12 + odbc/src/utils/util.h | 12 + odbc/tests/unit/convert_ut.cpp | 14 +- 18 files changed, 835 insertions(+), 187 deletions(-) create mode 100644 odbc/examples/scheme/CMakeLists.txt create mode 100644 odbc/examples/scheme/main.cpp create mode 100644 odbc/src/utils/result.cpp create mode 100644 odbc/src/utils/result.h create mode 100644 odbc/src/utils/types.cpp create mode 100644 odbc/src/utils/types.h create mode 100644 odbc/src/utils/util.cpp create mode 100644 odbc/src/utils/util.h diff --git a/odbc/CMakeLists.txt b/odbc/CMakeLists.txt index 6133906d9c..eb47edc922 100644 --- a/odbc/CMakeLists.txt +++ b/odbc/CMakeLists.txt @@ -1,4 +1,7 @@ add_library(ydb-odbc SHARED + src/utils/result.cpp + src/utils/types.cpp + src/utils/util.cpp src/utils/convert.cpp src/odbc_driver.cpp src/connection.cpp @@ -9,6 +12,7 @@ add_library(ydb-odbc SHARED target_include_directories(ydb-odbc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src ${ODBC_INCLUDE_DIRS} ) @@ -16,6 +20,7 @@ target_link_libraries(ydb-odbc PRIVATE YDB-CPP-SDK::Query YDB-CPP-SDK::Table + YDB-CPP-SDK::Scheme YDB-CPP-SDK::Driver ODBC::ODBC ) diff --git a/odbc/examples/CMakeLists.txt b/odbc/examples/CMakeLists.txt index 6f4cd2f5a3..88b1f27cc6 100644 --- a/odbc/examples/CMakeLists.txt +++ b/odbc/examples/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(basic) +add_subdirectory(scheme) diff --git a/odbc/examples/scheme/CMakeLists.txt b/odbc/examples/scheme/CMakeLists.txt new file mode 100644 index 0000000000..abca75eb73 --- /dev/null +++ b/odbc/examples/scheme/CMakeLists.txt @@ -0,0 +1,14 @@ +add_executable(odbc_scheme + main.cpp +) + +target_link_libraries(odbc_scheme + PRIVATE + ODBC::ODBC +) +target_compile_definitions(odbc_scheme + PRIVATE + ODBC_DRIVER_PATH="$" +) + +add_dependencies(odbc_scheme ydb-odbc) diff --git a/odbc/examples/scheme/main.cpp b/odbc/examples/scheme/main.cpp new file mode 100644 index 0000000000..3228ba2470 --- /dev/null +++ b/odbc/examples/scheme/main.cpp @@ -0,0 +1,143 @@ +#include +#include + +#include + +void PrintOdbcError(SQLSMALLINT handleType, SQLHANDLE handle) { + SQLCHAR sqlState[6] = {0}; + SQLINTEGER nativeError = 0; + SQLCHAR message[256] = {0}; + SQLSMALLINT textLength = 0; + SQLGetDiagRec(handleType, handle, 1, sqlState, &nativeError, message, sizeof(message), &textLength); + std::cerr << "ODBC error: [" << sqlState << "] " << message << std::endl; +} + +int main() { + SQLHENV henv = nullptr; + SQLHDBC hdbc = nullptr; + SQLHSTMT hstmt = nullptr; + SQLRETURN ret; + + std::cout << "1. Allocating environment handle" << std::endl; + ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv); + if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { + std::cerr << "Error allocating environment handle" << std::endl; + return 1; + } + SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + + std::cout << "2. Allocating connection handle" << std::endl; + ret = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); + if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { + std::cerr << "Error allocating connection handle" << std::endl; + SQLFreeHandle(SQL_HANDLE_ENV, henv); + return 1; + } + + std::cout << "3. Building connection string" << std::endl; + std::string connStr = "Driver=" ODBC_DRIVER_PATH ";Endpoint=localhost:2136;Database=/local;"; + SQLCHAR outConnStr[1024] = {0}; + SQLSMALLINT outConnStrLen = 0; + + std::cout << "4. Connecting with SQLDriverConnect" << std::endl; + ret = SQLDriverConnect(hdbc, NULL, (SQLCHAR*)connStr.c_str(), SQL_NTS, + outConnStr, sizeof(outConnStr), &outConnStrLen, SQL_DRIVER_COMPLETE); + if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { + std::cerr << "Error connecting with SQLDriverConnect" << std::endl; + PrintOdbcError(SQL_HANDLE_DBC, hdbc); + SQLFreeHandle(SQL_HANDLE_DBC, hdbc); + SQLFreeHandle(SQL_HANDLE_ENV, henv); + return 1; + } + + std::cout << "5. Allocating statement handle" << std::endl; + ret = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); + if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { + std::cerr << "Error allocating statement handle" << std::endl; + SQLDisconnect(hdbc); + SQLFreeHandle(SQL_HANDLE_DBC, hdbc); + SQLFreeHandle(SQL_HANDLE_ENV, henv); + return 1; + } + + // std::cout << "6. Executing query" << std::endl; + // SQLCHAR query[] = R"( + // DECLARE $p1 AS Int64?; + // SELECT $p1 + 1, 'test1'; + // SELECT $p1 + 2, 'test2'; + // SELECT $p1 + 3, 'test3'; + // SELECT $p1 + 4, 'test4'; + // SELECT $p1 + 5, 'test5'; + // SELECT $p1 + 6, 'test6'; + // SELECT $p1 + 7, 'test7'; + // SELECT $p1 + 8, 'test8'; + // SELECT $p1 + 9, 'test9'; + // )"; + + // int64_t paramValue = 42; + // SQLLEN paramInd = 0; + // ret = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, 0, 0, ¶mValue, 0, ¶mInd); + // if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { + // std::cerr << "Error binding parameter" << std::endl; + // PrintOdbcError(SQL_HANDLE_STMT, hstmt); + // SQLFreeHandle(SQL_HANDLE_STMT, hstmt); + // SQLDisconnect(hdbc); + // SQLFreeHandle(SQL_HANDLE_DBC, hdbc); + // SQLFreeHandle(SQL_HANDLE_ENV, henv); + // return 1; + // } + + std::cout << "6. Getting tables" << std::endl; + + SQLCHAR pattern[] = "/local"; + SQLCHAR tableType[] = "TABLE"; + + ret = SQLTables(hstmt, NULL, 0, NULL, 0, pattern, SQL_NTS, tableType, SQL_NTS); + if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { + std::cerr << "Error executing query" << std::endl; + PrintOdbcError(SQL_HANDLE_STMT, hstmt); + SQLFreeHandle(SQL_HANDLE_STMT, hstmt); + SQLDisconnect(hdbc); + SQLFreeHandle(SQL_HANDLE_DBC, hdbc); + SQLFreeHandle(SQL_HANDLE_ENV, henv); + return 1; + } + + std::cout << "7. Fetching result" << std::endl; + + SQLLEN ind = 0; + SQLCHAR value1[1024] = {0}; + if (SQLBindCol(hstmt, 3, SQL_C_CHAR, &value1, 1024, &ind) != SQL_SUCCESS) { + std::cerr << "Error binding column 1" << std::endl; + PrintOdbcError(SQL_HANDLE_STMT, hstmt); + return 1; + } + + SQLCHAR value2[1024] = {0}; + if (SQLBindCol(hstmt, 4, SQL_C_CHAR, &value2, 1024, &ind) != SQL_SUCCESS) { + std::cerr << "Error binding column 2" << std::endl; + PrintOdbcError(SQL_HANDLE_STMT, hstmt); + return 1; + } + + while ((ret = SQLFetch(hstmt)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { + if (ret != SQL_SUCCESS) { + std::cerr << "Error fetching result" << std::endl; + PrintOdbcError(SQL_HANDLE_STMT, hstmt); + return 1; + } + + std::cout << "Result column 1: " << value1 << std::endl; + std::cout << "Result column 2: " << value2 << std::endl; + + std::cout << "--------------------------------" << std::endl; + } + + std::cout << "8. Cleaning up" << std::endl; + SQLFreeHandle(SQL_HANDLE_STMT, hstmt); + SQLDisconnect(hdbc); + SQLFreeHandle(SQL_HANDLE_DBC, hdbc); + SQLFreeHandle(SQL_HANDLE_ENV, henv); + + return 0; +} diff --git a/odbc/src/connection.cpp b/odbc/src/connection.cpp index 6fbd8cf1e3..7806096bc6 100644 --- a/odbc/src/connection.cpp +++ b/odbc/src/connection.cpp @@ -44,6 +44,8 @@ SQLRETURN TConnection::DriverConnect(const std::string& connectionString) { .SetDatabase(Database_)); YdbClient_ = std::make_unique(*YdbDriver_); + YdbSchemeClient_ = std::make_unique(*YdbDriver_); + YdbTableClient_ = std::make_unique(*YdbDriver_); return SQL_SUCCESS; } @@ -71,6 +73,8 @@ SQLRETURN TConnection::Connect(const std::string& serverName, .SetDatabase(Database_)); YdbClient_ = std::make_unique(*YdbDriver_); + YdbSchemeClient_ = std::make_unique(*YdbDriver_); + YdbTableClient_ = std::make_unique(*YdbDriver_); return SQL_SUCCESS; } diff --git a/odbc/src/connection.h b/odbc/src/connection.h index 2b2fe22c81..fad8152777 100644 --- a/odbc/src/connection.h +++ b/odbc/src/connection.h @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include @@ -21,6 +23,8 @@ class TConnection { private: std::unique_ptr YdbDriver_; std::unique_ptr YdbClient_; + std::unique_ptr YdbTableClient_; + std::unique_ptr YdbSchemeClient_; std::optional Tx_; TErrorList Errors_; @@ -45,6 +49,8 @@ class TConnection { void RemoveStatement(TStatement* stmt); NYdb::NQuery::TQueryClient* GetClient() { return YdbClient_.get(); } + NYdb::NTable::TTableClient* GetTableClient() { return YdbTableClient_.get(); } + NScheme::TSchemeClient* GetSchemeClient() { return YdbSchemeClient_.get(); } void AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message); void ClearErrors(); diff --git a/odbc/src/odbc_driver.cpp b/odbc/src/odbc_driver.cpp index 26de48e6af..9e7a7d3aee 100644 --- a/odbc/src/odbc_driver.cpp +++ b/odbc/src/odbc_driver.cpp @@ -2,18 +2,11 @@ #include "connection.h" #include "statement.h" +#include "utils/util.h" + #include #include -namespace { - std::string GetString(SQLCHAR* str, SQLSMALLINT length) { - if (length == SQL_NTS) { - return std::string(reinterpret_cast(str)); - } - return std::string(reinterpret_cast(str), length); - } -} - extern "C" { SQLRETURN SQL_API SQLAllocHandle(SQLSMALLINT handleType, @@ -122,7 +115,7 @@ SQLRETURN SQL_API SQLDriverConnect(SQLHDBC connectionHandle, return SQL_INVALID_HANDLE; } - return conn->DriverConnect(GetString(inConnectionString, stringLength1)); + return conn->DriverConnect(NYdb::NOdbc::GetString(inConnectionString, stringLength1)); } SQLRETURN SQL_API SQLConnect(SQLHDBC connectionHandle, @@ -134,9 +127,9 @@ SQLRETURN SQL_API SQLConnect(SQLHDBC connectionHandle, return SQL_INVALID_HANDLE; } - return conn->Connect(GetString(serverName, nameLength1), - GetString(userName, nameLength2), - GetString(authentication, nameLength3)); + return conn->Connect(NYdb::NOdbc::GetString(serverName, nameLength1), + NYdb::NOdbc::GetString(userName, nameLength2), + NYdb::NOdbc::GetString(authentication, nameLength3)); } SQLRETURN SQL_API SQLDisconnect(SQLHDBC connectionHandle) { @@ -156,7 +149,7 @@ SQLRETURN SQL_API SQLExecDirect(SQLHSTMT statementHandle, return SQL_INVALID_HANDLE; } - return stmt->ExecDirect(GetString(statementText, textLength)); + return stmt->ExecDirect(NYdb::NOdbc::GetString(statementText, textLength)); } SQLRETURN SQL_API SQLFetch(SQLHSTMT statementHandle) { @@ -308,4 +301,36 @@ SQLRETURN SQL_API SQLSetConnectAttr(SQLHDBC connectionHandle, SQLINTEGER attribu return SQL_ERROR; } +SQLRETURN SQL_API SQLColumns(SQLHSTMT statementHandle, + SQLCHAR* catalogName, SQLSMALLINT nameLength1, + SQLCHAR* schemaName, SQLSMALLINT nameLength2, + SQLCHAR* tableName, SQLSMALLINT nameLength3, + SQLCHAR* columnName, SQLSMALLINT nameLength4) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + return stmt->Columns( + NYdb::NOdbc::GetString(catalogName, nameLength1), + NYdb::NOdbc::GetString(schemaName, nameLength2), + NYdb::NOdbc::GetString(tableName, nameLength3), + NYdb::NOdbc::GetString(columnName, nameLength4)); +} + +SQLRETURN SQL_API SQLTables(SQLHSTMT statementHandle, + SQLCHAR* catalogName, SQLSMALLINT nameLength1, + SQLCHAR* schemaName, SQLSMALLINT nameLength2, + SQLCHAR* tableName, SQLSMALLINT nameLength3, + SQLCHAR* tableType, SQLSMALLINT nameLength4) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + return stmt->Tables( + NYdb::NOdbc::GetString(catalogName, nameLength1), + NYdb::NOdbc::GetString(schemaName, nameLength2), + NYdb::NOdbc::GetString(tableName, nameLength3), + NYdb::NOdbc::GetString(tableType, nameLength4)); +} + } diff --git a/odbc/src/statement.cpp b/odbc/src/statement.cpp index 3d19988a0b..856eb5b341 100644 --- a/odbc/src/statement.cpp +++ b/odbc/src/statement.cpp @@ -1,5 +1,7 @@ #include "statement.h" +#include "utils/types.h" + #include #include @@ -43,60 +45,25 @@ SQLRETURN TStatement::ExecDirect(const std::string& statementText) { return SQL_ERROR; } - Iterator_ = std::make_unique(std::move(iterator)); + ResultSet_ = CreateExecResultSet(std::move(iterator)); return SQL_SUCCESS; } SQLRETURN TStatement::Fetch() { - if (!Iterator_) { + if (!ResultSet_) { ClearStatement(); return SQL_NO_DATA; } - - while (true) { - if (ResultSetParser_) { - if (ResultSetParser_->TryNextRow()) { - for (const auto& col : BoundColumns_) { - GetData(col.ColumnNumber, col.TargetType, col.TargetValue, col.BufferLength, col.StrLenOrInd); - } - return SQL_SUCCESS; - } - - ResultSetParser_.reset(); - } - - auto part = Iterator_->ReadNext().ExtractValueSync(); - if (part.EOS()) { - ClearStatement(); - return SQL_NO_DATA; - } - - if (!part.IsSuccess()) { - // AddError(part.GetStatus().GetStatus().GetCode(), part.GetStatus().GetStatus().GetReason()); - ClearStatement(); - return SQL_ERROR; - } - - if (part.HasResultSet()) { - ResultSetParser_ = std::make_unique(part.ExtractResultSet()); - } - } - - return SQL_SUCCESS; + return ResultSet_->Fetch() ? SQL_SUCCESS : SQL_NO_DATA; } SQLRETURN TStatement::GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) { - if (!ResultSetParser_) { + if (!ResultSet_) { return SQL_NO_DATA; } - - if (columnNumber < 1 || columnNumber > ResultSetParser_->ColumnsCount()) { - return SQL_ERROR; - } - - return ConvertYdbValue(ResultSetParser_->ColumnParser(columnNumber - 1), targetType, targetValue, bufferLength, strLenOrInd); + return ResultSet_->GetData(columnNumber, targetType, targetValue, bufferLength, strLenOrInd); } SQLRETURN TStatement::GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, @@ -124,20 +91,11 @@ SQLRETURN TStatement::GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLIN return SQL_SUCCESS; } -SQLRETURN TStatement::BindCol(SQLUSMALLINT columnNumber, - SQLSMALLINT targetType, - SQLPOINTER targetValue, - SQLLEN bufferLength, - SQLLEN* strLenOrInd) { - - BoundColumns_.erase(std::remove_if(BoundColumns_.begin(), BoundColumns_.end(), - [columnNumber](const TBoundColumn& col) { return col.ColumnNumber == columnNumber; }), BoundColumns_.end()); - - if (!targetValue) { - return SQL_SUCCESS; +SQLRETURN TStatement::BindCol(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) { + if (!ResultSet_) { + return SQL_NO_DATA; } - BoundColumns_.push_back({columnNumber, targetType, targetValue, bufferLength, strLenOrInd}); - return SQL_SUCCESS; + return ResultSet_->BindCol(columnNumber, targetType, targetValue, bufferLength, strLenOrInd); } SQLRETURN TStatement::BindParameter(SQLUSMALLINT paramNumber, @@ -174,117 +132,225 @@ void TStatement::ClearErrors() { } void TStatement::ClearStatement() { - Iterator_.reset(); - ResultSetParser_.reset(); - BoundColumns_.clear(); + ResultSet_.reset(); } -SQLRETURN TStatement::ConvertYdbValue(NYdb::TValueParser& valueParser, - SQLSMALLINT targetType, - SQLPOINTER targetValue, - SQLLEN bufferLength, - SQLLEN* strLenOrInd) { - - if (valueParser.IsNull()) { - if (strLenOrInd) *strLenOrInd = SQL_NULL_DATA; - return SQL_SUCCESS; +NYdb::TParams TStatement::BuildParams() { + Errors_.clear(); + NYdb::TParamsBuilder paramsBuilder; + for (const auto& param : BoundParams_) { + std::string paramName = "$p" + std::to_string(param.ParamNumber); + ConvertParam(param, paramsBuilder.AddParam(paramName)); } - if (valueParser.GetKind() == TTypeParser::ETypeKind::Optional) { - valueParser.OpenOptional(); - SQLRETURN ret = ConvertYdbValue(valueParser, targetType, targetValue, bufferLength, strLenOrInd); - valueParser.CloseOptional(); - return ret; - } + return paramsBuilder.Build(); +} + +SQLRETURN TStatement::Columns(const std::string& catalogName, + const std::string& schemaName, + const std::string& tableName, + const std::string& columnName) { + ClearErrors(); + ClearStatement(); - if (valueParser.GetKind() != TTypeParser::ETypeKind::Primitive) { + std::vector columns = { + {"TABLE_CAT", SQL_VARCHAR, 128, SQL_NULLABLE}, + {"TABLE_SCHEM", SQL_VARCHAR, 128, SQL_NULLABLE}, + {"TABLE_NAME", SQL_VARCHAR, 128, SQL_NO_NULLS}, + {"COLUMN_NAME", SQL_VARCHAR, 128, SQL_NO_NULLS}, + {"DATA_TYPE", SQL_INTEGER, 0, SQL_NO_NULLS}, + {"TYPE_NAME", SQL_VARCHAR, 128, SQL_NO_NULLS}, + {"COLUMN_SIZE", SQL_INTEGER, 0, SQL_NULLABLE}, + {"BUFFER_LENGTH", SQL_INTEGER, 0, SQL_NULLABLE}, + {"DECIMAL_DIGITS", SQL_INTEGER, 0, SQL_NULLABLE}, + {"NUM_PREC_RADIX", SQL_INTEGER, 0, SQL_NULLABLE}, + {"NULLABLE", SQL_INTEGER, 0, SQL_NO_NULLS}, + {"REMARKS", SQL_VARCHAR, 762, SQL_NULLABLE}, + {"COLUMN_DEF", SQL_VARCHAR, 254, SQL_NULLABLE}, + {"SQL_DATA_TYPE", SQL_INTEGER, 0, SQL_NO_NULLS}, + {"SQL_DATETIME_SUB", SQL_INTEGER, 0, SQL_NULLABLE}, + {"CHAR_OCTET_LENGTH", SQL_INTEGER, 0, SQL_NULLABLE}, + {"ORDINAL_POSITION", SQL_INTEGER, 0, SQL_NO_NULLS}, + {"IS_NULLABLE", SQL_VARCHAR, 254, SQL_NO_NULLS} + }; + + auto entries = GetPatternEntries(tableName); + if (entries.empty()) { + AddError("HYC00", 0, "No tables found"); return SQL_ERROR; } - EPrimitiveType ydbType = valueParser.GetPrimitiveType(); - - switch (targetType) { - case SQL_C_SLONG: - { - int32_t v = 0; - switch (ydbType) { - case EPrimitiveType::Int32: v = valueParser.GetInt32(); break; - case EPrimitiveType::Uint32: v = static_cast(valueParser.GetUint32()); break; - case EPrimitiveType::Int64: v = static_cast(valueParser.GetInt64()); break; - case EPrimitiveType::Uint64: v = static_cast(valueParser.GetUint64()); break; - case EPrimitiveType::Bool: v = valueParser.GetBool() ? 1 : 0; break; - default: return SQL_ERROR; - } - if (targetValue) *reinterpret_cast(targetValue) = v; - if (strLenOrInd) *strLenOrInd = sizeof(int32_t); - return SQL_SUCCESS; - } - case SQL_C_SBIGINT: - { - SQLBIGINT v = 0; - switch (ydbType) { - case EPrimitiveType::Int64: v = valueParser.GetInt64(); break; - case EPrimitiveType::Uint64: v = static_cast(valueParser.GetUint64()); break; - case EPrimitiveType::Int32: v = static_cast(valueParser.GetInt32()); break; - case EPrimitiveType::Uint32: v = static_cast(valueParser.GetUint32()); break; - default: return SQL_ERROR; - } - if (targetValue) *reinterpret_cast(targetValue) = v; - if (strLenOrInd) *strLenOrInd = sizeof(SQLBIGINT); - return SQL_SUCCESS; - } - case SQL_C_DOUBLE: - { - double v = 0.0; - switch (ydbType) { - case EPrimitiveType::Double: v = valueParser.GetDouble(); break; - case EPrimitiveType::Float: v = valueParser.GetFloat(); break; - default: return SQL_ERROR; - } - if (targetValue) *reinterpret_cast(targetValue) = v; - if (strLenOrInd) *strLenOrInd = sizeof(double); - return SQL_SUCCESS; + TTable table; + table.reserve(entries.size()); + + for (const auto& entry : entries) { + if (entry.Type != NScheme::ESchemeEntryType::Table && + entry.Type != NScheme::ESchemeEntryType::ColumnTable) { + continue; } - case SQL_C_CHAR: - { - std::string str; - switch (ydbType) { - case EPrimitiveType::Utf8: str = valueParser.GetUtf8(); break; - case EPrimitiveType::String: str = valueParser.GetString(); break; - case EPrimitiveType::Json: str = valueParser.GetJson(); break; - case EPrimitiveType::JsonDocument: str = valueParser.GetJsonDocument(); break; - default: return SQL_ERROR; + + auto status = Conn_->GetTableClient()->RetryOperationSync([path = entry.Name, &table, &columnName](NTable::TSession session) -> TStatus { + auto result = session.DescribeTable(path).ExtractValueSync(); + if (!result.IsSuccess()) { + return result; } - SQLLEN len = str.size(); - if (targetValue && bufferLength > 0) { - SQLLEN copyLen = std::min(len, bufferLength - 1); - memcpy(targetValue, str.data(), copyLen); - reinterpret_cast(targetValue)[copyLen] = 0; + auto columns = result.GetTableDescription().GetTableColumns(); + + auto columnIt = std::find_if(columns.begin(), columns.end(), [&columnName](const NTable::TTableColumn& column) { + return column.Name == columnName; + }); + + if (columnIt == columns.end()) { + return TStatus(EStatus::NOT_FOUND, { NYdb::NIssue::TIssue("Column not found") }); } - if (strLenOrInd) *strLenOrInd = len; - return SQL_SUCCESS; + + auto column = *columnIt; + + TTypeParser typeParser(column.Type); + + table.push_back({ + TValueBuilder().OptionalUtf8(std::nullopt).Build(), + TValueBuilder().OptionalUtf8(std::nullopt).Build(), + TValueBuilder().Utf8(path).Build(), + TValueBuilder().Utf8(column.Name).Build(), + TValueBuilder().Int16(GetTypeId(column.Type)).Build(), + TValueBuilder().Utf8(column.Type.ToString()).Build(), + TValueBuilder().OptionalInt32(std::nullopt).Build(), + TValueBuilder().OptionalInt32(std::nullopt).Build(), + TValueBuilder().OptionalInt16(GetDecimalDigits(column.Type)).Build(), + TValueBuilder().OptionalInt16(GetRadix(column.Type)).Build(), + TValueBuilder().Int16(column.NotNull && *column.NotNull ? SQL_NO_NULLS : SQL_NULLABLE).Build(), + TValueBuilder().OptionalUtf8(std::nullopt).Build(), + TValueBuilder().OptionalUtf8(std::nullopt).Build(), + TValueBuilder().Int16(GetTypeId(column.Type)).Build(), + TValueBuilder().OptionalInt16(std::nullopt).Build(), + TValueBuilder().OptionalInt32(8).Build(), + TValueBuilder().OptionalInt32(columnIt - columns.begin() + 1).Build(), + TValueBuilder().Utf8(column.NotNull && *column.NotNull ? "NO" : "YES").Build(), + }); + return TStatus(EStatus::SUCCESS, {}); + }); + + if (!status.IsSuccess()) { + return SQL_ERROR; } - case SQL_C_BIT: - { - char v = valueParser.GetBool() ? 1 : 0; - if (targetValue) *reinterpret_cast(targetValue) = v; - if (strLenOrInd) *strLenOrInd = sizeof(char); - return SQL_SUCCESS; + } + + ResultSet_ = CreateVirtualResultSet(columns, table); + return SQL_SUCCESS; +} + +SQLRETURN TStatement::Tables(const std::string& catalogName, + const std::string& schemaName, + const std::string& tableName, + const std::string& tableType) { + ClearErrors(); + ClearStatement(); + + std::vector columns = { + {"TABLE_CAT", SQL_VARCHAR, 128, SQL_NULLABLE}, + {"TABLE_SCHEM", SQL_VARCHAR, 128, SQL_NULLABLE}, + {"TABLE_NAME", SQL_VARCHAR, 128, SQL_NO_NULLS}, + {"TABLE_TYPE", SQL_VARCHAR, 128, SQL_NO_NULLS}, + {"REMARKS", SQL_VARCHAR, 254, SQL_NULLABLE} + }; + + auto entries = GetPatternEntries(tableName); + if (entries.empty()) { + AddError("HYC00", 0, "No tables found"); + return SQL_ERROR; + } + + TTable table; + table.reserve(entries.size()); + + for (const auto& entry : entries) { + auto tableType = GetTableType(entry.Type); + if (!tableType) { + continue; } - default: - return SQL_ERROR; + + std::cout << "Table name: " << entry.Name << " type: " << *tableType << std::endl; + + table.push_back({ + TValueBuilder().OptionalUtf8(std::nullopt).Build(), + TValueBuilder().OptionalUtf8(std::nullopt).Build(), + TValueBuilder().Utf8(entry.Name).Build(), + TValueBuilder().Utf8(*tableType).Build(), + TValueBuilder().OptionalUtf8(std::nullopt).Build(), + }); } + + ResultSet_ = CreateVirtualResultSet(columns, table); + return SQL_SUCCESS; } -NYdb::TParams TStatement::BuildParams() { - Errors_.clear(); - NYdb::TParamsBuilder paramsBuilder; - for (const auto& param : BoundParams_) { - std::string paramName = "$p" + std::to_string(param.ParamNumber); - ConvertValue(param, paramsBuilder.AddParam(paramName)); +std::vector TStatement::GetPatternEntries(const std::string& pattern) { + std::vector entries; + VisitEntry("", pattern, entries); + return entries; +} + +SQLRETURN TStatement::VisitEntry(const std::string& path, const std::string& pattern, std::vector& resultEntries) { + auto schemeClient = Conn_->GetSchemeClient(); + auto listDirectoryResult = schemeClient->ListDirectory(path + "/").ExtractValueSync(); + if (!listDirectoryResult.IsSuccess()) { + return SQL_ERROR; + } + for (const auto& entry : listDirectoryResult.GetChildren()) { + std::string fullPath = path + "/" + entry.Name; + if (entry.Type == NScheme::ESchemeEntryType::Directory || + entry.Type == NScheme::ESchemeEntryType::SubDomain) { + VisitEntry(fullPath, pattern, resultEntries); + } else if (IsPatternMatch(fullPath, pattern)) { + NScheme::TSchemeEntry entryCopy = entry; + entryCopy.Name = fullPath; + resultEntries.push_back(entryCopy); + } } + return SQL_SUCCESS; +} - return paramsBuilder.Build(); +bool TStatement::IsPatternMatch(const std::string& path, const std::string& pattern) { + return path.starts_with(pattern); +} + +std::optional TStatement::GetTableType(NScheme::ESchemeEntryType type) { + switch (type) { + case NScheme::ESchemeEntryType::Table: + return "TABLE"; + case NScheme::ESchemeEntryType::View: + return "VIEW"; + case NScheme::ESchemeEntryType::ColumnStore: + return "COLUMN_STORE"; + case NScheme::ESchemeEntryType::ColumnTable: + return "COLUMN_TABLE"; + case NScheme::ESchemeEntryType::Sequence: + return "SEQUENCE"; + case NScheme::ESchemeEntryType::Replication: + return "REPLICATION"; + case NScheme::ESchemeEntryType::Topic: + return "TOPIC"; + case NScheme::ESchemeEntryType::ExternalTable: + return "EXTERNAL_TABLE"; + case NScheme::ESchemeEntryType::ExternalDataSource: + return "EXTERNAL_DATA_SOURCE"; + case NScheme::ESchemeEntryType::ResourcePool: + return "RESOURCE_POOL"; + case NScheme::ESchemeEntryType::PqGroup: + return "PQ_GROUP"; + case NScheme::ESchemeEntryType::RtmrVolume: + return "RTMR_VOLUME"; + case NScheme::ESchemeEntryType::BlockStoreVolume: + return "BLOCK_STORE_VOLUME"; + case NScheme::ESchemeEntryType::CoordinationNode: + return "COORDINATION_NODE"; + case NScheme::ESchemeEntryType::Unknown: + return "UNKNOWN"; + case NScheme::ESchemeEntryType::Directory: + case NScheme::ESchemeEntryType::SubDomain: + return std::nullopt; + } } } // namespace NOdbc diff --git a/odbc/src/statement.h b/odbc/src/statement.h index 8f51be6759..b4568b97fb 100644 --- a/odbc/src/statement.h +++ b/odbc/src/statement.h @@ -1,6 +1,7 @@ #pragma once #include "connection.h" +#include "utils/result.h" #include "utils/convert.h" #include @@ -17,15 +18,6 @@ namespace NYdb { namespace NOdbc { class TStatement { -private: - struct TBoundColumn { - SQLUSMALLINT ColumnNumber; - SQLSMALLINT TargetType; - SQLPOINTER TargetValue; - SQLLEN BufferLength; - SQLLEN* StrLenOrInd; - }; - public: TStatement(TConnection* conn); @@ -40,6 +32,16 @@ class TStatement { SQLRETURN BindCol(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd); SQLRETURN BindParameter(SQLUSMALLINT paramNumber, SQLSMALLINT inputOutputType, SQLSMALLINT valueType, SQLSMALLINT parameterType, SQLULEN columnSize, SQLSMALLINT decimalDigits, SQLPOINTER parameterValuePtr, SQLLEN bufferLength, SQLLEN* strLenOrIndPtr); + SQLRETURN Columns(const std::string& catalogName, + const std::string& schemaName, + const std::string& tableName, + const std::string& columnName); + + SQLRETURN Tables(const std::string& catalogName, + const std::string& schemaName, + const std::string& tableName, + const std::string& tableType); + TConnection* GetConnection() { return Conn_; } @@ -49,16 +51,19 @@ class TStatement { NYdb::TParams BuildParams(); -private: void ClearStatement(); - SQLRETURN ConvertYdbValue(NYdb::TValueParser& valueParser, SQLSMALLINT targetType, - SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd); +private: + std::vector GetPatternEntries(const std::string& pattern); + SQLRETURN VisitEntry(const std::string& path, const std::string& pattern, std::vector& resultEntries); + bool IsPatternMatch(const std::string& path, const std::string& pattern); + + std::optional GetTableType(NScheme::ESchemeEntryType type); TConnection* Conn_; TErrorList Errors_; - std::unique_ptr Iterator_; - std::unique_ptr ResultSetParser_; + + std::unique_ptr ResultSet_; std::vector BoundColumns_; std::vector BoundParams_; diff --git a/odbc/src/utils/convert.cpp b/odbc/src/utils/convert.cpp index 432418315a..b10f839dc1 100644 --- a/odbc/src/utils/convert.cpp +++ b/odbc/src/utils/convert.cpp @@ -279,12 +279,105 @@ REGISTER_CONVERTER(SQL_C_BINARY, SQL_LONGVARBINARY, EPrimitiveType::String) { #undef REGISTER_CONVERTER -void ConvertValue(const TBoundParam& param, TParamValueBuilder& builder) { +SQLRETURN ConvertParam(const TBoundParam& param, TParamValueBuilder& builder) { auto converter = TConverterRegistry::GetInstance().GetConverter(param.ValueType, param.ParameterType); - if (converter) { - converter->AddToBuilder(param, builder); - } else { - throw 1; // TODO: throw exception + if (!converter) { + return SQL_ERROR; + } + + converter->AddToBuilder(param, builder); + return SQL_SUCCESS; +} + +SQLRETURN ConvertColumn(TValueParser& parser, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) { + if (parser.IsNull()) { + if (strLenOrInd) *strLenOrInd = SQL_NULL_DATA; + return SQL_SUCCESS; + } + + if (parser.GetKind() == TTypeParser::ETypeKind::Optional) { + parser.OpenOptional(); + SQLRETURN ret = ConvertColumn(parser, targetType, targetValue, bufferLength, strLenOrInd); + parser.CloseOptional(); + return ret; + } + + if (parser.GetKind() != TTypeParser::ETypeKind::Primitive) { + return SQL_ERROR; + } + + EPrimitiveType ydbType = parser.GetPrimitiveType(); + + switch (targetType) { + case SQL_C_SLONG: + { + int32_t v = 0; + switch (ydbType) { + case EPrimitiveType::Int32: v = parser.GetInt32(); break; + case EPrimitiveType::Uint32: v = static_cast(parser.GetUint32()); break; + case EPrimitiveType::Int64: v = static_cast(parser.GetInt64()); break; + case EPrimitiveType::Uint64: v = static_cast(parser.GetUint64()); break; + case EPrimitiveType::Bool: v = parser.GetBool() ? 1 : 0; break; + default: return SQL_ERROR; + } + if (targetValue) *reinterpret_cast(targetValue) = v; + if (strLenOrInd) *strLenOrInd = sizeof(int32_t); + return SQL_SUCCESS; + } + case SQL_C_SBIGINT: + { + SQLBIGINT v = 0; + switch (ydbType) { + case EPrimitiveType::Int64: v = parser.GetInt64(); break; + case EPrimitiveType::Uint64: v = static_cast(parser.GetUint64()); break; + case EPrimitiveType::Int32: v = static_cast(parser.GetInt32()); break; + case EPrimitiveType::Uint32: v = static_cast(parser.GetUint32()); break; + default: return SQL_ERROR; + } + if (targetValue) *reinterpret_cast(targetValue) = v; + if (strLenOrInd) *strLenOrInd = sizeof(SQLBIGINT); + return SQL_SUCCESS; + } + case SQL_C_DOUBLE: + { + double v = 0.0; + switch (ydbType) { + case EPrimitiveType::Double: v = parser.GetDouble(); break; + case EPrimitiveType::Float: v = parser.GetFloat(); break; + default: return SQL_ERROR; + } + if (targetValue) *reinterpret_cast(targetValue) = v; + if (strLenOrInd) *strLenOrInd = sizeof(double); + return SQL_SUCCESS; + } + case SQL_C_CHAR: + { + std::string str; + switch (ydbType) { + case EPrimitiveType::Utf8: str = parser.GetUtf8(); break; + case EPrimitiveType::String: str = parser.GetString(); break; + case EPrimitiveType::Json: str = parser.GetJson(); break; + case EPrimitiveType::JsonDocument: str = parser.GetJsonDocument(); break; + default: return SQL_ERROR; + } + SQLLEN len = str.size(); + if (targetValue && bufferLength > 0) { + SQLLEN copyLen = std::min(len, bufferLength - 1); + memcpy(targetValue, str.data(), copyLen); + reinterpret_cast(targetValue)[copyLen] = 0; + } + if (strLenOrInd) *strLenOrInd = len; + return SQL_SUCCESS; + } + case SQL_C_BIT: + { + char v = parser.GetBool() ? 1 : 0; + if (targetValue) *reinterpret_cast(targetValue) = v; + if (strLenOrInd) *strLenOrInd = sizeof(char); + return SQL_SUCCESS; + } + default: + return SQL_ERROR; } } diff --git a/odbc/src/utils/convert.h b/odbc/src/utils/convert.h index 525a43c79a..dba81c2b34 100644 --- a/odbc/src/utils/convert.h +++ b/odbc/src/utils/convert.h @@ -20,7 +20,16 @@ struct TBoundParam { SQLLEN* StrLenOrIndPtr; }; -void ConvertValue(const TBoundParam& param, TParamValueBuilder& builder); +struct TBoundColumn { + SQLUSMALLINT ColumnNumber; + SQLSMALLINT TargetType; + SQLPOINTER TargetValue; + SQLLEN BufferLength; + SQLLEN* StrLenOrInd; +}; + +SQLRETURN ConvertParam(const TBoundParam& param, TParamValueBuilder& builder); +SQLRETURN ConvertColumn(TValueParser& parser, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd); } // namespace NYdb } // namespace NOdbc diff --git a/odbc/src/utils/result.cpp b/odbc/src/utils/result.cpp new file mode 100644 index 0000000000..ca80f5b0b2 --- /dev/null +++ b/odbc/src/utils/result.cpp @@ -0,0 +1,140 @@ +#include "result.h" + +#include "convert.h" + +namespace NYdb { +namespace NOdbc { + +class TCommonResultSet : public IResultSet { +public: + SQLRETURN BindCol(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, + SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) override { + BoundColumns_.erase(std::remove_if(BoundColumns_.begin(), BoundColumns_.end(), + [columnNumber](const TBoundColumn& col) { return col.ColumnNumber == columnNumber; }), BoundColumns_.end()); + if (!targetValue) { + return SQL_SUCCESS; + } + BoundColumns_.push_back({columnNumber, targetType, targetValue, bufferLength, strLenOrInd}); + return SQL_SUCCESS; + } + +protected: + void FillBoundColumns() { + for (const auto& col : BoundColumns_) { + GetData(col.ColumnNumber, col.TargetType, col.TargetValue, col.BufferLength, col.StrLenOrInd); + } + } + +protected: + std::vector BoundColumns_; +}; + +class TExecResultSet : public TCommonResultSet { +public: + TExecResultSet(NYdb::NQuery::TExecuteQueryIterator iterator) + : Iterator_(std::move(iterator)) {} + + bool Fetch() override { + while (true) { + if (ResultSetParser_) { + if (ResultSetParser_->TryNextRow()) { + FillBoundColumns(); + return true; + } + ResultSetParser_.reset(); + } + auto part = Iterator_.ReadNext().ExtractValueSync(); + if (part.EOS()) { + return false; + } + if (!part.IsSuccess()) { + return false; + } + if (part.HasResultSet()) { + ResultSetParser_ = std::make_unique(part.ExtractResultSet()); + } + } + return false; + } + + SQLRETURN GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, + SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) override { + if (!ResultSetParser_) { + return SQL_NO_DATA; + } + if (columnNumber < 1 || columnNumber > ResultSetParser_->ColumnsCount()) { + return SQL_ERROR; + } + return ConvertColumn(ResultSetParser_->ColumnParser(columnNumber - 1), targetType, targetValue, bufferLength, strLenOrInd); + } + + size_t ColumnsCount() const override { + return ResultSetParser_ ? ResultSetParser_->ColumnsCount() : 0; + } + + const TColumnMeta& GetColumnMeta(size_t index) const override { + // TODO: implement return column metadata + static TColumnMeta dummy; + return dummy; + } + +private: + NYdb::NQuery::TExecuteQueryIterator Iterator_; + std::unique_ptr ResultSetParser_; +}; + +class TVirtualResultSet : public TCommonResultSet { +public: + TVirtualResultSet(const std::vector& columns, const TTable& table) + : Columns_(columns), Table_(table) { + std::cout << "TVirtualResultSet constructor" << std::endl; + std::cout << "Columns count: " << Columns_.size() << std::endl; + std::cout << "Table size: " << Table_.size() << std::endl; + } + + bool Fetch() override { + std::cout << "Fetching row " << Cursor_ << std::endl; + Cursor_++; + if (Cursor_ >= static_cast(Table_.size())) { + return false; + } + FillBoundColumns(); + return true; + } + + SQLRETURN GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, + SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) override { + if (Cursor_ >= static_cast(Table_.size())) { + return SQL_NO_DATA; + } + if (Cursor_ < 0 || columnNumber < 1 || columnNumber > Columns_.size()) { + return SQL_ERROR; + } + TValueParser parser{Table_[Cursor_][columnNumber - 1]}; + return ConvertColumn(parser, targetType, targetValue, bufferLength, strLenOrInd); + } + + size_t ColumnsCount() const override { + return Columns_.size(); + } + + const TColumnMeta& GetColumnMeta(size_t index) const override { + return Columns_[index]; + } + +private: + std::vector Columns_; + TTable Table_; + int64_t Cursor_ = -1; +}; + +std::unique_ptr CreateExecResultSet(NYdb::NQuery::TExecuteQueryIterator iterator) { + return std::make_unique(std::move(iterator)); +} + +std::unique_ptr CreateVirtualResultSet(const std::vector& columns, const TTable& table) { + return std::make_unique(columns, table); +} + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/utils/result.h b/odbc/src/utils/result.h new file mode 100644 index 0000000000..e5334038a2 --- /dev/null +++ b/odbc/src/utils/result.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include + +#include +#include + +namespace NYdb { +namespace NOdbc { + +struct TColumnMeta { + std::string Name; + SQLSMALLINT SqlType; + SQLULEN Size; + SQLSMALLINT Nullable; +}; + +using TTable = std::vector>; + +class IResultSet { +public: + virtual ~IResultSet() = default; + virtual bool Fetch() = 0; + virtual SQLRETURN GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, + SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) = 0; + virtual SQLRETURN BindCol(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, + SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) = 0; + virtual size_t ColumnsCount() const = 0; + virtual const TColumnMeta& GetColumnMeta(size_t index) const = 0; +}; + +std::unique_ptr CreateExecResultSet(NYdb::NQuery::TExecuteQueryIterator iterator); +std::unique_ptr CreateVirtualResultSet(const std::vector& columns, const TTable& table); + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/utils/types.cpp b/odbc/src/utils/types.cpp new file mode 100644 index 0000000000..aa038420e5 --- /dev/null +++ b/odbc/src/utils/types.cpp @@ -0,0 +1,60 @@ +#include "types.h" + +namespace NYdb { +namespace NOdbc { + +SQLINTEGER GetTypeId(const TType& type) { + return 0; +} + +std::optional GetDecimalDigits(const TType& type) { + TTypeParser typeParser(type); + if (typeParser.GetKind() != TTypeParser::ETypeKind::Primitive) { + return std::nullopt; + } + + switch (typeParser.GetPrimitive()) { + case EPrimitiveType::Int64: + return 64; + case EPrimitiveType::Uint64: + return 64; + case EPrimitiveType::Int32: + return 32; + case EPrimitiveType::Uint32: + return 32; + case EPrimitiveType::Int16: + return 16; + case EPrimitiveType::Uint16: + return 16; + case EPrimitiveType::Int8: + return 8; + case EPrimitiveType::Uint8: + return 8; + default: + return std::nullopt; + } +} + +std::optional GetRadix(const TType& type) { + TTypeParser typeParser(type); + if (typeParser.GetKind() != TTypeParser::ETypeKind::Primitive) { + return std::nullopt; + } + + switch (typeParser.GetPrimitive()) { + case EPrimitiveType::Int64: + case EPrimitiveType::Uint64: + case EPrimitiveType::Int32: + case EPrimitiveType::Uint32: + case EPrimitiveType::Int16: + case EPrimitiveType::Uint16: + case EPrimitiveType::Int8: + case EPrimitiveType::Uint8: + return 10; + default: + return std::nullopt; + } +} + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/utils/types.h b/odbc/src/utils/types.h new file mode 100644 index 0000000000..0b9dd76aa1 --- /dev/null +++ b/odbc/src/utils/types.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include + +namespace NYdb { +namespace NOdbc { + +SQLINTEGER GetTypeId(const TType& type); +std::optional GetDecimalDigits(const TType& type); +std::optional GetRadix(const TType& type); + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/utils/util.cpp b/odbc/src/utils/util.cpp new file mode 100644 index 0000000000..9097ce80db --- /dev/null +++ b/odbc/src/utils/util.cpp @@ -0,0 +1,12 @@ +#include "util.h" + +namespace NYdb::NOdbc { + +std::string GetString(SQLCHAR* str, SQLSMALLINT length) { + if (length == SQL_NTS) { + return std::string(reinterpret_cast(str)); + } + return std::string(reinterpret_cast(str), length); +} + +} // namespace NYdb::NOdbc diff --git a/odbc/src/utils/util.h b/odbc/src/utils/util.h new file mode 100644 index 0000000000..b17fe2c235 --- /dev/null +++ b/odbc/src/utils/util.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +#include + +namespace NYdb::NOdbc { + +std::string GetString(SQLCHAR* str, SQLSMALLINT length); + +} // namespace NYdb::NOdbc diff --git a/odbc/tests/unit/convert_ut.cpp b/odbc/tests/unit/convert_ut.cpp index 5c351a8677..f4bad34a36 100644 --- a/odbc/tests/unit/convert_ut.cpp +++ b/odbc/tests/unit/convert_ut.cpp @@ -33,7 +33,7 @@ TEST(OdbcConvert, Int64ToYdb) { }; TParamsBuilder paramsBuilder; - ConvertValue(param, paramsBuilder.AddParam("$p1")); + ConvertParam(param, paramsBuilder.AddParam("$p1")); auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); @@ -47,7 +47,7 @@ TEST(OdbcConvert, Uint64ToYdb) { 1, SQL_PARAM_INPUT, SQL_C_UBIGINT, SQL_BIGINT, 0, 0, &v, sizeof(v), nullptr }; TParamsBuilder paramsBuilder; - ConvertValue(param, paramsBuilder.AddParam("$p1")); + ConvertParam(param, paramsBuilder.AddParam("$p1")); auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); @@ -61,7 +61,7 @@ TEST(OdbcConvert, DoubleToYdb) { 1, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_DOUBLE, 0, 0, &v, sizeof(v), nullptr }; TParamsBuilder paramsBuilder; - ConvertValue(param, paramsBuilder.AddParam("$p1")); + ConvertParam(param, paramsBuilder.AddParam("$p1")); auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); @@ -76,7 +76,7 @@ TEST(OdbcConvert, StringToYdbUtf8) { 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 0, 0, (SQLPOINTER)str, len, nullptr }; TParamsBuilder paramsBuilder; - ConvertValue(param, paramsBuilder.AddParam("$p1")); + ConvertParam(param, paramsBuilder.AddParam("$p1")); auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); @@ -91,7 +91,7 @@ TEST(OdbcConvert, StringToYdbBinary) { 1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_BINARY, 0, 0, (SQLPOINTER)str, len, nullptr }; TParamsBuilder paramsBuilder; - ConvertValue(param, paramsBuilder.AddParam("$p1")); + ConvertParam(param, paramsBuilder.AddParam("$p1")); auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); @@ -106,7 +106,7 @@ TEST(OdbcConvert, Int64NullToYdb) { 1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, 0, 0, &v, sizeof(v), &nullInd }; TParamsBuilder paramsBuilder; - ConvertValue(param, paramsBuilder.AddParam("$p1")); + ConvertParam(param, paramsBuilder.AddParam("$p1")); auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); @@ -121,7 +121,7 @@ TEST(OdbcConvert, StringNullToYdb) { 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 0, 0, (SQLPOINTER)str, 4, &nullInd }; TParamsBuilder paramsBuilder; - ConvertValue(param, paramsBuilder.AddParam("$p1")); + ConvertParam(param, paramsBuilder.AddParam("$p1")); auto params = paramsBuilder.Build(); auto value = params.GetValue("$p1"); ASSERT_TRUE(value); From 50b29ba504e71bbe38f7ef2e955319bda355dd7d Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 2 Jun 2025 23:27:43 +0000 Subject: [PATCH 10/11] step --- odbc/CMakeLists.txt | 2 +- odbc/examples/basic/CMakeLists.txt | 10 +- odbc/examples/basic/main.cpp | 14 +-- odbc/examples/scheme/CMakeLists.txt | 10 +- odbc/examples/scheme/main.cpp | 31 +----- odbc/src/odbc_driver.cpp | 83 ++++++++++++++- odbc/src/statement.cpp | 122 ++++++++++++++++------ odbc/src/statement.h | 28 ++++-- odbc/src/utils/bindings.h | 37 +++++++ odbc/src/utils/convert.cpp | 40 ++++++-- odbc/src/utils/convert.h | 22 +--- odbc/src/utils/cursor.cpp | 119 ++++++++++++++++++++++ odbc/src/utils/{result.h => cursor.h} | 15 ++- odbc/src/utils/result.cpp | 140 -------------------------- odbc/src/utils/types.cpp | 12 ++- odbc/src/utils/types.h | 4 +- 16 files changed, 417 insertions(+), 272 deletions(-) create mode 100644 odbc/src/utils/bindings.h create mode 100644 odbc/src/utils/cursor.cpp rename odbc/src/utils/{result.h => cursor.h} (50%) delete mode 100644 odbc/src/utils/result.cpp diff --git a/odbc/CMakeLists.txt b/odbc/CMakeLists.txt index eb47edc922..f814f00313 100644 --- a/odbc/CMakeLists.txt +++ b/odbc/CMakeLists.txt @@ -1,5 +1,5 @@ add_library(ydb-odbc SHARED - src/utils/result.cpp + src/utils/cursor.cpp src/utils/types.cpp src/utils/util.cpp src/utils/convert.cpp diff --git a/odbc/examples/basic/CMakeLists.txt b/odbc/examples/basic/CMakeLists.txt index a34cbd9301..b99d1175f4 100644 --- a/odbc/examples/basic/CMakeLists.txt +++ b/odbc/examples/basic/CMakeLists.txt @@ -1,14 +1,14 @@ add_executable(odbc_basic - main.cpp + main.cpp ) target_link_libraries(odbc_basic - PRIVATE - ODBC::ODBC + PRIVATE + ODBC::ODBC ) target_compile_definitions(odbc_basic - PRIVATE - ODBC_DRIVER_PATH="$" + PRIVATE + ODBC_DRIVER_PATH="$" ) add_dependencies(odbc_basic ydb-odbc) diff --git a/odbc/examples/basic/main.cpp b/odbc/examples/basic/main.cpp index 364b9e1112..8084e32f3d 100644 --- a/odbc/examples/basic/main.cpp +++ b/odbc/examples/basic/main.cpp @@ -63,18 +63,10 @@ int main() { std::cout << "6. Executing query" << std::endl; SQLCHAR query[] = R"( DECLARE $p1 AS Int64?; - SELECT $p1 + 1, 'test1'; - SELECT $p1 + 2, 'test2'; - SELECT $p1 + 3, 'test3'; - SELECT $p1 + 4, 'test4'; - SELECT $p1 + 5, 'test5'; - SELECT $p1 + 6, 'test6'; - SELECT $p1 + 7, 'test7'; - SELECT $p1 + 8, 'test8'; - SELECT $p1 + 9, 'test9'; + SELECT id, data from test_table WHERE id == $p1; )"; - int64_t paramValue = 42; + int64_t paramValue = 1; SQLLEN paramInd = 0; ret = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, 0, 0, ¶mValue, 0, ¶mInd); if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { @@ -129,6 +121,8 @@ int main() { } std::cout << "8. Cleaning up" << std::endl; + + SQLCloseCursor(hstmt); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); SQLDisconnect(hdbc); SQLFreeHandle(SQL_HANDLE_DBC, hdbc); diff --git a/odbc/examples/scheme/CMakeLists.txt b/odbc/examples/scheme/CMakeLists.txt index abca75eb73..ffab881aed 100644 --- a/odbc/examples/scheme/CMakeLists.txt +++ b/odbc/examples/scheme/CMakeLists.txt @@ -1,14 +1,14 @@ add_executable(odbc_scheme - main.cpp + main.cpp ) target_link_libraries(odbc_scheme - PRIVATE - ODBC::ODBC + PRIVATE + ODBC::ODBC ) target_compile_definitions(odbc_scheme - PRIVATE - ODBC_DRIVER_PATH="$" + PRIVATE + ODBC_DRIVER_PATH="$" ) add_dependencies(odbc_scheme ydb-odbc) diff --git a/odbc/examples/scheme/main.cpp b/odbc/examples/scheme/main.cpp index 3228ba2470..3ae2cd6fe4 100644 --- a/odbc/examples/scheme/main.cpp +++ b/odbc/examples/scheme/main.cpp @@ -60,33 +60,6 @@ int main() { return 1; } - // std::cout << "6. Executing query" << std::endl; - // SQLCHAR query[] = R"( - // DECLARE $p1 AS Int64?; - // SELECT $p1 + 1, 'test1'; - // SELECT $p1 + 2, 'test2'; - // SELECT $p1 + 3, 'test3'; - // SELECT $p1 + 4, 'test4'; - // SELECT $p1 + 5, 'test5'; - // SELECT $p1 + 6, 'test6'; - // SELECT $p1 + 7, 'test7'; - // SELECT $p1 + 8, 'test8'; - // SELECT $p1 + 9, 'test9'; - // )"; - - // int64_t paramValue = 42; - // SQLLEN paramInd = 0; - // ret = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, 0, 0, ¶mValue, 0, ¶mInd); - // if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { - // std::cerr << "Error binding parameter" << std::endl; - // PrintOdbcError(SQL_HANDLE_STMT, hstmt); - // SQLFreeHandle(SQL_HANDLE_STMT, hstmt); - // SQLDisconnect(hdbc); - // SQLFreeHandle(SQL_HANDLE_DBC, hdbc); - // SQLFreeHandle(SQL_HANDLE_ENV, henv); - // return 1; - // } - std::cout << "6. Getting tables" << std::endl; SQLCHAR pattern[] = "/local"; @@ -127,8 +100,8 @@ int main() { return 1; } - std::cout << "Result column 1: " << value1 << std::endl; - std::cout << "Result column 2: " << value2 << std::endl; + std::cout << "Table name: " << value1 << std::endl; + std::cout << "Table type: " << value2 << std::endl; std::cout << "--------------------------------" << std::endl; } diff --git a/odbc/src/odbc_driver.cpp b/odbc/src/odbc_driver.cpp index 9e7a7d3aee..3dec17d6af 100644 --- a/odbc/src/odbc_driver.cpp +++ b/odbc/src/odbc_driver.cpp @@ -149,7 +149,29 @@ SQLRETURN SQL_API SQLExecDirect(SQLHSTMT statementHandle, return SQL_INVALID_HANDLE; } - return stmt->ExecDirect(NYdb::NOdbc::GetString(statementText, textLength)); + auto ret = stmt->Prepare(NYdb::NOdbc::GetString(statementText, textLength)); + if (ret != SQL_SUCCESS) { + return ret; + } + return stmt->Execute(); +} + +SQLRETURN SQL_API SQLPrepare(SQLHSTMT statementHandle, + SQLCHAR* statementText, + SQLINTEGER textLength) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + return stmt->Prepare(NYdb::NOdbc::GetString(statementText, textLength)); +} + +SQLRETURN SQL_API SQLExecute(SQLHSTMT statementHandle) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + return stmt->Execute(); } SQLRETURN SQL_API SQLFetch(SQLHSTMT statementHandle) { @@ -333,4 +355,63 @@ SQLRETURN SQL_API SQLTables(SQLHSTMT statementHandle, NYdb::NOdbc::GetString(tableType, nameLength4)); } +SQLRETURN SQL_API SQLCloseCursor(SQLHSTMT statementHandle) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + + return stmt->Close(false); +} + +SQLRETURN SQL_API SQLFreeStmt(SQLHSTMT statementHandle, SQLUSMALLINT option) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + switch (option) { + case SQL_CLOSE: + return stmt->Close(true); + case SQL_DROP: + return SQLFreeHandle(SQL_HANDLE_STMT, statementHandle); + case SQL_UNBIND: + stmt->UnbindColumns(); + return SQL_SUCCESS; + case SQL_RESET_PARAMS: + stmt->ResetParams(); + return SQL_SUCCESS; + default: + return SQL_ERROR; + } +} + +SQLRETURN SQL_API SQLFetchScroll(SQLHSTMT statementHandle, SQLSMALLINT fetchOrientation, SQLLEN fetchOffset) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + if (fetchOrientation == SQL_FETCH_NEXT) { + return stmt->Fetch(); + } else { + stmt->AddError("HYC00", 0, "Only SQL_FETCH_NEXT is supported"); + return SQL_ERROR; + } +} + +SQLRETURN SQL_API SQLRowCount(SQLHSTMT statementHandle, SQLLEN* rowCount) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + return stmt->RowCount(rowCount); +} + +SQLRETURN SQL_API SQLNumResultCols(SQLHSTMT statementHandle, SQLSMALLINT* colCount) { + auto stmt = static_cast(statementHandle); + if (!stmt) { + return SQL_INVALID_HANDLE; + } + return stmt->NumResultCols(colCount); +} + } diff --git a/odbc/src/statement.cpp b/odbc/src/statement.cpp index 856eb5b341..2bf4c78fd1 100644 --- a/odbc/src/statement.cpp +++ b/odbc/src/statement.cpp @@ -1,5 +1,6 @@ #include "statement.h" +#include "utils/convert.h" #include "utils/types.h" #include @@ -11,19 +12,27 @@ namespace NOdbc { TStatement::TStatement(TConnection* conn) : Conn_(conn) {} -SQLRETURN TStatement::ExecDirect(const std::string& statementText) { - ClearStatement(); +SQLRETURN TStatement::Prepare(const std::string& statementText) { + Cursor_.reset(); + PreparedQuery_ = statementText; + IsPrepared_ = true; + return SQL_SUCCESS; +} +SQLRETURN TStatement::Execute() { + if (!IsPrepared_ || PreparedQuery_.empty()) { + AddError("HY007", 0, "No prepared statement"); + return SQL_ERROR; + } + Cursor_.reset(); auto* client = Conn_->GetClient(); if (!client) { return SQL_ERROR; } - NYdb::TParams params = BuildParams(); if (!Errors_.empty()) { return SQL_ERROR; } - if (!Conn_->GetTx()) { auto sessionResult = client->GetSession().ExtractValueSync(); if (!sessionResult.IsSuccess()) { @@ -36,34 +45,41 @@ SQLRETURN TStatement::ExecDirect(const std::string& statementText) { } Conn_->SetTx(beginTxResult.GetTransaction()); } - auto session = Conn_->GetTx()->GetSession(); - auto iterator = session.StreamExecuteQuery(statementText, + auto iterator = session.StreamExecuteQuery(PreparedQuery_, NQuery::TTxControl::Tx(*Conn_->GetTx()).CommitTx(Conn_->GetAutocommit()), params).ExtractValueSync(); - if (!iterator.IsSuccess()) { return SQL_ERROR; } - - ResultSet_ = CreateExecResultSet(std::move(iterator)); - + Cursor_ = CreateExecCursor(this, std::move(iterator)); + IsPrepared_ = false; + PreparedQuery_.clear(); return SQL_SUCCESS; } SQLRETURN TStatement::Fetch() { - if (!ResultSet_) { - ClearStatement(); + if (!Cursor_) { + Cursor_.reset(); return SQL_NO_DATA; } - return ResultSet_->Fetch() ? SQL_SUCCESS : SQL_NO_DATA; + return Cursor_->Fetch() ? SQL_SUCCESS : SQL_NO_DATA; } SQLRETURN TStatement::GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) { - if (!ResultSet_) { + if (!Cursor_) { return SQL_NO_DATA; } - return ResultSet_->GetData(columnNumber, targetType, targetValue, bufferLength, strLenOrInd); + return Cursor_->GetData(columnNumber, targetType, targetValue, bufferLength, strLenOrInd); +} + +void TStatement::FillBoundColumns() { + if (!Cursor_) { + return; + } + for (const auto& col : BoundColumns_) { + Cursor_->GetData(col.ColumnNumber, col.TargetType, col.TargetValue, col.BufferLength, col.StrLenOrInd); + } } SQLRETURN TStatement::GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, @@ -92,10 +108,18 @@ SQLRETURN TStatement::GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLIN } SQLRETURN TStatement::BindCol(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) { - if (!ResultSet_) { + if (!Cursor_) { return SQL_NO_DATA; } - return ResultSet_->BindCol(columnNumber, targetType, targetValue, bufferLength, strLenOrInd); + + BoundColumns_.erase(std::remove_if(BoundColumns_.begin(), BoundColumns_.end(), + [columnNumber](const TBoundColumn& col) { return col.ColumnNumber == columnNumber; }), BoundColumns_.end()); + + if (!targetValue) { + return SQL_SUCCESS; + } + BoundColumns_.push_back({columnNumber, targetType, targetValue, bufferLength, strLenOrInd}); + return SQL_SUCCESS; } SQLRETURN TStatement::BindParameter(SQLUSMALLINT paramNumber, @@ -127,14 +151,6 @@ void TStatement::AddError(const std::string& sqlState, SQLINTEGER nativeError, c Errors_.push_back({sqlState, nativeError, message}); } -void TStatement::ClearErrors() { - Errors_.clear(); -} - -void TStatement::ClearStatement() { - ResultSet_.reset(); -} - NYdb::TParams TStatement::BuildParams() { Errors_.clear(); NYdb::TParamsBuilder paramsBuilder; @@ -150,8 +166,8 @@ SQLRETURN TStatement::Columns(const std::string& catalogName, const std::string& schemaName, const std::string& tableName, const std::string& columnName) { - ClearErrors(); - ClearStatement(); + Errors_.clear(); + Cursor_.reset(); std::vector columns = { {"TABLE_CAT", SQL_VARCHAR, 128, SQL_NULLABLE}, @@ -236,7 +252,7 @@ SQLRETURN TStatement::Columns(const std::string& catalogName, } } - ResultSet_ = CreateVirtualResultSet(columns, table); + Cursor_ = CreateVirtualCursor(this, columns, table); return SQL_SUCCESS; } @@ -244,8 +260,8 @@ SQLRETURN TStatement::Tables(const std::string& catalogName, const std::string& schemaName, const std::string& tableName, const std::string& tableType) { - ClearErrors(); - ClearStatement(); + Errors_.clear(); + Cursor_.reset(); std::vector columns = { {"TABLE_CAT", SQL_VARCHAR, 128, SQL_NULLABLE}, @@ -270,8 +286,6 @@ SQLRETURN TStatement::Tables(const std::string& catalogName, continue; } - std::cout << "Table name: " << entry.Name << " type: " << *tableType << std::endl; - table.push_back({ TValueBuilder().OptionalUtf8(std::nullopt).Build(), TValueBuilder().OptionalUtf8(std::nullopt).Build(), @@ -281,7 +295,7 @@ SQLRETURN TStatement::Tables(const std::string& catalogName, }); } - ResultSet_ = CreateVirtualResultSet(columns, table); + Cursor_ = CreateVirtualCursor(this, columns, table); return SQL_SUCCESS; } @@ -353,5 +367,47 @@ std::optional TStatement::GetTableType(NScheme::ESchemeEntryType ty } } +SQLRETURN TStatement::Close(bool force) { + if (!force && !Cursor_) { + AddError("24000", 0, "Invalid handle"); + return SQL_ERROR; + } + + Cursor_.reset(); + PreparedQuery_.clear(); + IsPrepared_ = false; + Errors_.clear(); + return SQL_SUCCESS; +} + +void TStatement::UnbindColumns() { + BoundColumns_.clear(); +} + +void TStatement::ResetParams() { + BoundParams_.clear(); +} + +SQLRETURN TStatement::RowCount(SQLLEN* rowCount) { + if (!rowCount) { + return SQL_ERROR; + } + + *rowCount = -1; + return SQL_SUCCESS; +} + +SQLRETURN TStatement::NumResultCols(SQLSMALLINT* colCount) { + if (!colCount) { + return SQL_ERROR; + } + if (!Cursor_) { + *colCount = 0; + return SQL_SUCCESS; + } + *colCount = static_cast(Cursor_->GetColumnMeta().size()); + return SQL_SUCCESS; +} + } // namespace NOdbc } // namespace NYdb diff --git a/odbc/src/statement.h b/odbc/src/statement.h index b4568b97fb..d6b47a1146 100644 --- a/odbc/src/statement.h +++ b/odbc/src/statement.h @@ -1,8 +1,9 @@ #pragma once #include "connection.h" -#include "utils/result.h" -#include "utils/convert.h" + +#include "utils/bindings.h" +#include "utils/cursor.h" #include @@ -17,15 +18,23 @@ namespace NYdb { namespace NOdbc { -class TStatement { +class TStatement : public IBindingFiller { public: TStatement(TConnection* conn); - SQLRETURN ExecDirect(const std::string& statementText); + SQLRETURN Prepare(const std::string& statementText); + SQLRETURN Execute(); + SQLRETURN Fetch(); SQLRETURN GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd); + void FillBoundColumns() override; + + SQLRETURN Close(bool force = false); + void UnbindColumns(); + void ResetParams(); + SQLRETURN GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength); @@ -42,17 +51,17 @@ class TStatement { const std::string& tableName, const std::string& tableType); + SQLRETURN RowCount(SQLLEN* rowCount); + SQLRETURN NumResultCols(SQLSMALLINT* colCount); + TConnection* GetConnection() { return Conn_; } void AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message); - void ClearErrors(); NYdb::TParams BuildParams(); - void ClearStatement(); - private: std::vector GetPatternEntries(const std::string& pattern); SQLRETURN VisitEntry(const std::string& path, const std::string& pattern, std::vector& resultEntries); @@ -63,10 +72,13 @@ class TStatement { TConnection* Conn_; TErrorList Errors_; - std::unique_ptr ResultSet_; + std::unique_ptr Cursor_; std::vector BoundColumns_; std::vector BoundParams_; + + std::string PreparedQuery_; + bool IsPrepared_ = false; }; } // namespace NOdbc diff --git a/odbc/src/utils/bindings.h b/odbc/src/utils/bindings.h new file mode 100644 index 0000000000..df76de4e95 --- /dev/null +++ b/odbc/src/utils/bindings.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +namespace NYdb { +namespace NOdbc { + +struct TBoundParam { + SQLUSMALLINT ParamNumber; + SQLSMALLINT InputOutputType; + SQLSMALLINT ValueType; + SQLSMALLINT ParameterType; + SQLULEN ColumnSize; + SQLSMALLINT DecimalDigits; + SQLPOINTER ParameterValuePtr; + SQLLEN BufferLength; + SQLLEN* StrLenOrIndPtr; +}; + +struct TBoundColumn { + SQLUSMALLINT ColumnNumber; + SQLSMALLINT TargetType; + SQLPOINTER TargetValue; + SQLLEN BufferLength; + SQLLEN* StrLenOrInd; +}; + +class IBindingFiller { +public: + virtual void FillBoundColumns() = 0; + + virtual ~IBindingFiller() = default; +}; + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/utils/convert.cpp b/odbc/src/utils/convert.cpp index b10f839dc1..8754862287 100644 --- a/odbc/src/utils/convert.cpp +++ b/odbc/src/utils/convert.cpp @@ -291,7 +291,9 @@ SQLRETURN ConvertParam(const TBoundParam& param, TParamValueBuilder& builder) { SQLRETURN ConvertColumn(TValueParser& parser, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) { if (parser.IsNull()) { - if (strLenOrInd) *strLenOrInd = SQL_NULL_DATA; + if (strLenOrInd) { + *strLenOrInd = SQL_NULL_DATA; + } return SQL_SUCCESS; } @@ -320,8 +322,12 @@ SQLRETURN ConvertColumn(TValueParser& parser, SQLSMALLINT targetType, SQLPOINTER case EPrimitiveType::Bool: v = parser.GetBool() ? 1 : 0; break; default: return SQL_ERROR; } - if (targetValue) *reinterpret_cast(targetValue) = v; - if (strLenOrInd) *strLenOrInd = sizeof(int32_t); + if (targetValue) { + *reinterpret_cast(targetValue) = v; + } + if (strLenOrInd) { + *strLenOrInd = sizeof(int32_t); + } return SQL_SUCCESS; } case SQL_C_SBIGINT: @@ -334,8 +340,12 @@ SQLRETURN ConvertColumn(TValueParser& parser, SQLSMALLINT targetType, SQLPOINTER case EPrimitiveType::Uint32: v = static_cast(parser.GetUint32()); break; default: return SQL_ERROR; } - if (targetValue) *reinterpret_cast(targetValue) = v; - if (strLenOrInd) *strLenOrInd = sizeof(SQLBIGINT); + if (targetValue) { + *reinterpret_cast(targetValue) = v; + } + if (strLenOrInd) { + *strLenOrInd = sizeof(SQLBIGINT); + } return SQL_SUCCESS; } case SQL_C_DOUBLE: @@ -346,8 +356,12 @@ SQLRETURN ConvertColumn(TValueParser& parser, SQLSMALLINT targetType, SQLPOINTER case EPrimitiveType::Float: v = parser.GetFloat(); break; default: return SQL_ERROR; } - if (targetValue) *reinterpret_cast(targetValue) = v; - if (strLenOrInd) *strLenOrInd = sizeof(double); + if (targetValue) { + *reinterpret_cast(targetValue) = v; + } + if (strLenOrInd) { + *strLenOrInd = sizeof(double); + } return SQL_SUCCESS; } case SQL_C_CHAR: @@ -366,14 +380,20 @@ SQLRETURN ConvertColumn(TValueParser& parser, SQLSMALLINT targetType, SQLPOINTER memcpy(targetValue, str.data(), copyLen); reinterpret_cast(targetValue)[copyLen] = 0; } - if (strLenOrInd) *strLenOrInd = len; + if (strLenOrInd) { + *strLenOrInd = len; + } return SQL_SUCCESS; } case SQL_C_BIT: { char v = parser.GetBool() ? 1 : 0; - if (targetValue) *reinterpret_cast(targetValue) = v; - if (strLenOrInd) *strLenOrInd = sizeof(char); + if (targetValue) { + *reinterpret_cast(targetValue) = v; + } + if (strLenOrInd) { + *strLenOrInd = sizeof(char); + } return SQL_SUCCESS; } default: diff --git a/odbc/src/utils/convert.h b/odbc/src/utils/convert.h index dba81c2b34..9b8140665e 100644 --- a/odbc/src/utils/convert.h +++ b/odbc/src/utils/convert.h @@ -1,5 +1,7 @@ #pragma once +#include "bindings.h" + #include #include @@ -8,26 +10,6 @@ namespace NYdb { namespace NOdbc { -struct TBoundParam { - SQLUSMALLINT ParamNumber; - SQLSMALLINT InputOutputType; - SQLSMALLINT ValueType; - SQLSMALLINT ParameterType; - SQLULEN ColumnSize; - SQLSMALLINT DecimalDigits; - SQLPOINTER ParameterValuePtr; - SQLLEN BufferLength; - SQLLEN* StrLenOrIndPtr; -}; - -struct TBoundColumn { - SQLUSMALLINT ColumnNumber; - SQLSMALLINT TargetType; - SQLPOINTER TargetValue; - SQLLEN BufferLength; - SQLLEN* StrLenOrInd; -}; - SQLRETURN ConvertParam(const TBoundParam& param, TParamValueBuilder& builder); SQLRETURN ConvertColumn(TValueParser& parser, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd); diff --git a/odbc/src/utils/cursor.cpp b/odbc/src/utils/cursor.cpp new file mode 100644 index 0000000000..fbd10588ab --- /dev/null +++ b/odbc/src/utils/cursor.cpp @@ -0,0 +1,119 @@ +#include "cursor.h" + +#include "convert.h" +#include "types.h" + +namespace NYdb { +namespace NOdbc { + +class TExecCursor : public ICursor { +public: + TExecCursor(IBindingFiller* bindingFiller, NQuery::TExecuteQueryIterator iterator) + : BindingFiller_(bindingFiller) + , Iterator_(std::move(iterator)) + {} + + bool Fetch() override { + while (true) { + if (ResultSetParser_) { + if (ResultSetParser_->TryNextRow()) { + BindingFiller_->FillBoundColumns(); + return true; + } + ResultSetParser_.reset(); + } + auto part = Iterator_.ReadNext().ExtractValueSync(); + if (part.EOS()) { + return false; + } + if (!part.IsSuccess()) { + return false; + } + if (part.HasResultSet()) { + ResultSetParser_ = std::make_unique(part.ExtractResultSet()); + } + } + return false; + } + + SQLRETURN GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, + SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) override { + if (!ResultSetParser_) { + return SQL_NO_DATA; + } + if (columnNumber < 1 || columnNumber > ResultSetParser_->ColumnsCount()) { + return SQL_ERROR; + } + return ConvertColumn(ResultSetParser_->ColumnParser(columnNumber - 1), targetType, targetValue, bufferLength, strLenOrInd); + } + + const std::vector& GetColumnMeta() const override { + return Columns_; + } + +private: + // void GetNextPart() { + // auto part = Iterator_.ReadNext().ExtractValueSync(); + // while (!part.EOS() && part.IsSuccess() && !part.HasResultSet()) { + // part = Iterator_.ReadNext().ExtractValueSync(); + // } + // Part_ = std::move(part); + // } + + IBindingFiller* BindingFiller_; + NQuery::TExecuteQueryIterator Iterator_; + // std::optional Part_; + std::unique_ptr ResultSetParser_; + std::vector Columns_; +}; + +class TVirtualCursor : public ICursor { +public: + TVirtualCursor(IBindingFiller* bindingFiller, const std::vector& columns, const TTable& table) + : BindingFiller_(bindingFiller) + , Columns_(columns) + , Table_(table) + {} + + bool Fetch() override { + Cursor_++; + if (Cursor_ >= static_cast(Table_.size())) { + return false; + } + BindingFiller_->FillBoundColumns(); + return true; + } + + SQLRETURN GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, + SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) override { + if (Cursor_ >= static_cast(Table_.size())) { + return SQL_NO_DATA; + } + if (Cursor_ < 0 || columnNumber < 1 || columnNumber > Columns_.size()) { + return SQL_ERROR; + } + TValueParser parser{Table_[Cursor_][columnNumber - 1]}; + return ConvertColumn(parser, targetType, targetValue, bufferLength, strLenOrInd); + } + + const std::vector& GetColumnMeta() const override { + return Columns_; + } + +private: + IBindingFiller* BindingFiller_; + std::vector Columns_; + TTable Table_; + int64_t Cursor_ = -1; +}; + +std::unique_ptr CreateExecCursor(IBindingFiller* bindingFiller, NQuery::TExecuteQueryIterator iterator) { + return std::make_unique(bindingFiller, std::move(iterator)); +} + +std::unique_ptr CreateVirtualCursor(IBindingFiller* bindingFiller, const std::vector& columns, const TTable& table) { + return std::make_unique(bindingFiller, columns, table); +} + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/utils/result.h b/odbc/src/utils/cursor.h similarity index 50% rename from odbc/src/utils/result.h rename to odbc/src/utils/cursor.h index e5334038a2..e4b13ed521 100644 --- a/odbc/src/utils/result.h +++ b/odbc/src/utils/cursor.h @@ -1,5 +1,7 @@ #pragma once +#include "bindings.h" + #include #include @@ -19,20 +21,17 @@ struct TColumnMeta { using TTable = std::vector>; -class IResultSet { +class ICursor { public: - virtual ~IResultSet() = default; + virtual ~ICursor() = default; virtual bool Fetch() = 0; virtual SQLRETURN GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) = 0; - virtual SQLRETURN BindCol(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, - SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) = 0; - virtual size_t ColumnsCount() const = 0; - virtual const TColumnMeta& GetColumnMeta(size_t index) const = 0; + virtual const std::vector& GetColumnMeta() const = 0; }; -std::unique_ptr CreateExecResultSet(NYdb::NQuery::TExecuteQueryIterator iterator); -std::unique_ptr CreateVirtualResultSet(const std::vector& columns, const TTable& table); +std::unique_ptr CreateExecCursor(IBindingFiller* bindingFiller, NYdb::NQuery::TExecuteQueryIterator iterator); +std::unique_ptr CreateVirtualCursor(IBindingFiller* bindingFiller, const std::vector& columns, const TTable& table); } // namespace NOdbc } // namespace NYdb diff --git a/odbc/src/utils/result.cpp b/odbc/src/utils/result.cpp deleted file mode 100644 index ca80f5b0b2..0000000000 --- a/odbc/src/utils/result.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "result.h" - -#include "convert.h" - -namespace NYdb { -namespace NOdbc { - -class TCommonResultSet : public IResultSet { -public: - SQLRETURN BindCol(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, - SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) override { - BoundColumns_.erase(std::remove_if(BoundColumns_.begin(), BoundColumns_.end(), - [columnNumber](const TBoundColumn& col) { return col.ColumnNumber == columnNumber; }), BoundColumns_.end()); - if (!targetValue) { - return SQL_SUCCESS; - } - BoundColumns_.push_back({columnNumber, targetType, targetValue, bufferLength, strLenOrInd}); - return SQL_SUCCESS; - } - -protected: - void FillBoundColumns() { - for (const auto& col : BoundColumns_) { - GetData(col.ColumnNumber, col.TargetType, col.TargetValue, col.BufferLength, col.StrLenOrInd); - } - } - -protected: - std::vector BoundColumns_; -}; - -class TExecResultSet : public TCommonResultSet { -public: - TExecResultSet(NYdb::NQuery::TExecuteQueryIterator iterator) - : Iterator_(std::move(iterator)) {} - - bool Fetch() override { - while (true) { - if (ResultSetParser_) { - if (ResultSetParser_->TryNextRow()) { - FillBoundColumns(); - return true; - } - ResultSetParser_.reset(); - } - auto part = Iterator_.ReadNext().ExtractValueSync(); - if (part.EOS()) { - return false; - } - if (!part.IsSuccess()) { - return false; - } - if (part.HasResultSet()) { - ResultSetParser_ = std::make_unique(part.ExtractResultSet()); - } - } - return false; - } - - SQLRETURN GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, - SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) override { - if (!ResultSetParser_) { - return SQL_NO_DATA; - } - if (columnNumber < 1 || columnNumber > ResultSetParser_->ColumnsCount()) { - return SQL_ERROR; - } - return ConvertColumn(ResultSetParser_->ColumnParser(columnNumber - 1), targetType, targetValue, bufferLength, strLenOrInd); - } - - size_t ColumnsCount() const override { - return ResultSetParser_ ? ResultSetParser_->ColumnsCount() : 0; - } - - const TColumnMeta& GetColumnMeta(size_t index) const override { - // TODO: implement return column metadata - static TColumnMeta dummy; - return dummy; - } - -private: - NYdb::NQuery::TExecuteQueryIterator Iterator_; - std::unique_ptr ResultSetParser_; -}; - -class TVirtualResultSet : public TCommonResultSet { -public: - TVirtualResultSet(const std::vector& columns, const TTable& table) - : Columns_(columns), Table_(table) { - std::cout << "TVirtualResultSet constructor" << std::endl; - std::cout << "Columns count: " << Columns_.size() << std::endl; - std::cout << "Table size: " << Table_.size() << std::endl; - } - - bool Fetch() override { - std::cout << "Fetching row " << Cursor_ << std::endl; - Cursor_++; - if (Cursor_ >= static_cast(Table_.size())) { - return false; - } - FillBoundColumns(); - return true; - } - - SQLRETURN GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, - SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd) override { - if (Cursor_ >= static_cast(Table_.size())) { - return SQL_NO_DATA; - } - if (Cursor_ < 0 || columnNumber < 1 || columnNumber > Columns_.size()) { - return SQL_ERROR; - } - TValueParser parser{Table_[Cursor_][columnNumber - 1]}; - return ConvertColumn(parser, targetType, targetValue, bufferLength, strLenOrInd); - } - - size_t ColumnsCount() const override { - return Columns_.size(); - } - - const TColumnMeta& GetColumnMeta(size_t index) const override { - return Columns_[index]; - } - -private: - std::vector Columns_; - TTable Table_; - int64_t Cursor_ = -1; -}; - -std::unique_ptr CreateExecResultSet(NYdb::NQuery::TExecuteQueryIterator iterator) { - return std::make_unique(std::move(iterator)); -} - -std::unique_ptr CreateVirtualResultSet(const std::vector& columns, const TTable& table) { - return std::make_unique(columns, table); -} - -} // namespace NOdbc -} // namespace NYdb diff --git a/odbc/src/utils/types.cpp b/odbc/src/utils/types.cpp index aa038420e5..ce5ead462c 100644 --- a/odbc/src/utils/types.cpp +++ b/odbc/src/utils/types.cpp @@ -3,10 +3,20 @@ namespace NYdb { namespace NOdbc { -SQLINTEGER GetTypeId(const TType& type) { +SQLSMALLINT GetTypeId(const TType& type) { + // TODO: implement return 0; } +SQLSMALLINT IsNullable(const TType& type) { + TTypeParser typeParser(type); + if (typeParser.GetKind() == TTypeParser::ETypeKind::Optional || typeParser.GetKind() == TTypeParser::ETypeKind::Null) { + return SQL_NULLABLE; + } + + return SQL_NO_NULLS; +} + std::optional GetDecimalDigits(const TType& type) { TTypeParser typeParser(type); if (typeParser.GetKind() != TTypeParser::ETypeKind::Primitive) { diff --git a/odbc/src/utils/types.h b/odbc/src/utils/types.h index 0b9dd76aa1..3f48170290 100644 --- a/odbc/src/utils/types.h +++ b/odbc/src/utils/types.h @@ -7,7 +7,9 @@ namespace NYdb { namespace NOdbc { -SQLINTEGER GetTypeId(const TType& type); +SQLSMALLINT GetTypeId(const TType& type); +SQLSMALLINT IsNullable(const TType& type); + std::optional GetDecimalDigits(const TType& type); std::optional GetRadix(const TType& type); From c775f216422e49d452fad30c9081f18aa922e424 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Tue, 3 Jun 2025 00:17:18 +0000 Subject: [PATCH 11/11] step --- cmake/testing.cmake | 4 +- odbc/README.md | 81 ++++++----------- odbc/src/connection.cpp | 4 +- odbc/src/utils/convert.cpp | 7 +- odbc/tests/CMakeLists.txt | 2 +- odbc/tests/integration/CMakeLists.txt | 4 + odbc/tests/integration/basic_it.cpp | 123 ++++++++++++++++++++++++++ 7 files changed, 167 insertions(+), 58 deletions(-) create mode 100644 odbc/tests/integration/CMakeLists.txt create mode 100644 odbc/tests/integration/basic_it.cpp diff --git a/cmake/testing.cmake b/cmake/testing.cmake index 1a650051a4..43a0ba126c 100644 --- a/cmake/testing.cmake +++ b/cmake/testing.cmake @@ -102,7 +102,9 @@ if (YDB_SDK_ODBC) LINK_LIBRARIES ${ODBC_TEST_LINK_LIBRARIES} ODBC::ODBC - LABELS ${ODBC_TEST_LABELS} + LABELS + integration + ${ODBC_TEST_LABELS} ) target_compile_definitions(${ODBC_TEST_NAME} diff --git a/odbc/README.md b/odbc/README.md index 4d502aaad7..c73f9b8704 100644 --- a/odbc/README.md +++ b/odbc/README.md @@ -1,105 +1,80 @@ # YDB ODBC Driver -ODBC драйвер для YDB. +ODBC driver for YDB. -## Требования +## Requirements -- CMake 3.10 или выше -- Компилятор C/C++ с поддержкой C11 и C++20 +- CMake 3.10 or higher +- C/C++ compiler with C11 and C++20 support - YDB C++ SDK -- unixODBC (для Linux/macOS) +- unixODBC (for Linux/macOS) -## Сборка +## Build ```bash -mkdir build && cd build -cmake .. -make +cmake -DYDB_SDK_ODBC=1 --preset release-clang +cmake --build --preset default ``` -## Установка +## Configuration -```bash -sudo make install -``` - -Это установит: -- Библиотеку драйвера в `/usr/local/lib/` -- Конфигурацию драйвера в `/etc/odbcinst.d/` -- Конфигурацию источников данных в `/etc/odbc.ini` - -## Настройка - -1. Убедитесь, что драйвер зарегистрирован: +1. Make sure the driver is registered: ```bash odbcinst -q -d ``` -2. Проверьте доступные источники данных: +2. Check available data sources: ```bash odbcinst -q -s ``` -3. Отредактируйте `/etc/odbc.ini` для настройки подключения: +3. Edit `/etc/odbc.ini` to configure the connection: ```ini [YDB] Driver=YDB Description=YDB Database Connection -Server=grpc://your-server:2136 -Database=your-database -AuthMode=none # или token для аутентификации по токену +Server=your-server:port +Database=/path/to/database ``` -## Использование +## Usage -Пример подключения через isql: +Example of connecting via isql: ```bash isql -v YDB ``` -Пример использования в C: +Example usage in C: ```c SQLHENV env; SQLHDBC dbc; SQLHSTMT stmt; -// Инициализация окружения +// Initialize environment SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env); SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); -// Подключение +// Connect SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc); SQLConnect(dbc, (SQLCHAR*)"YDB", SQL_NTS, (SQLCHAR*)"", SQL_NTS, (SQLCHAR*)"", SQL_NTS); -// Выполнение запроса +// Execute query SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt); SQLExecDirect(stmt, (SQLCHAR*)"SELECT * FROM mytable", SQL_NTS); -// Очистка +// Cleanup SQLFreeHandle(SQL_HANDLE_STMT, stmt); SQLDisconnect(dbc); SQLFreeHandle(SQL_HANDLE_DBC, dbc); SQLFreeHandle(SQL_HANDLE_ENV, env); ``` -## Поддерживаемые функции - -- SQLAllocHandle -- SQLConnect -- SQLDisconnect -- SQLExecDirect -- SQLFetch -- SQLGetData -- SQLPrepare -- SQLExecute -- SQLCloseCursor -- SQLFreeHandle -- SQLGetInfo -- SQLGetDescField -- SQLSetDescField - -## Лицензия - -Apache License 2.0 \ No newline at end of file +## Parameters + +Use names $p1, $p2, ... for parameter names + +## License + +Apache License 2.0 diff --git a/odbc/src/connection.cpp b/odbc/src/connection.cpp index 7806096bc6..eba32c74ef 100644 --- a/odbc/src/connection.cpp +++ b/odbc/src/connection.cpp @@ -57,8 +57,8 @@ SQLRETURN TConnection::Connect(const std::string& serverName, char endpoint[256] = {0}; char database[256] = {0}; - SQLGetPrivateProfileString(serverName.c_str(), "Endpoint", "", endpoint, sizeof(endpoint), nullptr); - SQLGetPrivateProfileString(serverName.c_str(), "Database", "", database, sizeof(database), nullptr); + //SQLGetPrivateProfileString(serverName.c_str(), "Endpoint", "", endpoint, sizeof(endpoint), nullptr); + //SQLGetPrivateProfileString(serverName.c_str(), "Database", "", database, sizeof(database), nullptr); Endpoint_ = endpoint; Database_ = database; diff --git a/odbc/src/utils/convert.cpp b/odbc/src/utils/convert.cpp index 8754862287..224f228e49 100644 --- a/odbc/src/utils/convert.cpp +++ b/odbc/src/utils/convert.cpp @@ -312,10 +312,15 @@ SQLRETURN ConvertColumn(TValueParser& parser, SQLSMALLINT targetType, SQLPOINTER switch (targetType) { case SQL_C_SLONG: + case SQL_C_LONG: { int32_t v = 0; switch (ydbType) { - case EPrimitiveType::Int32: v = parser.GetInt32(); break; + case EPrimitiveType::Int16: v = static_cast(parser.GetInt16()); break; + case EPrimitiveType::Uint16: v = static_cast(parser.GetUint16()); break; + case EPrimitiveType::Int8: v = static_cast(parser.GetInt8()); break; + case EPrimitiveType::Uint8: v = static_cast(parser.GetUint8()); break; + case EPrimitiveType::Int32: v = static_cast(parser.GetInt32()); break; case EPrimitiveType::Uint32: v = static_cast(parser.GetUint32()); break; case EPrimitiveType::Int64: v = static_cast(parser.GetInt64()); break; case EPrimitiveType::Uint64: v = static_cast(parser.GetUint64()); break; diff --git a/odbc/tests/CMakeLists.txt b/odbc/tests/CMakeLists.txt index 446b6139f9..729c6ee077 100644 --- a/odbc/tests/CMakeLists.txt +++ b/odbc/tests/CMakeLists.txt @@ -1,2 +1,2 @@ -#add_subdirectory(integration) +add_subdirectory(integration) add_subdirectory(unit) diff --git a/odbc/tests/integration/CMakeLists.txt b/odbc/tests/integration/CMakeLists.txt new file mode 100644 index 0000000000..e1aad9d391 --- /dev/null +++ b/odbc/tests/integration/CMakeLists.txt @@ -0,0 +1,4 @@ +add_odbc_test(NAME odbc-basic_it + SOURCES + basic_it.cpp +) diff --git a/odbc/tests/integration/basic_it.cpp b/odbc/tests/integration/basic_it.cpp new file mode 100644 index 0000000000..b4c7078ac4 --- /dev/null +++ b/odbc/tests/integration/basic_it.cpp @@ -0,0 +1,123 @@ +#include + +#include +#include + +#include + + +#define CHECK_ODBC_OK(rc, handle, type) \ + ASSERT_TRUE((rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO) << GetOdbcError(handle, type) + +std::string GetOdbcError(SQLHANDLE handle, SQLSMALLINT type) { + SQLCHAR sqlState[6], message[256]; + SQLINTEGER nativeError; + SQLSMALLINT textLength; + SQLRETURN rc = SQLGetDiagRec(type, handle, 1, sqlState, &nativeError, message, sizeof(message), &textLength); + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return std::string((char*)sqlState) + ": " + (char*)message; + } + return "Unknown ODBC error"; +} + +const char* kConnStr = "Driver=" ODBC_DRIVER_PATH ";Endpoint=localhost:2136;Database=/local;"; + +TEST(OdbcBasic, SimpleQuery) { + SQLHENV env; + SQLHDBC dbc; + SQLHSTMT stmt; + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env), SQL_SUCCESS); + ASSERT_EQ(SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0), SQL_SUCCESS); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc), SQL_SUCCESS); + CHECK_ODBC_OK(SQLDriverConnect(dbc, nullptr, (SQLCHAR*)kConnStr, SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE), dbc, SQL_HANDLE_DBC); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), SQL_SUCCESS); + + // Simple query + CHECK_ODBC_OK(SQLExecDirect(stmt, (SQLCHAR*)"SELECT 1 AS one, 'abc' AS str", SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + + SQLINTEGER ival = 0; + char sval[16] = {0}; + SQLLEN ival_ind = 0, sval_ind = 0; + ASSERT_EQ(SQLGetData(stmt, 1, SQL_C_LONG, &ival, 0, &ival_ind), SQL_SUCCESS); + ASSERT_EQ(SQLGetData(stmt, 2, SQL_C_CHAR, sval, sizeof(sval), &sval_ind), SQL_SUCCESS); + ASSERT_EQ(ival, 1); + ASSERT_STREQ(sval, "abc"); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcBasic, ParameterizedQuery) { + SQLHENV env; + SQLHDBC dbc; + SQLHSTMT stmt; + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env), SQL_SUCCESS); + ASSERT_EQ(SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0), SQL_SUCCESS); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc), SQL_SUCCESS); + CHECK_ODBC_OK(SQLDriverConnect(dbc, nullptr, (SQLCHAR*)kConnStr, SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE), dbc, SQL_HANDLE_DBC); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), SQL_SUCCESS); + + SQLCHAR query[] = R"( + DECLARE $p1 AS Int32?; + SELECT $p1 + 10 AS res; + )"; + + // Parameterized query + CHECK_ODBC_OK(SQLPrepare(stmt, query, SQL_NTS), stmt, SQL_HANDLE_STMT); + SQLINTEGER param = 5; + CHECK_ODBC_OK(SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, ¶m, 0, nullptr), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecute(stmt), stmt, SQL_HANDLE_STMT); + + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + + SQLINTEGER res = 0; + SQLLEN res_ind = 0; + ASSERT_EQ(SQLGetData(stmt, 1, SQL_C_LONG, &res, 0, &res_ind), SQL_SUCCESS); + ASSERT_EQ(res, 15); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcBasic, ColumnBinding) { + SQLHENV env; + SQLHDBC dbc; + SQLHSTMT stmt; + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env), SQL_SUCCESS); + ASSERT_EQ(SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0), SQL_SUCCESS); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc), SQL_SUCCESS); + CHECK_ODBC_OK(SQLDriverConnect(dbc, nullptr, (SQLCHAR*)kConnStr, SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE), dbc, SQL_HANDLE_DBC); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), SQL_SUCCESS); + + SQLCHAR query_ddl[] = R"( + DROP TABLE IF EXISTS test_bind; + CREATE TABLE test_bind (id Int32, name Text, PRIMARY KEY (id)); + )"; + + SQLCHAR query[] = R"( + UPSERT INTO test_bind (id, name) VALUES (1, 'foo'), (2, 'bar'); + SELECT id, name FROM test_bind ORDER BY id; + )"; + + CHECK_ODBC_OK(SQLExecDirect(stmt, query_ddl, SQL_NTS), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecDirect(stmt, query, SQL_NTS), stmt, SQL_HANDLE_STMT); + + SQLINTEGER id = 0; + char name[16] = {0}; + SQLLEN id_ind = 0, name_ind = 0; + ASSERT_EQ(SQLBindCol(stmt, 1, SQL_C_LONG, &id, 0, &id_ind), SQL_SUCCESS); + ASSERT_EQ(SQLBindCol(stmt, 2, SQL_C_CHAR, name, sizeof(name), &name_ind), SQL_SUCCESS); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + ASSERT_EQ(id, 1); + ASSERT_STREQ(name, "foo"); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + ASSERT_EQ(id, 2); + ASSERT_STREQ(name, "bar"); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); +}