Skip to content

Odbc driver #506

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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()
Expand Down
12 changes: 9 additions & 3 deletions cmake/common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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,$<TARGET_FILE:$<INSTALL_INTERFACE:YDB-CPP-SDK::>${TgtName}>")
else()
Expand Down Expand Up @@ -182,7 +182,7 @@ endfunction()

function(_ydb_sdk_add_library Tgt)
cmake_parse_arguments(ARG
"INTERFACE" "" ""
"INTERFACE;OBJECT;SHARED" "" ""
${ARGN}
)

Expand All @@ -192,6 +192,12 @@ function(_ydb_sdk_add_library Tgt)
set(libraryMode "INTERFACE")
set(includeMode "INTERFACE")
endif()
if (ARG_OBJECT)
set(libraryMode "OBJECT")
endif()
if (ARG_SHARED)
set(libraryMode "SHARED")
endif()
add_library(${Tgt} ${libraryMode})
target_include_directories(${Tgt} ${includeMode}
$<BUILD_INTERFACE:${YDB_SDK_SOURCE_DIR}>
Expand All @@ -201,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)
Expand Down Expand Up @@ -255,4 +262,3 @@ function(_ydb_sdk_validate_public_headers)
)
target_include_directories(validate_public_interface PUBLIC ${YDB_SDK_BINARY_DIR}/__validate_headers_dir/include)
endfunction()

4 changes: 4 additions & 0 deletions cmake/external_libs.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 32 additions & 0 deletions cmake/testing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,35 @@ 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
integration
${ODBC_TEST_LABELS}
)

target_compile_definitions(${ODBC_TEST_NAME}
PRIVATE
ODBC_DRIVER_PATH="$<TARGET_FILE:ydb-odbc>"
)

add_dependencies(${ODBC_TEST_NAME} ydb-odbc)
endfunction()
endif()
54 changes: 54 additions & 0 deletions odbc/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
add_library(ydb-odbc SHARED
src/utils/cursor.cpp
src/utils/types.cpp
src/utils/util.cpp
src/utils/convert.cpp
src/odbc_driver.cpp
src/connection.cpp
src/statement.cpp
src/environment.cpp
)

target_include_directories(ydb-odbc
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/src
${ODBC_INCLUDE_DIRS}
)

target_link_libraries(ydb-odbc
PRIVATE
YDB-CPP-SDK::Query
YDB-CPP-SDK::Table
YDB-CPP-SDK::Scheme
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}
)

install(DIRECTORY include/
DESTINATION include/ydb-odbc
)

add_subdirectory(examples)
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}
)
80 changes: 80 additions & 0 deletions odbc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# YDB ODBC Driver

ODBC driver for YDB.

## Requirements

- CMake 3.10 or higher
- C/C++ compiler with C11 and C++20 support
- YDB C++ SDK
- unixODBC (for Linux/macOS)

## Build

```bash
cmake -DYDB_SDK_ODBC=1 --preset release-clang
cmake --build --preset default
```

## Configuration

1. Make sure the driver is registered:
```bash
odbcinst -q -d
```

2. Check available data sources:
```bash
odbcinst -q -s
```

3. Edit `/etc/odbc.ini` to configure the connection:
```ini
[YDB]
Driver=YDB
Description=YDB Database Connection
Server=your-server:port
Database=/path/to/database
```

## Usage

Example of connecting via isql:
```bash
isql -v YDB
```

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);
```

## Parameters

Use names $p1, $p2, ... for parameter names

## License

Apache License 2.0
2 changes: 2 additions & 0 deletions odbc/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
add_subdirectory(basic)
add_subdirectory(scheme)
14 changes: 14 additions & 0 deletions odbc/examples/basic/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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="$<TARGET_FILE:ydb-odbc>"
)

add_dependencies(odbc_basic ydb-odbc)
132 changes: 132 additions & 0 deletions odbc/examples/basic/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#include <sql.h>
#include <sqlext.h>

#include <iostream>

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 id, data from test_table WHERE id == $p1;
)";

int64_t paramValue = 1;
SQLLEN paramInd = 0;
ret = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, 0, 0, &paramValue, 0, &paramInd);
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;

SQLCloseCursor(hstmt);
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
SQLDisconnect(hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
SQLFreeHandle(SQL_HANDLE_ENV, henv);

return 0;
}
Loading
Loading