Replaced tabs for spaces

This commit is contained in:
ruben 2020-04-20 11:36:17 +01:00
parent 16ef83b136
commit a54c83ef6a
113 changed files with 9058 additions and 9059 deletions

View File

@ -15,7 +15,7 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
include(CTest) include(CTest)
if(BUILD_TESTING) if(BUILD_TESTING)
set(_MYSQL_TESTING_ENABLED ON) set(_MYSQL_TESTING_ENABLED ON)
endif() endif()
endif() endif()
@ -34,7 +34,7 @@ find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
if (BOOST_MYSQL_VALGRIND_TESTS) if (BOOST_MYSQL_VALGRIND_TESTS)
find_package(Mysqlvalgrind REQUIRED) find_package(Mysqlvalgrind REQUIRED)
endif() endif()
# Date # Date
@ -58,35 +58,35 @@ add_library(
target_link_libraries( target_link_libraries(
mysql_asio mysql_asio
INTERFACE INTERFACE
Boost::system Boost::system
Threads::Threads Threads::Threads
OpenSSL::Crypto OpenSSL::Crypto
OpenSSL::SSL OpenSSL::SSL
) )
target_include_directories( target_include_directories(
mysql_asio mysql_asio
INTERFACE INTERFACE
include include
${date_SOURCE_DIR}/include ${date_SOURCE_DIR}/include
) )
target_compile_features( target_compile_features(
mysql_asio mysql_asio
INTERFACE INTERFACE
cxx_std_17 cxx_std_17
) )
if (BOOST_MYSQL_VALGRIND_TESTS) if (BOOST_MYSQL_VALGRIND_TESTS)
target_link_libraries( target_link_libraries(
mysql_asio mysql_asio
INTERFACE INTERFACE
Mysqlvalgrind::Mysqlvalgrind Mysqlvalgrind::Mysqlvalgrind
) )
endif() endif()
# Examples and tests # Examples and tests
if(_MYSQL_TESTING_ENABLED) if(_MYSQL_TESTING_ENABLED)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/test_utils.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/test_utils.cmake)
add_subdirectory(example) add_subdirectory(example)
add_subdirectory(test) add_subdirectory(test)
endif() endif()

View File

@ -1,5 +1,4 @@
Sanitize Sanitize
Tabs to spaces
Bad serialize(value.value) somewhere Bad serialize(value.value) somewhere
Copy operations for handshake Copy operations for handshake
Test zero dates Test zero dates

View File

@ -12,31 +12,31 @@
cp ci/*.pem /tmp # Copy SSL certs/keys to a known location cp ci/*.pem /tmp # Copy SSL certs/keys to a known location
if [ $TRAVIS_OS_NAME == "osx" ]; then if [ $TRAVIS_OS_NAME == "osx" ]; then
brew update brew update
brew install $DATABASE brew install $DATABASE
cp ci/unix-ci.cnf ~/.my.cnf # This location is checked by both MySQL and MariaDB cp ci/unix-ci.cnf ~/.my.cnf # This location is checked by both MySQL and MariaDB
sudo mkdir -p /var/run/mysqld/ sudo mkdir -p /var/run/mysqld/
sudo chmod 777 /var/run/mysqld/ sudo chmod 777 /var/run/mysqld/
mysql.server start # Note that running this with sudo fails mysql.server start # Note that running this with sudo fails
if [ $DATABASE == "mariadb" ]; then if [ $DATABASE == "mariadb" ]; then
sudo mysql -u root < ci/root_user_setup.sql sudo mysql -u root < ci/root_user_setup.sql
else else
export BOOST_MYSQL_HAS_SHA256=1 export BOOST_MYSQL_HAS_SHA256=1
fi fi
else else
sudo cp ci/unix-ci.cnf /etc/mysql/conf.d/ sudo cp ci/unix-ci.cnf /etc/mysql/conf.d/
sudo service mysql restart sudo service mysql restart
wget https://github.com/Kitware/CMake/releases/download/v3.17.0/cmake-3.17.0-Linux-x86_64.sh -q -O cmake-latest.sh wget https://github.com/Kitware/CMake/releases/download/v3.17.0/cmake-3.17.0-Linux-x86_64.sh -q -O cmake-latest.sh
mkdir -p /tmp/cmake-latest mkdir -p /tmp/cmake-latest
bash cmake-latest.sh --prefix=/tmp/cmake-latest --skip-license bash cmake-latest.sh --prefix=/tmp/cmake-latest --skip-license
export PATH=/tmp/cmake-latest/bin:$PATH export PATH=/tmp/cmake-latest/bin:$PATH
fi fi
mkdir -p build mkdir -p build
cd build cd build
cmake -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \ cmake -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \
$(if [ $USE_VALGRIND ]; then echo -DBOOST_MYSQL_VALGRIND_TESTS=ON; fi) \ $(if [ $USE_VALGRIND ]; then echo -DBOOST_MYSQL_VALGRIND_TESTS=ON; fi) \
$CMAKE_OPTIONS \ $CMAKE_OPTIONS \
.. ..
make -j6 CTEST_OUTPUT_ON_FAILURE=1 all test make -j6 CTEST_OUTPUT_ON_FAILURE=1 all test

View File

@ -26,29 +26,29 @@ if(Mysqlvalgrind_FOUND AND NOT TARGET Mysqlvalgrind::Mysqlvalgrind)
endif() endif()
if (Mysqlvalgrind_FOUND AND NOT COMMAND MysqlValgrind_AddTest) if (Mysqlvalgrind_FOUND AND NOT COMMAND MysqlValgrind_AddTest)
function(Mysqlvalgrind_AddMemcheckTest) function(Mysqlvalgrind_AddMemcheckTest)
set(options "") set(options "")
set(oneValueArgs NAME) set(oneValueArgs NAME)
set(multiValueArgs COMMAND) set(multiValueArgs COMMAND)
cmake_parse_arguments( cmake_parse_arguments(
AddMemcheckTest AddMemcheckTest
"${options}" "${options}"
"${oneValueArgs}" "${oneValueArgs}"
"${multiValueArgs}" "${multiValueArgs}"
${ARGN} ${ARGN}
) )
add_test( add_test(
NAME ${AddMemcheckTest_NAME} NAME ${AddMemcheckTest_NAME}
COMMAND COMMAND
${Mysqlvalgrind_EXECUTABLE} ${Mysqlvalgrind_EXECUTABLE}
--leak-check=full --leak-check=full
--error-limit=yes --error-limit=yes
--suppressions=${CMAKE_SOURCE_DIR}/tools/valgrind_suppressions.txt --suppressions=${CMAKE_SOURCE_DIR}/tools/valgrind_suppressions.txt
--error-exitcode=1 --error-exitcode=1
--gen-suppressions=all --gen-suppressions=all
${AddMemcheckTest_COMMAND} ${AddMemcheckTest_COMMAND}
) )
endfunction() endfunction()
endif() endif()

View File

@ -9,31 +9,31 @@ find_package(Python3 REQUIRED)
# Adds a test fixture that consists of running a SQL file in the MySQL server # Adds a test fixture that consists of running a SQL file in the MySQL server
function (_mysql_sql_setup_fixture) function (_mysql_sql_setup_fixture)
set(options "") set(options "")
set(oneValueArgs TEST_NAME FIXTURE_NAME SQL_FILE SKIP_VAR) set(oneValueArgs TEST_NAME FIXTURE_NAME SQL_FILE SKIP_VAR)
set(multiValueArgs "") set(multiValueArgs "")
cmake_parse_arguments( cmake_parse_arguments(
SQLFIXT SQLFIXT
"${options}" "${options}"
"${oneValueArgs}" "${oneValueArgs}"
"${multiValueArgs}" "${multiValueArgs}"
${ARGN} ${ARGN}
) )
# If this env var is defined we will skip setup # If this env var is defined we will skip setup
# (just for development) (done in the Python script) # (just for development) (done in the Python script)
if (SQLFIXT_SKIP_VAR) if (SQLFIXT_SKIP_VAR)
set(ADDITIONAL_OPTIONS -s ${SQLFIXT_SKIP_VAR}) set(ADDITIONAL_OPTIONS -s ${SQLFIXT_SKIP_VAR})
endif() endif()
# Actual test # Actual test
add_test( add_test(
NAME ${SQLFIXT_TEST_NAME} NAME ${SQLFIXT_TEST_NAME}
COMMAND COMMAND
${Python3_EXECUTABLE} ${Python3_EXECUTABLE}
${CMAKE_SOURCE_DIR}/tools/run_sql.py ${CMAKE_SOURCE_DIR}/tools/run_sql.py
${SQLFIXT_SQL_FILE} ${SQLFIXT_SQL_FILE}
${ADDITIONAL_OPTIONS} ${ADDITIONAL_OPTIONS}
) )
set_tests_properties(${SQLFIXT_TEST_NAME} PROPERTIES FIXTURES_SETUP ${SQLFIXT_FIXTURE_NAME}) set_tests_properties(${SQLFIXT_TEST_NAME} PROPERTIES FIXTURES_SETUP ${SQLFIXT_FIXTURE_NAME})
endfunction() endfunction()

View File

@ -8,26 +8,26 @@
# Utility function to set warnings and other compile properties of # Utility function to set warnings and other compile properties of
# our test targets # our test targets
function(_mysql_common_target_settings TARGET_NAME) function(_mysql_common_target_settings TARGET_NAME)
if(MSVC) if(MSVC)
if (WIN32 AND CMAKE_SYSTEM_VERSION) if (WIN32 AND CMAKE_SYSTEM_VERSION)
set(WINNT_VERSION ${CMAKE_SYSTEM_VERSION}) set(WINNT_VERSION ${CMAKE_SYSTEM_VERSION})
string(REPLACE "." "" WINNT_VERSION ${WINNT_VERSION}) string(REPLACE "." "" WINNT_VERSION ${WINNT_VERSION})
string(REGEX REPLACE "([0-9])" "0\\1" WINNT_VERSION ${WINNT_VERSION}) string(REGEX REPLACE "([0-9])" "0\\1" WINNT_VERSION ${WINNT_VERSION})
set(WINNT_VERSION "0x${WINNT_VERSION}") set(WINNT_VERSION "0x${WINNT_VERSION}")
endif() endif()
target_compile_definitions( target_compile_definitions(
${TARGET_NAME} ${TARGET_NAME}
PRIVATE PRIVATE
_CRT_SECURE_NO_WARNINGS _CRT_SECURE_NO_WARNINGS
_WIN32_WINNT=${WINNT_VERSION} # Silence warnings in Windows _WIN32_WINNT=${WINNT_VERSION} # Silence warnings in Windows
_SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING # Warnings in C++17 for Asio _SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING # Warnings in C++17 for Asio
) )
# For some reason, Appveyor setup needs this to link against coroutine # For some reason, Appveyor setup needs this to link against coroutine
target_link_directories(${TARGET_NAME} PRIVATE ${Boost_LIBRARY_DIRS}) target_link_directories(${TARGET_NAME} PRIVATE ${Boost_LIBRARY_DIRS})
target_compile_options(${TARGET_NAME} PRIVATE /bigobj) # Prevent failures on Windows target_compile_options(${TARGET_NAME} PRIVATE /bigobj) # Prevent failures on Windows
else() else()
target_compile_options(${TARGET_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) target_compile_options(${TARGET_NAME} PRIVATE -Wall -Wextra -pedantic -Werror)
endif() endif()
set_target_properties(${TARGET_NAME} PROPERTIES CXX_EXTENSIONS OFF) # disable extensions set_target_properties(${TARGET_NAME} PROPERTIES CXX_EXTENSIONS OFF) # disable extensions
endfunction() endfunction()

View File

@ -9,39 +9,39 @@ find_package(Boost REQUIRED COMPONENTS coroutine)
# Compile the example # Compile the example
function (_mysql_build_example EXECUTABLE_NAME CPPFILE) function (_mysql_build_example EXECUTABLE_NAME CPPFILE)
add_executable( add_executable(
${EXECUTABLE_NAME} ${EXECUTABLE_NAME}
${CPPFILE} ${CPPFILE}
) )
target_link_libraries( target_link_libraries(
${EXECUTABLE_NAME} ${EXECUTABLE_NAME}
PRIVATE PRIVATE
mysql_asio mysql_asio
Boost::coroutine Boost::coroutine
) )
_mysql_common_target_settings(${EXECUTABLE_NAME}) _mysql_common_target_settings(${EXECUTABLE_NAME})
endfunction() endfunction()
# Run it as a test # Run it as a test
function (_mysql_test_example EXECUTABLE_NAME) function (_mysql_test_example EXECUTABLE_NAME)
set(EXECUTABLE_PATH ${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE_NAME}) set(EXECUTABLE_PATH ${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE_NAME})
set(TEST_NAME "mysql_${EXECUTABLE_NAME}") set(TEST_NAME "mysql_${EXECUTABLE_NAME}")
add_test( add_test(
NAME ${TEST_NAME} NAME ${TEST_NAME}
COMMAND ${EXECUTABLE_PATH} example_user example_password COMMAND ${EXECUTABLE_PATH} example_user example_password
) )
set_tests_properties(${TEST_NAME} PROPERTIES FIXTURES_REQUIRED mysql_examples_fixture) set_tests_properties(${TEST_NAME} PROPERTIES FIXTURES_REQUIRED mysql_examples_fixture)
endfunction() endfunction()
# Run it as a test using Valgrind # Run it as a test using Valgrind
function (_mysql_memcheck_example EXECUTABLE_NAME) function (_mysql_memcheck_example EXECUTABLE_NAME)
set(EXECUTABLE_PATH ${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE_NAME}) set(EXECUTABLE_PATH ${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE_NAME})
set(TEST_NAME "mysql_${EXECUTABLE_NAME}_memcheck") set(TEST_NAME "mysql_${EXECUTABLE_NAME}_memcheck")
Mysqlvalgrind_AddMemcheckTest( Mysqlvalgrind_AddMemcheckTest(
NAME ${TEST_NAME} NAME ${TEST_NAME}
COMMAND ${EXECUTABLE_PATH} example_user example_password COMMAND ${EXECUTABLE_PATH} example_user example_password
) )
set_tests_properties(${TEST_NAME} PROPERTIES FIXTURES_REQUIRED mysql_examples_fixture) set_tests_properties(${TEST_NAME} PROPERTIES FIXTURES_REQUIRED mysql_examples_fixture)
endfunction() endfunction()
# The list of all the examples we have # The list of all the examples we have
@ -62,9 +62,9 @@ set(MYSQL_EXAMPLES_NOMEMCHECK
# Run setup # Run setup
_mysql_sql_setup_fixture( _mysql_sql_setup_fixture(
TEST_NAME mysql_examples_setup TEST_NAME mysql_examples_setup
FIXTURE_NAME mysql_examples_fixture FIXTURE_NAME mysql_examples_fixture
SQL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/db_setup.sql SQL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/db_setup.sql
) )
# Build and run examples # Build and run examples
@ -73,10 +73,10 @@ foreach(EXAMPLE_NAME ${MYSQL_EXAMPLES})
_mysql_build_example(${EXECUTABLE_NAME} "${EXAMPLE_NAME}.cpp") _mysql_build_example(${EXECUTABLE_NAME} "${EXAMPLE_NAME}.cpp")
# Don't run examples twice. Some of them would require re-running setup # Don't run examples twice. Some of them would require re-running setup
if (BOOST_MYSQL_VALGRIND_TESTS AND NOT ${EXAMPLE_NAME} IN_LIST MYSQL_EXAMPLES_NOMEMCHECK) if (BOOST_MYSQL_VALGRIND_TESTS AND NOT ${EXAMPLE_NAME} IN_LIST MYSQL_EXAMPLES_NOMEMCHECK)
_mysql_memcheck_example(${EXECUTABLE_NAME}) _mysql_memcheck_example(${EXECUTABLE_NAME})
else() else()
_mysql_test_example(${EXECUTABLE_NAME}) _mysql_test_example(${EXECUTABLE_NAME})
endif() endif()
endforeach() endforeach()

View File

@ -15,30 +15,30 @@ USE mysql_asio_examples;
-- Tables -- Tables
CREATE TABLE company( CREATE TABLE company(
id CHAR(10) NOT NULL PRIMARY KEY, id CHAR(10) NOT NULL PRIMARY KEY,
name VARCHAR(100) NOT NULL name VARCHAR(100) NOT NULL
); );
CREATE TABLE employee( CREATE TABLE employee(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(100) NOT NULL, first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL,
salary DOUBLE, salary DOUBLE,
company_id CHAR(10) NOT NULL, company_id CHAR(10) NOT NULL,
FOREIGN KEY (company_id) REFERENCES company(id) FOREIGN KEY (company_id) REFERENCES company(id)
); );
INSERT INTO company (name, id) VALUES INSERT INTO company (name, id) VALUES
("Award Winning Company, Inc.", "AWC"), ("Award Winning Company, Inc.", "AWC"),
("Sector Global Leader Plc", "SGL"), ("Sector Global Leader Plc", "SGL"),
("High Growth Startup, Ltd", "HGS") ("High Growth Startup, Ltd", "HGS")
; ;
INSERT INTO employee (first_name, last_name, salary, company_id) VALUES INSERT INTO employee (first_name, last_name, salary, company_id) VALUES
("Efficient", "Developer", 30000, "AWC"), ("Efficient", "Developer", 30000, "AWC"),
("Lazy", "Manager", 80000, "AWC"), ("Lazy", "Manager", 80000, "AWC"),
("Good", "Team Player", 35000, "HGS"), ("Good", "Team Player", 35000, "HGS"),
("Enormous", "Slacker", 45000, "SGL"), ("Enormous", "Slacker", 45000, "SGL"),
("Coffee", "Drinker", 30000, "HGS"), ("Coffee", "Drinker", 30000, "HGS"),
("Underpaid", "Intern", 15000, "AWC") ("Underpaid", "Intern", 15000, "AWC")
; ;
-- User -- User

View File

@ -24,78 +24,78 @@
void main_impl(int argc, char** argv) void main_impl(int argc, char** argv)
{ {
if (argc != 3) if (argc != 3)
{ {
std::cerr << "Usage: " << argv[0] << " <username> <password>\n"; std::cerr << "Usage: " << argv[0] << " <username> <password>\n";
exit(1); exit(1);
} }
// Connection params (host, port, user, password, database) // Connection params (host, port, user, password, database)
boost::asio::ip::tcp::endpoint ep (boost::asio::ip::address_v4::loopback(), boost::mysql::default_port); boost::asio::ip::tcp::endpoint ep (boost::asio::ip::address_v4::loopback(), boost::mysql::default_port);
boost::mysql::connection_params params (argv[1], argv[2], "mysql_asio_examples"); boost::mysql::connection_params params (argv[1], argv[2], "mysql_asio_examples");
// TCP and MySQL level connect // TCP and MySQL level connect
boost::asio::io_context ctx; boost::asio::io_context ctx;
boost::mysql::tcp_connection conn (ctx); boost::mysql::tcp_connection conn (ctx);
conn.next_layer().connect(ep); conn.next_layer().connect(ep);
conn.handshake(params); conn.handshake(params);
// Issue the query // Issue the query
const char* sql = R"( const char* sql = R"(
SELECT comp.name AS company_name, emp.id AS employee_id SELECT comp.name AS company_name, emp.id AS employee_id
FROM employee emp FROM employee emp
JOIN company comp ON (comp.id = emp.company_id) JOIN company comp ON (comp.id = emp.company_id)
)"; )";
boost::mysql::tcp_resultset result = conn.query(sql); boost::mysql::tcp_resultset result = conn.query(sql);
/** /**
* Resultsets allow you to access metadata about the fields in the query * Resultsets allow you to access metadata about the fields in the query
* using the fields() function, which returns a vector of field_metadata * using the fields() function, which returns a vector of field_metadata
* (one per field in the query, and in the same order as in the query). * (one per field in the query, and in the same order as in the query).
* You can retrieve the field name, type, number of decimals, * You can retrieve the field name, type, number of decimals,
* suggested display width, whether the field is part of a key... * suggested display width, whether the field is part of a key...
*/ */
assert(result.fields().size() == 2); assert(result.fields().size() == 2);
[[maybe_unused]] const boost::mysql::field_metadata& company_name = result.fields()[0]; [[maybe_unused]] const boost::mysql::field_metadata& company_name = result.fields()[0];
assert(company_name.database() == "mysql_asio_examples"); // database name assert(company_name.database() == "mysql_asio_examples"); // database name
assert(company_name.table() == "comp"); // the alias we assigned to the table in the query assert(company_name.table() == "comp"); // the alias we assigned to the table in the query
assert(company_name.original_table() == "company"); // the original table name assert(company_name.original_table() == "company"); // the original table name
assert(company_name.field_name() == "company_name"); // the name of the field in the query assert(company_name.field_name() == "company_name"); // the name of the field in the query
assert(company_name.original_field_name() == "name"); // the name of the physical field in the table assert(company_name.original_field_name() == "name"); // the name of the physical field in the table
assert(company_name.type() == boost::mysql::field_type::varchar); // we created the field as a VARCHAR assert(company_name.type() == boost::mysql::field_type::varchar); // we created the field as a VARCHAR
assert(!company_name.is_primary_key()); // field is not a primary key assert(!company_name.is_primary_key()); // field is not a primary key
assert(!company_name.is_auto_increment()); // field is not AUTO_INCREMENT assert(!company_name.is_auto_increment()); // field is not AUTO_INCREMENT
assert(company_name.is_not_null()); // field may not be NULL assert(company_name.is_not_null()); // field may not be NULL
[[maybe_unused]] const boost::mysql::field_metadata& employee_id = result.fields()[1]; [[maybe_unused]] const boost::mysql::field_metadata& employee_id = result.fields()[1];
assert(employee_id.database() == "mysql_asio_examples");// database name assert(employee_id.database() == "mysql_asio_examples");// database name
assert(employee_id.table() == "emp"); // the alias we assigned to the table in the query assert(employee_id.table() == "emp"); // the alias we assigned to the table in the query
assert(employee_id.original_table() == "employee"); // the original table name assert(employee_id.original_table() == "employee"); // the original table name
assert(employee_id.field_name() == "employee_id"); // the name of the field in the query assert(employee_id.field_name() == "employee_id"); // the name of the field in the query
assert(employee_id.original_field_name() == "id"); // the name of the physical field in the table assert(employee_id.original_field_name() == "id"); // the name of the physical field in the table
assert(employee_id.type() == boost::mysql::field_type::int_); // we created the field as INT assert(employee_id.type() == boost::mysql::field_type::int_); // we created the field as INT
assert(employee_id.is_primary_key()); // field is a primary key assert(employee_id.is_primary_key()); // field is a primary key
assert(employee_id.is_auto_increment()); // we declared the field as AUTO_INCREMENT assert(employee_id.is_auto_increment()); // we declared the field as AUTO_INCREMENT
assert(employee_id.is_not_null()); // field cannot be NULL assert(employee_id.is_not_null()); // field cannot be NULL
} }
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
try try
{ {
main_impl(argc, argv); main_impl(argc, argv);
} }
catch (const boost::system::system_error& err) catch (const boost::system::system_error& err)
{ {
std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl; std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl;
return 1; return 1;
} }
catch (const std::exception& err) catch (const std::exception& err)
{ {
std::cerr << "Error: " << err.what() << std::endl; std::cerr << "Error: " << err.what() << std::endl;
return 1; return 1;
} }
} }

View File

@ -24,128 +24,128 @@
void main_impl(int argc, char** argv) void main_impl(int argc, char** argv)
{ {
if (argc != 3) if (argc != 3)
{ {
std::cerr << "Usage: " << argv[0] << " <username> <password>\n"; std::cerr << "Usage: " << argv[0] << " <username> <password>\n";
exit(1); exit(1);
} }
// Connection parameters // Connection parameters
boost::asio::ip::tcp::endpoint ep ( boost::asio::ip::tcp::endpoint ep (
boost::asio::ip::address_v4::loopback(), // host boost::asio::ip::address_v4::loopback(), // host
boost::mysql::default_port // port boost::mysql::default_port // port
); );
boost::mysql::connection_params params ( boost::mysql::connection_params params (
argv[1], // username argv[1], // username
argv[2], // password argv[2], // password
"mysql_asio_examples" // database to use; leave empty or omit the parameter for no database "mysql_asio_examples" // database to use; leave empty or omit the parameter for no database
); );
boost::asio::io_context ctx; boost::asio::io_context ctx;
// Declare the connection object and authenticate to the server // Declare the connection object and authenticate to the server
boost::mysql::tcp_connection conn (ctx); boost::mysql::tcp_connection conn (ctx);
conn.next_layer().connect(ep); // next_level() returns a boost::asio::ip::tcp::socket conn.next_layer().connect(ep); // next_level() returns a boost::asio::ip::tcp::socket
conn.handshake(params); // Authenticates to the MySQL server conn.handshake(params); // Authenticates to the MySQL server
/** /**
* We can tell MySQL to prepare a statement using connection::prepare_statement. * We can tell MySQL to prepare a statement using connection::prepare_statement.
* We provide a string SQL statement, which can include any number of parameters, * We provide a string SQL statement, which can include any number of parameters,
* identified by question marks. Parameters are optional: you can prepare a statement * identified by question marks. Parameters are optional: you can prepare a statement
* with no parameters. * with no parameters.
* *
* Prepared statements are stored in the server on a per-connection basis. * Prepared statements are stored in the server on a per-connection basis.
* Once a connection is closed, all prepared statements for that connection are deallocated. * Once a connection is closed, all prepared statements for that connection are deallocated.
* *
* The result of prepare_statement is a mysql::prepared_statement object, which is * The result of prepare_statement is a mysql::prepared_statement object, which is
* templatized on the stream type of the connection (tcp_prepared_statement in our case). * templatized on the stream type of the connection (tcp_prepared_statement in our case).
* *
* We prepare two statements, a SELECT and an UPDATE. * We prepare two statements, a SELECT and an UPDATE.
*/ */
const char* salary_getter_sql = "SELECT salary FROM employee WHERE first_name = ?"; const char* salary_getter_sql = "SELECT salary FROM employee WHERE first_name = ?";
boost::mysql::tcp_prepared_statement salary_getter = conn.prepare_statement(salary_getter_sql); boost::mysql::tcp_prepared_statement salary_getter = conn.prepare_statement(salary_getter_sql);
assert(salary_getter.num_params() == 1); // num_params() returns the number of parameters (question marks) assert(salary_getter.num_params() == 1); // num_params() returns the number of parameters (question marks)
const char* salary_updater_sql = "UPDATE employee SET salary = ? WHERE first_name = ?"; const char* salary_updater_sql = "UPDATE employee SET salary = ? WHERE first_name = ?";
boost::mysql::tcp_prepared_statement salary_updater = conn.prepare_statement(salary_updater_sql); boost::mysql::tcp_prepared_statement salary_updater = conn.prepare_statement(salary_updater_sql);
assert(salary_updater.num_params() == 2); assert(salary_updater.num_params() == 2);
/* /*
* Once a statement has been prepared, it can be executed as many times as * Once a statement has been prepared, it can be executed as many times as
* desired, by calling prepared_statement::execute(). execute takes as input a * desired, by calling prepared_statement::execute(). execute takes as input a
* (possibly empty) collection of mysql::value's and returns a resultset. * (possibly empty) collection of mysql::value's and returns a resultset.
* The returned resultset works the same as the one returned by connection::query(). * The returned resultset works the same as the one returned by connection::query().
* *
* The parameters passed to execute() are replaced in order of declaration: * The parameters passed to execute() are replaced in order of declaration:
* the first question mark will be replaced by the first passed parameter, * the first question mark will be replaced by the first passed parameter,
* the second question mark by the second parameter and so on. The number * the second question mark by the second parameter and so on. The number
* of passed parameters must match exactly the number of parameters for * of passed parameters must match exactly the number of parameters for
* the prepared statement. * the prepared statement.
* *
* Any collection providing member functions begin() and end() returning * Any collection providing member functions begin() and end() returning
* forward iterators to mysql::value's is acceptable. We use mysql::make_values(), * forward iterators to mysql::value's is acceptable. We use mysql::make_values(),
* which creates a std::array with the passed in values converted to mysql::value's. * which creates a std::array with the passed in values converted to mysql::value's.
* An iterator version of execute() is also available. * An iterator version of execute() is also available.
*/ */
boost::mysql::tcp_resultset result = salary_getter.execute(boost::mysql::make_values("Efficient")); boost::mysql::tcp_resultset result = salary_getter.execute(boost::mysql::make_values("Efficient"));
std::vector<boost::mysql::owning_row> salaries = result.fetch_all(); // Get all the results std::vector<boost::mysql::owning_row> salaries = result.fetch_all(); // Get all the results
assert(salaries.size() == 1); assert(salaries.size() == 1);
[[maybe_unused]] auto salary = std::get<double>(salaries[0].values().at(0)); // First row, first column [[maybe_unused]] auto salary = std::get<double>(salaries[0].values().at(0)); // First row, first column
assert(salary == 30000); assert(salary == 30000);
std::cout << "The salary before the payrise was: " << salary << std::endl; std::cout << "The salary before the payrise was: " << salary << std::endl;
/** /**
* Run the update. In this case, we must pass in two parameters. * Run the update. In this case, we must pass in two parameters.
* Note that MySQL is flexible in the types passed as parameters. * Note that MySQL is flexible in the types passed as parameters.
* In this case, we are sending the value 35000, which gets converted * In this case, we are sending the value 35000, which gets converted
* into a mysql::value with type std::int32_t, while the 'salary' * into a mysql::value with type std::int32_t, while the 'salary'
* column is declared as a DOUBLE. The MySQL server will do * column is declared as a DOUBLE. The MySQL server will do
* the right thing for us. * the right thing for us.
*/ */
salary_updater.execute(boost::mysql::make_values(35000, "Efficient")); salary_updater.execute(boost::mysql::make_values(35000, "Efficient"));
/** /**
* Execute the select again. We can execute a prepared statement * Execute the select again. We can execute a prepared statement
* as many times as we want. We do NOT need to call * as many times as we want. We do NOT need to call
* connection::prepare_statement() again. * connection::prepare_statement() again.
*/ */
result = salary_getter.execute(boost::mysql::make_values("Efficient")); result = salary_getter.execute(boost::mysql::make_values("Efficient"));
salaries = result.fetch_all(); salaries = result.fetch_all();
assert(salaries.size() == 1); assert(salaries.size() == 1);
salary = std::get<double>(salaries[0].values().at(0)); salary = std::get<double>(salaries[0].values().at(0));
assert(salary == 35000); // Our update took place, and the dev got his pay rise assert(salary == 35000); // Our update took place, and the dev got his pay rise
std::cout << "The salary after the payrise was: " << salary << std::endl; std::cout << "The salary after the payrise was: " << salary << std::endl;
/** /**
* Close the statements. Closing a statement deallocates it from the server. * Close the statements. Closing a statement deallocates it from the server.
* Once a statement is closed, trying to execute it will return an error. * Once a statement is closed, trying to execute it will return an error.
* *
* Closing statements implies communicating with the server and can thus fail. * Closing statements implies communicating with the server and can thus fail.
* *
* Statements are automatically deallocated once the connection is closed. * Statements are automatically deallocated once the connection is closed.
* If you are re-using connection objects and preparing statements over time, * If you are re-using connection objects and preparing statements over time,
* you should close() your statements to prevent excessive resource usage. * you should close() your statements to prevent excessive resource usage.
* If you are not re-using the connections, or are preparing your statements * If you are not re-using the connections, or are preparing your statements
* just once at application startup, there is no need to perform this step. * just once at application startup, there is no need to perform this step.
*/ */
salary_updater.close(); salary_updater.close();
salary_getter.close(); salary_getter.close();
} }
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
try try
{ {
main_impl(argc, argv); main_impl(argc, argv);
} }
catch (const boost::system::system_error& err) catch (const boost::system::system_error& err)
{ {
std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl; std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl;
return 1; return 1;
} }
catch (const std::exception& err) catch (const std::exception& err)
{ {
std::cerr << "Error: " << err.what() << std::endl; std::cerr << "Error: " << err.what() << std::endl;
return 1; return 1;
} }
} }

View File

@ -48,126 +48,126 @@ using boost::mysql::owning_row;
void print_employee(const boost::mysql::row& employee) void print_employee(const boost::mysql::row& employee)
{ {
using boost::mysql::operator<<; // Required for mysql::value objects to be streamable, due to ADL rules using boost::mysql::operator<<; // Required for mysql::value objects to be streamable, due to ADL rules
std::cout << "Employee '" std::cout << "Employee '"
<< employee.values()[0] << " " // first_name (type std::string_view) << employee.values()[0] << " " // first_name (type std::string_view)
<< employee.values()[1] << "' earns " // last_name (type std::string_view) << employee.values()[1] << "' earns " // last_name (type std::string_view)
<< employee.values()[2] << " dollars yearly\n"; // salary (type double) << employee.values()[2] << " dollars yearly\n"; // salary (type double)
} }
void die_on_error( void die_on_error(
const error_code& err, const error_code& err,
const boost::mysql::error_info& info = boost::mysql::error_info() const boost::mysql::error_info& info = boost::mysql::error_info()
) )
{ {
if (err) if (err)
{ {
std::cerr << "Error: " << err << ": " << info.message() << std::endl; std::cerr << "Error: " << err << ": " << info.message() << std::endl;
exit(1); exit(1);
} }
} }
class application class application
{ {
boost::asio::ip::tcp::endpoint ep; // Physical endpoint to connect to boost::asio::ip::tcp::endpoint ep; // Physical endpoint to connect to
boost::mysql::connection_params conn_params; // MySQL credentials and other connection config boost::mysql::connection_params conn_params; // MySQL credentials and other connection config
boost::asio::io_context ctx; // boost::asio context boost::asio::io_context ctx; // boost::asio context
boost::mysql::tcp_connection connection; // Represents the connection to the MySQL server boost::mysql::tcp_connection connection; // Represents the connection to the MySQL server
boost::mysql::tcp_resultset resultset; // A result from a query boost::mysql::tcp_resultset resultset; // A result from a query
boost::mysql::error_info additional_info; // Will be populated with additional information about any errors boost::mysql::error_info additional_info; // Will be populated with additional information about any errors
public: public:
application(const char* username, const char* password) : application(const char* username, const char* password) :
ep (boost::asio::ip::address_v4::loopback(), boost::mysql::default_port), ep (boost::asio::ip::address_v4::loopback(), boost::mysql::default_port),
conn_params(username, password, "mysql_asio_examples"), conn_params(username, password, "mysql_asio_examples"),
connection(ctx) connection(ctx)
{ {
} }
void start() { connect(); } void start() { connect(); }
void connect() void connect()
{ {
connection.next_layer().async_connect(ep, [this](error_code err) { connection.next_layer().async_connect(ep, [this](error_code err) {
die_on_error(err); die_on_error(err);
connection.async_handshake(conn_params, [this](error_code err) { connection.async_handshake(conn_params, [this](error_code err) {
die_on_error(err, additional_info); die_on_error(err, additional_info);
query_employees(); query_employees();
}, &additional_info); }, &additional_info);
}); });
} }
void query_employees() void query_employees()
{ {
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'"; const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
connection.async_query(sql, [this](error_code err, tcp_resultset&& result) { connection.async_query(sql, [this](error_code err, tcp_resultset&& result) {
die_on_error(err, additional_info); die_on_error(err, additional_info);
resultset = std::move(result); resultset = std::move(result);
resultset.async_fetch_all([this](error_code err, const std::vector<owning_row>& rows) { resultset.async_fetch_all([this](error_code err, const std::vector<owning_row>& rows) {
die_on_error(err, additional_info); die_on_error(err, additional_info);
for (const auto& employee: rows) for (const auto& employee: rows)
{ {
print_employee(employee); print_employee(employee);
} }
update_slacker(); update_slacker();
}, &additional_info); }, &additional_info);
}, &additional_info); }, &additional_info);
} }
void update_slacker() void update_slacker()
{ {
const char* sql = "UPDATE employee SET salary = 15000 WHERE last_name = 'Slacker'"; const char* sql = "UPDATE employee SET salary = 15000 WHERE last_name = 'Slacker'";
connection.async_query(sql, [this](error_code err, [[maybe_unused]] tcp_resultset&& result) { connection.async_query(sql, [this](error_code err, [[maybe_unused]] tcp_resultset&& result) {
die_on_error(err, additional_info); die_on_error(err, additional_info);
assert(result.fields().size() == 0); assert(result.fields().size() == 0);
query_intern(); query_intern();
}, &additional_info); }, &additional_info);
} }
void query_intern() void query_intern()
{ {
const char* sql = "SELECT salary FROM employee WHERE last_name = 'Slacker'"; const char* sql = "SELECT salary FROM employee WHERE last_name = 'Slacker'";
connection.async_query(sql, [this](error_code err, tcp_resultset&& result) { connection.async_query(sql, [this](error_code err, tcp_resultset&& result) {
die_on_error(err, additional_info); die_on_error(err, additional_info);
resultset = std::move(result); resultset = std::move(result);
resultset.async_fetch_all([this](error_code err, const std::vector<owning_row>& rows) { resultset.async_fetch_all([this](error_code err, const std::vector<owning_row>& rows) {
die_on_error(err, additional_info); die_on_error(err, additional_info);
assert(rows.size() == 1); assert(rows.size() == 1);
[[maybe_unused]] auto salary = std::get<double>(rows[0].values()[0]); [[maybe_unused]] auto salary = std::get<double>(rows[0].values()[0]);
assert(salary == 15000); assert(salary == 15000);
}, &additional_info); }, &additional_info);
}, &additional_info); }, &additional_info);
} }
auto& context() { return ctx; } auto& context() { return ctx; }
}; };
void main_impl(int argc, char** argv) void main_impl(int argc, char** argv)
{ {
if (argc != 3) if (argc != 3)
{ {
std::cerr << "Usage: " << argv[0] << " <username> <password>\n"; std::cerr << "Usage: " << argv[0] << " <username> <password>\n";
exit(1); exit(1);
} }
application app (argv[1], argv[2]); application app (argv[1], argv[2]);
app.start(); // starts the async chain app.start(); // starts the async chain
app.context().run(); // run the asio::io_context until the async chain finishes app.context().run(); // run the asio::io_context until the async chain finishes
} }
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
try try
{ {
main_impl(argc, argv); main_impl(argc, argv);
} }
catch (const boost::system::system_error& err) catch (const boost::system::system_error& err)
{ {
std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl; std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl;
return 1; return 1;
} }
catch (const std::exception& err) catch (const std::exception& err)
{ {
std::cerr << "Error: " << err.what() << std::endl; std::cerr << "Error: " << err.what() << std::endl;
return 1; return 1;
} }
} }

View File

@ -45,117 +45,117 @@ using boost::mysql::error_info;
void print_employee(const boost::mysql::row& employee) void print_employee(const boost::mysql::row& employee)
{ {
using boost::mysql::operator<<; // Required for mysql::value objects to be streamable, due to ADL rules using boost::mysql::operator<<; // Required for mysql::value objects to be streamable, due to ADL rules
std::cout << "Employee '" std::cout << "Employee '"
<< employee.values()[0] << " " // first_name (type std::string_view) << employee.values()[0] << " " // first_name (type std::string_view)
<< employee.values()[1] << "' earns " // last_name (type std::string_view) << employee.values()[1] << "' earns " // last_name (type std::string_view)
<< employee.values()[2] << " dollars yearly\n"; // salary (type double) << employee.values()[2] << " dollars yearly\n"; // salary (type double)
} }
// Throws an exception if an operation failed // Throws an exception if an operation failed
void check_error( void check_error(
const error_code& err, const error_code& err,
const error_info& info = {} const error_info& info = {}
) )
{ {
if (err) if (err)
{ {
throw boost::system::system_error(err, info.message()); throw boost::system::system_error(err, info.message());
} }
} }
void main_impl(int argc, char** argv) void main_impl(int argc, char** argv)
{ {
if (argc != 3) if (argc != 3)
{ {
std::cerr << "Usage: " << argv[0] << " <username> <password>\n"; std::cerr << "Usage: " << argv[0] << " <username> <password>\n";
exit(1); exit(1);
} }
// Context and connections // Context and connections
boost::asio::io_context ctx; boost::asio::io_context ctx;
boost::mysql::tcp_connection conn (ctx); boost::mysql::tcp_connection conn (ctx);
boost::asio::ip::tcp::endpoint ep ( boost::asio::ip::tcp::endpoint ep (
boost::asio::ip::address_v4::loopback(), // host boost::asio::ip::address_v4::loopback(), // host
boost::mysql::default_port // port boost::mysql::default_port // port
); );
boost::mysql::connection_params params ( boost::mysql::connection_params params (
argv[1], // username argv[1], // username
argv[2], // password argv[2], // password
"mysql_asio_examples" // database to use; leave empty or omit the parameter for no database "mysql_asio_examples" // database to use; leave empty or omit the parameter for no database
); );
/** /**
* The entry point. We spawn a stackful coroutine using boost::asio::spawn * The entry point. We spawn a stackful coroutine using boost::asio::spawn
* (see https://www.boost.org/doc/libs/1_72_0/doc/html/boost_asio/reference/spawn.html). * (see https://www.boost.org/doc/libs/1_72_0/doc/html/boost_asio/reference/spawn.html).
* *
* The coroutine will actually start running when we call io_context::run(). * The coroutine will actually start running when we call io_context::run().
* It will suspend every time we call one of the asyncrhonous functions, saving * It will suspend every time we call one of the asyncrhonous functions, saving
* all information it needs for resuming. When the asynchronous operation completes, * all information it needs for resuming. When the asynchronous operation completes,
* the coroutine will resume in the point it was left. * the coroutine will resume in the point it was left.
* *
* The return type of a coroutine is the second argument to the handler signature * The return type of a coroutine is the second argument to the handler signature
* for the asynchronous operation. For example, connection::query has a handler * for the asynchronous operation. For example, connection::query has a handler
* signature of void(error_code, resultset<Stream>), so the coroutine return * signature of void(error_code, resultset<Stream>), so the coroutine return
* type is resultset<Stream>. * type is resultset<Stream>.
* *
*/ */
boost::asio::spawn(ctx.get_executor(), [&conn, ep, params](boost::asio::yield_context yield) { boost::asio::spawn(ctx.get_executor(), [&conn, ep, params](boost::asio::yield_context yield) {
// This error_code and error_info will be filled if an // This error_code and error_info will be filled if an
// operation fails. We will check them for every operation we perform. // operation fails. We will check them for every operation we perform.
boost::mysql::error_code ec; boost::mysql::error_code ec;
boost::mysql::error_info additional_info; boost::mysql::error_info additional_info;
// TCP connect // TCP connect
conn.next_layer().async_connect(ep, yield[ec]); conn.next_layer().async_connect(ep, yield[ec]);
check_error(ec); check_error(ec);
// MySQL handshake // MySQL handshake
conn.async_handshake(params, yield[ec], &additional_info); conn.async_handshake(params, yield[ec], &additional_info);
check_error(ec, additional_info); check_error(ec, additional_info);
// Issue the query to the server // Issue the query to the server
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'"; const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
boost::mysql::tcp_resultset result = conn.async_query(sql, yield[ec], &additional_info); boost::mysql::tcp_resultset result = conn.async_query(sql, yield[ec], &additional_info);
check_error(ec, additional_info); check_error(ec, additional_info);
/** /**
* Get all rows in the resultset. We will employ resultset::async_fetch_one(), * 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 * 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 * 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 remain valid until the next call to async_fetch_one(). When no more
* rows are available, async_fetch_one returns nullptr. * rows are available, async_fetch_one returns nullptr.
*/ */
while (true) while (true)
{ {
const boost::mysql::row* row = result.async_fetch_one(yield[ec], &additional_info); const boost::mysql::row* row = result.async_fetch_one(yield[ec], &additional_info);
check_error(ec, additional_info); check_error(ec, additional_info);
if (!row) break; // No more rows available if (!row) break; // No more rows available
print_employee(*row); print_employee(*row);
} }
}); });
// Don't forget to call run()! Otherwise, your program // Don't forget to call run()! Otherwise, your program
// will not spawn the coroutine and will do nothing. // will not spawn the coroutine and will do nothing.
ctx.run(); ctx.run();
} }
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
try try
{ {
main_impl(argc, argv); main_impl(argc, argv);
} }
catch (const boost::system::system_error& err) catch (const boost::system::system_error& err)
{ {
std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl; std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl;
return 1; return 1;
} }
catch (const std::exception& err) catch (const std::exception& err)
{ {
std::cerr << "Error: " << err.what() << std::endl; std::cerr << "Error: " << err.what() << std::endl;
return 1; return 1;
} }
} }

View File

@ -46,11 +46,11 @@ using boost::asio::use_future;
void print_employee(const boost::mysql::row& employee) void print_employee(const boost::mysql::row& employee)
{ {
using boost::mysql::operator<<; // Required for mysql::value objects to be streamable, due to ADL rules using boost::mysql::operator<<; // Required for mysql::value objects to be streamable, due to ADL rules
std::cout << "Employee '" std::cout << "Employee '"
<< employee.values()[0] << " " // first_name (type std::string_view) << employee.values()[0] << " " // first_name (type std::string_view)
<< employee.values()[1] << "' earns " // last_name (type std::string_view) << employee.values()[1] << "' earns " // last_name (type std::string_view)
<< employee.values()[2] << " dollars yearly\n"; // salary (type double) << employee.values()[2] << " dollars yearly\n"; // salary (type double)
} }
/** /**
@ -63,92 +63,92 @@ void print_employee(const boost::mysql::row& employee)
*/ */
class application class application
{ {
boost::asio::io_context ctx_; boost::asio::io_context ctx_;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> guard_; boost::asio::executor_work_guard<boost::asio::io_context::executor_type> guard_;
std::thread runner_; std::thread runner_;
public: public:
application(): guard_(ctx_.get_executor()), runner_([this] { ctx_.run(); }) {} application(): guard_(ctx_.get_executor()), runner_([this] { ctx_.run(); }) {}
application(const application&) = delete; application(const application&) = delete;
application(application&&) = delete; application(application&&) = delete;
application& operator=(const application&) = delete; application& operator=(const application&) = delete;
application& operator=(application&&) = delete; application& operator=(application&&) = delete;
~application() ~application()
{ {
guard_.reset(); guard_.reset();
runner_.join(); runner_.join();
} }
boost::asio::io_context& context() { return ctx_; } boost::asio::io_context& context() { return ctx_; }
}; };
void main_impl(int argc, char** argv) void main_impl(int argc, char** argv)
{ {
if (argc != 3) if (argc != 3)
{ {
std::cerr << "Usage: " << argv[0] << " <username> <password>\n"; std::cerr << "Usage: " << argv[0] << " <username> <password>\n";
exit(1); exit(1);
} }
// Context and connections // Context and connections
application app; // boost::asio::io_context and a thread that calls run() application app; // boost::asio::io_context and a thread that calls run()
boost::mysql::tcp_connection conn (app.context()); boost::mysql::tcp_connection conn (app.context());
boost::asio::ip::tcp::endpoint ep ( boost::asio::ip::tcp::endpoint ep (
boost::asio::ip::address_v4::loopback(), // host boost::asio::ip::address_v4::loopback(), // host
boost::mysql::default_port // port boost::mysql::default_port // port
); );
boost::mysql::connection_params params ( boost::mysql::connection_params params (
argv[1], // username argv[1], // username
argv[2], // password argv[2], // password
"mysql_asio_examples" // database to use; leave empty or omit the parameter for no database "mysql_asio_examples" // database to use; leave empty or omit the parameter for no database
); );
// TCP connect // TCP connect
std::future<void> fut = conn.next_layer().async_connect(ep, use_future); std::future<void> fut = conn.next_layer().async_connect(ep, use_future);
fut.get(); fut.get();
/** /**
* Perform the MySQL handshake. Calling async_handshake triggers the * Perform the MySQL handshake. Calling async_handshake triggers the
* operation, and calling future::get() blocks the current thread until * operation, and calling future::get() blocks the current thread until
* it completes. get() will throw an exception if the operation fails. * it completes. get() will throw an exception if the operation fails.
*/ */
fut = conn.async_handshake(params, use_future); fut = conn.async_handshake(params, use_future);
fut.get(); fut.get();
// Issue the query to the server // Issue the query to the server
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'"; const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
std::future<boost::mysql::tcp_resultset> resultset_fut = conn.async_query(sql, use_future); std::future<boost::mysql::tcp_resultset> resultset_fut = conn.async_query(sql, use_future);
boost::mysql::tcp_resultset result = resultset_fut.get(); boost::mysql::tcp_resultset result = resultset_fut.get();
/** /**
* Get all rows in the resultset. We will employ resultset::async_fetch_one(), * 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 * 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 * 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 remain valid until the next call to async_fetch_one(). When no more
* rows are available, async_fetch_one returns nullptr. * rows are available, async_fetch_one returns nullptr.
*/ */
while (const boost::mysql::row* current_row = result.async_fetch_one(use_future).get()) while (const boost::mysql::row* current_row = result.async_fetch_one(use_future).get())
{ {
print_employee(*current_row); print_employee(*current_row);
} }
// application dtor. stops io_context and then joins the thread // application dtor. stops io_context and then joins the thread
} }
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
try try
{ {
main_impl(argc, argv); main_impl(argc, argv);
} }
catch (const boost::system::system_error& err) catch (const boost::system::system_error& err)
{ {
std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl; std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl;
return 1; return 1;
} }
catch (const std::exception& err) catch (const std::exception& err)
{ {
std::cerr << "Error: " << err.what() << std::endl; std::cerr << "Error: " << err.what() << std::endl;
return 1; return 1;
} }
} }

View File

@ -31,102 +31,102 @@
*/ */
void print_employee(const boost::mysql::row& employee) void print_employee(const boost::mysql::row& employee)
{ {
using boost::mysql::operator<<; // Required for mysql::value objects to be streamable, due to ADL rules using boost::mysql::operator<<; // Required for mysql::value objects to be streamable, due to ADL rules
std::cout << "Employee '" std::cout << "Employee '"
<< employee.values()[0] << " " // first_name (type std::string_view) << employee.values()[0] << " " // first_name (type std::string_view)
<< employee.values()[1] << "' earns " // last_name (type std::string_view) << employee.values()[1] << "' earns " // last_name (type std::string_view)
<< employee.values()[2] << " dollars yearly\n"; // salary (type double) << employee.values()[2] << " dollars yearly\n"; // salary (type double)
} }
void main_impl(int argc, char** argv) void main_impl(int argc, char** argv)
{ {
if (argc != 3) if (argc != 3)
{ {
std::cerr << "Usage: " << argv[0] << " <username> <password>\n"; std::cerr << "Usage: " << argv[0] << " <username> <password>\n";
exit(1); exit(1);
} }
/** /**
* Connection parameters that tell us where and how to connect to the MySQL server. * Connection parameters that tell us where and how to connect to the MySQL server.
* There are two types of parameters: * There are two types of parameters:
* - TCP-level connection parameters, identifying the host and port to connect to. * - TCP-level connection parameters, identifying the host and port to connect to.
* - MySQL level parameters: database credentials and schema to use. * - MySQL level parameters: database credentials and schema to use.
*/ */
boost::asio::ip::tcp::endpoint ep ( boost::asio::ip::tcp::endpoint ep (
boost::asio::ip::address_v4::loopback(), // host boost::asio::ip::address_v4::loopback(), // host
boost::mysql::default_port // port boost::mysql::default_port // port
); );
boost::mysql::connection_params params ( boost::mysql::connection_params params (
argv[1], // username argv[1], // username
argv[2], // password argv[2], // password
"mysql_asio_examples" // database to use; leave empty or omit the parameter for no database "mysql_asio_examples" // database to use; leave empty or omit the parameter for no database
); );
// Note: by default, SSL will be used if the server supports it. // Note: by default, SSL will be used if the server supports it.
// connection_params accepts an optional ssl_options argument // connection_params accepts an optional ssl_options argument
// determining whether to use SSL or not. See ssl_options and ssl_mode // determining whether to use SSL or not. See ssl_options and ssl_mode
// documentation for further details on SSL. // documentation for further details on SSL.
boost::asio::io_context ctx; boost::asio::io_context ctx;
/** /**
* Represents a single connection over TCP to a MySQL server. * Represents a single connection over TCP to a MySQL server.
* Before being able to use it, you have to connect to the server by: * Before being able to use it, you have to connect to the server by:
* - Establishing the TCP-level session. * - Establishing the TCP-level session.
* - Authenticating to the MySQL server. * - Authenticating to the MySQL server.
*/ */
boost::mysql::tcp_connection conn (ctx); boost::mysql::tcp_connection conn (ctx);
conn.next_layer().connect(ep); // next_level() returns a boost::asio::ip::tcp::socket conn.next_layer().connect(ep); // next_level() returns a boost::asio::ip::tcp::socket
conn.handshake(params); // Authenticates to the MySQL server conn.handshake(params); // Authenticates to the MySQL server
/** /**
* To issue a SQL query to the database server, use tcp_connection::query, which takes * To issue a SQL query to the database server, use tcp_connection::query, which takes
* the SQL to be executed as parameter and returns a resultset object. * the SQL to be executed as parameter and returns a resultset object.
* *
* Resultset objects represent the result of a query, in tabular format. * 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, * 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. * 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. * We will use fetch_all, which returns all the received rows as a std::vector.
* *
* We will get all employees working for 'High Growth Startup'. * We will get all employees working for 'High Growth Startup'.
*/ */
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'"; const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
boost::mysql::tcp_resultset result = conn.query(sql); boost::mysql::tcp_resultset result = conn.query(sql);
// Get all the rows in the resultset // Get all the rows in the resultset
std::vector<boost::mysql::owning_row> employees = result.fetch_all(); std::vector<boost::mysql::owning_row> employees = result.fetch_all();
for (const auto& employee: employees) for (const auto& employee: employees)
{ {
print_employee(employee); print_employee(employee);
} }
// We can issue any SQL statement, not only SELECTs. In this case, the returned // We can issue any SQL statement, not only SELECTs. In this case, the returned
// resultset will have no fields and no rows // resultset will have no fields and no rows
sql = "UPDATE employee SET salary = 10000 WHERE first_name = 'Underpaid'"; sql = "UPDATE employee SET salary = 10000 WHERE first_name = 'Underpaid'";
result = conn.query(sql); result = conn.query(sql);
assert(result.fields().size() == 0); // fields() returns a vector containing metadata about the query fields assert(result.fields().size() == 0); // fields() returns a vector containing metadata about the query fields
// Check we have updated our poor intern salary // Check we have updated our poor intern salary
result = conn.query("SELECT salary FROM employee WHERE first_name = 'Underpaid'"); result = conn.query("SELECT salary FROM employee WHERE first_name = 'Underpaid'");
auto rows = result.fetch_all(); auto rows = result.fetch_all();
assert(rows.size() == 1); assert(rows.size() == 1);
[[maybe_unused]] auto salary = std::get<double>(rows[0].values()[0]); [[maybe_unused]] auto salary = std::get<double>(rows[0].values()[0]);
assert(salary == 10000); assert(salary == 10000);
} }
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
try try
{ {
main_impl(argc, argv); main_impl(argc, argv);
} }
catch (const boost::system::system_error& err) catch (const boost::system::system_error& err)
{ {
std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl; std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl;
return 1; return 1;
} }
catch (const std::exception& err) catch (const std::exception& err)
{ {
std::cerr << "Error: " << err.what() << std::endl; std::cerr << "Error: " << err.what() << std::endl;
return 1; return 1;
} }
} }

View File

@ -22,11 +22,11 @@
void print_employee(const boost::mysql::row& employee) void print_employee(const boost::mysql::row& employee)
{ {
using boost::mysql::operator<<; // Required for mysql::value objects to be streamable, due to ADL rules using boost::mysql::operator<<; // Required for mysql::value objects to be streamable, due to ADL rules
std::cout << "Employee '" std::cout << "Employee '"
<< employee.values()[0] << " " // first_name (type std::string_view) << employee.values()[0] << " " // first_name (type std::string_view)
<< employee.values()[1] << "' earns " // last_name (type std::string_view) << employee.values()[1] << "' earns " // last_name (type std::string_view)
<< employee.values()[2] << " dollars yearly\n"; // salary (type double) << employee.values()[2] << " dollars yearly\n"; // salary (type double)
} }
// UNIX sockets are only available in, er, UNIX systems. Typedefs for // UNIX sockets are only available in, er, UNIX systems. Typedefs for
@ -37,90 +37,90 @@ void print_employee(const boost::mysql::row& employee)
void main_impl(int argc, char** argv) void main_impl(int argc, char** argv)
{ {
if (argc != 3 && argc != 4) if (argc != 3 && argc != 4)
{ {
std::cerr << "Usage: " << argv[0] << " <username> <password> [<socket-path>]\n"; std::cerr << "Usage: " << argv[0] << " <username> <password> [<socket-path>]\n";
exit(1); exit(1);
} }
const char* socket_path = "/var/run/mysqld/mysqld.sock"; const char* socket_path = "/var/run/mysqld/mysqld.sock";
if (argc == 4) if (argc == 4)
{ {
socket_path = argv[3]; socket_path = argv[3];
} }
/** /**
* Connection parameters that tell us where and how to connect to the MySQL server. * Connection parameters that tell us where and how to connect to the MySQL server.
* There are two types of parameters: * There are two types of parameters:
* - UNIX-level connection parameters, identifying the UNIX socket to connect to. * - UNIX-level connection parameters, identifying the UNIX socket to connect to.
* - MySQL level parameters: database credentials and schema to use. * - MySQL level parameters: database credentials and schema to use.
*/ */
boost::asio::local::stream_protocol::endpoint ep (socket_path); boost::asio::local::stream_protocol::endpoint ep (socket_path);
boost::mysql::connection_params params ( boost::mysql::connection_params params (
argv[1], // username argv[1], // username
argv[2], // password argv[2], // password
"mysql_asio_examples" // database to use; leave empty or omit the parameter for no database "mysql_asio_examples" // database to use; leave empty or omit the parameter for no database
); );
// Note: by default, SSL will be used if the server supports it. // Note: by default, SSL will be used if the server supports it.
// connection_params accepts an optional ssl_options argument // connection_params accepts an optional ssl_options argument
// determining whether to use SSL or not. See ssl_options and ssl_mode // determining whether to use SSL or not. See ssl_options and ssl_mode
// documentation for further details on SSL. // documentation for further details on SSL.
boost::asio::io_context ctx; boost::asio::io_context ctx;
// Connection to the MySQL server, over a UNIX socket // Connection to the MySQL server, over a UNIX socket
boost::mysql::unix_connection conn (ctx); boost::mysql::unix_connection conn (ctx);
conn.next_layer().connect(ep); // next_level() returns a boost::asio::local::stream_protocol::socket conn.next_layer().connect(ep); // next_level() returns a boost::asio::local::stream_protocol::socket
conn.handshake(params); // Authenticates to the MySQL server conn.handshake(params); // Authenticates to the MySQL server
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'"; const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
boost::mysql::unix_resultset result = conn.query(sql); boost::mysql::unix_resultset result = conn.query(sql);
// Get all the rows in the resultset // Get all the rows in the resultset
std::vector<boost::mysql::owning_row> employees = result.fetch_all(); std::vector<boost::mysql::owning_row> employees = result.fetch_all();
for (const auto& employee: employees) for (const auto& employee: employees)
{ {
print_employee(employee); print_employee(employee);
} }
// We can issue any SQL statement, not only SELECTs. In this case, the returned // We can issue any SQL statement, not only SELECTs. In this case, the returned
// resultset will have no fields and no rows // resultset will have no fields and no rows
sql = "UPDATE employee SET salary = 10000 WHERE first_name = 'Underpaid'"; sql = "UPDATE employee SET salary = 10000 WHERE first_name = 'Underpaid'";
result = conn.query(sql); result = conn.query(sql);
assert(result.fields().size() == 0); // fields() returns a vector containing metadata about the query fields assert(result.fields().size() == 0); // fields() returns a vector containing metadata about the query fields
// Check we have updated our poor intern salary // Check we have updated our poor intern salary
result = conn.query("SELECT salary FROM employee WHERE first_name = 'Underpaid'"); result = conn.query("SELECT salary FROM employee WHERE first_name = 'Underpaid'");
auto rows = result.fetch_all(); auto rows = result.fetch_all();
assert(rows.size() == 1); assert(rows.size() == 1);
[[maybe_unused]] auto salary = std::get<double>(rows[0].values()[0]); [[maybe_unused]] auto salary = std::get<double>(rows[0].values()[0]);
assert(salary == 10000); assert(salary == 10000);
} }
#else #else
void main_impl(int, char**) void main_impl(int, char**)
{ {
std::cout << "Sorry, your system does not support UNIX sockets" << std::endl; std::cout << "Sorry, your system does not support UNIX sockets" << std::endl;
} }
#endif #endif
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
try try
{ {
main_impl(argc, argv); main_impl(argc, argv);
} }
catch (const boost::system::system_error& err) catch (const boost::system::system_error& err)
{ {
std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl; std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl;
return 1; return 1;
} }
catch (const std::exception& err) catch (const std::exception& err)
{ {
std::cerr << "Error: " << err.what() << std::endl; std::cerr << "Error: " << err.what() << std::endl;
return 1; return 1;
} }
} }

View File

@ -57,112 +57,112 @@ namespace mysql {
* Otherwise, results are undefined. * Otherwise, results are undefined.
*/ */
template < template <
typename Stream ///< The underlying stream to use; must satisfy Boost.Asio's SyncStream and AsyncStream concepts. typename Stream ///< The underlying stream to use; must satisfy Boost.Asio's SyncStream and AsyncStream concepts.
> >
class connection class connection
{ {
using channel_type = detail::channel<Stream>; using channel_type = detail::channel<Stream>;
Stream next_layer_; Stream next_layer_;
channel_type channel_; channel_type channel_;
public: public:
/** /**
* \brief Initializing constructor. * \brief Initializing constructor.
* \details Creates a Stream object by forwarding any passed in arguments to its constructor. * \details Creates a Stream object by forwarding any passed in arguments to its constructor.
*/ */
template <typename... Args> template <typename... Args>
connection(Args&&... args) : connection(Args&&... args) :
next_layer_(std::forward<Args>(args)...), next_layer_(std::forward<Args>(args)...),
channel_(next_layer_) channel_(next_layer_)
{ {
} }
/// Retrieves the underlying Stream object. /// Retrieves the underlying Stream object.
Stream& next_layer() { return next_layer_; } Stream& next_layer() { return next_layer_; }
/// Retrieves the underlying Stream object. /// Retrieves the underlying Stream object.
const Stream& next_layer() const { return next_layer_; } const Stream& next_layer() const { return next_layer_; }
/** /**
* \brief Returns whether the connection uses SSL or not. * \brief Returns whether the connection uses SSL or not.
* \details Will always return false for connections that haven't been * \details Will always return false for connections that haven't been
* established yet (handshake not run yet). If the handshake fails, * established yet (handshake not run yet). If the handshake fails,
* the return value is undefined. * the return value is undefined.
* *
* This function can be used to determine * This function can be used to determine
* whether you are using a SSL connection or not when using * whether you are using a SSL connection or not when using
* optional SSL (ssl_mode::enable). * optional SSL (ssl_mode::enable).
*/ */
bool uses_ssl() const noexcept { return channel_.ssl_active(); } bool uses_ssl() const noexcept { return channel_.ssl_active(); }
/// Performs the MySQL-level handshake (synchronous with error code version). /// Performs the MySQL-level handshake (synchronous with error code version).
void handshake(const connection_params& params, error_code& ec, error_info& info); void handshake(const connection_params& params, error_code& ec, error_info& info);
/// Performs the MySQL-level handshake (synchronous with exceptions version). /// Performs the MySQL-level handshake (synchronous with exceptions version).
void handshake(const connection_params& params); void handshake(const connection_params& params);
/// Handler signature for handshake. /// Handler signature for handshake.
using handshake_signature = void(error_code); using handshake_signature = void(error_code);
/** /**
* \brief Performs the MySQL-level handshake (asynchronous version). * \brief Performs the MySQL-level handshake (asynchronous version).
* \details The strings pointed to by params should be kept alive by the caller * \details The strings pointed to by params should be kept alive by the caller
* until the operation completes, as no copy is made by the library. * until the operation completes, as no copy is made by the library.
*/ */
template <typename CompletionToken> template <typename CompletionToken>
BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, handshake_signature) BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, handshake_signature)
async_handshake(const connection_params& params, CompletionToken&& token, error_info* info = nullptr); async_handshake(const connection_params& params, CompletionToken&& token, error_info* info = nullptr);
/** /**
* \brief Executes a SQL text query (sync with error code version). * \brief Executes a SQL text query (sync with error code version).
* \details Does not perform the actual retrieval of the data; use the various * \details Does not perform the actual retrieval of the data; use the various
* fetch functions within resultset to achieve that. * fetch functions within resultset to achieve that.
* \see resultset * \see resultset
* *
* Note that query_string may contain any valid SQL, not just SELECT statements. * Note that query_string may contain any valid SQL, not just SELECT statements.
* If your query does not return any data, then the resultset will be empty. * If your query does not return any data, then the resultset will be empty.
* *
* \warning After query() has returned, you should read the entire resultset * \warning After query() has returned, you should read the entire resultset
* before calling any function that involves communication with the server over this * before calling any function that involves communication with the server over this
* connection (like connection::query, connection::prepare_statement or * connection (like connection::query, connection::prepare_statement or
* prepared_statement::execute). Otherwise, the results are undefined. * prepared_statement::execute). Otherwise, the results are undefined.
*/ */
resultset<Stream> query(std::string_view query_string, error_code&, error_info&); resultset<Stream> query(std::string_view query_string, error_code&, error_info&);
/// Executes a SQL text query (sync with exceptions version). /// Executes a SQL text query (sync with exceptions version).
resultset<Stream> query(std::string_view query_string); resultset<Stream> query(std::string_view query_string);
/// Handler signature for query. /// Handler signature for query.
using query_signature = void(error_code, resultset<Stream>); using query_signature = void(error_code, resultset<Stream>);
/// Executes a SQL text query (async version). /// Executes a SQL text query (async version).
template <typename CompletionToken> template <typename CompletionToken>
BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, query_signature) BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, query_signature)
async_query(std::string_view query_string, CompletionToken&& token, error_info* info=nullptr); async_query(std::string_view query_string, CompletionToken&& token, error_info* info=nullptr);
/** /**
* \brief Prepares a statement in the server (sync with error code version). * \brief Prepares a statement in the server (sync with error code version).
* \details Instructs the server to create a prepared statement. The passed * \details Instructs the server to create a prepared statement. The passed
* in statement should be a SQL statement with question marks (?) as placeholders * in statement should be a SQL statement with question marks (?) as placeholders
* for the statement parameters. See the MySQL documentation on prepared statements * for the statement parameters. See the MySQL documentation on prepared statements
* for more info. * for more info.
* *
* Prepared statements are only valid while the connection object on which * Prepared statements are only valid while the connection object on which
* prepare_statement was called is alive and open. See prepared_statement docs * prepare_statement was called is alive and open. See prepared_statement docs
* for more info. * for more info.
*/ */
prepared_statement<Stream> prepare_statement(std::string_view statement, error_code&, error_info&); prepared_statement<Stream> prepare_statement(std::string_view statement, error_code&, error_info&);
/// Prepares a statement (sync with exceptions version). /// Prepares a statement (sync with exceptions version).
prepared_statement<Stream> prepare_statement(std::string_view statement); prepared_statement<Stream> prepare_statement(std::string_view statement);
/// Handler signature for prepare_statement. /// Handler signature for prepare_statement.
using prepare_statement_signature = void(error_code, prepared_statement<Stream>); using prepare_statement_signature = void(error_code, prepared_statement<Stream>);
/// Prepares a statement (async version). /// Prepares a statement (async version).
template <typename CompletionToken> template <typename CompletionToken>
BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, prepare_statement_signature) BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, prepare_statement_signature)
async_prepare_statement(std::string_view statement, CompletionToken&& token, error_info* info=nullptr); async_prepare_statement(std::string_view statement, CompletionToken&& token, error_info* info=nullptr);
}; };
/** /**

View File

@ -26,9 +26,9 @@ namespace mysql {
*/ */
enum class ssl_mode enum class ssl_mode
{ {
disable, ///< Never use TLS disable, ///< Never use TLS
enable, ///< Use TLS if the server supports it, fall back to non-encrypted connection if it does not. enable, ///< Use TLS if the server supports it, fall back to non-encrypted connection if it does not.
require ///< Always use TLS; abort the connection if the server does not support it. require ///< Always use TLS; abort the connection if the server does not support it.
}; };
/** /**
@ -39,18 +39,18 @@ enum class ssl_mode
*/ */
class ssl_options class ssl_options
{ {
ssl_mode mode_; ssl_mode mode_;
public: public:
/** /**
* \brief Default and initialization constructor. * \brief Default and initialization constructor.
* \details By default, SSL is enabled for the connection * \details By default, SSL is enabled for the connection
* if the server supports is (ssl_mode::enable). * if the server supports is (ssl_mode::enable).
*/ */
explicit ssl_options(ssl_mode mode=ssl_mode::enable) noexcept: explicit ssl_options(ssl_mode mode=ssl_mode::enable) noexcept:
mode_(mode) {} mode_(mode) {}
/// Retrieves the TLS mode to be used for the connection. /// Retrieves the TLS mode to be used for the connection.
ssl_mode mode() const noexcept { return mode_; } ssl_mode mode() const noexcept { return mode_; }
}; };
@ -60,57 +60,57 @@ public:
*/ */
class connection_params class connection_params
{ {
std::string_view username_; std::string_view username_;
std::string_view password_; std::string_view password_;
std::string_view database_; std::string_view database_;
collation connection_collation_; collation connection_collation_;
ssl_options ssl_; ssl_options ssl_;
public: public:
/// Initializing constructor /// Initializing constructor
connection_params( connection_params(
std::string_view username, ///< Username to authenticate as std::string_view username, ///< Username to authenticate as
std::string_view password, ///< Password for that username, possibly empty. std::string_view password, ///< Password for that username, possibly empty.
std::string_view db = "", ///< Database to use, or empty string for no database. std::string_view db = "", ///< Database to use, or empty string for no database.
collation connection_col = collation::utf8_general_ci, ///< The default character set and collation for the connection. collation connection_col = collation::utf8_general_ci, ///< The default character set and collation for the connection.
const ssl_options& opts = ssl_options() ///< The TLS options to use with this connection. const ssl_options& opts = ssl_options() ///< The TLS options to use with this connection.
) : ) :
username_(username), username_(username),
password_(password), password_(password),
database_(db), database_(db),
connection_collation_(connection_col), connection_collation_(connection_col),
ssl_(opts) ssl_(opts)
{ {
} }
/// Retrieves the username. /// Retrieves the username.
std::string_view username() const noexcept { return username_; } std::string_view username() const noexcept { return username_; }
/// Sets the username. /// Sets the username.
void set_username(std::string_view value) noexcept { username_ = value; } void set_username(std::string_view value) noexcept { username_ = value; }
/// Retrieves the password. /// Retrieves the password.
std::string_view password() const noexcept { return password_; } std::string_view password() const noexcept { return password_; }
/// Sets the password /// Sets the password
void set_password(std::string_view value) noexcept { password_ = value; } void set_password(std::string_view value) noexcept { password_ = value; }
/// Retrieves the database. /// Retrieves the database.
std::string_view database() const noexcept { return database_; } std::string_view database() const noexcept { return database_; }
/// Sets the database /// Sets the database
void set_database(std::string_view value) noexcept { database_ = value; } void set_database(std::string_view value) noexcept { database_ = value; }
/// Retrieves the connection collation. /// Retrieves the connection collation.
collation connection_collation() const noexcept { return connection_collation_; } collation connection_collation() const noexcept { return connection_collation_; }
/// Sets the connection collation /// Sets the connection collation
void set_connection_collation(collation value) noexcept { connection_collation_ = value; } void set_connection_collation(collation value) noexcept { connection_collation_ = value; }
/// Retrieves SSL options /// Retrieves SSL options
const ssl_options& ssl() const noexcept { return ssl_; } const ssl_options& ssl() const noexcept { return ssl_; }
/// Sets SSL options /// Sets SSL options
void set_ssl(const ssl_options& value) noexcept { ssl_ = value; } void set_ssl(const ssl_options& value) noexcept { ssl_ = value; }
}; };
} // mysql } // mysql

View File

@ -19,36 +19,36 @@ namespace detail {
struct authentication_plugin struct authentication_plugin
{ {
using calculator_signature = error_code (*)( using calculator_signature = error_code (*)(
std::string_view password, std::string_view password,
std::string_view challenge, std::string_view challenge,
bool use_ssl, bool use_ssl,
std::string& output std::string& output
); );
std::string_view name; std::string_view name;
calculator_signature calculator; calculator_signature calculator;
}; };
class auth_calculator class auth_calculator
{ {
const authentication_plugin* plugin_ {nullptr}; const authentication_plugin* plugin_ {nullptr};
std::string response_; std::string response_;
inline static const authentication_plugin* find_plugin(std::string_view name); inline static const authentication_plugin* find_plugin(std::string_view name);
public: public:
inline error_code calculate( inline error_code calculate(
std::string_view plugin_name, std::string_view plugin_name,
std::string_view password, std::string_view password,
std::string_view challenge, std::string_view challenge,
bool use_ssl bool use_ssl
); );
std::string_view response() const noexcept { return response_; } std::string_view response() const noexcept { return response_; }
std::string_view plugin_name() const noexcept std::string_view plugin_name() const noexcept
{ {
assert(plugin_); assert(plugin_);
return plugin_->name; return plugin_->name;
} }
}; };
} // detail } // detail

View File

@ -27,10 +27,10 @@ namespace caching_sha2_password {
// auth without an SSL connection, but that requires the server public key, // auth without an SSL connection, but that requires the server public key,
// and we do not implement that. // and we do not implement that.
inline error_code compute_response( inline error_code compute_response(
std::string_view password, std::string_view password,
std::string_view challenge, std::string_view challenge,
bool use_ssl, bool use_ssl,
std::string& output std::string& output
); );
} // caching_sha2_password } // caching_sha2_password

View File

@ -16,18 +16,18 @@ namespace mysql {
namespace detail { namespace detail {
constexpr authentication_plugin mysql_native_password_plugin { constexpr authentication_plugin mysql_native_password_plugin {
"mysql_native_password", "mysql_native_password",
&mysql_native_password::compute_response &mysql_native_password::compute_response
}; };
constexpr authentication_plugin caching_sha2_password_plugin { constexpr authentication_plugin caching_sha2_password_plugin {
"caching_sha2_password", "caching_sha2_password",
&caching_sha2_password::compute_response &caching_sha2_password::compute_response
}; };
constexpr std::array<const authentication_plugin*, 2> all_authentication_plugins { constexpr std::array<const authentication_plugin*, 2> all_authentication_plugins {
&mysql_native_password_plugin, &mysql_native_password_plugin,
&caching_sha2_password_plugin &caching_sha2_password_plugin
}; };
} // detail } // detail
@ -36,44 +36,44 @@ constexpr std::array<const authentication_plugin*, 2> all_authentication_plugins
inline const boost::mysql::detail::authentication_plugin* inline const boost::mysql::detail::authentication_plugin*
boost::mysql::detail::auth_calculator::find_plugin( boost::mysql::detail::auth_calculator::find_plugin(
std::string_view name std::string_view name
) )
{ {
auto it = std::find_if( auto it = std::find_if(
all_authentication_plugins.begin(), all_authentication_plugins.begin(),
all_authentication_plugins.end(), all_authentication_plugins.end(),
[name](const authentication_plugin* plugin) { return plugin->name == name; } [name](const authentication_plugin* plugin) { return plugin->name == name; }
); );
return it == std::end(all_authentication_plugins) ? nullptr : *it; return it == std::end(all_authentication_plugins) ? nullptr : *it;
} }
inline boost::mysql::error_code inline boost::mysql::error_code
boost::mysql::detail::auth_calculator::calculate( boost::mysql::detail::auth_calculator::calculate(
std::string_view plugin_name, std::string_view plugin_name,
std::string_view password, std::string_view password,
std::string_view challenge, std::string_view challenge,
bool use_ssl bool use_ssl
) )
{ {
plugin_ = find_plugin(plugin_name); plugin_ = find_plugin(plugin_name);
if (plugin_) if (plugin_)
{ {
// Blank password: we should just return an empty auth string // Blank password: we should just return an empty auth string
if (password.empty()) if (password.empty())
{ {
response_ = ""; response_ = "";
return error_code(); return error_code();
} }
else else
{ {
return plugin_->calculator(password, challenge, use_ssl, response_); return plugin_->calculator(password, challenge, use_ssl, response_);
} }
} }
else else
{ {
return make_error_code(errc::unknown_auth_plugin); return make_error_code(errc::unknown_auth_plugin);
} }
} }

View File

@ -23,33 +23,33 @@ constexpr std::string_view perform_full_auth = "\4";
// challenge must point to challenge_length bytes of data // challenge must point to challenge_length bytes of data
// output must point to response_length bytes of data // output must point to response_length bytes of data
inline void compute_auth_string( inline void compute_auth_string(
std::string_view password, std::string_view password,
const void* challenge, const void* challenge,
void* output void* output
) )
{ {
static_assert(response_length == SHA256_DIGEST_LENGTH); static_assert(response_length == SHA256_DIGEST_LENGTH);
// SHA(SHA(password_sha) concat challenge) XOR password_sha // SHA(SHA(password_sha) concat challenge) XOR password_sha
// hash1 = SHA(pass) // hash1 = SHA(pass)
using sha_buffer = std::uint8_t [response_length]; using sha_buffer = std::uint8_t [response_length];
sha_buffer password_sha; sha_buffer password_sha;
SHA256(reinterpret_cast<const unsigned char*>(password.data()), password.size(), password_sha); SHA256(reinterpret_cast<const unsigned char*>(password.data()), password.size(), password_sha);
// SHA(password_sha) concat challenge = buffer // SHA(password_sha) concat challenge = buffer
std::uint8_t buffer [response_length + challenge_length]; std::uint8_t buffer [response_length + challenge_length];
SHA256(password_sha, response_length, buffer); SHA256(password_sha, response_length, buffer);
std::memcpy(buffer + response_length, challenge, challenge_length); std::memcpy(buffer + response_length, challenge, challenge_length);
// SHA(SHA(password_sha) concat challenge) = SHA(buffer) = salted_password // SHA(SHA(password_sha) concat challenge) = SHA(buffer) = salted_password
sha_buffer salted_password; sha_buffer salted_password;
SHA256(buffer, sizeof(buffer), salted_password); SHA256(buffer, sizeof(buffer), salted_password);
// salted_password XOR password_sha // salted_password XOR password_sha
for (unsigned i = 0; i < response_length; ++i) for (unsigned i = 0; i < response_length; ++i)
{ {
static_cast<std::uint8_t*>(output)[i] = salted_password[i] ^ password_sha[i]; static_cast<std::uint8_t*>(output)[i] = salted_password[i] ^ password_sha[i];
} }
} }
} // caching_sha2_password } // caching_sha2_password
@ -61,39 +61,39 @@ inline void compute_auth_string(
inline boost::mysql::error_code inline boost::mysql::error_code
boost::mysql::detail::caching_sha2_password::compute_response( boost::mysql::detail::caching_sha2_password::compute_response(
std::string_view password, std::string_view password,
std::string_view challenge, std::string_view challenge,
bool use_ssl, bool use_ssl,
std::string& output std::string& output
) )
{ {
if (challenge == perform_full_auth) if (challenge == perform_full_auth)
{ {
if (!use_ssl) if (!use_ssl)
{ {
return make_error_code(errc::auth_plugin_requires_ssl); return make_error_code(errc::auth_plugin_requires_ssl);
} }
output = password; output = password;
output.push_back(0); output.push_back(0);
return error_code(); return error_code();
} }
else else
{ {
// Check challenge size // Check challenge size
if (challenge.size() != challenge_length) if (challenge.size() != challenge_length)
{ {
return make_error_code(errc::protocol_value_error); return make_error_code(errc::protocol_value_error);
} }
// Do the calculation // Do the calculation
output.resize(response_length); output.resize(response_length);
compute_auth_string( compute_auth_string(
password, password,
challenge.data(), challenge.data(),
output.data() output.data()
); );
return error_code(); return error_code();
} }
} }

View File

@ -23,29 +23,29 @@ constexpr std::size_t response_length = 20;
// output must point to response_length bytes of data // output must point to response_length bytes of data
// SHA1( password ) XOR SHA1( "20-bytes random data from server" <concat> SHA1( SHA1( password ) ) ) // SHA1( password ) XOR SHA1( "20-bytes random data from server" <concat> SHA1( SHA1( password ) ) )
inline void compute_auth_string( inline void compute_auth_string(
std::string_view password, std::string_view password,
const void* challenge, const void* challenge,
void* output void* output
) )
{ {
// SHA1 (password) // SHA1 (password)
using sha1_buffer = unsigned char [SHA_DIGEST_LENGTH]; using sha1_buffer = unsigned char [SHA_DIGEST_LENGTH];
sha1_buffer password_sha1; sha1_buffer password_sha1;
SHA1(reinterpret_cast<const unsigned char*>(password.data()), password.size(), password_sha1); SHA1(reinterpret_cast<const unsigned char*>(password.data()), password.size(), password_sha1);
// Add server challenge (salt) // Add server challenge (salt)
unsigned char salted_buffer [challenge_length + SHA_DIGEST_LENGTH]; unsigned char salted_buffer [challenge_length + SHA_DIGEST_LENGTH];
memcpy(salted_buffer, challenge, challenge_length); memcpy(salted_buffer, challenge, challenge_length);
SHA1(password_sha1, sizeof(password_sha1), salted_buffer + 20); SHA1(password_sha1, sizeof(password_sha1), salted_buffer + 20);
sha1_buffer salted_sha1; sha1_buffer salted_sha1;
SHA1(salted_buffer, sizeof(salted_buffer), salted_sha1); SHA1(salted_buffer, sizeof(salted_buffer), salted_sha1);
// XOR // XOR
static_assert(response_length == SHA_DIGEST_LENGTH); static_assert(response_length == SHA_DIGEST_LENGTH);
for (std::size_t i = 0; i < SHA_DIGEST_LENGTH; ++i) for (std::size_t i = 0; i < SHA_DIGEST_LENGTH; ++i)
{ {
static_cast<std::uint8_t*>(output)[i] = password_sha1[i] ^ salted_sha1[i]; static_cast<std::uint8_t*>(output)[i] = password_sha1[i] ^ salted_sha1[i];
} }
} }
} // mysql_native_password } // mysql_native_password
@ -56,26 +56,26 @@ inline void compute_auth_string(
inline boost::mysql::error_code inline boost::mysql::error_code
boost::mysql::detail::mysql_native_password::compute_response( boost::mysql::detail::mysql_native_password::compute_response(
std::string_view password, std::string_view password,
std::string_view challenge, std::string_view challenge,
bool, // use_ssl bool, // use_ssl
std::string& output std::string& output
) )
{ {
// Check challenge size // Check challenge size
if (challenge.size() != challenge_length) if (challenge.size() != challenge_length)
{ {
return make_error_code(errc::protocol_value_error); return make_error_code(errc::protocol_value_error);
} }
// Do the calculation // Do the calculation
output.resize(response_length); output.resize(response_length);
compute_auth_string( compute_auth_string(
password, password,
challenge.data(), challenge.data(),
output.data() output.data()
); );
return error_code(); return error_code();
} }

View File

@ -20,10 +20,10 @@ namespace mysql_native_password {
// Authorization for this plugin is always challenge (nonce) -> response // Authorization for this plugin is always challenge (nonce) -> response
// (hashed password). // (hashed password).
inline error_code compute_response( inline error_code compute_response(
std::string_view password, std::string_view password,
std::string_view challenge, std::string_view challenge,
bool use_ssl, bool use_ssl,
std::string& output std::string& output
); );

View File

@ -14,7 +14,7 @@
#define BOOST_MYSQL_INITFN_RESULT_TYPE(ct, sig) DEDUCED #define BOOST_MYSQL_INITFN_RESULT_TYPE(ct, sig) DEDUCED
#else #else
#define BOOST_MYSQL_INITFN_RESULT_TYPE(ct, sig) \ #define BOOST_MYSQL_INITFN_RESULT_TYPE(ct, sig) \
BOOST_ASIO_INITFN_RESULT_TYPE(ct, sig) BOOST_ASIO_INITFN_RESULT_TYPE(ct, sig)
#endif #endif
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_AUXILIAR_ASYNC_RESULT_MACRO_HPP_ */ #endif /* INCLUDE_BOOST_MYSQL_DETAIL_AUXILIAR_ASYNC_RESULT_MACRO_HPP_ */

View File

@ -22,38 +22,38 @@ struct get_handler_arg;
template <typename T> template <typename T>
struct get_handler_arg<void(error_code, T)> struct get_handler_arg<void(error_code, T)>
{ {
using type = T; using type = T;
}; };
template <> template <>
struct get_handler_arg<void(error_code)> struct get_handler_arg<void(error_code)>
{ {
using type = void; using type = void;
}; };
template <typename HandlerType, typename HandlerArg> template <typename HandlerType, typename HandlerArg>
constexpr bool is_handler_signature_ok() constexpr bool is_handler_signature_ok()
{ {
if constexpr (std::is_same_v<HandlerArg, void>) if constexpr (std::is_same_v<HandlerArg, void>)
{ {
return std::is_invocable_v<HandlerType, error_code>; return std::is_invocable_v<HandlerType, error_code>;
} }
else else
{ {
return std::is_invocable_v<HandlerType, error_code, HandlerArg>; return std::is_invocable_v<HandlerType, error_code, HandlerArg>;
} }
} }
template <typename CompletionToken, typename HandlerSignature> template <typename CompletionToken, typename HandlerSignature>
constexpr void check_completion_token() constexpr void check_completion_token()
{ {
using handler_type = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); using handler_type = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature);
using handler_arg = typename detail::get_handler_arg<HandlerSignature>::type; using handler_arg = typename detail::get_handler_arg<HandlerSignature>::type;
static_assert( static_assert(
is_handler_signature_ok<handler_type, handler_arg>(), is_handler_signature_ok<handler_type, handler_arg>(),
"Invalid CompletionToken type. Check that CompletionToken fullfills the CompletionToken " "Invalid CompletionToken type. Check that CompletionToken fullfills the CompletionToken "
"requirements or that the callback signature you passed in is correct" "requirements or that the callback signature you passed in is correct"
); );
} }
} // detail } // detail

View File

@ -17,16 +17,16 @@ namespace detail {
template <typename TLeft, typename TRight> template <typename TLeft, typename TRight>
inline bool container_equals( inline bool container_equals(
const std::vector<TLeft>& lhs, const std::vector<TLeft>& lhs,
const std::vector<TRight>& rhs const std::vector<TRight>& rhs
) )
{ {
if (lhs.size() != rhs.size()) return false; if (lhs.size() != rhs.size()) return false;
return std::equal( return std::equal(
lhs.begin(), lhs.begin(),
lhs.end(), lhs.end(),
rhs.begin() rhs.begin()
); );
} }
} // detail } // detail

View File

@ -18,9 +18,9 @@ namespace detail {
template <typename... Types> template <typename... Types>
std::string stringize(const Types&... inputs) std::string stringize(const Types&... inputs)
{ {
std::ostringstream ss; std::ostringstream ss;
(ss << ... << inputs); (ss << ... << inputs);
return ss.str(); return ss.str();
} }
} // detail } // detail

View File

@ -17,13 +17,13 @@ namespace detail {
template <typename T, typename Head, typename... Tail> template <typename T, typename Head, typename... Tail>
struct is_one_of struct is_one_of
{ {
static constexpr bool value = std::is_same_v<T, Head> || is_one_of<T, Tail...>::value; static constexpr bool value = std::is_same_v<T, Head> || is_one_of<T, Tail...>::value;
}; };
template <typename T, typename Head> template <typename T, typename Head>
struct is_one_of<T, Head> struct is_one_of<T, Head>
{ {
static constexpr bool value = std::is_same_v<T, Head>; static constexpr bool value = std::is_same_v<T, Head>;
}; };

View File

@ -17,11 +17,11 @@ namespace mysql {
namespace detail { namespace detail {
inline void valgrind_make_mem_defined( inline void valgrind_make_mem_defined(
[[maybe_unused]] boost::asio::const_buffer buff [[maybe_unused]] boost::asio::const_buffer buff
) )
{ {
#ifdef BOOST_MYSQL_VALGRIND_TESTS #ifdef BOOST_MYSQL_VALGRIND_TESTS
VALGRIND_MAKE_MEM_DEFINED(buff.data(), buff.size()); VALGRIND_MAKE_MEM_DEFINED(buff.data(), buff.size());
#endif #endif
} }

View File

@ -16,10 +16,10 @@ namespace detail {
template <typename StreamType> template <typename StreamType>
void close_statement( void close_statement(
channel<StreamType>& chan, channel<StreamType>& chan,
std::uint32_t statement_id, std::uint32_t statement_id,
error_code& code, error_code& code,
error_info& info error_info& info
); );
using close_signature = empty_signature; using close_signature = empty_signature;
@ -27,10 +27,10 @@ using close_signature = empty_signature;
template <typename StreamType, typename CompletionToken> template <typename StreamType, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, close_signature) BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, close_signature)
async_close_statement( async_close_statement(
channel<StreamType>& chan, channel<StreamType>& chan,
std::uint32_t statement_id, std::uint32_t statement_id,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
); );
} // detail } // detail

View File

@ -20,9 +20,9 @@ namespace mysql {
namespace detail { namespace detail {
using deserialize_row_fn = error_code (*)( using deserialize_row_fn = error_code (*)(
deserialization_context&, deserialization_context&,
const std::vector<field_metadata>&, const std::vector<field_metadata>&,
std::vector<value>& std::vector<value>&
); );
using empty_signature = void(error_code); using empty_signature = void(error_code);

View File

@ -18,12 +18,12 @@ namespace detail {
template <typename StreamType, typename Serializable> template <typename StreamType, typename Serializable>
void execute_generic( void execute_generic(
deserialize_row_fn deserializer, deserialize_row_fn deserializer,
channel<StreamType>& channel, channel<StreamType>& channel,
const Serializable& request, const Serializable& request,
resultset<StreamType>& output, resultset<StreamType>& output,
error_code& err, error_code& err,
error_info& info error_info& info
); );
template <typename StreamType> template <typename StreamType>
@ -32,11 +32,11 @@ using execute_generic_signature = void(error_code, resultset<StreamType>);
template <typename StreamType, typename Serializable, typename CompletionToken> template <typename StreamType, typename Serializable, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, execute_generic_signature<StreamType>) BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, execute_generic_signature<StreamType>)
async_execute_generic( async_execute_generic(
deserialize_row_fn deserializer, deserialize_row_fn deserializer,
channel<StreamType>& chan, channel<StreamType>& chan,
const Serializable& request, const Serializable& request,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
); );
} // detail } // detail

View File

@ -19,20 +19,20 @@ namespace detail {
template <typename StreamType> template <typename StreamType>
void execute_query( void execute_query(
channel<StreamType>& channel, channel<StreamType>& channel,
std::string_view query, std::string_view query,
resultset<StreamType>& output, resultset<StreamType>& output,
error_code& err, error_code& err,
error_info& info error_info& info
); );
template <typename StreamType, typename CompletionToken> template <typename StreamType, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, execute_generic_signature<StreamType>) BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, execute_generic_signature<StreamType>)
async_execute_query( async_execute_query(
channel<StreamType>& chan, channel<StreamType>& chan,
std::string_view query, std::string_view query,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
); );
} }

View File

@ -19,24 +19,24 @@ namespace detail {
template <typename StreamType, typename ForwardIterator> template <typename StreamType, typename ForwardIterator>
void execute_statement( void execute_statement(
channel<StreamType>& channel, channel<StreamType>& channel,
std::uint32_t statement_id, std::uint32_t statement_id,
ForwardIterator params_begin, ForwardIterator params_begin,
ForwardIterator params_end, ForwardIterator params_end,
resultset<StreamType>& output, resultset<StreamType>& output,
error_code& err, error_code& err,
error_info& info error_info& info
); );
template <typename StreamType, typename ForwardIterator, typename CompletionToken> template <typename StreamType, typename ForwardIterator, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, execute_generic_signature<StreamType>) BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, execute_generic_signature<StreamType>)
async_execute_statement( async_execute_statement(
channel<StreamType>& chan, channel<StreamType>& chan,
std::uint32_t statement_id, std::uint32_t statement_id,
ForwardIterator params_begin, ForwardIterator params_begin,
ForwardIterator params_end, ForwardIterator params_end,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
); );
} // detail } // detail

View File

@ -21,10 +21,10 @@ namespace detail {
template <typename StreamType> template <typename StreamType>
void hanshake( void hanshake(
channel<StreamType>& channel, channel<StreamType>& channel,
const connection_params& params, const connection_params& params,
error_code& err, error_code& err,
error_info& info error_info& info
); );
using handshake_signature = empty_signature; using handshake_signature = empty_signature;
@ -32,10 +32,10 @@ using handshake_signature = empty_signature;
template <typename StreamType, typename CompletionToken> template <typename StreamType, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, handshake_signature) BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, handshake_signature)
async_handshake( async_handshake(
channel<StreamType>& channel, channel<StreamType>& channel,
const connection_params& params, const connection_params& params,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
); );
} // detail } // detail

View File

@ -12,47 +12,47 @@
template <typename StreamType> template <typename StreamType>
void boost::mysql::detail::close_statement( void boost::mysql::detail::close_statement(
channel<StreamType>& chan, channel<StreamType>& chan,
std::uint32_t statement_id, std::uint32_t statement_id,
error_code& code, error_code& code,
error_info& error_info&
) )
{ {
// Compose the close message // Compose the close message
com_stmt_close_packet packet {int4(statement_id)}; com_stmt_close_packet packet {int4(statement_id)};
// Serialize it // Serialize it
serialize_message(packet, chan.current_capabilities(), chan.shared_buffer()); serialize_message(packet, chan.current_capabilities(), chan.shared_buffer());
// Send it. No response is sent back // Send it. No response is sent back
chan.reset_sequence_number(); chan.reset_sequence_number();
chan.write(boost::asio::buffer(chan.shared_buffer()), code); chan.write(boost::asio::buffer(chan.shared_buffer()), code);
} }
template <typename StreamType, typename CompletionToken> template <typename StreamType, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
boost::mysql::detail::close_signature boost::mysql::detail::close_signature
) )
boost::mysql::detail::async_close_statement( boost::mysql::detail::async_close_statement(
channel<StreamType>& chan, channel<StreamType>& chan,
std::uint32_t statement_id, std::uint32_t statement_id,
CompletionToken&& token, CompletionToken&& token,
error_info* error_info*
) )
{ {
// Compose the close message // Compose the close message
com_stmt_close_packet packet {int4(statement_id)}; com_stmt_close_packet packet {int4(statement_id)};
// Serialize it // Serialize it
serialize_message(packet, chan.current_capabilities(), chan.shared_buffer()); serialize_message(packet, chan.current_capabilities(), chan.shared_buffer());
// Send it. No response is sent back // Send it. No response is sent back
chan.reset_sequence_number(); chan.reset_sequence_number();
return chan.async_write( return chan.async_write(
boost::asio::buffer(chan.shared_buffer()), boost::asio::buffer(chan.shared_buffer()),
std::forward<CompletionToken>(token) std::forward<CompletionToken>(token)
); );
} }

View File

@ -18,123 +18,123 @@ namespace detail {
template <typename StreamType> template <typename StreamType>
class execute_processor class execute_processor
{ {
deserialize_row_fn deserializer_; deserialize_row_fn deserializer_;
channel<StreamType>& channel_; channel<StreamType>& channel_;
bytestring buffer_; bytestring buffer_;
std::size_t field_count_ {}; std::size_t field_count_ {};
ok_packet ok_packet_; ok_packet ok_packet_;
std::vector<field_metadata> fields_; std::vector<field_metadata> fields_;
std::vector<bytestring> field_buffers_; std::vector<bytestring> field_buffers_;
public: public:
execute_processor(deserialize_row_fn deserializer, channel<StreamType>& chan): execute_processor(deserialize_row_fn deserializer, channel<StreamType>& chan):
deserializer_(deserializer), channel_(chan) {}; deserializer_(deserializer), channel_(chan) {};
template <typename Serializable> template <typename Serializable>
void process_request( void process_request(
const Serializable& request const Serializable& request
) )
{ {
// Serialize the request // Serialize the request
capabilities caps = channel_.current_capabilities(); capabilities caps = channel_.current_capabilities();
serialize_message(request, caps, buffer_); serialize_message(request, caps, buffer_);
// Prepare the channel // Prepare the channel
channel_.reset_sequence_number(); channel_.reset_sequence_number();
} }
void process_response( void process_response(
error_code& err, error_code& err,
error_info& info error_info& info
) )
{ {
// Response may be: ok_packet, err_packet, local infile request (not implemented) // Response may be: ok_packet, err_packet, local infile request (not implemented)
// If it is none of this, then the message type itself is the beginning of // If it is none of this, then the message type itself is the beginning of
// a length-encoded int containing the field count // a length-encoded int containing the field count
deserialization_context ctx (boost::asio::buffer(buffer_), channel_.current_capabilities()); deserialization_context ctx (boost::asio::buffer(buffer_), channel_.current_capabilities());
std::uint8_t msg_type; std::uint8_t msg_type;
std::tie(err, msg_type) = deserialize_message_type(ctx); std::tie(err, msg_type) = deserialize_message_type(ctx);
if (err) return; if (err) return;
if (msg_type == ok_packet_header) if (msg_type == ok_packet_header)
{ {
err = deserialize_message(ok_packet_, ctx); err = deserialize_message(ok_packet_, ctx);
if (err) return; if (err) return;
field_count_ = 0; field_count_ = 0;
} }
else if (msg_type == error_packet_header) else if (msg_type == error_packet_header)
{ {
err = process_error_packet(ctx, info); err = process_error_packet(ctx, info);
} }
else else
{ {
// Resultset with metadata. First packet is an int_lenenc with // Resultset with metadata. First packet is an int_lenenc with
// the number of field definitions to expect. Message type is part // the number of field definitions to expect. Message type is part
// of this packet, so we must rewind the context // of this packet, so we must rewind the context
ctx.rewind(1); ctx.rewind(1);
int_lenenc num_fields; int_lenenc num_fields;
err = deserialize_message(num_fields, ctx); err = deserialize_message(num_fields, ctx);
if (err) return; if (err) return;
// For platforms where size_t is shorter than uint64_t, // For platforms where size_t is shorter than uint64_t,
// perform range check // perform range check
if (num_fields.value > std::numeric_limits<std::size_t>::max()) if (num_fields.value > std::numeric_limits<std::size_t>::max())
{ {
err = make_error_code(errc::protocol_value_error); err = make_error_code(errc::protocol_value_error);
return; return;
} }
// Ensure we have fields, as field_count is indicative of // Ensure we have fields, as field_count is indicative of
// a resultset with fields // a resultset with fields
field_count_ = static_cast<std::size_t>(num_fields.value); field_count_ = static_cast<std::size_t>(num_fields.value);
if (field_count_ == 0) if (field_count_ == 0)
{ {
err = make_error_code(errc::protocol_value_error); err = make_error_code(errc::protocol_value_error);
return; return;
} }
fields_.reserve(field_count_); fields_.reserve(field_count_);
field_buffers_.reserve(field_count_); field_buffers_.reserve(field_count_);
} }
} }
error_code process_field_definition() error_code process_field_definition()
{ {
column_definition_packet field_definition; column_definition_packet field_definition;
deserialization_context ctx (boost::asio::buffer(buffer_), channel_.current_capabilities()); deserialization_context ctx (boost::asio::buffer(buffer_), channel_.current_capabilities());
auto err = deserialize_message(field_definition, ctx); auto err = deserialize_message(field_definition, ctx);
if (err) return err; if (err) return err;
// Add it to our array // Add it to our array
fields_.push_back(field_definition); fields_.push_back(field_definition);
field_buffers_.push_back(std::move(buffer_)); field_buffers_.push_back(std::move(buffer_));
buffer_ = bytestring(); buffer_ = bytestring();
return error_code(); return error_code();
} }
resultset<StreamType> create_resultset() && resultset<StreamType> create_resultset() &&
{ {
if (field_count_ == 0) if (field_count_ == 0)
{ {
return resultset<StreamType>( return resultset<StreamType>(
channel_, channel_,
std::move(buffer_), std::move(buffer_),
ok_packet_ ok_packet_
); );
} }
else else
{ {
return resultset<StreamType>( return resultset<StreamType>(
channel_, channel_,
resultset_metadata(std::move(field_buffers_), std::move(fields_)), resultset_metadata(std::move(field_buffers_), std::move(fields_)),
deserializer_ deserializer_
); );
} }
} }
auto& get_channel() { return channel_; } auto& get_channel() { return channel_; }
auto& get_buffer() { return buffer_; } auto& get_buffer() { return buffer_; }
std::size_t field_count() const noexcept { return field_count_; } std::size_t field_count() const noexcept { return field_count_; }
}; };
} // detail } // detail
@ -143,164 +143,164 @@ public:
template <typename StreamType, typename Serializable> template <typename StreamType, typename Serializable>
void boost::mysql::detail::execute_generic( void boost::mysql::detail::execute_generic(
deserialize_row_fn deserializer, deserialize_row_fn deserializer,
channel<StreamType>& channel, channel<StreamType>& channel,
const Serializable& request, const Serializable& request,
resultset<StreamType>& output, resultset<StreamType>& output,
error_code& err, error_code& err,
error_info& info error_info& info
) )
{ {
// Compose a com_query message, reset seq num // Compose a com_query message, reset seq num
execute_processor<StreamType> processor (deserializer, channel); execute_processor<StreamType> processor (deserializer, channel);
processor.process_request(request); processor.process_request(request);
// Send it // Send it
channel.write(boost::asio::buffer(processor.get_buffer()), err); channel.write(boost::asio::buffer(processor.get_buffer()), err);
if (err) return; if (err) return;
// Read the response // Read the response
channel.read(processor.get_buffer(), err); channel.read(processor.get_buffer(), err);
if (err) return; if (err) return;
// Response may be: ok_packet, err_packet, local infile request (not implemented), or response with fields // Response may be: ok_packet, err_packet, local infile request (not implemented), or response with fields
processor.process_response(err, info); processor.process_response(err, info);
if (err) return; if (err) return;
// Read all of the field definitions (zero if empty resultset) // Read all of the field definitions (zero if empty resultset)
for (std::uint64_t i = 0; i < processor.field_count(); ++i) for (std::uint64_t i = 0; i < processor.field_count(); ++i)
{ {
// Read the field definition packet // Read the field definition packet
channel.read(processor.get_buffer(), err); channel.read(processor.get_buffer(), err);
if (err) return; if (err) return;
// Process the message // Process the message
err = processor.process_field_definition(); err = processor.process_field_definition();
if (err) return; if (err) return;
} }
// No EOF packet is expected here, as we require deprecate EOF capabilities // No EOF packet is expected here, as we require deprecate EOF capabilities
output = std::move(processor).create_resultset(); output = std::move(processor).create_resultset();
err.clear(); err.clear();
} }
template <typename StreamType, typename Serializable, typename CompletionToken> template <typename StreamType, typename Serializable, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
boost::mysql::detail::execute_generic_signature<StreamType> boost::mysql::detail::execute_generic_signature<StreamType>
) )
boost::mysql::detail::async_execute_generic( boost::mysql::detail::async_execute_generic(
deserialize_row_fn deserializer, deserialize_row_fn deserializer,
channel<StreamType>& chan, channel<StreamType>& chan,
const Serializable& request, const Serializable& request,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
) )
{ {
using HandlerSignature = execute_generic_signature<StreamType>; using HandlerSignature = execute_generic_signature<StreamType>;
using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature);
using BaseType = boost::beast::async_base<HandlerType, typename StreamType::executor_type>; using BaseType = boost::beast::async_base<HandlerType, typename StreamType::executor_type>;
using ResultsetType = resultset<StreamType>; using ResultsetType = resultset<StreamType>;
boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token); boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token);
struct Op: BaseType, boost::asio::coroutine struct Op: BaseType, boost::asio::coroutine
{ {
std::shared_ptr<execute_processor<StreamType>> processor_; std::shared_ptr<execute_processor<StreamType>> processor_;
std::uint64_t remaining_fields_ {0}; std::uint64_t remaining_fields_ {0};
error_info* output_info_; error_info* output_info_;
Op( Op(
HandlerType&& handler, HandlerType&& handler,
deserialize_row_fn deserializer, deserialize_row_fn deserializer,
channel<StreamType>& channel, channel<StreamType>& channel,
const Serializable& request, const Serializable& request,
error_info* output_info error_info* output_info
): ):
BaseType(std::move(handler), channel.next_layer().get_executor()), BaseType(std::move(handler), channel.next_layer().get_executor()),
processor_(std::make_shared<execute_processor<StreamType>>(deserializer, channel)), processor_(std::make_shared<execute_processor<StreamType>>(deserializer, channel)),
output_info_(output_info) output_info_(output_info)
{ {
processor_->process_request(request); processor_->process_request(request);
} }
void operator()( void operator()(
error_code err, error_code err,
bool cont=true bool cont=true
) )
{ {
// Error checking // Error checking
if (err) if (err)
{ {
this->complete(cont, err, ResultsetType()); this->complete(cont, err, ResultsetType());
return; return;
} }
// Non-error path // Non-error path
error_info info; error_info info;
reenter(*this) reenter(*this)
{ {
// The request message has already been composed in the ctor. Send it // The request message has already been composed in the ctor. Send it
yield processor_->get_channel().async_write( yield processor_->get_channel().async_write(
boost::asio::buffer(processor_->get_buffer()), boost::asio::buffer(processor_->get_buffer()),
std::move(*this) std::move(*this)
); );
// Read the response // Read the response
yield processor_->get_channel().async_read( yield processor_->get_channel().async_read(
processor_->get_buffer(), processor_->get_buffer(),
std::move(*this) std::move(*this)
); );
// Response may be: ok_packet, err_packet, local infile request (not implemented), or response with fields // Response may be: ok_packet, err_packet, local infile request (not implemented), or response with fields
processor_->process_response(err, info); processor_->process_response(err, info);
if (err) if (err)
{ {
conditional_assign(output_info_, std::move(info)); conditional_assign(output_info_, std::move(info));
this->complete(cont, err, ResultsetType()); this->complete(cont, err, ResultsetType());
yield break; yield break;
} }
remaining_fields_ = processor_->field_count(); remaining_fields_ = processor_->field_count();
// Read all of the field definitions // Read all of the field definitions
while (remaining_fields_ > 0) while (remaining_fields_ > 0)
{ {
// Read the field definition packet // Read the field definition packet
yield processor_->get_channel().async_read( yield processor_->get_channel().async_read(
processor_->get_buffer(), processor_->get_buffer(),
std::move(*this) std::move(*this)
); );
// Process the message // Process the message
err = processor_->process_field_definition(); err = processor_->process_field_definition();
if (err) if (err)
{ {
this->complete(cont, err, ResultsetType()); this->complete(cont, err, ResultsetType());
yield break; yield break;
} }
remaining_fields_--; remaining_fields_--;
} }
// No EOF packet is expected here, as we require deprecate EOF capabilities // No EOF packet is expected here, as we require deprecate EOF capabilities
this->complete( this->complete(
cont, cont,
error_code(), error_code(),
ResultsetType(std::move(*processor_).create_resultset()) ResultsetType(std::move(*processor_).create_resultset())
); );
} }
} }
}; };
Op( Op(
std::move(initiator.completion_handler), std::move(initiator.completion_handler),
deserializer, deserializer,
chan, chan,
request, request,
info info
)(error_code(), false); )(error_code(), false);
return initiator.result.get(); return initiator.result.get();
} }
#include <boost/asio/unyield.hpp> #include <boost/asio/unyield.hpp>

View File

@ -13,45 +13,45 @@
template <typename StreamType> template <typename StreamType>
void boost::mysql::detail::execute_query( void boost::mysql::detail::execute_query(
channel<StreamType>& channel, channel<StreamType>& channel,
std::string_view query, std::string_view query,
resultset<StreamType>& output, resultset<StreamType>& output,
error_code& err, error_code& err,
error_info& info error_info& info
) )
{ {
com_query_packet request { string_eof(query) }; com_query_packet request { string_eof(query) };
execute_generic( execute_generic(
&deserialize_text_row, &deserialize_text_row,
channel, channel,
request, request,
output, output,
err, err,
info info
); );
} }
template <typename StreamType, typename CompletionToken> template <typename StreamType, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
boost::mysql::detail::execute_generic_signature<StreamType> boost::mysql::detail::execute_generic_signature<StreamType>
) )
boost::mysql::detail::async_execute_query( boost::mysql::detail::async_execute_query(
channel<StreamType>& chan, channel<StreamType>& chan,
std::string_view query, std::string_view query,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
) )
{ {
com_query_packet request { string_eof(query) }; com_query_packet request { string_eof(query) };
return async_execute_generic( return async_execute_generic(
&deserialize_text_row, &deserialize_text_row,
chan, chan,
request, request,
std::forward<CompletionToken>(token), std::forward<CompletionToken>(token),
info info
); );
} }
#include <boost/asio/unyield.hpp> #include <boost/asio/unyield.hpp>

View File

@ -17,19 +17,19 @@ namespace detail {
template <typename ForwardIterator> template <typename ForwardIterator>
com_stmt_execute_packet<ForwardIterator> make_stmt_execute_packet( com_stmt_execute_packet<ForwardIterator> make_stmt_execute_packet(
std::uint32_t statement_id, std::uint32_t statement_id,
ForwardIterator params_begin, ForwardIterator params_begin,
ForwardIterator params_end ForwardIterator params_end
) )
{ {
return com_stmt_execute_packet<ForwardIterator> { return com_stmt_execute_packet<ForwardIterator> {
int4(statement_id), int4(statement_id),
int1(0), // flags int1(0), // flags
int4(1), // iteration count int4(1), // iteration count
int1(1), // new params flag: set int1(1), // new params flag: set
params_begin, params_begin,
params_end params_end
}; };
} }
} // detail } // detail
@ -38,46 +38,46 @@ com_stmt_execute_packet<ForwardIterator> make_stmt_execute_packet(
template <typename StreamType, typename ForwardIterator> template <typename StreamType, typename ForwardIterator>
void boost::mysql::detail::execute_statement( void boost::mysql::detail::execute_statement(
channel<StreamType>& chan, channel<StreamType>& chan,
std::uint32_t statement_id, std::uint32_t statement_id,
ForwardIterator params_begin, ForwardIterator params_begin,
ForwardIterator params_end, ForwardIterator params_end,
resultset<StreamType>& output, resultset<StreamType>& output,
error_code& err, error_code& err,
error_info& info error_info& info
) )
{ {
execute_generic( execute_generic(
&deserialize_binary_row, &deserialize_binary_row,
chan, chan,
make_stmt_execute_packet(statement_id, params_begin, params_end), make_stmt_execute_packet(statement_id, params_begin, params_end),
output, output,
err, err,
info info
); );
} }
template <typename StreamType, typename ForwardIterator, typename CompletionToken> template <typename StreamType, typename ForwardIterator, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
boost::mysql::detail::execute_generic_signature<StreamType> boost::mysql::detail::execute_generic_signature<StreamType>
) )
boost::mysql::detail::async_execute_statement( boost::mysql::detail::async_execute_statement(
channel<StreamType>& chan, channel<StreamType>& chan,
std::uint32_t statement_id, std::uint32_t statement_id,
ForwardIterator params_begin, ForwardIterator params_begin,
ForwardIterator params_end, ForwardIterator params_end,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
) )
{ {
return async_execute_generic( return async_execute_generic(
&deserialize_binary_row, &deserialize_binary_row,
chan, chan,
make_stmt_execute_packet(statement_id, params_begin, params_end), make_stmt_execute_packet(statement_id, params_begin, params_end),
std::forward<CompletionToken>(token), std::forward<CompletionToken>(token),
info info
); );
} }

View File

@ -20,36 +20,36 @@ namespace detail {
inline std::uint8_t get_collation_first_byte(collation value) inline std::uint8_t get_collation_first_byte(collation value)
{ {
return static_cast<std::uint16_t>(value) % 0xff; return static_cast<std::uint16_t>(value) % 0xff;
} }
inline capabilities conditional_capability(bool condition, std::uint32_t cap) inline capabilities conditional_capability(bool condition, std::uint32_t cap)
{ {
return capabilities(condition ? cap : 0); return capabilities(condition ? cap : 0);
} }
inline error_code deserialize_handshake( inline error_code deserialize_handshake(
boost::asio::const_buffer buffer, boost::asio::const_buffer buffer,
handshake_packet& output, handshake_packet& output,
error_info& info error_info& info
) )
{ {
deserialization_context ctx (boost::asio::buffer(buffer), capabilities()); deserialization_context ctx (boost::asio::buffer(buffer), capabilities());
auto [err, msg_type] = deserialize_message_type(ctx); auto [err, msg_type] = deserialize_message_type(ctx);
if (err) return err; if (err) return err;
if (msg_type == handshake_protocol_version_9) if (msg_type == handshake_protocol_version_9)
{ {
return make_error_code(errc::server_unsupported); return make_error_code(errc::server_unsupported);
} }
else if (msg_type == error_packet_header) else if (msg_type == error_packet_header)
{ {
return process_error_packet(ctx, info); return process_error_packet(ctx, info);
} }
else if (msg_type != handshake_protocol_version_10) else if (msg_type != handshake_protocol_version_10)
{ {
return make_error_code(errc::protocol_value_error); return make_error_code(errc::protocol_value_error);
} }
return deserialize_message(output, ctx); return deserialize_message(output, ctx);
} }
// When receiving an auth response from the server, several things can happen: // When receiving an auth response from the server, several things can happen:
@ -64,172 +64,172 @@ inline error_code deserialize_handshake(
// happens just for caching_sha2_password. // happens just for caching_sha2_password.
enum class auth_result enum class auth_result
{ {
complete, complete,
send_more_data, send_more_data,
wait_for_ok, wait_for_ok,
invalid invalid
}; };
class handshake_processor class handshake_processor
{ {
connection_params params_; connection_params params_;
capabilities negotiated_caps_; capabilities negotiated_caps_;
auth_calculator auth_calc_; auth_calculator auth_calc_;
public: public:
handshake_processor(const connection_params& params): params_(params) {}; handshake_processor(const connection_params& params): params_(params) {};
capabilities negotiated_capabilities() const noexcept { return negotiated_caps_; } capabilities negotiated_capabilities() const noexcept { return negotiated_caps_; }
const connection_params& params() const noexcept { return params_; } const connection_params& params() const noexcept { return params_; }
bool use_ssl() const noexcept { return negotiated_caps_.has(CLIENT_SSL); } bool use_ssl() const noexcept { return negotiated_caps_.has(CLIENT_SSL); }
// Initial greeting processing // Initial greeting processing
error_code process_capabilities(const handshake_packet& handshake) error_code process_capabilities(const handshake_packet& handshake)
{ {
auto ssl = params_.ssl().mode(); auto ssl = params_.ssl().mode();
capabilities server_caps (handshake.capability_falgs.value); capabilities server_caps (handshake.capability_falgs.value);
capabilities required_caps = mandatory_capabilities | capabilities required_caps = mandatory_capabilities |
conditional_capability(!params_.database().empty(), CLIENT_CONNECT_WITH_DB) | conditional_capability(!params_.database().empty(), CLIENT_CONNECT_WITH_DB) |
conditional_capability(ssl == ssl_mode::require, CLIENT_SSL); conditional_capability(ssl == ssl_mode::require, CLIENT_SSL);
if (!server_caps.has_all(required_caps)) if (!server_caps.has_all(required_caps))
{ {
return make_error_code(errc::server_unsupported); return make_error_code(errc::server_unsupported);
} }
negotiated_caps_ = server_caps & (required_caps | optional_capabilities | negotiated_caps_ = server_caps & (required_caps | optional_capabilities |
conditional_capability(ssl == ssl_mode::enable, CLIENT_SSL)); conditional_capability(ssl == ssl_mode::enable, CLIENT_SSL));
return error_code(); return error_code();
} }
error_code process_handshake(bytestring& buffer, error_info& info) error_code process_handshake(bytestring& buffer, error_info& info)
{ {
// Deserialize server greeting // Deserialize server greeting
handshake_packet handshake; handshake_packet handshake;
auto err = deserialize_handshake(boost::asio::buffer(buffer), handshake, info); auto err = deserialize_handshake(boost::asio::buffer(buffer), handshake, info);
if (err) return err; if (err) return err;
// Check capabilities // Check capabilities
err = process_capabilities(handshake); err = process_capabilities(handshake);
if (err) return err; if (err) return err;
// Compute auth response // Compute auth response
return auth_calc_.calculate( return auth_calc_.calculate(
handshake.auth_plugin_name.value, handshake.auth_plugin_name.value,
params_.password(), params_.password(),
handshake.auth_plugin_data.value, handshake.auth_plugin_data.value,
use_ssl() use_ssl()
); );
} }
// Response to that initial greeting // Response to that initial greeting
void compose_ssl_request(bytestring& buffer) void compose_ssl_request(bytestring& buffer)
{ {
ssl_request sslreq { ssl_request sslreq {
int4(negotiated_capabilities().get()), int4(negotiated_capabilities().get()),
int4(static_cast<std::uint32_t>(MAX_PACKET_SIZE)), int4(static_cast<std::uint32_t>(MAX_PACKET_SIZE)),
int1(get_collation_first_byte(params_.connection_collation())), int1(get_collation_first_byte(params_.connection_collation())),
{} {}
}; };
// Serialize and send // Serialize and send
serialize_message(sslreq, negotiated_caps_, buffer); serialize_message(sslreq, negotiated_caps_, buffer);
} }
void compose_handshake_response(bytestring& buffer) void compose_handshake_response(bytestring& buffer)
{ {
// Compose response // Compose response
handshake_response_packet response { handshake_response_packet response {
int4(negotiated_caps_.get()), int4(negotiated_caps_.get()),
int4(static_cast<std::uint32_t>(MAX_PACKET_SIZE)), int4(static_cast<std::uint32_t>(MAX_PACKET_SIZE)),
int1(get_collation_first_byte(params_.connection_collation())), int1(get_collation_first_byte(params_.connection_collation())),
string_null(params_.username()), string_null(params_.username()),
string_lenenc(auth_calc_.response()), string_lenenc(auth_calc_.response()),
string_null(params_.database()), string_null(params_.database()),
string_null(auth_calc_.plugin_name()) string_null(auth_calc_.plugin_name())
}; };
// Serialize // Serialize
serialize_message(response, negotiated_caps_, buffer); serialize_message(response, negotiated_caps_, buffer);
} }
// Server handshake response // Server handshake response
error_code process_handshake_server_response( error_code process_handshake_server_response(
bytestring& buffer, bytestring& buffer,
auth_result& result, auth_result& result,
error_info& info error_info& info
) )
{ {
deserialization_context ctx (boost::asio::buffer(buffer), negotiated_caps_); deserialization_context ctx (boost::asio::buffer(buffer), negotiated_caps_);
auto [err, msg_type] = deserialize_message_type(ctx); auto [err, msg_type] = deserialize_message_type(ctx);
if (err) return err; if (err) return err;
if (msg_type == ok_packet_header) if (msg_type == ok_packet_header)
{ {
// Auth success via fast auth path // Auth success via fast auth path
result = auth_result::complete; result = auth_result::complete;
return error_code(); return error_code();
} }
else if (msg_type == error_packet_header) else if (msg_type == error_packet_header)
{ {
return process_error_packet(ctx, info); return process_error_packet(ctx, info);
} }
else if (msg_type == auth_switch_request_header) else if (msg_type == auth_switch_request_header)
{ {
// We have received an auth switch request. Deserialize it // We have received an auth switch request. Deserialize it
auth_switch_request_packet auth_sw; auth_switch_request_packet auth_sw;
err = deserialize_message(auth_sw, ctx); err = deserialize_message(auth_sw, ctx);
if (err) return err; if (err) return err;
// Compute response // Compute response
auth_switch_response_packet auth_sw_res; auth_switch_response_packet auth_sw_res;
err = auth_calc_.calculate( err = auth_calc_.calculate(
auth_sw.plugin_name.value, auth_sw.plugin_name.value,
params_.password(), params_.password(),
auth_sw.auth_plugin_data.value, auth_sw.auth_plugin_data.value,
use_ssl() use_ssl()
); );
if (err) return err; if (err) return err;
auth_sw_res.auth_plugin_data.value = auth_calc_.response(); auth_sw_res.auth_plugin_data.value = auth_calc_.response();
// Serialize // Serialize
serialize_message(auth_sw_res, negotiated_caps_, buffer); serialize_message(auth_sw_res, negotiated_caps_, buffer);
result = auth_result::send_more_data; result = auth_result::send_more_data;
return error_code(); return error_code();
} }
else if (msg_type == auth_more_data_header) else if (msg_type == auth_more_data_header)
{ {
// We have received an auth more data request. Deserialize it // We have received an auth more data request. Deserialize it
auth_more_data_packet more_data; auth_more_data_packet more_data;
err = deserialize_message(more_data, ctx); err = deserialize_message(more_data, ctx);
if (err) return err; if (err) return err;
std::string_view challenge = more_data.auth_plugin_data.value; std::string_view challenge = more_data.auth_plugin_data.value;
if (challenge == fast_auth_complete_challenge) if (challenge == fast_auth_complete_challenge)
{ {
result = auth_result::wait_for_ok; result = auth_result::wait_for_ok;
return error_code(); return error_code();
} }
// Compute response // Compute response
err = auth_calc_.calculate( err = auth_calc_.calculate(
auth_calc_.plugin_name(), auth_calc_.plugin_name(),
params_.password(), params_.password(),
challenge, challenge,
use_ssl() use_ssl()
); );
if (err) return err; if (err) return err;
serialize_message( serialize_message(
auth_switch_response_packet {string_eof(auth_calc_.response())}, auth_switch_response_packet {string_eof(auth_calc_.response())},
negotiated_caps_, negotiated_caps_,
buffer buffer
); );
result = auth_result::send_more_data; result = auth_result::send_more_data;
return error_code(); return error_code();
} }
else else
{ {
return make_error_code(errc::protocol_value_error); return make_error_code(errc::protocol_value_error);
} }
} }
}; };
} // detail } // detail
@ -238,192 +238,192 @@ public:
template <typename StreamType> template <typename StreamType>
void boost::mysql::detail::hanshake( void boost::mysql::detail::hanshake(
channel<StreamType>& channel, channel<StreamType>& channel,
const connection_params& params, const connection_params& params,
error_code& err, error_code& err,
error_info& info error_info& info
) )
{ {
// Set up processor // Set up processor
handshake_processor processor (params); handshake_processor processor (params);
// Read server greeting // Read server greeting
channel.read(channel.shared_buffer(), err); channel.read(channel.shared_buffer(), err);
if (err) return; if (err) return;
// Process server greeting (handshake) // Process server greeting (handshake)
err = processor.process_handshake(channel.shared_buffer(), info); err = processor.process_handshake(channel.shared_buffer(), info);
if (err) return; if (err) return;
// Setup SSL if required // Setup SSL if required
if (processor.use_ssl()) if (processor.use_ssl())
{ {
// Send SSL request // Send SSL request
processor.compose_ssl_request(channel.shared_buffer()); processor.compose_ssl_request(channel.shared_buffer());
channel.write(boost::asio::buffer(channel.shared_buffer()), err); channel.write(boost::asio::buffer(channel.shared_buffer()), err);
if (err) return; if (err) return;
// SSL handshake // SSL handshake
channel.ssl_handshake(err); channel.ssl_handshake(err);
if (err) return; if (err) return;
} }
// Handshake response // Handshake response
processor.compose_handshake_response(channel.shared_buffer()); processor.compose_handshake_response(channel.shared_buffer());
channel.write(boost::asio::buffer(channel.shared_buffer()), err); channel.write(boost::asio::buffer(channel.shared_buffer()), err);
if (err) return; if (err) return;
auth_result auth_outcome = auth_result::invalid; auth_result auth_outcome = auth_result::invalid;
while (auth_outcome != auth_result::complete) while (auth_outcome != auth_result::complete)
{ {
// Receive response // Receive response
channel.read(channel.shared_buffer(), err); channel.read(channel.shared_buffer(), err);
if (err) return; if (err) return;
// Process it // Process it
err = processor.process_handshake_server_response(channel.shared_buffer(), auth_outcome, info); err = processor.process_handshake_server_response(channel.shared_buffer(), auth_outcome, info);
if (err) return; if (err) return;
if (auth_outcome == auth_result::send_more_data) if (auth_outcome == auth_result::send_more_data)
{ {
// We received an auth switch request and we have the response ready to be sent // We received an auth switch request and we have the response ready to be sent
channel.write(boost::asio::buffer(channel.shared_buffer()), err); channel.write(boost::asio::buffer(channel.shared_buffer()), err);
if (err) return; if (err) return;
} }
}; };
channel.set_current_capabilities(processor.negotiated_capabilities()); channel.set_current_capabilities(processor.negotiated_capabilities());
} }
template <typename StreamType, typename CompletionToken> template <typename StreamType, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
boost::mysql::detail::handshake_signature boost::mysql::detail::handshake_signature
) )
boost::mysql::detail::async_handshake( boost::mysql::detail::async_handshake(
channel<StreamType>& chan, channel<StreamType>& chan,
const connection_params& params, const connection_params& params,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
) )
{ {
using HandlerSignature = handshake_signature; using HandlerSignature = handshake_signature;
using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature);
using BaseType = boost::beast::async_base<HandlerType, typename StreamType::executor_type>; using BaseType = boost::beast::async_base<HandlerType, typename StreamType::executor_type>;
boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token); boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token);
struct Op: BaseType, boost::asio::coroutine struct Op: BaseType, boost::asio::coroutine
{ {
channel<StreamType>& channel_; channel<StreamType>& channel_;
handshake_processor processor_; handshake_processor processor_;
error_info info_; error_info info_;
error_info* output_info_; error_info* output_info_;
auth_result auth_state_ {auth_result::invalid}; auth_result auth_state_ {auth_result::invalid};
Op( Op(
HandlerType&& handler, HandlerType&& handler,
channel<StreamType>& channel, channel<StreamType>& channel,
const connection_params& params, const connection_params& params,
error_info* output_info error_info* output_info
): ):
BaseType(std::move(handler), channel.next_layer().get_executor()), BaseType(std::move(handler), channel.next_layer().get_executor()),
channel_(channel), channel_(channel),
processor_(params), processor_(params),
output_info_(output_info) output_info_(output_info)
{ {
} }
void complete(bool cont, error_code code) void complete(bool cont, error_code code)
{ {
channel_.set_current_capabilities(processor_.negotiated_capabilities()); channel_.set_current_capabilities(processor_.negotiated_capabilities());
conditional_assign(output_info_, std::move(info_)); conditional_assign(output_info_, std::move(info_));
BaseType::complete(cont, code); BaseType::complete(cont, code);
} }
void operator()( void operator()(
error_code err, error_code err,
bool cont=true bool cont=true
) )
{ {
// Error checking // Error checking
if (err) if (err)
{ {
complete(cont, err); complete(cont, err);
return; return;
} }
// Non-error path // Non-error path
reenter(*this) reenter(*this)
{ {
// Read server greeting // Read server greeting
yield channel_.async_read(channel_.shared_buffer(), std::move(*this)); yield channel_.async_read(channel_.shared_buffer(), std::move(*this));
// Process server greeting // Process server greeting
err = processor_.process_handshake(channel_.shared_buffer(), info_); err = processor_.process_handshake(channel_.shared_buffer(), info_);
if (err) if (err)
{ {
complete(cont, err); complete(cont, err);
yield break; yield break;
} }
// SSL // SSL
if (processor_.use_ssl()) if (processor_.use_ssl())
{ {
// Send SSL request // Send SSL request
processor_.compose_ssl_request(channel_.shared_buffer()); processor_.compose_ssl_request(channel_.shared_buffer());
yield channel_.async_write( yield channel_.async_write(
boost::asio::buffer(channel_.shared_buffer()), boost::asio::buffer(channel_.shared_buffer()),
std::move(*this) std::move(*this)
); );
// SSL handshake // SSL handshake
yield channel_.async_ssl_handshake(std::move(*this)); yield channel_.async_ssl_handshake(std::move(*this));
} }
// Compose and send handshake response // Compose and send handshake response
processor_.compose_handshake_response(channel_.shared_buffer()); processor_.compose_handshake_response(channel_.shared_buffer());
yield channel_.async_write(boost::asio::buffer(channel_.shared_buffer()), std::move(*this)); yield channel_.async_write(boost::asio::buffer(channel_.shared_buffer()), std::move(*this));
while (auth_state_ != auth_result::complete) while (auth_state_ != auth_result::complete)
{ {
// Receive response // Receive response
yield channel_.async_read(channel_.shared_buffer(), std::move(*this)); yield channel_.async_read(channel_.shared_buffer(), std::move(*this));
// Process it // Process it
err = processor_.process_handshake_server_response( err = processor_.process_handshake_server_response(
channel_.shared_buffer(), channel_.shared_buffer(),
auth_state_, auth_state_,
info_ info_
); );
if (err) if (err)
{ {
complete(cont, err); complete(cont, err);
yield break; yield break;
} }
if (auth_state_ == auth_result::send_more_data) if (auth_state_ == auth_result::send_more_data)
{ {
// We received an auth switch response and we have the response ready to be sent // We received an auth switch response and we have the response ready to be sent
yield channel_.async_write( yield channel_.async_write(
boost::asio::buffer(channel_.shared_buffer()), boost::asio::buffer(channel_.shared_buffer()),
std::move(*this) std::move(*this)
); );
} }
} }
complete(cont, error_code()); complete(cont, error_code());
} }
} }
}; };
Op( Op(
std::move(initiator.completion_handler), std::move(initiator.completion_handler),
chan, chan,
params, params,
info info
)(error_code(), false); )(error_code(), false);
return initiator.result.get(); return initiator.result.get();
} }
#include <boost/asio/unyield.hpp> #include <boost/asio/unyield.hpp>

View File

@ -17,47 +17,47 @@ namespace detail {
template <typename StreamType> template <typename StreamType>
class prepare_statement_processor class prepare_statement_processor
{ {
channel<StreamType>& channel_; channel<StreamType>& channel_;
com_stmt_prepare_ok_packet response_; com_stmt_prepare_ok_packet response_;
public: public:
prepare_statement_processor(channel<StreamType>& chan): channel_(chan) {} prepare_statement_processor(channel<StreamType>& chan): channel_(chan) {}
void process_request(std::string_view statement) void process_request(std::string_view statement)
{ {
com_stmt_prepare_packet packet { string_eof(statement) }; com_stmt_prepare_packet packet { string_eof(statement) };
serialize_message(packet, channel_.current_capabilities(), channel_.shared_buffer()); serialize_message(packet, channel_.current_capabilities(), channel_.shared_buffer());
channel_.reset_sequence_number(); channel_.reset_sequence_number();
} }
void process_response(error_code& err, error_info& info) void process_response(error_code& err, error_info& info)
{ {
deserialization_context ctx ( deserialization_context ctx (
boost::asio::buffer(channel_.shared_buffer()), boost::asio::buffer(channel_.shared_buffer()),
channel_.current_capabilities() channel_.current_capabilities()
); );
std::uint8_t msg_type = 0; std::uint8_t msg_type = 0;
std::tie(err, msg_type) = deserialize_message_type(ctx); std::tie(err, msg_type) = deserialize_message_type(ctx);
if (err) return; if (err) return;
if (msg_type == error_packet_header) if (msg_type == error_packet_header)
{ {
err = process_error_packet(ctx, info); err = process_error_packet(ctx, info);
} }
else if (msg_type != 0) else if (msg_type != 0)
{ {
err = make_error_code(errc::protocol_value_error); err = make_error_code(errc::protocol_value_error);
} }
else else
{ {
err = deserialize_message(response_, ctx); err = deserialize_message(response_, ctx);
} }
} }
auto& get_buffer() noexcept { return channel_.shared_buffer(); } auto& get_buffer() noexcept { return channel_.shared_buffer(); }
auto& get_channel() noexcept { return channel_; } auto& get_channel() noexcept { return channel_; }
const auto& get_response() const noexcept { return response_; } const auto& get_response() const noexcept { return response_; }
unsigned get_num_metadata_packets() const noexcept unsigned get_num_metadata_packets() const noexcept
{ {
return response_.num_columns.value + response_.num_params.value; return response_.num_columns.value + response_.num_params.value;
} }
}; };
} // detail } // detail
@ -66,145 +66,145 @@ public:
template <typename StreamType> template <typename StreamType>
void boost::mysql::detail::prepare_statement( void boost::mysql::detail::prepare_statement(
channel<StreamType>& channel, channel<StreamType>& channel,
std::string_view statement, std::string_view statement,
error_code& err, error_code& err,
error_info& info, error_info& info,
prepared_statement<StreamType>& output prepared_statement<StreamType>& output
) )
{ {
// Prepare message // Prepare message
prepare_statement_processor<StreamType> processor (channel); prepare_statement_processor<StreamType> processor (channel);
processor.process_request(statement); processor.process_request(statement);
// Write message // Write message
processor.get_channel().write(boost::asio::buffer(processor.get_buffer()), err); processor.get_channel().write(boost::asio::buffer(processor.get_buffer()), err);
if (err) return; if (err) return;
// Read response // Read response
processor.get_channel().read(processor.get_buffer(), err); processor.get_channel().read(processor.get_buffer(), err);
if (err) return; if (err) return;
// Process response // Process response
processor.process_response(err, info); processor.process_response(err, info);
if (err) return; if (err) return;
// Server sends now one packet per parameter and field. // Server sends now one packet per parameter and field.
// We ignore these for now. TODO: do sth useful with these // We ignore these for now. TODO: do sth useful with these
for (unsigned i = 0; i < processor.get_num_metadata_packets(); ++i) for (unsigned i = 0; i < processor.get_num_metadata_packets(); ++i)
{ {
processor.get_channel().read(processor.get_buffer(), err); processor.get_channel().read(processor.get_buffer(), err);
if (err) return; if (err) return;
} }
// Compose response // Compose response
output = prepared_statement<StreamType>(channel, processor.get_response()); output = prepared_statement<StreamType>(channel, processor.get_response());
} }
template <typename StreamType, typename CompletionToken> template <typename StreamType, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
boost::mysql::detail::prepare_statement_signature<StreamType> boost::mysql::detail::prepare_statement_signature<StreamType>
) )
boost::mysql::detail::async_prepare_statement( boost::mysql::detail::async_prepare_statement(
channel<StreamType>& chan, channel<StreamType>& chan,
std::string_view statement, std::string_view statement,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
) )
{ {
using HandlerSignature = prepare_statement_signature<StreamType>; using HandlerSignature = prepare_statement_signature<StreamType>;
using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature);
using BaseType = boost::beast::async_base<HandlerType, typename StreamType::executor_type>; using BaseType = boost::beast::async_base<HandlerType, typename StreamType::executor_type>;
using PreparedStatementType = prepared_statement<StreamType>; using PreparedStatementType = prepared_statement<StreamType>;
boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token); boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token);
struct Op: BaseType, boost::asio::coroutine struct Op: BaseType, boost::asio::coroutine
{ {
prepare_statement_processor<StreamType> processor_; prepare_statement_processor<StreamType> processor_;
unsigned remaining_meta_; unsigned remaining_meta_;
error_info* output_info_; error_info* output_info_;
Op( Op(
HandlerType&& handler, HandlerType&& handler,
channel<StreamType>& channel, channel<StreamType>& channel,
std::string_view statement, std::string_view statement,
error_info* output_info error_info* output_info
): ):
BaseType(std::move(handler), channel.next_layer().get_executor()), BaseType(std::move(handler), channel.next_layer().get_executor()),
processor_(channel), processor_(channel),
remaining_meta_(0), remaining_meta_(0),
output_info_(output_info) output_info_(output_info)
{ {
processor_.process_request(statement); processor_.process_request(statement);
} }
void operator()( void operator()(
error_code err, error_code err,
bool cont=true bool cont=true
) )
{ {
// Error checking // Error checking
if (err) if (err)
{ {
this->complete(cont, err, PreparedStatementType()); this->complete(cont, err, PreparedStatementType());
return; return;
} }
// Regular coroutine body; if there has been an error, we don't get here // Regular coroutine body; if there has been an error, we don't get here
error_info info; error_info info;
reenter(*this) reenter(*this)
{ {
// Write message (already serialized at this point) // Write message (already serialized at this point)
yield processor_.get_channel().async_write( yield processor_.get_channel().async_write(
boost::asio::buffer(processor_.get_buffer()), boost::asio::buffer(processor_.get_buffer()),
std::move(*this) std::move(*this)
); );
// Read response // Read response
yield processor_.get_channel().async_read( yield processor_.get_channel().async_read(
processor_.get_buffer(), processor_.get_buffer(),
std::move(*this) std::move(*this)
); );
// Process response // Process response
processor_.process_response(err, info); processor_.process_response(err, info);
if (err) if (err)
{ {
detail::conditional_assign(output_info_, std::move(info)); detail::conditional_assign(output_info_, std::move(info));
this->complete(cont, err, PreparedStatementType()); this->complete(cont, err, PreparedStatementType());
yield break; yield break;
} }
// Server sends now one packet per parameter and field. // Server sends now one packet per parameter and field.
// We ignore these for now. TODO: do sth useful with these // We ignore these for now. TODO: do sth useful with these
remaining_meta_ = processor_.get_num_metadata_packets(); remaining_meta_ = processor_.get_num_metadata_packets();
for (; remaining_meta_ > 0; --remaining_meta_) for (; remaining_meta_ > 0; --remaining_meta_)
{ {
yield processor_.get_channel().async_read( yield processor_.get_channel().async_read(
processor_.get_buffer(), processor_.get_buffer(),
std::move(*this) std::move(*this)
); );
} }
// Compose response // Compose response
this->complete( this->complete(
cont, cont,
err, err,
PreparedStatementType(processor_.get_channel(), processor_.get_response()) PreparedStatementType(processor_.get_channel(), processor_.get_response())
); );
} }
} }
}; };
Op( Op(
std::move(initiator.completion_handler), std::move(initiator.completion_handler),
chan, chan,
statement, statement,
info info
)(error_code(), false); )(error_code(), false);
return initiator.result.get(); return initiator.result.get();
} }
#include <boost/asio/unyield.hpp> #include <boost/asio/unyield.hpp>

View File

@ -16,44 +16,44 @@ namespace mysql {
namespace detail { namespace detail {
inline read_row_result process_read_message( inline read_row_result process_read_message(
deserialize_row_fn deserializer, deserialize_row_fn deserializer,
capabilities current_capabilities, capabilities current_capabilities,
const std::vector<field_metadata>& meta, const std::vector<field_metadata>& meta,
const bytestring& buffer, const bytestring& buffer,
std::vector<value>& output_values, std::vector<value>& output_values,
ok_packet& output_ok_packet, ok_packet& output_ok_packet,
error_code& err, error_code& err,
error_info& info error_info& info
) )
{ {
assert(deserializer); assert(deserializer);
// Message type: row, error or eof? // Message type: row, error or eof?
std::uint8_t msg_type; std::uint8_t msg_type;
deserialization_context ctx (boost::asio::buffer(buffer), current_capabilities); deserialization_context ctx (boost::asio::buffer(buffer), current_capabilities);
std::tie(err, msg_type) = deserialize_message_type(ctx); std::tie(err, msg_type) = deserialize_message_type(ctx);
if (err) return read_row_result::error; if (err) return read_row_result::error;
if (msg_type == eof_packet_header) if (msg_type == eof_packet_header)
{ {
// end of resultset // end of resultset
err = deserialize_message(output_ok_packet, ctx); err = deserialize_message(output_ok_packet, ctx);
if (err) return read_row_result::error; if (err) return read_row_result::error;
return read_row_result::eof; return read_row_result::eof;
} }
else if (msg_type == error_packet_header) else if (msg_type == error_packet_header)
{ {
// An error occurred during the generation of the rows // An error occurred during the generation of the rows
err = process_error_packet(ctx, info); err = process_error_packet(ctx, info);
return read_row_result::error; return read_row_result::error;
} }
else else
{ {
// An actual row // An actual row
ctx.rewind(1); // keep the 'message type' byte, as it is part of the actual message 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; if (err) return read_row_result::error;
return read_row_result::row; return read_row_result::row;
} }
} }
} // detail } // detail
@ -63,127 +63,127 @@ inline read_row_result process_read_message(
template <typename StreamType> template <typename StreamType>
boost::mysql::detail::read_row_result boost::mysql::detail::read_row( boost::mysql::detail::read_row_result boost::mysql::detail::read_row(
deserialize_row_fn deserializer, deserialize_row_fn deserializer,
channel<StreamType>& channel, channel<StreamType>& channel,
const std::vector<field_metadata>& meta, const std::vector<field_metadata>& meta,
bytestring& buffer, bytestring& buffer,
std::vector<value>& output_values, std::vector<value>& output_values,
ok_packet& output_ok_packet, ok_packet& output_ok_packet,
error_code& err, error_code& err,
error_info& info error_info& info
) )
{ {
// Read a packet // Read a packet
channel.read(buffer, err); channel.read(buffer, err);
if (err) return read_row_result::error; if (err) return read_row_result::error;
return process_read_message( return process_read_message(
deserializer, deserializer,
channel.current_capabilities(), channel.current_capabilities(),
meta, meta,
buffer, buffer,
output_values, output_values,
output_ok_packet, output_ok_packet,
err, err,
info info
); );
} }
template <typename StreamType, typename CompletionToken> template <typename StreamType, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
boost::mysql::detail::read_row_signature boost::mysql::detail::read_row_signature
) )
boost::mysql::detail::async_read_row( boost::mysql::detail::async_read_row(
deserialize_row_fn deserializer, deserialize_row_fn deserializer,
channel<StreamType>& chan, channel<StreamType>& chan,
const std::vector<field_metadata>& meta, const std::vector<field_metadata>& meta,
bytestring& buffer, bytestring& buffer,
std::vector<value>& output_values, std::vector<value>& output_values,
ok_packet& output_ok_packet, ok_packet& output_ok_packet,
CompletionToken&& token CompletionToken&& token
) )
{ {
using HandlerSignature = read_row_signature; using HandlerSignature = read_row_signature;
using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature);
using BaseType = boost::beast::async_base<HandlerType, typename StreamType::executor_type>; using BaseType = boost::beast::async_base<HandlerType, typename StreamType::executor_type>;
struct Op: BaseType, boost::asio::coroutine struct Op: BaseType, boost::asio::coroutine
{ {
deserialize_row_fn deserializer_; deserialize_row_fn deserializer_;
channel<StreamType>& channel_; channel<StreamType>& channel_;
const std::vector<field_metadata>& meta_; const std::vector<field_metadata>& meta_;
bytestring& buffer_; bytestring& buffer_;
std::vector<value>& output_values_; std::vector<value>& output_values_;
ok_packet& output_ok_packet_; ok_packet& output_ok_packet_;
Op( Op(
HandlerType&& handler, HandlerType&& handler,
deserialize_row_fn deserializer, deserialize_row_fn deserializer,
channel<StreamType>& channel, channel<StreamType>& channel,
const std::vector<field_metadata>& meta, const std::vector<field_metadata>& meta,
bytestring& buffer, bytestring& buffer,
std::vector<value>& output_values, std::vector<value>& output_values,
ok_packet& output_ok_packet ok_packet& output_ok_packet
): ):
BaseType(std::move(handler), channel.next_layer().get_executor()), BaseType(std::move(handler), channel.next_layer().get_executor()),
deserializer_(deserializer), deserializer_(deserializer),
channel_(channel), channel_(channel),
meta_(meta), meta_(meta),
buffer_(buffer), buffer_(buffer),
output_values_(output_values), output_values_(output_values),
output_ok_packet_(output_ok_packet) output_ok_packet_(output_ok_packet)
{ {
} }
void process_result(bool cont) void process_result(bool cont)
{ {
error_code err; error_code err;
error_info info; error_info info;
auto result = process_read_message( auto result = process_read_message(
deserializer_, deserializer_,
channel_.current_capabilities(), channel_.current_capabilities(),
meta_, meta_,
buffer_, buffer_,
output_values_, output_values_,
output_ok_packet_, output_ok_packet_,
err, err,
info info
); );
this->complete(cont, err, info, result); this->complete(cont, err, info, result);
} }
void operator()( void operator()(
error_code err, error_code err,
bool cont=true bool cont=true
) )
{ {
reenter(*this) reenter(*this)
{ {
yield channel_.async_read(buffer_, std::move(*this)); yield channel_.async_read(buffer_, std::move(*this));
if (err) if (err)
{ {
this->complete(cont, err, error_info(), read_row_result::error); this->complete(cont, err, error_info(), read_row_result::error);
yield break; yield break;
} }
process_result(cont); process_result(cont);
} }
} }
}; };
boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token); boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token);
Op( Op(
std::move(initiator.completion_handler), std::move(initiator.completion_handler),
deserializer, deserializer,
chan, chan,
meta, meta,
buffer, buffer,
output_values, output_values,
output_ok_packet output_ok_packet
)(error_code(), false); )(error_code(), false);
return initiator.result.get(); return initiator.result.get();
} }
#include <boost/asio/unyield.hpp> #include <boost/asio/unyield.hpp>

View File

@ -17,11 +17,11 @@ namespace detail {
template <typename StreamType> template <typename StreamType>
void prepare_statement( void prepare_statement(
::boost::mysql::detail::channel<StreamType>& channel, ::boost::mysql::detail::channel<StreamType>& channel,
std::string_view statement, std::string_view statement,
error_code& err, error_code& err,
error_info& info, error_info& info,
prepared_statement<StreamType>& output prepared_statement<StreamType>& output
); );
template <typename StreamType> template <typename StreamType>
@ -30,10 +30,10 @@ using prepare_statement_signature = void(error_code, prepared_statement<StreamTy
template <typename StreamType, typename CompletionToken> template <typename StreamType, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, prepare_statement_signature<StreamType>) BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, prepare_statement_signature<StreamType>)
async_prepare_statement( async_prepare_statement(
channel<StreamType>& channel, channel<StreamType>& channel,
std::string_view statement, std::string_view statement,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
); );
} // detail } // detail

View File

@ -19,21 +19,21 @@ namespace detail {
enum class read_row_result enum class read_row_result
{ {
error, error,
row, row,
eof eof
}; };
template <typename StreamType> template <typename StreamType>
read_row_result read_row( read_row_result read_row(
deserialize_row_fn deserializer, deserialize_row_fn deserializer,
channel<StreamType>& channel, channel<StreamType>& channel,
const std::vector<field_metadata>& meta, const std::vector<field_metadata>& meta,
bytestring& buffer, bytestring& buffer,
std::vector<value>& output_values, std::vector<value>& output_values,
ok_packet& output_ok_packet, ok_packet& output_ok_packet,
error_code& err, error_code& err,
error_info& info error_info& info
); );
using read_row_signature = void(error_code, error_info, read_row_result); using read_row_signature = void(error_code, error_info, read_row_result);
@ -41,13 +41,13 @@ using read_row_signature = void(error_code, error_info, read_row_result);
template <typename StreamType, typename CompletionToken> template <typename StreamType, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, read_row_signature) BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, read_row_signature)
async_read_row( async_read_row(
deserialize_row_fn deserializer, deserialize_row_fn deserializer,
channel<StreamType>& channel, channel<StreamType>& channel,
const std::vector<field_metadata>& meta, const std::vector<field_metadata>& meta,
bytestring& buffer, bytestring& buffer,
std::vector<value>& output_values, std::vector<value>& output_values,
ok_packet& output_ok_packet, ok_packet& output_ok_packet,
CompletionToken&& token CompletionToken&& token
); );

View File

@ -19,15 +19,15 @@ namespace mysql {
namespace detail { namespace detail {
inline errc deserialize_binary_value( inline errc deserialize_binary_value(
deserialization_context& ctx, deserialization_context& ctx,
const field_metadata& meta, const field_metadata& meta,
value& output value& output
); );
inline error_code deserialize_binary_row( inline error_code deserialize_binary_row(
deserialization_context& ctx, deserialization_context& ctx,
const std::vector<field_metadata>& meta, const std::vector<field_metadata>& meta,
std::vector<value>& output std::vector<value>& output
); );
} // detail } // detail

View File

@ -46,17 +46,17 @@ constexpr std::uint32_t CLIENT_REMEMBER_OPTIONS = (1UL << 31); // Don't reset th
class capabilities class capabilities
{ {
std::uint32_t value_; std::uint32_t value_;
public: public:
constexpr explicit capabilities(std::uint32_t value=0) noexcept : value_(value) {}; constexpr explicit capabilities(std::uint32_t value=0) noexcept : value_(value) {};
constexpr std::uint32_t get() const noexcept { return value_; } constexpr std::uint32_t get() const noexcept { return value_; }
constexpr void set(std::uint32_t value) noexcept { value_ = value; } constexpr void set(std::uint32_t value) noexcept { value_ = value; }
constexpr bool has(std::uint32_t cap) const noexcept { return value_ & cap; } constexpr bool has(std::uint32_t cap) const noexcept { return value_ & cap; }
constexpr bool has_all(capabilities other) const noexcept { return (value_ & other.get()) == other.get(); } constexpr bool has_all(capabilities other) const noexcept { return (value_ & other.get()) == other.get(); }
constexpr capabilities operator|(capabilities rhs) const noexcept { return capabilities(value_ | rhs.value_); } constexpr capabilities operator|(capabilities rhs) const noexcept { return capabilities(value_ | rhs.value_); }
constexpr capabilities operator&(capabilities rhs) const noexcept { return capabilities(value_ & rhs.value_); } constexpr capabilities operator&(capabilities rhs) const noexcept { return capabilities(value_ & rhs.value_); }
constexpr bool operator==(const capabilities& rhs) const noexcept { return value_ == rhs.value_; } constexpr bool operator==(const capabilities& rhs) const noexcept { return value_ == rhs.value_; }
constexpr bool operator!=(const capabilities& rhs) const noexcept { return value_ != rhs.value_; } constexpr bool operator!=(const capabilities& rhs) const noexcept { return value_ != rhs.value_; }
}; };
/* /*
@ -98,11 +98,11 @@ public:
*/ */
constexpr capabilities mandatory_capabilities { constexpr capabilities mandatory_capabilities {
CLIENT_PROTOCOL_41 | CLIENT_PROTOCOL_41 |
CLIENT_PLUGIN_AUTH | CLIENT_PLUGIN_AUTH |
CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA |
CLIENT_DEPRECATE_EOF | CLIENT_DEPRECATE_EOF |
CLIENT_SECURE_CONNECTION CLIENT_SECURE_CONNECTION
}; };
constexpr capabilities optional_capabilities {0}; constexpr capabilities optional_capabilities {0};

View File

@ -27,79 +27,79 @@ namespace detail {
template <typename AsyncStream> template <typename AsyncStream>
class channel class channel
{ {
// TODO: static asserts for AsyncStream concept // TODO: static asserts for AsyncStream concept
// TODO: actually we also require it to be SyncStream, name misleading // TODO: actually we also require it to be SyncStream, name misleading
struct ssl_block struct ssl_block
{ {
boost::asio::ssl::context ctx; boost::asio::ssl::context ctx;
boost::asio::ssl::stream<AsyncStream&> stream; boost::asio::ssl::stream<AsyncStream&> stream;
ssl_block(AsyncStream& base_stream): ssl_block(AsyncStream& base_stream):
ctx(boost::asio::ssl::context::tls_client), ctx(boost::asio::ssl::context::tls_client),
stream (base_stream, ctx) {} stream (base_stream, ctx) {}
}; };
AsyncStream& stream_; AsyncStream& stream_;
std::optional<ssl_block> ssl_block_; std::optional<ssl_block> ssl_block_;
std::uint8_t sequence_number_ {0}; std::uint8_t sequence_number_ {0};
std::array<std::uint8_t, 4> header_buffer_ {}; // for async ops std::array<std::uint8_t, 4> header_buffer_ {}; // for async ops
bytestring shared_buff_; // for async ops bytestring shared_buff_; // for async ops
capabilities current_caps_; capabilities current_caps_;
bool process_sequence_number(std::uint8_t got); bool process_sequence_number(std::uint8_t got);
std::uint8_t next_sequence_number() { return sequence_number_++; } std::uint8_t next_sequence_number() { return sequence_number_++; }
error_code process_header_read(std::uint32_t& size_to_read); // reads from header_buffer_ error_code process_header_read(std::uint32_t& size_to_read); // reads from header_buffer_
void process_header_write(std::uint32_t size_to_write); // writes to header_buffer_ void process_header_write(std::uint32_t size_to_write); // writes to header_buffer_
void create_ssl_block() { ssl_block_.emplace(stream_); } void create_ssl_block() { ssl_block_.emplace(stream_); }
template <typename BufferSeq> template <typename BufferSeq>
std::size_t read_impl(BufferSeq&& buff, error_code& ec); std::size_t read_impl(BufferSeq&& buff, error_code& ec);
template <typename BufferSeq> template <typename BufferSeq>
std::size_t write_impl(BufferSeq&& buff, error_code& ec); std::size_t write_impl(BufferSeq&& buff, error_code& ec);
template <typename BufferSeq, typename CompletionToken> template <typename BufferSeq, typename CompletionToken>
auto async_read_impl(BufferSeq&& buff, CompletionToken&& token); auto async_read_impl(BufferSeq&& buff, CompletionToken&& token);
template <typename BufferSeq, typename CompletionToken> template <typename BufferSeq, typename CompletionToken>
auto async_write_impl(BufferSeq&& buff, CompletionToken&& token); auto async_write_impl(BufferSeq&& buff, CompletionToken&& token);
public: public:
channel(AsyncStream& stream): stream_(stream) {} channel(AsyncStream& stream): stream_(stream) {}
bool ssl_active() const noexcept { return ssl_block_.has_value(); } bool ssl_active() const noexcept { return ssl_block_.has_value(); }
template <typename Allocator> template <typename Allocator>
void read(basic_bytestring<Allocator>& buffer, error_code& code); void read(basic_bytestring<Allocator>& buffer, error_code& code);
void write(boost::asio::const_buffer buffer, error_code& code); void write(boost::asio::const_buffer buffer, error_code& code);
template <typename Allocator, typename CompletionToken> template <typename Allocator, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code)) BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code))
async_read(basic_bytestring<Allocator>& buffer, CompletionToken&& token); async_read(basic_bytestring<Allocator>& buffer, CompletionToken&& token);
template <typename CompletionToken> template <typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code)) BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code))
async_write(boost::asio::const_buffer buffer, CompletionToken&& token); async_write(boost::asio::const_buffer buffer, CompletionToken&& token);
void ssl_handshake(error_code& ec); void ssl_handshake(error_code& ec);
template <typename CompletionToken> template <typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code)) BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code))
async_ssl_handshake(CompletionToken&& token); async_ssl_handshake(CompletionToken&& token);
void reset_sequence_number(std::uint8_t value = 0) { sequence_number_ = value; } void reset_sequence_number(std::uint8_t value = 0) { sequence_number_ = value; }
std::uint8_t sequence_number() const { return sequence_number_; } std::uint8_t sequence_number() const { return sequence_number_; }
using stream_type = AsyncStream; using stream_type = AsyncStream;
stream_type& next_layer() { return stream_; } stream_type& next_layer() { return stream_; }
capabilities current_capabilities() const noexcept { return current_caps_; } capabilities current_capabilities() const noexcept { return current_caps_; }
void set_current_capabilities(capabilities value) noexcept { current_caps_ = value; } void set_current_capabilities(capabilities value) noexcept { current_caps_ = value; }
const bytestring& shared_buffer() const noexcept { return shared_buff_; } const bytestring& shared_buffer() const noexcept { return shared_buff_; }
bytestring& shared_buffer() noexcept { return shared_buff_; } bytestring& shared_buffer() noexcept { return shared_buff_; }
}; };
template <typename ChannelType> template <typename ChannelType>

View File

@ -20,110 +20,110 @@ namespace detail {
// header // header
struct packet_header struct packet_header
{ {
int3 packet_size; int3 packet_size;
int1 sequence_number; int1 sequence_number;
}; };
template <> template <>
struct get_struct_fields<packet_header> struct get_struct_fields<packet_header>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&packet_header::packet_size, &packet_header::packet_size,
&packet_header::sequence_number &packet_header::sequence_number
); );
}; };
// ok packet // ok packet
struct ok_packet struct ok_packet
{ {
// header: int<1> header 0x00 or 0xFE the OK packet header // header: int<1> header 0x00 or 0xFE the OK packet header
int_lenenc affected_rows; int_lenenc affected_rows;
int_lenenc last_insert_id; int_lenenc last_insert_id;
int2 status_flags; // server_status_flags int2 status_flags; // server_status_flags
int2 warnings; int2 warnings;
// TODO: CLIENT_SESSION_TRACK // TODO: CLIENT_SESSION_TRACK
string_lenenc info; string_lenenc info;
}; };
template <> template <>
struct get_struct_fields<ok_packet> struct get_struct_fields<ok_packet>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&ok_packet::affected_rows, &ok_packet::affected_rows,
&ok_packet::last_insert_id, &ok_packet::last_insert_id,
&ok_packet::status_flags, &ok_packet::status_flags,
&ok_packet::warnings, &ok_packet::warnings,
&ok_packet::info &ok_packet::info
); );
}; };
template <> template <>
struct serialization_traits<ok_packet, serialization_tag::struct_with_fields> : struct serialization_traits<ok_packet, serialization_tag::struct_with_fields> :
noop_serialize<ok_packet> noop_serialize<ok_packet>
{ {
static inline errc deserialize_(ok_packet& output, deserialization_context& ctx) noexcept; static inline errc deserialize_(ok_packet& output, deserialization_context& ctx) noexcept;
}; };
// err packet // err packet
struct err_packet struct err_packet
{ {
// int<1> header 0xFF ERR packet header // int<1> header 0xFF ERR packet header
int2 error_code; int2 error_code;
string_fixed<1> sql_state_marker; string_fixed<1> sql_state_marker;
string_fixed<5> sql_state; string_fixed<5> sql_state;
string_eof error_message; string_eof error_message;
}; };
template <> template <>
struct get_struct_fields<err_packet> struct get_struct_fields<err_packet>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&err_packet::error_code, &err_packet::error_code,
&err_packet::sql_state_marker, &err_packet::sql_state_marker,
&err_packet::sql_state, &err_packet::sql_state,
&err_packet::error_message &err_packet::error_message
); );
}; };
// col def // col def
struct column_definition_packet struct column_definition_packet
{ {
string_lenenc catalog; // always "def" string_lenenc catalog; // always "def"
string_lenenc schema; string_lenenc schema;
string_lenenc table; // virtual table string_lenenc table; // virtual table
string_lenenc org_table; // physical table string_lenenc org_table; // physical table
string_lenenc name; // virtual column name string_lenenc name; // virtual column name
string_lenenc org_name; // physical column name string_lenenc org_name; // physical column name
collation character_set; collation character_set;
int4 column_length; // maximum length of the field int4 column_length; // maximum length of the field
protocol_field_type type; // type of the column as defined in enum_field_types protocol_field_type type; // type of the column as defined in enum_field_types
int2 flags; // Flags as defined in Column Definition Flags int2 flags; // Flags as defined in Column Definition Flags
int1 decimals; // max shown decimal digits. 0x00 for int/static strings; 0x1f for dynamic strings, double, float int1 decimals; // max shown decimal digits. 0x00 for int/static strings; 0x1f for dynamic strings, double, float
}; };
template <> template <>
struct get_struct_fields<column_definition_packet> struct get_struct_fields<column_definition_packet>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&column_definition_packet::catalog, &column_definition_packet::catalog,
&column_definition_packet::schema, &column_definition_packet::schema,
&column_definition_packet::table, &column_definition_packet::table,
&column_definition_packet::org_table, &column_definition_packet::org_table,
&column_definition_packet::name, &column_definition_packet::name,
&column_definition_packet::org_name, &column_definition_packet::org_name,
&column_definition_packet::character_set, &column_definition_packet::character_set,
&column_definition_packet::column_length, &column_definition_packet::column_length,
&column_definition_packet::type, &column_definition_packet::type,
&column_definition_packet::flags, &column_definition_packet::flags,
&column_definition_packet::decimals &column_definition_packet::decimals
); );
}; };
template <> template <>
struct serialization_traits<column_definition_packet, serialization_tag::struct_with_fields> : struct serialization_traits<column_definition_packet, serialization_tag::struct_with_fields> :
noop_serialize<column_definition_packet> noop_serialize<column_definition_packet>
{ {
static inline errc deserialize_(column_definition_packet& output, deserialization_context& ctx) noexcept; static inline errc deserialize_(column_definition_packet& output, deserialization_context& ctx) noexcept;
}; };
// aux // aux

View File

@ -16,32 +16,32 @@ namespace detail {
enum class protocol_field_type : std::uint8_t enum class protocol_field_type : std::uint8_t
{ {
decimal = 0x00, // Apparently not sent decimal = 0x00, // Apparently not sent
tiny = 0x01, // TINYINT tiny = 0x01, // TINYINT
short_ = 0x02, // SMALLINT short_ = 0x02, // SMALLINT
long_ = 0x03, // INT long_ = 0x03, // INT
float_ = 0x04, // FLOAT float_ = 0x04, // FLOAT
double_ = 0x05, // DOUBLE double_ = 0x05, // DOUBLE
null = 0x06, // Apparently not sent null = 0x06, // Apparently not sent
timestamp = 0x07, // TIMESTAMP timestamp = 0x07, // TIMESTAMP
longlong = 0x08, // BIGINT longlong = 0x08, // BIGINT
int24 = 0x09, // MEDIUMINT int24 = 0x09, // MEDIUMINT
date = 0x0a, // DATE date = 0x0a, // DATE
time = 0x0b, // TIME time = 0x0b, // TIME
datetime = 0x0c, // DATETIME datetime = 0x0c, // DATETIME
year = 0x0d, // YEAR year = 0x0d, // YEAR
varchar = 0x0f, // Apparently not sent varchar = 0x0f, // Apparently not sent
bit = 0x10, // BIT bit = 0x10, // BIT
newdecimal = 0xf6, // DECIMAL newdecimal = 0xf6, // DECIMAL
enum_ = 0xf7, // Apparently not sent enum_ = 0xf7, // Apparently not sent
set = 0xf8, // Apperently not sent set = 0xf8, // Apperently not sent
tiny_blob = 0xf9, // Apparently not sent tiny_blob = 0xf9, // Apparently not sent
medium_blob = 0xfa,// Apparently not sent medium_blob = 0xfa,// Apparently not sent
long_blob = 0xfb, // Apparently not sent long_blob = 0xfb, // Apparently not sent
blob = 0xfc, // Used for all TEXT and BLOB types blob = 0xfc, // Used for all TEXT and BLOB types
var_string = 0xfd, // Used for VARCHAR and VARBINARY var_string = 0xfd, // Used for VARCHAR and VARBINARY
string = 0xfe, // Used for CHAR and BINARY, ENUM (enum flag set), SET (set flag set) string = 0xfe, // Used for CHAR and BINARY, ENUM (enum flag set), SET (set flag set)
geometry = 0xff // GEOMETRY geometry = 0xff // GEOMETRY
}; };

View File

@ -21,34 +21,34 @@ namespace detail {
class deserialization_context class deserialization_context
{ {
const std::uint8_t* first_; const std::uint8_t* first_;
const std::uint8_t* last_; const std::uint8_t* last_;
capabilities capabilities_; capabilities capabilities_;
public: public:
deserialization_context(const std::uint8_t* first, const std::uint8_t* last, capabilities caps) noexcept: deserialization_context(const std::uint8_t* first, const std::uint8_t* last, capabilities caps) noexcept:
first_(first), last_(last), capabilities_(caps) { assert(last_ >= first_); }; first_(first), last_(last), capabilities_(caps) { assert(last_ >= first_); };
deserialization_context(boost::asio::const_buffer buff, capabilities caps) noexcept: deserialization_context(boost::asio::const_buffer buff, capabilities caps) noexcept:
deserialization_context( deserialization_context(
static_cast<const std::uint8_t*>(buff.data()), static_cast<const std::uint8_t*>(buff.data()),
static_cast<const std::uint8_t*>(buff.data()) + buff.size(), static_cast<const std::uint8_t*>(buff.data()) + buff.size(),
caps caps
) {}; ) {};
const std::uint8_t* first() const noexcept { return first_; } const std::uint8_t* first() const noexcept { return first_; }
const std::uint8_t* last() const noexcept { return last_; } const std::uint8_t* last() const noexcept { return last_; }
void set_first(const std::uint8_t* new_first) noexcept { first_ = new_first; assert(last_ >= first_); } void set_first(const std::uint8_t* new_first) noexcept { first_ = new_first; assert(last_ >= first_); }
void advance(std::size_t sz) noexcept { first_ += sz; assert(last_ >= first_); } void advance(std::size_t sz) noexcept { first_ += sz; assert(last_ >= first_); }
void rewind(std::size_t sz) noexcept { first_ -= sz; } void rewind(std::size_t sz) noexcept { first_ -= sz; }
std::size_t size() const noexcept { return last_ - first_; } std::size_t size() const noexcept { return last_ - first_; }
bool empty() const noexcept { return last_ == first_; } bool empty() const noexcept { return last_ == first_; }
bool enough_size(std::size_t required_size) const noexcept { return size() >= required_size; } bool enough_size(std::size_t required_size) const noexcept { return size() >= required_size; }
capabilities get_capabilities() const noexcept { return capabilities_; } capabilities get_capabilities() const noexcept { return capabilities_; }
errc copy(void* to, std::size_t sz) noexcept errc copy(void* to, std::size_t sz) noexcept
{ {
if (!enough_size(sz)) return errc::incomplete_message; if (!enough_size(sz)) return errc::incomplete_message;
memcpy(to, first_, sz); memcpy(to, first_, sz);
advance(sz); advance(sz);
return errc::ok; return errc::ok;
} }
}; };
} }

View File

@ -17,144 +17,144 @@ namespace detail {
// initial handshake // initial handshake
struct handshake_packet struct handshake_packet
{ {
// int<1> protocol version Always 10 // int<1> protocol version Always 10
string_null server_version; string_null server_version;
int4 connection_id; int4 connection_id;
string_lenenc auth_plugin_data; // not an actual protocol field, the merge of two fields string_lenenc auth_plugin_data; // not an actual protocol field, the merge of two fields
int4 capability_falgs; // merge of the two parts - not an actual field int4 capability_falgs; // merge of the two parts - not an actual field
int1 character_set; // default server a_protocol_character_set, only the lower 8-bits int1 character_set; // default server a_protocol_character_set, only the lower 8-bits
int2 status_flags; // server_status_flags int2 status_flags; // server_status_flags
string_null auth_plugin_name; string_null auth_plugin_name;
std::array<char, 8 + 0xff> auth_plugin_data_buffer; // not an actual protocol field, the merge of two fields std::array<char, 8 + 0xff> auth_plugin_data_buffer; // not an actual protocol field, the merge of two fields
}; };
template <> template <>
struct get_struct_fields<handshake_packet> struct get_struct_fields<handshake_packet>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&handshake_packet::server_version, &handshake_packet::server_version,
&handshake_packet::connection_id, &handshake_packet::connection_id,
&handshake_packet::auth_plugin_data, &handshake_packet::auth_plugin_data,
&handshake_packet::capability_falgs, &handshake_packet::capability_falgs,
&handshake_packet::character_set, &handshake_packet::character_set,
&handshake_packet::status_flags, &handshake_packet::status_flags,
&handshake_packet::auth_plugin_name &handshake_packet::auth_plugin_name
); );
}; };
template <> template <>
struct serialization_traits<handshake_packet, serialization_tag::struct_with_fields> : struct serialization_traits<handshake_packet, serialization_tag::struct_with_fields> :
noop_serialize<handshake_packet> noop_serialize<handshake_packet>
{ {
static inline errc deserialize_(handshake_packet& output, deserialization_context& ctx) noexcept; static inline errc deserialize_(handshake_packet& output, deserialization_context& ctx) noexcept;
}; };
// response // response
struct handshake_response_packet struct handshake_response_packet
{ {
int4 client_flag; // capabilities int4 client_flag; // capabilities
int4 max_packet_size; int4 max_packet_size;
int1 character_set; int1 character_set;
// string[23] filler filler to the size of the handhshake response packet. All 0s. // string[23] filler filler to the size of the handhshake response packet. All 0s.
string_null username; string_null username;
string_lenenc auth_response; // we require CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA string_lenenc auth_response; // we require CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
string_null database; // only to be serialized if CLIENT_CONNECT_WITH_DB string_null database; // only to be serialized if CLIENT_CONNECT_WITH_DB
string_null client_plugin_name; // we require CLIENT_PLUGIN_AUTH string_null client_plugin_name; // we require CLIENT_PLUGIN_AUTH
// TODO: CLIENT_CONNECT_ATTRS // TODO: CLIENT_CONNECT_ATTRS
}; };
template <> template <>
struct get_struct_fields<handshake_response_packet> struct get_struct_fields<handshake_response_packet>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&handshake_response_packet::client_flag, &handshake_response_packet::client_flag,
&handshake_response_packet::max_packet_size, &handshake_response_packet::max_packet_size,
&handshake_response_packet::character_set, &handshake_response_packet::character_set,
&handshake_response_packet::username, &handshake_response_packet::username,
&handshake_response_packet::auth_response, &handshake_response_packet::auth_response,
&handshake_response_packet::database, &handshake_response_packet::database,
&handshake_response_packet::client_plugin_name &handshake_response_packet::client_plugin_name
); );
}; };
template <> template <>
struct serialization_traits<handshake_response_packet, serialization_tag::struct_with_fields> : struct serialization_traits<handshake_response_packet, serialization_tag::struct_with_fields> :
noop_deserialize<handshake_response_packet> noop_deserialize<handshake_response_packet>
{ {
static inline std::size_t get_size_(const handshake_response_packet& value, const serialization_context& ctx) noexcept; static inline std::size_t get_size_(const handshake_response_packet& value, const serialization_context& ctx) noexcept;
static inline void serialize_(const handshake_response_packet& value, serialization_context& ctx) noexcept; static inline void serialize_(const handshake_response_packet& value, serialization_context& ctx) noexcept;
}; };
// SSL request // SSL request
struct ssl_request struct ssl_request
{ {
int4 client_flag; int4 client_flag;
int4 max_packet_size; int4 max_packet_size;
int1 character_set; int1 character_set;
string_fixed<23> filler {}; string_fixed<23> filler {};
}; };
template <> template <>
struct get_struct_fields<ssl_request> struct get_struct_fields<ssl_request>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&ssl_request::client_flag, &ssl_request::client_flag,
&ssl_request::max_packet_size, &ssl_request::max_packet_size,
&ssl_request::character_set, &ssl_request::character_set,
&ssl_request::filler &ssl_request::filler
); );
}; };
// auth switch request // auth switch request
struct auth_switch_request_packet struct auth_switch_request_packet
{ {
string_null plugin_name; string_null plugin_name;
string_eof auth_plugin_data; string_eof auth_plugin_data;
}; };
template <> template <>
struct get_struct_fields<auth_switch_request_packet> struct get_struct_fields<auth_switch_request_packet>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&auth_switch_request_packet::plugin_name, &auth_switch_request_packet::plugin_name,
&auth_switch_request_packet::auth_plugin_data &auth_switch_request_packet::auth_plugin_data
); );
}; };
// response // response
struct auth_switch_response_packet struct auth_switch_response_packet
{ {
string_eof auth_plugin_data; string_eof auth_plugin_data;
}; };
template <> template <>
struct get_struct_fields<auth_switch_response_packet> struct get_struct_fields<auth_switch_response_packet>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&auth_switch_response_packet::auth_plugin_data &auth_switch_response_packet::auth_plugin_data
); );
}; };
template <> template <>
struct serialization_traits<auth_switch_request_packet, serialization_tag::struct_with_fields> : struct serialization_traits<auth_switch_request_packet, serialization_tag::struct_with_fields> :
noop_serialize<auth_switch_request_packet> noop_serialize<auth_switch_request_packet>
{ {
static inline errc deserialize_(auth_switch_request_packet& output, deserialization_context& ctx) noexcept; static inline errc deserialize_(auth_switch_request_packet& output, deserialization_context& ctx) noexcept;
}; };
// more data (like auth switch request, but for the same plugin) // more data (like auth switch request, but for the same plugin)
struct auth_more_data_packet struct auth_more_data_packet
{ {
string_eof auth_plugin_data; string_eof auth_plugin_data;
}; };
template <> template <>
struct get_struct_fields<auth_more_data_packet> struct get_struct_fields<auth_more_data_packet>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&auth_more_data_packet::auth_plugin_data &auth_more_data_packet::auth_plugin_data
); );
}; };

View File

@ -18,58 +18,58 @@ namespace mysql {
namespace detail { namespace detail {
using binary_protocol_value = std::variant< using binary_protocol_value = std::variant<
int1, int1,
int2, int2,
int4, int4,
int8, int8,
int1_signed, int1_signed,
int2_signed, int2_signed,
int4_signed, int4_signed,
int8_signed, int8_signed,
string_lenenc, string_lenenc,
float, float,
double, double,
date, date,
datetime, datetime,
time time
>; >;
template <typename SignedType, typename UnsignedType> template <typename SignedType, typename UnsignedType>
binary_protocol_value get_int_deserializable_type( binary_protocol_value get_int_deserializable_type(
const field_metadata& meta const field_metadata& meta
) )
{ {
return meta.is_unsigned() ? binary_protocol_value(UnsignedType()) : return meta.is_unsigned() ? binary_protocol_value(UnsignedType()) :
binary_protocol_value(SignedType()); binary_protocol_value(SignedType());
} }
inline binary_protocol_value get_deserializable_type( inline binary_protocol_value get_deserializable_type(
const field_metadata& meta const field_metadata& meta
) )
{ {
switch (meta.protocol_type()) switch (meta.protocol_type())
{ {
case protocol_field_type::tiny: case protocol_field_type::tiny:
return get_int_deserializable_type<int1_signed, int1>(meta); return get_int_deserializable_type<int1_signed, int1>(meta);
case protocol_field_type::short_: case protocol_field_type::short_:
case protocol_field_type::year: case protocol_field_type::year:
return get_int_deserializable_type<int2_signed, int2>(meta); return get_int_deserializable_type<int2_signed, int2>(meta);
case protocol_field_type::int24: case protocol_field_type::int24:
case protocol_field_type::long_: case protocol_field_type::long_:
return get_int_deserializable_type<int4_signed, int4>(meta); return get_int_deserializable_type<int4_signed, int4>(meta);
case protocol_field_type::longlong: case protocol_field_type::longlong:
return get_int_deserializable_type<int8_signed, int8>(meta); return get_int_deserializable_type<int8_signed, int8>(meta);
case protocol_field_type::float_: case protocol_field_type::float_:
return float(); return float();
case protocol_field_type::double_: case protocol_field_type::double_:
return double(); return double();
case protocol_field_type::timestamp: case protocol_field_type::timestamp:
case protocol_field_type::datetime: case protocol_field_type::datetime:
return datetime(); return datetime();
case protocol_field_type::date: case protocol_field_type::date:
return date(); return date();
case protocol_field_type::time: case protocol_field_type::time:
return time(); return time();
// True string types // True string types
case protocol_field_type::varchar: case protocol_field_type::varchar:
case protocol_field_type::var_string: case protocol_field_type::var_string:
@ -86,8 +86,8 @@ inline binary_protocol_value get_deserializable_type(
case protocol_field_type::newdecimal: case protocol_field_type::newdecimal:
case protocol_field_type::geometry: case protocol_field_type::geometry:
default: default:
return string_lenenc(); return string_lenenc();
} }
} }
} // detail } // detail
@ -95,76 +95,76 @@ inline binary_protocol_value get_deserializable_type(
} // boost } // boost
inline boost::mysql::errc boost::mysql::detail::deserialize_binary_value( inline boost::mysql::errc boost::mysql::detail::deserialize_binary_value(
deserialization_context& ctx, deserialization_context& ctx,
const field_metadata& meta, const field_metadata& meta,
value& output value& output
) )
{ {
auto protocol_value = get_deserializable_type(meta); auto protocol_value = get_deserializable_type(meta);
return std::visit([&output, &ctx](auto typed_protocol_value) { return std::visit([&output, &ctx](auto typed_protocol_value) {
using type = decltype(typed_protocol_value); using type = decltype(typed_protocol_value);
auto err = deserialize(typed_protocol_value, ctx); auto err = deserialize(typed_protocol_value, ctx);
if (err == errc::ok) if (err == errc::ok)
{ {
if constexpr (std::is_constructible_v<value, type>) // not a value holder if constexpr (std::is_constructible_v<value, type>) // not a value holder
{ {
output = typed_protocol_value; output = typed_protocol_value;
} }
else if constexpr (is_one_of_v<type, int1, int2>) else if constexpr (is_one_of_v<type, int1, int2>)
{ {
// regular promotion would make this int32_t. Force it be uint32_t // regular promotion would make this int32_t. Force it be uint32_t
// TODO: check here // TODO: check here
output = std::uint32_t(typed_protocol_value.value); output = std::uint32_t(typed_protocol_value.value);
} }
else else
{ {
output = typed_protocol_value.value; output = typed_protocol_value.value;
} }
} }
return err; return err;
}, protocol_value); }, protocol_value);
} }
inline boost::mysql::error_code boost::mysql::detail::deserialize_binary_row( inline boost::mysql::error_code boost::mysql::detail::deserialize_binary_row(
deserialization_context& ctx, deserialization_context& ctx,
const std::vector<field_metadata>& meta, const std::vector<field_metadata>& meta,
std::vector<value>& output std::vector<value>& output
) )
{ {
// Skip packet header (it is not part of the message in the binary // Skip packet header (it is not part of the message in the binary
// protocol but it is in the text protocol, so we include it for homogeneity) // protocol but it is in the text protocol, so we include it for homogeneity)
// The caller will have checked we have this byte already for us // The caller will have checked we have this byte already for us
assert(ctx.enough_size(1)); assert(ctx.enough_size(1));
ctx.advance(1); ctx.advance(1);
// Number of fields // Number of fields
auto num_fields = meta.size(); auto num_fields = meta.size();
output.resize(num_fields); output.resize(num_fields);
// Null bitmap // Null bitmap
null_bitmap_traits null_bitmap (binary_row_null_bitmap_offset, num_fields); null_bitmap_traits null_bitmap (binary_row_null_bitmap_offset, num_fields);
const std::uint8_t* null_bitmap_begin = ctx.first(); const std::uint8_t* null_bitmap_begin = ctx.first();
if (!ctx.enough_size(null_bitmap.byte_count())) return make_error_code(errc::incomplete_message); if (!ctx.enough_size(null_bitmap.byte_count())) return make_error_code(errc::incomplete_message);
ctx.advance(null_bitmap.byte_count()); ctx.advance(null_bitmap.byte_count());
// Actual values // Actual values
for (std::vector<value>::size_type i = 0; i < output.size(); ++i) for (std::vector<value>::size_type i = 0; i < output.size(); ++i)
{ {
if (null_bitmap.is_null(null_bitmap_begin, i)) if (null_bitmap.is_null(null_bitmap_begin, i))
{ {
output[i] = nullptr; output[i] = nullptr;
} }
else else
{ {
auto err = deserialize_binary_value(ctx, meta[i], output[i]); auto err = deserialize_binary_value(ctx, meta[i], output[i]);
if (err != errc::ok) return make_error_code(err); if (err != errc::ok) return make_error_code(err);
} }
} }
// Check for remaining bytes // Check for remaining bytes
if (!ctx.empty()) return make_error_code(errc::extra_bytes); if (!ctx.empty()) return make_error_code(errc::extra_bytes);
return error_code(); return error_code();
} }

View File

@ -20,14 +20,14 @@ namespace mysql {
namespace detail { namespace detail {
inline std::uint32_t compute_size_to_write( inline std::uint32_t compute_size_to_write(
std::size_t buffer_size, std::size_t buffer_size,
std::size_t transferred_size std::size_t transferred_size
) )
{ {
return static_cast<std::uint32_t>(std::min( return static_cast<std::uint32_t>(std::min(
MAX_PACKET_SIZE, MAX_PACKET_SIZE,
buffer_size - transferred_size buffer_size - transferred_size
)); ));
} }
} // detail } // detail
@ -36,194 +36,194 @@ inline std::uint32_t compute_size_to_write(
template <typename AsyncStream> template <typename AsyncStream>
bool boost::mysql::detail::channel<AsyncStream>::process_sequence_number( bool boost::mysql::detail::channel<AsyncStream>::process_sequence_number(
std::uint8_t got std::uint8_t got
) )
{ {
if (got == sequence_number_) if (got == sequence_number_)
{ {
++sequence_number_; ++sequence_number_;
return true; return true;
} }
else else
{ {
return false; return false;
} }
} }
template <typename AsyncStream> template <typename AsyncStream>
boost::mysql::error_code boost::mysql::detail::channel<AsyncStream>::process_header_read( boost::mysql::error_code boost::mysql::detail::channel<AsyncStream>::process_header_read(
std::uint32_t& size_to_read std::uint32_t& size_to_read
) )
{ {
packet_header header; packet_header header;
deserialization_context ctx (boost::asio::buffer(header_buffer_), capabilities(0)); // unaffected by capabilities deserialization_context ctx (boost::asio::buffer(header_buffer_), capabilities(0)); // unaffected by capabilities
[[maybe_unused]] errc err = deserialize(header, ctx); [[maybe_unused]] errc err = deserialize(header, ctx);
assert(err == errc::ok); // this should always succeed assert(err == errc::ok); // this should always succeed
if (!process_sequence_number(header.sequence_number.value)) if (!process_sequence_number(header.sequence_number.value))
{ {
return make_error_code(errc::sequence_number_mismatch); return make_error_code(errc::sequence_number_mismatch);
} }
size_to_read = header.packet_size.value; size_to_read = header.packet_size.value;
return error_code(); return error_code();
} }
template <typename AsyncStream> template <typename AsyncStream>
void boost::mysql::detail::channel<AsyncStream>::process_header_write( void boost::mysql::detail::channel<AsyncStream>::process_header_write(
std::uint32_t size_to_write std::uint32_t size_to_write
) )
{ {
packet_header header; packet_header header;
header.packet_size.value = size_to_write; header.packet_size.value = size_to_write;
header.sequence_number.value = next_sequence_number(); header.sequence_number.value = next_sequence_number();
serialization_context ctx (capabilities(0), header_buffer_.data()); // capabilities not relevant here serialization_context ctx (capabilities(0), header_buffer_.data()); // capabilities not relevant here
serialize(header, ctx); serialize(header, ctx);
} }
template <typename AsyncStream> template <typename AsyncStream>
template <typename BufferSeq> template <typename BufferSeq>
std::size_t boost::mysql::detail::channel<AsyncStream>::read_impl( std::size_t boost::mysql::detail::channel<AsyncStream>::read_impl(
BufferSeq&& buff, BufferSeq&& buff,
error_code& ec error_code& ec
) )
{ {
if (ssl_active()) if (ssl_active())
{ {
return boost::asio::read(ssl_block_->stream, std::forward<BufferSeq>(buff), ec); return boost::asio::read(ssl_block_->stream, std::forward<BufferSeq>(buff), ec);
} }
else else
{ {
return boost::asio::read(stream_, std::forward<BufferSeq>(buff), ec); return boost::asio::read(stream_, std::forward<BufferSeq>(buff), ec);
} }
} }
template <typename AsyncStream> template <typename AsyncStream>
template <typename BufferSeq> template <typename BufferSeq>
std::size_t boost::mysql::detail::channel<AsyncStream>::write_impl( std::size_t boost::mysql::detail::channel<AsyncStream>::write_impl(
BufferSeq&& buff, BufferSeq&& buff,
error_code& ec error_code& ec
) )
{ {
if (ssl_active()) if (ssl_active())
{ {
return boost::asio::write(ssl_block_->stream, std::forward<BufferSeq>(buff), ec); return boost::asio::write(ssl_block_->stream, std::forward<BufferSeq>(buff), ec);
} }
else else
{ {
return boost::asio::write(stream_, std::forward<BufferSeq>(buff), ec); return boost::asio::write(stream_, std::forward<BufferSeq>(buff), ec);
} }
} }
template <typename AsyncStream> template <typename AsyncStream>
template <typename BufferSeq, typename CompletionToken> template <typename BufferSeq, typename CompletionToken>
auto boost::mysql::detail::channel<AsyncStream>::async_read_impl( auto boost::mysql::detail::channel<AsyncStream>::async_read_impl(
BufferSeq&& buff, BufferSeq&& buff,
CompletionToken&& token CompletionToken&& token
) )
{ {
if (ssl_active()) if (ssl_active())
{ {
return boost::asio::async_read( return boost::asio::async_read(
ssl_block_->stream, ssl_block_->stream,
std::forward<BufferSeq>(buff), std::forward<BufferSeq>(buff),
std::forward<CompletionToken>(token) std::forward<CompletionToken>(token)
); );
} }
else else
{ {
return boost::asio::async_read( return boost::asio::async_read(
stream_, stream_,
std::forward<BufferSeq>(buff), std::forward<BufferSeq>(buff),
std::forward<CompletionToken>(token) std::forward<CompletionToken>(token)
); );
} }
} }
template <typename AsyncStream> template <typename AsyncStream>
template <typename BufferSeq, typename CompletionToken> template <typename BufferSeq, typename CompletionToken>
auto boost::mysql::detail::channel<AsyncStream>::async_write_impl( auto boost::mysql::detail::channel<AsyncStream>::async_write_impl(
BufferSeq&& buff, BufferSeq&& buff,
CompletionToken&& token CompletionToken&& token
) )
{ {
if (ssl_active()) if (ssl_active())
{ {
return boost::asio::async_write( return boost::asio::async_write(
ssl_block_->stream, ssl_block_->stream,
std::forward<BufferSeq>(buff), std::forward<BufferSeq>(buff),
std::forward<CompletionToken>(token) std::forward<CompletionToken>(token)
); );
} }
else else
{ {
return boost::asio::async_write( return boost::asio::async_write(
stream_, stream_,
std::forward<BufferSeq>(buff), std::forward<BufferSeq>(buff),
std::forward<CompletionToken>(token) std::forward<CompletionToken>(token)
); );
} }
} }
template <typename AsyncStream> template <typename AsyncStream>
template <typename Allocator> template <typename Allocator>
void boost::mysql::detail::channel<AsyncStream>::read( void boost::mysql::detail::channel<AsyncStream>::read(
basic_bytestring<Allocator>& buffer, basic_bytestring<Allocator>& buffer,
error_code& code error_code& code
) )
{ {
std::size_t transferred_size = 0; std::size_t transferred_size = 0;
std::uint32_t size_to_read = 0; std::uint32_t size_to_read = 0;
buffer.clear(); buffer.clear();
code.clear(); code.clear();
auto header_buff = boost::asio::buffer(header_buffer_); auto header_buff = boost::asio::buffer(header_buffer_);
do do
{ {
// Read header // Read header
read_impl(header_buff, code); read_impl(header_buff, code);
valgrind_make_mem_defined(header_buff); valgrind_make_mem_defined(header_buff);
if (code) return; if (code) return;
// See how many bytes we should be reading // See how many bytes we should be reading
code = process_header_read(size_to_read); code = process_header_read(size_to_read);
if (code) return; if (code) return;
// Read the rest of the message // Read the rest of the message
buffer.resize(buffer.size() + size_to_read); buffer.resize(buffer.size() + size_to_read);
auto read_buffer = boost::asio::buffer(buffer.data() + transferred_size, size_to_read); auto read_buffer = boost::asio::buffer(buffer.data() + transferred_size, size_to_read);
read_impl(read_buffer, code); read_impl(read_buffer, code);
valgrind_make_mem_defined(read_buffer); valgrind_make_mem_defined(read_buffer);
if (code) return; if (code) return;
transferred_size += size_to_read; transferred_size += size_to_read;
} while (size_to_read == MAX_PACKET_SIZE); } while (size_to_read == MAX_PACKET_SIZE);
} }
template <typename AsyncStream> template <typename AsyncStream>
void boost::mysql::detail::channel<AsyncStream>::write( void boost::mysql::detail::channel<AsyncStream>::write(
boost::asio::const_buffer buffer, boost::asio::const_buffer buffer,
error_code& code error_code& code
) )
{ {
std::size_t transferred_size = 0; std::size_t transferred_size = 0;
auto bufsize = buffer.size(); auto bufsize = buffer.size();
auto first = static_cast<const std::uint8_t*>(buffer.data()); auto first = static_cast<const std::uint8_t*>(buffer.data());
// If the packet is empty, we should still write the header, saying // If the packet is empty, we should still write the header, saying
// we are sending an empty packet. // we are sending an empty packet.
do do
{ {
auto size_to_write = compute_size_to_write(bufsize, transferred_size); auto size_to_write = compute_size_to_write(bufsize, transferred_size);
process_header_write(size_to_write); process_header_write(size_to_write);
write_impl( write_impl(
std::array<boost::asio::const_buffer, 2> { std::array<boost::asio::const_buffer, 2> {
boost::asio::buffer(header_buffer_), boost::asio::buffer(header_buffer_),
boost::asio::buffer(first + transferred_size, size_to_write) boost::asio::buffer(first + transferred_size, size_to_write)
}, },
code code
); );
if (code) return; if (code) return;
transferred_size += size_to_write; transferred_size += size_to_write;
} while (transferred_size < bufsize); } while (transferred_size < bufsize);
} }
@ -231,185 +231,185 @@ template <typename AsyncStream>
template <typename Allocator, typename CompletionToken> template <typename Allocator, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code)) BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
boost::mysql::detail::channel<AsyncStream>::async_read( boost::mysql::detail::channel<AsyncStream>::async_read(
basic_bytestring<Allocator>& buffer, basic_bytestring<Allocator>& buffer,
CompletionToken&& token CompletionToken&& token
) )
{ {
using HandlerSignature = void(mysql::error_code); using HandlerSignature = void(mysql::error_code);
using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature);
using BaseType = boost::beast::async_base<HandlerType, typename AsyncStream::executor_type>; using BaseType = boost::beast::async_base<HandlerType, typename AsyncStream::executor_type>;
boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token); boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token);
struct Op: BaseType, boost::asio::coroutine struct Op: BaseType, boost::asio::coroutine
{ {
channel<AsyncStream>& stream_; channel<AsyncStream>& stream_;
basic_bytestring<Allocator>& buffer_; basic_bytestring<Allocator>& buffer_;
std::size_t total_transferred_size_ = 0; std::size_t total_transferred_size_ = 0;
Op( Op(
HandlerType&& handler, HandlerType&& handler,
channel<AsyncStream>& stream, channel<AsyncStream>& stream,
basic_bytestring<Allocator>& buffer basic_bytestring<Allocator>& buffer
): ):
BaseType(std::move(handler), stream.next_layer().get_executor()), BaseType(std::move(handler), stream.next_layer().get_executor()),
stream_(stream), stream_(stream),
buffer_(buffer) buffer_(buffer)
{ {
} }
void operator()( void operator()(
error_code code, error_code code,
std::size_t bytes_transferred, std::size_t bytes_transferred,
bool cont=true bool cont=true
) )
{ {
// Error checking // Error checking
if (code) if (code)
{ {
this->complete(cont, code); this->complete(cont, code);
return; return;
} }
// Non-error path // Non-error path
std::uint32_t size_to_read = 0; std::uint32_t size_to_read = 0;
reenter(*this) reenter(*this)
{ {
do do
{ {
yield stream_.async_read_impl( yield stream_.async_read_impl(
boost::asio::buffer(stream_.header_buffer_), boost::asio::buffer(stream_.header_buffer_),
std::move(*this) std::move(*this)
); );
valgrind_make_mem_defined(boost::asio::buffer(stream_.header_buffer_)); valgrind_make_mem_defined(boost::asio::buffer(stream_.header_buffer_));
code = stream_.process_header_read(size_to_read); code = stream_.process_header_read(size_to_read);
if (code) if (code)
{ {
this->complete(cont, code); this->complete(cont, code);
yield break; yield break;
} }
buffer_.resize(buffer_.size() + size_to_read); buffer_.resize(buffer_.size() + size_to_read);
yield stream_.async_read_impl( yield stream_.async_read_impl(
boost::asio::buffer(buffer_.data() + total_transferred_size_, size_to_read), boost::asio::buffer(buffer_.data() + total_transferred_size_, size_to_read),
std::move(*this) std::move(*this)
); );
valgrind_make_mem_defined( valgrind_make_mem_defined(
boost::asio::buffer(buffer_.data() + total_transferred_size_, bytes_transferred) boost::asio::buffer(buffer_.data() + total_transferred_size_, bytes_transferred)
); );
total_transferred_size_ += bytes_transferred; total_transferred_size_ += bytes_transferred;
} while (bytes_transferred == MAX_PACKET_SIZE); } while (bytes_transferred == MAX_PACKET_SIZE);
this->complete(cont, error_code()); this->complete(cont, error_code());
} }
} }
}; };
buffer.clear(); buffer.clear();
Op(std::move(initiator.completion_handler), *this, buffer)(error_code(), 0, false); Op(std::move(initiator.completion_handler), *this, buffer)(error_code(), 0, false);
return initiator.result.get(); return initiator.result.get();
} }
template <typename AsyncStream> template <typename AsyncStream>
template <typename CompletionToken> template <typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code)) BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
boost::mysql::detail::channel<AsyncStream>::async_write( boost::mysql::detail::channel<AsyncStream>::async_write(
boost::asio::const_buffer buffer, boost::asio::const_buffer buffer,
CompletionToken&& token CompletionToken&& token
) )
{ {
using HandlerSignature = void(mysql::error_code); using HandlerSignature = void(mysql::error_code);
using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature);
using BaseType = boost::beast::async_base<HandlerType, typename AsyncStream::executor_type>; using BaseType = boost::beast::async_base<HandlerType, typename AsyncStream::executor_type>;
boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token); boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token);
struct Op : BaseType, boost::asio::coroutine struct Op : BaseType, boost::asio::coroutine
{ {
channel<AsyncStream>& stream_; channel<AsyncStream>& stream_;
boost::asio::const_buffer buffer_; boost::asio::const_buffer buffer_;
std::size_t total_transferred_size_ = 0; std::size_t total_transferred_size_ = 0;
Op( Op(
HandlerType&& handler, HandlerType&& handler,
channel<AsyncStream>& stream, channel<AsyncStream>& stream,
boost::asio::const_buffer buffer boost::asio::const_buffer buffer
): ):
BaseType(std::move(handler), stream.next_layer().get_executor()), BaseType(std::move(handler), stream.next_layer().get_executor()),
stream_(stream), stream_(stream),
buffer_(buffer) buffer_(buffer)
{ {
} }
void operator()( void operator()(
error_code code, error_code code,
std::size_t bytes_transferred, std::size_t bytes_transferred,
bool cont=true bool cont=true
) )
{ {
// Error handling // Error handling
if (code) if (code)
{ {
this->complete(cont, code); this->complete(cont, code);
return; return;
} }
// Non-error path // Non-error path
std::uint32_t size_to_write; std::uint32_t size_to_write;
reenter(*this) reenter(*this)
{ {
// Force write the packet header on an empty packet, at least. // Force write the packet header on an empty packet, at least.
do do
{ {
size_to_write = compute_size_to_write(buffer_.size(), total_transferred_size_); size_to_write = compute_size_to_write(buffer_.size(), total_transferred_size_);
stream_.process_header_write(size_to_write); stream_.process_header_write(size_to_write);
yield stream_.async_write_impl( yield stream_.async_write_impl(
std::array<boost::asio::const_buffer, 2> { std::array<boost::asio::const_buffer, 2> {
boost::asio::buffer(stream_.header_buffer_), boost::asio::buffer(stream_.header_buffer_),
boost::asio::buffer(buffer_ + total_transferred_size_, size_to_write) boost::asio::buffer(buffer_ + total_transferred_size_, size_to_write)
}, },
std::move(*this) std::move(*this)
); );
total_transferred_size_ += (bytes_transferred - 4); // header size total_transferred_size_ += (bytes_transferred - 4); // header size
} while (total_transferred_size_ < buffer_.size()); } while (total_transferred_size_ < buffer_.size());
this->complete(cont, error_code()); this->complete(cont, error_code());
} }
} }
}; };
Op(std::move(initiator.completion_handler), *this, buffer)(error_code(), 0, false); Op(std::move(initiator.completion_handler), *this, buffer)(error_code(), 0, false);
return initiator.result.get(); return initiator.result.get();
} }
template <typename Stream> template <typename Stream>
void boost::mysql::detail::channel<Stream>::ssl_handshake( void boost::mysql::detail::channel<Stream>::ssl_handshake(
error_code& ec error_code& ec
) )
{ {
create_ssl_block(); create_ssl_block();
ssl_block_->stream.handshake(boost::asio::ssl::stream_base::client, ec); ssl_block_->stream.handshake(boost::asio::ssl::stream_base::client, ec);
} }
template <typename Stream> template <typename Stream>
template <typename CompletionToken> template <typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code)) BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
boost::mysql::detail::channel<Stream>::async_ssl_handshake( boost::mysql::detail::channel<Stream>::async_ssl_handshake(
CompletionToken&& token CompletionToken&& token
) )
{ {
create_ssl_block(); create_ssl_block();
return ssl_block_->stream.async_handshake( return ssl_block_->stream.async_handshake(
boost::asio::ssl::stream_base::client, boost::asio::ssl::stream_base::client,
std::forward<CompletionToken>(token) std::forward<CompletionToken>(token)
); );
} }
#include <boost/asio/unyield.hpp> #include <boost/asio/unyield.hpp>

View File

@ -10,68 +10,68 @@
inline boost::mysql::errc inline boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::ok_packet, boost::mysql::detail::ok_packet,
boost::mysql::detail::serialization_tag::struct_with_fields boost::mysql::detail::serialization_tag::struct_with_fields
>::deserialize_( >::deserialize_(
ok_packet& output, ok_packet& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
{ {
auto err = deserialize_fields( auto err = deserialize_fields(
ctx, ctx,
output.affected_rows, output.affected_rows,
output.last_insert_id, output.last_insert_id,
output.status_flags, output.status_flags,
output.warnings output.warnings
); );
if (err == errc::ok && ctx.enough_size(1)) // message is optional, may be omitted if (err == errc::ok && ctx.enough_size(1)) // message is optional, may be omitted
{ {
err = deserialize(output.info, ctx); err = deserialize(output.info, ctx);
} }
return err; return err;
} }
} }
inline boost::mysql::errc inline boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::column_definition_packet, boost::mysql::detail::column_definition_packet,
boost::mysql::detail::serialization_tag::struct_with_fields boost::mysql::detail::serialization_tag::struct_with_fields
>::deserialize_( >::deserialize_(
column_definition_packet& output, column_definition_packet& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
int_lenenc length_of_fixed_fields; int_lenenc length_of_fixed_fields;
int2 final_padding; int2 final_padding;
return deserialize_fields( return deserialize_fields(
ctx, ctx,
output.catalog, output.catalog,
output.schema, output.schema,
output.table, output.table,
output.org_table, output.org_table,
output.name, output.name,
output.org_name, output.org_name,
length_of_fixed_fields, length_of_fixed_fields,
output.character_set, output.character_set,
output.column_length, output.column_length,
output.type, output.type,
output.flags, output.flags,
output.decimals, output.decimals,
final_padding final_padding
); );
} }
inline boost::mysql::error_code boost::mysql::detail::process_error_packet( inline boost::mysql::error_code boost::mysql::detail::process_error_packet(
deserialization_context& ctx, deserialization_context& ctx,
error_info& info error_info& info
) )
{ {
err_packet error_packet; err_packet error_packet;
auto code = deserialize_message(error_packet, ctx); auto code = deserialize_message(error_packet, ctx);
if (code) return code; if (code) return code;
info.set_message(std::string(error_packet.error_message.value)); info.set_message(std::string(error_packet.error_message.value));
return make_error_code(static_cast<errc>(error_packet.error_code.value)); return make_error_code(static_cast<errc>(error_packet.error_code.value));
} }

View File

@ -11,137 +11,137 @@
inline boost::mysql::errc inline boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::handshake_packet, boost::mysql::detail::handshake_packet,
boost::mysql::detail::serialization_tag::struct_with_fields boost::mysql::detail::serialization_tag::struct_with_fields
>::deserialize_( >::deserialize_(
handshake_packet& output, handshake_packet& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
constexpr std::uint8_t auth1_length = 8; constexpr std::uint8_t auth1_length = 8;
string_fixed<auth1_length> auth_plugin_data_part_1; string_fixed<auth1_length> auth_plugin_data_part_1;
string_fixed<2> capability_flags_low; string_fixed<2> capability_flags_low;
string_fixed<2> capability_flags_high; string_fixed<2> capability_flags_high;
int1 filler; // should be 0 int1 filler; // should be 0
int1 auth_plugin_data_len; // TODO: double check this int1 auth_plugin_data_len; // TODO: double check this
string_fixed<10> reserved; string_fixed<10> reserved;
auto err = deserialize_fields( auto err = deserialize_fields(
ctx, ctx,
output.server_version, output.server_version,
output.connection_id, output.connection_id,
auth_plugin_data_part_1, auth_plugin_data_part_1,
filler, // TODO: docs state fields below the filler are optional filler, // TODO: docs state fields below the filler are optional
capability_flags_low, capability_flags_low,
output.character_set, output.character_set,
output.status_flags, output.status_flags,
capability_flags_high capability_flags_high
); );
if (err != errc::ok) return err; if (err != errc::ok) return err;
// Compose capabilities // Compose capabilities
auto capabilities_begin = reinterpret_cast<std::uint8_t*>(&output.capability_falgs.value); auto capabilities_begin = reinterpret_cast<std::uint8_t*>(&output.capability_falgs.value);
memcpy(capabilities_begin, capability_flags_low.data(), 2); memcpy(capabilities_begin, capability_flags_low.data(), 2);
memcpy(capabilities_begin + 2, capability_flags_high.data(), 2); memcpy(capabilities_begin + 2, capability_flags_high.data(), 2);
boost::endian::little_to_native_inplace(output.capability_falgs.value); boost::endian::little_to_native_inplace(output.capability_falgs.value);
// Check minimum server capabilities to deserialize this frame // Check minimum server capabilities to deserialize this frame
capabilities cap (output.capability_falgs.value); capabilities cap (output.capability_falgs.value);
if (!cap.has(CLIENT_PLUGIN_AUTH)) return errc::server_unsupported; if (!cap.has(CLIENT_PLUGIN_AUTH)) return errc::server_unsupported;
// Deserialize the rest of the frame // Deserialize the rest of the frame
err = deserialize_fields( err = deserialize_fields(
ctx, ctx,
auth_plugin_data_len, auth_plugin_data_len,
reserved reserved
); );
if (err != errc::ok) return err; if (err != errc::ok) return err;
auto auth2_length = static_cast<std::uint8_t>( auto auth2_length = static_cast<std::uint8_t>(
std::max(13, auth_plugin_data_len.value - auth1_length)); std::max(13, auth_plugin_data_len.value - auth1_length));
err = ctx.copy(output.auth_plugin_data_buffer.data() + auth1_length, auth2_length); err = ctx.copy(output.auth_plugin_data_buffer.data() + auth1_length, auth2_length);
if (err != errc::ok) return err; if (err != errc::ok) return err;
err = deserialize(output.auth_plugin_name, ctx); err = deserialize(output.auth_plugin_name, ctx);
if (err != errc::ok) return err; if (err != errc::ok) return err;
// Compose auth_plugin_data // Compose auth_plugin_data
memcpy( memcpy(
output.auth_plugin_data_buffer.data(), output.auth_plugin_data_buffer.data(),
auth_plugin_data_part_1.data(), auth_plugin_data_part_1.data(),
auth1_length auth1_length
); );
output.auth_plugin_data.value = std::string_view( output.auth_plugin_data.value = std::string_view(
output.auth_plugin_data_buffer.data(), output.auth_plugin_data_buffer.data(),
auth1_length + auth2_length - 1 // discard trailing null byte auth1_length + auth2_length - 1 // discard trailing null byte
); );
return errc::ok; return errc::ok;
} }
std::size_t std::size_t
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::handshake_response_packet, boost::mysql::detail::handshake_response_packet,
boost::mysql::detail::serialization_tag::struct_with_fields boost::mysql::detail::serialization_tag::struct_with_fields
>::get_size_( >::get_size_(
const handshake_response_packet& value, const handshake_response_packet& value,
const serialization_context& ctx const serialization_context& ctx
) noexcept ) noexcept
{ {
std::size_t res = std::size_t res =
get_size(value.client_flag, ctx) + get_size(value.client_flag, ctx) +
get_size(value.max_packet_size, ctx) + get_size(value.max_packet_size, ctx) +
get_size(value.character_set, ctx) + get_size(value.character_set, ctx) +
23 + // filler 23 + // filler
get_size(value.username, ctx) + get_size(value.username, ctx) +
get_size(value.auth_response, ctx); get_size(value.auth_response, ctx);
if (ctx.get_capabilities().has(CLIENT_CONNECT_WITH_DB)) if (ctx.get_capabilities().has(CLIENT_CONNECT_WITH_DB))
{ {
res += get_size(value.database, ctx); res += get_size(value.database, ctx);
} }
res += get_size(value.client_plugin_name, ctx); res += get_size(value.client_plugin_name, ctx);
return res; return res;
} }
inline void inline void
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::handshake_response_packet, boost::mysql::detail::handshake_response_packet,
boost::mysql::detail::serialization_tag::struct_with_fields boost::mysql::detail::serialization_tag::struct_with_fields
>::serialize_( >::serialize_(
const handshake_response_packet& value, const handshake_response_packet& value,
serialization_context& ctx serialization_context& ctx
) noexcept ) noexcept
{ {
serialize(value.client_flag, ctx); serialize(value.client_flag, ctx);
serialize(value.max_packet_size, ctx); serialize(value.max_packet_size, ctx);
serialize(value.character_set, ctx); serialize(value.character_set, ctx);
std::uint8_t buffer [23] {}; std::uint8_t buffer [23] {};
ctx.write(buffer, sizeof(buffer)); ctx.write(buffer, sizeof(buffer));
serialize(value.username, ctx); serialize(value.username, ctx);
serialize(value.auth_response, ctx); serialize(value.auth_response, ctx);
if (ctx.get_capabilities().has(CLIENT_CONNECT_WITH_DB)) if (ctx.get_capabilities().has(CLIENT_CONNECT_WITH_DB))
{ {
serialize(value.database, ctx); serialize(value.database, ctx);
} }
serialize(value.client_plugin_name, ctx); serialize(value.client_plugin_name, ctx);
} }
inline boost::mysql::errc inline boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::auth_switch_request_packet, boost::mysql::detail::auth_switch_request_packet,
boost::mysql::detail::serialization_tag::struct_with_fields boost::mysql::detail::serialization_tag::struct_with_fields
>::deserialize_( >::deserialize_(
auth_switch_request_packet& output, auth_switch_request_packet& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
auto err = deserialize_fields(ctx, output.plugin_name, output.auth_plugin_data); auto err = deserialize_fields(ctx, output.plugin_name, output.auth_plugin_data);
auto& auth_data = output.auth_plugin_data.value; auto& auth_data = output.auth_plugin_data.value;
// Discard an additional NULL at the end of auth data // Discard an additional NULL at the end of auth data
if (!auth_data.empty()) if (!auth_data.empty())
{ {
assert(auth_data.back() == 0); assert(auth_data.back() == 0);
auth_data = auth_data.substr(0, auth_data.size() - 1); auth_data = auth_data.substr(0, auth_data.size() - 1);
} }
return err; return err;
} }

View File

@ -16,38 +16,38 @@ namespace detail {
// Maps from an actual value to a protocol_field_type. Only value's type is used // Maps from an actual value to a protocol_field_type. Only value's type is used
inline protocol_field_type get_protocol_field_type( inline protocol_field_type get_protocol_field_type(
const value& input const value& input
) noexcept ) noexcept
{ {
struct visitor struct visitor
{ {
constexpr auto operator()(std::int32_t) const noexcept { return protocol_field_type::long_; } constexpr auto operator()(std::int32_t) const noexcept { return protocol_field_type::long_; }
constexpr auto operator()(std::uint32_t) const noexcept { return protocol_field_type::long_; } constexpr auto operator()(std::uint32_t) const noexcept { return protocol_field_type::long_; }
constexpr auto operator()(std::int64_t) const noexcept { return protocol_field_type::longlong; } constexpr auto operator()(std::int64_t) const noexcept { return protocol_field_type::longlong; }
constexpr auto operator()(std::uint64_t) const noexcept { return protocol_field_type::longlong; } constexpr auto operator()(std::uint64_t) const noexcept { return protocol_field_type::longlong; }
constexpr auto operator()(std::string_view) const noexcept { return protocol_field_type::varchar; } constexpr auto operator()(std::string_view) const noexcept { return protocol_field_type::varchar; }
constexpr auto operator()(float) const noexcept { return protocol_field_type::float_; } constexpr auto operator()(float) const noexcept { return protocol_field_type::float_; }
constexpr auto operator()(double) const noexcept { return protocol_field_type::double_; } constexpr auto operator()(double) const noexcept { return protocol_field_type::double_; }
constexpr auto operator()(date) const noexcept { return protocol_field_type::date; } constexpr auto operator()(date) const noexcept { return protocol_field_type::date; }
constexpr auto operator()(datetime) const noexcept { return protocol_field_type::datetime; } constexpr auto operator()(datetime) const noexcept { return protocol_field_type::datetime; }
constexpr auto operator()(time) const noexcept { return protocol_field_type::time; } constexpr auto operator()(time) const noexcept { return protocol_field_type::time; }
constexpr auto operator()(std::nullptr_t) const noexcept { return protocol_field_type::null; } constexpr auto operator()(std::nullptr_t) const noexcept { return protocol_field_type::null; }
}; };
return std::visit(visitor(), input); return std::visit(visitor(), input);
} }
// Whether to include the unsigned flag in the statement execute message // Whether to include the unsigned flag in the statement execute message
// for a given value or not. Only value's type is used // for a given value or not. Only value's type is used
inline bool is_unsigned( inline bool is_unsigned(
const value& input const value& input
) noexcept ) noexcept
{ {
// By default, return false; just for integer types explicitly unsigned return true // By default, return false; just for integer types explicitly unsigned return true
return std::visit([](auto v) { return std::visit([](auto v) {
using type = decltype(v); using type = decltype(v);
return std::is_same_v<type, std::uint32_t> || return std::is_same_v<type, std::uint32_t> ||
std::is_same_v<type, std::uint64_t>; std::is_same_v<type, std::uint64_t>;
}, input); }, input);
} }
// Performs a mapping from T to a type that can be serialized // Performs a mapping from T to a type that can be serialized
@ -60,12 +60,12 @@ using get_serializable_type_t = typename get_serializable_type<T>::type;
// Indicate no serialization is required // Indicate no serialization is required
struct dummy_serializable struct dummy_serializable
{ {
explicit dummy_serializable(std::nullptr_t) {} explicit dummy_serializable(std::nullptr_t) {}
}; };
template <> template <>
struct serialization_traits<dummy_serializable, serialization_tag::none> : struct serialization_traits<dummy_serializable, serialization_tag::none> :
noop_serialize<dummy_serializable> noop_serialize<dummy_serializable>
{ {
}; };
@ -79,27 +79,27 @@ template <> struct get_serializable_type<std::nullptr_t> { using type = dummy_se
template <typename T> template <typename T>
inline get_serializable_type_t<T> to_serializable_type(T input) noexcept inline get_serializable_type_t<T> to_serializable_type(T input) noexcept
{ {
return get_serializable_type_t<T>(input); return get_serializable_type_t<T>(input);
} }
inline std::size_t get_binary_value_size( inline std::size_t get_binary_value_size(
const value& input, const value& input,
const serialization_context& ctx const serialization_context& ctx
) noexcept ) noexcept
{ {
return std::visit([&ctx](const auto& v) { return std::visit([&ctx](const auto& v) {
return get_size(to_serializable_type(v), ctx); return get_size(to_serializable_type(v), ctx);
}, input); }, input);
} }
inline void serialize_binary_value( inline void serialize_binary_value(
const value& input, const value& input,
serialization_context& ctx serialization_context& ctx
) noexcept ) noexcept
{ {
std::visit([&ctx](const auto& v) { std::visit([&ctx](const auto& v) {
serialize(to_serializable_type(v), ctx); serialize(to_serializable_type(v), ctx);
}, input); }, input);
} }
} // detail } // detail
@ -108,99 +108,99 @@ inline void serialize_binary_value(
inline boost::mysql::errc inline boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::com_stmt_prepare_ok_packet, boost::mysql::detail::com_stmt_prepare_ok_packet,
boost::mysql::detail::serialization_tag::struct_with_fields boost::mysql::detail::serialization_tag::struct_with_fields
>::deserialize_( >::deserialize_(
com_stmt_prepare_ok_packet& output, com_stmt_prepare_ok_packet& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
int1 reserved; int1 reserved;
return deserialize_fields( return deserialize_fields(
ctx, ctx,
output.statement_id, output.statement_id,
output.num_columns, output.num_columns,
output.num_params, output.num_params,
reserved, reserved,
output.warning_count output.warning_count
); );
} }
template <typename ForwardIterator> template <typename ForwardIterator>
inline std::size_t inline std::size_t
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::com_stmt_execute_packet<ForwardIterator>, boost::mysql::detail::com_stmt_execute_packet<ForwardIterator>,
boost::mysql::detail::serialization_tag::struct_with_fields boost::mysql::detail::serialization_tag::struct_with_fields
>::get_size_( >::get_size_(
const com_stmt_execute_packet<ForwardIterator>& value, const com_stmt_execute_packet<ForwardIterator>& value,
const serialization_context& ctx const serialization_context& ctx
) noexcept ) noexcept
{ {
std::size_t res = 1 + // command ID std::size_t res = 1 + // command ID
get_size(value.statement_id, ctx) + get_size(value.statement_id, ctx) +
get_size(value.flags, ctx) + get_size(value.flags, ctx) +
get_size(value.iteration_count, ctx); get_size(value.iteration_count, ctx);
auto num_params = std::distance(value.params_begin, value.params_end); auto num_params = std::distance(value.params_begin, value.params_end);
assert(num_params >= 0 && num_params <= 255); assert(num_params >= 0 && num_params <= 255);
res += null_bitmap_traits(stmt_execute_null_bitmap_offset, num_params).byte_count(); res += null_bitmap_traits(stmt_execute_null_bitmap_offset, num_params).byte_count();
res += get_size(value.new_params_bind_flag, ctx); res += get_size(value.new_params_bind_flag, ctx);
res += get_size(com_stmt_execute_param_meta_packet{}, ctx) * num_params; res += get_size(com_stmt_execute_param_meta_packet{}, ctx) * num_params;
for (auto it = value.params_begin; it != value.params_end; ++it) for (auto it = value.params_begin; it != value.params_end; ++it)
{ {
res += get_binary_value_size(*it, ctx); res += get_binary_value_size(*it, ctx);
} }
return res; return res;
} }
template <typename ForwardIterator> template <typename ForwardIterator>
inline void inline void
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::com_stmt_execute_packet<ForwardIterator>, boost::mysql::detail::com_stmt_execute_packet<ForwardIterator>,
boost::mysql::detail::serialization_tag::struct_with_fields boost::mysql::detail::serialization_tag::struct_with_fields
>::serialize_( >::serialize_(
const com_stmt_execute_packet<ForwardIterator>& input, const com_stmt_execute_packet<ForwardIterator>& input,
serialization_context& ctx serialization_context& ctx
) noexcept ) noexcept
{ {
serialize(int1(com_stmt_execute_packet<ForwardIterator>::command_id), ctx); serialize(int1(com_stmt_execute_packet<ForwardIterator>::command_id), ctx);
serialize(input.statement_id, ctx); serialize(input.statement_id, ctx);
serialize(input.flags, ctx); serialize(input.flags, ctx);
serialize(input.iteration_count, ctx); serialize(input.iteration_count, ctx);
// Number of parameters // Number of parameters
auto num_params = std::distance(input.params_begin, input.params_end); auto num_params = std::distance(input.params_begin, input.params_end);
assert(num_params >= 0 && num_params <= 255); assert(num_params >= 0 && num_params <= 255);
// NULL bitmap (already size zero if num_params == 0) // NULL bitmap (already size zero if num_params == 0)
null_bitmap_traits traits (stmt_execute_null_bitmap_offset, num_params); null_bitmap_traits traits (stmt_execute_null_bitmap_offset, num_params);
std::size_t i = 0; std::size_t i = 0;
std::memset(ctx.first(), 0, traits.byte_count()); // Initialize to zeroes std::memset(ctx.first(), 0, traits.byte_count()); // Initialize to zeroes
for (auto it = input.params_begin; it != input.params_end; ++it, ++i) for (auto it = input.params_begin; it != input.params_end; ++it, ++i)
{ {
if (std::holds_alternative<std::nullptr_t>(*it)) if (std::holds_alternative<std::nullptr_t>(*it))
{ {
traits.set_null(ctx.first(), i); traits.set_null(ctx.first(), i);
} }
} }
ctx.advance(traits.byte_count()); ctx.advance(traits.byte_count());
// new parameters bind flag // new parameters bind flag
serialize(input.new_params_bind_flag, ctx); serialize(input.new_params_bind_flag, ctx);
// value metadata // value metadata
com_stmt_execute_param_meta_packet meta; com_stmt_execute_param_meta_packet meta;
for (auto it = input.params_begin; it != input.params_end; ++it) for (auto it = input.params_begin; it != input.params_end; ++it)
{ {
meta.type = get_protocol_field_type(*it); meta.type = get_protocol_field_type(*it);
meta.unsigned_flag.value = is_unsigned(*it) ? 0x80 : 0; meta.unsigned_flag.value = is_unsigned(*it) ? 0x80 : 0;
serialize(meta, ctx); serialize(meta, ctx);
} }
// actual values // actual values
for (auto it = input.params_begin; it != input.params_end; ++it) for (auto it = input.params_begin; it != input.params_end; ++it)
{ {
serialize_binary_value(*it, ctx); serialize_binary_value(*it, ctx);
} }
} }

View File

@ -16,9 +16,9 @@ namespace detail {
template <typename T> template <typename T>
constexpr std::size_t get_int_size() constexpr std::size_t get_int_size()
{ {
static_assert(is_fixed_size_int<T>()); static_assert(is_fixed_size_int<T>());
return std::is_same<T, int3>::value ? 3 : return std::is_same<T, int3>::value ? 3 :
std::is_same<T, int6>::value ? 6 : sizeof(T); std::is_same<T, int6>::value ? 6 : sizeof(T);
} }
// Helpers for structs // Helpers for structs
@ -38,101 +38,101 @@ struct is_command : decltype(is_command_helper::get<T>(nullptr))
template <std::size_t index, typename T> template <std::size_t index, typename T>
errc deserialize_struct( errc deserialize_struct(
[[maybe_unused]] T& output, [[maybe_unused]] T& output,
[[maybe_unused]] deserialization_context& ctx [[maybe_unused]] deserialization_context& ctx
) noexcept ) noexcept
{ {
constexpr auto fields = get_struct_fields<T>::value; constexpr auto fields = get_struct_fields<T>::value;
if constexpr (index == std::tuple_size<decltype(fields)>::value) if constexpr (index == std::tuple_size<decltype(fields)>::value)
{ {
return errc::ok; return errc::ok;
} }
else else
{ {
constexpr auto pmem = std::get<index>(fields); constexpr auto pmem = std::get<index>(fields);
errc err = deserialize(output.*pmem, ctx); errc err = deserialize(output.*pmem, ctx);
if (err != errc::ok) if (err != errc::ok)
{ {
return err; return err;
} }
else else
{ {
return deserialize_struct<index+1>(output, ctx); return deserialize_struct<index+1>(output, ctx);
} }
} }
} }
template <std::size_t index, typename T> template <std::size_t index, typename T>
void serialize_struct( void serialize_struct(
[[maybe_unused]] const T& value, [[maybe_unused]] const T& value,
[[maybe_unused]] serialization_context& ctx [[maybe_unused]] serialization_context& ctx
) noexcept ) noexcept
{ {
constexpr auto fields = get_struct_fields<T>::value; constexpr auto fields = get_struct_fields<T>::value;
if constexpr (index < std::tuple_size<decltype(fields)>::value) if constexpr (index < std::tuple_size<decltype(fields)>::value)
{ {
auto pmem = std::get<index>(fields); auto pmem = std::get<index>(fields);
serialize(value.*pmem, ctx); serialize(value.*pmem, ctx);
serialize_struct<index+1>(value, ctx); serialize_struct<index+1>(value, ctx);
} }
} }
template <std::size_t index, typename T> template <std::size_t index, typename T>
std::size_t get_size_struct( std::size_t get_size_struct(
[[maybe_unused]] const T& input, [[maybe_unused]] const T& input,
[[maybe_unused]] const serialization_context& ctx [[maybe_unused]] const serialization_context& ctx
) noexcept ) noexcept
{ {
constexpr auto fields = get_struct_fields<T>::value; constexpr auto fields = get_struct_fields<T>::value;
if constexpr (index == std::tuple_size<decltype(fields)>::value) if constexpr (index == std::tuple_size<decltype(fields)>::value)
{ {
return 0; return 0;
} }
else else
{ {
constexpr auto pmem = std::get<index>(fields); constexpr auto pmem = std::get<index>(fields);
return get_size_struct<index+1>(input, ctx) + return get_size_struct<index+1>(input, ctx) +
get_size(input.*pmem, ctx); get_size(input.*pmem, ctx);
} }
} }
// Helpers for (de)serialize_fields // Helpers for (de)serialize_fields
template <typename FirstType> template <typename FirstType>
errc deserialize_fields_helper(deserialization_context& ctx, FirstType& field) noexcept errc deserialize_fields_helper(deserialization_context& ctx, FirstType& field) noexcept
{ {
return deserialize(field, ctx); return deserialize(field, ctx);
} }
template <typename FirstType, typename... Types> template <typename FirstType, typename... Types>
errc deserialize_fields_helper( errc deserialize_fields_helper(
deserialization_context& ctx, deserialization_context& ctx,
FirstType& field, FirstType& field,
Types&... fields_tail Types&... fields_tail
) noexcept ) noexcept
{ {
errc err = deserialize(field, ctx); errc err = deserialize(field, ctx);
if (err == errc::ok) if (err == errc::ok)
{ {
err = deserialize_fields_helper(ctx, fields_tail...); err = deserialize_fields_helper(ctx, fields_tail...);
} }
return err; return err;
} }
template <typename FirstType> template <typename FirstType>
void serialize_fields_helper(serialization_context& ctx, const FirstType& field) noexcept void serialize_fields_helper(serialization_context& ctx, const FirstType& field) noexcept
{ {
serialize(field, ctx); serialize(field, ctx);
} }
template <typename FirstType, typename... Types> template <typename FirstType, typename... Types>
void serialize_fields_helper( void serialize_fields_helper(
serialization_context& ctx, serialization_context& ctx,
const FirstType& field, const FirstType& field,
const Types&... fields_tail const Types&... fields_tail
) )
{ {
serialize(field, ctx); serialize(field, ctx);
serialize_fields_helper(ctx, fields_tail...); serialize_fields_helper(ctx, fields_tail...);
} }
} // detail } // detail
@ -143,149 +143,149 @@ void serialize_fields_helper(
template <typename T> template <typename T>
constexpr bool boost::mysql::detail::is_fixed_size_int() constexpr bool boost::mysql::detail::is_fixed_size_int()
{ {
return return
std::is_integral<get_value_type_t<T>>::value && std::is_integral<get_value_type_t<T>>::value &&
std::is_base_of<value_holder<get_value_type_t<T>>, T>::value && std::is_base_of<value_holder<get_value_type_t<T>>, T>::value &&
!std::is_same<T, int_lenenc>::value; !std::is_same<T, int_lenenc>::value;
} }
template <typename T> template <typename T>
boost::mysql::errc boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
T, T,
boost::mysql::detail::serialization_tag::fixed_size_int boost::mysql::detail::serialization_tag::fixed_size_int
>::deserialize_( >::deserialize_(
T& output, T& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
static_assert(std::is_standard_layout_v<decltype(T::value)>); static_assert(std::is_standard_layout_v<decltype(T::value)>);
constexpr auto size = get_int_size<T>(); constexpr auto size = get_int_size<T>();
if (!ctx.enough_size(size)) if (!ctx.enough_size(size))
{ {
return errc::incomplete_message; return errc::incomplete_message;
} }
memset(&output.value, 0, sizeof(output.value)); memset(&output.value, 0, sizeof(output.value));
memcpy(&output.value, ctx.first(), size); memcpy(&output.value, ctx.first(), size);
boost::endian::little_to_native_inplace(output); boost::endian::little_to_native_inplace(output);
ctx.advance(size); ctx.advance(size);
return errc::ok; return errc::ok;
} }
template <typename T> template <typename T>
void boost::mysql::detail::serialization_traits< void boost::mysql::detail::serialization_traits<
T, T,
boost::mysql::detail::serialization_tag::fixed_size_int boost::mysql::detail::serialization_tag::fixed_size_int
>::serialize_( >::serialize_(
T input, T input,
serialization_context& ctx serialization_context& ctx
) noexcept ) noexcept
{ {
boost::endian::native_to_little_inplace(input); boost::endian::native_to_little_inplace(input);
ctx.write(&input.value, get_int_size<T>()); ctx.write(&input.value, get_int_size<T>());
} }
template <typename T> template <typename T>
constexpr std::size_t boost::mysql::detail::serialization_traits< constexpr std::size_t boost::mysql::detail::serialization_traits<
T, T,
boost::mysql::detail::serialization_tag::fixed_size_int boost::mysql::detail::serialization_tag::fixed_size_int
>::get_size_(T, const serialization_context&) noexcept >::get_size_(T, const serialization_context&) noexcept
{ {
return get_int_size<T>(); return get_int_size<T>();
} }
// Fixed size strings // Fixed size strings
template <std::size_t N> template <std::size_t N>
boost::mysql::errc boost::mysql::detail::serialization_traits< boost::mysql::errc boost::mysql::detail::serialization_traits<
boost::mysql::detail::string_fixed<N>, boost::mysql::detail::string_fixed<N>,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::deserialize_( >::deserialize_(
string_fixed<N>& output, string_fixed<N>& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
if (!ctx.enough_size(N)) if (!ctx.enough_size(N))
{ {
return errc::incomplete_message; return errc::incomplete_message;
} }
memcpy(output.data(), ctx.first(), N); memcpy(output.data(), ctx.first(), N);
ctx.advance(N); ctx.advance(N);
return errc::ok; return errc::ok;
} }
// Enums // Enums
template <typename T> template <typename T>
boost::mysql::errc boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
T, T,
boost::mysql::detail::serialization_tag::enumeration boost::mysql::detail::serialization_tag::enumeration
>::deserialize_( >::deserialize_(
T& output, T& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
serializable_type value; serializable_type value;
errc err = deserialize(value, ctx); errc err = deserialize(value, ctx);
if (err != errc::ok) if (err != errc::ok)
{ {
return err; return err;
} }
output = static_cast<T>(value.value); output = static_cast<T>(value.value);
return errc::ok; return errc::ok;
} }
template <typename T> template <typename T>
std::size_t boost::mysql::detail::serialization_traits< std::size_t boost::mysql::detail::serialization_traits<
T, T,
boost::mysql::detail::serialization_tag::enumeration boost::mysql::detail::serialization_tag::enumeration
>::get_size_(T, const serialization_context&) noexcept >::get_size_(T, const serialization_context&) noexcept
{ {
return get_int_size<serializable_type>(); return get_int_size<serializable_type>();
} }
// Floating points // Floating points
template <typename T> template <typename T>
boost::mysql::errc boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
T, T,
boost::mysql::detail::serialization_tag::floating_point boost::mysql::detail::serialization_tag::floating_point
>::deserialize_(T& output, deserialization_context& ctx) noexcept >::deserialize_(T& output, deserialization_context& ctx) noexcept
{ {
// Size check // Size check
if (!ctx.enough_size(sizeof(T))) return errc::incomplete_message; if (!ctx.enough_size(sizeof(T))) return errc::incomplete_message;
// Endianness conversion // Endianness conversion
// Boost.Endian support for floats start at 1.71. TODO: maybe update requirements and CI // Boost.Endian support for floats start at 1.71. TODO: maybe update requirements and CI
#if BOOST_ENDIAN_BIG_BYTE #if BOOST_ENDIAN_BIG_BYTE
char buf [sizeof(T)]; char buf [sizeof(T)];
std::memcpy(buf, ctx.first(), sizeof(T)); std::memcpy(buf, ctx.first(), sizeof(T));
std::reverse(buf, buf + sizeof(T)); std::reverse(buf, buf + sizeof(T));
std::memcpy(&output, buf, sizeof(T)); std::memcpy(&output, buf, sizeof(T));
#else #else
std::memcpy(&output, ctx.first(), sizeof(T)); std::memcpy(&output, ctx.first(), sizeof(T));
#endif #endif
ctx.advance(sizeof(T)); ctx.advance(sizeof(T));
return errc::ok; return errc::ok;
} }
template <typename T> template <typename T>
void void
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
T, T,
boost::mysql::detail::serialization_tag::floating_point boost::mysql::detail::serialization_tag::floating_point
>::serialize_(T input, serialization_context& ctx) noexcept >::serialize_(T input, serialization_context& ctx) noexcept
{ {
// Endianness conversion // Endianness conversion
#if BOOST_ENDIAN_BIG_BYTE #if BOOST_ENDIAN_BIG_BYTE
char buf [sizeof(T)]; char buf [sizeof(T)];
std::memcpy(buf, &input, sizeof(T)); std::memcpy(buf, &input, sizeof(T));
std::reverse(buf, buf + sizeof(T)); std::reverse(buf, buf + sizeof(T));
ctx.write(buf, sizeof(T)); ctx.write(buf, sizeof(T));
#else #else
ctx.write(&input, sizeof(T)); ctx.write(&input, sizeof(T));
#endif #endif
} }
@ -293,110 +293,110 @@ boost::mysql::detail::serialization_traits<
template <typename T> template <typename T>
constexpr bool boost::mysql::detail::is_struct_with_fields() constexpr bool boost::mysql::detail::is_struct_with_fields()
{ {
return !std::is_same_v< return !std::is_same_v<
std::decay_t<decltype(get_struct_fields<T>::value)>, std::decay_t<decltype(get_struct_fields<T>::value)>,
not_a_struct_with_fields not_a_struct_with_fields
>; >;
} }
template <typename T> template <typename T>
boost::mysql::errc boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
T, T,
boost::mysql::detail::serialization_tag::struct_with_fields boost::mysql::detail::serialization_tag::struct_with_fields
>::deserialize_(T& output, deserialization_context& ctx) noexcept >::deserialize_(T& output, deserialization_context& ctx) noexcept
{ {
return deserialize_struct<0>(output, ctx); return deserialize_struct<0>(output, ctx);
} }
template <typename T> template <typename T>
void void
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
T, T,
boost::mysql::detail::serialization_tag::struct_with_fields boost::mysql::detail::serialization_tag::struct_with_fields
>::serialize_( >::serialize_(
[[maybe_unused]] const T& input, [[maybe_unused]] const T& input,
[[maybe_unused]] serialization_context& ctx [[maybe_unused]] serialization_context& ctx
) noexcept ) noexcept
{ {
// For commands, add the command ID. Commands are only sent by the client, // For commands, add the command ID. Commands are only sent by the client,
// so this is not considered in the deserialization functions. // so this is not considered in the deserialization functions.
if constexpr (is_command<T>::value) if constexpr (is_command<T>::value)
{ {
serialize(int1(T::command_id), ctx); serialize(int1(T::command_id), ctx);
} }
serialize_struct<0>(input, ctx); serialize_struct<0>(input, ctx);
} }
template <typename T> template <typename T>
std::size_t std::size_t
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
T, T,
boost::mysql::detail::serialization_tag::struct_with_fields boost::mysql::detail::serialization_tag::struct_with_fields
>::get_size_(const T& input, const serialization_context& ctx) noexcept >::get_size_(const T& input, const serialization_context& ctx) noexcept
{ {
std::size_t res = is_command<T>::value ? 1 : 0; std::size_t res = is_command<T>::value ? 1 : 0;
res += get_size_struct<0>(input, ctx); res += get_size_struct<0>(input, ctx);
return res; return res;
} }
// Miscellanea // Miscellanea
template <typename Serializable, typename Allocator> template <typename Serializable, typename Allocator>
void boost::mysql::detail::serialize_message( void boost::mysql::detail::serialize_message(
const Serializable& input, const Serializable& input,
capabilities caps, capabilities caps,
basic_bytestring<Allocator>& buffer basic_bytestring<Allocator>& buffer
) )
{ {
serialization_context ctx (caps); serialization_context ctx (caps);
std::size_t size = get_size(input, ctx); std::size_t size = get_size(input, ctx);
buffer.resize(size); buffer.resize(size);
ctx.set_first(buffer.data()); ctx.set_first(buffer.data());
serialize(input, ctx); serialize(input, ctx);
assert(ctx.first() == buffer.data() + buffer.size()); assert(ctx.first() == buffer.data() + buffer.size());
} }
template <typename Deserializable> template <typename Deserializable>
boost::mysql::error_code boost::mysql::detail::deserialize_message( boost::mysql::error_code boost::mysql::detail::deserialize_message(
Deserializable& output, Deserializable& output,
deserialization_context& ctx deserialization_context& ctx
) )
{ {
auto err = deserialize(output, ctx); auto err = deserialize(output, ctx);
if (err != errc::ok) return make_error_code(err); if (err != errc::ok) return make_error_code(err);
if (!ctx.empty()) return make_error_code(errc::extra_bytes); if (!ctx.empty()) return make_error_code(errc::extra_bytes);
return error_code(); return error_code();
} }
template <typename... Types> template <typename... Types>
boost::mysql::errc boost::mysql::errc
boost::mysql::detail::deserialize_fields( boost::mysql::detail::deserialize_fields(
deserialization_context& ctx, deserialization_context& ctx,
Types&... fields Types&... fields
) noexcept ) noexcept
{ {
return deserialize_fields_helper(ctx, fields...); return deserialize_fields_helper(ctx, fields...);
} }
template <typename... Types> template <typename... Types>
void boost::mysql::detail::serialize_fields( void boost::mysql::detail::serialize_fields(
serialization_context& ctx, serialization_context& ctx,
const Types&... fields const Types&... fields
) noexcept ) noexcept
{ {
serialize_fields_helper(ctx, fields...); serialize_fields_helper(ctx, fields...);
} }
template <typename T> template <typename T>
constexpr boost::mysql::detail::serialization_tag constexpr boost::mysql::detail::serialization_tag
boost::mysql::detail::get_serialization_tag() boost::mysql::detail::get_serialization_tag()
{ {
return return
is_fixed_size_int<T>() ? serialization_tag::fixed_size_int : is_fixed_size_int<T>() ? serialization_tag::fixed_size_int :
std::is_floating_point<T>::value ? serialization_tag::floating_point : std::is_floating_point<T>::value ? serialization_tag::floating_point :
std::is_enum<T>::value ? serialization_tag::enumeration : std::is_enum<T>::value ? serialization_tag::enumeration :
is_struct_with_fields<T>() ? serialization_tag::struct_with_fields : is_struct_with_fields<T>() ? serialization_tag::struct_with_fields :
serialization_tag::none; serialization_tag::none;
} }

View File

@ -15,112 +15,112 @@ namespace mysql {
namespace detail { namespace detail {
inline std::string_view get_string( inline std::string_view get_string(
const std::uint8_t* from, const std::uint8_t* from,
std::size_t size std::size_t size
) )
{ {
return std::string_view(reinterpret_cast<const char*>(from), size); return std::string_view(reinterpret_cast<const char*>(from), size);
} }
inline errc deserialize_binary_date( inline errc deserialize_binary_date(
date& output, date& output,
std::uint8_t length, std::uint8_t length,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
int2 year; int2 year;
int1 month; int1 month;
int1 day; int1 day;
if (length >= 4) // if length is zero, year, month and day are zero if (length >= 4) // if length is zero, year, month and day are zero
{ {
auto err = deserialize_fields(ctx, year, month, day); auto err = deserialize_fields(ctx, year, month, day);
if (err != errc::ok) return err; if (err != errc::ok) return err;
} }
// TODO: how does this handle zero dates? // TODO: how does this handle zero dates?
::date::year_month_day ymd (::date::year(year.value), ::date::month(month.value), ::date::day(day.value)); ::date::year_month_day ymd (::date::year(year.value), ::date::month(month.value), ::date::day(day.value));
output = date(ymd); output = date(ymd);
return errc::ok; return errc::ok;
} }
// Does not add the length prefix byte // Does not add the length prefix byte
inline void serialize_binary_ymd( inline void serialize_binary_ymd(
const ::date::year_month_day& ymd, const ::date::year_month_day& ymd,
serialization_context& ctx serialization_context& ctx
) noexcept ) noexcept
{ {
serialize_fields( serialize_fields(
ctx, ctx,
int2(static_cast<std::uint16_t>(static_cast<int>(ymd.year()))), int2(static_cast<std::uint16_t>(static_cast<int>(ymd.year()))),
int1(static_cast<std::uint8_t>(static_cast<unsigned>(ymd.month()))), int1(static_cast<std::uint8_t>(static_cast<unsigned>(ymd.month()))),
int1(static_cast<std::uint8_t>(static_cast<unsigned>(ymd.day()))) int1(static_cast<std::uint8_t>(static_cast<unsigned>(ymd.day())))
); );
} }
struct broken_datetime struct broken_datetime
{ {
::date::year_month_day ymd; ::date::year_month_day ymd;
::date::time_of_day<std::chrono::microseconds> tod; ::date::time_of_day<std::chrono::microseconds> tod;
broken_datetime(const datetime& input) noexcept : broken_datetime(const datetime& input) noexcept :
ymd(::date::floor<::date::days>(input)), ymd(::date::floor<::date::days>(input)),
tod(input - ::date::floor<::date::days>(input)) tod(input - ::date::floor<::date::days>(input))
{ {
} }
// Doesn't count the first length byte // Doesn't count the first length byte
std::uint8_t binary_serialized_length() const noexcept std::uint8_t binary_serialized_length() const noexcept
{ {
std::uint8_t res = 11; // base length std::uint8_t res = 11; // base length
if (tod.subseconds().count() == 0) if (tod.subseconds().count() == 0)
{ {
res -= 4; res -= 4;
if (tod.seconds().count() == 0 && if (tod.seconds().count() == 0 &&
tod.minutes().count() == 0 && tod.minutes().count() == 0 &&
tod.hours().count() == 0) tod.hours().count() == 0)
{ {
res -= 3; res -= 3;
} }
} }
return res; return res;
} }
}; };
struct broken_time struct broken_time
{ {
::date::days days; ::date::days days;
std::chrono::hours hours; std::chrono::hours hours;
std::chrono::minutes minutes; std::chrono::minutes minutes;
std::chrono::seconds seconds; std::chrono::seconds seconds;
std::chrono::microseconds microseconds; std::chrono::microseconds microseconds;
broken_time(const time& input) noexcept : broken_time(const time& input) noexcept :
days(std::chrono::duration_cast<::date::days>(input)), days(std::chrono::duration_cast<::date::days>(input)),
hours(std::chrono::duration_cast<std::chrono::hours>(input % ::date::days(1))), hours(std::chrono::duration_cast<std::chrono::hours>(input % ::date::days(1))),
minutes(std::chrono::duration_cast<std::chrono::minutes>(input % std::chrono::hours(1))), minutes(std::chrono::duration_cast<std::chrono::minutes>(input % std::chrono::hours(1))),
seconds(std::chrono::duration_cast<std::chrono::seconds>(input % std::chrono::minutes(1))), seconds(std::chrono::duration_cast<std::chrono::seconds>(input % std::chrono::minutes(1))),
microseconds(input % std::chrono::seconds(1)) microseconds(input % std::chrono::seconds(1))
{ {
} }
// Doesn't count the first length byte // Doesn't count the first length byte
std::uint8_t binary_serialized_length() const noexcept std::uint8_t binary_serialized_length() const noexcept
{ {
std::uint8_t res = 12; std::uint8_t res = 12;
if (microseconds.count() == 0) if (microseconds.count() == 0)
{ {
res -= 4; res -= 4;
if (seconds.count() == 0 && if (seconds.count() == 0 &&
minutes.count() == 0 && minutes.count() == 0 &&
hours.count() == 0 && hours.count() == 0 &&
days.count() == 0) days.count() == 0)
{ {
res -= 8; res -= 8;
} }
} }
return res; return res;
} }
}; };
} // detail } // detail
@ -129,394 +129,394 @@ struct broken_time
inline boost::mysql::errc inline boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::int_lenenc, boost::mysql::detail::int_lenenc,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::deserialize_( >::deserialize_(
int_lenenc& output, int_lenenc& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
int1 first_byte; int1 first_byte;
errc err = deserialize(first_byte, ctx); errc err = deserialize(first_byte, ctx);
if (err != errc::ok) if (err != errc::ok)
{ {
return err; return err;
} }
if (first_byte.value == 0xFC) if (first_byte.value == 0xFC)
{ {
int2 value; int2 value;
err = deserialize(value, ctx); err = deserialize(value, ctx);
output.value = value.value; output.value = value.value;
} }
else if (first_byte.value == 0xFD) else if (first_byte.value == 0xFD)
{ {
int3 value; int3 value;
err = deserialize(value, ctx); err = deserialize(value, ctx);
output.value = value.value; output.value = value.value;
} }
else if (first_byte.value == 0xFE) else if (first_byte.value == 0xFE)
{ {
int8 value; int8 value;
err = deserialize(value, ctx); err = deserialize(value, ctx);
output.value = value.value; output.value = value.value;
} }
else else
{ {
err = errc::ok; err = errc::ok;
output.value = first_byte.value; output.value = first_byte.value;
} }
return err; return err;
} }
inline void inline void
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::int_lenenc, boost::mysql::detail::int_lenenc,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::serialize_( >::serialize_(
int_lenenc input, int_lenenc input,
serialization_context& ctx serialization_context& ctx
) noexcept ) noexcept
{ {
if (input.value < 251) if (input.value < 251)
{ {
serialize(int1(static_cast<std::uint8_t>(input.value)), ctx); serialize(int1(static_cast<std::uint8_t>(input.value)), ctx);
} }
else if (input.value < 0x10000) else if (input.value < 0x10000)
{ {
ctx.write(0xfc); ctx.write(0xfc);
serialize(int2(static_cast<std::uint16_t>(input.value)), ctx); serialize(int2(static_cast<std::uint16_t>(input.value)), ctx);
} }
else if (input.value < 0x1000000) else if (input.value < 0x1000000)
{ {
ctx.write(0xfd); ctx.write(0xfd);
serialize(int3(static_cast<std::uint32_t>(input.value)), ctx); serialize(int3(static_cast<std::uint32_t>(input.value)), ctx);
} }
else else
{ {
ctx.write(0xfe); ctx.write(0xfe);
serialize(int8(static_cast<std::uint64_t>(input.value)), ctx); serialize(int8(static_cast<std::uint64_t>(input.value)), ctx);
} }
} }
inline std::size_t inline std::size_t
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::int_lenenc, boost::mysql::detail::int_lenenc,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::get_size_( >::get_size_(
int_lenenc input, int_lenenc input,
const serialization_context& const serialization_context&
) noexcept ) noexcept
{ {
if (input.value < 251) return 1; if (input.value < 251) return 1;
else if (input.value < 0x10000) return 3; else if (input.value < 0x10000) return 3;
else if (input.value < 0x1000000) return 4; else if (input.value < 0x1000000) return 4;
else return 9; else return 9;
} }
inline boost::mysql::errc inline boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::string_null, boost::mysql::detail::string_null,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::deserialize_( >::deserialize_(
string_null& output, string_null& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
auto string_end = std::find(ctx.first(), ctx.last(), 0); auto string_end = std::find(ctx.first(), ctx.last(), 0);
if (string_end == ctx.last()) if (string_end == ctx.last())
{ {
return errc::incomplete_message; return errc::incomplete_message;
} }
output.value = get_string(ctx.first(), string_end-ctx.first()); output.value = get_string(ctx.first(), string_end-ctx.first());
ctx.set_first(string_end + 1); // skip the null terminator ctx.set_first(string_end + 1); // skip the null terminator
return errc::ok; return errc::ok;
} }
inline boost::mysql::errc inline boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::string_eof, boost::mysql::detail::string_eof,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::deserialize_( >::deserialize_(
string_eof& output, string_eof& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
output.value = get_string(ctx.first(), ctx.last()-ctx.first()); output.value = get_string(ctx.first(), ctx.last()-ctx.first());
ctx.set_first(ctx.last()); ctx.set_first(ctx.last());
return errc::ok; return errc::ok;
} }
inline boost::mysql::errc inline boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::detail::string_lenenc, boost::mysql::detail::string_lenenc,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::deserialize_( >::deserialize_(
string_lenenc& output, string_lenenc& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
int_lenenc length; int_lenenc length;
errc err = deserialize(length, ctx); errc err = deserialize(length, ctx);
if (err != errc::ok) if (err != errc::ok)
{ {
return err; return err;
} }
if (length.value > std::numeric_limits<std::size_t>::max()) if (length.value > std::numeric_limits<std::size_t>::max())
{ {
return errc::protocol_value_error; return errc::protocol_value_error;
} }
auto len = static_cast<std::size_t>(length.value); auto len = static_cast<std::size_t>(length.value);
if (!ctx.enough_size(len)) if (!ctx.enough_size(len))
{ {
return errc::incomplete_message; return errc::incomplete_message;
} }
output.value = get_string(ctx.first(), len); output.value = get_string(ctx.first(), len);
ctx.advance(len); ctx.advance(len);
return errc::ok; return errc::ok;
} }
inline std::size_t inline std::size_t
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::date, boost::mysql::date,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::get_size_( >::get_size_(
const date&, const date&,
const serialization_context& const serialization_context&
) noexcept ) noexcept
{ {
// TODO: consider zero dates? // TODO: consider zero dates?
return 5; // length, year, month, day return 5; // length, year, month, day
} }
inline void inline void
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::date, boost::mysql::date,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::serialize_( >::serialize_(
const date& input, const date& input,
serialization_context& ctx serialization_context& ctx
) noexcept ) noexcept
{ {
// TODO: consider zero dates? // TODO: consider zero dates?
serialize(int1(4), ctx); // serialize(int1(4), ctx); //
serialize_binary_ymd(::date::year_month_day (input), ctx); serialize_binary_ymd(::date::year_month_day (input), ctx);
} }
inline boost::mysql::errc inline boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::date, boost::mysql::date,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::deserialize_( >::deserialize_(
date& output, date& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
int1 length; int1 length;
auto err = deserialize(length, ctx); auto err = deserialize(length, ctx);
if (err != errc::ok) return err; if (err != errc::ok) return err;
return deserialize_binary_date(output, length.value, ctx); return deserialize_binary_date(output, length.value, ctx);
} }
// datetime // datetime
inline std::size_t inline std::size_t
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::datetime, boost::mysql::datetime,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::get_size_( >::get_size_(
const datetime& input, const datetime& input,
const serialization_context& const serialization_context&
) noexcept ) noexcept
{ {
broken_datetime dt (input); broken_datetime dt (input);
return dt.binary_serialized_length() + 1; // extra length prefix byte return dt.binary_serialized_length() + 1; // extra length prefix byte
} }
inline void inline void
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::datetime, boost::mysql::datetime,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::serialize_( >::serialize_(
const datetime& input, const datetime& input,
serialization_context& ctx serialization_context& ctx
) noexcept ) noexcept
{ {
broken_datetime brokendt (input); broken_datetime brokendt (input);
auto length = brokendt.binary_serialized_length(); auto length = brokendt.binary_serialized_length();
serialize(int1(length), ctx); serialize(int1(length), ctx);
if (length >= 4) // TODO: refactor these magic constants if (length >= 4) // TODO: refactor these magic constants
{ {
serialize_binary_ymd(brokendt.ymd, ctx); serialize_binary_ymd(brokendt.ymd, ctx);
} }
if (length >= 7) if (length >= 7)
{ {
serialize_fields( serialize_fields(
ctx, ctx,
int1(static_cast<std::uint8_t>(brokendt.tod.hours().count())), int1(static_cast<std::uint8_t>(brokendt.tod.hours().count())),
int1(static_cast<std::uint8_t>(brokendt.tod.minutes().count())), int1(static_cast<std::uint8_t>(brokendt.tod.minutes().count())),
int1(static_cast<std::uint8_t>(brokendt.tod.seconds().count())) int1(static_cast<std::uint8_t>(brokendt.tod.seconds().count()))
); );
} }
if (length >= 11) if (length >= 11)
{ {
auto micros = static_cast<std::uint32_t>(brokendt.tod.subseconds().count()); auto micros = static_cast<std::uint32_t>(brokendt.tod.subseconds().count());
serialize(int4(micros), ctx); serialize(int4(micros), ctx);
} }
} }
inline boost::mysql::errc inline boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::datetime, boost::mysql::datetime,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::deserialize_( >::deserialize_(
datetime& output, datetime& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
int1 length; int1 length;
date date_part; date date_part;
int1 hours; int1 hours;
int1 minutes; int1 minutes;
int1 seconds; int1 seconds;
int4 micros; int4 micros;
// Deserialize length // Deserialize length
auto err = deserialize(length, ctx); auto err = deserialize(length, ctx);
if (err != errc::ok) return err; if (err != errc::ok) return err;
// Based on length, deserialize the rest of the fields // Based on length, deserialize the rest of the fields
err = deserialize_binary_date(date_part, length.value, ctx); err = deserialize_binary_date(date_part, length.value, ctx);
if (err != errc::ok) return err; if (err != errc::ok) return err;
if (length.value >= 7) if (length.value >= 7)
{ {
err = deserialize_fields(ctx, hours, minutes, seconds); err = deserialize_fields(ctx, hours, minutes, seconds);
if (err != errc::ok) return err; if (err != errc::ok) return err;
} }
if (length.value >= 11) if (length.value >= 11)
{ {
err = deserialize(micros, ctx); err = deserialize(micros, ctx);
if (err != errc::ok) return err; if (err != errc::ok) return err;
} }
// Compose the final datetime. Doing time of day and date separately to avoid overflow // Compose the final datetime. Doing time of day and date separately to avoid overflow
auto time_of_day_part = std::chrono::hours(hours.value) + std::chrono::minutes(minutes.value) + auto time_of_day_part = std::chrono::hours(hours.value) + std::chrono::minutes(minutes.value) +
std::chrono::seconds(seconds.value) + std::chrono::microseconds(micros.value); std::chrono::seconds(seconds.value) + std::chrono::microseconds(micros.value);
output = date_part + time_of_day_part; output = date_part + time_of_day_part;
return errc::ok; return errc::ok;
} }
// time // time
inline std::size_t inline std::size_t
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::time, boost::mysql::time,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::get_size_( >::get_size_(
const time& input, const time& input,
const serialization_context& const serialization_context&
) noexcept ) noexcept
{ {
return broken_time(input).binary_serialized_length() + 1; // length byte return broken_time(input).binary_serialized_length() + 1; // length byte
} }
inline void inline void
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::time, boost::mysql::time,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::serialize_( >::serialize_(
const time& input, const time& input,
serialization_context& ctx serialization_context& ctx
) noexcept ) noexcept
{ {
broken_time broken (input); broken_time broken (input);
auto length = broken.binary_serialized_length(); auto length = broken.binary_serialized_length();
serialize(int1(length), ctx); serialize(int1(length), ctx);
if (length >= 8) // TODO: magic constants if (length >= 8) // TODO: magic constants
{ {
int1 is_negative (input.count() < 0 ? 1 : 0); int1 is_negative (input.count() < 0 ? 1 : 0);
serialize_fields( serialize_fields(
ctx, ctx,
is_negative, is_negative,
int4(static_cast<std::uint32_t>(std::abs(broken.days.count()))), int4(static_cast<std::uint32_t>(std::abs(broken.days.count()))),
int1(static_cast<std::uint8_t>(std::abs(broken.hours.count()))), int1(static_cast<std::uint8_t>(std::abs(broken.hours.count()))),
int1(static_cast<std::uint8_t>(std::abs(broken.minutes.count()))), int1(static_cast<std::uint8_t>(std::abs(broken.minutes.count()))),
int1(static_cast<std::uint8_t>(std::abs(broken.seconds.count()))) int1(static_cast<std::uint8_t>(std::abs(broken.seconds.count())))
); );
} }
if (length >= 12) if (length >= 12)
{ {
auto micros = static_cast<std::uint32_t>(std::abs(broken.microseconds.count())); auto micros = static_cast<std::uint32_t>(std::abs(broken.microseconds.count()));
serialize(int4(micros), ctx); serialize(int4(micros), ctx);
} }
} }
inline boost::mysql::errc inline boost::mysql::errc
boost::mysql::detail::serialization_traits< boost::mysql::detail::serialization_traits<
boost::mysql::time, boost::mysql::time,
boost::mysql::detail::serialization_tag::none boost::mysql::detail::serialization_tag::none
>::deserialize_( >::deserialize_(
time& output, time& output,
deserialization_context& ctx deserialization_context& ctx
) noexcept ) noexcept
{ {
// Length // Length
int1 length; int1 length;
auto err = deserialize(length, ctx); auto err = deserialize(length, ctx);
if (err != errc::ok) return err; if (err != errc::ok) return err;
int1 is_negative (0); int1 is_negative (0);
int4 days (0); int4 days (0);
int1 hours (0); int1 hours (0);
int1 minutes(0); int1 minutes(0);
int1 seconds(0); int1 seconds(0);
int4 microseconds(0); int4 microseconds(0);
if (length.value >= 8) if (length.value >= 8)
{ {
err = deserialize_fields( err = deserialize_fields(
ctx, ctx,
is_negative, is_negative,
days, days,
hours, hours,
minutes, minutes,
seconds seconds
); );
if (err != errc::ok) return err; if (err != errc::ok) return err;
} }
if (length.value >= 12) if (length.value >= 12)
{ {
err = deserialize(microseconds, ctx); err = deserialize(microseconds, ctx);
if (err != errc::ok) return err; if (err != errc::ok) return err;
} }
output = (is_negative.value ? -1 : 1) * ( output = (is_negative.value ? -1 : 1) * (
::date::days(days.value) + ::date::days(days.value) +
std::chrono::hours(hours.value) + std::chrono::hours(hours.value) +
std::chrono::minutes(minutes.value) + std::chrono::minutes(minutes.value) +
std::chrono::seconds(seconds.value) + std::chrono::seconds(seconds.value) +
std::chrono::microseconds(microseconds.value) std::chrono::microseconds(microseconds.value)
); );
return errc::ok; return errc::ok;
} }
inline std::pair<boost::mysql::error_code, std::uint8_t> inline std::pair<boost::mysql::error_code, std::uint8_t>
boost::mysql::detail::deserialize_message_type( boost::mysql::detail::deserialize_message_type(
deserialization_context& ctx deserialization_context& ctx
) )
{ {
int1 msg_type; int1 msg_type;
std::pair<mysql::error_code, std::uint8_t> res {}; std::pair<mysql::error_code, std::uint8_t> res {};
auto err = deserialize(msg_type, ctx); auto err = deserialize(msg_type, ctx);
if (err == errc::ok) if (err == errc::ok)
{ {
res.second = msg_type.value; res.second = msg_type.value;
} }
else else
{ {
res.first = make_error_code(err); res.first = make_error_code(err);
} }
return res; return res;
} }

View File

@ -18,90 +18,90 @@ namespace mysql {
namespace detail { namespace detail {
inline errc deserialize_text_value_impl( inline errc deserialize_text_value_impl(
std::string_view from, std::string_view from,
date& to date& to
) )
{ {
constexpr std::size_t size = 4 + 2 + 2 + 2; // year, month, day, separators constexpr std::size_t size = 4 + 2 + 2 + 2; // year, month, day, separators
if (from.size() != size) return errc::protocol_value_error; if (from.size() != size) return errc::protocol_value_error;
unsigned year, month, day; unsigned year, month, day;
char buffer [size + 1] {}; char buffer [size + 1] {};
memcpy(buffer, from.data(), from.size()); memcpy(buffer, from.data(), from.size());
int parsed = sscanf(buffer, "%4u-%2u-%2u", &year, &month, &day); int parsed = sscanf(buffer, "%4u-%2u-%2u", &year, &month, &day);
if (parsed != 3) return errc::protocol_value_error; if (parsed != 3) return errc::protocol_value_error;
::date::year_month_day result (::date::year(year)/::date::month(month)/::date::day(day)); ::date::year_month_day result (::date::year(year)/::date::month(month)/::date::day(day));
if (!result.ok()) return errc::protocol_value_error; if (!result.ok()) return errc::protocol_value_error;
if (result > max_date || result < min_date) return errc::protocol_value_error; if (result > max_date || result < min_date) return errc::protocol_value_error;
to = result; to = result;
return errc::ok; return errc::ok;
} }
inline errc deserialize_text_value_impl( inline errc deserialize_text_value_impl(
std::string_view from, std::string_view from,
time& to, time& to,
unsigned decimals unsigned decimals
) )
{ {
// size check // size check
constexpr std::size_t min_size = 2 + 2 + 2 + 2; // hours, mins, seconds, no micros constexpr std::size_t min_size = 2 + 2 + 2 + 2; // hours, mins, seconds, no micros
constexpr std::size_t max_size = min_size + 1 + 1 + 7; // hour extra character, sign and micros constexpr std::size_t max_size = min_size + 1 + 1 + 7; // hour extra character, sign and micros
decimals = std::min(decimals, 6u); decimals = std::min(decimals, 6u);
if (from.size() < min_size || from.size() > max_size) return errc::protocol_value_error; if (from.size() < min_size || from.size() > max_size) return errc::protocol_value_error;
// Parse it // Parse it
int hours; int hours;
unsigned minutes, seconds, micros = 0; unsigned minutes, seconds, micros = 0;
char buffer [max_size + 1] {}; char buffer [max_size + 1] {};
memcpy(buffer, from.data(), from.size()); memcpy(buffer, from.data(), from.size());
int parsed = decimals ? sscanf(buffer, "%4d:%2u:%2u.%6u", &hours, &minutes, &seconds, &micros) : // sign adds 1 char int parsed = decimals ? sscanf(buffer, "%4d:%2u:%2u.%6u", &hours, &minutes, &seconds, &micros) : // sign adds 1 char
sscanf(buffer, "%4d:%2u:%2u", &hours, &minutes, &seconds); sscanf(buffer, "%4d:%2u:%2u", &hours, &minutes, &seconds);
if ((decimals && parsed != 4) || (!decimals && parsed != 3)) return errc::protocol_value_error; if ((decimals && parsed != 4) || (!decimals && parsed != 3)) return errc::protocol_value_error;
micros *= static_cast<unsigned>(std::pow(10, 6 - decimals)); micros *= static_cast<unsigned>(std::pow(10, 6 - decimals));
hours = std::abs(hours); hours = std::abs(hours);
bool is_negative = from[0] == '-'; bool is_negative = from[0] == '-';
// Sum it // Sum it
auto res = std::chrono::hours(hours) + std::chrono::minutes(minutes) + auto res = std::chrono::hours(hours) + std::chrono::minutes(minutes) +
std::chrono::seconds(seconds) + std::chrono::microseconds(micros); std::chrono::seconds(seconds) + std::chrono::microseconds(micros);
if (is_negative) if (is_negative)
{ {
res = -res; res = -res;
} }
// Range check // Range check
if (res > max_time || res < min_time) return errc::protocol_value_error; if (res > max_time || res < min_time) return errc::protocol_value_error;
to = res; to = res;
return errc::ok; return errc::ok;
} }
inline errc deserialize_text_value_impl( inline errc deserialize_text_value_impl(
std::string_view from, std::string_view from,
datetime& to, datetime& to,
unsigned decimals unsigned decimals
) )
{ {
// Length check // Length check
constexpr std::size_t min_size = 4 + 5*2 + 5; // year, month, day, hour, minute, seconds, separators constexpr std::size_t min_size = 4 + 5*2 + 5; // year, month, day, hour, minute, seconds, separators
decimals = std::min(decimals, 6u); decimals = std::min(decimals, 6u);
std::size_t expected_size = min_size + (decimals ? decimals + 1 : 0); std::size_t expected_size = min_size + (decimals ? decimals + 1 : 0);
if (from.size() != expected_size) return errc::protocol_value_error; if (from.size() != expected_size) return errc::protocol_value_error;
// Date part // Date part
date dt; date dt;
auto err = deserialize_text_value_impl(from.substr(0, 10), dt); auto err = deserialize_text_value_impl(from.substr(0, 10), dt);
if (err != errc::ok) return err; if (err != errc::ok) return err;
// Time of day part // Time of day part
time time_of_day; time time_of_day;
err = deserialize_text_value_impl(from.substr(11), time_of_day, decimals); err = deserialize_text_value_impl(from.substr(11), time_of_day, decimals);
if (err != errc::ok) return err; if (err != errc::ok) return err;
constexpr auto max_time_of_day = std::chrono::hours(24) - std::chrono::microseconds(1); constexpr auto max_time_of_day = std::chrono::hours(24) - std::chrono::microseconds(1);
if (time_of_day < std::chrono::seconds(0) || time_of_day > max_time_of_day) return errc::protocol_value_error; if (time_of_day < std::chrono::seconds(0) || time_of_day > max_time_of_day) return errc::protocol_value_error;
// Sum it up // Sum it up
to = dt + time_of_day; to = dt + time_of_day;
return errc::ok; return errc::ok;
} }
@ -109,43 +109,43 @@ template <typename T>
std::enable_if_t<std::is_arithmetic_v<T>, errc> std::enable_if_t<std::is_arithmetic_v<T>, errc>
deserialize_text_value_impl(std::string_view from, T& to) deserialize_text_value_impl(std::string_view from, T& to)
{ {
bool ok = boost::conversion::try_lexical_convert(from.data(), from.size(), to); bool ok = boost::conversion::try_lexical_convert(from.data(), from.size(), to);
return ok ? errc::ok : errc::protocol_value_error; return ok ? errc::ok : errc::protocol_value_error;
} }
inline errc deserialize_text_value_impl(std::string_view from, std::string_view& to) inline errc deserialize_text_value_impl(std::string_view from, std::string_view& to)
{ {
to = from; to = from;
return errc::ok; return errc::ok;
} }
template <typename T, typename... Args> template <typename T, typename... Args>
errc deserialize_text_value_to_variant(std::string_view from, value& to, Args&&... args) errc deserialize_text_value_to_variant(std::string_view from, value& to, Args&&... args)
{ {
T value; T value;
auto err = deserialize_text_value_impl(from, value, std::forward<Args>(args)...); auto err = deserialize_text_value_impl(from, value, std::forward<Args>(args)...);
if (err == errc::ok) if (err == errc::ok)
{ {
to = value; to = value;
} }
return err; return err;
} }
inline bool is_next_field_null( inline bool is_next_field_null(
deserialization_context& ctx deserialization_context& ctx
) )
{ {
int1 type_byte; int1 type_byte;
errc err = deserialize(type_byte, ctx); errc err = deserialize(type_byte, ctx);
if (err == errc::ok) if (err == errc::ok)
{ {
if (type_byte.value == 0xfb) if (type_byte.value == 0xfb)
{ {
return true; // it was null, do not rewind return true; // it was null, do not rewind
} }
ctx.rewind(1); // it was not null, rewind (this byte is part of the actual message) ctx.rewind(1); // it was not null, rewind (this byte is part of the actual message)
} }
return false; return false;
} }
} // detail } // detail
@ -153,36 +153,36 @@ inline bool is_next_field_null(
} // boost } // boost
inline boost::mysql::errc boost::mysql::detail::deserialize_text_value( inline boost::mysql::errc boost::mysql::detail::deserialize_text_value(
std::string_view from, std::string_view from,
const field_metadata& meta, const field_metadata& meta,
value& output value& output
) )
{ {
switch (meta.protocol_type()) switch (meta.protocol_type())
{ {
case protocol_field_type::tiny: case protocol_field_type::tiny:
case protocol_field_type::short_: case protocol_field_type::short_:
case protocol_field_type::int24: case protocol_field_type::int24:
case protocol_field_type::long_: case protocol_field_type::long_:
case protocol_field_type::year: case protocol_field_type::year:
return meta.is_unsigned() ? return meta.is_unsigned() ?
deserialize_text_value_to_variant<std::uint32_t>(from, output) : deserialize_text_value_to_variant<std::uint32_t>(from, output) :
deserialize_text_value_to_variant<std::int32_t>(from, output); deserialize_text_value_to_variant<std::int32_t>(from, output);
case protocol_field_type::longlong: case protocol_field_type::longlong:
return meta.is_unsigned() ? return meta.is_unsigned() ?
deserialize_text_value_to_variant<std::uint64_t>(from, output) : deserialize_text_value_to_variant<std::uint64_t>(from, output) :
deserialize_text_value_to_variant<std::int64_t>(from, output); deserialize_text_value_to_variant<std::int64_t>(from, output);
case protocol_field_type::float_: case protocol_field_type::float_:
return deserialize_text_value_to_variant<float>(from, output); return deserialize_text_value_to_variant<float>(from, output);
case protocol_field_type::double_: case protocol_field_type::double_:
return deserialize_text_value_to_variant<double>(from, output); return deserialize_text_value_to_variant<double>(from, output);
case protocol_field_type::timestamp: case protocol_field_type::timestamp:
case protocol_field_type::datetime: case protocol_field_type::datetime:
return deserialize_text_value_to_variant<datetime>(from, output, meta.decimals()); return deserialize_text_value_to_variant<datetime>(from, output, meta.decimals());
case protocol_field_type::date: case protocol_field_type::date:
return deserialize_text_value_to_variant<date>(from, output); return deserialize_text_value_to_variant<date>(from, output);
case protocol_field_type::time: case protocol_field_type::time:
return deserialize_text_value_to_variant<time>(from, output, meta.decimals()); return deserialize_text_value_to_variant<time>(from, output, meta.decimals());
// True string types // True string types
case protocol_field_type::varchar: case protocol_field_type::varchar:
case protocol_field_type::var_string: case protocol_field_type::var_string:
@ -199,36 +199,36 @@ inline boost::mysql::errc boost::mysql::detail::deserialize_text_value(
case protocol_field_type::newdecimal: case protocol_field_type::newdecimal:
case protocol_field_type::geometry: case protocol_field_type::geometry:
default: default:
return deserialize_text_value_to_variant<std::string_view>(from, output); return deserialize_text_value_to_variant<std::string_view>(from, output);
} }
} }
boost::mysql::error_code boost::mysql::detail::deserialize_text_row( boost::mysql::error_code boost::mysql::detail::deserialize_text_row(
deserialization_context& ctx, deserialization_context& ctx,
const std::vector<field_metadata>& fields, const std::vector<field_metadata>& fields,
std::vector<value>& output std::vector<value>& output
) )
{ {
output.resize(fields.size()); output.resize(fields.size());
for (std::vector<value>::size_type i = 0; i < fields.size(); ++i) for (std::vector<value>::size_type i = 0; i < fields.size(); ++i)
{ {
bool is_null = is_next_field_null(ctx); bool is_null = is_next_field_null(ctx);
if (is_null) if (is_null)
{ {
output[i] = nullptr; output[i] = nullptr;
} }
else else
{ {
string_lenenc value_str; string_lenenc value_str;
errc err = deserialize(value_str, ctx); errc err = deserialize(value_str, ctx);
if (err != errc::ok) return make_error_code(err); if (err != errc::ok) return make_error_code(err);
err = deserialize_text_value(value_str.value, fields[i], output[i]); err = deserialize_text_value(value_str.value, fields[i], output[i]);
if (err != errc::ok) return make_error_code(err); if (err != errc::ok) return make_error_code(err);
} }
} }
if (!ctx.empty()) return make_error_code(errc::extra_bytes); if (!ctx.empty()) return make_error_code(errc::extra_bytes);
return error_code(); return error_code();
} }

View File

@ -17,28 +17,28 @@ namespace detail {
class null_bitmap_traits class null_bitmap_traits
{ {
std::size_t offset_; std::size_t offset_;
std::size_t num_fields_; std::size_t num_fields_;
constexpr std::size_t byte_pos(std::size_t field_pos) const noexcept { return (field_pos + offset_) / 8; } constexpr std::size_t byte_pos(std::size_t field_pos) const noexcept { return (field_pos + offset_) / 8; }
constexpr std::size_t bit_pos(std::size_t field_pos) const noexcept { return (field_pos + offset_) % 8; } constexpr std::size_t bit_pos(std::size_t field_pos) const noexcept { return (field_pos + offset_) % 8; }
public: public:
constexpr null_bitmap_traits(std::size_t offset, std::size_t num_fields) noexcept: constexpr null_bitmap_traits(std::size_t offset, std::size_t num_fields) noexcept:
offset_(offset), num_fields_ {num_fields} {}; offset_(offset), num_fields_ {num_fields} {};
constexpr std::size_t offset() const noexcept { return offset_; } constexpr std::size_t offset() const noexcept { return offset_; }
constexpr std::size_t num_fields() const noexcept { return num_fields_; } constexpr std::size_t num_fields() const noexcept { return num_fields_; }
constexpr std::size_t byte_count() const noexcept { return (num_fields_ + 7 + offset_) / 8; } constexpr std::size_t byte_count() const noexcept { return (num_fields_ + 7 + offset_) / 8; }
bool is_null(const std::uint8_t* null_bitmap_begin, std::size_t field_pos) const noexcept bool is_null(const std::uint8_t* null_bitmap_begin, std::size_t field_pos) const noexcept
{ {
assert(field_pos < num_fields_); assert(field_pos < num_fields_);
return null_bitmap_begin[byte_pos(field_pos)] & (1 << bit_pos(field_pos)); return null_bitmap_begin[byte_pos(field_pos)] & (1 << bit_pos(field_pos));
} }
void set_null(std::uint8_t* null_bitmap_begin, std::size_t field_pos) const noexcept void set_null(std::uint8_t* null_bitmap_begin, std::size_t field_pos) const noexcept
{ {
assert(field_pos < num_fields_); assert(field_pos < num_fields_);
null_bitmap_begin[byte_pos(field_pos)] |= (1 << bit_pos(field_pos)); null_bitmap_begin[byte_pos(field_pos)] |= (1 << bit_pos(field_pos));
} }
}; };
constexpr std::size_t stmt_execute_null_bitmap_offset = 0; constexpr std::size_t stmt_execute_null_bitmap_offset = 0;

View File

@ -19,117 +19,117 @@ namespace detail {
// prepare // prepare
struct com_stmt_prepare_packet struct com_stmt_prepare_packet
{ {
string_eof statement; string_eof statement;
static constexpr std::uint8_t command_id = 0x16; static constexpr std::uint8_t command_id = 0x16;
}; };
template <> template <>
struct get_struct_fields<com_stmt_prepare_packet> struct get_struct_fields<com_stmt_prepare_packet>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&com_stmt_prepare_packet::statement &com_stmt_prepare_packet::statement
); );
}; };
// response // response
struct com_stmt_prepare_ok_packet struct com_stmt_prepare_ok_packet
{ {
// int1 status: must be 0 // int1 status: must be 0
int4 statement_id; int4 statement_id;
int2 num_columns; int2 num_columns;
int2 num_params; int2 num_params;
// int1 reserved_1: must be 0 // int1 reserved_1: must be 0
int2 warning_count; int2 warning_count;
// TODO: int1 metadata_follows when CLIENT_OPTIONAL_RESULTSET_METADATA // TODO: int1 metadata_follows when CLIENT_OPTIONAL_RESULTSET_METADATA
}; };
template <> template <>
struct get_struct_fields<com_stmt_prepare_ok_packet> struct get_struct_fields<com_stmt_prepare_ok_packet>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&com_stmt_prepare_ok_packet::statement_id, &com_stmt_prepare_ok_packet::statement_id,
&com_stmt_prepare_ok_packet::num_columns, &com_stmt_prepare_ok_packet::num_columns,
&com_stmt_prepare_ok_packet::num_params, &com_stmt_prepare_ok_packet::num_params,
&com_stmt_prepare_ok_packet::warning_count &com_stmt_prepare_ok_packet::warning_count
); );
}; };
template <> template <>
struct serialization_traits<com_stmt_prepare_ok_packet, serialization_tag::struct_with_fields> : struct serialization_traits<com_stmt_prepare_ok_packet, serialization_tag::struct_with_fields> :
noop_serialize<com_stmt_prepare_ok_packet> noop_serialize<com_stmt_prepare_ok_packet>
{ {
static inline errc deserialize_(com_stmt_prepare_ok_packet& output, static inline errc deserialize_(com_stmt_prepare_ok_packet& output,
deserialization_context& ctx) noexcept; deserialization_context& ctx) noexcept;
}; };
// execute // execute
template <typename ForwardIterator> template <typename ForwardIterator>
struct com_stmt_execute_packet struct com_stmt_execute_packet
{ {
int4 statement_id; int4 statement_id;
int1 flags; int1 flags;
int4 iteration_count; int4 iteration_count;
// if num_params > 0: NULL bitmap // if num_params > 0: NULL bitmap
int1 new_params_bind_flag; int1 new_params_bind_flag;
ForwardIterator params_begin; ForwardIterator params_begin;
ForwardIterator params_end; ForwardIterator params_end;
static constexpr std::uint8_t command_id = 0x17; static constexpr std::uint8_t command_id = 0x17;
}; };
template <typename ForwardIterator> template <typename ForwardIterator>
struct get_struct_fields<com_stmt_execute_packet<ForwardIterator>> struct get_struct_fields<com_stmt_execute_packet<ForwardIterator>>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&com_stmt_execute_packet<ForwardIterator>::statement_id, &com_stmt_execute_packet<ForwardIterator>::statement_id,
&com_stmt_execute_packet<ForwardIterator>::flags, &com_stmt_execute_packet<ForwardIterator>::flags,
&com_stmt_execute_packet<ForwardIterator>::iteration_count, &com_stmt_execute_packet<ForwardIterator>::iteration_count,
&com_stmt_execute_packet<ForwardIterator>::new_params_bind_flag &com_stmt_execute_packet<ForwardIterator>::new_params_bind_flag
); );
}; };
template <typename ForwardIterator> template <typename ForwardIterator>
struct serialization_traits< struct serialization_traits<
com_stmt_execute_packet<ForwardIterator>, com_stmt_execute_packet<ForwardIterator>,
serialization_tag::struct_with_fields serialization_tag::struct_with_fields
> : noop_deserialize<com_stmt_execute_packet<ForwardIterator>> > : noop_deserialize<com_stmt_execute_packet<ForwardIterator>>
{ {
static inline std::size_t get_size_(const com_stmt_execute_packet<ForwardIterator>& value, static inline std::size_t get_size_(const com_stmt_execute_packet<ForwardIterator>& value,
const serialization_context& ctx) noexcept; const serialization_context& ctx) noexcept;
static inline void serialize_(const com_stmt_execute_packet<ForwardIterator>& input, static inline void serialize_(const com_stmt_execute_packet<ForwardIterator>& input,
serialization_context& ctx) noexcept; serialization_context& ctx) noexcept;
}; };
struct com_stmt_execute_param_meta_packet struct com_stmt_execute_param_meta_packet
{ {
protocol_field_type type; protocol_field_type type;
int1 unsigned_flag; int1 unsigned_flag;
}; };
template <> template <>
struct get_struct_fields<com_stmt_execute_param_meta_packet> struct get_struct_fields<com_stmt_execute_param_meta_packet>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&com_stmt_execute_param_meta_packet::type, &com_stmt_execute_param_meta_packet::type,
&com_stmt_execute_param_meta_packet::unsigned_flag &com_stmt_execute_param_meta_packet::unsigned_flag
); );
}; };
// close // close
struct com_stmt_close_packet struct com_stmt_close_packet
{ {
int4 statement_id; int4 statement_id;
static constexpr std::uint8_t command_id = 0x19; static constexpr std::uint8_t command_id = 0x19;
}; };
template <> template <>
struct get_struct_fields<com_stmt_close_packet> struct get_struct_fields<com_stmt_close_packet>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&com_stmt_close_packet::statement_id &com_stmt_close_packet::statement_id
); );
}; };

View File

@ -17,17 +17,17 @@ namespace detail {
struct com_query_packet struct com_query_packet
{ {
string_eof query; string_eof query;
static constexpr std::uint8_t command_id = 3; static constexpr std::uint8_t command_id = 3;
}; };
template <> template <>
struct get_struct_fields<com_query_packet> struct get_struct_fields<com_query_packet>
{ {
static constexpr auto value = std::make_tuple( static constexpr auto value = std::make_tuple(
&com_query_packet::query &com_query_packet::query
); );
}; };
} }

View File

@ -24,11 +24,11 @@ namespace detail {
enum class serialization_tag enum class serialization_tag
{ {
none, none,
fixed_size_int, fixed_size_int,
floating_point, floating_point,
enumeration, enumeration,
struct_with_fields struct_with_fields
}; };
template <typename T> template <typename T>
@ -40,19 +40,19 @@ struct serialization_traits;
template <typename T> template <typename T>
errc deserialize(T& output, deserialization_context& ctx) noexcept errc deserialize(T& output, deserialization_context& ctx) noexcept
{ {
return serialization_traits<T>::deserialize_(output, ctx); return serialization_traits<T>::deserialize_(output, ctx);
} }
template <typename T> template <typename T>
void serialize(const T& input, serialization_context& ctx) noexcept void serialize(const T& input, serialization_context& ctx) noexcept
{ {
serialization_traits<T>::serialize_(input, ctx); serialization_traits<T>::serialize_(input, ctx);
} }
template <typename T> template <typename T>
std::size_t get_size(const T& input, const serialization_context& ctx) noexcept std::size_t get_size(const T& input, const serialization_context& ctx) noexcept
{ {
return serialization_traits<T>::get_size_(input, ctx); return serialization_traits<T>::get_size_(input, ctx);
} }
// Fixed-size integers // Fixed-size integers
@ -62,18 +62,18 @@ constexpr bool is_fixed_size_int();
template <typename T> template <typename T>
struct serialization_traits<T, serialization_tag::fixed_size_int> struct serialization_traits<T, serialization_tag::fixed_size_int>
{ {
static errc deserialize_(T& output, deserialization_context& ctx) noexcept; static errc deserialize_(T& output, deserialization_context& ctx) noexcept;
static void serialize_(T input, serialization_context& ctx) noexcept; static void serialize_(T input, serialization_context& ctx) noexcept;
static constexpr std::size_t get_size_(T, const serialization_context&) noexcept; static constexpr std::size_t get_size_(T, const serialization_context&) noexcept;
}; };
// int_lenenc // int_lenenc
template <> template <>
struct serialization_traits<int_lenenc, serialization_tag::none> struct serialization_traits<int_lenenc, serialization_tag::none>
{ {
static inline errc deserialize_(int_lenenc& output, deserialization_context& ctx) noexcept; static inline errc deserialize_(int_lenenc& output, deserialization_context& ctx) noexcept;
static inline void serialize_(int_lenenc input, serialization_context& ctx) noexcept; static inline void serialize_(int_lenenc input, serialization_context& ctx) noexcept;
static inline std::size_t get_size_(int_lenenc input, const serialization_context&) noexcept; static inline std::size_t get_size_(int_lenenc input, const serialization_context&) noexcept;
}; };
@ -81,15 +81,15 @@ struct serialization_traits<int_lenenc, serialization_tag::none>
template <std::size_t N> template <std::size_t N>
struct serialization_traits<string_fixed<N>, serialization_tag::none> struct serialization_traits<string_fixed<N>, serialization_tag::none>
{ {
static errc deserialize_(string_fixed<N>& output, deserialization_context& ctx) noexcept; static errc deserialize_(string_fixed<N>& output, deserialization_context& ctx) noexcept;
static void serialize_(const string_fixed<N>& input, serialization_context& ctx) noexcept static void serialize_(const string_fixed<N>& input, serialization_context& ctx) noexcept
{ {
ctx.write(input.data(), N); ctx.write(input.data(), N);
} }
static constexpr std::size_t get_size_(const string_fixed<N>&, const serialization_context&) noexcept static constexpr std::size_t get_size_(const string_fixed<N>&, const serialization_context&) noexcept
{ {
return N; return N;
} }
}; };
@ -97,75 +97,75 @@ struct serialization_traits<string_fixed<N>, serialization_tag::none>
template <> template <>
struct serialization_traits<string_null, serialization_tag::none> struct serialization_traits<string_null, serialization_tag::none>
{ {
static inline errc deserialize_(string_null& output, deserialization_context& ctx) noexcept; static inline errc deserialize_(string_null& output, deserialization_context& ctx) noexcept;
static inline void serialize_(string_null input, serialization_context& ctx) noexcept static inline void serialize_(string_null input, serialization_context& ctx) noexcept
{ {
ctx.write(input.value.data(), input.value.size()); ctx.write(input.value.data(), input.value.size());
ctx.write(0); // null terminator ctx.write(0); // null terminator
} }
static inline std::size_t get_size_(string_null input, const serialization_context&) noexcept static inline std::size_t get_size_(string_null input, const serialization_context&) noexcept
{ {
return input.value.size() + 1; return input.value.size() + 1;
} }
}; };
// string_eof // string_eof
template <> template <>
struct serialization_traits<string_eof, serialization_tag::none> struct serialization_traits<string_eof, serialization_tag::none>
{ {
static inline errc deserialize_(string_eof& output, deserialization_context& ctx) noexcept; static inline errc deserialize_(string_eof& output, deserialization_context& ctx) noexcept;
static inline void serialize_(string_eof input, serialization_context& ctx) noexcept static inline void serialize_(string_eof input, serialization_context& ctx) noexcept
{ {
ctx.write(input.value.data(), input.value.size()); ctx.write(input.value.data(), input.value.size());
} }
static inline std::size_t get_size_(string_eof input, const serialization_context&) noexcept static inline std::size_t get_size_(string_eof input, const serialization_context&) noexcept
{ {
return input.value.size(); return input.value.size();
} }
}; };
// string_lenenc // string_lenenc
template <> template <>
struct serialization_traits<string_lenenc, serialization_tag::none> struct serialization_traits<string_lenenc, serialization_tag::none>
{ {
static inline errc deserialize_(string_lenenc& output, deserialization_context& ctx) noexcept; static inline errc deserialize_(string_lenenc& output, deserialization_context& ctx) noexcept;
static inline void serialize_(string_lenenc input, serialization_context& ctx) noexcept static inline void serialize_(string_lenenc input, serialization_context& ctx) noexcept
{ {
serialize(int_lenenc(input.value.size()), ctx); serialize(int_lenenc(input.value.size()), ctx);
ctx.write(input.value.data(), input.value.size()); ctx.write(input.value.data(), input.value.size());
} }
static inline std::size_t get_size_(string_lenenc input, const serialization_context& ctx) noexcept static inline std::size_t get_size_(string_lenenc input, const serialization_context& ctx) noexcept
{ {
return get_size(int_lenenc(input.value.size()), ctx) + input.value.size(); return get_size(int_lenenc(input.value.size()), ctx) + input.value.size();
} }
}; };
// Enums // Enums
template <typename T> template <typename T>
struct serialization_traits<T, serialization_tag::enumeration> struct serialization_traits<T, serialization_tag::enumeration>
{ {
using underlying_type = std::underlying_type_t<T>; using underlying_type = std::underlying_type_t<T>;
using serializable_type = value_holder<underlying_type>; using serializable_type = value_holder<underlying_type>;
static errc deserialize_(T& output, deserialization_context& ctx) noexcept; static errc deserialize_(T& output, deserialization_context& ctx) noexcept;
static void serialize_(T input, serialization_context& ctx) noexcept static void serialize_(T input, serialization_context& ctx) noexcept
{ {
serialize(serializable_type(static_cast<underlying_type>(input)), ctx); serialize(serializable_type(static_cast<underlying_type>(input)), ctx);
} }
static std::size_t get_size_(T, const serialization_context&) noexcept; static std::size_t get_size_(T, const serialization_context&) noexcept;
}; };
// Floating points // Floating points
template <typename T> template <typename T>
struct serialization_traits<T, serialization_tag::floating_point> struct serialization_traits<T, serialization_tag::floating_point>
{ {
static_assert(std::numeric_limits<T>::is_iec559); static_assert(std::numeric_limits<T>::is_iec559);
static errc deserialize_(T& output, deserialization_context& ctx) noexcept; static errc deserialize_(T& output, deserialization_context& ctx) noexcept;
static void serialize_(T input, serialization_context& ctx) noexcept; static void serialize_(T input, serialization_context& ctx) noexcept;
static std::size_t get_size_(T, const serialization_context&) noexcept static std::size_t get_size_(T, const serialization_context&) noexcept
{ {
return sizeof(T); return sizeof(T);
} }
}; };
@ -173,25 +173,25 @@ struct serialization_traits<T, serialization_tag::floating_point>
template <> template <>
struct serialization_traits<date, serialization_tag::none> struct serialization_traits<date, serialization_tag::none>
{ {
static inline std::size_t get_size_(const date& input, const serialization_context& ctx) noexcept; static inline std::size_t get_size_(const date& input, const serialization_context& ctx) noexcept;
static inline void serialize_(const date& input, serialization_context& ctx) noexcept; static inline void serialize_(const date& input, serialization_context& ctx) noexcept;
static inline errc deserialize_(date& output, deserialization_context& ctx) noexcept; static inline errc deserialize_(date& output, deserialization_context& ctx) noexcept;
}; };
template <> template <>
struct serialization_traits<datetime, serialization_tag::none> struct serialization_traits<datetime, serialization_tag::none>
{ {
static inline std::size_t get_size_(const datetime& input, const serialization_context& ctx) noexcept; static inline std::size_t get_size_(const datetime& input, const serialization_context& ctx) noexcept;
static inline void serialize_(const datetime& input, serialization_context& ctx) noexcept; static inline void serialize_(const datetime& input, serialization_context& ctx) noexcept;
static inline errc deserialize_(datetime& output, deserialization_context& ctx) noexcept; static inline errc deserialize_(datetime& output, deserialization_context& ctx) noexcept;
}; };
template <> template <>
struct serialization_traits<time, serialization_tag::none> struct serialization_traits<time, serialization_tag::none>
{ {
static inline std::size_t get_size_(const time& input, const serialization_context& ctx) noexcept; static inline std::size_t get_size_(const time& input, const serialization_context& ctx) noexcept;
static inline void serialize_(const time& input, serialization_context& ctx) noexcept; static inline void serialize_(const time& input, serialization_context& ctx) noexcept;
static inline errc deserialize_(time& output, deserialization_context& ctx) noexcept; static inline errc deserialize_(time& output, deserialization_context& ctx) noexcept;
}; };
// Structs and commands (messages) // Structs and commands (messages)
@ -204,7 +204,7 @@ struct not_a_struct_with_fields {}; // Tag indicating a type is not a struct wit
template <typename T> template <typename T>
struct get_struct_fields struct get_struct_fields
{ {
static constexpr not_a_struct_with_fields value {}; static constexpr not_a_struct_with_fields value {};
}; };
template <typename T> template <typename T>
@ -213,9 +213,9 @@ constexpr bool is_struct_with_fields();
template <typename T> template <typename T>
struct serialization_traits<T, serialization_tag::struct_with_fields> struct serialization_traits<T, serialization_tag::struct_with_fields>
{ {
static errc deserialize_(T& output, deserialization_context& ctx) noexcept; static errc deserialize_(T& output, deserialization_context& ctx) noexcept;
static void serialize_(const T& input, serialization_context& ctx) noexcept; static void serialize_(const T& input, serialization_context& ctx) noexcept;
static std::size_t get_size_(const T& input, const serialization_context& ctx) noexcept; static std::size_t get_size_(const T& input, const serialization_context& ctx) noexcept;
}; };
// Use these to make all messages implement all methods, leaving the not required // Use these to make all messages implement all methods, leaving the not required
@ -224,28 +224,28 @@ struct serialization_traits<T, serialization_tag::struct_with_fields>
template <typename T> template <typename T>
struct noop_serialize struct noop_serialize
{ {
static inline std::size_t get_size_(const T&, const serialization_context&) noexcept { return 0; } static inline std::size_t get_size_(const T&, const serialization_context&) noexcept { return 0; }
static inline void serialize_(const T&, serialization_context&) noexcept {} static inline void serialize_(const T&, serialization_context&) noexcept {}
}; };
template <typename T> template <typename T>
struct noop_deserialize struct noop_deserialize
{ {
static inline errc deserialize_(T&, deserialization_context&) noexcept { return errc::ok; } static inline errc deserialize_(T&, deserialization_context&) noexcept { return errc::ok; }
}; };
// Helper to serialize top-level messages // Helper to serialize top-level messages
template <typename Serializable, typename Allocator> template <typename Serializable, typename Allocator>
void serialize_message( void serialize_message(
const Serializable& input, const Serializable& input,
capabilities caps, capabilities caps,
basic_bytestring<Allocator>& buffer basic_bytestring<Allocator>& buffer
); );
template <typename Deserializable> template <typename Deserializable>
error_code deserialize_message( error_code deserialize_message(
Deserializable& output, Deserializable& output,
deserialization_context& ctx deserialization_context& ctx
); );
// Helpers for (de) serializing a set of fields // Helpers for (de) serializing a set of fields
@ -256,7 +256,7 @@ template <typename... Types>
void serialize_fields(serialization_context& ctx, const Types&... fields) noexcept; void serialize_fields(serialization_context& ctx, const Types&... fields) noexcept;
inline std::pair<error_code, std::uint8_t> deserialize_message_type( inline std::pair<error_code, std::uint8_t> deserialize_message_type(
deserialization_context& ctx deserialization_context& ctx
); );

View File

@ -19,18 +19,18 @@ namespace detail {
class serialization_context class serialization_context
{ {
std::uint8_t* first_; std::uint8_t* first_;
capabilities capabilities_; capabilities capabilities_;
public: public:
serialization_context(capabilities caps, std::uint8_t* first = nullptr) noexcept: serialization_context(capabilities caps, std::uint8_t* first = nullptr) noexcept:
first_(first), capabilities_(caps) {}; first_(first), capabilities_(caps) {};
std::uint8_t* first() const noexcept { return first_; } std::uint8_t* first() const noexcept { return first_; }
void set_first(std::uint8_t* new_first) noexcept { first_ = new_first; } void set_first(std::uint8_t* new_first) noexcept { first_ = new_first; }
void set_first(boost::asio::mutable_buffer buff) noexcept { first_ = static_cast<std::uint8_t*>(buff.data()); } void set_first(boost::asio::mutable_buffer buff) noexcept { first_ = static_cast<std::uint8_t*>(buff.data()); }
void advance(std::size_t size) noexcept { first_ += size; } void advance(std::size_t size) noexcept { first_ += size; }
capabilities get_capabilities() const noexcept { return capabilities_; } capabilities get_capabilities() const noexcept { return capabilities_; }
void write(const void* buffer, std::size_t size) noexcept { memcpy(first_, buffer, size); advance(size); } void write(const void* buffer, std::size_t size) noexcept { memcpy(first_, buffer, size); advance(size); }
void write(std::uint8_t elm) noexcept { *first_ = elm; ++first_; } void write(std::uint8_t elm) noexcept { *first_ = elm; ++first_; }
}; };
} }

View File

@ -19,15 +19,15 @@ namespace mysql {
namespace detail { namespace detail {
inline errc deserialize_text_value( inline errc deserialize_text_value(
std::string_view from, std::string_view from,
const field_metadata& meta, const field_metadata& meta,
value& output value& output
); );
inline error_code deserialize_text_row( inline error_code deserialize_text_row(
deserialization_context& ctx, deserialization_context& ctx,
const std::vector<field_metadata>& meta, const std::vector<field_metadata>& meta,
std::vector<value>& output std::vector<value>& output
); );
} // detail } // detail

View File

@ -18,20 +18,20 @@ namespace detail {
template <typename T> template <typename T>
struct value_holder struct value_holder
{ {
using value_type = T; using value_type = T;
static_assert(std::is_nothrow_default_constructible_v<T>); static_assert(std::is_nothrow_default_constructible_v<T>);
value_type value; value_type value;
value_holder() noexcept: value{} {}; value_holder() noexcept: value{} {};
template <typename U> template <typename U>
explicit constexpr value_holder(U&& u) explicit constexpr value_holder(U&& u)
noexcept(std::is_nothrow_constructible_v<T, decltype(u)>): noexcept(std::is_nothrow_constructible_v<T, decltype(u)>):
value(std::forward<U>(u)) {} value(std::forward<U>(u)) {}
constexpr bool operator==(const value_holder<T>& rhs) const { return value == rhs.value; } constexpr bool operator==(const value_holder<T>& rhs) const { return value == rhs.value; }
constexpr bool operator!=(const value_holder<T>& rhs) const { return value != rhs.value; } constexpr bool operator!=(const value_holder<T>& rhs) const { return value != rhs.value; }
}; };
// Operations on value holders // Operations on value holders

View File

@ -30,20 +30,20 @@ namespace mysql {
*/ */
enum class errc : int enum class errc : int
{ {
ok = 0, ///< No error. ok = 0, ///< No error.
// Server returned errors // Server returned errors
#include "boost/mysql/impl/server_error_enum.hpp" #include "boost/mysql/impl/server_error_enum.hpp"
// Protocol errors // Protocol errors
incomplete_message = 0x10000, ///< An incomplete message was received from the server. incomplete_message = 0x10000, ///< An incomplete message was received from the server.
extra_bytes, ///< Unexpected extra bytes at the end of a message were received. extra_bytes, ///< Unexpected extra bytes at the end of a message were received.
sequence_number_mismatch, ///< A sequence number mismatched happened. sequence_number_mismatch, ///< A sequence number mismatched happened.
server_unsupported, ///< The server does not support the minimum required capabilities to establish the connection. server_unsupported, ///< The server does not support the minimum required capabilities to establish the connection.
protocol_value_error, ///< An unexpected value was found in a server-received message. protocol_value_error, ///< An unexpected value was found in a server-received message.
unknown_auth_plugin, ///< The user employs an authentication plugin not known to this library. unknown_auth_plugin, ///< The user employs an authentication plugin not known to this library.
auth_plugin_requires_ssl, ///< The authentication plugin requires the connection to use SSL. auth_plugin_requires_ssl, ///< The authentication plugin requires the connection to use SSL.
wrong_num_params ///< The number of parameters passed to the prepared statement does not match the number of actual parameters. wrong_num_params ///< The number of parameters passed to the prepared statement does not match the number of actual parameters.
}; };
/** /**
@ -61,22 +61,22 @@ using error_code = boost::system::error_code;
*/ */
class error_info class error_info
{ {
std::string msg_; std::string msg_;
public: public:
/// Default constructor. /// Default constructor.
error_info() = default; error_info() = default;
/// Initialization constructor. /// Initialization constructor.
error_info(std::string&& err) noexcept: msg_(std::move(err)) {} error_info(std::string&& err) noexcept: msg_(std::move(err)) {}
/// Gets the error message. /// Gets the error message.
const std::string& message() const noexcept { return msg_; } const std::string& message() const noexcept { return msg_; }
/// Sets the error message. /// Sets the error message.
void set_message(std::string&& err) { msg_ = std::move(err); } void set_message(std::string&& err) { msg_ = std::move(err); }
/// Restores the object to its initial state. /// Restores the object to its initial state.
void clear() noexcept { msg_.clear(); } void clear() noexcept { msg_.clear(); }
}; };
/** /**

View File

@ -23,32 +23,32 @@ namespace mysql {
*/ */
enum class field_type enum class field_type
{ {
tinyint, ///< TINYINT (signed and unsigned). tinyint, ///< TINYINT (signed and unsigned).
smallint, ///< SMALLINT (signed and unsigned). smallint, ///< SMALLINT (signed and unsigned).
mediumint, ///< MEDIUMINT (signed and unsigned). mediumint, ///< MEDIUMINT (signed and unsigned).
int_, ///< Plain INT (signed and unsigned). int_, ///< Plain INT (signed and unsigned).
bigint, ///< BIGINT (signed and unsigned). bigint, ///< BIGINT (signed and unsigned).
float_, ///< FLOAT (warning: FLOAT(p) where p >= 24 creates a DOUBLE column). float_, ///< FLOAT (warning: FLOAT(p) where p >= 24 creates a DOUBLE column).
double_, ///< DOUBLE double_, ///< DOUBLE
decimal, ///< DECIMAL decimal, ///< DECIMAL
bit, ///< BIT bit, ///< BIT
year, ///< YEAR year, ///< YEAR
time, ///< TIME time, ///< TIME
date, ///< DATE date, ///< DATE
datetime, ///< DATETIME datetime, ///< DATETIME
timestamp, ///< TIMESTAMP timestamp, ///< TIMESTAMP
char_, ///< CHAR (any length) char_, ///< CHAR (any length)
varchar, ///< VARCHAR (any length) varchar, ///< VARCHAR (any length)
binary, ///< BINARY (any length) binary, ///< BINARY (any length)
varbinary, ///< VARBINARY (any length) varbinary, ///< VARBINARY (any length)
text, ///< TINYTEXT, TEXT, MEDIUMTEXT and LONGTEXT text, ///< TINYTEXT, TEXT, MEDIUMTEXT and LONGTEXT
blob, ///< TINYBLOB, BLOB, MEDIUMBLOB and LONGBLOB blob, ///< TINYBLOB, BLOB, MEDIUMBLOB and LONGBLOB
enum_, ///< ENUM enum_, ///< ENUM
set, ///< SET set, ///< SET
geometry, ///< GEOMETRY geometry, ///< GEOMETRY
unknown, ///< None of the known types; maybe a new MySQL type we have no knowledge of. unknown, ///< None of the known types; maybe a new MySQL type we have no knowledge of.
_not_computed, _not_computed,
}; };
} // mysql } // mysql

View File

@ -16,147 +16,147 @@
template <typename Stream> template <typename Stream>
void boost::mysql::connection<Stream>::handshake( void boost::mysql::connection<Stream>::handshake(
const connection_params& params, const connection_params& params,
error_code& code, error_code& code,
error_info& info error_info& info
) )
{ {
code.clear(); code.clear();
info.clear(); info.clear();
detail::hanshake(channel_, params, code, info); detail::hanshake(channel_, params, code, info);
// TODO: should we close() the stream in case of error? // TODO: should we close() the stream in case of error?
} }
template <typename Stream> template <typename Stream>
void boost::mysql::connection<Stream>::handshake( void boost::mysql::connection<Stream>::handshake(
const connection_params& params const connection_params& params
) )
{ {
error_code code; error_code code;
error_info info; error_info info;
handshake(params, code, info); handshake(params, code, info);
detail::check_error_code(code, info); detail::check_error_code(code, info);
} }
template <typename Stream> template <typename Stream>
template <typename CompletionToken> template <typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
typename boost::mysql::connection<Stream>::handshake_signature typename boost::mysql::connection<Stream>::handshake_signature
) )
boost::mysql::connection<Stream>::async_handshake( boost::mysql::connection<Stream>::async_handshake(
const connection_params& params, const connection_params& params,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
) )
{ {
detail::conditional_clear(info); detail::conditional_clear(info);
detail::check_completion_token<CompletionToken, handshake_signature>(); detail::check_completion_token<CompletionToken, handshake_signature>();
return detail::async_handshake( return detail::async_handshake(
channel_, channel_,
params, params,
std::forward<CompletionToken>(token), std::forward<CompletionToken>(token),
info info
); );
} }
// Query // Query
template <typename Stream> template <typename Stream>
boost::mysql::resultset<Stream> boost::mysql::connection<Stream>::query( boost::mysql::resultset<Stream> boost::mysql::connection<Stream>::query(
std::string_view query_string, std::string_view query_string,
error_code& err, error_code& err,
error_info& info error_info& info
) )
{ {
err.clear(); err.clear();
info.clear(); info.clear();
resultset<Stream> res; resultset<Stream> res;
detail::execute_query(channel_, query_string, res, err, info); detail::execute_query(channel_, query_string, res, err, info);
return res; return res;
} }
template <typename Stream> template <typename Stream>
boost::mysql::resultset<Stream> boost::mysql::connection<Stream>::query( boost::mysql::resultset<Stream> boost::mysql::connection<Stream>::query(
std::string_view query_string std::string_view query_string
) )
{ {
resultset<Stream> res; resultset<Stream> res;
error_code err; error_code err;
error_info info; error_info info;
detail::execute_query(channel_, query_string, res, err, info); detail::execute_query(channel_, query_string, res, err, info);
detail::check_error_code(err, info); detail::check_error_code(err, info);
return res; return res;
} }
template <typename Stream> template <typename Stream>
template <typename CompletionToken> template <typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
typename boost::mysql::connection<Stream>::query_signature typename boost::mysql::connection<Stream>::query_signature
) )
boost::mysql::connection<Stream>::async_query( boost::mysql::connection<Stream>::async_query(
std::string_view query_string, std::string_view query_string,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
) )
{ {
detail::conditional_clear(info); detail::conditional_clear(info);
detail::check_completion_token<CompletionToken, query_signature>(); detail::check_completion_token<CompletionToken, query_signature>();
return detail::async_execute_query( return detail::async_execute_query(
channel_, channel_,
query_string, query_string,
std::forward<CompletionToken>(token), std::forward<CompletionToken>(token),
info info
); );
} }
template <typename Stream> template <typename Stream>
boost::mysql::prepared_statement<Stream> boost::mysql::connection<Stream>::prepare_statement( boost::mysql::prepared_statement<Stream> boost::mysql::connection<Stream>::prepare_statement(
std::string_view statement, std::string_view statement,
error_code& err, error_code& err,
error_info& info error_info& info
) )
{ {
mysql::prepared_statement<Stream> res; mysql::prepared_statement<Stream> res;
err.clear(); err.clear();
info.clear(); info.clear();
detail::prepare_statement(channel_, statement, err, info, res); detail::prepare_statement(channel_, statement, err, info, res);
return res; return res;
} }
template <typename Stream> template <typename Stream>
boost::mysql::prepared_statement<Stream> boost::mysql::connection<Stream>::prepare_statement( boost::mysql::prepared_statement<Stream> boost::mysql::connection<Stream>::prepare_statement(
std::string_view statement std::string_view statement
) )
{ {
mysql::prepared_statement<Stream> res; mysql::prepared_statement<Stream> res;
error_code err; error_code err;
error_info info; error_info info;
detail::prepare_statement(channel_, statement, err, info, res); detail::prepare_statement(channel_, statement, err, info, res);
detail::check_error_code(err, info); detail::check_error_code(err, info);
return res; return res;
} }
template <typename Stream> template <typename Stream>
template <typename CompletionToken> template <typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
typename boost::mysql::connection<Stream>::prepare_statement_signature typename boost::mysql::connection<Stream>::prepare_statement_signature
) )
boost::mysql::connection<Stream>::async_prepare_statement( boost::mysql::connection<Stream>::async_prepare_statement(
std::string_view statement, std::string_view statement,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
) )
{ {
detail::conditional_clear(info); detail::conditional_clear(info);
detail::check_completion_token<CompletionToken, prepare_statement_signature>(); detail::check_completion_token<CompletionToken, prepare_statement_signature>();
return detail::async_prepare_statement( return detail::async_prepare_statement(
channel_, channel_,
statement, statement,
std::forward<CompletionToken>(token), std::forward<CompletionToken>(token),
info info
); );
} }

View File

@ -17,7 +17,7 @@ namespace system {
template <> template <>
struct is_error_code_enum<mysql::errc> struct is_error_code_enum<mysql::errc>
{ {
static constexpr bool value = true; static constexpr bool value = true;
}; };
} // system } // system
@ -27,61 +27,61 @@ namespace detail {
inline const char* error_to_string(errc error) noexcept inline const char* error_to_string(errc error) noexcept
{ {
switch (error) switch (error)
{ {
case errc::ok: return "no error"; case errc::ok: return "no error";
case errc::incomplete_message: return "The message read was incomplete (not enough bytes to fully decode it)"; case errc::incomplete_message: return "The message read was incomplete (not enough bytes to fully decode it)";
case errc::extra_bytes: return "Extra bytes at the end of the message"; case errc::extra_bytes: return "Extra bytes at the end of the message";
case errc::sequence_number_mismatch: return "Mismatched sequence numbers"; case errc::sequence_number_mismatch: return "Mismatched sequence numbers";
case errc::server_unsupported: return "The server does not implement the minimum features to be supported"; case errc::server_unsupported: return "The server does not implement the minimum features to be supported";
case errc::protocol_value_error: return "A field in a message had an unexpected value"; case errc::protocol_value_error: return "A field in a message had an unexpected value";
case errc::unknown_auth_plugin: return "The user employs an authentication plugin unknown to the client"; case errc::unknown_auth_plugin: return "The user employs an authentication plugin unknown to the client";
case errc::wrong_num_params: return "The provided parameter count does not match the prepared statement parameter count"; case errc::wrong_num_params: return "The provided parameter count does not match the prepared statement parameter count";
#include "boost/mysql/impl/server_error_descriptions.hpp" #include "boost/mysql/impl/server_error_descriptions.hpp"
default: return "<unknown error>"; default: return "<unknown error>";
} }
} }
class mysql_error_category_t : public boost::system::error_category class mysql_error_category_t : public boost::system::error_category
{ {
public: public:
const char* name() const noexcept final override { return "mysql"; } const char* name() const noexcept final override { return "mysql"; }
std::string message(int ev) const final override std::string message(int ev) const final override
{ {
return error_to_string(static_cast<errc>(ev)); return error_to_string(static_cast<errc>(ev));
} }
}; };
inline mysql_error_category_t mysql_error_category; inline mysql_error_category_t mysql_error_category;
inline boost::system::error_code make_error_code(errc error) inline boost::system::error_code make_error_code(errc error)
{ {
return boost::system::error_code(static_cast<int>(error), mysql_error_category); return boost::system::error_code(static_cast<int>(error), mysql_error_category);
} }
inline void check_error_code(const error_code& code, const error_info& info) inline void check_error_code(const error_code& code, const error_info& info)
{ {
if (code) if (code)
{ {
throw boost::system::system_error(code, info.message()); throw boost::system::system_error(code, info.message());
} }
} }
inline void conditional_clear(error_info* info) noexcept inline void conditional_clear(error_info* info) noexcept
{ {
if (info) if (info)
{ {
info->clear(); info->clear();
} }
} }
inline void conditional_assign(error_info* to, error_info&& from) inline void conditional_assign(error_info* to, error_info&& from)
{ {
if (to) if (to)
{ {
*to = std::move(from); *to = std::move(from);
} }
} }
} // detail } // detail

View File

@ -13,60 +13,60 @@ namespace mysql {
namespace detail { namespace detail {
inline field_type compute_field_type_string( inline field_type compute_field_type_string(
std::uint32_t flags std::uint32_t flags
) )
{ {
if (flags & column_flags::set) return field_type::set; if (flags & column_flags::set) return field_type::set;
else if (flags & column_flags::enum_) return field_type::enum_; else if (flags & column_flags::enum_) return field_type::enum_;
else if (flags & column_flags::binary) return field_type::binary; else if (flags & column_flags::binary) return field_type::binary;
else return field_type::char_; else return field_type::char_;
} }
inline field_type compute_field_type_var_string( inline field_type compute_field_type_var_string(
std::uint32_t flags std::uint32_t flags
) )
{ {
if (flags & column_flags::binary) return field_type::varbinary; if (flags & column_flags::binary) return field_type::varbinary;
else return field_type::varchar; else return field_type::varchar;
} }
inline field_type compute_field_type_blob( inline field_type compute_field_type_blob(
std::uint32_t flags std::uint32_t flags
) )
{ {
if (flags & column_flags::binary) return field_type::blob; if (flags & column_flags::binary) return field_type::blob;
else return field_type::text; else return field_type::text;
} }
inline field_type compute_field_type( inline field_type compute_field_type(
protocol_field_type protocol_type, protocol_field_type protocol_type,
std::uint32_t flags std::uint32_t flags
) )
{ {
switch (protocol_type) switch (protocol_type)
{ {
case protocol_field_type::decimal: case protocol_field_type::decimal:
case protocol_field_type::newdecimal: case protocol_field_type::newdecimal:
return field_type::decimal; return field_type::decimal;
case protocol_field_type::geometry: return field_type::geometry; case protocol_field_type::geometry: return field_type::geometry;
case protocol_field_type::tiny: return field_type::tinyint; case protocol_field_type::tiny: return field_type::tinyint;
case protocol_field_type::short_: return field_type::smallint; case protocol_field_type::short_: return field_type::smallint;
case protocol_field_type::int24: return field_type::mediumint; case protocol_field_type::int24: return field_type::mediumint;
case protocol_field_type::long_: return field_type::int_; case protocol_field_type::long_: return field_type::int_;
case protocol_field_type::longlong: return field_type::bigint; case protocol_field_type::longlong: return field_type::bigint;
case protocol_field_type::float_: return field_type::float_; case protocol_field_type::float_: return field_type::float_;
case protocol_field_type::double_: return field_type::double_; case protocol_field_type::double_: return field_type::double_;
case protocol_field_type::bit: return field_type::bit; case protocol_field_type::bit: return field_type::bit;
case protocol_field_type::date: return field_type::date; case protocol_field_type::date: return field_type::date;
case protocol_field_type::datetime: return field_type::datetime; case protocol_field_type::datetime: return field_type::datetime;
case protocol_field_type::timestamp: return field_type::timestamp; case protocol_field_type::timestamp: return field_type::timestamp;
case protocol_field_type::time: return field_type::time; case protocol_field_type::time: return field_type::time;
case protocol_field_type::year: return field_type::year; case protocol_field_type::year: return field_type::year;
case protocol_field_type::string: return compute_field_type_string(flags); case protocol_field_type::string: return compute_field_type_string(flags);
case protocol_field_type::var_string: return compute_field_type_var_string(flags); case protocol_field_type::var_string: return compute_field_type_var_string(flags);
case protocol_field_type::blob: return compute_field_type_blob(flags); case protocol_field_type::blob: return compute_field_type_blob(flags);
default: return field_type::unknown; default: return field_type::unknown;
} }
} }
} // detail } // detail
@ -75,12 +75,12 @@ inline field_type compute_field_type(
inline boost::mysql::field_type boost::mysql::field_metadata::type() const noexcept inline boost::mysql::field_type boost::mysql::field_metadata::type() const noexcept
{ {
if (field_type_ == field_type::_not_computed) if (field_type_ == field_type::_not_computed)
{ {
field_type_ = detail::compute_field_type(msg_.type, msg_.flags.value); field_type_ = detail::compute_field_type(msg_.type, msg_.flags.value);
assert(field_type_ != field_type::_not_computed); assert(field_type_ != field_type::_not_computed);
} }
return field_type_; return field_type_;
} }
#endif #endif

View File

@ -17,160 +17,160 @@
template <typename Stream> template <typename Stream>
template <typename ForwardIterator> template <typename ForwardIterator>
void boost::mysql::prepared_statement<Stream>::check_num_params( void boost::mysql::prepared_statement<Stream>::check_num_params(
ForwardIterator first, ForwardIterator first,
ForwardIterator last, ForwardIterator last,
error_code& err, error_code& err,
error_info& info error_info& info
) const ) const
{ {
auto param_count = std::distance(first, last); auto param_count = std::distance(first, last);
if (param_count != num_params()) if (param_count != num_params())
{ {
err = detail::make_error_code(errc::wrong_num_params); err = detail::make_error_code(errc::wrong_num_params);
info.set_message(detail::stringize( info.set_message(detail::stringize(
"prepared_statement::execute: expected ", num_params(), " params, but got ", param_count)); "prepared_statement::execute: expected ", num_params(), " params, but got ", param_count));
} }
} }
template <typename Stream> template <typename Stream>
template <typename ForwardIterator> template <typename ForwardIterator>
boost::mysql::resultset<Stream> boost::mysql::prepared_statement<Stream>::execute( boost::mysql::resultset<Stream> boost::mysql::prepared_statement<Stream>::execute(
ForwardIterator params_first, ForwardIterator params_first,
ForwardIterator params_last, ForwardIterator params_last,
error_code& err, error_code& err,
error_info& info error_info& info
) const ) const
{ {
assert(valid()); assert(valid());
mysql::resultset<Stream> res; mysql::resultset<Stream> res;
err.clear(); err.clear();
info.clear(); info.clear();
// Verify we got passed the right number of params // Verify we got passed the right number of params
check_num_params(params_first, params_last, err, info); check_num_params(params_first, params_last, err, info);
if (!err) if (!err)
{ {
detail::execute_statement( detail::execute_statement(
*channel_, *channel_,
stmt_msg_.statement_id.value, stmt_msg_.statement_id.value,
params_first, params_first,
params_last, params_last,
res, res,
err, err,
info info
); );
} }
return res; return res;
} }
template <typename Stream> template <typename Stream>
template <typename ForwardIterator> template <typename ForwardIterator>
boost::mysql::resultset<Stream> boost::mysql::prepared_statement<Stream>::execute( boost::mysql::resultset<Stream> boost::mysql::prepared_statement<Stream>::execute(
ForwardIterator params_first, ForwardIterator params_first,
ForwardIterator params_last ForwardIterator params_last
) const ) const
{ {
error_code err; error_code err;
error_info info; error_info info;
auto res = execute(params_first, params_last, err, info); auto res = execute(params_first, params_last, err, info);
detail::check_error_code(err, info); detail::check_error_code(err, info);
return res; return res;
} }
template <typename StreamType> template <typename StreamType>
template <typename ForwardIterator, typename CompletionToken> template <typename ForwardIterator, typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
typename boost::mysql::prepared_statement<StreamType>::execute_signature typename boost::mysql::prepared_statement<StreamType>::execute_signature
) )
boost::mysql::prepared_statement<StreamType>::async_execute( boost::mysql::prepared_statement<StreamType>::async_execute(
ForwardIterator params_first, ForwardIterator params_first,
ForwardIterator params_last, ForwardIterator params_last,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
) const ) const
{ {
detail::conditional_clear(info); detail::conditional_clear(info);
detail::check_completion_token<CompletionToken, execute_signature>(); detail::check_completion_token<CompletionToken, execute_signature>();
// Check we got passed the right number of params // Check we got passed the right number of params
error_code err; error_code err;
error_info nonnull_info; error_info nonnull_info;
check_num_params(params_first, params_last, err, nonnull_info); check_num_params(params_first, params_last, err, nonnull_info);
if (err) if (err)
{ {
detail::conditional_assign(info, std::move(nonnull_info)); detail::conditional_assign(info, std::move(nonnull_info));
boost::asio::async_completion<CompletionToken, execute_signature> completion (token); boost::asio::async_completion<CompletionToken, execute_signature> completion (token);
// TODO: is executor correctly preserved here? // TODO: is executor correctly preserved here?
boost::asio::post( boost::asio::post(
channel_->next_layer().get_executor(), channel_->next_layer().get_executor(),
boost::beast::bind_front_handler( boost::beast::bind_front_handler(
std::move(completion.completion_handler), std::move(completion.completion_handler),
err, err,
resultset<StreamType>() resultset<StreamType>()
) )
); );
return completion.result.get(); return completion.result.get();
} }
else else
{ {
// Actually execute the statement // Actually execute the statement
return detail::async_execute_statement( return detail::async_execute_statement(
*channel_, *channel_,
stmt_msg_.statement_id.value, stmt_msg_.statement_id.value,
params_first, params_first,
params_last, params_last,
std::forward<CompletionToken>(token), std::forward<CompletionToken>(token),
info info
); );
} }
} }
template <typename StreamType> template <typename StreamType>
void boost::mysql::prepared_statement<StreamType>::close( void boost::mysql::prepared_statement<StreamType>::close(
error_code& code, error_code& code,
error_info& info error_info& info
) )
{ {
assert(valid()); assert(valid());
code.clear(); code.clear();
info.clear(); info.clear();
detail::close_statement(*channel_, id(), code, info); detail::close_statement(*channel_, id(), code, info);
} }
template <typename StreamType> template <typename StreamType>
void boost::mysql::prepared_statement<StreamType>::close() void boost::mysql::prepared_statement<StreamType>::close()
{ {
assert(valid()); assert(valid());
error_code code; error_code code;
error_info info; error_info info;
detail::close_statement(*channel_, id(), code, info); detail::close_statement(*channel_, id(), code, info);
detail::check_error_code(code, info); detail::check_error_code(code, info);
} }
template <typename StreamType> template <typename StreamType>
template <typename CompletionToken> template <typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
typename boost::mysql::prepared_statement<StreamType>::close_signature typename boost::mysql::prepared_statement<StreamType>::close_signature
) )
boost::mysql::prepared_statement<StreamType>::async_close( boost::mysql::prepared_statement<StreamType>::async_close(
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
) )
{ {
assert(valid()); assert(valid());
detail::conditional_clear(info); detail::conditional_clear(info);
detail::check_completion_token<CompletionToken, close_signature>(); detail::check_completion_token<CompletionToken, close_signature>();
return detail::async_close_statement( return detail::async_close_statement(
*channel_, *channel_,
id(), id(),
std::forward<CompletionToken>(token), std::forward<CompletionToken>(token),
info info
); );
} }
#endif /* INCLUDE_BOOST_MYSQL_IMPL_PREPARED_STATEMENT_HPP_ */ #endif /* INCLUDE_BOOST_MYSQL_IMPL_PREPARED_STATEMENT_HPP_ */

View File

@ -17,322 +17,322 @@
template <typename StreamType> template <typename StreamType>
const boost::mysql::row* boost::mysql::resultset<StreamType>::fetch_one( const boost::mysql::row* boost::mysql::resultset<StreamType>::fetch_one(
error_code& err, error_code& err,
error_info& info error_info& info
) )
{ {
assert(valid()); assert(valid());
err.clear(); err.clear();
info.clear(); info.clear();
if (complete()) if (complete())
{ {
return nullptr; return nullptr;
} }
auto result = detail::read_row( auto result = detail::read_row(
deserializer_, deserializer_,
*channel_, *channel_,
meta_.fields(), meta_.fields(),
buffer_, buffer_,
current_row_.values(), current_row_.values(),
ok_packet_, ok_packet_,
err, err,
info info
); );
eof_received_ = result == detail::read_row_result::eof; eof_received_ = result == detail::read_row_result::eof;
return result == detail::read_row_result::row ? &current_row_ : nullptr; return result == detail::read_row_result::row ? &current_row_ : nullptr;
} }
template <typename StreamType> template <typename StreamType>
const boost::mysql::row* boost::mysql::resultset<StreamType>::fetch_one() const boost::mysql::row* boost::mysql::resultset<StreamType>::fetch_one()
{ {
error_code code; error_code code;
error_info info; error_info info;
const row* res = fetch_one(code, info); const row* res = fetch_one(code, info);
detail::check_error_code(code, info); detail::check_error_code(code, info);
return res; return res;
} }
template <typename StreamType> template <typename StreamType>
std::vector<boost::mysql::owning_row> boost::mysql::resultset<StreamType>::fetch_many( std::vector<boost::mysql::owning_row> boost::mysql::resultset<StreamType>::fetch_many(
std::size_t count, std::size_t count,
error_code& err, error_code& err,
error_info& info error_info& info
) )
{ {
assert(valid()); assert(valid());
err.clear(); err.clear();
info.clear(); info.clear();
std::vector<mysql::owning_row> res; std::vector<mysql::owning_row> res;
if (!complete()) // support calling fetch on already exhausted resultset if (!complete()) // support calling fetch on already exhausted resultset
{ {
for (std::size_t i = 0; i < count; ++i) for (std::size_t i = 0; i < count; ++i)
{ {
detail::bytestring buff; detail::bytestring buff;
std::vector<value> values; std::vector<value> values;
auto result = detail::read_row( auto result = detail::read_row(
deserializer_, deserializer_,
*channel_, *channel_,
meta_.fields(), meta_.fields(),
buff, buff,
values, values,
ok_packet_, ok_packet_,
err, err,
info info
); );
eof_received_ = result == detail::read_row_result::eof; eof_received_ = result == detail::read_row_result::eof;
if (result == detail::read_row_result::row) if (result == detail::read_row_result::row)
{ {
res.emplace_back(std::move(values), std::move(buff)); res.emplace_back(std::move(values), std::move(buff));
} }
else else
{ {
break; break;
} }
} }
} }
return res; return res;
} }
template <typename StreamType> template <typename StreamType>
std::vector<boost::mysql::owning_row> boost::mysql::resultset<StreamType>::fetch_many( std::vector<boost::mysql::owning_row> boost::mysql::resultset<StreamType>::fetch_many(
std::size_t count std::size_t count
) )
{ {
error_code code; error_code code;
error_info info; error_info info;
auto res = fetch_many(count, code, info); auto res = fetch_many(count, code, info);
detail::check_error_code(code, info); detail::check_error_code(code, info);
return res; return res;
} }
template <typename StreamType> template <typename StreamType>
std::vector<boost::mysql::owning_row> boost::mysql::resultset<StreamType>::fetch_all( std::vector<boost::mysql::owning_row> boost::mysql::resultset<StreamType>::fetch_all(
error_code& err, error_code& err,
error_info& info error_info& info
) )
{ {
return fetch_many(std::numeric_limits<std::size_t>::max(), err, info); return fetch_many(std::numeric_limits<std::size_t>::max(), err, info);
} }
template <typename StreamType> template <typename StreamType>
std::vector<boost::mysql::owning_row> boost::mysql::resultset<StreamType>::fetch_all() std::vector<boost::mysql::owning_row> boost::mysql::resultset<StreamType>::fetch_all()
{ {
return fetch_many(std::numeric_limits<std::size_t>::max()); return fetch_many(std::numeric_limits<std::size_t>::max());
} }
template <typename StreamType> template <typename StreamType>
template <typename CompletionToken> template <typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
typename boost::mysql::resultset<StreamType>::fetch_one_signature typename boost::mysql::resultset<StreamType>::fetch_one_signature
) )
boost::mysql::resultset<StreamType>::async_fetch_one( boost::mysql::resultset<StreamType>::async_fetch_one(
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
) )
{ {
detail::conditional_clear(info); detail::conditional_clear(info);
detail::check_completion_token<CompletionToken, fetch_one_signature>(); detail::check_completion_token<CompletionToken, fetch_one_signature>();
using HandlerSignature = fetch_one_signature; using HandlerSignature = fetch_one_signature;
using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature);
using BaseType = boost::beast::async_base<HandlerType, typename StreamType::executor_type>; using BaseType = boost::beast::async_base<HandlerType, typename StreamType::executor_type>;
struct Op: BaseType, boost::asio::coroutine struct Op: BaseType, boost::asio::coroutine
{ {
resultset<StreamType>& resultset_; resultset<StreamType>& resultset_;
error_info* output_info_; error_info* output_info_;
Op( Op(
HandlerType&& handler, HandlerType&& handler,
resultset<StreamType>& obj, resultset<StreamType>& obj,
error_info* output_info error_info* output_info
): ):
BaseType(std::move(handler), obj.channel_->next_layer().get_executor()), BaseType(std::move(handler), obj.channel_->next_layer().get_executor()),
resultset_(obj), resultset_(obj),
output_info_(output_info) output_info_(output_info)
{ {
}; };
void operator()( void operator()(
error_code err, error_code err,
error_info info, error_info info,
detail::read_row_result result, detail::read_row_result result,
bool cont=true bool cont=true
) )
{ {
reenter(*this) reenter(*this)
{ {
if (resultset_.complete()) if (resultset_.complete())
{ {
this->complete(cont, error_code(), nullptr); this->complete(cont, error_code(), nullptr);
} }
else else
{ {
yield detail::async_read_row( yield detail::async_read_row(
resultset_.deserializer_, resultset_.deserializer_,
*resultset_.channel_, *resultset_.channel_,
resultset_.meta_.fields(), resultset_.meta_.fields(),
resultset_.buffer_, resultset_.buffer_,
resultset_.current_row_.values(), resultset_.current_row_.values(),
resultset_.ok_packet_, resultset_.ok_packet_,
std::move(*this) std::move(*this)
); );
resultset_.eof_received_ = result == detail::read_row_result::eof; resultset_.eof_received_ = result == detail::read_row_result::eof;
detail::conditional_assign(output_info_, std::move(info)); detail::conditional_assign(output_info_, std::move(info));
this->complete( this->complete(
cont, cont,
err, err,
result == detail::read_row_result::row ? &resultset_.current_row_ : nullptr result == detail::read_row_result::row ? &resultset_.current_row_ : nullptr
); );
} }
} }
} }
}; };
assert(valid()); assert(valid());
boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token); boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token);
Op( Op(
std::move(initiator.completion_handler), std::move(initiator.completion_handler),
*this, *this,
info info
)(error_code(), error_info(), detail::read_row_result::error, false); )(error_code(), error_info(), detail::read_row_result::error, false);
return initiator.result.get(); return initiator.result.get();
} }
template <typename StreamType> template <typename StreamType>
template <typename CompletionToken> template <typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
typename boost::mysql::resultset<StreamType>::fetch_many_signature typename boost::mysql::resultset<StreamType>::fetch_many_signature
) )
boost::mysql::resultset<StreamType>::async_fetch_many( boost::mysql::resultset<StreamType>::async_fetch_many(
std::size_t count, std::size_t count,
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
) )
{ {
detail::conditional_clear(info); detail::conditional_clear(info);
detail::check_completion_token<CompletionToken, fetch_many_signature>(); detail::check_completion_token<CompletionToken, fetch_many_signature>();
using HandlerSignature = fetch_many_signature; using HandlerSignature = fetch_many_signature;
using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature);
using BaseType = boost::beast::async_base<HandlerType, typename StreamType::executor_type>; using BaseType = boost::beast::async_base<HandlerType, typename StreamType::executor_type>;
struct OpImpl struct OpImpl
{ {
resultset<StreamType>& parent_resultset; resultset<StreamType>& parent_resultset;
std::vector<owning_row> rows; std::vector<owning_row> rows;
detail::bytestring buffer; detail::bytestring buffer;
std::vector<value> values; std::vector<value> values;
std::size_t remaining; std::size_t remaining;
error_info* output_info_; error_info* output_info_;
OpImpl(resultset<StreamType>& obj, std::size_t count, error_info* output_info): OpImpl(resultset<StreamType>& obj, std::size_t count, error_info* output_info):
parent_resultset(obj), parent_resultset(obj),
remaining(count), remaining(count),
output_info_(output_info) output_info_(output_info)
{ {
}; };
void row_received() void row_received()
{ {
rows.emplace_back(std::move(values), std::move(buffer)); rows.emplace_back(std::move(values), std::move(buffer));
values = std::vector<value>(); values = std::vector<value>();
buffer = detail::bytestring(); buffer = detail::bytestring();
--remaining; --remaining;
} }
}; };
struct Op: BaseType, boost::asio::coroutine struct Op: BaseType, boost::asio::coroutine
{ {
std::shared_ptr<OpImpl> impl_; std::shared_ptr<OpImpl> impl_;
Op( Op(
HandlerType&& handler, HandlerType&& handler,
std::shared_ptr<OpImpl>&& impl std::shared_ptr<OpImpl>&& impl
): ):
BaseType(std::move(handler), impl->parent_resultset.channel_->next_layer().get_executor()), BaseType(std::move(handler), impl->parent_resultset.channel_->next_layer().get_executor()),
impl_(std::move(impl)) impl_(std::move(impl))
{ {
}; };
void operator()( void operator()(
error_code err, error_code err,
error_info info, error_info info,
detail::read_row_result result, detail::read_row_result result,
bool cont=true bool cont=true
) )
{ {
reenter(*this) reenter(*this)
{ {
while (!impl_->parent_resultset.complete() && impl_->remaining > 0) while (!impl_->parent_resultset.complete() && impl_->remaining > 0)
{ {
yield detail::async_read_row( yield detail::async_read_row(
impl_->parent_resultset.deserializer_, impl_->parent_resultset.deserializer_,
*impl_->parent_resultset.channel_, *impl_->parent_resultset.channel_,
impl_->parent_resultset.meta_.fields(), impl_->parent_resultset.meta_.fields(),
impl_->buffer, impl_->buffer,
impl_->values, impl_->values,
impl_->parent_resultset.ok_packet_, impl_->parent_resultset.ok_packet_,
std::move(*this) std::move(*this)
); );
if (result == detail::read_row_result::error) if (result == detail::read_row_result::error)
{ {
detail::conditional_assign(impl_->output_info_, std::move(info)); detail::conditional_assign(impl_->output_info_, std::move(info));
this->complete(cont, err, std::move(impl_->rows)); this->complete(cont, err, std::move(impl_->rows));
yield break; yield break;
} }
else if (result == detail::read_row_result::eof) else if (result == detail::read_row_result::eof)
{ {
impl_->parent_resultset.eof_received_ = true; impl_->parent_resultset.eof_received_ = true;
} }
else else
{ {
impl_->row_received(); impl_->row_received();
} }
} }
this->complete(cont, err, std::move(impl_->rows)); this->complete(cont, err, std::move(impl_->rows));
} }
} }
}; };
assert(valid()); assert(valid());
boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token); boost::asio::async_completion<CompletionToken, HandlerSignature> initiator(token);
Op( Op(
std::move(initiator.completion_handler), std::move(initiator.completion_handler),
std::make_shared<OpImpl>(*this, count, info) std::make_shared<OpImpl>(*this, count, info)
)(error_code(), error_info(), detail::read_row_result::error, false); )(error_code(), error_info(), detail::read_row_result::error, false);
return initiator.result.get(); return initiator.result.get();
} }
template <typename StreamType> template <typename StreamType>
template <typename CompletionToken> template <typename CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE( BOOST_ASIO_INITFN_RESULT_TYPE(
CompletionToken, CompletionToken,
typename boost::mysql::resultset<StreamType>::fetch_all_signature typename boost::mysql::resultset<StreamType>::fetch_all_signature
) )
boost::mysql::resultset<StreamType>::async_fetch_all( boost::mysql::resultset<StreamType>::async_fetch_all(
CompletionToken&& token, CompletionToken&& token,
error_info* info error_info* info
) )
{ {
return async_fetch_many( return async_fetch_many(
std::numeric_limits<std::size_t>::max(), std::numeric_limits<std::size_t>::max(),
std::forward<CompletionToken>(token), std::forward<CompletionToken>(token),
info info
); );
} }

View File

@ -32,35 +32,35 @@ static_assert(time::max() >= max_time);
struct print_visitor struct print_visitor
{ {
std::ostream& os; std::ostream& os;
print_visitor(std::ostream& os): os(os) {}; print_visitor(std::ostream& os): os(os) {};
template <typename T> template <typename T>
void operator()(const T& value) const { os << value; } void operator()(const T& value) const { os << value; }
void operator()(const date& value) const { ::date::operator<<(os, value); } void operator()(const date& value) const { ::date::operator<<(os, value); }
void operator()(const time& value) const void operator()(const time& value) const
{ {
char buffer [100] {}; char buffer [100] {};
const char* sign = value < std::chrono::microseconds(0) ? "-" : ""; const char* sign = value < std::chrono::microseconds(0) ? "-" : "";
auto hours = std::abs(std::chrono::duration_cast<std::chrono::hours>(value).count()); auto hours = std::abs(std::chrono::duration_cast<std::chrono::hours>(value).count());
auto mins = std::abs(std::chrono::duration_cast<std::chrono::minutes>(value % std::chrono::hours(1)).count()); auto mins = std::abs(std::chrono::duration_cast<std::chrono::minutes>(value % std::chrono::hours(1)).count());
auto secs = std::abs(std::chrono::duration_cast<std::chrono::seconds>(value % std::chrono::minutes(1)).count()); auto secs = std::abs(std::chrono::duration_cast<std::chrono::seconds>(value % std::chrono::minutes(1)).count());
auto micros = std::abs((value % std::chrono::seconds(1)).count()); auto micros = std::abs((value % std::chrono::seconds(1)).count());
snprintf(buffer, sizeof(buffer), "%s%02d:%02u:%02u:%06u", snprintf(buffer, sizeof(buffer), "%s%02d:%02u:%02u:%06u",
sign, sign,
static_cast<int>(hours), static_cast<int>(hours),
static_cast<unsigned>(mins), static_cast<unsigned>(mins),
static_cast<unsigned>(secs), static_cast<unsigned>(secs),
static_cast<unsigned>(micros) static_cast<unsigned>(micros)
); );
os << buffer; os << buffer;
} }
void operator()(const datetime& value) const { ::date::operator<<(os, value); } void operator()(const datetime& value) const { ::date::operator<<(os, value); }
void operator()(std::nullptr_t) const { os << "<NULL>"; } void operator()(std::nullptr_t) const { os << "<NULL>"; }
}; };
} // detail } // detail
@ -68,41 +68,41 @@ struct print_visitor
} // boost } // boost
inline bool boost::mysql::operator==( inline bool boost::mysql::operator==(
const value& lhs, const value& lhs,
const value& rhs const value& rhs
) )
{ {
if (lhs.index() != rhs.index()) return false; if (lhs.index() != rhs.index()) return false;
return std::visit([&lhs](const auto& rhs_value) { return std::visit([&lhs](const auto& rhs_value) {
using T = std::decay_t<decltype(rhs_value)>; using T = std::decay_t<decltype(rhs_value)>;
return std::get<T>(lhs) == rhs_value; return std::get<T>(lhs) == rhs_value;
}, rhs); }, rhs);
} }
inline bool boost::mysql::operator==( inline bool boost::mysql::operator==(
const std::vector<value>& lhs, const std::vector<value>& lhs,
const std::vector<value>& rhs const std::vector<value>& rhs
) )
{ {
return detail::container_equals(lhs, rhs); return detail::container_equals(lhs, rhs);
} }
inline std::ostream& boost::mysql::operator<<( inline std::ostream& boost::mysql::operator<<(
std::ostream& os, std::ostream& os,
const value& value const value& value
) )
{ {
std::visit(detail::print_visitor(os), value); std::visit(detail::print_visitor(os), value);
return os; return os;
} }
template <typename... Types> template <typename... Types>
std::array<boost::mysql::value, sizeof...(Types)> std::array<boost::mysql::value, sizeof...(Types)>
boost::mysql::make_values( boost::mysql::make_values(
Types&&... args Types&&... args
) )
{ {
return std::array<value, sizeof...(Types)>{value(std::forward<Types>(args))...}; return std::array<value, sizeof...(Types)>{value(std::forward<Types>(args))...};
} }

View File

@ -23,104 +23,104 @@ namespace mysql {
*/ */
class field_metadata class field_metadata
{ {
detail::column_definition_packet msg_; detail::column_definition_packet msg_;
mutable field_type field_type_ { field_type::_not_computed }; mutable field_type field_type_ { field_type::_not_computed };
bool flag_set(std::uint16_t flag) const noexcept { return msg_.flags.value & flag; } bool flag_set(std::uint16_t flag) const noexcept { return msg_.flags.value & flag; }
public: public:
/// Default constructor. /// Default constructor.
field_metadata() = default; field_metadata() = default;
// Private, do not use. // Private, do not use.
field_metadata(const detail::column_definition_packet& msg) noexcept: msg_(msg) {}; field_metadata(const detail::column_definition_packet& msg) noexcept: msg_(msg) {};
/// Returns the name of the database (schema) the field belongs to. /// Returns the name of the database (schema) the field belongs to.
std::string_view database() const noexcept { return msg_.schema.value; } std::string_view database() const noexcept { return msg_.schema.value; }
/** /**
* \brief Returns the name of the virtual table the field belongs to. * \brief Returns the name of the virtual table the field belongs to.
* \details If the table was aliased, this will be the name of the alias * \details If the table was aliased, this will be the name of the alias
* (e.g. in "SELECT * FROM employees emp", table() will be "emp"). * (e.g. in "SELECT * FROM employees emp", table() will be "emp").
*/ */
std::string_view table() const noexcept { return msg_.table.value; } std::string_view table() const noexcept { return msg_.table.value; }
/** /**
* \brief Returns the name of the physical table the field belongs to. * \brief Returns the name of the physical table the field belongs to.
* \details E.g. in "SELECT * FROM employees emp", * \details E.g. in "SELECT * FROM employees emp",
* original_table() will be "employees". * original_table() will be "employees".
*/ */
std::string_view original_table() const noexcept { return msg_.org_table.value; } std::string_view original_table() const noexcept { return msg_.org_table.value; }
/** /**
* \brief Returns the actual name of the field. * \brief Returns the actual name of the field.
* \details If the field was aliased, this will be the name of the alias * \details If the field was aliased, this will be the name of the alias
* (e.g. in "SELECT id AS employee_id FROM employees", * (e.g. in "SELECT id AS employee_id FROM employees",
* field_name() will be "employee_id"). * field_name() will be "employee_id").
*/ */
std::string_view field_name() const noexcept { return msg_.name.value; } std::string_view field_name() const noexcept { return msg_.name.value; }
/** /**
* \brief Returns the original (physical) name of the field. * \brief Returns the original (physical) name of the field.
* \details E.g. in "SELECT id AS employee_id FROM employees", * \details E.g. in "SELECT id AS employee_id FROM employees",
* original_field_name() will be "id". * original_field_name() will be "id".
*/ */
std::string_view original_field_name() const noexcept { return msg_.org_name.value; } std::string_view original_field_name() const noexcept { return msg_.org_name.value; }
/// Returns the maximum length of the field. /// Returns the maximum length of the field.
unsigned column_length() const noexcept { return msg_.column_length.value; } unsigned column_length() const noexcept { return msg_.column_length.value; }
detail::protocol_field_type protocol_type() const noexcept { return msg_.type; } detail::protocol_field_type protocol_type() const noexcept { return msg_.type; }
/// Returns the type of the field (see field_type for more info). /// Returns the type of the field (see field_type for more info).
field_type type() const noexcept; field_type type() const noexcept;
/// Returns the number of decimals of the field. /// Returns the number of decimals of the field.
unsigned decimals() const noexcept { return msg_.decimals.value; } unsigned decimals() const noexcept { return msg_.decimals.value; }
/// Returns true if the field is not allowed to be NULL, false if it is nullable. /// Returns true if the field is not allowed to be NULL, false if it is nullable.
bool is_not_null() const noexcept { return flag_set(detail::column_flags::not_null); } bool is_not_null() const noexcept { return flag_set(detail::column_flags::not_null); }
/// Returns true if the field is part of a PRIMARY KEY. /// Returns true if the field is part of a PRIMARY KEY.
bool is_primary_key() const noexcept { return flag_set(detail::column_flags::pri_key); } bool is_primary_key() const noexcept { return flag_set(detail::column_flags::pri_key); }
/// Returns true if the field is part of a UNIQUE KEY (but not a PRIMARY KEY). /// Returns true if the field is part of a UNIQUE KEY (but not a PRIMARY KEY).
bool is_unique_key() const noexcept { return flag_set(detail::column_flags::unique_key); } bool is_unique_key() const noexcept { return flag_set(detail::column_flags::unique_key); }
/// Returns true if the field is part of a KEY (but not a UNIQUE KEY or PRIMARY KEY). /// Returns true if the field is part of a KEY (but not a UNIQUE KEY or PRIMARY KEY).
bool is_multiple_key() const noexcept { return flag_set(detail::column_flags::multiple_key); } bool is_multiple_key() const noexcept { return flag_set(detail::column_flags::multiple_key); }
/// Returns true if the field has no sign. /// Returns true if the field has no sign.
bool is_unsigned() const noexcept { return flag_set(detail::column_flags::unsigned_); } bool is_unsigned() const noexcept { return flag_set(detail::column_flags::unsigned_); }
/// Returns true if the field is defined as zerofill (padded to its maximum length by zeros). /// Returns true if the field is defined as zerofill (padded to its maximum length by zeros).
bool is_zerofill() const noexcept { return flag_set(detail::column_flags::zerofill); } bool is_zerofill() const noexcept { return flag_set(detail::column_flags::zerofill); }
/// Returns true if the field is defined as AUTO_INCREMENT /// Returns true if the field is defined as AUTO_INCREMENT
bool is_auto_increment() const noexcept { return flag_set(detail::column_flags::auto_increment); } bool is_auto_increment() const noexcept { return flag_set(detail::column_flags::auto_increment); }
/// Returns true if the field does not have a default value. /// Returns true if the field does not have a default value.
bool has_no_default_value() const noexcept { return flag_set(detail::column_flags::no_default_value); } bool has_no_default_value() const noexcept { return flag_set(detail::column_flags::no_default_value); }
/// Returns true if the field is defined as ON UPDATE CURRENT_TIMESTAMP. /// Returns true if the field is defined as ON UPDATE CURRENT_TIMESTAMP.
bool is_set_to_now_on_update() const noexcept { return flag_set(detail::column_flags::on_update_now); } bool is_set_to_now_on_update() const noexcept { return flag_set(detail::column_flags::on_update_now); }
}; };
namespace detail { namespace detail {
class resultset_metadata class resultset_metadata
{ {
std::vector<bytestring> buffers_; std::vector<bytestring> buffers_;
std::vector<field_metadata> fields_; std::vector<field_metadata> fields_;
public: public:
resultset_metadata() = default; resultset_metadata() = default;
resultset_metadata(std::vector<bytestring>&& buffers, std::vector<field_metadata>&& fields): resultset_metadata(std::vector<bytestring>&& buffers, std::vector<field_metadata>&& fields):
buffers_(std::move(buffers)), fields_(std::move(fields)) {}; buffers_(std::move(buffers)), fields_(std::move(fields)) {};
resultset_metadata(const resultset_metadata&) = delete; resultset_metadata(const resultset_metadata&) = delete;
resultset_metadata(resultset_metadata&&) = default; resultset_metadata(resultset_metadata&&) = default;
resultset_metadata& operator=(const resultset_metadata&) = delete; resultset_metadata& operator=(const resultset_metadata&) = delete;
resultset_metadata& operator=(resultset_metadata&&) = default; resultset_metadata& operator=(resultset_metadata&&) = default;
~resultset_metadata() = default; ~resultset_metadata() = default;
const auto& fields() const noexcept { return fields_; } const auto& fields() const noexcept { return fields_; }
}; };
} // detail } // detail

View File

@ -64,122 +64,122 @@ constexpr std::array<value, 0> no_statement_params {};
template <typename Stream> template <typename Stream>
class prepared_statement class prepared_statement
{ {
detail::channel<Stream>* channel_ {}; detail::channel<Stream>* channel_ {};
detail::com_stmt_prepare_ok_packet stmt_msg_; detail::com_stmt_prepare_ok_packet stmt_msg_;
template <typename ForwardIterator> template <typename ForwardIterator>
void check_num_params(ForwardIterator first, ForwardIterator last, error_code& err, error_info& info) const; void check_num_params(ForwardIterator first, ForwardIterator last, error_code& err, error_info& info) const;
public: public:
/// Default constructor. /// Default constructor.
prepared_statement() = default; prepared_statement() = default;
// Private. Do not use. // Private. Do not use.
prepared_statement(detail::channel<Stream>& chan, const detail::com_stmt_prepare_ok_packet& msg) noexcept: prepared_statement(detail::channel<Stream>& chan, const detail::com_stmt_prepare_ok_packet& msg) noexcept:
channel_(&chan), stmt_msg_(msg) {} channel_(&chan), stmt_msg_(msg) {}
/// Retrieves the stream object associated with the underlying connection. /// Retrieves the stream object associated with the underlying connection.
Stream& next_layer() noexcept { assert(channel_); return channel_->next_layer(); } Stream& next_layer() noexcept { assert(channel_); return channel_->next_layer(); }
/// Retrieves the stream object associated with the underlying connection. /// Retrieves the stream object associated with the underlying connection.
const Stream& next_layer() const noexcept { assert(channel_); return channel_->next_layer(); } const Stream& next_layer() const noexcept { assert(channel_); return channel_->next_layer(); }
/// Returns true if the statement is not a default-constructed object. /// Returns true if the statement is not a default-constructed object.
bool valid() const noexcept { return channel_ != nullptr; } bool valid() const noexcept { return channel_ != nullptr; }
/// Returns a server-side identifier for the statement (unique in a per-connection basis). /// Returns a server-side identifier for the statement (unique in a per-connection basis).
unsigned id() const noexcept { assert(valid()); return stmt_msg_.statement_id.value; } unsigned id() const noexcept { assert(valid()); return stmt_msg_.statement_id.value; }
/// Returns the number of parameters that should be passed to execute(). /// Returns the number of parameters that should be passed to execute().
unsigned num_params() const noexcept { assert(valid()); return stmt_msg_.num_params.value; } unsigned num_params() const noexcept { assert(valid()); return stmt_msg_.num_params.value; }
/** /**
* \brief Executes a statement (collection, sync with error code version). * \brief Executes a statement (collection, sync with error code version).
* \details Collection should be a sequence for which std::begin() and * \details Collection should be a sequence for which std::begin() and
* std::end() return forward iterators to a valid boost::mysql::value range. * std::end() return forward iterators to a valid boost::mysql::value range.
* Use no_statement_params to execute a statement with no params. * Use no_statement_params to execute a statement with no params.
*/ */
template <typename Collection> template <typename Collection>
resultset<Stream> execute(const Collection& params, error_code& err, error_info& info) const resultset<Stream> execute(const Collection& params, error_code& err, error_info& info) const
{ {
return execute(std::begin(params), std::end(params), err, info); return execute(std::begin(params), std::end(params), err, info);
} }
/// Executes a statement (collection, sync with exceptions code version). /// Executes a statement (collection, sync with exceptions code version).
template <typename Collection> template <typename Collection>
resultset<Stream> execute(const Collection& params) const resultset<Stream> execute(const Collection& params) const
{ {
return execute(std::begin(params), std::end(params)); return execute(std::begin(params), std::end(params));
} }
/// The handler signature for execute. /// The handler signature for execute.
using execute_signature = void(error_code, resultset<Stream>); using execute_signature = void(error_code, resultset<Stream>);
/** /**
* \brief Executes a statement (collection, sync with exceptions code version). * \brief Executes a statement (collection, sync with exceptions code version).
* \details It is **not** necessary to keep the collection of parameters or the * \details It is **not** necessary to keep the collection of parameters or the
* values they may point to alive. * values they may point to alive.
*/ */
template <typename Collection, typename CompletionToken> template <typename Collection, typename CompletionToken>
BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, execute_signature) BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, execute_signature)
async_execute(const Collection& params, CompletionToken&& token, error_info* info=nullptr) const async_execute(const Collection& params, CompletionToken&& token, error_info* info=nullptr) const
{ {
return async_execute( return async_execute(
std::begin(params), std::begin(params),
std::end(params), std::end(params),
std::forward<CompletionToken>(token), std::forward<CompletionToken>(token),
info info
); );
} }
/** /**
* \brief Executes a statement (iterator, sync with error code version). * \brief Executes a statement (iterator, sync with error code version).
* \details params_first and params_last should point to a valid range, identifying * \details params_first and params_last should point to a valid range, identifying
* the parameters to be used. They should be forward iterators, at least. The value_type * the parameters to be used. They should be forward iterators, at least. The value_type
* of these iterators should be convertible to boost::mysql::value. * of these iterators should be convertible to boost::mysql::value.
*/ */
template <typename ForwardIterator> template <typename ForwardIterator>
resultset<Stream> execute(ForwardIterator params_first, ForwardIterator params_last, resultset<Stream> execute(ForwardIterator params_first, ForwardIterator params_last,
error_code&, error_info&) const; error_code&, error_info&) const;
/// Executes a statement (iterator, sync with exceptions version). /// Executes a statement (iterator, sync with exceptions version).
template <typename ForwardIterator> template <typename ForwardIterator>
resultset<Stream> execute(ForwardIterator params_first, ForwardIterator params_last) const; resultset<Stream> execute(ForwardIterator params_first, ForwardIterator params_last) const;
/** /**
* \brief Executes a statement (iterator, async version). * \brief Executes a statement (iterator, async version).
* \details The sequence [params_first, params_last) and the values that may be pointed * \details The sequence [params_first, params_last) and the values that may be pointed
* by the elements of the sequence need **not** be kept alive. * by the elements of the sequence need **not** be kept alive.
*/ */
template <typename ForwardIterator, typename CompletionToken> template <typename ForwardIterator, typename CompletionToken>
BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, execute_signature) BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, execute_signature)
async_execute(ForwardIterator params_first, ForwardIterator params_last, async_execute(ForwardIterator params_first, ForwardIterator params_last,
CompletionToken&& token, error_info* info=nullptr) const; CompletionToken&& token, error_info* info=nullptr) const;
/** /**
* \brief Closes a prepared statement, deallocating it from the server (sync with error code version). * \brief Closes a prepared statement, deallocating it from the server (sync with error code version).
* \details This function may be used to free resources from the server. * \details This function may be used to free resources from the server.
* Closing the parent connection object causes all prepared statements associated * Closing the parent connection object causes all prepared statements associated
* with the connection to also be deallocated from the server. Thus, you should be * with the connection to also be deallocated from the server. Thus, you should be
* using close() when reusing connection objects but creating and destroying prepared * using close() when reusing connection objects but creating and destroying prepared
* statements. If you do not reuse connections, or if you just create your prepared * statements. If you do not reuse connections, or if you just create your prepared
* statements at application startup, it is not required to call this function. * statements at application startup, it is not required to call this function.
* *
* After calling this function, no further functions may be called on this prepared * After calling this function, no further functions may be called on this prepared
* statement, other than assignment. Failing to do so results in undefined behavior. * statement, other than assignment. Failing to do so results in undefined behavior.
*/ */
void close(error_code&, error_info&); void close(error_code&, error_info&);
/// Closes a prepared statement, deallocating it from the server (sync with exceptions version). /// Closes a prepared statement, deallocating it from the server (sync with exceptions version).
void close(); void close();
/// The handler signature for close. /// The handler signature for close.
using close_signature = void(error_code); using close_signature = void(error_code);
/// Closes a prepared statement, deallocating it from the server (async version). /// Closes a prepared statement, deallocating it from the server (async version).
template <typename CompletionToken> template <typename CompletionToken>
BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, close_signature) BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, close_signature)
async_close(CompletionToken&& token, error_info* info=nullptr); async_close(CompletionToken&& token, error_info* info=nullptr);
}; };
/** /**

View File

@ -62,148 +62,148 @@ namespace mysql {
* resultset, other than assignment. Resultsets are movable but not copyable. * resultset, other than assignment. Resultsets are movable but not copyable.
*/ */
template < template <
typename StreamType typename StreamType
> >
class resultset class resultset
{ {
using channel_type = detail::channel<StreamType>; using channel_type = detail::channel<StreamType>;
detail::deserialize_row_fn deserializer_ {}; detail::deserialize_row_fn deserializer_ {};
channel_type* channel_; channel_type* channel_;
detail::resultset_metadata meta_; detail::resultset_metadata meta_;
row current_row_; row current_row_;
detail::bytestring buffer_; detail::bytestring buffer_;
detail::ok_packet ok_packet_; detail::ok_packet ok_packet_;
bool eof_received_ {false}; bool eof_received_ {false};
public: public:
/// Default constructor. /// Default constructor.
resultset(): channel_(nullptr) {}; resultset(): channel_(nullptr) {};
// Private, do not use // Private, do not use
resultset(channel_type& channel, detail::resultset_metadata&& meta, detail::deserialize_row_fn deserializer): resultset(channel_type& channel, detail::resultset_metadata&& meta, detail::deserialize_row_fn deserializer):
deserializer_(deserializer), channel_(&channel), meta_(std::move(meta)) {}; deserializer_(deserializer), channel_(&channel), meta_(std::move(meta)) {};
resultset(channel_type& channel, detail::bytestring&& buffer, const detail::ok_packet& ok_pack): resultset(channel_type& 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), buffer_(std::move(buffer)), ok_packet_(ok_pack), eof_received_(true) {};
/// Retrieves the stream object associated with the underlying connection. /// Retrieves the stream object associated with the underlying connection.
StreamType& next_layer() noexcept { assert(channel_); return channel_->next_layer(); } StreamType& next_layer() noexcept { assert(channel_); return channel_->next_layer(); }
/// Retrieves the stream object associated with the underlying connection. /// Retrieves the stream object associated with the underlying connection.
const StreamType& next_layer() const noexcept { assert(channel_); return channel_->next_layer(); } const StreamType& next_layer() const noexcept { assert(channel_); return channel_->next_layer(); }
/** /**
* \brief Fetches a single row (sync with error code version). * \brief Fetches a single row (sync with error code version).
* \details The returned object will be nullptr if there are no more rows * \details The returned object will be nullptr if there are no more rows
* to be read. Calling fetch_one on a complete resultset returns nullptr. * to be read. Calling fetch_one on a complete resultset returns nullptr.
* *
* The returned row points into memory owned by the resultset. Destroying * The returned row points into memory owned by the resultset. Destroying
* or moving the resultset object invalidates the returned row. Calling * or moving the resultset object invalidates the returned row. Calling
* any of the fetch methods again does also invalidate the returned row. * any of the fetch methods again does also invalidate the returned row.
* fetch_one is the fetch method that performs the less memory allocations * fetch_one is the fetch method that performs the less memory allocations
* of the three. * of the three.
*/ */
const row* fetch_one(error_code& err, error_info& info); const row* fetch_one(error_code& err, error_info& info);
/// Fetches a single row (sync with exceptions version). /// Fetches a single row (sync with exceptions version).
const row* fetch_one(); const row* fetch_one();
/** /**
* \brief Fetches at most count rows (sync with error code version). * \brief Fetches at most count rows (sync with error code version).
* \details The returned rows are guaranteed to be valid as long as the * \details The returned rows are guaranteed to be valid as long as the
* resultset is alive. Contrary to fetch_one, subsequent calls to any of * resultset is alive. Contrary to fetch_one, subsequent calls to any of
* the fetch methods do not invalidate the returned rows. * the fetch methods do not invalidate the returned rows.
* *
* Only if count is **greater** than the available number of rows, * Only if count is **greater** than the available number of rows,
* the resultset will be completed. * the resultset will be completed.
*/ */
std::vector<owning_row> fetch_many(std::size_t count, error_code& err, error_info& info); std::vector<owning_row> fetch_many(std::size_t count, error_code& err, error_info& info);
/// Fetches at most count rows (sync with exceptions version). /// Fetches at most count rows (sync with exceptions version).
std::vector<owning_row> fetch_many(std::size_t count); std::vector<owning_row> fetch_many(std::size_t count);
/** /**
* \brief Fetches all available rows (sync with error code version). * \brief Fetches all available rows (sync with error code version).
* \details The returned rows are guaranteed to be valid as long as the * \details The returned rows are guaranteed to be valid as long as the
* resultset is alive. Contrary to fetch_one, subsequent calls to any of * resultset is alive. Contrary to fetch_one, subsequent calls to any of
* the fetch methods do not invalidate the returned rows. * the fetch methods do not invalidate the returned rows.
* *
* The resultset is guaranteed to be complete() after this call returns. * The resultset is guaranteed to be complete() after this call returns.
*/ */
std::vector<owning_row> fetch_all(error_code& err, error_info& info); std::vector<owning_row> fetch_all(error_code& err, error_info& info);
/// Fetches all available rows (sync with exceptions version). /// Fetches all available rows (sync with exceptions version).
std::vector<owning_row> fetch_all(); std::vector<owning_row> fetch_all();
/// Handler signature for fetch_one. /// Handler signature for fetch_one.
using fetch_one_signature = void(error_code, const row*); using fetch_one_signature = void(error_code, const row*);
/// Fetchs a single row (async version). /// Fetchs a single row (async version).
template <typename CompletionToken> template <typename CompletionToken>
BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, fetch_one_signature) BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, fetch_one_signature)
async_fetch_one(CompletionToken&& token, error_info* info=nullptr); async_fetch_one(CompletionToken&& token, error_info* info=nullptr);
/// Handler signature for fetch_many. /// Handler signature for fetch_many.
using fetch_many_signature = void(error_code, std::vector<owning_row>); using fetch_many_signature = void(error_code, std::vector<owning_row>);
/// Fetches at most count rows (async version). /// Fetches at most count rows (async version).
template <typename CompletionToken> template <typename CompletionToken>
BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, fetch_many_signature) BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, fetch_many_signature)
async_fetch_many(std::size_t count, CompletionToken&& token, error_info* info=nullptr); async_fetch_many(std::size_t count, CompletionToken&& token, error_info* info=nullptr);
/// Handler signature for fetch_all. /// Handler signature for fetch_all.
using fetch_all_signature = void(error_code, std::vector<owning_row>); using fetch_all_signature = void(error_code, std::vector<owning_row>);
/// Fetches all available rows (async version). /// Fetches all available rows (async version).
template <typename CompletionToken> template <typename CompletionToken>
BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, fetch_all_signature) BOOST_MYSQL_INITFN_RESULT_TYPE(CompletionToken, fetch_all_signature)
async_fetch_all(CompletionToken&& token, error_info* info=nullptr); async_fetch_all(CompletionToken&& token, error_info* info=nullptr);
/** /**
* \brief Returns whether this object represents a valid resultset. * \brief Returns whether this object represents a valid resultset.
* \details Returns false for default-constructed resultsets. It is * \details Returns false for default-constructed resultsets. It is
* undefined to call any member function on an invalid resultset, * undefined to call any member function on an invalid resultset,
* except assignment. * except assignment.
*/ */
bool valid() const noexcept { return channel_ != nullptr; } bool valid() const noexcept { return channel_ != nullptr; }
/// Returns whether the resultset has been completely read or not. /// Returns whether the resultset has been completely read or not.
bool complete() const noexcept { return eof_received_; } bool complete() const noexcept { return eof_received_; }
/** /**
* \brief Returns metadata about the fields in the query. * \brief Returns metadata about the fields in the query.
* \details There will be as many field_metadata objects as fields * \details There will be as many field_metadata objects as fields
* in the SQL query, and in the same order. For SQL statements * in the SQL query, and in the same order. For SQL statements
* that do not return values (like UPDATEs), it will be empty. * that do not return values (like UPDATEs), it will be empty.
* \see field_metadata for more details. * \see field_metadata for more details.
*/ */
const std::vector<field_metadata>& fields() const noexcept { return meta_.fields(); } const std::vector<field_metadata>& fields() const noexcept { return meta_.fields(); }
/** /**
* \brief The number of rows affected by the SQL that generated this resultset. * \brief The number of rows affected by the SQL that generated this resultset.
* \warning The resultset **must be complete** before calling this function. * \warning The resultset **must be complete** before calling this function.
*/ */
std::uint64_t affected_rows() const noexcept { assert(complete()); return ok_packet_.affected_rows.value; } std::uint64_t affected_rows() const noexcept { assert(complete()); return ok_packet_.affected_rows.value; }
/** /**
* \brief The last insert ID produced by the SQL that generated this resultset. * \brief The last insert ID produced by the SQL that generated this resultset.
* \warning The resultset **must be complete** before calling this function. * \warning The resultset **must be complete** before calling this function.
*/ */
std::uint64_t last_insert_id() const noexcept { assert(complete()); return ok_packet_.last_insert_id.value; } std::uint64_t last_insert_id() const noexcept { assert(complete()); return ok_packet_.last_insert_id.value; }
/** /**
* \brief The number of warnings produced by the SQL that generated this resultset. * \brief The number of warnings produced by the SQL that generated this resultset.
* \warning The resultset **must be complete** before calling this function. * \warning The resultset **must be complete** before calling this function.
*/ */
unsigned warning_count() const noexcept { assert(complete()); return ok_packet_.warnings.value; } unsigned warning_count() const noexcept { assert(complete()); return ok_packet_.warnings.value; }
/** /**
* \brief Additionat text information about the execution of * \brief Additionat text information about the execution of
* the SQL that generated this resultset. * the SQL that generated this resultset.
* \warning The resultset **must be complete** before calling this function. * \warning The resultset **must be complete** before calling this function.
*/ */
std::string_view info() const noexcept { assert(complete()); return ok_packet_.info.value; } std::string_view info() const noexcept { assert(complete()); return ok_packet_.info.value; }
// TODO: status flags accessors // TODO: status flags accessors
}; };
/** /**

View File

@ -32,17 +32,17 @@ namespace mysql {
*/ */
class row class row
{ {
std::vector<value> values_; std::vector<value> values_;
public: public:
/// Default and initializing constructor. /// Default and initializing constructor.
row(std::vector<value>&& values = {}): row(std::vector<value>&& values = {}):
values_(std::move(values)) {}; values_(std::move(values)) {};
/// Accessor for the sequence of values. /// Accessor for the sequence of values.
const std::vector<value>& values() const noexcept { return values_; } const std::vector<value>& values() const noexcept { return values_; }
/// Accessor for the sequence of values. /// Accessor for the sequence of values.
std::vector<value>& values() noexcept { return values_; } std::vector<value>& values() noexcept { return values_; }
}; };
/** /**
@ -52,16 +52,16 @@ public:
*/ */
class owning_row : public row class owning_row : public row
{ {
detail::bytestring buffer_; detail::bytestring buffer_;
public: public:
owning_row() = default; owning_row() = default;
owning_row(std::vector<value>&& values, detail::bytestring&& buffer) : owning_row(std::vector<value>&& values, detail::bytestring&& buffer) :
row(std::move(values)), buffer_(std::move(buffer)) {}; row(std::move(values)), buffer_(std::move(buffer)) {};
owning_row(const owning_row&) = delete; owning_row(const owning_row&) = delete;
owning_row(owning_row&&) = default; owning_row(owning_row&&) = default;
owning_row& operator=(const owning_row&) = delete; owning_row& operator=(const owning_row&) = delete;
owning_row& operator=(owning_row&&) = default; owning_row& operator=(owning_row&&) = default;
~owning_row() = default; ~owning_row() = default;
}; };
/** /**
@ -82,38 +82,38 @@ inline bool operator!=(const row& lhs, const row& rhs) { return !(lhs == rhs); }
*/ */
inline std::ostream& operator<<(std::ostream& os, const row& value) inline std::ostream& operator<<(std::ostream& os, const row& value)
{ {
os << '{'; os << '{';
const auto& arr = value.values(); const auto& arr = value.values();
if (!arr.empty()) if (!arr.empty())
{ {
os << arr[0]; os << arr[0];
for (auto it = std::next(arr.begin()); it != arr.end(); ++it) for (auto it = std::next(arr.begin()); it != arr.end(); ++it)
{ {
os << ", " << *it; os << ", " << *it;
} }
} }
return os << '}'; return os << '}';
} }
// Allow comparisons between vectors of rows and owning rows // Allow comparisons between vectors of rows and owning rows
template < template <
typename RowTypeLeft, typename RowTypeLeft,
typename RowTypeRight, typename RowTypeRight,
typename=std::enable_if_t<std::is_base_of_v<row, RowTypeLeft> && std::is_base_of_v<row, RowTypeRight>> typename=std::enable_if_t<std::is_base_of_v<row, RowTypeLeft> && std::is_base_of_v<row, RowTypeRight>>
> >
inline bool operator==(const std::vector<RowTypeLeft>& lhs, const std::vector<RowTypeRight>& rhs) inline bool operator==(const std::vector<RowTypeLeft>& lhs, const std::vector<RowTypeRight>& rhs)
{ {
return detail::container_equals(lhs, rhs); return detail::container_equals(lhs, rhs);
} }
template < template <
typename RowTypeLeft, typename RowTypeLeft,
typename RowTypeRight, typename RowTypeRight,
typename=std::enable_if_t<std::is_base_of_v<row, RowTypeLeft> && std::is_base_of_v<row, RowTypeRight>> typename=std::enable_if_t<std::is_base_of_v<row, RowTypeLeft> && std::is_base_of_v<row, RowTypeRight>>
> >
inline bool operator!=(const std::vector<RowTypeLeft>& lhs, const std::vector<RowTypeRight>& rhs) inline bool operator!=(const std::vector<RowTypeLeft>& lhs, const std::vector<RowTypeRight>& rhs)
{ {
return !(lhs == rhs); return !(lhs == rhs);
} }
} // mysql } // mysql

View File

@ -57,17 +57,17 @@ using time = std::chrono::microseconds;
* They are represented as binary strings. * They are represented as binary strings.
*/ */
using value = std::variant< using value = std::variant<
std::int32_t, // signed TINYINT, SMALLINT, MEDIUMINT, INT std::int32_t, // signed TINYINT, SMALLINT, MEDIUMINT, INT
std::int64_t, // signed BIGINT std::int64_t, // signed BIGINT
std::uint32_t, // unsigned TINYINT, SMALLINT, MEDIUMINT, INT, YEAR std::uint32_t, // unsigned TINYINT, SMALLINT, MEDIUMINT, INT, YEAR
std::uint64_t, // unsigned BIGINT std::uint64_t, // unsigned BIGINT
std::string_view, // CHAR, VARCHAR, BINARY, VARBINARY, TEXT (all sizes), BLOB (all sizes), ENUM, SET, DECIMAL, BIT, GEOMTRY std::string_view, // CHAR, VARCHAR, BINARY, VARBINARY, TEXT (all sizes), BLOB (all sizes), ENUM, SET, DECIMAL, BIT, GEOMTRY
float, // FLOAT float, // FLOAT
double, // DOUBLE double, // DOUBLE
date, // DATE date, // DATE
datetime, // DATETIME, TIMESTAMP datetime, // DATETIME, TIMESTAMP
time, // TIME time, // TIME
std::nullptr_t // Any of the above when the value is NULL std::nullptr_t // Any of the above when the value is NULL
>; >;
/** /**

View File

@ -22,150 +22,150 @@ endif()
# Unit testing. We do not use gtest_discover_tests because # Unit testing. We do not use gtest_discover_tests because
# of the runtime penalty (specially considerable in integration tests) # of the runtime penalty (specially considerable in integration tests)
add_executable( add_executable(
mysql_unittests mysql_unittests
unit/detail/auth/auth_calculator.cpp unit/detail/auth/auth_calculator.cpp
unit/detail/protocol/serialization_test_common.cpp unit/detail/protocol/serialization_test_common.cpp
unit/detail/protocol/serialization.cpp unit/detail/protocol/serialization.cpp
unit/detail/protocol/common_messages.cpp unit/detail/protocol/common_messages.cpp
unit/detail/protocol/handshake_messages.cpp unit/detail/protocol/handshake_messages.cpp
unit/detail/protocol/query_messages.cpp unit/detail/protocol/query_messages.cpp
unit/detail/protocol/prepared_statement_messages.cpp unit/detail/protocol/prepared_statement_messages.cpp
unit/detail/protocol/capabilities.cpp unit/detail/protocol/capabilities.cpp
unit/detail/protocol/text_deserialization.cpp unit/detail/protocol/text_deserialization.cpp
unit/detail/protocol/binary_deserialization.cpp unit/detail/protocol/binary_deserialization.cpp
unit/detail/protocol/null_bitmap_traits.cpp unit/detail/protocol/null_bitmap_traits.cpp
unit/metadata.cpp unit/metadata.cpp
unit/value.cpp unit/value.cpp
unit/row.cpp unit/row.cpp
unit/error.cpp unit/error.cpp
unit/prepared_statement.cpp unit/prepared_statement.cpp
) )
# A codegen issue in MSVC C++17 makes gmock expectations not work # A codegen issue in MSVC C++17 makes gmock expectations not work
if (NOT MSVC) if (NOT MSVC)
target_sources(mysql_unittests PRIVATE unit/detail/protocol/channel.cpp) target_sources(mysql_unittests PRIVATE unit/detail/protocol/channel.cpp)
endif() endif()
target_include_directories( target_include_directories(
mysql_unittests mysql_unittests
PRIVATE PRIVATE
common common
) )
target_link_libraries( target_link_libraries(
mysql_unittests mysql_unittests
PRIVATE PRIVATE
gtest gtest
gtest_main gtest_main
gmock gmock
mysql_asio mysql_asio
) )
_mysql_common_target_settings(mysql_unittests) _mysql_common_target_settings(mysql_unittests)
if (BOOST_MYSQL_VALGRIND_TESTS) if (BOOST_MYSQL_VALGRIND_TESTS)
Mysqlvalgrind_AddMemcheckTest( Mysqlvalgrind_AddMemcheckTest(
NAME mysql_unittests_memcheck NAME mysql_unittests_memcheck
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/mysql_unittests COMMAND ${CMAKE_CURRENT_BINARY_DIR}/mysql_unittests
) )
else() else()
add_test( add_test(
NAME mysql_unittests NAME mysql_unittests
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/mysql_unittests COMMAND ${CMAKE_CURRENT_BINARY_DIR}/mysql_unittests
) )
endif() endif()
# Integration testing # Integration testing
find_package(Boost REQUIRED COMPONENTS coroutine) find_package(Boost REQUIRED COMPONENTS coroutine)
add_executable( add_executable(
mysql_integrationtests mysql_integrationtests
integration/metadata_validator.cpp integration/metadata_validator.cpp
integration/network_functions.cpp integration/network_functions.cpp
integration/handshake.cpp integration/handshake.cpp
integration/query.cpp integration/query.cpp
integration/prepare_statement.cpp integration/prepare_statement.cpp
integration/execute_statement.cpp integration/execute_statement.cpp
integration/close_statement.cpp integration/close_statement.cpp
integration/resultset.cpp integration/resultset.cpp
integration/prepared_statement_lifecycle.cpp integration/prepared_statement_lifecycle.cpp
integration/database_types.cpp integration/database_types.cpp
) )
target_link_libraries( target_link_libraries(
mysql_integrationtests mysql_integrationtests
PRIVATE PRIVATE
gtest gtest
gtest_main gtest_main
gmock gmock
mysql_asio mysql_asio
Boost::coroutine Boost::coroutine
) )
target_include_directories( target_include_directories(
mysql_integrationtests mysql_integrationtests
PRIVATE PRIVATE
common common
) )
_mysql_common_target_settings(mysql_integrationtests) _mysql_common_target_settings(mysql_integrationtests)
# Integration test setup # Integration test setup
_mysql_sql_setup_fixture( _mysql_sql_setup_fixture(
TEST_NAME mysql_integrationtests_setup TEST_NAME mysql_integrationtests_setup
FIXTURE_NAME mysql_integrationtests_fixture FIXTURE_NAME mysql_integrationtests_fixture
SQL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/integration/db_setup.sql SQL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/integration/db_setup.sql
SKIP_VAR BOOST_MYSQL_SKIP_DB_SETUP SKIP_VAR BOOST_MYSQL_SKIP_DB_SETUP
) )
# Regular integration tests # Regular integration tests
add_test( add_test(
NAME mysql_integrationtests NAME mysql_integrationtests
COMMAND COMMAND
${CMAKE_CURRENT_BINARY_DIR}/mysql_integrationtests ${CMAKE_CURRENT_BINARY_DIR}/mysql_integrationtests
"--gtest_filter=-*RequiresSha256*" # Exclude anything using SHA256 "--gtest_filter=-*RequiresSha256*" # Exclude anything using SHA256
) )
set_tests_properties( set_tests_properties(
mysql_integrationtests mysql_integrationtests
PROPERTIES FIXTURES_REQUIRED mysql_integrationtests_setup PROPERTIES FIXTURES_REQUIRED mysql_integrationtests_setup
) )
# If we are using memcheck, then run a subset of the integration tests # If we are using memcheck, then run a subset of the integration tests
# under valgrind. Coroutine tests don't work well under Valgrind, and # under valgrind. Coroutine tests don't work well under Valgrind, and
# SSL tests are too slow # SSL tests are too slow
if (BOOST_MYSQL_VALGRIND_TESTS) if (BOOST_MYSQL_VALGRIND_TESTS)
if (DEFINED ENV{BOOST_MYSQL_HAS_SHA256}) if (DEFINED ENV{BOOST_MYSQL_HAS_SHA256})
set(GTEST_FILTER "--gtest_filter=-*sslrequire*:*coroutine*") set(GTEST_FILTER "--gtest_filter=-*sslrequire*:*coroutine*")
else() else()
set(GTEST_FILTER "--gtest_filter=-*RequiresSha256*:*sslrequire*:*coroutine*") set(GTEST_FILTER "--gtest_filter=-*RequiresSha256*:*sslrequire*:*coroutine*")
endif() endif()
add_test( add_test(
NAME mysql_integrationtests_memcheck NAME mysql_integrationtests_memcheck
COMMAND COMMAND
${CMAKE_CURRENT_BINARY_DIR}/mysql_integrationtests ${CMAKE_CURRENT_BINARY_DIR}/mysql_integrationtests
${GTEST_FILTER} ${GTEST_FILTER}
) )
set_tests_properties( set_tests_properties(
mysql_integrationtests_memcheck mysql_integrationtests_memcheck
PROPERTIES FIXTURES_REQUIRED mysql_integrationtests_setup PROPERTIES FIXTURES_REQUIRED mysql_integrationtests_setup
) )
endif() endif()
# SHA256 tests # SHA256 tests
if (DEFINED ENV{BOOST_MYSQL_HAS_SHA256}) if (DEFINED ENV{BOOST_MYSQL_HAS_SHA256})
# Setup # Setup
_mysql_sql_setup_fixture( _mysql_sql_setup_fixture(
TEST_NAME mysql_integrationtests_sha256_setup TEST_NAME mysql_integrationtests_sha256_setup
FIXTURE_NAME mysql_integrationtests_sha256_fixture FIXTURE_NAME mysql_integrationtests_sha256_fixture
SQL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/integration/db_setup_sha256.sql SQL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/integration/db_setup_sha256.sql
SKIP_VAR BOOST_MYSQL_SKIP_DB_SETUP SKIP_VAR BOOST_MYSQL_SKIP_DB_SETUP
) )
# Actual tests # Actual tests
add_test( add_test(
NAME mysql_integrationtests_sha256 NAME mysql_integrationtests_sha256
COMMAND COMMAND
${CMAKE_CURRENT_BINARY_DIR}/mysql_integrationtests ${CMAKE_CURRENT_BINARY_DIR}/mysql_integrationtests
"--gtest_filter=*RequiresSha256*" # Run only SHA256 stuff "--gtest_filter=*RequiresSha256*" # Run only SHA256 stuff
) )
set_tests_properties( set_tests_properties(
mysql_integrationtests_sha256 mysql_integrationtests_sha256
PROPERTIES FIXTURES_REQUIRED PROPERTIES FIXTURES_REQUIRED
"mysql_integrationtests_fixture;mysql_integrationtests_sha256_fixture" "mysql_integrationtests_fixture;mysql_integrationtests_sha256_fixture"
) )
endif() endif()

View File

@ -31,146 +31,146 @@ namespace test {
template <typename... Types> template <typename... Types>
std::vector<value> makevalues(Types&&... args) std::vector<value> makevalues(Types&&... args)
{ {
return std::vector<value>{mysql::value(std::forward<Types>(args))...}; return std::vector<value>{mysql::value(std::forward<Types>(args))...};
} }
template <typename... Types> template <typename... Types>
row makerow(Types&&... args) row makerow(Types&&... args)
{ {
return row(makevalues(std::forward<Types>(args)...)); return row(makevalues(std::forward<Types>(args)...));
} }
template <typename... Types> template <typename... Types>
std::vector<row> makerows(std::size_t row_size, Types&&... args) std::vector<row> makerows(std::size_t row_size, Types&&... args)
{ {
auto values = makevalues(std::forward<Types>(args)...); auto values = makevalues(std::forward<Types>(args)...);
assert(values.size() % row_size == 0); assert(values.size() % row_size == 0);
std::vector<row> res; std::vector<row> res;
for (std::size_t i = 0; i < values.size(); i += row_size) for (std::size_t i = 0; i < values.size(); i += row_size)
{ {
std::vector<value> row_values (values.begin() + i, values.begin() + i + row_size); std::vector<value> row_values (values.begin() + i, values.begin() + i + row_size);
res.push_back(row(std::move(row_values))); res.push_back(row(std::move(row_values)));
} }
return res; return res;
} }
inline date makedate(int years, int months, int days) inline date makedate(int years, int months, int days)
{ {
return mysql::date(::date::year(years)/::date::month(months)/::date::day(days)); return mysql::date(::date::year(years)/::date::month(months)/::date::day(days));
} }
inline datetime makedt(int years, int months, int days, int hours=0, int mins=0, int secs=0, int micros=0) inline datetime makedt(int years, int months, int days, int hours=0, int mins=0, int secs=0, int micros=0)
{ {
return mysql::datetime(mysql::date(::date::year(years)/::date::month(months)/::date::day(days))) + return mysql::datetime(mysql::date(::date::year(years)/::date::month(months)/::date::day(days))) +
std::chrono::hours(hours) + std::chrono::minutes(mins) + std::chrono::hours(hours) + std::chrono::minutes(mins) +
std::chrono::seconds(secs) + std::chrono::microseconds(micros); std::chrono::seconds(secs) + std::chrono::microseconds(micros);
} }
inline mysql::time maket(int hours, int mins, int secs, int micros=0) inline mysql::time maket(int hours, int mins, int secs, int micros=0)
{ {
return std::chrono::hours(hours) + std::chrono::minutes(mins) return std::chrono::hours(hours) + std::chrono::minutes(mins)
+ std::chrono::seconds(secs) + std::chrono::microseconds(micros); + std::chrono::seconds(secs) + std::chrono::microseconds(micros);
} }
template <std::size_t N> template <std::size_t N>
inline std::string_view makesv(const char (&value) [N]) inline std::string_view makesv(const char (&value) [N])
{ {
static_assert(N>=1); static_assert(N>=1);
return std::string_view(value, N-1); // discard null terminator return std::string_view(value, N-1); // discard null terminator
} }
template <std::size_t N> template <std::size_t N>
inline std::string_view makesv(const std::uint8_t (&value) [N]) inline std::string_view makesv(const std::uint8_t (&value) [N])
{ {
return std::string_view(reinterpret_cast<const char*>(value), N); return std::string_view(reinterpret_cast<const char*>(value), N);
} }
inline std::string_view makesv(const std::uint8_t* value, std::size_t size) inline std::string_view makesv(const std::uint8_t* value, std::size_t size)
{ {
return std::string_view(reinterpret_cast<const char*>(value), size); return std::string_view(reinterpret_cast<const char*>(value), size);
} }
inline void validate_string_contains(std::string value, const std::vector<std::string>& to_check) inline void validate_string_contains(std::string value, const std::vector<std::string>& to_check)
{ {
std::transform(value.begin(), value.end(), value.begin(), &tolower); std::transform(value.begin(), value.end(), value.begin(), &tolower);
for (const auto& elm: to_check) for (const auto& elm: to_check)
{ {
EXPECT_THAT(value, testing::HasSubstr(elm)); EXPECT_THAT(value, testing::HasSubstr(elm));
} }
} }
inline void validate_error_info(const boost::mysql::error_info& value, const std::vector<std::string>& to_check) inline void validate_error_info(const boost::mysql::error_info& value, const std::vector<std::string>& to_check)
{ {
validate_string_contains(value.message(), to_check); validate_string_contains(value.message(), to_check);
} }
inline std::string buffer_diff(std::string_view s0, std::string_view s1) inline std::string buffer_diff(std::string_view s0, std::string_view s1)
{ {
std::ostringstream ss; std::ostringstream ss;
ss << std::hex; ss << std::hex;
for (std::size_t i = 0; i < std::min(s0.size(), s1.size()); ++i) for (std::size_t i = 0; i < std::min(s0.size(), s1.size()); ++i)
{ {
unsigned b0 = reinterpret_cast<const std::uint8_t*>(s0.data())[i]; unsigned b0 = reinterpret_cast<const std::uint8_t*>(s0.data())[i];
unsigned b1 = reinterpret_cast<const std::uint8_t*>(s1.data())[i]; unsigned b1 = reinterpret_cast<const std::uint8_t*>(s1.data())[i];
if (b0 != b1) if (b0 != b1)
{ {
ss << "i=" << i << ": " << b0 << " != " << b1 << "\n"; ss << "i=" << i << ": " << b0 << " != " << b1 << "\n";
} }
} }
if (s0.size() != s1.size()) if (s0.size() != s1.size())
{ {
ss << "sizes: " << s0.size() << " != " << s1.size() << "\n"; ss << "sizes: " << s0.size() << " != " << s1.size() << "\n";
} }
return ss.str(); return ss.str();
} }
inline void compare_buffers(std::string_view s0, std::string_view s1, const char* msg = "") inline void compare_buffers(std::string_view s0, std::string_view s1, const char* msg = "")
{ {
EXPECT_EQ(s0, s1) << msg << ":\n" << buffer_diff(s0, s1); EXPECT_EQ(s0, s1) << msg << ":\n" << buffer_diff(s0, s1);
} }
inline void concat(std::vector<std::uint8_t>& lhs, boost::asio::const_buffer rhs) inline void concat(std::vector<std::uint8_t>& lhs, boost::asio::const_buffer rhs)
{ {
auto current_size = lhs.size(); auto current_size = lhs.size();
lhs.resize(current_size + rhs.size()); lhs.resize(current_size + rhs.size());
memcpy(lhs.data() + current_size, rhs.data(), rhs.size()); memcpy(lhs.data() + current_size, rhs.data(), rhs.size());
} }
inline void concat(std::vector<std::uint8_t>& lhs, const std::vector<uint8_t>& rhs) inline void concat(std::vector<std::uint8_t>& lhs, const std::vector<uint8_t>& rhs)
{ {
concat(lhs, boost::asio::buffer(rhs)); concat(lhs, boost::asio::buffer(rhs));
} }
inline std::vector<std::uint8_t> concat_copy( inline std::vector<std::uint8_t> concat_copy(
std::vector<uint8_t>&& lhs, std::vector<uint8_t>&& lhs,
const std::vector<uint8_t>& rhs const std::vector<uint8_t>& rhs
) )
{ {
concat(lhs, rhs); concat(lhs, rhs);
return std::move(lhs); return std::move(lhs);
} }
inline void check_call(const std::string& command) inline void check_call(const std::string& command)
{ {
int code = std::system(command.c_str()); int code = std::system(command.c_str());
if (code != 0) // we are assuming 0 means success if (code != 0) // we are assuming 0 means success
{ {
throw std::runtime_error( throw std::runtime_error(
detail::stringize("Command '", command, "' returned status code ", code) detail::stringize("Command '", command, "' returned status code ", code)
); );
} }
} }
inline const char* to_string(ssl_mode m) inline const char* to_string(ssl_mode m)
{ {
switch (m) switch (m)
{ {
case ssl_mode::disable: return "ssldisable"; case ssl_mode::disable: return "ssldisable";
case ssl_mode::enable: return "sslenable"; case ssl_mode::enable: return "sslenable";
case ssl_mode::require: return "sslrequire"; case ssl_mode::require: return "sslrequire";
default: assert(false); return ""; default: assert(false); return "";
} }
} }
struct named_param {}; struct named_param {};
@ -179,11 +179,11 @@ template <typename T, typename=std::enable_if_t<std::is_base_of_v<named_param, T
std::ostream& operator<<(std::ostream& os, const T& v) { return os << v.name; } std::ostream& operator<<(std::ostream& os, const T& v) { return os << v.name; }
constexpr auto test_name_generator = [](const auto& param_info) { constexpr auto test_name_generator = [](const auto& param_info) {
std::ostringstream os; std::ostringstream os;
os << param_info.param; os << param_info.param;
std::string res = os.str(); std::string res = os.str();
std::replace_if(res.begin(), res.end(), [](char c) { return !std::isalnum(c); }, '_'); std::replace_if(res.begin(), res.end(), [](char c) { return !std::isalnum(c); }, '_');
return res; return res;
}; };
} // test } // test

View File

@ -17,31 +17,31 @@ template <typename Stream>
struct CloseStatementTest : NetworkTest<Stream> struct CloseStatementTest : NetworkTest<Stream>
{ {
void ExistingOrClosedStatement() void ExistingOrClosedStatement()
{ {
auto* net = this->GetParam().net; auto* net = this->GetParam().net;
// Prepare a statement // Prepare a statement
auto stmt = net->prepare_statement(this->conn, "SELECT * FROM empty_table"); auto stmt = net->prepare_statement(this->conn, "SELECT * FROM empty_table");
stmt.validate_no_error(); stmt.validate_no_error();
// Verify it works fine // Verify it works fine
auto exec_result = net->execute_statement(stmt.value, {}); auto exec_result = net->execute_statement(stmt.value, {});
exec_result.validate_no_error(); exec_result.validate_no_error();
exec_result.value.fetch_all(); // clean all packets sent for this execution exec_result.value.fetch_all(); // clean all packets sent for this execution
// Close the statement // Close the statement
auto close_result = net->close_statement(stmt.value); auto close_result = net->close_statement(stmt.value);
close_result.validate_no_error(); close_result.validate_no_error();
// Close it again // Close it again
close_result = net->close_statement(stmt.value); close_result = net->close_statement(stmt.value);
close_result.validate_no_error(); close_result.validate_no_error();
// Verify close took effect // Verify close took effect
exec_result = net->execute_statement(stmt.value, {}); exec_result = net->execute_statement(stmt.value, {});
exec_result.validate_error(boost::mysql::errc::unknown_stmt_handler, {"unknown prepared statement"}); exec_result.validate_error(boost::mysql::errc::unknown_stmt_handler, {"unknown prepared statement"});
} }
}; };
BOOST_MYSQL_NETWORK_TEST_SUITE(CloseStatementTest) BOOST_MYSQL_NETWORK_TEST_SUITE(CloseStatementTest)

View File

@ -32,118 +32,118 @@ namespace
*/ */
struct database_types_testcase struct database_types_testcase
{ {
std::string table; std::string table;
std::string field; std::string field;
std::string row_id; std::string row_id;
value expected_value; value expected_value;
meta_validator mvalid; meta_validator mvalid;
template <typename ValueType, typename... Args> template <typename ValueType, typename... Args>
database_types_testcase(std::string table, std::string field, std::string row_id, database_types_testcase(std::string table, std::string field, std::string row_id,
ValueType&& expected_value, Args&&... args) : ValueType&& expected_value, Args&&... args) :
table(table), table(table),
field(field), field(field),
row_id(move(row_id)), row_id(move(row_id)),
expected_value(std::forward<ValueType>(expected_value)), expected_value(std::forward<ValueType>(expected_value)),
mvalid(move(table), move(field), std::forward<Args>(args)...) mvalid(move(table), move(field), std::forward<Args>(args)...)
{ {
} }
}; };
std::ostream& operator<<(std::ostream& os, const database_types_testcase& v) std::ostream& operator<<(std::ostream& os, const database_types_testcase& v)
{ {
return os << v.table << "." << v.field << "." << v.row_id; return os << v.table << "." << v.field << "." << v.row_id;
} }
// Note: NetworkTest with do_handshake=true requires GetParam() to have an ssl data member // Note: NetworkTest with do_handshake=true requires GetParam() to have an ssl data member
struct DatabaseTypesTest : struct DatabaseTypesTest :
NetworkTest<boost::asio::ip::tcp::socket, database_types_testcase, false> NetworkTest<boost::asio::ip::tcp::socket, database_types_testcase, false>
{ {
DatabaseTypesTest() DatabaseTypesTest()
{ {
handshake(boost::mysql::ssl_mode::disable); handshake(boost::mysql::ssl_mode::disable);
} }
}; };
TEST_P(DatabaseTypesTest, Query_MetadataAndValueCorrect) TEST_P(DatabaseTypesTest, Query_MetadataAndValueCorrect)
{ {
const auto& param = GetParam(); const auto& param = GetParam();
// Compose the query // Compose the query
std::ostringstream queryss; std::ostringstream queryss;
queryss << "SELECT " << param.field queryss << "SELECT " << param.field
<< " FROM " << param.table << " FROM " << param.table
<< " WHERE id = '" << param.row_id << "'"; << " WHERE id = '" << param.row_id << "'";
auto query = queryss.str(); auto query = queryss.str();
// Execute it // Execute it
auto result = conn.query(query); auto result = conn.query(query);
auto rows = result.fetch_all(); auto rows = result.fetch_all();
// Validate the received metadata // Validate the received metadata
validate_meta(result.fields(), {param.mvalid}); validate_meta(result.fields(), {param.mvalid});
// Validate the returned value // Validate the returned value
row expected_row ({param.expected_value}); row expected_row ({param.expected_value});
ASSERT_EQ(rows.size(), 1); ASSERT_EQ(rows.size(), 1);
EXPECT_EQ(static_cast<const row&>(rows[0]), expected_row); // make gtest pick operator<< EXPECT_EQ(static_cast<const row&>(rows[0]), expected_row); // make gtest pick operator<<
} }
TEST_P(DatabaseTypesTest, PreparedStatementExecuteResult_MetadataAndValueCorrect) TEST_P(DatabaseTypesTest, PreparedStatementExecuteResult_MetadataAndValueCorrect)
{ {
// Parameter // Parameter
const auto& param = GetParam(); const auto& param = GetParam();
// Prepare the statement // Prepare the statement
std::ostringstream stmtss; std::ostringstream stmtss;
stmtss << "SELECT " << param.field stmtss << "SELECT " << param.field
<< " FROM " << param.table << " FROM " << param.table
<< " WHERE id = ?"; << " WHERE id = ?";
auto stmt_sql = stmtss.str(); auto stmt_sql = stmtss.str();
auto stmt = conn.prepare_statement(stmt_sql); auto stmt = conn.prepare_statement(stmt_sql);
// Execute it with the provided parameters // Execute it with the provided parameters
auto result = stmt.execute(makevalues(param.row_id)); auto result = stmt.execute(makevalues(param.row_id));
auto rows = result.fetch_all(); auto rows = result.fetch_all();
// Validate the received metadata // Validate the received metadata
validate_meta(result.fields(), {param.mvalid}); validate_meta(result.fields(), {param.mvalid});
// Validate the returned value // Validate the returned value
row expected_row ({param.expected_value}); row expected_row ({param.expected_value});
ASSERT_EQ(rows.size(), 1); ASSERT_EQ(rows.size(), 1);
EXPECT_EQ(static_cast<const row&>(rows[0]), expected_row); // make gtest pick operator<< EXPECT_EQ(static_cast<const row&>(rows[0]), expected_row); // make gtest pick operator<<
} }
TEST_P(DatabaseTypesTest, PreparedStatementExecuteParam_ValueSerializedCorrectly) TEST_P(DatabaseTypesTest, PreparedStatementExecuteParam_ValueSerializedCorrectly)
{ {
// This test is not applicable (yet) to nullptr values or bit values. // This test is not applicable (yet) to nullptr values or bit values.
// Doing "field = ?" where ? is nullptr never matches anything. // Doing "field = ?" where ? is nullptr never matches anything.
// Bit values are returned as strings bit need to be sent as integers in // Bit values are returned as strings bit need to be sent as integers in
// prepared statements. TODO: make this better. // prepared statements. TODO: make this better.
const auto& param = GetParam(); const auto& param = GetParam();
if (param.expected_value == value(nullptr) || if (param.expected_value == value(nullptr) ||
param.mvalid.type() == field_type::bit) param.mvalid.type() == field_type::bit)
{ {
return; return;
} }
// Prepare the statement // Prepare the statement
std::ostringstream stmtss; std::ostringstream stmtss;
stmtss << "SELECT " << param.field stmtss << "SELECT " << param.field
<< " FROM " << param.table << " FROM " << param.table
<< " WHERE id = ? AND " << param.field << " = ?"; << " WHERE id = ? AND " << param.field << " = ?";
auto stmt_sql = stmtss.str(); auto stmt_sql = stmtss.str();
auto stmt = conn.prepare_statement(stmt_sql); auto stmt = conn.prepare_statement(stmt_sql);
// Execute it with the provided parameters // Execute it with the provided parameters
auto result = stmt.execute(makevalues(param.row_id, param.expected_value)); auto result = stmt.execute(makevalues(param.row_id, param.expected_value));
auto rows = result.fetch_all(); auto rows = result.fetch_all();
// Validate the returned value // Validate the returned value
row expected_row ({param.expected_value}); row expected_row ({param.expected_value});
ASSERT_EQ(rows.size(), 1); ASSERT_EQ(rows.size(), 1);
EXPECT_EQ(static_cast<const row&>(rows[0]), expected_row); // make gtest pick operator<< EXPECT_EQ(static_cast<const row&>(rows[0]), expected_row); // make gtest pick operator<<
} }
using flagsvec = std::vector<meta_validator::flag_getter>; using flagsvec = std::vector<meta_validator::flag_getter>;
@ -154,147 +154,147 @@ const flagsvec flags_zerofill { &field_metadata::is_unsigned, &field_metadata::i
// Integers // Integers
INSTANTIATE_TEST_SUITE_P(TINYINT, DatabaseTypesTest, Values( INSTANTIATE_TEST_SUITE_P(TINYINT, DatabaseTypesTest, Values(
database_types_testcase("types_tinyint", "field_signed", "regular", std::int32_t(20), field_type::tinyint), database_types_testcase("types_tinyint", "field_signed", "regular", std::int32_t(20), field_type::tinyint),
database_types_testcase("types_tinyint", "field_signed", "negative", std::int32_t(-20), field_type::tinyint), database_types_testcase("types_tinyint", "field_signed", "negative", std::int32_t(-20), field_type::tinyint),
database_types_testcase("types_tinyint", "field_signed", "min", std::int32_t(-0x80), field_type::tinyint), database_types_testcase("types_tinyint", "field_signed", "min", std::int32_t(-0x80), field_type::tinyint),
database_types_testcase("types_tinyint", "field_signed", "max", std::int32_t(0x7f), field_type::tinyint), database_types_testcase("types_tinyint", "field_signed", "max", std::int32_t(0x7f), field_type::tinyint),
database_types_testcase("types_tinyint", "field_unsigned", "regular", std::uint32_t(20), field_type::tinyint, flags_unsigned), database_types_testcase("types_tinyint", "field_unsigned", "regular", std::uint32_t(20), field_type::tinyint, flags_unsigned),
database_types_testcase("types_tinyint", "field_unsigned", "min", std::uint32_t(0), field_type::tinyint, flags_unsigned), database_types_testcase("types_tinyint", "field_unsigned", "min", std::uint32_t(0), field_type::tinyint, flags_unsigned),
database_types_testcase("types_tinyint", "field_unsigned", "max", std::uint32_t(0xff), field_type::tinyint, flags_unsigned), database_types_testcase("types_tinyint", "field_unsigned", "max", std::uint32_t(0xff), field_type::tinyint, flags_unsigned),
database_types_testcase("types_tinyint", "field_width", "regular", std::int32_t(20), field_type::tinyint), database_types_testcase("types_tinyint", "field_width", "regular", std::int32_t(20), field_type::tinyint),
database_types_testcase("types_tinyint", "field_width", "negative", std::int32_t(-20), field_type::tinyint), database_types_testcase("types_tinyint", "field_width", "negative", std::int32_t(-20), field_type::tinyint),
database_types_testcase("types_tinyint", "field_zerofill", "regular", std::uint32_t(20), field_type::tinyint, flags_zerofill), database_types_testcase("types_tinyint", "field_zerofill", "regular", std::uint32_t(20), field_type::tinyint, flags_zerofill),
database_types_testcase("types_tinyint", "field_zerofill", "min", std::uint32_t(0), field_type::tinyint, flags_zerofill) database_types_testcase("types_tinyint", "field_zerofill", "min", std::uint32_t(0), field_type::tinyint, flags_zerofill)
), test_name_generator); ), test_name_generator);
INSTANTIATE_TEST_SUITE_P(SMALLINT, DatabaseTypesTest, Values( INSTANTIATE_TEST_SUITE_P(SMALLINT, DatabaseTypesTest, Values(
database_types_testcase("types_smallint", "field_signed", "regular", std::int32_t(20), field_type::smallint), database_types_testcase("types_smallint", "field_signed", "regular", std::int32_t(20), field_type::smallint),
database_types_testcase("types_smallint", "field_signed", "negative", std::int32_t(-20), field_type::smallint), database_types_testcase("types_smallint", "field_signed", "negative", std::int32_t(-20), field_type::smallint),
database_types_testcase("types_smallint", "field_signed", "min", std::int32_t(-0x8000), field_type::smallint), database_types_testcase("types_smallint", "field_signed", "min", std::int32_t(-0x8000), field_type::smallint),
database_types_testcase("types_smallint", "field_signed", "max", std::int32_t(0x7fff), field_type::smallint), database_types_testcase("types_smallint", "field_signed", "max", std::int32_t(0x7fff), field_type::smallint),
database_types_testcase("types_smallint", "field_unsigned", "regular", std::uint32_t(20), field_type::smallint, flags_unsigned), database_types_testcase("types_smallint", "field_unsigned", "regular", std::uint32_t(20), field_type::smallint, flags_unsigned),
database_types_testcase("types_smallint", "field_unsigned", "min", std::uint32_t(0), field_type::smallint, flags_unsigned), database_types_testcase("types_smallint", "field_unsigned", "min", std::uint32_t(0), field_type::smallint, flags_unsigned),
database_types_testcase("types_smallint", "field_unsigned", "max", std::uint32_t(0xffff), field_type::smallint, flags_unsigned), database_types_testcase("types_smallint", "field_unsigned", "max", std::uint32_t(0xffff), field_type::smallint, flags_unsigned),
database_types_testcase("types_smallint", "field_width", "regular", std::int32_t(20), field_type::smallint), database_types_testcase("types_smallint", "field_width", "regular", std::int32_t(20), field_type::smallint),
database_types_testcase("types_smallint", "field_width", "negative", std::int32_t(-20), field_type::smallint), database_types_testcase("types_smallint", "field_width", "negative", std::int32_t(-20), field_type::smallint),
database_types_testcase("types_smallint", "field_zerofill", "regular", std::uint32_t(20), field_type::smallint, flags_zerofill), database_types_testcase("types_smallint", "field_zerofill", "regular", std::uint32_t(20), field_type::smallint, flags_zerofill),
database_types_testcase("types_smallint", "field_zerofill", "min", std::uint32_t(0), field_type::smallint, flags_zerofill) database_types_testcase("types_smallint", "field_zerofill", "min", std::uint32_t(0), field_type::smallint, flags_zerofill)
), test_name_generator); ), test_name_generator);
INSTANTIATE_TEST_SUITE_P(MEDIUMINT, DatabaseTypesTest, Values( INSTANTIATE_TEST_SUITE_P(MEDIUMINT, DatabaseTypesTest, Values(
database_types_testcase("types_mediumint", "field_signed", "regular", std::int32_t(20), field_type::mediumint), database_types_testcase("types_mediumint", "field_signed", "regular", std::int32_t(20), field_type::mediumint),
database_types_testcase("types_mediumint", "field_signed", "negative", std::int32_t(-20), field_type::mediumint), database_types_testcase("types_mediumint", "field_signed", "negative", std::int32_t(-20), field_type::mediumint),
database_types_testcase("types_mediumint", "field_signed", "min", std::int32_t(-0x800000), field_type::mediumint), database_types_testcase("types_mediumint", "field_signed", "min", std::int32_t(-0x800000), field_type::mediumint),
database_types_testcase("types_mediumint", "field_signed", "max", std::int32_t(0x7fffff), field_type::mediumint), database_types_testcase("types_mediumint", "field_signed", "max", std::int32_t(0x7fffff), field_type::mediumint),
database_types_testcase("types_mediumint", "field_unsigned", "regular", std::uint32_t(20), field_type::mediumint, flags_unsigned), database_types_testcase("types_mediumint", "field_unsigned", "regular", std::uint32_t(20), field_type::mediumint, flags_unsigned),
database_types_testcase("types_mediumint", "field_unsigned", "min", std::uint32_t(0), field_type::mediumint, flags_unsigned), database_types_testcase("types_mediumint", "field_unsigned", "min", std::uint32_t(0), field_type::mediumint, flags_unsigned),
database_types_testcase("types_mediumint", "field_unsigned", "max", std::uint32_t(0xffffff), field_type::mediumint, flags_unsigned), database_types_testcase("types_mediumint", "field_unsigned", "max", std::uint32_t(0xffffff), field_type::mediumint, flags_unsigned),
database_types_testcase("types_mediumint", "field_width", "regular", std::int32_t(20), field_type::mediumint), database_types_testcase("types_mediumint", "field_width", "regular", std::int32_t(20), field_type::mediumint),
database_types_testcase("types_mediumint", "field_width", "negative", std::int32_t(-20), field_type::mediumint), database_types_testcase("types_mediumint", "field_width", "negative", std::int32_t(-20), field_type::mediumint),
database_types_testcase("types_mediumint", "field_zerofill", "regular", std::uint32_t(20), field_type::mediumint, flags_zerofill), database_types_testcase("types_mediumint", "field_zerofill", "regular", std::uint32_t(20), field_type::mediumint, flags_zerofill),
database_types_testcase("types_mediumint", "field_zerofill", "min", std::uint32_t(0), field_type::mediumint, flags_zerofill) database_types_testcase("types_mediumint", "field_zerofill", "min", std::uint32_t(0), field_type::mediumint, flags_zerofill)
), test_name_generator); ), test_name_generator);
INSTANTIATE_TEST_SUITE_P(INT, DatabaseTypesTest, Values( INSTANTIATE_TEST_SUITE_P(INT, DatabaseTypesTest, Values(
database_types_testcase("types_int", "field_signed", "regular", std::int32_t(20), field_type::int_), database_types_testcase("types_int", "field_signed", "regular", std::int32_t(20), field_type::int_),
database_types_testcase("types_int", "field_signed", "negative", std::int32_t(-20), field_type::int_), database_types_testcase("types_int", "field_signed", "negative", std::int32_t(-20), field_type::int_),
database_types_testcase("types_int", "field_signed", "min", std::int32_t(-0x80000000), field_type::int_), database_types_testcase("types_int", "field_signed", "min", std::int32_t(-0x80000000), field_type::int_),
database_types_testcase("types_int", "field_signed", "max", std::int32_t(0x7fffffff), field_type::int_), database_types_testcase("types_int", "field_signed", "max", std::int32_t(0x7fffffff), field_type::int_),
database_types_testcase("types_int", "field_unsigned", "regular", std::uint32_t(20), field_type::int_, flags_unsigned), database_types_testcase("types_int", "field_unsigned", "regular", std::uint32_t(20), field_type::int_, flags_unsigned),
database_types_testcase("types_int", "field_unsigned", "min", std::uint32_t(0), field_type::int_, flags_unsigned), database_types_testcase("types_int", "field_unsigned", "min", std::uint32_t(0), field_type::int_, flags_unsigned),
database_types_testcase("types_int", "field_unsigned", "max", std::uint32_t(0xffffffff), field_type::int_, flags_unsigned), database_types_testcase("types_int", "field_unsigned", "max", std::uint32_t(0xffffffff), field_type::int_, flags_unsigned),
database_types_testcase("types_int", "field_width", "regular", std::int32_t(20), field_type::int_), database_types_testcase("types_int", "field_width", "regular", std::int32_t(20), field_type::int_),
database_types_testcase("types_int", "field_width", "negative", std::int32_t(-20), field_type::int_), database_types_testcase("types_int", "field_width", "negative", std::int32_t(-20), field_type::int_),
database_types_testcase("types_int", "field_zerofill", "regular", std::uint32_t(20), field_type::int_, flags_zerofill), database_types_testcase("types_int", "field_zerofill", "regular", std::uint32_t(20), field_type::int_, flags_zerofill),
database_types_testcase("types_int", "field_zerofill", "min", std::uint32_t(0), field_type::int_, flags_zerofill) database_types_testcase("types_int", "field_zerofill", "min", std::uint32_t(0), field_type::int_, flags_zerofill)
), test_name_generator); ), test_name_generator);
INSTANTIATE_TEST_SUITE_P(BIGINT, DatabaseTypesTest, Values( INSTANTIATE_TEST_SUITE_P(BIGINT, DatabaseTypesTest, Values(
database_types_testcase("types_bigint", "field_signed", "regular", std::int64_t(20), field_type::bigint), database_types_testcase("types_bigint", "field_signed", "regular", std::int64_t(20), field_type::bigint),
database_types_testcase("types_bigint", "field_signed", "negative", std::int64_t(-20), field_type::bigint), database_types_testcase("types_bigint", "field_signed", "negative", std::int64_t(-20), field_type::bigint),
database_types_testcase("types_bigint", "field_signed", "min", std::int64_t(-0x8000000000000000), field_type::bigint), database_types_testcase("types_bigint", "field_signed", "min", std::int64_t(-0x8000000000000000), field_type::bigint),
database_types_testcase("types_bigint", "field_signed", "max", std::int64_t(0x7fffffffffffffff), field_type::bigint), database_types_testcase("types_bigint", "field_signed", "max", std::int64_t(0x7fffffffffffffff), field_type::bigint),
database_types_testcase("types_bigint", "field_unsigned", "regular", std::uint64_t(20), field_type::bigint, flags_unsigned), database_types_testcase("types_bigint", "field_unsigned", "regular", std::uint64_t(20), field_type::bigint, flags_unsigned),
database_types_testcase("types_bigint", "field_unsigned", "min", std::uint64_t(0), field_type::bigint, flags_unsigned), database_types_testcase("types_bigint", "field_unsigned", "min", std::uint64_t(0), field_type::bigint, flags_unsigned),
database_types_testcase("types_bigint", "field_unsigned", "max", std::uint64_t(0xffffffffffffffff), field_type::bigint, flags_unsigned), database_types_testcase("types_bigint", "field_unsigned", "max", std::uint64_t(0xffffffffffffffff), field_type::bigint, flags_unsigned),
database_types_testcase("types_bigint", "field_width", "regular", std::int64_t(20), field_type::bigint), database_types_testcase("types_bigint", "field_width", "regular", std::int64_t(20), field_type::bigint),
database_types_testcase("types_bigint", "field_width", "negative", std::int64_t(-20), field_type::bigint), database_types_testcase("types_bigint", "field_width", "negative", std::int64_t(-20), field_type::bigint),
database_types_testcase("types_bigint", "field_zerofill", "regular", std::uint64_t(20), field_type::bigint, flags_zerofill), database_types_testcase("types_bigint", "field_zerofill", "regular", std::uint64_t(20), field_type::bigint, flags_zerofill),
database_types_testcase("types_bigint", "field_zerofill", "min", std::uint64_t(0), field_type::bigint, flags_zerofill) database_types_testcase("types_bigint", "field_zerofill", "min", std::uint64_t(0), field_type::bigint, flags_zerofill)
), test_name_generator); ), test_name_generator);
// Floating point // Floating point
INSTANTIATE_TEST_SUITE_P(FLOAT, DatabaseTypesTest, Values( INSTANTIATE_TEST_SUITE_P(FLOAT, DatabaseTypesTest, Values(
database_types_testcase("types_float", "field_signed", "zero", 0.0f, field_type::float_, no_flags, 31), database_types_testcase("types_float", "field_signed", "zero", 0.0f, field_type::float_, no_flags, 31),
database_types_testcase("types_float", "field_signed", "int_positive", 4.0f, field_type::float_, no_flags, 31), database_types_testcase("types_float", "field_signed", "int_positive", 4.0f, field_type::float_, no_flags, 31),
database_types_testcase("types_float", "field_signed", "int_negative", -4.0f, field_type::float_, no_flags, 31), database_types_testcase("types_float", "field_signed", "int_negative", -4.0f, field_type::float_, no_flags, 31),
database_types_testcase("types_float", "field_signed", "fractional_positive", 4.2f, field_type::float_, no_flags, 31), database_types_testcase("types_float", "field_signed", "fractional_positive", 4.2f, field_type::float_, no_flags, 31),
database_types_testcase("types_float", "field_signed", "fractional_negative", -4.2f, field_type::float_, no_flags, 31), database_types_testcase("types_float", "field_signed", "fractional_negative", -4.2f, field_type::float_, no_flags, 31),
database_types_testcase("types_float", "field_signed", "positive_exp_positive_int", 3e20f, field_type::float_, no_flags, 31), database_types_testcase("types_float", "field_signed", "positive_exp_positive_int", 3e20f, field_type::float_, no_flags, 31),
database_types_testcase("types_float", "field_signed", "positive_exp_negative_int", -3e20f, field_type::float_, no_flags, 31), database_types_testcase("types_float", "field_signed", "positive_exp_negative_int", -3e20f, field_type::float_, no_flags, 31),
database_types_testcase("types_float", "field_signed", "positive_exp_positive_fractional", 3.14e20f, field_type::float_, no_flags, 31), database_types_testcase("types_float", "field_signed", "positive_exp_positive_fractional", 3.14e20f, field_type::float_, no_flags, 31),
database_types_testcase("types_float", "field_signed", "positive_exp_negative_fractional", -3.14e20f, field_type::float_, no_flags, 31), database_types_testcase("types_float", "field_signed", "positive_exp_negative_fractional", -3.14e20f, field_type::float_, no_flags, 31),
database_types_testcase("types_float", "field_signed", "negative_exp_positive_fractional", 3.14e-20f, field_type::float_, no_flags, 31), database_types_testcase("types_float", "field_signed", "negative_exp_positive_fractional", 3.14e-20f, field_type::float_, no_flags, 31),
database_types_testcase("types_float", "field_unsigned", "zero", 0.0f, field_type::float_, flags_unsigned, 31), database_types_testcase("types_float", "field_unsigned", "zero", 0.0f, field_type::float_, flags_unsigned, 31),
database_types_testcase("types_float", "field_unsigned", "fractional_positive", 4.2f, field_type::float_, flags_unsigned, 31), database_types_testcase("types_float", "field_unsigned", "fractional_positive", 4.2f, field_type::float_, flags_unsigned, 31),
database_types_testcase("types_float", "field_width", "zero", 0.0f, field_type::float_, no_flags, 10), database_types_testcase("types_float", "field_width", "zero", 0.0f, field_type::float_, no_flags, 10),
database_types_testcase("types_float", "field_width", "fractional_positive", 4.2f, field_type::float_, no_flags, 10), database_types_testcase("types_float", "field_width", "fractional_positive", 4.2f, field_type::float_, no_flags, 10),
database_types_testcase("types_float", "field_width", "fractional_negative", -4.2f, field_type::float_, no_flags, 10), database_types_testcase("types_float", "field_width", "fractional_negative", -4.2f, field_type::float_, no_flags, 10),
database_types_testcase("types_float", "field_zerofill", "zero", 0.0f, field_type::float_, flags_zerofill, 31), database_types_testcase("types_float", "field_zerofill", "zero", 0.0f, field_type::float_, flags_zerofill, 31),
database_types_testcase("types_float", "field_zerofill", "fractional_positive", 4.2f, field_type::float_, flags_zerofill, 31), database_types_testcase("types_float", "field_zerofill", "fractional_positive", 4.2f, field_type::float_, flags_zerofill, 31),
database_types_testcase("types_float", "field_zerofill", "positive_exp_positive_fractional", 3.14e20f, field_type::float_, flags_zerofill, 31), database_types_testcase("types_float", "field_zerofill", "positive_exp_positive_fractional", 3.14e20f, field_type::float_, flags_zerofill, 31),
database_types_testcase("types_float", "field_zerofill", "negative_exp_positive_fractional", 3.14e-20f, field_type::float_, flags_zerofill, 31) database_types_testcase("types_float", "field_zerofill", "negative_exp_positive_fractional", 3.14e-20f, field_type::float_, flags_zerofill, 31)
), test_name_generator); ), test_name_generator);
INSTANTIATE_TEST_SUITE_P(DOUBLE, DatabaseTypesTest, Values( INSTANTIATE_TEST_SUITE_P(DOUBLE, DatabaseTypesTest, Values(
database_types_testcase("types_double", "field_signed", "zero", 0.0, field_type::double_, no_flags, 31), database_types_testcase("types_double", "field_signed", "zero", 0.0, field_type::double_, no_flags, 31),
database_types_testcase("types_double", "field_signed", "int_positive", 4.0, field_type::double_, no_flags, 31), database_types_testcase("types_double", "field_signed", "int_positive", 4.0, field_type::double_, no_flags, 31),
database_types_testcase("types_double", "field_signed", "int_negative", -4.0, field_type::double_, no_flags, 31), database_types_testcase("types_double", "field_signed", "int_negative", -4.0, field_type::double_, no_flags, 31),
database_types_testcase("types_double", "field_signed", "fractional_positive", 4.2, field_type::double_, no_flags, 31), database_types_testcase("types_double", "field_signed", "fractional_positive", 4.2, field_type::double_, no_flags, 31),
database_types_testcase("types_double", "field_signed", "fractional_negative", -4.2, field_type::double_, no_flags, 31), database_types_testcase("types_double", "field_signed", "fractional_negative", -4.2, field_type::double_, no_flags, 31),
database_types_testcase("types_double", "field_signed", "positive_exp_positive_int", 3e200, field_type::double_, no_flags, 31), database_types_testcase("types_double", "field_signed", "positive_exp_positive_int", 3e200, field_type::double_, no_flags, 31),
database_types_testcase("types_double", "field_signed", "positive_exp_negative_int", -3e200, field_type::double_, no_flags, 31), database_types_testcase("types_double", "field_signed", "positive_exp_negative_int", -3e200, field_type::double_, no_flags, 31),
database_types_testcase("types_double", "field_signed", "positive_exp_positive_fractional", 3.14e200, field_type::double_, no_flags, 31), database_types_testcase("types_double", "field_signed", "positive_exp_positive_fractional", 3.14e200, field_type::double_, no_flags, 31),
database_types_testcase("types_double", "field_signed", "positive_exp_negative_fractional", -3.14e200, field_type::double_, no_flags, 31), database_types_testcase("types_double", "field_signed", "positive_exp_negative_fractional", -3.14e200, field_type::double_, no_flags, 31),
database_types_testcase("types_double", "field_signed", "negative_exp_positive_fractional", 3.14e-200, field_type::double_, no_flags, 31), database_types_testcase("types_double", "field_signed", "negative_exp_positive_fractional", 3.14e-200, field_type::double_, no_flags, 31),
database_types_testcase("types_double", "field_unsigned", "zero", 0.0, field_type::double_, flags_unsigned, 31), database_types_testcase("types_double", "field_unsigned", "zero", 0.0, field_type::double_, flags_unsigned, 31),
database_types_testcase("types_double", "field_unsigned", "fractional_positive", 4.2, field_type::double_, flags_unsigned, 31), database_types_testcase("types_double", "field_unsigned", "fractional_positive", 4.2, field_type::double_, flags_unsigned, 31),
database_types_testcase("types_double", "field_width", "zero", 0.0, field_type::double_, no_flags, 10), database_types_testcase("types_double", "field_width", "zero", 0.0, field_type::double_, no_flags, 10),
database_types_testcase("types_double", "field_width", "fractional_positive", 4.2, field_type::double_, no_flags, 10), database_types_testcase("types_double", "field_width", "fractional_positive", 4.2, field_type::double_, no_flags, 10),
database_types_testcase("types_double", "field_width", "fractional_negative", -4.2, field_type::double_, no_flags, 10), database_types_testcase("types_double", "field_width", "fractional_negative", -4.2, field_type::double_, no_flags, 10),
database_types_testcase("types_double", "field_zerofill", "zero", 0.0, field_type::double_, flags_zerofill, 31), database_types_testcase("types_double", "field_zerofill", "zero", 0.0, field_type::double_, flags_zerofill, 31),
database_types_testcase("types_double", "field_zerofill", "fractional_positive", 4.2, field_type::double_, flags_zerofill, 31), database_types_testcase("types_double", "field_zerofill", "fractional_positive", 4.2, field_type::double_, flags_zerofill, 31),
database_types_testcase("types_double", "field_zerofill", "positive_exp_positive_fractional", 3.14e200, field_type::double_, flags_zerofill, 31), database_types_testcase("types_double", "field_zerofill", "positive_exp_positive_fractional", 3.14e200, field_type::double_, flags_zerofill, 31),
database_types_testcase("types_double", "field_zerofill", "negative_exp_positive_fractional", 3.14e-200, field_type::double_, flags_zerofill, 31) database_types_testcase("types_double", "field_zerofill", "negative_exp_positive_fractional", 3.14e-200, field_type::double_, flags_zerofill, 31)
), test_name_generator); ), test_name_generator);
// Dates and times // Dates and times
INSTANTIATE_TEST_SUITE_P(DATE, DatabaseTypesTest, Values( INSTANTIATE_TEST_SUITE_P(DATE, DatabaseTypesTest, Values(
database_types_testcase("types_date", "field_date", "regular", 2010_y/3/28_d, field_type::date), database_types_testcase("types_date", "field_date", "regular", 2010_y/3/28_d, field_type::date),
database_types_testcase("types_date", "field_date", "leap", 1788_y/2/29_d, field_type::date), database_types_testcase("types_date", "field_date", "leap", 1788_y/2/29_d, field_type::date),
database_types_testcase("types_date", "field_date", "min", 1000_y/1/1_d, field_type::date), database_types_testcase("types_date", "field_date", "min", 1000_y/1/1_d, field_type::date),
database_types_testcase("types_date", "field_date", "max", 9999_y/12/31_d, field_type::date) database_types_testcase("types_date", "field_date", "max", 9999_y/12/31_d, field_type::date)
), test_name_generator); ), test_name_generator);
// Infrastructure to generate DATETIME and TIMESTAMP test cases // Infrastructure to generate DATETIME and TIMESTAMP test cases
@ -302,182 +302,182 @@ INSTANTIATE_TEST_SUITE_P(DATE, DatabaseTypesTest, Values(
// Given a number of microseconds, removes the least significant part according to decimals // Given a number of microseconds, removes the least significant part according to decimals
int round_micros(int input, int decimals) int round_micros(int input, int decimals)
{ {
assert(decimals >= 0 && decimals <= 6); assert(decimals >= 0 && decimals <= 6);
if (decimals == 0) return 0; if (decimals == 0) return 0;
auto modulus = static_cast<int>(std::pow(10, 6 - decimals)); auto modulus = static_cast<int>(std::pow(10, 6 - decimals));
return (input / modulus) * modulus; return (input / modulus) * modulus;
} }
std::chrono::microseconds round_micros(std::chrono::microseconds input, int decimals) std::chrono::microseconds round_micros(std::chrono::microseconds input, int decimals)
{ {
return std::chrono::microseconds(round_micros(static_cast<int>(input.count()), decimals)); return std::chrono::microseconds(round_micros(static_cast<int>(input.count()), decimals));
} }
std::pair<std::string, datetime> datetime_from_id(std::bitset<4> id, int decimals) std::pair<std::string, datetime> datetime_from_id(std::bitset<4> id, int decimals)
{ {
// id represents which components (h, m, s, u) should the test case have // id represents which components (h, m, s, u) should the test case have
constexpr struct constexpr struct
{ {
char letter; char letter;
std::chrono::microseconds offset; std::chrono::microseconds offset;
} bit_meaning [] = { } bit_meaning [] = {
{ 'h', std::chrono::hours(23) }, // bit 0 { 'h', std::chrono::hours(23) }, // bit 0
{ 'm', std::chrono::minutes(1) }, { 'm', std::chrono::minutes(1) },
{ 's', std::chrono::seconds(50) }, { 's', std::chrono::seconds(50) },
{ 'u', std::chrono::microseconds(123456) } { 'u', std::chrono::microseconds(123456) }
}; };
std::string name; std::string name;
datetime dt = makedt(2010, 5, 2); // components all tests have datetime dt = makedt(2010, 5, 2); // components all tests have
for (std::size_t i = 0; i < id.size(); ++i) for (std::size_t i = 0; i < id.size(); ++i)
{ {
if (id[i]) // component present if (id[i]) // component present
{ {
char letter = bit_meaning[i].letter; char letter = bit_meaning[i].letter;
auto offset = bit_meaning[i].offset; auto offset = bit_meaning[i].offset;
name.push_back(letter); // add to name name.push_back(letter); // add to name
dt += letter == 'u' ? round_micros(offset, decimals) : offset; // add to value dt += letter == 'u' ? round_micros(offset, decimals) : offset; // add to value
} }
} }
if (name.empty()) name = "date"; if (name.empty()) name = "date";
return {name, dt}; return {name, dt};
} }
database_types_testcase create_datetime_testcase( database_types_testcase create_datetime_testcase(
int decimals, int decimals,
std::string id, std::string id,
value expected, value expected,
field_type type field_type type
) )
{ {
static std::unordered_map<field_type, const char*> table_map { static std::unordered_map<field_type, const char*> table_map {
{ field_type::datetime, "types_datetime" }, { field_type::datetime, "types_datetime" },
{ field_type::timestamp, "types_timestamp" }, { field_type::timestamp, "types_timestamp" },
{ field_type::time, "types_time" } { field_type::time, "types_time" }
}; };
// Inconsistencies between Maria and MySQL in the unsigned flag // Inconsistencies between Maria and MySQL in the unsigned flag
// we don't really care here about signedness of timestamps // we don't really care here about signedness of timestamps
return database_types_testcase( return database_types_testcase(
table_map.at(type), table_map.at(type),
"field_" + std::to_string(decimals), "field_" + std::to_string(decimals),
std::move(id), std::move(id),
expected, expected,
type, type,
no_flags, no_flags,
decimals, decimals,
flagsvec{ &field_metadata::is_unsigned } flagsvec{ &field_metadata::is_unsigned }
); );
} }
std::pair<std::string, boost::mysql::time> time_from_id(std::bitset<6> id, int decimals) std::pair<std::string, boost::mysql::time> time_from_id(std::bitset<6> id, int decimals)
{ {
// id represents which components (h, m, s, u) should the test case have // id represents which components (h, m, s, u) should the test case have
constexpr struct constexpr struct
{ {
char letter; char letter;
std::chrono::microseconds offset; std::chrono::microseconds offset;
} bit_meaning [] = { } bit_meaning [] = {
{ 'n', std::chrono::hours(0) }, // bit 0: negative bit { 'n', std::chrono::hours(0) }, // bit 0: negative bit
{ 'd', std::chrono::hours(48) }, // bit 1 { 'd', std::chrono::hours(48) }, // bit 1
{ 'h', std::chrono::hours(23) }, // bit 2 { 'h', std::chrono::hours(23) }, // bit 2
{ 'm', std::chrono::minutes(1) }, { 'm', std::chrono::minutes(1) },
{ 's', std::chrono::seconds(50) }, { 's', std::chrono::seconds(50) },
{ 'u', std::chrono::microseconds(123456) } { 'u', std::chrono::microseconds(123456) }
}; };
std::string name; std::string name;
boost::mysql::time t {0}; boost::mysql::time t {0};
for (std::size_t i = 1; i < id.size(); ++i) for (std::size_t i = 1; i < id.size(); ++i)
{ {
if (id[i]) // component present if (id[i]) // component present
{ {
char letter = bit_meaning[i].letter; char letter = bit_meaning[i].letter;
auto offset = bit_meaning[i].offset; auto offset = bit_meaning[i].offset;
name.push_back(letter); // add to name name.push_back(letter); // add to name
t += letter == 'u' ? round_micros(offset, decimals) : offset; // add to value t += letter == 'u' ? round_micros(offset, decimals) : offset; // add to value
} }
} }
if (name.empty()) name = "zero"; if (name.empty()) name = "zero";
if (id[0]) // bit sign if (id[0]) // bit sign
{ {
name = "negative_" + name; name = "negative_" + name;
t = -t; t = -t;
} }
return {name, t}; return {name, t};
} }
// shared between DATETIME and TIMESTAMP // shared between DATETIME and TIMESTAMP
std::vector<database_types_testcase> generate_common_datetime_cases( std::vector<database_types_testcase> generate_common_datetime_cases(
field_type type field_type type
) )
{ {
std::vector<database_types_testcase> res; std::vector<database_types_testcase> res;
for (int decimals = 0; decimals <= 6; ++decimals) for (int decimals = 0; decimals <= 6; ++decimals)
{ {
// Regular values // Regular values
auto max_int_id = static_cast<std::size_t>(std::pow(2, 4)); // 4 components can be varied auto max_int_id = static_cast<std::size_t>(std::pow(2, 4)); // 4 components can be varied
for (std::size_t int_id = 0; int_id < max_int_id; ++int_id) for (std::size_t int_id = 0; int_id < max_int_id; ++int_id)
{ {
std::bitset<4> bitset_id (int_id); std::bitset<4> bitset_id (int_id);
if (bitset_id[3] && decimals == 0) continue; // cases with micros don't make sense for fields with no decimals if (bitset_id[3] && decimals == 0) continue; // cases with micros don't make sense for fields with no decimals
auto [id, value] = datetime_from_id(int_id, decimals); auto [id, value] = datetime_from_id(int_id, decimals);
res.push_back(create_datetime_testcase(decimals, move(id), value, type)); res.push_back(create_datetime_testcase(decimals, move(id), value, type));
} }
} }
return res; return res;
} }
std::vector<database_types_testcase> generate_datetime_cases() std::vector<database_types_testcase> generate_datetime_cases()
{ {
auto res = generate_common_datetime_cases(field_type::datetime); auto res = generate_common_datetime_cases(field_type::datetime);
// min and max // min and max
for (int decimals = 0; decimals <= 6; ++decimals) for (int decimals = 0; decimals <= 6; ++decimals)
{ {
res.push_back(create_datetime_testcase(decimals, "min", res.push_back(create_datetime_testcase(decimals, "min",
makedt(1000, 1, 1), field_type::datetime)); makedt(1000, 1, 1), field_type::datetime));
res.push_back(create_datetime_testcase(decimals, "max", res.push_back(create_datetime_testcase(decimals, "max",
makedt(9999, 12, 31, 23, 59, 59, round_micros(999999, decimals)), field_type::datetime)); makedt(9999, 12, 31, 23, 59, 59, round_micros(999999, decimals)), field_type::datetime));
} }
return res; return res;
} }
std::vector<database_types_testcase> generate_timestamp_cases() std::vector<database_types_testcase> generate_timestamp_cases()
{ {
return generate_common_datetime_cases(field_type::timestamp); return generate_common_datetime_cases(field_type::timestamp);
} }
std::vector<database_types_testcase> generate_time_cases() std::vector<database_types_testcase> generate_time_cases()
{ {
std::vector<database_types_testcase> res; std::vector<database_types_testcase> res;
for (int decimals = 0; decimals <= 6; ++decimals) for (int decimals = 0; decimals <= 6; ++decimals)
{ {
// Regular values // Regular values
auto max_int_id = static_cast<std::size_t>(std::pow(2, 6)); // 6 components can be varied auto max_int_id = static_cast<std::size_t>(std::pow(2, 6)); // 6 components can be varied
for (std::size_t int_id = 0; int_id < max_int_id; ++int_id) for (std::size_t int_id = 0; int_id < max_int_id; ++int_id)
{ {
std::bitset<6> bitset_id (int_id); std::bitset<6> bitset_id (int_id);
if (bitset_id[5] && decimals == 0) continue; // cases with micros don't make sense for fields with no decimals if (bitset_id[5] && decimals == 0) continue; // cases with micros don't make sense for fields with no decimals
if (bitset_id.to_ulong() == 1) continue; // negative zero does not make sense if (bitset_id.to_ulong() == 1) continue; // negative zero does not make sense
auto [id, value] = time_from_id(int_id, decimals); auto [id, value] = time_from_id(int_id, decimals);
res.push_back(create_datetime_testcase(decimals, move(id), value, field_type::time)); res.push_back(create_datetime_testcase(decimals, move(id), value, field_type::time));
} }
// min and max // min and max
auto max_value = decimals == 0 ? maket(838, 59, 59) : maket(838, 59, 58, round_micros(999999, decimals)); auto max_value = decimals == 0 ? maket(838, 59, 59) : maket(838, 59, 58, round_micros(999999, decimals));
res.push_back(create_datetime_testcase(decimals, "min", -max_value, field_type::time)); res.push_back(create_datetime_testcase(decimals, "min", -max_value, field_type::time));
res.push_back(create_datetime_testcase(decimals, "max", max_value, field_type::time)); res.push_back(create_datetime_testcase(decimals, "max", max_value, field_type::time));
} }
return res; return res;
} }
@ -486,98 +486,98 @@ INSTANTIATE_TEST_SUITE_P(TIMESTAMP, DatabaseTypesTest, ValuesIn(generate_timesta
INSTANTIATE_TEST_SUITE_P(TIME, DatabaseTypesTest, ValuesIn(generate_time_cases()), test_name_generator); INSTANTIATE_TEST_SUITE_P(TIME, DatabaseTypesTest, ValuesIn(generate_time_cases()), test_name_generator);
INSTANTIATE_TEST_SUITE_P(YEAR, DatabaseTypesTest, Values( INSTANTIATE_TEST_SUITE_P(YEAR, DatabaseTypesTest, Values(
database_types_testcase("types_year", "field_default", "regular", std::uint32_t(2019), field_type::year, flags_zerofill), database_types_testcase("types_year", "field_default", "regular", std::uint32_t(2019), field_type::year, flags_zerofill),
database_types_testcase("types_year", "field_default", "min", std::uint32_t(1901), field_type::year, flags_zerofill), database_types_testcase("types_year", "field_default", "min", std::uint32_t(1901), field_type::year, flags_zerofill),
database_types_testcase("types_year", "field_default", "max", std::uint32_t(2155), field_type::year, flags_zerofill), database_types_testcase("types_year", "field_default", "max", std::uint32_t(2155), field_type::year, flags_zerofill),
database_types_testcase("types_year", "field_default", "zero", std::uint32_t(0), field_type::year, flags_zerofill) database_types_testcase("types_year", "field_default", "zero", std::uint32_t(0), field_type::year, flags_zerofill)
), test_name_generator); ), test_name_generator);
INSTANTIATE_TEST_SUITE_P(STRING, DatabaseTypesTest, Values( INSTANTIATE_TEST_SUITE_P(STRING, DatabaseTypesTest, Values(
database_types_testcase("types_string", "field_char", "regular", "test_char", field_type::char_), database_types_testcase("types_string", "field_char", "regular", "test_char", field_type::char_),
database_types_testcase("types_string", "field_char", "utf8", u8"\u00F1", field_type::char_), database_types_testcase("types_string", "field_char", "utf8", u8"\u00F1", field_type::char_),
database_types_testcase("types_string", "field_char", "empty", "", field_type::char_), database_types_testcase("types_string", "field_char", "empty", "", field_type::char_),
database_types_testcase("types_string", "field_varchar", "regular", "test_varchar", field_type::varchar), database_types_testcase("types_string", "field_varchar", "regular", "test_varchar", field_type::varchar),
database_types_testcase("types_string", "field_varchar", "utf8", u8"\u00D1", field_type::varchar), database_types_testcase("types_string", "field_varchar", "utf8", u8"\u00D1", field_type::varchar),
database_types_testcase("types_string", "field_varchar", "empty", "", field_type::varchar), database_types_testcase("types_string", "field_varchar", "empty", "", field_type::varchar),
database_types_testcase("types_string", "field_tinytext", "regular", "test_tinytext", field_type::text), database_types_testcase("types_string", "field_tinytext", "regular", "test_tinytext", field_type::text),
database_types_testcase("types_string", "field_tinytext", "utf8", u8"\u00e1", field_type::text), database_types_testcase("types_string", "field_tinytext", "utf8", u8"\u00e1", field_type::text),
database_types_testcase("types_string", "field_tinytext", "empty", "", field_type::text), database_types_testcase("types_string", "field_tinytext", "empty", "", field_type::text),
database_types_testcase("types_string", "field_text", "regular", "test_text", field_type::text), database_types_testcase("types_string", "field_text", "regular", "test_text", field_type::text),
database_types_testcase("types_string", "field_text", "utf8", u8"\u00e9", field_type::text), database_types_testcase("types_string", "field_text", "utf8", u8"\u00e9", field_type::text),
database_types_testcase("types_string", "field_text", "empty", "", field_type::text), database_types_testcase("types_string", "field_text", "empty", "", field_type::text),
database_types_testcase("types_string", "field_mediumtext", "regular", "test_mediumtext", field_type::text), database_types_testcase("types_string", "field_mediumtext", "regular", "test_mediumtext", field_type::text),
database_types_testcase("types_string", "field_mediumtext", "utf8", u8"\u00ed", field_type::text), database_types_testcase("types_string", "field_mediumtext", "utf8", u8"\u00ed", field_type::text),
database_types_testcase("types_string", "field_mediumtext", "empty", "", field_type::text), database_types_testcase("types_string", "field_mediumtext", "empty", "", field_type::text),
database_types_testcase("types_string", "field_longtext", "regular", "test_longtext", field_type::text), database_types_testcase("types_string", "field_longtext", "regular", "test_longtext", field_type::text),
database_types_testcase("types_string", "field_longtext", "utf8", u8"\u00f3", field_type::text), database_types_testcase("types_string", "field_longtext", "utf8", u8"\u00f3", field_type::text),
database_types_testcase("types_string", "field_longtext", "empty", "", field_type::text), database_types_testcase("types_string", "field_longtext", "empty", "", field_type::text),
database_types_testcase("types_string", "field_enum", "regular", "red", field_type::enum_), database_types_testcase("types_string", "field_enum", "regular", "red", field_type::enum_),
database_types_testcase("types_string", "field_set", "regular", "red,green", field_type::set), database_types_testcase("types_string", "field_set", "regular", "red,green", field_type::set),
database_types_testcase("types_string", "field_set", "empty", "", field_type::set) database_types_testcase("types_string", "field_set", "empty", "", field_type::set)
), test_name_generator); ), test_name_generator);
INSTANTIATE_TEST_SUITE_P(BINARY, DatabaseTypesTest, Values( INSTANTIATE_TEST_SUITE_P(BINARY, DatabaseTypesTest, Values(
// BINARY values get padded with zeros to the declared length // BINARY values get padded with zeros to the declared length
database_types_testcase("types_binary", "field_binary", "regular", makesv("\0_binary\0\0"), field_type::binary), database_types_testcase("types_binary", "field_binary", "regular", makesv("\0_binary\0\0"), field_type::binary),
database_types_testcase("types_binary", "field_binary", "nonascii", makesv("\0\xff" "\0\0\0\0\0\0\0\0"), field_type::binary), database_types_testcase("types_binary", "field_binary", "nonascii", makesv("\0\xff" "\0\0\0\0\0\0\0\0"), field_type::binary),
database_types_testcase("types_binary", "field_binary", "empty", makesv("\0\0\0\0\0\0\0\0\0\0"), field_type::binary), database_types_testcase("types_binary", "field_binary", "empty", makesv("\0\0\0\0\0\0\0\0\0\0"), field_type::binary),
database_types_testcase("types_binary", "field_varbinary", "regular", makesv("\0_varbinary"), field_type::varbinary), database_types_testcase("types_binary", "field_varbinary", "regular", makesv("\0_varbinary"), field_type::varbinary),
database_types_testcase("types_binary", "field_varbinary", "nonascii", makesv("\1\xfe"), field_type::varbinary), database_types_testcase("types_binary", "field_varbinary", "nonascii", makesv("\1\xfe"), field_type::varbinary),
database_types_testcase("types_binary", "field_varbinary", "empty", "", field_type::varbinary), database_types_testcase("types_binary", "field_varbinary", "empty", "", field_type::varbinary),
database_types_testcase("types_binary", "field_tinyblob", "regular", makesv("\0_tinyblob"), field_type::blob), database_types_testcase("types_binary", "field_tinyblob", "regular", makesv("\0_tinyblob"), field_type::blob),
database_types_testcase("types_binary", "field_tinyblob", "nonascii", makesv("\2\xfd"), field_type::blob), database_types_testcase("types_binary", "field_tinyblob", "nonascii", makesv("\2\xfd"), field_type::blob),
database_types_testcase("types_binary", "field_tinyblob", "empty", "", field_type::blob), database_types_testcase("types_binary", "field_tinyblob", "empty", "", field_type::blob),
database_types_testcase("types_binary", "field_blob", "regular", makesv("\0_blob"), field_type::blob), database_types_testcase("types_binary", "field_blob", "regular", makesv("\0_blob"), field_type::blob),
database_types_testcase("types_binary", "field_blob", "nonascii", makesv("\3\xfc"), field_type::blob), database_types_testcase("types_binary", "field_blob", "nonascii", makesv("\3\xfc"), field_type::blob),
database_types_testcase("types_binary", "field_blob", "empty", "", field_type::blob), database_types_testcase("types_binary", "field_blob", "empty", "", field_type::blob),
database_types_testcase("types_binary", "field_mediumblob", "regular", makesv("\0_mediumblob"), field_type::blob), database_types_testcase("types_binary", "field_mediumblob", "regular", makesv("\0_mediumblob"), field_type::blob),
database_types_testcase("types_binary", "field_mediumblob", "nonascii", makesv("\4\xfb"), field_type::blob), database_types_testcase("types_binary", "field_mediumblob", "nonascii", makesv("\4\xfb"), field_type::blob),
database_types_testcase("types_binary", "field_mediumblob", "empty", "", field_type::blob), database_types_testcase("types_binary", "field_mediumblob", "empty", "", field_type::blob),
database_types_testcase("types_binary", "field_longblob", "regular", makesv("\0_longblob"), field_type::blob), database_types_testcase("types_binary", "field_longblob", "regular", makesv("\0_longblob"), field_type::blob),
database_types_testcase("types_binary", "field_longblob", "nonascii", makesv("\5\xfa"), field_type::blob), database_types_testcase("types_binary", "field_longblob", "nonascii", makesv("\5\xfa"), field_type::blob),
database_types_testcase("types_binary", "field_longblob", "empty", "", field_type::blob) database_types_testcase("types_binary", "field_longblob", "empty", "", field_type::blob)
), test_name_generator); ), test_name_generator);
// These types do not have a more concrete representation in the library yet. // These types do not have a more concrete representation in the library yet.
// Check we get them as strings and we get the metadata correctly // Check we get them as strings and we get the metadata correctly
std::uint8_t geometry_value [] = { std::uint8_t geometry_value [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x40 0x00, 0x00, 0x00, 0x00, 0x00, 0x40
}; };
INSTANTIATE_TEST_SUITE_P(NOT_IMPLEMENTED_TYPES, DatabaseTypesTest, Values( INSTANTIATE_TEST_SUITE_P(NOT_IMPLEMENTED_TYPES, DatabaseTypesTest, Values(
database_types_testcase("types_not_implemented", "field_bit", "regular", "\xfe", field_type::bit, flags_unsigned), database_types_testcase("types_not_implemented", "field_bit", "regular", "\xfe", field_type::bit, flags_unsigned),
database_types_testcase("types_not_implemented", "field_decimal", "regular", "300", field_type::decimal), database_types_testcase("types_not_implemented", "field_decimal", "regular", "300", field_type::decimal),
database_types_testcase("types_not_implemented", "field_geometry", "regular", makesv(geometry_value), field_type::geometry) database_types_testcase("types_not_implemented", "field_geometry", "regular", makesv(geometry_value), field_type::geometry)
), test_name_generator); ), test_name_generator);
// Tests for certain metadata flags and NULL values // Tests for certain metadata flags and NULL values
INSTANTIATE_TEST_SUITE_P(METADATA_FLAGS, DatabaseTypesTest, Values( INSTANTIATE_TEST_SUITE_P(METADATA_FLAGS, DatabaseTypesTest, Values(
database_types_testcase("types_flags", "field_timestamp", "default", nullptr, field_type::timestamp, database_types_testcase("types_flags", "field_timestamp", "default", nullptr, field_type::timestamp,
flagsvec{&field_metadata::is_set_to_now_on_update}, 0, flagsvec{&field_metadata::is_unsigned}), flagsvec{&field_metadata::is_set_to_now_on_update}, 0, flagsvec{&field_metadata::is_unsigned}),
database_types_testcase("types_flags", "field_primary_key", "default", std::int32_t(50), field_type::int_, database_types_testcase("types_flags", "field_primary_key", "default", std::int32_t(50), field_type::int_,
flagsvec{&field_metadata::is_primary_key, &field_metadata::is_not_null, flagsvec{&field_metadata::is_primary_key, &field_metadata::is_not_null,
&field_metadata::is_auto_increment}), &field_metadata::is_auto_increment}),
database_types_testcase("types_flags", "field_not_null", "default", "char", field_type::char_, database_types_testcase("types_flags", "field_not_null", "default", "char", field_type::char_,
flagsvec{&field_metadata::is_not_null}), flagsvec{&field_metadata::is_not_null}),
database_types_testcase("types_flags", "field_unique", "default", std::int32_t(21), field_type::int_, database_types_testcase("types_flags", "field_unique", "default", std::int32_t(21), field_type::int_,
flagsvec{&field_metadata::is_unique_key}), flagsvec{&field_metadata::is_unique_key}),
database_types_testcase("types_flags", "field_indexed", "default", std::int32_t(42), field_type::int_, database_types_testcase("types_flags", "field_indexed", "default", std::int32_t(42), field_type::int_,
flagsvec{&field_metadata::is_multiple_key}) flagsvec{&field_metadata::is_multiple_key})
), test_name_generator); ), test_name_generator);

View File

@ -30,25 +30,25 @@ INSERT INTO updates_table (field_varchar, field_int)
VALUES ('f0', 42), ('f1', 43), ('fnull', NULL); VALUES ('f0', 42), ('f1', 43), ('fnull', NULL);
CREATE TABLE empty_table ( CREATE TABLE empty_table (
id INT, id INT,
field_varchar VARCHAR(255) field_varchar VARCHAR(255)
); );
CREATE TABLE one_row_table ( CREATE TABLE one_row_table (
id INT, id INT,
field_varchar VARCHAR(255) field_varchar VARCHAR(255)
); );
INSERT INTO one_row_table VALUES (1, 'f0'); INSERT INTO one_row_table VALUES (1, 'f0');
CREATE TABLE two_rows_table ( CREATE TABLE two_rows_table (
id INT, id INT,
field_varchar VARCHAR(255) field_varchar VARCHAR(255)
); );
INSERT INTO two_rows_table VALUES (1, 'f0'), (2, 'f1'); INSERT INTO two_rows_table VALUES (1, 'f0'), (2, 'f1');
CREATE TABLE three_rows_table ( CREATE TABLE three_rows_table (
id INT, id INT,
field_varchar VARCHAR(255) field_varchar VARCHAR(255)
); );
INSERT INTO three_rows_table VALUES (1, 'f0'), (2, 'f1'), (3, 'f2'); INSERT INTO three_rows_table VALUES (1, 'f0'), (2, 'f1'), (3, 'f2');
@ -58,168 +58,168 @@ INSERT INTO three_rows_table VALUES (1, 'f0'), (2, 'f1'), (3, 'f2');
-- Integer types -- Integer types
CREATE TABLE types_tinyint( CREATE TABLE types_tinyint(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_signed TINYINT, field_signed TINYINT,
field_unsigned TINYINT UNSIGNED, field_unsigned TINYINT UNSIGNED,
field_width TINYINT(4), field_width TINYINT(4),
field_zerofill TINYINT(6) ZEROFILL field_zerofill TINYINT(6) ZEROFILL
); );
INSERT INTO types_tinyint VALUES INSERT INTO types_tinyint VALUES
("regular", 20, 20, 20, 20), ("regular", 20, 20, 20, 20),
("negative", -20, NULL, -20, NULL), ("negative", -20, NULL, -20, NULL),
("min", -0x80, 0, NULL, 0), ("min", -0x80, 0, NULL, 0),
("max", 0x7f, 0xff, NULL, NULL) ("max", 0x7f, 0xff, NULL, NULL)
; ;
CREATE TABLE types_smallint( CREATE TABLE types_smallint(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_signed SMALLINT, field_signed SMALLINT,
field_unsigned SMALLINT UNSIGNED, field_unsigned SMALLINT UNSIGNED,
field_width SMALLINT(8), field_width SMALLINT(8),
field_zerofill SMALLINT(7) ZEROFILL field_zerofill SMALLINT(7) ZEROFILL
); );
INSERT INTO types_smallint VALUES INSERT INTO types_smallint VALUES
("regular", 20, 20, 20, 20), ("regular", 20, 20, 20, 20),
("negative", -20, NULL, -20, NULL), ("negative", -20, NULL, -20, NULL),
("min", -0x8000, 0, NULL, 0), ("min", -0x8000, 0, NULL, 0),
("max", 0x7fff, 0xffff, NULL, NULL) ("max", 0x7fff, 0xffff, NULL, NULL)
; ;
CREATE TABLE types_mediumint( CREATE TABLE types_mediumint(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_signed MEDIUMINT, field_signed MEDIUMINT,
field_unsigned MEDIUMINT UNSIGNED, field_unsigned MEDIUMINT UNSIGNED,
field_width MEDIUMINT(8), field_width MEDIUMINT(8),
field_zerofill MEDIUMINT(7) ZEROFILL field_zerofill MEDIUMINT(7) ZEROFILL
); );
INSERT INTO types_mediumint VALUES INSERT INTO types_mediumint VALUES
("regular", 20, 20, 20, 20), ("regular", 20, 20, 20, 20),
("negative", -20, NULL, -20, NULL), ("negative", -20, NULL, -20, NULL),
("min", -0x800000, 0, NULL, 0), ("min", -0x800000, 0, NULL, 0),
("max", 0x7fffff, 0xffffff, NULL, NULL) ("max", 0x7fffff, 0xffffff, NULL, NULL)
; ;
CREATE TABLE types_int( CREATE TABLE types_int(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_signed INT, field_signed INT,
field_unsigned INT UNSIGNED, field_unsigned INT UNSIGNED,
field_width INT(8), field_width INT(8),
field_zerofill INT(7) ZEROFILL field_zerofill INT(7) ZEROFILL
); );
INSERT INTO types_int VALUES INSERT INTO types_int VALUES
("regular", 20, 20, 20, 20), ("regular", 20, 20, 20, 20),
("negative", -20, NULL, -20, NULL), ("negative", -20, NULL, -20, NULL),
("min", -0x80000000, 0, NULL, 0), ("min", -0x80000000, 0, NULL, 0),
("max", 0x7fffffff, 0xffffffff, NULL, NULL) ("max", 0x7fffffff, 0xffffffff, NULL, NULL)
; ;
CREATE TABLE types_bigint( CREATE TABLE types_bigint(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_signed BIGINT, field_signed BIGINT,
field_unsigned BIGINT UNSIGNED, field_unsigned BIGINT UNSIGNED,
field_width BIGINT(8), field_width BIGINT(8),
field_zerofill BIGINT(7) ZEROFILL field_zerofill BIGINT(7) ZEROFILL
); );
INSERT INTO types_bigint VALUES INSERT INTO types_bigint VALUES
("regular", 20, 20, 20, 20), ("regular", 20, 20, 20, 20),
("negative", -20, NULL, -20, NULL), ("negative", -20, NULL, -20, NULL),
("min", -0x8000000000000000, 0, NULL, 0), ("min", -0x8000000000000000, 0, NULL, 0),
("max", 0x7fffffffffffffff, 0xffffffffffffffff, NULL, NULL) ("max", 0x7fffffffffffffff, 0xffffffffffffffff, NULL, NULL)
; ;
-- Floating point types -- Floating point types
CREATE TABLE types_float( CREATE TABLE types_float(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_signed FLOAT, field_signed FLOAT,
field_unsigned FLOAT UNSIGNED, field_unsigned FLOAT UNSIGNED,
field_width FLOAT(30, 10), field_width FLOAT(30, 10),
field_zerofill FLOAT(20) ZEROFILL field_zerofill FLOAT(20) ZEROFILL
); );
INSERT INTO types_float VALUES INSERT INTO types_float VALUES
("zero", 0, 0, 0, 0), ("zero", 0, 0, 0, 0),
("int_positive", 4, NULL, NULL, NULL), ("int_positive", 4, NULL, NULL, NULL),
("int_negative", -4, NULL, NULL, NULL), ("int_negative", -4, NULL, NULL, NULL),
("fractional_positive", 4.2, 4.2, 4.2, 4.2), ("fractional_positive", 4.2, 4.2, 4.2, 4.2),
("fractional_negative", -4.2, NULL, -4.2, NULL), ("fractional_negative", -4.2, NULL, -4.2, NULL),
("positive_exp_positive_int", 3e20, NULL, NULL, NULL), ("positive_exp_positive_int", 3e20, NULL, NULL, NULL),
("positive_exp_negative_int", -3e20, NULL, NULL, NULL), ("positive_exp_negative_int", -3e20, NULL, NULL, NULL),
("positive_exp_positive_fractional", 3.14e20, NULL, NULL, 3.14e20), ("positive_exp_positive_fractional", 3.14e20, NULL, NULL, 3.14e20),
("positive_exp_negative_fractional", -3.14e20, NULL, NULL, NULL), ("positive_exp_negative_fractional", -3.14e20, NULL, NULL, NULL),
("negative_exp_positive_fractional", 3.14e-20, NULL, NULL, 3.14e-20) ("negative_exp_positive_fractional", 3.14e-20, NULL, NULL, 3.14e-20)
; ;
CREATE TABLE types_double( CREATE TABLE types_double(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_signed DOUBLE, field_signed DOUBLE,
field_unsigned DOUBLE UNSIGNED, field_unsigned DOUBLE UNSIGNED,
field_width DOUBLE(60, 10), field_width DOUBLE(60, 10),
field_zerofill FLOAT(40) ZEROFILL field_zerofill FLOAT(40) ZEROFILL
); );
INSERT INTO types_double VALUES INSERT INTO types_double VALUES
("zero", 0, 0, 0, 0), ("zero", 0, 0, 0, 0),
("int_positive", 4, NULL, NULL, NULL), ("int_positive", 4, NULL, NULL, NULL),
("int_negative", -4, NULL, NULL, NULL), ("int_negative", -4, NULL, NULL, NULL),
("fractional_positive", 4.2, 4.2, 4.2, 4.2), ("fractional_positive", 4.2, 4.2, 4.2, 4.2),
("fractional_negative", -4.2, NULL, -4.2, NULL), ("fractional_negative", -4.2, NULL, -4.2, NULL),
("positive_exp_positive_int", 3e200, NULL, NULL, NULL), ("positive_exp_positive_int", 3e200, NULL, NULL, NULL),
("positive_exp_negative_int", -3e200, NULL, NULL, NULL), ("positive_exp_negative_int", -3e200, NULL, NULL, NULL),
("positive_exp_positive_fractional", 3.14e200, NULL, NULL, 3.14e200), ("positive_exp_positive_fractional", 3.14e200, NULL, NULL, 3.14e200),
("positive_exp_negative_fractional", -3.14e200, NULL, NULL, NULL), ("positive_exp_negative_fractional", -3.14e200, NULL, NULL, NULL),
("negative_exp_positive_fractional", 3.14e-200, NULL, NULL, 3.14e-200) ("negative_exp_positive_fractional", 3.14e-200, NULL, NULL, 3.14e-200)
; ;
-- Dates and times -- Dates and times
CREATE TABLE types_date( CREATE TABLE types_date(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_date DATE field_date DATE
); );
INSERT INTO types_date VALUES INSERT INTO types_date VALUES
("regular", "2010-03-28"), ("regular", "2010-03-28"),
("leap", "1788-02-29"), ("leap", "1788-02-29"),
("min", "1000-01-01"), ("min", "1000-01-01"),
("max", "9999-12-31") ("max", "9999-12-31")
; ;
CREATE TABLE types_datetime( CREATE TABLE types_datetime(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_0 DATETIME(0), field_0 DATETIME(0),
field_1 DATETIME(1), field_1 DATETIME(1),
field_2 DATETIME(2), field_2 DATETIME(2),
field_3 DATETIME(3), field_3 DATETIME(3),
field_4 DATETIME(4), field_4 DATETIME(4),
field_5 DATETIME(5), field_5 DATETIME(5),
field_6 DATETIME(6) field_6 DATETIME(6)
); );
INSERT INTO types_datetime VALUES INSERT INTO types_datetime VALUES
("date", "2010-05-02 00:00:00", "2010-05-02 00:00:00", "2010-05-02 00:00:00", "2010-05-02 00:00:00", "2010-05-02 00:00:00", "2010-05-02 00:00:00", "2010-05-02 00:00:00"), ("date", "2010-05-02 00:00:00", "2010-05-02 00:00:00", "2010-05-02 00:00:00", "2010-05-02 00:00:00", "2010-05-02 00:00:00", "2010-05-02 00:00:00", "2010-05-02 00:00:00"),
("u", NULL, "2010-05-02 00:00:00.1", "2010-05-02 00:00:00.12", "2010-05-02 00:00:00.123", "2010-05-02 00:00:00.1234", "2010-05-02 00:00:00.12345", "2010-05-02 00:00:00.123456"), ("u", NULL, "2010-05-02 00:00:00.1", "2010-05-02 00:00:00.12", "2010-05-02 00:00:00.123", "2010-05-02 00:00:00.1234", "2010-05-02 00:00:00.12345", "2010-05-02 00:00:00.123456"),
("s", "2010-05-02 00:00:50", "2010-05-02 00:00:50", "2010-05-02 00:00:50", "2010-05-02 00:00:50", "2010-05-02 00:00:50", "2010-05-02 00:00:50", "2010-05-02 00:00:50"), ("s", "2010-05-02 00:00:50", "2010-05-02 00:00:50", "2010-05-02 00:00:50", "2010-05-02 00:00:50", "2010-05-02 00:00:50", "2010-05-02 00:00:50", "2010-05-02 00:00:50"),
("m", "2010-05-02 00:01:00", "2010-05-02 00:01:00", "2010-05-02 00:01:00", "2010-05-02 00:01:00", "2010-05-02 00:01:00", "2010-05-02 00:01:00", "2010-05-02 00:01:00"), ("m", "2010-05-02 00:01:00", "2010-05-02 00:01:00", "2010-05-02 00:01:00", "2010-05-02 00:01:00", "2010-05-02 00:01:00", "2010-05-02 00:01:00", "2010-05-02 00:01:00"),
("hs", "2010-05-02 23:00:50", "2010-05-02 23:00:50", "2010-05-02 23:00:50", "2010-05-02 23:00:50", "2010-05-02 23:00:50", "2010-05-02 23:00:50", "2010-05-02 23:00:50"), ("hs", "2010-05-02 23:00:50", "2010-05-02 23:00:50", "2010-05-02 23:00:50", "2010-05-02 23:00:50", "2010-05-02 23:00:50", "2010-05-02 23:00:50", "2010-05-02 23:00:50"),
("ms", "2010-05-02 00:01:50", "2010-05-02 00:01:50", "2010-05-02 00:01:50", "2010-05-02 00:01:50", "2010-05-02 00:01:50", "2010-05-02 00:01:50", "2010-05-02 00:01:50"), ("ms", "2010-05-02 00:01:50", "2010-05-02 00:01:50", "2010-05-02 00:01:50", "2010-05-02 00:01:50", "2010-05-02 00:01:50", "2010-05-02 00:01:50", "2010-05-02 00:01:50"),
("hu", NULL, "2010-05-02 23:00:00.1", "2010-05-02 23:00:00.12", "2010-05-02 23:00:00.123", "2010-05-02 23:00:00.1234", "2010-05-02 23:00:00.12345", "2010-05-02 23:00:00.123456"), ("hu", NULL, "2010-05-02 23:00:00.1", "2010-05-02 23:00:00.12", "2010-05-02 23:00:00.123", "2010-05-02 23:00:00.1234", "2010-05-02 23:00:00.12345", "2010-05-02 23:00:00.123456"),
("mu", NULL, "2010-05-02 00:01:00.1", "2010-05-02 00:01:00.12", "2010-05-02 00:01:00.123", "2010-05-02 00:01:00.1234", "2010-05-02 00:01:00.12345", "2010-05-02 00:01:00.123456"), ("mu", NULL, "2010-05-02 00:01:00.1", "2010-05-02 00:01:00.12", "2010-05-02 00:01:00.123", "2010-05-02 00:01:00.1234", "2010-05-02 00:01:00.12345", "2010-05-02 00:01:00.123456"),
("hmu", NULL, "2010-05-02 23:01:00.1", "2010-05-02 23:01:00.12", "2010-05-02 23:01:00.123", "2010-05-02 23:01:00.1234", "2010-05-02 23:01:00.12345", "2010-05-02 23:01:00.123456"), ("hmu", NULL, "2010-05-02 23:01:00.1", "2010-05-02 23:01:00.12", "2010-05-02 23:01:00.123", "2010-05-02 23:01:00.1234", "2010-05-02 23:01:00.12345", "2010-05-02 23:01:00.123456"),
("su", NULL, "2010-05-02 00:00:50.1", "2010-05-02 00:00:50.12", "2010-05-02 00:00:50.123", "2010-05-02 00:00:50.1234", "2010-05-02 00:00:50.12345", "2010-05-02 00:00:50.123456"), ("su", NULL, "2010-05-02 00:00:50.1", "2010-05-02 00:00:50.12", "2010-05-02 00:00:50.123", "2010-05-02 00:00:50.1234", "2010-05-02 00:00:50.12345", "2010-05-02 00:00:50.123456"),
("hsu", NULL, "2010-05-02 23:00:50.1", "2010-05-02 23:00:50.12", "2010-05-02 23:00:50.123", "2010-05-02 23:00:50.1234", "2010-05-02 23:00:50.12345", "2010-05-02 23:00:50.123456"), ("hsu", NULL, "2010-05-02 23:00:50.1", "2010-05-02 23:00:50.12", "2010-05-02 23:00:50.123", "2010-05-02 23:00:50.1234", "2010-05-02 23:00:50.12345", "2010-05-02 23:00:50.123456"),
("msu", NULL, "2010-05-02 00:01:50.1", "2010-05-02 00:01:50.12", "2010-05-02 00:01:50.123", "2010-05-02 00:01:50.1234", "2010-05-02 00:01:50.12345", "2010-05-02 00:01:50.123456"), ("msu", NULL, "2010-05-02 00:01:50.1", "2010-05-02 00:01:50.12", "2010-05-02 00:01:50.123", "2010-05-02 00:01:50.1234", "2010-05-02 00:01:50.12345", "2010-05-02 00:01:50.123456"),
("h", "2010-05-02 23:00:00", "2010-05-02 23:00:00", "2010-05-02 23:00:00", "2010-05-02 23:00:00", "2010-05-02 23:00:00", "2010-05-02 23:00:00", "2010-05-02 23:00:00"), ("h", "2010-05-02 23:00:00", "2010-05-02 23:00:00", "2010-05-02 23:00:00", "2010-05-02 23:00:00", "2010-05-02 23:00:00", "2010-05-02 23:00:00", "2010-05-02 23:00:00"),
("hm", "2010-05-02 23:01:00", "2010-05-02 23:01:00", "2010-05-02 23:01:00", "2010-05-02 23:01:00", "2010-05-02 23:01:00", "2010-05-02 23:01:00", "2010-05-02 23:01:00"), ("hm", "2010-05-02 23:01:00", "2010-05-02 23:01:00", "2010-05-02 23:01:00", "2010-05-02 23:01:00", "2010-05-02 23:01:00", "2010-05-02 23:01:00", "2010-05-02 23:01:00"),
("hms", "2010-05-02 23:01:50", "2010-05-02 23:01:50", "2010-05-02 23:01:50", "2010-05-02 23:01:50", "2010-05-02 23:01:50", "2010-05-02 23:01:50", "2010-05-02 23:01:50"), ("hms", "2010-05-02 23:01:50", "2010-05-02 23:01:50", "2010-05-02 23:01:50", "2010-05-02 23:01:50", "2010-05-02 23:01:50", "2010-05-02 23:01:50", "2010-05-02 23:01:50"),
("hmsu", NULL, "2010-05-02 23:01:50.1", "2010-05-02 23:01:50.12", "2010-05-02 23:01:50.123", "2010-05-02 23:01:50.1234", "2010-05-02 23:01:50.12345", "2010-05-02 23:01:50.123456"), ("hmsu", NULL, "2010-05-02 23:01:50.1", "2010-05-02 23:01:50.12", "2010-05-02 23:01:50.123", "2010-05-02 23:01:50.1234", "2010-05-02 23:01:50.12345", "2010-05-02 23:01:50.123456"),
("min", "1000-01-01", "1000-01-01", "1000-01-01", "1000-01-01", "1000-01-01", "1000-01-01", "1000-01-01"), ("min", "1000-01-01", "1000-01-01", "1000-01-01", "1000-01-01", "1000-01-01", "1000-01-01", "1000-01-01"),
("max", "9999-12-31 23:59:59", "9999-12-31 23:59:59.9", "9999-12-31 23:59:59.99", "9999-12-31 23:59:59.999", "9999-12-31 23:59:59.9999", "9999-12-31 23:59:59.99999", "9999-12-31 23:59:59.999999") ("max", "9999-12-31 23:59:59", "9999-12-31 23:59:59.9", "9999-12-31 23:59:59.99", "9999-12-31 23:59:59.999", "9999-12-31 23:59:59.9999", "9999-12-31 23:59:59.99999", "9999-12-31 23:59:59.999999")
; ;
CREATE TABLE types_timestamp( CREATE TABLE types_timestamp(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_0 TIMESTAMP(0) NULL DEFAULT NULL, field_0 TIMESTAMP(0) NULL DEFAULT NULL,
field_1 TIMESTAMP(1) NULL DEFAULT NULL, field_1 TIMESTAMP(1) NULL DEFAULT NULL,
field_2 TIMESTAMP(2) NULL DEFAULT NULL, field_2 TIMESTAMP(2) NULL DEFAULT NULL,
field_3 TIMESTAMP(3) NULL DEFAULT NULL, field_3 TIMESTAMP(3) NULL DEFAULT NULL,
field_4 TIMESTAMP(4) NULL DEFAULT NULL, field_4 TIMESTAMP(4) NULL DEFAULT NULL,
field_5 TIMESTAMP(5) NULL DEFAULT NULL, field_5 TIMESTAMP(5) NULL DEFAULT NULL,
field_6 TIMESTAMP(6) NULL DEFAULT NULL field_6 TIMESTAMP(6) NULL DEFAULT NULL
); );
-- TODO: min and max not reproducible due to time zones. Find a programmatic way of determining the values here -- TODO: min and max not reproducible due to time zones. Find a programmatic way of determining the values here
INSERT INTO types_timestamp INSERT INTO types_timestamp
@ -227,149 +227,149 @@ SELECT * FROM types_datetime
WHERE id NOT IN ('min', 'max'); WHERE id NOT IN ('min', 'max');
CREATE TABLE types_time( CREATE TABLE types_time(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_0 TIME(0), field_0 TIME(0),
field_1 TIME(1), field_1 TIME(1),
field_2 TIME(2), field_2 TIME(2),
field_3 TIME(3), field_3 TIME(3),
field_4 TIME(4), field_4 TIME(4),
field_5 TIME(5), field_5 TIME(5),
field_6 TIME(6) field_6 TIME(6)
); );
INSERT INTO types_time VALUES INSERT INTO types_time VALUES
("zero", "00:00:00", "00:00:00", "00:00:00", "00:00:00", "00:00:00", "00:00:00", "00:00:00"), ("zero", "00:00:00", "00:00:00", "00:00:00", "00:00:00", "00:00:00", "00:00:00", "00:00:00"),
("d", "48:00:00", "48:00:00", "48:00:00", "48:00:00", "48:00:00", "48:00:00", "48:00:00"), ("d", "48:00:00", "48:00:00", "48:00:00", "48:00:00", "48:00:00", "48:00:00", "48:00:00"),
("negative_d", "-48:00:00", "-48:00:00", "-48:00:00", "-48:00:00", "-48:00:00", "-48:00:00", "-48:00:00"), ("negative_d", "-48:00:00", "-48:00:00", "-48:00:00", "-48:00:00", "-48:00:00", "-48:00:00", "-48:00:00"),
("h", "23:00:00", "23:00:00", "23:00:00", "23:00:00", "23:00:00", "23:00:00", "23:00:00"), ("h", "23:00:00", "23:00:00", "23:00:00", "23:00:00", "23:00:00", "23:00:00", "23:00:00"),
("negative_h", "-23:00:00", "-23:00:00", "-23:00:00", "-23:00:00", "-23:00:00", "-23:00:00", "-23:00:00"), ("negative_h", "-23:00:00", "-23:00:00", "-23:00:00", "-23:00:00", "-23:00:00", "-23:00:00", "-23:00:00"),
("dh", "71:00:00", "71:00:00", "71:00:00", "71:00:00", "71:00:00", "71:00:00", "71:00:00"), ("dh", "71:00:00", "71:00:00", "71:00:00", "71:00:00", "71:00:00", "71:00:00", "71:00:00"),
("negative_dh", "-71:00:00", "-71:00:00", "-71:00:00", "-71:00:00", "-71:00:00", "-71:00:00", "-71:00:00"), ("negative_dh", "-71:00:00", "-71:00:00", "-71:00:00", "-71:00:00", "-71:00:00", "-71:00:00", "-71:00:00"),
("m", "00:01:00", "00:01:00", "00:01:00", "00:01:00", "00:01:00", "00:01:00", "00:01:00"), ("m", "00:01:00", "00:01:00", "00:01:00", "00:01:00", "00:01:00", "00:01:00", "00:01:00"),
("negative_m", "-00:01:00", "-00:01:00", "-00:01:00", "-00:01:00", "-00:01:00", "-00:01:00", "-00:01:00"), ("negative_m", "-00:01:00", "-00:01:00", "-00:01:00", "-00:01:00", "-00:01:00", "-00:01:00", "-00:01:00"),
("dm", "48:01:00", "48:01:00", "48:01:00", "48:01:00", "48:01:00", "48:01:00", "48:01:00"), ("dm", "48:01:00", "48:01:00", "48:01:00", "48:01:00", "48:01:00", "48:01:00", "48:01:00"),
("negative_dm", "-48:01:00", "-48:01:00", "-48:01:00", "-48:01:00", "-48:01:00", "-48:01:00", "-48:01:00"), ("negative_dm", "-48:01:00", "-48:01:00", "-48:01:00", "-48:01:00", "-48:01:00", "-48:01:00", "-48:01:00"),
("hm", "23:01:00", "23:01:00", "23:01:00", "23:01:00", "23:01:00", "23:01:00", "23:01:00"), ("hm", "23:01:00", "23:01:00", "23:01:00", "23:01:00", "23:01:00", "23:01:00", "23:01:00"),
("negative_hm", "-23:01:00", "-23:01:00", "-23:01:00", "-23:01:00", "-23:01:00", "-23:01:00", "-23:01:00"), ("negative_hm", "-23:01:00", "-23:01:00", "-23:01:00", "-23:01:00", "-23:01:00", "-23:01:00", "-23:01:00"),
("dhm", "71:01:00", "71:01:00", "71:01:00", "71:01:00", "71:01:00", "71:01:00", "71:01:00"), ("dhm", "71:01:00", "71:01:00", "71:01:00", "71:01:00", "71:01:00", "71:01:00", "71:01:00"),
("negative_dhm", "-71:01:00", "-71:01:00", "-71:01:00", "-71:01:00", "-71:01:00", "-71:01:00", "-71:01:00"), ("negative_dhm", "-71:01:00", "-71:01:00", "-71:01:00", "-71:01:00", "-71:01:00", "-71:01:00", "-71:01:00"),
("s", "00:00:50", "00:00:50", "00:00:50", "00:00:50", "00:00:50", "00:00:50", "00:00:50"), ("s", "00:00:50", "00:00:50", "00:00:50", "00:00:50", "00:00:50", "00:00:50", "00:00:50"),
("negative_s", "-00:00:50", "-00:00:50", "-00:00:50", "-00:00:50", "-00:00:50", "-00:00:50", "-00:00:50"), ("negative_s", "-00:00:50", "-00:00:50", "-00:00:50", "-00:00:50", "-00:00:50", "-00:00:50", "-00:00:50"),
("ds", "48:00:50", "48:00:50", "48:00:50", "48:00:50", "48:00:50", "48:00:50", "48:00:50"), ("ds", "48:00:50", "48:00:50", "48:00:50", "48:00:50", "48:00:50", "48:00:50", "48:00:50"),
("negative_ds", "-48:00:50", "-48:00:50", "-48:00:50", "-48:00:50", "-48:00:50", "-48:00:50", "-48:00:50"), ("negative_ds", "-48:00:50", "-48:00:50", "-48:00:50", "-48:00:50", "-48:00:50", "-48:00:50", "-48:00:50"),
("hs", "23:00:50", "23:00:50", "23:00:50", "23:00:50", "23:00:50", "23:00:50", "23:00:50"), ("hs", "23:00:50", "23:00:50", "23:00:50", "23:00:50", "23:00:50", "23:00:50", "23:00:50"),
("negative_hs", "-23:00:50", "-23:00:50", "-23:00:50", "-23:00:50", "-23:00:50", "-23:00:50", "-23:00:50"), ("negative_hs", "-23:00:50", "-23:00:50", "-23:00:50", "-23:00:50", "-23:00:50", "-23:00:50", "-23:00:50"),
("dhs", "71:00:50", "71:00:50", "71:00:50", "71:00:50", "71:00:50", "71:00:50", "71:00:50"), ("dhs", "71:00:50", "71:00:50", "71:00:50", "71:00:50", "71:00:50", "71:00:50", "71:00:50"),
("negative_dhs", "-71:00:50", "-71:00:50", "-71:00:50", "-71:00:50", "-71:00:50", "-71:00:50", "-71:00:50"), ("negative_dhs", "-71:00:50", "-71:00:50", "-71:00:50", "-71:00:50", "-71:00:50", "-71:00:50", "-71:00:50"),
("ms", "00:01:50", "00:01:50", "00:01:50", "00:01:50", "00:01:50", "00:01:50", "00:01:50"), ("ms", "00:01:50", "00:01:50", "00:01:50", "00:01:50", "00:01:50", "00:01:50", "00:01:50"),
("negative_ms", "-00:01:50", "-00:01:50", "-00:01:50", "-00:01:50", "-00:01:50", "-00:01:50", "-00:01:50"), ("negative_ms", "-00:01:50", "-00:01:50", "-00:01:50", "-00:01:50", "-00:01:50", "-00:01:50", "-00:01:50"),
("dms", "48:01:50", "48:01:50", "48:01:50", "48:01:50", "48:01:50", "48:01:50", "48:01:50"), ("dms", "48:01:50", "48:01:50", "48:01:50", "48:01:50", "48:01:50", "48:01:50", "48:01:50"),
("negative_dms", "-48:01:50", "-48:01:50", "-48:01:50", "-48:01:50", "-48:01:50", "-48:01:50", "-48:01:50"), ("negative_dms", "-48:01:50", "-48:01:50", "-48:01:50", "-48:01:50", "-48:01:50", "-48:01:50", "-48:01:50"),
("hms", "23:01:50", "23:01:50", "23:01:50", "23:01:50", "23:01:50", "23:01:50", "23:01:50"), ("hms", "23:01:50", "23:01:50", "23:01:50", "23:01:50", "23:01:50", "23:01:50", "23:01:50"),
("negative_hms", "-23:01:50", "-23:01:50", "-23:01:50", "-23:01:50", "-23:01:50", "-23:01:50", "-23:01:50"), ("negative_hms", "-23:01:50", "-23:01:50", "-23:01:50", "-23:01:50", "-23:01:50", "-23:01:50", "-23:01:50"),
("dhms", "71:01:50", "71:01:50", "71:01:50", "71:01:50", "71:01:50", "71:01:50", "71:01:50"), ("dhms", "71:01:50", "71:01:50", "71:01:50", "71:01:50", "71:01:50", "71:01:50", "71:01:50"),
("negative_dhms", "-71:01:50", "-71:01:50", "-71:01:50", "-71:01:50", "-71:01:50", "-71:01:50", "-71:01:50"), ("negative_dhms", "-71:01:50", "-71:01:50", "-71:01:50", "-71:01:50", "-71:01:50", "-71:01:50", "-71:01:50"),
("u", NULL, "00:00:00.1", "00:00:00.12", "00:00:00.123", "00:00:00.1234", "00:00:00.12345", "00:00:00.123456"), ("u", NULL, "00:00:00.1", "00:00:00.12", "00:00:00.123", "00:00:00.1234", "00:00:00.12345", "00:00:00.123456"),
("negative_u", NULL, "-00:00:00.1", "-00:00:00.12", "-00:00:00.123", "-00:00:00.1234", "-00:00:00.12345", "-00:00:00.123456"), ("negative_u", NULL, "-00:00:00.1", "-00:00:00.12", "-00:00:00.123", "-00:00:00.1234", "-00:00:00.12345", "-00:00:00.123456"),
("du", NULL, "48:00:00.1", "48:00:00.12", "48:00:00.123", "48:00:00.1234", "48:00:00.12345", "48:00:00.123456"), ("du", NULL, "48:00:00.1", "48:00:00.12", "48:00:00.123", "48:00:00.1234", "48:00:00.12345", "48:00:00.123456"),
("negative_du", NULL, "-48:00:00.1", "-48:00:00.12", "-48:00:00.123", "-48:00:00.1234", "-48:00:00.12345", "-48:00:00.123456"), ("negative_du", NULL, "-48:00:00.1", "-48:00:00.12", "-48:00:00.123", "-48:00:00.1234", "-48:00:00.12345", "-48:00:00.123456"),
("hu", NULL, "23:00:00.1", "23:00:00.12", "23:00:00.123", "23:00:00.1234", "23:00:00.12345", "23:00:00.123456"), ("hu", NULL, "23:00:00.1", "23:00:00.12", "23:00:00.123", "23:00:00.1234", "23:00:00.12345", "23:00:00.123456"),
("negative_hu", NULL, "-23:00:00.1", "-23:00:00.12", "-23:00:00.123", "-23:00:00.1234", "-23:00:00.12345", "-23:00:00.123456"), ("negative_hu", NULL, "-23:00:00.1", "-23:00:00.12", "-23:00:00.123", "-23:00:00.1234", "-23:00:00.12345", "-23:00:00.123456"),
("dhu", NULL, "71:00:00.1", "71:00:00.12", "71:00:00.123", "71:00:00.1234", "71:00:00.12345", "71:00:00.123456"), ("dhu", NULL, "71:00:00.1", "71:00:00.12", "71:00:00.123", "71:00:00.1234", "71:00:00.12345", "71:00:00.123456"),
("negative_dhu", NULL, "-71:00:00.1", "-71:00:00.12", "-71:00:00.123", "-71:00:00.1234", "-71:00:00.12345", "-71:00:00.123456"), ("negative_dhu", NULL, "-71:00:00.1", "-71:00:00.12", "-71:00:00.123", "-71:00:00.1234", "-71:00:00.12345", "-71:00:00.123456"),
("mu", NULL, "00:01:00.1", "00:01:00.12", "00:01:00.123", "00:01:00.1234", "00:01:00.12345", "00:01:00.123456"), ("mu", NULL, "00:01:00.1", "00:01:00.12", "00:01:00.123", "00:01:00.1234", "00:01:00.12345", "00:01:00.123456"),
("negative_mu", NULL, "-00:01:00.1", "-00:01:00.12", "-00:01:00.123", "-00:01:00.1234", "-00:01:00.12345", "-00:01:00.123456"), ("negative_mu", NULL, "-00:01:00.1", "-00:01:00.12", "-00:01:00.123", "-00:01:00.1234", "-00:01:00.12345", "-00:01:00.123456"),
("dmu", NULL, "48:01:00.1", "48:01:00.12", "48:01:00.123", "48:01:00.1234", "48:01:00.12345", "48:01:00.123456"), ("dmu", NULL, "48:01:00.1", "48:01:00.12", "48:01:00.123", "48:01:00.1234", "48:01:00.12345", "48:01:00.123456"),
("negative_dmu", NULL, "-48:01:00.1", "-48:01:00.12", "-48:01:00.123", "-48:01:00.1234", "-48:01:00.12345", "-48:01:00.123456"), ("negative_dmu", NULL, "-48:01:00.1", "-48:01:00.12", "-48:01:00.123", "-48:01:00.1234", "-48:01:00.12345", "-48:01:00.123456"),
("hmu", NULL, "23:01:00.1", "23:01:00.12", "23:01:00.123", "23:01:00.1234", "23:01:00.12345", "23:01:00.123456"), ("hmu", NULL, "23:01:00.1", "23:01:00.12", "23:01:00.123", "23:01:00.1234", "23:01:00.12345", "23:01:00.123456"),
("negative_hmu", NULL, "-23:01:00.1", "-23:01:00.12", "-23:01:00.123", "-23:01:00.1234", "-23:01:00.12345", "-23:01:00.123456"), ("negative_hmu", NULL, "-23:01:00.1", "-23:01:00.12", "-23:01:00.123", "-23:01:00.1234", "-23:01:00.12345", "-23:01:00.123456"),
("dhmu", NULL, "71:01:00.1", "71:01:00.12", "71:01:00.123", "71:01:00.1234", "71:01:00.12345", "71:01:00.123456"), ("dhmu", NULL, "71:01:00.1", "71:01:00.12", "71:01:00.123", "71:01:00.1234", "71:01:00.12345", "71:01:00.123456"),
("negative_dhmu", NULL, "-71:01:00.1", "-71:01:00.12", "-71:01:00.123", "-71:01:00.1234", "-71:01:00.12345", "-71:01:00.123456"), ("negative_dhmu", NULL, "-71:01:00.1", "-71:01:00.12", "-71:01:00.123", "-71:01:00.1234", "-71:01:00.12345", "-71:01:00.123456"),
("su", NULL, "00:00:50.1", "00:00:50.12", "00:00:50.123", "00:00:50.1234", "00:00:50.12345", "00:00:50.123456"), ("su", NULL, "00:00:50.1", "00:00:50.12", "00:00:50.123", "00:00:50.1234", "00:00:50.12345", "00:00:50.123456"),
("negative_su", NULL, "-00:00:50.1", "-00:00:50.12", "-00:00:50.123", "-00:00:50.1234", "-00:00:50.12345", "-00:00:50.123456"), ("negative_su", NULL, "-00:00:50.1", "-00:00:50.12", "-00:00:50.123", "-00:00:50.1234", "-00:00:50.12345", "-00:00:50.123456"),
("dsu", NULL, "48:00:50.1", "48:00:50.12", "48:00:50.123", "48:00:50.1234", "48:00:50.12345", "48:00:50.123456"), ("dsu", NULL, "48:00:50.1", "48:00:50.12", "48:00:50.123", "48:00:50.1234", "48:00:50.12345", "48:00:50.123456"),
("negative_dsu", NULL, "-48:00:50.1", "-48:00:50.12", "-48:00:50.123", "-48:00:50.1234", "-48:00:50.12345", "-48:00:50.123456"), ("negative_dsu", NULL, "-48:00:50.1", "-48:00:50.12", "-48:00:50.123", "-48:00:50.1234", "-48:00:50.12345", "-48:00:50.123456"),
("hsu", NULL, "23:00:50.1", "23:00:50.12", "23:00:50.123", "23:00:50.1234", "23:00:50.12345", "23:00:50.123456"), ("hsu", NULL, "23:00:50.1", "23:00:50.12", "23:00:50.123", "23:00:50.1234", "23:00:50.12345", "23:00:50.123456"),
("negative_hsu", NULL, "-23:00:50.1", "-23:00:50.12", "-23:00:50.123", "-23:00:50.1234", "-23:00:50.12345", "-23:00:50.123456"), ("negative_hsu", NULL, "-23:00:50.1", "-23:00:50.12", "-23:00:50.123", "-23:00:50.1234", "-23:00:50.12345", "-23:00:50.123456"),
("dhsu", NULL, "71:00:50.1", "71:00:50.12", "71:00:50.123", "71:00:50.1234", "71:00:50.12345", "71:00:50.123456"), ("dhsu", NULL, "71:00:50.1", "71:00:50.12", "71:00:50.123", "71:00:50.1234", "71:00:50.12345", "71:00:50.123456"),
("negative_dhsu", NULL, "-71:00:50.1", "-71:00:50.12", "-71:00:50.123", "-71:00:50.1234", "-71:00:50.12345", "-71:00:50.123456"), ("negative_dhsu", NULL, "-71:00:50.1", "-71:00:50.12", "-71:00:50.123", "-71:00:50.1234", "-71:00:50.12345", "-71:00:50.123456"),
("msu", NULL, "00:01:50.1", "00:01:50.12", "00:01:50.123", "00:01:50.1234", "00:01:50.12345", "00:01:50.123456"), ("msu", NULL, "00:01:50.1", "00:01:50.12", "00:01:50.123", "00:01:50.1234", "00:01:50.12345", "00:01:50.123456"),
("negative_msu", NULL, "-00:01:50.1", "-00:01:50.12", "-00:01:50.123", "-00:01:50.1234", "-00:01:50.12345", "-00:01:50.123456"), ("negative_msu", NULL, "-00:01:50.1", "-00:01:50.12", "-00:01:50.123", "-00:01:50.1234", "-00:01:50.12345", "-00:01:50.123456"),
("dmsu", NULL, "48:01:50.1", "48:01:50.12", "48:01:50.123", "48:01:50.1234", "48:01:50.12345", "48:01:50.123456"), ("dmsu", NULL, "48:01:50.1", "48:01:50.12", "48:01:50.123", "48:01:50.1234", "48:01:50.12345", "48:01:50.123456"),
("negative_dmsu", NULL, "-48:01:50.1", "-48:01:50.12", "-48:01:50.123", "-48:01:50.1234", "-48:01:50.12345", "-48:01:50.123456"), ("negative_dmsu", NULL, "-48:01:50.1", "-48:01:50.12", "-48:01:50.123", "-48:01:50.1234", "-48:01:50.12345", "-48:01:50.123456"),
("hmsu", NULL, "23:01:50.1", "23:01:50.12", "23:01:50.123", "23:01:50.1234", "23:01:50.12345", "23:01:50.123456"), ("hmsu", NULL, "23:01:50.1", "23:01:50.12", "23:01:50.123", "23:01:50.1234", "23:01:50.12345", "23:01:50.123456"),
("negative_hmsu", NULL, "-23:01:50.1", "-23:01:50.12", "-23:01:50.123", "-23:01:50.1234", "-23:01:50.12345", "-23:01:50.123456"), ("negative_hmsu", NULL, "-23:01:50.1", "-23:01:50.12", "-23:01:50.123", "-23:01:50.1234", "-23:01:50.12345", "-23:01:50.123456"),
("dhmsu", NULL, "71:01:50.1", "71:01:50.12", "71:01:50.123", "71:01:50.1234", "71:01:50.12345", "71:01:50.123456"), ("dhmsu", NULL, "71:01:50.1", "71:01:50.12", "71:01:50.123", "71:01:50.1234", "71:01:50.12345", "71:01:50.123456"),
("negative_dhmsu", NULL, "-71:01:50.1", "-71:01:50.12", "-71:01:50.123", "-71:01:50.1234", "-71:01:50.12345", "-71:01:50.123456"), ("negative_dhmsu", NULL, "-71:01:50.1", "-71:01:50.12", "-71:01:50.123", "-71:01:50.1234", "-71:01:50.12345", "-71:01:50.123456"),
("min", "-838:59:59", "-838:59:58.9", "-838:59:58.99", "-838:59:58.999", "-838:59:58.9999", "-838:59:58.99999", "-838:59:58.999999"), ("min", "-838:59:59", "-838:59:58.9", "-838:59:58.99", "-838:59:58.999", "-838:59:58.9999", "-838:59:58.99999", "-838:59:58.999999"),
("max", "838:59:59", "838:59:58.9", "838:59:58.99", "838:59:58.999", "838:59:58.9999", "838:59:58.99999", "838:59:58.999999") ("max", "838:59:59", "838:59:58.9", "838:59:58.99", "838:59:58.999", "838:59:58.9999", "838:59:58.99999", "838:59:58.999999")
; ;
CREATE TABLE types_year( CREATE TABLE types_year(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_default YEAR field_default YEAR
); );
INSERT INTO types_year VALUES INSERT INTO types_year VALUES
("regular", 2019), ("regular", 2019),
("min", 1901), ("min", 1901),
("max", 2155), ("max", 2155),
("zero", 0) ("zero", 0)
; ;
CREATE TABLE types_string( CREATE TABLE types_string(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_char CHAR(20), field_char CHAR(20),
field_varchar VARCHAR(30), field_varchar VARCHAR(30),
field_tinytext TINYTEXT, field_tinytext TINYTEXT,
field_text TEXT, field_text TEXT,
field_mediumtext MEDIUMTEXT, field_mediumtext MEDIUMTEXT,
field_longtext LONGTEXT, field_longtext LONGTEXT,
field_enum ENUM("red", "green", "blue"), field_enum ENUM("red", "green", "blue"),
field_set SET("red", "green", "blue") field_set SET("red", "green", "blue")
); );
INSERT INTO types_string VALUES INSERT INTO types_string VALUES
("regular", "test_char", "test_varchar", "test_tinytext", "test_text", "test_mediumtext", "test_longtext", "red", "red,green"), ("regular", "test_char", "test_varchar", "test_tinytext", "test_text", "test_mediumtext", "test_longtext", "red", "red,green"),
("utf8", "ñ", "Ñ", "á", "é", "í", "ó", NULL, NULL), ("utf8", "ñ", "Ñ", "á", "é", "í", "ó", NULL, NULL),
("empty", "", "", "", "", "", "", NULL, "") ("empty", "", "", "", "", "", "", NULL, "")
; ;
CREATE TABLE types_binary( CREATE TABLE types_binary(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_binary BINARY(10), field_binary BINARY(10),
field_varbinary VARBINARY(30), field_varbinary VARBINARY(30),
field_tinyblob TINYBLOB, field_tinyblob TINYBLOB,
field_blob BLOB, field_blob BLOB,
field_mediumblob MEDIUMBLOB, field_mediumblob MEDIUMBLOB,
field_longblob LONGBLOB field_longblob LONGBLOB
); );
INSERT INTO types_binary VALUES INSERT INTO types_binary VALUES
("regular", "\0_binary", "\0_varbinary", "\0_tinyblob", "\0_blob", "\0_mediumblob", "\0_longblob"), ("regular", "\0_binary", "\0_varbinary", "\0_tinyblob", "\0_blob", "\0_mediumblob", "\0_longblob"),
("nonascii", X'00FF', X'01FE', X'02FD', X'03FC', X'04FB', X'05FA'), ("nonascii", X'00FF', X'01FE', X'02FD', X'03FC', X'04FB', X'05FA'),
("empty", "", "", "", "", "", "") ("empty", "", "", "", "", "", "")
; ;
CREATE TABLE types_not_implemented( CREATE TABLE types_not_implemented(
id VARCHAR(50) NOT NULL PRIMARY KEY, id VARCHAR(50) NOT NULL PRIMARY KEY,
field_bit BIT(8), field_bit BIT(8),
field_decimal DECIMAL, field_decimal DECIMAL,
field_geometry GEOMETRY field_geometry GEOMETRY
); );
INSERT INTO types_not_implemented VALUES INSERT INTO types_not_implemented VALUES
("regular", 0xfe, 300, POINT(1, 2)) ("regular", 0xfe, 300, POINT(1, 2))
; ;
CREATE TABLE types_flags( CREATE TABLE types_flags(
id VARCHAR(50) NOT NULL, id VARCHAR(50) NOT NULL,
field_timestamp TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, field_timestamp TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
field_primary_key INT NOT NULL AUTO_INCREMENT, field_primary_key INT NOT NULL AUTO_INCREMENT,
field_not_null CHAR(8) NOT NULL DEFAULT "", field_not_null CHAR(8) NOT NULL DEFAULT "",
field_unique INT, field_unique INT,
field_indexed INT, field_indexed INT,
PRIMARY KEY (field_primary_key, id), PRIMARY KEY (field_primary_key, id),
UNIQUE KEY (field_unique), UNIQUE KEY (field_unique),
KEY (field_indexed) KEY (field_indexed)
); );
INSERT INTO types_flags VALUES INSERT INTO types_flags VALUES
("default", NULL, 50, "char", 21, 42) ("default", NULL, 50, "char", 21, 42)
; ;
-- Users -- Users

View File

@ -21,94 +21,94 @@ namespace
template <typename Stream> template <typename Stream>
struct ExecuteStatementTest : public NetworkTest<Stream> struct ExecuteStatementTest : public NetworkTest<Stream>
{ {
prepared_statement<Stream> do_prepare(std::string_view stmt) prepared_statement<Stream> do_prepare(std::string_view stmt)
{ {
auto res = this->GetParam().net->prepare_statement(this->conn, stmt); auto res = this->GetParam().net->prepare_statement(this->conn, stmt);
res.validate_no_error(); res.validate_no_error();
return std::move(res.value); return std::move(res.value);
} }
auto do_execute(prepared_statement<Stream>& stmt, value_list_it first, value_list_it last) auto do_execute(prepared_statement<Stream>& stmt, value_list_it first, value_list_it last)
{ {
return this->GetParam().net->execute_statement(stmt, first, last); return this->GetParam().net->execute_statement(stmt, first, last);
} }
auto do_execute(prepared_statement<Stream>& stmt, const std::vector<value>& params) auto do_execute(prepared_statement<Stream>& stmt, const std::vector<value>& params)
{ {
return this->GetParam().net->execute_statement(stmt, params); return this->GetParam().net->execute_statement(stmt, params);
} }
// Iterator version // Iterator version
void Iterator_OkNoParams() void Iterator_OkNoParams()
{ {
std::forward_list<value> params; std::forward_list<value> params;
auto stmt = do_prepare("SELECT * FROM empty_table"); auto stmt = do_prepare("SELECT * FROM empty_table");
auto result = do_execute(stmt, params.begin(), params.end()); // execute auto result = do_execute(stmt, params.begin(), params.end()); // execute
result.validate_no_error(); result.validate_no_error();
EXPECT_TRUE(result.value.valid()); EXPECT_TRUE(result.value.valid());
} }
void Iterator_OkWithParams() void Iterator_OkWithParams()
{ {
std::forward_list<value> params { value("item"), value(42) }; std::forward_list<value> params { value("item"), value(42) };
auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)"); auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)");
auto result = do_execute(stmt, params.begin(), params.end()); auto result = do_execute(stmt, params.begin(), params.end());
result.validate_no_error(); result.validate_no_error();
EXPECT_TRUE(result.value.valid()); EXPECT_TRUE(result.value.valid());
} }
void Iterator_MismatchedNumParams() void Iterator_MismatchedNumParams()
{ {
std::forward_list<value> params { value("item") }; std::forward_list<value> params { value("item") };
auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)"); auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)");
auto result = do_execute(stmt, params.begin(), params.end()); auto result = do_execute(stmt, params.begin(), params.end());
result.validate_error(errc::wrong_num_params, {"param", "2", "1", "statement", "execute"}); result.validate_error(errc::wrong_num_params, {"param", "2", "1", "statement", "execute"});
EXPECT_FALSE(result.value.valid()); EXPECT_FALSE(result.value.valid());
} }
void Iterator_ServerError() void Iterator_ServerError()
{ {
std::forward_list<value> params { value("f0"), value("bad_date") }; std::forward_list<value> params { value("f0"), value("bad_date") };
auto stmt = do_prepare("INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)"); auto stmt = do_prepare("INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)");
auto result = do_execute(stmt, params.begin(), params.end()); auto result = do_execute(stmt, params.begin(), params.end());
result.validate_error(errc::truncated_wrong_value, {"field_date", "bad_date", "incorrect date value"}); result.validate_error(errc::truncated_wrong_value, {"field_date", "bad_date", "incorrect date value"});
EXPECT_FALSE(result.value.valid()); EXPECT_FALSE(result.value.valid());
} }
// Container version // Container version
void Container_OkNoParams() void Container_OkNoParams()
{ {
auto stmt = do_prepare("SELECT * FROM empty_table"); auto stmt = do_prepare("SELECT * FROM empty_table");
auto result = do_execute(stmt, std::vector<value>()); // execute auto result = do_execute(stmt, std::vector<value>()); // execute
result.validate_no_error(); result.validate_no_error();
EXPECT_TRUE(result.value.valid()); EXPECT_TRUE(result.value.valid());
} }
void Container_OkWithParams() void Container_OkWithParams()
{ {
std::vector<value> params { value("item"), value(42) }; std::vector<value> params { value("item"), value(42) };
auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)"); auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)");
auto result = do_execute(stmt, params); auto result = do_execute(stmt, params);
result.validate_no_error(); result.validate_no_error();
EXPECT_TRUE(result.value.valid()); EXPECT_TRUE(result.value.valid());
} }
void Container_MismatchedNumParams() void Container_MismatchedNumParams()
{ {
std::vector<value> params { value("item") }; std::vector<value> params { value("item") };
auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)"); auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)");
auto result = do_execute(stmt, params); auto result = do_execute(stmt, params);
result.validate_error(errc::wrong_num_params, {"param", "2", "1", "statement", "execute"}); result.validate_error(errc::wrong_num_params, {"param", "2", "1", "statement", "execute"});
EXPECT_FALSE(result.value.valid()); EXPECT_FALSE(result.value.valid());
} }
void Container_ServerError() void Container_ServerError()
{ {
auto stmt = do_prepare("INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)"); auto stmt = do_prepare("INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)");
auto result = do_execute(stmt, makevalues("f0", "bad_date")); auto result = do_execute(stmt, makevalues("f0", "bad_date"));
result.validate_error(errc::truncated_wrong_value, {"field_date", "bad_date", "incorrect date value"}); result.validate_error(errc::truncated_wrong_value, {"field_date", "bad_date", "incorrect date value"});
EXPECT_FALSE(result.value.valid()); EXPECT_FALSE(result.value.valid());
} }
}; };
BOOST_MYSQL_NETWORK_TEST_SUITE(ExecuteStatementTest) BOOST_MYSQL_NETWORK_TEST_SUITE(ExecuteStatementTest)
@ -127,22 +127,22 @@ BOOST_MYSQL_NETWORK_TEST(ExecuteStatementTest, Container_ServerError)
// Other containers // Other containers
struct ExecuteStatementOtherContainersTest : IntegTest<boost::asio::ip::tcp::socket> struct ExecuteStatementOtherContainersTest : IntegTest<boost::asio::ip::tcp::socket>
{ {
ExecuteStatementOtherContainersTest() { handshake(boost::mysql::ssl_mode::disable); } ExecuteStatementOtherContainersTest() { handshake(boost::mysql::ssl_mode::disable); }
}; };
TEST_F(ExecuteStatementOtherContainersTest, NoParams_CanUseNoStatementParamsVariable) TEST_F(ExecuteStatementOtherContainersTest, NoParams_CanUseNoStatementParamsVariable)
{ {
auto stmt = conn.prepare_statement("SELECT * FROM empty_table"); auto stmt = conn.prepare_statement("SELECT * FROM empty_table");
auto result = stmt.execute(boost::mysql::no_statement_params); auto result = stmt.execute(boost::mysql::no_statement_params);
EXPECT_TRUE(result.valid()); EXPECT_TRUE(result.valid());
} }
TEST_F(ExecuteStatementOtherContainersTest, CArray) TEST_F(ExecuteStatementOtherContainersTest, CArray)
{ {
value arr [] = { value("hola"), value(10) }; value arr [] = { value("hola"), value(10) };
auto stmt = conn.prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)"); auto stmt = conn.prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)");
auto result = stmt.execute(arr); auto result = stmt.execute(arr);
EXPECT_TRUE(result.valid()); EXPECT_TRUE(result.valid());
} }
} }

View File

@ -32,67 +32,67 @@ namespace
// Handshake tests not depending on whether we use SSL or not // Handshake tests not depending on whether we use SSL or not
template <typename Stream> template <typename Stream>
struct SslIndifferentHandshakeTest : struct SslIndifferentHandshakeTest :
NetworkTest<Stream, network_testcase_with_ssl<Stream>, false> NetworkTest<Stream, network_testcase_with_ssl<Stream>, false>
{ {
auto do_handshake() auto do_handshake()
{ {
this->connection_params.set_ssl(boost::mysql::ssl_options(this->GetParam().ssl)); this->connection_params.set_ssl(boost::mysql::ssl_options(this->GetParam().ssl));
return this->GetParam().net->handshake(this->conn, this->connection_params); return this->GetParam().net->handshake(this->conn, this->connection_params);
} }
// does handshake and verifies it went OK // does handshake and verifies it went OK
void do_handshake_ok() void do_handshake_ok()
{ {
auto result = do_handshake(); auto result = do_handshake();
result.validate_no_error(); result.validate_no_error();
this->validate_ssl(this->GetParam().ssl); this->validate_ssl(this->GetParam().ssl);
} }
}; };
template <typename Stream> template <typename Stream>
struct SslSensitiveHandshakeTest : NetworkTest<Stream, network_testcase<Stream>, false> struct SslSensitiveHandshakeTest : NetworkTest<Stream, network_testcase<Stream>, false>
{ {
void set_ssl(ssl_mode m) void set_ssl(ssl_mode m)
{ {
this->connection_params.set_ssl(boost::mysql::ssl_options(m)); this->connection_params.set_ssl(boost::mysql::ssl_options(m));
} }
auto do_handshake() auto do_handshake()
{ {
return this->GetParam().net->handshake(this->conn, this->connection_params); return this->GetParam().net->handshake(this->conn, this->connection_params);
} }
void do_handshake_ok(ssl_mode m) void do_handshake_ok(ssl_mode m)
{ {
set_ssl(m); set_ssl(m);
auto result = do_handshake(); auto result = do_handshake();
result.validate_no_error(); result.validate_no_error();
this->validate_ssl(m); this->validate_ssl(m);
} }
}; };
// mysql_native_password // mysql_native_password
template <typename Stream> template <typename Stream>
struct MysqlNativePasswordHandshakeTest : SslIndifferentHandshakeTest<Stream> struct MysqlNativePasswordHandshakeTest : SslIndifferentHandshakeTest<Stream>
{ {
void RegularUser_SuccessfulLogin() void RegularUser_SuccessfulLogin()
{ {
this->set_credentials("mysqlnp_user", "mysqlnp_password"); this->set_credentials("mysqlnp_user", "mysqlnp_password");
this->do_handshake_ok(); this->do_handshake_ok();
} }
void EmptyPassword_SuccessfulLogin() void EmptyPassword_SuccessfulLogin()
{ {
this->set_credentials("mysqlnp_empty_password_user", ""); this->set_credentials("mysqlnp_empty_password_user", "");
this->do_handshake_ok(); this->do_handshake_ok();
} }
void BadPassword_FailedLogin() void BadPassword_FailedLogin()
{ {
this->set_credentials("mysqlnp_user", "bad_password"); this->set_credentials("mysqlnp_user", "bad_password");
auto result = this->do_handshake(); auto result = this->do_handshake();
result.validate_error(errc::access_denied_error, {"access denied", "mysqlnp_user"}); result.validate_error(errc::access_denied_error, {"access denied", "mysqlnp_user"});
} }
}; };
BOOST_MYSQL_NETWORK_TEST_SUITE(MysqlNativePasswordHandshakeTest) BOOST_MYSQL_NETWORK_TEST_SUITE(MysqlNativePasswordHandshakeTest)
@ -106,26 +106,26 @@ BOOST_MYSQL_NETWORK_TEST(MysqlNativePasswordHandshakeTest, BadPassword_FailedLog
template <typename Stream> template <typename Stream>
struct MiscSslIndifferentHandshakeTest : SslIndifferentHandshakeTest<Stream> struct MiscSslIndifferentHandshakeTest : SslIndifferentHandshakeTest<Stream>
{ {
void NoDatabase_SuccessfulLogin() void NoDatabase_SuccessfulLogin()
{ {
this->connection_params.set_database(""); this->connection_params.set_database("");
this->do_handshake_ok(); this->do_handshake_ok();
} }
void BadDatabase_FailedLogin() void BadDatabase_FailedLogin()
{ {
this->connection_params.set_database("bad_database"); this->connection_params.set_database("bad_database");
auto result = this->do_handshake(); auto result = this->do_handshake();
result.validate_error(errc::dbaccess_denied_error, {"database", "bad_database"}); result.validate_error(errc::dbaccess_denied_error, {"database", "bad_database"});
} }
void UnknownAuthPlugin_FailedLogin_RequiresSha256() void UnknownAuthPlugin_FailedLogin_RequiresSha256()
{ {
// Note: sha256_password is not supported, so it's an unknown plugin to us // Note: sha256_password is not supported, so it's an unknown plugin to us
this->set_credentials("sha2p_user", "sha2p_password"); this->set_credentials("sha2p_user", "sha2p_password");
auto result = this->do_handshake(); auto result = this->do_handshake();
result.validate_error(errc::unknown_auth_plugin, {}); result.validate_error(errc::unknown_auth_plugin, {});
} }
}; };
BOOST_MYSQL_NETWORK_TEST_SUITE(MiscSslIndifferentHandshakeTest) BOOST_MYSQL_NETWORK_TEST_SUITE(MiscSslIndifferentHandshakeTest)
@ -138,104 +138,104 @@ BOOST_MYSQL_NETWORK_TEST(MiscSslIndifferentHandshakeTest, UnknownAuthPlugin_Fail
template <typename Stream> template <typename Stream>
struct CachingSha2HandshakeTest : SslSensitiveHandshakeTest<Stream> struct CachingSha2HandshakeTest : SslSensitiveHandshakeTest<Stream>
{ {
void load_sha256_cache(const std::string& user, const std::string& password) void load_sha256_cache(const std::string& user, const std::string& password)
{ {
if (password.empty()) if (password.empty())
{ {
check_call(stringize("mysql -u ", user, " -e \"\"")); check_call(stringize("mysql -u ", user, " -e \"\""));
} }
else else
{ {
check_call(stringize("mysql -u ", user, " -p", password, " -e \"\"")); check_call(stringize("mysql -u ", user, " -p", password, " -e \"\""));
} }
} }
void clear_sha256_cache() void clear_sha256_cache()
{ {
check_call("mysql -u root -e \"FLUSH PRIVILEGES\""); check_call("mysql -u root -e \"FLUSH PRIVILEGES\"");
} }
// Actual tests // Actual tests
void SslOnCacheHit_SuccessfulLogin_RequiresSha256() void SslOnCacheHit_SuccessfulLogin_RequiresSha256()
{ {
this->set_credentials("csha2p_user", "csha2p_password"); this->set_credentials("csha2p_user", "csha2p_password");
load_sha256_cache("csha2p_user", "csha2p_password"); load_sha256_cache("csha2p_user", "csha2p_password");
this->do_handshake_ok(ssl_mode::require); this->do_handshake_ok(ssl_mode::require);
} }
void SslOffCacheHit_SuccessfulLogin_RequiresSha256() void SslOffCacheHit_SuccessfulLogin_RequiresSha256()
{ {
// As we are sending password hashed, it is OK to not have SSL for this // As we are sending password hashed, it is OK to not have SSL for this
this->set_credentials("csha2p_user", "csha2p_password"); this->set_credentials("csha2p_user", "csha2p_password");
load_sha256_cache("csha2p_user", "csha2p_password"); load_sha256_cache("csha2p_user", "csha2p_password");
this->do_handshake_ok(ssl_mode::disable); this->do_handshake_ok(ssl_mode::disable);
} }
void SslOnCacheMiss_SuccessfulLogin_RequiresSha256() void SslOnCacheMiss_SuccessfulLogin_RequiresSha256()
{ {
this->set_credentials("csha2p_user", "csha2p_password"); this->set_credentials("csha2p_user", "csha2p_password");
clear_sha256_cache(); clear_sha256_cache();
this->do_handshake_ok(ssl_mode::require); this->do_handshake_ok(ssl_mode::require);
} }
void SslOffCacheMiss_FailedLogin_RequiresSha256() void SslOffCacheMiss_FailedLogin_RequiresSha256()
{ {
// A cache miss would force us send a plaintext password over // A cache miss would force us send a plaintext password over
// a non-TLS connection, so we fail // a non-TLS connection, so we fail
this->set_ssl(ssl_mode::disable); this->set_ssl(ssl_mode::disable);
this->set_credentials("csha2p_user", "csha2p_password"); this->set_credentials("csha2p_user", "csha2p_password");
clear_sha256_cache(); clear_sha256_cache();
auto result = this->do_handshake(); auto result = this->do_handshake();
result.validate_error(errc::auth_plugin_requires_ssl, {}); result.validate_error(errc::auth_plugin_requires_ssl, {});
} }
void EmptyPasswordSslOnCacheHit_SuccessfulLogin_RequiresSha256() void EmptyPasswordSslOnCacheHit_SuccessfulLogin_RequiresSha256()
{ {
this->set_credentials("csha2p_empty_password_user", ""); this->set_credentials("csha2p_empty_password_user", "");
load_sha256_cache("csha2p_empty_password_user", ""); load_sha256_cache("csha2p_empty_password_user", "");
this->do_handshake_ok(ssl_mode::require); this->do_handshake_ok(ssl_mode::require);
} }
void EmptyPasswordSslOffCacheHit_SuccessfulLogin_RequiresSha256() void EmptyPasswordSslOffCacheHit_SuccessfulLogin_RequiresSha256()
{ {
// Empty passwords are allowed over non-TLS connections // Empty passwords are allowed over non-TLS connections
this->set_credentials("csha2p_empty_password_user", ""); this->set_credentials("csha2p_empty_password_user", "");
load_sha256_cache("csha2p_empty_password_user", ""); load_sha256_cache("csha2p_empty_password_user", "");
this->do_handshake_ok(ssl_mode::disable); this->do_handshake_ok(ssl_mode::disable);
} }
void EmptyPasswordSslOnCacheMiss_SuccessfulLogin_RequiresSha256() void EmptyPasswordSslOnCacheMiss_SuccessfulLogin_RequiresSha256()
{ {
this->set_credentials("csha2p_empty_password_user", ""); this->set_credentials("csha2p_empty_password_user", "");
clear_sha256_cache(); clear_sha256_cache();
this->do_handshake_ok(ssl_mode::require); this->do_handshake_ok(ssl_mode::require);
} }
void EmptyPasswordSslOffCacheMiss_SuccessfulLogin_RequiresSha256() void EmptyPasswordSslOffCacheMiss_SuccessfulLogin_RequiresSha256()
{ {
// Empty passwords are allowed over non-TLS connections // Empty passwords are allowed over non-TLS connections
this->set_credentials("csha2p_empty_password_user", ""); this->set_credentials("csha2p_empty_password_user", "");
clear_sha256_cache(); clear_sha256_cache();
this->do_handshake_ok(ssl_mode::disable); this->do_handshake_ok(ssl_mode::disable);
} }
void BadPasswordSslOnCacheMiss_FailedLogin_RequiresSha256() void BadPasswordSslOnCacheMiss_FailedLogin_RequiresSha256()
{ {
this->set_ssl(ssl_mode::require); // Note: test over non-TLS would return "ssl required" this->set_ssl(ssl_mode::require); // Note: test over non-TLS would return "ssl required"
clear_sha256_cache(); clear_sha256_cache();
this->set_credentials("csha2p_user", "bad_password"); this->set_credentials("csha2p_user", "bad_password");
auto result = this->do_handshake(); auto result = this->do_handshake();
result.validate_error(errc::access_denied_error, {"access denied", "csha2p_user"}); result.validate_error(errc::access_denied_error, {"access denied", "csha2p_user"});
} }
void BadPasswordSslOnCacheHit_FailedLogin_RequiresSha256() void BadPasswordSslOnCacheHit_FailedLogin_RequiresSha256()
{ {
this->set_ssl(ssl_mode::require); // Note: test over non-TLS would return "ssl required" this->set_ssl(ssl_mode::require); // Note: test over non-TLS would return "ssl required"
this->set_credentials("csha2p_user", "bad_password"); this->set_credentials("csha2p_user", "bad_password");
load_sha256_cache("csha2p_user", "csha2p_password"); load_sha256_cache("csha2p_user", "csha2p_password");
auto result = this->do_handshake(); auto result = this->do_handshake();
result.validate_error(errc::access_denied_error, {"access denied", "csha2p_user"}); result.validate_error(errc::access_denied_error, {"access denied", "csha2p_user"});
} }
}; };
BOOST_MYSQL_NETWORK_TEST_SUITE(CachingSha2HandshakeTest) BOOST_MYSQL_NETWORK_TEST_SUITE(CachingSha2HandshakeTest)
@ -255,23 +255,23 @@ BOOST_MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, BadPasswordSslOnCacheHit_Fail
template <typename Stream> template <typename Stream>
struct MiscSslSensitiveHandshakeTest : SslSensitiveHandshakeTest<Stream> struct MiscSslSensitiveHandshakeTest : SslSensitiveHandshakeTest<Stream>
{ {
void BadUser_FailedLogin() void BadUser_FailedLogin()
{ {
// unreliable without SSL. If the default plugin requires SSL // unreliable without SSL. If the default plugin requires SSL
// (like SHA256), this would fail with 'ssl required' // (like SHA256), this would fail with 'ssl required'
this->set_ssl(ssl_mode::require); this->set_ssl(ssl_mode::require);
this->set_credentials("non_existing_user", "bad_password"); this->set_credentials("non_existing_user", "bad_password");
auto result = this->do_handshake(); auto result = this->do_handshake();
EXPECT_NE(result.err, error_code()); // may be access denied or unknown auth plugin EXPECT_NE(result.err, error_code()); // may be access denied or unknown auth plugin
} }
void SslEnable_SuccessfulLogin() void SslEnable_SuccessfulLogin()
{ {
// In all our CI systems, our servers support SSL, so // In all our CI systems, our servers support SSL, so
// ssl_mode::enable will do the same as ssl_mode::require. // ssl_mode::enable will do the same as ssl_mode::require.
// We test for this fact. // We test for this fact.
this->do_handshake_ok(ssl_mode::enable); this->do_handshake_ok(ssl_mode::enable);
} }
}; };
BOOST_MYSQL_NETWORK_TEST_SUITE(MiscSslSensitiveHandshakeTest) BOOST_MYSQL_NETWORK_TEST_SUITE(MiscSslSensitiveHandshakeTest)

View File

@ -26,15 +26,15 @@ namespace test {
inline void physical_connect(tcp_connection& conn) inline void physical_connect(tcp_connection& conn)
{ {
boost::asio::ip::tcp::endpoint endpoint (boost::asio::ip::address_v4::loopback(), 3306); boost::asio::ip::tcp::endpoint endpoint (boost::asio::ip::address_v4::loopback(), 3306);
conn.next_layer().connect(endpoint); conn.next_layer().connect(endpoint);
} }
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS #ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
inline void physical_connect(unix_connection& conn) inline void physical_connect(unix_connection& conn)
{ {
boost::asio::local::stream_protocol::endpoint endpoint ("/var/run/mysqld/mysqld.sock"); boost::asio::local::stream_protocol::endpoint endpoint ("/var/run/mysqld/mysqld.sock");
conn.next_layer().connect(endpoint); conn.next_layer().connect(endpoint);
} }
#endif #endif
@ -50,102 +50,102 @@ inline void physical_connect(unix_connection& conn)
template <typename Stream> template <typename Stream>
struct IntegTest : testing::Test struct IntegTest : testing::Test
{ {
using stream_type = Stream; using stream_type = Stream;
mysql::connection_params connection_params {"integ_user", "integ_password", "awesome"}; mysql::connection_params connection_params {"integ_user", "integ_password", "awesome"};
boost::asio::io_context ctx; boost::asio::io_context ctx;
mysql::connection<Stream> conn {ctx}; mysql::connection<Stream> conn {ctx};
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> guard { ctx.get_executor() }; boost::asio::executor_work_guard<boost::asio::io_context::executor_type> guard { ctx.get_executor() };
std::thread runner {[this]{ ctx.run(); } }; std::thread runner {[this]{ ctx.run(); } };
IntegTest() IntegTest()
{ {
try try
{ {
physical_connect(conn); physical_connect(conn);
} }
catch (...) // prevent terminate without an active exception on connect error catch (...) // prevent terminate without an active exception on connect error
{ {
guard.reset(); guard.reset();
runner.join(); runner.join();
throw; throw;
} }
} }
~IntegTest() ~IntegTest()
{ {
error_code code; error_code code;
conn.next_layer().close(code); conn.next_layer().close(code);
guard.reset(); guard.reset();
runner.join(); runner.join();
} }
void set_credentials(std::string_view user, std::string_view password) void set_credentials(std::string_view user, std::string_view password)
{ {
connection_params.set_username(user); connection_params.set_username(user);
connection_params.set_password(password); connection_params.set_password(password);
} }
void handshake(ssl_mode m = ssl_mode::require) void handshake(ssl_mode m = ssl_mode::require)
{ {
connection_params.set_ssl(ssl_options(m)); connection_params.set_ssl(ssl_options(m));
conn.handshake(connection_params); conn.handshake(connection_params);
validate_ssl(m); validate_ssl(m);
} }
static bool should_use_ssl(ssl_mode m) static bool should_use_ssl(ssl_mode m)
{ {
return m == ssl_mode::enable || m == ssl_mode::require; return m == ssl_mode::enable || m == ssl_mode::require;
} }
// Verifies that we are or are not using SSL, depending on what mode was requested. // Verifies that we are or are not using SSL, depending on what mode was requested.
void validate_ssl(ssl_mode m) void validate_ssl(ssl_mode m)
{ {
if (should_use_ssl(m)) if (should_use_ssl(m))
{ {
// All our test systems MUST support SSL to run these tests // All our test systems MUST support SSL to run these tests
EXPECT_TRUE(conn.uses_ssl()); EXPECT_TRUE(conn.uses_ssl());
} }
else else
{ {
EXPECT_FALSE(conn.uses_ssl()); EXPECT_FALSE(conn.uses_ssl());
} }
} }
void validate_eof( void validate_eof(
const resultset<Stream>& result, const resultset<Stream>& result,
int affected_rows=0, int affected_rows=0,
int warnings=0, int warnings=0,
int last_insert=0, int last_insert=0,
std::string_view info="" std::string_view info=""
) )
{ {
EXPECT_TRUE(result.valid()); EXPECT_TRUE(result.valid());
EXPECT_TRUE(result.complete()); EXPECT_TRUE(result.complete());
EXPECT_EQ(result.affected_rows(), affected_rows); EXPECT_EQ(result.affected_rows(), affected_rows);
EXPECT_EQ(result.warning_count(), warnings); EXPECT_EQ(result.warning_count(), warnings);
EXPECT_EQ(result.last_insert_id(), last_insert); EXPECT_EQ(result.last_insert_id(), last_insert);
EXPECT_EQ(result.info(), info); EXPECT_EQ(result.info(), info);
} }
void validate_2fields_meta( void validate_2fields_meta(
const std::vector<field_metadata>& fields, const std::vector<field_metadata>& fields,
const std::string& table const std::string& table
) const ) const
{ {
validate_meta(fields, { validate_meta(fields, {
meta_validator(table, "id", field_type::int_), meta_validator(table, "id", field_type::int_),
meta_validator(table, "field_varchar", field_type::varchar) meta_validator(table, "field_varchar", field_type::varchar)
}); });
} }
void validate_2fields_meta( void validate_2fields_meta(
const resultset<Stream>& result, const resultset<Stream>& result,
const std::string& table const std::string& table
) const ) const
{ {
validate_2fields_meta(result.fields(), table); validate_2fields_meta(result.fields(), table);
} }
}; };
@ -154,19 +154,19 @@ struct IntegTest : testing::Test
template <typename Stream> template <typename Stream>
struct network_testcase struct network_testcase
{ {
network_functions<Stream>* net; network_functions<Stream>* net;
std::string name() const { return net->name(); } std::string name() const { return net->name(); }
static std::vector<network_testcase<Stream>> make_all() static std::vector<network_testcase<Stream>> make_all()
{ {
std::vector<network_testcase<Stream>> res; std::vector<network_testcase<Stream>> res;
for (auto* net: make_all_network_functions<Stream>()) for (auto* net: make_all_network_functions<Stream>())
{ {
res.push_back(network_testcase<Stream>{net}); res.push_back(network_testcase<Stream>{net});
} }
return res; return res;
} }
}; };
// To be used as test parameter, when a test should be run over // To be used as test parameter, when a test should be run over
@ -174,26 +174,26 @@ struct network_testcase
template <typename Stream> template <typename Stream>
struct network_testcase_with_ssl struct network_testcase_with_ssl
{ {
network_functions<Stream>* net; network_functions<Stream>* net;
ssl_mode ssl; ssl_mode ssl;
std::string name() const std::string name() const
{ {
return detail::stringize(this->net->name(), '_', to_string(ssl)); return detail::stringize(this->net->name(), '_', to_string(ssl));
} }
static std::vector<network_testcase_with_ssl<Stream>> make_all() static std::vector<network_testcase_with_ssl<Stream>> make_all()
{ {
std::vector<network_testcase_with_ssl<Stream>> res; std::vector<network_testcase_with_ssl<Stream>> res;
for (auto* net: make_all_network_functions<Stream>()) for (auto* net: make_all_network_functions<Stream>())
{ {
for (auto ssl: {ssl_mode::require, ssl_mode::disable}) for (auto ssl: {ssl_mode::require, ssl_mode::disable})
{ {
res.push_back(network_testcase_with_ssl<Stream>{net, ssl}); res.push_back(network_testcase_with_ssl<Stream>{net, ssl});
} }
} }
return res; return res;
} }
}; };
/** /**
@ -217,20 +217,20 @@ struct network_testcase_with_ssl
* calling the method you defined in the fixture. * calling the method you defined in the fixture.
*/ */
template < template <
typename Stream, typename Stream,
typename Param=network_testcase_with_ssl<Stream>, typename Param=network_testcase_with_ssl<Stream>,
bool do_handshake=true bool do_handshake=true
> >
struct NetworkTest : public IntegTest<Stream>, struct NetworkTest : public IntegTest<Stream>,
public testing::WithParamInterface<Param> public testing::WithParamInterface<Param>
{ {
NetworkTest() NetworkTest()
{ {
if constexpr (do_handshake) if constexpr (do_handshake)
{ {
this->handshake(this->GetParam().ssl); this->handshake(this->GetParam().ssl);
} }
} }
}; };
} // test } // test
@ -239,66 +239,66 @@ struct NetworkTest : public IntegTest<Stream>,
// Typedefs // Typedefs
#define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_HELPER(TestSuiteName, Suffix, Stream) \ #define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_HELPER(TestSuiteName, Suffix, Stream) \
using TestSuiteName##Suffix = TestSuiteName<Stream>; using TestSuiteName##Suffix = TestSuiteName<Stream>;
#define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_TCP(TestSuiteName) \ #define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_TCP(TestSuiteName) \
BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_HELPER(TestSuiteName, TCP, boost::asio::ip::tcp::socket) BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_HELPER(TestSuiteName, TCP, boost::asio::ip::tcp::socket)
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS #ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
#define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_UNIX(TestSuiteName) \ #define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_UNIX(TestSuiteName) \
BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_HELPER(TestSuiteName, UNIX, boost::asio::local::stream_protocol::socket) BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_HELPER(TestSuiteName, UNIX, boost::asio::local::stream_protocol::socket)
#else #else
#define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_UNIX(TestSuiteName) #define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_UNIX(TestSuiteName)
#endif #endif
#define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS(TestSuiteName) \ #define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS(TestSuiteName) \
BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_TCP(TestSuiteName) \ BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_TCP(TestSuiteName) \
BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_UNIX(TestSuiteName) BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_UNIX(TestSuiteName)
// Test definition // Test definition
#define BOOST_MYSQL_NETWORK_TEST_HELPER(TestSuiteName, TestName, Suffix) \ #define BOOST_MYSQL_NETWORK_TEST_HELPER(TestSuiteName, TestName, Suffix) \
TEST_P(TestSuiteName##Suffix, TestName) { this->TestName(); } TEST_P(TestSuiteName##Suffix, TestName) { this->TestName(); }
#define BOOST_MYSQL_NETWORK_TEST_TCP(TestSuiteName, TestName) \ #define BOOST_MYSQL_NETWORK_TEST_TCP(TestSuiteName, TestName) \
BOOST_MYSQL_NETWORK_TEST_HELPER(TestSuiteName, TestName, TCP) BOOST_MYSQL_NETWORK_TEST_HELPER(TestSuiteName, TestName, TCP)
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS #ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
#define BOOST_MYSQL_NETWORK_TEST_UNIX(TestSuiteName, TestName) \ #define BOOST_MYSQL_NETWORK_TEST_UNIX(TestSuiteName, TestName) \
BOOST_MYSQL_NETWORK_TEST_HELPER(TestSuiteName, TestName, UNIX) BOOST_MYSQL_NETWORK_TEST_HELPER(TestSuiteName, TestName, UNIX)
#else #else
#define BOOST_MYSQL_NETWORK_TEST_UNIX(TestSuiteName, TestName) #define BOOST_MYSQL_NETWORK_TEST_UNIX(TestSuiteName, TestName)
#endif #endif
#define BOOST_MYSQL_NETWORK_TEST(TestSuiteName, TestName) \ #define BOOST_MYSQL_NETWORK_TEST(TestSuiteName, TestName) \
BOOST_MYSQL_NETWORK_TEST_TCP(TestSuiteName, TestName) \ BOOST_MYSQL_NETWORK_TEST_TCP(TestSuiteName, TestName) \
BOOST_MYSQL_NETWORK_TEST_UNIX(TestSuiteName, TestName) BOOST_MYSQL_NETWORK_TEST_UNIX(TestSuiteName, TestName)
// Test suite instantiation // Test suite instantiation
#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_HELPER(TestSuiteName, Suffix) \ #define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_HELPER(TestSuiteName, Suffix) \
INSTANTIATE_TEST_SUITE_P(Default, TestSuiteName##Suffix, testing::ValuesIn( \ INSTANTIATE_TEST_SUITE_P(Default, TestSuiteName##Suffix, testing::ValuesIn( \
TestSuiteName##Suffix::ParamType::make_all() \ TestSuiteName##Suffix::ParamType::make_all() \
), [](const auto& param_info) { \ ), [](const auto& param_info) { \
return param_info.param.name(); \ return param_info.param.name(); \
}); });
#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_TCP(TestSuiteName) \ #define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_TCP(TestSuiteName) \
BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_HELPER(TestSuiteName, TCP) BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_HELPER(TestSuiteName, TCP)
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS #ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_UNIX(TestSuiteName) \ #define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_UNIX(TestSuiteName) \
BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_HELPER(TestSuiteName, UNIX) BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_HELPER(TestSuiteName, UNIX)
#else #else
#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_UNIX(TestSuiteName) #define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_UNIX(TestSuiteName)
#endif #endif
#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE(TestSuiteName) \ #define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE(TestSuiteName) \
BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_TCP(TestSuiteName) \ BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_TCP(TestSuiteName) \
BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_UNIX(TestSuiteName) BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_UNIX(TestSuiteName)
// Typedefs + Instantiation // Typedefs + Instantiation
#define BOOST_MYSQL_NETWORK_TEST_SUITE(TestSuiteName) \ #define BOOST_MYSQL_NETWORK_TEST_SUITE(TestSuiteName) \
BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS(TestSuiteName) \ BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS(TestSuiteName) \
BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE(TestSuiteName) BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE(TestSuiteName)

View File

@ -11,80 +11,80 @@
using namespace boost::mysql::test; using namespace boost::mysql::test;
#define MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(getter) \ #define MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(getter) \
{ #getter, &boost::mysql::field_metadata::getter } { #getter, &boost::mysql::field_metadata::getter }
static struct flag_entry static struct flag_entry
{ {
const char* name; const char* name;
meta_validator::flag_getter getter; meta_validator::flag_getter getter;
} flag_names [] = { } flag_names [] = {
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_not_null), MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_not_null),
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_primary_key), MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_primary_key),
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_unique_key), MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_unique_key),
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_multiple_key), MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_multiple_key),
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_unsigned), MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_unsigned),
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_zerofill), MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_zerofill),
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_auto_increment), MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_auto_increment),
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(has_no_default_value), MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(has_no_default_value),
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_set_to_now_on_update) MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_set_to_now_on_update)
}; };
static bool contains( static bool contains(
meta_validator::flag_getter flag, meta_validator::flag_getter flag,
const std::vector<meta_validator::flag_getter>& flagvec const std::vector<meta_validator::flag_getter>& flagvec
) )
{ {
return std::find(flagvec.begin(), flagvec.end(), flag) != flagvec.end(); return std::find(flagvec.begin(), flagvec.end(), flag) != flagvec.end();
} }
void meta_validator::validate( void meta_validator::validate(
const mysql::field_metadata& value const mysql::field_metadata& value
) const ) const
{ {
// Fixed fields // Fixed fields
EXPECT_EQ(value.database(), "awesome"); EXPECT_EQ(value.database(), "awesome");
EXPECT_EQ(value.table(), table_); EXPECT_EQ(value.table(), table_);
EXPECT_EQ(value.original_table(), org_table_); EXPECT_EQ(value.original_table(), org_table_);
EXPECT_EQ(value.field_name(), field_); EXPECT_EQ(value.field_name(), field_);
EXPECT_EQ(value.original_field_name(), org_field_); EXPECT_EQ(value.original_field_name(), org_field_);
EXPECT_GT(value.column_length(), 0u); EXPECT_GT(value.column_length(), 0u);
EXPECT_EQ(value.type(), type_); EXPECT_EQ(value.type(), type_);
EXPECT_EQ(value.decimals(), decimals_); EXPECT_EQ(value.decimals(), decimals_);
// Flags // Flags
std::vector<flag_entry> all_flags (std::begin(flag_names), std::end(flag_names)); std::vector<flag_entry> all_flags (std::begin(flag_names), std::end(flag_names));
for (flag_getter true_flag: flags_) for (flag_getter true_flag: flags_)
{ {
auto it = std::find_if( auto it = std::find_if(
all_flags.begin(), all_flags.begin(),
all_flags.end(), all_flags.end(),
[true_flag](const flag_entry& entry) { return entry.getter == true_flag; } [true_flag](const flag_entry& entry) { return entry.getter == true_flag; }
); );
ASSERT_NE(it, all_flags.end()); // no repeated flag ASSERT_NE(it, all_flags.end()); // no repeated flag
ASSERT_FALSE(contains(true_flag, ignore_flags_)); // ignore flags cannot be set to true ASSERT_FALSE(contains(true_flag, ignore_flags_)); // ignore flags cannot be set to true
EXPECT_TRUE((value.*true_flag)()) << it->name; EXPECT_TRUE((value.*true_flag)()) << it->name;
all_flags.erase(it); all_flags.erase(it);
} }
for (const auto& entry: all_flags) for (const auto& entry: all_flags)
{ {
if (!contains(entry.getter, ignore_flags_)) if (!contains(entry.getter, ignore_flags_))
{ {
EXPECT_FALSE((value.*entry.getter)()) << entry.name; EXPECT_FALSE((value.*entry.getter)()) << entry.name;
} }
} }
} }
void boost::mysql::test::validate_meta( void boost::mysql::test::validate_meta(
const std::vector<field_metadata>& actual, const std::vector<field_metadata>& actual,
const std::vector<meta_validator>& expected const std::vector<meta_validator>& expected
) )
{ {
ASSERT_EQ(actual.size(), expected.size()); ASSERT_EQ(actual.size(), expected.size());
for (std::size_t i = 0; i < actual.size(); ++i) for (std::size_t i = 0; i < actual.size(); ++i)
{ {
expected[i].validate(actual[i]); expected[i].validate(actual[i]);
} }
} }

View File

@ -18,36 +18,36 @@ namespace test {
class meta_validator class meta_validator
{ {
public: public:
using flag_getter = bool (field_metadata::*)() const noexcept; using flag_getter = bool (field_metadata::*)() const noexcept;
meta_validator(std::string table, std::string field, field_type type, meta_validator(std::string table, std::string field, field_type type,
std::vector<flag_getter> flags={}, unsigned decimals=0, std::vector<flag_getter> flags={}, unsigned decimals=0,
std::vector<flag_getter> ignore_flags={}): std::vector<flag_getter> ignore_flags={}):
table_(std::move(table)), org_table_(table_), field_(std::move(field)), org_field_(field_), table_(std::move(table)), org_table_(table_), field_(std::move(field)), org_field_(field_),
decimals_(decimals), type_(type), flags_(std::move(flags)), decimals_(decimals), type_(type), flags_(std::move(flags)),
ignore_flags_(std::move(ignore_flags)) ignore_flags_(std::move(ignore_flags))
{ {
} }
meta_validator(std::string table, std::string org_table, meta_validator(std::string table, std::string org_table,
std::string field, std::string org_field, field_type type, std::string field, std::string org_field, field_type type,
std::vector<flag_getter> flags={}, unsigned decimals=0, std::vector<flag_getter> flags={}, unsigned decimals=0,
std::vector<flag_getter> ignore_flags={}): std::vector<flag_getter> ignore_flags={}):
table_(std::move(table)), org_table_(std::move(org_table)), table_(std::move(table)), org_table_(std::move(org_table)),
field_(std::move(field)), org_field_(std::move(org_field)), field_(std::move(field)), org_field_(std::move(org_field)),
decimals_(decimals), type_(type), flags_(std::move(flags)), decimals_(decimals), type_(type), flags_(std::move(flags)),
ignore_flags_(std::move(ignore_flags)) ignore_flags_(std::move(ignore_flags))
{ {
} }
void validate(const field_metadata& value) const; void validate(const field_metadata& value) const;
field_type type() const noexcept { return type_; } field_type type() const noexcept { return type_; }
private: private:
std::string table_; std::string table_;
std::string org_table_; std::string org_table_;
std::string field_; std::string field_;
std::string org_field_; std::string org_field_;
unsigned decimals_; unsigned decimals_;
field_type type_; field_type type_;
std::vector<flag_getter> flags_; std::vector<flag_getter> flags_;
std::vector<flag_getter> ignore_flags_; std::vector<flag_getter> ignore_flags_;
}; };
void validate_meta(const std::vector<field_metadata>& actual, const std::vector<meta_validator>& expected); void validate_meta(const std::vector<field_metadata>& actual, const std::vector<meta_validator>& expected);

File diff suppressed because it is too large Load Diff

View File

@ -50,46 +50,46 @@ struct no_result {};
template <typename T> template <typename T>
struct network_result struct network_result
{ {
error_code err; error_code err;
std::optional<error_info> info; // some async initiators (futures) don't support this std::optional<error_info> info; // some async initiators (futures) don't support this
T value; T value;
network_result() = default; network_result() = default;
network_result(error_code ec, error_info info, T&& value = {}): network_result(error_code ec, error_info info, T&& value = {}):
err(ec), info(std::move(info)), value(std::move(value)) {} err(ec), info(std::move(info)), value(std::move(value)) {}
network_result(error_code ec, T&& value = {}): network_result(error_code ec, T&& value = {}):
err(ec), value(std::move(value)) {} err(ec), value(std::move(value)) {}
void validate_no_error() const void validate_no_error() const
{ {
ASSERT_EQ(err, error_code()); ASSERT_EQ(err, error_code());
if (info) if (info)
{ {
EXPECT_EQ(*info, error_info()); EXPECT_EQ(*info, error_info());
} }
} }
void validate_error( void validate_error(
error_code expected_errc, error_code expected_errc,
const std::vector<std::string>& expected_msg const std::vector<std::string>& expected_msg
) const ) const
{ {
EXPECT_EQ(err, expected_errc); EXPECT_EQ(err, expected_errc);
if (info) if (info)
{ {
validate_string_contains(info->message(), expected_msg); validate_string_contains(info->message(), expected_msg);
} }
} }
void validate_error( void validate_error(
errc expected_errc, errc expected_errc,
const std::vector<std::string>& expected_msg const std::vector<std::string>& expected_msg
) const ) const
{ {
validate_error(detail::make_error_code(expected_errc), expected_msg); validate_error(detail::make_error_code(expected_errc), expected_msg);
} }
}; };
using value_list_it = std::forward_list<value>::const_iterator; using value_list_it = std::forward_list<value>::const_iterator;
@ -98,24 +98,24 @@ template <typename Stream>
class network_functions class network_functions
{ {
public: public:
using connection_type = connection<Stream>; using connection_type = connection<Stream>;
using prepared_statement_type = prepared_statement<Stream>; using prepared_statement_type = prepared_statement<Stream>;
using resultset_type = resultset<Stream>; using resultset_type = resultset<Stream>;
virtual ~network_functions() = default; virtual ~network_functions() = default;
virtual const char* name() const = 0; virtual const char* name() const = 0;
virtual network_result<no_result> handshake(connection_type&, const connection_params&) = 0; virtual network_result<no_result> handshake(connection_type&, const connection_params&) = 0;
virtual network_result<resultset_type> query(connection_type&, std::string_view query) = 0; virtual network_result<resultset_type> query(connection_type&, std::string_view query) = 0;
virtual network_result<prepared_statement_type> prepare_statement( virtual network_result<prepared_statement_type> prepare_statement(
connection_type&, std::string_view statement) = 0; connection_type&, std::string_view statement) = 0;
virtual network_result<resultset_type> execute_statement( virtual network_result<resultset_type> execute_statement(
prepared_statement_type&, value_list_it params_first, value_list_it params_last) = 0; prepared_statement_type&, value_list_it params_first, value_list_it params_last) = 0;
virtual network_result<resultset_type> execute_statement( virtual network_result<resultset_type> execute_statement(
prepared_statement_type&, const std::vector<value>&) = 0; prepared_statement_type&, const std::vector<value>&) = 0;
virtual network_result<no_result> close_statement(prepared_statement_type&) = 0; virtual network_result<no_result> close_statement(prepared_statement_type&) = 0;
virtual network_result<const row*> fetch_one(resultset_type&) = 0; virtual network_result<const row*> fetch_one(resultset_type&) = 0;
virtual network_result<std::vector<owning_row>> fetch_many(resultset_type&, std::size_t count) = 0; virtual network_result<std::vector<owning_row>> fetch_many(resultset_type&, std::size_t count) = 0;
virtual network_result<std::vector<owning_row>> fetch_all(resultset_type&) = 0; virtual network_result<std::vector<owning_row>> fetch_all(resultset_type&) = 0;
}; };
template <typename Stream> template <typename Stream>

View File

@ -20,35 +20,35 @@ namespace
template <typename Stream> template <typename Stream>
struct PrepareStatementTest : public NetworkTest<Stream> struct PrepareStatementTest : public NetworkTest<Stream>
{ {
auto do_prepare(std::string_view stmt) auto do_prepare(std::string_view stmt)
{ {
return this->GetParam().net->prepare_statement(this->conn, stmt); return this->GetParam().net->prepare_statement(this->conn, stmt);
} }
void OkNoParams() void OkNoParams()
{ {
auto stmt = do_prepare("SELECT * FROM empty_table"); auto stmt = do_prepare("SELECT * FROM empty_table");
stmt.validate_no_error(); stmt.validate_no_error();
ASSERT_TRUE(stmt.value.valid()); ASSERT_TRUE(stmt.value.valid());
EXPECT_GT(stmt.value.id(), 0u); EXPECT_GT(stmt.value.id(), 0u);
EXPECT_EQ(stmt.value.num_params(), 0); EXPECT_EQ(stmt.value.num_params(), 0);
} }
void OkWithParams() void OkWithParams()
{ {
auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)"); auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)");
stmt.validate_no_error(); stmt.validate_no_error();
ASSERT_TRUE(stmt.value.valid()); ASSERT_TRUE(stmt.value.valid());
EXPECT_GT(stmt.value.id(), 0u); EXPECT_GT(stmt.value.id(), 0u);
EXPECT_EQ(stmt.value.num_params(), 2); EXPECT_EQ(stmt.value.num_params(), 2);
} }
void InvalidStatement() void InvalidStatement()
{ {
auto stmt = do_prepare("SELECT * FROM bad_table WHERE id IN (?, ?)"); auto stmt = do_prepare("SELECT * FROM bad_table WHERE id IN (?, ?)");
stmt.validate_error(errc::no_such_table, {"table", "doesn't exist", "bad_table"}); stmt.validate_error(errc::no_such_table, {"table", "doesn't exist", "bad_table"});
EXPECT_FALSE(stmt.value.valid()); EXPECT_FALSE(stmt.value.valid());
} }
}; };
BOOST_MYSQL_NETWORK_TEST_SUITE(PrepareStatementTest) BOOST_MYSQL_NETWORK_TEST_SUITE(PrepareStatementTest)

View File

@ -17,219 +17,219 @@ namespace
template <typename Stream> template <typename Stream>
struct PreparedStatementLifecycleTest : NetworkTest<Stream> struct PreparedStatementLifecycleTest : NetworkTest<Stream>
{ {
std::int64_t get_table_size(const std::string& table) std::int64_t get_table_size(const std::string& table)
{ {
return std::get<std::int64_t>( return std::get<std::int64_t>(
this->conn.query("SELECT COUNT(*) FROM " + table).fetch_all().at(0).values().at(0)); this->conn.query("SELECT COUNT(*) FROM " + table).fetch_all().at(0).values().at(0));
} }
value get_updates_table_value(const std::string& field_varchar="f0") value get_updates_table_value(const std::string& field_varchar="f0")
{ {
return this->conn.query("SELECT field_int FROM updates_table WHERE field_varchar = '" + field_varchar + "'") return this->conn.query("SELECT field_int FROM updates_table WHERE field_varchar = '" + field_varchar + "'")
.fetch_all().at(0).values().at(0); .fetch_all().at(0).values().at(0);
} }
void SelectWithParametersMultipleExecutions() void SelectWithParametersMultipleExecutions()
{ {
auto* net = this->GetParam().net; auto* net = this->GetParam().net;
// Prepare a statement // Prepare a statement
auto stmt = net->prepare_statement( auto stmt = net->prepare_statement(
this->conn, this->conn,
"SELECT * FROM two_rows_table WHERE id = ? OR field_varchar = ?" "SELECT * FROM two_rows_table WHERE id = ? OR field_varchar = ?"
); );
stmt.validate_no_error(); stmt.validate_no_error();
// Execute it. Only one row will be returned (because of the id) // Execute it. Only one row will be returned (because of the id)
auto result = net->execute_statement(stmt.value, makevalues(1, "non_existent")); auto result = net->execute_statement(stmt.value, makevalues(1, "non_existent"));
result.validate_no_error(); result.validate_no_error();
EXPECT_TRUE(result.value.valid()); EXPECT_TRUE(result.value.valid());
EXPECT_FALSE(result.value.complete()); EXPECT_FALSE(result.value.complete());
this->validate_2fields_meta(result.value, "two_rows_table"); this->validate_2fields_meta(result.value, "two_rows_table");
auto rows = net->fetch_all(result.value); auto rows = net->fetch_all(result.value);
rows.validate_no_error(); rows.validate_no_error();
ASSERT_EQ(rows.value.size(), 1); ASSERT_EQ(rows.value.size(), 1);
EXPECT_EQ(static_cast<const row&>(rows.value[0]), (makerow(1, "f0"))); EXPECT_EQ(static_cast<const row&>(rows.value[0]), (makerow(1, "f0")));
EXPECT_TRUE(result.value.complete()); EXPECT_TRUE(result.value.complete());
// Execute it again, but with different values. This time, two rows are returned // Execute it again, but with different values. This time, two rows are returned
result = net->execute_statement(stmt.value, makevalues(1, "f1")); result = net->execute_statement(stmt.value, makevalues(1, "f1"));
result.validate_no_error(); result.validate_no_error();
EXPECT_TRUE(result.value.valid()); EXPECT_TRUE(result.value.valid());
EXPECT_FALSE(result.value.complete()); EXPECT_FALSE(result.value.complete());
this->validate_2fields_meta(result.value, "two_rows_table"); this->validate_2fields_meta(result.value, "two_rows_table");
rows = net->fetch_all(result.value); rows = net->fetch_all(result.value);
rows.validate_no_error(); rows.validate_no_error();
ASSERT_EQ(rows.value.size(), 2); ASSERT_EQ(rows.value.size(), 2);
EXPECT_EQ(static_cast<const row&>(rows.value[0]), (makerow(1, "f0"))); EXPECT_EQ(static_cast<const row&>(rows.value[0]), (makerow(1, "f0")));
EXPECT_EQ(static_cast<const row&>(rows.value[1]), (makerow(2, "f1"))); EXPECT_EQ(static_cast<const row&>(rows.value[1]), (makerow(2, "f1")));
EXPECT_TRUE(result.value.complete()); EXPECT_TRUE(result.value.complete());
// Close it // Close it
auto close_result = net->close_statement(stmt.value); auto close_result = net->close_statement(stmt.value);
close_result.validate_no_error(); close_result.validate_no_error();
} }
void InsertWithParametersMultipleExecutions() void InsertWithParametersMultipleExecutions()
{ {
auto* net = this->GetParam().net; auto* net = this->GetParam().net;
// Get the number of rows before insertion // Get the number of rows before insertion
auto rows_before = get_table_size("inserts_table"); auto rows_before = get_table_size("inserts_table");
// Prepare a statement // Prepare a statement
auto stmt = net->prepare_statement( auto stmt = net->prepare_statement(
this->conn, this->conn,
"INSERT INTO inserts_table (field_varchar) VALUES (?)" "INSERT INTO inserts_table (field_varchar) VALUES (?)"
); );
stmt.validate_no_error(); stmt.validate_no_error();
// Insert one value // Insert one value
auto result = net->execute_statement(stmt.value, makevalues("value0")); auto result = net->execute_statement(stmt.value, makevalues("value0"));
result.validate_no_error(); result.validate_no_error();
EXPECT_TRUE(result.value.valid()); EXPECT_TRUE(result.value.valid());
EXPECT_TRUE(result.value.complete()); EXPECT_TRUE(result.value.complete());
EXPECT_TRUE(result.value.fields().empty()); EXPECT_TRUE(result.value.fields().empty());
// Insert another one // Insert another one
result = net->execute_statement(stmt.value, makevalues("value1")); result = net->execute_statement(stmt.value, makevalues("value1"));
result.validate_no_error(); result.validate_no_error();
EXPECT_TRUE(result.value.valid()); EXPECT_TRUE(result.value.valid());
EXPECT_TRUE(result.value.complete()); EXPECT_TRUE(result.value.complete());
EXPECT_TRUE(result.value.fields().empty()); EXPECT_TRUE(result.value.fields().empty());
// Validate we did something // Validate we did something
auto rows_after = get_table_size("inserts_table"); auto rows_after = get_table_size("inserts_table");
EXPECT_EQ(rows_after, rows_before + 2); EXPECT_EQ(rows_after, rows_before + 2);
// Close it // Close it
auto close_result = net->close_statement(stmt.value); auto close_result = net->close_statement(stmt.value);
close_result.validate_no_error(); close_result.validate_no_error();
} }
void UpdateWithParametersMultipleExecutions() void UpdateWithParametersMultipleExecutions()
{ {
auto* net = this->GetParam().net; auto* net = this->GetParam().net;
// Prepare a statement // Prepare a statement
auto stmt = net->prepare_statement( auto stmt = net->prepare_statement(
this->conn, this->conn,
"UPDATE updates_table SET field_int = ? WHERE field_varchar = ?" "UPDATE updates_table SET field_int = ? WHERE field_varchar = ?"
); );
stmt.validate_no_error(); stmt.validate_no_error();
// Set field_int to something // Set field_int to something
auto result = net->execute_statement(stmt.value, makevalues(200, "f0")); auto result = net->execute_statement(stmt.value, makevalues(200, "f0"));
result.validate_no_error(); result.validate_no_error();
EXPECT_TRUE(result.value.valid()); EXPECT_TRUE(result.value.valid());
EXPECT_TRUE(result.value.complete()); EXPECT_TRUE(result.value.complete());
EXPECT_TRUE(result.value.fields().empty()); EXPECT_TRUE(result.value.fields().empty());
// Verify that took effect // Verify that took effect
EXPECT_EQ(get_updates_table_value(), value(std::int32_t(200))); EXPECT_EQ(get_updates_table_value(), value(std::int32_t(200)));
// Set field_int to something different // Set field_int to something different
result = net->execute_statement(stmt.value, makevalues(250, "f0")); result = net->execute_statement(stmt.value, makevalues(250, "f0"));
result.validate_no_error(); result.validate_no_error();
EXPECT_TRUE(result.value.valid()); EXPECT_TRUE(result.value.valid());
EXPECT_TRUE(result.value.complete()); EXPECT_TRUE(result.value.complete());
EXPECT_TRUE(result.value.fields().empty()); EXPECT_TRUE(result.value.fields().empty());
// Verify that took effect // Verify that took effect
EXPECT_EQ(get_updates_table_value(), value(std::int32_t(250))); EXPECT_EQ(get_updates_table_value(), value(std::int32_t(250)));
// Close the statement // Close the statement
auto close_result = net->close_statement(stmt.value); auto close_result = net->close_statement(stmt.value);
close_result.validate_no_error(); close_result.validate_no_error();
} }
void MultipleStatements() void MultipleStatements()
{ {
auto* net = this->GetParam().net; auto* net = this->GetParam().net;
// Prepare an update // Prepare an update
auto stmt_update = net->prepare_statement( auto stmt_update = net->prepare_statement(
this->conn, this->conn,
"UPDATE updates_table SET field_int = ? WHERE field_varchar = ?" "UPDATE updates_table SET field_int = ? WHERE field_varchar = ?"
); );
stmt_update.validate_no_error(); stmt_update.validate_no_error();
// Prepare a select // Prepare a select
auto stmt_select = net->prepare_statement( auto stmt_select = net->prepare_statement(
this->conn, this->conn,
"SELECT field_int FROM updates_table WHERE field_varchar = ?" "SELECT field_int FROM updates_table WHERE field_varchar = ?"
); );
stmt_select.validate_no_error(); stmt_select.validate_no_error();
// They have different IDs // They have different IDs
EXPECT_NE(stmt_update.value.id(), stmt_select.value.id()); EXPECT_NE(stmt_update.value.id(), stmt_select.value.id());
// Execute update // Execute update
auto update_result = net->execute_statement(stmt_update.value, makevalues(210, "f0")); auto update_result = net->execute_statement(stmt_update.value, makevalues(210, "f0"));
update_result.validate_no_error(); update_result.validate_no_error();
EXPECT_TRUE(update_result.value.complete()); EXPECT_TRUE(update_result.value.complete());
// Execute select // Execute select
auto select_result = net->execute_statement(stmt_select.value, makevalues("f0")); auto select_result = net->execute_statement(stmt_select.value, makevalues("f0"));
select_result.validate_no_error(); select_result.validate_no_error();
auto rows = net->fetch_all(select_result.value); auto rows = net->fetch_all(select_result.value);
rows.validate_no_error(); rows.validate_no_error();
EXPECT_EQ(rows.value.size(), 1); EXPECT_EQ(rows.value.size(), 1);
EXPECT_EQ(static_cast<row&>(rows.value[0]), makerow(210)); EXPECT_EQ(static_cast<row&>(rows.value[0]), makerow(210));
// Execute update again // Execute update again
update_result = net->execute_statement(stmt_update.value, makevalues(220, "f0")); update_result = net->execute_statement(stmt_update.value, makevalues(220, "f0"));
update_result.validate_no_error(); update_result.validate_no_error();
EXPECT_TRUE(update_result.value.complete()); EXPECT_TRUE(update_result.value.complete());
// Update no longer needed, close it // Update no longer needed, close it
auto close_result = net->close_statement(stmt_update.value); auto close_result = net->close_statement(stmt_update.value);
close_result.validate_no_error(); close_result.validate_no_error();
// Execute select again // Execute select again
select_result = net->execute_statement(stmt_select.value, makevalues("f0")); select_result = net->execute_statement(stmt_select.value, makevalues("f0"));
select_result.validate_no_error(); select_result.validate_no_error();
rows = net->fetch_all(select_result.value); rows = net->fetch_all(select_result.value);
rows.validate_no_error(); rows.validate_no_error();
EXPECT_EQ(rows.value.size(), 1); EXPECT_EQ(rows.value.size(), 1);
EXPECT_EQ(static_cast<row&>(rows.value[0]), makerow(220)); EXPECT_EQ(static_cast<row&>(rows.value[0]), makerow(220));
// Close select // Close select
close_result = net->close_statement(stmt_select.value); close_result = net->close_statement(stmt_select.value);
close_result.validate_no_error(); close_result.validate_no_error();
} }
void InsertWithNullValues() void InsertWithNullValues()
{ {
auto* net = this->GetParam().net; auto* net = this->GetParam().net;
// Statement to perform the updates // Statement to perform the updates
auto stmt = net->prepare_statement( auto stmt = net->prepare_statement(
this->conn, this->conn,
"UPDATE updates_table SET field_int = ? WHERE field_varchar = 'fnull'" "UPDATE updates_table SET field_int = ? WHERE field_varchar = 'fnull'"
); );
stmt.validate_no_error(); stmt.validate_no_error();
// Set the value we will be updating to something non-NULL // Set the value we will be updating to something non-NULL
auto result = net->execute_statement(stmt.value, makevalues(42)); auto result = net->execute_statement(stmt.value, makevalues(42));
result.validate_no_error(); result.validate_no_error();
// Verify it took effect // Verify it took effect
ASSERT_EQ(get_updates_table_value("fnull"), value(std::int32_t(42))); ASSERT_EQ(get_updates_table_value("fnull"), value(std::int32_t(42)));
// Update the value to NULL // Update the value to NULL
result = net->execute_statement(stmt.value, makevalues(nullptr)); result = net->execute_statement(stmt.value, makevalues(nullptr));
result.validate_no_error(); result.validate_no_error();
// Verify it took effect // Verify it took effect
ASSERT_EQ(get_updates_table_value("fnull"), value(nullptr)); ASSERT_EQ(get_updates_table_value("fnull"), value(nullptr));
// Close statement // Close statement
auto close_result = net->close_statement(stmt.value); auto close_result = net->close_statement(stmt.value);
close_result.validate_no_error(); close_result.validate_no_error();
} }
}; };
BOOST_MYSQL_NETWORK_TEST_SUITE(PreparedStatementLifecycleTest) BOOST_MYSQL_NETWORK_TEST_SUITE(PreparedStatementLifecycleTest)

View File

@ -26,72 +26,72 @@ namespace
template <typename Stream> template <typename Stream>
struct QueryTest : public NetworkTest<Stream> struct QueryTest : public NetworkTest<Stream>
{ {
auto do_query(std::string_view sql) auto do_query(std::string_view sql)
{ {
return this->GetParam().net->query(this->conn, sql); return this->GetParam().net->query(this->conn, sql);
} }
void InsertQueryOk() void InsertQueryOk()
{ {
const char* sql = "INSERT INTO inserts_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')"; const char* sql = "INSERT INTO inserts_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')";
auto result = do_query(sql); auto result = do_query(sql);
result.validate_no_error(); result.validate_no_error();
EXPECT_TRUE(result.value.fields().empty()); EXPECT_TRUE(result.value.fields().empty());
EXPECT_TRUE(result.value.valid()); EXPECT_TRUE(result.value.valid());
EXPECT_TRUE(result.value.complete()); EXPECT_TRUE(result.value.complete());
EXPECT_EQ(result.value.affected_rows(), 1); EXPECT_EQ(result.value.affected_rows(), 1);
EXPECT_EQ(result.value.warning_count(), 0); EXPECT_EQ(result.value.warning_count(), 0);
EXPECT_GT(result.value.last_insert_id(), 0); EXPECT_GT(result.value.last_insert_id(), 0);
EXPECT_EQ(result.value.info(), ""); EXPECT_EQ(result.value.info(), "");
} }
void InsertQueryFailed() void InsertQueryFailed()
{ {
const char* sql = "INSERT INTO bad_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')"; const char* sql = "INSERT INTO bad_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')";
auto result = do_query(sql); auto result = do_query(sql);
result.validate_error(errc::no_such_table, {"table", "doesn't exist", "bad_table"}); result.validate_error(errc::no_such_table, {"table", "doesn't exist", "bad_table"});
EXPECT_FALSE(result.value.valid()); EXPECT_FALSE(result.value.valid());
} }
void UpdateQueryOk() void UpdateQueryOk()
{ {
const char* sql = "UPDATE updates_table SET field_int = field_int+1"; const char* sql = "UPDATE updates_table SET field_int = field_int+1";
auto result = do_query(sql); auto result = do_query(sql);
result.validate_no_error(); result.validate_no_error();
EXPECT_TRUE(result.value.fields().empty()); EXPECT_TRUE(result.value.fields().empty());
EXPECT_TRUE(result.value.valid()); EXPECT_TRUE(result.value.valid());
EXPECT_TRUE(result.value.complete()); EXPECT_TRUE(result.value.complete());
EXPECT_EQ(result.value.affected_rows(), 2); EXPECT_EQ(result.value.affected_rows(), 2);
EXPECT_EQ(result.value.warning_count(), 0); EXPECT_EQ(result.value.warning_count(), 0);
EXPECT_EQ(result.value.last_insert_id(), 0); EXPECT_EQ(result.value.last_insert_id(), 0);
EXPECT_THAT(std::string(result.value.info()), HasSubstr("Rows matched")); EXPECT_THAT(std::string(result.value.info()), HasSubstr("Rows matched"));
} }
void SelectOk() void SelectOk()
{ {
auto result = do_query("SELECT * FROM empty_table"); auto result = do_query("SELECT * FROM empty_table");
result.validate_no_error(); result.validate_no_error();
EXPECT_TRUE(result.value.valid()); EXPECT_TRUE(result.value.valid());
EXPECT_FALSE(result.value.complete()); EXPECT_FALSE(result.value.complete());
this->validate_2fields_meta(result.value, "empty_table"); this->validate_2fields_meta(result.value, "empty_table");
} }
void SelectQueryFailed() void SelectQueryFailed()
{ {
auto result = do_query("SELECT field_varchar, field_bad FROM one_row_table"); auto result = do_query("SELECT field_varchar, field_bad FROM one_row_table");
result.validate_error(errc::bad_field_error, {"unknown column", "field_bad"}); result.validate_error(errc::bad_field_error, {"unknown column", "field_bad"});
EXPECT_FALSE(result.value.valid()); EXPECT_FALSE(result.value.valid());
} }
// Some system-level query tests (TODO: this does not feel right here) // Some system-level query tests (TODO: this does not feel right here)
void QueryAndFetch_AliasedTableAndField_MetadataCorrect() void QueryAndFetch_AliasedTableAndField_MetadataCorrect()
{ {
auto result = do_query("SELECT field_varchar AS field_alias FROM empty_table table_alias"); auto result = do_query("SELECT field_varchar AS field_alias FROM empty_table table_alias");
meta_validator validator ("table_alias", "empty_table", "field_alias", meta_validator validator ("table_alias", "empty_table", "field_alias",
"field_varchar", field_type::varchar); "field_varchar", field_type::varchar);
result.validate_no_error(); result.validate_no_error();
validate_meta(result.value.fields(), {validator}); validate_meta(result.value.fields(), {validator});
} }
}; };
BOOST_MYSQL_NETWORK_TEST_SUITE(QueryTest) BOOST_MYSQL_NETWORK_TEST_SUITE(QueryTest)

View File

@ -28,31 +28,31 @@ template <typename Stream>
class resultset_generator class resultset_generator
{ {
public: public:
virtual ~resultset_generator() {} virtual ~resultset_generator() {}
virtual const char* name() const = 0; virtual const char* name() const = 0;
virtual resultset<Stream> generate(connection<Stream>&, std::string_view) = 0; virtual resultset<Stream> generate(connection<Stream>&, std::string_view) = 0;
}; };
template <typename Stream> template <typename Stream>
class text_resultset_generator : public resultset_generator<Stream> class text_resultset_generator : public resultset_generator<Stream>
{ {
public: public:
const char* name() const override { return "text"; } const char* name() const override { return "text"; }
resultset<Stream> generate(connection<Stream>& conn, std::string_view query) override resultset<Stream> generate(connection<Stream>& conn, std::string_view query) override
{ {
return conn.query(query); return conn.query(query);
} }
}; };
template <typename Stream> template <typename Stream>
class binary_resultset_generator : public resultset_generator<Stream> class binary_resultset_generator : public resultset_generator<Stream>
{ {
public: public:
const char* name() const override { return "binary"; } const char* name() const override { return "binary"; }
resultset<Stream> generate(connection<Stream>& conn, std::string_view query) override resultset<Stream> generate(connection<Stream>& conn, std::string_view query) override
{ {
return conn.prepare_statement(query).execute(boost::mysql::no_statement_params); return conn.prepare_statement(query).execute(boost::mysql::no_statement_params);
} }
}; };
template <typename Stream> text_resultset_generator<Stream> text_obj; template <typename Stream> text_resultset_generator<Stream> text_obj;
@ -62,233 +62,233 @@ template <typename Stream> binary_resultset_generator<Stream> binary_obj;
template <typename Stream> template <typename Stream>
struct resultset_testcase : network_testcase_with_ssl<Stream> struct resultset_testcase : network_testcase_with_ssl<Stream>
{ {
resultset_generator<Stream>* gen; resultset_generator<Stream>* gen;
resultset_testcase(network_testcase_with_ssl<Stream> base, resultset_generator<Stream>* gen): resultset_testcase(network_testcase_with_ssl<Stream> base, resultset_generator<Stream>* gen):
network_testcase_with_ssl<Stream>(base), gen(gen) {} network_testcase_with_ssl<Stream>(base), gen(gen) {}
std::string name() const std::string name() const
{ {
return network_testcase_with_ssl<Stream>::name() + '_' + gen->name(); return network_testcase_with_ssl<Stream>::name() + '_' + gen->name();
} }
static std::vector<resultset_testcase<Stream>> make_all() static std::vector<resultset_testcase<Stream>> make_all()
{ {
resultset_generator<Stream>* all_resultset_generators [] = { resultset_generator<Stream>* all_resultset_generators [] = {
&text_obj<Stream>, &text_obj<Stream>,
&binary_obj<Stream> &binary_obj<Stream>
}; };
std::vector<resultset_testcase<Stream>> res; std::vector<resultset_testcase<Stream>> res;
for (auto* gen: all_resultset_generators) for (auto* gen: all_resultset_generators)
{ {
for (auto base: network_testcase_with_ssl<Stream>::make_all()) for (auto base: network_testcase_with_ssl<Stream>::make_all())
{ {
res.push_back(resultset_testcase<Stream>(base, gen)); res.push_back(resultset_testcase<Stream>(base, gen));
} }
} }
return res; return res;
} }
}; };
template <typename Stream> template <typename Stream>
struct ResultsetTest : public NetworkTest<Stream, resultset_testcase<Stream>, true> struct ResultsetTest : public NetworkTest<Stream, resultset_testcase<Stream>, true>
{ {
auto do_generate(std::string_view query) { return this->GetParam().gen->generate(this->conn, query); } auto do_generate(std::string_view query) { return this->GetParam().gen->generate(this->conn, query); }
auto do_fetch_one(resultset<Stream>& r) { return this->GetParam().net->fetch_one(r); } auto do_fetch_one(resultset<Stream>& r) { return this->GetParam().net->fetch_one(r); }
auto do_fetch_many(resultset<Stream>& r, std::size_t count) { return this->GetParam().net->fetch_many(r, count); } auto do_fetch_many(resultset<Stream>& r, std::size_t count) { return this->GetParam().net->fetch_many(r, count); }
auto do_fetch_all(resultset<Stream>& r) { return this->GetParam().net->fetch_all(r); } auto do_fetch_all(resultset<Stream>& r) { return this->GetParam().net->fetch_all(r); }
// FetchOne // FetchOne
void FetchOne_NoResults() void FetchOne_NoResults()
{ {
auto result = do_generate("SELECT * FROM empty_table"); auto result = do_generate("SELECT * FROM empty_table");
EXPECT_TRUE(result.valid()); EXPECT_TRUE(result.valid());
EXPECT_FALSE(result.complete()); EXPECT_FALSE(result.complete());
EXPECT_EQ(result.fields().size(), 2); EXPECT_EQ(result.fields().size(), 2);
// Already in the end of the resultset, we receive the EOF // Already in the end of the resultset, we receive the EOF
auto row_result = do_fetch_one(result); auto row_result = do_fetch_one(result);
row_result.validate_no_error(); row_result.validate_no_error();
EXPECT_EQ(row_result.value, nullptr); EXPECT_EQ(row_result.value, nullptr);
this->validate_eof(result); this->validate_eof(result);
// Fetching again just returns null // Fetching again just returns null
row_result = do_fetch_one(result); row_result = do_fetch_one(result);
row_result.validate_no_error(); row_result.validate_no_error();
EXPECT_EQ(row_result.value, nullptr); EXPECT_EQ(row_result.value, nullptr);
this->validate_eof(result); this->validate_eof(result);
} }
void FetchOne_OneRow() void FetchOne_OneRow()
{ {
auto result = do_generate("SELECT * FROM one_row_table"); auto result = do_generate("SELECT * FROM one_row_table");
EXPECT_TRUE(result.valid()); EXPECT_TRUE(result.valid());
EXPECT_FALSE(result.complete()); EXPECT_FALSE(result.complete());
EXPECT_EQ(result.fields().size(), 2); EXPECT_EQ(result.fields().size(), 2);
// Fetch only row // Fetch only row
auto row_result = do_fetch_one(result); auto row_result = do_fetch_one(result);
row_result.validate_no_error(); row_result.validate_no_error();
ASSERT_NE(row_result.value, nullptr); ASSERT_NE(row_result.value, nullptr);
this->validate_2fields_meta(result, "one_row_table"); this->validate_2fields_meta(result, "one_row_table");
EXPECT_EQ(row_result.value->values(), makevalues(1, "f0")); EXPECT_EQ(row_result.value->values(), makevalues(1, "f0"));
EXPECT_FALSE(result.complete()); EXPECT_FALSE(result.complete());
// Fetch next: end of resultset // Fetch next: end of resultset
row_result = do_fetch_one(result); row_result = do_fetch_one(result);
row_result.validate_no_error(); row_result.validate_no_error();
ASSERT_EQ(row_result.value, nullptr); ASSERT_EQ(row_result.value, nullptr);
this->validate_eof(result); this->validate_eof(result);
} }
void FetchOne_TwoRows() void FetchOne_TwoRows()
{ {
auto result = do_generate("SELECT * FROM two_rows_table"); auto result = do_generate("SELECT * FROM two_rows_table");
EXPECT_TRUE(result.valid()); EXPECT_TRUE(result.valid());
EXPECT_FALSE(result.complete()); EXPECT_FALSE(result.complete());
EXPECT_EQ(result.fields().size(), 2); EXPECT_EQ(result.fields().size(), 2);
// Fetch first row // Fetch first row
auto row_result = do_fetch_one(result); auto row_result = do_fetch_one(result);
row_result.validate_no_error(); row_result.validate_no_error();
ASSERT_NE(row_result.value, nullptr); ASSERT_NE(row_result.value, nullptr);
this->validate_2fields_meta(result, "two_rows_table"); this->validate_2fields_meta(result, "two_rows_table");
EXPECT_EQ(row_result.value->values(), makevalues(1, "f0")); EXPECT_EQ(row_result.value->values(), makevalues(1, "f0"));
EXPECT_FALSE(result.complete()); EXPECT_FALSE(result.complete());
// Fetch next row // Fetch next row
row_result = do_fetch_one(result); row_result = do_fetch_one(result);
row_result.validate_no_error(); row_result.validate_no_error();
ASSERT_NE(row_result.value, nullptr); ASSERT_NE(row_result.value, nullptr);
this->validate_2fields_meta(result, "two_rows_table"); this->validate_2fields_meta(result, "two_rows_table");
EXPECT_EQ(row_result.value->values(), makevalues(2, "f1")); EXPECT_EQ(row_result.value->values(), makevalues(2, "f1"));
EXPECT_FALSE(result.complete()); EXPECT_FALSE(result.complete());
// Fetch next: end of resultset // Fetch next: end of resultset
row_result = do_fetch_one(result); row_result = do_fetch_one(result);
row_result.validate_no_error(); row_result.validate_no_error();
ASSERT_EQ(row_result.value, nullptr); ASSERT_EQ(row_result.value, nullptr);
this->validate_eof(result); this->validate_eof(result);
} }
// There seems to be no real case where fetch can fail (other than net fails) // There seems to be no real case where fetch can fail (other than net fails)
// FetchMany // FetchMany
void FetchMany_NoResults() void FetchMany_NoResults()
{ {
auto result = do_generate("SELECT * FROM empty_table"); auto result = do_generate("SELECT * FROM empty_table");
// Fetch many, but there are no results // Fetch many, but there are no results
auto rows_result = do_fetch_many(result, 10); auto rows_result = do_fetch_many(result, 10);
rows_result.validate_no_error(); rows_result.validate_no_error();
EXPECT_TRUE(rows_result.value.empty()); EXPECT_TRUE(rows_result.value.empty());
EXPECT_TRUE(result.complete()); EXPECT_TRUE(result.complete());
this->validate_eof(result); this->validate_eof(result);
// Fetch again, should return OK and empty // Fetch again, should return OK and empty
rows_result = do_fetch_many(result, 10); rows_result = do_fetch_many(result, 10);
rows_result.validate_no_error(); rows_result.validate_no_error();
EXPECT_TRUE(rows_result.value.empty()); EXPECT_TRUE(rows_result.value.empty());
EXPECT_TRUE(result.complete()); EXPECT_TRUE(result.complete());
this->validate_eof(result); this->validate_eof(result);
} }
void FetchMany_MoreRowsThanCount() void FetchMany_MoreRowsThanCount()
{ {
auto result = do_generate("SELECT * FROM three_rows_table"); auto result = do_generate("SELECT * FROM three_rows_table");
// Fetch 2, one remaining // Fetch 2, one remaining
auto rows_result = do_fetch_many(result, 2); auto rows_result = do_fetch_many(result, 2);
rows_result.validate_no_error(); rows_result.validate_no_error();
EXPECT_FALSE(result.complete()); EXPECT_FALSE(result.complete());
EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1"))); EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1")));
// Fetch another two (completes the resultset) // Fetch another two (completes the resultset)
rows_result = do_fetch_many(result, 2); rows_result = do_fetch_many(result, 2);
rows_result.validate_no_error(); rows_result.validate_no_error();
EXPECT_TRUE(result.complete()); EXPECT_TRUE(result.complete());
this->validate_eof(result); this->validate_eof(result);
EXPECT_EQ(rows_result.value, (makerows(2, 3, "f2"))); EXPECT_EQ(rows_result.value, (makerows(2, 3, "f2")));
} }
void FetchMany_LessRowsThanCount() void FetchMany_LessRowsThanCount()
{ {
auto result = do_generate("SELECT * FROM two_rows_table"); auto result = do_generate("SELECT * FROM two_rows_table");
// Fetch 3, resultset exhausted // Fetch 3, resultset exhausted
auto rows_result = do_fetch_many(result, 3); auto rows_result = do_fetch_many(result, 3);
rows_result.validate_no_error(); rows_result.validate_no_error();
EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1"))); EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1")));
this->validate_eof(result); this->validate_eof(result);
} }
void FetchMany_SameRowsAsCount() void FetchMany_SameRowsAsCount()
{ {
auto result = do_generate("SELECT * FROM two_rows_table"); auto result = do_generate("SELECT * FROM two_rows_table");
// Fetch 2, 0 remaining but resultset not exhausted // Fetch 2, 0 remaining but resultset not exhausted
auto rows_result = do_fetch_many(result, 2); auto rows_result = do_fetch_many(result, 2);
rows_result.validate_no_error(); rows_result.validate_no_error();
EXPECT_FALSE(result.complete()); EXPECT_FALSE(result.complete());
EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1"))); EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1")));
// Fetch again, exhausts the resultset // Fetch again, exhausts the resultset
rows_result = do_fetch_many(result, 2); rows_result = do_fetch_many(result, 2);
rows_result.validate_no_error(); rows_result.validate_no_error();
EXPECT_EQ(rows_result.value.size(), 0); EXPECT_EQ(rows_result.value.size(), 0);
this->validate_eof(result); this->validate_eof(result);
} }
void FetchMany_CountEqualsOne() void FetchMany_CountEqualsOne()
{ {
auto result = do_generate("SELECT * FROM one_row_table"); auto result = do_generate("SELECT * FROM one_row_table");
// Fetch 1, 1 remaining // Fetch 1, 1 remaining
auto rows_result = do_fetch_many(result, 1); auto rows_result = do_fetch_many(result, 1);
rows_result.validate_no_error(); rows_result.validate_no_error();
EXPECT_FALSE(result.complete()); EXPECT_FALSE(result.complete());
EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0"))); EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0")));
} }
// FetchAll // FetchAll
void FetchAll_NoResults() void FetchAll_NoResults()
{ {
auto result = do_generate("SELECT * FROM empty_table"); auto result = do_generate("SELECT * FROM empty_table");
// Fetch many, but there are no results // Fetch many, but there are no results
auto rows_result = do_fetch_all(result); auto rows_result = do_fetch_all(result);
rows_result.validate_no_error(); rows_result.validate_no_error();
EXPECT_TRUE(rows_result.value.empty()); EXPECT_TRUE(rows_result.value.empty());
EXPECT_TRUE(result.complete()); EXPECT_TRUE(result.complete());
// Fetch again, should return OK and empty // Fetch again, should return OK and empty
rows_result = do_fetch_all(result); rows_result = do_fetch_all(result);
rows_result.validate_no_error(); rows_result.validate_no_error();
EXPECT_TRUE(rows_result.value.empty()); EXPECT_TRUE(rows_result.value.empty());
this->validate_eof(result); this->validate_eof(result);
} }
void FetchAll_OneRow() void FetchAll_OneRow()
{ {
auto result = do_generate("SELECT * FROM one_row_table"); auto result = do_generate("SELECT * FROM one_row_table");
auto rows_result = do_fetch_all(result); auto rows_result = do_fetch_all(result);
rows_result.validate_no_error(); rows_result.validate_no_error();
EXPECT_TRUE(result.complete()); EXPECT_TRUE(result.complete());
EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0"))); EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0")));
} }
void FetchAll_SeveralRows() void FetchAll_SeveralRows()
{ {
auto result = do_generate("SELECT * FROM two_rows_table"); auto result = do_generate("SELECT * FROM two_rows_table");
auto rows_result = do_fetch_all(result); auto rows_result = do_fetch_all(result);
rows_result.validate_no_error(); rows_result.validate_no_error();
this->validate_eof(result); this->validate_eof(result);
EXPECT_TRUE(result.complete()); EXPECT_TRUE(result.complete());
EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1"))); EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1")));
} }
}; };
BOOST_MYSQL_NETWORK_TEST_SUITE(ResultsetTest) BOOST_MYSQL_NETWORK_TEST_SUITE(ResultsetTest)

View File

@ -21,159 +21,159 @@ namespace
// mysql_native_password // mysql_native_password
struct MysqlNativePasswordTest : public Test struct MysqlNativePasswordTest : public Test
{ {
auth_calculator calc; auth_calculator calc;
std::uint8_t challenge_buffer [20] { std::uint8_t challenge_buffer [20] {
0x79, 0x64, 0x3d, 0x12, 0x1d, 0x71, 0x74, 0x47, 0x79, 0x64, 0x3d, 0x12, 0x1d, 0x71, 0x74, 0x47,
0x5f, 0x48, 0x3e, 0x3e, 0x0b, 0x62, 0x0a, 0x03, 0x5f, 0x48, 0x3e, 0x3e, 0x0b, 0x62, 0x0a, 0x03,
0x3d, 0x27, 0x3a, 0x4c 0x3d, 0x27, 0x3a, 0x4c
}; // Values snooped using Wireshark }; // Values snooped using Wireshark
std::uint8_t expected_buffer [20] { std::uint8_t expected_buffer [20] {
0xf1, 0xb2, 0xfb, 0x1c, 0x8d, 0xe7, 0x5d, 0xb8, 0xf1, 0xb2, 0xfb, 0x1c, 0x8d, 0xe7, 0x5d, 0xb8,
0xeb, 0xa8, 0x12, 0x6a, 0xd1, 0x0f, 0xe9, 0xb1, 0xeb, 0xa8, 0x12, 0x6a, 0xd1, 0x0f, 0xe9, 0xb1,
0x10, 0x50, 0xd4, 0x28 0x10, 0x50, 0xd4, 0x28
}; };
std::string_view challenge = makesv(challenge_buffer); std::string_view challenge = makesv(challenge_buffer);
std::string_view expected = makesv(expected_buffer); std::string_view expected = makesv(expected_buffer);
}; };
TEST_F(MysqlNativePasswordTest, NonEmptyPasswordSslFalse_ReturnsExpectedHash) TEST_F(MysqlNativePasswordTest, NonEmptyPasswordSslFalse_ReturnsExpectedHash)
{ {
auto err = calc.calculate("mysql_native_password", "root", challenge, false); auto err = calc.calculate("mysql_native_password", "root", challenge, false);
EXPECT_EQ(err, error_code()); EXPECT_EQ(err, error_code());
EXPECT_EQ(calc.response(), expected); EXPECT_EQ(calc.response(), expected);
EXPECT_EQ(calc.plugin_name(), "mysql_native_password"); EXPECT_EQ(calc.plugin_name(), "mysql_native_password");
} }
TEST_F(MysqlNativePasswordTest, NonEmptyPasswordSslTrue_ReturnsExpectedHash) TEST_F(MysqlNativePasswordTest, NonEmptyPasswordSslTrue_ReturnsExpectedHash)
{ {
auto err = calc.calculate("mysql_native_password", "root", challenge, true); auto err = calc.calculate("mysql_native_password", "root", challenge, true);
EXPECT_EQ(err, error_code()); EXPECT_EQ(err, error_code());
EXPECT_EQ(calc.response(), expected); EXPECT_EQ(calc.response(), expected);
EXPECT_EQ(calc.plugin_name(), "mysql_native_password"); EXPECT_EQ(calc.plugin_name(), "mysql_native_password");
} }
TEST_F(MysqlNativePasswordTest, EmptyPasswordSslFalse_ReturnsEmpty) TEST_F(MysqlNativePasswordTest, EmptyPasswordSslFalse_ReturnsEmpty)
{ {
auto err = calc.calculate("mysql_native_password", "", challenge, false); auto err = calc.calculate("mysql_native_password", "", challenge, false);
EXPECT_EQ(err, error_code()); EXPECT_EQ(err, error_code());
EXPECT_EQ(calc.response(), ""); EXPECT_EQ(calc.response(), "");
EXPECT_EQ(calc.plugin_name(), "mysql_native_password"); EXPECT_EQ(calc.plugin_name(), "mysql_native_password");
} }
TEST_F(MysqlNativePasswordTest, EmptyPasswordSslTrue_ReturnsEmpty) TEST_F(MysqlNativePasswordTest, EmptyPasswordSslTrue_ReturnsEmpty)
{ {
auto err = calc.calculate("mysql_native_password", "", challenge, false); auto err = calc.calculate("mysql_native_password", "", challenge, false);
EXPECT_EQ(err, error_code()); EXPECT_EQ(err, error_code());
EXPECT_EQ(calc.response(), ""); EXPECT_EQ(calc.response(), "");
EXPECT_EQ(calc.plugin_name(), "mysql_native_password"); EXPECT_EQ(calc.plugin_name(), "mysql_native_password");
} }
TEST_F(MysqlNativePasswordTest, BadChallengeLength_Fail) TEST_F(MysqlNativePasswordTest, BadChallengeLength_Fail)
{ {
EXPECT_EQ((calc.calculate("mysql_native_password", "password", "", true)), EXPECT_EQ((calc.calculate("mysql_native_password", "password", "", true)),
make_error_code(errc::protocol_value_error)); make_error_code(errc::protocol_value_error));
EXPECT_EQ((calc.calculate("mysql_native_password", "password", "bad_challenge", true)), EXPECT_EQ((calc.calculate("mysql_native_password", "password", "bad_challenge", true)),
make_error_code(errc::protocol_value_error)); make_error_code(errc::protocol_value_error));
} }
// caching_sha2_password // caching_sha2_password
struct CachingSha2PasswordTest : public Test struct CachingSha2PasswordTest : public Test
{ {
auth_calculator calc; auth_calculator calc;
std::uint8_t challenge_buffer [20] { std::uint8_t challenge_buffer [20] {
0x3e, 0x3b, 0x4, 0x55, 0x4, 0x70, 0x16, 0x3a, 0x3e, 0x3b, 0x4, 0x55, 0x4, 0x70, 0x16, 0x3a,
0x4c, 0x15, 0x35, 0x3, 0x15, 0x76, 0x73, 0x22, 0x4c, 0x15, 0x35, 0x3, 0x15, 0x76, 0x73, 0x22,
0x46, 0x8, 0x18, 0x1 0x46, 0x8, 0x18, 0x1
}; // Values snooped using the MySQL Python connector }; // Values snooped using the MySQL Python connector
std::uint8_t expected_buffer [32] { std::uint8_t expected_buffer [32] {
0xa1, 0xc1, 0xe1, 0xe9, 0x1b, 0xb6, 0x54, 0x4b, 0xa1, 0xc1, 0xe1, 0xe9, 0x1b, 0xb6, 0x54, 0x4b,
0xa7, 0x37, 0x4b, 0x9c, 0x56, 0x6d, 0x69, 0x3e, 0xa7, 0x37, 0x4b, 0x9c, 0x56, 0x6d, 0x69, 0x3e,
0x6, 0xca, 0x7, 0x2, 0x98, 0xac, 0xd1, 0x6, 0x6, 0xca, 0x7, 0x2, 0x98, 0xac, 0xd1, 0x6,
0x18, 0xc6, 0x90, 0x38, 0x9d, 0x88, 0xe1, 0x20 0x18, 0xc6, 0x90, 0x38, 0x9d, 0x88, 0xe1, 0x20
}; };
std::string_view challenge = makesv(challenge_buffer); std::string_view challenge = makesv(challenge_buffer);
std::string_view expected = makesv(expected_buffer); std::string_view expected = makesv(expected_buffer);
std::string_view cleartext_challenge { "\4" }; std::string_view cleartext_challenge { "\4" };
}; };
TEST_F(CachingSha2PasswordTest, NonEmptyPasswordChallengeAuthSslFalse_ReturnsExpectedHash) TEST_F(CachingSha2PasswordTest, NonEmptyPasswordChallengeAuthSslFalse_ReturnsExpectedHash)
{ {
auto err = calc.calculate("caching_sha2_password", "hola", challenge, false); auto err = calc.calculate("caching_sha2_password", "hola", challenge, false);
EXPECT_EQ(err, error_code()); EXPECT_EQ(err, error_code());
EXPECT_EQ(calc.response(), expected); EXPECT_EQ(calc.response(), expected);
EXPECT_EQ(calc.plugin_name(), "caching_sha2_password"); EXPECT_EQ(calc.plugin_name(), "caching_sha2_password");
} }
TEST_F(CachingSha2PasswordTest, NonEmptyPasswordChallengeAuthSslTrue_ReturnsExpectedHash) TEST_F(CachingSha2PasswordTest, NonEmptyPasswordChallengeAuthSslTrue_ReturnsExpectedHash)
{ {
auto err = calc.calculate("caching_sha2_password", "hola", challenge, true); auto err = calc.calculate("caching_sha2_password", "hola", challenge, true);
EXPECT_EQ(err, error_code()); EXPECT_EQ(err, error_code());
EXPECT_EQ(calc.response(), expected); EXPECT_EQ(calc.response(), expected);
EXPECT_EQ(calc.plugin_name(), "caching_sha2_password"); EXPECT_EQ(calc.plugin_name(), "caching_sha2_password");
} }
TEST_F(CachingSha2PasswordTest, NonEmptyPasswordCleartextAuthSslFalse_Fail) TEST_F(CachingSha2PasswordTest, NonEmptyPasswordCleartextAuthSslFalse_Fail)
{ {
auto err = calc.calculate("caching_sha2_password", "hola", cleartext_challenge, false); auto err = calc.calculate("caching_sha2_password", "hola", cleartext_challenge, false);
EXPECT_EQ(err, make_error_code(errc::auth_plugin_requires_ssl)); EXPECT_EQ(err, make_error_code(errc::auth_plugin_requires_ssl));
} }
TEST_F(CachingSha2PasswordTest, NonEmptyPasswordCleartextAuthSslTrue_ReturnsPassword) TEST_F(CachingSha2PasswordTest, NonEmptyPasswordCleartextAuthSslTrue_ReturnsPassword)
{ {
auto err = calc.calculate("caching_sha2_password", "hola", cleartext_challenge, true); auto err = calc.calculate("caching_sha2_password", "hola", cleartext_challenge, true);
EXPECT_EQ(err, error_code()); EXPECT_EQ(err, error_code());
EXPECT_EQ(calc.response(), std::string("hola") + '\0'); EXPECT_EQ(calc.response(), std::string("hola") + '\0');
EXPECT_EQ(calc.plugin_name(), "caching_sha2_password"); EXPECT_EQ(calc.plugin_name(), "caching_sha2_password");
} }
TEST_F(CachingSha2PasswordTest, EmptyPasswordChallengeAuthSslFalse_ReturnsEmpty) TEST_F(CachingSha2PasswordTest, EmptyPasswordChallengeAuthSslFalse_ReturnsEmpty)
{ {
auto err = calc.calculate("caching_sha2_password", "", challenge, false); auto err = calc.calculate("caching_sha2_password", "", challenge, false);
EXPECT_EQ(err, error_code()); EXPECT_EQ(err, error_code());
EXPECT_EQ(calc.response(), ""); EXPECT_EQ(calc.response(), "");
EXPECT_EQ(calc.plugin_name(), "caching_sha2_password"); EXPECT_EQ(calc.plugin_name(), "caching_sha2_password");
} }
TEST_F(CachingSha2PasswordTest, EmptyPasswordChallengeAuthSslTrue_ReturnsEmpty) TEST_F(CachingSha2PasswordTest, EmptyPasswordChallengeAuthSslTrue_ReturnsEmpty)
{ {
auto err = calc.calculate("caching_sha2_password", "", challenge, true); auto err = calc.calculate("caching_sha2_password", "", challenge, true);
EXPECT_EQ(err, error_code()); EXPECT_EQ(err, error_code());
EXPECT_EQ(calc.response(), ""); EXPECT_EQ(calc.response(), "");
EXPECT_EQ(calc.plugin_name(), "caching_sha2_password"); EXPECT_EQ(calc.plugin_name(), "caching_sha2_password");
} }
TEST_F(CachingSha2PasswordTest, EmptyPasswordCleartextAuthSslFalse_ReturnsEmpty) TEST_F(CachingSha2PasswordTest, EmptyPasswordCleartextAuthSslFalse_ReturnsEmpty)
{ {
auto err = calc.calculate("caching_sha2_password", "", cleartext_challenge, false); auto err = calc.calculate("caching_sha2_password", "", cleartext_challenge, false);
EXPECT_EQ(err, error_code()); EXPECT_EQ(err, error_code());
EXPECT_EQ(calc.response(), ""); EXPECT_EQ(calc.response(), "");
EXPECT_EQ(calc.plugin_name(), "caching_sha2_password"); EXPECT_EQ(calc.plugin_name(), "caching_sha2_password");
} }
TEST_F(CachingSha2PasswordTest, EmptyPasswordCleartextAuthSslTrue_ReturnsEmpty) TEST_F(CachingSha2PasswordTest, EmptyPasswordCleartextAuthSslTrue_ReturnsEmpty)
{ {
auto err = calc.calculate("caching_sha2_password", "", cleartext_challenge, true); auto err = calc.calculate("caching_sha2_password", "", cleartext_challenge, true);
EXPECT_EQ(err, error_code()); EXPECT_EQ(err, error_code());
EXPECT_EQ(calc.response(), ""); EXPECT_EQ(calc.response(), "");
EXPECT_EQ(calc.plugin_name(), "caching_sha2_password"); EXPECT_EQ(calc.plugin_name(), "caching_sha2_password");
} }
TEST_F(CachingSha2PasswordTest, BadChallengeLength_Fail) TEST_F(CachingSha2PasswordTest, BadChallengeLength_Fail)
{ {
EXPECT_EQ((calc.calculate("caching_sha2_password", "password", "", true)), EXPECT_EQ((calc.calculate("caching_sha2_password", "password", "", true)),
make_error_code(errc::protocol_value_error)); make_error_code(errc::protocol_value_error));
EXPECT_EQ((calc.calculate("caching_sha2_password", "password", "bad_challenge", true)), EXPECT_EQ((calc.calculate("caching_sha2_password", "password", "bad_challenge", true)),
make_error_code(errc::protocol_value_error)); make_error_code(errc::protocol_value_error));
} }
// Bad authentication plugin // Bad authentication plugin
TEST(AuthCalculator, UnknownAuthPlugin_Fail) TEST(AuthCalculator, UnknownAuthPlugin_Fail)
{ {
auth_calculator calc; auth_calculator calc;
EXPECT_EQ((calc.calculate("bad_plugin", "password", "challenge", true)), EXPECT_EQ((calc.calculate("bad_plugin", "password", "challenge", true)),
make_error_code(errc::unknown_auth_plugin)); make_error_code(errc::unknown_auth_plugin));
EXPECT_EQ((calc.calculate("", "password", "challenge", true)), EXPECT_EQ((calc.calculate("", "password", "challenge", true)),
make_error_code(errc::unknown_auth_plugin)); make_error_code(errc::unknown_auth_plugin));
} }

View File

@ -24,241 +24,241 @@ namespace
using boost::mysql::operator<<; using boost::mysql::operator<<;
std::vector<boost::mysql::field_metadata> make_meta( std::vector<boost::mysql::field_metadata> make_meta(
const std::vector<protocol_field_type>& types const std::vector<protocol_field_type>& types
) )
{ {
std::vector<boost::mysql::field_metadata> res; std::vector<boost::mysql::field_metadata> res;
for (const auto type: types) for (const auto type: types)
{ {
column_definition_packet coldef; column_definition_packet coldef;
coldef.type = type; coldef.type = type;
res.emplace_back(coldef); res.emplace_back(coldef);
} }
return res; return res;
} }
// for deserialize_binary_value // for deserialize_binary_value
struct BinaryValueParam : named_param struct BinaryValueParam : named_param
{ {
std::string name; std::string name;
std::vector<std::uint8_t> from; std::vector<std::uint8_t> from;
value expected; value expected;
protocol_field_type type; protocol_field_type type;
std::uint16_t flags; std::uint16_t flags;
template <typename T> template <typename T>
BinaryValueParam( BinaryValueParam(
std::string name, std::string name,
std::vector<std::uint8_t> from, std::vector<std::uint8_t> from,
T&& expected_value, T&& expected_value,
protocol_field_type type, protocol_field_type type,
std::uint16_t flags=0 std::uint16_t flags=0
): ):
name(std::move(name)), name(std::move(name)),
from(std::move(from)), from(std::move(from)),
expected(std::forward<T>(expected_value)), expected(std::forward<T>(expected_value)),
type(type), type(type),
flags(flags) flags(flags)
{ {
} }
}; };
struct DeserializeBinaryValueTest : public TestWithParam<BinaryValueParam> {}; struct DeserializeBinaryValueTest : public TestWithParam<BinaryValueParam> {};
TEST_P(DeserializeBinaryValueTest, CorrectFormat_SetsOutputValueReturnsTrue) TEST_P(DeserializeBinaryValueTest, CorrectFormat_SetsOutputValueReturnsTrue)
{ {
column_definition_packet coldef; column_definition_packet coldef;
coldef.type = GetParam().type; coldef.type = GetParam().type;
coldef.flags.value = GetParam().flags; coldef.flags.value = GetParam().flags;
boost::mysql::field_metadata meta (coldef); boost::mysql::field_metadata meta (coldef);
value actual_value; value actual_value;
const auto& buffer = GetParam().from; const auto& buffer = GetParam().from;
deserialization_context ctx (buffer.data(), buffer.data() + buffer.size(), capabilities()); deserialization_context ctx (buffer.data(), buffer.data() + buffer.size(), capabilities());
auto err = deserialize_binary_value(ctx, meta, actual_value); auto err = deserialize_binary_value(ctx, meta, actual_value);
EXPECT_EQ(err, errc::ok); EXPECT_EQ(err, errc::ok);
EXPECT_EQ(actual_value, GetParam().expected); EXPECT_EQ(actual_value, GetParam().expected);
} }
INSTANTIATE_TEST_SUITE_P(StringTypes, DeserializeBinaryValueTest, Values( INSTANTIATE_TEST_SUITE_P(StringTypes, DeserializeBinaryValueTest, Values(
BinaryValueParam("varchar", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", protocol_field_type::var_string), BinaryValueParam("varchar", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", protocol_field_type::var_string),
BinaryValueParam("char", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", protocol_field_type::string), BinaryValueParam("char", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", protocol_field_type::string),
BinaryValueParam("varbinary", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", BinaryValueParam("varbinary", {0x04, 0x74, 0x65, 0x73, 0x74}, "test",
protocol_field_type::var_string, column_flags::binary), protocol_field_type::var_string, column_flags::binary),
BinaryValueParam("binary", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", BinaryValueParam("binary", {0x04, 0x74, 0x65, 0x73, 0x74}, "test",
protocol_field_type::string, column_flags::binary), protocol_field_type::string, column_flags::binary),
BinaryValueParam("text_blob", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", BinaryValueParam("text_blob", {0x04, 0x74, 0x65, 0x73, 0x74}, "test",
protocol_field_type::blob, column_flags::blob), protocol_field_type::blob, column_flags::blob),
BinaryValueParam("enum", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", BinaryValueParam("enum", {0x04, 0x74, 0x65, 0x73, 0x74}, "test",
protocol_field_type::string, column_flags::enum_), protocol_field_type::string, column_flags::enum_),
BinaryValueParam("set", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", BinaryValueParam("set", {0x04, 0x74, 0x65, 0x73, 0x74}, "test",
protocol_field_type::string, column_flags::set), protocol_field_type::string, column_flags::set),
BinaryValueParam("bit", {0x02, 0x02, 0x01}, "\2\1", protocol_field_type::bit), BinaryValueParam("bit", {0x02, 0x02, 0x01}, "\2\1", protocol_field_type::bit),
BinaryValueParam("decimal", {0x02, 0x31, 0x30}, "10", protocol_field_type::newdecimal), BinaryValueParam("decimal", {0x02, 0x31, 0x30}, "10", protocol_field_type::newdecimal),
BinaryValueParam("geomtry", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", protocol_field_type::geometry) BinaryValueParam("geomtry", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", protocol_field_type::geometry)
), test_name_generator); ), test_name_generator);
INSTANTIATE_TEST_SUITE_P(IntTypes, DeserializeBinaryValueTest, Values( INSTANTIATE_TEST_SUITE_P(IntTypes, DeserializeBinaryValueTest, Values(
BinaryValueParam("tinyint_unsigned", {0x14}, std::uint32_t(20), BinaryValueParam("tinyint_unsigned", {0x14}, std::uint32_t(20),
protocol_field_type::tiny, column_flags::unsigned_), protocol_field_type::tiny, column_flags::unsigned_),
BinaryValueParam("tinyint_signed", {0xec}, std::int32_t(-20), protocol_field_type::tiny), BinaryValueParam("tinyint_signed", {0xec}, std::int32_t(-20), protocol_field_type::tiny),
BinaryValueParam("smallint_unsigned", {0x14, 0x00}, std::uint32_t(20), BinaryValueParam("smallint_unsigned", {0x14, 0x00}, std::uint32_t(20),
protocol_field_type::short_, column_flags::unsigned_), protocol_field_type::short_, column_flags::unsigned_),
BinaryValueParam("smallint_signed", {0xec, 0xff}, std::int32_t(-20), protocol_field_type::short_), BinaryValueParam("smallint_signed", {0xec, 0xff}, std::int32_t(-20), protocol_field_type::short_),
BinaryValueParam("mediumint_unsigned", {0x14, 0x00, 0x00, 0x00}, std::uint32_t(20), BinaryValueParam("mediumint_unsigned", {0x14, 0x00, 0x00, 0x00}, std::uint32_t(20),
protocol_field_type::int24, column_flags::unsigned_), protocol_field_type::int24, column_flags::unsigned_),
BinaryValueParam("mediumint_signed", {0xec, 0xff, 0xff, 0xff}, std::int32_t(-20), protocol_field_type::int24), BinaryValueParam("mediumint_signed", {0xec, 0xff, 0xff, 0xff}, std::int32_t(-20), protocol_field_type::int24),
BinaryValueParam("int_unsigned", {0x14, 0x00, 0x00, 0x00}, std::uint32_t(20), BinaryValueParam("int_unsigned", {0x14, 0x00, 0x00, 0x00}, std::uint32_t(20),
protocol_field_type::long_, column_flags::unsigned_), protocol_field_type::long_, column_flags::unsigned_),
BinaryValueParam("int_signed", {0xec, 0xff, 0xff, 0xff}, std::int32_t(-20), protocol_field_type::long_), BinaryValueParam("int_signed", {0xec, 0xff, 0xff, 0xff}, std::int32_t(-20), protocol_field_type::long_),
BinaryValueParam("bigint_unsigned", {0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, std::uint64_t(20), BinaryValueParam("bigint_unsigned", {0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, std::uint64_t(20),
protocol_field_type::longlong, column_flags::unsigned_), protocol_field_type::longlong, column_flags::unsigned_),
BinaryValueParam("bigint_signed", {0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, std::int64_t(-20), BinaryValueParam("bigint_signed", {0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, std::int64_t(-20),
protocol_field_type::longlong) protocol_field_type::longlong)
), test_name_generator); ), test_name_generator);
INSTANTIATE_TEST_SUITE_P(FloatingPointTypes, DeserializeBinaryValueTest, Values( INSTANTIATE_TEST_SUITE_P(FloatingPointTypes, DeserializeBinaryValueTest, Values(
BinaryValueParam("float", {0x66, 0x66, 0x86, 0xc0}, -4.2f, protocol_field_type::float_), BinaryValueParam("float", {0x66, 0x66, 0x86, 0xc0}, -4.2f, protocol_field_type::float_),
BinaryValueParam("double", {0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x10, 0xc0}, -4.2, protocol_field_type::double_) BinaryValueParam("double", {0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x10, 0xc0}, -4.2, protocol_field_type::double_)
), test_name_generator); ), test_name_generator);
INSTANTIATE_TEST_SUITE_P(TimeTypes, DeserializeBinaryValueTest, Values( INSTANTIATE_TEST_SUITE_P(TimeTypes, DeserializeBinaryValueTest, Values(
BinaryValueParam("date", {0x04, 0xda, 0x07, 0x03, 0x1c}, makedate(2010, 3, 28), protocol_field_type::date), BinaryValueParam("date", {0x04, 0xda, 0x07, 0x03, 0x1c}, makedate(2010, 3, 28), protocol_field_type::date),
BinaryValueParam("datetime", {0x0b, 0xda, 0x07, 0x05, 0x02, 0x17, 0x01, 0x32, 0xa0, 0x86, 0x01, 0x00}, BinaryValueParam("datetime", {0x0b, 0xda, 0x07, 0x05, 0x02, 0x17, 0x01, 0x32, 0xa0, 0x86, 0x01, 0x00},
makedt(2010, 5, 2, 23, 1, 50, 100000), protocol_field_type::datetime), makedt(2010, 5, 2, 23, 1, 50, 100000), protocol_field_type::datetime),
BinaryValueParam("timestamp", {0x0b, 0xda, 0x07, 0x05, 0x02, 0x17, 0x01, 0x32, 0xa0, 0x86, 0x01, 0x00}, BinaryValueParam("timestamp", {0x0b, 0xda, 0x07, 0x05, 0x02, 0x17, 0x01, 0x32, 0xa0, 0x86, 0x01, 0x00},
makedt(2010, 5, 2, 23, 1, 50, 100000), protocol_field_type::timestamp), makedt(2010, 5, 2, 23, 1, 50, 100000), protocol_field_type::timestamp),
BinaryValueParam("time", { 0x0c, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0xa0, 0x86, 0x01, 0x00}, BinaryValueParam("time", { 0x0c, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0xa0, 0x86, 0x01, 0x00},
maket(120, 2, 3, 100000), protocol_field_type::time), maket(120, 2, 3, 100000), protocol_field_type::time),
BinaryValueParam("year", {0xe3, 0x07}, std::uint32_t(2019), protocol_field_type::year, column_flags::unsigned_) BinaryValueParam("year", {0xe3, 0x07}, std::uint32_t(2019), protocol_field_type::year, column_flags::unsigned_)
), test_name_generator); ), test_name_generator);
// for deserialize_binary_row // for deserialize_binary_row
struct BinaryRowParam : named_param struct BinaryRowParam : named_param
{ {
std::string name; std::string name;
std::vector<std::uint8_t> from; std::vector<std::uint8_t> from;
std::vector<value> expected; std::vector<value> expected;
std::vector<protocol_field_type> types; std::vector<protocol_field_type> types;
BinaryRowParam( BinaryRowParam(
std::string name, std::string name,
std::vector<std::uint8_t> from, std::vector<std::uint8_t> from,
std::vector<value> expected, std::vector<value> expected,
std::vector<protocol_field_type> types std::vector<protocol_field_type> types
): ):
name(std::move(name)), name(std::move(name)),
from(std::move(from)), from(std::move(from)),
expected(std::move(expected)), expected(std::move(expected)),
types(std::move(types)) types(std::move(types))
{ {
assert(expected.size() == types.size()); assert(expected.size() == types.size());
} }
}; };
struct DeserializeBinaryRowTest : public TestWithParam<BinaryRowParam> {}; struct DeserializeBinaryRowTest : public TestWithParam<BinaryRowParam> {};
TEST_P(DeserializeBinaryRowTest, CorrectFormat_SetsOutputValueReturnsTrue) TEST_P(DeserializeBinaryRowTest, CorrectFormat_SetsOutputValueReturnsTrue)
{ {
auto meta = make_meta(GetParam().types); auto meta = make_meta(GetParam().types);
const auto& buffer = GetParam().from; const auto& buffer = GetParam().from;
deserialization_context ctx (buffer.data(), buffer.data() + buffer.size(), capabilities()); deserialization_context ctx (buffer.data(), buffer.data() + buffer.size(), capabilities());
std::vector<value> actual; std::vector<value> actual;
auto err = deserialize_binary_row(ctx, meta, actual); auto err = deserialize_binary_row(ctx, meta, actual);
EXPECT_EQ(err, error_code()); EXPECT_EQ(err, error_code());
EXPECT_EQ(actual, GetParam().expected); EXPECT_EQ(actual, GetParam().expected);
} }
INSTANTIATE_TEST_SUITE_P(Default, DeserializeBinaryRowTest, testing::Values( INSTANTIATE_TEST_SUITE_P(Default, DeserializeBinaryRowTest, testing::Values(
BinaryRowParam("one_value", {0x00, 0x00, 0x14}, makevalues(std::int32_t(20)), {protocol_field_type::tiny}), BinaryRowParam("one_value", {0x00, 0x00, 0x14}, makevalues(std::int32_t(20)), {protocol_field_type::tiny}),
BinaryRowParam("one_null", {0x00, 0x04}, makevalues(nullptr), {protocol_field_type::tiny}), BinaryRowParam("one_null", {0x00, 0x04}, makevalues(nullptr), {protocol_field_type::tiny}),
BinaryRowParam("two_values", {0x00, 0x00, 0x03, 0x6d, 0x69, 0x6e, 0x6d, 0x07}, BinaryRowParam("two_values", {0x00, 0x00, 0x03, 0x6d, 0x69, 0x6e, 0x6d, 0x07},
makevalues("min", std::int32_t(1901)), {protocol_field_type::var_string, protocol_field_type::short_}), makevalues("min", std::int32_t(1901)), {protocol_field_type::var_string, protocol_field_type::short_}),
BinaryRowParam("one_value_one_null", {0x00, 0x08, 0x03, 0x6d, 0x61, 0x78}, BinaryRowParam("one_value_one_null", {0x00, 0x08, 0x03, 0x6d, 0x61, 0x78},
makevalues("max", nullptr), {protocol_field_type::var_string, protocol_field_type::tiny}), makevalues("max", nullptr), {protocol_field_type::var_string, protocol_field_type::tiny}),
BinaryRowParam("two_nulls", {0x00, 0x0c}, BinaryRowParam("two_nulls", {0x00, 0x0c},
makevalues(nullptr, nullptr), {protocol_field_type::tiny, protocol_field_type::tiny}), makevalues(nullptr, nullptr), {protocol_field_type::tiny, protocol_field_type::tiny}),
BinaryRowParam("six_nulls", {0x00, 0xfc}, std::vector<value>(6, value(nullptr)), BinaryRowParam("six_nulls", {0x00, 0xfc}, std::vector<value>(6, value(nullptr)),
std::vector<protocol_field_type>(6, protocol_field_type::tiny)), std::vector<protocol_field_type>(6, protocol_field_type::tiny)),
BinaryRowParam("seven_nulls", {0x00, 0xfc, 0x01}, std::vector<value>(7, value(nullptr)), BinaryRowParam("seven_nulls", {0x00, 0xfc, 0x01}, std::vector<value>(7, value(nullptr)),
std::vector<protocol_field_type>(7, protocol_field_type::tiny)), std::vector<protocol_field_type>(7, protocol_field_type::tiny)),
BinaryRowParam("several_values", { BinaryRowParam("several_values", {
0x00, 0x90, 0x00, 0xfd, 0x14, 0x00, 0xc3, 0xf5, 0x48, 0x00, 0x90, 0x00, 0xfd, 0x14, 0x00, 0xc3, 0xf5, 0x48,
0x40, 0x02, 0x61, 0x62, 0x04, 0xe2, 0x07, 0x0a, 0x40, 0x02, 0x61, 0x62, 0x04, 0xe2, 0x07, 0x0a,
0x05, 0x71, 0x99, 0x6d, 0xe2, 0x93, 0x4d, 0xf5, 0x05, 0x71, 0x99, 0x6d, 0xe2, 0x93, 0x4d, 0xf5,
0x3d 0x3d
}, makevalues( }, makevalues(
std::int32_t(-3), std::int32_t(-3),
std::int32_t(20), std::int32_t(20),
nullptr, nullptr,
3.14f, 3.14f,
"ab", "ab",
nullptr, nullptr,
makedate(2018, 10, 5), makedate(2018, 10, 5),
3.10e-10 3.10e-10
), { ), {
protocol_field_type::tiny, protocol_field_type::tiny,
protocol_field_type::short_, protocol_field_type::short_,
protocol_field_type::long_, protocol_field_type::long_,
protocol_field_type::float_, protocol_field_type::float_,
protocol_field_type::string, protocol_field_type::string,
protocol_field_type::long_, protocol_field_type::long_,
protocol_field_type::date, protocol_field_type::date,
protocol_field_type::double_ protocol_field_type::double_
} }
) )
), test_name_generator); ), test_name_generator);
// errc cases for deserialize_binary_row // errc cases for deserialize_binary_row
struct BinaryRowErrorParam : named_param struct BinaryRowErrorParam : named_param
{ {
std::string name; std::string name;
std::vector<std::uint8_t> from; std::vector<std::uint8_t> from;
errc expected; errc expected;
std::vector<protocol_field_type> types; std::vector<protocol_field_type> types;
BinaryRowErrorParam( BinaryRowErrorParam(
std::string name, std::string name,
std::vector<std::uint8_t> from, std::vector<std::uint8_t> from,
errc expected, errc expected,
std::vector<protocol_field_type> types std::vector<protocol_field_type> types
): ):
name(std::move(name)), name(std::move(name)),
from(std::move(from)), from(std::move(from)),
expected(expected), expected(expected),
types(std::move(types)) types(std::move(types))
{ {
} }
}; };
struct DeserializeBinaryRowErrorTest : public TestWithParam<BinaryRowErrorParam> {}; struct DeserializeBinaryRowErrorTest : public TestWithParam<BinaryRowErrorParam> {};
TEST_P(DeserializeBinaryRowErrorTest, ErrorCondition_ReturnsErrorCode) TEST_P(DeserializeBinaryRowErrorTest, ErrorCondition_ReturnsErrorCode)
{ {
auto meta = make_meta(GetParam().types); auto meta = make_meta(GetParam().types);
const auto& buffer = GetParam().from; const auto& buffer = GetParam().from;
deserialization_context ctx (buffer.data(), buffer.data() + buffer.size(), capabilities()); deserialization_context ctx (buffer.data(), buffer.data() + buffer.size(), capabilities());
std::vector<value> actual; std::vector<value> actual;
auto err = deserialize_binary_row(ctx, meta, actual); auto err = deserialize_binary_row(ctx, meta, actual);
EXPECT_EQ(err, make_error_code(GetParam().expected)); EXPECT_EQ(err, make_error_code(GetParam().expected));
} }
INSTANTIATE_TEST_SUITE_P(Default, DeserializeBinaryRowErrorTest, testing::Values( INSTANTIATE_TEST_SUITE_P(Default, DeserializeBinaryRowErrorTest, testing::Values(
BinaryRowErrorParam("no_space_null_bitmap_1", {0x00}, errc::incomplete_message, {protocol_field_type::tiny}), BinaryRowErrorParam("no_space_null_bitmap_1", {0x00}, errc::incomplete_message, {protocol_field_type::tiny}),
BinaryRowErrorParam("no_space_null_bitmap_2", {0x00, 0xfc}, errc::incomplete_message, BinaryRowErrorParam("no_space_null_bitmap_2", {0x00, 0xfc}, errc::incomplete_message,
std::vector<protocol_field_type>(7, protocol_field_type::tiny)), std::vector<protocol_field_type>(7, protocol_field_type::tiny)),
BinaryRowErrorParam("no_space_value_single", {0x00, 0x00}, errc::incomplete_message, {protocol_field_type::tiny}), BinaryRowErrorParam("no_space_value_single", {0x00, 0x00}, errc::incomplete_message, {protocol_field_type::tiny}),
BinaryRowErrorParam("no_space_value_last", {0x00, 0x00, 0x01}, errc::incomplete_message, BinaryRowErrorParam("no_space_value_last", {0x00, 0x00, 0x01}, errc::incomplete_message,
std::vector<protocol_field_type>(2, protocol_field_type::tiny)), std::vector<protocol_field_type>(2, protocol_field_type::tiny)),
BinaryRowErrorParam("no_space_value_middle", {0x00, 0x00, 0x01}, errc::incomplete_message, BinaryRowErrorParam("no_space_value_middle", {0x00, 0x00, 0x01}, errc::incomplete_message,
std::vector<protocol_field_type>(3, protocol_field_type::tiny)), std::vector<protocol_field_type>(3, protocol_field_type::tiny)),
BinaryRowErrorParam("extra_bytes", {0x00, 0x00, 0x01, 0x02}, errc::extra_bytes, {protocol_field_type::tiny}) BinaryRowErrorParam("extra_bytes", {0x00, 0x00, 0x01, 0x02}, errc::extra_bytes, {protocol_field_type::tiny})
), test_name_generator); ), test_name_generator);

View File

@ -13,64 +13,64 @@ using namespace testing;
TEST(Capabilities, Has_BitSet_ReturnsTrue) TEST(Capabilities, Has_BitSet_ReturnsTrue)
{ {
capabilities caps (CLIENT_COMPRESS); capabilities caps (CLIENT_COMPRESS);
EXPECT_TRUE(caps.has(CLIENT_COMPRESS)); EXPECT_TRUE(caps.has(CLIENT_COMPRESS));
} }
TEST(Capabilities, Has_BitNotSet_ReturnsFalse) TEST(Capabilities, Has_BitNotSet_ReturnsFalse)
{ {
capabilities caps (CLIENT_COMPRESS); capabilities caps (CLIENT_COMPRESS);
EXPECT_FALSE(caps.has(CLIENT_SSL)); EXPECT_FALSE(caps.has(CLIENT_SSL));
} }
TEST(Capabilities, Has_MultipleBitsSet_ReturnsTrueForSetBits) TEST(Capabilities, Has_MultipleBitsSet_ReturnsTrueForSetBits)
{ {
capabilities caps ( capabilities caps (
CLIENT_CONNECT_WITH_DB | CLIENT_CONNECT_WITH_DB |
CLIENT_SSL | CLIENT_SSL |
CLIENT_COMPRESS); CLIENT_COMPRESS);
for (int i = 0; i < 32; ++i) for (int i = 0; i < 32; ++i)
{ {
std::uint32_t cap_bit = 1 << i; std::uint32_t cap_bit = 1 << i;
bool is_set = bool is_set =
cap_bit == CLIENT_CONNECT_WITH_DB || cap_bit == CLIENT_CONNECT_WITH_DB ||
cap_bit == CLIENT_SSL || cap_bit == CLIENT_SSL ||
cap_bit == CLIENT_COMPRESS; cap_bit == CLIENT_COMPRESS;
EXPECT_EQ(caps.has(cap_bit), is_set); EXPECT_EQ(caps.has(cap_bit), is_set);
} }
} }
struct CapabilitiesHasAllTest : public Test struct CapabilitiesHasAllTest : public Test
{ {
capabilities rhs {CLIENT_CONNECT_WITH_DB | CLIENT_SSL | CLIENT_COMPRESS}; capabilities rhs {CLIENT_CONNECT_WITH_DB | CLIENT_SSL | CLIENT_COMPRESS};
}; };
TEST_F(CapabilitiesHasAllTest, HasAll_HasNone_ReturnsFalse) TEST_F(CapabilitiesHasAllTest, HasAll_HasNone_ReturnsFalse)
{ {
capabilities lhs (0); capabilities lhs (0);
EXPECT_FALSE(lhs.has_all(rhs)); EXPECT_FALSE(lhs.has_all(rhs));
} }
TEST_F(CapabilitiesHasAllTest, HasAll_HasSomeButNotAll_ReturnsFalse) TEST_F(CapabilitiesHasAllTest, HasAll_HasSomeButNotAll_ReturnsFalse)
{ {
capabilities lhs (CLIENT_CONNECT_WITH_DB | CLIENT_COMPRESS); capabilities lhs (CLIENT_CONNECT_WITH_DB | CLIENT_COMPRESS);
EXPECT_FALSE(lhs.has_all(rhs)); EXPECT_FALSE(lhs.has_all(rhs));
} }
TEST_F(CapabilitiesHasAllTest, HasAll_HasSomeButNotAllPlusUnrelated_ReturnsFalse) TEST_F(CapabilitiesHasAllTest, HasAll_HasSomeButNotAllPlusUnrelated_ReturnsFalse)
{ {
capabilities lhs (CLIENT_CONNECT_WITH_DB | CLIENT_COMPRESS | CLIENT_TRANSACTIONS); capabilities lhs (CLIENT_CONNECT_WITH_DB | CLIENT_COMPRESS | CLIENT_TRANSACTIONS);
EXPECT_FALSE(lhs.has_all(rhs)); EXPECT_FALSE(lhs.has_all(rhs));
} }
TEST_F(CapabilitiesHasAllTest, HasAll_HasOnlyTheRequestedOnes_ReturnsTrue) TEST_F(CapabilitiesHasAllTest, HasAll_HasOnlyTheRequestedOnes_ReturnsTrue)
{ {
capabilities lhs (rhs); capabilities lhs (rhs);
EXPECT_TRUE(lhs.has_all(rhs)); EXPECT_TRUE(lhs.has_all(rhs));
} }
TEST_F(CapabilitiesHasAllTest, HasAll_HasTheRequestedOnesAndOthers_ReturnsTrue) TEST_F(CapabilitiesHasAllTest, HasAll_HasTheRequestedOnesAndOthers_ReturnsTrue)
{ {
capabilities lhs = rhs | capabilities(CLIENT_TRANSACTIONS); capabilities lhs = rhs | capabilities(CLIENT_TRANSACTIONS);
EXPECT_TRUE(lhs.has_all(rhs)); EXPECT_TRUE(lhs.has_all(rhs));
} }

View File

@ -26,369 +26,369 @@ namespace
class MockStream class MockStream
{ {
public: public:
MOCK_METHOD2(read_buffer, std::size_t(boost::asio::mutable_buffer, error_code&)); MOCK_METHOD2(read_buffer, std::size_t(boost::asio::mutable_buffer, error_code&));
MOCK_METHOD2(write_buffer, std::size_t(boost::asio::const_buffer, error_code&)); MOCK_METHOD2(write_buffer, std::size_t(boost::asio::const_buffer, error_code&));
void set_default_behavior() void set_default_behavior()
{ {
ON_CALL(*this, read_buffer).WillByDefault(DoAll( ON_CALL(*this, read_buffer).WillByDefault(DoAll(
SetArgReferee<1>(make_error_code(boost::system::errc::timed_out)), SetArgReferee<1>(make_error_code(boost::system::errc::timed_out)),
Return(0) Return(0)
)); ));
ON_CALL(*this, write_buffer).WillByDefault(DoAll( ON_CALL(*this, write_buffer).WillByDefault(DoAll(
SetArgReferee<1>(make_error_code(boost::system::errc::timed_out)), SetArgReferee<1>(make_error_code(boost::system::errc::timed_out)),
Return(0) Return(0)
)); ));
} }
template <typename MutableBufferSequence> template <typename MutableBufferSequence>
std::size_t read_some(MutableBufferSequence mb, error_code& ec) std::size_t read_some(MutableBufferSequence mb, error_code& ec)
{ {
if (buffer_size(mb) == 0) if (buffer_size(mb) == 0)
{ {
ec.clear(); ec.clear();
return 0; return 0;
} }
size_t res = 0; size_t res = 0;
for (auto it = buffer_sequence_begin(mb); it != buffer_sequence_end(mb); ++it) for (auto it = buffer_sequence_begin(mb); it != buffer_sequence_end(mb); ++it)
res += read_buffer(*it, ec); res += read_buffer(*it, ec);
return res; return res;
} }
template <typename MutableBufferSequence> template <typename MutableBufferSequence>
std::size_t read_some(MutableBufferSequence mb) std::size_t read_some(MutableBufferSequence mb)
{ {
error_code ec; error_code ec;
auto res = read_some(mb, ec); auto res = read_some(mb, ec);
if (res) if (res)
{ {
throw boost::system::system_error(ec); throw boost::system::system_error(ec);
} }
return res; return res;
} }
template <typename ConstBufferSequence> template <typename ConstBufferSequence>
std::size_t write_some(ConstBufferSequence cb, error_code& ec) std::size_t write_some(ConstBufferSequence cb, error_code& ec)
{ {
if (buffer_size(cb) == 0) if (buffer_size(cb) == 0)
{ {
ec.clear(); ec.clear();
return 0; return 0;
} }
size_t res = 0; size_t res = 0;
for (auto it = buffer_sequence_begin(cb); it != buffer_sequence_end(cb); ++it) for (auto it = buffer_sequence_begin(cb); it != buffer_sequence_end(cb); ++it)
{ {
auto written = write_buffer(*it, ec); auto written = write_buffer(*it, ec);
res += written; res += written;
if (written < it->size()) break; if (written < it->size()) break;
} }
return res; return res;
} }
template <typename ConstBufferSequence> template <typename ConstBufferSequence>
std::size_t write_some(ConstBufferSequence mb) std::size_t write_some(ConstBufferSequence mb)
{ {
error_code ec; error_code ec;
auto res = write_some(mb, ec); auto res = write_some(mb, ec);
if (res) if (res)
{ {
throw boost::system::system_error(ec); throw boost::system::system_error(ec);
} }
return res; return res;
} }
using lowest_layer_type = MockStream; using lowest_layer_type = MockStream;
using executor_type = boost::asio::system_executor; using executor_type = boost::asio::system_executor;
MockStream& lowest_layer() { return *this; } MockStream& lowest_layer() { return *this; }
executor_type get_executor() { return boost::asio::system_executor(); } executor_type get_executor() { return boost::asio::system_executor(); }
}; };
struct MysqlChannelFixture : public Test struct MysqlChannelFixture : public Test
{ {
using MockChannel = channel<NiceMock<MockStream>>; using MockChannel = channel<NiceMock<MockStream>>;
NiceMock<MockStream> stream; NiceMock<MockStream> stream;
MockChannel chan {stream}; MockChannel chan {stream};
error_code code; error_code code;
InSequence seq; InSequence seq;
MysqlChannelFixture() MysqlChannelFixture()
{ {
stream.set_default_behavior(); stream.set_default_behavior();
} }
}; };
struct MysqlChannelReadTest : public MysqlChannelFixture struct MysqlChannelReadTest : public MysqlChannelFixture
{ {
std::vector<uint8_t> buffer { 0xab, 0xac, 0xad, 0xae }; // simulate buffer was not empty, to verify we clear it std::vector<uint8_t> buffer { 0xab, 0xac, 0xad, 0xae }; // simulate buffer was not empty, to verify we clear it
std::vector<uint8_t> bytes_to_read; std::vector<uint8_t> bytes_to_read;
std::size_t index {0}; std::size_t index {0};
void verify_buffer(const std::vector<uint8_t>& expected) void verify_buffer(const std::vector<uint8_t>& expected)
{ {
EXPECT_EQ(buffer, expected); EXPECT_EQ(buffer, expected);
} }
static auto buffer_copier(const std::vector<uint8_t>& buffer) static auto buffer_copier(const std::vector<uint8_t>& buffer)
{ {
return [buffer](boost::asio::mutable_buffer b, error_code& ec) { return [buffer](boost::asio::mutable_buffer b, error_code& ec) {
assert(b.size() >= buffer.size()); assert(b.size() >= buffer.size());
memcpy(b.data(), buffer.data(), buffer.size()); memcpy(b.data(), buffer.data(), buffer.size());
ec.clear(); ec.clear();
return buffer.size(); return buffer.size();
}; };
} }
auto make_read_handler() auto make_read_handler()
{ {
return [this](boost::asio::mutable_buffer b, error_code& ec) { return [this](boost::asio::mutable_buffer b, error_code& ec) {
std::size_t to_copy = std::min(b.size(), bytes_to_read.size() - index); std::size_t to_copy = std::min(b.size(), bytes_to_read.size() - index);
memcpy(b.data(), bytes_to_read.data() + index, to_copy); memcpy(b.data(), bytes_to_read.data() + index, to_copy);
index += to_copy; index += to_copy;
ec.clear(); ec.clear();
return to_copy; return to_copy;
}; };
} }
static auto read_failer(error_code error) static auto read_failer(error_code error)
{ {
return [error](boost::asio::mutable_buffer, error_code& ec) { return [error](boost::asio::mutable_buffer, error_code& ec) {
ec = error; ec = error;
return size_t(0); return size_t(0);
}; };
} }
}; };
TEST_F(MysqlChannelReadTest, SyncRead_AllReadsSuccessful_ReadHeaderPopulatesBuffer) TEST_F(MysqlChannelReadTest, SyncRead_AllReadsSuccessful_ReadHeaderPopulatesBuffer)
{ {
ON_CALL(stream, read_buffer) ON_CALL(stream, read_buffer)
.WillByDefault(Invoke(make_read_handler())); .WillByDefault(Invoke(make_read_handler()));
bytes_to_read = { bytes_to_read = {
0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0xfe, 0x03, 0x02 0xfe, 0x03, 0x02
}; };
chan.read(buffer, code); chan.read(buffer, code);
EXPECT_EQ(code, error_code()); EXPECT_EQ(code, error_code());
verify_buffer({0xfe, 0x03, 0x02}); verify_buffer({0xfe, 0x03, 0x02});
} }
TEST_F(MysqlChannelReadTest, SyncRead_MoreThan16M_JoinsPackets) TEST_F(MysqlChannelReadTest, SyncRead_MoreThan16M_JoinsPackets)
{ {
ON_CALL(stream, read_buffer) ON_CALL(stream, read_buffer)
.WillByDefault(Invoke(make_read_handler())); .WillByDefault(Invoke(make_read_handler()));
concat(bytes_to_read, {0xff, 0xff, 0xff, 0x00}); concat(bytes_to_read, {0xff, 0xff, 0xff, 0x00});
concat(bytes_to_read, std::vector<uint8_t>(0xffffff, 0x20)); concat(bytes_to_read, std::vector<uint8_t>(0xffffff, 0x20));
concat(bytes_to_read, {0xff, 0xff, 0xff, 0x01}); concat(bytes_to_read, {0xff, 0xff, 0xff, 0x01});
concat(bytes_to_read, std::vector<uint8_t>(0xffffff, 0x20)); concat(bytes_to_read, std::vector<uint8_t>(0xffffff, 0x20));
concat(bytes_to_read, {0x04, 0x00, 0x00, 0x02}); concat(bytes_to_read, {0x04, 0x00, 0x00, 0x02});
concat(bytes_to_read, {0x20, 0x20, 0x20, 0x20}); concat(bytes_to_read, {0x20, 0x20, 0x20, 0x20});
chan.read(buffer, code); chan.read(buffer, code);
EXPECT_EQ(code, error_code()); EXPECT_EQ(code, error_code());
verify_buffer(std::vector<uint8_t>(0xffffff * 2 + 4, 0x20)); verify_buffer(std::vector<uint8_t>(0xffffff * 2 + 4, 0x20));
} }
TEST_F(MysqlChannelReadTest, SyncRead_EmptyPacket_LeavesBufferEmpty) TEST_F(MysqlChannelReadTest, SyncRead_EmptyPacket_LeavesBufferEmpty)
{ {
ON_CALL(stream, read_buffer) ON_CALL(stream, read_buffer)
.WillByDefault(Invoke(make_read_handler())); .WillByDefault(Invoke(make_read_handler()));
concat(bytes_to_read, {0x00, 0x00, 0x00, 0x00}); concat(bytes_to_read, {0x00, 0x00, 0x00, 0x00});
chan.read(buffer, code); chan.read(buffer, code);
EXPECT_EQ(code, error_code()); EXPECT_EQ(code, error_code());
verify_buffer(std::vector<uint8_t>{}); verify_buffer(std::vector<uint8_t>{});
} }
TEST_F(MysqlChannelReadTest, SyncRead_ShortReads_InvokesReadAgain) TEST_F(MysqlChannelReadTest, SyncRead_ShortReads_InvokesReadAgain)
{ {
EXPECT_CALL(stream, read_buffer) EXPECT_CALL(stream, read_buffer)
.WillOnce(Invoke(buffer_copier({0x04}))) .WillOnce(Invoke(buffer_copier({0x04})))
.WillOnce(Invoke(buffer_copier({ 0x00, 0x00, 0x00}))) .WillOnce(Invoke(buffer_copier({ 0x00, 0x00, 0x00})))
.WillOnce(Invoke(buffer_copier({0x01, 0x02}))) .WillOnce(Invoke(buffer_copier({0x01, 0x02})))
.WillOnce(Invoke(buffer_copier({0x03, 0x04}))); .WillOnce(Invoke(buffer_copier({0x03, 0x04})));
chan.read(buffer, code); chan.read(buffer, code);
EXPECT_EQ(code, error_code()); EXPECT_EQ(code, error_code());
verify_buffer({0x01, 0x02, 0x03, 0x04}); verify_buffer({0x01, 0x02, 0x03, 0x04});
} }
TEST_F(MysqlChannelReadTest, SyncRead_ReadErrorInHeader_ReturnsFailureErrorCode) TEST_F(MysqlChannelReadTest, SyncRead_ReadErrorInHeader_ReturnsFailureErrorCode)
{ {
auto expected_error = make_error_code(boost::system::errc::not_supported); auto expected_error = make_error_code(boost::system::errc::not_supported);
EXPECT_CALL(stream, read_buffer) EXPECT_CALL(stream, read_buffer)
.WillOnce(Invoke(read_failer(expected_error))); .WillOnce(Invoke(read_failer(expected_error)));
chan.read(buffer, code); chan.read(buffer, code);
EXPECT_EQ(code, expected_error); EXPECT_EQ(code, expected_error);
} }
TEST_F(MysqlChannelReadTest, SyncRead_ReadErrorInPacket_ReturnsFailureErrorCode) TEST_F(MysqlChannelReadTest, SyncRead_ReadErrorInPacket_ReturnsFailureErrorCode)
{ {
auto expected_error = make_error_code(boost::system::errc::not_supported); auto expected_error = make_error_code(boost::system::errc::not_supported);
EXPECT_CALL(stream, read_buffer) EXPECT_CALL(stream, read_buffer)
.WillOnce(Invoke(buffer_copier({0xff, 0xff, 0xff, 0x00}))) .WillOnce(Invoke(buffer_copier({0xff, 0xff, 0xff, 0x00})))
.WillOnce(Invoke(read_failer(expected_error))); .WillOnce(Invoke(read_failer(expected_error)));
chan.read(buffer, code); chan.read(buffer, code);
EXPECT_EQ(code, expected_error); EXPECT_EQ(code, expected_error);
} }
TEST_F(MysqlChannelReadTest, SyncRead_SequenceNumberMismatch_ReturnsAppropriateErrorCode) TEST_F(MysqlChannelReadTest, SyncRead_SequenceNumberMismatch_ReturnsAppropriateErrorCode)
{ {
ON_CALL(stream, read_buffer) ON_CALL(stream, read_buffer)
.WillByDefault(Invoke(make_read_handler())); .WillByDefault(Invoke(make_read_handler()));
bytes_to_read = {0xff, 0xff, 0xff, 0x05}; bytes_to_read = {0xff, 0xff, 0xff, 0x05};
chan.read(buffer, code); chan.read(buffer, code);
EXPECT_EQ(code, make_error_code(errc::sequence_number_mismatch)); EXPECT_EQ(code, make_error_code(errc::sequence_number_mismatch));
} }
TEST_F(MysqlChannelReadTest, SyncRead_SequenceNumberNotZero_RespectsCurrentSequenceNumber) TEST_F(MysqlChannelReadTest, SyncRead_SequenceNumberNotZero_RespectsCurrentSequenceNumber)
{ {
ON_CALL(stream, read_buffer) ON_CALL(stream, read_buffer)
.WillByDefault(Invoke(make_read_handler())); .WillByDefault(Invoke(make_read_handler()));
bytes_to_read = { bytes_to_read = {
0x03, 0x00, 0x00, 0x21, 0x03, 0x00, 0x00, 0x21,
0xfe, 0x03, 0x02 0xfe, 0x03, 0x02
}; };
chan.reset_sequence_number(0x21); chan.reset_sequence_number(0x21);
chan.read(buffer, code); chan.read(buffer, code);
EXPECT_EQ(code, error_code()); EXPECT_EQ(code, error_code());
verify_buffer({0xfe, 0x03, 0x02}); verify_buffer({0xfe, 0x03, 0x02});
EXPECT_EQ(chan.sequence_number(), 0x22); EXPECT_EQ(chan.sequence_number(), 0x22);
} }
TEST_F(MysqlChannelReadTest, SyncRead_SequenceNumberFF_SequenceNumberWraps) TEST_F(MysqlChannelReadTest, SyncRead_SequenceNumberFF_SequenceNumberWraps)
{ {
ON_CALL(stream, read_buffer) ON_CALL(stream, read_buffer)
.WillByDefault(Invoke(make_read_handler())); .WillByDefault(Invoke(make_read_handler()));
bytes_to_read = { bytes_to_read = {
0x03, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff,
0xfe, 0x03, 0x02 0xfe, 0x03, 0x02
}; };
chan.reset_sequence_number(0xff); chan.reset_sequence_number(0xff);
chan.read(buffer, code); chan.read(buffer, code);
EXPECT_EQ(code, error_code()); EXPECT_EQ(code, error_code());
verify_buffer({0xfe, 0x03, 0x02}); verify_buffer({0xfe, 0x03, 0x02});
EXPECT_EQ(chan.sequence_number(), 0); EXPECT_EQ(chan.sequence_number(), 0);
} }
struct MysqlChannelWriteTest : public MysqlChannelFixture struct MysqlChannelWriteTest : public MysqlChannelFixture
{ {
std::vector<uint8_t> bytes_written; std::vector<uint8_t> bytes_written;
void verify_buffer(const std::vector<uint8_t>& expected) void verify_buffer(const std::vector<uint8_t>& expected)
{ {
EXPECT_EQ(bytes_written, expected); EXPECT_EQ(bytes_written, expected);
} }
auto make_write_handler(std::size_t max_bytes_written = 0xffffffff) auto make_write_handler(std::size_t max_bytes_written = 0xffffffff)
{ {
return [this, max_bytes_written](boost::asio::const_buffer buff, error_code& ec) { return [this, max_bytes_written](boost::asio::const_buffer buff, error_code& ec) {
auto actual_size = std::min(buff.size(), max_bytes_written); auto actual_size = std::min(buff.size(), max_bytes_written);
concat(bytes_written, boost::asio::buffer(buff.data(), actual_size)); concat(bytes_written, boost::asio::buffer(buff.data(), actual_size));
ec.clear(); ec.clear();
return actual_size; return actual_size;
}; };
} }
static auto write_failer(boost::system::errc::errc_t error) static auto write_failer(boost::system::errc::errc_t error)
{ {
return [error](boost::asio::const_buffer, error_code& ec) { return [error](boost::asio::const_buffer, error_code& ec) {
ec = make_error_code(error); ec = make_error_code(error);
return 0; return 0;
}; };
} }
}; };
TEST_F(MysqlChannelWriteTest, SyncWrite_AllWritesSuccessful_WritesHeaderAndBuffer) TEST_F(MysqlChannelWriteTest, SyncWrite_AllWritesSuccessful_WritesHeaderAndBuffer)
{ {
ON_CALL(stream, write_buffer) ON_CALL(stream, write_buffer)
.WillByDefault(Invoke(make_write_handler())); .WillByDefault(Invoke(make_write_handler()));
chan.write(buffer(std::vector<uint8_t>{0xaa, 0xab, 0xac}), code); chan.write(buffer(std::vector<uint8_t>{0xaa, 0xab, 0xac}), code);
verify_buffer({ verify_buffer({
0x03, 0x00, 0x00, 0x00, // header 0x03, 0x00, 0x00, 0x00, // header
0xaa, 0xab, 0xac // body 0xaa, 0xab, 0xac // body
}); });
EXPECT_EQ(code, error_code()); EXPECT_EQ(code, error_code());
} }
TEST_F(MysqlChannelWriteTest, SyncWrite_MoreThan16M_SplitsInPackets) TEST_F(MysqlChannelWriteTest, SyncWrite_MoreThan16M_SplitsInPackets)
{ {
ON_CALL(stream, write_buffer) ON_CALL(stream, write_buffer)
.WillByDefault(Invoke(make_write_handler())); .WillByDefault(Invoke(make_write_handler()));
chan.write(buffer(std::vector<uint8_t>(2*0xffffff + 4, 0xab)), code); chan.write(buffer(std::vector<uint8_t>(2*0xffffff + 4, 0xab)), code);
std::vector<uint8_t> expected_buffer {0xff, 0xff, 0xff, 0x00}; std::vector<uint8_t> expected_buffer {0xff, 0xff, 0xff, 0x00};
concat(expected_buffer, std::vector<std::uint8_t>(0xffffff, 0xab)); concat(expected_buffer, std::vector<std::uint8_t>(0xffffff, 0xab));
concat(expected_buffer, {0xff, 0xff, 0xff, 0x01}); concat(expected_buffer, {0xff, 0xff, 0xff, 0x01});
concat(expected_buffer, std::vector<std::uint8_t>(0xffffff, 0xab)); concat(expected_buffer, std::vector<std::uint8_t>(0xffffff, 0xab));
concat(expected_buffer, {0x04, 0x00, 0x00, 0x02}); concat(expected_buffer, {0x04, 0x00, 0x00, 0x02});
concat(expected_buffer, std::vector<std::uint8_t>(4, 0xab)); concat(expected_buffer, std::vector<std::uint8_t>(4, 0xab));
verify_buffer(expected_buffer); verify_buffer(expected_buffer);
EXPECT_EQ(code, error_code()); EXPECT_EQ(code, error_code());
} }
TEST_F(MysqlChannelWriteTest, SyncWrite_EmptyPacket_WritesHeader) TEST_F(MysqlChannelWriteTest, SyncWrite_EmptyPacket_WritesHeader)
{ {
ON_CALL(stream, write_buffer) ON_CALL(stream, write_buffer)
.WillByDefault(Invoke(make_write_handler())); .WillByDefault(Invoke(make_write_handler()));
chan.reset_sequence_number(2); chan.reset_sequence_number(2);
chan.write(buffer(std::vector<uint8_t>{}), code); chan.write(buffer(std::vector<uint8_t>{}), code);
verify_buffer({0x00, 0x00, 0x00, 0x02}); verify_buffer({0x00, 0x00, 0x00, 0x02});
EXPECT_EQ(code, error_code()); EXPECT_EQ(code, error_code());
} }
TEST_F(MysqlChannelWriteTest, SyncWrite_ShortWrites_WritesHeaderAndBuffer) TEST_F(MysqlChannelWriteTest, SyncWrite_ShortWrites_WritesHeaderAndBuffer)
{ {
ON_CALL(stream, write_buffer) ON_CALL(stream, write_buffer)
.WillByDefault(Invoke(make_write_handler(2))); .WillByDefault(Invoke(make_write_handler(2)));
chan.write(buffer(std::vector<uint8_t>{0xaa, 0xab, 0xac}), code); chan.write(buffer(std::vector<uint8_t>{0xaa, 0xab, 0xac}), code);
verify_buffer({ verify_buffer({
0x03, 0x00, 0x00, 0x00, // header 0x03, 0x00, 0x00, 0x00, // header
0xaa, 0xab, 0xac // body 0xaa, 0xab, 0xac // body
}); });
EXPECT_EQ(code, error_code()); EXPECT_EQ(code, error_code());
} }
TEST_F(MysqlChannelWriteTest, SyncWrite_WriteErrorInHeader_ReturnsErrorCode) TEST_F(MysqlChannelWriteTest, SyncWrite_WriteErrorInHeader_ReturnsErrorCode)
{ {
ON_CALL(stream, write_buffer) ON_CALL(stream, write_buffer)
.WillByDefault(Invoke(write_failer(boost::system::errc::broken_pipe))); .WillByDefault(Invoke(write_failer(boost::system::errc::broken_pipe)));
chan.write(buffer(std::vector<uint8_t>(10, 0x01)), code); chan.write(buffer(std::vector<uint8_t>(10, 0x01)), code);
EXPECT_EQ(code, make_error_code(boost::system::errc::broken_pipe)); EXPECT_EQ(code, make_error_code(boost::system::errc::broken_pipe));
} }
TEST_F(MysqlChannelWriteTest, SyncWrite_WriteErrorInPacket_ReturnsErrorCode) TEST_F(MysqlChannelWriteTest, SyncWrite_WriteErrorInPacket_ReturnsErrorCode)
{ {
EXPECT_CALL(stream, write_buffer) EXPECT_CALL(stream, write_buffer)
.WillOnce(Return(4)) .WillOnce(Return(4))
.WillOnce(Invoke(write_failer(boost::system::errc::broken_pipe))); .WillOnce(Invoke(write_failer(boost::system::errc::broken_pipe)));
chan.write(buffer(std::vector<uint8_t>(10, 0x01)), code); chan.write(buffer(std::vector<uint8_t>(10, 0x01)), code);
EXPECT_EQ(code, make_error_code(boost::system::errc::broken_pipe)); EXPECT_EQ(code, make_error_code(boost::system::errc::broken_pipe));
} }
TEST_F(MysqlChannelWriteTest, SyncWrite_SequenceNumberNotZero_RespectsSequenceNumber) TEST_F(MysqlChannelWriteTest, SyncWrite_SequenceNumberNotZero_RespectsSequenceNumber)
{ {
chan.reset_sequence_number(0xab); chan.reset_sequence_number(0xab);
ON_CALL(stream, write_buffer) ON_CALL(stream, write_buffer)
.WillByDefault(Invoke(make_write_handler())); .WillByDefault(Invoke(make_write_handler()));
chan.write(buffer(std::vector<uint8_t>{0xaa, 0xab, 0xac}), code); chan.write(buffer(std::vector<uint8_t>{0xaa, 0xab, 0xac}), code);
verify_buffer({ verify_buffer({
0x03, 0x00, 0x00, 0xab, // header 0x03, 0x00, 0x00, 0xab, // header
0xaa, 0xab, 0xac // body 0xaa, 0xab, 0xac // body
}); });
EXPECT_EQ(code, error_code()); EXPECT_EQ(code, error_code());
EXPECT_EQ(chan.sequence_number(), 0xac); EXPECT_EQ(chan.sequence_number(), 0xac);
} }
TEST_F(MysqlChannelWriteTest, SyncWrite_SequenceIsFF_WrapsSequenceNumber) TEST_F(MysqlChannelWriteTest, SyncWrite_SequenceIsFF_WrapsSequenceNumber)
{ {
chan.reset_sequence_number(0xff); chan.reset_sequence_number(0xff);
ON_CALL(stream, write_buffer) ON_CALL(stream, write_buffer)
.WillByDefault(Invoke(make_write_handler())); .WillByDefault(Invoke(make_write_handler()));
chan.write(buffer(std::vector<uint8_t>{0xaa, 0xab, 0xac}), code); chan.write(buffer(std::vector<uint8_t>{0xaa, 0xab, 0xac}), code);
verify_buffer({ verify_buffer({
0x03, 0x00, 0x00, 0xff, // header 0x03, 0x00, 0x00, 0xff, // header
0xaa, 0xab, 0xac // body 0xaa, 0xab, 0xac // body
}); });
EXPECT_EQ(code, error_code()); EXPECT_EQ(code, error_code());
EXPECT_EQ(chan.sequence_number(), 0); EXPECT_EQ(chan.sequence_number(), 0);
} }
} // anon namespace } // anon namespace

View File

@ -16,166 +16,166 @@ namespace
{ {
INSTANTIATE_TEST_SUITE_P(PacketHeader, FullSerializationTest, ::testing::Values( INSTANTIATE_TEST_SUITE_P(PacketHeader, FullSerializationTest, ::testing::Values(
serialization_testcase(packet_header{int3(3), int1(0)}, {0x03, 0x00, 0x00, 0x00}, "small_packet_seqnum_0"), serialization_testcase(packet_header{int3(3), int1(0)}, {0x03, 0x00, 0x00, 0x00}, "small_packet_seqnum_0"),
serialization_testcase(packet_header{int3(9), int1(2)}, {0x09, 0x00, 0x00, 0x02}, "small_packet_seqnum_not_0"), serialization_testcase(packet_header{int3(9), int1(2)}, {0x09, 0x00, 0x00, 0x02}, "small_packet_seqnum_not_0"),
serialization_testcase(packet_header{int3(0xcacbcc), int1(0xfa)}, {0xcc, 0xcb, 0xca, 0xfa}, "big_packet_seqnum_0"), serialization_testcase(packet_header{int3(0xcacbcc), int1(0xfa)}, {0xcc, 0xcb, 0xca, 0xfa}, "big_packet_seqnum_0"),
serialization_testcase(packet_header{int3(0xffffff), int1(0xff)}, {0xff, 0xff, 0xff, 0xff}, "max_packet_max_seqnum") serialization_testcase(packet_header{int3(0xffffff), int1(0xff)}, {0xff, 0xff, 0xff, 0xff}, "max_packet_max_seqnum")
), test_name_generator); ), test_name_generator);
INSTANTIATE_TEST_SUITE_P(OkPacket, DeserializeTest, ::testing::Values( INSTANTIATE_TEST_SUITE_P(OkPacket, DeserializeTest, ::testing::Values(
serialization_testcase(ok_packet{ serialization_testcase(ok_packet{
int_lenenc(4), // affected rows int_lenenc(4), // affected rows
int_lenenc(0), // last insert ID int_lenenc(0), // last insert ID
int2(static_cast<std::uint16_t>(SERVER_STATUS_AUTOCOMMIT | SERVER_QUERY_NO_INDEX_USED)), // server status int2(static_cast<std::uint16_t>(SERVER_STATUS_AUTOCOMMIT | SERVER_QUERY_NO_INDEX_USED)), // server status
int2(0), // warnings int2(0), // warnings
string_lenenc("Rows matched: 5 Changed: 4 Warnings: 0") string_lenenc("Rows matched: 5 Changed: 4 Warnings: 0")
}, { }, {
0x04, 0x00, 0x22, 0x00, 0x00, 0x00, 0x28, 0x52, 0x6f, 0x77, 0x73, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, 0x28, 0x52, 0x6f, 0x77, 0x73,
0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x3a, 0x20, 0x35, 0x20, 0x20, 0x43, 0x68, 0x61, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x3a, 0x20, 0x35, 0x20, 0x20, 0x43, 0x68, 0x61,
0x6e, 0x67, 0x65, 0x64, 0x3a, 0x20, 0x34, 0x20, 0x20, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x67, 0x65, 0x64, 0x3a, 0x20, 0x34, 0x20, 0x20, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67,
0x73, 0x3a, 0x20, 0x30 0x73, 0x3a, 0x20, 0x30
}, "successful_update"), }, "successful_update"),
serialization_testcase(ok_packet{ serialization_testcase(ok_packet{
int_lenenc(1), // affected rows int_lenenc(1), // affected rows
int_lenenc(6), // last insert ID int_lenenc(6), // last insert ID
int2(static_cast<std::uint16_t>(SERVER_STATUS_AUTOCOMMIT)), // server status int2(static_cast<std::uint16_t>(SERVER_STATUS_AUTOCOMMIT)), // server status
int2(0), // warnings int2(0), // warnings
string_lenenc("") // no message string_lenenc("") // no message
},{ },{
0x01, 0x06, 0x02, 0x00, 0x00, 0x00 0x01, 0x06, 0x02, 0x00, 0x00, 0x00
}, "successful_insert"), }, "successful_insert"),
serialization_testcase(ok_packet{ serialization_testcase(ok_packet{
int_lenenc(0), // affected rows int_lenenc(0), // affected rows
int_lenenc(0), // last insert ID int_lenenc(0), // last insert ID
int2(static_cast<std::uint16_t>(SERVER_STATUS_AUTOCOMMIT)), // server status int2(static_cast<std::uint16_t>(SERVER_STATUS_AUTOCOMMIT)), // server status
int2(0), // warnings int2(0), // warnings
string_lenenc("") // no message string_lenenc("") // no message
}, { }, {
0x00, 0x00, 0x02, 0x00, 0x00, 0x00 0x00, 0x00, 0x02, 0x00, 0x00, 0x00
}, "successful_login") }, "successful_login")
), test_name_generator); ), test_name_generator);
INSTANTIATE_TEST_SUITE_P(ErrPacket, DeserializeTest, ::testing::Values( INSTANTIATE_TEST_SUITE_P(ErrPacket, DeserializeTest, ::testing::Values(
serialization_testcase(err_packet{ serialization_testcase(err_packet{
int2(1049), // eror code int2(1049), // eror code
string_fixed<1>{0x23}, // sql state marker string_fixed<1>{0x23}, // sql state marker
string_fixed<5>{'4', '2', '0', '0', '0'}, // sql state string_fixed<5>{'4', '2', '0', '0', '0'}, // sql state
string_eof("Unknown database 'a'") // err msg string_eof("Unknown database 'a'") // err msg
}, { }, {
0x19, 0x04, 0x23, 0x34, 0x32, 0x30, 0x30, 0x30, 0x55, 0x6e, 0x6b, 0x19, 0x04, 0x23, 0x34, 0x32, 0x30, 0x30, 0x30, 0x55, 0x6e, 0x6b,
0x6e, 0x6f, 0x77, 0x6e, 0x20, 0x64, 0x61, 0x74, 0x6e, 0x6f, 0x77, 0x6e, 0x20, 0x64, 0x61, 0x74,
0x61, 0x62, 0x61, 0x73, 0x65, 0x20, 0x27, 0x61, 0x27 0x61, 0x62, 0x61, 0x73, 0x65, 0x20, 0x27, 0x61, 0x27
}, "wrong_use_database"), }, "wrong_use_database"),
serialization_testcase(err_packet{ serialization_testcase(err_packet{
int2(1146), // eror code int2(1146), // eror code
string_fixed<1>{0x23}, // sql state marker string_fixed<1>{0x23}, // sql state marker
string_fixed<5>{'4', '2', 'S', '0', '2'}, // sql state string_fixed<5>{'4', '2', 'S', '0', '2'}, // sql state
string_eof("Table 'awesome.unknown' doesn't exist") // err msg string_eof("Table 'awesome.unknown' doesn't exist") // err msg
}, { }, {
0x7a, 0x04, 0x23, 0x34, 0x32, 0x53, 0x30, 0x32, 0x7a, 0x04, 0x23, 0x34, 0x32, 0x53, 0x30, 0x32,
0x54, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x27, 0x61, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x27, 0x61,
0x77, 0x65, 0x73, 0x6f, 0x6d, 0x65, 0x2e, 0x75, 0x77, 0x65, 0x73, 0x6f, 0x6d, 0x65, 0x2e, 0x75,
0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x27, 0x20, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x27, 0x20,
0x64, 0x6f, 0x65, 0x73, 0x6e, 0x27, 0x74, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x6e, 0x27, 0x74, 0x20,
0x65, 0x78, 0x69, 0x73, 0x74 0x65, 0x78, 0x69, 0x73, 0x74
}, "unknown_table"), }, "unknown_table"),
serialization_testcase(err_packet{ serialization_testcase(err_packet{
int2(1045), // error code int2(1045), // error code
string_fixed<1>{0x23}, // SQL state marker string_fixed<1>{0x23}, // SQL state marker
string_fixed<5>{'2', '8', '0', '0', '0'}, // SQL state string_fixed<5>{'2', '8', '0', '0', '0'}, // SQL state
string_eof("Access denied for user 'root'@'localhost' (using password: YES)") string_eof("Access denied for user 'root'@'localhost' (using password: YES)")
}, { }, {
0x15, 0x04, 0x23, 0x32, 0x38, 0x30, 0x30, 0x30, 0x15, 0x04, 0x23, 0x32, 0x38, 0x30, 0x30, 0x30,
0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x20, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x20, 0x64,
0x65, 0x6e, 0x69, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x65, 0x6e, 0x69, 0x65, 0x64, 0x20, 0x66, 0x6f,
0x72, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x27, 0x72, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x27,
0x72, 0x6f, 0x6f, 0x74, 0x27, 0x40, 0x27, 0x6c, 0x72, 0x6f, 0x6f, 0x74, 0x27, 0x40, 0x27, 0x6c,
0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74,
0x27, 0x20, 0x28, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x27, 0x20, 0x28, 0x75, 0x73, 0x69, 0x6e, 0x67,
0x20, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x20, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
0x64, 0x3a, 0x20, 0x59, 0x45, 0x53, 0x29 0x64, 0x3a, 0x20, 0x59, 0x45, 0x53, 0x29
}, "failed_login") }, "failed_login")
), test_name_generator); ), test_name_generator);
// Column definition // Column definition
INSTANTIATE_TEST_SUITE_P(ColumnDefinition, DeserializeSpaceTest, testing::Values( INSTANTIATE_TEST_SUITE_P(ColumnDefinition, DeserializeSpaceTest, testing::Values(
serialization_testcase(column_definition_packet{ serialization_testcase(column_definition_packet{
string_lenenc("def"), //catalog string_lenenc("def"), //catalog
string_lenenc("awesome"), // schema (database) string_lenenc("awesome"), // schema (database)
string_lenenc("test_table"), // table string_lenenc("test_table"), // table
string_lenenc("test_table"), // physical table string_lenenc("test_table"), // physical table
string_lenenc("id"), // field name string_lenenc("id"), // field name
string_lenenc("id"), // physical field name string_lenenc("id"), // physical field name
collation::binary, collation::binary,
int4(11), // length int4(11), // length
protocol_field_type::long_, protocol_field_type::long_,
int2( int2(
column_flags::not_null | column_flags::not_null |
column_flags::pri_key | column_flags::pri_key |
column_flags::auto_increment | column_flags::auto_increment |
column_flags::part_key column_flags::part_key
), ),
int1(0) // decimals int1(0) // decimals
}, { }, {
0x03, 0x64, 0x65, 0x66, 0x07, 0x61, 0x77, 0x65, 0x03, 0x64, 0x65, 0x66, 0x07, 0x61, 0x77, 0x65,
0x73, 0x6f, 0x6d, 0x65, 0x0a, 0x74, 0x65, 0x73, 0x73, 0x6f, 0x6d, 0x65, 0x0a, 0x74, 0x65, 0x73,
0x74, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x0a, 0x74, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x0a,
0x74, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x61, 0x62, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x61, 0x62,
0x6c, 0x65, 0x02, 0x69, 0x64, 0x02, 0x69, 0x64, 0x6c, 0x65, 0x02, 0x69, 0x64, 0x02, 0x69, 0x64,
0x0c, 0x3f, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x03, 0x0c, 0x3f, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x03,
0x03, 0x42, 0x00, 0x00, 0x00 0x03, 0x42, 0x00, 0x00, 0x00
}, "numeric_auto_increment_primary_key"), }, "numeric_auto_increment_primary_key"),
serialization_testcase(column_definition_packet{ serialization_testcase(column_definition_packet{
string_lenenc("def"), //catalog string_lenenc("def"), //catalog
string_lenenc("awesome"), // schema (database) string_lenenc("awesome"), // schema (database)
string_lenenc("child"), // table string_lenenc("child"), // table
string_lenenc("child_table"), // physical table string_lenenc("child_table"), // physical table
string_lenenc("field_alias"), // field name string_lenenc("field_alias"), // field name
string_lenenc("field_varchar"), // physical field name string_lenenc("field_varchar"), // physical field name
collation::utf8_general_ci, collation::utf8_general_ci,
int4(765), // length int4(765), // length
protocol_field_type::var_string, protocol_field_type::var_string,
int2(), // no column flags int2(), // no column flags
int1(0) // decimals int1(0) // decimals
}, { }, {
0x03, 0x64, 0x65, 0x66, 0x07, 0x61, 0x77, 0x65, 0x03, 0x64, 0x65, 0x66, 0x07, 0x61, 0x77, 0x65,
0x73, 0x6f, 0x6d, 0x65, 0x05, 0x63, 0x68, 0x69, 0x73, 0x6f, 0x6d, 0x65, 0x05, 0x63, 0x68, 0x69,
0x6c, 0x64, 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x6c, 0x64, 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64,
0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x0b, 0x66, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x0b, 0x66,
0x69, 0x65, 0x6c, 0x64, 0x5f, 0x61, 0x6c, 0x69, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x61, 0x6c, 0x69,
0x61, 0x73, 0x0d, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x61, 0x73, 0x0d, 0x66, 0x69, 0x65, 0x6c, 0x64,
0x5f, 0x76, 0x61, 0x72, 0x63, 0x68, 0x61, 0x72, 0x5f, 0x76, 0x61, 0x72, 0x63, 0x68, 0x61, 0x72,
0x0c, 0x21, 0x00, 0xfd, 0x02, 0x00, 0x00, 0xfd, 0x0c, 0x21, 0x00, 0xfd, 0x02, 0x00, 0x00, 0xfd,
0x00, 0x00, 0x00, 0x00, 0x00 0x00, 0x00, 0x00, 0x00, 0x00
}, "varchar_field_aliased_field_and_table_names_join"), }, "varchar_field_aliased_field_and_table_names_join"),
serialization_testcase(column_definition_packet{ serialization_testcase(column_definition_packet{
string_lenenc("def"), //catalog string_lenenc("def"), //catalog
string_lenenc("awesome"), // schema (database) string_lenenc("awesome"), // schema (database)
string_lenenc("test_table"), // table string_lenenc("test_table"), // table
string_lenenc("test_table"), // physical table string_lenenc("test_table"), // physical table
string_lenenc("field_float"), // field name string_lenenc("field_float"), // field name
string_lenenc("field_float"), // physical field name string_lenenc("field_float"), // physical field name
collation::binary, // binary collation::binary, // binary
int4(12), // length int4(12), // length
protocol_field_type::float_, protocol_field_type::float_,
int2(), // no column flags int2(), // no column flags
int1(31) // decimals int1(31) // decimals
}, { }, {
0x03, 0x64, 0x65, 0x66, 0x07, 0x61, 0x77, 0x65, 0x03, 0x64, 0x65, 0x66, 0x07, 0x61, 0x77, 0x65,
0x73, 0x6f, 0x6d, 0x65, 0x0a, 0x74, 0x65, 0x73, 0x73, 0x6f, 0x6d, 0x65, 0x0a, 0x74, 0x65, 0x73,
0x74, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x0a, 0x74, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x0a,
0x74, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x61, 0x62, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x61, 0x62,
0x6c, 0x65, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x6c, 0x65, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64,
0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x0b, 0x66, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x0b, 0x66,
0x69, 0x65, 0x6c, 0x64, 0x5f, 0x66, 0x6c, 0x6f, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x66, 0x6c, 0x6f,
0x61, 0x74, 0x0c, 0x3f, 0x00, 0x0c, 0x00, 0x00, 0x61, 0x74, 0x0c, 0x3f, 0x00, 0x0c, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x1f, 0x00, 0x00 0x00, 0x04, 0x00, 0x00, 0x1f, 0x00, 0x00
}, "float_field") }, "float_field")
), test_name_generator); ), test_name_generator);

Some files were not shown because too many files have changed in this diff Show More