From d800f4d6fa0f40bc384580cfe3f3f7638fa13a42 Mon Sep 17 00:00:00 2001 From: MeanSquaredError <35379301+MeanSquaredError@users.noreply.github.com> Date: Mon, 7 Aug 2023 20:37:59 +0300 Subject: [PATCH] Cleanup the code of the MySQL connector (#511) * Move the method definitions for sqlpp::postgresql::detail::statement_handle_t inside the class. * Move the method definitions for sqlpp::postgresql::detail::prepared_statement_handle_t inside the class. * Move the method definitions for sqlpp::postgresql::prepared_statement_t inside the class. * Move the method definitions for sqlpp::postgresql::::Result inside the class. * Remove superfluous inline specifier from the in-class method definitions of sqlpp::postgresql::Result. * Remove unused method declaration sqlpp::postgresql::Result::hasError() * Move the method definitions for sqlpp::postgresql::bind_result_t inside the class. * Move the method definitions for sqlpp::postgresql::connection_base inside the class. * Add a comment explaining why sqlpp::postgresql::context_t::escape() is defined out-of-class. * Remove incorrect comment about forward declaration * Remove a superfluous function that forwards the call to PQFinish(). * Replace "std::string" parameters with "const std::string&". * Remove superfluous detail:: namespace qualification. * Rename class/struct types, variables and functions from CamelCase to snake_case. --- include/sqlpp11/postgresql/bind_result.h | 534 +++++++++--------- include/sqlpp11/postgresql/connection.h | 531 ++++++++--------- .../postgresql/detail/connection_handle.h | 7 +- .../detail/prepared_statement_handle.h | 181 +++--- .../sqlpp11/postgresql/prepared_statement.h | 369 ++++++------ include/sqlpp11/postgresql/result.h | 495 ++++++++-------- include/sqlpp11/postgresql/serializer.h | 4 +- 7 files changed, 1000 insertions(+), 1121 deletions(-) diff --git a/include/sqlpp11/postgresql/bind_result.h b/include/sqlpp11/postgresql/bind_result.h index 4146460a..92a1681f 100644 --- a/include/sqlpp11/postgresql/bind_result.h +++ b/include/sqlpp11/postgresql/bind_result.h @@ -56,11 +56,55 @@ namespace sqlpp private: std::shared_ptr _handle; - bool next_impl(); + bool next_impl() + { + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: accessing next row of handle at " << _handle.get() << std::endl; + } + + // Fetch total amount + if (_handle->total_count == 0U) + { + _handle->total_count = _handle->result.records_size(); + if (_handle->total_count == 0U) + return false; + } + else + { + // Next row + if (_handle->count < (_handle->total_count - 1)) + { + _handle->count++; + } + else + { + return false; + } + } + + // Really needed? + if (_handle->fields == 0U) + { + _handle->fields = _handle->result.field_count(); + } + + return true; + } public: bind_result_t() = default; - bind_result_t(const std::shared_ptr& handle); + + bind_result_t(const std::shared_ptr& handle) : _handle(handle) + { + if (this->_handle && this->_handle->debug()) + { + // cerr + std::cerr << "PostgreSQL debug: constructing bind result, using handle at: " << this->_handle.get() + << std::endl; + } + } + bind_result_t(const bind_result_t&) = delete; bind_result_t(bind_result_t&&) = default; bind_result_t& operator=(const bind_result_t&) = delete; @@ -98,314 +142,254 @@ namespace sqlpp } } - void _bind_boolean_result(size_t index, signed char* value, bool* is_null); - void _bind_floating_point_result(size_t index, double* value, bool* is_null); - void _bind_integral_result(size_t index, int64_t* value, bool* is_null); - void _bind_unsigned_integral_result(size_t index, uint64_t* value, bool* is_null); - void _bind_text_result(size_t index, const char** value, size_t* len); - void _bind_date_result(size_t index, ::sqlpp::chrono::day_point* value, bool* is_null); - void _bind_date_time_result(size_t index, ::sqlpp::chrono::microsecond_point* value, bool* is_null); - void _bind_time_of_day_result(size_t index, ::std::chrono::microseconds* value, bool* is_null); - void _bind_blob_result(size_t index, const uint8_t** value, size_t* len); - - int size() const; - }; - - inline bind_result_t::bind_result_t(const std::shared_ptr& handle) : _handle(handle) - { - if (this->_handle && this->_handle->debug()) + void _bind_boolean_result(size_t _index, signed char* value, bool* is_null) { - // cerr - std::cerr << "PostgreSQL debug: constructing bind result, using handle at: " << this->_handle.get() - << std::endl; - } - } - - inline bool bind_result_t::next_impl() - { - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: accessing next row of handle at " << _handle.get() << std::endl; - } - - // Fetch total amount - if (_handle->totalCount == 0U) - { - _handle->totalCount = _handle->result.records_size(); - if (_handle->totalCount == 0U) - return false; - } - else - { - // Next row - if (_handle->count < (_handle->totalCount - 1)) + auto index = static_cast(_index); + if (_handle->debug()) { - _handle->count++; + std::cerr << "PostgreSQL debug: binding boolean result at index: " << index << std::endl; + } + + *is_null = _handle->result.is_null(_handle->count, index); + *value = _handle->result.get_bool_value(_handle->count, index); + } + + void _bind_floating_point_result(size_t _index, double* value, bool* is_null) + { + auto index = static_cast(_index); + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding floating_point result at index: " << index << std::endl; + } + + *is_null = _handle->result.is_null(_handle->count, index); + *value = _handle->result.get_double_value(_handle->count, index); + } + + void _bind_integral_result(size_t _index, int64_t* value, bool* is_null) + { + auto index = static_cast(_index); + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding integral result at index: " << index << std::endl; + } + + *is_null = _handle->result.is_null(_handle->count, index); + *value = _handle->result.get_int64_value(_handle->count, index); + } + + void _bind_unsigned_integral_result(size_t _index, uint64_t* value, bool* is_null) + { + auto index = static_cast(_index); + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding unsigned integral result at index: " << index << std::endl; + } + + *is_null = _handle->result.is_null(_handle->count, index); + *value = _handle->result.get_uint64_value(_handle->count, index); + } + + void _bind_text_result(size_t _index, const char** value, size_t* len) + { + auto index = static_cast(_index); + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding text result at index: " << index << std::endl; + } + + if (_handle->result.is_null(_handle->count, index)) + { + *value = nullptr; + *len = 0; } else { - return false; + *value = _handle->result.get_char_ptr_value(_handle->count, index); + *len = static_cast(_handle->result.length(_handle->count, index)); } } - // Really needed? - if (_handle->fields == 0U) + // PostgreSQL will return one of those (using the default ISO client): + // + // 2010-10-11 01:02:03 - ISO timestamp without timezone + // 2011-11-12 01:02:03.123456 - ISO timesapt with sub-second (microsecond) precision + // 1997-12-17 07:37:16-08 - ISO timestamp with timezone + // 1992-10-10 01:02:03-06:30 - for some timezones with non-hour offset + // 1900-01-01 - date only + // we do not support time-only values ! + void _bind_date_result(size_t _index, ::sqlpp::chrono::day_point* value, bool* is_null) { - _handle->fields = _handle->result.field_count(); - } - - return true; - } - - inline void bind_result_t::_bind_boolean_result(size_t _index, signed char* value, bool* is_null) - { - auto index = static_cast(_index); - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding boolean result at index: " << index << std::endl; - } - - *is_null = _handle->result.isNull(_handle->count, index); - *value = _handle->result.getBoolValue(_handle->count, index); - } - - inline void bind_result_t::_bind_floating_point_result(size_t _index, double* value, bool* is_null) - { - auto index = static_cast(_index); - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding floating_point result at index: " << index << std::endl; - } - - *is_null = _handle->result.isNull(_handle->count, index); - *value = _handle->result.getDoubleValue(_handle->count, index); - } - - inline void bind_result_t::_bind_integral_result(size_t _index, int64_t* value, bool* is_null) - { - auto index = static_cast(_index); - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding integral result at index: " << index << std::endl; - } - - *is_null = _handle->result.isNull(_handle->count, index); - *value = _handle->result.getInt64Value(_handle->count, index); - } - - inline void bind_result_t::_bind_unsigned_integral_result(size_t _index, uint64_t* value, bool* is_null) - { - auto index = static_cast(_index); - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding unsigned integral result at index: " << index << std::endl; - } - - *is_null = _handle->result.isNull(_handle->count, index); - *value = _handle->result.getUInt64Value(_handle->count, index); - } - - inline void bind_result_t::_bind_text_result(size_t _index, const char** value, size_t* len) - { - auto index = static_cast(_index); - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding text result at index: " << index << std::endl; - } - - if (_handle->result.isNull(_handle->count, index)) - { - *value = nullptr; - *len = 0; - } - else - { - *value = _handle->result.getCharPtrValue(_handle->count, index); - *len = static_cast(_handle->result.length(_handle->count, index)); - } - } - - // PostgreSQL will return one of those (using the default ISO client): - // - // 2010-10-11 01:02:03 - ISO timestamp without timezone - // 2011-11-12 01:02:03.123456 - ISO timesapt with sub-second (microsecond) precision - // 1997-12-17 07:37:16-08 - ISO timestamp with timezone - // 1992-10-10 01:02:03-06:30 - for some timezones with non-hour offset - // 1900-01-01 - date only - // we do not support time-only values ! - - inline void bind_result_t::_bind_date_result(size_t _index, ::sqlpp::chrono::day_point* value, bool* is_null) - { - auto index = static_cast(_index); - - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding date result at index: " << index << std::endl; - } - - *is_null = _handle->result.isNull(_handle->count, index); - - if (!(*is_null)) - { - const auto date_string = _handle->result.getCharPtrValue(_handle->count, index); + auto index = static_cast(_index); if (_handle->debug()) { - std::cerr << "PostgreSQL debug: date string: " << date_string << std::endl; + std::cerr << "PostgreSQL debug: binding date result at index: " << index << std::endl; } - static const std::regex rx {"(\\d{4})-(\\d{2})-(\\d{2})"}; - std::cmatch mr; - if (std::regex_match (date_string, mr, rx)) { - *value = - ::sqlpp::chrono::day_point{ - ::date::year{std::atoi(date_string + mr.position(1))} / // Year - std::atoi(date_string + mr.position(2)) / // Month - std::atoi(date_string + mr.position(3)) // Day of month - }; - } else { - if (_handle->debug()) { - std::cerr << "PostgreSQL debug: got invalid date '" << date_string << "'" << std::endl; + *is_null = _handle->result.is_null(_handle->count, index); + + if (!(*is_null)) + { + const auto date_string = _handle->result.get_char_ptr_value(_handle->count, index); + + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: date string: " << date_string << std::endl; } - *value = {}; - } - } - else - { - *value = {}; - } - } - // always returns UTC time for timestamp with time zone - inline void bind_result_t::_bind_date_time_result(size_t _index, ::sqlpp::chrono::microsecond_point* value, bool* is_null) - { - auto index = static_cast(_index); - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding date_time result at index: " << index << std::endl; - } - - *is_null = _handle->result.isNull(_handle->count, index); - - if (!(*is_null)) - { - const auto date_string = _handle->result.getCharPtrValue(_handle->count, index); - - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: got date_time string: " << date_string << std::endl; - } - - static const std::regex rx { - "(\\d{4})-(\\d{2})-(\\d{2}) " - "(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?" - "(?:([+-])(\\d{2})(?::(\\d{2})(?::(\\d{2}))?)?)?" - }; - std::cmatch mr; - if (std::regex_match (date_string, mr, rx)) { - *value = - ::sqlpp::chrono::day_point{ - ::date::year{std::atoi(date_string + mr.position(1))} / // Year - std::atoi(date_string + mr.position(2)) / // Month - std::atoi(date_string + mr.position(3)) // Day of month - } + - std::chrono::hours{std::atoi(date_string + mr.position(4))} + // Hour - std::chrono::minutes{std::atoi(date_string + mr.position(5))} + // Minute - std::chrono::seconds{std::atoi(date_string + mr.position(6))} + // Second - ::std::chrono::microseconds{ // Microsecond - mr[7].matched ? std::stoi((mr[7].str() + "000000").substr(0, 6)) : 0 - }; - if (mr[8].matched) { - const auto tz_sign = (date_string[mr.position(8)] == '+') ? 1 : -1; - const auto tz_offset = - std::chrono::hours{std::atoi(date_string + mr.position(9))} + - std::chrono::minutes{mr[10].matched ? std::atoi(date_string + mr.position(10)) : 0} + - std::chrono::seconds{mr[11].matched ? std::atoi(date_string + mr.position(11)) : 0}; - *value -= tz_sign * tz_offset; + static const std::regex rx {"(\\d{4})-(\\d{2})-(\\d{2})"}; + std::cmatch mr; + if (std::regex_match (date_string, mr, rx)) { + *value = + ::sqlpp::chrono::day_point{ + ::date::year{std::atoi(date_string + mr.position(1))} / // Year + std::atoi(date_string + mr.position(2)) / // Month + std::atoi(date_string + mr.position(3)) // Day of month + }; + } else { + if (_handle->debug()) { + std::cerr << "PostgreSQL debug: got invalid date '" << date_string << "'" << std::endl; } - } else { - if (_handle->debug()) { - std::cerr << "PostgreSQL debug: got invalid date_time '" << date_string << "'" << std::endl; + *value = {}; } + } + else + { *value = {}; } } - } - // always returns UTC time for time with time zone - inline void bind_result_t::_bind_time_of_day_result(size_t _index, ::std::chrono::microseconds* value, bool* is_null) - { - auto index = static_cast(_index); - if (_handle->debug()) + // always returns UTC time for timestamp with time zone + void _bind_date_time_result(size_t _index, ::sqlpp::chrono::microsecond_point* value, bool* is_null) { - std::cerr << "PostgreSQL debug: binding time result at index: " << index << std::endl; - } - - *is_null = _handle->result.isNull(_handle->count, index); - - if (!(*is_null)) - { - const auto time_string = _handle->result.getCharPtrValue(_handle->count, index); - + auto index = static_cast(_index); if (_handle->debug()) { - std::cerr << "PostgreSQL debug: got time string: " << time_string << std::endl; + std::cerr << "PostgreSQL debug: binding date_time result at index: " << index << std::endl; } - static const std::regex rx { - "(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?" - "(?:([+-])(\\d{2})(?::(\\d{2})(?::(\\d{2}))?)?)?" - }; - std::cmatch mr; - if (std::regex_match (time_string, mr, rx)) { - *value = - std::chrono::hours{std::atoi(time_string + mr.position(1))} + // Hour - std::chrono::minutes{std::atoi(time_string + mr.position(2))} + // Minute - std::chrono::seconds{std::atoi(time_string + mr.position(3))} + // Second - ::std::chrono::microseconds{ // Microsecond - mr[4].matched ? std::stoi((mr[4].str() + "000000").substr(0, 6)) : 0 - }; - if (mr[5].matched) { - const auto tz_sign = (time_string[mr.position(5)] == '+') ? 1 : -1; - const auto tz_offset = - std::chrono::hours{std::atoi(time_string + mr.position(6))} + - std::chrono::minutes{mr[7].matched ? std::atoi(time_string + mr.position(7)) : 0} + - std::chrono::seconds{mr[8].matched ? std::atoi(time_string + mr.position(8)) : 0}; - *value -= tz_sign * tz_offset; - } - } else { - if (_handle->debug()) { - std::cerr << "PostgreSQL debug: got invalid time '" << time_string << "'" << std::endl; + *is_null = _handle->result.is_null(_handle->count, index); + + if (!(*is_null)) + { + const auto date_string = _handle->result.get_char_ptr_value(_handle->count, index); + + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: got date_time string: " << date_string << std::endl; + } + + static const std::regex rx { + "(\\d{4})-(\\d{2})-(\\d{2}) " + "(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?" + "(?:([+-])(\\d{2})(?::(\\d{2})(?::(\\d{2}))?)?)?" + }; + std::cmatch mr; + if (std::regex_match (date_string, mr, rx)) { + *value = + ::sqlpp::chrono::day_point{ + ::date::year{std::atoi(date_string + mr.position(1))} / // Year + std::atoi(date_string + mr.position(2)) / // Month + std::atoi(date_string + mr.position(3)) // Day of month + } + + std::chrono::hours{std::atoi(date_string + mr.position(4))} + // Hour + std::chrono::minutes{std::atoi(date_string + mr.position(5))} + // Minute + std::chrono::seconds{std::atoi(date_string + mr.position(6))} + // Second + ::std::chrono::microseconds{ // Microsecond + mr[7].matched ? std::stoi((mr[7].str() + "000000").substr(0, 6)) : 0 + }; + if (mr[8].matched) { + const auto tz_sign = (date_string[mr.position(8)] == '+') ? 1 : -1; + const auto tz_offset = + std::chrono::hours{std::atoi(date_string + mr.position(9))} + + std::chrono::minutes{mr[10].matched ? std::atoi(date_string + mr.position(10)) : 0} + + std::chrono::seconds{mr[11].matched ? std::atoi(date_string + mr.position(11)) : 0}; + *value -= tz_sign * tz_offset; + } + } else { + if (_handle->debug()) { + std::cerr << "PostgreSQL debug: got invalid date_time '" << date_string << "'" << std::endl; + } + *value = {}; } - *value = {}; } } - } - inline void bind_result_t::_bind_blob_result(size_t _index, const uint8_t** value, size_t* len) - { - - auto index = static_cast(_index); - if (_handle->debug()) + // always returns UTC time for time with time zone + void _bind_time_of_day_result(size_t _index, ::std::chrono::microseconds* value, bool* is_null) { - std::cerr << "PostgreSQL debug: binding blob result at index: " << index << std::endl; + auto index = static_cast(_index); + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding time result at index: " << index << std::endl; + } + + *is_null = _handle->result.is_null(_handle->count, index); + + if (!(*is_null)) + { + const auto time_string = _handle->result.get_char_ptr_value(_handle->count, index); + + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: got time string: " << time_string << std::endl; + } + + static const std::regex rx { + "(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?" + "(?:([+-])(\\d{2})(?::(\\d{2})(?::(\\d{2}))?)?)?" + }; + std::cmatch mr; + if (std::regex_match (time_string, mr, rx)) { + *value = + std::chrono::hours{std::atoi(time_string + mr.position(1))} + // Hour + std::chrono::minutes{std::atoi(time_string + mr.position(2))} + // Minute + std::chrono::seconds{std::atoi(time_string + mr.position(3))} + // Second + ::std::chrono::microseconds{ // Microsecond + mr[4].matched ? std::stoi((mr[4].str() + "000000").substr(0, 6)) : 0 + }; + if (mr[5].matched) { + const auto tz_sign = (time_string[mr.position(5)] == '+') ? 1 : -1; + const auto tz_offset = + std::chrono::hours{std::atoi(time_string + mr.position(6))} + + std::chrono::minutes{mr[7].matched ? std::atoi(time_string + mr.position(7)) : 0} + + std::chrono::seconds{mr[8].matched ? std::atoi(time_string + mr.position(8)) : 0}; + *value -= tz_sign * tz_offset; + } + } else { + if (_handle->debug()) { + std::cerr << "PostgreSQL debug: got invalid time '" << time_string << "'" << std::endl; + } + *value = {}; + } + } } - if (_handle->result.isNull(_handle->count, index)) + void _bind_blob_result(size_t _index, const uint8_t** value, size_t* len) { - *value = nullptr; - *len = 0; - } - else - { - *value = _handle->result.getBlobValue(_handle->count, index); - *len = static_cast(_handle->result.length(_handle->count, index)); - } - } + auto index = static_cast(_index); + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding blob result at index: " << index << std::endl; + } - inline int bind_result_t::size() const - { - return _handle->result.records_size(); - } + if (_handle->result.is_null(_handle->count, index)) + { + *value = nullptr; + *len = 0; + } + else + { + *value = _handle->result.get_blob_value(_handle->count, index); + *len = static_cast(_handle->result.length(_handle->count, index)); + } + } + + int size() const + { + return _handle->result.records_size(); + } + }; } // namespace postgresql } // namespace sqlpp diff --git a/include/sqlpp11/postgresql/connection.h b/include/sqlpp11/postgresql/connection.h index e7ecdde4..92635163 100644 --- a/include/sqlpp11/postgresql/connection.h +++ b/include/sqlpp11/postgresql/connection.h @@ -62,10 +62,9 @@ namespace sqlpp namespace detail { - // Forward declaration inline std::unique_ptr prepare_statement(std::unique_ptr& handle, const std::string& stmt, - const size_t& paramCount) + const size_t& param_count) { if (handle->config->debug) { @@ -73,7 +72,7 @@ namespace sqlpp } return std::unique_ptr(new detail::prepared_statement_handle_t - (*handle, stmt, paramCount)); + (*handle, stmt, param_count)); } inline void execute_prepared_statement(std::unique_ptr& handle, std::shared_ptr& prepared) @@ -144,18 +143,67 @@ namespace sqlpp } // direct execution - bind_result_t select_impl(const std::string& stmt); - size_t insert_impl(const std::string& stmt); - size_t update_impl(const std::string& stmt); - size_t remove_impl(const std::string& stmt); + bind_result_t select_impl(const std::string& stmt) + { + return execute(stmt); + } + + size_t insert_impl(const std::string& stmt) + { + return static_cast(execute(stmt)->result.affected_rows()); + } + + size_t update_impl(const std::string& stmt) + { + return static_cast(execute(stmt)->result.affected_rows()); + } + + size_t remove_impl(const std::string& stmt) + { + return static_cast(execute(stmt)->result.affected_rows()); + } // prepared execution - prepared_statement_t prepare_impl(const std::string& stmt, const size_t& paramCount); - bind_result_t run_prepared_select_impl(prepared_statement_t& prep); - size_t run_prepared_execute_impl(prepared_statement_t& prep); - size_t run_prepared_insert_impl(prepared_statement_t& prep); - size_t run_prepared_update_impl(prepared_statement_t& prep); - size_t run_prepared_remove_impl(prepared_statement_t& prep); + prepared_statement_t prepare_impl(const std::string& stmt, const size_t& param_count) + { + validate_connection_handle(); + return {prepare_statement(_handle, stmt, param_count)}; + } + + bind_result_t run_prepared_select_impl(prepared_statement_t& prep) + { + validate_connection_handle(); + execute_prepared_statement(_handle, prep._handle); + return {prep._handle}; + } + + size_t run_prepared_execute_impl(prepared_statement_t& prep) + { + validate_connection_handle(); + execute_prepared_statement(_handle, prep._handle); + return static_cast(prep._handle->result.affected_rows()); + } + + size_t run_prepared_insert_impl(prepared_statement_t& prep) + { + validate_connection_handle(); + execute_prepared_statement(_handle, prep._handle); + return static_cast(prep._handle->result.affected_rows()); + } + + size_t run_prepared_update_impl(prepared_statement_t& prep) + { + validate_connection_handle(); + execute_prepared_statement(_handle, prep._handle); + return static_cast(prep._handle->result.affected_rows()); + } + + size_t run_prepared_remove_impl(prepared_statement_t& prep) + { + validate_connection_handle(); + execute_prepared_statement(_handle, prep._handle); + return static_cast(prep._handle->result.affected_rows()); + } public: using _connection_base_t = connection_base; @@ -284,7 +332,20 @@ namespace sqlpp } // Execute - std::shared_ptr execute(const std::string& command); + std::shared_ptr execute(const std::string& stmt) + { + validate_connection_handle(); + if (_handle->config->debug) + { + std::cerr << "PostgreSQL debug: executing: " << stmt << std::endl; + } + + auto result = std::make_shared(*_handle); + result->result = PQexec(native_handle(), stmt.c_str()); + result->valid = true; + + return result; + } template < typename Execute, @@ -313,7 +374,19 @@ namespace sqlpp } // escape argument - std::string escape(const std::string& s) const; + // TODO: Fix escaping. + std::string escape(const std::string& s) const + { + validate_connection_handle(); + // Escape strings + std::string result; + result.resize((s.size() * 2) + 1); + + int err; + size_t length = PQescapeStringConn(native_handle(), &result[0], s.c_str(), s.size(), &err); + result.resize(length); + return result; + } //! call run on the argument template @@ -348,37 +421,176 @@ namespace sqlpp } //! set the default transaction isolation level to use for new transactions - void set_default_isolation_level(isolation_level level); + void set_default_isolation_level(isolation_level level) + { + std::string level_str = "read uncommmitted"; + switch (level) + { + /// @todo what about undefined ? + case isolation_level::read_committed: + level_str = "read committed"; + break; + case isolation_level::read_uncommitted: + level_str = "read uncommitted"; + break; + case isolation_level::repeatable_read: + level_str = "repeatable read"; + break; + case isolation_level::serializable: + level_str = "serializable"; + break; + default: + throw sqlpp::exception("Invalid isolation level"); + } + std::string cmd = "SET default_transaction_isolation to '" + level_str + "'"; + execute(cmd); + } //! get the currently set default transaction isolation level - isolation_level get_default_isolation_level(); + isolation_level get_default_isolation_level() + { + auto res = execute("SHOW default_transaction_isolation;"); + auto status = res->result.status(); + if ((status != PGRES_TUPLES_OK) && (status != PGRES_COMMAND_OK)) + { + throw sqlpp::exception("PostgreSQL error: could not read default_transaction_isolation"); + } + + auto in = res->result.get_string_value(0, 0); + if (in == "read committed") + { + return isolation_level::read_committed; + } + else if (in == "read uncommitted") + { + return isolation_level::read_uncommitted; + } + else if (in == "repeatable read") + { + return isolation_level::repeatable_read; + } + else if (in == "serializable") + { + return isolation_level::serializable; + } + return isolation_level::undefined; + } //! create savepoint - void savepoint(const std::string& name); + void savepoint(const std::string& name) + { + /// NOTE prevent from sql injection? + execute("SAVEPOINT " + name); + } //! ROLLBACK TO SAVEPOINT - void rollback_to_savepoint(const std::string& name); + void rollback_to_savepoint(const std::string& name) + { + /// NOTE prevent from sql injection? + execute("ROLLBACK TO SAVEPOINT " + name); + } //! release_savepoint - void release_savepoint(const std::string& name); + void release_savepoint(const std::string& name) + { + /// NOTE prevent from sql injection? + execute("RELEASE SAVEPOINT " + name); + } //! start transaction - void start_transaction(isolation_level level = isolation_level::undefined); + void start_transaction(isolation_level level = isolation_level::undefined) + { + if (_transaction_active) + { + throw sqlpp::exception("PostgreSQL error: transaction already open"); + } + switch (level) + { + case isolation_level::serializable: + { + execute("BEGIN ISOLATION LEVEL SERIALIZABLE"); + break; + } + case isolation_level::repeatable_read: + { + execute("BEGIN ISOLATION LEVEL REPEATABLE READ"); + break; + } + case isolation_level::read_committed: + { + execute("BEGIN ISOLATION LEVEL READ COMMITTED"); + break; + } + case isolation_level::read_uncommitted: + { + execute("BEGIN ISOLATION LEVEL READ UNCOMMITTED"); + break; + } + case isolation_level::undefined: + { + execute("BEGIN"); + break; + } + } + _transaction_active = true; + } - //! commit transaction (or throw transaction if transaction has - // finished already) - void commit_transaction(); + //! commit transaction (or throw transaction if transaction has finished already) + void commit_transaction() + { + if (!_transaction_active) + { + throw sqlpp::exception("PostgreSQL error: transaction failed or finished."); + } + + _transaction_active = false; + execute("COMMIT"); + } //! rollback transaction - void rollback_transaction(bool report); + void rollback_transaction(bool report) + { + if (!_transaction_active) + { + throw sqlpp::exception("PostgreSQL error: transaction failed or finished."); + } + execute("ROLLBACK"); + if (report) + { + std::cerr << "PostgreSQL warning: rolling back unfinished transaction" << std::endl; + } + + _transaction_active = false; + } //! report rollback failure - void report_rollback_failure(const std::string& message) noexcept; + void report_rollback_failure(const std::string& message) noexcept + { + std::cerr << "PostgreSQL error: " << message << std::endl; + } //! get the last inserted id for a certain table - uint64_t last_insert_id(const std::string& table, const std::string& fieldname); + uint64_t last_insert_id(const std::string& table, const std::string& fieldname) + { + std::string sql = "SELECT currval('" + table + "_" + fieldname + "_seq')"; + PGresult* res = PQexec(native_handle(), sql.c_str()); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + std::string err{PQresultErrorMessage(res)}; + PQclear(res); + throw sqlpp::postgresql::undefined_table(err, sql); + } - ::PGconn* native_handle() const; + // Parse the number and return. + std::string in{PQgetvalue(res, 0, 0)}; + PQclear(res); + return std::stoul(in); + } + + ::PGconn* native_handle() const + { + return _handle->native_handle(); + } protected: _handle_ptr_t _handle; @@ -390,268 +602,7 @@ namespace sqlpp } }; - inline std::shared_ptr connection_base::execute(const std::string& stmt) - { - validate_connection_handle(); - if (_handle->config->debug) - { - std::cerr << "PostgreSQL debug: executing: " << stmt << std::endl; - } - - auto result = std::make_shared(*_handle); - result->result = PQexec(native_handle(), stmt.c_str()); - result->valid = true; - - return result; - } - - // direct execution - inline bind_result_t connection_base::select_impl(const std::string& stmt) - { - return execute(stmt); - } - - inline size_t connection_base::insert_impl(const std::string& stmt) - { - return static_cast(execute(stmt)->result.affected_rows()); - } - - inline size_t connection_base::update_impl(const std::string& stmt) - { - return static_cast(execute(stmt)->result.affected_rows()); - } - - inline size_t connection_base::remove_impl(const std::string& stmt) - { - return static_cast(execute(stmt)->result.affected_rows()); - } - - // prepared execution - inline prepared_statement_t connection_base::prepare_impl(const std::string& stmt, const size_t& paramCount) - { - validate_connection_handle(); - return {prepare_statement(_handle, stmt, paramCount)}; - } - - inline bind_result_t connection_base::run_prepared_select_impl(prepared_statement_t& prep) - { - validate_connection_handle(); - execute_prepared_statement(_handle, prep._handle); - return {prep._handle}; - } - - inline size_t connection_base::run_prepared_execute_impl(prepared_statement_t& prep) - { - validate_connection_handle(); - execute_prepared_statement(_handle, prep._handle); - return static_cast(prep._handle->result.affected_rows()); - } - - inline size_t connection_base::run_prepared_insert_impl(prepared_statement_t& prep) - { - validate_connection_handle(); - execute_prepared_statement(_handle, prep._handle); - return static_cast(prep._handle->result.affected_rows()); - } - - inline size_t connection_base::run_prepared_update_impl(prepared_statement_t& prep) - { - validate_connection_handle(); - execute_prepared_statement(_handle, prep._handle); - return static_cast(prep._handle->result.affected_rows()); - } - - inline size_t connection_base::run_prepared_remove_impl(prepared_statement_t& prep) - { - validate_connection_handle(); - execute_prepared_statement(_handle, prep._handle); - return static_cast(prep._handle->result.affected_rows()); - } - - inline void connection_base::set_default_isolation_level(isolation_level level) - { - std::string level_str = "read uncommmitted"; - switch (level) - { - /// @todo what about undefined ? - case isolation_level::read_committed: - level_str = "read committed"; - break; - case isolation_level::read_uncommitted: - level_str = "read uncommitted"; - break; - case isolation_level::repeatable_read: - level_str = "repeatable read"; - break; - case isolation_level::serializable: - level_str = "serializable"; - break; - default: - throw sqlpp::exception("Invalid isolation level"); - } - std::string cmd = "SET default_transaction_isolation to '" + level_str + "'"; - execute(cmd); - } - - //! get the currently set default transaction isolation level - inline isolation_level connection_base::get_default_isolation_level() - { - auto res = execute("SHOW default_transaction_isolation;"); - auto status = res->result.status(); - if ((status != PGRES_TUPLES_OK) && (status != PGRES_COMMAND_OK)) - { - throw sqlpp::exception("PostgreSQL error: could not read default_transaction_isolation"); - } - - auto in = res->result.getStringValue(0, 0); - if (in == "read committed") - { - return isolation_level::read_committed; - } - else if (in == "read uncommitted") - { - return isolation_level::read_uncommitted; - } - else if (in == "repeatable read") - { - return isolation_level::repeatable_read; - } - else if (in == "serializable") - { - return isolation_level::serializable; - } - return isolation_level::undefined; - } - - // TODO: Fix escaping. - inline std::string connection_base::escape(const std::string& s) const - { - validate_connection_handle(); - // Escape strings - std::string result; - result.resize((s.size() * 2) + 1); - - int err; - size_t length = PQescapeStringConn(native_handle(), &result[0], s.c_str(), s.size(), &err); - result.resize(length); - return result; - } - - //! start transaction - inline void connection_base::start_transaction(isolation_level level) - { - if (_transaction_active) - { - throw sqlpp::exception("PostgreSQL error: transaction already open"); - } - switch (level) - { - case isolation_level::serializable: - { - execute("BEGIN ISOLATION LEVEL SERIALIZABLE"); - break; - } - case isolation_level::repeatable_read: - { - execute("BEGIN ISOLATION LEVEL REPEATABLE READ"); - break; - } - case isolation_level::read_committed: - { - execute("BEGIN ISOLATION LEVEL READ COMMITTED"); - break; - } - case isolation_level::read_uncommitted: - { - execute("BEGIN ISOLATION LEVEL READ UNCOMMITTED"); - break; - } - case isolation_level::undefined: - { - execute("BEGIN"); - break; - } - } - _transaction_active = true; - } - - //! create savepoint - inline void connection_base::savepoint(const std::string& name) - { - /// NOTE prevent from sql injection? - execute("SAVEPOINT " + name); - } - - //! ROLLBACK TO SAVEPOINT - inline void connection_base::rollback_to_savepoint(const std::string& name) - { - /// NOTE prevent from sql injection? - execute("ROLLBACK TO SAVEPOINT " + name); - } - - //! release_savepoint - inline void connection_base::release_savepoint(const std::string& name) - { - /// NOTE prevent from sql injection? - execute("RELEASE SAVEPOINT " + name); - } - - //! commit transaction (or throw transaction if transaction has finished already) - inline void connection_base::commit_transaction() - { - if (!_transaction_active) - { - throw sqlpp::exception("PostgreSQL error: transaction failed or finished."); - } - - _transaction_active = false; - execute("COMMIT"); - } - - //! rollback transaction - inline void connection_base::rollback_transaction(bool report) - { - if (!_transaction_active) - { - throw sqlpp::exception("PostgreSQL error: transaction failed or finished."); - } - execute("ROLLBACK"); - if (report) - { - std::cerr << "PostgreSQL warning: rolling back unfinished transaction" << std::endl; - } - - _transaction_active = false; - } - - //! report rollback failure - inline void connection_base::report_rollback_failure(const std::string& message) noexcept - { - std::cerr << "PostgreSQL error: " << message << std::endl; - } - - inline uint64_t connection_base::last_insert_id(const std::string& table, const std::string& fieldname) - { - std::string sql = "SELECT currval('" + table + "_" + fieldname + "_seq')"; - PGresult* res = PQexec(native_handle(), sql.c_str()); - if (PQresultStatus(res) != PGRES_TUPLES_OK) - { - std::string err{PQresultErrorMessage(res)}; - PQclear(res); - throw sqlpp::postgresql::undefined_table(err, sql); - } - - // Parse the number and return. - std::string in{PQgetvalue(res, 0, 0)}; - PQclear(res); - return std::stoul(in); - } - - inline ::PGconn* connection_base::native_handle() const - { - return _handle->native_handle(); - } - + // Method definition moved outside of class because it needs connection_base inline std::string context_t::escape(const std::string& arg) const { return _db.escape(arg); diff --git a/include/sqlpp11/postgresql/detail/connection_handle.h b/include/sqlpp11/postgresql/detail/connection_handle.h index 33b0161e..6e73ffa6 100644 --- a/include/sqlpp11/postgresql/detail/connection_handle.h +++ b/include/sqlpp11/postgresql/detail/connection_handle.h @@ -52,11 +52,6 @@ namespace sqlpp namespace detail { - inline void handle_cleanup(PGconn* postgres) - { - PQfinish(postgres); - } - struct DLL_LOCAL connection_handle { std::shared_ptr config; @@ -76,7 +71,7 @@ namespace sqlpp }; inline connection_handle::connection_handle(const std::shared_ptr& conf) - : config(conf), postgres{nullptr, handle_cleanup} + : config(conf), postgres{nullptr, PQfinish} { #ifdef SQLPP_DYNAMIC_LOADING init_pg(""); diff --git a/include/sqlpp11/postgresql/detail/prepared_statement_handle.h b/include/sqlpp11/postgresql/detail/prepared_statement_handle.h index 89d11a60..f154fc57 100644 --- a/include/sqlpp11/postgresql/detail/prepared_statement_handle.h +++ b/include/sqlpp11/postgresql/detail/prepared_statement_handle.h @@ -54,25 +54,45 @@ namespace sqlpp { struct DLL_PUBLIC statement_handle_t { - detail::connection_handle& connection; + connection_handle& connection; Result result; bool valid = false; int count = 0; - int totalCount = 0; + int total_count = 0; int fields = 0; // ctor - statement_handle_t(detail::connection_handle& _connection); + statement_handle_t(connection_handle& _connection) : connection(_connection) + { + } + statement_handle_t(const statement_handle_t&) = delete; statement_handle_t(statement_handle_t&&) = delete; statement_handle_t& operator=(const statement_handle_t&) = delete; statement_handle_t& operator=(statement_handle_t&&) = delete; - virtual ~statement_handle_t(); - bool operator!() const; - void clearResult(); + virtual ~statement_handle_t() + { + clear_result(); + } - bool debug() const; + bool operator!() const + { + return !valid; + } + + void clear_result() + { + if (result) + { + result.clear(); + } + } + + bool debug() const + { + return connection.config->debug; + } }; struct prepared_statement_handle_t : public statement_handle_t @@ -82,19 +102,47 @@ namespace sqlpp public: // Store prepared statement arguments - std::vector nullValues; - std::vector paramValues; + std::vector null_values; + std::vector param_values; // ctor - prepared_statement_handle_t(detail::connection_handle& _connection, std::string stmt, const size_t& paramCount); + prepared_statement_handle_t(connection_handle& _connection, const std::string& stmt, const size_t& param_count) + : statement_handle_t(_connection), null_values(param_count), param_values(param_count) + { + generate_name(); + prepare(std::move(stmt)); + } + prepared_statement_handle_t(const prepared_statement_handle_t&) = delete; prepared_statement_handle_t(prepared_statement_handle_t&&) = delete; prepared_statement_handle_t& operator=(const prepared_statement_handle_t&) = delete; prepared_statement_handle_t& operator=(prepared_statement_handle_t&&) = delete; - virtual ~prepared_statement_handle_t(); + virtual ~prepared_statement_handle_t() + { + if (valid && !_name.empty()) + { + connection.deallocate_prepared_statement(_name); + } + } - void execute(); + void execute() + { + const size_t size = param_values.size(); + + std::vector values; + for (size_t i = 0u; i < size; i++) + values.push_back(null_values[i] ? nullptr : const_cast(param_values[i].c_str())); + + // Execute prepared statement with the parameters. + clear_result(); + valid = false; + count = 0; + total_count = 0; + result = PQexecPrepared(connection.native_handle(), _name.data(), static_cast(size), values.data(), nullptr, nullptr, 0); + /// @todo validate result? is it really valid + valid = true; + } std::string name() const { @@ -102,94 +150,29 @@ namespace sqlpp } private: - void generate_name(); - void prepare(std::string stmt); + void generate_name() + { + // Generate a random name for the prepared statement + while (connection.prepared_statement_names.find(_name) != connection.prepared_statement_names.end()) + { + std::generate_n(_name.begin(), 6, []() { + constexpr static auto charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + constexpr size_t max = (sizeof(charset) - 1); + std::random_device rd; + return charset[rd() % max]; + }); + } + connection.prepared_statement_names.insert(_name); + } + + void prepare(const std::string& stmt) + { + // Create the prepared statement + result = PQprepare(connection.native_handle(), _name.c_str(), stmt.c_str(), 0, nullptr); + valid = true; + } }; - - inline statement_handle_t::statement_handle_t(connection_handle& _connection) : connection(_connection) - { - } - - inline statement_handle_t::~statement_handle_t() - { - clearResult(); - } - - inline bool statement_handle_t::operator!() const - { - return !valid; - } - - inline void statement_handle_t::clearResult() - { - if (result) - { - result.clear(); - } - } - - inline bool statement_handle_t::debug() const - { - return connection.config->debug; - } - - inline prepared_statement_handle_t::prepared_statement_handle_t(connection_handle& _connection, - std::string stmt, - const size_t& paramCount) - : statement_handle_t(_connection), nullValues(paramCount), paramValues(paramCount) - { - generate_name(); - prepare(std::move(stmt)); - } - - inline prepared_statement_handle_t::~prepared_statement_handle_t() - { - if (valid && !_name.empty()) - { - connection.deallocate_prepared_statement(_name); - } - } - - inline void prepared_statement_handle_t::execute() - { - const size_t size = paramValues.size(); - - std::vector values; - for (size_t i = 0u; i < size; i++) - values.push_back(nullValues[i] ? nullptr : const_cast(paramValues[i].c_str())); - - // Execute prepared statement with the parameters. - clearResult(); - valid = false; - count = 0; - totalCount = 0; - result = PQexecPrepared(connection.native_handle(), _name.data(), static_cast(size), values.data(), nullptr, nullptr, 0); - /// @todo validate result? is it really valid - valid = true; - } - - inline void prepared_statement_handle_t::generate_name() - { - // Generate a random name for the prepared statement - while (connection.prepared_statement_names.find(_name) != connection.prepared_statement_names.end()) - { - std::generate_n(_name.begin(), 6, []() { - constexpr static auto charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; - constexpr size_t max = (sizeof(charset) - 1); - std::random_device rd; - return charset[rd() % max]; - }); - } - connection.prepared_statement_names.insert(_name); - } - - inline void prepared_statement_handle_t::prepare(std::string stmt) - { - // Create the prepared statement - result = PQprepare(connection.native_handle(), _name.c_str(), stmt.c_str(), 0, nullptr); - valid = true; - } } } } diff --git a/include/sqlpp11/postgresql/prepared_statement.h b/include/sqlpp11/postgresql/prepared_statement.h index d4727610..b1295d3b 100644 --- a/include/sqlpp11/postgresql/prepared_statement.h +++ b/include/sqlpp11/postgresql/prepared_statement.h @@ -60,7 +60,18 @@ namespace sqlpp public: prepared_statement_t() = default; - prepared_statement_t(std::shared_ptr&& handle); + + // ctor + prepared_statement_t(std::shared_ptr&& handle) + : _handle{handle} + { + if (_handle && _handle->debug()) + { + std::cerr << "PostgreSQL debug: constructing prepared_statement, using handle at: " << _handle.get() + << std::endl; + } + } + prepared_statement_t(const prepared_statement_t&) = delete; prepared_statement_t(prepared_statement_t&&) = default; prepared_statement_t& operator=(const prepared_statement_t&) = delete; @@ -72,194 +83,174 @@ namespace sqlpp return (this->_handle == rhs._handle); } - void _bind_boolean_parameter(size_t index, const signed char* value, bool is_null); - void _bind_floating_point_parameter(size_t index, const double* value, bool is_null); - void _bind_integral_parameter(size_t index, const int64_t* value, bool is_null); - void _bind_text_parameter(size_t index, const std::string* value, bool is_null); - void _bind_date_parameter(size_t index, const ::sqlpp::chrono::day_point* value, bool is_null); - void _bind_time_of_day_parameter(size_t index, const ::std::chrono::microseconds* value, bool is_null); - void _bind_date_time_parameter(size_t index, const ::sqlpp::chrono::microsecond_point* value, bool is_null); - void _bind_blob_parameter(size_t index, const std::vector* value, bool is_null); + void _bind_boolean_parameter(size_t index, const signed char* value, bool is_null) + { + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding boolean parameter " << (*value ? "true" : "false") + << " at index: " << index << ", being " << (is_null ? "" : "not ") << "null" << std::endl; + } + + _handle->null_values[index] = is_null; + if (!is_null) + { + if (*value) + { + _handle->param_values[index] = "TRUE"; + } + else + { + _handle->param_values[index] = "FALSE"; + } + } + } + + void _bind_floating_point_parameter(size_t index, const double* value, bool is_null) + { + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding floating_point parameter " << *value << " at index: " << index + << ", being " << (is_null ? "" : "not ") << "null" << std::endl; + } + + _handle->null_values[index] = is_null; + if (!is_null) + { + sqlpp::detail::float_safe_ostringstream out; + out << *value; + _handle->param_values[index] = out.str(); + } + } + + void _bind_integral_parameter(size_t index, const int64_t* value, bool is_null) + { + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding integral parameter " << *value << " at index: " << index << ", being " + << (is_null ? "" : "not ") << "null" << std::endl; + } + + // Assign values + _handle->null_values[index] = is_null; + if (!is_null) + { + _handle->param_values[index] = std::to_string(*value); + } + } + + void _bind_text_parameter(size_t index, const std::string* value, bool is_null) + { + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding text parameter " << *value << " at index: " << index << ", being " + << (is_null ? "" : "not ") << "null" << std::endl; + } + + // Assign values + _handle->null_values[index] = is_null; + if (!is_null) + { + _handle->param_values[index] = *value; + } + } + + void _bind_date_parameter(size_t index, const ::sqlpp::chrono::day_point* value, bool is_null) + { + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding date parameter at index " + << index << ", being " << (is_null ? "" : "not ") << "null" << std::endl; + } + _handle->null_values[index] = is_null; + if (not is_null) + { + const auto ymd = ::date::year_month_day{*value}; + std::ostringstream os; + os << ymd; + _handle->param_values[index] = os.str(); + + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding date parameter string: " << _handle->param_values[index] << std::endl; + } + } + } + + void _bind_time_of_day_parameter(size_t index, const ::std::chrono::microseconds* value, bool is_null) + { + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding time parameter at index " + << index << ", being " << (is_null ? "" : "not ") << "null" << std::endl; + } + _handle->null_values[index] = is_null; + if (not is_null) + { + const auto time = ::date::make_time(*value) ; + + // Timezone handling - always treat the local value as UTC. + std::ostringstream os; + os << time << "+00"; + _handle->param_values[index] = os.str(); + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding time parameter string: " << _handle->param_values[index] << std::endl; + } + } + } + + void _bind_date_time_parameter(size_t index, const ::sqlpp::chrono::microsecond_point* value, bool is_null) + { + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding date_time parameter at index " + << index << ", being " << (is_null ? "" : "not ") << "null" << std::endl; + } + _handle->null_values[index] = is_null; + if (not is_null) + { + const auto dp = ::sqlpp::chrono::floor<::date::days>(*value); + const auto time = ::date::make_time(::sqlpp::chrono::floor<::std::chrono::microseconds>(*value - dp)); + const auto ymd = ::date::year_month_day{dp}; + + // Timezone handling - always treat the local value as UTC. + std::ostringstream os; + os << ymd << ' ' << time << "+00"; + _handle->param_values[index] = os.str(); + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding date_time parameter string: " << _handle->param_values[index] << std::endl; + } + } + } + + void _bind_blob_parameter(size_t index, const std::vector* value, bool is_null) + { + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding blob parameter at index " + << index << ", being " << (is_null ? "" : "not ") << "null" << std::endl; + } + _handle->null_values[index] = is_null; + if (not is_null) + { + constexpr char hex_chars[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + auto param = std::string(value->size() * 2 + 2, '\0'); + param[0] = '\\'; + param[1] = 'x'; + auto i = size_t{1}; + for (const auto c : *value) + { + param[++i] = hex_chars[c >> 4]; + param[++i] = hex_chars[c & 0x0F]; + } + _handle->param_values[index] = std::move(param); + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: binding blob parameter string (up to 100 chars): " << _handle->param_values[index].substr(0, 100) << std::endl; + } + } + } }; - - // ctor - inline prepared_statement_t::prepared_statement_t(std::shared_ptr&& handle) - : _handle{handle} - { - if (_handle && _handle->debug()) - { - std::cerr << "PostgreSQL debug: constructing prepared_statement, using handle at: " << _handle.get() - << std::endl; - } - } - - inline void prepared_statement_t::_bind_boolean_parameter(size_t index, const signed char* value, bool is_null) - { - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding boolean parameter " << (*value ? "true" : "false") - << " at index: " << index << ", being " << (is_null ? "" : "not ") << "null" << std::endl; - } - - _handle->nullValues[index] = is_null; - if (!is_null) - { - if (*value) - { - _handle->paramValues[index] = "TRUE"; - } - else - { - _handle->paramValues[index] = "FALSE"; - } - } - } - - inline void prepared_statement_t::_bind_floating_point_parameter(size_t index, const double* value, bool is_null) - { - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding floating_point parameter " << *value << " at index: " << index - << ", being " << (is_null ? "" : "not ") << "null" << std::endl; - } - - _handle->nullValues[index] = is_null; - if (!is_null) - { - sqlpp::detail::float_safe_ostringstream out; - out << *value; - _handle->paramValues[index] = out.str(); - } - } - - inline void prepared_statement_t::_bind_integral_parameter(size_t index, const int64_t* value, bool is_null) - { - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding integral parameter " << *value << " at index: " << index << ", being " - << (is_null ? "" : "not ") << "null" << std::endl; - } - - // Assign values - _handle->nullValues[index] = is_null; - if (!is_null) - { - _handle->paramValues[index] = std::to_string(*value); - } - } - - inline void prepared_statement_t::_bind_text_parameter(size_t index, const std::string* value, bool is_null) - { - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding text parameter " << *value << " at index: " << index << ", being " - << (is_null ? "" : "not ") << "null" << std::endl; - } - - // Assign values - _handle->nullValues[index] = is_null; - if (!is_null) - { - _handle->paramValues[index] = *value; - } - } - - inline void prepared_statement_t::_bind_date_parameter(size_t index, const ::sqlpp::chrono::day_point* value, bool is_null) - { - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding date parameter at index " - << index << ", being " << (is_null ? "" : "not ") << "null" << std::endl; - } - _handle->nullValues[index] = is_null; - if (not is_null) - { - const auto ymd = ::date::year_month_day{*value}; - std::ostringstream os; - os << ymd; - _handle->paramValues[index] = os.str(); - - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding date parameter string: " << _handle->paramValues[index] << std::endl; - } - } - } - - inline void prepared_statement_t::_bind_time_of_day_parameter(size_t index, const ::std::chrono::microseconds* value, bool is_null) - { - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding time parameter at index " - << index << ", being " << (is_null ? "" : "not ") << "null" << std::endl; - } - _handle->nullValues[index] = is_null; - if (not is_null) - { - const auto time = ::date::make_time(*value) ; - - // Timezone handling - always treat the local value as UTC. - std::ostringstream os; - os << time << "+00"; - _handle->paramValues[index] = os.str(); - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding time parameter string: " << _handle->paramValues[index] << std::endl; - } - } - } - - inline void prepared_statement_t::_bind_date_time_parameter(size_t index, const ::sqlpp::chrono::microsecond_point* value, bool is_null) - { - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding date_time parameter at index " - << index << ", being " << (is_null ? "" : "not ") << "null" << std::endl; - } - _handle->nullValues[index] = is_null; - if (not is_null) - { - const auto dp = ::sqlpp::chrono::floor<::date::days>(*value); - const auto time = ::date::make_time(::sqlpp::chrono::floor<::std::chrono::microseconds>(*value - dp)); - const auto ymd = ::date::year_month_day{dp}; - - // Timezone handling - always treat the local value as UTC. - std::ostringstream os; - os << ymd << ' ' << time << "+00"; - _handle->paramValues[index] = os.str(); - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding date_time parameter string: " << _handle->paramValues[index] << std::endl; - } - } - } - - inline void prepared_statement_t::_bind_blob_parameter(size_t index, const std::vector* value, bool is_null) - { - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding blob parameter at index " - << index << ", being " << (is_null ? "" : "not ") << "null" << std::endl; - } - _handle->nullValues[index] = is_null; - if (not is_null) - { - constexpr char hexChars[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - auto param = std::string(value->size() * 2 + 2, '\0'); - param[0] = '\\'; - param[1] = 'x'; - auto i = size_t{1}; - for (const auto c : *value) - { - param[++i] = hexChars[c >> 4]; - param[++i] = hexChars[c & 0x0F]; - } - _handle->paramValues[index] = std::move(param); - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: binding blob parameter string (up to 100 chars): " << _handle->paramValues[index].substr(0, 100) << std::endl; - } - } - } } } diff --git a/include/sqlpp11/postgresql/result.h b/include/sqlpp11/postgresql/result.h index 879417d9..97b2aaa3 100644 --- a/include/sqlpp11/postgresql/result.h +++ b/include/sqlpp11/postgresql/result.h @@ -53,26 +53,71 @@ namespace sqlpp class DLL_PUBLIC Result { public: - Result(); - ~Result(); - - ExecStatusType status(); - - void clear(); - - int affected_rows(); - int records_size() const; - int field_count() const; - int length(int record, int field) const; - bool isNull(int record, int field) const; - void operator=(PGresult* res); - operator bool() const; - - inline int64_t getInt64Value(int record, int field) const + Result() : m_result(nullptr) { - checkIndex(record, field); + } + + ~Result() + { + clear(); + } + + ExecStatusType status() + { + return PQresultStatus(m_result); + } + + void clear() + { + if (m_result) + PQclear(m_result); + m_result = nullptr; + } + + int affected_rows() + { + const char* const rows_str = PQcmdTuples(m_result); + return rows_str[0] ? std::stoi(std::string(rows_str)) : 0; + } + + int records_size() const + { + return m_result ? PQntuples(m_result) : 0; + } + + int field_count() const + { + return m_result ? PQnfields(m_result) : 0; + } + + int length(int record, int field) const + { + /// check index? + return PQgetlength(m_result, record, field); + } + + bool is_null(int record, int field) const + { + /// check index? + return PQgetisnull(m_result, record, field); + } + + void operator=(PGresult* res) + { + m_result = res; + check_status(); + } + + operator bool() const + { + return m_result != 0; + } + + int64_t get_int64_value(int record, int field) const + { + check_index(record, field); auto t = int64_t{}; - const auto txt = std::string(getPqValue(m_result, record, field)); + const auto txt = std::string(get_pq_value(m_result, record, field)); if(txt != "") { t = std::stoll(txt); @@ -81,11 +126,11 @@ namespace sqlpp return t; } - inline uint64_t getUInt64Value(int record, int field) const + uint64_t get_uint64_value(int record, int field) const { - checkIndex(record, field); + check_index(record, field); auto t = uint64_t{}; - const auto txt = std::string(getPqValue(m_result, record, field)); + const auto txt = std::string(get_pq_value(m_result, record, field)); if(txt != "") { t = std::stoull(txt); @@ -94,11 +139,11 @@ namespace sqlpp return t; } - inline double getDoubleValue(int record, int field) const + double get_double_value(int record, int field) const { - checkIndex(record, field); + check_index(record, field); auto t = double{}; - auto txt = std::string(getPqValue(m_result, record, field)); + auto txt = std::string(get_pq_value(m_result, record, field)); if(txt != "") { t = std::stod(txt); @@ -107,25 +152,25 @@ namespace sqlpp return t; } - inline const char* getCharPtrValue(int record, int field) const + const char* get_char_ptr_value(int record, int field) const { - return const_cast(getPqValue(m_result, record, field)); + return const_cast(get_pq_value(m_result, record, field)); } - inline std::string getStringValue(int record, int field) const + std::string get_string_value(int record, int field) const { - return {getCharPtrValue(record, field)}; + return {get_char_ptr_value(record, field)}; } - inline const uint8_t* getBlobValue(int record, int field) const + const uint8_t* get_blob_value(int record, int field) const { - return reinterpret_cast(getPqValue(m_result, record, field)); + return reinterpret_cast(get_pq_value(m_result, record, field)); } - inline bool getBoolValue(int record, int field) const + bool get_bool_value(int record, int field) const { - checkIndex(record, field); - auto val = getPqValue(m_result, record, field); + check_index(record, field); + auto val = get_pq_value(m_result, record, field); if (*val == 't') return true; else if (*val == 'f') @@ -144,239 +189,169 @@ namespace sqlpp } private: - void CheckStatus() const; - [[noreturn]] void ThrowSQLError(const std::string& Err, const std::string& Query) const; - std::string StatusError() const; - int errorPosition() const noexcept; - bool hasError(); - void checkIndex(int record, int field) const noexcept(false); + void check_status() const + { + const std::string err = status_error(); + if (!err.empty()) + throw_sql_error(err, query()); + } + + [[noreturn]] void throw_sql_error(const std::string& err, const std::string& query) const + { + // Try to establish more precise error type, and throw corresponding exception + const char* const code = PQresultErrorField(m_result, 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); + } + break; + case '2': + switch (code[1]) + { + case '2': + throw data_exception(err, query); + case '3': + if (strcmp(code, "23001") == 0) + throw restrict_violation(err, query); + if (strcmp(code, "23502") == 0) + throw not_null_violation(err, query); + if (strcmp(code, "23503") == 0) + throw foreign_key_violation(err, query); + if (strcmp(code, "23505") == 0) + throw unique_violation(err, query); + if (strcmp(code, "23514") == 0) + throw check_violation(err, query); + throw integrity_constraint_violation(err, query); + case '4': + throw invalid_cursor_state(err, query); + case '6': + throw invalid_sql_statement_name(err, query); + } + break; + case '3': + switch (code[1]) + { + case '4': + throw invalid_cursor_name(err, query); + } + break; + case '4': + switch (code[1]) + { + case '2': + if (strcmp(code, "42501") == 0) + throw insufficient_privilege(err, query); + if (strcmp(code, "42601") == 0) + throw syntax_error(err, query, error_position()); + if (strcmp(code, "42703") == 0) + throw undefined_column(err, query); + if (strcmp(code, "42883") == 0) + throw undefined_function(err, query); + if (strcmp(code, "42P01") == 0) + throw undefined_table(err, query); + } + break; + case '5': + switch (code[1]) + { + case '3': + if (strcmp(code, "53100") == 0) + throw disk_full(err, query); + if (strcmp(code, "53200") == 0) + throw out_of_memory(err, query); + if (strcmp(code, "53300") == 0) + throw too_many_connections(err); + throw insufficient_resources(err, query); + } + break; + + case 'P': + if (strcmp(code, "P0001") == 0) + throw plpgsql_raise(err, query); + if (strcmp(code, "P0002") == 0) + throw plpgsql_no_data_found(err, query); + if (strcmp(code, "P0003") == 0) + throw plpgsql_too_many_rows(err, query); + throw plpgsql_error(err, query); + break; + default: + throw sql_user_error(err, query, code); + } + throw sql_error(err, query); + } + + std::string status_error() const + { + if (!m_result) + throw failure("No result set given"); + + std::string err; + + switch (PQresultStatus(m_result)) + { + 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_result); + break; + #if PG_MAJORVERSION_NUM >= 13 + case PGRES_COPY_BOTH: + case PGRES_SINGLE_TUPLE: + #endif + #if PG_MAJORVERSION_NUM >= 14 + case PGRES_PIPELINE_SYNC: + case PGRES_PIPELINE_ABORTED: + #endif + default: + throw sqlpp::exception("pqxx::result: Unrecognized response code " + + std::to_string(PQresultStatus(m_result))); + } + return err; + } + + int error_position() const noexcept + { + int pos = -1; + if (m_result) + { + const char* p = PQresultErrorField(m_result, PG_DIAG_STATEMENT_POSITION); + if (p) + pos = std::stoi(std::string(p)); + } + return pos; + } + + void check_index(int record, int field) const noexcept(false) + { + if (record > records_size() || field > field_count()) + throw std::out_of_range("PostgreSQL error: index out of range"); + } // move PQgetvalue to implementation so we don't depend on the libpq in the // public interface - const char* getPqValue(PGresult* result, int record, int field) const; + const char* get_pq_value(PGresult* result, int record, int field) const + { + return const_cast(PQgetvalue(result, record, field)); + } PGresult* m_result; std::string m_query; }; - - - inline Result::Result() : m_result(nullptr) - { - } - - inline void Result::checkIndex(int record, int field) const noexcept(false) - { - if (record > records_size() || field > field_count()) - throw std::out_of_range("PostgreSQL error: index out of range"); - } - - inline void Result::operator=(PGresult* res) - { - m_result = res; - CheckStatus(); - } - - inline void Result::CheckStatus() const - { - const std::string Err = StatusError(); - if (!Err.empty()) - ThrowSQLError(Err, query()); - } - - inline const char* Result::getPqValue(PGresult* result, int record, int field) const - { - return const_cast(PQgetvalue(result, record, field)); - } - - [[noreturn]] inline void Result::ThrowSQLError(const std::string& Err, const std::string& Query) const - { - // Try to establish more precise error type, and throw corresponding exception - const char* const code = PQresultErrorField(m_result, 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); - } - break; - case '2': - switch (code[1]) - { - case '2': - throw data_exception(Err, Query); - case '3': - if (strcmp(code, "23001") == 0) - throw restrict_violation(Err, Query); - if (strcmp(code, "23502") == 0) - throw not_null_violation(Err, Query); - if (strcmp(code, "23503") == 0) - throw foreign_key_violation(Err, Query); - if (strcmp(code, "23505") == 0) - throw unique_violation(Err, Query); - if (strcmp(code, "23514") == 0) - throw check_violation(Err, Query); - throw integrity_constraint_violation(Err, Query); - case '4': - throw invalid_cursor_state(Err, Query); - case '6': - throw invalid_sql_statement_name(Err, Query); - } - break; - case '3': - switch (code[1]) - { - case '4': - throw invalid_cursor_name(Err, Query); - } - break; - case '4': - switch (code[1]) - { - case '2': - if (strcmp(code, "42501") == 0) - throw insufficient_privilege(Err, Query); - if (strcmp(code, "42601") == 0) - throw syntax_error(Err, Query, errorPosition()); - if (strcmp(code, "42703") == 0) - throw undefined_column(Err, Query); - if (strcmp(code, "42883") == 0) - throw undefined_function(Err, Query); - if (strcmp(code, "42P01") == 0) - throw undefined_table(Err, Query); - } - break; - case '5': - switch (code[1]) - { - case '3': - if (strcmp(code, "53100") == 0) - throw disk_full(Err, Query); - if (strcmp(code, "53200") == 0) - throw out_of_memory(Err, Query); - if (strcmp(code, "53300") == 0) - throw too_many_connections(Err); - throw insufficient_resources(Err, Query); - } - break; - - case 'P': - if (strcmp(code, "P0001") == 0) - throw plpgsql_raise(Err, Query); - if (strcmp(code, "P0002") == 0) - throw plpgsql_no_data_found(Err, Query); - if (strcmp(code, "P0003") == 0) - throw plpgsql_too_many_rows(Err, Query); - throw plpgsql_error(Err, Query); - break; - default: - throw sql_user_error(Err, Query, code); - } - throw sql_error(Err, Query); - } - - inline std::string Result::StatusError() const - { - if (!m_result) - throw failure("No result set given"); - - std::string Err; - - switch (PQresultStatus(m_result)) - { - 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_result); - break; -#if PG_MAJORVERSION_NUM >= 13 - case PGRES_COPY_BOTH: - case PGRES_SINGLE_TUPLE: -#endif -#if PG_MAJORVERSION_NUM >= 14 - case PGRES_PIPELINE_SYNC: - case PGRES_PIPELINE_ABORTED: -#endif - default: - throw sqlpp::exception("pqxx::result: Unrecognized response code " + - std::to_string(PQresultStatus(m_result))); - } - return Err; - } - - inline int Result::errorPosition() const noexcept - { - int pos = -1; - if (m_result) - { - const char* p = PQresultErrorField(m_result, PG_DIAG_STATEMENT_POSITION); - if (p) - pos = std::stoi(std::string(p)); - } - return pos; - } - - inline sqlpp::postgresql::Result::operator bool() const - { - return m_result != 0; - } - - inline void Result::clear() - { - if (m_result) - PQclear(m_result); - m_result = nullptr; - } - - inline int Result::affected_rows() - { - const char* const RowsStr = PQcmdTuples(m_result); - return RowsStr[0] ? std::stoi(std::string(RowsStr)) : 0; - } - - inline int Result::records_size() const - { - return m_result ? PQntuples(m_result) : 0; - } - - inline int Result::field_count() const - { - return m_result ? PQnfields(m_result) : 0; - } - - inline bool Result::isNull(int record, int field) const - { - /// check index? - return PQgetisnull(m_result, record, field); - } - - inline int Result::length(int record, int field) const - { - /// check index? - return PQgetlength(m_result, record, field); - } - - inline Result::~Result() - { - clear(); - } - - inline ExecStatusType Result::status() - { - return PQresultStatus(m_result); - } } } diff --git a/include/sqlpp11/postgresql/serializer.h b/include/sqlpp11/postgresql/serializer.h index 95ba037d..3ad5e32d 100644 --- a/include/sqlpp11/postgresql/serializer.h +++ b/include/sqlpp11/postgresql/serializer.h @@ -44,11 +44,11 @@ namespace sqlpp inline postgresql::context_t& serialize(const blob_operand& t, postgresql::context_t& context) { - constexpr char hexChars[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + constexpr char hex_chars[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; context << "'\\x"; for (const auto c : t._t) { - context << hexChars[c >> 4] << hexChars[c & 0x0F]; + context << hex_chars[c >> 4] << hex_chars[c & 0x0F]; } context << '\'';