diff --git a/include/ogawayama/stub/api.h b/include/ogawayama/stub/api.h index e76f4c9..6fa3f2a 100644 --- a/include/ogawayama/stub/api.h +++ b/include/ogawayama/stub/api.h @@ -347,6 +347,13 @@ class Connection : public manager::message::Receiver { */ [[nodiscard]] manager::message::Status receive_drop_index(manager::metadata::ObjectIdType object_id) const override; + /** + * @brief get the error of the last SQL executed + * @param code returns the error code reported by the tsurugidb + * @return error code defined in error_code.h + */ + ErrorCode tsurugi_error(tsurugi_error_code& code); + private: std::unique_ptr impl_; diff --git a/include/ogawayama/stub/error_code.h b/include/ogawayama/stub/error_code.h index d686415..c0408a7 100644 --- a/include/ogawayama/stub/error_code.h +++ b/include/ogawayama/stub/error_code.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 Project Tsurugi. + * Copyright 2019-2024 Project Tsurugi. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,9 @@ */ #pragma once +#include +#include + namespace ogawayama::stub { /** @@ -87,6 +90,10 @@ enum class ErrorCode { */ TRANSACTION_ALREADY_STARTED, + /** + * @brief Encountered server error, where you can obtain error detail with Connection::tsurugi_error() API. + */ + SERVER_ERROR, }; constexpr std::string_view error_name(ErrorCode code) { @@ -107,4 +114,22 @@ constexpr std::string_view error_name(ErrorCode code) { } } +struct tsurugi_error_code { + public: + enum class tsurugi_error_type : std::int32_t { + none = 0, + framework_error = 1, + sql_error = 2 + }; + // SQL or framework error + tsurugi_error_type type{}; //NOLINT(misc-non-private-member-variables-in-classes) + // see SqlServiceCode.java for SQL error + std::uint32_t code{}; //NOLINT(misc-non-private-member-variables-in-classes) + std::string name{}; //NOLINT(misc-non-private-member-variables-in-classes) + std::string detail{}; //NOLINT(misc-non-private-member-variables-in-classes) + std::string supplemental_text{}; //NOLINT(misc-non-private-member-variables-in-classes) + + tsurugi_error_code() = default; +}; + } // namespace ogawayama::stub diff --git a/src/jogasaki/proto/sql/response.proto b/src/jogasaki/proto/sql/response.proto index 5dda727..a1c7c42 100644 --- a/src/jogasaki/proto/sql/response.proto +++ b/src/jogasaki/proto/sql/response.proto @@ -107,9 +107,6 @@ message Explain { // request is successfully completed. Success success = 11; - // deprecated: The result output string of the explain. - string output = 1; - Error error = 2; } } @@ -144,20 +141,6 @@ message DescribeTable { } } -// execute a statement with 2-D parameter table. -message Batch { - reserved 1 to 10; - - // the response body. - oneof result { - // request is successfully completed. - Success success = 11; - - // engine error was occurred. - Error error = 12; - } -} - message Identifier { // the label. string label = 1; @@ -295,7 +278,7 @@ message Response { ExecuteQuery execute_query = 4; Explain explain = 5; DescribeTable describe_table = 6; - Batch batch = 7; + // 7 is no longer used. ListTables list_tables = 8; GetSearchPath get_search_path = 9; GetErrorInfo get_error_info = 10; diff --git a/src/ogawayama/stub/connection.cpp b/src/ogawayama/stub/connection.cpp index 332f7b0..ecc8f24 100644 --- a/src/ogawayama/stub/connection.cpp +++ b/src/ogawayama/stub/connection.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 Project Tsurugi. + * Copyright 2019-2024 Project Tsurugi. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ #include #include "prepared_statementImpl.h" +#include "ogawayama/transport/tsurugi_error.h" #include "connectionImpl.h" namespace ogawayama::stub { @@ -76,17 +77,22 @@ ErrorCode Connection::Impl::hello() */ ErrorCode Connection::Impl::begin(TransactionPtr& transaction) { - ::jogasaki::proto::sql::request::Begin request{}; - auto response_opt = transport_.send(request); - if (!response_opt) { - return ErrorCode::SERVER_FAILURE; - } - auto response_begin = response_opt.value(); - if (response_begin.has_success()) { - transaction = std::make_unique(std::make_unique(this, transport_, response_begin.success().transaction_handle())); - return ErrorCode::OK; + try { + ::jogasaki::proto::sql::request::Begin request{}; + + auto response_opt = transport_.send(request); + if (!response_opt) { + return ErrorCode::SERVER_FAILURE; + } + auto response_begin = response_opt.value(); + if (response_begin.has_success()) { + transaction = std::make_unique(std::make_unique(this, transport_, response_begin.success().transaction_handle())); + return ErrorCode::OK; + } + return ErrorCode::SERVER_ERROR; + } catch (std::runtime_error &e) { + return ErrorCode::SERVER_ERROR; } - return ErrorCode::SERVER_FAILURE; } /** @@ -143,16 +149,20 @@ ErrorCode Connection::Impl::begin(const boost::property_tree::ptree& option, Tra } } - auto response_opt = transport_.send(request); - if (!response_opt) { + try { + auto response_opt = transport_.send(request); + if (!response_opt) { + return ErrorCode::SERVER_FAILURE; + } + auto response_begin = response_opt.value(); + if (response_begin.has_success()) { + transaction = std::make_unique(std::make_unique(this, transport_, response_begin.success().transaction_handle())); + return ErrorCode::OK; + } return ErrorCode::SERVER_FAILURE; + } catch (std::runtime_error &e) { + return ErrorCode::SERVER_ERROR; } - auto response_begin = response_opt.value(); - if (response_begin.has_success()) { - transaction = std::make_unique(std::make_unique(this, transport_, response_begin.success().transaction_handle())); - return ErrorCode::OK; - } - return ErrorCode::SERVER_FAILURE; } /** @@ -207,19 +217,23 @@ ErrorCode Connection::Impl::prepare(std::string_view sql, const placeholders_typ } } - auto response_opt = transport_.send(request); - if (!response_opt) { - return ErrorCode::SERVER_FAILURE; - } - auto response_prepare = response_opt.value(); - if (response_prepare.has_prepared_statement_handle()) { - auto& psh = response_prepare.prepared_statement_handle(); - std::size_t id = psh.handle(); - bool has_result_records = psh.has_result_records(); - prepared = std::make_unique(std::make_unique(this, id, has_result_records)); - return ErrorCode::OK; + try { + auto response_opt = transport_.send(request); + if (!response_opt) { + return ErrorCode::SERVER_FAILURE; + } + auto response_prepare = response_opt.value(); + if (response_prepare.has_prepared_statement_handle()) { + auto& psh = response_prepare.prepared_statement_handle(); + std::size_t id = psh.handle(); + bool has_result_records = psh.has_result_records(); + prepared = std::make_unique(std::make_unique(this, id, has_result_records)); + return ErrorCode::OK; + } + return ErrorCode::SERVER_ERROR; + } catch (std::runtime_error &e) { + return ErrorCode::SERVER_ERROR; } - return ErrorCode::SERVER_FAILURE; } static inline @@ -448,6 +462,60 @@ ErrorCode Connection::Impl::end_ddl() return ERROR_CODE::SERVER_FAILURE; // service returns std::nullopt } +static inline bool handle_sql_error(ogawayama::stub::tsurugi_error_code& code, ::jogasaki::proto::sql::response::Error& sql_error) { + if (auto itr = ogawayama::transport::error_map.find(sql_error.code()); itr != ogawayama::transport::error_map.end()) { + code.type = tsurugi_error_code::tsurugi_error_type::sql_error; + code.code = itr->second.second; + code.name = itr->second.first; + code.detail = sql_error.detail(); + code.supplemental_text = sql_error.supplemental_text(); + return true; + } + return false; +} + +static inline bool handle_framework_error(ogawayama::stub::tsurugi_error_code& code, ::tateyama::proto::diagnostics::Record& framework_error) { + if (auto itr = ogawayama::transport::framework_error_map.find(framework_error.code()); itr != ogawayama::transport::framework_error_map.end()) { + code.type = tsurugi_error_code::tsurugi_error_type::framework_error; + code.code = itr->second.second; + code.name = itr->second.first; + code.detail = framework_error.message(); + return true; + } + return false; +} + +/** + * @brief get the error of the last SQL executed + */ +ErrorCode Connection::Impl::tsurugi_error(tsurugi_error_code& code) +{ + switch(transport_.last_header().payload_type()) { + case ::tateyama::proto::framework::response::Header_PayloadType::Header_PayloadType_UNKNOWN: + break; + case ::tateyama::proto::framework::response::Header_PayloadType::Header_PayloadType_SERVER_DIAGNOSTICS: + { + auto frame_error = transport_.last_framework_error(); + if (handle_framework_error(code, frame_error)) { + return ErrorCode::OK; + } + break; + } + case ::tateyama::proto::framework::response::Header_PayloadType::Header_PayloadType_SERVICE_RESULT: + { + auto sql_error = transport_.last_sql_error(); + if (sql_error.code() == ::jogasaki::proto::sql::error::Code::CODE_UNSPECIFIED) { + code.type = tsurugi_error_code::tsurugi_error_type::none; + return ErrorCode::OK; + } + if (handle_sql_error(code, sql_error)) { + return ErrorCode::OK; + } + break; + } + } + return ErrorCode::UNKNOWN; +} /** * @brief constructor of Connection class @@ -528,4 +596,12 @@ manager::message::Status Connection::receive_drop_index(const manager::metadata: return {reply == ErrorCode::OK ? manager::message::ErrorCode::SUCCESS : manager::message::ErrorCode::FAILURE, static_cast(reply)}; } +/** + * @brief get the error of the last SQL executed + */ +ErrorCode Connection::tsurugi_error(tsurugi_error_code& code) +{ + return impl_->tsurugi_error(code); +} + } // namespace ogawayama::stub diff --git a/src/ogawayama/stub/connectionImpl.h b/src/ogawayama/stub/connectionImpl.h index a4d6149..d675f42 100644 --- a/src/ogawayama/stub/connectionImpl.h +++ b/src/ogawayama/stub/connectionImpl.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 Project Tsurugi. + * Copyright 2019-2024 Project Tsurugi. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -99,6 +99,13 @@ class Connection::Impl */ ErrorCode end_ddl(); + /** + * @brief get the error of the last SQL executed + * @param code returns the error code reported by the tsurugidb + * @return error code defined in error_code.h + */ + ErrorCode tsurugi_error(tsurugi_error_code& code); + private: Stub::Impl* manager_; std::string session_id_; diff --git a/src/ogawayama/stub/transaction.cpp b/src/ogawayama/stub/transaction.cpp index b51730d..e3acf03 100644 --- a/src/ogawayama/stub/transaction.cpp +++ b/src/ogawayama/stub/transaction.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 Project Tsurugi. + * Copyright 2019-2024 Project Tsurugi. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,18 +67,23 @@ ErrorCode Transaction::Impl::execute_statement(std::string_view statement, std:: ::jogasaki::proto::sql::request::ExecuteStatement request{}; *(request.mutable_transaction_handle()) = transaction_handle_; *(request.mutable_sql()) = statement; - auto response_opt = transport_.send(request); - request.clear_sql(); - request.clear_transaction_handle(); - if (!response_opt) { - return ErrorCode::SERVER_FAILURE; - } - auto response = response_opt.value(); - if (response.has_success()) { - num_rows = num_rows_processed(response.success()); - return ErrorCode::OK; + + try { + auto response_opt = transport_.send(request); + request.clear_sql(); + request.clear_transaction_handle(); + if (!response_opt) { + return ErrorCode::SERVER_FAILURE; + } + auto response = response_opt.value(); + if (response.has_success()) { + num_rows = num_rows_processed(response.success()); + return ErrorCode::OK; + } + return ErrorCode::SERVER_ERROR; + } catch (std::runtime_error &e) { + return ErrorCode::SERVER_ERROR; } - return ErrorCode::SERVER_FAILURE; } return ErrorCode::NO_TRANSACTION; } @@ -200,17 +205,21 @@ ErrorCode Transaction::Impl::execute_statement(PreparedStatementPtr& prepared, c for (auto& e : parameters) { *(request.add_parameters()) = std::visit(parameter(e.first), e.second); } - auto response_opt = transport_.send(request); + try { + auto response_opt = transport_.send(request); - if (!response_opt) { - return ErrorCode::SERVER_FAILURE; - } - auto response = response_opt.value(); - if (response.has_success()) { - num_rows = num_rows_processed(response.success()); - return ErrorCode::OK; + if (!response_opt) { + return ErrorCode::SERVER_FAILURE; + } + auto response = response_opt.value(); + if (response.has_success()) { + num_rows = num_rows_processed(response.success()); + return ErrorCode::OK; + } + return ErrorCode::SERVER_ERROR; + } catch (std::runtime_error &e) { + return ErrorCode::SERVER_ERROR; } - return ErrorCode::SERVER_FAILURE; } return ErrorCode::NO_TRANSACTION; } @@ -245,7 +254,7 @@ ErrorCode Transaction::Impl::execute_query(std::string_view query, std::shared_p ); return ErrorCode::OK; } catch (std::runtime_error &e) { - return ErrorCode::SERVER_FAILURE; + return ErrorCode::SERVER_ERROR; } } return ErrorCode::NO_TRANSACTION; @@ -292,7 +301,7 @@ ErrorCode Transaction::Impl::execute_query(PreparedStatementPtr& prepared, const ); return ErrorCode::OK; } catch (std::runtime_error &e) { - return ErrorCode::SERVER_FAILURE; + return ErrorCode::SERVER_ERROR; } } return ErrorCode::NO_TRANSACTION; @@ -318,7 +327,7 @@ ErrorCode Transaction::Impl::commit() return dispose_transaction(); } dispose_transaction(); - return ErrorCode::SERVER_FAILURE; + return ErrorCode::SERVER_ERROR; } return ErrorCode::NO_TRANSACTION; } @@ -342,7 +351,7 @@ ErrorCode Transaction::Impl::rollback() if (response.has_success()) { return dispose_transaction(); } - return ErrorCode::SERVER_FAILURE; + return ErrorCode::SERVER_ERROR; } return ErrorCode::OK; // rollback is idempotent } diff --git a/src/ogawayama/transport/transport.h b/src/ogawayama/transport/transport.h index 61b2d83..f14dd68 100644 --- a/src/ogawayama/transport/transport.h +++ b/src/ogawayama/transport/transport.h @@ -114,7 +114,9 @@ class transport { if (response_opt) { auto response_message = response_opt.value(); if (response_message.has_begin()) { - return response_message.begin(); + auto response = response_message.begin(); + sql_error_ = response.has_error() ? response.error() : ::jogasaki::proto::sql::response::Error{}; + return response; } } return std::nullopt; @@ -134,7 +136,9 @@ class transport { if (response_opt) { auto response_message = response_opt.value(); if (response_message.has_prepare()) { - return response_message.prepare(); + auto response = response_message.prepare(); + sql_error_ = response.has_error() ? response.error() : ::jogasaki::proto::sql::response::Error{}; + return response; } } return std::nullopt; @@ -143,7 +147,7 @@ class transport { /** * @brief send a execute statement request to the sql service. * @param execute_statement_request a execute statement request message in ::jogasaki::proto::sql::request::ExecuteStatement - * @return std::optional of ::jogasaki::proto::sql::request::ResultOnly + * @return std::optional of ::jogasaki::proto::sql::request::ExecuteResult */ std::optional<::jogasaki::proto::sql::response::ExecuteResult> send(::jogasaki::proto::sql::request::ExecuteStatement& execute_statement_request) { tateyama::common::wire::message_header::index_type slot_index{}; @@ -154,7 +158,9 @@ class transport { if (response_opt) { auto response_message = response_opt.value(); if (response_message.has_execute_result()) { - return response_message.execute_result(); + auto response = response_message.execute_result(); + sql_error_ = response.has_error() ? response.error() : ::jogasaki::proto::sql::response::Error{}; + return response; } } return std::nullopt; @@ -163,7 +169,7 @@ class transport { /** * @brief send a execute statement request to the sql service. * @param execute_statement_request a execute statement request message in ::jogasaki::proto::sql::request::ExecutePreparedStatement - * @return std::optional of ::jogasaki::proto::sql::request::ResultOnly + * @return std::optional of ::jogasaki::proto::sql::request::ExecuteResult */ std::optional<::jogasaki::proto::sql::response::ExecuteResult> send(::jogasaki::proto::sql::request::ExecutePreparedStatement& execute_statement_request) { tateyama::common::wire::message_header::index_type slot_index{}; @@ -174,7 +180,9 @@ class transport { if (response_opt) { auto response_message = response_opt.value(); if (response_message.has_execute_result()) { - return response_message.execute_result(); + auto response = response_message.execute_result(); + sql_error_ = response.has_error() ? response.error() : ::jogasaki::proto::sql::response::Error{}; + return response; } } return std::nullopt; @@ -197,6 +205,8 @@ class transport { return response_message.execute_query(); } if (response_message.has_result_only()) { + const auto& response = response_message.result_only(); + sql_error_ = response.has_error() ? response.error() : ::jogasaki::proto::sql::response::Error{}; throw std::runtime_error("no body_head message"); } } @@ -219,6 +229,8 @@ class transport { return response_message.execute_query(); } if (response_message.has_result_only()) { + const auto& response = response_message.result_only(); + sql_error_ = response.has_error() ? response.error() : ::jogasaki::proto::sql::response::Error{}; throw std::runtime_error("no body_head message"); } } @@ -247,7 +259,9 @@ class transport { if (response_opt) { auto response_message = response_opt.value(); if (response_message.has_result_only()) { - return response_message.result_only(); + auto response = response_message.result_only(); + sql_error_ = response.has_error() ? response.error() : ::jogasaki::proto::sql::response::Error{}; + return response; } } return std::nullopt; @@ -331,6 +345,17 @@ class transport { return std::nullopt; } + // used only by connection + ::tateyama::proto::framework::response::Header& last_header() { + return response_header_; + } + ::jogasaki::proto::sql::response::Error& last_sql_error() { + return sql_error_; + } + ::tateyama::proto::diagnostics::Record& last_framework_error() { + return framework_error_; + } + private: tateyama::common::wire::session_wire_container& wire_; ::tateyama::proto::framework::request::Header header_{}; @@ -340,7 +365,10 @@ class transport { std::string query_result_for_the_one_{}; std::vector query_results_{}; bool closed_{}; - std::unique_ptr timer_{}; + std::unique_ptr timer_{}; + ::tateyama::proto::framework::response::Header response_header_{}; + ::jogasaki::proto::sql::response::Error sql_error_{}; + ::tateyama::proto::diagnostics::Record framework_error_{}; template std::optional send(::jogasaki::proto::sql::request::Request& request, tateyama::common::wire::message_header::index_type& slot_index) { @@ -411,11 +439,24 @@ class transport { std::string response_message{}; wire_.receive(response_message, slot_index); - ::tateyama::proto::framework::response::Header header{}; + response_header_ = ::tateyama::proto::framework::response::Header{}; google::protobuf::io::ArrayInputStream in{response_message.data(), static_cast(response_message.length())}; - if(auto res = tateyama::utils::ParseDelimitedFromZeroCopyStream(std::addressof(header), std::addressof(in), nullptr); ! res) { + if(auto res = tateyama::utils::ParseDelimitedFromZeroCopyStream(std::addressof(response_header_), std::addressof(in), nullptr); ! res) { return std::nullopt; } + if (response_header_.payload_type() == ::tateyama::proto::framework::response::Header_PayloadType::Header_PayloadType_SERVER_DIAGNOSTICS) { + std::string_view record{}; + if (auto res = tateyama::utils::GetDelimitedBodyFromZeroCopyStream(std::addressof(in), nullptr, record); ! res) { + return std::nullopt; + } + if(auto res = framework_error_.ParseFromArray(record.data(), record.length()); ! res) { + return std::nullopt; + } + throw std::runtime_error("received SERVER_DIAGNOSTICS"); + } + if (response_header_.payload_type() != ::tateyama::proto::framework::response::Header_PayloadType::Header_PayloadType_SERVICE_RESULT) { + throw std::runtime_error("unknown payload type"); + } std::string_view payload{}; if (auto res = tateyama::utils::GetDelimitedBodyFromZeroCopyStream(std::addressof(in), nullptr, payload); ! res) { return std::nullopt; @@ -478,11 +519,24 @@ class transport { std::string response_message{}; wire_.receive(response_message, slot_index); - ::tateyama::proto::framework::response::Header header{}; + response_header_ = ::tateyama::proto::framework::response::Header{}; google::protobuf::io::ArrayInputStream in{response_message.data(), static_cast(response_message.length())}; - if(auto res = tateyama::utils::ParseDelimitedFromZeroCopyStream(std::addressof(header), std::addressof(in), nullptr); ! res) { + if(auto res = tateyama::utils::ParseDelimitedFromZeroCopyStream(std::addressof(response_header_), std::addressof(in), nullptr); ! res) { return std::nullopt; } + if (response_header_.payload_type() == ::tateyama::proto::framework::response::Header_PayloadType::Header_PayloadType_SERVER_DIAGNOSTICS) { + std::string_view record{}; + if (auto res = tateyama::utils::GetDelimitedBodyFromZeroCopyStream(std::addressof(in), nullptr, record); ! res) { + return std::nullopt; + } + if(auto res = framework_error_.ParseFromArray(record.data(), record.length()); ! res) { + return std::nullopt; + } + throw std::runtime_error("received SERVER_DIAGNOSTICS"); + } + if (response_header_.payload_type() != ::tateyama::proto::framework::response::Header_PayloadType::Header_PayloadType_SERVICE_RESULT) { + throw std::runtime_error("unknown payload type"); + } std::string_view response{}; bool eof{}; if(auto res = tateyama::utils::GetDelimitedBodyFromZeroCopyStream(std::addressof(in), &eof, response); ! res) { diff --git a/src/ogawayama/transport/tsurugi_error.h b/src/ogawayama/transport/tsurugi_error.h new file mode 100644 index 0000000..19ad7eb --- /dev/null +++ b/src/ogawayama/transport/tsurugi_error.h @@ -0,0 +1,193 @@ +/* + * Copyright 2022-2024 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +#include +#include + +namespace ogawayama::transport { + +static const std::map<::jogasaki::proto::sql::error::Code, std::pair> error_map = { // NOLINT + + {::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION, {"SQL_SERVICE_EXCEPTION", 1000}}, + + {::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION, {"SQL_SERVICE_EXCEPTION", 1000}}, + + {::jogasaki::proto::sql::error::Code::SQL_EXECUTION_EXCEPTION, {"SQL_EXECUTION_EXCEPTION", 2000}}, + + {::jogasaki::proto::sql::error::Code::CONSTRAINT_VIOLATION_EXCEPTION, {"CONSTRAINT_VIOLATION_EXCEPTION", 2001}}, + + {::jogasaki::proto::sql::error::Code::UNIQUE_CONSTRAINT_VIOLATION_EXCEPTION, {"UNIQUE_CONSTRAINT_VIOLATION_EXCEPTION", 2002}}, + + {::jogasaki::proto::sql::error::Code::NOT_NULL_CONSTRAINT_VIOLATION_EXCEPTION, {"NOT_NULL_CONSTRAINT_VIOLATION_EXCEPTION", 2003}}, + + {::jogasaki::proto::sql::error::Code::REFERENTIAL_INTEGRITY_CONSTRAINT_VIOLATION_EXCEPTION, {"REFERENTIAL_INTEGRITY_CONSTRAINT_VIOLATION_EXCEPTION", 2004}}, + + {::jogasaki::proto::sql::error::Code::CHECK_CONSTRAINT_VIOLATION_EXCEPTION, {"CHECK_CONSTRAINT_VIOLATION_EXCEPTION", 2005}}, + + {::jogasaki::proto::sql::error::Code::EVALUATION_EXCEPTION, {"EVALUATION_EXCEPTION", 2010}}, + + {::jogasaki::proto::sql::error::Code::VALUE_EVALUATION_EXCEPTION, {"VALUE_EVALUATION_EXCEPTION", 2011}}, + + {::jogasaki::proto::sql::error::Code::SCALAR_SUBQUERY_EVALUATION_EXCEPTION, {"SCALAR_SUBQUERY_EVALUATION_EXCEPTION", 2012}}, + + {::jogasaki::proto::sql::error::Code::TARGET_NOT_FOUND_EXCEPTION, {"TARGET_NOT_FOUND_EXCEPTION", 2014}}, + + {::jogasaki::proto::sql::error::Code::TARGET_ALREADY_EXISTS_EXCEPTION, {"TARGET_ALREADY_EXISTS_EXCEPTION", 2016}}, + + {::jogasaki::proto::sql::error::Code::INCONSISTENT_STATEMENT_EXCEPTION, {"INCONSISTENT_STATEMENT_EXCEPTION", 2018}}, + + {::jogasaki::proto::sql::error::Code::RESTRICTED_OPERATION_EXCEPTION, {"RESTRICTED_OPERATION_EXCEPTION", 2020}}, + + {::jogasaki::proto::sql::error::Code::DEPENDENCIES_VIOLATION_EXCEPTION, {"DEPENDENCIES_VIOLATION_EXCEPTION", 2021}}, + + {::jogasaki::proto::sql::error::Code::WRITE_OPERATION_BY_RTX_EXCEPTION, {"WRITE_OPERATION_BY_RTX_EXCEPTION", 2022}}, + + {::jogasaki::proto::sql::error::Code::LTX_WRITE_OPERATION_WITHOUT_WRITE_PRESERVE_EXCEPTION, {"LTX_WRITE_OPERATION_WITHOUT_WRITE_PRESERVE_EXCEPTION", 2023}}, + + {::jogasaki::proto::sql::error::Code::READ_OPERATION_ON_RESTRICTED_READ_AREA_EXCEPTION, {"READ_OPERATION_ON_RESTRICTED_READ_AREA_EXCEPTION", 2024}}, + + {::jogasaki::proto::sql::error::Code::INACTIVE_TRANSACTION_EXCEPTION, {"INACTIVE_TRANSACTION_EXCEPTION", 2025}}, + + {::jogasaki::proto::sql::error::Code::PARAMETER_EXCEPTION, {"PARAMETER_EXCEPTION", 2027}}, + + {::jogasaki::proto::sql::error::Code::UNRESOLVED_PLACEHOLDER_EXCEPTION, {"UNRESOLVED_PLACEHOLDER_EXCEPTION", 2028}}, + + {::jogasaki::proto::sql::error::Code::LOAD_FILE_EXCEPTION, {"LOAD_FILE_EXCEPTION", 2030}}, + + {::jogasaki::proto::sql::error::Code::LOAD_FILE_NOT_FOUND_EXCEPTION, {"LOAD_FILE_NOT_FOUND_EXCEPTION", 2031}}, + + {::jogasaki::proto::sql::error::Code::LOAD_FILE_FORMAT_EXCEPTION, {"LOAD_FILE_FORMAT_EXCEPTION", 2032}}, + + {::jogasaki::proto::sql::error::Code::DUMP_FILE_EXCEPTION, {"DUMP_FILE_EXCEPTION", 2033}}, + + {::jogasaki::proto::sql::error::Code::DUMP_DIRECTORY_INACCESSIBLE_EXCEPTION, {"DUMP_DIRECTORY_INACCESSIBLE_EXCEPTION", 2034}}, + + {::jogasaki::proto::sql::error::Code::SQL_LIMIT_REACHED_EXCEPTION, {"SQL_LIMIT_REACHED_EXCEPTION", 2036}}, + + {::jogasaki::proto::sql::error::Code::TRANSACTION_EXCEEDED_LIMIT_EXCEPTION, {"TRANSACTION_EXCEEDED_LIMIT_EXCEPTION", 2037}}, + + {::jogasaki::proto::sql::error::Code::SQL_REQUEST_TIMEOUT_EXCEPTION, {"SQL_REQUEST_TIMEOUT_EXCEPTION", 2039}}, + + {::jogasaki::proto::sql::error::Code::DATA_CORRUPTION_EXCEPTION, {"DATA_CORRUPTION_EXCEPTION", 2041}}, + + {::jogasaki::proto::sql::error::Code::SECONDARY_INDEX_CORRUPTION_EXCEPTION, {"SECONDARY_INDEX_CORRUPTION_EXCEPTION", 2042}}, + + {::jogasaki::proto::sql::error::Code::REQUEST_FAILURE_EXCEPTION, {"REQUEST_FAILURE_EXCEPTION", 2044}}, + + {::jogasaki::proto::sql::error::Code::TRANSACTION_NOT_FOUND_EXCEPTION, {"TRANSACTION_NOT_FOUND_EXCEPTION", 2045}}, + + {::jogasaki::proto::sql::error::Code::STATEMENT_NOT_FOUND_EXCEPTION, {"STATEMENT_NOT_FOUND_EXCEPTION", 2046}}, + + {::jogasaki::proto::sql::error::Code::INTERNAL_EXCEPTION, {"INTERNAL_EXCEPTION", 2048}}, + + {::jogasaki::proto::sql::error::Code::UNSUPPORTED_RUNTIME_FEATURE_EXCEPTION, {"UNSUPPORTED_RUNTIME_FEATURE_EXCEPTION", 2050}}, + + {::jogasaki::proto::sql::error::Code::BLOCKED_BY_HIGH_PRIORITY_TRANSACTION_EXCEPTION, {"BLOCKED_BY_HIGH_PRIORITY_TRANSACTION_EXCEPTION", 2052}}, + + {::jogasaki::proto::sql::error::Code::INVALID_RUNTIME_VALUE_EXCEPTION, {"INVALID_RUNTIME_VALUE_EXCEPTION", 2054}}, + + {::jogasaki::proto::sql::error::Code::VALUE_OUT_OF_RANGE_EXCEPTION, {"VALUE_OUT_OF_RANGE_EXCEPTION", 2056}}, + + {::jogasaki::proto::sql::error::Code::VALUE_TOO_LONG_EXCEPTION, {"VALUE_TOO_LONG_EXCEPTION", 2058}}, + + {::jogasaki::proto::sql::error::Code::INVALID_DECIMAL_VALUE_EXCEPTION, {"INVALID_DECIMAL_VALUE_EXCEPTION", 2060}}, + +// reserved 42 to 100; + + {::jogasaki::proto::sql::error::Code::COMPILE_EXCEPTION, {"COMPILE_EXCEPTION", 3000}}, + + {::jogasaki::proto::sql::error::Code::SYNTAX_EXCEPTION, {"SYNTAX_EXCEPTION", 3001}}, + + {::jogasaki::proto::sql::error::Code::ANALYZE_EXCEPTION, {"ANALYZE_EXCEPTION", 3002}}, + + {::jogasaki::proto::sql::error::Code::TYPE_ANALYZE_EXCEPTION, {"TYPE_ANALYZE_EXCEPTION", 3003}}, + + {::jogasaki::proto::sql::error::Code::SYMBOL_ANALYZE_EXCEPTION, {"SYMBOL_ANALYZE_EXCEPTION", 3004}}, + + {::jogasaki::proto::sql::error::Code::VALUE_ANALYZE_EXCEPTION, {"VALUE_ANALYZE_EXCEPTION", 3005}}, + + {::jogasaki::proto::sql::error::Code::UNSUPPORTED_COMPILER_FEATURE_EXCEPTION, {"UNSUPPORTED_COMPILER_FEATURE_EXCEPTION", 3010}}, + +// reserved 108 to 200; + + {::jogasaki::proto::sql::error::Code::CC_EXCEPTION, {"CC_EXCEPTION", 4000}}, + + {::jogasaki::proto::sql::error::Code::OCC_EXCEPTION, {"OCC_EXCEPTION", 4001}}, + + {::jogasaki::proto::sql::error::Code::OCC_READ_EXCEPTION, {"OCC_READ_EXCEPTION", 4010}}, + + {::jogasaki::proto::sql::error::Code::CONFLICT_ON_WRITE_PRESERVE_EXCEPTION, {"CONFLICT_ON_WRITE_PRESERVE_EXCEPTION", 4015}}, + + {::jogasaki::proto::sql::error::Code::OCC_WRITE_EXCEPTION, {"OCC_WRITE_EXCEPTION", 4011}}, + + {::jogasaki::proto::sql::error::Code::LTX_EXCEPTION, {"LTX_EXCEPTION", 4003}}, + + {::jogasaki::proto::sql::error::Code::LTX_READ_EXCEPTION, {"LTX_READ_EXCEPTION", 4013}}, + + {::jogasaki::proto::sql::error::Code::LTX_WRITE_EXCEPTION, {"LTX_WRITE_EXCEPTION", 4014}}, + + {::jogasaki::proto::sql::error::Code::RTX_EXCEPTION, {"RTX_EXCEPTION", 4005}}, + + {::jogasaki::proto::sql::error::Code::BLOCKED_BY_CONCURRENT_OPERATION_EXCEPTION, {"BLOCKED_BY_CONCURRENT_OPERATION_EXCEPTION", 4007}} +}; + +static const std::map<::tateyama::proto::diagnostics::Code, std::pair> framework_error_map = { // NOLINT + + {::tateyama::proto::diagnostics::Code::UNKNOWN, {"unknown error was occurred in the server.", 0}}, + + {::tateyama::proto::diagnostics::Code::SYSTEM_ERROR, {"the server system is something wrong.", 100}}, + + {::tateyama::proto::diagnostics::Code::UNSUPPORTED_OPERATION, {"the requested operation is not supported.", 101}}, + + {::tateyama::proto::diagnostics::Code::ILLEGAL_STATE, {"I/O error was occurred in the server.", 102}}, + + {::tateyama::proto::diagnostics::Code::ILLEGAL_STATE, {"operation was requested in illegal or inappropriate time.", 102}}, + + {::tateyama::proto::diagnostics::Code::IO_ERROR, {"I/O error was occurred in the server.", 103}}, + + {::tateyama::proto::diagnostics::Code::OUT_OF_MEMORY, {"the server is out of memory.", 104}}, + + {::tateyama::proto::diagnostics::Code::RESOURCE_LIMIT_REACHED, {"the server reached resource limit.", 105}}, + + {::tateyama::proto::diagnostics::Code::AUTHENTICATION_ERROR, {"authentication was failed.", 201}}, + + {::tateyama::proto::diagnostics::Code::PERMISSION_ERROR, {"request is not permitted.", 202}}, + + {::tateyama::proto::diagnostics::Code::ACCESS_EXPIRED, {"access right has been expired.", 203}}, + + {::tateyama::proto::diagnostics::Code::REFRESH_EXPIRED, {"refresh right has been expired.", 204}}, + + {::tateyama::proto::diagnostics::Code::BROKEN_CREDENTIAL, {"credential information is broken.", 205}}, + + {::tateyama::proto::diagnostics::Code::SESSION_CLOSED, {"the current session is already closed.", 301}}, + + {::tateyama::proto::diagnostics::Code::SESSION_EXPIRED, {"the current session is expired.", 302}}, + + {::tateyama::proto::diagnostics::Code::SERVICE_NOT_FOUND, {"the destination service was not found.", 401}}, + + {::tateyama::proto::diagnostics::Code::SERVICE_UNAVAILABLE, {"the destination service was not found.", 402}}, + + {::tateyama::proto::diagnostics::Code::OPERATION_CANCELED, {"operation was canceled by user or system.", 403}}, + + {::tateyama::proto::diagnostics::Code::INVALID_REQUEST, {"the service received a request message with invalid payload.", 501}} +}; + +} // namespace ogawayama::transport diff --git a/test/ogawayama/stub/ErrorTest.cpp b/test/ogawayama/stub/ErrorTest.cpp new file mode 100644 index 0000000..6054af2 --- /dev/null +++ b/test/ogawayama/stub/ErrorTest.cpp @@ -0,0 +1,389 @@ +/* + * Copyright 2018-2024 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include + +#include + +#include "stub_test_root.h" + + +namespace ogawayama::testing { + +static constexpr const char* name_prefix = "error_test"; + +class ErrorTest : public ::testing::Test { + virtual void SetUp() { + shm_name_ = std::string(name_prefix); + shm_name_ += std::to_string(getpid()); + server_ = std::make_unique(shm_name_); + } +protected: + std::unique_ptr server_{}; + std::string shm_name_{}; +}; + +TEST_F(ErrorTest, begin) { + StubPtr stub; + ConnectionPtr connection; + TransactionPtr transaction; + + EXPECT_EQ(ERROR_CODE::OK, make_stub(stub, shm_name_)); + + EXPECT_EQ(ERROR_CODE::OK, stub->get_connection(connection, 16)); + + { + jogasaki::proto::sql::response::Begin b{}; + jogasaki::proto::sql::response::Error e{}; + e.set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + e.set_detail("sql_error_for_test"); + e.set_supplemental_text("supplemental_text_for_test"); + b.set_allocated_error(&e); + server_->response_message(b); + EXPECT_EQ(ERROR_CODE::SERVER_ERROR, connection->begin(transaction)); + (void) b.release_error(); + + std::optional request_opt = server_->request_message(); + EXPECT_TRUE(request_opt); + auto request = request_opt.value(); + EXPECT_EQ(request.request_case(), jogasaki::proto::sql::request::Request::RequestCase::kBegin); + EXPECT_FALSE(request.begin().has_option()); + } + { + ogawayama::stub::tsurugi_error_code code{}; + EXPECT_EQ(ERROR_CODE::OK, connection->tsurugi_error(code)); + + EXPECT_EQ(code.type, ogawayama::stub::tsurugi_error_code::tsurugi_error_type::sql_error); + EXPECT_EQ(code.code, 1000); // 1000 corresponds to SQL_SERVICE_EXCEPTION + EXPECT_EQ(code.name, "SQL_SERVICE_EXCEPTION"); + EXPECT_EQ(code.detail, "sql_error_for_test"); + EXPECT_EQ(code.supplemental_text, "supplemental_text_for_test"); + } +} + +TEST_F(ErrorTest, prepare) { + StubPtr stub; + ConnectionPtr connection; + PreparedStatementPtr prepared_statement; + + EXPECT_EQ(ERROR_CODE::OK, make_stub(stub, shm_name_)); + + EXPECT_EQ(ERROR_CODE::OK, stub->get_connection(connection, 16)); + + { + jogasaki::proto::sql::response::Prepare rp{}; + jogasaki::proto::sql::response::Error e{}; + e.set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + e.set_detail("sql_error_for_test"); + e.set_supplemental_text("supplemental_text_for_test"); + rp.set_allocated_error(&e); + server_->response_message(rp); + (void) rp.release_error(); + + ogawayama::stub::placeholders_type placeholders{}; + EXPECT_EQ(ERROR_CODE::SERVER_ERROR, connection->prepare("insert into table (c1, c2) values(1, 23)", placeholders, prepared_statement)); + + std::optional request_opt = server_->request_message(); + EXPECT_TRUE(request_opt); + auto request = request_opt.value(); + EXPECT_EQ(request.request_case(), ::jogasaki::proto::sql::request::Request::RequestCase::kPrepare); + } + { + ogawayama::stub::tsurugi_error_code code{}; + EXPECT_EQ(ERROR_CODE::OK, connection->tsurugi_error(code)); + + EXPECT_EQ(code.type, ogawayama::stub::tsurugi_error_code::tsurugi_error_type::sql_error); + EXPECT_EQ(code.code, 1000); // 1000 corresponds to SQL_SERVICE_EXCEPTION + EXPECT_EQ(code.name, "SQL_SERVICE_EXCEPTION"); + EXPECT_EQ(code.detail, "sql_error_for_test"); + EXPECT_EQ(code.supplemental_text, "supplemental_text_for_test"); + } +} + +TEST_F(ErrorTest, statement) { + StubPtr stub; + ConnectionPtr connection; + TransactionPtr transaction; + ResultSetPtr result_set; + + EXPECT_EQ(ERROR_CODE::OK, make_stub(stub, shm_name_)); + + EXPECT_EQ(ERROR_CODE::OK, stub->get_connection(connection, 16)); + + { + jogasaki::proto::sql::response::Begin b{}; + jogasaki::proto::sql::response::Begin::Success s{}; + jogasaki::proto::sql::common::Transaction t{}; + jogasaki::proto::sql::common::TransactionId tid{}; + tid.set_id("transaction_id_for_test"); + t.set_handle(0x12345678); + s.set_allocated_transaction_handle(&t); + s.set_allocated_transaction_id(&tid); + b.set_allocated_success(&s); + server_->response_message(b); + EXPECT_EQ(ERROR_CODE::OK, connection->begin(transaction)); + (void) s.release_transaction_handle(); + (void) s.release_transaction_id(); + (void) b.release_success(); + + std::optional request_opt = server_->request_message(); + EXPECT_TRUE(request_opt); + auto request = request_opt.value(); + EXPECT_EQ(request.request_case(), jogasaki::proto::sql::request::Request::RequestCase::kBegin); + EXPECT_FALSE(request.begin().has_option()); + } + + { + // body + jogasaki::proto::sql::response::ExecuteResult er{}; + jogasaki::proto::sql::response::Error e{}; + e.set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + e.set_detail("sql_error_for_test"); + e.set_supplemental_text("supplemental_text_for_test"); + er.set_allocated_error(&e); + server_->response_message(er); + std::size_t num_rows{}; + EXPECT_EQ(ERROR_CODE::SERVER_ERROR, transaction->execute_statement("INSERT INTO TABLE (C1, C2) VALUES(1, 2)", num_rows)); + (void) er.release_error(); + + std::optional request_opt = server_->request_message(); + EXPECT_TRUE(request_opt); + auto request = request_opt.value(); + EXPECT_EQ(request.request_case(), jogasaki::proto::sql::request::Request::RequestCase::kExecuteStatement); + EXPECT_EQ(request.execute_statement().sql(), "INSERT INTO TABLE (C1, C2) VALUES(1, 2)"); + } + { + ogawayama::stub::tsurugi_error_code code{}; + EXPECT_EQ(ERROR_CODE::OK, connection->tsurugi_error(code)); + + EXPECT_EQ(code.type, ogawayama::stub::tsurugi_error_code::tsurugi_error_type::sql_error); + EXPECT_EQ(code.code, 1000); // 1000 corresponds to SQL_SERVICE_EXCEPTION + EXPECT_EQ(code.name, "SQL_SERVICE_EXCEPTION"); + EXPECT_EQ(code.detail, "sql_error_for_test"); + EXPECT_EQ(code.supplemental_text, "supplemental_text_for_test"); + } + { + jogasaki::proto::sql::response::ResultOnly roc{}; + jogasaki::proto::sql::response::Success sc{}; + roc.set_allocated_success(&sc); + server_->response_message(roc); + + jogasaki::proto::sql::response::ResultOnly rod{}; + jogasaki::proto::sql::response::Success sd{}; + rod.set_allocated_success(&sd); + server_->response_message(rod); + + EXPECT_EQ(ERROR_CODE::OK, transaction->commit()); + (void) roc.release_success(); + (void) rod.release_success(); + + std::optional requestc_opt = server_->request_message(); + EXPECT_TRUE(requestc_opt); + auto requestc = requestc_opt.value(); + EXPECT_EQ(requestc.request_case(), jogasaki::proto::sql::request::Request::RequestCase::kCommit); + + std::optional requestd_opt = server_->request_message(); + EXPECT_TRUE(requestd_opt); + auto requestd = requestd_opt.value(); + EXPECT_EQ(requestd.request_case(), jogasaki::proto::sql::request::Request::RequestCase::kDisposeTransaction); + } +} + +TEST_F(ErrorTest, query) { + StubPtr stub; + ConnectionPtr connection; + TransactionPtr transaction; + ResultSetPtr result_set; + + EXPECT_EQ(ERROR_CODE::OK, make_stub(stub, shm_name_)); + + EXPECT_EQ(ERROR_CODE::OK, stub->get_connection(connection, 16)); + + { + jogasaki::proto::sql::response::Begin b{}; + jogasaki::proto::sql::response::Begin::Success s{}; + jogasaki::proto::sql::common::Transaction t{}; + jogasaki::proto::sql::common::TransactionId tid{}; + tid.set_id("transaction_id_for_test"); + t.set_handle(0x12345678); + s.set_allocated_transaction_handle(&t); + s.set_allocated_transaction_id(&tid); + b.set_allocated_success(&s); + server_->response_message(b); + EXPECT_EQ(ERROR_CODE::OK, connection->begin(transaction)); + (void) s.release_transaction_handle(); + (void) s.release_transaction_id(); + (void) b.release_success(); + + std::optional request_opt = server_->request_message(); + EXPECT_TRUE(request_opt); + auto request = request_opt.value(); + EXPECT_EQ(request.request_case(), jogasaki::proto::sql::request::Request::RequestCase::kBegin); + EXPECT_FALSE(request.begin().has_option()); + } + + { + // body + jogasaki::proto::sql::response::ResultOnly ro{}; + jogasaki::proto::sql::response::Error e{}; + e.set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + e.set_detail("sql_error_for_test"); + e.set_supplemental_text("supplemental_text_for_test"); + ro.set_allocated_error(&e); + server_->response_message(ro); + EXPECT_EQ(ERROR_CODE::SERVER_ERROR, transaction->execute_query("SELECT * FROM T2", result_set)); + (void) ro.release_error(); + + std::optional request_opt = server_->request_message(); + EXPECT_TRUE(request_opt); + auto request = request_opt.value(); + EXPECT_EQ(request.request_case(), jogasaki::proto::sql::request::Request::RequestCase::kExecuteQuery); + EXPECT_EQ(request.execute_query().sql(), "SELECT * FROM T2"); + } + { + ogawayama::stub::tsurugi_error_code code{}; + EXPECT_EQ(ERROR_CODE::OK, connection->tsurugi_error(code)); + + EXPECT_EQ(code.type, ogawayama::stub::tsurugi_error_code::tsurugi_error_type::sql_error); + EXPECT_EQ(code.code, 1000); // 1000 corresponds to SQL_SERVICE_EXCEPTION + EXPECT_EQ(code.name, "SQL_SERVICE_EXCEPTION"); + EXPECT_EQ(code.detail, "sql_error_for_test"); + EXPECT_EQ(code.supplemental_text, "supplemental_text_for_test"); + } + { + jogasaki::proto::sql::response::ResultOnly roc{}; + jogasaki::proto::sql::response::Success sc{}; + roc.set_allocated_success(&sc); + server_->response_message(roc); + + jogasaki::proto::sql::response::ResultOnly rod{}; + jogasaki::proto::sql::response::Success sd{}; + rod.set_allocated_success(&sd); + server_->response_message(rod); + + EXPECT_EQ(ERROR_CODE::OK, transaction->commit()); + (void) roc.release_success(); + (void) rod.release_success(); + + std::optional requestc_opt = server_->request_message(); + EXPECT_TRUE(requestc_opt); + auto requestc = requestc_opt.value(); + EXPECT_EQ(requestc.request_case(), jogasaki::proto::sql::request::Request::RequestCase::kCommit); + + std::optional requestd_opt = server_->request_message(); + EXPECT_TRUE(requestd_opt); + auto requestd = requestd_opt.value(); + EXPECT_EQ(requestd.request_case(), jogasaki::proto::sql::request::Request::RequestCase::kDisposeTransaction); + } +} + +TEST_F(ErrorTest, commit) { + StubPtr stub; + ConnectionPtr connection; + TransactionPtr transaction; + + EXPECT_EQ(ERROR_CODE::OK, make_stub(stub, shm_name_)); + + EXPECT_EQ(ERROR_CODE::OK, stub->get_connection(connection, 16)); + + { + jogasaki::proto::sql::response::Begin b{}; + jogasaki::proto::sql::response::Begin::Success s{}; + jogasaki::proto::sql::common::Transaction t{}; + jogasaki::proto::sql::common::TransactionId tid{}; + tid.set_id("transaction_id_for_test"); + t.set_handle(0x12345678); + s.set_allocated_transaction_handle(&t); + s.set_allocated_transaction_id(&tid); + b.set_allocated_success(&s); + server_->response_message(b); + EXPECT_EQ(ERROR_CODE::OK, connection->begin(transaction)); + (void) s.release_transaction_handle(); + (void) s.release_transaction_id(); + (void) b.release_success(); + + std::optional request_opt = server_->request_message(); + EXPECT_TRUE(request_opt); + auto request = request_opt.value(); + EXPECT_EQ(request.request_case(), jogasaki::proto::sql::request::Request::RequestCase::kBegin); + EXPECT_FALSE(request.begin().has_option()); + } + { + jogasaki::proto::sql::response::ResultOnly roc{}; + jogasaki::proto::sql::response::Error e{}; + e.set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + e.set_detail("sql_error_for_test"); + e.set_supplemental_text("supplemental_text_for_test"); + roc.set_allocated_error(&e); + server_->response_message(roc); + + jogasaki::proto::sql::response::ResultOnly rod{}; + jogasaki::proto::sql::response::Success sd{}; + rod.set_allocated_success(&sd); + server_->response_message(rod); + + EXPECT_EQ(ERROR_CODE::SERVER_ERROR, transaction->commit()); + (void) roc.release_error(); + (void) rod.release_success(); + + std::optional requestc_opt = server_->request_message(); + EXPECT_TRUE(requestc_opt); + auto requestc = requestc_opt.value(); + EXPECT_EQ(requestc.request_case(), jogasaki::proto::sql::request::Request::RequestCase::kCommit); + } + { + ogawayama::stub::tsurugi_error_code code{}; + EXPECT_EQ(ERROR_CODE::OK, connection->tsurugi_error(code)); + + EXPECT_EQ(code.type, ogawayama::stub::tsurugi_error_code::tsurugi_error_type::sql_error); + EXPECT_EQ(code.code, 1000); // 1000 corresponds to SQL_SERVICE_EXCEPTION + EXPECT_EQ(code.name, "SQL_SERVICE_EXCEPTION"); + EXPECT_EQ(code.detail, "sql_error_for_test"); + EXPECT_EQ(code.supplemental_text, "supplemental_text_for_test"); + } + { + std::optional requestd_opt = server_->request_message(); + EXPECT_TRUE(requestd_opt); + auto requestd = requestd_opt.value(); + EXPECT_EQ(requestd.request_case(), jogasaki::proto::sql::request::Request::RequestCase::kDisposeTransaction); + } +} + +TEST_F(ErrorTest, framework) { + StubPtr stub; + ConnectionPtr connection; + TransactionPtr transaction; + + EXPECT_EQ(ERROR_CODE::OK, make_stub(stub, shm_name_)); + + EXPECT_EQ(ERROR_CODE::OK, stub->get_connection(connection, 16)); + + { + ::tateyama::proto::framework::response::Header h{}; + h.set_payload_type(::tateyama::proto::framework::response::Header_PayloadType::Header_PayloadType_SERVER_DIAGNOSTICS); + server_->response_message(h); + EXPECT_EQ(ERROR_CODE::SERVER_ERROR, connection->begin(transaction)); + } + { + ogawayama::stub::tsurugi_error_code code{}; + EXPECT_EQ(ERROR_CODE::OK, connection->tsurugi_error(code)); + EXPECT_EQ(code.type, ogawayama::stub::tsurugi_error_code::tsurugi_error_type::framework_error); + EXPECT_EQ(code.code, 403); // SCD-00403 is OPERATION_CANCELED + EXPECT_EQ(code.name, "operation was canceled by user or system."); + } +} + +} // namespace ogawayama::testing diff --git a/test/ogawayama/stub/endpoint.h b/test/ogawayama/stub/endpoint.h index c22afae..3586178 100644 --- a/test/ogawayama/stub/endpoint.h +++ b/test/ogawayama/stub/endpoint.h @@ -87,6 +87,7 @@ class endpoint_response { type type_{}; }; +// The number of workers is limited to 1, as this is for testing purposes, class endpoint { constexpr static tateyama::common::wire::response_header::msg_type RESPONSE_BODY = 1; constexpr static tateyama::common::wire::response_header::msg_type RESPONSE_BODYHEAD = 2; @@ -98,13 +99,8 @@ class endpoint { public: class worker { public: - worker(std::string session_id, std::unique_ptr wire, std::function clean_up) - : session_id_(session_id), wire_(std::move(wire)), clean_up_(std::move(clean_up)), thread_(std::thread(std::ref(*this))) { - } - ~worker() { - if (thread_.joinable()) { - thread_.join(); - } + worker(std::string session_id, std::unique_ptr wire, std::function clean_up, endpoint* ep) + : session_id_(session_id), wire_(std::move(wire)), clean_up_(std::move(clean_up)), endpoint_(ep) { } void operator()() { while(true) { @@ -116,6 +112,10 @@ class endpoint { wire_->get_request_wire()->read(message.data()); { + if (endpoint_->framework_error_) { + send_framework_error(index); + continue; + } ::tateyama::proto::framework::request::Header req_header{}; google::protobuf::io::ArrayInputStream in{message.data(), static_cast(message.length())}; if(auto res = tateyama::utils::ParseDelimitedFromZeroCopyStream(std::addressof(req_header), std::addressof(in), nullptr); ! res) { @@ -124,6 +124,7 @@ class endpoint { if (auto service_id = req_header.service_id(); service_id == tateyama::framework::service_id_fdw) { std::stringstream ss{}; ::tateyama::proto::framework::response::Header header{}; + header.set_payload_type(tateyama::proto::framework::response::Header_PayloadType::Header_PayloadType_SERVICE_RESULT); if(auto res = tateyama::utils::SerializeDelimitedToOstream(header, std::addressof(ss)); ! res) { throw std::runtime_error("error formatting response message"); } @@ -140,6 +141,7 @@ class endpoint { } else if (service_id == tateyama::framework::service_id_endpoint_broker) { std::stringstream ss{}; ::tateyama::proto::framework::response::Header header{}; + header.set_payload_type(tateyama::proto::framework::response::Header_PayloadType::Header_PayloadType_SERVICE_RESULT); if(auto res = tateyama::utils::SerializeDelimitedToOstream(header, std::addressof(ss)); ! res) { throw std::runtime_error("error formatting response message"); } @@ -157,6 +159,7 @@ class endpoint { } else if (service_id == tateyama::framework::service_id_routing) { std::stringstream ss{}; ::tateyama::proto::framework::response::Header header{}; + header.set_payload_type(tateyama::proto::framework::response::Header_PayloadType::Header_PayloadType_SERVICE_RESULT); if(auto res = tateyama::utils::SerializeDelimitedToOstream(header, std::addressof(ss)); ! res) { throw std::runtime_error("error formatting response message"); } @@ -173,9 +176,10 @@ class endpoint { } } - requests_.push(message); - auto reply = responses_.front(); - responses_.pop(); + // handle SQL + endpoint_->requests_.push(message); + auto reply = endpoint_->responses_.front(); + endpoint_->responses_.pop(); if (reply.get_type() == endpoint_response::BODY_ONLY) { auto reply_message = reply.get_body(); wire_->get_response_wire().write(reply_message.data(), tateyama::common::wire::response_header(index, reply_message.length(), RESPONSE_BODY)); @@ -207,71 +211,39 @@ class endpoint { } clean_up_(); } - void response_message(const jogasaki::proto::sql::response::Response& message) { - std::stringstream ss{}; - ::tateyama::proto::framework::response::Header header{}; - if(auto res = tateyama::utils::SerializeDelimitedToOstream(header, std::addressof(ss)); ! res) { - throw std::runtime_error("error formatting response message"); - } - if(auto res = tateyama::utils::SerializeDelimitedToOstream(message, std::addressof(ss)); ! res) { - throw std::runtime_error("error formatting response message"); - } - responses_.emplace(endpoint_response(ss.str())); - } - void response_message(const jogasaki::proto::sql::response::Response& head, std::string_view name, std::queue& resultset, const jogasaki::proto::sql::response::Response& body) { - std::stringstream ss_head{}; - ::tateyama::proto::framework::response::Header header{}; - if(auto res = tateyama::utils::SerializeDelimitedToOstream(header, std::addressof(ss_head)); ! res) { - throw std::runtime_error("error formatting response message"); - } - if(auto res = tateyama::utils::SerializeDelimitedToOstream(head, std::addressof(ss_head)); ! res) { - throw std::runtime_error("error formatting response message"); - } - - std::stringstream ss_body{}; - if(auto res = tateyama::utils::SerializeDelimitedToOstream(header, std::addressof(ss_body)); ! res) { - throw std::runtime_error("error formatting response message"); - } - if(auto res = tateyama::utils::SerializeDelimitedToOstream(body, std::addressof(ss_body)); ! res) { - throw std::runtime_error("error formatting response message"); - } - responses_.emplace(endpoint_response(ss_head.str(), name, resultset, ss_body.str())); - } - std::string_view request_message() { - current_request_ = requests_.front(); - requests_.pop(); - return current_request_; - } private: std::string session_id_; std::unique_ptr wire_; std::function clean_up_; - std::thread thread_; + endpoint* endpoint_; - std::queue requests_; - std::queue responses_; std::array resultset_wires_array_; std::array resultset_wire_array_; - std::string current_request_; + + void send_framework_error(int index) { + std::stringstream ss{}; + if(auto res = tateyama::utils::SerializeDelimitedToOstream(endpoint_->framework_header_, std::addressof(ss)); ! res) { + throw std::runtime_error("error formatting response message"); + } + ::tateyama::proto::diagnostics::Record r{}; + r.set_code(::tateyama::proto::diagnostics::Code::OPERATION_CANCELED); + auto record = r.SerializeAsString(); + if(auto res = tateyama::utils::PutDelimitedBodyToOstream(record, std::addressof(ss)); ! res) { + throw std::runtime_error("error formatting record message"); + } + auto reply_message = ss.str(); + wire_->get_response_wire().write(reply_message.data(), tateyama::common::wire::response_header(index, reply_message.length(), RESPONSE_BODY)); + } }; endpoint(std::string name) - : name_(name), container_(std::make_unique(name_, 1)), thread_(std::thread(std::ref(*this))) { + : name_(name), container_(std::make_unique(name_, 1)) { } ~endpoint() { if (thread_.joinable()) { thread_.join(); } } - worker* get_worker() { - if (!worker_) { - std::unique_lock lk(mutex_); - condition_.wait(lk, [&]{ - return worker_.get() != nullptr; - }); - } - return worker_.get(); - } void operator()() { auto& connection_queue = container_->get_connection_queue(); @@ -288,9 +260,8 @@ class endpoint { std::size_t index = connection_queue.slot(); connection_queue.accept(index, session_id); try { - std::unique_lock lk(mutex_); - worker_ = std::make_unique(session_name, std::move(wire), [&connection_queue, index](){ connection_queue.disconnect(index); }); - condition_.notify_all(); + worker_ = std::make_unique(session_name, std::move(wire), [&connection_queue, index](){ connection_queue.disconnect(index); }, this); + thread_ = std::thread(std::ref(*worker_)); } catch (std::exception& ex) { LOG(ERROR) << ex.what(); break; @@ -301,13 +272,60 @@ class endpoint { container_->get_connection_queue().request_terminate(); } + void response_message(const jogasaki::proto::sql::response::Response& message) { + std::stringstream ss{}; + ::tateyama::proto::framework::response::Header header{}; + header.set_payload_type(tateyama::proto::framework::response::Header_PayloadType::Header_PayloadType_SERVICE_RESULT); + if(auto res = tateyama::utils::SerializeDelimitedToOstream(header, std::addressof(ss)); ! res) { + throw std::runtime_error("error formatting response message"); + } + if(auto res = tateyama::utils::SerializeDelimitedToOstream(message, std::addressof(ss)); ! res) { + throw std::runtime_error("error formatting response message"); + } + responses_.emplace(endpoint_response(ss.str())); + } + void response_message(const jogasaki::proto::sql::response::Response& head, std::string_view name, std::queue& resultset, const jogasaki::proto::sql::response::Response& body) { + std::stringstream ss_head{}; + ::tateyama::proto::framework::response::Header header{}; + header.set_payload_type(tateyama::proto::framework::response::Header_PayloadType::Header_PayloadType_SERVICE_RESULT); + if(auto res = tateyama::utils::SerializeDelimitedToOstream(header, std::addressof(ss_head)); ! res) { + throw std::runtime_error("error formatting response message"); + } + if(auto res = tateyama::utils::SerializeDelimitedToOstream(head, std::addressof(ss_head)); ! res) { + throw std::runtime_error("error formatting response message"); + } + + std::stringstream ss_body{}; + if(auto res = tateyama::utils::SerializeDelimitedToOstream(header, std::addressof(ss_body)); ! res) { + throw std::runtime_error("error formatting response message"); + } + if(auto res = tateyama::utils::SerializeDelimitedToOstream(body, std::addressof(ss_body)); ! res) { + throw std::runtime_error("error formatting response message"); + } + responses_.emplace(endpoint_response(ss_head.str(), name, resultset, ss_body.str())); + } + std::string_view request_message() { + current_request_ = requests_.front(); + requests_.pop(); + return current_request_; + } + void framework_error(::tateyama::proto::framework::response::Header& h) { + framework_error_ = true; + framework_header_ = h; + } + + private: std::string name_; std::unique_ptr container_; std::thread thread_; std::unique_ptr worker_{}; - std::mutex mutex_{}; - std::condition_variable condition_{}; + + std::queue requests_{}; + std::queue responses_{}; + std::string current_request_{}; + bool framework_error_{}; + ::tateyama::proto::framework::response::Header framework_header_{}; }; } // namespace ogawayama::testing diff --git a/test/ogawayama/stub/stub_test_root.h b/test/ogawayama/stub/stub_test_root.h index e6b2bcb..94a9e7e 100644 --- a/test/ogawayama/stub/stub_test_root.h +++ b/test/ogawayama/stub/stub_test_root.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "server_wires_impl.h" #include "endpoint_proto_utils.h" @@ -41,10 +42,13 @@ class server { constexpr static std::string_view resultset_name_prefix = "resultset_for_test_"; // NOLINT public: - server(std::string name) : name_(name), endpoint_(name_) { + server(std::string name) : name_(name), endpoint_(name_), thread_(std::thread(std::ref(endpoint_))) { } ~server() { endpoint_.terminate(); + if (thread_.joinable()) { + thread_.join(); + } remove_shm(); } @@ -66,7 +70,7 @@ class server { jogasaki::proto::sql::response::Response rb{}; rb.set_allocated_result_only(&ro); // set response - endpoint_.get_worker()->response_message(rh, resultset_name, resultset, rb); + endpoint_.response_message(rh, resultset_name, resultset, rb); // release (void) rb.release_result_only(); (void) rh.release_execute_query(); @@ -74,7 +78,7 @@ class server { } std::optional request_message() { - auto request_packet = endpoint_.get_worker()->request_message(); + auto request_packet = endpoint_.request_message(); ::tateyama::proto::framework::request::Header header{}; google::protobuf::io::ArrayInputStream in{request_packet.data(), static_cast(request_packet.length())}; @@ -95,6 +99,7 @@ class server { std::string name_; endpoint endpoint_; std::size_t resultset_number_{}; + std::thread thread_; void remove_shm() { std::string cmd = "if [ -f /dev/shm/" + name_ + " ]; then rm -f /dev/shm/" + name_ + "*; fi"; @@ -108,29 +113,33 @@ template<> inline void server::response_message(jogasaki::proto::sql::response::Begin& b) { jogasaki::proto::sql::response::Response r{}; r.set_allocated_begin(&b); - endpoint_.get_worker()->response_message(r); + endpoint_.response_message(r); (void) r.release_begin(); } template<> inline void server::response_message(jogasaki::proto::sql::response::ResultOnly& ro) { jogasaki::proto::sql::response::Response r{}; r.set_allocated_result_only(&ro); - endpoint_.get_worker()->response_message(r); + endpoint_.response_message(r); (void) r.release_result_only(); } template<> inline void server::response_message(jogasaki::proto::sql::response::Prepare& p) { jogasaki::proto::sql::response::Response r{}; r.set_allocated_prepare(&p); - endpoint_.get_worker()->response_message(r); + endpoint_.response_message(r); (void) r.release_prepare(); } template<> inline void server::response_message(jogasaki::proto::sql::response::ExecuteResult& er) { jogasaki::proto::sql::response::Response r{}; r.set_allocated_execute_result(&er); - endpoint_.get_worker()->response_message(r); + endpoint_.response_message(r); (void) r.release_execute_result(); } +template<> +inline void server::response_message(tateyama::proto::framework::response::Header& h) { + endpoint_.framework_error(h); +} } // namespace ogawayama::testing