├── .gitignore ├── contrib └── CMakeLists.txt ├── include ├── CMakeLists.txt └── ozo │ ├── pg │ ├── types │ │ ├── text.h │ │ ├── uuid.h │ │ ├── char.h │ │ ├── oid.h │ │ ├── timestamp.h │ │ ├── interval.h │ │ ├── bool.h │ │ ├── json.h │ │ ├── name.h │ │ ├── bytea.h │ │ ├── float.h │ │ ├── integer.h │ │ ├── jsonb.h │ │ └── ltree.h │ ├── types.h │ └── handle.h │ ├── detail │ ├── stub_mutex.h │ ├── typed_buffer.h │ ├── epoch.h │ ├── base36.h │ ├── post_handler.h │ ├── timeout_handler.h │ ├── connection_pool.h │ ├── wrap_executor.h │ ├── float.h │ ├── endian.h │ ├── bind.h │ ├── make_copyable.h │ ├── functional.h │ ├── deadline.h │ └── begin_statement_builder.h │ ├── ext │ ├── boost.h │ ├── std │ │ ├── list.h │ │ ├── vector.h │ │ ├── optional.h │ │ ├── string.h │ │ ├── tuple.h │ │ ├── pair.h │ │ ├── nullptr_t.h │ │ ├── nullopt_t.h │ │ ├── shared_ptr.h │ │ ├── array.h │ │ ├── unique_ptr.h │ │ ├── weak_ptr.h │ │ ├── time_point.h │ │ └── duration.h │ ├── std.h │ └── boost │ │ ├── optional.h │ │ ├── tuple.h │ │ ├── uuid.h │ │ ├── scoped_ptr.h │ │ ├── shared_ptr.h │ │ └── weak_ptr.h │ ├── core │ ├── thread_safety.h │ ├── none.h │ ├── recursive.h │ ├── unwrap.h │ └── strong_typedef.h │ ├── optional.h │ ├── impl │ ├── result_status.h │ ├── async_execute.h │ ├── transaction_status.h │ ├── query.h │ ├── result.h │ ├── request_oid_map.h │ └── io.h │ ├── time_traits.h │ ├── io │ ├── type_traits.h │ ├── ostream.h │ └── istream.h │ ├── ozo.h │ ├── transaction_status.h │ ├── transaction_options.h │ ├── shortcuts.h │ ├── execute.h │ └── deadline.h ├── .codecov.yml ├── .gitmodules ├── scripts ├── wait_postgres.sh ├── run_example.sh ├── gen_h_from_dat.py └── benchmark.sh ├── test_package ├── CMakeLists.txt └── conanfile.py ├── tests ├── main.cpp ├── external_project │ ├── CMakeLists.txt │ └── warning_option_propagation.cpp ├── detail │ ├── base36.cpp │ ├── timeout_handler.cpp │ ├── functional.cpp │ ├── make_copyable.cpp │ └── deadline.cpp ├── connection_info.cpp ├── integration │ ├── execute_integration.cpp │ ├── get_connection_integration.cpp │ ├── cancel_integration.cpp │ ├── role_based_integration.cpp │ └── retry_integration.cpp ├── io │ └── size_of.cpp ├── none.cpp ├── bind.cpp ├── impl │ ├── async_start_transaction.cpp │ ├── async_end_transaction.cpp │ ├── request_oid_map_handler.cpp │ ├── request_oid_map.cpp │ └── cancel.cpp ├── result_mock.h ├── error.cpp ├── deadline.cpp ├── transaction_status.cpp ├── concept.cpp ├── test_error.h └── CMakeLists.txt ├── docker ├── aiopg │ └── Dockerfile ├── asyncpg │ └── Dockerfile └── build │ └── Dockerfile ├── AUTHORS ├── cmake └── ozo-config.cmake ├── LICENSE ├── benchmarks ├── asyncpg_benchmark.py ├── aiopg_benchmark.py ├── ozo_benchmark.cpp └── CMakeLists.txt ├── docs └── recv_impl_example.dox ├── docker-compose.yml ├── .travis.yml ├── examples ├── CMakeLists.txt ├── retry_request.cpp ├── role_based_request.cpp └── request.cpp ├── conanfile.py └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /contrib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(resource_pool) 2 | -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | install(DIRECTORY ozo DESTINATION include) 2 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "include/ozo/error.h" 3 | - "include/ozo/detail/do_nothing.h" 4 | - "include/ozo/impl/result_status.h" 5 | - "tests/.*" 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "contrib/resource_pool"] 2 | path = contrib/resource_pool 3 | url = https://github.com/elsid/resource_pool.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /include/ozo/pg/types/text.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::pg { 6 | using text = std::string; 7 | } // namespace ozo:pg 8 | -------------------------------------------------------------------------------- /include/ozo/pg/types/uuid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::pg { 6 | using uuid = boost::uuids::uuid; 7 | } // namespace ozo::pg 8 | -------------------------------------------------------------------------------- /include/ozo/pg/types/char.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::pg { 6 | using char_t = char; 7 | } 8 | 9 | OZO_PG_BIND_TYPE(ozo::pg::char_t, "char") 10 | -------------------------------------------------------------------------------- /include/ozo/pg/types/oid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::pg { 6 | using oid = ozo::oid_t; 7 | } 8 | 9 | OZO_PG_BIND_TYPE(ozo::pg::oid, "oid") 10 | -------------------------------------------------------------------------------- /include/ozo/pg/types/timestamp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::pg { 6 | using timestamp = std::chrono::system_clock::time_point; 7 | } // namespace ozo:pg 8 | -------------------------------------------------------------------------------- /include/ozo/pg/types/interval.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::pg { 6 | 7 | using interval = std::chrono::microseconds; 8 | 9 | } // namespace ozo::pg 10 | -------------------------------------------------------------------------------- /include/ozo/pg/types/bool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::pg { 6 | using bool_t = bool; 7 | using boolean = bool_t; 8 | } 9 | 10 | OZO_PG_BIND_TYPE(ozo::pg::bool_t, "bool") 11 | -------------------------------------------------------------------------------- /scripts/wait_postgres.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | while ! pg_isready -h ${POSTGRES_HOST} -p 5432 -U ${POSTGRES_USER}; do 4 | echo "waiting until postgres at ${POSTGRES_HOST}:5432 is accepting connections..." 5 | sleep 1 6 | done 7 | -------------------------------------------------------------------------------- /include/ozo/pg/types/json.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace ozo::pg { 9 | OZO_STRONG_TYPEDEF(std::string, json) 10 | } 11 | 12 | OZO_PG_BIND_TYPE(ozo::pg::json, "json") 13 | -------------------------------------------------------------------------------- /include/ozo/pg/types/name.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace ozo::pg { 9 | OZO_STRONG_TYPEDEF(std::string, name) 10 | } 11 | 12 | OZO_PG_BIND_TYPE(ozo::pg::name, "name") 13 | -------------------------------------------------------------------------------- /include/ozo/pg/types/bytea.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace ozo::pg { 9 | OZO_STRONG_TYPEDEF(std::vector, bytea) 10 | } 11 | 12 | OZO_PG_BIND_TYPE(ozo::pg::bytea, "bytea") 13 | -------------------------------------------------------------------------------- /test_package/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(PackageTest CXX) 3 | 4 | find_package(ozo REQUIRED) 5 | 6 | add_executable(example ../examples/connection_pool.cpp) 7 | target_compile_features(example PRIVATE cxx_std_17) 8 | target_link_libraries(example PRIVATE yandex::ozo) 9 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char* argv[]) { 5 | testing::FLAGS_gtest_output = "xml"; 6 | testing::FLAGS_gtest_death_test_style = "threadsafe"; 7 | testing::InitGoogleMock(&argc, argv); 8 | 9 | return RUN_ALL_TESTS(); 10 | } 11 | -------------------------------------------------------------------------------- /include/ozo/detail/stub_mutex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace ozo::detail { 4 | 5 | struct stub_mutex { 6 | stub_mutex() = default; 7 | stub_mutex(const stub_mutex&) = delete; 8 | constexpr void lock() const noexcept {} 9 | constexpr void unlock() const noexcept {} 10 | }; 11 | 12 | } // namespace ozo::detail 13 | -------------------------------------------------------------------------------- /tests/external_project/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(my_ozo_using_project) 3 | 4 | find_package(ozo REQUIRED) 5 | add_executable(my_app ../../examples/request.cpp warning_option_propagation.cpp) 6 | target_compile_options(my_app PRIVATE -Wall -Wextra -pedantic -Werror) 7 | target_link_libraries(my_app PRIVATE yandex::ozo) 8 | -------------------------------------------------------------------------------- /docker/aiopg/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y \ 5 | python3 \ 6 | python3-pip \ 7 | postgresql-client \ 8 | && \ 9 | rm -rf /var/lib/apt/lists/* 10 | 11 | RUN pip3 install aiopg 12 | 13 | VOLUME /code 14 | 15 | WORKDIR /code 16 | 17 | ENV CCACHE_DIR=/ccache 18 | -------------------------------------------------------------------------------- /docker/asyncpg/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y \ 5 | python3 \ 6 | python3-pip \ 7 | postgresql-client \ 8 | && \ 9 | rm -rf /var/lib/apt/lists/* 10 | 11 | RUN pip3 install asyncpg 12 | 13 | VOLUME /code 14 | 15 | WORKDIR /code 16 | 17 | ENV CCACHE_DIR=/ccache 18 | -------------------------------------------------------------------------------- /include/ozo/detail/typed_buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::detail { 6 | 7 | template 8 | union typed_buffer { 9 | static_assert(std::is_fundamental_v, "T should be fundamental type"); 10 | 11 | T typed; 12 | char raw[sizeof(T)]; 13 | }; 14 | 15 | } // namespace ozo::detail 16 | -------------------------------------------------------------------------------- /include/ozo/pg/types/float.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::pg { 6 | 7 | using float8 = double; 8 | using double_precision = float8; 9 | using float4 = float; 10 | using real = float4; 11 | 12 | } // namespace ozo::pg 13 | 14 | OZO_PG_BIND_TYPE(ozo::pg::float8, "float8") 15 | OZO_PG_BIND_TYPE(ozo::pg::float4, "float4") 16 | -------------------------------------------------------------------------------- /include/ozo/pg/types/integer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::pg { 6 | 7 | using int2 = int16_t; 8 | using smallint = int2; 9 | using int4 = int32_t; 10 | using integer = int4; 11 | using int8 = int64_t; 12 | using bigint = int8; 13 | 14 | } // namespace ozo::pg 15 | 16 | OZO_PG_BIND_TYPE(ozo::pg::int8, "int8") 17 | OZO_PG_BIND_TYPE(ozo::pg::int4, "int4") 18 | OZO_PG_BIND_TYPE(ozo::pg::int2, "int2") 19 | -------------------------------------------------------------------------------- /include/ozo/ext/boost.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * @defgroup group-ext-boost Boost 5 | * @ingroup group-ext 6 | * @brief Library extentions for Boost. 7 | * 8 | * Contains extentions are related to Boost library types. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | -------------------------------------------------------------------------------- /include/ozo/detail/epoch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::detail { 6 | 7 | // pg epoch is 2000-01-01 00:00:00, which is 30 years (10957 days) 8 | // after the unix epoch (1970-01-01 00:00:00, the default value for a time_point) 9 | // c++20: static constexpr const auto epoch = std::chrono::system_clock::time_point{} + std::chrono::years{ 30 }; 10 | static constexpr const auto epoch = std::chrono::system_clock::time_point{} + std::chrono::hours{ 24 } * 10957; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /tests/detail/base36.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace { 6 | 7 | TEST(b36tol, should_with_HV001_return_29999809) { 8 | EXPECT_EQ(ozo::detail::b36tol("HV001"), 29999809); 9 | } 10 | 11 | TEST(b36tol, should_with_hv001_return_29999809) { 12 | EXPECT_EQ(ozo::detail::b36tol("hv001"), 29999809); 13 | } 14 | 15 | TEST(ltob36, should_with_29999809_return_HV001) { 16 | EXPECT_EQ("HV001", ozo::detail::ltob36(29999809)); 17 | } 18 | 19 | } // namespace 20 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The following authors have created the source code of "OZO" 2 | published and distributed by YANDEX LLC as the owner: 3 | 4 | Dmitrii Potepalov 5 | Roman Siromakha 6 | Sergei Khandrikov 7 | Jonas Stoehr 8 | Evgeny Nikulin 9 | Martijn Otto 10 | Mikhail Belyavsky 11 | Yuriy Chernshov 12 | Timur Ziganshin 13 | Neo 14 | -------------------------------------------------------------------------------- /tests/external_project/warning_option_propagation.cpp: -------------------------------------------------------------------------------- 1 | // Test file to check, whether -Wno-gnu-string-literal-operator-template -Wno-gnu-zero-variadic-macro-arguments are properly passed to external projects 2 | 3 | // -Wno-gnu-string-literal-operator-template 4 | struct S { constexpr S() = default; }; 5 | 6 | template 7 | auto operator ""_somesuffix() { return S{}; } 8 | 9 | // -Wno-gnu-zero-variadic-macro-arguments 10 | #define VARIADIC_MACRO(x, ...) "asdf" 11 | 12 | char const* test_va_string() 13 | { 14 | return VARIADIC_MACRO(); 15 | } 16 | -------------------------------------------------------------------------------- /tests/detail/timeout_handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../test_asio.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace { 8 | 9 | using namespace testing; 10 | 11 | struct connection_mock { 12 | MOCK_METHOD0(cancel, void()); 13 | }; 14 | 15 | TEST(cancel_io, should_cancel_socket_io) { 16 | connection_mock conn; 17 | ozo::detail::cancel_io cancel_io_handler{conn, std::allocator{}}; 18 | EXPECT_CALL(conn, cancel()).WillOnce(Return()); 19 | cancel_io_handler(ozo::error_code{}); 20 | } 21 | 22 | } // namespace 23 | -------------------------------------------------------------------------------- /include/ozo/ext/std/list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | /** 8 | * @defgroup group-ext-std-list std::list 9 | * @ingroup group-ext-std 10 | * @brief [std::list](https://en.cppreference.com/w/cpp/container/list) support 11 | * 12 | *@code 13 | #include 14 | *@endcode 15 | * 16 | * `std::list` is declared as an one dimensional array representation type. 17 | * @models{Array} 18 | */ 19 | ///@{ 20 | template 21 | struct is_array> : std::true_type {}; 22 | ///@} 23 | } 24 | -------------------------------------------------------------------------------- /include/ozo/ext/std/vector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | /** 8 | * @defgroup group-ext-std-vector std::vector 9 | * @ingroup group-ext-std 10 | * @brief [std::vector](https://en.cppreference.com/w/cpp/container/vector) support 11 | * 12 | *@code 13 | #include 14 | *@endcode 15 | * 16 | * `std::vector` is declared as the one dimensional array representation type. 17 | * @models{Array} 18 | */ 19 | ///@{ 20 | template 21 | struct is_array> : std::true_type {}; 22 | ///@} 23 | } 24 | -------------------------------------------------------------------------------- /include/ozo/detail/base36.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ozo::detail { 8 | 9 | inline long b36tol(std::string_view in) { 10 | return strtol(in.data(), nullptr, 36); 11 | } 12 | 13 | inline std::string ltob36(long i) { 14 | auto u = static_cast(i); 15 | std::string out ; 16 | constexpr auto base = 36; 17 | do { 18 | const char d = u % base; 19 | out.push_back(d < 10 ? '0' + d : 'A' + d - 10); 20 | u /= base; 21 | } while (u > 0); 22 | 23 | boost::range::reverse(out); 24 | return out; 25 | } 26 | 27 | } // namespace ozo::detail 28 | -------------------------------------------------------------------------------- /include/ozo/detail/post_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace ozo::detail { 10 | 11 | template 12 | struct post_handler { 13 | Handler handler; 14 | 15 | post_handler(Handler handler) : handler(std::move(handler)) {} 16 | 17 | template 18 | void operator() (error_code ec, Connection&& connection) { 19 | auto ex = get_executor(connection); 20 | asio::post(ex, detail::bind(std::move(handler), std::move(ec), std::forward(connection))); 21 | } 22 | }; 23 | 24 | } // namespace ozo::detail 25 | -------------------------------------------------------------------------------- /test_package/conanfile.py: -------------------------------------------------------------------------------- 1 | from conans import ConanFile, CMake 2 | 3 | 4 | class OzoTestConan(ConanFile): 5 | settings = "os", "compiler", "build_type", "arch" 6 | generators = "cmake_find_package" 7 | 8 | def build(self): 9 | cmake = CMake(self) 10 | # Current dir is "test_package/build/" and CMakeLists.txt is 11 | # in "test_package" 12 | cmake.configure() 13 | cmake.build() 14 | 15 | def imports(self): 16 | self.copy("*.dll", dst="bin", src="bin") 17 | self.copy("*.dylib*", dst="bin", src="lib") 18 | self.copy('*.so*', dst='bin', src='lib') 19 | 20 | def test(self): 21 | pass # Building alone is sufficient 22 | -------------------------------------------------------------------------------- /tests/connection_info.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | namespace { 7 | 8 | TEST(connection_info, should_return_error_and_bad_connect_for_invalid_connection_info) { 9 | ozo::io_context io; 10 | ozo::connection_info conn_info("invalid connection info"); 11 | 12 | ozo::get_connection(conn_info[io], [](ozo::error_code ec, auto conn){ 13 | EXPECT_TRUE(ec); 14 | EXPECT_TRUE(!ozo::error_message(conn).empty()); 15 | EXPECT_TRUE(ozo::connection_bad(conn)); 16 | }); 17 | 18 | io.run(); 19 | } 20 | 21 | TEST(make_connection_info, should_not_throw) { 22 | EXPECT_NO_THROW(ozo::make_connection_info("conn info string")); 23 | } 24 | 25 | } // namespace 26 | -------------------------------------------------------------------------------- /include/ozo/ext/std.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * @defgroup group-ext-std STL 5 | * @ingroup group-ext 6 | * @brief Library extentions for STL. 7 | * 8 | * Contains extentions are related to the Standard C++ library types. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | -------------------------------------------------------------------------------- /tests/integration/execute_integration.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | namespace { 8 | 9 | using namespace testing; 10 | 11 | TEST(execute, should_perform_operation_without_result) { 12 | using namespace ozo::literals; 13 | 14 | ozo::io_context io; 15 | ozo::connection_info conn_info(OZO_PG_TEST_CONNINFO); 16 | 17 | ozo::execute(conn_info[io], "BEGIN"_SQL, 18 | [&](ozo::error_code ec, auto conn) { 19 | ASSERT_FALSE(ec) << ec.message() << " | " << error_message(conn) << " | " << get_error_context(conn); 20 | EXPECT_FALSE(ozo::connection_bad(conn)); 21 | }); 22 | 23 | io.run(); 24 | } 25 | 26 | } // namespace 27 | -------------------------------------------------------------------------------- /include/ozo/core/thread_safety.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo { 6 | 7 | /** 8 | * @brief defines admissibility to use in multithreaded environment without additional synchronization 9 | * 10 | * Represents binary state. True enables memory sychronization for underlying types to support 11 | * safe access in multithreaded environment. False disables memory synchronization. 12 | * @ingroup group-core-types 13 | * 14 | * @tparam enabled --- binary state. 15 | */ 16 | template 17 | struct thread_safety : std::bool_constant { 18 | constexpr auto operator !() const noexcept { 19 | return thread_safety{}; 20 | } 21 | }; 22 | 23 | constexpr thread_safety thread_safe; 24 | 25 | } // namespace ozo 26 | -------------------------------------------------------------------------------- /include/ozo/pg/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * @defgroup group-type_system-pg-types PostgreSQL 5 | * @ingroup group-type_system 6 | * @brief PostgreSQL types reflections definitions. 7 | * 8 | * Contains definition of types are reflecting PostgreSQL built-in types. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | -------------------------------------------------------------------------------- /cmake/ozo-config.cmake: -------------------------------------------------------------------------------- 1 | include(CMakeFindDependencyMacro) 2 | 3 | # Find PostgreSQL using new provided script 4 | # Remove this once we move to CMake 3.14+ 5 | set(CMAKE_MODULE_PATH_old_ ${CMAKE_MODULE_PATH}) 6 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}") 7 | find_dependency(PostgreSQL) 8 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH_old_}) 9 | 10 | find_dependency(Boost COMPONENTS coroutine context system thread atomic) 11 | find_dependency(resource_pool) 12 | 13 | include("${CMAKE_CURRENT_LIST_DIR}/ozo-targets.cmake") 14 | target_compile_options(yandex::ozo INTERFACE 15 | $<$,$,$,9.0>>>:-Wno-gnu-string-literal-operator-template -Wno-gnu-zero-variadic-macro-arguments>) 16 | -------------------------------------------------------------------------------- /include/ozo/optional.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined(__has_include) 4 | # if __has_include() 5 | # define OZO_HAS_STD_OPTIONAL 1 6 | # elif __has_include() 7 | # define OZO_HAS_STD_EXPIREMENTAL_OPTIONAL 1 8 | # endif 9 | #elif 10 | # define OZO_HAS_STD_OPTIONAL 1 11 | #endif 12 | 13 | 14 | #if defined(OZO_HAS_STD_OPTIONAL) 15 | # define OZO_STD_OPTIONAL std::optional 16 | # define OZO_NULLOPT_T std::nullopt_t 17 | # define OZO_NULLOPT std::nullopt 18 | # include 19 | #elif defined(OZO_HAS_STD_EXPIREMENTAL_OPTIONAL) 20 | # define OZO_STD_OPTIONAL std::experimental::optional 21 | # define OZO_NULLOPT_T std::experimental::nullopt_t 22 | # define OZO_NULLOPT std::experimental::nullopt 23 | # include 24 | #endif 25 | -------------------------------------------------------------------------------- /include/ozo/detail/timeout_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo::detail { 7 | 8 | template 9 | struct cancel_io { 10 | Connection conn_; 11 | Allocator allocator_; 12 | 13 | cancel_io(Connection&& conn, const Allocator& allocator) 14 | : conn_(std::forward(conn)), allocator_(allocator) {} 15 | 16 | void operator() (error_code) const { 17 | conn_.cancel(); 18 | } 19 | 20 | using allocator_type = Allocator; 21 | 22 | allocator_type get_allocator() const noexcept { return allocator_;} 23 | }; 24 | 25 | template 26 | cancel_io(Connection&& conn, const Allocator& allocator) -> cancel_io; 27 | 28 | } // namespace ozo::detail 29 | -------------------------------------------------------------------------------- /include/ozo/impl/result_status.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::impl { 6 | 7 | inline const char* get_result_status_name(ExecStatusType status) noexcept { 8 | #define OZO_CASE_RETURN(item) case item: return #item; 9 | switch (status) { 10 | OZO_CASE_RETURN(PGRES_SINGLE_TUPLE) 11 | OZO_CASE_RETURN(PGRES_TUPLES_OK) 12 | OZO_CASE_RETURN(PGRES_COMMAND_OK) 13 | OZO_CASE_RETURN(PGRES_COPY_OUT) 14 | OZO_CASE_RETURN(PGRES_COPY_IN) 15 | OZO_CASE_RETURN(PGRES_COPY_BOTH) 16 | OZO_CASE_RETURN(PGRES_NONFATAL_ERROR) 17 | OZO_CASE_RETURN(PGRES_BAD_RESPONSE) 18 | OZO_CASE_RETURN(PGRES_EMPTY_QUERY) 19 | OZO_CASE_RETURN(PGRES_FATAL_ERROR) 20 | } 21 | #undef OZO_CASE_RETURN 22 | return "unknown"; 23 | } 24 | 25 | } // namespace ozo::impl 26 | -------------------------------------------------------------------------------- /scripts/run_example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | scripts/build.sh pg docker clang release 4 | 5 | docker-compose up -d ozo_postgres 6 | 7 | docker-compose run \ 8 | --rm \ 9 | --user "$(id -u):$(id -g)" \ 10 | ozo_build_with_pg_tests \ 11 | bash \ 12 | -exc '/code/scripts/wait_postgres.sh; ${BASE_BUILD_DIR}/clang_release/examples/ozo_request "host=${POSTGRES_HOST} user=${POSTGRES_USER} dbname=${POSTGRES_DB} password=${POSTGRES_PASSWORD}"' 13 | 14 | docker-compose run \ 15 | --rm \ 16 | --user "$(id -u):$(id -g)" \ 17 | ozo_build_with_pg_tests \ 18 | bash \ 19 | -exc '/code/scripts/wait_postgres.sh; ${BASE_BUILD_DIR}/clang_release/examples/ozo_connection_pool "host=${POSTGRES_HOST} user=${POSTGRES_USER} dbname=${POSTGRES_DB} password=${POSTGRES_PASSWORD}"' 20 | 21 | docker-compose stop ozo_postgres 22 | docker-compose rm -f ozo_postgres 23 | -------------------------------------------------------------------------------- /include/ozo/ext/boost/optional.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | /** 8 | * @defgroup group-ext-boost-optional boost::optional 9 | * @ingroup group-ext-boost 10 | * @brief [Boost.Optional](https://www.boost.org/doc/libs/1_69_0/libs/optional/doc/html/index.html) library support 11 | * 12 | *@code 13 | #include 14 | *@endcode 15 | * 16 | * The `boost::optional` type is defined as #Nullable and uses default 17 | * implementation of related functions. 18 | * 19 | * The `ozo::unwrap()` function is implemented via the dereference operator. 20 | */ 21 | ///@{ 22 | template 23 | struct is_nullable> : std::true_type {}; 24 | 25 | template 26 | struct unwrap_impl> : detail::functional::dereference {}; 27 | ///@} 28 | } // namespace ozo 29 | -------------------------------------------------------------------------------- /include/ozo/ext/std/optional.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | /** 8 | * @defgroup group-ext-std-optional std::optional 9 | * @ingroup group-ext-std 10 | * @brief [std::optional](https://en.cppreference.com/w/cpp/utility/optional) type support 11 | * 12 | *@code 13 | #include 14 | *@endcode 15 | * 16 | * The `std::optional` type is defined as #Nullable and uses default 17 | * implementation of related functions. 18 | * 19 | * The `ozo::unwrap()` function is implemented via the dereference operator. 20 | */ 21 | ///@{ 22 | #ifdef OZO_STD_OPTIONAL 23 | template 24 | struct is_nullable> : std::true_type {}; 25 | 26 | template 27 | struct unwrap_impl> : detail::functional::dereference {}; 28 | #endif 29 | ///@} 30 | } // namespace ozo 31 | -------------------------------------------------------------------------------- /include/ozo/impl/async_execute.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo::impl { 7 | 8 | template 9 | inline void async_execute(P&& provider, Q&& query, TimeConstraint t, Handler&& handler) { 10 | static_assert(ConnectionProvider

