From fdb3e0f4dd5022cdc11807df1f7607534927a8bb Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Sun, 23 Jul 2023 23:54:07 +0200 Subject: [PATCH] Introduce `std::source_location`. (#702) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce `std::source_location`. Allow exceptions to track source locations. This is still pretty rudimentary. One thing I'm not happy about is the way different exception classes have their own `source_location` members that are not related to each other. This means that if you want to access that field, you have to catch a fairly specific exception type. Also of course, it's not great that we can't assume that the compiler actually supports this feature. The testing helpers could be much nicer if they could rely on `source_location` — we might even get by without some of the preprocessor macros. --- NEWS | 5 +- config/Makefile.in | 2 +- include/pqxx/except.hxx | 353 +++++++++++++++++++++++-- include/pqxx/internal/cxx-features.hxx | 6 + include/pqxx/internal/header-pre.hxx | 6 + include/pqxx/result.hxx | 1 - include/pqxx/util.hxx | 5 - src/blob.cxx | 5 +- src/except.cxx | 202 +++++++++++--- src/transaction.cxx | 7 +- test/runner.cxx | 78 +++++- test/test_helpers.hxx | 192 ++++++++++++-- test/unit/test_test_helpers.cxx | 22 +- 13 files changed, 780 insertions(+), 104 deletions(-) diff --git a/NEWS b/NEWS index 198c053b8..8b77b46c8 100644 --- a/NEWS +++ b/NEWS @@ -31,9 +31,8 @@ - Support for `PQinitOpenSSL()`. (#678) - Slightly more helpful error for unsupported conversions. (#695) - Replace some C++ feature tests with C++20 feature macros. - - Give `stream_to` a move constructor. (#706) - - Support move in `stream_to`. (#706) ->>>>>>> 42a46214 (Support move assignment as well.) + - Support moving of `stream_to`. (#706) + - Incorporate `source_location` in exceptions. 7.7.4 - `transaction_base::for_each()` is now called `for_stream()`. (#580) - New `transaction_base::for_query()` is similar, but non-streaming. (#580) diff --git a/config/Makefile.in b/config/Makefile.in index ef0904008..bb5865a19 100644 --- a/config/Makefile.in +++ b/config/Makefile.in @@ -123,7 +123,7 @@ am__can_run_installinfo = \ esac am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) am__DIST_COMMON = $(srcdir)/Makefile.in compile config.guess \ - config.sub install-sh ltmain.sh missing mkinstalldirs + config.sub depcomp install-sh ltmain.sh missing mkinstalldirs DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ diff --git a/include/pqxx/except.hxx b/include/pqxx/except.hxx index 267295b77..55947e57d 100644 --- a/include/pqxx/except.hxx +++ b/include/pqxx/except.hxx @@ -17,6 +17,10 @@ # error "Include libpqxx headers as , not ." #endif +#if pqxx_have_source_location +#include +#endif + #include #include @@ -42,7 +46,15 @@ namespace pqxx /// Run-time failure encountered by libpqxx, similar to std::runtime_error. struct PQXX_LIBEXPORT failure : std::runtime_error { +#if pqxx_have_source_location + explicit failure( + std::string const &, + std::source_location = std::source_location::current() + ); + std::source_location location; +#else explicit failure(std::string const &); +#endif }; @@ -57,11 +69,11 @@ struct PQXX_LIBEXPORT failure : std::runtime_error * signal harmless is to make your program ignore it: * * ```cxx - * #include + * #include * * int main() * { - * signal(SIGPIPE, SIG_IGN); + * std::signal(SIGPIPE, SIG_IGN); * // ... * } * ``` @@ -69,11 +81,15 @@ struct PQXX_LIBEXPORT failure : std::runtime_error struct PQXX_LIBEXPORT broken_connection : failure { broken_connection(); - explicit broken_connection(std::string const &); + explicit broken_connection(std::string const & +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); }; -/// Exception class for micommunication with th server. +/// Exception class for micommunication with the server. /** This happens when the conversation between libpq and the server gets messed * up. There aren't many situations where this happens, but one known instance * is when you call a parameterised or prepared statement with th ewrong number @@ -84,15 +100,23 @@ struct PQXX_LIBEXPORT broken_connection : failure */ struct PQXX_LIBEXPORT protocol_violation : broken_connection { - explicit protocol_violation(std::string const &); + explicit protocol_violation(std::string const & +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); }; /// The caller attempted to set a variable to null, which is not allowed. struct PQXX_LIBEXPORT variable_set_to_null : failure { - variable_set_to_null(); - explicit variable_set_to_null(std::string const &); + explicit variable_set_to_null( + std::string const & +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); }; @@ -110,7 +134,11 @@ class PQXX_LIBEXPORT sql_error : public failure public: explicit sql_error( std::string const &whatarg = "", std::string const &Q = "", - char const sqlstate[] = nullptr); + char const sqlstate[] = nullptr +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); virtual ~sql_error() noexcept override; /// The query whose execution triggered the exception @@ -130,7 +158,11 @@ public: */ struct PQXX_LIBEXPORT in_doubt_error : failure { - explicit in_doubt_error(std::string const &); + explicit in_doubt_error(std::string const & +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); }; @@ -139,7 +171,11 @@ struct PQXX_LIBEXPORT transaction_rollback : sql_error { explicit transaction_rollback( std::string const &whatarg, std::string const &q = "", - char const sqlstate[] = nullptr); + char const sqlstate[] = nullptr +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); }; @@ -156,7 +192,11 @@ struct PQXX_LIBEXPORT serialization_failure : transaction_rollback { explicit serialization_failure( std::string const &whatarg, std::string const &q, - char const sqlstate[] = nullptr); + char const sqlstate[] = nullptr +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); }; @@ -165,7 +205,11 @@ struct PQXX_LIBEXPORT statement_completion_unknown : transaction_rollback { explicit statement_completion_unknown( std::string const &whatarg, std::string const &q, - char const sqlstate[] = nullptr); + char const sqlstate[] = nullptr +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); }; @@ -174,7 +218,11 @@ struct PQXX_LIBEXPORT deadlock_detected : transaction_rollback { explicit deadlock_detected( std::string const &whatarg, std::string const &q, - char const sqlstate[] = nullptr); + char const sqlstate[] = nullptr +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); }; @@ -188,151 +236,298 @@ struct PQXX_LIBEXPORT internal_error : std::logic_error /// Error in usage of libpqxx library, similar to std::logic_error struct PQXX_LIBEXPORT usage_error : std::logic_error { - explicit usage_error(std::string const &); + explicit usage_error(std::string const & +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); + +#if pqxx_have_source_location + std::source_location location; +#endif }; /// Invalid argument passed to libpqxx, similar to std::invalid_argument struct PQXX_LIBEXPORT argument_error : std::invalid_argument { - explicit argument_error(std::string const &); + explicit argument_error(std::string const & +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); + +#if pqxx_have_source_location + std::source_location location; +#endif }; /// Value conversion failed, e.g. when converting "Hello" to int. struct PQXX_LIBEXPORT conversion_error : std::domain_error { - explicit conversion_error(std::string const &); + explicit conversion_error(std::string const & +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); + +#if pqxx_have_source_location + std::source_location location; +#endif }; /// Could not convert null value: target type does not support null. struct PQXX_LIBEXPORT unexpected_null : conversion_error { - explicit unexpected_null(std::string const &); + explicit unexpected_null(std::string const & +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); }; /// Could not convert value to string: not enough buffer space. struct PQXX_LIBEXPORT conversion_overrun : conversion_error { - explicit conversion_overrun(std::string const &); + explicit conversion_overrun(std::string const & +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); }; /// Something is out of range, similar to std::out_of_range struct PQXX_LIBEXPORT range_error : std::out_of_range { - explicit range_error(std::string const &); + explicit range_error(std::string const & +#if pqxx_have_source_location + , std::source_location = std::source_location::current() +#endif + ); + +#if pqxx_have_source_location + std::source_location location; +#endif }; /// Query returned an unexpected number of rows. struct PQXX_LIBEXPORT unexpected_rows : public range_error { +#if pqxx_have_source_location + explicit unexpected_rows( + std::string const &msg, + std::source_location loc = std::source_location::current() + ) : + range_error{msg, loc} {} +#else explicit unexpected_rows(std::string const &msg) : range_error{msg} {} +#endif }; /// Database feature not supported in current setup. struct PQXX_LIBEXPORT feature_not_supported : sql_error { +#if pqxx_have_source_location + explicit feature_not_supported( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + sql_error{err, Q, sqlstate, loc} + {} +#else explicit feature_not_supported( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : sql_error{err, Q, sqlstate} {} +#endif }; /// Error in data provided to SQL statement. struct PQXX_LIBEXPORT data_exception : sql_error { +#if pqxx_have_source_location + explicit data_exception( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + sql_error{err, Q, sqlstate, loc} + {} +#else explicit data_exception( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : sql_error{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT integrity_constraint_violation : sql_error { +#if pqxx_have_source_location + explicit integrity_constraint_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + sql_error{err, Q, sqlstate, loc} + {} +#else explicit integrity_constraint_violation( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : sql_error{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT restrict_violation : integrity_constraint_violation { +#if pqxx_have_source_location + explicit restrict_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + integrity_constraint_violation{err, Q, sqlstate, loc} + {} +#else explicit restrict_violation( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : integrity_constraint_violation{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT not_null_violation : integrity_constraint_violation { +#if pqxx_have_source_location + explicit not_null_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + integrity_constraint_violation{err, Q, sqlstate, loc} + {} +#else explicit not_null_violation( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : integrity_constraint_violation{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT foreign_key_violation : integrity_constraint_violation { +#if pqxx_have_source_location + explicit foreign_key_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + integrity_constraint_violation{err, Q, sqlstate, loc} + {} +#else explicit foreign_key_violation( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : integrity_constraint_violation{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT unique_violation : integrity_constraint_violation { +#if pqxx_have_source_location + explicit unique_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + integrity_constraint_violation{err, Q, sqlstate, loc} + {} +#else explicit unique_violation( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : integrity_constraint_violation{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT check_violation : integrity_constraint_violation { +#if pqxx_have_source_location + explicit check_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + integrity_constraint_violation{err, Q, sqlstate, loc} + {} +#else explicit check_violation( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : integrity_constraint_violation{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT invalid_cursor_state : sql_error { +#if pqxx_have_source_location + explicit invalid_cursor_state( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + sql_error{err, Q, sqlstate, loc} + {} +#else explicit invalid_cursor_state( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : sql_error{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT invalid_sql_statement_name : sql_error { +#if pqxx_have_source_location + explicit invalid_sql_statement_name( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + sql_error{err, Q, sqlstate, loc} + {} +#else explicit invalid_sql_statement_name( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : sql_error{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT invalid_cursor_name : sql_error { +#if pqxx_have_source_location + explicit invalid_cursor_name( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + sql_error{err, Q, sqlstate, loc} + {} +#else explicit invalid_cursor_name( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : sql_error{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT syntax_error : sql_error @@ -340,82 +535,166 @@ struct PQXX_LIBEXPORT syntax_error : sql_error /// Approximate position in string where error occurred, or -1 if unknown. int const error_position; +#if pqxx_have_source_location + explicit syntax_error( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, int pos = -1, + std::source_location loc = std::source_location::current()) : + sql_error{err, Q, sqlstate, loc}, error_position{pos} + {} +#else explicit syntax_error( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, int pos = -1) : sql_error{err, Q, sqlstate}, error_position{pos} {} +#endif }; struct PQXX_LIBEXPORT undefined_column : syntax_error { +#if pqxx_have_source_location + explicit undefined_column( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : +// TODO: Can we get the column? + syntax_error{err, Q, sqlstate, -1, loc} + {} +#else explicit undefined_column( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : syntax_error{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT undefined_function : syntax_error { +#if pqxx_have_source_location + explicit undefined_function( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : +// TODO: Can we get the column? + syntax_error{err, Q, sqlstate, -1, loc} + {} +#else explicit undefined_function( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : syntax_error{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT undefined_table : syntax_error { +#if pqxx_have_source_location + explicit undefined_table( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : +// TODO: Can we get the column? + syntax_error{err, Q, sqlstate, -1, loc} + {} +#else explicit undefined_table( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : syntax_error{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT insufficient_privilege : sql_error { +#if pqxx_have_source_location + explicit insufficient_privilege( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + sql_error{err, Q, sqlstate, loc} + {} +#else explicit insufficient_privilege( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : sql_error{err, Q, sqlstate} {} +#endif }; /// Resource shortage on the server struct PQXX_LIBEXPORT insufficient_resources : sql_error { +#if pqxx_have_source_location + explicit insufficient_resources( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + sql_error{err, Q, sqlstate, loc} + {} +#else explicit insufficient_resources( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : sql_error{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT disk_full : insufficient_resources { +#if pqxx_have_source_location + explicit disk_full( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + insufficient_resources{err, Q, sqlstate, loc} + {} +#else explicit disk_full( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : insufficient_resources{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT out_of_memory : insufficient_resources { +#if pqxx_have_source_location + explicit out_of_memory( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + insufficient_resources{err, Q, sqlstate, loc} + {} +#else explicit out_of_memory( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : insufficient_resources{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT too_many_connections : broken_connection { +#if pqxx_have_source_location + explicit too_many_connections( + std::string const &err, + std::source_location loc = std::source_location::current() + ) : + broken_connection{err, loc} + {} +#else explicit too_many_connections(std::string const &err) : broken_connection{err} {} +#endif }; /// PL/pgSQL error @@ -423,39 +702,75 @@ struct PQXX_LIBEXPORT too_many_connections : broken_connection */ struct PQXX_LIBEXPORT plpgsql_error : sql_error { +#if pqxx_have_source_location + explicit plpgsql_error( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + sql_error{err, Q, sqlstate, loc} + {} +#else explicit plpgsql_error( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : sql_error{err, Q, sqlstate} {} +#endif }; /// Exception raised in PL/pgSQL procedure struct PQXX_LIBEXPORT plpgsql_raise : plpgsql_error { +#if pqxx_have_source_location + explicit plpgsql_raise( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + plpgsql_error{err, Q, sqlstate, loc} + {} +#else explicit plpgsql_raise( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : plpgsql_error{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT plpgsql_no_data_found : plpgsql_error { +#if pqxx_have_source_location + explicit plpgsql_no_data_found( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + plpgsql_error{err, Q, sqlstate, loc} + {} +#else explicit plpgsql_no_data_found( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : plpgsql_error{err, Q, sqlstate} {} +#endif }; struct PQXX_LIBEXPORT plpgsql_too_many_rows : plpgsql_error { +#if pqxx_have_source_location + explicit plpgsql_too_many_rows( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, + std::source_location loc = std::source_location::current()) : + plpgsql_error{err, Q, sqlstate, loc} + {} +#else explicit plpgsql_too_many_rows( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr) : plpgsql_error{err, Q, sqlstate} {} +#endif }; /** diff --git a/include/pqxx/internal/cxx-features.hxx b/include/pqxx/internal/cxx-features.hxx index db0dde0f9..7e3e9733e 100644 --- a/include/pqxx/internal/cxx-features.hxx +++ b/include/pqxx/internal/cxx-features.hxx @@ -24,6 +24,12 @@ #define pqxx_have_multidim 0 #endif // pqxx_have_multidim +#if defined(__cpp_lib_source_location) && __cpp_lib_source_location +#define pqxx_have_source_location 1 +#else +#define pqxx_have_source_location 0 +#endif // __cpp_lib_source_location + #if defined(__cpp_lib_ssize) && __cpp_lib_ssize #define pqxx_have_ssize 1 #else diff --git a/include/pqxx/internal/header-pre.hxx b/include/pqxx/internal/header-pre.hxx index d247a79a5..595a53b45 100644 --- a/include/pqxx/internal/header-pre.hxx +++ b/include/pqxx/internal/header-pre.hxx @@ -17,6 +17,12 @@ * mistake, or contact the author. */ +#if __has_include() +#include +#endif + +#include "pqxx/internal/cxx-features.hxx" + // NO GUARD HERE! This part should be included every time this file is. #if defined(_MSC_VER) diff --git a/include/pqxx/result.hxx b/include/pqxx/result.hxx index a274d4f1f..59c61fbf3 100644 --- a/include/pqxx/result.hxx +++ b/include/pqxx/result.hxx @@ -23,7 +23,6 @@ #include #include "pqxx/except.hxx" -#include "pqxx/internal/cxx-features.hxx" #include "pqxx/types.hxx" #include "pqxx/util.hxx" #include "pqxx/zview.hxx" diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index a3687e777..e3d107d5b 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -32,12 +32,7 @@ #include #include -#if __has_include() -# include -#endif - #include "pqxx/except.hxx" -#include "pqxx/internal/cxx-features.hxx" #include "pqxx/types.hxx" #include "pqxx/version.hxx" diff --git a/src/blob.cxx b/src/blob.cxx index 1492d9107..9e16fdaa1 100644 --- a/src/blob.cxx +++ b/src/blob.cxx @@ -118,9 +118,8 @@ pqxx::blob::~blob() catch (std::exception const &e) { if (m_conn != nullptr) - PQXX_UNLIKELY - m_conn->process_notice(internal::concat( - "Failure while closing binary large object: ", e.what(), "\n")); + m_conn->process_notice(internal::concat( + "Failure while closing binary large object: ", e.what(), "\n")); } } diff --git a/src/except.cxx b/src/except.cxx index 90d5c5f66..737e3131c 100644 --- a/src/except.cxx +++ b/src/except.cxx @@ -15,9 +15,16 @@ #include "pqxx/internal/header-post.hxx" +#if pqxx_have_source_location +pqxx::failure::failure(std::string const &whatarg, std::source_location loc) : + std::runtime_error{whatarg}, + location{loc} +{} +#else pqxx::failure::failure(std::string const &whatarg) : std::runtime_error{whatarg} {} +#endif pqxx::broken_connection::broken_connection() : @@ -25,30 +32,63 @@ pqxx::broken_connection::broken_connection() : {} -pqxx::broken_connection::broken_connection(std::string const &whatarg) : - failure{whatarg} -{} - - -pqxx::protocol_violation::protocol_violation(std::string const &whatarg) : - broken_connection{whatarg} +pqxx::broken_connection::broken_connection( + std::string const &whatarg +#if pqxx_have_source_location + , std::source_location loc +#endif + ) : + failure{whatarg +#if pqxx_have_source_location + , loc +#endif + } {} -pqxx::variable_set_to_null::variable_set_to_null() : - variable_set_to_null{ - "Attempt to set a variable to null. This is not allowed."} +pqxx::protocol_violation::protocol_violation( + std::string const &whatarg +#if pqxx_have_source_location + , std::source_location loc +#endif +) : + broken_connection{whatarg +#if pqxx_have_source_location + , loc +#endif + } {} -pqxx::variable_set_to_null::variable_set_to_null(std::string const &whatarg) : - failure{whatarg} +pqxx::variable_set_to_null::variable_set_to_null( + std::string const &whatarg +#if pqxx_have_source_location + , std::source_location loc +#endif +) : + failure{ + whatarg +#if pqxx_have_source_location + , loc +#endif + } {} pqxx::sql_error::sql_error( - std::string const &whatarg, std::string const &Q, char const sqlstate[]) : - failure{whatarg}, m_query{Q}, m_sqlstate{sqlstate ? sqlstate : ""} + std::string const &whatarg, std::string const &Q, char const sqlstate[] +#if pqxx_have_source_location + , std::source_location loc +#endif +) : + failure{ + whatarg +#if pqxx_have_source_location + , loc +#endif + }, + m_query{Q}, + m_sqlstate{sqlstate ? sqlstate : ""} {} @@ -67,32 +107,78 @@ PQXX_PURE std::string const &pqxx::sql_error::sqlstate() const noexcept } -pqxx::in_doubt_error::in_doubt_error(std::string const &whatarg) : - failure{whatarg} +pqxx::in_doubt_error::in_doubt_error( + std::string const &whatarg +#if pqxx_have_source_location + , std::source_location loc +#endif +) : + failure{ + whatarg +#if pqxx_have_source_location + , loc +#endif + } {} pqxx::transaction_rollback::transaction_rollback( - std::string const &whatarg, std::string const &q, char const sqlstate[]) : - sql_error{whatarg, q, sqlstate} + std::string const &whatarg, std::string const &q, char const sqlstate[] +#if pqxx_have_source_location + , std::source_location loc +#endif + ) : + sql_error{ + whatarg, q, sqlstate +#if pqxx_have_source_location + , loc +#endif + } {} pqxx::serialization_failure::serialization_failure( - std::string const &whatarg, std::string const &q, char const sqlstate[]) : - transaction_rollback{whatarg, q, sqlstate} + std::string const &whatarg, std::string const &q, char const sqlstate[] +#if pqxx_have_source_location + , std::source_location loc +#endif +) : + transaction_rollback{ + whatarg, q, sqlstate +#if pqxx_have_source_location + , loc +#endif + } {} pqxx::statement_completion_unknown::statement_completion_unknown( - std::string const &whatarg, std::string const &q, char const sqlstate[]) : - transaction_rollback{whatarg, q, sqlstate} + std::string const &whatarg, std::string const &q, char const sqlstate[] +#if pqxx_have_source_location + , std::source_location loc +#endif +) : + transaction_rollback{ + whatarg, q, sqlstate +#if pqxx_have_source_location + , loc +#endif + } {} pqxx::deadlock_detected::deadlock_detected( - std::string const &whatarg, std::string const &q, char const sqlstate[]) : - transaction_rollback{whatarg, q, sqlstate} + std::string const &whatarg, std::string const &q, char const sqlstate[] +#if pqxx_have_source_location + , std::source_location loc +#endif +) : + transaction_rollback{ + whatarg, q, sqlstate +#if pqxx_have_source_location + , loc +#endif + } {} @@ -101,31 +187,83 @@ pqxx::internal_error::internal_error(std::string const &whatarg) : {} -pqxx::usage_error::usage_error(std::string const &whatarg) : +pqxx::usage_error::usage_error( + std::string const &whatarg +#if pqxx_have_source_location + , std::source_location loc +#endif +) : std::logic_error{whatarg} +#if pqxx_have_source_location + , location{loc} +#endif {} -pqxx::argument_error::argument_error(std::string const &whatarg) : +pqxx::argument_error::argument_error( + std::string const &whatarg +#if pqxx_have_source_location + , std::source_location loc +#endif +) : invalid_argument{whatarg} +#if pqxx_have_source_location + , location{loc} +#endif {} -pqxx::conversion_error::conversion_error(std::string const &whatarg) : +pqxx::conversion_error::conversion_error( + std::string const &whatarg +#if pqxx_have_source_location + , std::source_location loc +#endif +) : domain_error{whatarg} +#if pqxx_have_source_location + , location{loc} +#endif {} -pqxx::unexpected_null::unexpected_null(std::string const &whatarg) : - conversion_error{whatarg} +pqxx::unexpected_null::unexpected_null( + std::string const &whatarg +#if pqxx_have_source_location + , std::source_location loc +#endif +) : + conversion_error{ + whatarg +#if pqxx_have_source_location + , loc +#endif + } {} -pqxx::conversion_overrun::conversion_overrun(std::string const &whatarg) : - conversion_error{whatarg} +pqxx::conversion_overrun::conversion_overrun( + std::string const &whatarg +#if pqxx_have_source_location + , std::source_location loc +#endif +) : + conversion_error{ + whatarg +#if pqxx_have_source_location + , loc +#endif + } {} -pqxx::range_error::range_error(std::string const &whatarg) : +pqxx::range_error::range_error( + std::string const &whatarg +#if pqxx_have_source_location + , std::source_location loc +#endif +) : out_of_range{whatarg} +#if pqxx_have_source_location + , location{loc} +#endif {} diff --git a/src/transaction.cxx b/src/transaction.cxx index 8e5fc7ed5..439e6c5d1 100644 --- a/src/transaction.cxx +++ b/src/transaction.cxx @@ -78,7 +78,12 @@ void pqxx::internal::basic_transaction::do_commit() process_notice(msg); // Strip newline. It was only needed for process_notice(). msg.pop_back(); - throw in_doubt_error{std::move(msg)}; + throw in_doubt_error{ + std::move(msg) +#if pqxx_have_source_location + , e.location +#endif + }; } catch (std::exception const &e) { diff --git a/test/runner.cxx b/test/runner.cxx index 38c9849d8..dddc24896 100644 --- a/test/runner.cxx +++ b/test/runner.cxx @@ -23,10 +23,16 @@ inline std::string deref_field(pqxx::field const &f) namespace pqxx::test { +#if pqxx_have_source_location +test_failure::test_failure(std::string const &desc, std::source_location loc) : + std::logic_error{desc}, m_loc{loc} +{} +#else test_failure::test_failure( std::string const &ffile, int fline, std::string const &desc) : std::logic_error(desc), m_file(ffile), m_line(fline) {} +#endif test_failure::~test_failure() noexcept = default; @@ -39,19 +45,49 @@ inline void drop_table(transaction_base &t, std::string const &table) [[noreturn]] void -check_notreached(char const file[], int line, std::string desc) +check_notreached( +#if !pqxx_have_source_location + char const file[], int line, +#endif + std::string desc +#if pqxx_have_source_location + , std::source_location loc +#endif +) { - throw test_failure(file, line, desc); + throw test_failure{ +#if !pqxx_have_source_location + file, line, +#endif + desc +#if pqxx_have_source_location + , loc +#endif + }; } void check( - char const file[], int line, bool condition, char const text[], - std::string const &desc) +#if !pqxx_have_source_location + char const file[], int line, +#endif + bool condition, char const text[], + std::string const &desc +#if pqxx_have_source_location + , std::source_location loc +#endif +) { if (not condition) - throw test_failure( - file, line, desc + " (failed expression: " + text + ")"); + throw test_failure{ +#if !pqxx_have_source_location + file, line, +#endif + desc + " (failed expression: " + text + ")" +#if pqxx_have_source_location + , loc +#endif + }; } @@ -154,9 +190,10 @@ int main(int argc, char const *argv[]) } catch (pqxx::test::test_failure const &e) { - std::cerr << "Test failure in " + e.file() + " line " + - pqxx::to_string(e.line()) - << ": " << e.what() << std::endl; + std::cerr + << "Test failure in " << e.file() << " line " + << pqxx::to_string(e.line()) + << ": " << e.what() << std::endl; } catch (std::bad_alloc const &) { @@ -164,15 +201,30 @@ int main(int argc, char const *argv[]) } catch (pqxx::feature_not_supported const &e) { - std::cerr << "Not testing unsupported feature: " << e.what() - << std::endl; + std::cerr << "Not testing unsupported feature: " << e.what() << '\n'; +#if pqxx_have_source_location + std::string func{e.location.function_name()}; + std::cerr << "("; + std::cerr << e.location.file_name() << ':' << e.location.line(); + if (not func.empty()) + std::cerr << " in " << e.location.function_name(); + std::cerr << ")\n"; +#endif success = true; --test_count; } catch (pqxx::sql_error const &e) { - std::cerr << "SQL error: " << e.what() << std::endl - << "Query was: " << e.query() << std::endl; + std::cerr << "SQL error: " << e.what() << '\n'; +#if pqxx_have_source_location + std::string func{e.location.function_name()}; + std::cerr << "("; + std::cerr << e.location.file_name() << ':' << e.location.line(); + if (not func.empty()) + std::cerr << " in " << e.location.function_name(); + std::cerr << ")\n"; +#endif + std::cerr << "Query was: " << e.query() << std::endl; } catch (std::exception const &e) { diff --git a/test/test_helpers.hxx b/test/test_helpers.hxx index e88110e0f..87d88a00f 100644 --- a/test/test_helpers.hxx +++ b/test/test_helpers.hxx @@ -10,16 +10,32 @@ namespace test { class test_failure : public std::logic_error { - std::string const m_file; - int m_line; - public: +#if pqxx_have_source_location + test_failure( + std::string const &desc, + std::source_location loc=std::source_location::current()); +#else test_failure(std::string const &ffile, int fline, std::string const &desc); +#endif ~test_failure() noexcept override; +#if pqxx_have_source_location + constexpr char const *file() const noexcept { return m_loc.file_name(); } + constexpr auto line() const noexcept { return m_loc.line(); } +#else std::string const &file() const noexcept { return m_file; } int line() const noexcept { return m_line; } +#endif + +private: +#if pqxx_have_source_location + std::source_location m_loc; +#else + std::string const m_file; + int m_line; +#endif }; @@ -52,26 +68,62 @@ struct registrar // Unconditional test failure. +#if pqxx_have_source_location +#define PQXX_CHECK_NOTREACHED(desc) pqxx::test::check_notreached((desc)) +#else #define PQXX_CHECK_NOTREACHED(desc) \ pqxx::test::check_notreached(__FILE__, __LINE__, (desc)) +#endif [[noreturn]] void -check_notreached(char const file[], int line, std::string desc); +check_notreached( +#if !pqxx_have_source_location + char const file[], int line, +#endif + std::string desc +#if pqxx_have_source_location + , std::source_location loc=std::source_location::current() +#endif +); // Verify that a condition is met, similar to assert() +#if pqxx_have_source_location +#define PQXX_CHECK(condition, desc) \ + pqxx::test::check((condition), #condition, (desc)) +#else #define PQXX_CHECK(condition, desc) \ pqxx::test::check(__FILE__, __LINE__, (condition), #condition, (desc)) +#endif void check( - char const file[], int line, bool condition, char const text[], - std::string const &desc); +#if !pqxx_have_source_location + char const file[], int line, +#endif + bool condition, char const text[], + std::string const &desc +#if pqxx_have_source_location + , std::source_location loc=std::source_location::current() +#endif +); // Verify that variable has the expected value. +#if pqxx_have_source_location +#define PQXX_CHECK_EQUAL(actual, expected, desc) \ + pqxx::test::check_equal((actual), #actual, (expected), #expected, (desc)) +#else #define PQXX_CHECK_EQUAL(actual, expected, desc) \ pqxx::test::check_equal( \ __FILE__, __LINE__, (actual), #actual, (expected), #expected, (desc)) +#endif template inline void check_equal( - char const file[], int line, ACTUAL actual, char const actual_text[], - EXPECTED expected, char const expected_text[], std::string const &desc) +#if !pqxx_have_source_location + char const file[], int line, +#endif + ACTUAL actual, char const actual_text[], + EXPECTED expected, char const expected_text[], std::string const &desc +#if pqxx_have_source_location + , std::source_location loc=std::source_location::current() +#endif + ) { if (expected == actual) return; @@ -83,17 +135,33 @@ inline void check_equal( ", " "expected=" + to_string(expected) + ")"; +#if pqxx_have_source_location + throw test_failure{fulldesc, loc}; +#else throw test_failure(file, line, fulldesc); +#endif } // Verify that two values are not equal. +#if pqxx_have_source_location +#define PQXX_CHECK_NOT_EQUAL(value1, value2, desc) \ + pqxx::test::check_not_equal((value1), #value1, (value2), #value2, (desc)) +#else #define PQXX_CHECK_NOT_EQUAL(value1, value2, desc) \ pqxx::test::check_not_equal( \ __FILE__, __LINE__, (value1), #value1, (value2), #value2, (desc)) +#endif template inline void check_not_equal( - char const file[], int line, VALUE1 value1, char const text1[], - VALUE2 value2, char const text2[], std::string const &desc) +#if !pqxx_have_source_location + char const file[], int line, +#endif + VALUE1 value1, char const text1[], + VALUE2 value2, char const text2[], std::string const &desc +#if pqxx_have_source_location + , std::source_location loc=std::source_location::current() +#endif + ) { if (value1 != value2) return; @@ -101,10 +169,22 @@ inline void check_not_equal( ": " "both are " + to_string(value2) + ")"; - throw test_failure(file, line, fulldesc); +# if pqxx_have_source_location + throw test_failure{fulldesc, loc}; +#else + throw test_failure{file, line, fulldesc}; +#endif } +#if pqxx_have_source_location +// Verify that value1 is less than value2. +#define PQXX_CHECK_LESS(value1, value2, desc) \ + pqxx::test::check_less((value1), #value1, (value2), #value2, (desc)) +// Verify that value1 is greater than value2. +#define PQXX_CHECK_GREATER(value2, value1, desc) \ + pqxx::test::check_less((value1), #value1, (value2), #value2, (desc)) +#else // Verify that value1 is less than value2. #define PQXX_CHECK_LESS(value1, value2, desc) \ pqxx::test::check_less( \ @@ -113,10 +193,18 @@ inline void check_not_equal( #define PQXX_CHECK_GREATER(value2, value1, desc) \ pqxx::test::check_less( \ __FILE__, __LINE__, (value1), #value1, (value2), #value2, (desc)) +#endif template inline void check_less( - char const file[], int line, VALUE1 value1, char const text1[], - VALUE2 value2, char const text2[], std::string const &desc) +#if !pqxx_have_source_location + char const file[], int line, +#endif + VALUE1 value1, char const text1[], + VALUE2 value2, char const text2[], std::string const &desc +#if pqxx_have_source_location + , std::source_location loc=std::source_location::current() +#endif +) { if (value1 < value2) return; @@ -127,10 +215,22 @@ inline void check_less( ", " "\"upper\"=" + to_string(value2) + ")"; +#if pqxx_have_source_location + throw test_failure{fulldesc, loc}; +#else throw test_failure(file, line, fulldesc); +#endif } +#if pqxx_have_source_location +// Verify that value1 is less than or equal to value2. +#define PQXX_CHECK_LESS_EQUAL(value1, value2, desc) \ + pqxx::test::check_less_equal((value1), #value1, (value2), #value2, (desc)) +// Verify that value1 is greater than or equal to value2. +#define PQXX_CHECK_GREATER_EQUAL(value2, value1, desc) \ + pqxx::test::check_less_equal((value1), #value1, (value2), #value2, (desc)) +#else // Verify that value1 is less than or equal to value2. #define PQXX_CHECK_LESS_EQUAL(value1, value2, desc) \ pqxx::test::check_less_equal( \ @@ -139,10 +239,18 @@ inline void check_less( #define PQXX_CHECK_GREATER_EQUAL(value2, value1, desc) \ pqxx::test::check_less_equal( \ __FILE__, __LINE__, (value1), #value1, (value2), #value2, (desc)) +#endif template inline void check_less_equal( - char const file[], int line, VALUE1 value1, char const text1[], - VALUE2 value2, char const text2[], std::string const &desc) +#if !pqxx_have_source_location + char const file[], int line, +#endif + VALUE1 value1, char const text1[], + VALUE2 value2, char const text2[], std::string const &desc +#if pqxx_have_source_location + , std::source_location loc=std::source_location::current() +#endif +) { if (value1 <= value2) return; @@ -153,7 +261,11 @@ inline void check_less_equal( ", " "\"upper\"=" + to_string(value2) + ")"; +#if pqxx_have_source_location + throw test_failure{fulldesc, loc}; +#else throw test_failure(file, line, fulldesc); +#endif } @@ -245,15 +357,28 @@ inline void end_of_statement() {} } \ pqxx::test::internal::end_of_statement() +#if pqxx_have_source_location +#define PQXX_CHECK_BOUNDS(value, lower, upper, desc) \ + pqxx::test::check_bounds( \ + (value), #value, (lower), #lower, (upper), #upper, (desc)) +#else #define PQXX_CHECK_BOUNDS(value, lower, upper, desc) \ pqxx::test::check_bounds( \ __FILE__, __LINE__, (value), #value, (lower), #lower, (upper), #upper, \ (desc)) +#endif template inline void check_bounds( - char const file[], int line, VALUE value, char const text[], LOWER lower, +#if !pqxx_have_source_location + char const file[], int line, +#endif + VALUE value, char const text[], LOWER lower, char const lower_text[], UPPER upper, char const upper_text[], - std::string const &desc) + std::string const &desc +#if pqxx_have_source_location + , std::source_location loc=std::source_location::current() +#endif +) { std::string const range_check = std::string{lower_text} + " < " + upper_text, lower_check = @@ -261,14 +386,35 @@ inline void check_bounds( upper_check = std::string{text} + " < " + upper_text; pqxx::test::check( - file, line, lower < upper, range_check.c_str(), - desc + " (acceptable range is empty; value was " + text + ")"); +#if !pqxx_have_source_location + file, line, +#endif + lower < upper, range_check.c_str(), + desc + " (acceptable range is empty; value was " + text + ")" +#if pqxx_have_source_location + , loc +#endif + ); pqxx::test::check( - file, line, not(value < lower), lower_check.c_str(), - desc + " (" + text + " is below lower bound " + lower_text + ")"); +#if !pqxx_have_source_location + file, line, +#endif + not(value < lower), lower_check.c_str(), + desc + " (" + text + " is below lower bound " + lower_text + ")" +#if pqxx_have_source_location + , loc +#endif + ); pqxx::test::check( - file, line, value < upper, upper_check.c_str(), - desc + " (" + text + " is not below upper bound " + upper_text + ")"); +#if !pqxx_have_source_location + file, line, +#endif + value < upper, upper_check.c_str(), + desc + " (" + text + " is not below upper bound " + upper_text + ")" +#if pqxx_have_source_location + , loc +#endif + ); } diff --git a/test/unit/test_test_helpers.cxx b/test/unit/test_test_helpers.cxx index 89cde627e..c2c0c1a8b 100644 --- a/test/unit/test_test_helpers.cxx +++ b/test/unit/test_test_helpers.cxx @@ -19,8 +19,11 @@ void test_check_notreached() // This is what we expect. } if (not failed) - throw pqxx::test::test_failure( - __FILE__, __LINE__, "PQXX_CHECK_NOTREACHED is broken."); + throw pqxx::test::test_failure{ +#if !pqxx_have_source_location + __FILE__, __LINE__, +#endif + "PQXX_CHECK_NOTREACHED is broken."}; } @@ -51,9 +54,15 @@ void test_check_throws_exception() "PQXX_CHECK_THROWS_EXCEPTION did not catch std::exception."); // ...or any exception type derived from it. +#if pqxx_have_source_location PQXX_CHECK_THROWS_EXCEPTION( - throw pqxx::test::test_failure(__FILE__, __LINE__, "(expected)"), + throw pqxx::test::test_failure{"(expected)"}, + "PQXX_CHECK_THROWS_EXCEPTION() failed to catch expected exception."); +#else + PQXX_CHECK_THROWS_EXCEPTION( + throw (pqxx::test::test_failure{__FILE__, __LINE__, "(expected)"}), "PQXX_CHECK_THROWS_EXCEPTION() failed to catch expected exception."); +#endif // Any other type is an error. bool failed{true}; @@ -98,10 +107,17 @@ void test_check_throws_exception() // Test PQXX_CHECK_THROWS. void test_check_throws() { +#if pqxx_have_source_location + PQXX_CHECK_THROWS( + throw pqxx::test::test_failure{"(expected)"}, + pqxx::test::test_failure, + "PQXX_CHECK_THROWS() failed to catch expected exception."); +#else PQXX_CHECK_THROWS( throw pqxx::test::test_failure(__FILE__, __LINE__, "(expected)"), pqxx::test::test_failure, "PQXX_CHECK_THROWS() failed to catch expected exception."); +#endif // Even if it's not std::exception-derived. PQXX_CHECK_THROWS(throw 1, int, "(expected)");