From 58cc98f44f3be2859392e8250e6848fdc73ea426 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Tue, 10 Nov 2026 22:51:03 -0300 Subject: [PATCH 1/2] Align message buffers and RowSet rows Message buffers (Statement input/output, RequestMessage, RowSet) now use MessageBuffer, a std::vector whose start is aligned to MESSAGE_BUFFER_ALIGNMENT (16 bytes) through an aligned allocator, instead of relying on the global allocator's alignment. RowSet rows are now spaced by getRowStride(), the message length rounded up to that alignment, so every row starts aligned (previously rows after the first could be misaligned). getAlignedLength() is not used because it requires a Firebird 4.0+ client. --- src/fb-cpp/MessageBuffer.h | 99 +++++++++++++++++++++++++++++ src/fb-cpp/RowSet.cpp | 7 +- src/fb-cpp/RowSet.h | 22 ++++++- src/fb-cpp/Statement.cpp | 4 +- src/fb-cpp/Statement.h | 11 ++-- src/fb-cpp/impl/ParameterSetter.h | 3 +- src/fb-cpp/request/RequestMessage.h | 5 +- src/test/RowSet.cpp | 49 +++++++++++++- 8 files changed, 182 insertions(+), 18 deletions(-) create mode 100644 src/fb-cpp/MessageBuffer.h diff --git a/src/fb-cpp/MessageBuffer.h b/src/fb-cpp/MessageBuffer.h new file mode 100644 index 0000000..deb4a9c --- /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 final + { + 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) From f90965a36874b06a0f57d488272a4fcac26fc821 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Wed, 11 Nov 2026 00:03:05 -0300 Subject: [PATCH 2/2] Don't mark AlignedAllocator final (libstdc++ derives from the allocator) --- src/fb-cpp/MessageBuffer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fb-cpp/MessageBuffer.h b/src/fb-cpp/MessageBuffer.h index deb4a9c..af5b101 100644 --- a/src/fb-cpp/MessageBuffer.h +++ b/src/fb-cpp/MessageBuffer.h @@ -44,7 +44,7 @@ namespace fbcpp namespace impl { template - struct AlignedAllocator final + struct AlignedAllocator { using value_type = T;