From c37dccd22b26ca4d947fa0c904585e4592a582e9 Mon Sep 17 00:00:00 2001 From: Ylonies Date: Tue, 7 Apr 2026 14:12:28 +0000 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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