Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions src/fb-cpp/Descriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
///
namespace fbcpp
{
///
/// Firebird character set identifier for OCTETS.
///
inline constexpr unsigned octetsCharSetId = 1u;

///
/// Descriptor original type.
///
Expand Down
47 changes: 45 additions & 2 deletions src/fb-cpp/Row.h
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,31 @@ namespace fbcpp
}
}

///
/// @brief Reads a text or varying column as its raw bytes.
///
std::optional<std::vector<std::byte>> getBytes(unsigned index)
{
const auto& descriptor = getDescriptor(index);

if (*reinterpret_cast<const std::int16_t*>(&message[descriptor.nullOffset]) != FB_FALSE)
return std::nullopt;

const auto data = &message[descriptor.offset];

switch (descriptor.adjustedType)
{
case DescriptorAdjustedType::STRING:
{
const auto length = *reinterpret_cast<const std::uint16_t*>(data);
return std::vector<std::byte>{data + sizeof(std::uint16_t), data + sizeof(std::uint16_t) + length};
}

default:
throwInvalidType("std::vector<std::byte>", descriptor.adjustedType);
}
}

///
/// @}
///
Expand Down Expand Up @@ -871,8 +896,20 @@ namespace fbcpp
break;

case DescriptorAdjustedType::STRING:
if constexpr (variantContainsV<std::string, V>)
return V{get<std::optional<std::string>>(index).value()};
if (descriptor.charSetId == octetsCharSetId)
{
if constexpr (variantContainsV<std::vector<std::byte>, V>)
return V{get<std::optional<std::vector<std::byte>>>(index).value()};
if constexpr (variantContainsV<std::string, V>)
return V{get<std::optional<std::string>>(index).value()};
}
else
{
if constexpr (variantContainsV<std::string, V>)
return V{get<std::optional<std::string>>(index).value()};
if constexpr (variantContainsV<std::vector<std::byte>, V>)
return V{get<std::optional<std::vector<std::byte>>>(index).value()};
}
break;

case DescriptorAdjustedType::DATE:
Expand Down Expand Up @@ -1300,6 +1337,12 @@ namespace fbcpp
return getString(index);
}

template <>
inline std::optional<std::vector<std::byte>> Row::get<std::optional<std::vector<std::byte>>>(unsigned index)
{
return getBytes(index);
}

///
/// @}
///
Expand Down
98 changes: 98 additions & 0 deletions src/fb-cpp/Statement.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include <memory>
#include <optional>
#include <stdexcept>
#include <span>
#include <string>
#include <string_view>
#include <type_traits>
Expand Down Expand Up @@ -1249,6 +1250,72 @@ namespace fbcpp
*reinterpret_cast<std::int16_t*>(&message[descriptor.nullOffset]) = FB_FALSE;
}

///
/// @brief Binds raw bytes to a text or varying parameter.
///
void setBytes(unsigned index, std::span<const std::byte> value)
{
assert(isValid());

const auto& descriptor = getInDescriptor(index);
auto* const message = inMessage.data();

switch (descriptor.adjustedType)
{
case DescriptorAdjustedType::STRING:
if (value.size() > descriptor.length)
{
static constexpr std::intptr_t STATUS_STRING_TRUNCATION[] = {
isc_arith_except,
isc_string_truncation,
isc_arg_end,
};

throw DatabaseException(getClient(), STATUS_STRING_TRUNCATION);
}

*reinterpret_cast<std::uint16_t*>(&message[descriptor.offset]) =
static_cast<std::uint16_t>(value.size());
std::copy(value.begin(), value.end(), &message[descriptor.offset + sizeof(std::uint16_t)]);
break;

default:
throwInvalidType("std::span<const std::byte>", descriptor.adjustedType);
}

*reinterpret_cast<std::int16_t*>(&message[descriptor.nullOffset]) = FB_FALSE;
}

///
/// @brief Binds a vector of raw bytes to a text or varying parameter.
///
void setBytes(unsigned index, const std::vector<std::byte>& value)
{
setBytes(index, std::span<const std::byte>{value});
}

///
/// @brief Binds an optional vector of raw bytes to a text or varying parameter.
///
void setBytes(unsigned index, std::optional<std::vector<std::byte>> optValue)
{
if (!optValue.has_value())
{
setNull(index);
return;
}

setBytes(index, std::span<const std::byte>{optValue.value()});
}

///
/// @brief Binds a null byte value.
///
void setBytes(unsigned index, std::nullopt_t)
{
setNull(index);
}

///
/// @brief Binds a blob identifier to the specified parameter or null.
///
Expand Down Expand Up @@ -1560,6 +1627,22 @@ namespace fbcpp
setString(index, value);
}

///
/// @brief Convenience overload that binds a vector of raw bytes.
///
void set(unsigned index, const std::vector<std::byte>& value)
{
setBytes(index, value);
}

///
/// @brief Convenience overload that binds a span of raw bytes.
///
void set(unsigned index, std::span<const std::byte> value)
{
setBytes(index, value);
}

///
/// @brief Convenience template that forwards optional values to specialized overloads.
///
Expand Down Expand Up @@ -1878,6 +1961,15 @@ namespace fbcpp
return outRow->getString(index);
}

