diff --git a/include/sqlpp11/detail/parse_date_time.h b/include/sqlpp11/detail/parse_date_time.h new file mode 100644 index 00000000..d6e36170 --- /dev/null +++ b/include/sqlpp11/detail/parse_date_time.h @@ -0,0 +1,128 @@ +#pragma once + +/** + * Copyright (c) 2023, Vesselin Atanasov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +namespace sqlpp +{ + namespace detail + { + // Parse a date string formatted as YYYY-MM-DD + // + inline bool parse_string_date(::sqlpp::chrono::day_point& value, const char* date_string) + { + static const std::regex rx{"(\\d{4})-(\\d{2})-(\\d{2})"}; + std::cmatch mr; + if (std::regex_match(date_string, mr, rx) == false) + { + return false; + } + 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 + }; + return true; + } + + // Parse a date string formatted as YYYY-MM-DD HH:MM:SS.US TZ + // .US are optional fractional seconds, up to 6 digits in length + // TZ is an optional time zone offset formatted as +HH[:MM] or -HH[:MM] + // + inline bool parse_string_date_time(::sqlpp::chrono::microsecond_point& value, const char* date_time_string) + { + 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_time_string, mr, rx) == false) + { + return false; + } + value = + ::sqlpp::chrono::day_point{ + ::date::year{std::atoi(date_time_string + mr.position(1))} / // Year + std::atoi(date_time_string + mr.position(2)) / // Month + std::atoi(date_time_string + mr.position(3)) // Day of month + } + + std::chrono::hours{std::atoi(date_time_string + mr.position(4))} + // Hour + std::chrono::minutes{std::atoi(date_time_string + mr.position(5))} + // Minute + std::chrono::seconds{std::atoi(date_time_string + mr.position(6))} + // Second + ::std::chrono::microseconds{ // Second fraction + mr[7].matched ? std::stoi((mr[7].str() + "000000").substr(0, 6)) : 0 + }; + if (mr[8].matched) + { + const auto tz_sign = (date_time_string[mr.position(8)] == '+') ? 1 : -1; + const auto tz_offset = + std::chrono::hours{std::atoi(date_time_string + mr.position(9))} + + std::chrono::minutes{mr[10].matched ? std::atoi(date_time_string + mr.position(10)) : 0} + + std::chrono::seconds{mr[11].matched ? std::atoi(date_time_string + mr.position(11)) : 0}; + value -= tz_sign * tz_offset; + } + return true; + } + + // Parse a time string formatted as HH:MM:SS[.US][ TZ] + // .US is up to 6 digits in length + // TZ is an optional time zone offset formatted as +HH[:MM] or -HH[:MM] + // + inline bool parse_string_time_of_day(::std::chrono::microseconds& value, const char* time_string) + { + 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) == false) + { + return false; + } + 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{ // Second fraction + 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; + } + return true; + } + } // namespace detail +} // namespace sqlpp diff --git a/include/sqlpp11/mysql/char_result.h b/include/sqlpp11/mysql/char_result.h index cc6d11e4..951c1707 100644 --- a/include/sqlpp11/mysql/char_result.h +++ b/include/sqlpp11/mysql/char_result.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -40,50 +41,6 @@ namespace sqlpp { namespace mysql { - namespace detail - { - inline auto check_first_digit(const char* text, bool digit_flag) -> bool - { - if (digit_flag) - { - if (not std::isdigit(*text)) - { - return false; - } - } - else - { - if (std::isdigit(*text) or *text == '\0') - { - return false; - } - } - return true; - } - - inline auto check_date_digits(const char* text) -> bool - { - for (const auto digit_flag : {true, true, true, true, false, true, true, false, true, true}) // YYYY-MM-DD - { - if (not check_first_digit(text, digit_flag)) - return false; - ++text; - } - return true; - } - - inline auto check_time_digits(const char* text) -> bool - { - for (const auto digit_flag : {true, true, false, true, true, false, true, true}) // hh:mm:ss - { - if (not check_first_digit(text, digit_flag)) - return false; - ++text; - } - return true; - } - } // namespace detail - class char_result_t { std::unique_ptr _handle; @@ -184,10 +141,10 @@ namespace sqlpp if (_handle->debug) std::cerr << "MySQL debug: parsing date result at index: " << index << std::endl; + *value = {}; *is_null = (_char_result_row.data == nullptr or _char_result_row.data[index] == nullptr); if (*is_null) { - *value = {}; return; } @@ -195,16 +152,10 @@ namespace sqlpp if (_handle->debug) std::cerr << "MySQL debug: date string: " << date_string << std::endl; - if (detail::check_date_digits(date_string)) - { - const auto ymd = ::date::year(std::atoi(date_string)) / atoi(date_string + 5) / atoi(date_string + 8); - *value = ::sqlpp::chrono::day_point(ymd); - } - else + if (::sqlpp::detail::parse_string_date(*value, date_string) == false) { if (_handle->debug) std::cerr << "MySQL debug: invalid date result: " << date_string << std::endl; - *value = {}; } } @@ -213,10 +164,10 @@ namespace sqlpp if (_handle->debug) std::cerr << "MySQL debug: parsing date result at index: " << index << std::endl; + *value = {}; *is_null = (_char_result_row.data == nullptr or _char_result_row.data[index] == nullptr); if (*is_null) { - *value = {}; return; } @@ -224,41 +175,10 @@ namespace sqlpp if (_handle->debug) std::cerr << "MySQL debug: date_time string: " << date_time_string << std::endl; - if (detail::check_date_digits(date_time_string)) - { - const auto ymd = - ::date::year(std::atoi(date_time_string)) / atoi(date_time_string + 5) / atoi(date_time_string + 8); - *value = ::sqlpp::chrono::day_point(ymd); - } - else + if (::sqlpp::detail::parse_string_date_time(*value, date_time_string) == false) { if (_handle->debug) std::cerr << "MySQL debug: invalid date_time result: " << date_time_string << std::endl; - *value = {}; - - return; - } - - const auto time_string = date_time_string + 11; // YYYY-MM-DDT - if (detail::check_time_digits(time_string)) - { - *value += ::std::chrono::hours(std::atoi(time_string + 0)) + - std::chrono::minutes(std::atoi(time_string + 3)) + std::chrono::seconds(std::atoi(time_string + 6)); - } - else - { - return; - } - - const auto mu_string = time_string + 8; // hh:mm:ss - if (mu_string[0] == '\0') - { - return; - } - auto factor = 100 * 1000; - for (auto i = 1u; i <= 6u and std::isdigit(mu_string[i]); ++i, factor /= 10) - { - *value += ::std::chrono::microseconds(factor * (mu_string[i] - '0')); } } diff --git a/include/sqlpp11/postgresql/bind_result.h b/include/sqlpp11/postgresql/bind_result.h index 92a1681f..220977ee 100644 --- a/include/sqlpp11/postgresql/bind_result.h +++ b/include/sqlpp11/postgresql/bind_result.h @@ -27,13 +27,13 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include #include #include +#include #include #include -#include +#include #include #include "detail/prepared_statement_handle.h" @@ -227,36 +227,24 @@ namespace sqlpp std::cerr << "PostgreSQL debug: binding date result at index: " << index << std::endl; } + *value = {}; *is_null = _handle->result.is_null(_handle->count, index); - - if (!(*is_null)) + if (*is_null) { - const auto date_string = _handle->result.get_char_ptr_value(_handle->count, index); + return; + } + 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; + } + if (::sqlpp::detail::parse_string_date(*value, date_string) == false) + { if (_handle->debug()) { - std::cerr << "PostgreSQL debug: date string: " << date_string << std::endl; + std::cerr << "PostgreSQL debug: got invalid date '" << date_string << "'" << 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; - } - *value = {}; - } - } - else - { - *value = {}; } } @@ -269,49 +257,23 @@ namespace sqlpp std::cerr << "PostgreSQL debug: binding date_time result at index: " << index << std::endl; } + *value = {}; *is_null = _handle->result.is_null(_handle->count, index); - - if (!(*is_null)) + if (*is_null) { - const auto date_string = _handle->result.get_char_ptr_value(_handle->count, index); + return; + } + 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; + } + if (::sqlpp::detail::parse_string_date_time(*value, date_string) == false) + { 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 = {}; + std::cerr << "PostgreSQL debug: got invalid date_time '" << date_string << "'" << std::endl; } } } @@ -325,43 +287,24 @@ namespace sqlpp std::cerr << "PostgreSQL debug: binding time result at index: " << index << std::endl; } + *value = {}; *is_null = _handle->result.is_null(_handle->count, index); - - if (!(*is_null)) + if (*is_null) { - const auto time_string = _handle->result.get_char_ptr_value(_handle->count, index); + return; + } - if (_handle->debug()) - { - std::cerr << "PostgreSQL debug: got time string: " << time_string << std::endl; - } + const auto time_string = _handle->result.get_char_ptr_value(_handle->count, index); - 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->debug()) + { + std::cerr << "PostgreSQL debug: got time string: " << time_string << std::endl; + } + + if (::sqlpp::detail::parse_string_time_of_day(*value, time_string) == false) + { + if (_handle->debug()) { + std::cerr << "PostgreSQL debug: got invalid time '" << time_string << "'" << std::endl; } } } diff --git a/include/sqlpp11/sqlite3/bind_result.h b/include/sqlpp11/sqlite3/bind_result.h index 2aff1e7c..c7bf98f2 100644 --- a/include/sqlpp11/sqlite3/bind_result.h +++ b/include/sqlpp11/sqlite3/bind_result.h @@ -26,13 +26,16 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include -#include #include +#include #include #include #include +#include +#include +#include + #ifdef _MSC_VER #include #pragma warning(push) @@ -45,54 +48,33 @@ namespace sqlpp { namespace detail { - inline auto check_first_digit(const char* text, bool digit_flag) -> bool + // Parse a date string formatted as YYYY-MM-DD[ HH:MM:SS[.US]] + // + inline bool parse_string_date_opt_time(::sqlpp::chrono::microsecond_point& value, const char* date_time_string) { - if (digit_flag) + static const std::regex rx{ + "(\\d{4})-(\\d{2})-(\\d{2})" + "(?: (\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?)?" + }; + std::cmatch mr; + if (std::regex_match(date_time_string, mr, rx) == false) { - if (not std::isdigit(*text)) - { - return false; - } + return false; } - else + value = ::sqlpp::chrono::day_point{ + ::date::year{std::atoi(date_time_string + mr.position(1))} / // Year + std::atoi(date_time_string + mr.position(2)) / // Month + std::atoi(date_time_string + mr.position(3)) // Day of month + }; + if (mr[4].matched) { - if (std::isdigit(*text) or *text == '\0') - { - return false; - } - } - return true; - } - - inline auto check_date_digits(const char* text) -> bool - { - for (const auto digit_flag : {true, true, true, true, false, true, true, false, true, true}) // YYYY-MM-DD - { - if (not check_first_digit(text, digit_flag)) - return false; - ++text; - } - return true; - } - - inline auto check_time_digits(const char* text) -> bool - { - for (const auto digit_flag : {true, true, false, true, true, false, true, true}) // hh:mm:ss - { - if (not check_first_digit(text, digit_flag)) - return false; - ++text; - } - return true; - } - - inline auto check_ms_digits(const char* text) -> bool - { - for (const auto digit_flag : {true, true, true}) - { - if (not check_first_digit(text, digit_flag)) - return false; - ++text; + value += + std::chrono::hours{std::atoi(date_time_string + mr.position(4))} + // Hour + std::chrono::minutes{std::atoi(date_time_string + mr.position(5))} + // Minute + std::chrono::seconds{std::atoi(date_time_string + mr.position(6))} + // Second + ::std::chrono::microseconds{ // Second fraction + mr[7].matched ? std::stoi((mr[7].str() + "000000").substr(0, 6)) : 0 + }; } return true; } @@ -215,10 +197,10 @@ namespace sqlpp if (_handle->debug) std::cerr << "Sqlite3 debug: binding date result at index: " << index << std::endl; + *value = {}; *is_null = sqlite3_column_type(_handle->sqlite_statement, static_cast(index)) == SQLITE_NULL; if (*is_null) { - *value = {}; return; } @@ -226,17 +208,10 @@ namespace sqlpp reinterpret_cast(sqlite3_column_text(_handle->sqlite_statement, static_cast(index))); if (_handle->debug) std::cerr << "Sqlite3 debug: date string: " << date_string << std::endl; - - if (detail::check_date_digits(date_string)) - { - const auto ymd = ::date::year(std::atoi(date_string)) / atoi(date_string + 5) / atoi(date_string + 8); - *value = ::sqlpp::chrono::day_point{ymd}; - } - else + if (::sqlpp::detail::parse_string_date(*value, date_string) == false) { if (_handle->debug) std::cerr << "Sqlite3 debug: invalid date result: " << date_string << std::endl; - *value = {}; } } @@ -245,10 +220,10 @@ namespace sqlpp if (_handle->debug) std::cerr << "Sqlite3 debug: binding date result at index: " << index << std::endl; + *value = {}; *is_null = sqlite3_column_type(_handle->sqlite_statement, static_cast(index)) == SQLITE_NULL; if (*is_null) { - *value = {}; return; } @@ -256,40 +231,11 @@ namespace sqlpp reinterpret_cast(sqlite3_column_text(_handle->sqlite_statement, static_cast(index))); if (_handle->debug) std::cerr << "Sqlite3 debug: date_time string: " << date_time_string << std::endl; - - if (detail::check_date_digits(date_time_string)) - { - const auto ymd = - ::date::year(std::atoi(date_time_string)) / atoi(date_time_string + 5) / atoi(date_time_string + 8); - *value = ::sqlpp::chrono::day_point{ymd}; - } - else + // We treat DATETIME fields as containing either date+time or just date. + if (detail::parse_string_date_opt_time(*value, date_time_string) == false) { if (_handle->debug) std::cerr << "Sqlite3 debug: invalid date_time result: " << date_time_string << std::endl; - *value = {}; - - return; - } - - const auto time_string = date_time_string + 11; // YYYY-MM-DDT - if (detail::check_time_digits(time_string)) - { - *value += ::std::chrono::hours{std::atoi(time_string + 0)} + - std::chrono::minutes{std::atoi(time_string + 3)} + std::chrono::seconds{std::atoi(time_string + 6)}; - } - else - { - return; - } - const auto ms_string = time_string + 9; // hh:mm:ss. - if (detail::check_ms_digits(ms_string) and ms_string[4] == '\0') - { - *value += ::std::chrono::milliseconds{std::atoi(ms_string)}; - } - else - { - return; } }