, "is not a ConnectionProvider"); 11 | static_assert(BinaryQueryConvertible, "query should be convertible to the binary_query"); 12 | static_assert(ozo::TimeConstraint, "should model TimeConstraint concept"); 13 | async_get_connection(std::forward

(provider), deadline(t), 14 | async_request_op { 15 | std::forward(query), 16 | deadline(t), 17 | none, 18 | std::forward(handler) 19 | } 20 | ); 21 | } 22 | 23 | } // namespace ozo::impl 24 | -------------------------------------------------------------------------------- /include/ozo/ext/std/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * @defgroup group-ext-std-string std::string 8 | * @ingroup group-ext-std 9 | * @brief [std::string](https://en.cppreference.com/w/cpp/string/basic_string) 10 | * 11 | *@code 12 | #include 13 | *@endcode 14 | * 15 | * `std::string` is mapped as `text` PostgreSQL type. 16 | */ 17 | 18 | OZO_PG_BIND_TYPE(std::string, "text") 19 | 20 | /** 21 | * @defgroup group-ext-std-string_view std::string_view 22 | * @ingroup group-ext-std 23 | * @brief [std::string_view](https://en.cppreference.com/w/cpp/string/basic_string_view) 24 | * 25 | *@code 26 | #include 27 | *@endcode 28 | * 29 | * `std::string_view` is mapped as `text` PostgreSQL type. 30 | * @note It can be used as a query parameter only! 31 | */ 32 | 33 | OZO_PG_BIND_TYPE(std::string_view, "text") 34 | -------------------------------------------------------------------------------- /include/ozo/impl/transaction_status.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | 8 | template 9 | inline transaction_status get_transaction_status(T&& conn) { 10 | static_assert(Connection, "T must be a Connection"); 11 | 12 | if (ozo::is_null(conn)) { 13 | return transaction_status::unknown; 14 | } 15 | 16 | const auto v = PQtransactionStatus(get_native_handle(conn)); 17 | switch (v) { 18 | case PQTRANS_UNKNOWN: return transaction_status::unknown; 19 | case PQTRANS_IDLE: return transaction_status::idle; 20 | case PQTRANS_ACTIVE: return transaction_status::active; 21 | case PQTRANS_INTRANS: return transaction_status::transaction; 22 | case PQTRANS_INERROR: return transaction_status::error; 23 | } 24 | throw std::invalid_argument("unsupported transaction state"); 25 | } 26 | 27 | } // namespace ozo 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, YANDEX LLC 2 | 3 | Permission to use, copy, modify, and distribute this software and its 4 | documentation for any purpose, without fee, and without a written 5 | agreement is hereby granted, provided that the above copyright notice 6 | and this paragraph and the following two paragraphs appear in all copies. 7 | 8 | IN NO EVENT SHALL YANDEX LLC BE LIABLE TO ANY PARTY FOR DIRECT, 9 | INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST 10 | PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, 11 | EVEN IF YANDEX LLC HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | 13 | YANDEX LLC SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT 14 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 15 | PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" 16 | BASIS, AND YANDEX LLC HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, 17 | SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 18 | -------------------------------------------------------------------------------- /include/ozo/time_traits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | 8 | /** 9 | * @brief Time traits of the library 10 | * @ingroup group-core-types 11 | */ 12 | struct time_traits { 13 | using duration = std::chrono::steady_clock::duration; //!< Time duration type of the library 14 | using time_point = std::chrono::steady_clock::time_point; //!< Time point type of the library 15 | /** 16 | * Get current time 17 | * 18 | * @return time_point --- current time 19 | */ 20 | static time_point now() noexcept(noexcept(std::chrono::steady_clock::now())) { 21 | return std::chrono::steady_clock::now(); 22 | } 23 | }; 24 | 25 | template 26 | struct is_time_constraint> : std::true_type {}; 27 | 28 | template 29 | struct is_time_constraint> : std::true_type {}; 30 | 31 | } // namespace ozo 32 | -------------------------------------------------------------------------------- /include/ozo/ext/std/tuple.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | /** 8 | * @defgroup group-ext-std-tuple std::tuple 9 | * @ingroup group-ext-std 10 | * @brief [std::tuple](https://en.cppreference.com/w/cpp/utility/tuple) support 11 | * 12 | *@code 13 | #include 14 | *@endcode 15 | * 16 | * `std::tuple` is defined as a generic composite type. and mapped as PostgreSQL 17 | * `Record` type and its array. 18 | */ 19 | namespace ozo::definitions { 20 | 21 | template 22 | struct type> : pg::type_definition{}; 23 | 24 | template 25 | struct array> : pg::array_definition{}; 26 | 27 | } // namespace ozo::definitions 28 | 29 | namespace ozo { 30 | 31 | template 32 | struct is_composite> : std::true_type {}; 33 | 34 | } // namespace ozo 35 | -------------------------------------------------------------------------------- /include/ozo/ext/std/pair.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | /** 8 | * @defgroup group-ext-std-pair std::pair 9 | * @ingroup group-ext-std 10 | * @brief [std::pair](https://en.cppreference.com/w/cpp/utility/pair) support 11 | * 12 | *@code 13 | #include 14 | *@endcode 15 | * 16 | * `std::pair` is defined as a generic composite type of two elements 17 | * and mapped as PostgreSQL `Record` type and its array. 18 | */ 19 | namespace ozo::definitions { 20 | 21 | template 22 | struct type> : pg::type_definition{}; 23 | 24 | template 25 | struct array> : pg::array_definition{}; 26 | 27 | } // namespace ozo::definitions 28 | 29 | namespace ozo { 30 | 31 | template 32 | struct is_composite> : std::true_type {}; 33 | 34 | } // namespace ozo 35 | -------------------------------------------------------------------------------- /include/ozo/detail/connection_pool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace ozo::detail { 11 | 12 | template 13 | struct get_connection_pool_impl; 14 | 15 | template 16 | struct get_connection_pool_impl> { 17 | using type = yamail::resource_pool::async::pool; 18 | }; 19 | 20 | template 21 | struct get_connection_pool_impl> { 22 | using type = yamail::resource_pool::async::pool; 23 | }; 24 | 25 | template 26 | using get_connection_pool_impl_t = typename get_connection_pool_impl>::type; 27 | 28 | } // namespace ozo::detail 29 | -------------------------------------------------------------------------------- /tests/io/size_of.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | namespace ozo::tests { 7 | 8 | struct sized_type {}; 9 | 10 | } // namespace ozo::tests 11 | 12 | namespace ozo { 13 | 14 | template <> 15 | struct size_of_impl { 16 | static constexpr ozo::size_type apply(const ozo::tests::sized_type&) { 17 | return 42; 18 | } 19 | }; 20 | 21 | } // namespace ozo 22 | 23 | OZO_PG_DEFINE_CUSTOM_TYPE(ozo::tests::sized_type, "sized_type", dynamic_size) 24 | 25 | namespace { 26 | 27 | using namespace testing; 28 | 29 | using ozo::tests::sized_type; 30 | 31 | TEST(data_frame_size, should_add_size_of_size_type_and_size_of_data) { 32 | EXPECT_EQ(ozo::data_frame_size(sized_type()), sizeof(ozo::size_type) + 42); 33 | } 34 | 35 | TEST(data_frame_size, for_empty_optional_should_be_equal_to_size_of_size_type) { 36 | EXPECT_EQ(ozo::data_frame_size(OZO_STD_OPTIONAL()), sizeof(ozo::size_type)); 37 | } 38 | 39 | } // namespace 40 | -------------------------------------------------------------------------------- /tests/none.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | namespace { 7 | 8 | using namespace testing; 9 | 10 | TEST(none_t, should_be_equal_to_none_t_object) { 11 | EXPECT_EQ(ozo::none_t{}, ozo::none_t{}); 12 | } 13 | 14 | TEST(none_t, should_be_not_equal_to_any_other_type_object) { 15 | EXPECT_NE(ozo::none, int{}); 16 | EXPECT_NE(ozo::none, double{}); 17 | EXPECT_NE(ozo::none, std::string{}); 18 | } 19 | 20 | TEST(none_t, should_return_void_when_called) { 21 | EXPECT_TRUE(std::is_void_v); 22 | } 23 | 24 | TEST(none_t, should_return_void_when_applied) { 25 | EXPECT_TRUE(std::is_void_v); 26 | } 27 | 28 | TEST(IsNone, should_return_true_for_none_t) { 29 | EXPECT_TRUE(ozo::IsNone); 30 | } 31 | 32 | TEST(IsNone, should_return_false_for_different_type) { 33 | EXPECT_FALSE(ozo::IsNone); 34 | EXPECT_FALSE(ozo::IsNone); 35 | } 36 | 37 | } // namespace 38 | -------------------------------------------------------------------------------- /tests/bind.cpp: -------------------------------------------------------------------------------- 1 | #include "test_asio.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace { 11 | 12 | using namespace testing; 13 | using namespace ozo::tests; 14 | 15 | namespace asio = boost::asio; 16 | 17 | struct bind : Test { 18 | StrictMock> cb_mock {}; 19 | ozo::tests::execution_context io; 20 | }; 21 | 22 | TEST_F(bind, should_use_handler_executor) { 23 | const InSequence s; 24 | 25 | EXPECT_CALL(cb_mock, get_executor()).WillOnce(Return(io.get_executor())); 26 | EXPECT_CALL(io.executor_, post(_)).WillOnce(InvokeArgument<0>()); 27 | EXPECT_CALL(cb_mock, call(_, _)).WillOnce(Return()); 28 | 29 | asio::post(ozo::detail::bind(wrap(cb_mock), ozo::error_code{}, 42)); 30 | } 31 | 32 | TEST_F(bind, should_forward_binded_values) { 33 | EXPECT_CALL(cb_mock, call(ozo::error_code{}, 42)).WillOnce(Return()); 34 | 35 | ozo::detail::bind(wrap(cb_mock), ozo::error_code{}, 42)(); 36 | } 37 | 38 | } // namespace 39 | -------------------------------------------------------------------------------- /include/ozo/ext/boost/tuple.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | /** 8 | * @defgroup group-ext-boost-tuple boost::tuple 9 | * @ingroup group-ext-boost 10 | * @brief [boost::tuple](https://www.boost.org/doc/libs/1_66_0/libs/tuple/doc/html/tuple_users_guide.html) support 11 | * 12 | *@code 13 | #include 14 | *@endcode 15 | * 16 | * `boost::tuple` is defined as a generic composite type. and mapped as PostgreSQL 17 | * `Record` type and its array. 18 | */ 19 | namespace ozo::definitions { 20 | 21 | template 22 | struct type> : pg::type_definition{}; 23 | 24 | template 25 | struct array> : pg::array_definition{}; 26 | 27 | } // namespace ozo::definitions 28 | 29 | namespace ozo { 30 | 31 | template 32 | struct is_composite> : std::true_type {}; 33 | 34 | } // namespace ozo 35 | -------------------------------------------------------------------------------- /benchmarks/asyncpg_benchmark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import asyncio 4 | import asyncpg 5 | import time 6 | import sys 7 | 8 | 9 | def main(): 10 | loop = asyncio.get_event_loop() 11 | loop.run_until_complete(run()) 12 | 13 | 14 | async def run(): 15 | total_rows = [0] 16 | start = time.monotonic() 17 | await asyncio.gather(*[reuse_connection(total_rows) for _ in range(CONNECTIONS)]) 18 | finish = time.monotonic() 19 | print('read %s rows, %.3f row/sec' % (total_rows[0], total_rows[0] / (finish - start))) 20 | 21 | 22 | async def reuse_connection(total_rows): 23 | conn = await asyncpg.connect(sys.argv[1]) 24 | while total_rows[0] < 10000000: 25 | values = await conn.fetch(QUERY, -1, True) 26 | total_rows[0] += len(values) 27 | await conn.close() 28 | 29 | 30 | CONNECTIONS = 8 31 | QUERY = ''' 32 | SELECT typname, typnamespace, typowner, typlen, typbyval, typcategory, 33 | typispreferred, typisdefined, typdelim, typrelid, typelem, typarray 34 | FROM pg_type 35 | WHERE typtypmod = $1 AND typisdefined = $2 36 | ''' 37 | 38 | if __name__ == '__main__': 39 | main() 40 | -------------------------------------------------------------------------------- /include/ozo/ext/boost/uuid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace ozo { 10 | 11 | /** 12 | * @defgroup group-ext-boost-uuid boost::uuids::uuid 13 | * @ingroup group-ext-boost 14 | * @brief [boost::uuids::uuid](https://www.boost.org/doc/libs/1_62_0/libs/uuid/uuid.html) support 15 | * 16 | *@code 17 | #include 18 | *@endcode 19 | * 20 | * `boost::uuids::uuid` is defined as Universally Unique Identifierss data type. 21 | */ 22 | 23 | template <> 24 | struct send_impl { 25 | template 26 | static ostream& apply(ostream& out, const OidMap&, const boost::uuids::uuid& in) { 27 | return write(out, in.data); 28 | } 29 | }; 30 | 31 | template <> 32 | struct recv_impl { 33 | template 34 | static istream& apply(istream& in, size_type, const OidMap&, boost::uuids::uuid& out) { 35 | return read(in, out.data); 36 | } 37 | }; 38 | 39 | } 40 | 41 | OZO_PG_BIND_TYPE(boost::uuids::uuid, "uuid") 42 | -------------------------------------------------------------------------------- /include/ozo/io/type_traits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | 8 | template > 9 | struct has_resize : std::false_type {}; 10 | 11 | template 12 | struct has_resize().resize(std::size_t()))>> : std::true_type {}; 13 | 14 | template 15 | inline constexpr auto Resizable = has_resize::value; 16 | 17 | template > 18 | struct is_writable : std::false_type {}; 19 | 20 | template 21 | struct is_writable(), std::declval()))>> : std::true_type {}; 22 | 23 | template 24 | inline constexpr auto Writable = is_writable::value; 25 | 26 | template > 27 | struct is_readable : std::false_type {}; 28 | 29 | template 30 | struct is_readable(), std::declval()))>> : std::true_type {}; 31 | 32 | template 33 | inline constexpr auto Readable = is_readable::value; 34 | 35 | } // namespace ozo 36 | -------------------------------------------------------------------------------- /scripts/gen_h_from_dat.py: -------------------------------------------------------------------------------- 1 | # greeting.py 2 | # 3 | # Demonstration of the pyparsing module, on the prototypical "Hello, World!" 4 | # example 5 | # 6 | # Copyright 2003, 2019 by Paul McGuire 7 | # 8 | import pyparsing as pp 9 | import sys 10 | from jinja2 import Template, Environment 11 | from collections import OrderedDict 12 | 13 | # define grammar 14 | singleLineComment = "#" + pp.restOfLine 15 | identifier = pp.Word( pp.alphas, pp.alphanums + "_").setName("identifier") 16 | attr = pp.Group(identifier + pp.Literal("=>").suppress() + pp.QuotedString(quoteChar="'", escChar="\\")) 17 | entry = pp.Dict(pp.Group(pp.Literal('{').suppress() + pp.delimitedList(attr) + pp.Literal('}').suppress())).setName("entry") 18 | bnf = pp.Literal('[').suppress() + (pp.delimitedList(entry)) + pp.Optional(pp.Literal(',').suppress()) + pp.Literal(']').suppress() 19 | bnf.ignore(singleLineComment) 20 | 21 | env = Environment(trim_blocks=True) 22 | tmpl = env.from_string(open(sys.argv[1], "r").read()) 23 | 24 | entries = [ dict([(x[0], x[1]) for x in entry]) for entry in bnf.parseFile( sys.argv[2] )] 25 | entries = OrderedDict(sorted(dict([(x["typname"], x) for x in entries]).items())) 26 | print (tmpl.render(src=sys.argv[2], entries=entries)) 27 | -------------------------------------------------------------------------------- /include/ozo/impl/query.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace ozo::impl { 10 | 11 | template 12 | struct query { 13 | Text text; 14 | hana::tuple params; 15 | }; 16 | 17 | } // namespace ozo::impl 18 | 19 | namespace ozo { 20 | template 21 | struct get_query_text_impl> { 22 | static constexpr decltype(auto) apply(const impl::query& q) noexcept { 23 | return q.text; 24 | } 25 | }; 26 | 27 | template 28 | struct get_query_params_impl> { 29 | static constexpr decltype(auto) apply(const impl::query& q) noexcept { 30 | return q.params; 31 | } 32 | }; 33 | 34 | template 35 | inline constexpr auto make_query(Text&& text, ParamsT&& ...params) { 36 | static_assert(QueryText, "text must be QueryText concept"); 37 | using query = impl::query, std::decay_t...>; 38 | return query {std::forward(text), hana::make_tuple(std::forward(params)...)}; 39 | } 40 | 41 | } // namespace ozo 42 | -------------------------------------------------------------------------------- /include/ozo/detail/wrap_executor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace ozo::detail { 8 | /** 9 | * @brief Safely wraps handler with a given Executor 10 | * 11 | * Comparing to asio::bind_executor this object dispatches handler to its 12 | * associated executor. 13 | */ 14 | template 15 | struct wrap_executor { 16 | Executor ex; 17 | Handler handler; 18 | 19 | wrap_executor(const Executor& ex, Handler handler) 20 | : ex(ex), handler(std::move(handler)) {} 21 | 22 | template 23 | void operator() (Args&& ...args) { 24 | asio::dispatch(detail::bind(std::move(handler), std::forward(args)...)); 25 | } 26 | 27 | using executor_type = Executor; 28 | 29 | executor_type get_executor() const noexcept { return ex;} 30 | 31 | using allocator_type = asio::associated_allocator_t; 32 | 33 | allocator_type get_allocator() const noexcept { return asio::get_associated_allocator(handler);} 34 | }; 35 | 36 | template 37 | wrap_executor(const Executor&, Handler) -> wrap_executor; 38 | 39 | } // namespace ozo::detail 40 | -------------------------------------------------------------------------------- /include/ozo/ext/std/nullptr_t.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ozo { 8 | /** 9 | * @defgroup group-ext-std-nullptr std::nullptr 10 | * @ingroup group-ext-std 11 | * @brief [std::nullptr](https://en.cppreference.com/w/cpp/utility/optional/nullptr) support 12 | * 13 | *@code 14 | #include 15 | *@endcode 16 | * 17 | * The `std::nullptr_t` type is defined as #Nullable which is always in null state. 18 | * 19 | * The `ozo::unwrap()` function with `std::nullptr_t` type argument returns std::nullptr. 20 | * 21 | * The `std::nullptr_t` type is mapped as `NULL` for PostgreSQL. 22 | */ 23 | ///@{ 24 | template <> 25 | struct is_nullable : std::true_type {}; 26 | 27 | template <> 28 | struct unwrap_impl : detail::functional::forward {}; 29 | 30 | template <> 31 | struct is_null_impl : detail::functional::always_true {}; 32 | 33 | template <> 34 | struct send_impl { 35 | template 36 | static ostream& apply(ostream& out, M&&, T&&) { return out;} 37 | }; 38 | ///@} 39 | } // namespace ozo 40 | 41 | OZO_PG_BIND_TYPE(std::nullptr_t, "null") 42 | -------------------------------------------------------------------------------- /tests/impl/async_start_transaction.cpp: -------------------------------------------------------------------------------- 1 | #include "connection_mock.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace { 10 | 11 | using namespace testing; 12 | using namespace ozo::tests; 13 | 14 | using ozo::error_code; 15 | using ozo::time_traits; 16 | 17 | struct async_start_transaction : Test { 18 | decltype(ozo::make_options()) options = ozo::make_options(); 19 | StrictMock connection {}; 20 | StrictMock callback_executor{}; 21 | StrictMock, decltype(options)>>> callback {}; 22 | StrictMock strand {}; 23 | io_context io; 24 | StrictMock handle; 25 | connection_ptr<> conn = make_connection(connection, io, handle); 26 | time_traits::duration timeout {42}; 27 | }; 28 | 29 | TEST_F(async_start_transaction, should_call_async_execute) { 30 | EXPECT_CALL(handle, PQstatus()).WillRepeatedly(Return(CONNECTION_OK)); 31 | 32 | EXPECT_CALL(connection, async_execute()).WillOnce(Return()); 33 | 34 | ozo::detail::async_start_transaction(conn, options, empty_query {}, timeout, wrap(callback)); 35 | } 36 | 37 | } // namespace 38 | -------------------------------------------------------------------------------- /include/ozo/ozo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * @defgroup group-core Core 5 | * @brief Core utilities of the library. 6 | */ 7 | 8 | /** 9 | * @defgroup group-core-functions Functions 10 | * @ingroup group-core 11 | * @brief Core utility functions of the library. 12 | */ 13 | 14 | /** 15 | * @defgroup group-core-types Types 16 | * @ingroup group-core 17 | * @brief Core utility types of the library. 18 | */ 19 | 20 | /** 21 | * @defgroup group-requests Database requests 22 | * @brief Database requests related concepts, types and functions. 23 | */ 24 | 25 | /** 26 | * @defgroup group-requests-functions Functions 27 | * @ingroup group-requests 28 | * @brief Database requests related functions. 29 | */ 30 | 31 | /** 32 | * @defgroup group-requests-types Types 33 | * @ingroup group-requests 34 | * @brief Database requests related types. 35 | */ 36 | 37 | /** 38 | * @defgroup group-ext External adaptors 39 | * @brief External type adaptors. 40 | * 41 | * The Library can be extended via specialization of different 42 | * functors and traits. Here in-library eternal type adaptors are collected. 43 | * Use it as an example of external types adaptation. 44 | */ 45 | 46 | #include 47 | 48 | namespace ozo { 49 | 50 | } // namespace ozo 51 | -------------------------------------------------------------------------------- /include/ozo/ext/std/nullopt_t.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace ozo { 9 | /** 10 | * @defgroup group-ext-std-nullopt std::nullopt_t 11 | * @ingroup group-ext-std 12 | * @brief [std::nullopt](https://en.cppreference.com/w/cpp/utility/optional/nullopt) support 13 | * 14 | *@code 15 | #include 16 | *@endcode 17 | * 18 | * The `std::nullopt_t` type is defined as #Nullable which is always in null state. 19 | * 20 | * The `ozo::unwrap()` function with `std::nullopt_t` type argument returns `std::nullopt`. 21 | * 22 | * The `std::nullopt_t` type is mapped as `NULL` for PostgreSQL. 23 | */ 24 | ///@{ 25 | template <> 26 | struct is_nullable : std::true_type {}; 27 | 28 | template <> 29 | struct unwrap_impl : detail::functional::forward {}; 30 | 31 | template <> 32 | struct is_null_impl : detail::functional::always_true {}; 33 | 34 | template <> 35 | struct send_impl { 36 | template 37 | static ostream& apply(ostream& out, M&&, T&&) { return out;} 38 | }; 39 | ///@} 40 | } // namespace ozo 41 | 42 | OZO_PG_BIND_TYPE(OZO_NULLOPT_T, "null") 43 | -------------------------------------------------------------------------------- /benchmarks/aiopg_benchmark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import asyncio 4 | import aiopg 5 | import time 6 | import sys 7 | 8 | 9 | def main(): 10 | loop = asyncio.get_event_loop() 11 | loop.run_until_complete(run()) 12 | 13 | 14 | async def run(): 15 | total_rows = [0] 16 | start = time.monotonic() 17 | await asyncio.gather(*[reuse_connection(total_rows) for _ in range(CONNECTIONS)]) 18 | finish = time.monotonic() 19 | print('read %s rows, %.3f row/sec' % (total_rows[0], total_rows[0] / (finish - start))) 20 | 21 | 22 | async def reuse_connection(total_rows): 23 | conn = await aiopg.connect(sys.argv[1]) 24 | async with conn.cursor() as cur: 25 | while total_rows[0] < 10000000: 26 | await cur.execute(QUERY, parameters=(-1, True)) 27 | values = [] 28 | async for row in cur: 29 | values.append(row) 30 | total_rows[0] += len(values) 31 | await conn.close() 32 | 33 | 34 | CONNECTIONS = 8 35 | QUERY = ''' 36 | SELECT typname, typnamespace, typowner, typlen, typbyval, typcategory, 37 | typispreferred, typisdefined, typdelim, typrelid, typelem, typarray 38 | FROM pg_type 39 | WHERE typtypmod = %s AND typisdefined = %s 40 | ''' 41 | 42 | if __name__ == '__main__': 43 | main() 44 | -------------------------------------------------------------------------------- /include/ozo/ext/std/shared_ptr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | /** 8 | * @defgroup group-ext-std-shared_ptr std::shared_ptr 9 | * @ingroup group-ext-std 10 | * @brief [std::shared_ptr](https://en.cppreference.com/w/cpp/memory/shared_ptr) support 11 | * 12 | *@code 13 | #include 14 | *@endcode 15 | * 16 | * `std::shared_ptr` is defined as #Nullable and uses the default implementation of `ozo::is_null()`. 17 | * 18 | * Function `ozo::allocate_nullable()` implementation is specialized via 19 | * [std::allocate_shared()](https://en.cppreference.com/w/cpp/memory/shared_ptr/allocate_shared). 20 | * 21 | * The `ozo::unwrap()` function is implemented via the dereference operator. 22 | */ 23 | ///@{ 24 | template 25 | struct is_nullable> : std::true_type {}; 26 | 27 | template 28 | struct allocate_nullable_impl> { 29 | template 30 | static void apply(std::shared_ptr& out, const Alloc& a) { 31 | out = std::allocate_shared(a); 32 | } 33 | }; 34 | 35 | template 36 | struct unwrap_impl> : detail::functional::dereference {}; 37 | ///@} 38 | } // namespace ozo 39 | -------------------------------------------------------------------------------- /include/ozo/ext/boost/scoped_ptr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | /** 8 | * @defgroup group-ext-boost-scoped_ptr boost::scoped_ptr 9 | * @ingroup group-ext-boost 10 | * @brief [boost::scoped_ptr](https://www.boost.org/doc/libs/1_69_0/libs/smart_ptr/doc/html/smart_ptr.html#scoped_ptr) support 11 | * 12 | *@code 13 | #include 14 | *@endcode 15 | * 16 | * `boost::scoped_ptr` is defined as #Nullable and uses the default implementation of `ozo::is_null()`. 17 | * 18 | * Function `ozo::allocate_nullable()` implementation is specialized via direct call of `operator new`, so the allocator 19 | * argument is ignored. 20 | * 21 | * The `ozo::unwrap()` function is implemented via the dereference operator. 22 | */ 23 | ///@{ 24 | template 25 | struct is_nullable> : std::true_type {}; 26 | 27 | template 28 | struct allocate_nullable_impl> { 29 | template 30 | static void apply(boost::scoped_ptr& out, const Alloc&) { 31 | out.reset(new T{}); 32 | } 33 | }; 34 | 35 | template 36 | struct unwrap_impl> : detail::functional::dereference {}; 37 | ///@} 38 | } // namespace ozo 39 | -------------------------------------------------------------------------------- /include/ozo/ext/std/array.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | /** 8 | * @defgroup group-ext-std-array std::array 9 | * @ingroup group-ext-std 10 | * @brief [std::array](https://en.cppreference.com/w/cpp/container/array) support 11 | * 12 | *@code 13 | #include 14 | *@endcode 15 | * 16 | * `std::array` is declared as an one dimensional array representation type 17 | * with fixed size. 18 | * 19 | * The `ozo::fit_array_size()` function throws `ozo::system_error` exception, 20 | * with `ozo::error::bad_array_size` error code, if it's size argument does not 21 | * equal to the array size. 22 | * 23 | * @models{Array} 24 | */ 25 | ///@{ 26 | template 27 | struct is_array> : std::true_type {}; 28 | 29 | template 30 | struct fit_array_size_impl> { 31 | static void apply(const std::array& array, size_type size) { 32 | if (size != static_cast(array.size())) { 33 | throw system_error(error::bad_array_size, 34 | "received size " + std::to_string(size) 35 | + " does not match array size " + std::to_string(array.size())); 36 | } 37 | } 38 | }; 39 | ///@} 40 | } // namespace ozo 41 | -------------------------------------------------------------------------------- /tests/impl/async_end_transaction.cpp: -------------------------------------------------------------------------------- 1 | #include "connection_mock.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace { 10 | 11 | using namespace testing; 12 | using namespace ozo::tests; 13 | 14 | using ozo::error_code; 15 | using ozo::time_traits; 16 | 17 | struct async_end_transaction : Test { 18 | StrictMock connection {}; 19 | StrictMock callback_executor{}; 20 | StrictMock>> callback {}; 21 | StrictMock strand {}; 22 | io_context io; 23 | StrictMock handle; 24 | connection_ptr<> conn = make_connection(connection, io, handle); 25 | decltype(ozo::make_options()) options = ozo::make_options(); 26 | time_traits::duration timeout {42}; 27 | }; 28 | 29 | TEST_F(async_end_transaction, should_call_async_execute) { 30 | EXPECT_CALL(handle, PQstatus()).WillRepeatedly(Return(CONNECTION_OK)); 31 | 32 | auto transaction = ozo::transaction(std::move(conn), options); 33 | 34 | const InSequence s; 35 | 36 | EXPECT_CALL(connection, async_execute()).WillOnce(Return()); 37 | 38 | ozo::detail::async_end_transaction(std::move(transaction), empty_query {}, timeout, wrap(callback)); 39 | } 40 | 41 | } // namespace 42 | -------------------------------------------------------------------------------- /include/ozo/ext/std/unique_ptr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | /** 8 | * @defgroup group-ext-std-unique_ptr std::unique_ptr 9 | * @ingroup group-ext-std 10 | * @brief [std::unique_ptr](https://www.std.org/doc/libs/1_69_0/libs/smart_ptr/doc/html/smart_ptr.html#unique_ptr) support 11 | * 12 | *@code 13 | #include 14 | *@endcode 15 | * 16 | * `std::unique_ptr` is defined as #Nullable and uses the default implementation of `ozo::is_null()`. 17 | * 18 | * Function `ozo::allocate_nullable()` implementation is specialized via 19 | * [std::make_unique()](https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique) so, 20 | * the allocator argument is ignored. 21 | * 22 | * The `ozo::unwrap()` function is implemented via the dereference operator. 23 | */ 24 | ///@{ 25 | template 26 | struct is_nullable> : std::true_type {}; 27 | 28 | template 29 | struct allocate_nullable_impl> { 30 | template 31 | static void apply(std::unique_ptr& out, const Alloc&) { 32 | out = std::make_unique(); 33 | } 34 | }; 35 | 36 | template 37 | struct unwrap_impl> : detail::functional::dereference {}; 38 | ///@} 39 | } // namespace ozo 40 | -------------------------------------------------------------------------------- /tests/integration/get_connection_integration.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | namespace { 7 | 8 | TEST(get_connection, should_return_timeout_error_for_zero_connect_timeout) { 9 | ozo::io_context io; 10 | const ozo::connection_info conn_info(OZO_PG_TEST_CONNINFO); 11 | const std::chrono::seconds timeout(0); 12 | 13 | std::atomic_flag called {}; 14 | ozo::get_connection(conn_info[io], timeout, [&] (ozo::error_code ec, auto conn) { 15 | EXPECT_FALSE(called.test_and_set()); 16 | EXPECT_EQ(ec, boost::asio::error::timed_out); 17 | EXPECT_TRUE(ozo::connection_bad(conn)); 18 | EXPECT_EQ(ozo::get_error_context(conn), "error while connection polling"); 19 | }); 20 | 21 | io.run(); 22 | } 23 | 24 | TEST(get_connection, should_return_connection_for_max_connect_timeout) { 25 | ozo::io_context io; 26 | const ozo::connection_info conn_info(OZO_PG_TEST_CONNINFO); 27 | const auto timeout = ozo::time_traits::duration::max(); 28 | 29 | std::atomic_flag called {}; 30 | ozo::get_connection(conn_info[io], timeout, [&] (ozo::error_code ec, auto conn) { 31 | EXPECT_FALSE(called.test_and_set()); 32 | EXPECT_FALSE(ec); 33 | EXPECT_FALSE(ozo::connection_bad(conn)); 34 | }); 35 | 36 | io.run(); 37 | } 38 | 39 | } // namespace 40 | -------------------------------------------------------------------------------- /docs/recv_impl_example.dox: -------------------------------------------------------------------------------- 1 | File MyString.h 2 | @code 3 | namespace NSdemo { 4 | // Out string-like class with our own code-style 5 | class MyString { 6 | public: 7 | //... 8 | SizeType Size() const; 9 | char* Buffer(); 10 | void Resize(size_type size); 11 | //... 12 | }; 13 | } // namespace NSdemo 14 | @endcode 15 | File MyStringOzoAdaptor.h 16 | @code 17 | #include 18 | 19 | namespace NSdemo { 20 | // E.g. size and data can be adapted via the free functions 21 | // in same namespace 22 | inline auto size(const MyString& s) { return s.Size(); } 23 | inline auto data(const MyString& s) { return s.Buffer(); } 24 | 25 | } // namespace NSdemo 26 | 27 | // We want to use it for the 'text' type 28 | OZO_PG_DEFINE_TYPE_AND_ARRAY(demo::my_string, "text", TEXTOID, TEXTARRAYOID, dynamic_size) 29 | 30 | namespace ozo { 31 | // Since we do not have resize() method and can not implement it 32 | // we need to specialize ozo::recv_impl template to allocate place 33 | // for the data. 34 | template <> 35 | struct recv_impl { 36 | template 37 | static istream& apply(istream& in, size_type size, const OidMap&, NSdemo::MyString& out) { 38 | // Allocating space for info 39 | out.Resize(size); 40 | // Reading raw info from the input stream 41 | return read(in, out); 42 | } 43 | }; 44 | } // namespace ozo 45 | @endcode -------------------------------------------------------------------------------- /include/ozo/ext/boost/shared_ptr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ozo { 8 | /** 9 | * @defgroup group-ext-boost-shared_ptr boost::shared_ptr 10 | * @ingroup group-ext-boost 11 | * @brief [boost::shared_ptr](https://www.boost.org/doc/libs/1_69_0/libs/smart_ptr/doc/html/smart_ptr.html#shared_ptr) support 12 | * 13 | *@code 14 | #include 15 | *@endcode 16 | * 17 | * `boost::shared_ptr` is defined as #Nullable and uses the default implementation of `ozo::is_null()`. 18 | * 19 | * Function `ozo::allocate_nullable()` implementation is specialized via 20 | * [boost::allocate_shared()](https://www.boost.org/doc/libs/1_69_0/libs/smart_ptr/doc/html/smart_ptr.html#make_shared). 21 | * 22 | * The `ozo::unwrap()` function is implemented via the dereference operator. 23 | */ 24 | ///@{ 25 | template 26 | struct is_nullable> : std::true_type {}; 27 | 28 | template 29 | struct allocate_nullable_impl> { 30 | template 31 | static void apply(boost::shared_ptr& out, const Alloc& a) { 32 | out = boost::allocate_shared(a); 33 | } 34 | }; 35 | 36 | template 37 | struct unwrap_impl> : detail::functional::dereference {}; 38 | ///@} 39 | } // namespace ozo 40 | -------------------------------------------------------------------------------- /include/ozo/ext/std/weak_ptr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | /** 8 | * @defgroup group-ext-std-weak_ptr std::weak_ptr 9 | * @ingroup group-ext-std 10 | * @brief [std::weak_ptr](https://en.cppreference.com/w/cpp/memory/weak_ptr) support 11 | * 12 | *@code 13 | #include 14 | *@endcode 15 | * 16 | * `std::weak_ptr` is defined as #Nullable and uses the implementation of `ozo::is_null()` examing the 17 | * result of `std::weak_ptr::lock()` method call. 18 | * 19 | * The `ozo::unwrap()` function is implemented dereferencing the result of `std::weak_ptr::lock()` 20 | * method call. 21 | * @note `ozo::unwrap()` from `std::weak_ptr` is not safe for reference holding since no object owning. 22 | */ 23 | ///@{ 24 | template 25 | struct is_nullable> : std::true_type {}; 26 | 27 | template 28 | struct unwrap_impl> { 29 | template 30 | constexpr static decltype(auto) apply(T1&& v) noexcept(noexcept(*v.lock())) { 31 | return *v.lock(); 32 | } 33 | }; 34 | 35 | template 36 | struct is_null_impl> { 37 | constexpr static auto apply(const std::weak_ptr& v) noexcept( 38 | noexcept(ozo::is_null(v.lock()))) { 39 | return ozo::is_null(v.lock()); 40 | } 41 | }; 42 | ///@} 43 | } // namespace ozo 44 | -------------------------------------------------------------------------------- /include/ozo/pg/handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ozo::pg { 8 | 9 | template 10 | struct safe_handle; 11 | 12 | template<> 13 | struct safe_handle<::PGresult> { 14 | struct deleter { 15 | void operator() (::PGresult *ptr) const noexcept { ::PQclear(ptr); } 16 | }; 17 | using type = std::unique_ptr<::PGresult, deleter>; 18 | }; 19 | 20 | template <> 21 | struct safe_handle<::PGconn> { 22 | struct deleter { 23 | void operator() (::PGconn *ptr) const noexcept { ::PQfinish(ptr); } 24 | }; 25 | using type = std::unique_ptr<::PGconn, deleter>; 26 | }; 27 | 28 | template 29 | using safe_handle_t = typename safe_handle::type; 30 | 31 | template 32 | safe_handle_t make_safe(T* handle) noexcept(noexcept(safe_handle_t{handle})) { 33 | return safe_handle_t{handle}; 34 | } 35 | 36 | using conn = safe_handle_t<::PGconn>; 37 | 38 | using result = pg::safe_handle_t<::PGresult>; 39 | 40 | using shared_result = std::shared_ptr<::PGresult>; 41 | 42 | } // namespace ozo::pg 43 | 44 | namespace boost::hana { 45 | 46 | template <> 47 | struct to_impl { 48 | static ozo::pg::shared_result apply(ozo::pg::result x) { 49 | return {x.release(), x.get_deleter()}; 50 | } 51 | }; 52 | 53 | } // namespace boost::hana 54 | -------------------------------------------------------------------------------- /include/ozo/detail/float.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::detail { 6 | 7 | template 8 | struct floating_point_integral {}; 9 | 10 | template <> 11 | struct floating_point_integral { 12 | using type = std::int32_t; 13 | }; 14 | 15 | template <> 16 | struct floating_point_integral { 17 | using type = std::int64_t; 18 | }; 19 | 20 | template 21 | using floating_point_integral_t = typename floating_point_integral::type; 22 | 23 | template 24 | struct integral_floating_point {}; 25 | 26 | template <> 27 | struct integral_floating_point> { 28 | using type = float; 29 | }; 30 | 31 | template <> 32 | struct integral_floating_point> { 33 | using type = double; 34 | }; 35 | 36 | template 37 | using integral_floating_point_t = typename integral_floating_point::type; 38 | 39 | template 40 | constexpr floating_point_integral_t to_integral(T value) noexcept { 41 | union { 42 | T input; 43 | floating_point_integral_t output; 44 | } data {value}; 45 | return data.output; 46 | } 47 | 48 | template 49 | constexpr integral_floating_point_t to_floating_point(T value) noexcept { 50 | union { 51 | T input; 52 | integral_floating_point_t output; 53 | } data {value}; 54 | return data.output; 55 | } 56 | 57 | } // namespace ozo::detail 58 | -------------------------------------------------------------------------------- /include/ozo/detail/endian.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace ozo::detail { 8 | 9 | enum class endian { 10 | #ifdef _WIN32 11 | little = 0, 12 | big = 1, 13 | native = little 14 | #else 15 | little = __ORDER_LITTLE_ENDIAN__, 16 | big = __ORDER_BIG_ENDIAN__, 17 | native = __BYTE_ORDER__ 18 | #endif 19 | }; 20 | 21 | template 22 | constexpr T byte_order_swap(T value, std::index_sequence) noexcept { 23 | return (((value >> N * CHAR_BIT & std::numeric_limits::max()) << (sizeof(T) - 1 - N) * CHAR_BIT) | ...); 24 | } 25 | 26 | template > 27 | constexpr Require convert_to_big_endian(T value) noexcept { 28 | return byte_order_swap(value, std::make_index_sequence {}); 29 | } 30 | 31 | template 32 | constexpr Require convert_to_big_endian(T value) noexcept { 33 | return value; 34 | } 35 | 36 | template > 37 | constexpr Require convert_from_big_endian(T value) noexcept { 38 | return byte_order_swap(value, std::make_index_sequence {}); 39 | } 40 | 41 | template 42 | constexpr Require convert_from_big_endian(T value) noexcept { 43 | return value; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /include/ozo/ext/boost/weak_ptr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | /** 8 | * @defgroup group-ext-boost-weak_ptr boost::weak_ptr 9 | * @ingroup group-ext-boost 10 | * @brief [boost::weak_ptr](https://www.boost.org/doc/libs/1_69_0/libs/smart_ptr/doc/html/smart_ptr.html#weak_ptr) support 11 | * 12 | *@code 13 | #include 14 | *@endcode 15 | * 16 | * `boost::weak_ptr` is defined as #Nullable and uses the implementation of `ozo::is_null()` examing the 17 | * result of `boost::weak_ptr::lock()` method call. 18 | * 19 | * The `ozo::unwrap()` function is implemented dereferencing the result of `boost::weak_ptr::lock()` 20 | * method call. 21 | * @note `ozo::unwrap()` from `boost::weak_ptr` is not safe for reference holding since no object owning. 22 | */ 23 | ///@{ 24 | template 25 | struct is_nullable> : std::true_type {}; 26 | 27 | template 28 | struct unwrap_impl> { 29 | template 30 | constexpr static decltype(auto) apply(T1&& v) noexcept(noexcept(*v.lock())) { 31 | return *v.lock(); 32 | } 33 | }; 34 | 35 | template 36 | struct is_null_impl> { 37 | constexpr static auto apply(const boost::weak_ptr& v) noexcept( 38 | noexcept(ozo::is_null(v.lock()))) { 39 | return ozo::is_null(v.lock()); 40 | } 41 | }; 42 | ///@} 43 | } // namespace ozo 44 | -------------------------------------------------------------------------------- /include/ozo/ext/std/time_point.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | 12 | namespace ozo { 13 | 14 | 15 | /** 16 | * @defgroup group-ext-std-chrono-system-clock-time-point std::chrono::system_clock::time_point 17 | * @ingroup group-ext-std 18 | * @brief [std::chrono::system_clock::time_point](https://en.cppreference.com/w/cpp/chrono/time_point) support 19 | * 20 | *@code 21 | #include 22 | *@endcode 23 | * 24 | * `std::chrono::system_clock::time_point` is defined as a point in time. 25 | */ 26 | 27 | template <> 28 | struct send_impl { 29 | template 30 | static ostream& apply(ostream& out, const OidMap&, const std::chrono::system_clock::time_point& in) { 31 | int64_t value = std::chrono::duration_cast(in - detail::epoch).count(); 32 | return write(out, value); 33 | } 34 | }; 35 | 36 | template <> 37 | struct recv_impl { 38 | template 39 | static istream& apply(istream& in, size_type, const OidMap&, std::chrono::system_clock::time_point& out) { 40 | int64_t value; 41 | read(in, value); 42 | out = detail::epoch + std::chrono::microseconds{ value }; 43 | return in; 44 | } 45 | }; 46 | 47 | } 48 | 49 | OZO_PG_BIND_TYPE(std::chrono::system_clock::time_point, "timestamp") 50 | -------------------------------------------------------------------------------- /include/ozo/core/none.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | 8 | /** 9 | * @brief None type 10 | * None type modelling void as ordinary type. This type is callable with return type void. 11 | * @ingroup group-core-types 12 | */ 13 | struct none_t { 14 | using type = void; 15 | template 16 | constexpr void operator() (Args&& ...) const noexcept {} 17 | template 18 | static constexpr void apply (Args&& ...) noexcept {} 19 | }; 20 | 21 | /** 22 | * @brief None object 23 | * @ingroup group-core-types 24 | * 25 | * Instance of ozo::none_t type. 26 | */ 27 | constexpr auto none = none_t{}; 28 | 29 | template <> 30 | struct is_time_constraint : std::true_type {}; 31 | 32 | inline constexpr std::true_type operator == (const none_t&, const none_t&) { return {};} 33 | 34 | template 35 | inline constexpr std::false_type operator == (const none_t&, const T&) { return {};} 36 | 37 | template 38 | inline constexpr std::false_type operator == (const T&, const none_t&) { return {};} 39 | 40 | inline constexpr std::false_type operator != (const none_t&, const none_t&) { return {};} 41 | 42 | template 43 | inline constexpr std::true_type operator != (const none_t&, const T&) { return {};} 44 | 45 | template 46 | inline constexpr std::true_type operator != (const T&, const none_t&) { return {};} 47 | 48 | template 49 | using is_none = decltype(std::declval() == none); 50 | 51 | template 52 | inline constexpr auto IsNone = is_none>::value; 53 | 54 | } // namespace ozo 55 | -------------------------------------------------------------------------------- /include/ozo/detail/bind.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace ozo { 11 | namespace detail { 12 | 13 | /** 14 | * This binder preserves handler context just like 15 | * boost::asio::detail::bind_handler does. It is 16 | * needed because in other case you will loose the handler 17 | * context. 18 | * 19 | * For more information see the link below: 20 | * 21 | * https://stackoverflow.com/questions/32857101/when-to-use-asio-handler-invoke 22 | */ 23 | template 24 | struct binder { 25 | Handler handler_; 26 | Tuple args_; 27 | 28 | auto operator() () { return std::apply(std::move(handler_), std::move(args_)); } 29 | 30 | using executor_type = decltype(asio::get_associated_executor(handler_)); 31 | 32 | executor_type get_executor() const noexcept { 33 | return asio::get_associated_executor(handler_); 34 | } 35 | 36 | using allocator_type = decltype(asio::get_associated_allocator(handler_)); 37 | 38 | allocator_type get_allocator() const noexcept { 39 | return asio::get_associated_allocator(handler_); 40 | } 41 | }; 42 | 43 | template 44 | inline auto bind(Handler&& h, Args&& ... args) { 45 | using result_type = binder< 46 | std::decay_t, 47 | std::decay_t(args)...))> 48 | >; 49 | return result_type { 50 | std::forward(h), 51 | std::make_tuple(std::forward(args)...) 52 | }; 53 | } 54 | 55 | } // namespace detail 56 | } // namespace ozo 57 | -------------------------------------------------------------------------------- /benchmarks/ozo_benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include "benchmark.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | int main(int argc, char *argv[]) { 13 | using namespace ozo::literals; 14 | using namespace ozo::benchmark; 15 | 16 | namespace asio = boost::asio; 17 | 18 | if (argc < 2) { 19 | std::cout << "Usage: " << argv[0] << " " << std::endl; 20 | return 1; 21 | } 22 | 23 | rows_count_limit_benchmark benchmark(10000000); 24 | asio::io_context io(1); 25 | ozo::connection_info connection_info(argv[1]); 26 | const auto query = ("SELECT typname, typnamespace, typowner, typlen, typbyval, typcategory, "_SQL + 27 | "typispreferred, typisdefined, typdelim, typrelid, typelem, typarray "_SQL + 28 | "FROM pg_type WHERE typtypmod = "_SQL + 29 | -1 + " AND typisdefined = "_SQL + true).build(); 30 | 31 | for (int i = 0; i < 8; ++i) { 32 | asio::spawn(io, [&] (auto yield) { 33 | try { 34 | auto connection = ozo::get_connection(connection_info[io], yield); 35 | benchmark.start(); 36 | while (true) { 37 | ozo::result result; 38 | ozo::request(connection, query, std::ref(result), yield); 39 | if (!benchmark.step(result.size())) { 40 | break; 41 | } 42 | } 43 | } catch (const std::exception& e) { 44 | std::cout << e.what() << '\n'; 45 | } 46 | }); 47 | } 48 | 49 | io.run(); 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_program(CCACHE_FOUND ccache) 2 | 3 | if(CCACHE_FOUND) 4 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) 5 | set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) 6 | endif() 7 | 8 | add_executable(ozo_benchmark ozo_benchmark.cpp) 9 | target_link_libraries(ozo_benchmark ozo) 10 | 11 | find_package(Boost COMPONENTS coroutine context system thread atomic program_options REQUIRED) 12 | 13 | include(ExternalProject) 14 | ExternalProject_Add( 15 | NlohmannJson 16 | GIT_REPOSITORY "https://github.com/nlohmann/json.git" 17 | GIT_TAG v3.7.3 18 | CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR} -DBUILD_TESTING=OFF 19 | UPDATE_COMMAND "" 20 | LOG_DOWNLOAD ON 21 | LOG_CONFIGURE ON 22 | LOG_BUILD ON 23 | ) 24 | include_directories(SYSTEM ${CMAKE_CURRENT_BINARY_DIR}/include) 25 | 26 | # enable a bunch of warnings and make them errors 27 | target_compile_options(ozo_benchmark PRIVATE -Wall -Wextra -Wsign-compare -pedantic -Werror) 28 | 29 | # ignore specific error for clang 30 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 31 | target_compile_options(ozo_benchmark PRIVATE -Wno-ignored-optimization-argument) 32 | endif() 33 | 34 | add_executable(ozo_benchmark_performance performance.cpp) 35 | add_dependencies(ozo_benchmark_performance NlohmannJson) 36 | target_link_libraries(ozo_benchmark_performance ozo) 37 | target_link_libraries(ozo_benchmark_performance Boost::program_options) 38 | 39 | # enable a bunch of warnings and make them errors 40 | target_compile_options(ozo_benchmark_performance PRIVATE -Wall -Wextra -Wsign-compare -pedantic -Werror) 41 | 42 | # ignore specific error for clang 43 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 44 | target_compile_options(ozo_benchmark_performance PRIVATE -Wno-ignored-optimization-argument) 45 | endif() 46 | -------------------------------------------------------------------------------- /tests/result_mock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace ozo::tests { 9 | 10 | using namespace testing; 11 | 12 | struct pg_result_mock { 13 | MOCK_CONST_METHOD1(field_type, ozo::oid_t(int column)); 14 | MOCK_CONST_METHOD1(field_format, ozo::impl::result_format(int column)); 15 | MOCK_CONST_METHOD2(get_value, const char*(int row, int column)); 16 | MOCK_CONST_METHOD2(get_length, std::size_t(int row, int column)); 17 | MOCK_CONST_METHOD2(get_isnull, bool(int row, int column)); 18 | MOCK_CONST_METHOD1(field_number, int(const char* name)); 19 | MOCK_CONST_METHOD0(nfields, int()); 20 | MOCK_CONST_METHOD0(ntuples, int()); 21 | 22 | friend ozo::oid_t pq_field_type(const pg_result_mock& m, int column) { 23 | return m.field_type(column); 24 | } 25 | 26 | friend ozo::impl::result_format pq_field_format(const pg_result_mock& m, int column) { 27 | return m.field_format(column); 28 | } 29 | 30 | friend const char* pq_get_value(const pg_result_mock& m, int row, int column) { 31 | return m.get_value(row, column); 32 | } 33 | 34 | friend std::size_t pq_get_length(const pg_result_mock& m, int row, int column) { 35 | return m.get_length(row, column); 36 | } 37 | 38 | friend bool pq_get_isnull(const pg_result_mock& m, int row, int column) { 39 | return m.get_isnull(row, column); 40 | } 41 | 42 | friend int pq_field_number(const pg_result_mock& m, const char* name) { 43 | return m.field_number(name); 44 | } 45 | 46 | friend int pq_nfields(const pg_result_mock& m) { 47 | return m.nfields(); 48 | } 49 | 50 | friend int pq_ntuples(const pg_result_mock& m) { 51 | return m.ntuples(); 52 | } 53 | }; 54 | 55 | } // namespace ozo::tests 56 | -------------------------------------------------------------------------------- /include/ozo/pg/types/jsonb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace ozo::pg { 10 | 11 | class jsonb { 12 | friend send_impl; 13 | friend recv_impl; 14 | friend size_of_impl; 15 | 16 | public: 17 | jsonb() = default; 18 | 19 | jsonb(std::string raw_string) noexcept 20 | : value(std::move(raw_string)) {} 21 | 22 | std::string raw_string() const & noexcept { 23 | return value; 24 | } 25 | 26 | std::string raw_string() && noexcept { 27 | return std::move(value); 28 | } 29 | 30 | private: 31 | std::string value; 32 | }; 33 | 34 | } // namespace ozo::pg 35 | 36 | namespace ozo { 37 | 38 | template <> 39 | struct size_of_impl { 40 | static auto apply(const pg::jsonb& v) noexcept { 41 | return std::size(v.value) + 1; 42 | } 43 | }; 44 | 45 | template <> 46 | struct send_impl { 47 | template 48 | static ostream& apply(ostream& out, const OidMap&, const pg::jsonb& in) { 49 | const std::int8_t version = 1; 50 | write(out, version); 51 | return write(out, in.value); 52 | } 53 | }; 54 | 55 | template <> 56 | struct recv_impl { 57 | template 58 | static istream& apply(istream& in, size_type size, const OidMap&, pg::jsonb& out) { 59 | if (size < 1) { 60 | throw std::range_error("data size " + std::to_string(size) + " is too small to read jsonb"); 61 | } 62 | std::int8_t version; 63 | read(in, version); 64 | out.value.resize(static_cast(size - 1)); 65 | return read(in, out.value); 66 | } 67 | }; 68 | 69 | } // namespace ozo 70 | 71 | OZO_PG_BIND_TYPE(ozo::pg::jsonb, "jsonb") 72 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | ozo_build: 5 | build: docker/build 6 | image: ozo_build 7 | privileged: true # https://github.com/google/sanitizers/issues/764 8 | environment: 9 | BASE_BUILD_DIR: build/docker 10 | volumes: 11 | - ~/.ccache:/ccache 12 | - .:/code 13 | ozo_postgres: 14 | image: postgres:alpine 15 | environment: 16 | POSTGRES_DB: &POSTGRES_DB ozo_test_db 17 | POSTGRES_USER: &POSTGRES_USER ozo_test_user 18 | POSTGRES_PASSWORD: &POSTGRES_PASSWORD 'v4Xpkocl~5l6h219Ynk1lJbM61jIr!ca' 19 | networks: 20 | - ozo 21 | ozo_build_with_pg_tests: 22 | build: docker/build 23 | image: ozo_build 24 | privileged: true # https://github.com/google/sanitizers/issues/764 25 | environment: 26 | BASE_BUILD_DIR: build/docker_with_pg_tests 27 | OZO_BUILD_PG_TESTS: 'ON' 28 | POSTGRES_HOST: ozo_postgres 29 | POSTGRES_DB: *POSTGRES_DB 30 | POSTGRES_USER: *POSTGRES_USER 31 | POSTGRES_PASSWORD: *POSTGRES_PASSWORD 32 | depends_on: 33 | - ozo_postgres 34 | volumes: 35 | - ~/.ccache:/ccache 36 | - .:/code 37 | networks: 38 | - ozo 39 | asyncpg: 40 | build: docker/asyncpg 41 | image: asyncpg 42 | environment: 43 | POSTGRES_HOST: ozo_postgres 44 | POSTGRES_DB: *POSTGRES_DB 45 | POSTGRES_USER: *POSTGRES_USER 46 | POSTGRES_PASSWORD: *POSTGRES_PASSWORD 47 | depends_on: 48 | - ozo_postgres 49 | volumes: 50 | - .:/code 51 | networks: 52 | - ozo 53 | aiopg: 54 | build: docker/aiopg 55 | image: aiopg 56 | environment: 57 | POSTGRES_HOST: ozo_postgres 58 | POSTGRES_DB: *POSTGRES_DB 59 | POSTGRES_USER: *POSTGRES_USER 60 | POSTGRES_PASSWORD: *POSTGRES_PASSWORD 61 | depends_on: 62 | - ozo_postgres 63 | volumes: 64 | - .:/code 65 | networks: 66 | - ozo 67 | networks: 68 | ozo: {} 69 | -------------------------------------------------------------------------------- /tests/error.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | namespace { 7 | 8 | TEST(connection_error, should_match_to_mapped_errors_only) { 9 | const auto connection_error = ozo::error_condition{ozo::errc::connection_error}; 10 | EXPECT_EQ(connection_error, ozo::sqlstate::make_error_code(ozo::sqlstate::connection_does_not_exist)); 11 | EXPECT_EQ(connection_error, boost::asio::error::connection_aborted); 12 | EXPECT_EQ(connection_error, boost::system::errc::make_error_code(boost::system::errc::io_error)); 13 | EXPECT_EQ(connection_error, ozo::error::pq_socket_failed); 14 | EXPECT_NE(connection_error, ozo::error::bad_object_size); 15 | } 16 | 17 | TEST(database_readonly, should_match_to_mapped_errors_only) { 18 | const auto database_readonly = ozo::error_condition{ozo::errc::database_readonly}; 19 | EXPECT_EQ(database_readonly, ozo::sqlstate::make_error_code(ozo::sqlstate::read_only_sql_transaction)); 20 | EXPECT_NE(database_readonly, ozo::error::pq_socket_failed); 21 | } 22 | 23 | TEST(introspection_error, should_match_to_mapped_errors_only) { 24 | const auto introspection_error = ozo::error_condition{ozo::errc::introspection_error}; 25 | EXPECT_EQ(introspection_error, ozo::error::bad_object_size); 26 | EXPECT_NE(introspection_error, ozo::error::pq_socket_failed); 27 | } 28 | 29 | TEST(type_mismatch, should_match_to_mapped_errors_only) { 30 | const auto type_mismatch = ozo::error_condition{ozo::errc::type_mismatch}; 31 | EXPECT_EQ(type_mismatch, ozo::error::oid_type_mismatch); 32 | EXPECT_NE(type_mismatch, ozo::error::pq_socket_failed); 33 | } 34 | 35 | TEST(protocol_error, should_match_to_mapped_errors_only) { 36 | const auto protocol_error = ozo::error_condition{ozo::errc::protocol_error}; 37 | EXPECT_EQ(protocol_error, ozo::error::no_sql_state_found); 38 | EXPECT_NE(protocol_error, ozo::error::pq_socket_failed); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /tests/detail/functional.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace { 6 | 7 | template 8 | struct test_functional { 9 | static constexpr int apply(const T&, int v) { return v;} 10 | }; 11 | 12 | template 13 | struct test_noexcept_functional { 14 | static constexpr int apply(const T&, int v) noexcept { return v;} 15 | }; 16 | 17 | template 18 | struct test_dispatch {}; 19 | 20 | template <> 21 | struct test_dispatch { 22 | static constexpr int apply(const std::string&, int) { return 777;} 23 | }; 24 | 25 | TEST(IsApplicable, should_return_true_for_applicable_functional_arguments) { 26 | const bool res = ozo::detail::IsApplicable; 27 | EXPECT_TRUE(res); 28 | } 29 | 30 | TEST(IsApplicable, should_return_false_for_non_applicable_functional_arguments) { 31 | const bool res = ozo::detail::IsApplicable; 32 | EXPECT_FALSE(res); 33 | } 34 | 35 | TEST(result_of, should_return_type_of_functional_result) { 36 | using type = ozo::detail::result_of; 37 | const bool res = std::is_same_v; 38 | EXPECT_TRUE(res); 39 | } 40 | 41 | TEST(apply, should_invoke_functional_and_return_result) { 42 | const auto res = ozo::detail::apply(std::string{}, 42); 43 | EXPECT_EQ(res, 42); 44 | } 45 | 46 | TEST(apply, should_dispatch_functional_by_first_argument) { 47 | const auto res = ozo::detail::apply(std::string{}, 42); 48 | EXPECT_EQ(res, 777); 49 | } 50 | 51 | TEST(apply, should_be_no_noexcept_if_implementation_is_not_noexcept) { 52 | EXPECT_FALSE(noexcept(ozo::detail::apply(std::string{}, 42))); 53 | } 54 | 55 | TEST(apply, should_be_noexcept_if_implementation_is_noexcept) { 56 | EXPECT_TRUE(noexcept(ozo::detail::apply(std::string{}, 42))); 57 | } 58 | 59 | } // namespace 60 | -------------------------------------------------------------------------------- /include/ozo/pg/types/ltree.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace ozo::pg { 10 | 11 | class ltree { 12 | friend send_impl; 13 | friend recv_impl; 14 | friend size_of_impl; 15 | 16 | public: 17 | ltree() = default; 18 | 19 | ltree(std::string raw_string) noexcept 20 | : value(std::move(raw_string)) {} 21 | 22 | const std::string& raw_string() const & noexcept { 23 | return value; 24 | } 25 | 26 | std::string raw_string() && noexcept { 27 | return std::move(value); 28 | } 29 | 30 | friend bool operator ==(const ltree& lhs, const ltree& rhs) { 31 | return lhs.value == rhs.value; 32 | } 33 | 34 | private: 35 | std::string value; 36 | }; 37 | 38 | } // namespace ozo::pg 39 | 40 | namespace ozo { 41 | 42 | template <> 43 | struct size_of_impl { 44 | static auto apply(const pg::ltree& v) noexcept { 45 | return std::size(v.value) + 1; 46 | } 47 | }; 48 | 49 | template <> 50 | struct send_impl { 51 | template 52 | static ostream& apply(ostream& out, const OidMap&, const pg::ltree& in) { 53 | const std::int8_t version = 1; 54 | write(out, version); 55 | return write(out, in.value); 56 | } 57 | }; 58 | 59 | template <> 60 | struct recv_impl { 61 | template 62 | static istream& apply(istream& in, size_type size, const OidMap&, pg::ltree& out) { 63 | if (size < 1) { 64 | throw std::range_error("data size " + std::to_string(size) + " is too small to read ltree"); 65 | } 66 | std::int8_t version; 67 | read(in, version); 68 | out.value.resize(static_cast(size - 1)); 69 | return read(in, out.value); 70 | } 71 | }; 72 | 73 | } // namespace ozo 74 | 75 | OZO_PG_DEFINE_CUSTOM_TYPE(ozo::pg::ltree, "ltree") 76 | -------------------------------------------------------------------------------- /tests/deadline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | namespace { 7 | 8 | using namespace testing; 9 | using namespace std::literals; 10 | 11 | using time_point = ozo::time_traits::time_point; 12 | using duration = ozo::time_traits::duration; 13 | 14 | TEST(deadline, should_return_its_argument_for_time_point_type) { 15 | EXPECT_EQ(ozo::deadline(time_point{}), time_point{}); 16 | } 17 | 18 | TEST(deadline, should_return_none_for_ozo_none_t_type) { 19 | EXPECT_EQ(ozo::deadline(ozo::none_t{}), ozo::none); 20 | } 21 | 22 | TEST(deadline, should_return_proper_time_point_for_time_point_and_duration) { 23 | EXPECT_EQ(ozo::deadline(1s, time_point{}), time_point{} + 1s); 24 | } 25 | 26 | TEST(deadline, should_return_time_point_max_on_saturation) { 27 | EXPECT_EQ(ozo::deadline(duration::max(), time_point{} + 1s), 28 | time_point::max()); 29 | } 30 | 31 | TEST(deadline, should_return_time_point_argument_on_negative_duration) { 32 | EXPECT_EQ(ozo::deadline(-1s, time_point{}), time_point{}); 33 | } 34 | 35 | TEST(time_left, should_return_duration_for_time_point_less_than_deadline) { 36 | EXPECT_EQ(ozo::time_left(time_point{} + 1s, time_point{}), 1s); 37 | } 38 | 39 | TEST(time_left, should_return_zero_for_time_point_equal_to_deadline) { 40 | EXPECT_EQ(ozo::time_left(time_point{}, time_point{}), duration(0)); 41 | } 42 | 43 | TEST(time_left, should_return_zero_for_time_point_greater_than_deadline) { 44 | EXPECT_EQ(ozo::time_left(time_point{}, time_point{} + 1s), duration(0)); 45 | } 46 | 47 | TEST(expired, should_return_false_for_time_point_less_than_deadline) { 48 | EXPECT_FALSE(ozo::expired(time_point{} + 1s, time_point{})); 49 | } 50 | 51 | TEST(expired, should_return_true_for_time_point_equal_to_deadline) { 52 | EXPECT_TRUE(ozo::expired(time_point{}, time_point{})); 53 | } 54 | 55 | TEST(expired, should_return_true_for_time_point_greater_than_deadline) { 56 | EXPECT_TRUE(ozo::expired(time_point{}, time_point{} + 1s)); 57 | } 58 | 59 | } // namespace 60 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | sudo: required 4 | 5 | cache: 6 | ccache: true 7 | directories: 8 | - ${HOME}/.ccache 9 | 10 | addons: 11 | apt: 12 | packages: 13 | - python3-pip 14 | - python3-setuptools 15 | 16 | services: 17 | - docker 18 | 19 | install: 20 | - pip3 install --user --upgrade "pip>=20.3.4,<21.0" 21 | - pip3 install --user docker-compose 22 | 23 | matrix: 24 | include: 25 | # - os: linux 26 | # dist: xenial 27 | # env: BUILD_ARGS='pg docker gcc coverage' 28 | # after_success: 29 | # - > 30 | # docker run -ti --rm -v ${PWD}:/code $(bash -e <(curl -s https://codecov.io/env)) \ 31 | # ozo_build /codecov.sh 32 | 33 | - os: linux 34 | dist: xenial 35 | env: BUILD_ARGS='pg docker gcc debug' 36 | 37 | - os: linux 38 | dist: xenial 39 | env: BUILD_ARGS='pg docker gcc release' 40 | 41 | - os: linux 42 | dist: xenial 43 | env: BUILD_ARGS='pg docker gcc test_external_project' 44 | 45 | - os: linux 46 | dist: xenial 47 | env: BUILD_ARGS='pg docker clang debug' 48 | 49 | - os: linux 50 | dist: xenial 51 | env: BUILD_ARGS='pg docker clang release' 52 | 53 | - os: linux 54 | dist: xenial 55 | env: BUILD_ARGS='pg docker clang asan' 56 | 57 | - os: linux 58 | dist: xenial 59 | env: BUILD_ARGS='pg docker clang ubsan' 60 | 61 | - os: linux 62 | dist: xenial 63 | env: BUILD_ARGS='pg docker clang tsan' 64 | 65 | - os: linux 66 | dist: xenial 67 | env: BUILD_ARGS='pg docker clang test_external_project' 68 | 69 | - os: linux 70 | dist: xenial 71 | env: BUILD_ARGS='docker gcc conan' 72 | 73 | - os: linux 74 | dist: xenial 75 | env: BUILD_ARGS='docker clang conan' 76 | 77 | - os: osx 78 | osx_image: xcode11 79 | install: 80 | # - brew update 81 | # - brew unlink python@2 82 | # - brew upgrade boost 83 | # - brew upgrade postgresql 84 | # - brew upgrade cmake 85 | - brew install ccache 86 | env: BUILD_ARGS='clang debug' 87 | 88 | script: scripts/build.sh ${BUILD_ARGS} 89 | -------------------------------------------------------------------------------- /include/ozo/transaction_status.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo { 6 | 7 | /** 8 | * @brief Transaction status of a `Connection` 9 | * 10 | * Indicates current transaction status of a `Connection` object. It reflects (but not equal!) libpq 11 | * PGTransactionStatusType. 12 | * 13 | * @note In case of developing own connection pool, it must be taking in account what `Connection` 14 | * can be reused only with `ozo::transaction_status::idle` status, in case of the other status 15 | * the `Connection` shall be closed rather than collected back into the pool. 16 | * 17 | * @ingroup group-connection-types 18 | */ 19 | enum class transaction_status { 20 | unknown, //!< transaction state is unknown due to bad or invalid connection object, reflects `PQTRANS_UNKNOWN` 21 | idle, //!< connection is in idle state and can be reused, reflects `QTRANS_IDLE` 22 | active, //!< command execution is in progress, reflects `PQTRANS_ACTIVE` 23 | transaction, //!< idle, but within transaction block, reflects `PQTRANS_INTRANS` 24 | error, //!< idle, within failed transaction, reflects `PQTRANS_INERROR` 25 | }; 26 | 27 | /** 28 | * @brief Returns current status of a `Connection` object 29 | * 30 | * Returns current status of specified `Connection`. If the `Connection` is 31 | * #Nullable in null-state returns `ozo::transaction_status::unknown`. In other 32 | * case it returns value associated with libpq connection transaction status. 33 | * 34 | * @param conn --- `Connection` 35 | * @return transaction_status --- status of the `Connection` 36 | * @throws std::invalid_argument --- in case of unsupported value returned by libpq 37 | * function. It is better to use `-Werror` compiler flag to prevent such possability by 38 | * checking enumeration coverage in the `switch-case` statement. 39 | * 40 | * @ingroup group-connection-functions 41 | */ 42 | template 43 | inline transaction_status get_transaction_status(Connection&& conn); 44 | 45 | } // namespace ozo 46 | 47 | #include 48 | -------------------------------------------------------------------------------- /docker/build/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y apt-transport-https ca-certificates gnupg software-properties-common wget 5 | 6 | RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | apt-key add - 7 | 8 | RUN apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' 9 | 10 | RUN apt-get update && \ 11 | apt-get install -y \ 12 | ccache \ 13 | clang-7 \ 14 | curl \ 15 | g++-8 \ 16 | git \ 17 | gdb \ 18 | libpq-dev \ 19 | llvm-7 \ 20 | postgresql-client \ 21 | postgresql-server-dev-all \ 22 | python-openssl \ 23 | python-pip \ 24 | python-yaml \ 25 | python3-pip \ 26 | cmake \ 27 | doxygen \ 28 | && \ 29 | rm -rf /var/lib/apt/lists/* 30 | 31 | RUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-7 100 && \ 32 | update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-7 100 && \ 33 | update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 100 && \ 34 | update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 100 && \ 35 | update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-8 100 && \ 36 | update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-7 100 37 | 38 | RUN wget -qO boost_1_66_0.tar.gz https://boostorg.jfrog.io/artifactory/main/release/1.66.0/source/boost_1_66_0.tar.gz && \ 39 | tar xzf boost_1_66_0.tar.gz && \ 40 | cd boost_1_66_0 && \ 41 | ./bootstrap.sh --with-libraries=atomic,system,thread,chrono,date_time,context,coroutine,program_options && \ 42 | ./b2 \ 43 | -j $(nproc) \ 44 | --reconfigure \ 45 | link=static \ 46 | threading=multi \ 47 | variant=release \ 48 | cxxflags='-std=c++17 -DBOOST_COROUTINES_NO_DEPRECATION_WARNING' \ 49 | debug-symbols=on \ 50 | warnings=off \ 51 | install 52 | 53 | RUN pip install gcovr && \ 54 | pip3 install conan 55 | 56 | RUN wget -qO /codecov.sh https://codecov.io/bash && chmod +x /codecov.sh 57 | 58 | VOLUME /ccache 59 | VOLUME /code 60 | 61 | WORKDIR /code 62 | 63 | ENV CCACHE_DIR=/ccache 64 | -------------------------------------------------------------------------------- /tests/detail/make_copyable.cpp: -------------------------------------------------------------------------------- 1 | #include "test_asio.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace { 11 | 12 | using namespace testing; 13 | using namespace ozo::tests; 14 | 15 | namespace asio = boost::asio; 16 | 17 | struct make_copyable : Test { 18 | StrictMock> cb_mock {}; 19 | ozo::tests::execution_context io; 20 | }; 21 | 22 | struct handler_mock { 23 | MOCK_METHOD2(call_ref, void(ozo::error_code, int)); 24 | MOCK_METHOD2(call_rval_ref, void(ozo::error_code, int)); 25 | MOCK_CONST_METHOD2(call_const_ref, void(ozo::error_code, int)); 26 | }; 27 | 28 | struct handler_obj { 29 | handler_mock& mock_; 30 | 31 | void operator()(ozo::error_code ec, int v) & { mock_.call_ref(ec, v); } 32 | void operator()(ozo::error_code ec, int v) && { mock_.call_rval_ref(ec, v); } 33 | void operator()(ozo::error_code ec, int v) const & { mock_.call_const_ref(ec, v); } 34 | }; 35 | 36 | TEST_F(make_copyable, should_provide_handler_executor) { 37 | EXPECT_CALL(cb_mock, get_executor()).WillOnce(Return(io.get_executor())); 38 | 39 | EXPECT_EQ(ozo::detail::make_copyable(wrap(cb_mock)).get_executor(), io.get_executor()); 40 | } 41 | 42 | TEST_F(make_copyable, should_call_wrapped_handler) { 43 | handler_mock mock; 44 | auto obj = ozo::detail::make_copyable(handler_obj{mock}); 45 | 46 | EXPECT_CALL(mock, call_ref(ozo::error_code{}, 42)); 47 | static_cast(obj)(ozo::error_code{}, 42); 48 | 49 | EXPECT_CALL(mock, call_const_ref(ozo::error_code{}, 42)); 50 | static_cast(obj)(ozo::error_code{}, 42); 51 | 52 | EXPECT_CALL(mock, call_rval_ref(ozo::error_code{}, 42)); 53 | static_cast(obj)(ozo::error_code{}, 42); 54 | } 55 | 56 | TEST(make_copyable_t, should_forward_copyable_handler) { 57 | struct copyable {}; 58 | EXPECT_TRUE((std::is_same_v, copyable>)); 59 | } 60 | 61 | TEST(make_copyable_t, should_wrap_non_copyable_handler) { 62 | struct non_copyable { 63 | std::unique_ptr v_; 64 | }; 65 | EXPECT_TRUE((std::is_same_v, ozo::detail::make_copyable>)); 66 | } 67 | 68 | } // namespace 69 | -------------------------------------------------------------------------------- /tests/transaction_status.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "connection_mock.h" 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace { 9 | 10 | using namespace testing; 11 | 12 | struct get_transaction_status : Test { 13 | ozo::tests::io_context io; 14 | StrictMock handle; 15 | auto make_connection() { 16 | using namespace ozo::tests; 17 | EXPECT_CALL(handle, PQstatus()).WillRepeatedly(Return(CONNECTION_OK)); 18 | return std::make_shared>(connection<>{ 19 | std::addressof(handle), 20 | {}, nullptr, "", nullptr 21 | }); 22 | } 23 | }; 24 | 25 | TEST_F(get_transaction_status, should_return_transaction_status_unknown_for_null_transaction) { 26 | const auto conn = ozo::tests::connection_ptr<>(); 27 | EXPECT_EQ(ozo::transaction_status::unknown, ozo::get_transaction_status(conn)); 28 | } 29 | 30 | TEST_F(get_transaction_status, should_return_throw_for_unsupported_status) { 31 | const auto conn = make_connection(); 32 | EXPECT_CALL(handle, PQtransactionStatus()) 33 | .WillOnce(Return(static_cast(-1))); 34 | EXPECT_THROW(ozo::get_transaction_status(conn), std::invalid_argument); 35 | } 36 | 37 | namespace with_params { 38 | 39 | struct get_transaction_status : ::get_transaction_status, 40 | WithParamInterface> { 41 | }; 42 | 43 | TEST_P(get_transaction_status, should_return_status_for_connection){ 44 | const auto conn = make_connection(); 45 | EXPECT_CALL(handle, PQtransactionStatus()) 46 | .WillOnce(Return(std::get<0>(GetParam()))); 47 | EXPECT_EQ(std::get<1>(GetParam()), ozo::get_transaction_status(conn)); 48 | } 49 | 50 | INSTANTIATE_TEST_SUITE_P( 51 | with_any_PGTransactionStatusType, 52 | get_transaction_status, 53 | testing::Values( 54 | std::make_tuple(PQTRANS_UNKNOWN, ozo::transaction_status::unknown), 55 | std::make_tuple(PQTRANS_IDLE, ozo::transaction_status::idle), 56 | std::make_tuple(PQTRANS_ACTIVE, ozo::transaction_status::active), 57 | std::make_tuple(PQTRANS_INTRANS, ozo::transaction_status::transaction), 58 | std::make_tuple(PQTRANS_INERROR, ozo::transaction_status::error) 59 | ) 60 | ); 61 | 62 | } // namespace with_params 63 | 64 | } 65 | -------------------------------------------------------------------------------- /include/ozo/io/ostream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | namespace ozo { 17 | 18 | class ostream { 19 | public: 20 | using traits_type = std::ostream::traits_type; 21 | using char_type = std::ostream::char_type; 22 | 23 | ostream(std::vector& buf) : buf_(buf) {} 24 | 25 | ostream& write(const char_type* s, std::streamsize n) { 26 | buf_.insert(buf_.end(), s, s + n); 27 | return *this; 28 | } 29 | 30 | ostream& put(char_type ch) { 31 | buf_.push_back(ch); 32 | return *this; 33 | } 34 | 35 | template 36 | Require && sizeof(T) == 1, ostream&> write(T in) { 37 | return put(static_cast(in)); 38 | } 39 | 40 | template 41 | Require, ostream&> write(const T& in) { 42 | using std::data; 43 | using std::size; 44 | return write(reinterpret_cast(data(in)), size(in)); 45 | } 46 | 47 | template 48 | Require && sizeof(T) != 1, ostream&> write(T in) { 49 | detail::typed_buffer buf; 50 | buf.typed = detail::convert_to_big_endian(in); 51 | return write(buf.raw); 52 | } 53 | 54 | template 55 | Require, ostream&> write(T in) { 56 | return write(detail::to_integral(in)); 57 | } 58 | 59 | ostream& write(bool in) { 60 | const char_type value = in ? char_type(1) : char_type(0); 61 | return write(value); 62 | } 63 | 64 | template 65 | Require, ostream&> write(const T& in) { 66 | hana::for_each(in, [&](auto& item) { write(item); }); 67 | return *this; 68 | } 69 | 70 | template 71 | Require, ostream&> write(const T& in) { 72 | return write(hana::members(in)); 73 | } 74 | 75 | private: 76 | std::vector& buf_; 77 | }; 78 | 79 | template 80 | inline ostream& write(ostream& out, Ts&& ...vs) { 81 | return out.write(std::forward(vs)...); 82 | } 83 | 84 | } // namespace ozo 85 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_program(CCACHE_FOUND ccache) 2 | 3 | if(CCACHE_FOUND) 4 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) 5 | set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) 6 | endif() 7 | 8 | add_executable(ozo_request request.cpp) 9 | target_link_libraries(ozo_request ozo) 10 | 11 | # enable a bunch of warnings and make them errors 12 | target_compile_options(ozo_request PRIVATE -Wall -Wextra -Wsign-compare -pedantic -Werror) 13 | 14 | # ignore specific errors for clang 15 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 16 | target_compile_options(ozo_request PRIVATE -Wno-ignored-optimization-argument) 17 | endif() 18 | 19 | add_executable(ozo_transaction transaction.cpp) 20 | target_link_libraries(ozo_transaction ozo) 21 | 22 | # enable a bunch of warnings and make them errors 23 | target_compile_options(ozo_transaction PRIVATE -Wall -Wextra -Wsign-compare -pedantic -Werror) 24 | 25 | # ignore specific errors for clang 26 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 27 | target_compile_options(ozo_transaction PRIVATE -Wno-ignored-optimization-argument) 28 | endif() 29 | 30 | add_executable(ozo_retry_request retry_request.cpp) 31 | target_link_libraries(ozo_retry_request ozo) 32 | 33 | # enable a bunch of warnings and make them errors 34 | target_compile_options(ozo_retry_request PRIVATE -Wall -Wextra -Wsign-compare -pedantic -Werror) 35 | 36 | # ignore specific errors for clang 37 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 38 | target_compile_options(ozo_retry_request PRIVATE -Wno-ignored-optimization-argument) 39 | endif() 40 | 41 | add_executable(ozo_role_based_request role_based_request.cpp) 42 | target_link_libraries(ozo_role_based_request ozo) 43 | 44 | # enable a bunch of warnings and make them errors 45 | target_compile_options(ozo_role_based_request PRIVATE -Wall -Wextra -Wsign-compare -pedantic -Werror) 46 | 47 | # ignore specific errors for clang 48 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 49 | target_compile_options(ozo_role_based_request PRIVATE -Wno-ignored-optimization-argument) 50 | endif() 51 | 52 | add_executable(ozo_connection_pool connection_pool.cpp) 53 | target_link_libraries(ozo_connection_pool ozo) 54 | 55 | # enable a bunch of warnings and make them errors 56 | target_compile_options(ozo_connection_pool PRIVATE -Wall -Wextra -Wsign-compare -pedantic -Werror) 57 | 58 | # ignore specific errors for clang 59 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 60 | target_compile_options(ozo_connection_pool PRIVATE -Wno-ignored-optimization-argument) 61 | endif() 62 | -------------------------------------------------------------------------------- /scripts/benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | docker-compose build asyncpg aiopg ozo_build_with_pg_tests 4 | 5 | if ! [[ "${OZO_BENCHMARK_COMPILER}" ]]; then 6 | OZO_BENCHMARK_COMPILER=clang 7 | fi 8 | 9 | if ! [[ "${OZO_BENCHMARK_BUILD_TYPE}" ]]; then 10 | OZO_BENCHMARK_BUILD_TYPE=release 11 | fi 12 | 13 | scripts/build.sh pg docker ${OZO_BENCHMARK_COMPILER} ${OZO_BENCHMARK_BUILD_TYPE} 14 | 15 | function run_benchmark { 16 | docker-compose run \ 17 | --rm \ 18 | --user "$(id -u):$(id -g)" \ 19 | ${1:?} \ 20 | bash \ 21 | -exc "/code/scripts/wait_postgres.sh; ${2:?} ${3:?}" 22 | } 23 | 24 | run_benchmark asyncpg benchmarks/asyncpg_benchmark.py '"postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}/${POSTGRES_DB}"' 25 | run_benchmark aiopg benchmarks/aiopg_benchmark.py '"host=${POSTGRES_HOST} user=${POSTGRES_USER} dbname=${POSTGRES_DB} password=${POSTGRES_PASSWORD}"' 26 | run_benchmark ozo_build_with_pg_tests "\${BASE_BUILD_DIR}/${OZO_BENCHMARK_COMPILER}_${OZO_BENCHMARK_BUILD_TYPE}/benchmarks/ozo_benchmark" \ 27 | '"host=${POSTGRES_HOST} user=${POSTGRES_USER} dbname=${POSTGRES_DB} password=${POSTGRES_PASSWORD}"' 28 | 29 | function run_ozo_benchmark_performance { 30 | run_benchmark ozo_build_with_pg_tests "\${BASE_BUILD_DIR}/${OZO_BENCHMARK_COMPILER}_${OZO_BENCHMARK_BUILD_TYPE}/benchmarks/ozo_benchmark_performance" \ 31 | "${1:?} --conninfo=\"host=\${POSTGRES_HOST} user=\${POSTGRES_USER} dbname=\${POSTGRES_DB} password=\${POSTGRES_PASSWORD}\"" 32 | } 33 | 34 | run_ozo_benchmark_performance '--benchmark=reopen_connection --query=simple' 35 | run_ozo_benchmark_performance '--benchmark=reuse_connection --query=simple' 36 | run_ozo_benchmark_performance '--benchmark=use_connection_pool --query=simple --coroutines=1' 37 | run_ozo_benchmark_performance '--benchmark=use_connection_pool --query=simple --coroutines=2' 38 | run_ozo_benchmark_performance '--benchmark=use_connection_pool_mult_threads --query=simple --coroutines=2 --threads=2 --connections=5 --queue=0' 39 | run_ozo_benchmark_performance '--benchmark=use_connection_pool_mult_threads --query=simple --coroutines=2 --threads=2 --connections=2 --queue=4' 40 | run_ozo_benchmark_performance '--benchmark=use_connection_pool --query=simple --coroutines=1 --parse' 41 | run_ozo_benchmark_performance '--benchmark=use_connection_pool --query=complex --coroutines=1' 42 | run_ozo_benchmark_performance '--benchmark=use_connection_pool --query=complex --coroutines=1 --parse' 43 | 44 | docker-compose stop ozo_postgres 45 | docker-compose rm -f ozo_postgres 46 | -------------------------------------------------------------------------------- /include/ozo/detail/make_copyable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace ozo::detail { 9 | 10 | /** 11 | * @brief Wrapper to make Handler object copyable 12 | * 13 | * Sometimes asynchronous API does make a copy of completion handlers 14 | * (e.g. at that moment resource_pool does). In this case to do not obligate 15 | * user to use only copyable completion handlers it is better to provide 16 | * convenient wrapper for make a completion handler copyable. 17 | * 18 | * @tparam Handler 19 | */ 20 | template 21 | struct make_copyable { 22 | using target_type = std::decay_t; 23 | using type = std::conditional_t, 24 | target_type, 25 | make_copyable>; 26 | 27 | using executor_type = typename asio::associated_executor::type; 28 | 29 | executor_type get_executor() const noexcept { 30 | return asio::get_associated_executor(*handler_); 31 | } 32 | 33 | using allocator_type = typename asio::associated_allocator::type; 34 | 35 | allocator_type get_allocator() const noexcept { 36 | return asio::get_associated_allocator(*handler_); 37 | } 38 | 39 | make_copyable(Handler&& handler) 40 | : make_copyable(std::allocator_arg, asio::get_associated_allocator(handler), std::move(handler)) {} 41 | 42 | template 43 | make_copyable(const std::allocator_arg_t&, const Allocator& alloc, Ts&& ...vs) 44 | : handler_(std::allocate_shared(alloc, std::forward(vs)...)) {} 45 | 46 | template 47 | auto operator()(Args&& ...args) & { 48 | return static_cast(*this)(std::forward(args)...); 49 | } 50 | 51 | template 52 | auto operator()(Args&& ...args) const & { 53 | return static_cast(*this)(std::forward(args)...); 54 | } 55 | 56 | template 57 | auto operator()(Args&& ...args) && { 58 | return static_cast(static_cast(*this))(std::forward(args)...); 59 | } 60 | 61 | operator const target_type& () const & { return *handler_;} 62 | 63 | operator target_type& () & { return *handler_;} 64 | 65 | operator target_type&& () && { return static_cast(*handler_);} 66 | 67 | std::shared_ptr handler_; 68 | }; 69 | 70 | template 71 | using make_copyable_t = typename make_copyable::type; 72 | 73 | } // namespace ozo::detail 74 | -------------------------------------------------------------------------------- /tests/concept.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace { 12 | 13 | TEST(ForwardIterator, should_return_true_for_iterator_type) { 14 | EXPECT_TRUE(ozo::ForwardIterator::iterator>); 15 | } 16 | 17 | TEST(ForwardIterator, should_return_false_for_not_iterator_type) { 18 | EXPECT_FALSE(ozo::ForwardIterator); 19 | } 20 | 21 | TEST(Iterable, should_return_true_for_iterable_type) { 22 | EXPECT_TRUE(ozo::Iterable>); 23 | } 24 | 25 | TEST(Iterable, should_return_false_for_not_iterable_type) { 26 | EXPECT_FALSE(ozo::Iterable); 27 | } 28 | 29 | TEST(RawDataWritable, should_return_true_for_type_with_mutable_data_method_and_non_const_result) { 30 | EXPECT_TRUE(ozo::RawDataWritable); 31 | } 32 | 33 | TEST(RawDataWritable, should_return_false_for_type_without_mutable_data_method_or_non_const_result) { 34 | EXPECT_FALSE(ozo::RawDataWritable); 35 | } 36 | 37 | TEST(RawDataWritable, should_return_false_for_type_with_data_point_to_more_than_a_single_byte_value) { 38 | EXPECT_FALSE(ozo::RawDataWritable); 39 | } 40 | 41 | TEST(RawDataWritable, should_return_true_for_type_lvalue_reference) { 42 | EXPECT_TRUE(ozo::RawDataWritable); 43 | } 44 | 45 | TEST(RawDataWritable, should_return_true_for_type_rvalue_reference) { 46 | EXPECT_TRUE(ozo::RawDataWritable); 47 | } 48 | 49 | TEST(RawDataWritable, should_return_false_for_type_const_reference) { 50 | EXPECT_FALSE(ozo::RawDataWritable); 51 | } 52 | 53 | TEST(RawDataReadable, should_return_true_for_type_with_mutable_data_method_and_non_const_result) { 54 | EXPECT_TRUE(ozo::RawDataReadable); 55 | } 56 | 57 | TEST(RawDataReadable, should_return_true_for_type_with_const_data_method_and_const_result) { 58 | EXPECT_TRUE(ozo::RawDataReadable); 59 | } 60 | 61 | TEST(RawDataReadable, should_return_false_for_type_with_data_point_to_more_than_a_single_byte_value) { 62 | EXPECT_FALSE(ozo::RawDataReadable); 63 | } 64 | 65 | TEST(RawDataReadable, should_return_true_for_type_lvalue_reference) { 66 | EXPECT_TRUE(ozo::RawDataReadable); 67 | } 68 | 69 | TEST(RawDataReadable, should_return_true_for_type_rvalue_reference) { 70 | EXPECT_TRUE(ozo::RawDataReadable); 71 | } 72 | 73 | TEST(RawDataReadable, should_return_true_for_type_const_reference) { 74 | EXPECT_TRUE(ozo::RawDataReadable); 75 | } 76 | 77 | } // namespace 78 | -------------------------------------------------------------------------------- /tests/detail/deadline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../test_asio.h" 3 | #include "../test_error.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace { 9 | 10 | using namespace testing; 11 | using namespace std::literals; 12 | 13 | using time_point = ozo::time_traits::time_point; 14 | using duration = ozo::time_traits::duration; 15 | 16 | struct stream_mock { 17 | using executor_type = ozo::tests::io_context::executor_type; 18 | MOCK_METHOD0(cancel, void()); 19 | MOCK_METHOD0(get_executor, executor_type()); 20 | }; 21 | 22 | struct io_deadline_handler : Test { 23 | ozo::tests::steady_timer_mock timer; 24 | ozo::tests::execution_context io; 25 | StrictMock> continuation; 26 | StrictMock continuation_executor; 27 | StrictMock stream; 28 | std::function on_timer_expired; 29 | 30 | io_deadline_handler() { 31 | EXPECT_CALL(io.timer_service_, timer(An())).WillRepeatedly(ReturnRef(timer)); 32 | EXPECT_CALL(stream, get_executor()).WillRepeatedly(Return(io.get_executor())); 33 | EXPECT_CALL(continuation, get_executor()) 34 | .WillRepeatedly(Return(ozo::tests::executor{continuation_executor, io})); 35 | EXPECT_CALL(timer, async_wait(_)).WillOnce(SaveArg<0>(&on_timer_expired)); 36 | } 37 | 38 | using deadline_handler = ozo::detail::io_deadline_handler, int>; 39 | }; 40 | 41 | TEST_F(io_deadline_handler, should_cancel_stream_io_and_call_handler_with_timeout_error_and_result_on_timer_expired) { 42 | InSequence s; 43 | EXPECT_CALL(continuation_executor, post(_)).WillOnce(InvokeArgument<0>()); 44 | EXPECT_CALL(stream, cancel()); 45 | EXPECT_CALL(continuation, call(Eq(boost::asio::error::timed_out), 42)); 46 | using ozo::tests::wrap; 47 | deadline_handler handler{stream, time_point{}, wrap(continuation)}; 48 | on_timer_expired(ozo::error_code{}); 49 | handler(boost::asio::error::operation_aborted, 42); 50 | } 51 | 52 | TEST_F(io_deadline_handler, should_cancel_timer_and_call_handler_with_error_and_result_on_normal_call) { 53 | InSequence s; 54 | EXPECT_CALL(timer, cancel()); 55 | EXPECT_CALL(continuation_executor, post(_)).WillOnce(InvokeArgument<0>()); 56 | EXPECT_CALL(continuation, call(Eq(ozo::tests::error::error), 777)); 57 | using ozo::tests::wrap; 58 | deadline_handler handler{stream, time_point{}, wrap(continuation)}; 59 | handler(ozo::tests::error::error, 777); 60 | on_timer_expired(boost::asio::error::operation_aborted); 61 | } 62 | 63 | } // namespace 64 | -------------------------------------------------------------------------------- /examples/retry_request.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace asio = boost::asio; 12 | 13 | const auto print_error = [](ozo::error_code ec, const auto& conn) { 14 | std::cout << "error code message: \"" << ec.message(); 15 | // Here we should check if the connection is in null state to avoid UB. 16 | if (!ozo::is_null_recursive(conn)) { 17 | std::cout << "\", libpq error message: \"" << ozo::error_message(conn) 18 | << "\", error context: \"" << ozo::get_error_context(conn); 19 | } 20 | std::cout << "\"" << std::endl; 21 | }; 22 | 23 | int main(int argc, char **argv) { 24 | std::cout << "OZO request example" << std::endl; 25 | 26 | if (argc < 2) { 27 | std::cerr << "Usage: " << argv[0] << " \n"; 28 | return 1; 29 | } 30 | 31 | asio::io_context io; 32 | 33 | auto conn_info = ozo::connection_info(argv[1]); 34 | 35 | asio::spawn(io, [&] (asio::yield_context yield) { 36 | ozo::rows_of result; 37 | ozo::error_code ec; 38 | using namespace ozo::literals; 39 | using namespace std::chrono_literals; 40 | namespace failover = ozo::failover; 41 | using retry_options = failover::retry_options; 42 | // Here we will retry operation no more than 3 times on connection errors 43 | // Each try will have its own time constraint 44 | // 1st try will be limited by 1/3 sec. 45 | // 2nd try will be limited by (1 - t(1st try)) / 2 sec, which is not less than 1/3 sec. 46 | // 3rd try will be limited by 1 - (t(1st try) + t(2nd try)), which is not less than 1/3 sec too. 47 | auto retry = 3*failover::retry(ozo::errc::connection_error) 48 | // We want to print out information about retries 49 | .set(retry_options::on_retry = print_error); 50 | // Here a request call with retry fallback strategy 51 | auto conn = ozo::request[retry](conn_info[io], "SELECT 1"_SQL, 1s, ozo::into(result), yield[ec]); 52 | 53 | // When request is completed we check is there an error. 54 | if (ec) { 55 | std::cout << "Request failed; "; 56 | print_error(ec, conn); 57 | return; 58 | } 59 | 60 | // Just print request result 61 | std::cout << "Selected:" << std::endl; 62 | for (auto value : result) { 63 | std::cout << std::get<0>(value) << std::endl; 64 | } 65 | }); 66 | 67 | io.run(); 68 | 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /tests/impl/request_oid_map_handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace ozo::tests { 10 | 11 | struct custom_type {}; 12 | 13 | } // namespace ozo::tests 14 | 15 | OZO_PG_DEFINE_CUSTOM_TYPE(ozo::tests::custom_type, "custom_type") 16 | 17 | namespace { 18 | 19 | using namespace testing; 20 | using namespace ozo::tests; 21 | 22 | using ozo::empty_oid_map; 23 | using ozo::error_code; 24 | 25 | struct connection_mock { 26 | MOCK_METHOD0(request_oid_map, void()); 27 | }; 28 | 29 | template 30 | struct connection_wrapper { 31 | connection_mock& mock_; 32 | OidMap oid_map_; 33 | 34 | using oid_map_type = OidMap; 35 | 36 | OidMap& oid_map() { 37 | return oid_map_; 38 | } 39 | 40 | template 41 | friend void request_oid_map(connection_wrapper c, Handler&&) { 42 | c.mock_.request_oid_map(); 43 | } 44 | }; 45 | 46 | struct request_oid_map_handler : Test { 47 | StrictMock connection{}; 48 | 49 | template 50 | auto make_connection(OidMap oid_map) { 51 | return connection_wrapper{connection, oid_map}; 52 | } 53 | 54 | template 55 | auto make_callback(Conn&&) { 56 | return StrictMock>> {}; 57 | } 58 | }; 59 | 60 | TEST_F(request_oid_map_handler, should_request_for_oid_when_oid_map_is_not_empty) { 61 | auto conn = make_connection(ozo::register_types()); 62 | auto callback = make_callback(conn); 63 | 64 | EXPECT_CALL(connection, request_oid_map()).WillOnce(Return()); 65 | 66 | ozo::impl::apply_oid_map_request(wrap(callback))(error_code{}, std::move(conn)); 67 | } 68 | 69 | TEST_F(request_oid_map_handler, should_not_request_for_oid_when_oid_map_is_not_empty_but_error_occured) { 70 | auto conn = make_connection(ozo::register_types()); 71 | auto callback = make_callback(conn); 72 | 73 | EXPECT_CALL(callback, call(error_code{error::error}, _)) 74 | .WillOnce(Return()); 75 | 76 | ozo::impl::apply_oid_map_request(wrap(callback))(error::error, std::move(conn)); 77 | } 78 | 79 | TEST_F(request_oid_map_handler, should_not_request_for_oid_when_oid_map_is_empty) { 80 | auto conn = make_connection(ozo::register_types<>()); 81 | auto callback = make_callback(conn); 82 | 83 | EXPECT_CALL(callback, call(error_code{}, _)).WillOnce(Return()); 84 | 85 | ozo::impl::apply_oid_map_request(wrap(callback))(error_code{}, std::move(conn)); 86 | } 87 | 88 | } // namespace 89 | -------------------------------------------------------------------------------- /include/ozo/detail/functional.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo::detail::functional { 6 | 7 | struct forward { 8 | template 9 | constexpr static decltype(auto) apply(T&& v) noexcept { 10 | return std::forward(v); 11 | } 12 | }; 13 | 14 | struct dereference { 15 | template 16 | constexpr static decltype(auto) apply(T&& v) noexcept(noexcept(*v)) { 17 | return *v; 18 | } 19 | }; 20 | 21 | struct operator_not { 22 | template 23 | constexpr static decltype(auto) apply(T&& v) noexcept(noexcept(!v)) { 24 | return !v; 25 | } 26 | }; 27 | 28 | struct always_true { 29 | template 30 | constexpr static std::true_type apply(T&&) noexcept { return {};} 31 | }; 32 | 33 | struct always_false { 34 | template 35 | constexpr static std::false_type apply(T&&) noexcept { return {};} 36 | }; 37 | 38 | } // namespace ozo::detail::functional 39 | 40 | namespace ozo::detail { 41 | 42 | template typename Functional, typename T, typename ...Ts> 43 | constexpr Functional> make_functional(T&&, Ts&&...) { return {};} 44 | 45 | template typename Functional, typename ...Ts> 46 | using functional_type = decltype(make_functional(std::declval()...)); 47 | 48 | struct is_applicable_impl { 49 | template 50 | using true_type = std::is_void>; 51 | 52 | template 53 | static constexpr auto get(Functional, Ts&&... args) -> 54 | true_type(args)...))>; 55 | 56 | static constexpr std::false_type get(...); 57 | }; 58 | 59 | template typename Functional, typename ...Ts> 60 | constexpr auto is_applicable(Ts&&... args) { 61 | return decltype( 62 | is_applicable_impl::get( 63 | make_functional(std::forward(args)...), 64 | std::forward(args)...) 65 | ){}; 66 | } 67 | 68 | template typename Functional, typename ...Ts> 69 | constexpr auto IsApplicable = decltype(is_applicable(std::declval()...)){}; 70 | 71 | template typename Functional, typename ...Ts> 72 | using result_of = decltype(functional_type::apply(std::declval()...)); 73 | 74 | template typename Functional, typename ...Ts> 75 | constexpr bool is_noexcept = noexcept(functional_type::apply(std::declval()...)); 76 | 77 | template typename Functional, typename ...Ts> 78 | constexpr result_of apply(Ts&&... args) noexcept(is_noexcept) { 79 | return functional_type::apply(std::forward(args)...); 80 | } 81 | 82 | } // namespace ozo::detail 83 | -------------------------------------------------------------------------------- /include/ozo/impl/result.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ozo::impl { 8 | 9 | enum class result_format : int { 10 | text = 0, 11 | binary = 1, 12 | }; 13 | 14 | namespace pq { 15 | 16 | inline oid_t pq_field_type(const PGresult& res, int column) noexcept { 17 | return PQftype(std::addressof(res), column); 18 | } 19 | 20 | inline result_format pq_field_format(const PGresult& res, int column) noexcept { 21 | return static_cast(PQfformat(std::addressof(res), column)); 22 | } 23 | 24 | inline const char* pq_get_value(const PGresult& res, int row, int column) noexcept { 25 | return PQgetvalue(std::addressof(res), row, column); 26 | } 27 | 28 | inline std::size_t pq_get_length(const PGresult& res, int row, int column) noexcept { 29 | return static_cast(PQgetlength(std::addressof(res), row, column)); 30 | } 31 | 32 | inline bool pq_get_isnull(const PGresult& res, int row, int column) noexcept { 33 | return PQgetisnull(std::addressof(res), row, column); 34 | } 35 | 36 | inline int pq_field_number(const PGresult& res, const char* name) noexcept { 37 | return PQfnumber(std::addressof(res), name); 38 | } 39 | 40 | inline int pq_nfields(const PGresult& res) noexcept { 41 | return PQnfields(std::addressof(res)); 42 | } 43 | 44 | inline int pq_ntuples(const PGresult& res) noexcept { 45 | return PQntuples(std::addressof(res)); 46 | } 47 | 48 | } // namespace pq 49 | 50 | template 51 | inline oid_t field_type(T&& res, int column) noexcept { 52 | using pq::pq_field_type; 53 | return pq_field_type(std::forward(res), column); 54 | } 55 | 56 | template 57 | inline result_format field_format(T&& res, int column) noexcept { 58 | using pq::pq_field_format; 59 | return pq_field_format(std::forward(res), column); 60 | } 61 | 62 | template 63 | inline const char* get_value(T&& res, int row, int column) noexcept { 64 | using pq::pq_get_value; 65 | return pq_get_value(std::forward(res), row, column); 66 | } 67 | 68 | template 69 | inline std::size_t get_length(T&& res, int row, int column) noexcept { 70 | using pq::pq_get_length; 71 | return pq_get_length(std::forward(res), row, column); 72 | } 73 | 74 | template 75 | inline bool get_isnull(T&& res, int row, int column) noexcept { 76 | using pq::pq_get_isnull; 77 | return pq_get_isnull(std::forward(res), row, column); 78 | } 79 | 80 | template 81 | inline int field_number(T&& res, const char* name) noexcept { 82 | using pq::pq_field_number; 83 | return pq_field_number(std::forward(res), name); 84 | } 85 | 86 | template 87 | inline int nfields(T&& res) noexcept { 88 | using pq::pq_nfields; 89 | return pq_nfields(std::forward(res)); 90 | } 91 | 92 | template 93 | inline int ntuples(T&& res) noexcept { 94 | using pq::pq_ntuples; 95 | return pq_ntuples(std::forward(res)); 96 | } 97 | 98 | } // namespace ozo::impl 99 | -------------------------------------------------------------------------------- /include/ozo/transaction_options.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ozo { 10 | 11 | namespace hana = boost::hana; 12 | 13 | /** 14 | * @ingroup group-transaction-types 15 | * @brief 'type enum' for transaction isolation levels supported by PostgreSQL 16 | * 17 | * See the official documentation [on transaction isolation](https://www.postgresql.org/docs/11/transaction-iso.html) and 18 | * [transaction initiation](https://www.postgresql.org/docs/11/sql-set-transaction.html) for more information on guarantees every level makes 19 | * and how these options affect transaction behaviour. 20 | */ 21 | struct isolation_level { 22 | constexpr static hana::type serializable{}; //!< SERIALIZABLE isolation level 23 | constexpr static hana::type repeatable_read{}; //!< REPEATABLE READ isolation level 24 | constexpr static hana::type read_committed{}; //!< READ COMMITTED isolation level 25 | constexpr static hana::type read_uncommitted{}; //!< READ UNCOMMITTED isolation level (treated like READ COMMITTED by PostgreSQL) 26 | }; 27 | 28 | /** 29 | * @ingroup group-transaction-types 30 | * @brief 'type enum' for transaction modes supported by PostgreSQL 31 | * 32 | * See the official documentation on [transaction initiation](https://www.postgresql.org/docs/11/sql-set-transaction.html) 33 | * for more information on how these options affect transaction behaviour. 34 | */ 35 | struct transaction_mode { 36 | constexpr static hana::type read_write{}; //!< READ WRITE transaction mode 37 | constexpr static hana::type read_only{}; //!< READ ONLY transaction mode 38 | }; 39 | 40 | /** 41 | * @ingroup group-transaction-types 42 | * @brief transaction deferrability indicator 43 | * @tparam V integral constant indicating the deferrability 44 | * 45 | * See the official documentation on [transaction initiation](https://www.postgresql.org/docs/11/sql-set-transaction.html) 46 | * for more information on how these options affect transaction behaviour. 47 | */ 48 | template 49 | struct deferrable_mode : V { 50 | using base = V; 51 | 52 | constexpr auto operator!() const noexcept { 53 | return deferrable_mode::type>{}; 54 | } 55 | }; 56 | 57 | constexpr deferrable_mode deferrable; 58 | 59 | /** 60 | * @ingroup group-transaction-types 61 | * @brief options for transactions 62 | * 63 | * This options can be used with ozo::begin 64 | */ 65 | struct transaction_options { 66 | constexpr static option isolation_level{}; //!< Transaction isolation level, see ozo::isolation_level 67 | constexpr static option mode{}; //!< Transaction mode, see ozo::transaction_mode 68 | constexpr static option deferrability{}; //!< Transaction deferrability, see ozo::deferrable_mode 69 | }; 70 | 71 | } // ozo 72 | -------------------------------------------------------------------------------- /include/ozo/core/recursive.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | 8 | template > 9 | struct unwrap_recursive_impl : unwrap_impl {}; 10 | 11 | /** 12 | * @brief Unwraps argument underlying value recursively. 13 | * 14 | * This utility function unwraps argument recursively applying `ozo::unwrap()` function until 15 | * the type of unwrapped value and type of it's argument become the same. 16 | * 17 | * @note Before applying the function it is better to check the object recursively for a null 18 | * state via `ozo::is_null_recursive()`. 19 | * 20 | * The function is equal to this pesudo-code: 21 | * @code 22 | constexpr decltype(auto) unwrap_recursive(auto&& arg) noexcept { 23 | while (decltype(arg) != unwrap_type) { 24 | arg = unwrap(arg); 25 | } 26 | return arg; 27 | } 28 | * @endcode 29 | * 30 | * @param value --- object to unwrap 31 | * @return implementation depended result of recursively applied `ozo::unwrap()` 32 | * @ingroup group-core-functions 33 | * @sa ozo::is_null_recursive() 34 | */ 35 | template 36 | inline constexpr decltype(auto) unwrap_recursive(T&& v) noexcept { 37 | return unwrap_recursive_impl>::apply(std::forward(v)); 38 | } 39 | 40 | template 41 | struct unwrap_recursive_impl>>> { 42 | template 43 | static constexpr decltype(auto) apply(TT&& v) noexcept { 44 | return ozo::unwrap_recursive(ozo::unwrap(v)); 45 | } 46 | }; 47 | 48 | template > 49 | struct is_null_recursive_impl : is_null_impl {}; 50 | 51 | /** 52 | * @brief Indicates if one of unwrapped values is in null state 53 | * 54 | * This utility function recursively examines the value for a null state. 55 | * The function is useful to examine `Connection` object for a null state 56 | * because it is normal for such object to be wrapped. 57 | * 58 | * The function is equal to this pesudo-code: 59 | * @code 60 | constexpr bool is_null_recursive(auto&& arg) noexcept { 61 | while (decltype(arg) != unwrap_type) { 62 | if (is_null(arg)) { 63 | return true; 64 | } 65 | arg = unwrap(arg); 66 | } 67 | return false; 68 | } 69 | * @endcode 70 | * 71 | * @param v --- object to examine 72 | * @return `true` --- one of the objects is in null-state, 73 | * @return `false` --- overwise. 74 | * @ingroup group-core-functions 75 | * @sa ozo::is_null(), ozo::unwrap() 76 | */ 77 | template 78 | inline constexpr bool is_null_recursive(T&& v) noexcept { 79 | return is_null_recursive_impl>::apply(std::forward(v)); 80 | } 81 | 82 | template 83 | struct is_null_recursive_impl>>> { 84 | template 85 | static constexpr bool apply(TT&& v) noexcept { 86 | return ozo::is_null(v) ? true : ozo::is_null_recursive(ozo::unwrap(v)); 87 | } 88 | }; 89 | 90 | } // namespace ozo 91 | -------------------------------------------------------------------------------- /include/ozo/detail/deadline.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace ozo::detail { 11 | 12 | template 13 | class io_deadline_handler { 14 | public: 15 | template 16 | io_deadline_handler (Stream& stream, const TimeConstraint& t, Handler handler) 17 | : timer_(ozo::detail::get_operation_timer(stream.get_executor(), t)) { 18 | auto allocator = asio::get_associated_allocator(handler); 19 | ctx_ = std::allocate_shared(allocator, stream, std::move(handler)); 20 | timer_.async_wait(timer_handler{ctx_}); 21 | } 22 | 23 | void operator() (error_code ec, Result result) { 24 | if (--ctx_->first_call) { 25 | timer_.cancel(); 26 | ctx_->ec = std::move(ec); 27 | ctx_->result = std::move(result); 28 | ctx_.reset(); 29 | } else { 30 | auto handler = std::move(ctx_->handler); 31 | auto ec = std::move(ctx_->ec); 32 | ctx_.reset(); 33 | handler(std::move(ec), std::move(result)); 34 | } 35 | } 36 | 37 | using executor_type = asio::associated_executor_t; 38 | 39 | executor_type get_executor() const noexcept { return asio::get_associated_executor(ctx_->handler);} 40 | 41 | using allocator_type = asio::associated_allocator_t; 42 | 43 | allocator_type get_allocator() const noexcept { return asio::get_associated_allocator(ctx_->handler);} 44 | 45 | private: 46 | using timer_type = typename ozo::detail::operation_timer::type; 47 | 48 | struct context { 49 | Stream& stream; 50 | Handler handler; 51 | Result result; 52 | error_code ec; 53 | std::atomic first_call{2}; 54 | 55 | context(Stream& stream, Handler&& handler) 56 | : stream(stream), handler(std::move(handler)) { 57 | } 58 | }; 59 | 60 | timer_type timer_; 61 | std::shared_ptr ctx_; 62 | 63 | struct timer_handler { 64 | using executor_type = asio::associated_executor_t; 65 | 66 | executor_type get_executor() const noexcept { return asio::get_associated_executor(ctx_->handler);} 67 | 68 | using allocator_type = asio::associated_allocator_t; 69 | 70 | allocator_type get_allocator() const noexcept { return asio::get_associated_allocator(ctx_->handler);} 71 | 72 | void operator() (error_code) { 73 | if (--ctx_->first_call) { 74 | ctx_->stream.cancel(); 75 | ctx_->ec = asio::error::timed_out; 76 | ctx_.reset(); 77 | } else { 78 | auto handler = std::move(ctx_->handler); 79 | auto ec = std::move(ctx_->ec); 80 | auto result = std::move(ctx_->result); 81 | ctx_.reset(); 82 | handler(std::move(ec), std::move(result)); 83 | } 84 | } 85 | 86 | std::shared_ptr ctx_; 87 | }; 88 | }; 89 | 90 | } // namespace ozo::detail 91 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | from conans import ConanFile, CMake 2 | from conans.errors import ConanInvalidConfiguration 3 | from conans.tools import Version, check_min_cppstd, load 4 | import re 5 | 6 | 7 | def get_version(): 8 | try: 9 | content = load("CMakeLists.txt") 10 | version = re.search(r"^\s*project\(ozo\s+VERSION\s+([^\s)]+)", content, re.M).group(1) 11 | return version.strip() 12 | except Exception: 13 | return None 14 | 15 | 16 | class OzoConan(ConanFile): 17 | name = "ozo" 18 | version = get_version() 19 | license = "PostgreSQL" 20 | topics = ("ozo", "yandex", "postgres", "postgresql", "cpp17", "database", "db", "asio") 21 | url = "https://github.com/yandex/ozo" 22 | description = "Conan package for yandex ozo" 23 | settings = "os", "compiler" 24 | 25 | exports_sources = "include/*", "CMakeLists.txt", "cmake/*", "LICENCE", "AUTHORS" 26 | 27 | generators = "cmake_find_package" 28 | requires = ("boost/1.74.0", "resource_pool/0.1.0", "libpq/13.1") 29 | 30 | def _configure_cmake(self): 31 | cmake = CMake(self) 32 | cmake.configure() 33 | return cmake 34 | 35 | def configure(self): 36 | if self.settings.os == "Windows": 37 | raise ConanInvalidConfiguration("OZO is not compatible with Winows") 38 | if self.settings.compiler.get_safe("cppstd"): 39 | check_min_cppstd(self, "17") 40 | 41 | def build(self): 42 | cmake = self._configure_cmake() 43 | cmake.build() 44 | 45 | def package(self): 46 | cmake = self._configure_cmake() 47 | cmake.install() 48 | 49 | def package_id(self): 50 | self.info.header_only() 51 | 52 | def package_info(self): 53 | self.cpp_info.components["_ozo"].includedirs = ["include"] 54 | self.cpp_info.components["_ozo"].requires = ["boost::boost", "boost::system", "boost::thread", "boost::coroutine", 55 | "resource_pool::resource_pool", # == elsid::resource_pool in cmake 56 | "libpq::pq", # == PostgreSQL::PostgreSQL in cmake 57 | ] 58 | self.cpp_info.components["_ozo"].defines = [ 59 | "BOOST_COROUTINES_NO_DEPRECATION_WARNING", 60 | "BOOST_HANA_CONFIG_ENABLE_STRING_UDL", 61 | "BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT" 62 | ] 63 | 64 | compiler = self.settings.compiler 65 | version = Version(compiler.version) 66 | if compiler == "clang" or compiler == "apple-clang" or (compiler == "gcc" and version >= 9): 67 | self.cpp_info.components["_ozo"].cxxflags = [ 68 | "-Wno-gnu-string-literal-operator-template", 69 | "-Wno-gnu-zero-variadic-macro-arguments", 70 | ] 71 | 72 | self.cpp_info.filenames["cmake_find_package"] = "ozo" 73 | self.cpp_info.filenames["cmake_find_package_multi"] = "ozo" 74 | self.cpp_info.names["cmake_find_package"] = "yandex" 75 | self.cpp_info.names["cmake_find_package_multi"] = "yandex" 76 | self.cpp_info.components["_ozo"].names["cmake_find_package"] = "ozo" 77 | self.cpp_info.components["_ozo"].names["cmake_find_package_multi"] = "ozo" 78 | -------------------------------------------------------------------------------- /tests/impl/request_oid_map.cpp: -------------------------------------------------------------------------------- 1 | #include "test_asio.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace ozo::tests { 10 | 11 | struct custom_type1 {}; 12 | struct custom_type2 {}; 13 | 14 | } // namespace ozo::tests 15 | 16 | OZO_PG_DEFINE_CUSTOM_TYPE(ozo::tests::custom_type1, "custom_type1") 17 | OZO_PG_DEFINE_CUSTOM_TYPE(ozo::tests::custom_type2, "custom_type2") 18 | 19 | namespace { 20 | 21 | using namespace testing; 22 | using namespace ozo::tests; 23 | 24 | TEST(get_types_names, should_return_empty_container_for_empty_oid_map) { 25 | auto type_names = ozo::impl::get_types_names(ozo::empty_oid_map{}); 26 | EXPECT_TRUE(type_names.empty()); 27 | } 28 | 29 | TEST(get_types_names, should_return_type_names_from_oid_map) { 30 | auto type_names = ozo::impl::get_types_names( 31 | ozo::register_types()); 32 | EXPECT_THAT(type_names, ElementsAre("custom_type1", "custom_type2")); 33 | } 34 | 35 | TEST(set_oid_map, should_set_oids_for_oid_map_from_oids_result_argument) { 36 | auto oid_map = ozo::register_types(); 37 | const ozo::impl::oids_result res = {11, 22}; 38 | ozo::impl::set_oid_map(oid_map, res); 39 | ozo::impl::get_types_names(ozo::empty_oid_map{}); 40 | EXPECT_EQ(ozo::type_oid(oid_map), 11u); 41 | EXPECT_EQ(ozo::type_oid(oid_map), 22u); 42 | } 43 | 44 | TEST(set_oid_map, should_throw_on_oid_map_size_is_not_equal_to_oids_result_size) { 45 | auto oid_map = ozo::register_types(); 46 | const ozo::impl::oids_result res = {11}; 47 | EXPECT_THROW(ozo::impl::set_oid_map(oid_map, res), std::length_error); 48 | } 49 | 50 | TEST(set_oid_map, should_throw_on_null_oid_in_oids_result) { 51 | auto oid_map = ozo::register_types(); 52 | const ozo::impl::oids_result res = {11, ozo::null_oid}; 53 | EXPECT_THROW(ozo::impl::set_oid_map(oid_map, res), std::invalid_argument); 54 | } 55 | 56 | template 57 | struct connection { 58 | OidMap oid_map_; 59 | std::string error_context_; 60 | using error_context = std::string; 61 | 62 | const error_context& get_error_context() const noexcept { return error_context_; } 63 | void set_error_context(error_context v = error_context{}) { error_context_ = std::move(v); } 64 | 65 | OidMap& oid_map() noexcept { 66 | return oid_map_; 67 | } 68 | const OidMap& oid_map() const noexcept { 69 | return oid_map_; 70 | } 71 | }; 72 | 73 | } 74 | 75 | namespace ozo { 76 | template 77 | struct is_connection<::connection> : std::true_type {}; 78 | } // namespace ozo 79 | 80 | namespace { 81 | TEST(request_oid_map_op, should_call_handler_with_oid_request_failed_error_when_oid_map_length_differs_from_result_length) { 82 | StrictMock>> cb_mock {}; 83 | auto operation = ozo::impl::request_oid_map_op{wrap(cb_mock)}; 84 | operation.ctx_->res_ = ozo::impl::oids_result(1); 85 | 86 | EXPECT_CALL(cb_mock, call(ozo::error_code(ozo::error::oid_request_failed), _)).WillOnce(Return()); 87 | operation(ozo::error_code {}, connection {}); 88 | } 89 | 90 | } // namespace 91 | -------------------------------------------------------------------------------- /examples/role_based_request.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace asio = boost::asio; 12 | namespace hana = boost::hana; 13 | namespace failover = ozo::failover; 14 | 15 | const auto print_error = [](ozo::error_code ec, const auto& conn) { 16 | std::cout << "error code message: \"" << ec.message(); 17 | // Here we should check if the connection is in null state to avoid UB. 18 | if (!ozo::is_null_recursive(conn)) { 19 | std::cout << "\", libpq error message: \"" << ozo::error_message(conn) 20 | << "\", error context: \"" << ozo::get_error_context(conn); 21 | } 22 | std::cout << "\""; 23 | }; 24 | 25 | const auto print_fallback = [](ozo::error_code ec, const auto& conn, const auto& fallback) { 26 | print_error(ec, conn); 27 | // We can print information about fallback will be used for next try 28 | if constexpr (decltype(fallback.role() == ozo::failover::master)::value) { 29 | std::cout << " fallback is \"master\"" << std::endl; 30 | } else { 31 | std::cout << " fallback is \"replica\"" << std::endl; 32 | } 33 | }; 34 | 35 | int main(int argc, char **argv) { 36 | std::cout << "OZO role-based request failover example" << std::endl; 37 | 38 | if (argc < 3) { 39 | std::cerr << "Usage: " << argv[0] << " \n"; 40 | return 1; 41 | } 42 | 43 | asio::io_context io; 44 | 45 | // Here we provide a mapping of roles to connection strings 46 | auto conn_info = failover::make_role_based_connection_source( 47 | failover::master=ozo::connection_info(argv[1]), 48 | failover::replica=ozo::connection_info(argv[2]) 49 | ); 50 | 51 | asio::spawn(io, [&] (asio::yield_context yield) { 52 | using namespace ozo::literals; 53 | using namespace std::chrono_literals; 54 | using opt = failover::role_based_options; 55 | 56 | ozo::rows_of result; 57 | ozo::error_code ec; 58 | // Here we will try operation on master first and then replica if any problem will take place 59 | // Each try will have its own time constraint 60 | // master try will be limited by 1/2 sec. 61 | // replica try will be limited by (1 - t(1st try)) / 2 sec, which is not less than 1/2 sec. 62 | auto roles = failover::role_based(failover::master, failover::replica) 63 | // We want to print out information about retries 64 | .set(opt::on_fallback = print_fallback); 65 | // Here a request call with role based failover strategy 66 | auto conn = ozo::request[roles](conn_info[io], "SELECT 1"_SQL, 1s, ozo::into(result), yield[ec]); 67 | 68 | // When request is completed we check is there an error. 69 | if (ec) { 70 | std::cout << "Request failed; "; 71 | print_error(ec, conn); 72 | std::cout << std::endl; 73 | return; 74 | } 75 | 76 | // Just print request result 77 | std::cout << "Selected:" << std::endl; 78 | for (auto value : result) { 79 | std::cout << std::get<0>(value) << std::endl; 80 | } 81 | }); 82 | 83 | io.run(); 84 | 85 | return 0; 86 | } 87 | -------------------------------------------------------------------------------- /tests/integration/cancel_integration.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #define ASSERT_REQUEST_OK(ec, conn)\ 12 | ASSERT_FALSE(ec) << ec.message() \ 13 | << "|" << ozo::error_message(conn) \ 14 | << "|" << ozo::get_error_context(conn) << std::endl 15 | 16 | namespace { 17 | 18 | namespace hana = boost::hana; 19 | 20 | using namespace testing; 21 | 22 | TEST(cancel, should_cancel_operation) { 23 | using namespace ozo::literals; 24 | using namespace std::chrono_literals; 25 | using namespace hana::literals; 26 | 27 | ozo::io_context io; 28 | boost::asio::steady_timer timer(io); 29 | 30 | boost::asio::spawn(io, [&io, &timer](auto yield){ 31 | const ozo::connection_info conn_info(OZO_PG_TEST_CONNINFO); 32 | ozo::error_code ec; 33 | auto conn = ozo::get_connection(conn_info[io], yield[ec]); 34 | EXPECT_FALSE(ec); 35 | boost::asio::spawn(yield, [&io, &timer, handle = get_cancel_handle(conn)](auto yield) mutable { 36 | timer.expires_after(1s); 37 | ozo::error_code ec; 38 | timer.async_wait(yield[ec]); 39 | if (!ec) { 40 | // Guard is needed since cancel will be served with external 41 | // system executor, so we need to preserve our io_context from 42 | // stop until all the operation processed properly 43 | auto guard = boost::asio::make_work_guard(io); 44 | ozo::cancel(std::move(handle), io, 5s, yield[ec]); 45 | } 46 | }); 47 | ozo::execute(conn, "SELECT pg_sleep(1000000)"_SQL, yield[ec]); 48 | EXPECT_EQ(ec, ozo::sqlstate::query_canceled); 49 | }); 50 | 51 | io.run(); 52 | } 53 | 54 | TEST(cancel, should_stop_cancel_operation_on_zero_timeout) { 55 | using namespace ozo::literals; 56 | using namespace std::chrono_literals; 57 | using namespace hana::literals; 58 | 59 | ozo::io_context io; 60 | ozo::io_context dummy_io; 61 | boost::asio::steady_timer timer(io); 62 | 63 | boost::asio::spawn(io, [&io, &timer, &dummy_io](auto yield){ 64 | const ozo::connection_info conn_info(OZO_PG_TEST_CONNINFO); 65 | ozo::error_code ec; 66 | auto conn = ozo::get_connection(conn_info[io], yield[ec]); 67 | EXPECT_FALSE(ec); 68 | boost::asio::spawn(yield, [&io, &timer, handle = get_cancel_handle(conn, dummy_io.get_executor())](auto yield) mutable { 69 | timer.expires_after(1s); 70 | ozo::error_code ec; 71 | timer.async_wait(yield[ec]); 72 | if (!ec) { 73 | // Guard is needed since cancel will be served with external 74 | // system executor, so we need to preserve our io_context from 75 | // stop until all the operation processed properly 76 | auto guard = boost::asio::make_work_guard(io); 77 | ozo::cancel(std::move(handle), io, 0s, yield[ec]); 78 | EXPECT_EQ(ec, boost::asio::error::timed_out); 79 | } 80 | }); 81 | ozo::execute(conn, "SELECT pg_sleep(1000000)"_SQL, 2s, yield[ec]); 82 | EXPECT_EQ(ec, boost::asio::error::timed_out); 83 | }); 84 | 85 | io.run(); 86 | } 87 | 88 | } // namespace 89 | -------------------------------------------------------------------------------- /tests/test_error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace ozo::tests::error { 10 | 11 | enum code { 12 | ok, // to do no use error code 0 13 | error, // error 14 | another_error, // another error 15 | }; 16 | 17 | namespace detail { 18 | 19 | class category : public error_category { 20 | public: 21 | const char* name() const noexcept override { return "ozo::tests::error::detail::category"; } 22 | 23 | std::string message(int value) const override { 24 | switch (code(value)) { 25 | case ok: 26 | return "no error"; 27 | case error: 28 | return "test error"; 29 | case another_error: 30 | return "another error"; 31 | } 32 | std::ostringstream error; 33 | error << "no message for value: " << value; 34 | return error.str(); 35 | } 36 | }; 37 | 38 | } // namespace detail 39 | 40 | inline const error_category& get_category() { 41 | static detail::category instance; 42 | return instance; 43 | } 44 | 45 | } // namespace ozo::tests::error 46 | 47 | namespace boost::system { 48 | 49 | template <> 50 | struct is_error_code_enum : std::true_type {}; 51 | 52 | } // namespace boost::system 53 | 54 | namespace ozo::tests::error { 55 | 56 | inline auto make_error_code(const code e) { 57 | return boost::system::error_code(static_cast(e), get_category()); 58 | } 59 | 60 | } // namespace ozo::tests::error 61 | 62 | namespace ozo::tests::errc { 63 | 64 | enum code { 65 | ok, // to do no use error code 0 66 | error, // error 67 | }; 68 | 69 | namespace detail { 70 | 71 | class category : public error_category { 72 | public: 73 | const char* name() const noexcept override { return "ozo::tests::error::detail::category"; } 74 | 75 | std::string message(int value) const override { 76 | switch (code(value)) { 77 | case ok: 78 | return "no error"; 79 | case error: 80 | return "test error"; 81 | } 82 | std::ostringstream error; 83 | error << "no message for value: " << value; 84 | return error.str(); 85 | } 86 | 87 | bool equivalent( const boost::system::error_code& code, int condition ) const noexcept override { 88 | if (condition == error) { 89 | return code.category() == ozo::tests::error::get_category() && 90 | (code.value() == ozo::tests::error::error || code.value() == ozo::tests::error::another_error); 91 | } 92 | return condition == ok && code == ozo::tests::error::ok; 93 | } 94 | }; 95 | 96 | } // namespace detail 97 | 98 | inline const error_category& get_category() { 99 | static detail::category instance; 100 | return instance; 101 | } 102 | 103 | } // namespace ozo::tests::error 104 | 105 | namespace boost::system { 106 | 107 | template <> 108 | struct is_error_condition_enum : std::true_type {}; 109 | 110 | } // namespace boost::system 111 | 112 | namespace ozo::tests::errc { 113 | 114 | inline auto make_error_condition(const code e) { 115 | return boost::system::error_condition(static_cast(e), get_category()); 116 | } 117 | 118 | } // namespace ozo::tests::error 119 | 120 | -------------------------------------------------------------------------------- /include/ozo/io/istream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | namespace ozo { 13 | 14 | class istream { 15 | class istreambuf { 16 | const char* i_; 17 | const char* last_; 18 | public: 19 | 20 | constexpr istreambuf(const char* data, size_t len) noexcept 21 | : i_(data), last_(data + len) {} 22 | 23 | std::streamsize read(char* buf, std::streamsize n) noexcept { 24 | auto last = std::min(i_ + n, last_); 25 | std::copy(i_, last, buf); 26 | n = std::distance(i_, last); 27 | i_ = last; 28 | return n; 29 | } 30 | }; 31 | public: 32 | using traits_type = std::istream::traits_type; 33 | using char_type = std::istream::char_type; 34 | 35 | constexpr istream(const char* data, size_t len) noexcept 36 | : buf_(data, len) {} 37 | 38 | istream& read(char_type* buf, std::streamsize len) noexcept { 39 | const std::streamsize nbytes = buf_.read(buf, len); 40 | if (nbytes != len) { 41 | unexpected_eof_ = true; 42 | } 43 | return *this; 44 | } 45 | 46 | traits_type::int_type get() noexcept { 47 | char retval; 48 | if (!read(&retval, 1)) { 49 | return traits_type::eof(); 50 | } 51 | return retval; 52 | } 53 | 54 | operator bool() const noexcept { return !unexpected_eof_;} 55 | 56 | template 57 | Require, istream&> read(T& out) { 58 | using std::data; 59 | using std::size; 60 | return read(reinterpret_cast(data(out)), size(out)); 61 | } 62 | 63 | template 64 | Require && sizeof(T) == 1, istream&> read(T& out) noexcept { 65 | out = istream::traits_type::to_char_type(get()); 66 | return *this; 67 | } 68 | 69 | template 70 | Require && sizeof(T) != 1, istream&> read(T& out) noexcept { 71 | detail::typed_buffer buf; 72 | read(buf.raw); 73 | out = detail::convert_from_big_endian(buf.typed); 74 | return *this; 75 | } 76 | 77 | template 78 | Require, istream&> read(T& out) noexcept { 79 | detail::floating_point_integral_t tmp; 80 | read(tmp); 81 | out = detail::to_floating_point(tmp); 82 | return *this; 83 | } 84 | 85 | istream& read(bool& out) noexcept { 86 | char_type tmp; 87 | read(tmp); 88 | out = (tmp != 0); 89 | return *this; 90 | } 91 | 92 | template 93 | Require, istream&> read(T& out) { 94 | hana::for_each(hana::keys(out), [&](auto key) { 95 | read(hana::at_key(out, key)); 96 | }); 97 | return *this; 98 | } 99 | 100 | private: 101 | istreambuf buf_; 102 | bool unexpected_eof_ = false; 103 | }; 104 | 105 | template 106 | inline istream& read(istream& in, Ts&& ...vs) { 107 | if (!in.read(std::forward(vs)...)) { 108 | throw system_error(error::unexpected_eof); 109 | } 110 | return in; 111 | } 112 | 113 | } // namespace ozo 114 | -------------------------------------------------------------------------------- /include/ozo/detail/begin_statement_builder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace ozo::detail { 18 | 19 | namespace hana = boost::hana; 20 | 21 | struct begin_statement_builder { 22 | using opt = transaction_options; 23 | 24 | template 25 | constexpr static auto to_string(decltype(opt::isolation_level), Level level) { 26 | [[maybe_unused]] const auto prefix = BOOST_HANA_STRING(" ISOLATION LEVEL "); 27 | const auto sql_levels = hana::make_map( 28 | hana::make_pair(isolation_level::serializable, BOOST_HANA_STRING("SERIALIZABLE")), 29 | hana::make_pair(isolation_level::repeatable_read, BOOST_HANA_STRING("REPEATABLE READ")), 30 | hana::make_pair(isolation_level::read_committed, BOOST_HANA_STRING("READ COMMITTED")), 31 | hana::make_pair(isolation_level::read_uncommitted, BOOST_HANA_STRING("READ UNCOMMITTED")) 32 | ); 33 | 34 | if constexpr (const auto sql = hana::find(sql_levels, level); sql != hana::nothing) { 35 | return prefix + *sql; 36 | } else { 37 | static_assert(std::is_void_v, "unexpected transaction level"); 38 | } 39 | } 40 | 41 | template 42 | constexpr static auto to_string(decltype(opt::mode), Mode mode) { 43 | const auto sql_modes = hana::make_map( 44 | hana::make_pair(transaction_mode::read_write, BOOST_HANA_STRING(" READ WRITE")), 45 | hana::make_pair(transaction_mode::read_only, BOOST_HANA_STRING(" READ ONLY")) 46 | ); 47 | 48 | if constexpr (const auto sql = hana::find(sql_modes, mode); sql != hana::nothing) { 49 | return *sql; 50 | } else { 51 | static_assert(std::is_void_v, "unknown transaction mode"); 52 | } 53 | } 54 | 55 | template 56 | constexpr static auto to_string(decltype(opt::deferrability), Deferrable) { 57 | if constexpr (Deferrable::value) { 58 | return BOOST_HANA_STRING(" DEFERRABLE"); 59 | } else { 60 | return BOOST_HANA_STRING(" NOT DEFERRABLE"); 61 | } 62 | } 63 | 64 | template 65 | constexpr static auto build(Options const& options) { 66 | constexpr auto supported_options = hana::make_tuple(opt::isolation_level, opt::mode, opt::deferrability); 67 | constexpr auto query_prefix = BOOST_HANA_STRING("BEGIN"); 68 | 69 | const auto real_options = hana::filter(hana::filter(supported_options, hana::partial(hana::contains, options)), 70 | ([&](const auto& v) { return std::negation>>{}; })); 71 | const auto strings = hana::transform(real_options, [&](const auto& v) { return to_string(v, options[v]); }); 72 | return make_query(hana::unpack(strings, [query_prefix](const auto& ...s) { 73 | return query_prefix + (s + ... + hana::string_c<>); 74 | })); 75 | } 76 | }; 77 | 78 | } // ozo::detail 79 | -------------------------------------------------------------------------------- /examples/request.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace asio = boost::asio; 11 | 12 | int main(int argc, char **argv) { 13 | std::cout << "OZO request example" << std::endl; 14 | 15 | if (argc < 2) { 16 | std::cerr << "Usage: " << argv[0] << " \n"; 17 | return 1; 18 | } 19 | 20 | // Ozo perform all IO using Boost.Asio, so first thing we need to do is setup asio::io_context 21 | asio::io_context io; 22 | 23 | // To make a request we need to make a ConnectionSource. It knows how to connect to database using 24 | // connection string. See https://www.postgresql.org/docs/9.4/static/libpq-connect.html#LIBPQ-CONNSTRING 25 | // how to make a connection string. 26 | auto conn_info = ozo::connection_info(argv[1]); 27 | 28 | const auto coroutine = [&] (asio::yield_context yield) { 29 | // Request result is always set of rows. Client should take care of output object lifetime. 30 | ozo::rows_of result; 31 | 32 | // Request operation require ConnectionProvider, query, output object for result and CompletionToken. 33 | // Also we setup request timeout and reference for error code to avoid throwing exceptions. 34 | // Function returns connection which can be used as ConnectionProvider for futher requests or to 35 | // get additional information about error through error context. 36 | ozo::error_code ec; 37 | // This allows to use _SQL literals 38 | using namespace ozo::literals; 39 | using namespace std::chrono_literals; 40 | const auto connection = ozo::request(conn_info[io], "SELECT 1"_SQL, 1s, ozo::into(result), yield[ec]); 41 | 42 | // When request is completed we check is there an error. This example should not produce any errors 43 | // if there are no problems with target database, network or permissions for given user in connection 44 | // string. 45 | if (ec) { 46 | std::cout << "Request failed with error: " << ec.message(); 47 | // Here we should check if the connection is in null state to avoid UB. 48 | if (!ozo::is_null_recursive(connection)) { 49 | // Let's check libpq native error message and if so - print it out 50 | if (auto msg = ozo::error_message(connection); !msg.empty()) { 51 | std::cout << ", error message: " << msg; 52 | } 53 | // Sometimes libpq native error message is not enough, so let's check 54 | // the additional error context from OZO 55 | if (auto ctx = ozo::get_error_context(connection); !ctx.empty()) { 56 | std::cout << ", error context: " << ctx; 57 | } 58 | } 59 | std::cout << std::endl; 60 | return; 61 | } 62 | 63 | // Just print request result 64 | std::cout << "Selected:" << std::endl; 65 | for (auto value : result) { 66 | std::cout << std::get<0>(value) << std::endl; 67 | } 68 | }; 69 | 70 | // All IO is asynchronous, therefore we have a choice here, what should be our CompletionToken. 71 | // We use Boost.Coroutines to write asynchronous code in synchronouse style. Coroutine will be 72 | // called after io.run() is called. 73 | asio::spawn(io, coroutine); 74 | 75 | io.run(); 76 | 77 | return 0; 78 | } 79 | -------------------------------------------------------------------------------- /include/ozo/shortcuts.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace ozo { 9 | 10 | template 11 | using typed_row = std::tuple; 12 | 13 | /** 14 | * @ingroup group-requests-types 15 | * @brief Shortcut for easy result container definition. 16 | * 17 | * This shortcut defines `std::vector` container for row tuples. 18 | * @note It is very important to keep a sequence of types according to fields in query statement (see the example below). 19 | * 20 | * ### Example 21 | * 22 | @code{cpp} 23 | 24 | // Query statement 25 | const auto query = 26 | "SELECT id , name FROM users_info WHERE amount>="_SQL + std::int64_t(25); 27 | // ---- ====== 28 | // V V 29 | ozo::rows_of rows; 30 | // ------------ =========== 31 | 32 | ozo::request(conn_info[io], query, ozo::into(rows), boost::asio::use_future); 33 | @endcode 34 | * @tparam Ts --- types of columns in result 35 | */ 36 | template 37 | using rows_of = std::vector>; 38 | 39 | /** 40 | * @ingroup group-requests-types 41 | * @brief Shortcut for easy result container definition. 42 | * 43 | * This shortcut defines `std::list` container for row tuples. 44 | * @note It is very important to keep a sequence of types according to fields in query statement (see the example below). 45 | * 46 | * ### Example 47 | * 48 | @code{cpp} 49 | 50 | // Query statement 51 | const auto query = 52 | "SELECT id , name FROM users_info WHERE amount>="_SQL + std::int64_t(25); 53 | // ---- ====== 54 | // V V 55 | ozo::lrows_of rows; 56 | // ------------ =========== 57 | 58 | ozo::request(conn_info[io], query, ozo::into(rows), boost::asio::use_future); 59 | @endcode 60 | * @tparam Ts --- types of columns in result 61 | */ 62 | template 63 | using lrows_of = std::list>; 64 | 65 | /** 66 | * @ingroup group-requests-functions 67 | * @brief Shortcut for create result container back inserter. 68 | * 69 | * This shortcut defines insert iterator for row container. 70 | * 71 | * ### Example 72 | * 73 | @code{cpp} 74 | 75 | // Query statement 76 | const auto query = "SELECT id, name FROM users_info WHERE amount>="_SQL + std::int64_t(25); 77 | 78 | ozo::rows_of rows; 79 | 80 | ozo::request(conn_info[io], query, ozo::into(rows), boost::asio::use_future); 81 | @endcode 82 | * @param v --- container for rows 83 | */ 84 | template 85 | constexpr auto into(T& v) { return std::back_inserter(v);} 86 | 87 | /** 88 | * @ingroup group-requests-functions 89 | * @brief Shortcut for create reference wrapper for `ozo::basic_result`. 90 | * 91 | * This shortcut creates reference wrapper for `ozo::basic_result` to obtain raw result into it. 92 | * 93 | * ### Example 94 | * 95 | @code{cpp} 96 | 97 | // Query statement 98 | const auto query = "SELECT id, name FROM users_info WHERE amount>="_SQL + std::int64_t(25); 99 | 100 | ozo::result res; 101 | 102 | ozo::request(conn_info[io], query, ozo::into(res), boost::asio::use_future); 103 | @endcode 104 | * @param v --- `ozo::basic_result` object for rows. 105 | */ 106 | template 107 | constexpr auto into(basic_result& v) noexcept { return std::ref(v);} 108 | 109 | } // namespace ozo 110 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | project(ozo VERSION 0.0.1) 4 | 5 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules") 6 | 7 | # note: boost::coroutine should automatically pull in all the dependencies, 8 | # however, due to changes in boost it doesn't always work until CMake 9 | # is updated. we therefore look for all the dependencies ourselves. 10 | find_package(Boost COMPONENTS coroutine context system thread atomic REQUIRED) 11 | find_package(PostgreSQL REQUIRED) 12 | 13 | # Try and find provided resource pool (e.g. from conan), default to vendored version otherwise 14 | find_package(resource_pool 0.1.0 QUIET) 15 | if (NOT resource_pool_FOUND) 16 | add_subdirectory(contrib) 17 | endif() 18 | 19 | option(OZO_BUILD_TESTS "Enable tests build" OFF) 20 | option(OZO_COVERAGE "Enable tests coverage" OFF) 21 | option(OZO_BUILD_EXAMPLES "Enable examples build" OFF) 22 | 23 | set(CMAKE_CXX_EXTENSIONS OFF) 24 | 25 | add_library(ozo INTERFACE) 26 | add_library(yandex::ozo ALIAS ozo) 27 | 28 | target_compile_features(ozo INTERFACE cxx_std_17) 29 | 30 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") 31 | # disable warnings about a compiler-specific option used for gnu-compatible compilers 32 | target_compile_options(ozo INTERFACE $) 33 | endif() 34 | 35 | 36 | target_include_directories(ozo INTERFACE 37 | $ 38 | $ 39 | ) 40 | 41 | target_compile_definitions(ozo INTERFACE -DBOOST_COROUTINES_NO_DEPRECATION_WARNING) 42 | target_compile_definitions(ozo INTERFACE -DBOOST_HANA_CONFIG_ENABLE_STRING_UDL) 43 | # For the time OZO may not support Executor TS 44 | # See https://github.com/yandex/ozo/issues/266 45 | target_compile_definitions(ozo INTERFACE -DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT) 46 | 47 | target_link_libraries(ozo INTERFACE Boost::coroutine) 48 | target_link_libraries(ozo INTERFACE PostgreSQL::PostgreSQL) 49 | target_link_libraries(ozo INTERFACE elsid::resource_pool) 50 | 51 | install( 52 | DIRECTORY include/ozo 53 | DESTINATION include 54 | ) 55 | 56 | install( 57 | TARGETS ozo 58 | EXPORT ozo-targets 59 | DESTINATION lib 60 | ) 61 | 62 | install( 63 | EXPORT ozo-targets 64 | NAMESPACE yandex:: 65 | DESTINATION lib/cmake/ozo 66 | ) 67 | 68 | include(CMakePackageConfigHelpers) 69 | write_basic_package_version_file( 70 | "${CMAKE_CURRENT_BINARY_DIR}/ozo/ozo-config-version.cmake" 71 | VERSION ${PROJECT_VERSION} 72 | COMPATIBILITY AnyNewerVersion 73 | ) 74 | 75 | export( 76 | EXPORT ozo-targets 77 | FILE "${CMAKE_CURRENT_BINARY_DIR}/ozo/ozo-targets.cmake" 78 | NAMESPACE yandex:: 79 | ) 80 | 81 | configure_file(cmake/ozo-config.cmake 82 | "${CMAKE_CURRENT_BINARY_DIR}/ozo/ozo-config.cmake" 83 | COPYONLY 84 | ) 85 | 86 | install( 87 | FILES 88 | cmake/ozo-config.cmake 89 | cmake/modules/FindPostgreSQL.cmake 90 | "${CMAKE_CURRENT_BINARY_DIR}/ozo/ozo-config-version.cmake" 91 | DESTINATION 92 | lib/cmake/ozo 93 | ) 94 | 95 | if(OZO_BUILD_TESTS) 96 | enable_testing() 97 | add_subdirectory(tests) 98 | endif() 99 | 100 | if(OZO_BUILD_EXAMPLES) 101 | add_subdirectory(examples) 102 | endif() 103 | 104 | if(OZO_BUILD_BENCHMARKS) 105 | add_subdirectory(benchmarks) 106 | endif() 107 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_program(CCACHE_FOUND ccache) 2 | 3 | include(ExternalProject) 4 | ExternalProject_Add( 5 | GoogleTest 6 | GIT_REPOSITORY "https://github.com/google/googletest.git" 7 | GIT_TAG release-1.10.0 8 | CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR} 9 | UPDATE_COMMAND "" 10 | LOG_DOWNLOAD ON 11 | LOG_CONFIGURE ON 12 | LOG_BUILD ON 13 | ) 14 | ExternalProject_Get_Property(GoogleTest source_dir) 15 | include_directories(SYSTEM "${source_dir}/include") 16 | include_directories(SYSTEM "${CMAKE_CURRENT_BINARY_DIR}/include") 17 | link_directories("${CMAKE_CURRENT_BINARY_DIR}/lib") 18 | 19 | include_directories("${CMAKE_CURRENT_SOURCE_DIR}") 20 | 21 | set(SOURCES 22 | impl/async_connect.cpp 23 | binary_deserialization.cpp 24 | binary_query.cpp 25 | binary_serialization.cpp 26 | bind.cpp 27 | composite.cpp 28 | connection.cpp 29 | connection_info.cpp 30 | connection_pool.cpp 31 | query_builder.cpp 32 | query_conf.cpp 33 | type_traits.cpp 34 | concept.cpp 35 | result.cpp 36 | none.cpp 37 | deadline.cpp 38 | error.cpp 39 | impl/async_send_query_params.cpp 40 | impl/async_get_result.cpp 41 | detail/base36.cpp 42 | detail/begin_statement_builder.cpp 43 | detail/functional.cpp 44 | detail/timeout_handler.cpp 45 | detail/make_copyable.cpp 46 | impl/request_oid_map.cpp 47 | impl/request_oid_map_handler.cpp 48 | impl/async_start_transaction.cpp 49 | impl/async_end_transaction.cpp 50 | transaction_status.cpp 51 | impl/async_request.cpp 52 | io/size_of.cpp 53 | failover/retry.cpp 54 | failover/strategy.cpp 55 | failover/role_based.cpp 56 | detail/deadline.cpp 57 | impl/cancel.cpp 58 | transaction.cpp 59 | main.cpp 60 | ) 61 | 62 | if(OZO_BUILD_PG_TESTS) 63 | set(SOURCES 64 | ${SOURCES} 65 | integration/result_integration.cpp 66 | integration/request_integration.cpp 67 | integration/get_connection_integration.cpp 68 | integration/execute_integration.cpp 69 | integration/transaction_integration.cpp 70 | integration/retry_integration.cpp 71 | integration/cancel_integration.cpp 72 | integration/role_based_integration.cpp 73 | integration/connection_pool_integration.cpp 74 | ) 75 | add_definitions(-DOZO_PG_TEST_CONNINFO="${OZO_PG_TEST_CONNINFO}") 76 | endif() 77 | 78 | add_executable(ozo_tests ${SOURCES}) 79 | add_dependencies(ozo_tests GoogleTest) 80 | target_link_libraries(ozo_tests gtest) 81 | target_link_libraries(ozo_tests gmock) 82 | target_link_libraries(ozo_tests ozo) 83 | add_test(ozo_tests ozo_tests) 84 | 85 | if(CCACHE_FOUND) 86 | set_target_properties(ozo_tests PROPERTIES RULE_LAUNCH_COMPILE ccache) 87 | set_target_properties(ozo_tests PROPERTIES RULE_LAUNCH_LINK ccache) 88 | endif() 89 | 90 | # enable useful warnings and errors 91 | target_compile_options(ozo_tests PRIVATE -Wall -Wextra -pedantic -Werror) 92 | 93 | # ignore specific errors for clang 94 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 95 | target_compile_options(ozo_tests PRIVATE -Wno-ignored-optimization-argument) 96 | endif() 97 | 98 | if(OZO_COVERAGE AND CMAKE_COMPILER_IS_GNUCXX) 99 | include(CodeCoverage) 100 | APPEND_COVERAGE_COMPILER_FLAGS() 101 | set(COVERAGE_EXCLUDES "'.*/(tests|contrib|gmock|gtest)/.*'") 102 | SETUP_TARGET_FOR_COVERAGE_GCOVR( 103 | NAME ozo_coverage 104 | EXECUTABLE ctest -V 105 | DEPENDENCIES ozo_tests 106 | ) 107 | endif() 108 | -------------------------------------------------------------------------------- /include/ozo/impl/request_oid_map.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo::impl { 7 | 8 | template 9 | inline T unwrap_type_c(hana::basic_type); 10 | 11 | template 12 | inline auto get_types_names(const oid_map_t& oid_map) { 13 | return hana::unpack(hana::keys(oid_map.impl), [](auto const& ...x) { 14 | return std::array{ 15 | {type_name()...} 16 | }; 17 | }); 18 | } 19 | 20 | template 21 | inline auto make_oids_query(const oid_map_t& oid_map) { 22 | using namespace literals; 23 | return "SELECT COALESCE(to_regtype(f)::oid, 0) AS oid FROM UNNEST("_SQL 24 | + get_types_names(oid_map) + ") AS f"_SQL; 25 | } 26 | 27 | using oids_result = std::vector; 28 | 29 | template 30 | inline void set_oid_map(oid_map_t& oid_map, const oids_result& res) { 31 | if (hana::length(oid_map.impl).value != res.size()) { 32 | throw std::length_error(std::string("result size ") 33 | + std::to_string(res.size()) 34 | + " does not match to oid map size " 35 | + std::to_string(hana::length(oid_map.impl))); 36 | } 37 | 38 | auto i = res.begin(); 39 | 40 | hana::for_each(hana::keys(oid_map.impl), [&] (const auto& key) { 41 | if (*i == null_oid) { 42 | throw std::invalid_argument( 43 | std::string("null oid for type ") 44 | + boost::core::demangle(typeid(key).name()) 45 | + " which is mapped as " 46 | + type_name()); 47 | } 48 | oid_map.impl[key] = *i; 49 | ++i; 50 | }); 51 | } 52 | 53 | template 54 | struct request_oid_map_op { 55 | struct context { 56 | Handler handler_; 57 | oids_result res_; 58 | context(Handler&& handler) 59 | : handler_(std::move(handler)) {} 60 | }; 61 | 62 | std::shared_ptr ctx_; 63 | 64 | request_oid_map_op(Handler handler) { 65 | auto allocator = asio::get_associated_allocator(handler); 66 | ctx_ = std::allocate_shared(allocator, std::move(handler)); 67 | } 68 | 69 | template 70 | void perform(Connection&& conn) { 71 | const auto oid_map = ozo::unwrap_connection(conn).oid_map(); 72 | ctx_->res_.reserve(hana::length(oid_map.impl)); 73 | async_request(std::forward(conn), make_oids_query(oid_map), 74 | none, std::back_inserter(ctx_->res_), std::move(*this)); 75 | } 76 | 77 | template 78 | void operator() (error_code ec, Connection&& conn) { 79 | if (!ec) try { 80 | set_oid_map(ozo::unwrap_connection(conn).oid_map(), ctx_->res_); 81 | } catch (const std::exception& e) { 82 | unwrap_connection(conn).set_error_context(e.what()); 83 | ec = error::oid_request_failed; 84 | } 85 | ctx_->handler_(ec, std::forward(conn)); 86 | } 87 | 88 | using executor_type = asio::associated_executor_t; 89 | 90 | executor_type get_executor() const noexcept { 91 | return asio::get_associated_executor(ctx_->handler_); 92 | } 93 | 94 | using allocator_type = asio::associated_allocator_t; 95 | 96 | allocator_type get_allocator() const noexcept { 97 | return asio::get_associated_allocator(ctx_->handler_); 98 | } 99 | }; 100 | 101 | } // namespace ozo::impl 102 | -------------------------------------------------------------------------------- /include/ozo/core/unwrap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace ozo { 10 | 11 | namespace hana = boost::hana; 12 | 13 | /** 14 | * @brief Default implementation of `ozo::unwrap()` 15 | * 16 | * This functional class defines how to unwrap an object of type T by means of 17 | * static member function apply. E.g. in most common cases it may be defined 18 | * in general way like: 19 | * 20 | * @code 21 | template 22 | constexpr static decltype(auto) apply(Arg&& v); 23 | * @endcode 24 | * 25 | * In case if you want to specify different behaviour for different type of references 26 | * the function may be implemented in specific way like: 27 | * 28 | * @code 29 | constexpr static decltype(auto) apply(T&& v); 30 | constexpr static decltype(auto) apply(T& v); 31 | constexpr static decltype(auto) apply(const T& v); 32 | * @endcode 33 | * 34 | * @tparam T --- decayed type to apply function to. 35 | * @tparam When --- boost::hana::when specified condition for the overloading. 36 | * @ingroup group-core-types 37 | */ 38 | template > 39 | struct unwrap_impl : detail::functional::forward {}; 40 | 41 | /** 42 | * @brief Unwraps argument underlying value or forwards the argument. 43 | * 44 | * This utility function returns underlying value of it's argument as defined by 45 | * the operation implementation `ozo::unwrap_impl`. 46 | * 47 | * In-library implementations returns: 48 | * - reference on an underlying value - `std::reference_wrapper` and other types like pointers, 49 | * optional values which are defined via @ref group-ext, 50 | * - argument itself by default. 51 | * 52 | * @param value --- object to unwrap 53 | * @return implementation depended result of `unwrap_impl>::apply(std::forward(v))` call 54 | * @ingroup group-core-functions 55 | */ 56 | template 57 | constexpr decltype(auto) unwrap(T&& v) noexcept( 58 | noexcept(unwrap_impl>::apply(std::forward(v)))) { 59 | return unwrap_impl>::apply(std::forward(v)); 60 | } 61 | 62 | template 63 | struct unwrap_impl> { 64 | template 65 | constexpr static decltype(auto) apply(Ref&& v) noexcept { 66 | return v.get(); 67 | } 68 | }; 69 | 70 | /** 71 | * @brief Unwraps nullable or reference wrapped type 72 | * @ingroup group-core-types 73 | * Sometimes it is needed to know the underlying type of #Nullable or 74 | * type is wrapped with `std::reference_type`. So that is why the type exists. 75 | * Convenient shortcut is `ozo::unwrap_type`. 76 | * @tparam T -- type to unwrap. 77 | * 78 | * ### Example 79 | * 80 | * @code 81 | int a = 42; 82 | auto ref_a = std::ref(a); 83 | auto opt_a = std::make_optional(a); 84 | 85 | static_assert(std::is_same, decltype(a)>); 86 | static_assert(std::is_same, decltype(a)>); 87 | * @endcode 88 | * 89 | * @sa ozo::unwrap_type, ozo::unwrap 90 | */ 91 | template 92 | struct get_unwrapped_type { 93 | #ifdef OZO_DOCUMENTATION 94 | using type = ; //!< Unwrapped type 95 | #else 96 | using type = std::decay_t()))>; 97 | #endif 98 | }; 99 | 100 | /** 101 | * @brief Shortcut for `ozo::get_unwrapped_type`. 102 | * @ingroup group-core-types 103 | * 104 | * @tparam T -- type to unwrap. 105 | */ 106 | template 107 | using unwrap_type = typename get_unwrapped_type::type; 108 | 109 | } // namespace ozo 110 | -------------------------------------------------------------------------------- /include/ozo/execute.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ozo { 6 | 7 | #ifdef OZO_DOCUMENTATION 8 | /** 9 | * @brief Executes query with no result data expected 10 | * 11 | * This function is same as `ozo::request()` function except it does not provide any result data. 12 | * It suitable to use with `UPDATE` `INSERT` statements, or invoking procedures without result. 13 | * 14 | * @note The function does not particitate in ADL since could be implemented via functional object. 15 | * 16 | * @param provider --- connection provider object 17 | * @param query --- query object to execute 18 | * @param time_constraint --- request #TimeConstraint; this time constrain includes time for getting connection from provider. 19 | * @param token --- operation #CompletionToken. 20 | * @return deduced from #CompletionToken. 21 | * @ingroup group-requests-functions 22 | */ 23 | template 24 | decltype(auto) execute(ConnectionProvider&& provider, BinaryQueryConvertible&& query, TimeConstraint time_constraint, CompletionToken&& token); 25 | 26 | /** 27 | * @brief Executes query with no result data expected 28 | * 29 | * This function is time constrain free shortcut to `ozo::execute()` function. 30 | * Its call is equal to `ozo::execute(provider, query, ozo::none, token)` call. 31 | * 32 | * @note The function does not particitate in ADL since could be implemented via functional object. 33 | * 34 | * @param provider --- connection provider object 35 | * @param query --- query object to execute 36 | * @param token --- operation #CompletionToken. 37 | * @return deduced from #CompletionToken. 38 | * @ingroup group-requests-functions 39 | */ 40 | template 41 | decltype(auto) execute(ConnectionProvider&& provider, BinaryQueryConvertible&& query, CompletionToken&& token); 42 | #else 43 | 44 | template 45 | struct execute_op : base_async_operation , Initiator> { 46 | using base = typename execute_op::base; 47 | using base::base; 48 | 49 | template 50 | decltype(auto) operator() (P&& provider, Q&& query, TimeConstraint t, CompletionToken&& token) const { 51 | static_assert(ConnectionProvider

