diff --git a/src/fb-cpp/MessageBuffer.h b/src/fb-cpp/MessageBuffer.h new file mode 100644 index 0000000..af5b101 --- /dev/null +++ b/src/fb-cpp/MessageBuffer.h @@ -0,0 +1,99 @@ +/* + * MIT License + * + * Copyright (c) 2026 F.D.Castel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef FBCPP_MESSAGE_BUFFER_H +#define FBCPP_MESSAGE_BUFFER_H + +#include +#include +#include + + +/// +/// fb-cpp namespace. +/// +namespace fbcpp +{ + /// + /// Alignment, in bytes, of the start of every message buffer and of every row inside + /// a RowSet buffer. It is enough for any field of a Firebird message. + /// + inline constexpr std::size_t MESSAGE_BUFFER_ALIGNMENT = 16u; + + namespace impl + { + template + struct AlignedAllocator + { + using value_type = T; + + template + struct rebind + { + using other = AlignedAllocator; + }; + + AlignedAllocator() noexcept = default; + + template + AlignedAllocator(const AlignedAllocator&) noexcept + { + } + + T* allocate(std::size_t n) + { + return static_cast(::operator new(n * sizeof(T), std::align_val_t{Alignment})); + } + + void deallocate(T* p, std::size_t) noexcept + { + ::operator delete(p, std::align_val_t{Alignment}); + } + + template + bool operator==(const AlignedAllocator&) const noexcept + { + return true; + } + }; + + /// + /// Rounds `length` up to a multiple of MESSAGE_BUFFER_ALIGNMENT. + /// + constexpr unsigned alignMessageLength(unsigned length) noexcept + { + constexpr auto alignment = static_cast(MESSAGE_BUFFER_ALIGNMENT); + return (length + alignment - 1u) & ~(alignment - 1u); + } + } // namespace impl + + /// + /// Byte buffer holding Firebird messages, whose start is aligned to + /// MESSAGE_BUFFER_ALIGNMENT regardless of the global allocator. + /// + using MessageBuffer = std::vector>; +} // namespace fbcpp + + +#endif // FBCPP_MESSAGE_BUFFER_H diff --git a/src/fb-cpp/RowSet.cpp b/src/fb-cpp/RowSet.cpp index 13032f5..499422e 100644 --- a/src/fb-cpp/RowSet.cpp +++ b/src/fb-cpp/RowSet.cpp @@ -45,8 +45,9 @@ RowSet::RowSet(Statement& statement, unsigned maxRows) auto outMetadata = statement.getOutputMetadata(); messageLength = outMetadata->getMessageLength(&statusWrapper); + rowStride = alignMessageLength(messageLength); - buffer.resize(static_cast(maxRows) * messageLength); + buffer.resize(static_cast(maxRows) * rowStride); auto resultSet = statement.getResultSetHandle(); auto* dest = buffer.data(); @@ -56,7 +57,7 @@ RowSet::RowSet(Statement& statement, unsigned maxRows) auto& currentRow = statement.getOutputMessage(); assert(currentRow.size() == messageLength); std::copy(currentRow.begin(), currentRow.end(), dest); - dest += messageLength; + dest += rowStride; ++count; statement.clearCurrentRow(); } @@ -72,7 +73,7 @@ RowSet::RowSet(Statement& statement, unsigned maxRows) if (resultSet->fetchNext(&statusWrapper, dest) != fb::IStatus::RESULT_OK) break; - dest += messageLength; + dest += rowStride; ++count; } diff --git a/src/fb-cpp/RowSet.h b/src/fb-cpp/RowSet.h index ae9b329..cfb5b99 100644 --- a/src/fb-cpp/RowSet.h +++ b/src/fb-cpp/RowSet.h @@ -33,6 +33,7 @@ #include "CalendarConverter.h" #include "Descriptor.h" #include "Exception.h" +#include "MessageBuffer.h" #include #include #include @@ -74,6 +75,7 @@ namespace fbcpp : client{o.client}, count{o.count}, messageLength{o.messageLength}, + rowStride{o.rowStride}, buffer{std::move(o.buffer)}, descriptors{std::move(o.descriptors)}, statusWrapper{std::move(o.statusWrapper)}, @@ -82,6 +84,7 @@ namespace fbcpp { o.count = 0; o.messageLength = 0; + o.rowStride = 0; } RowSet& operator=(RowSet&& o) noexcept @@ -91,6 +94,7 @@ namespace fbcpp client = o.client; count = o.count; messageLength = o.messageLength; + rowStride = o.rowStride; buffer = std::move(o.buffer); descriptors = std::move(o.descriptors); statusWrapper = std::move(o.statusWrapper); @@ -98,6 +102,7 @@ namespace fbcpp calendarConverter = std::move(o.calendarConverter); o.count = 0; o.messageLength = 0; + o.rowStride = 0; } return *this; @@ -123,6 +128,15 @@ namespace fbcpp return messageLength; } + /// + /// @brief Returns the distance (in bytes) between the starts of consecutive rows in the buffer. + /// It is the message length rounded up to MESSAGE_BUFFER_ALIGNMENT, so every row starts aligned. + /// + unsigned getRowStride() const noexcept + { + return rowStride; + } + /// /// @brief Returns a Row view for typed access to the row at `index`. /// @param index Zero-based row index (must be < getCount()). @@ -140,14 +154,15 @@ namespace fbcpp std::span getRawRow(unsigned index) const { assert(index < count); - const auto* data = buffer.data() + static_cast(index) * messageLength; + const auto* data = buffer.data() + static_cast(index) * rowStride; return {data, messageLength}; } /// /// @brief Returns the entire contiguous buffer containing all fetched rows. + /// Row index starts at index * getRowStride(). /// - const std::vector& getRawBuffer() const noexcept + const MessageBuffer& getRawBuffer() const noexcept { return buffer; } @@ -156,7 +171,8 @@ namespace fbcpp Client* client; unsigned count = 0; unsigned messageLength = 0; - std::vector buffer; + unsigned rowStride = 0; + MessageBuffer buffer; std::vector descriptors; impl::StatusWrapper statusWrapper; impl::NumericConverter numericConverter; diff --git a/src/fb-cpp/Statement.cpp b/src/fb-cpp/Statement.cpp index f67445f..092c602 100644 --- a/src/fb-cpp/Statement.cpp +++ b/src/fb-cpp/Statement.cpp @@ -84,8 +84,8 @@ Statement::Statement( break; } - const auto processMetadata = [&](FbRef& metadata, std::vector& descriptors, - std::vector& message) + const auto processMetadata = + [&](FbRef& metadata, std::vector& descriptors, MessageBuffer& message) { if (!metadata) return; diff --git a/src/fb-cpp/Statement.h b/src/fb-cpp/Statement.h index 1cf1d38..9d02619 100644 --- a/src/fb-cpp/Statement.h +++ b/src/fb-cpp/Statement.h @@ -37,6 +37,7 @@ #include "NumericConverter.h" #include "CalendarConverter.h" #include "Descriptor.h" +#include "MessageBuffer.h" #include "impl/ParameterSetter.h" #include "SmartPtrs.h" #include "Exception.h" @@ -255,7 +256,7 @@ namespace fbcpp /// /// @brief Provides direct access to the raw input message buffer. /// - std::vector& getInputMessage() noexcept + MessageBuffer& getInputMessage() noexcept { return inMessage; } @@ -271,7 +272,7 @@ namespace fbcpp /// /// @brief Provides direct access to the raw output message buffer. /// - std::vector& getOutputMessage() noexcept + MessageBuffer& getOutputMessage() noexcept { return outMessage; } @@ -808,7 +809,7 @@ namespace fbcpp return inDescriptors; } - std::vector& getSetterMessage() noexcept + MessageBuffer& getSetterMessage() noexcept { return inMessage; } @@ -839,10 +840,10 @@ namespace fbcpp FbRef resultSetHandle; FbRef inMetadata; std::vector inDescriptors; - std::vector inMessage; + MessageBuffer inMessage; FbRef outMetadata; std::vector outDescriptors; - std::vector outMessage; + MessageBuffer outMessage; std::unique_ptr outRow; StatementType type; unsigned cursorFlags = 0; diff --git a/src/fb-cpp/impl/ParameterSetter.h b/src/fb-cpp/impl/ParameterSetter.h index a81fae3..cfa6a4b 100644 --- a/src/fb-cpp/impl/ParameterSetter.h +++ b/src/fb-cpp/impl/ParameterSetter.h @@ -33,6 +33,7 @@ #include "../Client.h" #include "../Descriptor.h" #include "../Exception.h" +#include "../MessageBuffer.h" #include "../NumericConverter.h" #include "../StructBinding.h" #include "../types.h" @@ -1181,7 +1182,7 @@ namespace fbcpp::impl return owner().getSetterDescriptors(); } - std::vector& message() noexcept + MessageBuffer& message() noexcept { return owner().getSetterMessage(); } diff --git a/src/fb-cpp/request/RequestMessage.h b/src/fb-cpp/request/RequestMessage.h index b7bac7f..068e945 100644 --- a/src/fb-cpp/request/RequestMessage.h +++ b/src/fb-cpp/request/RequestMessage.h @@ -31,6 +31,7 @@ #include "../types.h" #include "../Blob.h" #include "../Descriptor.h" +#include "../MessageBuffer.h" #include "../NumericConverter.h" #include "../CalendarConverter.h" #include "../impl/ParameterSetter.h" @@ -185,7 +186,7 @@ namespace fbcpp::request return descriptors; } - std::vector& getSetterMessage() noexcept + MessageBuffer& getSetterMessage() noexcept { return message; } @@ -212,7 +213,7 @@ namespace fbcpp::request private: Client* client; std::vector descriptors; - std::vector message; + MessageBuffer message; impl::StatusWrapper statusWrapper; impl::NumericConverter numericConverter; impl::CalendarConverter calendarConverter; diff --git a/src/test/RowSet.cpp b/src/test/RowSet.cpp index b540acd..eee3beb 100644 --- a/src/test/RowSet.cpp +++ b/src/test/RowSet.cpp @@ -27,6 +27,7 @@ #include "fb-cpp/Statement.h" #include "fb-cpp/Transaction.h" #include +#include #include @@ -62,7 +63,7 @@ BOOST_AUTO_TEST_CASE(fetchRowsIntoRowSet) BOOST_CHECK_EQUAL(rowSet.getCount(), 5u); BOOST_CHECK(rowSet.getMessageLength() > 0); BOOST_CHECK_EQUAL( - rowSet.getRawBuffer().size(), static_cast(rowSet.getCount()) * rowSet.getMessageLength()); + rowSet.getRawBuffer().size(), static_cast(rowSet.getCount()) * rowSet.getRowStride()); // Verify row data using typed Row access. The current execute() row is included. for (unsigned i = 0; i < rowSet.getCount(); ++i) @@ -100,7 +101,51 @@ BOOST_AUTO_TEST_CASE(fetchFewerRowsThanMaxRows) BOOST_CHECK_EQUAL(rowSet.getCount(), 3u); BOOST_CHECK_EQUAL( - rowSet.getRawBuffer().size(), static_cast(rowSet.getCount()) * rowSet.getMessageLength()); + rowSet.getRawBuffer().size(), static_cast(rowSet.getCount()) * rowSet.getRowStride()); +} + +BOOST_AUTO_TEST_CASE(rowsAreAligned) +{ + const auto database = getTempFile("RowSet-rowsAreAligned.fdb"); + + Attachment attachment{getClient(), database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + + const auto isAligned = [](const std::byte* ptr) + { return reinterpret_cast(ptr) % MESSAGE_BUFFER_ALIGNMENT == 0u; }; + + // A message length that is not a multiple of the alignment: smallint + bigint + double + null flags. + Statement select{attachment, transaction, + "select cast(? as smallint), cast(n as bigint) * 1000000000000, cast(n as double precision) / 4 " + "from (select 1 n from rdb$database union all select 2 from rdb$database union all " + "select 3 from rdb$database)"}; + + BOOST_CHECK(isAligned(select.getInputMessage().data())); + BOOST_CHECK(isAligned(select.getOutputMessage().data())); + + select.setInt16(0, 7); + BOOST_REQUIRE(select.execute(transaction)); + + RowSet rowSet{select, 10}; + + BOOST_REQUIRE_EQUAL(rowSet.getCount(), 3u); + BOOST_CHECK_NE(rowSet.getMessageLength() % MESSAGE_BUFFER_ALIGNMENT, 0u); + BOOST_CHECK_EQUAL(rowSet.getRowStride() % MESSAGE_BUFFER_ALIGNMENT, 0u); + BOOST_CHECK_GE(rowSet.getRowStride(), rowSet.getMessageLength()); + + for (unsigned i = 0; i < rowSet.getCount(); ++i) + { + const auto rawRow = rowSet.getRawRow(i); + BOOST_CHECK(isAligned(rawRow.data())); + BOOST_CHECK_EQUAL(rawRow.size(), rowSet.getMessageLength()); + + auto row = rowSet.getRow(i); + BOOST_CHECK_EQUAL(row.getInt16(0).value(), 7); + BOOST_CHECK_EQUAL(row.getInt64(1).value(), static_cast(i + 1) * 1000000000000); + BOOST_CHECK_EQUAL(row.getDouble(2).value(), (i + 1) / 4.0); + } } BOOST_AUTO_TEST_CASE(rowSetIsDisconnectedFromStatement)