diff options
author | galaxycrab <UgnineSirdis@ydb.tech> | 2023-11-23 11:26:33 +0300 |
---|---|---|
committer | galaxycrab <UgnineSirdis@ydb.tech> | 2023-11-23 12:01:57 +0300 |
commit | 44354d0fc55926c1d4510d1d2c9c9f6a1a5e9300 (patch) | |
tree | cb4d75cd1c6dbc3da0ed927337fd8d1b6ed9da84 /contrib/libs/libpqxx/src | |
parent | 0e69bf615395fdd48ecee032faaec81bc468b0b8 (diff) | |
download | ydb-44354d0fc55926c1d4510d1d2c9c9f6a1a5e9300.tar.gz |
YQ Connector:test INNER JOIN
Diffstat (limited to 'contrib/libs/libpqxx/src')
32 files changed, 8337 insertions, 0 deletions
diff --git a/contrib/libs/libpqxx/src/array.cxx b/contrib/libs/libpqxx/src/array.cxx new file mode 100644 index 0000000000..c16c7ae586 --- /dev/null +++ b/contrib/libs/libpqxx/src/array.cxx @@ -0,0 +1,312 @@ +/** Handling of SQL arrays. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <cassert> +#include <cstddef> +#include <cstring> +#include <utility> + +#include "pqxx/array" +#include "pqxx/except" + + +namespace pqxx +{ +/// Scan to next glyph in the buffer. Assumes there is one. +std::string::size_type array_parser::scan_glyph( + std::string::size_type pos) const +{ + assert(pos < m_end); + return m_scan(m_input, m_end, pos); +} + + +/// Scan to next glyph in a substring. Assumes there is one. +std::string::size_type array_parser::scan_glyph( + std::string::size_type pos, + std::string::size_type end) const +{ + assert(pos < end); + assert(end <= m_end); + return m_scan(m_input, end, pos); +} + + +/// Find the end of a single-quoted SQL string in an SQL array. +/** Returns the offset of the first character after the closing quote. + */ +std::string::size_type array_parser::scan_single_quoted_string() const +{ + auto here = m_pos, next = scan_glyph(here); + assert(next < m_end); + assert(next - here == 1); + assert(m_input[here] == '\''); + for ( + here = next, next = scan_glyph(here); + here < m_end; + here = next, next = scan_glyph(here) + ) + { + if (next - here == 1) switch (m_input[here]) + { + case '\'': + // SQL escapes single quotes by doubling them. Terrible idea, but it's + // what we have. Inspect the next character to find out whether this is + // the closing quote, or an escaped one inside the string. + here = next; + // (We can read beyond this quote because the array will always end in + // a closing brace.) + next = scan_glyph(here); + + if ((here + 1 < next) or (m_input[here] != '\'')) + { + // Our lookahead character is not an escaped quote. It's the first + // character outside our string. So, return it. + return here; + } + + // We've just scanned an escaped quote. Keep going. + break; + + case '\\': + // Backslash escape. Skip ahead by one more character. + here = next; + next = scan_glyph(here); + break; + } + } + throw argument_error{"Null byte in SQL string: " + std::string{m_input}}; +} + + +/// Parse a single-quoted SQL string: un-quote it and un-escape it. +std::string array_parser::parse_single_quoted_string( + std::string::size_type end) const +{ + // There have to be at least 2 characters: the opening and closing quotes. + assert(m_pos + 1 < end); + assert(m_input[m_pos] == '\''); + assert(m_input[end - 1] == '\''); + + std::string output; + // Maximum output size is same as the input size, minus the opening and + // closing quotes. In the worst case, the real number could be half that. + // Usually it'll be a pretty close estimate. + output.reserve(end - m_pos - 2); + for ( + auto here = m_pos + 1, next = scan_glyph(here, end); + here < end - 1; + here = next, next = scan_glyph(here, end) + ) + { + if ( + next - here == 1 and + (m_input[here] == '\'' or m_input[here] == '\\') + ) + { + // Skip escape. + here = next; + next = scan_glyph(here, end); + } + + output.append(m_input + here, m_input + next); + } + + return output; +} + + +/// Find the end of a double-quoted SQL string in an SQL array. +std::string::size_type array_parser::scan_double_quoted_string() const +{ + auto here = m_pos; + assert(here < m_end); + auto next = scan_glyph(here); + assert(next - here == 1); + assert(m_input[here] == '"'); + for ( + here = next, next = scan_glyph(here); + here < m_end; + here = next, next = scan_glyph(here) + ) + { + if (next - here == 1) switch (m_input[here]) + { + case '\\': + // Backslash escape. Skip ahead by one more character. + here = next; + next = scan_glyph(here); + break; + + case '"': + // Closing quote. Return the position right after. + return next; + } + } + throw argument_error{"Null byte in SQL string: " + std::string{m_input}}; +} + + +/// Parse a double-quoted SQL string: un-quote it and un-escape it. +std::string array_parser::parse_double_quoted_string( + std::string::size_type end) const +{ + // There have to be at least 2 characters: the opening and closing quotes. + assert(m_pos + 1 < end); + assert(m_input[m_pos] == '"'); + assert(m_input[end - 1] == '"'); + + std::string output; + // Maximum output size is same as the input size, minus the opening and + // closing quotes. In the worst case, the real number could be half that. + // Usually it'll be a pretty close estimate. + output.reserve(std::size_t(end - m_pos - 2)); + + for ( + auto here = scan_glyph(m_pos, end), next = scan_glyph(here, end); + here < end - 1; + here = next, next = scan_glyph(here, end) + ) + { + if ((next - here == 1) and (m_input[here] == '\\')) + { + // Skip escape. + here = next; + next = scan_glyph(here, end); + } + + output.append(m_input + here, m_input + next); + } + + return output; +} + + +/// Find the end of an unquoted string in an SQL array. +/** Assumes UTF-8 or an ASCII-superset single-byte encoding. + */ +std::string::size_type array_parser::scan_unquoted_string() const +{ + auto here = m_pos, next = scan_glyph(here); + assert(here < m_end); + assert((next - here > 1) or (m_input[here] != '\'')); + assert((next - here > 1) or (m_input[here] != '"')); + + while ( + (next - here) > 1 or + ( + m_input[here] != ',' and + m_input[here] != ';' and + m_input[here] != '}' + ) + ) + { + here = next; + next = scan_glyph(here); + } + return here; +} + + +/// Parse an unquoted SQL string. +/** Here, the special unquoted value NULL means a null value, not a string + * that happens to spell "NULL". + */ +std::string array_parser::parse_unquoted_string( + std::string::size_type end) const +{ + return std::string{m_input + m_pos, m_input + end}; +} + + +array_parser::array_parser( + const char input[], + internal::encoding_group enc) : + m_input(input), + m_end(input == nullptr ? 0 : std::strlen(input)), + m_scan(internal::get_glyph_scanner(enc)), + m_pos(0) +{ +} + + +std::pair<array_parser::juncture, std::string> +array_parser::get_next() +{ + juncture found; + std::string value; + std::string::size_type end; + + if (m_input == nullptr or (m_pos >= m_end)) + return std::make_pair(juncture::done, value); + + if (scan_glyph(m_pos) - m_pos > 1) + { + // Non-ASCII unquoted string. + end = scan_unquoted_string(); + value = parse_unquoted_string(end); + found = juncture::string_value; + } + else switch (m_input[m_pos]) + { + case '\0': + // TODO: Maybe just raise an error? + found = juncture::done; + end = m_pos; + break; + case '{': + found = juncture::row_start; + end = scan_glyph(m_pos); + break; + case '}': + found = juncture::row_end; + end = scan_glyph(m_pos); + break; + case '\'': + found = juncture::string_value; + end = scan_single_quoted_string(); + value = parse_single_quoted_string(end); + break; + case '"': + found = juncture::string_value; + end = scan_double_quoted_string(); + value = parse_double_quoted_string(end); + break; + default: + end = scan_unquoted_string(); + value = parse_unquoted_string(end); + if (value == "NULL") + { + // In this one situation, as a special case, NULL means a null field, + // not a string that happens to spell "NULL". + value.clear(); + found = juncture::null_value; + } + else + { + // The normal case: we just parsed an unquoted string. The value is + // what we need. + found = juncture::string_value; + } + break; + } + + // Skip a trailing field separator, if present. + if (end < m_end) + { + auto next = scan_glyph(end); + if (next - end == 1 and (m_input[end] == ',' or m_input[end] == ';')) + end = next; + } + + m_pos = end; + return std::make_pair(found, value); +} +} // namespace pqxx diff --git a/contrib/libs/libpqxx/src/binarystring.cxx b/contrib/libs/libpqxx/src/binarystring.cxx new file mode 100644 index 0000000000..0091f48baf --- /dev/null +++ b/contrib/libs/libpqxx/src/binarystring.cxx @@ -0,0 +1,150 @@ +/** Implementation of bytea (binary string) conversions. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <new> +#include <stdexcept> + +extern "C" +{ +#include "libpq-fe.h" +} + +#include "pqxx/binarystring" +#include "pqxx/field" + + +using namespace pqxx::internal; + +namespace +{ +using unsigned_char = unsigned char; +using buffer = std::pair<unsigned char *, size_t>; + + +buffer to_buffer(const void *data, size_t len) +{ + void *const output{malloc(len + 1)}; + if (output == nullptr) throw std::bad_alloc{}; + static_cast<char *>(output)[len] = '\0'; + memcpy(static_cast<char *>(output), data, len); + return buffer{static_cast<unsigned char *>(output), len}; +} + + +buffer to_buffer(const std::string &source) +{ + return to_buffer(source.c_str(), source.size()); +} + + + +buffer unescape(const unsigned char escaped[]) +{ +#ifdef _WIN32 + /* On Windows only, the return value from PQunescapeBytea() must be freed + * using PQfreemem. Copy to a buffer allocated by libpqxx, so that the + * binarystring's buffer can be freed uniformly, + */ + size_t unescaped_len = 0; + // TODO: Use make_unique once we require C++14. Sooo much easier. + std::unique_ptr<unsigned char, void(*)(unsigned char *)> A( + PQunescapeBytea(const_cast<unsigned char *>(escaped), &unescaped_len), + freepqmem_templated<unsigned char>); + void *data = A.get(); + if (data == nullptr) throw std::bad_alloc{}; + return to_buffer(data, unescaped_len); +#else + /* On non-Windows platforms, it's okay to free libpq-allocated memory using + * free(). No extra copy needed. + */ + buffer unescaped; + unescaped.first = PQunescapeBytea( + const_cast<unsigned char *>(escaped), &unescaped.second); + if (unescaped.first == nullptr) throw std::bad_alloc{}; + return unescaped; +#endif +} + +} // namespace + + +pqxx::binarystring::binarystring(const field &F) : + m_buf{make_smart_pointer()}, + m_size{0} +{ + buffer unescaped{unescape(reinterpret_cast<const_pointer>(F.c_str()))}; + m_buf = make_smart_pointer(unescaped.first); + m_size = unescaped.second; +} + + +pqxx::binarystring::binarystring(const std::string &s) : + m_buf{make_smart_pointer()}, + m_size{s.size()} +{ + m_buf = make_smart_pointer(to_buffer(s).first); +} + + +pqxx::binarystring::binarystring(const void *binary_data, size_t len) : + m_buf{make_smart_pointer()}, + m_size{len} +{ + m_buf = make_smart_pointer(to_buffer(binary_data, len).first); +} + + +bool pqxx::binarystring::operator==(const binarystring &rhs) const noexcept +{ + if (rhs.size() != size()) return false; + return std::memcmp(data(), rhs.data(), size()) == 0; +} + + +pqxx::binarystring &pqxx::binarystring::operator=(const binarystring &rhs) +{ + m_buf = rhs.m_buf; + m_size = rhs.m_size; + return *this; +} + + +pqxx::binarystring::const_reference pqxx::binarystring::at(size_type n) const +{ + if (n >= m_size) + { + if (m_size == 0) + throw std::out_of_range{"Accessing empty binarystring"}; + throw std::out_of_range{ + "binarystring index out of range: " + + to_string(n) + " (should be below " + to_string(m_size) + ")"}; + } + return data()[n]; +} + + +void pqxx::binarystring::swap(binarystring &rhs) +{ + m_buf.swap(rhs.m_buf); + + // This part very obviously can't go wrong, so do it last + const auto s = m_size; + m_size = rhs.m_size; + rhs.m_size = s; +} + + +std::string pqxx::binarystring::str() const +{ + return std::string{get(), m_size}; +} diff --git a/contrib/libs/libpqxx/src/connection.cxx b/contrib/libs/libpqxx/src/connection.cxx new file mode 100644 index 0000000000..982b6d60f9 --- /dev/null +++ b/contrib/libs/libpqxx/src/connection.cxx @@ -0,0 +1,182 @@ +/** Implementation of the pqxx::connection and sibling classes. + * + * Different ways of setting up a backend connection. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <stdexcept> + +extern "C" +{ +#include "libpq-fe.h" +} + +#include "pqxx/connection" + + +pqxx::connectionpolicy::connectionpolicy(const std::string &opts) : + m_options{opts} +{ +} + + +pqxx::connectionpolicy::~connectionpolicy() noexcept +{ +} + + +pqxx::connectionpolicy::handle +pqxx::connectionpolicy::normalconnect(handle orig) +{ + if (orig) return orig; + orig = PQconnectdb(options().c_str()); + if (orig == nullptr) throw std::bad_alloc{}; + if (PQstatus(orig) != CONNECTION_OK) + { + const std::string msg{PQerrorMessage(orig)}; + PQfinish(orig); + throw broken_connection{msg}; + } + return orig; +} + + +pqxx::connectionpolicy::handle +pqxx::connectionpolicy::do_startconnect(handle orig) +{ + return orig; +} + +pqxx::connectionpolicy::handle +pqxx::connectionpolicy::do_completeconnect(handle orig) +{ + return orig; +} + +pqxx::connectionpolicy::handle +pqxx::connectionpolicy::do_dropconnect(handle orig) noexcept +{ + return orig; +} + +pqxx::connectionpolicy::handle +pqxx::connectionpolicy::do_disconnect(handle orig) noexcept +{ + orig = do_dropconnect(orig); + if (orig) PQfinish(orig); + return nullptr; +} + + +bool pqxx::connectionpolicy::is_ready(handle h) const noexcept +{ + return h != nullptr; +} + + +pqxx::connectionpolicy::handle +pqxx::connect_direct::do_startconnect(handle orig) +{ + if (orig) return orig; + orig = normalconnect(orig); + if (PQstatus(orig) != CONNECTION_OK) + { + const std::string msg{PQerrorMessage(orig)}; + do_disconnect(orig); + throw broken_connection{msg}; + } + return orig; +} + + +pqxx::connectionpolicy::handle +pqxx::connect_lazy::do_completeconnect(handle orig) +{ + return normalconnect(orig); +} + + +pqxx::connect_async::connect_async(const std::string &opts) : + connectionpolicy{opts}, + m_connecting{false} +{ +} + +pqxx::connectionpolicy::handle +pqxx::connect_async::do_startconnect(handle orig) +{ + if (orig != nullptr) return orig; // Already connecting or connected. + m_connecting = false; + orig = PQconnectStart(options().c_str()); + if (orig == nullptr) throw std::bad_alloc{}; + if (PQstatus(orig) == CONNECTION_BAD) + { + do_dropconnect(orig); + throw broken_connection{std::string{PQerrorMessage(orig)}}; + } + m_connecting = true; + return orig; +} + + +pqxx::connectionpolicy::handle +pqxx::connect_async::do_completeconnect(handle orig) +{ + const bool makenew = (orig == nullptr); + if (makenew) orig = do_startconnect(orig); + if (not m_connecting) return orig; + + // Our "attempt to connect" state ends here, for better or for worse + m_connecting = false; + + PostgresPollingStatusType pollstatus = PGRES_POLLING_WRITING; + + do + { + switch (pollstatus) + { + case PGRES_POLLING_FAILED: + if (makenew) do_disconnect(orig); + throw broken_connection{std::string{PQerrorMessage(orig)}}; + + case PGRES_POLLING_READING: + internal::wait_read(orig); + break; + + case PGRES_POLLING_WRITING: + internal::wait_write(orig); + break; + + case PGRES_POLLING_OK: + break; + + default: + // Meaningless, really, but deals with the obsolete PGRES_POLLING_ACTIVE + // without requiring it to be defined. + break; + } + pollstatus = PQconnectPoll(orig); + } while (pollstatus != PGRES_POLLING_OK); + + return orig; +} + + +pqxx::connectionpolicy::handle +pqxx::connect_async::do_dropconnect(handle orig) noexcept +{ + m_connecting = false; + return orig; +} + + +bool pqxx::connect_async::is_ready(handle h) const noexcept +{ + return h != nullptr and not m_connecting; +} diff --git a/contrib/libs/libpqxx/src/connection_base.cxx b/contrib/libs/libpqxx/src/connection_base.cxx new file mode 100644 index 0000000000..345e3414ee --- /dev/null +++ b/contrib/libs/libpqxx/src/connection_base.cxx @@ -0,0 +1,1475 @@ +/** Implementation of the pqxx::connection_base abstract base class. + * + * pqxx::connection_base encapsulates a frontend to backend connection. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <algorithm> +#include <cassert> +#include <cerrno> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> +#include <iterator> +#include <memory> +#include <stdexcept> + +#if defined(_WIN32) +// Includes for WSAPoll(). +#include <winsock2.h> +#include <ws2tcpip.h> +#include <mstcpip.h> +#elif defined(HAVE_POLL) +// Include for poll(). +#include <poll.h> +#elif defined(HAVE_SYS_SELECT_H) +// Include for select() on (recent) POSIX systems. +#include <sys/select.h> +#else +// Includes for select() according to various older standards. +#if defined(HAVE_SYS_TYPES_H) +#include <sys/types.h> +#endif +#if defined(HAVE_UNISTD_H) +#include <unistd.h> +#endif +#endif +#if defined(HAVE_SYS_TIME_H) +#include <sys/time.h> +#endif + +extern "C" +{ +#include "libpq-fe.h" +} + +#include "pqxx/binarystring" +#include "pqxx/connection" +#include "pqxx/connection_base" +#include "pqxx/nontransaction" +#include "pqxx/pipeline" +#include "pqxx/result" +#include "pqxx/strconv" +#include "pqxx/transaction" +#include "pqxx/notification" + +#include "pqxx/internal/gates/connection-reactivation_avoidance_exemption.hxx" +#include "pqxx/internal/gates/errorhandler-connection.hxx" +#include "pqxx/internal/gates/result-creation.hxx" +#include "pqxx/internal/gates/result-connection.hxx" + +using namespace pqxx; +using namespace pqxx::internal; +using namespace pqxx::prepare; + + +extern "C" +{ +// The PQnoticeProcessor that receives an error or warning from libpq and sends +// it to the appropriate connection for processing. +void pqxx_notice_processor(void *conn, const char *msg) noexcept +{ + reinterpret_cast<pqxx::connection_base *>(conn)->process_notice(msg); +} + + +// There's no way in libpq to disable a connection's notice processor. So, +// set an inert one to get the same effect. +void inert_notice_processor(void *, const char *) noexcept {} +} + + +std::string pqxx::encrypt_password( + const std::string &user, const std::string &password) +{ + std::unique_ptr<char, void (*)(char *)> p{ + PQencryptPassword(password.c_str(), user.c_str()), + freepqmem_templated<char>}; + return std::string{p.get()}; +} + + +void pqxx::connection_base::init() +{ + m_conn = m_policy.do_startconnect(m_conn); +#include "pqxx/internal/ignore-deprecated-pre.hxx" + if (m_policy.is_ready(m_conn)) activate(); +#include "pqxx/internal/ignore-deprecated-post.hxx" +} + + +pqxx::result pqxx::connection_base::make_result( + internal::pq::PGresult *rhs, + const std::string &query) +{ + return gate::result_creation::create( + rhs, + query, + internal::enc_group(encoding_id())); +} + + +int pqxx::connection_base::backendpid() const noexcept +{ + return m_conn ? PQbackendPID(m_conn) : 0; +} + + +namespace +{ +PQXX_PURE int socket_of(const ::pqxx::internal::pq::PGconn *c) noexcept +{ + return c ? PQsocket(c) : -1; +} +} + + +int pqxx::connection_base::sock() const noexcept +{ + return socket_of(m_conn); +} + + +void pqxx::connection_base::activate() +{ + if (not is_open()) + { + if (m_inhibit_reactivation) + throw broken_connection{ + "Could not reactivate connection; " + "reactivation is inhibited"}; + + // If any objects were open that didn't survive the closing of our + // connection, don't try to reactivate + if (m_reactivation_avoidance.get()) return; + + try + { + m_conn = m_policy.do_startconnect(m_conn); + m_conn = m_policy.do_completeconnect(m_conn); + m_completed = true; // (But retracted if error is thrown below) + + if (not is_open()) throw broken_connection{}; + + set_up_state(); + } + catch (const broken_connection &e) + { + disconnect(); + m_completed = false; + throw broken_connection{e.what()}; + } + catch (const std::exception &) + { + m_completed = false; + throw; + } + } +} + + +void pqxx::connection_base::deactivate() +{ + if (m_conn == nullptr) return; + + if (m_trans.get()) + throw usage_error{ + "Attempt to deactivate connection while " + + m_trans.get()->description() + " still open"}; + + if (m_reactivation_avoidance.get()) + { + process_notice( + "Attempt to deactivate connection while it is in a state " + "that cannot be fully recovered later (ignoring)"); + return; + } + + m_completed = false; + m_conn = m_policy.do_disconnect(m_conn); +} + + +void pqxx::connection_base::simulate_failure() +{ + if (m_conn) + { + m_conn = m_policy.do_disconnect(m_conn); +#include <pqxx/internal/ignore-deprecated-pre.hxx> + inhibit_reactivation(true); +#include <pqxx/internal/ignore-deprecated-post.hxx> + } +} + + +int pqxx::connection_base::protocol_version() const noexcept +{ + return m_conn ? PQprotocolVersion(m_conn) : 0; +} + + +int pqxx::connection_base::server_version() const noexcept +{ + return m_serverversion; +} + + +void pqxx::connection_base::set_variable(const std::string &Var, + const std::string &Value) +{ + if (m_trans.get()) + { + // We're in a transaction. The variable should go in there. + m_trans.get()->set_variable(Var, Value); + } + else + { + // We're not in a transaction. Set a session variable. + if (is_open()) raw_set_var(Var, Value); + m_vars[Var] = Value; + } +} + + +std::string pqxx::connection_base::get_variable(const std::string &Var) +{ + return m_trans.get() ? m_trans.get()->get_variable(Var) : raw_get_var(Var); +} + + +std::string pqxx::connection_base::raw_get_var(const std::string &Var) +{ + // Is this variable in our local map of set variables? + const auto i = m_vars.find(Var); + if (i != m_vars.end()) return i->second; + + return exec(("SHOW " + Var).c_str(), 0).at(0).at(0).as(std::string{}); +} + + +void pqxx::connection_base::clearcaps() noexcept +{ + m_caps.reset(); +} + + +/** Set up various parts of logical connection state that may need to be + * 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_base::set_up_state() +{ + if (m_conn == nullptr) + throw internal_error{"set_up_state() on no connection"}; + + if (status() != CONNECTION_OK) + { + const auto msg = err_msg(); + m_conn = m_policy.do_disconnect(m_conn); + throw failure{msg}; + } + + read_capabilities(); + + for (auto &p: m_prepared) p.second.registered = false; + + // The default notice processor in libpq writes to stderr. Ours does + // nothing. + // If the caller registers an error handler, this gets replaced with an + // error handler that walks down the connection's chain of handlers. We + // don't do that by default because there's a danger: libpq may call the + // notice processor via a result object, even after the connection has been + // destroyed and the handlers list no longer exists. + clear_notice_processor(); + + internal_set_trace(); + + if (not m_receivers.empty() or not m_vars.empty()) + { + std::stringstream restore_query; + + // Pipeline all queries needed to restore receivers and variables, so we can + // send them over in one go. + + // Reinstate all active receivers + if (not m_receivers.empty()) + { + std::string Last; + for (auto &i: m_receivers) + { + // m_receivers can handle multiple receivers waiting on the same event; + // issue just one LISTEN for each event. + if (i.first != Last) + { + restore_query << "LISTEN " << quote_name(i.first) << "; "; + Last = i.first; + } + } + } + + for (auto &i: m_vars) + restore_query << "SET " << i.first << "=" << i.second << "; "; + + // Now do the whole batch at once + PQsendQuery(m_conn, restore_query.str().c_str()); + result r; + do + r = make_result(PQgetResult(m_conn), "[RECONNECT]"); + while (gate::result_connection(r)); + } + + m_completed = true; + if (not is_open()) throw broken_connection{}; +} + + +void pqxx::connection_base::check_result(const result &R) +{ + if (not is_open()) throw broken_connection{}; + + // A shame we can't quite detect out-of-memory to turn this into a bad_alloc! + if (not gate::result_connection{R}) throw failure(err_msg()); + + gate::result_creation{R}.check_status(); +} + + +void pqxx::connection_base::disconnect() noexcept +{ + // When we activate again, the server may be different! + clearcaps(); + + m_conn = m_policy.do_disconnect(m_conn); +} + + +bool pqxx::connection_base::is_open() const noexcept +{ + return m_conn and m_completed and (status() == CONNECTION_OK); +} + + +void pqxx::connection_base::process_notice_raw(const char msg[]) noexcept +{ + if ((msg == nullptr) or (*msg == '\0')) return; + const auto + rbegin = m_errorhandlers.crbegin(), + rend = m_errorhandlers.crend(); + for (auto i = rbegin; (i != rend) and (**i)(msg); ++i) ; +} + + +void pqxx::connection_base::process_notice(const char msg[]) noexcept +{ + if (msg == nullptr) return; + const auto len = strlen(msg); + if (len == 0) return; + if (msg[len-1] == '\n') + { + process_notice_raw(msg); + } + else try + { + // Newline is missing. Try the C++ string version of this function. + process_notice(std::string{msg}); + } + catch (const std::exception &) + { + // If we can't even do that, use plain old buffer copying instead + // (unavoidably, this will break up overly long messages!) + const char separator[] = "[...]\n"; + char buf[1007]; + size_t bytes = sizeof(buf)-sizeof(separator)-1; + size_t written; + strcpy(&buf[bytes], separator); + // Write all chunks but last. Each will fill the buffer exactly. + for (written = 0; (written+bytes) < len; written += bytes) + { + memcpy(buf, &msg[written], bytes); + process_notice_raw(buf); + } + // Write any remaining bytes (which won't fill an entire buffer) + bytes = len-written; + memcpy(buf, &msg[written], bytes); + // Add trailing nul byte, plus newline unless there already is one + strcpy(&buf[bytes], &"\n"[buf[bytes-1]=='\n']); + process_notice_raw(buf); + } +} + + +void pqxx::connection_base::process_notice(const std::string &msg) noexcept +{ + // Ensure that message passed to errorhandler ends in newline + if (msg[msg.size()-1] == '\n') + { + process_notice_raw(msg.c_str()); + } + else try + { + const std::string nl = msg + "\n"; + process_notice_raw(nl.c_str()); + } + catch (const std::exception &) + { + // If nothing else works, try writing the message without the newline + process_notice_raw(msg.c_str()); + // This is ugly. + process_notice_raw("\n"); + } +} + + +void pqxx::connection_base::trace(FILE *Out) noexcept +{ + m_trace = Out; + if (m_conn) internal_set_trace(); +} + + +void pqxx::connection_base::add_receiver(pqxx::notification_receiver *T) +{ + if (T == nullptr) throw argument_error{"Null receiver registered"}; + + // Add to receiver list and attempt to start listening. + const auto p = m_receivers.find(T->channel()); + const receiver_list::value_type NewVal(T->channel(), T); + + if (p == m_receivers.end()) + { + // Not listening on this event yet, start doing so. + const std::string LQ("LISTEN " + quote_name(T->channel())); + + if (is_open()) try + { + check_result(make_result(PQexec(m_conn, LQ.c_str()), LQ)); + } + catch (const broken_connection &) + { + } + m_receivers.insert(NewVal); + } + else + { + m_receivers.insert(p, NewVal); + } +} + + +void pqxx::connection_base::remove_receiver(pqxx::notification_receiver *T) + noexcept +{ + if (T == nullptr) return; + + try + { + const std::pair<const std::string, notification_receiver *> needle{ + T->channel(), T}; + auto R = m_receivers.equal_range(needle.first); + const auto i = find(R.first, R.second, needle); + + if (i == R.second) + { + process_notice( + "Attempt to remove unknown receiver '" + needle.first + "'"); + } + else + { + // Erase first; otherwise a notification for the same receiver may yet + // come in and wreak havoc. Thanks Dragan Milenkovic. + const bool gone = (m_conn and (R.second == ++R.first)); + m_receivers.erase(i); + if (gone) exec(("UNLISTEN " + quote_name(needle.first)).c_str(), 0); + } + } + catch (const std::exception &e) + { + process_notice(e.what()); + } +} + + +bool pqxx::connection_base::consume_input() noexcept +{ + return PQconsumeInput(m_conn) != 0; +} + + +bool pqxx::connection_base::is_busy() const noexcept +{ + return PQisBusy(m_conn) != 0; +} + + +namespace +{ +/// Stateful libpq "cancel" operation. +class cancel_wrapper +{ + PGcancel *m_cancel; + char m_errbuf[500]; + +public: + explicit cancel_wrapper(PGconn *conn) : + m_cancel{nullptr}, + m_errbuf{} + { + if (conn) + { + m_cancel = PQgetCancel(conn); + if (m_cancel == nullptr) throw std::bad_alloc{}; + } + } + ~cancel_wrapper() { if (m_cancel) PQfreeCancel(m_cancel); } + + void operator()() + { + if (not m_cancel) return; + if (PQcancel(m_cancel, m_errbuf, int{sizeof(m_errbuf)}) == 0) + throw sql_error{std::string{m_errbuf}}; + } +}; +} + + +void pqxx::connection_base::cancel_query() +{ + cancel_wrapper cancel{m_conn}; + cancel(); +} + + +void pqxx::connection_base::set_verbosity(error_verbosity verbosity) noexcept +{ + PQsetErrorVerbosity(m_conn, static_cast<PGVerbosity>(verbosity)); + m_verbosity = verbosity; +} + + +namespace +{ +/// Unique pointer to PGnotify. +using notify_ptr = std::unique_ptr<PGnotify, void (*)(PGnotify *)>; + + +/// Get one notification from a connection, or null. +notify_ptr get_notif(pqxx::internal::pq::PGconn *conn) +{ + return notify_ptr(PQnotifies(conn), freepqmem_templated<PGnotify>); +} +} + + +int pqxx::connection_base::get_notifs() +{ + if (not is_open()) return 0; + + if (not consume_input()) throw broken_connection{}; + + // Even if somehow we receive notifications during our transaction, don't + // deliver them. + if (m_trans.get()) return 0; + + int notifs = 0; + for (auto N = get_notif(m_conn); N.get(); N = get_notif(m_conn)) + { + notifs++; + + const auto Hit = m_receivers.equal_range(std::string{N->relname}); + for (auto i = Hit.first; i != Hit.second; ++i) try + { + (*i->second)(N->extra, N->be_pid); + } + catch (const std::exception &e) + { + try + { + process_notice( + "Exception in notification receiver '" + + i->first + + "': " + + e.what() + + "\n"); + } + catch (const std::bad_alloc &) + { + // Out of memory. Try to get the message out in a more robust way. + process_notice( + "Exception in notification receiver, " + "and also ran out of memory\n"); + } + catch (const std::exception &) + { + process_notice( + "Exception in notification receiver " + "(compounded by other error)\n"); + } + } + + N.reset(); + } + return notifs; +} + + +const char *pqxx::connection_base::dbname() +{ +#include <pqxx/internal/ignore-deprecated-pre.hxx> + if (m_conn == nullptr) activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + return PQdb(m_conn); +} + + +const char *pqxx::connection_base::username() +{ +#include <pqxx/internal/ignore-deprecated-pre.hxx> + if (m_conn == nullptr) activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + return PQuser(m_conn); +} + + +const char *pqxx::connection_base::hostname() +{ +#include <pqxx/internal/ignore-deprecated-pre.hxx> + if (m_conn == nullptr) activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + return PQhost(m_conn); +} + + +const char *pqxx::connection_base::port() +{ +#include <pqxx/internal/ignore-deprecated-pre.hxx> + if (m_conn == nullptr) activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + return PQport(m_conn); +} + + +const char *pqxx::connection_base::err_msg() const noexcept +{ + return m_conn ? PQerrorMessage(m_conn) : "No connection to database"; +} + + +void pqxx::connection_base::clear_notice_processor() +{ + PQsetNoticeProcessor(m_conn, inert_notice_processor, nullptr); +} + + +void pqxx::connection_base::set_notice_processor() +{ + PQsetNoticeProcessor(m_conn, pqxx_notice_processor, this); +} + + +void pqxx::connection_base::register_errorhandler(errorhandler *handler) +{ + // Set notice processor on demand, i.e. only when the caller actually + // registers an error handler. + // We do this just to make it less likely that users fall into the trap + // where a result object may hold a notice processor derived from its parent + // connection which has already been destroyed. Our notice processor goes + // through the connection's list of error handlers. If the connection object + // has already been destroyed though, that list no longer exists. + // By setting the notice processor on demand, we absolve users who never + // register an error handler from ahving to care about this nasty subtlety. + if (m_errorhandlers.empty()) set_notice_processor(); + m_errorhandlers.push_back(handler); +} + + +void pqxx::connection_base::unregister_errorhandler(errorhandler *handler) + noexcept +{ + // The errorhandler itself will take care of nulling its pointer to this + // connection. + m_errorhandlers.remove(handler); + if (m_errorhandlers.empty()) clear_notice_processor(); +} + + +std::vector<errorhandler *> pqxx::connection_base::get_errorhandlers() const +{ + return std::vector<errorhandler *>{ + std::begin(m_errorhandlers), std::end(m_errorhandlers)}; +} + + +pqxx::result pqxx::connection_base::exec(const char Query[], int Retries) +{ +#include <pqxx/internal/ignore-deprecated-pre.hxx> + activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + + auto R = make_result(PQexec(m_conn, Query), Query); + + while ((Retries > 0) and not gate::result_connection{R} and not is_open()) + { + Retries--; + reset(); + if (is_open()) R = make_result(PQexec(m_conn, Query), Query); + } + + check_result(R); + + get_notifs(); + return R; +} + + +void pqxx::connection_base::prepare( + const std::string &name, + const std::string &definition) +{ + auto i = m_prepared.find(name); + if (i != m_prepared.end()) + { + if (definition != i->second.definition) + { + if (not name.empty()) + throw argument_error{ + "Inconsistent redefinition of prepared statement " + name}; + + i->second.registered = false; + i->second.definition = definition; + } + } + else + { + m_prepared.insert(make_pair( + name, + prepare::internal::prepared_def{definition})); + } +} + + +void pqxx::connection_base::prepare(const std::string &definition) +{ + this->prepare(std::string{}, definition); +} + + +void pqxx::connection_base::unprepare(const std::string &name) +{ + auto i = m_prepared.find(name); + + // Quietly ignore duplicated or spurious unprepare()s + if (i == m_prepared.end()) return; + + if (i->second.registered) + exec(("DEALLOCATE " + quote_name(name)).c_str(), 0); + + m_prepared.erase(i); +} + + +pqxx::prepare::internal::prepared_def & +pqxx::connection_base::find_prepared(const std::string &statement) +{ + auto s = m_prepared.find(statement); + if (s == m_prepared.end()) + throw argument_error{"Unknown prepared statement '" + statement + "'"}; + return s->second; +} + + +pqxx::prepare::internal::prepared_def & +pqxx::connection_base::register_prepared(const std::string &name) +{ +#include <pqxx/internal/ignore-deprecated-pre.hxx> + activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + auto &s = find_prepared(name); + + // "Register" (i.e., define) prepared statement with backend on demand + if (not s.registered) + { + auto r = make_result( + PQprepare(m_conn, name.c_str(), s.definition.c_str(), 0, nullptr), + "[PREPARE " + name + "]"); + check_result(r); + s.registered = not name.empty(); + return s; + } + + return s; +} + + +void pqxx::connection_base::prepare_now(const std::string &name) +{ + register_prepared(name); +} + + +pqxx::result pqxx::connection_base::prepared_exec( + const std::string &statement, + const char *const params[], + const int paramlengths[], + const int binary[], + int nparams, + result_format format) +{ + register_prepared(statement); +#include <pqxx/internal/ignore-deprecated-pre.hxx> + activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + auto r = make_result( + PQexecPrepared( + m_conn, + statement.c_str(), + nparams, + params, + paramlengths, + binary, + format == result_format::binary? 1 : 0), + statement); + check_result(r); + get_notifs(); + return r; +} + + +pqxx::result pqxx::connection_base::exec_prepared( + const std::string &statement, + const internal::params &args, + result_format format) +{ + register_prepared(statement); +#include <pqxx/internal/ignore-deprecated-pre.hxx> + activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + const auto pointers = args.get_pointers(); + const auto pq_result = PQexecPrepared( + m_conn, + statement.c_str(), + int(args.nonnulls.size()), + pointers.data(), + args.lengths.data(), + args.binaries.data(), + format == result_format::binary? 1 : 0); + const auto r = make_result(pq_result, statement); + check_result(r); + get_notifs(); + return r; +} + + +bool pqxx::connection_base::prepared_exists(const std::string &statement) const +{ + auto s = m_prepared.find(statement); + return s != PSMap::const_iterator(m_prepared.end()); +} + + +void pqxx::connection_base::reset() +{ + if (m_inhibit_reactivation) + throw broken_connection{ + "Could not reset connection: reactivation is inhibited"}; + if (m_reactivation_avoidance.get()) return; + + // TODO: Probably need to go through a full disconnect/reconnect! + // Forget about any previously ongoing connection attempts + m_conn = m_policy.do_dropconnect(m_conn); + m_completed = false; + + if (m_conn) + { + // Reset existing connection + PQreset(m_conn); + set_up_state(); + } + else + { + // No existing connection--start a new one +#include <pqxx/internal/ignore-deprecated-pre.hxx> + activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + } +} + + +void pqxx::connection_base::close() noexcept +{ + m_completed = false; +#include <pqxx/internal/ignore-deprecated-pre.hxx> + inhibit_reactivation(false); +#include <pqxx/internal/ignore-deprecated-post.hxx> + m_reactivation_avoidance.clear(); + try + { + if (m_trans.get()) + process_notice( + "Closing connection while " + m_trans.get()->description() + + " still open"); + + if (not m_receivers.empty()) + { + process_notice("Closing connection with outstanding receivers."); + m_receivers.clear(); + } + + std::list<errorhandler *> old_handlers; + m_errorhandlers.swap(old_handlers); + const auto + rbegin = old_handlers.crbegin(), + rend = old_handlers.crend(); + for (auto i = rbegin; i!=rend; ++i) + gate::errorhandler_connection_base{**i}.unregister(); + + m_conn = m_policy.do_disconnect(m_conn); + } + catch (...) + { + } +} + + +void pqxx::connection_base::raw_set_var( + const std::string &Var, + const std::string &Value) +{ + exec(("SET " + Var + "=" + Value).c_str(), 0); +} + + +void pqxx::connection_base::add_variables( + const std::map<std::string,std::string> &Vars) +{ + for (auto &i: Vars) m_vars[i.first] = i.second; +} + + +void pqxx::connection_base::internal_set_trace() noexcept +{ + if (m_conn) + { + if (m_trace) PQtrace(m_conn, m_trace); + else PQuntrace(m_conn); + } +} + + +int pqxx::connection_base::status() const noexcept +{ + return PQstatus(m_conn); +} + + +void pqxx::connection_base::register_transaction(transaction_base *T) +{ + m_trans.register_guest(T); +} + + +void pqxx::connection_base::unregister_transaction(transaction_base *T) + noexcept +{ + try + { + m_trans.unregister_guest(T); + } + catch (const std::exception &e) + { + process_notice(e.what()); + } +} + + +bool pqxx::connection_base::read_copy_line(std::string &Line) +{ + if (not is_open()) + throw internal_error{"read_copy_line() without connection"}; + + Line.erase(); + bool Result; + + char *Buf = nullptr; + const std::string query = "[END COPY]"; + const auto line_len = PQgetCopyData(m_conn, &Buf, false); + switch (line_len) + { + case -2: + throw failure{"Reading of table data failed: " + std::string{err_msg()}}; + + case -1: + for ( + auto R = make_result(PQgetResult(m_conn), query); + gate::result_connection(R); + R=make_result(PQgetResult(m_conn), query) + ) + check_result(R); + Result = false; + break; + + case 0: + throw internal_error{"table read inexplicably went asynchronous"}; + + default: + if (Buf) + { + std::unique_ptr<char, void (*)(char *)> PQA( + Buf, freepqmem_templated<char>); + Line.assign(Buf, unsigned(line_len)); + } + Result = true; + } + + return Result; +} + + +void pqxx::connection_base::write_copy_line(const std::string &Line) +{ + if (not is_open()) + throw internal_error{"write_copy_line() without connection"}; + + const std::string L = Line + '\n'; + const char *const LC = L.c_str(); + const auto Len = L.size(); + + if (PQputCopyData(m_conn, LC, int(Len)) <= 0) + { + const std::string msg = ( + std::string{"Error writing to table: "} + err_msg()); +// TODO: PQendcopy() is documented as obsolete! + PQendcopy(m_conn); + throw failure{msg}; + } +} + + +void pqxx::connection_base::end_copy_write() +{ + int Res = PQputCopyEnd(m_conn, nullptr); + switch (Res) + { + case -1: + throw failure{"Write to table failed: " + std::string{err_msg()}}; + case 0: + throw internal_error{"table write is inexplicably asynchronous"}; + case 1: + // Normal termination. Retrieve result object. + break; + + default: + throw internal_error{ + "unexpected result " + to_string(Res) + " from PQputCopyEnd()"}; + } + + check_result(make_result(PQgetResult(m_conn), "[END COPY]")); +} + + +void pqxx::connection_base::start_exec(const std::string &Q) +{ +#include <pqxx/internal/ignore-deprecated-pre.hxx> + activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + if (PQsendQuery(m_conn, Q.c_str()) == 0) throw failure{err_msg()}; +} + + +pqxx::internal::pq::PGresult *pqxx::connection_base::get_result() +{ + if (m_conn == nullptr) throw broken_connection{}; + return PQgetResult(m_conn); +} + + +void pqxx::connection_base::add_reactivation_avoidance_count(int n) +{ + m_reactivation_avoidance.add(n); +} + + +std::string pqxx::connection_base::esc(const char str[], size_t maxlen) +{ + // We need a connection object... This is the one reason why this function is + // not const! +#include <pqxx/internal/ignore-deprecated-pre.hxx> + if (m_conn == nullptr) activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + + std::vector<char> buf(2 * maxlen + 1); + int err = 0; + // TODO: Can we make a callback-based string_view alternative to this? + // TODO: If we can, then quote() can wrap PQescapeLiteral()! + PQescapeStringConn(m_conn, buf.data(), str, maxlen, &err); + if (err) throw argument_error{err_msg()}; + return std::string{buf.data()}; +} + + +std::string pqxx::connection_base::esc(const char str[]) +{ + return this->esc(str, strlen(str)); +} + + +std::string pqxx::connection_base::esc(const std::string &str) +{ + return this->esc(str.c_str(), str.size()); +} + + +std::string pqxx::connection_base::esc_raw( + const unsigned char str[], + size_t len) +{ + size_t bytes = 0; + // We need a connection object... This is the one reason why this function is + // not const! +#include <pqxx/internal/ignore-deprecated-pre.hxx> + activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + + std::unique_ptr<unsigned char, void (*)(unsigned char *)> buf{ + PQescapeByteaConn(m_conn, str, len, &bytes), + freepqmem_templated<unsigned char>}; + if (buf.get() == nullptr) throw std::bad_alloc{}; + return std::string{reinterpret_cast<char *>(buf.get())}; +} + + +std::string pqxx::connection_base::unesc_raw(const char *text) +{ + size_t len; + unsigned char *bytes = const_cast<unsigned char *>( + reinterpret_cast<const unsigned char *>(text)); + const std::unique_ptr<unsigned char, decltype(internal::freepqmem)*> ptr{ + PQunescapeBytea(bytes, &len), + internal::freepqmem}; + return std::string{ptr.get(), ptr.get() + len}; +} + + +std::string pqxx::connection_base::quote_raw( + const unsigned char str[], + size_t len) +{ + return "'" + esc_raw(str, len) + "'::bytea"; +} + + +std::string pqxx::connection_base::quote(const binarystring &b) +{ + return quote_raw(b.data(), b.size()); +} + + +std::string pqxx::connection_base::quote_name(const std::string &identifier) +{ + // We need a connection object... This is the one reason why this function is + // not const! +#include <pqxx/internal/ignore-deprecated-pre.hxx> + activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + std::unique_ptr<char, void (*)(char *)> buf{ + PQescapeIdentifier(m_conn, identifier.c_str(), identifier.size()), + freepqmem_templated<char>}; + if (buf.get() == nullptr) throw failure{err_msg()}; + return std::string{buf.get()}; +} + + +std::string pqxx::connection_base::esc_like( + const std::string &str, + char escape_char) const +{ + std::string out; + out.reserve(str.size()); + internal::for_glyphs( + internal::enc_group(encoding_id()), + [&out, escape_char](const char *gbegin, const char *gend) + { + if ((gend - gbegin == 1) and (*gbegin == '_' or *gbegin == '%')) + out.push_back(escape_char); + + for (; gbegin != gend; ++gbegin) out.push_back(*gbegin); + }, + str.c_str(), + str.size()); + return out; +} + + +pqxx::internal::reactivation_avoidance_exemption:: + reactivation_avoidance_exemption( + connection_base &C) : + m_home{C}, + m_count{gate::connection_reactivation_avoidance_exemption(C).get_counter()}, + m_open{C.is_open()} +{ + gate::connection_reactivation_avoidance_exemption gate{C}; + gate.clear_counter(); +} + + +pqxx::internal::reactivation_avoidance_exemption:: + ~reactivation_avoidance_exemption() +{ + // Don't leave the connection open if reactivation avoidance is in effect and + // the connection needed to be reactivated temporarily. + if (m_count and not m_open) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + m_home.deactivate(); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + gate::connection_reactivation_avoidance_exemption gate{m_home}; + gate.add_counter(m_count); +} + + +namespace +{ +#if defined(_WIN32) || defined(HAVE_POLL) +// Convert a timeval to milliseconds, or -1 if no timeval is given. +inline int tv_milliseconds(timeval *tv = nullptr) +{ + return tv ? int(tv->tv_sec * 1000 + tv->tv_usec/1000) : -1; +} +#endif + + +/// Wait for an fd to become free for reading/writing. Optional timeout. +void wait_fd(int fd, bool forwrite=false, timeval *tv=nullptr) +{ + if (fd < 0) throw pqxx::broken_connection{}; + +// WSAPoll is available in winsock2.h only for versions of Windows >= 0x0600 +#if defined(_WIN32) && (_WIN32_WINNT >= 0x0600) + const short events = (forwrite ? POLLWRNORM : POLLRDNORM); + WSAPOLLFD fdarray{SOCKET(fd), events, 0}; + WSAPoll(&fdarray, 1, tv_milliseconds(tv)); +#elif defined(HAVE_POLL) + const short events = short( + POLLERR|POLLHUP|POLLNVAL | (forwrite?POLLOUT:POLLIN)); + pollfd pfd{fd, events, 0}; + poll(&pfd, 1, tv_milliseconds(tv)); +#else + // No poll()? Our last option is select(). + fd_set read_fds; + FD_ZERO(&read_fds); + if (not forwrite) FD_SET(fd, &read_fds); + + fd_set write_fds; + FD_ZERO(&write_fds); + if (forwrite) FD_SET(fd, &write_fds); + + fd_set except_fds; + FD_ZERO(&except_fds); + FD_SET(fd, &except_fds); + + select(fd+1, &read_fds, &write_fds, &except_fds, tv); +#endif + + // No need to report errors. The caller will try to use the file + // descriptor right after we return, so if the file descriptor is broken, + // the caller will notice soon enough. +} +} // namespace + +void pqxx::internal::wait_read(const internal::pq::PGconn *c) +{ + wait_fd(socket_of(c)); +} + + +void pqxx::internal::wait_read( + const internal::pq::PGconn *c, + long seconds, + long microseconds) +{ + // These are really supposed to be time_t and suseconds_t. But not all + // platforms have that type; some use "long" instead, and some 64-bit + // systems use 32-bit integers here. So "int" seems to be the only really + // safe type to use. + timeval tv = { time_t(seconds), int(microseconds) }; + wait_fd(socket_of(c), false, &tv); +} + + +void pqxx::internal::wait_write(const internal::pq::PGconn *c) +{ + wait_fd(socket_of(c), true); +} + + +void pqxx::connection_base::wait_read() const +{ + internal::wait_read(m_conn); +} + + +void pqxx::connection_base::wait_read(long seconds, long microseconds) const +{ + internal::wait_read(m_conn, seconds, microseconds); +} + + +void pqxx::connection_base::wait_write() const +{ + internal::wait_write(m_conn); +} + + +int pqxx::connection_base::await_notification() +{ +#include <pqxx/internal/ignore-deprecated-pre.hxx> + activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + int notifs = get_notifs(); + if (notifs == 0) + { + wait_read(); + notifs = get_notifs(); + } + return notifs; +} + + +int pqxx::connection_base::await_notification(long seconds, long microseconds) +{ +#include <pqxx/internal/ignore-deprecated-pre.hxx> + activate(); +#include <pqxx/internal/ignore-deprecated-post.hxx> + int notifs = get_notifs(); + if (notifs == 0) + { + wait_read(seconds, microseconds); + notifs = get_notifs(); + } + return notifs; +} + + +void pqxx::connection_base::read_capabilities() +{ + m_serverversion = PQserverVersion(m_conn); + if (m_serverversion <= 90000) + throw feature_not_supported{ + "Unsupported server version; 9.0 is the minimum."}; + + switch (protocol_version()) { + case 0: + throw broken_connection{}; + case 1: + case 2: + throw feature_not_supported{ + "Unsupported frontend/backend protocol version; 3.0 is the minimum."}; + default: + break; + } + + // TODO: Check for capabilities here. Currently don't need any checks. +} + + +std::string pqxx::connection_base::adorn_name(const std::string &n) +{ + const std::string id = to_string(++m_unique_id); + return n.empty() ? ("x"+id) : (n+"_"+id); +} + + +std::string pqxx::connection_base::get_client_encoding() const +{ + return internal::name_encoding(encoding_id()); +} + + +void pqxx::connection_base::set_client_encoding(const char encoding[]) +{ + const auto retval = PQsetClientEncoding(m_conn, encoding); + switch (retval) + { + case 0: + // OK. + break; + case -1: + // TODO: Any helpful information we could give here? + throw failure{"Setting client encoding failed."}; + default: + throw internal_error{ + "Unexpected result from PQsetClientEncoding: " + to_string(retval)}; + } +} + + +void pqxx::connection_base::set_client_encoding(const std::string &encoding) +{ + set_client_encoding(encoding.c_str()); +} + + +int pqxx::connection_base::encoding_id() const +{ + const int enc = PQclientEncoding(m_conn); + if (enc == -1) + { + if (not is_open()) + throw broken_connection{ + "Could not obtain client encoding: not connected."}; + throw failure{"Could not obtain client encoding."}; + } + return enc; +} + + +pqxx::result pqxx::connection_base::parameterized_exec( + const std::string &query, + const char *const params[], + const int paramlengths[], + const int binaries[], + int nparams) +{ + auto r = make_result( + PQexecParams( + m_conn, + query.c_str(), + nparams, + nullptr, + params, + paramlengths, + binaries, + 0), + query); + check_result(r); + get_notifs(); + return r; +} + + +pqxx::result pqxx::connection_base::exec_params( + const std::string &query, + const internal::params &args) +{ + const auto pointers = args.get_pointers(); + const auto pq_result = PQexecParams( + m_conn, + query.c_str(), + int(args.nonnulls.size()), + nullptr, + pointers.data(), + args.lengths.data(), + args.binaries.data(), + 0); + const auto r = make_result(pq_result, query); + check_result(r); + get_notifs(); + return r; +} diff --git a/contrib/libs/libpqxx/src/cursor.cxx b/contrib/libs/libpqxx/src/cursor.cxx new file mode 100644 index 0000000000..8d2c7dfb25 --- /dev/null +++ b/contrib/libs/libpqxx/src/cursor.cxx @@ -0,0 +1,321 @@ +/** Implementation of libpqxx STL-style cursor classes. + * + * These classes wrap SQL cursors in STL-like interfaces. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <iterator> + +#include "pqxx/cursor" +#include "pqxx/result" +#include "pqxx/strconv" +#include "pqxx/transaction" + +#include "pqxx/internal/gates/icursor_iterator-icursorstream.hxx" +#include "pqxx/internal/gates/icursorstream-icursor_iterator.hxx" + +using namespace pqxx; +using namespace pqxx::internal; + + +pqxx::cursor_base::difference_type pqxx::cursor_base::all() noexcept +{ + // Implemented out-of-line so we don't fall afoul of Visual Studio defining + // min() and max() macros, which turn this expression into malformed code: + return std::numeric_limits<int>::max() - 1; +} + + +pqxx::cursor_base::difference_type cursor_base::backward_all() noexcept +{ + // Implemented out-of-line so we don't fall afoul of Visual Studio defining + // min() and max() macros, which turn this expression into malformed code: + return std::numeric_limits<int>::min() + 1; +} + + +pqxx::cursor_base::cursor_base( + connection_base &context, + const std::string &Name, + bool embellish_name) : + m_name{embellish_name ? context.adorn_name(Name) : Name} +{ +} + + +result::size_type pqxx::internal::obtain_stateless_cursor_size(sql_cursor &cur) +{ + if (cur.endpos() == -1) cur.move(cursor_base::all()); + return result::size_type(cur.endpos() - 1); +} + + +result pqxx::internal::stateless_cursor_retrieve( + sql_cursor &cur, + result::difference_type size, + result::difference_type begin_pos, + result::difference_type end_pos) +{ + if (begin_pos < 0 or begin_pos > size) + throw range_error{"Starting position out of range"}; + + if (end_pos < -1) end_pos = -1; + else if (end_pos > size) end_pos = size; + + if (begin_pos == end_pos) return cur.empty_result(); + + const int direction = ((begin_pos < end_pos) ? 1 : -1); + cur.move((begin_pos-direction) - (cur.pos()-1)); + return cur.fetch(end_pos - begin_pos); +} + + +pqxx::icursorstream::icursorstream( + transaction_base &context, + const std::string &query, + const std::string &basename, + difference_type sstride) : + m_cur{context, + query, + basename, + cursor_base::forward_only, + cursor_base::read_only, + cursor_base::owned, + false}, + m_stride{sstride}, + m_realpos{0}, + m_reqpos{0}, + m_iterators{nullptr}, + m_done{false} +{ + set_stride(sstride); +} + + +pqxx::icursorstream::icursorstream( + transaction_base &context, + const field &cname, + difference_type sstride, + cursor_base::ownershippolicy op) : + m_cur{context, cname.c_str(), op}, + m_stride{sstride}, + m_realpos{0}, + m_reqpos{0}, + m_iterators{nullptr}, + m_done{false} +{ + set_stride(sstride); +} + + +void pqxx::icursorstream::set_stride(difference_type n) +{ + if (n < 1) + throw argument_error{"Attempt to set cursor stride to " + to_string(n)}; + m_stride = n; +} + +result pqxx::icursorstream::fetchblock() +{ + const result r{m_cur.fetch(m_stride)}; + m_realpos += r.size(); + if (r.empty()) m_done = true; + return r; +} + + +icursorstream &pqxx::icursorstream::ignore(std::streamsize n) +{ + auto offset = m_cur.move(difference_type(n)); + m_realpos += offset; + if (offset < n) m_done = true; + return *this; +} + + +icursorstream::size_type pqxx::icursorstream::forward(size_type n) +{ + m_reqpos += difference_type(n) * m_stride; + return icursorstream::size_type(m_reqpos); +} + + +void pqxx::icursorstream::insert_iterator(icursor_iterator *i) noexcept +{ + gate::icursor_iterator_icursorstream{*i}.set_next(m_iterators); + if (m_iterators) + gate::icursor_iterator_icursorstream{*m_iterators}.set_prev(i); + m_iterators = i; +} + + +void pqxx::icursorstream::remove_iterator(icursor_iterator *i) const noexcept +{ + gate::icursor_iterator_icursorstream igate{*i}; + if (i == m_iterators) + { + m_iterators = igate.get_next(); + if (m_iterators) + gate::icursor_iterator_icursorstream{*m_iterators}.set_prev(nullptr); + } + else + { + auto prev = igate.get_prev(), next = igate.get_next(); + gate::icursor_iterator_icursorstream{*prev}.set_next(next); + if (next) gate::icursor_iterator_icursorstream{*next}.set_prev(prev); + } + igate.set_prev(nullptr); + igate.set_next(nullptr); +} + + +void pqxx::icursorstream::service_iterators(difference_type topos) +{ + if (topos < m_realpos) return; + + using todolist = std::multimap<difference_type,icursor_iterator*>; + todolist todo; + for (icursor_iterator *i = m_iterators, *next; i; i = next) + { + gate::icursor_iterator_icursorstream gate{*i}; + const auto ipos = gate.pos(); + if (ipos >= m_realpos and ipos <= topos) + todo.insert(todolist::value_type(ipos, i)); + next = gate.get_next(); + } + const auto todo_end = std::end(todo); + for (auto i = std::begin(todo); i != todo_end; ) + { + const auto readpos = i->first; + if (readpos > m_realpos) ignore(readpos - m_realpos); + const result r = fetchblock(); + for ( ; i != todo_end and i->first == readpos; ++i) + gate::icursor_iterator_icursorstream{*i->second}.fill(r); + } +} + + +pqxx::icursor_iterator::icursor_iterator() noexcept : + m_pos{0} +{ +} + + +pqxx::icursor_iterator::icursor_iterator(istream_type &s) noexcept : + m_stream{&s}, + m_pos{difference_type(gate::icursorstream_icursor_iterator(s).forward(0))} +{ + gate::icursorstream_icursor_iterator{*m_stream}.insert_iterator(this); +} + + +pqxx::icursor_iterator::icursor_iterator(const icursor_iterator &rhs) + noexcept : + m_stream{rhs.m_stream}, + m_here{rhs.m_here}, + m_pos{rhs.m_pos} +{ + if (m_stream) + gate::icursorstream_icursor_iterator{*m_stream}.insert_iterator(this); +} + + +pqxx::icursor_iterator::~icursor_iterator() noexcept +{ + if (m_stream) + gate::icursorstream_icursor_iterator{*m_stream}.remove_iterator(this); +} + + +icursor_iterator pqxx::icursor_iterator::operator++(int) +{ + icursor_iterator old{*this}; + m_pos = difference_type( + gate::icursorstream_icursor_iterator{*m_stream}.forward()); + m_here.clear(); + return old; +} + + +icursor_iterator &pqxx::icursor_iterator::operator++() +{ + m_pos = difference_type( + gate::icursorstream_icursor_iterator{*m_stream}.forward()); + m_here.clear(); + return *this; +} + + +icursor_iterator &pqxx::icursor_iterator::operator+=(difference_type n) +{ + if (n <= 0) + { + if (n == 0) return *this; + throw argument_error{"Advancing icursor_iterator by negative offset."}; + } + m_pos = difference_type( + gate::icursorstream_icursor_iterator{*m_stream}.forward( + icursorstream::size_type(n))); + m_here.clear(); + return *this; +} + + +icursor_iterator & +pqxx::icursor_iterator::operator=(const icursor_iterator &rhs) noexcept +{ + if (rhs.m_stream == m_stream) + { + m_here = rhs.m_here; + m_pos = rhs.m_pos; + } + else + { + if (m_stream) + gate::icursorstream_icursor_iterator{*m_stream}.remove_iterator(this); + m_here = rhs.m_here; + m_pos = rhs.m_pos; + m_stream = rhs.m_stream; + if (m_stream) + gate::icursorstream_icursor_iterator{*m_stream}.insert_iterator(this); + } + return *this; +} + + +bool pqxx::icursor_iterator::operator==(const icursor_iterator &rhs) const +{ + if (m_stream == rhs.m_stream) return pos() == rhs.pos(); + if (m_stream and rhs.m_stream) return false; + refresh(); + rhs.refresh(); + return m_here.empty() and rhs.m_here.empty(); +} + + +bool pqxx::icursor_iterator::operator<(const icursor_iterator &rhs) const +{ + if (m_stream == rhs.m_stream) return pos() < rhs.pos(); + refresh(); + rhs.refresh(); + return not m_here.empty(); +} + + +void pqxx::icursor_iterator::refresh() const +{ + if (m_stream) + gate::icursorstream_icursor_iterator{*m_stream}.service_iterators(pos()); +} + + +void pqxx::icursor_iterator::fill(const result &r) +{ + m_here = r; +} diff --git a/contrib/libs/libpqxx/src/dbtransaction.cxx b/contrib/libs/libpqxx/src/dbtransaction.cxx new file mode 100644 index 0000000000..9d0938a181 --- /dev/null +++ b/contrib/libs/libpqxx/src/dbtransaction.cxx @@ -0,0 +1,99 @@ +/** Implementation of the pqxx::dbtransaction class. + * + * pqxx::dbtransaction represents a real backend transaction. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/dbtransaction" + +#include "pqxx/internal/gates/connection-dbtransaction.hxx" + +using namespace pqxx::internal; + + +namespace +{ +std::string generate_set_transaction( + pqxx::readwrite_policy rw, + const std::string &IsolationString=std::string{}) +{ + std::string args; + + if (not IsolationString.empty()) + if (IsolationString != pqxx::isolation_traits<pqxx::read_committed>::name()) + args += " ISOLATION LEVEL " + IsolationString; + + if (rw != pqxx::read_write) args += " READ ONLY"; + + return args.empty() ? "BEGIN" : ("BEGIN; SET TRANSACTION" + args); +} +} // namespace + + +pqxx::dbtransaction::dbtransaction( + connection_base &C, + const std::string &IsolationString, + readwrite_policy rw) : + namedclass{"dbtransaction"}, + transaction_base{C}, + m_start_cmd{generate_set_transaction(rw, IsolationString)} +{ +} + + +pqxx::dbtransaction::dbtransaction( + connection_base &C, + bool direct, + readwrite_policy rw) : + namedclass{"dbtransaction"}, + transaction_base(C, direct), + m_start_cmd{generate_set_transaction(rw)} +{ +} + + +pqxx::dbtransaction::~dbtransaction() +{ +} + + +void pqxx::dbtransaction::do_begin() +{ + const gate::connection_dbtransaction gate(conn()); + const int avoidance_counter = gate.get_reactivation_avoidance_count(); + direct_exec(m_start_cmd.c_str(), avoidance_counter ? 0 : 2); +} + + +pqxx::result pqxx::dbtransaction::do_exec(const char Query[]) +{ + try + { + return direct_exec(Query); + } + catch (const std::exception &) + { + try { abort(); } catch (const std::exception &) {} + throw; + } +} + + +void pqxx::dbtransaction::do_abort() +{ + reactivation_avoidance_clear(); + direct_exec("ROLLBACK"); +} + + +std::string pqxx::dbtransaction::fullname(const std::string &ttype, + const std::string &isolation) +{ + return ttype + "<" + isolation + ">"; +} diff --git a/contrib/libs/libpqxx/src/encodings.cxx b/contrib/libs/libpqxx/src/encodings.cxx new file mode 100644 index 0000000000..7102c891c4 --- /dev/null +++ b/contrib/libs/libpqxx/src/encodings.cxx @@ -0,0 +1,826 @@ +/** Implementation of string encodings support + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/except.hxx" +#include "pqxx/internal/encodings.hxx" + +#include <cstring> +#include <iomanip> +#include <map> +#include <sstream> + +using namespace pqxx::internal; + +extern "C" +{ +#include "libpq-fe.h" +} + + +// Internal helper functions +namespace +{ +/// Extract byte from buffer, return as unsigned char. +unsigned char get_byte(const char buffer[], std::string::size_type offset) +{ + return static_cast<unsigned char>(buffer[offset]); +} + + +[[noreturn]] void throw_for_encoding_error( + const char* encoding_name, + const char buffer[], + std::string::size_type start, + std::string::size_type count +) +{ + std::stringstream s; + s + << "Invalid byte sequence for encoding " + << encoding_name + << " at byte " + << start + << ": " + << std::hex + << std::setw(2) + << std::setfill('0') + ; + for (std::string::size_type i{0}; i < count; ++i) + { + s << "0x" << static_cast<unsigned int>(get_byte(buffer, start + i)); + if (i + 1 < count) s << " "; + } + throw pqxx::argument_error{s.str()}; +} + + +/// Does value lie between bottom and top, inclusive? +constexpr bool between_inc(unsigned char value, unsigned bottom, unsigned top) +{ + return value >= bottom and value <= top; +} + + +/* +EUC-JP and EUC-JIS-2004 represent slightly different code points but iterate +the same: + * https://en.wikipedia.org/wiki/Extended_Unix_Code#EUC-JP + * http://x0213.org/codetable/index.en.html +*/ +std::string::size_type next_seq_for_euc_jplike( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start, + const char encoding_name[]) +{ + if (start >= buffer_len) return std::string::npos; + + const auto byte1 = get_byte(buffer, start); + if (byte1 < 0x80) return start + 1; + + if (start + 2 > buffer_len) + throw_for_encoding_error(encoding_name, buffer, start, 1); + + const auto byte2 = get_byte(buffer, start + 1); + if (byte1 == 0x8e) + { + if (not between_inc(byte2, 0xa1, 0xfe)) + throw_for_encoding_error(encoding_name, buffer, start, 2); + + return start + 2; + } + + if (between_inc(byte1, 0xa1, 0xfe)) + { + if (not between_inc(byte2, 0xa1, 0xfe)) + throw_for_encoding_error(encoding_name, buffer, start, 2); + + return start + 2; + } + + if (byte1 == 0x8f and start + 3 <= buffer_len) + { + const auto byte3 = get_byte(buffer, start + 2); + if ( + not between_inc(byte2, 0xa1, 0xfe) or + not between_inc(byte3, 0xa1, 0xfe) + ) + throw_for_encoding_error(encoding_name, buffer, start, 3); + + return start + 3; + } + + throw_for_encoding_error(encoding_name, buffer, start, 1); +} + +/* +As far as I can tell, for the purposes of iterating the only difference between +SJIS and SJIS-2004 is increased range in the first byte of two-byte sequences +(0xEF increased to 0xFC). Officially, that is; apparently the version of SJIS +used by Postgres has the same range as SJIS-2004. They both have increased +range over the documented versions, not having the even/odd restriction for the +first byte in 2-byte sequences. +*/ +// https://en.wikipedia.org/wiki/Shift_JIS#Shift_JIS_byte_map +// http://x0213.org/codetable/index.en.html +std::string::size_type next_seq_for_sjislike( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start, + const char* encoding_name +) +{ + if (start >= buffer_len) return std::string::npos; + + const auto byte1 = get_byte(buffer, start); + if (byte1 < 0x80 or between_inc(byte1, 0xa1, 0xdf)) return start + 1; + + if ( + not between_inc(byte1, 0x81, 0x9f) and + not between_inc(byte1, 0xe0, 0xfc) + ) + throw_for_encoding_error(encoding_name, buffer, start, 1); + + if (start + 2 > buffer_len) + throw_for_encoding_error( + encoding_name, + buffer, + start, + buffer_len - start); + + const auto byte2 = get_byte(buffer, start + 1); + if (byte2 == 0x7f) throw_for_encoding_error(encoding_name, buffer, start, 2); + + if (between_inc(byte2, 0x40, 0x9e) or between_inc(byte2, 0x9f, 0xfc)) + return start + 2; + + throw_for_encoding_error(encoding_name, buffer, start, 2); +} +} // namespace + + +// Implement template specializations first +namespace pqxx +{ +namespace internal +{ +template<encoding_group> struct glyph_scanner +{ + static std::string::size_type call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start); +}; + +template<> +std::string::size_type glyph_scanner<encoding_group::MONOBYTE>::call( + const char /* buffer */[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + if (start >= buffer_len) return std::string::npos; + else return start + 1; +} + +// https://en.wikipedia.org/wiki/Big5#Organization +template<> std::string::size_type glyph_scanner<encoding_group::BIG5>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + if (start >= buffer_len) return std::string::npos; + + const auto byte1 = get_byte(buffer, start); + if (byte1 < 0x80) return start + 1; + + if (not between_inc(byte1, 0x81, 0xfe) or (start + 2 > buffer_len)) + throw_for_encoding_error("BIG5", buffer, start, 1); + + const auto byte2 = get_byte(buffer, start + 1); + if ( + not between_inc(byte2, 0x40, 0x7e) and + not between_inc(byte2, 0xa1, 0xfe)) + throw_for_encoding_error("BIG5", buffer, start, 2); + + return start + 2; +} + +/* +The PostgreSQL documentation claims that the EUC_* encodings are 1-3 bytes each, +but other documents explain that the EUC sets can contain 1-(2,3,4) bytes +depending on the specific extension: + EUC_CN : 1-2 + EUC_JP : 1-3 + EUC_JIS_2004: 1-2 + EUC_KR : 1-2 + EUC_TW : 1-4 +*/ + +// https://en.wikipedia.org/wiki/GB_2312#EUC-CN +template<> std::string::size_type glyph_scanner<encoding_group::EUC_CN>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + if (start >= buffer_len) return std::string::npos; + + const auto byte1 = get_byte(buffer, start); + if (byte1 < 0x80) return start + 1; + + if (not between_inc(byte1, 0xa1, 0xf7) or start + 2 > buffer_len) + throw_for_encoding_error("EUC_CN", buffer, start, 1); + + const auto byte2 = get_byte(buffer, start + 1); + if (not between_inc(byte2, 0xa1, 0xfe)) + throw_for_encoding_error("EUC_CN", buffer, start, 2); + + return start + 2; +} + +template<> std::string::size_type glyph_scanner<encoding_group::EUC_JP>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + return next_seq_for_euc_jplike(buffer, buffer_len, start, "EUC_JP"); +} + +template<> +std::string::size_type glyph_scanner<encoding_group::EUC_JIS_2004>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + return next_seq_for_euc_jplike(buffer, buffer_len, start, "EUC_JIS_2004"); +} + +// https://en.wikipedia.org/wiki/Extended_Unix_Code#EUC-KR +template<> std::string::size_type glyph_scanner<encoding_group::EUC_KR>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + if (start >= buffer_len) return std::string::npos; + + const auto byte1 = get_byte(buffer, start); + if (byte1 < 0x80) return start + 1; + + if (not between_inc(byte1, 0xa1, 0xfe) or start + 2 > buffer_len) + throw_for_encoding_error("EUC_KR", buffer, start, 1); + + const auto byte2 = get_byte(buffer, start + 1); + if (not between_inc(byte2, 0xa1, 0xfe)) + throw_for_encoding_error("EUC_KR", buffer, start, 1); + + return start + 2; +} + +// https://en.wikipedia.org/wiki/Extended_Unix_Code#EUC-TW +template<> std::string::size_type glyph_scanner<encoding_group::EUC_TW>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + if (start >= buffer_len) return std::string::npos; + + const auto byte1 = get_byte(buffer, start); + if (byte1 < 0x80) return start + 1; + + if (start + 2 > buffer_len) + throw_for_encoding_error("EUC_KR", buffer, start, 1); + + const auto byte2 = get_byte(buffer, start + 1); + if (between_inc(byte1, 0xa1, 0xfe)) + { + if (not between_inc(byte2, 0xa1, 0xfe)) + throw_for_encoding_error("EUC_KR", buffer, start, 2); + + return start + 2; + } + + if (byte1 != 0x8e or start + 4 > buffer_len) + throw_for_encoding_error("EUC_KR", buffer, start, 1); + + if ( + between_inc(byte2, 0xa1, 0xb0) and + between_inc(get_byte(buffer, start + 2), 0xa1, 0xfe) and + between_inc(get_byte(buffer, start + 3), 0xa1, 0xfe) + ) + return start + 4; + + throw_for_encoding_error("EUC_KR", buffer, start, 4); +} + +// https://en.wikipedia.org/wiki/GB_18030#Mapping +template<> std::string::size_type glyph_scanner<encoding_group::GB18030>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + if (start >= buffer_len) return std::string::npos; + + const auto byte1 = get_byte(buffer, start); + if (between_inc(byte1, 0x80, 0xff)) return start + 1; + + if (start + 2 > buffer_len) + throw_for_encoding_error("GB18030", buffer, start, buffer_len - start); + + const auto byte2 = get_byte(buffer, start + 1); + if (between_inc(byte2, 0x40, 0xfe)) + { + if (byte2 == 0x7f) + throw_for_encoding_error("GB18030", buffer, start, 2); + + return start + 2; + } + + if (start + 4 > buffer_len) + throw_for_encoding_error("GB18030", buffer, start, buffer_len - start); + + if ( + between_inc(byte2, 0x30, 0x39) and + between_inc(get_byte(buffer, start + 2), 0x81, 0xfe) and + between_inc(get_byte(buffer, start + 3), 0x30, 0x39) + ) + return start + 4; + + throw_for_encoding_error("GB18030", buffer, start, 4); +} + +// https://en.wikipedia.org/wiki/GBK_(character_encoding)#Encoding +template<> std::string::size_type glyph_scanner<encoding_group::GBK>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + if (start >= buffer_len) return std::string::npos; + + const auto byte1 = get_byte(buffer, start); + if (byte1 < 0x80) return start + 1; + + if (start + 2 > buffer_len) + throw_for_encoding_error("GBK", buffer, start, 1); + + const auto byte2 = get_byte(buffer, start + 1); + if ( + (between_inc(byte1, 0xa1, 0xa9) and between_inc(byte2, 0xa1, 0xfe)) + or + (between_inc(byte1, 0xb0, 0xf7) and between_inc(byte2, 0xa1, 0xfe)) + or + ( + between_inc(byte1, 0x81, 0xa0) and + between_inc(byte2, 0x40, 0xfe) and + byte2 != 0x7f + ) + or + ( + between_inc(byte1, 0xaa, 0xfe) and + between_inc(byte2, 0x40, 0xa0) and + byte2 != 0x7f + ) + or + ( + between_inc(byte1, 0xa8, 0xa9) and + between_inc(byte2, 0x40, 0xa0) and + byte2 != 0x7f + ) + or + (between_inc(byte1, 0xaa, 0xaf) and between_inc(byte2, 0xa1, 0xfe)) + or + (between_inc(byte1, 0xf8, 0xfe) and between_inc(byte2, 0xa1, 0xfe)) + or + ( + between_inc(byte1, 0xa1, 0xa7) and + between_inc(byte2, 0x40, 0xa0) and + byte2 != 0x7f + ) + ) + return start + 2; + + throw_for_encoding_error("GBK", buffer, start, 2); +} + +/* +The PostgreSQL documentation claims that the JOHAB encoding is 1-3 bytes, but +"CJKV Information Processing" describes it (actually just the Hangul portion) +as "three five-bit segments" that reside inside 16 bits (2 bytes). + +CJKV Information Processing by Ken Lunde, pg. 269: + + https://bit.ly/2BEOu5V +*/ +template<> std::string::size_type glyph_scanner<encoding_group::JOHAB>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + if (start >= buffer_len) return std::string::npos; + + const auto byte1 = get_byte(buffer, start); + if (byte1 < 0x80) return start + 1; + + if (start + 2 > buffer_len) + throw_for_encoding_error("JOHAB", buffer, start, 1); + + const auto byte2 = get_byte(buffer, start); + if ( + ( + between_inc(byte1, 0x84, 0xd3) and + (between_inc(byte2, 0x41, 0x7e) or between_inc(byte2, 0x81, 0xfe)) + ) + or + ( + (between_inc(byte1, 0xd8, 0xde) or between_inc(byte1, 0xe0, 0xf9)) and + (between_inc(byte2, 0x31, 0x7e) or between_inc(byte2, 0x91, 0xfe)) + ) + ) + return start + 2; + + throw_for_encoding_error("JOHAB", buffer, start, 2); +} + +/* +PostgreSQL's MULE_INTERNAL is the emacs rather than Xemacs implementation; +see the server/mb/pg_wchar.h PostgreSQL header file. +This is implemented according to the description in said header file, but I was +unable to get it to successfully iterate a MULE-encoded test CSV generated using +PostgreSQL 9.2.23. Use this at your own risk. +*/ +template<> +std::string::size_type glyph_scanner<encoding_group::MULE_INTERNAL>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + if (start >= buffer_len) return std::string::npos; + + const auto byte1 = get_byte(buffer, start); + if (byte1 < 0x80) return start + 1; + + if (start + 2 > buffer_len) + throw_for_encoding_error("MULE_INTERNAL", buffer, start, 1); + + const auto byte2 = get_byte(buffer, start + 1); + if (between_inc(byte1, 0x81, 0x8d) and byte2 >= 0xA0) + return start + 2; + + if (start + 3 > buffer_len) + throw_for_encoding_error("MULE_INTERNAL", buffer, start, 2); + + if ( + ( + (byte1 == 0x9A and between_inc(byte2, 0xa0, 0xdf)) or + (byte1 == 0x9B and between_inc(byte2, 0xe0, 0xef)) or + (between_inc(byte1, 0x90, 0x99) and byte2 >= 0xa0) + ) + and + ( + byte2 >= 0xA0 + ) + ) + return start + 3; + + if (start + 4 > buffer_len) + throw_for_encoding_error("MULE_INTERNAL", buffer, start, 3); + + if ( + ( + (byte1 == 0x9C and between_inc(byte2, 0xf0, 0xf4)) or + (byte1 == 0x9D and between_inc(byte2, 0xf5, 0xfe)) + ) + and + get_byte(buffer, start + 2) >= 0xa0 and + get_byte(buffer, start + 4) >= 0xa0 + ) + return start + 4; + + throw_for_encoding_error("MULE_INTERNAL", buffer, start, 4); +} + +template<> std::string::size_type glyph_scanner<encoding_group::SJIS>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + return next_seq_for_sjislike(buffer, buffer_len, start, "SJIS"); +} + +template<> +std::string::size_type glyph_scanner<encoding_group::SHIFT_JIS_2004>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + return next_seq_for_sjislike(buffer, buffer_len, start, "SHIFT_JIS_2004"); +} + +// https://en.wikipedia.org/wiki/Unified_Hangul_Code +template<> std::string::size_type glyph_scanner<encoding_group::UHC>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + if (start >= buffer_len) return std::string::npos; + + const auto byte1 = get_byte(buffer, start); + if (byte1 < 0x80) return start + 1; + + if (start + 2 > buffer_len) + throw_for_encoding_error("UHC", buffer, start, buffer_len - start); + + const auto byte2 = get_byte(buffer, start + 1); + if (between_inc(byte1, 0x80, 0xc6)) + { + if ( + between_inc(byte2, 0x41, 0x5a) or + between_inc(byte2, 0x61, 0x7a) or + between_inc(byte2, 0x80, 0xfe) + ) + return start + 2; + + throw_for_encoding_error("UHC", buffer, start, 2); + } + + if (between_inc(byte1, 0xa1, 0xfe)) + { + if (not between_inc(byte2, 0xa1, 0xfe)) + throw_for_encoding_error("UHC", buffer, start, 2); + + return start + 2; + } + + throw_for_encoding_error("UHC", buffer, start, 1); +} + +// https://en.wikipedia.org/wiki/UTF-8#Description +template<> std::string::size_type glyph_scanner<encoding_group::UTF8>::call( + const char buffer[], + std::string::size_type buffer_len, + std::string::size_type start +) +{ + if (start >= buffer_len) return std::string::npos; + + const auto byte1 = get_byte(buffer, start); + if (byte1 < 0x80) return start + 1; + + if (start + 2 > buffer_len) + throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); + + const auto byte2 = get_byte(buffer, start + 1); + if (between_inc(byte1, 0xc0, 0xdf)) + { + if (not between_inc(byte2, 0x80, 0xbf)) + throw_for_encoding_error("UTF8", buffer, start, 2); + + return start + 2; + } + + if (start + 3 > buffer_len) + throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); + + const auto byte3 = get_byte(buffer, start + 2); + if (between_inc(byte1, 0xe0, 0xef)) + { + if (between_inc(byte2, 0x80, 0xbf) and between_inc(byte3, 0x80, 0xbf)) + return start + 3; + + throw_for_encoding_error("UTF8", buffer, start, 3); + } + + if (start + 4 > buffer_len) + throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); + + if (between_inc(byte1, 0xf0, 0xf7)) + { + if ( + between_inc(byte2, 0x80, 0xbf) and + between_inc(byte3, 0x80, 0xbf) and + between_inc(get_byte(buffer, start + 3), 0x80, 0xbf) + ) + return start + 4; + + throw_for_encoding_error("UTF8", buffer, start, 4); + } + + throw_for_encoding_error("UTF8", buffer, start, 1); +} + + +const char *name_encoding(int encoding_id) +{ + return pg_encoding_to_char(encoding_id); +} + + +encoding_group enc_group(int libpq_enc_id) +{ + return enc_group(name_encoding(libpq_enc_id)); +} + + +encoding_group enc_group(const std::string& encoding_name) +{ + static const std::map<std::string, encoding_group> encoding_map{ + {"BIG5", encoding_group::BIG5}, + {"EUC_CN", encoding_group::EUC_CN}, + {"EUC_JP", encoding_group::EUC_JP}, + {"EUC_JIS_2004", encoding_group::EUC_JIS_2004}, + {"EUC_KR", encoding_group::EUC_KR}, + {"EUC_TW", encoding_group::EUC_TW}, + {"GB18030", encoding_group::GB18030}, + {"GBK", encoding_group::GBK}, + {"ISO_8859_5", encoding_group::MONOBYTE}, + {"ISO_8859_6", encoding_group::MONOBYTE}, + {"ISO_8859_7", encoding_group::MONOBYTE}, + {"ISO_8859_8", encoding_group::MONOBYTE}, + {"JOHAB", encoding_group::JOHAB}, + {"KOI8R", encoding_group::MONOBYTE}, + {"KOI8U", encoding_group::MONOBYTE}, + {"LATIN1", encoding_group::MONOBYTE}, + {"LATIN2", encoding_group::MONOBYTE}, + {"LATIN3", encoding_group::MONOBYTE}, + {"LATIN4", encoding_group::MONOBYTE}, + {"LATIN5", encoding_group::MONOBYTE}, + {"LATIN6", encoding_group::MONOBYTE}, + {"LATIN7", encoding_group::MONOBYTE}, + {"LATIN8", encoding_group::MONOBYTE}, + {"LATIN9", encoding_group::MONOBYTE}, + {"LATIN10", encoding_group::MONOBYTE}, + {"MULE_INTERNAL", encoding_group::MULE_INTERNAL}, + {"SJIS", encoding_group::SJIS}, + {"SHIFT_JIS_2004", encoding_group::SHIFT_JIS_2004}, + {"SQL_ASCII", encoding_group::MONOBYTE}, + {"UHC", encoding_group::UHC}, + {"UTF8", encoding_group::UTF8}, + {"WIN866", encoding_group::MONOBYTE}, + {"WIN874", encoding_group::MONOBYTE}, + {"WIN1250", encoding_group::MONOBYTE}, + {"WIN1251", encoding_group::MONOBYTE}, + {"WIN1252", encoding_group::MONOBYTE}, + {"WIN1253", encoding_group::MONOBYTE}, + {"WIN1254", encoding_group::MONOBYTE}, + {"WIN1255", encoding_group::MONOBYTE}, + {"WIN1256", encoding_group::MONOBYTE}, + {"WIN1257", encoding_group::MONOBYTE}, + {"WIN1258", encoding_group::MONOBYTE}, + }; + + const auto found_encoding_group = encoding_map.find(encoding_name); + if (found_encoding_group == encoding_map.end()) + throw std::invalid_argument{ + "unrecognized encoding '" + encoding_name + "'" + }; + return found_encoding_group->second; +} + + +/// Look up instantiation @c T<enc>::call at runtime. +/** Here, "T" is a struct template with a static member function "call", whose + * type is "F". + * + * The return value is a pointer to the "call" member function for the + * instantiation of T for encoding group enc. + */ +template<template<encoding_group> class T, typename F> +inline F *for_encoding(encoding_group enc) +{ + +#define CASE_GROUP(ENC) \ + case encoding_group::ENC: return T<encoding_group::ENC>::call + + switch (enc) + { + CASE_GROUP(MONOBYTE); + CASE_GROUP(BIG5); + CASE_GROUP(EUC_CN); + CASE_GROUP(EUC_JP); + CASE_GROUP(EUC_JIS_2004); + CASE_GROUP(EUC_KR); + CASE_GROUP(EUC_TW); + CASE_GROUP(GB18030); + CASE_GROUP(GBK); + CASE_GROUP(JOHAB); + CASE_GROUP(MULE_INTERNAL); + CASE_GROUP(SJIS); + CASE_GROUP(SHIFT_JIS_2004); + CASE_GROUP(UHC); + CASE_GROUP(UTF8); + } + throw pqxx::usage_error{ + "Unsupported encoding group code " + to_string(int(enc)) + "."}; + +#undef CASE_GROUP +} + + +glyph_scanner_func *get_glyph_scanner(encoding_group enc) +{ + return for_encoding<glyph_scanner, glyph_scanner_func>(enc); +} + + +template<encoding_group E> struct char_finder +{ + static std::string::size_type call( + const std::string &haystack, + char needle, + std::string::size_type start) + { + const auto buffer = haystack.c_str(); + const auto size = haystack.size(); + for ( + auto here = start; + here + 1 <= size; + here = glyph_scanner<E>::call(buffer, size, here) + ) + { + if (haystack[here] == needle) return here; + } + return std::string::npos; + } +}; + + +template<encoding_group E> struct string_finder +{ + static std::string::size_type call( + const std::string &haystack, + const std::string &needle, + std::string::size_type start) + { + const auto buffer = haystack.c_str(); + const auto size = haystack.size(); + const auto needle_size = needle.size(); + for ( + auto here = start; + here + needle_size <= size; + here = glyph_scanner<E>::call(buffer, size, here) + ) + { + if (std::memcmp(buffer + here, needle.c_str(), needle_size) == 0) + return here; + } + return std::string::npos; + } +}; + + +std::string::size_type find_with_encoding( + encoding_group enc, + const std::string& haystack, + char needle, + std::string::size_type start +) +{ + using finder_func = + std::string::size_type( + const std::string &haystack, + char needle, + std::string::size_type start); + const auto finder = for_encoding<char_finder, finder_func>(enc); + return finder(haystack, needle, start); +} + + +std::string::size_type find_with_encoding( + encoding_group enc, + const std::string& haystack, + const std::string& needle, + std::string::size_type start +) +{ + using finder_func = + std::string::size_type( + const std::string &haystack, + const std::string &needle, + std::string::size_type start); + const auto finder = for_encoding<string_finder, finder_func>(enc); + return finder(haystack, needle, start); +} + +#undef DISPATCH_ENCODING_OPERATION + +} // namespace pqxx::internal +} // namespace pqxx diff --git a/contrib/libs/libpqxx/src/errorhandler.cxx b/contrib/libs/libpqxx/src/errorhandler.cxx new file mode 100644 index 0000000000..f561746f9e --- /dev/null +++ b/contrib/libs/libpqxx/src/errorhandler.cxx @@ -0,0 +1,44 @@ +/** Implementation of pqxx::errorhandler and helpers. + * + * pqxx::errorhandler allows programs to receive errors and warnings. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/connection_base" +#include "pqxx/errorhandler" + +#include "pqxx/internal/gates/connection-errorhandler.hxx" + + +using namespace pqxx; +using namespace pqxx::internal; + + +pqxx::errorhandler::errorhandler(connection_base &conn) : + m_home{&conn} +{ + gate::connection_errorhandler{*m_home}.register_errorhandler(this); +} + + +pqxx::errorhandler::~errorhandler() +{ + unregister(); +} + + +void pqxx::errorhandler::unregister() noexcept +{ + if (m_home != nullptr) + { + gate::connection_errorhandler connection_gate{*m_home}; + m_home = nullptr; + connection_gate.unregister_errorhandler(this); + } +} diff --git a/contrib/libs/libpqxx/src/except.cxx b/contrib/libs/libpqxx/src/except.cxx new file mode 100644 index 0000000000..9dcc8a8201 --- /dev/null +++ b/contrib/libs/libpqxx/src/except.cxx @@ -0,0 +1,124 @@ +/** Implementation of libpqxx exception classes. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/except" + + +pqxx::pqxx_exception::~pqxx_exception() noexcept +{ +} + + +pqxx::failure::failure(const std::string &whatarg) : + std::runtime_error{whatarg} +{ +} + + +pqxx::broken_connection::broken_connection() : + failure{"Connection to database failed"} +{ +} + + +pqxx::broken_connection::broken_connection(const std::string &whatarg) : + failure{whatarg} +{ +} + + +pqxx::sql_error::sql_error( + const std::string &whatarg, + const std::string &Q, + const char sqlstate[]) : + failure{whatarg}, + m_query{Q}, + m_sqlstate{sqlstate ? sqlstate : ""} +{ +} + + +pqxx::sql_error::~sql_error() noexcept +{ +} + + +PQXX_PURE const std::string &pqxx::sql_error::query() const noexcept +{ + return m_query; +} + + +PQXX_PURE const std::string &pqxx::sql_error::sqlstate() const noexcept +{ + return m_sqlstate; +} + + +pqxx::in_doubt_error::in_doubt_error(const std::string &whatarg) : + failure{whatarg} +{ +} + + +pqxx::transaction_rollback::transaction_rollback(const std::string &whatarg) : + failure{whatarg} +{ +} + + +pqxx::serialization_failure::serialization_failure( + const std::string &whatarg) : + transaction_rollback{whatarg} +{ +} + + +pqxx::statement_completion_unknown::statement_completion_unknown( + const std::string &whatarg) : + transaction_rollback{whatarg} +{ +} + + +pqxx::deadlock_detected::deadlock_detected(const std::string &whatarg) : + transaction_rollback{whatarg} +{ +} + + +pqxx::internal_error::internal_error(const std::string &whatarg) : + logic_error{"libpqxx internal error: " + whatarg} +{ +} + + +pqxx::usage_error::usage_error(const std::string &whatarg) : + logic_error{whatarg} +{ +} + + +pqxx::argument_error::argument_error(const std::string &whatarg) : + invalid_argument{whatarg} +{ +} + + +pqxx::conversion_error::conversion_error(const std::string &whatarg) : + domain_error{whatarg} +{ +} + + +pqxx::range_error::range_error(const std::string &whatarg) : + out_of_range{whatarg} +{ +} diff --git a/contrib/libs/libpqxx/src/field.cxx b/contrib/libs/libpqxx/src/field.cxx new file mode 100644 index 0000000000..10f0f69e2b --- /dev/null +++ b/contrib/libs/libpqxx/src/field.cxx @@ -0,0 +1,77 @@ +/** Implementation of the pqxx::field class. + * + * pqxx::field refers to a field in a query result. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <cstring> + +#include "pqxx/internal/libpq-forward.hxx" + +#include "pqxx/result" + + +pqxx::field::field(const pqxx::row &R, pqxx::row::size_type C) noexcept : + m_col{static_cast<decltype(m_col)>(C)}, + m_home{R.m_result}, + m_row{pqxx::result_size_type(R.m_index)} +{ +} + + +bool pqxx::field::operator==(const field &rhs) const +{ + if (is_null() != rhs.is_null()) return false; + // TODO: Verify null handling decision + const size_type s = size(); + if (s != rhs.size()) return false; + return std::memcmp(c_str(), rhs.c_str(), s) == 0; +} + + +const char *pqxx::field::name() const +{ + return home().column_name(col()); +} + + +pqxx::oid pqxx::field::type() const +{ + return home().column_type(col()); +} + + +pqxx::oid pqxx::field::table() const +{ + return home().column_table(col()); +} + + +pqxx::row::size_type pqxx::field::table_column() const +{ + return home().table_column(col()); +} + + +const char *pqxx::field::c_str() const +{ + return home().GetValue(idx(), col()); +} + + +bool pqxx::field::is_null() const noexcept +{ + return home().get_is_null(idx(), col()); +} + + +pqxx::field::size_type pqxx::field::size() const noexcept +{ + return home().get_length(idx(), col()); +} diff --git a/contrib/libs/libpqxx/src/largeobject.cxx b/contrib/libs/libpqxx/src/largeobject.cxx new file mode 100644 index 0000000000..8020a2fc04 --- /dev/null +++ b/contrib/libs/libpqxx/src/largeobject.cxx @@ -0,0 +1,313 @@ +/** Implementation of the Large Objects interface. + * + * Allows direct access to large objects, as well as though I/O streams. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <algorithm> +#include <cerrno> +#include <stdexcept> + +extern "C" +{ +#include "libpq-fe.h" +} + +#include "pqxx/largeobject" + +#include "pqxx/internal/gates/connection-largeobject.hxx" + + +using namespace pqxx::internal; + +namespace +{ +inline int StdModeToPQMode(std::ios::openmode mode) +{ + /// Mode bits, copied from libpq-fs.h so that we no longer need that header. + constexpr int + INV_WRITE = 0x00020000, + INV_READ = 0x00040000; + + return + ((mode & std::ios::in) ? INV_READ : 0) | + ((mode & std::ios::out) ? INV_WRITE : 0); +} + + +inline int StdDirToPQDir(std::ios::seekdir dir) noexcept +{ + // TODO: Figure out whether seekdir values match C counterparts! +#ifdef PQXX_SEEKDIRS_MATCH_C + return dir; +#else + int pqdir; + switch (dir) + { + case std::ios::beg: pqdir=SEEK_SET; break; + case std::ios::cur: pqdir=SEEK_CUR; break; + case std::ios::end: pqdir=SEEK_END; break; + + /* Added mostly to silence compiler warning, but also to help compiler detect + * cases where this function can be optimized away completely. This latter + * reason should go away as soon as PQXX_SEEKDIRS_MATCH_C works. + */ + default: pqdir = dir; break; + } + return pqdir; +#endif +} + + +} // namespace + + +pqxx::largeobject::largeobject(dbtransaction &T) : + m_id{} +{ + // (Mode is ignored as of postgres 8.1.) + m_id = lo_creat(raw_connection(T), 0); + if (m_id == oid_none) + { + const int err = errno; + if (err == ENOMEM) throw std::bad_alloc{}; + throw failure{"Could not create large object: " + reason(T.conn(), err)}; + } +} + + +pqxx::largeobject::largeobject(dbtransaction &T, const std::string &File) : + m_id{} +{ + m_id = lo_import(raw_connection(T), File.c_str()); + if (m_id == oid_none) + { + const int err = errno; + if (err == ENOMEM) throw std::bad_alloc{}; + throw failure{ + "Could not import file '" + File + "' to large object: " + + reason(T.conn(), err)}; + } +} + + +pqxx::largeobject::largeobject(const largeobjectaccess &O) noexcept : + m_id{O.id()} +{ +} + + +void pqxx::largeobject::to_file( + dbtransaction &T, + const std::string &File) const +{ + if (lo_export(raw_connection(T), id(), File.c_str()) == -1) + { + const int err = errno; + if (err == ENOMEM) throw std::bad_alloc{}; + throw failure{ + "Could not export large object " + to_string(m_id) + " " + "to file '" + File + "': " + reason(T.conn(), err)}; + } +} + + +void pqxx::largeobject::remove(dbtransaction &T) const +{ + if (lo_unlink(raw_connection(T), id()) == -1) + { + const int err = errno; + if (err == ENOMEM) throw std::bad_alloc{}; + throw failure{ + "Could not delete large object " + to_string(m_id) + ": " + + reason(T.conn(), err)}; + } +} + + +pqxx::internal::pq::PGconn *pqxx::largeobject::raw_connection( + const dbtransaction &T) +{ + return gate::connection_largeobject{T.conn()}.raw_connection(); +} + + +std::string pqxx::largeobject::reason(const connection_base &c, int err) const +{ + if (err == ENOMEM) return "Out of memory"; + if (id() == oid_none) return "No object selected"; + return gate::const_connection_largeobject{c}.error_message(); +} + + +pqxx::largeobjectaccess::largeobjectaccess(dbtransaction &T, openmode mode) : + largeobject{T}, + m_trans{T} +{ + open(mode); +} + + +pqxx::largeobjectaccess::largeobjectaccess( + dbtransaction &T, + oid O, + openmode mode) : + largeobject{O}, + m_trans{T} +{ + open(mode); +} + + +pqxx::largeobjectaccess::largeobjectaccess( + dbtransaction &T, + largeobject O, + openmode mode) : + largeobject{O}, + m_trans{T} +{ + open(mode); +} + + +pqxx::largeobjectaccess::largeobjectaccess( + dbtransaction &T, + const std::string &File, + openmode mode) : + largeobject{T, File}, + m_trans{T} +{ + open(mode); +} + + +pqxx::largeobjectaccess::size_type +pqxx::largeobjectaccess::seek(size_type dest, seekdir dir) +{ + const auto Result = cseek(dest, dir); + if (Result == -1) + { + const int err = errno; + if (err == ENOMEM) throw std::bad_alloc{}; + throw failure{"Error seeking in large object: " + reason(err)}; + } + + return Result; +} + + +pqxx::largeobjectaccess::pos_type +pqxx::largeobjectaccess::cseek(off_type dest, seekdir dir) noexcept +{ + return lo_lseek(raw_connection(), m_fd, int(dest), StdDirToPQDir(dir)); +} + + +pqxx::largeobjectaccess::pos_type +pqxx::largeobjectaccess::cwrite(const char Buf[], size_type Len) noexcept +{ + return + std::max( + lo_write(raw_connection(), m_fd,const_cast<char *>(Buf), size_t(Len)), + -1); +} + + +pqxx::largeobjectaccess::pos_type +pqxx::largeobjectaccess::cread(char Buf[], size_type Bytes) noexcept +{ + return std::max(lo_read(raw_connection(), m_fd, Buf, size_t(Bytes)), -1); +} + + +pqxx::largeobjectaccess::pos_type +pqxx::largeobjectaccess::ctell() const noexcept +{ + return lo_tell(raw_connection(), m_fd); +} + + +void pqxx::largeobjectaccess::write(const char Buf[], size_type Len) +{ + const auto Bytes = cwrite(Buf, Len); + if (Bytes < Len) + { + const int err = errno; + if (err == ENOMEM) throw std::bad_alloc{}; + if (Bytes < 0) + throw failure{ + "Error writing to large object #" + to_string(id()) + ": " + + reason(err)}; + if (Bytes == 0) + throw failure{ + "Could not write to large object #" + to_string(id()) + ": " + + reason(err)}; + + throw failure{ + "Wanted to write " + to_string(Len) + " bytes to large object #" + + to_string(id()) + "; " "could only write " + to_string(Bytes)}; + } +} + + +pqxx::largeobjectaccess::size_type +pqxx::largeobjectaccess::read(char Buf[], size_type Len) +{ + const auto Bytes = cread(Buf, Len); + if (Bytes < 0) + { + const int err = errno; + if (err == ENOMEM) throw std::bad_alloc{}; + throw failure{ + "Error reading from large object #" + to_string(id()) + ": " + + reason(err)}; + } + return Bytes; +} + + +void pqxx::largeobjectaccess::open(openmode mode) +{ + m_fd = lo_open(raw_connection(), id(), StdModeToPQMode(mode)); + if (m_fd < 0) + { + const int err = errno; + if (err == ENOMEM) throw std::bad_alloc{}; + throw failure{ + "Could not open large object " + to_string(id()) + ": " + + reason(err)}; + } +} + + +void pqxx::largeobjectaccess::close() noexcept +{ + if (m_fd >= 0) lo_close(raw_connection(), m_fd); +} + + +pqxx::largeobjectaccess::size_type pqxx::largeobjectaccess::tell() const +{ + const size_type res = ctell(); + if (res == -1) throw failure{reason(errno)}; + return res; +} + + +std::string pqxx::largeobjectaccess::reason(int err) const +{ + if (m_fd == -1) return "No object opened."; + return largeobject::reason(m_trans.conn(), err); +} + + +void pqxx::largeobjectaccess::process_notice(const std::string &s) noexcept +{ + m_trans.process_notice(s); +} diff --git a/contrib/libs/libpqxx/src/nontransaction.cxx b/contrib/libs/libpqxx/src/nontransaction.cxx new file mode 100644 index 0000000000..09e13a94d0 --- /dev/null +++ b/contrib/libs/libpqxx/src/nontransaction.cxx @@ -0,0 +1,25 @@ +/** Implementation of the pqxx::nontransaction class. + * + * pqxx::nontransaction provides nontransactional database access. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/nontransaction" + + +pqxx::nontransaction::~nontransaction() +{ + End(); +} + + +pqxx::result pqxx::nontransaction::do_exec(const char Query[]) +{ + return direct_exec(Query, 0); +} diff --git a/contrib/libs/libpqxx/src/notification.cxx b/contrib/libs/libpqxx/src/notification.cxx new file mode 100644 index 0000000000..391a71c1a4 --- /dev/null +++ b/contrib/libs/libpqxx/src/notification.cxx @@ -0,0 +1,36 @@ +/** Implementation of the pqxx::notification_receiever class. + * + * pqxx::notification_receiver processes notifications. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <string> + +#include "pqxx/internal/gates/connection-notification_receiver.hxx" + +#include "pqxx/notification" + + +using namespace pqxx::internal; + + +pqxx::notification_receiver::notification_receiver( + connection_base &c, + const std::string &channel_name) : + m_conn{c}, + m_channel{channel_name} +{ + gate::connection_notification_receiver{c}.add_receiver(this); +} + + +pqxx::notification_receiver::~notification_receiver() +{ + gate::connection_notification_receiver{this->conn()}.remove_receiver(this); +} diff --git a/contrib/libs/libpqxx/src/pipeline.cxx b/contrib/libs/libpqxx/src/pipeline.cxx new file mode 100644 index 0000000000..15b646ea3b --- /dev/null +++ b/contrib/libs/libpqxx/src/pipeline.cxx @@ -0,0 +1,413 @@ +/** Implementation of the pqxx::pipeline class. + * + * Throughput-optimized query interface. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <iterator> + +#include "pqxx/dbtransaction" +#include "pqxx/pipeline" + +#include "pqxx/internal/gates/connection-pipeline.hxx" +#include "pqxx/internal/gates/result-creation.hxx" + + +using namespace pqxx; +using namespace pqxx::internal; + + +namespace +{ +const std::string theSeparator{"; "}; +const std::string theDummyValue{"1"}; +const std::string theDummyQuery{"SELECT " + theDummyValue + theSeparator}; +} + + +pqxx::pipeline::pipeline(transaction_base &t, const std::string &Name) : + namedclass{"pipeline", Name}, + transactionfocus{t} +{ + m_issuedrange = make_pair(m_queries.end(), m_queries.end()); + attach(); +} + + +pqxx::pipeline::~pipeline() noexcept +{ + try { cancel(); } catch (const std::exception &) {} + detach(); +} + + +void pqxx::pipeline::attach() +{ + if (not registered()) register_me(); +} + + +void pqxx::pipeline::detach() +{ + if (registered()) unregister_me(); +} + + +pipeline::query_id pqxx::pipeline::insert(const std::string &q) +{ + attach(); + const query_id qid = generate_id(); + const auto i = m_queries.insert(std::make_pair(qid,Query(q))).first; + + if (m_issuedrange.second == m_queries.end()) + { + m_issuedrange.second = i; + if (m_issuedrange.first == m_queries.end()) m_issuedrange.first = i; + } + m_num_waiting++; + + if (m_num_waiting > m_retain) + { + if (have_pending()) receive_if_available(); + if (not have_pending()) issue(); + } + + return qid; +} + + +void pqxx::pipeline::complete() +{ + if (have_pending()) receive(m_issuedrange.second); + if (m_num_waiting and (m_error == qid_limit())) + { + issue(); + receive(m_queries.end()); + } + detach(); +} + + +void pqxx::pipeline::flush() +{ + if (not m_queries.empty()) + { + if (have_pending()) receive(m_issuedrange.second); + m_issuedrange.first = m_issuedrange.second = m_queries.end(); + m_num_waiting = 0; + m_dummy_pending = false; + m_queries.clear(); + } + detach(); +} + + +void pqxx::pipeline::cancel() +{ + while (have_pending()) + { + gate::connection_pipeline(m_trans.conn()).cancel_query(); + auto canceled_query = m_issuedrange.first; + ++m_issuedrange.first; + m_queries.erase(canceled_query); + } +} + + +bool pqxx::pipeline::is_finished(pipeline::query_id q) const +{ + if (m_queries.find(q) == m_queries.end()) + throw std::logic_error{ + "Requested status for unknown query '" + to_string(q) + "'."}; + return + (QueryMap::const_iterator(m_issuedrange.first)==m_queries.end()) or + (q < m_issuedrange.first->first and q < m_error); +} + + +std::pair<pipeline::query_id, result> pqxx::pipeline::retrieve() +{ + if (m_queries.empty()) + throw std::logic_error{"Attempt to retrieve result from empty pipeline."}; + return retrieve(std::begin(m_queries)); +} + + +int pqxx::pipeline::retain(int retain_max) +{ + if (retain_max < 0) + throw range_error{ + "Attempt to make pipeline retain " + + to_string(retain_max) + " queries"}; + + const int oldvalue = m_retain; + m_retain = retain_max; + + if (m_num_waiting >= m_retain) resume(); + + return oldvalue; +} + + +void pqxx::pipeline::resume() +{ + if (have_pending()) receive_if_available(); + if (not have_pending() and m_num_waiting) + { + issue(); + receive_if_available(); + } +} + + +pipeline::query_id pqxx::pipeline::generate_id() +{ + if (m_q_id == qid_limit()) + throw std::overflow_error{"Too many queries went through pipeline."}; + ++m_q_id; + return m_q_id; +} + + + +void pqxx::pipeline::issue() +{ + // TODO: Wrap in nested transaction if available, for extra "replayability" + + // Retrieve that null result for the last query, if needed + obtain_result(); + + // Don't issue anything if we've encountered an error + if (m_error < qid_limit()) return; + + // Start with oldest query (lowest id) not in previous issue range + auto oldest = m_issuedrange.second; + + // Construct cumulative query string for entire batch + std::string cum = separated_list( + theSeparator, oldest, m_queries.end(), + [](QueryMap::const_iterator i){return i->second.get_query();}); + const auto num_issued = QueryMap::size_type(std::distance( + oldest, m_queries.end())); + const bool prepend_dummy = (num_issued > 1); + if (prepend_dummy) cum = theDummyQuery + cum; + + gate::connection_pipeline{m_trans.conn()}.start_exec(cum); + + // Since we managed to send out these queries, update state to reflect this + m_dummy_pending = prepend_dummy; + m_issuedrange.first = oldest; + m_issuedrange.second = m_queries.end(); + m_num_waiting -= int(num_issued); +} + + +void pqxx::pipeline::internal_error(const std::string &err) +{ + set_error_at(0); + throw pqxx::internal_error{err}; +} + + +bool pqxx::pipeline::obtain_result(bool expect_none) +{ + gate::connection_pipeline gate{m_trans.conn()}; + const auto r = gate.get_result(); + if (r == nullptr) + { + if (have_pending() and not expect_none) + { + set_error_at(m_issuedrange.first->first); + m_issuedrange.second = m_issuedrange.first; + } + return false; + } + + const result res = gate::result_creation::create( + r, std::begin(m_queries)->second.get_query(), + internal::enc_group(m_trans.conn().encoding_id())); + + if (not have_pending()) + { + set_error_at(std::begin(m_queries)->first); + throw std::logic_error{ + "Got more results from pipeline than there were queries."}; + } + + // Must be the result for the oldest pending query + if (not m_issuedrange.first->second.get_result().empty()) + internal_error("Multiple results for one query."); + + m_issuedrange.first->second.set_result(res); + ++m_issuedrange.first; + + return true; +} + + +void pqxx::pipeline::obtain_dummy() +{ + gate::connection_pipeline gate{m_trans.conn()}; + const auto r = gate.get_result(); + m_dummy_pending = false; + + if (r == nullptr) + internal_error("Pipeline got no result from backend when it expected one."); + + result R = gate::result_creation::create( + r, + "[DUMMY PIPELINE QUERY]", + internal::enc_group(m_trans.conn().encoding_id())); + + bool OK = false; + try + { + gate::result_creation{R}.check_status(); + OK = true; + } + catch (const sql_error &) + { + } + if (OK) + { + if (R.size() > 1) + internal_error("Unexpected result for dummy query in pipeline."); + + if (std::string{R.at(0).at(0).c_str()} != theDummyValue) + internal_error("Dummy query in pipeline returned unexpected value."); + return; + } + + /* Since none of the queries in the batch were actually executed, we can + * afford to replay them one by one until we find the exact query that + * caused the error. This gives us not only a more specific error message + * to report, but also tells us which query to report it for. + */ + // First, give the whole batch the same syntax error message, in case all else + // is going to fail. + for (auto i = m_issuedrange.first; i != m_issuedrange.second; ++i) + i->second.set_result(R); + + // Remember where the end of this batch was + const auto stop = m_issuedrange.second; + + // Retrieve that null result for the last query, if needed + obtain_result(true); + + + // Reset internal state to forget botched batch attempt + m_num_waiting += int(std::distance(m_issuedrange.first, stop)); + m_issuedrange.second = m_issuedrange.first; + + // Issue queries in failed batch one at a time. + unregister_me(); + try + { + do + { + m_num_waiting--; + const std::string &query = m_issuedrange.first->second.get_query(); + const result res{m_trans.exec(query)}; + m_issuedrange.first->second.set_result(res); + gate::result_creation{res}.check_status(); + ++m_issuedrange.first; + } + while (m_issuedrange.first != stop); + } + catch (const std::exception &) + { + const query_id thud = m_issuedrange.first->first; + ++m_issuedrange.first; + m_issuedrange.second = m_issuedrange.first; + auto q = m_issuedrange.first; + set_error_at( (q == m_queries.end()) ? thud + 1 : q->first); + } +} + + +std::pair<pipeline::query_id, result> +pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q) +{ + if (q == m_queries.end()) + throw std::logic_error{"Attempt to retrieve result for unknown query."}; + + if (q->first >= m_error) + throw std::runtime_error{ + "Could not complete query in pipeline due to error in earlier query."}; + + // If query hasn't issued yet, do it now + if (m_issuedrange.second != m_queries.end() and + (q->first >= m_issuedrange.second->first)) + { + if (have_pending()) receive(m_issuedrange.second); + if (m_error == qid_limit()) issue(); + } + + // If result not in yet, get it; else get at least whatever's convenient + if (have_pending()) + { + if (q->first >= m_issuedrange.first->first) + { + auto suc = q; + ++suc; + receive(suc); + } + else + { + receive_if_available(); + } + } + + if (q->first >= m_error) + throw std::runtime_error{ + "Could not complete query in pipeline due to error in earlier query."}; + + // 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(); + + const result R = q->second.get_result(); + const auto P = std::make_pair(q->first, R); + + m_queries.erase(q); + + gate::result_creation{R}.check_status(); + return P; +} + + +void pqxx::pipeline::get_further_available_results() +{ + gate::connection_pipeline gate{m_trans.conn()}; + while (not gate.is_busy() and obtain_result()) + if (not gate.consume_input()) throw broken_connection{}; +} + + +void pqxx::pipeline::receive_if_available() +{ + gate::connection_pipeline gate{m_trans.conn()}; + if (not gate.consume_input()) throw broken_connection{}; + if (gate.is_busy()) return; + + if (m_dummy_pending) obtain_dummy(); + if (have_pending()) get_further_available_results(); +} + + +void pqxx::pipeline::receive(pipeline::QueryMap::const_iterator stop) +{ + if (m_dummy_pending) obtain_dummy(); + + while (obtain_result() 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(); +} diff --git a/contrib/libs/libpqxx/src/prepared_statement.cxx b/contrib/libs/libpqxx/src/prepared_statement.cxx new file mode 100644 index 0000000000..c2d7fe23f9 --- /dev/null +++ b/contrib/libs/libpqxx/src/prepared_statement.cxx @@ -0,0 +1,69 @@ +/** Helper classes for defining and executing prepared statements> + * + * See the connection_base hierarchy for more about prepared statements. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/connection_base" +#include "pqxx/prepared_statement" +#include "pqxx/result" +#include "pqxx/transaction_base" + +#include "pqxx/internal/gates/connection-prepare-invocation.hxx" + + +using namespace pqxx; +using namespace pqxx::internal; + + +pqxx::prepare::invocation::invocation( + transaction_base &home, + const std::string &statement) : + m_home{home}, + m_statement{statement} +{ +} + +pqxx::result pqxx::prepare::invocation::exec() const +{ + return internal_exec(result_format::text); +} + +pqxx::result pqxx::prepare::invocation::exec_binary() const +{ + return internal_exec(result_format::binary); +} + +pqxx::result pqxx::prepare::invocation::internal_exec(result_format format) const +{ + std::vector<const char *> ptrs; + std::vector<int> lens; + std::vector<int> binaries; + const int elts = marshall(ptrs, lens, binaries); + + return gate::connection_prepare_invocation{m_home.conn()}.prepared_exec( + m_statement, + ptrs.data(), + lens.data(), + binaries.data(), + elts, + format); +} + +bool pqxx::prepare::invocation::exists() const +{ + return gate::connection_prepare_invocation{m_home.conn()}.prepared_exists( + m_statement); +} + + +pqxx::prepare::internal::prepared_def::prepared_def(const std::string &def) : + definition{def} +{ +} diff --git a/contrib/libs/libpqxx/src/result.cxx b/contrib/libs/libpqxx/src/result.cxx new file mode 100644 index 0000000000..fcb602d779 --- /dev/null +++ b/contrib/libs/libpqxx/src/result.cxx @@ -0,0 +1,454 @@ +/** Implementation of the pqxx::result class and support classes. + * + * pqxx::result represents the set of result rows from a database query + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <cstdlib> +#include <cstring> +#include <stdexcept> + +extern "C" +{ +#include "libpq-fe.h" +} + +#include "pqxx/except" +#include "pqxx/result" + + +const std::string pqxx::result::s_empty_string; + + +/// C++ wrapper for libpq's PQclear. +void pqxx::internal::clear_result(const pq::PGresult *data) +{ + PQclear(const_cast<pq::PGresult *>(data)); +} + + +pqxx::result::result( + pqxx::internal::pq::PGresult *rhs, + const std::string &Query, + internal::encoding_group enc) : + m_data{make_data_pointer(rhs)}, + m_query{std::make_shared<std::string>(Query)}, + m_encoding(enc) +{ +} + + +bool pqxx::result::operator==(const result &rhs) const noexcept +{ + if (&rhs == this) return true; + const auto s = size(); + if (rhs.size() != s) return false; + for (size_type i=0; i<s; ++i) + if ((*this)[i] != rhs[i]) return false; + return true; +} + + +pqxx::result::const_reverse_iterator pqxx::result::rbegin() const +{ + return const_reverse_iterator{end()}; +} + + +pqxx::result::const_reverse_iterator pqxx::result::crbegin() const +{ + return rbegin(); +} + + +pqxx::result::const_reverse_iterator pqxx::result::rend() const +{ + return const_reverse_iterator{begin()}; +} + + +pqxx::result::const_reverse_iterator pqxx::result::crend() const +{ + return rend(); +} + + +pqxx::result::const_iterator pqxx::result::begin() const noexcept +{ + return const_iterator{this, 0}; +} + + +pqxx::result::const_iterator pqxx::result::cbegin() const noexcept +{ + return begin(); +} + + +pqxx::result::size_type pqxx::result::size() const noexcept +{ + return m_data.get() ? size_type(PQntuples(m_data.get())) : 0; +} + + +bool pqxx::result::empty() const noexcept +{ + return (m_data.get() == nullptr) or (PQntuples(m_data.get()) == 0); +} + + +pqxx::result::reference pqxx::result::front() const noexcept +{ + return row{*this, 0}; +} + + +pqxx::result::reference pqxx::result::back() const noexcept +{ + return row{*this, size() - 1}; +} + + +void pqxx::result::swap(result &rhs) noexcept +{ + m_data.swap(rhs.m_data); + m_query.swap(rhs.m_query); +} + + +const pqxx::row pqxx::result::operator[](result_size_type i) const noexcept +{ + return row{*this, i}; +} + + +const pqxx::row pqxx::result::at(pqxx::result::size_type i) const +{ + if (i >= size()) throw range_error{"Row number out of range."}; + return operator[](i); +} + + +namespace +{ +/// C string comparison. +inline bool equal(const char lhs[], const char rhs[]) +{ + return strcmp(lhs, rhs) == 0; +} +} // namespace + +void pqxx::result::ThrowSQLError( + const std::string &Err, + const std::string &Query) const +{ + // Try to establish more precise error type, and throw corresponding + // type of exception. + const char *const code = PQresultErrorField(m_data.get(), PG_DIAG_SQLSTATE); + if (code) switch (code[0]) + { + case '0': + switch (code[1]) + { + case '8': + throw broken_connection{Err}; + case 'A': + throw feature_not_supported{Err, Query, code}; + } + break; + case '2': + switch (code[1]) + { + case '2': + throw data_exception{Err, Query, code}; + case '3': + if (equal(code,"23001")) throw restrict_violation{Err, Query, code}; + if (equal(code,"23502")) throw not_null_violation{Err, Query, code}; + if (equal(code,"23503")) + throw foreign_key_violation{Err, Query, code}; + if (equal(code,"23505")) throw unique_violation{Err, Query, code}; + 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}; + } + break; + case '3': + switch (code[1]) + { + case '4': + throw invalid_cursor_name{Err, Query, code}; + } + break; + case '4': + switch (code[1]) + { + case '0': + if (equal(code, "40000")) throw transaction_rollback{Err}; + if (equal(code, "40001")) throw serialization_failure{Err}; + if (equal(code, "40003")) throw statement_completion_unknown{Err}; + if (equal(code, "40P01")) throw deadlock_detected{Err}; + break; + case '2': + if (equal(code,"42501")) throw insufficient_privilege{Err, Query}; + if (equal(code,"42601")) + throw syntax_error{Err, Query, code, errorposition()}; + if (equal(code,"42703")) throw undefined_column{Err, Query, code}; + if (equal(code,"42883")) throw undefined_function{Err, Query, code}; + if (equal(code,"42P01")) throw undefined_table{Err, Query, code}; + } + break; + case '5': + switch (code[1]) + { + case '3': + if (equal(code,"53100")) throw disk_full{Err, Query, code}; + if (equal(code,"53200")) throw out_of_memory{Err, Query, code}; + if (equal(code,"53300")) throw too_many_connections{Err}; + throw insufficient_resources{Err, Query, code}; + } + break; + + case 'P': + if (equal(code, "P0001")) throw plpgsql_raise{Err, Query, code}; + if (equal(code, "P0002")) + throw plpgsql_no_data_found{Err, Query, code}; + if (equal(code, "P0003")) + throw plpgsql_too_many_rows{Err, Query, code}; + throw plpgsql_error{Err, Query, code}; + } + // Fallback: No error code. + throw sql_error{Err, Query, code}; +} + +void pqxx::result::check_status() const +{ + const std::string Err = StatusError(); + if (not Err.empty()) ThrowSQLError(Err, query()); +} + + +std::string pqxx::result::StatusError() const +{ + if (m_data.get() == nullptr) throw failure{"No result set given."}; + + std::string Err; + + switch (PQresultStatus(m_data.get())) + { + case PGRES_EMPTY_QUERY: // The string sent to the backend was empty. + case PGRES_COMMAND_OK: // Successful completion of a command returning no data + case PGRES_TUPLES_OK: // The query successfully executed + break; + + case PGRES_COPY_OUT: // Copy Out (from server) data transfer started + case PGRES_COPY_IN: // Copy In (to server) data transfer started + break; + + case PGRES_BAD_RESPONSE: // The server's response was not understood + case PGRES_NONFATAL_ERROR: + case PGRES_FATAL_ERROR: + Err = PQresultErrorMessage(m_data.get()); + break; + + default: + throw internal_error{ + "pqxx::result: Unrecognized response code " + + to_string(int(PQresultStatus(m_data.get())))}; + } + return Err; +} + + +const char *pqxx::result::cmd_status() const noexcept +{ + return PQcmdStatus(const_cast<internal::pq::PGresult *>(m_data.get())); +} + + +const std::string &pqxx::result::query() const noexcept +{ + return m_query ? *m_query : s_empty_string; +} + + +pqxx::oid pqxx::result::inserted_oid() const +{ + if (m_data.get() == nullptr) + throw usage_error{ + "Attempt to read oid of inserted row without an INSERT result"}; + return PQoidValue(const_cast<internal::pq::PGresult *>(m_data.get())); +} + + +pqxx::result::size_type pqxx::result::affected_rows() const +{ + const char *const RowsStr = PQcmdTuples( + const_cast<internal::pq::PGresult *>(m_data.get())); + return RowsStr[0] ? size_type(atoi(RowsStr)) : 0; +} + + +const char *pqxx::result::GetValue( + pqxx::result::size_type Row, + pqxx::row::size_type Col) const +{ + return PQgetvalue(m_data.get(), int(Row), int(Col)); +} + + +bool pqxx::result::get_is_null( + pqxx::result::size_type Row, + pqxx::row::size_type Col) const +{ + return PQgetisnull(m_data.get(), int(Row), int(Col)) != 0; +} + +pqxx::field::size_type pqxx::result::get_length( + pqxx::result::size_type Row, + pqxx::row::size_type Col) const noexcept +{ + return field::size_type(PQgetlength(m_data.get(), int(Row), int(Col))); +} + + +pqxx::oid pqxx::result::column_type(row::size_type ColNum) const +{ + const oid T = PQftype(m_data.get(), int(ColNum)); + if (T == oid_none) + throw argument_error{ + "Attempt to retrieve type of nonexistent column " + + to_string(ColNum) + " of query result."}; + return T; +} + + +pqxx::oid pqxx::result::column_table(row::size_type ColNum) const +{ + const oid T = PQftable(m_data.get(), int(ColNum)); + + /* If we get oid_none, it may be because the column is computed, or because we + * got an invalid row number. + */ + if (T == oid_none and ColNum >= columns()) + throw argument_error{ + "Attempt to retrieve table ID for column " + to_string(ColNum) + + " out of " + to_string(columns())}; + + return T; +} + + +pqxx::row::size_type pqxx::result::table_column(row::size_type ColNum) const +{ + const auto n = row::size_type(PQftablecol(m_data.get(), int(ColNum))); + if (n != 0) return n-1; + + // Failed. Now find out why, so we can throw a sensible exception. + const std::string col_num = to_string(ColNum); + if (ColNum > columns()) + throw range_error{"Invalid column index in table_column(): " + col_num}; + + if (m_data.get() == nullptr) + throw usage_error{ + "Can't query origin of column " + col_num + ": " + "result is not initialized."}; + + throw usage_error{ + "Can't query origin of column " + col_num + ": " + "not derived from table column."}; +} + +int pqxx::result::errorposition() const +{ + int pos = -1; + if (m_data.get()) + { + const char *p = PQresultErrorField( + const_cast<internal::pq::PGresult *>(m_data.get()), + PG_DIAG_STATEMENT_POSITION); + if (p) from_string(p, pos); + } + return pos; +} + + +const char *pqxx::result::column_name(pqxx::row::size_type Number) const +{ + const char *const N = PQfname(m_data.get(), int(Number)); + if (N == nullptr) + { + if (m_data.get() == nullptr) + throw usage_error{"Queried column name on null result."}; + throw range_error{ + "Invalid column number: " + to_string(Number) + + " (maximum is " + to_string(columns() - 1) + ")."}; + } + return N; +} + + +pqxx::row::size_type pqxx::result::columns() const noexcept +{ + auto ptr = const_cast<internal::pq::PGresult *>(m_data.get()); + return ptr ? row::size_type(PQnfields(ptr)) : 0; +} + + +// const_result_iterator + +pqxx::const_result_iterator pqxx::const_result_iterator::operator++(int) +{ + const_result_iterator old{*this}; + m_index++; + return old; +} + + +pqxx::const_result_iterator pqxx::const_result_iterator::operator--(int) +{ + const_result_iterator old{*this}; + m_index--; + return old; +} + + +pqxx::result::const_iterator +pqxx::result::const_reverse_iterator::base() const noexcept +{ + iterator_type tmp{*this}; + return ++tmp; +} + + +pqxx::const_reverse_result_iterator +pqxx::const_reverse_result_iterator::operator++(int) +{ + const_reverse_result_iterator tmp{*this}; + iterator_type::operator--(); + return tmp; +} + + +pqxx::const_reverse_result_iterator +pqxx::const_reverse_result_iterator::operator--(int) +{ + const_reverse_result_iterator tmp{*this}; + iterator_type::operator++(); + return tmp; +} + + +template<> +std::string pqxx::to_string(const field &Obj) +{ + return std::string{Obj.c_str(), Obj.size()}; +} diff --git a/contrib/libs/libpqxx/src/robusttransaction.cxx b/contrib/libs/libpqxx/src/robusttransaction.cxx new file mode 100644 index 0000000000..fbada337df --- /dev/null +++ b/contrib/libs/libpqxx/src/robusttransaction.cxx @@ -0,0 +1,317 @@ +/** Implementation of the pqxx::robusttransaction class. + * + * pqxx::robusttransaction is a slower but safer transaction class. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <stdexcept> + +#include "pqxx/connection_base" +#include "pqxx/result" +#include "pqxx/robusttransaction" + + +using namespace pqxx::internal; + + +// TODO: Log username in more places. + +pqxx::internal::basic_robusttransaction::basic_robusttransaction( + connection_base &C, + const std::string &IsolationLevel, + const std::string &table_name) : + namedclass{"robusttransaction"}, + dbtransaction(C, IsolationLevel), + m_log_table{table_name} +{ + if (table_name.empty()) m_log_table = "pqxx_robusttransaction_log"; + m_sequence = m_log_table + "_seq"; +} + + +pqxx::internal::basic_robusttransaction::~basic_robusttransaction() +{ +} + + +void pqxx::internal::basic_robusttransaction::do_begin() +{ + try + { + CreateTransactionRecord(); + } + catch (const std::exception &) + { + // The problem here *may* be that the log table doesn't exist yet. Create + // one, start a new transaction, and try again. + try { dbtransaction::do_abort(); } catch (const std::exception &) {} + CreateLogTable(); + dbtransaction::do_begin(); + m_backendpid = conn().backendpid(); + CreateTransactionRecord(); + } + + dbtransaction::do_begin(); + + // If this transaction commits, the transaction record should also be gone. + direct_exec(sql_delete().c_str()); + + if (conn().server_version() >= 80300) + direct_exec("SELECT txid_current()")[0][0].to(m_xid); +} + + + +void pqxx::internal::basic_robusttransaction::do_commit() +{ + if (m_record_id == 0) + throw internal_error{"transaction '" + name() + "' has no ID."}; + + // Check constraints before sending the COMMIT to the database to reduce the + // work being done inside our in-doubt window. + try + { + direct_exec("SET CONSTRAINTS ALL IMMEDIATE"); + } + catch (...) + { + do_abort(); + throw; + } + + // Here comes the critical part. If we lose our connection here, we'll be + // left clueless as to whether the backend got the message and is trying to + // commit the transaction (let alone whether it will succeed if so). That + // case requires some special handling that makes robusttransaction what it + // is. + try + { + direct_exec("COMMIT"); + + // If we make it here, great. Normal, successful commit. + m_record_id = 0; + return; + } + catch (const broken_connection &) + { + // Oops, lost connection at the crucial moment. Fall through to in-doubt + // handling below. + } + catch (...) + { + if (conn().is_open()) + { + // Commit failed--probably due to a constraint violation or something + // similar. But we're still connected, so no worries from a consistency + // point of view. + do_abort(); + throw; + } + // Otherwise, fall through to in-doubt handling. + } + + // If we get here, we're in doubt. Talk to the backend, figure out what + // happened. If the transaction record still exists, the transaction failed. + // If not, it succeeded. + + bool exists; + try + { + exists = CheckTransactionRecord(); + } + catch (const std::exception &f) + { + // Couldn't check for transaction record. We're still in doubt as to + // whether the transaction was performed. + const std::string Msg = + "WARNING: Connection lost while committing transaction " + "'" + name() + "' (id " + to_string(m_record_id) + ", " + "transaction_id " + m_xid + "). " + "Please check for this record in the " + "'" + m_log_table + "' table. " + "If the record exists, the transaction was executed. " + "If not, then it wasn't.\n"; + + process_notice(Msg); + process_notice( + "Could not verify existence of transaction record because of the " + "following error:\n"); + process_notice(std::string{f.what()} + "\n"); + + throw in_doubt_error{Msg}; + } + + // Transaction record is still there, so the transaction failed and all we + // have is a "normal" transaction failure. + if (exists) + { + do_abort(); + throw broken_connection{"Connection lost while committing."}; + } + + // Otherwise, the transaction succeeded. Forget there was ever an error. +} + + +void pqxx::internal::basic_robusttransaction::do_abort() +{ + dbtransaction::do_abort(); + DeleteTransactionRecord(); +} + + +// Create transaction log table if it didn't already exist +void pqxx::internal::basic_robusttransaction::CreateLogTable() +{ + // Create log table in case it doesn't already exist. This code must only be + // executed before the backend transaction has properly started. + std::string CrTab = + "CREATE TABLE " + quote_name(m_log_table) + " (" + "id INTEGER NOT NULL, " + "username VARCHAR(256), " + "transaction_id xid, " + "name VARCHAR(256), " + "date TIMESTAMP NOT NULL" + ")"; + + try + { + direct_exec(CrTab.c_str(), 1); + } + catch (const std::exception &e) + { + conn().process_notice( + "Could not create transaction log table: " + std::string{e.what()}); + } + + try + { + direct_exec(("CREATE SEQUENCE " + m_sequence).c_str()); + } + catch (const std::exception &e) + { + conn().process_notice( + "Could not create transaction log sequence: " + std::string{e.what()}); + } +} + + +void pqxx::internal::basic_robusttransaction::CreateTransactionRecord() +{ + // Clean up old transaction records. + direct_exec(( + "DELETE FROM " + m_log_table + " " + "WHERE date < CURRENT_TIMESTAMP - '30 days'::interval").c_str()); + + // Allocate id. + const std::string sql_get_id{"SELECT nextval(" + quote(m_sequence) + ")"}; + direct_exec(sql_get_id.c_str())[0][0].to(m_record_id); + + direct_exec(( + "INSERT INTO " + quote_name(m_log_table) + + " (id, username, name, date) " + "VALUES " + "(" + + to_string(m_record_id) + ", " + + quote(conn().username()) + ", " + + (name().empty() ? "NULL" : quote(name())) + ", " + "CURRENT_TIMESTAMP" + ")").c_str()); +} + + +std::string pqxx::internal::basic_robusttransaction::sql_delete() const +{ + return + "DELETE FROM " + quote_name(m_log_table) + " " + "WHERE id = " + to_string(m_record_id); +} + + +void pqxx::internal::basic_robusttransaction::DeleteTransactionRecord() + noexcept +{ + if (m_record_id == 0) return; + + try + { + const std::string Del = sql_delete(); + + reactivation_avoidance_exemption E(conn()); + direct_exec(Del.c_str(), 20); + + // Now that we've arrived here, we're about as sure as we can be that that + // record is quite dead. + m_record_id = 0; + } + catch (const std::exception &) + { + } + + if (m_record_id != 0) try + { + process_notice( + "WARNING: " + "Failed to delete obsolete transaction record with id " + + to_string(m_record_id) + " ('" + name() + "'). " + "Please delete it manually. Thank you.\n"); + } + catch (const std::exception &) + { + } +} + + +// Attempt to establish whether transaction record with given ID still exists +bool pqxx::internal::basic_robusttransaction::CheckTransactionRecord() +{ + bool hold = true; + for (int c=20; hold and c; internal::sleep_seconds(5), --c) + { + if (conn().server_version() > 80300) + { + const std::string query{ + "SELECT " + m_xid + " >= txid_snapshot_xmin(txid_current_snapshot())"}; + direct_exec(query.c_str())[0][0].to(hold); + } + else + { + /* Wait for the old backend (with the lost connection) to die. + * + * Actually this is only possible if stats_command_string (or maybe + * stats_start_collector?) has been set in postgresql.conf and we're + * running as the postgres superuser. + * + * Starting with 7.4, we could also use pg_locks. The entry for a zombied + * transaction will have a "relation" field of null, a "transaction" field + * with the transaction ID, and "pid" set to our backend pid. If the + * relation exists but no such record is found, then the transaction is no + * longer running. + */ + const result R{direct_exec(( + "SELECT current_query " + "FROM pq_stat_activity " + "WHERE procpid = " + to_string(m_backendpid)).c_str())}; + hold = not R.empty(); + } + } + + if (hold) + throw in_doubt_error{ + "Old backend process stays alive too long to wait for."}; + + // Now look for our transaction record + const std::string Find = + "SELECT id FROM " + quote_name(m_log_table) + " " + "WHERE " + "id = " + to_string(m_record_id) + " AND " + "user = " + conn().username(); + + return not direct_exec(Find.c_str(), 20).empty(); +} diff --git a/contrib/libs/libpqxx/src/row.cxx b/contrib/libs/libpqxx/src/row.cxx new file mode 100644 index 0000000000..3ae9a19a81 --- /dev/null +++ b/contrib/libs/libpqxx/src/row.cxx @@ -0,0 +1,276 @@ +/** Implementation of the pqxx::result class and support classes. + * + * pqxx::result represents the set of result rows from a database query. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <cstdlib> +#include <cstring> + +extern "C" +{ +#include "libpq-fe.h" +} + +#include "pqxx/except" +#include "pqxx/result" + +#include "pqxx/internal/gates/result-row.hxx" + + +pqxx::row::row(result r, size_t i) noexcept : + m_result{r}, + m_index{long(i)}, + m_end{internal::gate::result_row(r) ? r.columns() : 0} +{ +} + + +pqxx::row::const_iterator pqxx::row::begin() const noexcept +{ + return const_iterator{*this, m_begin}; +} + + +pqxx::row::const_iterator pqxx::row::cbegin() const noexcept +{ + return begin(); +} + + +pqxx::row::const_iterator pqxx::row::end() const noexcept +{ + return const_iterator{*this, m_end}; +} + + +pqxx::row::const_iterator pqxx::row::cend() const noexcept +{ + return end(); +} + + +pqxx::row::reference pqxx::row::front() const noexcept +{ + return field{*this, m_begin}; +} + + +pqxx::row::reference pqxx::row::back() const noexcept +{ + return field{*this, m_end - 1}; +} + + +pqxx::row::const_reverse_iterator pqxx::row::rbegin() const +{ + return const_reverse_row_iterator{end()}; +} + + +pqxx::row::const_reverse_iterator pqxx::row::crbegin() const +{ + return rbegin(); +} + + +pqxx::row::const_reverse_iterator pqxx::row::rend() const +{ + return const_reverse_row_iterator{begin()}; +} + + +pqxx::row::const_reverse_iterator pqxx::row::crend() const +{ + return rend(); +} + + +bool pqxx::row::operator==(const row &rhs) const noexcept +{ + if (&rhs == this) return true; + const auto s = size(); + if (rhs.size() != s) return false; + // TODO: Depends on how null is handled! + for (size_type i=0; i<s; ++i) if ((*this)[i] != rhs[i]) return false; + return true; +} + + +pqxx::row::reference pqxx::row::operator[](size_type i) const noexcept +{ + return field{*this, m_begin + i}; +} + + +pqxx::row::reference pqxx::row::operator[](int i) const noexcept +{ + return operator[](size_type(i)); +} + + +pqxx::row::reference pqxx::row::operator[](const char f[]) const +{ + return at(f); +} + + +pqxx::row::reference pqxx::row::operator[](const std::string &s) const +{ + return operator[](s.c_str()); +} + + +pqxx::row::reference pqxx::row::at(int i) const +{ + return at(size_type(i)); +} + + +pqxx::row::reference pqxx::row::at(const std::string &s) const +{ + return at(s.c_str()); +} + + +void pqxx::row::swap(row &rhs) noexcept +{ + const auto i = m_index; + const auto b= m_begin; + const auto e = m_end; + m_result.swap(rhs.m_result); + m_index = rhs.m_index; + m_begin = rhs.m_begin; + m_end = rhs.m_end; + rhs.m_index = i; + rhs.m_begin = b; + rhs.m_end = e; +} + + +pqxx::field pqxx::row::at(const char f[]) const +{ + return field{*this, m_begin + column_number(f)}; +} + + +pqxx::field pqxx::row::at(pqxx::row::size_type i) const +{ + if (i >= size()) + throw range_error{"Invalid field number."}; + + return operator[](i); +} + + +pqxx::oid pqxx::row::column_type(size_type ColNum) const +{ + return m_result.column_type(m_begin + ColNum); +} + + +pqxx::oid pqxx::row::column_table(size_type ColNum) const +{ + return m_result.column_table(m_begin + ColNum); +} + + +pqxx::row::size_type pqxx::row::table_column(size_type ColNum) const +{ + return m_result.table_column(m_begin + ColNum); +} + + +pqxx::row::size_type pqxx::row::column_number(const char ColName[]) const +{ + const auto n = m_result.column_number(ColName); + if (n >= m_end) + return result{}.column_number(ColName); + if (n >= m_begin) + return n - m_begin; + + const char *const AdaptedColName = m_result.column_name(n); + for (auto i = m_begin; i < m_end; ++i) + if (strcmp(AdaptedColName, m_result.column_name(i)) == 0) + return i - m_begin; + + return result{}.column_number(ColName); +} + + +pqxx::row::size_type pqxx::result::column_number(const char ColName[]) const +{ + const int N = PQfnumber( + const_cast<internal::pq::PGresult *>(m_data.get()), ColName); + if (N == -1) + throw argument_error{ + "Unknown column name: '" + std::string{ColName} + "'."}; + + return row::size_type(N); +} + + +pqxx::row pqxx::row::slice(size_type Begin, size_type End) const +{ + if (Begin > End or End > size()) + throw range_error{"Invalid field range."}; + + row result{*this}; + result.m_begin = m_begin + Begin; + result.m_end = m_begin + End; + return result; +} + + +bool pqxx::row::empty() const noexcept +{ + return m_begin == m_end; +} + + +pqxx::const_row_iterator pqxx::const_row_iterator::operator++(int) +{ + const_row_iterator old{*this}; + m_col++; + return old; +} + + +pqxx::const_row_iterator pqxx::const_row_iterator::operator--(int) +{ + const_row_iterator old{*this}; + m_col--; + return old; +} + + +pqxx::const_row_iterator +pqxx::const_reverse_row_iterator::base() const noexcept +{ + iterator_type tmp{*this}; + return ++tmp; +} + + +pqxx::const_reverse_row_iterator +pqxx::const_reverse_row_iterator::operator++(int) +{ + const_reverse_row_iterator tmp{*this}; + operator++(); + return tmp; +} + + +pqxx::const_reverse_row_iterator +pqxx::const_reverse_row_iterator::operator--(int) +{ + const_reverse_row_iterator tmp{*this}; + operator--(); + return tmp; +} diff --git a/contrib/libs/libpqxx/src/sql_cursor.cxx b/contrib/libs/libpqxx/src/sql_cursor.cxx new file mode 100644 index 0000000000..2ebdb1304d --- /dev/null +++ b/contrib/libs/libpqxx/src/sql_cursor.cxx @@ -0,0 +1,309 @@ +/** Implementation of libpqxx STL-style cursor classes. + * + * These classes wrap SQL cursors in STL-like interfaces. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <iterator> + +#include "pqxx/cursor" + +#include "pqxx/internal/encodings.hxx" +#include "pqxx/internal/gates/connection-sql_cursor.hxx" +#include "pqxx/internal/gates/transaction-sql_cursor.hxx" + +using namespace pqxx; +using namespace pqxx::internal; + + +namespace +{ +/// Is this character a "useless trailing character" in a query? +/** A character is "useless" at the end of a query if it is either whitespace or + * a semicolon. + */ +inline bool useless_trail(char c) +{ + return isspace(c) or c==';'; +} + + +/// Find end of nonempty query, stripping off any trailing semicolon. +/** When executing a normal query, a trailing semicolon is meaningless but + * won't hurt. That's why we can't rule out that some code may include one. + * + * But for cursor queries, a trailing semicolon is a problem. The query gets + * embedded in a larger statement, which a semicolon would break into two. + * We'll have to remove it if present. + * + * A trailing semicolon may not actually be at the end. It could be masked by + * subsequent whitespace. If there's also a comment though, that's the + * caller's own lookout. We can't guard against every possible mistake, and + * text processing is actually remarkably sensitive to mistakes in a + * multi-encoding world. + * + * If there is a trailing semicolon, this function returns its offset. If + * there are more than one, it returns the offset of the first one. If there + * is no trailing semicolon, it returns the length of the query string. + * + * The query must be nonempty. + */ +std::string::size_type find_query_end( + const std::string &query, + encoding_group enc) +{ + const auto text = query.c_str(); + const auto size = query.size(); + std::string::size_type end; + if (enc == encoding_group::MONOBYTE) + { + // This is an encoding where we can scan backwards from the end. + for (end = query.size(); end > 0 and useless_trail(text[end-1]); --end); + } + else + { + // Complex encoding. We only know how to iterate forwards, so start from + // the beginning. + end = 0; + + pqxx::internal::for_glyphs( + enc, + [text, &end](const char *gbegin, const char *gend) + { + if (gend - gbegin > 1 or not useless_trail(*gbegin)) + end = std::string::size_type(gend - text); + }, + text, size); + } + + return end; +} +} // namespace + + +pqxx::internal::sql_cursor::sql_cursor( + transaction_base &t, + const std::string &query, + const std::string &cname, + cursor_base::accesspolicy ap, + cursor_base::updatepolicy up, + cursor_base::ownershippolicy op, + bool hold, + result_format format) : + cursor_base{t.conn(), cname}, + m_home{t.conn()}, + m_adopted{false}, + m_at_end{-1}, + m_pos{0} +{ + if (&t.conn() != &m_home) throw internal_error{"Cursor in wrong connection"}; + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + m_home.activate(); +#include "pqxx/internal/ignore-deprecated-post.hxx" + + if (query.empty()) throw usage_error{"Cursor has empty query."}; + const auto enc = enc_group(t.conn().encoding_id()); + const auto qend = find_query_end(query, enc); + if (qend == 0) throw usage_error{"Cursor has effectively empty query."}; + + std::stringstream cq, qn; + + cq << "DECLARE " << t.quote_name(name()) << " "; + + if (format == result_format::binary) { + cq << "BINARY "; + } + + if (ap == cursor_base::forward_only) cq << "NO "; + cq << "SCROLL "; + + cq << "CURSOR "; + + if (hold) cq << "WITH HOLD "; + + cq << "FOR "; + cq.write(query.c_str(), std::streamsize(qend)); + cq << ' '; + + if (up != cursor_base::update) cq << "FOR READ ONLY "; + else cq << "FOR UPDATE "; + + qn << "[DECLARE " << name() << ']'; + t.exec(cq, qn.str()); + + // 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); + + // If we're creating a WITH HOLD cursor, noone is going to destroy it until + // after this transaction. That means the connection cannot be deactivated + // without losing the cursor. + if (hold) + gate::connection_sql_cursor{t.conn()}.add_reactivation_avoidance_count(1); + + m_ownership = op; +} + + +pqxx::internal::sql_cursor::sql_cursor( + transaction_base &t, + const std::string &cname, + cursor_base::ownershippolicy op) : + cursor_base{t.conn(), cname, false}, + m_home{t.conn()}, + m_empty_result{}, + m_adopted{true}, + m_at_end{0}, + m_pos{-1} +{ + // If we take responsibility for destroying the cursor, that's one less + // reason not to allow the connection to be deactivated and reactivated. + // TODO: Go over lifetime/reactivation rules again to be sure they work. + if (op==cursor_base::owned) + gate::connection_sql_cursor{t.conn()}.add_reactivation_avoidance_count(-1); + m_adopted = true; + m_ownership = op; +} + + +void pqxx::internal::sql_cursor::close() noexcept +{ + if (m_ownership==cursor_base::owned) + { + try + { + gate::connection_sql_cursor{m_home}.exec( + ("CLOSE " + m_home.quote_name(name())).c_str(), + 0); + } + catch (const std::exception &) + { + } + + if (m_adopted) + gate::connection_sql_cursor{m_home}.add_reactivation_avoidance_count(-1); + + m_ownership = cursor_base::loose; + } +} + + +void pqxx::internal::sql_cursor::init_empty_result(transaction_base &t) +{ + if (pos() != 0) throw internal_error{"init_empty_result() from bad pos()."}; + m_empty_result = t.exec("FETCH 0 IN " + m_home.quote_name(name())); +} + + +/// Compute actual displacement based on requested and reported displacements. +internal::sql_cursor::difference_type +pqxx::internal::sql_cursor::adjust(difference_type hoped, + difference_type actual) +{ + if (actual < 0) throw internal_error{"Negative rows in cursor movement."}; + if (hoped == 0) return 0; + const int direction = ((hoped < 0) ? -1 : 1); + bool hit_end = false; + if (actual != labs(hoped)) + { + if (actual > labs(hoped)) + throw internal_error{"Cursor displacement larger than requested."}; + + // If we see fewer rows than requested, then we've hit an end (on either + // side) of the result set. Wether we make an extra step to a one-past-end + // position or whether we're already there depends on where we were + // previously: if our last move was in the same direction and also fell + // short, we're already at a one-past-end row. + if (m_at_end != direction) ++actual; + + // If we hit the beginning, make sure our position calculation ends up + // at zero (even if we didn't previously know where we were!), and if we + // hit the other end, register the fact that we now know where the end + // of the result set is. + if (direction > 0) hit_end = true; + else if (m_pos == -1) m_pos = actual; + else if (m_pos != actual) + throw internal_error{ + "Moved back to beginning, but wrong position: " + "hoped=" + to_string(hoped) + ", " + "actual=" + to_string(actual) + ", " + "m_pos=" + to_string(m_pos) + ", " + "direction=" + to_string(direction) + "."}; + + m_at_end = direction; + } + else + { + m_at_end = 0; + } + + if (m_pos >= 0) m_pos += direction*actual; + if (hit_end) + { + if (m_endpos >= 0 and m_pos != m_endpos) + throw internal_error{"Inconsistent cursor end positions."}; + m_endpos = m_pos; + } + return direction*actual; +} + + +result pqxx::internal::sql_cursor::fetch( + difference_type rows, + difference_type &displacement) +{ + if (rows == 0) + { + displacement = 0; + return m_empty_result; + } + const std::string query = + "FETCH " + stridestring(rows) + " IN " + m_home.quote_name(name()); + const result r{gate::connection_sql_cursor{m_home}.exec(query.c_str(), 0)}; + displacement = adjust(rows, difference_type(r.size())); + return r; +} + + +cursor_base::difference_type pqxx::internal::sql_cursor::move( + difference_type rows, + difference_type &displacement) +{ + if (rows == 0) + { + displacement = 0; + return 0; + } + + const std::string query = + "MOVE " + stridestring(rows) + " IN " + m_home.quote_name(name()); + const result r(gate::connection_sql_cursor{m_home}.exec(query.c_str(), 0)); + difference_type d = difference_type(r.affected_rows()); + displacement = adjust(rows, d); + return d; +} + + +std::string pqxx::internal::sql_cursor::stridestring(difference_type n) +{ + /* Some special-casing for ALL and BACKWARD ALL here. We used to use numeric + * "infinities" for difference_type for this (the highest and lowest possible + * values for "long"), but for PostgreSQL 8.0 at least, the backend appears to + * expect a 32-bit number and fails to parse large 64-bit numbers. + * We could change the alias to match this behaviour, but that would break + * if/when Postgres is changed to accept 64-bit displacements. + */ + static const std::string All{"ALL"}, BackAll{"BACKWARD ALL"}; + if (n >= cursor_base::all()) return All; + else if (n <= cursor_base::backward_all()) return BackAll; + return to_string(n); +} diff --git a/contrib/libs/libpqxx/src/statement_parameters.cxx b/contrib/libs/libpqxx/src/statement_parameters.cxx new file mode 100644 index 0000000000..3500cb04a9 --- /dev/null +++ b/contrib/libs/libpqxx/src/statement_parameters.cxx @@ -0,0 +1,58 @@ +/** Common implementation for statement parameter lists. + * + * See the connection_base hierarchy for more about prepared statements + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/util" + +#include "pqxx/internal/statement_parameters.hxx" + + +void pqxx::internal::statement_parameters::add_checked_param( + const std::string &value, + bool nonnull, + bool binary) +{ + m_nonnull.push_back(nonnull); + if (nonnull) m_values.push_back(value); + m_binary.push_back(binary); +} + + +int pqxx::internal::statement_parameters::marshall( + std::vector<const char *> &values, + std::vector<int> &lengths, + std::vector<int> &binaries) const +{ + const auto elements = m_nonnull.size(); + const auto array_size = elements + 1; + values.clear(); + values.resize(array_size, nullptr); + lengths.clear(); + lengths.resize(array_size, 0); + // "Unpack" from m_values, which skips arguments that are null, to the + // outputs which represent all parameters including nulls. + size_t arg = 0; + for (size_t param = 0; param < elements; ++param) + if (m_nonnull[param]) + { + values[param] = m_values[arg].c_str(); + lengths[param] = int(m_values[arg].size()); + ++arg; + } + + // The binaries array is simpler: it maps 1-on-1. + binaries.resize(array_size); + for (size_t param = 0; param < elements; ++param) + binaries[param] = int(m_binary[param]); + binaries.back() = 0; + + return int(elements); +} diff --git a/contrib/libs/libpqxx/src/strconv.cxx b/contrib/libs/libpqxx/src/strconv.cxx new file mode 100644 index 0000000000..9e67d55bb3 --- /dev/null +++ b/contrib/libs/libpqxx/src/strconv.cxx @@ -0,0 +1,724 @@ +/** Implementation of string conversions. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <algorithm> +#include <cmath> +#include <cstring> +#include <limits> +#include <locale> +#include <system_error> + +#if __cplusplus < 201703 +// This is not C++17 or better. Don't use to_chars/from_chars; the code which +// uses those also relies on other C++17 features. +#if defined(PQXX_HAVE_CHARCONV_INT) +#undef PQXX_HAVE_CHARCONV_INT +#endif +#if defined(PQXX_HAVE_CHARCONV_FLOAT) +#undef PQXX_HAVE_CHARCONV_FLOAT +#endif +#endif + +#if defined(PQXX_HAVE_CHARCONV_INT) || defined(PQXX_HAVE_CHARCONV_FLOAT) +#include <charconv> +#endif + +#if __cplusplus >= 201703 +#include <string_view> +#endif + +#include "pqxx/except" +#include "pqxx/strconv" + + +using namespace pqxx::internal; + + +namespace +{ +/// C string comparison. +inline bool equal(const char lhs[], const char rhs[]) +{ + return strcmp(lhs, rhs) == 0; +} +} // namespace + + +namespace pqxx +{ +namespace internal +{ +void throw_null_conversion(const std::string &type) +{ + throw conversion_error{"Attempt to convert null to " + type + "."}; +} +} // namespace pqxx::internal +} // namespace pqxx + + +#if defined(PQXX_HAVE_CHARCONV_INT) || defined(PQXX_HAVE_CHARCONV_FLOAT) +namespace +{ +template<typename T> void wrap_from_chars(std::string_view in, T &out) +{ + using traits = pqxx::string_traits<T>; + const char *end = in.data() + in.size(); + const auto res = std::from_chars(in.data(), end, out); + if (res.ec == std::errc() and res.ptr == end) return; + + std::string msg; + if (res.ec == std::errc()) + { + msg = "Could not parse full string."; + } + else switch (res.ec) + { + case std::errc::result_out_of_range: + msg = "Value out of range."; + break; + case std::errc::invalid_argument: + msg = "Invalid argument."; + break; + default: + break; + } + + const std::string base = + "Could not convert '" + std::string(in) + "' " + "to " + traits::name(); + if (msg.empty()) throw pqxx::conversion_error{base + "."}; + else throw pqxx::conversion_error{base + ": " + msg}; +} + + +/// How big of a buffer do we want for representing a T? +template<typename T> constexpr int size_buffer() +{ + using lim = std::numeric_limits<T>; + // Allocate room for how many digits? There's "max_digits10" for + // floating-point numbers, but only "digits10" for integer types. + constexpr auto digits = std::max({lim::digits10, lim::max_digits10}); + // Leave a little bit of extra room for signs, decimal points, and the like. + return digits + 4; +} + + +/// Call @c std::to_chars. It differs for integer vs. floating-point types. +template<typename TYPE, bool INTEGRAL> struct to_chars_caller; + +#if defined(PQXX_HAVE_CHARCONV_INT) +/// For integer types, we pass "base 10" to @c std::to_chars. +template<typename TYPE> struct to_chars_caller<TYPE, true> +{ + static std::to_chars_result call(char *begin, char *end, TYPE in) + { return std::to_chars(begin, end, in, 10); } +}; +#endif + +#if defined(PQXX_HAVE_CHARCONV_FLOAT) +/// For floating-point types, we pass "general format" to @c std::to_chars. +template<typename TYPE> +template<typename TYPE> struct to_chars_caller<TYPE, true> +{ + static std::to_chars_result call(char *begin, char *end, TYPE in) + { return std::to_chars(begin, end, in, std::chars_format::general); } +}; +#endif +} // namespace + + +namespace pqxx +{ +namespace internal +{ +template<typename T> std::string builtin_traits<T>::to_string(T in) +{ + using traits = pqxx::string_traits<T>; + char buf[size_buffer<T>()]; + + // Annoying: we need to make slightly different calls to std::to_chars + // depending on whether this is an integral type or a floating-point type. + // Use to_chars_caller to hide the difference. + constexpr bool is_integer = std::numeric_limits<T>::is_integer; + const auto res = to_chars_caller<T, is_integer>::call( + buf, buf + sizeof(buf), in); + if (res.ec == std::errc()) return std::string(buf, res.ptr); + + std::string msg; + switch (res.ec) + { + case std::errc::value_too_large: + msg = "Value too large."; + break; + default: + break; + } + + const std::string base = + std::string{"Could not convert "} + traits::name() + " to string"; + if (msg.empty()) throw pqxx::conversion_error{base + "."}; + else throw pqxx::conversion_error{base + ": " + msg}; +} + + +/// Translate @c from_string calls to @c wrap_from_chars calls. +/** The only difference is the type of the string. + */ +template<typename TYPE> +void builtin_traits<TYPE>::from_string(const char Str[], TYPE &Obj) + { wrap_from_chars(std::string_view{Str}, Obj); } +} // namespace pqxx::internal +} // namespace pqxx +#endif // PQXX_HAVE_CHARCONV_INT || PQXX_HAVE_CHARCONV_FLOAT + + +#if !defined(PQXX_HAVE_CHARCONV_FLOAT) +namespace +{ +template<typename T> inline void set_to_Inf(T &t, int sign=1) +{ + T value = std::numeric_limits<T>::infinity(); + if (sign < 0) value = -value; + t = value; +} +} // namespace +#endif // !PQXX_HAVE_CHARCONV_FLOAT + + +#if !defined(PQXX_HAVE_CHARCONV_INT) +namespace +{ +[[noreturn]] void report_overflow() +{ + throw pqxx::conversion_error{ + "Could not convert string to integer: value out of range."}; +} + + +/** Helper to check for underflow before multiplying a number by 10. + * + * Needed just so the compiler doesn't get to complain about an "if (n < 0)" + * clause that's pointless for unsigned numbers. + */ +template<typename T, bool is_signed> struct underflow_check; + +/* Specialization for signed types: check. + */ +template<typename T> struct underflow_check<T, true> +{ + static void check_before_adding_digit(T n) + { + constexpr T ten{10}; + if (n < 0 and (std::numeric_limits<T>::min() / ten) > n) report_overflow(); + } +}; + +/* Specialization for unsigned types: no check needed becaue negative + * numbers don't exist. + */ +template<typename T> struct underflow_check<T, false> +{ + static void check_before_adding_digit(T) {} +}; + + +/// Return 10*n, or throw exception if it overflows. +template<typename T> T safe_multiply_by_ten(T n) +{ + using limits = std::numeric_limits<T>; + constexpr T ten{10}; + if (n > 0 and (limits::max() / n) < ten) report_overflow(); + underflow_check<T, limits::is_signed>::check_before_adding_digit(n); + return T(n * ten); +} + + +/// Add a digit d to n, or throw exception if it overflows. +template<typename T> T safe_add_digit(T n, T d) +{ + assert((n >= 0 and d >= 0) or (n <=0 and d <= 0)); + if ((n > 0) and (n > (std::numeric_limits<T>::max() - d))) report_overflow(); + if ((n < 0) and (n < (std::numeric_limits<T>::min() - d))) report_overflow(); + return n + d; +} + + +/// For use in string parsing: add new numeric digit to intermediate value +template<typename L, typename R> + inline L absorb_digit(L value, R digit) +{ + return L(safe_multiply_by_ten(value) + L(digit)); +} + + +template<typename T> void from_string_signed(const char Str[], T &Obj) +{ + int i = 0; + T result = 0; + + if (not isdigit(Str[i])) + { + if (Str[i] != '-') + throw pqxx::conversion_error{ + "Could not convert string to integer: '" + std::string{Str} + "'."}; + + for (++i; isdigit(Str[i]); ++i) + result = absorb_digit(result, -digit_to_number(Str[i])); + } + else + { + for (; isdigit(Str[i]); ++i) + result = absorb_digit(result, digit_to_number(Str[i])); + } + + if (Str[i]) + throw pqxx::conversion_error{ + "Unexpected text after integer: '" + std::string{Str} + "'."}; + + Obj = result; +} + +template<typename T> void from_string_unsigned(const char Str[], T &Obj) +{ + int i = 0; + T result = 0; + + if (not isdigit(Str[i])) + throw pqxx::conversion_error{ + "Could not convert string to unsigned integer: '" + + std::string{Str} + "'."}; + + for (; isdigit(Str[i]); ++i) + result = absorb_digit(result, digit_to_number(Str[i])); + + if (Str[i]) + throw pqxx::conversion_error{ + "Unexpected text after integer: '" + std::string{Str} + "'."}; + + Obj = result; +} +} // namespace +#endif // !PQXX_HAVE_CHARCONV_INT + + +#if !defined(PQXX_HAVE_CHARCONV_FLOAT) +namespace +{ +bool valid_infinity_string(const char str[]) noexcept +{ + return + equal("infinity", str) or + equal("Infinity", str) or + equal("INFINITY", str) or + equal("inf", str); +} + + +/// Wrapper for std::stringstream with C locale. +/** Some of our string conversions use the standard library. But, they must + * _not_ obey the system's locale settings, or a value like 1000.0 might end + * up looking like "1.000,0". + * + * Initialising the stream (including locale and tweaked precision) seems to + * be expensive though. So, create thread-local instances which we re-use. + * It's a lockless way of keeping global variables thread-safe, basically. + * + * The stream initialisation happens once per thread, in the constructor. + * And that's why we need to wrap this in a class. We can't just do it at the + * call site, or we'd still be doing it for every call. + */ +template<typename T> class dumb_stringstream : public std::stringstream +{ +public: + // Do not initialise the base-class object using "stringstream{}" (with curly + // braces): that breaks on Visual C++. The classic "stringstream()" syntax + // (with parentheses) does work. + dumb_stringstream() + { + this->imbue(std::locale::classic()); + this->precision(std::numeric_limits<T>::max_digits10); + } +}; + + +/* These are hard. Sacrifice performance of specialized, nonflexible, + * non-localized code and lean on standard library. Some special-case code + * handles NaNs. + */ +template<typename T> inline void from_string_float(const char Str[], T &Obj) +{ + bool ok = false; + T result; + + switch (Str[0]) + { + case 'N': + case 'n': + // Accept "NaN," "nan," etc. + ok = ( + (Str[1]=='A' or Str[1]=='a') and + (Str[2]=='N' or Str[2]=='n') and + (Str[3] == '\0')); + result = std::numeric_limits<T>::quiet_NaN(); + break; + + case 'I': + case 'i': + ok = valid_infinity_string(Str); + set_to_Inf(result); + break; + + default: + if (Str[0] == '-' and valid_infinity_string(&Str[1])) + { + ok = true; + set_to_Inf(result, -1); + } + else + { + thread_local dumb_stringstream<T> S; + // Visual Studio 2017 seems to fail on repeated conversions if the + // clear() is done before the seekg(). Still don't know why! See #124 + // and #125. + S.seekg(0); + S.clear(); + S.str(Str); + ok = static_cast<bool>(S >> result); + } + break; + } + + if (not ok) + throw pqxx::conversion_error{ + "Could not convert string to numeric value: '" + + std::string{Str} + "'."}; + + Obj = result; +} +} // namespace +#endif // !PQXX_HAVE_CHARCONV_FLOAT + + +#if !defined(PQXX_HAVE_CHARCONV_INT) +namespace +{ +template<typename T> inline std::string to_string_unsigned(T Obj) +{ + if (not Obj) return "0"; + + // Every byte of width on T adds somewhere between 3 and 4 digits to the + // maximum length of our decimal string. + char buf[4*sizeof(T)+1]; + + char *p = &buf[sizeof(buf)]; + *--p = '\0'; + while (Obj > 0) + { + *--p = number_to_digit(int(Obj%10)); + Obj = T(Obj / 10); + } + return p; +} +} // namespace +#endif // !PQXX_HAVE_CHARCONV_INT + + +#if !defined(PQXX_HAVE_CHARCONV_INT) || !defined(PQXX_HAVE_CHARCONV_FLOAT) +namespace +{ +template<typename T> inline std::string to_string_fallback(T Obj) +{ + thread_local dumb_stringstream<T> S; + S.str(""); + S << Obj; + return S.str(); +} +} // namespace +#endif // !PQXX_HAVE_CHARCONV_INT || !PQXX_HAVE_CHARCONV_FLOAT + + +#if !defined(PQXX_HAVE_CHARCONV_FLOAT) +namespace +{ +template<typename T> inline std::string to_string_float(T Obj) +{ + if (std::isnan(Obj)) return "nan"; + if (std::isinf(Obj)) return Obj > 0 ? "infinity" : "-infinity"; + return to_string_fallback(Obj); +} +} // namespace +#endif // !PQXX_HAVE_CHARCONV_FLOAT + + +#if !defined(PQXX_HAVE_CHARCONV_INT) +namespace +{ +template<typename T> inline std::string to_string_signed(T Obj) +{ + if (Obj < 0) + { + // Remember--the smallest negative number for a given two's-complement type + // cannot be negated. + const bool negatable = (Obj != std::numeric_limits<T>::min()); + if (negatable) + return '-' + to_string_unsigned(-Obj); + else + return to_string_fallback(Obj); + } + + return to_string_unsigned(Obj); +} +} // namespace +#endif // !PQXX_HAVE_CHARCONV_INT + + +#if defined(PQXX_HAVE_CHARCONV_INT) +namespace pqxx +{ +template void +builtin_traits<short>::from_string(const char[], short &); +template void +builtin_traits<unsigned short>::from_string(const char[], unsigned short &); +template void +builtin_traits<int>::from_string(const char[], int &); +template void +builtin_traits<unsigned int>::from_string(const char[], unsigned int &); +template void +builtin_traits<long>::from_string(const char[], long &); +template void +builtin_traits<unsigned long>::from_string(const char[], unsigned long &); +template void +builtin_traits<long long>::from_string(const char[], long long &); +template void +builtin_traits<unsigned long long>::from_string( + const char[], unsigned long long &); +} // namespace pqxx +#endif // PQXX_HAVE_CHARCONV_INT + + +#if defined(PQXX_HAVE_CHARCONV_FLOAT) +namespace pqxx +{ +template +void string_traits<float>::from_string(const char Str[], float &Obj); +template +void string_traits<double>::from_string(const char Str[], double &Obj); +template +void string_traits<long double>::from_string( + const char Str[], + long double &Obj); +} // namespace pqxx +#endif // PQXX_HAVE_CHARCONV_FLOAT + + +#if defined(PQXX_HAVE_CHARCONV_INT) +namespace pqxx +{ +namespace internal +{ +template +std::string builtin_traits<short>::to_string(short Obj); +template +std::string builtin_traits<unsigned short>::to_string(unsigned short Obj); +template +std::string builtin_traits<int>::to_string(int Obj); +template +std::string builtin_traits<unsigned int>::to_string(unsigned int Obj); +template +std::string builtin_traits<long>::to_string(long Obj); +template +std::string builtin_traits<unsigned long>::to_string(unsigned long Obj); +template +std::string builtin_traits<long long>::to_string(long long Obj); +template +std::string builtin_traits<unsigned long long>::to_string( + unsigned long long Obj); +} // namespace pqxx::internal +} // namespace pqxx +#endif // PQXX_HAVE_CHARCONV_INT + + +#if defined(PQXX_HAVE_CHARCONV_FLOAT) +namespace pqxx +{ +namespace internal +{ +template +std::string builtin_traits<float>::to_string(float Obj); +template +std::string builtin_traits<double>::to_string(double Obj); +template +std::string builtin_traits<long double>::to_string(long double Obj); +} // namespace pqxx::internal +} // namespace pqxx +#endif // PQXX_HAVE_CHARCONV_FLOAT + + +#if !defined(PQXX_HAVE_CHARCONV_INT) +namespace pqxx +{ +namespace internal +{ +template<> +void builtin_traits<short>::from_string(const char Str[], short &Obj) + { from_string_signed(Str, Obj); } +template<> +std::string builtin_traits<short>::to_string(short Obj) + { return to_string_signed(Obj); } +template<> +void builtin_traits<unsigned short>::from_string( + const char Str[], + unsigned short &Obj) + { from_string_unsigned(Str, Obj); } +template<> +std::string builtin_traits<unsigned short>::to_string(unsigned short Obj) + { return to_string_unsigned(Obj); } +template<> +void builtin_traits<int>::from_string(const char Str[], int &Obj) + { from_string_signed(Str, Obj); } +template<> +std::string builtin_traits<int>::to_string(int Obj) + { return to_string_signed(Obj); } +template<> +void builtin_traits<unsigned int>::from_string( + const char Str[], + unsigned int &Obj) + { from_string_unsigned(Str, Obj); } +template<> +std::string builtin_traits<unsigned int>::to_string(unsigned int Obj) + { return to_string_unsigned(Obj); } +template<> +void builtin_traits<long>::from_string(const char Str[], long &Obj) + { from_string_signed(Str, Obj); } +template<> +std::string builtin_traits<long>::to_string(long Obj) + { return to_string_signed(Obj); } +template<> +void builtin_traits<unsigned long>::from_string( + const char Str[], + unsigned long &Obj) + { from_string_unsigned(Str, Obj); } +template<> +std::string builtin_traits<unsigned long>::to_string(unsigned long Obj) + { return to_string_unsigned(Obj); } +template<> +void builtin_traits<long long>::from_string(const char Str[], long long &Obj) + { from_string_signed(Str, Obj); } +template<> +std::string builtin_traits<long long>::to_string(long long Obj) + { return to_string_signed(Obj); } +template<> +void builtin_traits<unsigned long long>::from_string( + const char Str[], + unsigned long long &Obj) + { from_string_unsigned(Str, Obj); } +template<> +std::string builtin_traits<unsigned long long>::to_string( + unsigned long long Obj) + { return to_string_unsigned(Obj); } +} // namespace pqxx::internal +} // namespace pqxx +#endif // !PQXX_HAVE_CHARCONV_INT + + +#if !defined(PQXX_HAVE_CHARCONV_FLOAT) +namespace pqxx +{ +namespace internal +{ +template<> +void builtin_traits<float>::from_string(const char Str[], float &Obj) + { from_string_float(Str, Obj); } +template<> +std::string builtin_traits<float>::to_string(float Obj) + { return to_string_float(Obj); } +template<> +void builtin_traits<double>::from_string(const char Str[], double &Obj) + { from_string_float(Str, Obj); } +template<> +std::string builtin_traits<double>::to_string(double Obj) + { return to_string_float(Obj); } +template<> +void builtin_traits<long double>::from_string( + const char Str[], long double &Obj) + { from_string_float(Str, Obj); } +template<> +std::string builtin_traits<long double>::to_string(long double Obj) + { return to_string_float(Obj); } +} // namespace pqxx::internal +} // namespace pqxx +#endif // !PQXX_HAVE_CHARCONV_FLOAT + + +namespace pqxx +{ +namespace internal +{ +template<> void builtin_traits<bool>::from_string(const char Str[], bool &Obj) +{ + bool OK, result=false; + + switch (Str[0]) + { + case 0: + result = false; + OK = true; + break; + + case 'f': + case 'F': + result = false; + OK = not ( + (Str[1] != '\0') and + (not equal(Str+1, "alse")) and + (not equal(Str+1, "ALSE"))); + break; + + case '0': + { + int I; + string_traits<int>::from_string(Str, I); + result = (I != 0); + OK = ((I == 0) or (I == 1)); + } + break; + + case '1': + result = true; + OK = (Str[1] == '\0'); + break; + + case 't': + case 'T': + result = true; + OK = not ( + (Str[1] != '\0') and + (not equal(Str+1, "rue")) and + (not equal(Str+1, "RUE"))); + break; + + default: + OK = false; + } + + if (not OK) + throw conversion_error{ + "Failed conversion to bool: '" + std::string{Str} + "'."}; + + Obj = result; +} + + +template<> std::string builtin_traits<bool>::to_string(bool Obj) +{ + return Obj ? "true" : "false"; +} +} // namespace pqxx::internal +} // namespace pqxx diff --git a/contrib/libs/libpqxx/src/stream_base.cxx b/contrib/libs/libpqxx/src/stream_base.cxx new file mode 100644 index 0000000000..598c2260e4 --- /dev/null +++ b/contrib/libs/libpqxx/src/stream_base.cxx @@ -0,0 +1,43 @@ +/** Implementation of the pqxx::stream_base class. + * + * pqxx::stream_base provides optimized batch access to a database table. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/stream_base.hxx" +#include "pqxx/transaction" + + +pqxx::stream_base::stream_base(transaction_base &tb) : + internal::namedclass("stream_base"), + internal::transactionfocus{tb}, + m_finished{false} +{} + + +pqxx::stream_base::operator bool() const noexcept +{ + return not m_finished; +} + + +bool pqxx::stream_base::operator!() const noexcept +{ + return not static_cast<bool>(*this); +} + + +void pqxx::stream_base::close() +{ + if (*this) + { + m_finished = true; + unregister_me(); + } +} diff --git a/contrib/libs/libpqxx/src/stream_from.cxx b/contrib/libs/libpqxx/src/stream_from.cxx new file mode 100644 index 0000000000..8dbe2df3f3 --- /dev/null +++ b/contrib/libs/libpqxx/src/stream_from.cxx @@ -0,0 +1,261 @@ +/** Implementation of the pqxx::stream_from class. + * + * pqxx::stream_from enables optimized batch reads from a database table. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/stream_from" + +#include "pqxx/internal/encodings.hxx" +#include "pqxx/internal/gates/transaction-stream_from.hxx" + + +namespace +{ + // bool is_octalchar(char o) noexcept + // { + // return (o>='0') and (o<='7'); + // } + + /// Find first tab character at or after start position in string + /** If not found, returns line.size() rather than string::npos. + */ + std::string::size_type find_tab( + pqxx::internal::encoding_group enc, + const std::string &line, + std::string::size_type start + ) + { + auto here = pqxx::internal::find_with_encoding(enc, line, '\t', start); + return (here == std::string::npos) ? line.size() : here; + } +} // namespace + + +pqxx::stream_from::stream_from( + transaction_base &tb, + const std::string &table_name +) : + namedclass{"stream_from", table_name}, + stream_base{tb}, + m_retry_line{false} +{ + set_up(tb, table_name); +} + + +pqxx::stream_from::~stream_from() noexcept +{ + try + { + complete(); + } + catch (const std::exception &e) + { + reg_pending_error(e.what()); + } +} + + +void pqxx::stream_from::complete() +{ + close(); +} + + +bool pqxx::stream_from::get_raw_line(std::string &line) +{ + internal::gate::transaction_stream_from gate{m_trans}; + if (*this) + try + { + if (not gate.read_copy_line(line)) close(); + } + catch (const std::exception &) + { + close(); + throw; + } + return *this; +} + + +void pqxx::stream_from::set_up( + transaction_base &tb, + const std::string &table_name +) +{ + set_up(tb, table_name, ""); +} + + +void pqxx::stream_from::set_up( + transaction_base &tb, + const std::string &table_name, + const std::string &columns +) +{ + // Get the encoding before starting the COPY, otherwise reading the the + // variable will interrupt it + m_copy_encoding = internal::enc_group(m_trans.conn().encoding_id()); + internal::gate::transaction_stream_from{tb}.BeginCopyRead( + table_name, + columns + ); + register_me(); +} + + +void pqxx::stream_from::close() +{ + pqxx::stream_base::close(); + try + { + // Flush any remaining lines + std::string s; + while (get_raw_line(s)); + } + catch (const broken_connection &) + { + try + { + pqxx::stream_base::close(); + } + catch (const std::exception &) {} + throw; + } + catch (const std::exception &e) + { + reg_pending_error(e.what()); + } +} + + +bool pqxx::stream_from::extract_field( + const std::string &line, + std::string::size_type &i, + std::string &s +) const +{ + using namespace pqxx::internal; + + const auto next_seq = get_glyph_scanner(m_copy_encoding); + s.clear(); + bool is_null{false}; + auto stop = find_tab(m_copy_encoding, line, i); + while (i < stop) + { + auto glyph_end = next_seq(line.c_str(), line.size(), i); + auto seq_len = glyph_end - i; + if (seq_len == 1) + { + switch (line[i]) + { + case '\n': + // End-of-row; shouldn't happen, but we may get old-style + // newline-terminated lines. + i = stop; + break; + + case '\\': + { + // Escape sequence. + if (glyph_end >= line.size()) + throw failure{"Row ends in backslash"}; + char n = line[glyph_end++]; + + /* + * "Presently, COPY TO will never emit an octal or hex-digits + * backslash sequence [...]" + * - https://www.postgresql.org/docs/10/sql-copy.html + */ + // if (is_octalchar(n)) + // { + // if (here.end_byte+2 >= line.size()) + // throw failure{"Row ends in middle of octal value"}; + // char n1 = line[here.end_byte++]; + // char n2 = line[here.end_byte++]; + // if (not is_octalchar(n1) or not is_octalchar(n2)) + // throw failure{ + // "Invalid octal in encoded table stream" + // }; + // s += ( + // (digit_to_number(n)<<6) | + // (digit_to_number(n1)<<3) | + // digit_to_number(n2) + // ); + // break; + // } + // else + switch (n) + { + case 'N': + // Null value + if (not s.empty()) + throw failure{ + "Null sequence found in nonempty field" + }; + is_null = true; + break; + + case 'b': // Backspace + s += '\b'; break; + case 'f': // Vertical tab + s += '\f'; break; + case 'n': // Form feed + s += '\n'; break; + case 'r': // Newline + s += '\r'; break; + case 't': // Tab + s += '\t'; break; + case 'v': // Carriage return + s += '\v'; break; + + default: + // Self-escaped character + s += n; + break; + } + } + break; + + default: + s += line[i]; + break; + } + } + else + { + // Multi-byte sequence; never treated specially, so just append + s.insert(s.size(), line.c_str() + i, seq_len); + } + + i = glyph_end; + } + + // Skip field separator + i += 1; + + return not is_null; +} + +template<> void pqxx::stream_from::extract_value<std::nullptr_t>( + const std::string &line, + std::nullptr_t&, + std::string::size_type &here, + std::string &workspace +) const +{ + if (extract_field(line, here, workspace)) + throw pqxx::conversion_error{ + "Attempt to convert non-null '" + + workspace + + "' to null" + }; +} diff --git a/contrib/libs/libpqxx/src/stream_to.cxx b/contrib/libs/libpqxx/src/stream_to.cxx new file mode 100644 index 0000000000..18e52b1e6a --- /dev/null +++ b/contrib/libs/libpqxx/src/stream_to.cxx @@ -0,0 +1,142 @@ +/** Implementation of the pqxx::stream_to class. + * + * pqxx::stream_to enables optimized batch updates to a database table. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/stream_to.hxx" + +#include "pqxx/internal/gates/transaction-stream_to.hxx" + + +pqxx::stream_to::stream_to( + transaction_base &tb, + const std::string &table_name +) : + namedclass{"stream_to", table_name}, + stream_base{tb} +{ + set_up(tb, table_name); +} + + +pqxx::stream_to::~stream_to() noexcept +{ + try + { + complete(); + } + catch (const std::exception &e) + { + reg_pending_error(e.what()); + } +} + + +void pqxx::stream_to::complete() +{ + close(); +} + + +void pqxx::stream_to::write_raw_line(const std::string &line) +{ + internal::gate::transaction_stream_to{m_trans}.write_copy_line(line); +} + + +pqxx::stream_to & pqxx::stream_to::operator<<(stream_from &tr) +{ + std::string line; + while (tr) + { + tr.get_raw_line(line); + write_raw_line(line); + } + return *this; +} + + +void pqxx::stream_to::set_up( + transaction_base &tb, + const std::string &table_name +) +{ + set_up(tb, table_name, ""); +} + + +void pqxx::stream_to::set_up( + transaction_base &tb, + const std::string &table_name, + const std::string &columns +) +{ + internal::gate::transaction_stream_to{tb}.BeginCopyWrite( + table_name, + columns + ); + register_me(); +} + + +void pqxx::stream_to::close() +{ + if (*this) + { + stream_base::close(); + try + { + internal::gate::transaction_stream_to{m_trans}.end_copy_write(); + } + catch (const std::exception &) + { + try + { + stream_base::close(); + } + catch (const std::exception &) {} + throw; + } + } +} + + +std::string pqxx::internal::TypedCopyEscaper::escape(const std::string &s) +{ + if (s.empty()) + return s; + + std::string escaped; + escaped.reserve(s.size()+1); + + for (auto c : s) + switch (c) + { + case '\b': escaped += "\\b"; break; // Backspace + case '\f': escaped += "\\f"; break; // Vertical tab + case '\n': escaped += "\\n"; break; // Form feed + case '\r': escaped += "\\r"; break; // Newline + case '\t': escaped += "\\t"; break; // Tab + case '\v': escaped += "\\v"; break; // Carriage return + case '\\': escaped += "\\\\"; break; // Backslash + default: + if (c < ' ' or c > '~') + { + escaped += "\\"; + for (auto i = 2; i >= 0; --i) + escaped += number_to_digit((c >> (3*i)) & 0x07); + } + else + escaped += c; + break; + } + + return escaped; +} diff --git a/contrib/libs/libpqxx/src/subtransaction.cxx b/contrib/libs/libpqxx/src/subtransaction.cxx new file mode 100644 index 0000000000..539c3c7847 --- /dev/null +++ b/contrib/libs/libpqxx/src/subtransaction.cxx @@ -0,0 +1,74 @@ +/** Implementation of the pqxx::subtransaction class. + * + * pqxx::transaction is a nested transaction, i.e. one within a transaction + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <stdexcept> + +#include "pqxx/connection_base" +#include "pqxx/subtransaction" + +#include "pqxx/internal/gates/transaction-subtransaction.hxx" + +using namespace pqxx::internal; + + +pqxx::subtransaction::subtransaction( + dbtransaction &T, + const std::string &Name) : + namedclass{"subtransaction", T.conn().adorn_name(Name)}, + transactionfocus{T}, + dbtransaction(T.conn(), false), + m_parent{T} +{ +} + + +namespace +{ +using dbtransaction_ref = pqxx::dbtransaction &; +} + + +pqxx::subtransaction::subtransaction( + subtransaction &T, + const std::string &Name) : + subtransaction(dbtransaction_ref(T), Name) +{ +} + + +void pqxx::subtransaction::do_begin() +{ + try + { + direct_exec(("SAVEPOINT " + quote_name(name())).c_str()); + } + catch (const sql_error &) + { + throw; + } +} + + +void pqxx::subtransaction::do_commit() +{ + const int ra = m_reactivation_avoidance.get(); + m_reactivation_avoidance.clear(); + direct_exec(("RELEASE SAVEPOINT " + quote_name(name())).c_str()); + gate::transaction_subtransaction{m_parent}.add_reactivation_avoidance_count( + ra); +} + + +void pqxx::subtransaction::do_abort() +{ + direct_exec(("ROLLBACK TO SAVEPOINT " + quote_name(name())).c_str()); +} diff --git a/contrib/libs/libpqxx/src/tablereader.cxx b/contrib/libs/libpqxx/src/tablereader.cxx new file mode 100644 index 0000000000..4e4f315c66 --- /dev/null +++ b/contrib/libs/libpqxx/src/tablereader.cxx @@ -0,0 +1,227 @@ +/** Implementation of the pqxx::tablereader class. + * + * pqxx::tablereader enables optimized batch reads from a database table. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/tablereader" +#include "pqxx/transaction" + +#include "pqxx/internal/gates/transaction-tablereader.hxx" + +using namespace pqxx::internal; + + +pqxx::tablereader::tablereader( + transaction_base &T, + const std::string &Name, + const std::string &Null) : + namedclass{"tablereader", Name}, + tablestream(T, Null), + m_done{true} +{ + set_up(T, Name); +} + +void pqxx::tablereader::set_up( + transaction_base &T, + const std::string &Name, + const std::string &Columns) +{ + gate::transaction_tablereader{T}.BeginCopyRead(Name, Columns); + register_me(); + m_done = false; +} + +pqxx::tablereader::~tablereader() noexcept +{ + try + { + reader_close(); + } + catch (const std::exception &e) + { + reg_pending_error(e.what()); + } +} + + +bool pqxx::tablereader::get_raw_line(std::string &Line) +{ + if (not m_done) try + { + m_done = not gate::transaction_tablereader{m_trans}.read_copy_line(Line); + } + catch (const std::exception &) + { + m_done = true; + throw; + } + return not m_done; +} + + +void pqxx::tablereader::complete() +{ + reader_close(); +} + + +void pqxx::tablereader::reader_close() +{ + if (not is_finished()) + { + base_close(); + + // If any lines remain to be read, consume them to not confuse PQendcopy() + if (not m_done) + { + try + { + std::string Dummy; + while (get_raw_line(Dummy)) ; + } + catch (const broken_connection &) + { + try { base_close(); } catch (const std::exception &) {} + throw; + } + catch (const std::exception &e) + { + reg_pending_error(e.what()); + } + } + } +} + + +namespace +{ +inline bool is_octalchar(char o) noexcept +{ + return (o>='0') and (o<='7'); +} + +/// Find first tab character at or after start position in string +/** If not found, returns Line.size() rather than string::npos. + */ +std::string::size_type findtab( + const std::string &Line, + std::string::size_type start) +{ + // TODO: Fix for multibyte encodings? + const auto here = Line.find('\t', start); + return (here == std::string::npos) ? Line.size() : here; +} +} // namespace + + +std::string pqxx::tablereader::extract_field( + const std::string &Line, + std::string::size_type &i) const +{ + // TODO: Pick better exception types + std::string R; + bool isnull=false; + auto stop = findtab(Line, i); + for (; i < stop; ++i) + { + const char c = Line[i]; + switch (c) + { + case '\n': // End of row + // Shouldn't happen, but we may get old-style, newline-terminated lines + i = stop; + break; + + case '\\': // Escape sequence + { + const char n = Line[++i]; + if (i >= Line.size()) + throw failure{"Row ends in backslash."}; + + switch (n) + { + case 'N': // Null value + if (not R.empty()) + throw failure{"Null sequence found in nonempty field."}; + R = NullStr(); + isnull = true; + break; + + case '0': // Octal sequence (3 digits) + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + if ((i+2) >= Line.size()) + throw failure{"Row ends in middle of octal value."}; + const char n1 = Line[++i]; + const char n2 = Line[++i]; + if (not is_octalchar(n1) or not is_octalchar(n2)) + throw failure{"Invalid octal in encoded table stream."}; + R += char( + (digit_to_number(n)<<6) | + (digit_to_number(n1)<<3) | + digit_to_number(n2)); + } + break; + + case 'b': + // TODO: Escape code? + R += char(8); + break; // Backspace + case 'v': + // TODO: Escape code? + R += char(11); + break; // Vertical tab + case 'f': + // TODO: Escape code? + R += char(12); + break; // Form feed + case 'n': + R += '\n'; + break; // Newline + case 't': + R += '\t'; + break; // Tab + case 'r': + R += '\r'; + break; // Carriage return; + + default: // Self-escaped character + R += n; + // This may be a self-escaped tab that we thought was a terminator... + if (i == stop) + { + if ((i+1) >= Line.size()) + throw internal_error{"COPY line ends in backslash."}; + stop = findtab(Line, i+1); + } + break; + } + } + break; + + default: + R += c; + break; + } + } + ++i; + + if (isnull and (R.size() != NullStr().size())) + throw failure{"Field contains data behind null sequence."}; + + return R; +} diff --git a/contrib/libs/libpqxx/src/tablestream.cxx b/contrib/libs/libpqxx/src/tablestream.cxx new file mode 100644 index 0000000000..6ab0148e1e --- /dev/null +++ b/contrib/libs/libpqxx/src/tablestream.cxx @@ -0,0 +1,38 @@ +/** Implementation of the pqxx::tablestream class. + * + * pqxx::tablestream provides optimized batch access to a database table. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/tablestream" +#include "pqxx/transaction" + + +pqxx::tablestream::tablestream(transaction_base &STrans, + const std::string &Null) : + internal::namedclass{"tablestream"}, + internal::transactionfocus{STrans}, + m_null{Null} +{ +} + + +pqxx::tablestream::~tablestream() noexcept +{ +} + + +void pqxx::tablestream::base_close() +{ + if (not is_finished()) + { + m_finished = true; + unregister_me(); + } +} diff --git a/contrib/libs/libpqxx/src/tablewriter.cxx b/contrib/libs/libpqxx/src/tablewriter.cxx new file mode 100644 index 0000000000..3ca5dae253 --- /dev/null +++ b/contrib/libs/libpqxx/src/tablewriter.cxx @@ -0,0 +1,160 @@ +/** Implementation of the pqxx::tablewriter class. + * + * pqxx::tablewriter enables optimized batch updates to a database table. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/tablereader" +#include "pqxx/tablewriter" +#include "pqxx/transaction" + +#include "pqxx/internal/gates/transaction-tablewriter.hxx" + +using namespace pqxx::internal; + + +pqxx::tablewriter::tablewriter( + transaction_base &T, + const std::string &WName, + const std::string &Null) : + namedclass{"tablewriter", WName}, + tablestream(T, Null) +{ + set_up(T, WName); +} + + +pqxx::tablewriter::~tablewriter() noexcept +{ + try + { + writer_close(); + } + catch (const std::exception &e) + { + reg_pending_error(e.what()); + } +} + + +void pqxx::tablewriter::set_up( + transaction_base &T, + const std::string &WName, + const std::string &Columns) +{ + gate::transaction_tablewriter{T}.BeginCopyWrite(WName, Columns); + register_me(); +} + + +pqxx::tablewriter &pqxx::tablewriter::operator<<(pqxx::tablereader &R) +{ + std::string Line; + // TODO: Can we do this in binary mode? (Might require protocol version check) + while (R.get_raw_line(Line)) write_raw_line(Line); + return *this; +} + + +void pqxx::tablewriter::write_raw_line(const std::string &Line) +{ + const std::string::size_type len = Line.size(); + gate::transaction_tablewriter{m_trans}.write_copy_line( + ((len == 0) or (Line[len-1] != '\n')) ? + Line : + std::string{Line, 0, len-1}); +} + + +void pqxx::tablewriter::complete() +{ + writer_close(); +} + + +void pqxx::tablewriter::writer_close() +{ + if (not is_finished()) + { + base_close(); + try + { + gate::transaction_tablewriter{m_trans}.end_copy_write(); + } + catch (const std::exception &) + { + try { base_close(); } catch (const std::exception &) {} + throw; + } + } +} + + +namespace +{ +inline char escapechar(char i) noexcept +{ + char r = '\0'; + switch (i) + { + case 8: r='b'; break; // backspace + case 11: r='v'; break; // vertical tab + case 12: r='f'; break; // form feed + case '\n': r='n'; break; // newline + case '\t': r='t'; break; // tab + case '\r': r='r'; break; // carriage return + case '\\': r='\\'; break; // backslash + } + return r; +} + +inline bool unprintable(char i) noexcept +{ + return i < ' ' or i > '~'; +} + +inline char tooctdigit(char c, int n) +{ + using unsigned_char = unsigned char; + unsigned int i = unsigned_char(c); + return number_to_digit((i>>(3*n)) & 0x07); +} +} // namespace + + +std::string pqxx::internal::escape( + const std::string &s, + const std::string &null) +{ + if (s == null) return "\\N"; + if (s.empty()) return s; + + std::string R; + R.reserve(s.size()+1); + + for (const auto c: s) + { + const char e = escapechar(c); + if (e) + { + R += '\\'; + R += e; + } + else if (unprintable(c)) + { + R += "\\"; + for (int n=2; n>=0; --n) R += tooctdigit(c, n); + } + else + { + R += c; + } + } + return R; +} diff --git a/contrib/libs/libpqxx/src/transaction.cxx b/contrib/libs/libpqxx/src/transaction.cxx new file mode 100644 index 0000000000..ff0f469e6c --- /dev/null +++ b/contrib/libs/libpqxx/src/transaction.cxx @@ -0,0 +1,72 @@ +/** Implementation of the pqxx::transaction class. + * + * pqxx::transaction represents a regular database transaction. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <stdexcept> + +#include "pqxx/connection_base" +#include "pqxx/result" +#include "pqxx/transaction" + + +pqxx::internal::basic_transaction::basic_transaction( + connection_base &C, + const std::string &IsolationLevel, + readwrite_policy rw) : + namedclass{"transaction"}, + dbtransaction(C, IsolationLevel, rw) +{ +} + + +void pqxx::internal::basic_transaction::do_commit() +{ + try + { + direct_exec("COMMIT"); + } + catch (const statement_completion_unknown &e) + { + // Outcome of "commit" is unknown. This is a disaster: we don't know the + // resulting state of the database. + process_notice(e.what() + std::string{"\n"}); + const std::string msg = + "WARNING: Commit of transaction '" + name() + "' is unknown. " + "There is no way to tell whether the transaction succeeded " + "or was aborted except to check manually."; + process_notice(msg + "\n"); + throw in_doubt_error{msg}; + } + catch (const std::exception &e) + { + if (not conn().is_open()) + { + // We've lost the connection while committing. There is just no way of + // telling what happened on the other end. >8-O + process_notice(e.what() + std::string{"\n"}); + + const std::string Msg = + "WARNING: Connection lost while committing transaction " + "'" + name() + "'. " + "There is no way to tell whether the transaction succeeded " + "or was aborted except to check manually."; + + process_notice(Msg + "\n"); + throw in_doubt_error{Msg}; + } + else + { + // Commit failed--probably due to a constraint violation or something + // similar. + throw; + } + } +} diff --git a/contrib/libs/libpqxx/src/transaction_base.cxx b/contrib/libs/libpqxx/src/transaction_base.cxx new file mode 100644 index 0000000000..84a7ab7e1b --- /dev/null +++ b/contrib/libs/libpqxx/src/transaction_base.cxx @@ -0,0 +1,577 @@ +/** Common code and definitions for the transaction classes. + * + * pqxx::transaction_base defines the interface for any abstract class that + * represents a database transaction. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <cstring> +#include <stdexcept> + +#include "pqxx/connection_base" +#include "pqxx/result" +#include "pqxx/transaction_base" + +#include "pqxx/internal/gates/connection-transaction.hxx" +#include "pqxx/internal/gates/connection-parameterized_invocation.hxx" +#include "pqxx/internal/gates/transaction-transactionfocus.hxx" + +#include "pqxx/internal/encodings.hxx" + + +using namespace pqxx::internal; + + +pqxx::internal::parameterized_invocation::parameterized_invocation( + connection_base &c, + const std::string &query) : + m_home{c}, + m_query{query} +{ +} + + +pqxx::result pqxx::internal::parameterized_invocation::exec() +{ + std::vector<const char *> values; + std::vector<int> lengths; + std::vector<int> binaries; + const int elements = marshall(values, lengths, binaries); + + return gate::connection_parameterized_invocation{m_home}.parameterized_exec( + m_query, + values.data(), + lengths.data(), + binaries.data(), + elements); +} + + +pqxx::transaction_base::transaction_base(connection_base &C, bool direct) : + namedclass{"transaction_base"}, + m_conn{C} +{ + if (direct) + { + gate::connection_transaction gate{conn()}; + gate.register_transaction(this); + m_registered = true; + } +} + + +pqxx::transaction_base::~transaction_base() +{ + try + { + reactivation_avoidance_clear(); + if (not m_pending_error.empty()) + process_notice("UNPROCESSED ERROR: " + m_pending_error + "\n"); + + if (m_registered) + { + m_conn.process_notice(description() + " was never closed properly!\n"); + gate::connection_transaction gate{conn()}; + gate.unregister_transaction(this); + } + } + catch (const std::exception &e) + { + try + { + process_notice(std::string{e.what()} + "\n"); + } + catch (const std::exception &) + { + process_notice(e.what()); + } + } +} + + +void pqxx::transaction_base::commit() +{ + CheckPendingError(); + + // Check previous status code. Caller should only call this function if + // we're in "implicit" state, but multiple commits are silently accepted. + switch (m_status) + { + case st_nascent: // Empty transaction. No skin off our nose. + return; + + case st_active: // Just fine. This is what we expect. + break; + + case st_aborted: + throw usage_error{"Attempt to commit previously aborted " + description()}; + + case st_committed: + // Transaction has been committed already. This is not exactly proper + // behaviour, but throwing an exception here would only give the impression + // that an abort is needed--which would only confuse things further at this + // stage. + // Therefore, multiple commits are accepted, though under protest. + m_conn.process_notice(description() + " committed more than once.\n"); + return; + + case st_in_doubt: + // Transaction may or may not have been committed. The only thing we can + // really do is keep telling the caller that the transaction is in doubt. + throw in_doubt_error{ + description() + " committed again while in an indeterminate state."}; + + default: + throw internal_error{"pqxx::transaction: invalid status code."}; + } + + // Tricky one. If stream is nested in transaction but inside the same scope, + // the commit() will come before the stream is closed. Which means the + // commit is premature. Punish this swiftly and without fail to discourage + // the habit from forming. + if (m_focus.get()) + throw failure{ + "Attempt to commit " + description() + " with " + + m_focus.get()->description() + " still open."}; + + // 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 + // already, the commit would fail anyway but this way at least we don't 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."}; + + try + { + do_commit(); + m_status = st_committed; + } + catch (const in_doubt_error &) + { + m_status = st_in_doubt; + throw; + } + catch (const std::exception &) + { + m_status = st_aborted; + throw; + } + + gate::connection_transaction gate{conn()}; + gate.add_variables(m_vars); + + End(); +} + + +void pqxx::transaction_base::abort() +{ + // Check previous status code. Quietly accept multiple aborts to + // simplify emergency bailout code. + switch (m_status) + { + case st_nascent: // Never began transaction. No need to issue rollback. + break; + + case st_active: + try { do_abort(); } catch (const std::exception &) { } + break; + + case st_aborted: + return; + + case st_committed: + throw usage_error{"Attempt to abort previously committed " + description()}; + + case st_in_doubt: + // Aborting an in-doubt transaction is probably a reasonably sane response + // to an insane situation. Log it, but do not complain. + m_conn.process_notice( + "Warning: " + description() + " aborted after going into " + "indeterminate state; it may have been executed anyway.\n"); + return; + + default: + throw internal_error{"Invalid transaction status."}; + } + + m_status = st_aborted; + End(); +} + + +std::string pqxx::transaction_base::esc_raw(const std::string &str) const +{ + const unsigned char *p = reinterpret_cast<const unsigned char *>(str.c_str()); + return conn().esc_raw(p, str.size()); +} + + +std::string pqxx::transaction_base::quote_raw(const std::string &str) const +{ + const unsigned char *p = reinterpret_cast<const unsigned char *>(str.c_str()); + return conn().quote_raw(p, str.size()); +} + + +void pqxx::transaction_base::activate() +{ + switch (m_status) + { + case st_nascent: + // Make sure transaction has begun before executing anything + Begin(); + break; + + case st_active: + break; + + case st_committed: + case st_aborted: + case st_in_doubt: + throw usage_error{ + "Attempt to activate " + description() + " " + "which is already closed."}; + + default: + throw internal_error{"pqxx::transaction: invalid status code."}; + } +} + + +pqxx::result pqxx::transaction_base::exec( + const std::string &Query, + const std::string &Desc) +{ + CheckPendingError(); + + const std::string N = (Desc.empty() ? "" : "'" + Desc + "' "); + + if (m_focus.get()) + throw usage_error{ + "Attempt to execute query " + N + + "on " + description() + " " + "with " + m_focus.get()->description() + " still open."}; + + try + { + activate(); + } + catch (const usage_error &e) + { + throw usage_error{"Error executing query " + N + ". " + e.what()}; + } + + // TODO: Pass Desc to do_exec(), and from there on down + return do_exec(Query.c_str()); +} + + +pqxx::result pqxx::transaction_base::exec_n( + size_t rows, + const std::string &Query, + const std::string &Desc) +{ + const result r = exec(Query, Desc); + if (r.size() != rows) + { + const std::string N = (Desc.empty() ? "" : "'" + Desc + "'"); + throw unexpected_rows{ + "Expected " + to_string(rows) + " row(s) of data " + "from query " + N + ", got " + to_string(r.size()) + "."}; + } + return r; +} + + +void pqxx::transaction_base::check_rowcount_prepared( + const std::string &statement, + size_t expected_rows, + size_t actual_rows) +{ + if (actual_rows != expected_rows) + { + throw unexpected_rows{ + "Expected " + to_string(expected_rows) + " row(s) of data " + "from prepared statement '" + statement + "', got " + + to_string(actual_rows) + "."}; + } +} + + +void pqxx::transaction_base::check_rowcount_params( + size_t expected_rows, + size_t actual_rows) +{ + if (actual_rows != expected_rows) + { + throw unexpected_rows{ + "Expected " + to_string(expected_rows) + " row(s) of data " + "from parameterised query, got " + to_string(actual_rows) + "."}; + } +} + + +pqxx::internal::parameterized_invocation +pqxx::transaction_base::parameterized(const std::string &query) +{ +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return internal::parameterized_invocation{conn(), query}; +#include "pqxx/internal/ignore-deprecated-post.hxx" +} + + +pqxx::prepare::invocation +pqxx::transaction_base::prepared(const std::string &statement) +{ + try + { + activate(); + } + catch (const usage_error &e) + { + throw usage_error{ + "Error executing prepared statement " + statement + ". " + e.what()}; + } +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return prepare::invocation{*this, statement}; +#include "pqxx/internal/ignore-deprecated-post.hxx" +} + + +pqxx::result pqxx::transaction_base::internal_exec_prepared( + const std::string &statement, + const internal::params &args, + result_format format) +{ + gate::connection_transaction gate{conn()}; + return gate.exec_prepared(statement, args, format); +} + + +pqxx::result pqxx::transaction_base::internal_exec_params( + const std::string &query, + const internal::params &args) +{ + gate::connection_transaction gate{conn()}; + return gate.exec_params(query, args); +} + + +void pqxx::transaction_base::set_variable( + const std::string &Var, + const std::string &Value) +{ + // Before committing to this new value, see what the backend thinks about it + gate::connection_transaction gate{conn()}; + gate.raw_set_var(Var, Value); + m_vars[Var] = Value; +} + + +std::string pqxx::transaction_base::get_variable(const std::string &Var) +{ + const std::map<std::string,std::string>::const_iterator i = m_vars.find(Var); + if (i != m_vars.end()) return i->second; + return gate::connection_transaction{conn()}.raw_get_var(Var); +} + + +void pqxx::transaction_base::Begin() +{ + if (m_status != st_nascent) + throw internal_error{ + "pqxx::transaction: Begin() called while not in nascent state."}; + + try + { + // Better handle any pending notifications before we begin + m_conn.get_notifs(); + + do_begin(); + m_status = st_active; + } + catch (const std::exception &) + { + End(); + throw; + } +} + + +void pqxx::transaction_base::End() noexcept +{ + try + { + try { CheckPendingError(); } + catch (const std::exception &e) { m_conn.process_notice(e.what()); } + + gate::connection_transaction gate{conn()}; + if (m_registered) + { + m_registered = false; + gate.unregister_transaction(this); + } + + if (m_status != st_active) return; + + if (m_focus.get()) + m_conn.process_notice( + "Closing " + description() + " with " + + m_focus.get()->description() + " still open.\n"); + + try { abort(); } + catch (const std::exception &e) { m_conn.process_notice(e.what()); } + + gate.take_reactivation_avoidance(m_reactivation_avoidance.get()); + m_reactivation_avoidance.clear(); + } + catch (const std::exception &e) + { + try { m_conn.process_notice(e.what()); } catch (const std::exception &) {} + } +} + + +void pqxx::transaction_base::register_focus(internal::transactionfocus *S) +{ + m_focus.register_guest(S); +} + + +void pqxx::transaction_base::unregister_focus(internal::transactionfocus *S) + noexcept +{ + try + { + m_focus.unregister_guest(S); + } + catch (const std::exception &e) + { + m_conn.process_notice(std::string{e.what()} + "\n"); + } +} + + +pqxx::result pqxx::transaction_base::direct_exec(const char C[], int Retries) +{ + CheckPendingError(); + return gate::connection_transaction{conn()}.exec(C, Retries); +} + + +void pqxx::transaction_base::register_pending_error(const std::string &Err) + noexcept +{ + if (m_pending_error.empty() and not Err.empty()) + { + try + { + m_pending_error = Err; + } + catch (const std::exception &e) + { + try + { + process_notice("UNABLE TO PROCESS ERROR\n"); + process_notice(e.what()); + process_notice("ERROR WAS:"); + process_notice(Err); + } + catch (...) + { + } + } + } +} + + +void pqxx::transaction_base::CheckPendingError() +{ + if (not m_pending_error.empty()) + { + const std::string Err{m_pending_error}; + m_pending_error.clear(); + throw failure{Err}; + } +} + + +namespace +{ +std::string MakeCopyString( + const std::string &Table, + const std::string &Columns) +{ + std::string Q = "COPY " + Table + " "; + if (not Columns.empty()) Q += "(" + Columns + ") "; + return Q; +} +} // namespace + + +void pqxx::transaction_base::BeginCopyRead( + const std::string &Table, + const std::string &Columns) +{ + exec(MakeCopyString(Table, Columns) + "TO STDOUT"); +} + + +void pqxx::transaction_base::BeginCopyWrite( + const std::string &Table, + const std::string &Columns) +{ + exec(MakeCopyString(Table, Columns) + "FROM STDIN"); +} + + +bool pqxx::transaction_base::read_copy_line(std::string &line) +{ + return gate::connection_transaction{conn()}.read_copy_line(line); +} + + +void pqxx::transaction_base::write_copy_line(const std::string &line) +{ + gate::connection_transaction gate{conn()}; + gate.write_copy_line(line); +} + + +void pqxx::transaction_base::end_copy_write() +{ + gate::connection_transaction gate{conn()}; + gate.end_copy_write(); +} + + +void pqxx::internal::transactionfocus::register_me() +{ + gate::transaction_transactionfocus gate{m_trans}; + gate.register_focus(this); + m_registered = true; +} + + +void pqxx::internal::transactionfocus::unregister_me() noexcept +{ + gate::transaction_transactionfocus gate{m_trans}; + gate.unregister_focus(this); + m_registered = false; +} + +void +pqxx::internal::transactionfocus::reg_pending_error(const std::string &err) + noexcept +{ + gate::transaction_transactionfocus gate{m_trans}; + gate.register_pending_error(err); +} diff --git a/contrib/libs/libpqxx/src/util.cxx b/contrib/libs/libpqxx/src/util.cxx new file mode 100644 index 0000000000..646ace9930 --- /dev/null +++ b/contrib/libs/libpqxx/src/util.cxx @@ -0,0 +1,121 @@ +/** Various utility functions. + * + * Copyright (c) 2000-2019, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this mistake, + * or contact the author. + */ +#include "pqxx/compiler-internal.hxx" + +#include <cerrno> +#include <chrono> +#include <cmath> +#include <cstdlib> +#include <cstring> +#include <new> +#include <thread> + +extern "C" +{ +#include "libpq-fe.h" +} + +#include "pqxx/except" +#include "pqxx/util" + + +using namespace pqxx::internal; + + +pqxx::thread_safety_model pqxx::describe_thread_safety() noexcept +{ + thread_safety_model model; + + if (PQisthreadsafe()) + { + model.safe_libpq = true; + } + else + { + model.safe_libpq = false; + model.description += "Using a libpq build that is not thread-safe.\n"; + } + + // Sadly I'm not aware of any way to avoid this just yet. + model.safe_kerberos = false; + model.description += + "Kerberos is not thread-safe. If your application uses Kerberos, " + "protect all calls to Kerberos or libpqxx using a global lock.\n"; + + return model; +} + + +std::string pqxx::internal::namedclass::description() const +{ + try + { + std::string desc = classname(); + if (not name().empty()) desc += " '" + name() + "'"; + return desc; + } + catch (const std::exception &) + { + // Oops, string composition failed! Probably out of memory. + // Let's try something easier. + } + return name().empty() ? classname() : name(); +} + + +void pqxx::internal::CheckUniqueRegistration(const namedclass *New, + const namedclass *Old) +{ + if (New == nullptr) + throw internal_error{"null pointer registered."}; + if (Old) + { + if (Old == New) + throw usage_error{"Started twice: " + New->description()}; + throw usage_error{ + "Started " + New->description() + " while " + Old->description() + + " still active."}; + } +} + + +void pqxx::internal::CheckUniqueUnregistration(const namedclass *New, + const namedclass *Old) +{ + if (New != Old) + { + if (New == nullptr) + throw usage_error{ + "Expected to close " + Old->description() + ", " + "but got null pointer instead."}; + if (Old == nullptr) + throw usage_error{"Closed while not open: " + New->description()}; + throw usage_error{ + "Closed " + New->description() + "; " + "expected to close " + Old->description()}; + } +} + + +void pqxx::internal::freepqmem(const void *p) noexcept +{ + PQfreemem(const_cast<void *>(p)); +} + + +void pqxx::internal::freemallocmem(const void *p) noexcept +{ + free(const_cast<void *>(p)); +} + + +void pqxx::internal::sleep_seconds(int s) +{ + std::this_thread::sleep_for(std::chrono::seconds(s)); +} diff --git a/contrib/libs/libpqxx/src/version.cxx b/contrib/libs/libpqxx/src/version.cxx new file mode 100644 index 0000000000..1fba1ec6d4 --- /dev/null +++ b/contrib/libs/libpqxx/src/version.cxx @@ -0,0 +1,18 @@ +#include "pqxx/compiler-internal.hxx" + +#include "pqxx/version" + +namespace pqxx +{ +namespace internal +{ +// One, single definition of this function. If a call fails to link, then the +// libpqxx binary was built against a different libpqxx version than the code +// which is being linked against it. +template<> PQXX_LIBEXPORT +int check_library_version<PQXX_VERSION_MAJOR, PQXX_VERSION_MINOR>() noexcept +{ + return 0; +} +} +} |