Skip to content

Commit

Permalink
Merge branch 'master' into odbc_unicode_support
Browse files Browse the repository at this point in the history
  • Loading branch information
bold84 committed Mar 19, 2024
2 parents f3f1c0a + ea28545 commit c922daf
Show file tree
Hide file tree
Showing 55 changed files with 1,758 additions and 769 deletions.
24 changes: 21 additions & 3 deletions docs/lobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();`
Expand All @@ -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

Expand Down
7 changes: 7 additions & 0 deletions include/private/soci-exchange-cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "soci/soci-backend.h"
#include "soci/type-wrappers.h"
#include "soci/blob.h"

#include <cstdint>
#include <ctime>
Expand Down Expand Up @@ -107,6 +108,12 @@ struct exchange_type_traits<x_xmltype>
typedef xml_type value_type;
};

template <>
struct exchange_type_traits<x_blob>
{
typedef blob value_type;
};

// exchange_type_traits not defined for x_statement, x_rowid and x_blob here.

template <exchange_type e>
Expand Down
82 changes: 82 additions & 0 deletions include/private/soci-trivial-blob-backend.h
Original file line number Diff line number Diff line change
@@ -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 <vector>
#include <cstring>
#include <cstdint>

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<decltype(toRead)>(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<std::size_t>(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
29 changes: 22 additions & 7 deletions include/soci/blob.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -62,6 +75,8 @@ class SOCI_DECL blob
SOCI_NOT_COPYABLE(blob)

std::unique_ptr<details::blob_backend> backEnd_;

void ensure_initialized();
};

} // namespace soci
Expand Down
3 changes: 3 additions & 0 deletions include/soci/boost-gregorian-date.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ struct type_conversion<boost::gregorian::date>
out = boost::gregorian::date_from_tm(in);
}

struct move_from_base_check :
std::integral_constant<bool, false> {};

static void to_base(
boost::gregorian::date const & in, base_type & out, indicator & ind)
{
Expand Down
36 changes: 36 additions & 0 deletions include/soci/boost-optional.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ struct type_conversion<boost::optional<T> >
{
typedef typename type_conversion<T>::base_type base_type;

struct from_base_check : std::integral_constant<bool, true> {};

static void from_base(base_type const & in, indicator ind,
boost::optional<T> & out)
{
Expand All @@ -37,6 +39,27 @@ struct type_conversion<boost::optional<T> >
}
}

struct move_from_base_check :
std::integral_constant<bool,
!std::is_const<base_type>::value
&& std::is_constructible<boost::optional<T>, typename std::add_rvalue_reference<base_type>::type>::value
> {};


static void move_from_base(base_type & in, indicator ind, boost::optional<T> & 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<T> const & in,
base_type & out, indicator & ind)
{
Expand All @@ -49,6 +72,19 @@ struct type_conversion<boost::optional<T> >
ind = i_null;
}
}

static void move_to_base(boost::optional<T> & in, base_type & out, indicator & ind)
{
if (in.is_initialized())
{
out = std::move(in.get());
ind = i_ok;
}
else
{
ind = i_null;
}
}
};

} // namespace soci
Expand Down
6 changes: 3 additions & 3 deletions include/soci/db2/soci-db2.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
Expand Down
6 changes: 3 additions & 3 deletions include/soci/empty/soci-empty.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
Expand Down
51 changes: 21 additions & 30 deletions include/soci/firebird/soci-firebird.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <cstdlib>
#include <vector>
#include <string>
#include <cstdint>

namespace soci
{
Expand Down Expand Up @@ -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<char> data_;

std::vector<std::uint8_t> data_;
bool loaded_;
long max_seg_size_;
};
Expand Down
Loading

0 comments on commit c922daf

Please sign in to comment.