Repository: boostorg/mysql Branch: develop Commit: 4558bb4579dd Files: 620 Total size: 4.5 MB Directory structure: gitextract_6ypeo_s4/ ├── .clang-format ├── .clangd ├── .codecov.yml ├── .dockerignore ├── .drone.star ├── .github/ │ └── workflows/ │ ├── build-code.yml │ ├── coverage.yml │ ├── docker-windows.yml │ └── fuzz.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE_1_0.txt ├── README.md ├── bench/ │ ├── CMakeLists.txt │ ├── bench.ipynb │ ├── connection_pool.cpp │ ├── db_setup.sql │ ├── many_rows_boost.cpp │ ├── many_rows_libmariadb.cpp │ ├── many_rows_libmysqlclient.cpp │ ├── one_big_row_boost.cpp │ ├── one_big_row_libmariadb.cpp │ ├── one_big_row_libmysqlclient.cpp │ ├── one_small_row_boost.cpp │ ├── one_small_row_libmariadb.cpp │ ├── one_small_row_libmysqlclient.cpp │ ├── stmt_params_boost.cpp │ ├── stmt_params_libmariadb.cpp │ └── stmt_params_libmysqlclient.cpp ├── build.jam ├── cmake/ │ └── utils.cmake ├── doc/ │ ├── Jamfile │ ├── config.json │ ├── doxygen.hpp │ ├── qbk/ │ │ ├── 00_main.qbk │ │ ├── 01_intro.qbk │ │ ├── 02_integrating.qbk │ │ ├── 03_1_tutorial_sync.qbk │ │ ├── 03_2_tutorial_async.qbk │ │ ├── 03_3_tutorial_with_params.qbk │ │ ├── 03_4_tutorial_static_interface.qbk │ │ ├── 03_5_tutorial_updates_transactions.qbk │ │ ├── 03_6_tutorial_connection_pool.qbk │ │ ├── 03_7_tutorial_error_handling.qbk │ │ ├── 04_overview.qbk │ │ ├── 05_connection_establishment.qbk │ │ ├── 06_text_queries.qbk │ │ ├── 07_prepared_statements.qbk │ │ ├── 08_dynamic_interface.qbk │ │ ├── 09_static_interface.qbk │ │ ├── 10_multi_resultset.qbk │ │ ├── 11_multi_function.qbk │ │ ├── 12_connection_pool.qbk │ │ ├── 13_1_interfacing_sync_async.qbk │ │ ├── 13_async.qbk │ │ ├── 14_error_handling.qbk │ │ ├── 15_sql_formatting_advanced.qbk │ │ ├── 16_metadata.qbk │ │ ├── 17_charsets.qbk │ │ ├── 18_time_types.qbk │ │ ├── 19_templated_connection.qbk │ │ ├── 20_1_benchmarks.qbk │ │ ├── 20_pipeline.qbk │ │ ├── 21_examples.qbk │ │ └── helpers/ │ │ ├── ExecutionRequest.qbk │ │ ├── ExecutionStateType.qbk │ │ ├── FieldViewFwdIterator.qbk │ │ ├── Formattable.qbk │ │ ├── OutputString.qbk │ │ ├── ResultsType.qbk │ │ ├── SocketStream.qbk │ │ ├── StaticRow.qbk │ │ ├── Stream.qbk │ │ ├── WritableFieldTuple.qbk │ │ └── quickref.xml │ └── upgrade_1_82.md ├── example/ │ ├── 1_tutorial/ │ │ ├── 1_sync.cpp │ │ ├── 2_async.cpp │ │ ├── 3_with_params.cpp │ │ ├── 4_static_interface.cpp │ │ ├── 5_updates_transactions.cpp │ │ ├── 6_connection_pool.cpp │ │ └── 7_error_handling.cpp │ ├── 2_simple/ │ │ ├── batch_inserts.cpp │ │ ├── batch_inserts_generic.cpp │ │ ├── callbacks.cpp │ │ ├── coroutines_cpp11.cpp │ │ ├── deletes.cpp │ │ ├── disable_tls.cpp │ │ ├── dynamic_filters.cpp │ │ ├── inserts.cpp │ │ ├── metadata.cpp │ │ ├── multi_function.cpp │ │ ├── patch_updates.cpp │ │ ├── pipeline.cpp │ │ ├── prepared_statements.cpp │ │ ├── source_script.cpp │ │ ├── tls_certificate_verification.cpp │ │ └── unix_socket.cpp │ ├── 3_advanced/ │ │ ├── http_server_cpp14_coroutines/ │ │ │ ├── handle_request.cpp │ │ │ ├── handle_request.hpp │ │ │ ├── main.cpp │ │ │ ├── repository.cpp │ │ │ ├── repository.hpp │ │ │ ├── server.cpp │ │ │ ├── server.hpp │ │ │ └── types.hpp │ │ └── http_server_cpp20/ │ │ ├── db_setup.sql │ │ ├── error.cpp │ │ ├── error.hpp │ │ ├── handle_request.cpp │ │ ├── handle_request.hpp │ │ ├── main.cpp │ │ ├── repository.cpp │ │ ├── repository.hpp │ │ ├── server.cpp │ │ ├── server.hpp │ │ └── types.hpp │ ├── CMakeLists.txt │ ├── Jamfile │ ├── db_setup.sql │ └── private/ │ ├── employees_multiple.json │ ├── employees_single.json │ ├── launch_server.py │ ├── run_batch_inserts.py │ ├── run_dynamic_filters.py │ ├── run_notes.py │ ├── run_orders.py │ ├── run_patch_updates.py │ ├── run_tutorial_connection_pool.py │ └── test_script.sql ├── include/ │ └── boost/ │ ├── mysql/ │ │ ├── any_address.hpp │ │ ├── any_connection.hpp │ │ ├── bad_field_access.hpp │ │ ├── blob.hpp │ │ ├── blob_view.hpp │ │ ├── buffer_params.hpp │ │ ├── character_set.hpp │ │ ├── client_errc.hpp │ │ ├── column_type.hpp │ │ ├── common_server_errc.hpp │ │ ├── connect_params.hpp │ │ ├── connection.hpp │ │ ├── connection_pool.hpp │ │ ├── constant_string_view.hpp │ │ ├── date.hpp │ │ ├── datetime.hpp │ │ ├── days.hpp │ │ ├── defaults.hpp │ │ ├── detail/ │ │ │ ├── access.hpp │ │ │ ├── algo_params.hpp │ │ │ ├── any_execution_request.hpp │ │ │ ├── any_resumable_ref.hpp │ │ │ ├── character_set.hpp │ │ │ ├── coldef_view.hpp │ │ │ ├── config.hpp │ │ │ ├── connect_params_helpers.hpp │ │ │ ├── connection_impl.hpp │ │ │ ├── connection_pool_fwd.hpp │ │ │ ├── datetime.hpp │ │ │ ├── engine.hpp │ │ │ ├── engine_impl.hpp │ │ │ ├── engine_stream_adaptor.hpp │ │ │ ├── escape_string.hpp │ │ │ ├── execution_concepts.hpp │ │ │ ├── execution_processor/ │ │ │ │ ├── execution_processor.hpp │ │ │ │ ├── execution_state_impl.hpp │ │ │ │ ├── results_impl.hpp │ │ │ │ ├── static_execution_state_impl.hpp │ │ │ │ └── static_results_impl.hpp │ │ │ ├── field_impl.hpp │ │ │ ├── flags.hpp │ │ │ ├── format_sql.hpp │ │ │ ├── initiation_base.hpp │ │ │ ├── intermediate_handler.hpp │ │ │ ├── next_action.hpp │ │ │ ├── ok_view.hpp │ │ │ ├── output_string.hpp │ │ │ ├── pipeline.hpp │ │ │ ├── rebind_executor.hpp │ │ │ ├── results_iterator.hpp │ │ │ ├── resultset_encoding.hpp │ │ │ ├── row_impl.hpp │ │ │ ├── rows_iterator.hpp │ │ │ ├── sequence.hpp │ │ │ ├── socket_stream.hpp │ │ │ ├── ssl_fwd.hpp │ │ │ ├── string_view_offset.hpp │ │ │ ├── throw_on_error_loc.hpp │ │ │ ├── typing/ │ │ │ │ ├── meta_check_context.hpp │ │ │ │ ├── pos_map.hpp │ │ │ │ ├── readable_field_traits.hpp │ │ │ │ └── row_traits.hpp │ │ │ ├── void_t.hpp │ │ │ └── writable_field_traits.hpp │ │ ├── diagnostics.hpp │ │ ├── error_categories.hpp │ │ ├── error_code.hpp │ │ ├── error_with_diagnostics.hpp │ │ ├── escape_string.hpp │ │ ├── execution_state.hpp │ │ ├── field.hpp │ │ ├── field_kind.hpp │ │ ├── field_view.hpp │ │ ├── format_sql.hpp │ │ ├── handshake_params.hpp │ │ ├── impl/ │ │ │ ├── any_connection.ipp │ │ │ ├── character_set.ipp │ │ │ ├── column_type.ipp │ │ │ ├── connection_impl.ipp │ │ │ ├── connection_pool.ipp │ │ │ ├── date.ipp │ │ │ ├── datetime.ipp │ │ │ ├── engine_impl_instantiations.ipp │ │ │ ├── error_categories.ipp │ │ │ ├── escape_string.ipp │ │ │ ├── execution_state_impl.ipp │ │ │ ├── field.ipp │ │ │ ├── field_kind.ipp │ │ │ ├── field_view.hpp │ │ │ ├── field_view.ipp │ │ │ ├── format_sql.hpp │ │ │ ├── format_sql.ipp │ │ │ ├── internal/ │ │ │ │ ├── byte_to_hex.hpp │ │ │ │ ├── call_next_char.hpp │ │ │ │ ├── connection_pool/ │ │ │ │ │ ├── connection_node.hpp │ │ │ │ │ ├── connection_pool_impl.hpp │ │ │ │ │ ├── internal_pool_params.hpp │ │ │ │ │ └── sansio_connection_node.hpp │ │ │ │ ├── coroutine.hpp │ │ │ │ ├── dt_to_string.hpp │ │ │ │ ├── error/ │ │ │ │ │ ├── server_error_to_string.hpp │ │ │ │ │ └── server_error_to_string.ipp │ │ │ │ ├── next_power_of_two.hpp │ │ │ │ ├── protocol/ │ │ │ │ │ ├── capabilities.hpp │ │ │ │ │ ├── db_flavor.hpp │ │ │ │ │ ├── deserialization.hpp │ │ │ │ │ ├── frame_header.hpp │ │ │ │ │ ├── impl/ │ │ │ │ │ │ ├── binary_protocol.hpp │ │ │ │ │ │ ├── bit_deserialization.hpp │ │ │ │ │ │ ├── deserialization_context.hpp │ │ │ │ │ │ ├── null_bitmap.hpp │ │ │ │ │ │ ├── protocol_field_type.hpp │ │ │ │ │ │ ├── protocol_types.hpp │ │ │ │ │ │ ├── serialization_context.hpp │ │ │ │ │ │ ├── span_string.hpp │ │ │ │ │ │ └── text_protocol.hpp │ │ │ │ │ ├── serialization.hpp │ │ │ │ │ └── static_buffer.hpp │ │ │ │ ├── sansio/ │ │ │ │ │ ├── auth_plugin_common.hpp │ │ │ │ │ ├── caching_sha2_password.hpp │ │ │ │ │ ├── close_connection.hpp │ │ │ │ │ ├── close_statement.hpp │ │ │ │ │ ├── connect.hpp │ │ │ │ │ ├── connection_state.hpp │ │ │ │ │ ├── connection_state_data.hpp │ │ │ │ │ ├── csha2p_encrypt_password.hpp │ │ │ │ │ ├── execute.hpp │ │ │ │ │ ├── handshake.hpp │ │ │ │ │ ├── message_reader.hpp │ │ │ │ │ ├── mysql_native_password.hpp │ │ │ │ │ ├── ping.hpp │ │ │ │ │ ├── prepare_statement.hpp │ │ │ │ │ ├── quit_connection.hpp │ │ │ │ │ ├── read_buffer.hpp │ │ │ │ │ ├── read_resultset_head.hpp │ │ │ │ │ ├── read_some_rows.hpp │ │ │ │ │ ├── read_some_rows_dynamic.hpp │ │ │ │ │ ├── reset_connection.hpp │ │ │ │ │ ├── run_pipeline.hpp │ │ │ │ │ ├── set_character_set.hpp │ │ │ │ │ ├── start_execution.hpp │ │ │ │ │ └── top_level_algo.hpp │ │ │ │ ├── ssl_context_with_default.hpp │ │ │ │ └── variant_stream.hpp │ │ │ ├── is_fatal_error.ipp │ │ │ ├── meta_check_context.ipp │ │ │ ├── pfr.hpp │ │ │ ├── pipeline.ipp │ │ │ ├── results_impl.ipp │ │ │ ├── resultset.ipp │ │ │ ├── row_impl.ipp │ │ │ ├── statement.hpp │ │ │ ├── static_execution_state_impl.ipp │ │ │ ├── static_results_impl.ipp │ │ │ ├── with_diagnostics.hpp │ │ │ └── with_params.hpp │ │ ├── is_fatal_error.hpp │ │ ├── mariadb_collations.hpp │ │ ├── mariadb_server_errc.hpp │ │ ├── metadata.hpp │ │ ├── metadata_collection_view.hpp │ │ ├── metadata_mode.hpp │ │ ├── mysql_collations.hpp │ │ ├── mysql_server_errc.hpp │ │ ├── pfr.hpp │ │ ├── pipeline.hpp │ │ ├── pool_params.hpp │ │ ├── results.hpp │ │ ├── resultset.hpp │ │ ├── resultset_view.hpp │ │ ├── row.hpp │ │ ├── row_view.hpp │ │ ├── rows.hpp │ │ ├── rows_view.hpp │ │ ├── sequence.hpp │ │ ├── src.hpp │ │ ├── ssl_mode.hpp │ │ ├── statement.hpp │ │ ├── static_execution_state.hpp │ │ ├── static_results.hpp │ │ ├── string_view.hpp │ │ ├── tcp.hpp │ │ ├── tcp_ssl.hpp │ │ ├── throw_on_error.hpp │ │ ├── time.hpp │ │ ├── underlying_row.hpp │ │ ├── unix.hpp │ │ ├── unix_ssl.hpp │ │ ├── with_diagnostics.hpp │ │ └── with_params.hpp │ └── mysql.hpp ├── index.html ├── meta/ │ └── libraries.json ├── test/ │ ├── CMakeLists.txt │ ├── Jamfile │ ├── cmake_b2_separate_compilation_test/ │ │ ├── CMakeLists.txt │ │ ├── boost_mysql.cpp │ │ └── main.cpp │ ├── cmake_b2_test/ │ │ ├── CMakeLists.txt │ │ └── main.cpp │ ├── cmake_test/ │ │ ├── CMakeLists.txt │ │ └── main.cpp │ ├── common/ │ │ ├── include/ │ │ │ └── test_common/ │ │ │ ├── assert_buffer_equals.hpp │ │ │ ├── buffer_concat.hpp │ │ │ ├── check_meta.hpp │ │ │ ├── ci_server.hpp │ │ │ ├── create_basic.hpp │ │ │ ├── create_diagnostics.hpp │ │ │ ├── has_ranges.hpp │ │ │ ├── io_context_fixture.hpp │ │ │ ├── netfun_maker.hpp │ │ │ ├── network_result.hpp │ │ │ ├── poll_until.hpp │ │ │ ├── printing.hpp │ │ │ ├── source_location.hpp │ │ │ ├── stringize.hpp │ │ │ ├── tracker_executor.hpp │ │ │ └── validate_string_contains.hpp │ │ └── src/ │ │ ├── boost_asio.cpp │ │ ├── boost_mysql.cpp │ │ ├── entry_point.cpp │ │ └── utils.cpp │ ├── fuzzing/ │ │ ├── Jamfile │ │ ├── fuzz_auth_switch.cpp │ │ ├── fuzz_column_definition.cpp │ │ ├── fuzz_err_packet.cpp │ │ ├── fuzz_escape_string.cpp │ │ ├── fuzz_execute_response.cpp │ │ ├── fuzz_format_args.cpp │ │ ├── fuzz_format_identifier.cpp │ │ ├── fuzz_format_sql_injection.cpp │ │ ├── fuzz_format_strings.cpp │ │ ├── fuzz_handshake_server_response.cpp │ │ ├── fuzz_ok_packet.cpp │ │ ├── fuzz_ok_response.cpp │ │ ├── fuzz_prepare_stmt_response.cpp │ │ ├── fuzz_row.cpp │ │ ├── fuzz_row_message.cpp │ │ ├── fuzz_server_hello.cpp │ │ ├── fuzz_text_field.cpp │ │ └── fuzz_utf8mb4.cpp │ ├── integration/ │ │ ├── CMakeLists.txt │ │ ├── Jamfile │ │ ├── db_setup.sql │ │ ├── db_setup_mnp.sql │ │ ├── db_setup_sha256.sql │ │ ├── include/ │ │ │ └── test_integration/ │ │ │ ├── any_connection_fixture.hpp │ │ │ ├── connect_params_builder.hpp │ │ │ ├── metadata_validator.hpp │ │ │ ├── run_coro.hpp │ │ │ ├── server_ca.hpp │ │ │ ├── server_features.hpp │ │ │ ├── snippets/ │ │ │ │ ├── credentials.hpp │ │ │ │ ├── describe.hpp │ │ │ │ ├── get_any_connection.hpp │ │ │ │ ├── get_connection.hpp │ │ │ │ └── snippets_fixture.hpp │ │ │ ├── spotchecks_helpers.hpp │ │ │ ├── static_rows.hpp │ │ │ └── tcp_connection_fixture.hpp │ │ ├── pch.hpp │ │ ├── src/ │ │ │ ├── metadata_validator.cpp │ │ │ ├── server_features.cpp │ │ │ ├── spotchecks_helpers.cpp │ │ │ └── utils.cpp │ │ └── test/ │ │ ├── any_connection.cpp │ │ ├── character_set_tracking.cpp │ │ ├── connection_id.cpp │ │ ├── connection_pool.cpp │ │ ├── crud.cpp │ │ ├── database_types.cpp │ │ ├── db_specific.cpp │ │ ├── execution_requests.cpp │ │ ├── handshake.cpp │ │ ├── multi_function.cpp │ │ ├── multi_queries.cpp │ │ ├── pipeline.cpp │ │ ├── prepared_statements.cpp │ │ ├── reconnect.cpp │ │ ├── snippets/ │ │ │ ├── charsets.cpp │ │ │ ├── connection_establishment.cpp │ │ │ ├── connection_pool.cpp │ │ │ ├── dynamic_interface.cpp │ │ │ ├── interfacing_sync_async.cpp │ │ │ ├── metadata.cpp │ │ │ ├── multi_function.cpp │ │ │ ├── multi_resultset.cpp │ │ │ ├── overview.cpp │ │ │ ├── pipeline.cpp │ │ │ ├── prepared_statements.cpp │ │ │ ├── sql_formatting_advanced.cpp │ │ │ ├── sql_formatting_advanced_2.cpp │ │ │ ├── static_interface.cpp │ │ │ ├── templated_connection.cpp │ │ │ ├── text_queries.cpp │ │ │ ├── time_types.cpp │ │ │ └── tutorials.cpp │ │ ├── spotchecks.cpp │ │ ├── static_interface.cpp │ │ └── stored_procedures.cpp │ ├── thread_safety/ │ │ ├── Jamfile │ │ ├── connection_pool.cpp │ │ ├── connection_pool_cancel.cpp │ │ ├── connection_pool_cancel_get_connection.cpp │ │ ├── connection_pool_coroutines.cpp │ │ ├── connection_pool_external_strand.cpp │ │ ├── connection_pool_two_contexts.cpp │ │ └── tsan_pool_common.hpp │ └── unit/ │ ├── CMakeLists.txt │ ├── Jamfile │ ├── include/ │ │ └── test_unit/ │ │ ├── algo_test.hpp │ │ ├── create_coldef_frame.hpp │ │ ├── create_err.hpp │ │ ├── create_execution_processor.hpp │ │ ├── create_frame.hpp │ │ ├── create_meta.hpp │ │ ├── create_ok.hpp │ │ ├── create_ok_frame.hpp │ │ ├── create_prepare_statement_response.hpp │ │ ├── create_query_frame.hpp │ │ ├── create_row_message.hpp │ │ ├── create_statement.hpp │ │ ├── custom_allocator.hpp │ │ ├── fail_count.hpp │ │ ├── ff_charset.hpp │ │ ├── mock_execution_processor.hpp │ │ ├── mock_message.hpp │ │ ├── mock_timer.hpp │ │ ├── printing.hpp │ │ ├── row_identity.hpp │ │ ├── serialize_to_vector.hpp │ │ ├── test_any_connection.hpp │ │ └── test_stream.hpp │ ├── pch.hpp │ ├── src/ │ │ └── utils.cpp │ ├── test/ │ │ ├── any_address.cpp │ │ ├── any_connection.cpp │ │ ├── character_set.cpp │ │ ├── client_errc.cpp │ │ ├── column_type.cpp │ │ ├── common_server_errc.cpp │ │ ├── connection.cpp │ │ ├── connection_pool/ │ │ │ ├── connection_pool_impl.cpp │ │ │ └── sansio_connection_node.cpp │ │ ├── connection_pool.cpp │ │ ├── constant_string_view.cpp │ │ ├── date.cpp │ │ ├── datetime.cpp │ │ ├── detail/ │ │ │ ├── connect_params_helpers.cpp │ │ │ ├── datetime.cpp │ │ │ ├── engine_impl.cpp │ │ │ ├── execution_concepts.cpp │ │ │ ├── intermediate_handler.cpp │ │ │ ├── output_string.cpp │ │ │ ├── row_impl.cpp │ │ │ ├── rows_iterator.cpp │ │ │ ├── socket_stream.cpp │ │ │ ├── typing/ │ │ │ │ ├── meta_check_context.cpp │ │ │ │ ├── pos_map.cpp │ │ │ │ ├── readable_field_traits.cpp │ │ │ │ └── row_traits.cpp │ │ │ └── writable_field_traits.cpp │ │ ├── diagnostics.cpp │ │ ├── escape_string.cpp │ │ ├── execution_processor/ │ │ │ ├── execution_processor.cpp │ │ │ ├── execution_processor_helpers.hpp │ │ │ ├── execution_state_impl.cpp │ │ │ ├── results_impl.cpp │ │ │ ├── static_execution_processor_helpers.hpp │ │ │ ├── static_execution_state_impl.cpp │ │ │ └── static_results_impl.cpp │ │ ├── execution_state.cpp │ │ ├── field.cpp │ │ ├── field_view.cpp │ │ ├── format_sql/ │ │ │ ├── api.cpp │ │ │ ├── basic_format_context.cpp │ │ │ ├── custom_formatter.cpp │ │ │ ├── format_common.hpp │ │ │ ├── format_strings.cpp │ │ │ ├── formattable.cpp │ │ │ ├── formattable_ref.cpp │ │ │ ├── individual_value.cpp │ │ │ ├── ranges.cpp │ │ │ └── sequence.cpp │ │ ├── impl/ │ │ │ ├── dt_to_string.cpp │ │ │ ├── next_power_of_two.cpp │ │ │ ├── ssl_context_with_default.cpp │ │ │ └── variant_stream.cpp │ │ ├── is_fatal_error.cpp │ │ ├── mariadb_server_errc.cpp │ │ ├── metadata.cpp │ │ ├── mysql_server_errc.cpp │ │ ├── pfr.cpp │ │ ├── pipeline.cpp │ │ ├── pool_params.cpp │ │ ├── protocol/ │ │ │ ├── binary_protocol.cpp │ │ │ ├── capabilities.cpp │ │ │ ├── deserialization.cpp │ │ │ ├── deserialization_context.cpp │ │ │ ├── frame_header.cpp │ │ │ ├── null_bitmap.cpp │ │ │ ├── operators.hpp │ │ │ ├── protocol_field_type.cpp │ │ │ ├── protocol_types.cpp │ │ │ ├── serialization.cpp │ │ │ ├── serialization_context.cpp │ │ │ ├── serialization_test.hpp │ │ │ ├── static_buffer.cpp │ │ │ └── text_protocol.cpp │ │ ├── results.cpp │ │ ├── resultset.cpp │ │ ├── resultset_view.cpp │ │ ├── row.cpp │ │ ├── row_view.cpp │ │ ├── rows.cpp │ │ ├── rows_view.cpp │ │ ├── sansio/ │ │ │ ├── close_connection.cpp │ │ │ ├── close_statement.cpp │ │ │ ├── execute.cpp │ │ │ ├── handshake/ │ │ │ │ ├── handshake.cpp │ │ │ │ ├── handshake_capabilities.cpp │ │ │ │ ├── handshake_common.hpp │ │ │ │ ├── handshake_connection_state_data.cpp │ │ │ │ ├── handshake_csha2p.cpp │ │ │ │ ├── handshake_csha2p_encrypt_password.cpp │ │ │ │ ├── handshake_csha2p_hash_password.cpp │ │ │ │ ├── handshake_csha2p_keys.hpp │ │ │ │ ├── handshake_mnp.cpp │ │ │ │ └── handshake_mnp_hash_password.cpp │ │ │ ├── message_reader.cpp │ │ │ ├── ping.cpp │ │ │ ├── prepare_statement.cpp │ │ │ ├── quit_connection.cpp │ │ │ ├── read_buffer.cpp │ │ │ ├── read_resultset_head.cpp │ │ │ ├── read_some_rows.cpp │ │ │ ├── read_some_rows_dynamic.cpp │ │ │ ├── reset_connection.cpp │ │ │ ├── run_pipeline.cpp │ │ │ ├── set_character_set.cpp │ │ │ ├── start_execution.cpp │ │ │ └── top_level_algo.cpp │ │ ├── spotchecks/ │ │ │ ├── connection_use_after_move.cpp │ │ │ ├── default_completion_tokens.cpp │ │ │ ├── execution_requests.cpp │ │ │ ├── misc.cpp │ │ │ ├── multifn.cpp │ │ │ └── read_some_rows_static.cpp │ │ ├── statement.cpp │ │ ├── static_execution_state.cpp │ │ ├── static_results.cpp │ │ ├── throw_on_error.cpp │ │ └── with_diagnostics.cpp │ └── test_csha2p_encrypt_password_errors.cpp └── tools/ ├── ci/ │ ├── ci_util/ │ │ ├── __init__.py │ │ ├── b2.py │ │ ├── bench.py │ │ ├── cmake.py │ │ ├── common.py │ │ ├── db_setup.py │ │ ├── docs.py │ │ ├── fuzz.py │ │ ├── install_boost.py │ │ ├── main.py │ │ └── seed_corpus.py │ ├── main.py │ ├── run_benchmarks.py │ └── seed_corpus.py ├── docker/ │ └── build-msvc.dockerfile ├── error_codes.csv ├── osx-ci.cnf ├── scripts/ │ ├── build_unix_local.sh │ ├── build_windows_local.bat │ ├── check_links.py │ ├── collations.py │ ├── corpus_field_table.py │ ├── examples_qbk.py │ ├── file_headers.py │ └── server_errors.py ├── seed_corpus/ │ ├── field_table.csv │ ├── protocol_messages.csv │ └── sql_injection_payloads.txt ├── setup_db_osx.sh ├── ssl/ │ ├── ca-cert.pem │ ├── server-cert.pem │ └── server-key.pem ├── user-config-osx-gha.jam ├── valgrind_suppressions.txt └── win-ci.cnf ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- Language: Cpp ColumnLimit: 110 IndentWidth: 4 BreakBeforeBraces: Custom BraceWrapping: AfterCaseLabel: true AfterClass: true AfterControlStatement: true AfterEnum: true AfterFunction: true AfterNamespace: false AfterStruct: true AfterUnion: true AfterExternBlock: false BeforeCatch: true BeforeElse: true IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true AccessModifierOffset: -4 BinPackArguments: false BinPackParameters: false AlignAfterOpenBracket: BlockIndent PointerAlignment: Left IncludeBlocks: Regroup IncludeCategories: - Regex: '^|^' Priority: -8 SortPriority: 1 - Regex: '^' Priority: -7 SortPriority: 4 - Regex: "^<.*" Priority: -6 SortPriority: 5 - Regex: ".*" Priority: -5 SortPriority: 6 IndentCaseLabels: false AllowShortCaseLabelsOnASingleLine: true AllowAllArgumentsOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlignArrayOfStructures: Left DerivePointerAlignment: false PenaltyBreakAssignment: 2000000 PenaltyBreakBeforeFirstCallParameter: 0 PenaltyBreakOpenParenthesis: 0 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200000000 # Defaults (based on Google) AlignConsecutiveMacros: false AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: true AllowAllConstructorInitializersOnNextLine: true AllowShortBlocksOnASingleLine: Never AllowShortFunctionsOnASingleLine: All AllowShortLambdasOnASingleLine: All AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: Yes BreakBeforeBinaryOperators: None BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true CommentPragmas: '(^ IWYU pragma:)|(^\\copydoc )|(^ ?\\n)' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DeriveLineEnding: true DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeIsMainRegex: null IncludeIsMainSourceRegex: "" IndentGotoLabels: true IndentPPDirectives: None IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: "" MacroBlockEnd: "" MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Never ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true RawStringFormats: - Language: Cpp Delimiters: - cc - CC - cpp - Cpp - CPP - "c++" - "C++" CanonicalDelimiter: "" BasedOnStyle: google - Language: TextProto Delimiters: - pb - PB - proto - PROTO EnclosingFunctions: - EqualsProto - EquivToProto - PARSE_PARTIAL_TEXT_PROTO - PARSE_TEST_PROTO - PARSE_TEXT_PROTO - ParseTextOrDie - ParseTextProtoOrDie CanonicalDelimiter: "" BasedOnStyle: google ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false SpaceBeforeSquareBrackets: false Standard: Auto StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION TabWidth: 8 UseCRLF: false UseTab: Never --- ================================================ FILE: .clangd ================================================ Diagnostics: ClangTidy: Remove: bugprone-unused-return-value ================================================ FILE: .codecov.yml ================================================ # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # # Change how pull request comments look comment: layout: "reach,diff,files,footer" codecov: disable_default_path_fixes: true ================================================ FILE: .dockerignore ================================================ .cache/ .cproject .project .settings/ .pydevproject private/ build*/ out/ .vs/ .vscode/ compile_commands.json .cache/ doc/ include/ __build*/ ================================================ FILE: .drone.star ================================================ # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # _triggers = { "branch": [ "master", "develop" ] } _win_container_tag = 'e7bd656c3515263f9b3c69a2d73d045f6a0fed72' def _image(name): return 'ghcr.io/anarthal/cpp-ci-containers/{}'.format(name) def _win_image(name): return 'ghcr.io/anarthal-containers/{}:{}'.format(name, _win_container_tag) def _b2_command( source_dir, toolset, cxxstd, variant, server_host='127.0.0.1', stdlib='native', address_model='64', separate_compilation=1, use_ts_executor=0, address_sanitizer=0, undefined_sanitizer=0, valgrind=0, fail_if_no_openssl=1 ): return 'python tools/ci/main.py ' + \ '--source-dir="{}" '.format(source_dir) + \ 'b2 ' + \ '--server-host={} '.format(server_host) + \ '--toolset={} '.format(toolset) + \ '--cxxstd={} '.format(cxxstd) + \ '--variant={} '.format(variant) + \ '--stdlib={} '.format(stdlib) + \ '--address-model={} '.format(address_model) + \ '--separate-compilation={} '.format(separate_compilation) + \ '--use-ts-executor={} '.format(use_ts_executor) + \ '--address-sanitizer={} '.format(address_sanitizer) + \ '--undefined-sanitizer={} '.format(undefined_sanitizer) + \ '--valgrind={} '.format(valgrind) + \ '--fail-if-no-openssl={} '.format(fail_if_no_openssl) def _cmake_command( source_dir, server_host='127.0.0.1', generator='Ninja', cmake_build_type='Debug', build_shared_libs=0, cxxstd='20', install_test=1 ): return 'python tools/ci/main.py ' + \ '--source-dir="{}" '.format(source_dir) + \ 'cmake ' + \ '--server-host={} '.format(server_host) + \ '--generator="{}" '.format(generator) + \ '--cmake-build-type={} '.format(cmake_build_type) + \ '--build-shared-libs={} '.format(build_shared_libs) + \ '--cxxstd={} '.format(cxxstd) + \ '--install-test={} '.format(install_test) def _find_package_b2_command(source_dir, generator): return 'python tools/ci/main.py ' + \ '--source-dir="{}" '.format(source_dir) + \ 'find-package-b2 ' + \ '--generator="{}" '.format(generator) def _pipeline( name, image, os, command, db, arch='amd64', disable_aslr=False ): steps = [] if disable_aslr: steps.append({ "name": "Disable ASLR", "image": image, "pull": "if-not-exists", "privileged": True, "commands": ["echo 0 | tee /proc/sys/kernel/randomize_va_space"] }) steps.append({ "name": "Build and run", "image": image, "pull": "if-not-exists", "privileged": arch == "arm64", # TSAN tests fail otherwise (personality syscall) "volumes":[{ "name": "mysql-socket", "path": "/var/run/mysqld" }] if db != None else [], "commands": [command] }) return { "name": name, "kind": "pipeline", "type": "docker", "trigger": _triggers, "platform": { "os": os, "arch": arch }, "clone": { "retries": 5 }, "node": {}, "steps": steps, "services": [{ "name": "mysql", "image": "ghcr.io/anarthal/cpp-ci-containers/{}".format(db), "volumes": [{ "name": "mysql-socket", "path": "/var/run/mysqld" }] }] if db != None else [], "volumes": [{ "name": "mysql-socket", "temp": {} }] if db != None else [] } def linux_b2( name, image, toolset, cxxstd, variant='debug,release', stdlib='native', address_model='64', separate_compilation=1, use_ts_executor = 0, address_sanitizer=0, undefined_sanitizer=0, valgrind=0, arch='amd64', fail_if_no_openssl=1, db='mysql-8_4_1:1', ): command = _b2_command( source_dir='$(pwd)', toolset=toolset, cxxstd=cxxstd, variant=variant, stdlib=stdlib, address_model=address_model, server_host='mysql', separate_compilation=separate_compilation, use_ts_executor=use_ts_executor, address_sanitizer=address_sanitizer, undefined_sanitizer=undefined_sanitizer, valgrind=valgrind, fail_if_no_openssl=fail_if_no_openssl ) return _pipeline( name=name, image=image, os='linux', command=command, db=db, arch=arch, disable_aslr=True ) def windows_b2( name, image, toolset, cxxstd, variant, address_model = '64', use_ts_executor = 0 ): command = _b2_command( source_dir='$Env:DRONE_WORKSPACE', toolset=toolset, cxxstd=cxxstd, variant=variant, address_model=address_model, server_host='127.0.0.1', use_ts_executor=use_ts_executor ) return _pipeline(name=name, image=image, os='windows', command=command, db=None) def linux_cmake( name, image, db='mysql-8_4_1:1', build_shared_libs=0, cmake_build_type='Debug', cxxstd='20', install_test=1 ): command = _cmake_command( source_dir='$(pwd)', build_shared_libs=build_shared_libs, cmake_build_type=cmake_build_type, cxxstd=cxxstd, server_host='mysql', install_test=install_test ) return _pipeline(name=name, image=image, os='linux', command=command, db=db) def linux_cmake_noopenssl(name): command = 'python tools/ci/main.py ' + \ '--source-dir=$(pwd) ' + \ 'cmake-noopenssl ' + \ '--generator=Ninja ' return _pipeline(name=name, image=_image('build-noopenssl:1'), os='linux', command=command, db=None) def linux_cmake_nointeg(name): command = 'python tools/ci/main.py ' + \ '--source-dir=$(pwd) ' + \ 'cmake-nointeg ' + \ '--generator=Ninja ' return _pipeline(name=name, image=_image('build-gcc13:1'), os='linux', command=command, db=None) def windows_cmake( name, build_shared_libs=0 ): command = _cmake_command( source_dir='$Env:DRONE_WORKSPACE', build_shared_libs=build_shared_libs, generator='Visual Studio 17 2022', server_host='127.0.0.1' ) return _pipeline( name=name, image=_win_image('build-msvc14_3'), os='windows', command=command, db=None ) def find_package_b2_linux(name): command = _find_package_b2_command(source_dir='$(pwd)', generator='Ninja') return _pipeline(name=name, image=_image('build-gcc13:1'), os='linux', command=command, db=None) def find_package_b2_windows(name): command = _find_package_b2_command(source_dir='$Env:DRONE_WORKSPACE', generator='Visual Studio 17 2022') return _pipeline(name=name, image=_win_image('build-msvc14_3'), os='windows', command=command, db=None) def bench(name): command = 'python tools/ci/main.py ' + \ '--source-dir="$(pwd)" ' + \ 'bench ' + \ '--server-host=mysql ' + \ '--connection-pool-iters=1 ' + \ '--protocol-iters=1 ' return _pipeline(name=name, image=_image('build-bench:1'), os='linux', command=command, db='mysql-8_4_1:1') def docs(name): return _pipeline( name=name, image=_image('build-docs'), os='linux', command='python tools/ci/main.py --source-dir=$(pwd) docs', db=None ) def main(ctx): return [ # CMake Linux linux_cmake('Linux CMake MySQL 5.x', _image('build-gcc14:1'), db='mysql-5_7_41:1', build_shared_libs=0), linux_cmake('Linux CMake MariaDB', _image('build-gcc14:1'), db='mariadb-11_4_2:1', build_shared_libs=1), linux_cmake('Linux CMake cmake 3.8', _image('build-cmake3_8:3'), cxxstd='11', install_test=0), linux_cmake('Linux CMake gcc Release', _image('build-gcc14:1'), cmake_build_type='Release'), linux_cmake('Linux CMake gcc MinSizeRel', _image('build-gcc14:1'), cmake_build_type='MinSizeRel'), linux_cmake_noopenssl('Linux CMake no OpenSSL'), linux_cmake_nointeg('Linux CMake without integration tests'), # CMake Windows windows_cmake('Windows CMake static', build_shared_libs=0), windows_cmake('Windows CMake shared', build_shared_libs=1), # find_package with B2 distribution find_package_b2_linux('Linux find_package b2 distribution'), find_package_b2_windows('Windows find_package b2 distribution'), # B2 Linux. Please try to keep this below 3 configurations per build so CI doesn't take forever # Default Ubuntu compilers: # Ubuntu 16.04: gcc5, clang 3.8 # Ubuntu 18.04: gcc7, clang 7 # Ubuntu 20.04: gcc9, clang 10 # Ubuntu 22.04: gcc11, clang 14 # Ubuntu 24.04: gcc13, clang 18 linux_b2('Linux B2 clang-4', _image('build-clang4:1'), toolset='clang-4', cxxstd='14'), linux_b2('Linux B2 clang-5-honly-dbg', _image('build-clang5:1'), toolset='clang-5', cxxstd='14', separate_compilation=0), linux_b2('Linux B2 clang-6', _image('build-clang5:1'), toolset='clang-5', cxxstd='14'), linux_b2('Linux B2 clang-7', _image('build-clang7:2'), toolset='clang-7', cxxstd='14,17'), linux_b2('Linux B2 clang-8', _image('build-clang8:2'), toolset='clang-8', cxxstd='14', variant='debug', address_sanitizer=1, undefined_sanitizer=1), linux_b2('Linux B2 clang-9', _image('build-clang9:2'), toolset='clang-9', cxxstd='17', variant='release'), linux_b2('Linux B2 clang-10', _image('build-clang10:2'), toolset='clang-10', cxxstd='17,20', variant='debug'), linux_b2('Linux B2 clang-11', _image('build-clang11:2'), toolset='clang-11', cxxstd='20'), linux_b2('Linux B2 clang-12', _image('build-clang12:2'), toolset='clang-12', cxxstd='20', variant='debug', stdlib='libc++', address_sanitizer=1, undefined_sanitizer=1), linux_b2('Linux B2 clang-13', _image('build-clang13:1'), toolset='clang-13', cxxstd='20', db='mysql-9_4_0:1'), linux_b2('Linux B2 clang-14', _image('build-clang14:1'), toolset='clang-14', cxxstd='20', variant='debug'), linux_b2('Linux B2 clang-15', _image('build-clang15:1'), toolset='clang-15', cxxstd='20', variant='debug'), linux_b2('Linux B2 clang-16', _image('build-clang16:1'), toolset='clang-16', cxxstd='20', variant='debug', address_sanitizer=1, undefined_sanitizer=1), linux_b2('Linux B2 clang-17-honly-rls', _image('build-clang17:1'), toolset='clang-17', cxxstd='20', variant='release', separate_compilation=0), linux_b2('Linux B2 clang-18-honly-dbg', _image('build-clang18:1'), toolset='clang-18', cxxstd='20', variant='debug', separate_compilation=0), linux_b2('Linux B2 clang-19-libc++', _image('build-clang19:1'), toolset='clang-19', cxxstd='23', stdlib='libc++'), linux_b2('Linux B2 clang-20', _image('build-clang20:1'), toolset='clang-20', cxxstd='23'), linux_b2('Linux B2 clang-sanit', _image('build-clang20:1'), toolset='clang-20', cxxstd='20', variant='debug', address_sanitizer=1, undefined_sanitizer=1), linux_b2('Linux B2 clang-i386-sanit', _image('build-clang16-i386:1'), toolset='clang-16', cxxstd='20', variant='debug', address_model='32', address_sanitizer=1, undefined_sanitizer=1), linux_b2('Linux B2 gcc-5', _image('build-gcc5:1'), toolset='gcc-5', cxxstd='11'), # gcc-5 C++14 doesn't like my constexpr field_view linux_b2('Linux B2 gcc-5-ts-executor', _image('build-gcc5:1'), toolset='gcc-5', cxxstd='11', use_ts_executor=1), linux_b2('Linux B2 gcc-6-honly-dbg', _image('build-gcc6:1'), toolset='gcc-6', cxxstd='14', variant='debug', separate_compilation=0), linux_b2('Linux B2 gcc-7', _image('build-gcc7:1'), toolset='gcc-7', cxxstd='14,17', variant='debug'), linux_b2('Linux B2 gcc-8', _image('build-gcc8:1'), toolset='gcc-8', cxxstd='17'), linux_b2('Linux B2 gcc-9', _image('build-gcc9:1'), toolset='gcc-9', cxxstd='14,17', variant='debug'), linux_b2('Linux B2 gcc-10', _image('build-gcc10:1'), toolset='gcc-10', cxxstd='17'), linux_b2('Linux B2 gcc-11', _image('build-gcc11:1'), toolset='gcc-11', cxxstd='20'), linux_b2('Linux B2 gcc-12', _image('build-gcc12:1'), toolset='gcc-12', cxxstd='20,23', variant='debug'), linux_b2('Linux B2 gcc-13', _image('build-gcc13:1'), toolset='gcc-13', cxxstd='20', db='mysql-9_4_0:1'), linux_b2('Linux B2 gcc-14', _image('build-gcc14:1'), toolset='gcc-14', cxxstd='23'), linux_b2('Linux B2 gcc-15', _image('build-gcc15:1'), toolset='gcc-15', cxxstd='23'), linux_b2('Linux B2 gcc-sanit', _image('build-gcc14:1'), toolset='gcc-14', cxxstd='23', variant='debug', address_sanitizer=1, undefined_sanitizer=1), linux_b2('Linux B2 gcc-valgrind', _image('build-gcc14:1'), toolset='gcc-14', cxxstd='23', variant='debug', valgrind=1), linux_b2('Linux B2 gcc-11-arm64', _image('build-gcc11:1'), toolset='gcc-11', cxxstd='20', arch='arm64', variant='release'), linux_b2('Linux B2 gcc-11-arm64-sanit', _image('build-gcc11:1'), toolset='gcc-11', cxxstd='20', arch='arm64', variant='debug'), linux_b2('Linux B2 noopenssl', _image('build-noopenssl:1'), toolset='gcc', cxxstd='11', fail_if_no_openssl=0), # B2 Windows windows_b2('Windows B2 msvc14.1 32-bit', _win_image('build-msvc14_1'), toolset='msvc-14.1', cxxstd='11,14,17', variant='release', address_model='32'), windows_b2('Windows B2 msvc14.1 64-bit', _win_image('build-msvc14_1'), toolset='msvc-14.1', cxxstd='14,17', variant='release'), windows_b2('Windows B2 msvc14.2', _win_image('build-msvc14_2'), toolset='msvc-14.2', cxxstd='14,17', variant='release'), windows_b2('Windows B2 msvc14.3', _win_image('build-msvc14_3'), toolset='msvc-14.3', cxxstd='17,20', variant='debug,release'), windows_b2('Windows B2 msvc14.3-ts-executor', _win_image('build-msvc14_3'), toolset='msvc-14.3', cxxstd='20', variant='release', use_ts_executor=1), # Benchmarks bench('Benchmarks'), # Docs docs('Linux docs') ] ================================================ FILE: .github/workflows/build-code.yml ================================================ # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # name: Build on: push: branches: [develop, master] tags: ['*'] pull_request: workflow_dispatch: jobs: osx: runs-on: macos-latest steps: - uses: actions/checkout@v4 - run: | unlink /usr/local/bin/python || echo "/usr/local/bin/python not found" ln -s /usr/local/bin/python3 /usr/local/bin/python cp tools/user-config-osx-gha.jam ~/user-config.jam python -m pip install requests source tools/setup_db_osx.sh python tools/ci/main.py \ --source-dir=$(pwd) \ b2 \ --toolset=clang \ --cxxstd=20 \ --variant=debug,release ================================================ FILE: .github/workflows/coverage.yml ================================================ # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # name: coverage on: push: branches: [develop, master] tags: ["*"] pull_request: workflow_dispatch: jobs: coverage: runs-on: ubuntu-latest container: image: ghcr.io/anarthal/cpp-ci-containers/build-gcc14-lcov:1 volumes: - /var/run/mysqld:/var/run/mysqld services: mysql: image: ghcr.io/anarthal/cpp-ci-containers/mysql-8_4_1:1 ports: - 3306:3306 volumes: - /var/run/mysqld:/var/run/mysqld steps: - name: Fetch code uses: actions/checkout@v4 - name: Build code run: | python tools/ci/main.py \ --source-dir=$(pwd) \ b2 \ --server-host=mysql \ --toolset=gcc \ --cxxstd=20 \ --variant=debug \ --disable-local-sockets=off,on \ --coverage=1 - name: Generate coverage reports shell: bash run: | cd ~/boost-root/bin.v2 lcov \ --rc branch_coverage=0 \ --rc geninfo_unexecuted_blocks=1 \ --ignore-errors mismatch \ --gcov-tool gcov-14 \ --directory . \ --capture \ --output-file all.info lcov \ --rc branch_coverage=0 \ --output-file coverage.info \ --extract all.info '*/boost/mysql*' sed "s|^SF:$HOME/boost-root/|SF:include/|g" coverage.info > $GITHUB_WORKSPACE/coverage.info - name: Upload coverage reports uses: codecov/codecov-action@v4 with: verbose: true fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} plugins: noop # Don't run gcov again, codecov doesn't know about the filtering we perform file: coverage.info disable_search: true # Don't upload unwanted files disable_file_fixes: true # Default fixes make reports unusable ================================================ FILE: .github/workflows/docker-windows.yml ================================================ # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # name: Build Docker Windows images on: workflow_dispatch: push: paths: - tools/docker/build-msvc.dockerfile jobs: docker-windows: strategy: matrix: include: - { image: build-msvc14_1, base-image: "cppalliance/dronevs2017:1" } - { image: build-msvc14_2, base-image: "cppalliance/dronevs2019:1" } - { image: build-msvc14_3, base-image: "cppalliance/dronevs2022:1" } permissions: contents: read packages: write runs-on: windows-2019 defaults: run: shell: bash steps: - name: Checkout repository uses: actions/checkout@v4 - name: Log in to the Container registry uses: docker/login-action@v3 with: registry: ghcr.io username: anarthal-containers password: ${{ secrets.ANARTHAL_CONTAINERS_TOKEN }} - name: Build and push Docker image run: | FULL_IMAGE=ghcr.io/anarthal-containers/${{ matrix.image }} docker build -f tools/docker/build-msvc.dockerfile --build-arg BASE_IMAGE=${{ matrix.base-image }} -t $FULL_IMAGE:$GITHUB_SHA -t $FULL_IMAGE:latest . docker push $FULL_IMAGE --all-tags ================================================ FILE: .github/workflows/fuzz.yml ================================================ # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # name: fuzz on: push: branches: [develop, master] tags: ['*'] pull_request: workflow_dispatch: schedule: - cron: "25 00 * * *" jobs: fuzz: runs-on: ubuntu-latest container: image: ghcr.io/anarthal/cpp-ci-containers/build-clang18:1 volumes: - /var/run/mysqld:/var/run/mysqld services: mysql: image: ghcr.io/anarthal/cpp-ci-containers/mysql-8_4_1:1 ports: - 3306:3306 volumes: - /var/run/mysqld:/var/run/mysqld steps: - name: Fetch code uses: actions/checkout@v4 - name: Restore corpus uses: actions/cache@v4 with: path: /tmp/corpus.tar.gz key: corpus-${{ github.run_id }} restore-keys: corpus- # Note: this will take care of using the corpus and updating it - name: Build and run the fuzzer run: | python tools/ci/main.py \ --source-dir=$(pwd) \ fuzz \ --server-host=mysql - name: Archive any crashes as an artifact uses: actions/upload-artifact@v4 if: always() with: name: crashes path: | ~/boost-root/crash-* ~/boost-root/leak-* ~/boost-root/timeout-* if-no-files-found: ignore ================================================ FILE: .gitignore ================================================ /__build/ .cproject .project .settings/ .pydevproject /private/ CMakeSettings.json out/ .vs/ doc/html/ doc/qbk/reference.qbk .vscode/ compile_commands.json .cache/ __build*__/ __pycache__/ ================================================ FILE: CMakeLists.txt ================================================ # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # cmake_minimum_required(VERSION 3.8...3.22) # Project project(boost_mysql VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX) # Library add_library(boost_mysql INTERFACE) add_library(Boost::mysql ALIAS boost_mysql) # Dependencies. If non-Boost dependencies are not found, we just bail out find_package(Threads) if(NOT Threads_FOUND) message(STATUS "Boost.MySQL has been disabled, because the required package Threads hasn't been found") return() endif() find_package(OpenSSL) if(NOT OpenSSL_FOUND) message(STATUS "Boost.MySQL has been disabled, because the required package OpenSSL hasn't been found") return() endif() # This is generated by boostdep. # Note that Boost::pfr is not listed because it's a peer dependency target_link_libraries( boost_mysql INTERFACE Boost::asio Boost::assert Boost::charconv Boost::compat Boost::config Boost::container Boost::core Boost::describe Boost::endian Boost::intrusive Boost::mp11 Boost::optional Boost::system Boost::throw_exception Boost::variant2 Threads::Threads OpenSSL::Crypto OpenSSL::SSL ) # Includes & features target_include_directories(boost_mysql INTERFACE include) target_compile_features(boost_mysql INTERFACE cxx_std_11) # Don't run integration testing unless explicitly requested, since these require a running MySQL server option(BOOST_MYSQL_INTEGRATION_TESTS OFF "Whether to build and run integration tests or not") mark_as_advanced(BOOST_MYSQL_INTEGRATION_TESTS) # List of server features that the CI DB server does not support. # Disables running some integration tests and examples set(BOOST_MYSQL_DISABLED_SERVER_FEATURES "" CACHE STRING "A CMake list of server features not supported by the CI server, for integration tests" ) mark_as_advanced(BOOST_MYSQL_DISABLED_SERVER_FEATURES) # Don't build benchmarks unless explicitly requested, since these require the official # MySQL and MariaDB client libraries option(BOOST_MYSQL_BENCH OFF "Whether to build the benchmarks") mark_as_advanced(BOOST_MYSQL_BENCH) # Examples and tests if(BUILD_TESTING) # Contains some functions to share code between examples and tests include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/utils.cmake) # Custom target tests; required by the Boost superproject if(NOT TARGET tests) add_custom_target(tests) endif() # Tests add_subdirectory(test) # All examples require a real server to run if (BOOST_MYSQL_INTEGRATION_TESTS) add_subdirectory(example) endif() endif() if (BOOST_MYSQL_BENCH) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/utils.cmake) add_subdirectory(bench) endif() ================================================ FILE: LICENSE_1_0.txt ================================================ Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Boost.MySQL Branch | Windows/Linux Build | OSX build | Coverage | Documentation -------|---------------------|-----------|--------- | ------------- [`master`](https://github.com/boostorg/mysql/tree/master) | [![Build Status](https://drone.cpp.al/api/badges/boostorg/mysql/status.svg)](https://drone.cpp.al/boostorg/mysql) | [![Build Status](https://github.com/boostorg/mysql/actions/workflows/build-code.yml/badge.svg)](https://github.com/boostorg/mysql) | [![codecov](https://codecov.io/gh/boostorg/mysql/branch/master/graph/badge.svg)](https://codecov.io/gh/boostorg/mysql/branch/master) | [Docs for master](https://www.boost.org/doc/libs/master/libs/mysql/doc/html/index.html) [`develop`](https://github.com/boostorg/mysql/tree/develop) | [![Build Status](https://drone.cpp.al/api/badges/boostorg/mysql/status.svg?ref=refs/heads/develop)](https://drone.cpp.al/boostorg/mysql) | [![Build Status](https://github.com/boostorg/mysql/actions/workflows/build-code.yml/badge.svg?branch=develop)](https://github.com/boostorg/mysql) | [![codecov](https://codecov.io/gh/boostorg/mysql/branch/develop/graph/badge.svg)](https://codecov.io/gh/boostorg/mysql/branch/develop) | [Docs for develop](https://www.boost.org/doc/libs/develop/libs/mysql/doc/html/index.html) Boost.MySQL is a C++11 client for MySQL and MariaDB database servers, based on Boost.Asio. Boost.MySQL is part of Boost. ## Breaking changes in Boost 1.85 Boost.MySQL now requires linking with Boost.Charconv, which is a compiled library. If you're getting link errors, link your executable to the `Boost::charconv` CMake target. No C++ code changes are required. ## Feedback Do you have any suggestion? Would you like to share a bad or good experience while using the library? Please comment [on this issue](https://github.com/boostorg/mysql/issues/140). ## Why another MySQL C++ client? - It is fully compatible with Boost.Asio and integrates well with any other library in the Boost.Asio ecosystem (like Boost.Beast). - It supports Boost.Asio's universal asynchronous model, which means you can go asynchronous using callbacks, futures or coroutines (including C++20 coroutines). - It is written in C++11 and takes advantage of it. - It is header only. ## Using the library To use this library, you need: - Boost 1.82 or higher (Boost.MySQL doesn't work with standalone Asio). - A C++11 capable compiler. - OpenSSL. The library is header-only, but it depends on other Boost header-only libraries and on OpenSSL. To use the library, install Boost the way you would normally do (e.g. via `b2 install`), and create a `CMakeLists.txt` like this (replace `main` by your executable name and `main.cpp` by your list of source files): ```cmake project(boost_mysql_example LANGUAGES CXX) find_package(Boost REQUIRED COMPONENTS charconv) find_package(Threads REQUIRED) find_package(OpenSSL REQUIRED) add_executable(main main.cpp) target_link_libraries(main PRIVATE Boost::charconv Threads::Threads OpenSSL::Crypto OpenSSL::SSL) ``` ## Tested with Boost.MySQL has been tested with the following compilers: - gcc 5 to 15. - clang 4 to 20. - msvc 14.1, 14.2 and 14.3. And with the following databases: - MySQL v5.7.41. - MySQL v8.4.1. - MariaDB v11.4.2. ## Features - Text queries (execution of text SQL queries and data retrieval). MySQL refers to this as the "text protocol", as all information is passed using text (as opposed to prepared statements, see below). - Prepared statements. MySQL refers to this as the "binary protocol", as the result of executing a prepared statement is sent in binary format rather than in text. - Stored procedures. - Authentication methods (authentication plugins): mysql_native_password and caching_sha2_password. These are the default methods in MySQL 5/MariaDB and MySQL 8, respectively. - Encrypted connections (TLS). - TCP and UNIX socket transports. - Connection pools. - Friendly client-side generated SQL. - (Experimental) pipelines. ================================================ FILE: bench/CMakeLists.txt ================================================ # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # # Utility target add_custom_target(boost_mysql_bench) # Find libraries required by the official clients find_package(OpenSSL REQUIRED) # Find libmysqlclient. This script has only been tested on Ubuntu Linux, where benchmarks are performed. find_path(LIBMYSQLCLIENT_INCLUDE_DIR mysql/mysql.h) if (NOT LIBMYSQLCLIENT_INCLUDE_DIR) message(FATAL_ERROR "Could not find libmysqlclient includes") endif() find_library(LIBMYSQLCLIENT_LIBRARY mysqlclient) if (NOT LIBMYSQLCLIENT_LIBRARY) message(FATAL_ERROR "Could not find the libmysqlclient library binary") endif() add_library(boost_mysql_libmysqlclient SHARED IMPORTED) set_property(TARGET boost_mysql_libmysqlclient PROPERTY IMPORTED_LOCATION ${LIBMYSQLCLIENT_LIBRARY}) target_include_directories(boost_mysql_libmysqlclient INTERFACE ${LIBMYSQLCLIENT_INCLUDE_DIR}) target_link_libraries(boost_mysql_libmysqlclient INTERFACE OpenSSL::SSL OpenSSL::Crypto) # Find libmariadb find_path(LIBMARIADB_INCLUDE_DIR mariadb/mysql.h) if (NOT LIBMARIADB_INCLUDE_DIR) message(FATAL_ERROR "Could not find libmariadb includes") endif() find_library(LIBMARIADB_LIBRARY mariadb) if (NOT LIBMARIADB_LIBRARY) message(FATAL_ERROR "Could not find the libmariadb library binary") endif() add_library(boost_mysql_libmariadb SHARED IMPORTED) set_property(TARGET boost_mysql_libmariadb PROPERTY IMPORTED_LOCATION ${LIBMARIADB_LIBRARY}) target_include_directories(boost_mysql_libmariadb INTERFACE ${LIBMARIADB_INCLUDE_DIR}) target_link_libraries(boost_mysql_libmariadb INTERFACE OpenSSL::SSL OpenSSL::Crypto) # Adds a benchmark target - pass the libraries to link to as extra args function (add_bench cpp_name) set(target_name "boost_mysql_bench_${cpp_name}") add_executable(${target_name} ${cpp_name}.cpp) target_link_libraries(${target_name} PUBLIC ${ARGN}) boost_mysql_common_target_settings(${target_name}) add_dependencies(boost_mysql_bench ${target_name}) endfunction() # We use the header-only version here because it's slightly faster # and to avoid dependencies to the test CMake scripts # Connection pool add_bench(connection_pool boost_mysql) # Protocol benchmarks add_bench(one_small_row_boost boost_mysql) add_bench(one_small_row_libmysqlclient boost_mysql_libmysqlclient) add_bench(one_small_row_libmariadb boost_mysql_libmariadb) add_bench(one_big_row_boost boost_mysql) add_bench(one_big_row_libmysqlclient boost_mysql_libmysqlclient) add_bench(one_big_row_libmariadb boost_mysql_libmariadb) add_bench(many_rows_boost boost_mysql) add_bench(many_rows_libmysqlclient boost_mysql_libmysqlclient) add_bench(many_rows_libmariadb boost_mysql_libmariadb) add_bench(stmt_params_boost boost_mysql) add_bench(stmt_params_libmysqlclient boost_mysql_libmysqlclient) add_bench(stmt_params_libmariadb boost_mysql_libmariadb) ================================================ FILE: bench/bench.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Benchmark data processing\n", "\n", "Contains the code required to plot the figures in the docs.\n", "\n", "* Build the benchmarks with CMake, using the `BOOST_MYSQL_BENCH` CMake option and the `boost_mysql_bench` CMake target.\n", "* Run the benchmarks with `tools/ci/run_benchmarks.py`, which leaves a .txt file with the results in the current directory.\n", "* Run this notebook to generate the graphs.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "from matplotlib import pyplot as plt\n", "\n", "df = pd.read_csv('../private/benchmark-results.txt', header=None) # Update file path as required\n", "df = df.set_index(df.index.map(lambda x: x % df.groupby(0).size()[0]))\n", "df = df.pivot(columns=[0], values=[1])\n", "df.columns = df.columns.get_level_values(0)\n", "df" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def plot_result(df: pd.DataFrame, bench: str, title: str, ax):\n", " cols = [f'{bench}_boost', f'{bench}_libmysqlclient', f'{bench}_libmariadb']\n", " df = df[cols].rename(columns={\n", " f'{bench}_boost': 'Boost.MySQL',\n", " f'{bench}_libmysqlclient': 'libmysqlclient',\n", " f'{bench}_libmariadb': 'libmariadb'\n", " })\n", " mean_val = round(df.mean().mean())\n", " df.plot.box(ylim=(mean_val-150, mean_val+150), ax=ax, title=title, ylabel='Time (ms)')\n", "\n", "\n", "fig, _ = plt.subplots(2, 2, figsize=(15, 15))\n", "\n", "plot_result(df, bench='one_small_row', title='Reading one small row', ax=fig.axes[0])\n", "plot_result(df, bench='one_big_row', title='Reading one big row', ax=fig.axes[1])\n", "plot_result(df, bench='many_rows', title='Reading 5k rows', ax=fig.axes[2])\n", "plot_result(df, bench='stmt_params', title='Statement with params', ax=fig.axes[3])\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "base", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.3" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: bench/connection_pool.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using boost::mysql::error_code; using std::chrono::steady_clock; namespace mysql = boost::mysql; namespace asio = boost::asio; namespace { static constexpr std::size_t num_parallel = 100; static constexpr std::size_t total = num_parallel * 100; static constexpr const char* default_unix_path = "/var/run/mysqld/mysqld.sock"; class coordinator { bool finished_{}; std::size_t remaining_queries_{total}; std::size_t outstanding_tasks_{num_parallel}; steady_clock::time_point tp_start_; steady_clock::time_point tp_finish_; mysql::connection_pool* pool_{}; public: coordinator(mysql::connection_pool* pool = nullptr) : pool_(pool) {} std::chrono::milliseconds ellapsed() const { return std::chrono::duration_cast(tp_finish_ - tp_start_); } void record_start() { tp_start_ = steady_clock::now(); } void on_finish() { if (--outstanding_tasks_ == 0) { tp_finish_ = steady_clock::now(); if (pool_) pool_->cancel(); } } bool on_loop_finish() { if (--remaining_queries_ == 0) finished_ = true; return !finished_; } bool check_ec(error_code ec, const mysql::diagnostics& diag) { if (ec) { finished_ = true; std::cout << ec << ", " << diag.server_message() << std::endl; } return !finished_; } }; class task_nopool { mysql::any_connection conn_; mysql::results r_; mysql::diagnostics diag_; coordinator* coord_{}; const mysql::connect_params* params_; mysql::statement stmt_; asio::coroutine coro_; public: task_nopool(asio::any_io_executor ex, coordinator& coord, const mysql::connect_params& params) : conn_(ex), coord_(&coord), params_(¶ms) { } void resume(error_code ec = {}) { // Error checking if (!coord_->check_ec(ec, diag_)) { coord_->on_finish(); return; } BOOST_ASIO_CORO_REENTER(coro_) { while (true) { BOOST_ASIO_CORO_YIELD conn_.async_connect(*params_, diag_, [this](error_code ec) { resume(ec); }); BOOST_ASIO_CORO_YIELD conn_.async_prepare_statement( "SELECT data FROM lightweight_data WHERE id = ?", diag_, [this](error_code ec, boost::mysql::statement s) { stmt_ = s; resume(ec); } ); BOOST_ASIO_CORO_YIELD conn_.async_execute(stmt_.bind(2), r_, diag_, [this](error_code ec) { resume(ec); }); BOOST_ASIO_CORO_YIELD conn_.async_close(diag_, [this](error_code ec) { resume(ec); }); if (!coord_->on_loop_finish()) { coord_->on_finish(); return; } } } } }; class task_pool { mysql::connection_pool* pool_; mysql::results r_; mysql::diagnostics diag_; coordinator* coord_{}; mysql::pooled_connection conn_; asio::coroutine coro_; mysql::statement stmt_; void on_finish() { conn_ = mysql::pooled_connection(); coord_->on_finish(); } public: task_pool(mysql::connection_pool& pool, coordinator& coord) : pool_(&pool), coord_(&coord) {} void resume(error_code ec = {}) { // Error checking if (!coord_->check_ec(ec, diag_)) { on_finish(); return; } BOOST_ASIO_CORO_REENTER(coro_) { while (true) { BOOST_ASIO_CORO_YIELD pool_->async_get_connection(diag_, [this](error_code ec, mysql::pooled_connection c) { conn_ = std::move(c); resume(ec); }); BOOST_ASIO_CORO_YIELD conn_->async_prepare_statement( "SELECT data FROM lightweight_data WHERE id = ?", diag_, [this](error_code ec, boost::mysql::statement s) { stmt_ = s; resume(ec); } ); BOOST_ASIO_CORO_YIELD conn_->async_execute(stmt_.bind(2), r_, diag_, [this](error_code ec) { resume(ec); }); conn_ = boost::mysql::pooled_connection(); if (!coord_->on_loop_finish()) { on_finish(); return; } } } } }; void run_nopool(mysql::any_address server_addr, bool use_ssl) { // Setup asio::io_context ctx; mysql::connect_params params; params.server_address = std::move(server_addr); params.username = "root"; params.password = ""; params.database = "boost_mysql_bench"; params.ssl = use_ssl ? mysql::ssl_mode::require : mysql::ssl_mode::disable; std::vector conns; coordinator coord; // Create connections for (std::size_t i = 0; i < num_parallel; ++i) conns.emplace_back(ctx.get_executor(), coord, params); // Launch coord.record_start(); for (auto& conn : conns) conn.resume(error_code()); // Run ctx.run(); // Print elapsed time std::cout << coord.ellapsed().count() << std::flush; } void run_pool(mysql::any_address server_addr, bool use_ssl) { // Setup asio::io_context ctx; mysql::pool_params params; params.server_address = std::move(server_addr); params.username = "root"; params.password = ""; params.database = "boost_mysql_bench"; params.max_size = num_parallel; params.ssl = use_ssl ? mysql::ssl_mode::require : mysql::ssl_mode::disable; mysql::connection_pool pool(ctx, std::move(params)); pool.async_run(asio::detached); std::vector conns; coordinator coord(&pool); // Create connections for (std::size_t i = 0; i < num_parallel; ++i) conns.emplace_back(pool, coord); // Launch coord.record_start(); for (auto& conn : conns) conn.resume(error_code()); // Run ctx.run(); // Print elapsed time std::cout << coord.ellapsed().count() << std::flush; } static constexpr const char* options[] = { "nopool-tcp", "nopool-tcpssl", "nopool-unix", "pool-tcp", "pool-tcpssl", "pool-unix", }; void usage(const char* progname) { std::cerr << "Usage: " << progname << " \nAvailable options:\n"; for (const char* opt : options) std::cerr << " " << opt << "\n"; exit(1); } } // namespace int main(int argc, char** argv) { if (argc != 3) { usage(argv[0]); } mysql::string_view opt = argv[1]; mysql::string_view addr = argv[2]; boost::mysql::host_and_port tcp_addr; if (opt == "nopool-tcp") { tcp_addr.host = addr; run_nopool(std::move(tcp_addr), false); } else if (opt == "nopool-tcpssl") { tcp_addr.host = addr; run_nopool(std::move(tcp_addr), true); } else if (opt == "nopool-unix") { run_nopool(mysql::unix_path{default_unix_path}, false); } else if (opt == "pool-tcp") { tcp_addr.host = addr; run_pool(std::move(tcp_addr), false); } else if (opt == "pool-tcpssl") { tcp_addr.host = addr; run_pool(std::move(tcp_addr), true); } else if (opt == "pool-unix") { run_pool(mysql::unix_path{default_unix_path}, false); } else usage(argv[0]); } ================================================ FILE: bench/db_setup.sql ================================================ -- -- Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) -- -- Distributed under the Boost Software License, Version 1.0. (See accompanying -- file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -- -- Database DROP DATABASE IF EXISTS boost_mysql_bench; CREATE DATABASE boost_mysql_bench; USE boost_mysql_bench; -- Required for the WITH RECURSIVE and the amount of rows we're generating SET SESSION cte_max_recursion_depth = 15000; -- Having this prevents sporadic CI failures SET global max_connections = 5000; -- An arbitrary value to pass to RAND(@seed). RAND(@i) generates a -- deterministic sequence, vs RAND(@seed) SET @seed = 3; -- Table, with a column per major type CREATE TABLE test_data( id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, s8 TINYINT NOT NULL, u8 TINYINT UNSIGNED NOT NULL, s16 SMALLINT NOT NULL, u16 SMALLINT UNSIGNED NOT NULL, s32 INT NOT NULL, u32 INT UNSIGNED NOT NULL, s64 BIGINT NOT NULL, u64 BIGINT UNSIGNED NOT NULL, s1 VARCHAR(256), s2 TEXT, b1 VARBINARY(256), b2 BLOB, flt FLOAT, dbl DOUBLE, dt DATE, dtime DATETIME, t TIME ); -- Generate 10000 random rows INSERT INTO test_data( s8, u8, s16, u16, s32, u32, s64, u64, s1, s2, b1, b2, flt, dbl, dt, dtime, t ) WITH RECURSIVE cte AS ( SELECT 0 num UNION ALL SELECT num + 1 FROM cte WHERE num < 5000 ) SELECT FLOOR(RAND(@seed)*(0x7f+0x80+1)-0x80), FLOOR(RAND(@seed)*(0xff+1)), FLOOR(RAND(@seed)*(0x7fff+0x8000+1)-0x8000), FLOOR(RAND(@seed)*(0xffff+1)), FLOOR(RAND(@seed)*(0x7fffffff+0x80000000+1)-0x80000000), FLOOR(RAND(@seed)*(0xffffffff+1)), FLOOR(RAND(@seed)*(0x7fffffffffffffff+0x8000000000000000)-0x7fffffffffffffff), FLOOR(RAND(@seed)*(0xffffffffffffffff)), REPEAT('a', 180), REPEAT('b', FLOOR(RAND(@seed)*(54000-36000+1)+36000)), REPEAT('c', 180), REPEAT('d', FLOOR(RAND(@seed)*(54000-36000+1)+36000)), RAND(@seed), RAND(@seed), DATE_ADD('2020-01-01', INTERVAL FLOOR(RAND(@seed)*(5000+5000+1)-5000) DAY), DATE_ADD('2010-03-20', INTERVAL FLOOR(RAND(@seed)*(3600*24*365*20+3600*24*365*20+1)-3600*24*365*20) SECOND), SEC_TO_TIME(RAND(@seed) + FLOOR(RAND(@seed)*(839*3600+839*3600+1)-839*3600)) FROM cte; -- A lightweight table, for the connection_pool benchmarks CREATE TABLE lightweight_data( id INT NOT NULL PRIMARY KEY, data VARCHAR(100) NOT NULL ); INSERT INTO lightweight_data VALUES (1, "Data piece 1"), (2, "Another data piece"), (3, "Final data piece") ; ================================================ FILE: bench/many_rows_boost.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include #include #include #include #include namespace asio = boost::asio; namespace mysql = boost::mysql; int main() { // Setup asio::io_context ctx; mysql::any_connection conn(ctx); mysql::execution_state st; // Connect mysql::connect_params params; params.server_address.emplace_unix_path("/var/run/mysqld/mysqld.sock"); params.username = "root"; params.password = ""; params.database = "boost_mysql_bench"; params.ssl = mysql::ssl_mode::disable; conn.connect(params); // Prepare the statement auto stmt = conn.prepare_statement("SELECT * FROM test_data"); // Ensure that nothing gets optimized away unsigned num_rows = 0; // Benchmark starts here auto tbegin = std::chrono::steady_clock::now(); // start_execution won't copy the strings in the rows (as opposed to execute), // so it's preferable when we have big rows, like here conn.start_execution(stmt.bind(), st); while (!st.complete()) num_rows += conn.read_some_rows(st).size(); // Benchmark ends here auto tend = std::chrono::steady_clock::now(); std::cout << std::chrono::duration_cast(tend - tbegin).count() << std::endl; // We expect many rows return num_rows == 0 ? EXIT_FAILURE : EXIT_SUCCESS; } ================================================ FILE: bench/many_rows_libmariadb.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include #include int main() { // Initialize if (mysql_library_init(0, NULL, NULL)) { fprintf(stderr, "could not initialize MySQL client library\n"); exit(1); } MYSQL* con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "Error initializing connection: %s\n", mysql_error(con)); exit(1); } // Connect if (mysql_real_connect(con, NULL, "root", "", "boost_mysql_bench", 0, "/var/run/mysqld/mysqld.sock", 0) == NULL) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } // Prepare the statement MYSQL_STMT* stmt; stmt = mysql_stmt_init(con); if (!stmt) { printf("Could not initialize statement\n"); exit(1); } constexpr const char* stmt_str = "SELECT * FROM test_data"; if (mysql_stmt_prepare(stmt, stmt_str, strlen(stmt_str))) { fprintf(stderr, "Error preparing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Prepare the bind objects long long int out_id = 0; MYSQL_BIND binds[18]{}; binds[0].buffer_type = MYSQL_TYPE_LONGLONG; binds[0].buffer = &out_id; binds[0].buffer_length = 8; signed char s8{}; binds[1].buffer_type = MYSQL_TYPE_TINY; binds[1].buffer = &s8; binds[1].buffer_length = 1; binds[1].is_unsigned = 0; unsigned char u8{}; binds[2].buffer_type = MYSQL_TYPE_TINY; binds[2].buffer = &u8; binds[2].buffer_length = 1; binds[2].is_unsigned = 1; short s16{}; binds[3].buffer_type = MYSQL_TYPE_SHORT; binds[3].buffer = &s16; binds[3].buffer_length = 2; binds[3].is_unsigned = 0; unsigned short u16{}; binds[4].buffer_type = MYSQL_TYPE_SHORT; binds[4].buffer = &u16; binds[4].buffer_length = 2; binds[4].is_unsigned = 1; int s32{}; binds[5].buffer_type = MYSQL_TYPE_LONG; binds[5].buffer = &s32; binds[5].buffer_length = 4; binds[5].is_unsigned = 0; unsigned u32{}; binds[6].buffer_type = MYSQL_TYPE_LONG; binds[6].buffer = &u32; binds[6].buffer_length = 4; binds[6].is_unsigned = 1; long long s64{}; binds[7].buffer_type = MYSQL_TYPE_LONGLONG; binds[7].buffer = &s64; binds[7].buffer_length = 8; binds[7].is_unsigned = 0; unsigned long long u64{}; binds[8].buffer_type = MYSQL_TYPE_LONGLONG; binds[8].buffer = &u64; binds[8].buffer_length = 8; binds[8].is_unsigned = 1; char s1[255]{}; binds[9].buffer_type = MYSQL_TYPE_STRING; binds[9].buffer = s1; binds[9].buffer_length = sizeof(s1); std::string s2; unsigned long s2_length = 0u; my_bool s2_truncated{}; binds[10].buffer_type = MYSQL_TYPE_STRING; binds[10].buffer = s2.data(); binds[10].buffer_length = s2_length; binds[10].length = &s2_length; binds[10].error = &s2_truncated; char b1[255]{}; binds[11].buffer_type = MYSQL_TYPE_BLOB; binds[11].buffer = b1; binds[11].buffer_length = sizeof(b1); std::string b2; unsigned long b2_length = 0u; my_bool b2_truncated{}; binds[12].buffer_type = MYSQL_TYPE_BLOB; binds[12].buffer = b2.data(); binds[12].buffer_length = b2_length; binds[12].length = &b2_length; binds[12].error = &b2_truncated; float flt{}; binds[13].buffer_type = MYSQL_TYPE_FLOAT; binds[13].buffer = &flt; binds[13].buffer_length = 4; double dbl{}; binds[14].buffer_type = MYSQL_TYPE_DOUBLE; binds[14].buffer = &dbl; binds[14].buffer_length = 8; MYSQL_TIME dt{}; binds[15].buffer_type = MYSQL_TYPE_DATE; binds[15].buffer = &dt; binds[15].buffer_length = sizeof(dt); MYSQL_TIME dtime{}; binds[16].buffer_type = MYSQL_TYPE_DATETIME; binds[16].buffer = &dtime; binds[16].buffer_length = sizeof(dtime); MYSQL_TIME t{}; binds[17].buffer_type = MYSQL_TYPE_TIME; binds[17].buffer = &t; binds[17].buffer_length = sizeof(t); // Ensure that nothing gets optimized away unsigned num_rows = 0; // Benchmark starts here auto tbegin = std::chrono::steady_clock::now(); // Execute the statement if (mysql_stmt_execute(stmt)) { fprintf(stderr, "Error executing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Bind output if (mysql_stmt_bind_result(stmt, binds)) { fprintf(stderr, "Error binding result: %s\n", mysql_stmt_error(stmt)); exit(1); } // Read the rows while (true) { auto status = mysql_stmt_fetch(stmt); if (status == MYSQL_DATA_TRUNCATED) { // On truncation, resize the buffer and read again if (s2_length > s2.size()) { s2.resize(s2_length); binds[10].buffer = s2.data(); binds[10].buffer_length = s2_length; if (mysql_stmt_fetch_column(stmt, &binds[10], 10, 0)) { fprintf(stderr, "Error fetching s2: %s\n", mysql_stmt_error(stmt)); exit(1); } } if (b2_length > b2.size()) { b2.resize(b2_length); binds[12].buffer = b2.data(); binds[12].buffer_length = b2_length; if (mysql_stmt_fetch_column(stmt, &binds[12], 12, 0)) { fprintf(stderr, "Error fetching b2: %s\n", mysql_stmt_error(stmt)); exit(1); } } ++num_rows; } else if (status == MYSQL_NO_DATA) { break; } else if (status == 1) { fprintf(stderr, "Error fetching result: %s\n", mysql_stmt_error(stmt)); exit(1); } else { ++num_rows; } } // Benchmark ends here auto tend = std::chrono::steady_clock::now(); std::cout << std::chrono::duration_cast(tend - tbegin).count() << std::endl; // Cleanup mysql_stmt_close(stmt); mysql_close(con); // We expect many rows return num_rows == 0 ? EXIT_FAILURE : EXIT_SUCCESS; } ================================================ FILE: bench/many_rows_libmysqlclient.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include #include #include #include int main() { // Initialize if (mysql_library_init(0, NULL, NULL)) { fprintf(stderr, "could not initialize MySQL client library\n"); exit(1); } MYSQL* con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "Error initializing connection: %s\n", mysql_error(con)); exit(1); } // Connect unsigned mode = SSL_MODE_DISABLED; if (mysql_options(con, MYSQL_OPT_SSL_MODE, &mode)) { fprintf(stderr, "Error in mysql_options: %s\n", mysql_error(con)); exit(1); } if (mysql_real_connect(con, NULL, "root", "", "boost_mysql_bench", 0, "/var/run/mysqld/mysqld.sock", 0) == NULL) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } // Prepare the statement MYSQL_STMT* stmt; stmt = mysql_stmt_init(con); if (!stmt) { printf("Could not initialize statement\n"); exit(1); } constexpr const char* stmt_str = "SELECT * FROM test_data"; if (mysql_stmt_prepare(stmt, stmt_str, strlen(stmt_str))) { fprintf(stderr, "Error preparing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Prepare the bind objects long long int out_id = 0; MYSQL_BIND binds[18]{}; binds[0].buffer_type = MYSQL_TYPE_LONGLONG; binds[0].buffer = &out_id; binds[0].buffer_length = 8; signed char s8{}; binds[1].buffer_type = MYSQL_TYPE_TINY; binds[1].buffer = &s8; binds[1].buffer_length = 1; binds[1].is_unsigned = 0; unsigned char u8{}; binds[2].buffer_type = MYSQL_TYPE_TINY; binds[2].buffer = &u8; binds[2].buffer_length = 1; binds[2].is_unsigned = 1; short s16{}; binds[3].buffer_type = MYSQL_TYPE_SHORT; binds[3].buffer = &s16; binds[3].buffer_length = 2; binds[3].is_unsigned = 0; unsigned short u16{}; binds[4].buffer_type = MYSQL_TYPE_SHORT; binds[4].buffer = &u16; binds[4].buffer_length = 2; binds[4].is_unsigned = 1; int s32{}; binds[5].buffer_type = MYSQL_TYPE_LONG; binds[5].buffer = &s32; binds[5].buffer_length = 4; binds[5].is_unsigned = 0; unsigned u32{}; binds[6].buffer_type = MYSQL_TYPE_LONG; binds[6].buffer = &u32; binds[6].buffer_length = 4; binds[6].is_unsigned = 1; long long s64{}; binds[7].buffer_type = MYSQL_TYPE_LONGLONG; binds[7].buffer = &s64; binds[7].buffer_length = 8; binds[7].is_unsigned = 0; unsigned long long u64{}; binds[8].buffer_type = MYSQL_TYPE_LONGLONG; binds[8].buffer = &u64; binds[8].buffer_length = 8; binds[8].is_unsigned = 1; char s1[255]{}; binds[9].buffer_type = MYSQL_TYPE_STRING; binds[9].buffer = s1; binds[9].buffer_length = sizeof(s1); std::string s2; unsigned long s2_length = 0u; bool s2_truncated{}; binds[10].buffer_type = MYSQL_TYPE_STRING; binds[10].buffer = s2.data(); binds[10].buffer_length = s2_length; binds[10].length = &s2_length; binds[10].error = &s2_truncated; char b1[255]{}; binds[11].buffer_type = MYSQL_TYPE_BLOB; binds[11].buffer = b1; binds[11].buffer_length = sizeof(b1); std::string b2; unsigned long b2_length = 0u; bool b2_truncated{}; binds[12].buffer_type = MYSQL_TYPE_BLOB; binds[12].buffer = b2.data(); binds[12].buffer_length = b2_length; binds[12].length = &b2_length; binds[12].error = &b2_truncated; float flt{}; binds[13].buffer_type = MYSQL_TYPE_FLOAT; binds[13].buffer = &flt; binds[13].buffer_length = 4; double dbl{}; binds[14].buffer_type = MYSQL_TYPE_DOUBLE; binds[14].buffer = &dbl; binds[14].buffer_length = 8; MYSQL_TIME dt{}; binds[15].buffer_type = MYSQL_TYPE_DATE; binds[15].buffer = &dt; binds[15].buffer_length = sizeof(dt); MYSQL_TIME dtime{}; binds[16].buffer_type = MYSQL_TYPE_DATETIME; binds[16].buffer = &dtime; binds[16].buffer_length = sizeof(dtime); MYSQL_TIME t{}; binds[17].buffer_type = MYSQL_TYPE_TIME; binds[17].buffer = &t; binds[17].buffer_length = sizeof(t); // Ensure that nothing gets optimized away unsigned num_rows = 0; // Benchmark starts here auto tbegin = std::chrono::steady_clock::now(); // Execute the statement if (mysql_stmt_execute(stmt)) { fprintf(stderr, "Error executing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Bind output if (mysql_stmt_bind_result(stmt, binds)) { fprintf(stderr, "Error binding result: %s\n", mysql_stmt_error(stmt)); exit(1); } while (true) { auto status = mysql_stmt_fetch(stmt); // On truncation, resize the buffer and read again if (status == MYSQL_DATA_TRUNCATED) { if (s2_length > s2.size()) { s2.resize(s2_length); binds[10].buffer = s2.data(); binds[10].buffer_length = s2_length; if (mysql_stmt_fetch_column(stmt, &binds[10], 10, 0)) { fprintf(stderr, "Error fetching s2: %s\n", mysql_stmt_error(stmt)); exit(1); } } if (b2_length > b2.size()) { b2.resize(b2_length); binds[12].buffer = b2.data(); binds[12].buffer_length = b2_length; if (mysql_stmt_fetch_column(stmt, &binds[12], 12, 0)) { fprintf(stderr, "Error fetching b2: %s\n", mysql_stmt_error(stmt)); exit(1); } } ++num_rows; } else if (status == MYSQL_NO_DATA) { break; } else if (status == 1) { fprintf(stderr, "Error fetching result: %s\n", mysql_stmt_error(stmt)); exit(1); } else { ++num_rows; } } // Benchmark ends here auto tend = std::chrono::steady_clock::now(); std::cout << std::chrono::duration_cast(tend - tbegin).count() << std::endl; // Cleanup mysql_stmt_close(stmt); mysql_close(con); // We expect many rows return num_rows == 0 ? EXIT_FAILURE : EXIT_SUCCESS; } ================================================ FILE: bench/one_big_row_boost.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include #include #include #include #include using namespace std; namespace asio = boost::asio; namespace mysql = boost::mysql; int main() { // Setup asio::io_context ctx; mysql::any_connection conn(ctx); mysql::execution_state st; // Connect mysql::connect_params params; params.server_address.emplace_unix_path("/var/run/mysqld/mysqld.sock"); params.username = "root"; params.password = ""; params.database = "boost_mysql_bench"; params.ssl = mysql::ssl_mode::disable; conn.connect(params); // Prepare the statement auto stmt = conn.prepare_statement("SELECT * FROM test_data WHERE id = 1"); // Ensure that nothing gets optimized away unsigned num_rows = 0; // Benchmark starts here auto tbegin = std::chrono::steady_clock::now(); for (int i = 0; i < 10000; ++i) { // start_execution won't copy the strings in the rows (as opposed to execute), // so it's preferable when we have big rows, like here conn.start_execution(stmt.bind(), st); while (!st.complete()) num_rows += conn.read_some_rows(st).size(); } // Benchmark ends here auto tend = std::chrono::steady_clock::now(); std::cout << std::chrono::duration_cast(tend - tbegin).count() << std::endl; // We expect one row per iteration return num_rows == 10000 ? EXIT_SUCCESS : EXIT_FAILURE; } ================================================ FILE: bench/one_big_row_libmariadb.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include #include int main() { // Initialize if (mysql_library_init(0, NULL, NULL)) { fprintf(stderr, "could not initialize MySQL client library\n"); exit(1); } MYSQL* con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "Error initializing connection: %s\n", mysql_error(con)); exit(1); } // Connect if (mysql_real_connect(con, NULL, "root", "", "boost_mysql_bench", 0, "/var/run/mysqld/mysqld.sock", 0) == NULL) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } // Prepare the statement MYSQL_STMT* stmt; stmt = mysql_stmt_init(con); if (!stmt) { printf("Could not initialize statement\n"); exit(1); } constexpr const char* stmt_str = "SELECT * FROM test_data WHERE id = 1"; if (mysql_stmt_prepare(stmt, stmt_str, strlen(stmt_str))) { fprintf(stderr, "Error preparing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Prepare the bind objects long long int out_id = 0; MYSQL_BIND binds[18]{}; binds[0].buffer_type = MYSQL_TYPE_LONGLONG; binds[0].buffer = &out_id; binds[0].buffer_length = 8; signed char s8{}; binds[1].buffer_type = MYSQL_TYPE_TINY; binds[1].buffer = &s8; binds[1].buffer_length = 1; binds[1].is_unsigned = 0; unsigned char u8{}; binds[2].buffer_type = MYSQL_TYPE_TINY; binds[2].buffer = &u8; binds[2].buffer_length = 1; binds[2].is_unsigned = 1; short s16{}; binds[3].buffer_type = MYSQL_TYPE_SHORT; binds[3].buffer = &s16; binds[3].buffer_length = 2; binds[3].is_unsigned = 0; unsigned short u16{}; binds[4].buffer_type = MYSQL_TYPE_SHORT; binds[4].buffer = &u16; binds[4].buffer_length = 2; binds[4].is_unsigned = 1; int s32{}; binds[5].buffer_type = MYSQL_TYPE_LONG; binds[5].buffer = &s32; binds[5].buffer_length = 4; binds[5].is_unsigned = 0; unsigned u32{}; binds[6].buffer_type = MYSQL_TYPE_LONG; binds[6].buffer = &u32; binds[6].buffer_length = 4; binds[6].is_unsigned = 1; long long s64{}; binds[7].buffer_type = MYSQL_TYPE_LONGLONG; binds[7].buffer = &s64; binds[7].buffer_length = 8; binds[7].is_unsigned = 0; unsigned long long u64{}; binds[8].buffer_type = MYSQL_TYPE_LONGLONG; binds[8].buffer = &u64; binds[8].buffer_length = 8; binds[8].is_unsigned = 1; char s1[255]{}; binds[9].buffer_type = MYSQL_TYPE_STRING; binds[9].buffer = s1; binds[9].buffer_length = sizeof(s1); std::string s2; unsigned long s2_length = 0u; my_bool s2_truncated{}; binds[10].buffer_type = MYSQL_TYPE_STRING; binds[10].buffer = s2.data(); binds[10].buffer_length = s2_length; binds[10].length = &s2_length; binds[10].error = &s2_truncated; char b1[255]{}; binds[11].buffer_type = MYSQL_TYPE_BLOB; binds[11].buffer = b1; binds[11].buffer_length = sizeof(b1); std::string b2; unsigned long b2_length = 0u; my_bool b2_truncated{}; binds[12].buffer_type = MYSQL_TYPE_BLOB; binds[12].buffer = b2.data(); binds[12].buffer_length = b2_length; binds[12].length = &b2_length; binds[12].error = &b2_truncated; float flt{}; binds[13].buffer_type = MYSQL_TYPE_FLOAT; binds[13].buffer = &flt; binds[13].buffer_length = 4; double dbl{}; binds[14].buffer_type = MYSQL_TYPE_DOUBLE; binds[14].buffer = &dbl; binds[14].buffer_length = 8; MYSQL_TIME dt{}; binds[15].buffer_type = MYSQL_TYPE_DATE; binds[15].buffer = &dt; binds[15].buffer_length = sizeof(dt); MYSQL_TIME dtime{}; binds[16].buffer_type = MYSQL_TYPE_DATETIME; binds[16].buffer = &dtime; binds[16].buffer_length = sizeof(dtime); MYSQL_TIME t{}; binds[17].buffer_type = MYSQL_TYPE_TIME; binds[17].buffer = &t; binds[17].buffer_length = sizeof(t); // Ensure that nothing gets optimized away unsigned num_rows = 0; // Benchmark starts here auto tbegin = std::chrono::steady_clock::now(); for (int i = 0; i < 10000; ++i) { // Execute the statement if (mysql_stmt_execute(stmt)) { fprintf(stderr, "Error executing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Bind output if (mysql_stmt_bind_result(stmt, binds)) { fprintf(stderr, "Error binding result: %s\n", mysql_stmt_error(stmt)); exit(1); } // Read the rows while (true) { auto status = mysql_stmt_fetch(stmt); // On truncation, resize the buffer and read again if (status == MYSQL_DATA_TRUNCATED) { if (s2_length > s2.size()) { s2.resize(s2_length); binds[10].buffer = s2.data(); binds[10].buffer_length = s2_length; if (mysql_stmt_fetch_column(stmt, &binds[10], 10, 0)) { fprintf(stderr, "Error fetching s2: %s\n", mysql_stmt_error(stmt)); exit(1); } } if (b2_length > b2.size()) { b2.resize(b2_length); binds[12].buffer = b2.data(); binds[12].buffer_length = b2_length; if (mysql_stmt_fetch_column(stmt, &binds[12], 12, 0)) { fprintf(stderr, "Error fetching b2: %s\n", mysql_stmt_error(stmt)); exit(1); } } ++num_rows; } else if (status == MYSQL_NO_DATA) { break; } else if (status == 1) { fprintf(stderr, "Error fetching result: %s\n", mysql_stmt_error(stmt)); exit(1); } else { ++num_rows; } } } // Benchmark ends here auto tend = std::chrono::steady_clock::now(); std::cout << std::chrono::duration_cast(tend - tbegin).count() << std::endl; // Cleanup mysql_stmt_close(stmt); mysql_close(con); // We expect one row per iteration return num_rows == 10000 ? EXIT_SUCCESS : EXIT_FAILURE; } ================================================ FILE: bench/one_big_row_libmysqlclient.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include #include #include #include int main() { // Initialize if (mysql_library_init(0, NULL, NULL)) { fprintf(stderr, "could not initialize MySQL client library\n"); exit(1); } MYSQL* con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "Error initializing connection: %s\n", mysql_error(con)); exit(1); } // Connect unsigned mode = SSL_MODE_DISABLED; if (mysql_options(con, MYSQL_OPT_SSL_MODE, &mode)) { fprintf(stderr, "Error in mysql_options: %s\n", mysql_error(con)); exit(1); } if (mysql_real_connect(con, NULL, "root", "", "boost_mysql_bench", 0, "/var/run/mysqld/mysqld.sock", 0) == NULL) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } // Prepare the statement MYSQL_STMT* stmt; stmt = mysql_stmt_init(con); if (!stmt) { printf("Could not initialize statement\n"); exit(1); } constexpr const char* stmt_str = "SELECT * FROM test_data WHERE id = 1"; if (mysql_stmt_prepare(stmt, stmt_str, strlen(stmt_str))) { fprintf(stderr, "Error preparing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Prepare the bind objects long long int out_id = 0; MYSQL_BIND binds[18]{}; binds[0].buffer_type = MYSQL_TYPE_LONGLONG; binds[0].buffer = &out_id; binds[0].buffer_length = 8; signed char s8{}; binds[1].buffer_type = MYSQL_TYPE_TINY; binds[1].buffer = &s8; binds[1].buffer_length = 1; binds[1].is_unsigned = 0; unsigned char u8{}; binds[2].buffer_type = MYSQL_TYPE_TINY; binds[2].buffer = &u8; binds[2].buffer_length = 1; binds[2].is_unsigned = 1; short s16{}; binds[3].buffer_type = MYSQL_TYPE_SHORT; binds[3].buffer = &s16; binds[3].buffer_length = 2; binds[3].is_unsigned = 0; unsigned short u16{}; binds[4].buffer_type = MYSQL_TYPE_SHORT; binds[4].buffer = &u16; binds[4].buffer_length = 2; binds[4].is_unsigned = 1; int s32{}; binds[5].buffer_type = MYSQL_TYPE_LONG; binds[5].buffer = &s32; binds[5].buffer_length = 4; binds[5].is_unsigned = 0; unsigned u32{}; binds[6].buffer_type = MYSQL_TYPE_LONG; binds[6].buffer = &u32; binds[6].buffer_length = 4; binds[6].is_unsigned = 1; long long s64{}; binds[7].buffer_type = MYSQL_TYPE_LONGLONG; binds[7].buffer = &s64; binds[7].buffer_length = 8; binds[7].is_unsigned = 0; unsigned long long u64{}; binds[8].buffer_type = MYSQL_TYPE_LONGLONG; binds[8].buffer = &u64; binds[8].buffer_length = 8; binds[8].is_unsigned = 1; char s1[255]{}; binds[9].buffer_type = MYSQL_TYPE_STRING; binds[9].buffer = s1; binds[9].buffer_length = sizeof(s1); std::string s2; unsigned long s2_length = 0u; bool s2_truncated{}; binds[10].buffer_type = MYSQL_TYPE_STRING; binds[10].buffer = s2.data(); binds[10].buffer_length = s2_length; binds[10].length = &s2_length; binds[10].error = &s2_truncated; char b1[255]{}; binds[11].buffer_type = MYSQL_TYPE_BLOB; binds[11].buffer = b1; binds[11].buffer_length = sizeof(b1); std::string b2; unsigned long b2_length = 0u; bool b2_truncated{}; binds[12].buffer_type = MYSQL_TYPE_BLOB; binds[12].buffer = b2.data(); binds[12].buffer_length = b2_length; binds[12].length = &b2_length; binds[12].error = &b2_truncated; float flt{}; binds[13].buffer_type = MYSQL_TYPE_FLOAT; binds[13].buffer = &flt; binds[13].buffer_length = 4; double dbl{}; binds[14].buffer_type = MYSQL_TYPE_DOUBLE; binds[14].buffer = &dbl; binds[14].buffer_length = 8; MYSQL_TIME dt{}; binds[15].buffer_type = MYSQL_TYPE_DATE; binds[15].buffer = &dt; binds[15].buffer_length = sizeof(dt); MYSQL_TIME dtime{}; binds[16].buffer_type = MYSQL_TYPE_DATETIME; binds[16].buffer = &dtime; binds[16].buffer_length = sizeof(dtime); MYSQL_TIME t{}; binds[17].buffer_type = MYSQL_TYPE_TIME; binds[17].buffer = &t; binds[17].buffer_length = sizeof(t); // Ensure that nothing gets optimized away unsigned num_rows = 0; // Benchmark starts here auto tbegin = std::chrono::steady_clock::now(); for (int i = 0; i < 10000; ++i) { // Execute the statement if (mysql_stmt_execute(stmt)) { fprintf(stderr, "Error executing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Bind output if (mysql_stmt_bind_result(stmt, binds)) { fprintf(stderr, "Error binding result: %s\n", mysql_stmt_error(stmt)); exit(1); } // Read the rows while (true) { auto status = mysql_stmt_fetch(stmt); if (status == MYSQL_DATA_TRUNCATED) { // On truncation, resize the buffer and read again if (s2_length > s2.size()) { s2.resize(s2_length); binds[10].buffer = s2.data(); binds[10].buffer_length = s2_length; if (mysql_stmt_fetch_column(stmt, &binds[10], 10, 0)) { fprintf(stderr, "Error fetching s2: %s\n", mysql_stmt_error(stmt)); exit(1); } } if (b2_length > b2.size()) { b2.resize(b2_length); binds[12].buffer = b2.data(); binds[12].buffer_length = b2_length; if (mysql_stmt_fetch_column(stmt, &binds[12], 12, 0)) { fprintf(stderr, "Error fetching b2: %s\n", mysql_stmt_error(stmt)); exit(1); } } ++num_rows; } else if (status == MYSQL_NO_DATA) { break; } else if (status == 1) { fprintf(stderr, "Error fetching result: %s\n", mysql_stmt_error(stmt)); exit(1); } else { ++num_rows; } } } // Benchmark ends here auto tend = std::chrono::steady_clock::now(); std::cout << std::chrono::duration_cast(tend - tbegin).count() << std::endl; // Cleanup mysql_stmt_close(stmt); mysql_close(con); // We expect one row per iteration return num_rows == 10000 ? EXIT_SUCCESS : EXIT_FAILURE; } ================================================ FILE: bench/one_small_row_boost.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include #include #include #include #include namespace asio = boost::asio; namespace mysql = boost::mysql; int main() { // Setup asio::io_context ctx; mysql::any_connection conn(ctx); mysql::results r; // Connect mysql::connect_params params; params.server_address.emplace_unix_path("/var/run/mysqld/mysqld.sock"); params.username = "root"; params.password = ""; params.database = "boost_mysql_bench"; params.ssl = mysql::ssl_mode::disable; conn.connect(params); // Prepare the statement. Exclude the big TEXT/BLOB fields. auto stmt = conn.prepare_statement( "SELECT s8, u8, s16, u16, s32, u32, s64, u64, s1, b1, flt, dbl, dt, dtime, t " "FROM test_data WHERE id = 1" ); // Ensure that nothing gets optimized away unsigned num_rows = 0; // Benchmark starts here auto tbegin = std::chrono::steady_clock::now(); for (int i = 0; i < 10000; ++i) { // Since the rows are small, using execute is recommended conn.execute(stmt.bind(), r); num_rows += r.rows().size(); } // Benchmark ends here auto tend = std::chrono::steady_clock::now(); std::cout << std::chrono::duration_cast(tend - tbegin).count() << std::endl; // We expect one row per iteration return num_rows == 10000 ? EXIT_SUCCESS : EXIT_FAILURE; } ================================================ FILE: bench/one_small_row_libmariadb.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include using namespace std; int main() { // Initialize if (mysql_library_init(0, NULL, NULL)) { fprintf(stderr, "could not initialize MySQL client library\n"); exit(1); } MYSQL* con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "Error initializing connection: %s\n", mysql_error(con)); exit(1); } // Connect if (mysql_real_connect(con, NULL, "root", "", "boost_mysql_bench", 0, "/var/run/mysqld/mysqld.sock", 0) == NULL) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } // Prepare the statement. Exclude the big TEXT/BLOB fields MYSQL_STMT* stmt; stmt = mysql_stmt_init(con); if (!stmt) { printf("Could not initialize statement\n"); exit(1); } constexpr const char* stmt_str = "SELECT s8, u8, s16, u16, s32, u32, s64, u64, s1, b1, flt, dbl, dt, dtime, t " "FROM test_data WHERE id = 1"; if (mysql_stmt_prepare(stmt, stmt_str, strlen(stmt_str))) { fprintf(stderr, "Error preparing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Prepare the bind objects MYSQL_BIND binds[15]{}; signed char s8{}; binds[0].buffer_type = MYSQL_TYPE_TINY; binds[0].buffer = &s8; binds[0].buffer_length = 1; binds[0].is_unsigned = 0; unsigned char u8{}; binds[1].buffer_type = MYSQL_TYPE_TINY; binds[1].buffer = &u8; binds[1].buffer_length = 1; binds[1].is_unsigned = 1; short s16{}; binds[2].buffer_type = MYSQL_TYPE_SHORT; binds[2].buffer = &s16; binds[2].buffer_length = 2; binds[2].is_unsigned = 0; unsigned short u16{}; binds[3].buffer_type = MYSQL_TYPE_SHORT; binds[3].buffer = &u16; binds[3].buffer_length = 2; binds[3].is_unsigned = 1; int s32{}; binds[4].buffer_type = MYSQL_TYPE_LONG; binds[4].buffer = &s32; binds[4].buffer_length = 4; binds[4].is_unsigned = 0; unsigned u32{}; binds[5].buffer_type = MYSQL_TYPE_LONG; binds[5].buffer = &u32; binds[5].buffer_length = 4; binds[5].is_unsigned = 1; long long s64{}; binds[6].buffer_type = MYSQL_TYPE_LONGLONG; binds[6].buffer = &s64; binds[6].buffer_length = 8; binds[6].is_unsigned = 0; unsigned long long u64{}; binds[7].buffer_type = MYSQL_TYPE_LONGLONG; binds[7].buffer = &u64; binds[7].buffer_length = 8; binds[7].is_unsigned = 1; char s1[255]{}; binds[8].buffer_type = MYSQL_TYPE_STRING; binds[8].buffer = s1; binds[8].buffer_length = sizeof(s1); char b1[255]{}; binds[9].buffer_type = MYSQL_TYPE_BLOB; binds[9].buffer = b1; binds[9].buffer_length = sizeof(b1); float flt{}; binds[10].buffer_type = MYSQL_TYPE_FLOAT; binds[10].buffer = &flt; binds[10].buffer_length = 4; double dbl{}; binds[11].buffer_type = MYSQL_TYPE_DOUBLE; binds[11].buffer = &dbl; binds[11].buffer_length = 8; MYSQL_TIME dt{}; binds[12].buffer_type = MYSQL_TYPE_DATE; binds[12].buffer = &dt; binds[12].buffer_length = sizeof(dt); MYSQL_TIME dtime{}; binds[13].buffer_type = MYSQL_TYPE_DATETIME; binds[13].buffer = &dtime; binds[13].buffer_length = sizeof(dtime); MYSQL_TIME t{}; binds[14].buffer_type = MYSQL_TYPE_TIME; binds[14].buffer = &t; binds[14].buffer_length = sizeof(t); // Ensure that nothing gets optimized away unsigned num_rows = 0; // Benchmark starts here auto tbegin = std::chrono::steady_clock::now(); for (int i = 0; i < 10000; ++i) { // Execute the statement if (mysql_stmt_execute(stmt)) { fprintf(stderr, "Error executing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Bind output if (mysql_stmt_bind_result(stmt, binds)) { fprintf(stderr, "Error binding result: %s\n", mysql_stmt_error(stmt)); exit(1); } // Read the rows while (true) { auto status = mysql_stmt_fetch(stmt); if (status == MYSQL_DATA_TRUNCATED) { // No truncation is expected here, since we don't have big strings/blobs fprintf(stderr, "Data truncation error\n"); exit(1); } else if (status == MYSQL_NO_DATA) { break; } else if (status == 1) { fprintf(stderr, "Error fetching result: %s\n", mysql_stmt_error(stmt)); exit(1); } else { ++num_rows; } } } // Benchmark ends here auto tend = std::chrono::steady_clock::now(); std::cout << std::chrono::duration_cast(tend - tbegin).count() << std::endl; // Cleanup mysql_stmt_close(stmt); mysql_close(con); // We expect one row per iteration return num_rows == 10000 ? EXIT_SUCCESS : EXIT_FAILURE; } ================================================ FILE: bench/one_small_row_libmysqlclient.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include #include #include int main() { // Initialize if (mysql_library_init(0, NULL, NULL)) { fprintf(stderr, "could not initialize MySQL client library\n"); exit(1); } MYSQL* con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "Error initializing connection: %s\n", mysql_error(con)); exit(1); } // Connect unsigned mode = SSL_MODE_DISABLED; if (mysql_options(con, MYSQL_OPT_SSL_MODE, &mode)) { fprintf(stderr, "Error in mysql_options: %s\n", mysql_error(con)); exit(1); } if (mysql_real_connect(con, NULL, "root", "", "boost_mysql_bench", 0, "/var/run/mysqld/mysqld.sock", 0) == NULL) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } // Prepare the statement. Exclude the big TEXT/BLOB fields MYSQL_STMT* stmt; stmt = mysql_stmt_init(con); if (!stmt) { printf("Could not initialize statement\n"); exit(1); } constexpr const char* stmt_str = "SELECT s8, u8, s16, u16, s32, u32, s64, u64, s1, b1, flt, dbl, dt, dtime, t " "FROM test_data WHERE id = 1"; if (mysql_stmt_prepare(stmt, stmt_str, strlen(stmt_str))) { fprintf(stderr, "Error preparing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Prepare the bind objects MYSQL_BIND binds[15]{}; signed char s8{}; binds[0].buffer_type = MYSQL_TYPE_TINY; binds[0].buffer = &s8; binds[0].buffer_length = 1; binds[0].is_unsigned = 0; unsigned char u8{}; binds[1].buffer_type = MYSQL_TYPE_TINY; binds[1].buffer = &u8; binds[1].buffer_length = 1; binds[1].is_unsigned = 1; short s16{}; binds[2].buffer_type = MYSQL_TYPE_SHORT; binds[2].buffer = &s16; binds[2].buffer_length = 2; binds[2].is_unsigned = 0; unsigned short u16{}; binds[3].buffer_type = MYSQL_TYPE_SHORT; binds[3].buffer = &u16; binds[3].buffer_length = 2; binds[3].is_unsigned = 1; int s32{}; binds[4].buffer_type = MYSQL_TYPE_LONG; binds[4].buffer = &s32; binds[4].buffer_length = 4; binds[4].is_unsigned = 0; unsigned u32{}; binds[5].buffer_type = MYSQL_TYPE_LONG; binds[5].buffer = &u32; binds[5].buffer_length = 4; binds[5].is_unsigned = 1; long long s64{}; binds[6].buffer_type = MYSQL_TYPE_LONGLONG; binds[6].buffer = &s64; binds[6].buffer_length = 8; binds[6].is_unsigned = 0; unsigned long long u64{}; binds[7].buffer_type = MYSQL_TYPE_LONGLONG; binds[7].buffer = &u64; binds[7].buffer_length = 8; binds[7].is_unsigned = 1; char s1[255]{}; binds[8].buffer_type = MYSQL_TYPE_STRING; binds[8].buffer = s1; binds[8].buffer_length = sizeof(s1); char b1[255]{}; binds[9].buffer_type = MYSQL_TYPE_BLOB; binds[9].buffer = b1; binds[9].buffer_length = sizeof(b1); float flt{}; binds[10].buffer_type = MYSQL_TYPE_FLOAT; binds[10].buffer = &flt; binds[10].buffer_length = 4; double dbl{}; binds[11].buffer_type = MYSQL_TYPE_DOUBLE; binds[11].buffer = &dbl; binds[11].buffer_length = 8; MYSQL_TIME dt{}; binds[12].buffer_type = MYSQL_TYPE_DATE; binds[12].buffer = &dt; binds[12].buffer_length = sizeof(dt); MYSQL_TIME dtime{}; binds[13].buffer_type = MYSQL_TYPE_DATETIME; binds[13].buffer = &dtime; binds[13].buffer_length = sizeof(dtime); MYSQL_TIME t{}; binds[14].buffer_type = MYSQL_TYPE_TIME; binds[14].buffer = &t; binds[14].buffer_length = sizeof(t); // Ensure that nothing gets optimized away unsigned num_rows = 0; // Benchmark starts here auto tbegin = std::chrono::steady_clock::now(); for (int i = 0; i < 10000; ++i) { // Execute the statement if (mysql_stmt_execute(stmt)) { fprintf(stderr, "Error executing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Bind output if (mysql_stmt_bind_result(stmt, binds)) { fprintf(stderr, "Error binding result: %s\n", mysql_stmt_error(stmt)); exit(1); } // Read the rows while (true) { auto status = mysql_stmt_fetch(stmt); if (status == MYSQL_DATA_TRUNCATED) { // No truncation is expected here, since we don't have big strings/blobs fprintf(stderr, "Data truncation error\n"); exit(1); } else if (status == MYSQL_NO_DATA) { break; } else if (status == 1) { fprintf(stderr, "Error fetching result: %s\n", mysql_stmt_error(stmt)); exit(1); } else { ++num_rows; } } } // Benchmark ends here auto tend = std::chrono::steady_clock::now(); std::cout << std::chrono::duration_cast(tend - tbegin).count() << std::endl; // Cleanup mysql_stmt_close(stmt); mysql_close(con); // We expect one row per iteration return num_rows == 10000 ? EXIT_SUCCESS : EXIT_FAILURE; } ================================================ FILE: bench/stmt_params_boost.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace asio = boost::asio; namespace mysql = boost::mysql; int main() { // Setup asio::io_context ctx; mysql::any_connection conn(ctx); mysql::results r; // Connect mysql::connect_params params; params.server_address.emplace_unix_path("/var/run/mysqld/mysqld.sock"); params.username = "root"; params.password = ""; params.database = "boost_mysql_bench"; params.ssl = mysql::ssl_mode::disable; conn.connect(params); // Prepare the statement. It should have many parameters and be a lightweight query. // This SELECT is lighter than an INSERT. auto stmt = conn.prepare_statement( "SELECT id FROM test_data WHERE id = 1 AND s8 = ? AND u8 = ? AND s16 = ? AND u16 = ? AND " "s32 = ? AND u32 = ? AND s64 = ? AND u64 = ? AND s1 = ? AND s2 = ? AND b1 = ? AND " "b2 = ? AND flt = ? AND dbl = ? AND dt = ? AND dtime = ? AND t = ?" ); // Statement params signed char s8 = 64; unsigned char u8 = 172; short s16 = -129; unsigned short u16 = 0xfe21; int s32 = 42; unsigned u32 = 0xfe8173; long long s64 = -1; unsigned long long u64 = 98302402; std::string s1(200, 'a'); std::string s2(36000, 'b'); std::vector b1(200, 5); std::vector b2(35000, 7); float flt = 3.14e10; double dbl = 7.1e-150; mysql::date dt(2010, 6, 20); mysql::datetime dtime(2020, 3, 21, 10, 40, 10, 123456); mysql::time t = std::chrono::hours(126) + std::chrono::minutes(18) + std::chrono::seconds(40) + std::chrono::microseconds(123456); // Ensure that nothing gets optimized away unsigned num_rows = 0; // Benchmark starts here auto tbegin = std::chrono::steady_clock::now(); for (int i = 0; i < 1000; ++i) { // No rows will be matched, so execute() works conn.execute( stmt.bind( s8, u8, s16, u16, s32, u32, s64, u64, mysql::string_view(s1), mysql::string_view(s2), mysql::blob_view(b1), mysql::blob_view(b2), flt, dbl, dt, dtime, t ), r ); num_rows += r.rows().size(); } // Benchmark ends here auto tend = std::chrono::steady_clock::now(); std::cout << std::chrono::duration_cast(tend - tbegin).count() << std::endl; // We don't expect any row to be matched return num_rows == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } ================================================ FILE: bench/stmt_params_libmariadb.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include #include #include int main() { // Initialize if (mysql_library_init(0, NULL, NULL)) { fprintf(stderr, "could not initialize MySQL client library\n"); exit(1); } MYSQL* con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "Error initializing connection: %s\n", mysql_error(con)); exit(1); } // Connect if (mysql_real_connect(con, NULL, "root", "", "boost_mysql_bench", 0, "/var/run/mysqld/mysqld.sock", 0) == NULL) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } // Prepare the statement. It should have many parameters and be a lightweight query. // This SELECT is lighter than an INSERT. MYSQL_STMT* stmt; stmt = mysql_stmt_init(con); if (!stmt) { printf("Could not initialize statement\n"); exit(1); } constexpr const char* stmt_str = "SELECT id FROM test_data WHERE id = 1 AND s8 = ? AND u8 = ? AND s16 = ? AND u16 = ? AND " "s32 = ? AND u32 = ? AND s64 = ? AND u64 = ? AND s1 = ? AND s2 = ? AND b1 = ? AND " "b2 = ? AND flt = ? AND dbl = ? AND dt = ? AND dtime = ? AND t = ?"; if (mysql_stmt_prepare(stmt, stmt_str, strlen(stmt_str))) { fprintf(stderr, "Error preparing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Statement params signed char s8 = 64; unsigned char u8 = 172; short s16 = -129; unsigned short u16 = 0xfe21; int s32 = 42; unsigned u32 = 0xfe8173; long long s64 = -1; unsigned long long u64 = 98302402; std::string s1(200, 'a'); std::string s2(36000, 'b'); std::vector b1(200, 5); std::vector b2(35000, 7); float flt = 3.14e10; double dbl = 7.1e-150; MYSQL_TIME dt{}, dtime{}, t{}; dt.year = 2010; dt.month = 6; dt.day = 20; dtime.year = 2020; dtime.month = 3; dtime.day = 21; dtime.hour = 10; dtime.minute = 40; dtime.second = 10; dtime.second_part = 123456; t.hour = 126; t.minute = 18; t.second = 40; t.second_part = 123456; // Prepare the bind objects MYSQL_BIND in_binds[17]{}; in_binds[0].buffer_type = MYSQL_TYPE_TINY; in_binds[0].buffer = &s8; in_binds[0].buffer_length = 1; in_binds[0].is_unsigned = 0; in_binds[1].buffer_type = MYSQL_TYPE_TINY; in_binds[1].buffer = &u8; in_binds[1].buffer_length = 1; in_binds[1].is_unsigned = 1; in_binds[2].buffer_type = MYSQL_TYPE_SHORT; in_binds[2].buffer = &s16; in_binds[2].buffer_length = 2; in_binds[2].is_unsigned = 0; in_binds[3].buffer_type = MYSQL_TYPE_SHORT; in_binds[3].buffer = &u16; in_binds[3].buffer_length = 2; in_binds[3].is_unsigned = 1; in_binds[4].buffer_type = MYSQL_TYPE_LONG; in_binds[4].buffer = &s32; in_binds[4].buffer_length = 4; in_binds[4].is_unsigned = 0; in_binds[5].buffer_type = MYSQL_TYPE_LONG; in_binds[5].buffer = &u32; in_binds[5].buffer_length = 4; in_binds[5].is_unsigned = 1; in_binds[6].buffer_type = MYSQL_TYPE_LONGLONG; in_binds[6].buffer = &s64; in_binds[6].buffer_length = 8; in_binds[6].is_unsigned = 0; in_binds[7].buffer_type = MYSQL_TYPE_LONGLONG; in_binds[7].buffer = &u64; in_binds[7].buffer_length = 8; in_binds[7].is_unsigned = 1; in_binds[8].buffer_type = MYSQL_TYPE_STRING; in_binds[8].buffer = s1.data(); in_binds[8].buffer_length = s1.size(); in_binds[9].buffer_type = MYSQL_TYPE_STRING; in_binds[9].buffer = s2.data(); in_binds[9].buffer_length = s2.size(); in_binds[10].buffer_type = MYSQL_TYPE_BLOB; in_binds[10].buffer = b1.data(); in_binds[10].buffer_length = b1.size(); in_binds[11].buffer_type = MYSQL_TYPE_BLOB; in_binds[11].buffer = b2.data(); in_binds[11].buffer_length = b2.size(); in_binds[12].buffer_type = MYSQL_TYPE_FLOAT; in_binds[12].buffer = &flt; in_binds[12].buffer_length = 4; in_binds[13].buffer_type = MYSQL_TYPE_DOUBLE; in_binds[13].buffer = &dbl; in_binds[13].buffer_length = 8; in_binds[14].buffer_type = MYSQL_TYPE_DATE; in_binds[14].buffer = &dt; in_binds[14].buffer_length = sizeof(dt); in_binds[15].buffer_type = MYSQL_TYPE_DATETIME; in_binds[15].buffer = &dtime; in_binds[15].buffer_length = sizeof(dtime); in_binds[16].buffer_type = MYSQL_TYPE_TIME; in_binds[16].buffer = &t; in_binds[16].buffer_length = sizeof(t); // Prepare the output bind objects (only one parameter) long long int out_id = 0; MYSQL_BIND out_binds[1]{}; out_binds[0].buffer_type = MYSQL_TYPE_LONGLONG; out_binds[0].buffer = &out_id; out_binds[0].buffer_length = 8; // Ensure that nothing gets optimized away unsigned num_rows = 0; // Benchmark starts here auto tbegin = std::chrono::steady_clock::now(); for (int i = 0; i < 1000; ++i) { // Bind the params if (mysql_stmt_bind_param(stmt, in_binds)) { fprintf(stderr, "Error binding params: %s\n", mysql_stmt_error(stmt)); exit(1); } // Execute the statement if (mysql_stmt_execute(stmt)) { fprintf(stderr, "Error executing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Bind output if (mysql_stmt_bind_result(stmt, out_binds)) { fprintf(stderr, "Error binding result: %s\n", mysql_stmt_error(stmt)); exit(1); } // Read the rows while (true) { auto status = mysql_stmt_fetch(stmt); if (status == MYSQL_DATA_TRUNCATED) { // No truncation is expected here fprintf(stderr, "Data truncation error\n"); exit(1); } else if (status == MYSQL_NO_DATA) { break; } else if (status == 1) { fprintf(stderr, "Error fetching result: %s\n", mysql_stmt_error(stmt)); exit(1); } else { ++num_rows; } } } // Benchmark ends here auto tend = std::chrono::steady_clock::now(); std::cout << std::chrono::duration_cast(tend - tbegin).count() << std::endl; // Cleanup mysql_stmt_close(stmt); mysql_close(con); // We don't expect any rows to be matched return num_rows == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } ================================================ FILE: bench/stmt_params_libmysqlclient.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include #include #include #include int main() { // Initialize if (mysql_library_init(0, NULL, NULL)) { fprintf(stderr, "could not initialize MySQL client library\n"); exit(1); } MYSQL* con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "Error initializing connection: %s\n", mysql_error(con)); exit(1); } // Connect unsigned mode = SSL_MODE_DISABLED; if (mysql_options(con, MYSQL_OPT_SSL_MODE, &mode)) { fprintf(stderr, "Error in mysql_options: %s\n", mysql_error(con)); exit(1); } if (mysql_real_connect(con, NULL, "root", "", "boost_mysql_bench", 0, "/var/run/mysqld/mysqld.sock", 0) == NULL) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } // Prepare the statement. It should have many parameters and be a lightweight query. // This SELECT is lighter than an INSERT. MYSQL_STMT* stmt; stmt = mysql_stmt_init(con); if (!stmt) { printf("Could not initialize statement\n"); exit(1); } constexpr const char* stmt_str = "SELECT id FROM test_data WHERE id = 1 AND s8 = ? AND u8 = ? AND s16 = ? AND u16 = ? AND " "s32 = ? AND u32 = ? AND s64 = ? AND u64 = ? AND s1 = ? AND s2 = ? AND b1 = ? AND " "b2 = ? AND flt = ? AND dbl = ? AND dt = ? AND dtime = ? AND t = ?"; if (mysql_stmt_prepare(stmt, stmt_str, strlen(stmt_str))) { fprintf(stderr, "Error preparing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Statement params signed char s8 = 64; unsigned char u8 = 172; short s16 = -129; unsigned short u16 = 0xfe21; int s32 = 42; unsigned u32 = 0xfe8173; long long s64 = -1; unsigned long long u64 = 98302402; std::string s1(200, 'a'); std::string s2(36000, 'b'); std::vector b1(200, 5); std::vector b2(35000, 7); float flt = 3.14e10; double dbl = 7.1e-150; MYSQL_TIME dt{}, dtime{}, t{}; dt.year = 2010; dt.month = 6; dt.day = 20; dtime.year = 2020; dtime.month = 3; dtime.day = 21; dtime.hour = 10; dtime.minute = 40; dtime.second = 10; dtime.second_part = 123456; t.hour = 126; t.minute = 18; t.second = 40; t.second_part = 123456; // Prepare the bind objects MYSQL_BIND in_binds[17]{}; in_binds[0].buffer_type = MYSQL_TYPE_TINY; in_binds[0].buffer = &s8; in_binds[0].buffer_length = 1; in_binds[0].is_unsigned = 0; in_binds[1].buffer_type = MYSQL_TYPE_TINY; in_binds[1].buffer = &u8; in_binds[1].buffer_length = 1; in_binds[1].is_unsigned = 1; in_binds[2].buffer_type = MYSQL_TYPE_SHORT; in_binds[2].buffer = &s16; in_binds[2].buffer_length = 2; in_binds[2].is_unsigned = 0; in_binds[3].buffer_type = MYSQL_TYPE_SHORT; in_binds[3].buffer = &u16; in_binds[3].buffer_length = 2; in_binds[3].is_unsigned = 1; in_binds[4].buffer_type = MYSQL_TYPE_LONG; in_binds[4].buffer = &s32; in_binds[4].buffer_length = 4; in_binds[4].is_unsigned = 0; in_binds[5].buffer_type = MYSQL_TYPE_LONG; in_binds[5].buffer = &u32; in_binds[5].buffer_length = 4; in_binds[5].is_unsigned = 1; in_binds[6].buffer_type = MYSQL_TYPE_LONGLONG; in_binds[6].buffer = &s64; in_binds[6].buffer_length = 8; in_binds[6].is_unsigned = 0; in_binds[7].buffer_type = MYSQL_TYPE_LONGLONG; in_binds[7].buffer = &u64; in_binds[7].buffer_length = 8; in_binds[7].is_unsigned = 1; in_binds[8].buffer_type = MYSQL_TYPE_STRING; in_binds[8].buffer = s1.data(); in_binds[8].buffer_length = s1.size(); in_binds[9].buffer_type = MYSQL_TYPE_STRING; in_binds[9].buffer = s2.data(); in_binds[9].buffer_length = s2.size(); in_binds[10].buffer_type = MYSQL_TYPE_BLOB; in_binds[10].buffer = b1.data(); in_binds[10].buffer_length = b1.size(); in_binds[11].buffer_type = MYSQL_TYPE_BLOB; in_binds[11].buffer = b2.data(); in_binds[11].buffer_length = b2.size(); in_binds[12].buffer_type = MYSQL_TYPE_FLOAT; in_binds[12].buffer = &flt; in_binds[12].buffer_length = 4; in_binds[13].buffer_type = MYSQL_TYPE_DOUBLE; in_binds[13].buffer = &dbl; in_binds[13].buffer_length = 8; in_binds[14].buffer_type = MYSQL_TYPE_DATE; in_binds[14].buffer = &dt; in_binds[14].buffer_length = sizeof(dt); in_binds[15].buffer_type = MYSQL_TYPE_DATETIME; in_binds[15].buffer = &dtime; in_binds[15].buffer_length = sizeof(dtime); in_binds[16].buffer_type = MYSQL_TYPE_TIME; in_binds[16].buffer = &t; in_binds[16].buffer_length = sizeof(t); // Prepare the output bind objects (only one parameter) long long int out_id = 0; MYSQL_BIND out_binds[1]{}; out_binds[0].buffer_type = MYSQL_TYPE_LONGLONG; out_binds[0].buffer = &out_id; out_binds[0].buffer_length = 8; // Ensure that nothing gets optimized away unsigned num_rows = 0; // Benchmark starts here auto tbegin = std::chrono::steady_clock::now(); for (int i = 0; i < 1000; ++i) { // Bind the params if (mysql_stmt_bind_param(stmt, in_binds)) { fprintf(stderr, "Error binding params: %s\n", mysql_stmt_error(stmt)); exit(1); } // Execute the statement if (mysql_stmt_execute(stmt)) { fprintf(stderr, "Error executing statement: %s\n", mysql_stmt_error(stmt)); exit(1); } // Bind output if (mysql_stmt_bind_result(stmt, out_binds)) { fprintf(stderr, "Error binding result: %s\n", mysql_stmt_error(stmt)); exit(1); } // Read the rows while (true) { auto status = mysql_stmt_fetch(stmt); if (status == MYSQL_DATA_TRUNCATED) { // No truncation is expected here fprintf(stderr, "Data truncation error\n"); exit(1); } else if (status == MYSQL_NO_DATA) { break; } else if (status == 1) { fprintf(stderr, "Error fetching result: %s\n", mysql_stmt_error(stmt)); exit(1); } else { ++num_rows; } } } // Benchmark ends here auto tend = std::chrono::steady_clock::now(); std::cout << std::chrono::duration_cast(tend - tbegin).count() << std::endl; // Cleanup mysql_stmt_close(stmt); mysql_close(con); // We don't expect any rows to be matched return num_rows == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } ================================================ FILE: build.jam ================================================ # Copyright René Ferdinand Rivera Morell 2024 # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) require-b2 5.2 ; constant boost_dependencies : /boost/asio//boost_asio /boost/assert//boost_assert /boost/charconv//boost_charconv /boost/compat//boost_compat /boost/config//boost_config /boost/container//boost_container /boost/core//boost_core /boost/describe//boost_describe /boost/endian//boost_endian /boost/intrusive//boost_intrusive /boost/mp11//boost_mp11 /boost/optional//boost_optional /boost/pfr//boost_pfr /boost/system//boost_system /boost/throw_exception//boost_throw_exception /boost/variant2//boost_variant2 ; project /boost/mysql : common-requirements include ; explicit [ alias boost_mysql : : : : $(boost_dependencies) ] [ alias all : boost_mysql example test ] ; call-if : boost-library mysql ; ================================================ FILE: cmake/utils.cmake ================================================ # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # # Include guard if (_BOOST_MYSQL_UTILS_INCLUDED) return() endif() set(_BOOST_MYSQL_UTILS_INCLUDED TRUE) # Sets _WIN32_WINNT on Windows function(boost_mysql_set_windows_version TARGET_NAME) if(MSVC) if(WIN32 AND CMAKE_SYSTEM_VERSION) set(WINNT_VERSION ${CMAKE_SYSTEM_VERSION}) string(REPLACE "." "" WINNT_VERSION ${WINNT_VERSION}) string(REGEX REPLACE "([0-9])" "0\\1" WINNT_VERSION ${WINNT_VERSION}) set(WINNT_VERSION "0x${WINNT_VERSION}") else() set(WINNT_VERSION "0x0601") endif() target_compile_definitions( ${TARGET_NAME} PUBLIC _WIN32_WINNT=${WINNT_VERSION} # Silence warnings in Windows ) endif() endfunction() # Utility function to set warnings and other compile properties of # our test targets function(boost_mysql_common_target_settings TARGET_NAME) boost_mysql_set_windows_version(${TARGET_NAME}) if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") target_compile_definitions( ${TARGET_NAME} PUBLIC _SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING # Warnings in C++17 for Asio ) target_compile_options(${TARGET_NAME} PUBLIC /bigobj) # Prevent failures on Windows else() # gcc-13 doesn't understand view types if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13.0) target_compile_options(${TARGET_NAME} PUBLIC -Wno-dangling-reference -Wno-array-bounds) endif() target_compile_options(${TARGET_NAME} PUBLIC -Wall -Wextra) endif() set_target_properties(${TARGET_NAME} PROPERTIES CXX_EXTENSIONS OFF) # disable extensions endfunction() function(boost_mysql_test_target_settings TARGET_NAME) boost_mysql_common_target_settings(${TARGET_NAME}) # Follow the Boost convention: don't build test targets by default, # and only when explicitly requested by building target tests set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL ON) add_dependencies(tests ${TARGET_NAME}) endfunction() ================================================ FILE: doc/Jamfile ================================================ # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # project mysql/doc ; import boostbook ; import os ; import path ; import-search /boost/docca ; import docca ; # Note: adding server-specific error codes and collations to the reference # increases build times a lot without any benefit local doxygen_exclussions = detail impl mysql_server_errc.hpp mariadb_server_errc.hpp mysql_collations.hpp mariadb_collations.hpp src.hpp ; local include-prefix = [ path.root $(__file__:D) [ path.pwd ] ] ; include-prefix = [ path.native $(include-prefix:D)/include ] ; docca.pyreference reference.qbk : [ glob-tree-ex ../include/boost/mysql : *.hpp : $(doxygen_exclussions) ] : PROJECT_NAME=MySQL PROJECT_BRIEF="C++ MySQL Client Library" DISTRIBUTE_GROUP_DOC=YES ENABLE_PREPROCESSING=YES MACRO_EXPANSION=YES EXPAND_ONLY_PREDEF=YES SEARCH_INCLUDES=NO STRIP_FROM_PATH=$(include-prefix) "PREDEFINED=\\ BOOST_MYSQL_DOXYGEN \\ __cpp_char8_t \\ \"BOOST_PFR_ENABLED=1\" \\ \"BOOST_PFR_CORE_NAME_ENABLED=1\" \\ \"BOOST_DEPRECATED(a)=\" \\ \"BOOST_ATTRIBUTE_NODISCARD=[[nodiscard]]\" \\ \"BOOST_ASIO_INITFN_RESULT_TYPE(t,a)=auto\" \\ \"BOOST_ASIO_COMPLETION_TOKEN_FOR(sig)=class\" \\ \"BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(ct,sig)=auto\" \\ \"BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(ex)=\" \\ \"BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(ex)=\" \\ \"BOOST_MYSQL_RETURN_TYPE(...)=\" \\ \"protected=private\" \\ \"BOOST_CXX14_CONSTEXPR=constexpr\" \\ \"BOOST_INLINE_CONSTEXPR=inline constexpr\" \\ \"BOOST_MYSQL_CONSTEVAL=consteval\" \\ \"BOOST_MYSQL_CXX14\" \\ \"BOOST_MYSQL_WRITABLE_FIELD_TUPLE=class\" \\ \"BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR=class\" \\ \"BOOST_MYSQL_EXECUTION_REQUEST=class\" \\ \"BOOST_MYSQL_RESULTS_TYPE=class\" \\ \"BOOST_MYSQL_EXECUTION_STATE_TYPE=class\" \\ \"BOOST_MYSQL_OUTPUT_STRING=class\" \\ \"BOOST_MYSQL_FORMATTABLE=class\" \\ \"BOOST_MYSQL_STATIC_ROW=class\" \\ \"BOOST_MYSQL_PIPELINE_REQUEST_TYPE=class\" \\ \"BOOST_MYSQL_PIPELINE_STAGE_TYPE=class\" \\ \"BOOST_MYSQL_WRITABLE_FIELD=class\" \\ \"BOOST_MYSQL_DECL=\" \\ \"BOOST_MYSQL_HAS_LOCAL_TIME=\" \\ \"BOOST_NO_CXX17_DEDUCTION_GUIDES=\" \\ " SKIP_FUNCTION_MACROS=NO OUTPUT_LANGUAGE=English ABBREVIATE_BRIEF= AUTOLINK_SUPPORT=NO EXTRACT_ALL=NO EXTRACT_PRIVATE=NO EXTRACT_LOCAL_CLASSES=NO HIDE_UNDOC_MEMBERS=YES HIDE_UNDOC_CLASSES=YES HIDE_FRIEND_COMPOUNDS=YES CASE_SENSE_NAMES=YES SHOW_INCLUDE_FILES=NO INLINE_INFO=NO SORT_MEMBER_DOCS=NO SORT_MEMBERS_CTORS_1ST=YES SHOW_USED_FILES=NO SHOW_FILES=NO SHOW_NAMESPACES=NO QUIET=YES config.json ; install images : [ glob images/*.png images/*.svg ] : html/mysql/images ; explicit images ; xml mysql_doc : qbk/00_main.qbk : reference.qbk images ; explicit mysql_doc ; boostbook mysql : mysql_doc : boost.root=../../../.. chapter.autolabel=1 chunk.section.depth=8 # Depth to which sections should be chunked chunk.first.sections=1 # Chunk the first top-level section? toc.section.depth=8 # How deep should recursive sections appear in the TOC? toc.max.depth=8 # How many levels should be created for each TOC? generate.toc="chapter toc,title section nop reference nop part toc" ../../../tools/boostbook/dtd : images ; # These are used to inform the build system of the # means to build the integrated and stand-alone docs. alias boostdoc ; explicit boostdoc ; alias boostrelease : mysql ; explicit boostrelease ; ================================================ FILE: doc/config.json ================================================ { "include_private": false, "legacy_behavior": false, "external_marker": "!EXTERNAL!", "link_prefix": "mysql.ref.", "default_namespace": "boost::mysql", "convenience_header": "boost/mysql.hpp", "replace_strings": { "__see_below__": "``['see-below]``", "\\bclass CompletionToken\\b": "class __CompletionToken__", "\\bclass ExecutionContext\\b": "class __ExecutionContext__", "\\bclass ExecutionRequest\\b": "class __ExecutionRequest__", "\\bclass ExecutionStateType\\b": "class __ExecutionStateType__", "\\bclass Executor\\b": "class __Executor__", "\\bclass FieldViewFwdIterator\\b": "class __FieldViewFwdIterator__", "\\bclass Formattable\\b": "class __Formattable__", "\\bclass OutputString\\b": "class __OutputString__", "\\bclass ResultsType\\b": "class __ResultsType__", "\\bclass SocketStream\\b": "class __SocketStream__", "\\bclass StaticRow\\b": "class __StaticRow__", "\\bclass Stream\\b": "class __Stream__", "\\bclass WritableField\\b": "class __WritableField__", "\\bclass WritableFieldTuple\\b": "class __WritableFieldTuple__", "\\bBOOST_MYSQL_FORMATTABLE\\b": "class" } } ================================================ FILE: doc/doxygen.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DOC_DOXYGEN_HPP #define BOOST_MYSQL_DOC_DOXYGEN_HPP /** * \brief brief * \details * description * * \par Preconditions * Only for functions, if they have a precondition (i.e. assert(xxx)) * * \par Exception safety * No-throw guarantee. * Only for functions. Don't include it in network operations for now. * Strong guarantee. * Basic guarantee. * No-throw guarantee. * \throws {exception} {condition}. * * \par Object lifetimes * For async stuff or if the function returns views * * \par Complexity * Include it only for functions in containers or where relevant. * Linear in xxxx. * * (Include only for async ops) * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)` * * \par Per-operation cancellation * This operation supports per-operation cancellation. * The following `asio::cancellation_type_t` values are supported: * * - `asio::cancellation_type_t::terminal` * - `asio::cancellation_type_t::partial` * - `asio::cancellation_type_t::total` * * Specify this where it adds any value. * \par Thread safety * Distinct objects: safe. \n * Shared objects: unsafe. \n * * Specify this for new async operations. Include the error codes that we may return. * \par Errors * \li \ref client_errc::X when condition Y */ #endif ================================================ FILE: doc/qbk/00_main.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [library Boost.MySQL [quickbook 1.7] [copyright 2019 - 2024 Ruben Perez] [id mysql] [purpose MySQL client library] [license Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at [@http://www.boost.org/LICENSE_1_0.txt]) ] [authors [Perez, Ruben]] [category template] [category generic] ] [template nochunk[] [block '''''']] [template mdash[] '''— '''] [template link_to_file[path][^''''''[path]'''''']] [template include_file[path][^<''''''[path]''''''>]] [template indexterm1[term1] ''''''[term1]''''''] [template indexterm2[term1 term2] ''''''[term1]''''''[term2]''''''] [template reflink2[id text][link mysql.ref.boost__mysql__[id] [^[text]]]] [template reflink[id][reflink2 [id] [id]]] [template refmem[class mem][reflink2 [class].[mem] [class]::[mem]]] [template refmemunq[class mem][reflink2 [class].[mem] [mem]]] [template asioreflink[id term][@boost:/doc/html/boost_asio/reference/[id].html [^asio::[term]]]] [template mysqllink[id text][@https://dev.mysql.com/doc/refman/8.0/en/[id] [text]]] [def __CompletionToken__ [@boost:/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.completion_tokens_and_handlers ['CompletionToken]]] [def __ExecutionContext__ [@boost:/doc/html/boost_asio/reference/ExecutionContext.html ['ExecutionContext]]] [def __ExecutionRequest__ [reflink2 ExecutionRequest ['ExecutionRequest]]] [def __ExecutionStateType__ [reflink2 ExecutionStateType ['ExecutionStateType]]] [def __Executor__ [@boost:/doc/html/boost_asio/reference/Executor1.html ['Executor]]] [def __FieldViewFwdIterator__ [reflink2 FieldViewFwdIterator ['FieldViewFwdIterator]]] [def __Formattable__ [reflink2 Formattable ['Formattable]]] [def __OutputString__ [reflink2 OutputString ['OutputString]]] [def __ResultsType__ [reflink2 ResultsType ['ResultsType]]] [def __SocketStream__ [reflink2 SocketStream ['SocketStream]]] [def __StaticRow__ [reflink2 StaticRow ['StaticRow]]] [def __Stream__ [reflink2 Stream ['Stream]]] [def __WritableField__ [reflink2 WritableFieldTuple ['WritableField]]] [def __WritableFieldTuple__ [reflink2 WritableFieldTuple ['WritableFieldTuple]]] [def __Boost__ [@https://www.boost.org/ Boost]] [def __Asio__ [@boost:/libs/asio/index.html Boost.Asio]] [def __Beast__ [@boost:/libs/beast/index.html Boost.Beast]] [def __Context__ [@boost:/libs/context/index.html Boost.Context]] [def __Self__ [@boost:/libs/mysql/index.html Boost.MySQL]] [def __boost_optional__ [@boost:/libs/optional/index.html `boost::optional`]] [def __Describe__ [@boost:/libs/describe/index.html Boost.Describe]] [def __Pfr__ [@boost:/libs/pfr/index.html Boost.Pfr]] [def __ssl_context__ [asioreflink ssl__context ssl::context]] [/ MySQL stuff] [def __Mysql__ [@https://www.mysql.com/ MySQL]] [def __sql_mode__ [mysqllink sql-mode.html `sql_mode`]] [def __allow_invalid_dates__ [mysqllink sql-mode.html#sqlmode_allow_invalid_dates `ALLOW_INVALID_DATES`]] [def __strict_sql__ [mysqllink sql-mode.html#sql-mode-strict strict SQL mode]] [def __time_zone__ [mysqllink server-system-variables.html#sysvar_time_zone `time_zone`]] [def __TINYINT__ [mysqllink integer-types.html `TINYINT`]] [def __SMALLINT__ [mysqllink integer-types.html `SMALLINT`]] [def __MEDIUMINT__ [mysqllink integer-types.html `MEDIUMINT`]] [def __INT__ [mysqllink integer-types.html `INT`]] [def __BIGINT__ [mysqllink integer-types.html `BIGINT`]] [def __YEAR__ [mysqllink year.html `YEAR`]] [def __DATE__ [mysqllink datetime.html `DATE`]] [def __DATETIME__ [mysqllink datetime.html `DATETIME`]] [def __TIMESTAMP__ [mysqllink datetime.html `TIMESTAMP`]] [def __TIME__ [mysqllink time.html `TIME`]] [def __FLOAT__ [mysqllink floating-point-types.html `FLOAT`]] [def __DOUBLE__ [mysqllink floating-point-types.html `DOUBLE`]] [def __DECIMAL__ [mysqllink fixed-point-types.html `DECIMAL`]] [def __NUMERIC__ [mysqllink fixed-point-types.html `NUMERIC`]] [def __BIT__ [mysqllink bit-type.html `BIT`]] [def __CHAR__ [mysqllink char.html `CHAR`]] [def __VARCHAR__ [mysqllink char.html `VARCHAR`]] [def __BINARY__ [mysqllink binary-varbinary.html `BINARY`]] [def __VARBINARY__ [mysqllink binary-varbinary.html `VARBINARY`]] [def __TEXT__ [mysqllink blob.html `TEXT`]] [def __BLOB__ [mysqllink blob.html `BLOB`]] [def __ENUM__ [mysqllink enum.html `ENUM`]] [def __SET__ [mysqllink set.html `SET`]] [def __JSON__ [mysqllink json.html `JSON`]] [def __GEOMETRY__ [mysqllink spatial-type-overview.html `GEOMETRY`]] [def __USE__ [mysqllink use.html `USE`]] [/ Taken db_setup.sql, because import doesn't work for SQL files - keep in sync. Having them in a separate file doesn't work ] [def __sp_get_employees__ ``` CREATE PROCEDURE get_employees(IN pin_company_id CHAR(10)) BEGIN START TRANSACTION READ ONLY; SELECT id, name, tax_id FROM company WHERE id = pin_company_id; SELECT first_name, last_name, salary FROM employee WHERE company_id = pin_company_id; COMMIT; END ```] [def __sp_create_employee__ ``` CREATE PROCEDURE create_employee( IN pin_company_id CHAR(10), IN pin_first_name VARCHAR(100), IN pin_last_name VARCHAR(100), OUT pout_employee_id INT ) BEGIN START TRANSACTION; INSERT INTO employee (company_id, first_name, last_name) VALUES (pin_company_id, pin_first_name, pin_last_name); SET pout_employee_id = LAST_INSERT_ID(); INSERT INTO audit_log (msg) VALUES ('Created new employee...'); COMMIT; END ```] [/ AUTOGENERATED IMPORTS BEGIN ] [import ../../example/1_tutorial/1_sync.cpp] [import ../../example/1_tutorial/2_async.cpp] [import ../../example/1_tutorial/3_with_params.cpp] [import ../../example/1_tutorial/4_static_interface.cpp] [import ../../example/1_tutorial/5_updates_transactions.cpp] [import ../../example/1_tutorial/6_connection_pool.cpp] [import ../../example/1_tutorial/7_error_handling.cpp] [import ../../example/2_simple/inserts.cpp] [import ../../example/2_simple/deletes.cpp] [import ../../example/2_simple/prepared_statements.cpp] [import ../../example/2_simple/disable_tls.cpp] [import ../../example/2_simple/tls_certificate_verification.cpp] [import ../../example/2_simple/metadata.cpp] [import ../../example/2_simple/multi_function.cpp] [import ../../example/2_simple/callbacks.cpp] [import ../../example/2_simple/coroutines_cpp11.cpp] [import ../../example/2_simple/unix_socket.cpp] [import ../../example/2_simple/batch_inserts.cpp] [import ../../example/2_simple/batch_inserts_generic.cpp] [import ../../example/2_simple/dynamic_filters.cpp] [import ../../example/2_simple/patch_updates.cpp] [import ../../example/2_simple/source_script.cpp] [import ../../example/2_simple/pipeline.cpp] [import ../../example/3_advanced/http_server_cpp20/main.cpp] [import ../../example/3_advanced/http_server_cpp20/types.hpp] [import ../../example/3_advanced/http_server_cpp20/error.hpp] [import ../../example/3_advanced/http_server_cpp20/error.cpp] [import ../../example/3_advanced/http_server_cpp20/repository.hpp] [import ../../example/3_advanced/http_server_cpp20/repository.cpp] [import ../../example/3_advanced/http_server_cpp20/handle_request.hpp] [import ../../example/3_advanced/http_server_cpp20/handle_request.cpp] [import ../../example/3_advanced/http_server_cpp20/server.hpp] [import ../../example/3_advanced/http_server_cpp20/server.cpp] [import ../../example/3_advanced/http_server_cpp14_coroutines/main.cpp] [import ../../example/3_advanced/http_server_cpp14_coroutines/types.hpp] [import ../../example/3_advanced/http_server_cpp14_coroutines/repository.hpp] [import ../../example/3_advanced/http_server_cpp14_coroutines/repository.cpp] [import ../../example/3_advanced/http_server_cpp14_coroutines/handle_request.hpp] [import ../../example/3_advanced/http_server_cpp14_coroutines/handle_request.cpp] [import ../../example/3_advanced/http_server_cpp14_coroutines/server.hpp] [import ../../example/3_advanced/http_server_cpp14_coroutines/server.cpp] [import ../../test/integration/test/snippets/prepared_statements.cpp] [import ../../test/integration/test/snippets/sql_formatting_advanced.cpp] [import ../../test/integration/test/snippets/connection_establishment.cpp] [import ../../test/integration/test/snippets/charsets.cpp] [import ../../test/integration/test/snippets/multi_function.cpp] [import ../../test/integration/test/snippets/tutorials.cpp] [import ../../test/integration/test/snippets/text_queries.cpp] [import ../../test/integration/test/snippets/templated_connection.cpp] [import ../../test/integration/test/snippets/metadata.cpp] [import ../../test/integration/test/snippets/connection_pool.cpp] [import ../../test/integration/test/snippets/time_types.cpp] [import ../../test/integration/test/snippets/interfacing_sync_async.cpp] [import ../../test/integration/test/snippets/pipeline.cpp] [import ../../test/integration/test/snippets/dynamic_interface.cpp] [import ../../test/integration/test/snippets/overview.cpp] [import ../../test/integration/test/snippets/static_interface.cpp] [import ../../test/integration/test/snippets/multi_resultset.cpp] [import ../../test/integration/test/snippets/sql_formatting_advanced_2.cpp] [/ AUTOGENERATED IMPORTS END ] [import ../../test/integration/include/test_integration/snippets/describe.hpp] [include 01_intro.qbk] [include 02_integrating.qbk] [include 03_1_tutorial_sync.qbk] [include 03_2_tutorial_async.qbk] [include 03_3_tutorial_with_params.qbk] [include 03_4_tutorial_static_interface.qbk] [include 03_5_tutorial_updates_transactions.qbk] [include 03_6_tutorial_connection_pool.qbk] [include 03_7_tutorial_error_handling.qbk] [include 04_overview.qbk] [include 05_connection_establishment.qbk] [include 06_text_queries.qbk] [include 07_prepared_statements.qbk] [include 08_dynamic_interface.qbk] [include 09_static_interface.qbk] [include 10_multi_resultset.qbk] [include 11_multi_function.qbk] [include 12_connection_pool.qbk] [include 13_async.qbk] [include 13_1_interfacing_sync_async.qbk] [include 14_error_handling.qbk] [include 15_sql_formatting_advanced.qbk] [include 16_metadata.qbk] [include 17_charsets.qbk] [include 18_time_types.qbk] [include 19_templated_connection.qbk] [include 20_pipeline.qbk] [include 20_1_benchmarks.qbk] [include 21_examples.qbk] [section:ref Reference] [xinclude helpers/quickref.xml] [block''''''] [include reference.qbk] [include helpers/ExecutionRequest.qbk] [include helpers/ExecutionStateType.qbk] [include helpers/FieldViewFwdIterator.qbk] [include helpers/Formattable.qbk] [include helpers/OutputString.qbk] [include helpers/ResultsType.qbk] [include helpers/SocketStream.qbk] [include helpers/StaticRow.qbk] [include helpers/Stream.qbk] [include helpers/WritableFieldTuple.qbk] [block''''''] [endsect] ================================================ FILE: doc/qbk/01_intro.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:intro Introduction] [nochunk] __Self__ is a C++11 client for the __Mysql__ and [@https://mariadb.com/ MariaDB] database servers, based on __Asio__. [heading Motivation] MySQL and MariaDB are widespread SQL database servers. MySQL clients connect to the server in order to issue SQL queries. For this purpose, MySQL employs a dedicated protocol. __Self__ is an implementation of the client side of this protocol. This library is a full implementation of the [@https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_PROTOCOL.html MySQL client/server protocol]. It aims to expose the protocol primitives in an efficient but easy-to-use way. It is similar in scope to the official [@https://dev.mysql.com/doc/c-api/8.0/en/ libmysqlclient], but interoperable with Asio, safer and more expressive. Note that __Self__ [*does not use libmysqlclient]: it's a full implementation of the MySQL protocol, which makes it natively compatible with Asio. This library is relatively low level. It gives you access to text SQL queries and prepared statements. Don't expect an ORM. [link mysql.overview This section] presents a quick tour over the main library capabilities. The design goals of this library are: * [*Interoperability with Asio]: this library employs the same principles as __Asio__ and __Beast__. Users of any of these libraries will immediately understand Boost.MySQL, and will have it easy to integrate it in their programs. * [*Basis for further abstraction]: it allows efficient access to the MySQL client/server protocol so it can be used by higher level components as a building block. Do a single thing and do it well. * [*Efficiency]. * [*Ease of use]: the MySQL protocol is full of pitfalls. We believe in simplicity. While retaining control over the important parts, the library hides as much complexity from the protocol as possible. Non-goals: * [*Being an ORM]. * [*Being a SQL query generator]. The library doesn't focus on utilities to generate queries. Don't expect syntax sugar like `table("orders").select(["id", "quantity"]).where("id", 42)`. * [*Portability to other SQL databases]. This library focuses on MySQL. It won't work with Postgres or SQLite. [heading When to use] If any of the following statements is true, you may consider using __Self__: * Your application uses __Asio__ and you need to access a MySQL server. * You need asynchronous access to a MySQL server from a C++ application. * You need efficient access to a MySQL server from a C++ application. * You need a BSL-licensed library to access your MySQL server. * You are writing a higher-level database access library, like an ORM. Use cases may include web servers, ETL processes and IoT systems. It may not be a good fit for you if: * You only need synchronous access to a MySQL server and efficiency doesn't matter to your application. The official client libraries may be better suited for you, in this case. * You need homogeneous SQL access to different SQL databases (and not only MySQL access). You may find more value in using [@https://github.com/rbock/sqlpp11 sqlpp11] or a similar wrapper library. [heading Tested compilers and systems] Boost.MySQL is tested under the following compilers: * gcc 5.4 to gcc 15.0 (Linux) * clang 4 to clang 20.0 (Linux) * Apple clang 14.0 (OSX) * MSVC 14.1 - Visual Studio 2017 (Windows) * MSVC 14.2 - Visual Studio 2019 (Windows) * MSVC 14.3 - Visual Studio 2022 (Windows) And with the following RDBMS systems: * MySQL v5.7.41. * MySQL v8.4.1. * MariaDB v11.4.2. [heading Acknowledgements] I would like to specially acknowledge [@https://github.com/madmongo1 Richard Hodges] (hodges.r@gmail.com) for his invaluable technical guidance during development. Thanks also to Christian Mazakas for his ideas in early stages, and to [@https://github.com/klemens-morgenstern Klemens Morgenstern] and and [@https://github.com/vinniefalco Vinnie Falco] for his technical advice. Without you, this library would not exist. Finally, thanks to [@https://github.com/chriskohlhoff Christopher Kohlhoff] for his awesome __Asio__ library, and to [@https://github.com/HowardHinnant Howard Hinnant] for his date algorithms, shamelessly copied in this lib. [endsect] [/intro] ================================================ FILE: doc/qbk/02_integrating.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:integrating Integrating Boost.MySQL into your project] [nochunk] [note [*Breaking change in Boost 1.85]: the compiled library Boost.Charconv is now required. If you're upgrading and getting linker errors, link your executable to the `Boost::charconv` CMake target. ] [section:header_only Header-only mode] The easiest way to start using the library is header-only mode (the default). You will need the following: * A C++11 compiler (like gcc >=5.4, clang >=3.6, or Visual Studio 2017 or higher). * The Boost headers and Boost.Charconv. You can obtain them following the official installation instructions for [@boost:/more/getting_started/unix-variants.html UNIX-like systems] and for [@boost:/more/getting_started/windows.html Windows], or from a package manager. Note that Boost.MySQL does not work with the standalone version of __Asio__. * The OpenSSL headers and libraries. We recommend using your system package manager to obtain them. * CMake. Use the following `CMakeLists.txt`, replacing `main.cpp` with your project's source files: [!teletype] ``` project(boost_mysql_example LANGUAGES CXX) find_package(Boost REQUIRED COMPONENTS charconv) find_package(Threads REQUIRED) find_package(OpenSSL REQUIRED) add_executable(main main.cpp) target_link_libraries(main PRIVATE Boost::charconv Threads::Threads OpenSSL::Crypto OpenSSL::SSL) ``` [note `Boost::charconv` is only available in Boost 1.85 and higher. If you're using an older version, use the `Boost::headers` target, instead. ] If you're happy with header-only mode, have a look at [link mysql.tutorial_sync the first tutorial] or [link mysql.examples any of the examples] to learn how to use the library. [endsect] [section:separate_compilation Separate compilation mode] Header-only mode is simple but can make your project's build times high. If this is the case, we recommend switching to separate compilation mode. To use it, you must add the following to exactly one `.cpp` file: ``` // Contents of boost_mysql.cpp // This header file contains all Boost.MySQL implementations. // It should be included in exactly one .cpp file. // All code using Boost.MySQL in separate build mode, including this file, // should define BOOST_MYSQL_SEPARATE_COMPILATION. #include ``` All of your code, including this `.cpp` file, should define the `BOOST_MYSQL_SEPARATE_COMPILATION` macro. This is what your `CMakeLists.txt` could look like: [!teletype] ``` project(boost_mysql_example LANGUAGES CXX) find_package(Boost REQUIRED COMPONENTS charconv) find_package(Threads REQUIRED) find_package(OpenSSL REQUIRED) add_executable( main # Contains Boost.MySQL sources via #include boost_mysql.cpp # List any other .cpp your exe has here main.cpp ) target_link_libraries(main PRIVATE Boost::charconv Threads::Threads OpenSSL::Crypto OpenSSL::SSL) # We need to define BOOST_MYSQL_SEPARATE_COMPILATION in any code using Boost.MySQL in separate-build mode target_compile_definitions(main PRIVATE BOOST_MYSQL_SEPARATE_COMPILATION) ``` Boost.Asio and Boost.Beast offer a very similar separate compilation mode. If you're using them together with Boost.MySQL, you may consider enabling separate compilation for them, too. [endsect] [endsect] ================================================ FILE: doc/qbk/03_1_tutorial_sync.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:tutorial_sync Tutorial 1: hello world!] In this first tutorial, we will write a simple program to demonstrate the basic concepts. We will connect to the server and issue the query `SELECT "Hello World!"`. To run this tutorial, you need a running MySQL server listening in localhost on port 3306 (the default one). You should have the credentials of a valid MySQL user (username and password). No further setup is needed. The full program listing for this tutorial can be found [link mysql.examples.tutorial_sync here]. We will follow these steps: # Create a connection object. # Establish a session with the server. # Issue the query. # Use the rows generated by the query. # Close the connection. This tutorial uses synchronous functions with exceptions, as they're easy to use. In subsequent tutorials, we will learn how to use asynchronous functions, which are more versatile. [heading Namespace conventions] All functions and classes reside within the `boost::mysql` namespace. To reduce verbosity, all examples and code samples use the following namespace aliases: [tutorial_sync_namespaces] [heading Connection object] Like most Asio-based applications, we need to create a [asioreflink io_context io_context] object before anything else. An `io_context` is an execution context: it contains an event loop, file descriptor states, timers and other items required to perform I/O. Most applications should only create a single `io_context`, even when multiple MySQL connections are needed. We then create an [reflink any_connection], which represents a single connection to a MySQL server: [tutorial_sync_connection] [heading Connecting to the server] [refmem any_connection connect] establishes a client session with the server. It takes a [reflink connect_params] object with the required information to establish a session: [tutorial_sync_connect] [heading Issuing the SQL query] [refmem any_connection execute] accepts a string containing the SQL query to run and sends it to the server for execution. It returns a [reflink results] object, containing the rows returned by the query: [tutorial_sync_query] [heading Obtaining the results] [reflink results] is a class that holds the result of a query in memory. To obtain the value we selected, we can write: [tutorial_sync_results] Let's break this into steps: * [refmem results rows] returns all the rows that this object contains. It returns a [reflink rows_view], which is a 2D matrix-like structure. * `result.rows().at(0)` returns the first row, represented as a [reflink row_view]. * `result.rows().at(0).at(0)` returns the first field in the first row. This is a [reflink field_view], a variant-like class that can hold any type allowed in MySQL. * The obtained `field_view` is streamed to `std::cout`. [heading Closing the connection] Once we are done with the connection, we can close it by calling [refmem any_connection close]. Note that this will send a final quit packet to the MySQL server to notify we are closing the connection, and thus may fail. [tutorial_sync_close] [heading Next steps] Full program listing for this tutorial is [link mysql.examples.tutorial_sync here]. You can now proceed to [link mysql.tutorial_async the next tutorial]. [endsect] ================================================ FILE: doc/qbk/03_2_tutorial_async.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:tutorial_async Tutorial 2: going async with C++20 coroutines] In the [link mysql.tutorial_sync previous tutorial] we used synchronous functions. They are simple, but have a number of limitations: * They aren't as versatile as async functions. For example, there is no way to set a timeout to a sync operation. * They don't scale well. Since sync functions block the calling thread until they complete, you need to create OS threads to achieve parallelism. This doesn't scale well and leads to the inherent complexities of multi-threaded programs. * Some classes (like [reflink connection_pool]) only offer an async interface. For this reason, we recommend to always use async functions. All Asio-compatible libraries (including this one) allow async programming using a variety of styles. In this chapter, we will explain how to use C++20 coroutines because they are the simplest to use. [note Still not using C++20? Don't worry, you can use [link mysql.examples.coroutines_cpp11 stackful coroutines] and [link mysql.examples.callbacks callbacks] even in C++11. ] [heading What is a coroutine?] Roughly speaking, it's a function that can suspend and resume, keeping local variables alive in the process. Suspension happens when reaching a `co_await` expression. These usually appear when the program performs an I/O operation. When an expression like this is encountered, the following happens: # The coroutine initiates the I/O operation. # The coroutine suspends, passing control back to the `io_context` (that is, the event loop). # While the I/O operation is in progress, the `io_context` may run other operations, like other coroutines. # When the I/O operation completes, the `io_context` resumes the coroutine immediately after the `co_await` expression. [heading Transforming sync code into coroutines] Recall the following code from our previous tutorial: [tutorial_sync_main] To transform this code into a coroutine, we need to: * Extract it to a separate function returning `boost::asio::awaitable`. * Replace sync functions (like [refmem any_connection connect]) by async ones (like [refmem any_connection async_connect]). * Place a `co_await` operator in front of each I/O operation. Doing this, we have: [tutorial_async_coro] Note that the coroutine doesn't create or return explicitly any `boost::asio::awaitable` object - this is handled by the compiler. The return type actually marks the function as being a coroutine. `void` here means that the coroutine doesn't return anything. If any of the above I/O operations fail, an exception is thrown. You can prevent this by [link mysql.overview.errors using `asio::redirect_error`]. [heading Running our coroutine] As in the previous tutorial, we first need to create an `io_context` and a connection: [tutorial_async_connection] To run a coroutine, use [asioreflink co_spawn co_spawn]: [tutorial_async_co_spawn] Note that this will only schedule the coroutine. To actually run it, we need to call `io_context::run`. This will block the calling thread until all the scheduled coroutines and I/O operations complete: [tutorial_async_run] [heading Next steps] Full program listing for this tutorial is [link mysql.examples.tutorial_async here]. You can now proceed to [link mysql.tutorial_with_params the next tutorial]. [endsect] ================================================ FILE: doc/qbk/03_3_tutorial_with_params.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:tutorial_with_params Tutorial 3: queries with parameters] Until now, our SQL queries were hard-coded string literals. However, most real-world use cases involve running queries containing user-supplied parameters. In this tutorial, we will be using an employee database. You can obtain this sample database by sourcing the `example/db_setup.sql` file in Boost.MySQL source code repository. The employee table is defined as: [!teletype] ``` CREATE TABLE employee( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, ... -- other fields not relevant for us ); ``` We will write a program that retrieves an employee by ID and prints their full name. The employee ID will be supplied by the user as a command line argument. In more realistic examples, you may get it from a file or HTTP request. [heading Avoiding SQL injection] We need to build a query like the following: [!teletype] ``` SELECT first_name, last_name FROM employee WHERE id = ``` Replacing `` by the value passed by the user. Since we don't control the employee ID, we must consider it [*untrusted]. We must [*never use raw string concatenation] to build queries. Otherwise, malicious values can cause SQL injection vulnerabilities, which are extremely severe. Boost.MySQL offers two options to deal with this: # Compose the query dynamically in the client, using specialized tools to avoid SQL injection. This option is versatile, simple and appropriate for general use. # Perform parameter substitution server side using [link mysql.prepared_statements prepared statements]. This is more complex and better suited for cases involving lots of numeric data or executing same query repeatedly. In this tutorial, we will use client-side generation (termed [link mysql.text_queries client-side SQL formatting] throughout the documentation). [heading Using with_params] [refmem any_connection async_execute] can also deal with queries with parameters. We need to replace the string literal by a call to [reflink with_params], passing a query template and the actual values of the parameters: [tutorial_with_params_execute] The query template uses a syntax similar to `std::format`. You can use numbers, strings, dates, times and many other types as parameters. More information about client-side SQL formatting is available in [link mysql.text_queries this page]. [heading Using the retrieved rows] Our query might return either one row (if an employee is found) or none (if no employee with the given ID exists). Accounting for this: [tutorial_with_params_results] [heading Connecting with database] If you've run `example/db_setup.sql`, the `employee` table exists within the `boost_mysql_examples` database. To use this table without qualification, we need to specify a database name when connecting. This is achieved by setting [refmem connect_params database]: [tutorial_with_params_connect_params] [heading Creating the connection inside the coroutine] Since we're connecting and closing the connection in our coroutine, it makes sense to make it a local variable, instead of passing it as parameter. Recall that we need a reference to an execution context (i.e. `io_context`) to build a connection. We could pass the `io_context` as a parameter to our coroutine, but there's a simpler way: coroutines already hold a reference to where they are executing: [tutorial_with_params_connection] The expression `co_await asio::this_coro::executor` retrieves the [*executor] that our coroutine is using. An executor is a lightweight handle to an execution context, and can be used to create our connection. [note `co_await asio::this_coro::executor` does not perform any I/O. It only retrieves the current coroutine's executor. ] [heading Wrapping up] With all these changes, this is how our coroutine looks like: [tutorial_with_params_coroutine] Full program listing for this tutorial is [link mysql.examples.tutorial_with_params here]. You can now proceed to [link mysql.tutorial_static_interface the next tutorial]. [endsect] ================================================ FILE: doc/qbk/03_4_tutorial_static_interface.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:tutorial_static_interface Tutorial 4: the static interface] Until now we've read the rows generated by our queries into [reflink results] objects. As we've seen, `results` is a 2D structure that contains variant-like values. Throughout the documentation, these variant-based APIs are called [link mysql.dynamic_interface the dynamic interface]. [heading Dynamic interface limitations] Working with variant-like objects can be cumbersome. Recall the following lines from our previous tutorial, where we used the retrieved rows: [tutorial_with_params_results] An employee is represented here by a [reflink row_view], which is a collection of [reflink field_view] objects. `field_view` is a variant-like type that can represent all the types supported by MySQL. Since `field_view` supports streaming, this code doesn't require any casting. However, consider refactoring our code to split the printing logic to a separate function: [tutorial_static_fn] Looking at our database schema, we know that both values are strings. We can use [refmem field_view as_string] to perform the casts: [tutorial_static_casts] While this code works, it can create maintenance problems: * We retrieve fields by position. That is, we know that `employee.at(0)` holds the employee's `first_name`. However, if we refactor our query, we might forget updating the indices. * Following a similar reasoning, the casts are error-prone. * Both `at` and `as_string` throw on error. Attempting to avoid exceptions while still performing the required safety checks quickly becomes unmanageable. If we know the types returned by our queries at compile time, we can use the static interface, instead. This interface can parse the rows returned by a query into instances of a C++ struct defined by us. [note The static interface requires C++14 or later. ] [heading Using static_results with Boost.Pfr] In C++20 and later, we can use __Pfr__ and the [reflink static_results] class to parse the rows. The first step is to define a plain C++ struct with the fields we expect in our query: [tutorial_static_struct] We now replace the [reflink results] object by a [reflink static_results]. The marker type [reflink pfr_by_name] indicates Boot.MySQL that it should use Boost.Pfr for reflection. [tutorial_static_execute] Using the retrieved data is now much easier, since [refmem static_results rows] returns a `span`: [tutorial_static_results] When using the static interface, `async_execute` will perform a set of checks on the query results to ensure compatibility between the C++ types and what MySQL returns. These checks aim to discover potential problems as early as possible, and are called [link mysql.static_interface.meta_checks metadata checks]. [heading Using the static interface in C++14] C++20 makes it possible to use Boost.Pfr as described here, which is the easiest option. Boost.MySQL also supports __Describe__ and `std::tuple`'s, which can be used in C++14. The mechanics are quite similar to what's been explained here. [heading Wrapping up] [link mysql.static_interface This section] contains more information about the static interface. Full program listing for this tutorial is [link mysql.examples.tutorial_static_interface here]. You can now proceed to [link mysql.tutorial_updates_transactions the next tutorial]. [endsect] ================================================ FILE: doc/qbk/03_5_tutorial_updates_transactions.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:tutorial_updates_transactions Tutorial 5: UPDATEs, transactions and semicolon-separated queries] All the previous tutorials have only used `SELECT` statements, but Boost.MySQL is not limited to them. Using [refmemunq any_connection async_execute] you can run any SQL statement supported by MySQL. In this tutorial, we will write a program that changes the first name of an employee, given their ID, and prints the updated employee details. We will use an `UPDATE` and transaction management statements. `INSERT` and `DELETE` statements have similar mechanics. [heading A simple UPDATE] We can use the same tools and functions as in previous tutorials: [tutorial_updates_transactions_update] By default, auto-commit is enabled, meaning that when `async_execute` returns, the `UPDATE` is visible to other client connections. [heading Checking that the UPDATE took effect] The above query will succeed even if there was no employee with the given ID. We would like to retrieve the updated employee details on success, and emit a useful error message if no employee was matched. We may be tempted to use [refmem results affected_rows] at first, but this doesn't convey the information we're looking for: a row may be matched but not affected. For example, if you try to set `first_name` to the same value it already has, MySQL will count the row as a matched, but not affected. MySQL does not support the `UPDATE ... RETURNING` syntax, so we will have to retrieve the employee manually after updating it. We can add the following after our `UPDATE`: [tutorial_updates_transactions_select] However, the code above contains a race condition. Imagine the following situation: * The `UPDATE` is issued. No employee is matched. * Before our program sends the `SELECT` query, a different program inserts an employee with the ID that we're trying to update. * Our program runs the `SELECT` statement and retrieves the newly inserted row. To our program, it looks like we succeeded performing the update, when we really didn't. Depending on the nature of our program, this may or may not have serious consequences, but it's something we should avoid. [heading Avoiding the race condition with a transaction block] We can fix the race condition using transactions. In MySQL, a transaction block is opened with `START TRANSACTION`. Subsequent statements will belong to the transaction block, until the transaction either commits or is rolled back. A `COMMIT` statement commits the transaction. A rollback happens if the connection that initiated the transaction closes or an explicit `ROLLBACK` statement is used. We will enclose our `UPDATE` and `SELECT` statements in a transaction block. This will ensure that the `SELECT` will get the updated row, if any: [tutorial_updates_transactions_txn] [heading Using multi-queries] While the code we've written is correct, it's not very performant. We're incurring in 4 round-trips to the server, when our queries don't depend on the result of previous ones. The round-trips occur within a transaction block, which causes certain database rows to be locked, increasing contention. We can improve the situation by running our four statements in a single batch. Multi-queries are a protocol feature that lets you execute several queries at once. Individual queries must be separated by semicolons. Multi-queries are disabled by default. To enable them, set [refmem connect_params multi_queries] to `true` before connecting: [tutorial_updates_transactions_connect] Multi-queries can be composed an executed using the same functions we've been using: [tutorial_updates_transactions_multi_queries] Accessing the results is slightly different. MySQL returns 4 resultsets, one for each query. In Boost.MySQL, this operation is said to be [link mysql.multi_resultset multi-resultset]. [reflink results] can actually store more than one resultset. [refmem results rows] actually accesses the rows in the first resultset, because it's the most common use case. We want to get the rows retrieved by the `SELECT` statement, which corresponds to the third resultset. [refmem results at] returns a [reflink resultset_view] containing data for the requested resultset: [tutorial_updates_transactions_dynamic_results] [heading Using manual indices in with_params] Repeating `employee_id` in the parameter list passed to `with_params` violates the DRY principle. As with `std::format`, we can refer to a format argument more than once by using manual indices: [tutorial_updates_transactions_manual_indices] [heading Using the static interface with multi-resultset] Finally, we can rewrite our code to use the static interface so it's safer. In multi-resultset scenarios, we can pass as many row types to [reflink static_results] as resultsets we expect. We can use the empty tuple (`std::tuple<>`) as a row type for operations that don't return rows, like the `UPDATE`. Our code becomes: [tutorial_updates_transactions_static] [heading Wrapping up] Full program listing for this tutorial is [link mysql.examples.tutorial_updates_transactions here]. You can now proceed to [link mysql.tutorial_connection_pool the next tutorial]. [endsect] ================================================ FILE: doc/qbk/03_6_tutorial_connection_pool.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:tutorial_connection_pool Tutorial 6: Connection pools] All our programs until now have used one-shot connections. They also didn't feature any fault tolerance: if the server is unavailable, our program throws an exception and terminates. Most real world scenarios require long-lived, reliable connections, instead. In this tutorial, we will implement a server for a simple request-reply protocol. The protocol allows clients to retrieve the full name of an employee given their ID. We will use [reflink connection_pool] to maintain a set of healthy connections that we can use when a client connects to our server. [heading The protocol] The protocol is TCP based, and can be described as follows: * After connecting, the client sends a message containing the employee ID, encoded as an 8-byte, big-endian integer. * The server replies with a string containing the employee full name, or "NOT_FOUND", if the ID doesn't match any employee. * The connection is closed after that. This protocol is intentionally overly simplistic, and shouldn't be used in production. See our [link mysql.examples.http_server_cpp20 HTTP examples] for more advanced use cases. [heading Creating a connection pool] [reflink connection_pool] is an I/O object that contains [reflink any_connection] objects, and can be constructed from an execution context and a [reflink pool_params] config struct: [tutorial_connection_pool_create] A single connection pool is usually created per application. [refmem connection_pool async_run] should be called once per pool: [tutorial_connection_pool_run] [heading Using pooled connections] Let's first write a coroutine that encapsulates database access. Given an employee ID, it should return the string to be sent as response to the client. Don't worry about error handling for now - we will take care of it in the next tutorial. When using a pool, we don't need to explicitly create, connect or close connections. Instead, we use [refmem connection_pool async_get_connection] to obtain them from the pool: [tutorial_connection_pool_get_connection] [reflink pooled_connection] is a wrapper around [reflink any_connection], with some pool-specific additions. We can use it like a regular connection: [tutorial_connection_pool_use] When a [reflink pooled_connection] is destroyed, the connection is returned to the pool. The underlying connection will be cleaned up using a lightweight session reset mechanism and recycled. Subsequent [refmemunq connection_pool async_get_connection] calls may retrieve the same connection. This improves efficiency, since session establishment is costly. [refmemunq connection_pool async_get_connection] waits for a client connection to become available before completing. If the server is unavailable or credentials are invalid, it may wait indefinitely. This is a problem for both development and production. We can solve this by using [asioreflink cancel_after cancel_after], which allows setting timeouts to async operations: [tutorial_connection_pool_get_connection_timeout] Don't worry if you don't fully understand how this works. We will go into more detail on [asioreflink cancel_after cancel_after], cancellations and completion tokens in the next tutorial. Putting all pieces together, our coroutine becomes: [tutorial_connection_pool_db] [heading Handling a client session] Let's now build a function that handles a client sessions, invoking the database access logic in the process: [tutorial_connection_pool_session] [heading Listening for connections] We now need logic to accept incoming TCP connections. We will use an `asio::ip::tcp::acceptor` object to accomplish it, listening for connections in a loop until the server is stopped: [tutorial_connection_pool_listener] [heading Waiting for signals] Finally, we need a way to stop our program. We will use an `asio::signal_set` object to catch signals, and call `io_context::stop` when Ctrl-C is pressed: [tutorial_connection_pool_signals] Putting all these pieces together, our main program becomes: [tutorial_connection_pool_main] [heading Wrapping up] Full program listing for this tutorial is [link mysql.examples.tutorial_connection_pool here]. For simplicity, we've left error handling out of this tutorial. This is usually very important in a server like the one we've written, and is the topic of our [link mysql.tutorial_error_handling next tutorial]. [endsect] ================================================ FILE: doc/qbk/03_7_tutorial_error_handling.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:tutorial_error_handling Tutorial 7: Error handling] The [link mysql.tutorial_connection_pool previous tutorial] did not include any error handling. When an error is encountered while talking to the DB or the client, an exception is thrown and the program terminates. This is undesirable in server programs like the one we're writing. To add error handling, we can just add try/catch blocks to prevent exception propagation. However, many code bases discourage the use of exceptions for non-exceptional circumstances, like I/O errors. In this tutorial, we will learn how to manage I/O errors without exceptions by using [asioreflink as_tuple as_tuple] and error codes. [heading Error handling strategy] There are two kind of I/O errors that our program can encounter: * Reading and writing to the client may fail. This can happen if the client program is faulty or a network error happens. In this case, we should log the problem and close the connection. * Talking to the database may fail. This can happen if [refmemunq connection_pool async_get_connection] is cancelled because of a timeout. In this case, we will return a special string (`"ERROR"`) to the client, signalling that we can't fulfill the request, and log the problem. Additionally, we will modify how we use `asio::cancel_after` to make the system more reliable. [heading:completion_token Completion tokens] Before proceeding, we need to understand what a completion token is. The concepts in this section are not specific to Boost.MySQL, but apply to Asio and all Asio-compatible libraries. Since Asio docs can be terse, we explain them here to facilitate the reader. All asynchronous operations accept an optional, last parameter specifying what to do when the operation completes. This last parameter is the operation's [@boost:/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.completion_tokens_and_handlers completion token]. Callbacks are valid completion tokens. Taking [refmemunq connection_pool async_get_connection] as example, the following is valid: [tutorial_error_handling_callbacks] We have already been using this when creating coroutines. `asio::co_spawn` is also an async operation, and the callback we pass as its last parameter is the completion token. You might consider using callbacks if your compiler doesn't support coroutines, or just by personal preference. [link mysql.examples.callbacks This example] demonstrates how to use them. If you don't specify a completion token, the operation's [*default completion token] will be used. This is usually `asio::deferred` or `mysql::with_diagnostics(asio::deferred)` [footnote [reflink with_diagnostics] is an adapter completion token that enhances thrown exceptions with a diagnostic string supplied by the server. `mysql::with_diagnostics(asio::deferred)` is otherwise equivalent to `asio::deferred`.]. These tokens transform asynchronous operations into awaitables, so we can use them in C++20 coroutines. The default completion token for [refmemunq connection_pool async_get_connection] is `mysql::with_diagnostics(asio::deferred)`. This means that the following two are equivalent: [tutorial_error_handling_default_tokens] Completion tokens are generic: once you learn how to use one, you can use it with any Asio-compliant async operation. This includes all functions in Boost.Asio, Boost.MySQL, Boost.Beast and Boost.Redis. We say that operations in these libraries are compliant with Asio's universal async model. Writing these is hard, but they're easy to use! [heading Adapter completion tokens] Some tokens don't fully specify what to do when the operation completes, but rather modify some aspect of how the operation executes. They wrap (or adapt) other completion tokens. The underlying token determines what to do when the operation completes. [asioreflink cancel_after cancel_after] is an adapter token. It modifies how an operation executes by setting a timeout, but it doesn't specify what to do on completion. Adapter tokens can be passed an optional completion token as the last argument. If the token is omitted, the default one will be used. Continuing with our example: [tutorial_error_handling_adapter_tokens] [heading Handler signature and exceptions] Each async operation has an associated handler signature. We can find these signatures in the documentation for each operation. The handler signature is the prototype that a callback function passed as completion token would need to have to be compatible with the operation. The handler signature for [refmemunq connection_pool async_get_connection] is `void(boost::system::error_code, mysql::pooled_connection)`. However, when we invoke `co_await` on the awaitable returned by `async_get_connection`, we don't get any `error_code`. This is because `co_await` inspects the handler signature at compile-time, looking for an `error_code` as first parameter. If it finds it, `co_await` will remove it from the argument list, returning only the `pooled_connection`. At runtime, the error code is checked. If the code indicates a failure, an exception is thrown. This mechanism is important to understand how `as_tuple` works. [heading asio::as_tuple] [asioreflink as_tuple as_tuple] is another adapter completion token that can be used to prevent exceptions. It modifies the operation's handler signature, packing all arguments into a `std::tuple`. This inhibits the automatic error code checks explained in the previous section, thus preventing exceptions on I/O failure. Continuing with our example: [tutorial_error_handling_as_tuple] In practice, it's usually better to use structured bindings: [tutorial_error_handling_as_tuple_structured_bindings] All the properties of adapter completion tokens apply: [tutorial_error_handling_as_tuple_default_tokens] Adapter tokens can be combined. To apply a timeout to the operation while avoiding exceptions, you can use: [tutorial_error_handling_as_tuple_cancel_after] [heading Using asio::as_tuple for database code] Let's apply [asioreflink as_tuple as_tuple] to our database logic. We will remove timeouts for now - we will add them back later. [tutorial_error_handling_db_nodiag] [heading Diagnostics objects] While what we wrote works, it can be improved. When a database operation fails, the server may supply an error string with information about what went wrong. Boost.MySQL may also generate such strings in certain cases. We get this automatically when using exceptions. Thanks to [reflink with_diagnostics] and default completion tokens, the library throws [reflink error_with_diagnostics] objects, which inherit from `boost::system::system_error` and have a [refmemunq error_with_diagnostics get_diagnostics] member. When using error codes, we need to handle diagnostics manually. All functions in Boost.MySQL are overloaded to accept a [reflink diagnostics] output parameter. It will be populated with extra information in case of error. Let's update our code to use diagnostics: [tutorial_error_handling_db] We also need to write the function to log errors: [tutorial_error_handling_log_error] [refmem diagnostics client_message] and [refmem diagnostics server_message] differ in their origin. Client messages never contain user-supplied input, and can always be used safely. Server messages may contain user input, and should be treated with more caution (logging them is fine). [heading Using asio::as_tuple with client reads and writes] Since `asio::read` and `asio::write` are compliant async operations, we can use [asioreflink as_tuple as_tuple] with them, too: [tutorial_error_handling_session_as_tuple] [heading Timeouts] Our session handler has three logical steps: * Read a request from the client. * Access the database. * Write the response to the client. Each of these steps may take long to complete. We will set a separate timeout to each one. Client reads and writes are the easiest ones to handle - we just need to combine `as_tuple` and `cancel_after`: [tutorial_error_handling_read_timeout] The database logic is more involved. Ideally, we would like to set a timeout to the overall database access operation, rather than to individual steps. However, a `co_await` expression isn't an async operation, and can't be passed a completion token. We can fix this by replacing plain `co_await` by `asio::co_spawn`, which accepts a completion token: [tutorial_error_handling_db_timeout] With these modifications, the session handler becomes: [tutorial_error_handling_session] With these modifications, our server is ready! [heading Wrapping up] Full program listing for this tutorial is [link mysql.examples.tutorial_error_handling here]. This concludes our tutorial series. You can now look at the [link mysql.overview overview section] to learn more about the library features, or to the [link mysql.examples example section] if you prefer to learn by doing. [endsect] ================================================ FILE: doc/qbk/04_overview.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:overview Overview] [nochunk] This section briefly explains the library main classes and functions, and how to use them. Boost.MySQL exposes sync and async functions implementing functionality involving I/O. As explained [link mysql.tutorial_async in the second tutorial], it's advisable to use async functions when possible, because they are more flexible. Boost.MySQL supports the Boost.Asio universal async model. This means that a variety of async programming paradigms can be used with the library, including callbacks, stackful coroutines and C++20 coroutines. We will use C++20 coroutines throughout the document because they're easy to use. [note Still not using C++20? Don't worry, you can use [link mysql.examples.coroutines_cpp11 stackful coroutines] and [link mysql.examples.callbacks callbacks] even in C++11. ] [section Connection establishment] [reflink any_connection] is the most primitive I/O object in the library. It can establish and close connections, run queries and manage prepared statements. Like most I/O objects, `any_connection` can be constructed from an execution context: [tutorial_sync_connection] `any_connection` is named like this for historic reasons: a templated connection class came before it. We currently recommend using `any_connection` for new code because it's simpler and more powerful. The MySQL client/server protocol is session-oriented. Before anything else, you must perform session establishment by calling [refmem any_connection async_connect]: [overview_connect] [refmemunq any_connection async_connect] performs the hostname resolution, TCP session establishment, TLS handshake and MySQL handshake. By default, TLS is used if the server supports it. You can configure a number of parameters here, including the database to use, TLS options and buffer sizes. See [link mysql.connection_establishment this section] for more info. Boost.MySQL also supports [link mysql.connection_establishment.unix using UNIX-domain sockets]. To cleanly terminate a connection, use [refmemunq any_connection async_close]. This sends a packet informing of the imminent close and shuts down TLS. The connection destructor will also close the socket, so no leak occurs. [endsect] [section Running queries] The simplest way to run a SQL query is using [refmem any_connection async_execute]. You can execute queries by passing a string as first parameter: [overview_text_query] Most queries contain user-supplied input. [*Never use raw string concatenation] to build queries, since this is vulnerable to SQL injection. Boost.MySQL provides two interfaces to run queries with parameters: [table [ [Feature] [Code] ] [ [ [link mysql.text_queries Client-side SQL formatting]: * Securely expands queries client-side. * Text-based protocol. * Adequate for general use. ] [ [overview_with_params] ] ] [ [ [link mysql.prepared_statements Prepared statements]: * Parsed and executed in two different operations. * Binary protocol. * Adequate when running a query several times or retrieving lots of numeric data. ] [ [overview_statement] ] ] ] By default, we recommend using [reflink with_params] because it's simpler and entails less round-trips to the server. See [link mysql.text_queries.comparison the comparison section] for more info. Client-side SQL formatting can also be used to [link mysql.sql_formatting_advanced.expand expand queries] without sending them to the server. [endsect] [section The dynamic and the static interfaces] In MySQL, a ['resultset] refers to the results generated by a SQL query. A resultset is composed of rows, [link mysql.meta metadata] and additional info, like the last insert ID. There are two different interfaces to access resultsets. You can use the [reflink results] class to access rows using a dynamically-typed interface, using variant-like objects to represent values retrieved from the server. On other other hand, [reflink static_results] is statically-typed. You specify the shape of your rows at compile-time, and the library will parse the retrieved values for you into the types you provide. You can use almost every feature in this library (including text queries and prepared statements) with both interfaces. For example, given the following table : [/ (TODO: this code is duplicated.) ] [!teletype] ``` CREATE TABLE employee( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, ... -- other fields not relevant for us ); ``` This is how you would access its contents using either of the interfaces: [table [ [Interface] [Description] [Example] ] [ [ Dynamic interface: [reflink results] ] [ * Variant based * Available in C++11 * [link mysql.dynamic_interface Learn more] ] [ [overview_ifaces_dynamic] ] ] [ [ Static interface: [reflink static_results] ] [ * Parses rows into your own types * Requires C++20 when using Boost.Pfr, C++14 when using Boost.Describe * [link mysql.static_interface Learn more] ] [ [overview_static_struct][br] [overview_ifaces_static] ] ] ] Prefer using the static interface when possible. [endsect] [section Running INSERT, UPDATE and DELETE statements] The same APIs explained above can be used for SQL statements that don't retrieve data: [overview_update] When performing INSERTs, you might find [refmem results last_insert_id] handy, which retrieves the last AUTO INCREMENT ID generated by the executed statement. See [link mysql.tutorial_updates_transactions our tutorial on UPDATEs and transactions] for more info. [endsect] [section:async Single outstanding async operation per connection] At any given point in time, an `any_connection` can only have a single async operation outstanding. In other words, connections implement no asynchronous locking or queueing, which keeps code simple and efficient. If you need to perform several operations in parallel, you can open more connections or use [reflink connection_pool]. Trying to run operations concurrently on a single connection is detected at runtime and generates a `client_errc::operation_in_progress` error: [overview_async_parallel] [endsect] [section:errors Error handling] An operation fails if a network error happens, a protocol violation is encountered, or the server reports an error. For instance, SQL syntax errors make `async_execute` fail. When the server reports an error, it provides a diagnostic string describing what happened. The [reflink diagnostics] class encapsulates this message. Some library functions generate diagnostics strings, too. Both the sync functions in [link mysql.tutorial_sync the first tutorial] and the coroutines in this exposition throw exceptions when they fail. The exception type is [reflink error_with_diagnostics], which inherits from `boost::system::system_error` and adds a [reflink diagnostics] object. Async functions use [reflink with_diagnostics], a completion token adapter, to transparently include diagnostics in exceptions. You can avoid exceptions when using coroutines with `asio::redirect_error`: [overview_no_exceptions] [reflink2 error_code mysql::error_code] is an alias for `boost::system::error_code`. [endsect] [section Multi-function operations] Until now, we've been using [refmemunq any_connection async_execute], which executes some SQL and reads all generated data into an in-memory object. Some use cases may not fit in this simple pattern. For example: * When reading a very big resultset, it may not be efficient (or even possible) to completely load it in memory. Reading rows in batches may be more adequate. * If rows contain very long `TEXT` or `BLOB` fields, it may not be adequate to copy these values from the network buffer into the `results` object. A view-based approach may be better. For these cases, we can break the execute operation into several steps, using a ['multi-function operation] (the term is coined by this library). This example reads an entire table in batches, which can be the case in an ETL process: [overview_multifn] [note Once you start a multi-function operation with [refmemunq any_connection async_start_execution], the server immediately sends all rows to the client. [*You must read all rows] before engaging in further operations. If you try to initiate an unrelated operation before reading all rows, you will get a `client_errc::engaged_in_multi_function` error. ] Multi-function operations are powerful but complex. Only use them when there is a strong reason to do so. Multi-function operations also work with the static interface. Please refer to [link mysql.multi_function this section] for more information on these operations. [endsect] [section Connection pools] Connection pooling is a technique where several long-lived connections are re-used for independent logical operations. When compared to establishing individual connections, it has the following benefits: * It provides better performance. Please consult [link mysql.connection_pool.benchmarks our benchmarks] for more info. * It simplifies connection management. The connection pool will establish sessions, perform retries and apply timeouts out of the box. This is how you can create a pool of connections: [connection_pool_create] [refmem connection_pool async_run] must be called exactly once per pool. This function takes care of actually keeping connections healthy. To retrieve a connection, use [refmem connection_pool async_get_connection]: [connection_pool_get_connection] For more info, see [link mysql.connection_pool this section]. [endsect] [endsect] ================================================ FILE: doc/qbk/05_connection_establishment.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:connection_establishment Connection establishment and termination] [nochunk] This section discusses several aspects regarding the creation, establishment and termination of client connections. [section Authentication] [refmem connect_params username] and [refmem connect_params password] contain the credentials used during authentication. The password is sent to the server either hashed or over a secure channel such as TLS, as mandated by the protocol. MySQL implements several authentication plugins that can be used to authenticate a user (see the [mysqllink pluggable-authentication.html pluggable authentication MySQL docs]). Each MySQL user is associated to a single authentication plugin, specified during using creation. Additionally, servers define a default authentication plugin (see [mysqllink server-system-variables.html#sysvar_authentication_policy `authentication_policy`] and [mysqllink server-system-variables.html#sysvar_default_authentication_plugin `default_authentication_plugin`]). The default plugin will be used for newly created users, and may affect how the handshake works. This library implements the two most common authentication plugins: * [mysqllink native-pluggable-authentication.html `mysql_native_password`]. Unless otherwise configured, this is the default plugin for MySQL 5.7 and MariaDB. It can be used over both TLS and plaintext connections. It sends the password hashed, salted by a nonce. * [mysqllink caching-sha2-pluggable-authentication.html `caching_sha2_password`]. This is the default plugin for MySQL 8.0+. This plugin used to require using TLS. Since Boost 1.89, this is no longer the case, and it can be used with plaintext connections, too. Multi-factor authentication is not yet supported. If you require support for a plugin not listed above or for MFA, please file a feature request against the GitHub repository. [note Servers configured with a default authentication plugin not implemented in Boost.MySQL are not supported, regardless of the actual plugin the concrete user employs. This limitation may be lifted in the future. ] [endsect] [section Connect with database] [refmem connect_params database] contains the database name to connect to. If you specify it, your connection will default to use that database, as if you had issued a __USE__ statement. You can leave it blank to select no database. You can always issue a __USE__ statement using [refmemunq any_connection async_execute] to select a different database after establishing the connection. [endsect] [section:tls TLS support] TLS encrypted connections are fully supported by Boost.MySQL. TCP connections established using [reflink any_connection] use TLS by default. [heading TLS handshake and termination] The TLS handshake is performed by [refmem any_connection async_connect]. This contrasts with libraries like __Beast__, where the TLS handshake must be explicitly invoked by the user. We selected this approach because the TLS handshake is part of the MySQL protocol's handshake: the client and server exchange several unencrypted messages, then perform the TLS handshake and continue exchanging encrypted messages, until the connection either succeeds or fails. This scheme enables the TLS negotiation feature (see below for more info). If the TLS handshake fails, the entire [refmemunq any_connection async_connect] operation will also fail. TLS shutdown is performed by [refmem any_connection async_close]. MySQL doesn't always close TLS connections gracefully, so errors generated by the TLS shutdown are ignored. [heading:negotiation TLS negotiation] During connection establishment, client and server negotiate whether to use TLS or not. Boost.MySQL supports such negotiation using [refmem connect_params ssl]. This is a [reflink ssl_mode] enum with the following options: * `ssl_mode::enable` will make the connection use TLS if the server supports it, falling back to a plaintext connection otherwise. [*This is the default] for [reflink any_connection] when using TCP. * `ssl_mode::require` ensures that the connection uses TLS. If the server does not support it, [refmemunq any_connection async_connect] fails. * `ssl_mode::disable` unconditionally disables TLS. [*UNIX sockets] are considered secure channels and [*never use TLS]. When connecting using a UNIX socket, [refmem connect_params ssl] is ignored. After a successful connection establishment, you can use [refmem any_connection uses_ssl] to query whether the connection is encrypted or not. [heading Disabling TLS] As mentioned above, setting [refmem connect_params ssl] to `ssl_mode::disable` disables TLS: [section_connection_establishment_disable_tls] See the [link mysql.examples.disable_tls full example here]. [heading:options Certificate validation and other TLS options] You can pass an optional __ssl_context__ to [reflink any_connection] constructor. You can set many TLS parameters doing this, including trusted CAs, certificate validation callbacks and TLS extensions. [reflink any_connection_params] contains a [refmemunq any_connection_params ssl_context] member that can be used for this. For example, TLS certificate validation is disabled by default. To enable it: [section_connection_establishment_tls_options] You can safely share a single __ssl_context__ among several connections. If no `ssl::context` is passed, one will be internally created by the connection when required. The default context doesn't perform certificate validation. The full source code for the above example is [link mysql.examples.tls_certificate_verification here]. [heading TLS in connection_pool] Since [reflink connection_pool] creates [reflink any_connection] instances, the mechanics for TLS are similar. TLS-related parameters are specified during pool construction, as members of [reflink pool_params]: * [refmem pool_params ssl] controls TLS negotiation. It's a [reflink ssl_mode] value, with the semantics explained [link mysql.connection_establishment.tls.negotiation above]. * [refmem pool_params ssl_ctx] is a __ssl_context__ that is passed to connections created by the pool. It can be used to configure [link mysql.connection_establishment.tls.options TLS options] like certificate verification. The pool takes ownership of the passed `ssl::context`, as opposed to `any_connection`. [endsect] [/ TLS ] [section:unix UNIX sockets] [refmem connect_params server_address] is an [reflink any_address], a variant-like type that can hold a (hostname, port) pair or a UNIX socket path. To connect to MySQL using a UNIX socket, set `server_address` to a UNIX domain path: [section_connection_establishment_unix_socket] Note that UNIX sockets never use TLS, regardless of the value of [refmem connect_params ssl]. [endsect] [section Changing the network buffer size limit] [reflink any_connection] owns an internal network buffer used to store messages that are to be written or have been read from the server. Its initial size is given by [refmem any_connection_params initial_buffer_size]. Every protocol message needs to fit in memory, so the buffer is expanded as required. When reading data, every row is sent as an individual message. The buffer never resizes past [refmem any_connection_params max_buffer_size]. If an operation requires a bigger buffer, it will fail with the `client_errc::max_buffer_size_exceeded` error code. The default size is 64MB. If you need to read or write individual rows bigger than the default limit, you can increase it when constructing the connection: [section_connection_establishment_max_buffer_size] Note that reading datasets bigger than 64MB [*does not require increasing the limit] as long as individual rows are smaller than the aforementioned limit. Tweaking [refmem any_connection_params initial_buffer_size] may affect [refmem any_connection async_read_some_rows] performance, as explained in [link mysql.multi_function.read_some_rows this section]. [endsect] [section Enabling multi-queries] You can run several several semicolon-separated queries at once using [refmem any_connection async_execute]. This is a protocol feature that is disabled by default. You can enable it by setting [refmem connect_params multi_queries] to true before connecting: [section_connection_establishment_multi_queries] As explained [link mysql.tutorial_updates_transactions in the tutorial], multi-separated queries are useful in a number of cases, like when using transactions. [link mysql.multi_resultset.multi_queries This section] contains more info on how to use multi-queries. This protocol feature is disabled by default as a security hardening measure. If your application contains a SQL injection vulnerability, this feature can make exploiting it easier. Applications that don't need this feature should leave it off as a best practice. [note Using multi-queries correctly is secure. Just make sure to use the adequate client-side SQL formatting tools to generate queries securely. ] [endsect] [section Closing a connection] You can cleanly close a connection by calling [refmem any_connection async_close]. This sends a ['quit packet] to the server, notifying that we're about to end the connection, performs TLS shutdown, and closes the underlying transport. A clean close involves I/O and can thus fail. [*Destroying the connection] without performing a clean close will just close the underlying transport. [*It won't leak any resource], but you might see warnings in the server log. Try to close connections cleanly when possible. [endsect] [section Reconnection and long-lived connections] [reflink any_connection] doesn't perform any re-connection on its own. If a fatal error (like a network error) is encountered during an operation, you need to re-establish the connection explicitly. By design, [refmem any_connection async_connect] can [*always be used] to re-establish connections. It works even after the connection encountered a network error or a cancellation. To achieve this, `async_connect` will wipe any previous connection state before proceeding. If you need reliable, long-lived connections, consider [link mysql.connection_pool using a connection pool] instead of rolling out your own strategy. `connection_pool` takes care of re-connecting and re-using connections for you. [endsect] [endsect] [/ connparams] ================================================ FILE: doc/qbk/06_text_queries.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:text_queries Text queries and client-side SQL formatting] [nochunk] ['Text queries] are those that use MySQL text protocol for execution. Plain strings and [reflink with_params] use this protocol. This contrasts with [link mysql.prepared_statements prepared statements], which are first prepared and then executed separately, and use a binary protocol. [warning [*Never compose SQL queries using raw string concatenation]. This is insecure and can lead to [*SQL injection vulnerabilities]. Use the client-side SQL formatting facilities explained in this section to avoid vulnerabilities. ] Using text queries you can run [link mysql.multi_resultset.multi_queries multiple semicolon-separated queries], which can improve efficiency. [section Using with_params for simple queries] [reflink with_params] is the easiest way to use client-side SQL formatting. It can be used as a simpler and more flexible alternative to prepared statements. While prepared statements expand queries server-side, SQL formatting does it client-side. Please read the [link mysql.text_queries.comparison comparison with prepared statements] and the [link mysql.text_queries.comparison.security security considerations] sections for more info. [reflink with_params] takes a SQL query string with placeholders and a set of parameters. When passed to [refmemunq any_connection execute] or [refmemunq any_connection async_execute], the query is expanded in the client with the supplied parameters and sent to the server for execution: [text_queries_with_params_simple] Curly braces (`{}`) represent placeholders (technically called ['replacement fields]). The notation and semantics are similar to [@https://en.cppreference.com/w/cpp/utility/format/format `std::format`]. All fundamental types can be used as query parameters. This includes integers, floating point types, strings, blobs, dates and times: [text_queries_with_params_scalars] `std::optional` and `boost::optional` can also be used: [text_queries_with_params_optionals] Collections and ranges are supported, as long as its elements can be formatted: [text_queries_with_params_ranges] See [link mysql.sql_formatting_advanced.ranges this section] for more on formatting ranges, and [link mysql.sql_formatting_advanced.reference this table] for a reference of types that have built-in support for SQL formatting. [note Like with `std::format`, the query string passed to `with_params` must be known at compile-time. You can skip this check using the [reflink runtime] function. ] Like `std::format`, you can use arguments with explicit indices: [text_queries_with_params_manual_indices] See [link mysql.sql_formatting_advanced.format_string_syntax this section] for a reference on the format string syntax. [endsect] [section:errors Common errors and how to fix them] Not all values can be formatted. If the library finds that formatting a certain value can cause an ambiguity that could lead to a security problem, an error will be issued and the query won't be sent to the server. Here are the most common errors: * `client_errc::invalid_encoding` * Cause: one of your string parameters contains invalid code points. With the default character set, this means that it contains [*invalid UTF-8]. * Solution: all string values must be encoded according to the connection's character set (usually UTF-8). Sanitize or reject such values. Use the [reflink blob] and [reflink blob_view] types for values that don't represent character strings, but arbitrary binary values. * `client_errc::unformattable_value` * Cause: one of your parameters contains an invalid value. For instance, a `double` contains a `NaN` or an `Inf`, unsupported by MySQL. * Solution: reject such values, or replace them by `NULL` before passing them to client-side SQL formatting. * `client_errc::unknown_character_set` * Cause: your connection doesn't know the character set you're using. Knowing the character set in use is required to generate queries securely. This situation can happen after calling [refmemunq any_connection reset_connection] or if you used a custom [refmem connect_params connection_collation] when connecting. * Solution: use a [reflink connection_pool] instead of manually resetting connections. If you can't, use the default [refmemunq connect_params connection_collation] when connecting, and use [refmemunq any_connection set_character_set] or [refmemunq any_connection async_set_character_set] after resetting connections. * [link mysql.charsets.tracking Learn more] about how character set tracking works. For example: [text_queries_with_params_invalid_encoding] [endsect] [section:comparison Prepared statements vs. client-side SQL formatting] Although both serve a similar purpose, they are fundamentally different. Prepared statements are parsed and expanded by the server. Client-side SQL expands the query in the client and sends it to the server as a string. This means that [*client-side SQL does not understand your queries]. It just knows about how to format MySQL types into a string without creating vulnerabilities, but otherwise treats your queries as opaque strings. Client-side SQL yields [*greater flexibility] (you can dynamically compose any query), while statements have more limitations. This also means that [*you need to pay more attention to compose valid queries], specially when dealing with complex conditionals. Logic errors may lead to exploits. Please read the [link mysql.text_queries.comparison.security security considerations section] for more info. Client-side SQL entails [*less round-trips to the server] than statements, and is usually more efficient for lightweight queries. However, it uses the less compact text protocol, which may be slower for queries retrieving a lot of data. See the [link mysql.text_queries.comparison.efficiency efficiency considerations section] for more info. In general, [*use client-side SQL] formatting for the following cases: * Simple queries that don't retrieve a lot of data. Default to `with_params` and only switch to statements if your performance measurements says so. * Queries involving dynamic SQL that can't be achieved by statements. Typical cases include: * Dynamic filters ([link mysql.examples.dynamic_filters example]). * Batch inserts. Inserting rows one by one can lead to poor efficiency. You can use client-side SQL formatting to compose a single `INSERT` that inserts several rows at once (see [link mysql.examples.batch_inserts example 1] and [link mysql.examples.batch_inserts_generic example 2]). * PATCH-like updates, where the field list in an `UPDATE` must be dynamic ([link mysql.examples.patch_updates example]). * Queries involving dynamic identifiers, like table and field names. * Conditional sorting. * Pipelines consisting of several semicolon-separated queries with dynamic fields. On the other hand, [*prefer prepared statements] if: * You are executing the same query over and over. You can prepare the statement once and execute it several times. * Your query is retrieving a lot of data, and you have performed the relevant performance measurements. [heading:efficiency Efficiency considerations] Both client-side SQL formatting and prepared statements have pros and cons efficiency-wise: * Client-formatted SQL entails [*less round-trips to the server]. For prepared statements, you usually need a call to prepare the statement, another one to execute it, and possibly a final one to close it. Client-formatted SQL only requires the execution round-trip. This performance gain increases with network latency and if you are using TLS. * Prepared statements always entail a [*mutation of session state], while client-formatted SQL may not. If you're using a [reflink connection_pool] with prepared statements, you can't use [refmem pooled_connection return_without_reset], as this will leak the statement. With client-formatted queries, reset may not be required if your SQL doesn't mutate session state. * Client-formatted SQL queries use a usually [*less efficient text-based protocol], while prepared statements use a more compact binary protocol. This is relevant if you're retrieving lots of data that is slow to convert to and from text (like doubles). * [*Prepared statements can be re-used]. If you need to execute a query several times, prepared statements will only be parsed once. * Client-formatted SQL allows [*more efficient patterns] than prepared statements, like batch inserts and semicolon-separated queries. [heading:security Security considerations] Both client-side SQL formatting and prepared statements [*protect against SQL injection]. Statements do so by parsing the query with placeholders server-side, before performing parameter substitution. Client-side SQL quotes and escapes your values to avoid injection, but [*does not understand your queries]. This means that you need to [*ensure that your queries always expand to valid SQL]. This is trivial for simple queries, but may be an issue with more complex ones, involving ranges or dynamic identifiers. For instance, the following query may expand to invalid SQL if the provided range is empty: [text_queries_with_params_empty_ranges] The risk is higher if you're building your query by pieces using [reflink format_sql_to]. To sum up: * Client-side SQL protects against SQL injection. * Client-side SQL does not protect against logic errors. The risk is only present in complex queries. We suggest the following advice: * Avoid complex query generation logic as much as possible. Use a single format string instead of `format_sql_to`, unless you have no other option. * When using ranges, consider if the empty range would lead to valid SQL or not. * Thoroughly test complex query generation logic. * Client-side SQL requires knowing the connection's current character set. This usually happens out of the box, and will lead to a [link mysql.text_queries.errors controlled error] otherwise. Some recommendations: * If in doubt, always use the default character set (`utf8mb4`). * Never issue `SET NAMES` or `SET CHARACTER SET` statements directly - use [refmem any_connection set_character_set] or [refmemunq any_connection async_set_character_set], instead. * If you're using [reflink format_sql] or [reflink format_sql_to], never craft [reflink format_options] values manually. Use [refmem any_connection format_opts], instead. [endsect] [endsect] ================================================ FILE: doc/qbk/07_prepared_statements.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:prepared_statements Prepared statements] This section covers using [mysqllink sql-prepared-statements.html server-side prepared statements], an alternative to [reflink with_params]. In general, prepared statements are more complex and less flexible than `with_params`, but might provide more efficiency under certain circumstances. Prefer `with_params` if you're not sure. See [link mysql.text_queries.comparison this section] for a comparison between `with_params` and prepared statements. Working with statements involves three networking operations: * Preparing a statement: [refmem any_connection async_prepare_statement]. * Executing a statement: [refmem any_connection async_execute] or [refmem any_connection async_start_execution]. * Closing a statement (optional): [refmem any_connection async_close_statement]. The [reflink statement] class holds a server-supplied handle to an open prepared statement. [heading Preparing a statement] Calling [refmemunq any_connection async_prepare_statement] yields a [reflink statement] object: [prepared_statements_prepare] The question mark characters (`?`) represent parameters (as described [mysqllink prepare.html here]). When you execute the statement (next section), you provide values for each of the parameters you declared, and the server will use these values to run the statement. [heading Executing a statement] Before executing a statement, you must specify its actual parameters by calling [refmem statement bind]. Binding happens client-side: the statement handle and the passed parameters are packed into an object that can be passed to [refmemunq any_connection async_execute], without any communication with the server. The object returned by `bind` can be passed to `async_execute`: [prepared_statements_execute] Some observations: * You must pass in [*exactly as many parameters as the statement has]. Failing to do so will result in an error. * You don't need to sanitize the parameters anyhow. The server takes care of it. * Actual parameters are matched to `?` placeholders by order. * You can pass types like built-in integers, `float`, [reflink date] or `std::string`, with the expected effects. [link mysql.prepared_statements.writable_field_reference This table] contains a reference with all the allowed types. * You can also use [link mysql.static_interface the static interface] to execute statements by replacing [reflink results] by [reflink static_results]. [heading Closing a statement] Prepared statements are created server-side, and thus consume server resources. You can deallocate statements that you don't need anymore by calling [refmemunq any_connection async_close_statement]: [prepared_statements_close] Prepared statements are managed by the server on a per-session basis. This is, once you close your connection with the server, any allocated prepared statements will be automatically closed for you. Calling [refmem any_connection async_reset_connection] will also close all the statements prepared by the current session. This is used by [reflink connection_pool] to clean up sessions. In general, avoid closing statements explicitly if you're using `async_reset_connection` or `connection_pool`, or if you're preparing a fixed number of statements at program startup. Closing statements involves network traffic that can be avoided. On the other hand, if you are creating and destroying prepared statements dynamically without using the aforementioned techniques, consider closing statement explicitly to limit server resource consumption. [note [reflink statement]'s destructor [*does not deallocate the statement from the server]. This is intentional, as closing a statement involves a network operation that may block or fail, and is not required by strategies involving `async_reset_connection`. ] [heading NULLs and optionals] You can pass `std::optional` and `boost::optional` for parameters that may be `NULL`: [prepared_statements_execute_null] [heading Parameter server-side type casting] MySQL is quite permissive with the type of statement parameters. In most cases, it will perform the required casts for you. For example, the following will work: [prepared_statements_casting] [heading Executing a statement with a variable number of parameters] [refmem statement bind] has two forms: * A variadic form, where each statement argument is passed as a C++ argument. This is what we've been using until now. It can only be used if the number of arguments a statement has is known at compile time. * A range form, where arguments are passed as a range of variants. This one should only be used if the number of arguments is unknown at compile time. The range should contain [reflink field] or [reflink field_view] elements, which can represent any MySQL type. For example: [prepared_statements_iterator_range] [heading Type mapping reference for prepared statement parameters] The following table contains a reference of the types that can be used when binding a statement. If a type can be used this way, we say to satisfy the `WritableField` concept. The table shows how a parameter `v` in a expression `conn.execute(stmt.bind(v), result)` is interpreted by MySQL, depending on `v`'s type. [table:writable_field_reference [ [C++ type] [MySQL type] [Compatible with...] ] [ [`signed char`, `short`, `int`, `long`, `long long`] [`BIGINT`] [Signed `TINYINT`, `SMALLINT`, `MEDIUMINT`, `INT`, `BIGINT`] ] [ [`unsigned char`, `unsigned short`, `unsigned int`, `unsigned long`, `unsigned long long`] [`UNSIGNED BIGINT`] [Unsigned `TINYINT`, `SMALLINT`, `MEDIUMINT`, `INT`, `BIGINT`, `YEAR`, `BIT`] ] [ [`bool`] [`BIGINT` (`1` if `true`, `0` if `false`)] [`TINYINT`] ] [ [`std::basic_string, Allocator>` (including `std::string`), [reflink string_view], `std::string_view`, `const char*`] [`VARCHAR`] [`CHAR`, `VARCHAR`, `TEXT` (all sizes), `ENUM`, `SET`, `JSON`, `DECIMAL`, `NUMERIC`] ] [ [`std::basic_vector` (including [reflink blob]), [reflink blob_view]] [`BLOB`] [`BINARY`, `VARBINARY`, `BLOB` (all sizes), `GEOMETRY`] ] [ [`float`] [`FLOAT`] [`FLOAT`] ] [ [`double`] [`DOUBLE`] [`DOUBLE`] ] [ [[reflink date]] [`DATE`] [`DATE`] ] [ [[reflink datetime]] [`DATETIME`] [`DATETIME`, `TIMESTAMP`] ] [ [ [reflink time][br] Any `std::chrono::duration` convertible to `time` ] [`TIME`] [`TIME`] ] [ [`std::nullptr_t`] [`NULL`] [Any of the other types. Used to insert `NULL`s, for example.] ] [ [`std::optional`] [ Applies `T`'s type mapping if the optional has a value.[br] `NULL` otherwise ] [] ] [ [`boost::optional`] [ Applies `T`'s type mapping if the optional has a value.[br] `NULL` otherwise ] [] ] [ [[reflink field_view]] [Depends on the actual type stored by the field] [] ] [ [[reflink field]] [Depends on the actual type stored by the field] [] ] ] [endsect] ================================================ FILE: doc/qbk/08_dynamic_interface.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:dynamic_interface The dynamic interface] [nochunk] To use the dynamic interface, use the [reflink results] class. `results` is an in-memory representation of a resultset. We can depict it like this (this is actually a simplified representation, since [link mysql.multi_resultset some statements may return more than one resultset]). [$mysql/images/results.svg [align center] [scale 125]] We can see that [refmem results rows] returns a matrix-like object, containing the retrieved rows. This section is dedicated on diving deeper on how to use these objects. [heading Rows and fields] This matrix-like structure is composed of variant-like objects called ['fields]. Field objects are capable of representing any value retrieved from MySQL. This library defines the following classes to work with rows and fields: [variablelist [ [[reflink field]] [The smallest unit of data. A single "cell" in a MySQL table. This is an owning, variant-like type.] ] [ [[reflink field_view]] [Like `field`, but non-owning.] ] [ [[reflink row]] [An owning, `vector`-like collection of fields.] ] [ [[reflink row_view]] [Like `row`, but non-owning.] ] [ [[reflink rows]] [An owning, matrix-like collection of fields. Represents several rows of the same size in an optimized way.] ] [ [[reflink rows_view]] [Like `rows`, but non-owning.] ] ] [refmem results rows] returns a [reflink rows_view] object. The memory for the rows is owned by the `results` object. Indexing the returned view also returns view objects: [dynamic_views] Views behave similarly to `std::string_view`. You must make sure that you don't use a view after the storage it points to has gone out of scope. In this case, you must not use any of the views after the `results` object has gone out of scope. As it happens with `std::string_view`, you can take ownership of a view using its owning counterpart: [dynamic_taking_ownership] [heading Using fields] [reflink field] and [reflink field_view] are specialized variant-like types that can hold any type you may find in a MySQL table. Once you obtain a field, you can access its contents using the following functions: * You can query a field's type by using [refmemunq field_view kind], which returns a [reflink field_kind] enum. * You can query whether a field contains a certain type with `field::is_xxx`. * You can get the underlying value with `field::as_xxx` and `field::get_xxx`. The `as_xxx` functions are checked (they will throw an exception if the actual type doesn't match), while the `get_xxx` are unchecked (they result in undefined behavior on type mismatch). * You can stream fields and compare them for equality. For example: [dynamic_using_fields] `NULL` values are represented as field objects having `kind() == field_kind::null`. You can check whether a value is `NULL` or not using [refmemunq field_view is_null]. This is how `NULL`s are typically handled: [dynamic_handling_nulls] [heading MySQL to C++ type mappings] Every MySQL type is mapped to a single C++ type. The following table shows these mappings: [table:accessors [ [`field_kind`] [C++ type] [MySQL types] [`is` accessor] [`as` accessor] [`get` accessor] ] [ [`int64`] [`std::int64_t`] [__TINYINT__, __SMALLINT__, __MEDIUMINT__, __INT__, __BIGINT__] [[refmemunq field_view is_int64]] [[refmemunq field_view as_int64]] [[refmemunq field_view get_int64]] ] [ [`uint64`] [`std::uint64_t`] [Unsigned __TINYINT__, __SMALLINT__, __MEDIUMINT__, __INT__, __BIGINT__, __YEAR__, __BIT__] [[refmemunq field_view is_uint64]] [[refmemunq field_view as_uint64]] [[refmemunq field_view get_uint64]] ] [ [`string`] [ [reflink string_view] for `field_view` `std::string` for `field` ] [ __CHAR__, __VARCHAR__, __TEXT__ (all sizes), __ENUM__, __SET__, __DECIMAL__, __NUMERIC__, __JSON__ ] [[refmemunq field_view is_string]] [[refmemunq field_view as_string]] [[refmemunq field_view get_string]] ] [ [`blob`] [ [reflink blob_view] for `field_view` [reflink blob] for `field` ] [__BINARY__, __VARBINARY__, __BLOB__ (all sizes), __GEOMETRY__] [[refmemunq field_view is_blob]] [[refmemunq field_view as_blob]] [[refmemunq field_view get_blob]] ] [ [`float_`] [`float`] [__FLOAT__] [[refmemunq field_view is_float]] [[refmemunq field_view as_float]] [[refmemunq field_view get_float]] ] [ [`double_`] [`double`] [__DOUBLE__] [[refmemunq field_view is_double]] [[refmemunq field_view as_double]] [[refmemunq field_view get_double]] ] [ [`date`] [[reflink date]] [__DATE__] [[refmemunq field_view is_date]] [[refmemunq field_view as_date]] [[refmemunq field_view get_date]] ] [ [`datetime`] [[reflink datetime]] [__DATETIME__, __TIMESTAMP__] [[refmemunq field_view is_datetime]] [[refmemunq field_view as_datetime]] [[refmemunq field_view get_datetime]] ] [ [`time`] [[reflink time]] [__TIME__] [[refmemunq field_view is_time]] [[refmemunq field_view as_time]] [[refmemunq field_view get_time]] ] [ [`null`] [] [Any of the above, when they're `NULL`] [[refmemunq field_view is_null]] [] [] ] ] No character set conversion is applied on strings. They are provided as the server sends them. If you've run [refmemunq any_connection async_set_character_set], strings will be encoded according to the passed character set. For details, see [link mysql.charsets this section]. [heading The field class] [reflink field_view] is to [reflink field] what `std::string_view` is to `std::string`. `field_view`s are cheap to create and to copy, as they are small objects and don't perform any memory allocations. They are also immutable. On the other hand, `field`s may be more expensive to create and copy, as they may perform memory allocations. `field`s are mutable. `field` and `field_view` use the same underlying types for scalars. For strings and blobs, `field` uses the owning types `std::string` and [reflink blob], while `field_view` uses the reference types [reflink string_view] and [reflink blob_view]. `field` accessors return references, which allow you to mutate the underlying object: [dynamic_field_accessor_references] You can also mutate a `field` using the assignment operator. This allows you to also change the underlying type of a `field`: [dynamic_field_assignment] [heading Multi-resultset and multi-function operations] You can use both with the dynamic interface. Please refer to the sections on [link mysql.multi_resultset multi-resultset operations] and [link mysql.multi_function multi-function operations] for more information. [heading MySQL to C++ type mapping reference] The following table reflects mapping from database types to C++ types. The range column shows the range of values that MySQL admits for that type. This library guarantees that any field retrieved from the database honors that range. The `column_type` column shows what [refmem metadata type] would return for a column of that type. [table:dynamic_field_mappings [ [MySQL type] [`field_kind`] [C++ type] [Range] [`column_type`] [Considerations] ] [ [__TINYINT__] [`int64`] [`std::int64_t`] [`-0x80` to `0x7f`] [`tinyint`] [1 byte integer] ] [ [__TINYINT__ `UNSIGNED`] [`uint64`] [`std::uint64_t`] [`0` to `0xff`] [`tinyint`] [1 byte integer] ] [ [__SMALLINT__] [`int64`] [`std::int64_t`] [`-0x8000` to `0x7fff`] [`smallint`] [2 byte integer] ] [ [__SMALLINT__ `UNSIGNED`] [`uint64`] [`std::uint64_t`] [`0` to `0xffff`] [`smallint`] [2 byte integer] ] [ [__MEDIUMINT__] [`int64`] [`std::int64_t`] [`-0x800000` to `0x7fffff`] [`mediumint`] [3 byte integer] ] [ [__MEDIUMINT__ `UNSIGNED`] [`uint64`] [`std::uint64_t`] [`0` to `0xffffff`] [`mediumint`] [3 byte integer] ] [ [__INT__] [`int64`] [`std::int64_t`] [`-0x80000000` to `0x7fffffff`] [`int_`] [4 byte integer] ] [ [__INT__ `UNSIGNED`] [`uint64`] [`std::uint64_t`] [`0` to `0xffffffff`] [`int_`] [4 byte integer] ] [ [__BIGINT__] [`int64`] [`std::int64_t`] [`-0x8000000000000000` to `0x7fffffffffffffff`] [`bigint`] [8 byte integer] ] [ [__BIGINT__ `UNSIGNED`] [`uint64`] [`std::uint64_t`] [`0` and `0xffffffffffffffff`] [`bigint`] [8 byte integer] ] [ [__YEAR__] [`uint64`] [`std::uint64_t`] [\[`1901`, `2155`\], plus zero] [`year`] [ 1 byte integer type used to represent years Zero is often employed to represent invalid year values. We represent zero year as a numeric 0. ] ] [ [__BIT__] [`uint64`] [`std::uint64_t`] [Depends on the bitset width. Max `0` to `0xffffffffffffffff`.] [`bit`] [ A bitset between 1 and 64 bits wide. ] ] [ [__FLOAT__] [`float_`] [`float`] [IEEE 754 `float` range] [`float_`] [ 4 byte floating point type ] ] [ [__DOUBLE__] [`double_`] [`double`] [IEEE 754 `double` range] [`double_`] [ 8 byte floating point type ] ] [ [__DATE__] [`date`] [[reflink date]] [ \[[reflink min_date], [reflink max_date]\] (some MySQL implementations may allow a narrower range), plus invalid and zero dates (see __allow_invalid_dates__ and __strict_sql__). ] [`date`] [] ] [ [__DATETIME__] [`datetime`] [[reflink datetime]] [ \[[reflink min_datetime], [reflink max_datetime]\] (some MySQL implementations may allow a narrower range), plus invalid and zero datetimes (see __allow_invalid_dates__ and __strict_sql__). ] [`datetime`] [ Time point type without time zone, with a resolution of one microsecond. ] ] [ [__TIMESTAMP__] [`datetime`] [[reflink datetime]] [ \[[reflink min_datetime], [reflink max_datetime]\] (the actual MySQL supported range is usually narrower, but we don't enforce it in the client), plus zero timestamps (see __strict_sql__). ] [`timestamp`] [ Time point type with a resolution of one microsecond. ] ] [ [__TIME__] [`time`] [[reflink time]] [ \[[reflink min_time], [reflink max_time]\] ] [`time`] [ Signed time duration, with a resolution of one microsecond. ] ] [ [__CHAR__] [`string`] [[reflink string_view] or `std::string`] [] [`char_`] [ Fixed-size character string. ] ] [ [__VARCHAR__] [`string`] [[reflink string_view] or `std::string`] [] [`varchar`] [ Variable size character string with a maximum size. ] ] [ [__TEXT__ (all sizes)] [`string`] [[reflink string_view] or `std::string`] [] [`text`] [ Variable size character string. ] ] [ [__ENUM__] [`string`] [[reflink string_view] or `std::string`] [] [`enum_`] [ Character string with a fixed set of possible values (only one possible). ] ] [ [__SET__] [`string`] [[reflink string_view] or `std::string`] [] [`set`] [ Character string with a fixed set of possible values (many possible). ] ] [ [__JSON__] [`string`] [[reflink string_view] or `std::string`] [] [`json` (MySQL) or `text` (MariaDB)] [ A serialized JSON value of any type. Note that [refmem metadata type] is different depending on the DB system. MySQL has a dedicated `JSON` type, while in MariaDB `JSON` is an alias for `LONGTEXT`. JSON values are represented as strings by this library in both cases. ] ] [ [__DECIMAL__/__NUMERIC__] [`string`] [[reflink string_view] or `std::string`] [Depends on the column definition] [`decimal`] [ A fixed precision numeric value. In this case, the string will contain the textual representation of the number (e.g. the string `"20.52"` for `20.52`). This type is mapped to a string to avoid losing precision. ] ] [ [__BINARY__] [`blob`] [[reflink blob_view] or [reflink blob]] [] [`binary`] [ Fixed-size blob. ] ] [ [__VARBINARY__] [`blob`] [[reflink blob_view] or [reflink blob]] [] [`varbinary`] [ Variable size blob with a maximum size. ] ] [ [__BLOB__ (all sizes)] [`blob`] [[reflink blob_view] or [reflink blob]] [] [`blob`] [ Variable size blob. ] ] [ [__GEOMETRY__] [`blob`] [[reflink blob_view] or [reflink blob]] [] [`geometry`] [ Any of the spatial data types. The string contains the binary representation of the geometry type. ] ] ] [endsect] ================================================ FILE: doc/qbk/09_static_interface.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:static_interface The static interface] [nochunk] We have already covered the static interface basics in [link mysql.tutorial_static_interface the tutorials]. This section expands on this topic. To use the static interface, we must first define a data structure that describes the shape of our rows. We have several options: * Use __Describe__ to annotate a plain `struct` with `BOOST_DESCRIBE_STRUCT` to enable reflection on it. * Use __Pfr__ and [reflink pfr_by_name] or [reflink pfr_by_position] to use PFR automatic reflection capabilities. * Use `std::tuple`. We will work with the following table: [!teletype] ``` CREATE TABLE employee( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, salary INT UNSIGNED, company_id CHAR(10) NOT NULL, FOREIGN KEY (company_id) REFERENCES company(id) ); ``` [note For simplicity, code snippets use C++20 coroutines. You can use the techniques described here with lower C++ standards by using sync functions, callbacks or `asio::yield_context`. The [link mysql.static_interface.comparison comparison table] shows the C++ standard required by each technique shown here. ] Let's start with Boost.Describe. If we want to query the first three fields, we can define our row type like this: [static_interface_describe_employee_v1] And write the following to query our table: [static_interface_query] Note that [refmem static_results rows] returns a `boost::span` object, which is a C++11 backport of `std::span`. The span points into memory owned by the `static_results` object. Care must be taken not to use this view object after the `static_results` goes out of scope. [heading Field matching] Columns in the query are matched to fields in the struct by name. If a struct field cannot be matched to any query column, an error is issued. Extra columns in the query are ignored. If your query contains columns with names that don't qualify as C++ identifiers, you can use SQL aliases. For example, given this struct: [static_interface_describe_statistics] You can write your query as: [static_interface_field_order] [heading:meta_checks Metadata checking] The static interface will try to validate as soon as possible that the provided row type is compatible with the schema returned by the server. This process is known as [*metadata checking], and is performed before reading any data. The following checks are performed: * [*Type compatibility]: the C++ type must be able to represent any value that the MySQL type can represent. For example, `std::int32_t` is compatible with `TINYINT` (1 byte integer), but not with `BIGINT` (8 byte integer). For a full list of allowable field types, [link mysql.static_interface.readable_field_reference refer to this table]. * [*Nullability]: if MySQL reports that a column can be `NULL`, your type must account for it. You can use `std::optional` or `boost::optional` for these columns. Let's add the `salary` field to our employee query. We might try the following: [static_interface_describe_employee_v2] However, this won't work because of the nullability check. In this case, the correct definition would be: [static_interface_describe_employee_v3] [heading Using Boost.PFR] If you're using C++20 or above, you can use Boost.PFR to reflect types without the `BOOST_DESCRIBE_STRUCT` macro: [static_interface_pfr_employee] PFR reflection can be enabled in Boost.MySQL by using [reflink pfr_by_name]: [static_interface_pfr_by_name] Note that [reflink pfr_by_name] is what we call a ['marker type] - an empty type that tells classes like [reflink static_results] how to reflect a type. If no marker type is used, Boost.Describe is used to retrieve reflection data for struct types. [reflink pfr_by_position] is similar to `pfr_by_name`, but will match columns in the query to struct fields by position, rather than name. It only requires C++17 to work. For instance: [static_interface_pfr_by_position] Please refer to [link mysql.static_interface.comparison this table] for a comparison with Boost.Describe. [heading Using tuples] You can also use `std::tuple`s as row types. This can be handy for simple queries: [static_interface_tuples] Fields in tuples are matched to query columns by order. The query must return as many columns as fields the tuple has, at least. Any extra trailing columns in the query are ignored. [heading Multi-resultset and multi-function operations] You can use both with the dynamic interface. Please refer to the sections on [link mysql.multi_resultset multi-resultset operations] and [link mysql.multi_function multi-function operations] for more information. [heading Reflection techniques comparison] Should I use Boost.Describe, Boost.PFR or tuples? Each one has its advantages and drawbacks. This table may help you decide: [table:comparison [ [Technique] [Sample code] [Minimum C++ standard] [Comments] [Feature test macro] ] [ [Boost.Describe] [ [static_interface_comparison_describe_struct][br] [static_interface_comparison_describe] ] [ C++14 ] [ * Requires adding metadata with `BOOST_DESCRIBE_STRUCT`.[br] * Matches fields by name.[br] * No limitations placed on the row type (e.g. works for structs using inheritance). ] [ `BOOST_MYSQL_CXX14` is defined ] ] [ [Boost.PFR using names] [ [static_interface_comparison_pfr_struct][br] [static_interface_comparison_pfr_by_name] ] [ C++20 ] [ * Doesn't require adding metadata to structs.[br] * Matches fields by name.[br] * Works for row types satisfying [@boost:/doc/html/boost_pfr/limitations_and_configuration.html `SimpleAggregate`]. ] [ `BOOST_PFR_CORE_NAME_ENABLED` is defined and set to `1` ] ] [ [Boost.PFR using field position] [ [static_interface_comparison_pfr_struct][br] [static_interface_comparison_pfr_by_position] ] [ C++17[br] C++14 with limitations ] [ * Doesn't require adding metadata to structs.[br] * Matches fields by position.[br] * In C++17 mode, it works for row types satisfying [@boost:/doc/html/boost_pfr/limitations_and_configuration.html `SimpleAggregate`]. * In C++14 mode, it may not work for rows containing certain field types, like strings. See [@boost:/doc/html/boost_pfr/limitations_and_configuration.html Boost.PFR documentation] on C++14 limitations. ] [ `BOOST_PFR_ENABLED` is defined and set to `1`. `BOOST_PFR_USE_CPP17` is defined and set to `1` for C++17 mode. ] ] [ [Standard tuples] [ [static_interface_comparison_tuples] ] [ C++14 ] [ * Should only be used for very simple queries.[br] * Matches fields by position.[br] ] [ `BOOST_MYSQL_CXX14` is defined ] ] ] Note that using the static interface always requires C++14, at least. The `BOOST_MYSQL_CXX14` test macro is defined only if the static interface is supported. Including the static interface headers on an unsupported compiler doesn't cause any error, but classes like [reflink static_results] and [reflink static_execution_state] are not defined. The test macro is brought on scope by any of the static interface headers. [heading Allowed field types] All the types used within your Describe structs or tuples must be within the following table. A Describe struct or tuple composed of valid field types models the [reflink StaticRow] concept. The following table is a reference of the C++ types that can be used in a `StaticRow` and their compatibility with MySQL database types: [table:readable_field_reference [ [C++ type] [Compatible with...] ] [ [`std::int8_t`] [ __TINYINT__ ] ] [ [`std::uint8_t`] [ __TINYINT__ `UNSIGNED` ] ] [ [`std::int16_t`] [ __TINYINT__[br] __TINYINT__ `UNSIGNED`[br] __SMALLINT__ [br] __YEAR__ ] ] [ [`std::uint16_t`] [ __TINYINT__ `UNSIGNED`[br] __SMALLINT__ `UNSIGNED` [br] __YEAR__ ] ] [ [`std::int32_t`] [ __TINYINT__, __TINYINT__ `UNSIGNED`[br] __SMALLINT__, __SMALLINT__ `UNSIGNED`[br] __MEDIUMINT__, __MEDIUMINT__ `UNSIGNED`[br] __INT__[br] __YEAR__ ] ] [ [`std::uint32_t`] [ __TINYINT__ `UNSIGNED`[br] __SMALLINT__ `UNSIGNED`[br] __MEDIUMINT__ `UNSIGNED`[br] __INT__ `UNSIGNED`[br] __YEAR__ ] ] [ [`std::int64_t`] [ __TINYINT__, __TINYINT__ `UNSIGNED`[br] __SMALLINT__, __SMALLINT__ `UNSIGNED`[br] __MEDIUMINT__, __MEDIUMINT__ `UNSIGNED`[br] __INT__, __INT__ `UNSIGNED`[br] __BIGINT__[br] __YEAR__ ] ] [ [`std::uint64_t`] [ __TINYINT__ `UNSIGNED`[br] __SMALLINT__ `UNSIGNED`[br] __MEDIUMINT__ `UNSIGNED`[br] __INT__ `UNSIGNED`[br] __BIGINT__ `UNSIGNED`[br] __YEAR__[br] __BIT__ ] ] [ [`bool`] [ `BOOL` or `BOOLEAN` (alias for __TINYINT__). ] ] [ [`float`] [ __FLOAT__ ] ] [ [`double`] [ __FLOAT__, __DOUBLE__[br] ] ] [ [`date`] [ __DATE__ ] ] [ [`datetime`] [ __DATETIME__, __TIMESTAMP__ ] ] [ [`time`] [ __TIME__ ] ] [ [ `std::basic_string, Allocator>`[br][br] The object must be default-constructible. ] [ __CHAR__, __VARCHAR__, __TEXT__[br] __ENUM__, __SET__[br] __JSON__[br] __DECIMAL__/__NUMERIC__ ] ] [ [ `std::basic_vector`[br][br] The object must be default-constructible. ] [ __BINARY__, __VARBINARY__, __BLOB__[br] __GEOMETRY__ ] ] [ [ `std::optional`[br][br] `T` must be any of the types listed in this table. ] [ Any type compatible with `T` ] ] [ [ `boost::optional`[br][br] `T` must be any of the types listed in this table. ] [ Any type compatible with `T` ] ] ] [endsect] ================================================ FILE: doc/qbk/10_multi_resultset.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:multi_resultset Multi-resultset: stored procedures and multi-queries] [heading Using stored procedures] [mysqllink create-procedure.html Stored procedures] can be called using the `CALL` SQL statement. You can use `CALL` statements from both text queries and prepared statements, in a similar way to other SQL statements. Contrary to other statements, `CALL` may generate more than one resultset. For example, given a stored procedure like this: __sp_get_employees__ A statement like `CALL get_employees('my_company')` will generate three resultsets: * A first resultset containing the company matched by the first `SELECT`. * A second resultset containing the employees matched by the second `SELECT`. * A third, empty resultset containing information about the `CALL` statement. Every resultset contains its own rows, metadata, last insert ID, affected rows and so on. [heading Calling procedures using the dynamic interface] The same [reflink results] class we've been using supports storing more than one resultset. You can execute a `CALL` statement as any other SQL statement: [multi_resultset_call_dynamic] In this context, `results` can be seen as a random-access collection of resultsets. You can access resultsets by index using [refmem results at] and [reflink2 results.operator__lb__rb_ results::operator[]]. These operations yield a [reflink resultset_view], which is a lightweight object pointing into memory owned by the `results` object. You can take ownserhip of a `resultset_view` using the [reflink resultset] class. For example: [multi_resultset_results_as_collection] [heading Calling procedures using the static interface] The [reflink static_results] class supports operations that return multiple resultsets, too. As with other SQL statements, we need to define the row types in our resultsets in advance: [describe_stored_procedures] We can now use `static_results`, passing it as many template arguments as resultsets we expect. The library will check that the correct number of resultsets are actually returned by the server, and will parse them into the row types that we provided: [multi_resultset_call_static] Use [refmem static_results rows] with an explicit index to access each resultset's data. You can also use explicit indices with the other accessor functions, like [refmem static_results meta] and [refmem static_results last_insert_id]. For more information about the static interface, please refer to [link mysql.static_interface this section]. [heading Determining the number of resultsets] To know the number of resultsets to expect from a `CALL` statement, use these rules: * For every statement that retrieves data (e.g. a `SELECT` statement), a resultset is sent. `SELECT ... INTO ` statements don't cause a resultset to be sent. * Statements that don't retrieve columns (e.g. `UPDATE`, `DELETE`) don't cause a resultset to be sent. * An empty resultset containing information about the `CALL` statement execution is always sent last. Some examples: [!teletype] ``` -- Calling proc1 produces only 1 resultset because it only contains statements that -- don't retrieve data CREATE PROCEDURE proc1(IN pin_order_id INT, IN pin_quantity INT) BEGIN START TRANSACTION; UPDATE orders SET quantity = pin_quantity WHERE id = pin_order_id; INSERT INTO audit_log (msg) VALUES ("Updated order..."); COMMIT; END ``` [!teletype] ``` -- Calling proc2 produces 3 resultsets: one for the orders SELECT, one for the -- line_items SELECT, and one for the CALL statement CREATE PROCEDURE proc2(IN pin_order_id INT) BEGIN START TRANSACTION READ ONLY; SELECT * FROM orders WHERE id = pin_order_id; SELECT * FROM line_items WHERE order_id = pin_order_id; COMMIT; END ``` [heading Output parameters] You can get the value of `OUT` and `INOUT` parameters in stored procedures by using prepared statement placeholders for them. When doing this, you will receive another resultset with a single row containing all output parameter values. This resultset is located after all resultsets generated by `SELECT`s, and before the final, empty resultset. For example, given this procedure: __sp_create_employee__ You can use: [multi_resultset_out_params] [refmem results out_params] simplifies the process. [warning Due to a bug in MySQL, some `OUT` parameters are sent with wrong types. Concretely, string parameters are always sent as blobs, so you will have to use [refmem field_view as_blob] instead of [refmem field_view as_string]. ] [heading:multi_queries Semicolon-separated queries] It is possible to run several semicolon-separated text queries in a single [refmem connection execute] call. For security, this capability is disabled by default. Enabling it requires setting [refmem handshake_params multi_queries] before connecting: [multi_resultset_multi_queries] Note that statements like `DELIMITER` [*do not work] using this feature. This is because `DELIMITER` is a pseudo-command for the `mysql` command line tool, not actual SQL. You can also use the static interface with multi-queries. It works the same as with stored procedures. [endsect] ================================================ FILE: doc/qbk/11_multi_function.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:multi_function Multi-function operations] [nochunk] Multi-function operations allow running operations as a set of separate steps, which gives you better control over execution. They work by splitting some of the reads and writes into several function calls. You can use multi-function operations to execute text queries and prepared statements, and through the dynamic or the static interface. [heading Protocol primer] To make a good use of multi-function operations, you should have a basic understanding of the underlying protocol. The protocol uses ['messages] to communicate. These are delimited by headers containing the message length. All operations are initiated by the client, by sending a single ['request message], to which the server responds with a set of ['response messages]. The diagram below shows the message exchange between client and server for text queries and statement executions. Each arrow represents a message. [$mysql/images/protocol.svg [align center]] There are two separate cases: * If your query retrieved at least one column (even if no rows were generated), we're in ['case 1]. The server sends: * An initial packet informing that the query executed correctly, and that we're in ['case 1]. * Some metadata packets describing the columns that the query retrieved. These become available to the user and are necessary to parse the rows. * The actual rows. * An OK packet, which marks the end of the resultset and contains information like `last_insert_id` and `affected_rows`. * If your query didn't retrieve any column, we're in ['case 2]. The server will just send an OK packet, with the same information as in ['case 1]. [refmem connection execute] handles the full message exchange. In contrast, [refmem connection start_execution] will not read the rows, if any. Some takeaways: * The distinction between single-function and multi-function operations exists only in the client. The wire messages exchanged by both are the same. * There is no way to tell how many rows a resultset has upfront. You need to read row by row until you find the OK packet marking the end of the resultset. * When the server processes the request message, [*it sends all the response messages immediately]. These responses will be waiting in the client to be read. If you don't read [*all] of them, subsequent operations will mistakenly read them as their response, causing packet mismatches. Be careful and don't let this happen! [heading Using multi-function operations through the dynamic interface] [reflink execution_state] is the main class for the dynamic interface in multi-function operations. An execution state holds information required to progress the execution operation, like metadata (required to parse the rows) and protocol state. Contrary to `results`, it doesn't contain the rows. Given the following table definition: [multi_function_setup] You can start a multi-function operation using [refmem connection start_execution]: [multi_function_dynamic_start] We now [*must] read all the generated rows by calling [refmem connection read_some_rows], which will return a batch of an unspecified size: [multi_function_dynamic_read] Some remarks: * If there are rows to be read, `read_some_rows` will return at least one, but may return more. * [refmem execution_state complete] returns `true` after we've read the final OK packet for this operation. * The final `row_batch` may or may not be empty, depending on the number of rows and their size. * Calling `read_some_rows` after reading the final OK packet returns an empty batch. `read_some_rows` returns a [reflink rows_view] object pointing into the connection's internal buffer. This view is valid until the connection performs any other operation involving a network transfer. Note that there is no need to distinguish between ['case 1] and ['case 2] in the diagram above in our code, as reading rows for a complete operation is well defined. [heading Using multi-function operations through the static interface] The mechanics are similar to what's been exposed above. The static interface uses [reflink static_execution_state] to carry state. As with [reflink static_results], we must define and pass a type describing our rows: [describe_post] We can now start our operation using the same [refmem connection start_execution]: [multi_function_static_start] We now [*must] read all the generated rows by calling [refmem connection read_some_rows]: [multi_function_static_read] Some remarks: * [reflink static_execution_state] doesn't store rows anyhow. It uses the row types passed as template parameters to validate the metadata returned by the server, and ensure it is compatible with the C++ data structures that will be used with `read_some_rows`. * We must pass `read_some_rows` a `boost::span` of the appropriate row type. We've used `std::array` to place rows on the stack, but you can use any other contiguous range. * `read_some_rows` returns the number of read rows. At maximum, this will be the size of the span, but there may be less, depending on row and network buffer sizes. * If there are rows to be read, `read_some_rows` will return at least one, but may return more. * [refmem execution_state complete] returns `true` after we've read the final OK packet for this operation. * The final read may or may not return rows, depending on the number of rows and their size. * Calling `read_some_rows` after reading the final OK packet always reads zero rows. [heading Accessing metadata and OK packet data] You can access metadata at any point, using [refmem execution_state meta] or [refmem static_execution_state meta]. This function returns a collection of [reflink metadata] objects. For more information, please refer to [link mysql.meta this section]. You can access OK packet data using functions like [refmemunq execution_state last_insert_id] and [refmemunq execution_state affected_rows] in both `execution_state` and `static_execution_state`. As this information is contained in the OK packet, [*it can only be accessed once the [refmemunq execution_state complete] function returns `true`]. [heading Using multi-function operations with stored procedures and multi-queries] When using operations that return more than one resultset (e.g. when calling stored procedures), the protocol is slightly more complex: [$mysql/images/protocol_multi_resultset.svg [align center]] The message exchange is as follows: * A single execution request is sent to the server. * The server sends a first resultset, as in the single resultset case. The OK packet contains a flag indicating that another resultset follows. * Another resultset is sent, with the same structure as the previous one. The process is repeated until an OK packet indicates that no more resultsets follow. For example, given the following stored procedure: [/ This is an actual procedure. Code imports from SQL don't work. Make sure it doesn't go out of sync ] [!teletype] ``` CREATE PROCEDURE get_company(IN pin_company_id CHAR(10)) BEGIN START TRANSACTION READ ONLY; SELECT id, name, tax_id FROM company WHERE id = pin_company_id; SELECT first_name, last_name, salary FROM employee WHERE company_id = pin_company_id; COMMIT; END ``` We can write: [table [ [Dynamic interface] [Static interface] ] [ [[multi_function_stored_procedure_dynamic]] [[multi_function_stored_procedure_static]] ] ] Note that we're using [refmemunq execution_state should_read_rows] instead of [refmemunq execution_state complete] as our loop termination condition. `complete()` returns true when all the resultsets have been read, while `should_read_rows()` will return false once an individual result has been fully read. When using the static interface with multi-function operations, not all schema mismatches can be found by the `start_execution` function, since not all the information is available at this point. Errors may be reported by `read_some_rows` and `read_resultset_head`, too. Overall, the same checks are performed as when using [refmem connection execute], but at different points in time. [heading Multi-resultset in the general case] `execution_state` and `static_execution_state` can be seen as state machines with four states. Each state describes which reading function should be invoked next: * [refmemunq execution_state should_start_op]: the initial state, after you default-construct an `execution_state`. You should call [refmem connection start_execution] or [refmem connection async_start_execution] to start the operation. * [refmemunq execution_state should_read_rows]: the next operation should be [refmem connection read_some_rows], to read the generated rows. * [refmemunq execution_state should_read_head]: the next operation should be [refmem connection read_resultset_head], to read the next resultset metadata. Only operations that generate multiple resultsets go into this state. * [refmemunq execution_state complete]: no more operations are required. For multi-function operations, you may also access OK packet data ever time a resultset has completely been read, i.e. when [refmemunq execution_state should_read_head] returns `true`. [link mysql.examples.source_script This example] shows this general case. It uses multi-queries to run an arbitrary SQL script file. [heading:read_some_rows More on read_some_rows] To properly understand `read_some_rows`, we need to know that every [reflink connection] owns an *internal buffer*, where packets sent by the server are stored. It is a single, flat buffer, and you can configure its initial size when creating a `connection`, passing a [reflink buffer_params] object as the first argument to `connection`'s constructor. The read buffer may be grown under certain circumstances to accommodate large messages. `read_some_rows` gets the maximum number of rows that fit in the internal buffer (without growing it) performing a single `read_some` operation on the stream (or using cached data). If there are rows to read, `read_some_rows` guarantees to read at least one. This means that, if doing what we described yields no rows (e.g. because of a large row that doesn't fit into the buffer), `read_some_rows` will grow the buffer or perform more reads until at least one row has been read. If you're using the static interface, the number of read rows is limited by the size of span you passed, too. If you want to get the most of `read_some_rows`, customize the initial buffer size to maximize the number of rows that each batch retrieves. [endsect] ================================================ FILE: doc/qbk/12_connection_pool.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:connection_pool Connection pools] [nochunk] Connection pooling is a technique where several long-lived connections are re-used for independent logical operations. When compared to establishing individual connections, it has the following benefits: * It provides better performance. Please consult [link mysql.connection_pool.benchmarks our benchmarks] for more info. * It simplifies connection management. The connection pool will establish sessions, perform retries and apply timeouts out of the box. This is how you can create a pool of connections: [connection_pool_create] [reflink connection_pool] is an I/O object that manages connections. It can be constructed from an executor or execution context (like all I/O objects) and a [reflink pool_params] object. [refmem connection_pool async_run] must be called exactly once per pool. This function takes care of actually keeping connections healthy. We're now ready to obtain connections using [refmem connection_pool async_get_connection]. We will use C++20 coroutines to make async code simpler: [connection_pool_get_connection] By default, [refmem connection_pool async_run] will run forever. When your application exits, you will want to stop it using [refmem connection_pool cancel]. This is typical in signal handlers, to guarantee a clean shutdown. Note that pooling works only with [reflink any_connection]. [note `connection_pool` exposes async functions only. This has to do with efficiency and oddities in Boost.Asio executor model. If you need to use it from sync code, please visit [link mysql.interfacing_sync_async this section]. ] [heading Pool size] Pools start with a fixed initial size, and will be dynamically resized up to an upper limit if required. You can configure these sizes using [refmem pool_params initial_size] and [refmem pool_params max_size]. The resizing algorithm works like this: * When the pool is created, [refmem pool_params initial_size] number of connections are created and connected (by default, `initial_size` is 1). * If a connection is requested, but all available connections are in use, a new one is created, until `max_size` is reached. * If a connection is requested, and there are `max_size` connections in use, [refmem connection_pool async_get_connection] waits for a connection to become available. * Once created, connections never get deallocated. By default, [refmem pool_params max_size] is 151, which is MySQL's default value for the [mysqllink server-system-variables.html#sysvar_max_connections `max_connections`] system variable, controlling the maximum number of concurrent connections allowed by the server. [note Before increasing [refmem pool_params max_size], make sure to also increase the value of `max_connections` in the server. Otherwise, your connections will be rejected by the connection limit. ] This is how you configure pool sizes: [connection_pool_configure_size] [heading Applying a timeout to async_get_connection] By default, [refmem connection_pool async_get_connection] waits until a connection is available. This means that, if the server is unavailable, `async_get_connection` may wait forever. For this reason, you may consider setting a timeout to `async_get_connection`. You can do this using [asioreflink cancel_after cancel_after], which uses Asio's per-operation cancellation mechanism: [connection_pool_apply_timeout] You might consider setting the timeout at a higher level, instead. For instance, if you're handling an HTTP request, you can use `cancel_after` to set a timeout to the entire request. The [link mysql.examples.http_server_cpp20 connection pool example] takes this approach. [heading Session state] MySQL connections hold state. You change session state when you prepare statements, create temporary tables, start transactions, or set session variables. When using pooled connections, session state can be problematic: if not reset properly, state from a previous operation may affect subsequent ones. After you return a connection to the pool, the equivalent of [refmem any_connection async_reset_connection] and [refmemunq any_connection async_set_character_set] are used to wipe session state before the connection can be obtained again. This will deallocate prepared statements, rollback uncommitted transactions, clear variables and restore the connection's character set to `utf8mb4`. In particular, you don't need to call [refmem any_connection async_close_statement] to deallocate statements. Resetting a connection is cheap but entails a cost (a roundtrip to the server). If you've used a connection and you know that you didn't mutate session state, you can use [refmem pooled_connection return_without_reset] to skip resetting. For instance: [connection_pool_return_without_reset] Connection reset happens in the background, after the connection has been returned, so it does not affect latency. If you're not sure if an operation affects state or not, assume it does. [heading Character set] Pooled connections always use `utf8mb4` as its character set. When connections are reset, the equivalent of [refmem any_connection async_set_character_set] is used to restore the character set to `utf8mb4` (recall that raw [refmemunq any_connection async_reset_connection] will wipe character set data). Pooled connections always know the character set they're using. This means that [refmem any_connection format_opts] and [refmemunq any_connection current_character_set] always succeed. We recommend to always stick to `utf8mb4`. If you really need to use any other character set, use [refmemunq any_connection async_set_character_set] on your connection after it's been retrieved from the pool. [heading Connection lifecycle] The behavior already explained can be summarized using a state model like the following: [$mysql/images/pooled_connection_lifecycle.svg [align center]] In short: * When a connection is created, it goes into the `pending_connect` state. * Connection establishment is attempted. If it succeeds, the connection becomes `idle`. Otherwise, it stays `pending_connect`, and another attempt will be performed after [refmem pool_params retry_interval] has elapsed. * `idle` connections can be retrieved by [refmem connection_pool async_get_connection], and they become `in_use`. * If a connection is returned by [refmem pooled_connection return_without_reset], it becomes `idle` again. * If a connection is returned by [reflink pooled_connection]'s destructor, it becomes `pending_reset`. * [refmem any_connection async_reset_connection] is applied to `pending_reset` connections. On success, they become `idle` again. Otherwise, they become `pending_connect` and will be reconnected. * If a connection stays `idle` for [refmem pool_params ping_interval], it becomes `pending_ping`. At this point, the connection is probed. If it's alive, it will return to being `idle`. Otherwise, it becomes `pending_connect` to be reconnected. Pings can be disabled by setting [refmem pool_params ping_interval] to zero. [heading:thread_safe Thread-safety] By default, [reflink connection_pool] is [*not thread-safe], but it can be easily made thread-safe by setting [refmem pool_params thread_safe]: [connection_pool_thread_safe_create] To correctly understand what is protected by [refmem pool_params thread_safe] and what is not, we need a grasp of how pools are implemented. Both [reflink connection_pool] and individual [reflink pooled_connection]'s hold pointers to a shared state object containing all data required by the pool: [$mysql/images/connection_pool_impl.svg [align center]] Thread-safe connection pools internally create an [asioreflink strand strand] that protects the connection pool's state. Operations like [refmemunq connection_pool async_get_connection], [refmemunq connection_pool async_run] and [reflink pooled_connection]'s destructor will run through the strand, and are safe to be run from any thread. Operations that mutate state handles (the internal `std::shared_ptr`), like [*assignment operators, are not thread-safe]. Data outside the pool's state is not protected. In particular, [*`asio::cancel_after` creates an internal timer that can cause inadvertent race conditions]. For example: [connection_pool_thread_safe_use] This coroutine must be run within a strand: [connection_pool_thread_safe_spawn] If we don't use `asio::make_shared`, we have the following race condition: * The thread calling `async_get_connection` sets up the timer required by `asio::cancel_after`. * In parallel, the thread running the execution context sees that there is a healthy connection and completes the `async_get_connection` operation. As a result, the timer is cancelled. Thus, the timer is accessed concurrently from both threads without protection. If you're using callbacks, code gets slightly more convoluted. The above coroutine can be rewritten as: [connection_pool_thread_safe_callbacks] Thread-safety is disabled by default because strands impose a performance penalty that is avoidable in single-threaded programs. [heading Transport types and TLS] You can use the same set of transports as when working with [reflink any_connection]: plaintext TCP, TLS over TCP or UNIX sockets. You can configure them using [refmem pool_params server_address] and [refmem pool_params ssl]. By default, TLS over TCP will be used if the server supports it, falling back to plaintext TCP if it does not. You can use [refmem pool_params ssl_ctx] to configure TLS options for connections created by the pool. If no context is provided, one will be created for you internally. [heading:benchmarks Benchmarks] A throughput benchmark has been conducted to assess the performance gain provided by `connection_pool`. Benchmark code is under `bench/connection_pool.cpp`. The test goes as follows: * The test consists of N = 10000 logically independent sessions. In an application like a webserver, this would map to handling N HTTP requests. * Every logical session prepares a `SELECT` statement and executes it. The statement matches a single row by primary key and retrieves a single, short string field (a lightweight query). * `num_parallel` = 100 async agents are run in parallel. This means that, at any given point in time, no more than 100 parallel connections to MySQL are made. * The test measures the time elapsed between launching the first async agent and receiving the response for the last query (`ellapsed_time`). * The test is repeated 10 times for each different configuration, and results are averaged. This time is used to measure the throughput, in "connections/s" (as given by `N/ellapsed_time`). * Connection pool scenarios use `pooled_connection::~pooled_connection`, which causes a connection reset to be issued. Raw connection scenarios use [refmem any_connection async_connect] and [refmem any_connection async_close] for every session. All tests are single-threaded. * The server runs MySQL v8.0.33 in a Docker container, in the same machine as the benchmarks. * Benchmarks have been compiled using clang-18 using CMake's Release build type and C++20. They've been run in a Ubuntu 22.04 machine with an 8 core i7-10510U and 32GB of RAM. [$mysql/images/connection_pool_bench.svg] We can see that pooling significantly increases throughput. This is specially true when communication with the server is expensive (as is the case when using TLS over TCP). The performance gain is likely to increase over high-latency networks, and to decrease for heavyweight queries, since the connection establishment has less overall weight. [tip When using TLS or running small and frequent queries, pooling can help you. ] [endsect] ================================================ FILE: doc/qbk/13_1_interfacing_sync_async.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:interfacing_sync_async Interfacing sync and async code: using connection_pool in sync code] [nochunk] As you may already know, we recommend using asynchronous functions over sync ones because they are more versatile and scalable. Additionally, some classes like [reflink connection_pool] do not offer a sync API. If your entire application uses Asio, you can use async functions everywhere as explained in the tutorials. However, some legacy applications are inherently synchronous, and might need to call asynchronous code and wait for it synchronously. This section explains how to handle these cases. We will build a synchronous function that retrieves an employee object from the database given their ID. It will use [reflink connection_pool] and `asio::cancel_after`, which can only be accessed through asynchronous functions. [heading The asio::use_future completion token] [asioreflink use_future use_future] is a [link mysql.tutorial_error_handling.completion_token completion token] that does what we want: it launches an asynchronous operation and returns a `std::future` that will complete when the task finishes. With this knowledge, we can write a first version of our function: [interfacing_sync_async_v1] For this to work, we need a thread that runs the execution context (event loop). This is, calling `get()` on the future doesn't run the event loop. Also note that our function will be called from a thread different to the one running the execution context, so we need to make our pool thread-safe: [interfacing_sync_async_v1_init] [heading Adding timeouts] As you might know, [refmemunq connection_pool async_get_connection] may block indefinitely, so we should use [asioreflink cancel_after cancel_after] to set a timeout. We might be tempted to do this: [interfacing_sync_async_v2] It might not be obvious, but this is a data race. `asio::cancel_after` creates a timer under the hood. This timer is shared between the thread calling `async_get_connection` and the one running the execution context. The race condition goes like this: * The thread calling `async_get_connection` sets up the timer required by `asio::cancel_after`. * In parallel, the thread running the execution context sees that there is a healthy connection and completes the `async_get_connection` operation. As a result, the timer is cancelled. Thus, the timer is accessed concurrently from both threads without protection. Note that this happens even if the pool is thread-safe because the timer is not part of the pool. To work this around, we can use a [@boost:/doc/html/boost_asio/overview/core/strands.html strand], Asio's mechanism to protect against data races. We will create a strand, then enter it and use it to run `async_get_connection`. This is a chain of asynchronous operations, so we can use an [asioreflink deferred deferred] chain to implement it: [interfacing_sync_async_v3] Don't worry if this looks intimidating. Let's break this down into pieces: * A strand is a compliant Asio executor. This means that we can use `asio::dispatch` and similar functions to submit work to it. * [asioreflink dispatch dispatch] submits a piece of work to an executor. We specify the work to execute as a completion token. It uses the executor bound to the passed completion token. * [asioreflink bind_executor bind_executor] binds an executor to a completion token. Here, we're binding the strand to a deferred completion chain. This means that `dispatch` will use the strand to run its work. * When passing [asioreflink deferred deferred] to an async operation, like `dispatch`, it returns a packaged async operation. We can call the operation with any completion token to initiate it. Here, we use `asio::use_future` to transform the operation into a future. If we were in a C++20 coroutine, we could co_await the returned object, too. * The function passed to `deferred` will be executed when the first operation completes, and determines what to do next. This is similar to JavaScript promise chains. Our next operation is `async_get_connection`. * We use `bind_executor` with `asio::deferred` to make any intermediate handlers used by `async_get_connection` and `asio::cancel_after` go through the strand, effectively protecting our timer. * The future will complete once the entire chain finishes. [heading Refactoring to use C++20 coroutines] Deferred compositions can be used even in C++11, but they can get messy pretty fast. Reasoning about their thread safety is non-trivial, either. If you're in C++20 or above, a cleaner approach is to encapsulate all operations involving networking into a coroutine: [interfacing_sync_async_v4] We're keeping all interactions with the `connection_pool` within coroutines, so we don't need to make it thread-safe anymore: [interfacing_sync_async_v4_init] [heading If C++20 is not available] If you can't use C++20, you can still use `asio::spawn` or imitate the behavior of `asio::use_future` with callbacks. This is what the latter could look like: [interfacing_sync_async_v5] It's not as clean, but the idea remains the same. [endsect] ================================================ FILE: doc/qbk/13_async.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:async Going async] [nochunk] Following __Asio__'s convention, all network operations have asynchronous versions with the same name prefixed by `async_`. The last parameter to async operations is a __CompletionToken__, which dictates how the asynchronous operation will be managed and the function's return type. These `async_` functions are called async initiating functions. Every async initiating function has an associated handler type, which dictates how the asynchronous operation communicates its result back to the caller. This handler type always has one of the two following forms: # `void(error_code)`. Used in operations that do not have a proper result, e.g. [refmem connection async_connect]. # `void(error_code, T)`. Used in operations that have a result, e.g. [refmem connection async_prepare_statement] (in this case, `T` is `statement`). All asynchronous functions are overloaded to accept an optional [reflink diagnostics] output parameter. It is populated with any server-provided error information before calling the completion handler. [heading Single outstanding operation per connection] As mentioned in [link mysql.overview.async this section], only a single async operation per connection can be outstanding at a given point in time. If you need to perform queries in parallel, open more connections to the server. [heading Completion tokens] Any completion token you may use with Boost.Asio can also be used with this library. Here are some of the most common: * [*C++20 coroutines], using [asioreflink co_spawn co_spawn] and [asioreflink deferred deferred]. Passing `deferred` to an initiation function returns an object that can be `co_await`'ed. You can combine deferred with [link mysql.async.with_diagnostics with_diagnostics] to get better error reporting. See [*[link mysql.tutorial_async the async tutorial]] for details. * [*Stackful coroutines], which you can use to get coroutine-like functionality in C++11. Access this functionality using [asioreflink spawn spawn] and [asioreflink yield_context yield_context], possibly in conjunction with [link mysql.async.with_diagnostics with_diagnostics]. You need to link against __Context__ to use these coroutines. See [*[link mysql.examples.coroutines_cpp11 this example]] for details. * [*Callbacks]. You can pass in a callable (function pointer or function object) with the same signature as the handler signature specified for the operation. The callable will be called when the operation completes. The initiating function will return `void`. [link mysql.examples.callbacks This example] demonstrates how to use async functions with callbacks. * [*Futures]. In this case, you pass in the constant [asioreflink use_future use_future] as completion token. The initiating function will return one of the following: * `std::future`, if the completion handler has the form given by 1). * `std::future`, if the completion handler has the form given by 2). You can wait for the future by calling `future::get`. If an error occurs, `future::get` will throw an exception. Note that the exception is thrown by Asio itself, and will always be of type `boost::system::system_error`, even if diagnostics were available. * Any other type that satisfies the __CompletionToken__ type requirements. We have listed the most common ones here, but you can craft your own and use it with this library's async operations. [heading:with_diagnostics The with_diagnostics completion token] [reflink with_diagnostics] is a completion token adapter that you can use with async operations when using exceptions. `with_diagnostics` makes your operations throw [reflink error_with_diagnostics], like sync functions do. `with_diagnostics(asio::deferred)` is the default completion token for most operations in this library. If you're using C++20 coroutines as suggested in the tutorials, you're already using it. When using other completion styles that involve exceptions, like `asio::yield_context`, you may need to use `with_diagnostics` explicitly. [link mysql.examples.coroutines_cpp11 This example] shows how to do it. `with_diagnostics` only makes sense when using exceptions. When using error codes, you can keep using `asio::as_tuple` and `asio::redirect_error` normally. [heading Cancellations and timeouts] All async operations in this library support [@boost:/doc/html/boost_asio/overview/core/cancellation.html per-operation cancellation]. All operations support only the `terminal` [asioreflink cancellation_type cancellation_type]. This means that, if an async operation is cancelled, the [reflink connection] object is left in an unspecified state, after which you should close or destroy the connection. In particular, it is [*not] safe to retry the cancelled operation. Supporting cancellation allows you to implement timeouts without explicit support from the library. [link mysql.tutorial_error_handling This tutorial] covers the subject in depth. Note that cancellation happens at the Boost.Asio level, and not at the MySQL operation level. This means that, when cancelling an operation, the current network read or write will be cancelled. The operation may have already reached the server and be executed. As stated above, after an operation is cancelled, the connection is left in an unspecified state, and you should close or destroy it. [endsect] ================================================ FILE: doc/qbk/14_error_handling.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:error_handling Error handling and available overloads] This section describes the different error handling strategies you may use with this library, as well as the different overloads available for each function involving network transfers. This library uses Boost.System error codes and exceptions, like Asio and Beast. Some server-reported errors may include additional diagnostics information. For example, if you issue a query and one of the referenced fields does not exist, the server will return an error message indicating which was the offending field. This library makes these diagnostics available through the following classes and functions: [variablelist [ [[reflink diagnostics]] [ An object containing this extra diagnostic information about an error. [refmem diagnostics server_message] contains the server-generated error string, if any. ] ] [ [[reflink error_with_diagnostics]] [ An exception that inherits from `boost::system::system_error` that contains a `diagnostics` object. ] ] [ [[reflink with_diagnostics]] [A completion token that embeds diagnostics in exceptions thrown by async functions.] ] [ [[reflink is_fatal_error]] [Checks whether an `error_code` is [link mysql.error_handling.fatal fatal] and thus requires re-establishing the connection.] ] ] Every piece of functionality involving network transfers is offered in four versions: * [*Synchronous with exceptions]. When they fail, they throw an [reflink error_with_diagnostics] exception. * [*Synchronous with [reflink error_code] and [reflink diagnostics]]. These functions output an `error_code` and a `diagnostics` object by lvalue reference to report failures. * [*Asynchronous, without `diagnostics`]. When they fail, they call the completion handler with a non-empty `error_code`. * [*Asynchronous, with `diagnostics`]. They have a `diagnostics&` parameter before the `CompletionToken`. When they fail, they set the `diagnostics` parameter to any server-provided diagnostic information, if available, and then call the completion handler with a non-empty `error_code`. [heading Types of errors] This library defines the following types of errors: [table [ [Type of error] [Values contained in...] [Error category] [Description] ] [ [Client errors] [[reflink client_errc] enum] [[reflink get_client_category]] [Failures detected by Boost.MySQL, like corrupt messages.] ] [ [Common server errors] [[reflink common_server_errc] enum] [[reflink get_common_server_category]] [ Errors reported by the server, common to both MySQL and MariaDB. No new codes will be added here, since the two DBs are currently developed independently. ] ] [ [MySQL-specific server errors] [Integer codes in [br][include_file boost/mysql/mysql_server_errc.hpp]] [[reflink get_mysql_server_category]] [ Errors reported by the server, specific to MySQL. New codes will be added in the future. ] ] [ [MariaDB-specific server errors] [Integer codes in [br][include_file boost/mysql/mariadb_server_errc.hpp]] [[reflink get_mariadb_server_category]] [ Errors reported by the server, specific to MariaDB. New codes will be added in the future. ] ] ] Note that new codes are added frequently, so server-specific codes are represented as integers, instead of enums. [heading:fatal Fatal vs. non-fatal errors] When an operation on a established connection (like a query execution) results in an error, two situations may happen: * The connection object is left in a well-known state. You can safely use the object to run further operations without problems. For instance, if a query fails with `common_server_errc::er_no_such_table` because you misspelled a table name, it is safe to run other queries after the failed one. Such errors are called [*non-fatal]. * The connection object is left un an unknown state. Further operations will fail with unpredictable results. You should close and re-establish the connection. These are [*fatal] errors, and include protocol and network errors. You can use [reflink is_fatal_error] to distinguish between fatal and non-fatal error codes. [heading Security notes on diagnostics] The error message given by [refmem diagnostics server_message] [*may contain user-provided input, and should be treated as untrusted]. For certain errors, the MySQL server will include the offending field names and values, which may contain arbitrary input. Please use with caution. This message may contain non-ASCII characters. It's encoded using the connection's character set. [heading:system_result Using boost::system::result] Some functions, like [refmem basic_format_context get], use [@boost:/libs/system/doc/html/system.html#ref_boostsystemresult_hpp `boost::system::result`] to communicate errors. `result` contains either a value (an instance of `T`), or an [reflink error_code], if the operation failed. `result` is similar to `std::expected`, but only requires C++11. Given a `result` object `r`, you can get its contained value calling `r.value()`. If `r` contained an error, a `boost::system::result` exception with the contained error code is thrown. `r.has_value()`, `r.has_error()` and `r.error()` can be used to inspect the object. [endsect] ================================================ FILE: doc/qbk/15_sql_formatting_advanced.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:sql_formatting_advanced Advanced client-side SQL query formatting] [nochunk] [section:expand Formatting queries without executing them] `with_params` is handy, but may fall short in some cases involving queries with complex logic. For these cases, you can use [reflink format_sql] and [reflink format_sql_to] to expand a query without executing it. These APIs don't involve communication with the server. [reflink format_sql] is the simplest, and is akin to `std::format`: [sql_formatting_format_sql] `format_sql` requires a [reflink format_options] instance describing connection configuration, like the character set currently in use. [refmem any_connection format_opts] provides an easy way to retrieve these. [link mysql.sql_formatting_advanced.format_options This section] contains more info about `format_opts`. Some use cases, usually involving conditionals, may not be expressible in terms of a single format string. In such cases, you can use [reflink format_context] and [reflink format_sql_to] to build query strings incrementally: [sql_formatting_incremental_fn] [sql_formatting_incremental_use] [reflink sequence] uses this feature to make formatting ranges easier. Any type that works with `with_params` also does with `format_sql` and `format_sql_to`. These types are said to satisfy the [reflink Formattable] concept. [link mysql.sql_formatting_advanced.reference This table] summarizes such types. [endsect] [section:ranges Formatting ranges with sequence] The [reflink sequence] function can be used when the default range formatting isn't sufficient. If the elements in your range are not formattable, you can pass a user-defined function to `sequence` describing how to format each element: [sql_formatting_sequence_1] By default, elements are separated by commas, but this is configurable: [sql_formatting_sequence_2] You can use `sequence` and [reflink with_params] together. By default, `sequence` copies the range you pass as parameter, making it safer for async code. You can use `std::reference_wrapper` or `std::span` to avoid such copies. [endsect] [section Format specifiers] Some types, like strings, can be formatted in multiple ways. As with `std::format`, you can select how to format them using format specifiers. As we've seen, strings are formatted as single-quoted values by default. If you use the `{:i}` specifier, you can obtain dynamic SQL identifiers, instead: [sql_formatting_specifiers] Specifiers are compatible with explicit indices and named arguments, too. This is equivalent to the previous snippet: [sql_formatting_specifiers_explicit_indices] [endsect] [section Extending format_sql] You can specialize [reflink formatter] to add formatting support to your types: [sql_formatting_formatter_specialization] The type can now be used in [reflink format_sql], [reflink format_sql_to] and [reflink with_params]: [sql_formatting_formatter_use] You can add support for format specifiers for your type by modifying the `parse` function in `formatter`. For example, an `employee` can be formatted differently depending on whether we're using it in an `INSERT` or an `UPDATE`: [sql_formatting_formatter_specialization_specifiers] We can now use it like this: [sql_formatting_formatter_use_specifiers] See the [reflink formatter] reference docs for more info. [endsect] [heading:format_string_syntax Format string syntax] This section extends on the supported syntax for format strings. The syntax is similar to the one in `fmtlib`. A format string is composed of regular text and replacement fields. Regular text is output verbatim, while replacement fields are substituted by formatted arguments. For instance, in `"SELECT {} FROM employee"`, `"SELECT "` and `" FROM EMPLOYEE"` is regular text, and `"{}"` is a replacement field. A `{}` is called an [*automatic indexing] replacement field. Arguments are replaced in the order they were provided to the format function. For instance: [sql_formatting_auto_indexing] A field index can be included within the braces. This is called [*manual indexing]. Indices can appear in any order, and can be repeated: [sql_formatting_manual_indices] Format strings can use either manual or automatic indexing, but can't mix them: [sql_formatting_manual_auto_mix] Unreferenced format arguments are ignored. It's not an error to supply more format arguments than required: [sql_formatting_unused_args] You can output a brace literal by doubling it: [sql_formatting_brace_literal] Format specifiers (e.g. `{:i}`) are supported for some types, but are far less common than in fmtlib, since most types have a single, canonical representation. Specifiers can appear when doing automatic indexing (e.g. `{:i}`) or manual indexing (e.g. `{0:i}`). Types specializing formatters can define custom specifiers. Only printable ASCII characters that are not `{` or `}` can be used as specifiers. Format strings must be encoded according to [refmem format_options charset]. Otherwise, an error will be generated. [heading:error_handling Error handling model] Some values can't be securely formatted. For instance, C++ `double` can be NaN and infinity, which is not supported by MySQL. Strings can contain byte sequences that don't represent valid characters, which makes them impossible to escape securely. When using [reflink with_params] and any of these errors is encountered, the [refmemunq any_connection execute] operation fails, as if a server error had been encountered. This is transparent to the user, so no action is required. [reflink format_sql] reports these errors by throwing `boost::system::system_error` exceptions, which contain an error code with details about what happened. For instance: [sql_formatting_format_double_error] You don't have to use exceptions, though. [reflink basic_format_context] and [reflink format_sql_to] use [link mysql.error_handling.system_result `boost::system::result`], instead. [reflink basic_format_context] contains an error code that is set when formatting a value fails. This is called the ['error state], and can be queried using [refmem format_context_base error_state]. When [refmem basic_format_context get] is called (after all individual values have been formatted), the error state is checked. The `system::result` returned by `get` will contain the error state if it was set, or the generated query if it was not: [sql_formatting_no_exceptions] Rationale: the error state mechanism makes composing formatters easier, as the error state is checked only once. Errors caused by invalid format strings are also reported using this mechanism. [heading:format_options Format options and character set tracking] MySQL has many configuration options that affect its syntax. There are two options that formatting functions need to know in order to work: * Whether the backslash character represents an escape sequence or not. By default it does, but this can be disabled dynamically by setting the [@https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sqlmode_no_backslash_escapes NO_BACKSLASH_ESCAPES] SQL mode. This is tracked by [reflink any_connection] automatically (see [refmem any_connection backslash_escapes]). * The connection's [*current character set]. This determines which multi-byte sequences are valid, and is required to iterate and escape the string. The current character set is tracked by connections as far as possible, but deficiencies in the protocol create cases where the character set may not be known to the client. The current character set can be accessed using [refmem any_connection current_character_set]. [refmem any_connection format_opts] is a convenience function that returns a [link mysql.error_handling.system_result `boost::system::result`]`<`[reflink format_options]`>`. If the connection could not determine the current character set, the result will contain an error. For a reference on how character set tracking works, please read [link mysql.charsets.tracking this section]. [note Prior to connection establishment, the connection's character set is always unknown. Connect your connection before calling `format_opts`. ] [warning Passing an incorrect `format_options` value to formatting functions may cause escaping to generate incorrect values, which may generate vulnerabilities. Stay safe and always use [refmem any_connection format_opts] instead of hand-crafting `format_options` values. Doing this, if the character set can't be safely determined, you will get a `client_errc::unknown_character_set` error instead of a vulnerability. ] [heading Custom string types] [reflink format_sql_to] can be used with string types that are not `std::string`, as long as they satisfy the [reflink OutputString] concept. This includes strings with custom allocators (like `std::pmr::string`) and `boost::static_string`. You need to use [reflink basic_format_context], specifying the string type: [sql_formatting_custom_string] [heading Re-using string memory] You can pass a string value to the context's constructor, to re-use memory: [sql_formatting_memory_reuse] [heading Raw string escaping] If you're building a SQL framework, or otherwise performing very low-level tasks, you may need to just escape a string, without quoting or formatting. You can use [reflink escape_string], which mimics [@https://dev.mysql.com/doc/c-api/8.0/en/mysql-real-escape-string.html `mysql_real_escape_string`]. [note Don't use this unless you know what you're doing. ] [section:reference Types with built-in support for SQL formatting] [table [ [C++ type] [Formatted as...] [Example] ] [ [`signed char`, `short`, `int`, `long`, `long long`] [ Integral literal[br] No format specifiers allowed ] [ [sql_formatting_reference_signed] ] ] [ [`unsigned char`, `unsigned short`, `unsigned int`, `unsigned long`, `unsigned long long`] [ Integral literal[br] No format specifiers allowed ] [ [sql_formatting_reference_unsigned] ] ] [ [`bool`] [ Integral literal `1` if `true`, `0` if `false`[br] No format specifiers allowed ] [ [sql_formatting_reference_bool] ] ] [ [ String types (convertible to [reflink string_view]), including:[br][br] `std::string`[br][br] [reflink string_view][br][br] `std::string_view`[br][br] `const char*`[br][br] ] [ Without format specifiers: single-quoted escaped string literal. Note that `LIKE` special characters (`%` and `_`) are not escaped.[br][br] [*`i`] format specifier: backtick-quoted, escaped SQL identifier.[br][br] [*`r`] format specifier: raw, unescaped SQL. [*Warning]: use this specifier with caution. ] [ [sql_formatting_reference_string] ] ] [ [ Blob types (convertible to `span`), including:[br][br] [reflink blob] (`std::vector`)[br][br] [reflink blob_view] (`span`)[br][br] `std::array` ] [ Hex string literal[br] No format specifiers allowed ] [ [sql_formatting_reference_blob] ] ] [ [`float`, except NaN and inf] [ Floating-point literal, after casting to `double.`[br] MySQL does not support NaNs and infinities. Attempting to format these cause a `client_errc::unformattable_value` error.[br] No format specifiers allowed. ] [ [sql_formatting_reference_float] ] ] [ [`double`, except NaN and inf] [ Floating-point literal.[br] MySQL does not support NaNs and infinities. Attempting to format these cause a `client_errc::unformattable_value` error.[br] No format specifiers allowed. ] [ [sql_formatting_reference_double] ] ] [ [[reflink date]] [ Single quoted, `DATE`-compatible string literal[br] No format specifiers allowed ] [ [sql_formatting_reference_date] ] ] [ [[reflink datetime]] [ Single quoted `DATETIME`-compatible string literal[br] No format specifiers allowed ] [ [sql_formatting_reference_datetime] ] ] [ [[reflink time] and `std::chrono::duration` types convertible to [reflink time]] [ Single quoted `TIME`-compatible string literal[br] No format specifiers allowed ] [ [sql_formatting_reference_time] ] ] [ [`std::nullptr_t`] [ `NULL`[br] No format specifiers allowed ] [ [sql_formatting_reference_nullptr] ] ] [ [ `boost::optional` and `std::optional`, `T` being one of the fundamental types above.[br] Not applicable to custom types or ranges.[br] No format specifiers allowed ] [ Formats the underlying value if there is any.[br] `NULL` otherwise.[br] ] [ [sql_formatting_reference_optional] ] ] [ [[reflink field] and [reflink field_view]] [ Formats the underlying value.[br] No format specifiers allowed ] [ [sql_formatting_reference_field] ] ] [ [ Range of formattable elements. Informally, such ranges support `std::begin()` and `std::end()`, and its iterator `operator*` must yield one of the following: * A [reflink2 WritableFieldTuple WritableField] (i.e. one of the fundamental types above). * A type with a custom formatter. Ranges of ranges are not supported. Note that `vector` and similar types are formatted as blobs, not as sequences. See [reflink2 Formattable the Formattable concept reference] for a formal definition. ] [ Formats each element in the range, separating elements with commas.[br] Specifiers can be applied to individual elements by prefixing them with a colon (`:`) ] [ [sql_formatting_reference_ranges] ] ] [ [ [reflink format_sequence] (as returned by [reflink sequence]) ] [ Formats each element in a range by calling a user-supplied function, separating elements by a glue string (a comma by default).[br] No format specifiers allowed ] [ [sql_formatting_reference_sequence] ] ] [ [Custom type that specializes [reflink formatter]] [ Calls `formatter::parse` and `formatter::format`[br] May accept user-defined format specifiers. ] [] ] [ [[reflink formattable_ref]] [ Formats the underlying value. Can represent any of the types above.[br] Accepts the same format specifiers as the underlying type. ] [ [sql_formatting_reference_formattable_ref] ] ] ] [endsect] [endsect] ================================================ FILE: doc/qbk/16_metadata.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:meta Metadata] In the context of this library, metadata refers to information describing a column retrieved by the execution of a SQL query. The [reflink metadata] class contains information about an individual column. You may access metadata using [refmem results meta] or [refmem execution_state meta]. There is a `metadata` object per column retrieved by the query. The metadata objects are present even if no row was returned by the query (e.g. a `SELECT` on an empty table). [reflink connection] objects have an associated [reflink metadata_mode] that describes how to handle metadata when running a query or a statement: * If [refmem connection meta_mode] is `metadata_mode::minimal` (the default), the library will retain the minimal amount of data required to run the operation. Additional information, like column names, won't be retained. Unless you are using metadata explicitly, you should keep this default, as it consumes slightly less memory. * If [refmem connection meta_mode] is `metadata_mode::full`, the library will retain all the information provided by the server, including column names. Only the [reflink metadata] members that are strings (database, table and field names) are affected by this setting. You may change this setting using [refmem connection set_meta_mode]. For example: [metadata] [endsect] ================================================ FILE: doc/qbk/17_charsets.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:charsets Character sets and collations] [nochunk] According to [mysqllink charset.html MySQL docs], a [*character set] is ['a set of symbols and their respective encodings]. `utf8mb4`, `utf16` and `ascii` are character sets supported by MySQL. A [*collation] is a set of rules to compare characters, and is associated to a single character set. For example, `utf8mb4_spanish_ci` compares `utf8mb4` characters in a case-insensitive way. [heading The connection character set and collation] Every client session has an associated character set and collation. The [*connection's character set determines the encoding for character strings sent to and retrieved from the server]. This includes SQL query strings, string fields and column names in metadata. The connection's collation is used for string literal comparison. The connection's character set and collation can be changed dynamically using SQL. By default, Boost.MySQL connections use `utf8mb4_general_ci`, thus [*using UTF-8 for all strings]. We recommend using this default, as MySQL character sets are easy to get wrong. The connection's character set is not linked to the character set specified for databases, tables and columns. Consider the following declaration: ``` CREATE TABLE test_table( col1 TEXT CHARACTER SET utf16 COLLATE utf16_spanish_ci ); ``` Data stored in `col1` will be encoded using UTF-16 and use `utf16_spanish_ci` for comparisons. However, when sent to the client, [*it will be converted to the connection's character set]. [note `utf8mb4` is how MySQL calls regular UTF-8. Confusingly, MySQL has a character set named `utf8` which is not UTF-8 compliant. ] [heading Connection character set effects] The connection's character set is crucial because it affects the encoding of most string fields. The following is a summary of what's affected: * SQL query strings passed to [refmemunq any_connection async_execute] and [refmemunq any_connection async_prepare_statement] must be sent using the connection's character set. Otherwise, server-side parsing errors may happen. * SQL templates and string values passed to [reflink with_params] and [reflink format_sql] must be encoded using the connection's character set. Otherwise, values will be rejected by Boost.MySQL when composing the query. Connections [link mysql.charsets.tracking track the character set in use] to detect these errors. If you bypass character set tracking (e.g. by using `SET NAMES` instead of [refmemunq any_connection async_set_character_set]), you may run into vulnerabilities. * Statement string parameters passed to [refmem statement bind] should use the connection's character set. Otherwise, MySQL may reject the values. * String values in rows and metadata retrieved from the server use the connection's character set. * Server-supplied diagnostic messages ([refmem diagnostics server_message]) also use the connection's character set. To sum up, to properly use a connection, it's crucial to know the character set it's using. [heading Character set recommendations] The following sections provide a deep explanation on how character sets work in MySQL. If you don't have the time to read them, stick to the following advice: * [*Always use the default UTF-8]. Character sets in MySQL are complex and full of caveats. If you need to use a different encoding in your application, convert your data to/from UTF-8 when interacting with the server. The default [reflink connect_params] ensure that UTF-8 is used, without the need to run any SQL. * [*Don't execute SET NAMES] statements or change the `character_set_client` and `character_set_results` session variables using `async_execute`. This breaks character set tracking, which can lead to vulnerabilities. * Don't use [refmemunq any_connection async_reset_connection] unless you know what you're doing. If you need to reuse connections, use [reflink connection_pool], instead. * Connections obtained from a [reflink connection_pool] always use `utf8mb4`. When connections are returned to the pool, their character set is reset to `utf8mb4`. [heading:tracking Character set tracking] There is a number of actions that can change the connection's character set: * When connecting with [refmemunq any_connection async_connect], a numeric collation ID is supplied to the server. You can change it using [refmem connect_params connection_collation]. The [include_file boost/mysql/mysql_collations.hpp] and [include_file boost/mysql/mariadb_collations.hpp] headers contain available collation IDs. If the server recognizes the passed collation, the connection's character set will be the one associated to the collation. If it doesn't, the connection [*will silently fall back to the server's default character set] (usually `latin1`, which is not Unicode). This can happen when trying to use a newer collation, like `utf8mb4_0900_ai_ci`, with an old MySQL 5.7 server. By default, Boost.MySQL uses `utf8mb4_general_ci`, supported by all servers. * Using [refmemunq any_connection async_reset_connection] resets the connection's character set [*to the server's default character set]. * Using [refmemunq any_connection async_set_character_set] executes a `SET NAMES` statement to set the connection's character set. Executing a pipeline with a set character set stage has the same results. * Manually executing a `SET NAMES`, `SET CHARACTER SET` or modifying the `character_set_client` and `character_set_results` change the connection's character set. [*Don't do this], as it will confuse character set tracking. [reflink any_connection] attempts to track the connection's current character set because it's required to securely perform client-side SQL formatting. This info is available using [refmem any_connection current_character_set], which returns a [reflink character_set] object. The current character set is also used by `async_execute` when a [reflink with_params_t] object is passed, and by [refmem any_connection format_opts]. The MySQL protocol has limited support for character set tracking, so this task requires some help from the user. Some situations can make the current character set to be unknown. If this happens, executing a [reflink with_params_t] fails with `client_errc::unknown_character_set`. [refmem any_connection current_character_set] and [refmem any_connection format_opts] also return this error. Following the above points, this is how tracking works: * Before connection establishment, the current character set is always unknown. * After [refmemunq any_connection async_connect] succeeds, conservative heuristics are used to determine the current character set. If the passed [refmem connect_params connection_collation] is known to be accepted by all supported servers, its associated character set becomes the current one. If the library is not sure, the current character set is left unknown (this is the safe choice to avoid vulnerabilities). Note that leaving [refmemunq connect_params connection_collation] to its default value always sets the current character set to [reflink utf8mb4_charset]. * A successful [refmemunq any_connection async_set_character_set] sets the current character set to the passed one. The same applies for a successful set character set pipeline stage. * Calling [refmemunq any_connection async_reset_connection] makes the current character set unknown. [warning [*Do not execute `SET NAMES`], `SET CHARACTER SET` or any other SQL statement that modifies `character_set_client` using `async_execute`. This will make character set information stored in the client invalid. ] [heading:custom Adding support for a character set] Built-in support is provided for `utf8mb4` ([reflink utf8mb4_charset]) and `ascii` ([reflink ascii_charset]). We strongly encourage you to always use `utf8mb4`. Note that MySQL doesn't support setting the connection's character set to UTF-16 or UTF-32. If you really need to use a different character set, you can implement them by creating [reflink character_set] objects. You can then pass them to functions like [refmemunq any_connection set_character_set] like the built-in ones. [note This is an advanced technique. Don't use it unless you know what you are doing. ] The structure has the following members: * [refmem character_set name] must match the name you would use in `SET NAMES`. * [refmem character_set next_char] is used to iterate the string. It must return the length in bytes of the first code point in the string, or 0 if the code point is invalid. For example, this is how you could implement the `utf8mb4` character set. For brevity, only a small part of the implementation is shown - have a look at the definition of [reflink utf8mb4_charset] for a full implementation. [charsets_next_char] [heading character_set_results and character_set_client] Setting the connection's character set during connection establishment or using [refmemunq any_connection async_set_character_set] has the ultimate effect of changing some session variables. This section lists them as a reference. We [*strongly encourage you not to modify them manually], as this will confuse character set tracking. * [mysqllink server-system-variables.html#sysvar_character_set_client character_set_client] determines the encoding that SQL statements sent to the server should have. This includes the SQL strings passed to [refmemunq any_connection async_execute] and [refmemunq any_connection async_prepare_statement], and string parameters passed to [refmem statement bind]. Not all character sets are permissible in `character_set_client`. For example, UTF-16 and UTF-32 based character sets won't be accepted. * [mysqllink server-system-variables.html#sysvar_character_set_results character_set_results] determines the encoding that the server will use to send any kind of result, including string fields retrieved by [refmem connection execute], metadata like [refmem metadata column_name] and error messages. Note that [refmem metadata column_collation] reflects the character set and collation the server has converted the column to before sending it to the client. In the above example, `metadata::column_collation` will be the default collation for UTF16, rather than `latin1_swedish_ci`. The table below summarizes the encoding used by each piece of functionality in this library: [table:string_encoding [ [Functionality] [Encoding given by...] ] [ [ SQL query strings passed to [refmemunq any_connection async_execute] and [refmemunq any_connection async_prepare_statement] ] [`character_set_client`] ] [ [ Strings used with [reflink with_params] and [reflink format_sql] ] [`character_set_client`] ] [ [String values passed as parameters to [refmem statement bind]] [`character_set_client`] ] [ [ String fields in rows retrieved from the server ] [`character_set_results`] ] [ [ Metadata strings:[br][br] [refmem metadata database][br] [refmem metadata table][br] [refmem metadata original_table][br] [refmem metadata column_name][br] [refmem metadata original_column_name] ] [`character_set_results`] ] [ [Server-generated error messages: [refmem diagnostics server_message]] [`character_set_results`] ] [ [ Informational messages:[br][br] [refmem results info][br] [refmem execution_state info] ] [ ASCII. These can only contain ASCII characters and are always ASCII encoded. More info in [@https://dev.mysql.com/doc/c-api/8.0/en/mysql-info.html this section]. ] ] ] [endsect] ================================================ FILE: doc/qbk/18_time_types.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:time_types Time types: date, datetime and time] [nochunk] The [reflink date], [reflink datetime] and [reflink time] provide support for MySQL's native date and time types. This section expands on how to use them. [heading The date type] [reflink date] represents MySQL __DATE__ in C++. `date` holds the year, month and day components of a date, without any time zone information. It is a type close to the protocol, rather than a vocabulary type. The main reason for using `date` instead of a `std::chrono::time_point` type is that, under certain configurations, MySQL allows storing invalid dates, such as `2020-00-01`. These are not representable as a `std::chrono::time_point`. Unless dealing with these special values, we recommend converting `date`s to a `time_point` before using them. Since `date` represents a local time point, `std::chrono::local_time` is the most accurate representation for it. If your compiler supports C++20 calendar types (as per `__cpp_lib_chrono >= 201907L`), you can use [refmem date as_local_time_point] to perform the cast: [time_types_date_as_local_time_point] If the date is not valid, `as_time_point` will throw an exception. You can query whether a `date` contains a valid date or not using [refmem date valid]: [time_types_date_valid] You can combine it with [refmem date get_local_time_point], which performs an unchecked conversion: [time_types_date_get_local_time_point] [note Using `std::chrono` time zone functionality under MSVC can cause your tooling to report memory leaks. This is [@https://github.com/microsoft/STL/issues/2047 an issue in MSVC's standard library]. See [@https://github.com/microsoft/STL/issues/2504 this suggestion] for a possible workaround. ] If your compiler doesn't support local times, you can use [refmem date get_time_point] or [refmem date as_time_point], instead. These return [refmem date time_point] objects, which are `time_points` that use the system clock. These time points should be interpreted as local times, rather than UTC: [time_types_date_as_time_point] [heading The datetime type] [reflink datetime] represents MySQL __DATETIME__ and __TIMESTAMP__ in C++. `datetime` represents a broken time point, having year, month, day, hour, minute, second and microsecond. The `datetime` object doesn't carry any time zone information with it. The time zone semantics depend on the actual MySQL type: * __DATETIME__ is a "naive" time point object. It represents a time point without any time zone information at all. It is up to the user to interpret which time zone the object is in. * When a __TIMESTAMP__ object is inserted, it is interpreted to be in the connection's local time zone, as given by the __time_zone__ variable, and converted to UTC for storage. When retrieved, it is converted back to the time zone indicated by __time_zone__. The retrieved value of a `TIMESTAMP` field is thus a time point in some local time zone, dictated by the current __time_zone__ variable. As this variable can be changed programmatically from SQL, without the library knowing it, we represent `TIMESTAMP`'s using the `datetime` object, which doesn't include time zone information. MySQL also accepts invalid datetimes (like `2020-00-10 10:20:59.000000`). As with `date`, you can use [refmem datetime as_local_time_point], [refmemunq datetime get_local_time_point] and [refmemunq datetime valid] (or [refmemunq datetime as_time_point] and [refmemunq datetime get_time_point], if your compiler doesn't support C++20 calendar types): [time_types_datetime] [heading TIMESTAMP considerations] When using `TIMESTAMP`, we recommend setting the __time_zone__ session variable to a known value. To illustrate this, consider an event-logging system with the following table definition: [time_types_timestamp_setup] We will be inserting events with an explicit timestamp. We may also want to retrieve events with a timestamp filter. This is what our prepared statements would look like: [time_types_timestamp_stmts] These statements may be run from different parts of our code, or even from different applications. To get consistent results, we must make sure that the time zones used during insertion and retrieval are the same. By default, __time_zone__ gets set to `SYSTEM`, which will use the server's time zone settings. This is not what we want here, so let's change it: [time_types_timestamp_set_time_zone] With this, the insertion code can look like: [time_types_timestamp_insert] The querying code would be: [time_types_timestamp_select] If you don't set __time_zone__, you may apparently get the right results if you run both insertions and queries from clients that don't set `time_zone` and the server doesn't change its configuration. However, relying on this will make your applications brittle, so we don't recommend it. [heading The TIME type] The __TIME__ type is a signed duration with a resolution of one microsecond. It is represented using the [reflink time] type, an alias for a `std::chrono::duration` specialization with microseconds as period. [endsect] ================================================ FILE: doc/qbk/19_templated_connection.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:templated_connection The legacy connection class] [nochunk] You may encounter code using [reflink connection] or its aliases, [reflink tcp_connection], [reflink tcp_ssl_connection], [reflink unix_connection]. This was the main way to create client connections until Boost 1.87, when [reflink any_connection] became stable. `connection` is not deprecated, but we don't recommend using it in new code. [reflink any_connection] is simpler to use and provides the same level of efficiency. [heading Streams and type aliases] [reflink connection] is templated on the [reflink Stream] class, which implements the transport layer to read and write wire bytes. The library provides helper type aliases for the most common cases: [table [ [Transport] [Stream type] [Type alias] ] [ [SSL over TCP] [`boost::asio::ssl::stream`] [ [reflink tcp_ssl_connection] ] ] [ [Plaintext TCP] [`boost::asio::ip::tcp::socket`] [ [reflink tcp_connection] ] ] [ [UNIX sockets] [`boost::asio::local::stream_protocol::socket`] [ [reflink unix_connection] Only available if `BOOST_ASIO_HAS_LOCAL_SOCKETS` is defined. ] ] ] In contrast, [reflink any_connection] is not templated. The same three transports above can be used with `any_connection`. [heading Constructing a connection] `connection`'s constructor takes the same arguments as the underlying `Stream` constructor. For a [reflink tcp_ssl_connection], we need to pass an execution context and a __ssl_context__: [templated_connection_creation] [heading Connection establishment] Use [refmem connection connect] or [refmem connection async_connect] to perform connection establishment. This function takes two parameters: * An endpoint to connect to. The endpoint type depends on the stream type. For TCP connections, it's an [asioreflink ip__tcp/endpoint asio::ip::tcp::endpoint], which holds an IP address and a port. For UNIX sockets, it'd be an [asioreflink local__stream_protocol/endpoint asio::local::stream_protocol::endpoint], holding a UNIX path. * A [reflink handshake_params] instance, containing all the parameters required to perform the MySQL handshake. If you're using TCP, you must perform hostname resolution yourself. For example: [templated_connection_connect] As opposed to `connect_params`, [reflink handshake_params] does not own the strings it contains (like the username and the password). It's your responsibility to keep them alive until the connect operation completes. All functionality in [reflink handshake_params] has an equivalent in [reflink connect_params]. See the [link mysql.templated_connection.reference reference table] for more info. [heading Using a connection] Once connected, [reflink connection] and [reflink any_connection] can be used almost equivalently: [templated_connection_use] Some differences: * Some newer APIs, like [refmemunq any_connection async_set_character_set] and [refmemunq any_connection async_run_pipeline], are not present in [reflink connection]. * By default, `connection`'s completion token is `asio::deferred` instead of `mysql::with_diagnostics(asio::deferred)`. When using coroutines with exceptions, you need to pass `mysql::with_diagnostics` explicitly if you want exceptions with extra info. [heading Terminating a connection] As with `any_connection`, use [refmem connection close] or [refmemunq connection async_close]: [heading TLS support] To use TLS, you must use a [reflink connection] with a [reflink Stream] that supports TLS. A ['TLS-enabled stream] must inherit from [asioreflink ssl__stream_base ssl::stream_base]. The most common is [asioreflink ssl__stream ssl::stream] (used by [reflink tcp_ssl_connection]). When using a stream type that does not support TLS, like [reflink tcp_connection] or [reflink unix_connection], [refmem handshake_params ssl] is ignored. [heading UNIX sockets] To use UNIX sockets, use [reflink unix_connection]: [templated_connection_unix] [heading Handshake and quit] In addition to [refmemunq connection connect] and [refmemunq connection close], `connection` exposes two additional I/O operations: * [refmem connection handshake] is like `connect`, but doesn't connect the underlying `Stream`. * [refmem connection quit] is like `close`, but doesn't close the underlying `Stream`. You can use them like this: [templated_connection_handshake_quit] These functions can be useful in the following cases: * When you want to perform stream connection establishment yourself. For example, when you want to use the range `asio::connect` overloads, as in the example above. * When using an exotic `Stream` type. `connect` and `close` can only be used if the `Stream` type satisfies [reflink SocketStream] - that is, when its lowest layer type is a socket. This holds for all the stream types in the table above, but is not the case for [asioreflink windows__stream_handle windows::stream_handle]. [heading Reconnection] The reconnection capabilities of `connection` are more limited than those of `any_connection`. Concretely, when using TLS-capable streams, a `connection` can't be re-used after it's closed or encounters a fatal error. This is because [asioreflink ssl__stream ssl::stream] can't be re-used. This limitation is not present in [reflink any_connection]. If you are using [reflink tcp_connection] or [reflink unix_connection], or any other stream supporting reconnection, and you want to re-use a connection: * Call [refmem connection close], or manually close the underlying stream, even if you encountered a fatal error. * Call [refmem connection connect] normally, even if the close operation failed. * If your [refmem connection connect] operation failed, you can try opening it again by simply calling [refmem connection connect] again. If your `Stream` type doesn't fulfill the [reflink SocketStream] concept, you need to use [refmemunq connection handshake] and [refmemunq connection quit] instead of `connect` and `close`, and perform transport connection establishment yourself. As with `any_connection`, `connection` doesn't perform any built-in retry strategy. [heading Migrating to any_connection] We recommend migrating code using templated connections to `any_connection`. In most cases, you only need to change connection establishment code to use [reflink connect_params] instead of [reflink handshake_params]. The following table summarizes all the differences between the two connection types, and provides migration paths for each feature you may use: [table:reference [ [Feature] [any_connection] [connection] ] [ [Hostname resolution] [Performed by [refmem any_connection async_connect]] [Needs to be performed manually] ] [ [Credentials] [ [refmem connect_params username], [refmem connect_params password] ] [ [refmem handshake_params username], [refmem handshake_params password] ] ] [ [Database to use] [[refmem connect_params database]] [[refmem handshake_params database]] ] [ [Setting TLS options] [ [refmem any_connection_params ssl_context] ] [ Pass a __ssl_context__ to [reflink tcp_ssl_connection]'s constructor. ] ] [ [TLS negotiation] [[refmem connect_params ssl]. Ignored for if using UNIX sockets. Defaults to `mysql::ssl_mode::enable`.] [[refmem handshake_params ssl]. Ignored if `Stream` is not TLS-enabled. Defaults to `mysql::ssl_mode::require`.] ] [ [Connection collation] [[refmem connect_params connection_collation]] [[refmem handshake_params connection_collation]] ] [ [Enabling multi-queries] [[refmem connect_params multi_queries]] [[refmem handshake_params multi_queries]] ] [ [UNIX sockets] [Use a UNIX socket path in [refmem connect_params server_address]] [Use [reflink unix_connection] and pass a UNIX endpoint to [refmem connection connect]] ] [ [Windows named pipes] [Not available yet] [Use [asioreflink windows__stream_handle windows::stream_handle] as stream type] ] [ [Changing the initial size of the internal network buffer] [[refmem any_connection_params initial_buffer_size]] [Pass a [reflink buffer_params] instance to connection's constructor] ] [ [Changing the network buffer size limit] [[refmem any_connection_params max_buffer_size]] [Not available: no limit on the network buffer size] ] [ [Access the underlying stream] [Unavailable] [[refmem connection stream]] ] [ [Raw handshake and quit] [Unavailable] [[refmem connection handshake], [refmem connection quit]] ] [ [Reconnection] [[refmem any_connection async_connect] can always be used] [ Requires closing the current connection first. Unavailable for [reflink tcp_ssl_connection]. ] ] [ [Changing the connection's character set] [[refmem any_connection async_set_character_set]] [Unavailable] ] [ [Running pipelines] [[refmem any_connection async_run_pipeline]] [Unavailable] ] [ [Including diagnostics in coroutine exceptions] [Enabled by default] [[templated_connection_with_diagnostics]] ] [ [Connection pooling] [[reflink connection_pool]] [Unavailable] ] ] [endsect] ================================================ FILE: doc/qbk/20_1_benchmarks.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:benchmarks Benchmarks against the official connectors] [nochunk] MySQL and MariaDB ship with official C connectors: [@https://dev.mysql.com/downloads/c-api/ libmysqlclient] and [@https://mariadb.com/kb/en/mariadb-connectorc-api-functions/ libmariadb]. Both implement the client/server protocol, as Boost.MySQL does. The question then arises: is Boost.MySQL as fast as the official drivers? [note TL;DR: Boost.MySQL is as fast as the official C APIs, and may be faster under some circumstances. ] [heading Design decisions] These benchmarks focus on [*the speed of the protocol implementation], in an attempt to answer the question above. This should take into account, at least, (de)serialization and buffering. It shouldn't take into account features unique to Boost.MySQL, like the static interface or connection pooling. Both libmysqlclient and libmariadb offer a connection type, similar to [reflink any_connection], with both sync and async primitives. Sync functions are similar to the ones in Boost.MySQL (although C-flavored). Async functions are much lower-level, and often require either integration into a framework (like Asio or libuv) or writing `poll`/`epoll` code by hand. None of these options is trivial. Additionally, sync functions have less overhead, so they're best suited to answer our question. For this reason, [*we only use sync functions] in the benchmarks. The benchmarks [*use prepared statements only]. The official drivers handle text queries (issued by `mysql_real_query`) and prepared statements differently. Rows generated by text queries are returned as strings, and need to be parsed by the user. Boost.MySQL handles this parsing automatically for you. For this reason, comparing text queries doesn't make much sense. Prepared statements are handled similarly, and are better suited for big rows and datasets. [*All tests use a real database]. Neither Boost.MySQL nor the official C clients expose (de)serialization functions. Buffering and optimizing the number of system calls is also critical for efficiency, and can only be measured with real communication. The downside is that database processing introduces delays, and might end up being the bottleneck. The benchmarks try to [*minimize communication overhead by using UNIX sockets]. [heading Benchmark procedure] Benchmark source code can be found in the [@https://github.com/boostorg/mysql/tree/master/bench bench/] folder of the repo. The following benchmarks are performed: * One small row. Executes a statement yielding a single row with 15 fields, including most of the possible types. Each row weighs around 500 bytes. Execution is repeated 10000 times. The Boost.MySQL version uses [refmem any_connection execute]. * One big row. Like the above, but rows have 17 fields, and each row weighs between 72 and 108 KB. The Boost.MySQL version uses [refmem any_connection start_execution], which allows zero-copying. * Many rows. Executes a statement that yields 5000 of the "big rows" described above. The statement is executed only once. The Boost.MySQL version uses [refmem any_connection start_execution] because the resultset size is big. * Statement with parameters. Executes a statement with 17 parameters, roughly matching the "big row" structure described above. Intended to measure serialization speed. The statement is executed 1000 times. Benchmark conditions: * Database: MySQL 8.4.1, running on a Docker container in localhost. * OS: Ubuntu 24.04 * CPU: Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz, 8 cores. * Compiler: g++-14, using CMake's Release config, C++23. * MySQL C API: libmysqlclient24 (as included in the official MySQL 8.4.4 release). * MariaDB C API: libmariadb3 1:10.11.11-0ubuntu0.24.04.2 (official Ubuntu package). * Boost.MySQL: Boost 1.87.0. The header-only version is used (without defining `BOOST_MYSQL_SEPARATE_COMPILATION`), since it's slightly faster. [heading Results] [$mysql/images/bench-protocol.png [align center]] The three libraries exhibit a similar level of performance, which is expected from a correctly implemented binary protocol. Boost.MySQL outperforms libmysqlclient in the single row benchmarks, and is on par with libmariadb. Differences in the other benchmarks don't appear to be statistically significant. During these benchmarks, some potential performance improvement areas have been identified. See [https://github.com/boostorg/mysql/issues/458 this issue] for details. Remember that protocol is just one piece to the whole puzzle. Correctly using features like [reflink connection_pool], [reflink with_params], multi-function operations and multi-queries can make a huge performance difference in your application. Never assume anything and always measure! Acknowledgments: thanks [@https://github.com/LowLevelMahn LowLevelMahn] for proposing the benchmarks. [endsect] ================================================ FILE: doc/qbk/20_pipeline.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:pipeline (Experimental) Pipelines] [nochunk] Functions like [refmemunq any_connection execute], [refmemunq any_connection prepare_statement] and their async counterparts are half-duplex: they write a single request to the server and wait for its response. In contrast, pipelines can increase efficiency by coalescing several requests into a single message, saving round-trips to the server. [warning The MySQL client/server protocol doesn't have explicit support for pipelines. [*From the server's point of view, a pipeline is just a sequence of unrelated requests]. The server will try to execute all stages in each pipeline, regardless of the result of previous stages. Pipelines are considered an [*advanced feature]. Please read [link mysql.pipeline.pitfalls the pitfalls section] for more info. ] [note This feature is experimental. Its API may change in subsequent releases. ] [heading Use cases] You should use pipelines for lightweight operations, dominated by round-trip time. Typical examples include: * Running connection setup code, involving operations like [refmemunq any_connection reset_connection], [refmemunq any_connection set_character_set] or preparing statements. [reflink connection_pool] uses pipelines to clean up connections for re-use. * Preparing several statements, in batch. * Executing and closing a statement in a single round-trip. You should [*avoid] pipelines for the following cases: * When you can achieve the same functionality using semicolon-separated queries (thus using [link mysql.multi_resultset.multi_queries multi-queries] and [link mysql.text_queries client-side SQL formatting]). Multi-queries will stop after the first error, which is usually what you want. See [link mysql.pipeline.pitfalls this section] for more info. * When running heavyweight queries, where the gains in round-trip time are not significant. * When there are dependencies between stages in the pipeline. Lack of protocol support makes this use case impossible. If you're not sure, don't use this feature. [heading Pipeline requests and responses] To run a pipeline, create a [reflink pipeline_request] object describing what should the pipeline do: [pipeline_request] We're using [refmemunq pipeline_request add_execute] and [refmemunq pipeline_request add_prepare_statement] to add stages to our pipeline. You can find all available stage types in the [link mysql.pipeline.reference reference section]. To actually run the pipeline, create a response object and call [refmem any_connection run_pipeline] or [refmemunq any_connection async_run_pipeline]: [pipeline_run] Finally, you can access the statements using [refmem stage_response as_statement]: [pipeline_results] If your pipeline contains an execution stage, it will generate a `results` object that can be accessed using [refmem stage_response as_results]. [heading:error Error handling] If any of the pipeline stages result in an error, the entire [refmemunq any_connection run_pipeline] operation is considered failed. This means that [*if `run_pipipeline` completed successfully, all stages succeeded]. Recall that [*all stages are always run, regardless of the outcome of previous stages]. If `run_pipipeline` fails, you can check which stages succeeded and failed by inspecting responses. [refmem stage_response error] and [refmem stage_response diag] will return error information about failed steps. For instance: [pipeline_errors] [heading:pitfalls Potential pitfalls] All requests in the pipeline are always run, regardless of the outcome of previous requests. As a result, some pipelines can behave non-intuitively: [pipeline_pitfalls_bad] Pipelines aren't the best fit here. Instead, you can express the same logic using semicolon-separated queries: [pipeline_pitfalls_good] Pipeline stages are run sequentially by the server. If any of the stages involves a heavyweight query, the server won't process subsequent stages until the query completes. [heading:reference Pipeline stage reference] In the table below, the following variables are assumed: * `req` is a [reflink pipeline_request]. * `stmt` is a valid [reflink statement]. * `result` is a [reflink results] object. * `conn` is an [reflink any_connection] object. [table:reference [ [Stage type] [Example] [When run, equivalent to...] [Response type] ] [ [ [*Execute]: behaves like [refmem any_connection execute][br][br] [refmem pipeline_request add_execute][br] [refmem pipeline_request add_execute_range] ] [[pipeline_reference_execute]] [[pipeline_reference_execute_equivalent]] [ [reflink results] or an error ] ] [ [ [*Prepare statement]: behaves like [refmem any_connection prepare_statement][br][br] [refmem pipeline_request add_prepare_statement] ] [[pipeline_reference_prepare_statement]] [[pipeline_reference_prepare_statement_equivalent]] [ [reflink statement] or an error ] ] [ [ [*Close statement]: behaves like [refmem any_connection close_statement][br][br] [refmem pipeline_request add_close_statement] ] [[pipeline_reference_close_statement]] [[pipeline_reference_close_statement_equivalent]] [ Possibly empty error ] ] [ [ [*Reset connection]: behaves like [refmem any_connection reset_connection][br][br] [refmem pipeline_request add_reset_connection] ] [[pipeline_reference_reset_connection]] [[pipeline_reference_reset_connection_equivalent]] [ Possibly empty error ] ] [ [ [*Set character set]: behaves like [refmem any_connection set_character_set][br][br] [refmem pipeline_request add_set_character_set] ] [[pipeline_reference_set_character_set]] [[pipeline_reference_set_character_set_equivalent]] [ Possibly empty error ] ] ] [endsect] ================================================ FILE: doc/qbk/21_examples.qbk ================================================ [/ Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [/ This file was auto-generated by examples_qbk.py. Do not edit directly ] [section:examples Examples] To run the examples, please go through the [link mysql.examples.setup setup] first. Here is a list of available examples: [heading Tutorials] Self-contained programs demonstrating the basic concepts. * [link mysql.examples.tutorial_sync Tutorial 1 listing: hello world!] * [link mysql.examples.tutorial_async Tutorial 2 listing: going async with C++20 coroutines] * [link mysql.examples.tutorial_with_params Tutorial 3 listing: queries with parameters] * [link mysql.examples.tutorial_static_interface Tutorial 4 listing: the static interface] * [link mysql.examples.tutorial_updates_transactions Tutorial 5 listing: UPDATEs, transactions and multi-queries] * [link mysql.examples.tutorial_connection_pool Tutorial 6 listing: connection pools] * [link mysql.examples.tutorial_error_handling Tutorial 7 listing: error handling] [heading Simple programs] Self-contained programs demonstrating more advanced concepts and techniques. * [link mysql.examples.inserts INSERTs, last_insert_id() and NULL values] * [link mysql.examples.deletes DELETEs and affected_rows()] * [link mysql.examples.prepared_statements Prepared statements] * [link mysql.examples.disable_tls Disabling TLS for a connection] * [link mysql.examples.tls_certificate_verification Setting TLS options: enabling TLS certificate verification] * [link mysql.examples.metadata Metadata] * [link mysql.examples.multi_function Reading rows in batches with multi-function operations] * [link mysql.examples.callbacks Callbacks (async functions in C++11)] * [link mysql.examples.coroutines_cpp11 Stackful coroutines (async functions in C++11)] * [link mysql.examples.unix_socket UNIX sockets] * [link mysql.examples.batch_inserts Batch inserts using client-side query formatting] * [link mysql.examples.batch_inserts_generic Generic batch inserts with Boost.Describe] * [link mysql.examples.dynamic_filters Queries with dynamic filters] * [link mysql.examples.patch_updates Dynamic UPDATE queries with PATCH-like semantics] * [link mysql.examples.source_script Sourcing a .sql file using multi-queries] * [link mysql.examples.pipeline (Experimental) Pipelines] [heading Advanced examples] Programs implementing real-world functionality. # [@https://github.com/anarthal/servertech-chat The BoostServerTech chat project uses Boost.MySQL and Boost.Redis to implement a chat server] [heading Setup] To run the examples, you need a MySQL server you can connect to. Examples make use of a database named `boost_mysql_examples`. The server hostname and credentials (username and password) are passed to the examples via the command line. You can spin up a server quickly by using Docker: [!teletype] ``` # Remove the "-v /var/run/mysqld:/var/run/mysqld" part if you don't need UNIX sockets > docker run --name some-mysql -p 3306:3306 -v /var/run/mysqld:/var/run/mysqld -d -e MYSQL_ROOT_PASSWORD= -e MYSQL_ALLOW_EMPTY_PASSWORD=1 -d mysql # All the required data can be loaded by running example/db_setup.sql. # If you're using the above container, the root user has a blank password > mysql -u root < example/db_setup.sql ``` Please note that this container is just for demonstrative purposes, and is not suitable for production. The root MySQL user for these containers is `root` and has an empty password. [section:tutorial_sync Tutorial 1 listing: hello world!] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_tutorial_sync] [endsect] [section:tutorial_async Tutorial 2 listing: going async with C++20 coroutines] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_tutorial_async] [endsect] [section:tutorial_with_params Tutorial 3 listing: queries with parameters] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_tutorial_with_params] [endsect] [section:tutorial_static_interface Tutorial 4 listing: the static interface] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_tutorial_static_interface] [endsect] [section:tutorial_updates_transactions Tutorial 5 listing: UPDATEs, transactions and multi-queries] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_tutorial_updates_transactions] [endsect] [section:tutorial_connection_pool Tutorial 6 listing: connection pools] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_tutorial_connection_pool] [endsect] [section:tutorial_error_handling Tutorial 7 listing: error handling] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_tutorial_error_handling] [endsect] [section:inserts INSERTs, last_insert_id() and NULL values] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_inserts] [endsect] [section:deletes DELETEs and affected_rows()] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_deletes] [endsect] [section:prepared_statements Prepared statements] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_prepared_statements] [endsect] [section:disable_tls Disabling TLS for a connection] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_disable_tls] [endsect] [section:tls_certificate_verification Setting TLS options: enabling TLS certificate verification] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_tls_certificate_verification] [endsect] [section:metadata Metadata] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_metadata] [endsect] [section:multi_function Reading rows in batches with multi-function operations] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_multi_function] [endsect] [section:callbacks Callbacks (async functions in C++11)] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_callbacks] [endsect] [section:coroutines_cpp11 Stackful coroutines (async functions in C++11)] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_coroutines_cpp11] [endsect] [section:unix_socket UNIX sockets] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_unix_socket] [endsect] [section:batch_inserts Batch inserts using client-side query formatting] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_batch_inserts] [endsect] [section:batch_inserts_generic Generic batch inserts with Boost.Describe] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_batch_inserts_generic] [endsect] [section:dynamic_filters Queries with dynamic filters] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_dynamic_filters] [endsect] [section:patch_updates Dynamic UPDATE queries with PATCH-like semantics] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_patch_updates] [endsect] [section:source_script Sourcing a .sql file using multi-queries] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_source_script] [endsect] [section:pipeline (Experimental) Pipelines] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_pipeline] [endsect] [section:http_server_cpp20 A REST API server that uses C++20 coroutines] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_http_server_cpp20_main_cpp] [example_http_server_cpp20_types_hpp] [example_http_server_cpp20_error_hpp] [example_http_server_cpp20_error_cpp] [example_http_server_cpp20_repository_hpp] [example_http_server_cpp20_repository_cpp] [example_http_server_cpp20_handle_request_hpp] [example_http_server_cpp20_handle_request_cpp] [example_http_server_cpp20_server_hpp] [example_http_server_cpp20_server_cpp] [endsect] [section:http_server_cpp14_coroutines A C++14 REST API server that uses asio::yield_context] This example assumes you have gone through the [link mysql.examples.setup setup]. [example_http_server_cpp14_coroutines_main_cpp] [example_http_server_cpp14_coroutines_types_hpp] [example_http_server_cpp14_coroutines_repository_hpp] [example_http_server_cpp14_coroutines_repository_cpp] [example_http_server_cpp14_coroutines_handle_request_hpp] [example_http_server_cpp14_coroutines_handle_request_cpp] [example_http_server_cpp14_coroutines_server_hpp] [example_http_server_cpp14_coroutines_server_cpp] [endsect] [endsect] ================================================ FILE: doc/qbk/helpers/ExecutionRequest.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:boost__mysql__ExecutionRequest ExecutionRequest concept] An execution request represents a SQL statement to be executed by the server, plus any required parameters. It may model a plain text query, a client-side formatted query with parameters, or a prepared statement handle with parameters. Formally, a type `T` is a `ExecutionRequest` if it fulfills any of the following: * It is convertible to [reflink string_view]. In this case, the execution request contains a text query to be run by the server. * An instantiation of the [reflink bound_statement_tuple] class, or a (possibly cv-qualified) reference to it. * An instantiation of the [reflink bound_statement_iterator_range] class, or a (possibly cv-qualified) reference to it. * An instantiation of the [reflink with_params_t] class, or a (possibly cv-qualified) reference to it. This definition may be extended in future versions, but the above types will still satisfy `ExecutionRequest`. [endsect] ================================================ FILE: doc/qbk/helpers/ExecutionStateType.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:boost__mysql__ExecutionStateType ExecutionStateType concept] A type `T` satisfies `ExecutionStateType` if either: * It's exactly the [reflink execution_state] class. * It's an instantiation of the [reflink static_execution_state] template class. [endsect] ================================================ FILE: doc/qbk/helpers/FieldViewFwdIterator.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:boost__mysql__FieldViewFwdIterator FieldViewFwdIterator concept] A type `T` fulfills `FieldViewFwdIterator` if it fulfills the `LegacyForwardIterator` standard concept, and `std::iterator_traits::reference` is convertible to [reflink field_view]. [endsect] ================================================ FILE: doc/qbk/helpers/Formattable.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:boost__mysql__Formattable Formattable concept] A type `T` is `Formattable` if it can be passed as a format argument to SQL formatting functions, like [reflink format_sql]. Formally, let `T` be any type, and `U` the result of stripping cv-qualifiers and references from `T`. `T` satisfies `Formattable` if any of the following are true: * `U` satisfies [reflink2 WritableFieldTuple WritableField]. This includes scalar types and optionals. * The class [reflink formatter] has been specialized for `U`. * `T` is a formattable range, or a reference to one. Formally, given a variable `t` of type `T` (that might be a reference), `T` is a formattable range if: * `std::begin(t)` and `std::end(t)` return an iterator/sentinel pair that can be compared for (in)equality. * The type `std::decay_t` is a [reflink2 WritableFieldTuple WritableField] or has a specialized formatter. In other words, the range's element type must be either an elemental type or have a custom formatted defined, but must not be a range. * `U` does not satisfy [reflink2 WritableFieldTuple WritableField] (i.e. `vector` is formatted as a blob, not as a sequence). * `U` is [reflink formattable_ref]. For a reference table on built-in formattable types, see [link mysql.sql_formatting_advanced.reference this section]. [endsect] ================================================ FILE: doc/qbk/helpers/OutputString.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:boost__mysql__OutputString OutputString concept] An `OutputString` is a narrow character string type that can be used as output for operations that generate a string. Types like `std::string`, `std::basic_string` or `boost::static_string` satisfy this concept. Formally, a type `T` satisfies `OutputString` if all of the following are true: * It satisfies the `std::movable` concept. * Has an `append(const char* data, std::size_t size)` member function that can be used to add a character range to the string. * Has a `clear()` function that can be used to remove all characters from the string. [endsect] ================================================ FILE: doc/qbk/helpers/ResultsType.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:boost__mysql__ResultsType ResultsType concept] A type `T` satisfies `ResultsType` if either: * It's exactly the [reflink results] class. * It's an instantiation of the [reflink static_results] template class. [endsect] ================================================ FILE: doc/qbk/helpers/SocketStream.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:boost__mysql__SocketStream SocketStream concept] `SocketStream` should meet the [reflink Stream] requirements. Additionally, it should have a `lowest_layer_type` member type, and a `lowest_layer` member function, returning a `lowest_layer_type&`, following Asio's layered stream model. Additionally, `lowest_layer_type` should inherit from an instantiation of [asioreflink basic_stream_socket basic_stream_socket]. The types `boost::asio::basic_stream_socket` and `boost::asio::ssl::stream>` meet these requirements. [endsect] ================================================ FILE: doc/qbk/helpers/StaticRow.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:boost__mysql__StaticRow StaticRow concept] A `StaticRow` is a C++ type that can be used to model a row within the static interface (i.e. can be passed as template parameter to [reflink static_results] and [reflink static_execution_state]). Formally, a type `T` is a `StaticRow` if either of the following is true: * It is a non-const `struct` annotated with Boost.Describe data (i.e., having `boost::describe::has_describe_members::value == true`), and all the described members fulfill the `ReadableField` exposition-only concept. * It is a non-const `std::tuple` instantiation, and all of its types fulfill the `ReadableField` exposition-only concept. * It is an instantiation of the [reflink pfr_by_name] or [reflink pfr_by_position] marker types using a type `Underlying` that satisfies the following: * Is reflectable using Boost.PFR. For C++17 and later, this means satisfying [@boost:/doc/html/boost_pfr/limitations_and_configuration.html `SimpleAggregate`]. For C++14, stricter requirements apply - see [@boost:/doc/html/boost_pfr/limitations_and_configuration.html the Boost.PFR docs] for more info. * Is a non-const object type (i.e. not a `union` or built-in type). * All of its fields (as given by `pfr::structure_to_tuple`) satisfy `ReadableField`. Note that row types with no fields (like empty Describe structs and empty tuples) are valid `StaticRow`s. A `ReadableField` is C++ type that can be used to model a single value in a database row. A type `F` is a `ReadableField` if it is any of the types listed [link mysql.static_interface.readable_field_reference in this table]. The set of readable field types is currently fixed and can't be extended by the user. If this is something you have interest in, [@https://github.com/boostorg/mysql/issues/new please file an issue] with your use case to the repo. The set of allowable types may be extended in future releases, both for fields and for rows. [endsect] ================================================ FILE: doc/qbk/helpers/Stream.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:boost__mysql__Stream Stream concept] `Stream` should meet both the `AsyncStream` and `SyncStream` requirements, [@boost:/libs/beast/doc/html/beast/concepts/streams.html as defined by Boost.Beast here]. Additionally, the type `boost::asio::ssl::stream` should be valid and usable to both read and write data over SSL. [endsect] ================================================ FILE: doc/qbk/helpers/WritableFieldTuple.qbk ================================================ [/ Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] [section:boost__mysql__WritableFieldTuple WritableField and WritableFieldTuple concepts] A type is a `WritableField` if it can be used to represent a statement parameter value to be sent to the server. A type `T` satisfies `WritableField` if it is [link mysql.prepared_statements.writable_field_reference any of the types listed in this table]. More types may be added in future releases. A type `T` is a `WritableFieldTuple` if it's a `std::tuple` specialization, or a reference to one, and all element types fulfill `WritableField`, or are (possibly cv-qualified) references to `WritableField` types. Empty tuples satisfy `WritableFieldTuple`. [endsect] ================================================ FILE: doc/qbk/helpers/quickref.xml ================================================ Classes any_address any_connection any_connection_params bad_field_access basic_format_context bound_statement_tuple bound_statement_iterator_range buffer_params (legacy) character_set connect_params connection (legacy) connection_pool constant_string_view date datetime diagnostics error_with_diagnostics execution_state field field_view format_arg format_context_base formattable_ref formatter format_options format_sequence handshake_params (legacy) host_and_port metadata pfr_by_name pfr_by_position pipeline_request (experimental) pool_params pooled_connection results resultset_view resultset row row_view rows rows_view stage_response (experimental) statement static_execution_state static_results unix_path with_diagnostics_t with_params_t Enumerations address_type client_errc column_type common_server_errc field_kind metadata_mode quoting_context ssl_mode Constants ascii_charset default_initial_read_buffer_size default_port default_port_string max_date min_date max_datetime min_datetime max_time min_time utf8mb4_charset Functions escape_string format_sql format_sql_to get_client_category get_common_server_category get_mysql_server_category get_mariadb_server_category make_error_code runtime sequence throw_on_error (legacy) with_diagnostics with_params Type aliases blob blob_view days error_code format_context make_tuple_element_t metadata_collection_view sequence_range_t string_view tcp_connection (legacy) tcp_ssl_connection (legacy) time underlying_row_t unix_connection (legacy) unix_ssl_connection (legacy) Concepts ExecutionRequest ExecutionStateType FieldViewFwdIterator Formattable OutputString ResultsType SocketStream StaticRow Stream WritableField WritableFieldTuple Reference tables Dynamic interface type mappings ReadableField types WritableField types Formattable types Pipeline stage reference (experimental) ================================================ FILE: doc/upgrade_1_82.md ================================================ # Upgrade instructions to 1.82 This document describes how to upgrade from 0.2.x to 1.82 (please remember that 1.82 is the first stable release in this library, and the versioning follows Boost versioning). This is a major upgrade, since a lot of changes have been made since the review took place. If you encounter any problems, please file an issue against the repository, and we will be happy to help :) * The `value` class has been replaced by `field_view`. The generic accessors have been replaced by type-specific functions. Conversions, `get_optional` and `get_std_optional` have been removed, in favor of an interface more similar to `json::value`. The class `field` has been added to be able to take ownership of a `field_view`. * **Action**: replace `value` by `field_view`. * Statements are no longer I/O objects. `connection::prepare_statement` works the same, but execution requires going through the connection. * **Action**: replace `stmt.execute(...)` by `conn.execute_statement(stmt, ...)`, `stmt.close()` by `conn.close_statement(stmt)`. * Resultsets as I/O objects have been removed, and executing queries and statements is now simpler. There are now two ways to run queries or statements: * `connection::query` and `connection::execute_statement` now read all rows into memory, into a `results` object (which is similar to `resultset`, but is a plain data object and contains all the rows). These functions are equivalent to the old `connection::query`/`statement::execute` plus `resultset::read_all`. * **Action**: if you were using `conn.query(...).read_all()` (or similar), replace it by `conn.query(...)`. * `connection::start_query` and `statement::start_execution` behave similarly to the old `connection::query` and `statement::execute`. They use a data structure called `execution_state`, which is similar to the old `resultset`. * **Action**: if you were reading rows using `read_one` or `read_many`, consider whether your application requires reading row-by-row or not. If it doesn't, apply the point above. If it does, replace `conn.query(...)` by `conn.start_query(...)`, and `result.read_one(...)` by `conn.read_some_rows(...)`. There is no function to read a single row. * Statement parameters are now passed as a `std::tuple` to `execute_statement`, rather than a collection. * **Action**: * If you were using `stmt.execute(make_values(a, b), ...)`, replace it by `conn.execute_statement(std::make_tuple(a, b), ...)` * If you were using a collection and can't migrate to a `std::tuple` (because the number of parameters is unknown at compile-time), then have a look at [this solution](https://github.com/boostorg/mysql/issues/110). * `error_info` has been renamed to `diagnostics` and `error_info::message()` to `diagnostics::server_message()`. The message is no longer included by default in the thrown exceptions's `what()`. This is because the message may not be UTF-8 compatible. * **Action**: apply the renames. If you're using exceptions, catch the new `error_with_diagnostics` exception type to retrieve the server message. * The `date` and `datetime` types are now custom types, instead of aliases for `std::chrono`. This enables them to represent zero and invalid dates. * **Action**: to get a `time_point` from a `date` or `datetime`, use `as_time_point` or `get_time_point` + `valid`. * Binary types (`BLOB`, `BINARY`, `VARBINARY`, `GEOMETRY`) are now represented as a special type `blob_view`/`blob`. * **Action**: if you handle these types in your application, use `is_blob`, `get_blob` and `as_blob` functions in `field_view`. * The `collation` enum has been removed in favor of plain integers. * **Action**: if you were using collations explicitly, replace the enumerator by the collation ID. You can find them in `` and ``. * `connection_params` has been reverted to `handshake_params`. * **Action**: replace occurrences of `connection_params` by `handshake_params`. * `row` is no longer streamable. The stream operation on `row` wasn't a universal agreement. * **Action**: if you were streaming rows, switch to using a loop and streaming individual `field_view`s, instead. * Metadata strings is no longer retained by default, to save allocations. * **Action**: if your code uses metadata strings (e.g. `metadata::column_name()`, use `conn.set_metadata_mode(metadata_mode::full))` before initiating any query/statement execution. * The library now uses `boost::core::string_view` (under the alias `boost::mysql::string_view`) rather than `boost::string_view`. * **Action**: if you were using `boost::string_view` directly, change it to `boost::mysql::string_view`. * `errc` has been split into `client_errc`, `server_errc` and server-specific error codes (which don't have an enum). * **Action**: if you were referencing `errc` values directly, replace `errc` by `client_errc` or `server_errc`. * `field_type` has been renamed to `column_type`. * **Action**: apply the rename. * The following members in `metadata` have been renamed: * `field_name` => `column_name` * `original_field_name` => `original_column_name` * `character_set` => `column_collation` * **Action**: apply the renames. * `connection::next_layer()` has been renamed to `connection::stream()`. * **Action**: apply the rename. * `no_statement_params` has been removed. * **Action**: replace it by an empty `std::make_tuple()`. ================================================ FILE: example/1_tutorial/1_sync.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // //[example_tutorial_sync /** * Creates a connection, establishes a session and * runs a simple "Hello world!" query. * * This example uses synchronous functions and handles errors using exceptions. */ #include #include #include #include #include #include //[tutorial_sync_namespaces namespace mysql = boost::mysql; namespace asio = boost::asio; //] void main_impl(int argc, char** argv) { if (argc != 4) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } const char* hostname = argv[3]; const char* username = argv[1]; const char* password = argv[2]; //[tutorial_sync_connection // The execution context, required to run I/O operations. asio::io_context ctx; // Represents a connection to the MySQL server. mysql::any_connection conn(ctx); //] //[tutorial_sync_main //[tutorial_sync_connect // The hostname, username and password to use mysql::connect_params params; params.server_address.emplace_host_and_port(hostname); params.username = username; params.password = password; // Connect to the server conn.connect(params); //] //[tutorial_sync_query // Issue the SQL query to the server const char* sql = "SELECT 'Hello world!'"; mysql::results result; conn.execute(sql, result); //] //[tutorial_sync_results // Print the first field in the first row std::cout << result.rows().at(0).at(0) << std::endl; //] //[tutorial_sync_close // Close the connection conn.close(); //] //] } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] ================================================ FILE: example/1_tutorial/2_async.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_tutorial_async /** * This example is analogous to the synchronous tutorial, but uses async functions * with C++20 coroutines, instead. */ #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; /** * The main coroutine. * It must have a return type of asio::awaitable. * Our coroutine does not communicate any result back, so T=void. * * The coroutine will suspend every time we call one of the asynchronous functions, saving * all information it needs for resuming. When the asynchronous operation completes, * the coroutine will resume in the point it was left. * We use the same program structure as in the sync world, replacing * sync functions by their async equivalents and adding co_await in front of them. */ //[tutorial_async_coro asio::awaitable coro_main( mysql::any_connection& conn, std::string_view server_hostname, std::string_view username, std::string_view password ) { // The hostname, username, password and database to use. // TLS is used by default. mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = username; params.password = password; // Connect to the server co_await conn.async_connect(params); // Issue the SQL query to the server const char* sql = "SELECT 'Hello world!'"; mysql::results result; co_await conn.async_execute(sql, result); // Print the first field in the first row std::cout << result.rows().at(0).at(0) << std::endl; // Close the connection co_await conn.async_close(); } //] void main_impl(int argc, char** argv) { if (argc != 4) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } //[tutorial_async_connection // The execution context, required to run I/O operations. asio::io_context ctx; // Represents a connection to the MySQL server. mysql::any_connection conn(ctx); //] //[tutorial_async_co_spawn // Enqueue the coroutine for execution. // This does not wait for the coroutine to finish. asio::co_spawn( // The execution context where the coroutine will run ctx, // The coroutine to run. This must be a function taking no arguments // and returning an asio::awaitable [&conn, argv] { return coro_main(conn, argv[3], argv[1], argv[2]); }, // Callback to run when the coroutine completes. // If any exception is thrown in the coroutine body, propagate it to terminate the program. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); //] //[tutorial_async_run // Calling run will actually execute the coroutine until completion ctx.run(); //] } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/1_tutorial/3_with_params.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_tutorial_with_params /** * This example shows how to issue queries with parameters containing * untrusted input securely. Given an employee ID, it prints their full name. * The example builds on the previous async tutorial. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; //[tutorial_with_params_coroutine asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, std::string_view password, std::int64_t employee_id ) { //[tutorial_with_params_connection // The connection will use the same executor as the coroutine mysql::any_connection conn(co_await asio::this_coro::executor); //] //[tutorial_with_params_connect_params // The hostname, username, password and database to use. mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = username; params.password = password; params.database = "boost_mysql_examples"; //] // Connect to the server co_await conn.async_connect(params); //[tutorial_with_params_execute // Execute the query with the given parameters. When executed, with_params // expands the given query string template and sends it to the server for execution. // {} are placeholders, as in std::format. Values are escaped as required to prevent // SQL injection. mysql::results result; co_await conn.async_execute( mysql::with_params("SELECT first_name, last_name FROM employee WHERE id = {}", employee_id), result ); //] //[tutorial_with_params_results // Did we find an employee with that ID? if (result.rows().empty()) { std::cout << "Employee not found" << std::endl; } else { // Print the retrieved details. The first field is the first name, // and the second, the last name. mysql::row_view employee = result.rows().at(0); std::cout << "Employee's name is: " << employee.at(0) << ' ' << employee.at(1) << std::endl; } //] // Close the connection co_await conn.async_close(); } //] void main_impl(int argc, char** argv) { if (argc != 5) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } // The execution context, required to run I/O operations. asio::io_context ctx; // Enqueue the coroutine for execution. asio::co_spawn( // The execution context where the coroutine will run ctx, // The coroutine to run. This must be a function taking no arguments // and returning an asio::awaitable [argv] { return coro_main(argv[3], argv[1], argv[2], std::stoi(argv[4])); }, // Callback to run when the coroutine completes. // If any exception is thrown in the coroutine body, propagate it to terminate the program. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/1_tutorial/4_static_interface.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) && BOOST_PFR_CORE_NAME_ENABLED //[example_tutorial_static_interface /** * This example shows how to use the static interface to parse * the results of a query into a C++ struct. * Like the previous tutorial, given an employee ID, * it prints their full name. * * It uses Boost.Pfr for reflection, which requires C++20. * You can backport it to C++14 if you need by using Boost.Describe. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; //[tutorial_static_fn void print_employee(std::string_view first_name, std::string_view last_name) { std::cout << "Employee's name is: " << first_name << ' ' << last_name << std::endl; } //] //[tutorial_static_struct // Should contain a member for each field of interest present in our query. // Declaration order doesn't need to match field order in the query. // Field names should match the ones in our query struct employee { std::string first_name; std::string last_name; }; //] asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, std::string_view password, std::int64_t employee_id ) { // Represents a connection to the MySQL server. // The connection will use the same executor as the coroutine mysql::any_connection conn(co_await asio::this_coro::executor); // The hostname, username, password and database to use. mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = username; params.password = password; params.database = "boost_mysql_examples"; // Connect to the server co_await conn.async_connect(params); //[tutorial_static_execute // Using static_results will parse the result of our query // into instances of the employee type. Fields will be matched // by name, instead of by position. // pfr_by_name tells the library to use Boost.Pfr for reflection, // and to match fields by name. mysql::static_results> result; // Execute the query with the given parameters, performing the required // escaping to prevent SQL injection. co_await conn.async_execute( mysql::with_params("SELECT first_name, last_name FROM employee WHERE id = {}", employee_id), result ); //] //[tutorial_static_results // Did we find an employee with that ID? if (result.rows().empty()) { std::cout << "Employee not found" << std::endl; } else { // Print the retrieved details const employee& emp = result.rows()[0]; print_employee(emp.first_name, emp.last_name); } //] // Close the connection co_await conn.async_close(); } void main_impl(int argc, char** argv) { if (argc != 5) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } // The execution context, required to run I/O operations. asio::io_context ctx; // Enqueue the coroutine for execution. asio::co_spawn( // The execution context where the coroutine will run ctx, // The coroutine to run. This must be a function taking no arguments // and returning an asio::awaitable [argv] { return coro_main(argv[3], argv[1], argv[2], std::stoi(argv[4])); }, // Callback to run when the coroutine completes. // If any exception is thrown in the coroutine body, propagate it to terminate the program. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/1_tutorial/5_updates_transactions.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) && BOOST_PFR_CORE_NAME_ENABLED //[example_tutorial_updates_transactions /** * This example demonstrates how to use UPDATE statements, * transactions and semicolon-separated queries. * * The program updates the first name of an employee given their ID * and prints their full details. * * It uses Boost.Pfr for reflection, which requires C++20. * You can backport it to C++14 if you need by using Boost.Describe. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; // As in the previous tutorial, this struct models // the data returned by our SELECT query. It should contain a member // for each field of interest, with a matching name. struct employee { std::string first_name; std::string last_name; }; // The main coroutine asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, std::string_view password, std::int64_t employee_id, std::string_view new_first_name ) { // Create a connection. // Will use the same executor as the coroutine. mysql::any_connection conn(co_await asio::this_coro::executor); //[section_connection_establishment_multi_queries //[tutorial_updates_transactions_connect // The server host, username, password and database to use. // Setting multi_queries to true makes it possible to run several // semicolon-separated queries with async_execute. mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = std::move(username); params.password = std::move(password); params.database = "boost_mysql_examples"; params.multi_queries = true; // Connect to the server co_await conn.async_connect(params); //] //] // Perform the update and retrieve the results: // 1. Begin a transaction block. Further updates won't be visible to // other transactions until this one commits. // 2. Perform the update. // 3. Retrieve the employee we just updated. Since we're in a transaction, // this will be the employee we just updated (if any), // without the possibility of other transactions interfering. // 4. Commit the transaction and make everything visible to other transactions. // If any of the previous steps fail, the commit won't be run, and the // transaction will be rolled back when the connection is closed. //[tutorial_updates_transactions_static // MySQL returns one resultset for each query, so we pass 4 params to static_results //<- // clang-format off //-> mysql::static_results< std::tuple<>, // START TRANSACTION doesn't generate rows std::tuple<>, // The UPDATE doesn't generate rows mysql::pfr_by_name, // The SELECT generates employees std::tuple<> // The COMMIT doesn't generate rows > result; //<- // clang-format on //-> co_await conn.async_execute( mysql::with_params( "START TRANSACTION;" "UPDATE employee SET first_name = {0} WHERE id = {1};" "SELECT first_name, last_name FROM employee WHERE id = {1};" "COMMIT", new_first_name, employee_id ), result ); // We've run 4 SQL queries, so MySQL has returned us 4 resultsets. // The SELECT is the 3rd resultset. Retrieve the generated rows. // employees is a span auto employees = result.rows<2>(); if (employees.empty()) { std::cout << "No employee with ID = " << employee_id << std::endl; } else { const employee& emp = employees[0]; std::cout << "Updated: employee is now " << emp.first_name << " " << emp.last_name << std::endl; } //] // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { if (argc != 6) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [=] { return coro_main(argv[3], argv[1], argv[2], std::stoi(argv[4]), argv[5]); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); std::cout << "Done\n"; } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/1_tutorial/6_connection_pool.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) && BOOST_PFR_CORE_NAME_ENABLED //[example_tutorial_connection_pool /** * This example demonstrates how to use connection_pool * to implement a server for a simple custom TCP-based protocol. * It also demonstrates how to set timeouts with asio::cancel_after. * * The protocol can be used to retrieve the full name of an * employee, given their ID. It works as follows: * - The client connects. * - The client sends the employee ID, as a big-endian 64-bit signed int. * - The server responds with a string containing the employee full name. * - The connection is closed. * * This tutorial doesn't include proper error handling. * We will build it in the next one. * * It uses Boost.Pfr for reflection, which requires C++20. * You can backport it to C++14 if you need by using Boost.Describe. * It uses C++20 coroutines. If you need, you can backport * it to C++11 by using callbacks, asio::yield_context * or sync functions instead of coroutines. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; // Should contain a member for each field of interest present in our query struct employee { std::string first_name; std::string last_name; }; //[tutorial_connection_pool_db // Encapsulates the database access logic. // Given an employee_id, retrieves the employee details to be sent to the client. asio::awaitable get_employee_details(mysql::connection_pool& pool, std::int64_t employee_id) { //[tutorial_connection_pool_get_connection_timeout // Get a connection from the pool. // This will wait until a healthy connection is ready to be used. // pooled_connection grants us exclusive access to the connection until // the object is destroyed. // Fail the operation if no connection becomes available in the next 20 seconds. mysql::pooled_connection conn = co_await pool.async_get_connection( asio::cancel_after(std::chrono::seconds(1)) ); //] //[tutorial_connection_pool_use // Use the connection normally to query the database. // operator-> returns a reference to an any_connection, // so we can apply all what we learnt in previous tutorials mysql::static_results> result; co_await conn->async_execute( mysql::with_params("SELECT first_name, last_name FROM employee WHERE id = {}", employee_id), result ); //] // Compose the message to be sent back to the client if (result.rows().empty()) { co_return "NOT_FOUND"; } else { const auto& emp = result.rows()[0]; co_return emp.first_name + ' ' + emp.last_name; } // When the pooled_connection is destroyed, the connection is returned // to the pool, so it can be re-used. } //] //[tutorial_connection_pool_session asio::awaitable handle_session(mysql::connection_pool& pool, asio::ip::tcp::socket client_socket) { // Read the request from the client. // async_read ensures that the 8-byte buffer is filled, handling partial reads. unsigned char message[8]{}; co_await asio::async_read(client_socket, asio::buffer(message)); // Parse the 64-bit big-endian int into a native int64_t std::int64_t employee_id = boost::endian::load_big_s64(message); // Invoke the database handling logic std::string response = co_await get_employee_details(pool, employee_id); // Write the response back to the client. // async_write ensures that the entire message is written, handling partial writes co_await asio::async_write(client_socket, asio::buffer(response)); // The socket's destructor will close the client connection } //] //[tutorial_connection_pool_listener asio::awaitable listener(mysql::connection_pool& pool, unsigned short port) { // An object that accepts incoming TCP connections. asio::ip::tcp::acceptor acc(co_await asio::this_coro::executor); // The endpoint where the server will listen. asio::ip::tcp::endpoint listening_endpoint(asio::ip::make_address("0.0.0.0"), port); // Open the acceptor acc.open(listening_endpoint.protocol()); // Allow reusing the local address, so we can restart our server // without encountering errors in bind acc.set_option(asio::socket_base::reuse_address(true)); // Bind to the local address acc.bind(listening_endpoint); // Start listening for connections acc.listen(); std::cout << "Server listening at " << acc.local_endpoint() << std::endl; // Start the accept loop while (true) { // Accept a new connection auto sock = co_await acc.async_accept(); // Function implementing our session logic. // Takes ownership of the socket. // Having this as a named variable workarounds a gcc bug // (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107288) auto session_logic = [&pool, s = std::move(sock)]() mutable { return handle_session(pool, std::move(s)); }; // Launch a coroutine that runs our session logic. // We don't co_await this coroutine so we can listen // to new connections while the session is running. asio::co_spawn( // Use the same executor as the current coroutine co_await asio::this_coro::executor, // Session logic std::move(session_logic), // Propagate exceptions thrown in handle_session [](std::exception_ptr ex) { if (ex) std::rethrow_exception(ex); } ); } } //] void main_impl(int argc, char** argv) { if (argc != 5) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } const char* username = argv[1]; const char* password = argv[2]; const char* server_hostname = argv[3]; auto listener_port = static_cast(std::stoi(argv[4])); //[tutorial_connection_pool_main //[tutorial_connection_pool_create // Create an I/O context, required by all I/O objects asio::io_context ctx; // pool_params contains configuration for the pool. // You must specify enough information to establish a connection, // including the server address and credentials. // You can configure a lot of other things, like pool limits mysql::pool_params params; params.server_address.emplace_host_and_port(server_hostname); params.username = username; params.password = password; params.database = "boost_mysql_examples"; // Construct the pool. // ctx will be used to create the connections and other I/O objects mysql::connection_pool pool(ctx, std::move(params)); //] //[tutorial_connection_pool_run // You need to call async_run on the pool before doing anything useful with it. // async_run creates connections and keeps them healthy. It must be called // only once per pool. // The detached completion token means that we don't want to be notified when // the operation ends. It's similar to a no-op callback. pool.async_run(asio::detached); //] //[tutorial_connection_pool_signals // signal_set is an I/O object that allows waiting for signals asio::signal_set signals(ctx, SIGINT, SIGTERM); // Wait for signals signals.async_wait([&](boost::system::error_code, int) { // Stop the execution context. This will cause io_context::run to return ctx.stop(); }); //] // Launch our listener asio::co_spawn( ctx, [&pool, listener_port] { return listener(pool, listener_port); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); //] } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/1_tutorial/7_error_handling.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) && BOOST_PFR_CORE_NAME_ENABLED //[example_tutorial_error_handling /** * This tutorial adds error handling to the program in the previous tutorial. * It shows how to avoid exceptions and use diagnostics objects. * * It uses Boost.Pfr for reflection, which requires C++20. * You can backport it to C++14 if you need by using Boost.Describe. * It uses C++20 coroutines. If you need, you can backport * it to C++11 by using callbacks, asio::yield_context * or sync functions instead of coroutines. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; //[tutorial_error_handling_log_error // Log an error to std::cerr void log_error(const char* header, boost::system::error_code ec, const mysql::diagnostics& diag = {}) { // Inserting the error code only prints the number and category. Add the message, too. std::cerr << header << ": " << ec << " " << ec.message(); // client_message() contains client-side generated messages that don't // contain user-input. This is usually embedded in exceptions. // When working with error codes, we need to log it explicitly if (!diag.client_message().empty()) { std::cerr << ": " << diag.client_message(); } // server_message() contains server-side messages, and thus may // contain user-supplied input. Printing it is safe. if (!diag.server_message().empty()) { std::cerr << ": " << diag.server_message(); } // Done std::cerr << std::endl; } //] // Should contain a member for each field of interest present in our query struct employee { std::string first_name; std::string last_name; }; // Encapsulates the database access logic. // Given an employee_id, retrieves the employee details to be sent to the client. //[tutorial_error_handling_db asio::awaitable get_employee_details(mysql::connection_pool& pool, std::int64_t employee_id) { // Will be populated with error information in case of error mysql::diagnostics diag; // Get a connection from the pool. // This will wait until a healthy connection is ready to be used. // ec is an error_code, conn is the mysql::pooled_connection auto [ec, conn] = co_await pool.async_get_connection(diag, asio::as_tuple); if (ec) { // A connection couldn't be obtained. // This may be because a timeout happened. log_error("Error in async_get_connection", ec, diag); co_return "ERROR"; } // Use the connection normally to query the database. mysql::static_results> result; auto [ec2] = co_await conn->async_execute( mysql::with_params("SELECT first_name, last_name FROM employee WHERE id = {}", employee_id), result, diag, asio::as_tuple ); if (ec2) { log_error("Error running query", ec2, diag); co_return "ERROR"; } // Compose the message to be sent back to the client if (result.rows().empty()) { co_return "NOT_FOUND"; } else { const auto& emp = result.rows()[0]; co_return emp.first_name + ' ' + emp.last_name; } // When the pooled_connection is destroyed, the connection is returned // to the pool, so it can be re-used. } //] //[tutorial_error_handling_session asio::awaitable handle_session(mysql::connection_pool& pool, asio::ip::tcp::socket client_socket) { // Enable the use of the "s" suffix for std::chrono::seconds using namespace std::chrono_literals; //[tutorial_error_handling_read_timeout // Read the request from the client. // async_read ensures that the 8-byte buffer is filled, handling partial reads. // Error the read if it hasn't completed after 30 seconds. unsigned char message[8]{}; auto [ec1, bytes_read] = co_await asio::async_read( client_socket, asio::buffer(message), asio::cancel_after(30s, asio::as_tuple) ); if (ec1) { // An error or a timeout happened. log_error("Error reading from the socket", ec1); co_return; } //] // Parse the 64-bit big-endian int into a native int64_t std::int64_t employee_id = boost::endian::load_big_s64(message); //[tutorial_error_handling_db_timeout // Invoke the database handling logic. // Apply an overall timeout of 20 seconds to the entire coroutine. // Using asio::co_spawn allows us to pass a completion token, like asio::cancel_after. // As other async operations, co_spawn's default completion token allows // us to use co_await on its return value. std::string response = co_await asio::co_spawn( // Run the child coroutine using the same executor as this coroutine co_await asio::this_coro::executor, // The coroutine should run our database logic [&pool, employee_id] { return get_employee_details(pool, employee_id); }, // Apply a timeout, and return an object that can be co_awaited. // We don't use as_tuple here because we're already handling I/O errors // inside get_employee_details. If an unexpected exception happens, propagate it. asio::cancel_after(20s) ); //] // Write the response back to the client. // async_write ensures that the entire message is written, handling partial writes. // Set a timeout to the write operation, too. auto [ec2, bytes_written] = co_await asio::async_write( client_socket, asio::buffer(response), asio::cancel_after(30s, asio::as_tuple) ); if (ec2) { log_error("Error writing to the socket", ec2); co_return; } // The socket's destructor will close the client connection } //] asio::awaitable listener(mysql::connection_pool& pool, unsigned short port) { // An object that accepts incoming TCP connections. asio::ip::tcp::acceptor acc(co_await asio::this_coro::executor); // The endpoint where the server will listen. asio::ip::tcp::endpoint listening_endpoint(asio::ip::make_address("0.0.0.0"), port); // Open the acceptor acc.open(listening_endpoint.protocol()); // Allow reusing the local address, so we can restart our server // without encountering errors in bind acc.set_option(asio::socket_base::reuse_address(true)); // Bind to the local address acc.bind(listening_endpoint); // Start listening for connections acc.listen(); std::cout << "Server listening at " << acc.local_endpoint() << std::endl; // Start the accept loop while (true) { // Accept a new connection auto [ec, sock] = co_await acc.async_accept(asio::as_tuple); if (ec) { log_error("Error accepting connection", ec); co_return; } // Function implementing our session logic. // Take ownership of the socket. // Having this as a named variable workarounds a gcc bug // (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107288) auto session_logic = [&pool, s = std::move(sock)]() mutable { return handle_session(pool, std::move(s)); }; // Launch a coroutine that runs our session logic. // We don't co_await this coroutine so we can listen // to new connections while the session is running asio::co_spawn( // Use the same executor as the current coroutine co_await asio::this_coro::executor, // Session logic std::move(session_logic), // Will be called when the coroutine finishes [](std::exception_ptr ptr) { if (ptr) { // For extra safety, log the exception but don't propagate it. // If we failed to anticipate an error condition that ends up raising an exception, // terminate only the affected session, instead of crashing the server. try { std::rethrow_exception(ptr); } catch (const std::exception& exc) { std::cerr << "Uncaught error in a session: " << exc.what() << std::endl; } } } ); } } void main_impl(int argc, char** argv) { if (argc != 5) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } const char* username = argv[1]; const char* password = argv[2]; const char* server_hostname = argv[3]; auto listener_port = static_cast(std::stoi(argv[4])); // Create an I/O context, required by all I/O objects asio::io_context ctx; // pool_params contains configuration for the pool. // You must specify enough information to establish a connection, // including the server address and credentials. // You can configure a lot of other things, like pool limits mysql::pool_params params; params.server_address.emplace_host_and_port(server_hostname); params.username = username; params.password = password; params.database = "boost_mysql_examples"; // Construct the pool. // ctx will be used to create the connections and other I/O objects mysql::connection_pool pool(ctx, std::move(params)); // You need to call async_run on the pool before doing anything useful with it. // async_run creates connections and keeps them healthy. It must be called // only once per pool. // The detached completion token means that we don't want to be notified when // the operation ends. It's similar to a no-op callback. pool.async_run(asio::detached); // signal_set is an I/O object that allows waiting for signals asio::signal_set signals(ctx, SIGINT, SIGTERM); // Wait for signals signals.async_wait([&](boost::system::error_code, int) { // Stop the execution context. This will cause io_context::run to return ctx.stop(); }); // Launch our listener asio::co_spawn( ctx, [&pool, listener_port] { return listener(pool, listener_port); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/2_simple/batch_inserts.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_batch_inserts /** * This example demonstrates how to insert several records in a single * SQL statement using format_sql. * * The program reads a JSON file containing a list of employees * and inserts it into the employee table. It uses Boost.JSON and * Boost.Describe to parse the file. * * This example uses C++20 coroutines. If you need, you can backport * it to C++14 (required by Boost.Describe) by using callbacks, asio::yield_context * or sync functions instead of coroutines. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace asio = boost::asio; namespace mysql = boost::mysql; namespace json = boost::json; /** * We will use Boost.Describe to easily parse the JSON file * into a std::vector. The JSON file contain an array * of objects like the following: * { * "first_name": "Some string", * "last_name": "Some other string", * "company_id": "String", * "salary": 20000 * } */ struct employee { std::string first_name; std::string last_name; std::string company_id; std::int64_t salary; // in dollars per year }; // Adds reflection capabilities to employee. Required by the JSON parser. // Boost.Describe requires C++14 BOOST_DESCRIBE_STRUCT(employee, (), (first_name, last_name, company_id, salary)) // Reads a file into memory static std::string read_file(const char* file_name) { std::ifstream ifs(file_name); if (!ifs) throw std::runtime_error("Cannot open file: " + std::string(file_name)); return std::string(std::istreambuf_iterator(ifs), std::istreambuf_iterator()); } // The main coroutine asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, std::string_view password, const std::vector& employees ) { // Create a connection. // Will use the same executor as the coroutine. mysql::any_connection conn(co_await asio::this_coro::executor); // The hostname, username, password and database to use mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = username; params.password = password; params.database = "boost_mysql_examples"; // Connect to the server co_await conn.async_connect(params); // A function describing how to format a single employee object. Used with mysql::sequence. auto format_employee_fn = [](const employee& emp, mysql::format_context_base& ctx) { // format_context_base can be used to build query strings incrementally. // Used internally by the sequence() formatter. // format_sql_to expands a format string, replacing {} fields, // and appends the result to the passed context. // When formatted, strings are quoted and escaped as string literals. // ints are formatted as number literals. mysql::format_sql_to( ctx, "({}, {}, {}, {})", emp.first_name, emp.last_name, emp.company_id, emp.salary ); }; // Compose and execute the batch INSERT. When passed to execute(), with_params // replaces placeholders ({}) by actual parameter values before sending the query to the server. // When inserting two employees, something like the following may be generated: // INSERT INTO employee (first_name, last_name, company_id, salary) // VALUES ('John', 'Doe', 'HGS', 20000), ('Rick', 'Smith', 'LLC', 50000) mysql::results result; co_await conn.async_execute( mysql::with_params( "INSERT INTO employee (first_name, last_name, company_id, salary) VALUES {}", mysql::sequence(std::ref(employees), format_employee_fn) ), result ); // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { if (argc != 5) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } // Read our JSON file into memory auto contents = read_file(argv[4]); // Parse the JSON. json::parse parses the string into a DOM, // and json::value_to validates the JSON schema, parsing values into employee structures auto values = json::value_to>(json::parse(contents)); // We need one employee, at least if (values.empty()) { std::cerr << "Input file should contain one employee, at least\n"; exit(1); } // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [&] { return coro_main(argv[3], argv[1], argv[2], values); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); std::cout << "Done\n"; } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/2_simple/batch_inserts_generic.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_batch_inserts_generic /** * This example demonstrates how to insert several records in a single * SQL statement using format_sql. The implementation is generic, * and can be reused to batch-insert any type T with Boost.Describe metadata. * * The program reads a JSON file containing a list of employees * and inserts it into the employee table. It uses Boost.JSON and * Boost.Describe to parse the file. * * This example uses C++20 coroutines. If you need, you can backport * it to C++14 (required by Boost.Describe) by using callbacks, asio::yield_context * or sync functions instead of coroutines. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace describe = boost::describe; namespace mp11 = boost::mp11; namespace mysql = boost::mysql; namespace asio = boost::asio; namespace json = boost::json; /** * An example Boost.Describe struct. Our code will work with any struct like this, * as long as it has metadata as provided by BOOST_DESCRIBE_STRUCT. * We will use this type as an example. */ struct employee { std::string first_name; std::string last_name; std::string company_id; std::int64_t salary; // in dollars per year }; BOOST_DESCRIBE_STRUCT(employee, (), (first_name, last_name, company_id, salary)) // Retrieves all public data members from a Boost.Describe struct, including inherited ones. // This is a Boost.Mp11 compatible type list. template using public_members = describe::describe_members; // The number of public members of a Boost.Describe struct template constexpr std::size_t num_public_members = mp11::mp_size>::value; // Gets the member names of a struct, as an array of strings. // For employee, generates // {"first_name", "last_name", "company_id", "salary"} template constexpr std::array> get_field_names() { return mp11::tuple_apply( [](auto... descriptors) { return std::array>{{descriptors.name...}}; }, mp11::mp_rename, std::tuple>() ); } // A formatting function that generates an insert field list for any struct T with // Boost.Describe metadata. // For example, employee{"John", "Doe", "HGS", 20000} generates the string // "('John', 'Doe', 'HGS', 20000)" struct insert_struct_format_fn { template void operator()(const T& value, mysql::format_context_base& ctx) const { // Convert the struct into a std::array of formattable_ref // formattable_ref is a view type that can hold any type that can be formatted auto args = mp11::tuple_apply( [&value](auto... descriptors) { return std::array>{ {value.*descriptors.pointer...} }; }, mp11::mp_rename, std::tuple>() ); // Format them as a comma-separated sequence mysql::format_sql_to(ctx, "({})", args); } }; // Reads a file into memory std::string read_file(const char* file_name) { std::ifstream ifs(file_name); if (!ifs) throw std::runtime_error("Cannot open file: " + std::string(file_name)); return std::string(std::istreambuf_iterator(ifs), std::istreambuf_iterator()); } // The main coroutine asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, std::string_view password, const std::vector& employees ) { // Create a connection. // Will use the same executor as the coroutine. mysql::any_connection conn(co_await asio::this_coro::executor); // The hostname, username, password and database to use mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = username; params.password = password; params.database = "boost_mysql_examples"; // Connect to the server co_await conn.async_connect(params); // Run the query. Placeholders ({}) will be expanded before the query is sent to the server. // We use sequence() to format C++ ranges as comma-separated sequences. mysql::results result; co_await conn.async_execute( mysql::with_params( "INSERT INTO employee ({::i}) VALUES {}", get_field_names(), mysql::sequence(std::ref(employees), insert_struct_format_fn()) ), result ); // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { if (argc != 5) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } // Read our JSON file into memory auto contents = read_file(argv[4]); // Parse the JSON. json::parse parses the string into a DOM, // and json::value_to validates the JSON schema, parsing values into employee structures auto values = json::value_to>(json::parse(contents)); // We need one value to insert, at least if (values.empty()) { std::cerr << argv[0] << ": the JSON file should contain at least one employee" << std::endl; exit(1); } // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [&] { return coro_main(argv[3], argv[1], argv[2], values); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); std::cout << "Done\n"; } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/2_simple/callbacks.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // //[example_callbacks /** * This example demonstrates how to use callbacks when using async functions. * This can be a good choice when targeting a standard lower than C++20. * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include #include // When using callbacks, we usually employ error codes instead of exceptions. using boost::system::error_code; namespace mysql = boost::mysql; namespace asio = boost::asio; // Prints a database employee to stdout void print_employee(mysql::row_view employee) { std::cout << "Employee '" << employee.at(0) << " " // first_name (string) << employee.at(1) << "' earns " // last_name (string) << employee.at(2) << " dollars yearly\n"; // salary (double) } // A session object, containing all variables that need to be kept alive for our session. // We will use a shared_ptr to ensure that all these variables are kept alive // until the last callback is executed class session : public std::enable_shared_from_this { mysql::connect_params conn_params; // MySQL credentials and other connection config mysql::any_connection conn; // Represents the connection to the MySQL server mysql::results result; // A result from a query mysql::error_code final_error; // Will be set in case of error mysql::diagnostics diag; // Will be populated with info about server errors const char* company_id; // The ID of the company whose employees we want to list. Untrusted. public: session( asio::io_context& ctx, const char* server_hostname, const char* username, const char* password, const char* company_id ) : conn(ctx), company_id(company_id) { conn_params.server_address.emplace_host_and_port(server_hostname); conn_params.username = username; conn_params.password = password; conn_params.database = "boost_mysql_examples"; } // Accessor for error information, so main can access it error_code get_error() const { return final_error; } const boost::mysql::diagnostics& get_diagnostics() const { return diag; } // Initiates the callback chain void start() { // Will call on_connect when the connect operation completes. // The session object is kept alive with the shared_ptr that shared_from_this produces conn.async_connect( conn_params, diag, std::bind(&session::on_connect, shared_from_this(), std::placeholders::_1) ); } void on_connect(error_code ec) { // If there was an error, stop the callback chain if (ec) { final_error = ec; return; } // Initiate the query execution. company_id is an untrusted value. // with_params will securely compose a SQL query and send it to the server for execution. // Returned rows will be read into result. // We use the callback chain + shared_ptr technique again conn.async_execute( mysql::with_params( "SELECT first_name, last_name, salary FROM employee WHERE company_id = {}", company_id ), result, diag, std::bind(&session::on_execute, shared_from_this(), std::placeholders::_1) ); } void on_execute(error_code ec) { // If there was an error, stop the callback chain if (ec) { final_error = ec; return; } // Print the rows returned by the query for (boost::mysql::row_view employee : result.rows()) { print_employee(employee); } // Notify the MySQL server we want to quit and then close the socket conn.async_close(diag, std::bind(&session::finish, shared_from_this(), std::placeholders::_1)); } void finish(error_code err) { final_error = err; } }; void main_impl(int argc, char** argv) { if (argc != 4 && argc != 5) { std::cerr << "Usage: " << argv[0] << " [company-id]\n"; exit(1); } // The execution context, required to run I/O operations. boost::asio::io_context ctx; // The company_id whose employees we will be listing. This // is user-supplied input, and should be treated as untrusted. const char* company_id = argc == 5 ? argv[4] : "HGS"; // Create the session object and launch it auto sess = std::make_shared(ctx, argv[3], argv[1], argv[2], company_id); sess->start(); // Run the callback chain until it completes ctx.run(); // Check for errors if (error_code ec = sess->get_error()) { std::cerr << "Error: " << ec << ": " << ec.message() << '\n' << "Server diagnostics: " << sess->get_diagnostics().server_message() << std::endl; exit(1); } } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] ================================================ FILE: example/2_simple/coroutines_cpp11.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // //[example_coroutines_cpp11 /** * This example demonstrates how to use stackful coroutines when using async functions. * This can be a good choice when targeting a standard lower than C++20. * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. * You need to link your program to Boost.Context to use asio::spawn. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; void print_employee(mysql::row_view employee) { std::cout << "Employee '" << employee.at(0) << " " // first_name (string) << employee.at(1) << "' earns " // last_name (string) << employee.at(2) << " dollars yearly\n"; // salary (double) } /** * The main coroutine. It will suspend every time we call one of the asynchronous functions, saving * all information it needs for resuming. When the asynchronous operation completes, * the coroutine will resume in the point it was left. * We need to pass the yield object to async functions for this to work. */ void coro_main( const char* server_hostname, const char* username, const char* password, const char* company_id, asio::yield_context yield ) { // Represents a connection to the MySQL server. // The connection will use the same executor as the coroutine mysql::any_connection conn(yield.get_executor()); // The hostname, username, password and database to use mysql::connect_params conn_params; conn_params.server_address.emplace_host_and_port(server_hostname); conn_params.username = username; conn_params.password = password; conn_params.database = "boost_mysql_examples"; // Connect to server. with_diagnostics turns thrown exceptions // into error_with_diagnostics, which contain more info than regular exceptions conn.async_connect(conn_params, mysql::with_diagnostics(yield)); // Initiate the query execution. company_id is an untrusted value. // with_params will securely compose a SQL query and send it to the server for execution. // Returned rows will be read into result. mysql::results result; conn.async_execute( mysql::with_params( "SELECT first_name, last_name, salary FROM employee WHERE company_id = {}", company_id ), result, mysql::with_diagnostics(yield) ); // Print the employees for (boost::mysql::row_view employee : result.rows()) { print_employee(employee); } // Notify the MySQL server we want to quit, then close the underlying connection. conn.async_close(mysql::with_diagnostics(yield)); } void main_impl(int argc, char** argv) { if (argc != 4 && argc != 5) { std::cerr << "Usage: " << argv[0] << " [company-id]\n"; exit(1); } // The company_id whose employees we will be listing. This // is user-supplied input, and should be treated as untrusted. const char* company_id = argc == 5 ? argv[4] : "HGS"; // The execution context, required to run I/O operations. asio::io_context ctx; // Launch the coroutine asio::spawn( ctx, [argv, company_id](asio::yield_context yield) { coro_main(argv[3], argv[1], argv[2], company_id, yield); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // You will only get this type of exceptions if you use with_diagnostics. // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] ================================================ FILE: example/2_simple/deletes.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_deletes /** * This example demonstrates how to use DELETE statements * and the results::affected_rows() function. * * The program deletes an employee, given their ID, * and prints whether the deletion was successful. * * It uses C++20 coroutines. If you need, you can backport * it to C++11 by using callbacks, asio::yield_context * or sync functions instead of coroutines. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; // The main coroutine asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, std::string_view password, std::int64_t employee_id ) { // Create a connection. // Will use the same executor as the coroutine. mysql::any_connection conn(co_await asio::this_coro::executor); // The server host, username, password and database to use. mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = std::move(username); params.password = std::move(password); params.database = "boost_mysql_examples"; // Connect to the server co_await conn.async_connect(params); // Perform the deletion. mysql::results result; co_await conn.async_execute( mysql::with_params("DELETE FROM employee WHERE id = {}", employee_id), result ); // affected_rows() returns the number of rows that were affected // by the executed statement. If there was an affected row, the deletion was successful. // Note that this may not work for UPDATEs, as they may match but not affected some rows. if (result.affected_rows() != 0u) { std::cout << "Deletion successful\n"; } else { std::cout << "No employee with such ID\n"; } // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { if (argc != 5) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [=] { return coro_main(argv[3], argv[1], argv[2], std::stoi(argv[4])); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); std::cout << "Done\n"; } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/2_simple/disable_tls.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_disable_tls /** * This example demonstrates how to disable TLS when connecting to MySQL. * * It uses C++20 coroutines. If you need, you can backport * it to C++11 by using callbacks, asio::yield_context * or sync functions instead of coroutines. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; // The main coroutine asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, std::string_view password ) { // Create a connection. // Will use the same executor as the coroutine. mysql::any_connection conn(co_await asio::this_coro::executor); //[section_connection_establishment_disable_tls // The server host, username, password and database to use. // Passing ssl_mode::disable will disable the use of TLS. mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = std::move(username); params.password = std::move(password); params.database = "boost_mysql_examples"; params.ssl = mysql::ssl_mode::disable; //] // Connect to the server co_await conn.async_connect(params); // The connection can now be used normally mysql::results result; co_await conn.async_execute("SELECT 'Hello world!'", result); std::cout << result.rows().at(0).at(0) << std::endl; // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { if (argc != 4) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [=] { return coro_main(argv[3], argv[1], argv[2]); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); std::cout << "Done\n"; } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/2_simple/dynamic_filters.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_dynamic_filters /** * This example implements a dynamic filter using client-side SQL. * If you're implementing a filter with many options that can be * conditionally enabled, this pattern may be useful for you. * * This example uses C++20 coroutines. If you need, you can backport * it to C++11 by using callbacks, asio::yield_context * or sync functions instead of coroutines. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; // Prints an employee row to stdout void print_employee(mysql::row_view employee) { std::cout << "id: " << employee.at(0) // field 0: id << ", first_name: " << std::setw(16) << employee.at(1) // field 1: first_name << ", last_name: " << std::setw(16) << employee.at(2) // field 2: last_name << ", company_id: " << employee.at(3) // field 3: company_id << ", salary: " << employee.at(4) << '\n'; // field 4: salary } // An operator to use in a filter enum class op_type { lt, // < lte, // <= eq, // = gt, // > gte, // >= }; // Returns the SQL operator for the given op_type std::string_view op_type_to_sql(op_type value) { switch (value) { case op_type::lt: return "<"; case op_type::lte: return "<="; case op_type::eq: return "="; case op_type::gte: return ">="; case op_type::gt: return ">"; default: assert(false); return "="; } } // An individual filter to apply. // For example, filter{"salary", op_type::gt, field_view(20000)} should generate a // `salary` > 20000 condition struct filter { std::string_view field_name; // The database column name op_type op; // The operator to apply mysql::field_view field_value; // The value to check. field_view can hold any MySQL type }; // Command line arguments struct cmdline_args { // MySQL username to use during authentication. std::string_view username; // MySQL password to use during authentication. std::string_view password; // Hostname where the MySQL server is listening. std::string_view server_hostname; // The filters to apply std::vector filts; // If order_by.has_value(), order employees using the given field std::optional order_by; }; // Parses the command line static cmdline_args parse_cmdline_args(int argc, char** argv) { // Available options constexpr std::string_view company_id_prefix = "--company-id="; constexpr std::string_view first_name_prefix = "--first-name="; constexpr std::string_view last_name_prefix = "--last-name="; constexpr std::string_view min_salary_prefix = "--min-salary="; constexpr std::string_view order_by_prefix = "--order-by="; // Helper function to print the usage message and exit auto print_usage_and_exit = [argv]() { std::cerr << "Usage: " << argv[0] << " [filters]\n"; exit(1); }; // Check number of arguments if (argc <= 4) print_usage_and_exit(); // Parse the required arguments cmdline_args res; res.username = argv[1]; res.password = argv[2]; res.server_hostname = argv[3]; // Parse the filters for (int i = 4; i < argc; ++i) { std::string_view arg = argv[i]; // Attempt to match the argument against each prefix if (arg.starts_with(company_id_prefix)) { auto value = arg.substr(company_id_prefix.size()); res.filts.push_back({"company_id", op_type::eq, mysql::field_view(value)}); } else if (arg.starts_with(first_name_prefix)) { auto value = arg.substr(first_name_prefix.size()); res.filts.push_back({"first_name", op_type::eq, mysql::field_view(value)}); } else if (arg.starts_with(last_name_prefix)) { auto value = arg.substr(last_name_prefix.size()); res.filts.push_back({"last_name", op_type::eq, mysql::field_view(value)}); } else if (arg.starts_with(min_salary_prefix)) { auto value = std::stod(std::string(arg.substr(min_salary_prefix.size()))); res.filts.push_back({"salary", op_type::gte, mysql::field_view(value)}); } else if (arg.starts_with(order_by_prefix)) { auto field_name = arg.substr(order_by_prefix.size()); // For security, validate the passed field against a set of whitelisted fields if (field_name != "id" && field_name != "first_name" && field_name != "last_name" && field_name != "salary") { std::cerr << "Order-by: invalid field " << field_name << std::endl; print_usage_and_exit(); } res.order_by = field_name; } else { std::cerr << "Unrecognized option: " << arg << std::endl; print_usage_and_exit(); } } // We should have at least one filter if (res.filts.empty()) { std::cerr << "At least one filter should be specified" << std::endl; print_usage_and_exit(); } return res; } // Composes a SELECT query to retrieve employees according to the passed filters. // We allow an optional ORDER BY clause that must be added dynamically, // so we can't express our query as a single format string. // This function uses format_sql_to to build a query string incrementally. // format_sql_to requires us to pass a format_options value, containing configuration // options like the current character set. Use any_connection::format_opts to obtain it. // If your use case allows you to express your query as a single format string, use with_params, instead. std::string compose_get_employees_query( mysql::format_options opts, const std::vector& filts, std::optional order_by ) { // A format context allows composing queries incrementally. // This is required because we need to add the ORDER BY clause conditionally mysql::format_context ctx(opts); // Adds an individual filter to the context. Used by sequence() auto filter_format_fn = [](filter item, mysql::format_context_base& elm_ctx) { // {:i} formats a string as a SQL identifier. {:r} outputs raw SQL. // filter{"key", op_type::eq, field_view(42)} would get formatted as "`key` = 42" mysql::format_sql_to( elm_ctx, "{:i} {:r} {}", item.field_name, op_type_to_sql(item.op), item.field_value ); }; // Add the query with the filters to ctx. // sequence() will invoke filter_format_fn for each element in filts, // using the string " AND " as glue, to separate filters // By default, sequence copies its input range, but we don't need this here, // so we disable the copy by calling ref() mysql::format_sql_to( ctx, "SELECT id, first_name, last_name, company_id, salary FROM employee WHERE {}", mysql::sequence(std::ref(filts), filter_format_fn, " AND ") ); // Add the order by if (order_by) { // identifier formats a string as a SQL identifier, instead of a string literal. // For instance, this may generate "ORDER BY `first_name`" mysql::format_sql_to(ctx, " ORDER BY {:i}", *order_by); } // Get our generated query. get() returns a system::result, which // will contain errors if any of the args couldn't be formatted. This can happen // if you pass string values containing invalid UTF-8. // value() will throw an exception if that's the case. return std::move(ctx).get().value(); } // The main coroutine asio::awaitable coro_main(const cmdline_args& args) { // Create a connection. // Will use the same executor as the coroutine. mysql::any_connection conn(co_await asio::this_coro::executor); // The hostname, username, password and database to use mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(args.server_hostname)); params.username = args.username; params.password = args.password; params.database = "boost_mysql_examples"; // Connect to the server co_await conn.async_connect(params); // Compose the query. format_opts() returns a system::result, // containing the options required by format_context. format_opts() may return // an error if the connection doesn't know which character set is using - // use async_set_character_set if this happens. std::string query = compose_get_employees_query(conn.format_opts().value(), args.filts, args.order_by); // Execute the query as usual. Note that the query was generated // client-side. Appropriately using format_sql_to makes this approach secure. // with_params uses this same technique under the hood. // Casting to string_view saves a copy in async_execute mysql::results result; co_await conn.async_execute(std::string_view(query), result); // Print the employees for (mysql::row_view employee : result.rows()) { print_employee(employee); } // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { // Parse the command line cmdline_args args = parse_cmdline_args(argc, argv); // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [&] { return coro_main(args); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // You will only get this type of exceptions if you use with_diagnostics. // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's encoding // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/2_simple/inserts.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_inserts /** * This example demonstrates how to use INSERT statements, * the results::last_insert_id() function, and optionals * to represent potentially NULL values. * * The program inserts an employee, given their first name, * last name and company ID. It then prints the ID of the newly * inserted employee. * * It uses C++20 coroutines. If you need, you can backport * it to C++11 by using callbacks, asio::yield_context * or sync functions instead of coroutines. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; // The main coroutine asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, std::string_view password, std::string_view first_name, std::string_view last_name, std::string_view company_id, std::optional salary // empty optional means that a NULL value should be inserted ) { // Create a connection. // Will use the same executor as the coroutine. mysql::any_connection conn(co_await asio::this_coro::executor); // The server host, username, password and database to use. mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = std::move(username); params.password = std::move(password); params.database = "boost_mysql_examples"; // Connect to the server co_await conn.async_connect(params); // Perform the insertion. // If salary is empty, the last {} will be replaced by NULL. mysql::results result; co_await conn.async_execute( mysql::with_params( "INSERT INTO employee (first_name, last_name, company_id, salary) VALUES ({}, {}, {}, {})", first_name, last_name, company_id, salary ), result ); // results::last_insert_id retrieves the value of the latest // AUTO_INCREMENT field generated by the executed query, if any. // In this case, this is the generated employee_id. // If we needed the entire generated employee, we'd need a transaction // and multi-queries. std::cout << "Successfully created employee with ID: " << result.last_insert_id() << std::endl; // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { if (argc < 7 || argc > 8) { std::cerr << "Usage: " << argv[0] << " []\n"; exit(1); } // In DB, salary is an UNSIGNED INT (32-bit) representing employee salary in USD // It may be NULL (e.g. for contractors). // Parse the command line argument, if present, and validate it's within a sane range std::optional salary; if (argc == 8) { int parsed_salary = std::stoi(argv[7]); if (parsed_salary < 10000 || parsed_salary >= 1000000) { std::cerr << "Salary should be between 10000 and 1000000\n"; exit(1); } salary = static_cast(parsed_salary); } // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [=] { return coro_main(argv[3], argv[1], argv[2], argv[4], argv[5], argv[6], salary); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); std::cout << "Done\n"; } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/2_simple/metadata.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_metadata /** * This example shows how to obtain metadata from SQL queries, * including field and table names. * * This example uses C++20 coroutines. If you need, you can backport * it to C++11 by using callbacks, asio::yield_context * or sync functions instead of coroutines. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include namespace asio = boost::asio; namespace mysql = boost::mysql; // The main coroutine asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, std::string_view password ) { // Create a connection. // Will use the same executor as the coroutine. mysql::any_connection conn(co_await asio::this_coro::executor); // By default, string metadata (like column names) won't be retained. // This is for efficiency reasons. You can change this setting by calling // connection::set_meta_mode. It will affect any subsequent queries and statement executions. conn.set_meta_mode(mysql::metadata_mode::full); // The socket path, username, password and database to use. mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = username; params.password = password; params.database = "boost_mysql_examples"; // Connect to the server co_await conn.async_connect(params); // Issue the query constexpr const char* sql = R"( SELECT comp.name AS company_name, emp.id AS employee_id FROM employee emp JOIN company comp ON (comp.id = emp.company_id) )"; mysql::results result; co_await conn.async_execute(sql, result); /** * results objects allow you to access metadata about the columns in the query * using the meta() function, which returns span-like object containing metadata objects * (one per column in the query, and in the same order as in the query). * You can retrieve the column name, type, number of decimals, * suggested display width, whether the column is part of a key... * These metadata objects are owned by the results object. */ assert(result.meta().size() == 2); // <- // clang-format off // -> const mysql::metadata& company_name = result.meta()[0]; assert(company_name.database() == "boost_mysql_examples"); // database name 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.column_name() == "company_name"); // the name of the column in the query assert(company_name.original_column_name() == "name"); // the name of the physical column in the table assert(company_name.type() == boost::mysql::column_type::varchar); // we created the column as a VARCHAR assert(!company_name.is_primary_key()); // column is not a primary key assert(!company_name.is_auto_increment()); // column is not AUTO_INCREMENT assert(company_name.is_not_null()); // column may not be NULL const mysql::metadata& employee_id = result.meta()[1]; assert(employee_id.database() == "boost_mysql_examples"); // database name 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.column_name() == "employee_id"); // the name of the column in the query assert(employee_id.original_column_name() == "id"); // the name of the physical column in the table assert(employee_id.type() == boost::mysql::column_type::int_); // we created the column as INT assert(employee_id.is_primary_key()); // column is a primary key assert(employee_id.is_auto_increment()); // we declared the column as AUTO_INCREMENT assert(employee_id.is_not_null()); // column cannot be NULL // <- // clang-format on // avoid warnings in release mode static_cast(company_name); static_cast(employee_id); // -> // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { if (argc != 4) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [=] { return coro_main(argv[3], argv[1], argv[2]); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/2_simple/multi_function.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_multi_function /** * This example demonstrates how to run multi-function operations * to dump an entire table to stdout, reading rows in batches. * * It uses C++20 coroutines. If you need, you can backport * it to C++11 by using callbacks, asio::yield_context * or sync functions instead of coroutines. */ #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; void print_employee(mysql::row_view employee) { std::cout << "Employee '" << employee.at(0) << " " // first_name (string) << employee.at(1) << "' earns " // last_name (string) << employee.at(2) << " dollars yearly\n"; // salary (double) } // The main coroutine asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, std::string_view password ) { // Create a connection. It will use the same executor as our coroutine mysql::any_connection conn(co_await asio::this_coro::executor); // The hostname, username, password and database to use mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = username; params.password = password; params.database = "boost_mysql_examples"; // Connect to the server co_await conn.async_connect(params); // Start our query as a multi-function operation. // This will send the query for execution but won't read the rows. // An execution_state keep tracks of the operation. mysql::execution_state st; co_await conn.async_start_execution("SELECT first_name, last_name, salary FROM employee", st); // st.should_read_rows() returns true while there are more rows to read. // Use async_read_some_rows to read a batch of rows. // This function tries to minimize copies. employees is a view // object pointing into the connection's internal buffers, // and is valid until you start the next async operation. while (st.should_read_rows()) { mysql::rows_view employees = co_await conn.async_read_some_rows(st); for (auto employee : employees) print_employee(employee); } // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { if (argc != 4) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [=] { return coro_main(argv[3], argv[1], argv[2]); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); std::cout << "Done\n"; } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/2_simple/patch_updates.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_patch_updates /** * This example demonstrates how to implement dynamic updates * with PATCH-like semantics using client-side SQL formatting. * * The program updates an employee by ID, modifying fields * as provided by command-line arguments, and leaving all other * fields unmodified. * * This example uses C++20 coroutines. If you need, you can backport * it to C++14 (required by Boost.Describe) by using callbacks, asio::yield_context * or sync functions instead of coroutines. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; /** * Represents a single update as a name, value pair. * The idea is to use command-line arguments to compose * a std::vector with the fields to be updated, * and use mysql::sequence() to join these with commas */ struct update_field { // The field name to set (i.e. the column name) std::string_view field_name; // The value to set the field to. Recall that field_view is // a variant-like type that can hold all types that MySQL supports. mysql::field_view field_value; }; // Contains the parsed command-line arguments struct cmdline_args { // MySQL username to use during authentication. std::string_view username; // MySQL password to use during authentication. std::string_view password; // Hostname where the MySQL server is listening. std::string_view server_hostname; // The ID of the employee we want to update. std::int64_t employee_id{}; // A list of name, value pairs containing the employee fields to update. std::vector updates; }; // Parses the command line arguments, calling exit on failure. static cmdline_args parse_cmdline_args(int argc, char** argv) { // Available options constexpr std::string_view company_id_prefix = "--company-id="; constexpr std::string_view first_name_prefix = "--first-name="; constexpr std::string_view last_name_prefix = "--last-name="; constexpr std::string_view salary_prefix = "--salary="; // Helper function to print the usage message and exit auto print_usage_and_exit = [argv]() { std::cerr << "Usage: " << argv[0] << " employee_id [updates]\n"; exit(1); }; // Check number of arguments if (argc <= 5) print_usage_and_exit(); // Parse the required arguments cmdline_args res; res.username = argv[1]; res.password = argv[2]; res.server_hostname = argv[3]; res.employee_id = std::stoll(argv[4]); // Parse the requested updates for (int i = 5; i < argc; ++i) { // Get the argument std::string_view arg = argv[i]; // Attempt to match it with the options we have if (arg.starts_with(company_id_prefix)) { std::string_view new_value = arg.substr(company_id_prefix.size()); res.updates.push_back(update_field{"company_id", mysql::field_view(new_value)}); } else if (arg.starts_with(first_name_prefix)) { std::string_view new_value = arg.substr(first_name_prefix.size()); res.updates.push_back(update_field{"first_name", mysql::field_view(new_value)}); } else if (arg.starts_with(last_name_prefix)) { std::string_view new_value = arg.substr(last_name_prefix.size()); res.updates.push_back(update_field{"last_name", mysql::field_view(new_value)}); } else if (arg.starts_with(salary_prefix)) { double new_value = std::stod(std::string(arg.substr(salary_prefix.size()))); res.updates.push_back(update_field{"salary", mysql::field_view(new_value)}); } else { std::cerr << "Unrecognized option: " << arg << std::endl; print_usage_and_exit(); } } // There should be one update, at least if (res.updates.empty()) { std::cerr << "There should be one update, at least\n"; print_usage_and_exit(); } return res; } // The main coroutine asio::awaitable coro_main(const cmdline_args& args) { // Create a connection. // Will use the same executor as the coroutine. mysql::any_connection conn(co_await asio::this_coro::executor); // The hostname, username, password and database to use mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(args.server_hostname)); params.username = args.username; params.password = std::string(args.password); params.database = "boost_mysql_examples"; params.multi_queries = true; // Connect to the server co_await conn.async_connect(params); // Formats an individual update. Used by sequence(). // For update_field{"first_name", "John"}, it generates the string // "`first_name` = 'John'" // Format contexts can build a query string incrementally, and are used by sequence() internally auto update_format_fn = [](update_field upd, mysql::format_context_base& ctx) { mysql::format_sql_to(ctx, "{:i} = {}", upd.field_name, upd.field_value); }; // Compose and execute the query. with_params will expand placeholders // before sending the query to the server. // We use sequence() to output the update list separated by commas. // We want to update the employee and then retrieve it. MySQL doesn't support // the UPDATE ... RETURNING statement to update and retrieve data atomically, // so we will use a transaction to guarantee consistency. // Instead of running every statement separately, we activated params.multi_queries, // which allows semicolon-separated statements. // As in std::format, we can use explicit indices like {0} and {1} to reference arguments. // By default, sequence copies its input range, but we don't need this here, // so we disable the copy by calling ref() mysql::results result; co_await conn.async_execute( mysql::with_params( "START TRANSACTION; " "UPDATE employee SET {0} WHERE id = {1}; " "SELECT first_name, last_name, salary, company_id FROM employee WHERE id = {1}; " "COMMIT", mysql::sequence(std::ref(args.updates), update_format_fn), args.employee_id ), result ); // We ran 4 queries, so the results object will hold 4 resultsets. // Get the rows retrieved by the SELECT (the 3rd one). auto rws = result.at(2).rows(); // If there are no rows, the given employee does not exist. if (rws.empty()) { std::cerr << "employee_id=" << args.employee_id << " not found" << std::endl; exit(1); } // Print the updated employee. const auto employee = rws.at(0); std::cout << "Updated employee with id=" << args.employee_id << ":\n" << " first_name: " << employee.at(0) << "\n last_name: " << employee.at(1) << "\n salary: " << employee.at(2) << "\n company_id: " << employee.at(3) << std::endl; // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { // Parse the command line cmdline_args args = parse_cmdline_args(argc, argv); // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [&] { return coro_main(args); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's encoding // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/2_simple/pipeline.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_pipeline /** * (EXPERIMENTAL) * This example demonstrates how to use the pipeline API to prepare, * execute and close statements in batch. * Pipelines are a experimental API. * * This example uses C++20 coroutines. If you need, you can backport * it to C++11 by using callbacks or asio::yield_context. * Timeouts can't be used with sync functions. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace asio = boost::asio; namespace mysql = boost::mysql; // Prepare several statements in batch. // This is faster than preparing them one by one, as it saves round-trips to the server. asio::awaitable> batch_prepare( mysql::any_connection& conn, std::span statements ) { // Construct a pipeline request describing the work to be performed. // There must be one prepare_statement_stage per statement to prepare mysql::pipeline_request req; for (auto stmt_sql : statements) req.add_prepare_statement(stmt_sql); // Run the pipeline. // stage_response is a variant-like type that can hold the response of any stage type. std::vector pipe_res; co_await conn.async_run_pipeline(req, pipe_res); // If we got here, all statements were prepared successfully. // pipe_res contains as many elements as statements.size(), holding statement objects // Extract them into a vector std::vector res; res.reserve(statements.size()); for (const auto& stage_res : pipe_res) res.push_back(stage_res.get_statement()); co_return res; } // The main coroutine asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, std::string_view password, std::string_view company_id ) { // Create a connection. // Will use the same executor as the coroutine. mysql::any_connection conn(co_await asio::this_coro::executor); // The hostname, username, password and database to use mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = username; params.password = password; params.database = "boost_mysql_examples"; // Connect to server co_await conn.async_connect(params); // Prepare the statements using the batch prepare function that we previously defined const std::array stmt_sql{ "INSERT INTO employee (company_id, first_name, last_name) VALUES (?, ?, ?)", "INSERT INTO audit_log (msg) VALUES (?)" }; std::vector stmts = co_await batch_prepare(conn, stmt_sql); // Create a pipeline request to execute them. // Warning: do NOT include the COMMIT statement in this pipeline. // COMMIT must only be executed if all the previous statements succeeded. // In a pipeline, all stages get executed, regardless of the outcome of previous stages. // We say that COMMIT has a dependency on the result of previous stages. mysql::pipeline_request req; req.add_execute("START TRANSACTION") .add_execute(stmts.at(0), company_id, "Juan", "Lopez") .add_execute(stmts.at(0), company_id, "Pepito", "Rodriguez") .add_execute(stmts.at(0), company_id, "Someone", "Random") .add_execute(stmts.at(1), "Inserted 3 new emplyees"); std::vector res; // Execute the pipeline co_await conn.async_run_pipeline(req, res); // If we got here, all stages executed successfully. // Since they were execution stages, the response contains a results object. // Get the IDs of the newly created employees auto id1 = res.at(1).as_results().last_insert_id(); auto id2 = res.at(2).as_results().last_insert_id(); auto id3 = res.at(3).as_results().last_insert_id(); // We can now commit our transaction and close the statements. // Clear the request and populate it again req.clear(); req.add_execute("COMMIT").add_close_statement(stmts.at(0)).add_close_statement(stmts.at(1)); // Run it co_await conn.async_run_pipeline(req, res); // If we got here, our insertions got committed. std::cout << "Inserted employees: " << id1 << ", " << id2 << ", " << id3 << std::endl; // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { if (argc != 5) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [=] { return coro_main(argv[3], argv[1], argv[2], argv[4]); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/2_simple/prepared_statements.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_prepared_statements /** * This example demonstrates how to prepare, execute * and deallocate prepared statements. This program retrieves * all employees in a company, given its ID. * * It uses C++20 coroutines. If you need, you can backport * it to C++11 by using callbacks, asio::yield_context * or sync functions instead of coroutines. */ #include #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; void print_employee(mysql::row_view employee) { std::cout << "Employee '" << employee.at(0) << " " // first_name (string) << employee.at(1) << "' earns " // last_name (string) << employee.at(2) << " dollars yearly\n"; // salary (double) } // The main coroutine asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, std::string_view password, std::string_view company_id ) { // Create a connection. It will use the same executor as our coroutine mysql::any_connection conn(co_await asio::this_coro::executor); // The hostname, username, password and database to use mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = username; params.password = password; params.database = "boost_mysql_examples"; // Connect to the server co_await conn.async_connect(params); // Prepared statements can be used to execute queries with untrusted // parameters securely. They are an option to mysql::with_params, // but work server-side. // They are more complex but can yield more efficiency when retrieving // lots of numeric data, or when executing the same query several times with the same parameters. // Ask the server to prepare a statement and retrieve its handle mysql::statement stmt = co_await conn.async_prepare_statement( "SELECT first_name, last_name, salary FROM employee WHERE company_id = ?" ); // Execute the statement. bind() must be passed as many parameters (number of ?) // as the statement has. bind() packages the statement handle with the parameters, // and async_execute sends them to the server mysql::results result; co_await conn.async_execute(stmt.bind(company_id), result); for (mysql::row_view employee : result.rows()) print_employee(employee); // We can execute stmt as many times as we want, potentially with different // parameters, without the need to re-prepare it. // Once we're done with a statement, we can close it, to deallocate it from the server. // Closing the connection will also deallocate active statements, so this is not // strictly required here, but it's shown for completeness. // This can be relevant if you're using long-lived sessions. // Note that statement's destructor does NOT close the statement. co_await conn.async_close_statement(stmt); // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { if (argc != 5) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [=] { return coro_main(argv[3], argv[1], argv[2], argv[4]); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); std::cout << "Done\n"; } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/2_simple/source_script.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // //[example_source_script #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * This example runs all command in a .sql file, using multi-queries. * Note that special commands that are handled by the mysql command line tool * (like DELIMITER) won't work. * * For this example, we will be using the 'boost_mysql_examples' database. * You can get this database by running db_setup.sql. * This example assumes you are connecting to a localhost MySQL server. * * This example uses synchronous functions and handles errors using exceptions. */ // Reads a file into memory std::string read_file(const char* file_name) { std::ifstream ifs(file_name); if (!ifs) throw std::runtime_error("Cannot open file: " + std::string(file_name)); return std::string(std::istreambuf_iterator(ifs), std::istreambuf_iterator()); } void print_column_names(boost::mysql::metadata_collection_view meta_collection) { if (meta_collection.empty()) return; bool is_first = true; for (auto meta : meta_collection) { if (!is_first) { std::cout << " | "; } is_first = false; std::cout << meta.column_name(); } std::cout << "\n-----------------\n"; } void print_row(boost::mysql::row_view row) { bool is_first = true; for (auto field : row) { if (!is_first) { std::cout << " | "; } is_first = false; std::cout << field; } std::cout << '\n'; } void print_ok(const boost::mysql::execution_state& st) { std::cout << "Affected rows: " << st.affected_rows() << "\n" "Last insert ID: " << st.last_insert_id() << "\n" "Warnings: " << st.warning_count() << "\n\n" << std::flush; } void main_impl(int argc, char** argv) { if (argc != 5) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } // Read the script file into memory std::string script_contents = read_file(argv[4]); // Set up the io_context, SSL context and connection required to // connect to the server. boost::asio::io_context ctx; boost::asio::ssl::context ssl_ctx(boost::asio::ssl::context::tls_client); boost::mysql::tcp_ssl_connection conn(ctx.get_executor(), ssl_ctx); // Resolve the server hostname to get a collection of endpoints boost::asio::ip::tcp::resolver resolver(ctx.get_executor()); auto endpoints = resolver.resolve(argv[3], boost::mysql::default_port_string); // The username, password and database to use boost::mysql::handshake_params params( argv[1], // username argv[2], // password "boost_mysql_examples" // database ); // We're going to use multi-queries, which enables passing the server // a set of semicolon-separated queries. We need to explicitly enable support for it. params.set_multi_queries(true); // We'll be using metadata strings to print column names, so we need to enable support for it conn.set_meta_mode(boost::mysql::metadata_mode::full); // Connect to the server using the first endpoint returned by the resolver conn.connect(*endpoints.begin(), params); // The executed commands may generate a lot of output, so we're going to // use multi-function operations (i.e. start_execution) to read it in batches. boost::mysql::execution_state st; conn.start_execution(script_contents, st); // The main read loop. Each executed command will yield a resultset. // st.comoplete() returns true once all resultsets have been read. for (std::size_t resultset_number = 0; !st.complete(); ++resultset_number) { // Advance to next resultset, if required if (st.should_read_head()) { conn.read_resultset_head(st); } // Print the name of the fields std::cout << "Resultset number " << resultset_number << "\n"; print_column_names(st.meta()); // Read the rows and print them while (st.should_read_rows()) { boost::mysql::rows_view batch = conn.read_some_rows(st); for (auto row : batch) { print_row(row); } } // Print OK packet data print_ok(st); } // Close the connection conn.close(); } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] ================================================ FILE: example/2_simple/tls_certificate_verification.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_ASIO_HAS_CO_AWAIT //[example_tls_certificate_verification /** * This example demonstrates how to set up TLS certificate verification * and, more generally, how to pass custom TLS options to any_connection. * * It uses C++20 coroutines. If you need, you can backport * it to C++11 by using callbacks, asio::yield_context * or sync functions instead of coroutines. * * This example uses the 'boost_mysql_examples' database, which you * can get by running db_setup.sql. * Additionally, your server must be configured with a trusted certificate * with a common name of "mysql". */ #include #include #include #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; // The CA file that signed the server's certificate constexpr const char CA_PEM[] = R"%(-----BEGIN CERTIFICATE----- MIIDZzCCAk+gAwIBAgIUWznm2UoxXw3j7HCcp9PpiayTvFQwDQYJKoZIhvcNAQEL BQAwQjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAMBgNVBAoM BW15c3FsMQ4wDAYDVQQDDAVteXNxbDAgFw0yMDA0MDQxNDMwMjNaGA8zMDE5MDgw NjE0MzAyM1owQjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAM BgNVBAoMBW15c3FsMQ4wDAYDVQQDDAVteXNxbDCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBAN0WYdvsDb+a0TxOGPejcwZT0zvTrf921mmDUlrLN1Z0hJ/S ydgQCSD7Q+6za4lTFZCXcvs52xvvS2gfC0yXyYLCT/jA4RQRxuF+/+w1gDWEbGk0 KzEpsBuKrEIvEaVdoS78SxInnW/aegshdrRRocp4JQ6KHsZgkLTxSwPfYSUmMUo0 cRO0Q/ak3VK8NP13A6ZFvZjrBxjS3cSw9HqilgADcyj1D4EokvfI1C9LrgwgLlZC XVkjjBqqoMXGGlnXOEK+pm8bU68HM/QvMBkb1Amo8pioNaaYgqJUCP0Ch0iu1nUU HtsWt6emXv0jANgIW0oga7xcT4MDGN/M+IRWLTECAwEAAaNTMFEwHQYDVR0OBBYE FNxhaGwf5ePPhzK7yOAKD3VF6wm2MB8GA1UdIwQYMBaAFNxhaGwf5ePPhzK7yOAK D3VF6wm2MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAoeJCAX IDCFoAaZoQ1niI6Ac/cds8G8It0UCcFGSg+HrZ0YujJxWIruRCUG60Q2OAbEvn0+ uRpTm+4tV1Wt92WFeuRyqkomozx0g4CyfsxGX/x8mLhKPFK/7K9iTXM4/t+xQC4f J+iRmPVsMKQ8YsHYiWVhlOMH9XJQiqERCB2kOKJCH6xkaF2k0GbM2sGgbS7Z6lrd fsFTOIVx0VxLVsZnWX3byE9ghnDR5jn18u30Cpb/R/ShxNUGIHqRa4DkM5la6uZX W1fpSW11JBSUv4WnOO0C2rlIu7UJWOROqZZ0OsybPRGGwagcyff2qVRuI2XFvAMk OzBrmpfHEhF6NDU= -----END CERTIFICATE----- )%"; // The main coroutine asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, std::string_view password ) { //[section_connection_establishment_tls_options // Create a SSL context, which contains TLS configuration options asio::ssl::context ssl_ctx(asio::ssl::context::tls_client); // Enable certificate verification. If the server's certificate // is not valid or not signed by a trusted CA, async_connect will error. ssl_ctx.set_verify_mode(asio::ssl::verify_peer); // Load a trusted CA, which was used to sign the server's certificate. // This will allow the signature verification to succeed in our example. // You will have to run your MySQL server with the test certificates // located under $BOOST_MYSQL_ROOT/tools/ssl/ // If you want to use your system's trusted CAs, use // ssl::context::set_default_verify_paths() instead of this function. ssl_ctx.add_certificate_authority(asio::buffer(CA_PEM)); // We expect the server certificate's common name to be "mysql". // If it's not, the certificate will be rejected and handshake or connect will fail. // Replace "mysql" by the common name you expect. ssl_ctx.set_verify_callback(asio::ssl::host_name_verification("mysql")); // Create a connection. // We pass the context as the second argument to the connection's constructor. // Other TLS options can be also configured using this approach. // We need to keep ssl_ctx alive as long as we use the connection. mysql::any_connection conn(co_await asio::this_coro::executor, mysql::any_connection_params{&ssl_ctx}); // The hostname, username, password and database to use mysql::connect_params params; params.server_address.emplace_host_and_port(std::string(server_hostname)); params.username = username; params.password = password; params.database = "boost_mysql_examples"; // Connect to the server. If certificate verification fails, // async_connect will fail. co_await conn.async_connect(params); //] // The connection can now be used normally mysql::results result; co_await conn.async_execute("SELECT 'Hello world!'", result); std::cout << result.rows().at(0).at(0) << std::endl; // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { if (argc != 4) { std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [=] { return coro_main(argv[3], argv[1], argv[2]); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); std::cout << "Done\n"; } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/2_simple/unix_socket.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) && defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) //[example_unix_socket /** * This example demonstrates how to connect to MySQL using a UNIX socket. * * It uses C++20 coroutines. If you need, you can backport * it to C++11 by using callbacks, asio::yield_context * or sync functions instead of coroutines. */ #include #include #include #include #include #include #include #include #include namespace mysql = boost::mysql; namespace asio = boost::asio; // The main coroutine asio::awaitable coro_main( std::string_view unix_socket_path, std::string_view username, std::string_view password ) { //[section_connection_establishment_unix_socket // Create a connection. // Will use the same executor as the coroutine. mysql::any_connection conn(co_await asio::this_coro::executor); // The socket path, username, password and database to use. // server_address is a variant-like type. Using emplace_unix_path, // we can specify a UNIX socket path, instead of a hostname and a port. // UNIX socket connections never use TLS. mysql::connect_params params; params.server_address.emplace_unix_path(std::string(unix_socket_path)); params.username = username; params.password = password; params.database = "boost_mysql_examples"; // Connect to the server co_await conn.async_connect(params); //] // The connection can now be used normally mysql::results result; co_await conn.async_execute("SELECT 'Hello world!'", result); std::cout << result.rows().at(0).at(0) << std::endl; // Notify the MySQL server we want to quit, then close the underlying connection. co_await conn.async_close(); } void main_impl(int argc, char** argv) { if (argc != 3 && argc != 4) { std::cerr << "Usage: " << argv[0] << " []\n"; exit(1); } // If not provided, use the default UNIX socket path, // compatible with most UNIX systems. const char* socket_path = argc >= 4 ? argv[3] : "/var/run/mysqld/mysqld.sock"; // Create an I/O context, required by all I/O objects asio::io_context ctx; // Launch our coroutine asio::co_spawn( ctx, [=] { return coro_main(socket_path, argv[1], argv[2]); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) { std::rethrow_exception(ptr); } } ); // Calling run will actually execute the coroutine until completion ctx.run(); std::cout << "Done\n"; } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const boost::mysql::error_with_diagnostics& err) { // Some errors include additional diagnostics, like server-provided error messages. // Security note: diagnostics::server_message may contain user-supplied values (e.g. the // field value that caused the error) and is encoded using to the connection's character set // (UTF-8 by default). Treat is as untrusted input. std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n' << "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler/system doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/3_advanced/http_server_cpp14_coroutines/handle_request.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_MYSQL_CXX14 //[example_http_server_cpp14_coroutines_handle_request_cpp // // File: handle_request.cpp // // This file contains all the boilerplate code to dispatch HTTP // requests to API endpoints. Functions here end up calling // note_repository functions. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "handle_request.hpp" #include "repository.hpp" #include "types.hpp" namespace asio = boost::asio; namespace mysql = boost::mysql; namespace http = boost::beast::http; using namespace notes; namespace { // Helper function that logs errors thrown by db_repository // when an unexpected database error happens void log_mysql_error(boost::system::error_code ec, const mysql::diagnostics& diag) { // Inserting the error code only prints the number and category. Add the message, too. std::cerr << "MySQL error: " << ec << " " << ec.message(); // client_message() contains client-side generated messages that don't // contain user-input. This is usually embedded in exceptions. // When working with error codes, we need to log it explicitly if (!diag.client_message().empty()) { std::cerr << ": " << diag.client_message(); } // server_message() contains server-side messages, and thus may // contain user-supplied input. Printing it is safe. if (!diag.server_message().empty()) { std::cerr << ": " << diag.server_message(); } // Done std::cerr << std::endl; } // Attempts to parse a numeric ID from a string. // If you're using C++17, you can use std::from_chars, instead boost::optional parse_id(const std::string& from) { std::int64_t id{}; auto res = boost::charconv::from_chars(from.data(), from.data() + from.size(), id); if (res.ec != std::errc{} || res.ptr != from.data() + from.size()) return {}; return id; } // Helpers to create error responses with a single line of code http::response error_response(http::status code, const char* msg) { http::response res; res.result(code); res.body() = msg; return res; } // Like error_response, but always uses a 400 status code http::response bad_request(const char* body) { return error_response(http::status::bad_request, body); } // Like error_response, but always uses a 500 status code and // never provides extra information that might help potential attackers. http::response internal_server_error() { return error_response(http::status::internal_server_error, "Internal server error"); } // Creates a response with a serialized JSON body. // T should be a type with Boost.Describe metadata containing the // body data to be serialized template http::response json_response(const T& body) { http::response res; // Set the content-type header res.set("Content-Type", "application/json"); // Serialize the body data into a string and use it as the response body. // We use Boost.JSON's automatic serialization feature, which uses Boost.Describe // reflection data to generate a serialization function for us. res.body() = boost::json::serialize(boost::json::value_from(body)); // Done return res; } // Returns true if the request's Content-Type is set to JSON bool has_json_content_type(const http::request& req) { auto it = req.find("Content-Type"); return it != req.end() && it->value() == "application/json"; } // Attempts to parse a string as a JSON into an object of type T. // T should be a type with Boost.Describe metadata. // We use boost::system::result, which may contain a result or an error. template boost::system::result parse_json(boost::mysql::string_view json_string) { // Attempt to parse the request into a json::value. // This will fail if the provided body isn't valid JSON. boost::system::error_code ec; auto val = boost::json::parse(json_string, ec); if (ec) return ec; // Attempt to parse the json::value into a T. This will // fail if the provided JSON doesn't match T's shape. return boost::json::try_value_to(val); } // Contains data associated to an HTTP request. // To be passed to individual handler functions struct request_data { // The incoming request const http::request& request; // The URL the request is targeting boost::urls::url_view target; // Connection pool mysql::connection_pool& pool; note_repository repo() const { return note_repository(pool); } }; // // Endpoint handlers. We have a function per method. // All of our endpoints have /notes as the URL path. // // GET /notes: retrieves all the notes. // The request doesn't have a body. // The response has a JSON body with multi_notes_response format // // GET /notes?id=: retrieves a single note. // The request doesn't have a body. // The response has a JSON body with single_note_response format // // Both endpoints share path and method, so they share handler function http::response handle_get(const request_data& input, asio::yield_context yield) { // Parse the query parameter auto params_it = input.target.params().find("id"); // Did the client specify an ID? if (params_it == input.target.params().end()) { auto res = input.repo().get_notes(yield); return json_response(multi_notes_response{std::move(res)}); } else { // Parse id auto id = parse_id((*params_it).value); if (!id.has_value()) return bad_request("URL parameter 'id' should be a valid integer"); // Get the note auto res = input.repo().get_note(*id, yield); // If we didn't find it, return a 404 error if (!res.has_value()) return error_response(http::status::not_found, "The requested note was not found"); // Return it as response return json_response(single_note_response{std::move(*res)}); } } // POST /notes: creates a note. // The request has a JSON body with note_request_body format. // The response has a JSON body with single_note_response format. http::response handle_post(const request_data& input, asio::yield_context yield) { // Parse the request body if (!has_json_content_type(input.request)) return bad_request("Invalid Content-Type: expected 'application/json'"); auto args = parse_json(input.request.body()); if (args.has_error()) return bad_request("Invalid JSON"); // Actually create the note auto res = input.repo().create_note(args->title, args->content, yield); // Return the newly created note as response return json_response(single_note_response{std::move(res)}); } // PUT /notes?id=: replaces a note. // The request has a JSON body with note_request_body format. // The response has a JSON body with single_note_response format. http::response handle_put(const request_data& input, asio::yield_context yield) { // Parse the query parameter auto params_it = input.target.params().find("id"); if (params_it == input.target.params().end()) return bad_request("Mandatory URL parameter 'id' not found"); auto id = parse_id((*params_it).value); if (!id.has_value()) return bad_request("URL parameter 'id' should be a valid integer"); // Parse the request body if (!has_json_content_type(input.request)) return bad_request("Invalid Content-Type: expected 'application/json'"); auto args = parse_json(input.request.body()); if (args.has_error()) return bad_request("Invalid JSON"); // Perform the update auto res = input.repo().replace_note(*id, args->title, args->content, yield); // Check that it took effect. Otherwise, it's because the note wasn't there if (!res.has_value()) return bad_request("The requested note was not found"); // Return the updated note as response return json_response(single_note_response{std::move(*res)}); } // DELETE /notes/: deletes a note. // The request doesn't have a body. // The response has a JSON body with delete_note_response format. http::response handle_delete(const request_data& input, asio::yield_context yield) { // Parse the query parameter auto params_it = input.target.params().find("id"); if (params_it == input.target.params().end()) return bad_request("Mandatory URL parameter 'id' not found"); auto id = parse_id((*params_it).value); if (!id.has_value()) return bad_request("URL parameter 'id' should be a valid integer"); // Attempt to delete the note bool deleted = input.repo().delete_note(*id, yield); // Return whether the delete was successful in the response. // We don't fail DELETEs for notes that don't exist. return json_response(delete_note_response{deleted}); } } // namespace // External interface http::response notes::handle_request( mysql::connection_pool& pool, const http::request& request, asio::yield_context yield ) { // Parse the request target auto target = boost::urls::parse_origin_form(request.target()); if (!target.has_value()) return bad_request("Invalid request target"); // All our endpoints have /notes as path, with different verbs and parameters. // Verify that the path matches if (target->path() != "/notes") return error_response(http::status::not_found, "Endpoint not found"); // Compose the request_data object request_data input{request, *target, pool}; // Invoke the relevant handler, depending on the method try { switch (input.request.method()) { case http::verb::get: return handle_get(input, yield); case http::verb::post: return handle_post(input, yield); case http::verb::put: return handle_put(input, yield); case http::verb::delete_: return handle_delete(input, yield); default: return error_response(http::status::method_not_allowed, "Method not allowed for /notes"); } } catch (const mysql::error_with_diagnostics& err) { // A Boost.MySQL error. This will happen if you don't have connectivity // to your database, your schema is incorrect or your credentials are invalid. // Log the error, including diagnostics log_mysql_error(err.code(), err.get_diagnostics()); // Never disclose error info to a potential attacker return internal_server_error(); } catch (const std::exception& err) { // Another kind of error. This indicates a programming error or a severe // server condition (e.g. out of memory). Same procedure as above. std::cerr << "Uncaught exception: " << err.what() << std::endl; return internal_server_error(); } } //] #endif ================================================ FILE: example/3_advanced/http_server_cpp14_coroutines/handle_request.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP14_COROUTINES_HANDLE_REQUEST_HPP #define BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP14_COROUTINES_HANDLE_REQUEST_HPP //[example_http_server_cpp14_coroutines_handle_request_hpp // // File: handle_request.hpp // #include #include #include #include namespace notes { // Handles an individual HTTP request, producing a response. // The caller of this function should use response::version, // response::keep_alive and response::prepare_payload to adjust the response. boost::beast::http::response handle_request( boost::mysql::connection_pool& pool, const boost::beast::http::request& request, boost::asio::yield_context yield ); } // namespace notes //] #endif ================================================ FILE: example/3_advanced/http_server_cpp14_coroutines/main.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_MYSQL_CXX14 //[example_http_server_cpp14_coroutines_main_cpp /** * Implements a HTTP REST API using Boost.MySQL and Boost.Beast. * The server is asynchronous and uses asio::yield_context as its completion * style. It only requires C++14 to work. * * It implements a minimal REST API to manage notes. * A note is a simple object containing a user-defined title and content. * The REST API offers CRUD operations on such objects: * POST /notes Creates a new note. * GET /notes Retrieves all notes. * GET /notes?id= Retrieves a single note. * PUT /notes?id= Replaces a note, changing its title and content. * DELETE /notes?id= Deletes a note. * * Notes are stored in MySQL. The note_repository class encapsulates * access to MySQL, offering friendly functions to manipulate notes. * server.cpp encapsulates all the boilerplate to launch an HTTP server, * match URLs to API endpoints, and invoke the relevant note_repository functions. * * All communication happens asynchronously. We use stackful coroutines to simplify * development, using asio::spawn and asio::yield_context. * This example requires linking to Boost::context, Boost::json and Boost::url. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "server.hpp" namespace asio = boost::asio; namespace mysql = boost::mysql; using namespace notes; int main(int argc, char* argv[]) { // Check command line arguments. if (argc != 5) { std::cerr << "Usage: " << argv[0] << " \n"; return EXIT_FAILURE; } // Application config const char* mysql_username = argv[1]; const char* mysql_password = argv[2]; const char* mysql_hostname = argv[3]; auto port = static_cast(std::stoi(argv[4])); // An event loop, where the application will run. asio::io_context ctx; // Configuration for the connection pool mysql::pool_params params{ // Connect using TCP, to the given hostname and using the default port mysql::host_and_port{mysql_hostname}, // Authenticate using the given username mysql_username, // Password for the above username mysql_password, // Database to use when connecting "boost_mysql_examples", }; // Create the connection pool. // shared_state contains all singleton objects that our application may need. // Coroutines created by asio::spawn might survive until the io_context is destroyed // (even after io_context::stop() has been called). This is not the case for callbacks // and C++20 coroutines. Using a shared_ptr here ensures that the pool survives long enough. auto st = std::make_shared(mysql::connection_pool(ctx, std::move(params))); // Launch the MySQL pool st->pool.async_run(asio::detached); // A signal_set allows us to intercept SIGINT and SIGTERM and exit gracefully asio::signal_set signals{ctx.get_executor(), SIGINT, SIGTERM}; signals.async_wait([st, &ctx](boost::system::error_code, int) { // Stop the execution context. This will cause main to exit ctx.stop(); }); // Launch the server. This will run until the context is stopped asio::spawn( // Spawn the coroutine in the io_context ctx, // The coroutine to run [st, port](asio::yield_context yield) { run_server(st, port, yield); }, // If an exception is thrown in the coroutine, propagate it [](std::exception_ptr exc) { if (exc) std::rethrow_exception(exc); } ); // Run the server until stopped ctx.run(); std::cout << "Server exiting" << std::endl; // (If we get here, it means we got a SIGINT or SIGTERM) return EXIT_SUCCESS; } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't support C++14\n"; } #endif ================================================ FILE: example/3_advanced/http_server_cpp14_coroutines/repository.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_MYSQL_CXX14 //[example_http_server_cpp14_coroutines_repository_cpp // // File: repository.cpp // // SQL code to create the notes table is located under $REPO_ROOT/example/db_setup.sql // The table looks like this: // // CREATE TABLE notes( // id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, // title TEXT NOT NULL, // content TEXT NOT NULL // ); #include #include #include #include #include #include #include #include "repository.hpp" #include "types.hpp" namespace asio = boost::asio; namespace mysql = boost::mysql; using namespace notes; using mysql::with_diagnostics; std::vector note_repository::get_notes(asio::yield_context yield) { // Get a fresh connection from the pool. This returns a pooled_connection object, // which is a proxy to an any_connection object. Connections are returned to the // pool when the proxy object is destroyed. // with_diagnostics ensures that thrown exceptions include diagnostic information mysql::pooled_connection conn = pool_.async_get_connection(with_diagnostics(yield)); // Execute the query to retrieve all notes. We use the static interface to // parse results directly into static_results. mysql::static_results result; conn->async_execute("SELECT id, title, content FROM notes", result, with_diagnostics(yield)); // By default, connections are reset after they are returned to the pool // (by using any_connection::async_reset_connection). This will reset any // session state we changed while we were using the connection // (e.g. it will deallocate any statements we prepared). // We did nothing to mutate session state, so we can tell the pool to skip // this step, providing a minor performance gain. // We use pooled_connection::return_without_reset to do this. conn.return_without_reset(); // Move note_t objects into the result vector to save allocations return std::vector( std::make_move_iterator(result.rows().begin()), std::make_move_iterator(result.rows().end()) ); // If an exception is thrown, pooled_connection's destructor will // return the connection automatically to the pool. } boost::optional note_repository::get_note(std::int64_t note_id, asio::yield_context yield) { // Get a fresh connection from the pool. This returns a pooled_connection object, // which is a proxy to an any_connection object. Connections are returned to the // pool when the proxy object is destroyed. mysql::pooled_connection conn = pool_.async_get_connection(with_diagnostics(yield)); // When executed, with_params expands a query client-side before sending it to the server. // Placeholders are marked with {} mysql::static_results result; conn->async_execute( mysql::with_params("SELECT id, title, content FROM notes WHERE id = {}", note_id), result, with_diagnostics(yield) ); // We did nothing to mutate session state, so we can skip reset conn.return_without_reset(); // An empty results object indicates that no note was found if (result.rows().empty()) return {}; else return std::move(result.rows()[0]); } note_t note_repository::create_note( mysql::string_view title, mysql::string_view content, asio::yield_context yield ) { // Get a fresh connection from the pool. This returns a pooled_connection object, // which is a proxy to an any_connection object. Connections are returned to the // pool when the proxy object is destroyed. mysql::pooled_connection conn = pool_.async_get_connection(with_diagnostics(yield)); // We will use statements in this function for the sake of example. // We don't need to deallocate the statement explicitly, // since the pool takes care of it after the connection is returned. // You can also use with_params instead of statements. mysql::statement stmt = conn->async_prepare_statement( "INSERT INTO notes (title, content) VALUES (?, ?)", with_diagnostics(yield) ); // Execute the statement. The statement won't produce any rows, // so we can use static_results> mysql::static_results> result; conn->async_execute(stmt.bind(title, content), result, with_diagnostics(yield)); // MySQL reports last_insert_id as a uint64_t regardless of the actual ID type. // Given our table definition, this cast is safe auto new_id = static_cast(result.last_insert_id()); return note_t{new_id, title, content}; // There's no need to return the connection explicitly to the pool, // pooled_connection's destructor takes care of it. } boost::optional note_repository::replace_note( std::int64_t note_id, mysql::string_view title, mysql::string_view content, asio::yield_context yield ) { // Get a fresh connection from the pool. This returns a pooled_connection object, // which is a proxy to an any_connection object. Connections are returned to the // pool when the proxy object is destroyed. mysql::pooled_connection conn = pool_.async_get_connection(with_diagnostics(yield)); // Expand and execute the query. // It won't produce any rows, so we can use static_results> mysql::static_results> empty_result; conn->async_execute( mysql::with_params( "UPDATE notes SET title = {}, content = {} WHERE id = {}", title, content, note_id ), empty_result, with_diagnostics(yield) ); // We didn't mutate session state, so we can skip reset conn.return_without_reset(); // No affected rows means that the note doesn't exist if (empty_result.affected_rows() == 0u) return {}; return note_t{note_id, title, content}; } bool note_repository::delete_note(std::int64_t note_id, asio::yield_context yield) { // Get a fresh connection from the pool. This returns a pooled_connection object, // which is a proxy to an any_connection object. Connections are returned to the // pool when the proxy object is destroyed. mysql::pooled_connection conn = pool_.async_get_connection(with_diagnostics(yield)); // Expand and execute the query. // It won't produce any rows, so we can use static_results> mysql::static_results> empty_result; conn->async_execute( mysql::with_params("DELETE FROM notes WHERE id = {}", note_id), empty_result, with_diagnostics(yield) ); // We didn't mutate session state, so we can skip reset conn.return_without_reset(); // No affected rows means that the note didn't exist return empty_result.affected_rows() != 0u; } //] #endif ================================================ FILE: example/3_advanced/http_server_cpp14_coroutines/repository.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP14_COROUTINES_REPOSITORY_HPP #define BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP14_COROUTINES_REPOSITORY_HPP //[example_http_server_cpp14_coroutines_repository_hpp // // File: repository.hpp // #include #include #include #include #include #include "types.hpp" namespace notes { // Encapsulates database logic. // All operations are async, and use stackful coroutines (asio::yield_context). // If the database can't be contacted, or unexpected database errors are found, // an exception of type mysql::error_with_diagnostics is thrown. class note_repository { boost::mysql::connection_pool& pool_; public: // Constructor (this is a cheap-to-construct object) note_repository(boost::mysql::connection_pool& pool) noexcept : pool_(pool) {} // Retrieves all notes present in the database std::vector get_notes(boost::asio::yield_context yield); // Retrieves a single note by ID. Returns an empty optional // if no note with the given ID is present in the database. boost::optional get_note(std::int64_t note_id, boost::asio::yield_context yield); // Creates a new note in the database with the given components. // Returns the newly created note, including the newly allocated ID. note_t create_note( boost::mysql::string_view title, boost::mysql::string_view content, boost::asio::yield_context yield ); // Replaces the note identified by note_id, setting its components to the // ones passed. Returns the updated note. If no note with ID matching // note_id can be found, an empty optional is returned. boost::optional replace_note( std::int64_t note_id, boost::mysql::string_view title, boost::mysql::string_view content, boost::asio::yield_context yield ); // Deletes the note identified by note_id. Returns true if // a matching note was deleted, false otherwise. bool delete_note(std::int64_t note_id, boost::asio::yield_context yield); }; } // namespace notes //] #endif ================================================ FILE: example/3_advanced/http_server_cpp14_coroutines/server.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #ifdef BOOST_MYSQL_CXX14 //[example_http_server_cpp14_coroutines_server_cpp // // File: server.cpp // // This file contains all the boilerplate code to implement a HTTP // server. Functions here end up invoking handle_request. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "handle_request.hpp" #include "server.hpp" namespace asio = boost::asio; namespace http = boost::beast::http; using namespace notes; namespace { // Runs a single HTTP session until the client closes the connection void run_http_session(std::shared_ptr st, asio::ip::tcp::socket sock, asio::yield_context yield) { using namespace std::chrono_literals; boost::system::error_code ec; // A buffer to read incoming client requests boost::beast::flat_buffer buff; // A timer, to use with asio::cancel_after to implement timeouts. // Re-using the same timer multiple times with cancel_after // is more efficient than using raw cancel_after, // since the timer doesn't need to be re-created for every operation. asio::steady_timer timer(yield.get_executor()); // A HTTP session might involve more than one message if // keep-alive semantics are used. Loop until the connection closes. while (true) { // Construct a new parser for each message http::request_parser parser; // Apply a reasonable limit to the allowed size // of the body in bytes to prevent abuse. parser.body_limit(10000); // Read a request. yield[ec] prevents exceptions from being thrown // on error. We use cancel_after to set a timeout for the overall read operation. http::async_read(sock, buff, parser.get(), asio::cancel_after(60s, yield[ec])); if (ec) { if (ec == http::error::end_of_stream) { // This means they closed the connection sock.shutdown(asio::ip::tcp::socket::shutdown_send, ec); } else { // An unknown error happened std::cout << "Error reading HTTP request: " << ec.message() << std::endl; } return; } const auto& request = parser.get(); // Process the request to generate a response. // This invokes the business logic, which will need to access MySQL data. // Apply a timeout to the overall request handling process. auto response = asio::spawn( // Use the same executor as this coroutine yield.get_executor(), // The logic to invoke [&](asio::yield_context yield2) { return handle_request(st->pool, request, yield2); }, // Completion token. Passing yield blocks the current coroutine // until handle_request completes. asio::cancel_after(timer, 30s, yield) ); // Adjust the response, setting fields common to all responses bool keep_alive = response.keep_alive(); response.version(request.version()); response.keep_alive(keep_alive); response.prepare_payload(); // Send the response http::async_write(sock, response, asio::cancel_after(60s, yield[ec])); if (ec) { std::cout << "Error writing HTTP response: " << ec.message() << std::endl; return; } // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. if (!keep_alive) { sock.shutdown(asio::ip::tcp::socket::shutdown_send, ec); return; } } } } // namespace void notes::run_server(std::shared_ptr st, unsigned short port, asio::yield_context yield) { // An object that allows us to accept incoming TCP connections asio::ip::tcp::acceptor acc(yield.get_executor()); // The endpoint where the server will listen. Edit this if you want to // change the address or port we bind to. asio::ip::tcp::endpoint listening_endpoint(asio::ip::make_address("0.0.0.0"), port); // Open the acceptor acc.open(listening_endpoint.protocol()); // Allow address reuse acc.set_option(asio::socket_base::reuse_address(true)); // Bind to the server address acc.bind(listening_endpoint); // Start listening for connections acc.listen(asio::socket_base::max_listen_connections); std::cout << "Server listening at " << acc.local_endpoint() << std::endl; // Start the acceptor loop while (true) { // Accept a new connection asio::ip::tcp::socket sock = acc.async_accept(yield); // Launch a new session for this connection. Each session gets its // own coroutine, so we can get back to listening for new connections. asio::spawn( yield.get_executor(), // Function implementing our session logic. // Takes ownership of the socket. [st, sock = std::move(sock)](asio::yield_context yield2) mutable { return run_http_session(std::move(st), std::move(sock), yield2); }, // Callback to run when the coroutine finishes [](std::exception_ptr ptr) { if (ptr) { // For extra safety, log the exception but don't propagate it. // If we failed to anticipate an error condition that ends up raising an exception, // terminate only the affected session, instead of crashing the server. try { std::rethrow_exception(ptr); } catch (const std::exception& exc) { std::cerr << "Uncaught error in a session: " << exc.what() << std::endl; } } } ); } } //] #endif ================================================ FILE: example/3_advanced/http_server_cpp14_coroutines/server.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP14_COROUTINES_SERVER_HPP #define BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP14_COROUTINES_SERVER_HPP //[example_http_server_cpp14_coroutines_server_hpp // // File: server.hpp // #include #include #include namespace notes { // State shared by all sessions created by our server. // For this application, we only need a connection_pool object. // Place here any other singleton objects your application may need. // We will use std::shared_ptr to ensure that objects // are kept alive until all sessions are terminated. struct shared_state { boost::mysql::connection_pool pool; shared_state(boost::mysql::connection_pool pool) noexcept : pool(std::move(pool)) {} }; // Runs a HTTP server that will listen on 0.0.0.0:port. // If the server fails to launch (e.g. because the port is already in use), // throws an exception. The server runs until the underlying execution // context is stopped. void run_server(std::shared_ptr st, unsigned short port, boost::asio::yield_context yield); } // namespace notes //] #endif ================================================ FILE: example/3_advanced/http_server_cpp14_coroutines/types.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP14_COROUTINES_TYPES_HPP #define BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP14_COROUTINES_TYPES_HPP //[example_http_server_cpp14_coroutines_types_hpp // // File: types.hpp // // Contains type definitions used in the REST API and database code. // We use Boost.Describe (BOOST_DESCRIBE_STRUCT) to add reflection // capabilities to our types. This allows using Boost.MySQL // static interface (i.e. static_results) to parse query results, // and Boost.JSON automatic serialization/deserialization. #include #include #include #include namespace notes { struct note_t { // The unique database ID of the object. std::int64_t id; // The note's title. std::string title; // The note's actual content. std::string content; }; BOOST_DESCRIBE_STRUCT(note_t, (), (id, title, content)) // // REST API requests. // // Used for creating and replacing notes struct note_request_body { // The title that the new note should have. std::string title; // The content that the new note should have. std::string content; }; BOOST_DESCRIBE_STRUCT(note_request_body, (), (title, content)) // // REST API responses. // // Used by endpoints returning several notes (like GET /notes). struct multi_notes_response { // The retrieved notes. std::vector notes; }; BOOST_DESCRIBE_STRUCT(multi_notes_response, (), (notes)) // Used by endpoints returning a single note (like GET /notes/) struct single_note_response { // The retrieved note. note_t note; }; BOOST_DESCRIBE_STRUCT(single_note_response, (), (note)) // Used by DELETE /notes/ struct delete_note_response { // true if the note was found and deleted, false if the note didn't exist. bool deleted; }; BOOST_DESCRIBE_STRUCT(delete_note_response, (), (deleted)) } // namespace notes //] #endif ================================================ FILE: example/3_advanced/http_server_cpp20/db_setup.sql ================================================ -- -- Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) -- -- Distributed under the Boost Software License, Version 1.0. (See accompanying -- file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -- -- Connection system variables SET NAMES utf8; -- Database DROP DATABASE IF EXISTS boost_mysql_orders; CREATE DATABASE boost_mysql_orders; USE boost_mysql_orders; -- User DROP USER IF EXISTS 'orders_user'@'%'; CREATE USER 'orders_user'@'%' IDENTIFIED BY 'orders_password'; GRANT ALL PRIVILEGES ON boost_mysql_orders.* TO 'orders_user'@'%'; FLUSH PRIVILEGES; -- Tables CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, short_name VARCHAR(100) NOT NULL, descr TEXT, price INT NOT NULL, FULLTEXT(short_name, descr) ); CREATE TABLE orders( id INT PRIMARY KEY AUTO_INCREMENT, `status` ENUM('draft', 'pending_payment', 'complete') NOT NULL DEFAULT 'draft' ); CREATE TABLE order_items( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL, FOREIGN KEY (order_id) REFERENCES orders(id), FOREIGN KEY (product_id) REFERENCES products(id) ); -- Contents for the products table INSERT INTO products (price, short_name, descr) VALUES (6400, 'A Feast for Odin', 'A Feast for Odin is a points-driven game, with plethora of pathways to victory, with a range of risk balanced against reward. A significant portion of this is your central hall, which has a whopping -86 points of squares and a major part of your game is attempting to cover these up with various tiles. Likewise, long halls and island colonies can also offer large rewards, but they will have penalties of their own.'), (1600, 'Railroad Ink', 'The critically acclaimed roll and write game where you draw routes on your board trying to connect the exits at its edges. The more you connect, the more points you make, but beware: each incomplete route will make you lose points!'), (4000, 'Catan', 'Catan is a board game for two to four players in which you compete to gather resources and build the biggest settlements on the fictional island of Catan. It takes approximately one hour to play.'), (2500, 'Not Alone', 'It is the 25th century. You are a member of an intergalactic expedition shipwrecked on a mysterious planet named Artemia. While waiting for the rescue ship, you begin to explore the planet but an alien entity picks up your scent and begins to hunt you. You are NOT ALONE! Will you survive the dangers of Artemia?'), (4500, 'Dice Hospital', "In Dice Hospital, a worker placement board game, players are tasked with running a local hospital. Each round you'll be admitting new patients, hiring specialists, building new departments, and treating as many incoming patients as you can.") ; ================================================ FILE: example/3_advanced/http_server_cpp20/error.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) && BOOST_PFR_CORE_NAME_ENABLED //[example_http_server_cpp20_error_cpp #include #include #include #include "error.hpp" namespace { // Converts an orders::errc to string const char* error_to_string(orders::errc value) { switch (value) { case orders::errc::not_found: return "not_found"; case orders::errc::order_invalid_status: return "order_invalid_status"; case orders::errc::product_not_found: return "product_not_found"; default: return ""; } } // The category to be returned by get_orders_category class orders_category final : public boost::system::error_category { public: // Identifies the error category. Used when converting error_codes to string const char* name() const noexcept final override { return "orders"; } // Given a numeric error belonging to this category, convert it to a string std::string message(int ev) const final override { return error_to_string(static_cast(ev)); } }; // The error category static const orders_category g_category; // The std::mutex that guards std::cerr static std::mutex g_cerr_mutex; } // namespace // // External interface // const boost::system::error_category& orders::get_orders_category() { return g_category; } std::unique_lock orders::lock_cerr() { return std::unique_lock{g_cerr_mutex}; } void orders::log_error(std::string_view header, boost::system::error_code ec) { // Lock the mutex auto guard = lock_cerr(); // Logging the error code prints the number and category. Add the message, too std::cerr << header << ": " << ec << " " << ec.message() << std::endl; } //] #endif ================================================ FILE: example/3_advanced/http_server_cpp20/error.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP20_ERROR_HPP #define BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP20_ERROR_HPP //[example_http_server_cpp20_error_hpp // // File: error.hpp // // Contains an errc enumeration and the required pieces to // use it with boost::system::error_code. // We use this indirectly in the DB repository class, // when using the error codes in boost::system::result. #include #include #include #include namespace orders { // Error code enum for errors originated within our application enum class errc { not_found, // couldn't retrieve or modify a certain resource because it doesn't exist order_invalid_status, // an operation found an order in a status != the one expected (e.g. not editable) product_not_found, // a product referenced by a request doesn't exist }; // To use errc with boost::system::error_code, we need // to define an error category (see the cpp file). const boost::system::error_category& get_orders_category(); // Called when constructing an error_code from an errc value. inline boost::system::error_code make_error_code(errc v) { // Roughly, an error_code is an int and a category defining what the int means. return boost::system::error_code(static_cast(v), get_orders_category()); } // In multi-threaded programs, using std::cerr without any locking // can result in interleaved output. // Locks a mutex guarding std::cerr to prevent this. // All uses of std::cerr should respect this. std::unique_lock lock_cerr(); // A helper function for the common case where we want to log an error code void log_error(std::string_view header, boost::system::error_code ec); } // namespace orders // This specialization is required to construct error_code's from errc values template <> struct boost::system::is_error_code_enum : std::true_type { }; //] #endif ================================================ FILE: example/3_advanced/http_server_cpp20/handle_request.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) && BOOST_PFR_CORE_NAME_ENABLED //[example_http_server_cpp20_handle_request_cpp // // File: handle_request.cpp // // This file contains all the boilerplate code to dispatch HTTP // requests to API endpoints. Functions here end up calling // db_repository fuctions. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "error.hpp" #include "handle_request.hpp" #include "repository.hpp" #include "types.hpp" namespace asio = boost::asio; namespace http = boost::beast::http; namespace mysql = boost::mysql; using boost::system::result; namespace { // Helper function that logs errors thrown by db_repository // when an unexpected database error happens void log_mysql_error(boost::system::error_code ec, const mysql::diagnostics& diag) { // Lock std::cerr, to avoid race conditions auto guard = orders::lock_cerr(); // Inserting the error code only prints the number and category. Add the message, too. std::cerr << "MySQL error: " << ec << " " << ec.message(); // client_message() contains client-side generated messages that don't // contain user-input. This is usually embedded in exceptions. // When working with error codes, we need to log it explicitly if (!diag.client_message().empty()) { std::cerr << ": " << diag.client_message(); } // server_message() contains server-side messages, and thus may // contain user-supplied input. Printing it is safe. if (!diag.server_message().empty()) { std::cerr << ": " << diag.server_message(); } // Done std::cerr << std::endl; } // Attempts to parse a numeric ID from a string std::optional parse_id(std::string_view from) { std::int64_t id{}; auto res = std::from_chars(from.data(), from.data() + from.size(), id); if (res.ec != std::errc{} || res.ptr != from.data() + from.size()) return std::nullopt; return id; } // Helpers to create error responses with a single line of code http::response error_response(http::status code, std::string_view msg) { http::response res; res.result(code); res.body() = msg; return res; } // Like error_response, but always uses a 400 status code http::response bad_request(std::string_view body) { return error_response(http::status::bad_request, body); } // Like error_response, but always uses a 500 status code and // never provides extra information that might help potential attackers. http::response internal_server_error() { return error_response(http::status::internal_server_error, "Internal server error"); } // Creates a response with a serialized JSON body. // T should be a type with Boost.Describe metadata containing the // body data to be serialized template http::response json_response(const T& body) { http::response res; // Set the content-type header res.set("Content-Type", "application/json"); // Serialize the body data into a string and use it as the response body. // We use Boost.JSON's automatic serialization feature, which uses Boost.Describe // reflection data to generate a serialization function for us. res.body() = boost::json::serialize(boost::json::value_from(body)); // Done return res; } // Attempts to parse a string as a JSON into an object of type T. // T should be a type with Boost.Describe metadata. // We use boost::system::result, which may contain a result or an error. template result parse_json(std::string_view json_string) { // Attempt to parse the request into a json::value. // This will fail if the provided body isn't valid JSON. boost::system::error_code ec; auto val = boost::json::parse(json_string, ec); if (ec) return ec; // Attempt to parse the json::value into a T. This will // fail if the provided JSON doesn't match T's shape. return boost::json::try_value_to(val); } // Generates an HTTP error response based on an error code // returned by db_repository. http::response response_from_db_error(boost::system::error_code ec) { if (ec.category() == orders::get_orders_category()) { switch (static_cast(ec.value())) { case orders::errc::not_found: return error_response(http::status::not_found, "The referenced entity does not exist"); case orders::errc::product_not_found: return error_response( http::status::unprocessable_entity, "The referenced product does not exist" ); case orders::errc::order_invalid_status: return error_response( http::status::unprocessable_entity, "The referenced order doesn't have the status required by the operation" ); default: return internal_server_error(); } } else { return internal_server_error(); } } // Contains data associated to an HTTP request. // To be passed to individual handler functions struct request_data { // The incoming request const http::request& request; // The URL the request is targeting boost::urls::url_view target; // Connection pool mysql::connection_pool& pool; orders::db_repository repo() const { return orders::db_repository(pool); } }; // // Endpoint handlers. They should be functions with signature // asio::awaitable>(const request_data&). // Handlers are associated to a single URL path and HTTP method // // GET /products?search={s}: returns a list of products. // The 'search' parameter is mandatory. asio::awaitable> handle_get_products(const request_data& input) { // Parse the query parameter auto params_it = input.target.params().find("search"); if (params_it == input.target.params().end()) co_return bad_request("Missing mandatory query parameter: 'search'"); auto search = (*params_it).value; // Invoke the database logic std::vector products = co_await input.repo().get_products(search); // Return the response co_return json_response(products); } // GET /orders: returns all orders // GET /orders?id={}: returns a single order // Both endpoints share handler because they share path and method asio::awaitable> handle_get_orders(const request_data& input) { // Parse the query parameter auto params_it = input.target.params().find("id"); // Which of the two endpoints are we serving? if (params_it == input.target.params().end()) { // GET /orders // Invoke the database logic std::vector orders = co_await input.repo().get_orders(); // Return the response co_return json_response(orders); } else { // GET /orders?id={} // Parse the query parameter auto order_id = parse_id((*params_it).value); if (!order_id.has_value()) co_return bad_request("URL parameter 'id' should be a valid integer"); // Invoke the database logic result order = co_await input.repo().get_order_by_id(*order_id); if (order.has_error()) co_return response_from_db_error(order.error()); // Return the response co_return json_response(*order); } } // POST /orders: creates a new order. // Orders are created empty, so this request has no body. asio::awaitable> handle_create_order(const request_data& input) { // Invoke the database logic orders::order_with_items order = co_await input.repo().create_order(); // Return the response co_return json_response(order); } // POST /orders/items: adds a new order item to an existing order. // The request has a JSON body, described by the add_order_item_request struct. asio::awaitable> handle_add_order_item(const request_data& input) { // Check that the request has the appropriate content type auto it = input.request.find("Content-Type"); if (it == input.request.end() || it->value() != "application/json") co_return bad_request("Invalid Content-Type: expected 'application/json'"); // Parse the request body auto req = parse_json(input.request.body()); if (req.has_error()) co_return bad_request("Invalid JSON body"); // Invoke the database logic result res = co_await input.repo() .add_order_item(req->order_id, req->product_id, req->quantity); if (res.has_error()) co_return response_from_db_error(res.error()); // Return the response co_return json_response(*res); } // DELETE /orders/items?id={}: deletes an order item. // The request has no body. asio::awaitable> handle_remove_order_item(const request_data& input) { // Parse the query parameter auto params_it = input.target.params().find("id"); if (params_it == input.target.params().end()) co_return bad_request("Mandatory URL parameter 'id' not found"); auto id = parse_id((*params_it).value); if (!id.has_value()) co_return bad_request("URL parameter 'id' should be a valid integer"); // Invoke the database logic result res = co_await input.repo().remove_order_item(*id); if (res.has_error()) co_return response_from_db_error(res.error()); // Return the response co_return json_response(*res); } // POST /orders/checkout?id={}: checks out an order. // The request has no body. asio::awaitable> handle_checkout_order(const request_data& input) { // Parse the query parameter auto params_it = input.target.params().find("id"); if (params_it == input.target.params().end()) co_return bad_request("Mandatory URL parameter 'id' not found"); auto id = parse_id((*params_it).value); if (!id.has_value()) co_return bad_request("URL parameter 'id' should be a valid integer"); // Invoke the database logic result res = co_await input.repo().checkout_order(*id); if (res.has_error()) co_return response_from_db_error(res.error()); // Return the response co_return json_response(*res); } // POST /orders/complete?id={}: marks an order as completed. // The request has no body. asio::awaitable> handle_complete_order(const request_data& input) { // Parse the query parameter auto params_it = input.target.params().find("id"); if (params_it == input.target.params().end()) co_return bad_request("Mandatory URL parameter 'id' not found"); auto id = parse_id((*params_it).value); if (!id.has_value()) co_return bad_request("URL parameter 'id' should be a valid integer"); // Invoke the database logic result res = co_await input.repo().complete_order(*id); if (res.has_error()) co_return response_from_db_error(res.error()); // Return the response co_return json_response(*res); } // handle_request uses a table to dispatch to each endpoint. // This is the table's element type. struct http_endpoint { // The HTTP method associated to this endpoint. http::verb method; // The endpoint handler. asio::awaitable> (*handler)(const request_data&); }; // Maps from a URL path to an endpoint handler. // A URL path might be present more than once, for different methods. const std::unordered_multimap endpoint_table{ {"/products", {http::verb::get, &handle_get_products} }, {"/orders", {http::verb::get, &handle_get_orders} }, {"/orders", {http::verb::post, &handle_create_order} }, {"/orders/items", {http::verb::post, &handle_add_order_item} }, {"/orders/items", {http::verb::delete_, &handle_remove_order_item}}, {"/orders/checkout", {http::verb::post, &handle_checkout_order} }, {"/orders/complete", {http::verb::post, &handle_complete_order} }, }; } // namespace // External interface asio::awaitable> orders::handle_request( const http::request& request, mysql::connection_pool& pool ) { // Parse the request target auto target = boost::urls::parse_origin_form(request.target()); if (!target.has_value()) co_return bad_request("Invalid request target"); // Try to find an endpoint auto [it1, it2] = endpoint_table.equal_range(target->path()); if (it1 == endpoint_table.end()) co_return error_response(http::status::not_found, "The requested endpoint does not exist"); // Match the verb. The table structure that we created // allows us to distinguish between an "endpoint does not exist" error // and an "unsupported method" error. auto it3 = std::find_if(it1, it2, [&request](const std::pair& ep) { return ep.second.method == request.method(); }); if (it3 == it2) co_return error_response(http::status::method_not_allowed, "Unsupported HTTP method"); // Invoke the handler try { // Attempt to handle the request co_return co_await it3->second.handler(request_data{request, *target, pool}); } catch (const mysql::error_with_diagnostics& err) { // A Boost.MySQL error. This will happen if you don't have connectivity // to your database, your schema is incorrect or your credentials are invalid. // Log the error, including diagnostics log_mysql_error(err.code(), err.get_diagnostics()); // Never disclose error info to a potential attacker co_return internal_server_error(); } catch (const std::exception& err) { // Another kind of error. This indicates a programming error or a severe // server condition (e.g. out of memory). Same procedure as above. { auto guard = orders::lock_cerr(); std::cerr << "Uncaught exception: " << err.what() << std::endl; } co_return internal_server_error(); } } //] #endif ================================================ FILE: example/3_advanced/http_server_cpp20/handle_request.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP20_HANDLE_REQUEST_HPP #define BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP20_HANDLE_REQUEST_HPP //[example_http_server_cpp20_handle_request_hpp // // File: handle_request.hpp // #include #include #include #include namespace orders { // Handles an individual HTTP request, producing a response. // The caller of this function should use response::version, // response::keep_alive and response::prepare_payload to adjust the response. boost::asio::awaitable> handle_request( const boost::beast::http::request& request, boost::mysql::connection_pool& pool ); } // namespace orders //] #endif ================================================ FILE: example/3_advanced/http_server_cpp20/main.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) && BOOST_PFR_CORE_NAME_ENABLED //[example_http_server_cpp20_main_cpp /** * Implements a HTTP REST API using Boost.MySQL and Boost.Beast. * The API models a simplified order management system for an online store. * Using the API, users can query the store's product catalog, create and * edit orders, and check them out for payment. * * The API defines the following endpoints: * * GET /products?search={s} Returns a list of products * GET /orders Returns all orders * GET /orders?id={} Returns a single order * POST /orders Creates a new order * POST /orders/items Adds a new order item to an existing order * DELETE /orders/items?id={} Deletes an order item * POST /orders/checkout?id={} Checks out an order * POST /orders/complete?id={} Completes an order * * Each order can have any number of order items. An order item * represents an individual product that has been added to an order. * Orders are created empty, in a 'draft' state. Items can then be * added and removed from the order. After adding the desired items, * orders can be checked out for payment. A third-party service, like Stripe, * would be used to collect the payment. For simplicity, we've left this part * out of the example. Once checked out, an order is no longer editable. * Finally, after successful payment, order are transitioned to the * 'complete' status. * * The server uses C++20 coroutines and is multi-threaded. * It also requires linking to Boost::json and Boost::url. * The database schema is defined in db_setup.sql, in the same directory as this file. * You need to source this file before running the example. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "server.hpp" using namespace orders; namespace mysql = boost::mysql; namespace asio = boost::asio; // The number of threads to use static constexpr std::size_t num_threads = 5; int main_impl(int argc, char* argv[]) { // Check command line arguments. if (argc != 5) { std::cerr << "Usage: " << argv[0] << " \n"; return EXIT_FAILURE; } // Application config const char* mysql_username = argv[1]; const char* mysql_password = argv[2]; const char* mysql_hostname = argv[3]; auto port = static_cast(std::stoi(argv[4])); // An event loop, where the application will run. // We will use the main thread to run the pool, too, so we use // one thread less than configured asio::thread_pool th_pool(num_threads - 1); // Create a connection pool mysql::connection_pool pool( // Use the thread pool as execution context th_pool, // Pool configuration mysql::pool_params{ // Connect using TCP, to the given hostname and using the default port .server_address = mysql::host_and_port{mysql_hostname}, // Authenticate using the given username .username = mysql_username, // Password for the above username .password = mysql_password, // Database to use when connecting .database = "boost_mysql_orders", // We're using multi-queries .multi_queries = true, // Using thread_safe will make the pool thread-safe by internally // creating and using a strand. // This allows us to share the pool between sessions, which may run // concurrently, on different threads. .thread_safe = true, } ); // Launch the MySQL pool pool.async_run(asio::detached); // A signal_set allows us to intercept SIGINT and SIGTERM and // exit gracefully asio::signal_set signals{th_pool.get_executor(), SIGINT, SIGTERM}; // Capture SIGINT and SIGTERM to perform a clean shutdown signals.async_wait([&th_pool](boost::system::error_code, int) { // Stop the execution context. This will cause main to exit th_pool.stop(); }); // Start listening for HTTP connections. This will run until the context is stopped asio::co_spawn( // Use the thread pool to run the listener coroutine th_pool, // The coroutine to run [&pool, port] { return run_server(pool, port); }, // If an exception is thrown in the listener coroutine, propagate it [](std::exception_ptr exc) { if (exc) std::rethrow_exception(exc); } ); // Attach the current thread to the thread pool. This will block // until stop() is called th_pool.attach(); // Wait until all threads have exited th_pool.join(); std::cout << "Server exiting" << std::endl; // (If we get here, it means we got a SIGINT or SIGTERM) return EXIT_SUCCESS; } int main(int argc, char** argv) { try { main_impl(argc, argv); } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; } } //] #else #include int main() { std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example" << std::endl; } #endif ================================================ FILE: example/3_advanced/http_server_cpp20/repository.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) && BOOST_PFR_CORE_NAME_ENABLED //[example_http_server_cpp20_repository_cpp // // File: repository.cpp // // See the db_setup.sql file in this folder for the table definitions #include #include #include #include #include #include #include #include #include #include "error.hpp" #include "repository.hpp" #include "types.hpp" namespace mysql = boost::mysql; namespace asio = boost::asio; using namespace orders; asio::awaitable> db_repository::get_products(std::string_view search) { // Get a connection from the pool auto conn = co_await pool_.async_get_connection(); // Get the products using the MySQL built-in full-text search feature. // Look for the query string in the short_name and descr fields. // Parse the query results into product struct instances mysql::static_results res; co_await conn->async_execute( mysql::with_params( "SELECT id, short_name, descr, price FROM products " "WHERE MATCH(short_name, descr) AGAINST({}) " "LIMIT 10", search ), res ); // By default, connections are reset after they are returned to the pool // (by using any_connection::async_reset_connection). This will reset any // session state we changed while we were using the connection // (e.g. it will deallocate any statements we prepared). // We did nothing to mutate session state, so we can tell the pool to skip // this step, providing a minor performance gain. // We use pooled_connection::return_without_reset to do this. // If an exception was raised, the connection would be reset, for safety. conn.return_without_reset(); // Return the result co_return std::vector{res.rows().begin(), res.rows().end()}; } asio::awaitable> db_repository::get_orders() { // Get a connection from the pool auto conn = co_await pool_.async_get_connection(); // Get all the orders. // Parse the result into order structs. mysql::static_results res; co_await conn->async_execute("SELECT id, status FROM orders", res); // We didn't mutate session state, so we can skip resetting the connection conn.return_without_reset(); // Return the result co_return std::vector{res.rows().begin(), res.rows().end()}; } asio::awaitable> db_repository::get_order_by_id(std::int64_t id) { // Get a connection from the pool auto conn = co_await pool_.async_get_connection(); // Get a single order and all its associated items. // The transaction ensures atomicity between the two SELECTs. // We issued 4 queries, so we get 4 resultsets back. // Ignore the 1st and 4th, and parse the other two into order and order_item structs mysql::static_results, order, order_item, std::tuple<>> result; co_await conn->async_execute( mysql::with_params( "START TRANSACTION READ ONLY;" "SELECT id, status FROM orders WHERE id = {0};" "SELECT id, product_id, quantity FROM order_items WHERE order_id = {0};" "COMMIT", id ), result ); // We didn't mutate session state conn.return_without_reset(); // result.rows returns the rows for the N-th resultset, as a span auto orders = result.rows<1>(); auto order_items = result.rows<2>(); // Did we find the order we're looking for? if (orders.empty()) co_return orders::errc::not_found; const order& ord = orders[0]; // If we did, compose the result co_return order_with_items{ ord.id, ord.status, {order_items.begin(), order_items.end()} }; } asio::awaitable db_repository::create_order() { // Get a connection from the pool auto conn = co_await pool_.async_get_connection(); // Create the new order. // Orders are created empty, with all fields defaulted. // MySQL does not have an INSERT ... RETURNING statement, so we use // a transaction with an INSERT and a SELECT to create the order // and retrieve it atomically. // This yields 4 resultsets, one per SQL statement. // Ignore all except the SELECT, and parse it into an order struct. mysql::static_results, std::tuple<>, order, std::tuple<>> result; co_await conn->async_execute( "START TRANSACTION;" "INSERT INTO orders () VALUES ();" "SELECT id, status FROM orders WHERE id = LAST_INSERT_ID();" "COMMIT", result ); // We didn't mutate session state conn.return_without_reset(); // This must always yield one row. Return it. const order& ord = result.rows<2>().front(); co_return order_with_items{ ord.id, ord.status, {} // A newly created order never has items }; } asio::awaitable> db_repository::add_order_item( std::int64_t order_id, std::int64_t product_id, std::int64_t quantity ) { // Get a connection from the pool auto conn = co_await pool_.async_get_connection(); // Retrieve the order and the product. // SELECT ... FOR UPDATE places a lock on the retrieved rows, // so they're not modified by other transactions while we use them. // If you're targeting MySQL 8.0+, you can also use SELECT ... FOR SHARE. // For the product, we only need to check that it does exist, // so we get its ID and parse the returned rows into a std::tuple. mysql::static_results, order, std::tuple> result1; co_await conn->async_execute( mysql::with_params( "START TRANSACTION;" "SELECT id, status FROM orders WHERE id = {} FOR UPDATE;" "SELECT id FROM products WHERE id = {} FOR UPDATE", order_id, product_id ), result1 ); // Check that the order exists if (result1.rows<1>().empty()) { // Not found. We did mutate session state by opening a transaction, // so we can't use return_without_reset co_return orders::errc::not_found; } const order& ord = result1.rows<1>().front(); // Verify that the order is editable. // Using SELECT ... FOR UPDATE prevents race conditions with this check. if (ord.status != status_draft) { co_return orders::errc::order_invalid_status; } // Check that the product exists if (result1.rows<2>().empty()) { co_return orders::errc::product_not_found; } // Insert the new item and retrieve all the items associated to this order mysql::static_results, order_item, std::tuple<>> result2; co_await conn->async_execute( mysql::with_params( "INSERT INTO order_items (order_id, product_id, quantity) VALUES ({0}, {1}, {2});" "SELECT id, product_id, quantity FROM order_items WHERE order_id = {0};" "COMMIT", order_id, product_id, quantity ), result2 ); // If everything went well, we didn't mutate session state conn.return_without_reset(); // Compose the return value co_return order_with_items{ ord.id, ord.status, {result2.rows<1>().begin(), result2.rows<1>().end()} }; } asio::awaitable> db_repository::remove_order_item(std::int64_t item_id ) { // Get a connection from the pool auto conn = co_await pool_.async_get_connection(); // Retrieve the order. // SELECT ... FOR UPDATE places a lock on the order and the item, // so they're not modified by other transactions while we use them. mysql::static_results, order> result1; co_await conn->async_execute( mysql::with_params( "START TRANSACTION;" "SELECT ord.id AS id, status FROM orders ord" " JOIN order_items it ON (ord.id = it.order_id)" " WHERE it.id = {} FOR UPDATE", item_id ), result1 ); // Check that the item exists if (result1.rows<1>().empty()) { // Not found. We did mutate session state by opening a transaction, // so we can't use return_without_reset co_return orders::errc::not_found; } const order& ord = result1.rows<1>().front(); // Check that the order is editable if (ord.status != orders::status_draft) { co_return orders::errc::order_invalid_status; } // Perform the deletion and retrieve the items mysql::static_results, order_item, std::tuple<>> result2; co_await conn->async_execute( mysql::with_params( "DELETE FROM order_items WHERE id = {};" "SELECT id, product_id, quantity FROM order_items WHERE order_id = {};" "COMMIT", item_id, ord.id ), result2 ); // If everything went well, we didn't mutate session state conn.return_without_reset(); // Compose the return value co_return order_with_items{ ord.id, ord.status, {result2.rows<1>().begin(), result2.rows<1>().end()} }; } // Helper function to implement checkout_order and complete_order static asio::awaitable> change_order_status( mysql::connection_pool& pool, std::int64_t order_id, std::string_view original_status, // The status that the order should have std::string_view target_status // The status to transition the order to ) { // Get a connection from the pool auto conn = co_await pool.async_get_connection(); // Retrieve the order and lock it. // FOR UPDATE places an exclusive lock on the order, // preventing other concurrent transactions (including the ones // related to adding/removing items) from changing the order mysql::static_results, std::tuple> result1; co_await conn->async_execute( mysql::with_params( "START TRANSACTION;" "SELECT status FROM orders WHERE id = {} FOR UPDATE;", order_id ), result1 ); // Check that the order exists if (result1.rows<1>().empty()) { co_return orders::errc::not_found; } // Check that the order is in the expected status if (std::get<0>(result1.rows<1>().front()) != original_status) { co_return orders::errc::order_invalid_status; } // Update the order and retrieve the order details mysql::static_results, order_item, std::tuple<>> result2; co_await conn->async_execute( mysql::with_params( "UPDATE orders SET status = {1} WHERE id = {0};" "SELECT id, product_id, quantity FROM order_items WHERE order_id = {0};" "COMMIT", order_id, target_status ), result2 ); // If everything went well, we didn't mutate session state conn.return_without_reset(); // Compose the return value co_return order_with_items{ order_id, std::string(target_status), {result2.rows<1>().begin(), result2.rows<1>().end()} }; } asio::awaitable> db_repository::checkout_order(std::int64_t id) { return change_order_status(pool_, id, status_draft, status_pending_payment); } asio::awaitable> db_repository::complete_order(std::int64_t id) { return change_order_status(pool_, id, status_pending_payment, status_complete); } //] #endif ================================================ FILE: example/3_advanced/http_server_cpp20/repository.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP20_REPOSITORY_HPP #define BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP20_REPOSITORY_HPP //[example_http_server_cpp20_repository_hpp // // File: repository.hpp // #include #include #include #include #include #include #include "types.hpp" namespace orders { // Encapsulates database logic. // If the database is unavailable, these functions throw. // Additionally, functions that may fail depending on the supplied input // return boost::system::result, avoiding exceptions in common cases. class db_repository { boost::mysql::connection_pool& pool_; public: // Constructor (this is a cheap-to-construct object) db_repository(boost::mysql::connection_pool& pool) noexcept : pool_(pool) {} // Retrieves products using a full-text search boost::asio::awaitable> get_products(std::string_view search); // Retrieves all the orders in the database boost::asio::awaitable> get_orders(); // Retrieves an order by ID. // Returns an error if the ID doesn't match any order. boost::asio::awaitable> get_order_by_id(std::int64_t id); // Creates an empty order. Returns the created order. boost::asio::awaitable create_order(); // Adds an item to an order. Retrieves the updated order. // Returns an error if the ID doesn't match any order, the order // is not editable, or the product_id doesn't match any product boost::asio::awaitable> add_order_item( std::int64_t order_id, std::int64_t product_id, std::int64_t quantity ); // Removes an item from an order. Retrieves the updated order. // Returns an error if the ID doesn't match any order item // or the order is not editable. boost::asio::awaitable> remove_order_item(std::int64_t item_id); // Checks an order out, transitioning it to the pending_payment status. // Returns an error if the ID doesn't match any order // or the order is not editable. boost::asio::awaitable> checkout_order(std::int64_t id); // Completes an order, transitioning it to the complete status. // Returns an error if the ID doesn't match any order // or the order is not checked out. boost::asio::awaitable> complete_order(std::int64_t id); }; } // namespace orders //] #endif ================================================ FILE: example/3_advanced/http_server_cpp20/server.cpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) && BOOST_PFR_CORE_NAME_ENABLED //[example_http_server_cpp20_server_cpp // // File: server.cpp // // This file contains all the boilerplate code to implement a HTTP // server. Functions here end up invoking handle_request. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "error.hpp" #include "handle_request.hpp" #include "server.hpp" namespace asio = boost::asio; namespace http = boost::beast::http; namespace mysql = boost::mysql; namespace { // Runs a single HTTP session until the client closes the connection. // This coroutine will be spawned on a strand, to prevent data races. asio::awaitable run_http_session(asio::ip::tcp::socket sock, mysql::connection_pool& pool) { using namespace std::chrono_literals; boost::system::error_code ec; // A buffer to read incoming client requests boost::beast::flat_buffer buff; // A timer, to use with asio::cancel_after to implement timeouts. // Re-using the same timer multiple times with cancel_after // is more efficient than using raw cancel_after, // since the timer doesn't need to be re-created for every operation. asio::steady_timer timer(co_await asio::this_coro::executor); // A HTTP session might involve more than one message if // keep-alive semantics are used. Loop until the connection closes. while (true) { // Construct a new parser for each message http::request_parser parser; // Apply a reasonable limit to the allowed size // of the body in bytes to prevent abuse. parser.body_limit(10000); // Read a request. redirect_error prevents exceptions from being thrown // on error. We use cancel_after to set a timeout for the overall read operation. co_await http::async_read( sock, buff, parser.get(), asio::cancel_after(timer, 60s, asio::redirect_error(ec)) ); if (ec) { if (ec == http::error::end_of_stream) { // This means they closed the connection sock.shutdown(asio::ip::tcp::socket::shutdown_send, ec); } else { // An unknown error happened orders::log_error("Error reading HTTP request: ", ec); } co_return; } const auto& request = parser.get(); // Process the request to generate a response. // This invokes the business logic, which will need to access MySQL data. // Apply a timeout to the overall request handling process. auto response = co_await asio::co_spawn( // Use the same executor as this coroutine (it will be a strand) co_await asio::this_coro::executor, // The logic to invoke [&] { return orders::handle_request(request, pool); }, // Completion token. Returns an object that can be co_await'ed asio::cancel_after(timer, 30s) ); // Adjust the response, setting fields common to all responses bool keep_alive = response.keep_alive(); response.version(request.version()); response.keep_alive(keep_alive); response.prepare_payload(); // Send the response co_await http::async_write(sock, response, asio::cancel_after(timer, 60s, asio::redirect_error(ec))); if (ec) { orders::log_error("Error writing HTTP response: ", ec); co_return; } // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. if (!keep_alive) { sock.shutdown(asio::ip::tcp::socket::shutdown_send, ec); co_return; } } } } // namespace asio::awaitable orders::run_server(mysql::connection_pool& pool, unsigned short port) { // An object that allows us to accept incoming TCP connections asio::ip::tcp::acceptor acc(co_await asio::this_coro::executor); // The endpoint where the server will listen. Edit this if you want to // change the address or port we bind to. asio::ip::tcp::endpoint listening_endpoint(asio::ip::make_address("0.0.0.0"), port); // Open the acceptor acc.open(listening_endpoint.protocol()); // Allow address reuse acc.set_option(asio::socket_base::reuse_address(true)); // Bind to the server address acc.bind(listening_endpoint); // Start listening for connections acc.listen(asio::socket_base::max_listen_connections); std::cout << "Server listening at " << acc.local_endpoint() << std::endl; // Start the acceptor loop while (true) { // Accept a new connection asio::ip::tcp::socket sock = co_await acc.async_accept(); // Function implementing our session logic. // Takes ownership of the socket. // Having this as a named variable workarounds a gcc bug // (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107288) auto session_logic = [&pool, socket = std::move(sock)]() mutable { return run_http_session(std::move(socket), pool); }; // Launch a new session for this connection. Each session gets its // own coroutine, so we can get back to listening for new connections. asio::co_spawn( // Every session gets its own strand. This prevents data races. asio::make_strand(co_await asio::this_coro::executor), // The actual coroutine std::move(session_logic), // Callback to run when the coroutine finishes [](std::exception_ptr ptr) { if (ptr) { // For extra safety, log the exception but don't propagate it. // If we failed to anticipate an error condition that ends up raising an exception, // terminate only the affected session, instead of crashing the server. try { std::rethrow_exception(ptr); } catch (const std::exception& exc) { auto guard = lock_cerr(); std::cerr << "Uncaught error in a session: " << exc.what() << std::endl; } } } ); } } //] #endif ================================================ FILE: example/3_advanced/http_server_cpp20/server.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP20_SERVER_HPP #define BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP20_SERVER_HPP //[example_http_server_cpp20_server_hpp // // File: server.hpp // #include #include namespace orders { // Launches a HTTP server that will listen on 0.0.0.0:port. // If the server fails to launch (e.g. because the port is already in use), // throws an exception. The server runs until the underlying execution // context is stopped. boost::asio::awaitable run_server(boost::mysql::connection_pool& pool, unsigned short port); } // namespace orders //] #endif ================================================ FILE: example/3_advanced/http_server_cpp20/types.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP20_TYPES_HPP #define BOOST_MYSQL_EXAMPLE_3_ADVANCED_HTTP_SERVER_CPP20_TYPES_HPP //[example_http_server_cpp20_types_hpp // // File: types.hpp // // Contains type definitions used in the REST API and database code. // We use Boost.Describe (BOOST_DESCRIBE_STRUCT) to add reflection // capabilities to our types. This allows using Boost.MySQL // static interface (i.e. static_results) to parse query results, // and Boost.JSON automatic serialization/deserialization. #include #include #include #include #include #include namespace orders { // A product object, as defined in the database and in the GET /products endpoint struct product { // The unique database ID of the object. std::int64_t id; // The product's display name std::string short_name; // The product's description std::optional descr; // The product's price, in dollar cents std::int64_t price; }; BOOST_DESCRIBE_STRUCT(product, (), (id, short_name, descr, price)) // An order object, as defined in the database and in some REST endpoints. // This object does not include the items associated to the order. struct order { // The unique database ID of the object. std::int64_t id; // The order status. One of "draft", "pending_payment" or "complete". std::string status; }; BOOST_DESCRIBE_STRUCT(order, (), (id, status)) // Constants for the order::status member inline constexpr std::string_view status_draft = "draft"; inline constexpr std::string_view status_pending_payment = "pending_payment"; inline constexpr std::string_view status_complete = "complete"; // An order item object, as defined in the database and in some REST endpoints. // Does not include the order_id database field. struct order_item { // The unique database ID of the object. std::int64_t id; // The ID of the product that this order item represents std::int64_t product_id; // The number of units of the product that this item represents. // For instance, if product_id=2 and quantity=3, // the user wants to buy 3 units of the product with ID 2. std::int64_t quantity; }; BOOST_DESCRIBE_STRUCT(order_item, (), (id, product_id, quantity)) // An order object, with its associated order items. // Used in some REST endpoints. struct order_with_items { // The unique database ID of the object. std::int64_t id; // The order status. One of "draft", "pending_payment" or "complete". std::string status; // The items associated to this order. std::vector items; }; BOOST_DESCRIBE_STRUCT(order_with_items, (), (id, status, items)) // REST request for POST /orders/items struct add_order_item_request { // Identifies the order to which the item should be added. std::int64_t order_id; // Identifies the product that should be added to the order. std::int64_t product_id; // The number of units of the above product that should be added to the order. std::int64_t quantity; }; BOOST_DESCRIBE_STRUCT(add_order_item_request, (), (order_id, product_id, quantity)) } // namespace orders //] #endif ================================================ FILE: example/CMakeLists.txt ================================================ # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # # Note: examples count as integration tests. This is only processed # when BOOST_MYSQL_INTEGRATION_TESTS is on # Get the MySQL hostname to use for examples if(DEFINED ENV{BOOST_MYSQL_SERVER_HOST}) set(SERVER_HOST $ENV{BOOST_MYSQL_SERVER_HOST}) else() set(SERVER_HOST "127.0.0.1") endif() add_library(boost_mysql_examples_common INTERFACE) target_link_libraries( boost_mysql_examples_common INTERFACE boost_mysql_compiled ) function(add_example EXAMPLE_NAME) # Parse the arguments set(ONE_VALUE_ARGS PYTHON_RUNNER) set(MULTI_VALUE_ARGS SOURCES LIBS ARGS) cmake_parse_arguments(ADD_EXAMPLE "" "${ONE_VALUE_ARGS}" "${MULTI_VALUE_ARGS}" ${ARGN}) # Create the target set(TARGET_NAME "boost_mysql_example_${EXAMPLE_NAME}") add_executable(${TARGET_NAME} ${ADD_EXAMPLE_SOURCES}) target_link_libraries(${TARGET_NAME} PRIVATE boost_mysql_examples_common) boost_mysql_test_target_settings(${TARGET_NAME}) target_link_libraries(${TARGET_NAME} PRIVATE ${ADD_EXAMPLE_LIBS}) # Add it as a test if (ADD_EXAMPLE_PYTHON_RUNNER) add_test( NAME ${TARGET_NAME} COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/private/${ADD_EXAMPLE_PYTHON_RUNNER} $ ${ADD_EXAMPLE_ARGS} ) else() add_test( NAME ${TARGET_NAME} COMMAND ${TARGET_NAME} ${ADD_EXAMPLE_ARGS} ) endif() endfunction() function(add_tutorial EXAMPLE_NAME EXAMPLE_PATH) add_example(${EXAMPLE_NAME} SOURCES "1_tutorial/${EXAMPLE_PATH}" ${ARGN}) endfunction() function(add_simple_example EXAMPLE_NAME) add_example(${EXAMPLE_NAME} SOURCES "2_simple/${EXAMPLE_NAME}.cpp" ${ARGN}) endfunction() set(REGULAR_ARGS example_user example_password ${SERVER_HOST}) # Tutorials add_tutorial(tutorial_sync 1_sync.cpp ARGS ${REGULAR_ARGS}) add_tutorial(tutorial_async 2_async.cpp ARGS ${REGULAR_ARGS}) add_tutorial(tutorial_with_params 3_with_params.cpp ARGS ${REGULAR_ARGS} 1) add_tutorial(tutorial_static_interface 4_static_interface.cpp ARGS ${REGULAR_ARGS} 1 LIBS Boost::pfr) add_tutorial(tutorial_updates_transactions 5_updates_transactions.cpp ARGS ${REGULAR_ARGS} 1 "John" LIBS Boost::pfr) add_tutorial(tutorial_connection_pool 6_connection_pool.cpp ARGS ${SERVER_HOST} LIBS Boost::pfr PYTHON_RUNNER run_tutorial_connection_pool.py) add_tutorial(tutorial_error_handling 7_error_handling.cpp ARGS ${SERVER_HOST} --test-errors LIBS Boost::pfr PYTHON_RUNNER run_tutorial_connection_pool.py) # Simple add_simple_example(inserts ARGS ${REGULAR_ARGS} "John" "Doe" "HGS") add_simple_example(deletes ARGS ${REGULAR_ARGS} 20) add_simple_example(callbacks ARGS ${REGULAR_ARGS}) add_simple_example(coroutines_cpp11 ARGS ${REGULAR_ARGS} LIBS Boost::context) add_simple_example(batch_inserts ARGS ${SERVER_HOST} PYTHON_RUNNER run_batch_inserts.py LIBS Boost::json) add_simple_example(batch_inserts_generic ARGS ${SERVER_HOST} PYTHON_RUNNER run_batch_inserts.py LIBS Boost::json) add_simple_example(patch_updates ARGS ${SERVER_HOST} PYTHON_RUNNER run_patch_updates.py) add_simple_example(dynamic_filters ARGS ${SERVER_HOST} PYTHON_RUNNER run_dynamic_filters.py) add_simple_example(disable_tls ARGS ${REGULAR_ARGS}) add_simple_example(tls_certificate_verification ARGS ${REGULAR_ARGS}) add_simple_example(metadata ARGS ${REGULAR_ARGS}) add_simple_example(prepared_statements ARGS ${REGULAR_ARGS} "HGS") add_simple_example(pipeline ARGS ${REGULAR_ARGS} "HGS") add_simple_example(multi_function ARGS ${REGULAR_ARGS}) add_simple_example(source_script ARGS ${REGULAR_ARGS} ${CMAKE_CURRENT_SOURCE_DIR}/private/test_script.sql) # UNIX sockets. Don't run the example on Windows machines if (NOT WIN32) add_simple_example(unix_socket ARGS example_user example_password) endif() # Advanced add_example( http_server_cpp14_coroutines SOURCES 3_advanced/http_server_cpp14_coroutines/repository.cpp 3_advanced/http_server_cpp14_coroutines/handle_request.cpp 3_advanced/http_server_cpp14_coroutines/server.cpp 3_advanced/http_server_cpp14_coroutines/main.cpp LIBS Boost::context Boost::json Boost::url Boost::beast PYTHON_RUNNER run_notes.py ARGS ${SERVER_HOST} ) add_example( http_server_cpp20 SOURCES 3_advanced/http_server_cpp20/error.cpp 3_advanced/http_server_cpp20/repository.cpp 3_advanced/http_server_cpp20/handle_request.cpp 3_advanced/http_server_cpp20/server.cpp 3_advanced/http_server_cpp20/main.cpp LIBS Boost::json Boost::url Boost::beast Boost::pfr PYTHON_RUNNER run_orders.py ARGS ${SERVER_HOST} ) ================================================ FILE: example/Jamfile ================================================ # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # import os ; import sequence ; path-constant this_dir : . ; # The hostname to use for examples local hostname = [ os.environ BOOST_MYSQL_SERVER_HOST ] ; if $(hostname) = "" { hostname = "127.0.0.1" ; } # Builds and run an example rule run_example ( example_name : sources * : args * : python_runner ? : requirements * ) { # If we're using a Python runner, don't use Valgrind local valgrind_target = /boost/mysql/test//launch_with_valgrind ; local launcher = ; if python_runner { valgrind_target = ; launcher = "python $(this_dir)/private/$(python_runner)" ; } # Join the supplied command-line arguments local arg_str = [ sequence.join $(args) : " " ] ; run /boost/mysql/test//boost_mysql_compiled $(valgrind_target) $(sources) : requirements $(arg_str) $(launcher) $(requirements) : target-name $(example_name) ; } local regular_args = example_user example_password $(hostname) ; # Tutorials run_example tutorial_sync : 1_tutorial/1_sync.cpp : $(regular_args) ; run_example tutorial_async : 1_tutorial/2_async.cpp : $(regular_args) ; run_example tutorial_with_params : 1_tutorial/3_with_params.cpp : $(regular_args) 1 ; run_example tutorial_static_interface : 1_tutorial/4_static_interface.cpp : $(regular_args) 1 ; run_example tutorial_updates_transactions : 1_tutorial/5_updates_transactions.cpp : $(regular_args) 1 "John" ; run_example tutorial_connection_pool : 1_tutorial/6_connection_pool.cpp : $(hostname) : run_tutorial_connection_pool.py ; run_example tutorial_error_handling : 1_tutorial/7_error_handling.cpp : $(hostname) --test-errors : run_tutorial_connection_pool.py : ; # Simple examples run_example inserts : 2_simple/inserts.cpp : $(regular_args) "John" "Doe" "HGS" 50000 ; run_example deletes : 2_simple/deletes.cpp : $(regular_args) 20 ; run_example callbacks : 2_simple/callbacks.cpp : $(regular_args) ; run_example coroutines_cpp11 : 2_simple/coroutines_cpp11.cpp /boost/mysql/test//boost_context_lib : $(regular_args) : : # TODO: remove when https://github.com/boostorg/context/issues/284 is fixed off ; run_example batch_inserts : 2_simple/batch_inserts.cpp /boost/mysql/test//boost_json_lib : $(hostname) : run_batch_inserts.py ; run_example batch_inserts_generic : 2_simple/batch_inserts_generic.cpp /boost/mysql/test//boost_json_lib : $(hostname) : run_batch_inserts.py ; run_example patch_updates : 2_simple/patch_updates.cpp : $(hostname) : run_patch_updates.py ; run_example dynamic_filters : 2_simple/dynamic_filters.cpp : $(hostname) : run_dynamic_filters.py ; run_example disable_tls : 2_simple/disable_tls.cpp : $(regular_args) ; run_example tls_certificate_verification : 2_simple/tls_certificate_verification.cpp : $(regular_args) ; run_example metadata : 2_simple/metadata.cpp : $(regular_args) ; run_example prepared_statements : 2_simple/prepared_statements.cpp : $(regular_args) "HGS" ; run_example multi_function : 2_simple/multi_function.cpp : $(regular_args) ; run_example pipeline : 2_simple/pipeline.cpp : $(regular_args) "HGS" ; run_example source_script : 2_simple/source_script.cpp : $(regular_args) $(this_dir)/private/test_script.sql ; run_example unix_socket : 2_simple/unix_socket.cpp : example_user example_password : : windows:no ; # Advanced run_example http_server_cpp14_coroutines : 3_advanced/http_server_cpp14_coroutines/main.cpp 3_advanced/http_server_cpp14_coroutines/repository.cpp 3_advanced/http_server_cpp14_coroutines/handle_request.cpp 3_advanced/http_server_cpp14_coroutines/server.cpp /boost/mysql/test//boost_context_lib /boost/mysql/test//boost_json_lib /boost/url//boost_url /boost/mysql/test//boost_beast_lib : $(hostname) : run_notes.py : # MSVC 14.1 fails with an internal compiler error while building server.cpp for this config msvc-14.1,32,17,release:no # Uses heavily Boost.Context coroutines, which aren't fully supported by asan norecover:no enable:no # TODO: remove when https://github.com/boostorg/context/issues/284 is fixed off ; run_example http_server_cpp20 : 3_advanced/http_server_cpp20/main.cpp 3_advanced/http_server_cpp20/repository.cpp 3_advanced/http_server_cpp20/handle_request.cpp 3_advanced/http_server_cpp20/server.cpp 3_advanced/http_server_cpp20/error.cpp /boost/mysql/test//boost_json_lib /boost/url//boost_url /boost/mysql/test//boost_beast_lib : $(hostname) : run_orders.py ; ================================================ FILE: example/db_setup.sql ================================================ -- -- Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) -- -- Distributed under the Boost Software License, Version 1.0. (See accompanying -- file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -- -- Connection system variables SET NAMES utf8; -- Database DROP DATABASE IF EXISTS boost_mysql_examples; CREATE DATABASE boost_mysql_examples; USE boost_mysql_examples; -- Tables CREATE TABLE company( id CHAR(10) NOT NULL PRIMARY KEY, name VARCHAR(100) NOT NULL, tax_id VARCHAR(50) NOT NULL ); CREATE TABLE employee( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, salary INT UNSIGNED, company_id CHAR(10) NOT NULL, FOREIGN KEY (company_id) REFERENCES company(id) ); CREATE TABLE audit_log( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, t TIMESTAMP DEFAULT CURRENT_TIMESTAMP, msg TEXT ); CREATE TABLE notes( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, title TEXT NOT NULL, content TEXT NOT NULL ); INSERT INTO company (name, id, tax_id) VALUES ("Award Winning Company, Inc.", "AWC", "IE1234567V"), ("Sector Global Leader Plc", "SGL", "IE1234568V"), ("High Growth Startup, Ltd", "HGS", "IE1234569V") ; INSERT INTO employee (first_name, last_name, salary, company_id) VALUES ("Efficient", "Developer", 30000, "AWC"), ("Lazy", "Manager", 80000, "AWC"), ("Good", "Team Player", 35000, "HGS"), ("Enormous", "Slacker", 45000, "SGL"), ("Coffee", "Drinker", 30000, "HGS"), ("Underpaid", "Intern", 15000, "AWC") ; -- Stored procedures DELIMITER // CREATE PROCEDURE get_employees(IN pin_company_id CHAR(10)) BEGIN START TRANSACTION READ ONLY; SELECT id, name, tax_id FROM company WHERE id = pin_company_id; SELECT first_name, last_name, salary FROM employee WHERE company_id = pin_company_id; COMMIT; END// CREATE PROCEDURE create_employee( IN pin_company_id CHAR(10), IN pin_first_name VARCHAR(100), IN pin_last_name VARCHAR(100), OUT pout_employee_id INT ) BEGIN START TRANSACTION; INSERT INTO employee (company_id, first_name, last_name) VALUES (pin_company_id, pin_first_name, pin_last_name); SET pout_employee_id = LAST_INSERT_ID(); INSERT INTO audit_log (msg) VALUES ('Created new employee...'); COMMIT; END// DELIMITER ; -- User DROP USER IF EXISTS 'example_user'@'%'; CREATE USER 'example_user'@'%' IDENTIFIED BY 'example_password'; GRANT ALL PRIVILEGES ON boost_mysql_examples.* TO 'example_user'@'%'; FLUSH PRIVILEGES; ================================================ FILE: example/private/employees_multiple.json ================================================ [ { "first_name": "Alice", "last_name": "Davidson", "salary": 40000, "company_id": "HGS" }, { "first_name": "Bob", "last_name": "Henrik", "salary": 35000, "company_id": "HGS" }, { "first_name": "Darth", "last_name": "Smith", "salary": 99999, "company_id": "AWC" } ] ================================================ FILE: example/private/employees_single.json ================================================ [ { "first_name": "Lonely", "last_name": "Developer", "salary": 90000, "company_id": "AWC" } ] ================================================ FILE: example/private/launch_server.py ================================================ #!/usr/bin/python3 # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # # Utility to run the example TCP and HTTP servers import os import re from subprocess import Popen, PIPE, STDOUT from contextlib import contextmanager _is_win = os.name == 'nt' # Returns the port the server is listening at def _parse_server_start_line(line: str) -> int: m = re.match(r'Server listening at 0\.0\.0\.0:([0-9]+)', line) if m is None: raise RuntimeError('Unexpected server start line') return int(m.group(1)) @contextmanager def launch_server(exe: str, host: str, username: str, password: str): # Launch server and let it choose a free port for us. # This prevents port clashes during b2 parallel test runs server = Popen([exe, username, password, host, '0'], stdout=PIPE, stderr=STDOUT) assert server.stdout is not None with server: try: # Wait until the server is ready ready_line = server.stdout.readline().decode() print(ready_line, end='', flush=True) if ready_line.startswith('Sorry'): # C++ standard unsupported, skip the test exit(0) yield _parse_server_start_line(ready_line) finally: print('Terminating server...', flush=True) # In Windows, there is no sane way to cleanly terminate the process. # Sending a Ctrl-C terminates all process attached to the console (including ourselves # and any parent test runner). Running the process in a separate terminal doesn't allow # access to stdout, which is problematic, too. # terminate() sends SIGTERM in Unix, and uses TerminateProcess in Windows server.terminate() # Print any output the process generated print('Server stdout: \n', server.stdout.read().decode(), flush=True) # The return code is only relevant in Unix, as in Windows we used TerminateProcess if not _is_win and server.returncode != 0: raise RuntimeError('Server did not exit cleanly. retcode={}'.format(server.returncode)) ================================================ FILE: example/private/run_batch_inserts.py ================================================ #!/usr/bin/python3 # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # from subprocess import run import argparse from os import path # Helper to run the batch inserts example _this_dir = path.abspath(path.dirname(path.realpath(__file__))) class _Runner: def __init__(self, exe: str, host: str) -> None: self._exe = exe self._host = host def run(self, fname: str) -> None: json_path = path.join(_this_dir, fname) cmdline = [self._exe, 'example_user', 'example_password', self._host, json_path] print(' + ', ' '.join(cmdline)) run(cmdline, check=True) def main(): # Parse command line parser = argparse.ArgumentParser() parser.add_argument('executable') parser.add_argument('host') args = parser.parse_args() # Build a runner runner = _Runner(args.executable, args.host) # Run the example with several combinations runner.run('employees_single.json') runner.run('employees_multiple.json') if __name__ == '__main__': main() ================================================ FILE: example/private/run_dynamic_filters.py ================================================ #!/usr/bin/python3 # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # from subprocess import run import argparse from typing import List # Helper to run the dynamic filters example class _Runner: def __init__(self, exe: str, host: str) -> None: self._exe = exe self._host = host def run(self, opts: List[str]) -> None: cmdline = [self._exe, 'example_user', 'example_password', self._host] + opts print(' + ', ' '.join(cmdline)) run(cmdline, check=True) def main(): # Parse command line parser = argparse.ArgumentParser() parser.add_argument('executable') parser.add_argument('host') args = parser.parse_args() # Build a runner runner = _Runner(args.executable, args.host) # Run the example with several combinations runner.run(['--company-id=HGS']) runner.run(['--company-id=AWC', '--last-name=Alice']) runner.run(['--min-salary=25000', '--first-name=Bob', '--order-by=salary']) runner.run(['--company-id=AWC', '--first-name=Underpaid', '--last-name=Intern', '--min-salary=1', '--order-by=salary']) if __name__ == '__main__': main() ================================================ FILE: example/private/run_notes.py ================================================ #!/usr/bin/python3 # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # import requests import random import argparse import sys from os import path sys.path.append(path.abspath(path.dirname(path.realpath(__file__)))) from launch_server import launch_server def _check_response(res: requests.Response): if res.status_code >= 400: print(res.text) res.raise_for_status() def _random_string() -> str: return bytes(random.getrandbits(8) for _ in range(8)).hex() def _call_endpoints(port: int): url = 'http://127.0.0.1:{}/notes'.format(port) # Create a note note_unique = _random_string() title = 'My note {}'.format(note_unique) content = 'This is a note about {}'.format(note_unique) res = requests.post( url, json={'title': title, 'content': content} ) _check_response(res) note = res.json() note_id = int(note['note']['id']) assert note['note']['title'] == title assert note['note']['content'] == content # Retrieve all notes res = requests.get(url) _check_response(res) all_notes = res.json() assert len([n for n in all_notes['notes'] if n['id'] == note_id]) == 1 # Edit the note note_unique = _random_string() title = 'Edited {}'.format(note_unique) content = 'This is a note an edit on {}'.format(note_unique) res = requests.put( url, params={'id': note_id}, json={'title': title, 'content': content} ) _check_response(res) note = res.json() assert int(note['note']['id']) == note_id assert note['note']['title'] == title assert note['note']['content'] == content # Retrieve the note res = requests.get(url, params={'id': note_id}) _check_response(res) note = res.json() assert int(note['note']['id']) == note_id assert note['note']['title'] == title assert note['note']['content'] == content # Delete the note res = requests.delete(url, params={'id': note_id}) _check_response(res) assert res.json()['deleted'] == True # The note is not there res = requests.get(url, params={'id': note_id}) assert res.status_code == 404 def main(): # Parse command line arguments parser = argparse.ArgumentParser() parser.add_argument('executable') parser.add_argument('host') args = parser.parse_args() # Launch the server with launch_server(args.executable, args.host, 'example_user', 'example_password') as listening_port: # Run the tests _call_endpoints(listening_port) if __name__ == '__main__': main() ================================================ FILE: example/private/run_orders.py ================================================ #!/usr/bin/python3 # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # import requests import argparse from os import path import unittest import copy import sys sys.path.append(path.abspath(path.dirname(path.realpath(__file__)))) from launch_server import launch_server class TestOrders(unittest.TestCase): _port = -1 @property def _base_url(self) -> str: return 'http://127.0.0.1:{}'.format(self._port) @staticmethod def _json_response(res: requests.Response): if res.status_code >= 400: print(res.text) res.raise_for_status() return res.json() def _check_error(self, res: requests.Response, expected_status: int) -> None: self.assertEqual(res.status_code, expected_status) def _request(self, method: str, url: str, **kwargs) -> requests.Response: return requests.request(method=method, url=self._base_url + url, **kwargs) def _request_as_json(self, method: str, url: str, **kwargs): return self._json_response(self._request(method, url, **kwargs)) def _request_error(self, method: str, url: str, expected_status: int, **kwargs): return self._check_error(self._request(method, url, **kwargs), expected_status) # # Success cases # def test_search_products(self) -> None: # Issue the request products = self._request_as_json('get', '/products', params={'search': 'odin'}) # Check self.assertNotEqual(len(products), 0) # At least one product odin = products[0] self.assertIsInstance(odin['id'], int) # We don't know the exact ID self.assertEqual(odin['short_name'], 'A Feast for Odin') self.assertEqual(odin['price'], 6400) self.assertIsInstance(odin['descr'], str) def test_order_lifecycle(self) -> None: # Create an order order = self._request_as_json('post', '/orders') order_id = order['id'] self.assertIsInstance(order_id, int) self.assertEqual(order['status'], 'draft') self.assertEqual(order['items'], []) # Add an item order = self._request_as_json('post', '/orders/items', json={ 'order_id': order_id, 'product_id': 2, 'quantity': 20 }) items = order['items'] self.assertEqual(order['id'], order_id) self.assertEqual(order['status'], 'draft') self.assertEqual(len(order['items']), 1) self.assertIsInstance(items[0]['id'], int) self.assertEqual(items[0]['product_id'], 2) self.assertEqual(items[0]['quantity'], 20) # Checkout expected_order = copy.deepcopy(order) expected_order['status'] = 'pending_payment' order = self._request_as_json('post', '/orders/checkout', params={'id': order_id}) self.assertEqual(order, expected_order) # Complete expected_order = copy.deepcopy(order) expected_order['status'] = 'complete' order = self._request_as_json('post', '/orders/complete', params={'id': order_id}) self.assertEqual(order, expected_order) def test_remove_items(self) -> None: # Create an order order1 = self._request_as_json('post', '/orders') order_id = order1['id'] # Create two items self._request_as_json('post', '/orders/items', json={ 'order_id': order_id, 'product_id': 2, 'quantity': 20 }) order2 = self._request_as_json('post', '/orders/items', json={ 'order_id': order_id, 'product_id': 1, 'quantity': 1 }) order2_items = order2['items'] # Sanity check self.assertEqual(order2['id'], order_id) self.assertEqual(order2['status'], 'draft') self.assertEqual(len(order2_items), 2) product_ids = list(set(item['product_id'] for item in order2_items)) self.assertEqual(product_ids, [1, 2]) # IDs 1 and 2 in any order # Delete one of the items order3 = self._request_as_json('delete', '/orders/items', params={'id': order2_items[0]['id']}) self.assertEqual(order3['id'], order_id) self.assertEqual(order3['status'], 'draft') self.assertEqual(order3['items'], [order2_items[1]]) def test_get_orders(self) -> None: orders = self._request_as_json('get', '/orders') self.assertIsInstance(orders, list) def test_get_single_order(self) -> None: # Create an order and add an item order = self._request_as_json('post', '/orders') order = self._request_as_json('post', '/orders/items', json={ 'order_id': order['id'], 'product_id': 2, 'quantity': 20 }) # Retrieve the order by id order2 = self._request_as_json('get', '/orders', params={'id': order['id']}) self.assertEqual(order2, order) # # Search products errors # def test_search_products_missing_param(self) -> None: self._request_error('get', '/products', expected_status=400) # # Get order errors # def test_get_order_invalid_id(self) -> None: self._request_error('get', '/orders', params={'id': 'abc'}, expected_status=400) def test_get_order_not_found(self) -> None: self._request_error('get', '/orders', params={'id': 0xffffff}, expected_status=404) # # Add order item errors # def test_add_order_item_invalid_content_type(self) -> None: # Create an order order_id = self._request_as_json('post', '/orders')['id'] # Check the error self._request_error('post', '/orders/items', headers={'Content-Type':'text/html'}, json={ 'order_id': order_id, 'product_id': 1, 'quantity': 1 }, expected_status=400) def test_add_order_item_invalid_json(self) -> None: self._request_error('post', '/orders/items', headers={'Content-Type':'application/json'}, data='bad', expected_status=400) def test_add_order_item_invalid_json_keys(self) -> None: self._request_error('post', '/orders/items', json={ 'order_id': '1', 'product_id': 1, 'quantity': 1 }, expected_status=400) def test_add_order_item_order_not_found(self) -> None: self._request_error('post', '/orders/items', json={ 'order_id': 0xffffffff, 'product_id': 1, 'quantity': 1 }, expected_status=404) def test_add_order_item_product_not_found(self) -> None: # Create an order order_id = self._request_as_json('post', '/orders')['id'] # Check the error self._request_error('post', '/orders/items', json={ 'order_id': order_id, 'product_id': 0xffffffff, 'quantity': 1 }, expected_status=422) def test_add_order_item_order_not_editable(self) -> None: # Create an order and check it out order_id = self._request_as_json('post', '/orders')['id'] self._request_as_json('post', '/orders/checkout', params={'id': order_id}) # Check the error self._request_error('post', '/orders/items', json={ 'order_id': order_id, 'product_id': 1, 'quantity': 1 }, expected_status=422) # # Remove order item errors # def test_remove_order_item_missing_id(self) -> None: self._request_error('delete', '/orders/items', expected_status=400) def test_remove_order_item_invalid_id(self) -> None: self._request_error('delete', '/orders/items', params={'id': 'abc'}, expected_status=400) def test_remove_order_item_not_found(self) -> None: self._request_error('delete', '/orders/items', params={'id': 0xffffffff}, expected_status=404) def test_remove_order_item_order_not_editable(self) -> None: # Create an order with an item and check it out order_id = self._request_as_json('post', '/orders')['id'] item_id = self._request_as_json('post', '/orders/items', json={ 'order_id': order_id, 'product_id': 2, 'quantity': 20 })['items'][0]['id'] self._request_as_json('post', '/orders/checkout', params={'id': order_id}) # Check the error self._request_error('delete', '/orders/items', params={'id': item_id}, expected_status=422) # # Checkout order errors # def test_checkout_order_missing_id(self) -> None: self._request_error('post', '/orders/checkout', expected_status=400) def test_checkout_order_invalid_id(self) -> None: self._request_error('post', '/orders/checkout', params={'id': 'abc'}, expected_status=400) def test_checkout_order_not_found(self) -> None: self._request_error('post', '/orders/checkout', params={'id': 0xffffffff}, expected_status=404) def test_checkout_order_not_editable(self) -> None: # Create an order and check it out order_id = self._request_as_json('post', '/orders')['id'] self._request_as_json('post', '/orders/checkout', params={'id': order_id}) # Check the error self._request_error('post', '/orders/checkout', params={'id': order_id}, expected_status=422) # # Complete order errors # def test_complete_order_missing_id(self) -> None: self._request_error('post', '/orders/complete', expected_status=400) def test_complete_order_invalid_id(self) -> None: self._request_error('post', '/orders/complete', params={'id': 'abc'}, expected_status=400) def test_complete_order_not_found(self) -> None: self._request_error('post', '/orders/complete', params={'id': 0xffffffff}, expected_status=404) def test_complete_order_not_editable(self) -> None: # Create an order order_id = self._request_as_json('post', '/orders')['id'] # Check the error self._request_error('post', '/orders/complete', params={'id': order_id}, expected_status=422) # # Generic errors # def test_endpoint_not_found(self) -> None: self._request_error('get', '/orders/other', expected_status=404) self._request_error('get', '/orders_other', expected_status=404) def test_method_not_allowed(self) -> None: self._request_error('delete', '/orders', expected_status=405) def main(): # Parse command line arguments parser = argparse.ArgumentParser() parser.add_argument('executable') parser.add_argument('host') args = parser.parse_args() # Launch the server with launch_server(args.executable, args.host, 'orders_user', 'orders_password') as listening_port: TestOrders._port = listening_port unittest.main(argv=[sys.argv[0]]) if __name__ == '__main__': main() ================================================ FILE: example/private/run_patch_updates.py ================================================ #!/usr/bin/python3 # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # from subprocess import run import argparse from typing import List # Helper to run the batch updates example class _Runner: def __init__(self, exe: str, host: str) -> None: self._exe = exe self._host = host def run(self, updates: List[str]) -> None: employee_id = '1' # Guaranteed to exist cmdline = [self._exe, 'example_user', 'example_password', self._host, employee_id] + updates print(' + ', ' '.join(cmdline)) run(cmdline, check=True) def main(): # Parse command line parser = argparse.ArgumentParser() parser.add_argument('executable') parser.add_argument('host') args = parser.parse_args() # Build a runner runner = _Runner(args.executable, args.host) # Run the example with several combinations runner.run(['--salary=40000']) runner.run(['--company-id=HGS', '--last-name=Alice']) runner.run(['--salary=25000', '--company-id=AWC', '--first-name=John', '--last-name=Doe']) if __name__ == '__main__': main() ================================================ FILE: example/private/run_tutorial_connection_pool.py ================================================ #!/usr/bin/python3 # # Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # import argparse import sys from os import path import socket import struct sys.path.append(path.abspath(path.dirname(path.realpath(__file__)))) from launch_server import launch_server class _Runner: def __init__(self, port: int) -> None: self._port = port def _connect(self) -> socket.socket: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('127.0.0.1', self._port)) return sock def _query_employee(self, employee_id: int) -> str: # Open a connection sock = self._connect() # Send the request sock.send(struct.pack('>Q', employee_id)) # Receive the response. It should always fit in a single TCP segment # for the values we have in CI res = sock.recv(4096).decode() assert len(res) > 0 return res def _generate_error(self) -> None: # Open a connection sock = self._connect() # Send an incomplete message sock.send(b'abc') sock.close() def run(self, test_errors: bool) -> None: # Generate an error first. The server should not terminate if test_errors: self._generate_error() assert self._query_employee(1) != 'NOT_FOUND' value = self._query_employee(0xffffffff) assert value == 'NOT_FOUND', 'Value is: {}'.format(value) def main(): # Parse command line arguments parser = argparse.ArgumentParser() parser.add_argument('executable') parser.add_argument('host') parser.add_argument('--test-errors', action='store_true') args = parser.parse_args() # Launch the server with launch_server(args.executable, args.host, 'example_user', 'example_password') as listening_port: # Run the tests _Runner(listening_port).run(args.test_errors) if __name__ == '__main__': main() ================================================ FILE: example/private/test_script.sql ================================================ -- -- Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) -- -- Distributed under the Boost Software License, Version 1.0. (See accompanying -- file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -- USE boost_mysql_examples; CREATE TEMPORARY TABLE products ( id VARCHAR(50) PRIMARY KEY, description VARCHAR(256) ); INSERT INTO products VALUES ('PTT', 'Potatoes'), ('CAR', NULL); SELECT * FROM products; DROP TABLE products; ================================================ FILE: include/boost/mysql/any_address.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_ANY_ADDRESS_HPP #define BOOST_MYSQL_ANY_ADDRESS_HPP #include #include #include #include namespace boost { namespace mysql { /// The type of an address identifying a MySQL server. enum class address_type { /// An Internet hostname and a TCP port. host_and_port, /// A UNIX domain socket path. unix_path }; /** * \brief A host and port identifying how to connect to a MySQL server. * \details * This is an owning type with value semantics. */ struct host_and_port { /** * \brief The hostname where the MySQL server is expected to be listening. * \details * An empty string is equivalent to `localhost`. This is the default. * This is an owning field */ std::string host; /// The port where the MySQL server is expected to be listening. unsigned short port{default_port}; }; /** * \brief Contains a UNIX-socket domain path. * \details * This type is defined in all systems, regardless of their UNIX socket support. * \n * This is an owning type with value semantics. */ struct unix_path { /** * \brief The UNIX domain socket path where the MySQL server is listening. * \details Defaults to the empty string. This is an owning field. */ std::string path; }; /** * \brief A server address, identifying how to physically connect to a MySQL server. * \details * A variant-like type that can represent the network address of a MySQL server, * regardless of the transport type being used. It can contain either a host * and port (to connect using TCP) or a UNIX path (to connect using UNIX domain sockets). * \n * This class may be extended in the future to accommodate Windows named pipes. * \n * This type has value semantics: it is owning and regular. */ class any_address { #ifndef BOOST_MYSQL_DOXYGEN struct { address_type type; std::string address; unsigned short port; } impl_; any_address(address_type t, std::string&& addr, unsigned short port) noexcept : impl_{t, std::move(addr), port} { } friend struct detail::access; #endif public: /** * \brief Constructs an empty address. * \details Results in an address with `this->type() == address_type::host_and_port`, * `this->hostname() == ""` and `this->port() == default_port`, which identifies * a server running on `localhost` using the default port. * \par Exception safety * No-throw guarantee. */ any_address() noexcept : any_address(address_type::host_and_port, std::string(), default_port) {} /** * \brief Copy constructor. * \par Exception safety * Strong guarantee. Exceptions may be thrown by memory allocations. * \par Object lifetimes * `*this` and `other` will have independent lifetimes (regular value semantics). */ any_address(const any_address& other) = default; /** * \brief Move constructor. * \details Leaves `other` in a valid but unspecified state. * \par Exception safety * No-throw guarantee. */ any_address(any_address&& other) = default; /** * \brief Copy assignment. * \par Exception safety * Basic guarantee. Exceptions may be thrown by memory allocations. * \par Object lifetimes * `*this` and `other` will have independent lifetimes (regular value semantics). */ any_address& operator=(const any_address& other) = default; /** * \brief Move assignment. * \details Leaves `other` in a valid but unspecified state. * \par Exception safety * No-throw guarantee. */ any_address& operator=(any_address&& other) = default; /// Destructor. ~any_address() = default; /** * \brief Constructs an address containing a host and a port. * \details Results in an address with `this->type() == address_type::host_and_port`, * `this->hostname() == value.hostname()` and `this->port() == value.port()`. * * \par Object lifetimes * `*this` and `value` will have independent lifetimes (regular value semantics). * * \par Exception safety * No-throw guarantee. */ any_address(host_and_port value) noexcept : impl_{address_type::host_and_port, std::move(value.host), value.port} { } /** * \brief Constructs an address containing a UNIX socket path. * \details Results in an address with `this->type() == address_type::unix_path`, * `this->unix_socket_path() == value.path()`. * * \par Object lifetimes * `*this` and `value` will have independent lifetimes (regular value semantics). * * \par Exception safety * No-throw guarantee. */ any_address(unix_path value) noexcept : impl_{address_type::unix_path, std::move(value.path), 0} {} /** * \brief Retrieves the type of address that this object contains. * \par Exception safety * No-throw guarantee. */ address_type type() const noexcept { return impl_.type; } /** * \brief Retrieves the hostname that this object contains. * \par Preconditions * `this->type() == address_type::host_and_port` * * \par Object lifetimes * The returned view points into `*this`, and is valid as long as `*this` * is alive and hasn't been assigned to or moved from. * * \par Exception safety * No-throw guarantee. */ string_view hostname() const noexcept { BOOST_ASSERT(type() == address_type::host_and_port); return impl_.address; } /** * \brief Retrieves the port that this object contains. * \par Preconditions * `this->type() == address_type::host_and_port` * * \par Exception safety * No-throw guarantee. */ unsigned short port() const noexcept { BOOST_ASSERT(type() == address_type::host_and_port); return impl_.port; } /** * \brief Retrieves the UNIX socket path that this object contains. * \par Preconditions * `this->type() == address_type::unix_path` * * \par Object lifetimes * The returned view points into `*this`, and is valid as long as `*this` * is alive and hasn't been assigned to or moved from. * * \par Exception safety * No-throw guarantee. */ string_view unix_socket_path() const noexcept { BOOST_ASSERT(type() == address_type::unix_path); return impl_.address; } /** * \brief Replaces the current object with a host and port. * \details * Destroys the current contained object and constructs a new * host and port from the passed components. This function can * change the underlying type of object held by `*this`. * \n * The constructed object has `this->type() == address_type::host_and_port`, * `this->hostname() == hostname` and `this->port() == port`. * \n * An empty hostname is equivalent to `localhost`. * \n * \par Exception safety * Basic guarantee. Memory allocations may throw. * \par Object lifetimes * Invalidates views pointing into `*this`. */ void emplace_host_and_port(std::string hostname, unsigned short port = default_port) { impl_.type = address_type::host_and_port; impl_.address = std::move(hostname); impl_.port = port; } /** * \brief Replaces the current object with a UNIX socket path. * \details * Destroys the current contained object and constructs a new * UNIX socket path from the passed value. This function can * change the underlying type of object held by `*this`. * \n * The constructed object has `this->type() == address_type::unix_path` and * `this->unix_socket_path() == path`. * \n * \par Exception safety * Basic guarantee. Memory allocations may throw. * \par Object lifetimes * Invalidates views pointing into `*this`. */ void emplace_unix_path(std::string path) { impl_.type = address_type::unix_path; impl_.address = std::move(path); impl_.port = 0; } /** * \brief Tests for equality. * \details Two addresses are equal if they have the same type and individual components. * \par Exception safety * No-throw guarantee. */ bool operator==(const any_address& rhs) const noexcept { return impl_.type == rhs.impl_.type && impl_.address == rhs.impl_.address && impl_.port == rhs.impl_.port; } /** * \brief Tests for inequality. * \par Exception safety * No-throw guarantee. */ bool operator!=(const any_address& rhs) const noexcept { return !(*this == rhs); } }; } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/any_connection.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_ANY_CONNECTION_HPP #define BOOST_MYSQL_ANY_CONNECTION_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { // Forward declarations template class static_execution_state; class pipeline_request; class stage_response; /** * \brief Configuration parameters that can be passed to \ref any_connection's constructor. */ struct any_connection_params { /** * \brief An external SSL context containing options to configure TLS. * \details * Relevant only for SSL connections (those that result on \ref * any_connection::uses_ssl returning `true`). * \n * If the connection is configured to use TLS, an internal `asio::ssl::stream` * object will be created. If this member is set to a non-null value, * this internal object will be initialized using the passed context. * This is the only way to configure TLS options in `any_connection`. * \n * If the connection is configured to use TLS and this member is `nullptr`, * an internal `asio::ssl::context` object with suitable default options * will be created. * * \par Object lifetimes * If set to non-null, the pointee object must be kept alive until * all \ref any_connection objects constructed from `*this` are destroyed. */ asio::ssl::context* ssl_context{}; /** * \brief The initial size of the connection's buffer, in bytes. * \details A bigger read buffer can increase the number of rows * returned by \ref any_connection::read_some_rows. */ std::size_t initial_buffer_size{default_initial_read_buffer_size}; /** * \brief The maximum size of the connection's buffer, in bytes (64MB by default). * \details * Attempting to read or write a protocol packet bigger than this size * will fail with a \ref client_errc::max_buffer_size_exceeded error. * \n * This effectively means: \n * - Each request sent to the server must be smaller than this value. * - Each individual row received from the server must be smaller than this value. * Note that when using `execute` or `async_execute`, results objects may * allocate memory beyond this limit if the total number of rows is high. * \n * If you need to send or receive larger packets, you may need to adjust * your server's `max_allowed_packet` * system variable, too. */ std::size_t max_buffer_size{0x4000000}; }; /** * \brief A connection to a MySQL server. * \details * Represents a connection to a MySQL server. * This is the main I/O object that this library implements. It's logically comprised * of session state and an internal stream (usually a socket). The stream is not directly * accessible. It's constructed using the executor passed to the constructor. * * This class supports establishing connections * with servers using TCP, TCP over TLS and UNIX sockets. * * The class is named `any_connection` because it's not templated on a `Stream` * type, as opposed to \ref connection. New code should prefer using `any_connection` * whenever possible. * * Compared to \ref connection, this class: * * - Is type-erased. The type of the connection doesn't depend on the transport being used. * - Is easier to connect, as \ref connect and \ref async_connect handle hostname resolution. * - Can always be re-connected after being used or encountering an error. * - Always uses `asio::any_io_executor`. * - Has the same level of performance. * * This is a move-only type. * * \par Single outstanding async operation per connection * At any given point in time, only one async operation can be outstanding * per connection. If an async operation is initiated while another one is in progress, * it will fail with \ref client_errc::operation_in_progress. * * \par Default completion tokens * The default completion token for all async operations in this class is * `with_diagnostics(asio::deferred)`, which allows you to use `co_await` * and have the expected exceptions thrown on error. * * \par Thread safety * Distinct objects: safe. \n * Shared objects: unsafe. \n * This class is not thread-safe: for a single object, if you * call its member functions concurrently from separate threads, you will get a race condition. */ class any_connection { detail::connection_impl impl_; #ifndef BOOST_MYSQL_DOXYGEN friend struct detail::access; #endif BOOST_MYSQL_DECL static std::unique_ptr create_engine(asio::any_io_executor ex, asio::ssl::context* ctx); // Used by tests any_connection(std::unique_ptr eng, any_connection_params params) : impl_(params.initial_buffer_size, params.max_buffer_size, std::move(eng)) { } public: /** * \brief Constructs a connection object from an executor and an optional set of parameters. * \details * The resulting connection has `this->get_executor() == ex`. Any internally required I/O objects * will be constructed using this executor. * \n * You can configure extra parameters, like the SSL context and buffer sizes, by passing * an \ref any_connection_params object to this constructor. */ any_connection(boost::asio::any_io_executor ex, any_connection_params params = {}) : any_connection(create_engine(std::move(ex), params.ssl_context), params) { } /** * \brief Constructs a connection object from an execution context and an optional set of parameters. * \details * The resulting connection has `this->get_executor() == ctx.get_executor()`. * Any internally required I/O objects will be constructed using this executor. * \n * You can configure extra parameters, like the SSL context and buffer sizes, by passing * an \ref any_connection_params object to this constructor. * \n * This function participates in overload resolution only if `ExecutionContext` * satisfies the `ExecutionContext` requirements imposed by Boost.Asio. */ template < class ExecutionContext #ifndef BOOST_MYSQL_DOXYGEN , class = typename std::enable_if().get_executor()), asio::any_io_executor>::value>::type #endif > any_connection(ExecutionContext& ctx, any_connection_params params = {}) : any_connection(ctx.get_executor(), params) { } /** * \brief Move constructor. */ any_connection(any_connection&& other) = default; /** * \brief Move assignment. */ any_connection& operator=(any_connection&& rhs) = default; #ifndef BOOST_MYSQL_DOXYGEN any_connection(const any_connection&) = delete; any_connection& operator=(const any_connection&) = delete; #endif /** * \brief Destructor. * \details * Closes the connection at the transport layer (by closing any underlying socket objects). * If you require a clean close, call \ref close or \ref async_close before the connection * is destroyed. */ ~any_connection() = default; /// The executor type associated to this object. using executor_type = asio::any_io_executor; /** * \brief Retrieves the executor associated to this object. * \par Exception safety * No-throw guarantee. */ executor_type get_executor() noexcept { return impl_.get_engine().get_executor(); } /** * \brief Returns whether the connection negotiated the use of SSL or not. * \details * This function can be used to determine whether you are using a SSL * connection or not when using SSL negotiation. * \n * This function always returns `false` * for connections that haven't been established yet. If the connection establishment fails, * the return value is undefined. * * \par Exception safety * No-throw guarantee. */ bool uses_ssl() const noexcept { return impl_.ssl_active(); } /** * \brief Returns whether backslashes are being treated as escape sequences. * \details * By default, the server treats backslashes in string values as escape characters. * This behavior can be disabled by activating the `NO_BACKSLASH_ESCAPES` * SQL mode. * \n * Every time an operation involving server communication completes, the server reports whether * this mode was activated or not as part of the response. Connections store this information * and make it available through this function. * \n * \li If backslash are treated like escape characters, returns `true`. * \li If `NO_BACKSLASH_ESCAPES` has been activated, returns `false`. * \li If connection establishment hasn't happened yet, returns `true`. * \li Calling this function while an async operation that changes backslash behavior * is outstanding may return `true` or `false`. * \n * This function does not involve server communication. * * \par Exception safety * No-throw guarantee. */ bool backslash_escapes() const noexcept { return impl_.backslash_escapes(); } /** * \brief Returns the character set used by this connection. * \details * Connections attempt to keep track of the current character set. * Deficiencies in the protocol can cause the character set to be unknown, though. * When the character set is known, this function returns * the character set currently in use. Otherwise, returns \ref client_errc::unknown_character_set. * \n * The following functions can modify the return value of this function: \n * \li Prior to connection, the character set is always unknown. * \li \ref connect and \ref async_connect may set the current character set * to a known value, depending on the requested collation. * \li \ref set_character_set always and \ref async_set_character_set always * set the current character set to the passed value. * \li \ref reset_connection and \ref async_reset_connection always makes the current character * unknown. * * \par Avoid changing the character set directly * If you change the connection's character set directly using SQL statements * like `"SET NAMES utf8mb4"`, the client has no way to track this change, * and this function will return incorrect results. * * \par Errors * \li \ref client_errc::unknown_character_set if the current character set is unknown. * * \par Exception safety * No-throw guarantee. */ system::result current_character_set() const noexcept { return impl_.current_character_set(); } /** * \brief Returns format options suitable to format SQL according to the current connection configuration. * \details * If the current character set is known (as given by \ref current_character_set), returns * a value suitable to be passed to SQL formatting functions. Otherwise, returns an error. * * \par Errors * \li \ref client_errc::unknown_character_set if the current character set is unknown. * * \par Exception safety * No-throw guarantee. */ system::result format_opts() const noexcept { auto res = current_character_set(); if (res.has_error()) return res.error(); return format_options{res.value(), backslash_escapes()}; } /** * \brief Returns the current metadata mode that this connection is using. * \details * \par Exception safety * No-throw guarantee. * * \returns The metadata mode that will be used for queries and statement executions. */ metadata_mode meta_mode() const noexcept { return impl_.meta_mode(); } /** * \brief Sets the metadata mode. * \details * Will affect any query and statement executions performed after the call. * * \par Exception safety * No-throw guarantee. * * \par Preconditions * No asynchronous operation should be outstanding when this function is called. * * \param v The new metadata mode. */ void set_meta_mode(metadata_mode v) noexcept { impl_.set_meta_mode(v); } /** * \brief Retrieves the connection id associated to the current session. * \details * If a session has been established, returns its associated connection id. * If no session has been established (i.e. \ref async_connect hasn't been called yet) * or the session has been terminated (i.e. \ref async_close has been called), an empty * optional is returned. * * The connection id is a 4 byte value that uniquely identifies a client session * at a given point in time. It can be used with the * `KILL` SQL statement * to cancel queries and terminate connections. * * The server sends the connection id assigned to the current session as part of the * handshake process. The value is stored and made available through this function. * The same id can also be obtained by calling the * CONNECTION_ID() * SQL function. However, this function is faster and more reliable, since it does not entail * communication with the server. * * This function is equivalent to the * `mysql_thread_id` function * in the C connector. This function works properly in 64-bit systems, as opposed to what * the official docs suggest (see * this changelog). * * It is safe to call this function while an async operation is outstanding, except for \ref async_connect * and \ref async_close. * * \par Exception safety * No-throw guarantee. */ boost::optional connection_id() const noexcept { return impl_.connection_id(); } /** * \brief Establishes a connection to a MySQL server. * \details * This function performs the following: * \n * \li If a connection has already been established (by a previous call to \ref connect * or \ref async_connect), closes it at the transport layer (by closing any underlying socket) * and discards any protocol state associated to it. (If you require * a clean close, call \ref close or \ref async_close before using this function). * \li If the connection is configured to use TCP (`params.server_address.type() == * address_type::host_and_port`), resolves the passed hostname to a set of endpoints. An empty * hostname is equivalent to `"localhost"`. * \li Establishes the physical connection (performing the * TCP or UNIX socket connect). * \li Performs the MySQL handshake to establish a session. If the * connection is configured to use TLS, the TLS handshake is performed as part of this step. * \li If any of the above steps fail, the TCP or UNIX socket connection is closed. * \n * You can configure some options using the \ref connect_params struct. * \n * The decision to use TLS or not is performed using the following: * \n * \li If the transport is not TCP (`params.server_address.type() != address_type::host_and_port`), * the connection will never use TLS. * \li If the transport is TCP, and `params.ssl == ssl_mode::disable`, the connection will not use TLS. * \li If the transport is TCP, and `params.ssl == ssl_mode::enable`, the connection will use TLS * only if the server supports it. * \li If the transport is TCP, and `params.ssl == ssl_mode::require`, the connection will always use TLS. * If the server doesn't support it, the operation will fail with \ref * client_errc::server_doesnt_support_ssl. * \n * If `params.connection_collation` is within a set of well-known collations, this function * sets the current character set, such that \ref current_character_set returns a non-null value. * The default collation (`utf8mb4_general_ci`) is the only one guaranteed to be in the set of well-known * collations. */ void connect(const connect_params& params, error_code& ec, diagnostics& diag) { impl_.connect_v2(params, ec, diag); } /// \copydoc connect void connect(const connect_params& params) { error_code err; diagnostics diag; connect(params, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc connect * * \par Object lifetimes * params needs to be kept alive until the operation completes, as no * copies will be made by the library. * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) CompletionToken = with_diagnostics_t> auto async_connect(const connect_params& params, diagnostics& diag, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_connect_v2_t) { return impl_.async_connect_v2(params, diag, std::forward(token)); } /// \copydoc async_connect template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) CompletionToken = with_diagnostics_t> auto async_connect(const connect_params& params, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_connect_v2_t) { return async_connect(params, impl_.shared_diag(), std::forward(token)); } /** * \brief Executes a text query or prepared statement. * \details * Sends `req` to the server for execution and reads the response into `result`. * `result` may be either a \ref results or \ref static_results object. * `req` should may be either a type convertible to \ref string_view containing valid SQL * or a bound prepared statement, obtained by calling \ref statement::bind. * If a string, it must be encoded using the connection's character set. * Any string parameters provided to \ref statement::bind should also be encoded * using the connection's character set. * \n * After this operation completes successfully, `result.has_value() == true`. * \n * Metadata in `result` will be populated according to `this->meta_mode()`. */ template void execute(ExecutionRequest&& req, ResultsType& result, error_code& err, diagnostics& diag) { impl_.execute(std::forward(req), result, err, diag); } /// \copydoc execute template void execute(ExecutionRequest&& req, ResultsType& result) { error_code err; diagnostics diag; execute(std::forward(req), result, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc execute * \par Object lifetimes * If `CompletionToken` is a deferred completion token (e.g. `use_awaitable`), the caller is * responsible for managing `req`'s validity following these rules: * \n * \li If `req` is `string_view`, the string pointed to by `req` * must be kept alive by the caller until the operation is initiated. * \li If `req` is a \ref bound_statement_tuple, and any of the parameters is a reference * type (like `string_view`), the caller must keep the values pointed by these references alive * until the operation is initiated. * \li If `req` is a \ref bound_statement_iterator_range, the caller must keep objects in * the iterator range passed to \ref statement::bind alive until the operation is initiated. * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_RESULTS_TYPE ResultsType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_execute(ExecutionRequest&& req, ResultsType& result, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_execute_t) { return async_execute( std::forward(req), result, impl_.shared_diag(), std::forward(token) ); } /// \copydoc async_execute template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_RESULTS_TYPE ResultsType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_execute( ExecutionRequest&& req, ResultsType& result, diagnostics& diag, CompletionToken&& token = {} ) BOOST_MYSQL_RETURN_TYPE(detail::async_execute_t) { return impl_.async_execute( std::forward(req), result, diag, std::forward(token) ); } /** * \brief Starts a SQL execution as a multi-function operation. * \details * Writes the execution request and reads the initial server response and the column * metadata, but not the generated rows or subsequent resultsets, if any. * `st` may be either an \ref execution_state or \ref static_execution_state object. * \n * After this operation completes, `st` will have * \ref execution_state::meta populated. * Metadata will be populated according to `this->meta_mode()`. * \n * If the operation generated any rows or more than one resultset, these must be read (by using * \ref read_some_rows and \ref read_resultset_head) before engaging in any further network operation. * Otherwise, the results are undefined. * \n * req may be either a type convertible to \ref string_view containing valid SQL * or a bound prepared statement, obtained by calling \ref statement::bind. * If a string, it must be encoded using the connection's character set. * Any string parameters provided to \ref statement::bind should also be encoded * using the connection's character set. * \n * When using the static interface, this function will detect schema mismatches for the first * resultset. Further errors may be detected by \ref read_resultset_head and \ref read_some_rows. * \n */ template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType> void start_execution(ExecutionRequest&& req, ExecutionStateType& st, error_code& err, diagnostics& diag) { impl_.start_execution(std::forward(req), st, err, diag); } /// \copydoc start_execution template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType> void start_execution(ExecutionRequest&& req, ExecutionStateType& st) { error_code err; diagnostics diag; start_execution(std::forward(req), st, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc start_execution * \par Object lifetimes * If `CompletionToken` is a deferred completion token (e.g. `use_awaitable`), the caller is * responsible for managing `req`'s validity following these rules: * \n * \li If `req` is `string_view`, the string pointed to by `req` * must be kept alive by the caller until the operation is initiated. * \li If `req` is a \ref bound_statement_tuple, and any of the parameters is a reference * type (like `string_view`), the caller must keep the values pointed by these references alive * until the operation is initiated. * \li If `req` is a \ref bound_statement_iterator_range, the caller must keep objects in * the iterator range passed to \ref statement::bind alive until the operation is initiated. * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_start_execution(ExecutionRequest&& req, ExecutionStateType& st, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_start_execution_t< ExecutionRequest&&, ExecutionStateType, CompletionToken&&>) { return async_start_execution( std::forward(req), st, impl_.shared_diag(), std::forward(token) ); } /// \copydoc async_start_execution template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_start_execution( ExecutionRequest&& req, ExecutionStateType& st, diagnostics& diag, CompletionToken&& token = {} ) BOOST_MYSQL_RETURN_TYPE(detail::async_start_execution_t< ExecutionRequest&&, ExecutionStateType, CompletionToken&&>) { return impl_.async_start_execution( std::forward(req), st, diag, std::forward(token) ); } /** * \brief Prepares a statement server-side. * \details * `stmt` should be encoded using the connection's character set. * \n * The returned statement has `valid() == true`. */ statement prepare_statement(string_view stmt, error_code& err, diagnostics& diag) { return impl_.run(detail::prepare_statement_algo_params{stmt}, err, diag); } /// \copydoc prepare_statement statement prepare_statement(string_view stmt) { error_code err; diagnostics diag; statement res = prepare_statement(stmt, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); return res; } /** * \copydoc prepare_statement * \details * \par Object lifetimes * If `CompletionToken` is a deferred completion token (e.g. `use_awaitable`), the string * pointed to by `stmt` must be kept alive by the caller until the operation is * initiated. * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code, boost::mysql::statement)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::statement)) CompletionToken = with_diagnostics_t> auto async_prepare_statement(string_view stmt, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_prepare_statement_t) { return async_prepare_statement(stmt, impl_.shared_diag(), std::forward(token)); } /// \copydoc async_prepare_statement template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::statement)) CompletionToken = with_diagnostics_t> auto async_prepare_statement(string_view stmt, diagnostics& diag, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_prepare_statement_t) { return impl_.async_run( detail::prepare_statement_algo_params{stmt}, diag, std::forward(token) ); } /** * \brief Closes a statement, deallocating it from the server. * \details * After this operation succeeds, `stmt` must not be used again for execution. * \n * \par Preconditions * `stmt.valid() == true` */ void close_statement(const statement& stmt, error_code& err, diagnostics& diag) { impl_.run(impl_.make_params_close_statement(stmt), err, diag); } /// \copydoc close_statement void close_statement(const statement& stmt) { error_code err; diagnostics diag; close_statement(stmt, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc close_statement * \details * \par Object lifetimes * It is not required to keep `stmt` alive, as copies are made by the implementation as required. * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_close_statement(const statement& stmt, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_close_statement_t) { return async_close_statement(stmt, impl_.shared_diag(), std::forward(token)); } /// \copydoc async_close_statement template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_close_statement(const statement& stmt, diagnostics& diag, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_close_statement_t) { return impl_ .async_run(impl_.make_params_close_statement(stmt), diag, std::forward(token)); } /** * \brief Reads a batch of rows. * \details * The number of rows that will be read is unspecified. If the operation represented by `st` * has still rows to read, at least one will be read. If there are no more rows, or * `st.should_read_rows() == false`, returns an empty `rows_view`. * \n * The number of rows that will be read depends on the connection's buffer size. The bigger the buffer, * the greater the batch size (up to a maximum). You can set the initial buffer size in the * constructor. The buffer may be * grown bigger by other read operations, if required. * \n * The returned view points into memory owned by `*this`. It will be valid until * `*this` performs the next network operation or is destroyed. */ rows_view read_some_rows(execution_state& st, error_code& err, diagnostics& diag) { return impl_.run(impl_.make_params_read_some_rows(st), err, diag); } /// \copydoc read_some_rows(execution_state&,error_code&,diagnostics&) rows_view read_some_rows(execution_state& st) { error_code err; diagnostics diag; rows_view res = read_some_rows(st, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); return res; } /** * \copydoc read_some_rows(execution_state&,error_code&,diagnostics&) * \details * \par Handler signature * The handler signature for this operation is * `void(boost::mysql::error_code, boost::mysql::rows_view)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view)) CompletionToken = with_diagnostics_t> auto async_read_some_rows(execution_state& st, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_read_some_rows_dynamic_t) { return async_read_some_rows(st, impl_.shared_diag(), std::forward(token)); } /// \copydoc async_read_some_rows(execution_state&,CompletionToken&&) template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view)) CompletionToken = with_diagnostics_t> auto async_read_some_rows(execution_state& st, diagnostics& diag, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_read_some_rows_dynamic_t) { return impl_ .async_run(impl_.make_params_read_some_rows(st), diag, std::forward(token)); } #ifdef BOOST_MYSQL_CXX14 /** * \brief Reads a batch of rows. * \details * Reads a batch of rows of unspecified size into the storage given by `output`. * At most `output.size()` rows will be read. If the operation represented by `st` * has still rows to read, and `output.size() > 0`, at least one row will be read. * \n * Returns the number of read rows. * \n * If there are no more rows, or `st.should_read_rows() == false`, this function is a no-op and returns * zero. * \n * The number of rows that will be read depends on the connection's buffer size. The bigger the buffer, * the greater the batch size (up to a maximum). You can set the initial buffer size in the * constructor. The buffer may be grown bigger by other read operations, if required. * \n * Rows read by this function are owning objects, and don't hold any reference to * the connection's internal buffers (contrary what happens with the dynamic interface's counterpart). * \n * The type `SpanElementType` must be the underlying row type for one of the types in the * `StaticRow` parameter pack (i.e., one of the types in `underlying_row_t...`). * The type must match the resultset that is currently being processed by `st`. For instance, * given `static_execution_state`, when reading rows for the second resultset, `SpanElementType` * must exactly be `underlying_row_t`. If this is not the case, a runtime error will be issued. * \n * This function can report schema mismatches. */ template std::size_t read_some_rows( static_execution_state& st, span output, error_code& err, diagnostics& diag ) { return impl_.run(impl_.make_params_read_some_rows_static(st, output), err, diag); } /** * \brief Reads a batch of rows. * \details * Reads a batch of rows of unspecified size into the storage given by `output`. * At most `output.size()` rows will be read. If the operation represented by `st` * has still rows to read, and `output.size() > 0`, at least one row will be read. * \n * Returns the number of read rows. * \n * If there are no more rows, or `st.should_read_rows() == false`, this function is a no-op and returns * zero. * \n * The number of rows that will be read depends on the connection's buffer size. The bigger the buffer, * the greater the batch size (up to a maximum). You can set the initial buffer size in the * constructor. The buffer may be grown bigger by other read operations, if required. * \n * Rows read by this function are owning objects, and don't hold any reference to * the connection's internal buffers (contrary what happens with the dynamic interface's counterpart). * \n * The type `SpanElementType` must be the underlying row type for one of the types in the * `StaticRow` parameter pack (i.e., one of the types in `underlying_row_t...`). * The type must match the resultset that is currently being processed by `st`. For instance, * given `static_execution_state`, when reading rows for the second resultset, `SpanElementType` * must exactly be `underlying_row_t`. If this is not the case, a runtime error will be issued. * \n * This function can report schema mismatches. */ template std::size_t read_some_rows(static_execution_state& st, span output) { error_code err; diagnostics diag; std::size_t res = read_some_rows(st, output, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); return res; } /** * \brief Reads a batch of rows. * \details * Reads a batch of rows of unspecified size into the storage given by `output`. * At most `output.size()` rows will be read. If the operation represented by `st` * has still rows to read, and `output.size() > 0`, at least one row will be read. * \n * Returns the number of read rows. * \n * If there are no more rows, or `st.should_read_rows() == false`, this function is a no-op and returns * zero. * \n * The number of rows that will be read depends on the connection's buffer size. The bigger the buffer, * the greater the batch size (up to a maximum). You can set the initial buffer size in the * constructor. The buffer may be grown bigger by other read operations, if required. * \n * Rows read by this function are owning objects, and don't hold any reference to * the connection's internal buffers (contrary what happens with the dynamic interface's counterpart). * \n * The type `SpanElementType` must be the underlying row type for one of the types in the * `StaticRow` parameter pack (i.e., one of the types in `underlying_row_t...`). * The type must match the resultset that is currently being processed by `st`. For instance, * given `static_execution_state`, when reading rows for the second resultset, `SpanElementType` * must exactly be `underlying_row_t`. If this is not the case, a runtime error will be issued. * \n * This function can report schema mismatches. * * \par Handler signature * The handler signature for this operation is * `void(boost::mysql::error_code, std::size_t)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. * * \par Object lifetimes * The storage that `output` references must be kept alive until the operation completes. */ template < class SpanElementType, class... StaticRow, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, std::size_t)) CompletionToken = with_diagnostics_t> auto async_read_some_rows( static_execution_state& st, span output, CompletionToken&& token = {} ) { return async_read_some_rows(st, output, impl_.shared_diag(), std::forward(token)); } /** * \brief Reads a batch of rows. * \details * Reads a batch of rows of unspecified size into the storage given by `output`. * At most `output.size()` rows will be read. If the operation represented by `st` * has still rows to read, and `output.size() > 0`, at least one row will be read. * \n * Returns the number of read rows. * \n * If there are no more rows, or `st.should_read_rows() == false`, this function is a no-op and returns * zero. * \n * The number of rows that will be read depends on the connection's buffer size. The bigger the buffer, * the greater the batch size (up to a maximum). You can set the initial buffer size in the * constructor. The buffer may be grown bigger by other read operations, if required. * \n * Rows read by this function are owning objects, and don't hold any reference to * the connection's internal buffers (contrary what happens with the dynamic interface's counterpart). * \n * The type `SpanElementType` must be the underlying row type for one of the types in the * `StaticRow` parameter pack (i.e., one of the types in `underlying_row_t...`). * The type must match the resultset that is currently being processed by `st`. For instance, * given `static_execution_state`, when reading rows for the second resultset, `SpanElementType` * must exactly be `underlying_row_t`. If this is not the case, a runtime error will be issued. * \n * This function can report schema mismatches. * * \par Handler signature * The handler signature for this operation is * `void(boost::mysql::error_code, std::size_t)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. * * \par Object lifetimes * The storage that `output` references must be kept alive until the operation completes. */ template < class SpanElementType, class... StaticRow, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, std::size_t)) CompletionToken = with_diagnostics_t> auto async_read_some_rows( static_execution_state& st, span output, diagnostics& diag, CompletionToken&& token = {} ) { return impl_.async_run( impl_.make_params_read_some_rows_static(st, output), diag, std::forward(token) ); } #endif /** * \brief Reads metadata for subsequent resultsets in a multi-resultset operation. * \details * If `st.should_read_head() == true`, this function will read the next resultset's * initial response message and metadata, if any. If the resultset indicates a failure * (e.g. the query associated to this resultset contained an error), this function will fail * with that error. * \n * If `st.should_read_head() == false`, this function is a no-op. * \n * `st` may be either an \ref execution_state or \ref static_execution_state object. * \n * This function is only relevant when using multi-function operations with statements * that return more than one resultset. * \n * When using the static interface, this function will detect schema mismatches for the resultset * currently being read. Further errors may be detected by subsequent invocations of this function * and by \ref read_some_rows. * \n */ template void read_resultset_head(ExecutionStateType& st, error_code& err, diagnostics& diag) { return impl_.run(impl_.make_params_read_resultset_head(st), err, diag); } /// \copydoc read_resultset_head template void read_resultset_head(ExecutionStateType& st) { error_code err; diagnostics diag; read_resultset_head(st, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc read_resultset_head * \par Handler signature * The handler signature for this operation is * `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template < BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_read_resultset_head(ExecutionStateType& st, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_read_resultset_head_t) { return async_read_resultset_head(st, impl_.shared_diag(), std::forward(token)); } /// \copydoc async_read_resultset_head template < BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_read_resultset_head(ExecutionStateType& st, diagnostics& diag, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_read_resultset_head_t) { return impl_ .async_run(impl_.make_params_read_resultset_head(st), diag, std::forward(token)); } /** * \brief Sets the connection's character set, as per SET NAMES. * \details * Sets the connection's character set by running a * `SET NAMES` * SQL statement, using the passed \ref character_set::name as the charset name to set. * \n * This function will also update the value returned by \ref current_character_set, so * prefer using this function over raw SQL statements. * \n * If the server was unable to set the character set to the requested value (e.g. because * the server does not support the requested charset), this function will fail, * as opposed to how \ref connect behaves when an unsupported collation is passed. * This is a limitation of MySQL servers. * \n * You need to perform connection establishment for this function to succeed, since it * involves communicating with the server. * * \par Object lifetimes * `charset` will be copied as required, and does not need to be kept alive. */ void set_character_set(const character_set& charset, error_code& err, diagnostics& diag) { impl_.run(detail::set_character_set_algo_params{charset}, err, diag); } /// \copydoc set_character_set void set_character_set(const character_set& charset) { error_code err; diagnostics diag; set_character_set(charset, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc set_character_set * \details * \n * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_set_character_set(const character_set& charset, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_set_character_set_t) { return async_set_character_set(charset, impl_.shared_diag(), std::forward(token)); } /// \copydoc async_set_character_set template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_set_character_set( const character_set& charset, diagnostics& diag, CompletionToken&& token = {} ) BOOST_MYSQL_RETURN_TYPE(detail::async_set_character_set_t) { return impl_.async_run( detail::set_character_set_algo_params{charset}, diag, std::forward(token) ); } /** * \brief Checks whether the server is alive. * \details * If the server is alive, this function will complete without error. * If it's not, it will fail with the relevant network or protocol error. * \n * Note that ping requests are treated as any other type of request at the protocol * level, and won't be prioritized anyhow by the server. If the server is stuck * in a long-running query, the ping request won't be answered until the query is * finished. */ void ping(error_code& err, diagnostics& diag) { impl_.run(detail::ping_algo_params{}, err, diag); } /// \copydoc ping void ping() { error_code err; diagnostics diag; ping(err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc ping * \details * \n * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_ping(CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_ping_t) { return async_ping(impl_.shared_diag(), std::forward(token)); } /// \copydoc async_ping template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_ping(diagnostics& diag, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_ping_t) { return impl_.async_run(detail::ping_algo_params{}, diag, std::forward(token)); } /** * \brief Resets server-side session state, like variables and prepared statements. * \details * Resets all server-side state for the current session: * \n * \li Rolls back any active transactions and resets autocommit mode. * \li Releases all table locks. * \li Drops all temporary tables. * \li Resets all session system variables to their default values (including the ones set by `SET * NAMES`) and clears all user-defined variables. * \li Closes all prepared statements. * \n * A full reference on the affected session state can be found * here. * \n * \n * This function will not reset the current physical connection and won't cause re-authentication. * It is faster than closing and re-opening a connection. * \n * The connection must be connected and authenticated before calling this function. * This function involves communication with the server, and thus may fail. * * \par Warning on character sets * This function will restore the connection's character set and collation **to the server's default**, * and not to the one specified during connection establishment. Some servers have `latin1` as their * default character set, which is not usually what you want. Since there is no way to know this * character set, \ref current_character_set will return `nullptr` after the operation succeeds. * We recommend always using \ref set_character_set or \ref async_set_character_set after calling this * function. * \n * You can find the character set that your server will use after the reset by running: * \code * "SELECT @@global.character_set_client, @@global.character_set_results;" * \endcode */ void reset_connection(error_code& err, diagnostics& diag) { impl_.run(detail::reset_connection_algo_params{}, err, diag); } /// \copydoc reset_connection void reset_connection() { error_code err; diagnostics diag; reset_connection(err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc reset_connection * \details * \n * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_reset_connection(CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_reset_connection_t) { return async_reset_connection(impl_.shared_diag(), std::forward(token)); } /// \copydoc async_reset_connection template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_reset_connection(diagnostics& diag, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_reset_connection_t) { return impl_ .async_run(detail::reset_connection_algo_params{}, diag, std::forward(token)); } /** * \brief Cleanly closes the connection to the server. * \details * This function does the following: * \n * \li Sends a quit request. This is required by the MySQL protocol, to inform * the server that we're closing the connection gracefully. * \li If the connection is using TLS (`this->uses_ssl() == true`), performs * the TLS shutdown. * \li Closes the transport-level connection (the TCP or UNIX socket). * \n * Since this function involves writing a message to the server, it can fail. * Only use this function if you know that the connection is healthy and you want * to cleanly close it. * \n * If you don't call this function, the destructor or successive connects will * perform a transport-layer close. This doesn't cause any resource leaks, but may * cause warnings to be written to the server logs. */ void close(error_code& err, diagnostics& diag) { impl_.run(detail::close_connection_algo_params{}, err, diag); } /// \copydoc close void close() { error_code err; diagnostics diag; close(err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc close * \details * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) CompletionToken = with_diagnostics_t> auto async_close(CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_close_connection_t) { return async_close(impl_.shared_diag(), std::forward(token)); } /// \copydoc async_close template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) CompletionToken = with_diagnostics_t> auto async_close(diagnostics& diag, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(detail::async_close_connection_t) { return this->impl_ .async_run(detail::close_connection_algo_params{}, diag, std::forward(token)); } /** * \brief Runs a set of pipelined requests. * \details * Runs the pipeline described by `req` and stores its response in `res`. * After the operation completes, `res` will have as many elements as stages * were in `req`, even if the operation fails. * \n * Request stages are seen by the server as a series of unrelated requests. * As a consequence, all stages are always run, even if previous stages fail. * \n * If all stages succeed, the operation completes successfully. Thus, there is no need to check * the per-stage error code in `res` if this operation completed successfully. * \n * If any stage fails with a non-fatal error (as per \ref is_fatal_error), the result of the operation * is the first encountered error. You can check which stages succeeded and which ones didn't by * inspecting each stage in `res`. * \n * If any stage fails with a fatal error, the result of the operation is the fatal error. * Successive stages will be marked as failed with the fatal error. The server may or may * not have processed such stages. */ void run_pipeline( const pipeline_request& req, std::vector& res, error_code& err, diagnostics& diag ) { impl_.run(impl_.make_params_pipeline(req, res), err, diag); } /// \copydoc run_pipeline void run_pipeline(const pipeline_request& req, std::vector& res) { error_code err; diagnostics diag; run_pipeline(req, res, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc run_pipeline * \details * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. * * \par Object lifetimes * The request and response objects must be kept alive and should not be modified * until the operation completes. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) CompletionToken = with_diagnostics_t> auto async_run_pipeline( const pipeline_request& req, std::vector& res, CompletionToken&& token = {} ) BOOST_MYSQL_RETURN_TYPE(detail::async_run_pipeline_t) { return async_run_pipeline(req, res, impl_.shared_diag(), std::forward(token)); } /// \copydoc async_run_pipeline template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) CompletionToken = with_diagnostics_t> auto async_run_pipeline( const pipeline_request& req, std::vector& res, diagnostics& diag, CompletionToken&& token = {} ) BOOST_MYSQL_RETURN_TYPE(detail::async_run_pipeline_t) { return this->impl_ .async_run(impl_.make_params_pipeline(req, res), diag, std::forward(token)); } }; } // namespace mysql } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/bad_field_access.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_BAD_FIELD_ACCESS_HPP #define BOOST_MYSQL_BAD_FIELD_ACCESS_HPP #include namespace boost { namespace mysql { /// Exception type thrown when trying to access a \ref field /// or \ref field_view with an incorrect type. class bad_field_access : public std::exception { public: /// Returns the error message. const char* what() const noexcept override { return "bad_value_access"; } }; } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/blob.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_BLOB_HPP #define BOOST_MYSQL_BLOB_HPP #include namespace boost { namespace mysql { /// Owning type used to represent binary blobs. using blob = std::vector; } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/blob_view.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_BLOB_VIEW_HPP #define BOOST_MYSQL_BLOB_VIEW_HPP #include namespace boost { namespace mysql { /// Non-owning type used to represent binary blobs. using blob_view = boost::span; } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/buffer_params.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_BUFFER_PARAMS_HPP #define BOOST_MYSQL_BUFFER_PARAMS_HPP #include #include #include namespace boost { namespace mysql { /** * \brief (Legacy) Buffer configuration parameters for a connection. * * \par Legacy * This class is used with the legacy \ref connection class. * New code should use \ref any_connection, instead. * The equivalent to `buffer_params` is \ref any_connection_params::initial_buffer_size. */ class buffer_params { std::size_t initial_read_size_; public: /// The default value of \ref initial_read_size. static BOOST_INLINE_CONSTEXPR std::size_t default_initial_read_size = default_initial_read_buffer_size; /** * \brief Initializing constructor. * \param initial_read_size Initial size of the read buffer. A bigger read buffer * can increase the number of rows returned by \ref connection::read_some_rows. */ constexpr explicit buffer_params(std::size_t initial_read_size = default_initial_read_size) noexcept : initial_read_size_(initial_read_size) { } /// Gets the initial size of the read buffer. constexpr std::size_t initial_read_size() const noexcept { return initial_read_size_; } /// Sets the initial size of the read buffer. void set_initial_read_size(std::size_t v) noexcept { initial_read_size_ = v; } }; } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/character_set.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_CHARACTER_SET_HPP #define BOOST_MYSQL_CHARACTER_SET_HPP #include #include #include #include namespace boost { namespace mysql { /** * \brief Represents a MySQL character set. * \details * By default, you should always use \ref utf8mb4_charset, unless there is * a strong reason not to. This struct allows you to extend this library * with character sets that are not supported out of the box. */ struct character_set { /** * \brief The character set name, as a NULL-terminated string. * \details * This should match the character set name in MySQL. This is the string * you specify when issuing `SET NAMES` statements. You can find available * character sets using the `SHOW CHARACTER SET` statement. */ const char* name; /** * \brief Obtains the size of the first character of a string. * \details * Given a range of bytes, `r`, this function must interpret `r` as a * string encoded using this character set, and return the number of * bytes that the first character in the string spans, or 0 in case of error. * `r` is guaranteed to be non-empty (`r.size() > 0`). * \n * In some character sets (like UTF-8), not all byte sequences represent * valid characters. If this function finds an invalid byte sequence while * trying to interpret the first character, it should return 0 to signal the error. * \n * This function must not throw exceptions or have side effects. */ std::size_t (*next_char)(span); }; /// The utf8mb4 character set (the one you should use by default). BOOST_INLINE_CONSTEXPR character_set utf8mb4_charset #ifndef BOOST_MYSQL_DOXYGEN {"utf8mb4", detail::next_char_utf8mb4} #endif ; /// The ascii character set. BOOST_INLINE_CONSTEXPR character_set ascii_charset #ifndef BOOST_MYSQL_DOXYGEN {"ascii", detail::next_char_ascii}; #endif ; /** * \brief Settings required to format SQL queries client-side. * \details * The recommended way to obtain a value of this type is using \ref any_connection::format_opts. */ struct format_options { /// The connection's current character set. character_set charset; /// Whether backslashes represent escape sequences. bool backslash_escapes; }; } // namespace mysql } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/client_errc.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_CLIENT_ERRC_HPP #define BOOST_MYSQL_CLIENT_ERRC_HPP #include #include #include namespace boost { namespace mysql { /** * \brief MySQL client-defined error codes. * \details These errors are produced by the client itself, rather than the server. */ enum class client_errc : int { /** * \brief An incomplete message was received from the server (indicates a deserialization error or * packet mismatch). */ incomplete_message = 1, /** * \brief An unexpected value was found in a server-received message (indicates a deserialization * error or packet mismatch). */ protocol_value_error, /// The server does not support the minimum required capabilities to establish the connection. server_unsupported, /** * \brief Unexpected extra bytes at the end of a message were received (indicates a deserialization * error or packet mismatch). */ extra_bytes, /// Mismatched sequence numbers (usually caused by a packet mismatch). sequence_number_mismatch, /// The user employs an authentication plugin not known to this library. unknown_auth_plugin, /** * \brief (Legacy) The authentication plugin requires the connection to use SSL. * This code is no longer used, since all supported plugins support plaintext connections. */ auth_plugin_requires_ssl, /** * \brief The number of parameters passed to the prepared statement does not match the number of * actual parameters. */ wrong_num_params, /// The connection mandates SSL, but the server doesn't accept SSL connections. server_doesnt_support_ssl, /** * \brief * The static interface detected a mismatch between your C++ type definitions and what the server * returned in the query. */ metadata_check_failed, /** * \brief The static interface detected a mismatch between the number of row types passed to * \ref static_results or \ref static_execution_state and the number of resultsets returned by your query. */ num_resultsets_mismatch, /// The `StaticRow` type passed to read_some_rows does not correspond to the resultset type being read. row_type_mismatch, /// The static interface encountered an error when parsing a field into a C++ data structure. static_row_parsing_error, /** * \brief Getting a connection from a connection_pool was cancelled before * the pool was run. Ensure that you're calling connection_pool::async_run. */ pool_not_running, /** * \brief Getting a connection from a connection_pool failed because the * pool was cancelled. */ pool_cancelled, /** * \brief Getting a connection from a connection_pool was cancelled before * a connection was available. */ no_connection_available, /// An invalid byte sequence was found while trying to decode a string. invalid_encoding, /// A formatting operation could not format one of its arguments. unformattable_value, /// A format string containing invalid syntax was provided to a SQL formatting function. format_string_invalid_syntax, /** * \brief A format string with an invalid byte sequence was provided to a SQL formatting * function. */ format_string_invalid_encoding, /// A format string mixes manual (e.g. {0}) and automatic (e.g. {}) indexing. format_string_manual_auto_mix, /// The supplied format specifier (e.g. {:i}) is not supported by the type being formatted. format_string_invalid_specifier, /** * \brief A format argument referenced by a format string was not found. Check the number * of format arguments passed and their names. */ format_arg_not_found, /** * \brief The character set used by the connection is not known by the client. Use * \ref any_connection::set_character_set or \ref any_connection::async_set_character_set * before invoking operations that require a known charset. */ unknown_character_set, /** * \brief An operation attempted to read or write a packet larger than the maximum buffer * size. Try increasing \ref any_connection_params::max_buffer_size. */ max_buffer_size_exceeded, /** * \brief Another operation is currently in progress for this connection. Make sure * that a single connection does not run two asynchronous operations in parallel. */ operation_in_progress, /** * \brief The requested operation requires an established session. * Call `async_connect` before invoking other operations. */ not_connected, /** * \brief The connection is currently engaged in a multi-function operation. * Finish the current operation by calling `async_read_some_rows` and `async_read_resultset_head` * before starting any other operation. */ engaged_in_multi_function, /** * \brief The operation requires the connection to be engaged in a multi-function operation. * Use `async_start_execution` to start one. */ not_engaged_in_multi_function, /** * \brief During handshake, the server sent a packet type that is not allowed in the current state * (protocol violation). */ bad_handshake_packet_type, /// An OpenSSL function failed and did not provide any extra diagnostics. unknown_openssl_error, }; BOOST_MYSQL_DECL const boost::system::error_category& get_client_category() noexcept; /// Creates an \ref error_code from a \ref client_errc. inline error_code make_error_code(client_errc error) { return error_code(static_cast(error), get_client_category()); } } // namespace mysql #ifndef BOOST_MYSQL_DOXYGEN namespace system { template <> struct is_error_code_enum<::boost::mysql::client_errc> { static constexpr bool value = true; }; } // namespace system #endif } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/column_type.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_COLUMN_TYPE_HPP #define BOOST_MYSQL_COLUMN_TYPE_HPP #include #include namespace boost { namespace mysql { /** * \brief Represents the database type of a MySQL column. * \details This represents a database type, as opposed to \ref field_kind, which represents a * C++ type. *\n * Unless otherwise noted, the names in this enumeration * directly correspond to the names of the types you would use in * a `CREATE TABLE` statement to create a column of this type * (e.g. `VARCHAR` corresponds to \ref column_type::varchar). */ enum class column_type { tinyint, ///< `TINYINT` (signed and unsigned). smallint, ///< `SMALLINT` (signed and unsigned). mediumint, ///< `MEDIUMINT` (signed and unsigned). int_, ///< `INT` (signed and unsigned). bigint, ///< `BIGINT` (signed and unsigned). float_, ///< `FLOAT` (warning: FLOAT(p) where p >= 24 creates a DOUBLE column). double_, ///< `DOUBLE` decimal, ///< `DECIMAL` bit, ///< `BIT` year, ///< `YEAR` time, ///< `TIME` date, ///< `DATE` datetime, ///< `DATETIME` timestamp, ///< `TIMESTAMP` char_, ///< `CHAR` (any length) varchar, ///< `VARCHAR` (any length) binary, ///< `BINARY` (any length) varbinary, ///< `VARBINARY` (any length) text, ///< `TEXT` types (`TINYTEXT`, `MEDIUMTEXT`, `TEXT` and `LONGTEXT`) blob, ///< `BLOB` types (`TINYBLOB`, `MEDIUMBLOB`, `BLOB` and `LONGBLOB`) enum_, ///< `ENUM` set, ///< `SET` json, ///< `JSON` geometry, ///< `GEOMETRY` unknown, ///< None of the known types; maybe a new MySQL type we have no knowledge of. }; /** * \brief Streams a `column_type`. */ BOOST_MYSQL_DECL std::ostream& operator<<(std::ostream& os, column_type t); } // namespace mysql } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/common_server_errc.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_COMMON_SERVER_ERRC_HPP #define BOOST_MYSQL_COMMON_SERVER_ERRC_HPP #include #include #include namespace boost { namespace mysql { /** * \brief Server-defined error codes, shared between MySQL and MariaDB. * \details The numeric value and semantics match the ones described in the MySQL documentation. * For more info, consult the error reference for * MySQL 8.0, * MySQL 5.7, * MariaDB. */ enum class common_server_errc : int { /** * \brief Common server error. Error number: 1000, symbol: * ER_HASHCHK. */ er_hashchk = 1000, /** * \brief Common server error. Error number: 1001, symbol: * ER_NISAMCHK. */ er_nisamchk = 1001, /** * \brief Common server error. Error number: 1002, symbol: * ER_NO. */ er_no = 1002, /** * \brief Common server error. Error number: 1003, symbol: * ER_YES. */ er_yes = 1003, /** * \brief Common server error. Error number: 1004, symbol: * ER_CANT_CREATE_FILE. */ er_cant_create_file = 1004, /** * \brief Common server error. Error number: 1005, symbol: * ER_CANT_CREATE_TABLE. */ er_cant_create_table = 1005, /** * \brief Common server error. Error number: 1006, symbol: * ER_CANT_CREATE_DB. */ er_cant_create_db = 1006, /** * \brief Common server error. Error number: 1007, symbol: * ER_DB_CREATE_EXISTS. */ er_db_create_exists = 1007, /** * \brief Common server error. Error number: 1008, symbol: * ER_DB_DROP_EXISTS. */ er_db_drop_exists = 1008, /** * \brief Common server error. Error number: 1009, symbol: * ER_DB_DROP_DELETE. */ er_db_drop_delete = 1009, /** * \brief Common server error. Error number: 1010, symbol: * ER_DB_DROP_RMDIR. */ er_db_drop_rmdir = 1010, /** * \brief Common server error. Error number: 1011, symbol: * ER_CANT_DELETE_FILE. */ er_cant_delete_file = 1011, /** * \brief Common server error. Error number: 1012, symbol: * ER_CANT_FIND_SYSTEM_REC. */ er_cant_find_system_rec = 1012, /** * \brief Common server error. Error number: 1013, symbol: * ER_CANT_GET_STAT. */ er_cant_get_stat = 1013, /** * \brief Common server error. Error number: 1014, symbol: * ER_CANT_GET_WD. */ er_cant_get_wd = 1014, /** * \brief Common server error. Error number: 1015, symbol: * ER_CANT_LOCK. */ er_cant_lock = 1015, /** * \brief Common server error. Error number: 1016, symbol: * ER_CANT_OPEN_FILE. */ er_cant_open_file = 1016, /** * \brief Common server error. Error number: 1017, symbol: * ER_FILE_NOT_FOUND. */ er_file_not_found = 1017, /** * \brief Common server error. Error number: 1018, symbol: * ER_CANT_READ_DIR. */ er_cant_read_dir = 1018, /** * \brief Common server error. Error number: 1019, symbol: * ER_CANT_SET_WD. */ er_cant_set_wd = 1019, /** * \brief Common server error. Error number: 1020, symbol: * ER_CHECKREAD. */ er_checkread = 1020, /** * \brief Common server error. Error number: 1021, symbol: * ER_DISK_FULL. */ er_disk_full = 1021, /** * \brief Common server error. Error number: 1022, symbol: * ER_DUP_KEY. */ er_dup_key = 1022, /** * \brief Common server error. Error number: 1023, symbol: * ER_ERROR_ON_CLOSE. */ er_error_on_close = 1023, /** * \brief Common server error. Error number: 1024, symbol: * ER_ERROR_ON_READ. */ er_error_on_read = 1024, /** * \brief Common server error. Error number: 1025, symbol: * ER_ERROR_ON_RENAME. */ er_error_on_rename = 1025, /** * \brief Common server error. Error number: 1026, symbol: * ER_ERROR_ON_WRITE. */ er_error_on_write = 1026, /** * \brief Common server error. Error number: 1027, symbol: * ER_FILE_USED. */ er_file_used = 1027, /** * \brief Common server error. Error number: 1028, symbol: * ER_FILSORT_ABORT. */ er_filsort_abort = 1028, /** * \brief Common server error. Error number: 1029, symbol: * ER_FORM_NOT_FOUND. */ er_form_not_found = 1029, /** * \brief Common server error. Error number: 1030, symbol: * ER_GET_ERRNO. */ er_get_errno = 1030, /** * \brief Common server error. Error number: 1031, symbol: * ER_ILLEGAL_HA. */ er_illegal_ha = 1031, /** * \brief Common server error. Error number: 1032, symbol: * ER_KEY_NOT_FOUND. */ er_key_not_found = 1032, /** * \brief Common server error. Error number: 1033, symbol: * ER_NOT_FORM_FILE. */ er_not_form_file = 1033, /** * \brief Common server error. Error number: 1034, symbol: * ER_NOT_KEYFILE. */ er_not_keyfile = 1034, /** * \brief Common server error. Error number: 1035, symbol: * ER_OLD_KEYFILE. */ er_old_keyfile = 1035, /** * \brief Common server error. Error number: 1036, symbol: * ER_OPEN_AS_READONLY. */ er_open_as_readonly = 1036, /** * \brief Common server error. Error number: 1037, symbol: * ER_OUTOFMEMORY. */ er_outofmemory = 1037, /** * \brief Common server error. Error number: 1038, symbol: * ER_OUT_OF_SORTMEMORY. */ er_out_of_sortmemory = 1038, /** * \brief Common server error. Error number: 1039, symbol: * ER_UNEXPECTED_EOF. */ er_unexpected_eof = 1039, /** * \brief Common server error. Error number: 1040, symbol: * ER_CON_COUNT_ERROR. */ er_con_count_error = 1040, /** * \brief Common server error. Error number: 1041, symbol: * ER_OUT_OF_RESOURCES. */ er_out_of_resources = 1041, /** * \brief Common server error. Error number: 1042, symbol: * ER_BAD_HOST_ERROR. */ er_bad_host_error = 1042, /** * \brief Common server error. Error number: 1043, symbol: * ER_HANDSHAKE_ERROR. */ er_handshake_error = 1043, /** * \brief Common server error. Error number: 1044, symbol: * ER_DBACCESS_DENIED_ERROR. */ er_dbaccess_denied_error = 1044, /** * \brief Common server error. Error number: 1045, symbol: * ER_ACCESS_DENIED_ERROR. */ er_access_denied_error = 1045, /** * \brief Common server error. Error number: 1046, symbol: * ER_NO_DB_ERROR. */ er_no_db_error = 1046, /** * \brief Common server error. Error number: 1047, symbol: * ER_UNKNOWN_COM_ERROR. */ er_unknown_com_error = 1047, /** * \brief Common server error. Error number: 1048, symbol: * ER_BAD_NULL_ERROR. */ er_bad_null_error = 1048, /** * \brief Common server error. Error number: 1049, symbol: * ER_BAD_DB_ERROR. */ er_bad_db_error = 1049, /** * \brief Common server error. Error number: 1050, symbol: * ER_TABLE_EXISTS_ERROR. */ er_table_exists_error = 1050, /** * \brief Common server error. Error number: 1051, symbol: * ER_BAD_TABLE_ERROR. */ er_bad_table_error = 1051, /** * \brief Common server error. Error number: 1052, symbol: * ER_NON_UNIQ_ERROR. */ er_non_uniq_error = 1052, /** * \brief Common server error. Error number: 1053, symbol: * ER_SERVER_SHUTDOWN. */ er_server_shutdown = 1053, /** * \brief Common server error. Error number: 1054, symbol: * ER_BAD_FIELD_ERROR. */ er_bad_field_error = 1054, /** * \brief Common server error. Error number: 1055, symbol: * ER_WRONG_FIELD_WITH_GROUP. */ er_wrong_field_with_group = 1055, /** * \brief Common server error. Error number: 1056, symbol: * ER_WRONG_GROUP_FIELD. */ er_wrong_group_field = 1056, /** * \brief Common server error. Error number: 1057, symbol: * ER_WRONG_SUM_SELECT. */ er_wrong_sum_select = 1057, /** * \brief Common server error. Error number: 1058, symbol: * ER_WRONG_VALUE_COUNT. */ er_wrong_value_count = 1058, /** * \brief Common server error. Error number: 1059, symbol: * ER_TOO_LONG_IDENT. */ er_too_long_ident = 1059, /** * \brief Common server error. Error number: 1060, symbol: * ER_DUP_FIELDNAME. */ er_dup_fieldname = 1060, /** * \brief Common server error. Error number: 1061, symbol: * ER_DUP_KEYNAME. */ er_dup_keyname = 1061, /** * \brief Common server error. Error number: 1062, symbol: * ER_DUP_ENTRY. */ er_dup_entry = 1062, /** * \brief Common server error. Error number: 1063, symbol: * ER_WRONG_FIELD_SPEC. */ er_wrong_field_spec = 1063, /** * \brief Common server error. Error number: 1064, symbol: * ER_PARSE_ERROR. */ er_parse_error = 1064, /** * \brief Common server error. Error number: 1065, symbol: * ER_EMPTY_QUERY. */ er_empty_query = 1065, /** * \brief Common server error. Error number: 1066, symbol: * ER_NONUNIQ_TABLE. */ er_nonuniq_table = 1066, /** * \brief Common server error. Error number: 1067, symbol: * ER_INVALID_DEFAULT. */ er_invalid_default = 1067, /** * \brief Common server error. Error number: 1068, symbol: * ER_MULTIPLE_PRI_KEY. */ er_multiple_pri_key = 1068, /** * \brief Common server error. Error number: 1069, symbol: * ER_TOO_MANY_KEYS. */ er_too_many_keys = 1069, /** * \brief Common server error. Error number: 1070, symbol: * ER_TOO_MANY_KEY_PARTS. */ er_too_many_key_parts = 1070, /** * \brief Common server error. Error number: 1071, symbol: * ER_TOO_LONG_KEY. */ er_too_long_key = 1071, /** * \brief Common server error. Error number: 1072, symbol: * ER_KEY_COLUMN_DOES_NOT_EXITS. */ er_key_column_does_not_exits = 1072, /** * \brief Common server error. Error number: 1073, symbol: * ER_BLOB_USED_AS_KEY. */ er_blob_used_as_key = 1073, /** * \brief Common server error. Error number: 1074, symbol: * ER_TOO_BIG_FIELDLENGTH. */ er_too_big_fieldlength = 1074, /** * \brief Common server error. Error number: 1075, symbol: * ER_WRONG_AUTO_KEY. */ er_wrong_auto_key = 1075, /** * \brief Common server error. Error number: 1077, symbol: * ER_NORMAL_SHUTDOWN. */ er_normal_shutdown = 1077, /** * \brief Common server error. Error number: 1078, symbol: * ER_GOT_SIGNAL. */ er_got_signal = 1078, /** * \brief Common server error. Error number: 1079, symbol: * ER_SHUTDOWN_COMPLETE. */ er_shutdown_complete = 1079, /** * \brief Common server error. Error number: 1080, symbol: * ER_FORCING_CLOSE. */ er_forcing_close = 1080, /** * \brief Common server error. Error number: 1081, symbol: * ER_IPSOCK_ERROR. */ er_ipsock_error = 1081, /** * \brief Common server error. Error number: 1082, symbol: * ER_NO_SUCH_INDEX. */ er_no_such_index = 1082, /** * \brief Common server error. Error number: 1083, symbol: * ER_WRONG_FIELD_TERMINATORS. */ er_wrong_field_terminators = 1083, /** * \brief Common server error. Error number: 1084, symbol: * ER_BLOBS_AND_NO_TERMINATED. */ er_blobs_and_no_terminated = 1084, /** * \brief Common server error. Error number: 1085, symbol: * ER_TEXTFILE_NOT_READABLE. */ er_textfile_not_readable = 1085, /** * \brief Common server error. Error number: 1086, symbol: * ER_FILE_EXISTS_ERROR. */ er_file_exists_error = 1086, /** * \brief Common server error. Error number: 1087, symbol: * ER_LOAD_INFO. */ er_load_info = 1087, /** * \brief Common server error. Error number: 1088, symbol: * ER_ALTER_INFO. */ er_alter_info = 1088, /** * \brief Common server error. Error number: 1089, symbol: * ER_WRONG_SUB_KEY. */ er_wrong_sub_key = 1089, /** * \brief Common server error. Error number: 1090, symbol: * ER_CANT_REMOVE_ALL_FIELDS. */ er_cant_remove_all_fields = 1090, /** * \brief Common server error. Error number: 1091, symbol: * ER_CANT_DROP_FIELD_OR_KEY. */ er_cant_drop_field_or_key = 1091, /** * \brief Common server error. Error number: 1092, symbol: * ER_INSERT_INFO. */ er_insert_info = 1092, /** * \brief Common server error. Error number: 1093, symbol: * ER_UPDATE_TABLE_USED. */ er_update_table_used = 1093, /** * \brief Common server error. Error number: 1094, symbol: * ER_NO_SUCH_THREAD. */ er_no_such_thread = 1094, /** * \brief Common server error. Error number: 1095, symbol: * ER_KILL_DENIED_ERROR. */ er_kill_denied_error = 1095, /** * \brief Common server error. Error number: 1096, symbol: * ER_NO_TABLES_USED. */ er_no_tables_used = 1096, /** * \brief Common server error. Error number: 1097, symbol: * ER_TOO_BIG_SET. */ er_too_big_set = 1097, /** * \brief Common server error. Error number: 1098, symbol: * ER_NO_UNIQUE_LOGFILE. */ er_no_unique_logfile = 1098, /** * \brief Common server error. Error number: 1099, symbol: * ER_TABLE_NOT_LOCKED_FOR_WRITE. */ er_table_not_locked_for_write = 1099, /** * \brief Common server error. Error number: 1100, symbol: * ER_TABLE_NOT_LOCKED. */ er_table_not_locked = 1100, /** * \brief Common server error. Error number: 1102, symbol: * ER_WRONG_DB_NAME. */ er_wrong_db_name = 1102, /** * \brief Common server error. Error number: 1103, symbol: * ER_WRONG_TABLE_NAME. */ er_wrong_table_name = 1103, /** * \brief Common server error. Error number: 1104, symbol: * ER_TOO_BIG_SELECT. */ er_too_big_select = 1104, /** * \brief Common server error. Error number: 1105, symbol: * ER_UNKNOWN_ERROR. */ er_unknown_error = 1105, /** * \brief Common server error. Error number: 1106, symbol: * ER_UNKNOWN_PROCEDURE. */ er_unknown_procedure = 1106, /** * \brief Common server error. Error number: 1107, symbol: * ER_WRONG_PARAMCOUNT_TO_PROCEDURE. */ er_wrong_paramcount_to_procedure = 1107, /** * \brief Common server error. Error number: 1108, symbol: * ER_WRONG_PARAMETERS_TO_PROCEDURE. */ er_wrong_parameters_to_procedure = 1108, /** * \brief Common server error. Error number: 1109, symbol: * ER_UNKNOWN_TABLE. */ er_unknown_table = 1109, /** * \brief Common server error. Error number: 1110, symbol: * ER_FIELD_SPECIFIED_TWICE. */ er_field_specified_twice = 1110, /** * \brief Common server error. Error number: 1111, symbol: * ER_INVALID_GROUP_FUNC_USE. */ er_invalid_group_func_use = 1111, /** * \brief Common server error. Error number: 1112, symbol: * ER_UNSUPPORTED_EXTENSION. */ er_unsupported_extension = 1112, /** * \brief Common server error. Error number: 1113, symbol: * ER_TABLE_MUST_HAVE_COLUMNS. */ er_table_must_have_columns = 1113, /** * \brief Common server error. Error number: 1114, symbol: * ER_RECORD_FILE_FULL. */ er_record_file_full = 1114, /** * \brief Common server error. Error number: 1115, symbol: * ER_UNKNOWN_CHARACTER_SET. */ er_unknown_character_set = 1115, /** * \brief Common server error. Error number: 1116, symbol: * ER_TOO_MANY_TABLES. */ er_too_many_tables = 1116, /** * \brief Common server error. Error number: 1117, symbol: * ER_TOO_MANY_FIELDS. */ er_too_many_fields = 1117, /** * \brief Common server error. Error number: 1118, symbol: * ER_TOO_BIG_ROWSIZE. */ er_too_big_rowsize = 1118, /** * \brief Common server error. Error number: 1119, symbol: * ER_STACK_OVERRUN. */ er_stack_overrun = 1119, /** * \brief Common server error. Error number: 1121, symbol: * ER_NULL_COLUMN_IN_INDEX. */ er_null_column_in_index = 1121, /** * \brief Common server error. Error number: 1122, symbol: * ER_CANT_FIND_UDF. */ er_cant_find_udf = 1122, /** * \brief Common server error. Error number: 1123, symbol: * ER_CANT_INITIALIZE_UDF. */ er_cant_initialize_udf = 1123, /** * \brief Common server error. Error number: 1124, symbol: * ER_UDF_NO_PATHS. */ er_udf_no_paths = 1124, /** * \brief Common server error. Error number: 1125, symbol: * ER_UDF_EXISTS. */ er_udf_exists = 1125, /** * \brief Common server error. Error number: 1126, symbol: * ER_CANT_OPEN_LIBRARY. */ er_cant_open_library = 1126, /** * \brief Common server error. Error number: 1127, symbol: * ER_CANT_FIND_DL_ENTRY. */ er_cant_find_dl_entry = 1127, /** * \brief Common server error. Error number: 1128, symbol: * ER_FUNCTION_NOT_DEFINED. */ er_function_not_defined = 1128, /** * \brief Common server error. Error number: 1129, symbol: * ER_HOST_IS_BLOCKED. */ er_host_is_blocked = 1129, /** * \brief Common server error. Error number: 1130, symbol: * ER_HOST_NOT_PRIVILEGED. */ er_host_not_privileged = 1130, /** * \brief Common server error. Error number: 1131, symbol: * ER_PASSWORD_ANONYMOUS_USER. */ er_password_anonymous_user = 1131, /** * \brief Common server error. Error number: 1132, symbol: * ER_PASSWORD_NOT_ALLOWED. */ er_password_not_allowed = 1132, /** * \brief Common server error. Error number: 1133, symbol: * ER_PASSWORD_NO_MATCH. */ er_password_no_match = 1133, /** * \brief Common server error. Error number: 1134, symbol: * ER_UPDATE_INFO. */ er_update_info = 1134, /** * \brief Common server error. Error number: 1135, symbol: * ER_CANT_CREATE_THREAD. */ er_cant_create_thread = 1135, /** * \brief Common server error. Error number: 1136, symbol: * ER_WRONG_VALUE_COUNT_ON_ROW. */ er_wrong_value_count_on_row = 1136, /** * \brief Common server error. Error number: 1137, symbol: * ER_CANT_REOPEN_TABLE. */ er_cant_reopen_table = 1137, /** * \brief Common server error. Error number: 1138, symbol: * ER_INVALID_USE_OF_NULL. */ er_invalid_use_of_null = 1138, /** * \brief Common server error. Error number: 1139, symbol: * ER_REGEXP_ERROR. */ er_regexp_error = 1139, /** * \brief Common server error. Error number: 1140, symbol: * ER_MIX_OF_GROUP_FUNC_AND_FIELDS. */ er_mix_of_group_func_and_fields = 1140, /** * \brief Common server error. Error number: 1141, symbol: * ER_NONEXISTING_GRANT. */ er_nonexisting_grant = 1141, /** * \brief Common server error. Error number: 1142, symbol: * ER_TABLEACCESS_DENIED_ERROR. */ er_tableaccess_denied_error = 1142, /** * \brief Common server error. Error number: 1143, symbol: * ER_COLUMNACCESS_DENIED_ERROR. */ er_columnaccess_denied_error = 1143, /** * \brief Common server error. Error number: 1144, symbol: * ER_ILLEGAL_GRANT_FOR_TABLE. */ er_illegal_grant_for_table = 1144, /** * \brief Common server error. Error number: 1145, symbol: * ER_GRANT_WRONG_HOST_OR_USER. */ er_grant_wrong_host_or_user = 1145, /** * \brief Common server error. Error number: 1146, symbol: * ER_NO_SUCH_TABLE. */ er_no_such_table = 1146, /** * \brief Common server error. Error number: 1147, symbol: * ER_NONEXISTING_TABLE_GRANT. */ er_nonexisting_table_grant = 1147, /** * \brief Common server error. Error number: 1148, symbol: * ER_NOT_ALLOWED_COMMAND. */ er_not_allowed_command = 1148, /** * \brief Common server error. Error number: 1149, symbol: * ER_SYNTAX_ERROR. */ er_syntax_error = 1149, /** * \brief Common server error. Error number: 1152, symbol: * ER_ABORTING_CONNECTION. */ er_aborting_connection = 1152, /** * \brief Common server error. Error number: 1153, symbol: * ER_NET_PACKET_TOO_LARGE. */ er_net_packet_too_large = 1153, /** * \brief Common server error. Error number: 1154, symbol: * ER_NET_READ_ERROR_FROM_PIPE. */ er_net_read_error_from_pipe = 1154, /** * \brief Common server error. Error number: 1155, symbol: * ER_NET_FCNTL_ERROR. */ er_net_fcntl_error = 1155, /** * \brief Common server error. Error number: 1156, symbol: * ER_NET_PACKETS_OUT_OF_ORDER. */ er_net_packets_out_of_order = 1156, /** * \brief Common server error. Error number: 1157, symbol: * ER_NET_UNCOMPRESS_ERROR. */ er_net_uncompress_error = 1157, /** * \brief Common server error. Error number: 1158, symbol: * ER_NET_READ_ERROR. */ er_net_read_error = 1158, /** * \brief Common server error. Error number: 1159, symbol: * ER_NET_READ_INTERRUPTED. */ er_net_read_interrupted = 1159, /** * \brief Common server error. Error number: 1160, symbol: * ER_NET_ERROR_ON_WRITE. */ er_net_error_on_write = 1160, /** * \brief Common server error. Error number: 1161, symbol: * ER_NET_WRITE_INTERRUPTED. */ er_net_write_interrupted = 1161, /** * \brief Common server error. Error number: 1162, symbol: * ER_TOO_LONG_STRING. */ er_too_long_string = 1162, /** * \brief Common server error. Error number: 1163, symbol: * ER_TABLE_CANT_HANDLE_BLOB. */ er_table_cant_handle_blob = 1163, /** * \brief Common server error. Error number: 1164, symbol: * ER_TABLE_CANT_HANDLE_AUTO_INCREMENT. */ er_table_cant_handle_auto_increment = 1164, /** * \brief Common server error. Error number: 1166, symbol: * ER_WRONG_COLUMN_NAME. */ er_wrong_column_name = 1166, /** * \brief Common server error. Error number: 1167, symbol: * ER_WRONG_KEY_COLUMN. */ er_wrong_key_column = 1167, /** * \brief Common server error. Error number: 1168, symbol: * ER_WRONG_MRG_TABLE. */ er_wrong_mrg_table = 1168, /** * \brief Common server error. Error number: 1169, symbol: * ER_DUP_UNIQUE. */ er_dup_unique = 1169, /** * \brief Common server error. Error number: 1170, symbol: * ER_BLOB_KEY_WITHOUT_LENGTH. */ er_blob_key_without_length = 1170, /** * \brief Common server error. Error number: 1171, symbol: * ER_PRIMARY_CANT_HAVE_NULL. */ er_primary_cant_have_null = 1171, /** * \brief Common server error. Error number: 1172, symbol: * ER_TOO_MANY_ROWS. */ er_too_many_rows = 1172, /** * \brief Common server error. Error number: 1173, symbol: * ER_REQUIRES_PRIMARY_KEY. */ er_requires_primary_key = 1173, /** * \brief Common server error. Error number: 1174, symbol: * ER_NO_RAID_COMPILED. */ er_no_raid_compiled = 1174, /** * \brief Common server error. Error number: 1175, symbol: * ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE. */ er_update_without_key_in_safe_mode = 1175, /** * \brief Common server error. Error number: 1177, symbol: * ER_CHECK_NO_SUCH_TABLE. */ er_check_no_such_table = 1177, /** * \brief Common server error. Error number: 1178, symbol: * ER_CHECK_NOT_IMPLEMENTED. */ er_check_not_implemented = 1178, /** * \brief Common server error. Error number: 1179, symbol: * ER_CANT_DO_THIS_DURING_AN_TRANSACTION. */ er_cant_do_this_during_an_transaction = 1179, /** * \brief Common server error. Error number: 1180, symbol: * ER_ERROR_DURING_COMMIT. */ er_error_during_commit = 1180, /** * \brief Common server error. Error number: 1181, symbol: * ER_ERROR_DURING_ROLLBACK. */ er_error_during_rollback = 1181, /** * \brief Common server error. Error number: 1182, symbol: * ER_ERROR_DURING_FLUSH_LOGS. */ er_error_during_flush_logs = 1182, /** * \brief Common server error. Error number: 1183, symbol: * ER_ERROR_DURING_CHECKPOINT. */ er_error_during_checkpoint = 1183, /** * \brief Common server error. Error number: 1184, symbol: * ER_NEW_ABORTING_CONNECTION. */ er_new_aborting_connection = 1184, /** * \brief Common server error. Error number: 1186, symbol: * ER_FLUSH_MASTER_BINLOG_CLOSED. */ er_flush_master_binlog_closed = 1186, /** * \brief Common server error. Error number: 1187, symbol: * ER_INDEX_REBUILD. */ er_index_rebuild = 1187, /** * \brief Common server error. Error number: 1188, symbol: * ER_MASTER. */ er_master = 1188, /** * \brief Common server error. Error number: 1189, symbol: * ER_MASTER_NET_READ. */ er_master_net_read = 1189, /** * \brief Common server error. Error number: 1190, symbol: * ER_MASTER_NET_WRITE. */ er_master_net_write = 1190, /** * \brief Common server error. Error number: 1191, symbol: * ER_FT_MATCHING_KEY_NOT_FOUND. */ er_ft_matching_key_not_found = 1191, /** * \brief Common server error. Error number: 1192, symbol: * ER_LOCK_OR_ACTIVE_TRANSACTION. */ er_lock_or_active_transaction = 1192, /** * \brief Common server error. Error number: 1193, symbol: * ER_UNKNOWN_SYSTEM_VARIABLE. */ er_unknown_system_variable = 1193, /** * \brief Common server error. Error number: 1194, symbol: * ER_CRASHED_ON_USAGE. */ er_crashed_on_usage = 1194, /** * \brief Common server error. Error number: 1195, symbol: * ER_CRASHED_ON_REPAIR. */ er_crashed_on_repair = 1195, /** * \brief Common server error. Error number: 1196, symbol: * ER_WARNING_NOT_COMPLETE_ROLLBACK. */ er_warning_not_complete_rollback = 1196, /** * \brief Common server error. Error number: 1197, symbol: * ER_TRANS_CACHE_FULL. */ er_trans_cache_full = 1197, /** * \brief Common server error. Error number: 1198, symbol: * ER_SLAVE_MUST_STOP. */ er_slave_must_stop = 1198, /** * \brief Common server error. Error number: 1199, symbol: * ER_SLAVE_NOT_RUNNING. */ er_slave_not_running = 1199, /** * \brief Common server error. Error number: 1200, symbol: * ER_BAD_SLAVE. */ er_bad_slave = 1200, /** * \brief Common server error. Error number: 1201, symbol: * ER_MASTER_INFO. */ er_master_info = 1201, /** * \brief Common server error. Error number: 1202, symbol: * ER_SLAVE_THREAD. */ er_slave_thread = 1202, /** * \brief Common server error. Error number: 1203, symbol: * ER_TOO_MANY_USER_CONNECTIONS. */ er_too_many_user_connections = 1203, /** * \brief Common server error. Error number: 1204, symbol: * ER_SET_CONSTANTS_ONLY. */ er_set_constants_only = 1204, /** * \brief Common server error. Error number: 1205, symbol: * ER_LOCK_WAIT_TIMEOUT. */ er_lock_wait_timeout = 1205, /** * \brief Common server error. Error number: 1206, symbol: * ER_LOCK_TABLE_FULL. */ er_lock_table_full = 1206, /** * \brief Common server error. Error number: 1207, symbol: * ER_READ_ONLY_TRANSACTION. */ er_read_only_transaction = 1207, /** * \brief Common server error. Error number: 1208, symbol: * ER_DROP_DB_WITH_READ_LOCK. */ er_drop_db_with_read_lock = 1208, /** * \brief Common server error. Error number: 1209, symbol: * ER_CREATE_DB_WITH_READ_LOCK. */ er_create_db_with_read_lock = 1209, /** * \brief Common server error. Error number: 1210, symbol: * ER_WRONG_ARGUMENTS. */ er_wrong_arguments = 1210, /** * \brief Common server error. Error number: 1211, symbol: * ER_NO_PERMISSION_TO_CREATE_USER. */ er_no_permission_to_create_user = 1211, /** * \brief Common server error. Error number: 1212, symbol: * ER_UNION_TABLES_IN_DIFFERENT_DIR. */ er_union_tables_in_different_dir = 1212, /** * \brief Common server error. Error number: 1213, symbol: * ER_LOCK_DEADLOCK. */ er_lock_deadlock = 1213, /** * \brief Common server error. Error number: 1214, symbol: * ER_TABLE_CANT_HANDLE_FT. */ er_table_cant_handle_ft = 1214, /** * \brief Common server error. Error number: 1215, symbol: * ER_CANNOT_ADD_FOREIGN. */ er_cannot_add_foreign = 1215, /** * \brief Common server error. Error number: 1216, symbol: * ER_NO_REFERENCED_ROW. */ er_no_referenced_row = 1216, /** * \brief Common server error. Error number: 1217, symbol: * ER_ROW_IS_REFERENCED. */ er_row_is_referenced = 1217, /** * \brief Common server error. Error number: 1218, symbol: * ER_CONNECT_TO_MASTER. */ er_connect_to_master = 1218, /** * \brief Common server error. Error number: 1219, symbol: * ER_QUERY_ON_MASTER. */ er_query_on_master = 1219, /** * \brief Common server error. Error number: 1220, symbol: * ER_ERROR_WHEN_EXECUTING_COMMAND. */ er_error_when_executing_command = 1220, /** * \brief Common server error. Error number: 1221, symbol: * ER_WRONG_USAGE. */ er_wrong_usage = 1221, /** * \brief Common server error. Error number: 1222, symbol: * ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT. */ er_wrong_number_of_columns_in_select = 1222, /** * \brief Common server error. Error number: 1223, symbol: * ER_CANT_UPDATE_WITH_READLOCK. */ er_cant_update_with_readlock = 1223, /** * \brief Common server error. Error number: 1224, symbol: * ER_MIXING_NOT_ALLOWED. */ er_mixing_not_allowed = 1224, /** * \brief Common server error. Error number: 1225, symbol: * ER_DUP_ARGUMENT. */ er_dup_argument = 1225, /** * \brief Common server error. Error number: 1226, symbol: * ER_USER_LIMIT_REACHED. */ er_user_limit_reached = 1226, /** * \brief Common server error. Error number: 1227, symbol: * ER_SPECIFIC_ACCESS_DENIED_ERROR. */ er_specific_access_denied_error = 1227, /** * \brief Common server error. Error number: 1228, symbol: * ER_LOCAL_VARIABLE. */ er_local_variable = 1228, /** * \brief Common server error. Error number: 1229, symbol: * ER_GLOBAL_VARIABLE. */ er_global_variable = 1229, /** * \brief Common server error. Error number: 1230, symbol: * ER_NO_DEFAULT. */ er_no_default = 1230, /** * \brief Common server error. Error number: 1231, symbol: * ER_WRONG_VALUE_FOR_VAR. */ er_wrong_value_for_var = 1231, /** * \brief Common server error. Error number: 1232, symbol: * ER_WRONG_TYPE_FOR_VAR. */ er_wrong_type_for_var = 1232, /** * \brief Common server error. Error number: 1233, symbol: * ER_VAR_CANT_BE_READ. */ er_var_cant_be_read = 1233, /** * \brief Common server error. Error number: 1234, symbol: * ER_CANT_USE_OPTION_HERE. */ er_cant_use_option_here = 1234, /** * \brief Common server error. Error number: 1235, symbol: * ER_NOT_SUPPORTED_YET. */ er_not_supported_yet = 1235, /** * \brief Common server error. Error number: 1236, symbol: * ER_MASTER_FATAL_ERROR_READING_BINLOG. */ er_master_fatal_error_reading_binlog = 1236, /** * \brief Common server error. Error number: 1237, symbol: * ER_SLAVE_IGNORED_TABLE. */ er_slave_ignored_table = 1237, /** * \brief Common server error. Error number: 1238, symbol: * ER_INCORRECT_GLOBAL_LOCAL_VAR. */ er_incorrect_global_local_var = 1238, /** * \brief Common server error. Error number: 1239, symbol: * ER_WRONG_FK_DEF. */ er_wrong_fk_def = 1239, /** * \brief Common server error. Error number: 1240, symbol: * ER_KEY_REF_DO_NOT_MATCH_TABLE_REF. */ er_key_ref_do_not_match_table_ref = 1240, /** * \brief Common server error. Error number: 1241, symbol: * ER_OPERAND_COLUMNS. */ er_operand_columns = 1241, /** * \brief Common server error. Error number: 1242, symbol: * ER_SUBQUERY_NO_1_ROW. */ er_subquery_no_1_row = 1242, /** * \brief Common server error. Error number: 1243, symbol: * ER_UNKNOWN_STMT_HANDLER. */ er_unknown_stmt_handler = 1243, /** * \brief Common server error. Error number: 1244, symbol: * ER_CORRUPT_HELP_DB. */ er_corrupt_help_db = 1244, /** * \brief Common server error. Error number: 1245, symbol: * ER_CYCLIC_REFERENCE. */ er_cyclic_reference = 1245, /** * \brief Common server error. Error number: 1246, symbol: * ER_AUTO_CONVERT. */ er_auto_convert = 1246, /** * \brief Common server error. Error number: 1247, symbol: * ER_ILLEGAL_REFERENCE. */ er_illegal_reference = 1247, /** * \brief Common server error. Error number: 1248, symbol: * ER_DERIVED_MUST_HAVE_ALIAS. */ er_derived_must_have_alias = 1248, /** * \brief Common server error. Error number: 1249, symbol: * ER_SELECT_REDUCED. */ er_select_reduced = 1249, /** * \brief Common server error. Error number: 1250, symbol: * ER_TABLENAME_NOT_ALLOWED_HERE. */ er_tablename_not_allowed_here = 1250, /** * \brief Common server error. Error number: 1251, symbol: * ER_NOT_SUPPORTED_AUTH_MODE. */ er_not_supported_auth_mode = 1251, /** * \brief Common server error. Error number: 1252, symbol: * ER_SPATIAL_CANT_HAVE_NULL. */ er_spatial_cant_have_null = 1252, /** * \brief Common server error. Error number: 1253, symbol: * ER_COLLATION_CHARSET_MISMATCH. */ er_collation_charset_mismatch = 1253, /** * \brief Common server error. Error number: 1254, symbol: * ER_SLAVE_WAS_RUNNING. */ er_slave_was_running = 1254, /** * \brief Common server error. Error number: 1255, symbol: * ER_SLAVE_WAS_NOT_RUNNING. */ er_slave_was_not_running = 1255, /** * \brief Common server error. Error number: 1256, symbol: * ER_TOO_BIG_FOR_UNCOMPRESS. */ er_too_big_for_uncompress = 1256, /** * \brief Common server error. Error number: 1257, symbol: * ER_ZLIB_Z_MEM_ERROR. */ er_zlib_z_mem_error = 1257, /** * \brief Common server error. Error number: 1258, symbol: * ER_ZLIB_Z_BUF_ERROR. */ er_zlib_z_buf_error = 1258, /** * \brief Common server error. Error number: 1259, symbol: * ER_ZLIB_Z_DATA_ERROR. */ er_zlib_z_data_error = 1259, /** * \brief Common server error. Error number: 1260, symbol: * ER_CUT_VALUE_GROUP_CONCAT. */ er_cut_value_group_concat = 1260, /** * \brief Common server error. Error number: 1261, symbol: * ER_WARN_TOO_FEW_RECORDS. */ er_warn_too_few_records = 1261, /** * \brief Common server error. Error number: 1262, symbol: * ER_WARN_TOO_MANY_RECORDS. */ er_warn_too_many_records = 1262, /** * \brief Common server error. Error number: 1263, symbol: * ER_WARN_NULL_TO_NOTNULL. */ er_warn_null_to_notnull = 1263, /** * \brief Common server error. Error number: 1264, symbol: * ER_WARN_DATA_OUT_OF_RANGE. */ er_warn_data_out_of_range = 1264, /** * \brief Common server error. Error number: 1265, symbol: * WARN_DATA_TRUNCATED. */ warn_data_truncated = 1265, /** * \brief Common server error. Error number: 1266, symbol: * ER_WARN_USING_OTHER_HANDLER. */ er_warn_using_other_handler = 1266, /** * \brief Common server error. Error number: 1267, symbol: * ER_CANT_AGGREGATE_2COLLATIONS. */ er_cant_aggregate_2collations = 1267, /** * \brief Common server error. Error number: 1268, symbol: * ER_DROP_USER. */ er_drop_user = 1268, /** * \brief Common server error. Error number: 1269, symbol: * ER_REVOKE_GRANTS. */ er_revoke_grants = 1269, /** * \brief Common server error. Error number: 1270, symbol: * ER_CANT_AGGREGATE_3COLLATIONS. */ er_cant_aggregate_3collations = 1270, /** * \brief Common server error. Error number: 1271, symbol: * ER_CANT_AGGREGATE_NCOLLATIONS. */ er_cant_aggregate_ncollations = 1271, /** * \brief Common server error. Error number: 1272, symbol: * ER_VARIABLE_IS_NOT_STRUCT. */ er_variable_is_not_struct = 1272, /** * \brief Common server error. Error number: 1273, symbol: * ER_UNKNOWN_COLLATION. */ er_unknown_collation = 1273, /** * \brief Common server error. Error number: 1274, symbol: * ER_SLAVE_IGNORED_SSL_PARAMS. */ er_slave_ignored_ssl_params = 1274, /** * \brief Common server error. Error number: 1275, symbol: * ER_SERVER_IS_IN_SECURE_AUTH_MODE. */ er_server_is_in_secure_auth_mode = 1275, /** * \brief Common server error. Error number: 1276, symbol: * ER_WARN_FIELD_RESOLVED. */ er_warn_field_resolved = 1276, /** * \brief Common server error. Error number: 1277, symbol: * ER_BAD_SLAVE_UNTIL_COND. */ er_bad_slave_until_cond = 1277, /** * \brief Common server error. Error number: 1278, symbol: * ER_MISSING_SKIP_SLAVE. */ er_missing_skip_slave = 1278, /** * \brief Common server error. Error number: 1279, symbol: * ER_UNTIL_COND_IGNORED. */ er_until_cond_ignored = 1279, /** * \brief Common server error. Error number: 1280, symbol: * ER_WRONG_NAME_FOR_INDEX. */ er_wrong_name_for_index = 1280, /** * \brief Common server error. Error number: 1281, symbol: * ER_WRONG_NAME_FOR_CATALOG. */ er_wrong_name_for_catalog = 1281, /** * \brief Common server error. Error number: 1282, symbol: * ER_WARN_QC_RESIZE. */ er_warn_qc_resize = 1282, /** * \brief Common server error. Error number: 1283, symbol: * ER_BAD_FT_COLUMN. */ er_bad_ft_column = 1283, /** * \brief Common server error. Error number: 1284, symbol: * ER_UNKNOWN_KEY_CACHE. */ er_unknown_key_cache = 1284, /** * \brief Common server error. Error number: 1285, symbol: * ER_WARN_HOSTNAME_WONT_WORK. */ er_warn_hostname_wont_work = 1285, /** * \brief Common server error. Error number: 1286, symbol: * ER_UNKNOWN_STORAGE_ENGINE. */ er_unknown_storage_engine = 1286, /** * \brief Common server error. Error number: 1287, symbol: * ER_WARN_DEPRECATED_SYNTAX. */ er_warn_deprecated_syntax = 1287, /** * \brief Common server error. Error number: 1288, symbol: * ER_NON_UPDATABLE_TABLE. */ er_non_updatable_table = 1288, /** * \brief Common server error. Error number: 1289, symbol: * ER_FEATURE_DISABLED. */ er_feature_disabled = 1289, /** * \brief Common server error. Error number: 1290, symbol: * ER_OPTION_PREVENTS_STATEMENT. */ er_option_prevents_statement = 1290, /** * \brief Common server error. Error number: 1291, symbol: * ER_DUPLICATED_VALUE_IN_TYPE. */ er_duplicated_value_in_type = 1291, /** * \brief Common server error. Error number: 1292, symbol: * ER_TRUNCATED_WRONG_VALUE. */ er_truncated_wrong_value = 1292, /** * \brief Common server error. Error number: 1293, symbol: * ER_TOO_MUCH_AUTO_TIMESTAMP_COLS. */ er_too_much_auto_timestamp_cols = 1293, /** * \brief Common server error. Error number: 1294, symbol: * ER_INVALID_ON_UPDATE. */ er_invalid_on_update = 1294, /** * \brief Common server error. Error number: 1295, symbol: * ER_UNSUPPORTED_PS. */ er_unsupported_ps = 1295, /** * \brief Common server error. Error number: 1296, symbol: * ER_GET_ERRMSG. */ er_get_errmsg = 1296, /** * \brief Common server error. Error number: 1297, symbol: * ER_GET_TEMPORARY_ERRMSG. */ er_get_temporary_errmsg = 1297, /** * \brief Common server error. Error number: 1298, symbol: * ER_UNKNOWN_TIME_ZONE. */ er_unknown_time_zone = 1298, /** * \brief Common server error. Error number: 1299, symbol: * ER_WARN_INVALID_TIMESTAMP. */ er_warn_invalid_timestamp = 1299, /** * \brief Common server error. Error number: 1300, symbol: * ER_INVALID_CHARACTER_STRING. */ er_invalid_character_string = 1300, /** * \brief Common server error. Error number: 1301, symbol: * ER_WARN_ALLOWED_PACKET_OVERFLOWED. */ er_warn_allowed_packet_overflowed = 1301, /** * \brief Common server error. Error number: 1302, symbol: * ER_CONFLICTING_DECLARATIONS. */ er_conflicting_declarations = 1302, /** * \brief Common server error. Error number: 1303, symbol: * ER_SP_NO_RECURSIVE_CREATE. */ er_sp_no_recursive_create = 1303, /** * \brief Common server error. Error number: 1304, symbol: * ER_SP_ALREADY_EXISTS. */ er_sp_already_exists = 1304, /** * \brief Common server error. Error number: 1305, symbol: * ER_SP_DOES_NOT_EXIST. */ er_sp_does_not_exist = 1305, /** * \brief Common server error. Error number: 1306, symbol: * ER_SP_DROP_FAILED. */ er_sp_drop_failed = 1306, /** * \brief Common server error. Error number: 1307, symbol: * ER_SP_STORE_FAILED. */ er_sp_store_failed = 1307, /** * \brief Common server error. Error number: 1308, symbol: * ER_SP_LILABEL_MISMATCH. */ er_sp_lilabel_mismatch = 1308, /** * \brief Common server error. Error number: 1309, symbol: * ER_SP_LABEL_REDEFINE. */ er_sp_label_redefine = 1309, /** * \brief Common server error. Error number: 1310, symbol: * ER_SP_LABEL_MISMATCH. */ er_sp_label_mismatch = 1310, /** * \brief Common server error. Error number: 1311, symbol: * ER_SP_UNINIT_VAR. */ er_sp_uninit_var = 1311, /** * \brief Common server error. Error number: 1312, symbol: * ER_SP_BADSELECT. */ er_sp_badselect = 1312, /** * \brief Common server error. Error number: 1313, symbol: * ER_SP_BADRETURN. */ er_sp_badreturn = 1313, /** * \brief Common server error. Error number: 1314, symbol: * ER_SP_BADSTATEMENT. */ er_sp_badstatement = 1314, /** * \brief Common server error. Error number: 1315, symbol: * ER_UPDATE_LOG_DEPRECATED_IGNORED. */ er_update_log_deprecated_ignored = 1315, /** * \brief Common server error. Error number: 1316, symbol: * ER_UPDATE_LOG_DEPRECATED_TRANSLATED. */ er_update_log_deprecated_translated = 1316, /** * \brief Common server error. Error number: 1317, symbol: * ER_QUERY_INTERRUPTED. */ er_query_interrupted = 1317, /** * \brief Common server error. Error number: 1318, symbol: * ER_SP_WRONG_NO_OF_ARGS. */ er_sp_wrong_no_of_args = 1318, /** * \brief Common server error. Error number: 1319, symbol: * ER_SP_COND_MISMATCH. */ er_sp_cond_mismatch = 1319, /** * \brief Common server error. Error number: 1320, symbol: * ER_SP_NORETURN. */ er_sp_noreturn = 1320, /** * \brief Common server error. Error number: 1321, symbol: * ER_SP_NORETURNEND. */ er_sp_noreturnend = 1321, /** * \brief Common server error. Error number: 1322, symbol: * ER_SP_BAD_CURSOR_QUERY. */ er_sp_bad_cursor_query = 1322, /** * \brief Common server error. Error number: 1323, symbol: * ER_SP_BAD_CURSOR_SELECT. */ er_sp_bad_cursor_select = 1323, /** * \brief Common server error. Error number: 1324, symbol: * ER_SP_CURSOR_MISMATCH. */ er_sp_cursor_mismatch = 1324, /** * \brief Common server error. Error number: 1325, symbol: * ER_SP_CURSOR_ALREADY_OPEN. */ er_sp_cursor_already_open = 1325, /** * \brief Common server error. Error number: 1326, symbol: * ER_SP_CURSOR_NOT_OPEN. */ er_sp_cursor_not_open = 1326, /** * \brief Common server error. Error number: 1327, symbol: * ER_SP_UNDECLARED_VAR. */ er_sp_undeclared_var = 1327, /** * \brief Common server error. Error number: 1328, symbol: * ER_SP_WRONG_NO_OF_FETCH_ARGS. */ er_sp_wrong_no_of_fetch_args = 1328, /** * \brief Common server error. Error number: 1329, symbol: * ER_SP_FETCH_NO_DATA. */ er_sp_fetch_no_data = 1329, /** * \brief Common server error. Error number: 1330, symbol: * ER_SP_DUP_PARAM. */ er_sp_dup_param = 1330, /** * \brief Common server error. Error number: 1331, symbol: * ER_SP_DUP_VAR. */ er_sp_dup_var = 1331, /** * \brief Common server error. Error number: 1332, symbol: * ER_SP_DUP_COND. */ er_sp_dup_cond = 1332, /** * \brief Common server error. Error number: 1333, symbol: * ER_SP_DUP_CURS. */ er_sp_dup_curs = 1333, /** * \brief Common server error. Error number: 1334, symbol: * ER_SP_CANT_ALTER. */ er_sp_cant_alter = 1334, /** * \brief Common server error. Error number: 1335, symbol: * ER_SP_SUBSELECT_NYI. */ er_sp_subselect_nyi = 1335, /** * \brief Common server error. Error number: 1336, symbol: * ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG. */ er_stmt_not_allowed_in_sf_or_trg = 1336, /** * \brief Common server error. Error number: 1337, symbol: * ER_SP_VARCOND_AFTER_CURSHNDLR. */ er_sp_varcond_after_curshndlr = 1337, /** * \brief Common server error. Error number: 1338, symbol: * ER_SP_CURSOR_AFTER_HANDLER. */ er_sp_cursor_after_handler = 1338, /** * \brief Common server error. Error number: 1339, symbol: * ER_SP_CASE_NOT_FOUND. */ er_sp_case_not_found = 1339, /** * \brief Common server error. Error number: 1340, symbol: * ER_FPARSER_TOO_BIG_FILE. */ er_fparser_too_big_file = 1340, /** * \brief Common server error. Error number: 1341, symbol: * ER_FPARSER_BAD_HEADER. */ er_fparser_bad_header = 1341, /** * \brief Common server error. Error number: 1342, symbol: * ER_FPARSER_EOF_IN_COMMENT. */ er_fparser_eof_in_comment = 1342, /** * \brief Common server error. Error number: 1343, symbol: * ER_FPARSER_ERROR_IN_PARAMETER. */ er_fparser_error_in_parameter = 1343, /** * \brief Common server error. Error number: 1344, symbol: * ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER. */ er_fparser_eof_in_unknown_parameter = 1344, /** * \brief Common server error. Error number: 1345, symbol: * ER_VIEW_NO_EXPLAIN. */ er_view_no_explain = 1345, /** * \brief Common server error. Error number: 1346, symbol: * ER_FRM_UNKNOWN_TYPE. */ er_frm_unknown_type = 1346, /** * \brief Common server error. Error number: 1347, symbol: * ER_WRONG_OBJECT. */ er_wrong_object = 1347, /** * \brief Common server error. Error number: 1348, symbol: * ER_NONUPDATEABLE_COLUMN. */ er_nonupdateable_column = 1348, /** * \brief Common server error. Error number: 1350, symbol: * ER_VIEW_SELECT_CLAUSE. */ er_view_select_clause = 1350, /** * \brief Common server error. Error number: 1351, symbol: * ER_VIEW_SELECT_VARIABLE. */ er_view_select_variable = 1351, /** * \brief Common server error. Error number: 1352, symbol: * ER_VIEW_SELECT_TMPTABLE. */ er_view_select_tmptable = 1352, /** * \brief Common server error. Error number: 1353, symbol: * ER_VIEW_WRONG_LIST. */ er_view_wrong_list = 1353, /** * \brief Common server error. Error number: 1354, symbol: * ER_WARN_VIEW_MERGE. */ er_warn_view_merge = 1354, /** * \brief Common server error. Error number: 1355, symbol: * ER_WARN_VIEW_WITHOUT_KEY. */ er_warn_view_without_key = 1355, /** * \brief Common server error. Error number: 1356, symbol: * ER_VIEW_INVALID. */ er_view_invalid = 1356, /** * \brief Common server error. Error number: 1357, symbol: * ER_SP_NO_DROP_SP. */ er_sp_no_drop_sp = 1357, /** * \brief Common server error. Error number: 1358, symbol: * ER_SP_GOTO_IN_HNDLR. */ er_sp_goto_in_hndlr = 1358, /** * \brief Common server error. Error number: 1359, symbol: * ER_TRG_ALREADY_EXISTS. */ er_trg_already_exists = 1359, /** * \brief Common server error. Error number: 1360, symbol: * ER_TRG_DOES_NOT_EXIST. */ er_trg_does_not_exist = 1360, /** * \brief Common server error. Error number: 1361, symbol: * ER_TRG_ON_VIEW_OR_TEMP_TABLE. */ er_trg_on_view_or_temp_table = 1361, /** * \brief Common server error. Error number: 1362, symbol: * ER_TRG_CANT_CHANGE_ROW. */ er_trg_cant_change_row = 1362, /** * \brief Common server error. Error number: 1363, symbol: * ER_TRG_NO_SUCH_ROW_IN_TRG. */ er_trg_no_such_row_in_trg = 1363, /** * \brief Common server error. Error number: 1364, symbol: * ER_NO_DEFAULT_FOR_FIELD. */ er_no_default_for_field = 1364, /** * \brief Common server error. Error number: 1365, symbol: * ER_DIVISION_BY_ZERO. */ er_division_by_zero = 1365, /** * \brief Common server error. Error number: 1366, symbol: * ER_TRUNCATED_WRONG_VALUE_FOR_FIELD. */ er_truncated_wrong_value_for_field = 1366, /** * \brief Common server error. Error number: 1367, symbol: * ER_ILLEGAL_VALUE_FOR_TYPE. */ er_illegal_value_for_type = 1367, /** * \brief Common server error. Error number: 1368, symbol: * ER_VIEW_NONUPD_CHECK. */ er_view_nonupd_check = 1368, /** * \brief Common server error. Error number: 1369, symbol: * ER_VIEW_CHECK_FAILED. */ er_view_check_failed = 1369, /** * \brief Common server error. Error number: 1370, symbol: * ER_PROCACCESS_DENIED_ERROR. */ er_procaccess_denied_error = 1370, /** * \brief Common server error. Error number: 1371, symbol: * ER_RELAY_LOG_FAIL. */ er_relay_log_fail = 1371, /** * \brief Common server error. Error number: 1372, symbol: * ER_PASSWD_LENGTH. */ er_passwd_length = 1372, /** * \brief Common server error. Error number: 1373, symbol: * ER_UNKNOWN_TARGET_BINLOG. */ er_unknown_target_binlog = 1373, /** * \brief Common server error. Error number: 1374, symbol: * ER_IO_ERR_LOG_INDEX_READ. */ er_io_err_log_index_read = 1374, /** * \brief Common server error. Error number: 1375, symbol: * ER_BINLOG_PURGE_PROHIBITED. */ er_binlog_purge_prohibited = 1375, /** * \brief Common server error. Error number: 1376, symbol: * ER_FSEEK_FAIL. */ er_fseek_fail = 1376, /** * \brief Common server error. Error number: 1377, symbol: * ER_BINLOG_PURGE_FATAL_ERR. */ er_binlog_purge_fatal_err = 1377, /** * \brief Common server error. Error number: 1378, symbol: * ER_LOG_IN_USE. */ er_log_in_use = 1378, /** * \brief Common server error. Error number: 1379, symbol: * ER_LOG_PURGE_UNKNOWN_ERR. */ er_log_purge_unknown_err = 1379, /** * \brief Common server error. Error number: 1380, symbol: * ER_RELAY_LOG_INIT. */ er_relay_log_init = 1380, /** * \brief Common server error. Error number: 1381, symbol: * ER_NO_BINARY_LOGGING. */ er_no_binary_logging = 1381, /** * \brief Common server error. Error number: 1382, symbol: * ER_RESERVED_SYNTAX. */ er_reserved_syntax = 1382, /** * \brief Common server error. Error number: 1383, symbol: * ER_WSAS_FAILED. */ er_wsas_failed = 1383, /** * \brief Common server error. Error number: 1384, symbol: * ER_DIFF_GROUPS_PROC. */ er_diff_groups_proc = 1384, /** * \brief Common server error. Error number: 1385, symbol: * ER_NO_GROUP_FOR_PROC. */ er_no_group_for_proc = 1385, /** * \brief Common server error. Error number: 1386, symbol: * ER_ORDER_WITH_PROC. */ er_order_with_proc = 1386, /** * \brief Common server error. Error number: 1387, symbol: * ER_LOGGING_PROHIBIT_CHANGING_OF. */ er_logging_prohibit_changing_of = 1387, /** * \brief Common server error. Error number: 1388, symbol: * ER_NO_FILE_MAPPING. */ er_no_file_mapping = 1388, /** * \brief Common server error. Error number: 1389, symbol: * ER_WRONG_MAGIC. */ er_wrong_magic = 1389, /** * \brief Common server error. Error number: 1390, symbol: * ER_PS_MANY_PARAM. */ er_ps_many_param = 1390, /** * \brief Common server error. Error number: 1391, symbol: * ER_KEY_PART_0. */ er_key_part_0 = 1391, /** * \brief Common server error. Error number: 1392, symbol: * ER_VIEW_CHECKSUM. */ er_view_checksum = 1392, /** * \brief Common server error. Error number: 1393, symbol: * ER_VIEW_MULTIUPDATE. */ er_view_multiupdate = 1393, /** * \brief Common server error. Error number: 1394, symbol: * ER_VIEW_NO_INSERT_FIELD_LIST. */ er_view_no_insert_field_list = 1394, /** * \brief Common server error. Error number: 1395, symbol: * ER_VIEW_DELETE_MERGE_VIEW. */ er_view_delete_merge_view = 1395, /** * \brief Common server error. Error number: 1396, symbol: * ER_CANNOT_USER. */ er_cannot_user = 1396, /** * \brief Common server error. Error number: 1397, symbol: * ER_XAER_NOTA. */ er_xaer_nota = 1397, /** * \brief Common server error. Error number: 1398, symbol: * ER_XAER_INVAL. */ er_xaer_inval = 1398, /** * \brief Common server error. Error number: 1399, symbol: * ER_XAER_RMFAIL. */ er_xaer_rmfail = 1399, /** * \brief Common server error. Error number: 1400, symbol: * ER_XAER_OUTSIDE. */ er_xaer_outside = 1400, /** * \brief Common server error. Error number: 1401, symbol: * ER_XAER_RMERR. */ er_xaer_rmerr = 1401, /** * \brief Common server error. Error number: 1402, symbol: * ER_XA_RBROLLBACK. */ er_xa_rbrollback = 1402, /** * \brief Common server error. Error number: 1403, symbol: * ER_NONEXISTING_PROC_GRANT. */ er_nonexisting_proc_grant = 1403, /** * \brief Common server error. Error number: 1404, symbol: * ER_PROC_AUTO_GRANT_FAIL. */ er_proc_auto_grant_fail = 1404, /** * \brief Common server error. Error number: 1405, symbol: * ER_PROC_AUTO_REVOKE_FAIL. */ er_proc_auto_revoke_fail = 1405, /** * \brief Common server error. Error number: 1406, symbol: * ER_DATA_TOO_LONG. */ er_data_too_long = 1406, /** * \brief Common server error. Error number: 1407, symbol: * ER_SP_BAD_SQLSTATE. */ er_sp_bad_sqlstate = 1407, /** * \brief Common server error. Error number: 1408, symbol: * ER_STARTUP. */ er_startup = 1408, /** * \brief Common server error. Error number: 1409, symbol: * ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR. */ er_load_from_fixed_size_rows_to_var = 1409, /** * \brief Common server error. Error number: 1410, symbol: * ER_CANT_CREATE_USER_WITH_GRANT. */ er_cant_create_user_with_grant = 1410, /** * \brief Common server error. Error number: 1411, symbol: * ER_WRONG_VALUE_FOR_TYPE. */ er_wrong_value_for_type = 1411, /** * \brief Common server error. Error number: 1412, symbol: * ER_TABLE_DEF_CHANGED. */ er_table_def_changed = 1412, /** * \brief Common server error. Error number: 1413, symbol: * ER_SP_DUP_HANDLER. */ er_sp_dup_handler = 1413, /** * \brief Common server error. Error number: 1414, symbol: * ER_SP_NOT_VAR_ARG. */ er_sp_not_var_arg = 1414, /** * \brief Common server error. Error number: 1415, symbol: * ER_SP_NO_RETSET. */ er_sp_no_retset = 1415, /** * \brief Common server error. Error number: 1416, symbol: * ER_CANT_CREATE_GEOMETRY_OBJECT. */ er_cant_create_geometry_object = 1416, /** * \brief Common server error. Error number: 1417, symbol: * ER_FAILED_ROUTINE_BREAK_BINLOG. */ er_failed_routine_break_binlog = 1417, /** * \brief Common server error. Error number: 1418, symbol: * ER_BINLOG_UNSAFE_ROUTINE. */ er_binlog_unsafe_routine = 1418, /** * \brief Common server error. Error number: 1419, symbol: * ER_BINLOG_CREATE_ROUTINE_NEED_SUPER. */ er_binlog_create_routine_need_super = 1419, /** * \brief Common server error. Error number: 1420, symbol: * ER_EXEC_STMT_WITH_OPEN_CURSOR. */ er_exec_stmt_with_open_cursor = 1420, /** * \brief Common server error. Error number: 1421, symbol: * ER_STMT_HAS_NO_OPEN_CURSOR. */ er_stmt_has_no_open_cursor = 1421, /** * \brief Common server error. Error number: 1422, symbol: * ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG. */ er_commit_not_allowed_in_sf_or_trg = 1422, /** * \brief Common server error. Error number: 1423, symbol: * ER_NO_DEFAULT_FOR_VIEW_FIELD. */ er_no_default_for_view_field = 1423, /** * \brief Common server error. Error number: 1424, symbol: * ER_SP_NO_RECURSION. */ er_sp_no_recursion = 1424, /** * \brief Common server error. Error number: 1425, symbol: * ER_TOO_BIG_SCALE. */ er_too_big_scale = 1425, /** * \brief Common server error. Error number: 1426, symbol: * ER_TOO_BIG_PRECISION. */ er_too_big_precision = 1426, /** * \brief Common server error. Error number: 1427, symbol: * ER_M_BIGGER_THAN_D. */ er_m_bigger_than_d = 1427, /** * \brief Common server error. Error number: 1428, symbol: * ER_WRONG_LOCK_OF_SYSTEM_TABLE. */ er_wrong_lock_of_system_table = 1428, /** * \brief Common server error. Error number: 1429, symbol: * ER_CONNECT_TO_FOREIGN_DATA_SOURCE. */ er_connect_to_foreign_data_source = 1429, /** * \brief Common server error. Error number: 1430, symbol: * ER_QUERY_ON_FOREIGN_DATA_SOURCE. */ er_query_on_foreign_data_source = 1430, /** * \brief Common server error. Error number: 1431, symbol: * ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST. */ er_foreign_data_source_doesnt_exist = 1431, /** * \brief Common server error. Error number: 1432, symbol: * ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE. */ er_foreign_data_string_invalid_cant_create = 1432, /** * \brief Common server error. Error number: 1433, symbol: * ER_FOREIGN_DATA_STRING_INVALID. */ er_foreign_data_string_invalid = 1433, /** * \brief Common server error. Error number: 1434, symbol: * ER_CANT_CREATE_FEDERATED_TABLE. */ er_cant_create_federated_table = 1434, /** * \brief Common server error. Error number: 1435, symbol: * ER_TRG_IN_WRONG_SCHEMA. */ er_trg_in_wrong_schema = 1435, /** * \brief Common server error. Error number: 1436, symbol: * ER_STACK_OVERRUN_NEED_MORE. */ er_stack_overrun_need_more = 1436, /** * \brief Common server error. Error number: 1437, symbol: * ER_TOO_LONG_BODY. */ er_too_long_body = 1437, /** * \brief Common server error. Error number: 1438, symbol: * ER_WARN_CANT_DROP_DEFAULT_KEYCACHE. */ er_warn_cant_drop_default_keycache = 1438, /** * \brief Common server error. Error number: 1439, symbol: * ER_TOO_BIG_DISPLAYWIDTH. */ er_too_big_displaywidth = 1439, /** * \brief Common server error. Error number: 1440, symbol: * ER_XAER_DUPID. */ er_xaer_dupid = 1440, /** * \brief Common server error. Error number: 1441, symbol: * ER_DATETIME_FUNCTION_OVERFLOW. */ er_datetime_function_overflow = 1441, /** * \brief Common server error. Error number: 1442, symbol: * ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG. */ er_cant_update_used_table_in_sf_or_trg = 1442, /** * \brief Common server error. Error number: 1443, symbol: * ER_VIEW_PREVENT_UPDATE. */ er_view_prevent_update = 1443, /** * \brief Common server error. Error number: 1444, symbol: * ER_PS_NO_RECURSION. */ er_ps_no_recursion = 1444, /** * \brief Common server error. Error number: 1445, symbol: * ER_SP_CANT_SET_AUTOCOMMIT. */ er_sp_cant_set_autocommit = 1445, /** * \brief Common server error. Error number: 1446, symbol: * ER_MALFORMED_DEFINER. */ er_malformed_definer = 1446, /** * \brief Common server error. Error number: 1447, symbol: * ER_VIEW_FRM_NO_USER. */ er_view_frm_no_user = 1447, /** * \brief Common server error. Error number: 1448, symbol: * ER_VIEW_OTHER_USER. */ er_view_other_user = 1448, /** * \brief Common server error. Error number: 1449, symbol: * ER_NO_SUCH_USER. */ er_no_such_user = 1449, /** * \brief Common server error. Error number: 1450, symbol: * ER_FORBID_SCHEMA_CHANGE. */ er_forbid_schema_change = 1450, /** * \brief Common server error. Error number: 1451, symbol: * ER_ROW_IS_REFERENCED_2. */ er_row_is_referenced_2 = 1451, /** * \brief Common server error. Error number: 1452, symbol: * ER_NO_REFERENCED_ROW_2. */ er_no_referenced_row_2 = 1452, /** * \brief Common server error. Error number: 1453, symbol: * ER_SP_BAD_VAR_SHADOW. */ er_sp_bad_var_shadow = 1453, /** * \brief Common server error. Error number: 1454, symbol: * ER_TRG_NO_DEFINER. */ er_trg_no_definer = 1454, /** * \brief Common server error. Error number: 1455, symbol: * ER_OLD_FILE_FORMAT. */ er_old_file_format = 1455, /** * \brief Common server error. Error number: 1456, symbol: * ER_SP_RECURSION_LIMIT. */ er_sp_recursion_limit = 1456, /** * \brief Common server error. Error number: 1457, symbol: * ER_SP_PROC_TABLE_CORRUPT. */ er_sp_proc_table_corrupt = 1457, /** * \brief Common server error. Error number: 1458, symbol: * ER_SP_WRONG_NAME. */ er_sp_wrong_name = 1458, /** * \brief Common server error. Error number: 1459, symbol: * ER_TABLE_NEEDS_UPGRADE. */ er_table_needs_upgrade = 1459, /** * \brief Common server error. Error number: 1460, symbol: * ER_SP_NO_AGGREGATE. */ er_sp_no_aggregate = 1460, /** * \brief Common server error. Error number: 1461, symbol: * ER_MAX_PREPARED_STMT_COUNT_REACHED. */ er_max_prepared_stmt_count_reached = 1461, /** * \brief Common server error. Error number: 1462, symbol: * ER_VIEW_RECURSIVE. */ er_view_recursive = 1462, /** * \brief Common server error. Error number: 1463, symbol: * ER_NON_GROUPING_FIELD_USED. */ er_non_grouping_field_used = 1463, /** * \brief Common server error. Error number: 1464, symbol: * ER_TABLE_CANT_HANDLE_SPKEYS. */ er_table_cant_handle_spkeys = 1464, /** * \brief Common server error. Error number: 1465, symbol: * ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA. */ er_no_triggers_on_system_schema = 1465, /** * \brief Common server error. Error number: 1466, symbol: * ER_REMOVED_SPACES. */ er_removed_spaces = 1466, /** * \brief Common server error. Error number: 1467, symbol: * ER_AUTOINC_READ_FAILED. */ er_autoinc_read_failed = 1467, /** * \brief Common server error. Error number: 1468, symbol: * ER_USERNAME. */ er_username = 1468, /** * \brief Common server error. Error number: 1469, symbol: * ER_HOSTNAME. */ er_hostname = 1469, /** * \brief Common server error. Error number: 1470, symbol: * ER_WRONG_STRING_LENGTH. */ er_wrong_string_length = 1470, /** * \brief Common server error. Error number: 1471, symbol: * ER_NON_INSERTABLE_TABLE. */ er_non_insertable_table = 1471, /** * \brief Common server error. Error number: 1472, symbol: * ER_ADMIN_WRONG_MRG_TABLE. */ er_admin_wrong_mrg_table = 1472, /** * \brief Common server error. Error number: 1473, symbol: * ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT. */ er_too_high_level_of_nesting_for_select = 1473, /** * \brief Common server error. Error number: 1474, symbol: * ER_NAME_BECOMES_EMPTY. */ er_name_becomes_empty = 1474, /** * \brief Common server error. Error number: 1475, symbol: * ER_AMBIGUOUS_FIELD_TERM. */ er_ambiguous_field_term = 1475, /** * \brief Common server error. Error number: 1476, symbol: * ER_FOREIGN_SERVER_EXISTS. */ er_foreign_server_exists = 1476, /** * \brief Common server error. Error number: 1477, symbol: * ER_FOREIGN_SERVER_DOESNT_EXIST. */ er_foreign_server_doesnt_exist = 1477, /** * \brief Common server error. Error number: 1478, symbol: * ER_ILLEGAL_HA_CREATE_OPTION. */ er_illegal_ha_create_option = 1478, /** * \brief Common server error. Error number: 1479, symbol: * ER_PARTITION_REQUIRES_VALUES_ERROR. */ er_partition_requires_values_error = 1479, /** * \brief Common server error. Error number: 1480, symbol: * ER_PARTITION_WRONG_VALUES_ERROR. */ er_partition_wrong_values_error = 1480, /** * \brief Common server error. Error number: 1481, symbol: * ER_PARTITION_MAXVALUE_ERROR. */ er_partition_maxvalue_error = 1481, /** * \brief Common server error. Error number: 1482, symbol: * ER_PARTITION_SUBPARTITION_ERROR. */ er_partition_subpartition_error = 1482, /** * \brief Common server error. Error number: 1483, symbol: * ER_PARTITION_SUBPART_MIX_ERROR. */ er_partition_subpart_mix_error = 1483, /** * \brief Common server error. Error number: 1484, symbol: * ER_PARTITION_WRONG_NO_PART_ERROR. */ er_partition_wrong_no_part_error = 1484, /** * \brief Common server error. Error number: 1485, symbol: * ER_PARTITION_WRONG_NO_SUBPART_ERROR. */ er_partition_wrong_no_subpart_error = 1485, /** * \brief Common server error. Error number: 1486, symbol: * ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR. */ er_wrong_expr_in_partition_func_error = 1486, /** * \brief Common server error. Error number: 1488, symbol: * ER_FIELD_NOT_FOUND_PART_ERROR. */ er_field_not_found_part_error = 1488, /** * \brief Common server error. Error number: 1489, symbol: * ER_LIST_OF_FIELDS_ONLY_IN_HASH_ERROR. */ er_list_of_fields_only_in_hash_error = 1489, /** * \brief Common server error. Error number: 1490, symbol: * ER_INCONSISTENT_PARTITION_INFO_ERROR. */ er_inconsistent_partition_info_error = 1490, /** * \brief Common server error. Error number: 1491, symbol: * ER_PARTITION_FUNC_NOT_ALLOWED_ERROR. */ er_partition_func_not_allowed_error = 1491, /** * \brief Common server error. Error number: 1492, symbol: * ER_PARTITIONS_MUST_BE_DEFINED_ERROR. */ er_partitions_must_be_defined_error = 1492, /** * \brief Common server error. Error number: 1493, symbol: * ER_RANGE_NOT_INCREASING_ERROR. */ er_range_not_increasing_error = 1493, /** * \brief Common server error. Error number: 1494, symbol: * ER_INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR. */ er_inconsistent_type_of_functions_error = 1494, /** * \brief Common server error. Error number: 1495, symbol: * ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR. */ er_multiple_def_const_in_list_part_error = 1495, /** * \brief Common server error. Error number: 1496, symbol: * ER_PARTITION_ENTRY_ERROR. */ er_partition_entry_error = 1496, /** * \brief Common server error. Error number: 1497, symbol: * ER_MIX_HANDLER_ERROR. */ er_mix_handler_error = 1497, /** * \brief Common server error. Error number: 1498, symbol: * ER_PARTITION_NOT_DEFINED_ERROR. */ er_partition_not_defined_error = 1498, /** * \brief Common server error. Error number: 1499, symbol: * ER_TOO_MANY_PARTITIONS_ERROR. */ er_too_many_partitions_error = 1499, /** * \brief Common server error. Error number: 1500, symbol: * ER_SUBPARTITION_ERROR. */ er_subpartition_error = 1500, /** * \brief Common server error. Error number: 1501, symbol: * ER_CANT_CREATE_HANDLER_FILE. */ er_cant_create_handler_file = 1501, /** * \brief Common server error. Error number: 1502, symbol: * ER_BLOB_FIELD_IN_PART_FUNC_ERROR. */ er_blob_field_in_part_func_error = 1502, /** * \brief Common server error. Error number: 1503, symbol: * ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF. */ er_unique_key_need_all_fields_in_pf = 1503, /** * \brief Common server error. Error number: 1504, symbol: * ER_NO_PARTS_ERROR. */ er_no_parts_error = 1504, /** * \brief Common server error. Error number: 1505, symbol: * ER_PARTITION_MGMT_ON_NONPARTITIONED. */ er_partition_mgmt_on_nonpartitioned = 1505, /** * \brief Common server error. Error number: 1507, symbol: * ER_DROP_PARTITION_NON_EXISTENT. */ er_drop_partition_non_existent = 1507, /** * \brief Common server error. Error number: 1508, symbol: * ER_DROP_LAST_PARTITION. */ er_drop_last_partition = 1508, /** * \brief Common server error. Error number: 1509, symbol: * ER_COALESCE_ONLY_ON_HASH_PARTITION. */ er_coalesce_only_on_hash_partition = 1509, /** * \brief Common server error. Error number: 1510, symbol: * ER_REORG_HASH_ONLY_ON_SAME_NO. */ er_reorg_hash_only_on_same_no = 1510, /** * \brief Common server error. Error number: 1511, symbol: * ER_REORG_NO_PARAM_ERROR. */ er_reorg_no_param_error = 1511, /** * \brief Common server error. Error number: 1512, symbol: * ER_ONLY_ON_RANGE_LIST_PARTITION. */ er_only_on_range_list_partition = 1512, /** * \brief Common server error. Error number: 1513, symbol: * ER_ADD_PARTITION_SUBPART_ERROR. */ er_add_partition_subpart_error = 1513, /** * \brief Common server error. Error number: 1514, symbol: * ER_ADD_PARTITION_NO_NEW_PARTITION. */ er_add_partition_no_new_partition = 1514, /** * \brief Common server error. Error number: 1515, symbol: * ER_COALESCE_PARTITION_NO_PARTITION. */ er_coalesce_partition_no_partition = 1515, /** * \brief Common server error. Error number: 1516, symbol: * ER_REORG_PARTITION_NOT_EXIST. */ er_reorg_partition_not_exist = 1516, /** * \brief Common server error. Error number: 1517, symbol: * ER_SAME_NAME_PARTITION. */ er_same_name_partition = 1517, /** * \brief Common server error. Error number: 1518, symbol: * ER_NO_BINLOG_ERROR. */ er_no_binlog_error = 1518, /** * \brief Common server error. Error number: 1519, symbol: * ER_CONSECUTIVE_REORG_PARTITIONS. */ er_consecutive_reorg_partitions = 1519, /** * \brief Common server error. Error number: 1520, symbol: * ER_REORG_OUTSIDE_RANGE. */ er_reorg_outside_range = 1520, /** * \brief Common server error. Error number: 1521, symbol: * ER_PARTITION_FUNCTION_FAILURE. */ er_partition_function_failure = 1521, /** * \brief Common server error. Error number: 1522, symbol: * ER_PART_STATE_ERROR. */ er_part_state_error = 1522, /** * \brief Common server error. Error number: 1523, symbol: * ER_LIMITED_PART_RANGE. */ er_limited_part_range = 1523, /** * \brief Common server error. Error number: 1524, symbol: * ER_PLUGIN_IS_NOT_LOADED. */ er_plugin_is_not_loaded = 1524, /** * \brief Common server error. Error number: 1525, symbol: * ER_WRONG_VALUE. */ er_wrong_value = 1525, /** * \brief Common server error. Error number: 1526, symbol: * ER_NO_PARTITION_FOR_GIVEN_VALUE. */ er_no_partition_for_given_value = 1526, /** * \brief Common server error. Error number: 1527, symbol: * ER_FILEGROUP_OPTION_ONLY_ONCE. */ er_filegroup_option_only_once = 1527, /** * \brief Common server error. Error number: 1528, symbol: * ER_CREATE_FILEGROUP_FAILED. */ er_create_filegroup_failed = 1528, /** * \brief Common server error. Error number: 1529, symbol: * ER_DROP_FILEGROUP_FAILED. */ er_drop_filegroup_failed = 1529, /** * \brief Common server error. Error number: 1530, symbol: * ER_TABLESPACE_AUTO_EXTEND_ERROR. */ er_tablespace_auto_extend_error = 1530, /** * \brief Common server error. Error number: 1531, symbol: * ER_WRONG_SIZE_NUMBER. */ er_wrong_size_number = 1531, /** * \brief Common server error. Error number: 1532, symbol: * ER_SIZE_OVERFLOW_ERROR. */ er_size_overflow_error = 1532, /** * \brief Common server error. Error number: 1533, symbol: * ER_ALTER_FILEGROUP_FAILED. */ er_alter_filegroup_failed = 1533, /** * \brief Common server error. Error number: 1534, symbol: * ER_BINLOG_ROW_LOGGING_FAILED. */ er_binlog_row_logging_failed = 1534, /** * \brief Common server error. Error number: 1535, symbol: * ER_BINLOG_ROW_WRONG_TABLE_DEF. */ er_binlog_row_wrong_table_def = 1535, /** * \brief Common server error. Error number: 1536, symbol: * ER_BINLOG_ROW_RBR_TO_SBR. */ er_binlog_row_rbr_to_sbr = 1536, /** * \brief Common server error. Error number: 1537, symbol: * ER_EVENT_ALREADY_EXISTS. */ er_event_already_exists = 1537, /** * \brief Common server error. Error number: 1538, symbol: * ER_EVENT_STORE_FAILED. */ er_event_store_failed = 1538, /** * \brief Common server error. Error number: 1539, symbol: * ER_EVENT_DOES_NOT_EXIST. */ er_event_does_not_exist = 1539, /** * \brief Common server error. Error number: 1540, symbol: * ER_EVENT_CANT_ALTER. */ er_event_cant_alter = 1540, /** * \brief Common server error. Error number: 1541, symbol: * ER_EVENT_DROP_FAILED. */ er_event_drop_failed = 1541, /** * \brief Common server error. Error number: 1542, symbol: * ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG. */ er_event_interval_not_positive_or_too_big = 1542, /** * \brief Common server error. Error number: 1543, symbol: * ER_EVENT_ENDS_BEFORE_STARTS. */ er_event_ends_before_starts = 1543, /** * \brief Common server error. Error number: 1544, symbol: * ER_EVENT_EXEC_TIME_IN_THE_PAST. */ er_event_exec_time_in_the_past = 1544, /** * \brief Common server error. Error number: 1545, symbol: * ER_EVENT_OPEN_TABLE_FAILED. */ er_event_open_table_failed = 1545, /** * \brief Common server error. Error number: 1546, symbol: * ER_EVENT_NEITHER_M_EXPR_NOR_M_AT. */ er_event_neither_m_expr_nor_m_at = 1546, /** * \brief Common server error. Error number: 1549, symbol: * ER_EVENT_CANNOT_DELETE. */ er_event_cannot_delete = 1549, /** * \brief Common server error. Error number: 1550, symbol: * ER_EVENT_COMPILE_ERROR. */ er_event_compile_error = 1550, /** * \brief Common server error. Error number: 1551, symbol: * ER_EVENT_SAME_NAME. */ er_event_same_name = 1551, /** * \brief Common server error. Error number: 1552, symbol: * ER_EVENT_DATA_TOO_LONG. */ er_event_data_too_long = 1552, /** * \brief Common server error. Error number: 1553, symbol: * ER_DROP_INDEX_FK. */ er_drop_index_fk = 1553, /** * \brief Common server error. Error number: 1554, symbol: * ER_WARN_DEPRECATED_SYNTAX_WITH_VER. */ er_warn_deprecated_syntax_with_ver = 1554, /** * \brief Common server error. Error number: 1555, symbol: * ER_CANT_WRITE_LOCK_LOG_TABLE. */ er_cant_write_lock_log_table = 1555, /** * \brief Common server error. Error number: 1556, symbol: * ER_CANT_LOCK_LOG_TABLE. */ er_cant_lock_log_table = 1556, /** * \brief Common server error. Error number: 1558, symbol: * ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE. */ er_col_count_doesnt_match_please_update = 1558, /** * \brief Common server error. Error number: 1559, symbol: * ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR. */ er_temp_table_prevents_switch_out_of_rbr = 1559, /** * \brief Common server error. Error number: 1560, symbol: * ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT. */ er_stored_function_prevents_switch_binlog_format = 1560, /** * \brief Common server error. Error number: 1562, symbol: * ER_PARTITION_NO_TEMPORARY. */ er_partition_no_temporary = 1562, /** * \brief Common server error. Error number: 1563, symbol: * ER_PARTITION_CONST_DOMAIN_ERROR. */ er_partition_const_domain_error = 1563, /** * \brief Common server error. Error number: 1564, symbol: * ER_PARTITION_FUNCTION_IS_NOT_ALLOWED. */ er_partition_function_is_not_allowed = 1564, /** * \brief Common server error. Error number: 1565, symbol: * ER_DDL_LOG_ERROR. */ er_ddl_log_error = 1565, /** * \brief Common server error. Error number: 1566, symbol: * ER_NULL_IN_VALUES_LESS_THAN. */ er_null_in_values_less_than = 1566, /** * \brief Common server error. Error number: 1567, symbol: * ER_WRONG_PARTITION_NAME. */ er_wrong_partition_name = 1567, /** * \brief Common server error. Error number: 1568, symbol: * ER_CANT_CHANGE_TX_CHARACTERISTICS. */ er_cant_change_tx_characteristics = 1568, /** * \brief Common server error. Error number: 1569, symbol: * ER_DUP_ENTRY_AUTOINCREMENT_CASE. */ er_dup_entry_autoincrement_case = 1569, /** * \brief Common server error. Error number: 1570, symbol: * ER_EVENT_MODIFY_QUEUE_ERROR. */ er_event_modify_queue_error = 1570, /** * \brief Common server error. Error number: 1571, symbol: * ER_EVENT_SET_VAR_ERROR. */ er_event_set_var_error = 1571, /** * \brief Common server error. Error number: 1572, symbol: * ER_PARTITION_MERGE_ERROR. */ er_partition_merge_error = 1572, /** * \brief Common server error. Error number: 1573, symbol: * ER_CANT_ACTIVATE_LOG. */ er_cant_activate_log = 1573, /** * \brief Common server error. Error number: 1574, symbol: * ER_RBR_NOT_AVAILABLE. */ er_rbr_not_available = 1574, /** * \brief Common server error. Error number: 1575, symbol: * ER_BASE64_DECODE_ERROR. */ er_base64_decode_error = 1575, /** * \brief Common server error. Error number: 1576, symbol: * ER_EVENT_RECURSION_FORBIDDEN. */ er_event_recursion_forbidden = 1576, /** * \brief Common server error. Error number: 1577, symbol: * ER_EVENTS_DB_ERROR. */ er_events_db_error = 1577, /** * \brief Common server error. Error number: 1578, symbol: * ER_ONLY_INTEGERS_ALLOWED. */ er_only_integers_allowed = 1578, /** * \brief Common server error. Error number: 1579, symbol: * ER_UNSUPORTED_LOG_ENGINE. */ er_unsuported_log_engine = 1579, /** * \brief Common server error. Error number: 1580, symbol: * ER_BAD_LOG_STATEMENT. */ er_bad_log_statement = 1580, /** * \brief Common server error. Error number: 1581, symbol: * ER_CANT_RENAME_LOG_TABLE. */ er_cant_rename_log_table = 1581, /** * \brief Common server error. Error number: 1582, symbol: * ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT. */ er_wrong_paramcount_to_native_fct = 1582, /** * \brief Common server error. Error number: 1583, symbol: * ER_WRONG_PARAMETERS_TO_NATIVE_FCT. */ er_wrong_parameters_to_native_fct = 1583, /** * \brief Common server error. Error number: 1584, symbol: * ER_WRONG_PARAMETERS_TO_STORED_FCT. */ er_wrong_parameters_to_stored_fct = 1584, /** * \brief Common server error. Error number: 1585, symbol: * ER_NATIVE_FCT_NAME_COLLISION. */ er_native_fct_name_collision = 1585, /** * \brief Common server error. Error number: 1586, symbol: * ER_DUP_ENTRY_WITH_KEY_NAME. */ er_dup_entry_with_key_name = 1586, /** * \brief Common server error. Error number: 1587, symbol: * ER_BINLOG_PURGE_EMFILE. */ er_binlog_purge_emfile = 1587, /** * \brief Common server error. Error number: 1588, symbol: * ER_EVENT_CANNOT_CREATE_IN_THE_PAST. */ er_event_cannot_create_in_the_past = 1588, /** * \brief Common server error. Error number: 1589, symbol: * ER_EVENT_CANNOT_ALTER_IN_THE_PAST. */ er_event_cannot_alter_in_the_past = 1589, /** * \brief Common server error. Error number: 1590, symbol: * ER_SLAVE_INCIDENT. */ er_slave_incident = 1590, /** * \brief Common server error. Error number: 1591, symbol: * ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT. */ er_no_partition_for_given_value_silent = 1591, /** * \brief Common server error. Error number: 1592, symbol: * ER_BINLOG_UNSAFE_STATEMENT. */ er_binlog_unsafe_statement = 1592, /** * \brief Common server error. Error number: 1594, symbol: * ER_SLAVE_RELAY_LOG_READ_FAILURE. */ er_slave_relay_log_read_failure = 1594, /** * \brief Common server error. Error number: 1595, symbol: * ER_SLAVE_RELAY_LOG_WRITE_FAILURE. */ er_slave_relay_log_write_failure = 1595, /** * \brief Common server error. Error number: 1596, symbol: * ER_SLAVE_CREATE_EVENT_FAILURE. */ er_slave_create_event_failure = 1596, /** * \brief Common server error. Error number: 1597, symbol: * ER_SLAVE_MASTER_COM_FAILURE. */ er_slave_master_com_failure = 1597, /** * \brief Common server error. Error number: 1598, symbol: * ER_BINLOG_LOGGING_IMPOSSIBLE. */ er_binlog_logging_impossible = 1598, /** * \brief Common server error. Error number: 1599, symbol: * ER_VIEW_NO_CREATION_CTX. */ er_view_no_creation_ctx = 1599, /** * \brief Common server error. Error number: 1600, symbol: * ER_VIEW_INVALID_CREATION_CTX. */ er_view_invalid_creation_ctx = 1600, /** * \brief Common server error. Error number: 1601, symbol: * ER_SR_INVALID_CREATION_CTX. */ er_sr_invalid_creation_ctx = 1601, /** * \brief Common server error. Error number: 1602, symbol: * ER_TRG_CORRUPTED_FILE. */ er_trg_corrupted_file = 1602, /** * \brief Common server error. Error number: 1603, symbol: * ER_TRG_NO_CREATION_CTX. */ er_trg_no_creation_ctx = 1603, /** * \brief Common server error. Error number: 1604, symbol: * ER_TRG_INVALID_CREATION_CTX. */ er_trg_invalid_creation_ctx = 1604, /** * \brief Common server error. Error number: 1605, symbol: * ER_EVENT_INVALID_CREATION_CTX. */ er_event_invalid_creation_ctx = 1605, /** * \brief Common server error. Error number: 1606, symbol: * ER_TRG_CANT_OPEN_TABLE. */ er_trg_cant_open_table = 1606, /** * \brief Common server error. Error number: 1607, symbol: * ER_CANT_CREATE_SROUTINE. */ er_cant_create_sroutine = 1607, /** * \brief Common server error. Error number: 1609, symbol: * ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT. */ er_no_format_description_event_before_binlog_statement = 1609, /** * \brief Common server error. Error number: 1610, symbol: * ER_SLAVE_CORRUPT_EVENT. */ er_slave_corrupt_event = 1610, /** * \brief Common server error. Error number: 1612, symbol: * ER_LOG_PURGE_NO_FILE. */ er_log_purge_no_file = 1612, /** * \brief Common server error. Error number: 1613, symbol: * ER_XA_RBTIMEOUT. */ er_xa_rbtimeout = 1613, /** * \brief Common server error. Error number: 1614, symbol: * ER_XA_RBDEADLOCK. */ er_xa_rbdeadlock = 1614, /** * \brief Common server error. Error number: 1615, symbol: * ER_NEED_REPREPARE. */ er_need_reprepare = 1615, /** * \brief Common server error. Error number: 1616, symbol: * ER_DELAYED_NOT_SUPPORTED. */ er_delayed_not_supported = 1616, /** * \brief Common server error. Error number: 1617, symbol: * WARN_NO_MASTER_INFO. */ warn_no_master_info = 1617, /** * \brief Common server error. Error number: 1618, symbol: * WARN_OPTION_IGNORED. */ warn_option_ignored = 1618, /** * \brief Common server error. Error number: 1619, symbol: * ER_PLUGIN_DELETE_BUILTIN. */ er_plugin_delete_builtin = 1619, /** * \brief Common server error. Error number: 1620, symbol: * WARN_PLUGIN_BUSY. */ warn_plugin_busy = 1620, /** * \brief Common server error. Error number: 1621, symbol: * ER_VARIABLE_IS_READONLY. */ er_variable_is_readonly = 1621, /** * \brief Common server error. Error number: 1622, symbol: * ER_WARN_ENGINE_TRANSACTION_ROLLBACK. */ er_warn_engine_transaction_rollback = 1622, /** * \brief Common server error. Error number: 1623, symbol: * ER_SLAVE_HEARTBEAT_FAILURE. */ er_slave_heartbeat_failure = 1623, /** * \brief Common server error. Error number: 1624, symbol: * ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE. */ er_slave_heartbeat_value_out_of_range = 1624, /** * \brief Common server error. Error number: 1626, symbol: * ER_CONFLICT_FN_PARSE_ERROR. */ er_conflict_fn_parse_error = 1626, /** * \brief Common server error. Error number: 1627, symbol: * ER_EXCEPTIONS_WRITE_ERROR. */ er_exceptions_write_error = 1627, /** * \brief Common server error. Error number: 1628, symbol: * ER_TOO_LONG_TABLE_COMMENT. */ er_too_long_table_comment = 1628, /** * \brief Common server error. Error number: 1629, symbol: * ER_TOO_LONG_FIELD_COMMENT. */ er_too_long_field_comment = 1629, /** * \brief Common server error. Error number: 1630, symbol: * ER_FUNC_INEXISTENT_NAME_COLLISION. */ er_func_inexistent_name_collision = 1630, /** * \brief Common server error. Error number: 1631, symbol: * ER_DATABASE_NAME. */ er_database_name = 1631, /** * \brief Common server error. Error number: 1632, symbol: * ER_TABLE_NAME. */ er_table_name = 1632, /** * \brief Common server error. Error number: 1633, symbol: * ER_PARTITION_NAME. */ er_partition_name = 1633, /** * \brief Common server error. Error number: 1634, symbol: * ER_SUBPARTITION_NAME. */ er_subpartition_name = 1634, /** * \brief Common server error. Error number: 1635, symbol: * ER_TEMPORARY_NAME. */ er_temporary_name = 1635, /** * \brief Common server error. Error number: 1636, symbol: * ER_RENAMED_NAME. */ er_renamed_name = 1636, /** * \brief Common server error. Error number: 1637, symbol: * ER_TOO_MANY_CONCURRENT_TRXS. */ er_too_many_concurrent_trxs = 1637, /** * \brief Common server error. Error number: 1638, symbol: * WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED. */ warn_non_ascii_separator_not_implemented = 1638, /** * \brief Common server error. Error number: 1639, symbol: * ER_DEBUG_SYNC_TIMEOUT. */ er_debug_sync_timeout = 1639, /** * \brief Common server error. Error number: 1640, symbol: * ER_DEBUG_SYNC_HIT_LIMIT. */ er_debug_sync_hit_limit = 1640, /** * \brief Common server error. Error number: 1641, symbol: * ER_DUP_SIGNAL_SET. */ er_dup_signal_set = 1641, /** * \brief Common server error. Error number: 1642, symbol: * ER_SIGNAL_WARN. */ er_signal_warn = 1642, /** * \brief Common server error. Error number: 1643, symbol: * ER_SIGNAL_NOT_FOUND. */ er_signal_not_found = 1643, /** * \brief Common server error. Error number: 1644, symbol: * ER_SIGNAL_EXCEPTION. */ er_signal_exception = 1644, /** * \brief Common server error. Error number: 1645, symbol: * ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER. */ er_resignal_without_active_handler = 1645, /** * \brief Common server error. Error number: 1646, symbol: * ER_SIGNAL_BAD_CONDITION_TYPE. */ er_signal_bad_condition_type = 1646, /** * \brief Common server error. Error number: 1647, symbol: * WARN_COND_ITEM_TRUNCATED. */ warn_cond_item_truncated = 1647, /** * \brief Common server error. Error number: 1648, symbol: * ER_COND_ITEM_TOO_LONG. */ er_cond_item_too_long = 1648, /** * \brief Common server error. Error number: 1649, symbol: * ER_UNKNOWN_LOCALE. */ er_unknown_locale = 1649, /** * \brief Common server error. Error number: 1650, symbol: * ER_SLAVE_IGNORE_SERVER_IDS. */ er_slave_ignore_server_ids = 1650, /** * \brief Common server error. Error number: 1651, symbol: * ER_QUERY_CACHE_DISABLED. */ er_query_cache_disabled = 1651, /** * \brief Common server error. Error number: 1652, symbol: * ER_SAME_NAME_PARTITION_FIELD. */ er_same_name_partition_field = 1652, /** * \brief Common server error. Error number: 1653, symbol: * ER_PARTITION_COLUMN_LIST_ERROR. */ er_partition_column_list_error = 1653, /** * \brief Common server error. Error number: 1654, symbol: * ER_WRONG_TYPE_COLUMN_VALUE_ERROR. */ er_wrong_type_column_value_error = 1654, /** * \brief Common server error. Error number: 1655, symbol: * ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR. */ er_too_many_partition_func_fields_error = 1655, /** * \brief Common server error. Error number: 1656, symbol: * ER_MAXVALUE_IN_VALUES_IN. */ er_maxvalue_in_values_in = 1656, /** * \brief Common server error. Error number: 1657, symbol: * ER_TOO_MANY_VALUES_ERROR. */ er_too_many_values_error = 1657, /** * \brief Common server error. Error number: 1658, symbol: * ER_ROW_SINGLE_PARTITION_FIELD_ERROR. */ er_row_single_partition_field_error = 1658, /** * \brief Common server error. Error number: 1659, symbol: * ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD. */ er_field_type_not_allowed_as_partition_field = 1659, /** * \brief Common server error. Error number: 1660, symbol: * ER_PARTITION_FIELDS_TOO_LONG. */ er_partition_fields_too_long = 1660, /** * \brief Common server error. Error number: 1661, symbol: * ER_BINLOG_ROW_ENGINE_AND_STMT_ENGINE. */ er_binlog_row_engine_and_stmt_engine = 1661, /** * \brief Common server error. Error number: 1662, symbol: * ER_BINLOG_ROW_MODE_AND_STMT_ENGINE. */ er_binlog_row_mode_and_stmt_engine = 1662, /** * \brief Common server error. Error number: 1663, symbol: * ER_BINLOG_UNSAFE_AND_STMT_ENGINE. */ er_binlog_unsafe_and_stmt_engine = 1663, /** * \brief Common server error. Error number: 1664, symbol: * ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE. */ er_binlog_row_injection_and_stmt_engine = 1664, /** * \brief Common server error. Error number: 1665, symbol: * ER_BINLOG_STMT_MODE_AND_ROW_ENGINE. */ er_binlog_stmt_mode_and_row_engine = 1665, /** * \brief Common server error. Error number: 1666, symbol: * ER_BINLOG_ROW_INJECTION_AND_STMT_MODE. */ er_binlog_row_injection_and_stmt_mode = 1666, /** * \brief Common server error. Error number: 1667, symbol: * ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE. */ er_binlog_multiple_engines_and_self_logging_engine = 1667, /** * \brief Common server error. Error number: 1668, symbol: * ER_BINLOG_UNSAFE_LIMIT. */ er_binlog_unsafe_limit = 1668, /** * \brief Common server error. Error number: 1670, symbol: * ER_BINLOG_UNSAFE_SYSTEM_TABLE. */ er_binlog_unsafe_system_table = 1670, /** * \brief Common server error. Error number: 1671, symbol: * ER_BINLOG_UNSAFE_AUTOINC_COLUMNS. */ er_binlog_unsafe_autoinc_columns = 1671, /** * \brief Common server error. Error number: 1672, symbol: * ER_BINLOG_UNSAFE_UDF. */ er_binlog_unsafe_udf = 1672, /** * \brief Common server error. Error number: 1673, symbol: * ER_BINLOG_UNSAFE_SYSTEM_VARIABLE. */ er_binlog_unsafe_system_variable = 1673, /** * \brief Common server error. Error number: 1674, symbol: * ER_BINLOG_UNSAFE_SYSTEM_FUNCTION. */ er_binlog_unsafe_system_function = 1674, /** * \brief Common server error. Error number: 1675, symbol: * ER_BINLOG_UNSAFE_NONTRANS_AFTER_TRANS. */ er_binlog_unsafe_nontrans_after_trans = 1675, /** * \brief Common server error. Error number: 1676, symbol: * ER_MESSAGE_AND_STATEMENT. */ er_message_and_statement = 1676, /** * \brief Common server error. Error number: 1677, symbol: * ER_SLAVE_CONVERSION_FAILED. */ er_slave_conversion_failed = 1677, /** * \brief Common server error. Error number: 1678, symbol: * ER_SLAVE_CANT_CREATE_CONVERSION. */ er_slave_cant_create_conversion = 1678, /** * \brief Common server error. Error number: 1679, symbol: * ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT. */ er_inside_transaction_prevents_switch_binlog_format = 1679, /** * \brief Common server error. Error number: 1680, symbol: * ER_PATH_LENGTH. */ er_path_length = 1680, /** * \brief Common server error. Error number: 1681, symbol: * ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT. */ er_warn_deprecated_syntax_no_replacement = 1681, /** * \brief Common server error. Error number: 1682, symbol: * ER_WRONG_NATIVE_TABLE_STRUCTURE. */ er_wrong_native_table_structure = 1682, /** * \brief Common server error. Error number: 1683, symbol: * ER_WRONG_PERFSCHEMA_USAGE. */ er_wrong_perfschema_usage = 1683, /** * \brief Common server error. Error number: 1684, symbol: * ER_WARN_I_S_SKIPPED_TABLE. */ er_warn_i_s_skipped_table = 1684, /** * \brief Common server error. Error number: 1685, symbol: * ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT. */ er_inside_transaction_prevents_switch_binlog_direct = 1685, /** * \brief Common server error. Error number: 1686, symbol: * ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT. */ er_stored_function_prevents_switch_binlog_direct = 1686, /** * \brief Common server error. Error number: 1687, symbol: * ER_SPATIAL_MUST_HAVE_GEOM_COL. */ er_spatial_must_have_geom_col = 1687, /** * \brief Common server error. Error number: 1688, symbol: * ER_TOO_LONG_INDEX_COMMENT. */ er_too_long_index_comment = 1688, /** * \brief Common server error. Error number: 1689, symbol: * ER_LOCK_ABORTED. */ er_lock_aborted = 1689, /** * \brief Common server error. Error number: 1690, symbol: * ER_DATA_OUT_OF_RANGE. */ er_data_out_of_range = 1690, /** * \brief Common server error. Error number: 1691, symbol: * ER_WRONG_SPVAR_TYPE_IN_LIMIT. */ er_wrong_spvar_type_in_limit = 1691, /** * \brief Common server error. Error number: 1692, symbol: * ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE. */ er_binlog_unsafe_multiple_engines_and_self_logging_engine = 1692, /** * \brief Common server error. Error number: 1693, symbol: * ER_BINLOG_UNSAFE_MIXED_STATEMENT. */ er_binlog_unsafe_mixed_statement = 1693, /** * \brief Common server error. Error number: 1694, symbol: * ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN. */ er_inside_transaction_prevents_switch_sql_log_bin = 1694, /** * \brief Common server error. Error number: 1695, symbol: * ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN. */ er_stored_function_prevents_switch_sql_log_bin = 1695, /** * \brief Common server error. Error number: 1696, symbol: * ER_FAILED_READ_FROM_PAR_FILE. */ er_failed_read_from_par_file = 1696, /** * \brief Common server error. Error number: 1697, symbol: * ER_VALUES_IS_NOT_INT_TYPE_ERROR. */ er_values_is_not_int_type_error = 1697, /** * \brief Common server error. Error number: 1698, symbol: * ER_ACCESS_DENIED_NO_PASSWORD_ERROR. */ er_access_denied_no_password_error = 1698, /** * \brief Common server error. Error number: 1699, symbol: * ER_SET_PASSWORD_AUTH_PLUGIN. */ er_set_password_auth_plugin = 1699, /** * \brief Common server error. Error number: 1700, symbol: * ER_GRANT_PLUGIN_USER_EXISTS. */ er_grant_plugin_user_exists = 1700, /** * \brief Common server error. Error number: 1701, symbol: * ER_TRUNCATE_ILLEGAL_FK. */ er_truncate_illegal_fk = 1701, /** * \brief Common server error. Error number: 1702, symbol: * ER_PLUGIN_IS_PERMANENT. */ er_plugin_is_permanent = 1702, /** * \brief Common server error. Error number: 1703, symbol: * ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN. */ er_slave_heartbeat_value_out_of_range_min = 1703, /** * \brief Common server error. Error number: 1704, symbol: * ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX. */ er_slave_heartbeat_value_out_of_range_max = 1704, /** * \brief Common server error. Error number: 1705, symbol: * ER_STMT_CACHE_FULL. */ er_stmt_cache_full = 1705, /** * \brief Common server error. Error number: 1706, symbol: * ER_MULTI_UPDATE_KEY_CONFLICT. */ er_multi_update_key_conflict = 1706, /** * \brief Common server error. Error number: 1707, symbol: * ER_TABLE_NEEDS_REBUILD. */ er_table_needs_rebuild = 1707, /** * \brief Common server error. Error number: 1708, symbol: * WARN_OPTION_BELOW_LIMIT. */ warn_option_below_limit = 1708, /** * \brief Common server error. Error number: 1709, symbol: * ER_INDEX_COLUMN_TOO_LONG. */ er_index_column_too_long = 1709, /** * \brief Common server error. Error number: 1710, symbol: * ER_ERROR_IN_TRIGGER_BODY. */ er_error_in_trigger_body = 1710, /** * \brief Common server error. Error number: 1711, symbol: * ER_ERROR_IN_UNKNOWN_TRIGGER_BODY. */ er_error_in_unknown_trigger_body = 1711, /** * \brief Common server error. Error number: 1712, symbol: * ER_INDEX_CORRUPT. */ er_index_corrupt = 1712, /** * \brief Common server error. Error number: 1713, symbol: * ER_UNDO_RECORD_TOO_BIG. */ er_undo_record_too_big = 1713, /** * \brief Common server error. Error number: 1714, symbol: * ER_BINLOG_UNSAFE_INSERT_IGNORE_SELECT. */ er_binlog_unsafe_insert_ignore_select = 1714, /** * \brief Common server error. Error number: 1715, symbol: * ER_BINLOG_UNSAFE_INSERT_SELECT_UPDATE. */ er_binlog_unsafe_insert_select_update = 1715, /** * \brief Common server error. Error number: 1716, symbol: * ER_BINLOG_UNSAFE_REPLACE_SELECT. */ er_binlog_unsafe_replace_select = 1716, /** * \brief Common server error. Error number: 1717, symbol: * ER_BINLOG_UNSAFE_CREATE_IGNORE_SELECT. */ er_binlog_unsafe_create_ignore_select = 1717, /** * \brief Common server error. Error number: 1718, symbol: * ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT. */ er_binlog_unsafe_create_replace_select = 1718, /** * \brief Common server error. Error number: 1719, symbol: * ER_BINLOG_UNSAFE_UPDATE_IGNORE. */ er_binlog_unsafe_update_ignore = 1719, /** * \brief Common server error. Error number: 1722, symbol: * ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT. */ er_binlog_unsafe_write_autoinc_select = 1722, /** * \brief Common server error. Error number: 1723, symbol: * ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC. */ er_binlog_unsafe_create_select_autoinc = 1723, /** * \brief Common server error. Error number: 1724, symbol: * ER_BINLOG_UNSAFE_INSERT_TWO_KEYS. */ er_binlog_unsafe_insert_two_keys = 1724, /** * \brief Common server error. Error number: 1727, symbol: * ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST. */ er_binlog_unsafe_autoinc_not_first = 1727, /** * \brief Common server error. Error number: 1728, symbol: * ER_CANNOT_LOAD_FROM_TABLE_V2. */ er_cannot_load_from_table_v2 = 1728, /** * \brief Common server error. Error number: 1729, symbol: * ER_MASTER_DELAY_VALUE_OUT_OF_RANGE. */ er_master_delay_value_out_of_range = 1729, /** * \brief Common server error. Error number: 1730, symbol: * ER_ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT. */ er_only_fd_and_rbr_events_allowed_in_binlog_statement = 1730, /** * \brief Common server error. Error number: 1731, symbol: * ER_PARTITION_EXCHANGE_DIFFERENT_OPTION. */ er_partition_exchange_different_option = 1731, /** * \brief Common server error. Error number: 1732, symbol: * ER_PARTITION_EXCHANGE_PART_TABLE. */ er_partition_exchange_part_table = 1732, /** * \brief Common server error. Error number: 1733, symbol: * ER_PARTITION_EXCHANGE_TEMP_TABLE. */ er_partition_exchange_temp_table = 1733, /** * \brief Common server error. Error number: 1734, symbol: * ER_PARTITION_INSTEAD_OF_SUBPARTITION. */ er_partition_instead_of_subpartition = 1734, /** * \brief Common server error. Error number: 1735, symbol: * ER_UNKNOWN_PARTITION. */ er_unknown_partition = 1735, /** * \brief Common server error. Error number: 1736, symbol: * ER_TABLES_DIFFERENT_METADATA. */ er_tables_different_metadata = 1736, /** * \brief Common server error. Error number: 1737, symbol: * ER_ROW_DOES_NOT_MATCH_PARTITION. */ er_row_does_not_match_partition = 1737, /** * \brief Common server error. Error number: 1738, symbol: * ER_BINLOG_CACHE_SIZE_GREATER_THAN_MAX. */ er_binlog_cache_size_greater_than_max = 1738, /** * \brief Common server error. Error number: 1739, symbol: * ER_WARN_INDEX_NOT_APPLICABLE. */ er_warn_index_not_applicable = 1739, /** * \brief Common server error. Error number: 1740, symbol: * ER_PARTITION_EXCHANGE_FOREIGN_KEY. */ er_partition_exchange_foreign_key = 1740, /** * \brief Common server error. Error number: 1741, symbol: * ER_NO_SUCH_KEY_VALUE. */ er_no_such_key_value = 1741, /** * \brief Common server error. Error number: 1743, symbol: * ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE. */ er_network_read_event_checksum_failure = 1743, /** * \brief Common server error. Error number: 1744, symbol: * ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE. */ er_binlog_read_event_checksum_failure = 1744, /** * \brief Common server error. Error number: 1745, symbol: * ER_BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX. */ er_binlog_stmt_cache_size_greater_than_max = 1745, /** * \brief Common server error. Error number: 1746, symbol: * ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT. */ er_cant_update_table_in_create_table_select = 1746, /** * \brief Common server error. Error number: 1747, symbol: * ER_PARTITION_CLAUSE_ON_NONPARTITIONED. */ er_partition_clause_on_nonpartitioned = 1747, /** * \brief Common server error. Error number: 1748, symbol: * ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET. */ er_row_does_not_match_given_partition_set = 1748, /** * \brief Common server error. Error number: 1750, symbol: * ER_CHANGE_RPL_INFO_REPOSITORY_FAILURE. */ er_change_rpl_info_repository_failure = 1750, /** * \brief Common server error. Error number: 1751, symbol: * ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE. */ er_warning_not_complete_rollback_with_created_temp_table = 1751, /** * \brief Common server error. Error number: 1752, symbol: * ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE. */ er_warning_not_complete_rollback_with_dropped_temp_table = 1752, /** * \brief Common server error. Error number: 1753, symbol: * ER_MTS_FEATURE_IS_NOT_SUPPORTED. */ er_mts_feature_is_not_supported = 1753, /** * \brief Common server error. Error number: 1754, symbol: * ER_MTS_UPDATED_DBS_GREATER_MAX. */ er_mts_updated_dbs_greater_max = 1754, /** * \brief Common server error. Error number: 1755, symbol: * ER_MTS_CANT_PARALLEL. */ er_mts_cant_parallel = 1755, /** * \brief Common server error. Error number: 1756, symbol: * ER_MTS_INCONSISTENT_DATA. */ er_mts_inconsistent_data = 1756, /** * \brief Common server error. Error number: 1757, symbol: * ER_FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING. */ er_fulltext_not_supported_with_partitioning = 1757, /** * \brief Common server error. Error number: 1758, symbol: * ER_DA_INVALID_CONDITION_NUMBER. */ er_da_invalid_condition_number = 1758, /** * \brief Common server error. Error number: 1759, symbol: * ER_INSECURE_PLAIN_TEXT. */ er_insecure_plain_text = 1759, /** * \brief Common server error. Error number: 1760, symbol: * ER_INSECURE_CHANGE_MASTER. */ er_insecure_change_master = 1760, /** * \brief Common server error. Error number: 1761, symbol: * ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO. */ er_foreign_duplicate_key_with_child_info = 1761, /** * \brief Common server error. Error number: 1762, symbol: * ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO. */ er_foreign_duplicate_key_without_child_info = 1762, /** * \brief Common server error. Error number: 1763, symbol: * ER_SQLTHREAD_WITH_SECURE_SLAVE. */ er_sqlthread_with_secure_slave = 1763, /** * \brief Common server error. Error number: 1764, symbol: * ER_TABLE_HAS_NO_FT. */ er_table_has_no_ft = 1764, /** * \brief Common server error. Error number: 1765, symbol: * ER_VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER. */ er_variable_not_settable_in_sf_or_trigger = 1765, /** * \brief Common server error. Error number: 1766, symbol: * ER_VARIABLE_NOT_SETTABLE_IN_TRANSACTION. */ er_variable_not_settable_in_transaction = 1766, /** * \brief Common server error. Error number: 1767, symbol: * ER_GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST. */ er_gtid_next_is_not_in_gtid_next_list = 1767, /** * \brief Common server error. Error number: 1769, symbol: * ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION. */ er_set_statement_cannot_invoke_function = 1769, /** * \brief Common server error. Error number: 1770, symbol: * ER_GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL. */ er_gtid_next_cant_be_automatic_if_gtid_next_list_is_non_null = 1770, /** * \brief Common server error. Error number: 1771, symbol: * ER_SKIPPING_LOGGED_TRANSACTION. */ er_skipping_logged_transaction = 1771, /** * \brief Common server error. Error number: 1772, symbol: * ER_MALFORMED_GTID_SET_SPECIFICATION. */ er_malformed_gtid_set_specification = 1772, /** * \brief Common server error. Error number: 1773, symbol: * ER_MALFORMED_GTID_SET_ENCODING. */ er_malformed_gtid_set_encoding = 1773, /** * \brief Common server error. Error number: 1774, symbol: * ER_MALFORMED_GTID_SPECIFICATION. */ er_malformed_gtid_specification = 1774, /** * \brief Common server error. Error number: 1775, symbol: * ER_GNO_EXHAUSTED. */ er_gno_exhausted = 1775, /** * \brief Common server error. Error number: 1776, symbol: * ER_BAD_SLAVE_AUTO_POSITION. */ er_bad_slave_auto_position = 1776, /** * \brief Common server error. Error number: 1778, symbol: * ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET. */ er_cant_do_implicit_commit_in_trx_when_gtid_next_is_set = 1778, /** * \brief Common server error. Error number: 1780, symbol: * ER_GTID_MODE_REQUIRES_BINLOG. */ er_gtid_mode_requires_binlog = 1780, /** * \brief Common server error. Error number: 1781, symbol: * ER_CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF. */ er_cant_set_gtid_next_to_gtid_when_gtid_mode_is_off = 1781, /** * \brief Common server error. Error number: 1782, symbol: * ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON. */ er_cant_set_gtid_next_to_anonymous_when_gtid_mode_is_on = 1782, /** * \brief Common server error. Error number: 1783, symbol: * ER_CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF. */ er_cant_set_gtid_next_list_to_non_null_when_gtid_mode_is_off = 1783, /** * \brief Common server error. Error number: 1785, symbol: * ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE. */ er_gtid_unsafe_non_transactional_table = 1785, /** * \brief Common server error. Error number: 1786, symbol: * ER_GTID_UNSAFE_CREATE_SELECT. */ er_gtid_unsafe_create_select = 1786, /** * \brief Common server error. Error number: 1787, symbol: * ER_GTID_UNSAFE_CREATE_DROP_TEMPORARY_TABLE_IN_TRANSACTION. */ er_gtid_unsafe_create_drop_temporary_table_in_transaction = 1787, /** * \brief Common server error. Error number: 1788, symbol: * ER_GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME. */ er_gtid_mode_can_only_change_one_step_at_a_time = 1788, /** * \brief Common server error. Error number: 1789, symbol: * ER_MASTER_HAS_PURGED_REQUIRED_GTIDS. */ er_master_has_purged_required_gtids = 1789, /** * \brief Common server error. Error number: 1790, symbol: * ER_CANT_SET_GTID_NEXT_WHEN_OWNING_GTID. */ er_cant_set_gtid_next_when_owning_gtid = 1790, /** * \brief Common server error. Error number: 1791, symbol: * ER_UNKNOWN_EXPLAIN_FORMAT. */ er_unknown_explain_format = 1791, /** * \brief Common server error. Error number: 1792, symbol: * ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION. */ er_cant_execute_in_read_only_transaction = 1792, /** * \brief Common server error. Error number: 1793, symbol: * ER_TOO_LONG_TABLE_PARTITION_COMMENT. */ er_too_long_table_partition_comment = 1793, /** * \brief Common server error. Error number: 1794, symbol: * ER_SLAVE_CONFIGURATION. */ er_slave_configuration = 1794, /** * \brief Common server error. Error number: 1795, symbol: * ER_INNODB_FT_LIMIT. */ er_innodb_ft_limit = 1795, /** * \brief Common server error. Error number: 1796, symbol: * ER_INNODB_NO_FT_TEMP_TABLE. */ er_innodb_no_ft_temp_table = 1796, /** * \brief Common server error. Error number: 1797, symbol: * ER_INNODB_FT_WRONG_DOCID_COLUMN. */ er_innodb_ft_wrong_docid_column = 1797, /** * \brief Common server error. Error number: 1798, symbol: * ER_INNODB_FT_WRONG_DOCID_INDEX. */ er_innodb_ft_wrong_docid_index = 1798, /** * \brief Common server error. Error number: 1799, symbol: * ER_INNODB_ONLINE_LOG_TOO_BIG. */ er_innodb_online_log_too_big = 1799, /** * \brief Common server error. Error number: 1800, symbol: * ER_UNKNOWN_ALTER_ALGORITHM. */ er_unknown_alter_algorithm = 1800, /** * \brief Common server error. Error number: 1801, symbol: * ER_UNKNOWN_ALTER_LOCK. */ er_unknown_alter_lock = 1801, /** * \brief Common server error. Error number: 1802, symbol: * ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS. */ er_mts_change_master_cant_run_with_gaps = 1802, /** * \brief Common server error. Error number: 1803, symbol: * ER_MTS_RECOVERY_FAILURE. */ er_mts_recovery_failure = 1803, /** * \brief Common server error. Error number: 1804, symbol: * ER_MTS_RESET_WORKERS. */ er_mts_reset_workers = 1804, /** * \brief Common server error. Error number: 1805, symbol: * ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2. */ er_col_count_doesnt_match_corrupted_v2 = 1805, /** * \brief Common server error. Error number: 1806, symbol: * ER_SLAVE_SILENT_RETRY_TRANSACTION. */ er_slave_silent_retry_transaction = 1806, /** * \brief Common server error. Error number: 1808, symbol: * ER_TABLE_SCHEMA_MISMATCH. */ er_table_schema_mismatch = 1808, /** * \brief Common server error. Error number: 1809, symbol: * ER_TABLE_IN_SYSTEM_TABLESPACE. */ er_table_in_system_tablespace = 1809, /** * \brief Common server error. Error number: 1810, symbol: * ER_IO_READ_ERROR. */ er_io_read_error = 1810, /** * \brief Common server error. Error number: 1811, symbol: * ER_IO_WRITE_ERROR. */ er_io_write_error = 1811, /** * \brief Common server error. Error number: 1812, symbol: * ER_TABLESPACE_MISSING. */ er_tablespace_missing = 1812, /** * \brief Common server error. Error number: 1813, symbol: * ER_TABLESPACE_EXISTS. */ er_tablespace_exists = 1813, /** * \brief Common server error. Error number: 1814, symbol: * ER_TABLESPACE_DISCARDED. */ er_tablespace_discarded = 1814, /** * \brief Common server error. Error number: 1815, symbol: * ER_INTERNAL_ERROR. */ er_internal_error = 1815, /** * \brief Common server error. Error number: 1816, symbol: * ER_INNODB_IMPORT_ERROR. */ er_innodb_import_error = 1816, /** * \brief Common server error. Error number: 1817, symbol: * ER_INNODB_INDEX_CORRUPT. */ er_innodb_index_corrupt = 1817, /** * \brief Common server error. Error number: 1818, symbol: * ER_INVALID_YEAR_COLUMN_LENGTH. */ er_invalid_year_column_length = 1818, /** * \brief Common server error. Error number: 1819, symbol: * ER_NOT_VALID_PASSWORD. */ er_not_valid_password = 1819, /** * \brief Common server error. Error number: 1820, symbol: * ER_MUST_CHANGE_PASSWORD. */ er_must_change_password = 1820, /** * \brief Common server error. Error number: 1821, symbol: * ER_FK_NO_INDEX_CHILD. */ er_fk_no_index_child = 1821, /** * \brief Common server error. Error number: 1822, symbol: * ER_FK_NO_INDEX_PARENT. */ er_fk_no_index_parent = 1822, /** * \brief Common server error. Error number: 1823, symbol: * ER_FK_FAIL_ADD_SYSTEM. */ er_fk_fail_add_system = 1823, /** * \brief Common server error. Error number: 1824, symbol: * ER_FK_CANNOT_OPEN_PARENT. */ er_fk_cannot_open_parent = 1824, /** * \brief Common server error. Error number: 1825, symbol: * ER_FK_INCORRECT_OPTION. */ er_fk_incorrect_option = 1825, /** * \brief Common server error. Error number: 1827, symbol: * ER_PASSWORD_FORMAT. */ er_password_format = 1827, /** * \brief Common server error. Error number: 1828, symbol: * ER_FK_COLUMN_CANNOT_DROP. */ er_fk_column_cannot_drop = 1828, /** * \brief Common server error. Error number: 1829, symbol: * ER_FK_COLUMN_CANNOT_DROP_CHILD. */ er_fk_column_cannot_drop_child = 1829, /** * \brief Common server error. Error number: 1830, symbol: * ER_FK_COLUMN_NOT_NULL. */ er_fk_column_not_null = 1830, /** * \brief Common server error. Error number: 1831, symbol: * ER_DUP_INDEX. */ er_dup_index = 1831, /** * \brief Common server error. Error number: 1832, symbol: * ER_FK_COLUMN_CANNOT_CHANGE. */ er_fk_column_cannot_change = 1832, /** * \brief Common server error. Error number: 1833, symbol: * ER_FK_COLUMN_CANNOT_CHANGE_CHILD. */ er_fk_column_cannot_change_child = 1833, /** * \brief Common server error. Error number: 1835, symbol: * ER_MALFORMED_PACKET. */ er_malformed_packet = 1835, /** * \brief Common server error. Error number: 1836, symbol: * ER_READ_ONLY_MODE. */ er_read_only_mode = 1836, /** * \brief Common server error. Error number: 1838, symbol: * ER_VARIABLE_NOT_SETTABLE_IN_SP. */ er_variable_not_settable_in_sp = 1838, /** * \brief Common server error. Error number: 1839, symbol: * ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF. */ er_cant_set_gtid_purged_when_gtid_mode_is_off = 1839, /** * \brief Common server error. Error number: 1840, symbol: * ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY. */ er_cant_set_gtid_purged_when_gtid_executed_is_not_empty = 1840, /** * \brief Common server error. Error number: 1841, symbol: * ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY. */ er_cant_set_gtid_purged_when_owned_gtids_is_not_empty = 1841, /** * \brief Common server error. Error number: 1842, symbol: * ER_GTID_PURGED_WAS_CHANGED. */ er_gtid_purged_was_changed = 1842, /** * \brief Common server error. Error number: 1843, symbol: * ER_GTID_EXECUTED_WAS_CHANGED. */ er_gtid_executed_was_changed = 1843, /** * \brief Common server error. Error number: 1844, symbol: * ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES. */ er_binlog_stmt_mode_and_no_repl_tables = 1844, /** * \brief Common server error. Error number: 1845, symbol: * ER_ALTER_OPERATION_NOT_SUPPORTED. */ er_alter_operation_not_supported = 1845, /** * \brief Common server error. Error number: 1846, symbol: * ER_ALTER_OPERATION_NOT_SUPPORTED_REASON. */ er_alter_operation_not_supported_reason = 1846, /** * \brief Common server error. Error number: 1847, symbol: * ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY. */ er_alter_operation_not_supported_reason_copy = 1847, /** * \brief Common server error. Error number: 1848, symbol: * ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION. */ er_alter_operation_not_supported_reason_partition = 1848, /** * \brief Common server error. Error number: 1849, symbol: * ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME. */ er_alter_operation_not_supported_reason_fk_rename = 1849, /** * \brief Common server error. Error number: 1850, symbol: * ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE. */ er_alter_operation_not_supported_reason_column_type = 1850, /** * \brief Common server error. Error number: 1851, symbol: * ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK. */ er_alter_operation_not_supported_reason_fk_check = 1851, /** * \brief Common server error. Error number: 1853, symbol: * ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK. */ er_alter_operation_not_supported_reason_nopk = 1853, /** * \brief Common server error. Error number: 1854, symbol: * ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC. */ er_alter_operation_not_supported_reason_autoinc = 1854, /** * \brief Common server error. Error number: 1855, symbol: * ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS. */ er_alter_operation_not_supported_reason_hidden_fts = 1855, /** * \brief Common server error. Error number: 1856, symbol: * ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS. */ er_alter_operation_not_supported_reason_change_fts = 1856, /** * \brief Common server error. Error number: 1857, symbol: * ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS. */ er_alter_operation_not_supported_reason_fts = 1857, /** * \brief Common server error. Error number: 1858, symbol: * ER_SQL_SLAVE_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE. */ er_sql_slave_skip_counter_not_settable_in_gtid_mode = 1858, /** * \brief Common server error. Error number: 1859, symbol: * ER_DUP_UNKNOWN_IN_INDEX. */ er_dup_unknown_in_index = 1859, /** * \brief Common server error. Error number: 1860, symbol: * ER_IDENT_CAUSES_TOO_LONG_PATH. */ er_ident_causes_too_long_path = 1860, /** * \brief Common server error. Error number: 1861, symbol: * ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL. */ er_alter_operation_not_supported_reason_not_null = 1861, /** * \brief Common server error. Error number: 1862, symbol: * ER_MUST_CHANGE_PASSWORD_LOGIN. */ er_must_change_password_login = 1862, /** * \brief Common server error. Error number: 1863, symbol: * ER_ROW_IN_WRONG_PARTITION. */ er_row_in_wrong_partition = 1863, /** * \brief Common server error. Error number: 1864, symbol: * ER_MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX. */ er_mts_event_bigger_pending_jobs_size_max = 1864, /** * \brief Common server error. Error number: 1865, symbol: * ER_INNODB_NO_FT_USES_PARSER. */ er_innodb_no_ft_uses_parser = 1865, /** * \brief Common server error. Error number: 1866, symbol: * ER_BINLOG_LOGICAL_CORRUPTION. */ er_binlog_logical_corruption = 1866, /** * \brief Common server error. Error number: 1867, symbol: * ER_WARN_PURGE_LOG_IN_USE. */ er_warn_purge_log_in_use = 1867, /** * \brief Common server error. Error number: 1868, symbol: * ER_WARN_PURGE_LOG_IS_ACTIVE. */ er_warn_purge_log_is_active = 1868, /** * \brief Common server error. Error number: 1869, symbol: * ER_AUTO_INCREMENT_CONFLICT. */ er_auto_increment_conflict = 1869, /** * \brief Common server error. Error number: 1870, symbol: * WARN_ON_BLOCKHOLE_IN_RBR. */ warn_on_blockhole_in_rbr = 1870, /** * \brief Common server error. Error number: 1871, symbol: * ER_SLAVE_MI_INIT_REPOSITORY. */ er_slave_mi_init_repository = 1871, /** * \brief Common server error. Error number: 1872, symbol: * ER_SLAVE_RLI_INIT_REPOSITORY. */ er_slave_rli_init_repository = 1872, /** * \brief Common server error. Error number: 1873, symbol: * ER_ACCESS_DENIED_CHANGE_USER_ERROR. */ er_access_denied_change_user_error = 1873, /** * \brief Common server error. Error number: 1874, symbol: * ER_INNODB_READ_ONLY. */ er_innodb_read_only = 1874, /** * \brief Common server error. Error number: 1875, symbol: * ER_STOP_SLAVE_SQL_THREAD_TIMEOUT. */ er_stop_slave_sql_thread_timeout = 1875, /** * \brief Common server error. Error number: 1876, symbol: * ER_STOP_SLAVE_IO_THREAD_TIMEOUT. */ er_stop_slave_io_thread_timeout = 1876, /** * \brief Common server error. Error number: 1877, symbol: * ER_TABLE_CORRUPT. */ er_table_corrupt = 1877, /** * \brief Common server error. Error number: 1878, symbol: * ER_TEMP_FILE_WRITE_FAILURE. */ er_temp_file_write_failure = 1878, /** * \brief Common server error. Error number: 1879, symbol: * ER_INNODB_FT_AUX_NOT_HEX_ID. */ er_innodb_ft_aux_not_hex_id = 1879, }; BOOST_MYSQL_DECL const boost::system::error_category& get_common_server_category() noexcept; /// Creates an \ref error_code from a \ref common_server_errc. inline error_code make_error_code(common_server_errc error) { return error_code(static_cast(error), get_common_server_category()); } } // namespace mysql #ifndef BOOST_MYSQL_DOXYGEN namespace system { template <> struct is_error_code_enum<::boost::mysql::common_server_errc> { static constexpr bool value = true; }; } // namespace system #endif } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/connect_params.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_CONNECT_PARAMS_HPP #define BOOST_MYSQL_CONNECT_PARAMS_HPP #include #include #include #include #include namespace boost { namespace mysql { /** * \brief Parameters to be used with \ref any_connection connect functions. * \details * To be passed to \ref any_connection::connect and \ref any_connection::async_connect. * Includes the server address and MySQL handshake parameters. This is an owning type. */ struct connect_params { /** * \brief Determines how to establish a physical connection to the MySQL server. * \details * This can be either a host and port or a UNIX socket path. * Defaults to (localhost, 3306). */ any_address server_address; /// User name to authenticate as. std::string username; /// Password for that username, possibly empty. std::string password; /// Database name to use, or empty string for no database (this is the default). std::string database; /** * \brief The ID of the collation to use for the connection. * \details Impacts how text queries and prepared statements are interpreted. Defaults to * `utf8mb4_general_ci`, which is compatible with MySQL 5.x, 8.x and MariaDB. */ std::uint16_t connection_collation{45}; /** * \brief Controls whether to use TLS or not. * \details * See \ref ssl_mode for more information about the possible modes. * This option is only relevant when `server_address.type() == address_type::host_and_port`. * UNIX socket connections will never use TLS, regardless of this value. */ ssl_mode ssl{ssl_mode::enable}; /** * \brief Whether to enable support for executing semicolon-separated text queries. * \details Disabled by default. */ bool multi_queries{false}; }; } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/connection.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_CONNECTION_HPP #define BOOST_MYSQL_CONNECTION_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /// The Boost libraries namespace. namespace boost { /// Boost.MySQL library namespace. namespace mysql { // Forward declarations template class static_execution_state; /** * \brief (Legacy) A connection to a MySQL server. * \details * Represents a templated connection to a MySQL server. * * `connection` owns a `Stream` object that * is accessed by functions involving network operations, as well as session state. You can access * the stream using \ref connection::stream, and its executor via \ref connection::get_executor. The * executor used by this object is always the same as the underlying stream. * * \par Single outstanding async operation per connection * At any given point in time, only one async operation can be outstanding * per connection. If an async operation is initiated while another one is in progress, * it will fail with \ref client_errc::operation_in_progress. * * \par Thread safety * Distinct objects: safe. \n * Shared objects: unsafe. \n * This class is not thread-safe: for a single object, if you * call its member functions concurrently from separate threads, you will get a race condition. * * \par Legacy * New code should use \ref any_connection instead of this class, as it's simpler to use * and provides the same level of performance. */ template class connection { detail::connection_impl impl_; public: /** * \brief Initializing constructor. * \details * As part of the initialization, an internal `Stream` object is created. * * \par Exception safety * Basic guarantee. Throws if the `Stream` constructor throws * or if memory allocation for internal state fails. * * \param args Arguments to be forwarded to the `Stream` constructor. */ template < class... Args, class EnableIf = typename std::enable_if::value>::type> connection(Args&&... args) : connection(buffer_params(), std::forward(args)...) { } /** * \brief Initializing constructor with buffer params. * \details * As part of the initialization, an internal `Stream` object is created. * * \par Exception safety * Basic guarantee. Throws if the `Stream` constructor throws * or if memory allocation for internal state fails. * * \param buff_params Specifies initial sizes for internal buffers. * \param args Arguments to be forwarded to the `Stream` constructor. */ template < class... Args, class EnableIf = typename std::enable_if::value>::type> connection(const buffer_params& buff_params, Args&&... args) : impl_( buff_params.initial_read_size(), static_cast(-1), detail::make_engine(std::forward(args)...) ) { } /** * \brief Move constructor. */ connection(connection&& other) = default; /** * \brief Move assignment. */ connection& operator=(connection&& rhs) = default; #ifndef BOOST_MYSQL_DOXYGEN connection(const connection&) = delete; connection& operator=(const connection&) = delete; #endif /// The executor type associated to this object. using executor_type = typename Stream::executor_type; /// Retrieves the executor associated to this object. executor_type get_executor() { return stream().get_executor(); } /// The `Stream` type this connection is using. using stream_type = Stream; /** * \brief Retrieves the underlying Stream object. * \details * * \par Exception safety * No-throw guarantee. */ Stream& stream() noexcept { return detail::stream_from_engine(impl_.get_engine()); } /** * \brief Retrieves the underlying Stream object. * \details * * \par Exception safety * No-throw guarantee. */ const Stream& stream() const noexcept { return detail::stream_from_engine(impl_.get_engine()); } /** * \brief Returns whether the connection negotiated the use of SSL or not. * \details * This function can be used to determine whether you are using a SSL * connection or not when using SSL negotiation. * \n * This function always returns `false` if the underlying * stream does not support SSL. This function always returns `false` * for connections that haven't been * established yet (handshake not run yet). If the handshake fails, * the return value is undefined. * * \par Exception safety * No-throw guarantee. * * \returns Whether the connection is using SSL. */ bool uses_ssl() const noexcept { return impl_.ssl_active(); } /// \copydoc any_connection::meta_mode metadata_mode meta_mode() const noexcept { return impl_.meta_mode(); } /// \copydoc any_connection::set_meta_mode void set_meta_mode(metadata_mode v) noexcept { impl_.set_meta_mode(v); } /** * \brief Establishes a connection to a MySQL server. * \details * This function is only available if `Stream` satisfies the * `SocketStream` concept. * \n * Connects the underlying stream and performs the handshake * with the server. The underlying stream is closed in case of error. Prefer * this function to \ref connection::handshake. * \n * If using a SSL-capable stream, the SSL handshake will be performed by this function. * \n * `endpoint` should be convertible to `Stream::lowest_layer_type::endpoint_type`. */ template void connect( const EndpointType& endpoint, const handshake_params& params, error_code& ec, diagnostics& diag ) { static_assert( detail::is_socket_stream::value, "connect can only be used if Stream satisfies the SocketStream concept" ); impl_.connect(endpoint, params, ec, diag); } /// \copydoc connect template void connect(const EndpointType& endpoint, const handshake_params& params) { static_assert( detail::is_socket_stream::value, "connect can only be used if Stream satisfies the SocketStream concept" ); error_code err; diagnostics diag; connect(endpoint, params, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc connect * \par Object lifetimes * 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. * `endpoint` is copied as required and doesn't need to be kept alive. * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template < typename EndpointType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code)) async_connect( const EndpointType& endpoint, const handshake_params& params, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) { static_assert( detail::is_socket_stream::value, "async_connect can only be used if Stream satisfies the SocketStream concept" ); return async_connect(endpoint, params, impl_.shared_diag(), std::forward(token)); } /// \copydoc async_connect template < typename EndpointType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code)) async_connect( const EndpointType& endpoint, const handshake_params& params, diagnostics& diag, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) { static_assert( detail::is_socket_stream::value, "async_connect can only be used if Stream satisfies the SocketStream concept" ); return impl_.async_connect( endpoint, params, diag, std::forward(token) ); } /** * \brief Performs the MySQL-level handshake. * \details * Does not connect the underlying stream. * If the `Stream` template parameter fulfills the `SocketConnection` * requirements, use \ref connection::connect instead of this function. * \n * If using a SSL-capable stream, the SSL handshake will be performed by this function. */ void handshake(const handshake_params& params, error_code& ec, diagnostics& diag) { impl_.run(impl_.make_params_handshake(params), ec, diag); } /// \copydoc handshake void handshake(const handshake_params& params) { error_code err; diagnostics diag; handshake(params, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc handshake * \par Object lifetimes * 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. * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template auto async_handshake( const handshake_params& params, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_handshake_t) { return async_handshake(params, impl_.shared_diag(), std::forward(token)); } /// \copydoc async_handshake template auto async_handshake( const handshake_params& params, diagnostics& diag, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_handshake_t) { return impl_ .async_run(impl_.make_params_handshake(params), diag, std::forward(token)); } /// \copydoc any_connection::execute template void execute(ExecutionRequest&& req, ResultsType& result, error_code& err, diagnostics& diag) { impl_.execute(std::forward(req), result, err, diag); } /// \copydoc execute template void execute(ExecutionRequest&& req, ResultsType& result) { error_code err; diagnostics diag; execute(std::forward(req), result, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /// \copydoc any_connection::async_execute template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_RESULTS_TYPE ResultsType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> auto async_execute( ExecutionRequest&& req, ResultsType& result, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_execute_t) { return async_execute( std::forward(req), result, impl_.shared_diag(), std::forward(token) ); } /// \copydoc async_execute template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_RESULTS_TYPE ResultsType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> auto async_execute( ExecutionRequest&& req, ResultsType& result, diagnostics& diag, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_execute_t) { return impl_.async_execute( std::forward(req), result, diag, std::forward(token) ); } /// \copydoc any_connection::start_execution template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType> void start_execution(ExecutionRequest&& req, ExecutionStateType& st, error_code& err, diagnostics& diag) { impl_.start_execution(std::forward(req), st, err, diag); } /// \copydoc start_execution template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType> void start_execution(ExecutionRequest&& req, ExecutionStateType& st) { error_code err; diagnostics diag; start_execution(std::forward(req), st, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /// \copydoc any_connection::async_start_execution template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> auto async_start_execution( ExecutionRequest&& req, ExecutionStateType& st, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_start_execution_t< ExecutionRequest&&, ExecutionStateType, CompletionToken&&>) { return async_start_execution( std::forward(req), st, impl_.shared_diag(), std::forward(token) ); } /// \copydoc async_start_execution template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> auto async_start_execution( ExecutionRequest&& req, ExecutionStateType& st, diagnostics& diag, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_start_execution_t< ExecutionRequest&&, ExecutionStateType, CompletionToken&&>) { return impl_.async_start_execution( std::forward(req), st, diag, std::forward(token) ); } /// \copydoc any_connection::prepare_statement statement prepare_statement(string_view stmt, error_code& err, diagnostics& diag) { return impl_.run(detail::prepare_statement_algo_params{stmt}, err, diag); } /// \copydoc prepare_statement statement prepare_statement(string_view stmt) { error_code err; diagnostics diag; statement res = prepare_statement(stmt, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); return res; } /// \copydoc any_connection::async_prepare_statement template auto async_prepare_statement( string_view stmt, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_prepare_statement_t) { return async_prepare_statement(stmt, impl_.shared_diag(), std::forward(token)); } /// \copydoc async_prepare_statement template auto async_prepare_statement( string_view stmt, diagnostics& diag, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_prepare_statement_t) { return impl_.async_run( detail::prepare_statement_algo_params{stmt}, diag, std::forward(token) ); } /// \copydoc any_connection::close_statement void close_statement(const statement& stmt, error_code& err, diagnostics& diag) { impl_.run(impl_.make_params_close_statement(stmt), err, diag); } /// \copydoc close_statement void close_statement(const statement& stmt) { error_code err; diagnostics diag; close_statement(stmt, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /// \copydoc any_connection::async_close_statement template auto async_close_statement( const statement& stmt, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_close_statement_t) { return async_close_statement(stmt, impl_.shared_diag(), std::forward(token)); } /// \copydoc async_close_statement template auto async_close_statement( const statement& stmt, diagnostics& diag, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_close_statement_t) { return impl_ .async_run(impl_.make_params_close_statement(stmt), diag, std::forward(token)); } /// \copydoc any_connection::read_some_rows rows_view read_some_rows(execution_state& st, error_code& err, diagnostics& diag) { return impl_.run(impl_.make_params_read_some_rows(st), err, diag); } /// \copydoc read_some_rows(execution_state&,error_code&,diagnostics&) rows_view read_some_rows(execution_state& st) { error_code err; diagnostics diag; rows_view res = read_some_rows(st, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); return res; } /// \copydoc any_connection::async_read_some_rows(execution_state&,CompletionToken&&) template auto async_read_some_rows( execution_state& st, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_read_some_rows_dynamic_t) { return async_read_some_rows(st, impl_.shared_diag(), std::forward(token)); } /// \copydoc async_read_some_rows(execution_state&,CompletionToken&&) template auto async_read_some_rows( execution_state& st, diagnostics& diag, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_read_some_rows_dynamic_t) { return impl_ .async_run(impl_.make_params_read_some_rows(st), diag, std::forward(token)); } #ifdef BOOST_MYSQL_CXX14 /** * \brief Reads a batch of rows. * \details * Reads a batch of rows of unspecified size into the storage given by `output`. * At most `output.size()` rows will be read. If the operation represented by `st` * has still rows to read, and `output.size() > 0`, at least one row will be read. * \n * Returns the number of read rows. * \n * If there are no more rows, or `st.should_read_rows() == false`, this function is a no-op and returns * zero. * \n * The number of rows that will be read depends on the connection's buffer size. The bigger the buffer, * the greater the batch size (up to a maximum). You can set the initial buffer size in `connection`'s * constructor, using \ref buffer_params::initial_read_size. The buffer may be * grown bigger by other read operations, if required. * \n * Rows read by this function are owning objects, and don't hold any reference to * the connection's internal buffers (contrary what happens with the dynamic interface's counterpart). * \n * The type `SpanElementType` must be the underlying row type for one of the types in the * `StaticRow` parameter pack (i.e., one of the types in `underlying_row_t...`). * The type must match the resultset that is currently being processed by `st`. For instance, * given `static_execution_state`, when reading rows for the second resultset, `SpanElementType` * must exactly be `underlying_row_t`. If this is not the case, a runtime error will be issued. * \n * This function can report schema mismatches. */ template std::size_t read_some_rows( static_execution_state& st, span output, error_code& err, diagnostics& diag ) { return impl_.run(impl_.make_params_read_some_rows_static(st, output), err, diag); } /** * \brief Reads a batch of rows. * \details * Reads a batch of rows of unspecified size into the storage given by `output`. * At most `output.size()` rows will be read. If the operation represented by `st` * has still rows to read, and `output.size() > 0`, at least one row will be read. * \n * Returns the number of read rows. * \n * If there are no more rows, or `st.should_read_rows() == false`, this function is a no-op and returns * zero. * \n * The number of rows that will be read depends on the connection's buffer size. The bigger the buffer, * the greater the batch size (up to a maximum). You can set the initial buffer size in `connection`'s * constructor, using \ref buffer_params::initial_read_size. The buffer may be * grown bigger by other read operations, if required. * \n * Rows read by this function are owning objects, and don't hold any reference to * the connection's internal buffers (contrary what happens with the dynamic interface's counterpart). * \n * The type `SpanElementType` must be the underlying row type for one of the types in the * `StaticRow` parameter pack (i.e., one of the types in `underlying_row_t...`). * The type must match the resultset that is currently being processed by `st`. For instance, * given `static_execution_state`, when reading rows for the second resultset, `SpanElementType` * must exactly be `underlying_row_t`. If this is not the case, a runtime error will be issued. * \n * This function can report schema mismatches. */ template std::size_t read_some_rows(static_execution_state& st, span output) { error_code err; diagnostics diag; std::size_t res = read_some_rows(st, output, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); return res; } /** * \brief Reads a batch of rows. * \details * Reads a batch of rows of unspecified size into the storage given by `output`. * At most `output.size()` rows will be read. If the operation represented by `st` * has still rows to read, and `output.size() > 0`, at least one row will be read. * \n * Returns the number of read rows. * \n * If there are no more rows, or `st.should_read_rows() == false`, this function is a no-op and returns * zero. * \n * The number of rows that will be read depends on the connection's buffer size. The bigger the buffer, * the greater the batch size (up to a maximum). You can set the initial buffer size in `connection`'s * constructor, using \ref buffer_params::initial_read_size. The buffer may be * grown bigger by other read operations, if required. * \n * Rows read by this function are owning objects, and don't hold any reference to * the connection's internal buffers (contrary what happens with the dynamic interface's counterpart). * \n * The type `SpanElementType` must be the underlying row type for one of the types in the * `StaticRow` parameter pack (i.e., one of the types in `underlying_row_t...`). * The type must match the resultset that is currently being processed by `st`. For instance, * given `static_execution_state`, when reading rows for the second resultset, `SpanElementType` * must exactly be `underlying_row_t`. If this is not the case, a runtime error will be issued. * \n * This function can report schema mismatches. * * \par Handler signature * The handler signature for this operation is * `void(boost::mysql::error_code, std::size_t)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. * * \par Object lifetimes * The storage that `output` references must be kept alive until the operation completes. */ template < class SpanElementType, class... StaticRow, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, std::size_t)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::size_t)) async_read_some_rows( static_execution_state& st, span output, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) { return async_read_some_rows(st, output, impl_.shared_diag(), std::forward(token)); } /** * \brief Reads a batch of rows. * \details * Reads a batch of rows of unspecified size into the storage given by `output`. * At most `output.size()` rows will be read. If the operation represented by `st` * has still rows to read, and `output.size() > 0`, at least one row will be read. * \n * Returns the number of read rows. * \n * If there are no more rows, or `st.should_read_rows() == false`, this function is a no-op and returns * zero. * \n * The number of rows that will be read depends on the connection's buffer size. The bigger the buffer, * the greater the batch size (up to a maximum). You can set the initial buffer size in `connection`'s * constructor, using \ref buffer_params::initial_read_size. The buffer may be * grown bigger by other read operations, if required. * \n * Rows read by this function are owning objects, and don't hold any reference to * the connection's internal buffers (contrary what happens with the dynamic interface's counterpart). * \n * The type `SpanElementType` must be the underlying row type for one of the types in the * `StaticRow` parameter pack (i.e., one of the types in `underlying_row_t...`). * The type must match the resultset that is currently being processed by `st`. For instance, * given `static_execution_state`, when reading rows for the second resultset, `SpanElementType` * must exactly be `underlying_row_t`. If this is not the case, a runtime error will be issued. * \n * This function can report schema mismatches. * * \par Handler signature * The handler signature for this operation is * `void(boost::mysql::error_code, std::size_t)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. * * \par Object lifetimes * The storage that `output` references must be kept alive until the operation completes. */ template < class SpanElementType, class... StaticRow, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, std::size_t)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::size_t)) async_read_some_rows( static_execution_state& st, span output, diagnostics& diag, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) { return impl_.async_run( impl_.make_params_read_some_rows_static(st, output), diag, std::forward(token) ); } #endif /// \copydoc any_connection::read_resultset_head template void read_resultset_head(ExecutionStateType& st, error_code& err, diagnostics& diag) { return impl_.run(impl_.make_params_read_resultset_head(st), err, diag); } /// \copydoc read_resultset_head template void read_resultset_head(ExecutionStateType& st) { error_code err; diagnostics diag; read_resultset_head(st, err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /// \copydoc any_connection::async_read_resultset_head template < BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> auto async_read_resultset_head( ExecutionStateType& st, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_read_resultset_head_t) { return async_read_resultset_head(st, impl_.shared_diag(), std::forward(token)); } /// \copydoc async_read_resultset_head template < BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> auto async_read_resultset_head( ExecutionStateType& st, diagnostics& diag, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_read_resultset_head_t) { return impl_ .async_run(impl_.make_params_read_resultset_head(st), diag, std::forward(token)); } /// \copydoc any_connection::ping void ping(error_code& err, diagnostics& diag) { impl_.run(detail::ping_algo_params{}, err, diag); } /// \copydoc ping void ping() { error_code err; diagnostics diag; ping(err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /// \copydoc any_connection::async_ping template auto async_ping(CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) BOOST_MYSQL_RETURN_TYPE(detail::async_ping_t) { return async_ping(impl_.shared_diag(), std::forward(token)); } /// \copydoc async_ping template auto async_ping( diagnostics& diag, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_ping_t) { return impl_.async_run(detail::ping_algo_params{}, diag, std::forward(token)); } /** * \brief Resets server-side session state, like variables and prepared statements. * \details * Resets all server-side state for the current session: * \n * \li Rolls back any active transactions and resets autocommit mode. * \li Releases all table locks. * \li Drops all temporary tables. * \li Resets all session system variables to their default values (including the ones set by `SET * NAMES`) and clears all user-defined variables. * \li Closes all prepared statements. * \n * A full reference on the affected session state can be found * here. * \n * This function will not reset the current physical connection and won't cause re-authentication. * It is faster than closing and re-opening a connection. * \n * The connection must be connected and authenticated before calling this function. * This function involves communication with the server, and thus may fail. * * \par Warning on character sets * This function will restore the connection's character set and collation **to the server's default**, * and not to the one specified during connection establishment. Some servers have `latin1` as their * default character set, which is not usually what you want. Use a `SET NAMES` statement after using * this function to be sure. * \n * You can find the character set that your server will use after reset by running: * \code * "SELECT @@global.character_set_client, @@global.character_set_results;" * \endcode */ void reset_connection(error_code& err, diagnostics& diag) { impl_.run(detail::reset_connection_algo_params{}, err, diag); } /// \copydoc reset_connection void reset_connection() { error_code err; diagnostics diag; reset_connection(err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc reset_connection * \details * \n * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template auto async_reset_connection(CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) BOOST_MYSQL_RETURN_TYPE(detail::async_reset_connection_t) { return async_reset_connection(impl_.shared_diag(), std::forward(token)); } /// \copydoc async_reset_connection template auto async_reset_connection( diagnostics& diag, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_reset_connection_t) { return impl_ .async_run(detail::reset_connection_algo_params{}, diag, std::forward(token)); } /** * \brief Closes the connection to the server. * \details * This function is only available if `Stream` satisfies the `SocketStream` concept. * \n * Sends a quit request, performs the TLS shutdown (if required) * and closes the underlying stream. Prefer this function to \ref connection::quit. * \n */ void close(error_code& err, diagnostics& diag) { static_assert( detail::is_socket_stream::value, "close can only be used if Stream satisfies the SocketStream concept" ); impl_.run(detail::close_connection_algo_params{}, err, diag); } /// \copydoc close void close() { static_assert( detail::is_socket_stream::value, "close can only be used if Stream satisfies the SocketStream concept" ); error_code err; diagnostics diag; close(err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc close * \details * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template auto async_close(CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) BOOST_MYSQL_RETURN_TYPE(detail::async_close_connection_t) { static_assert( detail::is_socket_stream::value, "async_close can only be used if Stream satisfies the SocketStream concept" ); return async_close(impl_.shared_diag(), std::forward(token)); } /// \copydoc async_close template auto async_close( diagnostics& diag, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_close_connection_t) { static_assert( detail::is_socket_stream::value, "async_close can only be used if Stream satisfies the SocketStream concept" ); return impl_ .async_run(detail::close_connection_algo_params{}, diag, std::forward(token)); } /** * \brief Notifies the MySQL server that the client wants to end the session and shutdowns SSL. * \details Sends a quit request to the MySQL server. If the connection is using SSL, * this function will also perform the SSL shutdown. You should * close the underlying physical connection after calling this function. * \n * If the `Stream` template parameter fulfills the `SocketConnection` * requirements, use \ref connection::close instead of this function, * as it also takes care of closing the underlying stream. */ void quit(error_code& err, diagnostics& diag) { impl_.run(detail::quit_connection_algo_params{}, err, diag); } /// \copydoc quit void quit() { error_code err; diagnostics diag; quit(err, diag); detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION); } /** * \copydoc quit * \details * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * * \par Executor * Intermediate completion handlers, as well as the final handler, are executed using * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated * executor. * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. */ template auto async_quit(CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) BOOST_MYSQL_RETURN_TYPE(detail::async_quit_connection_t) { return async_quit(impl_.shared_diag(), std::forward(token)); } /// \copydoc async_quit template auto async_quit( diagnostics& diag, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) ) BOOST_MYSQL_RETURN_TYPE(detail::async_quit_connection_t) { return impl_ .async_run(detail::quit_connection_algo_params{}, diag, std::forward(token)); } /** * \brief Rebinds the connection type to another executor. * \details * The `Stream` type must either provide a `rebind_executor` * member with the same semantics, or be an instantiation of `boost::asio::ssl::stream` with * a `Stream` type providing a `rebind_executor` member. */ template struct rebind_executor { /// The connection type when rebound to the specified executor. using other = connection::type>; }; }; } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/connection_pool.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_CONNECTION_POOL_HPP #define BOOST_MYSQL_CONNECTION_POOL_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { /** * \brief A proxy to a connection owned by a pool that returns it to the pool when destroyed. * \details * A `pooled_connection` behaves like to a `std::unique_ptr`: it has exclusive ownership of an * \ref any_connection created by the pool. When destroyed, it returns the connection to the pool. * A `pooled_connection` may own nothing. We say such a connection is invalid (`this->valid() == false`). * * This class is movable but not copyable. * * \par Object lifetimes * While `*this` is alive, the \ref connection_pool internal data will be kept alive * automatically. It's safe to destroy the `connection_pool` object before `*this`. * * \par Thread safety * This object and the \ref any_connection object it may point to are **not thread safe**, * even if the connection pool used to obtain them was constructed with * \ref pool_params::thread_safe set to true. * * Functions that return the underlying connection to the pool * cause a mutation on the pool state object. Calling such functions * on objects obtained from the same pool * is thread-safe only if the pool was constructed with \ref pool_params::thread_safe set to true. * * In other words, individual connections can't be shared between threads. Pools can * be shared only if they're constructed with \ref pool_params::thread_safe set to true. * * - Distinct objects: safe if the \ref connection_pool that was used to obtain the objects * was created with \ref pool_params::thread_safe set to true. Otherwise, unsafe. * - Shared objects: always unsafe. */ class pooled_connection { #ifndef BOOST_MYSQL_DOXYGEN friend struct detail::access; friend class detail::basic_pool_impl; #endif struct impl_t { detail::connection_node* node; std::shared_ptr pool; } impl_{}; pooled_connection(detail::connection_node& node, std::shared_ptr pool_impl) noexcept : impl_{&node, std::move(pool_impl)} { BOOST_ASSERT(impl_.pool); } public: /** * \brief Constructs an invalid pooled connection. * \details * The resulting object is invalid (`this->valid() == false`). * * \par Exception safety * No-throw guarantee. */ pooled_connection() noexcept = default; /** * \brief Move constructor. * \details * Transfers connection ownership from `other` to `*this`. * * After this function returns, if `other.valid() == true`, `this->valid() == true`. * In any case, `other` will become invalid (`other.valid() == false`). * * \par Exception safety * No-throw guarantee. */ pooled_connection(pooled_connection&& other) noexcept : impl_(std::move(other.impl_)) {} /** * \brief Move assignment. * \details * If `this->valid()`, returns the connection owned by `*this` to the pool and marks * it as pending reset (as if the destructor was called). * It then transfers connection ownership from `other` to `*this`. * * After this function returns, if `other.valid() == true`, `this->valid() == true`. * In any case, `other` will become invalid (`other.valid() == false`). * * \par Thread-safety * May cause a mutation on the connection pool that `this` points to. * Thread-safe for a shared pool only if it was constructed with * \ref pool_params::thread_safe set to true. * * \par Exception safety * No-throw guarantee. */ pooled_connection& operator=(pooled_connection&& other) noexcept { if (valid()) { detail::return_connection(*impl_.pool, *impl_.node, true); } impl_ = std::move(other.impl_); return *this; } #ifndef BOOST_MYSQL_DOXYGEN pooled_connection(const pooled_connection&) = delete; pooled_connection& operator=(const pooled_connection&) = delete; #endif /** * \brief Destructor. * \details * If `this->valid() == true`, returns the owned connection to the pool * and marks it as pending reset. If your connection doesn't need to be reset * (e.g. because you didn't mutate session state), use \ref return_without_reset. * * \par Thread-safety * May cause a mutation on the connection pool that `this` points to. * Thread-safe for a shared pool only if it was constructed with * \ref pool_params::thread_safe set to true. */ ~pooled_connection() { if (valid()) detail::return_connection(*impl_.pool, *impl_.node, true); } /** * \brief Returns whether the object owns a connection or not. * \par Exception safety * No-throw guarantee. */ bool valid() const noexcept { return impl_.pool.get() != nullptr; } /** * \brief Retrieves the connection owned by this object. * \par Preconditions * The object should own a connection (`this->valid() == true`). * * \par Object lifetimes * The returned reference is valid as long as `*this` or an object * move-constructed or move-assigned from `*this` is alive. * * \par Exception safety * No-throw guarantee. */ any_connection& get() noexcept { return detail::get_connection(*impl_.node); } /// \copydoc get const any_connection& get() const noexcept { return detail::get_connection(*impl_.node); } /// \copydoc get any_connection* operator->() noexcept { return &get(); } /// \copydoc get const any_connection* operator->() const noexcept { return &get(); } /** * \brief Returns the owned connection to the pool and marks it as not requiring reset. * \details * Returns a connection to the pool and marks it as idle. This will * skip the \ref any_connection::async_reset_connection call to wipe session state. * \n * This can provide a performance gain, but must be used with care. Failing to wipe * session state can lead to resource leaks (prepared statements not being released), * incorrect results and vulnerabilities (different logical operations interacting due * to leftover state). * \n * Please read the documentation on \ref any_connection::async_reset_connection before * calling this function. If in doubt, don't use it, and leave the destructor return * the connection to the pool for you. * \n * When this function returns, `*this` will own nothing (`this->valid() == false`). * * \par Preconditions * `this->valid() == true` * * \par Exception safety * No-throw guarantee. * * \par Thread-safety * Causes a mutation on the connection pool that `this` points to. * Thread-safe for a shared pool only if it was constructed with * \ref pool_params::thread_safe set to true. */ void return_without_reset() noexcept { BOOST_ASSERT(valid()); detail::return_connection(*impl_.pool, *impl_.node, false); impl_ = impl_t{}; } }; /** * \brief A pool of connections of variable size. * \details * A connection pool creates and manages \ref any_connection objects. * Using a pool allows to reuse sessions, avoiding part of the overhead associated * to session establishment. It also features built-in error handling and reconnection. * See the discussion and examples for more details on when to use this class. * * Connections are retrieved by \ref async_get_connection, which yields a * \ref pooled_connection object. They are returned to the pool when the * `pooled_connection` is destroyed, or by calling \ref pooled_connection::return_without_reset. * * A pool needs to be run before it can return any connection. Use \ref async_run for this. * Pools can only be run once. * * Connections are created, connected and managed internally by the pool, following * a well-defined state model. Please refer to the discussion for details. * * Due to oddities in Boost.Asio's universal async model, this class only * exposes async functions. You can use `asio::use_future` to transform them * into sync functions (please read the discussion for details). * * This is a move-only type. * * \par Default completion tokens * The default completion token for all async operations in this class is * `with_diagnostics(asio::deferred)`, which allows you to use `co_await` * and have the expected exceptions thrown on error. * * \par Thread-safety * Pools are composed of an internal state object, plus a handle to such state. * Each component has different thread-safety rules. * * Regarding **internal state**, connection pools are **not thread-safe by default**, * but can be made safe by constructing them with * \ref pool_params::thread_safe set to `true`. * Internal state is also mutated by some functions outside `connection_pool`, like * returning connections. * * The following actions imply a pool state mutation, and are protected by a strand * when thread-safety is enabled: * * - Calling \ref connection_pool::async_run. * - Calling \ref connection_pool::async_get_connection. * - Cancelling \ref async_get_connection by emitting a cancellation signal. * - Returning a connection by destroying a \ref pooled_connection or * calling \ref pooled_connection::return_without_reset. * - Cancelling the pool by calling \ref connection_pool::cancel, * emitting a cancellation signal for \ref async_run, or destroying the * `connection_pool` object. * * The **handle to the pool state** is **never thread-safe**, even for * pools with thread-safety enabled. Functions like assignments * modify the handle, and cause race conditions if called * concurrently with other functions. Other objects, * like \ref pooled_connection, have their own state handle, * and thus interact only with the pool state. * * If configured to be thread-safe, the protection applies only to the pool's state. * In particular, be careful when using `asio::cancel_after` and similar tokens. * Please read * this page for more info. * * In summary: * * - Distinct objects: safe. \n * - Shared objects: unsafe. Setting \ref pool_params::thread_safe * to `true` makes some functions safe. * * \par Object lifetimes * Connection pool objects create an internal state object that is referenced * by other objects and operations (like \ref pooled_connection). This object * will be kept alive using shared ownership semantics even after the `connection_pool` * object is destroyed. This results in intuitive lifetime rules. */ class connection_pool { std::shared_ptr impl_; #ifndef BOOST_MYSQL_DOXYGEN friend struct detail::access; #endif struct initiate_run : detail::initiation_base { using detail::initiation_base::initiation_base; // Having diagnostics* here makes async_run compatible with with_diagnostics template void operator()(Handler&& h, diagnostics*, std::shared_ptr self) { async_run_erased(std::move(self), std::forward(h)); } }; BOOST_MYSQL_DECL static void async_run_erased( std::shared_ptr pool, asio::any_completion_handler handler ); struct initiate_get_connection : detail::initiation_base { using detail::initiation_base::initiation_base; template void operator()(Handler&& h, diagnostics* diag, std::shared_ptr self) { async_get_connection_erased(std::move(self), diag, std::forward(h)); } }; BOOST_MYSQL_DECL static void async_get_connection_erased( std::shared_ptr pool, diagnostics* diag, asio::any_completion_handler handler ); template auto async_get_connection_impl(diagnostics* diag, CompletionToken&& token) -> decltype(asio::async_initiate( std::declval(), token, diag, impl_ )) { BOOST_ASSERT(valid()); return asio::async_initiate( initiate_get_connection{get_executor()}, token, diag, impl_ ); } BOOST_MYSQL_DECL connection_pool(asio::any_io_executor ex, pool_params&& params, int); public: /** * \brief Constructs a connection pool. * \details * * The pool is created in a "not-running" state. Call \ref async_run to transition to the * "running" state. * * The constructed pool is always valid (`this->valid() == true`). * * \par Executor * The passed executor becomes the pool executor, available through \ref get_executor. * `ex` is used as follows: * * - If `params.thread_safe == true`, `ex` is used to build a strand. The strand is used * to build internal I/O objects, like timers. * - If `params.thread_safe == false`, `ex` is used directly to build internal I/O objects. * - If `params.connection_executor` is empty, `ex` is used to build individual connections, * regardless of the chosen thread-safety mode. Otherwise, `params.connection_executor` * is used. * * \par Exception safety * Strong guarantee. Exceptions may be thrown by memory allocations. * \throws std::invalid_argument If `params` contains values that violate the rules described in \ref * pool_params. */ connection_pool(asio::any_io_executor ex, pool_params params) : connection_pool(std::move(ex), std::move(params), 0) { } /** * \brief Constructs a connection pool. * \details * Equivalent to `connection_pool(ctx.get_executor(), params)`. * * This function participates in overload resolution only if `ExecutionContext` * satisfies the `ExecutionContext` requirements imposed by Boost.Asio. * * \par Exception safety * Strong guarantee. Exceptions may be thrown by memory allocations. * \throws std::invalid_argument If `params` contains values that violate the rules described in \ref * pool_params. */ template < class ExecutionContext #ifndef BOOST_MYSQL_DOXYGEN , class = typename std::enable_if().get_executor()), asio::any_io_executor>::value>::type #endif > connection_pool(ExecutionContext& ctx, pool_params params) : connection_pool(ctx.get_executor(), std::move(params), 0) { } #ifndef BOOST_MYSQL_DOXYGEN connection_pool(const connection_pool&) = delete; connection_pool& operator=(const connection_pool&) = delete; #endif /** * \brief Move-constructor. * \details * Constructs a connection pool by taking ownership of `other`. * * After this function returns, if `other.valid() == true`, `this->valid() == true`. * In any case, `other` will become invalid (`other.valid() == false`). * * Moving a connection pool with outstanding async operations * is safe. * * \par Exception safety * No-throw guarantee. * * \par Thread-safety * Mutates `other`'s internal state handle. Does not access the pool state. * This function **can never be called concurrently with other functions * that read the internal state handle**, even for pools created * with \ref pool_params::thread_safe set to true. * * The internal pool state is not accessed, so this function can be called * concurrently with functions that only access the pool's internal state, * like returning connections. */ connection_pool(connection_pool&& other) = default; /** * \brief Move assignment. * \details * Assigns `other` to `*this`, transferring ownership. * * After this function returns, if `other.valid() == true`, `this->valid() == true`. * In any case, `other` will become invalid (`other.valid() == false`). * * Moving a connection pool with outstanding async operations * is safe. * * \par Exception safety * No-throw guarantee. * * \par Thread-safety * Mutates `*this` and `other`'s internal state handle. Does not access the pool state. * This function **can never be called concurrently with other functions * that read the internal state handle**, even for pools created * with \ref pool_params::thread_safe set to true. * * The internal pool state is not accessed, so this function can be called * concurrently with functions that only access the pool's internal state, * like returning connections. */ connection_pool& operator=(connection_pool&& other) = default; /** * \brief Destructor. * \details * Cancels all outstanding async operations on `*this`, as per \ref cancel. * * \par Thread-safety * Mutates the internal state handle. Mutates the pool state. * This function **can never be called concurrently with other functions * that read the internal state handle**, even for pools created * with \ref pool_params::thread_safe set to true. * * The internal pool state is modified as per \ref cancel. * If thread-safety is enabled, it's safe to call the destructor concurrently * with functions that only access the pool's internal state, * like returning connections. */ ~connection_pool() { if (valid()) cancel(); } /** * \brief Returns whether the object is in a moved-from state. * \details * This function returns always `true` except for pools that have been * moved-from. Moved-from objects don't represent valid pools. They can only * be assigned to or destroyed. * * \par Exception safety * No-throw guarantee. * * \par Thread-safety * Reads the internal state handle. Does not access the pool state. * Can be called concurrently with any other function that reads the state handle, * like \ref async_run or \ref async_get_connection. * It can't be called concurrently with functions modifying the handle, like assignments, * even if \ref pool_params::thread_safe is set to true. */ bool valid() const noexcept { return impl_.get() != nullptr; } /// The executor type associated to this object. using executor_type = asio::any_io_executor; /** * \brief Retrieves the executor associated to this object. * \details * Returns the executor used to construct the pool as first argument. * This is the case even when using \ref pool_params::thread_safe - * the internal strand created in this case is never exposed. * * \par Exception safety * No-throw guarantee. * * \par Thread-safety * Reads the internal state handle. Reads the pool state. * If the pool was built with thread-safety enabled, it can be called * concurrently with other functions that don't modify the state handle. */ BOOST_MYSQL_DECL executor_type get_executor() noexcept; /** * \brief Runs the pool task in charge of managing connections. * \details * This function creates and connects new connections, and resets and pings * already created ones. You need to call this function for \ref async_get_connection * to succeed. * * The async operation will run indefinitely, until the pool is cancelled * (by calling \ref cancel or using per-operation cancellation on the `async_run` operation). * The operation completes once all internal connection operations * (including connects, pings and resets) complete. * * It is safe to call this function after calling \ref cancel. * * \par Preconditions * This function can be called at most once for a single pool. * Formally, `async_run` hasn't been called before on `*this` or any object * used to move-construct or move-assign `*this`. * * Additionally, `this->valid() == true`. * * \par Object lifetimes * While the operation is outstanding, the pool's internal data will be kept alive. * It is safe to destroy `*this` while the operation is outstanding. * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)` * * \par Executor * * The final handler is executed using `token`'s associated executor, * or `this->get_executor()` if the token doesn't have an associated * executor. The final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. * * If the pool was constructed with thread-safety enabled, intermediate * completion handlers are executed using an internal strand that wraps `this->get_executor()`. * Otherwise, intermediate handlers are executed using `this->get_executor()`. * In any case, the token's associated executor is only used for the final handler. * * \par Per-operation cancellation * This operation supports per-operation cancellation. Cancelling `async_run` * is equivalent to calling \ref connection_pool::cancel. * The following `asio::cancellation_type_t` values are supported: * * - `asio::cancellation_type_t::terminal` * - `asio::cancellation_type_t::partial` * * Note that `asio::cancellation_type_t::total` is not supported because invoking * `async_run` always has observable side effects. * * \par Errors * This function always complete successfully. The handler signature ensures * maximum compatibility with Boost.Asio infrastructure. * * \par Thread-safety * Reads the internal state handle. Mutates the pool state. * If the pool was built with thread-safety enabled, it can be called * concurrently with other functions that don't modify the state handle. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken = with_diagnostics_t> auto async_run(CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE(decltype(asio::async_initiate( std::declval(), token, static_cast(nullptr), impl_ ))) { BOOST_ASSERT(valid()); return asio::async_initiate( initiate_run{get_executor()}, token, static_cast(nullptr), impl_ ); } /** * \brief Retrieves a connection from the pool. * \details * Retrieves an idle connection from the pool to be used. * * If this function completes successfully (empty error code), the return \ref pooled_connection * will have `valid() == true` and will be usable. If it completes with a non-empty error code, * it will have `valid() == false`. * * If a connection is idle when the operation is started, it will complete immediately * with that connection. Otherwise, it will wait for a connection to become idle * (possibly creating one in the process, if pool configuration allows it), until * the operation is cancelled (by emitting a cancellation signal) or the pool * is cancelled (by calling \ref connection_pool::cancel). * If the pool is not running, the operation fails immediately. * * If the operation is cancelled, and the overload with \ref diagnostics was used, * the output diagnostics will contain the most recent error generated by * the connections attempting to connect (via \ref any_connection::async_connect), if any. * In cases where \ref async_get_connection doesn't complete because connections are unable * to connect, this feature can help figuring out where the problem is. * * \par Preconditions * `this->valid() == true` \n * * \par Object lifetimes * While the operation is outstanding, the pool's internal data will be kept alive. * It is safe to destroy `*this` while the operation is outstanding. * * \par Handler signature * The handler signature for this operation is * `void(boost::mysql::error_code, boost::mysql::pooled_connection)` * * \par Executor * * If the final handler has an associated immediate executor, and the operation * completes immediately, the final handler is dispatched to it. * Otherwise, the final handler is called as if it was submitted using `asio::post`, * and is never be called inline from within this function. * Immediate completions can only happen when thread-safety is not enabled. * * The final handler is executed using `token`'s associated executor, * or `this->get_executor()` if the token doesn't have an associated * executor. * * If the pool was constructed with thread-safety enabled, intermediate * completion handlers are executed using an internal strand that wraps `this->get_executor()`. * Otherwise, intermediate handlers are executed using * `token`'s associated executor if it has one, or `this->get_executor()` if it hasn't. * * **Caution**: be careful when using thread-safety and `asio::cancel_after`, as it * can result in inadvertent race conditions. Please refer to * this * page for more info. * * \par Per-operation cancellation * This operation supports per-operation cancellation. * Cancelling `async_get_connection` has no observable side effects. * The following `asio::cancellation_type_t` values are supported: * * - `asio::cancellation_type_t::terminal` * - `asio::cancellation_type_t::partial` * - `asio::cancellation_type_t::total` * * \par Errors * - \ref client_errc::no_connection_available, if the `async_get_connection` * operation is cancelled before a connection becomes available. * - \ref client_errc::pool_not_running, if the `async_get_connection` * operation is cancelled before async_run is called. * - \ref client_errc::pool_cancelled, if the pool is cancelled before * the operation completes, or `async_get_connection` is called * on a pool that has been cancelled. * * \par Thread-safety * Reads the internal state handle. Mutates the pool state. * If the pool was built with thread-safety enabled, it can be called * concurrently with other functions that don't modify the state handle. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::pooled_connection)) CompletionToken = with_diagnostics_t> auto async_get_connection(CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE( decltype(async_get_connection_impl(nullptr, std::forward(token))) ) { return async_get_connection_impl(nullptr, std::forward(token)); } /// \copydoc async_get_connection template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::pooled_connection)) CompletionToken = with_diagnostics_t> auto async_get_connection(diagnostics& diag, CompletionToken&& token = {}) BOOST_MYSQL_RETURN_TYPE( decltype(async_get_connection_impl(nullptr, std::forward(token))) ) { return async_get_connection_impl(&diag, std::forward(token)); } /** * \brief Stops any current outstanding operation and marks the pool as cancelled. * \details * This function has the following effects: * * \li Stops the currently outstanding \ref async_run operation, if any, which will complete * with a success error code. * \li Cancels any outstanding \ref async_get_connection operations. * \li Marks the pool as cancelled. Successive `async_get_connection` calls will * fail immediately. * * This function will return immediately, without waiting for the cancelled operations to complete. * * You may call this function any number of times. Successive calls will have no effect. * * \par Preconditions * `this->valid() == true` * * \par Exception safety * Basic guarantee. Memory allocations and acquiring mutexes may throw. * * \par Thread-safety * Reads the internal state handle. Mutates the pool state. * If the pool was built with thread-safety enabled, it can be called * concurrently with other functions that don't modify the state handle. */ BOOST_MYSQL_DECL void cancel(); }; } // namespace mysql } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/constant_string_view.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_CONSTANT_STRING_VIEW_HPP #define BOOST_MYSQL_CONSTANT_STRING_VIEW_HPP #include #include #include #include namespace boost { namespace mysql { /** * \brief A string view that should be known at compile-time. * \details * This type is used when a string function argument must always be known at compile-time * except in rare cases. See \ref format_sql format strings for an example. * * \par Object lifetimes * This type holds internally a \ref string_view, and follows the same lifetime rules as `string_view`. * We recommend to only use this type as a function argument, to provide compile-time checks. */ class constant_string_view { string_view impl_; #ifndef BOOST_MYSQL_DOXYGEN constexpr constant_string_view(string_view value, int) noexcept : impl_(value) {} friend constexpr constant_string_view runtime(string_view) noexcept; #endif public: /** * \brief Consteval constructor. * \details * Constructs a \ref string_view from the passed argument. * \n * This function is `consteval`: it results in a compile-time error * if the passed value is not known at compile-time. You can bypass * this check using the \ref runtime function. This check works only * for C++20 and above. No check is performed for lower C++ standard versions. * \n * This constructor is only considered if a \ref string_view can be constructed * from the passed value. * * \par Exception safety * No-throw guarantee. * * \par Object lifetimes * Ownership is not transferred to the constructed object. As with `string_view`, * the user is responsible for keeping the original character buffer alive. */ template < class T #ifndef BOOST_MYSQL_DOXYGEN , class = typename std::enable_if::value>::type #endif > BOOST_MYSQL_CONSTEVAL constant_string_view(const T& value) noexcept : impl_(value) { } /** * \brief Retrieves the underlying string view. * * \par Exception safety * No-throw guarantee. * * \par Object lifetimes * The returned view has the same lifetime rules as `*this`. */ constexpr string_view get() const noexcept { return impl_; } }; /** * \brief Creates a \ref constant_string_view from a runtime value. * \details * You can use this function to bypass the `consteval` check performed by \ref constant_string_view * constructor. * \n * Don't use this function unless you know what you are doing. `consteval` checks exist * for the sake of security. Make sure to only pass trusted values to the relevant API. * * \par Exception safety * No-throw guarantee. * * \par Object lifetimes * The returned value has the same lifetime semantics as the passed view. */ constexpr constant_string_view runtime(string_view value) noexcept { return constant_string_view(value, 0); } } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/date.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DATE_HPP #define BOOST_MYSQL_DATE_HPP #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { /** * \brief Type representing MySQL `DATE` data type. * \details * Represents a Gregorian date broken by its year, month and day components, without a time zone. * \n * This type is close to the protocol and should not be used as a vocabulary type. * Instead, cast it to a `std::chrono::time_point` by calling \ref as_time_point, * \ref get_time_point, \ref as_local_time_point or \ref get_local_time_point. * \n * Dates retrieved from MySQL don't include any time zone information. Determining the time zone * is left to the application. Thus, any time point obtained from this class should be * interpreted as a local time in an unspecified time zone, like `std::chrono::local_time`. * For compatibility with older compilers, \ref as_time_point and \ref get_time_point return * `system_clock` time points. These should be interpreted as local times rather * than UTC. Prefer using \ref as_local_time_point or \ref get_local_time_point * if your compiler supports them, as they provide more accurate semantics. * \n * As opposed to `time_point`, this type allows representing MySQL invalid and zero dates. * These values are allowed by MySQL but don't represent real dates. * \n * Note: using `std::chrono` time zone functionality under MSVC may cause memory leaks to be reported. * See this issue for an explanation and * this other issue for a workaround. */ class date { public: /** * \brief A `std::chrono::time_point` that can represent any valid `date`. * \details * Time points used by this class are always local times, even if defined * to use the system clock. */ using time_point = std::chrono::time_point; /** * \brief Constructs a zero date. * \details * Results in a date with all of its components set to zero. * The resulting object has `this->valid() == false`. * * \par Exception safety * No-throw guarantee. */ constexpr date() noexcept = default; /** * \brief Constructs a date from its year, month and date components. * \details * Component values that yield invalid dates (like zero or out-of-range * values) are allowed, resulting in an object with `this->valid() == false`. * * \par Exception safety * No-throw guarantee. */ constexpr date(std::uint16_t year, std::uint8_t month, std::uint8_t day) noexcept : year_(year), month_(month), day_(day) { } /** * \brief Constructs a date from a `time_point`. * \details * The time point is interpreted as a local time. No time zone conversion is performed. * * \par Exception safety * Strong guarantee. Throws on invalid input. * \throws std::out_of_range If the resulting `date` would be * out of the [\ref min_date, \ref max_date] range. */ BOOST_CXX14_CONSTEXPR explicit date(time_point tp) { bool ok = detail::days_to_ymd(tp.time_since_epoch().count(), year_, month_, day_); if (!ok) BOOST_THROW_EXCEPTION(std::out_of_range("date::date: time_point was out of range")); } #ifdef BOOST_MYSQL_HAS_LOCAL_TIME /** * \brief Constructs a date from a local time point. * \details * Equivalent to constructing a `date` from a `time_point` with the same * `time_since_epoch()` as `tp`. * \n * Requires C++20 calendar types. * * \par Exception safety * Strong guarantee. Throws on invalid input. * \throws std::out_of_range If the resulting `date` would be * out of the [\ref min_date, \ref max_date] range. */ constexpr explicit date(std::chrono::local_days tp) : date(time_point(tp.time_since_epoch())) {} #endif /** * \brief Retrieves the year component. * \details * Represents the year number in the Gregorian calendar. * If `this->valid() == true`, this value is within the `[0, 9999]` range. * * \par Exception safety * No-throw guarantee. */ constexpr std::uint16_t year() const noexcept { return year_; } /** * \brief Retrieves the month component (1-based). * \details * A value of 1 represents January. * If `this->valid() == true`, this value is within the `[1, 12]` range. * * \par Exception safety * No-throw guarantee. */ constexpr std::uint8_t month() const noexcept { return month_; } /** * \brief Retrieves the day component (1-based). * \details * A value of 1 represents the first day of the month. * If `this->valid() == true`, this value is within the `[1, last_month_day]` range * (where `last_month_day` is the last day of the month). * * \par Exception safety * No-throw guarantee. */ constexpr std::uint8_t day() const noexcept { return day_; } /** * \brief Returns `true` if `*this` represents a valid `time_point`. * \details If any of the individual components is out of range, the date * doesn't represent an actual `time_point` (e.g. `date(2020, 2, 30)`) or * the date is not in the [\ref min_date, \ref max_date] validity range, * returns `false`. Otherwise, returns `true`. * \par Exception safety * No-throw guarantee. */ constexpr bool valid() const noexcept { return detail::is_valid(year_, month_, day_); } /** * \brief Converts `*this` into a `time_point` (unchecked access). * \details * If your compiler supports it, prefer using \ref get_local_time_point, * as it provides more accurate semantics. * * \par Preconditions * `this->valid() == true` (if violated, results in undefined behavior). * * \par Exception safety * No-throw guarantee. */ BOOST_CXX14_CONSTEXPR time_point get_time_point() const noexcept { BOOST_ASSERT(valid()); return time_point(unch_get_days()); } /** * \brief Converts `*this` into a `time_point` (checked access). * \details * If your compiler supports it, prefer using \ref as_local_time_point, * as it provides more accurate semantics. * * \par Exception safety * Strong guarantee. * \throws std::invalid_argument If `!this->valid()`. */ BOOST_CXX14_CONSTEXPR time_point as_time_point() const { if (!valid()) BOOST_THROW_EXCEPTION(std::invalid_argument("date::as_time_point: invalid date")); return time_point(unch_get_days()); } #ifdef BOOST_MYSQL_HAS_LOCAL_TIME /** * \brief Converts `*this` into a local time point (unchecked access). * \details * The returned object has the same `time_since_epoch()` as `this->get_time_point()`, * but uses the `std::chrono::local_t` pseudo-clock to better represent * the absence of time zone information. * \n * Requires C++20 calendar types. * * \par Preconditions * `this->valid() == true` (if violated, results in undefined behavior). * * \par Exception safety * No-throw guarantee. */ constexpr std::chrono::local_days get_local_time_point() const noexcept { BOOST_ASSERT(valid()); return std::chrono::local_days(unch_get_days()); } /** * \brief Converts `*this` into a local time point (checked access). * \details * The returned object has the same `time_since_epoch()` as `this->as_time_point()`, * but uses the `std::chrono::local_t` pseudo-clock to better represent * the absence of time zone information. * \n * Requires C++20 calendar types. * * \par Exception safety * Strong guarantee. * \throws std::invalid_argument If `!this->valid()`. */ constexpr std::chrono::local_days as_local_time_point() const { if (!valid()) BOOST_THROW_EXCEPTION(std::invalid_argument("date::as_local_time_point: invalid date")); return std::chrono::local_days(unch_get_days()); } #endif /** * \brief Tests for equality. * \details Two dates are considered equal if all of its individual components * are equal. This function works for invalid dates, too. * * \par Exception safety * No-throw guarantee. */ constexpr bool operator==(const date& rhs) const noexcept { return year_ == rhs.year_ && month_ == rhs.month_ && day_ == rhs.day_; } /** * \brief Tests for inequality. * * \par Exception safety * No-throw guarantee. */ constexpr bool operator!=(const date& rhs) const noexcept { return !(rhs == *this); } /** * \brief Returns the current system time as a date object. * \par Exception safety * Strong guarantee. Only throws if obtaining the current time throws. */ static date now() { auto now = time_point::clock::now(); return date(std::chrono::time_point_cast(now)); } private: std::uint16_t year_{}; std::uint8_t month_{}; std::uint8_t day_{}; BOOST_CXX14_CONSTEXPR days unch_get_days() const { return days(detail::ymd_to_days(year_, month_, day_)); } }; /** * \relates date * \brief Streams a date. * \details This function works for invalid dates, too. */ BOOST_MYSQL_DECL std::ostream& operator<<(std::ostream& os, const date& v); /// The minimum allowed value for \ref date. BOOST_INLINE_CONSTEXPR date min_date{0u, 1u, 1u}; /// The maximum allowed value for \ref date. BOOST_INLINE_CONSTEXPR date max_date{9999u, 12u, 31u}; } // namespace mysql } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/datetime.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DATETIME_HPP #define BOOST_MYSQL_DATETIME_HPP #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { /** * \brief Type representing MySQL `DATETIME` and `TIMESTAMP` data types. * \details Represents a Gregorian date and time broken by its year, month, day, hour, minute, second and * microsecond components, without a time zone. * \n * This type is close to the protocol and should not be used as a vocabulary type. * Instead, cast it to a `std::chrono::time_point` by calling \ref as_time_point, * \ref get_time_point, \ref as_local_time_point or \ref get_local_time_point. * \n * Datetimes retrieved from MySQL don't include any time zone information. Determining the time zone * is left to the application. Thus, any time point obtained from this class should be * interpreted as a local time in an unspecified time zone, like `std::chrono::local_time`. * For compatibility with older compilers, \ref as_time_point and \ref get_time_point return * `system_clock` time points. These should be interpreted as local times rather * than UTC. Prefer using \ref as_local_time_point or \ref get_local_time_point * if your compiler supports them, as they provide more accurate semantics. * \n * As opposed to `time_point`, this type allows representing MySQL invalid and zero datetimes. * These values are allowed by MySQL but don't represent real time points. * \n * Note: using `std::chrono` time zone functionality under MSVC may cause memory leaks to be reported. * See this issue for an explanation and * this other issue for a workaround. */ class datetime { public: /** * \brief A `std::chrono::time_point` that can represent any valid datetime, with microsecond resolution. * \details * Time points used by this class are always local times, even if defined * to use the system clock. Prefer using \ref local_time_point, if your compiler * supports it. */ using time_point = std::chrono:: time_point>; #ifdef BOOST_MYSQL_HAS_LOCAL_TIME /** * \brief A `std::chrono::local_time` that can represent any valid datetime, with microsecond resolution. * \details Requires C++20 calendar types. */ using local_time_point = std::chrono::local_time>; #endif /** * \brief Constructs a zero datetime. * \details Results in a datetime with all of its components set to zero. * The resulting object has `this->valid() == false`. * \par Exception safety * No-throw guarantee. */ constexpr datetime() noexcept = default; /** * \brief Constructs a datetime from its individual components. * \details * Component values that yield invalid datetimes (like zero or out-of-range * values) are allowed, resulting in an object with `this->valid() == false`. * \par Exception safety * No-throw guarantee. */ constexpr datetime( std::uint16_t year, std::uint8_t month, std::uint8_t day, std::uint8_t hour = 0, std::uint8_t minute = 0, std::uint8_t second = 0, std::uint32_t microsecond = 0 ) noexcept : year_(year), month_(month), day_(day), hour_(hour), minute_(minute), second_(second), microsecond_(microsecond) { } /** * \brief Constructs a datetime from a `time_point`. * \par Exception safety * Strong guarantee. Throws on invalid input. * \throws std::out_of_range If the resulting `datetime` object would be * out of the [\ref min_datetime, \ref max_datetime] range. */ BOOST_CXX14_CONSTEXPR inline explicit datetime(time_point tp); #ifdef BOOST_MYSQL_HAS_LOCAL_TIME /** * \brief Constructs a datetime from a `local_time_point`. * \details * Equivalent to constructing a `date` from a `time_point` with the same * `time_since_epoch()` as `tp`. * \n * Requires C++20 calendar types. * * \par Exception safety * Strong guarantee. Throws on invalid input. * \throws std::out_of_range If the resulting `datetime` object would be * out of the [\ref min_datetime, \ref max_datetime] range. */ constexpr explicit datetime(local_time_point tp) : datetime(time_point(tp.time_since_epoch())) {} #endif /** * \brief Retrieves the year component. * \details * Represents the year number in the Gregorian calendar. * If `this->valid() == true`, this value is within the `[0, 9999]` range. * * \par Exception safety * No-throw guarantee. */ constexpr std::uint16_t year() const noexcept { return year_; } /** * \brief Retrieves the month component (1-based). * \details * A value of 1 represents January. * If `this->valid() == true`, this value is within the `[1, 12]` range. * * \par Exception safety * No-throw guarantee. */ constexpr std::uint8_t month() const noexcept { return month_; } /** * \brief Retrieves the day component (1-based). * \details * A value of 1 represents the first day of the month. * If `this->valid() == true`, this value is within the `[1, last_month_day]` range * (where `last_month_day` is the last day of the month). * * \par Exception safety * No-throw guarantee. */ constexpr std::uint8_t day() const noexcept { return day_; } /** * \brief Retrieves the hour component. * \details If `this->valid() == true`, this value is within the `[0, 23]` range. * \par Exception safety * No-throw guarantee. */ constexpr std::uint8_t hour() const noexcept { return hour_; } /** * \brief Retrieves the minute component. * \details If `this->valid() == true`, this value is within the `[0, 59]` range. * \par Exception safety * No-throw guarantee. */ constexpr std::uint8_t minute() const noexcept { return minute_; } /** * \brief Retrieves the second component. * \details If `this->valid() == true`, this value is within the `[0, 59]` range. * \par Exception safety * No-throw guarantee. */ constexpr std::uint8_t second() const noexcept { return second_; } /** * \brief Retrieves the microsecond component. * \details If `this->valid() == true`, this value is within the `[0, 999999]` range. * \par Exception safety * No-throw guarantee. */ constexpr std::uint32_t microsecond() const noexcept { return microsecond_; } /** * \brief Returns `true` if `*this` represents a valid `time_point`. * \details If any of the individual components is out of range, the datetime * doesn't represent an actual `time_point` (e.g. `datetime(2020, 2, 30)`) or * the datetime is not in the [\ref min_date, \ref max_date] validity range, * returns `false`. Otherwise, returns `true`. * * \par Exception safety * No-throw guarantee. */ constexpr bool valid() const noexcept { return detail::is_valid(year_, month_, day_) && hour_ <= detail::max_hour && minute_ <= detail::max_min && second_ <= detail::max_sec && microsecond_ <= detail::max_micro; } /** * \brief Converts `*this` into a `time_point` (unchecked access). * \details * If your compiler supports it, prefer using \ref get_local_time_point, * as it provides more accurate semantics. * * \par Preconditions * `this->valid() == true` (if violated, results in undefined behavior). * * \par Exception safety * No-throw guarantee. */ BOOST_CXX14_CONSTEXPR time_point get_time_point() const noexcept { BOOST_ASSERT(valid()); return time_point(unch_get_micros()); } /** * \brief Converts `*this` into a `time_point` (checked access). * \details * If your compiler supports it, prefer using \ref as_local_time_point, * as it provides more accurate semantics. * * \par Exception safety * Strong guarantee. * \throws std::invalid_argument If `!this->valid()`. */ BOOST_CXX14_CONSTEXPR inline time_point as_time_point() const { if (!valid()) BOOST_THROW_EXCEPTION(std::invalid_argument("datetime::as_time_point: invalid datetime")); return time_point(unch_get_micros()); } #ifdef BOOST_MYSQL_HAS_LOCAL_TIME /** * \brief Converts `*this` into a `local_time_point` (unchecked access). * \details * The returned object has the same `time_since_epoch()` as `this->get_time_point()`, * but uses the `std::chrono::local_t` pseudo-clock to better represent * the absence of time zone information. * \n * Requires C++20 calendar types. * * \par Preconditions * `this->valid() == true` (if violated, results in undefined behavior). * * \par Exception safety * No-throw guarantee. */ constexpr local_time_point get_local_time_point() const noexcept { BOOST_ASSERT(valid()); return local_time_point(unch_get_micros()); } /** * \brief Converts `*this` into a local time point (checked access). * \details * The returned object has the same `time_since_epoch()` as `this->as_time_point()`, * but uses the `std::chrono::local_t` pseudo-clock to better represent * the absence of time zone information. * \n * Requires C++20 calendar types. * * \par Exception safety * Strong guarantee. * \throws std::invalid_argument If `!this->valid()`. */ constexpr local_time_point as_local_time_point() const { if (!valid()) BOOST_THROW_EXCEPTION(std::invalid_argument("date::as_local_time_point: invalid date")); return local_time_point(unch_get_micros()); } #endif /** * \brief Tests for equality. * \details Two datetimes are considered equal if all of its individual components * are equal. This function works for invalid datetimes, too. * * \par Exception safety * No-throw guarantee. */ constexpr bool operator==(const datetime& rhs) const noexcept { return year_ == rhs.year_ && month_ == rhs.month_ && day_ == rhs.day_ && hour_ == rhs.hour_ && minute_ == rhs.minute_ && second_ == rhs.second_ && microsecond_ == rhs.microsecond_; } /** * \brief Tests for inequality. * \par Exception safety * No-throw guarantee. */ constexpr bool operator!=(const datetime& rhs) const noexcept { return !(*this == rhs); } /** * \brief Returns the current system time as a datetime object. * \par Exception safety * Strong guarantee. Only throws if obtaining the current time throws. */ static datetime now() { auto now = time_point::clock::now(); return datetime(std::chrono::time_point_cast(now)); } private: std::uint16_t year_{}; std::uint8_t month_{}; std::uint8_t day_{}; std::uint8_t hour_{}; std::uint8_t minute_{}; std::uint8_t second_{}; std::uint32_t microsecond_{}; BOOST_CXX14_CONSTEXPR inline time_point::duration unch_get_micros() const { // Doing time of day independently to prevent overflow days d(detail::ymd_to_days(year_, month_, day_)); auto time_of_day = std::chrono::hours(hour_) + std::chrono::minutes(minute_) + std::chrono::seconds(second_) + std::chrono::microseconds(microsecond_); return time_point::duration(d) + time_of_day; } }; /** * \relates datetime * \brief Streams a datetime. * \details This function works for invalid datetimes, too. */ BOOST_MYSQL_DECL std::ostream& operator<<(std::ostream& os, const datetime& v); /// The minimum allowed value for \ref datetime. BOOST_INLINE_CONSTEXPR datetime min_datetime(0u, 1u, 1u); /// The maximum allowed value for \ref datetime. BOOST_INLINE_CONSTEXPR datetime max_datetime(9999u, 12u, 31u, 23u, 59u, 59u, 999999u); } // namespace mysql } // namespace boost // Implementations BOOST_CXX14_CONSTEXPR boost::mysql::datetime::datetime(time_point tp) { using std::chrono::duration_cast; using std::chrono::hours; using std::chrono::microseconds; using std::chrono::minutes; using std::chrono::seconds; // Avoiding using -= for durations as it's not constexpr until C++17 auto input_dur = tp.time_since_epoch(); auto rem = input_dur % days(1); auto num_days = duration_cast(input_dur); if (rem.count() < 0) { rem = rem + days(1); num_days = num_days - days(1); } auto num_hours = duration_cast(rem); rem = rem - num_hours; auto num_minutes = duration_cast(rem); rem = rem - num_minutes; auto num_seconds = duration_cast(rem); rem = rem - num_seconds; auto num_microseconds = duration_cast(rem); BOOST_ASSERT(num_hours.count() >= 0 && num_hours.count() <= detail::max_hour); BOOST_ASSERT(num_minutes.count() >= 0 && num_minutes.count() <= detail::max_min); BOOST_ASSERT(num_seconds.count() >= 0 && num_seconds.count() <= detail::max_sec); BOOST_ASSERT(num_microseconds.count() >= 0 && num_microseconds.count() <= detail::max_micro); bool ok = detail::days_to_ymd(num_days.count(), year_, month_, day_); if (!ok) BOOST_THROW_EXCEPTION(std::out_of_range("datetime::datetime: time_point was out of range")); microsecond_ = static_cast(num_microseconds.count()); second_ = static_cast(num_seconds.count()); minute_ = static_cast(num_minutes.count()); hour_ = static_cast(num_hours.count()); } #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/days.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DAYS_HPP #define BOOST_MYSQL_DAYS_HPP #include namespace boost { namespace mysql { /** * \brief Duration representing a day (24 hours). * \details Suitable to represent the range of dates MySQL offers. * May differ in representation from `std::chrono::days` in C++20. */ using days = std::chrono::duration>; } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/defaults.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DEFAULTS_HPP #define BOOST_MYSQL_DEFAULTS_HPP #include #include namespace boost { namespace mysql { /// The default TCP port for the MySQL protocol. BOOST_INLINE_CONSTEXPR unsigned short default_port = 3306; /// The default TCP port for the MySQL protocol, as a string. Useful for hostname resolution. BOOST_INLINE_CONSTEXPR const char* default_port_string = "3306"; /// The default initial size of the connection's internal buffer, in bytes. BOOST_INLINE_CONSTEXPR std::size_t default_initial_read_buffer_size = 1024; } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/access.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_ACCESS_HPP #define BOOST_MYSQL_DETAIL_ACCESS_HPP #include namespace boost { namespace mysql { namespace detail { // Exposes access to the implementation of public access, which is sometimes // required by library internals. struct access { template static decltype(std::declval().impl_)& get_impl(T& obj) noexcept { return obj.impl_; } template static const decltype(std::declval().impl_)& get_impl(const T& obj) noexcept { return obj.impl_; } template static T construct(Args&&... args) { return T(std::forward(args)...); } }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/algo_params.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_ALGO_PARAMS_HPP #define BOOST_MYSQL_DETAIL_ALGO_PARAMS_HPP #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { class rows_view; class statement; class stage_response; namespace detail { class execution_processor; class execution_state_impl; struct pipeline_request_stage; struct connect_algo_params { const void* server_address; // Points to an any_address or an endpoint for the corresponding stream. For // the templated connection, only valid until the first yield! handshake_params hparams; bool secure_channel; // Are we using UNIX sockets or any other secure channel? using result_type = void; }; struct handshake_algo_params { handshake_params hparams; bool secure_channel; // Are we using UNIX sockets or any other secure channel? using result_type = void; }; struct execute_algo_params { any_execution_request req; execution_processor* proc; using result_type = void; }; struct start_execution_algo_params { any_execution_request req; execution_processor* proc; using result_type = void; }; struct read_resultset_head_algo_params { execution_processor* proc; using result_type = void; }; struct read_some_rows_algo_params { execution_processor* proc; output_ref output; using result_type = std::size_t; }; struct read_some_rows_dynamic_algo_params { execution_state_impl* exec_st; using result_type = rows_view; }; struct prepare_statement_algo_params { string_view stmt_sql; using result_type = statement; }; struct close_statement_algo_params { std::uint32_t stmt_id; using result_type = void; }; struct ping_algo_params { using result_type = void; }; struct reset_connection_algo_params { using result_type = void; }; struct set_character_set_algo_params { character_set charset; using result_type = void; }; struct quit_connection_algo_params { using result_type = void; }; struct close_connection_algo_params { using result_type = void; }; struct run_pipeline_algo_params { span request_buffer; span request_stages; std::vector* response; using result_type = void; }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/any_execution_request.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_ANY_EXECUTION_REQUEST_HPP #define BOOST_MYSQL_DETAIL_ANY_EXECUTION_REQUEST_HPP #include #include #include #include #include #include namespace boost { namespace mysql { class field_view; class format_arg; namespace detail { struct any_execution_request { enum class type_t { query, query_with_params, stmt }; union data_t { string_view query; struct query_with_params_t { constant_string_view query; span args; } query_with_params; struct stmt_t { std::uint32_t stmt_id; std::uint16_t num_params; span params; } stmt; data_t(string_view q) noexcept : query(q) {} data_t(query_with_params_t v) noexcept : query_with_params(v) {} data_t(stmt_t v) noexcept : stmt(v) {} }; type_t type; data_t data; any_execution_request(string_view q) noexcept : type(type_t::query), data(q) {} any_execution_request(data_t::query_with_params_t v) noexcept : type(type_t::query_with_params), data(v) { } any_execution_request(data_t::stmt_t v) noexcept : type(type_t::stmt), data(v) {} }; struct no_execution_request_traits { }; template struct execution_request_traits : no_execution_request_traits { }; template struct execution_request_traits::value>::type> { static any_execution_request make_request(string_view input, std::vector&) { return input; } }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/any_resumable_ref.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_ANY_RESUMABLE_REF_HPP #define BOOST_MYSQL_DETAIL_ANY_RESUMABLE_REF_HPP #include #include #include namespace boost { namespace mysql { namespace detail { class any_resumable_ref { public: using fn_t = next_action (*)(void*, error_code, std::size_t); template ::value>::type> explicit any_resumable_ref(T& op) noexcept : algo_(&op), fn_(&do_resume) { } // Allow using standalone functions any_resumable_ref(void* algo, fn_t fn) noexcept : algo_(algo), fn_(fn) {} next_action resume(error_code ec, std::size_t bytes_transferred) { return fn_(algo_, ec, bytes_transferred); } private: template static next_action do_resume(void* self, error_code ec, std::size_t bytes_transferred) { return static_cast(self)->resume(ec, bytes_transferred); } void* algo_{}; fn_t fn_{}; }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/character_set.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_CHARACTER_SET_HPP #define BOOST_MYSQL_DETAIL_CHARACTER_SET_HPP #include #include #include namespace boost { namespace mysql { namespace detail { inline std::size_t next_char_ascii(span input) { return input[0] <= 0x7f ? 1 : 0; } BOOST_MYSQL_DECL std::size_t next_char_utf8mb4(span input); } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/coldef_view.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_COLDEF_VIEW_HPP #define BOOST_MYSQL_DETAIL_COLDEF_VIEW_HPP #include #include #include namespace boost { namespace mysql { namespace detail { struct coldef_view { string_view database; string_view table; string_view org_table; string_view name; string_view org_name; std::uint16_t collation_id; std::uint32_t column_length; // maximum length of the field column_type type; std::uint16_t flags; std::uint8_t decimals; // max shown decimal digits. 0x00 for int/static strings; 0x1f for // dynamic strings, double, float }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/config.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_CONFIG_HPP #define BOOST_MYSQL_DETAIL_CONFIG_HPP #include // clang-format off // Concepts #if defined(__cpp_concepts) && defined(__cpp_lib_concepts) #define BOOST_MYSQL_HAS_CONCEPTS #endif // C++14 conformance #if BOOST_CXX_VERSION >= 201402L #define BOOST_MYSQL_CXX14 #endif // Consteval #ifdef __cpp_consteval #define BOOST_MYSQL_CONSTEVAL consteval #else #define BOOST_MYSQL_CONSTEVAL constexpr #endif // Separate build #if defined(BOOST_MYSQL_SEPARATE_COMPILATION) #define BOOST_MYSQL_DECL #else #define BOOST_MYSQL_HEADER_ONLY #define BOOST_MYSQL_DECL inline #endif // Auto return type. Having this as a macro helps the documentation tool. #ifdef BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION #define BOOST_MYSQL_RETURN_TYPE(...) -> __VA_ARGS__ #else #define BOOST_MYSQL_RETURN_TYPE(...) #endif // Chrono calendar types and functions #if __cpp_lib_chrono >= 201907L #define BOOST_MYSQL_HAS_LOCAL_TIME #endif // clang-format on #endif ================================================ FILE: include/boost/mysql/detail/connect_params_helpers.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_CONNECT_PARAMS_HELPERS_HPP #define BOOST_MYSQL_DETAIL_CONNECT_PARAMS_HELPERS_HPP #include #include #include #include namespace boost { namespace mysql { namespace detail { inline ssl_mode adjust_ssl_mode(ssl_mode input, address_type addr_type) { return addr_type == address_type::host_and_port ? input : ssl_mode::disable; } inline handshake_params make_hparams(const connect_params& input) { return handshake_params( input.username, input.password, input.database, input.connection_collation, adjust_ssl_mode(input.ssl, input.server_address.type()), input.multi_queries ); } } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/connection_impl.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_CONNECTION_IMPL_HPP #define BOOST_MYSQL_DETAIL_CONNECTION_IMPL_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { // Forward decl template class static_execution_state; struct character_set; class pipeline_request; namespace detail { // // Helpers to interact with connection_state, without including its definition // class connection_state; struct connection_state_deleter { BOOST_MYSQL_DECL void operator()(connection_state*) const; }; BOOST_MYSQL_DECL std::vector& get_shared_fields(connection_state&); template any_resumable_ref setup(connection_state&, diagnostics&, const AlgoParams&); // Note: AlgoParams should have !is_void_result template typename AlgoParams::result_type get_result(const connection_state&); // // helpers to run algos // template using has_void_result = std::is_same; template struct completion_signature_impl; template struct completion_signature_impl { // Using typedef to workaround a msvc 14.1 bug typedef void(type)(error_code); }; template struct completion_signature_impl { // Using typedef to workaround a msvc 14.1 bug typedef void(type)(error_code, typename AlgoParams::result_type); }; template using completion_signature_t = typename completion_signature_impl< AlgoParams, has_void_result::value>::type; // Intermediate handler template struct generic_algo_fn { static_assert(!has_void_result::value, "Internal error: result_type should be non-void"); using result_t = typename AlgoParams::result_type; template void operator()(Handler&& handler, error_code ec) { std::move(handler)(ec, ec ? result_t{} : get_result(*st)); } connection_state* st; }; // Note: the 1st async_initiate arg should be a diagnostics*, // so completion tokens knowing how Boost.MySQL work can operate class connection_impl { std::unique_ptr engine_; std::unique_ptr st_; asio::any_io_executor get_executor() const { return engine_->get_executor(); } // Helper for execution requests template static auto make_request(T&& input, connection_state& st) -> decltype(execution_request_traits::type>::make_request( std::forward(input), get_shared_fields(st) )) { return execution_request_traits::type>::make_request( std::forward(input), get_shared_fields(st) ); } // Generic algorithm template typename AlgoParams::result_type run_impl( AlgoParams params, error_code& ec, diagnostics& diag, std::true_type /* has_void_result */ ) { engine_->run(setup(*st_, diag, params), ec); } template typename AlgoParams::result_type run_impl( AlgoParams params, error_code& ec, diagnostics& diag, std::false_type /* has_void_result */ ) { engine_->run(setup(*st_, diag, params), ec); return get_result(*st_); } template static void async_run_impl( engine& eng, connection_state& st, AlgoParams params, diagnostics& diag, Handler&& handler, std::true_type /* has_void_result */ ) { eng.async_run(setup(st, diag, params), std::forward(handler)); } template static void async_run_impl( engine& eng, connection_state& st, AlgoParams params, diagnostics& diag, Handler&& handler, std::false_type /* has_void_result */ ) { eng.async_run( setup(st, diag, params), make_intermediate_handler(generic_algo_fn{&st}, std::forward(handler)) ); } template static void async_run_impl( engine& eng, connection_state& st, AlgoParams params, diagnostics& diag, Handler&& handler ) { async_run_impl(eng, st, params, diag, std::forward(handler), has_void_result{}); } struct run_algo_initiation : initiation_base { using initiation_base::initiation_base; template void operator()( Handler&& handler, diagnostics* diag, engine* eng, connection_state* st, AlgoParams params ) { async_run_impl(*eng, *st, params, *diag, std::forward(handler)); } }; // Connect static connect_algo_params make_params_connect(const void* server_address, const handshake_params& params) { return connect_algo_params{server_address, params, false}; } static connect_algo_params make_params_connect_v2(const connect_params& params) { return connect_algo_params{ ¶ms.server_address, make_hparams(params), params.server_address.type() == address_type::unix_path }; } template struct initiate_connect : initiation_base { using initiation_base::initiation_base; template void operator()( Handler&& handler, diagnostics* diag, engine* eng, connection_state* st, const EndpointType& endpoint, handshake_params params ) { async_run_impl( *eng, *st, make_params_connect(&endpoint, params), *diag, std::forward(handler) ); } }; struct initiate_connect_v2 : initiation_base { using initiation_base::initiation_base; template void operator()( Handler&& handler, diagnostics* diag, engine* eng, connection_state* st, const connect_params* params ) { async_run_impl(*eng, *st, make_params_connect_v2(*params), *diag, std::forward(handler)); } }; // execute struct initiate_execute : initiation_base { using initiation_base::initiation_base; template void operator()( Handler&& handler, diagnostics* diag, engine* eng, connection_state* st, ExecutionRequest&& req, execution_processor* proc ) { async_run_impl( *eng, *st, execute_algo_params{make_request(std::forward(req), *st), proc}, *diag, std::forward(handler) ); } }; // start execution struct initiate_start_execution : initiation_base { using initiation_base::initiation_base; template void operator()( Handler&& handler, diagnostics* diag, engine* eng, connection_state* st, ExecutionRequest&& req, execution_processor* proc ) { async_run_impl( *eng, *st, start_execution_algo_params{make_request(std::forward(req), *st), proc}, *diag, std::forward(handler) ); } }; public: BOOST_MYSQL_DECL connection_impl( std::size_t read_buff_size, std::size_t max_buffer_size, std::unique_ptr eng ); BOOST_MYSQL_DECL metadata_mode meta_mode() const; BOOST_MYSQL_DECL void set_meta_mode(metadata_mode m); BOOST_MYSQL_DECL bool ssl_active() const; BOOST_MYSQL_DECL bool backslash_escapes() const; BOOST_MYSQL_DECL system::result current_character_set() const; BOOST_MYSQL_DECL boost::optional connection_id() const; BOOST_MYSQL_DECL diagnostics& shared_diag(); engine& get_engine() { BOOST_ASSERT(engine_); return *engine_; } const engine& get_engine() const { BOOST_ASSERT(engine_); return *engine_; } // Generic algorithm template typename AlgoParams::result_type run(AlgoParams params, error_code& ec, diagnostics& diag) { return run_impl(params, ec, diag, has_void_result{}); } template auto async_run(AlgoParams params, diagnostics& diag, CompletionToken&& token) -> decltype(asio::async_initiate>( run_algo_initiation(get_executor()), token, &diag, engine_.get(), st_.get(), params )) { return asio::async_initiate>( run_algo_initiation(get_executor()), token, &diag, engine_.get(), st_.get(), params ); } // Connect template void connect( const EndpointType& endpoint, const handshake_params& params, error_code& err, diagnostics& diag ) { run(make_params_connect(&endpoint, params), err, diag); } void connect_v2(const connect_params& params, error_code& err, diagnostics& diag) { run(make_params_connect_v2(params), err, diag); } template auto async_connect( const EndpointType& endpoint, const handshake_params& params, diagnostics& diag, CompletionToken&& token ) -> decltype(asio::async_initiate( initiate_connect(get_executor()), token, &diag, engine_.get(), st_.get(), endpoint, params )) { return asio::async_initiate( initiate_connect(get_executor()), token, &diag, engine_.get(), st_.get(), endpoint, params ); } template auto async_connect_v2(const connect_params& params, diagnostics& diag, CompletionToken&& token) -> decltype(asio::async_initiate( initiate_connect_v2(get_executor()), token, &diag, engine_.get(), st_.get(), ¶ms )) { return asio::async_initiate( initiate_connect_v2(get_executor()), token, &diag, engine_.get(), st_.get(), ¶ms ); } // Handshake handshake_algo_params make_params_handshake(const handshake_params& params) const { return {params, false}; } // Execute template void execute(ExecutionRequest&& req, ResultsType& result, error_code& err, diagnostics& diag) { run( execute_algo_params{ make_request(std::forward(req), *st_), &access::get_impl(result).get_interface() }, err, diag ); } template auto async_execute( ExecutionRequest&& req, ResultsType& result, diagnostics& diag, CompletionToken&& token ) -> decltype(asio::async_initiate( initiate_execute(get_executor()), token, &diag, engine_.get(), st_.get(), std::forward(req), &access::get_impl(result).get_interface() )) { return asio::async_initiate( initiate_execute(get_executor()), token, &diag, engine_.get(), st_.get(), std::forward(req), &access::get_impl(result).get_interface() ); } // Start execution template void start_execution( ExecutionRequest&& req, ExecutionStateType& exec_st, error_code& err, diagnostics& diag ) { run( start_execution_algo_params{ make_request(std::forward(req), *st_), &access::get_impl(exec_st).get_interface() }, err, diag ); } template auto async_start_execution( ExecutionRequest&& req, ExecutionStateType& exec_st, diagnostics& diag, CompletionToken&& token ) -> decltype(asio::async_initiate( initiate_start_execution(get_executor()), token, &diag, engine_.get(), st_.get(), std::forward(req), &access::get_impl(exec_st).get_interface() )) { return asio::async_initiate( initiate_start_execution(get_executor()), token, &diag, engine_.get(), st_.get(), std::forward(req), &access::get_impl(exec_st).get_interface() ); } // Read some rows (dynamic) read_some_rows_dynamic_algo_params make_params_read_some_rows(execution_state& st) const { return {&access::get_impl(st).get_interface()}; } // Read some rows (static) template read_some_rows_algo_params make_params_read_some_rows_static( ExecutionState& exec_st, span output ) const { return { &access::get_impl(exec_st).get_interface(), access::get_impl(exec_st).make_output_ref(output) }; } // Read resultset head template read_resultset_head_algo_params make_params_read_resultset_head(ExecutionStateType& st) const { return {&detail::access::get_impl(st).get_interface()}; } // Close statement close_statement_algo_params make_params_close_statement(statement stmt) const { return {stmt.id()}; } // Run pipeline. Separately compiled to avoid including the pipeline header here BOOST_MYSQL_DECL static run_pipeline_algo_params make_params_pipeline( const pipeline_request& req, std::vector& response ); // Exposed for testing connection_state& get_state() { return *st_; } }; // To use some completion tokens, like deferred, in C++11, the old macros // BOOST_ASIO_INITFN_AUTO_RESULT_TYPE are no longer enough. // Helper typedefs to reduce duplication template using async_run_t = decltype(std::declval().async_run( std::declval(), std::declval(), std::declval() )); template using async_connect_t = decltype(std::declval().async_connect( std::declval(), std::declval(), std::declval(), std::declval() )); template using async_connect_v2_t = decltype(std::declval().async_connect_v2( std::declval(), std::declval(), std::declval() )); template using async_execute_t = decltype(std::declval().async_execute( std::declval(), std::declval(), std::declval(), std::declval() )); template using async_start_execution_t = decltype(std::declval().async_start_execution( std::declval(), std::declval(), std::declval(), std::declval() )); template using async_handshake_t = async_run_t; template using async_read_resultset_head_t = async_run_t; template using async_read_some_rows_dynamic_t = async_run_t; template using async_prepare_statement_t = async_run_t; template using async_close_statement_t = async_run_t; template using async_set_character_set_t = async_run_t; template using async_ping_t = async_run_t; template using async_reset_connection_t = async_run_t; template using async_quit_connection_t = async_run_t; template using async_close_connection_t = async_run_t; template using async_run_pipeline_t = async_run_t; } // namespace detail } // namespace mysql } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/detail/connection_pool_fwd.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_CONNECTION_POOL_FWD_HPP #define BOOST_MYSQL_DETAIL_CONNECTION_POOL_FWD_HPP #include #include namespace boost { namespace mysql { class pooled_connection; class any_connection; namespace detail { template class basic_connection_node; template class basic_pool_impl; using connection_node = basic_connection_node; using pool_impl = basic_pool_impl; BOOST_MYSQL_DECL void return_connection(pool_impl& pool, connection_node& node, bool should_reset) noexcept; BOOST_MYSQL_DECL any_connection& get_connection(connection_node& node) noexcept; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/datetime.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_DATETIME_HPP #define BOOST_MYSQL_DETAIL_DATETIME_HPP // All these algorithms have been taken from: // http://howardhinnant.github.io/date_algorithms.html #include #include #include #include namespace boost { namespace mysql { namespace detail { // Helpers BOOST_INLINE_CONSTEXPR unsigned char last_month_day_arr[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; constexpr bool is_leap(std::uint16_t y) noexcept { return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); } constexpr inline std::uint8_t last_month_day(std::uint16_t y, std::uint8_t m) noexcept { return m != 2 || !is_leap(y) ? last_month_day_arr[m - 1] : 29u; } // Interface BOOST_INLINE_CONSTEXPR std::uint16_t max_year = 9999; BOOST_INLINE_CONSTEXPR std::uint8_t max_month = 12; BOOST_INLINE_CONSTEXPR std::uint8_t max_day = 31; BOOST_INLINE_CONSTEXPR std::uint8_t max_hour = 23; BOOST_INLINE_CONSTEXPR std::uint8_t max_min = 59; BOOST_INLINE_CONSTEXPR std::uint8_t max_sec = 59; BOOST_INLINE_CONSTEXPR std::uint32_t max_micro = 999999; constexpr inline bool is_valid(std::uint16_t years, std::uint8_t month, std::uint8_t day) noexcept { return years <= max_year && month > 0 && month <= max_month && day > 0 && day <= last_month_day(years, month); } BOOST_CXX14_CONSTEXPR inline int ymd_to_days( std::uint16_t years, std::uint8_t month, std::uint8_t day ) noexcept { BOOST_ASSERT(is_valid(years, month, day)); int y = years; const int m = month; const int d = day; y -= m <= 2; const int era = (y >= 0 ? y : y - 399) / 400; const unsigned yoe = static_cast(y - era * 400); // [0, 399] const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; // [0, 365] const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096] return era * 146097 + static_cast(doe) - 719468; } BOOST_CXX14_CONSTEXPR inline bool days_to_ymd( int num_days, std::uint16_t& years, std::uint8_t& month, std::uint8_t& day ) noexcept { // Prevent overflow constexpr int days_magic = 719468; if (num_days > (std::numeric_limits::max)() - days_magic) return false; num_days += days_magic; const int era = (num_days >= 0 ? num_days : num_days - 146096) / 146097; const unsigned doe = static_cast(num_days - era * 146097); // [0, 146096] const unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399] const int y = static_cast(yoe) + era * 400; const unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365] const unsigned mp = (5 * doy + 2) / 153; // [0, 11] const unsigned d = doy - (153 * mp + 2) / 5 + 1; // [1, 31] const unsigned m = mp + (mp < 10 ? 3 : -9); // [1, 12] const int final_year = y + (m <= 2); if (final_year < 0 || final_year > static_cast(max_year)) return false; else { years = static_cast(final_year); month = static_cast(m); day = static_cast(d); return true; } } } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/engine.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_ENGINE_HPP #define BOOST_MYSQL_DETAIL_ENGINE_HPP #include #include #include namespace boost { namespace mysql { namespace detail { class engine { public: using executor_type = asio::any_io_executor; virtual ~engine() {} virtual executor_type get_executor() = 0; virtual bool supports_ssl() const = 0; virtual void run(any_resumable_ref resumable, error_code& err) = 0; virtual void async_run(any_resumable_ref resumable, asio::any_completion_handler) = 0; }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/engine_impl.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_ENGINE_IMPL_HPP #define BOOST_MYSQL_DETAIL_ENGINE_IMPL_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { namespace detail { inline asio::mutable_buffer to_buffer(span buff) noexcept { return asio::mutable_buffer(buff.data(), buff.size()); } inline bool has_terminal_cancellation(asio::cancellation_type_t cancel_type) { return static_cast(cancel_type & asio::cancellation_type_t::terminal); } template struct run_algo_op { int resume_point_{0}; EngineStream& stream_; any_resumable_ref resumable_; bool has_done_io_{false}; error_code stored_ec_; run_algo_op(EngineStream& stream, any_resumable_ref algo) noexcept : stream_(stream), resumable_(algo) {} template void operator()(Self& self, error_code io_ec = {}, std::size_t bytes_transferred = 0) { next_action act; switch (resume_point_) { case 0: while (true) { // If we were cancelled, but the last operation completed successfully, // set a cancelled error code so the algorithm exits. This might happen // if a cancellation signal is emitted after an intermediate operation succeeded // but before the handler was called. if (!io_ec && has_terminal_cancellation(self.cancelled())) io_ec = asio::error::operation_aborted; // Run the op act = resumable_.resume(io_ec, bytes_transferred); if (act.is_done()) { stored_ec_ = act.error(); if (!has_done_io_) { BOOST_MYSQL_YIELD( resume_point_, 1, asio::async_immediate(stream_.get_executor(), std::move(self)) ) } self.complete(stored_ec_); return; } else if (act.type() == next_action_type::read) { BOOST_MYSQL_YIELD( resume_point_, 2, stream_.async_read_some( to_buffer(act.read_args().buffer), act.read_args().use_ssl, std::move(self) ) ) has_done_io_ = true; } else if (act.type() == next_action_type::write) { BOOST_MYSQL_YIELD( resume_point_, 3, stream_.async_write_some( asio::buffer(act.write_args().buffer), act.write_args().use_ssl, std::move(self) ) ) has_done_io_ = true; } else if (act.type() == next_action_type::ssl_handshake) { BOOST_MYSQL_YIELD(resume_point_, 4, stream_.async_ssl_handshake(std::move(self))) has_done_io_ = true; } else if (act.type() == next_action_type::ssl_shutdown) { BOOST_MYSQL_YIELD(resume_point_, 5, stream_.async_ssl_shutdown(std::move(self))) has_done_io_ = true; } else if (act.type() == next_action_type::connect) { BOOST_MYSQL_YIELD( resume_point_, 6, stream_.async_connect(act.connect_endpoint(), std::move(self)) ) has_done_io_ = true; } else { BOOST_ASSERT(act.type() == next_action_type::close); stream_.close(io_ec); } } } } }; // EngineStream is an "extended" stream concept, with the following operations: // using executor_type = asio::any_io_executor; // executor_type get_executor(); // bool supports_ssl() const; // std::size_t read_some(asio::mutable_buffer, bool use_ssl, error_code&); // void async_read_some(asio::mutable_buffer, bool use_ssl, CompletinToken&&); // std::size_t write_some(asio::const_buffer, bool use_ssl, error_code&); // void async_write_some(asio::const_buffer, bool use_ssl, CompletinToken&&); // void ssl_handshake(error_code&); // void async_ssl_handshake(CompletionToken&&); // void ssl_shutdown(error_code&); // void async_ssl_shutdown(CompletionToken&&); // void connect(const void* server_address, error_code&); // void async_connect(const void* server_address, CompletionToken&&); // void close(error_code&); // Async operations are only required to support callback types // See stream_adaptor for an implementation template class engine_impl final : public engine { EngineStream stream_; public: template engine_impl(Args&&... args) : stream_(std::forward(args)...) { } EngineStream& stream() { return stream_; } const EngineStream& stream() const { return stream_; } using executor_type = asio::any_io_executor; executor_type get_executor() override final { return stream_.get_executor(); } bool supports_ssl() const override final { return stream_.supports_ssl(); } void run(any_resumable_ref resumable, error_code& ec) override final { ec.clear(); error_code io_ec; std::size_t bytes_transferred = 0; while (true) { // Run the op auto act = resumable.resume(io_ec, bytes_transferred); // Apply the next action bytes_transferred = 0; if (act.is_done()) { ec = act.error(); return; } else if (act.type() == next_action_type::read) { bytes_transferred = stream_.read_some( to_buffer(act.read_args().buffer), act.read_args().use_ssl, io_ec ); } else if (act.type() == next_action_type::write) { bytes_transferred = stream_.write_some( asio::buffer(act.write_args().buffer), act.write_args().use_ssl, io_ec ); } else if (act.type() == next_action_type::ssl_handshake) { stream_.ssl_handshake(io_ec); } else if (act.type() == next_action_type::ssl_shutdown) { stream_.ssl_shutdown(io_ec); } else if (act.type() == next_action_type::connect) { stream_.connect(act.connect_endpoint(), io_ec); } else { BOOST_ASSERT(act.type() == next_action_type::close); stream_.close(io_ec); } } } void async_run(any_resumable_ref resumable, asio::any_completion_handler h) override final { return asio::async_compose, void(error_code)>( run_algo_op(stream_, resumable), h, stream_ ); } }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/engine_stream_adaptor.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_ENGINE_STREAM_ADAPTOR_HPP #define BOOST_MYSQL_DETAIL_ENGINE_STREAM_ADAPTOR_HPP #include #include #include #include #include #include #include #include #include #include #include // Adapts a regular Asio Stream to meet the EngineStream requirements // We only use callbacks with the async functions in this file, so no need to support arbitrary return types namespace boost { namespace mysql { namespace detail { // Connect and close helpers // LCOV_EXCL_START template void do_connect_impl(Stream&, const void*, error_code&, std::false_type) { BOOST_ASSERT(false); } // LCOV_EXCL_STOP template void do_connect_impl(Stream& stream, const void* ep, error_code& ec, std::true_type) { stream.lowest_layer().connect( *static_cast(ep), ec ); } template void do_connect(Stream& stream, const void* ep, error_code& ec) { do_connect_impl(stream, ep, ec, is_socket_stream{}); } // LCOV_EXCL_START template void do_async_connect_impl(Stream&, const void*, CompletionToken&&, std::false_type) { BOOST_ASSERT(false); } // LCOV_EXCL_STOP template void do_async_connect_impl(Stream& stream, const void* ep, CompletionToken&& token, std::true_type) { stream.lowest_layer().async_connect( *static_cast(ep), std::forward(token) ); } template void do_async_connect(Stream& stream, const void* ep, CompletionToken&& token) { do_async_connect_impl(stream, ep, std::forward(token), is_socket_stream{}); } // LCOV_EXCL_START template void do_close_impl(Stream&, error_code&, std::false_type) { BOOST_ASSERT(false); } // LCOV_EXCL_STOP template void do_close_impl(Stream& stream, error_code& ec, std::true_type) { stream.lowest_layer().shutdown(asio::socket_base::shutdown_both, ec); stream.lowest_layer().close(ec); } template void do_close(Stream& stream, error_code& ec) { do_close_impl(stream, ec, is_socket_stream{}); } template class engine_stream_adaptor { Stream stream_; public: template engine_stream_adaptor(Args&&... args) : stream_(std::forward(args)...) { } Stream& stream() { return stream_; } const Stream& stream() const { return stream_; } bool supports_ssl() const { return false; } using executor_type = asio::any_io_executor; executor_type get_executor() { return stream_.get_executor(); } // SSL // LCOV_EXCL_START void ssl_handshake(error_code&) { BOOST_ASSERT(false); } template void async_ssl_handshake(CompletinToken&&) { BOOST_ASSERT(false); } void ssl_shutdown(error_code&) { BOOST_ASSERT(false); } template void async_ssl_shutdown(CompletionToken&&) { BOOST_ASSERT(false); } // LCOV_EXCL_STOP // Reading std::size_t read_some(boost::asio::mutable_buffer buff, bool use_ssl, error_code& ec) { BOOST_ASSERT(!use_ssl); boost::ignore_unused(use_ssl); return stream_.read_some(buff, ec); } template void async_read_some(boost::asio::mutable_buffer buff, bool use_ssl, CompletionToken&& token) { BOOST_ASSERT(!use_ssl); boost::ignore_unused(use_ssl); stream_.async_read_some(buff, std::forward(token)); } // Writing std::size_t write_some(boost::asio::const_buffer buff, bool use_ssl, error_code& ec) { BOOST_ASSERT(!use_ssl); boost::ignore_unused(use_ssl); return stream_.write_some(buff, ec); } template void async_write_some(boost::asio::const_buffer buff, bool use_ssl, CompletionToken&& token) { BOOST_ASSERT(!use_ssl); boost::ignore_unused(use_ssl); stream_.async_write_some(buff, std::forward(token)); } // Connect and close void connect(const void* endpoint, error_code& ec) { do_connect(stream_, endpoint, ec); } template void async_connect(const void* endpoint, CompletionToken&& token) { do_async_connect(stream_, endpoint, std::forward(token)); } void close(error_code& ec) { do_close(stream_, ec); } }; template class engine_stream_adaptor> { asio::ssl::stream stream_; public: template engine_stream_adaptor(Args&&... args) : stream_(std::forward(args)...) { } asio::ssl::stream& stream() { return stream_; } const asio::ssl::stream& stream() const { return stream_; } bool supports_ssl() const { return true; } using executor_type = asio::any_io_executor; executor_type get_executor() { return stream_.get_executor(); } // SSL void ssl_handshake(error_code& ec) { stream_.handshake(asio::ssl::stream_base::client, ec); } template void async_ssl_handshake(CompletionToken&& token) { stream_.async_handshake(asio::ssl::stream_base::client, std::forward(token)); } void ssl_shutdown(error_code& ec) { stream_.shutdown(ec); } template void async_ssl_shutdown(CompletionToken&& token) { stream_.async_shutdown(std::forward(token)); } // Reading std::size_t read_some(boost::asio::mutable_buffer buff, bool use_ssl, error_code& ec) { if (use_ssl) { return stream_.read_some(buff, ec); } else { return stream_.next_layer().read_some(buff, ec); } } template void async_read_some(boost::asio::mutable_buffer buff, bool use_ssl, CompletionToken&& token) { if (use_ssl) { stream_.async_read_some(buff, std::forward(token)); } else { stream_.next_layer().async_read_some(buff, std::forward(token)); } } // Writing std::size_t write_some(boost::asio::const_buffer buff, bool use_ssl, error_code& ec) { if (use_ssl) { return stream_.write_some(buff, ec); } else { return stream_.next_layer().write_some(buff, ec); } } template void async_write_some(boost::asio::const_buffer buff, bool use_ssl, CompletionToken&& token) { if (use_ssl) { stream_.async_write_some(buff, std::forward(token)); } else { stream_.next_layer().async_write_some(buff, std::forward(token)); } } // Connect and close void connect(const void* endpoint, error_code& ec) { do_connect(stream_, endpoint, ec); } template void async_connect(const void* endpoint, CompletionToken&& token) { do_async_connect(stream_, endpoint, std::forward(token)); } void close(error_code& ec) { do_close(stream_, ec); } }; #ifdef BOOST_MYSQL_SEPARATE_COMPILATION extern template class engine_impl>>; extern template class engine_impl>; #endif template std::unique_ptr make_engine(Args&&... args) { return std::unique_ptr(new engine_impl>(std::forward(args)...) ); } // Use these only for engines created using make_engine template Stream& stream_from_engine(engine& eng) { using derived_t = engine_impl>; return static_cast(eng).stream().stream(); } template const Stream& stream_from_engine(const engine& eng) { using derived_t = engine_impl>; return static_cast(eng).stream().stream(); } } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/escape_string.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_ESCAPE_STRING_HPP #define BOOST_MYSQL_DETAIL_ESCAPE_STRING_HPP #include #include #include #include #include namespace boost { namespace mysql { // Forward decls struct format_options; namespace detail { BOOST_ATTRIBUTE_NODISCARD BOOST_MYSQL_DECL error_code escape_string(string_view input, const format_options& opts, char quote_char, output_string_ref output); } // namespace detail } // namespace mysql } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/detail/execution_concepts.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_EXECUTION_CONCEPTS_HPP #define BOOST_MYSQL_DETAIL_EXECUTION_CONCEPTS_HPP #include #include #include #include #ifdef BOOST_MYSQL_HAS_CONCEPTS namespace boost { namespace mysql { // Forward decls template class static_execution_state; template class static_results; class execution_state; class results; namespace detail { // Execution state template struct is_static_execution_state : std::false_type { }; template struct is_static_execution_state> : std::true_type { }; template concept execution_state_type = std::is_same_v || is_static_execution_state::value; // Results template struct is_static_results : std::false_type { }; template struct is_static_results> : std::true_type { }; template concept results_type = std::is_same_v || is_static_results::value; // Execution request template struct is_execution_request { static constexpr bool value = !std::is_base_of< no_execution_request_traits, execution_request_traits::type>>::value; }; template concept execution_request = is_execution_request::value; } // namespace detail } // namespace mysql } // namespace boost #define BOOST_MYSQL_EXECUTION_STATE_TYPE ::boost::mysql::detail::execution_state_type #define BOOST_MYSQL_RESULTS_TYPE ::boost::mysql::detail::results_type #define BOOST_MYSQL_EXECUTION_REQUEST ::boost::mysql::detail::execution_request #else #define BOOST_MYSQL_EXECUTION_STATE_TYPE class #define BOOST_MYSQL_RESULTS_TYPE class #define BOOST_MYSQL_EXECUTION_REQUEST class #endif #endif ================================================ FILE: include/boost/mysql/detail/execution_processor/execution_processor.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_EXECUTION_PROCESSOR_HPP #define BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_EXECUTION_PROCESSOR_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { namespace detail { // A type-erased reference to be used as the output range for static_execution_state class output_ref { // Pointer to the first element of the span void* data_{}; // Number of elements in the span std::size_t max_size_{(std::numeric_limits::max)()}; // Identifier for the type of elements. Index in the resultset type list std::size_t type_index_{}; // Offset into the span's data (static_execution_state). Otherwise unused std::size_t offset_{}; public: constexpr output_ref() noexcept = default; template constexpr output_ref(boost::span span, std::size_t type_index, std::size_t offset = 0) noexcept : data_(span.data()), max_size_(span.size()), type_index_(type_index), offset_(offset) { } std::size_t max_size() const noexcept { return max_size_; } std::size_t type_index() const noexcept { return type_index_; } std::size_t offset() const noexcept { return offset_; } void set_offset(std::size_t v) noexcept { offset_ = v; } template T& span_element() const noexcept { BOOST_ASSERT(data_); return static_cast(data_)[offset_]; } }; class execution_processor { public: virtual ~execution_processor() {} void reset(resultset_encoding enc, metadata_mode mode) noexcept { state_ = state_t::reading_first; encoding_ = enc; mode_ = mode; seqnum_ = 0; remaining_meta_ = 0; reset_impl(); } BOOST_ATTRIBUTE_NODISCARD error_code on_head_ok_packet(const ok_view& pack, diagnostics& diag) { BOOST_ASSERT(is_reading_head()); auto err = on_head_ok_packet_impl(pack, diag); set_state_for_ok(pack); return err; } void on_num_meta(std::size_t num_columns) { BOOST_ASSERT(is_reading_head()); on_num_meta_impl(num_columns); remaining_meta_ = num_columns; set_state(state_t::reading_metadata); } BOOST_ATTRIBUTE_NODISCARD error_code on_meta(const coldef_view& pack, diagnostics& diag) { BOOST_ASSERT(is_reading_meta()); bool is_last = --remaining_meta_ == 0; auto err = on_meta_impl(pack, is_last, diag); if (is_last) set_state(state_t::reading_rows); return err; } void on_row_batch_start() { BOOST_ASSERT(is_reading_rows()); on_row_batch_start_impl(); } void on_row_batch_finish() { on_row_batch_finish_impl(); } BOOST_ATTRIBUTE_NODISCARD error_code on_row(span msg, const output_ref& ref, std::vector& storage) { BOOST_ASSERT(is_reading_rows()); return on_row_impl(msg, ref, storage); } BOOST_ATTRIBUTE_NODISCARD error_code on_row_ok_packet(const ok_view& pack) { BOOST_ASSERT(is_reading_rows()); auto err = on_row_ok_packet_impl(pack); set_state_for_ok(pack); return err; } bool is_reading_first() const noexcept { return state_ == state_t::reading_first; } bool is_reading_first_subseq() const noexcept { return state_ == state_t::reading_first_subseq; } bool is_reading_head() const noexcept { return state_ == state_t::reading_first || state_ == state_t::reading_first_subseq; } bool is_reading_meta() const noexcept { return state_ == state_t::reading_metadata; } bool is_reading_rows() const noexcept { return state_ == state_t::reading_rows; } bool is_complete() const noexcept { return state_ == state_t::complete; } resultset_encoding encoding() const noexcept { return encoding_; } std::uint8_t& sequence_number() noexcept { return seqnum_; } metadata_mode meta_mode() const noexcept { return mode_; } protected: virtual void reset_impl() noexcept = 0; virtual error_code on_head_ok_packet_impl(const ok_view& pack, diagnostics& diag) = 0; virtual void on_num_meta_impl(std::size_t num_columns) = 0; virtual error_code on_meta_impl(const coldef_view& coldef, bool is_last, diagnostics& diag) = 0; virtual error_code on_row_ok_packet_impl(const ok_view& pack) = 0; virtual error_code on_row_impl( span msg, const output_ref& ref, std::vector& storage ) = 0; virtual void on_row_batch_start_impl() = 0; virtual void on_row_batch_finish_impl() = 0; metadata create_meta(const coldef_view& coldef) const { return access::construct(coldef, mode_ == metadata_mode::full); } private: enum class state_t { // waiting for 1st packet, for the 1st resultset reading_first, // same, but for subsequent resultsets (distiguised to provide a cleaner xp to // the user in (static_)execution_state) reading_first_subseq, // waiting for metadata packets reading_metadata, // waiting for rows reading_rows, // done complete }; state_t state_{state_t::reading_first}; resultset_encoding encoding_{resultset_encoding::text}; std::uint8_t seqnum_{}; metadata_mode mode_{metadata_mode::minimal}; std::size_t remaining_meta_{}; void set_state(state_t v) noexcept { state_ = v; } void set_state_for_ok(const ok_view& pack) noexcept { if (pack.more_results()) { set_state(state_t::reading_first_subseq); } else { set_state(state_t::complete); } } }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/execution_processor/execution_state_impl.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_EXECUTION_STATE_IMPL_HPP #define BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_EXECUTION_STATE_IMPL_HPP #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { namespace detail { class execution_state_impl final : public execution_processor { struct ok_data { bool has_value{false}; // The OK packet information is default constructed, or actual data? std::uint64_t affected_rows{}; // OK packet data std::uint64_t last_insert_id{}; // OK packet data std::uint16_t warnings{}; // OK packet data bool is_out_params{false}; // Does this resultset contain OUT param information? }; std::vector meta_; ok_data eof_data_; std::vector info_; void on_new_resultset() noexcept { meta_.clear(); eof_data_ = ok_data{}; info_.clear(); } BOOST_MYSQL_DECL void on_ok_packet_impl(const ok_view& pack); BOOST_MYSQL_DECL void reset_impl() noexcept override final; BOOST_MYSQL_DECL error_code on_head_ok_packet_impl(const ok_view& pack, diagnostics&) override final; BOOST_MYSQL_DECL void on_num_meta_impl(std::size_t num_columns) override final; BOOST_MYSQL_DECL error_code on_meta_impl(const coldef_view&, bool, diagnostics&) override final; BOOST_MYSQL_DECL error_code on_row_impl(span msg, const output_ref&, std::vector& fields) override final; BOOST_MYSQL_DECL error_code on_row_ok_packet_impl(const ok_view& pack) override final; void on_row_batch_start_impl() noexcept override final {} void on_row_batch_finish_impl() noexcept override final {} public: execution_state_impl() = default; metadata_collection_view meta() const noexcept { return meta_; } std::uint64_t get_affected_rows() const noexcept { BOOST_ASSERT(eof_data_.has_value); return eof_data_.affected_rows; } std::uint64_t get_last_insert_id() const noexcept { BOOST_ASSERT(eof_data_.has_value); return eof_data_.last_insert_id; } unsigned get_warning_count() const noexcept { BOOST_ASSERT(eof_data_.has_value); return eof_data_.warnings; } string_view get_info() const noexcept { BOOST_ASSERT(eof_data_.has_value); return string_view(info_.data(), info_.size()); } bool get_is_out_params() const noexcept { BOOST_ASSERT(eof_data_.has_value); return eof_data_.is_out_params; } execution_state_impl& get_interface() noexcept { return *this; } }; } // namespace detail } // namespace mysql } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/detail/execution_processor/results_impl.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_RESULTS_IMPL_HPP #define BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_RESULTS_IMPL_HPP #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { namespace detail { struct per_resultset_data { std::size_t num_columns{}; // Number of columns this resultset has std::size_t meta_offset{}; // Offset into the vector of metadata std::size_t field_offset; // Offset into the vector of fields (append mode only) std::size_t num_rows{}; // Number of rows this resultset has (append mode only) std::uint64_t affected_rows{}; // OK packet data std::uint64_t last_insert_id{}; // OK packet data std::uint16_t warnings{}; // OK packet data std::size_t info_offset{}; // Offset into the vector of info characters std::size_t info_size{}; // Number of characters that this resultset's info string has bool has_ok_packet_data{false}; // The OK packet information is default constructed, or actual data? bool is_out_params{false}; // Does this resultset contain OUT param information? }; // A container similar to a vector with SBO. To avoid depending on Boost.Container class resultset_container { bool first_has_data_{false}; per_resultset_data first_; std::vector rest_; public: resultset_container() = default; std::size_t size() const noexcept { return !first_has_data_ ? 0 : rest_.size() + 1; } bool empty() const noexcept { return !first_has_data_; } void clear() noexcept { first_has_data_ = false; rest_.clear(); } per_resultset_data& operator[](std::size_t i) noexcept { return const_cast(const_cast(*this)[i]); } const per_resultset_data& operator[](std::size_t i) const noexcept { BOOST_ASSERT(i < size()); return i == 0 ? first_ : rest_[i - 1]; } per_resultset_data& back() noexcept { return const_cast(const_cast(*this).back()); } const per_resultset_data& back() const noexcept { BOOST_ASSERT(first_has_data_); return rest_.empty() ? first_ : rest_.back(); } BOOST_MYSQL_DECL per_resultset_data& emplace_back(); }; // Rows for all resultsets are stored in a single rows_impl object. // - When a row batch is started, we record how many fields we had before the batch. // - When rows are read, fields are allocated in the rows_impl object, then deserialized against // the allocated storage. At this point, strings/blobs point into the connection read buffer. // - When a row batch is finished, we copy strings/blobs into the rows_impl, then transform them // into offsets to allow rows_impl to grow. // - When the final OK packet is received, offsets are transformed back into views. class results_impl final : public execution_processor { public: results_impl() = default; BOOST_MYSQL_DECL row_view get_out_params() const noexcept; std::size_t num_resultsets() const noexcept { return per_result_.size(); } rows_view get_rows(std::size_t index) const noexcept { const auto& resultset_data = per_result_[index]; return access::construct( rows_.fields().data() + resultset_data.field_offset, resultset_data.num_rows * resultset_data.num_columns, resultset_data.num_columns ); } metadata_collection_view get_meta(std::size_t index) const noexcept { const auto& resultset_data = get_resultset(index); return metadata_collection_view( meta_.data() + resultset_data.meta_offset, resultset_data.num_columns ); } std::uint64_t get_affected_rows(std::size_t index) const noexcept { return get_resultset(index).affected_rows; } std::uint64_t get_last_insert_id(std::size_t index) const noexcept { return get_resultset(index).last_insert_id; } unsigned get_warning_count(std::size_t index) const noexcept { return get_resultset(index).warnings; } string_view get_info(std::size_t index) const noexcept { const auto& resultset_data = get_resultset(index); return string_view(info_.data() + resultset_data.info_offset, resultset_data.info_size); } bool get_is_out_params(std::size_t index) const noexcept { return get_resultset(index).is_out_params; } results_impl& get_interface() noexcept { return *this; } private: // Virtual impls BOOST_MYSQL_DECL void reset_impl() noexcept override final; BOOST_MYSQL_DECL void on_num_meta_impl(std::size_t num_columns) override final; BOOST_MYSQL_DECL error_code on_head_ok_packet_impl(const ok_view& pack, diagnostics&) override final; BOOST_MYSQL_DECL error_code on_meta_impl(const coldef_view&, bool, diagnostics&) override final; BOOST_MYSQL_DECL error_code on_row_impl(span msg, const output_ref&, std::vector&) override final; BOOST_MYSQL_DECL error_code on_row_ok_packet_impl(const ok_view& pack) override final; BOOST_MYSQL_DECL void on_row_batch_start_impl() override final; BOOST_MYSQL_DECL void on_row_batch_finish_impl() override final; // Data std::vector meta_; resultset_container per_result_; std::vector info_; row_impl rows_; std::size_t num_fields_at_batch_start_{no_batch}; // Auxiliar static BOOST_INLINE_CONSTEXPR std::size_t no_batch = std::size_t(-1); bool has_active_batch() const noexcept { return num_fields_at_batch_start_ != no_batch; } BOOST_MYSQL_DECL void finish_batch(); per_resultset_data& current_resultset() noexcept { BOOST_ASSERT(!per_result_.empty()); return per_result_.back(); } const per_resultset_data& current_resultset() const noexcept { BOOST_ASSERT(!per_result_.empty()); return per_result_.back(); } BOOST_MYSQL_DECL per_resultset_data& add_resultset(); BOOST_MYSQL_DECL void on_ok_packet_impl(const ok_view& pack); const per_resultset_data& get_resultset(std::size_t index) const noexcept { BOOST_ASSERT(index < per_result_.size()); return per_result_[index]; } metadata_collection_view current_resultset_meta() const noexcept { return get_meta(per_result_.size() - 1); } }; } // namespace detail } // namespace mysql } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/detail/execution_processor/static_execution_state_impl.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_STATIC_EXECUTION_STATE_IMPL_HPP #define BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_STATIC_EXECUTION_STATE_IMPL_HPP #include #ifdef BOOST_MYSQL_CXX14 #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { namespace detail { using execst_parse_fn_t = error_code (*)(span pos_map, span from, const output_ref& ref); struct execst_resultset_descriptor { std::size_t num_columns; name_table_t name_table; meta_check_fn_t meta_check; execst_parse_fn_t parse_fn; std::size_t type_index; }; class execst_external_data { public: struct ptr_data { std::size_t* pos_map; }; execst_external_data(span desc, ptr_data ptr) noexcept : desc_(desc), ptr_(ptr) { } std::size_t num_resultsets() const noexcept { return desc_.size(); } std::size_t num_columns(std::size_t idx) const noexcept { BOOST_ASSERT(idx < num_resultsets()); return desc_[idx].num_columns; } name_table_t name_table(std::size_t idx) const noexcept { BOOST_ASSERT(idx < num_resultsets()); return desc_[idx].name_table; } meta_check_fn_t meta_check_fn(std::size_t idx) const noexcept { BOOST_ASSERT(idx < num_resultsets()); return desc_[idx].meta_check; } execst_parse_fn_t parse_fn(std::size_t idx) const noexcept { BOOST_ASSERT(idx < num_resultsets()); return desc_[idx].parse_fn; } std::size_t type_index(std::size_t idx) const noexcept { BOOST_ASSERT(idx < num_resultsets()); return desc_[idx].type_index; } span pos_map(std::size_t idx) const noexcept { return span(ptr_.pos_map, num_columns(idx)); } void set_pointers(ptr_data ptr) noexcept { ptr_ = ptr; } private: span desc_; ptr_data ptr_; }; class static_execution_state_erased_impl final : public execution_processor { public: static_execution_state_erased_impl(execst_external_data ext) noexcept : ext_(ext) {} execst_external_data& ext_data() noexcept { return ext_; } metadata_collection_view meta() const noexcept { return meta_; } std::uint64_t get_affected_rows() const noexcept { BOOST_ASSERT(ok_data_.has_value); return ok_data_.affected_rows; } std::uint64_t get_last_insert_id() const noexcept { BOOST_ASSERT(ok_data_.has_value); return ok_data_.last_insert_id; } unsigned get_warning_count() const noexcept { BOOST_ASSERT(ok_data_.has_value); return ok_data_.warnings; } string_view get_info() const noexcept { BOOST_ASSERT(ok_data_.has_value); return string_view(info_.data(), info_.size()); } bool get_is_out_params() const noexcept { BOOST_ASSERT(ok_data_.has_value); return ok_data_.is_out_params; } private: // Data struct ok_packet_data { bool has_value{false}; // The OK packet information is default constructed, or actual data? std::uint64_t affected_rows{}; // OK packet data std::uint64_t last_insert_id{}; // OK packet data std::uint16_t warnings{}; // OK packet data bool is_out_params{false}; // Does this resultset contain OUT param information? }; execst_external_data ext_; std::size_t resultset_index_{}; ok_packet_data ok_data_; std::vector info_; std::vector meta_; // Virtual impls BOOST_MYSQL_DECL void reset_impl() noexcept override final; BOOST_MYSQL_DECL error_code on_head_ok_packet_impl(const ok_view& pack, diagnostics& diag) override final; BOOST_MYSQL_DECL void on_num_meta_impl(std::size_t num_columns) override final; BOOST_MYSQL_DECL error_code on_meta_impl(const coldef_view& coldef, bool is_last, diagnostics& diag) override final; BOOST_MYSQL_DECL error_code on_row_impl( span msg, const output_ref& ref, std::vector& fields ) override final; BOOST_MYSQL_DECL error_code on_row_ok_packet_impl(const ok_view& pack) override final; void on_row_batch_start_impl() noexcept override final {} void on_row_batch_finish_impl() noexcept override final {} // Auxiliar name_table_t current_name_table() const noexcept { return ext_.name_table(resultset_index_ - 1); } span current_pos_map() noexcept { return ext_.pos_map(resultset_index_ - 1); } span current_pos_map() const noexcept { return ext_.pos_map(resultset_index_ - 1); } error_code meta_check(diagnostics& diag) const { return ext_.meta_check_fn(resultset_index_ - 1)(current_pos_map(), meta_, diag); } BOOST_MYSQL_DECL void on_new_resultset() noexcept; BOOST_MYSQL_DECL error_code on_ok_packet_impl(const ok_view& pack); }; template static error_code execst_parse_fn( span pos_map, span from, const output_ref& ref ) { return parse(pos_map, from, ref.span_element>()); } template constexpr std::array create_execst_resultset_descriptors() { return {{{ get_row_size(), get_row_name_table(), &meta_check, &execst_parse_fn, get_type_index, StaticRow...>(), }...}}; } template BOOST_INLINE_CONSTEXPR std::array execst_resultset_descriptor_table = create_execst_resultset_descriptors(); template class static_execution_state_impl { // Storage for our data, which requires knowing the template args struct { std::array> pos_map{}; } data_; // The type-erased impl, that will use pointers to the above storage static_execution_state_erased_impl impl_; execst_external_data::ptr_data ptr_data() noexcept { return { data_.pos_map.data(), }; } void set_pointers() noexcept { impl_.ext_data().set_pointers(ptr_data()); } public: static_execution_state_impl() noexcept : impl_({execst_resultset_descriptor_table, ptr_data()}) { } static_execution_state_impl(const static_execution_state_impl& rhs) : data_(rhs.data_), impl_(rhs.impl_) { set_pointers(); } static_execution_state_impl(static_execution_state_impl&& rhs) noexcept : data_(std::move(rhs.data_)), impl_(std::move(rhs.impl_)) { set_pointers(); } static_execution_state_impl& operator=(const static_execution_state_impl& rhs) { data_ = rhs.data_; impl_ = rhs.impl_; set_pointers(); return *this; } static_execution_state_impl& operator=(static_execution_state_impl&& rhs) { data_ = std::move(rhs.data_); impl_ = std::move(rhs.impl_); set_pointers(); return *this; } ~static_execution_state_impl() = default; template output_ref make_output_ref(span output, std::size_t offset = 0) const noexcept { constexpr std::size_t index = get_type_index(); static_assert( index != index_not_found, "SpanElementType must be one of the types returned by the query" ); return output_ref(output, index, offset); } const static_execution_state_erased_impl& get_interface() const noexcept { return impl_; } static_execution_state_erased_impl& get_interface() noexcept { return impl_; } }; } // namespace detail } // namespace mysql } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif // BOOST_MYSQL_CXX14 #endif ================================================ FILE: include/boost/mysql/detail/execution_processor/static_results_impl.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_STATIC_RESULTS_IMPL_HPP #define BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_STATIC_RESULTS_IMPL_HPP #include #ifdef BOOST_MYSQL_CXX14 #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { namespace detail { using results_reset_fn_t = void (*)(void*); using results_parse_fn_t = error_code (*)(span pos_map, span from, void* to); struct results_resultset_descriptor { std::size_t num_columns; name_table_t name_table; meta_check_fn_t meta_check; results_parse_fn_t parse_fn; }; struct static_per_resultset_data { std::size_t meta_offset{}; std::size_t meta_size{}; std::size_t info_offset{}; std::size_t info_size{}; bool has_ok_packet_data{false}; // The OK packet information is default constructed, or actual data? std::uint64_t affected_rows{}; // OK packet data std::uint64_t last_insert_id{}; // OK packet data std::uint16_t warnings{}; // OK packet data bool is_out_params{false}; // Does this resultset contain OUT param information? }; class results_external_data { public: struct ptr_data { void* rows; std::size_t* pos_map; static_per_resultset_data* per_resultset; }; results_external_data( span desc, results_reset_fn_t reset, ptr_data ptr ) noexcept : desc_(desc), reset_(reset), ptr_(ptr) { } void set_pointers(ptr_data ptr) noexcept { ptr_ = ptr; } std::size_t num_resultsets() const noexcept { return desc_.size(); } std::size_t num_columns(std::size_t idx) const noexcept { BOOST_ASSERT(idx < num_resultsets()); return desc_[idx].num_columns; } name_table_t name_table(std::size_t idx) const noexcept { BOOST_ASSERT(idx < num_resultsets()); return desc_[idx].name_table; } meta_check_fn_t meta_check_fn(std::size_t idx) const noexcept { BOOST_ASSERT(idx < num_resultsets()); return desc_[idx].meta_check; } results_parse_fn_t parse_fn(std::size_t idx) const noexcept { BOOST_ASSERT(idx < num_resultsets()); return desc_[idx].parse_fn; } results_reset_fn_t reset_fn() const noexcept { return reset_; } void* rows() const noexcept { return ptr_.rows; } span pos_map(std::size_t idx) const noexcept { return span(ptr_.pos_map, num_columns(idx)); } static_per_resultset_data& per_result(std::size_t idx) const noexcept { BOOST_ASSERT(idx < num_resultsets()); return ptr_.per_resultset[idx]; } private: span desc_; results_reset_fn_t reset_; ptr_data ptr_; }; class static_results_erased_impl final : public execution_processor { public: static_results_erased_impl(results_external_data ext) noexcept : ext_(ext) {} results_external_data& ext_data() noexcept { return ext_; } metadata_collection_view get_meta(std::size_t index) const noexcept { const auto& resultset_data = ext_.per_result(index); return metadata_collection_view(meta_.data() + resultset_data.meta_offset, resultset_data.meta_size); } std::uint64_t get_affected_rows(std::size_t index) const noexcept { return ext_.per_result(index).affected_rows; } std::uint64_t get_last_insert_id(std::size_t index) const noexcept { return ext_.per_result(index).last_insert_id; } unsigned get_warning_count(std::size_t index) const noexcept { return ext_.per_result(index).warnings; } string_view get_info(std::size_t index) const noexcept { const auto& resultset_data = ext_.per_result(index); return string_view(info_.data() + resultset_data.info_offset, resultset_data.info_size); } bool get_is_out_params(std::size_t index) const noexcept { return ext_.per_result(index).is_out_params; } private: // Virtual implementations BOOST_MYSQL_DECL void reset_impl() noexcept override final; BOOST_MYSQL_DECL error_code on_head_ok_packet_impl(const ok_view& pack, diagnostics& diag) override final; BOOST_MYSQL_DECL void on_num_meta_impl(std::size_t num_columns) override final; BOOST_MYSQL_DECL error_code on_meta_impl(const coldef_view& coldef, bool is_last, diagnostics& diag) override final; BOOST_MYSQL_DECL error_code on_row_impl(span msg, const output_ref&, std::vector& fields) override final; BOOST_MYSQL_DECL error_code on_row_ok_packet_impl(const ok_view& pack) override final; void on_row_batch_start_impl() override final {} void on_row_batch_finish_impl() override final {} // Data results_external_data ext_; std::vector meta_; std::vector info_; std::size_t resultset_index_{0}; // Helpers span current_pos_map() noexcept { return ext_.pos_map(resultset_index_ - 1); } span current_pos_map() const noexcept { return ext_.pos_map(resultset_index_ - 1); } name_table_t current_name_table() const noexcept { return ext_.name_table(resultset_index_ - 1); } static_per_resultset_data& current_resultset() noexcept { return ext_.per_result(resultset_index_ - 1); } metadata_collection_view current_resultset_meta() const noexcept { return get_meta(resultset_index_ - 1); } BOOST_MYSQL_DECL static_per_resultset_data& add_resultset(); BOOST_MYSQL_DECL error_code on_ok_packet_impl(const ok_view& pack); error_code meta_check(diagnostics& diag) const { return ext_.meta_check_fn(resultset_index_ - 1)(current_pos_map(), current_resultset_meta(), diag); } }; template using results_rows_t = std::tuple>...>; template struct results_fns { using rows_t = results_rows_t; struct reset_fn { rows_t& obj; template void operator()(boost::mp11::mp_size_t) const noexcept { std::get(obj).clear(); } }; static void reset(void* rows_ptr) noexcept { auto& rows = *static_cast(rows_ptr); boost::mp11::mp_for_each>(reset_fn{rows}); } template static error_code do_parse(span pos_map, span from, void* to) { using StaticRowT = mp11::mp_at_c, I>; auto& v = std::get(*static_cast(to)); v.emplace_back(); return parse(pos_map, from, v.back()); } template static constexpr results_resultset_descriptor create_descriptor() { using StaticRowT = mp11::mp_at_c, I>; return { get_row_size(), get_row_name_table(), &meta_check, &do_parse, }; } template static constexpr std::array create_descriptors(mp11::index_sequence< I...>) { return {{create_descriptor()...}}; } }; template BOOST_INLINE_CONSTEXPR std::array results_resultset_descriptor_table = results_fns::create_descriptors( mp11::make_index_sequence() ); template using rows_span_t = boost::span< const typename std::tuple_element...>>::type>; template class static_results_impl { // Data that requires knowing template params struct { results_rows_t rows; std::array> pos_map{}; std::array per_resultset{}; } data_; // The type-erased impl, that will use pointers to the above storage static_results_erased_impl impl_; results_external_data::ptr_data ptr_data() noexcept { return { &data_.rows, data_.pos_map.data(), data_.per_resultset.data(), }; } void set_pointers() noexcept { impl_.ext_data().set_pointers(ptr_data()); } public: static_results_impl() noexcept : impl_(results_external_data( results_resultset_descriptor_table, &results_fns::reset, ptr_data() )) { } static_results_impl(const static_results_impl& rhs) : data_(rhs.data_), impl_(rhs.impl_) { set_pointers(); } static_results_impl(static_results_impl&& rhs) noexcept : data_(std::move(rhs.data_)), impl_(std::move(rhs.impl_)) { set_pointers(); } static_results_impl& operator=(const static_results_impl& rhs) { data_ = rhs.data_; impl_ = rhs.impl_; set_pointers(); return *this; } static_results_impl& operator=(static_results_impl&& rhs) { data_ = std::move(rhs.data_); impl_ = std::move(rhs.impl_); set_pointers(); return *this; } // User facing template rows_span_t get_rows() const noexcept { return std::get(data_.rows); } const static_results_erased_impl& get_interface() const noexcept { return impl_; } static_results_erased_impl& get_interface() noexcept { return impl_; } }; } // namespace detail } // namespace mysql } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif // BOOST_MYSQL_CXX14 #endif ================================================ FILE: include/boost/mysql/detail/field_impl.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_FIELD_IMPL_HPP #define BOOST_MYSQL_DETAIL_FIELD_IMPL_HPP #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { namespace detail { // Breaks a circular dependency between field_view and field struct field_impl { using null_t = boost::variant2::monostate; using variant_type = boost::variant2::variant< null_t, // Any of the below when the value is NULL std::int64_t, // signed TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT std::uint64_t, // unsigned TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT, YEAR, BIT std::string, // CHAR, VARCHAR, TEXT (all sizes), , ENUM, // SET, DECIMAL blob, // BINARY, VARBINARY, BLOB (all sizes), GEOMETRY float, // FLOAT double, // DOUBLE date, // DATE datetime, // DATETIME, TIMESTAMP time // TIME >; variant_type data; field_impl() = default; template field_impl(Args&&... args) noexcept(std::is_nothrow_constructible::value) : data(std::forward(args)...) { } field_kind kind() const noexcept { return static_cast(data.index()); } template const T& as() const { const T* res = boost::variant2::get_if(&data); if (!res) BOOST_THROW_EXCEPTION(bad_field_access()); return *res; } template T& as() { T* res = boost::variant2::get_if(&data); if (!res) BOOST_THROW_EXCEPTION(bad_field_access()); return *res; } template const T& get() const noexcept { constexpr auto I = mp11::mp_find::value; return boost::variant2::unsafe_get(data); } template T& get() noexcept { constexpr auto I = mp11::mp_find::value; return boost::variant2::unsafe_get(data); } }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/flags.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_FLAGS_HPP #define BOOST_MYSQL_DETAIL_FLAGS_HPP #include #include namespace boost { namespace mysql { namespace detail { namespace column_flags { BOOST_INLINE_CONSTEXPR std::uint16_t not_null = 1; // Field can't be NULL. BOOST_INLINE_CONSTEXPR std::uint16_t pri_key = 2; // Field is part of a primary key. BOOST_INLINE_CONSTEXPR std::uint16_t unique_key = 4; // Field is part of a unique key. BOOST_INLINE_CONSTEXPR std::uint16_t multiple_key = 8; // Field is part of a key. BOOST_INLINE_CONSTEXPR std::uint16_t blob = 16; // Field is a blob. BOOST_INLINE_CONSTEXPR std::uint16_t unsigned_ = 32; // Field is unsigned. BOOST_INLINE_CONSTEXPR std::uint16_t zerofill = 64; // Field is zerofill. BOOST_INLINE_CONSTEXPR std::uint16_t binary = 128; // Field is binary. BOOST_INLINE_CONSTEXPR std::uint16_t enum_ = 256; // field is an enum BOOST_INLINE_CONSTEXPR std::uint16_t auto_increment = 512; // field is a autoincrement field BOOST_INLINE_CONSTEXPR std::uint16_t timestamp = 1024; // Field is a timestamp. BOOST_INLINE_CONSTEXPR std::uint16_t set = 2048; // field is a set BOOST_INLINE_CONSTEXPR std::uint16_t no_default_value = 4096; // Field doesn't have default value. BOOST_INLINE_CONSTEXPR std::uint16_t on_update_now = 8192; // Field is set to NOW on UPDATE. BOOST_INLINE_CONSTEXPR std::uint16_t part_key = 16384; // Intern; Part of some key. BOOST_INLINE_CONSTEXPR std::uint16_t num = 32768; // Field is num (for clients) } // namespace column_flags namespace status_flags { BOOST_INLINE_CONSTEXPR std::uint32_t more_results = 8; BOOST_INLINE_CONSTEXPR std::uint32_t no_backslash_escapes = 512; BOOST_INLINE_CONSTEXPR std::uint32_t out_params = 4096; } // namespace status_flags } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/format_sql.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_FORMAT_SQL_HPP #define BOOST_MYSQL_DETAIL_FORMAT_SQL_HPP #include #include #include #include #include #include #include namespace boost { namespace mysql { // Forward decls template struct formatter; class format_context_base; class formattable_ref; class format_arg; namespace detail { class format_state; struct formatter_is_unspecialized { }; template constexpr bool has_specialized_formatter() { return !std::is_base_of::type>>::value; } template struct is_writable_field_ref : is_writable_field::type> { }; template struct is_formattable_ref : std::is_same::type, formattable_ref> { }; // Is T suitable for being the element type of a formattable range? template constexpr bool is_formattable_range_elm_type() { return is_writable_field_ref::value || has_specialized_formatter() || is_formattable_ref::value; } template struct is_formattable_range : std::false_type { }; // Note: T might be a reference. // Using T& + reference collapsing gets the right semantics for non-const ranges template struct is_formattable_range< T, typename std::enable_if< // std::begin and std::end can be called on it, and we can compare values std::is_convertible()) != std::end(std::declval())), bool>:: value && // value_type is either a writable field or a type with a specialized formatter. // We don't support sequences of sequences out of the box (no known use case) is_formattable_range_elm_type()))>() // end of conditions >::type> : std::true_type { }; template constexpr bool is_formattable_type() { return is_formattable_range_elm_type() || is_formattable_range::value; } #ifdef BOOST_MYSQL_HAS_CONCEPTS // If you're getting an error referencing this concept, // it means that you are attempting to format a type that doesn't support it. template concept formattable = // This covers basic types and optionals is_writable_field_ref::value || // This covers custom types that specialized boost::mysql::formatter has_specialized_formatter() || // This covers ranges of formattable types is_formattable_range::value || // This covers passing formattable_ref as a format argument is_formattable_ref::value; #define BOOST_MYSQL_FORMATTABLE ::boost::mysql::detail::formattable #else #define BOOST_MYSQL_FORMATTABLE class #endif // A type-erased argument passed to format. Built-in types are passed // directly in the struct (as a field_view), instead of by pointer, // to reduce the number of do_format instantiations struct formattable_ref_impl { enum class type_t { field, field_with_specs, fn_and_ptr }; struct fn_and_ptr { const void* obj; bool (*format_fn)(const void*, const char*, const char*, format_context_base&); }; union data_t { field_view fv; fn_and_ptr custom; data_t(field_view fv) noexcept : fv(fv) {} data_t(fn_and_ptr v) noexcept : custom(v) {} }; type_t type; data_t data; }; // Create a type-erased formattable_ref_impl from a formattable value template formattable_ref_impl make_formattable_ref(T&& v); BOOST_MYSQL_DECL void vformat_sql_to(format_context_base& ctx, constant_string_view format_str, span args); } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/initiation_base.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_INITIATION_BASE_HPP #define BOOST_MYSQL_DETAIL_INITIATION_BASE_HPP #include #include #include #include #include namespace boost { namespace mysql { namespace detail { struct executor_with_default : asio::any_io_executor { using default_completion_token_type = with_diagnostics_t; template < typename InnerExecutor1, class = typename std::enable_if::value>::type> executor_with_default(const InnerExecutor1& ex) noexcept : asio::any_io_executor(ex) { } }; // Base class for initiation objects. Includes a bound executor, so they're compatible // with asio::cancel_after and similar. The bound executor has our default completion token. // Use only in the ops that should use this token. struct initiation_base { executor_with_default ex; initiation_base(asio::any_io_executor ex) noexcept : ex(std::move(ex)) {} using executor_type = executor_with_default; const executor_type& get_executor() const noexcept { return ex; } }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/intermediate_handler.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_INTERMEDIATE_HANDLER_HPP #define BOOST_MYSQL_DETAIL_INTERMEDIATE_HANDLER_HPP #include #include namespace boost { namespace mysql { namespace detail { // An intermediate handler that propagates associated characteristics. // HandlerFn must be a function void(Handler&&, args...) that calls the handler template struct intermediate_handler { HandlerFn fn; FinalHandler handler; template void operator()(Args&&... args) { fn(std::move(handler), std::forward(args)...); } }; template intermediate_handler::type, typename std::decay::type> make_intermediate_handler( HandlerFn&& fn, FinalHandler&& handler ) { return {std::forward(fn), std::forward(handler)}; } } // namespace detail } // namespace mysql namespace asio { template < template class Associator, class HandlerFn, class FinalHandler, typename DefaultCandidate> struct associator, DefaultCandidate> : Associator { static typename Associator::type get( const mysql::detail::intermediate_handler& h ) noexcept { return Associator::get(h.handler); } static auto get( const mysql::detail::intermediate_handler& h, const DefaultCandidate& c ) noexcept -> decltype(Associator::get(h.handler, c)) { return Associator::get(h.handler, c); } }; } // namespace asio } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/next_action.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_NEXT_ACTION_HPP #define BOOST_MYSQL_DETAIL_NEXT_ACTION_HPP #include #include #include #include namespace boost { namespace mysql { namespace detail { enum class next_action_type { none, write, read, ssl_handshake, ssl_shutdown, connect, close, }; class next_action { public: struct read_args_t { span buffer; bool use_ssl; }; struct write_args_t { span buffer; bool use_ssl; }; next_action(error_code ec = {}) noexcept : type_(next_action_type::none), data_(ec) {} // Type next_action_type type() const noexcept { return type_; } bool is_done() const noexcept { return type_ == next_action_type::none; } bool success() const noexcept { return is_done() && !data_.ec; } // Arguments error_code error() const noexcept { BOOST_ASSERT(is_done()); return data_.ec; } const void* connect_endpoint() const noexcept { return data_.connect_endpoint; } read_args_t read_args() const noexcept { BOOST_ASSERT(type_ == next_action_type::read); return data_.read_args; } write_args_t write_args() const noexcept { BOOST_ASSERT(type_ == next_action_type::write); return data_.write_args; } static next_action connect(const void* endpoint) noexcept { return next_action(next_action_type::connect, endpoint); } static next_action read(read_args_t args) noexcept { return next_action(next_action_type::read, args); } static next_action write(write_args_t args) noexcept { return next_action(next_action_type::write, args); } static next_action ssl_handshake() noexcept { return next_action(next_action_type::ssl_handshake, data_t()); } static next_action ssl_shutdown() noexcept { return next_action(next_action_type::ssl_shutdown, data_t()); } static next_action close() noexcept { return next_action(next_action_type::close, data_t()); } private: next_action_type type_{next_action_type::none}; union data_t { error_code ec; const void* connect_endpoint; read_args_t read_args; write_args_t write_args; data_t() noexcept : ec(error_code()) {} data_t(const void* endpoint) noexcept : connect_endpoint(endpoint) {} data_t(error_code ec) noexcept : ec(ec) {} data_t(read_args_t args) noexcept : read_args(args) {} data_t(write_args_t args) noexcept : write_args(args) {} } data_; next_action(next_action_type t, data_t data) noexcept : type_(t), data_(data) {} }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/ok_view.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_OK_VIEW_HPP #define BOOST_MYSQL_DETAIL_OK_VIEW_HPP #include #include #include namespace boost { namespace mysql { namespace detail { struct ok_view { std::uint64_t affected_rows; std::uint64_t last_insert_id; std::uint16_t status_flags; std::uint16_t warnings; string_view info; bool more_results() const noexcept { return status_flags & status_flags::more_results; } bool backslash_escapes() const noexcept { return !(status_flags & status_flags::no_backslash_escapes); } bool is_out_params() const noexcept { return status_flags & status_flags::out_params; } }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/output_string.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_OUTPUT_STRING_HPP #define BOOST_MYSQL_DETAIL_OUTPUT_STRING_HPP #include #include #include #ifdef BOOST_MYSQL_HAS_CONCEPTS #include #endif namespace boost { namespace mysql { namespace detail { #ifdef BOOST_MYSQL_HAS_CONCEPTS template concept output_string = std::movable && requires(T& t, const char* data, std::size_t sz) { t.append(data, sz); t.clear(); }; #define BOOST_MYSQL_OUTPUT_STRING ::boost::mysql::detail::output_string #else #define BOOST_MYSQL_OUTPUT_STRING class #endif class output_string_ref { using append_fn_t = void (*)(void*, const char*, std::size_t); append_fn_t append_fn_; void* container_; template static void do_append(void* container, const char* data, std::size_t size) { static_cast(container)->append(data, size); } public: output_string_ref(append_fn_t append_fn, void* container) noexcept : append_fn_(append_fn), container_(container) { } template static output_string_ref create(T& obj) noexcept { return output_string_ref(&do_append, &obj); } void append(string_view data) { if (data.size() > 0u) append_fn_(container_, data.data(), data.size()); } }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/pipeline.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_PIPELINE_HPP #define BOOST_MYSQL_DETAIL_PIPELINE_HPP #include #include #include #include namespace boost { namespace mysql { namespace detail { class execution_processor; enum class pipeline_stage_kind { execute, prepare_statement, close_statement, reset_connection, set_character_set, ping, }; struct pipeline_request_stage { pipeline_stage_kind kind; std::uint8_t seqnum; union stage_specific_t { std::nullptr_t nothing; resultset_encoding enc; character_set charset; stage_specific_t() noexcept : nothing() {} stage_specific_t(resultset_encoding v) noexcept : enc(v) {} stage_specific_t(character_set v) noexcept : charset(v) {} } stage_specific; }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/rebind_executor.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_REBIND_EXECUTOR_HPP #define BOOST_MYSQL_DETAIL_REBIND_EXECUTOR_HPP #include namespace boost { namespace mysql { namespace detail { // This is required because ssl::stream doesn't have a rebind_executor member type template struct rebind_executor { using type = typename Stream::template rebind_executor::other; }; template struct rebind_executor, Executor> { using type = boost::asio::ssl::stream::type>; }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/results_iterator.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_RESULTS_ITERATOR_HPP #define BOOST_MYSQL_DETAIL_RESULTS_ITERATOR_HPP #include #include #include #include namespace boost { namespace mysql { namespace detail { class results_iterator { const results_impl* self_{}; std::size_t index_{}; public: using value_type = resultset; using reference = resultset_view; using pointer = resultset_view; using difference_type = std::ptrdiff_t; using iterator_category = std::random_access_iterator_tag; results_iterator() = default; results_iterator(const results_impl* self, std::size_t index) noexcept : self_(self), index_(index) {} results_iterator& operator++() noexcept { ++index_; return *this; } results_iterator operator++(int) noexcept { auto res = *this; ++(*this); return res; } results_iterator& operator--() noexcept { --index_; return *this; } results_iterator operator--(int) noexcept { auto res = *this; --(*this); return res; } results_iterator& operator+=(std::ptrdiff_t n) noexcept { index_ += n; return *this; } results_iterator& operator-=(std::ptrdiff_t n) noexcept { index_ -= n; return *this; } results_iterator operator+(std::ptrdiff_t n) const noexcept { return results_iterator(self_, index_ + n); } results_iterator operator-(std::ptrdiff_t n) const noexcept { return *this + (-n); } std::ptrdiff_t operator-(results_iterator rhs) const noexcept { return index_ - rhs.index_; } pointer operator->() const noexcept { return **this; } reference operator*() const noexcept { return (*this)[0]; } reference operator[](std::ptrdiff_t i) const noexcept { return access::construct(*self_, index_ + i); } bool operator==(results_iterator rhs) const noexcept { return index_ == rhs.index_; } bool operator!=(results_iterator rhs) const noexcept { return !(*this == rhs); } bool operator<(results_iterator rhs) const noexcept { return index_ < rhs.index_; } bool operator<=(results_iterator rhs) const noexcept { return index_ <= rhs.index_; } bool operator>(results_iterator rhs) const noexcept { return index_ > rhs.index_; } bool operator>=(results_iterator rhs) const noexcept { return index_ >= rhs.index_; } std::size_t index() const noexcept { return index_; } const results_impl* obj() const noexcept { return self_; } }; inline results_iterator operator+(std::ptrdiff_t n, results_iterator it) noexcept { return it + n; } } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/resultset_encoding.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_RESULTSET_ENCODING_HPP #define BOOST_MYSQL_DETAIL_RESULTSET_ENCODING_HPP namespace boost { namespace mysql { namespace detail { enum class resultset_encoding { text, binary }; } // namespace detail } // namespace mysql } // namespace boost #endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_COMMON_HPP_ */ ================================================ FILE: include/boost/mysql/detail/row_impl.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_ROW_IMPL_HPP #define BOOST_MYSQL_DETAIL_ROW_IMPL_HPP #include #include #include #include #include namespace boost { namespace mysql { namespace detail { // Adds num_fields default-constructed fields to the vector, return pointer to the first // allocated value. Used to allocate fields before deserialization inline span add_fields(std::vector& storage, std::size_t num_fields) { std::size_t old_size = storage.size(); storage.resize(old_size + num_fields); return span(storage.data() + old_size, num_fields); } // A field_view vector with strings pointing into a // single character buffer. Used to implement owning row types class row_impl { public: row_impl() = default; BOOST_MYSQL_DECL row_impl(const row_impl&); row_impl(row_impl&&) = default; BOOST_MYSQL_DECL row_impl& operator=(const row_impl&); row_impl& operator=(row_impl&&) = default; ~row_impl() = default; // Copies the given span into *this BOOST_MYSQL_DECL row_impl(const field_view* fields, std::size_t size); // Copies the given span into *this, used by row/rows in assignment from view BOOST_MYSQL_DECL void assign(const field_view* fields, std::size_t size); // Adds new default constructed fields to provide storage to deserialization span add_fields(std::size_t num_fields) { return ::boost::mysql::detail::add_fields(fields_, num_fields); } // Saves strings in the [first, first+num_fields) range into the string buffer, used by execute BOOST_MYSQL_DECL void copy_strings_as_offsets(std::size_t first, std::size_t num_fields); // Restores any offsets into string views, used by execute BOOST_MYSQL_DECL void offsets_to_string_views(); const std::vector& fields() const noexcept { return fields_; } void clear() noexcept { fields_.clear(); string_buffer_.clear(); } private: std::vector fields_; std::vector string_buffer_; }; } // namespace detail } // namespace mysql } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/detail/rows_iterator.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_ROWS_ITERATOR_HPP #define BOOST_MYSQL_DETAIL_ROWS_ITERATOR_HPP #include #include #include #include #include #include #include namespace boost { namespace mysql { namespace detail { inline row_view row_slice(const field_view* fields, std::size_t num_columns, std::size_t offset) noexcept { return access::construct(fields + num_columns * offset, num_columns); } class rows_iterator { const field_view* fields_{nullptr}; std::size_t num_columns_{0}; std::size_t row_num_{0}; public: using value_type = row; using reference = row_view; using pointer = row_view; using difference_type = std::ptrdiff_t; using iterator_category = std::random_access_iterator_tag; rows_iterator() = default; rows_iterator(const field_view* fields, std::size_t num_columns, std::size_t rownum) noexcept : fields_(fields), num_columns_(num_columns), row_num_(rownum) { } rows_iterator& operator++() noexcept { ++row_num_; return *this; } rows_iterator operator++(int) noexcept { auto res = *this; ++(*this); return res; } rows_iterator& operator--() noexcept { --row_num_; return *this; } rows_iterator operator--(int) noexcept { auto res = *this; --(*this); return res; } rows_iterator& operator+=(std::ptrdiff_t n) noexcept { row_num_ += n; return *this; } rows_iterator& operator-=(std::ptrdiff_t n) noexcept { row_num_ -= n; return *this; } rows_iterator operator+(std::ptrdiff_t n) const noexcept { return rows_iterator(fields_, num_columns_, row_num_ + n); } rows_iterator operator-(std::ptrdiff_t n) const noexcept { return rows_iterator(fields_, num_columns_, row_num_ - n); } std::ptrdiff_t operator-(rows_iterator rhs) const noexcept { return row_num_ - rhs.row_num_; } pointer operator->() const noexcept { return **this; } reference operator*() const noexcept { return (*this)[0]; } reference operator[](std::ptrdiff_t i) const noexcept { return row_slice(fields_, num_columns_, row_num_ + i); } bool operator==(rows_iterator rhs) const noexcept { return row_num_ == rhs.row_num_; } bool operator!=(rows_iterator rhs) const noexcept { return !(*this == rhs); } bool operator<(rows_iterator rhs) const noexcept { return row_num_ < rhs.row_num_; } bool operator<=(rows_iterator rhs) const noexcept { return row_num_ <= rhs.row_num_; } bool operator>(rows_iterator rhs) const noexcept { return row_num_ > rhs.row_num_; } bool operator>=(rows_iterator rhs) const noexcept { return row_num_ >= rhs.row_num_; } }; inline rows_iterator operator+(std::ptrdiff_t n, rows_iterator it) noexcept { return it + n; } } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/sequence.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_SEQUENCE_HPP #define BOOST_MYSQL_DETAIL_SEQUENCE_HPP #include #include #include #include #include #include #include #include #ifdef BOOST_MYSQL_HAS_CONCEPTS #include #endif namespace boost { namespace mysql { namespace detail { template struct sequence_range_impl { using type = T; }; template struct sequence_range_impl> { using type = T&; }; template struct sequence_range_impl { using type = std::array; }; template using sequence_range_type = sequence_range_impl>; template Range&& cast_range(Range&& range) { return std::forward(range); } template std::array, N> cast_range(T (&a)[N]) { return compat::to_array(a); } template std::array, N> cast_range(T (&&a)[N]) { return compat::to_array(std::move(a)); } // TODO: should this be Range&&? template void do_format_sequence(Range& range, const FormatFn& fn, constant_string_view glue, format_context_base& ctx) { bool is_first = true; for (auto it = std::begin(range); it != std::end(range); ++it) { if (!is_first) ctx.append_raw(glue); is_first = false; fn(*it, ctx); } } #ifdef BOOST_MYSQL_HAS_CONCEPTS template concept format_fn_for_range = requires(const FormatFn& format_fn, Range&& range, format_context_base& ctx) { { std::begin(range) != std::end(range) } -> std::convertible_to; format_fn(*std::begin(range), ctx); std::end(range); }; #endif } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/socket_stream.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_SOCKET_STREAM_HPP #define BOOST_MYSQL_DETAIL_SOCKET_STREAM_HPP #include #include #include namespace boost { namespace mysql { namespace detail { template struct is_socket : std::false_type { }; // typename basic_stream_socket::lowest_layer_type is basic_socket, so we accept basic_socket and // basic_stream_socket here template struct is_socket> : std::true_type { }; template struct is_socket> : std::true_type { }; template struct is_socket_stream : std::false_type { }; template struct is_socket_stream::value>::type> : std::true_type { }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/ssl_fwd.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_SSL_FWD_HPP #define BOOST_MYSQL_DETAIL_SSL_FWD_HPP // SSL headers are heavyweight namespace boost { namespace asio { namespace ssl { class context; } // namespace ssl } // namespace asio } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/string_view_offset.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_STRING_VIEW_OFFSET_HPP #define BOOST_MYSQL_DETAIL_STRING_VIEW_OFFSET_HPP #include namespace boost { namespace mysql { namespace detail { // Represents a string_view using offsets into a buffer. // Useful during deserialization, for buffers that may reallocate. struct string_view_offset { std::size_t offset; std::size_t size; constexpr bool operator==(string_view_offset rhs) const noexcept { return offset == rhs.offset && size == rhs.size; } constexpr bool operator!=(string_view_offset rhs) const noexcept { return !(*this == rhs); } }; } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/throw_on_error_loc.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_THROW_ON_ERROR_LOC_HPP #define BOOST_MYSQL_DETAIL_THROW_ON_ERROR_LOC_HPP #include #include #include #include namespace boost { namespace mysql { namespace detail { inline void throw_on_error_loc(error_code err, const diagnostics& diag, const source_location& loc) { if (err) { ::boost::throw_exception(error_with_diagnostics(err, diag), loc); } } } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/typing/meta_check_context.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_TYPING_META_CHECK_CONTEXT_HPP #define BOOST_MYSQL_DETAIL_TYPING_META_CHECK_CONTEXT_HPP #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { namespace detail { inline const char* column_type_to_str(const metadata& meta) noexcept { switch (meta.type()) { case column_type::tinyint: return meta.is_unsigned() ? "TINYINT UNSIGNED" : "TINYINT"; case column_type::smallint: return meta.is_unsigned() ? "SMALLINT UNSIGNED" : "SMALLINT"; case column_type::mediumint: return meta.is_unsigned() ? "MEDIUMINT UNSIGNED" : "MEDIUMINT"; case column_type::int_: return meta.is_unsigned() ? "INT UNSIGNED" : "INT"; case column_type::bigint: return meta.is_unsigned() ? "BIGINT UNSIGNED" : "BIGINT"; case column_type::float_: return "FLOAT"; case column_type::double_: return "DOUBLE"; case column_type::decimal: return "DECIMAL"; case column_type::bit: return "BIT"; case column_type::year: return "YEAR"; case column_type::time: return "TIME"; case column_type::date: return "DATE"; case column_type::datetime: return "DATETIME"; case column_type::timestamp: return "TIMESTAMP"; case column_type::char_: return "CHAR"; case column_type::varchar: return "VARCHAR"; case column_type::binary: return "BINARY"; case column_type::varbinary: return "VARBINARY"; case column_type::text: return "TEXT"; case column_type::blob: return "BLOB"; case column_type::enum_: return "ENUM"; case column_type::set: return "SET"; case column_type::json: return "JSON"; case column_type::geometry: return "GEOMETRY"; default: return ""; } } class meta_check_context { std::unique_ptr errors_; std::size_t current_index_{}; span pos_map_; name_table_t name_table_; metadata_collection_view meta_{}; bool nullability_checked_{}; std::ostringstream& add_error() { if (!errors_) errors_.reset(new std::ostringstream); else *errors_ << '\n'; return *errors_; } void insert_field_name(std::ostringstream& os) { if (has_field_names(name_table_)) os << "'" << name_table_[current_index_] << "'"; else os << "in position " << current_index_; } public: meta_check_context( span pos_map, name_table_t name_table, metadata_collection_view meta ) noexcept : pos_map_(pos_map), name_table_(name_table), meta_(meta) { } // Accessors const metadata& current_meta() const noexcept { return map_metadata(pos_map_, current_index_, meta_); } bool is_current_field_absent() const noexcept { return pos_map_[current_index_] == pos_absent; } // Iteration void advance() noexcept { nullability_checked_ = false; ++current_index_; } // Nullability void set_nullability_checked() noexcept { nullability_checked_ = true; } bool nullability_checked() const noexcept { return nullability_checked_; } // Error reporting BOOST_MYSQL_DECL void add_field_absent_error(); BOOST_MYSQL_DECL void add_type_mismatch_error(const char* cpp_type_name); BOOST_MYSQL_DECL void add_nullability_error(); BOOST_MYSQL_DECL error_code check_errors(diagnostics& diag) const; }; } // namespace detail } // namespace mysql } // namespace boost #ifdef BOOST_MYSQL_HEADER_ONLY #include #endif #endif ================================================ FILE: include/boost/mysql/detail/typing/pos_map.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_TYPING_POS_MAP_HPP #define BOOST_MYSQL_DETAIL_TYPING_POS_MAP_HPP #include #include #include #include #include #include #include #include namespace boost { namespace mysql { namespace detail { // These functions map C++ type positions to positions to positions in the DB query BOOST_INLINE_CONSTEXPR std::size_t pos_absent = static_cast(-1); using name_table_t = boost::span; inline bool has_field_names(name_table_t name_table) noexcept { return !name_table.empty(); } inline void pos_map_reset(span self) noexcept { for (std::size_t i = 0; i < self.size(); ++i) self.data()[i] = pos_absent; } inline void pos_map_add_field( span self, name_table_t name_table, std::size_t db_index, string_view field_name ) noexcept { if (has_field_names(name_table)) { BOOST_ASSERT(self.size() == name_table.size()); // We're mapping fields by name. Try to find where in our target struct // is the current field located auto it = std::find(name_table.begin(), name_table.end(), field_name); if (it != name_table.end()) { std::size_t cpp_index = it - name_table.begin(); self[cpp_index] = db_index; } } else { // We're mapping by position. Any extra trailing fields are discarded if (db_index < self.size()) { self[db_index] = db_index; } } } inline field_view map_field_view( span self, std::size_t cpp_index, span array ) noexcept { BOOST_ASSERT(cpp_index < self.size()); return array[self[cpp_index]]; } inline const metadata& map_metadata( span self, std::size_t cpp_index, metadata_collection_view meta ) noexcept { BOOST_ASSERT(cpp_index < self.size()); return meta[self[cpp_index]]; } } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/typing/readable_field_traits.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_TYPING_READABLE_FIELD_TRAITS_HPP #define BOOST_MYSQL_DETAIL_TYPING_READABLE_FIELD_TRAITS_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { namespace detail { // Helpers for integers template error_code parse_signed_int(field_view input, SignedInt& output) { using unsigned_t = typename std::make_unsigned::type; using limits_t = std::numeric_limits; auto kind = input.kind(); if (kind == field_kind::int64) { auto v = input.get_int64(); if (v < (limits_t::min)() || v > (limits_t::max)()) { return client_errc::static_row_parsing_error; } output = static_cast(v); return error_code(); } else if (kind == field_kind::uint64) { auto v = input.get_uint64(); if (v > static_cast((limits_t::max)())) { return client_errc::static_row_parsing_error; } output = static_cast(v); return error_code(); } else { return client_errc::static_row_parsing_error; } } template error_code parse_unsigned_int(field_view input, UnsignedInt& output) { if (input.kind() != field_kind::uint64) { return client_errc::static_row_parsing_error; } auto v = input.get_uint64(); if (v > (std::numeric_limits::max)()) { return client_errc::static_row_parsing_error; } output = static_cast(v); return error_code(); } // We want all integer types to be allowed as fields. Some integers // may have the same width as others, but different type (e.g. long and long long // may both be 64-bit, but different types). Auxiliar int_traits to allow this to work template ::value, std::size_t width = sizeof(T)> struct int_traits { static constexpr bool is_supported = false; }; template struct int_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "int8_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: return !ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_signed_int(input, output); } }; template struct int_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "uint8_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: return ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_unsigned_int(input, output); } }; template struct int_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "int16_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: return true; case column_type::smallint: case column_type::year: return !ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_signed_int(input, output); } }; template struct int_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "uint16_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: case column_type::smallint: case column_type::year: return ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_unsigned_int(input, output); } }; template struct int_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "int32_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: case column_type::smallint: case column_type::year: case column_type::mediumint: return true; case column_type::int_: return !ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_signed_int(input, output); } }; template struct int_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "uint32_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: case column_type::smallint: case column_type::year: case column_type::mediumint: case column_type::int_: return ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_unsigned_int(input, output); } }; template struct int_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "int64_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: case column_type::smallint: case column_type::year: case column_type::mediumint: case column_type::int_: return true; case column_type::bigint: return !ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_signed_int(input, output); } }; template struct int_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "uint64_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: case column_type::smallint: case column_type::year: case column_type::mediumint: case column_type::int_: case column_type::bigint: return ctx.current_meta().is_unsigned(); case column_type::bit: return true; default: return false; } } static error_code parse(field_view input, std::uint64_t& output) { return parse_unsigned_int(input, output); } }; // Traits template struct readable_field_traits { static constexpr bool is_supported = false; }; template <> struct readable_field_traits : int_traits { }; template <> struct readable_field_traits : int_traits { }; template <> struct readable_field_traits : int_traits { }; template <> struct readable_field_traits : int_traits { }; template <> struct readable_field_traits : int_traits { }; template <> struct readable_field_traits : int_traits { }; template <> struct readable_field_traits : int_traits { }; template <> struct readable_field_traits : int_traits { }; template <> struct readable_field_traits : int_traits { }; template <> struct readable_field_traits : int_traits { }; template <> struct readable_field_traits : int_traits { }; template <> struct readable_field_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "bool"; static bool meta_check(meta_check_context& ctx) { return ctx.current_meta().type() == column_type::tinyint && !ctx.current_meta().is_unsigned(); } static error_code parse(field_view input, bool& output) { if (input.kind() != field_kind::int64) { return client_errc::static_row_parsing_error; } output = input.get_int64() != 0; return error_code(); } }; template <> struct readable_field_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "float"; static bool meta_check(meta_check_context& ctx) { return ctx.current_meta().type() == column_type::float_; } static error_code parse(field_view input, float& output) { if (input.kind() != field_kind::float_) { return client_errc::static_row_parsing_error; } output = input.get_float(); return error_code(); } }; template <> struct readable_field_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "double"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::float_: case column_type::double_: return true; default: return false; } } static error_code parse(field_view input, double& output) { auto kind = input.kind(); if (kind == field_kind::float_) { output = input.get_float(); return error_code(); } else if (kind == field_kind::double_) { output = input.get_double(); return error_code(); } else { return client_errc::static_row_parsing_error; } } }; template struct readable_field_traits, Allocator>, void> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "string"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::decimal: case column_type::char_: case column_type::varchar: case column_type::text: case column_type::enum_: case column_type::set: case column_type::json: return true; default: return false; } } static error_code parse( field_view input, std::basic_string, Allocator>& output ) { if (input.kind() != field_kind::string) { return client_errc::static_row_parsing_error; } output = input.get_string(); return error_code(); } }; template struct readable_field_traits, void> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "blob"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::binary: case column_type::varbinary: case column_type::blob: case column_type::geometry: case column_type::unknown: return true; default: return false; } } static error_code parse(field_view input, std::vector& output) { if (input.kind() != field_kind::blob) { return client_errc::static_row_parsing_error; } auto view = input.get_blob(); output.assign(view.begin(), view.end()); return error_code(); } }; template <> struct readable_field_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "date"; static bool meta_check(meta_check_context& ctx) { return ctx.current_meta().type() == column_type::date; } static error_code parse(field_view input, date& output) { if (input.kind() != field_kind::date) { return client_errc::static_row_parsing_error; } output = input.get_date(); return error_code(); } }; template <> struct readable_field_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "datetime"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::datetime: case column_type::timestamp: return true; default: return false; } } static error_code parse(field_view input, datetime& output) { if (input.kind() != field_kind::datetime) { return client_errc::static_row_parsing_error; } output = input.get_datetime(); return error_code(); } }; template <> struct readable_field_traits { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "time"; static bool meta_check(meta_check_context& ctx) { return ctx.current_meta().type() == column_type::time; } static error_code parse(field_view input, time& output) { if (input.kind() != field_kind::time) { return client_errc::static_row_parsing_error; } output = input.get_time(); return error_code(); } }; // std::optional and boost::optional. To avoid dependencies, // this is achieved through a "concept" template struct is_readable_optional : std::false_type { }; template struct is_readable_optional< T, void_t< typename std::enable_if< std::is_same().value()), typename T::value_type&>::value>::type, decltype(std::declval().emplace()), // T should be default constructible decltype(std::declval().reset())>> : std::true_type { }; template struct readable_field_traits< T, typename std::enable_if< is_readable_optional::value && readable_field_traits::is_supported>::type> { using value_type = typename T::value_type; static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = readable_field_traits::type_name; static bool meta_check(meta_check_context& ctx) { ctx.set_nullability_checked(); return readable_field_traits::meta_check(ctx); } static error_code parse(field_view input, T& output) { if (input.is_null()) { output.reset(); return error_code(); } else { output.emplace(); return readable_field_traits::parse(input, output.value()); } } }; template struct is_readable_field { static constexpr bool value = readable_field_traits::is_supported; }; template void meta_check_field_impl(meta_check_context& ctx) { using traits_t = readable_field_traits; // Verify that the field is present if (ctx.is_current_field_absent()) { ctx.add_field_absent_error(); return; } // Perform the check bool ok = traits_t::meta_check(ctx); if (!ok) { ctx.add_type_mismatch_error(traits_t::type_name); } // Check nullability if (!ctx.nullability_checked() && !ctx.current_meta().is_not_null()) { ctx.add_nullability_error(); } } template void meta_check_field(meta_check_context& ctx) { static_assert(is_readable_field::value, "Should be a ReadableField"); meta_check_field_impl(ctx); ctx.advance(); } } // namespace detail } // namespace mysql } // namespace boost #endif ================================================ FILE: include/boost/mysql/detail/typing/row_traits.hpp ================================================ // // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_MYSQL_DETAIL_TYPING_ROW_TRAITS_HPP #define BOOST_MYSQL_DETAIL_TYPING_ROW_TRAITS_HPP #include #ifdef BOOST_MYSQL_CXX14 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace mysql { namespace detail { // // Base templates. Every StaticRow type must specialize the row_traits class, // providing the following members: // // // type of the actual row to be parsed. This supports marker types, like pfr_by_name // using underlying_row_type = /* */; // // // MP11 type list with the row's member types // using field_types = /* */; // // static constexpr name_table_t name_table() noexcept; // field names // // template /* Apply F to each member */ // static void for_each_member(underlying_row_t& to, F&& function); // // struct row_traits_is_unspecialized { }; template ::value> class row_traits : public row_traits_is_unspecialized { }; // // Describe structs // // Workaround std::array::data not being constexpr in C++14 template struct array_wrapper { T data_[N]; constexpr boost::span span() const noexcept { return boost::span(data_); } }; template struct array_wrapper { struct { } data_; // allow empty brace initialization constexpr boost::span span() const noexcept { return boost::span(); } }; // Workaround for char_traits::length not being constexpr in C++14 // Only used to retrieve Describe member name lengths constexpr std::size_t get_length(const char* s) noexcept { const char* p = s; while (*p) ++p; return p - s; } template using row_members = describe:: describe_members; template