From 79e649caf8c73472f083a4b5617080ae51893cc7 Mon Sep 17 00:00:00 2001 From: "Sergei L. Khandrikov" Date: Sun, 16 Sep 2018 22:24:26 +0300 Subject: [PATCH] Update documentation --- Doxyfile | 6 +- customdoxygen.css | 62 +++++------ docs/howto.md | 10 +- examples/connection_pool.cpp | 24 ++-- include/ozo/concept.h | 93 ++++++++++++++-- include/ozo/connection.h | 26 +---- include/ozo/connection_info.h | 27 ++++- include/ozo/connection_pool.h | 57 ++++++---- include/ozo/connector.h | 30 ++--- include/ozo/error.h | 29 ++++- include/ozo/execute.h | 2 +- include/ozo/ozo.h | 18 +++ include/ozo/request.h | 4 +- include/ozo/result.h | 201 ++++++++++++++++++++++++++++++++-- include/ozo/shortcuts.h | 93 +++++++++++++++- include/ozo/type_traits.h | 72 ++++++++---- 16 files changed, 585 insertions(+), 169 deletions(-) diff --git a/Doxyfile b/Doxyfile index 671074b7a..264645cd2 100644 --- a/Doxyfile +++ b/Doxyfile @@ -157,7 +157,7 @@ STRIP_FROM_PATH = # specify the list of include paths that are normally passed to the compiler # using the -I flag. -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = include # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't @@ -777,7 +777,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = include docs/howto.md README.md +INPUT = include docs/howto.md README.md examples # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -856,7 +856,7 @@ EXCLUDE_SYMBOLS = # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = +EXAMPLE_PATH = examples # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and diff --git a/customdoxygen.css b/customdoxygen.css index 999bc59f5..48f003bda 100644 --- a/customdoxygen.css +++ b/customdoxygen.css @@ -1,7 +1,6 @@ /* The standard CSS for doxygen 1.8.4 */ body, table, div, p, dl { - // font: 400 14px/22px Roboto,sans-serif; font: 13px "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; } @@ -13,7 +12,6 @@ h1.groupheader { } .title { - //font: 400 14px/28px Roboto,sans-serif; font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; font-size: 24px; font-weight: normal; @@ -168,12 +166,10 @@ a.elRef { } a.code, a.code:visited { - //color: #4665A2; color: rgb(51, 102, 204); } a.codeRef, a.codeRef:visited { - //color: #4665A2; color: rgb(51, 102, 204); } @@ -184,46 +180,33 @@ dl.el { } pre.fragment { - //border: 1px solid #C4CFE5; border: solid 1px rgb(221, 221, 221); border-radius: 3px; - //background-color: #FBFCFD; background-color: rgb(248, 248, 248); - //padding: 4px 6px; padding: 6px 10px; - //margin: 4px 8px 4px 2px; margin: 15px 0px; overflow: auto; word-wrap: break-word; font-size: 9pt; line-height: 125%; - //font-family: monospace, fixed; - font-family: Consolas, "Liberation Mono", Courier, monospace; + font-family: "Courier New", Courier, Consolas, "Liberation Mono", monospace; font-size: 105%; } div.fragment { - //padding: 0px; - //padding: 4px 6px; - //margin: 0px; - //background-color: #FBFCFD; - //border: 1px solid #C4CFE5; - padding: 6px 10px; margin: 15px 0px 15px 30px; border: solid 1px rgb(221, 221, 221); border-radius: 3px; width: fit-content; - background-color: rgb(248, 248, 248); } div.line { - //font-family: monospace, fixed; - font-family: Consolas, "Liberation Mono", Courier, monospace; - font-size: 13px; + font-family: Courier, "Courier New", Consolas, "Liberation Mono", monospace; + font-size: 100%; min-height: 13px; - line-height: 1.0; + line-height: 1.3; text-wrap: unrestricted; white-space: -moz-pre-wrap; /* Moz */ white-space: -pre-wrap; /* Opera 4-6 */ @@ -371,31 +354,37 @@ img.footer { /* @group Code Colorization */ span.keyword { - color: #008000 + color: #10006b; + font-weight: bold; } span.keywordtype { - color: #604020 + color: #613700; + font-weight: bold; } span.keywordflow { - color: #e08000 + color: #002f5a; + font-weight: bold; } span.comment { - color: #800000 + color: #700067; + font-style: italic; } span.preprocessor { - color: #806020 + color: #1d7a00; } span.stringliteral { - color: #002080 + color: #6b6b6b; + font-style: italic; } span.charliteral { - color: #008080 + color: #6b6b6b; + font-style: italic; } span.vhdldigit { @@ -460,7 +449,6 @@ th.dirtab { hr { height: 0px; border: none; - //border-top: 1px solid #4A6AAA; border-top: 1px solid #444; } @@ -1077,13 +1065,15 @@ dl.section dl.note { - //margin-left:-7px; - margin-left: 0px; - padding: 6px 0px 3px 8px; - //padding-left: 8px; - border-left: 6px solid; - border-color: #D0C000; - background-color: #fff799 + margin-left: 0px; + padding: 6px 6px 6px 8px; + border-left: 6px solid; + border-bottom: 1px solid; + border-top: 1px solid; + border-right: 1px solid; + border-color: rgb(173, 165, 77); + background-color: rgb(255, 250, 182); + width: fit-content; } dl.warning, dl.attention diff --git a/docs/howto.md b/docs/howto.md index 7f96c5e96..8f8b9462b 100644 --- a/docs/howto.md +++ b/docs/howto.md @@ -31,7 +31,7 @@ int main() { ozo::rows_of> rows; // Connection info with host and port to coonect to - ozo::connection_info<> conn_info("host=... port=..."); + auto conn_info = ozo::make_connection_info("host=... port=..."); // For _SQL literal using namespace ozo::literals; @@ -86,7 +86,7 @@ The first argument of the tuple can not be a _NULL_, so here we do not to bother There is no mistake to expect nullable type for non-nullable result, but the opposite leads to run-time error in case of _NULL_ value result from database. ```cpp -ozo::connection_info<> conn_info("host=... port=..."); +auto conn_info = ozo::make_connection_info("host=... port=..."); ``` Now we need to create a connection information for database to connect to. This is our connection provider which can create a connection for us as it will be needed (see more information about connection provider and how to get connection). @@ -100,7 +100,7 @@ Here our query for database. There is a text with `_SQL` literal and single para Here is `request()` asynchronous function call. ```cpp -ozo::request(ozo::make_provider(io, conn_info), query, ozo::into(res), +ozo::request(ozo::make_connector(io, conn_info), query, ozo::into(res), [&](ozo::error_code ec, auto conn) { //... }); @@ -112,7 +112,7 @@ ozo::request(ozo::make_provider(io, conn_info), query, ozo::into(res), `ozo::into(res)` - the output perameter. In this case out parameter is back inserter iterator to the result vector. Note, what the life time of the output parameter managed by the user. In this case it correctly placed on stack since its lifetime overlaps `io.run()` call. But in more sophisticated code with callbacks it needs to be stored e.g. in shared pointer or something like this. The query output parameter can be iterator on container with appropriated data items, or it can be `ozo::result` which provides access to raw binary data. The second variant is not recommended since user must implement binary protocol parsing by self, but if it needed it can be used. -`[&](ozo::error_code ec, auto conn)` - completion token parameter, in this case is callback lambda. In other cases it can be [boost::asio::use_future](https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio/reference/use_future.html), [boost::asio::yield_context](https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio/reference/yield_context.html) or any other [boost::asio::async_result](https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio/reference/async_result.html) comatible [Completion Token](https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio/reference/async_completion.html). The arguments of the call back are error code `ec` (which is namely `boost::system::error_code` for now) and the connection `conn` with which the query was made. Even if you got an error it is possible what there is an additional error context in the `conn`. Since there is no rooms to place context depended information about connection error into error code the context depended information provided via `error_message()` and `get_error_context()` functions. The first one returns error message from `libpq`, the second - additional context from `OZO`. +`[&](ozo::error_code ec, auto conn)` - completion token parameter, in this case is callback lambda. In other cases it can be [boost::asio::use_future](https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio/reference/use_future.html), [boost::asio::yield_context](https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio/reference/yield_context.html) or any other [boost::asio::async_result](https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio/reference/async_result.html) compatible [Completion Token](https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio/reference/async_completion.html). The arguments of the call back are error code `ec` (which is namely `boost::system::error_code` for now) and the connection `conn` with which the query was made. Even if you got an error it is possible what there is an additional error context in the `conn`. Since there is no rooms to place context depended information about connection error into error code the context depended information provided via `error_message()` and `get_error_context()` functions. The first one returns error message from `libpq`, the second - additional context from `OZO`. `for(auto& row: res)` - so if there is no error we can handle result from the output container. @@ -145,7 +145,7 @@ int main() { const auto query = "SELECT id, name FROM users_info WHERE amount>="_SQL + std::int64_t(25); - ozo::request(ozo::make_provider(io, conn_info), query, ozo::into(res), + ozo::request(ozo::make_connector(io, conn_info), query, ozo::into(res), [&](ozo::error_code ec, auto conn) { //... }); diff --git a/examples/connection_pool.cpp b/examples/connection_pool.cpp index 833da41dd..ffc0c541e 100644 --- a/examples/connection_pool.cpp +++ b/examples/connection_pool.cpp @@ -18,35 +18,41 @@ int main(int argc, char **argv) { return 1; } - // Ozo perform all IO using Boost.Asio, so first thing we need to do is setup asio::io_context + // Ozo perform all IO using Boost.Asio, so the first thing we need to do is to setup asio::io_context asio::io_context io; - // To make a request we need to make a ConnectionSource. It knows how to connect to database using - // connection string. See https://www.postgresql.org/docs/9.4/static/libpq-connect.html#LIBPQ-CONNSTRING - // how to make a connection string. - ozo::connection_info<> connection_info(argv[1]); + //! [Creating Connection Pool] + + // To make a connection to a database we need to make a ConnectionSource. + auto connection_info = ozo::make_connection_info(argv[1]); - // Conneciton pool allows to store open connection and reuse them to avoid connect operation for each - // request. It supports asynchronous request for connections using callbacks queue with optional capacity - // limit and wait timeout. Also connection closes when there is no usage for more than idle timeout. ozo::connection_pool_config connection_pool_config; + // Maximum limit for number of stored connections connection_pool_config.capacity = 1; // Maximum limit for number of waiting requests for connection connection_pool_config.queue_capacity = 10; // Maximum time duration to store unused open connection connection_pool_config.idle_timeout = std::chrono::seconds(60); + + // Creating connection pool from connection_info as the underlying ConnectionSource auto connection_pool = ozo::make_connection_pool(connection_info, connection_pool_config); + //! [Creating Connection Pool] const auto coroutine = [&] (asio::yield_context yield) { + + //! [Creating Connection Provider] + // The next step is bind asio::io_context with ConnectionSource to setup executor for all // callbacks. Default connection is a ConnectionProvider. If there is some problem with network - // or database we don't want to wait indefinetely, so we establish connect timeout. If there is + // or database we don't want to wait indefinitely, so we establish connect timeout. If there is // no available connection in the connection pool we also want to wait within finite time duration. ozo::connection_pool_timeouts timeouts; timeouts.connect = std::chrono::seconds(1); timeouts.queue = std::chrono::seconds(1); + const auto connector = ozo::make_connector(connection_pool, io, timeouts); + //! [Creating Connection Provider] // Request result is always set of rows. Client should take care of output object lifetime. ozo::rows_of result; diff --git a/include/ozo/concept.h b/include/ozo/concept.h index 510d6cd16..92754d4a0 100644 --- a/include/ozo/concept.h +++ b/include/ozo/concept.h @@ -283,19 +283,19 @@ struct is_raw_data_writable : std::bool_constant< /** - * @brief RawDataWritable concept + * @brief `RawDataWritable` concept * - * Indicates if T can be written as a sequence of bytes without endian conversion. - * `RawDataWritable` is true if for object `v` of type `T` applicable one of this code: + * Indicates if `T` can be written as a sequence of bytes without endian conversion. + * `RawDataWritable` is `true` if for object `v` of type `T` applicable one of this code: * @code auto raw = v.data(); // has_data + static_assert(sizeof(*raw) == 1); // 1> auto n = v.size(); // has_size * @endcode * or * @code auto raw = data(v); // has_friend_data + static_assert(sizeof(*raw) == 1); // 1> auto n = size(v); // has_friend_size * @endcode * @tparam T - type to examine @@ -323,21 +323,44 @@ constexpr auto Emplaceable = is_emplaceable>::value; /** * @brief Completion token concept * - * CompletionToken is an entity which determines how to continue with asynchronous operation result when + * `CompletionToken` is an entity which determines how to continue with asynchronous operation result when * the operation is complete. According to * `boost::asio::async_completion` it defines the return value of an asynchronous function. * - * CompletionToken - is any of these next entities: - * * callback with `void(ozo::error_code, Connection)` signature, which can handle an `ozo::error_code` - * object as first argument, and #Connection implementation object as second argument. It is better to - * have second argument as a template parameter, but if it needed - it can be specialized with - * `ozo::connection_type`. Asynchronous function in this case will return `void`. + * Assume we have an asynchronous IO function: + * @code +template +auto async_io(ConnectionProvider&&, Param1 p1, ..., CompletionToken&&); + * @endcode + * + * Then the result type of the function depends on `CompletionToken`, and `CompletionToken` - is any of these next entities: + * * #Handler concept implementation. Asynchronous function in this case will return `void`. + * In this case the equivalent function signature will be: + * @code +template +void async_io(ConnectionProvider, Param1 p1, ..., Handler); + * @endcode + * * * * `boost::asio::use_future` - to get a future on the asynchronous operation result. * Asynchronous function in this case will return `std::future`. + * In this case the equivalent function signature will be: + * @code +template +std::future> async_io( + ConnectionProvider&&, Param1 p1, ..., boost::asio::use_future_t); + * @endcode + * * * * `boost::asio::yield_context` - to use async operation with Boost.Coroutine. * Asynchronous function in this case will return #Connection. + * In this case the equivalent function signature will be: + * @code +template +ozo::connection_type async_io( + ConnectionProvider&&, Param1 p1, ..., boost::asio::yield_context); + * @endcode + * * * any other type supported with * `boost::asio::async_completion` mechanism. * Asynchronous function in this case will return a type is depends on @@ -349,6 +372,54 @@ constexpr auto Emplaceable = is_emplaceable>::value; template constexpr auto CompletionToken = std::false_type; #endif + +/** + * @brief Handler concept + * + * `Handler` is a function or a functor which is used as a callback for handling result of asynchronous IO operations in the library. + * + * In case of function it has to have this signature: + *@code +template +void Handler(ozo::error_code ec, Connection connection) { + //... +} + *@endcode + * + * In case of functor it has to have such `operator()`: + *@code +struct Handler { + template + void operator() (ozo::error_code ec, Connection connection) { + //... + } +}; + *@endcode + * + * In case of lambda: + *@code +auto Handler = [&] (ozo::error_code ec, auto connection) { + //... +}; + *@endcode + * + * `Handler` has to handle an `ozo::error_code` object as first argument, and #Connection implementation + * object as a second one. It is better to define second argument as a template parameter because the + * implementation depends on a numerous of compile-time options but if it is really needed - real type + * can be obtained with `ozo::connection_type`. + * + * `Handler` has to be invoked according to this rules: + * * **Operation succeeded** --- `ozo::error_code` is empty, #Connection is in good state and can be used for next IO. + * * **Operation failed** --- `ozo::error_code` contains error, #Connection can be in these states: + * * #Connection in null-state --- `ozo::is_null()` returns `true`, object is useless; + * * #Connection in bad state --- `ozo::is_null()` returns `false`, object may provide additional error context via + * `ozo::error_message()` and `ozo::get_error_context()` functions. + * @hideinitializer + */ +#ifdef OZO_DOCUMENTATION +template +constexpr auto Handler = std::false_type; +#endif ///@} } // namespace ozo diff --git a/include/ozo/connection.h b/include/ozo/connection.h index 2cdc50797..d2d01a891 100644 --- a/include/ozo/connection.h +++ b/include/ozo/connection.h @@ -342,7 +342,7 @@ struct is_connection -#ifdef OZO_DOCUMENTATION -using connection_type = of connection; -#else using connection_type = typename get_connection_type::type; -#endif template > struct async_get_connection_impl { @@ -700,11 +697,6 @@ struct is_connection_source conn); - * @endcode - * * `ConnectionSource` must establish #Connection by means of `io_context` specified as first * argument. In case of connection has been established successful must dispatch * Handler with empty `error_code` as the first argument and established #Connection as the @@ -783,17 +775,11 @@ struct is_connection_provider conn); - * @endcode - * * See `ozo::connector` class - the default implementation and `ozo::get_connection()` description for more details. * @tparam T - type to examine. * @hideinitializer diff --git a/include/ozo/connection_info.h b/include/ozo/connection_info.h index fc96d30f6..5673b17d4 100644 --- a/include/ozo/connection_info.h +++ b/include/ozo/connection_info.h @@ -14,7 +14,7 @@ namespace ozo { * @ingroup group-connection-types * * This type is a basic #ConnectionSource implementation. This source allows to establish connection - * via connection string specified. + * via [connection string](https://www.postgresql.org/docs/9.4/static/libpq-connect.html#LIBPQ-CONNSTRING) specified. * @tparam OidMap --- oids map type which defines user types are used within this connection. * @tparam Statistics --- statistics type which defines statistics is collected for this connection. */ @@ -27,12 +27,35 @@ class connection_info { public: using connection = impl::connection_impl; - using connection_type = std::shared_ptr; + /** + * @brief Type of connection depends on built-in implementation + * + * Type is used to model #ConnectionSource + */ + using connection_type = std::shared_ptr; + + /** + * @brief Construct a new connection information object + * + * @param conn_str --- connection string which is being used to create connection to a database. + * For details of how to make string see [official libpq documentation](https://www.postgresql.org/docs/9.4/static/libpq-connect.html#LIBPQ-CONNSTRING) + * @param statistics --- statistics are being used for connections. + */ connection_info(std::string conn_str, Statistics statistics = Statistics{}) : conn_str(std::move(conn_str)), statistics(std::move(statistics)) { } + /** + * @brief Provides connection is binded to the given `io_context` + * + * In case of success --- the handler will be invoked as operation succeeded. + * In case of connection fail --- the handler will be invoked as operation failed. + * + * @param io --- `io_context` for the connection IO. + * @param handler --- #Handler. + * @param timeouts --- connection time-out. + */ template void operator ()(io_context& io, Handler&& handler, time_traits::duration timeout = time_traits::duration::max()) const { diff --git a/include/ozo/connection_pool.h b/include/ozo/connection_pool.h index 25e2a3dab..c6d03a268 100644 --- a/include/ozo/connection_pool.h +++ b/include/ozo/connection_pool.h @@ -10,7 +10,7 @@ namespace ozo { * @brief Connection pool configuration * @ingroup group-connection-types * - * Configuration of the ozo::connection_pool, e.g. how many connection are in the pool, + * Configuration of the `ozo::connection_pool`, e.g. how many connection are in the pool, * how many queries can be in wait queue if all connections are used by another queries, * and how long to keep connection open. */ @@ -24,28 +24,44 @@ struct connection_pool_config { * @brief Timeouts for the ozo::get_connection() operation * @ingroup group-connection-types * - * Time restrictions to get connection from the ozo::connection_pool + * Time restrictions to get connection from the `ozo::connection_pool` */ struct connection_pool_timeouts { time_traits::duration connect = std::chrono::seconds(10); //!< maximum time interval to establish connection with DBMS - time_traits::duration queue = std::chrono::seconds(10); //!< maximum time interval to wait for available connection handle in conneciton pool + time_traits::duration queue = std::chrono::seconds(10); //!< maximum time interval to wait for available connection handle in `conneciton_pool` }; /** * @brief Connection pool implementation * @ingroup group-connection-types * - * This is a simple connection pool - * implementation which can store up to maximum allowed number of connection to the same database. - * If all connections are busy client request will be placed into the internal queue to wait for - * free connection. If where is no free connections but it does not hit the limit of connections - * the new connection will be created via underlying #ConnectionSource being specified in constructor. - * All operations are timed out. Connection idle life time is timed out as well. Pool is configurable - * via constructor and operator(). + * This is a simple implementation connection pool (wikipedia). + * Connection pool allows to store established connections and reuse it to avoid a connect operation for each request. + * It supports asynchronous request for connections using a queue with optional limits of capacity and wait time. + * Also connection in the pool may be closed when it is not used for some time - idle timeout. + * + * This is how `connection_pool` handles user request to get a #Connection object: + * + * * If there is a free connection --- it will be provided for a user immediately. + * * If all connections are busy but its number less than the limit --- a new connection will be created via the #ConnectionSource and provided for a user. + * * If all connections are busy and there is no room to create a new one --- the request will be placed into the internal queue to wait for the free connection. + * + * The request may be limited by time via optional `connection_pool_timeouts` argument of the `connection_pool::operator()`. * * `connection_pool` models #ConnectionSource concept itself using underlying #ConnectionSource. * * @tparam Source --- underlying #ConnectionSource which is being used to create connection to a database. + * + * ###Example + * + * See [examples/connection_pool.cpp](examples/connection_pool.cpp). + * + * Creating the connection_pool instance: +@snippet examples/connection_pool.cpp Creating Connection Pool + * + * Creating a ConnectionProvider from the connection_pool instance: +@snippet examples/connection_pool.cpp Creating Connection Provider + * */ template class connection_pool { @@ -68,22 +84,13 @@ class connection_pool { using connection_type = impl::pooled_connection_ptr; /** - * @brief Provides connection is binded to the given io_context - * - * In case of success the handler will be called with no `error_code` and - * established connection object. If no connection available and number of - * connections of the pool does not hit the limit - a new connection will be made. - * If pool is out of limits - the handler will be placed into the internal queue to - * wait for free connection. + * @brief Provides connection is binded to the given `io_context` * - * In case of time-out of connection establishing or being in queue - the handler - * will be called with error_code. - * - * In case of hitting the limit of the internal queue - the handler will be called - * with error_code. + * In case of success --- the handler will be invoked as operation succeeded. + * In case of connection fail, queue timeout or queue full --- the handler will be invoked as operation failed. * * @param io --- `io_context` for the connection IO. - * @param handler --- a callback with signature `void(error_code, connection_type)`. + * @param handler --- #Handler. * @param timeouts --- connection acquisition related time-outs */ template @@ -125,9 +132,11 @@ constexpr auto ConnectionPool = is_connection_pool>::value; * @ingroup group-connection-functions * @relates ozo::connection_pool * + * Helper function which creates connection pool based on #ConnectionSource and configuration parameters. + * * @param source --- #ConnectionSource object which is being used to create connection to a database. * @param config --- pool configuration. - * @return connection_pool constructed object + * @return `ozo::connection_pool` object */ template auto make_connection_pool(Source&& source, const connection_pool_config& config) { diff --git a/include/ozo/connector.h b/include/ozo/connector.h index ca6ffe668..511dc61f0 100644 --- a/include/ozo/connector.h +++ b/include/ozo/connector.h @@ -8,11 +8,16 @@ namespace ozo { /** * @brief Default implementation of ConnectionProvider * - * @ingroup Connection_Provider + * @ingroup group-connection-types * - * This is default implementation of the ConnectionProvider concept. It binds - * `io_context`, additional parameters and ConnectionSource implementation object to - * bea able provide a Connection via get_connection functionality. + * This is the default implementation of the #ConnectionProvider concept. It binds + * `io_context` and additional #ConnectionSource-specific parameters to the + * #ConnectionSource implementation object. + * + * Thus `connector` can create connection via #ConnectionSource object running its + * asynchronous connect operation on the `io_context` with additional parameters. + * As a result, `connector` provides a #Connection object bound to `io_context` via + * `ozo::get_connection()` function. * * @tparam Base --- ConnectionSource implementation * @tparam Args --- ConnectionSource additional parameters types. @@ -23,23 +28,22 @@ class connector { static_assert(ConnectionSource, "is not a ConnectionSource"); /** - * Source type according to ConnectionProvider requirements + * Source type according to #ConnectionProvider requirements */ using source_type = Base; /** - * Connection implementation type according to ConnectionProvider requirements. - * Specifies the Connection implementation type which can be obtained from this provider. + * #Connection implementation type according to #ConnectionProvider requirements. + * Specifies the #Connection implementation type which can be obtained from this provider. */ using connection_type = typename source_type::connection_type; /** - * @brief Construct a new connector object - * - * Constructor as it is. + * @brief Construct a new `connector` object * - * @param base --- ConnectionSource implementation object - * @param io --- `io_context` for connect IO - * @param args --- additional arguments to be passed to ConnectionSource to create Connection + * @param base --- #ConnectionSource implementation + * @param io --- `io_context` for asynchronous IO + * @param args --- additional #ConnectionSource-specific arguments to be passed + * to make a #Connection */ connector(Base& base, io_context& io, std::tuple&& args) : base(base), io(io), args(std::move(args)) { diff --git a/include/ozo/error.h b/include/ozo/error.h index 4277c173c..b02d14bca 100644 --- a/include/ozo/error.h +++ b/include/ozo/error.h @@ -6,12 +6,18 @@ /** * @defgroup error-codes Error codes * @brief Errors system and codes description + */ +namespace ozo { + +/** + * @ingroup error-codes + * @brief Error code representation of the library. * * In this library the * Boost.System's * error_code is used. The reason is what it is provided by the * - * Boost.Asio library. So it looks like this: + * Boost.Asio library. So the related definitions look like this: * * @code using error_code = boost::system::error_code; @@ -20,13 +26,28 @@ using error_category = boost::system::error_category; using error_condition = boost::system::error_condition; * @endcode * - * This may be configurable in the future and `std::error_code` support may be added. + * @note This may be configurable in the future and `std::error_code` support may be added. + * @sa system_error, error_category, error_condition */ -namespace ozo { - using error_code = boost::system::error_code; + +/** + * @ingroup error-codes + * @brief Error code contaning exception of the library. + * @sa error_code , error_category, error_condition + */ using system_error = boost::system::system_error; +/** + * @ingroup error-codes + * @brief Error category representation of the library. + * @sa error_code, system_error, error_condition + */ using error_category = boost::system::error_category; +/** + * @ingroup error-codes + * @brief Error condition representation of the library. + * @sa error_code, system_error, error_category + */ using error_condition = boost::system::error_condition; /** diff --git a/include/ozo/execute.h b/include/ozo/execute.h index 9dec12940..d4e4fe0ad 100644 --- a/include/ozo/execute.h +++ b/include/ozo/execute.h @@ -6,7 +6,7 @@ namespace ozo { /** * @brief Executes query but does not return a result - * @ingroup group-requests + * @ingroup group-requests-functions * * This function is same as `ozo::request()` function except it does not return any result. * It suitable to use with `UPDATE` `INSERT` statements, or invoking procedures without result. diff --git a/include/ozo/ozo.h b/include/ozo/ozo.h index eb9ae6841..77056103a 100644 --- a/include/ozo/ozo.h +++ b/include/ozo/ozo.h @@ -11,11 +11,29 @@ * @brief Core utility functions of the library. */ +/** + * @defgroup group-core-types Utility types + * @ingroup group-core + * @brief Core utility types of the library. + */ + /** * @defgroup group-requests Requests * @brief Database requests related concepts, types and functions. */ +/** + * @defgroup group-requests-functions Functions + * @ingroup group-requests + * @brief Database requests related functions. + */ + +/** + * @defgroup group-requests-types Types + * @ingroup group-requests + * @brief Database requests related types. + */ + /** * @defgroup group-introspection Introspection * @brief Serialization and deserialization related concepts, types and functions. diff --git a/include/ozo/request.h b/include/ozo/request.h index d5f9492a5..8db0558b1 100644 --- a/include/ozo/request.h +++ b/include/ozo/request.h @@ -6,7 +6,7 @@ namespace ozo { /** * @brief Send request to a database and provides query result (time-out version). - * @ingroup group-requests + * @ingroup group-requests-functions * * The function sends request to a database and provides result via out parameter. The function can * be called as any of Boost.Asio asynchronous function with #CompletionToken. @@ -64,7 +64,7 @@ inline auto request(P&& provider, Q&& query, const time_traits::duration& timeou /** * @brief Send request to a database and provides query result. - * @ingroup group-requests + * @ingroup group-requests-functions * * This is time-out free version of the `ozo::request`. * @param provider --- #ConnectionProvider to get connection from. diff --git a/include/ozo/result.h b/include/ozo/result.h index 42df8fd00..70597cd05 100644 --- a/include/ozo/result.h +++ b/include/ozo/result.h @@ -13,7 +13,19 @@ namespace ozo { +/** + * @brief Database request result value proxy + * @ingroup group-requests-types + * + * Provides access to values of a database request result. The library is designed + * to do not obligate a user to have a deal with raw and untyped results representation. + * But sometimes it is really needed to have an access to raw representation of the + * result. E.g. for reducing memory consumption or performance reason. For that case + * the library provides this mechanism. + */ +#ifndef OZO_DOCUMENTATION template +#endif class value { public: struct coordinates { @@ -24,26 +36,61 @@ class value { value(const coordinates& v) : v_(v) {} + /** + * @brief Value type OID + * + * @return oid_t --- type OID + */ oid_t oid() const noexcept { return impl::field_type(res(), column()); } + /** + * @brief Indicates text format representation for the value + * + * @return `true` --- value in text format + * @return `false` --- value in binary format + */ bool is_text() const noexcept { return impl::field_format(res(), column()) == impl::result_format::text; } + /** + * @brief Indicates binary format representation for the value + * + * @return `true` --- value in binary format + * @return `false` --- value in text format + */ bool is_binary() const noexcept { return impl::field_format(res(), column()) == impl::result_format::binary; } + /** + * @brief Value raw data buffer access + * + * @return const char* --- pointer to raw data buffer + * @sa ozo::value::size() + */ const char* data() const noexcept { return impl::get_value(res(), row(), column()); } + /** + * @brief Value raw data buffer size + * + * @return std::size_t --- size of raw data buffer in bytes. + * @sa ozo::value::data() + */ std::size_t size() const noexcept { return impl::get_length(res(), row(), column()); } + /** + * @brief Indicates if value is in `NULL` state. + * + * @return `true` --- if value is `NULL` + * @return `false` --- value is not `NULL` + */ bool is_null() const noexcept { return impl::get_isnull(res(), row(), column()); } @@ -61,12 +108,30 @@ inline const T* data(const value& v) { return reinterpret_cast(v.data()); } +/** + * @brief Database request result row proxy + * + * @ingroup group-requests-types + */ +#ifndef OZO_DOCUMENTATION template +#endif class row { public: using value = ozo::value; using coordinates = typename value::coordinates; +#ifdef OZO_DOCUMENTATION + /** + * @brief Constant iterator on value in the row. + * + * Random access iterator. Provides access to a `ozo::value` object. + * Since `ozo::basic_result` provides read-only access to a database + * request result, all the iterators provide a read-only access to + * a `ozo::value` object. + */ + using const_iterator = ; +#else class const_iterator : public boost::iterator_facade< const_iterator, value, @@ -95,39 +160,92 @@ class row { friend class boost::iterator_core_access; }; - +#endif + /** + * @brief Iterator on value in the row, alias on `iterator` class. + */ using iterator = const_iterator; row(const coordinates& first) : first_(first) {} + /** + * @brief Iterator on the first of row values sequence. + * + * @return const_iterator --- iterator on the first of row values sequence + */ const_iterator begin() const noexcept { return {first_}; } + /** + * @brief Iterator on end of row values sequence. + * + * @return const_iterator --- iterator on end of row values sequence + */ const_iterator end() const noexcept { return begin() + size(); } + /** + * @brief Find value by field name. + * + * @param name --- value field name to find. + * @return `const_iterator` --- iterator on found value. + * @return `end()` --- in no field with given name found. + */ const_iterator find(const char* name) const noexcept { int i = impl::field_number(*(first_.res), name); return i == -1 ? end() : begin() + i; } - value operator[] (int i) const noexcept { return *(begin() + i); } - + /** + * @brief Get value by field index. + * + * Valid index is in range `[0, size())`. No index-in-range check is performing. + * @note If given index is out of range the result is UB. + * @param index --- index of the value field. + * @return `ozo::value` --- proxy object on the value. + */ + value operator[] (int index) const noexcept { return *(begin() + index); } + + /** + * @brief Get count of values in row. + * + * @return `std::size_t` --- count of values in row. + */ std::size_t size() const noexcept { return impl::nfields(*(first_.res)); } + /** + * @brief Indicates if there are no values in row. + * + * @return `true` --- no values in the row, `size() == 0`, `begin() == end()`. + * @return `false` --- row is not empty, `size() > 0`, `begin() != end()`. + */ bool empty() const noexcept { return size() == 0; } - value at(int column_index) const { - if (column_index < 0 || static_cast(column_index) >= size()) { - throw std::out_of_range("ozo::row::at() column index " - + std::to_string(column_index) + " out of range"); + /** + * @brief Get value by field index with range check + * + * If index in not range `[0, size())` throws `std::out_of_range`. + * @param index --- index of value in the row. + * @return `ozo::value` --- proxy object to a value. + */ + value at(int index) const { + if (index < 0 || static_cast(index) >= size()) { + throw std::out_of_range("ozo::row::at() field index " + + std::to_string(index) + " out of range"); } - return (*this)[column_index]; + return (*this)[index]; } - value at(const char* column_name) const { - auto i = find(column_name); + /** + * @brief Get value by field name with range check + * + * If value with field name not found throws `std::out_of_range`. + * @param name --- index of value in the row. + * @return value --- proxy object to a value. + */ + value at(const char* name) const { + auto i = find(name); if (i == end()) { - throw std::out_of_range(std::string("ozo::row::at() no such column name ") - + column_name); + throw std::out_of_range(std::string("ozo::row::at() no such field name ") + + name); } return *i; } @@ -136,6 +254,14 @@ class row { coordinates first_; }; +/** + * @brief Database raw result representation + * @ingroup group-requests-types + * + * This class provides access to the raw representation of a database request result. It + * models range of rows. Each row can be accessed via index or iterator. + * @tparam T --- underlying native result handler type, in common case `ozo::native_result_handle`. + */ template class basic_result { public: @@ -144,6 +270,17 @@ class basic_result { using value = typename row::value; using coordinates = typename row::coordinates; +#ifdef OZO_DOCUMENTATION + /** + * @brief Constant iterator on row in the result. + * + * Random access iterator. Provides access to a `ozo::row` object. + * Since `ozo::basic_result` provides read-only access to a database + * request result, all the iterators provide a read-only access to + * a `ozo::row` object. + */ + using const_iterator = ; +#else class const_iterator : public boost::iterator_facade< const_iterator, row, @@ -172,22 +309,62 @@ class basic_result { friend class boost::iterator_core_access; }; +#endif + /** + * @brief Iterator on value in the row, alias on `iterator` class. + */ using iterator = const_iterator; basic_result() = default; basic_result(handle_type res) : res_(std::move(res)) {} + /** + * @brief Iterator on the first row of the result. + * + * @return const_iterator --- iterator on the first row + */ const_iterator begin() const noexcept { return {{std::addressof(*res_), 0, 0}}; } + /** + * @brief Iterator on end of row sequence. + * + * @return const_iterator --- iterator on end of row sequence + */ const_iterator end() const noexcept { return begin() + size(); } + /** + * @brief Get count of rows. + * + * @return `std::size_t` --- count of rows. + */ std::size_t size() const noexcept { return impl::ntuples(*res_);} + /** + * @brief Indicates if result is empty. + * + * @return `true` --- no ros in the result, `size() == 0`, `begin() == end()`. + * @return `false` --- row is not empty, `size() > 0`, `begin() != end()`. + */ bool empty() const noexcept { return size() == 0; } + /** + * @brief Get row by index. + * + * Valid index is in range `[0, size())`. No index-in-range check is performing. + * @note If given index is out of range the result is UB. + * @param index --- index of a row. + * @return `ozo::row` --- proxy object to a row. + */ row operator[] (int i) const noexcept { return *(begin() + i); } + /** + * @brief Get row by index with range check + * + * If index in not range `[0, size())` throws `std::out_of_range`. + * @param index --- index of a row. + * @return `ozo::row` --- proxy object to a row. + */ row at(int i) const { if (i < 0 || static_cast(i) >= size()) { throw std::out_of_range("ozo::result::at() index " + std::to_string(i) + " out of range"); diff --git a/include/ozo/shortcuts.h b/include/ozo/shortcuts.h index 841d41ea3..d37429f45 100644 --- a/include/ozo/shortcuts.h +++ b/include/ozo/shortcuts.h @@ -3,7 +3,7 @@ #include #include #include - +#include /** * Useful shortcuts for typed unnamed results containers */ @@ -12,15 +12,100 @@ namespace ozo { template using typed_row = std::tuple; +/** + * @ingroup group-requests-types + * @brief Shortcut for easy result container definition. + * + * This shortcut defines `std::vector` container for row tuples. + * @note It is very important to keep a sequence of types according to fields in query statement (see the example below). + * + * ### Example + * +@code{cpp} + +// Query statement +const auto query = + "SELECT id , name FROM users_info WHERE amount>="_SQL + std::int64_t(25); +// ---- ====== +// V V +ozo::rows_of rows; +// ------------ =========== + +ozo::request(ozo::make_connector(io, conn_info), query, ozo::into(rows), boost::asio::use_future); +@endcode + * @tparam Ts --- types of columns in result + */ template using rows_of = std::vector>; +/** + * @ingroup group-requests-types + * @brief Shortcut for easy result container definition. + * + * This shortcut defines `std::list` container for row tuples. + * @note It is very important to keep a sequence of types according to fields in query statement (see the example below). + * + * ### Example + * +@code{cpp} + +// Query statement +const auto query = + "SELECT id , name FROM users_info WHERE amount>="_SQL + std::int64_t(25); +// ---- ====== +// V V +ozo::lrows_of rows; +// ------------ =========== + +ozo::request(ozo::make_connector(io, conn_info), query, ozo::into(rows), boost::asio::use_future); +@endcode + * @tparam Ts --- types of columns in result + */ template using lrows_of = std::list>; +/** + * @ingroup group-requests-functions + * @brief Shortcut for create result container back inserter. + * + * This shortcut defines insert iterator for row container. + * + * ### Example + * +@code{cpp} + +// Query statement +const auto query = "SELECT id, name FROM users_info WHERE amount>="_SQL + std::int64_t(25); + +ozo::rows_of rows; + +ozo::request(ozo::make_connector(io, conn_info), query, ozo::into(rows), boost::asio::use_future); +@endcode + * @param v --- container for rows + */ +template +constexpr auto into(T& v) { return std::back_inserter(v);} + +/** + * @ingroup group-requests-functions + * @brief Shortcut for create reference wrapper for `ozo::basic_result`. + * + * This shortcut creates insert iterator for row container. + * + * ### Example + * +@code{cpp} + +// Query statement +const auto query = "SELECT id, name FROM users_info WHERE amount>="_SQL + std::int64_t(25); + +ozo::result res; + +ozo::request(ozo::make_connector(io, conn_info), query, ozo::into(res), boost::asio::use_future); +@endcode + * @param v --- `ozo::basic_result` object for rows. + */ template -constexpr auto into(T& v) -> decltype(std::back_inserter(v)) { - return std::back_inserter(v); -} +constexpr auto into(basic_result& v) { return std::ref(v);} } // namespace ozo diff --git a/include/ozo/type_traits.h b/include/ozo/type_traits.h index 59eab2479..0c30f449a 100644 --- a/include/ozo/type_traits.h +++ b/include/ozo/type_traits.h @@ -35,7 +35,7 @@ /** * @defgroup group-type_system Type system * @ingroup group-core - * @brief Database related type system of the library. + * @brief Database-related type system of the library. */ namespace ozo { @@ -72,9 +72,6 @@ constexpr null_oid_t null_oid; template struct is_nullable : std::false_type {}; -/** -* These next types are nullable -*/ template struct is_nullable> : std::true_type {}; @@ -102,7 +99,8 @@ struct is_nullable> : std::true_type {}; * `Nullble` type has to: * * have a null-state, * * be `bool` convertable - `false` indicates null state, - * * be dereferenceable via `operator *`. + * * be dereferenceable via `operator *`, + * * have `allocate_nullable()` specialization (see the function documentation for how to). * * These next types are `Nullable` out of the box: * * `boost::optional`, @@ -112,16 +110,38 @@ struct is_nullable> : std::true_type {}; * * `boost::shared_ptr`, * * `std::shared_ptr`, * * `boost::weak_ptr`, - * * `std::weak_ptr` + * * `std::weak_ptr`. * - * @note Raw pointers are not supported as `Nullable` by default. But if it is really needed you can add them as described below + * @note Raw pointers are not supported as `Nullable` by default, because this library uses RAII model for objects and + * no special deallocation functions are used. But if it is really needed and you want to deallocate the allocated + * objects manually you can add them as described below. * - * If you want to extend this list - you have to specialize `ozo::is_nullable` struct like this: + * ### Example + * + * If you want to add nullable type to the library --- you have to specialize `ozo::is_nullable` struct and specify allocation function like this: * * @code - template - struct is_nullable> : std::true_type {}; +//Add raw pointer to nullables + +namespace ozo { + +// Mark raw pointer as nullable +template +struct is_nullable : std::true_type {}; + +// Specify allocation function +template +struct allocate_nullable_impl { + template + static void apply(T*& out, const Alloc&) { + out = new T(); + } +}; + +} + * @endcode + * * @sa ozo::unwrap_nullable(), is_null(), allocate_nullable(), init_nullable(), reset_nullable() * @ingroup group-core-concepts * @hideinitializer @@ -180,7 +200,8 @@ inline auto is_null(const std::weak_ptr& v) noexcept { * If argument is not #Nullable it always returns `false` * * @param value --- object to examine - * @return `true` if object is in null-state, overwise - `false`. + * @return `true` --- object is in null-state, + * @return `false` --- overwise. * @ingroup group-core-functions */ template @@ -281,7 +302,7 @@ struct type_traits; /** * Helpers to make size trait constant * bytes - makes fixed size trait -* dynamic_size - makes dynamis size trait +* dynamic_size - makes dynamic size trait */ template using bytes = std::integral_constant; @@ -361,6 +382,9 @@ constexpr auto size_of(const T& v) noexcept -> typename std::enable_if< return std::size(v) ? std::size(v) * size_of(*std::begin(v)) : 0; } +/** + * @brief Namespace for PostgreSQL specific types + */ namespace pg { BOOST_STRONG_TYPEDEF(std::string, name) @@ -419,6 +443,14 @@ BOOST_STRONG_TYPEDEF(std::vector, bytea) * * @note This macro can be called in the global namespace only * + * @param Type --- C++ type to be mapped to database type + * @param Name --- string with name of database type + * @param Oid --- oid for built-in type and `ozo::null_oid_t` for custom type + * @param ArrayOid --- oid for an array of built-in type and `ozo::null_oid_t` for custom type + * @param Size --- `bytes` for fixed-size type (like integer, bigint and so on), + * there N - size of the type in database, `dynamic_type` for dynamic size types (like `text` + * `bytea` and so on) + * * ### Example * * E.g. a definition of `uuid` type looks like this: @@ -440,14 +472,6 @@ BOOST_FUSION_DEFINE_STRUCT((smtp), message, OZO_PG_DEFINE_TYPE_AND_ARRAY(smtp::message, "code.message", null_oid, null_oid, dynamic_size) @endcode - * - * @param Type --- C++ type to be mapped to database type - * @param Name --- string with name of database type - * @param Oid --- oid for built-in type and `ozo::null_oid_t` for custom type - * @param ArrayOid --- oid for an array of built-in type and `ozo::null_oid_t` for custom type - * @param Size --- `bytes` for fixed-size type (like integer, bigint and so on), - * there N - size of the type in database, `dynamic_type` for dynamic size types (like `text` - * `bytea` and so on) */ #ifdef OZO_DOCUMENTATION #define OZO_PG_DEFINE_TYPE_AND_ARRAY(Type, Name, Oid, ArrayOid, Size) @@ -612,7 +636,7 @@ inline void set_type_oid(oid_map_t& map, oid_t oid) noexcept { * Function returns oid for type from #OidMap. * @ingroup group-type_system * @tparam T --- type to get OID for. -* ¶m map --- #OidMap to get OID from. +* @param map --- #OidMap to get OID from. */ template inline auto type_oid(const oid_map_t& map) noexcept @@ -662,11 +686,13 @@ inline bool accepts_oid(const oid_map_t& map, const T& , oid_t oid) no * @ingroup group-type_system * * ### Example + * * @code static_assert(empty(ozo::empty_oid_map{})); * @endcode - * @param map --- OidMap to check - * @return `true` if map contains no items, `false` - if contains. + * @param map --- #OidMap to check + * @return `true` --- if map contains no items + * @return `false` --- if contains items. */ template inline constexpr bool empty(const oid_map_t& map) noexcept {