diff --git a/CMakeLists.txt b/CMakeLists.txt index d4df94cd21b14c07cd84e554d12c99b0177d8fb0..7ee6d193f6814d1d24eb8cb35e3658a87d00e601 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,7 +323,7 @@ if(_LINTING) else() message(STATUS "clang-tidy: ${clang_tidy}") set(_CMAKE_CXX_CLANG_TIDY_CHECKS - "--checks=*,-fuchsia-*,-llvmlibc-*,-readability-convert-member-functions-to-static,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay,-llvm-else-after-return,-readability-else-after-return,-modernize-use-trailing-return-type,-bugprone-branch-clone") + "--checks=*,-fuchsia-*,-llvmlibc-*,-readability-convert-member-functions-to-static,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay,-llvm-else-after-return,-readability-else-after-return,-modernize-use-trailing-return-type,-bugprone-branch-clone,-altera-*") set(_CMAKE_C_CLANG_TIDY_CHECKS "${_CMAKE_CXX_CLANG_TIDY_CHECKS}") set(_CMAKE_CXX_CLANG_TIDY "${clang_tidy}" "--header-filter=caosdb/.*[^\(\.pb\.h\)]$" diff --git a/conanfile.py b/conanfile.py index f399a6445ef3a8f396ae08fc85fe3688fd80f5cc..c91038d2d9ab6e2f23c21696bd62d19c0421ca78 100644 --- a/conanfile.py +++ b/conanfile.py @@ -14,10 +14,10 @@ class CaosdbConan(ConanFile): default_options = {"shared": False, "fPIC": True} generators = "cmake" requires = [ - ("boost/1.77.0"), - ("grpc/1.39.1"), + ("grpc/1.45.2"), ] build_requires = [ + ("boost/1.77.0"), ("gtest/1.11.0"), ] exports = ("*.cmake", "*CMakeLists.txt", "*.in", diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 7e3d84f6ec43faa429e8a1e9fea8de1182c900c8..adbf07c79763f4af236ac90eff74e44669ec7f02 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -11,12 +11,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +* Transaction::ExecuteAsynchronously is actually asynchronous now. +* Removed boost from the headers. Boost is only a build dependency from now on. + ### Deprecated ### Removed ### Fixed +* Calling "GetFileDescriptor().wrapped->path()" after retrieval leads to SegFault. - #41 Updated Conan version in CI pipeline. ### Security diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 18d9334acdac17d3b583efabb4e2eeb116cbb5a9..784e2e04ae3baa8aacffbacbba2ecc55c1c0af6a 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -28,6 +28,7 @@ set(libcaosdb_INCL ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/data_type.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/entity.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/exceptions.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_descriptor.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/handler_interface.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/info.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/log_level.h @@ -42,8 +43,6 @@ set(libcaosdb_INCL ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/utility.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/value.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/register_file_upload_handler.h - # TODO this file is still missing - # ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/register_file_download_handler.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/upload_request_handler.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/download_request_handler.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/file_writer.h diff --git a/include/caosdb/authentication.h b/include/caosdb/authentication.h index 8e98a17e6d94fe118cf2fc478efbbf77733be4a6..2e117265d71f660888d6fd76d5fe3a3f898cac95 100644 --- a/include/caosdb/authentication.h +++ b/include/caosdb/authentication.h @@ -27,15 +27,15 @@ * @date 2021-06-28 * @brief Configuration and setup of the client authentication. */ -#include "caosdb/utility.h" // for base64_encode -#include <grpcpp/impl/codegen/interceptor.h> // for Status -#include <grpcpp/impl/codegen/security/auth_context.h> // for AuthContext -#include <grpcpp/impl/codegen/status.h> // for Status -#include <grpcpp/impl/codegen/string_ref.h> // for string_ref -#include <grpcpp/security/credentials.h> // for CallCredentials -#include <map> // for multimap -#include <memory> // for shared_ptr -#include <string> // for string +#include "caosdb/utility.h" // for base64_encode +#include <grpcpp/security/auth_context.h> // for AuthContext +#include <grpcpp/security/credentials.h> // for CallCredentials +#include <grpcpp/support/interceptor.h> // for Status +#include <grpcpp/support/status.h> // for Status +#include <grpcpp/support/string_ref.h> // for string_ref +#include <map> // for multimap +#include <memory> // for shared_ptr +#include <string> // for string namespace caosdb { namespace authentication { diff --git a/include/caosdb/certificate_provider.h b/include/caosdb/certificate_provider.h index 1fd4a80b630e53b8b704b29f7803837a3cff6ced..a2cb5b5a1e407d93f3aa0ac774c26aac5ff31f83 100644 --- a/include/caosdb/certificate_provider.h +++ b/include/caosdb/certificate_provider.h @@ -46,7 +46,7 @@ private: std::string certificate_provider; public: - explicit PemCertificateProvider(const std::string &certificate_provider); + explicit PemCertificateProvider(std::string certificate_provider); [[nodiscard]] auto GetCertificatePem() const -> std::string override; }; } // namespace caosdb::configuration diff --git a/include/caosdb/configuration.h b/include/caosdb/configuration.h index 649495f9a3722c9009edcb6b383f5c02941ade28..053888672ab3e1649a060553264fe8d26fda9907 100644 --- a/include/caosdb/configuration.h +++ b/include/caosdb/configuration.h @@ -25,27 +25,20 @@ #include "caosdb/authentication.h" // for Authenticator, PlainPassw... #include "caosdb/certificate_provider.h" // for CertificateProvider, path #include "caosdb/exceptions.h" // for ConfigurationError -#include "caosdb/logging.h" // for SinkConfiguration, Loggin... #include "caosdb/utility.h" // for load_json_file #include <google/protobuf/arena.h> // for Arena #include <google/protobuf/extension_set.h> // for Arena #include <grpcpp/security/credentials.h> // for ChannelCredentials -#include <boost/json/object.hpp> // for object -#include <boost/json/value.hpp> // for value -#include <boost/json/value_ref.hpp> // for array, object -// IWYU pragma: no_include "boost/json/fwd.hpp" -#include <filesystem> // for path, exists -#include <iosfwd> // for ostream -#include <memory> // for shared_ptr, unique_ptr -#include <string> // for string +#include <filesystem> // for path, exists +#include <iosfwd> // for ostream +#include <memory> // for shared_ptr, unique_ptr +#include <string> // for string namespace caosdb::configuration { -using boost::json::array; -using boost::json::object; -using boost::json::value; using caosdb::authentication::Authenticator; using caosdb::authentication::PlainPasswordAuthenticator; using caosdb::exceptions::ConfigurationError; +using caosdb::utility::JsonValue; using caosdb::utility::load_json_file; using google::protobuf::Arena; using grpc::ChannelCredentials; @@ -63,7 +56,7 @@ private: int port; public: - ConnectionConfiguration(const std::string &host, int port); + ConnectionConfiguration(std::string host, int port); virtual ~ConnectionConfiguration() = default; friend auto operator<<(std::ostream &out, const ConnectionConfiguration &configuration) -> std::ostream &; @@ -102,69 +95,6 @@ public: [[nodiscard]] auto ToString() const -> std::string override; }; -/** - * Helper class (no state, just member functions) which should only be used by - * the ConfigurationManager to initialize the logging framework from the stored - * configuration. - */ -class LoggingConfigurationHelper { -public: - friend class ConfigurationManager; - -private: - auto ConvertLogLevel(const std::string &string_level) const -> int; - auto CreateConsoleSinkConfiguration(const object &from, const std::string &name, int level) const - -> std::shared_ptr<caosdb::logging::SinkConfiguration>; - auto CreateSyslogSinkConfiguration(const object &from, const std::string &name, int level) const - -> std::shared_ptr<caosdb::logging::SinkConfiguration>; - auto CreateFileSinkConfiguration(const object &from, const std::string &name, int level) const - -> std::shared_ptr<caosdb::logging::SinkConfiguration>; - auto CreateSinkConfiguration(const object &from, const std::string &name, int default_level) const - -> std::shared_ptr<caosdb::logging::SinkConfiguration>; - auto CreateLoggingConfiguration(const object &from) const - -> caosdb::logging::LoggingConfiguration; -}; - -/** - * Helper class (no state, just member functions) which should only be used by - * the ConfigurationManager to construct Connection instances from the stored - * configuration. - */ -class ConnectionConfigurationHelper { -public: - friend class ConfigurationManager; - -private: - /** - * @param from - a single connection configuration. - */ - auto CreateCertificateProvider(const object &from) const -> std::unique_ptr<CertificateProvider>; - - /** - * @param from - a single connection configuration. - */ - auto CreateAuthenticator(const object &from) const -> std::unique_ptr<Authenticator>; - - /** - * @param from - a single connection configuration. - */ - auto CreateConnectionConfiguration(const bool tls, const std::string &host, const int port, - const CertificateProvider *certificate_provider, - const Authenticator *authenticator) const - -> std::unique_ptr<ConnectionConfiguration>; - - /** - * @param from - a single connection configuration. - */ - auto IsTls(const object &from) const -> bool; - - /** - * @param from - a single connection configuration. - */ - auto CreateConnectionConfiguration(const object &from) const - -> std::unique_ptr<ConnectionConfiguration>; -}; - /** * Reads the configuration file and keeps the configuration. Singleton. * @@ -225,13 +155,12 @@ public: private: Arena arena; - value json_configuration; - ConnectionConfigurationHelper connection_configuration_helper; - LoggingConfigurationHelper logging_configuration_helper; + JsonValue json_configuration; - inline ConfigurationManager(){ - // InitializeDefaults(); - }; + inline ConfigurationManager() + : json_configuration(nullptr){ + // InitializeDefaults(); + }; /** * Initialize this ConfigurationManager with the defaults. @@ -242,22 +171,6 @@ private: */ auto InitializeDefaults() -> int; - /** - * Return a json object representing the current configuration. - */ - auto GetConfiguration() const -> const object &; - - /** - * Return the connection configurations. - */ - auto GetConnections() const -> const object &; - - /** - * Return the configuration for the connection with the given name (as a json - * object). - */ - auto GetConnection(const std::string &name) const -> const object &; - /** * Reset this ConfigurationManager. * diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h index 2ff36d7242c5bcb8739bbca839e749825e4f6825..23b6e2f612a52e85df9eb2682b6cac11fd594063 100644 --- a/include/caosdb/entity.h +++ b/include/caosdb/entity.h @@ -29,29 +29,25 @@ #ifndef CAOSDB_ENTITY_H #define CAOSDB_ENTITY_H -#include "caosdb/data_type.h" // for DataType -#include "caosdb/entity/v1/main.pb.h" // for RepeatedPtrField -#include "caosdb/logging.h" // for CAOSDB_LOG_WARN -#include "caosdb/message_code.h" // for get_message_code -#include "caosdb/protobuf_helper.h" // for get_arena -#include "caosdb/status_code.h" // for StatusCode -#include "caosdb/value.h" // for Value -#include <boost/log/core/record.hpp> // for record -#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... -#include <boost/log/sources/record_ostream.hpp> // for basic_record_... -#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... -#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... -#include <cstdint> // for int64_t -#include <filesystem> // for path -#include <google/protobuf/arena.h> // for Arena -#include <google/protobuf/message.h> // for RepeatedPtrField -#include <iosfwd> // for streamsize -#include <iterator> // for iterator, output_iterato... -#include <map> // for map -#include <stdexcept> // for out_of_range -#include <string> // for string, basic... -#include <utility> // for move -#include <vector> // for vector +#include "caosdb/data_type.h" // for DataType +#include "caosdb/entity/v1/main.pb.h" // for RepeatedPtrField +#include "caosdb/file_descriptor.h" // for FileDescriptor +#include "caosdb/logging.h" // for CAOSDB_LOG_WARN +#include "caosdb/message_code.h" // for get_message_code +#include "caosdb/protobuf_helper.h" // for get_arena +#include "caosdb/status_code.h" // for StatusCode +#include "caosdb/value.h" // for Value +#include <cstdint> // for int64_t +#include <filesystem> // for path +#include <google/protobuf/arena.h> // for Arena +#include <google/protobuf/message.h> // for RepeatedPtrField +#include <iterator> // for iterator, output_iterato... +#include <map> // for map +#include <stdexcept> // for out_of_range +#include <string> // for string, basic... +#include <utility> // for move +#include <vector> // for vector +// IWYU pragma: no_include "net/proto2/public/repeated_field.h" namespace caosdb::entity { using caosdb::entity::v1::IdResponse; @@ -60,7 +56,6 @@ using std::filesystem::is_directory; using ProtoParent = caosdb::entity::v1::Parent; using ProtoProperty = caosdb::entity::v1::Property; using ProtoEntity = caosdb::entity::v1::Entity; -using ProtoFileDescriptor = caosdb::entity::v1::FileDescriptor; using ProtoMessage = caosdb::entity::v1::Message; using ProtoValue = caosdb::entity::v1::Value; using ProtoDataType = caosdb::entity::v1::DataType; @@ -109,12 +104,6 @@ const std::map<Role, std::string> role_names = {{Role::UNSPECIFIED, "UNSPECIFIED {Role::PROPERTY, "PROPERTY"}, {Role::FILE, "FILE"}}; -struct FileDescriptor { - FileTransmissionId *file_transmission_id; - ProtoFileDescriptor *wrapped; - std::filesystem::path local_path; -}; - /** * Abstract base class for Messages, Properties and Parents container classes. * @@ -719,6 +708,9 @@ public: : static_cast<ProtoDataType *>(nullptr)) { properties.wrapped = this->wrapped->mutable_properties(); parents.wrapped = this->wrapped->mutable_parents(); + if (this->wrapped->has_file_descriptor()) { + file_descriptor.wrapped = this->wrapped->mutable_file_descriptor(); + } }; explicit inline Entity(EntityResponse *response) : Entity(response->mutable_entity()) { this->errors.wrapped->Swap(response->mutable_errors()); diff --git a/include/caosdb/file_descriptor.h b/include/caosdb/file_descriptor.h new file mode 100644 index 0000000000000000000000000000000000000000..c6d6784c6cc73632dfdd871720c241a59ab03079 --- /dev/null +++ b/include/caosdb/file_descriptor.h @@ -0,0 +1,46 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2022 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + +/** + * @brief File descriptors are used to represent directories, links and files + * of the caosdb file system and their meta data. + * @file caosdb/entity.h + * @author Timm Fitchen + * @date 2022-01-21 + */ +#ifndef CAOSDB_FILE_DESCRIPTOR_H +#define CAOSDB_FILE_DESCRIPTOR_H + +#include "caosdb/entity/v1/main.pb.h" // for RepeatedPtrField +#include <filesystem> // for path + +namespace caosdb::entity { +using ProtoFileDescriptor = caosdb::entity::v1::FileDescriptor; +using caosdb::entity::v1::FileTransmissionId; + +struct FileDescriptor { + FileTransmissionId *file_transmission_id; + ProtoFileDescriptor *wrapped; + std::filesystem::path local_path; +}; + +} // namespace caosdb::entity +#endif diff --git a/include/caosdb/file_transmission/download_request_handler.h b/include/caosdb/file_transmission/download_request_handler.h index 0a8e13aac4237b2a9c5e1262dd8fdd0b742a0f29..9c82b232bed9e0b702c38eeeeb762899a3071528 100644 --- a/include/caosdb/file_transmission/download_request_handler.h +++ b/include/caosdb/file_transmission/download_request_handler.h @@ -49,15 +49,16 @@ #ifndef CAOSDB_FILE_TRANSMISSION_DOWNLOAD_REQUEST_HANDLER_H #define CAOSDB_FILE_TRANSMISSION_DOWNLOAD_REQUEST_HANDLER_H -#include "caosdb/entity.h" // for FileDescriptor +#include "caosdb/file_descriptor.h" // for FileDescriptor #include "caosdb/entity/v1/main.grpc.pb.h" // for FileTransmissionS... #include "caosdb/entity/v1/main.pb.h" // for FileDownloadResponse #include "caosdb/file_transmission/file_writer.h" // for FileWriter #include "caosdb/handler_interface.h" // for HandlerTag, Handl... -#include <grpcpp/impl/codegen/async_stream.h> // for ClientAsyncReader -#include <grpcpp/impl/codegen/client_context.h> // for ClientContext -#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue -#include <grpcpp/impl/codegen/status.h> // for Status +#include <cstdint> // for uint64_t +#include <grpcpp/client_context.h> // for ClientContext +#include <grpcpp/completion_queue.h> // for CompletionQueue +#include <grpcpp/support/async_stream.h> // for ClientAsyncReader +#include <grpcpp/support/status.h> // for Status #include <memory> // for unique_ptr namespace caosdb::transaction { @@ -83,7 +84,11 @@ public: DownloadRequestHandler(DownloadRequestHandler &&) = delete; DownloadRequestHandler &operator=(DownloadRequestHandler &&) = delete; - void Start() override { OnNext(true); } + void Start() override { + if (state_ == CallState::NewCall) { + OnNext(true); + } + } bool OnNext(bool ok) override; @@ -115,7 +120,7 @@ protected: FileDescriptor file_descriptor_; - unsigned long long bytesReceived_; + uint64_t bytesReceived_; }; } // namespace caosdb::transaction diff --git a/include/caosdb/file_transmission/file_reader.h b/include/caosdb/file_transmission/file_reader.h index 14f8614896de79b15e9bb793a04ac17f40f27fec..380b6a838b77e9f80f91f93c84eb0b0f888de2e0 100644 --- a/include/caosdb/file_transmission/file_reader.h +++ b/include/caosdb/file_transmission/file_reader.h @@ -50,8 +50,9 @@ #define CAOSDB_FILE_TRANSMISSION_FILE_READER_H #include <cstddef> // for size_t -#include <filesystem> // for ifstream -#include <fstream> // for ifstream, size_t +#include <cstdint> // for uint64_t +#include <filesystem> // for path, exists +#include <fstream> // for ifstream #include <string> // for string namespace caosdb::transaction { @@ -71,7 +72,7 @@ public: FileReader(FileReader &&) = default; FileReader &operator=(FileReader &&) = default; - unsigned long long fileSize() const { return size_; } + uint64_t fileSize() const { return size_; } std::size_t read(std::string &buffer); @@ -80,7 +81,7 @@ private: std::ifstream stream_; std::filesystem::path filename_; - unsigned long long size_; + uint64_t size_; }; } // namespace caosdb::transaction diff --git a/include/caosdb/file_transmission/register_file_upload_handler.h b/include/caosdb/file_transmission/register_file_upload_handler.h index 37df2dbf0fb895cd77f63bab2596a0b124c48b9c..37cc6cf6ae83aa23781cc65924cefba83be22627 100644 --- a/include/caosdb/file_transmission/register_file_upload_handler.h +++ b/include/caosdb/file_transmission/register_file_upload_handler.h @@ -49,13 +49,13 @@ #ifndef CAOSDB_FILE_TRANSMISSION_REGISTER_FILE_UPLOAD_H #define CAOSDB_FILE_TRANSMISSION_REGISTER_FILE_UPLOAD_H -#include "caosdb/entity/v1/main.grpc.pb.h" // for FileTransmissionS... -#include "caosdb/entity/v1/main.pb.h" // for FileDownloadResponse -#include "caosdb/handler_interface.h" // for HandlerTag, Handl... -#include "caosdb/unary_rpc_handler.h" -#include <grpcpp/impl/codegen/async_unary_call.h> // for ClientAsyncRespons... -#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue -#include <memory> // for unique_ptr +#include "caosdb/entity/v1/main.grpc.pb.h" // for FileTransmissionS... +#include "caosdb/entity/v1/main.pb.h" // for FileDownloadResponse +#include "caosdb/handler_interface.h" // for HandlerTag, Handl... +#include "caosdb/unary_rpc_handler.h" // for UnaryRpcHandler +#include <grpcpp/completion_queue.h> // for CompletionQueue +#include <grpcpp/support/async_unary_call.h> // for ClientAsyncResponseReader +#include <memory> // for unique_ptr namespace caosdb::transaction { diff --git a/include/caosdb/file_transmission/upload_request_handler.h b/include/caosdb/file_transmission/upload_request_handler.h index cb9748f868290d362bb21bb66eaacb0267c14eed..f9d6aab9abf1800ab7e5ebece6f2f7011f86cca0 100644 --- a/include/caosdb/file_transmission/upload_request_handler.h +++ b/include/caosdb/file_transmission/upload_request_handler.h @@ -49,16 +49,16 @@ #ifndef CAOSDB_FILE_TRANSMISSION_UPLOAD_REQUEST_HANDLER_H #define CAOSDB_FILE_TRANSMISSION_UPLOAD_REQUEST_HANDLER_H -#include "caosdb/entity.h" // for FileDescriptor #include "caosdb/entity/v1/main.grpc.pb.h" // for FileTransmissionS... #include "caosdb/entity/v1/main.pb.h" // for FileUploadRequest +#include "caosdb/file_descriptor.h" // for FileDescriptor #include "caosdb/file_transmission/file_reader.h" // for FileReader #include "caosdb/handler_interface.h" // for HandlerTag, Handl... #include <cstdint> // for uint64_t -#include <grpcpp/impl/codegen/async_stream.h> // for ClientAsyncWriter -#include <grpcpp/impl/codegen/client_context.h> // for ClientContext -#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue -#include <grpcpp/impl/codegen/status.h> // for Status +#include <grpcpp/client_context.h> // for ClientContext +#include <grpcpp/completion_queue.h> // for CompletionQueue +#include <grpcpp/support/async_stream.h> // for ClientAsyncReader +#include <grpcpp/support/status.h> // for Status #include <memory> // for unique_ptr namespace caosdb::transaction { @@ -81,7 +81,11 @@ public: UploadRequestHandler(UploadRequestHandler &&) = delete; UploadRequestHandler &operator=(UploadRequestHandler &&) = delete; - void Start() override { OnNext(true); } + void Start() override { + if (state_ == CallState::NewCall) { + OnNext(true); + } + } bool OnNext(bool ok) override; diff --git a/include/caosdb/logging.h b/include/caosdb/logging.h index eb99f9a51b3630ce48849acf935b316044aa82b2..e50c70f69bc0ed45e8f6a60f78cd11e4142b0568 100644 --- a/include/caosdb/logging.h +++ b/include/caosdb/logging.h @@ -23,32 +23,58 @@ #ifndef CAOSDB_LOGGING_H #define CAOSDB_LOGGING_H -#include "caosdb/log_level.h" // for CAOSDB_LOG_... -#include <boost/log/sources/record_ostream.hpp> // IWYU pragma: keep -#include <boost/log/sources/severity_channel_logger.hpp> // for BOOST_LOG_C... -#include <boost/log/utility/setup/settings.hpp> // for settings -#include <boost/smart_ptr/intrusive_ptr.hpp> // for intrusive_ptr -#include <boost/smart_ptr/intrusive_ref_counter.hpp> // for intrusive_p... -#include <memory> // for shared_ptr -#include <string> // for string -#include <vector> // for vector +#include "caosdb/log_level.h" // for CAOSDB_LOG_... +#include <cstdint> // for uint64_t +#include <iosfwd> // for streamsize +#include <memory> // for shared_ptr +#include <string> // for string +#include <vector> // for vector namespace caosdb::logging { const std::string logger_name = "caosdb::logging"; -typedef boost::log::sources::severity_channel_logger_mt<int, std::string> boost_logger_class; - -class logger { +class LoggerOutputStream { public: - static auto get() -> boost_logger_class & { return logger::GetInstance()._logger_instance; } + LoggerOutputStream(std::string channel, int level); + auto operator<<(int msg) -> LoggerOutputStream &; + auto operator<<(uint64_t msg) -> LoggerOutputStream &; + auto operator<<(int64_t msg) -> LoggerOutputStream &; + auto operator<<(std::streambuf *msg) -> LoggerOutputStream &; + auto operator<<(const char *msg) -> LoggerOutputStream &; + auto operator<<(const std::string &msg) -> LoggerOutputStream &; + auto operator<<(void *msg) -> LoggerOutputStream &; + static auto get(const std::string &channel, int level) -> LoggerOutputStream { + return LoggerOutputStream(channel, level); + } private: - static logger &GetInstance() { - static logger instance; - return instance; + std::string channel; + int level; +}; + +/** + * Helper class for logging the entering and leaving of a function or method. + * + * Please Use the macro + * + * CAOSDB_LOG_TRACE_ENTER_AND_LEAVE(logger_name, function_name); + */ +class TraceEnterLeaveLogger { +public: + inline TraceEnterLeaveLogger(const std::string &channel, const std::string &function_name) + : channel(channel), function_name(function_name) { + caosdb::logging::LoggerOutputStream::get(this->channel, CAOSDB_LOG_LEVEL_TRACE) + << "Enter " << this->function_name; + } + inline ~TraceEnterLeaveLogger() { + caosdb::logging::LoggerOutputStream::get(this->channel, CAOSDB_LOG_LEVEL_TRACE) + << "Leave " << this->function_name; } - boost_logger_class _logger_instance; + +private: + const std::string &channel; + const std::string function_name; }; /** @@ -112,7 +138,7 @@ public: friend auto initialize_logging(const LoggingConfiguration &logging_configuration) -> void; protected: - virtual auto Configure(boost::log::settings &settings) const -> void; + virtual auto Configure(void *settings) const -> void; private: std::string name; @@ -128,7 +154,7 @@ public: protected: typedef SinkConfiguration sink_configuration; - virtual auto Configure(boost::log::settings &settings) const -> void override; + virtual auto Configure(void *settings) const -> void override; private: const std::string destination = "Console"; @@ -151,7 +177,7 @@ public: protected: typedef SinkConfiguration sink_configuration; - virtual auto Configure(boost::log::settings &settings) const -> void override; + virtual auto Configure(void *settings) const -> void override; private: const std::string destination = "TextFile"; @@ -196,17 +222,20 @@ void caosdb_log_trace(const char *channel, const char *msg); } // namespace caosdb::logging #define CAOSDB_LOG_FATAL(Channel) \ - BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel, CAOSDB_LOG_LEVEL_FATAL) + caosdb::logging::LoggerOutputStream::get(Channel, CAOSDB_LOG_LEVEL_FATAL) #define CAOSDB_LOG_ERROR(Channel) \ - BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel, CAOSDB_LOG_LEVEL_ERROR) + caosdb::logging::LoggerOutputStream::get(Channel, CAOSDB_LOG_LEVEL_ERROR) #define CAOSDB_LOG_WARN(Channel) \ - BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel, CAOSDB_LOG_LEVEL_WARN) + caosdb::logging::LoggerOutputStream::get(Channel, CAOSDB_LOG_LEVEL_WARN) #define CAOSDB_LOG_INFO(Channel) \ - BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel, CAOSDB_LOG_LEVEL_INFO) + caosdb::logging::LoggerOutputStream::get(Channel, CAOSDB_LOG_LEVEL_INFO) #define CAOSDB_LOG_DEBUG(Channel) \ - BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel, CAOSDB_LOG_LEVEL_DEBUG) + caosdb::logging::LoggerOutputStream::get(Channel, CAOSDB_LOG_LEVEL_DEBUG) #define CAOSDB_LOG_TRACE(Channel) \ - BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel, CAOSDB_LOG_LEVEL_TRACE) + caosdb::logging::LoggerOutputStream::get(Channel, CAOSDB_LOG_LEVEL_TRACE) + +#define CAOSDB_LOG_TRACE_ENTER_AND_LEAVE(Channel, FunctionName) \ + const caosdb::logging::TraceEnterLeaveLogger trace_enter_leave_logger(Channel, FunctionName); #define CAOSDB_LOG_ERROR_AND_RETURN_STATUS(Channel, StatusCode, Message) \ CAOSDB_LOG_ERROR(Channel) << "StatusCode (" << StatusCode << ") " \ diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h index 0e11d3f1a5d9145795b2bdcd4e1c82c31e360aa2..e73840e4f2aa355961f653b24817784a2773313f 100644 --- a/include/caosdb/transaction.h +++ b/include/caosdb/transaction.h @@ -21,27 +21,26 @@ #ifndef CAOSDB_TRANSACTION_H #define CAOSDB_TRANSACTION_H -#include "caosdb/entity.h" // for Entity, FileDe... -#include "caosdb/entity/v1/main.grpc.pb.h" // for EntityTransact... -#include "caosdb/entity/v1/main.pb.h" // for MultiTransacti... -#include "caosdb/handler_interface.h" // for HandlerInterface -#include "caosdb/transaction_handler.h" // for EntityTransactionHandler -#include "caosdb/logging.h" // for CAOSDB_LOG_ERR... -#include "caosdb/protobuf_helper.h" // for get_arena -#include "caosdb/status_code.h" // for StatusCode -#include "caosdb/transaction_status.h" // for StatusCode -#include <boost/log/core/record.hpp> // for record -#include <boost/log/sources/record_ostream.hpp> // for basic_record_o... -#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_E... -#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_S... -#include <google/protobuf/arena.h> // for Arena -#include <google/protobuf/util/json_util.h> // for MessageToJsonS... -#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue -#include <algorithm> // for max -#include <iterator> // for iterator, next -#include <map> // for map +#include "caosdb/entity.h" // for Entity, FileDe... +#include "caosdb/entity/v1/main.grpc.pb.h" // for EntityTransact... +#include "caosdb/entity/v1/main.pb.h" // for MultiTransacti... +#include "caosdb/file_descriptor.h" // for FileDescriptor +#include "caosdb/handler_interface.h" // for HandlerInterface +#include "caosdb/transaction_handler.h" // for EntityTransactionHandler +#include "caosdb/logging.h" // for CAOSDB_LOG_ERR... +#include "caosdb/protobuf_helper.h" // for get_arena +#include "caosdb/status_code.h" // for StatusCode +#include "caosdb/transaction_status.h" // for StatusCode +#include <algorithm> // for max +#include <future> // for async, future +#include <google/protobuf/arena.h> // for Arena +#include <google/protobuf/util/json_util.h> // for MessageToJsonS... +#include <grpcpp/completion_queue.h> // for CompletionQueue +#include <iterator> // for iterator, next +#include <map> // for map // IWYU pragma: no_include <ext/alloc_traits.h> #include <memory> // for unique_ptr +#include <mutex> // for mutex #include <string> // for string #include <utility> // for move #include <vector> // for vector @@ -51,7 +50,8 @@ * query) can be added as a sub-request to a transaction. */ #define ASSERT_CAN_ADD_RETRIEVAL \ - if (!IsStatus(TransactionStatus::INITIAL()) && !IsStatus(TransactionStatus::GO_ON())) { \ + if (this->status.GetCode() != StatusCode::INITIAL && \ + this->status.GetCode() != StatusCode::GO_ON) { \ return StatusCode::TRANSACTION_STATUS_ERROR; \ } \ switch (this->transaction_type) { \ @@ -87,7 +87,8 @@ * sub-request to a transaction. */ #define ASSERT_CAN_ADD_DELETION \ - if (!IsStatus(TransactionStatus::INITIAL()) && !IsStatus(TransactionStatus::GO_ON())) { \ + if (this->status.GetCode() != StatusCode::INITIAL && \ + this->status.GetCode() != StatusCode::GO_ON) { \ return StatusCode::TRANSACTION_STATUS_ERROR; \ } \ switch (this->transaction_type) { \ @@ -109,7 +110,8 @@ * sub-request to a transaction. */ #define ASSERT_CAN_ADD_INSERTION \ - if (!IsStatus(TransactionStatus::INITIAL()) && !IsStatus(TransactionStatus::GO_ON())) { \ + if (this->status.GetCode() != StatusCode::INITIAL && \ + this->status.GetCode() != StatusCode::GO_ON) { \ return StatusCode::TRANSACTION_STATUS_ERROR; \ } \ switch (this->transaction_type) { \ @@ -131,7 +133,8 @@ * sub-request to a transaction. */ #define ASSERT_CAN_ADD_UPDATE \ - if (!IsStatus(TransactionStatus::INITIAL()) && !IsStatus(TransactionStatus::GO_ON())) { \ + if (this->status.GetCode() != StatusCode::INITIAL && \ + this->status.GetCode() != StatusCode::GO_ON) { \ return StatusCode::TRANSACTION_STATUS_ERROR; \ } \ switch (this->transaction_type) { \ @@ -177,6 +180,7 @@ using caosdb::entity::v1::MultiTransactionResponse; using caosdb::entity::v1::RegisterFileUploadRequest; using caosdb::entity::v1::RegisterFileUploadResponse; using caosdb::transaction::TransactionStatus; +using RetrieveResponse = caosdb::entity::v1::RetrieveResponse; using TransactionResponseCase = caosdb::entity::v1::TransactionResponse::TransactionResponseCase; using caosdb::utility::get_arena; using google::protobuf::Arena; @@ -364,10 +368,6 @@ public: */ auto DeleteById(const std::string &id) noexcept -> StatusCode; - inline auto IsStatus(const TransactionStatus &status) const noexcept -> bool { - return this->status.GetCode() == status.GetCode(); - }; - /** * Execute this transaction in blocking mode and return the status. */ @@ -378,8 +378,7 @@ public: * * A client may request the current status at any time via GetStatus(). * - * Use WaitForIt() to join the back-ground execution of this transaction, otherwise the behaviour - * of getting the ResultSet is undefined. + * Use WaitForIt() to join the back-ground execution of this transaction. */ auto ExecuteAsynchronously() noexcept -> StatusCode; @@ -390,7 +389,7 @@ public: * Use this after ExecuteAsynchronously(), otherwise the TransactionStatus still remains * EXECUTING. */ - [[nodiscard]] auto WaitForIt() const noexcept -> TransactionStatus; + auto WaitForIt() const noexcept -> TransactionStatus; /** * Return the current status of the transaction. @@ -401,12 +400,11 @@ public: * Return the ResultSet of this transaction. * * Note: If this method is called before the transaction has terminated - * (GetStatus().GetCode() < 0) this method has undefined behavior. - * - * Instead, do Execute() or WaitForIt() and only call this method afterwards. + * successfully (as indicated by GetStatus().GetCode == 0) this method has + * undefined behavior. */ [[nodiscard]] inline auto GetResultSet() const noexcept -> const ResultSet & { - if (this->result_set == nullptr) { + if (this->GetStatus().GetCode() < 0) { CAOSDB_LOG_ERROR(logger_name) << "GetResultSet was called before the transaction has terminated. This is a programming " "error of the code which uses the transaction."; @@ -414,7 +412,12 @@ public: // terminates and the result_set is being overriden, the unique_ptr // created here will be deleted and any client of the return ResultSet // will have a SegFault. - this->result_set = std::make_unique<MultiResultSet>(std::vector<std::unique_ptr<Entity>>()); + } else if (this->GetStatus().GetCode() == StatusCode::SPOILED) { + CAOSDB_LOG_ERROR(logger_name) + << "GetResultSet was called on a \"spoiled\" transaction. That means " + "that the result set has already been released via " + "ReleaseResultSet(). This is a programming error of the code which " + "uses the transaction."; } return *(this->result_set.get()); } @@ -431,7 +434,9 @@ public: */ [[nodiscard]] inline auto ReleaseResultSet() noexcept -> const ResultSet * { this->status = TransactionStatus::SPOILED(); - return this->result_set.release(); + auto result_set = this->result_set.release(); + this->result_set = std::make_unique<MultiResultSet>(std::vector<std::unique_ptr<Entity>>()); + return result_set; } /** @@ -484,22 +489,21 @@ public: */ inline auto GetUploadFiles() const -> const std::vector<FileDescriptor> & { return upload_files; } -protected: /** - * Await and process the current handler's results. + * Cancel this transaction after it has been started with + * ExecuteAsynchronously(). * - * This implies consecutive calls to the handler's OnNext function. + * This will cancel any active handler and drains the completion_queue. */ - auto ProcessCalls() -> TransactionStatus; + auto Cancel() -> void; +protected: /** - * Cancels any active handler and drains the completion_queue. + * Await and process the current handler's results. * - * Can stay protected until ExecuteAsynchronously() is actually asynchronous. - * Then it is also intended for aborting an execution after it has already - * started. + * This implies consecutive calls to the handler's OnNext function. */ - auto Cancel() -> void; + auto ProcessCalls() -> TransactionStatus; /** * Return the Arena where this transaction may create Message instances. @@ -511,6 +515,22 @@ protected: inline auto GetArena() const -> Arena * { return get_arena(); } private: + /** + * Await and process the termination of this transaction. + * + * To be called at the end of DoExecuteTransaction on success. + */ + auto ProcessTerminated() const noexcept -> void; + auto ProcessRetrieveResponse(RetrieveResponse *retrieve_response, + std::vector<std::unique_ptr<Entity>> *entities, + bool *set_error) const noexcept -> std::unique_ptr<Entity>; + /** + * This functions actually does all the work. Also, it is the one which is + * going to be send to the background thread by ExecuteAsynchronously. + */ + auto DoExecuteTransaction() noexcept -> void; + mutable std::mutex transaction_mutex; + mutable std::future<void> transaction_future; grpc::CompletionQueue completion_queue; std::unique_ptr<HandlerInterface> handler_; @@ -519,7 +539,6 @@ private: bool has_query = false; TransactionType transaction_type = TransactionType::NONE; - mutable std::unique_ptr<ResultSet> result_set; mutable TransactionStatus status = TransactionStatus::INITIAL(); std::shared_ptr<EntityTransactionService::Stub> entity_service; std::shared_ptr<FileTransmissionService::Stub> file_service; @@ -527,6 +546,7 @@ private: mutable MultiTransactionResponse *response; std::string error_message; mutable long query_count; + mutable std::unique_ptr<ResultSet> result_set; }; template <class InputIterator> diff --git a/include/caosdb/transaction_handler.h b/include/caosdb/transaction_handler.h index 6fa386be32628d5d0ae80a90dda1e1293d1eae70..3f9fc811a2137d9b8c8d0205c15704179a1c3554 100644 --- a/include/caosdb/transaction_handler.h +++ b/include/caosdb/transaction_handler.h @@ -1,11 +1,11 @@ #pragma once -#include "caosdb/entity/v1/main.grpc.pb.h" // for FileTransmissionS... -#include "caosdb/entity/v1/main.pb.h" // for FileDownloadResponse -#include "caosdb/handler_interface.h" // for HandlerTag -#include "caosdb/unary_rpc_handler.h" // for HandlerTag, Handl... -#include <grpcpp/impl/codegen/async_unary_call.h> // for ClientAsyncRespons... -#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue -#include <memory> // for unique_ptr +#include "caosdb/entity/v1/main.grpc.pb.h" // for FileTransmissionS... +#include "caosdb/entity/v1/main.pb.h" // for FileDownloadResponse +#include "caosdb/handler_interface.h" // for HandlerTag +#include "caosdb/unary_rpc_handler.h" // for HandlerTag, Handl... +#include <grpcpp/completion_queue.h> // for CompletionQueue +#include <grpcpp/support/async_unary_call.h> // for ClientAsyncResponseReader +#include <memory> // for unique_ptr namespace caosdb::transaction { diff --git a/include/caosdb/transaction_status.h b/include/caosdb/transaction_status.h index bdfd65595090acde2b177dd7486492738901b0e0..d46f3911aa17fbf518fd43b91e3568e4e4740c2f 100644 --- a/include/caosdb/transaction_status.h +++ b/include/caosdb/transaction_status.h @@ -92,6 +92,13 @@ public: * This status means that the transaction has been executed successfully. */ CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(SUCCESS, StatusCode::SUCCESS) + /** + * Factory for a CANCELLED status. + * + * This status means that the transaction has been canceled and should not be + * used anymore. + */ + CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(CANCELLED, StatusCode::CANCELLED) /** * Factory for a CONNECTION_ERROR status. * diff --git a/include/caosdb/unary_rpc_handler.h b/include/caosdb/unary_rpc_handler.h index 0d35eec0f03d546b70ae5c1c59aa057896bb575b..06b9457ceae715b4319739f46d810d7a892612d7 100644 --- a/include/caosdb/unary_rpc_handler.h +++ b/include/caosdb/unary_rpc_handler.h @@ -49,11 +49,11 @@ #ifndef CAOSDB_UNARY_RPC_HANDLER_H #define CAOSDB_UNARY_RPC_HANDLER_H -#include "caosdb/handler_interface.h" // for HandlerTag, Handl... -#include "caosdb/transaction_status.h" // for TransactionStatus -#include <grpcpp/impl/codegen/client_context.h> // for ClientContext -#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue -#include <grpcpp/impl/codegen/status.h> // for Status +#include "caosdb/handler_interface.h" // for HandlerTag, Handl... +#include "caosdb/transaction_status.h" // for TransactionStatus +#include <grpcpp/client_context.h> // for ClientContext +#include <grpcpp/completion_queue.h> // for CompletionQueue +#include <grpcpp/support/status.h> // for Status namespace caosdb::transaction { @@ -63,8 +63,10 @@ public: : HandlerInterface(), state_(CallState::NewCall), completion_queue(completion_queue) {} void Start() override { - transaction_status = TransactionStatus::EXECUTING(); - OnNext(true); + if (state_ == CallState::NewCall) { + transaction_status = TransactionStatus::EXECUTING(); + OnNext(true); + } } bool OnNext(bool ok) override; diff --git a/include/caosdb/utility.h b/include/caosdb/utility.h index 0a4d65e883ccfb6cc8f41d1c412bef5433b89417..13e3dfeb99dc5415a733d5b6dcea852705b1662d 100644 --- a/include/caosdb/utility.h +++ b/include/caosdb/utility.h @@ -21,27 +21,15 @@ #ifndef CAOSDB_UTILS_H #define CAOSDB_UTILS_H -#include "caosdb/data_type.h" // for AtomicDataType -#include "caosdb/entity.h" // for Importance, Role -#include <boost/beast/core/detail/base64.hpp> // for encoded_size -#include <boost/beast/core/detail/base64.ipp> // for encode -#include <boost/filesystem/string_file.hpp> // for load_string_file -#include <boost/json/stream_parser.hpp> // for stream_parser -#include <boost/json/value.hpp> // for value -#include <boost/lexical_cast.hpp> // for lexical_cast -#include <cassert> // for assert -#include <cstdlib> // for getenv -#include <filesystem> // for path -#include <fstream> // for basic_istream<>::__ist... -#include <memory> // for allocator, unique_ptr -#include <stdexcept> // for logic_error -#include <string> // for string, operator+, cha... -#include <type_traits> // for underlying_type_t -#include <typeinfo> // for type_info +#include "caosdb/data_type.h" // for AtomicDataType +#include "caosdb/entity.h" // for Importance, Role +#include <cstdlib> // for getenv +#include <filesystem> // for path +#include <fstream> // for basic_istream<>::__ist... +#include <memory> // for shared_ptr +#include <string> // for string, operator+, cha... namespace caosdb::utility { -using boost::json::stream_parser; -using boost::json::value; using std::ifstream; using std::filesystem::exists; using std::filesystem::path; @@ -49,12 +37,7 @@ using std::filesystem::path; /** * @brief Get the name of the enum value. May be useful for higher-order CaosDB clients. */ -template <typename Enum> auto getEnumNameFromValue(Enum v) -> std::string { - if (std::is_same_v<std::underlying_type_t<Enum>, int>) { - return boost::lexical_cast<std::string>(static_cast<int>(v)); - } - throw std::logic_error(std::string("Enum type ") + typeid(v).name() + " not implemented."); -} +template <typename Enum> auto getEnumNameFromValue(Enum v) -> std::string; // Forward declaration of specializations template <> @@ -84,11 +67,7 @@ auto getEnumValueFromName<caosdb::entity::Role>(const std::string &name) -> caos /** * @brief Read a text file into a string and return the file's content. */ -inline auto load_string_file(const path &file_path) -> std::string { - std::string result; - boost::filesystem::load_string_file(file_path.string(), result); - return result; -} +auto load_string_file(const path &file_path) -> std::string; /** * @brief Return the environment variable KEY, or FALLBACK if it does not exist. @@ -115,37 +94,82 @@ inline auto get_env_fallback(const std::string &key, const std::string &fallback } /** - * @brief Encode string as base64 + * @brief JsonValue is a thin wrapper around a implementation specific + * third-party json object (e.g. boost). */ -inline auto base64_encode(const std::string &plain) -> std::string { - auto size_plain = plain.size(); - auto size_encoded = boost::beast::detail::base64::encoded_size(size_plain); - - std::unique_ptr<char[]> encoded(new char[size_encoded]); - boost::beast::detail::base64::encode(encoded.get(), plain.c_str(), size_plain); - - // the encoded char[] is not null terminated, so explicitely set the length - return std::string(encoded.get(), encoded.get() + size_encoded); -} - -inline auto load_json_file(const path &json_file) -> value { - assert(exists(json_file)); +class JsonValue { +public: + /** + * Default Constructor. + * + * Creates an empty wrapper where `wrapped` is nullptr. + */ + JsonValue() : JsonValue(nullptr) {} + /** + * Constructor. + * + * By calling this constructor the ownership of the `wrapped` parameter is + * transferred to this object. + */ + JsonValue(void *wrapped); + /** + * Destructor. + * + * Also deletes the `wrapped` object. + */ + ~JsonValue(); + + /** + * Copy Constructor. + * + * Also copies the `wrapped` object. + */ + JsonValue(const JsonValue &other); + + /** + * Copy Assigment. + * + * Also copies the `wrapped` object. + */ + auto operator=(const JsonValue &other) -> JsonValue &; + + /** + * Move Constructor. + * + * Also moves the `wrapped` object. + */ + JsonValue(JsonValue &&other) noexcept; + + /** + * Move Assigment. + * + * Also moves the `wrapped` object. + */ + auto operator=(JsonValue &&other) noexcept -> JsonValue &; + + /** + * Reset this object. + * + * Also deletes `wrapped` sets it to the nullptr. + */ + auto Reset() -> void; + + /** + * An object which represents a JSON value. The object's class is an + * implementation detail. + */ + std::shared_ptr<void> wrapped; +}; - constexpr auto buffer_size = std::size_t(4096); - auto stream = ifstream(json_file); - stream.exceptions(std::ios_base::badbit); - - stream_parser parser; - auto result = std::string(); - auto buffer = std::string(buffer_size, '\0'); - while (stream.read(&buffer[0], buffer_size)) { - parser.write(buffer.c_str(), stream.gcount()); - } - parser.write(buffer.c_str(), stream.gcount()); +/** + * @brief Load json object from a json file and return it. + */ +auto load_json_file(const path &json_file) -> JsonValue; - assert(parser.done()); - return parser.release(); -} +/** + * @brief Encode string as base64 + */ +auto base64_encode(const std::string &plain) -> std::string; inline auto get_home_directory() -> const path { const auto *const home = getenv("HOME"); diff --git a/proto b/proto index 6f81c44a02b9258293bfb83b4de7831ef8d7c4e9..0b301401cf28d7a3edeb2c55e418f072b83cf5a7 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 6f81c44a02b9258293bfb83b4de7831ef8d7c4e9 +Subproject commit 0b301401cf28d7a3edeb2c55e418f072b83cf5a7 diff --git a/src/caosdb/authentication.cpp b/src/caosdb/authentication.cpp index ecfbe35344f557671d8529770f73a3987c389ab0..7faa8619b4153c8ff3c03549369b7d976a62e035 100644 --- a/src/caosdb/authentication.cpp +++ b/src/caosdb/authentication.cpp @@ -19,15 +19,11 @@ * */ #include "caosdb/authentication.h" -#include <grpcpp/impl/codegen/interceptor.h> // for Status -#include <grpcpp/impl/codegen/security/auth_context.h> // for AuthContext -#include <grpcpp/impl/codegen/status.h> // for Status, Status::OK -#include <grpcpp/impl/codegen/string_ref.h> // for string_ref -#include <grpcpp/security/credentials.h> // for MetadataCredentialsPlugin -#include <map> // for multimap -#include <memory> // for allocator, shared_ptr -#include <string> // for basic_string, operator+ -#include <utility> // for pair, move, make_pair +#include <grpcpp/security/credentials.h> // for MetadataCredentialsPlugin +#include <map> // for multimap +#include <memory> // for allocator, shared_ptr +#include <string> // for basic_string, operator+ +#include <utility> // for pair, move, make_pair namespace caosdb::authentication { using caosdb::utility::base64_encode; diff --git a/src/caosdb/configuration.cpp b/src/caosdb/configuration.cpp index 90cc97b3cebc1b39a1b376805ae73cc068173ef7..e1ffb23b3b1a2db5ecea79588c237bb36e79b1e1 100644 --- a/src/caosdb/configuration.cpp +++ b/src/caosdb/configuration.cpp @@ -19,35 +19,55 @@ * */ #include "caosdb/configuration.h" -#include "caosdb/authentication.h" // for Authenticator -#include "caosdb/connection.h" // for ConnectionManager -#include "caosdb/constants.h" // for LIBCAOSDB_CONF... -#include "caosdb/exceptions.h" // for ConfigurationE... -#include "caosdb/log_level.h" // for CAOSDB_DEFAULT... -#include "caosdb/status_code.h" // for StatusCode -#include "caosdb/utility.h" // for get_home_direc... -#include <boost/json/impl/object.hpp> // for object::at -#include <boost/json/object.hpp> // for object, objec... -#include <boost/json/string.hpp> // for string -#include <boost/json/string_view.hpp> // for string_view -#include <boost/json/value.hpp> // for value, key_va... -#include <boost/json/value_ref.hpp> // for object -#include <boost/log/core/record.hpp> // for record -#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... -#include <boost/log/sources/record_ostream.hpp> // for basic_record_o... -#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_E... -#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_S... -#include <cassert> // for assert -#include <cstdlib> // for getenv -#include <cstring> // for strcmp -#include <exception> // IWYU pragma: keep +#include "caosdb/authentication.h" // for Authenticator +#include "caosdb/connection.h" // for ConnectionManager +#include "caosdb/constants.h" // for LIBCAOSDB_CONF... +#include "caosdb/exceptions.h" // for ConfigurationE... +#include "caosdb/log_level.h" // for CAOSDB_DEFAULT... +#include "caosdb/logging.h" // for SinkConfiguration, Loggin... +#include "caosdb/status_code.h" // for StatusCode +#include "caosdb/utility.h" // for get_home_direc... +#include <boost/json/impl/object.hpp> // for object::at +#include <boost/json/object.hpp> // for object, objec... +#include <boost/json/string.hpp> // for string +#include <boost/json/string_view.hpp> // for string_view +#include <boost/json/value.hpp> // for value, key_va... +#include <boost/json/value_ref.hpp> // for object +#include <cassert> // for assert +#include <cstdlib> // for getenv +#include <cstring> // for strcmp +#include <exception> // IWYU pragma: keep // IWYU pragma: no_include <bits/exception.h> #include <grpcpp/security/credentials.h> // for SslCredentials #include <iterator> // for next #include <map> // for map -#include <sstream> // for basic_stringb... #include <stdexcept> // for out_of_range #include <string> // for string, operator+ +#include <utility> // for move + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define WRAPPED_JSON_CONFIGURATION(obj) \ + (static_cast<value *>((obj)->json_configuration.wrapped.get())) + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define GET_CONNECTIONS \ + if (!this->json_configuration.wrapped) { \ + throw ConfigurationError("This CaosDB client has not been configured."); \ + } \ + assert(WRAPPED_JSON_CONFIGURATION(this)->is_object()); \ + const auto &configuration = WRAPPED_JSON_CONFIGURATION(this)->as_object(); \ + if (!configuration.contains("connections")) { \ + throw ConfigurationError("This CaosDB client hasn't any configured connections."); \ + } \ + const auto &connections_value = configuration.at("connections"); \ + if (connections_value.is_null()) { \ + throw ConfigurationError("This CaosDB client hasn't any configured connections."); \ + } \ + assert(connections_value.is_object()); \ + const auto &connections = connections_value.as_object(); \ + if (connections.empty()) { \ + throw ConfigurationError("This CaosDB client hasn't any configured connections."); \ + } namespace caosdb::configuration { using boost::json::object; @@ -78,18 +98,15 @@ auto PemFileCertificateProvider::GetCertificatePem() const -> std::string { return this->certificate_provider; } -PemCertificateProvider::PemCertificateProvider(const std::string &certificate_provider) { - this->certificate_provider = certificate_provider; -} +PemCertificateProvider::PemCertificateProvider(std::string certificate_provider) + : certificate_provider(std::move(certificate_provider)) {} auto PemCertificateProvider::GetCertificatePem() const -> std::string { return this->certificate_provider; } -ConnectionConfiguration::ConnectionConfiguration(const std::string &host, int port) { - this->host = host; - this->port = port; -} +ConnectionConfiguration::ConnectionConfiguration(std::string host, int port) + : host(std::move(host)), port(port) {} auto ConnectionConfiguration::GetHost() const -> std::string { return this->host; } @@ -159,8 +176,7 @@ auto TlsConnectionConfiguration::ToString() const -> std::string { "," + this->certificate_provider + ")"; } -auto ConnectionConfigurationHelper::CreateCertificateProvider(const object &from) const - -> std::unique_ptr<CertificateProvider> { +auto CreateCertificateProvider(const object &from) -> std::unique_ptr<CertificateProvider> { std::unique_ptr<CertificateProvider> certificate_provider; if (from.contains("server_certificate_path")) { const value &path_str = from.at("server_certificate_path"); @@ -176,8 +192,7 @@ auto ConnectionConfigurationHelper::CreateCertificateProvider(const object &from return certificate_provider; } -auto ConnectionConfigurationHelper::CreateAuthenticator(const object &from) const - -> std::unique_ptr<Authenticator> { +auto CreateAuthenticator(const object &from) -> std::unique_ptr<Authenticator> { std::unique_ptr<Authenticator> authenticator; if (from.contains("authentication")) { assert(from.at("authentication").is_object()); @@ -205,9 +220,9 @@ auto ConnectionConfigurationHelper::CreateAuthenticator(const object &from) cons return authenticator; } -auto ConnectionConfigurationHelper::CreateConnectionConfiguration( - const bool tls, const std::string &host, const int port, - const CertificateProvider *certificate_provider, const Authenticator *authenticator) const +auto CreateConnectionConfiguration(const bool tls, const std::string &host, const int port, + const CertificateProvider *certificate_provider, + const Authenticator *authenticator) -> std::unique_ptr<ConnectionConfiguration> { if (tls) { if (certificate_provider != nullptr && authenticator != nullptr) { @@ -228,7 +243,7 @@ auto ConnectionConfigurationHelper::CreateConnectionConfiguration( } } -auto ConnectionConfigurationHelper::IsTls(const object &from) const -> bool { +auto IsTls(const object &from) -> bool { bool tls = true; if (from.contains("tls")) { auto tls_switch = from.at("tls"); @@ -238,8 +253,7 @@ auto ConnectionConfigurationHelper::IsTls(const object &from) const -> bool { return tls; } -auto ConnectionConfigurationHelper::CreateConnectionConfiguration(const object &from) const - -> std::unique_ptr<ConnectionConfiguration> { +auto CreateConnectionConfiguration(const object &from) -> std::unique_ptr<ConnectionConfiguration> { assert(from.contains("host")); const auto &host = from.at("host"); assert(host.is_string()); @@ -259,25 +273,19 @@ auto ConnectionConfigurationHelper::CreateConnectionConfiguration(const object & certificate_provider.get(), authenticator.get()); } -auto LoggingConfigurationHelper::CreateConsoleSinkConfiguration(const object & /*from*/, - const std::string &name, - int level) const +auto CreateConsoleSinkConfiguration(const object & /*from*/, const std::string &name, int level) -> std::shared_ptr<caosdb::logging::SinkConfiguration> { auto result = std::make_shared<ConsoleSinkConfiguration>(name, level); return result; } -auto LoggingConfigurationHelper::CreateSyslogSinkConfiguration(const object & /*from*/, - const std::string &name, - int level) const +auto CreateSyslogSinkConfiguration(const object & /*from*/, const std::string &name, int level) -> std::shared_ptr<caosdb::logging::SinkConfiguration> { auto result = std::make_shared<SyslogSinkConfiguration>(name, level); return result; } -auto LoggingConfigurationHelper::CreateFileSinkConfiguration(const object &from, - const std::string &name, - int level) const +auto CreateFileSinkConfiguration(const object &from, const std::string &name, int level) -> std::shared_ptr<caosdb::logging::SinkConfiguration> { auto result = std::make_shared<FileSinkConfiguration>(name, level); if (from.contains("directory")) { @@ -286,9 +294,21 @@ auto LoggingConfigurationHelper::CreateFileSinkConfiguration(const object &from, return result; } -auto LoggingConfigurationHelper::CreateSinkConfiguration(const object &from, - const std::string &name, - int default_level) const +auto ConvertLogLevel(const std::string &string_level) -> int { + static std::map<std::string, int> log_level_names = { + {"", CAOSDB_DEFAULT_LOG_LEVEL}, {"off", CAOSDB_LOG_LEVEL_OFF}, + {"fatal", CAOSDB_LOG_LEVEL_FATAL}, {"error", CAOSDB_LOG_LEVEL_ERROR}, + {"warn", CAOSDB_LOG_LEVEL_WARN}, {"info", CAOSDB_LOG_LEVEL_INFO}, + {"debug", CAOSDB_LOG_LEVEL_DEBUG}, {"trace", CAOSDB_LOG_LEVEL_TRACE}, + {"all", CAOSDB_LOG_LEVEL_ALL}}; + try { + return log_level_names.at(string_level); + } catch (const std::out_of_range &exc) { + throw ConfigurationError("Unknown log level: " + string_level); + } +} + +auto CreateSinkConfiguration(const object &from, const std::string &name, int default_level) -> std::shared_ptr<caosdb::logging::SinkConfiguration> { assert(from.contains("destination")); const auto &destination = std::string(from.at("destination").as_string().c_str()); @@ -307,22 +327,7 @@ auto LoggingConfigurationHelper::CreateSinkConfiguration(const object &from, } } -auto LoggingConfigurationHelper::ConvertLogLevel(const std::string &string_level) const -> int { - static std::map<std::string, int> log_level_names = { - {"", CAOSDB_DEFAULT_LOG_LEVEL}, {"off", CAOSDB_LOG_LEVEL_OFF}, - {"fatal", CAOSDB_LOG_LEVEL_FATAL}, {"error", CAOSDB_LOG_LEVEL_ERROR}, - {"warn", CAOSDB_LOG_LEVEL_WARN}, {"info", CAOSDB_LOG_LEVEL_INFO}, - {"debug", CAOSDB_LOG_LEVEL_DEBUG}, {"trace", CAOSDB_LOG_LEVEL_TRACE}, - {"all", CAOSDB_LOG_LEVEL_ALL}}; - try { - return log_level_names.at(string_level); - } catch (const std::out_of_range &exc) { - throw ConfigurationError("Unknown log level: " + string_level); - } -} - -auto LoggingConfigurationHelper::CreateLoggingConfiguration(const object &from) const - -> LoggingConfiguration { +auto CreateLoggingConfiguration(const object &from) -> LoggingConfiguration { auto default_level_str = from.contains("level") ? std::string(from.at("level").as_string().c_str()) : ""; int default_level = ConvertLogLevel(default_level_str); @@ -364,7 +369,7 @@ auto ConfigurationManager::mReset() noexcept -> int { auto ConfigurationManager::mClear() noexcept -> int { try { - json_configuration = value(nullptr); + json_configuration.Reset(); ConnectionManager::Reset(); return StatusCode::SUCCESS; } catch (const caosdb::exceptions::Exception &exc) { @@ -377,7 +382,7 @@ auto ConfigurationManager::mClear() noexcept -> int { } auto ConfigurationManager::mLoadSingleJSONConfiguration(const path &json_file) -> void { - if (!json_configuration.is_null()) { + if (json_configuration.wrapped) { throw ConfigurationError("This CaosDB client has already been configured."); } if (!exists(json_file)) { @@ -389,22 +394,27 @@ auto ConfigurationManager::mLoadSingleJSONConfiguration(const path &json_file) - auto ConfigurationManager::mGetConnectionConfiguration(const std::string &name) const -> std::unique_ptr<ConnectionConfiguration> { - auto connection_json = GetConnection(name); - return connection_configuration_helper.CreateConnectionConfiguration(connection_json); + GET_CONNECTIONS + if (connections.contains(name)) { + const auto &result_connection = connections.at(name); + assert(result_connection.is_object()); + return CreateConnectionConfiguration(result_connection.as_object()); + } + throw ConfigurationError("The connection '" + name + "' has not been defined."); } auto ConfigurationManager::mGetDefaultConnectionName() const -> std::string { - auto connections = GetConnections(); + GET_CONNECTIONS if (connections.contains("default")) { auto default_connection = connections.at("default"); if (default_connection.is_object()) { // the name is actually "default" - return std::string("default"); + return {"default"}; } else { assert(default_connection.is_string()); auto default_connection_name = default_connection.as_string(); // return the string value of connections.default - return std::string(default_connection_name.c_str()); + return {default_connection_name.c_str()}; } } if (connections.size() == 1) { @@ -414,41 +424,6 @@ auto ConfigurationManager::mGetDefaultConnectionName() const -> std::string { throw ConfigurationError("Could not determine the default connection."); } -auto ConfigurationManager::GetConfiguration() const -> const object & { - if (json_configuration.is_null()) { - throw ConfigurationError("This CaosDB client has not been configured."); - } - assert(json_configuration.is_object()); - return json_configuration.as_object(); -} - -auto ConfigurationManager::GetConnections() const -> const object & { - const auto &configuration = GetConfiguration(); - if (!configuration.contains("connections")) { - throw ConfigurationError("This CaosDB client hasn't any configured connections."); - } - const auto &connections_value = configuration.at("connections"); - if (connections_value.is_null()) { - throw ConfigurationError("This CaosDB client hasn't any configured connections."); - } - assert(connections_value.is_object()); - const auto &connections_object = connections_value.as_object(); - if (connections_object.empty()) { - throw ConfigurationError("This CaosDB client hasn't any configured connections."); - } - return connections_object; -} - -auto ConfigurationManager::GetConnection(const std::string &name) const -> const object & { - const auto &connections = GetConnections(); - if (connections.contains(name)) { - const auto &result_connection = connections.at(name); - assert(result_connection.is_object()); - return result_connection.as_object(); - } - throw ConfigurationError("The connection '" + name + "' has not been defined."); -} - // TODO(tf) This has apparently a cognitive complexity of 34>25 (threshold). auto ConfigurationManager::InitializeDefaults() -> int { // NOLINT @@ -496,11 +471,10 @@ auto ConfigurationManager::InitializeDefaults() -> int { // NOLINT } // Logging in the configuration leads to additional content. - if (this->json_configuration.is_object() && - this->json_configuration.as_object().contains("logging")) { + if (this->json_configuration.wrapped && WRAPPED_JSON_CONFIGURATION(this)->is_object() && + WRAPPED_JSON_CONFIGURATION(this)->as_object().contains("logging")) { LoggingConfiguration logging_configuration = - logging_configuration_helper.CreateLoggingConfiguration( - json_configuration.at("logging").as_object()); + CreateLoggingConfiguration(WRAPPED_JSON_CONFIGURATION(this)->at("logging").as_object()); logging::initialize_logging(logging_configuration); } else { logging::initialize_logging_defaults(); @@ -508,8 +482,9 @@ auto ConfigurationManager::InitializeDefaults() -> int { // NOLINT "We are using the default configuration"; } - if (configuration_file_path != nullptr && this->json_configuration.is_object()) { - CAOSDB_LOG_INFO(logger_name) << "Loaded configuration from " << *(configuration_file_path.get()) + if (configuration_file_path != nullptr && this->json_configuration.wrapped && + WRAPPED_JSON_CONFIGURATION(this)->is_object()) { + CAOSDB_LOG_INFO(logger_name) << "Loaded configuration from " << *(configuration_file_path) << "."; } diff --git a/src/caosdb/connection.cpp b/src/caosdb/connection.cpp index 02c190ee1beff00fc3cbffa950628a67b5ffa32d..867c38a9be90a127a84c5b3e104497535cf7ec3d 100644 --- a/src/caosdb/connection.cpp +++ b/src/caosdb/connection.cpp @@ -20,18 +20,18 @@ * */ #include "caosdb/connection.h" -#include "caosdb/configuration.h" // for ConnectionConfigur... -#include "caosdb/exceptions.h" // for ConfigurationError -#include "caosdb/info.h" // for VersionInfo -#include "caosdb/info/v1/main.grpc.pb.h" // for GeneralInfoService -#include "caosdb/info/v1/main.pb.h" // for GetVersionInfoRequest -#include "caosdb/transaction.h" // for Transaction -#include "caosdb/transaction_status.h" // for TransactionStatus -#include "grpcpp/impl/codegen/status_code_enum.h" // for StatusCode, UNAUTH... -#include <grpcpp/create_channel.h> // for CreateChannel -#include <grpcpp/impl/codegen/client_context.h> // for ClientContext -#include <grpcpp/impl/codegen/status.h> // for Status -#include <string> // for string, operator+ +#include "caosdb/configuration.h" // for ConnectionConfigur... +#include "caosdb/exceptions.h" // for ConfigurationError +#include "caosdb/info.h" // for VersionInfo +#include "caosdb/info/v1/main.grpc.pb.h" // for GeneralInfoService +#include "caosdb/info/v1/main.pb.h" // for GetVersionInfoRequest +#include "caosdb/transaction.h" // for Transaction +#include "caosdb/transaction_status.h" // for TransactionStatus +#include <grpcpp/client_context.h> // for ClientContext +#include <grpcpp/create_channel.h> // for CreateChannel +#include <grpcpp/support/status.h> // for Status +#include <grpcpp/support/status_code_enum.h> // for StatusCode, UNAUTHENTIC... +#include <string> // for string, operator+ namespace caosdb::connection { using caosdb::configuration::ConfigurationManager; diff --git a/src/caosdb/entity.cpp b/src/caosdb/entity.cpp index 127d5b4e41ef5b859bbb17d5ebcec1b2c678fa43..568cc5d97c38c8af0bf8fd632d93cb9b9bc4b2f1 100644 --- a/src/caosdb/entity.cpp +++ b/src/caosdb/entity.cpp @@ -25,7 +25,6 @@ #include "caosdb/protobuf_helper.h" // for get_arena #include "caosdb/value.h" // for Value #include <google/protobuf/arena.h> // for Arena -#include <new> // for operator new namespace caosdb::entity { using ProtoParent = caosdb::entity::v1::Parent; diff --git a/src/caosdb/file_transmission/download_request_handler.cpp b/src/caosdb/file_transmission/download_request_handler.cpp index 23520e0d78c5c8c8606ad5acf7611ba7c4806fc4..821ed79477045f4e3bf49c061cfac622b814700d 100644 --- a/src/caosdb/file_transmission/download_request_handler.cpp +++ b/src/caosdb/file_transmission/download_request_handler.cpp @@ -47,29 +47,23 @@ * > DEALINGS IN THE SOFTWARE. */ #include "caosdb/file_transmission/download_request_handler.h" -#include "caosdb/logging.h" // for CAOSDB_LOG_TRACE -#include "caosdb/protobuf_helper.h" // for get_arena -#include "caosdb/status_code.h" // for GENERIC_RPC_E... -#include "caosdb/transaction_status.h" // for TransactionStatus -#include <boost/log/core/record.hpp> // for record -#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... -#include <boost/log/sources/record_ostream.hpp> // for basic_record_... -#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... -#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... -#include <exception> // IWYU pragma: keep +#include "caosdb/logging.h" // for CAOSDB_LOG_TRACE +#include "caosdb/protobuf_helper.h" // for get_arena +#include "caosdb/status_code.h" // for GENERIC_RPC_E... +#include "caosdb/transaction_status.h" // for TransactionStatus +#include <exception> // IWYU pragma: keep +#include <filesystem> // for operator<<, path +#include <google/protobuf/arena.h> // for Arena +#include <grpcpp/client_context.h> // for ClientContext +#include <grpcpp/completion_queue.h> // for CompletionQueue +#include <grpcpp/support/async_stream.h> // for ClientAsyncReader +#include <grpcpp/support/status.h> // for Status +#include <grpcpp/support/status_code_enum.h> // for OK +#include <stdexcept> // for runtime_error +#include <string> // for string, opera... +#include <utility> // for move // IWYU pragma: no_include <bits/exception.h> -#include <filesystem> // for operator<<, path -#include <google/protobuf/arena.h> // for Arena -#include <grpcpp/impl/codegen/async_stream.h> // for ClientAsyncRe... -#include <grpcpp/impl/codegen/client_context.h> // for ClientContext -#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue -#include <grpcpp/impl/codegen/status.h> // for Status -#include <grpcpp/impl/codegen/status_code_enum.h> // for OK, UNAUTHENT... -#include <sstream> // for streamsize -#include <stdexcept> // for runtime_error -#include <string> // for string, opera... -#include <utility> // for move - +// namespace caosdb::transaction { using caosdb::StatusCode; using caosdb::utility::get_arena; @@ -122,7 +116,10 @@ bool DownloadRequestHandler::OnNext(bool ok) { return true; } -void DownloadRequestHandler::Cancel() { ctx_.TryCancel(); } +void DownloadRequestHandler::Cancel() { + state_ = CallState::CallComplete; + ctx_.TryCancel(); +} void DownloadRequestHandler::handleNewCallState() { CAOSDB_LOG_TRACE(logger_name) << "Enter DownloadRequestHandler::handleNewCallState. local_path = " diff --git a/src/caosdb/file_transmission/register_file_upload_handler.cpp b/src/caosdb/file_transmission/register_file_upload_handler.cpp index 7b5c18c83729060768d882132067c5c8dd496d67..3a47715c75eb797120e1a0116c37743ef6d2b92a 100644 --- a/src/caosdb/file_transmission/register_file_upload_handler.cpp +++ b/src/caosdb/file_transmission/register_file_upload_handler.cpp @@ -47,13 +47,7 @@ * > DEALINGS IN THE SOFTWARE. */ #include "caosdb/file_transmission/register_file_upload_handler.h" -#include "caosdb/logging.h" // for CAOSDB_LOG_TRACE -#include <boost/log/core/record.hpp> // for record -#include <boost/log/sources/record_ostream.hpp> // for basic_record_... -#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... -#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... -#include <grpcpp/impl/codegen/async_unary_call.h> // for ClientAsyncRes... -#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue +#include "caosdb/logging.h" // for CAOSDB_LOG_TRACE namespace caosdb::transaction { diff --git a/src/caosdb/file_transmission/upload_request_handler.cpp b/src/caosdb/file_transmission/upload_request_handler.cpp index 71f1106b78397a435638a0a1695bb86cb02a3edc..32bb8132e0b4a966774c5f8111800151895b6ced 100644 --- a/src/caosdb/file_transmission/upload_request_handler.cpp +++ b/src/caosdb/file_transmission/upload_request_handler.cpp @@ -47,30 +47,20 @@ * > DEALINGS IN THE SOFTWARE. */ #include "caosdb/file_transmission/upload_request_handler.h" -#include "caosdb/logging.h" // for CAOSDB_LOG_ERROR -#include "caosdb/protobuf_helper.h" // for get_arena -#include "caosdb/status_code.h" // for GENERIC_RPC_E... -#include "caosdb/transaction_status.h" // for TransactionStatus -#include <algorithm> // for min -#include <boost/log/core/record.hpp> // for record -#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... -#include <boost/log/sources/record_ostream.hpp> // for basic_record_... -#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... -#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... -#include <cstdint> // for uint64_t -#include <exception> // IWYU pragma: keep +#include "caosdb/logging.h" // for CAOSDB_LOG_ERROR +#include "caosdb/protobuf_helper.h" // for get_arena +#include "caosdb/status_code.h" // for GENERIC_RPC_E... +#include "caosdb/transaction_status.h" // for TransactionStatus +#include <algorithm> // for min +#include <cstdint> // for uint64_t +#include <exception> // IWYU pragma: keep +#include <filesystem> // for operator<<, path +#include <google/protobuf/arena.h> // for Arena +#include <grpcpp/impl/codegen/call_op_set.h> // for WriteOptions +#include <grpcpp/support/status_code_enum.h> // for OK +#include <string> // for basic_string +#include <utility> // for move // IWYU pragma: no_include <bits/exception.h> -#include <filesystem> // for operator<<, path -#include <google/protobuf/arena.h> // for Arena -#include <grpcpp/impl/codegen/async_stream.h> // for ClientAsyncWr... -#include <grpcpp/impl/codegen/call_op_set.h> // for WriteOptions -#include <grpcpp/impl/codegen/client_context.h> // for ClientContext -#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue -#include <grpcpp/impl/codegen/status.h> // for Status -#include <grpcpp/impl/codegen/status_code_enum.h> // for OK, UNAUTHENT... -#include <sstream> // for streamsize -#include <string> // for basic_string -#include <utility> // for move namespace caosdb::transaction { using caosdb::StatusCode; @@ -125,7 +115,10 @@ bool UploadRequestHandler::OnNext(bool ok) { return true; } -void UploadRequestHandler::Cancel() { ctx_.TryCancel(); } +void UploadRequestHandler::Cancel() { + state_ = CallState::CallComplete; + ctx_.TryCancel(); +} void UploadRequestHandler::handleNewCallState() { auto filename = file_descriptor_.local_path; @@ -154,7 +147,7 @@ void UploadRequestHandler::handleSendingHeaderState() { } void UploadRequestHandler::handleSendingFileState() { - const uint64_t DefaultChunkSize = 4 * 1024; // 4K + const auto DefaultChunkSize = static_cast<uint64_t>(4 * 1024); // 4K auto chunkSize = std::min(DefaultChunkSize, bytesToSend_); diff --git a/src/caosdb/logging.cpp b/src/caosdb/logging.cpp index 22cda0b65f5428fe485f62972fde3aac70f84080..7aaedee77696ada264418c104b6c8c28c4bb7b24 100644 --- a/src/caosdb/logging.cpp +++ b/src/caosdb/logging.cpp @@ -25,18 +25,20 @@ #include <boost/log/attributes/clock.hpp> #include <boost/log/core/core.hpp> // for core #include <boost/log/core/record.hpp> +#include <boost/log/detail/attachable_sstream_buf.hpp> #include <boost/log/sources/record_ostream.hpp> #include <boost/log/sources/severity_channel_logger.hpp> #include <boost/log/utility/setup/from_settings.hpp> #include <boost/log/utility/setup/settings.hpp> -#include <boost/move/utility_core.hpp> // for move #include <boost/multi_index/detail/bidir_node_iterator.hpp> #include <boost/operators.hpp> #include <boost/preprocessor/seq/limits/enum_256.hpp> #include <boost/preprocessor/seq/limits/size_256.hpp> #include <boost/property_tree/detail/exception_implementation.hpp> +#include <boost/smart_ptr/intrusive_ptr.hpp> +#include <boost/smart_ptr/intrusive_ref_counter.hpp> #include <boost/smart_ptr/shared_ptr.hpp> -#include <boost/tuple/detail/tuple_basic.hpp> // for get +#include <cstdint> // for uint64_t #include <memory> #include <sstream> #include <string> @@ -44,6 +46,64 @@ #include <vector> namespace caosdb::logging { +using boost_logger_class = boost::log::sources::severity_channel_logger_mt<int, std::string>; + +class logger { +public: + static auto get() -> boost_logger_class & { return logger::GetInstance()._logger_instance; } + +private: + static logger &GetInstance() { + static logger instance; + return instance; + } + boost_logger_class _logger_instance; +}; + +LoggerOutputStream::LoggerOutputStream(std::string channel, int level) + : channel(std::move(channel)), level(level) {} + +auto LoggerOutputStream::operator<<(std::streambuf *msg) -> LoggerOutputStream & { + BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), channel, this->level) << msg; + + return *this; +} + +auto LoggerOutputStream::operator<<(int msg) -> LoggerOutputStream & { + BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), channel, this->level) << msg; + + return *this; +} + +auto LoggerOutputStream::operator<<(int64_t msg) -> LoggerOutputStream & { + BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), channel, this->level) << msg; + + return *this; +} + +auto LoggerOutputStream::operator<<(uint64_t msg) -> LoggerOutputStream & { + BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), channel, this->level) << msg; + + return *this; +} + +auto LoggerOutputStream::operator<<(const char *msg) -> LoggerOutputStream & { + BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), channel, this->level) << msg; + + return *this; +} + +auto LoggerOutputStream::operator<<(const std::string &msg) -> LoggerOutputStream & { + BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), channel, this->level) << msg; + + return *this; +} + +auto LoggerOutputStream::operator<<(void *msg) -> LoggerOutputStream & { + BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), channel, this->level) << msg; + + return *this; +} LoggingConfiguration::LoggingConfiguration(int level) : LevelConfiguration(level) {} @@ -61,13 +121,14 @@ SinkConfiguration::SinkConfiguration(std::string name, int level) [[nodiscard]] auto SinkConfiguration::GetName() const -> const std::string & { return this->name; } -auto SinkConfiguration::Configure(boost::log::settings &settings) const -> void { - CAOSDB_LOG_TRACE(logger_name) << "Enter SinkConfiguration::Configure(&settings)"; +auto SinkConfiguration::Configure(void *settings) const -> void { + CAOSDB_LOG_TRACE(logger_name) << "Enter SinkConfiguration::Configure(*settings)"; auto sink = "Sinks." + GetName(); - settings[sink]["Destination"] = GetDestination(); - settings[sink]["Filter"] = "%Severity% >= " + std::to_string(GetLevel()); - settings[sink]["AutoFlush"] = true; - settings[sink]["Format"] = "[%TimeStamp%][%Severity%] %Channel% - %Message%"; + auto *boost_settings = static_cast<boost::log::settings *>(settings); + (*boost_settings)[sink]["Destination"] = GetDestination(); + (*boost_settings)[sink]["Filter"] = "%Severity% >= " + std::to_string(GetLevel()); + (*boost_settings)[sink]["AutoFlush"] = true; + (*boost_settings)[sink]["Format"] = "[%TimeStamp%][%Severity%] %Channel% - %Message%"; } ConsoleSinkConfiguration::ConsoleSinkConfiguration(const std::string &name, int level) @@ -78,8 +139,8 @@ ConsoleSinkConfiguration::ConsoleSinkConfiguration(const std::string &name, int return this->destination; } -auto ConsoleSinkConfiguration::Configure(boost::log::settings &settings) const -> void { - CAOSDB_LOG_TRACE(logger_name) << "Enter ConsoleSinkConfiguration::Configure(&settings)"; +auto ConsoleSinkConfiguration::Configure(void *settings) const -> void { + CAOSDB_LOG_TRACE(logger_name) << "Enter ConsoleSinkConfiguration::Configure(*settings)"; sink_configuration::Configure(settings); } @@ -95,10 +156,11 @@ auto FileSinkConfiguration::SetDirectory(const std::string &directory) -> void { this->directory = std::string(directory); } -auto FileSinkConfiguration::Configure(boost::log::settings &settings) const -> void { - CAOSDB_LOG_TRACE(logger_name) << "Enter FileSinkConfiguration::Configure(&settings)"; +auto FileSinkConfiguration::Configure(void *settings) const -> void { + CAOSDB_LOG_TRACE(logger_name) << "Enter FileSinkConfiguration::Configure(*settings)"; sink_configuration::Configure(settings); - settings["Sink." + GetName() + ".Target"] = this->directory; + auto *boost_settings = static_cast<boost::log::settings *>(settings); + (*boost_settings)["Sink." + GetName() + ".Target"] = this->directory; } SyslogSinkConfiguration::SyslogSinkConfiguration(const std::string &name, int level) @@ -127,7 +189,7 @@ auto initialize_logging_defaults() -> int { default_settings["Core.DisableLogging"] = false; for (const auto &sink : default_sinks) { - sink->Configure(default_settings); + sink->Configure(&default_settings); } boost::log::init_from_settings(default_settings); @@ -158,7 +220,7 @@ auto initialize_logging(const LoggingConfiguration &configuration) -> void { new_settings["Core.DisableLogging"] = false; for (const auto &sink : configuration.GetSinks()) { - sink->Configure(new_settings); + sink->Configure(&new_settings); } boost::log::init_from_settings(new_settings); @@ -167,27 +229,27 @@ auto initialize_logging(const LoggingConfiguration &configuration) -> void { } void caosdb_log_fatal(const char *channel, const char *msg) { - BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), channel, CAOSDB_LOG_LEVEL_FATAL) << msg; + LoggerOutputStream::get(channel, CAOSDB_LOG_LEVEL_FATAL) << msg; } void caosdb_log_error(const char *channel, const char *msg) { - BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), channel, CAOSDB_LOG_LEVEL_ERROR) << msg; + LoggerOutputStream::get(channel, CAOSDB_LOG_LEVEL_ERROR) << msg; } void caosdb_log_warn(const char *channel, const char *msg) { - BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), channel, CAOSDB_LOG_LEVEL_WARN) << msg; + LoggerOutputStream::get(channel, CAOSDB_LOG_LEVEL_WARN) << msg; } void caosdb_log_info(const char *channel, const char *msg) { - BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), channel, CAOSDB_LOG_LEVEL_INFO) << msg; + LoggerOutputStream::get(channel, CAOSDB_LOG_LEVEL_INFO) << msg; } void caosdb_log_debug(const char *channel, const char *msg) { - BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), channel, CAOSDB_LOG_LEVEL_DEBUG) << msg; + LoggerOutputStream::get(channel, CAOSDB_LOG_LEVEL_DEBUG) << msg; } void caosdb_log_trace(const char *channel, const char *msg) { - BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), channel, CAOSDB_LOG_LEVEL_TRACE) << msg; + LoggerOutputStream::get(channel, CAOSDB_LOG_LEVEL_TRACE) << msg; } } // namespace caosdb::logging diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp index 4c4bbda601d358c60fffa0537532d28a3699bb86..c75fdeb7fcf5b1a5854464dcabf76c11bb5a4262 100644 --- a/src/caosdb/transaction.cpp +++ b/src/caosdb/transaction.cpp @@ -28,22 +28,19 @@ #include "caosdb/status_code.h" // for StatusCode #include "caosdb/transaction_handler.h" // for EntityTransactionHandler #include <algorithm> // for max -#include <boost/log/core/record.hpp> // for record -#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... -#include <boost/log/sources/record_ostream.hpp> // for basic_record_... -#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... -#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... +#include <exception> // IWYU pragma: keep +#include <filesystem> // for operator<<, path +#include <future> // for async, future +#include <google/protobuf/arena.h> // for Arena +#include <grpc/impl/codegen/gpr_types.h> // for gpr_timespec +#include <map> // for map, operator!= +#include <memory> // for unique_ptr +#include <random> // for mt19937, rand... +#include <system_error> // for std::system_error +#include <utility> // for move, pair // IWYU pragma: no_include <bits/exception.h> -#include <exception> // IWYU pragma: keep -#include <filesystem> // for operator<<, path -#include <google/protobuf/arena.h> // for Arena -#include <grpc/impl/codegen/gpr_types.h> // for gpr_timespec -#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue -#include <map> // for map, operator!= -#include <memory> // for unique_ptr -#include <random> // for mt19937, rand... -#include <sstream> -#include <utility> // for move, pair +// IWYU pragma: no_include <cxxabi.h> +// IWYU pragma: no_include "net/proto2/public/repeated_field.h" namespace caosdb::transaction { using caosdb::entity::v1::EntityTransactionService; @@ -52,8 +49,10 @@ using caosdb::entity::v1::MultiTransactionRequest; using caosdb::entity::v1::MultiTransactionResponse; using TransactionResponseCase = caosdb::entity::v1::TransactionResponse::TransactionResponseCase; using RetrieveResponseCase = caosdb::entity::v1::RetrieveResponse::RetrieveResponseCase; +using RetrieveResponse = caosdb::entity::v1::RetrieveResponse; using ProtoEntity = caosdb::entity::v1::Entity; using caosdb::entity::v1::EntityRequest; + using google::protobuf::Arena; using NextStatus = grpc::CompletionQueue::NextStatus; using RegistrationStatus = caosdb::entity::v1::RegistrationStatus; @@ -89,12 +88,10 @@ MultiResultSet::MultiResultSet(std::vector<std::unique_ptr<Entity>> result_set) Transaction::Transaction(std::shared_ptr<EntityTransactionService::Stub> entity_service, std::shared_ptr<FileTransmissionService::Stub> file_service) - : request(Arena::CreateMessage<MultiTransactionRequest>(GetArena())), - response(Arena::CreateMessage<MultiTransactionResponse>(GetArena())) { - this->entity_service = std::move(entity_service); - this->file_service = std::move(file_service); - this->query_count = -1; -} + : entity_service(std::move(entity_service)), file_service(std::move(file_service)), + request(Arena::CreateMessage<MultiTransactionRequest>(GetArena())), + response(Arena::CreateMessage<MultiTransactionResponse>(GetArena())), query_count(-1), + result_set(std::make_unique<MultiResultSet>(std::vector<std::unique_ptr<Entity>>())) {} auto Transaction::RetrieveById(const std::string &id) noexcept -> StatusCode { ASSERT_CAN_ADD_RETRIEVAL @@ -202,59 +199,62 @@ auto Transaction::Execute() -> TransactionStatus { return status; } -// TODO(tf) This has apparently a cognitive complexity of 39>25 (threshold). -auto Transaction::ExecuteAsynchronously() noexcept -> StatusCode { // NOLINT - if (!IsStatus(TransactionStatus::READY()) && !IsStatus(TransactionStatus::GO_ON())) { - return StatusCode::TRANSACTION_STATUS_ERROR; - } - switch (this->transaction_type) { - case MIXED_READ_AND_WRITE: - CAOSDB_LOG_ERROR_AND_RETURN_STATUS( - logger_name, StatusCode::UNSUPPORTED_FEATURE, - "MIXED_READ_AND_WRITE UNSUPPORTED: The current implementation does not support " - "mixed read and write transactions (containing retrievals, insertions, " - "deletions, and updates in one transaction).") - default: - break; - } - this->status = TransactionStatus::EXECUTING(); +// NOLINTNEXTLINE +#define TRANSACTION_SYNCRONIZED_BLOCK \ + const std::lock_guard<std::mutex> lock(this->transaction_mutex); +// NOLINTNEXTLINE +auto Transaction::DoExecuteTransaction() noexcept -> void { // upload files first if (!upload_files.empty()) { CAOSDB_LOG_INFO(logger_name) << "Number of files to be uploaded: " << upload_files.size(); - auto *registration_request = Arena::CreateMessage<RegisterFileUploadRequest>(GetArena()); - auto *registration_response = Arena::CreateMessage<RegisterFileUploadResponse>(GetArena()); + auto *registration_request = Arena::CreateMessage<RegisterFileUploadRequest>(get_arena()); + auto *registration_response = Arena::CreateMessage<RegisterFileUploadResponse>(get_arena()); - handler_ = - std::make_unique<RegisterFileUploadHandler>(&handler_, file_service.get(), &completion_queue, - registration_request, registration_response); + { + TRANSACTION_SYNCRONIZED_BLOCK + if (this->status.GetCode() == StatusCode::EXECUTING) { + handler_ = std::make_unique<RegisterFileUploadHandler>( + &handler_, file_service.get(), &completion_queue, registration_request, + registration_response); + } + } this->status = ProcessCalls(); if (registration_response->status() != RegistrationStatus::REGISTRATION_STATUS_ACCEPTED) { this->status = TransactionStatus::FILE_UPLOAD_ERROR(); - return StatusCode::EXECUTING; } for (auto &file_descriptor : upload_files) { file_descriptor.file_transmission_id->set_registration_id( registration_response->registration_id()); - CAOSDB_LOG_INFO(logger_name) << "Uploading " << file_descriptor.local_path; - handler_ = std::make_unique<UploadRequestHandler>(&handler_, file_service.get(), - &completion_queue, file_descriptor); + { + TRANSACTION_SYNCRONIZED_BLOCK + if (this->status.GetCode() == StatusCode::EXECUTING) { + CAOSDB_LOG_INFO(logger_name) << "Uploading " << file_descriptor.local_path; + handler_ = std::make_unique<UploadRequestHandler>(&handler_, file_service.get(), + &completion_queue, file_descriptor); + } + } this->status = ProcessCalls(); if (this->status.GetCode() != StatusCode::EXECUTING) { - return StatusCode::EXECUTING; + // Return early, there has been an error. + return; } } } - CAOSDB_LOG_DEBUG(logger_name) << "RPC Request: " << RequestToString(); - handler_ = std::make_unique<EntityTransactionHandler>(&handler_, entity_service.get(), - &completion_queue, request, response); - this->status = ProcessCalls(); - if (this->status.GetCode() != StatusCode::EXECUTING) { - return StatusCode::EXECUTING; + if (this->status.GetCode() == StatusCode::EXECUTING) { + { + TRANSACTION_SYNCRONIZED_BLOCK + if (this->status.GetCode() == StatusCode::EXECUTING) { + CAOSDB_LOG_DEBUG(logger_name) << "RPC Request: " << RequestToString(); + handler_ = std::make_unique<EntityTransactionHandler>(&handler_, entity_service.get(), + &completion_queue, request, response); + } + } + this->status = ProcessCalls(); } // file download afterwards @@ -275,67 +275,98 @@ auto Transaction::ExecuteAsynchronously() noexcept -> StatusCode { // NOLINT for (const auto &item : download_files) { auto file_descriptor(item.second); - CAOSDB_LOG_INFO(logger_name) << "Downloading " << file_descriptor.local_path; + { + TRANSACTION_SYNCRONIZED_BLOCK + if (this->status.GetCode() == StatusCode::EXECUTING) { + CAOSDB_LOG_INFO(logger_name) << "Downloading " << file_descriptor.local_path; - handler_ = std::make_unique<DownloadRequestHandler>(&handler_, file_service.get(), - &completion_queue, file_descriptor); + handler_ = std::make_unique<DownloadRequestHandler>(&handler_, file_service.get(), + &completion_queue, file_descriptor); + } + } this->status = ProcessCalls(); if (this->status.GetCode() != StatusCode::EXECUTING) { - return StatusCode::EXECUTING; + // this indicates an error during the download + return; } } } + if (this->status.GetCode() == StatusCode::EXECUTING) { + ProcessTerminated(); + } +} + +auto Transaction::ExecuteAsynchronously() noexcept -> StatusCode { + TRANSACTION_SYNCRONIZED_BLOCK + if (this->status.GetCode() != StatusCode::READY && this->status.GetCode() != StatusCode::GO_ON) { + return StatusCode::TRANSACTION_STATUS_ERROR; + } + switch (this->transaction_type) { + case MIXED_READ_AND_WRITE: + CAOSDB_LOG_ERROR_AND_RETURN_STATUS( + logger_name, StatusCode::UNSUPPORTED_FEATURE, + "MIXED_WRITE UNSUPPORTED: The current implementation does not support " + "mixed read and write transactions (containing retrievals, insertions, " + "deletions, and updates in one transaction).") + default: + break; + } + this->status = TransactionStatus::EXECUTING(); + + this->transaction_future = std::async(std::launch::async, [this]() { DoExecuteTransaction(); }); + return StatusCode::EXECUTING; } -// TODO(tf) This has apparently a cognitive complexity of 36>25 (threshold). -auto Transaction::WaitForIt() const noexcept -> TransactionStatus { // NOLINT - if (this->status.GetCode() != StatusCode::EXECUTING) { - return this->status; +auto Transaction::ProcessRetrieveResponse(RetrieveResponse *retrieve_response, + std::vector<std::unique_ptr<Entity>> *entities, + bool *set_error) const noexcept + -> std::unique_ptr<Entity> { + std::unique_ptr<Entity> result; + switch (retrieve_response->retrieve_response_case()) { + case RetrieveResponseCase::kEntityResponse: { + auto *retrieve_entity_response = retrieve_response->release_entity_response(); + result = std::make_unique<Entity>(retrieve_entity_response); + } break; + case RetrieveResponseCase::kSelectResult: { + CAOSDB_LOG_ERROR(logger_name) << "Results of a SELECT query cannot be " + "processed by this client yet."; + // TODO(tf) Select queries + } break; + case RetrieveResponseCase::kCountResult: { + this->query_count = retrieve_response->count_result(); + } break; + case RetrieveResponseCase::kFindResult: { + std::unique_ptr<Entity> find_result; + for (auto &entity_response : *retrieve_response->mutable_find_result()->mutable_result_set()) { + find_result = std::make_unique<Entity>(&entity_response); + if (find_result->HasErrors()) { + *set_error = true; + } + entities->push_back(std::move(find_result)); + } + } break; + default: + CAOSDB_LOG_FATAL(logger_name) << "Received invalid QueryResponseCase."; + break; } - this->status = TransactionStatus::SUCCESS(); + return result; +} + +auto Transaction::ProcessTerminated() const noexcept -> void { + CAOSDB_LOG_TRACE_ENTER_AND_LEAVE(logger_name, "Transaction::ProcessTerminated()") bool set_error = false; auto *responses = this->response->mutable_responses(); std::vector<std::unique_ptr<Entity>> entities; + for (auto &sub_response : *responses) { std::unique_ptr<Entity> result; switch (sub_response.transaction_response_case()) { - case TransactionResponseCase::kRetrieveResponse: { auto *retrieve_response = sub_response.mutable_retrieve_response(); - - switch (retrieve_response->retrieve_response_case()) { - case RetrieveResponseCase::kEntityResponse: { - auto *retrieve_entity_response = retrieve_response->mutable_entity_response(); - result = std::make_unique<Entity>(retrieve_entity_response); - } break; - case RetrieveResponseCase::kSelectResult: { - CAOSDB_LOG_ERROR(logger_name) << "Results of a SELECT query cannot be " - "processed by this client yet."; - // TODO(tf) Select queries - } break; - case RetrieveResponseCase::kCountResult: { - this->query_count = retrieve_response->count_result(); - } break; - case RetrieveResponseCase::kFindResult: { - std::unique_ptr<Entity> find_result; - for (auto &entity_response : - *retrieve_response->mutable_find_result()->mutable_result_set()) { - find_result = std::make_unique<Entity>(&entity_response); - if (find_result->HasErrors()) { - set_error = true; - } - entities.push_back(std::move(find_result)); - } - } break; - default: - CAOSDB_LOG_FATAL(logger_name) << "Received invalid QueryResponseCase."; - break; - } - + result = ProcessRetrieveResponse(retrieve_response, &entities, &set_error); break; // break TransactionResponseCase::kRetrieveResponse } - case TransactionResponseCase::kInsertResponse: { auto *inserted_id_response = sub_response.mutable_insert_response()->mutable_id_response(); result = std::make_unique<Entity>(inserted_id_response); @@ -375,19 +406,41 @@ auto Transaction::WaitForIt() const noexcept -> TransactionStatus { // NOLINT if (set_error) { this->status = TransactionStatus::TRANSACTION_ERROR("The request terminated with errors."); + } else { + this->status = TransactionStatus::SUCCESS(); + } +} + +auto Transaction::WaitForIt() const noexcept -> TransactionStatus { + if (this->status.GetCode() != StatusCode::EXECUTING) { + return this->status; } + this->transaction_future.wait(); + return this->status; } auto Transaction::ProcessCalls() -> TransactionStatus { + CAOSDB_LOG_TRACE_ENTER_AND_LEAVE(logger_name, "Transaction::ProcessCalls()") { + TRANSACTION_SYNCRONIZED_BLOCK + if (this->status.GetCode() != StatusCode::EXECUTING) { + CAOSDB_LOG_ERROR(logger_name) + << "Transaction::ProcessCalls() was called, TransactionStatus was: " + << std::to_string(this->status.GetCode()) << " - " << this->status.GetDescription(); + return status; + } + + handler_->Start(); + } + gpr_timespec deadline; deadline.tv_sec = 1; deadline.tv_nsec = 0; deadline.clock_type = gpr_clock_type::GPR_TIMESPAN; TransactionStatus result = TransactionStatus::EXECUTING(); - handler_->Start(); + void *tag = nullptr; bool ok = false; while (true) { @@ -398,20 +451,17 @@ auto Transaction::ProcessCalls() -> TransactionStatus { if (!res) { // The handler has finished it's work result = handler_->GetStatus(); - handler_.reset(); return result; } } else { std::string description("Invalid tag delivered by notification queue."); CAOSDB_LOG_ERROR(logger_name) << description; - handler_.reset(); return TransactionStatus::RPC_ERROR(description); } } break; case NextStatus::SHUTDOWN: { CAOSDB_LOG_ERROR(logger_name) << "Notification queue has been shut down unexpectedly."; result = handler_->GetStatus(); - handler_.reset(); return result; } break; case NextStatus::TIMEOUT: { @@ -420,32 +470,48 @@ auto Transaction::ProcessCalls() -> TransactionStatus { default: CAOSDB_LOG_FATAL(logger_name) << "Got an invalid NextStatus from CompletionQueue."; result = handler_->GetStatus(); - handler_.reset(); return result; } } + result = handler_->GetStatus(); - handler_.reset(); return result; } Transaction::~Transaction() { + CAOSDB_LOG_TRACE_ENTER_AND_LEAVE(logger_name, "Transaction::~Transaction()") this->Cancel(); +} - completion_queue.Shutdown(); +void Transaction::Cancel() { + CAOSDB_LOG_TRACE_ENTER_AND_LEAVE(logger_name, "Transaction::Cancel()") - // drain the queue - void *ignoredTag = nullptr; - bool ok = false; - while (completion_queue.Next(&ignoredTag, &ok)) { - ; + if (this->status.GetCode() == StatusCode::CANCELLED) { + return; } -} -void Transaction::Cancel() { - // TODO(tf) State Canceled - if (handler_ != nullptr) { - handler_->Cancel(); + { + + TRANSACTION_SYNCRONIZED_BLOCK + + if (handler_ != nullptr) { + handler_->Cancel(); + } + + this->status = TransactionStatus::CANCELLED(); + + completion_queue.Shutdown(); + + // drain the queue + void *ignoredTag = nullptr; + bool ok = false; + while (completion_queue.Next(&ignoredTag, &ok)) { + ; + } + } + + if (transaction_future.valid()) { + transaction_future.wait(); } } diff --git a/src/caosdb/transaction_handler.cpp b/src/caosdb/transaction_handler.cpp index e97e626139dd967353491310627f96acefbdb8eb..ef4897d1392f46b374fbdf5831c3a9e74ebf9d63 100644 --- a/src/caosdb/transaction_handler.cpp +++ b/src/caosdb/transaction_handler.cpp @@ -1,15 +1,7 @@ #include "caosdb/transaction_handler.h" -#include "caosdb/logging.h" // for CAOSDB_LOG_TRACE -#include <boost/log/core/record.hpp> // for record -#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... -#include <boost/log/sources/record_ostream.hpp> // for basic_record_... -#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... -#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... -#include <exception> // IWYU pragma: keep +#include "caosdb/logging.h" // for CAOSDB_LOG_TRACE +#include <exception> // IWYU pragma: keep // IWYU pragma: no_include <bits/exception.h> -#include <grpcpp/impl/codegen/async_unary_call.h> // for ClientAsyncRes... -#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue -#include <iosfwd> // for streamsize namespace caosdb::transaction { @@ -22,17 +14,13 @@ EntityTransactionHandler::EntityTransactionHandler(HandlerTag tag, response_(response) {} void EntityTransactionHandler::handleNewCallState() { - CAOSDB_LOG_TRACE(logger_name) << "Enter EntityTransactionHandler::handleNewCallState with " - "CompletionQueue " - << completion_queue; + CAOSDB_LOG_TRACE_ENTER_AND_LEAVE(logger_name, "EntityTransactionHandler::handleNewCallState()") rpc_ = stub_->PrepareAsyncMultiTransaction(&call_context, *request_, completion_queue); state_ = CallState::CallComplete; rpc_->StartCall(); rpc_->Finish(response_, &status_, tag_); - - CAOSDB_LOG_TRACE(logger_name) << "Leave EntityTransactionHandler::handleNewCallState"; } } // namespace caosdb::transaction diff --git a/src/caosdb/unary_rpc_handler.cpp b/src/caosdb/unary_rpc_handler.cpp index 9a61a534ec30da943f7637d9995a2595acf96804..06afc127cecea22b1b805d20a68eab177841e981 100644 --- a/src/caosdb/unary_rpc_handler.cpp +++ b/src/caosdb/unary_rpc_handler.cpp @@ -47,24 +47,17 @@ * > DEALINGS IN THE SOFTWARE. */ #include "caosdb/unary_rpc_handler.h" -#include "caosdb/logging.h" // for CAOSDB_LOG_TRACE -#include "caosdb/status_code.h" // for GENERIC_RPC_E... -#include <boost/log/core/record.hpp> // for record -#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... -#include <boost/log/sources/record_ostream.hpp> // for basic_record_... -#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... -#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... +#include "caosdb/logging.h" // for CAOSDB_LOG_TRACE +#include "caosdb/status_code.h" // for GENERIC_RPC_E... +#include <exception> // IWYU pragma: keep +#include <grpcpp/support/status_code_enum.h> // for OK +#include <string> // for string, opera... // IWYU pragma: no_include <bits/exception.h> -#include <exception> // IWYU pragma: keep -#include <grpcpp/impl/codegen/client_context.h> // for ClientContext -#include <grpcpp/impl/codegen/status.h> // for Status -#include <grpcpp/impl/codegen/status_code_enum.h> // for OK, UNAUTHENT... -#include <iosfwd> // for streamsize -#include <string> // for string, opera... namespace caosdb::transaction { bool UnaryRpcHandler::OnNext(bool ok) { + CAOSDB_LOG_TRACE_ENTER_AND_LEAVE(logger_name, "UnaryRpcHandler::OnNext(bool)") try { if (ok) { if (state_ == CallState::NewCall) { @@ -105,10 +98,14 @@ bool UnaryRpcHandler::OnNext(bool ok) { return false; } -void UnaryRpcHandler::Cancel() { call_context.TryCancel(); } +void UnaryRpcHandler::Cancel() { + state_ = CallState::CallComplete; + transaction_status = TransactionStatus::CANCELLED(); + call_context.TryCancel(); +} void UnaryRpcHandler::handleCallCompleteState() { - CAOSDB_LOG_TRACE(logger_name) << "Enter UnaryRpcHandler::handleCallCompleteState"; + CAOSDB_LOG_TRACE_ENTER_AND_LEAVE(logger_name, "UnaryRpcHandler::handleCallCompleteState()") switch (status_.error_code()) { case grpc::OK: @@ -123,8 +120,6 @@ void UnaryRpcHandler::handleCallCompleteState() { << "): " << description; break; } - - CAOSDB_LOG_TRACE(logger_name) << "Leave UnaryRpcHandler::handleCallCompleteState"; } } // namespace caosdb::transaction diff --git a/src/caosdb/utility.cpp b/src/caosdb/utility.cpp index a4ed293adb6bd4fbfb10860e03185aeb567de163..ca7e1cbe9102763ed54763f7ca1dd271ed0b06db 100644 --- a/src/caosdb/utility.cpp +++ b/src/caosdb/utility.cpp @@ -19,18 +19,35 @@ * */ #include "caosdb/utility.h" -#include "caosdb/data_type.h" // for AtomicDataType, atomicdatatype_names -#include "caosdb/entity.h" // for Importance, Role, importance_names -#include <map> // for map, operator!=, _Rb_tree_const_iterator -#include <utility> // for pair +#include "caosdb/data_type.h" // for AtomicDataType, atomicdatatype_names +#include "caosdb/entity.h" // for Importance, Role, importance_names +#include <boost/beast/core/detail/base64.hpp> // for encoded_size +#include <boost/beast/core/detail/base64.ipp> // for encode +#include <boost/filesystem/string_file.hpp> // for load_string_file +#include <boost/json/stream_parser.hpp> // for stream_parser +#include <boost/json/value.hpp> // for value +#include <cassert> // for assert +#include <map> // for map, operator!=, _Rb_tree_const_iterator +#include <memory> // for allocator, unique_ptr +#include <stdexcept> // for logic_error +#include <type_traits> // for underlying_type_t +#include <typeinfo> // for type_info +#include <utility> // for pair namespace caosdb::utility { +using boost::json::stream_parser; +using boost::json::value; using caosdb::entity::AtomicDataType; using caosdb::entity::Importance; using caosdb::entity::Role; -// using emap = std::map<int, std::string>; // enum mapping +template <typename Enum> auto getEnumNameFromValue(Enum v) -> std::string { + if (std::is_same_v<std::underlying_type_t<Enum>, int>) { + return std::to_string(static_cast<int>(v)); + } + throw std::logic_error(std::string("Enum type ") + typeid(v).name() + " not implemented."); +} // Enum helper template specializations ////////////////////////////////////// template <> auto getEnumNameFromValue<Importance>(Importance v) -> std::string { @@ -87,4 +104,82 @@ template <> auto getEnumValueFromName<Role>(const std::string &name) -> Role { // End of template specialization ///////////////////////////////////////////// +auto load_string_file(const path &file_path) -> std::string { + std::string result; + boost::filesystem::load_string_file(file_path.string(), result); + return result; +} + +auto base64_encode(const std::string &plain) -> std::string { + auto size_plain = plain.size(); + auto size_encoded = boost::beast::detail::base64::encoded_size(size_plain); + + std::string result = std::string(size_encoded, '\0'); + boost::beast::detail::base64::encode(&result[0], plain.c_str(), size_plain); + + return result; +} + +auto _load_json_file(const path &json_file) -> value { + assert(exists(json_file)); + + constexpr auto buffer_size = std::size_t(4096); + auto stream = ifstream(json_file); + stream.exceptions(std::ios_base::badbit); + + stream_parser parser; + auto result = std::string(); + auto buffer = std::string(buffer_size, '\0'); + while (stream.read(&buffer[0], buffer_size)) { + parser.write(buffer.c_str(), stream.gcount()); + } + parser.write(buffer.c_str(), stream.gcount()); + + assert(parser.done()); + return parser.release(); +} + +auto load_json_file(const path &json_file) -> JsonValue { + return {new value(_load_json_file(json_file))}; +} + +JsonValue::JsonValue(void *wrapped) { this->wrapped.reset(static_cast<value *>(wrapped)); } + +JsonValue::~JsonValue() = default; + +auto JsonValue::Reset() -> void { + if (this->wrapped) { + this->wrapped.reset(); + } +} + +JsonValue::JsonValue(const JsonValue &other) { + if (other.wrapped) { + this->wrapped.reset(static_cast<value *>(other.wrapped.get())); + } +} + +auto JsonValue::operator=(const JsonValue &other) -> JsonValue & { + if (this != &other) { + if (other.wrapped) { + this->wrapped = std::make_shared<value>(*(static_cast<value *>(other.wrapped.get()))); + } + } + return *this; +} + +JsonValue::JsonValue(JsonValue &&other) noexcept { + if (other.wrapped) { + this->wrapped = std::move(other.wrapped); + } +} + +auto JsonValue::operator=(JsonValue &&other) noexcept -> JsonValue & { + if (this != &other) { + Reset(); + this->wrapped = std::move(other.wrapped); + } + return *this; +} + } // namespace caosdb::utility diff --git a/test/test_data_type.cpp b/test/test_data_type.cpp index bccc853e283cb00fd6fd1b40838f5a8ac1faae64..6b7b8b6ea3d36135164d73c90e8f45693a49b1cc 100644 --- a/test/test_data_type.cpp +++ b/test/test_data_type.cpp @@ -20,23 +20,17 @@ * */ -#include "caosdb/data_type.h" // for DataType, AtomicDataType -#include "caosdb/entity.h" // for Entity -#include "caosdb/entity/v1/main.pb.h" // for DataType, Ato... -#include "caosdb/logging.h" // for CAOSDB_LOG_DEBUG -#include "caosdb/protobuf_helper.h" // for CAOSDB_DEBUG_... -#include <boost/log/core/record.hpp> // for record -#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... -#include <boost/log/sources/record_ostream.hpp> // for operator<< -#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... -#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... -#include <gtest/gtest-message.h> // for Message -#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApi... -#include <gtest/gtest_pred_impl.h> // for AssertionResult, Test -#include <iosfwd> // for streamsize -#include <map> // for map, operator!= -#include <string> // for allocator -#include <utility> // for pair +#include "caosdb/data_type.h" // for DataType, AtomicDataType +#include "caosdb/entity.h" // for Entity +#include "caosdb/entity/v1/main.pb.h" // for DataType, Ato... +#include "caosdb/logging.h" // for CAOSDB_LOG_DEBUG +#include "caosdb/protobuf_helper.h" // for CAOSDB_DEBUG_... +#include <gtest/gtest-message.h> // for Message +#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApi... +#include <gtest/gtest_pred_impl.h> // for AssertionResult, Test +#include <map> // for map, operator!= +#include <string> // for allocator +#include <utility> // for pair namespace caosdb::entity { using ProtoEntity = caosdb::entity::v1::Entity; diff --git a/test/test_entity.cpp b/test/test_entity.cpp index 5f5aaaab4a5f6727f38ea30c0205ea8d79a925b1..a42e91781922c5e8b9530663b5bdbc53d4532fe3 100644 --- a/test/test_entity.cpp +++ b/test/test_entity.cpp @@ -39,6 +39,7 @@ #include <stdexcept> // for out_of_range #include <string> // for operator+, to_string #include <utility> // for move +// IWYU pragma: no_include "net/proto2/public/repeated_field.h" namespace caosdb::entity { using caosdb::entity::v1::IdResponse; diff --git a/test/test_issues.cpp b/test/test_issues.cpp index 389c8ee40c12a89a347a811cca5a4e7dd756068e..b7a6cc8fe7df2bbcf8b2db0902c57c7146b77faf 100644 --- a/test/test_issues.cpp +++ b/test/test_issues.cpp @@ -37,11 +37,13 @@ TEST(test_issues, test_issue_11) { Connection connection(configuration); auto transaction = connection.CreateTransaction(); - ASSERT_EQ(transaction->GetResultSet().size(), 0); + EXPECT_EQ(transaction->GetResultSet().size(), 0); transaction->RetrieveById("100"); - ASSERT_EQ(StatusCode::EXECUTING, transaction->ExecuteAsynchronously()); + EXPECT_EQ(StatusCode::EXECUTING, transaction->ExecuteAsynchronously()); // Trying to obtain ResultSet while it is still empty. - ASSERT_EQ(transaction->GetResultSet().size(), 0); + EXPECT_EQ(transaction->GetResultSet().size(), 0); + transaction->WaitForIt(); + EXPECT_EQ(transaction->GetResultSet().size(), 0); } } // namespace caosdb::transaction diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp index 797b6acc37a43a882b83aa540f17124b462add02..e710b6c775615968b567b6efa64ba76e56717c69 100644 --- a/test/test_transaction.cpp +++ b/test/test_transaction.cpp @@ -37,6 +37,7 @@ #include <string> // for string, basic_string #include <utility> // for move #include <vector> // for vector +// IWYU pragma: no_include "net/proto2/public/repeated_field.h" namespace caosdb::transaction { using caosdb::configuration::InsecureConnectionConfiguration; @@ -209,7 +210,7 @@ TEST(test_transaction, test_retrieve_and_download) { EXPECT_EQ(transaction->GetStatus().GetCode(), StatusCode::GO_ON); EXPECT_EQ(transaction->ExecuteAsynchronously(), StatusCode::EXECUTING); - EXPECT_EQ(transaction->GetStatus().GetCode(), StatusCode::CONNECTION_ERROR); + EXPECT_EQ(transaction->WaitForIt().GetCode(), StatusCode::CONNECTION_ERROR); } TEST(test_transaction, test_insert_with_file) { @@ -226,7 +227,7 @@ TEST(test_transaction, test_insert_with_file) { EXPECT_EQ(transaction->GetUploadFiles().size(), 1); transaction->ExecuteAsynchronously(); - EXPECT_EQ(transaction->GetStatus().GetCode(), StatusCode::FILE_UPLOAD_ERROR); + EXPECT_EQ(transaction->WaitForIt().GetCode(), StatusCode::FILE_UPLOAD_ERROR); } TEST(test_transaction, test_copy_result_set) { @@ -251,4 +252,39 @@ TEST(test_transaction, test_copy_result_set) { } } +TEST(test_transaction, test_multiple_execute) { + const auto *host = "localhost"; + auto configuration = InsecureConnectionConfiguration(host, 8000); + Connection connection(configuration); + auto transaction = connection.CreateTransaction(); + + EXPECT_EQ(transaction->GetStatus().GetCode(), StatusCode::INITIAL); + transaction->RetrieveById("asdf"); + + EXPECT_EQ(transaction->GetStatus().GetCode(), StatusCode::GO_ON); + + EXPECT_EQ(transaction->ExecuteAsynchronously(), StatusCode::EXECUTING); + EXPECT_EQ(transaction->ExecuteAsynchronously(), StatusCode::TRANSACTION_STATUS_ERROR); + EXPECT_EQ(transaction->ExecuteAsynchronously(), StatusCode::TRANSACTION_STATUS_ERROR); + transaction->Cancel(); +} + +TEST(test_transaction, test_multiple_wait_for_it) { + const auto *host = "localhost"; + auto configuration = InsecureConnectionConfiguration(host, 8000); + Connection connection(configuration); + auto transaction = connection.CreateTransaction(); + + EXPECT_EQ(transaction->GetStatus().GetCode(), StatusCode::INITIAL); + transaction->RetrieveById("asdf"); + + EXPECT_EQ(transaction->GetStatus().GetCode(), StatusCode::GO_ON); + + EXPECT_EQ(transaction->ExecuteAsynchronously(), StatusCode::EXECUTING); + + EXPECT_EQ(transaction->WaitForIt().GetCode(), StatusCode::CONNECTION_ERROR); + EXPECT_EQ(transaction->WaitForIt().GetCode(), StatusCode::CONNECTION_ERROR); + EXPECT_EQ(transaction->WaitForIt().GetCode(), StatusCode::CONNECTION_ERROR); +} + } // namespace caosdb::transaction diff --git a/test/test_utility.cpp b/test/test_utility.cpp index 3a9a420cee742c1001c727a6154452ee10e8d8fb..74db29afa798ccdbf9d4be45497e1a44762d630d 100644 --- a/test/test_utility.cpp +++ b/test/test_utility.cpp @@ -20,10 +20,7 @@ * */ -#include "gmock/gmock-matchers.h" // for ElementsAre, EXPECT_THAT #include "boost/beast/core/detail/base64.hpp" // for encoded_size -#include "boost/json/object.hpp" // for object -#include "boost/json/value.hpp" // for value #include "caosdb/data_type.h" // for atomicdatatype_names #include "caosdb/entity.h" // for importance_names, role... #include "caosdb/status_code.h" // for get_status_description @@ -38,7 +35,6 @@ #include <utility> // for pair namespace caosdb::utility { -using ::testing::ElementsAre; TEST(test_utility, base64_encode) { auto test_plain = std::string("admin:caosdb"); @@ -48,17 +44,6 @@ TEST(test_utility, base64_encode) { EXPECT_EQ(test_encoded, base64_encode(test_plain)); } -TEST(test_utility, test_load_json_file) { - auto json = load_json_file(TEST_DATA_DIR + "/test.json").as_object(); - - EXPECT_EQ(json["it"], "tests"); - EXPECT_EQ(json["null values"], nullptr); - EXPECT_THAT(json["this"].as_array(), ElementsAre("is", "a", "test")); - EXPECT_THAT(json["numbers"].as_array(), ElementsAre(1, 2, 3.3)); - auto sub = json["arrays and objects"].as_object(); - EXPECT_THAT(sub["see?"].as_array(), ElementsAre(true, false)); -} - TEST(test_utility, enum_names) { // All working enums for (const auto &entry : caosdb::entity::importance_names) {