From 215747877c01f0d67f2cb6958297b526c420691c Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Fri, 18 Apr 2025 13:21:32 +0000 Subject: [PATCH 01/21] 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 41f4783ca2d..30e9ba2f7e5 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 00000000000..985782900ee --- /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 00000000000..4d502aaad73 --- /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 00000000000..6a95f9958c8 --- /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 00000000000..ac6802dc6c8 --- /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 00000000000..2415f80f55e --- /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 00000000000..6335b3ee389 --- /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 00000000000..fade7b6fb92 --- /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 00000000000..faa87471e43 --- /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 00000000000..5da339d19bd --- /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 00000000000..d95dc8ade72 --- /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 00000000000..15380bf40aa --- /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 00000000000..5fc19f7294b --- /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 00000000000..b9a7a5ffcd7 --- /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 592badcf1e05341f0da3ed6dcc06669324ae62bf Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Sat, 10 May 2025 18:12:57 +0000 Subject: [PATCH 02/21] 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 30e9ba2f7e5..b4bfc154e2f 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 00000000000..6f7a7295390 --- /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 00000000000..b7f929c5538 --- /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 00000000000..557994675f0 --- /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 00000000000..e3bd3918ac6 --- /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 00000000000..fc762ccf864 --- /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 00000000000..5f0ad2caa11 --- /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 00000000000..27e1093d68e --- /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 00000000000..87644f15f26 --- /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 00000000000..b662ea4f452 --- /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 00000000000..d7a7a4eba9e --- /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 00000000000..0f11c52c17c --- /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 00000000000..c6c52bc7373 --- /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 00000000000..2aa25ce4183 --- /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 546ce4e81f0..11933883493 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 ba1c144f0a1..2ed5dc4190d 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 118dde30ced..6dc97b78bd6 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 75d2d1b2538..8e513439417 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 4f7c3eca7f9..34b8ed62c3c 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 00000000000..5060c4c25d3 --- /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 00000000000..ca1e2018d0f --- /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 0936f385585..2b29726f007 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 6030de5f7f0..47364c55979 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 b46cc79159c..e03e675827a 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 2cdc984955f..114a0ae35ba 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 68846ab215c..2b7da165f05 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 64d30b4d8c6..77fd8ab446e 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 48a004b4cc6..9a0655e7d1b 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 19249951e37..792d11cb3b1 100644 --- a/examples/vector_index/CMakeLists.txt +++ b/examples/vector_index/CMakeLists.txt @@ -4,7 +4,7 @@ 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 985782900ee..b2a5b03b083 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 6a95f9958c8..00000000000 --- 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 ac6802dc6c8..00000000000 --- 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 faa87471e43..00000000000 --- 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 5da339d19bd..00000000000 --- 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 55bdd05341b..9eec918ec0e 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 46848877c69..535d21f2d61 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 2d485de4e4a..66d1c00d641 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 33752c8a6ee..db5a3da0bd2 100644 --- a/tests/unit/client/CMakeLists.txt +++ b/tests/unit/client/CMakeLists.txt @@ -5,7 +5,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 @@ -16,8 +16,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 ) @@ -27,8 +27,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 ) @@ -65,7 +65,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 ) @@ -75,8 +75,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 ) @@ -86,8 +86,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 c0566394afbf5dd8929b345fa226861232d91f59 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Fri, 16 May 2025 14:20:46 +0000 Subject: [PATCH 03/21] 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 b4bfc154e2f..18a5e9f7e28 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 6f7a7295390..b2c8fec22a4 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 11933883493..55044794726 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 22d0603e77c..a252c588ae8 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 b2a5b03b083..46747768327 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 00000000000..6f4cd2f5a31 --- /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 00000000000..a34cbd9301b --- /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 00000000000..9b8123ef7c1 --- /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 2415f80f55e..00000000000 --- 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 d95dc8ade72..00000000000 --- 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 00000000000..427b03f7ba1 --- /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 00000000000..cceadc2433f --- /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 15380bf40aa..00000000000 --- 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 5fc19f7294b..00000000000 --- 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 00000000000..0e1eef594d9 --- /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 00000000000..a45d7f0b7ee --- /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 00000000000..6f3b865035f --- /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 b9a7a5ffcd7..00000000000 --- 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 00000000000..224cc8dc122 --- /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 00000000000..f34e0e771e4 --- /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 6049cad3446907da2e08e7836d39fa257675625d Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Fri, 16 May 2025 14:46:04 +0000 Subject: [PATCH 04/21] 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 18a5e9f7e28..dd1eef3b6f3 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 b2c8fec22a4..00000000000 --- 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 b7f929c5538..00000000000 --- 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 557994675f0..00000000000 --- 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 e3bd3918ac6..00000000000 --- 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 fc762ccf864..00000000000 --- 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 5f0ad2caa11..00000000000 --- 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 27e1093d68e..00000000000 --- 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 87644f15f26..00000000000 --- 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 b662ea4f452..00000000000 --- 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 d7a7a4eba9e..00000000000 --- 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 0f11c52c17c..00000000000 --- 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 c6c52bc7373..00000000000 --- 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 2aa25ce4183..00000000000 --- 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 6dc97b78bd6..118dde30ced 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 5060c4c25d3..00000000000 --- 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 ca1e2018d0f..00000000000 --- 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 959c26c9915122251a6743cbe7dd49b5edfe2984 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 26 May 2025 18:34:06 +0000 Subject: [PATCH 05/21] 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 +- 13 files changed, 430 insertions(+), 158 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 46747768327..c45baef5f54 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 9b8123ef7c1..364b9e11123 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 fade7b6fb92..fd0b3f27650 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 427b03f7ba1..a2c0df7c545 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 cceadc2433f..95c872f04ba 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 0e1eef594d9..a09a634879b 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 a45d7f0b7ee..0190b913831 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 224cc8dc122..ab8318b0f99 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 f34e0e771e4..8f51be6759b 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 00000000000..2c62d56347e --- /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 00000000000..525a43c79aa --- /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 100c8ace2bc..0cc47bfd4cf 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 6e7a6a70ab7..d37d9d500eb 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 From 87dd083c69d2d191439c10c316d713032c979f2f Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 26 May 2025 19:07:52 +0000 Subject: [PATCH 06/21] 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 1319cb16896..9baeb9c74e0 100644 --- a/cmake/testing.cmake +++ b/cmake/testing.cmake @@ -103,3 +103,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 c45baef5f54..95ce1702d43 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 00000000000..446b6139f92 --- /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 00000000000..d1eac199615 --- /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 00000000000..6df6be54f14 --- /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 3e4dce70917673906d7683c27c8004c6f5039e1a Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 26 May 2025 19:10:35 +0000 Subject: [PATCH 07/21] 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/unit/client/CMakeLists.txt | 20 +++++++++---------- 20 files changed, 38 insertions(+), 38 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd1eef3b6f3..30e9ba2f7e5 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 55044794726..89ebb5eaca1 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 2ed5dc4190d..ba1c144f0a1 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 8e513439417..75d2d1b2538 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 34b8ed62c3c..4f7c3eca7f9 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 2b29726f007..0936f385585 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 47364c55979..6030de5f7f0 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 e03e675827a..b46cc79159c 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 114a0ae35ba..2cdc984955f 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 2b7da165f05..68846ab215c 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 77fd8ab446e..64d30b4d8c6 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 9a0655e7d1b..48a004b4cc6 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 792d11cb3b1..19249951e37 100644 --- a/examples/vector_index/CMakeLists.txt +++ b/examples/vector_index/CMakeLists.txt @@ -4,7 +4,7 @@ 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 95ce1702d43..6133906d9c0 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 9eec918ec0e..55bdd05341b 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 535d21f2d61..46848877c69 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 66d1c00d641..2d485de4e4a 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 0cc47bfd4cf..100c8ace2bc 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 d37d9d500eb..6e7a6a70ab7 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/unit/client/CMakeLists.txt b/tests/unit/client/CMakeLists.txt index db5a3da0bd2..33752c8a6ee 100644 --- a/tests/unit/client/CMakeLists.txt +++ b/tests/unit/client/CMakeLists.txt @@ -5,7 +5,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 @@ -16,8 +16,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 ) @@ -27,8 +27,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 ) @@ -65,7 +65,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 ) @@ -75,8 +75,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 ) @@ -86,8 +86,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 b52beaae1139aa56d4a441fda3cb71922199d27e Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 26 May 2025 20:56:06 +0000 Subject: [PATCH 08/21] 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 a2c0df7c545..6fbd8cf1e32 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 95c872f04ba..2b2fe22c81c 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 6f3b865035f..26de48e6af5 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 ab8318b0f99..3d19988a0bd 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 2c62d56347e..432418315a8 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 6df6be54f14..5c351a86771 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 8ab9903effac51b84aca565bc03b7bac471e01ef Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Fri, 30 May 2025 02:16:38 +0000 Subject: [PATCH 09/21] 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 6133906d9c0..eb47edc922a 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 6f4cd2f5a31..88b1f27cc60 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 00000000000..abca75eb735 --- /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 00000000000..3228ba2470b --- /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 6fbd8cf1e32..7806096bc63 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 2b2fe22c81c..fad81527772 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 26de48e6af5..9e7a7d3aee7 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 3d19988a0bd..856eb5b3417 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 8f51be6759b..b4568b97fb8 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 432418315a8..b10f839dc11 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 525a43c79aa..dba81c2b345 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 00000000000..ca80f5b0b20 --- /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 00000000000..e5334038a25 --- /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 00000000000..aa038420e53 --- /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 00000000000..0b9dd76aa13 --- /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 00000000000..9097ce80dbf --- /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 00000000000..b17fe2c235f --- /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 5c351a86771..f4bad34a366 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 0e0cbcb336fe629f81cbd41a53f80e288bec54d9 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Mon, 2 Jun 2025 23:27:43 +0000 Subject: [PATCH 10/21] 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 eb47edc922a..f814f003138 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 a34cbd9301b..b99d1175f43 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 364b9e11123..8084e32f3d1 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 abca75eb735..ffab881aed5 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 3228ba2470b..3ae2cd6fe40 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 9e7a7d3aee7..3dec17d6aff 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 856eb5b3417..2bf4c78fd13 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 b4568b97fb8..d6b47a11462 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 00000000000..df76de4e951 --- /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 b10f839dc11..87548622876 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 dba81c2b345..9b8140665e8 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 00000000000..fbd10588aba --- /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 e5334038a25..e4b13ed5215 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 ca80f5b0b20..00000000000 --- 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 aa038420e53..ce5ead462cc 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 0b9dd76aa13..3f481702902 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 661cb65c41dd5e3f078031d1bae2ca1f7c6d1a65 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Tue, 3 Jun 2025 00:17:18 +0000 Subject: [PATCH 11/21] 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 9baeb9c74e0..7b4a9763f96 100644 --- a/cmake/testing.cmake +++ b/cmake/testing.cmake @@ -122,7 +122,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 4d502aaad73..c73f9b8704a 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 7806096bc63..eba32c74ef6 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 87548622876..224f228e498 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 446b6139f92..729c6ee0778 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 00000000000..e1aad9d3913 --- /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 00000000000..b4c7078ac4e --- /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); +} From e2eec95f524c8a014397825103a2bcaf1e92ed04 Mon Sep 17 00:00:00 2001 From: Bulat Gayazov Date: Fri, 11 Jul 2025 17:03:45 +0000 Subject: [PATCH 12/21] Error handlig --- CMakePresets.json | 1 + odbc/CMakeLists.txt | 1 + odbc/src/connection.cpp | 54 +---- odbc/src/connection.h | 9 +- odbc/src/environment.cpp | 37 --- odbc/src/environment.h | 21 +- odbc/src/odbc_driver.cpp | 397 +++++++++++++------------------ odbc/src/statement.cpp | 92 ++----- odbc/src/statement.h | 29 +-- odbc/src/utils/error_manager.cpp | 120 ++++++++++ odbc/src/utils/error_manager.h | 97 ++++++++ 11 files changed, 431 insertions(+), 427 deletions(-) create mode 100644 odbc/src/utils/error_manager.cpp create mode 100644 odbc/src/utils/error_manager.h diff --git a/CMakePresets.json b/CMakePresets.json index ad610dd6dd4..92416e766e8 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -55,6 +55,7 @@ "cacheVariables": { "YDB_SDK_TESTS": "TRUE", "YDB_SDK_EXAMPLES": "TRUE", + "YDB_SDK_ODBC": "TRUE", "ARCADIA_ROOT": "..", "ARCADIA_BUILD_ROOT": "." } diff --git a/odbc/CMakeLists.txt b/odbc/CMakeLists.txt index f814f003138..799c9b89b19 100644 --- a/odbc/CMakeLists.txt +++ b/odbc/CMakeLists.txt @@ -3,6 +3,7 @@ add_library(ydb-odbc SHARED src/utils/types.cpp src/utils/util.cpp src/utils/convert.cpp + src/utils/error_manager.cpp src/odbc_driver.cpp src/connection.cpp src/statement.cpp diff --git a/odbc/src/connection.cpp b/odbc/src/connection.cpp index eba32c74ef6..7ed7679e015 100644 --- a/odbc/src/connection.cpp +++ b/odbc/src/connection.cpp @@ -1,5 +1,6 @@ #include "connection.h" #include "statement.h" +#include "utils/error_manager.h" #include #include @@ -35,8 +36,7 @@ SQLRETURN TConnection::DriverConnect(const std::string& connectionString) { Database_ = params["Database"]; if (Endpoint_.empty() || Database_.empty()) { - AddError("08001", 0, "Missing Endpoint or Database in connection string"); - return SQL_ERROR; + throw TOdbcException("08001", 0, "Missing Endpoint or Database in connection string"); } YdbDriver_ = std::make_unique(NYdb::TDriverConfig() @@ -64,8 +64,7 @@ SQLRETURN TConnection::Connect(const std::string& serverName, Database_ = database; if (Endpoint_.empty() || Database_.empty()) { - AddError("08001", 0, "Missing Endpoint or Database in DSN"); - return SQL_ERROR; + throw TOdbcException("08001", 0, "Missing Endpoint or Database in DSN"); } YdbDriver_ = std::make_unique(NYdb::TDriverConfig() @@ -85,30 +84,6 @@ SQLRETURN TConnection::Disconnect() { 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); } @@ -118,22 +93,11 @@ void TConnection::RemoveStatement(TStatement* stmt) { [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(); -} - 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; - } + NStatusHelpers::ThrowOnError(status); Tx_.reset(); } return SQL_SUCCESS; @@ -153,20 +117,14 @@ void TConnection::SetTx(const NQuery::TTransaction& tx) { SQLRETURN TConnection::CommitTx() { auto status = Tx_->Commit().ExtractValueSync(); - if (!status.IsSuccess()) { - AddError("08001", 0, "Failed to commit transaction"); - return SQL_ERROR; - } + NStatusHelpers::ThrowOnError(status); 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; - } + NStatusHelpers::ThrowOnError(status); Tx_.reset(); return SQL_SUCCESS; } diff --git a/odbc/src/connection.h b/odbc/src/connection.h index fad81527772..ad69b0f171c 100644 --- a/odbc/src/connection.h +++ b/odbc/src/connection.h @@ -1,6 +1,7 @@ #pragma once #include "environment.h" +#include "utils/error_manager.h" #include #include @@ -19,7 +20,7 @@ namespace NOdbc { class TStatement; -class TConnection { +class TConnection : public TErrorManager { private: std::unique_ptr YdbDriver_; std::unique_ptr YdbClient_; @@ -27,7 +28,6 @@ class TConnection { std::unique_ptr YdbSchemeClient_; std::optional Tx_; - TErrorList Errors_; std::vector> Statements_; std::string Endpoint_; std::string Database_; @@ -42,8 +42,6 @@ class TConnection { 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); @@ -52,9 +50,6 @@ class TConnection { 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(); - SQLRETURN SetAutocommit(bool value); bool GetAutocommit() const; diff --git a/odbc/src/environment.cpp b/odbc/src/environment.cpp index a09a634879b..541ca9e2160 100644 --- a/odbc/src/environment.cpp +++ b/odbc/src/environment.cpp @@ -13,42 +13,5 @@ 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; - } - - 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 diff --git a/odbc/src/environment.h b/odbc/src/environment.h index 0190b913831..5258b722492 100644 --- a/odbc/src/environment.h +++ b/odbc/src/environment.h @@ -1,39 +1,24 @@ #pragma once +#include "utils/error_manager.h" + #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 { +class TEnvironment : public TErrorManager { 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 diff --git a/odbc/src/odbc_driver.cpp b/odbc/src/odbc_driver.cpp index 3dec17d6aff..c047f770837 100644 --- a/odbc/src/odbc_driver.cpp +++ b/odbc/src/odbc_driver.cpp @@ -3,10 +3,21 @@ #include "statement.h" #include "utils/util.h" +#include "utils/error_manager.h" #include #include +namespace { + template + Handle* GetHandle(SQLHANDLE handle) { + if (!handle) { + throw NYdb::NOdbc::TOdbcException("HY000", 0, "Invalid handle", SQL_INVALID_HANDLE); + } + return static_cast(handle); + } +} + extern "C" { SQLRETURN SQL_API SQLAllocHandle(SQLSMALLINT handleType, @@ -15,78 +26,58 @@ SQLRETURN SQL_API SQLAllocHandle(SQLSMALLINT handleType, if (!outputHandle) { return SQL_INVALID_HANDLE; } - - try { - switch (handleType) { - case SQL_HANDLE_ENV: { - if (inputHandle != SQL_NULL_HANDLE) { - return SQL_INVALID_HANDLE; - } + switch (handleType) { + case SQL_HANDLE_ENV: { + return NYdb::NOdbc::HandleOdbcExceptions(inputHandle, [&]() { *outputHandle = new NYdb::NOdbc::TEnvironment(); return SQL_SUCCESS; - } - - case SQL_HANDLE_DBC: { - if (!inputHandle) { - return SQL_INVALID_HANDLE; - } + }); + } + case SQL_HANDLE_DBC: { + return NYdb::NOdbc::HandleOdbcExceptions(inputHandle, [&]() { *outputHandle = new NYdb::NOdbc::TConnection(); return SQL_SUCCESS; - } - - case SQL_HANDLE_STMT: { - auto conn = static_cast(inputHandle); - if (!conn) { - return SQL_INVALID_HANDLE; - } + }); + } + case SQL_HANDLE_STMT: { + return NYdb::NOdbc::HandleOdbcExceptions(inputHandle, [&](auto* conn) { auto stmt = conn->CreateStatement(); *outputHandle = stmt.release(); return SQL_SUCCESS; - } - - default: - return SQL_ERROR; + }); } - } catch (...) { - return SQL_ERROR; + default: + 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); + switch (handleType) { + case SQL_HANDLE_ENV: { + return NYdb::NOdbc::HandleOdbcExceptions(handle, [](auto* env) { delete env; return SQL_SUCCESS; - } - - case SQL_HANDLE_DBC: { - auto conn = static_cast(handle); + }); + } + case SQL_HANDLE_DBC: { + return NYdb::NOdbc::HandleOdbcExceptions(handle, [](auto* conn) { delete conn; return SQL_SUCCESS; - } - - case SQL_HANDLE_STMT: { - auto stmt = static_cast(handle); + }); + } + case SQL_HANDLE_STMT: { + return NYdb::NOdbc::HandleOdbcExceptions(handle, [](auto* stmt) { if (stmt->GetConnection()) { stmt->GetConnection()->RemoveStatement(stmt); } delete stmt; return SQL_SUCCESS; - } - - default: - return SQL_ERROR; + }); } - } catch (...) { - return SQL_ERROR; + default: + return SQL_ERROR; } } @@ -99,7 +90,9 @@ SQLRETURN SQL_API SQLSetEnvAttr(SQLHENV environmentHandle, return SQL_INVALID_HANDLE; } - return env->SetAttribute(attribute, value, stringLength); + return NYdb::NOdbc::HandleOdbcExceptions(env, [&]() { + return env->SetAttribute(attribute, value, stringLength); + }); } SQLRETURN SQL_API SQLDriverConnect(SQLHDBC connectionHandle, @@ -110,77 +103,58 @@ SQLRETURN SQL_API SQLDriverConnect(SQLHDBC connectionHandle, SQLSMALLINT /*bufferLength*/, SQLSMALLINT* /*stringLength2Ptr*/, SQLUSMALLINT /*driverCompletion*/) { - auto conn = static_cast(connectionHandle); - if (!conn) { - return SQL_INVALID_HANDLE; - } - - return conn->DriverConnect(NYdb::NOdbc::GetString(inConnectionString, stringLength1)); + return NYdb::NOdbc::HandleOdbcExceptions(connectionHandle, [&](auto* conn) { + return conn->DriverConnect(NYdb::NOdbc::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(NYdb::NOdbc::GetString(serverName, nameLength1), - NYdb::NOdbc::GetString(userName, nameLength2), - NYdb::NOdbc::GetString(authentication, nameLength3)); + return NYdb::NOdbc::HandleOdbcExceptions(connectionHandle, [&](auto* conn) { + return conn->Connect(NYdb::NOdbc::GetString(serverName, nameLength1), + NYdb::NOdbc::GetString(userName, nameLength2), + NYdb::NOdbc::GetString(authentication, nameLength3)); + }); } SQLRETURN SQL_API SQLDisconnect(SQLHDBC connectionHandle) { - auto conn = static_cast(connectionHandle); - if (!conn) { - return SQL_INVALID_HANDLE; - } - - return conn->Disconnect(); + return NYdb::NOdbc::HandleOdbcExceptions(connectionHandle, [&](auto* conn) { + return conn->Disconnect(); + }); } SQLRETURN SQL_API SQLExecDirect(SQLHSTMT statementHandle, SQLCHAR* statementText, SQLINTEGER textLength) { - auto stmt = static_cast(statementHandle); - if (!stmt) { - return SQL_INVALID_HANDLE; - } - - auto ret = stmt->Prepare(NYdb::NOdbc::GetString(statementText, textLength)); - if (ret != SQL_SUCCESS) { - return ret; - } - return stmt->Execute(); + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + 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)); + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + 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(); + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + return stmt->Execute(); + }); } SQLRETURN SQL_API SQLFetch(SQLHSTMT statementHandle) { - auto stmt = static_cast(statementHandle); - if (!stmt) { - return SQL_INVALID_HANDLE; - } - - return stmt->Fetch(); + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + return stmt->Fetch(); + }); } SQLRETURN SQL_API SQLGetData(SQLHSTMT statementHandle, @@ -189,12 +163,9 @@ SQLRETURN SQL_API SQLGetData(SQLHSTMT statementHandle, 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); + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + return stmt->GetData(columnNumber, targetType, targetValue, bufferLength, strLenOrInd); + }); } SQLRETURN SQL_API SQLBindCol(SQLHSTMT statementHandle, @@ -203,11 +174,9 @@ SQLRETURN SQL_API SQLBindCol(SQLHSTMT statementHandle, 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); + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + return stmt->BindCol(columnNumber, targetType, targetValue, bufferLength, strLenOrInd); + }); } SQLRETURN SQL_API SQLGetDiagRec(SQLSMALLINT handleType, @@ -218,32 +187,24 @@ SQLRETURN SQL_API SQLGetDiagRec(SQLSMALLINT handleType, SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength) { - if (!handle) { - return SQL_INVALID_HANDLE; - } - - try { - switch (handleType) { - case SQL_HANDLE_ENV: { - auto env = static_cast(handle); + switch (handleType) { + case SQL_HANDLE_ENV: { + return NYdb::NOdbc::HandleOdbcExceptions(handle, [&](auto* env) { return env->GetDiagRec(recNumber, sqlState, nativeError, messageText, bufferLength, textLength); - } - - case SQL_HANDLE_DBC: { - auto conn = static_cast(handle); + }); + } + case SQL_HANDLE_DBC: { + return NYdb::NOdbc::HandleOdbcExceptions(handle, [&](auto* conn) { return conn->GetDiagRec(recNumber, sqlState, nativeError, messageText, bufferLength, textLength); - } - - case SQL_HANDLE_STMT: { - auto stmt = static_cast(handle); + }); + } + case SQL_HANDLE_STMT: { + return NYdb::NOdbc::HandleOdbcExceptions(handle, [&](auto* stmt) { return stmt->GetDiagRec(recNumber, sqlState, nativeError, messageText, bufferLength, textLength); - } - - default: - return SQL_ERROR; + }); } - } catch (...) { - return SQL_ERROR; + default: + return SQL_ERROR; } } @@ -257,32 +218,26 @@ SQLRETURN SQL_API SQLBindParameter(SQLHSTMT statementHandle, 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); + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + 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); + switch (handleType) { + case SQL_HANDLE_DBC: { + return NYdb::NOdbc::HandleOdbcExceptions(handle, [&](auto* conn) { if (completionType == SQL_COMMIT) { return conn->CommitTx(); } else if (completionType == SQL_ROLLBACK) { return conn->RollbackTx(); } else { - return SQL_ERROR; + throw NYdb::NOdbc::TOdbcException("HY000", 0, "Invalid completion type"); } - } - case SQL_HANDLE_STMT: { - auto stmt = static_cast(handle); + }); + } + case SQL_HANDLE_STMT: { + return NYdb::NOdbc::HandleOdbcExceptions(handle, [&](auto* stmt) -> SQLRETURN { auto conn = stmt->GetConnection(); if (!conn) return SQL_INVALID_HANDLE; if (completionType == SQL_COMMIT) { @@ -290,37 +245,33 @@ SQLRETURN SQL_API SQLEndTran(SQLSMALLINT handleType, SQLHANDLE handle, SQLSMALLI } else if (completionType == SQL_ROLLBACK) { return conn->RollbackTx(); } else { - return SQL_ERROR; + throw NYdb::NOdbc::TOdbcException("HY000", 0, "Invalid completion type"); } - } - 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; + }); + } + case SQL_HANDLE_ENV: { + // TODO: if's list of connections in ENV, go through them and commit/rollback transactions + return SQL_SUCCESS; } - } catch (...) { - return SQL_ERROR; + default: + 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; + return NYdb::NOdbc::HandleOdbcExceptions(connectionHandle, [&](auto* conn) { + 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 { + throw NYdb::NOdbc::TOdbcException("HY000", 0, "Invalid autocommit value"); + } } - } - // TODO: other attributes - return SQL_ERROR; + // TODO: other attributes + throw NYdb::NOdbc::TOdbcException("HYC00", 0, "Optional feature not implemented"); + }); } SQLRETURN SQL_API SQLColumns(SQLHSTMT statementHandle, @@ -328,15 +279,13 @@ SQLRETURN SQL_API SQLColumns(SQLHSTMT statementHandle, 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)); + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + 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, @@ -344,74 +293,60 @@ SQLRETURN SQL_API SQLTables(SQLHSTMT statementHandle, 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)); + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + return stmt->Tables( + NYdb::NOdbc::GetString(catalogName, nameLength1), + NYdb::NOdbc::GetString(schemaName, nameLength2), + NYdb::NOdbc::GetString(tableName, nameLength3), + 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); + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + 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; - } + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) -> SQLRETURN { + 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: + throw NYdb::NOdbc::TOdbcException("HY000", 0, "Invalid option"); + } + }); } 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; - } + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + if (fetchOrientation == SQL_FETCH_NEXT) { + return stmt->Fetch(); + } else { + throw NYdb::NOdbc::TOdbcException("HYC00", 0, "Only SQL_FETCH_NEXT is supported"); + } + }); } SQLRETURN SQL_API SQLRowCount(SQLHSTMT statementHandle, SQLLEN* rowCount) { - auto stmt = static_cast(statementHandle); - if (!stmt) { - return SQL_INVALID_HANDLE; - } - return stmt->RowCount(rowCount); + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + 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); + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + return stmt->NumResultCols(colCount); + }); } } diff --git a/odbc/src/statement.cpp b/odbc/src/statement.cpp index 2bf4c78fd13..b61b8f07eb2 100644 --- a/odbc/src/statement.cpp +++ b/odbc/src/statement.cpp @@ -2,6 +2,7 @@ #include "utils/convert.h" #include "utils/types.h" +#include "utils/error_manager.h" #include #include @@ -21,36 +22,30 @@ SQLRETURN TStatement::Prepare(const std::string& statementText) { SQLRETURN TStatement::Execute() { if (!IsPrepared_ || PreparedQuery_.empty()) { - AddError("HY007", 0, "No prepared statement"); - return SQL_ERROR; + throw TOdbcException("HY007", 0, "No prepared statement"); } Cursor_.reset(); auto* client = Conn_->GetClient(); if (!client) { - return SQL_ERROR; + throw TOdbcException("HY000", 0, "No client connection"); } NYdb::TParams params = BuildParams(); - if (!Errors_.empty()) { - return SQL_ERROR; - } + if (!Conn_->GetTx()) { auto sessionResult = client->GetSession().ExtractValueSync(); - if (!sessionResult.IsSuccess()) { - return SQL_ERROR; - } + NStatusHelpers::ThrowOnError(sessionResult); + auto session = sessionResult.GetSession(); auto beginTxResult = session.BeginTransaction(NQuery::TTxSettings::SerializableRW()).ExtractValueSync(); - if (!beginTxResult.IsSuccess()) { - return SQL_ERROR; - } + NStatusHelpers::ThrowOnError(beginTxResult); + Conn_->SetTx(beginTxResult.GetTransaction()); } auto session = Conn_->GetTx()->GetSession(); auto iterator = session.StreamExecuteQuery(PreparedQuery_, NQuery::TTxControl::Tx(*Conn_->GetTx()).CommitTx(Conn_->GetAutocommit()), params).ExtractValueSync(); - if (!iterator.IsSuccess()) { - return SQL_ERROR; - } + NStatusHelpers::ThrowOnError(iterator); + Cursor_ = CreateExecCursor(this, std::move(iterator)); IsPrepared_ = false; PreparedQuery_.clear(); @@ -82,31 +77,6 @@ void TStatement::FillBoundColumns() { } } -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) { if (!Cursor_) { return SQL_NO_DATA; @@ -133,8 +103,7 @@ SQLRETURN TStatement::BindParameter(SQLUSMALLINT paramNumber, SQLLEN* strLenOrIndPtr) { if (inputOutputType != SQL_PARAM_INPUT) { - AddError("HYC00", 0, "Only input parameters are supported"); - return SQL_ERROR; + throw TOdbcException("HYC00", 0, "Only input parameters are supported"); } BoundParams_.erase(std::remove_if(BoundParams_.begin(), BoundParams_.end(), @@ -147,12 +116,8 @@ SQLRETURN TStatement::BindParameter(SQLUSMALLINT paramNumber, return SQL_SUCCESS; } -void TStatement::AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message) { - Errors_.push_back({sqlState, nativeError, message}); -} - NYdb::TParams TStatement::BuildParams() { - Errors_.clear(); + ClearErrors(); NYdb::TParamsBuilder paramsBuilder; for (const auto& param : BoundParams_) { std::string paramName = "$p" + std::to_string(param.ParamNumber); @@ -166,7 +131,7 @@ SQLRETURN TStatement::Columns(const std::string& catalogName, const std::string& schemaName, const std::string& tableName, const std::string& columnName) { - Errors_.clear(); + ClearErrors(); Cursor_.reset(); std::vector columns = { @@ -192,8 +157,7 @@ SQLRETURN TStatement::Columns(const std::string& catalogName, auto entries = GetPatternEntries(tableName); if (entries.empty()) { - AddError("HYC00", 0, "No tables found"); - return SQL_ERROR; + throw TOdbcException("HYC00", 0, "No tables found"); } TTable table; @@ -207,9 +171,8 @@ SQLRETURN TStatement::Columns(const std::string& catalogName, 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; - } + NStatusHelpers::ThrowOnError(result); + auto columns = result.GetTableDescription().GetTableColumns(); auto columnIt = std::find_if(columns.begin(), columns.end(), [&columnName](const NTable::TTableColumn& column) { @@ -247,9 +210,7 @@ SQLRETURN TStatement::Columns(const std::string& catalogName, return TStatus(EStatus::SUCCESS, {}); }); - if (!status.IsSuccess()) { - return SQL_ERROR; - } + NStatusHelpers::ThrowOnError(status); } Cursor_ = CreateVirtualCursor(this, columns, table); @@ -260,7 +221,7 @@ SQLRETURN TStatement::Tables(const std::string& catalogName, const std::string& schemaName, const std::string& tableName, const std::string& tableType) { - Errors_.clear(); + ClearErrors(); Cursor_.reset(); std::vector columns = { @@ -273,8 +234,7 @@ SQLRETURN TStatement::Tables(const std::string& catalogName, auto entries = GetPatternEntries(tableName); if (entries.empty()) { - AddError("HYC00", 0, "No tables found"); - return SQL_ERROR; + throw TOdbcException("HYC00", 0, "No tables found"); } TTable table; @@ -308,9 +268,8 @@ std::vector TStatement::GetPatternEntries(const std::stri 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; - } + NStatusHelpers::ThrowOnError(listDirectoryResult); + for (const auto& entry : listDirectoryResult.GetChildren()) { std::string fullPath = path + "/" + entry.Name; if (entry.Type == NScheme::ESchemeEntryType::Directory || @@ -369,14 +328,13 @@ std::optional TStatement::GetTableType(NScheme::ESchemeEntryType ty SQLRETURN TStatement::Close(bool force) { if (!force && !Cursor_) { - AddError("24000", 0, "Invalid handle"); - return SQL_ERROR; + throw TOdbcException("24000", 0, "Invalid handle"); } Cursor_.reset(); PreparedQuery_.clear(); IsPrepared_ = false; - Errors_.clear(); + ClearErrors(); return SQL_SUCCESS; } @@ -390,7 +348,7 @@ void TStatement::ResetParams() { SQLRETURN TStatement::RowCount(SQLLEN* rowCount) { if (!rowCount) { - return SQL_ERROR; + throw TOdbcException("HY000", 0, "Invalid parameter"); } *rowCount = -1; @@ -399,7 +357,7 @@ SQLRETURN TStatement::RowCount(SQLLEN* rowCount) { SQLRETURN TStatement::NumResultCols(SQLSMALLINT* colCount) { if (!colCount) { - return SQL_ERROR; + throw TOdbcException("HY000", 0, "Invalid parameter"); } if (!Cursor_) { *colCount = 0; diff --git a/odbc/src/statement.h b/odbc/src/statement.h index d6b47a11462..8bed3534986 100644 --- a/odbc/src/statement.h +++ b/odbc/src/statement.h @@ -1,7 +1,7 @@ #pragma once #include "connection.h" - +#include "utils/error_manager.h" #include "utils/bindings.h" #include "utils/cursor.h" @@ -18,7 +18,7 @@ namespace NYdb { namespace NOdbc { -class TStatement : public IBindingFiller { +class TStatement : public TErrorManager, public IBindingFiller { public: TStatement(TConnection* conn); @@ -35,9 +35,6 @@ class TStatement : public IBindingFiller { void UnbindColumns(); void ResetParams(); - 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); @@ -58,27 +55,21 @@ class TStatement : public IBindingFiller { return Conn_; } - void AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message); - - NYdb::TParams BuildParams(); - 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 Cursor_; + std::string PreparedQuery_; + bool IsPrepared_ = false; std::vector BoundColumns_; std::vector BoundParams_; - std::string PreparedQuery_; - bool IsPrepared_ = false; + NYdb::TParams BuildParams(); + + 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); }; } // namespace NOdbc diff --git a/odbc/src/utils/error_manager.cpp b/odbc/src/utils/error_manager.cpp new file mode 100644 index 00000000000..da9f339af89 --- /dev/null +++ b/odbc/src/utils/error_manager.cpp @@ -0,0 +1,120 @@ +#include "error_manager.h" + +#include +#include + +namespace NYdb { +namespace NOdbc { +namespace { + struct OdbcErrorMapping { + const char* sqlState; + const char* description; + SQLRETURN returnCode; + }; + + const std::unordered_map ERROR_MAPPINGS = { + {EStatus::SUCCESS, {"00000", "Success", SQL_SUCCESS}}, + {EStatus::BAD_REQUEST, {"42000", "Syntax error or access rule violation", SQL_ERROR}}, + {EStatus::UNAUTHORIZED, {"28000", "Invalid authorization specification", SQL_ERROR}}, + {EStatus::INTERNAL_ERROR, {"HY000", "General error", SQL_ERROR}}, + {EStatus::ABORTED, {"25000", "Invalid transaction state", SQL_ERROR}}, + {EStatus::UNAVAILABLE, {"08001", "Client unable to establish connection", SQL_ERROR}}, + {EStatus::OVERLOADED, {"HY000", "General error - server overloaded", SQL_ERROR}}, + {EStatus::SCHEME_ERROR, {"42S02", "Base table or view not found", SQL_ERROR}}, + {EStatus::GENERIC_ERROR, {"HY000", "General error", SQL_ERROR}}, + {EStatus::TIMEOUT, {"HYT00", "Timeout expired", SQL_ERROR}}, + {EStatus::BAD_SESSION, {"08003", "Connection does not exist", SQL_ERROR}}, + {EStatus::PRECONDITION_FAILED, {"23000", "Integrity constraint violation", SQL_ERROR}}, + {EStatus::ALREADY_EXISTS, {"23000", "Integrity constraint violation", SQL_ERROR}}, + {EStatus::NOT_FOUND, {"02000", "No data found", SQL_NO_DATA}}, + {EStatus::SESSION_EXPIRED, {"08003", "Connection does not exist", SQL_ERROR}}, + {EStatus::CANCELLED, {"HY008", "Operation canceled", SQL_ERROR}}, + {EStatus::UNDETERMINED, {"HY000", "General error", SQL_ERROR}}, + {EStatus::UNSUPPORTED, {"HYC00", "Optional feature not implemented", SQL_ERROR}}, + {EStatus::SESSION_BUSY, {"HY000", "General error - session busy", SQL_ERROR}}, + // Transport errors + {EStatus::TRANSPORT_UNAVAILABLE, {"08001", "Client unable to establish connection", SQL_ERROR}}, + {EStatus::CLIENT_RESOURCE_EXHAUSTED, {"HY000", "General error - resource exhausted", SQL_ERROR}}, + {EStatus::CLIENT_DEADLINE_EXCEEDED, {"HYT00", "Timeout expired", SQL_ERROR}}, + {EStatus::CLIENT_INTERNAL_ERROR, {"HY000", "General error", SQL_ERROR}}, + {EStatus::CLIENT_CANCELLED, {"HY008", "Operation canceled", SQL_ERROR}}, + {EStatus::CLIENT_UNAUTHENTICATED, {"28000", "Invalid authorization specification", SQL_ERROR}}, + {EStatus::CLIENT_LIMITS_REACHED, {"HY000", "General error - limits reached", SQL_ERROR}}, + {EStatus::CLIENT_DISCOVERY_FAILED, {"08001", "Client unable to establish connection", SQL_ERROR}}, + {EStatus::CLIENT_CALL_UNIMPLEMENTED, {"HYC00", "Optional feature not implemented", SQL_ERROR}}, + {EStatus::CLIENT_OUT_OF_RANGE, {"22003", "Numeric value out of range", SQL_ERROR}}, + }; + + const OdbcErrorMapping DEFAULT_ERROR_MAPPING = {"HY000", "Unknown YDB error", SQL_ERROR}; + + OdbcErrorMapping GetErrorMappingForStatus(EStatus status) { + auto it = ERROR_MAPPINGS.find(status); + if (it != ERROR_MAPPINGS.end()) { + return it->second; + } + return DEFAULT_ERROR_MAPPING; + } +} // namespace + +SQLRETURN TErrorManager::AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message, SQLRETURN returnCode) { + Errors_.push_back({sqlState, nativeError, message, returnCode}); + return returnCode; +} + +SQLRETURN TErrorManager::AddError(const TOdbcException& ex) { + Errors_.push_back({ex.GetSqlState(), ex.GetNativeError(), ex.GetMessage(), ex.GetReturnCode()}); + return ex.GetReturnCode(); +} + +SQLRETURN TErrorManager::AddError(const TStatus& status) { + auto mapping = GetErrorMappingForStatus(status.GetStatus()); + std::string message = mapping.description; + if (!status.GetIssues().Empty()) { + message += ": " + status.GetIssues().ToString(); + } + Errors_.push_back({mapping.sqlState, static_cast(status.GetStatus()), message, mapping.returnCode}); + return mapping.returnCode; +} + +void TErrorManager::ClearErrors() { + Errors_.clear(); +} + +SQLRETURN TErrorManager::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 HandleOdbcExceptions(SQLHANDLE handlePtr, std::function&& func) { + if (!handlePtr) { + return SQL_INVALID_HANDLE; + } + + try { + return func(); + } catch (...) { + return SQL_ERROR; + } +} + +} // namespace NOdbc +} // namespace NYdb \ No newline at end of file diff --git a/odbc/src/utils/error_manager.h b/odbc/src/utils/error_manager.h new file mode 100644 index 00000000000..1e31349964d --- /dev/null +++ b/odbc/src/utils/error_manager.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace NYdb { +namespace NOdbc { + +struct TErrorInfo { + std::string SqlState; + SQLINTEGER NativeError; + std::string Message; + SQLRETURN ReturnCode; +}; + +using TErrorList = std::vector; + +class TOdbcException : public std::exception { +public: + TOdbcException(const std::string& sqlState, SQLINTEGER nativeError, + const std::string& message, SQLRETURN returnCode = SQL_ERROR) + : SqlState_(sqlState) + , NativeError_(nativeError) + , Message_(message) + , ReturnCode_(returnCode) + {} + + const std::string& GetSqlState() const { + return SqlState_; + } + + SQLINTEGER GetNativeError() const { + return NativeError_; + } + + const std::string& GetMessage() const { + return Message_; + } + + SQLRETURN GetReturnCode() const { + return ReturnCode_; + } + + const char* what() const noexcept override { + return Message_.c_str(); + } + +private: + std::string SqlState_; + SQLINTEGER NativeError_; + std::string Message_; + SQLRETURN ReturnCode_; +}; + +class TErrorManager { +public: + SQLRETURN AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message, SQLRETURN returnCode = SQL_ERROR); + SQLRETURN AddError(const TOdbcException& ex); + SQLRETURN AddError(const TStatus& status); + + void ClearErrors(); + + SQLRETURN GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, + SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength); + +private: + TErrorList Errors_; +}; + +template +SQLRETURN HandleOdbcExceptions(SQLHANDLE handlePtr, std::function&& func) { + if (!handlePtr) { + return SQL_INVALID_HANDLE; + } + auto handle = static_cast(handlePtr); + + try { + return func(handle); + } catch (const NStatusHelpers::TYdbErrorException& ex) { + return handle->AddError(ex.GetStatus()); + } catch (const TOdbcException& ex) { + return handle->AddError(ex); + } catch (const std::exception& ex) { + return handle->AddError("HY000", 0, ex.what()); + } catch (...) { + return handle->AddError("HY000", 0, "Unknown error"); + } +} + +SQLRETURN HandleOdbcExceptions(SQLHANDLE handlePtr, std::function&& func); + +} // namespace NOdbc +} // namespace NYdb From c37dccd22b26ca4d947fa0c904585e4592a582e9 Mon Sep 17 00:00:00 2001 From: Ylonies Date: Tue, 7 Apr 2026 14:12:28 +0000 Subject: [PATCH 13/21] some fixes --- odbc/CMakeLists.txt | 3 +- odbc/src/connection.cpp | 17 ++++++++ odbc/src/connection.h | 4 ++ odbc/src/odbc_driver.cpp | 39 +++++++++++++++-- odbc/src/statement.cpp | 75 ++++++++++++++++++++++++++------ odbc/src/statement.h | 5 +++ odbc/src/utils/bindings.h | 5 +++ odbc/src/utils/cursor.cpp | 22 +++++++--- odbc/src/utils/cursor.h | 5 ++- odbc/src/utils/error_manager.cpp | 54 ++++++++++++++++++++++- odbc/src/utils/error_manager.h | 12 ++++- 11 files changed, 212 insertions(+), 29 deletions(-) diff --git a/odbc/CMakeLists.txt b/odbc/CMakeLists.txt index 799c9b89b19..9919870702d 100644 --- a/odbc/CMakeLists.txt +++ b/odbc/CMakeLists.txt @@ -23,7 +23,6 @@ target_link_libraries(ydb-odbc YDB-CPP-SDK::Table YDB-CPP-SDK::Scheme YDB-CPP-SDK::Driver - ODBC::ODBC ) set_target_properties(ydb-odbc PROPERTIES @@ -43,7 +42,7 @@ add_subdirectory(tests) include(GNUInstallDirs) -install(FILES +install(FILES odbcinst.ini DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/odbcinst.d RENAME ydb-odbc.ini diff --git a/odbc/src/connection.cpp b/odbc/src/connection.cpp index 7ed7679e015..eb142108334 100644 --- a/odbc/src/connection.cpp +++ b/odbc/src/connection.cpp @@ -79,11 +79,24 @@ SQLRETURN TConnection::Connect(const std::string& serverName, } SQLRETURN TConnection::Disconnect() { + QuerySession_.reset(); + Tx_.reset(); + YdbSchemeClient_.reset(); + YdbTableClient_.reset(); YdbClient_.reset(); YdbDriver_.reset(); return SQL_SUCCESS; } +NQuery::TSession& TConnection::GetOrCreateQuerySession() { + if (!QuerySession_) { + auto sessionResult = YdbClient_->GetSession().ExtractValueSync(); + NStatusHelpers::ThrowOnError(sessionResult); + QuerySession_.emplace(std::move(sessionResult.GetSession())); + } + return *QuerySession_; +} + std::unique_ptr TConnection::CreateStatement() { return std::make_unique(this); } @@ -115,6 +128,10 @@ void TConnection::SetTx(const NQuery::TTransaction& tx) { Tx_ = tx; } +void TConnection::Reset() { + Tx_.reset(); +} + SQLRETURN TConnection::CommitTx() { auto status = Tx_->Commit().ExtractValueSync(); NStatusHelpers::ThrowOnError(status); diff --git a/odbc/src/connection.h b/odbc/src/connection.h index ad69b0f171c..a0ce4acb991 100644 --- a/odbc/src/connection.h +++ b/odbc/src/connection.h @@ -27,6 +27,8 @@ class TConnection : public TErrorManager { std::unique_ptr YdbTableClient_; std::unique_ptr YdbSchemeClient_; std::optional Tx_; + /** Одна сессия KQP на ODBC-соединение: DDL/DML/SELECT видят одну и ту же схему без «новой» сессии на каждый Execute. */ + std::optional QuerySession_; std::vector> Statements_; std::string Endpoint_; @@ -47,6 +49,7 @@ class TConnection : public TErrorManager { void RemoveStatement(TStatement* stmt); NYdb::NQuery::TQueryClient* GetClient() { return YdbClient_.get(); } + NQuery::TSession& GetOrCreateQuerySession(); NYdb::NTable::TTableClient* GetTableClient() { return YdbTableClient_.get(); } NScheme::TSchemeClient* GetSchemeClient() { return YdbSchemeClient_.get(); } @@ -55,6 +58,7 @@ class TConnection : public TErrorManager { const std::optional& GetTx(); void SetTx(const NQuery::TTransaction& tx); + void Reset(); SQLRETURN CommitTx(); SQLRETURN RollbackTx(); diff --git a/odbc/src/odbc_driver.cpp b/odbc/src/odbc_driver.cpp index c047f770837..f26bd55c828 100644 --- a/odbc/src/odbc_driver.cpp +++ b/odbc/src/odbc_driver.cpp @@ -29,10 +29,13 @@ SQLRETURN SQL_API SQLAllocHandle(SQLSMALLINT handleType, switch (handleType) { case SQL_HANDLE_ENV: { - return NYdb::NOdbc::HandleOdbcExceptions(inputHandle, [&]() { - *outputHandle = new NYdb::NOdbc::TEnvironment(); - return SQL_SUCCESS; - }); + return NYdb::NOdbc::HandleOdbcExceptions( + inputHandle, + [&]() { + *outputHandle = new NYdb::NOdbc::TEnvironment(); + return SQL_SUCCESS; + }, + NYdb::NOdbc::ENullInputHandlePolicy::Allow); } case SQL_HANDLE_DBC: { @@ -208,6 +211,34 @@ SQLRETURN SQL_API SQLGetDiagRec(SQLSMALLINT handleType, } } +SQLRETURN SQL_API SQLGetDiagField(SQLSMALLINT handleType, + SQLHANDLE handle, + SQLSMALLINT recNumber, + SQLSMALLINT diagIdentifier, + SQLPOINTER diagInfoPtr, + SQLSMALLINT bufferLength, + SQLSMALLINT* stringLengthPtr) { + switch (handleType) { + case SQL_HANDLE_ENV: { + return NYdb::NOdbc::HandleOdbcExceptions(handle, [&](auto* env) { + return env->GetDiagField(recNumber, diagIdentifier, diagInfoPtr, bufferLength, stringLengthPtr); + }); + } + case SQL_HANDLE_DBC: { + return NYdb::NOdbc::HandleOdbcExceptions(handle, [&](auto* conn) { + return conn->GetDiagField(recNumber, diagIdentifier, diagInfoPtr, bufferLength, stringLengthPtr); + }); + } + case SQL_HANDLE_STMT: { + return NYdb::NOdbc::HandleOdbcExceptions(handle, [&](auto* stmt) { + return stmt->GetDiagField(recNumber, diagIdentifier, diagInfoPtr, bufferLength, stringLengthPtr); + }); + } + default: + return SQL_ERROR; + } +} + SQLRETURN SQL_API SQLBindParameter(SQLHSTMT statementHandle, SQLUSMALLINT paramNumber, SQLSMALLINT inputOutputType, diff --git a/odbc/src/statement.cpp b/odbc/src/statement.cpp index b61b8f07eb2..b7fddc624bf 100644 --- a/odbc/src/statement.cpp +++ b/odbc/src/statement.cpp @@ -14,6 +14,7 @@ TStatement::TStatement(TConnection* conn) : Conn_(conn) {} SQLRETURN TStatement::Prepare(const std::string& statementText) { + StreamFetchError_ = false; Cursor_.reset(); PreparedQuery_ = statementText; IsPrepared_ = true; @@ -24,40 +25,86 @@ SQLRETURN TStatement::Execute() { if (!IsPrepared_ || PreparedQuery_.empty()) { throw TOdbcException("HY007", 0, "No prepared statement"); } + StreamFetchError_ = false; Cursor_.reset(); auto* client = Conn_->GetClient(); if (!client) { throw TOdbcException("HY000", 0, "No client connection"); } NYdb::TParams params = BuildParams(); - - if (!Conn_->GetTx()) { - auto sessionResult = client->GetSession().ExtractValueSync(); - NStatusHelpers::ThrowOnError(sessionResult); - - auto session = sessionResult.GetSession(); - auto beginTxResult = session.BeginTransaction(NQuery::TTxSettings::SerializableRW()).ExtractValueSync(); - NStatusHelpers::ThrowOnError(beginTxResult); - Conn_->SetTx(beginTxResult.GetTransaction()); + if (Conn_->GetAutocommit()){ + Conn_->Reset(); } - auto session = Conn_->GetTx()->GetSession(); - auto iterator = session.StreamExecuteQuery(PreparedQuery_, - NQuery::TTxControl::Tx(*Conn_->GetTx()).CommitTx(Conn_->GetAutocommit()), params).ExtractValueSync(); + + auto& session = Conn_->GetOrCreateQuerySession(); + + auto iterator = CreateExecuteIterator(session, params); NStatusHelpers::ThrowOnError(iterator); - Cursor_ = CreateExecCursor(this, std::move(iterator)); + std::optional prefetchedResultPart = PrefetchFirstResultPart(iterator); + if (prefetchedResultPart) { + Cursor_ = CreateExecCursor(this, std::move(iterator), std::move(prefetchedResultPart)); + } else { + Cursor_.reset(); + } IsPrepared_ = false; PreparedQuery_.clear(); return SQL_SUCCESS; } +NQuery::TExecuteQueryIterator TStatement::CreateExecuteIterator(NQuery::TSession& session, const NYdb::TParams& params){ + if (Conn_->GetAutocommit()) { + return session.StreamExecuteQuery( + PreparedQuery_, + NQuery::TTxControl::NoTx(), + params).ExtractValueSync(); + } + if (!Conn_->GetTx()) { + auto beginTxResult = session.BeginTransaction(NQuery::TTxSettings::SerializableRW()).ExtractValueSync(); + NStatusHelpers::ThrowOnError(beginTxResult); + Conn_->SetTx(beginTxResult.GetTransaction()); + } + return session.StreamExecuteQuery( + PreparedQuery_, + NQuery::TTxControl::Tx(*Conn_->GetTx()).CommitTx(false), + params).ExtractValueSync(); +} + +std::optional TStatement::PrefetchFirstResultPart(NQuery::TExecuteQueryIterator& iterator){ + std::optional prefetchedResultPart; + while (true) { + auto part = iterator.ReadNext().ExtractValueSync(); + if (part.EOS()) { + break; + } + if (!part.IsSuccess()) { + NStatusHelpers::ThrowOnError(part); + } + if (part.HasResultSet()) { + prefetchedResultPart.emplace(std::move(part)); + break; + } + } + return prefetchedResultPart; +} + SQLRETURN TStatement::Fetch() { if (!Cursor_) { Cursor_.reset(); return SQL_NO_DATA; } - return Cursor_->Fetch() ? SQL_SUCCESS : SQL_NO_DATA; + StreamFetchError_ = false; + if (!Cursor_->Fetch()) { + return StreamFetchError_ ? SQL_ERROR : SQL_NO_DATA; + } + return SQL_SUCCESS; +} + +void TStatement::OnStreamPartError(const TStatus& status) { + ClearErrors(); + AddError(status); + StreamFetchError_ = true; } SQLRETURN TStatement::GetData(SQLUSMALLINT columnNumber, SQLSMALLINT targetType, diff --git a/odbc/src/statement.h b/odbc/src/statement.h index 8bed3534986..f17780957bb 100644 --- a/odbc/src/statement.h +++ b/odbc/src/statement.h @@ -30,6 +30,7 @@ class TStatement : public TErrorManager, public IBindingFiller { SQLPOINTER targetValue, SQLLEN bufferLength, SQLLEN* strLenOrInd); void FillBoundColumns() override; + void OnStreamPartError(const TStatus& status) override; SQLRETURN Close(bool force = false); void UnbindColumns(); @@ -63,9 +64,13 @@ class TStatement : public TErrorManager, public IBindingFiller { std::vector BoundColumns_; std::vector BoundParams_; + bool StreamFetchError_ = false; NYdb::TParams BuildParams(); + NQuery::TExecuteQueryIterator CreateExecuteIterator(NQuery::TSession& session, const NYdb::TParams& params); + std::optional PrefetchFirstResultPart(NQuery::TExecuteQueryIterator& iterator); + 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); diff --git a/odbc/src/utils/bindings.h b/odbc/src/utils/bindings.h index df76de4e951..443d9787d70 100644 --- a/odbc/src/utils/bindings.h +++ b/odbc/src/utils/bindings.h @@ -3,6 +3,8 @@ #include #include +#include + namespace NYdb { namespace NOdbc { @@ -29,6 +31,9 @@ struct TBoundColumn { class IBindingFiller { public: virtual void FillBoundColumns() = 0; + virtual void OnStreamPartError(const TStatus& status) { + (void)status; + } virtual ~IBindingFiller() = default; }; diff --git a/odbc/src/utils/cursor.cpp b/odbc/src/utils/cursor.cpp index fbd10588aba..efbcea9a419 100644 --- a/odbc/src/utils/cursor.cpp +++ b/odbc/src/utils/cursor.cpp @@ -8,9 +8,11 @@ namespace NOdbc { class TExecCursor : public ICursor { public: - TExecCursor(IBindingFiller* bindingFiller, NQuery::TExecuteQueryIterator iterator) + TExecCursor(IBindingFiller* bindingFiller, NQuery::TExecuteQueryIterator iterator, + std::optional prefetchedPart) : BindingFiller_(bindingFiller) , Iterator_(std::move(iterator)) + , PrefetchedPart_(std::move(prefetchedPart)) {} bool Fetch() override { @@ -22,11 +24,19 @@ class TExecCursor : public ICursor { } ResultSetParser_.reset(); } - auto part = Iterator_.ReadNext().ExtractValueSync(); + NQuery::TExecuteQueryPart part = [&]() { + if (PrefetchedPart_) { + auto p = std::move(*PrefetchedPart_); + PrefetchedPart_.reset(); + return p; + } + return Iterator_.ReadNext().ExtractValueSync(); + }(); if (part.EOS()) { return false; } if (!part.IsSuccess()) { + BindingFiller_->OnStreamPartError(part); return false; } if (part.HasResultSet()) { @@ -62,7 +72,7 @@ class TExecCursor : public ICursor { IBindingFiller* BindingFiller_; NQuery::TExecuteQueryIterator Iterator_; - // std::optional Part_; + std::optional PrefetchedPart_; std::unique_ptr ResultSetParser_; std::vector Columns_; }; @@ -107,8 +117,10 @@ class TVirtualCursor : public ICursor { int64_t Cursor_ = -1; }; -std::unique_ptr CreateExecCursor(IBindingFiller* bindingFiller, NQuery::TExecuteQueryIterator iterator) { - return std::make_unique(bindingFiller, std::move(iterator)); +std::unique_ptr CreateExecCursor(IBindingFiller* bindingFiller, + NQuery::TExecuteQueryIterator iterator, + std::optional prefetchedPart) { + return std::make_unique(bindingFiller, std::move(iterator), std::move(prefetchedPart)); } std::unique_ptr CreateVirtualCursor(IBindingFiller* bindingFiller, const std::vector& columns, const TTable& table) { diff --git a/odbc/src/utils/cursor.h b/odbc/src/utils/cursor.h index e4b13ed5215..22828f66144 100644 --- a/odbc/src/utils/cursor.h +++ b/odbc/src/utils/cursor.h @@ -6,6 +6,7 @@ #include +#include #include #include @@ -30,7 +31,9 @@ class ICursor { virtual const std::vector& GetColumnMeta() const = 0; }; -std::unique_ptr CreateExecCursor(IBindingFiller* bindingFiller, NYdb::NQuery::TExecuteQueryIterator iterator); +std::unique_ptr CreateExecCursor(IBindingFiller* bindingFiller, + NYdb::NQuery::TExecuteQueryIterator iterator, + std::optional prefetchedPart = std::nullopt); std::unique_ptr CreateVirtualCursor(IBindingFiller* bindingFiller, const std::vector& columns, const TTable& table); } // namespace NOdbc diff --git a/odbc/src/utils/error_manager.cpp b/odbc/src/utils/error_manager.cpp index da9f339af89..fbb577e3824 100644 --- a/odbc/src/utils/error_manager.cpp +++ b/odbc/src/utils/error_manager.cpp @@ -104,8 +104,58 @@ SQLRETURN TErrorManager::GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQ return SQL_SUCCESS; } -SQLRETURN HandleOdbcExceptions(SQLHANDLE handlePtr, std::function&& func) { - if (!handlePtr) { +SQLRETURN TErrorManager::GetDiagField(SQLSMALLINT recNumber, SQLSMALLINT diagIdentifier, + SQLPOINTER diagInfoPtr, SQLSMALLINT bufferLength, SQLSMALLINT* stringLengthPtr) { + const SQLSMALLINT count = static_cast(Errors_.size()); + + if (recNumber == 0) { + if (diagIdentifier == SQL_DIAG_NUMBER) { + if (!diagInfoPtr) { + return SQL_ERROR; + } + *static_cast(diagInfoPtr) = count; + return SQL_SUCCESS; + } + return SQL_NO_DATA; + } + + if (recNumber < 1 || recNumber > count) { + return SQL_NO_DATA; + } + + const auto& err = Errors_[recNumber - 1]; + switch (diagIdentifier) { + case SQL_DIAG_SQLSTATE: + if (!diagInfoPtr) { + return SQL_ERROR; + } + strncpy((char*)diagInfoPtr, err.SqlState.c_str(), 6); + return SQL_SUCCESS; + case SQL_DIAG_NATIVE: + if (!diagInfoPtr) { + return SQL_ERROR; + } + *static_cast(diagInfoPtr) = err.NativeError; + return SQL_SUCCESS; + case SQL_DIAG_MESSAGE_TEXT: + if (!diagInfoPtr || bufferLength <= 0) { + return SQL_ERROR; + } + strncpy((char*)diagInfoPtr, err.Message.c_str(), bufferLength); + if (stringLengthPtr) { + *stringLengthPtr = static_cast(err.Message.size()); + } + return SQL_SUCCESS; + default: + return SQL_NO_DATA; + } +} + +SQLRETURN HandleOdbcExceptions( + SQLHANDLE handlePtr, + std::function&& func, + ENullInputHandlePolicy nullInputPolicy) { + if (!handlePtr && nullInputPolicy != ENullInputHandlePolicy::Allow) { return SQL_INVALID_HANDLE; } diff --git a/odbc/src/utils/error_manager.h b/odbc/src/utils/error_manager.h index 1e31349964d..5f72a69f563 100644 --- a/odbc/src/utils/error_manager.h +++ b/odbc/src/utils/error_manager.h @@ -66,11 +66,18 @@ class TErrorManager { SQLRETURN GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength); + SQLRETURN GetDiagField(SQLSMALLINT recNumber, SQLSMALLINT diagIdentifier, + SQLPOINTER diagInfoPtr, SQLSMALLINT bufferLength, SQLSMALLINT* stringLengthPtr); private: TErrorList Errors_; }; +enum class ENullInputHandlePolicy : unsigned char { + Reject, + Allow, +}; + template SQLRETURN HandleOdbcExceptions(SQLHANDLE handlePtr, std::function&& func) { if (!handlePtr) { @@ -91,7 +98,10 @@ SQLRETURN HandleOdbcExceptions(SQLHANDLE handlePtr, std::function&& func); +SQLRETURN HandleOdbcExceptions( + SQLHANDLE handlePtr, + std::function&& func, + ENullInputHandlePolicy nullInputPolicy = ENullInputHandlePolicy::Reject); } // namespace NOdbc } // namespace NYdb From 062a3a0b73b03db96427156decbf6183b6f11e8f Mon Sep 17 00:00:00 2001 From: Ylonies Date: Tue, 7 Apr 2026 16:18:04 +0000 Subject: [PATCH 14/21] env features EndTran for env + tests --- odbc/src/connection.cpp | 11 +++ odbc/src/connection.h | 5 +- odbc/src/environment.cpp | 51 ++++++++++++++ odbc/src/environment.h | 9 +++ odbc/src/odbc_driver.cpp | 16 +++-- odbc/tests/integration/CMakeLists.txt | 5 ++ odbc/tests/integration/basic_it.cpp | 24 +------ odbc/tests/integration/env_it.cpp | 99 +++++++++++++++++++++++++++ odbc/tests/integration/test_utils.h | 25 +++++++ 9 files changed, 217 insertions(+), 28 deletions(-) create mode 100644 odbc/tests/integration/env_it.cpp create mode 100644 odbc/tests/integration/test_utils.h diff --git a/odbc/src/connection.cpp b/odbc/src/connection.cpp index eb142108334..29b6758b2a8 100644 --- a/odbc/src/connection.cpp +++ b/odbc/src/connection.cpp @@ -146,5 +146,16 @@ SQLRETURN TConnection::RollbackTx() { return SQL_SUCCESS; } +void TConnection::SetEnvironment(TEnvironment* env){ + if (ParentEnv_){ + throw std::logic_error("Connection already bound to environment"); + } + ParentEnv_ = env; +} + +TEnvironment* TConnection::GetEnvironment(){ + return ParentEnv_; +} + } // namespace NOdbc } // namespace NYdb diff --git a/odbc/src/connection.h b/odbc/src/connection.h index a0ce4acb991..f048e1cef4f 100644 --- a/odbc/src/connection.h +++ b/odbc/src/connection.h @@ -27,13 +27,13 @@ class TConnection : public TErrorManager { std::unique_ptr YdbTableClient_; std::unique_ptr YdbSchemeClient_; std::optional Tx_; - /** Одна сессия KQP на ODBC-соединение: DDL/DML/SELECT видят одну и ту же схему без «новой» сессии на каждый Execute. */ std::optional QuerySession_; std::vector> Statements_; std::string Endpoint_; std::string Database_; std::string AuthToken_; + TEnvironment* ParentEnv_; bool Autocommit_ = true; @@ -62,6 +62,9 @@ class TConnection : public TErrorManager { SQLRETURN CommitTx(); SQLRETURN RollbackTx(); + + void SetEnvironment(TEnvironment* env); + TEnvironment* GetEnvironment(); }; } // namespace NOdbc diff --git a/odbc/src/environment.cpp b/odbc/src/environment.cpp index 541ca9e2160..e66af68f11f 100644 --- a/odbc/src/environment.cpp +++ b/odbc/src/environment.cpp @@ -13,5 +13,56 @@ SQLRETURN TEnvironment::SetAttribute(SQLINTEGER attribute, SQLPOINTER value, SQL return SQL_SUCCESS; } +void TEnvironment::RegisterConnection(TConnection* conn){ + if (conn == nullptr){ + throw std::invalid_argument("null connection"); + } + connections_.insert(conn); +} + +void TEnvironment::UnregisterConnection(TConnection* conn){ + if (conn == nullptr){ + throw std::invalid_argument("null connection"); + } + connections_.erase(conn); +} + +std::vector TEnvironment::GetConnectionsSnapshot() const { + return std::vector(connections_.begin(), connections_.end()); +} + +SQLRETURN TEnvironment::EndTran(SQLSMALLINT completionType){ + if (completionType != SQL_COMMIT && completionType != SQL_ROLLBACK){ + return AddError("HY012", 0, "Invalid transaction operation code"); + } + bool hasFailures = false; + int failedCount = 0; + + for (auto* conn : connections_) { + if (!conn || !conn->GetTx()) { + continue; + } + try { + if (completionType == SQL_COMMIT) { + conn->CommitTx(); + } else { + conn->RollbackTx(); + } + } catch (const std::exception& ex) { + hasFailures = true; + ++failedCount; + AddError("HY000", 0, ex.what(), SQL_SUCCESS_WITH_INFO); + } catch (...) { + hasFailures = true; + ++failedCount; + AddError("HY000", 0, "Unknown error during ENV-level transaction completion", SQL_SUCCESS_WITH_INFO); + } + } + if (hasFailures) { + AddError("01000", 0, "SQLEndTran(SQL_HANDLE_ENV): some connections failed", SQL_SUCCESS_WITH_INFO); + return SQL_SUCCESS_WITH_INFO; + } + return SQL_SUCCESS; +} } // namespace NOdbc } // namespace NYdb diff --git a/odbc/src/environment.h b/odbc/src/environment.h index 5258b722492..70a785f45d7 100644 --- a/odbc/src/environment.h +++ b/odbc/src/environment.h @@ -4,6 +4,8 @@ #include #include +#include +#include namespace NYdb { namespace NOdbc { @@ -13,12 +15,19 @@ class TConnection; class TEnvironment : public TErrorManager { private: SQLINTEGER OdbcVersion_; + std::unordered_set connections_; public: TEnvironment(); ~TEnvironment(); SQLRETURN SetAttribute(SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER stringLength); + + void RegisterConnection(TConnection*); + void UnregisterConnection(TConnection*); + std::vector GetConnectionsSnapshot() const; + + SQLRETURN EndTran(SQLSMALLINT completionType); }; } // namespace NOdbc diff --git a/odbc/src/odbc_driver.cpp b/odbc/src/odbc_driver.cpp index f26bd55c828..2993c76fcef 100644 --- a/odbc/src/odbc_driver.cpp +++ b/odbc/src/odbc_driver.cpp @@ -39,8 +39,11 @@ SQLRETURN SQL_API SQLAllocHandle(SQLSMALLINT handleType, } case SQL_HANDLE_DBC: { - return NYdb::NOdbc::HandleOdbcExceptions(inputHandle, [&]() { - *outputHandle = new NYdb::NOdbc::TConnection(); + return NYdb::NOdbc::HandleOdbcExceptions(inputHandle, [&](auto* env) { + auto conn = std::make_unique(); + conn->SetEnvironment(env); + env->RegisterConnection(conn.get()); + *outputHandle = conn.release(); return SQL_SUCCESS; }); } @@ -66,6 +69,10 @@ SQLRETURN SQL_API SQLFreeHandle(SQLSMALLINT handleType, SQLHANDLE handle) { } case SQL_HANDLE_DBC: { return NYdb::NOdbc::HandleOdbcExceptions(handle, [](auto* conn) { + auto* env = conn->GetEnvironment(); + if (env != nullptr){ + env->UnregisterConnection(conn); + } delete conn; return SQL_SUCCESS; }); @@ -281,8 +288,9 @@ SQLRETURN SQL_API SQLEndTran(SQLSMALLINT handleType, SQLHANDLE handle, SQLSMALLI }); } case SQL_HANDLE_ENV: { - // TODO: if's list of connections in ENV, go through them and commit/rollback transactions - return SQL_SUCCESS; + return NYdb::NOdbc::HandleOdbcExceptions(handle, [&](auto* env) -> SQLRETURN { + return env->EndTran(completionType); + }); } default: return SQL_ERROR; diff --git a/odbc/tests/integration/CMakeLists.txt b/odbc/tests/integration/CMakeLists.txt index e1aad9d3913..0360679931c 100644 --- a/odbc/tests/integration/CMakeLists.txt +++ b/odbc/tests/integration/CMakeLists.txt @@ -2,3 +2,8 @@ add_odbc_test(NAME odbc-basic_it SOURCES basic_it.cpp ) + +add_odbc_test(NAME odbc-env_it + SOURCES + env_it.cpp +) diff --git a/odbc/tests/integration/basic_it.cpp b/odbc/tests/integration/basic_it.cpp index b4c7078ac4e..37973667147 100644 --- a/odbc/tests/integration/basic_it.cpp +++ b/odbc/tests/integration/basic_it.cpp @@ -1,26 +1,4 @@ -#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;"; +#include "test_utils.h" TEST(OdbcBasic, SimpleQuery) { SQLHENV env; diff --git a/odbc/tests/integration/env_it.cpp b/odbc/tests/integration/env_it.cpp new file mode 100644 index 00000000000..fd351d127af --- /dev/null +++ b/odbc/tests/integration/env_it.cpp @@ -0,0 +1,99 @@ +#include "test_utils.h" + +namespace { + +void AllocEnvAndConnect(SQLHENV* env, SQLHDBC* dbc) { + 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); + SQLRETURN rc = SQLDriverConnect( + *dbc, nullptr, (SQLCHAR*)kConnStr, SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE); + CHECK_ODBC_OK(rc, *dbc, SQL_HANDLE_DBC); +} + +void StartManualTx(SQLHDBC dbc, SQLHSTMT* stmt) { + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0), dbc, SQL_HANDLE_DBC); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, stmt), SQL_SUCCESS); + CHECK_ODBC_OK(SQLExecDirect(*stmt, (SQLCHAR*)"SELECT 1", SQL_NTS), *stmt, SQL_HANDLE_STMT); +} + +} // namespace + +TEST(OdbcEnv, EndTranCommitOnEnv) { + SQLHENV env; + SQLHDBC dbc1, dbc2; + SQLHSTMT stmt1, stmt2; + + AllocEnvAndConnect(&env, &dbc1); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc2), SQL_SUCCESS); + SQLRETURN rc = SQLDriverConnect( + dbc2, nullptr, (SQLCHAR*)kConnStr, SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE); + CHECK_ODBC_OK(rc, dbc2, SQL_HANDLE_DBC); + + StartManualTx(dbc1, &stmt1); + StartManualTx(dbc2, &stmt2); + + CHECK_ODBC_OK(SQLEndTran(SQL_HANDLE_ENV, env, SQL_COMMIT), env, SQL_HANDLE_ENV); + + SQLFreeHandle(SQL_HANDLE_STMT, stmt1); + SQLFreeHandle(SQL_HANDLE_STMT, stmt2); + SQLDisconnect(dbc1); + SQLDisconnect(dbc2); + SQLFreeHandle(SQL_HANDLE_DBC, dbc1); + SQLFreeHandle(SQL_HANDLE_DBC, dbc2); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcEnv, EndTranRollbackOnEnv) { + SQLHENV env; + SQLHDBC dbc1, dbc2; + SQLHSTMT stmt1, stmt2; + + AllocEnvAndConnect(&env, &dbc1); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc2), SQL_SUCCESS); + SQLRETURN rc = SQLDriverConnect( + dbc2, nullptr, (SQLCHAR*)kConnStr, SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE); + CHECK_ODBC_OK(rc, dbc2, SQL_HANDLE_DBC); + + StartManualTx(dbc1, &stmt1); + StartManualTx(dbc2, &stmt2); + + CHECK_ODBC_OK(SQLEndTran(SQL_HANDLE_ENV, env, SQL_ROLLBACK), env, SQL_HANDLE_ENV); + + SQLFreeHandle(SQL_HANDLE_STMT, stmt1); + SQLFreeHandle(SQL_HANDLE_STMT, stmt2); + SQLDisconnect(dbc1); + SQLDisconnect(dbc2); + SQLFreeHandle(SQL_HANDLE_DBC, dbc1); + SQLFreeHandle(SQL_HANDLE_DBC, dbc2); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcEnv, EndTranPartialFailureReturnsInfo) { + SQLHENV env; + SQLHDBC dbc1, dbc2; + SQLHSTMT stmt1, stmt2; + + AllocEnvAndConnect(&env, &dbc1); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc2), SQL_SUCCESS); + SQLRETURN rc = SQLDriverConnect( + dbc2, nullptr, (SQLCHAR*)kConnStr, SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE); + CHECK_ODBC_OK(rc, dbc2, SQL_HANDLE_DBC); + + StartManualTx(dbc1, &stmt1); + CHECK_ODBC_OK(SQLSetConnectAttr(dbc2, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0), dbc2, SQL_HANDLE_DBC); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc2, &stmt2), SQL_SUCCESS); + (void)SQLExecDirect(stmt2, (SQLCHAR*)"SELECT FROM", SQL_NTS); + + rc = SQLEndTran(SQL_HANDLE_ENV, env, SQL_COMMIT); + ASSERT_TRUE(rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO || rc == SQL_ERROR) + << GetOdbcError(env, SQL_HANDLE_ENV); + + SQLFreeHandle(SQL_HANDLE_STMT, stmt1); + SQLFreeHandle(SQL_HANDLE_STMT, stmt2); + SQLDisconnect(dbc1); + SQLDisconnect(dbc2); + SQLFreeHandle(SQL_HANDLE_DBC, dbc1); + SQLFreeHandle(SQL_HANDLE_DBC, dbc2); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} diff --git a/odbc/tests/integration/test_utils.h b/odbc/tests/integration/test_utils.h new file mode 100644 index 00000000000..c43272f0f54 --- /dev/null +++ b/odbc/tests/integration/test_utils.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include +#include + +#include + +#define CHECK_ODBC_OK(rc, handle, type) \ + ASSERT_TRUE((rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO) << GetOdbcError(handle, type) + +inline std::string GetOdbcError(SQLHANDLE handle, SQLSMALLINT type) { + SQLCHAR sqlState[6] = {0}; + SQLCHAR message[256] = {0}; + SQLINTEGER nativeError = 0; + SQLSMALLINT textLength = 0; + 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"; +} + +inline const char* kConnStr = "Driver=" ODBC_DRIVER_PATH ";Endpoint=localhost:2136;Database=/local;"; From fe35daac060d4873f89cd1f5d459fbe6c068943a Mon Sep 17 00:00:00 2001 From: Ylonies Date: Wed, 8 Apr 2026 11:54:28 +0000 Subject: [PATCH 15/21] attributes --- odbc/src/connection.cpp | 24 ++++- odbc/src/connection.h | 8 +- odbc/src/connection_attributes.cpp | 141 +++++++++++++++++++++++++++++ odbc/src/connection_attributes.h | 48 ++++++++++ odbc/src/environment.cpp | 20 +++- odbc/src/odbc_driver.cpp | 20 ++-- odbc/src/statement.cpp | 13 ++- 7 files changed, 250 insertions(+), 24 deletions(-) create mode 100644 odbc/src/connection_attributes.cpp create mode 100644 odbc/src/connection_attributes.h diff --git a/odbc/src/connection.cpp b/odbc/src/connection.cpp index 29b6758b2a8..b1049163a5d 100644 --- a/odbc/src/connection.cpp +++ b/odbc/src/connection.cpp @@ -2,9 +2,8 @@ #include "statement.h" #include "utils/error_manager.h" -#include -#include #include +#include #include #include @@ -107,8 +106,8 @@ void TConnection::RemoveStatement(TStatement* stmt) { } SQLRETURN TConnection::SetAutocommit(bool value) { - Autocommit_ = value; - if (Autocommit_ && Tx_) { + Attributes_.SetAutocommit(value); + if (Attributes_.GetAutocommit() && Tx_) { auto status = Tx_->Commit().ExtractValueSync(); NStatusHelpers::ThrowOnError(status); Tx_.reset(); @@ -117,7 +116,22 @@ SQLRETURN TConnection::SetAutocommit(bool value) { } bool TConnection::GetAutocommit() const { - return Autocommit_; + return Attributes_.GetAutocommit(); +} + +SQLRETURN TConnection::SetConnectAttr(SQLINTEGER attr, SQLPOINTER value, SQLINTEGER stringLength) { + return Attributes_.SetConnectAttr(attr, value, stringLength, [this](bool autocommit) { + return SetAutocommit(autocommit); + }, *this); +} + +SQLRETURN TConnection::GetConnectAttr(SQLINTEGER attr, SQLPOINTER value, SQLINTEGER bufferLength, + SQLINTEGER* stringLengthPtr) { + return Attributes_.GetConnectAttr(attr, value, bufferLength, stringLengthPtr, *this); +} + +NQuery::TTxSettings TConnection::MakeTxSettings() const { + return Attributes_.MakeTxSettings(); } const std::optional& TConnection::GetTx() { diff --git a/odbc/src/connection.h b/odbc/src/connection.h index f048e1cef4f..e1c9028fa84 100644 --- a/odbc/src/connection.h +++ b/odbc/src/connection.h @@ -1,6 +1,7 @@ #pragma once #include "environment.h" +#include "connection_attributes.h" #include "utils/error_manager.h" #include @@ -35,8 +36,7 @@ class TConnection : public TErrorManager { std::string AuthToken_; TEnvironment* ParentEnv_; - bool Autocommit_ = true; - + TConnectionAttributes Attributes_; public: SQLRETURN Connect(const std::string& serverName, const std::string& userName, @@ -56,6 +56,10 @@ class TConnection : public TErrorManager { SQLRETURN SetAutocommit(bool value); bool GetAutocommit() const; + SQLRETURN SetConnectAttr(SQLINTEGER attr, SQLPOINTER value, SQLINTEGER stringLength); + SQLRETURN GetConnectAttr(SQLINTEGER attr, SQLPOINTER value, SQLINTEGER bufferLength, SQLINTEGER* stringLengthPtr); + NQuery::TTxSettings MakeTxSettings() const; + const std::optional& GetTx(); void SetTx(const NQuery::TTransaction& tx); void Reset(); diff --git a/odbc/src/connection_attributes.cpp b/odbc/src/connection_attributes.cpp new file mode 100644 index 00000000000..61bfa7f8caa --- /dev/null +++ b/odbc/src/connection_attributes.cpp @@ -0,0 +1,141 @@ +#include "connection_attributes.h" + +#include + +namespace NYdb { +namespace NOdbc { + +std::optional TConnectionAttributes::ResolveTxMode(SQLUINTEGER accessMode, SQLUINTEGER txnIsolation) { + if (accessMode == SQL_MODE_READ_ONLY) { + switch (txnIsolation) { + case SQL_TXN_READ_UNCOMMITTED: + return NQuery::TTxSettings::TS_STALE_RO; + case SQL_TXN_READ_COMMITTED: + return NQuery::TTxSettings::TS_ONLINE_RO; + case SQL_TXN_REPEATABLE_READ: + case SQL_TXN_SERIALIZABLE: + return NQuery::TTxSettings::TS_SNAPSHOT_RO; + default: + return std::nullopt; + } + } + + switch (txnIsolation) { + case SQL_TXN_REPEATABLE_READ: + case SQL_TXN_SERIALIZABLE: + return NQuery::TTxSettings::TS_SERIALIZABLE_RW; + default: + return std::nullopt; + } +} + +SQLRETURN TConnectionAttributes::SetAutocommit(bool value) { + Autocommit_ = value; + return SQL_SUCCESS; +} + +bool TConnectionAttributes::GetAutocommit() const { + return Autocommit_; +} + +SQLRETURN TConnectionAttributes::SetConnectAttr( + SQLINTEGER attr, + SQLPOINTER value, + SQLINTEGER /*stringLength*/, + const std::function& applyAutocommit, + TErrorManager& errors) { + switch (attr) { + case SQL_ATTR_AUTOCOMMIT: { + const intptr_t val = reinterpret_cast(value); + if (val == static_cast(SQL_AUTOCOMMIT_ON)) { + return applyAutocommit(true); + } + if (val == static_cast(SQL_AUTOCOMMIT_OFF)) { + return applyAutocommit(false); + } + return errors.AddError("HY024", 0, "Invalid SQL_ATTR_AUTOCOMMIT value"); + } + case SQL_ATTR_ACCESS_MODE: { + const intptr_t val = reinterpret_cast(value); + if (val == static_cast(SQL_MODE_READ_WRITE)) { + AccessMode_ = SQL_MODE_READ_WRITE; + auto txMode = ResolveTxMode(AccessMode_, TxnIsolation_); + if (!txMode) { + return errors.AddError("HYC00", 0, "Transaction isolation is not supported for read-write mode"); + } + TxMode_ = *txMode; + return SQL_SUCCESS; + } + if (val == static_cast(SQL_MODE_READ_ONLY)) { + AccessMode_ = SQL_MODE_READ_ONLY; + auto txMode = ResolveTxMode(AccessMode_, TxnIsolation_); + if (!txMode) { + return errors.AddError("HYC00", 0, "Transaction isolation is not supported for read-only mode"); + } + TxMode_ = *txMode; + return SQL_SUCCESS; + } + return errors.AddError("HY024", 0, "Invalid SQL_ATTR_ACCESS_MODE value"); + } + case SQL_ATTR_TXN_ISOLATION: { + const intptr_t val = reinterpret_cast(value); + const SQLUINTEGER isolation = static_cast(val); + auto txMode = ResolveTxMode(AccessMode_, isolation); + if (!txMode) { + return errors.AddError("HYC00", 0, "SQL_ATTR_TXN_ISOLATION value is not supported"); + } + TxnIsolation_ = isolation; + TxMode_ = *txMode; + return SQL_SUCCESS; + } + default: + return errors.AddError("HYC00", 0, "Optional feature not implemented"); + } +} + +SQLRETURN TConnectionAttributes::GetConnectAttr( + SQLINTEGER attr, + SQLPOINTER value, + SQLINTEGER /*bufferLength*/, + SQLINTEGER* stringLengthPtr, + TErrorManager& errors) const { + if (!value) { + return errors.AddError("HY009", 0, "Invalid use of null pointer"); + } + if (stringLengthPtr) { + *stringLengthPtr = 0; + } + auto* out = reinterpret_cast(value); + switch (attr) { + case SQL_ATTR_AUTOCOMMIT: + *out = GetAutocommit() ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF; + return SQL_SUCCESS; + case SQL_ATTR_ACCESS_MODE: + *out = AccessMode_; + return SQL_SUCCESS; + case SQL_ATTR_TXN_ISOLATION: + *out = TxnIsolation_; + return SQL_SUCCESS; + default: + return errors.AddError("HYC00", 0, "Optional feature not implemented"); + } +} + +NQuery::TTxSettings TConnectionAttributes::MakeTxSettings() const { + switch (TxMode_) { + case NQuery::TTxSettings::TS_ONLINE_RO: + return NQuery::TTxSettings::OnlineRO(); + case NQuery::TTxSettings::TS_STALE_RO: + return NQuery::TTxSettings::StaleRO(); + case NQuery::TTxSettings::TS_SNAPSHOT_RO: + return NQuery::TTxSettings::SnapshotRO(); + case NQuery::TTxSettings::TS_SNAPSHOT_RW: + return NQuery::TTxSettings::SnapshotRW(); + case NQuery::TTxSettings::TS_SERIALIZABLE_RW: + default: + return NQuery::TTxSettings::SerializableRW(); + } +} + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/connection_attributes.h b/odbc/src/connection_attributes.h new file mode 100644 index 00000000000..7b2f0fc7221 --- /dev/null +++ b/odbc/src/connection_attributes.h @@ -0,0 +1,48 @@ +#pragma once + +#include "utils/error_manager.h" + +#include + +#include +#include + +#include +#include + +namespace NYdb { +namespace NOdbc { + +class TConnectionAttributes { +public: + SQLRETURN SetAutocommit(bool value); + bool GetAutocommit() const; + + SQLRETURN SetConnectAttr( + SQLINTEGER attr, + SQLPOINTER value, + SQLINTEGER stringLength, + const std::function& applyAutocommit, + TErrorManager& errors); + + SQLRETURN GetConnectAttr( + SQLINTEGER attr, + SQLPOINTER value, + SQLINTEGER bufferLength, + SQLINTEGER* stringLengthPtr, + TErrorManager& errors) const; + + NQuery::TTxSettings MakeTxSettings() const; + +private: + static std::optional ResolveTxMode(SQLUINTEGER accessMode, SQLUINTEGER txnIsolation); + +private: + bool Autocommit_ = true; + SQLUINTEGER AccessMode_ = SQL_MODE_READ_WRITE; + SQLUINTEGER TxnIsolation_ = SQL_TXN_SERIALIZABLE; + NQuery::TTxSettings::ETransactionMode TxMode_ = NQuery::TTxSettings::TS_SERIALIZABLE_RW; +}; + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/environment.cpp b/odbc/src/environment.cpp index e66af68f11f..44e3473d023 100644 --- a/odbc/src/environment.cpp +++ b/odbc/src/environment.cpp @@ -8,9 +8,23 @@ 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; + switch (attribute) { + case SQL_ATTR_ODBC_VERSION: { + if (!value) { + return AddError("HY009", 0, "Invalid use of null pointer"); + } + OdbcVersion_ = static_cast(reinterpret_cast(value)); + return SQL_SUCCESS; + } + case SQL_ATTR_OUTPUT_NTS: { + if (value && static_cast(reinterpret_cast(value)) != SQL_TRUE) { + return AddError("HY024", 0, "SQL_ATTR_OUTPUT_NTS must be SQL_TRUE"); + } + return SQL_SUCCESS; + } + default: + return AddError("HYC00", 0, "Optional feature not implemented"); + } } void TEnvironment::RegisterConnection(TConnection* conn){ diff --git a/odbc/src/odbc_driver.cpp b/odbc/src/odbc_driver.cpp index 2993c76fcef..6b516c63bb8 100644 --- a/odbc/src/odbc_driver.cpp +++ b/odbc/src/odbc_driver.cpp @@ -299,17 +299,14 @@ SQLRETURN SQL_API SQLEndTran(SQLSMALLINT handleType, SQLHANDLE handle, SQLSMALLI SQLRETURN SQL_API SQLSetConnectAttr(SQLHDBC connectionHandle, SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER stringLength) { return NYdb::NOdbc::HandleOdbcExceptions(connectionHandle, [&](auto* conn) { - 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 { - throw NYdb::NOdbc::TOdbcException("HY000", 0, "Invalid autocommit value"); - } - } - // TODO: other attributes - throw NYdb::NOdbc::TOdbcException("HYC00", 0, "Optional feature not implemented"); + return conn->SetConnectAttr(attribute, value, stringLength); + }); +} + +SQLRETURN SQL_API SQLGetConnectAttr(SQLHDBC connectionHandle, SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER bufferLength, + SQLINTEGER* stringLengthPtr) { + return NYdb::NOdbc::HandleOdbcExceptions(connectionHandle, [&](auto* conn) { + return conn->GetConnectAttr(attribute, value, bufferLength, stringLengthPtr); }); } @@ -373,6 +370,7 @@ SQLRETURN SQL_API SQLFetchScroll(SQLHSTMT statementHandle, SQLSMALLINT fetchOrie } else { throw NYdb::NOdbc::TOdbcException("HYC00", 0, "Only SQL_FETCH_NEXT is supported"); } + //TODO other fetch-orientation }); } diff --git a/odbc/src/statement.cpp b/odbc/src/statement.cpp index b7fddc624bf..6f714d0b0bf 100644 --- a/odbc/src/statement.cpp +++ b/odbc/src/statement.cpp @@ -36,7 +36,7 @@ SQLRETURN TStatement::Execute() { if (Conn_->GetAutocommit()){ Conn_->Reset(); } - + auto& session = Conn_->GetOrCreateQuerySession(); auto iterator = CreateExecuteIterator(session, params); @@ -55,13 +55,20 @@ SQLRETURN TStatement::Execute() { NQuery::TExecuteQueryIterator TStatement::CreateExecuteIterator(NQuery::TSession& session, const NYdb::TParams& params){ if (Conn_->GetAutocommit()) { + const auto txSettings = Conn_->MakeTxSettings(); + if (txSettings.GetMode() == NQuery::TTxSettings::TS_SERIALIZABLE_RW) { + return session.StreamExecuteQuery( + PreparedQuery_, + NQuery::TTxControl::NoTx(), + params).ExtractValueSync(); + } return session.StreamExecuteQuery( PreparedQuery_, - NQuery::TTxControl::NoTx(), + NQuery::TTxControl::BeginTx(txSettings).CommitTx(), params).ExtractValueSync(); } if (!Conn_->GetTx()) { - auto beginTxResult = session.BeginTransaction(NQuery::TTxSettings::SerializableRW()).ExtractValueSync(); + auto beginTxResult = session.BeginTransaction(Conn_->MakeTxSettings()).ExtractValueSync(); NStatusHelpers::ThrowOnError(beginTxResult); Conn_->SetTx(beginTxResult.GetTransaction()); } From 8a48e2d794bf5bd126347be0af49b79c2453b598 Mon Sep 17 00:00:00 2001 From: Ylonies Date: Thu, 9 Apr 2026 10:30:42 +0000 Subject: [PATCH 16/21] fix --- odbc/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/odbc/CMakeLists.txt b/odbc/CMakeLists.txt index 9919870702d..0390b20698c 100644 --- a/odbc/CMakeLists.txt +++ b/odbc/CMakeLists.txt @@ -5,6 +5,7 @@ add_library(ydb-odbc SHARED src/utils/convert.cpp src/utils/error_manager.cpp src/odbc_driver.cpp + src/connection_attributes.cpp src/connection.cpp src/statement.cpp src/environment.cpp From 97a83f701fec2d068a163bda1b433ea9e0f7933a Mon Sep 17 00:00:00 2001 From: Ylonies Date: Fri, 17 Apr 2026 13:37:51 +0300 Subject: [PATCH 17/21] driver pool --- odbc/src/connection.cpp | 91 +++++++++++++++++++++++++++++++++++++++++ odbc/src/connection.h | 2 +- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/odbc/src/connection.cpp b/odbc/src/connection.cpp index b1049163a5d..908edd962f0 100644 --- a/odbc/src/connection.cpp +++ b/odbc/src/connection.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -13,6 +14,60 @@ namespace NYdb { namespace NOdbc { +namespace { + +struct TDriverKey { + std::string Endpoint; + std::string Database; + + bool operator==(const TDriverKey& other) const noexcept { + return Endpoint == other.Endpoint && Database == other.Database; + } +}; + +struct TDriverKeyHash { + size_t operator()(const TDriverKey& key) const noexcept { + return std::hash{}(key.Endpoint) ^ (std::hash{}(key.Database) << 1U); + } +}; + +struct TDriverPool { + std::unordered_map, TDriverKeyHash> DriversByKey; + size_t InsertionsSinceCleanup = 0; +}; + +void CleanupExpiredDrivers(TDriverPool& pool) { + for (auto mapIt = pool.DriversByKey.begin(); mapIt != pool.DriversByKey.end();) { + if (mapIt->second.expired()) { + mapIt = pool.DriversByKey.erase(mapIt); + } else { + ++mapIt; + } + } +} + +std::shared_ptr AcquireSharedDriver(const std::string& endpoint, const std::string& database) { + static TDriverPool pool; + TDriverKey key{endpoint, database}; + auto it = pool.DriversByKey.find(key); + if (it != pool.DriversByKey.end()) { + if (std::shared_ptr existing = it->second.lock()) { + return existing; + } + } + auto driver = std::make_shared( + NYdb::TDriverConfig().SetEndpoint(endpoint).SetDatabase(database)); + pool.DriversByKey[std::move(key)] = driver; + ++pool.InsertionsSinceCleanup; + if (pool.InsertionsSinceCleanup >= 32) { + CleanupExpiredDrivers(pool); + pool.InsertionsSinceCleanup = 0; + } + return driver; +} + +} // namespace + SQLRETURN TConnection::DriverConnect(const std::string& connectionString) { std::map params; size_t pos = 0; @@ -171,5 +226,41 @@ TEnvironment* TConnection::GetEnvironment(){ return ParentEnv_; } +void TConnection::RecreateYdbClients() { + QuerySession_.reset(); + Tx_.reset(); + YdbSchemeClient_.reset(); + YdbTableClient_.reset(); + YdbClient_.reset(); + YdbDriver_ = AcquireSharedDriver(Endpoint_, Database_); + YdbClient_ = std::make_unique(*YdbDriver_); + YdbSchemeClient_ = std::make_unique(*YdbDriver_); + YdbTableClient_ = std::make_unique(*YdbDriver_); +} + +void TConnection::RebindToDatabase(const std::string& newDatabase) { + std::string db = newDatabase; + TConnectionAttributes::NormalizeCatalogPath(db); + Database_ = std::move(db); + Attributes_.SetCurrentCatalog(Database_); + RecreateYdbClients(); +} + + +std::string TConnection::WrapQueryForCurrentCatalog(const std::string& sql) const { + std::optional rel = Attributes_.ResolveCatalogRoute(Database_).TablePathPrefix; + if (!rel) { + return sql; + } + std::string escapedPrefix; + escapedPrefix.reserve(rel->size() + 8); + for (const char ch : *rel) { + if (ch == '\\' || ch == '"') { + escapedPrefix.push_back('\\'); + } + escapedPrefix.push_back(ch); + } + return "PRAGMA TablePathPrefix = \"" + escapedPrefix + "\";\n" + sql; +} } // namespace NOdbc } // namespace NYdb diff --git a/odbc/src/connection.h b/odbc/src/connection.h index e1c9028fa84..71bb6664454 100644 --- a/odbc/src/connection.h +++ b/odbc/src/connection.h @@ -23,7 +23,7 @@ class TStatement; class TConnection : public TErrorManager { private: - std::unique_ptr YdbDriver_; + std::shared_ptr YdbDriver_; std::unique_ptr YdbClient_; std::unique_ptr YdbTableClient_; std::unique_ptr YdbSchemeClient_; From 42111889ebfc3cbee8b46556dbabd4611b59662c Mon Sep 17 00:00:00 2001 From: Ylonies Date: Fri, 17 Apr 2026 13:55:29 +0300 Subject: [PATCH 18/21] conn attributes Combine connection attribute routing and error-localization updates into one focused commit while keeping driver pool changes separate. Made-with: Cursor --- odbc/CMakeLists.txt | 3 +- odbc/src/connection.cpp | 31 +-- odbc/src/connection.h | 10 +- odbc/src/connection_attr.cpp | 308 ++++++++++++++++++++++++++ odbc/src/connection_attr.h | 86 +++++++ odbc/src/connection_attributes.cpp | 141 ------------ odbc/src/connection_attributes.h | 48 ---- odbc/src/statement.cpp | 7 +- odbc/src/utils/attr.cpp | 51 +++++ odbc/src/utils/attr.h | 46 ++++ odbc/src/utils/diag.h | 33 +++ odbc/tests/integration/CMakeLists.txt | 5 + odbc/tests/integration/attr_it.cpp | 244 ++++++++++++++++++++ 13 files changed, 804 insertions(+), 209 deletions(-) create mode 100644 odbc/src/connection_attr.cpp create mode 100644 odbc/src/connection_attr.h delete mode 100644 odbc/src/connection_attributes.cpp delete mode 100644 odbc/src/connection_attributes.h create mode 100644 odbc/src/utils/attr.cpp create mode 100644 odbc/src/utils/attr.h create mode 100644 odbc/src/utils/diag.h create mode 100644 odbc/tests/integration/attr_it.cpp diff --git a/odbc/CMakeLists.txt b/odbc/CMakeLists.txt index 0390b20698c..5071c42f85d 100644 --- a/odbc/CMakeLists.txt +++ b/odbc/CMakeLists.txt @@ -1,11 +1,12 @@ add_library(ydb-odbc SHARED + src/utils/attr.cpp src/utils/cursor.cpp src/utils/types.cpp src/utils/util.cpp src/utils/convert.cpp src/utils/error_manager.cpp src/odbc_driver.cpp - src/connection_attributes.cpp + src/connection_attr.cpp src/connection.cpp src/statement.cpp src/environment.cpp diff --git a/odbc/src/connection.cpp b/odbc/src/connection.cpp index 908edd962f0..a52d5036f04 100644 --- a/odbc/src/connection.cpp +++ b/odbc/src/connection.cpp @@ -93,13 +93,9 @@ SQLRETURN TConnection::DriverConnect(const std::string& connectionString) { throw TOdbcException("08001", 0, "Missing Endpoint or Database in connection string"); } - YdbDriver_ = std::make_unique(NYdb::TDriverConfig() - .SetEndpoint(Endpoint_) - .SetDatabase(Database_)); - - YdbClient_ = std::make_unique(*YdbDriver_); - YdbSchemeClient_ = std::make_unique(*YdbDriver_); - YdbTableClient_ = std::make_unique(*YdbDriver_); + TConnectionAttributes::NormalizeCatalogPath(Database_); + RecreateYdbClients(); + Attributes_.SetCurrentCatalog(Database_); return SQL_SUCCESS; } @@ -121,13 +117,9 @@ SQLRETURN TConnection::Connect(const std::string& serverName, throw TOdbcException("08001", 0, "Missing Endpoint or Database in DSN"); } - YdbDriver_ = std::make_unique(NYdb::TDriverConfig() - .SetEndpoint(Endpoint_) - .SetDatabase(Database_)); - - YdbClient_ = std::make_unique(*YdbDriver_); - YdbSchemeClient_ = std::make_unique(*YdbDriver_); - YdbTableClient_ = std::make_unique(*YdbDriver_); + TConnectionAttributes::NormalizeCatalogPath(Database_); + RecreateYdbClients(); + Attributes_.SetCurrentCatalog(Database_); return SQL_SUCCESS; } @@ -175,6 +167,17 @@ bool TConnection::GetAutocommit() const { } SQLRETURN TConnection::SetConnectAttr(SQLINTEGER attr, SQLPOINTER value, SQLINTEGER stringLength) { + if (attr == SQL_ATTR_CURRENT_CATALOG) { + std::optional rebindDatabase; + SQLRETURN rc = Attributes_.ApplyCatalogChange(value, stringLength, Database_, rebindDatabase, *this); + if (rc != SQL_SUCCESS) { + return rc; + } + if (rebindDatabase) { + RebindToDatabase(*rebindDatabase); + } + return SQL_SUCCESS; + } return Attributes_.SetConnectAttr(attr, value, stringLength, [this](bool autocommit) { return SetAutocommit(autocommit); }, *this); diff --git a/odbc/src/connection.h b/odbc/src/connection.h index 71bb6664454..284ac36cf65 100644 --- a/odbc/src/connection.h +++ b/odbc/src/connection.h @@ -1,7 +1,7 @@ #pragma once #include "environment.h" -#include "connection_attributes.h" +#include "connection_attr.h" #include "utils/error_manager.h" #include @@ -13,8 +13,9 @@ #include #include -#include +#include #include +#include namespace NYdb { namespace NOdbc { @@ -37,6 +38,9 @@ class TConnection : public TErrorManager { TEnvironment* ParentEnv_; TConnectionAttributes Attributes_; + + void RecreateYdbClients(); + void RebindToDatabase(const std::string& newDatabase); public: SQLRETURN Connect(const std::string& serverName, const std::string& userName, @@ -60,6 +64,8 @@ class TConnection : public TErrorManager { SQLRETURN GetConnectAttr(SQLINTEGER attr, SQLPOINTER value, SQLINTEGER bufferLength, SQLINTEGER* stringLengthPtr); NQuery::TTxSettings MakeTxSettings() const; + std::string WrapQueryForCurrentCatalog(const std::string& sql) const; + const std::optional& GetTx(); void SetTx(const NQuery::TTransaction& tx); void Reset(); diff --git a/odbc/src/connection_attr.cpp b/odbc/src/connection_attr.cpp new file mode 100644 index 00000000000..6197a4ad4a9 --- /dev/null +++ b/odbc/src/connection_attr.cpp @@ -0,0 +1,308 @@ + +#include "connection_attr.h" +#include "utils/attr.h" +#include "utils/diag.h" + +#include + +namespace NYdb { +namespace NOdbc { + +namespace { + +namespace Catalog { + +void NormalizePath(std::string& path) { + if (path.empty() || path == "/") { + return; + } + const size_t trailingSlashStart = path.find_last_not_of('/'); + if (trailingSlashStart == std::string::npos) { + path.assign("/"); + return; + } + path.erase(trailingSlashStart + 1); +} + +TConnectionAttributes::TCatalogBinding BuildBinding(const std::string& currentCatalog, const std::string& database) { + TConnectionAttributes::TCatalogBinding binding; + binding.Catalog = currentCatalog; + binding.Database = database; + NormalizePath(binding.Catalog); + NormalizePath(binding.Database); + if (binding.Catalog == binding.Database) { + return binding; + } + + const std::string databasePrefix = binding.Database + "/"; + if (binding.Catalog.size() <= databasePrefix.size() || + binding.Catalog.compare(0, databasePrefix.size(), databasePrefix) != 0) { + return binding; + } + + std::string relativeCatalog = binding.Catalog.substr(databasePrefix.size()); + if (!relativeCatalog.empty()) { + binding.RelativeCatalog = std::move(relativeCatalog); + } + return binding; +} + +} // namespace Catalog + +namespace Tx { + +bool IsKnownTxnIsolation(SQLUINTEGER txnIsolation) { + switch (txnIsolation) { + case SQL_TXN_READ_UNCOMMITTED: + case SQL_TXN_READ_COMMITTED: + case SQL_TXN_REPEATABLE_READ: + case SQL_TXN_SERIALIZABLE: + return true; + default: + return false; + } +} + +std::optional ResolveTxMode(SQLUINTEGER accessMode, SQLUINTEGER txnIsolation) { + if (accessMode == SQL_MODE_READ_ONLY) { + switch (txnIsolation) { + case SQL_TXN_READ_UNCOMMITTED: + return NQuery::TTxSettings::TS_STALE_RO; + case SQL_TXN_READ_COMMITTED: + return NQuery::TTxSettings::TS_ONLINE_RO; + case SQL_TXN_REPEATABLE_READ: + case SQL_TXN_SERIALIZABLE: + return NQuery::TTxSettings::TS_SNAPSHOT_RO; + default: + return std::nullopt; + } + } + + switch (txnIsolation) { + case SQL_TXN_REPEATABLE_READ: + case SQL_TXN_SERIALIZABLE: + return NQuery::TTxSettings::TS_SERIALIZABLE_RW; + default: + return std::nullopt; + } +} + +} // namespace Tx + +namespace Autocommit { + +SQLRETURN Get(bool autocommitEnabled, SQLPOINTER value) { + auto* out = reinterpret_cast(value); + *out = autocommitEnabled ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF; + return SQL_SUCCESS; +} + +} // namespace Autocommit + +} + +void TConnectionAttributes::NormalizeCatalogPath(std::string& path) { + Catalog::NormalizePath(path); +} + +SQLRETURN TConnectionAttributes::SetAutocommit(bool value) { + Autocommit_ = value; + return SQL_SUCCESS; +} + +bool TConnectionAttributes::GetAutocommit() const { + return Autocommit_; +} + +SQLRETURN TConnectionAttributes::SetConnectAttr( + SQLINTEGER attr, + SQLPOINTER value, + SQLINTEGER stringLength, + const std::function& applyAutocommit, + TErrorManager& errors) { + switch (attr) { + case SQL_ATTR_AUTOCOMMIT: + return SetAutocommit(value, applyAutocommit, errors); + case SQL_ATTR_ACCESS_MODE: + return SetAccessMode(value, errors); + case SQL_ATTR_TXN_ISOLATION: + return SetTxnIsolation(value, errors); + case SQL_ATTR_CURRENT_CATALOG: + return SetCurrentCatalog(value, stringLength, errors); + default: + return Diag::AddNotImplemented(errors); + } +} + +SQLRETURN TConnectionAttributes::GetConnectAttr( + SQLINTEGER attr, + SQLPOINTER value, + SQLINTEGER bufferLength, + SQLINTEGER* stringLengthPtr, + TErrorManager& errors) const { + if (!value) { + return Diag::AddNullPointer(errors); + } + if (stringLengthPtr) { + *stringLengthPtr = 0; + } + switch (attr) { + case SQL_ATTR_AUTOCOMMIT: + return GetAutocommit(value); + case SQL_ATTR_ACCESS_MODE: + return GetAccessMode(value); + case SQL_ATTR_TXN_ISOLATION: + return GetTxnIsolation(value); + case SQL_ATTR_CURRENT_CATALOG: + return GetCurrentCatalog(value, bufferLength, stringLengthPtr, errors); + default: + return Diag::AddNotImplemented(errors); + } +} + +SQLRETURN TConnectionAttributes::SetAutocommit( + SQLPOINTER value, + const std::function& applyAutocommit, + TErrorManager& errors) { + const auto token = ReadIntegerAttrIfIn( + value, + {static_cast(SQL_AUTOCOMMIT_ON), static_cast(SQL_AUTOCOMMIT_OFF)}); + if (!token) { + return Diag::AddInvalidAttrValue(errors, "SQL_ATTR_AUTOCOMMIT"); + } + if (*token == static_cast(SQL_AUTOCOMMIT_ON)) { + return applyAutocommit(true); + } + return applyAutocommit(false); +} + +SQLRETURN TConnectionAttributes::SetAccessMode(SQLPOINTER value, TErrorManager& errors) { + const auto mode = ReadIntegerAttrIfIn(value, {SQL_MODE_READ_WRITE, SQL_MODE_READ_ONLY}); + if (!mode) { + return Diag::AddInvalidAttrValue(errors, "SQL_ATTR_ACCESS_MODE"); + } + AccessMode_ = *mode; + auto txMode = Tx::ResolveTxMode(AccessMode_, TxnIsolation_); + if (!txMode) { + return errors.AddError( + "HYC00", + 0, + AccessMode_ == SQL_MODE_READ_WRITE + ? "Transaction isolation is not supported for read-write mode" + : "Transaction isolation is not supported for read-only mode"); + } + TxMode_ = *txMode; + return SQL_SUCCESS; +} + +SQLRETURN TConnectionAttributes::SetTxnIsolation(SQLPOINTER value, TErrorManager& errors) { + const SQLUINTEGER isolation = ReadIntegerAttr(value); + if (!Tx::IsKnownTxnIsolation(isolation)) { + return Diag::AddInvalidAttrValue(errors, "SQL_ATTR_TXN_ISOLATION"); + } + auto txMode = Tx::ResolveTxMode(AccessMode_, isolation); + if (!txMode) { + return errors.AddError("HYC00", 0, "SQL_ATTR_TXN_ISOLATION value is not supported"); + } + TxnIsolation_ = isolation; + TxMode_ = *txMode; + return SQL_SUCCESS; +} + +SQLRETURN TConnectionAttributes::SetCurrentCatalog(SQLPOINTER value, SQLINTEGER stringLength, TErrorManager& errors) { + if (!value) { + return Diag::AddNullPointer(errors); + } + CurrentCatalog_ = ReadAttributeString(value, stringLength); + Catalog::NormalizePath(CurrentCatalog_); + if (CurrentCatalog_.empty()) { + return Diag::AddInvalidAttrValue(errors, "SQL_ATTR_CURRENT_CATALOG"); + } + return SQL_SUCCESS; +} + +SQLRETURN TConnectionAttributes::GetAutocommit(SQLPOINTER value) const { + return Autocommit::Get(Autocommit_, value); +} + +SQLRETURN TConnectionAttributes::GetAccessMode(SQLPOINTER value) const { + auto* out = reinterpret_cast(value); + *out = AccessMode_; + return SQL_SUCCESS; +} + +SQLRETURN TConnectionAttributes::GetTxnIsolation(SQLPOINTER value) const { + auto* out = reinterpret_cast(value); + *out = TxnIsolation_; + return SQL_SUCCESS; +} + +SQLRETURN TConnectionAttributes::GetCurrentCatalog( + SQLPOINTER value, + SQLINTEGER bufferLength, + SQLINTEGER* stringLengthPtr, + TErrorManager& errors) const { + return WriteAttributeString(CurrentCatalog_, value, bufferLength, stringLengthPtr, errors); +} + +NQuery::TTxSettings TConnectionAttributes::MakeTxSettings() const { + switch (TxMode_) { + case NQuery::TTxSettings::TS_ONLINE_RO: + return NQuery::TTxSettings::OnlineRO(); + case NQuery::TTxSettings::TS_STALE_RO: + return NQuery::TTxSettings::StaleRO(); + case NQuery::TTxSettings::TS_SNAPSHOT_RO: + return NQuery::TTxSettings::SnapshotRO(); + case NQuery::TTxSettings::TS_SNAPSHOT_RW: + return NQuery::TTxSettings::SnapshotRW(); + case NQuery::TTxSettings::TS_SERIALIZABLE_RW: + default: + return NQuery::TTxSettings::SerializableRW(); + } +} + +void TConnectionAttributes::SetCurrentCatalog(const std::string& value) { + CurrentCatalog_ = value; + Catalog::NormalizePath(CurrentCatalog_); +} + +const std::string& TConnectionAttributes::GetCurrentCatalog() const { + return CurrentCatalog_; +} + +TConnectionAttributes::TCatalogBinding TConnectionAttributes::BuildCatalogBinding(const std::string& database) const { + return Catalog::BuildBinding(CurrentCatalog_, database); +} + +TConnectionAttributes::TCatalogRoute TConnectionAttributes::ResolveCatalogRoute(const std::string& currentDatabase) const { + const TCatalogBinding binding = BuildCatalogBinding(currentDatabase); + if (binding.Catalog == binding.Database) { + return {binding.Database, std::nullopt}; + } + if (binding.RelativeCatalog) { + return {binding.Database, binding.Catalog}; + } + return {binding.Catalog, std::nullopt}; +} + +SQLRETURN TConnectionAttributes::ApplyCatalogChange( + SQLPOINTER value, + SQLINTEGER stringLength, + const std::string& currentDatabase, + std::optional& rebindDatabase, + TErrorManager& errors) { + SQLRETURN rc = SetCurrentCatalog(value, stringLength, errors); + if (rc != SQL_SUCCESS) { + return rc; + } + const TCatalogRoute route = ResolveCatalogRoute(currentDatabase); + if (route.EffectiveDatabase != currentDatabase) { + rebindDatabase = route.EffectiveDatabase; + } else { + rebindDatabase.reset(); + } + return SQL_SUCCESS; +} + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/connection_attr.h b/odbc/src/connection_attr.h new file mode 100644 index 00000000000..607f7bf929f --- /dev/null +++ b/odbc/src/connection_attr.h @@ -0,0 +1,86 @@ +#pragma once + +#include "utils/error_manager.h" + +#include + +#include +#include +#include + +#include +#include + +namespace NYdb { +namespace NOdbc { + +class TConnectionAttributes { +public: + struct TCatalogBinding { + std::string Catalog; + std::string Database; + std::optional RelativeCatalog; + }; + + struct TCatalogRoute { + std::string EffectiveDatabase; + std::optional TablePathPrefix; + }; + + SQLRETURN SetAutocommit(bool value); + bool GetAutocommit() const; + + SQLRETURN SetConnectAttr( + SQLINTEGER attr, + SQLPOINTER value, + SQLINTEGER stringLength, + const std::function& applyAutocommit, + TErrorManager& errors); + + SQLRETURN GetConnectAttr( + SQLINTEGER attr, + SQLPOINTER value, + SQLINTEGER bufferLength, + SQLINTEGER* stringLengthPtr, + TErrorManager& errors) const; + + NQuery::TTxSettings MakeTxSettings() const; + void SetCurrentCatalog(const std::string& value); + const std::string& GetCurrentCatalog() const; + TCatalogBinding BuildCatalogBinding(const std::string& database) const; + TCatalogRoute ResolveCatalogRoute(const std::string& currentDatabase) const; + SQLRETURN ApplyCatalogChange( + SQLPOINTER value, + SQLINTEGER stringLength, + const std::string& currentDatabase, + std::optional& rebindDatabase, + TErrorManager& errors); + static void NormalizeCatalogPath(std::string& path); + +private: + SQLRETURN SetAutocommit( + SQLPOINTER value, + const std::function& applyAutocommit, + TErrorManager& errors); + SQLRETURN SetAccessMode(SQLPOINTER value, TErrorManager& errors); + SQLRETURN SetTxnIsolation(SQLPOINTER value, TErrorManager& errors); + SQLRETURN SetCurrentCatalog(SQLPOINTER value, SQLINTEGER stringLength, TErrorManager& errors); + + SQLRETURN GetAutocommit(SQLPOINTER value) const; + SQLRETURN GetAccessMode(SQLPOINTER value) const; + SQLRETURN GetTxnIsolation(SQLPOINTER value) const; + SQLRETURN GetCurrentCatalog( + SQLPOINTER value, + SQLINTEGER bufferLength, + SQLINTEGER* stringLengthPtr, + TErrorManager& errors) const; + + bool Autocommit_ = true; + std::string CurrentCatalog_; + SQLUINTEGER AccessMode_ = SQL_MODE_READ_WRITE; + SQLUINTEGER TxnIsolation_ = SQL_TXN_SERIALIZABLE; + NQuery::TTxSettings::ETransactionMode TxMode_ = NQuery::TTxSettings::TS_SERIALIZABLE_RW; +}; + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/connection_attributes.cpp b/odbc/src/connection_attributes.cpp deleted file mode 100644 index 61bfa7f8caa..00000000000 --- a/odbc/src/connection_attributes.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include "connection_attributes.h" - -#include - -namespace NYdb { -namespace NOdbc { - -std::optional TConnectionAttributes::ResolveTxMode(SQLUINTEGER accessMode, SQLUINTEGER txnIsolation) { - if (accessMode == SQL_MODE_READ_ONLY) { - switch (txnIsolation) { - case SQL_TXN_READ_UNCOMMITTED: - return NQuery::TTxSettings::TS_STALE_RO; - case SQL_TXN_READ_COMMITTED: - return NQuery::TTxSettings::TS_ONLINE_RO; - case SQL_TXN_REPEATABLE_READ: - case SQL_TXN_SERIALIZABLE: - return NQuery::TTxSettings::TS_SNAPSHOT_RO; - default: - return std::nullopt; - } - } - - switch (txnIsolation) { - case SQL_TXN_REPEATABLE_READ: - case SQL_TXN_SERIALIZABLE: - return NQuery::TTxSettings::TS_SERIALIZABLE_RW; - default: - return std::nullopt; - } -} - -SQLRETURN TConnectionAttributes::SetAutocommit(bool value) { - Autocommit_ = value; - return SQL_SUCCESS; -} - -bool TConnectionAttributes::GetAutocommit() const { - return Autocommit_; -} - -SQLRETURN TConnectionAttributes::SetConnectAttr( - SQLINTEGER attr, - SQLPOINTER value, - SQLINTEGER /*stringLength*/, - const std::function& applyAutocommit, - TErrorManager& errors) { - switch (attr) { - case SQL_ATTR_AUTOCOMMIT: { - const intptr_t val = reinterpret_cast(value); - if (val == static_cast(SQL_AUTOCOMMIT_ON)) { - return applyAutocommit(true); - } - if (val == static_cast(SQL_AUTOCOMMIT_OFF)) { - return applyAutocommit(false); - } - return errors.AddError("HY024", 0, "Invalid SQL_ATTR_AUTOCOMMIT value"); - } - case SQL_ATTR_ACCESS_MODE: { - const intptr_t val = reinterpret_cast(value); - if (val == static_cast(SQL_MODE_READ_WRITE)) { - AccessMode_ = SQL_MODE_READ_WRITE; - auto txMode = ResolveTxMode(AccessMode_, TxnIsolation_); - if (!txMode) { - return errors.AddError("HYC00", 0, "Transaction isolation is not supported for read-write mode"); - } - TxMode_ = *txMode; - return SQL_SUCCESS; - } - if (val == static_cast(SQL_MODE_READ_ONLY)) { - AccessMode_ = SQL_MODE_READ_ONLY; - auto txMode = ResolveTxMode(AccessMode_, TxnIsolation_); - if (!txMode) { - return errors.AddError("HYC00", 0, "Transaction isolation is not supported for read-only mode"); - } - TxMode_ = *txMode; - return SQL_SUCCESS; - } - return errors.AddError("HY024", 0, "Invalid SQL_ATTR_ACCESS_MODE value"); - } - case SQL_ATTR_TXN_ISOLATION: { - const intptr_t val = reinterpret_cast(value); - const SQLUINTEGER isolation = static_cast(val); - auto txMode = ResolveTxMode(AccessMode_, isolation); - if (!txMode) { - return errors.AddError("HYC00", 0, "SQL_ATTR_TXN_ISOLATION value is not supported"); - } - TxnIsolation_ = isolation; - TxMode_ = *txMode; - return SQL_SUCCESS; - } - default: - return errors.AddError("HYC00", 0, "Optional feature not implemented"); - } -} - -SQLRETURN TConnectionAttributes::GetConnectAttr( - SQLINTEGER attr, - SQLPOINTER value, - SQLINTEGER /*bufferLength*/, - SQLINTEGER* stringLengthPtr, - TErrorManager& errors) const { - if (!value) { - return errors.AddError("HY009", 0, "Invalid use of null pointer"); - } - if (stringLengthPtr) { - *stringLengthPtr = 0; - } - auto* out = reinterpret_cast(value); - switch (attr) { - case SQL_ATTR_AUTOCOMMIT: - *out = GetAutocommit() ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF; - return SQL_SUCCESS; - case SQL_ATTR_ACCESS_MODE: - *out = AccessMode_; - return SQL_SUCCESS; - case SQL_ATTR_TXN_ISOLATION: - *out = TxnIsolation_; - return SQL_SUCCESS; - default: - return errors.AddError("HYC00", 0, "Optional feature not implemented"); - } -} - -NQuery::TTxSettings TConnectionAttributes::MakeTxSettings() const { - switch (TxMode_) { - case NQuery::TTxSettings::TS_ONLINE_RO: - return NQuery::TTxSettings::OnlineRO(); - case NQuery::TTxSettings::TS_STALE_RO: - return NQuery::TTxSettings::StaleRO(); - case NQuery::TTxSettings::TS_SNAPSHOT_RO: - return NQuery::TTxSettings::SnapshotRO(); - case NQuery::TTxSettings::TS_SNAPSHOT_RW: - return NQuery::TTxSettings::SnapshotRW(); - case NQuery::TTxSettings::TS_SERIALIZABLE_RW: - default: - return NQuery::TTxSettings::SerializableRW(); - } -} - -} // namespace NOdbc -} // namespace NYdb diff --git a/odbc/src/connection_attributes.h b/odbc/src/connection_attributes.h deleted file mode 100644 index 7b2f0fc7221..00000000000 --- a/odbc/src/connection_attributes.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include "utils/error_manager.h" - -#include - -#include -#include - -#include -#include - -namespace NYdb { -namespace NOdbc { - -class TConnectionAttributes { -public: - SQLRETURN SetAutocommit(bool value); - bool GetAutocommit() const; - - SQLRETURN SetConnectAttr( - SQLINTEGER attr, - SQLPOINTER value, - SQLINTEGER stringLength, - const std::function& applyAutocommit, - TErrorManager& errors); - - SQLRETURN GetConnectAttr( - SQLINTEGER attr, - SQLPOINTER value, - SQLINTEGER bufferLength, - SQLINTEGER* stringLengthPtr, - TErrorManager& errors) const; - - NQuery::TTxSettings MakeTxSettings() const; - -private: - static std::optional ResolveTxMode(SQLUINTEGER accessMode, SQLUINTEGER txnIsolation); - -private: - bool Autocommit_ = true; - SQLUINTEGER AccessMode_ = SQL_MODE_READ_WRITE; - SQLUINTEGER TxnIsolation_ = SQL_TXN_SERIALIZABLE; - NQuery::TTxSettings::ETransactionMode TxMode_ = NQuery::TTxSettings::TS_SERIALIZABLE_RW; -}; - -} // namespace NOdbc -} // namespace NYdb diff --git a/odbc/src/statement.cpp b/odbc/src/statement.cpp index 6f714d0b0bf..dd657f304ba 100644 --- a/odbc/src/statement.cpp +++ b/odbc/src/statement.cpp @@ -54,16 +54,17 @@ SQLRETURN TStatement::Execute() { } NQuery::TExecuteQueryIterator TStatement::CreateExecuteIterator(NQuery::TSession& session, const NYdb::TParams& params){ + const std::string queryText = Conn_->WrapQueryForCurrentCatalog(PreparedQuery_); if (Conn_->GetAutocommit()) { const auto txSettings = Conn_->MakeTxSettings(); if (txSettings.GetMode() == NQuery::TTxSettings::TS_SERIALIZABLE_RW) { return session.StreamExecuteQuery( - PreparedQuery_, + queryText, NQuery::TTxControl::NoTx(), params).ExtractValueSync(); } return session.StreamExecuteQuery( - PreparedQuery_, + queryText, NQuery::TTxControl::BeginTx(txSettings).CommitTx(), params).ExtractValueSync(); } @@ -73,7 +74,7 @@ NQuery::TExecuteQueryIterator TStatement::CreateExecuteIterator(NQuery::TSession Conn_->SetTx(beginTxResult.GetTransaction()); } return session.StreamExecuteQuery( - PreparedQuery_, + queryText, NQuery::TTxControl::Tx(*Conn_->GetTx()).CommitTx(false), params).ExtractValueSync(); } diff --git a/odbc/src/utils/attr.cpp b/odbc/src/utils/attr.cpp new file mode 100644 index 00000000000..1fb2a83324a --- /dev/null +++ b/odbc/src/utils/attr.cpp @@ -0,0 +1,51 @@ +#include "attr.h" +#include "diag.h" + +#include +#include + +namespace NYdb::NOdbc { + +std::string ReadAttributeString(SQLPOINTER value, SQLINTEGER stringLength) { + const char* const str = static_cast(value); + if (stringLength == SQL_NTS) { + return std::string(str); + } + if (stringLength < 0) { + return {}; + } + return std::string(str, static_cast(stringLength)); +} + +SQLRETURN WriteAttributeString( + const std::string& source, + SQLPOINTER value, + SQLINTEGER bufferLength, + SQLINTEGER* stringLengthPtr, + TErrorManager& errors) { + const SQLINTEGER length = static_cast(source.size()); + if (stringLengthPtr != nullptr) { + *stringLengthPtr = length; + } + if (value == nullptr) { + return SQL_SUCCESS; + } + if (bufferLength <= 0) { + return Diag::AddInvalidBufferLength(errors); + } + + auto* dest = static_cast(value); + const size_t maxData = static_cast(bufferLength - 1); + const size_t nCopy = std::min(source.size(), maxData); + if (nCopy > 0) { + std::memcpy(dest, source.data(), nCopy); + } + dest[nCopy] = 0; + + if (length >= bufferLength) { + return Diag::AddRightTruncated(errors); + } + return SQL_SUCCESS; +} + +} // namespace NYdb::NOdbc diff --git a/odbc/src/utils/attr.h b/odbc/src/utils/attr.h new file mode 100644 index 00000000000..34abeed42da --- /dev/null +++ b/odbc/src/utils/attr.h @@ -0,0 +1,46 @@ +#pragma once + +#include "error_manager.h" + +#include +#include +#include + +#include +#include + +namespace NYdb::NOdbc { + +std::string ReadAttributeString(SQLPOINTER value, SQLINTEGER stringLength); + +SQLRETURN WriteAttributeString( + const std::string& source, + SQLPOINTER value, + SQLINTEGER bufferLength, + SQLINTEGER* stringLengthPtr, + TErrorManager& errors); + +template +T ReadIntegerAttr(SQLPOINTER value) noexcept; + +template +std::optional ReadIntegerAttrIfIn(SQLPOINTER value, std::initializer_list allowed) noexcept; + +template +T ReadIntegerAttr(SQLPOINTER value) noexcept { + return static_cast(reinterpret_cast(value)); +} + +template +std::optional ReadIntegerAttrIfIn(SQLPOINTER value, std::initializer_list allowed) noexcept { + const T token = ReadIntegerAttr(value); + for (const T allowedValue : allowed) { + if (token == allowedValue) { + return token; + } + } + return std::nullopt; +} + + +} // namespace NYdb::NOdbc diff --git a/odbc/src/utils/diag.h b/odbc/src/utils/diag.h new file mode 100644 index 00000000000..5e2db740a07 --- /dev/null +++ b/odbc/src/utils/diag.h @@ -0,0 +1,33 @@ +#pragma once + +#include "error_manager.h" + +#include +#include + +namespace NYdb::NOdbc { +namespace Diag { + + inline SQLRETURN AddNullPointer(TErrorManager& errors) { + return errors.AddError("HY009", 0, "Invalid use of null pointer"); + } + + inline SQLRETURN AddNotImplemented(TErrorManager& errors) { + return errors.AddError("HYC00", 0, "Optional feature not implemented"); + } + + inline SQLRETURN AddInvalidAttrValue(TErrorManager& errors, std::string_view attrName) { + return errors.AddError("HY024", 0, "Invalid " + std::string(attrName) + " value"); + } + + inline SQLRETURN AddInvalidBufferLength(TErrorManager& errors) { + return errors.AddError("HY090", 0, "Invalid string or buffer length"); + } + + inline SQLRETURN AddRightTruncated(TErrorManager& errors) { + return errors.AddError("01004", 0, "String data, right truncated", SQL_SUCCESS_WITH_INFO); + } + +} + +} // namespace NYdb::NOdbc::Diag diff --git a/odbc/tests/integration/CMakeLists.txt b/odbc/tests/integration/CMakeLists.txt index 0360679931c..39128437ced 100644 --- a/odbc/tests/integration/CMakeLists.txt +++ b/odbc/tests/integration/CMakeLists.txt @@ -7,3 +7,8 @@ add_odbc_test(NAME odbc-env_it SOURCES env_it.cpp ) + +add_odbc_test(NAME odbc-attr_it + SOURCES + attr_it.cpp +) diff --git a/odbc/tests/integration/attr_it.cpp b/odbc/tests/integration/attr_it.cpp new file mode 100644 index 00000000000..514278f8e67 --- /dev/null +++ b/odbc/tests/integration/attr_it.cpp @@ -0,0 +1,244 @@ +#include "test_utils.h" + +#include +#include + +namespace { + +bool SqlStatePrefix(const std::string& diag, const char* state5) { + return diag.size() >= 5 && std::strncmp(diag.c_str(), state5, 5) == 0; +} + +void AllocEnv(SQLHENV* env) { + 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); +} + +void AllocEnvAndConnect(SQLHENV* env, SQLHDBC* dbc) { + AllocEnv(env); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_DBC, *env, dbc), SQL_SUCCESS); + SQLRETURN rc = SQLDriverConnect( + *dbc, nullptr, (SQLCHAR*)kConnStr, SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE); + CHECK_ODBC_OK(rc, *dbc, SQL_HANDLE_DBC); +} + +} // namespace + +TEST(OdbcAttrEnv, OdbcVersionAttr) { + SQLHENV env; + 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_NE(SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, nullptr, 0), SQL_SUCCESS); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcAttrEnv, OutputNtsAttr) { + SQLHENV env; + AllocEnv(&env); + ASSERT_EQ(SQLSetEnvAttr(env, SQL_ATTR_OUTPUT_NTS, (void*)SQL_TRUE, 0), SQL_SUCCESS); + ASSERT_NE(SQLSetEnvAttr(env, SQL_ATTR_OUTPUT_NTS, (void*)SQL_FALSE, 0), SQL_SUCCESS); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcAttrConn, AutocommitAttr) { + SQLHENV env; + SQLHDBC dbc; + AllocEnvAndConnect(&env, &dbc); + SQLHSTMT stmt; + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), SQL_SUCCESS); + + SQLCHAR dropQuery[] = "DROP TABLE IF EXISTS test_attr_autocommit"; + SQLCHAR createQuery[] = + "CREATE TABLE test_attr_autocommit (id Int32, value Int32, PRIMARY KEY (id))"; + SQLCHAR upsertRollbackQuery[] = "UPSERT INTO test_attr_autocommit (id, value) VALUES (1, 100)"; + SQLCHAR upsertCommitQuery[] = "UPSERT INTO test_attr_autocommit (id, value) VALUES (1, 200)"; + SQLCHAR selectQuery[] = "SELECT value FROM test_attr_autocommit WHERE id = 1"; + + CHECK_ODBC_OK(SQLExecDirect(stmt, dropQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecDirect(stmt, createQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0), dbc, SQL_HANDLE_DBC); + CHECK_ODBC_OK(SQLExecDirect(stmt, upsertRollbackQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLEndTran(SQL_HANDLE_DBC, dbc, SQL_ROLLBACK), SQL_SUCCESS); + CHECK_ODBC_OK(SQLExecDirect(stmt, selectQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_NO_DATA); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + + CHECK_ODBC_OK(SQLExecDirect(stmt, upsertCommitQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLEndTran(SQL_HANDLE_DBC, dbc, SQL_COMMIT), SQL_SUCCESS); + CHECK_ODBC_OK(SQLExecDirect(stmt, selectQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + SQLINTEGER valueInt = 0; + SQLLEN valueInd = 0; + ASSERT_EQ(SQLGetData(stmt, 1, SQL_C_LONG, &valueInt, 0, &valueInd), SQL_SUCCESS); + ASSERT_EQ(valueInt, 200); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0), dbc, SQL_HANDLE_DBC); + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcAttrConn, AccessModeAttr) { + SQLHENV env; + SQLHDBC dbc; + AllocEnvAndConnect(&env, &dbc); + + constexpr SQLUINTEGER readWriteMode = SQL_MODE_READ_WRITE; + constexpr SQLUINTEGER readOnlyMode = SQL_MODE_READ_ONLY; + SQLUINTEGER currentMode = 0; + ASSERT_EQ(SQLGetConnectAttr(dbc, SQL_ATTR_ACCESS_MODE, ¤tMode, sizeof(currentMode), nullptr), SQL_SUCCESS); + ASSERT_EQ(readWriteMode, currentMode); + + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER)readOnlyMode, 0), dbc, SQL_HANDLE_DBC); + ASSERT_EQ(SQLGetConnectAttr(dbc, SQL_ATTR_ACCESS_MODE, ¤tMode, sizeof(currentMode), nullptr), SQL_SUCCESS); + ASSERT_EQ(readOnlyMode, currentMode); + + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER)readWriteMode, 0), dbc, SQL_HANDLE_DBC); + ASSERT_EQ(SQLGetConnectAttr(dbc, SQL_ATTR_ACCESS_MODE, ¤tMode, sizeof(currentMode), nullptr), SQL_SUCCESS); + ASSERT_EQ(readWriteMode, currentMode); + + ASSERT_EQ(SQLSetConnectAttr(dbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER)9999, 0), SQL_ERROR); + EXPECT_TRUE(SqlStatePrefix(GetOdbcError(dbc, SQL_HANDLE_DBC), "HY024")); + + SQLHSTMT stmt; + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), SQL_SUCCESS); + SQLCHAR dropQuery[] = "DROP TABLE IF EXISTS test_attr_read_only"; + SQLCHAR createQuery[] = "CREATE TABLE test_attr_read_only (id Int32, PRIMARY KEY (id))"; + SQLCHAR selectOneQuery[] = "SELECT 1 AS value"; + SQLCHAR upsertQuery[] = "UPSERT INTO test_attr_read_only (id) VALUES (1)"; + CHECK_ODBC_OK(SQLExecDirect(stmt, dropQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecDirect(stmt, createQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER)readOnlyMode, 0), dbc, SQL_HANDLE_DBC); + CHECK_ODBC_OK(SQLExecDirect(stmt, selectOneQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + ASSERT_EQ(SQLExecDirect(stmt, upsertQuery, SQL_NTS), SQL_ERROR); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcAttrConn, TxnIsolationAttr) { + SQLHENV env; + SQLHDBC dbc; + AllocEnvAndConnect(&env, &dbc); + SQLHSTMT stmt; + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), SQL_SUCCESS); + SQLCHAR selectOneQuery[] = "SELECT 1 AS value"; + + SQLUINTEGER currentIsolation = 0; + ASSERT_EQ(SQLGetConnectAttr(dbc, SQL_ATTR_TXN_ISOLATION, ¤tIsolation, sizeof(currentIsolation), nullptr), SQL_SUCCESS); + ASSERT_EQ(static_cast(SQL_TXN_SERIALIZABLE), currentIsolation); + + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)SQL_TXN_REPEATABLE_READ, 0), dbc, SQL_HANDLE_DBC); + CHECK_ODBC_OK(SQLExecDirect(stmt, selectOneQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + + ASSERT_EQ(SQLSetConnectAttr(dbc, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)SQL_TXN_READ_COMMITTED, 0), SQL_ERROR); + EXPECT_TRUE(SqlStatePrefix(GetOdbcError(dbc, SQL_HANDLE_DBC), "HYC00")); + ASSERT_EQ(SQLGetConnectAttr(dbc, SQL_ATTR_TXN_ISOLATION, ¤tIsolation, sizeof(currentIsolation), nullptr), SQL_SUCCESS); + ASSERT_EQ(static_cast(SQL_TXN_REPEATABLE_READ), currentIsolation); + + // In read-only mode all four standard levels are accepted and remain executable. + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER)SQL_MODE_READ_ONLY, 0), dbc, SQL_HANDLE_DBC); + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)SQL_TXN_READ_UNCOMMITTED, 0), dbc, SQL_HANDLE_DBC); + CHECK_ODBC_OK(SQLExecDirect(stmt, selectOneQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)SQL_TXN_READ_COMMITTED, 0), dbc, SQL_HANDLE_DBC); + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)SQL_TXN_REPEATABLE_READ, 0), dbc, SQL_HANDLE_DBC); + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)SQL_TXN_SERIALIZABLE, 0), dbc, SQL_HANDLE_DBC); + + ASSERT_EQ(SQLSetConnectAttr(dbc, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)9999, 0), SQL_ERROR); + EXPECT_TRUE(SqlStatePrefix(GetOdbcError(dbc, SQL_HANDLE_DBC), "HY024")); + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcAttrConn, CurrentCatalogAttr) { + SQLHENV env; + SQLHDBC dbc; + AllocEnvAndConnect(&env, &dbc); + SQLHSTMT stmt; + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), SQL_SUCCESS); + constexpr const char* dbRoot = "/local"; + const std::string catA = std::string(dbRoot) + "/odbc_cat_a"; + const std::string catB = std::string(dbRoot) + "/odbc_cat_b"; + SQLCHAR dropAQuery[] = "DROP TABLE IF EXISTS `odbc_cat_a/probe`"; + SQLCHAR dropBQuery[] = "DROP TABLE IF EXISTS `odbc_cat_b/probe`"; + SQLCHAR createAQuery[] = + "CREATE TABLE `odbc_cat_a/probe` (id Int32, value Int32, PRIMARY KEY (id))"; + SQLCHAR createBQuery[] = + "CREATE TABLE `odbc_cat_b/probe` (id Int32, value Int32, PRIMARY KEY (id))"; + SQLCHAR upsertAQuery[] = "UPSERT INTO `odbc_cat_a/probe` (id, value) VALUES (1, 100)"; + SQLCHAR upsertBQuery[] = "UPSERT INTO `odbc_cat_b/probe` (id, value) VALUES (1, 200)"; + SQLCHAR selectAQuery[] = "SELECT value FROM `odbc_cat_a/probe` WHERE id = 1"; + SQLCHAR selectQuery[] = "SELECT value FROM probe WHERE id = 1"; + + char catalog[256] = {0}; + SQLINTEGER textLen = 0; + ASSERT_EQ(SQLGetConnectAttr(dbc, SQL_ATTR_CURRENT_CATALOG, catalog, sizeof(catalog), &textLen), SQL_SUCCESS); + ASSERT_STREQ(catalog, dbRoot); + + + CHECK_ODBC_OK(SQLExecDirect(stmt, dropAQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecDirect(stmt, dropBQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecDirect(stmt, createAQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecDirect(stmt, createBQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecDirect(stmt, upsertAQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecDirect(stmt, upsertBQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecDirect(stmt, selectAQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + SQLINTEGER valueInt = 0; + SQLLEN valueInd = 0; + + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_CURRENT_CATALOG, (SQLPOINTER)catA.c_str(), SQL_NTS), dbc, + SQL_HANDLE_DBC); + std::memset(catalog, 0, sizeof(catalog)); + textLen = 0; + ASSERT_EQ(SQLGetConnectAttr(dbc, SQL_ATTR_CURRENT_CATALOG, catalog, sizeof(catalog), &textLen), SQL_SUCCESS); + ASSERT_STREQ(catalog, catA.c_str()); + CHECK_ODBC_OK(SQLExecDirect(stmt, selectQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + ASSERT_EQ(SQLGetData(stmt, 1, SQL_C_LONG, &valueInt, 0, &valueInd), SQL_SUCCESS); + ASSERT_EQ(valueInt, 100); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + + valueInt = 0; + valueInd = 0; + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_CURRENT_CATALOG, (SQLPOINTER)catB.c_str(), SQL_NTS), dbc, + SQL_HANDLE_DBC); + std::memset(catalog, 0, sizeof(catalog)); + textLen = 0; + ASSERT_EQ(SQLGetConnectAttr(dbc, SQL_ATTR_CURRENT_CATALOG, catalog, sizeof(catalog), &textLen), SQL_SUCCESS); + ASSERT_STREQ(catalog, catB.c_str()); + CHECK_ODBC_OK(SQLExecDirect(stmt, selectQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + ASSERT_EQ(SQLGetData(stmt, 1, SQL_C_LONG, &valueInt, 0, &valueInd), SQL_SUCCESS); + ASSERT_EQ(valueInt, 200); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + + const std::string catWithSlashes = catB + "///"; + CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_CURRENT_CATALOG, (SQLPOINTER)catWithSlashes.c_str(), SQL_NTS), dbc, + SQL_HANDLE_DBC); + std::memset(catalog, 0, sizeof(catalog)); + textLen = 0; + ASSERT_EQ(SQLGetConnectAttr(dbc, SQL_ATTR_CURRENT_CATALOG, catalog, sizeof(catalog), &textLen), SQL_SUCCESS); + ASSERT_STREQ(catalog, catB.c_str()); + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + From 665d9dc76d9e5d6e504259e4a7d247a8b2fd8b11 Mon Sep 17 00:00:00 2001 From: Ylonies Date: Sat, 18 Apr 2026 17:53:17 +0000 Subject: [PATCH 19/21] stmt attr --- odbc/CMakeLists.txt | 2 + odbc/src/odbc_driver.cpp | 17 + odbc/src/statement.cpp | 67 ++- odbc/src/statement.h | 5 + odbc/src/statement_attr.cpp | 101 ++++ odbc/src/statement_attr.h | 39 ++ odbc/src/utils/convert.cpp | 74 +++ odbc/src/utils/cursor.cpp | 14 +- odbc/src/utils/escape.cpp | 444 ++++++++++++++++++ odbc/src/utils/escape.h | 9 + odbc/src/utils/sql_like.h | 49 ++ odbc/tests/integration/CMakeLists.txt | 5 + odbc/tests/integration/attr_it.cpp | 21 - odbc/tests/integration/env_it.cpp | 9 - odbc/tests/integration/stmt_attr_it.cpp | 334 +++++++++++++ odbc/tests/integration/test_utils.h | 24 +- odbc/tests/unit/CMakeLists.txt | 23 + odbc/tests/unit/escape_ut.cpp | 71 +++ odbc/tests/unit/sql_like_ut.cpp | 28 ++ .../unit/library/operation_id/CMakeLists.txt | 1 + 20 files changed, 1294 insertions(+), 43 deletions(-) create mode 100644 odbc/src/statement_attr.cpp create mode 100644 odbc/src/statement_attr.h create mode 100644 odbc/src/utils/escape.cpp create mode 100644 odbc/src/utils/escape.h create mode 100644 odbc/src/utils/sql_like.h create mode 100644 odbc/tests/integration/stmt_attr_it.cpp create mode 100644 odbc/tests/unit/escape_ut.cpp create mode 100644 odbc/tests/unit/sql_like_ut.cpp diff --git a/odbc/CMakeLists.txt b/odbc/CMakeLists.txt index 5071c42f85d..06386fd31dd 100644 --- a/odbc/CMakeLists.txt +++ b/odbc/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(ydb-odbc SHARED src/utils/attr.cpp + src/utils/escape.cpp src/utils/cursor.cpp src/utils/types.cpp src/utils/util.cpp @@ -8,6 +9,7 @@ add_library(ydb-odbc SHARED src/odbc_driver.cpp src/connection_attr.cpp src/connection.cpp + src/statement_attr.cpp src/statement.cpp src/environment.cpp ) diff --git a/odbc/src/odbc_driver.cpp b/odbc/src/odbc_driver.cpp index 6b516c63bb8..cba323453af 100644 --- a/odbc/src/odbc_driver.cpp +++ b/odbc/src/odbc_driver.cpp @@ -386,4 +386,21 @@ SQLRETURN SQL_API SQLNumResultCols(SQLHSTMT statementHandle, SQLSMALLINT* colCou }); } +SQLRETURN SQL_API SQLSetStmtAttr(SQLHSTMT statementHandle, SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER stringLength) { + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + return stmt->SetStmtAttr(attribute, value, stringLength); + }); +} + +SQLRETURN SQL_API SQLGetStmtAttr( + SQLHSTMT statementHandle, + SQLINTEGER attribute, + SQLPOINTER value, + SQLINTEGER bufferLength, + SQLINTEGER* stringLengthPtr) { + return NYdb::NOdbc::HandleOdbcExceptions(statementHandle, [&](auto* stmt) { + return stmt->GetStmtAttr(attribute, value, bufferLength, stringLengthPtr); + }); +} + } diff --git a/odbc/src/statement.cpp b/odbc/src/statement.cpp index dd657f304ba..f4b04ec0be2 100644 --- a/odbc/src/statement.cpp +++ b/odbc/src/statement.cpp @@ -3,10 +3,14 @@ #include "utils/convert.h" #include "utils/types.h" #include "utils/error_manager.h" +#include "utils/escape.h" +#include "utils/sql_like.h" #include #include +#include + namespace NYdb { namespace NOdbc { @@ -15,6 +19,7 @@ TStatement::TStatement(TConnection* conn) SQLRETURN TStatement::Prepare(const std::string& statementText) { StreamFetchError_ = false; + RowsFetched_ = 0; Cursor_.reset(); PreparedQuery_ = statementText; IsPrepared_ = true; @@ -26,6 +31,7 @@ SQLRETURN TStatement::Execute() { throw TOdbcException("HY007", 0, "No prepared statement"); } StreamFetchError_ = false; + RowsFetched_ = 0; Cursor_.reset(); auto* client = Conn_->GetClient(); if (!client) { @@ -54,19 +60,27 @@ SQLRETURN TStatement::Execute() { } NQuery::TExecuteQueryIterator TStatement::CreateExecuteIterator(NQuery::TSession& session, const NYdb::TParams& params){ - const std::string queryText = Conn_->WrapQueryForCurrentCatalog(PreparedQuery_); + const std::string sqlText = Attributes_.GetNoScanMode() == SQL_NOSCAN_ON + ? PreparedQuery_ + : RewriteOdbcEscapes(PreparedQuery_); + const std::string queryText = Conn_->WrapQueryForCurrentCatalog(sqlText); + NQuery::TExecuteQuerySettings execSettings; + const SQLUINTEGER queryTimeoutSec = Attributes_.GetQueryTimeoutSec(); + execSettings.ClientTimeout(TDuration::Seconds(queryTimeoutSec)); if (Conn_->GetAutocommit()) { const auto txSettings = Conn_->MakeTxSettings(); if (txSettings.GetMode() == NQuery::TTxSettings::TS_SERIALIZABLE_RW) { return session.StreamExecuteQuery( queryText, NQuery::TTxControl::NoTx(), - params).ExtractValueSync(); + params, + execSettings).ExtractValueSync(); } return session.StreamExecuteQuery( queryText, NQuery::TTxControl::BeginTx(txSettings).CommitTx(), - params).ExtractValueSync(); + params, + execSettings).ExtractValueSync(); } if (!Conn_->GetTx()) { auto beginTxResult = session.BeginTransaction(Conn_->MakeTxSettings()).ExtractValueSync(); @@ -76,7 +90,8 @@ NQuery::TExecuteQueryIterator TStatement::CreateExecuteIterator(NQuery::TSession return session.StreamExecuteQuery( queryText, NQuery::TTxControl::Tx(*Conn_->GetTx()).CommitTx(false), - params).ExtractValueSync(); + params, + execSettings).ExtractValueSync(); } std::optional TStatement::PrefetchFirstResultPart(NQuery::TExecuteQueryIterator& iterator){ @@ -102,10 +117,15 @@ SQLRETURN TStatement::Fetch() { Cursor_.reset(); return SQL_NO_DATA; } + const SQLULEN maxRows = Attributes_.GetMaxRows(); + if (maxRows > 0 && RowsFetched_ >= maxRows) { + return SQL_NO_DATA; + } StreamFetchError_ = false; if (!Cursor_->Fetch()) { return StreamFetchError_ ? SQL_ERROR : SQL_NO_DATA; } + ++RowsFetched_; return SQL_SUCCESS; } @@ -187,6 +207,7 @@ SQLRETURN TStatement::Columns(const std::string& catalogName, const std::string& tableName, const std::string& columnName) { ClearErrors(); + RowsFetched_ = 0; Cursor_.reset(); std::vector columns = { @@ -224,18 +245,24 @@ SQLRETURN TStatement::Columns(const std::string& catalogName, continue; } - auto status = Conn_->GetTableClient()->RetryOperationSync([path = entry.Name, &table, &columnName](NTable::TSession session) -> TStatus { + auto status = Conn_->GetTableClient()->RetryOperationSync([this, path = entry.Name, &table, &columnName](NTable::TSession session) -> TStatus { auto result = session.DescribeTable(path).ExtractValueSync(); NStatusHelpers::ThrowOnError(result); auto columns = result.GetTableDescription().GetTableColumns(); - auto columnIt = std::find_if(columns.begin(), columns.end(), [&columnName](const NTable::TTableColumn& column) { - return column.Name == columnName; + auto columnIt = std::find_if(columns.begin(), columns.end(), [&](const NTable::TTableColumn& column) { + if (Attributes_.GetMetadataId() == SQL_TRUE) { + return column.Name == columnName; + } + if (columnName.empty()) { + return column.Name.empty(); + } + return SqlLikeMatch(column.Name, columnName); }); if (columnIt == columns.end()) { - return TStatus(EStatus::NOT_FOUND, { NYdb::NIssue::TIssue("Column not found") }); + throw TOdbcException("42S22", 0, "Column not found", SQL_ERROR); } auto column = *columnIt; @@ -277,6 +304,7 @@ SQLRETURN TStatement::Tables(const std::string& catalogName, const std::string& tableName, const std::string& tableType) { ClearErrors(); + RowsFetched_ = 0; Cursor_.reset(); std::vector columns = { @@ -340,7 +368,13 @@ SQLRETURN TStatement::VisitEntry(const std::string& path, const std::string& pat } bool TStatement::IsPatternMatch(const std::string& path, const std::string& pattern) { - return path.starts_with(pattern); + if (pattern.empty()) { + return true; + } + if (Attributes_.GetMetadataId() == SQL_TRUE) { + return path == pattern; + } + return SqlLikeMatch(path, pattern); } std::optional TStatement::GetTableType(NScheme::ESchemeEntryType type) { @@ -375,9 +409,15 @@ std::optional TStatement::GetTableType(NScheme::ESchemeEntryType ty return "COORDINATION_NODE"; case NScheme::ESchemeEntryType::Unknown: return "UNKNOWN"; + case NScheme::ESchemeEntryType::SysView: + return "SYSTEM VIEW"; + case NScheme::ESchemeEntryType::Transfer: + return "TRANSFER"; case NScheme::ESchemeEntryType::Directory: case NScheme::ESchemeEntryType::SubDomain: return std::nullopt; + default: + return std::nullopt; } } @@ -387,6 +427,7 @@ SQLRETURN TStatement::Close(bool force) { } Cursor_.reset(); + RowsFetched_ = 0; PreparedQuery_.clear(); IsPrepared_ = false; ClearErrors(); @@ -422,5 +463,13 @@ SQLRETURN TStatement::NumResultCols(SQLSMALLINT* colCount) { return SQL_SUCCESS; } +SQLRETURN TStatement::SetStmtAttr(SQLINTEGER attr, SQLPOINTER value, SQLINTEGER stringLength) { + return Attributes_.SetStmtAttr(attr, value, stringLength, *this); +} + +SQLRETURN TStatement::GetStmtAttr(SQLINTEGER attr, SQLPOINTER value, SQLINTEGER bufferLength, SQLINTEGER* stringLengthPtr) { + return Attributes_.GetStmtAttr(attr, value, bufferLength, stringLengthPtr, *this); +} + } // namespace NOdbc } // namespace NYdb diff --git a/odbc/src/statement.h b/odbc/src/statement.h index f17780957bb..702fe56c71e 100644 --- a/odbc/src/statement.h +++ b/odbc/src/statement.h @@ -1,6 +1,7 @@ #pragma once #include "connection.h" +#include "statement_attr.h" #include "utils/error_manager.h" #include "utils/bindings.h" #include "utils/cursor.h" @@ -51,6 +52,8 @@ class TStatement : public TErrorManager, public IBindingFiller { SQLRETURN RowCount(SQLLEN* rowCount); SQLRETURN NumResultCols(SQLSMALLINT* colCount); + SQLRETURN SetStmtAttr(SQLINTEGER attr, SQLPOINTER value, SQLINTEGER stringLength); + SQLRETURN GetStmtAttr(SQLINTEGER attr, SQLPOINTER value, SQLINTEGER bufferLength, SQLINTEGER* stringLengthPtr); TConnection* GetConnection() { return Conn_; @@ -65,6 +68,8 @@ class TStatement : public TErrorManager, public IBindingFiller { std::vector BoundColumns_; std::vector BoundParams_; bool StreamFetchError_ = false; + SQLULEN RowsFetched_ = 0; + TStatementAttributes Attributes_; NYdb::TParams BuildParams(); diff --git a/odbc/src/statement_attr.cpp b/odbc/src/statement_attr.cpp new file mode 100644 index 00000000000..f0baad0016a --- /dev/null +++ b/odbc/src/statement_attr.cpp @@ -0,0 +1,101 @@ +#include "statement_attr.h" + +#include "utils/attr.h" +#include "utils/diag.h" + +#include + +namespace NYdb { +namespace NOdbc { + +SQLRETURN TStatementAttributes::SetStmtAttr( + SQLINTEGER attr, + SQLPOINTER value, + SQLINTEGER /*stringLength*/, + TErrorManager& errors) { + switch (attr) { + case SQL_ATTR_QUERY_TIMEOUT: { + const SQLINTEGER timeout = ReadIntegerAttr(value); + if (timeout < 0) { + return Diag::AddInvalidAttrValue(errors, "SQL_ATTR_QUERY_TIMEOUT"); + } + QueryTimeoutSec_ = static_cast(timeout); + return SQL_SUCCESS; + } + case SQL_ATTR_MAX_ROWS: { + const SQLLEN maxRows = ReadIntegerAttr(value); + if (maxRows < 0) { + return Diag::AddInvalidAttrValue(errors, "SQL_ATTR_MAX_ROWS"); + } + MaxRows_ = static_cast(maxRows); + return SQL_SUCCESS; + } + case SQL_ATTR_NOSCAN: { + const auto mode = ReadIntegerAttrIfIn(value, {SQL_NOSCAN_OFF, SQL_NOSCAN_ON}); + if (!mode) { + return Diag::AddInvalidAttrValue(errors, "SQL_ATTR_NOSCAN"); + } + NoScan_ = *mode; + return SQL_SUCCESS; + } + case SQL_ATTR_METADATA_ID: { + const auto mode = ReadIntegerAttrIfIn(value, {SQL_FALSE, SQL_TRUE}); + if (!mode) { + return Diag::AddInvalidAttrValue(errors, "SQL_ATTR_METADATA_ID"); + } + MetadataId_ = *mode; + return SQL_SUCCESS; + } + default: + return Diag::AddNotImplemented(errors); + } +} + +SQLRETURN TStatementAttributes::GetStmtAttr( + SQLINTEGER attr, + SQLPOINTER value, + SQLINTEGER /*bufferLength*/, + SQLINTEGER* stringLengthPtr, + TErrorManager& errors) const { + if (!value) { + return Diag::AddNullPointer(errors); + } + if (stringLengthPtr) { + *stringLengthPtr = 0; + } + switch (attr) { + case SQL_ATTR_QUERY_TIMEOUT: + *reinterpret_cast(value) = QueryTimeoutSec_; + return SQL_SUCCESS; + case SQL_ATTR_MAX_ROWS: + *reinterpret_cast(value) = MaxRows_; + return SQL_SUCCESS; + case SQL_ATTR_NOSCAN: + *reinterpret_cast(value) = NoScan_; + return SQL_SUCCESS; + case SQL_ATTR_METADATA_ID: + *reinterpret_cast(value) = MetadataId_; + return SQL_SUCCESS; + default: + return Diag::AddNotImplemented(errors); + } +} + +SQLUINTEGER TStatementAttributes::GetQueryTimeoutSec() const noexcept{ + return QueryTimeoutSec_; +} + +SQLULEN TStatementAttributes::GetMaxRows() const noexcept { + return MaxRows_; +} + +SQLULEN TStatementAttributes::GetNoScanMode() const noexcept { + return NoScan_; +} + +SQLULEN TStatementAttributes::GetMetadataId() const noexcept { + return MetadataId_; +} + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/statement_attr.h b/odbc/src/statement_attr.h new file mode 100644 index 00000000000..b0d6e9bd97f --- /dev/null +++ b/odbc/src/statement_attr.h @@ -0,0 +1,39 @@ +#pragma once + +#include "utils/error_manager.h" + +#include +#include + +namespace NYdb { +namespace NOdbc { + +class TStatementAttributes { +public: + SQLRETURN SetStmtAttr( + SQLINTEGER attr, + SQLPOINTER value, + SQLINTEGER stringLength, + TErrorManager& errors); + + SQLRETURN GetStmtAttr( + SQLINTEGER attr, + SQLPOINTER value, + SQLINTEGER bufferLength, + SQLINTEGER* stringLengthPtr, + TErrorManager& errors) const; + + SQLUINTEGER GetQueryTimeoutSec() const noexcept; + SQLULEN GetMaxRows() const noexcept; + SQLULEN GetNoScanMode() const noexcept; + SQLULEN GetMetadataId() const noexcept; + +private: + SQLUINTEGER QueryTimeoutSec_ = 0; + SQLULEN MaxRows_ = 0; + SQLULEN NoScan_ = SQL_NOSCAN_OFF; + SQLULEN MetadataId_ = SQL_FALSE; +}; + +} // namespace NOdbc +} // namespace NYdb diff --git a/odbc/src/utils/convert.cpp b/odbc/src/utils/convert.cpp index 224f228e498..4e415c65521 100644 --- a/odbc/src/utils/convert.cpp +++ b/odbc/src/utils/convert.cpp @@ -1,7 +1,10 @@ #include "convert.h" +#include #include +#include + namespace NYdb { namespace NOdbc { @@ -311,6 +314,28 @@ SQLRETURN ConvertColumn(TValueParser& parser, SQLSMALLINT targetType, SQLPOINTER EPrimitiveType ydbType = parser.GetPrimitiveType(); switch (targetType) { + case SQL_C_SHORT: + case SQL_C_SSHORT: + { + SQLSMALLINT v = 0; + switch (ydbType) { + case EPrimitiveType::Int16: v = 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::Bool: v = parser.GetBool() ? 1 : 0; break; + default: return SQL_ERROR; + } + if (targetValue) { + *reinterpret_cast(targetValue) = v; + } + if (strLenOrInd) { + *strLenOrInd = sizeof(SQLSMALLINT); + } + return SQL_SUCCESS; + } case SQL_C_SLONG: case SQL_C_LONG: { @@ -377,6 +402,55 @@ SQLRETURN ConvertColumn(TValueParser& parser, SQLSMALLINT targetType, SQLPOINTER case EPrimitiveType::String: str = parser.GetString(); break; case EPrimitiveType::Json: str = parser.GetJson(); break; case EPrimitiveType::JsonDocument: str = parser.GetJsonDocument(); break; + case EPrimitiveType::Date: { + const TString t = parser.GetDate().FormatGmTime("%Y-%m-%d"); + str.assign(t.data(), t.size()); + break; + } + case EPrimitiveType::Date32: { + const i32 days = parser.GetDate32(); + if (days < 0) { + return SQL_ERROR; + } + const TString t = TInstant::Days(static_cast(days)).FormatGmTime("%Y-%m-%d"); + str.assign(t.data(), t.size()); + break; + } + case EPrimitiveType::Datetime: { + const TString t = parser.GetDatetime().FormatGmTime("%Y-%m-%d %H:%M:%S"); + str.assign(t.data(), t.size()); + break; + } + case EPrimitiveType::Datetime64: { + const std::int64_t secs = parser.GetDatetime64(); + if (secs < 0) { + return SQL_ERROR; + } + const TString t = + TInstant::Seconds(static_cast(static_cast(secs))) + .FormatGmTime("%Y-%m-%d %H:%M:%S"); + str.assign(t.data(), t.size()); + break; + } + case EPrimitiveType::Timestamp: { + const TString t = parser.GetTimestamp().FormatGmTime("%Y-%m-%d %H:%M:%S"); + str.assign(t.data(), t.size()); + break; + } + case EPrimitiveType::Timestamp64: { + const std::int64_t micros = parser.GetTimestamp64(); + if (micros < 0) { + return SQL_ERROR; + } + const TString t = + TInstant::MicroSeconds(static_cast(static_cast(micros))) + .FormatGmTime("%Y-%m-%d %H:%M:%S"); + str.assign(t.data(), t.size()); + break; + } + case EPrimitiveType::TzDate: str = parser.GetTzDate(); break; + case EPrimitiveType::TzDatetime: str = parser.GetTzDatetime(); break; + case EPrimitiveType::TzTimestamp: str = parser.GetTzTimestamp(); break; default: return SQL_ERROR; } SQLLEN len = str.size(); diff --git a/odbc/src/utils/cursor.cpp b/odbc/src/utils/cursor.cpp index efbcea9a419..26ad393b03a 100644 --- a/odbc/src/utils/cursor.cpp +++ b/odbc/src/utils/cursor.cpp @@ -3,6 +3,8 @@ #include "convert.h" #include "types.h" +#include + namespace NYdb { namespace NOdbc { @@ -40,7 +42,17 @@ class TExecCursor : public ICursor { return false; } if (part.HasResultSet()) { - ResultSetParser_ = std::make_unique(part.ExtractResultSet()); + TResultSet rs = part.ExtractResultSet(); + Columns_.clear(); + Columns_.reserve(rs.ColumnsCount()); + for (const auto& col : rs.GetColumnsMeta()) { + Columns_.push_back(TColumnMeta{ + col.Name, + GetTypeId(col.Type), + 0, + IsNullable(col.Type)}); + } + ResultSetParser_ = std::make_unique(rs); } } return false; diff --git a/odbc/src/utils/escape.cpp b/odbc/src/utils/escape.cpp new file mode 100644 index 00000000000..5a9c643eb7a --- /dev/null +++ b/odbc/src/utils/escape.cpp @@ -0,0 +1,444 @@ +#include "escape.h" + +#include +#include +#include +#include +#include + +namespace NYdb::NOdbc { +namespace { + +bool EqualNoCase(std::string_view lhs, std::string_view rhs) { + return lhs.size() == rhs.size() && + std::equal(lhs.begin(), lhs.end(), rhs.begin(), [](char leftCh, char rightCh) { + return std::tolower(static_cast(leftCh)) == + std::tolower(static_cast(rightCh)); + }); +} + +void SkipLeadingWhitespace(std::string_view sql, size_t& cursor) { + const auto strEnd = sql.end(); + const auto firstNonSpace = std::find_if_not( + sql.begin() + static_cast(cursor), + strEnd, + [](unsigned char byte) { + return std::isspace(byte) != 0; + }); + cursor = static_cast(firstNonSpace - sql.begin()); +} + +bool ReadIdent(std::string_view sql, size_t& cursor, std::string_view* outIdent) { + SkipLeadingWhitespace(sql, cursor); + const size_t identStart = cursor; + const auto afterIdent = std::find_if_not( + sql.begin() + static_cast(cursor), + sql.end(), + [](unsigned char byte) { + return std::isalpha(byte) != 0 || byte == '_'; + }); + cursor = static_cast(afterIdent - sql.begin()); + if (cursor == identStart) { + return false; + } + *outIdent = std::string_view(sql.data() + identStart, cursor - identStart); + return true; +} + +bool ParseSingleQuoted(std::string_view sql, size_t& cursor, std::string* outValue) { + SkipLeadingWhitespace(sql, cursor); + if (cursor >= sql.size() || sql[cursor] != '\'') { + return false; + } + ++cursor; + outValue->clear(); + while (cursor < sql.size()) { + if (sql[cursor] == '\'') { + if (cursor + 1 < sql.size() && sql[cursor + 1] == '\'') { + outValue->push_back('\''); + cursor += 2; + continue; + } + ++cursor; + return true; + } + outValue->push_back(sql[cursor++]); + } + return false; +} + +size_t FindMatchingCloseBrace(std::string_view sql, size_t openBrace) { + if (openBrace >= sql.size() || sql[openBrace] != '{') { + return std::string_view::npos; + } + int braceDepth = 1; + for (size_t idx = openBrace + 1; idx < sql.size(); ++idx) { + if (sql[idx] == '{') { + ++braceDepth; + } else if (sql[idx] == '}') { + --braceDepth; + if (braceDepth == 0) { + return idx; + } + } + } + return std::string_view::npos; +} + +std::string NormalizeOdbcTimestampLiteral(const std::string& raw) { + std::string normalized = raw; + const auto firstSpace = std::find(normalized.begin(), normalized.end(), ' '); + if (firstSpace != normalized.end()) { + *firstSpace = 'T'; + } + if (std::find(normalized.begin(), normalized.end(), 'Z') == normalized.end()) { + normalized.push_back('Z'); + } + return normalized; +} + +std::string ToUpperAscii(std::string_view sv) { + std::string upper; + upper.resize(sv.size()); + std::transform(sv.begin(), sv.end(), upper.begin(), [](unsigned char byte) { + return static_cast(std::toupper(byte)); + }); + return upper; +} + +std::string MapSqlTypeToken(std::string_view sqlType) { + static const std::unordered_map kMap = { + {"CHAR", "Utf8"}, + {"VARCHAR", "Utf8"}, + {"LONGVARCHAR", "Utf8"}, + {"WCHAR", "Utf8"}, + {"WVARCHAR", "Utf8"}, + {"WLONGVARCHAR", "Utf8"}, + {"BIT", "Bool"}, + {"TINYINT", "Int8"}, + {"SMALLINT", "Int16"}, + {"INTEGER", "Int32"}, + {"BIGINT", "Int64"}, + {"REAL", "Float"}, + {"FLOAT", "Double"}, + {"DOUBLE", "Double"}, + {"DECIMAL", "Decimal(22, 9)"}, + {"NUMERIC", "Decimal(22, 9)"}, + {"BINARY", "String"}, + {"VARBINARY", "String"}, + {"LONGVARBINARY", "String"}, + {"DATE", "Date"}, + {"TIME", "Time"}, + {"TIMESTAMP", "Datetime"}, + {"TYPE_DATE", "Date"}, + {"TYPE_TIME", "Time"}, + {"TYPE_TIMESTAMP", "Datetime"}, + }; + std::string key = ToUpperAscii(sqlType); + const std::string kSql = "SQL_"; + if (key.size() > kSql.size() && key.compare(0, kSql.size(), kSql) == 0) { + key.erase(0, kSql.size()); + } + const auto mapped = kMap.find(key); + if (mapped != kMap.end()) { + return mapped->second; + } + return key; +} + +std::string RewriteOdbcEscapesImpl(std::string_view sql); + + +enum class OdbcBraceKind { + OutputProcedureCall, // {?= call ... } + FnBody, // {fn ...} + OjBody, // {oj ...} + DateLiteral, // {d '...'} + TimeLiteral, // {t '...'} + TimestampLiteral, // {ts '...'} + ProcedureCall, // {call ...} + LikeEscape, // {escape '...'} +}; + +struct OdbcBraceParsed { + OdbcBraceKind Kind; + std::string_view RecurseTail; + std::string QuotedValue; +}; + +std::optional TryParseOutputCallBrace(std::string_view sql, size_t parsePos, size_t closeBrace) { + if (parsePos + 1 >= sql.size() || sql[parsePos] != '?' || sql[parsePos + 1] != '=') { + return std::nullopt; + } + size_t inner = parsePos + 2; + SkipLeadingWhitespace(sql, inner); + std::string_view keyword; + if (!ReadIdent(sql, inner, &keyword) || !EqualNoCase(keyword, "call")) { + return std::nullopt; + } + SkipLeadingWhitespace(sql, inner); + if (inner > closeBrace) { + return std::nullopt; + } + OdbcBraceParsed parsed; + parsed.Kind = OdbcBraceKind::OutputProcedureCall; + parsed.RecurseTail = std::string_view(sql.data() + inner, closeBrace - inner); + return parsed; +} + +std::optional MakeRecurseTailBrace(OdbcBraceKind kind, std::string_view sql, size_t& parsePos, size_t closeBrace) { + SkipLeadingWhitespace(sql, parsePos); + if (parsePos > closeBrace) { + return std::nullopt; + } + OdbcBraceParsed parsed; + parsed.Kind = kind; + parsed.RecurseTail = std::string_view(sql.data() + parsePos, closeBrace - parsePos); + return parsed; +} + +std::optional MakeQuotedBrace(OdbcBraceKind kind, std::string_view sql, size_t& parsePos, size_t closeBrace) { + std::string quotedLit; + if (!ParseSingleQuoted(sql, parsePos, "edLit) || parsePos > closeBrace) { + return std::nullopt; + } + SkipLeadingWhitespace(sql, parsePos); + if (parsePos != closeBrace) { + return std::nullopt; + } + OdbcBraceParsed parsed; + parsed.Kind = kind; + parsed.QuotedValue = std::move(quotedLit); + return parsed; +} + +struct BraceKeywordSpec { + std::string_view Keyword; + OdbcBraceKind Kind; + bool IsQuotedLiteral; +}; + +static constexpr BraceKeywordSpec kBraceKeywordSpecs[] = { + {"fn", OdbcBraceKind::FnBody, false}, + {"oj", OdbcBraceKind::OjBody, false}, + {"d", OdbcBraceKind::DateLiteral, true}, + {"t", OdbcBraceKind::TimeLiteral, true}, + {"ts", OdbcBraceKind::TimestampLiteral, true}, + {"call", OdbcBraceKind::ProcedureCall, false}, + {"escape", OdbcBraceKind::LikeEscape, true}, +}; + +std::optional TryParseOdbcBrace(std::string_view sql, size_t openBrace, size_t closeBrace) { + size_t parsePos = openBrace + 1; + SkipLeadingWhitespace(sql, parsePos); + + if (std::optional outputCall = TryParseOutputCallBrace(sql, parsePos, closeBrace)) { + return outputCall; + } + if (parsePos + 1 < sql.size() && sql[parsePos] == '?' && sql[parsePos + 1] == '=') { + return std::nullopt; + } + + std::string_view token; + if (!ReadIdent(sql, parsePos, &token)) { + return std::nullopt; + } + + for (const BraceKeywordSpec& spec : kBraceKeywordSpecs) { + if (!EqualNoCase(token, spec.Keyword)) { + continue; + } + if (spec.IsQuotedLiteral) { + return MakeQuotedBrace(spec.Kind, sql, parsePos, closeBrace); + } + return MakeRecurseTailBrace(spec.Kind, sql, parsePos, closeBrace); + } + + return std::nullopt; +} + +void AppendRewrittenBrace(std::string& rewritten, const OdbcBraceParsed& parsed) { + switch (parsed.Kind) { + case OdbcBraceKind::OutputProcedureCall: + case OdbcBraceKind::ProcedureCall: + rewritten += "CALL "; + rewritten.append(RewriteOdbcEscapesImpl(parsed.RecurseTail)); + return; + case OdbcBraceKind::FnBody: + case OdbcBraceKind::OjBody: + rewritten.append(RewriteOdbcEscapesImpl(parsed.RecurseTail)); + return; + case OdbcBraceKind::DateLiteral: + rewritten += "CAST('"; + rewritten += parsed.QuotedValue; + rewritten += "' AS Date)"; + return; + case OdbcBraceKind::TimeLiteral: + rewritten += "CAST('"; + rewritten += parsed.QuotedValue; + rewritten += "' AS Time)"; + return; + case OdbcBraceKind::TimestampLiteral: { + const std::string normalizedTs = NormalizeOdbcTimestampLiteral(parsed.QuotedValue); + rewritten += "CAST('"; + rewritten += normalizedTs; + rewritten += "' AS Datetime)"; + return; + } + case OdbcBraceKind::LikeEscape: + rewritten += " ESCAPE '"; + rewritten += parsed.QuotedValue; + rewritten += '\''; + return; + } +} + +std::string RewriteOdbcEscapesImpl(std::string_view sql) { + std::string rewritten; + rewritten.reserve(sql.size()); + + for (size_t readPos = 0; readPos < sql.size();) { + if (sql[readPos] != '{') { + rewritten.push_back(sql[readPos++]); + continue; + } + + const size_t closeBrace = FindMatchingCloseBrace(sql, readPos); + if (closeBrace == std::string_view::npos) { + rewritten.push_back(sql[readPos++]); + continue; + } + + if (std::optional parsedBrace = TryParseOdbcBrace(sql, readPos, closeBrace)) { + AppendRewrittenBrace(rewritten, *parsedBrace); + readPos = closeBrace + 1; + continue; + } + + rewritten.push_back(sql[readPos++]); + } + + return rewritten; +} + +std::string RewriteOdbcConvertCalls(std::string_view sql); + +class TOdbcConvertCallRewriter { +public: + explicit TOdbcConvertCallRewriter(std::string_view sql) + : Sql_(sql) { + Rewritten_.reserve(sql.size()); + } + + std::string TakeResult() && { + return std::move(Rewritten_); + } + + void Run() { + while (SegmentStart_ < Sql_.size()) { + const std::optional convertKeywordPos = FindNextConvertKeyword(SegmentStart_); + if (!convertKeywordPos) { + Rewritten_.append(Sql_.substr(SegmentStart_)); + break; + } + Rewritten_.append(Sql_.substr(SegmentStart_, *convertKeywordPos - SegmentStart_)); + if (!TryRewriteConvertAt(*convertKeywordPos)) { + break; + } + } + } + +private: + static constexpr size_t kConvertTokenLen = 7; + + std::optional FindNextConvertKeyword(size_t from) const { + for (size_t probePos = from; probePos + kConvertTokenLen <= Sql_.size(); ++probePos) { + if (!EqualNoCase(Sql_.substr(probePos, kConvertTokenLen), "CONVERT")) { + continue; + } + size_t afterKeyword = probePos + kConvertTokenLen; + SkipLeadingWhitespace(Sql_, afterKeyword); + if (afterKeyword < Sql_.size() && Sql_[afterKeyword] == '(') { + return probePos; + } + } + return std::nullopt; + } + + bool TryRewriteConvertAt(size_t convertKeywordPos) { + size_t parsePos = convertKeywordPos + kConvertTokenLen; + SkipLeadingWhitespace(Sql_, parsePos); + if (parsePos >= Sql_.size() || Sql_[parsePos] != '(') { + Rewritten_.append(Sql_.substr(convertKeywordPos, kConvertTokenLen)); + SegmentStart_ = convertKeywordPos + kConvertTokenLen; + return true; + } + ++parsePos; + + int parenDepth = 1; + const size_t firstArgStart = parsePos; + std::optional typeCommaPos; + for (; parsePos < Sql_.size(); ++parsePos) { + if (Sql_[parsePos] == '(') { + ++parenDepth; + } else if (Sql_[parsePos] == ')') { + --parenDepth; + } else if (Sql_[parsePos] == ',' && parenDepth == 1) { + typeCommaPos = parsePos; + break; + } + } + if (!typeCommaPos) { + Rewritten_.append(Sql_.substr(convertKeywordPos)); + return false; + } + + const std::string_view firstArg(Sql_.data() + firstArgStart, *typeCommaPos - firstArgStart); + parsePos = *typeCommaPos + 1; + SkipLeadingWhitespace(Sql_, parsePos); + const size_t sqlTypeStart = parsePos; + const auto sqlTypeEnd = std::find_if_not( + Sql_.begin() + static_cast(parsePos), + Sql_.end(), + [](unsigned char byte) { + return std::isalpha(byte) != 0 || byte == '_'; + }); + parsePos = static_cast(sqlTypeEnd - Sql_.begin()); + const std::string_view sqlTypeToken(Sql_.data() + sqlTypeStart, parsePos - sqlTypeStart); + SkipLeadingWhitespace(Sql_, parsePos); + if (parsePos >= Sql_.size() || Sql_[parsePos] != ')') { + Rewritten_.append(Sql_.substr(convertKeywordPos)); + return false; + } + + const std::string yqlType = MapSqlTypeToken(sqlTypeToken); + Rewritten_ += "CAST("; + Rewritten_ += RewriteOdbcConvertCalls(RewriteOdbcEscapesImpl(firstArg)); + Rewritten_ += " AS "; + Rewritten_ += yqlType; + Rewritten_ += ')'; + SegmentStart_ = parsePos + 1; + return true; + } + + std::string_view Sql_; + std::string Rewritten_; + size_t SegmentStart_ = 0; +}; + +std::string RewriteOdbcConvertCalls(std::string_view sql) { + TOdbcConvertCallRewriter rewriter(sql); + rewriter.Run(); + return std::move(rewriter).TakeResult(); +} + +} // namespace + + + +std::string RewriteOdbcEscapes(const std::string& sql) { + std::string afterBraceRewrite = RewriteOdbcEscapesImpl(sql); + return RewriteOdbcConvertCalls(afterBraceRewrite); +} + +} // namespace NYdb::NOdbc diff --git a/odbc/src/utils/escape.h b/odbc/src/utils/escape.h new file mode 100644 index 00000000000..7397a128450 --- /dev/null +++ b/odbc/src/utils/escape.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace NYdb::NOdbc { + +std::string RewriteOdbcEscapes(const std::string& sql); + +} // namespace NYdb::NOdbc diff --git a/odbc/src/utils/sql_like.h b/odbc/src/utils/sql_like.h new file mode 100644 index 00000000000..f51c10ca28c --- /dev/null +++ b/odbc/src/utils/sql_like.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +namespace NYdb::NOdbc { + +// SQL LIKE — '%' is any substring, '_' is any single character. +inline bool SqlLikeMatch(std::string_view text, std::string_view pattern) { + size_t textPos = 0; + size_t patPos = 0; + size_t lastPercentPat = std::string_view::npos; + size_t textStartAfterPercent = 0; + + const size_t textLen = text.size(); + const size_t patLen = pattern.size(); + + while (textPos < textLen) { + const bool morePat = patPos < patLen; + const char patCh = morePat ? pattern[patPos] : '\0'; + + if (morePat && patCh != '%' && (patCh == '_' || patCh == text[textPos])) { + ++textPos; + ++patPos; + continue; + } + + if (morePat && patCh == '%') { + lastPercentPat = patPos++; + textStartAfterPercent = textPos; + continue; + } + + if (lastPercentPat != std::string_view::npos) { + patPos = lastPercentPat + 1; + ++textStartAfterPercent; + textPos = textStartAfterPercent; + continue; + } + + return false; + } + + while (patPos < patLen && pattern[patPos] == '%') { + ++patPos; + } + return patPos == patLen; +} + +} // namespace NYdb::NOdbc diff --git a/odbc/tests/integration/CMakeLists.txt b/odbc/tests/integration/CMakeLists.txt index 39128437ced..43925350b02 100644 --- a/odbc/tests/integration/CMakeLists.txt +++ b/odbc/tests/integration/CMakeLists.txt @@ -12,3 +12,8 @@ add_odbc_test(NAME odbc-attr_it SOURCES attr_it.cpp ) + +add_odbc_test(NAME odbc-stmt_attr_it + SOURCES + stmt_attr_it.cpp +) \ No newline at end of file diff --git a/odbc/tests/integration/attr_it.cpp b/odbc/tests/integration/attr_it.cpp index 514278f8e67..2dc30446498 100644 --- a/odbc/tests/integration/attr_it.cpp +++ b/odbc/tests/integration/attr_it.cpp @@ -3,26 +3,6 @@ #include #include -namespace { - -bool SqlStatePrefix(const std::string& diag, const char* state5) { - return diag.size() >= 5 && std::strncmp(diag.c_str(), state5, 5) == 0; -} - -void AllocEnv(SQLHENV* env) { - 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); -} - -void AllocEnvAndConnect(SQLHENV* env, SQLHDBC* dbc) { - AllocEnv(env); - ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_DBC, *env, dbc), SQL_SUCCESS); - SQLRETURN rc = SQLDriverConnect( - *dbc, nullptr, (SQLCHAR*)kConnStr, SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE); - CHECK_ODBC_OK(rc, *dbc, SQL_HANDLE_DBC); -} - -} // namespace TEST(OdbcAttrEnv, OdbcVersionAttr) { SQLHENV env; @@ -145,7 +125,6 @@ TEST(OdbcAttrConn, TxnIsolationAttr) { ASSERT_EQ(SQLGetConnectAttr(dbc, SQL_ATTR_TXN_ISOLATION, ¤tIsolation, sizeof(currentIsolation), nullptr), SQL_SUCCESS); ASSERT_EQ(static_cast(SQL_TXN_REPEATABLE_READ), currentIsolation); - // In read-only mode all four standard levels are accepted and remain executable. CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER)SQL_MODE_READ_ONLY, 0), dbc, SQL_HANDLE_DBC); CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)SQL_TXN_READ_UNCOMMITTED, 0), dbc, SQL_HANDLE_DBC); CHECK_ODBC_OK(SQLExecDirect(stmt, selectOneQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); diff --git a/odbc/tests/integration/env_it.cpp b/odbc/tests/integration/env_it.cpp index fd351d127af..952c1459ad6 100644 --- a/odbc/tests/integration/env_it.cpp +++ b/odbc/tests/integration/env_it.cpp @@ -2,15 +2,6 @@ namespace { -void AllocEnvAndConnect(SQLHENV* env, SQLHDBC* dbc) { - 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); - SQLRETURN rc = SQLDriverConnect( - *dbc, nullptr, (SQLCHAR*)kConnStr, SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE); - CHECK_ODBC_OK(rc, *dbc, SQL_HANDLE_DBC); -} - void StartManualTx(SQLHDBC dbc, SQLHSTMT* stmt) { CHECK_ODBC_OK(SQLSetConnectAttr(dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0), dbc, SQL_HANDLE_DBC); ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, stmt), SQL_SUCCESS); diff --git a/odbc/tests/integration/stmt_attr_it.cpp b/odbc/tests/integration/stmt_attr_it.cpp new file mode 100644 index 00000000000..89faf9abed0 --- /dev/null +++ b/odbc/tests/integration/stmt_attr_it.cpp @@ -0,0 +1,334 @@ +#include "test_utils.h" + +#include +#include +#include +#include + +#ifndef SQL_ATTR_METADATA_ID +#define SQL_ATTR_METADATA_ID 10029 +#endif + + +TEST(OdbcStmtAttr, QueryTimeoutAttr) { + SQLHENV env; + SQLHDBC dbc; + SQLHSTMT stmt; + AllocEnvAndConnect(&env, &dbc); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), SQL_SUCCESS); + + SQLUINTEGER timeoutSec = 1; + CHECK_ODBC_OK( + SQLSetStmtAttr(stmt, SQL_ATTR_QUERY_TIMEOUT, (SQLPOINTER)(uintptr_t)timeoutSec, 0), + stmt, + SQL_HANDLE_STMT); + + SQLCHAR longQuery[] = + "SELECT COUNT(*) FROM AS_TABLE(ListMap(ListFromRange(1u, 100000000u), ($x)->(AsStruct($x AS v))))"; + ASSERT_EQ(SQLExecDirect(stmt, longQuery, SQL_NTS), SQL_ERROR); + EXPECT_TRUE(SqlStatePrefix(GetOdbcError(stmt, SQL_HANDLE_STMT), "HYT00")); + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcStmtAttr, MaxRowsAttr) { + SQLHENV env; + SQLHDBC dbc; + SQLHSTMT stmt; + AllocEnvAndConnect(&env, &dbc); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), SQL_SUCCESS); + + SQLCHAR dropQuery[] = "DROP TABLE IF EXISTS test_attr_max_rows"; + SQLCHAR createQuery[] = + "CREATE TABLE test_attr_max_rows (id Int32, value Int32, PRIMARY KEY (id))"; + SQLCHAR upsert1Query[] = "UPSERT INTO test_attr_max_rows (id, value) VALUES (1, 10)"; + SQLCHAR upsert2Query[] = "UPSERT INTO test_attr_max_rows (id, value) VALUES (2, 20)"; + SQLCHAR selectQuery[] = "SELECT value FROM test_attr_max_rows ORDER BY id"; + + CHECK_ODBC_OK(SQLExecDirect(stmt, dropQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecDirect(stmt, createQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecDirect(stmt, upsert1Query, SQL_NTS), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecDirect(stmt, upsert2Query, SQL_NTS), stmt, SQL_HANDLE_STMT); + + const SQLULEN maxRows = 1; + CHECK_ODBC_OK( + SQLSetStmtAttr(stmt, SQL_ATTR_MAX_ROWS, (SQLPOINTER)(uintptr_t)maxRows, 0), + stmt, + SQL_HANDLE_STMT); + + SQLULEN maxRowsOut = 0; + ASSERT_EQ(SQLGetStmtAttr(stmt, SQL_ATTR_MAX_ROWS, &maxRowsOut, 0, nullptr), SQL_SUCCESS); + ASSERT_EQ(maxRowsOut, maxRows); + + CHECK_ODBC_OK(SQLExecDirect(stmt, selectQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + ASSERT_EQ(SQLFetch(stmt), SQL_NO_DATA); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcStmtAttr, NoScanAttr) { + SQLHENV env; + SQLHDBC dbc; + SQLHSTMT stmt; + AllocEnvAndConnect(&env, &dbc); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), SQL_SUCCESS); + + SQLCHAR selectEscapeFnQuery[] = "SELECT {fn ABS(-12)} AS value"; + + CHECK_ODBC_OK(SQLSetStmtAttr(stmt, SQL_ATTR_NOSCAN, (SQLPOINTER)SQL_NOSCAN_OFF, 0), stmt, SQL_HANDLE_STMT); + CHECK_ODBC_OK(SQLExecDirect(stmt, selectEscapeFnQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + SQLINTEGER valueInt = 0; + SQLLEN valueInd = 0; + ASSERT_EQ(SQLGetData(stmt, 1, SQL_C_LONG, &valueInt, 0, &valueInd), SQL_SUCCESS); + ASSERT_EQ(valueInt, 12); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + + CHECK_ODBC_OK(SQLSetStmtAttr(stmt, SQL_ATTR_NOSCAN, (SQLPOINTER)SQL_NOSCAN_ON, 0), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLExecDirect(stmt, selectEscapeFnQuery, SQL_NTS), SQL_ERROR); + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcStmtAttr, OdbcEscapeSequences) { + SQLHENV env; + SQLHDBC dbc; + SQLHSTMT stmt; + AllocEnvAndConnect(&env, &dbc); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), SQL_SUCCESS); + CHECK_ODBC_OK(SQLSetStmtAttr(stmt, SQL_ATTR_NOSCAN, (SQLPOINTER)SQL_NOSCAN_OFF, 0), stmt, SQL_HANDLE_STMT); + + { + SQLCHAR convertQuery[] = "SELECT {fn CONVERT(42, SQL_SMALLINT)} AS value"; + CHECK_ODBC_OK(SQLExecDirect(stmt, convertQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + SQLSMALLINT valueSmall = 0; + SQLLEN valueInd = 0; + ASSERT_EQ(SQLGetData(stmt, 1, SQL_C_SSHORT, &valueSmall, 0, &valueInd), SQL_SUCCESS); + ASSERT_EQ(valueSmall, 42); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + } + + { + SQLCHAR convertDoubleQuery[] = "SELECT {fn CONVERT(2.5, SQL_DOUBLE)} AS value"; + CHECK_ODBC_OK(SQLExecDirect(stmt, convertDoubleQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + double valueDouble = 0; + SQLLEN valueInd = 0; + ASSERT_EQ(SQLGetData(stmt, 1, SQL_C_DOUBLE, &valueDouble, 0, &valueInd), SQL_SUCCESS); + ASSERT_LT(std::fabs(valueDouble - 2.5), 1e-9); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + } + + { + SQLCHAR nestedFnQuery[] = "SELECT {fn {fn ABS(-10)}} AS value"; + CHECK_ODBC_OK(SQLExecDirect(stmt, nestedFnQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + SQLINTEGER valueInt = 0; + SQLLEN valueInd = 0; + ASSERT_EQ(SQLGetData(stmt, 1, SQL_C_LONG, &valueInt, 0, &valueInd), SQL_SUCCESS); + ASSERT_EQ(valueInt, 10); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + } + + { + SQLCHAR asciiLowerQuery[] = "SELECT {fn String::AsciiToLower('AbC')} AS value"; + CHECK_ODBC_OK(SQLExecDirect(stmt, asciiLowerQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + char buf[32] = {}; + SQLLEN valueInd = 0; + ASSERT_EQ(SQLGetData(stmt, 1, SQL_C_CHAR, buf, sizeof(buf), &valueInd), SQL_SUCCESS); + ASSERT_STREQ(buf, "abc"); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + } + + { + SQLCHAR dateQuery[] = "SELECT {d '2024-06-15'} AS value"; + CHECK_ODBC_OK(SQLExecDirect(stmt, dateQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + char buf[32] = {}; + SQLLEN valueInd = 0; + ASSERT_EQ(SQLGetData(stmt, 1, SQL_C_CHAR, buf, sizeof(buf), &valueInd), SQL_SUCCESS); + ASSERT_STREQ(buf, "2024-06-15"); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + } + + { + SQLCHAR tsQuery[] = "SELECT {ts '2024-06-15 14:30:00'} AS value"; + CHECK_ODBC_OK(SQLExecDirect(stmt, tsQuery, SQL_NTS), stmt, SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + char buf[64] = {}; + SQLLEN valueInd = 0; + ASSERT_EQ(SQLGetData(stmt, 1, SQL_C_CHAR, buf, sizeof(buf), &valueInd), SQL_SUCCESS); + ASSERT_STREQ(buf, "2024-06-15 14:30:00"); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + } + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcStmtAttr, MetadataIdSqlLikeForTableNames) { + SQLHENV env; + SQLHDBC dbc; + SQLHSTMT stmt; + AllocEnvAndConnect(&env, &dbc); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), SQL_SUCCESS); + + SQLCHAR ddl[] = R"( + DROP TABLE IF EXISTS test_odbc_meta_like_a; + DROP TABLE IF EXISTS test_odbc_meta_like_b; + CREATE TABLE test_odbc_meta_like_a (id Int32, PRIMARY KEY (id)); + CREATE TABLE test_odbc_meta_like_b (id Int32, PRIMARY KEY (id)); + )"; + CHECK_ODBC_OK(SQLExecDirect(stmt, ddl, SQL_NTS), stmt, SQL_HANDLE_STMT); + + SQLULEN metadataId = SQL_TRUE; + ASSERT_EQ(SQLGetStmtAttr(stmt, SQL_ATTR_METADATA_ID, &metadataId, 0, nullptr), SQL_SUCCESS); + ASSERT_EQ(metadataId, SQL_FALSE); + + const char* likePattern = "%/test_odbc_meta_like_%"; + CHECK_ODBC_OK( + SQLTables(stmt, nullptr, 0, nullptr, 0, (SQLCHAR*)likePattern, SQL_NTS, (SQLCHAR*)"TABLE", SQL_NTS), + stmt, + SQL_HANDLE_STMT); + int tableRows = 0; + while (SQLFetch(stmt) == SQL_SUCCESS) { + ++tableRows; + } + ASSERT_EQ(tableRows, 2); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + + CHECK_ODBC_OK( + SQLSetStmtAttr(stmt, SQL_ATTR_METADATA_ID, (SQLPOINTER)(uintptr_t)SQL_TRUE, 0), + stmt, + SQL_HANDLE_STMT); + ASSERT_EQ(SQLGetStmtAttr(stmt, SQL_ATTR_METADATA_ID, &metadataId, 0, nullptr), SQL_SUCCESS); + ASSERT_EQ(metadataId, SQL_TRUE); + + ASSERT_EQ( + SQLTables(stmt, nullptr, 0, nullptr, 0, (SQLCHAR*)likePattern, SQL_NTS, (SQLCHAR*)"TABLE", SQL_NTS), + SQL_ERROR); + EXPECT_TRUE(SqlStatePrefix(GetOdbcError(stmt, SQL_HANDLE_STMT), "HYC00")); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + + const std::string exactPath = "/local/test_odbc_meta_like_a"; + CHECK_ODBC_OK( + SQLTables(stmt, nullptr, 0, nullptr, 0, (SQLCHAR*)exactPath.c_str(), SQL_NTS, (SQLCHAR*)"TABLE", SQL_NTS), + stmt, + SQL_HANDLE_STMT); + tableRows = 0; + while (SQLFetch(stmt) == SQL_SUCCESS) { + ++tableRows; + } + ASSERT_EQ(tableRows, 1); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + + CHECK_ODBC_OK( + SQLSetStmtAttr(stmt, SQL_ATTR_METADATA_ID, (SQLPOINTER)(uintptr_t)SQL_FALSE, 0), + stmt, + SQL_HANDLE_STMT); + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +TEST(OdbcStmtAttr, MetadataIdSqlLikeForColumnNames) { + SQLHENV env; + SQLHDBC dbc; + SQLHSTMT stmt; + AllocEnvAndConnect(&env, &dbc); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt), SQL_SUCCESS); + + SQLCHAR ddl[] = R"( + DROP TABLE IF EXISTS test_odbc_meta_col; + CREATE TABLE test_odbc_meta_col (id Int32, value_x Int32, PRIMARY KEY (id)); + )"; + CHECK_ODBC_OK(SQLExecDirect(stmt, ddl, SQL_NTS), stmt, SQL_HANDLE_STMT); + + constexpr SQLUSMALLINT kColumnNameCol = 4; + char colName[256] = {}; + SQLLEN colInd = 0; + const std::string exactTable = "/local/test_odbc_meta_col"; + + { + CHECK_ODBC_OK( + SQLColumns( + stmt, + nullptr, + 0, + nullptr, + 0, + (SQLCHAR*)"%/test_odbc_meta_col", + SQL_NTS, + (SQLCHAR*)"val%", + SQL_NTS), + stmt, + SQL_HANDLE_STMT); + ASSERT_EQ(SQLFetch(stmt), SQL_SUCCESS); + ASSERT_EQ(SQLGetData(stmt, kColumnNameCol, SQL_C_CHAR, colName, sizeof(colName), &colInd), SQL_SUCCESS); + ASSERT_STREQ(colName, "value_x"); + ASSERT_EQ(SQLFetch(stmt), SQL_NO_DATA); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + } + + { + CHECK_ODBC_OK( + SQLSetStmtAttr(stmt, SQL_ATTR_METADATA_ID, (SQLPOINTER)(uintptr_t)SQL_TRUE, 0), + stmt, + SQL_HANDLE_STMT); + + ASSERT_EQ( + SQLColumns( + stmt, + nullptr, + 0, + nullptr, + 0, + (SQLCHAR*)"%/test_odbc_meta_col", + SQL_NTS, + (SQLCHAR*)"value_x", + SQL_NTS), + SQL_ERROR); + EXPECT_TRUE(SqlStatePrefix(GetOdbcError(stmt, SQL_HANDLE_STMT), "HYC00")); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + } + + { + ASSERT_EQ( + SQLColumns( + stmt, + nullptr, + 0, + nullptr, + 0, + (SQLCHAR*)exactTable.c_str(), + SQL_NTS, + (SQLCHAR*)"val%", + SQL_NTS), + SQL_ERROR); + EXPECT_TRUE(SqlStatePrefix(GetOdbcError(stmt, SQL_HANDLE_STMT), "42S22")); + ASSERT_EQ(SQLFreeStmt(stmt, SQL_CLOSE), SQL_SUCCESS); + } + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); +} + diff --git a/odbc/tests/integration/test_utils.h b/odbc/tests/integration/test_utils.h index c43272f0f54..950ffef9508 100644 --- a/odbc/tests/integration/test_utils.h +++ b/odbc/tests/integration/test_utils.h @@ -5,11 +5,9 @@ #include #include +#include #include -#define CHECK_ODBC_OK(rc, handle, type) \ - ASSERT_TRUE((rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO) << GetOdbcError(handle, type) - inline std::string GetOdbcError(SQLHANDLE handle, SQLSMALLINT type) { SQLCHAR sqlState[6] = {0}; SQLCHAR message[256] = {0}; @@ -22,4 +20,24 @@ inline std::string GetOdbcError(SQLHANDLE handle, SQLSMALLINT type) { return "Unknown ODBC error"; } +#define CHECK_ODBC_OK(rc, handle, type) \ + ASSERT_TRUE((rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO) << GetOdbcError(handle, type) + inline const char* kConnStr = "Driver=" ODBC_DRIVER_PATH ";Endpoint=localhost:2136;Database=/local;"; + +inline bool SqlStatePrefix(const std::string& diag, const char* state5) { + return diag.size() >= 5 && std::strncmp(diag.c_str(), state5, 5) == 0; +} + +inline void AllocEnv(SQLHENV* env) { + 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); +} + +inline void AllocEnvAndConnect(SQLHENV* env, SQLHDBC* dbc) { + AllocEnv(env); + ASSERT_EQ(SQLAllocHandle(SQL_HANDLE_DBC, *env, dbc), SQL_SUCCESS); + SQLRETURN rc = SQLDriverConnect( + *dbc, nullptr, (SQLCHAR*)kConnStr, SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE); + CHECK_ODBC_OK(rc, *dbc, SQL_HANDLE_DBC); +} diff --git a/odbc/tests/unit/CMakeLists.txt b/odbc/tests/unit/CMakeLists.txt index d1eac199615..d23e837d2f3 100644 --- a/odbc/tests/unit/CMakeLists.txt +++ b/odbc/tests/unit/CMakeLists.txt @@ -8,3 +8,26 @@ add_ydb_test(NAME odbc-convert_ut GTEST LABELS unit ) + +add_ydb_test(NAME odbc-escape_ut GTEST + SOURCES + escape_ut.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/utils/escape.cpp + INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/../../src + LINK_LIBRARIES + yutil + LABELS + unit +) + +add_ydb_test(NAME odbc-sql_like_ut GTEST + SOURCES + sql_like_ut.cpp + INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/../../src + LINK_LIBRARIES + yutil + LABELS + unit +) diff --git a/odbc/tests/unit/escape_ut.cpp b/odbc/tests/unit/escape_ut.cpp new file mode 100644 index 00000000000..60b3e582e69 --- /dev/null +++ b/odbc/tests/unit/escape_ut.cpp @@ -0,0 +1,71 @@ +#include "utils/escape.h" + +#include + +using NYdb::NOdbc::RewriteOdbcEscapes; + +TEST(OdbcEscapeRewrite, FnUnwraps) { + EXPECT_EQ(RewriteOdbcEscapes("SELECT {fn ABS(-12)} AS v"), "SELECT ABS(-12) AS v"); +} + +TEST(OdbcEscapeRewrite, FnCaseInsensitive) { + EXPECT_EQ(RewriteOdbcEscapes("{FN LOWER('A')}"), "LOWER('A')"); +} + +TEST(OdbcEscapeRewrite, OjUnwraps) { + EXPECT_EQ(RewriteOdbcEscapes("{oj LEFT OUTER JOIN t ON a=b}"), "LEFT OUTER JOIN t ON a=b"); +} + +TEST(OdbcEscapeRewrite, DateLiteral) { + EXPECT_EQ(RewriteOdbcEscapes("SELECT {d '2024-01-01'}"), "SELECT CAST('2024-01-01' AS Date)"); +} + +TEST(OdbcEscapeRewrite, TimeLiteral) { + EXPECT_EQ(RewriteOdbcEscapes("{t '14:30:00'}"), "CAST('14:30:00' AS Time)"); +} + +TEST(OdbcEscapeRewrite, TimestampLiteralNormalizesSpaceToT) { + EXPECT_EQ( + RewriteOdbcEscapes("SELECT {ts '2024-06-15 14:30:00'} AS v"), + "SELECT CAST('2024-06-15T14:30:00Z' AS Datetime) AS v"); +} + +TEST(OdbcEscapeRewrite, TimestampLiteralKeepsExistingZ) { + EXPECT_EQ( + RewriteOdbcEscapes("SELECT {ts '2024-06-15T14:30:00Z'} AS v"), + "SELECT CAST('2024-06-15T14:30:00Z' AS Datetime) AS v"); +} + +TEST(OdbcEscapeRewrite, Call) { + EXPECT_EQ(RewriteOdbcEscapes("{call sp_demo(1, 2)}"), "CALL sp_demo(1, 2)"); +} + +TEST(OdbcEscapeRewrite, OutputCallBecomesPlainCall) { + EXPECT_EQ(RewriteOdbcEscapes("{?= call sp(1)}"), "CALL sp(1)"); +} + +TEST(OdbcEscapeRewrite, EscapeClause) { + EXPECT_EQ(RewriteOdbcEscapes("LIKE 'a%' {escape '\\'}"), "LIKE 'a%' ESCAPE '\\'"); +} + +TEST(OdbcEscapeRewrite, ConvertOdbcToYqlCast) { + EXPECT_EQ( + RewriteOdbcEscapes("SELECT {fn CONVERT(42, SQL_SMALLINT)} AS v"), + "SELECT CAST(42 AS Int16) AS v"); +} + +TEST(OdbcEscapeRewrite, ConvertNestedInFn) { + EXPECT_EQ(RewriteOdbcEscapes("{fn CONVERT(x, SQL_INTEGER)}"), "CAST(x AS Int32)"); +} + +TEST(OdbcEscapeRewrite, NestedFnEscapes) { + EXPECT_EQ(RewriteOdbcEscapes("{fn {fn ABS(1)}}"), "ABS(1)"); +} + +TEST(OdbcEscapeRewrite, UnknownBraceLeftUnchanged) { + EXPECT_EQ(RewriteOdbcEscapes("{not_a_keyword 1}"), "{not_a_keyword 1}"); +} + +TEST(OdbcEscapeRewrite, EmptyInput) { + EXPECT_EQ(RewriteOdbcEscapes(""), ""); +} diff --git a/odbc/tests/unit/sql_like_ut.cpp b/odbc/tests/unit/sql_like_ut.cpp new file mode 100644 index 00000000000..e0b8d87ee01 --- /dev/null +++ b/odbc/tests/unit/sql_like_ut.cpp @@ -0,0 +1,28 @@ +#include "utils/sql_like.h" + +#include + +using NYdb::NOdbc::SqlLikeMatch; + +TEST(SqlLikeMatch, PercentMatchesSubstring) { + EXPECT_TRUE(SqlLikeMatch("/local/foo_bar", "%foo%")); + EXPECT_TRUE(SqlLikeMatch("/local/pfx_foo_sfx", "%foo%")); + EXPECT_FALSE(SqlLikeMatch("/local/other", "%foo%")); +} + +TEST(SqlLikeMatch, UnderscoreMatchesSingleChar) { + EXPECT_TRUE(SqlLikeMatch("a_c", "a_c")); + EXPECT_TRUE(SqlLikeMatch("abc", "a_c")); + EXPECT_FALSE(SqlLikeMatch("abbc", "a_c")); +} + +TEST(SqlLikeMatch, EmptyPatternMatchesOnlyEmptyText) { + EXPECT_TRUE(SqlLikeMatch("", "")); + EXPECT_FALSE(SqlLikeMatch("anything", "")); +} + +TEST(SqlLikeMatch, PercentAtEnds) { + EXPECT_TRUE(SqlLikeMatch("hello", "%hello%")); + EXPECT_TRUE(SqlLikeMatch("hello", "hel%")); + EXPECT_TRUE(SqlLikeMatch("hello", "%llo")); +} diff --git a/tests/unit/library/operation_id/CMakeLists.txt b/tests/unit/library/operation_id/CMakeLists.txt index 86d3fd5131d..06c568af97c 100644 --- a/tests/unit/library/operation_id/CMakeLists.txt +++ b/tests/unit/library/operation_id/CMakeLists.txt @@ -5,6 +5,7 @@ add_ydb_test(NAME operation_id_ut yutil cpp-testing-unittest_main library-operation_id + lib-operation_id-protos cpp-testing-unittest LABELS unit From 67bc96404478d5edeaf2c344d8a622c808427bd1 Mon Sep 17 00:00:00 2001 From: Ylonies Date: Sat, 18 Apr 2026 21:09:41 +0000 Subject: [PATCH 20/21] retry qyery for autocommit --- odbc/src/connection.cpp | 6 ++- odbc/src/connection.h | 3 +- odbc/src/statement.cpp | 97 ++++++++++++++++++++++++++++++----------- odbc/src/statement.h | 2 +- 4 files changed, 80 insertions(+), 28 deletions(-) diff --git a/odbc/src/connection.cpp b/odbc/src/connection.cpp index a52d5036f04..85724670f9d 100644 --- a/odbc/src/connection.cpp +++ b/odbc/src/connection.cpp @@ -200,10 +200,14 @@ void TConnection::SetTx(const NQuery::TTransaction& tx) { Tx_ = tx; } -void TConnection::Reset() { +void TConnection::ResetTx() { Tx_.reset(); } +void TConnection::ResetQuerySession() { + QuerySession_.reset(); +} + SQLRETURN TConnection::CommitTx() { auto status = Tx_->Commit().ExtractValueSync(); NStatusHelpers::ThrowOnError(status); diff --git a/odbc/src/connection.h b/odbc/src/connection.h index 284ac36cf65..dac7721c000 100644 --- a/odbc/src/connection.h +++ b/odbc/src/connection.h @@ -68,7 +68,8 @@ class TConnection : public TErrorManager { const std::optional& GetTx(); void SetTx(const NQuery::TTransaction& tx); - void Reset(); + void ResetTx(); + void ResetQuerySession(); SQLRETURN CommitTx(); SQLRETURN RollbackTx(); diff --git a/odbc/src/statement.cpp b/odbc/src/statement.cpp index f4b04ec0be2..16efb84aa1c 100644 --- a/odbc/src/statement.cpp +++ b/odbc/src/statement.cpp @@ -8,12 +8,42 @@ #include #include +#include +#include #include +#include + namespace NYdb { namespace NOdbc { +namespace { + NYdb::TStatus StatusFrom(const NYdb::TStatus& ydb_status) { + return NYdb::TStatus(ydb_status.GetStatus(), NYdb::NIssue::TIssues(ydb_status.GetIssues())); + } + + + NYdb::TStatus PrefetchFirstPartStatus(NQuery::TExecuteQueryIterator& iterator, std::optional* prefetchedResultPart){ + prefetchedResultPart->reset(); + while (true) { + auto part = iterator.ReadNext().ExtractValueSync(); + if (part.EOS()) { + break; + } + if (!part.IsSuccess()) { + return StatusFrom(part); + + } + if (part.HasResultSet()) { + prefetchedResultPart->emplace(std::move(part)); + return NYdb::TStatus(EStatus::SUCCESS, NYdb::NIssue::TIssues()); + } + } + return NYdb::TStatus(EStatus::SUCCESS, NYdb::NIssue::TIssues()); + } +} + TStatement::TStatement(TConnection* conn) : Conn_(conn) {} @@ -39,18 +69,41 @@ SQLRETURN TStatement::Execute() { } NYdb::TParams params = BuildParams(); - if (Conn_->GetAutocommit()){ - Conn_->Reset(); - } + std::optional iterator; + std::optional prefetchedResultPart; - auto& session = Conn_->GetOrCreateQuerySession(); + if (Conn_->GetAutocommit()){ + Conn_->ResetTx(); + Conn_->ResetQuerySession(); + const NYdb::NRetry::TRetryOperationSettings retrySettings = + MakeAutocommitRetrySettings(); + + NYdb::TStatus execStatus = client->RetryQuerySync( + [this, ¶ms, &iterator, &prefetchedResultPart](NQuery::TSession session) -> NYdb::TStatus{ + auto retry_iterator = CreateExecuteIterator(session, params); + if (!retry_iterator.IsSuccess()) { + return StatusFrom(retry_iterator); + } + std::optional retry_prefetched; + const NYdb::TStatus prefetchStatus = PrefetchFirstPartStatus(retry_iterator, &retry_prefetched); + if (!prefetchStatus.IsSuccess()) { + return prefetchStatus; + } + iterator.emplace(std::move(retry_iterator)); + prefetchedResultPart = std::move(retry_prefetched); + return NYdb::TStatus(EStatus::SUCCESS, NYdb::NIssue::TIssues()); + }, retrySettings); - auto iterator = CreateExecuteIterator(session, params); - NStatusHelpers::ThrowOnError(iterator); + NStatusHelpers::ThrowOnError(execStatus); + } else { + NQuery::TSession& session = Conn_->GetOrCreateQuerySession(); + iterator.emplace(CreateExecuteIterator(session, params)); + NStatusHelpers::ThrowOnError(*iterator); + NStatusHelpers::ThrowOnError(PrefetchFirstPartStatus(*iterator, &prefetchedResultPart)); + } - std::optional prefetchedResultPart = PrefetchFirstResultPart(iterator); if (prefetchedResultPart) { - Cursor_ = CreateExecCursor(this, std::move(iterator), std::move(prefetchedResultPart)); + Cursor_ = CreateExecCursor(this, std::move(*iterator), std::move(prefetchedResultPart)); } else { Cursor_.reset(); } @@ -59,6 +112,16 @@ SQLRETURN TStatement::Execute() { return SQL_SUCCESS; } +NYdb::NRetry::TRetryOperationSettings TStatement::MakeAutocommitRetrySettings() { + NYdb::NRetry::TRetryOperationSettings settings; + SQLUINTEGER queryTimeoutSec = Attributes_.GetQueryTimeoutSec(); + if (queryTimeoutSec > 0) { + const TDuration deadline = TDuration::Seconds(queryTimeoutSec); + settings.MaxTimeout(deadline).GetSessionClientTimeout(deadline); + } + return settings; +} + NQuery::TExecuteQueryIterator TStatement::CreateExecuteIterator(NQuery::TSession& session, const NYdb::TParams& params){ const std::string sqlText = Attributes_.GetNoScanMode() == SQL_NOSCAN_ON ? PreparedQuery_ @@ -94,23 +157,7 @@ NQuery::TExecuteQueryIterator TStatement::CreateExecuteIterator(NQuery::TSession execSettings).ExtractValueSync(); } -std::optional TStatement::PrefetchFirstResultPart(NQuery::TExecuteQueryIterator& iterator){ - std::optional prefetchedResultPart; - while (true) { - auto part = iterator.ReadNext().ExtractValueSync(); - if (part.EOS()) { - break; - } - if (!part.IsSuccess()) { - NStatusHelpers::ThrowOnError(part); - } - if (part.HasResultSet()) { - prefetchedResultPart.emplace(std::move(part)); - break; - } - } - return prefetchedResultPart; -} + SQLRETURN TStatement::Fetch() { if (!Cursor_) { diff --git a/odbc/src/statement.h b/odbc/src/statement.h index 702fe56c71e..754250bd269 100644 --- a/odbc/src/statement.h +++ b/odbc/src/statement.h @@ -74,8 +74,8 @@ class TStatement : public TErrorManager, public IBindingFiller { NYdb::TParams BuildParams(); NQuery::TExecuteQueryIterator CreateExecuteIterator(NQuery::TSession& session, const NYdb::TParams& params); - std::optional PrefetchFirstResultPart(NQuery::TExecuteQueryIterator& iterator); + NYdb::NRetry::TRetryOperationSettings MakeAutocommitRetrySettings(); 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); From 8d223d008238fd0f107d0ae5ecb902631953e22a Mon Sep 17 00:00:00 2001 From: Ylonies Date: Mon, 27 Apr 2026 19:42:05 +0300 Subject: [PATCH 21/21] getdiagfield + fixes --- odbc/src/odbc_driver.cpp | 12 +++- odbc/src/statement.cpp | 16 +++++ odbc/src/statement.h | 3 + odbc/src/utils/bindings.h | 3 +- odbc/src/utils/error_manager.cpp | 115 +++++++++++++++++++++++-------- odbc/src/utils/error_manager.h | 14 +++- 6 files changed, 129 insertions(+), 34 deletions(-) diff --git a/odbc/src/odbc_driver.cpp b/odbc/src/odbc_driver.cpp index cba323453af..97d9ffd8a5a 100644 --- a/odbc/src/odbc_driver.cpp +++ b/odbc/src/odbc_driver.cpp @@ -32,7 +32,9 @@ SQLRETURN SQL_API SQLAllocHandle(SQLSMALLINT handleType, return NYdb::NOdbc::HandleOdbcExceptions( inputHandle, [&]() { - *outputHandle = new NYdb::NOdbc::TEnvironment(); + auto* const env = new NYdb::NOdbc::TEnvironment(); + *outputHandle = env; + env->SetLastReturnCode(SQL_SUCCESS); return SQL_SUCCESS; }, NYdb::NOdbc::ENullInputHandlePolicy::Allow); @@ -43,14 +45,18 @@ SQLRETURN SQL_API SQLAllocHandle(SQLSMALLINT handleType, auto conn = std::make_unique(); conn->SetEnvironment(env); env->RegisterConnection(conn.get()); - *outputHandle = conn.release(); + auto* const raw = conn.release(); + *outputHandle = raw; + raw->SetLastReturnCode(SQL_SUCCESS); return SQL_SUCCESS; }); } case SQL_HANDLE_STMT: { return NYdb::NOdbc::HandleOdbcExceptions(inputHandle, [&](auto* conn) { auto stmt = conn->CreateStatement(); - *outputHandle = stmt.release(); + auto* const raw = stmt.release(); + *outputHandle = raw; + raw->SetLastReturnCode(SQL_SUCCESS); return SQL_SUCCESS; }); } diff --git a/odbc/src/statement.cpp b/odbc/src/statement.cpp index 16efb84aa1c..5d6bb38f152 100644 --- a/odbc/src/statement.cpp +++ b/odbc/src/statement.cpp @@ -518,5 +518,21 @@ SQLRETURN TStatement::GetStmtAttr(SQLINTEGER attr, SQLPOINTER value, SQLINTEGER return Attributes_.GetStmtAttr(attr, value, bufferLength, stringLengthPtr, *this); } +SQLRETURN TStatement::GetDiagField( + SQLSMALLINT recNumber, + SQLSMALLINT diagIdentifier, + SQLPOINTER diagInfoPtr, + SQLSMALLINT bufferLength, + SQLSMALLINT* stringLengthPtr) { + if (recNumber == 0 && diagIdentifier == SQL_DIAG_ROW_COUNT) { + if (!diagInfoPtr) { + return SQL_ERROR; + } + *reinterpret_cast(diagInfoPtr) = -1; + return SQL_SUCCESS; + } + return TErrorManager::GetDiagField(recNumber, diagIdentifier, diagInfoPtr, bufferLength, stringLengthPtr); +} + } // namespace NOdbc } // namespace NYdb diff --git a/odbc/src/statement.h b/odbc/src/statement.h index 754250bd269..9f2eb8ade64 100644 --- a/odbc/src/statement.h +++ b/odbc/src/statement.h @@ -55,6 +55,9 @@ class TStatement : public TErrorManager, public IBindingFiller { SQLRETURN SetStmtAttr(SQLINTEGER attr, SQLPOINTER value, SQLINTEGER stringLength); SQLRETURN GetStmtAttr(SQLINTEGER attr, SQLPOINTER value, SQLINTEGER bufferLength, SQLINTEGER* stringLengthPtr); + SQLRETURN GetDiagField(SQLSMALLINT recNumber, SQLSMALLINT diagIdentifier, SQLPOINTER diagInfoPtr, SQLSMALLINT bufferLength, + SQLSMALLINT* stringLengthPtr) override; + TConnection* GetConnection() { return Conn_; } diff --git a/odbc/src/utils/bindings.h b/odbc/src/utils/bindings.h index 443d9787d70..2480f5367af 100644 --- a/odbc/src/utils/bindings.h +++ b/odbc/src/utils/bindings.h @@ -31,8 +31,7 @@ struct TBoundColumn { class IBindingFiller { public: virtual void FillBoundColumns() = 0; - virtual void OnStreamPartError(const TStatus& status) { - (void)status; + virtual void OnStreamPartError([[maybe_unused]] const TStatus& status) { } virtual ~IBindingFiller() = default; diff --git a/odbc/src/utils/error_manager.cpp b/odbc/src/utils/error_manager.cpp index fbb577e3824..92c8ec1750f 100644 --- a/odbc/src/utils/error_manager.cpp +++ b/odbc/src/utils/error_manager.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include namespace NYdb { namespace NOdbc { @@ -56,13 +58,57 @@ namespace { } } // namespace +namespace { + +SQLRETURN WriteDiagCStr( + const std::string& str, + SQLPOINTER diagInfoPtr, + SQLSMALLINT bufferLength, + SQLSMALLINT* stringLengthPtr, + bool sqlStateField = false) { + std::string storage; + const std::string* src = &str; + if (sqlStateField) { + storage = str; + if (storage.size() < 5) { + storage.append(5U - storage.size(), ' '); + } else { + storage.resize(5U); + } + src = &storage; + } + if (!diagInfoPtr) { + return SQL_ERROR; + } + if (bufferLength < 0) { + return SQL_ERROR; + } + const size_t fullLen = src->size(); + if (stringLengthPtr) { + *stringLengthPtr = static_cast(std::min(fullLen, 0x7FFFU)); + } + if (bufferLength == 0) { + return fullLen == 0 ? SQL_SUCCESS : SQL_SUCCESS_WITH_INFO; + } + auto* out = static_cast(diagInfoPtr); + const size_t maxData = static_cast(bufferLength - 1U); + const size_t copyLen = std::min(fullLen, maxData); + std::memcpy(out, src->data(), copyLen); + out[copyLen] = 0; + return (fullLen > maxData) ? SQL_SUCCESS_WITH_INFO : SQL_SUCCESS; +} + +} // namespace + SQLRETURN TErrorManager::AddError(const std::string& sqlState, SQLINTEGER nativeError, const std::string& message, SQLRETURN returnCode) { Errors_.push_back({sqlState, nativeError, message, returnCode}); + LastReturnCode_ = returnCode; return returnCode; } SQLRETURN TErrorManager::AddError(const TOdbcException& ex) { Errors_.push_back({ex.GetSqlState(), ex.GetNativeError(), ex.GetMessage(), ex.GetReturnCode()}); + LastReturnCode_ = ex.GetReturnCode(); return ex.GetReturnCode(); } @@ -73,6 +119,7 @@ SQLRETURN TErrorManager::AddError(const TStatus& status) { message += ": " + status.GetIssues().ToString(); } Errors_.push_back({mapping.sqlState, static_cast(status.GetStatus()), message, mapping.returnCode}); + LastReturnCode_ = mapping.returnCode; return mapping.returnCode; } @@ -104,19 +151,26 @@ SQLRETURN TErrorManager::GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQ return SQL_SUCCESS; } -SQLRETURN TErrorManager::GetDiagField(SQLSMALLINT recNumber, SQLSMALLINT diagIdentifier, - SQLPOINTER diagInfoPtr, SQLSMALLINT bufferLength, SQLSMALLINT* stringLengthPtr) { +SQLRETURN TErrorManager::GetDiagField(SQLSMALLINT recNumber, SQLSMALLINT diagIdentifier, SQLPOINTER diagInfoPtr, + SQLSMALLINT bufferLength, SQLSMALLINT* stringLengthPtr) { const SQLSMALLINT count = static_cast(Errors_.size()); - + if (diagInfoPtr == nullptr) { + return SQL_ERROR; + } if (recNumber == 0) { - if (diagIdentifier == SQL_DIAG_NUMBER) { - if (!diagInfoPtr) { - return SQL_ERROR; + switch (diagIdentifier) { + case SQL_DIAG_RETURNCODE: + *static_cast(diagInfoPtr) = LastReturnCode_; + return SQL_SUCCESS; + case SQL_DIAG_NUMBER: { + *static_cast(diagInfoPtr) = static_cast(count); + return SQL_SUCCESS; } - *static_cast(diagInfoPtr) = count; - return SQL_SUCCESS; + case SQL_DIAG_ROW_COUNT: + return SQL_ERROR; + default: + return SQL_ERROR; } - return SQL_NO_DATA; } if (recNumber < 1 || recNumber > count) { @@ -126,28 +180,28 @@ SQLRETURN TErrorManager::GetDiagField(SQLSMALLINT recNumber, SQLSMALLINT diagIde const auto& err = Errors_[recNumber - 1]; switch (diagIdentifier) { case SQL_DIAG_SQLSTATE: - if (!diagInfoPtr) { - return SQL_ERROR; - } - strncpy((char*)diagInfoPtr, err.SqlState.c_str(), 6); - return SQL_SUCCESS; - case SQL_DIAG_NATIVE: - if (!diagInfoPtr) { - return SQL_ERROR; - } + return WriteDiagCStr(err.SqlState, diagInfoPtr, bufferLength, stringLengthPtr, true); + case SQL_DIAG_NATIVE: { *static_cast(diagInfoPtr) = err.NativeError; return SQL_SUCCESS; + } case SQL_DIAG_MESSAGE_TEXT: - if (!diagInfoPtr || bufferLength <= 0) { - return SQL_ERROR; - } - strncpy((char*)diagInfoPtr, err.Message.c_str(), bufferLength); - if (stringLengthPtr) { - *stringLengthPtr = static_cast(err.Message.size()); - } + return WriteDiagCStr(err.Message, diagInfoPtr, bufferLength, stringLengthPtr); + case SQL_DIAG_CLASS_ORIGIN: + return WriteDiagCStr("ODBC 3.0", diagInfoPtr, bufferLength, stringLengthPtr); + case SQL_DIAG_SUBCLASS_ORIGIN: + return WriteDiagCStr("ODBC 3.0", diagInfoPtr, bufferLength, stringLengthPtr); + case SQL_DIAG_CONNECTION_NAME: + case SQL_DIAG_SERVER_NAME: + return WriteDiagCStr("", diagInfoPtr, bufferLength, stringLengthPtr); + case SQL_DIAG_COLUMN_NUMBER: + *static_cast(diagInfoPtr) = SQL_COLUMN_NUMBER_UNKNOWN; + return SQL_SUCCESS; + case SQL_DIAG_ROW_NUMBER: + *static_cast(diagInfoPtr) = SQL_ROW_NUMBER_UNKNOWN; return SQL_SUCCESS; default: - return SQL_NO_DATA; + return SQL_ERROR; } } @@ -160,8 +214,15 @@ SQLRETURN HandleOdbcExceptions( } try { - return func(); + const SQLRETURN r = func(); + if (handlePtr) { + static_cast(handlePtr)->SetLastReturnCode(r); + } + return r; } catch (...) { + if (handlePtr) { + static_cast(handlePtr)->SetLastReturnCode(SQL_ERROR); + } return SQL_ERROR; } } diff --git a/odbc/src/utils/error_manager.h b/odbc/src/utils/error_manager.h index 5f72a69f563..9f91fab8a1d 100644 --- a/odbc/src/utils/error_manager.h +++ b/odbc/src/utils/error_manager.h @@ -64,13 +64,21 @@ class TErrorManager { void ClearErrors(); + void SetLastReturnCode(SQLRETURN code) { + LastReturnCode_ = code; + } + [[nodiscard]] SQLRETURN GetLastReturnCode() const { + return LastReturnCode_; + } + SQLRETURN GetDiagRec(SQLSMALLINT recNumber, SQLCHAR* sqlState, SQLINTEGER* nativeError, SQLCHAR* messageText, SQLSMALLINT bufferLength, SQLSMALLINT* textLength); - SQLRETURN GetDiagField(SQLSMALLINT recNumber, SQLSMALLINT diagIdentifier, + virtual SQLRETURN GetDiagField(SQLSMALLINT recNumber, SQLSMALLINT diagIdentifier, SQLPOINTER diagInfoPtr, SQLSMALLINT bufferLength, SQLSMALLINT* stringLengthPtr); private: TErrorList Errors_; + SQLRETURN LastReturnCode_ = SQL_SUCCESS; }; enum class ENullInputHandlePolicy : unsigned char { @@ -86,7 +94,9 @@ SQLRETURN HandleOdbcExceptions(SQLHANDLE handlePtr, std::function(handlePtr); try { - return func(handle); + const SQLRETURN ret = func(handle); + handle->SetLastReturnCode(ret); + return ret; } catch (const NStatusHelpers::TYdbErrorException& ex) { return handle->AddError(ex.GetStatus()); } catch (const TOdbcException& ex) {