From f7f2060c44f2a64feebf011243cf5822372c3ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Hunold?= Date: Mon, 7 Feb 2022 20:45:39 +0100 Subject: [PATCH] Add support for TIME columns in postgresql --- include/sqlpp11/postgresql/bind_result.h | 49 ++++++++ .../sqlpp11/postgresql/prepared_statement.h | 25 ++++ tests/postgresql/usage/DateTime.cpp | 31 +++-- tests/postgresql/usage/TabDateTime.h | 112 ++++++++++++++++++ 4 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 tests/postgresql/usage/TabDateTime.h diff --git a/include/sqlpp11/postgresql/bind_result.h b/include/sqlpp11/postgresql/bind_result.h index a350601d..6f655b44 100644 --- a/include/sqlpp11/postgresql/bind_result.h +++ b/include/sqlpp11/postgresql/bind_result.h @@ -104,6 +104,7 @@ namespace sqlpp 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; @@ -401,6 +402,54 @@ namespace sqlpp } } + // always returns local 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()) + { + 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); + + if (_handle->debug()) + { + std::cerr << "PostgreSQL debug: got time string: " << time_string << std::endl; + } + + if (detail::check_time_digits(time_string)) + { + *value += std::chrono::hours(std::atoi(time_string)) + std::chrono::minutes(std::atoi(time_string + 3)) + + std::chrono::seconds(std::atoi(time_string + 6)); + } + else + { + return; + } + + if (std::strlen(time_string) <= 9) + return; + auto us_string = time_string + 9; // hh:mm:ss. + int usec = 0; + for (size_t i = 0u; i < 6u; ++i) + { + if (std::isdigit(us_string[0])) + { + usec = 10 * usec + (us_string[0] - '0'); + ++us_string; + } + else + usec *= 10; + } + *value += ::std::chrono::microseconds(usec); + } + } + inline void bind_result_t::_bind_blob_result(size_t _index, const uint8_t** value, size_t* len) { diff --git a/include/sqlpp11/postgresql/prepared_statement.h b/include/sqlpp11/postgresql/prepared_statement.h index 4282487d..87a94cd5 100644 --- a/include/sqlpp11/postgresql/prepared_statement.h +++ b/include/sqlpp11/postgresql/prepared_statement.h @@ -78,6 +78,7 @@ namespace sqlpp 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); }; @@ -187,6 +188,30 @@ namespace sqlpp } } + 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 value as UTC. + // It is assumed that the database timezone is set to UTC, too. + std::ostringstream os; + os << time; + _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()) diff --git a/tests/postgresql/usage/DateTime.cpp b/tests/postgresql/usage/DateTime.cpp index 29541801..c44d6e0d 100644 --- a/tests/postgresql/usage/DateTime.cpp +++ b/tests/postgresql/usage/DateTime.cpp @@ -31,7 +31,7 @@ #include #include -#include "TabFoo.h" +#include "TabDateTime.h" #include "make_test_connection.h" namespace @@ -39,6 +39,7 @@ namespace const auto now = ::date::floor<::std::chrono::microseconds>(std::chrono::system_clock::now()); const auto today = ::date::floor<::sqlpp::chrono::days>(now); const auto yesterday = today - ::sqlpp::chrono::days{1}; + const auto current = now - today; template auto require_equal(int line, const L& l, const R& r) -> void @@ -60,18 +61,15 @@ int DateTime(int, char*[]) sql::connection db = sql::make_test_connection(); - db.execute(R"(DROP TABLE IF EXISTS tabfoo;)"); - db.execute(R"(CREATE TABLE tabfoo + db.execute(R"(DROP TABLE IF EXISTS tabdatetime;)"); + db.execute(R"(CREATE TABLE tabdatetime ( - alpha bigserial NOT NULL, - beta smallint, - gamma text, - c_bool boolean, c_timepoint timestamp with time zone, + c_time time with time zone, c_day date ))"); - model::TabFoo tab = {}; + model::TabDateTime tab = {}; try { db(insert_into(tab).default_values()); @@ -79,15 +77,18 @@ int DateTime(int, char*[]) { require_equal(__LINE__, row.c_day.is_null(), true); require_equal(__LINE__, row.c_day.value(), ::sqlpp::chrono::day_point{}); + require_equal(__LINE__, row.c_time.is_null(), true); + require_equal(__LINE__, row.c_time.value(), ::std::chrono::microseconds{}); require_equal(__LINE__, row.c_timepoint.is_null(), true); require_equal(__LINE__, row.c_timepoint.value(), ::sqlpp::chrono::microsecond_point{}); } - db(update(tab).set(tab.c_day = today, tab.c_timepoint = now).unconditionally()); + db(update(tab).set(tab.c_day = today, tab.c_time = current, tab.c_timepoint = now).unconditionally()); for (const auto& row : db(select(all_of(tab)).from(tab).unconditionally())) { require_equal(__LINE__, row.c_day.value(), today); + require_equal(__LINE__, row.c_time.value(), current); require_equal(__LINE__, row.c_timepoint.value(), now); } @@ -96,14 +97,17 @@ int DateTime(int, char*[]) for (const auto& row : db(select(all_of(tab)).from(tab).unconditionally())) { require_equal(__LINE__, row.c_day.value(), yesterday); + require_equal(__LINE__, row.c_time.value(), current); require_equal(__LINE__, row.c_timepoint.value(), today); } - auto prepared_update = - db.prepare(update(tab) - .set(tab.c_day = parameter(tab.c_day), tab.c_timepoint = parameter(tab.c_timepoint)) - .unconditionally()); + auto prepared_update = db.prepare(update(tab) + .set(tab.c_day = parameter(tab.c_day), + tab.c_time = parameter(tab.c_time), + tab.c_timepoint = parameter(tab.c_timepoint)) + .unconditionally()); prepared_update.params.c_day = today; + prepared_update.params.c_time = current; prepared_update.params.c_timepoint = now; std::cout << "---- running prepared update ----" << std::endl; db(prepared_update); @@ -111,6 +115,7 @@ int DateTime(int, char*[]) for (const auto& row : db(select(all_of(tab)).from(tab).unconditionally())) { require_equal(__LINE__, row.c_day.value(), today); + require_equal(__LINE__, row.c_time.value(), current); require_equal(__LINE__, row.c_timepoint.value(), now); } } diff --git a/tests/postgresql/usage/TabDateTime.h b/tests/postgresql/usage/TabDateTime.h new file mode 100644 index 00000000..597eeb86 --- /dev/null +++ b/tests/postgresql/usage/TabDateTime.h @@ -0,0 +1,112 @@ +#ifndef MODEL_TABDATETIME_H +#define MODEL_TABDATETIME_H + +#include +#include +#include + +namespace model +{ + namespace TabDateTime_ + { + struct C_timepoint + { + struct _alias_t + { + static constexpr const char _literal[] = "c_timepoint"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T c_timepoint; + T& operator()() + { + return c_timepoint; + } + const T& operator()() const + { + return c_timepoint; + } + }; + }; + + using _traits = ::sqlpp::make_traits<::sqlpp::time_point, sqlpp::tag::can_be_null>; + }; + + struct C_day + { + struct _alias_t + { + static constexpr const char _literal[] = "c_day"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T c_day; + T& operator()() + { + return c_day; + } + const T& operator()() const + { + return c_day; + } + }; + }; + + using _traits = ::sqlpp::make_traits<::sqlpp::day_point, sqlpp::tag::can_be_null>; + }; + + struct C_time + { + struct _alias_t + { + static constexpr const char _literal[] = "c_time"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T c_time; + T& operator()() + { + return c_time; + } + const T& operator()() const + { + return c_time; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + } // namespace TabDatetime_ + + struct TabDateTime : sqlpp::table_t + { + using _value_type = sqlpp::no_value_t; + struct _alias_t + { + static constexpr const char _literal[] = "TabDatetime"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T TabDateTime; + T& operator()() + { + return TabDateTime; + } + const T& operator()() const + { + return TabDateTime; + } + }; + }; + }; +} + +#endif