From 8956e350664d7384fbc66c2b1bc3567287c60781 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 27 Mar 2024 01:15:25 +0100 Subject: [PATCH 1/7] Update the list of tested platforms in PostgreSQL backend docs Being it up to date with the reality. --- docs/backends/postgresql.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/docs/backends/postgresql.md b/docs/backends/postgresql.md index f7268ac32..8188c66a6 100644 --- a/docs/backends/postgresql.md +++ b/docs/backends/postgresql.md @@ -12,17 +12,13 @@ The SOCI PostgreSQL backend is supported for use with PostgreSQL >= 7.3, althoug |PostgreSQL|OS|Compiler| |--- |--- |--- | -|9.6|Windows Server 2016|MSVC++ 14.1| -|9.4|Windows Server 2012 R2|MSVC++ 14.0| -|9.4|Windows Server 2012 R2|MSVC++ 12.0| -|9.4|Windows Server 2012 R2|MSVC++ 11.0| -|9.4|Windows Server 2012 R2|Mingw-w64/GCC 4.8| -|9.3|Ubuntu 12.04|g++ 4.6.3| -|9.0|Mac OS X 10.6.6|g++ 4.2| -|8.4|FreeBSD 8.2|g++ 4.1| -|8.4|Debian 6|g++ 4.3| -|8.4|RedHat 5|g++ 4.3| -|10.03|macOS High Sierra 10.13.5|AppleClang 9.1.0.9020039| +| 14|macOS 11.7|AppleClang 13| +| 14|Ubuntu 22.04|gcc 11.4| +| 13|Windows Server 2019|MSVS 2022| +| 12|Windows Server 2019|MSVS 2019| +| 11|Windows Server 2016|MSVS 2017| +| 10|Windows Server 2012 R2|MSVS 2015| +|9.4|Windows Server 2012 R2|Mingw-w64/GCC 8.1| ### Required Client Libraries From e3bb60e10b9c1cbb663b94a65ffc62f8d1543fd8 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 27 Mar 2024 01:16:51 +0100 Subject: [PATCH 2/7] Remove vestigial traces of SOCI_POSTGRESQL_NOSINLGEROWMODE Also update the minimum supported version in the documentation, it is 9.x now and not 7.3 which isn't, and won't be, supported. --- docs/backends/postgresql.md | 8 +------- src/backends/postgresql/CMakeLists.txt | 14 -------------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/docs/backends/postgresql.md b/docs/backends/postgresql.md index 8188c66a6..4fab5ebeb 100644 --- a/docs/backends/postgresql.md +++ b/docs/backends/postgresql.md @@ -6,7 +6,7 @@ SOCI backend for accessing PostgreSQL database. ### Supported Versions -The SOCI PostgreSQL backend is supported for use with PostgreSQL >= 7.3, although versions older than 8.0 will suffer from limited feature support. See below for details. +The SOCI PostgreSQL backend is supported for use with PostgreSQL >= 9.0, although older versions may suffer from limited feature support. See below for details. ### Tested Platforms @@ -158,9 +158,3 @@ The PostgreSQL backend provides the following concrete classes for native API ac The PostgreSQL backend supports working with data stored in columns of type UUID via simple string operations. All string representations of UUID supported by PostgreSQL are accepted on input, the backend will return the standard format of UUID on output. See the test `test_uuid_column_type_support` for usage examples. - -## Configuration options - -To support older PostgreSQL versions, the following configuration macros are recognized: - -* `SOCI_POSTGRESQL_NOSINLGEROWMODE` - disable single mode retrieving query results row-by-row. It is necessary for PostgreSQL prior to version 9. diff --git a/src/backends/postgresql/CMakeLists.txt b/src/backends/postgresql/CMakeLists.txt index 44c27d3d9..5000da028 100644 --- a/src/backends/postgresql/CMakeLists.txt +++ b/src/backends/postgresql/CMakeLists.txt @@ -11,22 +11,8 @@ include(CMakeDependentOption) -option(SOCI_POSTGRESQL_NOSINGLEROWMODE - "Do not use single row mode. PostgreSQL <9 portability." - OFF) - -if (POSTGRESQL_VERSION VERSION_LESS "9.0.0") - set(SOCI_POSTGRESQL_NOSINGLEROWMODE ON CACHE BOOL "Use single row mode for PostgreSQL 9+" FORCE) -endif() - -if(SOCI_POSTGRESQL_NOSINGLEROWMODE) - add_definitions(-DSOCI_POSTGRESQL_NOSINGLEROWMODE=1) -endif() - soci_backend(PostgreSQL DEPENDS PostgreSQL DESCRIPTION "SOCI backend for PostgreSQL" AUTHORS "Maciej Sobczak, Stephen Hutton" MAINTAINERS "Mateusz Loskot") - -boost_report_value(SOCI_POSTGRESQL_NOSINLGEROWMODE) From 674e65dbd97101e492d65418e99811ce26b9459e Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 27 Mar 2024 01:17:56 +0100 Subject: [PATCH 3/7] Make PostgreSQL backend compatible with versions < 9.3 Don't use 64-bit functions added in 9.3 when using earlier version of the library. This doesn't take much effort to do and like this we can keep supporting 9.x for a bit longer and, notably, keep support for 9.2 in CentOS 7 which is still (just) supported. --- docs/backends/postgresql.md | 4 +++- src/backends/postgresql/blob.cpp | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/backends/postgresql.md b/docs/backends/postgresql.md index 4fab5ebeb..3cbcd8c9d 100644 --- a/docs/backends/postgresql.md +++ b/docs/backends/postgresql.md @@ -125,7 +125,9 @@ The PostgreSQL backend has full support for SOCI's [bulk operations](../binding. ### blob Data Type -The PostgreSQL backend supports working with data stored in columns of type Blob, via SOCI's [blob](../lobs.md) class with the exception that trimming is not supported. +The PostgreSQL backend supports working with data stored in columns of type Blob, via SOCI's [blob](../lobs.md) class. + +Note that 64-bit offsets require PostgreSQL client library 9.3 or later. ### rowid Data Type diff --git a/src/backends/postgresql/blob.cpp b/src/backends/postgresql/blob.cpp index 155e4afc3..39cc984f1 100644 --- a/src/backends/postgresql/blob.cpp +++ b/src/backends/postgresql/blob.cpp @@ -8,6 +8,7 @@ #define SOCI_POSTGRESQL_SOURCE #include "soci/postgresql/soci-postgresql.h" #include // libpq +#include // for PG_VERSION_NUM #include #include #include @@ -22,6 +23,20 @@ using namespace soci; using namespace soci::details; +// We need this helper function when using ancient PostgreSQL versions without +// support for 64-bit offets. +#if PG_VERSION_NUM < 90300 +static int pg_check_fits_32_bits(std::size_t len) +{ + if (len > 0x7fffffff) + { + throw soci_error("64-bit offsets support requires PostgreSQL 9.3 or later"); + } + + return static_cast(len); +} +#endif // PostgreSQL < 9.3 + postgresql_blob_backend::blob_details::blob_details() : oid(InvalidOid), fd(-1) {} @@ -144,7 +159,11 @@ void postgresql_blob_backend::trim(std::size_t newLen) init(); +#if PG_VERSION_NUM >= 90300 int ret_code = lo_truncate64(session_.conn_, details_.fd, newLen); +#else + int ret_code = lo_truncate(session_.conn_, details_.fd, pg_check_fits_32_bits(newLen)); +#endif if (ret_code < 0) { @@ -232,7 +251,11 @@ void postgresql_blob_backend::reset() std::size_t do_seek(std::size_t toOffset, int from, soci::postgresql_session_backend &session, soci::postgresql_blob_backend::blob_details &details) { +#if PG_VERSION_NUM >= 90300 pg_int64 pos = lo_lseek64(session.conn_, details.fd, static_cast(toOffset), from); +#else + int pos = lo_lseek(session.conn_, details.fd, pg_check_fits_32_bits(toOffset), from); +#endif if (pos < 0) { From ae07cd546ee3f21b7dc1be6ff36be5593ccb4a67 Mon Sep 17 00:00:00 2001 From: Lukas Zanner Date: Sun, 25 Feb 2024 00:28:16 +0100 Subject: [PATCH 4/7] Add automatic type conversion to row::get() Convert to the requested type if lossless conversion is possible to make the behaviour more compatible with the previous SOCI versions and more useful. See #1127. --- include/soci/row.h | 8 +- include/soci/type-holder.h | 412 +++++++++++++++++++++++++-- src/core/row.cpp | 3 +- tests/common-tests.h | 115 ++++++++ tests/mysql/test-mysql.cpp | 3 + tests/postgresql/test-postgresql.cpp | 2 +- 6 files changed, 507 insertions(+), 36 deletions(-) diff --git a/include/soci/row.h b/include/soci/row.h index e76aea403..07f7c3432 100644 --- a/include/soci/row.h +++ b/include/soci/row.h @@ -65,7 +65,7 @@ class SOCI_DECL row template inline void add_holder(T* t, indicator* ind) { - holders_.push_back(new details::type_holder(t)); + holders_.push_back(details::holder::make_holder(t)); indicators_.push_back(ind); } @@ -78,7 +78,8 @@ class SOCI_DECL row typedef typename type_conversion::base_type base_type; static_assert(details::can_use_from_base>(), "Can't use row::get() with this type (not convertible/copy-assignable from base_type) - did you mean to use move_as?"); - base_type const& baseVal = holders_.at(pos)->get(); + base_type const& baseVal = + holders_.at(pos)->get(details::value_cast_tag{}); T ret; type_conversion::from_base(baseVal, *indicators_.at(pos), ret); @@ -91,7 +92,8 @@ class SOCI_DECL row typedef typename type_conversion::base_type base_type; static_assert(details::can_use_move_from_base(), "row::move_as() can only be called with types that can be instantiated from a base type rvalue reference"); - base_type & baseVal = holders_.at(pos)->get(); + base_type & baseVal = + holders_.at(pos)->get(details::value_reference_tag{}); T ret; type_conversion::move_from_base(baseVal, *indicators_.at(pos), ret); diff --git a/include/soci/type-holder.h b/include/soci/type-holder.h index a9e189d23..73badaa14 100644 --- a/include/soci/type-holder.h +++ b/include/soci/type-holder.h @@ -8,9 +8,16 @@ #ifndef SOCI_TYPE_HOLDER_H_INCLUDED #define SOCI_TYPE_HOLDER_H_INCLUDED -#include "soci/soci-platform.h" -// std -#include +#include "soci/blob.h" +#include "soci/error.h" +#include "soci/soci-backend.h" +#include "soci/soci-types.h" + +#include +#include +#include +#include +#include #include namespace soci @@ -44,52 +51,395 @@ T* checked_ptr_cast(U* ptr) return static_cast(ptr); } -// Base class holder + derived class type_holder for storing type data -// instances in a container of holder objects -template -class type_holder; +template +struct soci_return_same +{ + static inline T& value(U&) + { + throw std::bad_cast(); + } +}; -class holder +template +struct soci_return_same< + T, U, + typename std::enable_if::value>::type> { -public: - holder() {} - virtual ~holder() {} + static inline T& value(U& val) + { + return val; + } +}; - template - T &get() +// Type safe conversion that throws if the types are mismatched +template +struct soci_cast +{ + static inline T cast(U) { - type_holder* p = checked_ptr_cast >(this); - if (p) - { - return p->template value(); - } - else + throw std::bad_cast(); + } +}; + +// Type safe conversion that is a noop +template +struct soci_cast< + T, U, + typename std::enable_if::value>::type> +{ + static inline T cast(U val) + { + return val; + } +}; + +// Type safe conversion that is widening the type +template +struct soci_cast< + T, U, + typename std::enable_if<( + !std::is_same::value && + std::is_integral::value && + std::is_integral::value + )>::type> +{ + static inline T cast(U val) { + intmax_t t_min = static_cast((std::numeric_limits::min)()); + intmax_t u_min = static_cast((std::numeric_limits::min)()); + uintmax_t t_max = static_cast((std::numeric_limits::max)()); + uintmax_t u_max = static_cast((std::numeric_limits::max)()); + +#ifdef _MSC_VER +// As long as we don't require C++17, we must disable the warning +// "conditional expression is constant" as it can give false positives here. +#pragma warning(push) +#pragma warning(disable:4127) +#endif + if ((t_min > u_min && val < static_cast(t_min)) || + (t_max < u_max && val > static_cast(t_max))) { throw std::bad_cast(); } - } +#ifdef _MSC_VER +#pragma warning(pop) +#endif -private: + return static_cast(val); + } +}; - template - T value(); +union type_holder +{ + std::string* s; + int8_t* i8; + int16_t* i16; + int32_t* i32; + int64_t* i64; + uint8_t* u8; + uint16_t* u16; + uint32_t* u32; + uint64_t* u64; + double* d; + std::tm* t; + blob* b; }; template -class type_holder : public holder +struct type_holder_trait; + +template <> +struct type_holder_trait +{ + static const db_type type = db_string; +}; + +template <> +struct type_holder_trait +{ + static const db_type type = db_int8; +}; + +template <> +struct type_holder_trait +{ + static const db_type type = db_int16; +}; + +template <> +struct type_holder_trait +{ + static const db_type type = db_int32; +}; + +template <> +struct type_holder_trait +{ + static const db_type type = db_int64; +}; + +template <> +struct type_holder_trait +{ + static const db_type type = db_uint8; +}; + +template <> +struct type_holder_trait +{ + static const db_type type = db_uint16; +}; + +template <> +struct type_holder_trait +{ + static const db_type type = db_uint32; +}; + +template <> +struct type_holder_trait +{ + static const db_type type = db_uint64; +}; + +#if defined(SOCI_INT64_IS_LONG) +template <> +struct type_holder_trait : type_holder_trait +{ +}; + +template <> +struct type_holder_trait : type_holder_trait +{ +}; +#elif defined(SOCI_LONG_IS_64_BIT) +template <> +struct type_holder_trait : type_holder_trait +{ +}; + +template <> +struct type_holder_trait : type_holder_trait +{ +}; +#else +template <> +struct type_holder_trait : type_holder_trait +{ +}; + +template <> +struct type_holder_trait : type_holder_trait +{ +}; +#endif + +template <> +struct type_holder_trait +{ + static const db_type type = db_double; +}; + +template <> +struct type_holder_trait +{ + static const db_type type = db_date; +}; + +template <> +struct type_holder_trait +{ + static const db_type type = db_blob; +}; + +struct value_cast_tag{}; +struct value_reference_tag{}; + +// Class for storing type data instances in a container of holder objects +class holder { public: - type_holder(T * t) : t_(t) {} - ~type_holder() override { delete t_; } + template + static holder* make_holder(T* val) + { + return new holder(type_holder_trait::type, val); + } - template - const TypeValue &value() const { return *t_; } + ~holder() + { + switch (dt_) + { + case db_double: + delete val_.d; + break; + case db_int8: + delete val_.i8; + break; + case db_int16: + delete val_.i16; + break; + case db_int32: + delete val_.i32; + break; + case db_int64: + delete val_.i64; + break; + case db_uint8: + delete val_.u8; + break; + case db_uint16: + delete val_.u16; + break; + case db_uint32: + delete val_.u32; + break; + case db_uint64: + delete val_.u64; + break; + case db_date: + delete val_.t; + break; + case db_blob: + delete val_.b; + break; + case db_xml: + case db_string: + delete val_.s; + break; + } + } - template - TypeValue &value() { return *t_; } +#ifdef _MSC_VER +// MSVC complains about "unreachable code" even though all +// code here can be reached. +#pragma warning(push) +#pragma warning(disable:4702) +#endif + template + T get(value_cast_tag) + { + switch (dt_) + { + case db_int8: + return soci_cast::cast(*val_.i8); + case db_int16: + return soci_cast::cast(*val_.i16); + case db_int32: + return soci_cast::cast(*val_.i32); + case db_int64: + return soci_cast::cast(*val_.i64); + case db_uint8: + return soci_cast::cast(*val_.u8); + case db_uint16: + return soci_cast::cast(*val_.u16); + case db_uint32: + return soci_cast::cast(*val_.u32); + case db_uint64: + return soci_cast::cast(*val_.u64); + case db_double: + return soci_cast::cast(*val_.d); + case db_date: + return soci_cast::cast(*val_.t); + case db_blob: + // blob is not copyable + break; + case db_xml: + case db_string: + return soci_cast::cast(*val_.s); + } + + throw std::bad_cast(); + } + + template + T& get(value_reference_tag) + { + switch (dt_) + { + case db_int8: + return soci_return_same::value(*val_.i8); + case db_int16: + return soci_return_same::value(*val_.i16); + case db_int32: + return soci_return_same::value(*val_.i32); + case db_int64: + return soci_return_same::value(*val_.i64); + case db_uint8: + return soci_return_same::value(*val_.u8); + case db_uint16: + return soci_return_same::value(*val_.u16); + case db_uint32: + return soci_return_same::value(*val_.u32); + case db_uint64: + return soci_return_same::value(*val_.u64); + case db_double: + return soci_return_same::value(*val_.d); + case db_date: + return soci_return_same::value(*val_.t); + case db_blob: + return soci_return_same::value(*val_.b); + case db_xml: + case db_string: + return soci_return_same::value(*val_.s); + } + + throw std::bad_cast(); + } +#ifdef _MSC_VER +#pragma warning(pop) +#endif private: - T * t_; + holder(db_type dt, void* val) : dt_(dt) + { + switch (dt_) + { + case db_double: + val_.d = static_cast(val); + return; + case db_int8: + val_.i8 = static_cast(val); + return; + case db_int16: + val_.i16 = static_cast(val); + return; + case db_int32: + val_.i32 = static_cast(val); + return; + case db_int64: + val_.i64 = static_cast(val); + return; + case db_uint8: + val_.u8 = static_cast(val); + return; + case db_uint16: + val_.u16 = static_cast(val); + return; + case db_uint32: + val_.u32 = static_cast(val); + return; + case db_uint64: + val_.u64 = static_cast(val); + return; + case db_date: + val_.t = static_cast(val); + return; + case db_blob: + val_.b = static_cast(val); + return; + case db_xml: + case db_string: + val_.s = static_cast(val); + return; + } + + // This should be unreachable + std::ostringstream ss; + ss << "Created holder with unsupported type " << std::to_string(dt); + throw soci_error(ss.str()); + } + + const db_type dt_; + type_holder val_; }; } // namespace details diff --git a/src/core/row.cpp b/src/core/row.cpp index da145deb7..af4bdc745 100644 --- a/src/core/row.cpp +++ b/src/core/row.cpp @@ -7,6 +7,7 @@ #define SOCI_SOURCE #include "soci/row.h" +#include "soci/type-holder.h" #include #include @@ -114,7 +115,7 @@ template <> blob row::move_as(std::size_t pos) const { typedef typename type_conversion::base_type base_type; - base_type & baseVal = holders_.at(pos)->get(); + base_type & baseVal = holders_.at(pos)->get(value_reference_tag{}); blob ret; type_conversion::move_from_base(baseVal, *indicators_.at(pos), ret); diff --git a/tests/common-tests.h b/tests/common-tests.h index c3d222c6f..d2b8fb42c 100644 --- a/tests/common-tests.h +++ b/tests/common-tests.h @@ -3323,6 +3323,121 @@ TEST_CASE_METHOD(common_tests, "Dynamic binding with type conversions", "[core][ } } +// Dynamic bindings with type casts +TEST_CASE_METHOD(common_tests, "Dynamic row binding 4", "[core][dynamic]") +{ + soci::session sql(backEndFactory_, connectString_); + + SECTION("simple type cast") + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + sql << "insert into soci_test(id, d, str, tm)" + << " values(10, 20.0, 'foobar'," + << tc_.to_date_time("2005-12-19 22:14:17") + << ")"; + + { + row r; + sql << "select id from soci_test", into(r); + + CHECK(r.size() == 1); + CHECK(r.get(0) == 10); + CHECK(r.get(0) == 10); + CHECK(r.get(0) == 10); + CHECK(r.get(0) == 10); + CHECK(r.get(0) == 10); + CHECK(r.get(0) == 10); + CHECK(r.get(0) == 10); + CHECK(r.get(0) == 10); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + } + { + row r; + sql << "select d from soci_test", into(r); + + CHECK(r.size() == 1); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + ASSERT_EQUAL_APPROX(r.get(0), 20.0); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + } + { + row r; + sql << "select str from soci_test", into(r); + + CHECK(r.size() == 1); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK(r.get(0) == "foobar"); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + } + { + row r; + sql << "select tm from soci_test", into(r); + + CHECK(r.size() == 1); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK(r.get(0).tm_year == 105); + CHECK(r.get(0).tm_mon == 11); + CHECK(r.get(0).tm_mday == 19); + CHECK(r.get(0).tm_hour == 22); + CHECK(r.get(0).tm_min == 14); + CHECK(r.get(0).tm_sec == 17); + } + } + SECTION("overflowing type cast") + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + sql << "insert into soci_test(id)" + << " values(" + << (std::numeric_limits::max)() + << ")"; + + row r; + sql << "select id from soci_test", into(r); + + intmax_t v = (intmax_t)(std::numeric_limits::max)(); + uintmax_t uv = (uintmax_t)(std::numeric_limits::max)(); + + CHECK(r.size() == 1); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK(r.get(0) == v); + CHECK(r.get(0) == v); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK_THROWS_AS(r.get(0), std::bad_cast); + CHECK(r.get(0) == uv); + CHECK(r.get(0) == uv); + } +} + TEST_CASE_METHOD(common_tests, "Prepared insert with ORM", "[core][orm]") { soci::session sql(backEndFactory_, connectString_); diff --git a/tests/mysql/test-mysql.cpp b/tests/mysql/test-mysql.cpp index 7d548d217..cc5c50c5f 100644 --- a/tests/mysql/test-mysql.cpp +++ b/tests/mysql/test-mysql.cpp @@ -637,6 +637,7 @@ TEST_CASE("MySQL tinyint", "[mysql][int][tinyint]") REQUIRE(r.size() == 1); CHECK(r.get_properties("val").get_data_type() == dt_long_long); CHECK(r.get_properties("val").get_db_type() == db_uint32); + CHECK(r.get("val") == 0xffffff00); CHECK(r.get("val") == 0xffffff00); CHECK(r.get("val") == 0xffffff00); } @@ -649,6 +650,7 @@ TEST_CASE("MySQL tinyint", "[mysql][int][tinyint]") REQUIRE(r.size() == 1); CHECK(r.get_properties("val").get_data_type() == dt_integer); CHECK(r.get_properties("val").get_db_type() == db_int8); + CHECK(r.get("val") == -123); CHECK(r.get("val") == -123); } { @@ -660,6 +662,7 @@ TEST_CASE("MySQL tinyint", "[mysql][int][tinyint]") REQUIRE(r.size() == 1); CHECK(r.get_properties("val").get_data_type() == dt_integer); CHECK(r.get_properties("val").get_db_type() == db_uint8); + CHECK(r.get("val") == 123); CHECK(r.get("val") == 123); } { diff --git a/tests/postgresql/test-postgresql.cpp b/tests/postgresql/test-postgresql.cpp index 651685866..16ce3fc8d 100644 --- a/tests/postgresql/test-postgresql.cpp +++ b/tests/postgresql/test-postgresql.cpp @@ -1161,7 +1161,7 @@ struct test_enum_with_explicit_custom_type_int_rowset : table_creator_base try { - sql << "CREATE TABLE soci_test( Type integer)"; + sql << "CREATE TABLE soci_test( Type smallint)"; ; } catch (...) From 7194e0367171ea50a4c689f4e3da42af12ea5e6c Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 27 Mar 2024 19:06:06 +0100 Subject: [PATCH 5/7] Avoid MSVC deprecation warnings for oracle_blob_backend functions Overriding a deprecated virtual function results in a warning, at least when using MSVS, so don't do this and override a non-deprecated private virtual function called from the deprecated public ones. No real changes, just refactor the code to avoid warnings. --- include/soci/oracle/soci-oracle.h | 26 ++++++++++++-------------- include/soci/soci-backend.h | 17 +++++++++++------ 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/include/soci/oracle/soci-oracle.h b/include/soci/oracle/soci-oracle.h index 800ad400e..0f7b9eb1d 100644 --- a/include/soci/oracle/soci-oracle.h +++ b/include/soci/oracle/soci-oracle.h @@ -269,22 +269,8 @@ struct oracle_blob_backend : details::blob_backend std::size_t get_len() override; - [[deprecated("Use read_from_start instead")]] - std::size_t read(std::size_t offset, void *buf, std::size_t toRead) override - { - // Offsets are 1-based in Oracle - return read_from_start(buf, toRead, offset - 1); - } - std::size_t read_from_start(void * buf, std::size_t toRead, std::size_t offset = 0) override; - [[deprecated("Use write_from_start instead")]] - std::size_t write(std::size_t offset, const void *buf, std::size_t toWrite) override - { - // Offsets are 1-based in Oracle - return write_from_start(buf, toWrite, offset - 1); - } - std::size_t write_from_start(const void * buf, std::size_t toWrite, std::size_t offset = 0) override; std::size_t append(const void *buf, std::size_t toWrite) override; @@ -300,6 +286,18 @@ struct oracle_blob_backend : details::blob_backend void ensure_initialized(); private: + std::size_t do_deprecated_read(std::size_t offset, void *buf, std::size_t toRead) override + { + // Offsets are 1-based in Oracle + return read_from_start(buf, toRead, offset - 1); + } + + std::size_t do_deprecated_write(std::size_t offset, const void *buf, std::size_t toWrite) override + { + // Offsets are 1-based in Oracle + return write_from_start(buf, toWrite, offset - 1); + } + oracle_session_backend &session_; locator_t lobp_; diff --git a/include/soci/soci-backend.h b/include/soci/soci-backend.h index 5e80d9e3f..f92e0d2ae 100644 --- a/include/soci/soci-backend.h +++ b/include/soci/soci-backend.h @@ -304,21 +304,26 @@ class blob_backend virtual std::size_t get_len() = 0; - [[deprecated("Use read_from_start instead")]] - virtual std::size_t read(std::size_t offset, void* buf, std::size_t toRead) { return read_from_start(buf, toRead, offset); } - virtual std::size_t read_from_start(void* buf, std::size_t toRead, std::size_t offset) = 0; - [[deprecated("Use write_from_start instead")]] - virtual std::size_t write(std::size_t offset, const void* buf, std::size_t toWrite) { return write_from_start(buf, toWrite, offset); } - virtual std::size_t write_from_start(const void* buf, std::size_t toWrite, std::size_t offset) = 0; virtual std::size_t append(const void* buf, std::size_t toWrite) = 0; virtual void trim(std::size_t newLen) = 0; + // Deprecated functions with backend-specific semantics preserved only for + // compatibility. + [[deprecated("Use read_from_start instead")]] + std::size_t read(std::size_t offset, void* buf, std::size_t toRead) { return do_deprecated_read(offset, buf, toRead); } + + [[deprecated("Use write_from_start instead")]] + virtual std::size_t write(std::size_t offset, const void* buf, std::size_t toWrite) { return do_deprecated_write(offset, buf, toWrite); } + private: + virtual std::size_t do_deprecated_read(std::size_t offset, void* buf, std::size_t toRead) { return read_from_start(buf, toRead, offset); } + virtual std::size_t do_deprecated_write(std::size_t offset, const void* buf, std::size_t toWrite) { return write_from_start(buf, toWrite, offset); } + SOCI_NOT_COPYABLE(blob_backend) }; From 74919e4436c76f0cf67a647ce5830c95a562f80a Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 27 Mar 2024 23:47:06 +0100 Subject: [PATCH 6/7] Move check for PostgreSQL 9.3 to CMake from the source file Unfortunately pg_config.h is not available in all installations under Windows, so don't rely on being able to include it and instead check POSTGRESQL_VERSION in CMake and define SOCI_POSTGRESQL_NO_LO64 if necessary. This has an additional advantage of allowing to test this code easily even when using PostgreSQL >= 9.3 by turning on this option. --- src/backends/postgresql/CMakeLists.txt | 14 ++++++++++++++ src/backends/postgresql/blob.cpp | 7 +++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/backends/postgresql/CMakeLists.txt b/src/backends/postgresql/CMakeLists.txt index 5000da028..3debd3aa0 100644 --- a/src/backends/postgresql/CMakeLists.txt +++ b/src/backends/postgresql/CMakeLists.txt @@ -11,8 +11,22 @@ include(CMakeDependentOption) +option(SOCI_POSTGRESQL_NO_LO64 + "Do not use lo_xxx64() functions for compatibility with PostgreSQL < 9.3" + OFF) + +if (POSTGRESQL_VERSION VERSION_LESS "9.3.0") + set(SOCI_POSTGRESQL_NO_LO64 ON CACHE BOOL "Avoid using lo_xxx64() functions" FORCE) +endif() + +if(SOCI_POSTGRESQL_NO_LO64) + add_definitions(-DSOCI_POSTGRESQL_NO_LO64=1) +endif() + soci_backend(PostgreSQL DEPENDS PostgreSQL DESCRIPTION "SOCI backend for PostgreSQL" AUTHORS "Maciej Sobczak, Stephen Hutton" MAINTAINERS "Mateusz Loskot") + +boost_report_value(SOCI_POSTGRESQL_NO_LO64) diff --git a/src/backends/postgresql/blob.cpp b/src/backends/postgresql/blob.cpp index 39cc984f1..bd2cda817 100644 --- a/src/backends/postgresql/blob.cpp +++ b/src/backends/postgresql/blob.cpp @@ -8,7 +8,6 @@ #define SOCI_POSTGRESQL_SOURCE #include "soci/postgresql/soci-postgresql.h" #include // libpq -#include // for PG_VERSION_NUM #include #include #include @@ -25,7 +24,7 @@ using namespace soci::details; // We need this helper function when using ancient PostgreSQL versions without // support for 64-bit offets. -#if PG_VERSION_NUM < 90300 +#ifdef SOCI_POSTGRESQL_NO_LO64 static int pg_check_fits_32_bits(std::size_t len) { if (len > 0x7fffffff) @@ -159,7 +158,7 @@ void postgresql_blob_backend::trim(std::size_t newLen) init(); -#if PG_VERSION_NUM >= 90300 +#ifndef SOCI_POSTGRESQL_NO_LO64 int ret_code = lo_truncate64(session_.conn_, details_.fd, newLen); #else int ret_code = lo_truncate(session_.conn_, details_.fd, pg_check_fits_32_bits(newLen)); @@ -251,7 +250,7 @@ void postgresql_blob_backend::reset() std::size_t do_seek(std::size_t toOffset, int from, soci::postgresql_session_backend &session, soci::postgresql_blob_backend::blob_details &details) { -#if PG_VERSION_NUM >= 90300 +#ifndef SOCI_POSTGRESQL_NO_LO64 pg_int64 pos = lo_lseek64(session.conn_, details.fd, static_cast(toOffset), from); #else int pos = lo_lseek(session.conn_, details.fd, pg_check_fits_32_bits(toOffset), from); From 8a5ed8731727cb1086fb0f341a97a7d60a926253 Mon Sep 17 00:00:00 2001 From: JaroslawCendrowski <165020924+JaroslawCendrowski@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:54:42 +0100 Subject: [PATCH 7/7] Fix conversion of dates before 1900 in Oracle backend Oracle stores dates in a weird "excess-100" format, quoting documentation for the "external 2024-04-01 type": | The century and year bytes (bytes 1 and 2) are in excess-100 notation. | The first byte stores the value of the year, which is 1992, as an | integer, divided by 100, giving 119 in excess-100 notation. The second | byte stores year modulo 100, giving 192. Fix our code to compute the year value correctly for the years before 1900, which were previously off by 100 years. Also add a test checking that this works correctly now by round tripping all dates from second to twenty-second century to the database. Closes #1135. Closes #1136. --- src/backends/oracle/standard-use-type.cpp | 2 +- tests/oracle/test-oracle.cpp | 58 +++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/backends/oracle/standard-use-type.cpp b/src/backends/oracle/standard-use-type.cpp index 78ac109e1..910a0b4a1 100644 --- a/src/backends/oracle/standard-use-type.cpp +++ b/src/backends/oracle/standard-use-type.cpp @@ -450,7 +450,7 @@ void oracle_standard_use_type_backend::pre_use(indicator const *ind) ub1* pos = reinterpret_cast(buf_); *pos++ = static_cast(100 + (1900 + t.tm_year) / 100); - *pos++ = static_cast(100 + t.tm_year % 100); + *pos++ = static_cast(100 + (t.tm_year % 100 + 100) % 100); *pos++ = static_cast(t.tm_mon + 1); *pos++ = static_cast(t.tm_mday); *pos++ = static_cast(t.tm_hour + 1); diff --git a/tests/oracle/test-oracle.cpp b/tests/oracle/test-oracle.cpp index d3531fd13..83def9819 100644 --- a/tests/oracle/test-oracle.cpp +++ b/tests/oracle/test-oracle.cpp @@ -8,6 +8,7 @@ #include "common-tests.h" #include "soci/soci.h" #include "soci/oracle/soci-oracle.h" +#include #include #include #include @@ -20,6 +21,14 @@ using namespace soci::tests; std::string connectString; backend_factory const &backEnd = *soci::factory_oracle(); +struct table_creator_for_timestamp : public tests::table_creator_base +{ + table_creator_for_timestamp(soci::session &sql) : tests::table_creator_base(sql) + { + sql << "create table soci_test(id integer, t timestamp (6))"; + } +}; + // Extra tests for date/time TEST_CASE("Oracle datetime", "[oracle][datetime]") { @@ -85,6 +94,55 @@ TEST_CASE("Oracle datetime", "[oracle][datetime]") CHECK(t_out == std::string(buf)); } + + { + // date and time - between years 1- 2201 + soci::session sql(backEnd, connectString); + + table_creator_for_timestamp tableCreator(sql); + + for(int i = 100; i <= 2201; i = i + 50) + { + char t[10]; + sprintf(t, "%04d", i); + + std::string date = std::string(t) + "-03-28 14:06:13"; + std::tm t1 {}, t2 {}, t4 {}; + + std::istringstream is(date.c_str()); + is.imbue(std::locale(setlocale(LC_ALL, nullptr))); + is >> std::get_time(&t2, "%Y-%m-%d %H:%M:%S"); + REQUIRE(!is.fail()); + + std::tm t3 = t2; + sql << "select t from (select :t as t from dual)", + into(t1), use(t3); + + char buf1[25]; + char buf2[25]; + strftime(buf1, sizeof(buf1), "%Y-%m-%d %H:%M:%S", &t1); + strftime(buf2, sizeof(buf2), "%Y-%m-%d %H:%M:%S", &t2); + std::cout << "buf1 = " << buf1 << ", buf2 = " << buf2 << std::endl; + CHECK(std::string(buf1) == std::string(buf2)); + CHECK(t1.tm_sec == t2.tm_sec); + CHECK(t1.tm_min == t2.tm_min); + CHECK(t1.tm_hour == t2.tm_hour); + CHECK(t1.tm_mday == t2.tm_mday); + CHECK(t1.tm_mon == t2.tm_mon); + CHECK(t1.tm_year == t2.tm_year); + CHECK((1900 + t1.tm_year) == i); + + sql << "insert into soci_test(id, t) values(:i, :t)", use(i), use(t3); + sql << "select t from soci_test where id = :i", use(i), into(t4); + CHECK(t4.tm_sec == t2.tm_sec); + CHECK(t4.tm_min == t2.tm_min); + CHECK(t4.tm_hour == t2.tm_hour); + CHECK(t4.tm_mday == t2.tm_mday); + CHECK(t4.tm_mon == t2.tm_mon); + CHECK(t4.tm_year == t2.tm_year); + CHECK((1900 + t4.tm_year) == i); + } + } } // explicit calls test