diff --git a/docs/lobs.md b/docs/lobs.md index a6273262c..d85cb41c6 100644 --- a/docs/lobs.md +++ b/docs/lobs.md @@ -4,9 +4,17 @@ The SOCI library provides also an interface for basic operations on large objects (BLOBs - Binary Large OBjects). +Selecting a BLOB from a table: + blob b(sql); // sql is a session object sql << "select mp3 from mymusic where id = 123", into(b); +Inserting a BLOB from a table: + + blob b(sql); // sql is a session object + b.write_from_start(data.data(), data.size()); // data is e.g. a std::vector< char > + sql << "insert into mymusic mp3, id VALUES(:mp3, 124)", use(b); + The following functions are provided in the `blob` interface, mimicking the file-like operations: * `std::size_t get_len();` @@ -15,13 +23,23 @@ The following functions are provided in the `blob` interface, mimicking the file * `std::size_t append(char const *buf, std::size_t toWrite);` * `void trim(std::size_t newLen);` -The `offset` parameter is always counted from the beginning of the BLOB's data. +The `offset` parameter is always counted from the beginning of the BLOB's data. `read_from_start` and `write_from_start` and `append` return the amount of read or written bytes. + +### Notes + +* As with empty files (but contrary to e.g. `std::vector`) reading from the **beginning** of an empty blob is a valid operation (effectively a no-op), + e.g. it won't throw or error otherwise. +* It is possible to default-construct `blob` objects. Default-constructed `blob`s are in an invalid state and must not be accessed other than to query + their validity (`is_valid()`) or to initialize them (`initialize(session &session)`) in order to bring them into a valid state. -### Portability notes +#### Portability * The way to define BLOB table columns and create or destroy BLOB objects in the database varies between different database engines. Please see the SQL documentation relevant for the given server to learn how this is actually done. The test programs provided with the SOCI library can be also a simple source of full working examples. -* The `trim` function is not currently available for the PostgreSQL backend. +* BLOBs are currently not implemented for all supported backends. Backends missing an implementation are `ODBC` and `DB2`. +* The plain `read(...)` and `write(...)` functions use offsets in a backend-specific format (some start at zero, some at one). They are retained only for backwards compatibility. Don't use them in new code! +* Some backends (e.g. PostgreSQL) support BLOBs only while a transaction is active. Using a `soci::blob` object outside of a transaction in these cases is undefined behavior. + In order to write portable code, you should always ensure to start a transaction before working with BLOBs and end it only after you are done with the BLOB object. ## Long strings and XML diff --git a/include/private/soci-exchange-cast.h b/include/private/soci-exchange-cast.h index 596386dc4..755af0ae4 100644 --- a/include/private/soci-exchange-cast.h +++ b/include/private/soci-exchange-cast.h @@ -10,6 +10,7 @@ #include "soci/soci-backend.h" #include "soci/type-wrappers.h" +#include "soci/blob.h" #include #include @@ -107,6 +108,12 @@ struct exchange_type_traits typedef xml_type value_type; }; +template <> +struct exchange_type_traits +{ + typedef blob value_type; +}; + // exchange_type_traits not defined for x_statement, x_rowid and x_blob here. template diff --git a/include/private/soci-trivial-blob-backend.h b/include/private/soci-trivial-blob-backend.h new file mode 100644 index 000000000..cc298bb88 --- /dev/null +++ b/include/private/soci-trivial-blob-backend.h @@ -0,0 +1,82 @@ +#ifndef SOCI_PRIVATE_SOCI_TRIVIAL_BLOB_BACKEND_H_INCLUDED +#define SOCI_PRIVATE_SOCI_TRIVIAL_BLOB_BACKEND_H_INCLUDED + +#include "soci/soci-backend.h" + +#include +#include +#include + +namespace soci +{ + +namespace details +{ + +/** + * This Blob implementation uses an explicit buffer that is read from and written to, instead of + * directly communicating with the underlying database. + * Thus, it is intended to be used whenever the underlying database does not offer a more efficient + * way of dealing with BLOBs. + */ +class trivial_blob_backend : public details::blob_backend +{ +public: + std::size_t get_len() override { return buffer_.size(); } + + std::size_t read_from_start(void* buf, std::size_t toRead, + std::size_t offset = 0) override + { + if (offset > buffer_.size() || (offset == buffer_.size() && offset > 0)) + { + throw soci_error("Can't read past-the-end of BLOB data."); + } + + // make sure that we don't try to read + // past the end of the data + toRead = std::min(toRead, buffer_.size() - offset); + + memcpy(buf, buffer_.data() + offset, toRead); + + return toRead; + } + + std::size_t write_from_start(const void* buf, std::size_t toWrite, + std::size_t offset = 0) override + { + if (offset > buffer_.size()) + { + throw soci_error("Can't start writing far past-the-end of BLOB data."); + } + + buffer_.resize(std::max(buffer_.size(), offset + toWrite)); + + memcpy(buffer_.data() + offset, buf, toWrite); + + return toWrite; + } + + std::size_t append(void const* buf, std::size_t toWrite) override + { + return write_from_start(buf, toWrite, buffer_.size()); + } + + void trim(std::size_t newLen) override { buffer_.resize(newLen); } + + std::size_t set_data(void const* buf, std::size_t toWrite) + { + buffer_.clear(); + return write_from_start(buf, toWrite); + } + + const std::uint8_t *get_buffer() const { return buffer_.data(); } + +protected: + std::vector< std::uint8_t > buffer_; +}; + +} + +} + +#endif // SOCI_PRIVATE_SOCI_TRIVIAL_BLOB_BACKEND_H_INCLUDED diff --git a/include/soci/blob.h b/include/soci/blob.h index 868ea8163..2b3d1dccf 100644 --- a/include/soci/blob.h +++ b/include/soci/blob.h @@ -27,32 +27,45 @@ class blob_backend; class SOCI_DECL blob { public: + // Creates an invalid blob object + blob() = default; explicit blob(session & s); ~blob(); blob(blob &&other) = default; blob &operator=(blob &&other) = default; + // Checks whether this blob is in a valid state + bool is_valid() const; + + // (Re)initializes this blob + void initialize(session &s); + std::size_t get_len(); // offset is backend-specific [[deprecated("Use read_from_start instead")]] - std::size_t read(std::size_t offset, char * buf, std::size_t toRead); - - // offset starts from 0 - std::size_t read_from_start(char * buf, std::size_t toRead, + std::size_t read(std::size_t offset, void * buf, std::size_t toRead); + + // Extracts data from this blob into the given buffer. + // At most toRead bytes are extracted (and copied into buf). + // The amount of actually read bytes is returned. + // + // Note: Using an offset > 0 on a blob whose size is less than + // or equal to offset, will throw an exception. + std::size_t read_from_start(void * buf, std::size_t toRead, std::size_t offset = 0); // offset is backend-specific [[deprecated("Use write_from_start instead")]] - std::size_t write(std::size_t offset, char const * buf, + std::size_t write(std::size_t offset, const void * buf, std::size_t toWrite); // offset starts from 0 - std::size_t write_from_start(const char * buf, std::size_t toWrite, + std::size_t write_from_start(const void * buf, std::size_t toWrite, std::size_t offset = 0); - std::size_t append(char const * buf, std::size_t toWrite); + std::size_t append(const void * buf, std::size_t toWrite); void trim(std::size_t newLen); @@ -62,6 +75,8 @@ class SOCI_DECL blob SOCI_NOT_COPYABLE(blob) std::unique_ptr backEnd_; + + void ensure_initialized(); }; } // namespace soci diff --git a/include/soci/boost-gregorian-date.h b/include/soci/boost-gregorian-date.h index 66321c529..97237aa4f 100644 --- a/include/soci/boost-gregorian-date.h +++ b/include/soci/boost-gregorian-date.h @@ -34,6 +34,9 @@ struct type_conversion out = boost::gregorian::date_from_tm(in); } + struct move_from_base_check : + std::integral_constant {}; + static void to_base( boost::gregorian::date const & in, base_type & out, indicator & ind) { diff --git a/include/soci/boost-optional.h b/include/soci/boost-optional.h index a9322b12c..6b06328a9 100644 --- a/include/soci/boost-optional.h +++ b/include/soci/boost-optional.h @@ -22,6 +22,8 @@ struct type_conversion > { typedef typename type_conversion::base_type base_type; + struct from_base_check : std::integral_constant {}; + static void from_base(base_type const & in, indicator ind, boost::optional & out) { @@ -37,6 +39,27 @@ struct type_conversion > } } + struct move_from_base_check : + std::integral_constant::value + && std::is_constructible, typename std::add_rvalue_reference::type>::value + > {}; + + + static void move_from_base(base_type & in, indicator ind, boost::optional & out) + { + static_assert(move_from_base_check::value, + "move_to_base can only be used if the target type can be constructed from an rvalue base reference"); + if (ind == i_null) + { + out.reset(); + } + else + { + out = std::move(in); + } + } + static void to_base(boost::optional const & in, base_type & out, indicator & ind) { @@ -49,6 +72,19 @@ struct type_conversion > ind = i_null; } } + + static void move_to_base(boost::optional & in, base_type & out, indicator & ind) + { + if (in.is_initialized()) + { + out = std::move(in.get()); + ind = i_ok; + } + else + { + ind = i_null; + } + } }; } // namespace soci diff --git a/include/soci/db2/soci-db2.h b/include/soci/db2/soci-db2.h index bb33128be..45191d406 100644 --- a/include/soci/db2/soci-db2.h +++ b/include/soci/db2/soci-db2.h @@ -229,9 +229,9 @@ struct db2_blob_backend : details::blob_backend ~db2_blob_backend() override; std::size_t get_len() override; - std::size_t read_from_start(char* buf, std::size_t toRead, std::size_t offset = 0) override; - std::size_t write_from_start(char const* buf, std::size_t toWrite, std::size_t offset = 0) override; - std::size_t append(char const* buf, std::size_t toWrite) override; + std::size_t read_from_start(void* buf, std::size_t toRead, std::size_t offset = 0) override; + 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; void trim(std::size_t newLen) override; db2_session_backend& session_; diff --git a/include/soci/empty/soci-empty.h b/include/soci/empty/soci-empty.h index ff2047c41..fec2495be 100644 --- a/include/soci/empty/soci-empty.h +++ b/include/soci/empty/soci-empty.h @@ -139,11 +139,11 @@ struct empty_blob_backend : details::blob_backend std::size_t get_len() override; - std::size_t read_from_start(char * buf, std::size_t toRead, std::size_t offset = 0) override; + std::size_t read_from_start(void * buf, std::size_t toRead, std::size_t offset = 0) override; - std::size_t write_from_start(const char * buf, std::size_t toWrite, std::size_t offset = 0) override; + std::size_t write_from_start(const void * buf, std::size_t toWrite, std::size_t offset = 0) override; - std::size_t append(char const* buf, std::size_t toWrite) override; + std::size_t append(const void* buf, std::size_t toWrite) override; void trim(std::size_t newLen) override; empty_session_backend& session_; diff --git a/include/soci/firebird/soci-firebird.h b/include/soci/firebird/soci-firebird.h index fbc039679..361fdb678 100644 --- a/include/soci/firebird/soci-firebird.h +++ b/include/soci/firebird/soci-firebird.h @@ -25,6 +25,7 @@ #include #include #include +#include namespace soci { @@ -258,46 +259,36 @@ struct firebird_blob_backend : details::blob_backend std::size_t get_len() override; - std::size_t read_from_start(char * buf, std::size_t toRead, std::size_t offset = 0) override; + std::size_t read_from_start(void * buf, std::size_t toRead, std::size_t offset = 0) override; - std::size_t write_from_start(const char * buf, std::size_t toWrite, std::size_t offset = 0) override; + std::size_t write_from_start(const void * buf, std::size_t toWrite, std::size_t offset = 0) override; - std::size_t append(char const *buf, std::size_t toWrite) override; + std::size_t append(const void *buf, std::size_t toWrite) override; void trim(std::size_t newLen) override; - firebird_session_backend &session_; - - virtual void save(); - virtual void assign(ISC_QUAD const & bid) - { - cleanUp(); - - bid_ = bid; - from_db_ = true; - } + // Writes the current data into the database by allocating a new BLOB + // object for it. + // + // Returns The ID of the newly created BLOB object + ISC_QUAD save_to_db(); + void assign(ISC_QUAD const & bid); - // BLOB id from in database - ISC_QUAD bid_; +private: + void open(); + long getBLOBInfo(); + void load(); + void writeBuffer(std::size_t offset, void const * buf, + std::size_t toWrite); + void closeBlob(); + firebird_session_backend &session_; + ISC_QUAD blob_id_; // BLOB id was fetched from database (true) // or this is new BLOB bool from_db_; - - // BLOB handle - isc_blob_handle bhp_; - -protected: - - virtual void open(); - virtual long getBLOBInfo(); - virtual void load(); - virtual void writeBuffer(std::size_t offset, char const * buf, - std::size_t toWrite); - virtual void cleanUp(); - + isc_blob_handle blob_handle_; // buffer for BLOB data - std::vector data_; - + std::vector data_; bool loaded_; long max_seg_size_; }; diff --git a/include/soci/is-detected.h b/include/soci/is-detected.h new file mode 100644 index 000000000..506e49f1a --- /dev/null +++ b/include/soci/is-detected.h @@ -0,0 +1,49 @@ +// +// Copyright (C) 2023 Robert Adam +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_IS_DETECTED_H_INCLUDED +#define SOCI_IS_DETECTED_H_INCLUDED + +#include + +namespace soci +{ + +namespace details +{ + +template +using void_t = void; + +using false_type = std::integral_constant; +using true_type = std::integral_constant; + +// Implementation from https://blog.tartanllama.xyz/detection-idiom/ +// Note, this is a stub that we require until standard C++ gets support +// for the detection idiom that is not experimental (and thus can be +// assumed to be present). + +namespace detector_detail +{ + + template