diff --git a/.gitignore b/.gitignore index 1b5c6dfb..a6fcfb12 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ out/ doc/html/ doc/qbk/reference.qbk .vscode/ +compile_commands.json +.cache/ diff --git a/doc/Doxyfile b/doc/Doxyfile index 9bafb357..de1b8077 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -16,7 +16,7 @@ INPUT_ENCODING = UTF-8 FILE_PATTERNS = RECURSIVE = NO EXCLUDE = $(LIB_DIR)/include/boost/mysql/detail/ \ - $(LIB_DIR)/include/boost/mysql/impl + $(LIB_DIR)/include/boost/mysql/impl/ EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = EXCLUDE_SYMBOLS = @@ -36,16 +36,16 @@ USE_MDFILE_AS_MAINPAGE = ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES -SEARCH_INCLUDES = YES +SEARCH_INCLUDES = NO PREDEFINED = BOOST_MYSQL_DOXYGEN \ - BOOST_ASIO_INITFN_RESULT_TYPE(t,a)=auto \ - BOOST_ASIO_COMPLETION_TOKEN_FOR(sig)=class \ - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(ct,sig)=auto \ - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(ex)= \ - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(ex)= \ - protected=private \ - BOOST_CXX14_CONSTEXPR=constexpr + "BOOST_ASIO_INITFN_RESULT_TYPE(t,a)=auto" \ + "BOOST_ASIO_COMPLETION_TOKEN_FOR(sig)=class" \ + "BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(ct,sig)=auto" \ + "BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(ex)=" \ + "BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(ex)=" \ + "protected=private" \ + "BOOST_CXX14_CONSTEXPR=constexpr" SKIP_FUNCTION_MACROS = NO @@ -189,7 +189,7 @@ HAVE_DOT = NO #--------------------------------------------------------------------------- GENERATE_XML = YES XML_OUTPUT = $(XML_OUTPUT) -XML_PROGRAMLISTING = YES +XML_PROGRAMLISTING = NO #--------------------------------------------------------------------------- # Configuration options related to external references @@ -199,4 +199,3 @@ GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES -PERL_PATH = /usr/bin/perl diff --git a/doc/Jamfile b/doc/Jamfile index 46570e72..5dcf5426 100644 --- a/doc/Jamfile +++ b/doc/Jamfile @@ -97,7 +97,8 @@ make base-extract-xml-pages.xsl : $(docca)/include/docca/base-extract-xml-pages. make common.xsl : $(docca)/include/docca/common.xsl : @copy_script ; make stage1.xsl : $(docca)/include/docca/stage1.xsl : @copy_script ; make base-stage1.xsl : $(docca)/include/docca/base-stage1.xsl : @copy_script ; -make stage2.xsl : $(docca)/include/docca/stage2.xsl : @copy_script ; +# Workaround to prevent docca from escaping quickbook from Doxygen comments +make stage2.xsl : $(BOOST_ROOT)/libs/mysql/doc/docca-stage2-noescape.xsl : @copy_script ; make assemble-quickbook.xsl : $(docca)/include/docca/assemble-quickbook.xsl : @copy_script ; make emphasized-types.xsl : $(docca)/include/docca/emphasized-types.xsl : @copy_script ; diff --git a/doc/docca-stage2-noescape.xsl b/doc/docca-stage2-noescape.xsl new file mode 100644 index 00000000..e9d78cf3 --- /dev/null +++ b/doc/docca-stage2-noescape.xsl @@ -0,0 +1,224 @@ + +]> + + + + + + + + + + + + + + + {$nl} + [section:{tokenize(@id,'\.')[last()]} {d:qb-escape(title)}] + + + + + {$nl}[indexterm1 {d:qb-escape(@primary-index-term)}]{$nl} + {$nl}[indexterm2 {d:qb-escape(@primary-index-term)}..{ + d:qb-escape(@secondary-index-term)}]{$nl} + + + + + {$nl}[heading {.}] + + + + + + + + + + {$nl}```{$nl} + {$nl}```{$nl} + + + + {$nl}{$nl} + + {$nl} + + ; + + + ;{$nl} + + '''&raquo;''' more... + + `` + + `` + + + ``[link {$doc-ref}.{@d:refid} {d:qb-escape(.)}]`` + [link {$doc-ref}.{@d:refid} {d:qb-escape(.)}] + [link {$doc-ref}.{@d:refid} `{d:qb-escape(.)}`] + + ` + ` + + enum + + using + = + ; + + ``['implementation-defined]`` + ``['see-below]`` + ``__deduced__`` + + {' '} + ; + + {' '} + + :{$nl} + {$nl} + , + + {' '} + + template< + >{$nl} + + {$nl} + , + + {' '} + + + ( + ) + + __{translate(.,'_','')}__ + + = + + {' '} + + + {$nl} + + + [role red error.{@message}] + + {$nl}[table + {$nl}] + + + [ + ] + + {$nl} [ + {$nl} ] + + {$nl} [ + {$nl} ] + + [* + ] + + [' + ] + + [@{@url} + ] + + {$nl} + + + {$nl} + + + + + + + + + {ancestor::listitem ! (1 to $list-indent-width) ! ' '} + + * + # + + + + [' + [role red \[Page type: [*{/*/@type}]\]] + [role green \[[@../../doc/html/{translate($doc-ref,'.','/')}/{ + translate(/page/@id,'.','/')}.html [role green doc_build_html]]\]] + [@../build/xml-pages/{/page/@id}.xml [role blue [*\[doxygen_page_xml\]]]] + [@../build/stage1_visualized/visualized/{/page/@id}.html [role magenta ---stage1_visualized-->]] + [@../build/stage1_visualized/results/{ /page/@id}.xml [role blue [*\[docca_page_xml\]]]] + [@../build/stage2_visualized/visualized/{/page/@id}.html [role magenta ---stage2_visualized-->]] + [@../build/stage2_visualized/results/{ /page/@id}.txt [role blue [*\[quickbook_result\]]]] + ] + + + + {$nl} + + {' '} + + {$nl}{$nl} + + {$nl}```{$nl} + ```{$nl} + + {$nl} + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/qbk/resultsets.qbk b/doc/qbk/resultsets.qbk index c494bb2f..8ea800f9 100644 --- a/doc/qbk/resultsets.qbk +++ b/doc/qbk/resultsets.qbk @@ -16,11 +16,9 @@ corresponding asynchronous versions. [section:rows Rows] A [reflink resultset] represents tabular data. Data is represented -row-by-row. Depending on which method you use to retrieve the rows, -these may be [reflink row] or [reflink owning_row] objects. -In essence, both represent an array of [reflink value]s, which -you can retrieve using [refmem row values]. This array will -have a single element for each field (column) in the [reflink resultset]. +row-by-row, using the [reflink row] class, which represent an array +of [reflink value]s, which you can retrieve using [refmem row values]. +This array will have a single element for each field (column) in the [reflink resultset]. Fields appear in the resultset in the same order as in the SQL query. For example, let `rowobj` an [reflink row] obtained from a [reflink resultset] @@ -44,70 +42,54 @@ directly. Instead, [reflink resultset]s are I/O objects, which know how to retrieve rows. This allows to read rows progressively, allowing for better efficiency. -There are three methods of retrieving rows, which resemble -[@https://www.python.org/dev/peps/pep-0249/#fetchone Python's DB v2.0 cursor API]: +There are three methods of retrieving rows: -* [refmem resultset fetch_one] and [refmem resultset async_fetch_one]: +* [refmem resultset read_one] and [refmem resultset async_read_one]: retrieves a single row. -* [refmem resultset fetch_many] and [refmem resultset async_fetch_many]: +* [refmem resultset read_many] and [refmem resultset async_read_many]: retrieves an array of rows, up to a certain count. -* [refmem resultset fetch_all] and [refmem resultset async_fetch_all]: +* [refmem resultset read_all] and [refmem resultset async_read_all]: retrieves all the remaining rows. [heading Fetching a single row] -The [refmem resultset fetch_one] family retrieves the next row. -It returns a `const` [reflink row]\* pointer. When there are no more -rows to be read, it will return `nullptr`. You can use it as follows: +The [refmem resultset read_one] family retrieve the next row. +The functions get passed a [reflink row] lvalue reference, which +will get populated by the next read row. The function returns a +`bool` indicating whether a row was read or not. You can use it as follows: `` tcp_resultset result = /* obtain a resultset, e.g. via connection::query */ -while (const row* row_obj = result.fetch_one()) +row row_obj; +while (result.read_one(row_obj)) { - // Do stuff with *row_obj + // Do stuff with row_obj } `` -The returned [reflink row] is owned by the [reflink resultset]. -Note also that, if any of the fields are strings, the memory -pointed to by the values will also be owned by the [reflink resultset]. -This object will remain valid until you call [*any] of the fetch -functions again, or you destroy the resultset. - -[warning - Use [refmem resultset fetch_one] with caution. If you - need the values to outlive the [reflink resultset], - use the other fetch methods. -] +Note that if any of the fields are strings, the memory +pointed to by the values will be owned by the [reflink row] object. +Thus, these values will be valid as long as the [reflink row] object +is kept alive and valid. The examples on async queries with [link mysql.examples.query_async_futures futures], [link mysql.examples.query_async_coroutines Boost.Coroutine coroutines] and [link mysql.examples.query_async_coroutinescpp20 C++20 coroutines] -make use of [refmem resultset async_fetch_one]. +make use of [refmem resultset async_read_one]. [heading Fetching multiple rows] -The [refmem resultset fetch_many] family retrieve many +The [refmem resultset read_many] family retrieve many rows at a single time. When you call them, you pass in the maximum number of rows you want to read. For example: `` tcp_resultset result = /* obtain a resultset, e.g. via connection::query */ -std::vector rows = result.fetch_many(5); // at most 5 rows +std::vector rows = result.read_many(5); // at most 5 rows `` -Note that, contrary to what happened in [refmem resultset fetch_one], -these functions return [reflink owning_row] objects. -This means that, if any of the fields are strings, the [reflink value] -objects will point into memory owned by the [reflink owning_row] -object. With [refmem resultset fetch_one], this memory was owned -by the [reflink resultset]. This also means that you can call -any other fetch methods without the row objects becoming invalid. -[reflink owning_row] objects may outlive their [reflink resultset] -object without incurring in undefined behavior. - -The [refmem resultset fetch_all] functions work similarly to -[refmem resultset fetch_many], except that they retrieve all the +The [refmem resultset read_all] functions work similarly to +[refmem resultset read_many], except that they retrieve all the rows in the resultset. [endsect] @@ -123,10 +105,10 @@ read a row, but there are not any more rows available. For example, in a resultset with 4 rows, any of the following actions will complete the resultset: -* Calling [refmem resultset fetch_one] 5 times. -* Calling [refmem resultset fetch_many] with a count of 5 or greater +* Calling [refmem resultset read_one] 5 times. +* Calling [refmem resultset read_many] with a count of 5 or greater (or several times, with a total count of 5 or greater). -* Calling [refmem resultset fetch_all]. +* Calling [refmem resultset read_all]. After a [reflink resultset] is complete, some extra information about the query becomes available, like [refmem resultset warning_count] @@ -138,7 +120,7 @@ A resultset may also be [*empty], which means it has no row to be retrieved. Non `SELECT` SQL statements (e.g. `UPDATE` and `INSERT`) generate empty resultsets. Empty resultsets are complete from the beginning: you don't need to -call [refmem resultset fetch_one] to make them complete. +call [refmem resultset read_one] to make them complete. [warning Because of how the MySQL protocol works, once you obtain @@ -187,7 +169,7 @@ how the [reflink resultset] was generated: (the protocol provides no option to avoid this). However you can choose to start processing them as soon as the first one arrives by using - [refmem resultset fetch_one] or [refmem resultset fetch_many]. + [refmem resultset read_one] or [refmem resultset read_many]. * If it was generated using [refmem prepared_statement execute] or [refmem prepared_statement async_execute], then you are using the [*binary protocol]. The current implementation @@ -195,8 +177,8 @@ how the [reflink resultset] was generated: making it similar to the case above. However, the protocol does allow sending rows progressively. Future implementations may take advantage of this - fact when using [refmem resultset fetch_one] or - [refmem resultset fetch_many]. + fact when using [refmem resultset read_one] or + [refmem resultset read_many]. [endsect] diff --git a/doc/qbk/tutorial.qbk b/doc/qbk/tutorial.qbk index fe692209..f5c2ad2d 100644 --- a/doc/qbk/tutorial.qbk +++ b/doc/qbk/tutorial.qbk @@ -73,15 +73,15 @@ A [reflink resultset] is an object that represents the result of a query, in tabular format. Resultsets are not containers, but I/O objects: they do not contain by themselves the entire result of the query, but allow the user to fetch it using several methods. We will use -[reflink2 resultset.fetch_all tcp_resultset::fetch_all], which just +[reflink2 resultset.read_all tcp_resultset::read_all], which just reads all the rows in the resultset and places them in a `std::vector` -of [reflink owning_row] objects: +of [reflink row] objects: [tutorial_fetch] Rows are essentially arrays of [reflink value]s . A [reflink value] is a union-like class of all types allowed in MySQL. You can access the row's -contents using [refmem owning_row values]. +contents using [refmem row values]. [tutorial_values] diff --git a/doc/qbk/values.qbk b/doc/qbk/values.qbk index fba2fe46..df4ede1c 100644 --- a/doc/qbk/values.qbk +++ b/doc/qbk/values.qbk @@ -132,11 +132,10 @@ Instead, prefer the following: ] In __Self__, the memory pointed to by string [reflink value]s -is owned by either [reflink resultset] or [reflink owning_row] -objects. This avoids unnecessary copies and makes the [reflink value] +is owned by the [reflink row] objects the values belong to. +This avoids unnecessary copies and makes the [reflink value] class lightweight and cheap to copy. In exchange, you must pay attention -to the lifetime of the [reflink resultset] or [reflink owning_row] -object you used to obtain the [reflink value] to not incur in +to the lifetime of the [reflink row] object you used to obtain the [reflink value] to not incur in undefined behavior. For more information on resultset mechanics, see [link mysql.resultsets this section]. @@ -147,7 +146,7 @@ see [link mysql.resultsets this section]. Values may be streamed and compared for equality. If you need to `visit()` a [reflink value], use [refmem value to_variant] and use `visit()` on the returned variant. Additionally, -[reflink row] and [reflink owning_row] objects may also +[reflink row] objects may also be streamed and compared for equality. [endsect] diff --git a/doc/xsl/config.xsl b/doc/xsl/config.xsl index 338e95d7..33f0db1c 100644 --- a/doc/xsl/config.xsl +++ b/doc/xsl/config.xsl @@ -11,4 +11,5 @@ + diff --git a/example/default_completion_tokens.cpp b/example/default_completion_tokens.cpp index e7bfb9f7..672c4385 100644 --- a/example/default_completion_tokens.cpp +++ b/example/default_completion_tokens.cpp @@ -7,6 +7,7 @@ //[example_default_completion_tokens] #include "boost/mysql/mysql.hpp" +#include "boost/mysql/row.hpp" #include #include #include @@ -111,15 +112,15 @@ boost::asio::awaitable start_query( auto result = co_await conn.async_query(sql); /** - * Get all rows in the resultset. We will employ resultset::async_fetch_one(), - * which returns a single row at every call. The returned row is a pointer - * to memory owned by the resultset, and is re-used for each row. Thus, returned - * rows remain valid until the next call to async_fetch_one(). When no more - * rows are available, async_fetch_one returns nullptr. + * Get all rows in the resultset. We will employ resultset::async_read_one(), + * which reads a single row at every call. The row is read in-place, preventing + * unnecessary copies. resultset::async_read_one() returns true if a row has been + * read, false if no more rows are available or an error occurred. */ - while (const boost::mysql::row* row = co_await result.async_fetch_one()) + boost::mysql::row row; + while (co_await result.async_read_one(row)) { - print_employee(*row); + print_employee(row); } // Notify the MySQL server we want to quit, then close the underlying connection. diff --git a/example/prepared_statements.cpp b/example/prepared_statements.cpp index 7d7f927f..21cdd952 100644 --- a/example/prepared_statements.cpp +++ b/example/prepared_statements.cpp @@ -87,7 +87,7 @@ void main_impl(int argc, char** argv) */ //[prepared_statements_execute boost::mysql::tcp_resultset result = salary_getter.execute(boost::mysql::make_values("Efficient")); - std::vector salaries = result.fetch_all(); // Get all the results + std::vector salaries = result.read_all(); // Get all the results //] ASSERT(salaries.size() == 1); double salary = salaries[0].values().at(0).get(); // First row, first column @@ -109,7 +109,7 @@ void main_impl(int argc, char** argv) * connection::prepare_statement() again. */ result = salary_getter.execute(boost::mysql::make_values("Efficient")); - salaries = result.fetch_all(); + salaries = result.read_all(); ASSERT(salaries.size() == 1); salary = salaries[0].values().at(0).get(); ASSERT(salary == 35000); // Our update took place, and the dev got his pay rise diff --git a/example/query_async_callbacks.cpp b/example/query_async_callbacks.cpp index b4a40b34..8c59bdef 100644 --- a/example/query_async_callbacks.cpp +++ b/example/query_async_callbacks.cpp @@ -17,7 +17,7 @@ using boost::mysql::error_code; using boost::mysql::error_info; using boost::mysql::tcp_resultset; -using boost::mysql::owning_row; +using boost::mysql::row; #define ASSERT(expr) \ if (!(expr)) \ @@ -78,7 +78,7 @@ public: connection.async_query(sql, additional_info, [this](error_code err, tcp_resultset&& result) { die_on_error(err, additional_info); resultset = std::move(result); - resultset.async_fetch_all(additional_info, [this](error_code err, const std::vector& rows) { + resultset.async_read_all(additional_info, [this](error_code err, const std::vector& rows) { die_on_error(err, additional_info); for (const auto& employee: rows) { @@ -106,7 +106,7 @@ public: connection.async_query(sql, additional_info, [this](error_code err, tcp_resultset&& result) { die_on_error(err, additional_info); resultset = std::move(result); - resultset.async_fetch_all(additional_info, [this](error_code err, const std::vector& rows) { + resultset.async_read_all(additional_info, [this](error_code err, const std::vector& rows) { die_on_error(err, additional_info); ASSERT(rows.size() == 1); auto salary = rows[0].values()[0].get(); diff --git a/example/query_async_coroutines.cpp b/example/query_async_coroutines.cpp index fc1660e2..eb519dcd 100644 --- a/example/query_async_coroutines.cpp +++ b/example/query_async_coroutines.cpp @@ -8,6 +8,7 @@ //[example_query_async_coroutines #include "boost/mysql/mysql.hpp" +#include "boost/mysql/row.hpp" #include #include #include @@ -90,18 +91,16 @@ void main_impl(int argc, char** argv) check_error(ec, additional_info); /** - * Get all rows in the resultset. We will employ resultset::async_fetch_one(), - * which returns a single row at every call. The returned row is a pointer - * to memory owned by the resultset, and is re-used for each row. Thus, returned - * rows remain valid until the next call to async_fetch_one(). When no more - * rows are available, async_fetch_one returns nullptr. + * Get all rows in the resultset. We will employ resultset::async_read_one(), + * which reads a single row at every call. The row is read in-place, preventing + * unnecessary copies. resultset::async_read_one() returns true if a row has been + * read, false if no more rows are available or an error occurred. */ - while (true) + boost::mysql::row row; + while (result.async_read_one(row, additional_info, yield[ec])) { - const boost::mysql::row* row = result.async_fetch_one(additional_info, yield[ec]); check_error(ec, additional_info); - if (!row) break; // No more rows available - print_employee(*row); + print_employee(row); } // Notify the MySQL server we want to quit, then close the underlying connection. diff --git a/example/query_async_coroutinescpp20.cpp b/example/query_async_coroutinescpp20.cpp index 7673d0b6..5a61edcb 100644 --- a/example/query_async_coroutinescpp20.cpp +++ b/example/query_async_coroutinescpp20.cpp @@ -8,6 +8,7 @@ //[example_query_async_coroutinescpp20 #include "boost/mysql/mysql.hpp" +#include "boost/mysql/row.hpp" #include #include #include @@ -101,16 +102,15 @@ boost::asio::awaitable start_query( auto result = co_await conn.async_query(sql, boost::asio::use_awaitable); /** - * Get all rows in the resultset. We will employ resultset::async_fetch_one(), - * which returns a single row at every call. The returned row is a pointer - * to memory owned by the resultset, and is re-used for each row. Thus, returned - * rows remain valid until the next call to async_fetch_one(). When no more - * rows are available, async_fetch_one returns nullptr. - */ - while (const boost::mysql::row* row = - co_await result.async_fetch_one(boost::asio::use_awaitable)) + * Get all rows in the resultset. We will employ resultset::async_read_one(), + * which reads a single row at every call. The row is read in-place, preventing + * unnecessary copies. resultset::async_read_one() returns true if a row has been + * read, false if no more rows are available or an error occurred. + */ + boost::mysql::row row; + while (co_await result.async_read_one(row, boost::asio::use_awaitable)) { - print_employee(*row); + print_employee(row); } // Notify the MySQL server we want to quit, then close the underlying connection. diff --git a/example/query_async_futures.cpp b/example/query_async_futures.cpp index 6113bf7b..101379ca 100644 --- a/example/query_async_futures.cpp +++ b/example/query_async_futures.cpp @@ -8,6 +8,7 @@ //[example_query_async_futures #include "boost/mysql/mysql.hpp" +#include "boost/mysql/row.hpp" #include #include #include @@ -90,15 +91,15 @@ void main_impl(int argc, char** argv) boost::mysql::tcp_resultset result = resultset_fut.get(); /** - * Get all rows in the resultset. We will employ resultset::async_fetch_one(), - * which returns a single row at every call. The returned row is a pointer - * to memory owned by the resultset, and is re-used for each row. Thus, returned - * rows remain valid until the next call to async_fetch_one(). When no more - * rows are available, async_fetch_one returns nullptr. - */ - while (const boost::mysql::row* current_row = result.async_fetch_one(use_future).get()) + * Get all rows in the resultset. We will employ resultset::async_read_one(), + * which reads a single row at every call. The row is read in-place, preventing + * unnecessary copies. resultset::async_read_one() returns true if a row has been + * read, false if no more rows are available or an error occurred. + */ + boost::mysql::row row; + while (result.async_read_one(row, use_future).get()) { - print_employee(*current_row); + print_employee(row); } // Notify the MySQL server we want to quit, then close the underlying connection. diff --git a/example/query_sync.cpp b/example/query_sync.cpp index 40be788f..f5571f35 100644 --- a/example/query_sync.cpp +++ b/example/query_sync.cpp @@ -83,8 +83,8 @@ void main_impl(int argc, char** argv) * * Resultset objects represent the result of a query, in tabular format. * They hold metadata describing the fields the resultset holds (in this case, first_name, - * last_name and salary). To get the actual data, use fetch_one, fetch_many or fetch_all. - * We will use fetch_all, which returns all the received rows as a std::vector. + * last_name and salary). To get the actual data, use read_one, read_many or read_all. + * We will use read_all, which returns all the received rows as a std::vector. * * We will get all employees working for 'High Growth Startup'. */ @@ -92,7 +92,7 @@ void main_impl(int argc, char** argv) boost::mysql::tcp_resultset result = conn.query(sql); // Get all the rows in the resultset - std::vector employees = result.fetch_all(); + std::vector employees = result.read_all(); for (const auto& employee: employees) { print_employee(employee); @@ -106,7 +106,7 @@ void main_impl(int argc, char** argv) // Check we have updated our poor intern salary result = conn.query("SELECT salary FROM employee WHERE first_name = 'Underpaid'"); - auto rows = result.fetch_all(); + auto rows = result.read_all(); ASSERT(rows.size() == 1); double salary = rows[0].values()[0].get(); ASSERT(salary == 10000); diff --git a/example/tutorial.cpp b/example/tutorial.cpp index 7b9b441b..90a92f71 100644 --- a/example/tutorial.cpp +++ b/example/tutorial.cpp @@ -52,7 +52,7 @@ void main_impl(int argc, char** argv) //] //[tutorial_fetch - std::vector employees = result.fetch_all(); + std::vector employees = result.read_all(); //] //[tutorial_values diff --git a/example/unix_socket.cpp b/example/unix_socket.cpp index 85cc371e..93f1fef3 100644 --- a/example/unix_socket.cpp +++ b/example/unix_socket.cpp @@ -74,7 +74,7 @@ void main_impl(int argc, char** argv) boost::mysql::unix_resultset result = conn.query(sql); // Get all the rows in the resultset - std::vector employees = result.fetch_all(); + std::vector employees = result.read_all(); for (const auto& employee: employees) { print_employee(employee); @@ -88,7 +88,7 @@ void main_impl(int argc, char** argv) // Check we have updated our poor intern salary result = conn.query("SELECT salary FROM employee WHERE first_name = 'Underpaid'"); - auto rows = result.fetch_all(); + auto rows = result.read_all(); ASSERT(rows.size() == 1); double salary = rows[0].values()[0].get(); ASSERT(salary == 10000); diff --git a/include/boost/mysql/detail/network_algorithms/impl/read_row.hpp b/include/boost/mysql/detail/network_algorithms/impl/read_row.hpp index 4236bd31..924561d6 100644 --- a/include/boost/mysql/detail/network_algorithms/impl/read_row.hpp +++ b/include/boost/mysql/detail/network_algorithms/impl/read_row.hpp @@ -18,8 +18,8 @@ inline read_row_result process_read_message( deserialize_row_fn deserializer, capabilities current_capabilities, const std::vector& meta, - const bytestring& buffer, - std::vector& output_values, + row& output, + bytestring& ok_packet_buffer, ok_packet& output_ok_packet, error_code& err, error_info& info @@ -29,17 +29,19 @@ inline read_row_result process_read_message( // Message type: row, error or eof? std::uint8_t msg_type = 0; - deserialization_context ctx (boost::asio::buffer(buffer), current_capabilities); + deserialization_context ctx (boost::asio::buffer(output.buffer()), current_capabilities); err = make_error_code(deserialize(ctx, msg_type)); if (err) return read_row_result::error; if (msg_type == eof_packet_header) { - // end of resultset + // end of resultset => we read the packet against the row, but it is the ok_packet, instead err = deserialize_message(ctx, output_ok_packet); - return err ? read_row_result::error : read_row_result::eof; if (err) return read_row_result::error; + std::swap(output.buffer(), ok_packet_buffer); + output.buffer().clear(); + output.values().clear(); return read_row_result::eof; } else if (msg_type == error_packet_header) @@ -52,7 +54,7 @@ inline read_row_result process_read_message( { // An actual row ctx.rewind(1); // keep the 'message type' byte, as it is part of the actual message - err = deserializer(ctx, meta, output_values); + err = deserializer(ctx, meta, output.values()); if (err) return read_row_result::error; return read_row_result::row; @@ -66,8 +68,8 @@ struct read_row_op : boost::asio::coroutine error_info& output_info_; deserialize_row_fn deserializer_; const std::vector& meta_; - bytestring& buffer_; - std::vector& output_values_; + row& output_; + bytestring& ok_packet_buffer_; ok_packet& output_ok_packet_; read_row_op( @@ -75,16 +77,16 @@ struct read_row_op : boost::asio::coroutine error_info& output_info, deserialize_row_fn deserializer, const std::vector& meta, - bytestring& buffer, - std::vector& output_values, + row& output, + bytestring& ok_packet_buffer, ok_packet& output_ok_packet ) : chan_(chan), output_info_(output_info), deserializer_(deserializer), meta_(meta), - buffer_(buffer), - output_values_(output_values), + output_(output), + ok_packet_buffer_(ok_packet_buffer), output_ok_packet_(output_ok_packet) { } @@ -108,15 +110,15 @@ struct read_row_op : boost::asio::coroutine BOOST_ASIO_CORO_REENTER(*this) { // Read the message - BOOST_ASIO_CORO_YIELD chan_.async_read(buffer_, std::move(self)); + BOOST_ASIO_CORO_YIELD chan_.async_read(output_.buffer(), std::move(self)); // Process it result = process_read_message( deserializer_, chan_.current_capabilities(), meta_, - buffer_, - output_values_, + output_, + ok_packet_buffer_, output_ok_packet_, err, output_info_ @@ -136,15 +138,15 @@ boost::mysql::detail::read_row_result boost::mysql::detail::read_row( deserialize_row_fn deserializer, channel& channel, const std::vector& meta, - bytestring& buffer, - std::vector& output_values, + row& output, + bytestring& ok_packet_buffer, ok_packet& output_ok_packet, error_code& err, error_info& info ) { // Read a packet - channel.read(buffer, err); + channel.read(output.buffer(), err); if (err) return read_row_result::error; @@ -152,8 +154,8 @@ boost::mysql::detail::read_row_result boost::mysql::detail::read_row( deserializer, channel.current_capabilities(), meta, - buffer, - output_values, + output, + ok_packet_buffer, output_ok_packet, err, info @@ -169,8 +171,8 @@ boost::mysql::detail::async_read_row( deserialize_row_fn deserializer, channel& chan, const std::vector& meta, - bytestring& buffer, - std::vector& output_values, + row& output, + bytestring& ok_packet_buffer, ok_packet& output_ok_packet, CompletionToken&& token, error_info& output_info @@ -182,8 +184,8 @@ boost::mysql::detail::async_read_row( output_info, deserializer, meta, - buffer, - output_values, + output, + ok_packet_buffer, output_ok_packet ), token, diff --git a/include/boost/mysql/detail/network_algorithms/read_row.hpp b/include/boost/mysql/detail/network_algorithms/read_row.hpp index 1edb94f9..e22441a1 100644 --- a/include/boost/mysql/detail/network_algorithms/read_row.hpp +++ b/include/boost/mysql/detail/network_algorithms/read_row.hpp @@ -10,6 +10,7 @@ #include "boost/mysql/detail/network_algorithms/common.hpp" #include "boost/mysql/metadata.hpp" +#include "boost/mysql/row.hpp" #include #include @@ -29,8 +30,8 @@ read_row_result read_row( deserialize_row_fn deserializer, channel& channel, const std::vector& meta, - bytestring& buffer, - std::vector& output_values, + row& output, + bytestring& ok_packet_buffer, ok_packet& output_ok_packet, error_code& err, error_info& info @@ -42,9 +43,9 @@ async_read_row( deserialize_row_fn deserializer, channel& channel, const std::vector& meta, - bytestring& buffer, - std::vector& output_values, - ok_packet& output_ok_packet, + row& output, + bytestring& ok_packet_buffer, + ok_packet& output_ok_packet, CompletionToken&& token, error_info& output_info ); diff --git a/include/boost/mysql/impl/resultset.hpp b/include/boost/mysql/impl/resultset.hpp index 1227974c..1dab1926 100644 --- a/include/boost/mysql/impl/resultset.hpp +++ b/include/boost/mysql/impl/resultset.hpp @@ -13,9 +13,11 @@ #include #include #include +#include template -const boost::mysql::row* boost::mysql::resultset::fetch_one( +bool boost::mysql::resultset::read_one( + row& output, error_code& err, error_info& info ) @@ -26,33 +28,36 @@ const boost::mysql::row* boost::mysql::resultset::fetch_one( if (complete()) { - return nullptr; + output.clear(); + return false; } auto result = detail::read_row( deserializer_, *channel_, meta_.fields(), - buffer_, - current_row_.values(), + output, + ok_packet_buffer_, ok_packet_, err, info ); eof_received_ = result == detail::read_row_result::eof; - return result == detail::read_row_result::row ? ¤t_row_ : nullptr; + return result == detail::read_row_result::row; } template -const boost::mysql::row* boost::mysql::resultset::fetch_one() +bool boost::mysql::resultset::read_one( + row& output +) { detail::error_block blk; - const row* res = fetch_one(blk.err, blk.info); + bool res = read_one(output, blk.err, blk.info); blk.check(); return res; } template -std::vector boost::mysql::resultset::fetch_many( +std::vector boost::mysql::resultset::read_many( std::size_t count, error_code& err, error_info& info @@ -62,78 +67,65 @@ std::vector boost::mysql::resultset::fetch_man detail::clear_errors(err, info); - std::vector res; + std::vector res; - if (!complete()) // support calling fetch on already exhausted resultset - { - for (std::size_t i = 0; i < count; ++i) - { - detail::bytestring buff; - std::vector values; - - auto result = detail::read_row( - deserializer_, - *channel_, - meta_.fields(), - buff, - values, - ok_packet_, - err, - info - ); - eof_received_ = result == detail::read_row_result::eof; - if (result == detail::read_row_result::row) - { - res.emplace_back(std::move(values), std::move(buff)); - } - else - { - break; - } - } - } + for (std::size_t i = 0; i < count; ++i) + { + row r; + if (this->read_one(r, err, info)) + { + res.push_back(std::move(r)); + } + else + { + break; + } + } return res; } template -std::vector boost::mysql::resultset::fetch_many( +std::vector boost::mysql::resultset::read_many( std::size_t count ) { detail::error_block blk; - auto res = fetch_many(count, blk.err, blk.info); + auto res = read_many(count, blk.err, blk.info); blk.check(); return res; } template -std::vector boost::mysql::resultset::fetch_all( +std::vector boost::mysql::resultset::read_all( error_code& err, error_info& info ) { - return fetch_many(std::numeric_limits::max(), err, info); + return read_many(std::numeric_limits::max(), err, info); } template -std::vector boost::mysql::resultset::fetch_all() +std::vector boost::mysql::resultset::read_all() { - return fetch_many(std::numeric_limits::max()); + return read_many(std::numeric_limits::max()); } template -struct boost::mysql::resultset::fetch_one_op +struct boost::mysql::resultset::read_one_op : boost::asio::coroutine { resultset& resultset_; + row& output_; error_info& output_info_; - fetch_one_op( + read_one_op( resultset& obj, + row& output, error_info& output_info ) : resultset_(obj), + output_(output), output_info_(output_info) { } @@ -151,15 +143,16 @@ struct boost::mysql::resultset::fetch_one_op { // ensure return as if by post BOOST_ASIO_CORO_YIELD boost::asio::post(std::move(self)); - self.complete(error_code(), nullptr); + output_.clear(); + self.complete(error_code(), false); BOOST_ASIO_CORO_YIELD break; } BOOST_ASIO_CORO_YIELD detail::async_read_row( resultset_.deserializer_, *resultset_.channel_, resultset_.meta_.fields(), - resultset_.buffer_, - resultset_.current_row_.values(), + output_, + resultset_.ok_packet_buffer_, resultset_.ok_packet_, std::move(self), output_info_ @@ -167,7 +160,7 @@ struct boost::mysql::resultset::fetch_one_op resultset_.eof_received_ = result == detail::read_row_result::eof; self.complete( err, - result == detail::read_row_result::row ? &resultset_.current_row_ : nullptr + result == detail::read_row_result::row ); } } @@ -175,20 +168,21 @@ struct boost::mysql::resultset::fetch_one_op template template + void(boost::mysql::error_code, bool)) CompletionToken> BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( CompletionToken, - void(boost::mysql::error_code, const boost::mysql::row*) + void(boost::mysql::error_code, bool) ) -boost::mysql::resultset::async_fetch_one( +boost::mysql::resultset::async_read_one( + row& output, error_info& output_info, CompletionToken&& token ) { assert(valid()); output_info.clear(); - return boost::asio::async_compose( - fetch_one_op(*this, output_info), + return boost::asio::async_compose( + read_one_op(*this, output, output_info), token, *this ); @@ -197,15 +191,14 @@ boost::mysql::resultset::async_fetch_one( template -struct boost::mysql::resultset::fetch_many_op +struct boost::mysql::resultset::read_many_op : boost::asio::coroutine { struct impl_struct { resultset& parent_resultset; - std::vector rows; - detail::bytestring buffer; - std::vector values; + std::vector rows; + row current_row; std::size_t remaining; error_info& output_info; bool cont {false}; @@ -219,16 +212,15 @@ struct boost::mysql::resultset::fetch_many_op void row_received() { - rows.emplace_back(std::move(values), std::move(buffer)); - values = std::vector(); - buffer = detail::bytestring(); + rows.push_back(std::move(current_row)); + current_row = row(); --remaining; } }; std::shared_ptr impl_; - fetch_many_op( + read_many_op( resultset& obj, error_info& output_info, std::size_t count @@ -258,7 +250,7 @@ struct boost::mysql::resultset::fetch_many_op void operator()( Self& self, error_code err = {}, - detail::read_row_result result=detail::read_row_result::error + detail::read_row_result result = detail::read_row_result::error ) { impl_struct& impl = *impl_; @@ -271,8 +263,8 @@ struct boost::mysql::resultset::fetch_many_op impl.parent_resultset.deserializer_, *impl.parent_resultset.channel_, impl.parent_resultset.meta_.fields(), - impl.buffer, - impl.values, + impl.current_row, + impl.parent_resultset.ok_packet_buffer_, impl.parent_resultset.ok_packet_, std::move(self), impl.output_info @@ -307,12 +299,12 @@ struct boost::mysql::resultset::fetch_many_op template template )) CompletionToken> + void(boost::mysql::error_code, std::vector)) CompletionToken> BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( CompletionToken, - void(boost::mysql::error_code, std::vector) + void(boost::mysql::error_code, std::vector) ) -boost::mysql::resultset::async_fetch_many( +boost::mysql::resultset::async_read_many( std::size_t count, error_info& output_info, CompletionToken&& token @@ -322,9 +314,9 @@ boost::mysql::resultset::async_fetch_many( output_info.clear(); return boost::asio::async_compose< CompletionToken, - void(error_code, std::vector) + void(error_code, std::vector) >( - fetch_many_op(*this, output_info, count), + read_many_op(*this, output_info, count), token, *this ); @@ -332,17 +324,17 @@ boost::mysql::resultset::async_fetch_many( template template )) CompletionToken> + void(boost::mysql::error_code, std::vector)) CompletionToken> BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( CompletionToken, - void(boost::mysql::error_code, std::vector) + void(boost::mysql::error_code, std::vector) ) -boost::mysql::resultset::async_fetch_all( +boost::mysql::resultset::async_read_all( error_info& info, CompletionToken&& token ) { - return async_fetch_many( + return async_read_many( std::numeric_limits::max(), info, std::forward(token) diff --git a/include/boost/mysql/resultset.hpp b/include/boost/mysql/resultset.hpp index ecc2f676..4136fbd0 100644 --- a/include/boost/mysql/resultset.hpp +++ b/include/boost/mysql/resultset.hpp @@ -42,16 +42,15 @@ class resultset detail::deserialize_row_fn deserializer_ {}; detail::channel* channel_; detail::resultset_metadata meta_; - row current_row_; - detail::bytestring buffer_; + detail::bytestring ok_packet_buffer_; detail::ok_packet ok_packet_; bool eof_received_ {false}; error_info& shared_info() noexcept { assert(channel_); return channel_->shared_info(); } - struct fetch_one_op; - struct fetch_many_op; - struct fetch_many_op_impl; + struct read_one_op; + struct read_many_op; + struct read_many_op_impl; public: /// \brief Default constructor. @@ -65,7 +64,7 @@ class resultset deserializer_(deserializer), channel_(&channel), meta_(std::move(meta)) {}; resultset(detail::channel& channel, detail::bytestring&& buffer, const detail::ok_packet& ok_pack): - channel_(&channel), buffer_(std::move(buffer)), ok_packet_(ok_pack), eof_received_(true) {}; + channel_(&channel), ok_packet_buffer_(std::move(buffer)), ok_packet_(ok_pack), eof_received_(true) {}; #endif /// The executor type associated to the object. @@ -82,184 +81,167 @@ class resultset /** * \brief Fetches a single row (sync with error code version). - * \details The returned object will be `nullptr` if there are no more rows - * to read. Calling this function on a complete resultset returns `nullptr`. - * - * The returned [reflink row] points into memory owned by the resultset. Destroying - * or moving the resultset object invalidates it. Calling - * any of the fetch methods again also invalidates it. + * \details Returns `true` if a row was read successfully, `false` if + * there was an error or there were no more rows to read. Calling + * this function on a complete resultset always returns `false`. + * + * If the operation succeeds and returns `true`, the new row will be + * read against `output`, possibly reusing its memory. If the operation + * succeeds but returns `false`, `output` will be set to the empty row + * (as if [refmem row clear] was called). If the operation fails, + * `output` is left in a valid but undetrmined state. */ - const row* fetch_one(error_code& err, error_info& info); + bool read_one(row& output, error_code& err, error_info& info); /** * \brief Fetches a single row (sync with exceptions version). - * \details The returned object will be `nullptr` if there are no more rows - * to read. Calling this function on a complete resultset returns `nullptr`. - * - * The returned [reflink row] points into memory owned by the resultset. Destroying - * or moving the resultset object invalidates it. Calling - * any of the fetch methods again also invalidates it. + * \details Returns `true` if a row was read successfully, `false` if + * there was an error or there were no more rows to read. Calling + * this function on a complete resultset always returns `false`. + * + * If the operation succeeds and returns `true`, the new row will be + * read against `output`, possibly reusing its memory. If the operation + * succeeds but returns `false`, `output` will be set to the empty row + * (as if [refmem row clear] was called). If the operation fails, + * `output` is left in a valid but undetrmined state. */ - const row* fetch_one(); + bool read_one(row& output); /** * \brief Fetches a single row (async without [reflink error_info] version). - * \details The pointer passed to the completion handler - * will be `nullptr` if there are no more rows - * to read. Calling this function on a complete resultset returns `nullptr`. - * - * The [reflink row] passed to the completion handler - * points into memory owned by the resultset. Destroying - * or moving the resultset object invalidates it. Calling - * any of the fetch methods again also invalidates it. + * \details Completes with `true` if a row was read successfully, and with `false` if + * there was an error or there were no more rows to read. Calling + * this function on a complete resultset always returns `false`. + * + * If the operation succeeds and completes with `true`, the new row will be + * read against `output`, possibly reusing its memory. If the operation + * succeeds but completes with `false`, `output` will be set to the empty row + * (as if [refmem row clear] was called). If the operation fails, + * `output` is left in a valid but undetrmined state. * * The handler signature for this operation is - * `void(boost::mysql::error_code, const boost::mysql::row*)`. + * `void(boost::mysql::error_code, bool)`. */ template < - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, const row*)) + BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, bool)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type) > - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, const row*)) - async_fetch_one(CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) + BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, bool)) + async_read_one(row& output, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) { - return async_fetch_one(shared_info(), std::forward(token)); + return async_read_one(output, shared_info(), std::forward(token)); } /** * \brief Fetches a single row (async with [reflink error_info] version). - * \details The pointer passed to the completion handler - * will be `nullptr` if there are no more rows - * to read. Calling this function on a complete resultset returns `nullptr`. - * - * The [reflink row] passed to the completion handler - * points into memory owned by the resultset. Destroying - * or moving the resultset object invalidates it. Calling - * any of the fetch methods again also invalidates it. + * \details Completes with `true` if a row was read successfully, and with `false` if + * there was an error or there were no more rows to read. Calling + * this function on a complete resultset always returns `false`. + * + * If the operation succeeds and completes with `true`, the new row will be + * read against `output`, possibly reusing its memory. If the operation + * succeeds but completes with `false`, `output` will be set to the empty row + * (as if [refmem row clear] was called). If the operation fails, + * `output` is left in a valid but undetrmined state. * * The handler signature for this operation is - * `void(boost::mysql::error_code, const boost::mysql::row*)`. + * `void(boost::mysql::error_code, bool)`. */ template < - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, const row*)) + BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, bool)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type) > - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, const row*)) - async_fetch_one( + BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, bool)) + async_read_one( + row& output, error_info& output_info, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ); - /** - * \brief Fetches several rows, up to a maximum - * (sync with error code version). - * \details The returned rows may outlive the resultset. - * Subsequent calls to any of the fetch methods do not invalidate the returned rows. - */ - std::vector fetch_many(std::size_t count, error_code& err, error_info& info); + /// Fetches several rows, up to a maximum (sync with error code version). + std::vector read_many(std::size_t count, error_code& err, error_info& info); - /** - * \brief Fetches several rows, up to a maximum - * (sync with exceptions version). - * \details The returned rows may outlive the resultset. - * Subsequent calls to any of the fetch methods do not invalidate the returned rows. - */ - std::vector fetch_many(std::size_t count); + /// Fetches several rows, up to a maximum (sync with exceptions version). + std::vector read_many(std::size_t count); /** * \brief Fetches several rows, up to a maximum * (async without [reflink error_info] version). - * \details The returned rows may outlive the resultset. - * Subsequent calls to any of the fetch methods do not invalidate the returned rows. - * + * \details * The handler signature for this operation is - * `void(boost::mysql::error_code, std::vector)`. + * `void(boost::mysql::error_code, std::vector)`. */ template < - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::vector)) + BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::vector)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type) > - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::vector)) - async_fetch_many( + BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::vector)) + async_read_many( std::size_t count, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) { - return async_fetch_many(count, shared_info(), std::forward(token)); + return async_read_many(count, shared_info(), std::forward(token)); } /** * \brief Fetches several rows, up to a maximum * (async with [reflink error_info] version). - * \details The returned rows may outlive the resultset. - * Subsequent calls to any of the fetch methods do not invalidate the returned rows. - * + * \details * The handler signature for this operation is - * `void(boost::mysql::error_code, std::vector)`. + * `void(boost::mysql::error_code, std::vector)`. */ template < - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::vector)) + BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::vector)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type) > - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::vector)) - async_fetch_many( + BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::vector)) + async_read_many( std::size_t count, error_info& output_info, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ); - /** - * \brief Fetches all available rows (sync with error code version). - * \details The returned rows may outlive the resultset. - * Subsequent calls to any of the fetch methods do not invalidate the returned rows. - */ - std::vector fetch_all(error_code& err, error_info& info); + /// Fetches all available rows (sync with error code version). + std::vector read_all(error_code& err, error_info& info); - /** - * \brief Fetches all available rows (sync with exceptions version). - * \details The returned rows may outlive the resultset. - * Subsequent calls to any of the fetch methods do not invalidate the returned rows. - */ - std::vector fetch_all(); + /// Fetches all available rows (sync with exceptions version). + std::vector read_all(); /** * \brief Fetches all available rows (async without [reflink error_info] version). - * \details The returned rows may outlive the resultset. - * Subsequent calls to any of the fetch methods do not invalidate the returned rows. - * + * \details * The handler signature for this operation is - * `void(boost::mysql::error_code, std::vector)`. + * `void(boost::mysql::error_code, std::vector)`. */ template < - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::vector)) + BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::vector)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type) > - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::vector)) - async_fetch_all(CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) + BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::vector)) + async_read_all(CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) { - return async_fetch_all(shared_info(), std::forward(token)); + return async_read_all(shared_info(), std::forward(token)); } /** * \brief Fetches all available rows (async with [reflink error_info] version). - * \details The returned rows may outlive the resultset. - * Subsequent calls to any of the fetch methods do not invalidate the returned rows. - * + * \details * The handler signature for this operation is - * `void(boost::mysql::error_code, std::vector)`. + * `void(boost::mysql::error_code, std::vector)`. */ template < - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::vector)) + BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::vector)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type) > - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::vector)) - async_fetch_all( + BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::vector)) + async_read_all( error_info& output_info, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ); diff --git a/include/boost/mysql/row.hpp b/include/boost/mysql/row.hpp index 1ef628a4..e9039319 100644 --- a/include/boost/mysql/row.hpp +++ b/include/boost/mysql/row.hpp @@ -18,51 +18,64 @@ namespace boost { namespace mysql { /** - * \brief Represents a row returned from a query. - * \details Call [refmem row values] to get the actual sequence of - * [reflinl value]s the row contains. + * \brief Represents a row returned from a database operation. + * \details A row is a collection of values, plus a buffer holding memory + * for the string [reflink value]s. + * + * Call [refmem row values] to get the actual sequence of + * [reflink value]s the row contains. * * There will be the same number of values and in the same order as fields * in the SQL query that produced the row. You can get more information * about these fields using [refmem resultset fields]. * - * If any of the values is a string, it will point to an externally owned piece of memory. - * Thus, the [reflink row] class is not owning, as opposed to [reflink owning_row]. - * Note that, in any case, the sequence of [reflink value]s __is__ owned by the - * row object. The distinction applies only to the memory pointed to by - * string [reflink value]s. + * If any of the values is a string, it will be represented as a `string_view` + * pointing into the row's buffer. These string values will be valid as long as + * the [reflink row] object containing the memory they point to is alive and valid. Concretely: + * - Destroying the row object invalidates the string values. + * - Move assigning against the row invalidates the string values. + * - Calling [refmem row clear] invalidates the string values. + * - Move-constructing a [reflink row] from the current row does **not** + * invalidate the string values. + * + * Default constructible and movable, but not copyable. */ class row { std::vector values_; + detail::bytestring buffer_; public: - /// Default and initializing constructor. - row(std::vector&& values = {}): - values_(std::move(values)) {}; + row() = default; + row(std::vector&& values, detail::bytestring&& buffer) noexcept : + values_(std::move(values)), buffer_(std::move(buffer)) {}; + row(const row&) = delete; + row(row&&) = default; + row& operator=(const row&) = delete; + row& operator=(row&&) = default; + ~row() = default; /// Accessor for the sequence of values. const std::vector& values() const noexcept { return values_; } /// Accessor for the sequence of values. std::vector& values() noexcept { return values_; } -}; -/** - * \brief A row that owns a chunk of memory for its string values. - * \details Default constructible and movable, but not copyable. - */ -class owning_row : public row -{ - detail::bytestring buffer_; -public: - owning_row() = default; - owning_row(std::vector&& values, detail::bytestring&& buffer) : - row(std::move(values)), buffer_(std::move(buffer)) {}; - owning_row(const owning_row&) = delete; - owning_row(owning_row&&) = default; - owning_row& operator=(const owning_row&) = delete; - owning_row& operator=(owning_row&&) = default; - ~owning_row() = default; + /** + * \brief Clears the row object. + * \details Clears the value array and the memory buffer associated to this row. + * After calling this operation, [refmem row values] will be the empty array. Any + * pointers, references and iterators to elements in [refmem row values] will be invalidated. + * Any string values using the memory held by this row will also become invalid. + */ + void clear() noexcept + { + values_.clear(); + buffer_.clear(); + } + + // Private, do not use + const detail::bytestring& buffer() const noexcept { return buffer_; } + detail::bytestring& buffer() noexcept { return buffer_; } }; /** diff --git a/test/common/test_common.hpp b/test/common/test_common.hpp index 3171ab0a..0056322e 100644 --- a/test/common/test_common.hpp +++ b/test/common/test_common.hpp @@ -33,7 +33,7 @@ std::vector make_value_vector(Types&&... args) template row makerow(Types&&... args) { - return row(make_value_vector(std::forward(args)...)); + return row(make_value_vector(std::forward(args)...), {}); } inline date makedate(int num_years, unsigned num_months, unsigned num_days) diff --git a/test/integration/close_statement.cpp b/test/integration/close_statement.cpp index 0131b8cb..c7252148 100644 --- a/test/integration/close_statement.cpp +++ b/test/integration/close_statement.cpp @@ -23,7 +23,7 @@ BOOST_MYSQL_NETWORK_TEST(existing_or_closed_statement, network_fixture, network_ // Verify it works fine auto exec_result = net->execute_statement(stmt.value, {}); exec_result.validate_no_error(); - exec_result.value.fetch_all(); // clean all packets sent for this execution + exec_result.value.read_all(); // clean all packets sent for this execution // Close the statement auto close_result = net->close_statement(stmt.value); diff --git a/test/integration/connect.cpp b/test/integration/connect.cpp index 60e4f6dc..933efd78 100644 --- a/test/integration/connect.cpp +++ b/test/integration/connect.cpp @@ -23,7 +23,7 @@ BOOST_MYSQL_NETWORK_TEST(physical_and_handshake_ok, network_fixture, network_ssl // We are able to query auto query_result = sample.net->query(this->conn, "SELECT 1"); query_result.validate_no_error(); - query_result.value.fetch_all(); // discard any result + query_result.value.read_all(); // discard any result } BOOST_MYSQL_NETWORK_TEST(physical_error, network_fixture, network_ssl_gen) diff --git a/test/integration/database_types.cpp b/test/integration/database_types.cpp index 4a989670..4c15b34f 100644 --- a/test/integration/database_types.cpp +++ b/test/integration/database_types.cpp @@ -18,7 +18,6 @@ using namespace boost::unit_test; using boost::mysql::value; using boost::mysql::field_metadata; using boost::mysql::field_type; -using boost::mysql::row; using boost::mysql::datetime; using boost::mysql::make_values; @@ -591,7 +590,7 @@ BOOST_DATA_TEST_CASE_F(database_types_fixture, query, data::make(all_samples)) // Execute it auto result = conn.query(query); - auto rows = result.fetch_all(); + auto rows = result.read_all(); // Validate the received metadata validate_meta(result.fields(), {sample.mvalid}); @@ -614,7 +613,7 @@ BOOST_DATA_TEST_CASE_F(database_types_fixture, prepared_statement, data::make(al // Execute it with the provided parameters auto result = stmt.execute(make_values(sample.row_id)); - auto rows = result.fetch_all(); + auto rows = result.read_all(); // Validate the received metadata validate_meta(result.fields(), {sample.mvalid}); @@ -661,7 +660,7 @@ BOOST_DATA_TEST_CASE_F(database_types_fixture, // Execute it with the provided parameters auto result = stmt.execute(make_values(sample.row_id, sample.expected_value)); - auto rows = result.fetch_all(); + auto rows = result.read_all(); // Validate the returned value BOOST_TEST_REQUIRE(rows.size() == 1); diff --git a/test/integration/integration_test_common.hpp b/test/integration/integration_test_common.hpp index 66bb06f3..25c91924 100644 --- a/test/integration/integration_test_common.hpp +++ b/test/integration/integration_test_common.hpp @@ -114,13 +114,13 @@ struct network_fixture // make the testing environment more stable and speed up the tests void start_transaction() { - this->conn.query("START TRANSACTION").fetch_all(); + this->conn.query("START TRANSACTION").read_all(); } std::int64_t get_table_size(const std::string& table) { return this->conn.query("SELECT COUNT(*) FROM " + table) - .fetch_all().at(0).values().at(0).template get(); + .read_all().at(0).values().at(0).template get(); } }; diff --git a/test/integration/network_functions.hpp b/test/integration/network_functions.hpp index 41ae4a50..20f9753d 100644 --- a/test/integration/network_functions.hpp +++ b/test/integration/network_functions.hpp @@ -100,9 +100,9 @@ public: virtual network_result execute_statement( prepared_statement_type&, const std::vector&) = 0; virtual network_result close_statement(prepared_statement_type&) = 0; - virtual network_result fetch_one(resultset_type&) = 0; - virtual network_result> fetch_many(resultset_type&, std::size_t count) = 0; - virtual network_result> fetch_all(resultset_type&) = 0; + virtual network_result read_one(resultset_type&, row&) = 0; + virtual network_result> read_many(resultset_type&, std::size_t count) = 0; + virtual network_result> read_all(resultset_type&) = 0; virtual network_result quit(connection_type&) = 0; virtual network_result close(connection_type&) = 0; }; diff --git a/test/integration/network_functions/async_callback.cpp b/test/integration/network_functions/async_callback.cpp index e926371a..bcbf9be8 100644 --- a/test/integration/network_functions/async_callback.cpp +++ b/test/integration/network_functions/async_callback.cpp @@ -14,10 +14,8 @@ using namespace boost::mysql::test; using boost::mysql::connection_params; using boost::mysql::error_code; using boost::mysql::error_info; -using boost::mysql::errc; using boost::mysql::value; using boost::mysql::row; -using boost::mysql::owning_row; namespace { @@ -152,29 +150,30 @@ public: return stmt.async_close(info, std::move(h)); }); } - network_result fetch_one( - resultset_type& r + network_result read_one( + resultset_type& r, + row& output ) override { - return impl([&](handler h, error_info& info) { - return r.async_fetch_one(info, std::move(h)); + return impl([&](handler h, error_info& info) { + return r.async_read_one(output, info, std::move(h)); }); } - network_result> fetch_many( + network_result> read_many( resultset_type& r, std::size_t count ) override { - return impl>([&](handler> h, error_info& info) { - return r.async_fetch_many(count, info, std::move(h)); + return impl>([&](handler> h, error_info& info) { + return r.async_read_many(count, info, std::move(h)); }); } - network_result> fetch_all( + network_result> read_all( resultset_type& r ) override { - return impl>([&](handler> h, error_info& info) { - return r.async_fetch_all(info, std::move(h)); + return impl>([&](handler> h, error_info& info) { + return r.async_read_all(info, std::move(h)); }); } network_result quit( @@ -301,29 +300,30 @@ public: return stmt.async_close(std::move(h)); }); } - network_result fetch_one( - resultset_type& r + network_result read_one( + resultset_type& r, + row& output ) override { - return impl([&](handler h) { - return r.async_fetch_one(std::move(h)); + return impl([&](handler h) { + return r.async_read_one(output, std::move(h)); }); } - network_result> fetch_many( + network_result> read_many( resultset_type& r, std::size_t count ) override { - return impl>([&](handler> h) { - return r.async_fetch_many(count, std::move(h)); + return impl>([&](handler> h) { + return r.async_read_many(count, std::move(h)); }); } - network_result> fetch_all( + network_result> read_all( resultset_type& r ) override { - return impl>([&](handler> h) { - return r.async_fetch_all(std::move(h)); + return impl>([&](handler> h) { + return r.async_read_all(std::move(h)); }); } network_result quit( diff --git a/test/integration/network_functions/async_coroutine.cpp b/test/integration/network_functions/async_coroutine.cpp index ae3cdf57..a2d05cc6 100644 --- a/test/integration/network_functions/async_coroutine.cpp +++ b/test/integration/network_functions/async_coroutine.cpp @@ -16,7 +16,6 @@ using boost::mysql::error_info; using boost::mysql::errc; using boost::mysql::value; using boost::mysql::row; -using boost::mysql::owning_row; using boost::asio::yield_context; namespace @@ -124,29 +123,30 @@ public: return no_result(); }); } - network_result fetch_one( - resultset_type& r + network_result read_one( + resultset_type& r, + row& output ) override { return impl(r, [&](yield_context yield, error_info& info) { - return r.async_fetch_one(info, yield); + return r.async_read_one(output, info, yield); }); } - network_result> fetch_many( + network_result> read_many( resultset_type& r, std::size_t count ) override { return impl(r, [&](yield_context yield, error_info& info) { - return r.async_fetch_many(count, info, yield); + return r.async_read_many(count, info, yield); }); } - network_result> fetch_all( + network_result> read_all( resultset_type& r ) override { return impl(r, [&](yield_context yield, error_info& info) { - return r.async_fetch_all(info, yield); + return r.async_read_all(info, yield); }); } network_result quit( @@ -269,29 +269,30 @@ public: return no_result(); }); } - network_result fetch_one( - resultset_type& r + network_result read_one( + resultset_type& r, + row& output ) override { return impl(r, [&](yield_context yield) { - return r.async_fetch_one(yield); + return r.async_read_one(output, yield); }); } - network_result> fetch_many( + network_result> read_many( resultset_type& r, std::size_t count ) override { return impl(r, [&](yield_context yield) { - return r.async_fetch_many(count, yield); + return r.async_read_many(count, yield); }); } - network_result> fetch_all( + network_result> read_all( resultset_type& r ) override { return impl(r, [&](yield_context yield) { - return r.async_fetch_all(yield); + return r.async_read_all(yield); }); } network_result quit( diff --git a/test/integration/network_functions/async_coroutinecpp20.cpp b/test/integration/network_functions/async_coroutinecpp20.cpp index e7404c79..a023e261 100644 --- a/test/integration/network_functions/async_coroutinecpp20.cpp +++ b/test/integration/network_functions/async_coroutinecpp20.cpp @@ -20,7 +20,6 @@ using boost::mysql::error_info; using boost::mysql::errc; using boost::mysql::value; using boost::mysql::row; -using boost::mysql::owning_row; using boost::asio::use_awaitable; namespace @@ -148,29 +147,30 @@ public: return stmt.async_close(info, use_awaitable); }); } - network_result fetch_one( - resultset_type& r + network_result read_one( + resultset_type& r, + row& output ) override { return impl(r, [&](error_info& info) { - return r.async_fetch_one(info, use_awaitable); + return r.async_read_one(output, info, use_awaitable); }); } - network_result> fetch_many( + network_result> read_many( resultset_type& r, std::size_t count ) override { return impl(r, [&](error_info& info) { - return r.async_fetch_many(count, info, use_awaitable); + return r.async_read_many(count, info, use_awaitable); }); } - network_result> fetch_all( + network_result> read_all( resultset_type& r ) override { return impl(r, [&](error_info& info) { - return r.async_fetch_all(info, use_awaitable); + return r.async_read_all(info, use_awaitable); }); } network_result quit( @@ -310,29 +310,30 @@ public: return stmt.async_close(use_awaitable); }); } - network_result fetch_one( - resultset_type& r + network_result read_one( + resultset_type& r, + row& output ) override { return impl(r, [&] { - return r.async_fetch_one(use_awaitable); + return r.async_read_one(output, use_awaitable); }); } - network_result> fetch_many( + network_result> read_many( resultset_type& r, std::size_t count ) override { return impl(r, [&] { - return r.async_fetch_many(count, use_awaitable); + return r.async_read_many(count, use_awaitable); }); } - network_result> fetch_all( + network_result> read_all( resultset_type& r ) override { return impl(r, [&] { - return r.async_fetch_all(use_awaitable); + return r.async_read_all(use_awaitable); }); } network_result quit( diff --git a/test/integration/network_functions/async_future.cpp b/test/integration/network_functions/async_future.cpp index 1be46042..eb8e5c29 100644 --- a/test/integration/network_functions/async_future.cpp +++ b/test/integration/network_functions/async_future.cpp @@ -12,10 +12,8 @@ using namespace boost::mysql::test; using boost::mysql::connection_params; using boost::mysql::error_code; using boost::mysql::error_info; -using boost::mysql::errc; using boost::mysql::value; using boost::mysql::row; -using boost::mysql::owning_row; using boost::asio::use_future; namespace @@ -174,29 +172,30 @@ public: return stmt.async_close(output_info, use_future); }); } - network_result fetch_one( - resultset_type& r + network_result read_one( + resultset_type& r, + row& output ) override { return impl_errinfo([&] (error_info& output_info) { - return r.async_fetch_one(output_info, use_future); + return r.async_read_one(output, output_info, use_future); }); } - network_result> fetch_many( + network_result> read_many( resultset_type& r, std::size_t count ) override { return impl_errinfo([&] (error_info& output_info) { - return r.async_fetch_many(count, output_info, use_future); + return r.async_read_many(count, output_info, use_future); }); } - network_result> fetch_all( + network_result> read_all( resultset_type& r ) override { return impl_errinfo([&] (error_info& output_info) { - return r.async_fetch_all(output_info, use_future); + return r.async_read_all(output_info, use_future); }); } network_result quit( @@ -290,29 +289,30 @@ public: return stmt.async_close(use_future); }); } - network_result fetch_one( - resultset_type& r + network_result read_one( + resultset_type& r, + row& output ) override { return impl_noerrinfo([&] { - return r.async_fetch_one(use_future); + return r.async_read_one(output, use_future); }); } - network_result> fetch_many( + network_result> read_many( resultset_type& r, std::size_t count ) override { return impl_noerrinfo([&] { - return r.async_fetch_many(count, use_future); + return r.async_read_many(count, use_future); }); } - network_result> fetch_all( + network_result> read_all( resultset_type& r ) override { return impl_noerrinfo([&] { - return r.async_fetch_all(use_future); + return r.async_read_all(use_future); }); } network_result quit( @@ -407,29 +407,30 @@ public: return stmt.async_close(output_info); }); } - network_result fetch_one( - resultset_type& r + network_result read_one( + resultset_type& r, + row& output ) override { return impl_errinfo([&] (error_info& output_info) { - return r.async_fetch_one(output_info); + return r.async_read_one(output, output_info); }); } - network_result> fetch_many( + network_result> read_many( resultset_type& r, std::size_t count ) override { return impl_errinfo([&] (error_info& output_info) { - return r.async_fetch_many(count, output_info); + return r.async_read_many(count, output_info); }); } - network_result> fetch_all( + network_result> read_all( resultset_type& r ) override { return impl_errinfo([&] (error_info& output_info) { - return r.async_fetch_all(output_info); + return r.async_read_all(output_info); }); } network_result quit( @@ -523,29 +524,30 @@ public: return stmt.async_close(); }); } - network_result fetch_one( - resultset_type& r + network_result read_one( + resultset_type& r, + row& output ) override { return impl_noerrinfo([&] { - return r.async_fetch_one(); + return r.async_read_one(output); }); } - network_result> fetch_many( + network_result> read_many( resultset_type& r, std::size_t count ) override { return impl_noerrinfo([&] { - return r.async_fetch_many(count); + return r.async_read_many(count); }); } - network_result> fetch_all( + network_result> read_all( resultset_type& r ) override { return impl_noerrinfo([&] { - return r.async_fetch_all(); + return r.async_read_all(); }); } network_result quit( diff --git a/test/integration/network_functions/sync.cpp b/test/integration/network_functions/sync.cpp index fc5e6761..e1ca0540 100644 --- a/test/integration/network_functions/sync.cpp +++ b/test/integration/network_functions/sync.cpp @@ -17,7 +17,6 @@ using boost::mysql::error_info; using boost::mysql::errc; using boost::mysql::value; using boost::mysql::row; -using boost::mysql::owning_row; namespace { @@ -114,29 +113,30 @@ public: return no_result(); }); } - network_result fetch_one( - resultset_type& r + network_result read_one( + resultset_type& r, + row& output ) override { return impl([&](error_code& code, error_info& info) { - return r.fetch_one(code, info); + return r.read_one(output, code, info); }); } - network_result> fetch_many( + network_result> read_many( resultset_type& r, std::size_t count ) override { return impl([&](error_code& code, error_info& info) { - return r.fetch_many(count, code, info); + return r.read_many(count, code, info); }); } - network_result> fetch_all( + network_result> read_all( resultset_type& r ) override { return impl([&](error_code& code, error_info& info) { - return r.fetch_all(code, info); + return r.read_all(code, info); }); } network_result quit( @@ -255,29 +255,30 @@ public: return no_result(); }); } - network_result fetch_one( - resultset_type& r + network_result read_one( + resultset_type& r, + row& output ) override { return impl([&] { - return r.fetch_one(); + return r.read_one(output); }); } - network_result> fetch_many( + network_result> read_many( resultset_type& r, std::size_t count ) override { return impl([&] { - return r.fetch_many(count); + return r.read_many(count); }); } - network_result> fetch_all( + network_result> read_all( resultset_type& r ) override { return impl([&] { - return r.fetch_all(); + return r.read_all(); }); } network_result quit( diff --git a/test/integration/prepared_statement_lifecycle.cpp b/test/integration/prepared_statement_lifecycle.cpp index e31c54b2..5b65627c 100644 --- a/test/integration/prepared_statement_lifecycle.cpp +++ b/test/integration/prepared_statement_lifecycle.cpp @@ -8,7 +8,6 @@ #include "integration_test_common.hpp" using namespace boost::mysql::test; -using boost::mysql::row; using boost::mysql::value; BOOST_AUTO_TEST_SUITE(test_prepared_statement_lifecycle) @@ -21,7 +20,7 @@ value get_updates_table_value( { return conn.query( "SELECT field_int FROM updates_table WHERE field_varchar = '" + field_varchar + "'") - .fetch_all().at(0).values().at(0); + .read_all().at(0).values().at(0); } BOOST_MYSQL_NETWORK_TEST(select_with_parameters_multiple_executions, network_fixture, network_ssl_gen) @@ -43,7 +42,7 @@ BOOST_MYSQL_NETWORK_TEST(select_with_parameters_multiple_executions, network_fix BOOST_TEST(!result.value.complete()); this->validate_2fields_meta(result.value, "two_rows_table"); - auto rows = net->fetch_all(result.value); + auto rows = net->read_all(result.value); rows.validate_no_error(); BOOST_TEST_REQUIRE(rows.value.size() == 1); BOOST_TEST((rows.value[0] == makerow(1, "f0"))); @@ -56,7 +55,7 @@ BOOST_MYSQL_NETWORK_TEST(select_with_parameters_multiple_executions, network_fix BOOST_TEST(!result.value.complete()); this->validate_2fields_meta(result.value, "two_rows_table"); - rows = net->fetch_all(result.value); + rows = net->read_all(result.value); rows.validate_no_error(); BOOST_TEST_REQUIRE(rows.value.size() == 2); BOOST_TEST((rows.value[0] == makerow(1, "f0"))); @@ -172,7 +171,7 @@ BOOST_MYSQL_NETWORK_TEST(multiple_statements, network_fixture, network_ssl_gen) // Execute select auto select_result = net->execute_statement(stmt_select.value, make_value_vector("f0")); select_result.validate_no_error(); - auto rows = net->fetch_all(select_result.value); + auto rows = net->read_all(select_result.value); rows.validate_no_error(); BOOST_TEST(rows.value.size() == 1); BOOST_TEST(rows.value[0] == makerow(210)); @@ -189,7 +188,7 @@ BOOST_MYSQL_NETWORK_TEST(multiple_statements, network_fixture, network_ssl_gen) // Execute select again select_result = net->execute_statement(stmt_select.value, make_value_vector("f0")); select_result.validate_no_error(); - rows = net->fetch_all(select_result.value); + rows = net->read_all(select_result.value); rows.validate_no_error(); BOOST_TEST(rows.value.size() == 1); BOOST_TEST(rows.value[0] == makerow(220)); diff --git a/test/integration/query.cpp b/test/integration/query.cpp index 0b0a929b..16335ba7 100644 --- a/test/integration/query.cpp +++ b/test/integration/query.cpp @@ -76,7 +76,7 @@ BOOST_MYSQL_NETWORK_TEST(update_ok, network_fixture, network_ssl_gen) result = sample.net->query(this->conn, "SELECT field_int FROM updates_table WHERE field_varchar = 'f0'"); result.validate_no_error(); - auto updated_value = result.value.fetch_all().at(0).values().at(0).template get(); + auto updated_value = result.value.read_all().at(0).values().at(0).template get(); BOOST_TEST(updated_value == 52); // initial value was 42 } diff --git a/test/integration/resultset.cpp b/test/integration/resultset.cpp index 30390ef7..c931b9f4 100644 --- a/test/integration/resultset.cpp +++ b/test/integration/resultset.cpp @@ -5,41 +5,36 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include "boost/mysql/row.hpp" #include "integration_test_common.hpp" +#include "test_common.hpp" using namespace boost::mysql::test; -using boost::mysql::detail::make_error_code; -using boost::mysql::field_metadata; -using boost::mysql::field_type; using boost::mysql::error_code; -using boost::mysql::error_info; using boost::mysql::ssl_mode; using boost::mysql::connection; using boost::mysql::resultset; -using boost::mysql::prepared_statement; using boost::mysql::row; -using boost::mysql::owning_row; -namespace net = boost::asio; BOOST_AUTO_TEST_SUITE(test_resultset) // Helpers template -std::vector makerows(std::size_t row_size, Types&&... args) +std::vector makerows(std::size_t row_size, Types&&... args) { auto values = make_value_vector(std::forward(args)...); assert(values.size() % row_size == 0); - std::vector res; + std::vector res; for (std::size_t i = 0; i < values.size(); i += row_size) { std::vector row_values ( values.begin() + i, values.begin() + i + row_size); - res.push_back(owning_row(std::move(row_values), {})); + res.push_back(row(std::move(row_values), {})); } return res; } -bool operator==(const std::vector& lhs, const std::vector& rhs) +bool operator==(const std::vector& lhs, const std::vector& rhs) { return boost::mysql::detail::container_equals(lhs, rhs); } @@ -162,7 +157,13 @@ struct sample_gen } }; -BOOST_AUTO_TEST_SUITE(fetch_one) +BOOST_AUTO_TEST_SUITE(read_one) + +// Verify read_one clears its paremeter correctly +static row make_initial_row() +{ + return row(make_value_vector(10), {}); +} BOOST_MYSQL_NETWORK_TEST(no_results, network_fixture, sample_gen) { @@ -174,15 +175,19 @@ BOOST_MYSQL_NETWORK_TEST(no_results, network_fixture, sample_gen) BOOST_TEST(result.fields().size() == 2); // Already in the end of the resultset, we receive the EOF - auto row_result = sample.net->fetch_one(result); + row r = make_initial_row(); + auto row_result = sample.net->read_one(result, r); row_result.validate_no_error(); - BOOST_TEST(row_result.value == nullptr); + BOOST_TEST(!row_result.value); + BOOST_TEST(r == row()); validate_eof(result); // Fetching again just returns null - row_result = sample.net->fetch_one(result); + r = make_initial_row(); + row_result = sample.net->read_one(result, r); row_result.validate_no_error(); - BOOST_TEST(row_result.value == nullptr); + BOOST_TEST(!row_result.value); + BOOST_TEST(r == row()); validate_eof(result); } @@ -196,17 +201,20 @@ BOOST_MYSQL_NETWORK_TEST(one_row, network_fixture, sample_gen) BOOST_TEST(result.fields().size() == 2); // Fetch only row - auto row_result = sample.net->fetch_one(result); + row r = make_initial_row(); + auto row_result = sample.net->read_one(result, r); row_result.validate_no_error(); - BOOST_TEST_REQUIRE(row_result.value != nullptr); this->validate_2fields_meta(result, "one_row_table"); - BOOST_TEST((*row_result.value == makerow(1, "f0"))); + BOOST_TEST(row_result.value); + BOOST_TEST((r == makerow(1, "f0"))); BOOST_TEST(!result.complete()); // Fetch next: end of resultset - row_result = sample.net->fetch_one(result); + r = make_initial_row(); + row_result = sample.net->read_one(result, r); row_result.validate_no_error(); - BOOST_TEST(row_result.value == nullptr); + BOOST_TEST(!row_result.value); + BOOST_TEST(r == row()); validate_eof(result); } @@ -220,33 +228,37 @@ BOOST_MYSQL_NETWORK_TEST(two_rows, network_fixture, sample_gen) BOOST_TEST(result.fields().size() == 2); // Fetch first row - auto row_result = sample.net->fetch_one(result); + row r = make_initial_row(); + auto row_result = sample.net->read_one(result, r); row_result.validate_no_error(); - BOOST_TEST_REQUIRE(row_result.value != nullptr); + BOOST_TEST(row_result.value); this->validate_2fields_meta(result, "two_rows_table"); - BOOST_TEST((*row_result.value == makerow(1, "f0"))); + BOOST_TEST((r == makerow(1, "f0"))); BOOST_TEST(!result.complete()); // Fetch next row - row_result = sample.net->fetch_one(result); + r = make_initial_row(); + row_result = sample.net->read_one(result, r); row_result.validate_no_error(); - BOOST_TEST_REQUIRE(row_result.value != nullptr); + BOOST_TEST(row_result.value); this->validate_2fields_meta(result, "two_rows_table"); - BOOST_TEST(*row_result.value == makerow(2, "f1")); + BOOST_TEST((r == makerow(2, "f1"))); BOOST_TEST(!result.complete()); // Fetch next: end of resultset - row_result = sample.net->fetch_one(result); + r = make_initial_row(); + row_result = sample.net->read_one(result, r); row_result.validate_no_error(); - BOOST_TEST(row_result.value == nullptr); + BOOST_TEST(!row_result.value); + BOOST_TEST(r == row()); validate_eof(result); } // There seems to be no real case where fetch can fail (other than net fails) -BOOST_AUTO_TEST_SUITE_END() // fetch_one +BOOST_AUTO_TEST_SUITE_END() // read_one -BOOST_AUTO_TEST_SUITE(fetch_many) +BOOST_AUTO_TEST_SUITE(read_many) BOOST_MYSQL_NETWORK_TEST(no_results, network_fixture, sample_gen) { @@ -255,13 +267,13 @@ BOOST_MYSQL_NETWORK_TEST(no_results, network_fixture, sample_gen) "SELECT * FROM empty_table"); // Fetch many, but there are no results - auto rows_result = sample.net->fetch_many(result, 10); + auto rows_result = sample.net->read_many(result, 10); rows_result.validate_no_error(); BOOST_TEST(rows_result.value.empty()); validate_eof(result); // Fetch again, should return OK and empty - rows_result = sample.net->fetch_many(result, 10); + rows_result = sample.net->read_many(result, 10); rows_result.validate_no_error(); BOOST_TEST(rows_result.value.empty()); validate_eof(result); @@ -274,13 +286,13 @@ BOOST_MYSQL_NETWORK_TEST(more_rows_than_count, network_fixture, sample_gen) "SELECT * FROM three_rows_table"); // Fetch 2, one remaining - auto rows_result = sample.net->fetch_many(result, 2); + auto rows_result = sample.net->read_many(result, 2); rows_result.validate_no_error(); BOOST_TEST(!result.complete()); BOOST_TEST((rows_result.value == makerows(2, 1, "f0", 2, "f1"))); // Fetch another two (completes the resultset) - rows_result = sample.net->fetch_many(result, 2); + rows_result = sample.net->read_many(result, 2); rows_result.validate_no_error(); validate_eof(result); BOOST_TEST((rows_result.value == makerows(2, 3, "f2"))); @@ -293,7 +305,7 @@ BOOST_MYSQL_NETWORK_TEST(less_rows_than_count, network_fixture, sample_gen) "SELECT * FROM two_rows_table"); // Fetch 3, resultset exhausted - auto rows_result = sample.net->fetch_many(result, 3); + auto rows_result = sample.net->read_many(result, 3); rows_result.validate_no_error(); BOOST_TEST((rows_result.value == makerows(2, 1, "f0", 2, "f1"))); validate_eof(result); @@ -306,13 +318,13 @@ BOOST_MYSQL_NETWORK_TEST(same_rows_as_count, network_fixture, sample_gen) "SELECT * FROM two_rows_table"); // Fetch 2, 0 remaining but resultset not exhausted - auto rows_result = sample.net->fetch_many(result, 2); + auto rows_result = sample.net->read_many(result, 2); rows_result.validate_no_error(); BOOST_TEST(!result.complete()); BOOST_TEST((rows_result.value == makerows(2, 1, "f0", 2, "f1"))); // Fetch again, exhausts the resultset - rows_result = sample.net->fetch_many(result, 2); + rows_result = sample.net->read_many(result, 2); rows_result.validate_no_error(); BOOST_TEST(rows_result.value.empty()); validate_eof(result); @@ -325,15 +337,15 @@ BOOST_MYSQL_NETWORK_TEST(count_equals_one, network_fixture, sample_gen) "SELECT * FROM one_row_table"); // Fetch 1, 1 remaining - auto rows_result = sample.net->fetch_many(result, 1); + auto rows_result = sample.net->read_many(result, 1); rows_result.validate_no_error(); BOOST_TEST(!result.complete()); BOOST_TEST((rows_result.value == makerows(2, 1, "f0"))); } -BOOST_AUTO_TEST_SUITE_END() // fetch_many +BOOST_AUTO_TEST_SUITE_END() // read_many -BOOST_AUTO_TEST_SUITE(fetch_all) +BOOST_AUTO_TEST_SUITE(read_all) BOOST_MYSQL_NETWORK_TEST(no_results, network_fixture, sample_gen) { @@ -342,13 +354,13 @@ BOOST_MYSQL_NETWORK_TEST(no_results, network_fixture, sample_gen) "SELECT * FROM empty_table"); // Fetch many, but there are no results - auto rows_result = sample.net->fetch_all(result); + auto rows_result = sample.net->read_all(result); rows_result.validate_no_error(); BOOST_TEST(rows_result.value.empty()); BOOST_TEST(result.complete()); // Fetch again, should return OK and empty - rows_result = sample.net->fetch_all(result); + rows_result = sample.net->read_all(result); rows_result.validate_no_error(); BOOST_TEST(rows_result.value.empty()); validate_eof(result); @@ -360,7 +372,7 @@ BOOST_MYSQL_NETWORK_TEST(one_row, network_fixture, sample_gen) auto result = sample.gen->generate(this->conn, "SELECT * FROM one_row_table"); - auto rows_result = sample.net->fetch_all(result); + auto rows_result = sample.net->read_all(result); rows_result.validate_no_error(); BOOST_TEST(result.complete()); BOOST_TEST((rows_result.value == makerows(2, 1, "f0"))); @@ -372,14 +384,14 @@ BOOST_MYSQL_NETWORK_TEST(several_rows, network_fixture, sample_gen) auto result = sample.gen->generate(this->conn, "SELECT * FROM two_rows_table"); - auto rows_result = sample.net->fetch_all(result); + auto rows_result = sample.net->read_all(result); rows_result.validate_no_error(); validate_eof(result); BOOST_TEST(result.complete()); BOOST_TEST((rows_result.value == makerows(2, 1, "f0", 2, "f1"))); } -BOOST_AUTO_TEST_SUITE_END() // fetch_all +BOOST_AUTO_TEST_SUITE_END() // read_all BOOST_AUTO_TEST_SUITE_END() // test_resultset diff --git a/test/unit/row.cpp b/test/unit/row.cpp index f2247b38..b4b71ed1 100644 --- a/test/unit/row.cpp +++ b/test/unit/row.cpp @@ -6,13 +6,87 @@ // #include "boost/mysql/row.hpp" +#include "boost/mysql/connection.hpp" +#include "boost/mysql/detail/auxiliar/bytestring.hpp" +#include "boost/mysql/value.hpp" #include "test_common.hpp" +#include +#include +#include using namespace boost::mysql::test; using boost::mysql::row; +using boost::mysql::value; +using boost::mysql::detail::bytestring; BOOST_AUTO_TEST_SUITE(test_row) +// Constructors +BOOST_AUTO_TEST_SUITE(constructors) + +static std::vector string_value_from_buffer(const bytestring& buffer) +{ + return make_value_vector(boost::string_view(reinterpret_cast(buffer.data()), buffer.size())); +} + +BOOST_AUTO_TEST_CASE(init_ctor) +{ + // Given a value vector with strings pointing into a buffer, after initializing + // the row via moves, the string still points to valid memory + bytestring buffer {'a', 'b', 'c', 'd'}; + auto values = string_value_from_buffer(buffer); + row r (std::move(values), std::move(buffer)); + buffer = {'e', 'f'}; + BOOST_TEST(r.values()[0] == value("abcd")); +} + +BOOST_AUTO_TEST_CASE(move_ctor) +{ + // Given a row with strings, after a move construction, + // the string still points to valid memory + bytestring buffer {'a', 'b', 'c', 'd'}; + auto values = string_value_from_buffer(buffer); + row r (std::move(values), std::move(buffer)); + row r2 (std::move(r)); + r = row({}, {'e', 'f'}); + BOOST_TEST(r2.values()[0] == value("abcd")); +} + +BOOST_AUTO_TEST_CASE(move_assignment) +{ + // Given a row with strings, after a move assignment, + // the string still points to valid memory + bytestring buffer {'a', 'b', 'c', 'd'}; + auto values = string_value_from_buffer(buffer); + row r (std::move(values), std::move(buffer)); + row r2; + r2 = std::move(r); + r = row({}, {'e', 'f'}); + BOOST_TEST(r2.values()[0] == value("abcd")); +} + +BOOST_AUTO_TEST_SUITE_END() + +// Clear +BOOST_AUTO_TEST_SUITE(clear) + +BOOST_AUTO_TEST_CASE(empty_row) +{ + row r; + r.clear(); + BOOST_TEST(r.values().empty()); +} + +BOOST_AUTO_TEST_CASE(non_empty_row) +{ + row r = makerow("abc"); + r.clear(); + BOOST_TEST(r.values().empty()); +} + +BOOST_AUTO_TEST_SUITE_END() + + // Equality operators BOOST_AUTO_TEST_SUITE(operators_eq_ne) diff --git a/tools/user-config.jam b/tools/user-config.jam index 75dac40a..6e0682e0 100644 --- a/tools/user-config.jam +++ b/tools/user-config.jam @@ -28,3 +28,6 @@ project user-config $(OPENSSL_ROOT)/lib/VC ; +using doxygen ; +using boostbook ; +using saxonhe ; \ No newline at end of file