, "provider should be a ConnectionProvider"); 52 | static_assert(ozo::TimeConstraint, "should model TimeConstraint concept"); 53 | return async_initiate>( 54 | get_operation_initiator(*this), token, std::forward

(provider), t, std::forward(query)); 55 | } 56 | 57 | template 58 | decltype(auto) operator() (P&& provider, Q&& query, CompletionToken&& token) const { 59 | return (*this)(std::forward

(provider), std::forward(query), none, 60 | std::forward(token)); 61 | } 62 | 63 | template 64 | constexpr static auto rebind_initiator(const OtherInitiator& other) { 65 | return execute_op{other}; 66 | } 67 | }; 68 | 69 | namespace detail { 70 | struct initiate_async_execute { 71 | template 72 | constexpr void operator()(Handler&& h, P&& provider, TimeConstraint t, Q&& query) const { 73 | impl::async_execute(std::forward

(provider), std::forward(query), t, std::forward(h)); 74 | } 75 | }; 76 | } // namespace detail 77 | 78 | constexpr execute_op execute; 79 | #endif 80 | } // namespace ozo 81 | -------------------------------------------------------------------------------- /include/ozo/impl/io.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | namespace ozo::impl { 16 | 17 | /** 18 | * Query states. The values similar to PQflush function results. 19 | * This state is used to synchronize async query parameters 20 | * send process with async query result receiving process. 21 | */ 22 | enum class query_state : int { 23 | error = -1, 24 | send_finish = 0, 25 | send_in_progress = 1 26 | }; 27 | 28 | namespace pq { 29 | 30 | template 31 | inline auto pq_start_connection(const T&, const std::string& conninfo) { 32 | static_assert(Connection, "T must be a Connection"); 33 | return ozo::pg::make_safe(PQconnectStart(conninfo.c_str())); 34 | } 35 | 36 | } // namespace pq 37 | 38 | template 39 | inline auto start_connection(T& conn, const std::string& conninfo) { 40 | static_assert(Connection, "T must be a Connection"); 41 | using pq::pq_start_connection; 42 | return pq_start_connection(unwrap_connection(conn), conninfo); 43 | } 44 | 45 | template 46 | inline int connect_poll(T& conn) { 47 | static_assert(Connection, "T must be a Connection"); 48 | return PQconnectPoll(get_native_handle(conn)); 49 | } 50 | 51 | template 52 | inline int send_query_params(T& conn, const binary_query& q) noexcept { 53 | static_assert(Connection, "T must be a Connection"); 54 | return PQsendQueryParams(get_native_handle(conn), 55 | q.text(), 56 | q.params_count(), 57 | q.types(), 58 | q.values(), 59 | q.lengths(), 60 | q.formats(), 61 | int(result_format::binary) 62 | ); 63 | } 64 | 65 | template 66 | inline error_code set_nonblocking(T& conn) noexcept { 67 | static_assert(Connection, "T must be a Connection"); 68 | if (PQsetnonblocking(get_native_handle(conn), 1)) { 69 | return error::pg_set_nonblocking_failed; 70 | } 71 | return {}; 72 | } 73 | 74 | template 75 | inline error_code consume_input(T& conn) noexcept { 76 | static_assert(Connection, "T must be a Connection"); 77 | if (!PQconsumeInput(get_native_handle(conn))) { 78 | return error::pg_consume_input_failed; 79 | } 80 | return {}; 81 | } 82 | 83 | template 84 | inline bool is_busy(T& conn) noexcept { 85 | static_assert(Connection, "T must be a Connection"); 86 | return PQisBusy(get_native_handle(conn)); 87 | } 88 | 89 | template 90 | inline query_state flush_output(T& conn) noexcept { 91 | static_assert(Connection, "T must be a Connection"); 92 | return static_cast(PQflush(get_native_handle(conn))); 93 | } 94 | 95 | template 96 | inline decltype(auto) get_result(T& conn) noexcept { 97 | static_assert(Connection, "T must be a Connection"); 98 | return ozo::pg::make_safe(PQgetResult(get_native_handle(conn))); 99 | } 100 | 101 | template 102 | inline ExecStatusType result_status(const T& res) noexcept { 103 | return PQresultStatus(std::addressof(res)); 104 | } 105 | 106 | template 107 | inline error_code result_error(const T& res) noexcept { 108 | if (auto s = PQresultErrorField(std::addressof(res), PG_DIAG_SQLSTATE)) { 109 | return sqlstate::make_error_code(std::strtol(s, nullptr, 36)); 110 | } 111 | return error::no_sql_state_found; 112 | } 113 | 114 | } // namespace ozo::impl 115 | -------------------------------------------------------------------------------- /tests/integration/role_based_integration.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace { 14 | 15 | namespace hana = boost::hana; 16 | namespace failover = ozo::failover; 17 | using op = failover::role_based_options; 18 | 19 | using namespace testing; 20 | 21 | struct callback_mock { 22 | template 23 | void operator () (ozo::error_code ec, Connection&&) const { 24 | call(ec); 25 | } 26 | template 27 | void operator () (ozo::error_code ec, Connection&&, const Fallback&) const { 28 | call(ec); 29 | } 30 | MOCK_CONST_METHOD1(call, void(ozo::error_code)); 31 | }; 32 | 33 | TEST(role_based, should_return_success_for_invalid_connection_info_retried_with_valid_connection_info) { 34 | using namespace ozo::literals; 35 | using namespace hana::literals; 36 | 37 | ozo::io_context io; 38 | auto conn_info = failover::make_role_based_connection_source( 39 | failover::master=ozo::connection_info("invalid connection info"), 40 | failover::replica=ozo::connection_info(OZO_PG_TEST_CONNINFO) 41 | ); 42 | StrictMock callback; 43 | auto roles = failover::role_based(failover::master, failover::replica) 44 | .set(op::on_fallback=std::cref(callback)); 45 | 46 | InSequence s; 47 | EXPECT_CALL(callback, call(Eq(ozo::errc::connection_error))); 48 | EXPECT_CALL(callback, call(ozo::error_code{})); 49 | 50 | std::vector res; 51 | ozo::request[roles](conn_info[io], "SELECT 1"_SQL + " + 1"_SQL, ozo::into(res), 52 | std::cref(callback)); 53 | 54 | io.run(); 55 | } 56 | 57 | TEST(role_based, should_not_try_next_role_and_return_error_for_sql_syntax_error) { 58 | using namespace ozo::literals; 59 | using namespace hana::literals; 60 | 61 | ozo::io_context io; 62 | auto conn_info = failover::make_role_based_connection_source( 63 | failover::master=ozo::connection_info(OZO_PG_TEST_CONNINFO), 64 | failover::replica=ozo::connection_info(OZO_PG_TEST_CONNINFO) 65 | ); 66 | StrictMock callback; 67 | auto roles = failover::role_based(failover::master, failover::replica) 68 | .set(op::on_fallback=std::cref(callback)); 69 | 70 | std::vector res; 71 | ozo::request[roles](conn_info[io], "BAD QUERY"_SQL, ozo::into(res), 72 | std::cref(callback)); 73 | EXPECT_CALL(callback, call(Eq(ozo::sqlstate::syntax_error_or_access_rule_violation))); 74 | io.run(); 75 | } 76 | 77 | TEST(role_based, should_return_error_for_invalid_connection_info_of_all_roles) { 78 | using namespace ozo::literals; 79 | using namespace hana::literals; 80 | 81 | ozo::io_context io; 82 | auto conn_info = failover::make_role_based_connection_source( 83 | failover::master=ozo::connection_info("invalid connection info"), 84 | failover::replica=ozo::connection_info("invalid connection info") 85 | ); 86 | StrictMock callback; 87 | auto roles = failover::role_based(failover::master, failover::replica) 88 | .set(op::on_fallback=std::cref(callback)); 89 | 90 | EXPECT_CALL(callback, call(Eq(ozo::errc::connection_error))).Times(2); 91 | 92 | std::vector res; 93 | ozo::request[roles](conn_info[io], "SELECT 1"_SQL + " + 1"_SQL, ozo::into(res), 94 | std::cref(callback)); 95 | 96 | io.run(); 97 | } 98 | 99 | } // namespace 100 | -------------------------------------------------------------------------------- /include/ozo/core/strong_typedef.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | 8 | template 9 | class strong_typedef_wrapper 10 | : boost::totally_ordered1 11 | , boost::totally_ordered2, T>> { 12 | 13 | T base_; 14 | public: 15 | using base_type = T; 16 | 17 | constexpr explicit strong_typedef_wrapper(const base_type& v) noexcept(noexcept(base_type(v))) 18 | : base_(v) {} 19 | constexpr explicit strong_typedef_wrapper(base_type&& v) noexcept(noexcept(base_type(v))) 20 | : base_(v) {} 21 | 22 | constexpr strong_typedef_wrapper() = default; 23 | constexpr strong_typedef_wrapper(const strong_typedef_wrapper&) = default; 24 | constexpr strong_typedef_wrapper(strong_typedef_wrapper&&) = default; 25 | 26 | constexpr strong_typedef_wrapper& operator = (const strong_typedef_wrapper&) = default; 27 | constexpr strong_typedef_wrapper& operator = (strong_typedef_wrapper&&) = default; 28 | 29 | constexpr strong_typedef_wrapper& operator = (const base_type& rhs) noexcept(noexcept(base_ = rhs)) { 30 | base_ = rhs; 31 | return *this; 32 | } 33 | 34 | constexpr strong_typedef_wrapper& operator = (base_type&& rhs) noexcept(noexcept(base_ = std::move(rhs))) { 35 | base_ = std::move(rhs); 36 | return *this; 37 | } 38 | 39 | constexpr const base_type& get() const & noexcept { return base_;} 40 | constexpr base_type& get() & noexcept { return base_;} 41 | constexpr base_type&& get() && noexcept { return std::move(base_);} 42 | 43 | constexpr operator const base_type&() const & noexcept {return get();} 44 | constexpr operator base_type&() & noexcept {return get();} 45 | constexpr operator base_type&&() && noexcept {return get();} 46 | 47 | constexpr bool operator == (const strong_typedef_wrapper& rhs) const {return base_ == rhs.base_;} 48 | constexpr bool operator < (const strong_typedef_wrapper& rhs) const {return base_ < rhs.base_;} 49 | }; 50 | 51 | template 52 | struct is_strong_typedef : std::false_type {}; 53 | 54 | template 55 | struct is_strong_typedef> : std::true_type {}; 56 | 57 | template 58 | inline constexpr auto StrongTypedef = is_strong_typedef>::value; 59 | 60 | } // namespace ozo 61 | 62 | /** 63 | * @brief Strong typedef 64 | * @ingroup group-core-types 65 | * 66 | * C++ `typedef` creates only an alias to a base type, so the both types are the same. 67 | * To get a really new type it is necessary to make some boilerplate code. 68 | * This macro do such work. It is very similar to `BOOST_STRONG_TYPEDEF` 69 | * Except the new type has `base_type` type and `get()` method which provides 70 | * access to underlying base type object. 71 | * @note This macro may be used inside a namespace. 72 | * @param base --- base type 73 | * @param type --- new type 74 | * 75 | * ### Example 76 | * 77 | * @code 78 | 79 | namespace demo { 80 | 81 | OZO_STRONG_TYPEDEF(std::vector, bytes) 82 | 83 | } 84 | // Types are completely different 85 | static_assert(!std::is_same_v, demo::bytes>); 86 | // Type bytes::base_type is same as a first argument of the macro 87 | static_assert(std::is_same_v, demo::bytes::base_type>); 88 | 89 | demo::bytes b{}; 90 | std::vector& base(b); 91 | 92 | // Member function bytes::get() provides access to the underlying 93 | // base type object 94 | assert(std::addressof(base) == std::addressof(b.get())); 95 | * @endcode 96 | */ 97 | #ifdef OZO_DOCUMENTATION 98 | #define OZO_STRONG_TYPEDEF(base, type) 99 | #else 100 | #define OZO_STRONG_TYPEDEF(base, type)\ 101 | using type = ozo::strong_typedef_wrapper; 102 | #endif 103 | -------------------------------------------------------------------------------- /tests/impl/cancel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | 10 | namespace { 11 | 12 | using namespace std::literals; 13 | using namespace ::testing; 14 | 15 | struct dispatch_cancel : Test { 16 | struct cancel_handle_mock { 17 | using self_type = cancel_handle_mock; 18 | MOCK_METHOD1(pq_cancel, bool(std::string& )); 19 | 20 | friend bool pq_cancel(self_type* self, std::string& err) { 21 | return self->pq_cancel(err); 22 | } 23 | } handle; 24 | }; 25 | 26 | TEST_F(dispatch_cancel, should_return_no_error_and_empty_string_if_pq_cancel_returns_true) { 27 | EXPECT_CALL(handle, pq_cancel(_)).WillOnce(Return(true)); 28 | auto [ec, msg] = ozo::impl::dispatch_cancel(&handle); 29 | EXPECT_FALSE(ec); 30 | EXPECT_TRUE(msg.empty()); 31 | } 32 | 33 | TEST_F(dispatch_cancel, should_return_pq_cancel_failed_and_non_empty_string_if_pq_cancel_returns_false_and_sets_message) { 34 | EXPECT_CALL(handle, pq_cancel(_)).WillOnce(Invoke([](std::string& msg){ 35 | msg = "error message"; 36 | return false; 37 | })); 38 | auto [ec, msg] = ozo::impl::dispatch_cancel(&handle); 39 | EXPECT_EQ(ozo::error_code{ozo::error::pq_cancel_failed}, ec); 40 | EXPECT_FALSE(msg.empty()); 41 | } 42 | 43 | TEST_F(dispatch_cancel, should_remove_trailing_zeroes_from_error_message) { 44 | EXPECT_CALL(handle, pq_cancel(_)).WillOnce(Invoke([](std::string& msg){ 45 | msg = "error message\0\0\0\0\0\0\0\0\0\0"; 46 | return false; 47 | })); 48 | auto [ec, msg] = ozo::impl::dispatch_cancel(&handle); 49 | EXPECT_TRUE(ec); 50 | EXPECT_EQ(msg, "error message"s); 51 | } 52 | 53 | TEST_F(dispatch_cancel, should_return_empty_string_from_all_zeroes) { 54 | EXPECT_CALL(handle, pq_cancel(_)).WillOnce(Invoke([](std::string& msg){ 55 | msg = "\0\0\0\0\0\0\0\0\0\0"; 56 | return false; 57 | })); 58 | auto [ec, msg] = ozo::impl::dispatch_cancel(&handle); 59 | EXPECT_TRUE(ec); 60 | EXPECT_TRUE(msg.empty()); 61 | } 62 | 63 | struct cancel_handle_mock { 64 | MOCK_CONST_METHOD0(dispatch_cancel, std::tuple()); 65 | }; 66 | 67 | struct cancel_handle { 68 | cancel_handle_mock* mock_ = nullptr; 69 | ozo::tests::executor_mock* executor_ = nullptr; 70 | 71 | cancel_handle(cancel_handle_mock& mock, ozo::tests::executor_mock& executor) 72 | : mock_(std::addressof(mock)), executor_(std::addressof(executor)) {} 73 | 74 | using executor_type = ozo::tests::executor; 75 | 76 | executor_type get_executor() const { return executor_type(*executor_);} 77 | 78 | friend auto dispatch_cancel(cancel_handle self) { 79 | return self.mock_->dispatch_cancel(); 80 | } 81 | }; 82 | 83 | struct initiate_async_cancel : Test { 84 | StrictMock timer; 85 | StrictMock strand; 86 | ozo::tests::execution_context io; 87 | StrictMock cancel_handle_; 88 | StrictMock handle_executor; 89 | StrictMock> callback; 90 | 91 | ozo::impl::initiate_async_cancel initiate_async_cancel_; 92 | }; 93 | 94 | TEST_F(initiate_async_cancel, should_post_cancel_op_into_cancel_handle_attached_executor) { 95 | EXPECT_CALL(handle_executor, post(_)); 96 | initiate_async_cancel_(ozo::tests::wrap(callback), cancel_handle(cancel_handle_, handle_executor)); 97 | } 98 | 99 | 100 | TEST_F(initiate_async_cancel, should_post_cancel_op_with_time_constraint_into_cancel_handle_attached_executor_and_wait_for_timer) { 101 | EXPECT_CALL(io.timer_service_, timer(An())).WillOnce(ReturnRef(timer)); 102 | EXPECT_CALL(io.strand_service_, get_executor()).WillOnce(ReturnRef(strand)); 103 | EXPECT_CALL(handle_executor, post(_)); 104 | EXPECT_CALL(timer, async_wait(_)); 105 | initiate_async_cancel_(ozo::tests::wrap(callback), cancel_handle(cancel_handle_, handle_executor), io, ozo::time_traits::time_point{}); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /include/ozo/deadline.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ozo { 7 | /** 8 | * @brief Dealdine calculation 9 | * 10 | * Calculate deadline from time point. Literally returns it's argument. 11 | * 12 | * @param t --- time point of a deadline 13 | * @return it's argument 14 | * @ingroup group-core-functions 15 | */ 16 | inline constexpr time_traits::time_point deadline(time_traits::time_point t) noexcept { 17 | return t; 18 | } 19 | 20 | /** 21 | * @brief Dealdine calculation 22 | * 23 | * Calculates deadline time point in a given duration from a given time point. 24 | * The result value range is [now, time_traits::time_point::max()]. 25 | * Literally: `now + after`. 26 | * 27 | * @param after --- duration to a deadline time point 28 | * @param now --- start time point for the duration 29 | * @return calculated deadline time point 30 | * @ingroup group-core-functions 31 | */ 32 | inline constexpr time_traits::time_point deadline( 33 | time_traits::duration after, time_traits::time_point now) noexcept { 34 | if (after < time_traits::duration(0)) { 35 | return now; 36 | } 37 | return after > time_traits::time_point::max() - now ? 38 | time_traits::time_point::max() : now + after; 39 | } 40 | 41 | /** 42 | * @brief Dealdine calculation 43 | * 44 | * Calculates deadline time point in a given duration from now. 45 | * The result value is limited by time_traits::time_point::max(). 46 | * Literally: `time_traits::now() + after`. 47 | * 48 | * @param after --- duration to a deadline time point 49 | * @return calculated deadline time point 50 | * @ingroup group-core-functions 51 | */ 52 | inline time_traits::time_point deadline(time_traits::duration after) noexcept { 53 | return deadline(after, time_traits::now()) ; 54 | } 55 | 56 | /** 57 | * @brief Dealdine calculation 58 | * 59 | * Calculates deadline from `ozo::none_t`. 60 | * 61 | * @param none_t 62 | * @return `ozo::none` 63 | * @ingroup group-core-functions 64 | */ 65 | inline constexpr auto deadline(none_t) noexcept { return none;} 66 | 67 | /** 68 | * @brief Time left to deadline 69 | * 70 | * Calculates time left form given `now` time point to a given deadline time point `t`. 71 | * 72 | * @param t --- deadline time point 73 | * @param now --- time point to calculate duration from 74 | * @return time left to the time point which is more or equal to a zero. 75 | * @ingroup group-core-functions 76 | */ 77 | inline constexpr time_traits::duration time_left( 78 | time_traits::time_point t, time_traits::time_point now) noexcept { 79 | return t > now ? t - now : time_traits::duration(0); 80 | } 81 | 82 | /** 83 | * @brief Time left to deadline 84 | * 85 | * Calculates time left to a given deadline time point. 86 | * 87 | * @param t --- deadline time point 88 | * @return time left to the time point which is more or equal to a zero. 89 | * @ingroup group-core-functions 90 | */ 91 | inline time_traits::duration time_left(time_traits::time_point t) noexcept { 92 | return time_left(t, time_traits::now()); 93 | } 94 | 95 | /** 96 | * @brief Deadline is expired 97 | * 98 | * Indicates if a given dedline is expired for a given time point. Deadline is expired then time 99 | * left duration to the deadline is 0. 100 | * 101 | * @param t --- deadline time point 102 | * @return true --- deadline has been expired 103 | * @return false --- there is time left to deadline 104 | * @ingroup group-core-functions 105 | */ 106 | inline bool expired(time_traits::time_point t, time_traits::time_point now) noexcept { 107 | return time_left(t, now) == time_traits::duration(0); 108 | } 109 | 110 | /** 111 | * @brief Deadline is expired 112 | * 113 | * Indicates if a given dedline is expired. Deadline is expired then time 114 | * left duration to the deadline is 0. 115 | * 116 | * @param t --- deadline time point 117 | * @return true --- deadline has been expired 118 | * @return false --- there is time left to deadline 119 | * @ingroup group-core-functions 120 | */ 121 | inline bool expired(time_traits::time_point t) noexcept { 122 | return expired(t, time_traits::now()); 123 | } 124 | 125 | } // namespace ozo 126 | -------------------------------------------------------------------------------- /include/ozo/ext/std/duration.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | /** 13 | * @defgroup group-ext-std-chrono-duration-microseconds std::chrono::microseconds 14 | * @ingroup group-ext-std 15 | * @brief [std::chrono::microseconds](https://en.cppreference.com/w/cpp/chrono/duration) support 16 | * 17 | *@code 18 | #include 19 | *@endcode 20 | * 21 | * `std::chrono::microseconds` is mapped as `interval` PostgreSQL type. 22 | * 23 | * @note Supported 64-bit microseconds representation values only. 24 | * @note In case of overflow (underflow) maximum (minimum) valid value is used. 25 | */ 26 | 27 | namespace ozo::detail { 28 | 29 | struct pg_interval { 30 | BOOST_HANA_DEFINE_STRUCT(pg_interval, 31 | (std::int64_t, microseconds), 32 | (std::int32_t, days), 33 | (std::int32_t, months) 34 | ); 35 | }; 36 | 37 | inline pg_interval from_chrono_duration(const std::chrono::microseconds& in) { 38 | static_assert( 39 | std::chrono::microseconds::min().count() == std::numeric_limits::min() && 40 | std::chrono::microseconds::max().count() == std::numeric_limits::max(), 41 | "std::chrono::microseconds tick representation type is not supported" 42 | ); 43 | 44 | using days = std::chrono::duration>; 45 | 46 | return pg_interval{(in % days(1)).count(), std::chrono::duration_cast(in).count(), 0}; 47 | } 48 | 49 | inline std::chrono::microseconds to_chrono_duration(const pg_interval& interval) { 50 | static_assert( 51 | std::chrono::microseconds::min().count() == std::numeric_limits::min() && 52 | std::chrono::microseconds::max().count() == std::numeric_limits::max(), 53 | "std::chrono::microseconds tick representation type is not supported" 54 | ); 55 | 56 | using std::chrono::microseconds; 57 | using std::chrono::duration_cast; 58 | 59 | using usecs = std::chrono::duration; 60 | using days = std::chrono::duration>; 61 | using months = std::chrono::duration>; 62 | 63 | auto usecs_surplus = usecs(interval.microseconds) % days(1); 64 | auto days_total = months(interval.months) + days(interval.days) + duration_cast(usecs(interval.microseconds)); 65 | 66 | if (days_total < (duration_cast(microseconds::min()) - days(1)) 67 | || ((days_total < days(0)) && ((days_total + days(1)) + usecs_surplus < microseconds::min() + days(1)))) { 68 | return microseconds::min(); 69 | } 70 | 71 | if ((duration_cast(microseconds::max()) + days(1)) < days_total 72 | || ((days(0) < days_total) && (microseconds::max() - days(1) < (days_total - days(1)) + usecs_surplus))) { 73 | return microseconds::max(); 74 | } 75 | 76 | return days_total + usecs_surplus; 77 | } 78 | 79 | } // namespace ozo::detail 80 | 81 | namespace ozo { 82 | 83 | template <> 84 | struct send_impl { 85 | template 86 | static ostream& apply(ostream& out, const OidMap&, const std::chrono::microseconds& in) { 87 | static_assert(ozo::OidMap, "OidMap should model ozo::OidMap"); 88 | 89 | return write(out, detail::from_chrono_duration(in)); 90 | } 91 | }; 92 | 93 | template <> 94 | struct recv_impl { 95 | template 96 | static istream& apply(istream& in, size_type, const OidMap&, std::chrono::microseconds& out) { 97 | static_assert(ozo::OidMap, "OidMap should model ozo::OidMap"); 98 | 99 | detail::pg_interval interval; 100 | read(in, interval); 101 | 102 | out = detail::to_chrono_duration(interval); 103 | 104 | return in; 105 | } 106 | }; 107 | 108 | } // namespace ozo 109 | 110 | namespace ozo::definitions { 111 | 112 | template <> 113 | struct type : pg::type_definition{}; 114 | 115 | } // namespace ozo::definitions 116 | -------------------------------------------------------------------------------- /tests/integration/retry_integration.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace { 14 | template < 15 | typename OidMap = ozo::empty_oid_map, 16 | typename Statistics = ozo::no_statistics> 17 | struct connection_info_sequence { 18 | using base_type = ozo::connection_info; 19 | using connection_type = typename base_type::connection_type; 20 | 21 | std::vector base_; 22 | typename std::vector::const_iterator i_; 23 | 24 | static std::vector make_connection_infos(std::vector in) { 25 | const auto infos = in | boost::adaptors::transformed([](std::string& info){ 26 | return base_type{std::move(info)}; 27 | }); 28 | return {infos.begin(), infos.end()}; 29 | } 30 | 31 | connection_info_sequence(std::vector conn_infos) 32 | : base_(make_connection_infos(std::move(conn_infos))), i_(base_.cbegin()) {} 33 | 34 | connection_info_sequence(const connection_info_sequence&) = delete; 35 | connection_info_sequence(connection_info_sequence&&) = delete; 36 | 37 | template 38 | void operator ()(ozo::io_context& io, TimeConstraint t, Handler&& handler) { 39 | if (i_==base_.end()) { 40 | handler(ozo::error::pq_connection_start_failed, connection_type{}); 41 | } else { 42 | (*i_++)(io, t, std::forward(handler)); 43 | } 44 | } 45 | 46 | auto operator [](ozo::io_context& io) & { 47 | return ozo::connection_provider(*this, io); 48 | } 49 | }; 50 | 51 | #define ASSERT_REQUEST_OK(ec, conn)\ 52 | ASSERT_FALSE(ec) << ec.message() \ 53 | << "|" << ozo::error_message(conn) \ 54 | << "|" << (!ozo::is_null_recursive(conn) ? ozo::get_error_context(conn) : "") << std::endl 55 | 56 | 57 | namespace hana = boost::hana; 58 | 59 | using namespace testing; 60 | // using namespace ozo::tests; 61 | 62 | TEST(request, should_return_success_for_invalid_connection_info_retried_with_valid_connection_info) { 63 | using namespace ozo::literals; 64 | using namespace hana::literals; 65 | 66 | ozo::io_context io; 67 | connection_info_sequence<> conn_info({"invalid connection info", OZO_PG_TEST_CONNINFO}); 68 | 69 | std::vector res; 70 | ozo::request[ozo::failover::retry(ozo::errc::connection_error)*2](conn_info[io], "SELECT 1"_SQL + " + 1"_SQL, ozo::into(res), 71 | [&](ozo::error_code ec, auto conn) { 72 | ASSERT_REQUEST_OK(ec, conn); 73 | EXPECT_EQ(res.front(), 2); 74 | }); 75 | 76 | io.run(); 77 | EXPECT_EQ(conn_info.i_, conn_info.base_.end()); 78 | } 79 | 80 | TEST(request, should_return_error_and_bad_connect_for_nonretryable_error) { 81 | using namespace ozo::literals; 82 | using namespace hana::literals; 83 | 84 | ozo::io_context io; 85 | connection_info_sequence<> conn_info({"invalid connection info", OZO_PG_TEST_CONNINFO}); 86 | 87 | std::vector res; 88 | ozo::request[ozo::failover::retry(ozo::errc::database_readonly)*2](conn_info[io], "SELECT 1"_SQL + " + 1"_SQL, ozo::into(res), 89 | [&](ozo::error_code ec, [[maybe_unused]] auto conn) { 90 | EXPECT_TRUE(ec); 91 | }); 92 | 93 | io.run(); 94 | EXPECT_NE(conn_info.i_, conn_info.base_.begin()); 95 | EXPECT_NE(conn_info.i_, conn_info.base_.end()); 96 | } 97 | 98 | TEST(request, should_return_error_and_bad_connect_for_invalid_connection_info_and_expired_tries) { 99 | using namespace ozo::literals; 100 | using namespace hana::literals; 101 | 102 | ozo::io_context io; 103 | connection_info_sequence<> conn_info({"invalid connection info", "invalid connection info"}); 104 | 105 | std::vector res; 106 | ozo::request[ozo::failover::retry()*2](conn_info[io], "SELECT 1"_SQL + " + 1"_SQL, ozo::into(res), 107 | [&](ozo::error_code ec, [[maybe_unused]] auto conn) { 108 | EXPECT_TRUE(ec); 109 | }); 110 | 111 | io.run(); 112 | EXPECT_EQ(conn_info.i_, conn_info.base_.end()); 113 | } 114 | 115 | } // namespace 116 | --------------------------------------------------------------------------------