diff --git a/CMakeLists.txt b/CMakeLists.txt index c4244df..3d4a2bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,16 +18,13 @@ set(dmitigr_pgfe_version_part1 1) set(dmitigr_pgfe_version_part2 1) add_definitions(-DDMITIGR_PGFE_VERSION_PART1=${dmitigr_pgfe_version_part1}) add_definitions(-DDMITIGR_PGFE_VERSION_PART2=${dmitigr_pgfe_version_part2}) -if(WIN32) - add_definitions(-DNOMINMAX) -endif() # ------------------------------------------------------------------------------ # Build options # ------------------------------------------------------------------------------ option(BUILD_SHARED_LIBS "Build shared library?" ON) -set(DMITIGR_PGFE_HEADER_ONLY OFF) # TODO +option(DMITIGR_PGFE_HEADER_ONLY "Header-only library?" OFF) option(DMITIGR_PGFE_BUILD_TESTS "Build tests?" ON) if(NOT DMITIGR_PGFE_HEADER_ONLY) @@ -296,7 +293,7 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/lib) include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/lib) # ------------------------------------------------------------------------------ -# Preprocessing of the sources +# Preprocessing # ------------------------------------------------------------------------------ # Generated headers (.hpp) should go to the CMAKE_CURRENT_SOURCE_DIR, but @@ -327,94 +324,71 @@ set(dmitigr_pgfe_preprocessed_headers ${CMAKE_CURRENT_BINARY_DIR}/lib/dmitigr/pgfe/connection_options.cxx ) -# set(dmitigr_pgfe_header_only_headers -# lib/dmitigr/pgfe/header_only.hpp -# ) +set(dmitigr_pgfe_header_only_headers + lib/dmitigr/pgfe/implementation_footer.hpp + lib/dmitigr/pgfe/implementation_header.hpp + ) set(dmitigr_pgfe_headers lib/dmitigr/pgfe/array_conversions.hpp lib/dmitigr/pgfe/basic_conversions.hpp - lib/dmitigr/pgfe/completion.hpp - lib/dmitigr/pgfe/completion.hxx - lib/dmitigr/pgfe/compositional.hpp - lib/dmitigr/pgfe/compositional.hxx - lib/dmitigr/pgfe/composite.hpp - lib/dmitigr/pgfe/composite.hxx - lib/dmitigr/pgfe/connection.hpp - lib/dmitigr/pgfe/connection.hxx - lib/dmitigr/pgfe/connection_options.hpp - lib/dmitigr/pgfe/connection_options.hxx - lib/dmitigr/pgfe/conversions_api.hpp lib/dmitigr/pgfe/conversions.hpp - lib/dmitigr/pgfe/conversions.hxx - lib/dmitigr/pgfe/data.hpp - lib/dmitigr/pgfe/data.hxx - lib/dmitigr/pgfe/dll.hpp - - lib/dmitigr/pgfe/errc.hxx - lib/dmitigr/pgfe/error.hpp - lib/dmitigr/pgfe/error.hxx - lib/dmitigr/pgfe/exceptions.hpp - lib/dmitigr/pgfe/exceptions.hxx - lib/dmitigr/pgfe/message.hpp - lib/dmitigr/pgfe/misc.hpp - - lib/dmitigr/pgfe/net.hxx - lib/dmitigr/pgfe/notice.hpp - lib/dmitigr/pgfe/notice.hxx - lib/dmitigr/pgfe/notification.hpp - lib/dmitigr/pgfe/notification.hxx - lib/dmitigr/pgfe/parameterizable.hpp - lib/dmitigr/pgfe/parameterizable.hxx - - lib/dmitigr/pgfe/pq.hxx - - lib/dmitigr/pgfe/prepared_statement.hpp - lib/dmitigr/pgfe/prepared_statement.hxx - + lib/dmitigr/pgfe/pq.hpp + lib/dmitigr/pgfe/prepared_statement_dfn.hpp + lib/dmitigr/pgfe/prepared_statement_impl.hpp lib/dmitigr/pgfe/problem.hpp - lib/dmitigr/pgfe/problem.hxx - lib/dmitigr/pgfe/response.hpp - lib/dmitigr/pgfe/row.hpp - lib/dmitigr/pgfe/row.hxx - lib/dmitigr/pgfe/row_info.hpp - lib/dmitigr/pgfe/row_info.hxx - lib/dmitigr/pgfe/server_message.hpp - lib/dmitigr/pgfe/signal.hpp - - lib/dmitigr/pgfe/sql.hxx - lib/dmitigr/pgfe/sql_string.hpp - lib/dmitigr/pgfe/sql_string.hxx - lib/dmitigr/pgfe/sql_vector.hpp - lib/dmitigr/pgfe/sql_vector.hxx - lib/dmitigr/pgfe/std_system_error.hpp - + lib/dmitigr/pgfe/util.hpp lib/dmitigr/pgfe/types_fwd.hpp ) +set(dmitigr_pgfe_implementations + lib/dmitigr/pgfe/basics.cpp + lib/dmitigr/pgfe/completion.cpp + lib/dmitigr/pgfe/composite.cpp + lib/dmitigr/pgfe/compositional.cpp + lib/dmitigr/pgfe/connection.cpp + lib/dmitigr/pgfe/connection_options.cpp + lib/dmitigr/pgfe/data.cpp + lib/dmitigr/pgfe/errc.cpp + lib/dmitigr/pgfe/error.cpp + lib/dmitigr/pgfe/exceptions.cpp + lib/dmitigr/pgfe/misc.cpp + lib/dmitigr/pgfe/notice.cpp + lib/dmitigr/pgfe/notification.cpp + lib/dmitigr/pgfe/parameterizable.cpp + lib/dmitigr/pgfe/prepared_statement_impl.cpp + lib/dmitigr/pgfe/problem.cpp + lib/dmitigr/pgfe/row.cpp + lib/dmitigr/pgfe/row_info.cpp + lib/dmitigr/pgfe/sql_string.cpp + lib/dmitigr/pgfe/sql_vector.cpp + lib/dmitigr/pgfe/std_system_error.cpp + lib/dmitigr/pgfe/util.cpp + ) + set(dmitigr_pgfe_cmake_sources lib/dmitigr/pgfe/basics.cmake lib/dmitigr/pgfe/errc.cmake @@ -427,27 +401,14 @@ set(dmitigr_pgfe_cmake_unpreprocessed ) set(dmitigr_pgfe_transunits - lib/dmitigr/pgfe/basics.cpp - lib/dmitigr/pgfe/composite.cpp - lib/dmitigr/pgfe/connection.cpp - lib/dmitigr/pgfe/connection_options.cpp - lib/dmitigr/pgfe/data.cpp - lib/dmitigr/pgfe/errc.cxx - lib/dmitigr/pgfe/misc.cpp - lib/dmitigr/pgfe/net.cxx - lib/dmitigr/pgfe/prepared_statement.cxx - lib/dmitigr/pgfe/problem.cpp - lib/dmitigr/pgfe/sql.cxx - lib/dmitigr/pgfe/sql_string.cpp - lib/dmitigr/pgfe/sql_string.cxx - lib/dmitigr/pgfe/sql_vector.cpp - lib/dmitigr/pgfe/std_system_error.cpp + lib/dmitigr/pgfe.cpp ) set(dmitigr_pgfe_sources ${dmitigr_pgfe_root_headers} - #${dmitigr_pgfe_header_only_headers} + ${dmitigr_pgfe_header_only_headers} ${dmitigr_pgfe_headers} + ${dmitigr_pgfe_implementations} ${dmitigr_pgfe_cmake_sources} ${dmitigr_pgfe_cmake_unpreprocessed} ${dmitigr_pgfe_transunits} @@ -458,6 +419,7 @@ if (WIN32) endif() set_source_files_properties( + ${dmitigr_pgfe_implementations} ${dmitigr_pgfe_cmake_sources} ${dmitigr_pgfe_cmake_unpreprocessed} @@ -534,7 +496,7 @@ include_directories(${Pq_INCLUDE_DIRS}) if(NOT DMITIGR_PGFE_HEADER_ONLY) target_link_libraries(${dmitigr_pgfe_target} - PRIVATE ${Suggested_Pq_LIBRARIES} ${dmitigr_common_library}) + PUBLIC ${Suggested_Pq_LIBRARIES} ${dmitigr_common_library}) if (UNIX) target_link_libraries(${dmitigr_pgfe_target} PRIVATE stdc++fs) diff --git a/lib/dmitigr/pgfe.cpp b/lib/dmitigr/pgfe.cpp new file mode 100644 index 0000000..beab618 --- /dev/null +++ b/lib/dmitigr/pgfe.cpp @@ -0,0 +1,7 @@ +// -*- C++ -*- +// Copyright (C) Dmitry Igrishin +// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp + +#define DMITIGR_PGFE_HEADER_ONLY +#define DMITIGR_PGFE_BUILDING +#include "dmitigr/pgfe.hpp" diff --git a/lib/dmitigr/pgfe.hpp b/lib/dmitigr/pgfe.hpp index 8d5f1f2..9ea348a 100644 --- a/lib/dmitigr/pgfe.hpp +++ b/lib/dmitigr/pgfe.hpp @@ -27,11 +27,12 @@ #include "dmitigr/pgfe/basic_conversions.hpp" #include "dmitigr/pgfe/basics.hpp" #include "dmitigr/pgfe/completion.hpp" +#include "dmitigr/pgfe/composite.hpp" #include "dmitigr/pgfe/compositional.hpp" #include "dmitigr/pgfe/connection.hpp" #include "dmitigr/pgfe/connection_options.hpp" -#include "dmitigr/pgfe/conversions.hpp" #include "dmitigr/pgfe/conversions_api.hpp" +#include "dmitigr/pgfe/conversions.hpp" #include "dmitigr/pgfe/data.hpp" #include "dmitigr/pgfe/errc.hpp" #include "dmitigr/pgfe/error.hpp" @@ -41,7 +42,9 @@ #include "dmitigr/pgfe/notice.hpp" #include "dmitigr/pgfe/notification.hpp" #include "dmitigr/pgfe/parameterizable.hpp" -#include "dmitigr/pgfe/prepared_statement.hpp" +#include "dmitigr/pgfe/pq.hpp" +// #include "dmitigr/pgfe/prepared_statement_dfn.hpp" +// #include "dmitigr/pgfe/prepared_statement_impl.hpp" #include "dmitigr/pgfe/problem.hpp" #include "dmitigr/pgfe/response.hpp" #include "dmitigr/pgfe/row.hpp" @@ -52,5 +55,6 @@ #include "dmitigr/pgfe/sql_vector.hpp" #include "dmitigr/pgfe/std_system_error.hpp" #include "dmitigr/pgfe/types_fwd.hpp" +#include "dmitigr/pgfe/util.hpp" #endif // DMITIGR_PGFE_HPP diff --git a/lib/dmitigr/pgfe/array_conversions.hpp b/lib/dmitigr/pgfe/array_conversions.hpp index a199283..e1f17b7 100644 --- a/lib/dmitigr/pgfe/array_conversions.hpp +++ b/lib/dmitigr/pgfe/array_conversions.hpp @@ -8,7 +8,7 @@ #include "dmitigr/pgfe/basic_conversions.hpp" #include "dmitigr/pgfe/conversions_api.hpp" #include "dmitigr/pgfe/data.hpp" -#include "dmitigr/pgfe/exceptions.hxx" +#include "dmitigr/pgfe/exceptions.hpp" #include #include @@ -52,7 +52,7 @@ Container to_container(const char* literal, char delimiter = ',', Types&& ... ar * @brief The compile-time converter from the "container of values" type to the "container of optionals" type. */ template -struct Cont_of_opts { +struct Cont_of_opts final { using Type = T; }; @@ -69,7 +69,7 @@ struct Cont_of_opts { * std::basic_string (see below) does not works. */ template<> -struct Cont_of_opts { +struct Cont_of_opts final { using Type = std::string; }; @@ -86,7 +86,7 @@ struct Cont_of_opts { * Thus, this specialization is not required at all. But let it be just in case. */ template -struct Cont_of_opts> { +struct Cont_of_opts> final { using Type = std::basic_string; }; @@ -98,7 +98,7 @@ struct Cont_of_opts> { template class Container, template class Allocator> -struct Cont_of_opts>> { +struct Cont_of_opts>> final { private: using Elem = typename Cont_of_opts::Type; public: @@ -121,7 +121,7 @@ using Cont_of_opts_t = typename Cont_of_opts::Type; * @brief The compile-time converter from the "container of optionals" type to the "container of values" type. */ template -struct Cont_of_vals { +struct Cont_of_vals final { using Type = T; }; @@ -134,7 +134,7 @@ template class Optional, template class Container, template class Allocator> -struct Cont_of_vals, Allocator>>> { +struct Cont_of_vals, Allocator>>> final { private: using Elem = typename Cont_of_vals::Type; public: @@ -187,7 +187,7 @@ template class Optional, template class Container, template class Allocator> -struct Array_string_conversions_opts, Allocator>>> { +struct Array_string_conversions_opts, Allocator>>> final { using Type = Container, Allocator>>; template @@ -207,7 +207,7 @@ template class Optional, template class Container, template class Allocator> -struct Array_data_conversions_opts, Allocator>>> { +struct Array_data_conversions_opts, Allocator>>> final { using Type = Container, Allocator>>; template @@ -239,7 +239,7 @@ template struct Array_data_conversions_vals; template class Container, template class Allocator> -struct Array_string_conversions_vals>> { +struct Array_string_conversions_vals>> final { using Type = Container>; template @@ -262,7 +262,7 @@ struct Array_string_conversions_vals>> { template class Container, template class Allocator> -struct Array_data_conversions_vals>> { +struct Array_data_conversions_vals>> final { using Type = Container>; template @@ -310,16 +310,16 @@ template class Optional, template class Container, template class Allocator> -class Filler_of_deepest_container { +class Filler_of_deepest_container final { private: template - struct Is_container : std::false_type{}; + struct Is_container final : std::false_type{}; template class Opt, template class Cont, template class Alloc> - struct Is_container, Alloc>>> : std::true_type{}; + struct Is_container, Alloc>>> final : std::true_type{}; public: using Value_type = T; @@ -840,7 +840,7 @@ template class Optional, template class Container, template class Allocator> -struct Conversions, Allocator>>> +struct Conversions, Allocator>>> final : public Basic_conversions, Allocator>>, detail::Array_string_conversions_opts, Allocator>>>, detail::Array_data_conversions_opts, Allocator>>>> {}; diff --git a/lib/dmitigr/pgfe/basics.cmake b/lib/dmitigr/pgfe/basics.cmake index 1fb6f4d..6cc6286 100644 --- a/lib/dmitigr/pgfe/basics.cmake +++ b/lib/dmitigr/pgfe/basics.cmake @@ -55,3 +55,10 @@ set(PGFE_PROBLEM_SEVERITY_WARNING 400) set(PGFE_PROBLEM_SEVERITY_ERROR 500) set(PGFE_PROBLEM_SEVERITY_FATAL 600) set(PGFE_PROBLEM_SEVERITY_PANIC 700) + +# ------------------------------------------------------------------------------ +# External_library +# ------------------------------------------------------------------------------ + +set(PGFE_EXTERNAL_LIBRARY_LIBSSL 2) +set(PGFE_EXTERNAL_LIBRARY_LIBCRYPTO 4) diff --git a/lib/dmitigr/pgfe/basics.cpp b/lib/dmitigr/pgfe/basics.cpp index 71a6286..84a4a09 100644 --- a/lib/dmitigr/pgfe/basics.cpp +++ b/lib/dmitigr/pgfe/basics.cpp @@ -3,17 +3,20 @@ // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp #include "dmitigr/pgfe/basics.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" #include #include -namespace pgfe = dmitigr::pgfe; - namespace dmitigr { -template<> struct Is_bitmask_enum : std::true_type {}; +template<> struct Is_bitmask_enum final : std::true_type {}; +template<> struct Is_bitmask_enum final : std::true_type {}; } // namespace dmitigr -DMITIGR_DEFINE_ENUM_BITMASK_OPERATORS(pgfe, pgfe::Socket_readiness) +DMITIGR_DEFINE_ENUM_BITMASK_OPERATORS(dmitigr::pgfe, dmitigr::pgfe::Socket_readiness) +DMITIGR_DEFINE_ENUM_BITMASK_OPERATORS(dmitigr::pgfe, dmitigr::pgfe::External_library) + +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/basics.hpp.in b/lib/dmitigr/pgfe/basics.hpp.in index 4ee4f12..deb37b8 100644 --- a/lib/dmitigr/pgfe/basics.hpp.in +++ b/lib/dmitigr/pgfe/basics.hpp.in @@ -5,8 +5,10 @@ #ifndef DMITIGR_PGFE_BASICS_HPP #define DMITIGR_PGFE_BASICS_HPP +#include "dmitigr/pgfe/dll.hpp" + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// This file was generated automatically. There is no sense to edit! +// This file was generated automatically. Edit basics.hpp.in instead!!!!!!!!!!!! // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! namespace dmitigr::pgfe { @@ -35,49 +37,49 @@ enum class Socket_readiness { * * Bitwise AND for Socket_readiness. */ -Socket_readiness operator&(Socket_readiness lhs, Socket_readiness rhs) noexcept; +DMITIGR_PGFE_API Socket_readiness operator&(Socket_readiness lhs, Socket_readiness rhs) noexcept; /** * @ingroup main * * @brief Bitwise OR for Socket_readiness. */ -Socket_readiness operator|(Socket_readiness lhs, Socket_readiness rhs) noexcept; +DMITIGR_PGFE_API Socket_readiness operator|(Socket_readiness lhs, Socket_readiness rhs) noexcept; /** * @ingroup main * * @brief Bitwise XOR for Socket_readiness. */ -Socket_readiness operator^(Socket_readiness lhs, Socket_readiness rhs) noexcept; +DMITIGR_PGFE_API Socket_readiness operator^(Socket_readiness lhs, Socket_readiness rhs) noexcept; /** * @ingroup main * * @brief Bitwise NOT for Socket_readiness. */ -Socket_readiness operator~(Socket_readiness lhs) noexcept; +DMITIGR_PGFE_API Socket_readiness operator~(Socket_readiness lhs) noexcept; /** * @ingroup main * * @brief Bitwise AND for Socket_readiness with assignment to lvalue. */ -Socket_readiness& operator&=(Socket_readiness& lhs, Socket_readiness rhs) noexcept; +DMITIGR_PGFE_API Socket_readiness& operator&=(Socket_readiness& lhs, Socket_readiness rhs) noexcept; /** * @ingroup main * * @brief Bitwise OR for Socket_readiness with assignment to lvalue. */ -Socket_readiness& operator|=(Socket_readiness& lhs, Socket_readiness rhs) noexcept; +DMITIGR_PGFE_API Socket_readiness& operator|=(Socket_readiness& lhs, Socket_readiness rhs) noexcept; /** * @ingroup main * * @brief Bitwise XOR for Socket_readiness with assignment to lvalue. */ -Socket_readiness& operator^=(Socket_readiness& lhs, Socket_readiness rhs) noexcept; +DMITIGR_PGFE_API Socket_readiness& operator^=(Socket_readiness& lhs, Socket_readiness rhs) noexcept; // ----------------------------------------------------------------------------- @@ -203,6 +205,74 @@ enum class Problem_severity { panic = @PGFE_PROBLEM_SEVERITY_PANIC@ }; +// ----------------------------------------------------------------------------- + +/** + * @ingroup main + * + * @brief Represents an external library. + */ +enum class External_library { + /** Denotes the OpenSSL library. */ + libssl = @PGFE_EXTERNAL_LIBRARY_LIBSSL@, + + /** Denotes the libcrypto library. */ + libcrypto = @PGFE_EXTERNAL_LIBRARY_LIBCRYPTO@ +}; + +/** + * @ingroup main + * + * Bitwise AND for External_library. + */ +DMITIGR_PGFE_API External_library operator&(External_library lhs, External_library rhs) noexcept; + +/** + * @ingroup main + * + * @brief Bitwise OR for External_library. + */ +DMITIGR_PGFE_API External_library operator|(External_library lhs, External_library rhs) noexcept; + +/** + * @ingroup main + * + * @brief Bitwise XOR for External_library. + */ +DMITIGR_PGFE_API External_library operator^(External_library lhs, External_library rhs) noexcept; + +/** + * @ingroup main + * + * @brief Bitwise NOT for External_library. + */ +DMITIGR_PGFE_API External_library operator~(External_library lhs) noexcept; + +/** + * @ingroup main + * + * @brief Bitwise AND for External_library with assignment to lvalue. + */ +DMITIGR_PGFE_API External_library& operator&=(External_library& lhs, External_library rhs) noexcept; + +/** + * @ingroup main + * + * @brief Bitwise OR for External_library with assignment to lvalue. + */ +DMITIGR_PGFE_API External_library& operator|=(External_library& lhs, External_library rhs) noexcept; + +/** + * @ingroup main + * + * @brief Bitwise XOR for External_library with assignment to lvalue. + */ +DMITIGR_PGFE_API External_library& operator^=(External_library& lhs, External_library rhs) noexcept; + } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/basics.cpp" +#endif + #endif // DMITIGR_PGFE_BASICS_HPP diff --git a/lib/dmitigr/pgfe/completion.hxx b/lib/dmitigr/pgfe/completion.cpp similarity index 93% rename from lib/dmitigr/pgfe/completion.hxx rename to lib/dmitigr/pgfe/completion.cpp index 6c1fddc..d0e3484 100644 --- a/lib/dmitigr/pgfe/completion.hxx +++ b/lib/dmitigr/pgfe/completion.cpp @@ -2,10 +2,8 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#ifndef DMITIGR_PGFE_COMPLETION_HXX -#define DMITIGR_PGFE_COMPLETION_HXX - #include "dmitigr/pgfe/completion.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" #include @@ -23,7 +21,7 @@ inline bool iCompletion::is_invariant_ok() // ----------------------------------------------------------------------------- -class simple_Completion : public iCompletion { +class simple_Completion final : public iCompletion { public: explicit simple_Completion(const std::string& tag) { @@ -84,4 +82,4 @@ class simple_Completion : public iCompletion { } // namespace dmitigr::pgfe::detail -#endif // DMITIGR_PGFE_COMPLETION_HXX +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/completion.hpp b/lib/dmitigr/pgfe/completion.hpp index 4de0230..c989180 100644 --- a/lib/dmitigr/pgfe/completion.hpp +++ b/lib/dmitigr/pgfe/completion.hpp @@ -49,4 +49,8 @@ class Completion : public Response { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/completion.cpp" +#endif + #endif // DMITIGR_PGFE_COMPLETION_HPP diff --git a/lib/dmitigr/pgfe/composite.cpp b/lib/dmitigr/pgfe/composite.cpp index 9aff50b..d9cafe1 100644 --- a/lib/dmitigr/pgfe/composite.cpp +++ b/lib/dmitigr/pgfe/composite.cpp @@ -2,18 +2,267 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#include "dmitigr/pgfe/composite.hxx" +#include "dmitigr/pgfe/composite.hpp" +#include "dmitigr/pgfe/compositional.hpp" +#include "dmitigr/pgfe/data.hpp" +#include "dmitigr/pgfe/util.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" + +#include + +#include +#include +#include + +namespace dmitigr::pgfe::detail { + +class iComposite : public Composite { +protected: + virtual bool is_invariant_ok() = 0; +}; + +inline bool iComposite::is_invariant_ok() +{ + const bool compositional_ok = detail::is_invariant_ok(*this); + return compositional_ok; +} + +// ----------------------------------------------------------------------------- + +/** + * @internal + * + * @brief An implementation of Composite that stores data as a vector of unique + * pointers. + * + * @remarks Fields removing will not invalidate pointers returned by data(). + */ +class heap_data_Composite final : public iComposite { +public: + heap_data_Composite() = default; + + explicit heap_data_Composite(std::vector>>&& datas) + : datas_{std::move(datas)} + { + DMITIGR_ASSERT(is_invariant_ok()); + } + + heap_data_Composite(const heap_data_Composite& rhs) + : datas_{rhs.datas_.size()} + { + std::transform(cbegin(rhs.datas_), cend(rhs.datas_), begin(datas_), + [&](const auto& pair) { return std::make_pair(pair.first, pair.second->to_data()); }); + DMITIGR_ASSERT(is_invariant_ok()); + } + + heap_data_Composite& operator=(const heap_data_Composite& rhs) + { + heap_data_Composite tmp{rhs}; + swap(tmp); + return *this; + } + + heap_data_Composite(heap_data_Composite&& rhs) = default; + + heap_data_Composite& operator=(heap_data_Composite&& rhs) = default; + + void swap(heap_data_Composite& rhs) noexcept + { + datas_.swap(rhs.datas_); + } + + // --------------------------------------------------------------------------- + // Compositional overridings + // --------------------------------------------------------------------------- + + std::size_t field_count() const override + { + return datas_.size(); + } + + bool has_fields() const override + { + return !datas_.empty(); + } + + const std::string& field_name(const std::size_t index) const override + { + DMITIGR_REQUIRE(index < field_count(), std::out_of_range); + return datas_[index].first; + } + + std::optional field_index(const std::string& name, const std::size_t offset = 0) const override + { + if (const auto i = field_index__(name, offset); i < field_count()) + return i; + else + return std::nullopt; + } + + std::size_t field_index_throw(const std::string& name, const std::size_t offset = 0) const override + { + const auto i = field_index__(name, offset); + DMITIGR_REQUIRE(i < field_count(), std::out_of_range); + return i; + } + + bool has_field(const std::string& name, const std::size_t offset = 0) const override + { + return bool(field_index(name, offset)); + } + + // --------------------------------------------------------------------------- + // Composite overridings + // --------------------------------------------------------------------------- + + std::unique_ptr to_composite() const override + { + return std::make_unique(*this); + } + + // --------------------------------------------------------------------------- + + const Data* data(const std::size_t index) const override + { + DMITIGR_REQUIRE(index < field_count(), std::out_of_range); + return datas_[index].second.get(); + } + + const Data* data(const std::string& name, const std::size_t offset) const override + { + return data(field_index_throw(name, offset)); + } + + // ------------------------------------------------------------------------------------- + + void set_data(const std::size_t index, std::unique_ptr&& data) override + { + DMITIGR_REQUIRE(index < field_count(), std::out_of_range); + datas_[index].second = std::move(data); + DMITIGR_ASSERT(is_invariant_ok()); + } + + void set_data(const std::size_t index, std::nullptr_t) override + { + set_data(index, std::unique_ptr{}); + } + + void set_data(const std::string& name, std::unique_ptr&& data) override + { + set_data(field_index_throw(name), std::move(data)); + } + + void set_data(const std::string& name, std::nullptr_t) override + { + set_data(name, std::unique_ptr{}); + } + + std::unique_ptr release_data(const std::size_t index) override + { + DMITIGR_REQUIRE(index < field_count(), std::out_of_range); + auto& data = datas_[index].second; + auto result = std::move(data); // As described in 14882:2014 20.8.1/4, u.p is equal to nullptr after transfer ownership... + data.reset(); // but just in case... + DMITIGR_ASSERT(is_invariant_ok()); + return result; + } + + std::unique_ptr release_data(const std::string& name, const std::size_t offset = 0) override + { + return release_data(field_index_throw(name, offset)); + } + + void append_field(const std::string& name, std::unique_ptr&& data = {}) override + { + datas_.emplace_back(name, std::move(data)); + DMITIGR_ASSERT(is_invariant_ok()); + } + + void insert_field(const std::size_t index, const std::string& name, std::unique_ptr&& data = {}) override + { + DMITIGR_REQUIRE(index < field_count(), std::out_of_range); + datas_.insert(begin(datas_) + index, std::make_pair(name, std::move(data))); + DMITIGR_ASSERT(is_invariant_ok()); + } + + void insert_field(const std::string& name, const std::string& new_field_name, std::unique_ptr&& data) override + { + insert_field(field_index_throw(name), new_field_name, std::move(data)); + } + + void remove_field(const std::size_t index) override + { + DMITIGR_REQUIRE(index < field_count(), std::out_of_range); + datas_.erase(cbegin(datas_) + index); + DMITIGR_ASSERT(is_invariant_ok()); + } + + void remove_field(const std::string& name, std::size_t offset = 0) override + { + remove_field(field_index_throw(name, offset)); + } + + // --------------------------------------------------------------------------- + + std::vector>> to_vector() const override + { + heap_data_Composite copy{*this}; + return std::move(copy.datas_); + } + + std::vector>> move_to_vector() override + { + std::vector>> result; + datas_.swap(result); + return std::move(result); + } + + // --------------------------------------------------------------------------- + // Non public API + // --------------------------------------------------------------------------- + + void append(heap_data_Composite&& rhs) + { + datas_.insert(cend(datas_), std::make_move_iterator(begin(rhs.datas_)), std::make_move_iterator(end(rhs.datas_))); + DMITIGR_ASSERT(is_invariant_ok()); + } + +protected: + bool is_invariant_ok() override + { + return true; + } + +private: + std::size_t field_index__(const std::string& name, std::size_t offset) const + { + DMITIGR_REQUIRE(offset < field_count(), std::out_of_range); + const auto b = cbegin(datas_); + const auto e = cend(datas_); + const auto ident = unquote_identifier(name); + const auto i = std::find_if(b + offset, e, [&](const auto& pair) { return pair.first == ident; }); + return (i - b); + } + + std::vector>> datas_; +}; + +} // namespace dmitigr::pgfe::detail + +// ============================================================================= namespace dmitigr::pgfe { -DMITIGR_PGFE_API std::unique_ptr Composite::make() +DMITIGR_PGFE_INLINE std::unique_ptr Composite::make() { return std::make_unique(); } -DMITIGR_PGFE_API std::unique_ptr Composite::make(std::vector>>&& v) +DMITIGR_PGFE_INLINE std::unique_ptr Composite::make(std::vector>>&& v) { return std::make_unique(std::move(v)); } } // namespace dmitigr::pgfe + +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/composite.hpp b/lib/dmitigr/pgfe/composite.hpp index 4413e0c..f7a4dd4 100644 --- a/lib/dmitigr/pgfe/composite.hpp +++ b/lib/dmitigr/pgfe/composite.hpp @@ -277,4 +277,8 @@ class Composite : public Compositional { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/composite.cpp" +#endif + #endif // DMITIGR_PGFE_COMPOSITE_HPP diff --git a/lib/dmitigr/pgfe/composite.hxx b/lib/dmitigr/pgfe/composite.hxx deleted file mode 100644 index ae65ff1..0000000 --- a/lib/dmitigr/pgfe/composite.hxx +++ /dev/null @@ -1,254 +0,0 @@ -// -*- C++ -*- -// Copyright (C) Dmitry Igrishin -// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp - -#ifndef DMITIGR_PGFE_COMPOSITE_HXX -#define DMITIGR_PGFE_COMPOSITE_HXX - -#include "dmitigr/pgfe/composite.hpp" -#include "dmitigr/pgfe/compositional.hxx" -#include "dmitigr/pgfe/data.hpp" -#include "dmitigr/pgfe/sql.hxx" - -#include - -#include -#include -#include - -namespace dmitigr::pgfe::detail { - -class iComposite : public Composite { -protected: - virtual bool is_invariant_ok() = 0; -}; - -inline bool iComposite::is_invariant_ok() -{ - const bool compositional_ok = detail::is_invariant_ok(*this); - return compositional_ok; -} - -// ----------------------------------------------------------------------------- - -/** - * @internal - * - * @brief An implementation of Composite that stores data as a vector of unique - * pointers. - * - * @remarks Fields removing will not invalidate pointers returned by data(). - */ -class heap_data_Composite : public iComposite { -public: - heap_data_Composite() = default; - - explicit heap_data_Composite(std::vector>>&& datas) - : datas_{std::move(datas)} - { - DMITIGR_ASSERT(is_invariant_ok()); - } - - heap_data_Composite(const heap_data_Composite& rhs) - : datas_{rhs.datas_.size()} - { - std::transform(cbegin(rhs.datas_), cend(rhs.datas_), begin(datas_), - [&](const auto& pair) { return std::make_pair(pair.first, pair.second->to_data()); }); - DMITIGR_ASSERT(is_invariant_ok()); - } - - heap_data_Composite& operator=(const heap_data_Composite& rhs) - { - heap_data_Composite tmp{rhs}; - swap(tmp); - return *this; - } - - heap_data_Composite(heap_data_Composite&& rhs) = default; - - heap_data_Composite& operator=(heap_data_Composite&& rhs) = default; - - void swap(heap_data_Composite& rhs) noexcept - { - datas_.swap(rhs.datas_); - } - - // --------------------------------------------------------------------------- - // Compositional overridings - // --------------------------------------------------------------------------- - - std::size_t field_count() const override - { - return datas_.size(); - } - - bool has_fields() const override - { - return !datas_.empty(); - } - - const std::string& field_name(const std::size_t index) const override - { - DMITIGR_REQUIRE(index < field_count(), std::out_of_range); - return datas_[index].first; - } - - std::optional field_index(const std::string& name, const std::size_t offset = 0) const override - { - if (const auto i = field_index__(name, offset); i < field_count()) - return i; - else - return std::nullopt; - } - - std::size_t field_index_throw(const std::string& name, const std::size_t offset = 0) const override - { - const auto i = field_index__(name, offset); - DMITIGR_REQUIRE(i < field_count(), std::out_of_range); - return i; - } - - bool has_field(const std::string& name, const std::size_t offset = 0) const override - { - return bool(field_index(name, offset)); - } - - // --------------------------------------------------------------------------- - // Composite overridings - // --------------------------------------------------------------------------- - - std::unique_ptr to_composite() const override - { - return std::make_unique(*this); - } - - // --------------------------------------------------------------------------- - - const Data* data(const std::size_t index) const override - { - DMITIGR_REQUIRE(index < field_count(), std::out_of_range); - return datas_[index].second.get(); - } - - const Data* data(const std::string& name, const std::size_t offset) const override - { - return data(field_index_throw(name, offset)); - } - - // ------------------------------------------------------------------------------------- - - void set_data(const std::size_t index, std::unique_ptr&& data) override - { - DMITIGR_REQUIRE(index < field_count(), std::out_of_range); - datas_[index].second = std::move(data); - DMITIGR_ASSERT(is_invariant_ok()); - } - - void set_data(const std::size_t index, std::nullptr_t) override - { - set_data(index, std::unique_ptr{}); - } - - void set_data(const std::string& name, std::unique_ptr&& data) override - { - set_data(field_index_throw(name), std::move(data)); - } - - void set_data(const std::string& name, std::nullptr_t) override - { - set_data(name, std::unique_ptr{}); - } - - std::unique_ptr release_data(const std::size_t index) override - { - DMITIGR_REQUIRE(index < field_count(), std::out_of_range); - auto& data = datas_[index].second; - auto result = std::move(data); // As described in 14882:2014 20.8.1/4, u.p is equal to nullptr after transfer ownership... - data.reset(); // but just in case... - DMITIGR_ASSERT(is_invariant_ok()); - return result; - } - - std::unique_ptr release_data(const std::string& name, const std::size_t offset = 0) override - { - return release_data(field_index_throw(name, offset)); - } - - void append_field(const std::string& name, std::unique_ptr&& data = {}) override - { - datas_.emplace_back(name, std::move(data)); - DMITIGR_ASSERT(is_invariant_ok()); - } - - void insert_field(const std::size_t index, const std::string& name, std::unique_ptr&& data = {}) override - { - DMITIGR_REQUIRE(index < field_count(), std::out_of_range); - datas_.insert(begin(datas_) + index, std::make_pair(name, std::move(data))); - DMITIGR_ASSERT(is_invariant_ok()); - } - - void insert_field(const std::string& name, const std::string& new_field_name, std::unique_ptr&& data) override - { - insert_field(field_index_throw(name), new_field_name, std::move(data)); - } - - void remove_field(const std::size_t index) override - { - DMITIGR_REQUIRE(index < field_count(), std::out_of_range); - datas_.erase(cbegin(datas_) + index); - DMITIGR_ASSERT(is_invariant_ok()); - } - - void remove_field(const std::string& name, std::size_t offset = 0) override - { - remove_field(field_index_throw(name, offset)); - } - - // --------------------------------------------------------------------------- - - std::vector>> to_vector() const override - { - heap_data_Composite copy{*this}; - return std::move(copy.datas_); - } - - std::vector>> move_to_vector() override - { - std::vector>> result; - datas_.swap(result); - return std::move(result); - } - - // --------------------------------------------------------------------------- - // Non public API - // --------------------------------------------------------------------------- - - void append(heap_data_Composite&& rhs) - { - datas_.insert(cend(datas_), std::make_move_iterator(begin(rhs.datas_)), std::make_move_iterator(end(rhs.datas_))); - DMITIGR_ASSERT(is_invariant_ok()); - } - -protected: - bool is_invariant_ok() override - { - return true; - } - -private: - std::size_t field_index__(const std::string& name, std::size_t offset) const - { - DMITIGR_REQUIRE(offset < field_count(), std::out_of_range); - const auto b = cbegin(datas_); - const auto e = cend(datas_); - const auto ident = unquote_identifier(name); - const auto i = std::find_if(b + offset, e, [&](const auto& pair) { return pair.first == ident; }); - return (i - b); - } - - std::vector>> datas_; -}; - -} // namespace dmitigr::pgfe::detail - -#endif // DMITIGR_PGFE_COMPOSITE_HXX diff --git a/lib/dmitigr/pgfe/compositional.hxx b/lib/dmitigr/pgfe/compositional.cpp similarity index 86% rename from lib/dmitigr/pgfe/compositional.hxx rename to lib/dmitigr/pgfe/compositional.cpp index 7f6e260..505655b 100644 --- a/lib/dmitigr/pgfe/compositional.hxx +++ b/lib/dmitigr/pgfe/compositional.cpp @@ -2,10 +2,8 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#ifndef DMITIGR_PGFE_COMPOSITIONAL_HXX -#define DMITIGR_PGFE_COMPOSITIONAL_HXX - #include "dmitigr/pgfe/compositional.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" #include @@ -32,4 +30,4 @@ inline bool is_invariant_ok(Compositional& o) } // namespace dmitigr::pgfe::detail -#endif // DMITIGR_PGFE_COMPOSITIONAL_HXX +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/compositional.hpp b/lib/dmitigr/pgfe/compositional.hpp index dc442df..a73a51c 100644 --- a/lib/dmitigr/pgfe/compositional.hpp +++ b/lib/dmitigr/pgfe/compositional.hpp @@ -83,4 +83,8 @@ class Compositional { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/compositional.cpp" +#endif + #endif // DMITIGR_PGFE_COMPOSITIONAL_HPP diff --git a/lib/dmitigr/pgfe/connection.cpp b/lib/dmitigr/pgfe/connection.cpp index 8347271..6274c76 100644 --- a/lib/dmitigr/pgfe/connection.cpp +++ b/lib/dmitigr/pgfe/connection.cpp @@ -2,70 +2,1370 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#include "dmitigr/pgfe/connection.hxx" -#include "dmitigr/pgfe/connection_options.hxx" +#include "dmitigr/pgfe/basics.hpp" +#include "dmitigr/pgfe/completion.hpp" +#include "dmitigr/pgfe/connection.hpp" +#include "dmitigr/pgfe/connection_options.hpp" +#include "dmitigr/pgfe/data.hpp" +#include "dmitigr/pgfe/error.hpp" +#include "dmitigr/pgfe/exceptions.hpp" +#include "dmitigr/pgfe/notice.hpp" +#include "dmitigr/pgfe/notification.hpp" +#include "dmitigr/pgfe/pq.hpp" +#include "dmitigr/pgfe/prepared_statement_impl.hpp" +#include "dmitigr/pgfe/row.hpp" +#include "dmitigr/pgfe/row_info.hpp" +#include "dmitigr/pgfe/sql_string.hpp" +#include "dmitigr/pgfe/util.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" -#include +#include -#include +#include +#include +#include +#include +#include +#include -namespace dmitigr::pgfe { +namespace dmitigr::pgfe::detail { -namespace detail { +class iConnection : public Connection { +public: + std::unique_ptr to_connection() const override + { + return options()->make_connection(); + } -std::unique_ptr iConnection_options::make_connection() const -{ - return std::make_unique(*this); -} + // --------------------------------------------------------------------------- -} // namespace detail + bool is_connected() const override + { + return (communication_status() == Communication_status::connected); + } -DMITIGR_PGFE_API std::unique_ptr Connection::make(const Connection_options* const options) -{ - if (options) - return options->make_connection(); - else - return detail::iConnection_options{}.make_connection(); -} + bool is_transaction_block_uncommitted() const override + { + return (transaction_block_status() == Transaction_block_status::uncommitted); + } -} // namespace dmitigr::pgfe + void connect(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) override + { + using std::chrono::milliseconds; + using std::chrono::system_clock; + using std::chrono::duration_cast; -namespace pgfe = dmitigr::pgfe; + DMITIGR_REQUIRE(timeout >= milliseconds{-1}, std::invalid_argument); -namespace { + if (is_connected()) + return; // No need to check invariant. Just return. -std::atomic& openssl_library_initialization_flag() -{ - static std::atomic result{true}; - return result; -} + // Stage 1: beginning. + auto timepoint1 = system_clock::now(); -std::atomic& crypto_library_initialization_flag() -{ - static std::atomic result{true}; - return result; -} + connect_async(); + auto current_status = communication_status(); -} // namespace + const bool ignore_timeout = (timeout == milliseconds{-1}); + const auto is_timeout = [&timeout]() + { + return timeout <= std::decay_t::zero(); + }; -DMITIGR_PGFE_API void pgfe::set_openssl_library_initialization_enabled(const bool value) -{ - openssl_library_initialization_flag() = value; - ::PQinitOpenSSL(openssl_library_initialization_flag(), crypto_library_initialization_flag()); -} + const auto throw_timeout = []() + { + throw detail::iClient_exception{Client_errc::timed_out, "connection timeout"}; + }; + + if (!ignore_timeout) { + timeout -= duration_cast(system_clock::now() - timepoint1); + if (is_timeout()) + throw_timeout(); + } + + // Stage 2: polling. + while (current_status != Communication_status::connected) { + timepoint1 = system_clock::now(); + + Socket_readiness current_socket_readiness{}; + switch (current_status) { + case Communication_status::establishment_reading: + current_socket_readiness = wait_socket_readiness(Socket_readiness::read_ready, timeout); + break; + + case Communication_status::establishment_writing: + current_socket_readiness = wait_socket_readiness(Socket_readiness::write_ready, timeout); + break; + + case Communication_status::connected: + break; + + case Communication_status::disconnected: + DMITIGR_ASSERT_ALWAYS(!true); + + case Communication_status::failure: + throw std::runtime_error{error_message()}; + } + + if (!ignore_timeout) { + timeout -= duration_cast(system_clock::now() - timepoint1); + DMITIGR_ASSERT(current_socket_readiness != Socket_readiness::unready || is_timeout()); + if (is_timeout()) + throw_timeout(); + } + + connect_async(); + current_status = communication_status(); + } // while + + DMITIGR_ASSERT(is_invariant_ok()); + } + + Socket_readiness wait_socket_readiness(Socket_readiness mask, + std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) const override + { + using std::chrono::system_clock; + using std::chrono::milliseconds; + using std::chrono::duration_cast; + + DMITIGR_REQUIRE(timeout >= milliseconds{-1}, std::invalid_argument); + + { + const auto cs = communication_status(); + DMITIGR_REQUIRE(cs != Communication_status::failure && cs != Communication_status::disconnected, std::logic_error); + } + + DMITIGR_ASSERT(socket() >= 0); + + const bool ignore_timeout = (timeout == milliseconds{-1}); + + while (true) { + const auto timepoint1 = system_clock::now(); + try { + return detail::poll_sock(socket(), mask, timeout); + } catch (const std::system_error& e) { + // Retry on EINTR. + if (e.code() == std::errc::interrupted) { + if (!ignore_timeout) { + timeout -= duration_cast(system_clock::now() - timepoint1); + if (timeout <= decltype (timeout)::zero()) + // Timeout. + return Socket_readiness::unready; + } else + continue; + } else + throw; + } + } + } + + Socket_readiness socket_readiness(const Socket_readiness mask) const override + { + constexpr std::chrono::milliseconds no_wait_just_poll{}; + return wait_socket_readiness(mask, no_wait_just_poll); + } + +protected: + virtual int socket() const = 0; + +public: + bool is_server_message_available() const noexcept override + { + return is_signal_available() || is_response_available(); + } + + void wait_response(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) override + { + using std::chrono::system_clock; + using std::chrono::milliseconds; + using std::chrono::duration_cast; + + DMITIGR_REQUIRE(timeout >= milliseconds{-1}, std::invalid_argument); + DMITIGR_REQUIRE(is_connected() && is_awaiting_response(), std::logic_error); + + if (is_response_available()) + return; + + const bool ignore_timeout = (timeout == milliseconds{-1}); + + while (true) { + collect_server_messages(); + handle_signals(); + if (is_response_available()) + break; + const auto timepoint1 = system_clock::now(); + if (wait_socket_readiness(Socket_readiness::read_ready, timeout) == Socket_readiness::read_ready) { + if (!ignore_timeout) + timeout -= duration_cast(system_clock::now() - timepoint1); + } else + // Timeout. + break; + } + + DMITIGR_ASSERT(is_invariant_ok()); + } + + void wait_response_throw(const std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) override + { + wait_response(timeout); + throw_if_error(); + } + + void wait_last_response(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) override + { + using std::chrono::system_clock; + using std::chrono::milliseconds; + using std::chrono::duration_cast; + + const bool ignore_timeout = (timeout == milliseconds{-1}); + + while (true) { + const auto timepoint1 = system_clock::now(); + + wait_response(timeout); + + if (is_awaiting_response()) + dismiss_response(); + else + break; + + if (!ignore_timeout) { + timeout -= duration_cast(system_clock::now() - timepoint1); + if (timeout <= decltype (timeout)::zero()) + break; + } + } + } + + void wait_last_response_throw(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) override + { + wait_last_response(timeout); + throw_if_error(); + } + +private: + template + Prepared_statement* prepare_statement__(T&& statement, const std::string& name) + { + DMITIGR_REQUIRE(is_ready_for_request(), std::logic_error); + prepare_statement_async(std::forward(statement), name); + wait_response_throw(); + return prepared_statement(); + } + +public: + Prepared_statement* prepare_statement(const Sql_string* const statement, const std::string& name = {}) override + { + return prepare_statement__(statement, name); + } + + Prepared_statement* prepare_statement(const std::string& statement, const std::string& name = {}) override + { + return prepare_statement__(statement, name); + } + + Prepared_statement* describe_prepared_statement(const std::string& name) override + { + DMITIGR_REQUIRE(is_ready_for_request(), std::logic_error); + describe_prepared_statement_async(name); + wait_response_throw(); + return prepared_statement(); + } + + void unprepare_statement(const std::string& name) override + { + DMITIGR_REQUIRE(is_ready_for_request(), std::logic_error); + unprepare_statement_async(name); + wait_response_throw(); // Checking invariant. + } + +protected: + virtual bool is_invariant_ok() = 0; + + virtual std::string error_message() const = 0; + + void throw_if_error() + { + if (const std::shared_ptr ei{release_error()}; ei) + throw iServer_exception(ei); + } +}; -DMITIGR_PGFE_API bool pgfe::is_openssl_library_initialization_enabled() +inline bool iConnection::is_invariant_ok() { - return openssl_library_initialization_flag(); + const bool trans_ok = !is_connected() || transaction_block_status(); + const bool sess_time_ok = !is_connected() || session_start_time(); + const bool pid_ok = !is_connected() || server_pid(); + const bool readiness_ok = is_ready_for_async_request() || !is_ready_for_request(); + return trans_ok && sess_time_ok && pid_ok && readiness_ok; } -DMITIGR_PGFE_API void pgfe::set_crypto_library_initialization_enabled(const bool value) +// ----------------------------------------------------------------------------- + +class pq_Connection final : public iConnection { +public: + ~pq_Connection() override + { + ::PQfinish(conn_); + } + + explicit pq_Connection(iConnection_options options) + : options_{std::move(options)} + , notice_handler_{&default_notice_handler} + {} + + // Non copyable. + pq_Connection(const pq_Connection&) = delete; + + // Movable. + pq_Connection(pq_Connection&& rhs) = default; + + // Non copyable. + pq_Connection& operator=(const pq_Connection&) = delete; + + // Movable. + pq_Connection& operator=(pq_Connection&& rhs) = default; + + // --------------------------------------------------------------------------- + + bool is_ssl_secured() const override + { + return conn_ ? ::PQsslInUse(conn_) : false; + } + + Communication_status communication_status() const override + { + using Status = Communication_status; + + if (polling_status_) { + DMITIGR_ASSERT(conn_); + return *polling_status_; + } else if (conn_) { + return (::PQstatus(conn_) == CONNECTION_OK) ? Status::connected : Status::failure; + } else + return Status::disconnected; + } + + std::optional transaction_block_status() const override + { + if (conn_) { + switch (::PQtransactionStatus(conn_)) { + case PQTRANS_IDLE: return (transaction_block_status_ = Transaction_block_status::unstarted); + case PQTRANS_INTRANS: return (transaction_block_status_ = Transaction_block_status::uncommitted); + case PQTRANS_INERROR: return (transaction_block_status_ = Transaction_block_status::failed); + default: return transaction_block_status_; // last reported transaction status + } + } else + return transaction_block_status_; + } + + std::optional session_start_time() const noexcept override + { + return session_start_time_; + } + + const Connection_options* options() const noexcept override + { + return &options_; + } + + std::optional server_pid() const override + { + if (conn_) { + if (const int result = ::PQbackendPID(conn_)) + return (server_pid_ = result); + else + return server_pid_; + } else + return server_pid_; + } + + void disconnect() override + { + reset_session(); + + ::PQfinish(conn_); // Discarding unhandled notifications btw. + conn_ = nullptr; + DMITIGR_ASSERT(communication_status() == Communication_status::disconnected); + + DMITIGR_ASSERT(is_invariant_ok()); + } + + void connect_async() override + { + using Status = Communication_status; + + const auto s = communication_status(); + if (s == Status::connected) { + return; + } else if (s == Status::establishment_reading || s == Status::establishment_writing) { + DMITIGR_ASSERT(conn_); + switch (::PQconnectPoll(conn_)) { + case PGRES_POLLING_READING: + polling_status_ = Status::establishment_reading; + DMITIGR_ASSERT(communication_status() == Status::establishment_reading); + goto done; + + case PGRES_POLLING_WRITING: + polling_status_ = Status::establishment_writing; + DMITIGR_ASSERT(communication_status() == Status::establishment_writing); + goto done; + + case PGRES_POLLING_FAILED: + polling_status_.reset(); + DMITIGR_ASSERT(communication_status() == Status::failure); + goto done; + + case PGRES_POLLING_OK: + polling_status_.reset(); + session_start_time_ = std::chrono::system_clock::now(); + /* + * We cannot assert here that communication_status() is "connected", because it can + * become "failure" at *any* time, even just after successful connection establishment. + */ + DMITIGR_ASSERT(communication_status() == Status::connected || communication_status() == Status::failure); + goto done; + + default: DMITIGR_ASSERT_ALWAYS(!true); + } // switch + } else /* failure or disconnected */ { + if (s == Status::failure) + disconnect(); + + DMITIGR_ASSERT(communication_status() == Status::disconnected); + + const pq_Connection_options pq_options(&options_); + constexpr int expand_dbname{0}; + conn_ = ::PQconnectStartParams(pq_options.keywords(), pq_options.values(), expand_dbname); + if (conn_) { + const auto conn_status = ::PQstatus(conn_); + if (conn_status == CONNECTION_BAD) + throw std::runtime_error(error_message()); + else + polling_status_ = Status::establishment_writing; + + // Caution: until now we cannot use communication_status()! + DMITIGR_ASSERT(communication_status() == Status::establishment_writing); + + ::PQsetNoticeReceiver(conn_, ¬ice_receiver, this); + } else + throw std::bad_alloc(); + } + + done: + DMITIGR_ASSERT(is_invariant_ok()); + } + +protected: + int socket() const override + { + return ::PQsocket(conn_); + } + +public: + void collect_server_messages() override + { + DMITIGR_REQUIRE(is_connected(), std::logic_error); + + const auto consume_input = [this]() + { + while (socket_readiness(Socket_readiness::read_ready) == Socket_readiness::read_ready) { + if (!::PQconsumeInput(conn_)) + throw std::runtime_error(error_message()); + } + }; + + const auto is_get_result_would_block = [this]() + { + /* + * Checking for nonblocking result and collecting notices btw. + * Note: notice_receiver() (which is the Notice collector) will be + * called (indirectly) from ::PQisBusy(). + * Note: ::PQisBusy() calls a routine (pqParseInput3() from fe-protocol3.c) + * which parses consumed input and stores notifications and notices if + * available. (::PQnotifies() calls this routine as well.) + */ + const int result = ::PQisBusy(conn_); + + /* + * Collecting notifications. + * Note: notifications are collected by libpq after ::PQisBusy() was called. + */ + while (auto* const n = ::PQnotifies(conn_)) + signals_.push_back(pq_Notification(n)); + + return result; + }; + + const auto is_pending_result_error = [this]() + { + return pending_result_ && (pending_result_.status() == PGRES_FATAL_ERROR); + }; + + const auto is_request_done = [&]() + { + consume_input(); + + if (!is_get_result_would_block()) { + pq::Result tmp{::PQgetResult(conn_)}; + if (is_pending_result_error()) { + /* + * According to https://www.postgresql.org/docs/10/static/libpq-async.html#LIBPQ-PQGETRESULT + * + * "Even when PQresultStatus indicates a fatal error, PQgetResult + * should be called until it returns a null pointer, to allow libpq + * to process the error information completely." + * + * We have stored the result with PGRES_FATAL_ERROR in the pending_result_ before. + * Now we have called ::PQgetResult() second time and we can assert that it returned + * nullptr (because it's logical and because the libpq documentation says nothing + * about what to do when the result is not null in this case). + */ + DMITIGR_ASSERT(!tmp && !response_); + response_ = problem(pending_result_.pg_result()); + pending_result_.reset(); + } else { + DMITIGR_ASSERT(!pending_result_); + pending_result_ = std::move(tmp); + } + return !bool(pending_result_); + } else + return false; // busy + }; + + const auto next_result = [&]() + { + DMITIGR_ASSERT(!is_pending_result_error()); + if (pending_result_) + return std::move(pending_result_); + else + return !is_get_result_would_block() ? pq::Result{::PQgetResult(conn_)} : pq::Result{}; + }; + + if (is_pending_result_error()) + goto almost_done; + + // Attempt to consume some input into the internal buffer even if the response is already available. + consume_input(); + + if (is_response_available()) + goto done; + + if (auto r = next_result(); !r) { + DMITIGR_ASSERT(::PQisBusy(conn_) || requests_.empty()); + goto done; + } else /* processing the new result */ { + DMITIGR_ASSERT(!pending_result_ && r && (r.status() != PGRES_NONFATAL_ERROR) && !response_ && !requests_.empty()); + const auto s = r.status(); + const auto op_id = requests_.front(); + switch (s) { + case PGRES_SINGLE_TUPLE: + DMITIGR_ASSERT(op_id == Request_id::perform || op_id == Request_id::execute); + if (!shared_field_names_) + shared_field_names_ = pq_Row_info::make_shared_field_names(r); + response_ = pq_Row(pq_Row_info(std::move(r), shared_field_names_)); + goto done; + + case PGRES_TUPLES_OK: + DMITIGR_ASSERT(op_id == Request_id::perform || op_id == Request_id::execute); + response_ = simple_Completion(r.command_tag()); + goto almost_done; + + case PGRES_FATAL_ERROR: + pending_result_ = std::move(r); // the error information may be incomplete at the moment! + goto almost_done; + + case PGRES_COMMAND_OK: + switch (op_id) { + case Request_id::perform: + [[fallthrough]]; + + case Request_id::execute: + response_ = simple_Completion(r.command_tag()); + goto almost_done; + + case Request_id::prepare_statement: + DMITIGR_ASSERT(request_prepared_statement_); + response_ = register_ps(std::move(*request_prepared_statement_)); + goto almost_done; + + case Request_id::describe_prepared_statement: { + DMITIGR_ASSERT(request_prepared_statement_name_); + auto* p = ps(*request_prepared_statement_name_); + if (!p) + p = register_ps(pq_Prepared_statement(std::move(*request_prepared_statement_name_), this, r.field_count())); + p->set_description(std::move(r)); + response_ = p; + goto almost_done; + } + + case Request_id::unprepare_statement: + DMITIGR_ASSERT(request_prepared_statement_name_ && std::strcmp(r.command_tag(), "DEALLOCATE") == 0); + unregister_ps(*request_prepared_statement_name_); + response_ = simple_Completion("unprepare_statement"); + goto almost_done; + + default: DMITIGR_ASSERT(!true); + } // PGRES_COMMAND_OK + + case PGRES_EMPTY_QUERY: + response_ = simple_Completion(std::string{}); + goto almost_done; + + case PGRES_BAD_RESPONSE: + response_ = simple_Completion("invalid response"); + goto almost_done; + + default: DMITIGR_ASSERT(!true); + } // switch (status) + } // if (next result) + + almost_done: + if (is_request_done()) { + shared_field_names_.reset(); + request_prepared_statement_.reset(); + request_prepared_statement_name_.reset(); + + if (error()) + requests_.clear(); + else + requests_.pop(); + } + + done: + DMITIGR_ASSERT(is_invariant_ok()); + } + + bool is_signal_available() const noexcept override + { + return !signals_.empty(); + } + + const Notice* notice() const noexcept override + { + return signal_ptr(); + } + + std::unique_ptr pop_notice() override + { + return pop_signal(); + } + + void dismiss_notice() override + { + dismiss_signal(); + } + + const Notification* notification() const noexcept override + { + return signal_ptr(); + } + + std::unique_ptr pop_notification() override + { + return pop_signal(); + } + + void dismiss_notification() override + { + dismiss_signal(); + } + + void set_notice_handler(const std::function&&)>& handler) override + { + notice_handler_ = handler; + + DMITIGR_ASSERT(is_invariant_ok()); + } + + std::function&&)> notice_handler() const override + { + return notice_handler_; + } + + void set_notification_handler(const std::function&&)>& handler) override + { + notification_handler_ = handler; + + DMITIGR_ASSERT(is_invariant_ok()); + } + + std::function&&)> notification_handler() const override + { + return notification_handler_; + } + + void handle_signals() override + { + const auto handle_notice = notice_handler(); + const auto handle_notification = notification_handler(); + if (handle_notice && handle_notification) { + while (is_signal_available()) { + std::visit( + [&](auto& signal) + { + using T = std::decay_t; + auto signal_ptr = std::make_unique(std::move(signal)); + if constexpr (std::is_same_v) + handle_notice(std::move(signal_ptr)); + else if constexpr (std::is_same_v) + handle_notification(std::move(signal_ptr)); + else + DMITIGR_ASSERT(!true); + }, signals_.front()); + signals_.pop_front(); + } + } else if (handle_notice) { + while (auto n = pop_notice()) + handle_notice(std::move(n)); + } else if (handle_notification) { + while (auto n = pop_notification()) + handle_notification(std::move(n)); + } + } + + bool is_awaiting_response() const noexcept override + { + return !requests_.empty(); + } + + bool is_response_available() const noexcept override + { + return bool(response_); + } + + void dismiss_response() noexcept override + { + response_.reset(); + } + + const Error* error() const noexcept override + { + return response_ptr(); + } + + std::unique_ptr release_error() override + { + return release_response(); + } + + const Row* row() const noexcept override + { + return response_ptr(); + } + + std::unique_ptr release_row() override + { + return release_response(); + } + + const Completion* completion() const noexcept override + { + return response_ptr(); + } + + std::unique_ptr release_completion() override + { + return release_response(); + } + + Prepared_statement* prepared_statement() const override + { + return response_ptr(); + } + + Prepared_statement* prepared_statement(const std::string& name) const override + { + return ps(name); + } + + bool is_ready_for_async_request() const override + { + return is_connected() && requests_.empty() && (!response_ || completion() || prepared_statement()); + } + + bool is_ready_for_request() const override + { + // At the moment, is_ready_for_request() is similar to is_ready_for_async_request(). + return is_ready_for_async_request(); + } + + void perform_async(const std::string& queries) override + { + DMITIGR_REQUIRE(is_ready_for_async_request(), std::logic_error); + + requests_.push(Request_id::perform); // can throw + try { + const auto send_ok = ::PQsendQuery(conn_, queries.c_str()); + if (!send_ok) + throw std::runtime_error(error_message()); + + const auto set_ok = ::PQsetSingleRowMode(conn_); + DMITIGR_ASSERT_ALWAYS(set_ok); + dismiss_response(); // cannot throw + } catch (...) { + requests_.pop(); // rollback + throw; + } + + DMITIGR_ASSERT(is_invariant_ok()); + } + + void perform(const std::string& queries) override + { + DMITIGR_REQUIRE(is_ready_for_request(), std::logic_error); + perform_async(queries); + wait_response_throw(); + } + +private: + // Exception safety guarantee: strong. + void prepare_statement_async__(const char* const query, const char* const name, const iSql_string* const preparsed) + { + DMITIGR_ASSERT(query && name); + DMITIGR_REQUIRE(is_ready_for_async_request(), std::logic_error); + DMITIGR_ASSERT(!request_prepared_statement_); + + requests_.push(Request_id::prepare_statement); // can throw + try { + pq_Prepared_statement ps{name, this, preparsed}; + constexpr int n_params{0}; + constexpr const ::Oid* const param_types{nullptr}; + const int send_ok = ::PQsendPrepare(conn_, name, query, n_params, param_types); + if (!send_ok) + throw std::runtime_error(error_message()); + request_prepared_statement_ = std::move(ps); // cannot throw + dismiss_response(); // cannot throw + } catch (...) { + requests_.pop(); // rollback + throw; + } + + DMITIGR_ASSERT(is_invariant_ok()); + } + +public: + void prepare_statement_async(const Sql_string* const statement, const std::string& name = {}) override + { + DMITIGR_REQUIRE(statement && !statement->has_missing_parameters(), std::invalid_argument); + const auto* const s = dynamic_cast(statement); + DMITIGR_ASSERT(s); + prepare_statement_async__(s->to_query_string().c_str(), name.c_str(), s); // can throw + } + + void prepare_statement_async(const std::string& query, const std::string& name = {}) override + { + prepare_statement_async__(query.c_str(), name.c_str(), nullptr); // can throw + } + + void describe_prepared_statement_async(const std::string& name) override + { + DMITIGR_REQUIRE(is_ready_for_async_request(), std::logic_error); + DMITIGR_ASSERT(!request_prepared_statement_name_); + + requests_.push(Request_id::describe_prepared_statement); // can throw + try { + auto name_copy = name; + const int send_ok = ::PQsendDescribePrepared(conn_, name.c_str()); + if (!send_ok) + throw std::runtime_error(error_message()); + request_prepared_statement_name_ = std::move(name_copy); // cannot throw + dismiss_response(); // cannot throw + } catch (...) { + requests_.pop(); // rollback + throw; + } + + DMITIGR_ASSERT(is_invariant_ok()); + } + + void unprepare_statement_async(const std::string& name) override + { + DMITIGR_REQUIRE(!name.empty(), std::invalid_argument); + DMITIGR_ASSERT(!request_prepared_statement_name_); + + auto name_copy = name; // can throw + const auto query = "DEALLOCATE " + to_quoted_identifier(name); // can throw + + perform_async(query); // can throw + DMITIGR_ASSERT(requests_.front() == Request_id::perform); + requests_.front() = Request_id::unprepare_statement; // cannot throw + request_prepared_statement_name_ = std::move(name_copy); // cannot throw + + DMITIGR_ASSERT(is_invariant_ok()); + } + + void set_result_format(const Data_format format) override + { + default_result_format_ = format; + + DMITIGR_ASSERT(is_invariant_ok()); + } + + Data_format result_format() const noexcept override + { + return default_result_format_; + } + + void for_each(const std::function& body) override + { + DMITIGR_REQUIRE(body, std::invalid_argument); + + while (const auto* const r = row()) { + body(r); + dismiss_response(); + wait_response_throw(); + } + } + + void for_each(const std::function&&)>& body) override + { + DMITIGR_REQUIRE(body, std::invalid_argument); + + while (auto r = release_row()) { + body(std::move(r)); + wait_response_throw(); + } + } + + void complete(const std::function& body = {}) override + { + if (is_awaiting_response()) + wait_last_response_throw(); + + if (const auto* const c = completion()) { + if (body) + body(c); + dismiss_response(); + } + } + + void complete(const std::function&&)>& body) override + { + if (is_awaiting_response()) + wait_last_response_throw(); + + if (auto c = release_completion()) { + if (body) + body(std::move(c)); + } + } + + std::string to_quoted_literal(const std::string& literal) const override + { + DMITIGR_REQUIRE(is_connected(), std::logic_error); + + using Uptr = std::unique_ptr; + if (const auto p = Uptr{::PQescapeLiteral(conn_, literal.data(), literal.size()), &::PQfreemem}) + return p.get(); + else if (is_out_of_memory()) + throw std::bad_alloc(); + else + throw std::runtime_error(error_message()); + } + + std::string to_quoted_identifier(const std::string& identifier) const override + { + DMITIGR_REQUIRE(is_connected(), std::logic_error); + + using Uptr = std::unique_ptr; + if (const auto p = Uptr{::PQescapeIdentifier(conn_, identifier.data(), identifier.size()), &::PQfreemem}) + return p.get(); + else if (is_out_of_memory()) + throw std::bad_alloc(); + else + throw std::runtime_error(error_message()); + } + + std::unique_ptr to_hex_data(const Data* const binary_data) const override + { + auto[storage, size] = to_hex_storage(binary_data); + return Data::make(std::move(storage), size, Data_format::text); + } + + std::string to_hex_string(const Data* const binary_data) const override + { + const auto[storage, size] = to_hex_storage(binary_data); + return std::string(reinterpret_cast(storage.get()), size); + } + +protected: + bool is_invariant_ok() override + { + using Status = Communication_status; + + const bool conn_ok = conn_ || !polling_status_; + const bool polling_status_ok = + !polling_status_ || + (*polling_status_ == Status::establishment_reading) || + (*polling_status_ == Status::establishment_writing); + const bool requests_ok = requests_.empty() || !is_ready_for_async_request(); + const bool request_prepared_ok = + requests_.empty() || + (requests_.front() != Request_id::prepare_statement && + requests_.front() != Request_id::describe_prepared_statement && + requests_.front() != Request_id::unprepare_statement && + !request_prepared_statement_ && !request_prepared_statement_name_) || + (requests_.front() == Request_id::prepare_statement && + request_prepared_statement_ && !request_prepared_statement_name_) || + ((requests_.front() == Request_id::describe_prepared_statement || + requests_.front() == Request_id::unprepare_statement) && + !request_prepared_statement_ && request_prepared_statement_name_); + const bool shared_field_names_ok = !row() || shared_field_names_; + const bool session_start_time_ok = + ((communication_status() == Communication_status::connected) == bool(session_start_time_)); + const bool session_data_empty = + !session_start_time_ && + signals_.empty() && + !response_ && + !pending_result_ && + !transaction_block_status_ && + !server_pid_ && + named_prepared_statements_.empty() && + !unnamed_prepared_statement_ && + !shared_field_names_ && + requests_.empty() && + !request_prepared_statement_ && + !request_prepared_statement_name_; + const bool session_data_ok = + session_data_empty || + ((communication_status() == Communication_status::failure) || (communication_status() == Communication_status::connected)); + const bool iconnection_ok = iConnection::is_invariant_ok(); + + return + conn_ok && + polling_status_ok && + requests_ok && + request_prepared_ok && + shared_field_names_ok && + session_start_time_ok && + session_data_ok && + iconnection_ok; + } + + std::string error_message() const override + { + /* + * If nullptr passed to ::PQerrorMessage() it returns + * something like "connection pointer is NULL\n". + */ + return conn_ ? string::literal(::PQerrorMessage(conn_)) : std::string{}; + } + +private: + friend class pq_Prepared_statement; + + // --------------------------------------------------------------------------- + // Persistent data + // --------------------------------------------------------------------------- + + // Persistent data / constant data + iConnection_options options_; + + // Persistent data / public-modifiable data + std::function&&)> notice_handler_; + std::function&&)> notification_handler_; + Data_format default_result_format_{Data_format::text}; + + // Persistent data / private-modifiable data + ::PGconn* conn_{nullptr}; + std::optional polling_status_; + + // --------------------------------------------------------------------------- + // Session data + // --------------------------------------------------------------------------- + + std::optional session_start_time_; + + using Signal_ = std::variant; + mutable std::list signals_; + + using Response_ = std::optional>; + mutable Response_ response_; + pq::Result pending_result_; + mutable std::optional transaction_block_status_; + mutable std::optional server_pid_; + mutable std::list named_prepared_statements_; + mutable std::optional unnamed_prepared_statement_; + std::shared_ptr> shared_field_names_; + + // Session data / requests data + enum class Request_id { + perform = 1, + execute, + prepare_statement, + describe_prepared_statement, + unprepare_statement + }; + + class Requests_queue final : public std::queue { + public: + void clear() { c.clear(); } + }; + + Requests_queue requests_; // for now only 1 request can be queued + std::optional request_prepared_statement_; + std::optional request_prepared_statement_name_; + + // --------------------------------------------------------------------------- + // Handlers + // --------------------------------------------------------------------------- + + static void notice_receiver(void* const arg, const ::PGresult* const r) + { + DMITIGR_ASSERT(arg); + DMITIGR_ASSERT(r); + auto const cn = static_cast(arg); + cn->signals_.push_back(problem(r)); + } + + static void default_notice_handler(std::unique_ptr&& n) + { + DMITIGR_ASSERT(n); + std::fprintf(stderr, "PostgreSQL Notice: %s\n", n->brief().c_str()); + } + + // --------------------------------------------------------------------------- + // Session data helpers + // --------------------------------------------------------------------------- + + void reset_session() + { + session_start_time_.reset(); + signals_.clear(); + response_.reset(); + pending_result_.reset(); + transaction_block_status_.reset(); + server_pid_.reset(); + named_prepared_statements_.clear(); + unnamed_prepared_statement_.reset(); + shared_field_names_.reset(); + requests_.clear(); + request_prepared_statement_.reset(); + request_prepared_statement_name_.reset(); + } + + // --------------------------------------------------------------------------- + // Prepared statement helpers + // --------------------------------------------------------------------------- + + /* + * Attempts to find the prepared statement. + * + * @returns The pointer to the founded prepared statement, or `nullptr` if not found. + */ + pq_Prepared_statement* ps(const std::string& name) const + { + if (!name.empty()) { + const auto b = begin(named_prepared_statements_); + const auto e = end(named_prepared_statements_); + auto p = std::find_if(b, e, + [&name](const auto& ps) + { + return (ps.name() == name); + }); + return (p != e) ? &*p : nullptr; + } else + return unnamed_prepared_statement_ ? &*unnamed_prepared_statement_ : nullptr; + } + + /* + * Register prepared statement. + * + * @returns The pointer to the registered prepared statement. + */ + pq_Prepared_statement* register_ps(pq_Prepared_statement&& ps) + { + if (ps.name().empty()) { + unnamed_prepared_statement_ = std::move(ps); + return &*unnamed_prepared_statement_; + } else { + named_prepared_statements_.push_front(std::move(ps)); + return &named_prepared_statements_.front(); + } + } + + // Unregisters the prepared statement. + void unregister_ps(const std::string& name) + { + if (name.empty()) + unnamed_prepared_statement_.reset(); + else + named_prepared_statements_.remove_if([&](const auto& ps){ return ps.name() == name; }); + } + + // --------------------------------------------------------------------------- + // Server messages helpers + // --------------------------------------------------------------------------- + + template + T* variant_ptr(V&& variant) const + { + return std::visit( + [](auto& value) -> T* + { + using U = std::decay_t; + + T* result{}; + if constexpr (std::is_same_v) + result = &value; + else if constexpr (std::is_same_v) + result = value; + + return result; + }, std::forward(variant)); + } + + template + std::enable_if_t, decltype (signals_)::iterator> signal_iterator() const + { + return std::find_if(begin(signals_), end(signals_), + [](auto& variant) + { + return std::visit( + [](auto& signal) + { + using T = std::decay_t; + if constexpr (std::is_same_v) + return true; + else + return false; + }, variant); + }); + } + + template + std::enable_if_t, R*> response_ptr() const + { + return response_ ? variant_ptr(*response_) : nullptr; + } + + template + std::enable_if_t, S*> signal_ptr() const + { + const auto i = signal_iterator(); + return (i != end(signals_)) ? &std::get(*i) : nullptr; + } + + template + std::enable_if_t<(std::is_base_of_v && + std::is_base_of_v), std::unique_ptr> pop_signal() + { + if (const auto i = signal_iterator(); i != end(signals_)) { + auto result = std::make_unique(std::move(std::get(*i))); + signals_.erase(i); + return result; + } else + return nullptr; + } + + template + std::enable_if_t, void> dismiss_signal() + { + if (const auto i = signal_iterator(); i != end(signals_)) + signals_.erase(i); + } + + template + std::enable_if_t<(std::is_base_of_v && + std::is_base_of_v), std::unique_ptr> release_response() + { + if (auto* const response = response_ptr()) { + auto result = std::make_unique(std::move(*response)); + response_.reset(); + return result; + } else + return nullptr; + } + + template + static Problem problem(const ::PGresult* const r) + { + using string::literal; + using string::coalesce; + + DMITIGR_ASSERT(::PQresultStatus(r) == PGRES_NONFATAL_ERROR || ::PQresultStatus(r) == PGRES_FATAL_ERROR); + + const auto oef = [](const char* const data) + { + return data ? std::optional{data} : std::nullopt; + }; + + return Problem(literal(::PQresultErrorField(r, PG_DIAG_SEVERITY)), + oef(::PQresultErrorField(r, PG_DIAG_SEVERITY_NONLOCALIZED)), + coalesce({::PQresultErrorField(r, PG_DIAG_SQLSTATE), "00000"}), + literal(::PQresultErrorField(r, PG_DIAG_MESSAGE_PRIMARY)), + oef(::PQresultErrorField(r, PG_DIAG_MESSAGE_DETAIL)), + oef(::PQresultErrorField(r, PG_DIAG_MESSAGE_HINT)), + oef(::PQresultErrorField(r, PG_DIAG_STATEMENT_POSITION)), + oef(::PQresultErrorField(r, PG_DIAG_INTERNAL_POSITION)), + oef(::PQresultErrorField(r, PG_DIAG_INTERNAL_QUERY)), + oef(::PQresultErrorField(r, PG_DIAG_CONTEXT)), + oef(::PQresultErrorField(r, PG_DIAG_SCHEMA_NAME)), + oef(::PQresultErrorField(r, PG_DIAG_TABLE_NAME)), + oef(::PQresultErrorField(r, PG_DIAG_COLUMN_NAME)), + oef(::PQresultErrorField(r, PG_DIAG_DATATYPE_NAME)), + oef(::PQresultErrorField(r, PG_DIAG_CONSTRAINT_NAME)), + oef(::PQresultErrorField(r, PG_DIAG_SOURCE_FILE)), + oef(::PQresultErrorField(r, PG_DIAG_SOURCE_LINE)), + oef(::PQresultErrorField(r, PG_DIAG_SOURCE_FUNCTION))); + } + + // --------------------------------------------------------------------------- + // Utilities helpers + // --------------------------------------------------------------------------- + + bool is_out_of_memory() const + { + constexpr char msg[] = "out of memory"; + return !std::strncmp(::PQerrorMessage(conn_), msg, sizeof(msg) - 1); + } + + std::pair, std::size_t> to_hex_storage(const pgfe::Data* const binary_data) const + { + DMITIGR_REQUIRE(binary_data && binary_data->format() == pgfe::Data_format::binary, std::invalid_argument); + DMITIGR_REQUIRE(is_connected(), std::logic_error); + + const auto from_length = binary_data->size(); + const auto* from = reinterpret_cast(binary_data->bytes()); + std::size_t result_length{0}; + using Uptr = std::unique_ptr; + if (auto storage = Uptr{::PQescapeByteaConn(conn_, from, from_length, &result_length), &::PQfreemem}) + // The result_length includes the terminating zero byte of the result. + return std::make_pair(std::move(storage), result_length - 1); + else + /* + * Currently, the only possible error is insufficient memory for the result string. + * See: https://www.postgresql.org/docs/10/static/libpq-exec.html#LIBPQ-PQESCAPEBYTEACONN + */ + throw std::bad_alloc(); + } +}; + +} // namespace dmitigr::pgfe::detail + +// ============================================================================= + +namespace dmitigr::pgfe { + +namespace detail { + +inline std::unique_ptr iConnection_options::make_connection() const { - crypto_library_initialization_flag() = value; - ::PQinitOpenSSL(openssl_library_initialization_flag(), crypto_library_initialization_flag()); + return std::make_unique(*this); } -DMITIGR_PGFE_API bool pgfe::is_crypto_library_initialization_enabled() +} // namespace detail + +DMITIGR_PGFE_INLINE std::unique_ptr Connection::make(const Connection_options* const options) { - return crypto_library_initialization_flag(); + if (options) + return options->make_connection(); + else + return detail::iConnection_options{}.make_connection(); } + +} // namespace dmitigr::pgfe + +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/connection.hpp b/lib/dmitigr/pgfe/connection.hpp index 2b6e88c..12746bf 100644 --- a/lib/dmitigr/pgfe/connection.hpp +++ b/lib/dmitigr/pgfe/connection.hpp @@ -6,7 +6,7 @@ #define DMITIGR_PGFE_CONNECTION_HPP #include "dmitigr/pgfe/dll.hpp" -#include "dmitigr/pgfe/prepared_statement.hpp" +#include "dmitigr/pgfe/prepared_statement_dfn.hpp" #include "dmitigr/pgfe/types_fwd.hpp" #include @@ -900,40 +900,11 @@ class Connection { Connection() = default; }; -/** - * @ingroup main - * - * @brief Sets the obligation of initialization the OpenSSL library when needed. - * - * @remarks This function must be called with the value of `false` if the OpenSSL - * library is initialized yet before first connection to the PostgreSQL server. - */ -DMITIGR_PGFE_API void set_openssl_library_initialization_enabled(bool value); - -/** - * @ingroup main - * - * @return The value of the obligation of initialization the OpenSSL library when needed. - */ -DMITIGR_PGFE_API bool is_openssl_library_initialization_enabled(); - -/** - * @ingroup main - * - * @brief Sets the obligation of initialization the Crypto library when needed. - * - * @remarks This function must be called with the value of `false` if the Crypto - * library is initialized yet before first connection to the PostgreSQL server. - */ -DMITIGR_PGFE_API void set_crypto_library_initialization_enabled(bool value); - -/** - * @ingroup main - * - * @returns The value of the obligation of initialization the Crypto library when needed. - */ -DMITIGR_PGFE_API bool is_crypto_library_initialization_enabled(); - } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/connection.cpp" +#include "dmitigr/pgfe/prepared_statement_impl.cpp" +#endif + #endif // DMITIGR_PGFE_CONNECTION_HPP diff --git a/lib/dmitigr/pgfe/connection.hxx b/lib/dmitigr/pgfe/connection.hxx deleted file mode 100644 index fff31bb..0000000 --- a/lib/dmitigr/pgfe/connection.hxx +++ /dev/null @@ -1,1350 +0,0 @@ -// -*- C++ -*- -// Copyright (C) Dmitry Igrishin -// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp - -#ifndef DMITIGR_PGFE_CONNECTION_HXX -#define DMITIGR_PGFE_CONNECTION_HXX - -#include "dmitigr/pgfe/basics.hpp" -#include "dmitigr/pgfe/completion.hxx" -#include "dmitigr/pgfe/connection.hpp" -#include "dmitigr/pgfe/connection_options.hxx" -#include "dmitigr/pgfe/data.hxx" -#include "dmitigr/pgfe/error.hxx" -#include "dmitigr/pgfe/exceptions.hxx" -#include "dmitigr/pgfe/net.hxx" -#include "dmitigr/pgfe/notice.hxx" -#include "dmitigr/pgfe/notification.hxx" -#include "dmitigr/pgfe/pq.hxx" -#include "dmitigr/pgfe/prepared_statement.hxx" -#include "dmitigr/pgfe/row.hxx" -#include "dmitigr/pgfe/row_info.hxx" -#include "dmitigr/pgfe/sql_string.hxx" - -#include - -#include -#include -#include -#include -#include -#include - -namespace dmitigr::pgfe::detail { - -class iConnection : public Connection { -public: - std::unique_ptr to_connection() const override - { - return options()->make_connection(); - } - - // --------------------------------------------------------------------------- - - bool is_connected() const override - { - return (communication_status() == Communication_status::connected); - } - - bool is_transaction_block_uncommitted() const override - { - return (transaction_block_status() == Transaction_block_status::uncommitted); - } - - void connect(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) override - { - using std::chrono::milliseconds; - using std::chrono::system_clock; - using std::chrono::duration_cast; - - DMITIGR_REQUIRE(timeout >= milliseconds{-1}, std::invalid_argument); - - if (is_connected()) - return; // No need to check invariant. Just return. - - // Stage 1: beginning. - auto timepoint1 = system_clock::now(); - - connect_async(); - auto current_status = communication_status(); - - const bool ignore_timeout = (timeout == milliseconds{-1}); - const auto is_timeout = [&timeout]() - { - return timeout <= std::decay_t::zero(); - }; - - const auto throw_timeout = []() - { - throw detail::iClient_exception{Client_errc::timed_out, "connection timeout"}; - }; - - if (!ignore_timeout) { - timeout -= duration_cast(system_clock::now() - timepoint1); - if (is_timeout()) - throw_timeout(); - } - - // Stage 2: polling. - while (current_status != Communication_status::connected) { - timepoint1 = system_clock::now(); - - Socket_readiness current_socket_readiness{}; - switch (current_status) { - case Communication_status::establishment_reading: - current_socket_readiness = wait_socket_readiness(Socket_readiness::read_ready, timeout); - break; - - case Communication_status::establishment_writing: - current_socket_readiness = wait_socket_readiness(Socket_readiness::write_ready, timeout); - break; - - case Communication_status::connected: - break; - - case Communication_status::disconnected: - DMITIGR_ASSERT_ALWAYS(!true); - - case Communication_status::failure: - throw std::runtime_error{error_message()}; - } - - if (!ignore_timeout) { - timeout -= duration_cast(system_clock::now() - timepoint1); - DMITIGR_ASSERT(current_socket_readiness != Socket_readiness::unready || is_timeout()); - if (is_timeout()) - throw_timeout(); - } - - connect_async(); - current_status = communication_status(); - } // while - - DMITIGR_ASSERT(is_invariant_ok()); - } - - Socket_readiness wait_socket_readiness(Socket_readiness mask, - std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) const override - { - using std::chrono::system_clock; - using std::chrono::milliseconds; - using std::chrono::duration_cast; - - DMITIGR_REQUIRE(timeout >= milliseconds{-1}, std::invalid_argument); - - { - const auto cs = communication_status(); - DMITIGR_REQUIRE(cs != Communication_status::failure && cs != Communication_status::disconnected, std::logic_error); - } - - DMITIGR_ASSERT(socket() >= 0); - - const bool ignore_timeout = (timeout == milliseconds{-1}); - - while (true) { - const auto timepoint1 = system_clock::now(); - try { - return detail::poll_sock(socket(), mask, timeout); - } catch (const std::system_error& e) { - // Retry on EINTR. - if (e.code() == std::errc::interrupted) { - if (!ignore_timeout) { - timeout -= duration_cast(system_clock::now() - timepoint1); - if (timeout <= decltype (timeout)::zero()) - // Timeout. - return Socket_readiness::unready; - } else - continue; - } else - throw; - } - } - } - - Socket_readiness socket_readiness(const Socket_readiness mask) const override - { - constexpr std::chrono::milliseconds no_wait_just_poll{}; - return wait_socket_readiness(mask, no_wait_just_poll); - } - -protected: - virtual int socket() const = 0; - -public: - bool is_server_message_available() const noexcept override - { - return is_signal_available() || is_response_available(); - } - - void wait_response(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) override - { - using std::chrono::system_clock; - using std::chrono::milliseconds; - using std::chrono::duration_cast; - - DMITIGR_REQUIRE(timeout >= milliseconds{-1}, std::invalid_argument); - DMITIGR_REQUIRE(is_connected() && is_awaiting_response(), std::logic_error); - - if (is_response_available()) - return; - - const bool ignore_timeout = (timeout == milliseconds{-1}); - - while (true) { - collect_server_messages(); - handle_signals(); - if (is_response_available()) - break; - const auto timepoint1 = system_clock::now(); - if (wait_socket_readiness(Socket_readiness::read_ready, timeout) == Socket_readiness::read_ready) { - if (!ignore_timeout) - timeout -= duration_cast(system_clock::now() - timepoint1); - } else - // Timeout. - break; - } - - DMITIGR_ASSERT(is_invariant_ok()); - } - - void wait_response_throw(const std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) override - { - wait_response(timeout); - throw_if_error(); - } - - void wait_last_response(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) override - { - using std::chrono::system_clock; - using std::chrono::milliseconds; - using std::chrono::duration_cast; - - const bool ignore_timeout = (timeout == milliseconds{-1}); - - while (true) { - const auto timepoint1 = system_clock::now(); - - wait_response(timeout); - - if (is_awaiting_response()) - dismiss_response(); - else - break; - - if (!ignore_timeout) { - timeout -= duration_cast(system_clock::now() - timepoint1); - if (timeout <= decltype (timeout)::zero()) - break; - } - } - } - - void wait_last_response_throw(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) override - { - wait_last_response(timeout); - throw_if_error(); - } - -private: - template - Prepared_statement* prepare_statement__(T&& statement, const std::string& name) - { - DMITIGR_REQUIRE(is_ready_for_request(), std::logic_error); - prepare_statement_async(std::forward(statement), name); - wait_response_throw(); - return prepared_statement(); - } - -public: - Prepared_statement* prepare_statement(const Sql_string* const statement, const std::string& name = {}) override - { - return prepare_statement__(statement, name); - } - - Prepared_statement* prepare_statement(const std::string& statement, const std::string& name = {}) override - { - return prepare_statement__(statement, name); - } - - Prepared_statement* describe_prepared_statement(const std::string& name) override - { - DMITIGR_REQUIRE(is_ready_for_request(), std::logic_error); - describe_prepared_statement_async(name); - wait_response_throw(); - return prepared_statement(); - } - - void unprepare_statement(const std::string& name) override - { - DMITIGR_REQUIRE(is_ready_for_request(), std::logic_error); - unprepare_statement_async(name); - wait_response_throw(); // Checking invariant. - } - -protected: - virtual bool is_invariant_ok() = 0; - - virtual std::string error_message() const = 0; - - void throw_if_error() - { - if (const std::shared_ptr ei{release_error()}; ei) - throw iServer_exception(ei); - } -}; - -inline bool iConnection::is_invariant_ok() -{ - const bool trans_ok = !is_connected() || transaction_block_status(); - const bool sess_time_ok = !is_connected() || session_start_time(); - const bool pid_ok = !is_connected() || server_pid(); - const bool readiness_ok = is_ready_for_async_request() || !is_ready_for_request(); - return trans_ok && sess_time_ok && pid_ok && readiness_ok; -} - -// ----------------------------------------------------------------------------- - -class pq_Connection : public iConnection { -public: - ~pq_Connection() override - { - ::PQfinish(conn_); - } - - explicit pq_Connection(iConnection_options options) - : options_{std::move(options)} - , notice_handler_{&default_notice_handler} - {} - - // Non copyable. - pq_Connection(const pq_Connection&) = delete; - - // Movable. - pq_Connection(pq_Connection&& rhs) = default; - - // Non copyable. - pq_Connection& operator=(const pq_Connection&) = delete; - - // Movable. - pq_Connection& operator=(pq_Connection&& rhs) = default; - - // --------------------------------------------------------------------------- - - bool is_ssl_secured() const override - { - return conn_ ? ::PQsslInUse(conn_) : false; - } - - Communication_status communication_status() const override - { - using Status = Communication_status; - - if (polling_status_) { - DMITIGR_ASSERT(conn_); - return *polling_status_; - } else if (conn_) { - return (::PQstatus(conn_) == CONNECTION_OK) ? Status::connected : Status::failure; - } else - return Status::disconnected; - } - - std::optional transaction_block_status() const override - { - if (conn_) { - switch (::PQtransactionStatus(conn_)) { - case PQTRANS_IDLE: return (transaction_block_status_ = Transaction_block_status::unstarted); - case PQTRANS_INTRANS: return (transaction_block_status_ = Transaction_block_status::uncommitted); - case PQTRANS_INERROR: return (transaction_block_status_ = Transaction_block_status::failed); - default: return transaction_block_status_; // last reported transaction status - } - } else - return transaction_block_status_; - } - - std::optional session_start_time() const noexcept override - { - return session_start_time_; - } - - const Connection_options* options() const noexcept override - { - return &options_; - } - - std::optional server_pid() const override - { - if (conn_) { - if (const int result = ::PQbackendPID(conn_)) - return (server_pid_ = result); - else - return server_pid_; - } else - return server_pid_; - } - - void disconnect() override - { - reset_session(); - - ::PQfinish(conn_); // Discarding unhandled notifications btw. - conn_ = nullptr; - DMITIGR_ASSERT(communication_status() == Communication_status::disconnected); - - DMITIGR_ASSERT(is_invariant_ok()); - } - - void connect_async() override - { - using Status = Communication_status; - - const auto s = communication_status(); - if (s == Status::connected) { - return; - } else if (s == Status::establishment_reading || s == Status::establishment_writing) { - DMITIGR_ASSERT(conn_); - switch (::PQconnectPoll(conn_)) { - case PGRES_POLLING_READING: - polling_status_ = Status::establishment_reading; - DMITIGR_ASSERT(communication_status() == Status::establishment_reading); - goto done; - - case PGRES_POLLING_WRITING: - polling_status_ = Status::establishment_writing; - DMITIGR_ASSERT(communication_status() == Status::establishment_writing); - goto done; - - case PGRES_POLLING_FAILED: - polling_status_.reset(); - DMITIGR_ASSERT(communication_status() == Status::failure); - goto done; - - case PGRES_POLLING_OK: - polling_status_.reset(); - session_start_time_ = std::chrono::system_clock::now(); - /* - * We cannot assert here that communication_status() is "connected", because it can - * become "failure" at *any* time, even just after successful connection establishment. - */ - DMITIGR_ASSERT(communication_status() == Status::connected || communication_status() == Status::failure); - goto done; - - default: DMITIGR_ASSERT_ALWAYS(!true); - } // switch - } else /* failure or disconnected */ { - if (s == Status::failure) - disconnect(); - - DMITIGR_ASSERT(communication_status() == Status::disconnected); - - const pq_Connection_options pq_options(&options_); - constexpr int expand_dbname{0}; - conn_ = ::PQconnectStartParams(pq_options.keywords(), pq_options.values(), expand_dbname); - if (conn_) { - const auto conn_status = ::PQstatus(conn_); - if (conn_status == CONNECTION_BAD) - throw std::runtime_error(error_message()); - else - polling_status_ = Status::establishment_writing; - - // Caution: until now we cannot use communication_status()! - DMITIGR_ASSERT(communication_status() == Status::establishment_writing); - - ::PQsetNoticeReceiver(conn_, ¬ice_receiver, this); - } else - throw std::bad_alloc(); - } - - done: - DMITIGR_ASSERT(is_invariant_ok()); - } - -protected: - int socket() const override - { - return ::PQsocket(conn_); - } - -public: - void collect_server_messages() override - { - DMITIGR_REQUIRE(is_connected(), std::logic_error); - - const auto consume_input = [this]() - { - while (socket_readiness(Socket_readiness::read_ready) == Socket_readiness::read_ready) { - if (!::PQconsumeInput(conn_)) - throw std::runtime_error(error_message()); - } - }; - - const auto is_get_result_would_block = [this]() - { - /* - * Checking for nonblocking result and collecting notices btw. - * Note: notice_receiver() (which is the Notice collector) will be - * called (indirectly) from ::PQisBusy(). - * Note: ::PQisBusy() calls a routine (pqParseInput3() from fe-protocol3.c) - * which parses consumed input and stores notifications and notices if - * available. (::PQnotifies() calls this routine as well.) - */ - const int result = ::PQisBusy(conn_); - - /* - * Collecting notifications. - * Note: notifications are collected by libpq after ::PQisBusy() was called. - */ - while (auto* const n = ::PQnotifies(conn_)) - signals_.push_back(pq_Notification(n)); - - return result; - }; - - const auto is_pending_result_error = [this]() - { - return pending_result_ && (pending_result_.status() == PGRES_FATAL_ERROR); - }; - - const auto is_request_done = [&]() - { - consume_input(); - - if (!is_get_result_would_block()) { - pq::Result tmp{::PQgetResult(conn_)}; - if (is_pending_result_error()) { - /* - * According to https://www.postgresql.org/docs/10/static/libpq-async.html#LIBPQ-PQGETRESULT - * - * "Even when PQresultStatus indicates a fatal error, PQgetResult - * should be called until it returns a null pointer, to allow libpq - * to process the error information completely." - * - * We have stored the result with PGRES_FATAL_ERROR in the pending_result_ before. - * Now we have called ::PQgetResult() second time and we can assert that it returned - * nullptr (because it's logical and because the libpq documentation says nothing - * about what to do when the result is not null in this case). - */ - DMITIGR_ASSERT(!tmp && !response_); - response_ = problem(pending_result_.pg_result()); - pending_result_.reset(); - } else { - DMITIGR_ASSERT(!pending_result_); - pending_result_ = std::move(tmp); - } - return !bool(pending_result_); - } else - return false; // busy - }; - - const auto next_result = [&]() - { - DMITIGR_ASSERT(!is_pending_result_error()); - if (pending_result_) - return std::move(pending_result_); - else - return !is_get_result_would_block() ? pq::Result{::PQgetResult(conn_)} : pq::Result{}; - }; - - if (is_pending_result_error()) - goto almost_done; - - // Attempt to consume some input into the internal buffer even if the response is already available. - consume_input(); - - if (is_response_available()) - goto done; - - if (auto r = next_result(); !r) { - DMITIGR_ASSERT(::PQisBusy(conn_) || requests_.empty()); - goto done; - } else /* processing the new result */ { - DMITIGR_ASSERT(!pending_result_ && r && (r.status() != PGRES_NONFATAL_ERROR) && !response_ && !requests_.empty()); - const auto s = r.status(); - const auto op_id = requests_.front(); - switch (s) { - case PGRES_SINGLE_TUPLE: - DMITIGR_ASSERT(op_id == Request_id::perform || op_id == Request_id::execute); - if (!shared_field_names_) - shared_field_names_ = pq_Row_info::make_shared_field_names(r); - response_ = pq_Row(pq_Row_info(std::move(r), shared_field_names_)); - goto done; - - case PGRES_TUPLES_OK: - DMITIGR_ASSERT(op_id == Request_id::perform || op_id == Request_id::execute); - response_ = simple_Completion(r.command_tag()); - goto almost_done; - - case PGRES_FATAL_ERROR: - pending_result_ = std::move(r); // the error information may be incomplete at the moment! - goto almost_done; - - case PGRES_COMMAND_OK: - switch (op_id) { - case Request_id::perform: - [[fallthrough]]; - - case Request_id::execute: - response_ = simple_Completion(r.command_tag()); - goto almost_done; - - case Request_id::prepare_statement: - DMITIGR_ASSERT(request_prepared_statement_); - response_ = register_ps(std::move(*request_prepared_statement_)); - goto almost_done; - - case Request_id::describe_prepared_statement: { - DMITIGR_ASSERT(request_prepared_statement_name_); - auto* p = ps(*request_prepared_statement_name_); - if (!p) - p = register_ps(pq_Prepared_statement(std::move(*request_prepared_statement_name_), this, r.field_count())); - p->set_description(std::move(r)); - response_ = p; - goto almost_done; - } - - case Request_id::unprepare_statement: - DMITIGR_ASSERT(request_prepared_statement_name_ && std::strcmp(r.command_tag(), "DEALLOCATE") == 0); - unregister_ps(*request_prepared_statement_name_); - response_ = simple_Completion("unprepare_statement"); - goto almost_done; - - default: DMITIGR_ASSERT(!true); - } // PGRES_COMMAND_OK - - case PGRES_EMPTY_QUERY: - response_ = simple_Completion(std::string{}); - goto almost_done; - - case PGRES_BAD_RESPONSE: - response_ = simple_Completion("invalid response"); - goto almost_done; - - default: DMITIGR_ASSERT(!true); - } // switch (status) - } // if (next result) - - almost_done: - if (is_request_done()) { - shared_field_names_.reset(); - request_prepared_statement_.reset(); - request_prepared_statement_name_.reset(); - - if (error()) - requests_.clear(); - else - requests_.pop(); - } - - done: - DMITIGR_ASSERT(is_invariant_ok()); - } - - bool is_signal_available() const noexcept override - { - return !signals_.empty(); - } - - const Notice* notice() const noexcept override - { - return signal_ptr(); - } - - std::unique_ptr pop_notice() override - { - return pop_signal(); - } - - void dismiss_notice() override - { - dismiss_signal(); - } - - const Notification* notification() const noexcept override - { - return signal_ptr(); - } - - std::unique_ptr pop_notification() override - { - return pop_signal(); - } - - void dismiss_notification() override - { - dismiss_signal(); - } - - void set_notice_handler(const std::function&&)>& handler) override - { - notice_handler_ = handler; - - DMITIGR_ASSERT(is_invariant_ok()); - } - - std::function&&)> notice_handler() const override - { - return notice_handler_; - } - - void set_notification_handler(const std::function&&)>& handler) override - { - notification_handler_ = handler; - - DMITIGR_ASSERT(is_invariant_ok()); - } - - std::function&&)> notification_handler() const override - { - return notification_handler_; - } - - void handle_signals() override - { - const auto handle_notice = notice_handler(); - const auto handle_notification = notification_handler(); - if (handle_notice && handle_notification) { - while (is_signal_available()) { - std::visit( - [&](auto& signal) - { - using T = std::decay_t; - auto signal_ptr = std::make_unique(std::move(signal)); - if constexpr (std::is_same_v) - handle_notice(std::move(signal_ptr)); - else if constexpr (std::is_same_v) - handle_notification(std::move(signal_ptr)); - else - DMITIGR_ASSERT(!true); - }, signals_.front()); - signals_.pop_front(); - } - } else if (handle_notice) { - while (auto n = pop_notice()) - handle_notice(std::move(n)); - } else if (handle_notification) { - while (auto n = pop_notification()) - handle_notification(std::move(n)); - } - } - - bool is_awaiting_response() const noexcept override - { - return !requests_.empty(); - } - - bool is_response_available() const noexcept override - { - return bool(response_); - } - - void dismiss_response() noexcept override - { - response_.reset(); - } - - const Error* error() const noexcept override - { - return response_ptr(); - } - - std::unique_ptr release_error() override - { - return release_response(); - } - - const Row* row() const noexcept override - { - return response_ptr(); - } - - std::unique_ptr release_row() override - { - return release_response(); - } - - const Completion* completion() const noexcept override - { - return response_ptr(); - } - - std::unique_ptr release_completion() override - { - return release_response(); - } - - Prepared_statement* prepared_statement() const override - { - return response_ptr(); - } - - Prepared_statement* prepared_statement(const std::string& name) const override - { - return ps(name); - } - - bool is_ready_for_async_request() const override - { - return is_connected() && requests_.empty() && (!response_ || completion() || prepared_statement()); - } - - bool is_ready_for_request() const override - { - // At the moment, is_ready_for_request() is similar to is_ready_for_async_request(). - return is_ready_for_async_request(); - } - - void perform_async(const std::string& queries) override - { - DMITIGR_REQUIRE(is_ready_for_async_request(), std::logic_error); - - requests_.push(Request_id::perform); // can throw - try { - const auto send_ok = ::PQsendQuery(conn_, queries.c_str()); - if (!send_ok) - throw std::runtime_error(error_message()); - - const auto set_ok = ::PQsetSingleRowMode(conn_); - DMITIGR_ASSERT_ALWAYS(set_ok); - dismiss_response(); // cannot throw - } catch (...) { - requests_.pop(); // rollback - throw; - } - - DMITIGR_ASSERT(is_invariant_ok()); - } - - void perform(const std::string& queries) override - { - DMITIGR_REQUIRE(is_ready_for_request(), std::logic_error); - perform_async(queries); - wait_response_throw(); - } - -private: - // Exception safety guarantee: strong. - void prepare_statement_async__(const char* const query, const char* const name, const iSql_string* const preparsed) - { - DMITIGR_ASSERT(query && name); - DMITIGR_REQUIRE(is_ready_for_async_request(), std::logic_error); - DMITIGR_ASSERT(!request_prepared_statement_); - - requests_.push(Request_id::prepare_statement); // can throw - try { - pq_Prepared_statement ps{name, this, preparsed}; - constexpr int n_params{0}; - constexpr const ::Oid* const param_types{nullptr}; - const int send_ok = ::PQsendPrepare(conn_, name, query, n_params, param_types); - if (!send_ok) - throw std::runtime_error(error_message()); - request_prepared_statement_ = std::move(ps); // cannot throw - dismiss_response(); // cannot throw - } catch (...) { - requests_.pop(); // rollback - throw; - } - - DMITIGR_ASSERT(is_invariant_ok()); - } - -public: - void prepare_statement_async(const Sql_string* const statement, const std::string& name = {}) override - { - DMITIGR_REQUIRE(statement && !statement->has_missing_parameters(), std::invalid_argument); - const auto* const s = dynamic_cast(statement); - DMITIGR_ASSERT(s); - prepare_statement_async__(s->to_query_string().c_str(), name.c_str(), s); // can throw - } - - void prepare_statement_async(const std::string& query, const std::string& name = {}) override - { - prepare_statement_async__(query.c_str(), name.c_str(), nullptr); // can throw - } - - void describe_prepared_statement_async(const std::string& name) override - { - DMITIGR_REQUIRE(is_ready_for_async_request(), std::logic_error); - DMITIGR_ASSERT(!request_prepared_statement_name_); - - requests_.push(Request_id::describe_prepared_statement); // can throw - try { - auto name_copy = name; - const int send_ok = ::PQsendDescribePrepared(conn_, name.c_str()); - if (!send_ok) - throw std::runtime_error(error_message()); - request_prepared_statement_name_ = std::move(name_copy); // cannot throw - dismiss_response(); // cannot throw - } catch (...) { - requests_.pop(); // rollback - throw; - } - - DMITIGR_ASSERT(is_invariant_ok()); - } - - void unprepare_statement_async(const std::string& name) override - { - DMITIGR_REQUIRE(!name.empty(), std::invalid_argument); - DMITIGR_ASSERT(!request_prepared_statement_name_); - - auto name_copy = name; // can throw - const auto query = "DEALLOCATE " + to_quoted_identifier(name); // can throw - - perform_async(query); // can throw - DMITIGR_ASSERT(requests_.front() == Request_id::perform); - requests_.front() = Request_id::unprepare_statement; // cannot throw - request_prepared_statement_name_ = std::move(name_copy); // cannot throw - - DMITIGR_ASSERT(is_invariant_ok()); - } - - void set_result_format(const Data_format format) override - { - default_result_format_ = format; - - DMITIGR_ASSERT(is_invariant_ok()); - } - - Data_format result_format() const noexcept override - { - return default_result_format_; - } - - void for_each(const std::function& body) override - { - DMITIGR_REQUIRE(body, std::invalid_argument); - - while (const auto* const r = row()) { - body(r); - dismiss_response(); - wait_response_throw(); - } - } - - void for_each(const std::function&&)>& body) override - { - DMITIGR_REQUIRE(body, std::invalid_argument); - - while (auto r = release_row()) { - body(std::move(r)); - wait_response_throw(); - } - } - - void complete(const std::function& body = {}) override - { - if (is_awaiting_response()) - wait_last_response_throw(); - - if (const auto* const c = completion()) { - if (body) - body(c); - dismiss_response(); - } - } - - void complete(const std::function&&)>& body) override - { - if (is_awaiting_response()) - wait_last_response_throw(); - - if (auto c = release_completion()) { - if (body) - body(std::move(c)); - } - } - - std::string to_quoted_literal(const std::string& literal) const override - { - DMITIGR_REQUIRE(is_connected(), std::logic_error); - - using Uptr = std::unique_ptr; - if (const auto p = Uptr{::PQescapeLiteral(conn_, literal.data(), literal.size()), &::PQfreemem}) - return p.get(); - else if (is_out_of_memory()) - throw std::bad_alloc(); - else - throw std::runtime_error(error_message()); - } - - std::string to_quoted_identifier(const std::string& identifier) const override - { - DMITIGR_REQUIRE(is_connected(), std::logic_error); - - using Uptr = std::unique_ptr; - if (const auto p = Uptr{::PQescapeIdentifier(conn_, identifier.data(), identifier.size()), &::PQfreemem}) - return p.get(); - else if (is_out_of_memory()) - throw std::bad_alloc(); - else - throw std::runtime_error(error_message()); - } - - std::unique_ptr to_hex_data(const Data* const binary_data) const override - { - auto[storage, size] = to_hex_storage(binary_data); - return Data::make(std::move(storage), size, Data_format::text); - } - - std::string to_hex_string(const Data* const binary_data) const override - { - const auto[storage, size] = to_hex_storage(binary_data); - return std::string(reinterpret_cast(storage.get()), size); - } - -protected: - bool is_invariant_ok() override - { - using Status = Communication_status; - - const bool conn_ok = conn_ || !polling_status_; - const bool polling_status_ok = - !polling_status_ || - (*polling_status_ == Status::establishment_reading) || - (*polling_status_ == Status::establishment_writing); - const bool requests_ok = requests_.empty() || !is_ready_for_async_request(); - const bool request_prepared_ok = - requests_.empty() || - (requests_.front() != Request_id::prepare_statement && - requests_.front() != Request_id::describe_prepared_statement && - requests_.front() != Request_id::unprepare_statement && - !request_prepared_statement_ && !request_prepared_statement_name_) || - (requests_.front() == Request_id::prepare_statement && - request_prepared_statement_ && !request_prepared_statement_name_) || - ((requests_.front() == Request_id::describe_prepared_statement || - requests_.front() == Request_id::unprepare_statement) && - !request_prepared_statement_ && request_prepared_statement_name_); - const bool shared_field_names_ok = !row() || shared_field_names_; - const bool session_start_time_ok = - ((communication_status() == Communication_status::connected) == bool(session_start_time_)); - const bool session_data_empty = - !session_start_time_ && - signals_.empty() && - !response_ && - !pending_result_ && - !transaction_block_status_ && - !server_pid_ && - named_prepared_statements_.empty() && - !unnamed_prepared_statement_ && - !shared_field_names_ && - requests_.empty() && - !request_prepared_statement_ && - !request_prepared_statement_name_; - const bool session_data_ok = - session_data_empty || - ((communication_status() == Communication_status::failure) || (communication_status() == Communication_status::connected)); - const bool iconnection_ok = iConnection::is_invariant_ok(); - - return - conn_ok && - polling_status_ok && - requests_ok && - request_prepared_ok && - shared_field_names_ok && - session_start_time_ok && - session_data_ok && - iconnection_ok; - } - - std::string error_message() const override - { - /* - * If nullptr passed to ::PQerrorMessage() it returns - * something like "connection pointer is NULL\n". - */ - return conn_ ? string::literal(::PQerrorMessage(conn_)) : std::string{}; - } - -private: - friend class pq_Prepared_statement; - - // --------------------------------------------------------------------------- - // Persistent data - // --------------------------------------------------------------------------- - - // Persistent data / constant data - iConnection_options options_; - - // Persistent data / public-modifiable data - std::function&&)> notice_handler_; - std::function&&)> notification_handler_; - Data_format default_result_format_{Data_format::text}; - - // Persistent data / private-modifiable data - ::PGconn* conn_{nullptr}; - std::optional polling_status_; - - // --------------------------------------------------------------------------- - // Session data - // --------------------------------------------------------------------------- - - std::optional session_start_time_; - - using Signal_ = std::variant; - mutable std::list signals_; - - using Response_ = std::optional>; - mutable Response_ response_; - pq::Result pending_result_; - mutable std::optional transaction_block_status_; - mutable std::optional server_pid_; - mutable std::list named_prepared_statements_; - mutable std::optional unnamed_prepared_statement_; - std::shared_ptr> shared_field_names_; - - // Session data / requests data - enum class Request_id { - perform = 1, - execute, - prepare_statement, - describe_prepared_statement, - unprepare_statement - }; - - class Requests_queue : public std::queue { - public: - void clear() { c.clear(); } - }; - - Requests_queue requests_; // for now only 1 request can be queued - std::optional request_prepared_statement_; - std::optional request_prepared_statement_name_; - - // --------------------------------------------------------------------------- - // Handlers - // --------------------------------------------------------------------------- - - static void notice_receiver(void* const arg, const ::PGresult* const r) - { - DMITIGR_ASSERT(arg); - DMITIGR_ASSERT(r); - auto const cn = static_cast(arg); - cn->signals_.push_back(problem(r)); - } - - static void default_notice_handler(std::unique_ptr&& n) - { - DMITIGR_ASSERT(n); - std::fprintf(stderr, "PostgreSQL Notice: %s\n", n->brief().c_str()); - } - - // --------------------------------------------------------------------------- - // Session data helpers - // --------------------------------------------------------------------------- - - void reset_session() - { - session_start_time_.reset(); - signals_.clear(); - response_.reset(); - pending_result_.reset(); - transaction_block_status_.reset(); - server_pid_.reset(); - named_prepared_statements_.clear(); - unnamed_prepared_statement_.reset(); - shared_field_names_.reset(); - requests_.clear(); - request_prepared_statement_.reset(); - request_prepared_statement_name_.reset(); - } - - // --------------------------------------------------------------------------- - // Prepared statement helpers - // --------------------------------------------------------------------------- - - /* - * Attempts to find the prepared statement. - * - * @returns The pointer to the founded prepared statement, or `nullptr` if not found. - */ - pq_Prepared_statement* ps(const std::string& name) const - { - if (!name.empty()) { - const auto b = begin(named_prepared_statements_); - const auto e = end(named_prepared_statements_); - auto p = std::find_if(b, e, - [&name](const auto& ps) - { - return (ps.name() == name); - }); - return (p != e) ? &*p : nullptr; - } else - return unnamed_prepared_statement_ ? &*unnamed_prepared_statement_ : nullptr; - } - - /* - * Register prepared statement. - * - * @returns The pointer to the registered prepared statement. - */ - pq_Prepared_statement* register_ps(pq_Prepared_statement&& ps) - { - if (ps.name().empty()) { - unnamed_prepared_statement_ = std::move(ps); - return &*unnamed_prepared_statement_; - } else { - named_prepared_statements_.push_front(std::move(ps)); - return &named_prepared_statements_.front(); - } - } - - // Unregisters the prepared statement. - void unregister_ps(const std::string& name) - { - if (name.empty()) - unnamed_prepared_statement_.reset(); - else - named_prepared_statements_.remove_if([&](const auto& ps){ return ps.name() == name; }); - } - - // --------------------------------------------------------------------------- - // Server messages helpers - // --------------------------------------------------------------------------- - - template - T* variant_ptr(V&& variant) const - { - return std::visit( - [](auto& value) -> T* - { - using U = std::decay_t; - - T* result{}; - if constexpr (std::is_same_v) - result = &value; - else if constexpr (std::is_same_v) - result = value; - - return result; - }, std::forward(variant)); - } - - template - std::enable_if_t, decltype (signals_)::iterator> signal_iterator() const - { - return std::find_if(begin(signals_), end(signals_), - [](auto& variant) - { - return std::visit( - [](auto& signal) - { - using T = std::decay_t; - if constexpr (std::is_same_v) - return true; - else - return false; - }, variant); - }); - } - - template - std::enable_if_t, R*> response_ptr() const - { - return response_ ? variant_ptr(*response_) : nullptr; - } - - template - std::enable_if_t, S*> signal_ptr() const - { - const auto i = signal_iterator(); - return (i != end(signals_)) ? &std::get(*i) : nullptr; - } - - template - std::enable_if_t<(std::is_base_of_v && - std::is_base_of_v), std::unique_ptr> pop_signal() - { - if (const auto i = signal_iterator(); i != end(signals_)) { - auto result = std::make_unique(std::move(std::get(*i))); - signals_.erase(i); - return result; - } else - return nullptr; - } - - template - std::enable_if_t, void> dismiss_signal() - { - if (const auto i = signal_iterator(); i != end(signals_)) - signals_.erase(i); - } - - template - std::enable_if_t<(std::is_base_of_v && - std::is_base_of_v), std::unique_ptr> release_response() - { - if (auto* const response = response_ptr()) { - auto result = std::make_unique(std::move(*response)); - response_.reset(); - return result; - } else - return nullptr; - } - - template - static Problem problem(const ::PGresult* const r) - { - using string::literal; - using string::coalesce; - - DMITIGR_ASSERT(::PQresultStatus(r) == PGRES_NONFATAL_ERROR || ::PQresultStatus(r) == PGRES_FATAL_ERROR); - - const auto oef = [](const char* const data) - { - return data ? std::optional{data} : std::nullopt; - }; - - return Problem(literal(::PQresultErrorField(r, PG_DIAG_SEVERITY)), - oef(::PQresultErrorField(r, PG_DIAG_SEVERITY_NONLOCALIZED)), - coalesce({::PQresultErrorField(r, PG_DIAG_SQLSTATE), "00000"}), - literal(::PQresultErrorField(r, PG_DIAG_MESSAGE_PRIMARY)), - oef(::PQresultErrorField(r, PG_DIAG_MESSAGE_DETAIL)), - oef(::PQresultErrorField(r, PG_DIAG_MESSAGE_HINT)), - oef(::PQresultErrorField(r, PG_DIAG_STATEMENT_POSITION)), - oef(::PQresultErrorField(r, PG_DIAG_INTERNAL_POSITION)), - oef(::PQresultErrorField(r, PG_DIAG_INTERNAL_QUERY)), - oef(::PQresultErrorField(r, PG_DIAG_CONTEXT)), - oef(::PQresultErrorField(r, PG_DIAG_SCHEMA_NAME)), - oef(::PQresultErrorField(r, PG_DIAG_TABLE_NAME)), - oef(::PQresultErrorField(r, PG_DIAG_COLUMN_NAME)), - oef(::PQresultErrorField(r, PG_DIAG_DATATYPE_NAME)), - oef(::PQresultErrorField(r, PG_DIAG_CONSTRAINT_NAME)), - oef(::PQresultErrorField(r, PG_DIAG_SOURCE_FILE)), - oef(::PQresultErrorField(r, PG_DIAG_SOURCE_LINE)), - oef(::PQresultErrorField(r, PG_DIAG_SOURCE_FUNCTION))); - } - - // --------------------------------------------------------------------------- - // Utilities helpers - // --------------------------------------------------------------------------- - - bool is_out_of_memory() const - { - constexpr char msg[] = "out of memory"; - return !std::strncmp(::PQerrorMessage(conn_), msg, sizeof(msg) - 1); - } - - std::pair, std::size_t> to_hex_storage(const pgfe::Data* const binary_data) const - { - DMITIGR_REQUIRE(binary_data && binary_data->format() == pgfe::Data_format::binary, std::invalid_argument); - DMITIGR_REQUIRE(is_connected(), std::logic_error); - - const auto from_length = binary_data->size(); - const auto* from = reinterpret_cast(binary_data->bytes()); - std::size_t result_length{0}; - using Uptr = std::unique_ptr; - if (auto storage = Uptr{::PQescapeByteaConn(conn_, from, from_length, &result_length), &::PQfreemem}) - // The result_length includes the terminating zero byte of the result. - return std::make_pair(std::move(storage), result_length - 1); - else - /* - * Currently, the only possible error is insufficient memory for the result string. - * See: https://www.postgresql.org/docs/10/static/libpq-exec.html#LIBPQ-PQESCAPEBYTEACONN - */ - throw std::bad_alloc(); - } -}; - -} // namespace dmitigr::pgfe::detail - -#endif // DMITIGR_PGFE_CONNECTION_HXX diff --git a/lib/dmitigr/pgfe/connection_options.cpp b/lib/dmitigr/pgfe/connection_options.cpp index 4ced866..334eea6 100644 --- a/lib/dmitigr/pgfe/connection_options.cpp +++ b/lib/dmitigr/pgfe/connection_options.cpp @@ -2,18 +2,735 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#include "dmitigr/pgfe/connection_options.hxx" +#include "dmitigr/pgfe/connection_options.hpp" +#include "dmitigr/pgfe/connection_options.cxx" +#include "dmitigr/pgfe/implementation_header.hpp" + +#include +#include + +#include +#include + +namespace dmitigr::pgfe::detail { + +inline namespace validators { + +template +bool is_non_negative(const T value) +{ + return (value >= 0); +} + +template +bool is_non_empty(const T& value) +{ + return !value.empty(); +} + +template +bool is_valid_port(const T value) +{ + return (value > 0 && value < 65536); +} + +inline bool is_ip_address(const std::string& value) +{ + return net::Ip_address::is_valid(value); +} + +inline bool is_hostname(const std::string& value) +{ + return net::is_hostname_valid(value); +} + +inline bool is_absolute_directory_name(const std::filesystem::path& value) +{ + return value.is_absolute(); +} + +inline void validate(const bool condition, const std::string& option_name) +{ + if (!condition) + throw std::logic_error{"invalid value of \"" + option_name + "\" connection option"}; +} + +} // namespace validators + +// ----------------------------------------------------------------------------- + +class iConnection_options final : public Connection_options { +public: + iConnection_options() + : iConnection_options{btd::communication_mode} + {} + + /* + * It is better to provide an initializers in the members declarations, but + * due to the bug in Microsoft Visual Studio 15.7, all of them are here. + */ + explicit iConnection_options(const Communication_mode communication_mode) + : communication_mode_{communication_mode} +#ifndef _WIN32 + , uds_directory_{btd::uds_directory} + , uds_require_server_process_username_{btd::uds_require_server_process_username} +#endif + , tcp_keepalives_enabled_{btd::tcp_keepalives_enabled} + , tcp_keepalives_idle_{btd::tcp_keepalives_idle} + , tcp_keepalives_interval_{btd::tcp_keepalives_interval} + , tcp_keepalives_count_{btd::tcp_keepalives_count} + , net_address_{btd::net_address} + , net_hostname_{btd::net_hostname} + , port_{btd::port} + , username_{btd::username} + , database_{btd::database} + , password_{btd::password} + , kerberos_service_name_{btd::kerberos_service_name} + , is_ssl_enabled_{btd::ssl_enabled} + , ssl_compression_enabled_{btd::ssl_compression_enabled} + , ssl_certificate_file_{btd::ssl_certificate_file} + , ssl_private_key_file_{btd::ssl_private_key_file} + , ssl_certificate_authority_file_{btd::ssl_certificate_authority_file} + , ssl_certificate_revocation_list_file_{btd::ssl_certificate_revocation_list_file} + , ssl_server_hostname_verification_enabled_{btd::ssl_server_hostname_verification_enabled} + { + DMITIGR_ASSERT(is_invariant_ok()); + } + + std::unique_ptr make_connection() const override; // defined in connection.cpp + + std::unique_ptr to_connection_options() const override + { + return std::make_unique(*this); + } + + Connection_options* set(const Communication_mode value) override + { +#ifdef _WIN32 + DMITIGR_ASSERT(value == Communication_mode::net); +#endif + communication_mode_ = value; + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + Communication_mode communication_mode() const override + { + return communication_mode_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_port(const std::int_fast32_t value) override + { + validate(is_valid_port(value), "server port"); + port_ = value; + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + std::int_fast32_t port() const override + { + return port_; + } + + // --------------------------------------------------------------------------- + +#ifndef _WIN32 + Connection_options* set_uds_directory(std::filesystem::path value) override + { + DMITIGR_REQUIRE(communication_mode() == Communication_mode::uds, std::logic_error); + validate(is_absolute_directory_name(value), "UDS directory"); + uds_directory_ = std::move(value); + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + const std::filesystem::path& uds_directory() const override + { + return uds_directory_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_uds_require_server_process_username(std::optional value) override + { + DMITIGR_REQUIRE(communication_mode() == Communication_mode::uds, std::logic_error); + if (value) + validate(is_non_empty(*value), "UDS require server process username"); + uds_require_server_process_username_ = std::move(value); + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + const std::optional& uds_require_server_process_username() const override + { + return uds_require_server_process_username_; + } + +#endif + + // --------------------------------------------------------------------------- + + Connection_options* set_tcp_keepalives_enabled(const bool value) override + { + DMITIGR_REQUIRE(communication_mode() == Communication_mode::net, std::logic_error); + tcp_keepalives_enabled_ = value; + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + bool is_tcp_keepalives_enabled() const override + { + return tcp_keepalives_enabled_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_tcp_keepalives_idle(const std::optional value) override + { + DMITIGR_REQUIRE(communication_mode() == Communication_mode::net, std::logic_error); + if (value) + validate(is_non_negative(value->count()), "TCP keepalives idle"); + tcp_keepalives_idle_ = value; + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + std::optional tcp_keepalives_idle() const override + { + return tcp_keepalives_idle_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_tcp_keepalives_interval(const std::optional value) override + { + DMITIGR_REQUIRE(communication_mode() == Communication_mode::net, std::logic_error); + if (value) + validate(is_non_negative(value->count()), "TCP keepalives interval"); + tcp_keepalives_interval_ = value; + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + std::optional tcp_keepalives_interval() const override + { + return tcp_keepalives_interval_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_tcp_keepalives_count(const std::optional value) override + { + DMITIGR_REQUIRE(communication_mode() == Communication_mode::net, std::logic_error); + if (value) + validate(is_non_negative(*value), "TCP keepalives count"); + tcp_keepalives_count_ = value; + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + std::optional tcp_keepalives_count() const override + { + return tcp_keepalives_count_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_net_address(std::optional value) override + { + DMITIGR_REQUIRE(communication_mode() == Communication_mode::net, std::logic_error); + if (value) + validate(is_ip_address(*value), "Network address"); + else + DMITIGR_REQUIRE(net_hostname(), std::logic_error); + net_address_ = std::move(value); + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + const std::optional& net_address() const override + { + return net_address_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_net_hostname(std::optional value) override + { + DMITIGR_REQUIRE(communication_mode() == Communication_mode::net, std::logic_error); + if (value) + validate(is_hostname(*value), "Network host name"); + else + DMITIGR_REQUIRE(net_address(), std::logic_error); + net_hostname_ = std::move(value); + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + const std::optional& net_hostname() const override + { + return net_hostname_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_username(std::string value) override + { + validate(is_non_empty(value), "username"); + username_ = std::move(value); + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + const std::string& username() const override + { + return username_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_database(std::string value) override + { + validate(is_non_empty(value), "database"); + database_ = std::move(value); + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + const std::string& database() const override + { + return database_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_password(std::optional value) override + { + if (value) + validate(is_non_empty(*value), "password"); + password_ = std::move(value); + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + const std::optional& password() const override + { + return password_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_kerberos_service_name(std::optional value) override + { + if (value) + validate(is_non_empty(*value), "Kerberos service name"); + kerberos_service_name_ = std::move(value); + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + const std::optional& kerberos_service_name() const override + { + return kerberos_service_name_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_ssl_enabled(const bool value) override + { + is_ssl_enabled_ = value; + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + bool is_ssl_enabled() const override + { + return is_ssl_enabled_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_ssl_compression_enabled(const bool value) override + { + DMITIGR_REQUIRE(is_ssl_enabled(), std::logic_error); + ssl_compression_enabled_ = value; + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + bool is_ssl_compression_enabled() const override + { + return ssl_compression_enabled_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_ssl_certificate_file(std::optional value) override + { + DMITIGR_REQUIRE(is_ssl_enabled(), std::logic_error); + if (value) + validate(is_non_empty(*value), "SSL certificate file"); + ssl_certificate_file_ = std::move(value); + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + const std::optional& ssl_certificate_file() const override + { + return ssl_certificate_file_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_ssl_private_key_file(std::optional value) override + { + DMITIGR_REQUIRE(is_ssl_enabled(), std::logic_error); + if (value) + validate(is_non_empty(*value), "SSL private key file"); + ssl_private_key_file_ = std::move(value); + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + const std::optional& ssl_private_key_file() const override + { + return ssl_private_key_file_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_ssl_certificate_authority_file(std::optional value) override + { + DMITIGR_REQUIRE(is_ssl_enabled(), std::logic_error); + if (value) + validate(is_non_empty(*value), "SSL certificate authority file"); + ssl_certificate_authority_file_ = std::move(value); + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + const std::optional& ssl_certificate_authority_file() const override + { + return ssl_certificate_authority_file_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_ssl_certificate_revocation_list_file(std::optional value) override + { + DMITIGR_REQUIRE(is_ssl_enabled(), std::logic_error); + if (value) + validate(is_non_empty(*value), "SSL certificate revocation list file"); + ssl_certificate_revocation_list_file_ = std::move(value); + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + const std::optional& ssl_certificate_revocation_list_file() const override + { + return ssl_certificate_revocation_list_file_; + } + + // --------------------------------------------------------------------------- + + Connection_options* set_ssl_server_hostname_verification_enabled(const bool value) override + { + DMITIGR_REQUIRE(is_ssl_enabled() && ssl_certificate_authority_file(), std::logic_error); + ssl_server_hostname_verification_enabled_ = value; + DMITIGR_ASSERT(is_invariant_ok()); + return this; + } + + bool is_ssl_server_hostname_verification_enabled() const override + { + return ssl_server_hostname_verification_enabled_; + } + +private: + virtual bool is_invariant_ok() + { +#ifdef _WIN32 + const bool communication_mode_ok = (communication_mode_ == Communication_mode::net); + constexpr bool uds_ok = true; +#else + constexpr bool communication_mode_ok = true; + const bool uds_ok = !(communication_mode_ == Communication_mode::uds) || + (is_absolute_directory_name(uds_directory_) && + is_valid_port(port_) && + (!uds_require_server_process_username_ || !uds_require_server_process_username_->empty())); +#endif + const bool tcp_ok = !(communication_mode_ == Communication_mode::net) || + ((!tcp_keepalives_idle_ || is_non_negative(tcp_keepalives_idle_->count())) && + (!tcp_keepalives_interval_ || is_non_negative(tcp_keepalives_interval_->count())) && + (!tcp_keepalives_count_ || is_non_negative(tcp_keepalives_count_)) && + (net_address_ || net_hostname_) && + (!net_address_ || is_ip_address(*net_address_)) && + (!net_hostname_ || is_hostname(*net_hostname_)) && + is_valid_port(port_)); + const bool auth_ok = + !username_.empty() && + !database_.empty() && + (!password_ || !password_->empty()) && + (!kerberos_service_name_ || !kerberos_service_name_->empty()); + const bool ssl_ok = + (!ssl_certificate_file_ || !ssl_certificate_file_->empty()) && + (!ssl_private_key_file_ || !ssl_private_key_file_->empty()) && + (!ssl_certificate_authority_file_ || !ssl_certificate_authority_file_->empty()) && + (!ssl_certificate_revocation_list_file_ || !ssl_certificate_revocation_list_file_->empty()) && + (!ssl_server_hostname_verification_enabled_ || ssl_certificate_authority_file_); + + return communication_mode_ok && uds_ok && tcp_ok && auth_ok && ssl_ok; + } + + Communication_mode communication_mode_; +#ifndef _WIN32 + std::filesystem::path uds_directory_; + std::optional uds_require_server_process_username_; +#endif + bool tcp_keepalives_enabled_; + std::optional tcp_keepalives_idle_; + std::optional tcp_keepalives_interval_; + std::optional tcp_keepalives_count_; + std::optional net_address_; + std::optional net_hostname_; + std::int_fast32_t port_; + std::string username_; + std::string database_; + std::optional password_; + std::optional kerberos_service_name_; + bool is_ssl_enabled_; + bool ssl_compression_enabled_; + std::optional ssl_certificate_file_; + std::optional ssl_private_key_file_; + std::optional ssl_certificate_authority_file_; + std::optional ssl_certificate_revocation_list_file_; + bool ssl_server_hostname_verification_enabled_; +}; + +// ============================================================================= + +class pq_Connection_options final { +public: + pq_Connection_options(const Connection_options* const o) + { + DMITIGR_ASSERT(o); + + switch (o->communication_mode()) { + case Communication_mode::net: { + constexpr auto z = std::chrono::seconds::zero(); + values_[host] = o->net_hostname().value_or(""); + values_[hostaddr] = o->net_address().value_or(""); + values_[port] = std::to_string(o->port()); + values_[keepalives] = std::to_string(o->is_tcp_keepalives_enabled()); + values_[keepalives_idle] = std::to_string(o->tcp_keepalives_idle().value_or(z).count()); + values_[keepalives_interval] = std::to_string(o->tcp_keepalives_interval().value_or(z).count()); + values_[keepalives_count] = std::to_string(o->tcp_keepalives_count().value_or(0)); + break; + } +#ifndef _WIN32 + case Communication_mode::uds: + values_[host] = o->uds_directory().generic_string(); + values_[port] = std::to_string(o->port()); + values_[requirepeer] = o->uds_require_server_process_username().value_or(""); + break; +#endif + } + + values_[dbname] = o->database(); + values_[user] = o->username(); + values_[password] = o->password().value_or(""); + + if (o->is_ssl_enabled()) { + if (o->is_ssl_server_hostname_verification_enabled()) { + values_[sslmode] = "verify-full"; + } else { + if (o->ssl_certificate_authority_file()) + values_[sslmode] = "verify-ca"; + else + values_[sslmode] = "require"; + } + + values_[sslcompression] = std::to_string(o->is_ssl_compression_enabled()); + values_[sslcert] = o->ssl_certificate_file().value_or(std::filesystem::path{}).generic_string(); + values_[sslkey] = o->ssl_private_key_file().value_or(std::filesystem::path{}).generic_string(); + values_[sslrootcert] = o->ssl_certificate_authority_file().value_or(std::filesystem::path{}).generic_string(); + values_[sslcrl] = o->ssl_certificate_revocation_list_file().value_or(std::filesystem::path{}).generic_string(); + } else { + values_[sslmode] = "disable"; + } + + values_[krbsrvname] = o->kerberos_service_name().value_or(""); + values_[gsslib] = ""; + + // ------------------------------------------------------------------------- + // Options that are unavailable from Pgfe API (at least for now) + // ------------------------------------------------------------------------- + + values_[passfile] = ""; + values_[connect_timeout] = ""; + values_[client_encoding] = "auto"; + values_[options] = ""; + values_[application_name] = ""; + values_[fallback_application_name] = ""; + values_[service] = ""; + values_[target_session_attrs] = "any"; + + update_cache(); + } + + pq_Connection_options(const pq_Connection_options& rhs) + { + for (decltype (+Keyword_count_) i = host; i < Keyword_count_; ++i) + values_[i] = rhs.values_[i]; + update_cache(); + } + + pq_Connection_options(pq_Connection_options&& rhs) + { + for (decltype (+Keyword_count_) i = host; i < Keyword_count_; ++i) + values_[i] = std::move(rhs.values_[i]); + update_cache(); + } + + pq_Connection_options& operator=(const pq_Connection_options& rhs) + { + if (this != &rhs) { + pq_Connection_options tmp(rhs); + swap(tmp); + } + return *this; + } + + pq_Connection_options& operator=(pq_Connection_options&& rhs) + { + if (this != &rhs) { + pq_Connection_options tmp(std::move(rhs)); + swap(tmp); + } + return *this; + } + + void swap(pq_Connection_options& rhs) + { + for (decltype (+Keyword_count_) i = host; i < Keyword_count_; ++i) { + std::swap(pq_keywords_[i], rhs.pq_keywords_[i]); + std::swap(pq_values_[i], rhs.pq_values_[i]); + std::swap(values_[i], rhs.values_[i]); + } + } + + const char* const* keywords() const + { + return pq_keywords_; + } + + const char* const* values() const + { + return pq_values_; + } + + static std::size_t count() + { + return Keyword_count_; + } + +private: + constexpr bool is_invariant_ok() const + { + constexpr auto keywords_count = sizeof(pq_keywords_) / sizeof(*pq_keywords_); + constexpr auto values_count = sizeof(values_) / sizeof(*values_); + static_assert(sizeof(pq_keywords_) == sizeof(pq_values_)); + static_assert(keywords_count == (1 + values_count)); + return true; + } + + enum Keyword : std::size_t { + host = 0, hostaddr, port, + dbname, user, password, + keepalives, keepalives_idle, keepalives_interval, keepalives_count, + sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, + requirepeer, + krbsrvname, + + // Options that are unavailable from Pgfe API (at least for now): + gsslib, passfile, connect_timeout, client_encoding, options, + application_name, fallback_application_name, service, target_session_attrs, + + // The last member is special - it denotes keyword count + Keyword_count_ + }; + + static const char* to_literal(const Keyword keyword) + { + switch (keyword) { + case host: return "host"; + case hostaddr: return "hostaddr"; + case port: return "port"; + case dbname: return "dbname"; + case user: return "user"; + case password: return "password"; + case keepalives: return "keepalives"; + case keepalives_idle: return "keepalives_idle"; + case keepalives_interval: return "keepalives_interval"; + case keepalives_count: return "keepalives_count"; + case sslmode: return "sslmode"; + case sslcompression: return "sslcompression"; + case sslcert: return "sslcert"; + case sslkey: return "sslkey"; + case sslrootcert: return "sslrootcert"; + case sslcrl: return "sslcrl"; + case krbsrvname: return "krbsrvname"; + case requirepeer: return "requirepeer"; + case passfile: return "passfile"; + case connect_timeout: return "connect_timeout"; + case client_encoding: return "client_encoding"; + case options: return "options"; + case application_name: return "application_name"; + case fallback_application_name: return "fallback_application_name"; + case gsslib: return "gsslib"; + case service: return "service"; + case target_session_attrs: return "target_session_attrs"; + case Keyword_count_:; + } + DMITIGR_ASSERT_ALWAYS(!true); + } + + void update_cache() + { + for (decltype (+Keyword_count_) i = host; i < Keyword_count_; ++i) { + pq_keywords_[i] = to_literal(Keyword(i)); + pq_values_[i] = values_[i].c_str(); + } + + pq_keywords_[Keyword_count_] = nullptr; + pq_values_[Keyword_count_] = nullptr; + + DMITIGR_ASSERT(is_invariant_ok()); + } + + const char* pq_keywords_[Keyword_count_ + 1]; + const char* pq_values_[Keyword_count_ + 1]; + std::string values_[Keyword_count_]; +}; + +} // namespace dmitigr::pgfe::detail + +// ============================================================================= namespace dmitigr::pgfe { -DMITIGR_PGFE_API std::unique_ptr Connection_options::make() +DMITIGR_PGFE_INLINE std::unique_ptr Connection_options::make() { return std::make_unique(); } -DMITIGR_PGFE_API std::unique_ptr Connection_options::make(const Communication_mode value) +DMITIGR_PGFE_INLINE std::unique_ptr Connection_options::make(const Communication_mode value) { return std::make_unique(value); } } // namespace dmitigr::pgfe + +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/connection_options.cxx.in b/lib/dmitigr/pgfe/connection_options.cxx.in index 68a665d..f85d1b2 100644 --- a/lib/dmitigr/pgfe/connection_options.cxx.in +++ b/lib/dmitigr/pgfe/connection_options.cxx.in @@ -5,9 +5,9 @@ #ifndef DMITIGR_PGFE_CONNECTION_OPTIONS_CXX #define DMITIGR_PGFE_CONNECTION_OPTIONS_CXX -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// This file was generated automatically. There is no sense to edit! -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// This file was generated automatically. Edit connection_options.cxx.in instead! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #include "dmitigr/pgfe/basics.hpp" diff --git a/lib/dmitigr/pgfe/connection_options.hpp b/lib/dmitigr/pgfe/connection_options.hpp index e6b142d..9deed21 100644 --- a/lib/dmitigr/pgfe/connection_options.hpp +++ b/lib/dmitigr/pgfe/connection_options.hpp @@ -589,4 +589,8 @@ class Connection_options { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/connection_options.cpp" +#endif + #endif // DMITIGR_PGFE_CONNECTION_OPTIONS_HPP diff --git a/lib/dmitigr/pgfe/connection_options.hxx b/lib/dmitigr/pgfe/connection_options.hxx deleted file mode 100644 index b0881ae..0000000 --- a/lib/dmitigr/pgfe/connection_options.hxx +++ /dev/null @@ -1,723 +0,0 @@ -// -*- C++ -*- -// Copyright (C) Dmitry Igrishin -// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp - -#ifndef DMITIGR_PGFE_CONNECTION_OPTIONS_HXX -#define DMITIGR_PGFE_CONNECTION_OPTIONS_HXX - -#include "dmitigr/pgfe/connection_options.hpp" -#include "dmitigr/pgfe/connection_options.cxx" -#include "dmitigr/pgfe/errc.hpp" - -#include -#include - -#include -#include - -namespace dmitigr::pgfe::detail { - -inline namespace validators { - -template -bool is_non_negative(const T value) -{ - return (value >= 0); -} - -template -bool is_non_empty(const T& value) -{ - return !value.empty(); -} - -template -bool is_valid_port(const T value) -{ - return (value > 0 && value < 65536); -} - -inline bool is_ip_address(const std::string& value) -{ - return net::Ip_address::is_valid(value); -} - -inline bool is_hostname(const std::string& value) -{ - return net::is_hostname_valid(value); -} - -inline bool is_absolute_directory_name(const std::filesystem::path& value) -{ - return value.is_absolute(); -} - -inline void validate(const bool condition, const std::string& option_name) -{ - if (!condition) - throw std::logic_error{"invalid value of \"" + option_name + "\" connection option"}; -} - -} // namespace validators - -// ----------------------------------------------------------------------------- - -class iConnection_options : public Connection_options { -public: - iConnection_options() - : iConnection_options{btd::communication_mode} - {} - - /* - * It is better to provide an initializers in the members declarations, but - * due to the bug in Microsoft Visual Studio 15.7, all of them are here. - */ - explicit iConnection_options(const Communication_mode communication_mode) - : communication_mode_{communication_mode} -#ifndef _WIN32 - , uds_directory_{btd::uds_directory} - , uds_require_server_process_username_{btd::uds_require_server_process_username} -#endif - , tcp_keepalives_enabled_{btd::tcp_keepalives_enabled} - , tcp_keepalives_idle_{btd::tcp_keepalives_idle} - , tcp_keepalives_interval_{btd::tcp_keepalives_interval} - , tcp_keepalives_count_{btd::tcp_keepalives_count} - , net_address_{btd::net_address} - , net_hostname_{btd::net_hostname} - , port_{btd::port} - , username_{btd::username} - , database_{btd::database} - , password_{btd::password} - , kerberos_service_name_{btd::kerberos_service_name} - , is_ssl_enabled_{btd::ssl_enabled} - , ssl_compression_enabled_{btd::ssl_compression_enabled} - , ssl_certificate_file_{btd::ssl_certificate_file} - , ssl_private_key_file_{btd::ssl_private_key_file} - , ssl_certificate_authority_file_{btd::ssl_certificate_authority_file} - , ssl_certificate_revocation_list_file_{btd::ssl_certificate_revocation_list_file} - , ssl_server_hostname_verification_enabled_{btd::ssl_server_hostname_verification_enabled} - { - DMITIGR_ASSERT(is_invariant_ok()); - } - - std::unique_ptr make_connection() const override; // defined in connection.cpp - - std::unique_ptr to_connection_options() const override - { - return std::make_unique(*this); - } - - Connection_options* set(const Communication_mode value) override - { -#ifdef _WIN32 - DMITIGR_ASSERT(value == Communication_mode::net); -#endif - communication_mode_ = value; - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - Communication_mode communication_mode() const override - { - return communication_mode_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_port(const std::int_fast32_t value) override - { - validate(is_valid_port(value), "server port"); - port_ = value; - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - std::int_fast32_t port() const override - { - return port_; - } - - // --------------------------------------------------------------------------- - -#ifndef _WIN32 - Connection_options* set_uds_directory(std::filesystem::path value) override - { - DMITIGR_REQUIRE(communication_mode() == Communication_mode::uds, std::logic_error); - validate(is_absolute_directory_name(value), "UDS directory"); - uds_directory_ = std::move(value); - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - const std::filesystem::path& uds_directory() const override - { - return uds_directory_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_uds_require_server_process_username(std::optional value) override - { - DMITIGR_REQUIRE(communication_mode() == Communication_mode::uds, std::logic_error); - if (value) - validate(is_non_empty(*value), "UDS require server process username"); - uds_require_server_process_username_ = std::move(value); - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - const std::optional& uds_require_server_process_username() const override - { - return uds_require_server_process_username_; - } - -#endif - - // --------------------------------------------------------------------------- - - Connection_options* set_tcp_keepalives_enabled(const bool value) override - { - DMITIGR_REQUIRE(communication_mode() == Communication_mode::net, std::logic_error); - tcp_keepalives_enabled_ = value; - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - bool is_tcp_keepalives_enabled() const override - { - return tcp_keepalives_enabled_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_tcp_keepalives_idle(const std::optional value) override - { - DMITIGR_REQUIRE(communication_mode() == Communication_mode::net, std::logic_error); - if (value) - validate(is_non_negative(value->count()), "TCP keepalives idle"); - tcp_keepalives_idle_ = value; - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - std::optional tcp_keepalives_idle() const override - { - return tcp_keepalives_idle_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_tcp_keepalives_interval(const std::optional value) override - { - DMITIGR_REQUIRE(communication_mode() == Communication_mode::net, std::logic_error); - if (value) - validate(is_non_negative(value->count()), "TCP keepalives interval"); - tcp_keepalives_interval_ = value; - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - std::optional tcp_keepalives_interval() const override - { - return tcp_keepalives_interval_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_tcp_keepalives_count(const std::optional value) override - { - DMITIGR_REQUIRE(communication_mode() == Communication_mode::net, std::logic_error); - if (value) - validate(is_non_negative(*value), "TCP keepalives count"); - tcp_keepalives_count_ = value; - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - std::optional tcp_keepalives_count() const override - { - return tcp_keepalives_count_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_net_address(std::optional value) override - { - DMITIGR_REQUIRE(communication_mode() == Communication_mode::net, std::logic_error); - if (value) - validate(is_ip_address(*value), "Network address"); - else - DMITIGR_REQUIRE(net_hostname(), std::logic_error); - net_address_ = std::move(value); - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - const std::optional& net_address() const override - { - return net_address_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_net_hostname(std::optional value) override - { - DMITIGR_REQUIRE(communication_mode() == Communication_mode::net, std::logic_error); - if (value) - validate(is_hostname(*value), "Network host name"); - else - DMITIGR_REQUIRE(net_address(), std::logic_error); - net_hostname_ = std::move(value); - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - const std::optional& net_hostname() const override - { - return net_hostname_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_username(std::string value) override - { - validate(is_non_empty(value), "username"); - username_ = std::move(value); - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - const std::string& username() const override - { - return username_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_database(std::string value) override - { - validate(is_non_empty(value), "database"); - database_ = std::move(value); - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - const std::string& database() const override - { - return database_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_password(std::optional value) override - { - if (value) - validate(is_non_empty(*value), "password"); - password_ = std::move(value); - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - const std::optional& password() const override - { - return password_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_kerberos_service_name(std::optional value) override - { - if (value) - validate(is_non_empty(*value), "Kerberos service name"); - kerberos_service_name_ = std::move(value); - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - const std::optional& kerberos_service_name() const override - { - return kerberos_service_name_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_ssl_enabled(const bool value) override - { - is_ssl_enabled_ = value; - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - bool is_ssl_enabled() const override - { - return is_ssl_enabled_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_ssl_compression_enabled(const bool value) override - { - DMITIGR_REQUIRE(is_ssl_enabled(), std::logic_error); - ssl_compression_enabled_ = value; - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - bool is_ssl_compression_enabled() const override - { - return ssl_compression_enabled_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_ssl_certificate_file(std::optional value) override - { - DMITIGR_REQUIRE(is_ssl_enabled(), std::logic_error); - if (value) - validate(is_non_empty(*value), "SSL certificate file"); - ssl_certificate_file_ = std::move(value); - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - const std::optional& ssl_certificate_file() const override - { - return ssl_certificate_file_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_ssl_private_key_file(std::optional value) override - { - DMITIGR_REQUIRE(is_ssl_enabled(), std::logic_error); - if (value) - validate(is_non_empty(*value), "SSL private key file"); - ssl_private_key_file_ = std::move(value); - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - const std::optional& ssl_private_key_file() const override - { - return ssl_private_key_file_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_ssl_certificate_authority_file(std::optional value) override - { - DMITIGR_REQUIRE(is_ssl_enabled(), std::logic_error); - if (value) - validate(is_non_empty(*value), "SSL certificate authority file"); - ssl_certificate_authority_file_ = std::move(value); - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - const std::optional& ssl_certificate_authority_file() const override - { - return ssl_certificate_authority_file_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_ssl_certificate_revocation_list_file(std::optional value) override - { - DMITIGR_REQUIRE(is_ssl_enabled(), std::logic_error); - if (value) - validate(is_non_empty(*value), "SSL certificate revocation list file"); - ssl_certificate_revocation_list_file_ = std::move(value); - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - const std::optional& ssl_certificate_revocation_list_file() const override - { - return ssl_certificate_revocation_list_file_; - } - - // --------------------------------------------------------------------------- - - Connection_options* set_ssl_server_hostname_verification_enabled(const bool value) override - { - DMITIGR_REQUIRE(is_ssl_enabled() && ssl_certificate_authority_file(), std::logic_error); - ssl_server_hostname_verification_enabled_ = value; - DMITIGR_ASSERT(is_invariant_ok()); - return this; - } - - bool is_ssl_server_hostname_verification_enabled() const override - { - return ssl_server_hostname_verification_enabled_; - } - -private: - virtual bool is_invariant_ok() - { -#ifdef _WIN32 - const bool communication_mode_ok = (communication_mode_ == Communication_mode::net); - constexpr bool uds_ok = true; -#else - constexpr bool communication_mode_ok = true; - const bool uds_ok = !(communication_mode_ == Communication_mode::uds) || - (is_absolute_directory_name(uds_directory_) && - is_valid_port(port_) && - (!uds_require_server_process_username_ || !uds_require_server_process_username_->empty())); -#endif - const bool tcp_ok = !(communication_mode_ == Communication_mode::net) || - ((!tcp_keepalives_idle_ || is_non_negative(tcp_keepalives_idle_->count())) && - (!tcp_keepalives_interval_ || is_non_negative(tcp_keepalives_interval_->count())) && - (!tcp_keepalives_count_ || is_non_negative(tcp_keepalives_count_)) && - (net_address_ || net_hostname_) && - (!net_address_ || is_ip_address(*net_address_)) && - (!net_hostname_ || is_hostname(*net_hostname_)) && - is_valid_port(port_)); - const bool auth_ok = - !username_.empty() && - !database_.empty() && - (!password_ || !password_->empty()) && - (!kerberos_service_name_ || !kerberos_service_name_->empty()); - const bool ssl_ok = - (!ssl_certificate_file_ || !ssl_certificate_file_->empty()) && - (!ssl_private_key_file_ || !ssl_private_key_file_->empty()) && - (!ssl_certificate_authority_file_ || !ssl_certificate_authority_file_->empty()) && - (!ssl_certificate_revocation_list_file_ || !ssl_certificate_revocation_list_file_->empty()) && - (!ssl_server_hostname_verification_enabled_ || ssl_certificate_authority_file_); - - return communication_mode_ok && uds_ok && tcp_ok && auth_ok && ssl_ok; - } - - Communication_mode communication_mode_; -#ifndef _WIN32 - std::filesystem::path uds_directory_; - std::optional uds_require_server_process_username_; -#endif - bool tcp_keepalives_enabled_; - std::optional tcp_keepalives_idle_; - std::optional tcp_keepalives_interval_; - std::optional tcp_keepalives_count_; - std::optional net_address_; - std::optional net_hostname_; - std::int_fast32_t port_; - std::string username_; - std::string database_; - std::optional password_; - std::optional kerberos_service_name_; - bool is_ssl_enabled_; - bool ssl_compression_enabled_; - std::optional ssl_certificate_file_; - std::optional ssl_private_key_file_; - std::optional ssl_certificate_authority_file_; - std::optional ssl_certificate_revocation_list_file_; - bool ssl_server_hostname_verification_enabled_; -}; - -// ============================================================================= - -class pq_Connection_options { -public: - pq_Connection_options(const Connection_options* const o) - { - DMITIGR_ASSERT(o); - - switch (o->communication_mode()) { - case Communication_mode::net: { - constexpr auto z = std::chrono::seconds::zero(); - values_[host] = o->net_hostname().value_or(""); - values_[hostaddr] = o->net_address().value_or(""); - values_[port] = std::to_string(o->port()); - values_[keepalives] = std::to_string(o->is_tcp_keepalives_enabled()); - values_[keepalives_idle] = std::to_string(o->tcp_keepalives_idle().value_or(z).count()); - values_[keepalives_interval] = std::to_string(o->tcp_keepalives_interval().value_or(z).count()); - values_[keepalives_count] = std::to_string(o->tcp_keepalives_count().value_or(0)); - break; - } -#ifndef _WIN32 - case Communication_mode::uds: - values_[host] = o->uds_directory().generic_string(); - values_[port] = std::to_string(o->port()); - values_[requirepeer] = o->uds_require_server_process_username().value_or(""); - break; -#endif - } - - values_[dbname] = o->database(); - values_[user] = o->username(); - values_[password] = o->password().value_or(""); - - if (o->is_ssl_enabled()) { - if (o->is_ssl_server_hostname_verification_enabled()) { - values_[sslmode] = "verify-full"; - } else { - if (o->ssl_certificate_authority_file()) - values_[sslmode] = "verify-ca"; - else - values_[sslmode] = "require"; - } - - values_[sslcompression] = std::to_string(o->is_ssl_compression_enabled()); - values_[sslcert] = o->ssl_certificate_file().value_or(std::filesystem::path{}).generic_string(); - values_[sslkey] = o->ssl_private_key_file().value_or(std::filesystem::path{}).generic_string(); - values_[sslrootcert] = o->ssl_certificate_authority_file().value_or(std::filesystem::path{}).generic_string(); - values_[sslcrl] = o->ssl_certificate_revocation_list_file().value_or(std::filesystem::path{}).generic_string(); - } else { - values_[sslmode] = "disable"; - } - - values_[krbsrvname] = o->kerberos_service_name().value_or(""); - values_[gsslib] = ""; - - // ------------------------------------------------------------------------- - // Options that are unavailable from Pgfe API (at least for now) - // ------------------------------------------------------------------------- - - values_[passfile] = ""; - values_[connect_timeout] = ""; - values_[client_encoding] = "auto"; - values_[options] = ""; - values_[application_name] = ""; - values_[fallback_application_name] = ""; - values_[service] = ""; - values_[target_session_attrs] = "any"; - - update_cache(); - } - - pq_Connection_options(const pq_Connection_options& rhs) - { - for (decltype (+Keyword_count_) i = host; i < Keyword_count_; ++i) - values_[i] = rhs.values_[i]; - update_cache(); - } - - pq_Connection_options(pq_Connection_options&& rhs) - { - for (decltype (+Keyword_count_) i = host; i < Keyword_count_; ++i) - values_[i] = std::move(rhs.values_[i]); - update_cache(); - } - - pq_Connection_options& operator=(const pq_Connection_options& rhs) - { - if (this != &rhs) { - pq_Connection_options tmp(rhs); - swap(tmp); - } - return *this; - } - - pq_Connection_options& operator=(pq_Connection_options&& rhs) - { - if (this != &rhs) { - pq_Connection_options tmp(std::move(rhs)); - swap(tmp); - } - return *this; - } - - void swap(pq_Connection_options& rhs) - { - for (decltype (+Keyword_count_) i = host; i < Keyword_count_; ++i) { - std::swap(pq_keywords_[i], rhs.pq_keywords_[i]); - std::swap(pq_values_[i], rhs.pq_values_[i]); - std::swap(values_[i], rhs.values_[i]); - } - } - - const char* const* keywords() const - { - return pq_keywords_; - } - - const char* const* values() const - { - return pq_values_; - } - - static std::size_t count() - { - return Keyword_count_; - } - -private: - constexpr bool is_invariant_ok() const - { - constexpr auto keywords_count = sizeof(pq_keywords_) / sizeof(*pq_keywords_); - constexpr auto values_count = sizeof(values_) / sizeof(*values_); - static_assert(sizeof(pq_keywords_) == sizeof(pq_values_)); - static_assert(keywords_count == (1 + values_count)); - return true; - } - - enum Keyword : std::size_t { - host = 0, hostaddr, port, - dbname, user, password, - keepalives, keepalives_idle, keepalives_interval, keepalives_count, - sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, - requirepeer, - krbsrvname, - - // Options that are unavailable from Pgfe API (at least for now): - gsslib, passfile, connect_timeout, client_encoding, options, - application_name, fallback_application_name, service, target_session_attrs, - - // The last member is special - it denotes keyword count - Keyword_count_ - }; - - static const char* to_literal(const Keyword keyword) - { - switch (keyword) { - case host: return "host"; - case hostaddr: return "hostaddr"; - case port: return "port"; - case dbname: return "dbname"; - case user: return "user"; - case password: return "password"; - case keepalives: return "keepalives"; - case keepalives_idle: return "keepalives_idle"; - case keepalives_interval: return "keepalives_interval"; - case keepalives_count: return "keepalives_count"; - case sslmode: return "sslmode"; - case sslcompression: return "sslcompression"; - case sslcert: return "sslcert"; - case sslkey: return "sslkey"; - case sslrootcert: return "sslrootcert"; - case sslcrl: return "sslcrl"; - case krbsrvname: return "krbsrvname"; - case requirepeer: return "requirepeer"; - case passfile: return "passfile"; - case connect_timeout: return "connect_timeout"; - case client_encoding: return "client_encoding"; - case options: return "options"; - case application_name: return "application_name"; - case fallback_application_name: return "fallback_application_name"; - case gsslib: return "gsslib"; - case service: return "service"; - case target_session_attrs: return "target_session_attrs"; - case Keyword_count_:; - } - DMITIGR_ASSERT_ALWAYS(!true); - } - - void update_cache() - { - for (decltype (+Keyword_count_) i = host; i < Keyword_count_; ++i) { - pq_keywords_[i] = to_literal(Keyword(i)); - pq_values_[i] = values_[i].c_str(); - } - - pq_keywords_[Keyword_count_] = nullptr; - pq_values_[Keyword_count_] = nullptr; - - DMITIGR_ASSERT(is_invariant_ok()); - } - - const char* pq_keywords_[Keyword_count_ + 1]; - const char* pq_values_[Keyword_count_ + 1]; - std::string values_[Keyword_count_]; -}; - -} // namespace dmitigr::pgfe::detail - -#endif // DMITIGR_PGFE_CONNECTION_OPTIONS_HXX diff --git a/lib/dmitigr/pgfe/conversions.hpp b/lib/dmitigr/pgfe/conversions.hpp index bb16a52..92b8ac0 100644 --- a/lib/dmitigr/pgfe/conversions.hpp +++ b/lib/dmitigr/pgfe/conversions.hpp @@ -7,7 +7,460 @@ #include "dmitigr/pgfe/array_conversions.hpp" #include "dmitigr/pgfe/basic_conversions.hpp" -#include "dmitigr/pgfe/conversions.hxx" +#include "dmitigr/pgfe/basics.hpp" +#include "dmitigr/pgfe/data.hpp" +#include "dmitigr/pgfe/exceptions.hpp" +#include "dmitigr/pgfe/types_fwd.hpp" + +#include +#include +#include +#include +#include +#include + +namespace dmitigr::pgfe::detail { + +/** + * @internal + * + * @brief `T` to/from `std::string` conversions. + */ +template +struct Generic_string_conversions final { + using Type = T; + + template + static Type to_type(const std::string& text, Types&& ...) + { + Type result; + std::istringstream stream{text}; + stream >> result; + if (!stream.eof()) + throw std::runtime_error("invalid text representation"); + return result; + } + + template + static std::string to_string(const Type& value, Types&& ...) + { + std::ostringstream stream; + stream << value; + if (stream.fail()) + throw std::runtime_error("invalid native representation"); + return stream.str(); + } +}; + +/** + * @internal + * + * @brief `T` to/from Data conversions. + */ +template +struct Generic_data_conversions final { + using Type = T; + + template + static Type to_type(const Data* const data, Types&& ... args) + { + DMITIGR_REQUIRE(data, std::invalid_argument); + return StringConversions::to_type(std::string(data->bytes(), data->size()), std::forward(args)...); + } + + template + static Type to_type(std::unique_ptr&& data, Types&& ... args) + { + return to_type(data.get(), std::forward(args)...); + } + + template + static std::enable_if_t>, std::unique_ptr> to_data(U&& value, Types&& ... args) + { + return Data::make(StringConversions::to_string(std::forward(value), std::forward(args)...)); + } +}; + +// ----------------------------------------------------------------------------- +// Optimized numeric to/from std::string conversions +// ----------------------------------------------------------------------------- + +/** + * @internal + * + * @brief The common implementation of numeric to/from `std::string` conversions. + */ +template +struct Numeric_string_conversions_base { + using Type = T; + + template + static std::string to_string(Type value, Types&& ...) + { + return std::to_string(value); + } + +protected: + template + static Type to_numeric__(const std::string& text, Converter converter) + { + Type result; + std::size_t idx; + result = converter(text, &idx); + if (idx != text.size()) + throw std::runtime_error{"the input string contains symbols not convertible to numeric"}; + + return result; + } +}; + +// ----------------------------------------------------------------------------- + +/** + * @internal + * + * @brief The implementation of `short int` to/from `std::string` conversions. + */ +template<> +struct Numeric_string_conversions final + : private Numeric_string_conversions_base { + using Type = short int; + using Numeric_string_conversions_base::to_string; + + template + static Type to_type(const std::string& text, Types&& ...) + { + const int result = to_numeric__(text, [](auto text, auto* idx) { return std::stoi(text, idx); }); + constexpr auto max = std::numeric_limits::max(); + if (result > max) + throw std::runtime_error("numeric value " + text + " > " + std::to_string(max)); + return Type(result); + } +}; + +/** + * @internal + * + * @brief The implementation of `int` to/from `std::string` conversions. + */ +template<> +struct Numeric_string_conversions final + : private Numeric_string_conversions_base { + using Type = int; + using Numeric_string_conversions_base::to_string; + + template + static Type to_type(const std::string& text, Types&& ...) + { + return to_numeric__(text, [](auto text, auto* idx) { return std::stoi(text, idx); }); + } +}; + +/** + * @internal + * + * @brief The implementation of `long int` to/from `std::string` conversions. + */ +template<> +struct Numeric_string_conversions final + : private Numeric_string_conversions_base { + using Type = long int; + using Numeric_string_conversions_base::to_string; + + template + static Type to_type(const std::string& text, Types&& ...) + { + return to_numeric__(text, [](auto text, auto* idx) { return std::stol(text, idx); }); + } +}; + +/** + * @internal + * + * @brief The implementation of `long long int` to/from `std::string` conversions. + */ +template<> +struct Numeric_string_conversions final + : private Numeric_string_conversions_base { + using Type = long long int; + using Numeric_string_conversions_base::to_string; + + template + static Type to_type(const std::string& text, Types&& ...) + { + return to_numeric__(text, [](auto text, auto* idx) { return std::stoll(text, idx); }); + } +}; + +/** + * @internal + * + * @brief The implementation of `float` to/from `std::string` conversions. + */ +template<> +struct Numeric_string_conversions final + : private Numeric_string_conversions_base { + using Type = float; + using Numeric_string_conversions_base::to_string; + + template + static Type to_type(const std::string& text, Types&& ...) + { + return to_numeric__(text, [](auto text, auto* idx) { return std::stof(text, idx); }); + } +}; + +/** + * @internal + * + * @brief The implementation of `double` to/from `std::string` conversions. + */ +template<> +struct Numeric_string_conversions final + : private Numeric_string_conversions_base { + using Type = double; + using Numeric_string_conversions_base::to_string; + + template + static Type to_type(const std::string& text, Types&& ...) + { + return to_numeric__(text, [](auto text, auto* idx) { return std::stod(text, idx); }); + } +}; + +/** + * @internal + * + * @brief The implementation of `long double` to/from `std::string` conversions. + */ +template<> +struct Numeric_string_conversions final + : private Numeric_string_conversions_base { + using Type = long double; + using Numeric_string_conversions_base::to_string; + + template + static Type to_type(const std::string& text, Types&& ...) + { + return to_numeric__(text, [](auto text, auto* idx) { return std::stold(text, idx); }); + } +}; + +// ----------------------------------------------------------------------------- +// Optimized numeric to/from Data conversions +// ----------------------------------------------------------------------------- + +/** + * @internal + * + * @brief The common implementation of numeric to/from Data conversions. + */ +template +struct Numeric_data_conversions final { + using Type = T; + + template + static Type to_type(const Data* const data, Types&& ... args) + { + DMITIGR_REQUIRE(data, std::invalid_argument); + if (data->format() == Data_format::binary) { + const auto data_size = data->size(); + DMITIGR_REQUIRE(data_size <= sizeof(Type), std::invalid_argument); + Type result{}; + const auto data_ubytes = reinterpret_cast(data->bytes()); + const auto result_ubytes = reinterpret_cast(&result); + using Counter = std::remove_const_t; + + static const auto endianness = endianness__(); + switch (endianness) { + case Endianness::big: + for (Counter i = 0; i < data_size; ++i) + result_ubytes[sizeof(Type) - data_size + i] = data_ubytes[i]; + break; + case Endianness::little: + for (Counter i = 0; i < data_size; ++i) + result_ubytes[sizeof(Type) - 1 - i] = data_ubytes[i]; + break; + case Endianness::unknown: + throw std::logic_error("unknown endianness"); + } + return result; + } else + return Generic_data_conversions::to_type(data, std::forward(args)...); + } + + template + static Type to_type(std::unique_ptr&& data, Types&& ... args) + { + return to_type(data.get(), std::forward(args)...); + } + + template + static std::unique_ptr to_data(Type value, Types&& ... args) + { + return Generic_data_conversions::to_data(value, std::forward(args)...); + } + +private: + enum class Endianness { + unknown = 0, + big, + little + }; + + static Endianness endianness__() + { + if constexpr (sizeof(unsigned char) < sizeof(unsigned long)) { + constexpr unsigned long number = 0x01; + return (reinterpret_cast(&number)[0] == 1) ? Endianness::little : Endianness::big; + } else + return Endianness::unknown; + } +}; + +// ----------------------------------------------------------------------------- +// std::string conversions +// ----------------------------------------------------------------------------- + +/** + * @internal + * + * @brief The implementation of `std::string` to/from `std::string` conversions. + */ +struct Std_string_conversions final { + using Type = std::string; + + template + static std::enable_if_t>, Type> to_type(String&& text, Types&& ...) + { + return std::forward(text); + } + + template + static std::enable_if_t>, std::string> to_string(String&& value, Types&& ...) + { + return std::forward(value); + } +}; + +// ----------------------------------------------------------------------------- +// char conversions +// ----------------------------------------------------------------------------- + +struct Char_string_conversions final { + using Type = char; + + template + static Type to_type(const std::string& text, Types&& ...) + { + DMITIGR_REQUIRE(text.size() == 1, std::invalid_argument); + return text[0]; + } + + template + static std::string to_string(Type value, Types&& ...) + { + return std::string{value}; + } +}; + +struct Char_data_conversions final { + using Type = char; + + template + static Type to_type(const Data* const data, Types&& ...) + { + DMITIGR_REQUIRE(data && (data->size() == 1), std::invalid_argument); + return data->bytes()[0]; + } + + template + static Type to_type(std::unique_ptr&& data, Types&& ...) + { + return to_type(data.get()); + } + + template + static std::unique_ptr to_data(Type value, Types&& ...) + { + return Data::make(Char_string_conversions::to_string(value)); + } +}; + +// ----------------------------------------------------------------------------- +// bool conversions +// ----------------------------------------------------------------------------- + +struct Bool_string_conversions final { + using Type = char; + + template + static Type to_type(const std::string& text, Types&& ...) + { + return to_type__(text.c_str(), text.size()); + } + + template + static std::string to_string(Type value, Types&& ...) + { + return value ? "t" : "f"; + } + +private: + friend struct Bool_data_conversions; + + static Type to_type__(const char* const text, const std::size_t size) + { + DMITIGR_ASSERT(text); + if (std::strncmp(text, "t", size) == 0 || + std::strncmp(text, "true", size) == 0 || + std::strncmp(text, "TRUE", size) == 0 || + std::strncmp(text, "y", size) == 0 || + std::strncmp(text, "yes", size) == 0 || + std::strncmp(text, "on", size) == 0 || + std::strncmp(text, "1", size) == 0) + return true; + else if (std::strncmp(text, "f", size) == 0 || + std::strncmp(text, "false", size) == 0 || + std::strncmp(text, "FALSE", size) == 0 || + std::strncmp(text, "n", size) == 0 || + std::strncmp(text, "no", size) == 0 || + std::strncmp(text, "off", size) == 0 || + std::strncmp(text, "0", size) == 0) + return false; + else + throw std::runtime_error("invalid text bool representation"); + } +}; + +struct Bool_data_conversions final { + using Type = char; + + template + static Type to_type(const Data* const data, Types&& ...) + { + DMITIGR_REQUIRE(data, std::invalid_argument); + if (data->format() == Data_format::binary) { + DMITIGR_REQUIRE(data->size() == 1, std::invalid_argument); + return data->bytes()[0]; + } else + return Bool_string_conversions::to_type__(data->bytes(), data->size()); + } + + template + static Type to_type(std::unique_ptr&& data, Types&& ...) + { + return to_type(data.get()); + } + + template + static std::unique_ptr to_data(Type value, Types&& ...) + { + return Data::make(Bool_string_conversions::to_string(value)); + } +}; + +} // namespace dmitigr::pgfe::detail namespace dmitigr::pgfe { @@ -30,7 +483,7 @@ struct Numeric_conversions : public Basic_conversions struct Conversions : public Basic_conversions, +template struct Conversions final : public Basic_conversions, detail::Generic_data_conversions> {}; /** @@ -43,7 +496,7 @@ template struct Conversions : public Basic_conversions struct Conversions : public Basic_conversions struct Conversions final : public Basic_conversions> {}; /** @@ -51,49 +504,49 @@ template<> struct Conversions : public Basic_conversions struct Conversions : public Numeric_conversions {}; +template<> struct Conversions final : public Numeric_conversions {}; /** * @ingroup conversions * * @brief Full specialization of Conversions for `int`. */ -template<> struct Conversions : public Numeric_conversions {}; +template<> struct Conversions final : public Numeric_conversions {}; /** * @ingroup conversions * * @brief Full specialization of Conversions for `long int`. */ -template<> struct Conversions : public Numeric_conversions {}; +template<> struct Conversions final : public Numeric_conversions {}; /** * @ingroup conversions * * @brief Full specialization of Conversions for `long long int`. */ -template<> struct Conversions : public Numeric_conversions {}; +template<> struct Conversions final : public Numeric_conversions {}; /** * @ingroup conversions * * @brief Full specialization of Conversions for `float`. */ -template<> struct Conversions : public Numeric_conversions {}; +template<> struct Conversions final : public Numeric_conversions {}; /** * @ingroup conversions * * @brief Full specialization of Conversions for `double`. */ -template<> struct Conversions : public Numeric_conversions {}; +template<> struct Conversions final : public Numeric_conversions {}; /** * @ingroup conversions * * @brief Full specialization of Conversions for `long double`. */ -template<> struct Conversions : public Numeric_conversions {}; +template<> struct Conversions final : public Numeric_conversions {}; /** * @ingroup conversions @@ -107,7 +560,7 @@ template<> struct Conversions : public Numeric_conversions struct Conversions : public Basic_conversions struct Conversions final : public Basic_conversions {}; /** @@ -122,7 +575,7 @@ template<> struct Conversions : public Basic_conversions struct Conversions : public Basic_conversions struct Conversions final : public Basic_conversions {}; } // namespace dmitigr::pgfe diff --git a/lib/dmitigr/pgfe/conversions.hxx b/lib/dmitigr/pgfe/conversions.hxx deleted file mode 100644 index d2d6177..0000000 --- a/lib/dmitigr/pgfe/conversions.hxx +++ /dev/null @@ -1,463 +0,0 @@ -// -*- C++ -*- -// Copyright (C) Dmitry Igrishin -// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp - -#ifndef DMITIGR_PGFE_CONVERSIONS_HXX -#define DMITIGR_PGFE_CONVERSIONS_HXX - -#include "dmitigr/pgfe/basics.hpp" -#include "dmitigr/pgfe/data.hpp" -#include "dmitigr/pgfe/exceptions.hxx" -#include "dmitigr/pgfe/types_fwd.hpp" - -#include -#include -#include -#include -#include -#include - -namespace dmitigr::pgfe::detail { - -/** - * @internal - * - * @brief `T` to/from `std::string` conversions. - */ -template -struct Generic_string_conversions { - using Type = T; - - template - static Type to_type(const std::string& text, Types&& ...) - { - Type result; - std::istringstream stream{text}; - stream >> result; - if (!stream.eof()) - throw std::runtime_error("invalid text representation"); - return result; - } - - template - static std::string to_string(const Type& value, Types&& ...) - { - std::ostringstream stream; - stream << value; - if (stream.fail()) - throw std::runtime_error("invalid native representation"); - return stream.str(); - } -}; - -/** - * @internal - * - * @brief `T` to/from Data conversions. - */ -template -struct Generic_data_conversions { - using Type = T; - - template - static Type to_type(const Data* const data, Types&& ... args) - { - DMITIGR_REQUIRE(data, std::invalid_argument); - return StringConversions::to_type(std::string(data->bytes(), data->size()), std::forward(args)...); - } - - template - static Type to_type(std::unique_ptr&& data, Types&& ... args) - { - return to_type(data.get(), std::forward(args)...); - } - - template - static std::enable_if_t>, std::unique_ptr> to_data(U&& value, Types&& ... args) - { - return Data::make(StringConversions::to_string(std::forward(value), std::forward(args)...)); - } -}; - -// ----------------------------------------------------------------------------- -// Optimized numeric to/from std::string conversions -// ----------------------------------------------------------------------------- - -/** - * @internal - * - * @brief The common implementation of numeric to/from `std::string` conversions. - */ -template -struct Numeric_string_conversions_base { - using Type = T; - - template - static std::string to_string(Type value, Types&& ...) - { - return std::to_string(value); - } - -protected: - template - static Type to_numeric__(const std::string& text, Converter converter) - { - Type result; - std::size_t idx; - result = converter(text, &idx); - if (idx != text.size()) - throw std::runtime_error{"the input string contains symbols not convertible to numeric"}; - - return result; - } -}; - -// ----------------------------------------------------------------------------- - -/** - * @internal - * - * @brief The implementation of `short int` to/from `std::string` conversions. - */ -template<> -struct Numeric_string_conversions - : private Numeric_string_conversions_base { - using Type = short int; - using Numeric_string_conversions_base::to_string; - - template - static Type to_type(const std::string& text, Types&& ...) - { - const int result = to_numeric__(text, [](auto text, auto* idx) { return std::stoi(text, idx); }); - constexpr auto max = std::numeric_limits::max(); - if (result > max) - throw std::runtime_error("numeric value " + text + " > " + std::to_string(max)); - return Type(result); - } -}; - -/** - * @internal - * - * @brief The implementation of `int` to/from `std::string` conversions. - */ -template<> -struct Numeric_string_conversions - : private Numeric_string_conversions_base { - using Type = int; - using Numeric_string_conversions_base::to_string; - - template - static Type to_type(const std::string& text, Types&& ...) - { - return to_numeric__(text, [](auto text, auto* idx) { return std::stoi(text, idx); }); - } -}; - -/** - * @internal - * - * @brief The implementation of `long int` to/from `std::string` conversions. - */ -template<> -struct Numeric_string_conversions - : private Numeric_string_conversions_base { - using Type = long int; - using Numeric_string_conversions_base::to_string; - - template - static Type to_type(const std::string& text, Types&& ...) - { - return to_numeric__(text, [](auto text, auto* idx) { return std::stol(text, idx); }); - } -}; - -/** - * @internal - * - * @brief The implementation of `long long int` to/from `std::string` conversions. - */ -template<> -struct Numeric_string_conversions - : private Numeric_string_conversions_base { - using Type = long long int; - using Numeric_string_conversions_base::to_string; - - template - static Type to_type(const std::string& text, Types&& ...) - { - return to_numeric__(text, [](auto text, auto* idx) { return std::stoll(text, idx); }); - } -}; - -/** - * @internal - * - * @brief The implementation of `float` to/from `std::string` conversions. - */ -template<> -struct Numeric_string_conversions - : private Numeric_string_conversions_base { - using Type = float; - using Numeric_string_conversions_base::to_string; - - template - static Type to_type(const std::string& text, Types&& ...) - { - return to_numeric__(text, [](auto text, auto* idx) { return std::stof(text, idx); }); - } -}; - -/** - * @internal - * - * @brief The implementation of `double` to/from `std::string` conversions. - */ -template<> -struct Numeric_string_conversions - : private Numeric_string_conversions_base { - using Type = double; - using Numeric_string_conversions_base::to_string; - - template - static Type to_type(const std::string& text, Types&& ...) - { - return to_numeric__(text, [](auto text, auto* idx) { return std::stod(text, idx); }); - } -}; - -/** - * @internal - * - * @brief The implementation of `long double` to/from `std::string` conversions. - */ -template<> -struct Numeric_string_conversions - : private Numeric_string_conversions_base { - using Type = long double; - using Numeric_string_conversions_base::to_string; - - template - static Type to_type(const std::string& text, Types&& ...) - { - return to_numeric__(text, [](auto text, auto* idx) { return std::stold(text, idx); }); - } -}; - -// ----------------------------------------------------------------------------- -// Optimized numeric to/from Data conversions -// ----------------------------------------------------------------------------- - -/** - * @internal - * - * @brief The common implementation of numeric to/from Data conversions. - */ -template -struct Numeric_data_conversions { - using Type = T; - - template - static Type to_type(const Data* const data, Types&& ... args) - { - DMITIGR_REQUIRE(data, std::invalid_argument); - if (data->format() == Data_format::binary) { - const auto data_size = data->size(); - DMITIGR_REQUIRE(data_size <= sizeof(Type), std::invalid_argument); - Type result{}; - const auto data_ubytes = reinterpret_cast(data->bytes()); - const auto result_ubytes = reinterpret_cast(&result); - using Counter = std::remove_const_t; - - static const auto endianness = endianness__(); - switch (endianness) { - case Endianness::big: - for (Counter i = 0; i < data_size; ++i) - result_ubytes[sizeof(Type) - data_size + i] = data_ubytes[i]; - break; - case Endianness::little: - for (Counter i = 0; i < data_size; ++i) - result_ubytes[sizeof(Type) - 1 - i] = data_ubytes[i]; - break; - case Endianness::unknown: - throw std::logic_error("unknown endianness"); - } - return result; - } else - return Generic_data_conversions::to_type(data, std::forward(args)...); - } - - template - static Type to_type(std::unique_ptr&& data, Types&& ... args) - { - return to_type(data.get(), std::forward(args)...); - } - - template - static std::unique_ptr to_data(Type value, Types&& ... args) - { - return Generic_data_conversions::to_data(value, std::forward(args)...); - } - -private: - enum class Endianness { - unknown = 0, - big, - little - }; - - static Endianness endianness__() - { - if constexpr (sizeof(unsigned char) < sizeof(unsigned long)) { - constexpr unsigned long number = 0x01; - return (reinterpret_cast(&number)[0] == 1) ? Endianness::little : Endianness::big; - } else - return Endianness::unknown; - } -}; - -// ----------------------------------------------------------------------------- -// std::string conversions -// ----------------------------------------------------------------------------- - -/** - * @internal - * - * @brief The implementation of `std::string` to/from `std::string` conversions. - */ -struct Std_string_conversions { - using Type = std::string; - - template - static std::enable_if_t>, Type> to_type(String&& text, Types&& ...) - { - return std::forward(text); - } - - template - static std::enable_if_t>, std::string> to_string(String&& value, Types&& ...) - { - return std::forward(value); - } -}; - -// ----------------------------------------------------------------------------- -// char conversions -// ----------------------------------------------------------------------------- - -struct Char_string_conversions { - using Type = char; - - template - static Type to_type(const std::string& text, Types&& ...) - { - DMITIGR_REQUIRE(text.size() == 1, std::invalid_argument); - return text[0]; - } - - template - static std::string to_string(Type value, Types&& ...) - { - return std::string{value}; - } -}; - -struct Char_data_conversions { - using Type = char; - - template - static Type to_type(const Data* const data, Types&& ...) - { - DMITIGR_REQUIRE(data && (data->size() == 1), std::invalid_argument); - return data->bytes()[0]; - } - - template - static Type to_type(std::unique_ptr&& data, Types&& ...) - { - return to_type(data.get()); - } - - template - static std::unique_ptr to_data(Type value, Types&& ...) - { - return Data::make(Char_string_conversions::to_string(value)); - } -}; - -// ----------------------------------------------------------------------------- -// bool conversions -// ----------------------------------------------------------------------------- - -struct Bool_string_conversions { - using Type = char; - - template - static Type to_type(const std::string& text, Types&& ...) - { - return to_type__(text.c_str(), text.size()); - } - - template - static std::string to_string(Type value, Types&& ...) - { - return value ? "t" : "f"; - } - -private: - friend struct Bool_data_conversions; - - static Type to_type__(const char* const text, const std::size_t size) - { - DMITIGR_ASSERT(text); - if (std::strncmp(text, "t", size) == 0 || - std::strncmp(text, "true", size) == 0 || - std::strncmp(text, "TRUE", size) == 0 || - std::strncmp(text, "y", size) == 0 || - std::strncmp(text, "yes", size) == 0 || - std::strncmp(text, "on", size) == 0 || - std::strncmp(text, "1", size) == 0) - return true; - else if (std::strncmp(text, "f", size) == 0 || - std::strncmp(text, "false", size) == 0 || - std::strncmp(text, "FALSE", size) == 0 || - std::strncmp(text, "n", size) == 0 || - std::strncmp(text, "no", size) == 0 || - std::strncmp(text, "off", size) == 0 || - std::strncmp(text, "0", size) == 0) - return false; - else - throw std::runtime_error("invalid text bool representation"); - } -}; - -struct Bool_data_conversions { - using Type = char; - - template - static Type to_type(const Data* const data, Types&& ...) - { - DMITIGR_REQUIRE(data, std::invalid_argument); - if (data->format() == Data_format::binary) { - DMITIGR_REQUIRE(data->size() == 1, std::invalid_argument); - return data->bytes()[0]; - } else - return Bool_string_conversions::to_type__(data->bytes(), data->size()); - } - - template - static Type to_type(std::unique_ptr&& data, Types&& ...) - { - return to_type(data.get()); - } - - template - static std::unique_ptr to_data(Type value, Types&& ...) - { - return Data::make(Bool_string_conversions::to_string(value)); - } -}; - -} // namespace dmitigr::pgfe::detail - -#endif // DMITIGR_PGFE_CONVERSIONS_HXX diff --git a/lib/dmitigr/pgfe/data.cpp b/lib/dmitigr/pgfe/data.cpp index f69fa4b..8684b26 100644 --- a/lib/dmitigr/pgfe/data.cpp +++ b/lib/dmitigr/pgfe/data.cpp @@ -2,17 +2,343 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#include "dmitigr/pgfe/data.hxx" -#include "dmitigr/pgfe/pq.hxx" +#include "dmitigr/pgfe/data.hpp" +#include "dmitigr/pgfe/pq.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" +#include + +#include #include #include -namespace pgfe = dmitigr::pgfe; +namespace dmitigr::pgfe::detail { + +class iData : public Data { +protected: + using Format = Data_format; + + virtual bool is_invariant_ok() = 0; +}; + +inline bool iData::is_invariant_ok() +{ + const bool size_ok = ((size() == 0) == is_empty()); + const bool empty_ok = !is_empty() || ((size() == 0) && !std::strcmp(bytes(), "")); + const bool bytes_ok = bytes() && (format() == Data_format::binary || bytes()[size()] == '\0'); + const bool memory_ok = !memory() || (memory() == bytes()); + return size_ok && empty_ok && bytes_ok && memory_ok; +} + +// ----------------------------------------------------------------------------- + +class container_Data_base : public iData { +protected: + bool is_invariant_ok() override + { + const bool idata_ok = iData::is_invariant_ok(); + return idata_ok; + } +}; + +template +class container_Data : public container_Data_base { +public: + using Storage = Container; + + Format format() const noexcept override + { + return format_; + } + + bool is_empty() const noexcept override + { + return storage_.empty(); + } + + const char* bytes() const noexcept override + { + return reinterpret_cast(storage_.data()); + } + +protected: + template + container_Data(S&& storage, const Format format) + : format_(format) + , storage_(std::forward(storage)) + {} + + const Format format_{Format::text}; + Storage storage_; +}; + +// ----------------------------------------------------------------------------- + +class string_Data final : public container_Data { +public: + string_Data(std::string storage, const Format format) + : container_Data(std::move(storage), format) + { + DMITIGR_ASSERT(is_invariant_ok()); + } + + std::unique_ptr to_data() const override + { + return std::make_unique(storage_, format_); + } + + std::size_t size() const noexcept override + { + return storage_.size(); + } + + void* memory() noexcept override + { + return storage_.data(); + } + +protected: + bool is_invariant_ok() override + { + const bool memory_ok = memory(); + const bool container_data_ok = container_Data::is_invariant_ok(); + return memory_ok && container_data_ok; + } +}; + +// ----------------------------------------------------------------------------- + +class vector_Data final : public container_Data> { +public: + template + vector_Data(S&& storage, const Format format) + : container_Data(std::forward(storage), format) + { + DMITIGR_ASSERT(is_invariant_ok()); + } + + vector_Data(const unsigned char* const bytes, const std::size_t size, const Format format) + : container_Data(std::vector((format == Data_format::binary) ? size : size + 1), format) + { + std::copy(bytes, bytes + size, begin(storage_)); + DMITIGR_ASSERT(is_invariant_ok()); + } + + std::unique_ptr to_data() const override + { + return std::make_unique(storage_, format_); + } + + std::size_t size() const noexcept override + { + return (format_ == Data_format::binary) ? storage_.size() : storage_.size() - 1; + } + + void* memory() noexcept override + { + return storage_.data(); + } + +protected: + bool is_invariant_ok() override + { + const bool memory_ok = memory(); + const bool container_data_ok = container_Data::is_invariant_ok(); + return memory_ok && container_data_ok; + } +}; + +// ----------------------------------------------------------------------------- + +class memory_Data_base : public iData { +protected: + bool is_invariant_ok() override + { + const bool memory_ok = memory(); + const bool idata_ok = iData::is_invariant_ok(); + return memory_ok && idata_ok; + } +}; + +template> +class memory_Data final : public memory_Data_base { +public: + using Storage = std::unique_ptr; + + memory_Data(Storage&& storage, const std::size_t size, const Format format) + : format_(format) + , size_(size) + , storage_(std::move(storage)) + { + DMITIGR_ASSERT(is_invariant_ok()); + } + + std::unique_ptr to_data() const override + { + return std::make_unique(static_cast(storage_.get()), size_, format_); + } + + Format format() const noexcept override + { + return format_; + } + + std::size_t size() const noexcept override + { + return size_; + } + + bool is_empty() const noexcept override + { + return (size() == 0); + } + + const char* bytes() const noexcept override + { + return static_cast(storage_.get()); + } + + void* memory() noexcept override + { + return storage_.get(); + } + +private: + const Format format_{Format::text}; + std::size_t size_{}; + std::unique_ptr storage_; +}; + +// ----------------------------------------------------------------------------- + +using array_memory_Data = memory_Data; +using custom_memory_Data = memory_Data; + +// ----------------------------------------------------------------------------- + +class empty_Data final : public iData { +public: + explicit empty_Data(const Format format) + : format_(format) + { + DMITIGR_ASSERT(is_invariant_ok()); + } + + std::unique_ptr to_data() const override + { + return std::make_unique(format_); + } + + Format format() const noexcept override + { + return format_; + } + + std::size_t size() const noexcept override + { + return 0; + } + + bool is_empty() const noexcept override + { + return true; + } + + const char* bytes() const noexcept override + { + return ""; + } + + void* memory() noexcept override + { + return nullptr; + } + +private: + bool is_invariant_ok() override + { + const bool empty_ok = is_empty(); + const bool memory_ok = !memory(); + const bool idata_ok = iData::is_invariant_ok(); + return empty_ok && memory_ok && idata_ok; + } + + const Format format_{Format::text}; +}; + +// ----------------------------------------------------------------------------- + +class Data_view final : public iData { +public: + Data_view() = default; + + Data_view(const char* const bytes, const std::size_t size, const Format format) + : format_(format) + , size_(size) + , bytes_(bytes) + { + DMITIGR_ASSERT(is_invariant_ok()); + } + + // Non copyable. + Data_view(const Data_view&) = delete; + Data_view& operator=(const Data_view&) = delete; + + // Movable. + Data_view(Data_view&&) = default; + Data_view& operator=(Data_view&&) = default; + + std::unique_ptr to_data() const override + { + return std::make_unique(reinterpret_cast(bytes_), size_, format_); + } + + Format format() const noexcept override + { + return format_; + } + + std::size_t size() const noexcept override + { + return size_; + } + + bool is_empty() const noexcept override + { + return (size() == 0); + } + + const char* bytes() const noexcept override + { + return bytes_; + } + + void* memory() noexcept override + { + return nullptr; + } + +protected: + bool is_invariant_ok() override + { + const bool memory_ok = !memory(); + const bool idata_ok = iData::is_invariant_ok(); + return memory_ok && idata_ok; + } + +private: + Format format_{Format::text}; + std::size_t size_{}; + const char* bytes_{}; // No ownership +}; + +} // namespace dmitigr::pgfe::detail + +// ============================================================================= namespace dmitigr::pgfe { -DMITIGR_PGFE_API std::unique_ptr +DMITIGR_PGFE_INLINE std::unique_ptr Data::make(const char* const bytes, const std::size_t size, const Data_format format) { DMITIGR_REQUIRE(bytes && (format == Data_format::binary || bytes[size] == '\0'), std::invalid_argument); @@ -22,13 +348,13 @@ Data::make(const char* const bytes, const std::size_t size, const Data_format fo return std::make_unique(format); } -DMITIGR_PGFE_API std::unique_ptr +DMITIGR_PGFE_INLINE std::unique_ptr Data::make(const char* const bytes) { return make(bytes, std::strlen(bytes), Data_format::text); } -DMITIGR_PGFE_API std::unique_ptr +DMITIGR_PGFE_INLINE std::unique_ptr Data::make(std::unique_ptr&& storage, const std::size_t size, const Data_format format) { DMITIGR_REQUIRE(storage && @@ -36,21 +362,19 @@ Data::make(std::unique_ptr&& storage, const std::size_t si return std::make_unique(std::move(storage), size, format); } -DMITIGR_PGFE_API std::unique_ptr +DMITIGR_PGFE_INLINE std::unique_ptr Data::make(std::string storage, const Data_format format) { return std::make_unique(std::move(storage), format); } -DMITIGR_PGFE_API std::unique_ptr +DMITIGR_PGFE_INLINE std::unique_ptr Data::make(std::vector storage, const Data_format format) { DMITIGR_REQUIRE(format == Data_format::binary || (!storage.empty() && storage.back() == '\0'), std::invalid_argument); return std::make_unique(std::move(storage), format); } -} // namespace dmitigr::pgfe - namespace { std::unique_ptr to_binary_data__(const char* const text) @@ -67,15 +391,17 @@ std::unique_ptr to_binary_data__(const char* const text) } // namespace -DMITIGR_PGFE_API auto -pgfe::to_binary_data(const Data* const text_data) -> std::unique_ptr +DMITIGR_PGFE_INLINE std::unique_ptr to_binary_data(const Data* const text_data) { DMITIGR_REQUIRE(text_data && text_data->format() == Data_format::text, std::invalid_argument); return to_binary_data__(text_data->bytes()); } -DMITIGR_PGFE_API auto -pgfe::to_binary_data(const std::string& text_data) -> std::unique_ptr +DMITIGR_PGFE_INLINE std::unique_ptr to_binary_data(const std::string& text_data) { return to_binary_data__(text_data.c_str()); } + +} // namespace dmitigr::pgfe + +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/data.hpp b/lib/dmitigr/pgfe/data.hpp index ece3348..7b9c8cf 100644 --- a/lib/dmitigr/pgfe/data.hpp +++ b/lib/dmitigr/pgfe/data.hpp @@ -165,4 +165,8 @@ DMITIGR_PGFE_API std::unique_ptr to_binary_data(const std::string& text_da } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/data.cpp" +#endif + #endif // DMITIGR_PGFE_DATA_HPP diff --git a/lib/dmitigr/pgfe/data.hxx b/lib/dmitigr/pgfe/data.hxx deleted file mode 100644 index 822e581..0000000 --- a/lib/dmitigr/pgfe/data.hxx +++ /dev/null @@ -1,337 +0,0 @@ -// -*- C++ -*- -// Copyright (C) Dmitry Igrishin -// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp - -#ifndef DMITIGR_PGFE_DATA_HXX -#define DMITIGR_PGFE_DATA_HXX - -#include "dmitigr/pgfe/data.hpp" - -#include - -#include -#include - -namespace dmitigr::pgfe::detail { - -class iData : public Data { -protected: - using Format = Data_format; - - virtual bool is_invariant_ok() = 0; -}; - -inline bool iData::is_invariant_ok() -{ - const bool size_ok = ((size() == 0) == is_empty()); - const bool empty_ok = !is_empty() || ((size() == 0) && !std::strcmp(bytes(), "")); - const bool bytes_ok = bytes() && (format() == Data_format::binary || bytes()[size()] == '\0'); - const bool memory_ok = !memory() || (memory() == bytes()); - return size_ok && empty_ok && bytes_ok && memory_ok; -} - -// ----------------------------------------------------------------------------- - -class container_Data_base : public iData { -protected: - bool is_invariant_ok() override - { - const bool idata_ok = iData::is_invariant_ok(); - return idata_ok; - } -}; - -template -class container_Data : public container_Data_base { -public: - using Storage = Container; - - Format format() const noexcept override - { - return format_; - } - - bool is_empty() const noexcept override - { - return storage_.empty(); - } - - const char* bytes() const noexcept override - { - return reinterpret_cast(storage_.data()); - } - -protected: - template - container_Data(S&& storage, const Format format) - : format_(format) - , storage_(std::forward(storage)) - {} - - const Format format_{Format::text}; - Storage storage_; -}; - -// ----------------------------------------------------------------------------- - -class string_Data : public container_Data { -public: - string_Data(std::string storage, const Format format) - : container_Data(std::move(storage), format) - { - DMITIGR_ASSERT(is_invariant_ok()); - } - - std::unique_ptr to_data() const override - { - return std::make_unique(storage_, format_); - } - - std::size_t size() const noexcept override - { - return storage_.size(); - } - - void* memory() noexcept override - { - return storage_.data(); - } - -protected: - bool is_invariant_ok() override - { - const bool memory_ok = memory(); - const bool container_data_ok = container_Data::is_invariant_ok(); - return memory_ok && container_data_ok; - } -}; - -// ----------------------------------------------------------------------------- - -class vector_Data : public container_Data> { -public: - template - vector_Data(S&& storage, const Format format) - : container_Data(std::forward(storage), format) - { - DMITIGR_ASSERT(is_invariant_ok()); - } - - vector_Data(const unsigned char* const bytes, const std::size_t size, const Format format) - : container_Data(std::vector((format == Data_format::binary) ? size : size + 1), format) - { - std::copy(bytes, bytes + size, begin(storage_)); - DMITIGR_ASSERT(is_invariant_ok()); - } - - std::unique_ptr to_data() const override - { - return std::make_unique(storage_, format_); - } - - std::size_t size() const noexcept override - { - return (format_ == Data_format::binary) ? storage_.size() : storage_.size() - 1; - } - - void* memory() noexcept override - { - return storage_.data(); - } - -protected: - bool is_invariant_ok() override - { - const bool memory_ok = memory(); - const bool container_data_ok = container_Data::is_invariant_ok(); - return memory_ok && container_data_ok; - } -}; - -// ----------------------------------------------------------------------------- - -class memory_Data_base : public iData { -protected: - bool is_invariant_ok() override - { - const bool memory_ok = memory(); - const bool idata_ok = iData::is_invariant_ok(); - return memory_ok && idata_ok; - } -}; - -template> -class memory_Data : public memory_Data_base { -public: - using Storage = std::unique_ptr; - - memory_Data(Storage&& storage, const std::size_t size, const Format format) - : format_(format) - , size_(size) - , storage_(std::move(storage)) - { - DMITIGR_ASSERT(is_invariant_ok()); - } - - std::unique_ptr to_data() const override - { - return std::make_unique(static_cast(storage_.get()), size_, format_); - } - - Format format() const noexcept override - { - return format_; - } - - std::size_t size() const noexcept override - { - return size_; - } - - bool is_empty() const noexcept override - { - return (size() == 0); - } - - const char* bytes() const noexcept override - { - return static_cast(storage_.get()); - } - - void* memory() noexcept override - { - return storage_.get(); - } - -private: - const Format format_{Format::text}; - std::size_t size_{}; - std::unique_ptr storage_; -}; - -// ----------------------------------------------------------------------------- - -using array_memory_Data = memory_Data; -using custom_memory_Data = memory_Data; - -// ----------------------------------------------------------------------------- - -class empty_Data : public iData { -public: - explicit empty_Data(const Format format) - : format_(format) - { - DMITIGR_ASSERT(is_invariant_ok()); - } - - std::unique_ptr to_data() const override - { - return std::make_unique(format_); - } - - Format format() const noexcept override - { - return format_; - } - - std::size_t size() const noexcept override - { - return 0; - } - - bool is_empty() const noexcept override - { - return true; - } - - const char* bytes() const noexcept override - { - return ""; - } - - void* memory() noexcept override - { - return nullptr; - } - -private: - bool is_invariant_ok() override - { - const bool empty_ok = is_empty(); - const bool memory_ok = !memory(); - const bool idata_ok = iData::is_invariant_ok(); - return empty_ok && memory_ok && idata_ok; - } - - const Format format_{Format::text}; -}; - -// ----------------------------------------------------------------------------- - -class Data_view : public iData { -public: - Data_view() = default; - - Data_view(const char* const bytes, const std::size_t size, const Format format) - : format_(format) - , size_(size) - , bytes_(bytes) - { - DMITIGR_ASSERT(is_invariant_ok()); - } - - // Non copyable. - Data_view(const Data_view&) = delete; - Data_view& operator=(const Data_view&) = delete; - - // Movable. - Data_view(Data_view&&) = default; - Data_view& operator=(Data_view&&) = default; - - std::unique_ptr to_data() const override - { - return std::make_unique(reinterpret_cast(bytes_), size_, format_); - } - - Format format() const noexcept override - { - return format_; - } - - std::size_t size() const noexcept override - { - return size_; - } - - bool is_empty() const noexcept override - { - return (size() == 0); - } - - const char* bytes() const noexcept override - { - return bytes_; - } - - void* memory() noexcept override - { - return nullptr; - } - -protected: - bool is_invariant_ok() override - { - const bool memory_ok = !memory(); - const bool idata_ok = iData::is_invariant_ok(); - return memory_ok && idata_ok; - } - -private: - Format format_{Format::text}; - std::size_t size_{}; - const char* bytes_{}; // No ownership -}; - -} // namespace dmitigr::pgfe::detail - -#endif // DMITIGR_PGFE_DATA_HXX diff --git a/lib/dmitigr/pgfe/errc.cxx b/lib/dmitigr/pgfe/errc.cpp similarity index 98% rename from lib/dmitigr/pgfe/errc.cxx rename to lib/dmitigr/pgfe/errc.cpp index d689ea4..4f518ef 100644 --- a/lib/dmitigr/pgfe/errc.cxx +++ b/lib/dmitigr/pgfe/errc.cpp @@ -2,13 +2,19 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#include "dmitigr/pgfe/errc.hxx" +#include "dmitigr/pgfe/errc.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" #include -namespace pgfe = dmitigr::pgfe; +namespace dmitigr::pgfe::detail { -const char* pgfe::detail::to_literal(const Client_errc errc) +/** + * @internal + * + * @returns The literal representation of the `errc`. + */ +DMITIGR_PGFE_INLINE const char* to_literal(const Client_errc errc) { switch (errc) { case Client_errc::success: @@ -27,7 +33,12 @@ const char* pgfe::detail::to_literal(const Client_errc errc) DMITIGR_ASSERT_ALWAYS(!true); } -const char* pgfe::detail::to_literal(const Server_errc errc) +/** + * @internal + * + * @returns The literal representation of the `errc`. + */ +DMITIGR_PGFE_INLINE const char* to_literal(const Server_errc errc) { switch (errc) { case Server_errc::c00_successful_completion: @@ -547,3 +558,7 @@ const char* pgfe::detail::to_literal(const Server_errc errc) } DMITIGR_ASSERT_ALWAYS(!true); } + +} // namespace dmitigr::pgfe::detail + +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/errc.hpp.in b/lib/dmitigr/pgfe/errc.hpp.in index ffbff17..69af75c 100644 --- a/lib/dmitigr/pgfe/errc.hpp.in +++ b/lib/dmitigr/pgfe/errc.hpp.in @@ -6,7 +6,7 @@ #define DMITIGR_PGFE_ERRC_HPP // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// This file was generated automatically. There is no sense to edit! +// This file was generated automatically. Edit errc.hpp.in instead !!!!!!!!!!!!! // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! namespace dmitigr::pgfe { @@ -988,4 +988,8 @@ enum class Server_errc { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/errc.cpp" +#endif + #endif // DMITIGR_PGFE_ERRC_HPP diff --git a/lib/dmitigr/pgfe/errc.hxx b/lib/dmitigr/pgfe/errc.hxx deleted file mode 100644 index 2c75180..0000000 --- a/lib/dmitigr/pgfe/errc.hxx +++ /dev/null @@ -1,24 +0,0 @@ -// -*- C++ -*- -// Copyright (C) Dmitry Igrishin -// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp - -#ifndef DMITIGR_PGFE_ERRC_HXX -#define DMITIGR_PGFE_ERRC_HXX - -#include "dmitigr/pgfe/errc.hpp" - -namespace dmitigr::pgfe::detail { - -/* - * @returns The literal representation of the `errc`. - */ -const char* to_literal(Client_errc errc); - -/* - * @returns The literal representation of the `errc`. - */ -const char* to_literal(Server_errc errc); - -} // namespace dmitigr::pgfe::detail - -#endif // DMITIGR_PGFE_ERRC_HXX diff --git a/lib/dmitigr/pgfe/error.hxx b/lib/dmitigr/pgfe/error.cpp similarity index 80% rename from lib/dmitigr/pgfe/error.hxx rename to lib/dmitigr/pgfe/error.cpp index 7094e16..7b042fb 100644 --- a/lib/dmitigr/pgfe/error.hxx +++ b/lib/dmitigr/pgfe/error.cpp @@ -2,11 +2,9 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#ifndef DMITIGR_PGFE_ERROR_HXX -#define DMITIGR_PGFE_ERROR_HXX - #include "dmitigr/pgfe/error.hpp" -#include "dmitigr/pgfe/problem.hxx" +#include "dmitigr/pgfe/problem.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" namespace dmitigr::pgfe::detail { @@ -30,4 +28,4 @@ using simple_Error = basic_Problem; } // namespace dmitigr::pgfe::detail -#endif // DMITIGR_PGFE_ERROR_HXX +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/error.hpp b/lib/dmitigr/pgfe/error.hpp index 8f703bf..7a09fee 100644 --- a/lib/dmitigr/pgfe/error.hpp +++ b/lib/dmitigr/pgfe/error.hpp @@ -39,4 +39,8 @@ class Error : public Response, public Problem { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/error.cpp" +#endif + #endif // DMITIGR_PGFE_ERROR_HPP diff --git a/lib/dmitigr/pgfe/exceptions.hxx b/lib/dmitigr/pgfe/exceptions.cpp similarity index 80% rename from lib/dmitigr/pgfe/exceptions.hxx rename to lib/dmitigr/pgfe/exceptions.cpp index f039f3a..e473c01 100644 --- a/lib/dmitigr/pgfe/exceptions.hxx +++ b/lib/dmitigr/pgfe/exceptions.cpp @@ -2,11 +2,9 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#ifndef DMITIGR_PGFE_EXCEPTIONS_HXX -#define DMITIGR_PGFE_EXCEPTIONS_HXX - #include "dmitigr/pgfe/error.hpp" #include "dmitigr/pgfe/exceptions.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" #include @@ -14,7 +12,7 @@ namespace dmitigr::pgfe::detail { -class iClient_exception : public Client_exception { +class iClient_exception final : public Client_exception { public: explicit iClient_exception(const Client_errc errc) : Client_exception(errc) @@ -25,7 +23,7 @@ class iClient_exception : public Client_exception { {} }; -class iServer_exception : public Server_exception { +class iServer_exception final : public Server_exception { public: explicit iServer_exception(std::shared_ptr error) : Server_exception(error->code()) @@ -50,4 +48,4 @@ class iServer_exception : public Server_exception { } // namespace dmitigr::pgfe::detail -#endif // DMITIGR_PGFE_EXCEPTIONS_HXX +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/exceptions.hpp b/lib/dmitigr/pgfe/exceptions.hpp index 4d6c5be..889c04a 100644 --- a/lib/dmitigr/pgfe/exceptions.hpp +++ b/lib/dmitigr/pgfe/exceptions.hpp @@ -66,4 +66,8 @@ class Server_exception : public std::system_error { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/exceptions.cpp" +#endif + #endif // DMITIGR_PGFE_EXCEPTIONS_HPP diff --git a/lib/dmitigr/pgfe/implementation_footer.hpp b/lib/dmitigr/pgfe/implementation_footer.hpp new file mode 100644 index 0000000..2f47e6b --- /dev/null +++ b/lib/dmitigr/pgfe/implementation_footer.hpp @@ -0,0 +1,12 @@ +// -*- C++ -*- +// Copyright (C) Dmitry Igrishin +// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp + +#ifdef DMITIGR_PGFE_NOMINMAX +#undef DMITIGR_PGFE_NOMINMAX +#undef NOMINMAX +#endif + +#ifdef DMITIGR_PGFE_INLINE +#undef DMITIGR_PGFE_INLINE +#endif diff --git a/lib/dmitigr/pgfe/implementation_header.hpp b/lib/dmitigr/pgfe/implementation_header.hpp new file mode 100644 index 0000000..474212c --- /dev/null +++ b/lib/dmitigr/pgfe/implementation_header.hpp @@ -0,0 +1,20 @@ +// -*- C++ -*- +// Copyright (C) Dmitry Igrishin +// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp + +#ifndef DMITIGR_PGFE_INLINE + #if defined(DMITIGR_PGFE_HEADER_ONLY) && !defined(DMITIGR_PGFE_BUILDING) + #define DMITIGR_PGFE_INLINE inline + #else + #define DMITIGR_PGFE_INLINE + #endif +#endif // DMITIGR_PGFE_INLINE + +#ifndef DMITIGR_PGFE_NOMINMAX + #ifdef _WIN32 + #ifndef NOMINMAX + #define NOMINMAX + #define DMITIGR_PGFE_NOMINMAX + #endif + #endif +#endif // DMITIGR_PGFE_NOMINMAX diff --git a/lib/dmitigr/pgfe/misc.cpp b/lib/dmitigr/pgfe/misc.cpp index 0f92a8d..cc11fce 100644 --- a/lib/dmitigr/pgfe/misc.cpp +++ b/lib/dmitigr/pgfe/misc.cpp @@ -2,11 +2,15 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp +#include "dmitigr/pgfe/basics.hpp" #include "dmitigr/pgfe/misc.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" -namespace pgfe = dmitigr::pgfe; +#include -DMITIGR_PGFE_API std::int_fast32_t pgfe::version() +namespace dmitigr::pgfe { + +DMITIGR_PGFE_INLINE std::int_fast32_t version() { // Actual values are set in CMakeLists.txt. constexpr std::int_least32_t major = DMITIGR_PGFE_VERSION_PART1; @@ -15,3 +19,14 @@ DMITIGR_PGFE_API std::int_fast32_t pgfe::version() // 11.234 -> 11 * 1000 + 234 = 11234 return major*1000 + minor; } + +DMITIGR_PGFE_INLINE void set_initialization(const External_library library) +{ + const auto libssl = static_cast(library & External_library::libssl); + const auto libcrypto = static_cast(library & External_library::libcrypto); + ::PQinitOpenSSL(libssl, libcrypto); +} + +} // namespace dmitigr::pgfe + +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/misc.hpp b/lib/dmitigr/pgfe/misc.hpp index 0d186d5..5ff8bb8 100644 --- a/lib/dmitigr/pgfe/misc.hpp +++ b/lib/dmitigr/pgfe/misc.hpp @@ -5,6 +5,7 @@ #ifndef DMITIGR_PGFE_MISC_HPP #define DMITIGR_PGFE_MISC_HPP +#include "dmitigr/pgfe/types_fwd.hpp" #include "dmitigr/pgfe/dll.hpp" #include @@ -21,6 +22,20 @@ namespace dmitigr::pgfe { */ DMITIGR_PGFE_API std::int_fast32_t version(); +/** + * @ingroup main + * + * @brief Sets obligation of initialization external libraries when needed. + * + * @remarks This function must be called with the value of `false` if the OpenSSL + * library is initialized yet before first connection to the PostgreSQL server. + */ +DMITIGR_PGFE_API void set_initialization(External_library library); + } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/misc.cpp" +#endif + #endif // DMITIGR_PGFE_MISC_HPP diff --git a/lib/dmitigr/pgfe/net.cxx b/lib/dmitigr/pgfe/net.cxx deleted file mode 100644 index d10ada4..0000000 --- a/lib/dmitigr/pgfe/net.cxx +++ /dev/null @@ -1,20 +0,0 @@ -// -*- C++ -*- -// Copyright (C) Dmitry Igrishin -// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp - -#include "dmitigr/pgfe/basics.hpp" -#include "dmitigr/pgfe/net.hxx" - -#include - -namespace dmitigr::pgfe::detail { - -Socket_readiness poll_sock(const int socket, const Socket_readiness mask, const std::chrono::milliseconds timeout) -{ - using Sock = net::Socket_native; - using Sock_readiness = net::Socket_readiness; - using net::poll; - return static_cast(poll(static_cast(socket), static_cast(mask), timeout)); -} - -} // namespace dmitigr::pgfe::detail diff --git a/lib/dmitigr/pgfe/net.hxx b/lib/dmitigr/pgfe/net.hxx deleted file mode 100644 index 9c1e4ab..0000000 --- a/lib/dmitigr/pgfe/net.hxx +++ /dev/null @@ -1,23 +0,0 @@ -// -*- C++ -*- -// Copyright (C) Dmitry Igrishin -// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp - -#ifndef DMITIGR_PGFE_NET_HXX -#define DMITIGR_PGFE_NET_HXX - -#include "dmitigr/pgfe/types_fwd.hpp" - -#include - -namespace dmitigr::pgfe::detail { - -/** - * @internal - * - * @brief A wrapper around internal::net::poll(). - */ -Socket_readiness poll_sock(int socket, Socket_readiness mask, std::chrono::milliseconds timeout); - -} // namespace dmitigr::pgfe::detail - -#endif // DMITIGR_PGFE_NET_HXX diff --git a/lib/dmitigr/pgfe/notice.hxx b/lib/dmitigr/pgfe/notice.cpp similarity index 80% rename from lib/dmitigr/pgfe/notice.hxx rename to lib/dmitigr/pgfe/notice.cpp index 2707858..715cacc 100644 --- a/lib/dmitigr/pgfe/notice.hxx +++ b/lib/dmitigr/pgfe/notice.cpp @@ -2,11 +2,9 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#ifndef DMITIGR_PGFE_NOTICE_HXX -#define DMITIGR_PGFE_NOTICE_HXX - #include "dmitigr/pgfe/notice.hpp" -#include "dmitigr/pgfe/problem.hxx" +#include "dmitigr/pgfe/problem.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" namespace dmitigr::pgfe::detail { @@ -30,4 +28,4 @@ using simple_Notice = basic_Problem; } // namespace dmitigr::pgfe::detail -#endif // DMITIGR_PGFE_NOTICE_HXX +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/notice.hpp b/lib/dmitigr/pgfe/notice.hpp index b404f82..9374a35 100644 --- a/lib/dmitigr/pgfe/notice.hpp +++ b/lib/dmitigr/pgfe/notice.hpp @@ -43,4 +43,8 @@ class Notice : public Signal, public Problem { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/notice.cpp" +#endif + #endif // DMITIGR_PGFE_NOTICE_HPP diff --git a/lib/dmitigr/pgfe/notification.hxx b/lib/dmitigr/pgfe/notification.cpp similarity index 88% rename from lib/dmitigr/pgfe/notification.hxx rename to lib/dmitigr/pgfe/notification.cpp index 5dae537..fce3413 100644 --- a/lib/dmitigr/pgfe/notification.hxx +++ b/lib/dmitigr/pgfe/notification.cpp @@ -2,12 +2,10 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#ifndef DMITIGR_PGFE_NOTIFICATION_HXX -#define DMITIGR_PGFE_NOTIFICATION_HXX - -#include "dmitigr/pgfe/data.hxx" +#include "dmitigr/pgfe/data.hpp" #include "dmitigr/pgfe/notification.hpp" -#include "dmitigr/pgfe/pq.hxx" +#include "dmitigr/pgfe/pq.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" #include @@ -24,7 +22,7 @@ class iNotification : public Notification { } }; -class pq_Notification : public iNotification { +class pq_Notification final : public iNotification { public: explicit pq_Notification(::PGnotify* const pgnotify) : pgnotify_(pgnotify) @@ -76,4 +74,4 @@ class pq_Notification : public iNotification { } // namespace dmitigr::pgfe::detail -#endif // DMITIGR_PGFE_NOTIFICATION_HXX +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/notification.hpp b/lib/dmitigr/pgfe/notification.hpp index f3adf5b..ac3d2c0 100644 --- a/lib/dmitigr/pgfe/notification.hpp +++ b/lib/dmitigr/pgfe/notification.hpp @@ -47,4 +47,8 @@ class Notification : public Signal { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/notification.cpp" +#endif + #endif // DMITIGR_PGFE_NOTIFICATION_HPP diff --git a/lib/dmitigr/pgfe/parameterizable.hxx b/lib/dmitigr/pgfe/parameterizable.cpp similarity index 84% rename from lib/dmitigr/pgfe/parameterizable.hxx rename to lib/dmitigr/pgfe/parameterizable.cpp index 7a232f8..d6cfb50 100644 --- a/lib/dmitigr/pgfe/parameterizable.hxx +++ b/lib/dmitigr/pgfe/parameterizable.cpp @@ -2,10 +2,8 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#ifndef DMITIGR_PGFE_PARAMETERIZABLE_HXX -#define DMITIGR_PGFE_PARAMETERIZABLE_HXX - #include "dmitigr/pgfe/parameterizable.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" #include @@ -29,4 +27,4 @@ inline bool is_invariant_ok(const Parameterizable& o) } // namespace dmitigr::pgfe::detail -#endif // DMITIGR_PGFE_PARAMETERIZABLE_HXX +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/parameterizable.hpp b/lib/dmitigr/pgfe/parameterizable.hpp index e388c41..9f711b2 100644 --- a/lib/dmitigr/pgfe/parameterizable.hpp +++ b/lib/dmitigr/pgfe/parameterizable.hpp @@ -91,4 +91,8 @@ class Parameterizable { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/parameterizable.cpp" +#endif + #endif // DMITIGR_PGFE_PARAMETERIZABLE_HPP diff --git a/lib/dmitigr/pgfe/pq.hxx b/lib/dmitigr/pgfe/pq.hpp similarity index 98% rename from lib/dmitigr/pgfe/pq.hxx rename to lib/dmitigr/pgfe/pq.hpp index 3aa5547..88785b1 100644 --- a/lib/dmitigr/pgfe/pq.hxx +++ b/lib/dmitigr/pgfe/pq.hpp @@ -2,8 +2,8 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#ifndef DMITIGR_PGFE_PQ_HXX -#define DMITIGR_PGFE_PQ_HXX +#ifndef DMITIGR_PGFE_PQ_HPP +#define DMITIGR_PGFE_PQ_HPP #include "dmitigr/pgfe/basics.hpp" @@ -21,7 +21,7 @@ namespace std { * * @brief The default deleter for ::PGnotify. */ -template<> struct default_delete<::PGnotify> { +template<> struct default_delete<::PGnotify> final { void operator()(::PGnotify* const ptr) const { ::PQfreemem(ptr); @@ -33,7 +33,7 @@ template<> struct default_delete<::PGnotify> { * * @brief The default deleter for `::PGresult`. */ -template<> struct default_delete<::PGresult> { +template<> struct default_delete<::PGresult> final { void operator()(::PGresult* const ptr) const { ::PQclear(ptr); @@ -496,4 +496,4 @@ inline Result make_empty_single_tuple(const Data_format fmt) } // namespace dmitigr::pgfe::detail::pq -#endif // DMITIGR_PGFE_PQ_HXX +#endif // DMITIGR_PGFE_PQ_HPP diff --git a/lib/dmitigr/pgfe/prepared_statement.hpp b/lib/dmitigr/pgfe/prepared_statement_dfn.hpp similarity index 98% rename from lib/dmitigr/pgfe/prepared_statement.hpp rename to lib/dmitigr/pgfe/prepared_statement_dfn.hpp index e605fb7..c088053 100644 --- a/lib/dmitigr/pgfe/prepared_statement.hpp +++ b/lib/dmitigr/pgfe/prepared_statement_dfn.hpp @@ -2,8 +2,8 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#ifndef DMITIGR_PGFE_PREPARED_STATEMENT_HPP -#define DMITIGR_PGFE_PREPARED_STATEMENT_HPP +#ifndef DMITIGR_PGFE_PREPARED_STATEMENT_DFN_HPP +#define DMITIGR_PGFE_PREPARED_STATEMENT_DFN_HPP #include "dmitigr/pgfe/basics.hpp" #include "dmitigr/pgfe/conversions.hpp" @@ -355,4 +355,4 @@ class Prepared_statement : public Response, public Parameterizable { } // namespace dmitigr::pgfe -#endif // DMITIGR_PGFE_PREPARED_STATEMENT_HPP +#endif // DMITIGR_PGFE_PREPARED_STATEMENT_DFN_HPP diff --git a/lib/dmitigr/pgfe/prepared_statement.cxx b/lib/dmitigr/pgfe/prepared_statement_impl.cpp similarity index 77% rename from lib/dmitigr/pgfe/prepared_statement.cxx rename to lib/dmitigr/pgfe/prepared_statement_impl.cpp index 1dabb42..a0934db 100644 --- a/lib/dmitigr/pgfe/prepared_statement.cxx +++ b/lib/dmitigr/pgfe/prepared_statement_impl.cpp @@ -2,12 +2,14 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#include "dmitigr/pgfe/connection.hxx" -#include "dmitigr/pgfe/prepared_statement.hxx" +#include "dmitigr/pgfe/connection.hpp" +#include "dmitigr/pgfe/prepared_statement_impl.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" namespace dmitigr::pgfe::detail { -pq_Prepared_statement::pq_Prepared_statement(std::string name, pq_Connection* const connection, const Sql_string* const preparsed) +inline pq_Prepared_statement::pq_Prepared_statement(std::string name, + pq_Connection* const connection, const Sql_string* const preparsed) : name_(std::move(name)) , preparsed_(bool(preparsed)) { @@ -25,7 +27,8 @@ pq_Prepared_statement::pq_Prepared_statement(std::string name, pq_Connection* co DMITIGR_ASSERT(is_invariant_ok()); } -pq_Prepared_statement::pq_Prepared_statement(std::string name, pq_Connection* const connection, const std::size_t parameters_count) +inline pq_Prepared_statement::pq_Prepared_statement(std::string name, + pq_Connection* const connection, const std::size_t parameters_count) : name_(std::move(name)) , parameters_(parameters_count) { @@ -33,7 +36,7 @@ pq_Prepared_statement::pq_Prepared_statement(std::string name, pq_Connection* co DMITIGR_ASSERT(is_invariant_ok()); } -void pq_Prepared_statement::init_connection__(pq_Connection* const connection) +inline void pq_Prepared_statement::init_connection__(pq_Connection* const connection) { /* * The maximum parameter count and the maximum data size are defined as a static @@ -46,7 +49,7 @@ void pq_Prepared_statement::init_connection__(pq_Connection* const connection) result_format_ = connection_->result_format(); } -void pq_Prepared_statement::execute_async() +inline void pq_Prepared_statement::execute_async() { DMITIGR_REQUIRE(connection()->is_ready_for_async_request(), std::logic_error); @@ -84,7 +87,7 @@ void pq_Prepared_statement::execute_async() DMITIGR_ASSERT(is_invariant_ok()); } -void pq_Prepared_statement::execute() +inline void pq_Prepared_statement::execute() { DMITIGR_REQUIRE(connection()->is_ready_for_request(), std::logic_error); execute_async(); @@ -92,29 +95,29 @@ void pq_Prepared_statement::execute() DMITIGR_ASSERT(is_invariant_ok()); } -Connection* pq_Prepared_statement::connection() +inline Connection* pq_Prepared_statement::connection() { return connection_; } -const Connection* pq_Prepared_statement::connection() const +inline const Connection* pq_Prepared_statement::connection() const { return connection_; } -void pq_Prepared_statement::describe_async() +inline void pq_Prepared_statement::describe_async() { connection_->describe_prepared_statement_async(name_); DMITIGR_ASSERT(is_invariant_ok()); } -void pq_Prepared_statement::describe() +inline void pq_Prepared_statement::describe() { connection_->describe_prepared_statement(name_); DMITIGR_ASSERT(is_invariant_ok()); } -bool pq_Prepared_statement::is_invariant_ok() +inline bool pq_Prepared_statement::is_invariant_ok() { const bool params_ok = (parameter_count() <= maximum_parameter_count()); const bool preparsed_ok = is_preparsed() || !has_named_parameters(); @@ -124,3 +127,5 @@ bool pq_Prepared_statement::is_invariant_ok() } } // namespace dmitigr::pgfe::detail + +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/prepared_statement.hxx b/lib/dmitigr/pgfe/prepared_statement_impl.hpp similarity index 95% rename from lib/dmitigr/pgfe/prepared_statement.hxx rename to lib/dmitigr/pgfe/prepared_statement_impl.hpp index 7ecc4ae..f88dad6 100644 --- a/lib/dmitigr/pgfe/prepared_statement.hxx +++ b/lib/dmitigr/pgfe/prepared_statement_impl.hpp @@ -2,14 +2,14 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#ifndef DMITIGR_PGFE_PREPARED_STATEMENT_HXX -#define DMITIGR_PGFE_PREPARED_STATEMENT_HXX +#ifndef DMITIGR_PGFE_PREPARED_STATEMENT_IMPL_HPP +#define DMITIGR_PGFE_PREPARED_STATEMENT_IMPL_HPP -#include "dmitigr/pgfe/exceptions.hxx" -#include "dmitigr/pgfe/parameterizable.hxx" -#include "dmitigr/pgfe/pq.hxx" -#include "dmitigr/pgfe/prepared_statement.hpp" -#include "dmitigr/pgfe/row_info.hxx" +#include "dmitigr/pgfe/exceptions.hpp" +#include "dmitigr/pgfe/parameterizable.hpp" +#include "dmitigr/pgfe/pq.hpp" +#include "dmitigr/pgfe/prepared_statement_dfn.hpp" +#include "dmitigr/pgfe/row_info.hpp" #include @@ -37,7 +37,7 @@ inline bool iPrepared_statement::is_invariant_ok() class pq_Connection; -class pq_Prepared_statement : public iPrepared_statement { +class pq_Prepared_statement final : public iPrepared_statement { public: // Construct prepared statement when preparing. pq_Prepared_statement(std::string name, pq_Connection* connection, const Sql_string* preparsed); @@ -257,7 +257,7 @@ class pq_Prepared_statement : public iPrepared_statement { using Data_deletion_required = memory::Conditional_delete; using Data_ptr = std::unique_ptr; - struct Parameter { + struct Parameter final { Data_ptr data; std::string name; }; @@ -313,4 +313,4 @@ class pq_Prepared_statement : public iPrepared_statement { } // namespace dmitigr::pgfe::detail -#endif // DMITIGR_PGFE_PREPARED_STATEMENT_HXX +#endif // DMITIGR_PGFE_PREPARED_STATEMENT_IMPL_HPP diff --git a/lib/dmitigr/pgfe/problem.cpp b/lib/dmitigr/pgfe/problem.cpp index 8309276..01bdffb 100644 --- a/lib/dmitigr/pgfe/problem.cpp +++ b/lib/dmitigr/pgfe/problem.cpp @@ -4,20 +4,238 @@ #include "dmitigr/pgfe/basics.hpp" #include "dmitigr/pgfe/problem.hpp" -#include "dmitigr/pgfe/sql.hxx" #include "dmitigr/pgfe/std_system_error.hpp" +#include "dmitigr/pgfe/util.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" #include +#include + +namespace dmitigr::pgfe::detail { + +template +class basic_Problem final : public ProblemDerived { + static_assert(std::is_base_of_v); +public: + basic_Problem(std::string severity_localized, + std::optional severity_non_localized, + std::string sqlstate, + std::string brief, + std::optional detail, + std::optional hint, + std::optional query_position, + std::optional internal_query_position, + std::optional internal_query, + std::optional context, + std::optional schema_name, + std::optional table_name, + std::optional column_name, + std::optional data_type_name, + std::optional constraint_name, + std::optional source_file, + std::optional source_line, + std::optional source_function) + : severity_localized_(std::move(severity_localized)) + , severity_non_localized_(std::move(severity_non_localized)) + , sqlstate_(std::move(sqlstate)) + , brief_(std::move(brief)) + , detail_(std::move(detail)) + , hint_(std::move(hint)) + , query_position_(std::move(query_position)) + , internal_query_position_(std::move(internal_query_position)) + , internal_query_(std::move(internal_query)) + , context_(std::move(context)) + , schema_name_(std::move(schema_name)) + , table_name_(std::move(table_name)) + , column_name_(std::move(column_name)) + , data_type_name_(std::move(data_type_name)) + , constraint_name_(std::move(constraint_name)) + , source_file_(std::move(source_file)) + , source_line_(std::move(source_line)) + , source_function_(std::move(source_function)) + { + DMITIGR_ASSERT(is_invariant_ok()); + } + + std::unique_ptr to_problem() const override + { + return std::make_unique(*this); + } + + std::error_code code() const noexcept override + { + return ProblemDerived::code(); + } + + Problem_severity severity() const override + { + return ProblemDerived::severity(); + } + + const std::string& severity_localized() const noexcept override + { + return severity_localized_; + } + + const std::string& severity_non_localized() const override + { + if (severity_non_localized_) + return *severity_non_localized_; + else + throw std::runtime_error("severity_non_localized is not presents in reports generated by PostgreSQL versions prior to 9.6"); + } + + const std::string& sqlstate() const noexcept override + { + return sqlstate_; + } + + const std::string& brief() const noexcept override + { + return brief_; + } + + const std::optional& detail() const noexcept override + { + return detail_; + } + + const std::optional& hint() const noexcept override + { + return hint_; + } + + const std::optional& query_position() const noexcept override + { + return query_position_; + } + + const std::optional& internal_query_position() const noexcept override + { + return internal_query_position_; + } + + const std::optional& internal_query() const noexcept override + { + return internal_query_; + } + + const std::optional& context() const noexcept override + { + return context_; + } + + const std::optional& schema_name() const noexcept override + { + return schema_name_; + } + + const std::optional& table_name() const noexcept override + { + return table_name_; + } + + const std::optional& column_name() const noexcept override + { + return column_name_; + } + + const std::optional& data_type_name() const noexcept override + { + return data_type_name_; + } + + const std::optional& constraint_name() const noexcept override + { + return constraint_name_; + } + + const std::optional& source_file() const noexcept override + { + return source_file_; + } + + const std::optional& source_line() const noexcept override + { + return source_line_; + } + + const std::optional& source_function() const noexcept override + { + return source_function_; + } + +protected: + bool is_invariant_ok() override + { + constexpr bool is_error = std::is_base_of_v; + const bool mandatory_ok = !severity_localized().empty() && !sqlstate().empty(); + const bool severity_ok = + !severity_non_localized_ || + (!is_error && ((severity_non_localized_ == "LOG") || + (severity_non_localized_ == "INFO") || + (severity_non_localized_ == "DEBUG") || + (severity_non_localized_ == "NOTICE") || + (severity_non_localized_ == "WARNING"))) + || + (is_error && ((severity_non_localized_ == "ERROR") || + (severity_non_localized_ == "FATAL") || + (severity_non_localized_ == "PANIC"))); + + /* + * Note: Error with SQLSTATE codes of classes 00, 01, 02 + * (which correspond to warnings, not errors) are legal. + */ + const int value = code().value(); + const bool code_ok = (min_warning_integer_code_ <= value && value <= max_error_integer_code_); + const bool problemderived_ok = ProblemDerived::is_invariant_ok(); + return mandatory_ok && severity_ok && code_ok && problemderived_ok; + } + +private: + // The integer with the base 36 that represents the error condition "00000". + constexpr static int min_warning_integer_code_ = 0; + + // The integer with the base 36 that represents the error condition "03000". + constexpr static int min_error_integer_code_ = 139968; + + // The integer with the base 36 that represents the error condition "ZZZZZ". + constexpr static int max_error_integer_code_ = 60466175; + + std::string severity_localized_; + std::optional severity_non_localized_; + std::string sqlstate_; + std::string brief_; + std::optional detail_; + std::optional hint_; + std::optional query_position_; + std::optional internal_query_position_; + std::optional internal_query_; + std::optional context_; + std::optional schema_name_; + std::optional table_name_; + std::optional column_name_; + std::optional data_type_name_; + std::optional constraint_name_; + std::optional source_file_; + std::optional source_line_; + std::optional source_function_; +}; + +} // namespace dmitigr::pgfe::detail + +// ============================================================================= + namespace dmitigr::pgfe { -std::error_code Problem::code() const +inline std::error_code Problem::code() const { const int code_integer = detail::sqlstate_to_int(sqlstate().c_str()); return std::error_code(code_integer, server_error_category()); } -Problem_severity Problem::severity() const +inline Problem_severity Problem::severity() const { Problem_severity result{}; const auto severity_string = severity_non_localized(); @@ -43,3 +261,5 @@ Problem_severity Problem::severity() const } } // namespace dmitigr::pgfe + +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/problem.hpp b/lib/dmitigr/pgfe/problem.hpp index c39d931..c453717 100644 --- a/lib/dmitigr/pgfe/problem.hpp +++ b/lib/dmitigr/pgfe/problem.hpp @@ -190,4 +190,8 @@ class Problem { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/problem.cpp" +#endif + #endif // DMITIGR_PGFE_PROBLEM_HPP diff --git a/lib/dmitigr/pgfe/problem.hxx b/lib/dmitigr/pgfe/problem.hxx deleted file mode 100644 index 71632c1..0000000 --- a/lib/dmitigr/pgfe/problem.hxx +++ /dev/null @@ -1,228 +0,0 @@ -// -*- C++ -*- -// Copyright (C) Dmitry Igrishin -// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp - -#ifndef DMITIGR_PGFE_PROBLEM_HXX -#define DMITIGR_PGFE_PROBLEM_HXX - -#include "dmitigr/pgfe/basics.hpp" -#include "dmitigr/pgfe/problem.hpp" - -#include - -#include - -namespace dmitigr::pgfe::detail { - -template -class basic_Problem : public ProblemDerived { - static_assert(std::is_base_of_v); -public: - basic_Problem(std::string severity_localized, - std::optional severity_non_localized, - std::string sqlstate, - std::string brief, - std::optional detail, - std::optional hint, - std::optional query_position, - std::optional internal_query_position, - std::optional internal_query, - std::optional context, - std::optional schema_name, - std::optional table_name, - std::optional column_name, - std::optional data_type_name, - std::optional constraint_name, - std::optional source_file, - std::optional source_line, - std::optional source_function) - : severity_localized_(std::move(severity_localized)) - , severity_non_localized_(std::move(severity_non_localized)) - , sqlstate_(std::move(sqlstate)) - , brief_(std::move(brief)) - , detail_(std::move(detail)) - , hint_(std::move(hint)) - , query_position_(std::move(query_position)) - , internal_query_position_(std::move(internal_query_position)) - , internal_query_(std::move(internal_query)) - , context_(std::move(context)) - , schema_name_(std::move(schema_name)) - , table_name_(std::move(table_name)) - , column_name_(std::move(column_name)) - , data_type_name_(std::move(data_type_name)) - , constraint_name_(std::move(constraint_name)) - , source_file_(std::move(source_file)) - , source_line_(std::move(source_line)) - , source_function_(std::move(source_function)) - { - DMITIGR_ASSERT(is_invariant_ok()); - } - - std::unique_ptr to_problem() const override - { - return std::make_unique(*this); - } - - std::error_code code() const noexcept override - { - return ProblemDerived::code(); - } - - Problem_severity severity() const override - { - return ProblemDerived::severity(); - } - - const std::string& severity_localized() const noexcept override - { - return severity_localized_; - } - - const std::string& severity_non_localized() const override - { - if (severity_non_localized_) - return *severity_non_localized_; - else - throw std::runtime_error("severity_non_localized is not presents in reports generated by PostgreSQL versions prior to 9.6"); - } - - const std::string& sqlstate() const noexcept override - { - return sqlstate_; - } - - const std::string& brief() const noexcept override - { - return brief_; - } - - const std::optional& detail() const noexcept override - { - return detail_; - } - - const std::optional& hint() const noexcept override - { - return hint_; - } - - const std::optional& query_position() const noexcept override - { - return query_position_; - } - - const std::optional& internal_query_position() const noexcept override - { - return internal_query_position_; - } - - const std::optional& internal_query() const noexcept override - { - return internal_query_; - } - - const std::optional& context() const noexcept override - { - return context_; - } - - const std::optional& schema_name() const noexcept override - { - return schema_name_; - } - - const std::optional& table_name() const noexcept override - { - return table_name_; - } - - const std::optional& column_name() const noexcept override - { - return column_name_; - } - - const std::optional& data_type_name() const noexcept override - { - return data_type_name_; - } - - const std::optional& constraint_name() const noexcept override - { - return constraint_name_; - } - - const std::optional& source_file() const noexcept override - { - return source_file_; - } - - const std::optional& source_line() const noexcept override - { - return source_line_; - } - - const std::optional& source_function() const noexcept override - { - return source_function_; - } - -protected: - bool is_invariant_ok() override - { - constexpr bool is_error = std::is_base_of_v; - const bool mandatory_ok = !severity_localized().empty() && !sqlstate().empty(); - const bool severity_ok = - !severity_non_localized_ || - (!is_error && ((severity_non_localized_ == "LOG") || - (severity_non_localized_ == "INFO") || - (severity_non_localized_ == "DEBUG") || - (severity_non_localized_ == "NOTICE") || - (severity_non_localized_ == "WARNING"))) - || - (is_error && ((severity_non_localized_ == "ERROR") || - (severity_non_localized_ == "FATAL") || - (severity_non_localized_ == "PANIC"))); - - /* - * Note: Error with SQLSTATE codes of classes 00, 01, 02 - * (which correspond to warnings, not errors) are legal. - */ - const int value = code().value(); - const bool code_ok = (min_warning_integer_code_ <= value && value <= max_error_integer_code_); - const bool problemderived_ok = ProblemDerived::is_invariant_ok(); - return mandatory_ok && severity_ok && code_ok && problemderived_ok; - } - -private: - // The integer with the base 36 that represents the error condition "00000". - constexpr static int min_warning_integer_code_ = 0; - - // The integer with the base 36 that represents the error condition "03000". - constexpr static int min_error_integer_code_ = 139968; - - // The integer with the base 36 that represents the error condition "ZZZZZ". - constexpr static int max_error_integer_code_ = 60466175; - - std::string severity_localized_; - std::optional severity_non_localized_; - std::string sqlstate_; - std::string brief_; - std::optional detail_; - std::optional hint_; - std::optional query_position_; - std::optional internal_query_position_; - std::optional internal_query_; - std::optional context_; - std::optional schema_name_; - std::optional table_name_; - std::optional column_name_; - std::optional data_type_name_; - std::optional constraint_name_; - std::optional source_file_; - std::optional source_line_; - std::optional source_function_; -}; - -} // namespace dmitigr::pgfe::detail - -#endif // DMITIGR_PGFE_PROBLEM_HXX diff --git a/lib/dmitigr/pgfe/row.hxx b/lib/dmitigr/pgfe/row.cpp similarity index 92% rename from lib/dmitigr/pgfe/row.hxx rename to lib/dmitigr/pgfe/row.cpp index 81085df..014b4da 100644 --- a/lib/dmitigr/pgfe/row.hxx +++ b/lib/dmitigr/pgfe/row.cpp @@ -2,13 +2,11 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#ifndef DMITIGR_PGFE_ROW_HXX -#define DMITIGR_PGFE_ROW_HXX - -#include "dmitigr/pgfe/compositional.hxx" -#include "dmitigr/pgfe/data.hxx" +#include "dmitigr/pgfe/compositional.hpp" +#include "dmitigr/pgfe/data.hpp" #include "dmitigr/pgfe/row.hpp" -#include "dmitigr/pgfe/row_info.hxx" +#include "dmitigr/pgfe/row_info.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" namespace dmitigr::pgfe::detail { @@ -25,7 +23,7 @@ inline bool iRow::is_invariant_ok() // ----------------------------------------------------------------------------- -class pq_Row : public iRow { +class pq_Row final : public iRow { public: explicit pq_Row(pq_Row_info&& info) : info_{std::move(info)} @@ -115,4 +113,4 @@ class pq_Row : public iRow { } // namespace dmitigr::pgfe::detail -#endif // DMITIGR_PGFE_ROW_HXX +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/row.hpp b/lib/dmitigr/pgfe/row.hpp index 3057cce..f084fe6 100644 --- a/lib/dmitigr/pgfe/row.hpp +++ b/lib/dmitigr/pgfe/row.hpp @@ -55,4 +55,8 @@ class Row : public Response, public Compositional { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/row.cpp" +#endif + #endif // DMITIGR_PGFE_ROW_HPP diff --git a/lib/dmitigr/pgfe/row_info.hxx b/lib/dmitigr/pgfe/row_info.cpp similarity index 96% rename from lib/dmitigr/pgfe/row_info.hxx rename to lib/dmitigr/pgfe/row_info.cpp index f075ac8..013c75b 100644 --- a/lib/dmitigr/pgfe/row_info.hxx +++ b/lib/dmitigr/pgfe/row_info.cpp @@ -2,13 +2,11 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#ifndef DMITIGR_PGFE_ROW_INFO_HXX -#define DMITIGR_PGFE_ROW_INFO_HXX - -#include "dmitigr/pgfe/compositional.hxx" -#include "dmitigr/pgfe/pq.hxx" +#include "dmitigr/pgfe/compositional.hpp" +#include "dmitigr/pgfe/pq.hpp" #include "dmitigr/pgfe/row_info.hpp" -#include "dmitigr/pgfe/sql.hxx" +#include "dmitigr/pgfe/util.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" #include #include @@ -75,7 +73,7 @@ inline bool iRow_info::is_invariant_ok() // ----------------------------------------------------------------------------- -class pq_Row_info : public iRow_info { +class pq_Row_info final : public iRow_info { public: explicit pq_Row_info(pq::Result&& pq_result) : pq_result_(std::move(pq_result)) @@ -242,4 +240,4 @@ class pq_Row_info : public iRow_info { } // namespace dmitigr::pgfe::detail -#endif // DMITIGR_PGFE_ROW_INFO_HXX +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/row_info.hpp b/lib/dmitigr/pgfe/row_info.hpp index e7a114d..5a4644a 100644 --- a/lib/dmitigr/pgfe/row_info.hpp +++ b/lib/dmitigr/pgfe/row_info.hpp @@ -169,4 +169,8 @@ class Row_info : public Compositional { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/row_info.cpp" +#endif + #endif // DMITIGR_PGFE_ROW_INFO_HPP diff --git a/lib/dmitigr/pgfe/sql.cxx b/lib/dmitigr/pgfe/sql.cxx deleted file mode 100644 index da28ac1..0000000 --- a/lib/dmitigr/pgfe/sql.cxx +++ /dev/null @@ -1,93 +0,0 @@ -// -*- C++ -*- -// Copyright (C) Dmitry Igrishin -// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp - -#include "dmitigr/pgfe/sql.hxx" - -#include - -#include -#include -#include -#include -#include - -namespace { - -/** - * @internal - * - * @returns The next number. - * - * @par Thread safety - * Thread-safe. - */ -std::uint_fast64_t next_number() noexcept -{ - static std::atomic result{0}; - ++result; - return result; -} - -/** - * @internal - * - * @returns The prefix of unique SQL identifier. - * - * @par Thread safety - * Thread-safe. - */ -const std::string& sqlid_prefix() -{ - static const std::string result{"$dmitigr_pgfe$_"}; - return result; -} - -} // namespace - -std::string dmitigr::pgfe::detail::unique_sqlid() -{ - return sqlid_prefix() + std::to_string(next_number()); -} - -std::string dmitigr::pgfe::detail::unquote_identifier(const std::string& identifier) -{ - enum { top, double_quote, adjacent_double_quote } state = top; - - std::string result; - const auto sz = identifier.size(); - using Counter = std::remove_const_t; - for (Counter i = 0; i < sz; ++i) { - const char c = identifier[i]; - if (state == top) { - if (c != '"') { - result += std::tolower(c, std::locale{}); - } else - state = double_quote; - } else if (state == double_quote) { - if (c != '"') - result += c; - else // Note: identifier[sz] == 0 - state = (identifier[i + 1] == '"') ? adjacent_double_quote : top; - } else if (state == adjacent_double_quote) { - result += c; - state = double_quote; - } - } - return result; -} - -int dmitigr::pgfe::detail::sqlstate_to_int(const char* const code) -{ - DMITIGR_ASSERT(code && - ( std::isalnum(code[0], std::locale{}) && - std::isalnum(code[1], std::locale{}) && - std::isalnum(code[2], std::locale{}) && - std::isalnum(code[3], std::locale{}) && - std::isalnum(code[4], std::locale{}) && code[5] == '\0')); - - const long int result_candidate = std::strtol(code, NULL, 36); - DMITIGR_ASSERT(errno == 0); - DMITIGR_ASSERT(result_candidate >= 0 && result_candidate <= std::numeric_limits::max()); - return result_candidate; -} diff --git a/lib/dmitigr/pgfe/sql_string.cpp b/lib/dmitigr/pgfe/sql_string.cpp index bb4e91c..7e55026 100644 --- a/lib/dmitigr/pgfe/sql_string.cpp +++ b/lib/dmitigr/pgfe/sql_string.cpp @@ -2,15 +2,1351 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#include "dmitigr/pgfe/sql_string.hxx" +#include "dmitigr/pgfe/composite.hpp" +#include "dmitigr/pgfe/data.hpp" +#include "dmitigr/pgfe/parameterizable.hpp" +#include "dmitigr/pgfe/sql_string.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" -namespace pgfe = dmitigr::pgfe; +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace dmitigr::pgfe::detail { + +std::pair parse_sql_input(const char* text); + +class iSql_string final : public Sql_string { +public: + iSql_string() + { + DMITIGR_ASSERT(is_invariant_ok()); + } + + iSql_string(const iSql_string& rhs) + : fragments_{rhs.fragments_} + , positional_parameters_{rhs.positional_parameters_} + { + named_parameters_ = named_parameters(); + } + + iSql_string& operator=(const iSql_string& rhs) + { + iSql_string tmp{rhs}; + swap(tmp); + return *this; + } + + iSql_string(iSql_string&& rhs) + : fragments_{std::move(rhs.fragments_)} + , positional_parameters_{std::move(rhs.positional_parameters_)} + { + named_parameters_ = named_parameters(); + } + + iSql_string& operator=(iSql_string&& rhs) + { + iSql_string tmp{std::move(rhs)}; + swap(tmp); + return *this; + } + + void swap(iSql_string& other) + { + fragments_.swap(other.fragments_); + positional_parameters_.swap(other.positional_parameters_); + named_parameters_.swap(other.named_parameters_); + } + + explicit iSql_string(const char* const text) + : iSql_string(parse_sql_input(text).first) + { + DMITIGR_ASSERT(is_invariant_ok()); + } + + explicit iSql_string(const std::string& text) + : iSql_string(text.c_str()) + { + DMITIGR_ASSERT(is_invariant_ok()); + } + + // --------------------------------------------------------------------------- + + std::size_t positional_parameter_count() const override + { + return positional_parameters_.size(); + } + + std::size_t named_parameter_count() const override + { + return named_parameters_.size(); + } + + std::size_t parameter_count() const override + { + return (positional_parameter_count() + named_parameter_count()); + } + + const std::string& parameter_name(const std::size_t index) const override + { + DMITIGR_ASSERT(positional_parameter_count() <= index && index < parameter_count()); + return (named_parameters_[index - positional_parameter_count()])->str; + } + + std::optional parameter_index(const std::string& name) const override + { + if (const auto i = named_parameter_index__(name); i < parameter_count()) + return i; + else + return std::nullopt; + } + + std::size_t parameter_index_throw(const std::string& name) const override + { + const auto i = named_parameter_index__(name); + DMITIGR_REQUIRE(i < parameter_count(), std::out_of_range); + return i; + } + + bool has_parameter(const std::string& name) const override + { + return bool(parameter_index(name)); + } + + bool has_positional_parameters() const override + { + return !positional_parameters_.empty(); + } + + bool has_named_parameters() const override + { + return !named_parameters_.empty(); + } + + bool has_parameters() const override + { + return (has_positional_parameters() || has_named_parameters()); + } + + // --------------------------------------------------------------------------- + + std::unique_ptr to_sql_string() const override + { + return std::make_unique(*this); + } + + bool is_empty() const noexcept override + { + return fragments_.empty(); + } + + bool is_query_empty() const noexcept override + { + return std::all_of(cbegin(fragments_), cend(fragments_), + [](const Fragment& f) + { + return is_comment(f) || (is_text(f) && is_blank_string(f.str)); + }); + } + + bool is_parameter_missing(const std::size_t index) const override + { + DMITIGR_REQUIRE(index < positional_parameter_count(), std::out_of_range); + return !positional_parameters_[index]; + } + + bool has_missing_parameters() const override + { + return any_of(cbegin(positional_parameters_), cend(positional_parameters_), [](const auto is_present) { return !is_present; }); + } + + void append(const Sql_string* const appendix) override + { + DMITIGR_REQUIRE(appendix, std::invalid_argument); + const auto* const iappendix = dynamic_cast(appendix); + DMITIGR_ASSERT_ALWAYS(iappendix); + + const bool was_query_empty = is_query_empty(); + + // Updating fragments + auto old_fragments = fragments_; + try { + fragments_.insert(cend(fragments_), cbegin(iappendix->fragments_), cend(iappendix->fragments_)); + update_cache(*iappendix); // can throw (strong exception safety guarantee) + + if (was_query_empty) + is_extra_data_should_be_extracted_from_comments_ = true; + } catch (...) { + fragments_.swap(old_fragments); // rollback + throw; + } + + DMITIGR_ASSERT(is_invariant_ok()); + } + + void append(const std::string& appendix) override + { + iSql_string a(appendix); + append(&a); // includes invariant check + } + + void replace_parameter(const std::string& name, const Sql_string* const replacement) override + { + DMITIGR_REQUIRE(has_parameter(name), std::out_of_range); + DMITIGR_REQUIRE(replacement, std::invalid_argument); + const auto* const ireplacement = dynamic_cast(replacement); + DMITIGR_ASSERT_ALWAYS(ireplacement); + + // Updating fragments + auto old_fragments = fragments_; + try { + for (auto fi = begin(fragments_); fi != end(fragments_);) { + if (fi->type == Fragment::Type::named_parameter && fi->str == name) { + // Firstly, we'll insert the `replacement` just before `fi`. + fragments_.insert(fi, cbegin(ireplacement->fragments_), cend(ireplacement->fragments_)); + // Secondly, we'll erase named parameter pointed by `fi` and got the next iterator. + fi = fragments_.erase(fi); + } else + ++fi; + } + + update_cache(*ireplacement); // can throw (strong exception safety guarantee) + } catch (...) { + fragments_.swap(old_fragments); + throw; + } + + DMITIGR_ASSERT(is_invariant_ok()); + } + + void replace_parameter(const std::string& name, const std::string& replacement) override + { + iSql_string r(replacement); + replace_parameter(name, &r); // includes invariant check + } + + std::string to_string() const override + { + std::string result; + result.reserve(512); + for (const auto& fragment : fragments_) { + switch (fragment.type) { + case Fragment::Type::text: + result += fragment.str; + break; + case Fragment::Type::one_line_comment: + result += "--"; + result += fragment.str; + result += '\n'; + break; + case Fragment::Type::multi_line_comment: + result += "/*"; + result += fragment.str; + result += "*/"; + break; + case Fragment::Type::named_parameter: + result += ':'; + result += fragment.str; + break; + case Fragment::Type::positional_parameter: + result += '$'; + result += fragment.str; + break; + } + } + result.shrink_to_fit(); + return result; + } + + // Returns: output query string. + std::string to_query_string() const override + { + std::string result; + result.reserve(512); + for (const auto& fragment : fragments_) { + switch (fragment.type) { + case Fragment::Type::text: + result += fragment.str; + break; + case Fragment::Type::one_line_comment: + case Fragment::Type::multi_line_comment: + break; + case Fragment::Type::named_parameter: { + const auto idx = named_parameter_index__(fragment.str); + DMITIGR_ASSERT(idx < parameter_count()); + result += '$'; + result += std::to_string(idx + 1); + break; + } + case Fragment::Type::positional_parameter: + result += '$'; + result += fragment.str; + break; + } + } + return result; + } + + Composite* extra() override + { + return const_cast(static_cast(this)->extra()); + } + + const Composite* extra() const override + { + if (!extra_) + extra_.emplace(Extra::extract(fragments_)); + else if (is_extra_data_should_be_extracted_from_comments_) + extra_->append(heap_data_Composite(Extra::extract(fragments_))); + is_extra_data_should_be_extracted_from_comments_ = false; + DMITIGR_ASSERT(is_invariant_ok()); + return &*extra_; + } + +protected: + bool is_invariant_ok() const + { + const bool positional_parameters_ok = ((positional_parameter_count() > 0) == has_positional_parameters()); + const bool named_parameters_ok = ((named_parameter_count() > 0) == has_named_parameters()); + const bool parameters_ok = ((parameter_count() > 0) == has_parameters()); + const bool parameters_count_ok = (parameter_count() == (positional_parameter_count() + named_parameter_count())); + const bool empty_ok = !is_empty() || !has_parameters(); + const bool extra_ok = is_extra_data_should_be_extracted_from_comments_ || extra_; + const bool parameterizable_ok = detail::is_invariant_ok(*this); + + return + positional_parameters_ok && + named_parameters_ok && + parameters_ok && + parameters_count_ok && + empty_ok && + extra_ok && + parameterizable_ok; + } + +private: + friend std::pair parse_sql_input(const char*); + + constexpr static std::size_t maximum_parameter_count_{65536}; + + struct Fragment final { + enum class Type { + text, + one_line_comment, + multi_line_comment, + named_parameter, + positional_parameter + }; + + Fragment(const Type tp, const std::string& s) + : type(tp) + , str(s) + {} + + Type type; + std::string str; + }; + using Fragment_list = std::list; + + // --------------------------------------------------------------------------- + // Initializers + // --------------------------------------------------------------------------- + + void push_back_fragment__(Fragment::Type type, const std::string& str) + { + fragments_.emplace_back(type, str); + // The invariant should be checked by the caller. + } + + void push_text(const std::string& str) + { + push_back_fragment__(Fragment::Type::text, str); + DMITIGR_ASSERT(is_invariant_ok()); + } + + void push_one_line_comment(const std::string& str) + { + push_back_fragment__(Fragment::Type::one_line_comment, str); + DMITIGR_ASSERT(is_invariant_ok()); + } + + void push_multi_line_comment(const std::string& str) + { + push_back_fragment__(Fragment::Type::multi_line_comment, str); + DMITIGR_ASSERT(is_invariant_ok()); + } + + void push_positional_parameter(const std::string& str) + { + push_back_fragment__(Fragment::Type::positional_parameter, str); + + using Size = std::vector::size_type; + const decltype (maximum_parameter_count_) position = stoi(str); + if (position < 1 || position > maximum_parameter_count_ - 1) + throw std::runtime_error("invalid parameter position \"" + str + "\""); + else if (Size(position) > positional_parameters_.size()) + positional_parameters_.resize(position, false); + + positional_parameters_[Size(position) - 1] = true; // set parameter presence flag + DMITIGR_ASSERT(is_invariant_ok()); + } + + void push_named_parameter(const std::string& str) + { + if (parameter_count() < maximum_parameter_count_) { + push_back_fragment__(Fragment::Type::named_parameter, str); + if (none_of(cbegin(named_parameters_), cend(named_parameters_), [&str](const auto& i) { return (i->str == str); })) { + auto e = cend(fragments_); + --e; + named_parameters_.push_back(e); + } + } else + throw std::runtime_error("maximum parameters count (" + std::to_string(maximum_parameter_count_) + ") exceeded"); + + DMITIGR_ASSERT(is_invariant_ok()); + } + + // --------------------------------------------------------------------------- + // Updaters + // --------------------------------------------------------------------------- + + // Exception safety guarantee: strong. + void update_cache(const iSql_string& rhs) + { + // Preparing for merge positional parameters. + const auto old_pos_params_size = positional_parameters_.size(); + const auto rhs_pos_params_size = rhs.positional_parameters_.size(); + if (old_pos_params_size < rhs_pos_params_size) + positional_parameters_.resize(rhs_pos_params_size); // can throw + + try { + const auto new_pos_params_size = positional_parameters_.size(); + DMITIGR_ASSERT(new_pos_params_size >= rhs_pos_params_size); + + // Creating the cache for named parameters. + decltype (named_parameters_) new_named_parameters = named_parameters(); // can throw + + // Check the new parameter count. + const auto new_parameter_count = new_pos_params_size + new_named_parameters.size(); + if (new_parameter_count > maximum_parameter_count_) + throw std::runtime_error("parameter count (" + std::to_string(new_parameter_count) + ") " + "exceeds the maximum (" + std::to_string(maximum_parameter_count_) + ")"); + + // Merging positional parameters (cannot throw). + using Counter = std::remove_const_t; + for (Counter i = 0; i < rhs_pos_params_size; ++i) { + if (!positional_parameters_[i] && rhs.positional_parameters_[i]) + positional_parameters_[i] = true; + } + + named_parameters_.swap(new_named_parameters); // commit (cannot throw) + } catch (...) { + positional_parameters_.resize(old_pos_params_size); // rollback + throw; + } + + DMITIGR_ASSERT(is_invariant_ok()); + } + + // --------------------------------------------------------------------------- + // Generators + // --------------------------------------------------------------------------- + + std::vector unique_fragments(const Fragment::Type type) const + { + std::vector result; + result.reserve(8); + const auto e = cend(fragments_); + for (auto i = cbegin(fragments_); i != e; ++i) { + if (i->type == type) { + if (none_of(cbegin(result), cend(result), [&i](const auto& result_i) { return (i->str == result_i->str); })) + result.push_back(i); + } + } + return result; + } + + std::size_t unique_fragment_index(const std::vector& unique_fragments, + const std::string& str, + std::size_t offset = 0) const noexcept + { + const auto b = cbegin(unique_fragments); + const auto e = cend(unique_fragments); + const auto i = find_if(b, e, [&str](const auto& pi) { return (pi->str == str); }); + return offset + (i - b); + }; + + std::size_t named_parameter_index__(const std::string& name) const + { + return unique_fragment_index(named_parameters_, name, positional_parameter_count()); + } + + std::vector named_parameters() const + { + return unique_fragments(Fragment::Type::named_parameter); + } + + // --------------------------------------------------------------------------- + // Predicates + // --------------------------------------------------------------------------- + + static bool is_space(const char c) + { + return std::isspace(c, std::locale{}); + } + + static bool is_blank_string(const std::string& str) + { + return std::all_of(cbegin(str), cend(str), is_space); + }; + + /** + * @return `true` if the given fragment is a comment. + */ + static bool is_comment(const Fragment& f) + { + return (f.type == Fragment::Type::one_line_comment || f.type == Fragment::Type::multi_line_comment); + }; + + /** + * @return `true` if the given fragment is a text. + */ + static bool is_text(const Fragment& f) + { + return (f.type == Fragment::Type::text); + } + + // --------------------------------------------------------------------------- + // Extra data + // --------------------------------------------------------------------------- + + /** + * @internal + * + * @brief Represents an API for extraction the extra data from the comments. + */ + struct Extra final { + public: + /** Denotes the key type of the associated data. */ + using Key = std::string; + + /** Denotes the value type of the associated data. */ + using Value = std::unique_ptr; + + /** Denotes the fragment type. */ + using Fragment = iSql_string::Fragment; + + /** Denotes the fragment list type. */ + using Fragment_list = iSql_string::Fragment_list; + + /** + * @returns The vector of associated extra data. + */ + static std::vector> extract(const Fragment_list& fragments) + { + std::vector> result; + const auto iters = first_related_comments(fragments); + if (iters.first != cend(fragments)) { + const auto comments = joined_comments(iters.first, iters.second); + for (const auto& comment : comments) { + auto associations = extract(comment.first, comment.second); + result.reserve(result.capacity() + associations.size()); + for (auto& a : associations) + result.push_back(std::move(a)); + } + } + return result; + } + + private: + /** + * Represents a comment type. + */ + enum class Comment_type { + /** Denotes one line comment */ + one_line, + + /** Denotes multi line comment */ + multi_line + }; + + /** + * @brief Extracts the associated data from dollar quoted literals found in comments. + * + * @returns Extracted data as key/value pairs. + * + * @param input - the input string with comments. + * @param comment_type - the type of comments in the `input`. + */ + static std::vector> extract(const std::string& input, const Comment_type comment_type) + { + enum { top, dollar, dollar_quote_leading_tag, dollar_quote, dollar_quote_dollar } state = top; + + std::vector> result; + std::string content; + std::string dollar_quote_leading_tag_name; + std::string dollar_quote_trailing_tag_name; + + const auto is_valid_tag_char = [](const char c) + { + return std::isalnum(c, std::locale{}) || c == '_' || c == '-'; + }; + + for (const auto current_char : input) { + switch (state) { + case top: + if (current_char == '$') + state = dollar; + continue; + case dollar: + if (is_valid_tag_char(current_char)) { + state = dollar_quote_leading_tag; + dollar_quote_leading_tag_name += current_char; + } + continue; + case dollar_quote_leading_tag: + if (current_char == '$') { + state = dollar_quote; + } else if (is_valid_tag_char(current_char)) { + dollar_quote_leading_tag_name += current_char; + } else + throw std::runtime_error("invalid dollar quote tag"); + continue; + case dollar_quote: + if (current_char == '$') + state = dollar_quote_dollar; + else + content += current_char; + continue; + case dollar_quote_dollar: + if (current_char == '$') { + if (dollar_quote_leading_tag_name == dollar_quote_trailing_tag_name) { + /* + * Okay, the tag's name and content are successfully extracted. + * Now attempt to clean up the content before adding it to the result. + */ + state = top; + result.emplace_back(std::move(dollar_quote_leading_tag_name), + Data::make(cleaned_content(std::move(content), comment_type), Data_format::text)); + content = std::string{}; + dollar_quote_leading_tag_name = std::string{}; + } else + state = dollar_quote; + + dollar_quote_trailing_tag_name.clear(); + } else + dollar_quote_trailing_tag_name += current_char; + continue; + } + } + + if (state != top) + throw std::runtime_error("invalid comment block:\n" + input); + + return result; + } + + /** + * @brief Scans the extra data content to determine the indent size. + * + * @returns The number of characters to remove after each '\n'. + */ + static std::size_t indent_size(const std::string& content, const Comment_type comment_type) + { + const auto set_if_less = [](auto& variable, const auto count) + { + if (!variable) + variable.emplace(count); + else if (count < variable) + variable = count; + }; + + enum { counting, after_asterisk, after_non_asterisk, skiping } state = counting; + std::optional min_indent_to_border{}; + std::optional min_indent_to_content{}; + std::size_t count{}; + for (const auto current_char : content) { + switch (state) { + case counting: + if (current_char == '\n') + count = 0; + else if (current_char == '*') + state = after_asterisk; + else if (std::isspace(current_char, std::locale{})) + ++count; + else + state = after_non_asterisk; + continue; + case after_asterisk: + if (current_char == ' ') { + if (min_indent_to_border) { + if (count < *min_indent_to_border) { + set_if_less(min_indent_to_content, *min_indent_to_border); + min_indent_to_border = count; + } else if (count == *min_indent_to_border + 1) + set_if_less(min_indent_to_content, count); + } else + min_indent_to_border.emplace(count); + } else + set_if_less(min_indent_to_content, count); + state = skiping; + continue; + case after_non_asterisk: + set_if_less(min_indent_to_content, count); + state = skiping; + continue; + case skiping: + if (current_char == '\n') { + count = 0; + state = counting; + } + continue; + } + } + + // Calculating the result depending on the comment type. + switch (comment_type) { + case Comment_type::multi_line: + if (min_indent_to_border) { + if (min_indent_to_content) { + if (min_indent_to_content <= min_indent_to_border) + return 0; + else if (min_indent_to_content == *min_indent_to_border + 1) + return *min_indent_to_content; + } + return *min_indent_to_border + 1 + 1; + } else + return 0; + case Comment_type::one_line: + return min_indent_to_content ? (*min_indent_to_content == 0 ? 0 : 1) : 1; + } + + DMITIGR_ASSERT_ALWAYS(!true); + } + + /** + * @brief Cleans up the extra data content. + * + * Cleaning up includes: + * - removing the indentation characters; + * - trimming most leading and/or most trailing newline characters (for multiline comments only). + */ + static std::string cleaned_content(std::string&& content, const Comment_type comment_type) + { + std::string result; + + // Removing the indentation characters (if any). + if (const std::size_t isize = indent_size(content, comment_type); isize > 0) { + std::size_t count{}; + enum { eating, skiping } state = eating; + for (const auto current_char : content) { + switch (state) { + case eating: + if (current_char == '\n') { + count = isize; + state = skiping; + } + result += current_char; + continue; + case skiping: + if (count > 1) + --count; + else + state = eating; + continue; + } + } + std::string{}.swap(content); + } else + result.swap(content); + + // Trimming the result string: remove the most leading and the most trailing newline-characters. + if (const auto size = result.size(); size > 0) { + std::string::size_type start{}; + if (result[start] == '\r') + ++start; + if (start < size && result[start] == '\n') + ++start; + + std::string::size_type end{size}; + if (start < end && result[end - 1] == '\n') + --end; + if (start < end && result[end - 1] == '\r') + --end; + + if (start > 0 || end < size) + result = result.substr(start, end - start); + } + + return result; + } + + // ------------------------------------------------------------------------- + // Related comments extraction + // ------------------------------------------------------------------------- + + /** + * @brief Finds very first relevant comments of the specified fragments. + * + * @returns The pair of iterators that specifies the range of relevant comments. + */ + std::pair + static first_related_comments(const Fragment_list& fragments) + { + const auto b = cbegin(fragments); + const auto e = cend(fragments); + auto result = std::make_pair(e, e); + + const auto is_nearby_string = [&](const std::string& str) + { + std::string::size_type count{}; + for (const auto c : str) { + if (c == '\n') { + ++count; + if (count > 1) + return false; + } else if (!is_space(c)) + break; + } + return true; + }; + + /* An attempt to find the first commented out text fragment. + * Stops lookup when either named parameter or positional parameter are found. + * (Only fragments of type `text` can have related comments.) + */ + auto i = std::find_if(b, e, [&](const Fragment& f) + { + return (f.type == Fragment::Type::text && is_nearby_string(f.str) && !is_blank_string(f.str)) || + f.type == Fragment::Type::named_parameter || + f.type == Fragment::Type::positional_parameter; + }); + if (i != b && i != e && is_text(*i)) { + result.second = i; + do { + --i; + DMITIGR_ASSERT(is_comment(*i) || (is_text(*i) && is_blank_string(i->str))); + if (i->type == Fragment::Type::text) { + if (!is_nearby_string(i->str)) + break; + } + result.first = i; + } while (i != b); + } + + return result; + } + + /** + * @brief Joins first comments of the same type into the result string. + * + * @returns The pair of: + * - the pair of the result string (comment) and its type; + * - the iterator that points to the fragment that follows the last comment + * appended to the result. + */ + std::pair, Fragment_list::const_iterator> + static joined_comments_of_same_type(Fragment_list::const_iterator i, const Fragment_list::const_iterator e) + { + DMITIGR_ASSERT(is_comment(*i)); + std::string result; + const auto fragment_type = i->type; + for (; i->type == fragment_type && i != e; ++i) { + result.append(i->str); + if (fragment_type == Fragment::Type::one_line_comment) + result.append("\n"); + } + const auto comment_type = [](const Fragment::Type ft) + { + switch (ft) { + case Fragment::Type::one_line_comment: return Extra::Comment_type::one_line; + case Fragment::Type::multi_line_comment: return Extra::Comment_type::multi_line; + default: DMITIGR_ASSERT_ALWAYS(!true); + } + }; + return std::make_pair(std::make_pair(result, comment_type(fragment_type)), i); + } + + /** + * @brief Joins all comments into the vector of strings. + * + * @returns The vector of pairs of: + * - the joined comments as first element; + * - the type of the joined comments as second element. + */ + std::vector> + static joined_comments(Fragment_list::const_iterator i, const Fragment_list::const_iterator e) + { + std::vector> result; + while (i != e) { + if (is_comment(*i)) { + auto comments = joined_comments_of_same_type(i, e); + result.push_back(std::move(comments.first)); + i = comments.second; + } else + ++i; + } + return result; + } + }; + + Fragment_list fragments_; + std::vector positional_parameters_; // cache + std::vector named_parameters_; // cache + mutable bool is_extra_data_should_be_extracted_from_comments_{true}; + mutable std::optional extra_; // cache +}; + +} // namespace dmitigr::pgfe::detail + +// ============================================================================= + +// ----------------------------------------------------------------------------- +// Very basic SQL input parser +// ----------------------------------------------------------------------------- + +/* + * SQL SYNTAX BASICS (from PostgreSQL documentation): + * http://www.postgresql.org/docs/10/static/sql-syntax-lexical.html + * + * COMMANDS + * + * A command is composed of a sequence of tokens, terminated by a (";"). + * A token can be a key word, an identifier, a quoted identifier, + * a literal (or constant), or a special character symbol. Tokens are normally + * separated by whitespace (space, tab, newline), but need not be if there is no + * ambiguity. + * + * IDENTIFIERS (UNQUOTED) + * + * SQL identifiers and key words must begin with a letter (a-z, but also + * letters with diacritical marks and non-Latin letters) or an ("_"). + * Subsequent characters in an identifier or key word can be letters, + * underscores, digits (0-9), or dollar signs ($). + * + * QUOTED IDENTIFIERS + * + * The delimited identifier or quoted identifier is formed by enclosing an + * arbitrary sequence of characters in double-quotes ("). Quoted identifiers can + * contain any character, except the character with code zero. (To include a + * double quote, two double quotes should be written.) + * + * CONSTANTS + * + * STRING CONSTANTS (QUOTED LITERALS) + * + * A string constant in SQL is an arbitrary sequence of characters bounded + * by single quotes ('), for example 'This is a string'. To include a + * single-quote character within a string constant, write two adjacent + * single quotes, e.g., 'Dianne''s horse'. + * + * DOLLAR QUOTED STRING CONSTANTS + * + * A dollar-quoted string constant consists of a dollar sign ($), an + * optional "tag" of zero or more characters, another dollar sign, an + * arbitrary sequence of characters that makes up the string content, a + * dollar sign, the same tag that began this dollar quote, and a dollar + * sign. + * The tag, if any, of a dollar-quoted string follows the same rules + * as an unquoted identifier, except that it cannot contain a dollar sign. + * A dollar-quoted string that follows a keyword or identifier must be + * separated from it by whitespace; otherwise the dollar quoting delimiter + * would be taken as part of the preceding identifier. + * + * SPECIAL CHARACTERS + * + * - A dollar sign ("$") followed by digits is used to represent a positional + * parameter in the body of a function definition or a prepared statement. + * In other contexts the dollar sign can be part of an identifier or a + * dollar-quoted string constant. + * + * - The colon (":") is used to select "slices" from arrays. In certain SQL + * dialects (such as Embedded SQL), the colon is used to prefix variable + * names. + * [In Pgfe we will use ":" to prefix named parameters and placeholders.] + * + * - Brackets ([]) are used to select the elements of an array. + */ + +namespace dmitigr::pgfe::detail { + +namespace { + +/** + * @internal + * + * @returns `true` if `c` is a valid character of unquoted SQL identifier. + */ +inline bool is_ident_char(const char c) noexcept +{ + return (std::isalnum(c, std::locale{}) || c == '_' || c == '$'); +} + +} // namespace + +std::pair parse_sql_input(const char* text) +{ + DMITIGR_ASSERT(text); + + enum { + top, + + bracket, + + colon, + named_parameter, + + dollar, + positional_parameter, + dollar_quote_leading_tag, + dollar_quote, + dollar_quote_dollar, + + quote, + quote_quote, + + dash, + one_line_comment, + + slash, + multi_line_comment, + multi_line_comment_star + } state = top; + + iSql_string result; + int depth{}; + char current_char{*text}; + char previous_char{}; + char quote_char{}; + std::string fragment; + std::string dollar_quote_leading_tag_name; + std::string dollar_quote_trailing_tag_name; + for (; current_char; previous_char = current_char, current_char = *++text) { + switch (state) { + case top: + switch (current_char) { + case '\'': + state = quote; + quote_char = current_char; + fragment += current_char; + continue; + + case '"': + state = quote; + quote_char = current_char; + fragment += current_char; + continue; + + case '[': + state = bracket; + depth = 1; + fragment += current_char; + continue; + + case '$': + if (!is_ident_char(previous_char)) + state = dollar; + else + fragment += current_char; + + continue; + + case ':': + if (previous_char != ':') + state = colon; + else + fragment += current_char; + + continue; + + case '-': + state = dash; + continue; + + case '/': + state = slash; + continue; + + case ';': + goto finish; + + default: + fragment += current_char; + continue; + } // switch (current_char) + + case bracket: + if (current_char == ']') + --depth; + else if (current_char == '[') + ++depth; + + if (depth == 0) { + DMITIGR_ASSERT(current_char == ']'); + state = top; + } + + fragment += current_char; + continue; + + case dollar: + DMITIGR_ASSERT(previous_char == '$'); + if (std::isdigit(current_char, std::locale{})) { + state = positional_parameter; + result.push_text(fragment); + fragment.clear(); + // The first digit of positional parameter (current_char) will be stored below. + } else if (is_ident_char(current_char)) { + if (current_char == '$') { + state = dollar_quote; + } else { + state = dollar_quote_leading_tag; + dollar_quote_leading_tag_name += current_char; + } + fragment += previous_char; + } else { + state = top; + fragment += previous_char; + } + + fragment += current_char; + continue; + + case positional_parameter: + DMITIGR_ASSERT(std::isdigit(previous_char, std::locale{})); + if (!std::isdigit(current_char, std::locale{})) { + state = top; + result.push_positional_parameter(fragment); + fragment.clear(); + } + + if (current_char != ';') { + fragment += current_char; + continue; + } else + goto finish; + + case dollar_quote_leading_tag: + DMITIGR_ASSERT(previous_char != '$' && is_ident_char(previous_char)); + if (current_char == '$') { + state = dollar_quote; + } else if (is_ident_char(current_char)) { + dollar_quote_leading_tag_name += current_char; + fragment += current_char; + } else + throw std::runtime_error("invalid dollar quote tag"); + + continue; + + case dollar_quote: + if (current_char == '$') + state = dollar_quote_dollar; + + fragment += current_char; + continue; + + case dollar_quote_dollar: + if (current_char == '$') { + if (dollar_quote_leading_tag_name == dollar_quote_trailing_tag_name) { + state = top; + dollar_quote_leading_tag_name.clear(); + } else + state = dollar_quote; + + dollar_quote_trailing_tag_name.clear(); + } else + dollar_quote_trailing_tag_name += current_char; + + fragment += current_char; + continue; + + case colon: + DMITIGR_ASSERT(previous_char == ':'); + if (is_ident_char(current_char)) { + state = named_parameter; + result.push_text(fragment); + fragment.clear(); + // The first character of the named parameter (current_char) will be stored below. + } else { + state = top; + fragment += previous_char; + } + + if (current_char != ';') { + fragment += current_char; + continue; + } else + goto finish; + + case named_parameter: + DMITIGR_ASSERT(is_ident_char(previous_char)); + if (!is_ident_char(current_char)) { + state = top; + result.push_named_parameter(fragment); + fragment.clear(); + } + + if (current_char != ';') { + fragment += current_char; + continue; + } else + goto finish; + + case quote: + if (current_char == quote_char) + state = quote_quote; + else + fragment += current_char; + + continue; + + case quote_quote: + DMITIGR_ASSERT(previous_char == quote_char); + if (current_char == quote_char) { + state = quote; + // Skip previous quote. + } else { + state = top; + fragment += previous_char; // store previous quote + } + + if (current_char != ';') { + fragment += current_char; + continue; + } else + goto finish; + + case dash: + DMITIGR_ASSERT(previous_char == '-'); + if (current_char == '-') { + state = one_line_comment; + result.push_text(fragment); + fragment.clear(); + // The comment marker ("--") will not be included in the next fragment. + } else { + state = top; + fragment += previous_char; + + if (current_char != ';') { + fragment += current_char; + continue; + } else + goto finish; + } + + continue; + + case one_line_comment: + if (current_char == '\n') { + state = top; + if (!fragment.empty() && fragment.back() == '\r') + fragment.pop_back(); + result.push_one_line_comment(fragment); + fragment.clear(); + } else + fragment += current_char; + + continue; + + case slash: + DMITIGR_ASSERT(previous_char == '/'); + if (current_char == '*') { + state = multi_line_comment; + if (depth > 0) { + fragment += previous_char; + fragment += current_char; + } else { + result.push_text(fragment); + fragment.clear(); + // The comment marker ("/*") will not be included in the next fragment. + } + ++depth; + } else { + state = (depth == 0) ? top : multi_line_comment; + fragment += previous_char; + fragment += current_char; + } + + continue; + + case multi_line_comment: + if (current_char == '/') { + state = slash; + } else if (current_char == '*') { + state = multi_line_comment_star; + } else + fragment += current_char; + + continue; + + case multi_line_comment_star: + DMITIGR_ASSERT(previous_char == '*'); + if (current_char == '/') { + --depth; + if (depth == 0) { + state = top; + result.push_multi_line_comment(fragment); // without trailing "*/" + fragment.clear(); + } else { + state = multi_line_comment; + fragment += previous_char; // '*' + fragment += current_char; // '/' + } + } else { + state = multi_line_comment; + fragment += previous_char; + fragment += current_char; + } + + continue; + } // switch (state) + } // for + + finish: + switch (state) { + case top: + if (current_char == ';') + ++text; + if (!fragment.empty()) + result.push_text(fragment); + break; + case quote_quote: + fragment += previous_char; + result.push_text(fragment); + break; + case one_line_comment: + result.push_one_line_comment(fragment); + break; + case positional_parameter: + result.push_positional_parameter(fragment); + break; + case named_parameter: + result.push_named_parameter(fragment); + break; + default: + throw std::runtime_error("invalid SQL input"); + } + + return std::make_pair(result, text); +} + +} // namespace dmitigr::pgfe::detail + +// ============================================================================= namespace dmitigr::pgfe { -DMITIGR_PGFE_API std::unique_ptr Sql_string::make(const std::string& input) +DMITIGR_PGFE_INLINE std::unique_ptr Sql_string::make(const std::string& input) { return std::make_unique(input); } } // namespace dmitigr::pgfe + +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/sql_string.cxx b/lib/dmitigr/pgfe/sql_string.cxx deleted file mode 100644 index da08070..0000000 --- a/lib/dmitigr/pgfe/sql_string.cxx +++ /dev/null @@ -1,432 +0,0 @@ -// -*- C++ -*- -// Copyright (C) Dmitry Igrishin -// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp - -#include "dmitigr/pgfe/sql_string.hxx" - -// ----------------------------------------------------------------------------- -// Very basic SQL input parser -// ----------------------------------------------------------------------------- - -/* - * SQL SYNTAX BASICS (from PostgreSQL documentation): - * http://www.postgresql.org/docs/10/static/sql-syntax-lexical.html - * - * COMMANDS - * - * A command is composed of a sequence of tokens, terminated by a (";"). - * A token can be a key word, an identifier, a quoted identifier, - * a literal (or constant), or a special character symbol. Tokens are normally - * separated by whitespace (space, tab, newline), but need not be if there is no - * ambiguity. - * - * IDENTIFIERS (UNQUOTED) - * - * SQL identifiers and key words must begin with a letter (a-z, but also - * letters with diacritical marks and non-Latin letters) or an ("_"). - * Subsequent characters in an identifier or key word can be letters, - * underscores, digits (0-9), or dollar signs ($). - * - * QUOTED IDENTIFIERS - * - * The delimited identifier or quoted identifier is formed by enclosing an - * arbitrary sequence of characters in double-quotes ("). Quoted identifiers can - * contain any character, except the character with code zero. (To include a - * double quote, two double quotes should be written.) - * - * CONSTANTS - * - * STRING CONSTANTS (QUOTED LITERALS) - * - * A string constant in SQL is an arbitrary sequence of characters bounded - * by single quotes ('), for example 'This is a string'. To include a - * single-quote character within a string constant, write two adjacent - * single quotes, e.g., 'Dianne''s horse'. - * - * DOLLAR QUOTED STRING CONSTANTS - * - * A dollar-quoted string constant consists of a dollar sign ($), an - * optional "tag" of zero or more characters, another dollar sign, an - * arbitrary sequence of characters that makes up the string content, a - * dollar sign, the same tag that began this dollar quote, and a dollar - * sign. - * The tag, if any, of a dollar-quoted string follows the same rules - * as an unquoted identifier, except that it cannot contain a dollar sign. - * A dollar-quoted string that follows a keyword or identifier must be - * separated from it by whitespace; otherwise the dollar quoting delimiter - * would be taken as part of the preceding identifier. - * - * SPECIAL CHARACTERS - * - * - A dollar sign ("$") followed by digits is used to represent a positional - * parameter in the body of a function definition or a prepared statement. - * In other contexts the dollar sign can be part of an identifier or a - * dollar-quoted string constant. - * - * - The colon (":") is used to select "slices" from arrays. In certain SQL - * dialects (such as Embedded SQL), the colon is used to prefix variable - * names. - * [In Pgfe we will use ":" to prefix named parameters and placeholders.] - * - * - Brackets ([]) are used to select the elements of an array. - */ - -namespace { - -/** - * @internal - * - * @returns `true` if `c` is a valid character of unquoted SQL identifier. - */ -inline bool is_ident_char(const char c) noexcept -{ - return (std::isalnum(c, std::locale{}) || c == '_' || c == '$'); -} - -} // namespace - -auto dmitigr::pgfe::detail::parse_sql_input(const char* text) -> std::pair -{ - DMITIGR_ASSERT(text); - - enum { - top, - - bracket, - - colon, - named_parameter, - - dollar, - positional_parameter, - dollar_quote_leading_tag, - dollar_quote, - dollar_quote_dollar, - - quote, - quote_quote, - - dash, - one_line_comment, - - slash, - multi_line_comment, - multi_line_comment_star - } state = top; - - iSql_string result; - int depth{}; - char current_char{*text}; - char previous_char{}; - char quote_char{}; - std::string fragment; - std::string dollar_quote_leading_tag_name; - std::string dollar_quote_trailing_tag_name; - for (; current_char; previous_char = current_char, current_char = *++text) { - switch (state) { - case top: - switch (current_char) { - case '\'': - state = quote; - quote_char = current_char; - fragment += current_char; - continue; - - case '"': - state = quote; - quote_char = current_char; - fragment += current_char; - continue; - - case '[': - state = bracket; - depth = 1; - fragment += current_char; - continue; - - case '$': - if (!is_ident_char(previous_char)) - state = dollar; - else - fragment += current_char; - - continue; - - case ':': - if (previous_char != ':') - state = colon; - else - fragment += current_char; - - continue; - - case '-': - state = dash; - continue; - - case '/': - state = slash; - continue; - - case ';': - goto finish; - - default: - fragment += current_char; - continue; - } // switch (current_char) - - case bracket: - if (current_char == ']') - --depth; - else if (current_char == '[') - ++depth; - - if (depth == 0) { - DMITIGR_ASSERT(current_char == ']'); - state = top; - } - - fragment += current_char; - continue; - - case dollar: - DMITIGR_ASSERT(previous_char == '$'); - if (std::isdigit(current_char, std::locale{})) { - state = positional_parameter; - result.push_text(fragment); - fragment.clear(); - // The first digit of positional parameter (current_char) will be stored below. - } else if (is_ident_char(current_char)) { - if (current_char == '$') { - state = dollar_quote; - } else { - state = dollar_quote_leading_tag; - dollar_quote_leading_tag_name += current_char; - } - fragment += previous_char; - } else { - state = top; - fragment += previous_char; - } - - fragment += current_char; - continue; - - case positional_parameter: - DMITIGR_ASSERT(std::isdigit(previous_char, std::locale{})); - if (!std::isdigit(current_char, std::locale{})) { - state = top; - result.push_positional_parameter(fragment); - fragment.clear(); - } - - if (current_char != ';') { - fragment += current_char; - continue; - } else - goto finish; - - case dollar_quote_leading_tag: - DMITIGR_ASSERT(previous_char != '$' && is_ident_char(previous_char)); - if (current_char == '$') { - state = dollar_quote; - } else if (is_ident_char(current_char)) { - dollar_quote_leading_tag_name += current_char; - fragment += current_char; - } else - throw std::runtime_error("invalid dollar quote tag"); - - continue; - - case dollar_quote: - if (current_char == '$') - state = dollar_quote_dollar; - - fragment += current_char; - continue; - - case dollar_quote_dollar: - if (current_char == '$') { - if (dollar_quote_leading_tag_name == dollar_quote_trailing_tag_name) { - state = top; - dollar_quote_leading_tag_name.clear(); - } else - state = dollar_quote; - - dollar_quote_trailing_tag_name.clear(); - } else - dollar_quote_trailing_tag_name += current_char; - - fragment += current_char; - continue; - - case colon: - DMITIGR_ASSERT(previous_char == ':'); - if (is_ident_char(current_char)) { - state = named_parameter; - result.push_text(fragment); - fragment.clear(); - // The first character of the named parameter (current_char) will be stored below. - } else { - state = top; - fragment += previous_char; - } - - if (current_char != ';') { - fragment += current_char; - continue; - } else - goto finish; - - case named_parameter: - DMITIGR_ASSERT(is_ident_char(previous_char)); - if (!is_ident_char(current_char)) { - state = top; - result.push_named_parameter(fragment); - fragment.clear(); - } - - if (current_char != ';') { - fragment += current_char; - continue; - } else - goto finish; - - case quote: - if (current_char == quote_char) - state = quote_quote; - else - fragment += current_char; - - continue; - - case quote_quote: - DMITIGR_ASSERT(previous_char == quote_char); - if (current_char == quote_char) { - state = quote; - // Skip previous quote. - } else { - state = top; - fragment += previous_char; // store previous quote - } - - if (current_char != ';') { - fragment += current_char; - continue; - } else - goto finish; - - case dash: - DMITIGR_ASSERT(previous_char == '-'); - if (current_char == '-') { - state = one_line_comment; - result.push_text(fragment); - fragment.clear(); - // The comment marker ("--") will not be included in the next fragment. - } else { - state = top; - fragment += previous_char; - - if (current_char != ';') { - fragment += current_char; - continue; - } else - goto finish; - } - - continue; - - case one_line_comment: - if (current_char == '\n') { - state = top; - if (!fragment.empty() && fragment.back() == '\r') - fragment.pop_back(); - result.push_one_line_comment(fragment); - fragment.clear(); - } else - fragment += current_char; - - continue; - - case slash: - DMITIGR_ASSERT(previous_char == '/'); - if (current_char == '*') { - state = multi_line_comment; - if (depth > 0) { - fragment += previous_char; - fragment += current_char; - } else { - result.push_text(fragment); - fragment.clear(); - // The comment marker ("/*") will not be included in the next fragment. - } - ++depth; - } else { - state = (depth == 0) ? top : multi_line_comment; - fragment += previous_char; - fragment += current_char; - } - - continue; - - case multi_line_comment: - if (current_char == '/') { - state = slash; - } else if (current_char == '*') { - state = multi_line_comment_star; - } else - fragment += current_char; - - continue; - - case multi_line_comment_star: - DMITIGR_ASSERT(previous_char == '*'); - if (current_char == '/') { - --depth; - if (depth == 0) { - state = top; - result.push_multi_line_comment(fragment); // without trailing "*/" - fragment.clear(); - } else { - state = multi_line_comment; - fragment += previous_char; // '*' - fragment += current_char; // '/' - } - } else { - state = multi_line_comment; - fragment += previous_char; - fragment += current_char; - } - - continue; - } // switch (state) - } // for - - finish: - switch (state) { - case top: - if (current_char == ';') - ++text; - if (!fragment.empty()) - result.push_text(fragment); - break; - case quote_quote: - fragment += previous_char; - result.push_text(fragment); - break; - case one_line_comment: - result.push_one_line_comment(fragment); - break; - case positional_parameter: - result.push_positional_parameter(fragment); - break; - case named_parameter: - result.push_named_parameter(fragment); - break; - default: - throw std::runtime_error("invalid SQL input"); - } - - return std::make_pair(result, text); -} diff --git a/lib/dmitigr/pgfe/sql_string.hpp b/lib/dmitigr/pgfe/sql_string.hpp index 72f15c4..85bb48e 100644 --- a/lib/dmitigr/pgfe/sql_string.hpp +++ b/lib/dmitigr/pgfe/sql_string.hpp @@ -254,4 +254,8 @@ class Sql_string : public Parameterizable { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/sql_string.cpp" +#endif + #endif // DMITIGR_PGFE_SQL_STRING_HPP diff --git a/lib/dmitigr/pgfe/sql_string.hxx b/lib/dmitigr/pgfe/sql_string.hxx deleted file mode 100644 index 411a754..0000000 --- a/lib/dmitigr/pgfe/sql_string.hxx +++ /dev/null @@ -1,910 +0,0 @@ -// -*- C++ -*- -// Copyright (C) Dmitry Igrishin -// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp - -#ifndef DMITIGR_PGFE_SQL_STRING_HXX -#define DMITIGR_PGFE_SQL_STRING_HXX - -#include "dmitigr/pgfe/composite.hxx" -#include "dmitigr/pgfe/data.hpp" -#include "dmitigr/pgfe/parameterizable.hxx" -#include "dmitigr/pgfe/sql_string.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace dmitigr::pgfe::detail { - -std::pair parse_sql_input(const char* text); - -class iSql_string : public Sql_string { -public: - iSql_string() - { - DMITIGR_ASSERT(is_invariant_ok()); - } - - iSql_string(const iSql_string& rhs) - : fragments_{rhs.fragments_} - , positional_parameters_{rhs.positional_parameters_} - { - named_parameters_ = named_parameters(); - } - - iSql_string& operator=(const iSql_string& rhs) - { - iSql_string tmp{rhs}; - swap(tmp); - return *this; - } - - iSql_string(iSql_string&& rhs) - : fragments_{std::move(rhs.fragments_)} - , positional_parameters_{std::move(rhs.positional_parameters_)} - { - named_parameters_ = named_parameters(); - } - - iSql_string& operator=(iSql_string&& rhs) - { - iSql_string tmp{std::move(rhs)}; - swap(tmp); - return *this; - } - - void swap(iSql_string& other) - { - fragments_.swap(other.fragments_); - positional_parameters_.swap(other.positional_parameters_); - named_parameters_.swap(other.named_parameters_); - } - - explicit iSql_string(const char* const text) - : iSql_string(parse_sql_input(text).first) - { - DMITIGR_ASSERT(is_invariant_ok()); - } - - explicit iSql_string(const std::string& text) - : iSql_string(text.c_str()) - { - DMITIGR_ASSERT(is_invariant_ok()); - } - - // --------------------------------------------------------------------------- - - std::size_t positional_parameter_count() const override - { - return positional_parameters_.size(); - } - - std::size_t named_parameter_count() const override - { - return named_parameters_.size(); - } - - std::size_t parameter_count() const override - { - return (positional_parameter_count() + named_parameter_count()); - } - - const std::string& parameter_name(const std::size_t index) const override - { - DMITIGR_ASSERT(positional_parameter_count() <= index && index < parameter_count()); - return (named_parameters_[index - positional_parameter_count()])->str; - } - - std::optional parameter_index(const std::string& name) const override - { - if (const auto i = named_parameter_index__(name); i < parameter_count()) - return i; - else - return std::nullopt; - } - - std::size_t parameter_index_throw(const std::string& name) const override - { - const auto i = named_parameter_index__(name); - DMITIGR_REQUIRE(i < parameter_count(), std::out_of_range); - return i; - } - - bool has_parameter(const std::string& name) const override - { - return bool(parameter_index(name)); - } - - bool has_positional_parameters() const override - { - return !positional_parameters_.empty(); - } - - bool has_named_parameters() const override - { - return !named_parameters_.empty(); - } - - bool has_parameters() const override - { - return (has_positional_parameters() || has_named_parameters()); - } - - // --------------------------------------------------------------------------- - - std::unique_ptr to_sql_string() const override - { - return std::make_unique(*this); - } - - bool is_empty() const noexcept override - { - return fragments_.empty(); - } - - bool is_query_empty() const noexcept override - { - return std::all_of(cbegin(fragments_), cend(fragments_), - [](const Fragment& f) - { - return is_comment(f) || (is_text(f) && is_blank_string(f.str)); - }); - } - - bool is_parameter_missing(const std::size_t index) const override - { - DMITIGR_REQUIRE(index < positional_parameter_count(), std::out_of_range); - return !positional_parameters_[index]; - } - - bool has_missing_parameters() const override - { - return any_of(cbegin(positional_parameters_), cend(positional_parameters_), [](const auto is_present) { return !is_present; }); - } - - void append(const Sql_string* const appendix) override - { - DMITIGR_REQUIRE(appendix, std::invalid_argument); - const auto* const iappendix = dynamic_cast(appendix); - DMITIGR_ASSERT_ALWAYS(iappendix); - - const bool was_query_empty = is_query_empty(); - - // Updating fragments - auto old_fragments = fragments_; - try { - fragments_.insert(cend(fragments_), cbegin(iappendix->fragments_), cend(iappendix->fragments_)); - update_cache(*iappendix); // can throw (strong exception safety guarantee) - - if (was_query_empty) - is_extra_data_should_be_extracted_from_comments_ = true; - } catch (...) { - fragments_.swap(old_fragments); // rollback - throw; - } - - DMITIGR_ASSERT(is_invariant_ok()); - } - - void append(const std::string& appendix) override - { - iSql_string a(appendix); - append(&a); // includes invariant check - } - - void replace_parameter(const std::string& name, const Sql_string* const replacement) override - { - DMITIGR_REQUIRE(has_parameter(name), std::out_of_range); - DMITIGR_REQUIRE(replacement, std::invalid_argument); - const auto* const ireplacement = dynamic_cast(replacement); - DMITIGR_ASSERT_ALWAYS(ireplacement); - - // Updating fragments - auto old_fragments = fragments_; - try { - for (auto fi = begin(fragments_); fi != end(fragments_);) { - if (fi->type == Fragment::Type::named_parameter && fi->str == name) { - // Firstly, we'll insert the `replacement` just before `fi`. - fragments_.insert(fi, cbegin(ireplacement->fragments_), cend(ireplacement->fragments_)); - // Secondly, we'll erase named parameter pointed by `fi` and got the next iterator. - fi = fragments_.erase(fi); - } else - ++fi; - } - - update_cache(*ireplacement); // can throw (strong exception safety guarantee) - } catch (...) { - fragments_.swap(old_fragments); - throw; - } - - DMITIGR_ASSERT(is_invariant_ok()); - } - - void replace_parameter(const std::string& name, const std::string& replacement) override - { - iSql_string r(replacement); - replace_parameter(name, &r); // includes invariant check - } - - std::string to_string() const override - { - std::string result; - result.reserve(512); - for (const auto& fragment : fragments_) { - switch (fragment.type) { - case Fragment::Type::text: - result += fragment.str; - break; - case Fragment::Type::one_line_comment: - result += "--"; - result += fragment.str; - result += '\n'; - break; - case Fragment::Type::multi_line_comment: - result += "/*"; - result += fragment.str; - result += "*/"; - break; - case Fragment::Type::named_parameter: - result += ':'; - result += fragment.str; - break; - case Fragment::Type::positional_parameter: - result += '$'; - result += fragment.str; - break; - } - } - result.shrink_to_fit(); - return result; - } - - // Returns: output query string. - std::string to_query_string() const override - { - std::string result; - result.reserve(512); - for (const auto& fragment : fragments_) { - switch (fragment.type) { - case Fragment::Type::text: - result += fragment.str; - break; - case Fragment::Type::one_line_comment: - case Fragment::Type::multi_line_comment: - break; - case Fragment::Type::named_parameter: { - const auto idx = named_parameter_index__(fragment.str); - DMITIGR_ASSERT(idx < parameter_count()); - result += '$'; - result += std::to_string(idx + 1); - break; - } - case Fragment::Type::positional_parameter: - result += '$'; - result += fragment.str; - break; - } - } - return result; - } - - Composite* extra() override - { - return const_cast(static_cast(this)->extra()); - } - - const Composite* extra() const override - { - if (!extra_) - extra_.emplace(Extra::extract(fragments_)); - else if (is_extra_data_should_be_extracted_from_comments_) - extra_->append(heap_data_Composite(Extra::extract(fragments_))); - is_extra_data_should_be_extracted_from_comments_ = false; - DMITIGR_ASSERT(is_invariant_ok()); - return &*extra_; - } - -protected: - bool is_invariant_ok() const - { - const bool positional_parameters_ok = ((positional_parameter_count() > 0) == has_positional_parameters()); - const bool named_parameters_ok = ((named_parameter_count() > 0) == has_named_parameters()); - const bool parameters_ok = ((parameter_count() > 0) == has_parameters()); - const bool parameters_count_ok = (parameter_count() == (positional_parameter_count() + named_parameter_count())); - const bool empty_ok = !is_empty() || !has_parameters(); - const bool extra_ok = is_extra_data_should_be_extracted_from_comments_ || extra_; - const bool parameterizable_ok = detail::is_invariant_ok(*this); - - return - positional_parameters_ok && - named_parameters_ok && - parameters_ok && - parameters_count_ok && - empty_ok && - extra_ok && - parameterizable_ok; - } - -private: - friend std::pair parse_sql_input(const char*); - - constexpr static std::size_t maximum_parameter_count_{65536}; - - struct Fragment { - enum class Type { - text, - one_line_comment, - multi_line_comment, - named_parameter, - positional_parameter - }; - - Fragment(const Type tp, const std::string& s) - : type(tp) - , str(s) - {} - - Type type; - std::string str; - }; - using Fragment_list = std::list; - - // --------------------------------------------------------------------------- - // Initializers - // --------------------------------------------------------------------------- - - void push_back_fragment__(Fragment::Type type, const std::string& str) - { - fragments_.emplace_back(type, str); - // The invariant should be checked by the caller. - } - - void push_text(const std::string& str) - { - push_back_fragment__(Fragment::Type::text, str); - DMITIGR_ASSERT(is_invariant_ok()); - } - - void push_one_line_comment(const std::string& str) - { - push_back_fragment__(Fragment::Type::one_line_comment, str); - DMITIGR_ASSERT(is_invariant_ok()); - } - - void push_multi_line_comment(const std::string& str) - { - push_back_fragment__(Fragment::Type::multi_line_comment, str); - DMITIGR_ASSERT(is_invariant_ok()); - } - - void push_positional_parameter(const std::string& str) - { - push_back_fragment__(Fragment::Type::positional_parameter, str); - - using Size = std::vector::size_type; - const decltype (maximum_parameter_count_) position = stoi(str); - if (position < 1 || position > maximum_parameter_count_ - 1) - throw std::runtime_error("invalid parameter position \"" + str + "\""); - else if (Size(position) > positional_parameters_.size()) - positional_parameters_.resize(position, false); - - positional_parameters_[Size(position) - 1] = true; // set parameter presence flag - DMITIGR_ASSERT(is_invariant_ok()); - } - - void push_named_parameter(const std::string& str) - { - if (parameter_count() < maximum_parameter_count_) { - push_back_fragment__(Fragment::Type::named_parameter, str); - if (none_of(cbegin(named_parameters_), cend(named_parameters_), [&str](const auto& i) { return (i->str == str); })) { - auto e = cend(fragments_); - --e; - named_parameters_.push_back(e); - } - } else - throw std::runtime_error("maximum parameters count (" + std::to_string(maximum_parameter_count_) + ") exceeded"); - - DMITIGR_ASSERT(is_invariant_ok()); - } - - // --------------------------------------------------------------------------- - // Updaters - // --------------------------------------------------------------------------- - - // Exception safety guarantee: strong. - void update_cache(const iSql_string& rhs) - { - // Preparing for merge positional parameters. - const auto old_pos_params_size = positional_parameters_.size(); - const auto rhs_pos_params_size = rhs.positional_parameters_.size(); - if (old_pos_params_size < rhs_pos_params_size) - positional_parameters_.resize(rhs_pos_params_size); // can throw - - try { - const auto new_pos_params_size = positional_parameters_.size(); - DMITIGR_ASSERT(new_pos_params_size >= rhs_pos_params_size); - - // Creating the cache for named parameters. - decltype (named_parameters_) new_named_parameters = named_parameters(); // can throw - - // Check the new parameter count. - const auto new_parameter_count = new_pos_params_size + new_named_parameters.size(); - if (new_parameter_count > maximum_parameter_count_) - throw std::runtime_error("parameter count (" + std::to_string(new_parameter_count) + ") " - "exceeds the maximum (" + std::to_string(maximum_parameter_count_) + ")"); - - // Merging positional parameters (cannot throw). - using Counter = std::remove_const_t; - for (Counter i = 0; i < rhs_pos_params_size; ++i) { - if (!positional_parameters_[i] && rhs.positional_parameters_[i]) - positional_parameters_[i] = true; - } - - named_parameters_.swap(new_named_parameters); // commit (cannot throw) - } catch (...) { - positional_parameters_.resize(old_pos_params_size); // rollback - throw; - } - - DMITIGR_ASSERT(is_invariant_ok()); - } - - // --------------------------------------------------------------------------- - // Generators - // --------------------------------------------------------------------------- - - std::vector unique_fragments(const Fragment::Type type) const - { - std::vector result; - result.reserve(8); - const auto e = cend(fragments_); - for (auto i = cbegin(fragments_); i != e; ++i) { - if (i->type == type) { - if (none_of(cbegin(result), cend(result), [&i](const auto& result_i) { return (i->str == result_i->str); })) - result.push_back(i); - } - } - return result; - } - - std::size_t unique_fragment_index(const std::vector& unique_fragments, - const std::string& str, - std::size_t offset = 0) const noexcept - { - const auto b = cbegin(unique_fragments); - const auto e = cend(unique_fragments); - const auto i = find_if(b, e, [&str](const auto& pi) { return (pi->str == str); }); - return offset + (i - b); - }; - - std::size_t named_parameter_index__(const std::string& name) const - { - return unique_fragment_index(named_parameters_, name, positional_parameter_count()); - } - - std::vector named_parameters() const - { - return unique_fragments(Fragment::Type::named_parameter); - } - - // --------------------------------------------------------------------------- - // Predicates - // --------------------------------------------------------------------------- - - static bool is_space(const char c) - { - return std::isspace(c, std::locale{}); - } - - static bool is_blank_string(const std::string& str) - { - return std::all_of(cbegin(str), cend(str), is_space); - }; - - /** - * @return `true` if the given fragment is a comment. - */ - static bool is_comment(const Fragment& f) - { - return (f.type == Fragment::Type::one_line_comment || f.type == Fragment::Type::multi_line_comment); - }; - - /** - * @return `true` if the given fragment is a text. - */ - static bool is_text(const Fragment& f) - { - return (f.type == Fragment::Type::text); - } - - // --------------------------------------------------------------------------- - // Extra data - // --------------------------------------------------------------------------- - - /** - * @internal - * - * @brief Represents an API for extraction the extra data from the comments. - */ - struct Extra { - public: - /** Denotes the key type of the associated data. */ - using Key = std::string; - - /** Denotes the value type of the associated data. */ - using Value = std::unique_ptr; - - /** Denotes the fragment type. */ - using Fragment = iSql_string::Fragment; - - /** Denotes the fragment list type. */ - using Fragment_list = iSql_string::Fragment_list; - - /** - * @returns The vector of associated extra data. - */ - static std::vector> extract(const Fragment_list& fragments) - { - std::vector> result; - const auto iters = first_related_comments(fragments); - if (iters.first != cend(fragments)) { - const auto comments = joined_comments(iters.first, iters.second); - for (const auto& comment : comments) { - auto associations = extract(comment.first, comment.second); - result.reserve(result.capacity() + associations.size()); - for (auto& a : associations) - result.push_back(std::move(a)); - } - } - return result; - } - - private: - /** - * Represents a comment type. - */ - enum class Comment_type { - /** Denotes one line comment */ - one_line, - - /** Denotes multi line comment */ - multi_line - }; - - /** - * @brief Extracts the associated data from dollar quoted literals found in comments. - * - * @returns Extracted data as key/value pairs. - * - * @param input - the input string with comments. - * @param comment_type - the type of comments in the `input`. - */ - static std::vector> extract(const std::string& input, const Comment_type comment_type) - { - enum { top, dollar, dollar_quote_leading_tag, dollar_quote, dollar_quote_dollar } state = top; - - std::vector> result; - std::string content; - std::string dollar_quote_leading_tag_name; - std::string dollar_quote_trailing_tag_name; - - const auto is_valid_tag_char = [](const char c) - { - return std::isalnum(c, std::locale{}) || c == '_' || c == '-'; - }; - - for (const auto current_char : input) { - switch (state) { - case top: - if (current_char == '$') - state = dollar; - continue; - case dollar: - if (is_valid_tag_char(current_char)) { - state = dollar_quote_leading_tag; - dollar_quote_leading_tag_name += current_char; - } - continue; - case dollar_quote_leading_tag: - if (current_char == '$') { - state = dollar_quote; - } else if (is_valid_tag_char(current_char)) { - dollar_quote_leading_tag_name += current_char; - } else - throw std::runtime_error("invalid dollar quote tag"); - continue; - case dollar_quote: - if (current_char == '$') - state = dollar_quote_dollar; - else - content += current_char; - continue; - case dollar_quote_dollar: - if (current_char == '$') { - if (dollar_quote_leading_tag_name == dollar_quote_trailing_tag_name) { - /* - * Okay, the tag's name and content are successfully extracted. - * Now attempt to clean up the content before adding it to the result. - */ - state = top; - result.emplace_back(std::move(dollar_quote_leading_tag_name), - Data::make(cleaned_content(std::move(content), comment_type), Data_format::text)); - content = std::string{}; - dollar_quote_leading_tag_name = std::string{}; - } else - state = dollar_quote; - - dollar_quote_trailing_tag_name.clear(); - } else - dollar_quote_trailing_tag_name += current_char; - continue; - } - } - - if (state != top) - throw std::runtime_error("invalid comment block:\n" + input); - - return result; - } - - /** - * @brief Scans the extra data content to determine the indent size. - * - * @returns The number of characters to remove after each '\n'. - */ - static std::size_t indent_size(const std::string& content, const Comment_type comment_type) - { - const auto set_if_less = [](auto& variable, const auto count) - { - if (!variable) - variable.emplace(count); - else if (count < variable) - variable = count; - }; - - enum { counting, after_asterisk, after_non_asterisk, skiping } state = counting; - std::optional min_indent_to_border{}; - std::optional min_indent_to_content{}; - std::size_t count{}; - for (const auto current_char : content) { - switch (state) { - case counting: - if (current_char == '\n') - count = 0; - else if (current_char == '*') - state = after_asterisk; - else if (std::isspace(current_char, std::locale{})) - ++count; - else - state = after_non_asterisk; - continue; - case after_asterisk: - if (current_char == ' ') { - if (min_indent_to_border) { - if (count < *min_indent_to_border) { - set_if_less(min_indent_to_content, *min_indent_to_border); - min_indent_to_border = count; - } else if (count == *min_indent_to_border + 1) - set_if_less(min_indent_to_content, count); - } else - min_indent_to_border.emplace(count); - } else - set_if_less(min_indent_to_content, count); - state = skiping; - continue; - case after_non_asterisk: - set_if_less(min_indent_to_content, count); - state = skiping; - continue; - case skiping: - if (current_char == '\n') { - count = 0; - state = counting; - } - continue; - } - } - - // Calculating the result depending on the comment type. - switch (comment_type) { - case Comment_type::multi_line: - if (min_indent_to_border) { - if (min_indent_to_content) { - if (min_indent_to_content <= min_indent_to_border) - return 0; - else if (min_indent_to_content == *min_indent_to_border + 1) - return *min_indent_to_content; - } - return *min_indent_to_border + 1 + 1; - } else - return 0; - case Comment_type::one_line: - return min_indent_to_content ? (*min_indent_to_content == 0 ? 0 : 1) : 1; - } - - DMITIGR_ASSERT_ALWAYS(!true); - } - - /** - * @brief Cleans up the extra data content. - * - * Cleaning up includes: - * - removing the indentation characters; - * - trimming most leading and/or most trailing newline characters (for multiline comments only). - */ - static std::string cleaned_content(std::string&& content, const Comment_type comment_type) - { - std::string result; - - // Removing the indentation characters (if any). - if (const std::size_t isize = indent_size(content, comment_type); isize > 0) { - std::size_t count{}; - enum { eating, skiping } state = eating; - for (const auto current_char : content) { - switch (state) { - case eating: - if (current_char == '\n') { - count = isize; - state = skiping; - } - result += current_char; - continue; - case skiping: - if (count > 1) - --count; - else - state = eating; - continue; - } - } - std::string{}.swap(content); - } else - result.swap(content); - - // Trimming the result string: remove the most leading and the most trailing newline-characters. - if (const auto size = result.size(); size > 0) { - std::string::size_type start{}; - if (result[start] == '\r') - ++start; - if (start < size && result[start] == '\n') - ++start; - - std::string::size_type end{size}; - if (start < end && result[end - 1] == '\n') - --end; - if (start < end && result[end - 1] == '\r') - --end; - - if (start > 0 || end < size) - result = result.substr(start, end - start); - } - - return result; - } - - // ------------------------------------------------------------------------- - // Related comments extraction - // ------------------------------------------------------------------------- - - /** - * @brief Finds very first relevant comments of the specified fragments. - * - * @returns The pair of iterators that specifies the range of relevant comments. - */ - std::pair - static first_related_comments(const Fragment_list& fragments) - { - const auto b = cbegin(fragments); - const auto e = cend(fragments); - auto result = std::make_pair(e, e); - - const auto is_nearby_string = [&](const std::string& str) - { - std::string::size_type count{}; - for (const auto c : str) { - if (c == '\n') { - ++count; - if (count > 1) - return false; - } else if (!is_space(c)) - break; - } - return true; - }; - - /* An attempt to find the first commented out text fragment. - * Stops lookup when either named parameter or positional parameter are found. - * (Only fragments of type `text` can have related comments.) - */ - auto i = std::find_if(b, e, [&](const Fragment& f) - { - return (f.type == Fragment::Type::text && is_nearby_string(f.str) && !is_blank_string(f.str)) || - f.type == Fragment::Type::named_parameter || - f.type == Fragment::Type::positional_parameter; - }); - if (i != b && i != e && is_text(*i)) { - result.second = i; - do { - --i; - DMITIGR_ASSERT(is_comment(*i) || (is_text(*i) && is_blank_string(i->str))); - if (i->type == Fragment::Type::text) { - if (!is_nearby_string(i->str)) - break; - } - result.first = i; - } while (i != b); - } - - return result; - } - - /** - * @brief Joins first comments of the same type into the result string. - * - * @returns The pair of: - * - the pair of the result string (comment) and its type; - * - the iterator that points to the fragment that follows the last comment - * appended to the result. - */ - std::pair, Fragment_list::const_iterator> - static joined_comments_of_same_type(Fragment_list::const_iterator i, const Fragment_list::const_iterator e) - { - DMITIGR_ASSERT(is_comment(*i)); - std::string result; - const auto fragment_type = i->type; - for (; i->type == fragment_type && i != e; ++i) { - result.append(i->str); - if (fragment_type == Fragment::Type::one_line_comment) - result.append("\n"); - } - const auto comment_type = [](const Fragment::Type ft) - { - switch (ft) { - case Fragment::Type::one_line_comment: return Extra::Comment_type::one_line; - case Fragment::Type::multi_line_comment: return Extra::Comment_type::multi_line; - default: DMITIGR_ASSERT_ALWAYS(!true); - } - }; - return std::make_pair(std::make_pair(result, comment_type(fragment_type)), i); - } - - /** - * @brief Joins all comments into the vector of strings. - * - * @returns The vector of pairs of: - * - the joined comments as first element; - * - the type of the joined comments as second element. - */ - std::vector> - static joined_comments(Fragment_list::const_iterator i, const Fragment_list::const_iterator e) - { - std::vector> result; - while (i != e) { - if (is_comment(*i)) { - auto comments = joined_comments_of_same_type(i, e); - result.push_back(std::move(comments.first)); - i = comments.second; - } else - ++i; - } - return result; - } - }; - - Fragment_list fragments_; - std::vector positional_parameters_; // cache - std::vector named_parameters_; // cache - mutable bool is_extra_data_should_be_extracted_from_comments_{true}; - mutable std::optional extra_; // cache -}; - -} // namespace dmitigr::pgfe::detail - -#endif // DMITIGR_PGFE_SQL_STRING_HXX diff --git a/lib/dmitigr/pgfe/sql_vector.cpp b/lib/dmitigr/pgfe/sql_vector.cpp index 33ddaca..d8663c7 100644 --- a/lib/dmitigr/pgfe/sql_vector.cpp +++ b/lib/dmitigr/pgfe/sql_vector.cpp @@ -2,25 +2,238 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#include "dmitigr/pgfe/sql_vector.hxx" +#include "dmitigr/pgfe/sql_string.hpp" +#include "dmitigr/pgfe/sql_vector.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" -namespace pgfe = dmitigr::pgfe; +namespace dmitigr::pgfe::detail { + +/** + * @internal + * + * @brief An straightforward implementation of Sql_vector. + */ +class iSql_vector final : public Sql_vector { +public: + using Container = std::vector>; + + iSql_vector() = default; + + explicit iSql_vector(const std::string& input) + { + const char* text{input.c_str()}; + while (*text != '\0') { + const auto parsed = parse_sql_input(text); + auto s = std::make_unique(std::move(parsed.first)); + storage_.push_back(std::move(s)); + text = parsed.second; + } + DMITIGR_ASSERT(is_invariant_ok()); + } + + explicit iSql_vector(std::vector>&& storage) + : storage_{std::move(storage)} + { + DMITIGR_ASSERT(is_invariant_ok()); + } + + iSql_vector(const iSql_vector& rhs) + : storage_(rhs.storage_.size()) + { + std::transform(cbegin(rhs.storage_), cend(rhs.storage_), begin(storage_), + [](const auto& sqlstr) { return sqlstr->to_sql_string(); }); + DMITIGR_ASSERT(is_invariant_ok()); + } + + iSql_vector& operator=(const iSql_vector& rhs) + { + iSql_vector tmp{rhs}; + swap(tmp); + return *this; + } + + iSql_vector(iSql_vector&& rhs) = default; + + iSql_vector& operator=(iSql_vector&& rhs) = default; + + void swap(iSql_vector& rhs) noexcept + { + storage_.swap(rhs.storage_); + } + + // --------------------------------------------------------------------------- + + std::unique_ptr to_sql_vector() const override + { + return std::make_unique(*this); + } + + // --------------------------------------------------------------------------- + + std::size_t sql_string_count() const override + { + return storage_.size(); + } + + bool has_sql_strings() const override + { + return !storage_.empty(); + } + + bool has_sql_string(const std::string& extra_name, const std::string& extra_value, + const std::size_t offset = 0, const std::size_t extra_offset = 0) const override + { + return bool(sql_string_index(extra_name, extra_value, offset, extra_offset)); + } + + std::optional sql_string_index(const std::string& extra_name, const std::string& extra_value, + const std::size_t offset = 0, const std::size_t extra_offset = 0) const override + { + DMITIGR_REQUIRE(offset < sql_string_count(), std::out_of_range); + if (const auto i = sql_string_index__(extra_name, extra_value, offset, extra_offset); i < sql_string_count()) + return i; + else + return std::nullopt; + } + + std::size_t sql_string_index_throw(const std::string& extra_name, const std::string& extra_value, + const std::size_t offset = 0, const std::size_t extra_offset = 0) const override + { + const auto index = sql_string_index(extra_name, extra_value, offset, extra_offset); + DMITIGR_REQUIRE(index, std::out_of_range); + return *index; + } + + Sql_string* sql_string(const std::size_t index) override + { + return const_cast(static_cast(this)->sql_string(index)); + } + + const Sql_string* sql_string(const std::size_t index) const override + { + DMITIGR_REQUIRE(index < sql_string_count(), std::out_of_range); + return storage_[index].get(); + } + + Sql_string* sql_string(const std::string& extra_name, const std::string& extra_value, + const std::size_t offset = 0, const std::size_t extra_offset = 0) override + { + return const_cast(static_cast(this)-> + sql_string(extra_name, extra_value, offset, extra_offset)); + } + + const Sql_string* sql_string(const std::string& extra_name, const std::string& extra_value, + const std::size_t offset = 0, const std::size_t extra_offset = 0) const override + { + return sql_string(sql_string_index_throw(extra_name, extra_value, offset, extra_offset)); + } + + // -------------------------------------------------------------------------- + + void set_sql_string(const std::size_t index, std::unique_ptr&& sql_string) override + { + DMITIGR_REQUIRE(index < sql_string_count(), std::out_of_range); + DMITIGR_REQUIRE(sql_string, std::invalid_argument); + storage_[index] = std::move(sql_string); + } + + void append_sql_string(std::unique_ptr&& sql_string) override + { + DMITIGR_REQUIRE(sql_string, std::invalid_argument); + storage_.push_back(std::move(sql_string)); + } + + void insert_sql_string(const std::size_t index, std::unique_ptr&& sql_string) override + { + DMITIGR_REQUIRE(index < sql_string_count(), std::out_of_range); + DMITIGR_REQUIRE(sql_string, std::invalid_argument); + storage_.insert(begin(storage_) + index, std::move(sql_string)); + } + + void remove_sql_string(const std::size_t index) override + { + DMITIGR_REQUIRE(index < sql_string_count(), std::out_of_range); + storage_.erase(begin(storage_) + index); + } + + // --------------------------------------------------------------------------- + + std::string to_string() const override + { + std::string result; + if (!storage_.empty()) { + for (const auto& sql_string : storage_) + result.append(sql_string->to_string()).append(";"); + result.pop_back(); + } + return result; + } + + std::vector> to_vector() const override + { + iSql_vector copy{*this}; + return std::move(copy.storage_); + } + + std::vector> move_to_vector() override + { + std::vector> result; + storage_.swap(result); + return std::move(result); + } + +protected: + bool is_invariant_ok() const + { + return true; + } + +private: + std::size_t sql_string_index__(const std::string& extra_name, const std::string& extra_value, + const std::size_t offset = 0, const std::size_t extra_offset = 0) const + { + const auto b = cbegin(storage_); + const auto e = cend(storage_); + const auto i = std::find_if(b + offset, e, + [&](const auto& sql_string) + { + DMITIGR_ASSERT(sql_string); + if (const auto* const extra = sql_string->extra()) { + if (extra_offset < extra->field_count()) { + const auto index = extra->field_index(extra_name, extra_offset); + return (index && (extra->data(*index)->bytes() == extra_value)); + } else + return false; + } else + return false; + }); + return (i - b); + } + + mutable std::vector> storage_; +}; + +} // namespace dmitigr::pgfe::detail + +// ============================================================================= namespace dmitigr::pgfe { -DMITIGR_PGFE_API std::unique_ptr Sql_vector::make() +DMITIGR_PGFE_INLINE std::unique_ptr Sql_vector::make() { return std::make_unique(); } -DMITIGR_PGFE_API std::unique_ptr Sql_vector::make(const std::string& input) +DMITIGR_PGFE_INLINE std::unique_ptr Sql_vector::make(const std::string& input) { return std::make_unique(input); } -DMITIGR_PGFE_API std::unique_ptr Sql_vector::make(std::vector>&& v) +DMITIGR_PGFE_INLINE std::unique_ptr Sql_vector::make(std::vector>&& v) { return std::make_unique(std::move(v)); } } // namespace dmitigr::pgfe + +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/sql_vector.hpp b/lib/dmitigr/pgfe/sql_vector.hpp index 5787dbd..3b21405 100644 --- a/lib/dmitigr/pgfe/sql_vector.hpp +++ b/lib/dmitigr/pgfe/sql_vector.hpp @@ -275,4 +275,8 @@ class Sql_vector { } // namespace dmitigr::pgfe +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/sql_vector.cpp" +#endif + #endif // DMITIGR_PGFE_SQL_VECTOR_HPP diff --git a/lib/dmitigr/pgfe/sql_vector.hxx b/lib/dmitigr/pgfe/sql_vector.hxx deleted file mode 100644 index 2d95880..0000000 --- a/lib/dmitigr/pgfe/sql_vector.hxx +++ /dev/null @@ -1,215 +0,0 @@ -// -*- C++ -*- -// Copyright (C) Dmitry Igrishin -// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp - -#include "dmitigr/pgfe/sql_vector.hpp" -#include "dmitigr/pgfe/sql_string.hxx" - -namespace dmitigr::pgfe::detail { - -/** - * @internal - * - * @brief An straightforward implementation of Sql_vector. - */ -class iSql_vector : public Sql_vector { -public: - using Container = std::vector>; - - iSql_vector() = default; - - explicit iSql_vector(const std::string& input) - { - const char* text{input.c_str()}; - while (*text != '\0') { - const auto parsed = parse_sql_input(text); - auto s = std::make_unique(std::move(parsed.first)); - storage_.push_back(std::move(s)); - text = parsed.second; - } - DMITIGR_ASSERT(is_invariant_ok()); - } - - explicit iSql_vector(std::vector>&& storage) - : storage_{std::move(storage)} - { - DMITIGR_ASSERT(is_invariant_ok()); - } - - iSql_vector(const iSql_vector& rhs) - : storage_(rhs.storage_.size()) - { - std::transform(cbegin(rhs.storage_), cend(rhs.storage_), begin(storage_), - [](const auto& sqlstr) { return sqlstr->to_sql_string(); }); - DMITIGR_ASSERT(is_invariant_ok()); - } - - iSql_vector& operator=(const iSql_vector& rhs) - { - iSql_vector tmp{rhs}; - swap(tmp); - return *this; - } - - iSql_vector(iSql_vector&& rhs) = default; - - iSql_vector& operator=(iSql_vector&& rhs) = default; - - void swap(iSql_vector& rhs) noexcept - { - storage_.swap(rhs.storage_); - } - - // --------------------------------------------------------------------------- - - std::unique_ptr to_sql_vector() const override - { - return std::make_unique(*this); - } - - // --------------------------------------------------------------------------- - - std::size_t sql_string_count() const override - { - return storage_.size(); - } - - bool has_sql_strings() const override - { - return !storage_.empty(); - } - - bool has_sql_string(const std::string& extra_name, const std::string& extra_value, - const std::size_t offset = 0, const std::size_t extra_offset = 0) const override - { - return bool(sql_string_index(extra_name, extra_value, offset, extra_offset)); - } - - std::optional sql_string_index(const std::string& extra_name, const std::string& extra_value, - const std::size_t offset = 0, const std::size_t extra_offset = 0) const override - { - DMITIGR_REQUIRE(offset < sql_string_count(), std::out_of_range); - if (const auto i = sql_string_index__(extra_name, extra_value, offset, extra_offset); i < sql_string_count()) - return i; - else - return std::nullopt; - } - - std::size_t sql_string_index_throw(const std::string& extra_name, const std::string& extra_value, - const std::size_t offset = 0, const std::size_t extra_offset = 0) const override - { - const auto index = sql_string_index(extra_name, extra_value, offset, extra_offset); - DMITIGR_REQUIRE(index, std::out_of_range); - return *index; - } - - Sql_string* sql_string(const std::size_t index) override - { - return const_cast(static_cast(this)->sql_string(index)); - } - - const Sql_string* sql_string(const std::size_t index) const override - { - DMITIGR_REQUIRE(index < sql_string_count(), std::out_of_range); - return storage_[index].get(); - } - - Sql_string* sql_string(const std::string& extra_name, const std::string& extra_value, - const std::size_t offset = 0, const std::size_t extra_offset = 0) override - { - return const_cast(static_cast(this)-> - sql_string(extra_name, extra_value, offset, extra_offset)); - } - - const Sql_string* sql_string(const std::string& extra_name, const std::string& extra_value, - const std::size_t offset = 0, const std::size_t extra_offset = 0) const override - { - return sql_string(sql_string_index_throw(extra_name, extra_value, offset, extra_offset)); - } - - // -------------------------------------------------------------------------- - - void set_sql_string(const std::size_t index, std::unique_ptr&& sql_string) override - { - DMITIGR_REQUIRE(index < sql_string_count(), std::out_of_range); - DMITIGR_REQUIRE(sql_string, std::invalid_argument); - storage_[index] = std::move(sql_string); - } - - void append_sql_string(std::unique_ptr&& sql_string) override - { - DMITIGR_REQUIRE(sql_string, std::invalid_argument); - storage_.push_back(std::move(sql_string)); - } - - void insert_sql_string(const std::size_t index, std::unique_ptr&& sql_string) override - { - DMITIGR_REQUIRE(index < sql_string_count(), std::out_of_range); - DMITIGR_REQUIRE(sql_string, std::invalid_argument); - storage_.insert(begin(storage_) + index, std::move(sql_string)); - } - - void remove_sql_string(const std::size_t index) override - { - DMITIGR_REQUIRE(index < sql_string_count(), std::out_of_range); - storage_.erase(begin(storage_) + index); - } - - // --------------------------------------------------------------------------- - - std::string to_string() const override - { - std::string result; - if (!storage_.empty()) { - for (const auto& sql_string : storage_) - result.append(sql_string->to_string()).append(";"); - result.pop_back(); - } - return result; - } - - std::vector> to_vector() const override - { - iSql_vector copy{*this}; - return std::move(copy.storage_); - } - - std::vector> move_to_vector() override - { - std::vector> result; - storage_.swap(result); - return std::move(result); - } - -protected: - bool is_invariant_ok() const - { - return true; - } - -private: - std::size_t sql_string_index__(const std::string& extra_name, const std::string& extra_value, - const std::size_t offset = 0, const std::size_t extra_offset = 0) const - { - const auto b = cbegin(storage_); - const auto e = cend(storage_); - const auto i = std::find_if(b + offset, e, - [&](const auto& sql_string) - { - DMITIGR_ASSERT(sql_string); - if (const auto* const extra = sql_string->extra()) { - if (extra_offset < extra->field_count()) { - const auto index = extra->field_index(extra_name, extra_offset); - return (index && (extra->data(*index)->bytes() == extra_value)); - } else - return false; - } else - return false; - }); - return (i - b); - } - - mutable std::vector> storage_; -}; - -} // namespace dmitigr::pgfe::detail diff --git a/lib/dmitigr/pgfe/std_system_error.cpp b/lib/dmitigr/pgfe/std_system_error.cpp index b108462..c7ff6ff 100644 --- a/lib/dmitigr/pgfe/std_system_error.cpp +++ b/lib/dmitigr/pgfe/std_system_error.cpp @@ -2,8 +2,9 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#include "dmitigr/pgfe/errc.hxx" +#include "dmitigr/pgfe/errc.hpp" #include "dmitigr/pgfe/std_system_error.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" #include @@ -41,38 +42,40 @@ std::string Server_error_category::message(const int ev) const return result; } -} // namespace dmitigr::pgfe - -namespace pgfe = dmitigr::pgfe; +// ============================================================================= -DMITIGR_PGFE_API auto pgfe::client_error_category() noexcept -> const Client_error_category& +DMITIGR_PGFE_INLINE const Client_error_category& client_error_category() noexcept { static const Client_error_category result; return result; } -DMITIGR_PGFE_API auto pgfe::server_error_category() noexcept -> const Server_error_category& +DMITIGR_PGFE_INLINE const Server_error_category& pgfe::server_error_category() noexcept { static const Server_error_category result; return result; } -DMITIGR_PGFE_API std::error_code pgfe::make_error_code(Client_errc errc) noexcept +DMITIGR_PGFE_INLINE std::error_code make_error_code(Client_errc errc) noexcept { return std::error_code(int(errc), client_error_category()); } -DMITIGR_PGFE_API std::error_code pgfe::make_error_code(Server_errc errc) noexcept +DMITIGR_PGFE_INLINE std::error_code make_error_code(Server_errc errc) noexcept { return std::error_code(int(errc), server_error_category()); } -DMITIGR_PGFE_API std::error_condition pgfe::make_error_condition(Client_errc errc) noexcept +DMITIGR_PGFE_INLINE std::error_condition make_error_condition(Client_errc errc) noexcept { return std::error_condition(int(errc), client_error_category()); } -DMITIGR_PGFE_API std::error_condition pgfe::make_error_condition(Server_errc errc) noexcept +DMITIGR_PGFE_INLINE std::error_condition make_error_condition(Server_errc errc) noexcept { return std::error_condition(int(errc), server_error_category()); } + +} // namespace dmitigr::pgfe + +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/std_system_error.hpp b/lib/dmitigr/pgfe/std_system_error.hpp index b76adbe..e2e516c 100644 --- a/lib/dmitigr/pgfe/std_system_error.hpp +++ b/lib/dmitigr/pgfe/std_system_error.hpp @@ -19,7 +19,7 @@ namespace dmitigr::pgfe { * * @see Client_exception. */ -class Client_error_category : public std::error_category { +class Client_error_category final : public std::error_category { public: /** * @returns The literal `dmitigr_pgfe_client_error`. @@ -44,7 +44,7 @@ class Client_error_category : public std::error_category { * * @see Server_exception. */ -class Server_error_category : public std::error_category { +class Server_error_category final : public std::error_category { public: /** * @returns The literal `dmitigr_pgfe_server_error`. @@ -113,15 +113,19 @@ namespace std { * * @brief The full specialization for integration with ``. */ -template<> struct is_error_code_enum : true_type {}; +template<> struct is_error_code_enum final : true_type {}; /** * @ingroup errors * * @brief The full specialization for integration with ``. */ -template<> struct is_error_code_enum : true_type {}; +template<> struct is_error_code_enum final : true_type {}; } // namespace std +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/std_system_error.cpp" +#endif + #endif // DMITIGR_PGFE_STD_SYSTEM_ERROR_HPP diff --git a/lib/dmitigr/pgfe/types_fwd.hpp b/lib/dmitigr/pgfe/types_fwd.hpp index d73f39c..4d80a03 100644 --- a/lib/dmitigr/pgfe/types_fwd.hpp +++ b/lib/dmitigr/pgfe/types_fwd.hpp @@ -27,6 +27,7 @@ namespace dmitigr::pgfe { enum class Communication_mode; enum class Communication_status; enum class Data_format; +enum class External_library; enum class Password_encryption; enum class Problem_severity; enum class Socket_readiness; diff --git a/lib/dmitigr/pgfe/util.cpp b/lib/dmitigr/pgfe/util.cpp new file mode 100644 index 0000000..69c289e --- /dev/null +++ b/lib/dmitigr/pgfe/util.cpp @@ -0,0 +1,71 @@ +// -*- C++ -*- +// Copyright (C) Dmitry Igrishin +// For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp + +#include "dmitigr/pgfe/basics.hpp" +#include "dmitigr/pgfe/util.hpp" +#include "dmitigr/pgfe/implementation_header.hpp" + +#include +#include + +#include +#include +#include +#include + +namespace dmitigr::pgfe::detail { + +DMITIGR_PGFE_INLINE std::string unquote_identifier(const std::string& identifier) +{ + enum { top, double_quote, adjacent_double_quote } state = top; + + std::string result; + const auto sz = identifier.size(); + using Counter = std::remove_const_t; + for (Counter i = 0; i < sz; ++i) { + const char c = identifier[i]; + if (state == top) { + if (c != '"') { + result += std::tolower(c, std::locale{}); + } else + state = double_quote; + } else if (state == double_quote) { + if (c != '"') + result += c; + else // Note: identifier[sz] == 0 + state = (identifier[i + 1] == '"') ? adjacent_double_quote : top; + } else if (state == adjacent_double_quote) { + result += c; + state = double_quote; + } + } + return result; +} + +DMITIGR_PGFE_INLINE int sqlstate_to_int(const char* const code) +{ + DMITIGR_ASSERT(code && + (std::isalnum(code[0], std::locale{}) && + std::isalnum(code[1], std::locale{}) && + std::isalnum(code[2], std::locale{}) && + std::isalnum(code[3], std::locale{}) && + std::isalnum(code[4], std::locale{}) && code[5] == '\0')); + + const long int result = std::strtol(code, NULL, 36); + DMITIGR_ASSERT(errno == 0); + DMITIGR_ASSERT(result >= 0 && result <= std::numeric_limits::max()); + return result; +} + +DMITIGR_PGFE_INLINE Socket_readiness poll_sock(const int socket, const Socket_readiness mask, const std::chrono::milliseconds timeout) +{ + using Sock = net::Socket_native; + using Sock_readiness = net::Socket_readiness; + using net::poll; + return static_cast(poll(static_cast(socket), static_cast(mask), timeout)); +} + +} // namespace dmitigr::pgfe::detail + +#include "dmitigr/pgfe/implementation_footer.hpp" diff --git a/lib/dmitigr/pgfe/sql.hxx b/lib/dmitigr/pgfe/util.hpp similarity index 65% rename from lib/dmitigr/pgfe/sql.hxx rename to lib/dmitigr/pgfe/util.hpp index 8822cd6..d1b24f9 100644 --- a/lib/dmitigr/pgfe/sql.hxx +++ b/lib/dmitigr/pgfe/util.hpp @@ -2,23 +2,16 @@ // Copyright (C) Dmitry Igrishin // For conditions of distribution and use, see files LICENSE.txt or pgfe.hpp -#ifndef DMITIGR_PGFE_SQL_HXX -#define DMITIGR_PGFE_SQL_HXX +#ifndef DMITIGR_PGFE_UTIL_HPP +#define DMITIGR_PGFE_UTIL_HPP +#include "dmitigr/pgfe/types_fwd.hpp" + +#include #include namespace dmitigr::pgfe::detail { -/** - * @internal - * - * @returns The unique SQL identifier. - * - * @par Thread safety - * Thread-safe. - */ -std::string unique_sqlid(); - /** * @internal * @@ -41,6 +34,17 @@ std::string unquote_identifier(const std::string& identifier); */ int sqlstate_to_int(const char* const code); +/** + * @internal + * + * @brief A wrapper around net::poll(). + */ +Socket_readiness poll_sock(int socket, Socket_readiness mask, std::chrono::milliseconds timeout); + } // namespace dmitigr::pgfe::detail -#endif // DMITIGR_PGFE_SQL_HXX +#ifdef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/util.cpp" +#endif + +#endif // DMITIGR_PGFE_UTIL_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 43c6181..ead6e20 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,11 +14,6 @@ foreach(t unit-connection-deferrable unit-connection-err_in_mid unit-ps unit-conversions-online benchmark-sql_string_replace benchmark-array-client benchmark-array-server) set(test_sources ${t}.cpp unit.hpp) - if("${t}" STREQUAL "unit-problem") - list(APPEND test_sources - ${dmitigr_pgfe_SOURCE_DIR}/lib/dmitigr/pgfe/problem.cpp - ${dmitigr_pgfe_SOURCE_DIR}/lib/dmitigr/pgfe/sql.cxx) - endif() add_executable(${t} ${test_sources}) target_link_libraries(${t} PRIVATE ${dmitigr_pgfe_target} ${dmitigr_common_library}) dmitigr_target_compile_options(${t}) diff --git a/tests/benchmark-array-client.cpp b/tests/benchmark-array-client.cpp index 2032c73..4c96e5b 100644 --- a/tests/benchmark-array-client.cpp +++ b/tests/benchmark-array-client.cpp @@ -4,6 +4,9 @@ #include "benchmark-array.hpp" +#ifndef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/exceptions.cpp" +#endif #include "dmitigr/pgfe/conversions.hpp" #include "dmitigr/pgfe/row.hpp" diff --git a/tests/benchmark-array-server.cpp b/tests/benchmark-array-server.cpp index caaa242..600fff3 100644 --- a/tests/benchmark-array-server.cpp +++ b/tests/benchmark-array-server.cpp @@ -4,6 +4,9 @@ #include "benchmark-array.hpp" +#ifndef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/exceptions.cpp" +#endif #include "dmitigr/pgfe/conversions.hpp" #include "dmitigr/pgfe/row.hpp" #include "dmitigr/pgfe/row_info.hpp" diff --git a/tests/unit-connection_options.cpp b/tests/unit-connection_options.cpp index 053f0e5..c2fdcb7 100644 --- a/tests/unit-connection_options.cpp +++ b/tests/unit-connection_options.cpp @@ -4,7 +4,25 @@ #include "unit.hpp" -#include "dmitigr/pgfe/connection_options.hxx" +// ----------------------------------------------------------------------------- +#ifndef DMITIGR_PGFE_HEADER_ONLY + +#ifdef WIN32 +#define DMITIGR_PGFE_INLINE inline // avoid warning C4273: inconsistent dll linkage +#endif +#include "dmitigr/pgfe/connection_options.cpp" + +namespace dmitigr::pgfe::detail { + +std::unique_ptr iConnection_options::make_connection() const +{ + throw std::logic_error{"iConnection_options::make_connection(): dummy implementation"}; +} + +} // namespace dmitigr::pgfe::detail + +#endif +// ----------------------------------------------------------------------------- #include #include diff --git a/tests/unit-conversions.cpp b/tests/unit-conversions.cpp index 74bb04e..758a656 100644 --- a/tests/unit-conversions.cpp +++ b/tests/unit-conversions.cpp @@ -4,6 +4,12 @@ #include "unit.hpp" +#ifndef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/exceptions.cpp" +#else +#include "dmitigr/pgfe/exceptions.hpp" +#endif + #include "dmitigr/pgfe/conversions.hpp" #include diff --git a/tests/unit-problem.cpp b/tests/unit-problem.cpp index 793d63c..0312391 100644 --- a/tests/unit-problem.cpp +++ b/tests/unit-problem.cpp @@ -4,8 +4,17 @@ #include "unit.hpp" -#include "dmitigr/pgfe/error.hxx" -#include "dmitigr/pgfe/notice.hxx" +#ifndef DMITIGR_PGFE_HEADER_ONLY +#include "dmitigr/pgfe/problem.cpp" +#include "dmitigr/pgfe/error.cpp" +#include "dmitigr/pgfe/notice.cpp" +#include "dmitigr/pgfe/util.cpp" +#else +#include "dmitigr/pgfe/problem.hpp" +#include "dmitigr/pgfe/error.hpp" +#include "dmitigr/pgfe/notice.hpp" +#include "dmitigr/pgfe/util.hpp" +#endif int main(int, char* argv[]) { diff --git a/tests/unit-ps.cpp b/tests/unit-ps.cpp index a005f2e..8866cfd 100644 --- a/tests/unit-ps.cpp +++ b/tests/unit-ps.cpp @@ -6,7 +6,7 @@ #include "dmitigr/pgfe/conversions.hpp" #include "dmitigr/pgfe/exceptions.hpp" -#include "dmitigr/pgfe/prepared_statement.hpp" +#include "dmitigr/pgfe/prepared_statement_dfn.hpp" #include "dmitigr/pgfe/row.hpp" #include "dmitigr/pgfe/row_info.hpp" #include "dmitigr/pgfe/sql_string.hpp"