OZO 「お象」
Boost.Asio and libpq based asynchronous PostgreSQL unofficial header-only C++17 client library.
role_based.h
1 #pragma once
2 
3 #include <ozo/failover/strategy.h>
4 #include <ozo/failover/retry.h>
5 #include <ozo/core/options.h>
6 #include <ozo/ext/std/optional.h>
7 #include <ozo/ext/std/nullopt_t.h>
8 
9 #include <boost/hana/tuple.hpp>
10 #include <boost/hana/empty.hpp>
11 #include <boost/hana/size.hpp>
12 #include <boost/hana/minus.hpp>
13 #include <boost/hana/not_equal.hpp>
14 #include <boost/hana/greater.hpp>
15 
50 namespace ozo::failover {
51 
59  class on_fallback_tag;
60  class close_connection_tag;
61  class roles_tag;
62 
63  constexpr static option<on_fallback_tag> on_fallback{};
65  constexpr static option<roles_tag> roles{};
66 };
67 
68 template <typename Tag>
69 using role = ozo::option<Tag>;
70 
71 template <typename Role>
72 struct can_recover_impl {
73  static constexpr std::false_type apply(const Role&, const error_code&) {
74  static_assert(std::is_void_v<Role>, "no can_recover_impl specified for a given role");
75  return {};
76  }
77 };
78 
94 
95 template <>
96 struct can_recover_impl<std::decay_t<decltype(master)>> {
97  static constexpr auto value = hana::make_tuple(
102  );
103 
104  static constexpr auto apply(const std::decay_t<decltype(master)>&, const error_code& ec) {
105  return errc::match_code(value, ec);
106  }
107 };
108 
123 
124 template <>
125 struct can_recover_impl<std::decay_t<decltype(replica)>> {
126  static constexpr auto value = hana::make_tuple(
130  );
131 
132  static constexpr auto apply(const std::decay_t<decltype(replica)>&, const error_code& ec) {
133  return errc::match_code(value, ec);
134  }
135 };
136 
191 template <typename Role>
192 constexpr auto can_recover(const Role& role, const error_code& ec) {
193  return can_recover_impl<Role>::apply(role, ec);
194 }
195 
196 namespace detail {
197 
198 template <typename Source, typename Role, typename = hana::when<true>>
199 struct connection_source_supports_role : std::false_type {};
200 
201 template <typename Source, typename Role>
202 struct connection_source_supports_role <
203  Source, Role,
204  hana::when_valid<decltype(std::declval<Source>().rebind_role(std::declval<const Role&>()))>
205 > : std::true_type {};
206 
207 } // namespace detail
208 
221 template <typename Source>
223  Source source_;
224  io_context& io_;
225 public:
226 
227  static_assert(ozo::ConnectionSource<Source>, "Source should model a ConnectionSource concept");
232  using source_type = std::decay_t<Source>;
233 
240  using connection_type = typename connection_source_traits<source_type>::connection_type;
241 
248  template <typename OtherRole>
249  static constexpr auto is_supported(const OtherRole&) {
250  return typename detail::connection_source_supports_role<source_type, OtherRole>::type{};
251  }
252 
259  template <typename T>
260  role_based_connection_provider(T&& source, io_context& io)
261  : source_(std::forward<T>(source)), io_(io) {
262  }
263 
270  template <typename OtherRole>
271  constexpr decltype(auto) rebind_role(OtherRole r) const & {
272  static_assert(is_supported(r), "role is not supported by a connection provider");
273  using rebind_type = role_based_connection_provider<decltype(source_.rebind_role(r))>;
274  return rebind_type{ozo::unwrap(source_).rebind_role(r), io_};
275  }
276 
283  template <typename OtherRole>
284  constexpr decltype(auto) rebind_role(OtherRole r) & {
285  static_assert(is_supported(r), "role is not supported by a connection provider");
286  using rebind_type = role_based_connection_provider<decltype(source_.rebind_role(r))>;
287  return rebind_type{ozo::unwrap(source_).rebind_role(r), io_};
288  }
289 
296  template <typename OtherRole>
297  constexpr decltype(auto) rebind_role(OtherRole r) && {
298  static_assert(is_supported(r), "role is not supported by a connection provider");
299  using rebind_type = role_based_connection_provider<decltype(std::move(source_).rebind_role(r))>;
300  return rebind_type{ozo::unwrap(std::forward<Source>(source_)).rebind_role(r), io_};
301  }
302 
303  template <typename TimeConstraint, typename Handler>
304  void async_get_connection(TimeConstraint t, Handler&& h) const & {
305  static_assert(ozo::TimeConstraint<TimeConstraint>, "should model TimeConstraint concept");
306  ozo::unwrap(source_)(io_, std::move(t), std::forward<Handler>(h));
307  }
308 
309  template <typename TimeConstraint, typename Handler>
310  void async_get_connection(TimeConstraint t, Handler&& h) & {
311  static_assert(ozo::TimeConstraint<TimeConstraint>, "should model TimeConstraint concept");
312  ozo::unwrap(source_)(io_, std::move(t), std::forward<Handler>(h));
313  }
314 
315  template <typename TimeConstraint, typename Handler>
316  void async_get_connection(TimeConstraint t, Handler&& h) && {
317  static_assert(ozo::TimeConstraint<TimeConstraint>, "should model TimeConstraint concept");
318  ozo::unwrap(std::forward<Source>(source_))(io_, std::move(t), std::forward<Handler>(h));
319  }
320 };
321 
322 template <typename T>
323 role_based_connection_provider(T&& source, io_context& io) -> role_based_connection_provider<T>;
324 
334 template <typename SourcesMap, typename Role>
336  static_assert(decltype(hana::is_a<hana::map_tag>(std::declval<SourcesMap>()))::value, "SourcesMap should be a boost::hana::map");
337  static_assert(decltype(hana::size(std::declval<const SourcesMap&>())!=hana::size_c<0>)::value, "sources should not be empty");
338 
339  SourcesMap sources_;
340  Role role_;
341 
342  using source_type = std::decay_t<decltype(sources_[role_])>;
347  using connection_type = typename connection_source_traits<source_type>::connection_type;
348 
349  template <typename OtherRole>
350  static constexpr bool is_supported() {
351  return decltype(hana::contains(std::declval<SourcesMap>(), std::declval<OtherRole>()))::value;
352  }
353 
354  role_based_connection_source(SourcesMap sources, Role role)
355  : sources_(std::move(sources)), role_(std::move(role)) {
356  static_assert(is_supported<Role>(), "no default role found in sources");
357  }
358 
359  template <typename OtherRole>
360  constexpr auto rebind_role(OtherRole r) const & ->
361  Require<is_supported<OtherRole>(), role_based_connection_source<SourcesMap, OtherRole>> {
362  return {sources_, r};
363  }
364 
365  template <typename OtherRole>
366  constexpr auto rebind_role(OtherRole r) && ->
367  Require<is_supported<OtherRole>(), role_based_connection_source<SourcesMap, OtherRole>> {
368  return {std::move(sources_), r};
369  }
370 
371  template <typename TimeConstraint, typename Handler>
372  constexpr void operator() (io_context& io, TimeConstraint t, Handler&& h) const & {
373  sources_[role_](io, std::move(t), std::forward<Handler>(h));
374  }
375 
376  template <typename TimeConstraint, typename Handler>
377  constexpr void operator() (io_context& io, TimeConstraint t, Handler&& h) & {
378  sources_[role_](io, std::move(t), std::forward<Handler>(h));
379  }
380 
381  template <typename TimeConstraint, typename Handler>
382  constexpr void operator() (io_context& io, TimeConstraint t, Handler&& h) && {
383  std::move(sources_[role_])(io, std::move(t), std::forward<Handler>(h));
384  }
385 
386  constexpr auto operator[] (io_context& io) const & {
387  return role_based_connection_provider(*this, io);
388  }
389 
390  constexpr auto operator[] (io_context& io) & {
391  return role_based_connection_provider(*this, io);
392  }
393 
394  constexpr auto operator[] (io_context& io) && {
395  return role_based_connection_provider(std::move(*this), io);
396  }
397 };
398 
418 template <typename Key, typename Value, typename ...Pairs>
419 constexpr decltype(auto) make_role_based_connection_source(hana::pair<Key, Value> p1, Pairs&& ...ps) {
420  auto default_role = hana::first(p1);
421  auto map = hana::make_map(std::move(p1), std::forward<Pairs>(ps)...);
422  return role_based_connection_source{std::move(map), std::move(default_role)};
423 }
424 
425 template <typename Options, typename Context, typename RoleIdx = decltype(hana::size_c<0>)>
426 class role_based_try {
427  Context ctx_;
428  Options options_;
429  static constexpr RoleIdx idx_{};
430  using opt = role_based_options;
431 
432 public:
439  constexpr role_based_try(Options options, Context ctx)
440  : ctx_(std::move(ctx)), options_(std::move(options)) {
441  static_assert(decltype(hana::is_a<hana::map_tag>(options))::value, "Options should be boost::hana::map");
442  }
443 
444  constexpr role_based_try(const role_based_try&) = delete;
445  constexpr role_based_try(role_based_try&&) = default;
446  constexpr role_based_try& operator = (const role_based_try&) = delete;
447  constexpr role_based_try& operator = (role_based_try&&) = default;
448 
454  constexpr const Options& options() const { return options_;}
455  constexpr Options& options() { return options_;}
456 
462  static constexpr auto role_index() { return idx_;}
463 
469  constexpr auto role() const { return roles_seq()[role_index()];}
470 
477  auto get_context() const {
478  return hana::concat(
479  hana::make_tuple(ozo::unwrap(ctx_).provider.rebind_role(role()), time_constraint()),
480  ozo::unwrap(ctx_).args
481  );
482  }
483 
489  auto time_constraint() const {
490  return detail::get_try_time_constraint(ozo::unwrap(ctx_).time_constraint, tries_left().value);
491  }
492 
498  constexpr decltype(auto) roles_seq() const {
499  return get_option(options(), opt::roles);
500  }
501 
507  constexpr auto tries_left() const {
508  return decltype(hana::size(roles_seq()) - role_index()){};
509  }
510 
521  template <typename Connection, typename Initiator>
522  void initiate_next_try([[maybe_unused]] ozo::error_code ec, Connection& conn, [[maybe_unused]] Initiator&& init) const {
523  auto guard = defer_close_connection(get_option(options(), opt::close_connection, true) ? std::addressof(conn) : nullptr);
524 
525  if constexpr (decltype(tries_left() > hana::size_c<1>)::value) {
526  using fallback_try = role_based_try<Options, Context, decltype(role_index() + hana::size_c<1>)>;
527  fallback_try fallback{std::move(options_), std::move(ctx_)};
528 
529  if (can_recover(fallback.role(), ec)) {
530  get_option(fallback.options(), opt::on_fallback, [](auto&&...){})(ec, conn, std::as_const(fallback));
531  init(std::move(fallback));
532  } else {
533  guard.release();
534  fallback.initiate_next_try(ec, conn, std::move(init));
535  }
536  }
537  }
538 };
539 
540 template <typename Options, typename Context, typename RoleIdx>
541 struct initiate_next_try_impl<role_based_try<Options, Context, RoleIdx>> {
542  template <typename Connection, typename Initiator>
543  static void apply(role_based_try<Options, Context, RoleIdx>& a_try,
544  const error_code& ec, Connection& conn, Initiator&& init) {
545  a_try.initiate_next_try(ec, conn, std::forward<Initiator>(init));
546  }
547 };
548 
556 template <typename Options = decltype(hana::make_map())>
557 class role_based_strategy : public options_factory_base<role_based_strategy<Options>, Options> {
558  using opt = role_based_options;
559 
560  friend class ozo::options_factory_base<role_based_strategy<Options>, Options>;
562 
563  template <typename OtherOptions>
564  constexpr static auto rebind_options(OtherOptions&& options) {
565  return role_based_strategy<std::decay_t<OtherOptions>>(std::forward<OtherOptions>(options));
566  }
567 
568 public:
574  constexpr role_based_strategy(Options options = Options{}) : base(std::move(options)) {
575  static_assert(decltype(hana::is_a<hana::map_tag>(options))::value, "Options should be boost::hana::map");
576  }
577 
578  template <typename Operation, typename Allocator, typename Source,
579  typename TimeConstraint, typename ...Args>
580  auto get_first_try(const Operation&, const Allocator&,
581  role_based_connection_provider<Source> provider, TimeConstraint t, Args&& ...args) const {
582 
583  static_assert(decltype(this->has(opt::roles))::value, "roles should be specified");
584  static_assert(!decltype(hana::is_empty(this->get(opt::roles)))::value, "roles should not be empty");
585 
586  return role_based_try {
587  this->options(),
588  basic_context{std::move(provider), ozo::deadline(t), std::forward<Args>(args)...}
589  };
590  }
591 };
592 
651 template <typename ...Roles>
652 constexpr auto role_based(Roles ...roles) {
653  if constexpr (sizeof...(roles) != 0) {
654  return role_based_strategy{hana::make_map(role_based_options::roles=hana::make_tuple(roles...))};
655  } else {
656  return role_based_strategy{};
657  }
658 }
659 
660 } // namespace ozo::failover
661 
662 namespace ozo {
663 
664 template <typename ...Ts, typename Op>
665 struct construct_initiator_impl<failover::role_based_strategy<Ts...>, Op>
666 : failover::construct_initiator_impl {};
667 
668 } // namespace ozo
ozo::unwrap
constexpr decltype(auto) unwrap(T &&v) noexcept(noexcept(unwrap_impl< std::decay_t< T >>::apply(std::forward< T >(v))))
Unwraps argument underlying value or forwards the argument.
Definition: unwrap.h:57
ozo::failover::make_role_based_connection_source
constexpr decltype(auto) make_role_based_connection_source(hana::pair< Key, Value > p1, Pairs &&...ps)
Definition: role_based.h:419
ozo::error_code
boost::system::error_code error_code
Error code representation of the library.
Definition: error.h:38
ozo::deadline
constexpr time_traits::time_point deadline(time_traits::time_point t) noexcept
Dealdine calculation.
Definition: deadline.h:16
ozo::failover::role_based_options::close_connection
constexpr static option< close_connection_tag > close_connection
Close connection policy on retry, possible values true(default), false.
Definition: role_based.h:64
ozo::failover::role_based_connection_provider
Definition: role_based.h:222
ozo::failover::role_based_connection_provider::is_supported
static constexpr auto is_supported(const OtherRole &)
Definition: role_based.h:249
ozo::failover::role_based_connection_provider::source_type
std::decay_t< Source > source_type
Definition: role_based.h:232
ozo::errc::connection_error
@ connection_error
connection-related error condition, incorporates ozo, libpq and Boost.Asio connection errors
Definition: error.h:399
ozo::failover::role_based_strategy
Role-based strategy.
Definition: role_based.h:557
ozo::failover::role_based_connection_provider::rebind_role
constexpr decltype(auto) rebind_role(OtherRole r) const &
Rebind the ConnectionProvider to an other role.
Definition: role_based.h:271
ozo::failover::role_based_options::on_fallback
constexpr static option< on_fallback_tag > on_fallback
Handler for fallback event with signature void(error_code, Connection, Fallback), may be useful for l...
Definition: role_based.h:63
ozo::failover::role_based_connection_provider::connection_type
typename connection_source_traits< source_type >::connection_type connection_type
Definition: role_based.h:240
ozo::defer_close_connection
auto defer_close_connection(Connection *conn)
Close connection to the database when leaving the scope.
Definition: connection.h:891
ozo::errc::database_readonly
@ database_readonly
database in read-only state - useful to detect attempt of modify data on replica host
Definition: error.h:400
ozo::failover::role_based_connection_source::connection_type
typename connection_source_traits< source_type >::connection_type connection_type
Definition: role_based.h:347
ozo::options_factory_base::get
constexpr decltype(auto) get(ozo::option< Key > op) const
Definition: options.h:243
ozo::failover::role_based_connection_provider::role_based_connection_provider
role_based_connection_provider(T &&source, io_context &io)
Definition: role_based.h:260
ozo::failover::can_recover
constexpr auto can_recover(const Role &role, const error_code &ec)
Determines if an error can be recovered by executing an operation on host with a given role.
Definition: role_based.h:192
ozo::failover::get_first_try
auto get_first_try(const Operation &op, const FailoverStrategy &strategy, const Allocator &alloc, Args &&...args)
Get the first try object for an operation.
Definition: strategy.h:213
ozo::Require
Type Require
Concept requirement emulation.
Definition: concept.h:54
ozo::failover::replica
constexpr role< class replica_tag > replica
Definition: role_based.h:122
ozo::value
Database request result value proxy.
Definition: result.h:25
ozo::failover::master
constexpr role< class master_tag > master
Definition: role_based.h:93
ozo::failover::role_based_strategy::role_based_strategy
constexpr role_based_strategy(Options options=Options{})
Construct a new retry strategy object.
Definition: role_based.h:574
ozo::failover::initiate_next_try
auto initiate_next_try(Try &a_try, const error_code &ec, Connection &conn, Initiator &&init)
Initiates the next try of an operation execution.
Definition: strategy.h:361
ozo::options_factory_base
Base class for options factories.
Definition: options.h:121
ozo::option< on_fallback_tag >
ozo::failover::role_based
constexpr auto role_based(Roles ...roles)
Definition: role_based.h:652
ozo::get_option
constexpr decltype(auto) get_option(Map &&map, ozo::option< Key > op)
Get the option object from Hana.Map.
Definition: options.h:52
Handler
Handler concept.
ozo::failover::role_based_connection_source
Definition: role_based.h:335
ozo::failover::role_based_options
Options for role-based failover.
Definition: role_based.h:58
Connection
Database connection concept.
ozo::errc::type_mismatch
@ type_mismatch
result type mismatch, indicates types mismatch between result of query and expected result
Definition: error.h:402
ozo::errc::protocol_error
@ protocol_error
specific protocol-related errors
Definition: error.h:403
ozo::options_factory_base::options
constexpr const Options & options() const &
Definition: options.h:250
ozo::failover::role_based_options::roles
constexpr static option< roles_tag > roles
Strategy roles sequence.
Definition: role_based.h:65
ozo::options_factory_base::has
constexpr auto has(ozo::option< Key > op) const
Definition: options.h:232