Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revamp BLOB implementations #992

Merged
merged 78 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
843e190
Postgresql: Implement support for BLOB trimming
Krzmbrzl Sep 15, 2022
2f330d4
Revamp Postgresql BLOB implementation
Krzmbrzl Sep 15, 2022
8f0dbcb
Simplify SQLite BLOB implementation
Krzmbrzl Sep 15, 2022
642cd19
SQLite: Be consistent where to implement methods
Krzmbrzl Sep 16, 2022
02642d0
Streamline SQLite BLOB interface
Krzmbrzl Sep 15, 2022
6cbc98e
SQLite: prefer named constant over plain NULL as parameter
Krzmbrzl Sep 16, 2022
c8e3de6
Request SQLite to copy BLOB data
Krzmbrzl Sep 16, 2022
8cc3512
SQLite allow insertion of default-constructed BLOBs
Krzmbrzl Sep 16, 2022
e86da4e
Extend SQLite BLOB tests
Krzmbrzl Sep 15, 2022
213e99e
Extend BLOB documentation
Krzmbrzl Sep 15, 2022
e80e494
Postgresql: BLOB - Implement append in terms of write
Krzmbrzl Sep 15, 2022
22af83a
Postgresql: BLOB - make errors more helpful
Krzmbrzl Sep 15, 2022
52b6862
Postgresql: Implement read-ops on uninitialized BLOBs
Krzmbrzl Sep 16, 2022
7fa93e8
BLOB: Add default-constructed read tests
Krzmbrzl Sep 16, 2022
5e2efa4
Factor out vector-based blob-backend
Krzmbrzl Sep 16, 2022
2e27651
MySQL: Add support for BLOBs
Krzmbrzl Sep 16, 2022
fb492ee
Oracle: Be conforming to documentation
Krzmbrzl Sep 18, 2022
047b749
Oracle: Streamline BLOB interface
Krzmbrzl Sep 18, 2022
4eec33a
Oracle: Extend test cases
Krzmbrzl Sep 18, 2022
62a58ad
fix bug in trivial_blob_backend
Krzmbrzl Sep 19, 2022
68033e0
Start implementing shared BLOB tests
Krzmbrzl Sep 19, 2022
a075da6
Fix Postgresql out-of-bound write not erroring
Krzmbrzl Jan 7, 2023
3ed2c88
Oracle: Make conforming to common standards
Krzmbrzl Jan 7, 2023
761e35d
Firebird: Fix failing common tests
Krzmbrzl Jan 7, 2023
25b5b27
Fix default-constructed blob test
Krzmbrzl Jan 7, 2023
3d3f456
trivial_backend: don't implement depracated functions
Krzmbrzl Jan 7, 2023
de996e9
Disable BLOB tests for ODBC::MySQL
Krzmbrzl Jan 7, 2023
ae75990
finalize common blob test & remove backend-specific ones
Krzmbrzl Jan 7, 2023
53969cc
postgres: require halfway recent Postgresql version (9+)
Krzmbrzl Jan 7, 2023
50c7130
postgresql: properly reset blob
Krzmbrzl Jan 7, 2023
b00f9e1
postgresql: clone before modify after having inserted BLOB into DB
Krzmbrzl Jan 7, 2023
d3b722a
extended BLOB test-cases
Krzmbrzl Jan 7, 2023
be816d2
oracle: don't reset after use
Krzmbrzl Jan 8, 2023
ad61165
postgresql: remove unnecessary include
Krzmbrzl Jan 8, 2023
3b5a0fe
Make describe_column properly identify BLOB cols
Krzmbrzl Oct 15, 2023
9f05ee4
Add rowset type detection test for BLOBs
Krzmbrzl Oct 15, 2023
f48b1ac
Remove MySQL-specific (and now wrong) BLOB tests
Krzmbrzl Oct 17, 2023
d6986a3
Debug output
Krzmbrzl Jan 8, 2023
552f321
Bind blobs to actual blob objects
Krzmbrzl Oct 24, 2023
48e4609
More debug output
Krzmbrzl Oct 24, 2023
6f35bf1
test
Krzmbrzl Oct 26, 2023
8b5b312
Fix Oracle bug that prevented selecting into initialized BLOB
Krzmbrzl Nov 1, 2023
47d9068
Special case data type for Oracle backend
Krzmbrzl Nov 1, 2023
9040094
Remove superfluous test block
Krzmbrzl Nov 1, 2023
b198321
Add private default ctor to blob class
Krzmbrzl Nov 3, 2023
5f5f5f2
Add creator surrogate for default-constructed BLOBs
Krzmbrzl Nov 9, 2023
dc4c584
Introduce row::move_as as an alternative to row::get()
Krzmbrzl Nov 9, 2023
2491820
Add row::move_as specialization for blobs
Krzmbrzl Nov 9, 2023
eb15a0a
Add BLOB interface test with different types
Krzmbrzl Nov 9, 2023
57fe4fb
Don't explicitly support BLOB<->string interface
Krzmbrzl Nov 14, 2023
e22cc5b
Postgres: Consistently report error messages for failed BLOB operations
Krzmbrzl Nov 18, 2023
f9ecd48
Add assertions on BLOB size and content after move from row
Krzmbrzl Nov 18, 2023
44324ed
Another error message forwarded
Krzmbrzl Nov 18, 2023
fd8bdf1
Properly export template specialization
Krzmbrzl Dec 26, 2023
2fcb46d
Fix error message
Krzmbrzl Dec 28, 2023
9fa9647
Implement metaprogramming checks to Boost wrappers
Krzmbrzl Dec 28, 2023
8a6fbf0
Default to assuming types don't have move_from_base impl
Krzmbrzl Dec 28, 2023
45f5e62
Work around MSVC 2015 issue
Krzmbrzl Jan 9, 2024
7767be0
Increase BLOB test coverage
Krzmbrzl Jan 9, 2024
49bd5c9
Remove superfluous debug message
Krzmbrzl Jan 9, 2024
3f16e00
Revamp Firebird Blob backend implementation
Krzmbrzl Jan 13, 2024
19cf660
Address review comments
Krzmbrzl Jan 13, 2024
aea6b41
Remove outdated comment from docs
Krzmbrzl Jan 13, 2024
77ed7fb
Adapt code formatting
Krzmbrzl Jan 13, 2024
be46771
Address review comments
Krzmbrzl Jan 16, 2024
3e21119
Remove asserts
Krzmbrzl Jan 16, 2024
4fc8736
Disable Blob tests for ODBC
Krzmbrzl Jan 18, 2024
766d7a2
Moved trivial blob backend impl to private header
Krzmbrzl Jan 18, 2024
a5921ce
Rename type-traits.h to is-detected.h
Krzmbrzl Jan 18, 2024
d92a250
Switch to a void* based API for blobs
Krzmbrzl Jan 18, 2024
0dd4a5a
Oracle: Add 64-bit size support for BLOBs
Krzmbrzl Jan 28, 2024
10d5f08
Postgresql: Add 64-bit size support for BLOBs
Krzmbrzl Jan 28, 2024
f19c23b
Make blobs default-constructible
Krzmbrzl Jan 28, 2024
ea3e29e
Remove unnecessary reinterpret_casts
Krzmbrzl Jan 28, 2024
ca32ddd
Update BLOB docs
Krzmbrzl Jan 28, 2024
08f750c
Make blob default ctor implicit
Krzmbrzl Feb 6, 2024
8798c3f
Fix coding style
Krzmbrzl Feb 6, 2024
f267fb1
Don't use macros for what can be done by a member function
Krzmbrzl Feb 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();

Krzmbrzl marked this conversation as resolved.
Show resolved Hide resolved
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
Loading