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] == '.')