///
/// @brief Reads a text or varying column as its raw bytes.
///
std::optional<std::vector<std::byte>> getBytes(unsigned index)
{
assert(isValid());
return outRow->getBytes(index);
}

///
/// @}
///
Expand Down Expand Up @@ -2498,6 +2590,12 @@ namespace fbcpp
return getString(index);
}

template <>
inline std::optional<std::vector<std::byte>> Statement::get<std::optional<std::vector<std::byte>>>(unsigned index)
{
return getBytes(index);
}

///
/// @}
///
Expand Down
13 changes: 13 additions & 0 deletions src/fb-cpp/VariantTypeTraits.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
#include "config.h"
#include "types.h"
#include "StructBinding.h"
#include <cstddef>
#include <vector>

#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
#include <boost/multiprecision/cpp_int.hpp>
Expand Down Expand Up @@ -95,6 +97,12 @@ namespace fbcpp::impl::reflection
{
};

// Bytes
template <>
struct IsSupportedVariantType<std::vector<std::byte>> : std::true_type
{
};

// Date/Time types
template <>
struct IsSupportedVariantType<Date> : std::true_type
Expand Down Expand Up @@ -267,6 +275,11 @@ namespace fbcpp::impl::reflection
struct IsOpaqueType<OpaqueTimestampTz> : std::true_type
{
};

template <>
struct IsOpaqueType<std::vector<std::byte>> : std::true_type
{
};
} // namespace fbcpp::impl::reflection


Expand Down
35 changes: 35 additions & 0 deletions src/test/Attachment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
#include "fb-cpp/RowSet.h"
#include "fb-cpp/Statement.h"
#include "fb-cpp/Transaction.h"
#include <cstddef>
#include <exception>
#include <vector>


BOOST_AUTO_TEST_SUITE(AttachmentSuite)
Expand Down Expand Up @@ -266,6 +268,39 @@ BOOST_AUTO_TEST_CASE(queryScalarReturnsFirstColumnOfFirstRow)
transaction.commit();
}

BOOST_AUTO_TEST_CASE(queryScalarReturnsBytes)
{
const auto database = getTempFile("Attachment-queryScalarReturnsBytes.fdb");
Attachment attachment{
getClient(), database, AttachmentOptions().setCreateDatabase(true).setConnectionCharSet("UTF8")};
FbDropDatabase attachmentDrop{attachment};

Transaction transaction{attachment};
const auto value = attachment.queryScalar<std::vector<std::byte>>(
transaction, "select cast('attachment bytes' as varchar(32) character set octets) from rdb$database");

const std::vector<std::byte> expected{
std::byte{'a'},
std::byte{'t'},
std::byte{'t'},
std::byte{'a'},
std::byte{'c'},
std::byte{'h'},
std::byte{'m'},
std::byte{'e'},
std::byte{'n'},
std::byte{'t'},
std::byte{' '},
std::byte{'b'},
std::byte{'y'},
std::byte{'t'},
std::byte{'e'},
std::byte{'s'},
};
BOOST_REQUIRE(value.has_value());
BOOST_CHECK(*value == expected);
}

BOOST_AUTO_TEST_CASE(queryScalarReturnsNulloptForNoRows)
{
const auto database = getTempFile("Attachment-queryScalarReturnsNulloptForNoRows.fdb");
Expand Down
37 changes: 37 additions & 0 deletions src/test/RowSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
#include "fb-cpp/RowSet.h"
#include "fb-cpp/Statement.h"
#include "fb-cpp/Transaction.h"
#include <cstddef>
#include <vector>


BOOST_AUTO_TEST_SUITE(RowSetSuite)
Expand Down Expand Up @@ -337,4 +339,39 @@ BOOST_AUTO_TEST_CASE(includesCurrentRowAndDoesNotDuplicateAcrossUses)
BOOST_CHECK_EQUAL(procedureRowSet.getRow(0).getInt32(0).value(), 42);
}

BOOST_AUTO_TEST_CASE(readBytesFromDisconnectedRowSet)
{
const auto database = getTempFile("RowSet-readBytesFromDisconnectedRowSet.fdb");

Attachment attachment{
getClient(), database, AttachmentOptions().setCreateDatabase(true).setConnectionCharSet("UTF8")};
FbDropDatabase attachmentDrop{attachment};

Transaction transaction{attachment};
Statement select{
attachment, transaction, "select cast('row bytes' as varchar(16) character set octets) from rdb$database"};
BOOST_REQUIRE(select.execute(transaction));

RowSet rowSet{select, 1u};
select.free();

const std::vector<std::byte> expected{
std::byte{'r'},
std::byte{'o'},
std::byte{'w'},
std::byte{' '},
std::byte{'b'},
std::byte{'y'},
std::byte{'t'},
std::byte{'e'},
std::byte{'s'},
};
const auto result = rowSet.getRow(0).getBytes(0);
BOOST_REQUIRE(result.has_value());
BOOST_CHECK(*result == expected);
const auto typedResult = rowSet.getRow(0).get<std::optional<std::vector<std::byte>>>(0);
BOOST_REQUIRE(typedResult.has_value());
BOOST_CHECK(*typedResult == expected);
}

BOOST_AUTO_TEST_SUITE_END()
Loading
Loading