From 7e1434e3edf192b8f3c3c8c2a488231a6d400c31 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Sat, 15 Feb 2025 13:56:13 +0100 Subject: [PATCH] Pass `std::source_location` into libpqxx functions. (#947) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I'm trying to ensure that every libpqxx function that may throw an exception has a `source_location` parameter, and that it passes this to any functions it calls that may throw. The end result would ideally be that any exception thrown by libpqxx would include the location of the original call into libpqxx. (Unless you pass your own choice of source location, of course). In practice, this ideal is not quite attainable... * Parameter packs make it hard — you can't just make a function accept any number of arguments of any type, and then add a `source_location` parameter _after_ that. * With overloaded operators it's completely impossible — there's no way of adding parameters to a function with a fixed, language-dictated signature. * Destructors take no arguments at all, so they're out as well. * Callbacks and string conversions are a problem as well, because their existing definitions don't include `source_location`. I do have a plan for that last item though. I can probably update the signatures and use compile-time introspection (probably using function concepts) to detect whether the given user-defined function supports the old API or the new API. That's a separate job though. Oh, and because I don't want to grow all my function signatures with an additional tedious `std::source_location location = std::source_location::current()` etc, I created a type alias `sl`. Hate how cryptic this makes the functions, but I really need these to be brief. --- NEWS | 3 + config/Makefile.in | 2 +- include/pqxx/array.hxx | 169 +++++++----- include/pqxx/blob.hxx | 73 ++--- include/pqxx/composite.hxx | 35 ++- include/pqxx/connection.hxx | 184 ++++++++----- include/pqxx/cursor.hxx | 49 ++-- include/pqxx/except.hxx | 161 ++++------- include/pqxx/field.hxx | 47 ++-- include/pqxx/internal/array-composite.hxx | 77 +++--- include/pqxx/internal/conversions.hxx | 9 +- include/pqxx/internal/encoding_group.hxx | 6 +- include/pqxx/internal/encodings.hxx | 164 +++++------ .../connection-notification_receiver.hxx | 4 +- .../internal/gates/connection-pipeline.hxx | 4 +- .../internal/gates/connection-sql_cursor.hxx | 2 +- .../internal/gates/connection-stream_to.hxx | 5 +- .../internal/gates/connection-transaction.hxx | 26 +- .../gates/icursorstream-icursor_iterator.hxx | 4 +- .../pqxx/internal/gates/result-creation.hxx | 6 +- include/pqxx/internal/result_iter.hxx | 30 +- include/pqxx/internal/result_iterator.hxx | 10 +- include/pqxx/internal/sql_cursor.hxx | 30 +- include/pqxx/internal/stream_iterator.hxx | 21 +- include/pqxx/internal/stream_query.hxx | 20 +- include/pqxx/internal/stream_query_impl.hxx | 40 +-- include/pqxx/internal/wait.hxx | 5 +- include/pqxx/nontransaction.hxx | 4 +- include/pqxx/params.hxx | 12 +- include/pqxx/pipeline.hxx | 45 +-- include/pqxx/range.hxx | 52 ++-- include/pqxx/result.hxx | 95 ++++--- include/pqxx/robusttransaction.hxx | 15 +- include/pqxx/row.hxx | 70 ++--- include/pqxx/stream_from.hxx | 22 +- include/pqxx/stream_to.hxx | 54 ++-- include/pqxx/subtransaction.hxx | 14 +- include/pqxx/time.hxx | 7 +- include/pqxx/transaction.hxx | 21 +- include/pqxx/transaction_base.hxx | 256 ++++++++++++------ include/pqxx/transactor.hxx | 43 ++- include/pqxx/types.hxx | 4 + include/pqxx/util.hxx | 20 +- src/array.cxx | 63 +++-- src/blob.cxx | 163 ++++++----- src/connection.cxx | 247 +++++++++-------- src/cursor.cxx | 55 ++-- src/encodings.cxx | 6 +- src/except.cxx | 49 ++-- src/field.cxx | 18 +- src/notification.cxx | 4 +- src/params.cxx | 7 +- src/pipeline.cxx | 116 ++++---- src/result.cxx | 218 ++++++++------- src/robusttransaction.cxx | 68 ++--- src/row.cxx | 27 +- src/sql_cursor.cxx | 40 +-- src/strconv.cxx | 10 +- src/stream_from.cxx | 38 ++- src/stream_to.cxx | 42 +-- src/subtransaction.cxx | 23 +- src/time.cxx | 40 +-- src/transaction.cxx | 19 +- src/transaction_base.cxx | 82 +++--- src/util.cxx | 18 +- src/wait.cxx | 11 +- test/runner.cxx | 12 +- test/test07.cxx | 4 +- test/test_array.cxx | 24 ++ test/test_encodings.cxx | 23 +- test/test_helpers.hxx | 200 +++++++------- test/test_sql_cursor.cxx | 53 ++-- test/test_stream_from.cxx | 8 +- test/test_stream_to.cxx | 8 +- test/test_test_helpers.cxx | 12 +- test/test_types.hxx | 4 +- 76 files changed, 2065 insertions(+), 1567 deletions(-) diff --git a/NEWS b/NEWS index aeb56dce7..710d5161b 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ - Conversion from string to `char const *` is no longer allowed. - Binary data can be any `std::contiguous_range` of `std::byte`. (#925) - Generic `quote()` takes `always_null` into account. + - The libpqxx exceptions now have a `std::source_location`. + - More getters are now `noexcept`. - Retired `binarystring` and its headers. Use `blob` instead. - Retired `connection_base` type alias. Use `connection`. - Retired `pqxx::encrypt_password()`. Use the ones in `pqxx::connection`. @@ -21,6 +23,7 @@ - Assume compiler supports `std::filesystem::path`. - Assume compiler supports `[[likely]]` & `[[unlikely]]`. - Assume compiler supports `ssize()`. + - Assume compiler supports `std::source_location`. - Assume compiler supports ISO-646 without needing `` header. 7.10.1 - Fix string conversion buffer budget for arrays containing nulls. (#921) diff --git a/config/Makefile.in b/config/Makefile.in index 51586bda2..9b1454986 100644 --- a/config/Makefile.in +++ b/config/Makefile.in @@ -124,7 +124,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/array.hxx b/include/pqxx/array.hxx index 31b31b89c..b4667784b 100644 --- a/include/pqxx/array.hxx +++ b/include/pqxx/array.hxx @@ -65,8 +65,8 @@ public: * @throws pqxx::unexpected_null if the array contains a null value, and the * `ELEMENT` type does not support null values. */ - array(std::string_view data, connection const &cx) : - array{data, pqxx::internal::enc_group(cx.encoding_id())} + array(std::string_view data, connection const &cx, sl loc = sl::current()) : + array{data, pqxx::internal::enc_group(cx.encoding_id(loc), loc), loc} {} /// How many dimensions does this array have? @@ -84,7 +84,8 @@ public: return m_extents; } - template ELEMENT const &at(INDEX... index) const + // TODO: How can we pass std::source_location here? + template ELEMENT const &at(INDEX... index) const { static_assert(sizeof...(index) == DIMENSIONS); check_bounds(index...); @@ -100,7 +101,8 @@ public: * better. In older versions of C++ it will work only with * single-dimensional arrays. */ - template ELEMENT const &operator[](INDEX... index) const + template + ELEMENT const &operator[](INDEX... index) const { static_assert(sizeof...(index) == DIMENSIONS); return m_elts[locate(index...)]; @@ -167,13 +169,15 @@ private: * walking through the entire array sequentially, and identifying all the * character boundaries. The main parsing routine detects that one. */ - void check_dims(std::string_view data) + void check_dims(std::string_view data, sl loc = sl::current()) { auto sz{std::size(data)}; if (sz < DIMENSIONS * 2) - throw conversion_error{pqxx::internal::concat( - "Trying to parse a ", DIMENSIONS, "-dimensional array out of '", data, - "'.")}; + throw conversion_error{ + pqxx::internal::concat( + "Trying to parse a ", DIMENSIONS, "-dimensional array out of '", + data, "'."), + loc}; // Making some assumptions here: // * The array holds no extraneous whitespace. @@ -185,43 +189,48 @@ private: // of DIMENSIONS bytes with the ASCII value for '}'. if (data[0] != '{') - throw conversion_error{"Malformed array: does not start with '{'."}; + throw conversion_error{"Malformed array: does not start with '{'.", loc}; for (std::size_t i{0}; i < DIMENSIONS; ++i) if (data[i] != '{') - throw conversion_error{pqxx::internal::concat( - "Expecting ", DIMENSIONS, "-dimensional array, but found ", i, ".")}; + throw conversion_error{ + pqxx::internal::concat( + "Expecting ", DIMENSIONS, "-dimensional array, but found ", i, + "."), + loc}; if (data[DIMENSIONS] == '{') - throw conversion_error{pqxx::internal::concat( - "Tried to parse ", DIMENSIONS, - "-dimensional array from array data that has more dimensions.")}; + throw conversion_error{ + pqxx::internal::concat( + "Tried to parse ", DIMENSIONS, + "-dimensional array from array data that has more dimensions."), + loc}; for (std::size_t i{0}; i < DIMENSIONS; ++i) if (data[sz - 1 - i] != '}') throw conversion_error{ - "Malformed array: does not end in the right number of '}'."}; + "Malformed array: does not end in the right number of '}'.", loc}; } // Allow fields to construct arrays passing the encoding group. // Couldn't make this work through a call gate, thanks to the templating. friend class ::pqxx::field; - array(std::string_view data, pqxx::internal::encoding_group enc) + array(std::string_view data, pqxx::internal::encoding_group enc, sl loc) { using group = pqxx::internal::encoding_group; switch (enc) { - case group::MONOBYTE: parse(data); break; - case group::BIG5: parse(data); break; - case group::EUC_CN: parse(data); break; - case group::EUC_JP: parse(data); break; - case group::EUC_KR: parse(data); break; - case group::EUC_TW: parse(data); break; - case group::GB18030: parse(data); break; - case group::GBK: parse(data); break; - case group::JOHAB: parse(data); break; - case group::MULE_INTERNAL: parse(data); break; - case group::SJIS: parse(data); break; - case group::UHC: parse(data); break; - case group::UTF8: parse(data); break; + case group::MONOBYTE: parse(data, loc); break; + case group::BIG5: parse(data, loc); break; + case group::EUC_CN: parse(data, loc); break; + case group::EUC_JP: parse(data, loc); break; + case group::EUC_KR: parse(data, loc); break; + case group::EUC_TW: parse(data, loc); break; + case group::GB18030: parse(data, loc); break; + case group::GBK: parse(data, loc); break; + case group::JOHAB: parse(data, loc); break; + case group::MULE_INTERNAL: parse(data, loc); break; + case group::SJIS: parse(data, loc); break; + case group::UHC: parse(data, loc); break; + case group::UTF8: parse(data, loc); break; default: PQXX_UNREACHABLE; break; } } @@ -230,7 +239,8 @@ private: /** Check for a trailing separator, detect any syntax errors at this somewhat * complicated point, and return the offset where parsing should continue. */ - std::size_t parse_field_end(std::string_view data, std::size_t here) const + std::size_t + parse_field_end(std::string_view data, std::size_t here, sl loc) const { auto const sz{std::size(data)}; if (here < sz) @@ -239,21 +249,24 @@ private: case SEPARATOR: ++here; if (here >= sz) - throw conversion_error{"Array looks truncated."}; + throw conversion_error{"Array looks truncated.", loc}; switch (data[here]) { case SEPARATOR: - throw conversion_error{"Array contains double separator."}; - case '}': throw conversion_error{"Array contains trailing separator."}; + throw conversion_error{"Array contains double separator.", loc}; + case '}': + throw conversion_error{"Array contains trailing separator.", loc}; default: break; } break; case '}': break; default: - throw conversion_error{pqxx::internal::concat( - "Unexpected character in array: ", - static_cast(static_cast(data[here])), - " where separator or closing brace expected.")}; + throw conversion_error{ + pqxx::internal::concat( + "Unexpected character in array: ", + static_cast(static_cast(data[here])), + " where separator or closing brace expected."), + loc}; } return here; } @@ -278,7 +291,7 @@ private: } template - void parse(std::string_view data) + void parse(std::string_view data, sl loc) { static_assert(DIMENSIONS > 0u, "Can't create a zero-dimensional array."); auto const sz{std::size(data)}; @@ -322,7 +335,8 @@ private: if (know_extents_from != DIMENSIONS) throw conversion_error{ "Array text representation closed and reopened its outside " - "brace pair."}; + "brace pair.", + loc}; assert(here == 0); PQXX_ASSUME(here == 0); } @@ -330,7 +344,7 @@ private: { if (dim >= (DIMENSIONS - 1)) throw conversion_error{ - "Array seems to have inconsistent number of dimensions."}; + "Array seems to have inconsistent number of dimensions.", loc}; ++extents[dim]; } // (Rolls over to zero if we're coming from the outer dimension.) @@ -341,7 +355,7 @@ private: else if (data[here] == '}') { if (dim == outer) - throw conversion_error{"Array has spurious '}'."}; + throw conversion_error{"Array has spurious '}'.", loc}; if (dim < know_extents_from) { // We just finished parsing our first row in this dimension. @@ -352,13 +366,14 @@ private: else { if (extents[dim] != m_extents[dim]) - throw conversion_error{"Rows in array have inconsistent sizes."}; + throw conversion_error{ + "Rows in array have inconsistent sizes.", loc}; } // Bump back down to the next-lower dimension. Which may be the outer // dimension, through underflow. --dim; ++here; - here = parse_field_end(data, here); + here = parse_field_end(data, here, loc); } else { @@ -366,14 +381,16 @@ private: // "inner" dimension. if (dim != DIMENSIONS - 1) throw conversion_error{ - "Malformed array: found element where sub-array was expected."}; + "Malformed array: found element where sub-array was expected.", + loc}; assert(dim != outer); ++extents[dim]; std::size_t end; switch (data[here]) { - case '\0': throw conversion_error{"Unexpected zero byte in array."}; - case ',': throw conversion_error{"Array contains empty field."}; + case '\0': + throw conversion_error{"Unexpected zero byte in array.", loc}; + case ',': throw conversion_error{"Array contains empty field.", loc}; case '"': { // Double-quoted string. We parse it into a buffer before parsing // the resulting string as an element. This seems wasteful: the @@ -385,11 +402,11 @@ private: // So in practice, this optimisation would only apply if the only // special characters in the string were commas. end = pqxx::internal::scan_double_quoted_string( - std::data(data), std::size(data), here); + std::data(data), std::size(data), here, loc); // TODO: scan_double_quoted_string() with reusable buffer. std::string const buf{ pqxx::internal::parse_double_quoted_string( - std::data(data), end, here)}; + std::data(data), end, here, loc)}; m_elts.emplace_back(from_string(buf)); } break; @@ -398,7 +415,7 @@ private: // escaping or encoding, so we don't need to parse it into a // buffer. We can just read it as a string_view. end = pqxx::internal::scan_unquoted_string( - std::data(data), std::size(data), here); + std::data(data), std::size(data), here, loc); std::string_view const field{ std::string_view{std::data(data) + here, end - here}}; if (field == "NULL") @@ -406,10 +423,12 @@ private: if constexpr (nullness::has_null) m_elts.emplace_back(nullness::null()); else - throw unexpected_null{pqxx::internal::concat( - "Array contains a null ", type_name, - ". Consider making it an array of std::optional<", - type_name, "> instead.")}; + throw unexpected_null{ + pqxx::internal::concat( + "Array contains a null ", type_name, + ". Consider making it an array of std::optional<", + type_name, "> instead."), + loc}; } else m_elts.emplace_back(from_string(field)); @@ -417,12 +436,12 @@ private: } here = end; PQXX_ASSUME(here <= sz); - here = parse_field_end(data, here); + here = parse_field_end(data, here, loc); } } if (dim != outer) - throw conversion_error{"Malformed array; may be truncated."}; + throw conversion_error{"Malformed array; may be truncated.", loc}; assert(know_extents_from == 0); PQXX_ASSUME(know_extents_from == 0); @@ -452,7 +471,9 @@ private: template constexpr std::size_t add_index(OUTER outer, INDEX... indexes) const noexcept { - std::size_t const first{check_cast(outer, "array index"sv)}; + sl loc{sl::current()}; + std::size_t const first{ + check_cast(outer, "array index"sv, loc)}; if constexpr (sizeof...(indexes) == 0) { return first; @@ -467,13 +488,16 @@ private: } } + // TODO: How can we pass std::source_location here? /// Check that indexes are within bounds. /** @throw pqxx::range_error if not. */ - template + template constexpr void check_bounds(OUTER outer, INDEX... indexes) const { - std::size_t const first{check_cast(outer, "array index"sv)}; + sl loc{sl::current()}; + std::size_t const first{ + check_cast(outer, "array index"sv, loc)}; static_assert(sizeof...(indexes) < DIMENSIONS); // (Offset by 1 here because the outer dimension is not in there.) constexpr auto dimension{DIMENSIONS - (sizeof...(indexes) + 1)}; @@ -550,6 +574,7 @@ public: * remains valid. Once all `result` objects referring to that data have been * destroyed, the parser will no longer refer to valid memory. */ + [[deprecated("Use pqxx::array instead.")]] explicit array_parser( std::string_view input, internal::encoding_group = internal::encoding_group::MONOBYTE); @@ -561,7 +586,10 @@ public: * Call this until the @ref array_parser::juncture it returns is * @ref juncture::done. */ - std::pair get_next() { return (this->*m_impl)(); } + std::pair get_next(sl loc = sl::current()) + { + return (this->*m_impl)(loc); + } private: std::string_view m_input; @@ -575,33 +603,36 @@ private: * compiler to inline the parsing of each text encoding, which happens in * very hot loops. */ - using implementation = std::pair (array_parser::*)(); + using implementation = + std::pair (array_parser::*)(sl); /// Pick the `implementation` for `enc`. static implementation - specialize_for_encoding(pqxx::internal::encoding_group enc); + specialize_for_encoding(pqxx::internal::encoding_group enc, sl loc); /// Our implementation of `parse_array_step`, specialised for our encoding. implementation m_impl; /// Perform one step of array parsing. template - std::pair parse_array_step(); + std::pair parse_array_step(sl loc); template - std::string::size_type scan_double_quoted_string() const; + std::string::size_type scan_double_quoted_string(sl loc) const; template - std::string parse_double_quoted_string(std::string::size_type end) const; + std::string + parse_double_quoted_string(std::string::size_type end, sl loc) const; template - std::string::size_type scan_unquoted_string() const; + std::string::size_type scan_unquoted_string(sl loc) const; template - std::string_view parse_unquoted_string(std::string::size_type end) const; + std::string_view + parse_unquoted_string(std::string::size_type end, sl loc) const; template - std::string::size_type scan_glyph(std::string::size_type pos) const; + std::string::size_type scan_glyph(std::string::size_type pos, sl loc) const; template - std::string::size_type - scan_glyph(std::string::size_type pos, std::string::size_type end) const; + std::string::size_type scan_glyph( + std::string::size_type pos, std::string::size_type end, sl loc) const; }; } // namespace pqxx #endif diff --git a/include/pqxx/blob.hxx b/include/pqxx/blob.hxx index dcd504934..7a0833230 100644 --- a/include/pqxx/blob.hxx +++ b/include/pqxx/blob.hxx @@ -48,17 +48,18 @@ public: * the new object will have that oid -- or creation will fail if there * already is an object with that oid. */ - [[nodiscard]] static oid create(dbtransaction &, oid = 0); + [[nodiscard]] static oid + create(dbtransaction &, oid = 0, sl = sl::current()); /// Delete a large object, or fail if it does not exist. - static void remove(dbtransaction &, oid); + static void remove(dbtransaction &, oid, sl = sl::current()); /// Open blob for reading. Any attempt to write to it will fail. - [[nodiscard]] static blob open_r(dbtransaction &, oid); + [[nodiscard]] static blob open_r(dbtransaction &, oid, sl = sl::current()); // Open blob for writing. Any attempt to read from it will fail. - [[nodiscard]] static blob open_w(dbtransaction &, oid); + [[nodiscard]] static blob open_w(dbtransaction &, oid, sl = sl::current()); // Open blob for reading and/or writing. - [[nodiscard]] static blob open_rw(dbtransaction &, oid); + [[nodiscard]] static blob open_rw(dbtransaction &, oid, sl = sl::current()); /// You can default-construct a blob, but it won't do anything useful. /** Most operations on a default-constructed blob will throw @ref @@ -94,7 +95,7 @@ public: * @warning The underlying protocol only supports reads up to 2GB at a time. * If you need to read more, try making repeated calls to @ref append_to_buf. */ - std::size_t read(bytes &buf, std::size_t size); + std::size_t read(bytes &buf, std::size_t size, sl = sl::current()); /// Read up to `std::size(buf)` bytes from the object. /** Retrieves bytes from the blob, at the current position, until `buf` is @@ -103,9 +104,10 @@ public: * Returns the filled portion of `buf`. This may be empty. */ template - writable_bytes_view read(std::span buf) + writable_bytes_view + read(std::span buf, sl loc = sl::current()) { - return buf.subspan(0, raw_read(std::data(buf), std::size(buf))); + return buf.subspan(0, raw_read(std::data(buf), std::size(buf), loc)); } /// Read up to `std::size(buf)` bytes from the object. @@ -114,9 +116,10 @@ public: * * Returns the filled portion of `buf`. This may be empty. */ - template writable_bytes_view read(DATA &buf) + template + writable_bytes_view read(DATA &buf, sl loc = sl::current()) { - return {std::data(buf), raw_read(std::data(buf), std::size(buf))}; + return {std::data(buf), raw_read(std::data(buf), std::size(buf), loc)}; } /// Write `data` to large object, at the current position. @@ -138,9 +141,9 @@ public: * time. If you need to write more, try making repeated calls to * @ref append_from_buf. */ - template void write(DATA const &data) + template void write(DATA const &data, sl loc = sl::current()) { - return raw_write(binary_cast(data)); + return raw_write(binary_cast(data), loc); } /// Resize large object to `size` bytes. @@ -150,72 +153,78 @@ public: * If the blob is less than `size` bytes long, it adds enough zero bytes to * make it the desired length. */ - void resize(std::int64_t size); + void resize(std::int64_t size, sl = sl::current()); /// Return the current reading/writing position in the large object. - [[nodiscard]] std::int64_t tell() const; + [[nodiscard]] std::int64_t tell(sl = sl::current()) const; /// Set the current reading/writing position to an absolute offset. /** Returns the new file offset. */ - std::int64_t seek_abs(std::int64_t offset = 0); + std::int64_t seek_abs(std::int64_t offset = 0, sl = sl::current()); /// Move the current reading/writing position forwards by an offset. /** To move backwards, pass a negative offset. * * Returns the new file offset. */ - std::int64_t seek_rel(std::int64_t offset = 0); + std::int64_t seek_rel(std::int64_t offset = 0, sl = sl::current()); /// Set the current position to an offset relative to the end of the blob. /** You'll probably want an offset of zero or less. * * Returns the new file offset. */ - std::int64_t seek_end(std::int64_t offset = 0); + std::int64_t seek_end(std::int64_t offset = 0, sl = sl::current()); /// Create a binary large object containing given `data`. /** You may optionally specify an oid for the new object. If you do, and an * object with that oid already exists, creation will fail. */ - static oid from_buf(dbtransaction &tx, bytes_view data, oid id = 0); + static oid + from_buf(dbtransaction &tx, bytes_view data, oid id = 0, sl = sl::current()); /// Create a binary large object containing given `data`. /** You may optionally specify an oid for the new object. If you do, and an * object with that oid already exists, creation will fail. */ template - static oid from_buf(dbtransaction &tx, DATA data, oid id = 0) + static oid + from_buf(dbtransaction &tx, DATA data, oid id = 0, sl loc = sl::current()) { - return from_buf(tx, binary_cast(data), id); + return from_buf(tx, binary_cast(data), id, loc); } /// Append `data` to binary large object. /** The underlying protocol only supports appending blocks up to 2 GB. */ - static void append_from_buf(dbtransaction &tx, bytes_view data, oid id); + static void append_from_buf( + dbtransaction &tx, bytes_view data, oid id, sl = sl::current()); /// Append `data` to binary large object. /** The underlying protocol only supports appending blocks up to 2 GB. */ template - static void append_from_buf(dbtransaction &tx, DATA data, oid id) + static void + append_from_buf(dbtransaction &tx, DATA data, oid id, sl loc = sl::current()) { - append_from_buf(tx, binary_cast(data), id); + append_from_buf(tx, binary_cast(data), id, loc); } /// Read client-side file and store it server-side as a binary large object. - [[nodiscard]] static oid from_file(dbtransaction &, zview path); + [[nodiscard]] static oid + from_file(dbtransaction &, zview path, sl = sl::current()); /// Read client-side file and store it server-side as a binary large object. /** In this version, you specify the binary large object's oid. If that oid * is already in use, the operation will fail. */ - static oid from_file(dbtransaction &, zview path, oid); + static oid from_file(dbtransaction &, zview path, oid, sl = sl::current()); // XXX: Can we build a generic version of this? /// Convenience function: Read up to `max_size` bytes from blob with `id`. /** You could easily do this yourself using the @ref open_r and @ref read * functions, but it can save you a bit of code to do it this way. */ - static void to_buf(dbtransaction &, oid, bytes &, std::size_t max_size); + static void to_buf( + dbtransaction &, oid, bytes &, std::size_t max_size, sl = sl::current()); // XXX: Can we build a generic version of this? /// Read part of the binary large object with `id`, and append it to `buf`. @@ -227,10 +236,10 @@ public: */ static std::size_t append_to_buf( dbtransaction &tx, oid id, std::int64_t offset, bytes &buf, - std::size_t append_max); + std::size_t append_max, sl = sl::current()); /// Write a binary large object's contents to a client-side file. - static void to_file(dbtransaction &, oid, zview path); + static void to_file(dbtransaction &, oid, zview path, sl = sl::current()); /// Close this blob. /** This does not delete the blob from the database; it only terminates your @@ -248,7 +257,7 @@ public: private: PQXX_PRIVATE blob(connection &cx, int fd) noexcept : m_conn{&cx}, m_fd{fd} {} - static PQXX_PRIVATE blob open_internal(dbtransaction &, oid, int); + static PQXX_PRIVATE blob open_internal(dbtransaction &, oid, int, sl); static PQXX_PRIVATE pqxx::internal::pq::PGconn * raw_conn(pqxx::connection *) noexcept; static PQXX_PRIVATE pqxx::internal::pq::PGconn * @@ -259,9 +268,9 @@ private: return errmsg(&tx.conn()); } PQXX_PRIVATE std::string errmsg() const { return errmsg(m_conn); } - PQXX_PRIVATE std::int64_t seek(std::int64_t offset, int whence); - std::size_t raw_read(std::byte buf[], std::size_t size); - void raw_write(bytes_view); + PQXX_PRIVATE std::int64_t seek(std::int64_t offset, int whence, sl); + std::size_t raw_read(std::byte buf[], std::size_t size, sl); + void raw_write(bytes_view, sl); connection *m_conn = nullptr; int m_fd = -1; diff --git a/include/pqxx/composite.hxx b/include/pqxx/composite.hxx index 73702a23f..821f2e61f 100644 --- a/include/pqxx/composite.hxx +++ b/include/pqxx/composite.hxx @@ -11,6 +11,7 @@ namespace pqxx { +// TODO: How can we pass std::source_location here? /// Parse a string representation of a value of a composite type. /** @warning This code is still experimental. Use with care. * @@ -36,36 +37,44 @@ inline void parse_composite( pqxx::internal::encoding_group enc, std::string_view text, T &...fields) { static_assert(sizeof...(fields) > 0); + // TODO: Turn this into a parameter. + auto const loc{sl::current()}; - auto const scan{pqxx::internal::get_glyph_scanner(enc)}; + auto const scan{pqxx::internal::get_glyph_scanner(enc, loc)}; auto const data{std::data(text)}; auto const size{std::size(text)}; if (size == 0) - throw conversion_error{"Cannot parse composite value from empty string."}; + throw conversion_error{ + "Cannot parse composite value from empty string.", loc}; - std::size_t here{0}, next{scan(data, size, here)}; + std::size_t here{0}, next{scan(data, size, here, loc)}; if (next != 1 or data[here] != '(') throw conversion_error{ - internal::concat("Invalid composite value string: ", text)}; + internal::concat("Invalid composite value string: ", text), loc}; here = next; // TODO: Reuse parse_composite_field specialisation across calls. constexpr auto num_fields{sizeof...(fields)}; std::size_t index{0}; - (pqxx::internal::specialize_parse_composite_field(enc)( - index, text, here, fields, num_fields - 1), + (pqxx::internal::specialize_parse_composite_field(enc, loc)( + index, text, here, fields, num_fields - 1, loc), ...); if (here != std::size(text)) - throw conversion_error{internal::concat( - "Composite value did not end at the closing parenthesis: '", text, - "'.")}; + throw conversion_error{ + internal::concat( + "Composite value did not end at the closing parenthesis: '", text, + "'."), + loc}; if (text[here - 1] != ')') - throw conversion_error{internal::concat( - "Composive value did not end in parenthesis: '", text, "'")}; + throw conversion_error{ + internal::concat( + "Composite value did not end in parenthesis: '", text, "'"), + loc}; } +// TODO: How can we pass std::source_location here? /// Parse a string representation of a value of a composite type. /** @warning This version only works for UTF-8 and single-byte encodings. * @@ -113,6 +122,7 @@ composite_size_buffer(T const &...fields) noexcept } +// TODO: How can we pass std::source_location here? /// Render a series of values as a single composite SQL value. /** @warning This code is still experimental. Use with care. * @@ -122,9 +132,10 @@ composite_size_buffer(T const &...fields) noexcept template inline char *composite_into_buf(char *begin, char *end, T const &...fields) { + auto loc{sl::current()}; if (std::size_t(end - begin) < composite_size_buffer(fields...)) throw conversion_error{ - "Buffer space may not be enough to represent composite value."}; + "Buffer space may not be enough to represent composite value.", loc}; constexpr auto num_fields{sizeof...(fields)}; if constexpr (num_fields == 0) diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index bfd8a454b..35d15bbb1 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -273,17 +273,18 @@ enum class error_verbosity : int class PQXX_LIBEXPORT connection { public: - connection() : connection{""} {} + connection(sl loc = sl::current()) : connection{"", loc} {} /// Connect to a database, using `options` string. - explicit connection(char const options[]) + explicit connection(char const options[], sl loc = sl::current()) { check_version(); - init(options); + init(options, loc); } /// Connect to a database, using `options` string. - explicit connection(zview options) : connection{options.c_str()} + explicit connection(zview options, sl loc = sl::current()) : + connection{options.c_str(), loc} { // (Delegates to other constructor which calls check_version for us.) } @@ -294,7 +295,7 @@ public: * other objects may hold references to the old object which would become * invalid and might produce hard-to-diagnose bugs. */ - connection(connection &&rhs); + connection(connection &&rhs, sl = sl::current()); /// Connect to a database, passing options as a range of key/value pairs. /** There's no need to escape the parameter values. @@ -310,13 +311,15 @@ public: * `std::map`, and so on. */ template - inline connection(MAPPING const ¶ms); + inline connection(MAPPING const ¶ms, sl = sl::current()); ~connection() { + // TODO: How can we pass std::source_location? + sl loc{sl::current()}; try { - close(); + close(loc); } catch (std::exception const &) {} @@ -363,20 +366,20 @@ public: //@{ /// Name of the database to which we're connected, if any. /** Returns nullptr when not connected. */ - [[nodiscard]] char const *dbname() const; + [[nodiscard]] char const *dbname() const noexcept; /// Database user ID under which we are connected, if any. /** Returns nullptr when not connected. */ - [[nodiscard]] char const *username() const; + [[nodiscard]] char const *username() const noexcept; /// Database server address, if given. /** This may be an IP address, or a hostname, or (for a Unix domain socket) * a socket path. Returns nullptr when not connected. */ - [[nodiscard]] char const *hostname() const; + [[nodiscard]] char const *hostname() const noexcept; /// Server port number on which we are connected to the database. - [[nodiscard]] char const *port() const; + [[nodiscard]] char const *port() const noexcept; /// Process ID for backend process, or 0 if inactive. [[nodiscard]] int PQXX_PURE backendpid() const & noexcept; @@ -438,25 +441,25 @@ public: */ //@{ /// Get client-side character encoding, by name. - [[nodiscard]] std::string get_client_encoding() const; + [[nodiscard]] std::string get_client_encoding(sl loc = sl::current()) const; /// Set client-side character encoding, by name. /** * @param encoding Name of the character set encoding to use. */ - void set_client_encoding(zview encoding) & + void set_client_encoding(zview encoding, sl loc = sl::current()) & { - set_client_encoding(encoding.c_str()); + set_client_encoding(encoding.c_str(), loc); } /// Set client-side character encoding, by name. /** * @param encoding Name of the character set encoding to use. */ - void set_client_encoding(char const encoding[]) &; + void set_client_encoding(char const encoding[], sl = sl::current()) &; /// Get the connection's encoding, as a PostgreSQL-defined code. - [[nodiscard]] int encoding_id() const; + [[nodiscard]] int encoding_id(sl = sl::current()) const; //@} @@ -483,15 +486,18 @@ public: * allowed. */ template - void set_session_var(std::string_view var, TYPE const &value) & + void set_session_var( + std::string_view var, TYPE const &value, sl loc = sl::current()) & { if constexpr (nullness::has_null) { if (nullness::is_null(value)) throw variable_set_to_null{ - internal::concat("Attempted to set variable ", var, " to null.")}; + internal::concat("Attempted to set variable ", var, " to null."), + loc}; } - exec(internal::concat("SET ", quote_name(var), "=", quote(value))); + exec( + internal::concat("SET ", quote_name(var), "=", quote(value, loc)), loc); } /// Read currently applicable value of a variable. @@ -501,7 +507,7 @@ public: * @return a blank `std::optional` if the variable's value is null, or its * string value otherwise. */ - std::string get_var(std::string_view var); + std::string get_var(std::string_view var, sl loc = sl::current()); /// Read currently applicable value of a variable. /** This function executes an SQL statement, so it won't work while a @@ -510,9 +516,10 @@ public: * If there is any possibility that the variable is null, ensure that `TYPE` * can represent null values. */ - template TYPE get_var_as(std::string_view var) + template + TYPE get_var_as(std::string_view var, sl loc = sl::current()) { - return from_string(get_var(var)); + return from_string(get_var(var, loc)); } /** @@ -623,7 +630,7 @@ public: * * @return Number of notifications processed. */ - int get_notifs(); + int get_notifs(sl = sl::current()); /// Wait for a notification to come in. /** There are other events that will also cancel the wait, such as the @@ -640,7 +647,7 @@ public: * * @return Number of notifications processed. */ - int await_notification(); + int await_notification(sl = sl::current()); /// Wait for a notification to come in, or for given timeout to pass. /** There are other events that will also cancel the wait, such as the @@ -659,7 +666,8 @@ public: * * @return Number of notifications processed. */ - int await_notification(std::time_t seconds, long microseconds = 0); + int await_notification( + std::time_t seconds, long microseconds = 0, sl = sl::current()); /// A handler callback for incoming notifications on a given channel. /** Your callback must accept a @ref notification object. This object can @@ -693,7 +701,9 @@ public: * different handlers on the same channel, then the second overwrites the * first. */ - void listen(std::string_view channel, notification_handler handler = {}); + void listen( + std::string_view channel, notification_handler handler = {}, + sl = sl::current()); //@} @@ -786,16 +796,17 @@ public: * @param name unique name for the new prepared statement. * @param definition SQL statement to prepare. */ - void prepare(zview name, zview definition) & + void prepare(zview name, zview definition, sl loc = sl::current()) & { - prepare(name.c_str(), definition.c_str()); + prepare(name.c_str(), definition.c_str(), loc); } /** * @param name unique name for the new prepared statement. * @param definition SQL statement to prepare. */ - void prepare(char const name[], char const definition[]) &; + void prepare( + char const name[], char const definition[], sl loc = sl::current()) &; /// Define a nameless prepared statement. /** @@ -805,11 +816,14 @@ public: * the nameless statement being redefined unexpectedly by code somewhere * else. */ - void prepare(char const definition[]) &; - void prepare(zview definition) & { return prepare(definition.c_str()); } + void prepare(char const definition[], sl loc = sl::current()) &; + void prepare(zview definition, sl loc = sl::current()) & + { + return prepare(definition.c_str(), loc); + } /// Drop prepared statement. - void unprepare(std::string_view name); + void unprepare(std::string_view name, sl loc = sl::current()); //@} @@ -825,9 +839,10 @@ public: //@{ /// Escape string for use as SQL string literal on this connection. - [[nodiscard]] std::string esc(char const text[]) const + [[nodiscard]] std::string + esc(char const text[], sl loc = sl::current()) const { - return esc(std::string_view{text}); + return esc(std::string_view{text}, loc); } /// Escape string for use as SQL string literal, into `buffer`. @@ -843,23 +858,26 @@ public: * `buffer`. */ [[nodiscard]] std::string_view - esc(std::string_view text, std::span buffer) + esc(std::string_view text, std::span buffer, sl loc = sl::current()) { auto const size{std::size(text)}, space{std::size(buffer)}; auto const needed{2 * size + 1}; if (space < needed) - throw range_error{internal::concat( - "Not enough room to escape string of ", size, " byte(s): need ", - needed, " bytes of buffer space, but buffer size is ", space, ".")}; + throw range_error{ + internal::concat( + "Not enough room to escape string of ", size, " byte(s): need ", + needed, " bytes of buffer space, but buffer size is ", space, "."), + loc}; auto const data{buffer.data()}; - return {data, esc_to_buf(text, data)}; + return {data, esc_to_buf(text, data, loc)}; } /// Escape string for use as SQL string literal on this connection. /** @warning This is meant for text strings only. It cannot contain bytes * whose value is zero ("nul bytes"). */ - [[nodiscard]] std::string esc(std::string_view text) const; + [[nodiscard]] std::string + esc(std::string_view text, sl loc = sl::current()) const; /// Escape binary string for use as SQL string literal on this connection. /** This is identical to `esc_raw(data)`. */ @@ -930,11 +948,12 @@ public: * "bytea" escape format, used prior to PostgreSQL 9.0, is no longer * supported.) */ - [[nodiscard]] bytes unesc_bin(std::string_view text) const + [[nodiscard]] bytes + unesc_bin(std::string_view text, sl loc = sl::current()) const { bytes buf; buf.resize(pqxx::internal::size_unesc_bin(std::size(text))); - pqxx::internal::unesc_bin(text, buf.data()); + pqxx::internal::unesc_bin(text, buf.data(), loc); return buf; } @@ -991,7 +1010,7 @@ public: * Recognises nulls and represents them as SQL nulls. They get no quotes. */ template - [[nodiscard]] inline std::string quote(T const &t) const; + [[nodiscard]] inline std::string quote(T const &t, sl = sl::current()) const; // TODO: Make "into buffer" variant to eliminate a string allocation. /// Escape string for literal LIKE match. @@ -1020,8 +1039,9 @@ public: * The SQL "LIKE" operator also lets you choose your own escape character. * This is supported, but must be a single-byte character. */ - [[nodiscard]] std::string - esc_like(std::string_view text, char escape_char = '\\') const; + [[nodiscard]] std::string esc_like( + std::string_view text, char escape_char = '\\', + sl loc = sl::current()) const; //@} /// Attempt to cancel the ongoing query, if any. @@ -1029,7 +1049,7 @@ public: * in a pipeline, but it's up to you to ensure that you're not canceling the * wrong query. This may involve locking. */ - void cancel_query(); + void cancel_query(sl = sl::current()); #if defined(_WIN32) || __has_include() /// Set socket to blocking (true) or nonblocking (false). @@ -1037,7 +1057,7 @@ public: * @warning This function is available on most systems, but not necessarily * all. */ - void set_blocking(bool block) &; + void set_blocking(bool block, sl = sl::current()) &; #endif // defined(_WIN32) || __has_include() /// Set session verbosity. @@ -1100,7 +1120,7 @@ public: * Closing a connection is idempotent. Closing a connection that's already * closed does nothing. */ - void close(); + void close(sl = sl::current()); /// Seize control of a raw libpq connection. /** @warning Do not do this. Please. It's for very rare, very specific @@ -1140,14 +1160,15 @@ public: * a string, be sure that it's properly escaped and quoted. */ [[deprecated("To set session variables, use set_session_var.")]] void - set_variable(std::string_view var, std::string_view value) &; + set_variable( + std::string_view var, std::string_view value, sl loc = sl::current()) &; /// Read session variable, using SQL's `SHOW` command. /** @warning This executes an SQL query, so do not get or set variables while * a table stream or pipeline is active on the same connection. */ [[deprecated("Use get_var instead.")]] std::string - get_variable(std::string_view); + get_variable(std::string_view, sl loc = sl::current()); private: friend class connecting; @@ -1155,7 +1176,7 @@ private: { connect_nonblocking }; - connection(connect_mode, zview connection_string); + connection(connect_mode, zview connection_string, sl); /// For use by @ref seize_raw_connection. explicit connection(internal::pq::PGconn *raw_conn); @@ -1166,20 +1187,27 @@ private: * * Throws an exception if polling indicates that the connection has failed. */ - std::pair poll_connect(); + std::pair poll_connect(sl); // Initialise based on connection string. - void init(char const options[]); + void init(char const options[], sl); // Initialise based on parameter names and values. - void init(char const *params[], char const *values[]); + void init(char const *params[], char const *values[], sl); void set_up_notice_handlers(); - void complete_init(); + void complete_init(sl); result make_result( internal::pq::PGresult *pgr, std::shared_ptr const &query, - std::string_view desc = ""sv); + std::string_view desc, sl = sl::current()); - void PQXX_PRIVATE set_up_state(); + result make_result( + internal::pq::PGresult *pgr, std::shared_ptr const &query, + sl loc = sl::current()) + { + return make_result(pgr, query, "", loc); + } + + void PQXX_PRIVATE set_up_state(sl); int PQXX_PRIVATE PQXX_PURE status() const noexcept; @@ -1188,26 +1216,34 @@ private: * * Returns the number of bytes written, including the trailing zero. */ - std::size_t esc_to_buf(std::string_view text, char *buf) const; + std::size_t esc_to_buf(std::string_view text, char *buf, sl loc) const; friend class internal::gate::const_connection_largeobject; char const *PQXX_PURE err_msg() const noexcept; - result exec_prepared(std::string_view statement, internal::c_params const &); + result exec_prepared( + std::string_view statement, internal::c_params const &, + sl loc = sl::current()); /// Throw @ref usage_error if this connection is not in a movable state. - void check_movable() const; + void check_movable(sl) const; /// Throw @ref usage_error if not in a state where it can be move-assigned. - void check_overwritable() const; + void check_overwritable(sl) const; friend class internal::gate::connection_errorhandler; void PQXX_PRIVATE register_errorhandler(errorhandler *); void PQXX_PRIVATE unregister_errorhandler(errorhandler *) noexcept; friend class internal::gate::connection_transaction; - result exec(std::string_view, std::string_view = ""sv); + result exec(std::string_view query, sl loc) { return exec(query, "", loc); } + result exec(std::string_view, std::string_view, sl); result PQXX_PRIVATE - exec(std::shared_ptr const &, std::string_view = ""sv); + exec(std::shared_ptr const &, std::string_view, sl); + result PQXX_PRIVATE exec(std::shared_ptr const &query, sl loc) + { + return exec(query, "", loc); + } + void PQXX_PRIVATE register_transaction(transaction_base *); void PQXX_PRIVATE unregister_transaction(transaction_base *) noexcept; @@ -1222,7 +1258,7 @@ private: read_copy_line(); friend class internal::gate::connection_stream_to; - void PQXX_PRIVATE write_copy_line(std::string_view); + void PQXX_PRIVATE write_copy_line(std::string_view, sl); void PQXX_PRIVATE end_copy_write(); friend class internal::gate::connection_largeobject; @@ -1230,7 +1266,7 @@ private: friend class internal::gate::connection_notification_receiver; void add_receiver(notification_receiver *); - void remove_receiver(notification_receiver *) noexcept; + void remove_receiver(notification_receiver *, sl loc) noexcept; friend class internal::gate::connection_pipeline; void PQXX_PRIVATE start_exec(char const query[]); @@ -1241,7 +1277,8 @@ private: friend class internal::gate::connection_dbtransaction; friend class internal::gate::connection_sql_cursor; - result exec_params(std::string_view query, internal::c_params const &args); + result + exec_params(std::string_view query, internal::c_params const &args, sl); /// Connection handle. internal::pq::PGconn *m_conn = nullptr; @@ -1329,7 +1366,7 @@ class PQXX_LIBEXPORT connecting { public: /// Start connecting. - connecting(zview connection_string = ""_zv); + connecting(zview connection_string = ""_zv, sl = sl::current()); connecting(connecting const &) = delete; connecting(connecting &&) = default; @@ -1352,7 +1389,7 @@ public: } /// Progress towards completion (but don't block). - void process() &; + void process(sl loc = sl::current()) &; /// Is our connection finished? [[nodiscard]] constexpr bool done() const & noexcept @@ -1369,7 +1406,7 @@ public: * it on an rvalue instance of the class. If what you have is not an rvalue, * turn it into one by wrapping it in `std::move()`. */ - [[nodiscard]] connection produce() &&; + [[nodiscard]] connection produce(sl = sl::current()) &&; private: connection m_conn; @@ -1378,7 +1415,8 @@ private: }; -template inline std::string connection::quote(T const &t) const +template +inline std::string connection::quote(T const &t, sl loc) const { if (is_null(t)) { @@ -1403,7 +1441,7 @@ template inline std::string connection::quote(T const &t) const // incur some unnecessary memory allocations and deallocations. std::string buf{'\''}; buf.resize(2 + 2 * std::size(text) + 1); - auto const content_bytes{esc_to_buf(text, buf.data() + 1)}; + auto const content_bytes{esc_to_buf(text, buf.data() + 1, loc)}; auto const closing_quote{1 + content_bytes}; buf[closing_quote] = '\''; auto const end{closing_quote + 1}; @@ -1423,7 +1461,7 @@ inline std::string connection::quote_columns(STRINGS const &columns) const template -inline connection::connection(MAPPING const ¶ms) +inline connection::connection(MAPPING const ¶ms, sl loc) { check_version(); @@ -1441,7 +1479,7 @@ inline connection::connection(MAPPING const ¶ms) } keys.push_back(nullptr); values.push_back(nullptr); - init(std::data(keys), std::data(values)); + init(std::data(keys), std::data(values), loc); } } // namespace pqxx #endif diff --git a/include/pqxx/cursor.hxx b/include/pqxx/cursor.hxx index 0ff0f28a7..cb2ccfce8 100644 --- a/include/pqxx/cursor.hxx +++ b/include/pqxx/cursor.hxx @@ -182,8 +182,9 @@ public: */ stateless_cursor( transaction_base &tx, std::string_view query, std::string_view cname, - bool hold) : - m_cur{tx, query, cname, cursor_base::random_access, up, op, hold} + bool hold, sl loc = sl::current()) : + m_cur{tx, query, cname, cursor_base::random_access, + up, op, hold, loc} {} /// Adopt an existing scrolling SQL cursor. @@ -193,11 +194,13 @@ public: * @param tx The transaction within which you want to manage the cursor. * @param adopted_cursor Your cursor's SQL name. */ - stateless_cursor(transaction_base &tx, std::string_view adopted_cursor) : + stateless_cursor( + transaction_base &tx, std::string_view adopted_cursor, + sl loc = sl::current()) : m_cur{tx, adopted_cursor, op} { // Put cursor in known position - m_cur.move(cursor_base::backward_all()); + m_cur.move(cursor_base::backward_all(), loc); } /// Close this cursor. @@ -206,15 +209,15 @@ public: * Closing a cursor is idempotent. Closing a cursor that's already closed * does nothing. */ - void close() noexcept { m_cur.close(); } + void close(sl loc = sl::current()) noexcept { m_cur.close(loc); } /// Number of rows in cursor's result set /** @note This function is not const; it may need to scroll to find the size * of the result set. */ - [[nodiscard]] size_type size() + [[nodiscard]] size_type size(sl loc = sl::current()) { - return internal::obtain_stateless_cursor_size(m_cur); + return internal::obtain_stateless_cursor_size(m_cur, loc); } /// Retrieve rows from begin_pos (inclusive) to end_pos (exclusive) @@ -229,10 +232,11 @@ public: * arbitrarily inside or outside the result set; only existing rows are * included in the result. */ - result retrieve(difference_type begin_pos, difference_type end_pos) + result retrieve( + difference_type begin_pos, difference_type end_pos, sl loc = sl::current()) { return internal::stateless_cursor_retrieve( - m_cur, result::difference_type(size()), begin_pos, end_pos); + m_cur, result::difference_type(size()), begin_pos, end_pos, loc); } /// Return this cursor's name. @@ -295,7 +299,8 @@ public: */ icursorstream( transaction_base &context, std::string_view query, - std::string_view basename, difference_type sstride = 1); + std::string_view basename, difference_type sstride = 1, + sl = sl::current()); /// Adopt existing SQL cursor. Use with care. /** Forms a cursor stream around an existing SQL cursor, as returned by e.g. @@ -324,7 +329,7 @@ public: */ icursorstream( transaction_base &context, field const &cname, difference_type sstride = 1, - cursor_base::ownership_policy op = cursor_base::owned); + cursor_base::ownership_policy op = cursor_base::owned, sl = sl::current()); /// Return `true` if this stream may still return more data. constexpr operator bool() const & noexcept { return not m_done; } @@ -338,9 +343,9 @@ public: * @return Reference to this very stream, to facilitate "chained" invocations * ("C.get(r1).get(r2);") */ - icursorstream &get(result &res) + icursorstream &get(result &res, sl loc = sl::current()) { - res = fetchblock(); + res = fetchblock(loc); return *this; } /// Read new value into given result object; same as `get(result&)`. @@ -361,27 +366,27 @@ public: * @return Reference to this stream itself, to facilitate "chained" * invocations. */ - icursorstream &ignore(std::streamsize n = 1) &; + icursorstream &ignore(std::streamsize n = 1, sl = sl::current()) &; /// Change stride, i.e. the number of rows to fetch per read operation. /** * @param stride Must be a positive number. */ - void set_stride(difference_type stride) &; + void set_stride(difference_type stride, sl = sl::current()) &; [[nodiscard]] constexpr difference_type stride() const noexcept { return m_stride; } private: - result fetchblock(); + result fetchblock(sl); friend class internal::gate::icursorstream_icursor_iterator; size_type forward(size_type n = 1); void insert_iterator(icursor_iterator *) noexcept; void remove_iterator(icursor_iterator *) const noexcept; - void service_iterators(difference_type); + void service_iterators(difference_type, sl); internal::sql_cursor m_cur; @@ -439,12 +444,16 @@ public: result const &operator*() const { - refresh(); + // TODO: How can we pass std::source_location here? + auto loc{sl::current()}; + refresh(loc); return m_here; } result const *operator->() const { - refresh(); + // TODO: How can we pass std::source_location here? + auto loc{sl::current()}; + refresh(loc); return &m_here; } icursor_iterator &operator++(); @@ -472,7 +481,7 @@ public: } private: - void refresh() const; + void refresh(sl) const; friend class internal::gate::icursor_iterator_icursorstream; difference_type pos() const noexcept { return m_pos; } diff --git a/include/pqxx/except.hxx b/include/pqxx/except.hxx index 63cad9af9..cc08e5e94 100644 --- a/include/pqxx/except.hxx +++ b/include/pqxx/except.hxx @@ -17,10 +17,11 @@ # error "Include libpqxx headers as , not ." #endif -#include #include #include +#include "pqxx/types.hxx" + namespace pqxx { @@ -43,10 +44,10 @@ namespace pqxx /// Run-time failure encountered by libpqxx, similar to std::runtime_error. struct PQXX_LIBEXPORT failure : std::runtime_error { - explicit failure( - std::string const &, - std::source_location = std::source_location::current()); - std::source_location location; + explicit failure(std::string const &, sl = sl::current()); + + /// A `std::source_location` as a hint to the origin of the problem. + sl location; }; @@ -72,10 +73,8 @@ struct PQXX_LIBEXPORT failure : std::runtime_error */ struct PQXX_LIBEXPORT broken_connection : failure { - broken_connection(); - explicit broken_connection( - std::string const &, - std::source_location = std::source_location::current()); + broken_connection(sl loc = sl::current()); + explicit broken_connection(std::string const &, sl = sl::current()); }; @@ -90,18 +89,14 @@ struct PQXX_LIBEXPORT broken_connection : failure */ struct PQXX_LIBEXPORT protocol_violation : broken_connection { - explicit protocol_violation( - std::string const &, - std::source_location = std::source_location::current()); + explicit protocol_violation(std::string const &, sl = sl::current()); }; /// The caller attempted to set a variable to null, which is not allowed. struct PQXX_LIBEXPORT variable_set_to_null : failure { - explicit variable_set_to_null( - std::string const &, - std::source_location = std::source_location::current()); + explicit variable_set_to_null(std::string const &, sl = sl::current()); }; @@ -119,8 +114,7 @@ class PQXX_LIBEXPORT sql_error : public failure public: explicit sql_error( std::string const &whatarg = "", std::string Q = "", - char const *sqlstate = nullptr, - std::source_location = std::source_location::current()); + char const *sqlstate = nullptr, sl = sl::current()); virtual ~sql_error() noexcept override; /// The query whose execution triggered the exception @@ -140,9 +134,7 @@ public: */ struct PQXX_LIBEXPORT in_doubt_error : failure { - explicit in_doubt_error( - std::string const &, - std::source_location = std::source_location::current()); + explicit in_doubt_error(std::string const &, sl = sl::current()); }; @@ -151,8 +143,7 @@ struct PQXX_LIBEXPORT transaction_rollback : sql_error { explicit transaction_rollback( std::string const &whatarg, std::string const &q = "", - char const sqlstate[] = nullptr, - std::source_location = std::source_location::current()); + char const sqlstate[] = nullptr, sl = sl::current()); }; @@ -169,8 +160,7 @@ struct PQXX_LIBEXPORT serialization_failure : transaction_rollback { explicit serialization_failure( std::string const &whatarg, std::string const &q, - char const sqlstate[] = nullptr, - std::source_location = std::source_location::current()); + char const sqlstate[] = nullptr, sl = sl::current()); }; @@ -179,8 +169,7 @@ struct PQXX_LIBEXPORT statement_completion_unknown : transaction_rollback { explicit statement_completion_unknown( std::string const &whatarg, std::string const &q, - char const sqlstate[] = nullptr, - std::source_location = std::source_location::current()); + char const sqlstate[] = nullptr, sl = sl::current()); }; @@ -189,86 +178,78 @@ struct PQXX_LIBEXPORT deadlock_detected : transaction_rollback { explicit deadlock_detected( std::string const &whatarg, std::string const &q, - char const sqlstate[] = nullptr, - std::source_location = std::source_location::current()); + char const sqlstate[] = nullptr, sl = sl::current()); }; /// Internal error in libpqxx library struct PQXX_LIBEXPORT internal_error : std::logic_error { - explicit internal_error(std::string const &); + explicit internal_error(std::string const &, sl = sl::current()); + + /// A `std::source_location` as a hint to the origin of the problem. + sl location; }; /// 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 &, - std::source_location = std::source_location::current()); + explicit usage_error(std::string const &, sl = sl::current()); - std::source_location location; + /// A `std::source_location` as a hint to the origin of the problem. + sl location; }; /// Invalid argument passed to libpqxx, similar to std::invalid_argument struct PQXX_LIBEXPORT argument_error : std::invalid_argument { - explicit argument_error( - std::string const &, - std::source_location = std::source_location::current()); + explicit argument_error(std::string const &, sl = sl::current()); - std::source_location location; + /// A `std::source_location` as a hint to the origin of the problem. + sl location; }; /// Value conversion failed, e.g. when converting "Hello" to int. struct PQXX_LIBEXPORT conversion_error : std::domain_error { - explicit conversion_error( - std::string const &, - std::source_location = std::source_location::current()); + explicit conversion_error(std::string const &, sl = sl::current()); - std::source_location location; + /// A `std::source_location` as a hint to the origin of the problem. + sl location; }; /// Could not convert null value: target type does not support null. struct PQXX_LIBEXPORT unexpected_null : conversion_error { - explicit unexpected_null( - std::string const &, - std::source_location = std::source_location::current()); + explicit unexpected_null(std::string const &, sl = sl::current()); }; /// Could not convert value to string: not enough buffer space. struct PQXX_LIBEXPORT conversion_overrun : conversion_error { - explicit conversion_overrun( - std::string const &, - std::source_location = std::source_location::current()); + explicit conversion_overrun(std::string const &, sl = sl::current()); }; /// 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 &, - std::source_location = std::source_location::current()); + explicit range_error(std::string const &, sl = sl::current()); - std::source_location location; + /// A `std::source_location` as a hint to the origin of the problem. + sl location; }; /// Query returned an unexpected number of rows. struct PQXX_LIBEXPORT unexpected_rows : public range_error { - explicit unexpected_rows( - std::string const &msg, - std::source_location loc = std::source_location::current()) : + explicit unexpected_rows(std::string const &msg, sl loc = sl::current()) : range_error{msg, loc} {} }; @@ -279,8 +260,7 @@ struct PQXX_LIBEXPORT feature_not_supported : sql_error { explicit feature_not_supported( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -290,8 +270,7 @@ struct PQXX_LIBEXPORT data_exception : sql_error { explicit data_exception( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -300,8 +279,7 @@ struct PQXX_LIBEXPORT integrity_constraint_violation : sql_error { explicit integrity_constraint_violation( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -310,8 +288,7 @@ struct PQXX_LIBEXPORT restrict_violation : integrity_constraint_violation { explicit restrict_violation( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} }; @@ -320,8 +297,7 @@ struct PQXX_LIBEXPORT not_null_violation : integrity_constraint_violation { explicit not_null_violation( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} }; @@ -330,8 +306,7 @@ struct PQXX_LIBEXPORT foreign_key_violation : integrity_constraint_violation { explicit foreign_key_violation( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} }; @@ -340,8 +315,7 @@ struct PQXX_LIBEXPORT unique_violation : integrity_constraint_violation { explicit unique_violation( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} }; @@ -350,8 +324,7 @@ struct PQXX_LIBEXPORT check_violation : integrity_constraint_violation { explicit check_violation( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} }; @@ -360,8 +333,7 @@ struct PQXX_LIBEXPORT invalid_cursor_state : sql_error { explicit invalid_cursor_state( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -370,8 +342,7 @@ struct PQXX_LIBEXPORT invalid_sql_statement_name : sql_error { 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()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -380,8 +351,7 @@ struct PQXX_LIBEXPORT invalid_cursor_name : sql_error { explicit invalid_cursor_name( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -393,8 +363,7 @@ struct PQXX_LIBEXPORT syntax_error : sql_error 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()) : + char const sqlstate[] = nullptr, int pos = -1, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc}, error_position{pos} {} }; @@ -403,8 +372,7 @@ struct PQXX_LIBEXPORT undefined_column : syntax_error { explicit undefined_column( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : // TODO: Can we get the column? syntax_error{err, Q, sqlstate, -1, loc} {} @@ -414,8 +382,7 @@ struct PQXX_LIBEXPORT undefined_function : syntax_error { explicit undefined_function( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : // TODO: Can we get the column? syntax_error{err, Q, sqlstate, -1, loc} {} @@ -425,8 +392,7 @@ struct PQXX_LIBEXPORT undefined_table : syntax_error { explicit undefined_table( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : // TODO: Can we get the column? syntax_error{err, Q, sqlstate, -1, loc} {} @@ -436,8 +402,7 @@ struct PQXX_LIBEXPORT insufficient_privilege : sql_error { explicit insufficient_privilege( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -447,8 +412,7 @@ struct PQXX_LIBEXPORT insufficient_resources : sql_error { explicit insufficient_resources( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -457,8 +421,7 @@ struct PQXX_LIBEXPORT disk_full : insufficient_resources { explicit disk_full( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : insufficient_resources{err, Q, sqlstate, loc} {} }; @@ -467,8 +430,7 @@ struct PQXX_LIBEXPORT out_of_memory : insufficient_resources { explicit out_of_memory( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : insufficient_resources{err, Q, sqlstate, loc} {} }; @@ -476,8 +438,7 @@ struct PQXX_LIBEXPORT out_of_memory : insufficient_resources struct PQXX_LIBEXPORT too_many_connections : broken_connection { explicit too_many_connections( - std::string const &err, - std::source_location loc = std::source_location::current()) : + std::string const &err, sl loc = sl::current()) : broken_connection{err, loc} {} }; @@ -489,8 +450,7 @@ struct PQXX_LIBEXPORT plpgsql_error : sql_error { explicit plpgsql_error( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -500,8 +460,7 @@ struct PQXX_LIBEXPORT plpgsql_raise : plpgsql_error { explicit plpgsql_raise( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : plpgsql_error{err, Q, sqlstate, loc} {} }; @@ -510,8 +469,7 @@ struct PQXX_LIBEXPORT plpgsql_no_data_found : plpgsql_error { 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()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : plpgsql_error{err, Q, sqlstate, loc} {} }; @@ -520,8 +478,7 @@ struct PQXX_LIBEXPORT plpgsql_too_many_rows : plpgsql_error { 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()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : plpgsql_error{err, Q, sqlstate, loc} {} }; diff --git a/include/pqxx/field.hxx b/include/pqxx/field.hxx index 16fe5a93c..fc7d6a326 100644 --- a/include/pqxx/field.hxx +++ b/include/pqxx/field.hxx @@ -73,19 +73,19 @@ public: */ //@{ /// Column name. - [[nodiscard]] PQXX_PURE char const *name() const &; + [[nodiscard]] PQXX_PURE char const *name(sl = sl::current()) const &; /// Column type. - [[nodiscard]] oid PQXX_PURE type() const; + [[nodiscard]] oid PQXX_PURE type(sl loc = sl::current()) const; /// What table did this column come from? - [[nodiscard]] PQXX_PURE oid table() const; + [[nodiscard]] PQXX_PURE oid table(sl = sl::current()) const; /// Return row number. The first row is row 0, the second is row 1, etc. PQXX_PURE constexpr row_size_type num() const noexcept { return col(); } /// What column number in its originating table did this column come from? - [[nodiscard]] PQXX_PURE row_size_type table_column() const; + [[nodiscard]] PQXX_PURE row_size_type table_column(sl = sl::current()) const; //@} /** @@ -108,9 +108,9 @@ public: * @ref result exists. Once all `result` objects referring to that data have * been destroyed, the `string_view` will no longer point to valid memory. */ - [[nodiscard]] PQXX_PURE std::string_view view() const & + [[nodiscard]] PQXX_PURE std::string_view view() const & noexcept { - return std::string_view(c_str(), size()); + return {c_str(), size()}; } /// Read as plain C string. @@ -123,7 +123,7 @@ public: * convert the value to your desired type using `to()` or `as()`. For * example: `f.as()`. */ - [[nodiscard]] PQXX_PURE char const *c_str() const &; + [[nodiscard]] PQXX_PURE char const *c_str() const & noexcept; /// Is this field's value null? [[nodiscard]] PQXX_PURE bool is_null() const noexcept; @@ -173,7 +173,11 @@ public: } /// Read value into obj; or leave obj untouched and return `false` if null. - template bool operator>>(T &obj) const { return to(obj); } + template + [[deprecated("Use to() or as().")]] bool operator>>(T &obj) const + { + return to(obj); + } /// Read value into obj; or if null, use default value and return `false`. /** This can be used with `std::optional`, as well as with standard smart @@ -218,12 +222,12 @@ public: * (other than C-strings) because storage for the value can't safely be * allocated here */ - template T as() const + template T as(sl loc = sl::current()) const { if (is_null()) { if constexpr (not nullness::has_null) - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); else return nullness::null(); } @@ -245,15 +249,15 @@ public: /// Read SQL array contents as a @ref pqxx::array. template - array as_sql_array() const + array as_sql_array(sl loc = sl::current()) const { using array_type = array; // There's no such thing as a null SQL array. if (is_null()) - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); else - return array_type{this->view(), this->m_home.m_encoding}; + return array_type{this->view(), this->m_home.m_encoding, loc}; } /// Parse the field as an SQL array. @@ -268,7 +272,9 @@ public: "Instead, use as_sql_array() to convert to pqxx::array.")]] array_parser as_array() const & noexcept { +#include "pqxx/internal/ignore-deprecated-pre.hxx" return array_parser{c_str(), m_home.m_encoding}; +#include "pqxx/internal/ignore-deprecated-post.hxx" } //@} @@ -372,10 +378,10 @@ inline bool field::to(zview &obj, zview const &default_value) const } -template<> inline zview field::as() const +template<> inline zview field::as(sl loc) const { if (is_null()) - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); return zview{c_str(), size()}; } @@ -507,14 +513,15 @@ template /** Unlike the "regular" `from_string`, this knows how to deal with null * values. */ -template inline T from_string(field const &value) +template +inline T from_string(field const &value, sl loc = sl::current()) { if (value.is_null()) { if constexpr (nullness::has_null) return nullness::null(); else - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); } else { @@ -523,6 +530,8 @@ template inline T from_string(field const &value) } +// TODO: Can we make this generic across all "always-null" types? +// TODO: Do the same for streams. /// Convert a field's value to `nullptr_t`. /** Yes, you read that right. This conversion does nothing useful. It always * returns `nullptr`. @@ -531,11 +540,11 @@ template inline T from_string(field const &value) * @ref conversion_error. */ template<> -inline std::nullptr_t from_string(field const &value) +inline std::nullptr_t from_string(field const &value, sl loc) { if (not value.is_null()) throw conversion_error{ - "Extracting non-null field into nullptr_t variable."}; + "Extracting non-null field into nullptr_t variable.", loc}; return nullptr; } diff --git a/include/pqxx/internal/array-composite.hxx b/include/pqxx/internal/array-composite.hxx index 689dd7300..8e4747975 100644 --- a/include/pqxx/internal/array-composite.hxx +++ b/include/pqxx/internal/array-composite.hxx @@ -18,15 +18,15 @@ namespace pqxx::internal */ template inline std::size_t scan_double_quoted_string( - char const input[], std::size_t size, std::size_t pos) + char const input[], std::size_t size, std::size_t pos, sl loc) { // TODO: find_char<'"', '\\'>(). using scanner = glyph_scanner; - auto next{scanner::call(input, size, pos)}; + auto next{scanner::call(input, size, pos, loc)}; PQXX_ASSUME(next > pos); bool at_quote{false}; pos = next; - next = scanner::call(input, size, pos); + next = scanner::call(input, size, pos, loc); PQXX_ASSUME(next > pos); while (pos < size) { @@ -52,7 +52,7 @@ inline std::size_t scan_double_quoted_string( case '\\': // Backslash escape. Skip ahead by one more character. pos = next; - next = scanner::call(input, size, pos); + next = scanner::call(input, size, pos, loc); PQXX_ASSUME(next > pos); break; @@ -68,12 +68,12 @@ inline std::size_t scan_double_quoted_string( // Multibyte character. Carry on. } pos = next; - next = scanner::call(input, size, pos); + next = scanner::call(input, size, pos, loc); PQXX_ASSUME(next > pos); } if (not at_quote) throw argument_error{ - "Missing closing double-quote: " + std::string{input}}; + "Missing closing double-quote: " + std::string{input}, loc}; return pos; } @@ -82,7 +82,7 @@ inline std::size_t scan_double_quoted_string( /// Un-quote and un-escape a double-quoted SQL string. template inline std::string parse_double_quoted_string( - char const input[], std::size_t end, std::size_t pos) + char const input[], std::size_t end, std::size_t pos, sl loc) { std::string output; // Maximum output size is same as the input size, minus the opening and @@ -92,8 +92,8 @@ inline std::string parse_double_quoted_string( // TODO: Use find_char<...>(). using scanner = glyph_scanner; - auto here{scanner::call(input, end, pos)}, - next{scanner::call(input, end, here)}; + auto here{scanner::call(input, end, pos, loc)}, + next{scanner::call(input, end, here, loc)}; PQXX_ASSUME(here > pos); PQXX_ASSUME(next > here); while (here < end - 1) @@ -106,12 +106,12 @@ inline std::string parse_double_quoted_string( { // Skip escape. here = next; - next = scanner::call(input, end, here); + next = scanner::call(input, end, here, loc); PQXX_ASSUME(next > here); } output.append(input + here, input + next); here = next; - next = scanner::call(input, end, here); + next = scanner::call(input, end, here, loc); PQXX_ASSUME(next > here); } return output; @@ -127,16 +127,16 @@ inline std::string parse_double_quoted_string( * comma or a closing parenthesis. */ template -inline std::size_t -scan_unquoted_string(char const input[], std::size_t size, std::size_t pos) +inline std::size_t scan_unquoted_string( + char const input[], std::size_t size, std::size_t pos, sl loc) { using scanner = glyph_scanner; - auto next{scanner::call(input, size, pos)}; + auto next{scanner::call(input, size, pos, loc)}; PQXX_ASSUME(next > pos); while ((pos < size) and ((next - pos) > 1 or ((input[pos] != STOP) and ...))) { pos = next; - next = scanner::call(input, size, pos); + next = scanner::call(input, size, pos, loc); PQXX_ASSUME(next > pos); } return pos; @@ -146,7 +146,7 @@ scan_unquoted_string(char const input[], std::size_t size, std::size_t pos) /// Parse an unquoted array entry or cfield of a composite-type field. template inline std::string_view -parse_unquoted_string(char const input[], std::size_t end, std::size_t pos) +parse_unquoted_string(char const input[], std::size_t end, std::size_t pos, sl) { return {&input[pos], end - pos}; } @@ -179,13 +179,15 @@ parse_unquoted_string(char const input[], std::size_t end, std::size_t pos) template inline void parse_composite_field( std::size_t &index, std::string_view input, std::size_t &pos, T &field, - std::size_t last_field) + std::size_t last_field, sl loc) { assert(index <= last_field); - auto next{glyph_scanner::call(std::data(input), std::size(input), pos)}; + auto next{ + glyph_scanner::call(std::data(input), std::size(input), pos, loc)}; PQXX_ASSUME(next > pos); if ((next - pos) != 1) - throw conversion_error{"Non-ASCII character in composite-type syntax."}; + throw conversion_error{ + "Non-ASCII character in composite-type syntax.", loc}; // Expect a field. switch (input[pos]) @@ -199,15 +201,16 @@ inline void parse_composite_field( else throw conversion_error{ "Can't read composite field " + to_string(index) + ": C++ type " + - type_name + " does not support nulls."}; + type_name + " does not support nulls.", + loc}; break; case '"': { - auto const stop{ - scan_double_quoted_string(std::data(input), std::size(input), pos)}; + auto const stop{scan_double_quoted_string( + std::data(input), std::size(input), pos, loc)}; PQXX_ASSUME(stop > pos); auto const text{ - parse_double_quoted_string(std::data(input), stop, pos)}; + parse_double_quoted_string(std::data(input), stop, pos, loc)}; field = from_string(text); pos = stop; } @@ -215,7 +218,7 @@ inline void parse_composite_field( default: { auto const stop{scan_unquoted_string( - std::data(input), std::size(input), pos)}; + std::data(input), std::size(input), pos, loc)}; PQXX_ASSUME(stop >= pos); field = from_string(std::string_view{std::data(input) + pos, stop - pos}); @@ -225,36 +228,42 @@ inline void parse_composite_field( } // Expect a comma or a closing parenthesis. - next = glyph_scanner::call(std::data(input), std::size(input), pos); + next = + glyph_scanner::call(std::data(input), std::size(input), pos, loc); PQXX_ASSUME(next > pos); if ((next - pos) != 1) throw conversion_error{ "Unexpected non-ASCII character after composite field: " + - std::string{input}}; + std::string{input}, + loc}; if (index < last_field) { if (input[pos] != ',') throw conversion_error{ "Found '" + std::string{input[pos]} + - "' in composite value where comma was expected: " + std::data(input)}; + "' in composite value where comma was expected: " + std::data(input), + loc}; } else { if (input[pos] == ',') throw conversion_error{ "Composite value contained more fields than the expected " + - to_string(last_field) + ": " + std::data(input)}; + to_string(last_field) + ": " + std::data(input), + loc}; if (input[pos] != ')' and input[pos] != ']') throw conversion_error{ "Composite value has unexpected characters where closing parenthesis " "was expected: " + - std::string{input}}; + std::string{input}, + loc}; if (next != std::size(input)) throw conversion_error{ "Composite value has unexpected text after closing parenthesis: " + - std::string{input}}; + std::string{input}, + loc}; } pos = next; @@ -266,12 +275,13 @@ inline void parse_composite_field( template using composite_field_parser = void (*)( std::size_t &index, std::string_view input, std::size_t &pos, T &field, - std::size_t last_field); + std::size_t last_field, sl loc); /// Look up implementation of parse_composite_field for ENC. template -composite_field_parser specialize_parse_composite_field(encoding_group enc) +composite_field_parser +specialize_parse_composite_field(encoding_group enc, sl loc) { switch (enc) { @@ -300,7 +310,8 @@ composite_field_parser specialize_parse_composite_field(encoding_group enc) case encoding_group::UTF8: return parse_composite_field; } - throw internal_error{concat("Unexpected encoding group code: ", enc, ".")}; + throw internal_error{ + concat("Unexpected encoding group code: ", enc, "."), loc}; } diff --git a/include/pqxx/internal/conversions.hxx b/include/pqxx/internal/conversions.hxx index 2a4ea8106..ff4f7806b 100644 --- a/include/pqxx/internal/conversions.hxx +++ b/include/pqxx/internal/conversions.hxx @@ -52,12 +52,12 @@ inline std::string state_buffer_overrun(HAVE have_bytes, NEED need_bytes) /// Throw exception for attempt to convert SQL NULL to given type. [[noreturn]] PQXX_LIBEXPORT PQXX_COLD void -throw_null_conversion(std::string const &type); +throw_null_conversion(std::string const &type, sl); /// Throw exception for attempt to convert SQL NULL to given type. [[noreturn]] PQXX_LIBEXPORT PQXX_COLD void -throw_null_conversion(std::string_view type); +throw_null_conversion(std::string_view type, sl); /// Deliberately nonfunctional conversion traits for `char` types. @@ -937,13 +937,14 @@ template struct string_traits return begin + budget; } - static DATA from_string(std::string_view text) + static DATA from_string(std::string_view text, sl loc = sl::current()) { auto const size{pqxx::internal::size_unesc_bin(std::size(text))}; bytes buf; buf.resize(size); // XXX: Use std::as_writable_bytes. - pqxx::internal::unesc_bin(text, reinterpret_cast(buf.data())); + pqxx::internal::unesc_bin( + text, reinterpret_cast(buf.data()), loc); return buf; } }; diff --git a/include/pqxx/internal/encoding_group.hxx b/include/pqxx/internal/encoding_group.hxx index 7e2603471..c496c27ed 100644 --- a/include/pqxx/internal/encoding_group.hxx +++ b/include/pqxx/internal/encoding_group.hxx @@ -49,8 +49,8 @@ enum class encoding_group * There are multiple different glyph scanner implementations, for different * kinds of encodings. */ -using glyph_scanner_func = - std::size_t(char const buffer[], std::size_t buffer_len, std::size_t start); +using glyph_scanner_func = std::size_t( + char const buffer[], std::size_t buffer_len, std::size_t start, sl); /// Function type: "find first occurrence of specific any of ASCII characters." @@ -68,7 +68,7 @@ using glyph_scanner_func = * end of `haystack`. */ using char_finder_func = - std::size_t(std::string_view haystack, std::size_t start); + std::size_t(std::string_view haystack, std::size_t start, sl); } // namespace pqxx::internal #endif diff --git a/include/pqxx/internal/encodings.hxx b/include/pqxx/internal/encodings.hxx index 92aa341fc..959994f23 100644 --- a/include/pqxx/internal/encodings.hxx +++ b/include/pqxx/internal/encodings.hxx @@ -29,7 +29,7 @@ namespace pqxx::internal PQXX_PURE char const *name_encoding(int encoding_id); /// Convert libpq encoding enum value to its libpqxx group. -PQXX_LIBEXPORT encoding_group enc_group(int /* libpq encoding ID */); +PQXX_LIBEXPORT encoding_group enc_group(int /* libpq encoding ID */, sl); /// Look up the glyph scanner function for a given encoding group. @@ -37,7 +37,7 @@ PQXX_LIBEXPORT encoding_group enc_group(int /* libpq encoding ID */); * scanner function appropriate for the buffer's encoding. Then, repeatedly * call the scanner function to find the glyphs. */ -PQXX_LIBEXPORT glyph_scanner_func *get_glyph_scanner(encoding_group); +PQXX_LIBEXPORT glyph_scanner_func *get_glyph_scanner(encoding_group, sl); // TODO: Get rid of this one. Use compile-time-specialised version instead. @@ -50,14 +50,14 @@ PQXX_LIBEXPORT glyph_scanner_func *get_glyph_scanner(encoding_group); */ template inline std::size_t find_char( - glyph_scanner_func *scanner, std::string_view haystack, - std::size_t here = 0u) + glyph_scanner_func *scanner, std::string_view haystack, std::size_t here, + sl loc) { auto const sz{std::size(haystack)}; auto const data{std::data(haystack)}; while (here < sz) { - auto next{scanner(data, sz, here)}; + auto next{scanner(data, sz, here, loc)}; PQXX_ASSUME(next > here); // (For some reason gcc had a problem with a right-fold here. But clang // was fine.) @@ -86,12 +86,12 @@ inline std::size_t find_char( template inline void for_glyphs( encoding_group enc, CALLABLE callback, char const buffer[], - std::size_t buffer_len, std::size_t start = 0) + std::size_t buffer_len, std::size_t start, sl loc) { - auto const scan{get_glyph_scanner(enc)}; + auto const scan{get_glyph_scanner(enc, loc)}; for (std::size_t here = start, next; here < buffer_len; here = next) { - next = scan(buffer, buffer_len, here); + next = scan(buffer, buffer_len, here, loc); PQXX_ASSUME(next > here); callback(buffer + here, buffer + next); } @@ -110,7 +110,7 @@ get_byte(char const buffer[], std::size_t offset) noexcept [[noreturn]] PQXX_COLD void throw_for_encoding_error( char const *encoding_name, char const buffer[], std::size_t start, - std::size_t count) + std::size_t count, sl loc) { std::stringstream s; s << "Invalid byte sequence for encoding " << encoding_name << " at byte " @@ -121,7 +121,7 @@ get_byte(char const buffer[], std::size_t offset) noexcept if (i + 1 < count) s << " "; } - throw pqxx::argument_error{s.str()}; + throw pqxx::argument_error{s.str(), loc}; } @@ -144,7 +144,7 @@ template struct glyph_scanner // TODO: Convert to use string_view? /// Find the next glyph in `buffer` after position `start`. PQXX_PURE static std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start); + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl); }; @@ -159,7 +159,7 @@ namespace */ template PQXX_PURE inline std::size_t -find_ascii_char(std::string_view haystack, std::size_t here) +find_ascii_char(std::string_view haystack, std::size_t here, sl loc) { // We only know how to search for ASCII characters. It's an optimisation // assumption in the code below. @@ -171,7 +171,7 @@ find_ascii_char(std::string_view haystack, std::size_t here) { // Look up the next character boundary. This can be quite costly, so we // desperately want the call inlined. - auto next{glyph_scanner::call(data, sz, here)}; + auto next{glyph_scanner::call(data, sz, here, loc)}; PQXX_ASSUME(next > here); // (For some reason gcc had a problem with a right-fold here. But clang @@ -208,7 +208,7 @@ find_ascii_char(std::string_view haystack, std::size_t here) */ template PQXX_PURE std::size_t -find_s_ascii_char(std::string_view haystack, std::size_t here) +find_s_ascii_char(std::string_view haystack, std::size_t here, sl loc) { // We only know how to search for ASCII characters. It's an optimisation // assumption in the code below. @@ -221,7 +221,7 @@ find_s_ascii_char(std::string_view haystack, std::size_t here) // ASCII-range byte. while ((... and (data[here] != NEEDLE))) { - auto const next = glyph_scanner::call(data, sz, here); + auto const next = glyph_scanner::call(data, sz, here, loc); PQXX_ASSUME(next > here); here = next; } @@ -231,8 +231,8 @@ find_s_ascii_char(std::string_view haystack, std::size_t here) template<> struct glyph_scanner { - static PQXX_PURE constexpr std::size_t - call(char const /* buffer */[], std::size_t buffer_len, std::size_t start) + static PQXX_PURE constexpr std::size_t call( + char const /* buffer */[], std::size_t buffer_len, std::size_t start, sl) { // TODO: Don't bother with npos. Let the caller check. if (start >= buffer_len) [[unlikely]] @@ -247,7 +247,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -258,13 +258,13 @@ template<> struct glyph_scanner if (not between_inc(byte1, 0x81, 0xfe) or (start + 2 > buffer_len)) [[unlikely]] - throw_for_encoding_error("BIG5", buffer, start, 1); + throw_for_encoding_error("BIG5", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if ( not between_inc(byte2, 0x40, 0x7e) and not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("BIG5", buffer, start, 2); + throw_for_encoding_error("BIG5", buffer, start, 2, loc); return start + 2; } @@ -286,7 +286,7 @@ depending on the specific extension: template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) return std::string::npos; @@ -297,11 +297,11 @@ template<> struct glyph_scanner if (not between_inc(byte1, 0xa1, 0xf7) or start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("EUC_CN", buffer, start, 1); + throw_for_encoding_error("EUC_CN", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("EUC_CN", buffer, start, 2); + throw_for_encoding_error("EUC_CN", buffer, start, 2, loc); return start + 2; } @@ -316,7 +316,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) return std::string::npos; @@ -326,13 +326,13 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("EUC_JP", buffer, start, 1); + throw_for_encoding_error("EUC_JP", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if (byte1 == 0x8e) { if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("EUC_JP", buffer, start, 2); + throw_for_encoding_error("EUC_JP", buffer, start, 2, loc); return start + 2; } @@ -340,7 +340,7 @@ template<> struct glyph_scanner if (between_inc(byte1, 0xa1, 0xfe)) { if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("EUC_JP", buffer, start, 2); + throw_for_encoding_error("EUC_JP", buffer, start, 2, loc); return start + 2; } @@ -351,12 +351,12 @@ template<> struct glyph_scanner if ( not between_inc(byte2, 0xa1, 0xfe) or not between_inc(byte3, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("EUC_JP", buffer, start, 3); + throw_for_encoding_error("EUC_JP", buffer, start, 3, loc); return start + 3; } - throw_for_encoding_error("EUC_JP", buffer, start, 1); + throw_for_encoding_error("EUC_JP", buffer, start, 1, loc); } }; @@ -365,7 +365,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -376,11 +376,11 @@ template<> struct glyph_scanner if (not between_inc(byte1, 0xa1, 0xfe) or start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("EUC_KR", buffer, start, 1); + throw_for_encoding_error("EUC_KR", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("EUC_KR", buffer, start, 1); + throw_for_encoding_error("EUC_KR", buffer, start, 1, loc); return start + 2; } @@ -391,7 +391,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -401,19 +401,19 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("EUC_KR", buffer, start, 1); + throw_for_encoding_error("EUC_KR", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte1, 0xa1, 0xfe)) { if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("EUC_KR", buffer, start, 2); + throw_for_encoding_error("EUC_KR", buffer, start, 2, loc); return start + 2; } if (byte1 != 0x8e or start + 4 > buffer_len) [[unlikely]] - throw_for_encoding_error("EUC_KR", buffer, start, 1); + throw_for_encoding_error("EUC_KR", buffer, start, 1, loc); if ( between_inc(byte2, 0xa1, 0xb0) and @@ -421,7 +421,7 @@ template<> struct glyph_scanner between_inc(get_byte(buffer, start + 3), 0xa1, 0xfe)) return start + 4; - [[unlikely]] throw_for_encoding_error("EUC_KR", buffer, start, 4); + [[unlikely]] throw_for_encoding_error("EUC_KR", buffer, start, 4, loc); } }; @@ -430,7 +430,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -439,22 +439,25 @@ template<> struct glyph_scanner if (byte1 < 0x80) return start + 1; if (byte1 == 0x80) - throw_for_encoding_error("GB18030", buffer, start, buffer_len - start); + throw_for_encoding_error( + "GB18030", buffer, start, buffer_len - start, loc); if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("GB18030", buffer, start, buffer_len - start); + throw_for_encoding_error( + "GB18030", buffer, start, buffer_len - start, loc); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte2, 0x40, 0xfe)) { if (byte2 == 0x7f) [[unlikely]] - throw_for_encoding_error("GB18030", buffer, start, 2); + throw_for_encoding_error("GB18030", buffer, start, 2, loc); return start + 2; } if (start + 4 > buffer_len) [[unlikely]] - throw_for_encoding_error("GB18030", buffer, start, buffer_len - start); + throw_for_encoding_error( + "GB18030", buffer, start, buffer_len - start, loc); if ( between_inc(byte2, 0x30, 0x39) and @@ -462,7 +465,7 @@ template<> struct glyph_scanner between_inc(get_byte(buffer, start + 3), 0x30, 0x39)) return start + 4; - [[unlikely]] throw_for_encoding_error("GB18030", buffer, start, 4); + [[unlikely]] throw_for_encoding_error("GB18030", buffer, start, 4, loc); } }; @@ -471,7 +474,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -481,7 +484,7 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("GBK", buffer, start, 1); + throw_for_encoding_error("GBK", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if ( @@ -499,7 +502,7 @@ template<> struct glyph_scanner byte2 != 0x7f)) return start + 2; - [[unlikely]] throw_for_encoding_error("GBK", buffer, start, 2); + [[unlikely]] throw_for_encoding_error("GBK", buffer, start, 2, loc); } }; @@ -516,7 +519,7 @@ CJKV Information Processing by Ken Lunde, pg. 269: template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -526,7 +529,7 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("JOHAB", buffer, start, 1); + throw_for_encoding_error("JOHAB", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start)}; if ( @@ -536,7 +539,7 @@ template<> struct glyph_scanner (between_inc(byte2, 0x31, 0x7e) or between_inc(byte2, 0x91, 0xfe)))) return start + 2; - [[unlikely]] throw_for_encoding_error("JOHAB", buffer, start, 2); + [[unlikely]] throw_for_encoding_error("JOHAB", buffer, start, 2, loc); } }; @@ -551,7 +554,7 @@ using PostgreSQL 9.2.23. Use this at your own risk. template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -561,14 +564,14 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("MULE_INTERNAL", buffer, start, 1); + throw_for_encoding_error("MULE_INTERNAL", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte1, 0x81, 0x8d) and byte2 >= 0xa0) return start + 2; if (start + 3 > buffer_len) [[unlikely]] - throw_for_encoding_error("MULE_INTERNAL", buffer, start, 2); + throw_for_encoding_error("MULE_INTERNAL", buffer, start, 2, loc); if ( ((byte1 == 0x9a and between_inc(byte2, 0xa0, 0xdf)) or @@ -578,7 +581,7 @@ template<> struct glyph_scanner return start + 3; if (start + 4 > buffer_len) [[unlikely]] - throw_for_encoding_error("MULE_INTERNAL", buffer, start, 3); + throw_for_encoding_error("MULE_INTERNAL", buffer, start, 3, loc); if ( ((byte1 == 0x9c and between_inc(byte2, 0xf0, 0xf4)) or @@ -587,7 +590,8 @@ template<> struct glyph_scanner get_byte(buffer, start + 4) >= 0xa0) return start + 4; - [[unlikely]] throw_for_encoding_error("MULE_INTERNAL", buffer, start, 4); + [[unlikely]] throw_for_encoding_error( + "MULE_INTERNAL", buffer, start, 4, loc); } }; @@ -604,7 +608,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) return std::string::npos; @@ -616,19 +620,19 @@ template<> struct glyph_scanner if ( not between_inc(byte1, 0x81, 0x9f) and not between_inc(byte1, 0xe0, 0xfc)) [[unlikely]] - throw_for_encoding_error("SJIS", buffer, start, 1); + throw_for_encoding_error("SJIS", buffer, start, 1, loc); if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("SJIS", buffer, start, buffer_len - start); + throw_for_encoding_error("SJIS", buffer, start, buffer_len - start, loc); auto const byte2{get_byte(buffer, start + 1)}; if (byte2 == 0x7f) [[unlikely]] - throw_for_encoding_error("SJIS", buffer, start, 2); + throw_for_encoding_error("SJIS", buffer, start, 2, loc); if (between_inc(byte2, 0x40, 0x9e) or between_inc(byte2, 0x9f, 0xfc)) return start + 2; - [[unlikely]] throw_for_encoding_error("SJIS", buffer, start, 2); + [[unlikely]] throw_for_encoding_error("SJIS", buffer, start, 2, loc); } }; @@ -637,7 +641,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -647,7 +651,7 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("UHC", buffer, start, buffer_len - start); + throw_for_encoding_error("UHC", buffer, start, buffer_len - start, loc); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte1, 0x80, 0xc6)) @@ -657,18 +661,18 @@ template<> struct glyph_scanner between_inc(byte2, 0x80, 0xfe)) return start + 2; - [[unlikely]] throw_for_encoding_error("UHC", buffer, start, 2); + [[unlikely]] throw_for_encoding_error("UHC", buffer, start, 2, loc); } if (between_inc(byte1, 0xa1, 0xfe)) { if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("UHC", buffer, start, 2); + throw_for_encoding_error("UHC", buffer, start, 2, loc); return start + 2; } - throw_for_encoding_error("UHC", buffer, start, 1); + throw_for_encoding_error("UHC", buffer, start, 1, loc); } }; @@ -677,7 +681,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -687,19 +691,19 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); + throw_for_encoding_error("UTF8", buffer, start, buffer_len - start, loc); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte1, 0xc0, 0xdf)) { if (not between_inc(byte2, 0x80, 0xbf)) [[unlikely]] - throw_for_encoding_error("UTF8", buffer, start, 2); + throw_for_encoding_error("UTF8", buffer, start, 2, loc); return start + 2; } if (start + 3 > buffer_len) [[unlikely]] - throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); + throw_for_encoding_error("UTF8", buffer, start, buffer_len - start, loc); auto const byte3{get_byte(buffer, start + 2)}; if (between_inc(byte1, 0xe0, 0xef)) @@ -707,11 +711,11 @@ template<> struct glyph_scanner if (between_inc(byte2, 0x80, 0xbf) and between_inc(byte3, 0x80, 0xbf)) return start + 3; - [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 3); + [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 3, loc); } if (start + 4 > buffer_len) [[unlikely]] - throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); + throw_for_encoding_error("UTF8", buffer, start, buffer_len - start, loc); if (between_inc(byte1, 0xf0, 0xf7)) { @@ -720,10 +724,10 @@ template<> struct glyph_scanner between_inc(get_byte(buffer, start + 3), 0x80, 0xbf)) return start + 4; - [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 4); + [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 4, loc); } - [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 1); + [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 1, loc); } }; @@ -774,7 +778,7 @@ map_ascii_search_group(encoding_group enc) noexcept */ template PQXX_PURE constexpr inline char_finder_func * -get_char_finder(encoding_group enc) +get_char_finder(encoding_group enc, sl loc) { auto const as_if{map_ascii_search_group(enc)}; switch (as_if) @@ -796,8 +800,10 @@ get_char_finder(encoding_group enc) return pqxx::internal::find_ascii_char; default: - throw pqxx::internal_error{concat( - "Unexpected encoding group: ", as_if, " (mapped from ", enc, ").")}; + throw pqxx::internal_error{ + concat( + "Unexpected encoding group: ", as_if, " (mapped from ", enc, ")."), + loc}; } } @@ -808,7 +814,7 @@ get_char_finder(encoding_group enc) */ template PQXX_PURE constexpr inline char_finder_func * -get_s_char_finder(encoding_group enc) +get_s_char_finder(encoding_group enc, sl loc) { auto const as_if{map_ascii_search_group(enc)}; switch (as_if) @@ -831,8 +837,10 @@ get_s_char_finder(encoding_group enc) return pqxx::internal::find_s_ascii_char; default: - throw pqxx::internal_error{concat( - "Unexpected encoding group: ", as_if, " (mapped from ", enc, ").")}; + throw pqxx::internal_error{ + concat( + "Unexpected encoding group: ", as_if, " (mapped from ", enc, ")."), + loc}; } } } // namespace pqxx::internal diff --git a/include/pqxx/internal/gates/connection-notification_receiver.hxx b/include/pqxx/internal/gates/connection-notification_receiver.hxx index 0bcb2db17..7fc6a2744 100644 --- a/include/pqxx/internal/gates/connection-notification_receiver.hxx +++ b/include/pqxx/internal/gates/connection-notification_receiver.hxx @@ -21,9 +21,9 @@ class PQXX_PRIVATE connection_notification_receiver : callgate { home().add_receiver(receiver); } - void remove_receiver(notification_receiver *receiver) noexcept + void remove_receiver(notification_receiver *receiver, sl loc) noexcept { - home().remove_receiver(receiver); + home().remove_receiver(receiver, loc); } }; } // namespace pqxx::internal::gate diff --git a/include/pqxx/internal/gates/connection-pipeline.hxx b/include/pqxx/internal/gates/connection-pipeline.hxx index ff9acd65a..23259e428 100644 --- a/include/pqxx/internal/gates/connection-pipeline.hxx +++ b/include/pqxx/internal/gates/connection-pipeline.hxx @@ -13,12 +13,12 @@ class PQXX_PRIVATE connection_pipeline : callgate void start_exec(char const query[]) { home().start_exec(query); } pqxx::internal::pq::PGresult *get_result() { return home().get_result(); } - void cancel_query() { home().cancel_query(); } + void cancel_query(sl loc) { home().cancel_query(loc); } bool consume_input() noexcept { return home().consume_input(); } bool is_busy() const noexcept { return home().is_busy(); } - int encoding_id() { return home().encoding_id(); } + int encoding_id(sl loc) { return home().encoding_id(loc); } auto get_notice_waiters() const { return home().m_notice_waiters; } }; diff --git a/include/pqxx/internal/gates/connection-sql_cursor.hxx b/include/pqxx/internal/gates/connection-sql_cursor.hxx index 51a889844..55d26efbf 100644 --- a/include/pqxx/internal/gates/connection-sql_cursor.hxx +++ b/include/pqxx/internal/gates/connection-sql_cursor.hxx @@ -14,6 +14,6 @@ class PQXX_PRIVATE connection_sql_cursor : callgate connection_sql_cursor(reference x) : super(x) {} - result exec(char const query[]) { return home().exec(query); } + result exec(char const query[], sl loc) { return home().exec(query, loc); } }; } // namespace pqxx::internal::gate diff --git a/include/pqxx/internal/gates/connection-stream_to.hxx b/include/pqxx/internal/gates/connection-stream_to.hxx index a6974fb21..ea44bc8ea 100644 --- a/include/pqxx/internal/gates/connection-stream_to.hxx +++ b/include/pqxx/internal/gates/connection-stream_to.hxx @@ -11,7 +11,10 @@ class PQXX_PRIVATE connection_stream_to : callgate connection_stream_to(reference x) : super(x) {} - void write_copy_line(std::string_view line) { home().write_copy_line(line); } + void write_copy_line(std::string_view line, sl loc) + { + home().write_copy_line(line, loc); + } void end_copy_write() { home().end_copy_write(); } }; } // namespace pqxx::internal::gate diff --git a/include/pqxx/internal/gates/connection-transaction.hxx b/include/pqxx/internal/gates/connection-transaction.hxx index 3a7546a4f..e94bfd409 100644 --- a/include/pqxx/internal/gates/connection-transaction.hxx +++ b/include/pqxx/internal/gates/connection-transaction.hxx @@ -13,9 +13,15 @@ class PQXX_PRIVATE connection_transaction : callgate connection_transaction(reference x) : super(x) {} - template result exec(STRING query, std::string_view desc) + template + result exec(STRING query, std::string_view desc, sl loc) { - return home().exec(query, desc); + return home().exec(query, desc, loc); + } + + template result exec(STRING query, sl loc) + { + return home().exec(query, "", loc); } void register_transaction(transaction_base *t) @@ -28,18 +34,22 @@ class PQXX_PRIVATE connection_transaction : callgate } auto read_copy_line() { return home().read_copy_line(); } - void write_copy_line(std::string_view line) { home().write_copy_line(line); } + void write_copy_line(std::string_view line, sl loc) + { + home().write_copy_line(line, loc); + } void end_copy_write() { home().end_copy_write(); } - result - exec_prepared(std::string_view statement, internal::c_params const &args) + result exec_prepared( + std::string_view statement, internal::c_params const &args, sl loc) { - return home().exec_prepared(statement, args); + return home().exec_prepared(statement, args, loc); } - result exec_params(std::string_view query, internal::c_params const &args) + result + exec_params(std::string_view query, internal::c_params const &args, sl loc) { - return home().exec_params(query, args); + return home().exec_params(query, args, loc); } }; } // namespace pqxx::internal::gate diff --git a/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx b/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx index 56056d5ef..90bd0d494 100644 --- a/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx +++ b/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx @@ -24,9 +24,9 @@ class PQXX_PRIVATE icursorstream_icursor_iterator : callgate return home().forward(n); } - void service_iterators(icursorstream::difference_type p) + void service_iterators(icursorstream::difference_type p, sl loc) { - home().service_iterators(p); + home().service_iterators(p, loc); } }; } // namespace pqxx::internal::gate diff --git a/include/pqxx/internal/gates/result-creation.hxx b/include/pqxx/internal/gates/result-creation.hxx index f82bbda5f..b67c7d943 100644 --- a/include/pqxx/internal/gates/result-creation.hxx +++ b/include/pqxx/internal/gates/result-creation.hxx @@ -18,9 +18,11 @@ class PQXX_PRIVATE result_creation : callgate return result(rhs, query, notice_waiters, enc); } - void check_status(std::string_view desc = ""sv) const + void check_status(std::string_view desc, sl loc) const { - return home().check_status(desc); + return home().check_status(desc, loc); } + + void check_status(sl loc) const { return home().check_status("", loc); } }; } // namespace pqxx::internal::gate diff --git a/include/pqxx/internal/result_iter.hxx b/include/pqxx/internal/result_iter.hxx index 9eea49861..faba17b28 100644 --- a/include/pqxx/internal/result_iter.hxx +++ b/include/pqxx/internal/result_iter.hxx @@ -31,35 +31,39 @@ public: /// Construct an "end" iterator. result_iter() = default; - explicit result_iter(result const &home) : + explicit result_iter(result const &home, sl loc = sl::current()) : m_home{&home}, m_size{std::size(home)} { if (not std::empty(home)) - read(); + read(loc); } result_iter(result_iter const &) = default; result_iter &operator++() { + sl loc{sl::current()}; m_index++; if (m_index >= m_size) m_home = nullptr; else - read(); + read(loc); return *this; } /// Comparison only works for comparing to end(). - bool operator==(result_iter const &rhs) const + bool operator==(result_iter const &rhs) const noexcept { return m_home == rhs.m_home; } - bool operator!=(result_iter const &rhs) const { return not(*this == rhs); } + bool operator!=(result_iter const &rhs) const noexcept + { + return not(*this == rhs); + } - value_type const &operator*() const { return m_value; } + value_type const &operator*() const noexcept { return m_value; } private: - void read() { (*m_home)[m_index].convert(m_value); } + void read(sl loc) { (*m_home)[m_index].convert(m_value, loc); } result const *m_home{nullptr}; result::size_type m_index{0}; @@ -100,7 +104,7 @@ template inline auto pqxx::result::iter() const template -inline void pqxx::result::for_each(CALLABLE &&func) const +inline void pqxx::result::for_each(CALLABLE &&func, sl loc) const { using args_tuple = internal::args_t; constexpr auto sz{std::tuple_size_v}; @@ -111,11 +115,13 @@ inline void pqxx::result::for_each(CALLABLE &&func) const auto const cols{this->columns()}; if (sz != cols) - throw usage_error{internal::concat( - "Callback to for_each takes ", sz, "parameter", (sz == 1) ? "" : "s", - ", but result set has ", cols, "field", (cols == 1) ? "" : "s", ".")}; + throw usage_error{ + internal::concat( + "Callback to for_each takes ", sz, "parameter", (sz == 1) ? "" : "s", + ", but result set has ", cols, "field", (cols == 1) ? "" : "s", "."), + loc}; using pass_tuple = pqxx::internal::strip_types_t; - for (auto const r : *this) std::apply(func, r.as_tuple()); + for (auto const r : *this) std::apply(func, r.as_tuple(loc)); } #endif diff --git a/include/pqxx/internal/result_iterator.hxx b/include/pqxx/internal/result_iterator.hxx index 7b911b5ae..6af22b861 100644 --- a/include/pqxx/internal/result_iterator.hxx +++ b/include/pqxx/internal/result_iterator.hxx @@ -65,10 +65,10 @@ public: */ //@{ /// Dereference the iterator. - [[nodiscard]] pointer operator->() const { return this; } + [[nodiscard]] pointer operator->() const noexcept { return this; } /// Dereference the iterator. - [[nodiscard]] reference operator*() const { return *this; } + [[nodiscard]] reference operator*() const noexcept { return *this; } //@} /** @@ -196,10 +196,10 @@ public: using reference = iterator_type::reference; /// Create an iterator, but in an unusable state. - const_reverse_result_iterator() = default; + const_reverse_result_iterator() noexcept = default; /// Copy an iterator. - const_reverse_result_iterator(const_reverse_result_iterator const &rhs) = - default; + const_reverse_result_iterator( + const_reverse_result_iterator const &rhs) noexcept = default; /// Copy a reverse iterator from a regular iterator. explicit const_reverse_result_iterator(const_result_iterator const &rhs) : const_result_iterator{rhs} diff --git a/include/pqxx/internal/sql_cursor.hxx b/include/pqxx/internal/sql_cursor.hxx index 4d711273a..947eab344 100644 --- a/include/pqxx/internal/sql_cursor.hxx +++ b/include/pqxx/internal/sql_cursor.hxx @@ -34,25 +34,31 @@ public: sql_cursor( transaction_base &t, std::string_view query, std::string_view cname, cursor_base::access_policy ap, cursor_base::update_policy up, - cursor_base::ownership_policy op, bool hold); + cursor_base::ownership_policy op, bool hold, sl = sl::current()); sql_cursor( transaction_base &t, std::string_view cname, cursor_base::ownership_policy op); - ~sql_cursor() noexcept { close(); } + ~sql_cursor() noexcept + { + // TODO: How can we pass std::source_location here? + auto loc{sl::current()}; + close(loc); + } - result fetch(difference_type rows, difference_type &displacement); - result fetch(difference_type rows) + result fetch(difference_type rows, difference_type &displacement, sl); + result fetch(difference_type rows, sl loc) { difference_type d = 0; - return fetch(rows, d); + return fetch(rows, d, loc); } - difference_type move(difference_type rows, difference_type &displacement); - difference_type move(difference_type rows) + difference_type + move(difference_type rows, difference_type &displacement, sl); + difference_type move(difference_type rows, sl loc) { difference_type d = 0; - return move(rows, d); + return move(rows, d, loc); } /// Current position, or -1 for unknown @@ -76,13 +82,13 @@ public: /// Return zero-row result for this cursor. result const &empty_result() const noexcept { return m_empty_result; } - void close() noexcept; + void close(sl loc) noexcept; private: difference_type adjust(difference_type hoped, difference_type actual); static std::string stridestring(difference_type); /// Initialize cached empty result. Call only at beginning or end! - void init_empty_result(transaction_base &); + void init_empty_result(transaction_base &, sl); /// Connection in which this cursor lives. connection &m_home; @@ -107,9 +113,9 @@ private: }; -PQXX_LIBEXPORT result_size_type obtain_stateless_cursor_size(sql_cursor &); +PQXX_LIBEXPORT result_size_type obtain_stateless_cursor_size(sql_cursor &, sl); PQXX_LIBEXPORT result stateless_cursor_retrieve( sql_cursor &, result::difference_type size, - result::difference_type begin_pos, result::difference_type end_pos); + result::difference_type begin_pos, result::difference_type end_pos, sl); } // namespace pqxx::internal #endif diff --git a/include/pqxx/internal/stream_iterator.hxx b/include/pqxx/internal/stream_iterator.hxx index cd85b5b69..0e7ddf686 100644 --- a/include/pqxx/internal/stream_iterator.hxx +++ b/include/pqxx/internal/stream_iterator.hxx @@ -34,36 +34,36 @@ public: /// Construct an "end" iterator. stream_from_input_iterator() = default; - explicit stream_from_input_iterator(stream_t &home) : m_home(&home) + explicit stream_from_input_iterator(stream_t &home, sl loc) : m_home(&home) { - advance(); + advance(loc); } stream_from_input_iterator(stream_from_input_iterator const &) = default; stream_from_input_iterator &operator++() { - advance(); + advance(sl::current()); return *this; } - value_type const &operator*() const { return m_value; } + value_type const &operator*() const noexcept { return m_value; } /// Comparison only works for comparing to end(). - bool operator==(stream_from_input_iterator const &rhs) const + bool operator==(stream_from_input_iterator const &rhs) const noexcept { return m_home == rhs.m_home; } /// Comparison only works for comparing to end(). - bool operator!=(stream_from_input_iterator const &rhs) const + bool operator!=(stream_from_input_iterator const &rhs) const noexcept { return not(*this == rhs); } private: - void advance() + void advance(sl loc) { if (m_home == nullptr) - throw usage_error{"Moving stream_from iterator beyond end()."}; + throw usage_error{"Moving stream_from iterator beyond end().", loc}; if (not((*m_home) >> m_value)) m_home = nullptr; } @@ -81,7 +81,10 @@ public: using stream_t = stream_from; using iterator = stream_from_input_iterator; explicit stream_input_iteration(stream_t &home) : m_home{home} {} - iterator begin() const { return iterator{m_home}; } + iterator begin(sl loc = sl::current()) const + { + return iterator{m_home, loc}; + } iterator end() const { return {}; } private: diff --git a/include/pqxx/internal/stream_query.hxx b/include/pqxx/internal/stream_query.hxx index 3cd73699b..83ee08622 100644 --- a/include/pqxx/internal/stream_query.hxx +++ b/include/pqxx/internal/stream_query.hxx @@ -82,10 +82,10 @@ public: using line_handle = std::unique_ptr; /// Execute `query` on `tx`, stream results. - inline stream_query(transaction_base &tx, std::string_view query); + inline stream_query(transaction_base &tx, std::string_view query, sl loc); /// Execute `query` on `tx`, stream results. inline stream_query( - transaction_base &tx, std::string_view query, params const &); + transaction_base &tx, std::string_view query, params const &, sl loc); stream_query(stream_query &&) = delete; stream_query &operator=(stream_query &&) = delete; @@ -115,7 +115,7 @@ public: auto end() const & { return stream_query_end_iterator{}; } /// Parse and convert the latest line of data we received. - std::tuple parse_line(zview line) & + std::tuple parse_line(zview line, sl loc) & { assert(not done()); @@ -139,7 +139,7 @@ public: // Folding expression: scan and unescape each field, and convert it to its // requested type. - std::tuple data{parse_field(line, offset, write)...}; + std::tuple data{parse_field(line, offset, write, loc)...}; assert(offset == line_size + 1u); return data; @@ -153,7 +153,7 @@ private: /** This is the only encoding-dependent code in the class. All we need to * store after that is this function pointer. */ - static inline char_finder_func *get_finder(transaction_base const &tx); + static inline char_finder_func *get_finder(transaction_base const &tx, sl); /// Scan and unescape a field into the row buffer. /** The row buffer is `m_row`. @@ -171,7 +171,7 @@ private: * one greater than the size of the line, pointing at the terminating zero. */ std::tuple - read_field(zview line, std::size_t offset, char *write) + read_field(zview line, std::size_t offset, char *write, sl loc) { #if !defined(NDEBUG) auto const line_size{std::size(line)}; @@ -211,7 +211,7 @@ private: assert(lp[offset] != '\0'); // Beginning of the next character of interest (or the end of the line). - auto const stop_char{m_char_finder(line, offset)}; + auto const stop_char{m_char_finder(line, offset, loc)}; PQXX_ASSUME(stop_char > offset); assert(stop_char < (line_size + 1)); @@ -266,14 +266,14 @@ private: * @return Field value converted to TARGET type. */ template - TARGET parse_field(zview line, std::size_t &offset, char *&write) + TARGET parse_field(zview line, std::size_t &offset, char *&write, sl loc) { using field_type = std::remove_cvref_t; using nullity = nullness; assert(offset <= std::size(line)); - auto [new_offset, new_write, text]{read_field(line, offset, write)}; + auto [new_offset, new_write, text]{read_field(line, offset, write, loc)}; PQXX_ASSUME(new_offset > offset); PQXX_ASSUME(new_write >= write); offset = new_offset; @@ -290,7 +290,7 @@ private: if constexpr (nullity::has_null) return nullity::null(); else - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); } else { diff --git a/include/pqxx/internal/stream_query_impl.hxx b/include/pqxx/internal/stream_query_impl.hxx index 829730f79..4e2f247b3 100644 --- a/include/pqxx/internal/stream_query_impl.hxx +++ b/include/pqxx/internal/stream_query_impl.hxx @@ -10,38 +10,43 @@ namespace pqxx::internal { template inline stream_query::stream_query( - transaction_base &tx, std::string_view query) : - transaction_focus{tx, "stream_query"}, m_char_finder{get_finder(tx)} + transaction_base &tx, std::string_view query, sl loc) : + transaction_focus{tx, "stream_query"}, + m_char_finder{get_finder(tx, loc)} { - auto const r{tx.exec(internal::concat("COPY (", query, ") TO STDOUT"))}; - r.expect_columns(sizeof...(TYPE)); - r.expect_rows(0); + auto const r{tx.exec(internal::concat("COPY (", query, ") TO STDOUT"), loc)}; + r.expect_columns(sizeof...(TYPE), loc); + r.expect_rows(0, loc); register_me(); } template inline stream_query::stream_query( - transaction_base &tx, std::string_view query, params const &parms) : - transaction_focus{tx, "stream_query"}, m_char_finder{get_finder(tx)} + transaction_base &tx, std::string_view query, params const &parms, sl loc) : + transaction_focus{tx, "stream_query"}, + m_char_finder{get_finder(tx, loc)} { - auto const r{tx.exec(internal::concat("COPY (", query, ") TO STDOUT"), parms) - .no_rows()}; + auto const r{ + tx.exec(internal::concat("COPY (", query, ") TO STDOUT"), parms, loc) + .no_rows(loc)}; if (r.columns() != sizeof...(TYPE)) - throw usage_error{concat( - "Parsing query stream with wrong number of columns: " - "code expects ", - sizeof...(TYPE), " but query returns ", r.columns(), ".")}; + throw usage_error{ + concat( + "Parsing query stream with wrong number of columns: " + "code expects ", + sizeof...(TYPE), " but query returns ", r.columns(), "."), + loc}; register_me(); } template inline char_finder_func * -stream_query::get_finder(transaction_base const &tx) +stream_query::get_finder(transaction_base const &tx, sl loc) { - auto const group{enc_group(tx.conn().encoding_id())}; - return get_s_char_finder<'\t', '\\'>(group); + auto const group{enc_group(tx.conn().encoding_id(loc), loc)}; + return get_s_char_finder<'\t', '\\'>(group, loc); } @@ -88,7 +93,8 @@ public: /// Dereference. There's no caching in here, so don't repeat calls. value_type operator*() const { - return m_home->parse_line(zview{m_line.get(), m_line_size}); + sl loc{sl::current()}; + return m_home->parse_line(zview{m_line.get(), m_line_size}, loc); } /// Are we at the end? diff --git a/include/pqxx/internal/wait.hxx b/include/pqxx/internal/wait.hxx index 7a82e6553..6cc81b121 100644 --- a/include/pqxx/internal/wait.hxx +++ b/include/pqxx/internal/wait.hxx @@ -1,6 +1,9 @@ #if !defined(PQXX_WAIT_HXX) # define PQXX_WAIT_HXX +# include "pqxx/types.hxx" + + namespace pqxx::internal { /// Wait. @@ -13,6 +16,6 @@ void PQXX_LIBEXPORT wait_for(unsigned int microseconds); /// Wait for a socket to be ready for reading/writing, or timeout. PQXX_LIBEXPORT void wait_fd( int fd, bool for_read, bool for_write, unsigned seconds = 1, - unsigned microseconds = 0); + unsigned microseconds = 0, sl = sl::current()); } // namespace pqxx::internal #endif diff --git a/include/pqxx/nontransaction.hxx b/include/pqxx/nontransaction.hxx index 334c20aef..44eae7ada 100644 --- a/include/pqxx/nontransaction.hxx +++ b/include/pqxx/nontransaction.hxx @@ -72,10 +72,10 @@ public: register_transaction(); } - virtual ~nontransaction() override { close(); } + virtual ~nontransaction() override { close(sl::current()); } private: - virtual void do_commit() override {} + virtual void do_commit(sl) override {} }; } // namespace pqxx #endif diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index 3957d3545..5f00ceb1f 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -146,7 +146,7 @@ public: * As soon as we climb back out of that call tree, we're done with that * data. */ - pqxx::internal::c_params make_c_params() const; + pqxx::internal::c_params make_c_params(sl loc) const; private: /// Recursively append a pack of params. @@ -215,11 +215,13 @@ public: std::string get() const { return std::string(std::data(m_buf), m_len); } /// Move on to the next parameter. - void next() & + void next(sl loc = sl::current()) & { if (m_current >= max_params) - throw range_error{pqxx::internal::concat( - "Too many parameters in one statement: limit is ", max_params, ".")}; + throw range_error{ + pqxx::internal::concat( + "Too many parameters in one statement: limit is ", max_params, "."), + loc}; PQXX_ASSUME(m_current > 0); ++m_current; if (m_current % 10 == 0) @@ -231,7 +233,7 @@ public: char *const end{string_traits::into_buf( data + 1, data + std::size(m_buf), m_current)}; // (Subtract because we don't include the trailing zero.) - m_len = check_cast(end - data, "placeholders counter") - 1; + m_len = check_cast(end - data, "placeholders counter", loc) - 1; } else { diff --git a/include/pqxx/pipeline.hxx b/include/pqxx/pipeline.hxx index 9d39933b1..f6ca05c39 100644 --- a/include/pqxx/pipeline.hxx +++ b/include/pqxx/pipeline.hxx @@ -60,15 +60,17 @@ public: /// Start a pipeline. - explicit pipeline(transaction_base &t) : transaction_focus{t, s_classname} + explicit pipeline(transaction_base &t, sl loc = sl::current()) : + transaction_focus{t, s_classname} { - init(); + init(loc); } /// Start a pipeline. Assign it a name, for more helpful error messages. - pipeline(transaction_base &t, std::string_view tname) : + pipeline( + transaction_base &t, std::string_view tname, sl loc = sl::current()) : transaction_focus{t, s_classname, tname} { - init(); + init(loc); } /// Close the pipeline. @@ -81,7 +83,7 @@ public: * * @return Identifier for this query, unique only within this pipeline. */ - query_id insert(std::string_view) &; + query_id insert(std::string_view, sl = sl::current()) &; /// Wait for all ongoing or pending operations to complete, and detach. /** Detaches from the transaction when done. @@ -90,7 +92,7 @@ public: * errors which may have occurred in their execution. To be sure that your * statements succeeded, call @ref retrieve until the pipeline is empty. */ - void complete(); + void complete(sl = sl::current()); /// Forget all ongoing or pending operations and retrieved results. /** Queries already sent to the backend may still be completed, depending @@ -102,7 +104,7 @@ public: * * Detaches from the transaction when done. */ - void flush(); + void flush(sl = sl::current()); /// Cancel ongoing query, if any. /** May cancel any or all of the queries that have been inserted at this @@ -113,7 +115,7 @@ public: * Therefore, either use this function in a nontransaction, or abort the * transaction after calling it. */ - void cancel(); + void cancel(sl = sl::current()); /// Is result for given query available? [[nodiscard]] bool is_finished(query_id) const; @@ -125,14 +127,14 @@ public: * than the one in which their queries were inserted, errors may "propagate" * to subsequent queries. */ - result retrieve(query_id qid) + result retrieve(query_id qid, sl loc = sl::current()) { - return retrieve(m_queries.find(qid)).second; + return retrieve(m_queries.find(qid), loc).second; } /// Retrieve oldest unretrieved result (possibly wait for one). /** @return The query's identifier and its result set. */ - std::pair retrieve(); + std::pair retrieve(sl = sl::current()); [[nodiscard]] bool empty() const noexcept { return std::empty(m_queries); } @@ -153,7 +155,7 @@ public: /// Resume retained query emission. Harmless when not needed. - void resume() &; + void resume(sl loc = sl::current()) &; private: struct PQXX_PRIVATE Query @@ -168,7 +170,7 @@ private: using QueryMap = std::map; - void init(); + void init(sl); void attach(); void detach(); @@ -190,7 +192,7 @@ private: return m_issuedrange.second != m_issuedrange.first; } - PQXX_PRIVATE void issue(); + PQXX_PRIVATE void issue(sl); /// The given query failed; never issue anything beyond that. void set_error_at(query_id qid) noexcept @@ -201,20 +203,21 @@ private: } /// Throw pqxx::internal_error. - [[noreturn]] PQXX_PRIVATE void internal_error(std::string const &err); + [[noreturn]] PQXX_PRIVATE void internal_error(std::string const &err, sl); - PQXX_PRIVATE bool obtain_result(bool expect_none = false); + PQXX_PRIVATE bool obtain_result(bool expect_none, sl); - PQXX_PRIVATE void obtain_dummy(); - PQXX_PRIVATE void get_further_available_results(); + PQXX_PRIVATE void obtain_dummy(sl); + PQXX_PRIVATE void get_further_available_results(sl); PQXX_PRIVATE void check_end_results(); /// Receive any results that happen to be available; it's not urgent. - PQXX_PRIVATE void receive_if_available(); + PQXX_PRIVATE void receive_if_available(sl loc); /// Receive results, up to stop if possible. - PQXX_PRIVATE void receive(pipeline::QueryMap::const_iterator stop); - std::pair retrieve(pipeline::QueryMap::iterator); + PQXX_PRIVATE void receive(pipeline::QueryMap::const_iterator stop, sl); + std::pair + retrieve(pipeline::QueryMap::iterator, sl); QueryMap m_queries; std::pair m_issuedrange; diff --git a/include/pqxx/range.hxx b/include/pqxx/range.hxx index ef5afa8ff..4735a6631 100644 --- a/include/pqxx/range.hxx +++ b/include/pqxx/range.hxx @@ -47,10 +47,12 @@ private: public: inclusive_bound() = delete; - constexpr explicit inclusive_bound(TYPE const &value) : m_value{value} + constexpr explicit inclusive_bound( + TYPE const &value, sl loc = sl::current()) : + m_value{value} { if (is_null(value)) - throw argument_error{"Got null value as an inclusive range bound."}; + throw argument_error{"Got null value as an inclusive range bound.", loc}; } [[nodiscard]] constexpr TYPE const &get() const & noexcept @@ -86,10 +88,12 @@ private: public: exclusive_bound() = delete; - constexpr explicit exclusive_bound(TYPE const &value) : m_value{value} + constexpr explicit exclusive_bound( + TYPE const &value, sl loc = sl::current()) : + m_value{value} { if (is_null(value)) - throw argument_error{"Got null value as an exclusive range bound."}; + throw argument_error{"Got null value as an exclusive range bound.", loc}; } [[nodiscard]] constexpr TYPE const &get() const & noexcept @@ -248,15 +252,18 @@ public: * or * @ref exclusive_bound. */ - constexpr range(range_bound lower, range_bound upper) : + constexpr range( + range_bound lower, range_bound upper, sl loc = sl::current()) : m_lower{lower}, m_upper{upper} { if ( lower.is_limited() and upper.is_limited() and (*upper.value() < *lower.value())) - throw range_error{internal::concat( - "Range's lower bound (", *lower.value(), - ") is greater than its upper bound (", *upper.value(), ").")}; + throw range_error{ + internal::concat( + "Range's lower bound (", *lower.value(), + ") is greater than its upper bound (", *upper.value(), ")."), + loc}; } /// Create an empty range. @@ -408,13 +415,13 @@ template struct string_traits> return generic_to_buf(begin, end, value); } - static inline char * - into_buf(char *begin, char *end, range const &value) + static inline char *into_buf( + char *begin, char *end, range const &value, sl loc = sl::current()) { if (value.empty()) { if ((end - begin) <= std::ssize(s_empty)) - throw conversion_overrun{s_overrun.c_str()}; + throw conversion_overrun{s_overrun.c_str(), loc}; char *here = begin + s_empty.copy(begin, std::size(s_empty)); *here++ = '\0'; return here; @@ -422,7 +429,7 @@ template struct string_traits> else { if (end - begin < 4) - throw conversion_overrun{s_overrun.c_str()}; + throw conversion_overrun{s_overrun.c_str(), loc}; char *here = begin; *here++ = (static_cast(value.lower_bound().is_inclusive() ? '[' : '(')); @@ -436,7 +443,7 @@ template struct string_traits> if (upper != nullptr) here = string_traits::into_buf(here, end, *upper) - 1; if ((end - here) < 2) - throw conversion_overrun{s_overrun.c_str()}; + throw conversion_overrun{s_overrun.c_str(), loc}; *here++ = static_cast(value.upper_bound().is_inclusive() ? ']' : ')'); *here++ = '\0'; @@ -444,10 +451,11 @@ template struct string_traits> } } - [[nodiscard]] static inline range from_string(std::string_view text) + [[nodiscard]] static inline range + from_string(std::string_view text, sl loc = sl::current()) { if (std::size(text) < 3) - throw pqxx::conversion_error{err_bad_input(text)}; + throw pqxx::conversion_error{err_bad_input(text), loc}; bool left_inc{false}; switch (text[0]) { @@ -463,11 +471,11 @@ template struct string_traits> (text[2] != 'p' and text[2] != 'P') or (text[3] != 't' and text[3] != 'T') or (text[4] != 'y' and text[4] != 'Y')) - throw pqxx::conversion_error{err_bad_input(text)}; + throw pqxx::conversion_error{err_bad_input(text), loc}; return {}; break; - default: throw pqxx::conversion_error{err_bad_input(text)}; + default: throw pqxx::conversion_error{err_bad_input(text), loc}; } // The field parser uses this to track which field it's parsing, and @@ -482,16 +490,16 @@ template struct string_traits> // We reuse the same field parser we use for composite values and arrays. auto const field_parser{ pqxx::internal::specialize_parse_composite_field>( - pqxx::internal::encoding_group::UTF8)}; - field_parser(index, text, pos, lower, last); - field_parser(index, text, pos, upper, last); + pqxx::internal::encoding_group::UTF8, loc)}; + field_parser(index, text, pos, lower, last, loc); + field_parser(index, text, pos, upper, last, loc); // We need one more character: the closing parenthesis or bracket. if (pos != std::size(text)) - throw pqxx::conversion_error{err_bad_input(text)}; + throw pqxx::conversion_error{err_bad_input(text), loc}; char const closing{text[pos - 1]}; if (closing != ')' and closing != ']') - throw pqxx::conversion_error{err_bad_input(text)}; + throw pqxx::conversion_error{err_bad_input(text), loc}; bool const right_inc{closing == ']'}; range_bound lower_bound{no_bound{}}, upper_bound{no_bound{}}; diff --git a/include/pqxx/result.hxx b/include/pqxx/result.hxx index bd0da6f29..4c7be08f4 100644 --- a/include/pqxx/result.hxx +++ b/include/pqxx/result.hxx @@ -142,10 +142,10 @@ public: */ template auto iter() const; - [[nodiscard]] const_reverse_iterator rbegin() const; - [[nodiscard]] const_reverse_iterator crbegin() const; - [[nodiscard]] const_reverse_iterator rend() const; - [[nodiscard]] const_reverse_iterator crend() const; + [[nodiscard]] const_reverse_iterator rbegin() const noexcept; + [[nodiscard]] const_reverse_iterator crbegin() const noexcept; + [[nodiscard]] const_reverse_iterator rend() const noexcept; + [[nodiscard]] const_reverse_iterator crend() const noexcept; [[nodiscard]] const_iterator begin() const noexcept; [[nodiscard]] const_iterator cbegin() const noexcept; @@ -179,10 +179,10 @@ public: #endif // PQXX_HAVE_MULTIDIM /// Index a row by number, but check that the row number is valid. - row at(size_type) const; + row at(size_type, sl = sl::current()) const; /// Index a field by row number and column number. - field at(size_type, row_size_type) const; + field at(size_type, row_size_type, sl = sl::current()) const; /// Let go of the result's data. /** Use this if you need to deallocate the result data earlier than you can @@ -206,16 +206,19 @@ public: [[nodiscard]] PQXX_PURE row_size_type columns() const noexcept; /// Number of given column (throws exception if it doesn't exist). - [[nodiscard]] row_size_type column_number(zview name) const; + [[nodiscard]] row_size_type + column_number(zview name, sl = sl::current()) const; /// Name of column with this number (throws exception if it doesn't exist) - [[nodiscard]] char const *column_name(row_size_type number) const &; + [[nodiscard]] char const * + column_name(row_size_type number, sl = sl::current()) const &; /// Server-side storage size for field of column's type, in bytes. /** Returns the size of the server's internal representation of the column's * data type. A negative value indicates the data type is variable-length. */ - [[nodiscard]] int column_storage(row_size_type number) const; + [[nodiscard]] int + column_storage(row_size_type number, sl = sl::current()) const; /// Type modifier of the column with this number. /** The meaning of modifier values is type-specific; they typically indicate @@ -231,30 +234,34 @@ public: [[nodiscard]] int column_type_modifier(row_size_type number) const noexcept; /// Return column's type, as an OID from the system catalogue. - [[nodiscard]] oid column_type(row_size_type col_num) const; + [[nodiscard]] oid + column_type(row_size_type col_num, sl = sl::current()) const; /// Return column's type, as an OID from the system catalogue. - [[nodiscard]] oid column_type(zview col_name) const + [[nodiscard]] oid column_type(zview col_name, sl loc = sl::current()) const { - return column_type(column_number(col_name)); + return column_type(column_number(col_name, loc)); } /// What table did this column come from? - [[nodiscard]] oid column_table(row_size_type col_num) const; + [[nodiscard]] oid + column_table(row_size_type col_num, sl = sl::current()) const; /// What table did this column come from? - [[nodiscard]] oid column_table(zview col_name) const + [[nodiscard]] oid column_table(zview col_name, sl loc = sl::current()) const { - return column_table(column_number(col_name)); + return column_table(column_number(col_name, loc), loc); } /// What column in its table did this column come from? - [[nodiscard]] row_size_type table_column(row_size_type col_num) const; + [[nodiscard]] row_size_type + table_column(row_size_type col_num, sl = sl::current()) const; /// What column in its table did this column come from? - [[nodiscard]] row_size_type table_column(zview col_name) const + [[nodiscard]] row_size_type + table_column(zview col_name, sl loc = sl::current()) const { - return table_column(column_number(col_name)); + return table_column(column_number(col_name), loc); } //@} @@ -265,7 +272,7 @@ public: /** @return Identifier of inserted row if exactly one row was inserted, or * @ref oid_none otherwise. */ - [[nodiscard]] PQXX_PURE oid inserted_oid() const; + [[nodiscard]] PQXX_PURE oid inserted_oid(sl = sl::current()) const; /// If command was `INSERT`, `UPDATE`, or `DELETE`: number of affected rows. /** @return Number of affected rows if last command was `INSERT`, `UPDATE`, @@ -312,25 +319,30 @@ public: * The parameter types must have conversions from PostgreSQL's string format * defined; see @ref datatypes. */ - template inline void for_each(CALLABLE &&func) const; + template + inline void for_each(CALLABLE &&func, sl = sl::current()) const; /// Check that result contains exactly `n` rows. /** @return The result itself, for convenience. * @throw @ref unexpected_rows if the actual count is not equal to `n`. */ - result expect_rows(size_type n) const + result expect_rows(size_type n, sl loc = sl::current()) const { auto const sz{size()}; if (sz != n) { // TODO: See whether result contains a generated statement. if (not m_query or m_query->empty()) - throw unexpected_rows{pqxx::internal::concat( - "Expected ", n, " row(s) from query, got ", sz, ".")}; + throw unexpected_rows{ + pqxx::internal::concat( + "Expected ", n, " row(s) from query, got ", sz, "."), + loc}; else - throw unexpected_rows{pqxx::internal::concat( - "Expected ", n, " row(s) from query '", *m_query, "', got ", sz, - ".")}; + throw unexpected_rows{ + pqxx::internal::concat( + "Expected ", n, " row(s) from query '", *m_query, "', got ", sz, + "."), + loc}; } return *this; } @@ -339,7 +351,7 @@ public: /** @return @ref pqxx::row * @throw @ref unexpected_rows if the actual count is not equal to `n`. */ - row one_row() const; + row one_row(sl = sl::current()) const; /// Expect that result contains at moost one row, and return as optional. /** Returns an empty `std::optional` if the result is empty, or if it has @@ -347,12 +359,12 @@ public: * * @throw @ref unexpected_rows is the row count is not 0 or 1. */ - std::optional opt_row() const; + std::optional opt_row(sl = sl::current()) const; /// Expect that result contains no rows. Return result for convenience. - result no_rows() const + result no_rows(sl loc = sl::current()) const { - expect_rows(0); + expect_rows(0, loc); return *this; } @@ -360,18 +372,23 @@ public: /** @return The result itself, for convenience. * @throw @ref usage_error otherwise. */ - result expect_columns(row_size_type cols) const + result expect_columns(row_size_type cols, sl loc = sl::current()) const { auto const actual{columns()}; if (actual != cols) { // TODO: See whether result contains a generated statement. if (not m_query or m_query->empty()) - throw usage_error{pqxx::internal::concat( - "Expected 1 column from query, got ", actual, ".")}; + throw usage_error{ + pqxx::internal::concat( + "Expected 1 column from query, got ", actual, "."), + loc}; else - throw usage_error{pqxx::internal::concat( - "Expected 1 column from query '", *m_query, "', got ", actual, ".")}; + throw usage_error{ + pqxx::internal::concat( + "Expected 1 column from query '", *m_query, "', got ", actual, + "."), + loc}; } return *this; } @@ -380,7 +397,7 @@ public: /** @return The one @ref pqxx::field in the result. * @throw @ref usage_error otherwise. */ - field one_field() const; + field one_field(sl = sl::current()) const; private: using data_pointer = std::shared_ptr; @@ -422,7 +439,7 @@ private: std::shared_ptr const &waiters, internal::encoding_group enc); - PQXX_PRIVATE void check_status(std::string_view desc = ""sv) const; + PQXX_PRIVATE void check_status(std::string_view desc, sl loc) const; friend class pqxx::internal::gate::result_connection; friend class pqxx::internal::gate::result_row; @@ -430,9 +447,9 @@ private: operator bool() const noexcept { return m_data.get() != nullptr; } [[noreturn]] PQXX_PRIVATE PQXX_COLD void - throw_sql_error(std::string const &Err, std::string const &Query) const; + throw_sql_error(std::string const &Err, std::string const &Query, sl) const; PQXX_PRIVATE PQXX_PURE int errorposition() const; - PQXX_PRIVATE std::string status_error() const; + PQXX_PRIVATE std::string status_error(sl) const; friend class pqxx::internal::gate::result_sql_cursor; PQXX_PURE char const *cmd_status() const noexcept; diff --git a/include/pqxx/robusttransaction.hxx b/include/pqxx/robusttransaction.hxx index a9618dd99..6f785a048 100644 --- a/include/pqxx/robusttransaction.hxx +++ b/include/pqxx/robusttransaction.hxx @@ -30,8 +30,8 @@ public: protected: basic_robusttransaction( - connection &cx, zview begin_command, std::string_view tname); - basic_robusttransaction(connection &cx, zview begin_command); + connection &cx, zview begin_command, std::string_view tname, sl); + basic_robusttransaction(connection &cx, zview begin_command, sl); private: using IDType = unsigned long; @@ -40,10 +40,10 @@ private: std::string m_xid; int m_backendpid = -1; - void init(zview begin_command); + void init(zview begin_command, sl); // @warning This function will become `final`. - virtual void do_commit() override; + virtual void do_commit(sl) override; }; } // namespace pqxx::internal @@ -105,12 +105,13 @@ public: /** Create robusttransaction of given name. * @param cx Connection inside which this robusttransaction should live. */ - explicit robusttransaction(connection &cx) : + explicit robusttransaction(connection &cx, sl loc = sl::current()) : internal::basic_robusttransaction{ - cx, pqxx::internal::begin_cmd} + cx, pqxx::internal::begin_cmd, + loc} {} - virtual ~robusttransaction() noexcept override { close(); } + virtual ~robusttransaction() noexcept override { close(sl::current()); } }; /** diff --git a/include/pqxx/row.hxx b/include/pqxx/row.hxx index d8ac841f2..dd66cc199 100644 --- a/include/pqxx/row.hxx +++ b/include/pqxx/row.hxx @@ -97,11 +97,13 @@ public: */ [[nodiscard]] reference operator[](zview col_name) const; - reference at(size_type) const; + /// Address a field by number, but check that the number is in range. + reference at(size_type, sl = sl::current()) const; + /** Address field by name. * @warning This is much slower than indexing by number, or iterating. */ - reference at(zview col_name) const; + reference at(zview col_name, sl = sl::current()) const; [[nodiscard]] constexpr size_type size() const noexcept { return m_end; } @@ -116,24 +118,25 @@ public: */ //@{ /// Number of given column (throws exception if it doesn't exist). - [[nodiscard]] size_type column_number(zview col_name) const; + [[nodiscard]] size_type + column_number(zview col_name, sl = sl::current()) const; /// Return a column's type. - [[nodiscard]] oid column_type(size_type) const; + [[nodiscard]] oid column_type(size_type, sl = sl::current()) const; /// Return a column's type. - [[nodiscard]] oid column_type(zview col_name) const + [[nodiscard]] oid column_type(zview col_name, sl loc = sl::current()) const { - return column_type(column_number(col_name)); + return column_type(column_number(col_name, loc), loc); } /// What table did this column come from? - [[nodiscard]] oid column_table(size_type col_num) const; + [[nodiscard]] oid column_table(size_type col_num, sl = sl::current()) const; /// What table did this column come from? - [[nodiscard]] oid column_table(zview col_name) const + [[nodiscard]] oid column_table(zview col_name, sl loc = sl::current()) const { - return column_table(column_number(col_name)); + return column_table(column_number(col_name, loc), loc); } /// What column number in its table did this result column come from? @@ -144,12 +147,13 @@ public: * @param col_num a zero-based column number in this result set * @return a zero-based column number in originating table */ - [[nodiscard]] size_type table_column(size_type) const; + [[nodiscard]] size_type table_column(size_type, sl = sl::current()) const; /// What column number in its table did this result column come from? - [[nodiscard]] size_type table_column(zview col_name) const + [[nodiscard]] size_type + table_column(zview col_name, sl loc = sl::current()) const { - return table_column(column_number(col_name)); + return table_column(column_number(col_name, loc), loc); } //@} @@ -167,10 +171,10 @@ public: * @throw usage_error If the number of columns in the `row` does not match * the number of fields in `t`. */ - template void to(Tuple &t) const + template void to(Tuple &t, sl loc = sl::current()) const { - check_size(std::tuple_size_v); - convert(t); + check_size(std::tuple_size_v, loc); + convert(t, loc); } /// Extract entire row's values into a tuple. @@ -182,11 +186,12 @@ public: * @throw usage_error If the number of columns in the `row` does not match * the number of fields in `t`. */ - template std::tuple as() const + template + std::tuple as(sl loc = sl::current()) const { - check_size(sizeof...(TYPE)); + check_size(sizeof...(TYPE), loc); using seq = std::make_index_sequence; - return get_tuple>(seq{}); + return get_tuple>(seq{}, loc); } [[deprecated("Swap iterators, not rows.")]] void swap(row &) noexcept; @@ -197,29 +202,32 @@ protected: row(result r, result_size_type index, size_type cols) noexcept; /// Throw @ref usage_error if row size is not `expected`. - void check_size(size_type expected) const + void check_size(size_type expected, sl loc) const { if (size() != expected) - throw usage_error{internal::concat( - "Tried to extract ", expected, " field(s) from a row of ", size(), - ".")}; + throw usage_error{ + internal::concat( + "Tried to extract ", expected, " field(s) from a row of ", size(), + "."), + loc}; } /// Convert to a given tuple of values, don't check sizes. /** We need this for cases where we have a full tuple of field types, but * not a parameter pack. */ - template TUPLE as_tuple() const + template TUPLE as_tuple(sl loc) const { using seq = std::make_index_sequence>; - return get_tuple(seq{}); + return get_tuple(seq{}, loc); } template friend class pqxx::internal::result_iter; /// Convert entire row to tuple fields, without checking row size. - template void convert(Tuple &t) const + template void convert(Tuple &t, sl loc) const { - extract_fields(t, std::make_index_sequence>{}); + extract_fields( + t, std::make_index_sequence>{}, loc); } friend class field; @@ -239,17 +247,17 @@ protected: private: template - void extract_fields(Tuple &t, std::index_sequence) const + void extract_fields(Tuple &t, std::index_sequence, sl loc) const { - (extract_value(t), ...); + (extract_value(t, loc), ...); } template - void extract_value(Tuple &t) const; + void extract_value(Tuple &t, sl loc) const; /// Convert row's values as a new tuple. template - auto get_tuple(std::index_sequence) const + auto get_tuple(std::index_sequence, sl) const { return std::make_tuple(get_field()...); } @@ -543,7 +551,7 @@ const_row_iterator::operator-(const_row_iterator const &i) const noexcept template -inline void row::extract_value(Tuple &t) const +inline void row::extract_value(Tuple &t, sl) const { using field_type = std::remove_cvref_t(t))>; field const f{m_result, m_index, index}; diff --git a/include/pqxx/stream_from.hxx b/include/pqxx/stream_from.hxx index 09f927263..37c387fd0 100644 --- a/include/pqxx/stream_from.hxx +++ b/include/pqxx/stream_from.hxx @@ -256,7 +256,7 @@ public: * @warning The return type may change in the future, to support C++20 * coroutine-based usage. */ - std::vector const *read_row() &; + std::vector const *read_row(sl loc = sl::current()) &; /// Read a raw line of text from the COPY command. /** @warning Do not use this unless you really know what you're doing. */ @@ -276,9 +276,9 @@ private: std::string_view columns, from_table_t, int); template - void extract_fields(Tuple &t, std::index_sequence) const + void extract_fields(Tuple &t, std::index_sequence, sl loc) const { - (extract_value(t), ...); + (extract_value(t, loc), ...); } pqxx::internal::char_finder_func *m_char_finder; @@ -294,10 +294,10 @@ private: void close(); template - void extract_value(Tuple &) const; + void extract_value(Tuple &, sl loc) const; /// Read a line of COPY data, write `m_row` and `m_fields`. - void parse_line(); + void parse_line(sl); }; @@ -320,13 +320,15 @@ inline stream_from::stream_from( {} +// TODO: How can we pass std::source_location? template inline stream_from &stream_from::operator>>(Tuple &t) { + sl loc{sl::current()}; if (m_finished) [[unlikely]] return *this; static constexpr auto tup_size{std::tuple_size_v}; m_fields.reserve(tup_size); - parse_line(); + parse_line(loc); if (m_finished) [[unlikely]] return *this; @@ -335,13 +337,13 @@ template inline stream_from &stream_from::operator>>(Tuple &t) "Tried to extract ", tup_size, " field(s) from a stream of ", std::size(m_fields), ".")}; - extract_fields(t, std::make_index_sequence{}); + extract_fields(t, std::make_index_sequence{}, loc); return *this; } template -inline void stream_from::extract_value(Tuple &t) const +inline void stream_from::extract_value(Tuple &t, sl loc) const { using field_type = std::remove_cvref_t(t))>; using nullity = nullness; @@ -349,14 +351,14 @@ inline void stream_from::extract_value(Tuple &t) const if constexpr (nullity::always_null) { if (std::data(m_fields[index]) != nullptr) - throw conversion_error{"Streaming non-null value into null field."}; + throw conversion_error{"Streaming non-null value into null field.", loc}; } else if (std::data(m_fields[index]) == nullptr) { if constexpr (nullity::has_null) std::get(t) = nullity::null(); else - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); } else { diff --git a/include/pqxx/stream_to.hxx b/include/pqxx/stream_to.hxx index ff9df1043..565a24d1f 100644 --- a/include/pqxx/stream_to.hxx +++ b/include/pqxx/stream_to.hxx @@ -102,9 +102,10 @@ public: * the stream will write all columns in the table, in schema order. */ static stream_to raw_table( - transaction_base &tx, std::string_view path, std::string_view columns = "") + transaction_base &tx, std::string_view path, std::string_view columns = "", + sl loc = sl::current()) { - return {tx, path, columns}; + return {tx, path, columns, loc}; } /// Create a `stream_to` writing to a named table and columns. @@ -202,7 +203,8 @@ public: */ template stream_to &operator<<(Row const &row) { - write_row(row); + sl loc{sl::current()}; + write_row(row, loc); return *this; } @@ -220,26 +222,28 @@ public: * * The preferred way to insert a row is @c write_values. */ - template void write_row(Row const &row) + template void write_row(Row const &row, sl loc = sl::current()) { - fill_buffer(row); - write_buffer(); + fill_buffer(row, loc); + write_buffer(loc); } + // TODO: How can we pass std::source_location here? /// Insert values as a row. /** This is the recommended way of inserting data. Pass your field values, * of any convertible type. */ template void write_values(Ts const &...fields) { + auto loc{sl::current()}; fill_buffer(fields...); - write_buffer(); + write_buffer(loc); } private: /// Stream a pre-quoted table name and columns list. stream_to( - transaction_base &tx, std::string_view path, std::string_view columns); + transaction_base &tx, std::string_view path, std::string_view columns, sl); bool m_finished = false; @@ -253,12 +257,12 @@ private: internal::char_finder_func *m_finder; /// Write a row of raw text-format data into the destination table. - void write_raw_line(std::string_view); + void write_raw_line(std::string_view, sl); /// Write a row of data from @c m_buffer into the destination table. /** Resets the buffer for the next row. */ - void write_buffer(); + void write_buffer(sl); /// COPY encoding for a null field, plus subsequent separator. static constexpr std::string_view null_field{"\\N\t"}; @@ -283,7 +287,7 @@ private: } /// Append escaped version of @c data to @c m_buffer, plus a tab. - void escape_field_to_buffer(std::string_view data); + void escape_field_to_buffer(std::string_view data, sl loc); /// Append string representation for @c f to @c m_buffer. /** This is for the general case, where the field may contain a value. @@ -294,7 +298,7 @@ private: */ template std::enable_if_t::always_null> - append_to_buffer(Field const &f) + append_to_buffer(Field const &f, sl loc) { // We append each field, terminated by a tab. That will leave us with // one tab too many, assuming we write any fields at all; we remove that @@ -335,7 +339,7 @@ private: { // This string may need escaping. m_field_buf.resize(budget); - escape_field_to_buffer(f); + escape_field_to_buffer(f, loc); } else if constexpr ( std::is_same_v> or @@ -345,7 +349,7 @@ private: // Optional string. It's not null (we checked for that above), so... // Treat like a string. m_field_buf.resize(budget); - escape_field_to_buffer(f.value()); + escape_field_to_buffer(f.value(), loc); } // TODO: Support deleter template argument on unique_ptr. else if constexpr ( @@ -360,7 +364,7 @@ private: // Effectively also an optional string. It's not null (we checked // for that above). m_field_buf.resize(budget); - escape_field_to_buffer(*f); + escape_field_to_buffer(*f, loc); } else { @@ -369,7 +373,7 @@ private: m_field_buf.resize(budget); auto const data{m_field_buf.data()}; escape_field_to_buffer( - traits::to_buf(data, data + std::size(m_field_buf), f)); + traits::to_buf(data, data + std::size(m_field_buf), f), loc); } } } @@ -383,7 +387,7 @@ private: */ template std::enable_if_t::always_null> - append_to_buffer(Field const &) + append_to_buffer(Field const &, sl) { m_buffer.append(null_field); } @@ -392,7 +396,7 @@ private: template std::enable_if_t< not std::is_same_v, char>> - fill_buffer(Container const &c) + fill_buffer(Container const &c, sl loc) { // To avoid unnecessary allocations and deallocations, we run through c // twice: once to determine how much buffer space we may need, and once to @@ -400,7 +404,7 @@ private: std::size_t budget{0}; for (auto const &f : c) budget += estimate_buffer(f); m_buffer.reserve(budget); - for (auto const &f : c) append_to_buffer(f); + for (auto const &f : c) append_to_buffer(f, loc); } /// Estimate how many buffer bytes we need to write tuple. @@ -413,24 +417,26 @@ private: /// Write tuple of fields to @c m_buffer. template - void append_tuple(Tuple const &t, std::index_sequence) + void append_tuple(Tuple const &t, std::index_sequence, sl loc) { - (append_to_buffer(std::get(t)), ...); + (append_to_buffer(std::get(t), loc), ...); } /// Write raw COPY line into @c m_buffer, based on a tuple of fields. - template void fill_buffer(std::tuple const &t) + template + void fill_buffer(std::tuple const &t, sl loc) { using indexes = std::make_index_sequence; m_buffer.reserve(budget_tuple(t, indexes{})); - append_tuple(t, indexes{}); + append_tuple(t, indexes{}, loc); } + // TODO: How can we pass std::source_location here? /// Write raw COPY line into @c m_buffer, based on varargs fields. template void fill_buffer(const Ts &...fields) { - (..., append_to_buffer(fields)); + (..., append_to_buffer(fields, sl::current())); } constexpr static std::string_view s_classname{"stream_to"}; diff --git a/include/pqxx/subtransaction.hxx b/include/pqxx/subtransaction.hxx index bb9564c07..ee1712115 100644 --- a/include/pqxx/subtransaction.hxx +++ b/include/pqxx/subtransaction.hxx @@ -40,6 +40,7 @@ namespace pqxx * do_firstpart(tx); * * // Attempt to delete our temporary table if it already existed. + * // (In reality you would just use the IF EXISTS option to DROP TABLE.) * try * { * subtransaction S(tx, "droptemp"); @@ -78,10 +79,17 @@ class PQXX_LIBEXPORT subtransaction : public transaction_focus, { public: /// Nest a subtransaction nested in another transaction. - explicit subtransaction(dbtransaction &t, std::string_view tname = ""sv); + explicit subtransaction( + dbtransaction &t, std::string_view tname, sl = sl::current()); + + /// Nest a subtransaction nested in another transaction. + explicit subtransaction(dbtransaction &t, sl loc = sl::current()) : + subtransaction(t, "", loc) + {} /// Nest a subtransaction in another subtransaction. - explicit subtransaction(subtransaction &t, std::string_view name = ""sv); + explicit subtransaction( + subtransaction &t, std::string_view name = ""sv, sl loc = sl::current()); virtual ~subtransaction() noexcept override; @@ -90,7 +98,7 @@ private: { return quote_name(transaction_focus::name()); } - virtual void do_commit() override; + virtual void do_commit(sl) override; }; } // namespace pqxx #endif diff --git a/include/pqxx/time.hxx b/include/pqxx/time.hxx index effed05e0..7976562af 100644 --- a/include/pqxx/time.hxx +++ b/include/pqxx/time.hxx @@ -65,11 +65,12 @@ template<> struct PQXX_LIBEXPORT string_traits return generic_to_buf(begin, end, value); } - static char * - into_buf(char *begin, char *end, std::chrono::year_month_day const &value); + static char *into_buf( + char *begin, char *end, std::chrono::year_month_day const &value, + sl = sl::current()); [[nodiscard]] static std::chrono::year_month_day - from_string(std::string_view text); + from_string(std::string_view text, sl = sl::current()); [[nodiscard]] static std::size_t size_buffer(std::chrono::year_month_day const &) noexcept diff --git a/include/pqxx/transaction.hxx b/include/pqxx/transaction.hxx index 6e6844ce1..81bb4b569 100644 --- a/include/pqxx/transaction.hxx +++ b/include/pqxx/transaction.hxx @@ -25,14 +25,17 @@ class PQXX_LIBEXPORT basic_transaction : public dbtransaction { protected: basic_transaction( - connection &cx, zview begin_command, std::string_view tname); - basic_transaction(connection &cx, zview begin_command, std::string &&tname); - basic_transaction(connection &cx, zview begin_command); + connection &cx, zview begin_command, std::string_view tname, + sl = sl::current()); + basic_transaction( + connection &cx, zview begin_command, std::string &&tname, + sl = sl::current()); + basic_transaction(connection &cx, zview begin_command, sl = sl::current()); virtual ~basic_transaction() noexcept override = 0; private: - virtual void do_commit() override; + virtual void do_commit(sl) override; }; } // namespace pqxx::internal @@ -77,9 +80,9 @@ public: * @param tname Optional name for transaction. Must begin with a letter and * may contain letters and digits only. */ - transaction(connection &cx, std::string_view tname) : + transaction(connection &cx, std::string_view tname, sl loc = sl::current()) : internal::basic_transaction{ - cx, internal::begin_cmd, tname} + cx, internal::begin_cmd, tname, loc} {} /// Begin a transaction. @@ -87,12 +90,12 @@ public: * @param cx Connection for this transaction to operate on. * may contain letters and digits only. */ - explicit transaction(connection &cx) : + explicit transaction(connection &cx, sl loc = sl::current()) : internal::basic_transaction{ - cx, internal::begin_cmd} + cx, internal::begin_cmd, loc} {} - virtual ~transaction() noexcept override { close(); } + virtual ~transaction() noexcept override { close(sl::current()); } }; diff --git a/include/pqxx/transaction_base.hxx b/include/pqxx/transaction_base.hxx index 19942e01e..17dd839cf 100644 --- a/include/pqxx/transaction_base.hxx +++ b/include/pqxx/transaction_base.hxx @@ -172,13 +172,13 @@ public: * @ref robusttransaction which takes some special precautions to reduce this * risk. */ - void commit(); + void commit(sl = sl::current()); /// Abort the transaction. /** No special effort is required to call this function; it will be called * implicitly when the transaction is destructed. */ - void abort(); + void abort(sl = sl::current()); /** * @ingroup escaping-functions @@ -190,12 +190,19 @@ public: * functions on the connection object. */ //@{ + [[nodiscard]] std::string esc(char const str[], sl loc = sl::current()) + { + return conn().esc(str, loc); + } + + // TODO: De-templatise this so we can pass std::source_location. /// Escape string for use as SQL string literal in this transaction. template [[nodiscard]] auto esc(ARGS &&...args) const { return conn().esc(std::forward(args)...); } + // TODO: De-templatise this so we can pass std::source_location. /// Escape binary data for use as SQL string literal in this transaction. /** Raw, binary data is treated differently from regular strings. Binary * strings are never interpreted as text, so they may safely include byte @@ -217,30 +224,35 @@ public: /** Takes a binary string as escaped by PostgreSQL, and returns a restored * copy of the original binary data. */ - [[nodiscard]] bytes unesc_bin(zview text) { return conn().unesc_bin(text); } + [[nodiscard]] bytes unesc_bin(zview text, sl loc = sl::current()) + { + return conn().unesc_bin(text, loc); + } /// Unescape binary data, e.g. from a `bytea` field. /** Takes a binary string as escaped by PostgreSQL, and returns a restored * copy of the original binary data. */ - [[nodiscard]] bytes unesc_bin(char const text[]) + [[nodiscard]] bytes unesc_bin(char const text[], sl loc = sl::current()) { - return conn().unesc_bin(text); + return conn().unesc_bin(text, loc); } /// Represent object as SQL string, including quoting & escaping. /** Nulls are recognized and represented as SQL nulls. */ - template [[nodiscard]] std::string quote(T const &t) const + template + [[nodiscard]] std::string quote(T const &t, sl loc = sl::current()) const { - return conn().quote(t); + return conn().quote(t, loc); } /// Binary-escape and quote a binary string for use as an SQL constant. /** For binary data you can also just use @ref quote(data). */ template - [[nodiscard]] std::string quote_raw(DATA const &data) const + [[nodiscard]] std::string + quote_raw(DATA const &data, sl loc = sl::current()) const { - return conn().quote_raw(data); + return conn().quote_raw(data, loc); } /// Escape an SQL identifier for use in a query. @@ -250,10 +262,11 @@ public: } /// Escape string for literal LIKE match. - [[nodiscard]] std::string - esc_like(std::string_view bin, char escape_char = '\\') const + [[nodiscard]] std::string esc_like( + std::string_view bin, char escape_char = '\\', + sl loc = sl::current()) const { - return conn().esc_like(bin, escape_char); + return conn().esc_like(bin, escape_char, loc); } //@} @@ -299,13 +312,14 @@ public: * @return A result set describing the query's or command's result. */ [[deprecated("The desc parameter is going away.")]] - result exec(std::string_view query, std::string_view desc); + result + exec(std::string_view query, std::string_view desc, sl = sl::current()); // TODO: Wrap PQdescribePrepared(). - result exec(std::string_view query, params parms) + result exec(std::string_view query, params parms, sl loc = sl::current()) { - return internal_exec_params(query, parms.make_c_params()); + return internal_exec_params(query, parms.make_c_params(loc), loc); } /// Execute a command. @@ -313,7 +327,7 @@ public: * @param query Query or command to execute. * @return A result set describing the query's or command's result. */ - result exec(std::string_view query); + result exec(std::string_view query, sl = sl::current()); /// Execute a command. /** @@ -427,9 +441,9 @@ public: * @throw unexpected_rows If the query did not return exactly 1 row. * @throw usage_error If the row did not contain exactly 1 field. */ - template TYPE query_value(zview query) + template TYPE query_value(zview query, sl loc = sl::current()) { - return exec(query).one_field().as(); + return exec(query, loc).one_field(loc).as(loc); } /// Perform query returning exactly one row, and convert its fields. @@ -441,9 +455,12 @@ public: * the number of fields in the tuple. */ template - [[nodiscard]] std::tuple query1(zview query) + [[nodiscard]] std::tuple query1(zview query, sl loc = sl::current()) { - return exec(query).expect_columns(sizeof...(TYPE)).one_row().as(); + return exec(query, loc) + .expect_columns(sizeof...(TYPE), loc) + .one_row(loc) + .as(loc); } /// Query at most one row of data, and if there is one, convert it. @@ -455,15 +472,17 @@ public: * the number of fields in the tuple. */ template - [[nodiscard]] std::optional> query01(zview query) + [[nodiscard]] std::optional> + query01(zview query, sl loc = sl::current()) { - std::optional const r{exec(query).opt_row()}; + std::optional const r{exec(query, loc).opt_row(loc)}; if (r) - return {r->as()}; + return {r->as(loc)}; else return {}; } + // C++20: Update type requirements. /// Execute a query, in streaming fashion; loop over the results row by row. /** Converts the rows to `std::tuple`, of the column types you specify. * @@ -517,18 +536,18 @@ public: * requirements may loosen a bit once libpqxx moves on to C++20. */ template - [[nodiscard]] auto stream(std::string_view query) & + [[nodiscard]] auto stream(std::string_view query, sl loc = sl::current()) & { - return pqxx::internal::stream_query{*this, query}; + return pqxx::internal::stream_query{*this, query, loc}; } /// Execute a query, in streaming fashion; loop over the results row by row. /** Like @ref stream(std::string_view), but with parameters. */ template - [[nodiscard]] auto stream(std::string_view query, params parms) & + [[nodiscard]] auto stream(std::string_view query, params parms, sl loc) & { - return pqxx::internal::stream_query{*this, query, parms}; + return pqxx::internal::stream_query{*this, query, parms, loc}; } // C++20: Concept like std::invocable, but without specifying param types. @@ -562,21 +581,23 @@ public: * @ref datatypes. */ template - auto for_stream(std::string_view query, CALLABLE &&func) + auto + for_stream(std::string_view query, CALLABLE &&func, sl loc = sl::current()) { + // TODO: Can we pass loc into func if appropriate? using param_types = pqxx::internal::strip_types_t>; param_types const *const sample{nullptr}; - auto data_stream{stream_like(query, sample)}; + auto data_stream{stream_like(query, sample, loc)}; for (auto const &fields : data_stream) std::apply(func, fields); } template [[deprecated( "pqxx::transaction_base::for_each is now called for_stream.")]] auto - for_each(std::string_view query, CALLABLE &&func) + for_each(std::string_view query, CALLABLE &&func, sl loc = sl::current()) { - return for_stream(query, std::forward(func)); + return for_stream(query, std::forward(func), loc); } /// Execute query, read full results, then iterate rows of data. @@ -611,17 +632,17 @@ public: * @return Something you can iterate using "range `for`" syntax. The actual * type details may change. */ - template auto query(zview query) + template auto query(zview query, sl loc = sl::current()) { - return exec(query).iter(); + return exec(query, loc).iter(); } /// Perform query, expect given number of rows, iterate results. template [[deprecated("Use query() instead, and call expect_rows() on the result.")]] - auto query_n(result::size_type rows, zview query) + auto query_n(result::size_type rows, zview query, sl loc = sl::current()) { - return exec(query).expect_rows(rows).iter(); + return exec(query, loc).expect_rows(rows, loc).iter(); } // C++20: Concept like std::invocable, but without specifying param types. @@ -634,9 +655,10 @@ public: * 2. The `exec` functions are faster for small results, but slower for large * results. */ - template void for_query(zview query, CALLABLE &&func) + template + void for_query(zview query, CALLABLE &&func, sl loc = sl::current()) { - exec(query).for_each(std::forward(func)); + exec(query).for_each(std::forward(func), loc); } /** @@ -678,7 +700,7 @@ public: [[deprecated("Use exec(zview, params) instead.")]] result exec_params(std::string_view query, Args &&...args) { - return exec(query, params{args...}); + return exec(query, params{args...}, sl::current()); } // Execute parameterised statement, expect a single-row result. @@ -709,8 +731,10 @@ public: [[deprecated("Use exec(), and call expect_rows() on the result.")]] result exec_params_n(std::size_t rows, zview query, Args &&...args) { - return exec(query, params{args...}) - .expect_rows(check_cast(rows, "number of rows")); + sl loc{sl::current()}; + return exec(query, params{args...}, loc) + .expect_rows( + check_cast(rows, "number of rows", loc), loc); } // Execute parameterised statement, expect exactly a given number of rows. @@ -757,9 +781,10 @@ public: * @return Something you can iterate using "range `for`" syntax. The actual * type details may change. */ - template auto query(zview query, params const &parms) + template + auto query(zview query, params const &parms, sl loc = sl::current()) { - return exec(query, parms).iter(); + return exec(query, parms, loc).iter(); } /// Perform query parameterised, expect given number of rows, iterate @@ -786,9 +811,13 @@ public: * @throw unexpected_rows If the query did not return exactly 1 row. * @throw usage_error If the row did not contain exactly 1 field. */ - template TYPE query_value(zview query, params const &parms) + template + TYPE query_value(zview query, params const &parms, sl loc = sl::current()) { - return exec(query, parms).expect_columns(1).one_field().as(); + return exec(query, parms, loc) + .expect_columns(1, loc) + .one_field(loc) + .as(loc); } /// Perform query returning exactly one row, and convert its fields. @@ -801,9 +830,10 @@ public: */ template [[nodiscard]] - std::tuple query1(zview query, params const &parms) + std::tuple + query1(zview query, params const &parms, sl loc = sl::current()) { - return exec(query, parms).one_row().as(); + return exec(query, parms, loc).one_row(loc).as(loc); } /// Query at most one row of data, and if there is one, convert it. @@ -816,11 +846,11 @@ public: */ template [[nodiscard]] std::optional> - query01(zview query, params const &parms) + query01(zview query, params const &parms, sl loc = sl::current()) { - std::optional r{exec(query, parms).opt_row()}; + std::optional r{exec(query, parms, loc).opt_row(loc)}; if (r) - return {r->as()}; + return {r->as(loc)}; else return {}; } @@ -839,9 +869,10 @@ public: * results. */ template - void for_query(zview query, CALLABLE &&func, params const &parms) + void for_query( + zview query, CALLABLE &&func, params const &parms, sl loc = sl::current()) { - exec(query, parms).for_each(std::forward(func)); + exec(query, parms, loc).for_each(std::forward(func), loc); } /// Send a notification. @@ -860,7 +891,25 @@ public: * receive. If you leave this out, they will receive an empty string as the * payload. */ - void notify(std::string_view channel, std::string_view payload = {}); + void notify( + std::string_view channel, std::string_view payload, sl = sl::current()); + + /// Send a notification (without payload). + /** Convenience shorthand for executing a "NOTIFY" command. Most of the + * logic for handling _incoming_ notifications is in @ref pqxx::connection + * (particularly @ref pqxx::connection::listen), but _outgoing_ + * notifications happen here. + * + * Unless this transaction is a nontransaction, the actual notification only + * goes out once the outer transaction is committed. + * + * @param channel Name of the "channel" on which clients will need to be + * listening in order to receive this notification. + */ + void notify(std::string_view channel, sl loc = sl::current()) + { + notify(channel, {}, loc); + } //@} /// Execute a prepared statement, with optional arguments. @@ -872,10 +921,10 @@ public: } /// Execute a prepared statement taking no parameters. - result exec(prepped statement) + result exec(prepped statement, sl loc = sl::current()) { params pp; - return internal_exec_prepared(statement, pp.make_c_params()); + return internal_exec_prepared(statement, pp.make_c_params(loc), loc); } /// Execute prepared statement, read full results, iterate rows of data. @@ -885,9 +934,35 @@ public: * type details may change. */ template - auto query(prepped statement, params const &parms = {}) + auto query(prepped statement, params const &parms, sl loc = sl::current()) { - return exec(statement, parms).iter(); + return exec(statement, parms, loc).iter(); + } + + /// Execute prepared statement, read full results, iterate rows of data. + /** Like @ref query(zview), but using a prepared statement. + * + * @return Something you can iterate using "range `for`" syntax. The actual + * type details may change. + */ + template + auto query(prepped statement, sl loc = sl::current()) + { + return exec(statement, {}, loc).iter(); + } + + /// Perform prepared statement returning exactly 1 value. + /** This is just like @ref query_value(zview), but using a prepared + * statement. + */ + template + TYPE + query_value(prepped statement, params const &parms, sl loc = sl::current()) + { + return exec(statement, parms, loc) + .expect_columns(1, loc) + .one_field(loc) + .as(loc); } /// Perform prepared statement returning exactly 1 value. @@ -895,9 +970,12 @@ public: * statement. */ template - TYPE query_value(prepped statement, params const &parms = {}) + TYPE query_value(prepped statement, sl loc = sl::current()) { - return exec(statement, parms).expect_columns(1).one_field().as(); + return exec(statement, {}, loc) + .expect_columns(1, loc) + .one_field(loc) + .as(loc); } // C++20: Concept like std::invocable, but without specifying param types. @@ -905,18 +983,27 @@ public: /** This is just like @ref for_query(zview), but using a prepared statement. */ template - void for_query(prepped statement, CALLABLE &&func, params const &parms = {}) + void for_query( + prepped statement, CALLABLE &&func, params const &parms, + sl loc = sl::current()) { - exec(statement, parms).for_each(std::forward(func)); + exec(statement, parms, loc).for_each(std::forward(func), loc); } - // TODO: stream() with prepped. - // TODO: stream_like() with prepped. + // C++20: Concept like std::invocable, but without specifying param types. + /// Execute prepared statement, load result, perform `func` for each row. + /** This is just like @ref for_query(zview), but using a prepared statement. + */ + template + void for_query(prepped statement, CALLABLE &&func, sl loc = sl::current()) + { + exec(statement, {}, loc).for_each(std::forward(func), loc); + } /// Execute a prepared statement with parameters. - result exec(prepped statement, params const &parms) + result exec(prepped statement, params const &parms, sl loc = sl::current()) { - return internal_exec_prepared(statement, parms.make_c_params()); + return internal_exec_prepared(statement, parms.make_c_params(loc), loc); } /// Execute a prepared statement, and expect a single-row result. @@ -927,7 +1014,8 @@ public: "Use exec(string_view, params) and call one_row() on the result.")]] row exec_prepared1(zview statement, Args &&...args) { - return exec(prepped{statement}, params{args...}).one_row(); + sl loc{sl::current()}; + return exec(prepped{statement}, params{args...}).one_row(loc); } /// Execute a prepared statement, and expect a result with zero rows. @@ -938,7 +1026,8 @@ public: "Use exec(prepped, params), and call no_rows() on the result.")]] result exec_prepared0(zview statement, Args &&...args) { - return exec(prepped{statement}, params{args...}).no_rows(); + sl loc{sl::current()}; + return exec(prepped{statement}, params{args...}).no_rows(loc); } /// Execute a prepared statement, expect a result with given number of rows. @@ -951,7 +1040,9 @@ public: result exec_prepared_n(result::size_type rows, zview statement, Args &&...args) { - return exec(pqxx::prepped{statement}, params{args...}).expect_rows(rows); + sl loc{sl::current()}; + return exec(pqxx::prepped{statement}, params{args...}) + .expect_rows(rows, loc); } /** @@ -1025,16 +1116,16 @@ protected: void register_transaction(); /// End transaction. To be called by implementing class' destructor. - void close() noexcept; + void close(sl = sl::current()) noexcept; /// To be implemented by derived implementation class: commit transaction. - virtual void do_commit() = 0; + virtual void do_commit(sl) = 0; /// Transaction type-specific way of aborting a transaction. /** @warning This will become "final", since this function can be called * from the implementing class destructor. */ - virtual void do_abort(); + virtual void do_abort(sl); /// Set the rollback command. void set_rollback_cmd(std::shared_ptr cmd) @@ -1043,9 +1134,16 @@ protected: } /// Execute query on connection directly. - result direct_exec(std::string_view, std::string_view desc = ""sv); - result - direct_exec(std::shared_ptr, std::string_view desc = ""sv); + result direct_exec(std::string_view, std::string_view desc, sl); + result direct_exec(std::string_view query, sl loc) + { + return direct_exec(query, "", loc); + } + result direct_exec(std::shared_ptr, std::string_view desc, sl); + result direct_exec(std::shared_ptr query, sl loc) + { + return direct_exec(query, "", loc); + } private: enum class status @@ -1059,10 +1157,10 @@ private: PQXX_PRIVATE void check_pending_error(); result internal_exec_prepared( - std::string_view statement, internal::c_params const &args); + std::string_view statement, internal::c_params const &args, sl); - result - internal_exec_params(std::string_view query, internal::c_params const &args); + result internal_exec_params( + std::string_view query, internal::c_params const &args, sl); /// Describe this transaction to humans, e.g. "transaction 'foo'". [[nodiscard]] std::string description() const; @@ -1075,9 +1173,11 @@ private: /// Like @ref stream(), but takes a tuple rather than a parameter pack. template - auto stream_like(std::string_view query, std::tuple const *) + auto stream_like( + std::string_view query, std::tuple const *, + sl loc = sl::current()) { - return stream(query); + return stream(query, loc); } connection &m_conn; diff --git a/include/pqxx/transactor.hxx b/include/pqxx/transactor.hxx index a188941b1..79bb16402 100644 --- a/include/pqxx/transactor.hxx +++ b/include/pqxx/transactor.hxx @@ -97,7 +97,8 @@ namespace pqxx * @return Whatever your callback returns. */ template -inline auto perform(TRANSACTION_CALLBACK &&callback, int attempts = 3) +inline auto +perform(TRANSACTION_CALLBACK &&callback, int attempts, sl loc = sl::current()) -> std::invoke_result_t { if (attempts <= 0) @@ -145,7 +146,45 @@ inline auto perform(TRANSACTION_CALLBACK &&callback, int attempts = 3) continue; } } - throw pqxx::internal_error{"No outcome reached on perform()."}; + throw pqxx::internal_error{"No outcome reached on perform().", loc}; +} + + +/// Simple way to execute a transaction with automatic retry. +/** + * Executes your transaction code as a callback. Repeats it until it completes + * normally, or it throws an error other than the few libpqxx-generated + * exceptions that the framework understands, or after a given number of failed + * attempts, or if the transaction ends in an "in-doubt" state. + * + * (An in-doubt state is one where libpqxx cannot determine whether the server + * finally committed a transaction or not. This can happen if the network + * connection to the server is lost just while we're waiting for its reply to + * a "commit" statement. The server may have completed the commit, or not, but + * it can't tell you because there's no longer a connection. + * + * Using this still takes a bit of care. If your callback makes use of data + * from the database, you'll probably have to query that data within your + * callback. If the attempt to perform your callback fails, and the framework + * tries again, you'll be in a new transaction and the data in the database may + * have changed under your feet. + * + * Also be careful about changing variables or data structures from within + * your callback. The run may still fail, and perhaps get run again. The + * ideal way to do it (in most cases) is to return your result from your + * callback, and change your program's data state only after @ref perform + * completes successfully. + * + * @param callback Transaction code that can be called with no arguments. + * @param attempts Maximum number of times to attempt performing callback. + * Must be greater than zero. + * @return Whatever your callback returns. + */ +template +inline auto perform(TRANSACTION_CALLBACK &&callback, sl loc = sl::current()) + -> std::invoke_result_t +{ + return perform(callback, 3, loc); } } // namespace pqxx //@} diff --git a/include/pqxx/types.hxx b/include/pqxx/types.hxx index 523693db9..11fb0ad72 100644 --- a/include/pqxx/types.hxx +++ b/include/pqxx/types.hxx @@ -17,11 +17,15 @@ #include #include #include +#include #include namespace pqxx { +/// Conenience alias for `std::source_location`. It's just too long. +using sl = std::source_location; + /// Number of rows in a result set. using result_size_type = int; diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index 64aedfa13..dd2c4d672 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -88,7 +88,7 @@ template inline constexpr void ignore_unused(T &&...) noexcept * or both floating-point types. */ template -inline TO check_cast(FROM value, std::string_view description) +inline TO check_cast(FROM value, std::string_view description, sl loc) { static_assert(std::is_arithmetic_v); static_assert(std::is_arithmetic_v); @@ -110,7 +110,8 @@ inline TO check_cast(FROM value, std::string_view description) if constexpr (std::is_signed_v) { if (value < to_limits::lowest()) - throw range_error{internal::cat2("Cast underflow: "sv, description)}; + throw range_error{ + internal::cat2("Cast underflow: "sv, description), loc}; } else { @@ -118,8 +119,10 @@ inline TO check_cast(FROM value, std::string_view description) // there may not be a good broader type in which the compiler can even // perform our check. if (value < 0) - throw range_error{internal::cat2( - "Casting negative value to unsigned type: "sv, description)}; + throw range_error{ + internal::cat2( + "Casting negative value to unsigned type: "sv, description), + loc}; } } else @@ -137,13 +140,14 @@ inline TO check_cast(FROM value, std::string_view description) if constexpr (from_max > to_max) { if (std::cmp_greater(value, to_max)) - throw range_error{internal::cat2("Cast overflow: "sv, description)}; + throw range_error{ + internal::cat2("Cast overflow: "sv, description), loc}; } } else if constexpr ((from_limits::max)() > (to_limits::max)()) { if (value > (to_limits::max)()) - throw range_error{internal::cat2("Cast overflow: ", description)}; + throw range_error{internal::cat2("Cast overflow: ", description), loc}; } return static_cast(value); @@ -457,11 +461,11 @@ std::string PQXX_LIBEXPORT esc_bin(bytes_view binary_data); /// Reconstitute binary data from its escaped version. void PQXX_LIBEXPORT -unesc_bin(std::string_view escaped_data, std::byte buffer[]); +unesc_bin(std::string_view escaped_data, std::byte buffer[], sl loc); /// Reconstitute binary data from its escaped version. -bytes PQXX_LIBEXPORT unesc_bin(std::string_view escaped_data); +bytes PQXX_LIBEXPORT unesc_bin(std::string_view escaped_data, sl loc); /// Helper for determining a function's parameter types. diff --git a/src/array.cxx b/src/array.cxx index 5d5594ee4..5d76af9d0 100644 --- a/src/array.cxx +++ b/src/array.cxx @@ -28,39 +28,39 @@ namespace pqxx /// Scan to next glyph in the buffer. Assumes there is one. template [[nodiscard]] std::string::size_type -array_parser::scan_glyph(std::string::size_type pos) const +array_parser::scan_glyph(std::string::size_type pos, sl loc) const { return pqxx::internal::glyph_scanner::call( - std::data(m_input), std::size(m_input), pos); + std::data(m_input), std::size(m_input), pos, loc); } /// Scan to next glyph in a substring. Assumes there is one. template std::string::size_type array_parser::scan_glyph( - std::string::size_type pos, std::string::size_type end) const + std::string::size_type pos, std::string::size_type end, sl loc) const { return pqxx::internal::glyph_scanner::call( - std::data(m_input), end, pos); + std::data(m_input), end, pos, loc); } /// Find the end of a double-quoted SQL string in an SQL array. template -std::string::size_type array_parser::scan_double_quoted_string() const +std::string::size_type array_parser::scan_double_quoted_string(sl loc) const { return pqxx::internal::scan_double_quoted_string( - std::data(m_input), std::size(m_input), m_pos); + std::data(m_input), std::size(m_input), m_pos, loc); } /// Parse a double-quoted SQL string: un-quote it and un-escape it. template -std::string -array_parser::parse_double_quoted_string(std::string::size_type end) const +std::string array_parser::parse_double_quoted_string( + std::string::size_type end, sl loc) const { return pqxx::internal::parse_double_quoted_string( - std::data(m_input), end, m_pos); + std::data(m_input), end, m_pos, loc); } @@ -68,10 +68,10 @@ array_parser::parse_double_quoted_string(std::string::size_type end) const /** Assumes UTF-8 or an ASCII-superset single-byte encoding. */ template -std::string::size_type array_parser::scan_unquoted_string() const +std::string::size_type array_parser::scan_unquoted_string(sl loc) const { return pqxx::internal::scan_unquoted_string( - std::data(m_input), std::size(m_input), m_pos); + std::data(m_input), std::size(m_input), m_pos, loc); } @@ -81,49 +81,52 @@ std::string::size_type array_parser::scan_unquoted_string() const */ template std::string_view -array_parser::parse_unquoted_string(std::string::size_type end) const +array_parser::parse_unquoted_string(std::string::size_type end, sl loc) const { return pqxx::internal::parse_unquoted_string( - std::data(m_input), end, m_pos); + std::data(m_input), end, m_pos, loc); } array_parser::array_parser( std::string_view input, internal::encoding_group enc) : - m_input{input}, m_impl{specialize_for_encoding(enc)} + m_input{input}, m_impl{specialize_for_encoding(enc, sl::current())} {} template -std::pair array_parser::parse_array_step() +std::pair +array_parser::parse_array_step(sl loc) { std::string value{}; if (m_pos >= std::size(m_input)) return std::make_pair(juncture::done, value); - auto [found, end] = [this, &value] { - if (scan_glyph(m_pos) - m_pos > 1) + auto [found, end] = [this, &value, loc] { + if (scan_glyph(m_pos, loc) - m_pos > 1) { // Non-ASCII unquoted string. - auto const endpoint = scan_unquoted_string(); - value = parse_unquoted_string(endpoint); + auto const endpoint = scan_unquoted_string(loc); + value = parse_unquoted_string(endpoint, loc); return std::tuple{juncture::string_value, endpoint}; } else switch (m_input[m_pos]) { - case '\0': throw failure{"Unexpected zero byte in array."}; - case '{': return std::tuple{juncture::row_start, scan_glyph(m_pos)}; - case '}': return std::tuple{juncture::row_end, scan_glyph(m_pos)}; + case '\0': throw failure{"Unexpected zero byte in array.", loc}; + case '{': + return std::tuple{juncture::row_start, scan_glyph(m_pos, loc)}; + case '}': + return std::tuple{juncture::row_end, scan_glyph(m_pos, loc)}; case '"': { - auto const endpoint = scan_double_quoted_string(); - value = parse_double_quoted_string(endpoint); + auto const endpoint = scan_double_quoted_string(loc); + value = parse_double_quoted_string(endpoint, loc); return std::tuple{juncture::string_value, endpoint}; } default: { - auto const endpoint = scan_unquoted_string(); - value = parse_unquoted_string(endpoint); + auto const endpoint = scan_unquoted_string(loc); + value = parse_unquoted_string(endpoint, loc); if (value == "NULL") { // In this one situation, as a special case, NULL means a null @@ -144,7 +147,7 @@ std::pair array_parser::parse_array_step() // Skip a trailing field separator, if present. if (end < std::size(m_input)) { - auto next{scan_glyph(end)}; + auto next{scan_glyph(end, loc)}; if (((next - end) == 1) and (m_input[end] == ',')) [[unlikely]] end = next; } @@ -154,8 +157,8 @@ std::pair array_parser::parse_array_step() } -array_parser::implementation -array_parser::specialize_for_encoding(pqxx::internal::encoding_group enc) +array_parser::implementation array_parser::specialize_for_encoding( + pqxx::internal::encoding_group enc, sl loc) { using encoding_group = pqxx::internal::encoding_group; @@ -180,7 +183,7 @@ array_parser::specialize_for_encoding(pqxx::internal::encoding_group enc) PQXX_ENCODING_CASE(UTF8); } [[unlikely]] throw pqxx::internal_error{ - pqxx::internal::concat("Unsupported encoding code: ", enc, ".")}; + pqxx::internal::concat("Unsupported encoding code: ", enc, "."), loc}; #undef PQXX_ENCODING_CASE } diff --git a/src/blob.cxx b/src/blob.cxx index 2f6edaea2..ed0a332a4 100644 --- a/src/blob.cxx +++ b/src/blob.cxx @@ -43,52 +43,60 @@ std::string pqxx::blob::errmsg(connection const *cx) } -pqxx::blob pqxx::blob::open_internal(dbtransaction &tx, oid id, int mode) +pqxx::blob +pqxx::blob::open_internal(dbtransaction &tx, oid id, int mode, sl loc) { auto &cx{tx.conn()}; int const fd{lo_open(raw_conn(&cx), id, mode)}; if (fd == -1) - throw pqxx::failure{internal::concat( - "Could not open binary large object ", id, ": ", errmsg(&cx))}; + throw pqxx::failure{ + internal::concat( + "Could not open binary large object ", id, ": ", errmsg(&cx)), + loc}; return {cx, fd}; } -pqxx::oid pqxx::blob::create(dbtransaction &tx, oid id) +pqxx::oid pqxx::blob::create(dbtransaction &tx, oid id, sl loc) { oid const actual_id{lo_create(raw_conn(tx), id)}; if (actual_id == 0) - throw failure{internal::concat( - "Could not create binary large object: ", errmsg(&tx.conn()))}; + throw failure{ + internal::concat( + "Could not create binary large object: ", errmsg(&tx.conn())), + loc}; return actual_id; } -void pqxx::blob::remove(dbtransaction &tx, oid id) +void pqxx::blob::remove(dbtransaction &tx, oid id, sl loc) { if (id == 0) - throw usage_error{"Trying to delete binary large object without an ID."}; + throw usage_error{ + "Trying to delete binary large object without an ID.", loc}; if (lo_unlink(raw_conn(tx), id) == -1) - throw failure{internal::concat( - "Could not delete large object ", id, ": ", errmsg(&tx.conn()))}; + throw failure{ + internal::concat( + "Could not delete large object ", id, ": ", errmsg(&tx.conn())), + loc}; } -pqxx::blob pqxx::blob::open_r(dbtransaction &tx, oid id) +pqxx::blob pqxx::blob::open_r(dbtransaction &tx, oid id, sl loc) { - return open_internal(tx, id, INV_READ); + return open_internal(tx, id, INV_READ, loc); } -pqxx::blob pqxx::blob::open_w(dbtransaction &tx, oid id) +pqxx::blob pqxx::blob::open_w(dbtransaction &tx, oid id, sl loc) { - return open_internal(tx, id, INV_WRITE); + return open_internal(tx, id, INV_WRITE, loc); } -pqxx::blob pqxx::blob::open_rw(dbtransaction &tx, oid id) +pqxx::blob pqxx::blob::open_rw(dbtransaction &tx, oid id, sl loc) { - return open_internal(tx, id, INV_READ | INV_WRITE); + return open_internal(tx, id, INV_READ | INV_WRITE, loc); } @@ -137,111 +145,120 @@ void pqxx::blob::close() } -std::size_t pqxx::blob::raw_read(std::byte buf[], std::size_t size) +std::size_t pqxx::blob::raw_read(std::byte buf[], std::size_t size, sl loc) { if (m_conn == nullptr) - throw usage_error{"Attempt to read from a closed binary large object."}; + throw usage_error{ + "Attempt to read from a closed binary large object.", loc}; if (size > chunk_limit) throw range_error{ - "Reads from a binary large object must be less than 2 GB at once."}; + "Reads from a binary large object must be less than 2 GB at once.", loc}; auto data{reinterpret_cast(buf)}; int const received{lo_read(raw_conn(m_conn), m_fd, data, size)}; if (received < 0) throw failure{ - internal::concat("Could not read from binary large object: ", errmsg())}; + internal::concat("Could not read from binary large object: ", errmsg()), + loc}; return static_cast(received); } -std::size_t pqxx::blob::read(bytes &buf, std::size_t size) +std::size_t pqxx::blob::read(bytes &buf, std::size_t size, sl loc) { buf.resize(size); - auto const received{raw_read(std::data(buf), size)}; + auto const received{raw_read(std::data(buf), size, loc)}; buf.resize(received); return static_cast(received); } -void pqxx::blob::raw_write(bytes_view data) +void pqxx::blob::raw_write(bytes_view data, sl loc) { if (m_conn == nullptr) - throw usage_error{"Attempt to write to a closed binary large object."}; + throw usage_error{ + "Attempt to write to a closed binary large object.", loc}; auto const sz{std::size(data)}; if (sz > chunk_limit) - throw range_error{"Write to binary large object exceeds 2GB limit."}; + throw range_error{"Write to binary large object exceeds 2GB limit.", loc}; auto ptr{reinterpret_cast(std::data(data))}; int const written{lo_write(raw_conn(m_conn), m_fd, ptr, sz)}; if (written < 0) throw failure{ - internal::concat("Write to binary large object failed: ", errmsg())}; + internal::concat("Write to binary large object failed: ", errmsg()), + loc}; } -void pqxx::blob::resize(std::int64_t size) +void pqxx::blob::resize(std::int64_t size, sl loc) { if (m_conn == nullptr) - throw usage_error{"Attempt to resize a closed binary large object."}; + throw usage_error{"Attempt to resize a closed binary large object.", loc}; if (lo_truncate64(raw_conn(m_conn), m_fd, size) < 0) throw failure{ - internal::concat("Binary large object truncation failed: ", errmsg())}; + internal::concat("Binary large object truncation failed: ", errmsg()), + loc}; } -std::int64_t pqxx::blob::tell() const +std::int64_t pqxx::blob::tell(sl loc) const { if (m_conn == nullptr) - throw usage_error{"Attempt to tell() a closed binary large object."}; + throw usage_error{"Attempt to tell() a closed binary large object.", loc}; std::int64_t const offset{lo_tell64(raw_conn(m_conn), m_fd)}; if (offset < 0) - throw failure{internal::concat( - "Error reading binary large object position: ", errmsg())}; + throw failure{ + internal::concat( + "Error reading binary large object position: ", errmsg()), + loc}; return offset; } -std::int64_t pqxx::blob::seek(std::int64_t offset, int whence) +std::int64_t pqxx::blob::seek(std::int64_t offset, int whence, sl loc) { if (m_conn == nullptr) - throw usage_error{"Attempt to seek() a closed binary large object."}; + throw usage_error{"Attempt to seek() a closed binary large object.", loc}; std::int64_t const seek_result{ lo_lseek64(raw_conn(m_conn), m_fd, offset, whence)}; if (seek_result < 0) - throw failure{internal::concat( - "Error during seek on binary large object: ", errmsg())}; + throw failure{ + internal::concat("Error during seek on binary large object: ", errmsg()), + loc}; return seek_result; } -std::int64_t pqxx::blob::seek_abs(std::int64_t offset) +std::int64_t pqxx::blob::seek_abs(std::int64_t offset, sl loc) { - return this->seek(offset, SEEK_SET); + return this->seek(offset, SEEK_SET, loc); } -std::int64_t pqxx::blob::seek_rel(std::int64_t offset) +std::int64_t pqxx::blob::seek_rel(std::int64_t offset, sl loc) { - return this->seek(offset, SEEK_CUR); + return this->seek(offset, SEEK_CUR, loc); } -std::int64_t pqxx::blob::seek_end(std::int64_t offset) +std::int64_t pqxx::blob::seek_end(std::int64_t offset, sl loc) { - return this->seek(offset, SEEK_END); + return this->seek(offset, SEEK_END, loc); } -pqxx::oid pqxx::blob::from_buf(dbtransaction &tx, bytes_view data, oid id) +pqxx::oid +pqxx::blob::from_buf(dbtransaction &tx, bytes_view data, oid id, sl loc) { - oid const actual_id{create(tx, id)}; + oid const actual_id{create(tx, id, loc)}; try { - open_w(tx, actual_id).write(data); + open_w(tx, actual_id, loc).write(data, loc); } catch (std::exception const &) { try { - remove(tx, id); + remove(tx, id, loc); } catch (std::exception const &e) { @@ -260,33 +277,34 @@ pqxx::oid pqxx::blob::from_buf(dbtransaction &tx, bytes_view data, oid id) } -void pqxx::blob::append_from_buf(dbtransaction &tx, bytes_view data, oid id) +void pqxx::blob::append_from_buf( + dbtransaction &tx, bytes_view data, oid id, sl loc) { if (std::size(data) > chunk_limit) throw range_error{ - "Writes to a binary large object must be less than 2 GB at once."}; - blob b{open_w(tx, id)}; - b.seek_end(); - b.write(data); + "Writes to a binary large object must be less than 2 GB at once.", loc}; + blob b{open_w(tx, id, loc)}; + b.seek_end(0, loc); + b.write(data, loc); } void pqxx::blob::to_buf( - dbtransaction &tx, oid id, bytes &buf, std::size_t max_size) + dbtransaction &tx, oid id, bytes &buf, std::size_t max_size, sl loc) { - open_r(tx, id).read(buf, max_size); + open_r(tx, id, loc).read(buf, max_size, loc); } std::size_t pqxx::blob::append_to_buf( dbtransaction &tx, oid id, std::int64_t offset, bytes &buf, - std::size_t append_max) + std::size_t append_max, sl loc) { if (append_max > chunk_limit) throw range_error{ - "Reads from a binary large object must be less than 2 GB at once."}; - auto b{open_r(tx, id)}; - b.seek_abs(offset); + "Reads from a binary large object must be less than 2 GB at once.", loc}; + auto b{open_r(tx, id, loc)}; + b.seek_abs(offset, loc); auto const org_size{std::size(buf)}; buf.resize(org_size + append_max); try @@ -305,31 +323,38 @@ std::size_t pqxx::blob::append_to_buf( } -pqxx::oid pqxx::blob::from_file(dbtransaction &tx, zview path) +pqxx::oid pqxx::blob::from_file(dbtransaction &tx, zview path, sl loc) { auto id{lo_import(raw_conn(tx), path.c_str())}; if (id == 0) - throw failure{internal::concat( - "Could not import '", path, "' as a binary large object: ", errmsg(tx))}; + throw failure{ + internal::concat( + "Could not import '", path, + "' as a binary large object: ", errmsg(tx)), + loc}; return id; } -pqxx::oid pqxx::blob::from_file(dbtransaction &tx, zview path, oid id) +pqxx::oid pqxx::blob::from_file(dbtransaction &tx, zview path, oid id, sl loc) { auto actual_id{lo_import_with_oid(raw_conn(tx), path.c_str(), id)}; if (actual_id == 0) - throw failure{internal::concat( - "Could not import '", path, "' as binary large object ", id, ": ", - errmsg(tx))}; + throw failure{ + internal::concat( + "Could not import '", path, "' as binary large object ", id, ": ", + errmsg(tx)), + loc}; return actual_id; } -void pqxx::blob::to_file(dbtransaction &tx, oid id, zview path) +void pqxx::blob::to_file(dbtransaction &tx, oid id, zview path, sl loc) { if (lo_export(raw_conn(tx), id, path.c_str()) < 0) - throw failure{internal::concat( - "Could not export binary large object ", id, " to file '", path, - "': ", errmsg(tx))}; + throw failure{ + internal::concat( + "Could not export binary large object ", id, " to file '", path, + "': ", errmsg(tx)), + loc}; } diff --git a/src/connection.cxx b/src/connection.cxx index 8a7c5cbd9..1054b4b17 100644 --- a/src/connection.cxx +++ b/src/connection.cxx @@ -105,19 +105,19 @@ void PQXX_COLD PQXX_LIBEXPORT pqxx::internal::skip_init_ssl(int skips) noexcept } -pqxx::connection::connection(connection &&rhs) : +pqxx::connection::connection(connection &&rhs, sl loc) : m_conn{rhs.m_conn}, m_notice_waiters{std::move(rhs.m_notice_waiters)}, m_notification_handlers{std::move(rhs.m_notification_handlers)}, m_unique_id{rhs.m_unique_id} { - rhs.check_movable(); + rhs.check_movable(loc); rhs.m_conn = nullptr; } pqxx::connection::connection( - connection::connect_mode, zview connection_string) : + connection::connect_mode, zview connection_string, sl loc) : m_conn{PQconnectStart(connection_string.c_str())} { if (m_conn == nullptr) @@ -130,7 +130,7 @@ pqxx::connection::connection( std::string const msg{PQerrorMessage(m_conn)}; PQfinish(m_conn); m_conn = nullptr; - throw pqxx::broken_connection{msg}; + throw pqxx::broken_connection{msg, loc}; } } @@ -141,24 +141,24 @@ pqxx::connection::connection(internal::pq::PGconn *raw_conn) : m_conn{raw_conn} } -std::pair pqxx::connection::poll_connect() +std::pair pqxx::connection::poll_connect(sl loc) { switch (PQconnectPoll(m_conn)) { case PGRES_POLLING_FAILED: - throw pqxx::broken_connection{PQerrorMessage(m_conn)}; + throw pqxx::broken_connection{PQerrorMessage(m_conn), loc}; case PGRES_POLLING_READING: return std::make_pair(true, false); case PGRES_POLLING_WRITING: return std::make_pair(false, true); case PGRES_POLLING_OK: if (not is_open()) - throw pqxx::broken_connection{PQerrorMessage(m_conn)}; + throw pqxx::broken_connection{PQerrorMessage(m_conn), loc}; [[likely]] return std::make_pair(false, false); case PGRES_POLLING_ACTIVE: throw internal_error{ - "Nonblocking connection poll returned obsolete 'active' state."}; + "Nonblocking connection poll returned obsolete 'active' state.", loc}; default: throw internal_error{ - "Nonblocking connection poll returned unknown value."}; + "Nonblocking connection poll returned unknown value.", loc}; } } @@ -178,16 +178,16 @@ void pqxx::connection::set_up_notice_handlers() } -void pqxx::connection::complete_init() +void pqxx::connection::complete_init(sl loc) { if (m_conn == nullptr) throw std::bad_alloc{}; try { if (not is_open()) - throw broken_connection{PQerrorMessage(m_conn)}; + throw broken_connection{PQerrorMessage(m_conn), loc}; - set_up_state(); + set_up_state(loc); } catch (std::exception const &) { @@ -198,51 +198,55 @@ void pqxx::connection::complete_init() } -void pqxx::connection::init(char const options[]) +void pqxx::connection::init(char const options[], sl loc) { m_conn = PQconnectdb(options); set_up_notice_handlers(); - complete_init(); + complete_init(loc); } -void pqxx::connection::init(char const *params[], char const *values[]) +void pqxx::connection::init(char const *params[], char const *values[], sl loc) { m_conn = PQconnectdbParams(params, values, 0); set_up_notice_handlers(); - complete_init(); + complete_init(loc); } -void pqxx::connection::check_movable() const +void pqxx::connection::check_movable(sl loc) const { if (m_trans) - throw pqxx::usage_error{"Moving a connection with a transaction open."}; + throw pqxx::usage_error{ + "Moving a connection with a transaction open.", loc}; if (not std::empty(m_receivers)) throw pqxx::usage_error{ - "Moving a connection with notification receivers registered."}; + "Moving a connection with notification receivers registered.", loc}; } -void pqxx::connection::check_overwritable() const +void pqxx::connection::check_overwritable(sl loc) const { if (m_trans) throw pqxx::usage_error{ - "Moving a connection onto one with a transaction open."}; + "Moving a connection onto one with a transaction open.", loc}; if (not std::empty(m_receivers)) throw usage_error{ "Moving a connection onto one " - "with notification receivers registered."}; + "with notification receivers registered.", + loc}; } +// TODO: How can we pass std::source_location here? pqxx::connection &pqxx::connection::operator=(connection &&rhs) { - check_overwritable(); - rhs.check_movable(); + auto loc{sl::current()}; + check_overwritable(loc); + rhs.check_movable(loc); // Close our old connection, if any. - close(); + close(loc); m_conn = std::exchange(rhs.m_conn, nullptr); m_unique_id = rhs.m_unique_id; @@ -255,7 +259,7 @@ pqxx::connection &pqxx::connection::operator=(connection &&rhs) pqxx::result pqxx::connection::make_result( internal::pq::PGresult *pgr, std::shared_ptr const &query, - std::string_view desc) + std::string_view desc, sl loc) { std::shared_ptr const smart{ pgr, internal::clear_result}; @@ -264,12 +268,12 @@ pqxx::result pqxx::connection::make_result( if (is_open()) throw failure(err_msg()); else - throw broken_connection{"Lost connection to the database server."}; + throw broken_connection{"Lost connection to the database server.", loc}; } - auto const enc{internal::enc_group(encoding_id())}; + auto const enc{internal::enc_group(encoding_id(loc), loc)}; auto r{pqxx::internal::gate::result_creation::create( smart, query, m_notice_waiters, enc)}; - pqxx::internal::gate::result_creation{r}.check_status(desc); + pqxx::internal::gate::result_creation{r}.check_status(desc, loc); return r; } @@ -308,27 +312,27 @@ int PQXX_COLD pqxx::connection::server_version() const noexcept void pqxx::connection::set_variable( - std::string_view var, std::string_view value) & + std::string_view var, std::string_view value, sl loc) & { - exec(internal::concat("SET ", quote_name(var), "=", value)); + exec(internal::concat("SET ", quote_name(var), "=", value), loc); } -std::string pqxx::connection::get_variable(std::string_view var) +std::string pqxx::connection::get_variable(std::string_view var, sl loc) { - return exec(internal::concat("SHOW ", quote_name(var))) + return exec(internal::concat("SHOW ", quote_name(var)), loc) .at(0) .at(0) .as(std::string{}); } -std::string pqxx::connection::get_var(std::string_view var) +std::string pqxx::connection::get_var(std::string_view var, sl loc) { // (Variables can't be null, so far as I can make out.) - return exec(internal::concat("SHOW "sv, quote_name(var))) - .one_field() - .as(); + return exec(internal::concat("SHOW "sv, quote_name(var)), loc) + .one_field(loc) + .as(loc); } @@ -336,21 +340,23 @@ std::string pqxx::connection::get_var(std::string_view var) * recovered because the physical connection to the database was lost and is * being reset, or that may not have been initialized yet. */ -void pqxx::connection::set_up_state() +void pqxx::connection::set_up_state(sl loc) { if (auto const proto_ver{protocol_version()}; proto_ver < 3) { if (proto_ver == 0) - throw broken_connection{"No connection."}; + throw broken_connection{"No connection.", loc}; else throw feature_not_supported{ - "Unsupported frontend/backend protocol version; 3.0 is the minimum."}; + "Unsupported frontend/backend protocol version; 3.0 is the minimum.", + "[connect]", nullptr, loc}; } constexpr int oldest_server{90000}; if (server_version() <= oldest_server) throw feature_not_supported{ - "Unsupported server version; 9.0 is the minimum."}; + "Unsupported server version; 9.0 is the minimum.", "[connect]", nullptr, + loc}; } @@ -410,12 +416,14 @@ void PQXX_COLD pqxx::connection::add_receiver(pqxx::notification_receiver *n) void pqxx::connection::listen( - std::string_view channel, notification_handler handler) + std::string_view channel, notification_handler handler, sl loc) { if (m_trans != nullptr) - throw usage_error{pqxx::internal::concat( - "Attempting to listen for notifications on '", channel, - "' while transaction is active.")}; + throw usage_error{ + pqxx::internal::concat( + "Attempting to listen for notifications on '", channel, + "' while transaction is active."), + loc}; std::string str_name{channel}; @@ -434,7 +442,8 @@ void pqxx::connection::listen( else { // We had no handler installed for this name. Start listening. - exec(pqxx::internal::concat("LISTEN ", quote_name(channel))).no_rows(); + exec(pqxx::internal::concat("LISTEN ", quote_name(channel)), loc) + .no_rows(); m_notification_handlers.emplace_hint(pos, channel, std::move(handler)); } } @@ -445,15 +454,16 @@ void pqxx::connection::listen( if (pos != handlers_end) { // Yes, we had a handler for this name. Remove it. - exec(pqxx::internal::concat("UNLISTEN ", quote_name(channel))).no_rows(); + exec(pqxx::internal::concat("UNLISTEN ", quote_name(channel)), loc) + .no_rows(); m_notification_handlers.erase(pos); } } } -void PQXX_COLD -pqxx::connection::remove_receiver(pqxx::notification_receiver *T) noexcept +void PQXX_COLD pqxx::connection::remove_receiver( + pqxx::notification_receiver *T, sl loc) noexcept { if (T == nullptr) return; @@ -477,7 +487,7 @@ pqxx::connection::remove_receiver(pqxx::notification_receiver *T) noexcept bool const gone{R.second == ++R.first}; m_receivers.erase(i); if (gone) - exec(internal::concat("UNLISTEN ", quote_name(needle.first))); + exec(internal::concat("UNLISTEN ", quote_name(needle.first)), loc); } } catch (std::exception const &e) @@ -514,7 +524,7 @@ constexpr int buf_size{500u}; } // namespace -void PQXX_COLD pqxx::connection::cancel_query() +void PQXX_COLD pqxx::connection::cancel_query(sl loc) { std::unique_ptr const cancel{ PQgetCancel(m_conn), wrap_pgfreecancel}; @@ -525,12 +535,13 @@ void PQXX_COLD pqxx::connection::cancel_query() auto const err{errbuf.data()}; auto const c{PQcancel(cancel.get(), err, buf_size)}; if (c == 0) [[unlikely]] - throw pqxx::sql_error{std::string{err, std::size(errbuf)}, "[cancel]"}; + throw pqxx::sql_error{ + std::string{err, std::size(errbuf)}, "[cancel]", nullptr, loc}; } #if defined(_WIN32) || __has_include() -void pqxx::connection::set_blocking(bool block) & +void pqxx::connection::set_blocking(bool block, sl loc) & { auto const fd{sock()}; # if defined _WIN32 @@ -540,7 +551,7 @@ void pqxx::connection::set_blocking(bool block) & std::array errbuf{}; char const *err{pqxx::internal::error_string(WSAGetLastError(), errbuf)}; throw broken_connection{ - internal::concat("Could not set socket's blocking mode: ", err)}; + internal::concat("Could not set socket's blocking mode: ", err), loc}; } # else // _WIN32 std::array errbuf{}; @@ -549,7 +560,7 @@ void pqxx::connection::set_blocking(bool block) & { char const *const err{pqxx::internal::error_string(errno, errbuf)}; throw broken_connection{ - internal::concat("Could not get socket state: ", err)}; + internal::concat("Could not get socket state: ", err), loc}; } if (block) flags |= O_NONBLOCK; @@ -559,7 +570,7 @@ void pqxx::connection::set_blocking(bool block) & { char const *const err{pqxx::internal::error_string(errno, errbuf)}; throw broken_connection{ - internal::concat("Could not set socket's blocking mode: ", err)}; + internal::concat("Could not set socket's blocking mode: ", err), loc}; } # endif // _WIN32 } @@ -587,10 +598,10 @@ notify_ptr get_notif(pqxx::internal::pq::PGconn *cx) } // namespace -int pqxx::connection::get_notifs() +int pqxx::connection::get_notifs(sl loc) { if (not consume_input()) - throw broken_connection{"Connection lost."}; + throw broken_connection{"Connection lost.", loc}; // Even if somehow we receive notifications during our transaction, don't // deliver them. @@ -654,25 +665,25 @@ int pqxx::connection::get_notifs() } -char const *PQXX_COLD pqxx::connection::dbname() const +char const *PQXX_COLD pqxx::connection::dbname() const noexcept { return PQdb(m_conn); } -char const *PQXX_COLD pqxx::connection::username() const +char const *PQXX_COLD pqxx::connection::username() const noexcept { return PQuser(m_conn); } -char const *PQXX_COLD pqxx::connection::hostname() const +char const *PQXX_COLD pqxx::connection::hostname() const noexcept { return PQhost(m_conn); } -char const *PQXX_COLD pqxx::connection::port() const +char const *PQXX_COLD pqxx::connection::port() const noexcept { return PQport(m_conn); } @@ -710,17 +721,17 @@ std::vector pqxx::result -pqxx::connection::exec(std::string_view query, std::string_view desc) +pqxx::connection::exec(std::string_view query, std::string_view desc, sl loc) { - return exec(std::make_shared(query), desc); + return exec(std::make_shared(query), desc, loc); } pqxx::result pqxx::connection::exec( - std::shared_ptr const &query, std::string_view desc) + std::shared_ptr const &query, std::string_view desc, sl loc) { auto res{make_result(PQexec(m_conn, query->c_str()), query, desc)}; - get_notifs(); + get_notifs(loc); return res; } @@ -735,7 +746,8 @@ std::string pqxx::connection::encrypt_password( } -void pqxx::connection::prepare(char const name[], char const definition[]) & +void pqxx::connection::prepare( + char const name[], char const definition[], sl) & { auto const q{std::make_shared( pqxx::internal::concat("[PREPARE ", name, "]"))}; @@ -745,35 +757,35 @@ void pqxx::connection::prepare(char const name[], char const definition[]) & } -void pqxx::connection::prepare(char const definition[]) & +void pqxx::connection::prepare(char const definition[], sl loc) & { - this->prepare("", definition); + this->prepare("", definition, loc); } -void pqxx::connection::unprepare(std::string_view name) +void pqxx::connection::unprepare(std::string_view name, sl loc) { - exec(internal::concat("DEALLOCATE ", quote_name(name))); + exec(internal::concat("DEALLOCATE ", quote_name(name)), loc); } pqxx::result pqxx::connection::exec_prepared( - std::string_view statement, internal::c_params const &args) + std::string_view statement, internal::c_params const &args, sl loc) { auto const q{std::make_shared(statement)}; auto const pq_result{PQexecPrepared( m_conn, q->c_str(), - check_cast(std::size(args.values), "exec_prepared"sv), + check_cast(std::size(args.values), "exec_prepared"sv, loc), args.values.data(), args.lengths.data(), reinterpret_cast(args.formats.data()), static_cast(format::text))}; auto r{make_result(pq_result, q, statement)}; - get_notifs(); + get_notifs(loc); return r; } -void pqxx::connection::close() +void pqxx::connection::close(sl) { // Just in case PQfinish() doesn't handle nullptr nicely. if (m_conn == nullptr) @@ -899,15 +911,15 @@ pqxx::connection::read_copy_line() } -void pqxx::connection::write_copy_line(std::string_view line) +void pqxx::connection::write_copy_line(std::string_view line, sl loc) { static std::string const err_prefix{"Error writing to table: "}; auto const size{check_cast( - std::ssize(line), "Line in stream_to is too long to process."sv)}; + std::ssize(line), "Line in stream_to is too long to process."sv, loc)}; if (PQputCopyData(m_conn, line.data(), size) <= 0) [[unlikely]] - throw failure{err_prefix + err_msg()}; + throw failure{err_prefix + err_msg(), loc}; if (PQputCopyData(m_conn, "\n", 1) <= 0) [[unlikely]] - throw failure{err_prefix + err_msg()}; + throw failure{err_prefix + err_msg(), loc}; } @@ -946,22 +958,23 @@ pqxx::internal::pq::PGresult *pqxx::connection::get_result() } -size_t pqxx::connection::esc_to_buf(std::string_view text, char *buf) const +size_t +pqxx::connection::esc_to_buf(std::string_view text, char *buf, sl loc) const { int err{0}; auto const copied{ PQescapeStringConn(m_conn, buf, text.data(), std::size(text), &err)}; if (err) [[unlikely]] - throw argument_error{err_msg()}; + throw argument_error{err_msg(), loc}; return copied; } -std::string pqxx::connection::esc(std::string_view text) const +std::string pqxx::connection::esc(std::string_view text, sl loc) const { std::string buf; buf.resize(2 * std::size(text) + 1); - auto const copied{esc_to_buf(text, buf.data())}; + auto const copied{esc_to_buf(text, buf.data(), loc)}; buf.resize(copied); return buf; } @@ -1004,14 +1017,14 @@ std::string pqxx::connection::quote_table(table_path path) const } -std::string -pqxx::connection::esc_like(std::string_view text, char escape_char) const +std::string pqxx::connection::esc_like( + std::string_view text, char escape_char, sl loc) const { std::string out; out.reserve(std::size(text)); // TODO: Rewrite using a char_finder. internal::for_glyphs( - internal::enc_group(encoding_id()), + internal::enc_group(encoding_id(loc), loc), [&out, escape_char](char const *gbegin, char const *gend) { if ((gend - gbegin == 1) and (*gbegin == '_' or *gbegin == '%')) [[unlikely]] @@ -1019,34 +1032,34 @@ pqxx::connection::esc_like(std::string_view text, char escape_char) const for (; gbegin != gend; ++gbegin) out.push_back(*gbegin); }, - text.data(), std::size(text)); + text.data(), std::size(text), 0u, loc); return out; } -int pqxx::connection::await_notification() +int pqxx::connection::await_notification(sl loc) { - int notifs = get_notifs(); + int notifs = get_notifs(loc); if (notifs == 0) { [[likely]] internal::wait_fd(socket_of(m_conn), true, false, 10, 0); - notifs = get_notifs(); + notifs = get_notifs(loc); } return notifs; } int pqxx::connection::await_notification( - std::time_t seconds, long microseconds) + std::time_t seconds, long microseconds, sl loc) { - int const notifs = get_notifs(); + int const notifs = get_notifs(loc); if (notifs == 0) { [[likely]] internal::wait_fd( socket_of(m_conn), true, false, - check_cast(seconds, "Seconds out of range."), - check_cast(microseconds, "Microseconds out of range.")); - return get_notifs(); + check_cast(seconds, "Seconds out of range.", loc), + check_cast(microseconds, "Microseconds out of range.", loc)); + return get_notifs(loc); } return notifs; } @@ -1062,13 +1075,14 @@ std::string pqxx::connection::adorn_name(std::string_view n) } -std::string pqxx::connection::get_client_encoding() const +std::string pqxx::connection::get_client_encoding(sl loc) const { - return internal::name_encoding(encoding_id()); + return internal::name_encoding(encoding_id(loc)); } -void PQXX_COLD pqxx::connection::set_client_encoding(char const encoding[]) & +void PQXX_COLD +pqxx::connection::set_client_encoding(char const encoding[], sl loc) & { switch (auto const retval{PQsetClientEncoding(m_conn, encoding)}; retval) { @@ -1078,17 +1092,18 @@ void PQXX_COLD pqxx::connection::set_client_encoding(char const encoding[]) & case -1: [[unlikely]] if (is_open()) - throw failure{"Setting client encoding failed."}; + throw failure{"Setting client encoding failed.", loc}; else - throw broken_connection{"Lost connection to the database server."}; + throw broken_connection{"Lost connection to the database server.", loc}; default: - [[unlikely]] throw internal_error{internal::concat( - "Unexpected result from PQsetClientEncoding: ", retval)}; + [[unlikely]] throw internal_error{ + internal::concat("Unexpected result from PQsetClientEncoding: ", retval), + loc}; } } -int pqxx::connection::encoding_id() const +int pqxx::connection::encoding_id(sl loc) const { int const enc{PQclientEncoding(m_conn)}; if (enc == -1) @@ -1100,26 +1115,26 @@ int pqxx::connection::encoding_id() const // TODO: Make pqxx::result::result(...) do all the checking. [[unlikely]] if (is_open()) - throw failure{"Could not obtain client encoding."}; + throw failure{"Could not obtain client encoding.", loc}; else - throw broken_connection{"Lost connection to the database server."}; + throw broken_connection{"Lost connection to the database server.", loc}; } [[likely]] return enc; } pqxx::result pqxx::connection::exec_params( - std::string_view query, internal::c_params const &args) + std::string_view query, internal::c_params const &args, sl loc) { auto const q{std::make_shared(query)}; auto const pq_result{PQexecParams( m_conn, q->c_str(), - check_cast(std::size(args.values), "exec_params"sv), nullptr, + check_cast(std::size(args.values), "exec_params"sv, loc), nullptr, args.values.data(), args.lengths.data(), reinterpret_cast(args.formats.data()), static_cast(format::text))}; auto r{make_result(pq_result, q)}; - get_notifs(); + get_notifs(loc); return r; } @@ -1197,16 +1212,16 @@ std::string pqxx::connection::connection_string() const #if defined(_WIN32) || __has_include() -pqxx::connecting::connecting(zview connection_string) : - m_conn{connection::connect_nonblocking, connection_string} +pqxx::connecting::connecting(zview connection_string, sl loc) : + m_conn{connection::connect_nonblocking, connection_string, loc} {} #endif // defined(_WIN32) || __has_include( #if defined(_WIN32) || __has_include() -void pqxx::connecting::process() & +void pqxx::connecting::process(sl loc) & { - auto const [reading, writing]{m_conn.poll_connect()}; + auto const [reading, writing]{m_conn.poll_connect(loc)}; m_reading = reading; m_writing = writing; } @@ -1214,12 +1229,12 @@ void pqxx::connecting::process() & #if defined(_WIN32) || __has_include() -pqxx::connection pqxx::connecting::produce() && +pqxx::connection pqxx::connecting::produce(sl loc) && { if (!done()) throw usage_error{ - "Tried to produce a nonblocking connection before it was done."}; - m_conn.complete_init(); - return std::move(m_conn); + "Tried to produce a nonblocking connection before it was done.", loc}; + m_conn.complete_init(loc); + return {std::move(m_conn), loc}; } #endif // defined(_WIN32) || __has_include( diff --git a/src/cursor.cxx b/src/cursor.cxx index 3e12c0fc5..fc576f335 100644 --- a/src/cursor.cxx +++ b/src/cursor.cxx @@ -31,20 +31,20 @@ pqxx::cursor_base::cursor_base( pqxx::result::size_type -pqxx::internal::obtain_stateless_cursor_size(sql_cursor &cur) +pqxx::internal::obtain_stateless_cursor_size(sql_cursor &cur, sl loc) { if (cur.endpos() == -1) - cur.move(cursor_base::all()); + cur.move(cursor_base::all(), loc); return result::size_type(cur.endpos() - 1); } pqxx::result pqxx::internal::stateless_cursor_retrieve( sql_cursor &cur, result::difference_type size, - result::difference_type begin_pos, result::difference_type end_pos) + result::difference_type begin_pos, result::difference_type end_pos, sl loc) { if (begin_pos < 0 or begin_pos > size) - throw range_error{"Starting position out of range"}; + throw range_error{"Starting position out of range", loc}; if (end_pos < -1) end_pos = -1; @@ -55,14 +55,14 @@ pqxx::result pqxx::internal::stateless_cursor_retrieve( return cur.empty_result(); int const direction{((begin_pos < end_pos) ? 1 : -1)}; - cur.move((begin_pos - direction) - (cur.pos() - 1)); - return cur.fetch(end_pos - begin_pos); + cur.move((begin_pos - direction) - (cur.pos() - 1), loc); + return cur.fetch(end_pos - begin_pos, loc); } pqxx::icursorstream::icursorstream( transaction_base &context, std::string_view query, std::string_view basename, - difference_type sstride) : + difference_type sstride, sl loc) : m_cur{ context, query, @@ -70,20 +70,21 @@ pqxx::icursorstream::icursorstream( cursor_base::forward_only, cursor_base::read_only, cursor_base::owned, - false}, + false, + loc}, m_stride{sstride}, m_realpos{0}, m_reqpos{0}, m_iterators{nullptr}, m_done{false} { - set_stride(sstride); + set_stride(sstride, loc); } pqxx::icursorstream::icursorstream( transaction_base &context, field const &cname, difference_type sstride, - cursor_base::ownership_policy op) : + cursor_base::ownership_policy op, sl loc) : m_cur{context, cname.c_str(), op}, m_stride{sstride}, m_realpos{0}, @@ -91,22 +92,22 @@ pqxx::icursorstream::icursorstream( m_iterators{nullptr}, m_done{false} { - set_stride(sstride); + set_stride(sstride, loc); } -void pqxx::icursorstream::set_stride(difference_type stride) & +void pqxx::icursorstream::set_stride(difference_type stride, sl loc) & { if (stride < 1) throw argument_error{ - internal::concat("Attempt to set cursor stride to ", stride)}; + internal::concat("Attempt to set cursor stride to ", stride), loc}; m_stride = stride; } -pqxx::result pqxx::icursorstream::fetchblock() +pqxx::result pqxx::icursorstream::fetchblock(sl loc) { - result r{m_cur.fetch(m_stride)}; + result r{m_cur.fetch(m_stride, loc)}; m_realpos += std::size(r); if (std::empty(r)) m_done = true; @@ -114,9 +115,9 @@ pqxx::result pqxx::icursorstream::fetchblock() } -pqxx::icursorstream &pqxx::icursorstream::ignore(std::streamsize n) & +pqxx::icursorstream &pqxx::icursorstream::ignore(std::streamsize n, sl loc) & { - auto offset{m_cur.move(difference_type(n))}; + auto offset{m_cur.move(difference_type(n), loc)}; m_realpos += offset; if (offset < n) m_done = true; @@ -165,7 +166,7 @@ void pqxx::icursorstream::remove_iterator(icursor_iterator *i) const noexcept } -void pqxx::icursorstream::service_iterators(difference_type topos) +void pqxx::icursorstream::service_iterators(difference_type topos, sl loc) { if (topos < m_realpos) return; @@ -186,7 +187,7 @@ void pqxx::icursorstream::service_iterators(difference_type topos) auto const readpos{i->first}; if (readpos > m_realpos) ignore(readpos - m_realpos); - result const r{fetchblock()}; + result const r{fetchblock(loc)}; for (; i != todo_end and i->first == readpos; ++i) pqxx::internal::gate::icursor_iterator_icursorstream{*i->second}.fill(r); } @@ -288,12 +289,14 @@ pqxx::icursor_iterator::operator=(icursor_iterator const &rhs) noexcept bool pqxx::icursor_iterator::operator==(icursor_iterator const &rhs) const { + // TODO: How can we pass std::source_location here? + auto loc{sl::current()}; if (m_stream == rhs.m_stream) return pos() == rhs.pos(); if (m_stream != nullptr and rhs.m_stream != nullptr) return false; - refresh(); - rhs.refresh(); + refresh(loc); + rhs.refresh(loc); return std::empty(m_here) and std::empty(rhs.m_here); } @@ -302,17 +305,19 @@ bool pqxx::icursor_iterator::operator<(icursor_iterator const &rhs) const { if (m_stream == rhs.m_stream) return pos() < rhs.pos(); - refresh(); - rhs.refresh(); + // TODO: How can we pass std::source_location here? + auto loc{sl::current()}; + refresh(loc); + rhs.refresh(loc); return not std::empty(m_here); } -void pqxx::icursor_iterator::refresh() const +void pqxx::icursor_iterator::refresh(sl loc) const { if (m_stream != nullptr) pqxx::internal::gate::icursorstream_icursor_iterator{*m_stream} - .service_iterators(pos()); + .service_iterators(pos(), loc); } diff --git a/src/encodings.cxx b/src/encodings.cxx index 2e8d80b4c..9571d71d7 100644 --- a/src/encodings.cxx +++ b/src/encodings.cxx @@ -165,14 +165,14 @@ PQXX_PURE char const *name_encoding(int encoding_id) } -encoding_group enc_group(int libpq_enc_id) +encoding_group enc_group(int libpq_enc_id, sl) { // TODO: Can we safely do this without using string representation? return enc_group(name_encoding(libpq_enc_id)); } -PQXX_PURE glyph_scanner_func *get_glyph_scanner(encoding_group enc) +PQXX_PURE glyph_scanner_func *get_glyph_scanner(encoding_group enc, sl loc) { #define CASE_GROUP(ENC) \ case encoding_group::ENC: return glyph_scanner::call @@ -194,7 +194,7 @@ PQXX_PURE glyph_scanner_func *get_glyph_scanner(encoding_group enc) [[likely]] CASE_GROUP(UTF8); } throw usage_error{ - internal::concat("Unsupported encoding group code ", enc, ".")}; + internal::concat("Unsupported encoding group code ", enc, "."), loc}; #undef CASE_GROUP } diff --git a/src/except.cxx b/src/except.cxx index 75088c483..2bd97aaa8 100644 --- a/src/except.cxx +++ b/src/except.cxx @@ -15,37 +15,36 @@ #include "pqxx/internal/header-post.hxx" -pqxx::failure::failure(std::string const &whatarg, std::source_location loc) : +pqxx::failure::failure(std::string const &whatarg, sl loc) : std::runtime_error{whatarg}, location{loc} {} -pqxx::broken_connection::broken_connection() : - failure{"Connection to database failed."} +pqxx::broken_connection::broken_connection(sl loc) : + failure{"Connection to database failed.", loc} {} pqxx::broken_connection::broken_connection( - std::string const &whatarg, std::source_location loc) : + std::string const &whatarg, sl loc) : failure{whatarg, loc} {} pqxx::protocol_violation::protocol_violation( - std::string const &whatarg, std::source_location loc) : + std::string const &whatarg, sl loc) : broken_connection{whatarg, loc} {} pqxx::variable_set_to_null::variable_set_to_null( - std::string const &whatarg, std::source_location loc) : + std::string const &whatarg, sl loc) : failure{whatarg, loc} {} pqxx::sql_error::sql_error( - std::string const &whatarg, std::string Q, char const *sqlstate, - std::source_location loc) : + std::string const &whatarg, std::string Q, char const *sqlstate, sl loc) : failure{whatarg, loc}, m_query{std::move(Q)}, m_sqlstate{sqlstate ? sqlstate : ""} @@ -67,76 +66,72 @@ PQXX_PURE std::string const &pqxx::sql_error::sqlstate() const noexcept } -pqxx::in_doubt_error::in_doubt_error( - std::string const &whatarg, std::source_location loc) : +pqxx::in_doubt_error::in_doubt_error(std::string const &whatarg, sl loc) : failure{whatarg, loc} {} pqxx::transaction_rollback::transaction_rollback( std::string const &whatarg, std::string const &q, char const sqlstate[], - std::source_location loc) : + sl loc) : sql_error{whatarg, q, sqlstate, loc} {} pqxx::serialization_failure::serialization_failure( std::string const &whatarg, std::string const &q, char const sqlstate[], - std::source_location loc) : + sl loc) : transaction_rollback{whatarg, q, sqlstate, loc} {} pqxx::statement_completion_unknown::statement_completion_unknown( std::string const &whatarg, std::string const &q, char const sqlstate[], - std::source_location loc) : + sl loc) : transaction_rollback{whatarg, q, sqlstate, loc} {} pqxx::deadlock_detected::deadlock_detected( std::string const &whatarg, std::string const &q, char const sqlstate[], - std::source_location loc) : + sl loc) : transaction_rollback{whatarg, q, sqlstate, loc} {} -pqxx::internal_error::internal_error(std::string const &whatarg) : - std::logic_error{internal::concat("libpqxx internal error: ", whatarg)} +pqxx::internal_error::internal_error(std::string const &whatarg, sl loc) : + std::logic_error{ + internal::concat("libpqxx internal error: ", whatarg)}, + location{loc} {} -pqxx::usage_error::usage_error( - std::string const &whatarg, std::source_location loc) : +pqxx::usage_error::usage_error(std::string const &whatarg, sl loc) : std::logic_error{whatarg}, location{loc} {} -pqxx::argument_error::argument_error( - std::string const &whatarg, std::source_location loc) : +pqxx::argument_error::argument_error(std::string const &whatarg, sl loc) : invalid_argument{whatarg}, location{loc} {} -pqxx::conversion_error::conversion_error( - std::string const &whatarg, std::source_location loc) : +pqxx::conversion_error::conversion_error(std::string const &whatarg, sl loc) : domain_error{whatarg}, location{loc} {} -pqxx::unexpected_null::unexpected_null( - std::string const &whatarg, std::source_location loc) : +pqxx::unexpected_null::unexpected_null(std::string const &whatarg, sl loc) : conversion_error{whatarg, loc} {} pqxx::conversion_overrun::conversion_overrun( - std::string const &whatarg, std::source_location loc) : + std::string const &whatarg, sl loc) : conversion_error{whatarg, loc} {} -pqxx::range_error::range_error( - std::string const &whatarg, std::source_location loc) : +pqxx::range_error::range_error(std::string const &whatarg, sl loc) : out_of_range{whatarg}, location{loc} {} diff --git a/src/field.cxx b/src/field.cxx index 10f15961f..6c0d359e0 100644 --- a/src/field.cxx +++ b/src/field.cxx @@ -38,31 +38,31 @@ bool PQXX_COLD pqxx::field::operator==(field const &rhs) const noexcept } -char const *pqxx::field::name() const & +char const *pqxx::field::name(sl loc) const & { - return home().column_name(col()); + return home().column_name(col(), loc); } -pqxx::oid pqxx::field::type() const +pqxx::oid pqxx::field::type(sl loc) const { - return home().column_type(col()); + return home().column_type(col(), loc); } -pqxx::oid pqxx::field::table() const +pqxx::oid pqxx::field::table(sl loc) const { - return home().column_table(col()); + return home().column_table(col(), loc); } -pqxx::row::size_type pqxx::field::table_column() const +pqxx::row::size_type pqxx::field::table_column(sl loc) const { - return home().table_column(col()); + return home().table_column(col(), loc); } -char const *pqxx::field::c_str() const & +char const *pqxx::field::c_str() const & noexcept { return home().get_value(idx(), col()); } diff --git a/src/notification.cxx b/src/notification.cxx index 415bc8f7f..f9e4a8beb 100644 --- a/src/notification.cxx +++ b/src/notification.cxx @@ -31,6 +31,8 @@ pqxx::notification_receiver::notification_receiver( pqxx::notification_receiver::~notification_receiver() { + // TODO: How can we pass std::source_location here? + auto loc{sl::current()}; pqxx::internal::gate::connection_notification_receiver{this->conn()} - .remove_receiver(this); + .remove_receiver(this, loc); } diff --git a/src/params.cxx b/src/params.cxx index 603c405d5..4bfb2d217 100644 --- a/src/params.cxx +++ b/src/params.cxx @@ -80,13 +80,13 @@ void pqxx::params::append(params &&value) & } -pqxx::internal::c_params pqxx::params::make_c_params() const +pqxx::internal::c_params pqxx::params::make_c_params(sl loc) const { pqxx::internal::c_params p; p.reserve(std::size(m_params)); for (auto const ¶m : m_params) std::visit( - [&p](auto const &value) { + [&p, loc](auto const &value) { using T = std::remove_cvref_t; if constexpr (std::is_same_v) @@ -97,7 +97,8 @@ pqxx::internal::c_params pqxx::params::make_c_params() const else { p.values.push_back(reinterpret_cast(std::data(value))); - p.lengths.push_back(check_cast(std::ssize(value), s_overflow)); + p.lengths.push_back( + check_cast(std::ssize(value), s_overflow, loc)); } p.formats.push_back(param_format(value)); diff --git a/src/pipeline.cxx b/src/pipeline.cxx index 7eb7c38aa..4014fbab7 100644 --- a/src/pipeline.cxx +++ b/src/pipeline.cxx @@ -35,9 +35,9 @@ constexpr std::string_view theSeparator{"; "sv}, theDummyValue{"1"sv}, } // namespace -void pqxx::pipeline::init() +void pqxx::pipeline::init(sl loc) { - m_encoding = internal::enc_group(m_trans->conn().encoding_id()); + m_encoding = internal::enc_group(m_trans->conn().encoding_id(loc), loc); m_issuedrange = make_pair(std::end(m_queries), std::end(m_queries)); attach(); } @@ -45,9 +45,11 @@ void pqxx::pipeline::init() pqxx::pipeline::~pipeline() noexcept { + // TODO: How can we pass std::source_location here? + sl const loc{sl::current()}; try { - cancel(); + cancel(loc); } catch (std::exception const &) {} @@ -69,7 +71,7 @@ void pqxx::pipeline::detach() } -pqxx::pipeline::query_id pqxx::pipeline::insert(std::string_view q) & +pqxx::pipeline::query_id pqxx::pipeline::insert(std::string_view q, sl loc) & { attach(); query_id const qid{generate_id()}; @@ -86,34 +88,34 @@ pqxx::pipeline::query_id pqxx::pipeline::insert(std::string_view q) & if (m_num_waiting > m_retain) { if (have_pending()) - receive_if_available(); + receive_if_available(loc); if (not have_pending()) - issue(); + issue(loc); } return qid; } -void pqxx::pipeline::complete() +void pqxx::pipeline::complete(sl loc) { if (have_pending()) - receive(m_issuedrange.second); + receive(m_issuedrange.second, loc); if (m_num_waiting and (m_error == qid_limit())) { - issue(); - receive(std::end(m_queries)); + issue(loc); + receive(std::end(m_queries), loc); } detach(); } -void pqxx::pipeline::flush() +void pqxx::pipeline::flush(sl loc) { if (not std::empty(m_queries)) { if (have_pending()) - receive(m_issuedrange.second); + receive(m_issuedrange.second, loc); m_issuedrange.first = m_issuedrange.second = std::end(m_queries); m_num_waiting = 0; m_dummy_pending = false; @@ -123,11 +125,12 @@ void pqxx::pipeline::flush() } -void PQXX_COLD pqxx::pipeline::cancel() +void PQXX_COLD pqxx::pipeline::cancel(sl loc) { while (have_pending()) { - pqxx::internal::gate::connection_pipeline(m_trans->conn()).cancel_query(); + pqxx::internal::gate::connection_pipeline(m_trans->conn()) + .cancel_query(loc); auto canceled_query{m_issuedrange.first}; ++m_issuedrange.first; m_queries.erase(canceled_query); @@ -146,11 +149,12 @@ bool pqxx::pipeline::is_finished(pipeline::query_id q) const } -std::pair pqxx::pipeline::retrieve() +std::pair +pqxx::pipeline::retrieve(sl loc) { if (std::empty(m_queries)) throw std::logic_error{"Attempt to retrieve result from empty pipeline."}; - return retrieve(std::begin(m_queries)); + return retrieve(std::begin(m_queries), loc); } @@ -170,14 +174,14 @@ int pqxx::pipeline::retain(int retain_max) & } -void pqxx::pipeline::resume() & +void pqxx::pipeline::resume(sl loc) & { if (have_pending()) - receive_if_available(); + receive_if_available(loc); if (not have_pending() and m_num_waiting) { - issue(); - receive_if_available(); + issue(loc); + receive_if_available(loc); } } @@ -191,10 +195,10 @@ pqxx::pipeline::query_id pqxx::pipeline::generate_id() } -void pqxx::pipeline::issue() +void pqxx::pipeline::issue(sl loc) { // Retrieve that null result for the last query, if needed. - obtain_result(); + obtain_result(false, loc); // Don't issue anything if we've encountered an error. if (m_error < qid_limit()) @@ -220,18 +224,18 @@ void pqxx::pipeline::issue() m_dummy_pending = prepend_dummy; m_issuedrange.first = oldest; m_issuedrange.second = std::end(m_queries); - m_num_waiting -= check_cast(num_issued, "pipeline issue()"sv); + m_num_waiting -= check_cast(num_issued, "pipeline issue()"sv, loc); } -void PQXX_COLD pqxx::pipeline::internal_error(std::string const &err) +void PQXX_COLD pqxx::pipeline::internal_error(std::string const &err, sl loc) { set_error_at(0); - throw pqxx::internal_error{err}; + throw pqxx::internal_error{err, loc}; } -bool pqxx::pipeline::obtain_result(bool expect_none) +bool pqxx::pipeline::obtain_result(bool expect_none, sl loc) { pqxx::internal::gate::connection_pipeline gate{m_trans->conn()}; std::shared_ptr const r{ @@ -260,7 +264,7 @@ bool pqxx::pipeline::obtain_result(bool expect_none) // Must be the result for the oldest pending query. if (not std::empty(m_issuedrange.first->second.res)) [[unlikely]] - internal_error("Multiple results for one query."); + internal_error("Multiple results for one query.", loc); m_issuedrange.first->second.res = res; ++m_issuedrange.first; @@ -269,7 +273,7 @@ bool pqxx::pipeline::obtain_result(bool expect_none) } -void pqxx::pipeline::obtain_dummy() +void pqxx::pipeline::obtain_dummy(sl loc) { // Allocate once, re-use across invocations. static auto const text{ @@ -282,7 +286,7 @@ void pqxx::pipeline::obtain_dummy() if (not r) [[unlikely]] internal_error( - "Pipeline got no result from backend when it expected one."); + "Pipeline got no result from backend when it expected one.", loc); pqxx::internal::gate::connection_pipeline const pgate{m_trans->conn()}; auto handler{pgate.get_notice_waiters()}; @@ -292,7 +296,7 @@ void pqxx::pipeline::obtain_dummy() bool OK{false}; try { - pqxx::internal::gate::result_creation{R}.check_status(); + pqxx::internal::gate::result_creation{R}.check_status(loc); OK = true; } catch (sql_error const &) @@ -300,10 +304,11 @@ void pqxx::pipeline::obtain_dummy() if (OK) [[likely]] { if (std::size(R) > 1) [[unlikely]] - internal_error("Unexpected result for dummy query in pipeline."); + internal_error("Unexpected result for dummy query in pipeline.", loc); - if (R.at(0).at(0).as() != theDummyValue) [[unlikely]] - internal_error("Dummy query in pipeline returned unexpected value."); + if (R.at(0).at(0).as(loc) != theDummyValue) [[unlikely]] + internal_error( + "Dummy query in pipeline returned unexpected value.", loc); return; } @@ -327,11 +332,12 @@ void pqxx::pipeline::obtain_dummy() auto const stop{m_issuedrange.second}; // Retrieve that null result for the last query, if needed - obtain_result(true); + obtain_result(true, loc); // Reset internal state to forget botched batch attempt m_num_waiting += check_cast( - std::distance(m_issuedrange.first, stop), "pipeline obtain_dummy()"sv); + std::distance(m_issuedrange.first, stop), "pipeline obtain_dummy()"sv, + loc); m_issuedrange.second = m_issuedrange.first; // Issue queries in failed batch one at a time. @@ -342,8 +348,8 @@ void pqxx::pipeline::obtain_dummy() m_num_waiting--; auto const query{*m_issuedrange.first->second.query}; auto &holder{m_issuedrange.first->second}; - holder.res = m_trans->exec(query); - pqxx::internal::gate::result_creation{holder.res}.check_status(); + holder.res = m_trans->exec(query, loc); + pqxx::internal::gate::result_creation{holder.res}.check_status(loc); ++m_issuedrange.first; } while (m_issuedrange.first != stop); } @@ -359,7 +365,7 @@ void pqxx::pipeline::obtain_dummy() std::pair -pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q) +pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q, sl loc) { if (q == std::end(m_queries)) throw std::logic_error{"Attempt to retrieve result for unknown query."}; @@ -374,9 +380,9 @@ pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q) (q->first >= m_issuedrange.second->first)) { if (have_pending()) - receive(m_issuedrange.second); + receive(m_issuedrange.second, loc); if (m_error == qid_limit()) - issue(); + issue(loc); } // If result not in yet, get it; else get at least whatever's convenient. @@ -386,11 +392,11 @@ pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q) { auto suc{q}; ++suc; - receive(suc); + receive(suc, loc); } else { - receive_if_available(); + receive_if_available(loc); } } @@ -400,51 +406,51 @@ pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q) // Don't leave the backend idle if there are queries waiting to be issued. if (m_num_waiting and not have_pending() and (m_error == qid_limit())) - issue(); + issue(loc); result const R{q->second.res}; auto P{std::make_pair(q->first, R)}; m_queries.erase(q); - pqxx::internal::gate::result_creation{R}.check_status(); + pqxx::internal::gate::result_creation{R}.check_status(loc); return P; } -void pqxx::pipeline::get_further_available_results() +void pqxx::pipeline::get_further_available_results(sl loc) { pqxx::internal::gate::connection_pipeline gate{m_trans->conn()}; - while (not gate.is_busy() and obtain_result()) + while (not gate.is_busy() and obtain_result(false, loc)) if (not gate.consume_input()) - throw broken_connection{}; + throw broken_connection{loc}; } -void pqxx::pipeline::receive_if_available() +void pqxx::pipeline::receive_if_available(sl loc) { pqxx::internal::gate::connection_pipeline gate{m_trans->conn()}; if (not gate.consume_input()) - throw broken_connection{}; + throw broken_connection{loc}; if (gate.is_busy()) return; if (m_dummy_pending) - obtain_dummy(); + obtain_dummy(loc); if (have_pending()) - get_further_available_results(); + get_further_available_results(loc); } -void pqxx::pipeline::receive(pipeline::QueryMap::const_iterator stop) +void pqxx::pipeline::receive(pipeline::QueryMap::const_iterator stop, sl loc) { if (m_dummy_pending) - obtain_dummy(); + obtain_dummy(loc); - while (obtain_result() and + while (obtain_result(false, loc) and QueryMap::const_iterator{m_issuedrange.first} != stop); // Also haul in any remaining "targets of opportunity". if (QueryMap::const_iterator{m_issuedrange.first} == stop) - get_further_available_results(); + get_further_available_results(loc); } diff --git a/src/result.cxx b/src/result.cxx index ad13f207f..0e7c03d81 100644 --- a/src/result.cxx +++ b/src/result.cxx @@ -75,25 +75,25 @@ bool pqxx::result::operator==(result const &rhs) const noexcept } -pqxx::result::const_reverse_iterator pqxx::result::rbegin() const +pqxx::result::const_reverse_iterator pqxx::result::rbegin() const noexcept { return const_reverse_iterator{end()}; } -pqxx::result::const_reverse_iterator pqxx::result::crbegin() const +pqxx::result::const_reverse_iterator pqxx::result::crbegin() const noexcept { return rbegin(); } -pqxx::result::const_reverse_iterator pqxx::result::rend() const +pqxx::result::const_reverse_iterator pqxx::result::rend() const noexcept { return const_reverse_iterator{begin()}; } -pqxx::result::const_reverse_iterator pqxx::result::crend() const +pqxx::result::const_reverse_iterator pqxx::result::crend() const noexcept { return rend(); } @@ -159,21 +159,21 @@ pqxx::field pqxx::result::operator[]( #endif // PQXX_HAVE_MULTIDIM -pqxx::row pqxx::result::at(pqxx::result::size_type i) const +pqxx::row pqxx::result::at(pqxx::result::size_type i, sl loc) const { if (i >= size()) - throw range_error{"Row number out of range."}; + throw range_error{"Row number out of range.", loc}; return operator[](i); } pqxx::field pqxx::result::at( - pqxx::result_size_type row_num, pqxx::row_size_type col_num) const + pqxx::result_size_type row_num, pqxx::row_size_type col_num, sl loc) const { if (row_num >= size()) - throw range_error{"Row number out of range."}; + throw range_error{"Row number out of range.", loc}; if (col_num >= columns()) - throw range_error{"Column out of range."}; + throw range_error{"Column out of range.", loc}; return {*this, row_num, col_num}; } @@ -188,7 +188,7 @@ inline bool equal(char const lhs[], char const rhs[]) } // namespace void PQXX_COLD pqxx::result::throw_sql_error( - std::string const &Err, std::string const &Query) const + std::string const &Err, std::string const &Query, sl loc) const { // Try to establish more precise error type, and throw corresponding // type of exception. @@ -197,7 +197,7 @@ void PQXX_COLD pqxx::result::throw_sql_error( { // No SQLSTATE at all. Can this even happen? // Let's assume the connection is no longer usable. - throw broken_connection{Err}; + throw broken_connection{Err, loc}; } switch (code[0]) @@ -209,44 +209,44 @@ void PQXX_COLD pqxx::result::throw_sql_error( // connection was just fine, so we had no real way of detecting the // problem. (Trying to continue to use the connection does break // though, so I feel justified in panicking.) - throw broken_connection{Err}; + throw broken_connection{Err, loc}; case '0': switch (code[1]) { - case 'A': throw feature_not_supported{Err, Query, code}; + case 'A': throw feature_not_supported{Err, Query, code, loc}; case '8': if (equal(code, "08P01")) - throw protocol_violation{Err}; - throw broken_connection{Err}; + throw protocol_violation{Err, loc}; + throw broken_connection{Err, loc}; case 'L': - case 'P': throw insufficient_privilege{Err, Query, code}; + case 'P': throw insufficient_privilege{Err, Query, code, loc}; } break; case '2': switch (code[1]) { - case '2': throw data_exception{Err, Query, code}; + case '2': throw data_exception{Err, Query, code, loc}; case '3': if (equal(code, "23001")) - throw restrict_violation{Err, Query, code}; + throw restrict_violation{Err, Query, code, loc}; if (equal(code, "23502")) - throw not_null_violation{Err, Query, code}; + throw not_null_violation{Err, Query, code, loc}; if (equal(code, "23503")) - throw foreign_key_violation{Err, Query, code}; + throw foreign_key_violation{Err, Query, code, loc}; if (equal(code, "23505")) - throw unique_violation{Err, Query, code}; + throw unique_violation{Err, Query, code, loc}; if (equal(code, "23514")) - throw check_violation{Err, Query, code}; - throw integrity_constraint_violation{Err, Query, code}; - case '4': throw invalid_cursor_state{Err, Query, code}; - case '6': throw invalid_sql_statement_name{Err, Query, code}; + throw check_violation{Err, Query, code, loc}; + throw integrity_constraint_violation{Err, Query, code, loc}; + case '4': throw invalid_cursor_state{Err, Query, code, loc}; + case '6': throw invalid_sql_statement_name{Err, Query, code, loc}; } break; case '3': switch (code[1]) { - case '4': throw invalid_cursor_name{Err, Query, code}; + case '4': throw invalid_cursor_name{Err, Query, code, loc}; } break; case '4': @@ -254,25 +254,25 @@ void PQXX_COLD pqxx::result::throw_sql_error( { case '0': if (equal(code, "40000")) - throw transaction_rollback{Err, Query, code}; + throw transaction_rollback{Err, Query, code, loc}; if (equal(code, "40001")) - throw serialization_failure{Err, Query, code}; + throw serialization_failure{Err, Query, code, loc}; if (equal(code, "40003")) - throw statement_completion_unknown{Err, Query, code}; + throw statement_completion_unknown{Err, Query, code, loc}; if (equal(code, "40P01")) - throw deadlock_detected{Err, Query, code}; + throw deadlock_detected{Err, Query, code, loc}; break; case '2': if (equal(code, "42501")) - throw insufficient_privilege{Err, Query}; + throw insufficient_privilege{Err, Query, nullptr, loc}; if (equal(code, "42601")) - throw syntax_error{Err, Query, code, errorposition()}; + throw syntax_error{Err, Query, code, errorposition(), loc}; if (equal(code, "42703")) - throw undefined_column{Err, Query, code}; + throw undefined_column{Err, Query, code, loc}; if (equal(code, "42883")) - throw undefined_function{Err, Query, code}; + throw undefined_function{Err, Query, code, loc}; if (equal(code, "42P01")) - throw undefined_table{Err, Query, code}; + throw undefined_table{Err, Query, code, loc}; } break; case '5': @@ -280,44 +280,44 @@ void PQXX_COLD pqxx::result::throw_sql_error( { case '3': if (equal(code, "53100")) - throw disk_full{Err, Query, code}; + throw disk_full{Err, Query, code, loc}; if (equal(code, "53200")) - throw out_of_memory{Err, Query, code}; + throw out_of_memory{Err, Query, code, loc}; if (equal(code, "53300")) - throw too_many_connections{Err}; - throw insufficient_resources{Err, Query, code}; + throw too_many_connections{Err, loc}; + throw insufficient_resources{Err, Query, code, loc}; } break; case 'P': if (equal(code, "P0001")) - throw plpgsql_raise{Err, Query, code}; + throw plpgsql_raise{Err, Query, code, loc}; if (equal(code, "P0002")) - throw plpgsql_no_data_found{Err, Query, code}; + throw plpgsql_no_data_found{Err, Query, code, loc}; if (equal(code, "P0003")) - throw plpgsql_too_many_rows{Err, Query, code}; + throw plpgsql_too_many_rows{Err, Query, code, loc}; throw plpgsql_error{Err, Query, code}; } // Unknown error code. - throw sql_error{Err, Query, code}; + throw sql_error{Err, Query, code, loc}; } -void pqxx::result::check_status(std::string_view desc) const +void pqxx::result::check_status(std::string_view desc, sl loc) const { - if (auto err{status_error()}; not std::empty(err)) [[unlikely]] + if (auto err{status_error(loc)}; not std::empty(err)) [[unlikely]] { if (not std::empty(desc)) err = pqxx::internal::concat("Failure during '", desc, "': ", err); - throw_sql_error(err, query()); + throw_sql_error(err, query(), loc); } } -std::string pqxx::result::status_error() const +std::string pqxx::result::status_error(sl loc) const { if (m_data.get() == nullptr) - throw failure{"No result set given."}; + throw failure{"No result set given.", loc}; std::string err; @@ -334,7 +334,8 @@ std::string pqxx::result::status_error() const #if defined(LIBPQ_HAS_PIPELINING) case PGRES_PIPELINE_SYNC: // Pipeline mode synchronisation point. case PGRES_PIPELINE_ABORTED: // Previous command in pipeline failed. - throw feature_not_supported{"Not supported yet: libpq pipelines."}; + throw feature_not_supported{ + "Not supported yet: libpq pipelines.", "", nullptr, loc}; #endif case PGRES_BAD_RESPONSE: // The server's response was not understood. @@ -344,12 +345,15 @@ std::string pqxx::result::status_error() const break; case PGRES_SINGLE_TUPLE: - throw feature_not_supported{"Not supported: single-row mode."}; + throw feature_not_supported{ + "Not supported: single-row mode.", "", nullptr, loc}; default: - throw internal_error{internal::concat( - "pqxx::result: Unrecognized result status code ", - PQresultStatus(m_data.get()))}; + throw internal_error{ + internal::concat( + "pqxx::result: Unrecognized result status code ", + PQresultStatus(m_data.get())), + loc}; } return err; } @@ -371,11 +375,11 @@ std::string const &pqxx::result::query() const & noexcept } -pqxx::oid pqxx::result::inserted_oid() const +pqxx::oid pqxx::result::inserted_oid(sl loc) const { if (m_data.get() == nullptr) throw usage_error{ - "Attempt to read oid of inserted row without an INSERT result"}; + "Attempt to read oid of inserted row without an INSERT result", loc}; return PQoidValue(m_data.get()); } @@ -413,29 +417,31 @@ pqxx::field::size_type pqxx::result::get_length( } -pqxx::oid pqxx::result::column_type(row::size_type col_num) const +pqxx::oid pqxx::result::column_type(row::size_type col_num, sl loc) const { oid const t{PQftype(m_data.get(), col_num)}; if (t == oid_none) - throw argument_error{internal::concat( - "Attempt to retrieve type of nonexistent column ", col_num, - " of query result.")}; + throw argument_error{ + internal::concat( + "Attempt to retrieve type of nonexistent column ", col_num, + " of query result."), + loc}; return t; } -pqxx::row::size_type pqxx::result::column_number(zview col_name) const +pqxx::row::size_type pqxx::result::column_number(zview col_name, sl loc) const { auto const n{PQfnumber(m_data.get(), col_name.c_str())}; if (n == -1) throw argument_error{ - internal::concat("Unknown column name: '", col_name, "'.")}; + internal::concat("Unknown column name: '", col_name, "'."), loc}; return static_cast(n); } -pqxx::oid pqxx::result::column_table(row::size_type col_num) const +pqxx::oid pqxx::result::column_table(row::size_type col_num, sl loc) const { oid const t{PQftable(m_data.get(), col_num)}; @@ -443,15 +449,18 @@ pqxx::oid pqxx::result::column_table(row::size_type col_num) const * we got an invalid row number. */ if (t == oid_none and col_num >= columns()) - throw argument_error{internal::concat( - "Attempt to retrieve table ID for column ", col_num, " out of ", - columns())}; + throw argument_error{ + internal::concat( + "Attempt to retrieve table ID for column ", col_num, " out of ", + columns()), + loc}; return t; } -pqxx::row::size_type pqxx::result::table_column(row::size_type col_num) const +pqxx::row::size_type +pqxx::result::table_column(row::size_type col_num, sl loc) const { auto const n{row::size_type(PQftablecol(m_data.get(), col_num))}; if (n != 0) [[likely]] @@ -461,16 +470,21 @@ pqxx::row::size_type pqxx::result::table_column(row::size_type col_num) const auto const col_str{to_string(col_num)}; if (col_num > columns()) throw range_error{ - internal::concat("Invalid column index in table_column(): ", col_str)}; + internal::concat("Invalid column index in table_column(): ", col_str), + loc}; if (m_data.get() == nullptr) - throw usage_error{internal::concat( - "Can't query origin of column ", col_str, - ": result is not initialized.")}; + throw usage_error{ + internal::concat( + "Can't query origin of column ", col_str, + ": result is not initialized."), + loc}; - throw usage_error{internal::concat( - "Can't query origin of column ", col_str, - ": not derived from table column.")}; + throw usage_error{ + internal::concat( + "Can't query origin of column ", col_str, + ": not derived from table column."), + loc}; } @@ -487,16 +501,19 @@ int pqxx::result::errorposition() const } -char const *pqxx::result::column_name(pqxx::row::size_type number) const & +char const * +pqxx::result::column_name(pqxx::row::size_type number, sl loc) const & { auto const n{PQfname(m_data.get(), number)}; if (n == nullptr) [[unlikely]] { if (m_data.get() == nullptr) - throw usage_error{"Queried column name on null result."}; - throw range_error{internal::concat( - "Invalid column number: ", number, " (maximum is ", (columns() - 1), - ").")}; + throw usage_error{"Queried column name on null result.", loc}; + throw range_error{ + internal::concat( + "Invalid column number: ", number, " (maximum is ", (columns() - 1), + ")."), + loc}; } return n; } @@ -508,17 +525,21 @@ pqxx::row::size_type pqxx::result::columns() const noexcept } -int pqxx::result::column_storage(pqxx::row::size_type number) const +int pqxx::result::column_storage(pqxx::row::size_type number, sl loc) const { int const out{PQfsize(m_data.get(), number)}; if (out == 0) { auto const sz{this->size()}; if ((number < 0) or (number >= sz)) - throw argument_error{pqxx::internal::concat( - "Column number out of range: ", number, " (have 0 - ", sz, ")")}; - throw failure{pqxx::internal::concat( - "Error getting column_storage for column ", number)}; + throw argument_error{ + pqxx::internal::concat( + "Column number out of range: ", number, " (have 0 - ", sz, ")"), + loc}; + throw failure{ + pqxx::internal::concat( + "Error getting column_storage for column ", number), + loc}; } return out; } @@ -531,7 +552,7 @@ int pqxx::result::column_type_modifier( } -pqxx::row pqxx::result::one_row() const +pqxx::row pqxx::result::one_row(sl loc) const { auto const sz{size()}; if (sz != 1) @@ -539,34 +560,41 @@ pqxx::row pqxx::result::one_row() const // TODO: See whether result contains a generated statement. if (not m_query or m_query->empty()) throw unexpected_rows{ - pqxx::internal::concat("Expected 1 row from query, got ", sz, ".")}; + pqxx::internal::concat("Expected 1 row from query, got ", sz, "."), + loc}; else - throw unexpected_rows{pqxx::internal::concat( - "Expected 1 row from query '", *m_query, "', got ", sz, ".")}; + throw unexpected_rows{ + pqxx::internal::concat( + "Expected 1 row from query '", *m_query, "', got ", sz, "."), + loc}; } return front(); } -pqxx::field pqxx::result::one_field() const +pqxx::field pqxx::result::one_field(sl loc) const { - expect_columns(1); - return one_row()[0]; + expect_columns(1, loc); + return one_row(loc)[0]; } -std::optional pqxx::result::opt_row() const +std::optional pqxx::result::opt_row(sl loc) const { auto const sz{size()}; if (sz > 1) { // TODO: See whether result contains a generated statement. if (not m_query or m_query->empty()) - throw unexpected_rows{pqxx::internal::concat( - "Expected at most 1 row from query, got ", sz, ".")}; + throw unexpected_rows{ + pqxx::internal::concat( + "Expected at most 1 row from query, got ", sz, "."), + loc}; else - throw unexpected_rows{pqxx::internal::concat( - "Expected at most 1 row from query '", *m_query, "', got ", sz, ".")}; + throw unexpected_rows{ + pqxx::internal::concat( + "Expected at most 1 row from query '", *m_query, "', got ", sz, "."), + loc}; } else if (sz == 1) { diff --git a/src/robusttransaction.cxx b/src/robusttransaction.cxx index f2726e33b..7c05d7bf2 100644 --- a/src/robusttransaction.cxx +++ b/src/robusttransaction.cxx @@ -72,55 +72,59 @@ constexpr tx_stat parse_status(std::string_view text) noexcept } -tx_stat query_status(std::string const &xid, std::string const &conn_str) +tx_stat query_status( + std::string const &xid, std::string const &conn_str, + pqxx::sl loc = pqxx::sl::current()) { static std::string const name{"robusttxck"sv}; auto const query{pqxx::internal::concat("SELECT txid_status(", xid, ")")}; - pqxx::connection cx{conn_str}; + pqxx::connection cx{conn_str, loc}; pqxx::nontransaction tx{cx, name}; - auto const status_row{tx.exec(query).one_row()}; + auto const status_row{tx.exec(query, loc).one_row(loc)}; auto const status_field{status_row[0]}; if (std::size(status_field) == 0) - throw pqxx::internal_error{"Transaction status string is empty."}; - auto const status{parse_status(status_field.as())}; + throw pqxx::internal_error{"Transaction status string is empty.", loc}; + auto const status{parse_status(status_field.as(loc))}; if (status == tx_unknown) - throw pqxx::internal_error{pqxx::internal::concat( - "Unknown transaction status string: ", status_field.view())}; + throw pqxx::internal_error{ + pqxx::internal::concat( + "Unknown transaction status string: ", status_field.view()), + loc}; return status; } } // namespace -void pqxx::internal::basic_robusttransaction::init(zview begin_command) +void pqxx::internal::basic_robusttransaction::init(zview begin_command, sl loc) { static auto const txid_q{ std::make_shared("SELECT txid_current()"sv)}; m_backendpid = conn().backendpid(); - direct_exec(begin_command); - direct_exec(txid_q).one_field().to(m_xid); + direct_exec(begin_command, loc); + direct_exec(txid_q, loc).one_field(loc).to(m_xid); } pqxx::internal::basic_robusttransaction::basic_robusttransaction( - connection &cx, zview begin_command, std::string_view tname) : + connection &cx, zview begin_command, std::string_view tname, sl loc) : dbtransaction(cx, tname), m_conn_string{cx.connection_string()} { - init(begin_command); + init(begin_command, loc); } pqxx::internal::basic_robusttransaction::basic_robusttransaction( - connection &cx, zview begin_command) : + connection &cx, zview begin_command, sl loc) : dbtransaction(cx), m_conn_string{cx.connection_string()} { - init(begin_command); + init(begin_command, loc); } pqxx::internal::basic_robusttransaction::~basic_robusttransaction() = default; -void pqxx::internal::basic_robusttransaction::do_commit() +void pqxx::internal::basic_robusttransaction::do_commit(sl loc) { static auto const check_constraints_q{ std::make_shared("SET CONSTRAINTS ALL IMMEDIATE"sv)}, @@ -129,11 +133,11 @@ void pqxx::internal::basic_robusttransaction::do_commit() // minimise our in-doubt window. try { - direct_exec(check_constraints_q); + direct_exec(check_constraints_q, loc); } catch (std::exception const &) { - do_abort(); + do_abort(loc); throw; } @@ -148,7 +152,7 @@ void pqxx::internal::basic_robusttransaction::do_commit() // robusttransaction what it is. try { - direct_exec(commit_q); + direct_exec(commit_q, loc); // If we make it here, great. Normal, successful commit. return; @@ -163,7 +167,7 @@ void pqxx::internal::basic_robusttransaction::do_commit() if (conn().is_open()) { // Commit failed, for some other reason. - do_abort(); + do_abort(loc); throw; } // Otherwise, fall through to in-doubt handling. @@ -180,7 +184,7 @@ void pqxx::internal::basic_robusttransaction::do_commit() { try { - switch (query_status(m_xid, m_conn_string)) + switch (query_status(m_xid, m_conn_string, loc)) { case tx_unknown: // We were unable to reconnect and query transaction status. @@ -191,7 +195,7 @@ void pqxx::internal::basic_robusttransaction::do_commit() return; case tx_aborted: // Aborted. We're done. - do_abort(); + do_abort(loc); return; case tx_in_progress: // The transaction is still running. Stick around until we know what @@ -208,14 +212,16 @@ void pqxx::internal::basic_robusttransaction::do_commit() } // Okay, this has taken too long. Give up, report in-doubt state. - throw in_doubt_error{internal::concat( - "Transaction ", name(), " (with transaction ID ", m_xid, - ") " - "lost connection while committing. It's impossible to tell whether " - "it committed, or aborted, or is still running. " - "Attempts to find out its outcome have failed. " - "The backend process on the server had process ID ", - m_backendpid, - ". " - "You may be able to check what happened to that process.")}; + throw in_doubt_error{ + internal::concat( + "Transaction ", name(), " (with transaction ID ", m_xid, + ") " + "lost connection while committing. It's impossible to tell whether " + "it committed, or aborted, or is still running. " + "Attempts to find out its outcome have failed. " + "The backend process on the server had process ID ", + m_backendpid, + ". " + "You may be able to check what happened to that process."), + loc}; } diff --git a/src/row.cxx b/src/row.cxx index 63972314c..78ea7fa1a 100644 --- a/src/row.cxx +++ b/src/row.cxx @@ -114,7 +114,8 @@ pqxx::row::reference pqxx::row::operator[](size_type i) const noexcept pqxx::row::reference pqxx::row::operator[](zview col_name) const { - return at(col_name); + sl const loc{sl::current()}; + return at(col_name, loc); } @@ -130,42 +131,42 @@ void pqxx::row::swap(row &rhs) noexcept } -pqxx::field pqxx::row::at(zview col_name) const +pqxx::field pqxx::row::at(zview col_name, sl loc) const { - return {m_result, m_index, column_number(col_name)}; + return {m_result, m_index, column_number(col_name, loc)}; } -pqxx::field pqxx::row::at(pqxx::row::size_type i) const +pqxx::field pqxx::row::at(pqxx::row::size_type i, sl loc) const { if (i >= size()) - throw range_error{"Invalid field number."}; + throw range_error{"Invalid field number.", loc}; return operator[](i); } -pqxx::oid pqxx::row::column_type(size_type col_num) const +pqxx::oid pqxx::row::column_type(size_type col_num, sl loc) const { - return m_result.column_type(col_num); + return m_result.column_type(col_num, loc); } -pqxx::oid pqxx::row::column_table(size_type col_num) const +pqxx::oid pqxx::row::column_table(size_type col_num, sl loc) const { - return m_result.column_table(col_num); + return m_result.column_table(col_num, loc); } -pqxx::row::size_type pqxx::row::table_column(size_type col_num) const +pqxx::row::size_type pqxx::row::table_column(size_type col_num, sl loc) const { - return m_result.table_column(col_num); + return m_result.table_column(col_num, loc); } -pqxx::row::size_type pqxx::row::column_number(zview col_name) const +pqxx::row::size_type pqxx::row::column_number(zview col_name, sl loc) const { - return m_result.column_number(col_name); + return m_result.column_number(col_name, loc); } diff --git a/src/sql_cursor.cxx b/src/sql_cursor.cxx index 4a7c2537c..e3bc6225f 100644 --- a/src/sql_cursor.cxx +++ b/src/sql_cursor.cxx @@ -57,8 +57,8 @@ inline bool useless_trail(char c) * * The query must be nonempty. */ -std::string::size_type -find_query_end(std::string_view query, pqxx::internal::encoding_group enc) +std::string::size_type find_query_end( + std::string_view query, pqxx::internal::encoding_group enc, pqxx::sl loc) { auto const text{std::data(query)}; auto const size{std::size(query)}; @@ -81,7 +81,7 @@ find_query_end(std::string_view query, pqxx::internal::encoding_group enc) if (gend - gbegin > 1 or not useless_trail(*gbegin)) end = std::string::size_type(gend - text); }, - text, size); + text, size, 0u, loc); } return end; @@ -92,18 +92,18 @@ find_query_end(std::string_view query, pqxx::internal::encoding_group enc) pqxx::internal::sql_cursor::sql_cursor( transaction_base &t, std::string_view query, std::string_view cname, cursor_base::access_policy ap, cursor_base::update_policy up, - cursor_base::ownership_policy op, bool hold) : + cursor_base::ownership_policy op, bool hold, sl loc) : cursor_base{t.conn(), cname}, m_home{t.conn()}, m_at_end{-1}, m_pos{0} { if (&t.conn() != &m_home) - throw internal_error{"Cursor in wrong connection"}; + throw internal_error{"Cursor in wrong connection", loc}; if (std::empty(query)) - throw usage_error{"Cursor has empty query."}; - auto const enc{enc_group(t.conn().encoding_id())}; - auto const qend{find_query_end(query, enc)}; + throw usage_error{"Cursor has empty query.", loc}; + auto const enc{enc_group(t.conn().encoding_id(loc), loc)}; + auto const qend{find_query_end(query, enc, loc)}; if (qend == 0) - throw usage_error{"Cursor has effectively empty query."}; + throw usage_error{"Cursor has effectively empty query.", loc}; query.remove_suffix(std::size(query) - qend); std::string const cq{internal::concat( @@ -112,13 +112,13 @@ pqxx::internal::sql_cursor::sql_cursor( (hold ? "WITH HOLD "sv : ""sv), "FOR "sv, query, " "sv, ((up == cursor_base::update) ? "FOR UPDATE "sv : "FOR READ ONLY "sv))}; - t.exec(cq); + t.exec(cq, loc); // Now that we're here in the starting position, keep a copy of an empty // result. That may come in handy later, because we may not be able to // construct an empty result with all the right metadata due to the weird // meaning of "FETCH 0." - init_empty_result(t); + init_empty_result(t, loc); m_ownership = op; } @@ -135,14 +135,14 @@ pqxx::internal::sql_cursor::sql_cursor( {} -void pqxx::internal::sql_cursor::close() noexcept +void pqxx::internal::sql_cursor::close(sl loc) noexcept { if (m_ownership == cursor_base::owned) { try { gate::connection_sql_cursor{m_home}.exec( - internal::concat("CLOSE "sv, m_home.quote_name(name())).c_str()); + internal::concat("CLOSE "sv, m_home.quote_name(name())).c_str(), loc); } catch (std::exception const &) {} @@ -151,12 +151,12 @@ void pqxx::internal::sql_cursor::close() noexcept } -void pqxx::internal::sql_cursor::init_empty_result(transaction_base &t) +void pqxx::internal::sql_cursor::init_empty_result(transaction_base &t, sl loc) { if (pos() != 0) - throw internal_error{"init_empty_result() from bad pos()."}; + throw internal_error{"init_empty_result() from bad pos().", loc}; m_empty_result = - t.exec(internal::concat("FETCH 0 IN "sv, m_home.quote_name(name()))); + t.exec(internal::concat("FETCH 0 IN "sv, m_home.quote_name(name())), loc); } @@ -217,7 +217,7 @@ pqxx::internal::sql_cursor::difference_type pqxx::internal::sql_cursor::adjust( pqxx::result pqxx::internal::sql_cursor::fetch( - difference_type rows, difference_type &displacement) + difference_type rows, difference_type &displacement, sl loc) { if (rows == 0) { @@ -226,14 +226,14 @@ pqxx::result pqxx::internal::sql_cursor::fetch( } auto const query{pqxx::internal::concat( "FETCH "sv, stridestring(rows), " IN "sv, m_home.quote_name(name()))}; - auto r{gate::connection_sql_cursor{m_home}.exec(query.c_str())}; + auto r{gate::connection_sql_cursor{m_home}.exec(query.c_str(), loc)}; displacement = adjust(rows, difference_type(std::size(r))); return r; } pqxx::cursor_base::difference_type pqxx::internal::sql_cursor::move( - difference_type rows, difference_type &displacement) + difference_type rows, difference_type &displacement, sl loc) { if (rows == 0) { @@ -243,7 +243,7 @@ pqxx::cursor_base::difference_type pqxx::internal::sql_cursor::move( auto const query{pqxx::internal::concat( "MOVE "sv, stridestring(rows), " IN "sv, m_home.quote_name(name()))}; - auto const r{gate::connection_sql_cursor{m_home}.exec(query.c_str())}; + auto const r{gate::connection_sql_cursor{m_home}.exec(query.c_str(), loc)}; auto d{static_cast(r.affected_rows())}; displacement = adjust(rows, d); return d; diff --git a/src/strconv.cxx b/src/strconv.cxx index d12eb3a32..43d901b76 100644 --- a/src/strconv.cxx +++ b/src/strconv.cxx @@ -234,15 +234,17 @@ std::string demangle_type_name(char const raw[]) // TODO: Equivalents for converting a null in the other direction. -void PQXX_COLD throw_null_conversion(std::string const &type) +void PQXX_COLD throw_null_conversion(std::string const &type, sl loc) { - throw conversion_error{concat("Attempt to convert SQL null to ", type, ".")}; + throw conversion_error{ + concat("Attempt to convert SQL null to ", type, "."), loc}; } -void PQXX_COLD throw_null_conversion(std::string_view type) +void PQXX_COLD throw_null_conversion(std::string_view type, sl loc) { - throw conversion_error{concat("Attempt to convert SQL null to ", type, ".")}; + throw conversion_error{ + concat("Attempt to convert SQL null to ", type, "."), loc}; } diff --git a/src/stream_from.cxx b/src/stream_from.cxx index 98d7ace7b..b51a610b2 100644 --- a/src/stream_from.cxx +++ b/src/stream_from.cxx @@ -28,8 +28,9 @@ namespace { pqxx::internal::char_finder_func *get_finder(pqxx::transaction_base const &tx) { - auto const group{pqxx::internal::enc_group(tx.conn().encoding_id())}; - return pqxx::internal::get_char_finder<'\t', '\\'>(group); + pqxx::sl const loc{pqxx::sl::current()}; + auto const group{pqxx::internal::enc_group(tx.conn().encoding_id(loc), loc)}; + return pqxx::internal::get_char_finder<'\t', '\\'>(group, loc); } @@ -41,7 +42,9 @@ pqxx::stream_from::stream_from( transaction_base &tx, from_query_t, std::string_view query) : transaction_focus{tx, class_name}, m_char_finder{get_finder(tx)} { - tx.exec(internal::concat("COPY ("sv, query, ") TO STDOUT"sv)).no_rows(); + sl const loc{sl::current()}; + tx.exec(internal::concat("COPY ("sv, query, ") TO STDOUT"sv), loc) + .no_rows(loc); register_me(); } @@ -50,8 +53,10 @@ pqxx::stream_from::stream_from( transaction_base &tx, from_table_t, std::string_view table) : transaction_focus{tx, class_name, table}, m_char_finder{get_finder(tx)} { - tx.exec(internal::concat("COPY "sv, tx.quote_name(table), " TO STDOUT"sv)) - .no_rows(); + sl const loc{sl::current()}; + tx.exec( + internal::concat("COPY "sv, tx.quote_name(table), " TO STDOUT"sv), loc) + .no_rows(loc); register_me(); } @@ -61,12 +66,15 @@ pqxx::stream_from::stream_from( from_table_t) : transaction_focus{tx, class_name, table}, m_char_finder{get_finder(tx)} { + sl const loc{sl::current()}; if (std::empty(columns)) [[unlikely]] - tx.exec(internal::concat("COPY "sv, table, " TO STDOUT"sv)).no_rows(); + tx.exec(internal::concat("COPY "sv, table, " TO STDOUT"sv), loc) + .no_rows(loc); else [[likely]] tx.exec( - internal::concat("COPY "sv, table, "("sv, columns, ") TO STDOUT"sv)) - .no_rows(); + internal::concat("COPY "sv, table, "("sv, columns, ") TO STDOUT"sv), + loc) + .no_rows(loc); register_me(); } @@ -175,7 +183,7 @@ void pqxx::stream_from::complete() } -void pqxx::stream_from::parse_line() +void pqxx::stream_from::parse_line(sl loc) { if (m_finished) [[unlikely]] return; @@ -191,7 +199,7 @@ void pqxx::stream_from::parse_line() } if (line_size >= (std::numeric_limits::max() / 2)) - throw range_error{"Stream produced a ridiculously long line."}; + throw range_error{"Stream produced a ridiculously long line.", loc}; // Make room for unescaping the line. It's a pessimistic size. // Unusually, we're storing terminating zeroes *inside* the string. @@ -222,7 +230,7 @@ void pqxx::stream_from::parse_line() std::size_t offset{0}; while (offset < line_size) { - auto const stop_char{m_char_finder(line_view, offset)}; + auto const stop_char{m_char_finder(line_view, offset, loc)}; // Copy the text we have so far. It's got no special characters in it. std::memcpy(write, &line_begin[offset], stop_char - offset); write += (stop_char - offset); @@ -256,7 +264,7 @@ void pqxx::stream_from::parse_line() // Escape sequence. assert(special == '\\'); if ((offset) >= line_size) - throw failure{"Row ends in backslash"}; + throw failure{"Row ends in backslash", loc}; // The database will only escape ASCII characters, so no need to use // the glyph scanner. @@ -265,7 +273,7 @@ void pqxx::stream_from::parse_line() { // Null value. if (write != field_begin) - throw failure{"Null sequence found in nonempty field"}; + throw failure{"Null sequence found in nonempty field", loc}; field_begin = nullptr; // (If there's any characters _after_ the null we'll just crash.) } @@ -289,8 +297,8 @@ void pqxx::stream_from::parse_line() } -std::vector const *pqxx::stream_from::read_row() & +std::vector const *pqxx::stream_from::read_row(sl loc) & { - parse_line(); + parse_line(loc); return m_finished ? nullptr : &m_fields; } diff --git a/src/stream_to.cxx b/src/stream_to.cxx index 2af257bc7..8913168e1 100644 --- a/src/stream_to.cxx +++ b/src/stream_to.cxx @@ -25,19 +25,21 @@ namespace using namespace std::literals; void begin_copy( - pqxx::transaction_base &tx, std::string_view table, std::string_view columns) + pqxx::transaction_base &tx, std::string_view table, std::string_view columns, + pqxx::sl loc) { tx.exec( std::empty(columns) ? pqxx::internal::concat("COPY "sv, table, " FROM STDIN"sv) : pqxx::internal::concat( - "COPY "sv, table, "("sv, columns, ") FROM STDIN"sv)) - .no_rows(); + "COPY "sv, table, "("sv, columns, ") FROM STDIN"sv), + loc) + .no_rows(loc); } /// Return the escape character for escaping the given special character. -char escape_char(char special) +char escape_char(char special, pqxx::sl loc) { switch (special) { @@ -50,9 +52,11 @@ char escape_char(char special) case '\\': return '\\'; default: break; } - throw pqxx::internal_error{pqxx::internal::concat( - "Stream escaping unexpectedly stopped at '", - static_cast(static_cast(special)), "'.")}; + throw pqxx::internal_error{ + pqxx::internal::concat( + "Stream escaping unexpectedly stopped at '", + static_cast(static_cast(special)), "'."), + loc}; } } // namespace @@ -70,13 +74,14 @@ pqxx::stream_to::~stream_to() noexcept } -void pqxx::stream_to::write_raw_line(std::string_view text) +void pqxx::stream_to::write_raw_line(std::string_view text, sl loc) { - internal::gate::connection_stream_to{m_trans->conn()}.write_copy_line(text); + internal::gate::connection_stream_to{m_trans->conn()}.write_copy_line( + text, loc); } -void pqxx::stream_to::write_buffer() +void pqxx::stream_to::write_buffer(sl loc) { if (not std::empty(m_buffer)) { @@ -85,7 +90,7 @@ void pqxx::stream_to::write_buffer() assert(m_buffer[std::size(m_buffer) - 1] == '\t'); m_buffer.resize(std::size(m_buffer) - 1); } - write_raw_line(m_buffer); + write_raw_line(m_buffer, loc); m_buffer.clear(); } @@ -97,20 +102,21 @@ pqxx::stream_to &pqxx::stream_to::operator<<(stream_from &tr) const auto [line, size] = tr.get_raw_line(); if (line.get() == nullptr) break; - write_raw_line(std::string_view{line.get(), size}); + write_raw_line(std::string_view{line.get(), size}, sl::current()); } return *this; } pqxx::stream_to::stream_to( - transaction_base &tx, std::string_view path, std::string_view columns) : + transaction_base &tx, std::string_view path, std::string_view columns, + sl loc) : transaction_focus{tx, s_classname, path}, m_finder{pqxx::internal::get_char_finder< '\b', '\f', '\n', '\r', '\t', '\v', '\\'>( - pqxx::internal::enc_group(tx.conn().encoding_id()))} + pqxx::internal::enc_group(tx.conn().encoding_id(loc), loc), loc)} { - begin_copy(tx, path, columns); + begin_copy(tx, path, columns, loc); register_me(); } @@ -126,19 +132,19 @@ void pqxx::stream_to::complete() } -void pqxx::stream_to::escape_field_to_buffer(std::string_view data) +void pqxx::stream_to::escape_field_to_buffer(std::string_view data, sl loc) { std::size_t const end{std::size(data)}; std::size_t here{0}; while (here < end) { - auto const stop_char{m_finder(data, here)}; + auto const stop_char{m_finder(data, here, loc)}; // Append any unremarkable we just skipped over. m_buffer.append(std::data(data) + here, stop_char - here); if (stop_char < end) { m_buffer.push_back('\\'); - m_buffer.push_back(escape_char(data[stop_char])); + m_buffer.push_back(escape_char(data[stop_char], loc)); } here = stop_char + 1; } diff --git a/src/subtransaction.cxx b/src/subtransaction.cxx index 727167774..e335cc0e0 100644 --- a/src/subtransaction.cxx +++ b/src/subtransaction.cxx @@ -30,7 +30,7 @@ constexpr std::string_view class_name{"subtransaction"sv}; pqxx::subtransaction::subtransaction( - dbtransaction &t, std::string_view tname) : + dbtransaction &t, std::string_view tname, sl loc) : transaction_focus{t, class_name, t.conn().adorn_name(tname)}, // We can't initialise the rollback command here, because we don't yet // have a full object to implement quoted_name(). @@ -38,8 +38,10 @@ pqxx::subtransaction::subtransaction( { set_rollback_cmd(std::make_shared( internal::concat("ROLLBACK TO SAVEPOINT ", quoted_name()))); - direct_exec(std::make_shared( - internal::concat("SAVEPOINT ", quoted_name()))); + direct_exec( + std::make_shared( + internal::concat("SAVEPOINT ", quoted_name())), + loc); } @@ -50,19 +52,22 @@ using dbtransaction_ref = pqxx::dbtransaction &; pqxx::subtransaction::subtransaction( - subtransaction &t, std::string_view tname) : - subtransaction(dbtransaction_ref(t), tname) + subtransaction &t, std::string_view tname, sl loc) : + subtransaction(dbtransaction_ref(t), tname, loc) {} pqxx::subtransaction::~subtransaction() noexcept { - close(); + sl const loc{sl::current()}; + close(loc); } -void pqxx::subtransaction::do_commit() +void pqxx::subtransaction::do_commit(sl loc) { - direct_exec(std::make_shared( - internal::concat("RELEASE SAVEPOINT ", quoted_name()))); + direct_exec( + std::make_shared( + internal::concat("RELEASE SAVEPOINT ", quoted_name())), + loc); } diff --git a/src/time.cxx b/src/time.cxx index e216c1ade..f68ebd006 100644 --- a/src/time.cxx +++ b/src/time.cxx @@ -75,17 +75,17 @@ year_into_buf(char *begin, char *end, std::chrono::year const &value) /// Parse the numeric part of a year value. -inline int year_from_buf(std::string_view text) +inline int year_from_buf(std::string_view text, pqxx::sl loc) { if (std::size(text) < 4) throw pqxx::conversion_error{ - pqxx::internal::concat("Year field is too small: '", text, "'.")}; + pqxx::internal::concat("Year field is too small: '", text, "'."), loc}; // Parse as int, so we can accommodate 32768 BC which won't fit in a short // as-is, but equates to 32767 BCE which will. int const year{pqxx::string_traits::from_string(text)}; if (year <= 0) throw pqxx::conversion_error{ - pqxx::internal::concat("Bad year: '", text, "'.")}; + pqxx::internal::concat("Bad year: '", text, "'."), loc}; return year; } @@ -110,13 +110,14 @@ inline char *month_into_buf(char *begin, std::chrono::month const &value) /// Parse a 1-based month value. -inline std::chrono::month month_from_string(std::string_view text) +inline std::chrono::month +month_from_string(std::string_view text, pqxx::sl loc) { if ( not pqxx::internal::is_digit(text[0]) or not pqxx::internal::is_digit(text[1])) throw pqxx::conversion_error{ - pqxx::internal::concat("Invalid month: '", text, "'.")}; + pqxx::internal::concat("Invalid month: '", text, "'."), loc}; return std::chrono::month{unsigned( (ten * pqxx::internal::digit_to_number(text[0])) + pqxx::internal::digit_to_number(text[1]))}; @@ -134,19 +135,19 @@ inline char *day_into_buf(char *begin, std::chrono::day const &value) /// Parse a 1-based day-of-month value. -inline std::chrono::day day_from_string(std::string_view text) +inline std::chrono::day day_from_string(std::string_view text, pqxx::sl loc) { if ( not pqxx::internal::is_digit(text[0]) or not pqxx::internal::is_digit(text[1])) throw pqxx::conversion_error{ - pqxx::internal::concat("Bad day in date: '", text, "'.")}; + pqxx::internal::concat("Bad day in date: '", text, "'."), loc}; std::chrono::day const d{unsigned( (ten * pqxx::internal::digit_to_number(text[0])) + pqxx::internal::digit_to_number(text[1]))}; if (not d.ok()) throw pqxx::conversion_error{ - pqxx::internal::concat("Bad day in date: '", text, "'.")}; + pqxx::internal::concat("Bad day in date: '", text, "'."), loc}; return d; } @@ -177,10 +178,10 @@ std::string make_parse_error(std::string_view text) namespace pqxx { char *string_traits::into_buf( - char *begin, char *end, std::chrono::year_month_day const &value) + char *begin, char *end, std::chrono::year_month_day const &value, sl loc) { if (std::size_t(end - begin) < size_buffer(value)) - throw conversion_overrun{"Not enough room in buffer for date."}; + throw conversion_overrun{"Not enough room in buffer for date.", loc}; begin = year_into_buf(begin, end, value.year()); *begin++ = '-'; begin = month_into_buf(begin, value.month()); @@ -194,30 +195,31 @@ char *string_traits::into_buf( std::chrono::year_month_day -string_traits::from_string(std::string_view text) +string_traits::from_string( + std::string_view text, sl loc) { // We can't just re-use the std::chrono::year conversions, because the "BC" // suffix comes at the very end. if (std::size(text) < 9) - throw conversion_error{make_parse_error(text)}; + throw conversion_error{make_parse_error(text), loc}; bool const is_bc{text.ends_with(s_bc)}; if (is_bc) [[unlikely]] text = text.substr(0, std::size(text) - std::size(s_bc)); auto const ymsep{find_year_month_separator(text)}; if ((std::size(text) - ymsep) != 6) - throw conversion_error{make_parse_error(text)}; + throw conversion_error{make_parse_error(text), loc}; auto const base_year{ - year_from_buf(std::string_view{std::data(text), ymsep})}; + year_from_buf(std::string_view{std::data(text), ymsep}, loc)}; if (base_year == 0) - throw conversion_error{"Year zero conversion."}; + throw conversion_error{"Year zero conversion.", loc}; std::chrono::year const y{is_bc ? (-base_year + 1) : base_year}; - auto const m{month_from_string(text.substr(ymsep + 1, 2))}; + auto const m{month_from_string(text.substr(ymsep + 1, 2), loc)}; if (text[ymsep + 3] != '-') - throw conversion_error{make_parse_error(text)}; - auto const d{day_from_string(text.substr(ymsep + 4, 2))}; + throw conversion_error{make_parse_error(text), loc}; + auto const d{day_from_string(text.substr(ymsep + 4, 2), loc)}; std::chrono::year_month_day const date{y, m, d}; if (not date.ok()) - throw conversion_error{make_parse_error(text)}; + throw conversion_error{make_parse_error(text), loc}; return date; } } // namespace pqxx diff --git a/src/transaction.cxx b/src/transaction.cxx index a20852afd..b5701e2b5 100644 --- a/src/transaction.cxx +++ b/src/transaction.cxx @@ -22,29 +22,29 @@ pqxx::internal::basic_transaction::basic_transaction( - connection &cx, zview begin_command, std::string_view tname) : + connection &cx, zview begin_command, std::string_view tname, sl loc) : dbtransaction(cx, tname) { register_transaction(); - direct_exec(begin_command); + direct_exec(begin_command, loc); } pqxx::internal::basic_transaction::basic_transaction( - connection &cx, zview begin_command, std::string &&tname) : + connection &cx, zview begin_command, std::string &&tname, sl loc) : dbtransaction(cx, std::move(tname)) { register_transaction(); - direct_exec(begin_command); + direct_exec(begin_command, loc); } pqxx::internal::basic_transaction::basic_transaction( - connection &cx, zview begin_command) : + connection &cx, zview begin_command, sl loc) : dbtransaction(cx) { register_transaction(); - direct_exec(begin_command); + direct_exec(begin_command, loc); } @@ -57,12 +57,12 @@ pqxx::internal::basic_transaction::basic_transaction( pqxx::internal::basic_transaction::~basic_transaction() noexcept = default; -void pqxx::internal::basic_transaction::do_commit() +void pqxx::internal::basic_transaction::do_commit(sl loc) { static auto const commit_q{std::make_shared("COMMIT"sv)}; try { - direct_exec(commit_q); + direct_exec(commit_q, loc); } catch (statement_completion_unknown const &e) { @@ -70,6 +70,7 @@ void pqxx::internal::basic_transaction::do_commit() // resulting state of the database. process_notice(internal::concat(e.what(), "\n")); + // XXX: Log source location? std::string msg{internal::concat( "WARNING: Commit status of transaction '", name(), "' is unknown. " @@ -95,7 +96,7 @@ 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{msg}; + throw in_doubt_error{msg, loc}; } else { diff --git a/src/transaction_base.cxx b/src/transaction_base.cxx index c82c8162a..e54563910 100644 --- a/src/transaction_base.cxx +++ b/src/transaction_base.cxx @@ -95,7 +95,7 @@ void pqxx::transaction_base::register_transaction() } -void pqxx::transaction_base::commit() +void pqxx::transaction_base::commit(sl loc) { check_pending_error(); @@ -107,8 +107,9 @@ void pqxx::transaction_base::commit() break; case status::aborted: - throw usage_error{internal::concat( - "Attempt to commit previously aborted ", description())}; + throw usage_error{ + internal::concat("Attempt to commit previously aborted ", description()), + loc}; case status::committed: // Transaction has been committed already. This is not exactly proper @@ -134,9 +135,11 @@ void pqxx::transaction_base::commit() // commit is premature. Punish this swiftly and without fail to discourage // the habit from forming. if (m_focus != nullptr) - throw failure{internal::concat( - "Attempt to commit ", description(), " with ", m_focus->description(), - " still open.")}; + throw failure{ + internal::concat( + "Attempt to commit ", description(), " with ", m_focus->description(), + " still open."), + loc}; // Check that we're still connected (as far as we know--this is not an // absolute thing!) before trying to commit. If the connection was broken @@ -144,11 +147,11 @@ void pqxx::transaction_base::commit() // remain in-doubt as to whether the backend got the commit order at all. if (not m_conn.is_open()) throw broken_connection{ - "Broken connection to backend; cannot complete transaction."}; + "Broken connection to backend; cannot complete transaction.", loc}; try { - do_commit(); + do_commit(loc); m_status = status::committed; } catch (in_doubt_error const &) @@ -162,18 +165,18 @@ void pqxx::transaction_base::commit() throw; } - close(); + close(loc); } -void pqxx::transaction_base::do_abort() +void pqxx::transaction_base::do_abort(sl loc) { if (m_rollback_cmd) - direct_exec(m_rollback_cmd); + direct_exec(m_rollback_cmd, loc); } -void pqxx::transaction_base::abort() +void pqxx::transaction_base::abort(sl loc) { // Check previous status code. Quietly accept multiple aborts to // simplify emergency bailout code. @@ -182,7 +185,7 @@ void pqxx::transaction_base::abort() case status::active: try { - do_abort(); + do_abort(loc); } catch (std::exception const &e) { @@ -193,8 +196,10 @@ void pqxx::transaction_base::abort() case status::aborted: return; case status::committed: - throw usage_error{internal::concat( - "Attempt to abort previously committed ", description())}; + throw usage_error{ + internal::concat( + "Attempt to abort previously committed ", description()), + loc}; case status::in_doubt: // Aborting an in-doubt transaction is probably a reasonably sane response @@ -209,7 +214,7 @@ void pqxx::transaction_base::abort() } m_status = status::aborted; - close(); + close(loc); } @@ -238,7 +243,7 @@ class PQXX_PRIVATE command : pqxx::transaction_focus }; } // namespace -pqxx::result pqxx::transaction_base::exec(std::string_view query) +pqxx::result pqxx::transaction_base::exec(std::string_view query, sl loc) { check_pending_error(); @@ -253,17 +258,17 @@ pqxx::result pqxx::transaction_base::exec(std::string_view query) case status::in_doubt: // TODO: Pass query. throw usage_error{ - "Could not execute command: transaction is already closed."}; + "Could not execute command: transaction is already closed.", loc}; default: PQXX_UNREACHABLE; } - return direct_exec(query); + return direct_exec(query, loc); } -pqxx::result -pqxx::transaction_base::exec(std::string_view query, std::string_view desc) +pqxx::result pqxx::transaction_base::exec( + std::string_view query, std::string_view desc, sl loc) { check_pending_error(); @@ -279,14 +284,16 @@ pqxx::transaction_base::exec(std::string_view query, std::string_view desc) std::string const n{ std::empty(desc) ? "" : internal::concat("'", desc, "' ")}; - throw usage_error{internal::concat( - "Could not execute command ", n, ": transaction is already closed.")}; + throw usage_error{ + internal::concat( + "Could not execute command ", n, ": transaction is already closed."), + loc}; } default: PQXX_UNREACHABLE; } - return direct_exec(query, desc); + return direct_exec(query, desc, loc); } @@ -302,30 +309,30 @@ pqxx::result pqxx::transaction_base::exec_n( pqxx::result pqxx::transaction_base::internal_exec_prepared( - std::string_view statement, internal::c_params const &args) + std::string_view statement, internal::c_params const &args, sl loc) { command const cmd{*this, statement}; return pqxx::internal::gate::connection_transaction{conn()}.exec_prepared( - statement, args); + statement, args, loc); } pqxx::result pqxx::transaction_base::internal_exec_params( - std::string_view query, internal::c_params const &args) + std::string_view query, internal::c_params const &args, sl loc) { command const cmd{*this, query}; return pqxx::internal::gate::connection_transaction{conn()}.exec_params( - query, args); + query, args, loc); } void pqxx::transaction_base::notify( - std::string_view channel, std::string_view payload) + std::string_view channel, std::string_view payload, sl loc) { // For some reason, NOTIFY does not work as a parameterised statement, // even just for the payload (which is supposed to be a normal string). // Luckily, pg_notify() does. - exec("SELECT pg_notify($1, $2)", params{channel, payload}).one_row(); + exec("SELECT pg_notify($1, $2)", params{channel, payload}, loc).one_row(loc); } @@ -346,7 +353,7 @@ std::string pqxx::transaction_base::get_variable(std::string_view var) } -void pqxx::transaction_base::close() noexcept +void pqxx::transaction_base::close(sl loc) noexcept { try { @@ -377,7 +384,7 @@ void pqxx::transaction_base::close() noexcept try { - abort(); + abort(loc); } catch (std::exception const &e) { @@ -443,19 +450,20 @@ void pqxx::transaction_base::unregister_focus( pqxx::result pqxx::transaction_base::direct_exec( - std::string_view cmd, std::string_view desc) + std::string_view cmd, std::string_view desc, sl loc) { check_pending_error(); - return pqxx::internal::gate::connection_transaction{conn()}.exec(cmd, desc); + return pqxx::internal::gate::connection_transaction{conn()}.exec( + cmd, desc, loc); } pqxx::result pqxx::transaction_base::direct_exec( - std::shared_ptr cmd, std::string_view desc) + std::shared_ptr cmd, std::string_view desc, sl loc) { check_pending_error(); return pqxx::internal::gate::connection_transaction{conn()}.exec( - std::move(cmd), desc); + std::move(cmd), desc, loc); } @@ -471,6 +479,7 @@ void pqxx::transaction_base::register_pending_error(zview err) noexcept { try { + // XXX: Log source location? [[unlikely]] process_notice("UNABLE TO PROCESS ERROR\n"); // TODO: Make at least an attempt to append a newline. process_notice(e.what()); @@ -513,6 +522,7 @@ void pqxx::transaction_base::check_pending_error() { if (not std::empty(m_pending_error)) { + // TODO: Store exceptions, or message + source_location. std::string err; err.swap(m_pending_error); throw failure{err}; diff --git a/src/util.cxx b/src/util.cxx index 57283c52f..75db2593f 100644 --- a/src/util.cxx +++ b/src/util.cxx @@ -110,7 +110,6 @@ constexpr std::array hex_digits{ constexpr char hex_digit(int c) noexcept { PQXX_ASSUME(c >= 0); - PQXX_ASSUME(c < std::ssize(hex_digits)); return hex_digits.at(static_cast(c)); } @@ -166,39 +165,40 @@ std::string pqxx::internal::esc_bin(bytes_view binary_data) void pqxx::internal::unesc_bin( - std::string_view escaped_data, std::byte buffer[]) + std::string_view escaped_data, std::byte buffer[], sl loc) { auto const in_size{std::size(escaped_data)}; if (in_size < 2) - throw pqxx::failure{"Binary data appears truncated."}; + throw pqxx::failure{"Binary data appears truncated.", loc}; if ((in_size % 2) != 0) - throw pqxx::failure{"Invalid escaped binary length."}; + throw pqxx::failure{"Invalid escaped binary length.", loc}; char const *in{escaped_data.data()}; char const *const end{in + in_size}; if (*in++ != '\\' or *in++ != 'x') throw pqxx::failure( "Escaped binary data did not start with '\\x'`. Is the server or libpq " - "too old?"); + "too old?", + loc); auto out{buffer}; while (in != end) { int const hi{nibble(*in++)}; if (hi < 0) - throw pqxx::failure{"Invalid hex-escaped data."}; + throw pqxx::failure{"Invalid hex-escaped data.", loc}; int const lo{nibble(*in++)}; if (lo < 0) - throw pqxx::failure{"Invalid hex-escaped data."}; + throw pqxx::failure{"Invalid hex-escaped data.", loc}; *out++ = static_cast((hi << 4) | lo); } } -pqxx::bytes pqxx::internal::unesc_bin(std::string_view escaped_data) +pqxx::bytes pqxx::internal::unesc_bin(std::string_view escaped_data, sl loc) { auto const bytes{size_unesc_bin(std::size(escaped_data))}; pqxx::bytes buf; buf.resize(bytes); - unesc_bin(escaped_data, buf.data()); + unesc_bin(escaped_data, buf.data(), loc); return buf; } diff --git a/src/wait.cxx b/src/wait.cxx index 6c087a9c1..d8acdff29 100644 --- a/src/wait.cxx +++ b/src/wait.cxx @@ -60,11 +60,12 @@ namespace { -template T to_milli(unsigned seconds, unsigned microseconds) +template +T to_milli(unsigned seconds, unsigned microseconds, pqxx::sl loc) { return pqxx::check_cast( (seconds * 1000) + (microseconds / 1000), - "Wait timeout value out of bounds."); + "Wait timeout value out of bounds.", loc); } @@ -90,7 +91,7 @@ template T to_milli(unsigned seconds, unsigned microseconds) void pqxx::internal::wait_fd( int fd, bool for_read, bool for_write, unsigned seconds, - unsigned microseconds) + unsigned microseconds, sl loc) { // WSAPoll is available in winsock2.h only for versions of Windows >= 0x0600 #if defined(_WIN32) && (_WIN32_WINNT >= 0x0600) @@ -99,13 +100,13 @@ void pqxx::internal::wait_fd( (for_read ? POLLRDNORM : 0) | (for_write ? POLLWRNORM : 0))}; WSAPOLLFD fdarray{SOCKET(fd), events, 0}; int const code{ - WSAPoll(&fdarray, 1u, to_milli(seconds, microseconds))}; + WSAPoll(&fdarray, 1u, to_milli(seconds, microseconds, loc))}; #elif defined(PQXX_HAVE_POLL) auto const events{static_cast( POLLERR | POLLHUP | POLLNVAL | (for_read ? POLLIN : 0) | (for_write ? POLLOUT : 0))}; pollfd pfd{fd, events, 0}; - int const code{poll(&pfd, 1, to_milli(seconds, microseconds))}; + int const code{poll(&pfd, 1, to_milli(seconds, microseconds, loc))}; #else // No poll()? Our last option is select(). fd_set read_fds; diff --git a/test/runner.cxx b/test/runner.cxx index e66609755..5f8aeef3c 100644 --- a/test/runner.cxx +++ b/test/runner.cxx @@ -14,7 +14,7 @@ namespace pqxx::test { -test_failure::test_failure(std::string const &desc, std::source_location loc) : +test_failure::test_failure(std::string const &desc, sl loc) : std::logic_error{desc}, m_loc{loc} {} @@ -22,21 +22,19 @@ test_failure::~test_failure() noexcept = default; /// Drop table, if it exists. -inline void drop_table(transaction_base &t, std::string const &table) +inline void drop_table(transaction_base &t, std::string const &table, sl loc) { - t.exec("DROP TABLE IF EXISTS " + table); + t.exec("DROP TABLE IF EXISTS " + table, loc); } -[[noreturn]] void check_notreached(std::string desc, std::source_location loc) +[[noreturn]] void check_notreached(std::string desc, sl loc) { throw test_failure{desc, loc}; } -void check( - bool condition, char const text[], std::string const &desc, - std::source_location loc) +void check(bool condition, char const text[], std::string const &desc, sl loc) { if (not condition) throw test_failure{desc + " (failed expression: " + text + ")", loc}; diff --git a/test/test07.cxx b/test/test07.cxx index cfb1b58cf..eead0b966 100644 --- a/test/test07.cxx +++ b/test/test07.cxx @@ -30,7 +30,7 @@ int To4Digits(int Y) else if (Y < 100) Result += 1900; else if (Y < 1970) - PQXX_CHECK_NOTREACHED("Unexpected year: " + to_string(Y)); + pqxx::test::check_notreached("Unexpected year: " + to_string(Y)); return Result; } @@ -81,9 +81,11 @@ void test_007() { int Y{0}; +#include "pqxx/internal/ignore-deprecated-pre.hxx" // Read year, and if it is non-null, note its converted value if (r[0] >> Y) conversions[Y] = To4Digits(Y); +#include "pqxx/internal/ignore-deprecated-post.hxx" // See if type identifiers are consistent oid const tctype{r.column_type(0)}; diff --git a/test/test_array.cxx b/test/test_array.cxx index e7da2ef6c..1ab2124ca 100644 --- a/test/test_array.cxx +++ b/test/test_array.cxx @@ -15,6 +15,7 @@ struct nullness : no_null inline std::string to_string(pqxx::array_parser::juncture const &j) { +#include "pqxx/internal/ignore-deprecated-pre.hxx" using junc = pqxx::array_parser::juncture; switch (j) { @@ -25,6 +26,7 @@ inline std::string to_string(pqxx::array_parser::juncture const &j) case junc::done: return "done"; default: return "UNKNOWN JUNCTURE: " + to_string(static_cast(j)); } +#include "pqxx/internal/ignore-deprecated-post.hxx" } } // namespace pqxx @@ -33,6 +35,7 @@ namespace { void test_empty_arrays() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; // Parsing a null pointer just immediately returns "done". @@ -68,11 +71,13 @@ void test_empty_arrays() output.first, pqxx::array_parser::juncture::done, "Empty array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_array_null_value() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser containing_null("{NULL}"); @@ -99,11 +104,13 @@ void test_array_null_value() output.first, pqxx::array_parser::juncture::done, "Array containing null did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_array_double_quoted_string() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser("{\"item\"}"); @@ -129,11 +136,13 @@ void test_array_double_quoted_string() output.first, pqxx::array_parser::juncture::done, "Array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_array_double_quoted_escaping() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser(R"--({"don''t\\ care"})--"); @@ -159,12 +168,14 @@ void test_array_double_quoted_escaping() output.first, pqxx::array_parser::juncture::done, "Array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } // A pair of double quotes in a double-quoted string is an escaped quote. void test_array_double_double_quoted_string() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser{R"--({"3"" steel"})--"}; @@ -179,11 +190,13 @@ void test_array_double_double_quoted_string() "Array did not return string_value."); PQXX_CHECK_EQUAL(output.second, "3\" steel", "Unexpected string value."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_array_unquoted_string() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser("{item}"); @@ -209,11 +222,13 @@ void test_array_unquoted_string() output.first, pqxx::array_parser::juncture::done, "Array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_array_multiple_values() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser("{1,2}"); @@ -245,11 +260,13 @@ void test_array_multiple_values() output.first, pqxx::array_parser::juncture::done, "Array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_nested_array() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser("{{item}}"); @@ -286,11 +303,13 @@ void test_nested_array() output.first, pqxx::array_parser::juncture::done, "Array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_nested_array_with_multiple_entries() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser("{{1,2},{3,4}}"); @@ -356,6 +375,7 @@ void test_nested_array_with_multiple_entries() output.first, pqxx::array_parser::juncture::done, "Array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } @@ -493,6 +513,7 @@ void test_array_generate() void test_array_roundtrip() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" pqxx::connection cx; pqxx::work tx{cx}; @@ -526,11 +547,13 @@ void test_array_roundtrip() PQXX_CHECK_EQUAL( item.first, pqxx::array_parser::juncture::done, "Array did not end in done."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_array_strings() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::vector inputs{ "", "null", "NULL", "\\N", "'", "''", "\\", "\n\t", "\\n", "\"", "\"\"", "a b", "a<>b", "{", "}", "{}", @@ -552,6 +575,7 @@ void test_array_strings() "Bad value juncture."); PQXX_CHECK_EQUAL(value, input, "Bad array value roundtrip."); } +#include "pqxx/internal/ignore-deprecated-post.hxx" } diff --git a/test/test_encodings.cxx b/test/test_encodings.cxx index 7965357a5..bb5a32485 100644 --- a/test/test_encodings.cxx +++ b/test/test_encodings.cxx @@ -8,30 +8,30 @@ namespace void test_scan_ascii() { auto const scan{pqxx::internal::get_glyph_scanner( - pqxx::internal::encoding_group::MONOBYTE)}; + pqxx::internal::encoding_group::MONOBYTE, pqxx::sl::current())}; std::string const text{"hello"}; PQXX_CHECK_EQUAL( - scan(text.c_str(), std::size(text), 0), 1ul, + scan(text.c_str(), std::size(text), 0, pqxx::sl::current()), 1ul, "Monobyte scanner acting up."); PQXX_CHECK_EQUAL( - scan(text.c_str(), std::size(text), 1), 2ul, + scan(text.c_str(), std::size(text), 1, pqxx::sl::current()), 2ul, "Monobyte scanner is inconsistent."); } void test_scan_utf8() { - auto const scan{ - pqxx::internal::get_glyph_scanner(pqxx::internal::encoding_group::UTF8)}; + auto const scan{pqxx::internal::get_glyph_scanner( + pqxx::internal::encoding_group::UTF8, pqxx::sl::current())}; // Thai: "Khrab". std::string const text{"\xe0\xb8\x95\xe0\xb8\xa3\xe0\xb8\xb1\xe0\xb8\x9a"}; PQXX_CHECK_EQUAL( - scan(text.c_str(), std::size(text), 0), 3ul, + scan(text.c_str(), std::size(text), 0, pqxx::sl::current()), 3ul, "UTF-8 scanner mis-scanned Thai khor khwai."); PQXX_CHECK_EQUAL( - scan(text.c_str(), std::size(text), 3), 6ul, + scan(text.c_str(), std::size(text), 3, pqxx::sl::current()), 6ul, "UTF-8 scanner mis-scanned Thai ror reua."); } @@ -41,7 +41,8 @@ void test_for_glyphs_empty() bool iterated{false}; pqxx::internal::for_glyphs( pqxx::internal::encoding_group::MONOBYTE, - [&iterated](char const *, char const *) { iterated = true; }, "", 0); + [&iterated](char const *, char const *) { iterated = true; }, "", 0, 0, + pqxx::sl::current()); PQXX_CHECK(!iterated, "Empty string went through an iteration."); } @@ -56,7 +57,7 @@ void test_for_glyphs_ascii() [&points](char const *gbegin, char const *gend) { points.push_back(gend - gbegin); }, - text.c_str(), std::size(text)); + text.c_str(), std::size(text), 0, pqxx::sl::current()); PQXX_CHECK_EQUAL(std::size(points), 2u, "Wrong number of ASCII iterations."); PQXX_CHECK_EQUAL(points[0], 1u, "ASCII iteration started off wrong."); @@ -75,7 +76,7 @@ void test_for_glyphs_utf8() [&points](char const *gbegin, char const *gend) { points.push_back(gend - gbegin); }, - text.c_str(), std::size(text)); + text.c_str(), std::size(text), 0, pqxx::sl::current()); PQXX_CHECK_EQUAL(std::size(points), 2u, "Wrong number of UTF-8 iterations."); PQXX_CHECK_EQUAL(points[0], 2u, "UTF-8 iteration started off wrong."); @@ -90,7 +91,7 @@ void test_for_glyphs_utf8() [&points](char const *gbegin, char const *gend) { points.push_back(gend - gbegin); }, - mix.c_str(), std::size(mix)); + mix.c_str(), std::size(mix), 0, pqxx::sl::current()); PQXX_CHECK_EQUAL(std::size(points), 3u, "Mixed UTF-8 iteration is broken."); PQXX_CHECK_EQUAL(points[0], 2u, "Mixed UTF-8 iteration started off wrong."); diff --git a/test/test_helpers.hxx b/test/test_helpers.hxx index 6c1e59de3..268aff561 100644 --- a/test/test_helpers.hxx +++ b/test/test_helpers.hxx @@ -11,9 +11,7 @@ namespace test class test_failure : public std::logic_error { public: - test_failure( - std::string const &desc, - std::source_location loc = std::source_location::current()); + test_failure(std::string const &desc, sl loc = sl::current()); ~test_failure() noexcept override; @@ -21,12 +19,13 @@ public: constexpr auto line() const noexcept { return m_loc.line(); } private: - std::source_location m_loc; + sl m_loc; }; /// Drop a table, if it exists. -void drop_table(transaction_base &, std::string const &table); +void drop_table( + transaction_base &, std::string const &table, sl loc = sl::current()); using testfunc = void (*)(); @@ -54,17 +53,16 @@ struct registrar // Unconditional test failure. -#define PQXX_CHECK_NOTREACHED(desc) pqxx::test::check_notreached((desc)) [[noreturn]] void check_notreached( - std::string desc, - std::source_location loc = std::source_location::current()); + std::string desc = "Execution was never supposed to reach this point.", + sl loc = sl::current()); // Verify that a condition is met, similar to assert() #define PQXX_CHECK(condition, desc) \ pqxx::test::check((condition), #condition, (desc)) void check( bool condition, char const text[], std::string const &desc, - std::source_location loc = std::source_location::current()); + sl loc = sl::current()); // Verify that variable has the expected value. #define PQXX_CHECK_EQUAL(actual, expected, desc) \ @@ -72,8 +70,7 @@ void check( template inline void check_equal( ACTUAL actual, char const actual_text[], EXPECTED expected, - char const expected_text[], std::string const &desc, - std::source_location loc = std::source_location::current()) + char const expected_text[], std::string const &desc, sl loc = sl::current()) { if (expected == actual) return; @@ -94,8 +91,7 @@ inline void check_equal( template inline void check_not_equal( VALUE1 value1, char const text1[], VALUE2 value2, char const text2[], - std::string const &desc, - std::source_location loc = std::source_location::current()) + std::string const &desc, sl loc = sl::current()) { if (value1 != value2) return; @@ -116,8 +112,7 @@ inline void check_not_equal( template inline void check_less( VALUE1 value1, char const text1[], VALUE2 value2, char const text2[], - std::string const &desc, - std::source_location loc = std::source_location::current()) + std::string const &desc, sl loc = sl::current()) { if (value1 < value2) return; @@ -141,22 +136,18 @@ inline void check_less( template inline void check_less_equal( VALUE1 value1, char const text1[], VALUE2 value2, char const text2[], - std::string const &desc, - std::source_location loc = std::source_location::current()) + std::string const &desc, sl loc = sl::current()) { if (value1 <= value2) return; - std::string const fulldesc = desc + " (" + text1 + " > " + text2 + - ": " - "\"lower\"=" + - to_string(value1) + - ", " - "\"upper\"=" + - to_string(value2) + ")"; + std::string const fulldesc = pqxx::internal::concat( + desc, " (", text1, " > ", text2, ": \"lower\"=", value1, + ", \"upper\"=", value2, ")"); throw test_failure{fulldesc, loc}; } +/// A special exception type not derived from `std::exception`. struct failure_to_fail {}; @@ -170,80 +161,103 @@ inline void end_of_statement() {} // Verify that "action" does not throw an exception. #define PQXX_CHECK_SUCCEEDS(action, desc) \ - { \ - try \ - { \ - action; \ - } \ - catch (std::exception const &e) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + " - \"" + \ - #action "\" threw exception: " + e.what()); \ - } \ - catch (...) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + " - \"" + #action "\" threw a non-exception!"); \ - } \ - } \ - pqxx::test::internal::end_of_statement() + pqxx::test::check_succeeds(([&]() { action; }), #action, (desc)) + +template +inline void check_succeeds( + F &&f, char const text[], std::string desc = "Expected this to succeed.", + sl loc = sl::current()) +{ + try + { + f(); + } + catch (std::exception const &e) + { + pqxx::test::check_notreached( + pqxx::internal::concat( + desc, " - \"", text, "\" threw exception: ", e.what()), + loc); + } + catch (...) + { + pqxx::test::check_notreached( + pqxx::internal::concat(desc, " - \"", text, "\" threw a non-exception!"), + loc); + } +} + + +template +inline void check_throws( + F &&f, char const text[], + std::string desc = "This code did not thow the expected exception.", + pqxx::sl loc = sl::current()) +{ + try + { + f(); + throw failure_to_fail{}; + } + catch (failure_to_fail const &) + { + check_notreached( + pqxx::internal::concat(desc, " (\"", text, "\" did not throw)"), loc); + } + catch (EXC const &) + {} + catch (std::exception const &e) + { + check_notreached(pqxx::internal::concat( + desc, " (\"", text, "\" threw the wrong exception type: ", e.what(), + ")")); + } + catch (...) + { + check_notreached( + pqxx::internal::concat( + desc, " (\"", text, "\" threw a non-exception type!)"), + loc); + } +} + + +template +inline void check_throws_exception( + F &&f, char const text[], + std::string desc = "This code did not thow a std::exception.", + pqxx::sl loc = sl::current()) +{ + try + { + f(); + throw failure_to_fail{}; + } + catch (failure_to_fail const &) + { + check_notreached( + pqxx::internal::concat(desc, " (\"", text, "\" did not throw)"), loc); + } + catch (std::exception const &) + {} + catch (...) + { + check_notreached( + pqxx::internal::concat( + desc, " (\"", text, "\" threw a non-exception type!)"), + loc); + } +} + // Verify that "action" throws an exception, of any std::exception-based type. #define PQXX_CHECK_THROWS_EXCEPTION(action, desc) \ - { \ - try \ - { \ - action; \ - throw pqxx::test::failure_to_fail(); \ - } \ - catch (pqxx::test::failure_to_fail const &) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + " (\"" #action "\" did not throw)"); \ - } \ - catch (std::exception const &) \ - {} \ - catch (...) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + " (\"" #action "\" threw non-exception type)"); \ - } \ - } \ - pqxx::test::internal::end_of_statement() + pqxx::test::check_throws_exception(([&]() { action; }), #action, desc) // Verify that "action" throws "exception_type" (which is not std::exception). #define PQXX_CHECK_THROWS(action, exception_type, desc) \ - { \ - try \ - { \ - action; \ - throw pqxx::test::failure_to_fail(); \ - } \ - catch (pqxx::test::failure_to_fail const &) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + " (\"" #action \ - "\" did not throw " #exception_type ")"); \ - } \ - catch (exception_type const &) \ - {} \ - catch (std::exception const &e) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + \ - " (\"" #action \ - "\" " \ - "threw exception other than " #exception_type ": " + \ - e.what() + ")"); \ - } \ - catch (...) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + " (\"" #action "\" threw non-exception type)"); \ - } \ - } \ - pqxx::test::internal::end_of_statement() + pqxx::test::check_throws(([&] { action; }), #action, desc) + #define PQXX_CHECK_BOUNDS(value, lower, upper, desc) \ pqxx::test::check_bounds( \ @@ -252,7 +266,7 @@ template inline void check_bounds( VALUE value, char const text[], LOWER lower, char const lower_text[], UPPER upper, char const upper_text[], std::string const &desc, - std::source_location loc = std::source_location::current()) + sl loc = sl::current()) { std::string const range_check = std::string{lower_text} + " < " + upper_text, lower_check = diff --git a/test/test_sql_cursor.cxx b/test/test_sql_cursor.cxx index bc37b4030..0e1d6e73f 100644 --- a/test/test_sql_cursor.cxx +++ b/test/test_sql_cursor.cxx @@ -23,45 +23,45 @@ void test_forward_sql_cursor() PQXX_CHECK_EQUAL(std::size(empty_result), 0, "Empty result not empty"); auto displacement{0}; - auto one{forward.fetch(1, displacement)}; + auto one{forward.fetch(1, displacement, pqxx::sl::current())}; PQXX_CHECK_EQUAL(std::size(one), 1, "Fetched wrong number of rows"); PQXX_CHECK_EQUAL(one[0][0].as(), "1", "Unexpected result"); PQXX_CHECK_EQUAL(displacement, 1, "Wrong displacement"); PQXX_CHECK_EQUAL(forward.pos(), 1, "In wrong position"); - auto offset{forward.move(1, displacement)}; + auto offset{forward.move(1, displacement, pqxx::sl::current())}; PQXX_CHECK_EQUAL(offset, 1, "Unexpected offset from move()"); PQXX_CHECK_EQUAL(displacement, 1, "Unexpected displacement after move()"); PQXX_CHECK_EQUAL(forward.pos(), 2, "Wrong position after move()"); PQXX_CHECK_EQUAL(forward.endpos(), -1, "endpos() unexpectedly set"); - auto row{forward.fetch(0, displacement)}; + auto row{forward.fetch(0, displacement, pqxx::sl::current())}; PQXX_CHECK_EQUAL(std::size(row), 0, "fetch(0, displacement) returns rows"); PQXX_CHECK_EQUAL(displacement, 0, "Unexpected displacement after fetch(0)"); PQXX_CHECK_EQUAL(forward.pos(), 2, "fetch(0, displacement) affected pos()"); - row = forward.fetch(0); + row = forward.fetch(0, pqxx::sl::current()); PQXX_CHECK_EQUAL(std::size(row), 0, "fetch(0) fetched wrong number of rows"); PQXX_CHECK_EQUAL(forward.pos(), 2, "fetch(0) moved cursor"); PQXX_CHECK_EQUAL(forward.pos(), 2, "fetch(0) affected pos()"); - offset = forward.move(1); + offset = forward.move(1, pqxx::sl::current()); PQXX_CHECK_EQUAL(offset, 1, "move(1) returned unexpected value"); PQXX_CHECK_EQUAL(forward.pos(), 3, "move(1) after fetch(0) broke"); - row = forward.fetch(1); + row = forward.fetch(1, pqxx::sl::current()); PQXX_CHECK_EQUAL( std::size(row), 1, "fetch(1) returned wrong number of rows"); PQXX_CHECK_EQUAL(forward.pos(), 4, "fetch(1) results in bad pos()"); PQXX_CHECK_EQUAL(row[0][0].as(), "4", "pos() is lying"); - empty_result = forward.fetch(1, displacement); + empty_result = forward.fetch(1, displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(std::size(empty_result), 0, "Got rows at end of cursor"); PQXX_CHECK_EQUAL(forward.pos(), 5, "Not at one-past-end position"); PQXX_CHECK_EQUAL(forward.endpos(), 5, "Failed to notice end position"); PQXX_CHECK_EQUAL(displacement, 1, "Wrong displacement at end position"); - offset = forward.move(5, displacement); + offset = forward.move(5, displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(offset, 0, "move() lied at end of result set"); PQXX_CHECK_EQUAL(forward.pos(), 5, "pos() is beyond end"); PQXX_CHECK_EQUAL(forward.endpos(), 5, "endpos() changed after end position"); @@ -74,7 +74,8 @@ void test_forward_sql_cursor() pqxx::cursor_base::owned, false); // Move through entire result set at once. - offset = forward2.move(pqxx::cursor_base::all(), displacement); + offset = + forward2.move(pqxx::cursor_base::all(), displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(offset, 4, "Unexpected number of rows in result set"); PQXX_CHECK_EQUAL(displacement, 5, "displacement != rows+1"); PQXX_CHECK_EQUAL(forward2.pos(), 5, "Bad pos() after skipping all rows"); @@ -86,7 +87,8 @@ void test_forward_sql_cursor() pqxx::cursor_base::owned, false); // Fetch entire result set at once. - auto rows{forward3.fetch(pqxx::cursor_base::all(), displacement)}; + auto rows{forward3.fetch( + pqxx::cursor_base::all(), displacement, pqxx::sl::current())}; PQXX_CHECK_EQUAL( std::size(rows), 4, "Unexpected number of rows in result set"); PQXX_CHECK_EQUAL(displacement, 5, "displacement != rows+1"); @@ -98,7 +100,7 @@ void test_forward_sql_cursor() pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only, pqxx::cursor_base::owned, false); - offset = forward_empty.move(3, displacement); + offset = forward_empty.move(3, displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(forward_empty.pos(), 1, "Bad pos() at end of result"); PQXX_CHECK_EQUAL(forward_empty.endpos(), 1, "Bad endpos() in empty result"); PQXX_CHECK_EQUAL(displacement, 1, "Bad displacement in empty result"); @@ -117,7 +119,7 @@ void test_scroll_sql_cursor() PQXX_CHECK_EQUAL(scroll.pos(), 0, "Scroll cursor's initial pos() is wrong"); PQXX_CHECK_EQUAL(scroll.endpos(), -1, "New scroll cursor has endpos() set"); - auto rows{scroll.fetch(pqxx::cursor_base::next())}; + auto rows{scroll.fetch(pqxx::cursor_base::next(), pqxx::sl::current())}; PQXX_CHECK_EQUAL(std::size(rows), 1, "Scroll cursor is broken"); PQXX_CHECK_EQUAL(scroll.pos(), 1, "Scroll cursor's pos() is broken"); PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set prematurely"); @@ -125,7 +127,7 @@ void test_scroll_sql_cursor() // Turn cursor around. This is where we begin to feel SQL cursors' // semantics: we pre-decrement, ending up on the position in front of the // first row and returning no rows. - rows = scroll.fetch(pqxx::cursor_base::prior()); + rows = scroll.fetch(pqxx::cursor_base::prior(), pqxx::sl::current()); PQXX_CHECK_EQUAL(std::empty(rows), true, "Turning around on fetch() broke"); PQXX_CHECK_EQUAL(scroll.pos(), 0, "pos() is not back at zero"); PQXX_CHECK_EQUAL( @@ -134,7 +136,7 @@ void test_scroll_sql_cursor() // Bounce off the left-hand side of the result set. Can't move before the // starting position. auto offset{0}, displacement{0}; - offset = scroll.move(-3, displacement); + offset = scroll.move(-3, displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(offset, 0, "Rows found before beginning"); PQXX_CHECK_EQUAL(displacement, 0, "Failed to bounce off beginning"); PQXX_CHECK_EQUAL(scroll.pos(), 0, "pos() moved back from zero"); @@ -142,27 +144,27 @@ void test_scroll_sql_cursor() // Try bouncing off the left-hand side a little harder. Take 4 paces away // from the boundary and run into it. - offset = scroll.move(4, displacement); + offset = scroll.move(4, displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(offset, 4, "Offset mismatch"); PQXX_CHECK_EQUAL(displacement, 4, "Displacement mismatch"); PQXX_CHECK_EQUAL(scroll.pos(), 4, "Position mismatch"); PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set at weird time"); - offset = scroll.move(-10, displacement); + offset = scroll.move(-10, displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(offset, 3, "Offset mismatch"); PQXX_CHECK_EQUAL(displacement, -4, "Displacement mismatch"); PQXX_CHECK_EQUAL(scroll.pos(), 0, "Hard bounce failed"); PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set during hard bounce"); - rows = scroll.fetch(3); + rows = scroll.fetch(3, pqxx::sl::current()); PQXX_CHECK_EQUAL(scroll.pos(), 3, "Bad pos()"); PQXX_CHECK_EQUAL(std::size(rows), 3, "Wrong number of rows"); PQXX_CHECK_EQUAL(rows[2][0].as(), 3, "pos() does not match data"); - rows = scroll.fetch(-1); + rows = scroll.fetch(-1, pqxx::sl::current()); PQXX_CHECK_EQUAL(scroll.pos(), 2, "Bad pos()"); PQXX_CHECK_EQUAL(rows[0][0].as(), 2, "pos() does not match data"); - rows = scroll.fetch(1); + rows = scroll.fetch(1, pqxx::sl::current()); PQXX_CHECK_EQUAL(scroll.pos(), 3, "Bad pos() after inverse turnaround"); PQXX_CHECK_EQUAL(rows[0][0].as(), 3, "Data position mismatch"); } @@ -182,7 +184,8 @@ void test_adopted_sql_cursor() PQXX_CHECK_EQUAL(adopted.endpos(), -1, "Adopted cursor has known endpos()"); auto displacement{0}; - auto rows{adopted.fetch(pqxx::cursor_base::all(), displacement)}; + auto rows{adopted.fetch( + pqxx::cursor_base::all(), displacement, pqxx::sl::current())}; PQXX_CHECK_EQUAL(std::size(rows), 3, "Wrong number of rows in result"); PQXX_CHECK_EQUAL(rows[0][0].as(), 1, "Wrong result data"); PQXX_CHECK_EQUAL(rows[2][0].as(), 3, "Wrong result data"); @@ -191,7 +194,8 @@ void test_adopted_sql_cursor() adopted.pos(), -1, "End-of-result set pos() on adopted cur"); PQXX_CHECK_EQUAL(adopted.endpos(), -1, "endpos() set too early"); - rows = adopted.fetch(pqxx::cursor_base::backward_all(), displacement); + rows = adopted.fetch( + pqxx::cursor_base::backward_all(), displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(std::size(rows), 3, "Wrong number of rows in result"); PQXX_CHECK_EQUAL(rows[0][0].as(), 3, "Wrong result data"); PQXX_CHECK_EQUAL(rows[2][0].as(), 1, "Wrong result data"); @@ -199,7 +203,7 @@ void test_adopted_sql_cursor() PQXX_CHECK_EQUAL(adopted.pos(), 0, "Failed to recognize starting position"); PQXX_CHECK_EQUAL(adopted.endpos(), -1, "endpos() set too early"); - auto offset{adopted.move(pqxx::cursor_base::all())}; + auto offset{adopted.move(pqxx::cursor_base::all(), pqxx::sl::current())}; PQXX_CHECK_EQUAL(offset, 3, "Unexpected move() offset"); PQXX_CHECK_EQUAL(adopted.pos(), 4, "Bad position on adopted cursor"); PQXX_CHECK_EQUAL(adopted.endpos(), 4, "endpos() not set properly"); @@ -244,7 +248,7 @@ void test_hold_cursor() pqxx::cursor_base::owned, true); tx.commit(); pqxx::work tx2(cx, "tx2"); - auto rows{with_hold.fetch(1)}; + auto rows{with_hold.fetch(1, pqxx::sl::current())}; PQXX_CHECK_EQUAL( std::size(rows), 1, "Did not get 1 row from with-hold cursor"); @@ -256,7 +260,8 @@ void test_hold_cursor() tx2.commit(); pqxx::work tx3(cx, "tx3"); PQXX_CHECK_THROWS( - no_hold.fetch(1), pqxx::sql_error, "Cursor not closed on commit"); + no_hold.fetch(1, pqxx::sl::current()), pqxx::sql_error, + "Cursor not closed on commit"); } diff --git a/test/test_stream_from.cxx b/test/test_stream_from.cxx index 25c173186..2e216a6f7 100644 --- a/test/test_stream_from.cxx +++ b/test/test_stream_from.cxx @@ -31,7 +31,7 @@ void test_nonoptionals(pqxx::connection &connection) // We can't read the "910" row -- it contains nulls, which our tuple does // not accept. extractor >> got_tuple; - PQXX_CHECK_NOTREACHED( + pqxx::test::check_notreached( "Failed to fail to stream null values into null-less fields."); } catch (pqxx::conversion_error const &e) @@ -86,7 +86,7 @@ void test_nonoptionals(pqxx::connection &connection) try { ex2 >> null_tup; - PQXX_CHECK_NOTREACHED( + pqxx::test::check_notreached( "stream_from should have refused to convert non-null value to " "nullptr_t."); } @@ -116,7 +116,7 @@ void test_bad_tuples(pqxx::connection &cx) try { extractor >> got_tuple_too_short; - PQXX_CHECK_NOTREACHED("stream_from improperly read first row"); + pqxx::test::check_notreached("stream_from improperly read first row"); } catch (pqxx::usage_error const &e) { @@ -133,7 +133,7 @@ void test_bad_tuples(pqxx::connection &cx) try { extractor >> got_tuple_too_long; - PQXX_CHECK_NOTREACHED("stream_from improperly read first row"); + pqxx::test::check_notreached("stream_from improperly read first row"); } catch (pqxx::usage_error const &e) { diff --git a/test/test_stream_to.cxx b/test/test_stream_to.cxx index 634179039..2aae7616f 100644 --- a/test/test_stream_to.cxx +++ b/test/test_stream_to.cxx @@ -148,7 +148,7 @@ void test_too_few_fields(pqxx::connection &connection) inserter << std::make_tuple(1234, "now", 4321, ipv4{8, 8, 8, 8}); inserter.complete(); tx.commit(); - PQXX_CHECK_NOTREACHED("stream_from improperly inserted row"); + pqxx::test::check_notreached("stream_from improperly inserted row"); } catch (pqxx::sql_error const &e) { @@ -171,7 +171,7 @@ void test_too_few_fields_fold(pqxx::connection &connection) inserter.write_values(1234, "now", 4321, ipv4{8, 8, 8, 8}); inserter.complete(); tx.commit(); - PQXX_CHECK_NOTREACHED("stream_from_fold improperly inserted row"); + pqxx::test::check_notreached("stream_from_fold improperly inserted row"); } catch (pqxx::sql_error const &e) { @@ -197,7 +197,7 @@ void test_too_many_fields(pqxx::connection &connection) bytea{'\x00', '\x01', '\x02'}, 5678); inserter.complete(); tx.commit(); - PQXX_CHECK_NOTREACHED("stream_from improperly inserted row"); + pqxx::test::check_notreached("stream_from improperly inserted row"); } catch (pqxx::sql_error const &e) { @@ -222,7 +222,7 @@ void test_too_many_fields_fold(pqxx::connection &connection) bytea{'\x00', '\x01', '\x02'}, 5678); inserter.complete(); tx.commit(); - PQXX_CHECK_NOTREACHED("stream_from_fold improperly inserted row"); + pqxx::test::check_notreached("stream_from_fold improperly inserted row"); } catch (pqxx::sql_error const &e) { diff --git a/test/test_test_helpers.cxx b/test/test_test_helpers.cxx index 7acac6138..e653d20b5 100644 --- a/test/test_test_helpers.cxx +++ b/test/test_test_helpers.cxx @@ -7,11 +7,11 @@ void empty() {} void test_check_notreached() { - // At a minimum, PQXX_CHECK_NOTREACHED must work. + // At a minimum, check_notreached() must work. bool failed{true}; try { - PQXX_CHECK_NOTREACHED("(expected)"); + pqxx::test::check_notreached("(expected)"); failed = false; } catch (pqxx::test::test_failure const &) @@ -19,7 +19,7 @@ void test_check_notreached() // This is what we expect. } if (not failed) - throw pqxx::test::test_failure{"PQXX_CHECK_NOTREACHED is broken."}; + throw pqxx::test::test_failure{"check_notreached is broken."}; } @@ -37,7 +37,7 @@ void test_check() catch (pqxx::test::test_failure const &) {} if (not failed) - PQXX_CHECK_NOTREACHED("PQXX_CHECK failed to notice failure."); + pqxx::test::check_notreached("PQXX_CHECK failed to notice failure."); } @@ -156,8 +156,8 @@ void test_test_helpers() // Test other helpers against PQXX_CHECK_THROWS. PQXX_CHECK_THROWS( - PQXX_CHECK_NOTREACHED("(expected)"), pqxx::test::test_failure, - "PQXX_CHECK_THROWS did not catch PQXX_CHECK_NOTREACHED."); + pqxx::test::check_notreached("(expected)"), pqxx::test::test_failure, + "PQXX_CHECK_THROWS did not catch check_notreached()."); PQXX_CHECK_THROWS( PQXX_CHECK(false, "(expected)"), pqxx::test::test_failure, diff --git a/test/test_types.hxx b/test/test_types.hxx index ebf53c94e..9b513fb32 100644 --- a/test/test_types.hxx +++ b/test/test_types.hxx @@ -87,11 +87,11 @@ template<> struct string_traits static constexpr bool converts_to_string{true}; static constexpr bool converts_from_string{true}; - static ipv4 from_string(std::string_view text) + static ipv4 from_string(std::string_view text, sl loc = sl::current()) { ipv4 ts; if (std::data(text) == nullptr) - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); std::vector ends; for (std::size_t i{0}; i < std::size(text); ++i) if (text[i] == '.')