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

Allow to row::get blob into suitable containers #1189

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
9 changes: 9 additions & 0 deletions include/soci/row.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ class SOCI_DECL row
column_properties const& get_properties(std::size_t pos) const;
column_properties const& get_properties(std::string const& name) const;

#ifdef _MSC_VER
// MSVC complains about "unreachable code" in case get<base_type> can
// be determined at compile time to throw.
#pragma warning(push)
#pragma warning(disable:4702)
#endif
template <typename T>
T get(std::size_t pos) const
{
Expand Down Expand Up @@ -104,6 +110,9 @@ class SOCI_DECL row

return ret;
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif

template <typename T>
T get(std::size_t pos, T const &nullValue) const
Expand Down
70 changes: 68 additions & 2 deletions include/soci/type-holder.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <sstream>
#include <type_traits>
#include <typeinfo>
#include <iterator>

namespace soci
{
Expand Down Expand Up @@ -128,6 +129,72 @@ struct soci_cast<
}
};

template<typename T, typename Enable = void>
struct is_contiguous_byte_container
{
static constexpr bool value = false;
};

// What we are checking here is not really a guarantee that the container will
// use contiguous memory for storing its elements. However, usually the ability
// of random-access is only supplied for containers that are in fact contiguous
// in memory.
// Once switched to C++20, the check should be for contiguous_iterator_tag instead
template<typename T>
struct is_contiguous_byte_container<
T,
typename std::enable_if<
std::is_same<
typename std::iterator_traits<typename T::iterator>::iterator_category,
std::random_access_iterator_tag
>::value
&& sizeof(typename T::value_type) == sizeof(char)
>::type
>
{
static constexpr bool value = true;
};

static_assert(is_contiguous_byte_container<std::string>::value, "std::string should pass constraint");
static_assert(is_contiguous_byte_container<std::vector<char>>::value, "std::vector<char> should pass constraint");

// Specialization for the blob type
// It throws unless it is requested to be cast to a container
// of type T where sizeof(T) == 1 and which is using
// contiguous storage in memory.
template <typename T>
struct soci_cast<T, blob,
typename std::enable_if<!is_contiguous_byte_container<T>::value>::type>
{
static inline T cast(blob &)
{
// Blobs are non-copyable and therefore, we have to throw
// here even if T is soci::blob as this function is always
// used in context where copying of the blob would happen.
throw std::bad_cast();
}
};

template <typename T>
struct soci_cast<T, blob,
typename std::enable_if<is_contiguous_byte_container<T>::value>::type>
{
static inline T cast(blob &blob)
{
T container;
container.resize(blob.get_len());
Krzmbrzl marked this conversation as resolved.
Show resolved Hide resolved

if (container.size() > 0)
Krzmbrzl marked this conversation as resolved.
Show resolved Hide resolved
{
// Note: if the container isn't actually contiguous in memory, this
// will likely lead to a segmentation fault!
blob.read_from_start(&container[0], container.size(), 0);
}

return container;
}
};

union type_holder
{
std::string* s;
Expand Down Expand Up @@ -340,8 +407,7 @@ class holder
case db_date:
return soci_cast<T, std::tm>::cast(*val_.t);
case db_blob:
// blob is not copyable
break;
return soci_cast<T, blob>::cast(*val_.b);
case db_xml:
case db_string:
return soci_cast<T, std::string>::cast(*val_.s);
Expand Down
32 changes: 32 additions & 0 deletions tests/common/test-lob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@

#include "test-context.h"

#include <vector>
#include <list>
#include <set>

namespace soci
{

Expand Down Expand Up @@ -653,6 +657,34 @@ TEST_CASE_METHOD(common_tests, "BLOB", "[core][blob]")
}
CHECK(containedData);
}
SECTION("get into container")
{
soci::rowset< soci::row > rowSet = (sql.prepare << "select b from soci_test where id=:id", soci::use(id1));
bool containedData = false;
for (auto it = rowSet.begin(); it != rowSet.end(); ++it)
{
containedData = true;
const soci::row &currentRow = *it;

std::string strData = currentRow.get<std::string>(0);
std::vector<unsigned char> vecData = currentRow.get<std::vector<unsigned char>>(0);

// Container is required to hold a type that has a size of 1 byte
CHECK_THROWS(currentRow.get<std::vector<int>>(0));
// Container has to use contiguous storage
CHECK_THROWS(currentRow.get<std::list<char>>(0));
CHECK_THROWS(currentRow.get<std::set<char>>(0));

CHECK(strData.size() == 10);
CHECK(vecData.size() == 10);
for (std::size_t i = 0; i < 10; ++i)
{
CHECK(strData[i] == dummy_data[i]);
CHECK(vecData[i] == dummy_data[i]);
}
}
CHECK(containedData);
}
SECTION("reusing bound blob")
{
int secondID = id2 + 1;
Expand Down