Privately introduce QProtobufPropertyOrderBuilder

I wanted to write a Builder class for this for some time now because
protobuf technically supports dynamically retrieving new types using
some URL scheme. But we never needed it, so it wasn't done.
Now that we want to treat a Map entry as what it is - a message with
two fields - we need to be able to set up a "property ordering" object
for it. This is what the builder is for.

Pick-to: 6.8
Change-Id: I3c8da6be98745c5ba270ca320b2898157a1b32e4
Reviewed-by: Tatiana Borisova <tatiana.borisova@qt.io>
This commit is contained in:
Mårten Nordheim 2024-05-30 00:09:35 +02:00 committed by Alexey Edelev
parent 15e10394d7
commit af54f630a2
9 changed files with 250 additions and 1 deletions

2
.gitignore vendored
View File

@ -137,7 +137,7 @@ CMakeCache.txt
cmake_install.cmake
CTestTestfile.cmake
CMakeLists.txt.user
**build*
**/build/
# vscode specific
.vscode/

View File

@ -14,6 +14,7 @@ qt_internal_add_module(Protobuf
qtprotobuflogging.cpp qtprotobuflogging_p.h
qprotobufregistration.cpp qprotobufregistration.h
qprotobufpropertyordering.cpp qprotobufpropertyordering.h
qprotobufpropertyorderingbuilder_p.h
qtprotobuftypes.cpp qtprotobuftypes.h
qprotobufoneof.cpp qprotobufoneof.h
qtprotobufdefs_p.h

View File

@ -2,7 +2,9 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qprotobufpropertyordering.h"
#include "qprotobufpropertyorderingbuilder_p.h"
#include "qtprotobuflogging_p.h"
#include "qtprotobufdefs_p.h"
#include "qprotobufregistration.h"
@ -212,6 +214,117 @@ QProtobufMessagePointer constructMessageByName(const QString &messageType)
return pointer;
}
class QProtobufPropertyOrderingBuilderPrivate
{
public:
struct FieldDefinition
{
QByteArray jsonName;
uint fieldNumber;
uint propertyIndex;
FieldFlags flags;
};
std::vector<FieldDefinition> fields;
QByteArray packageName;
};
QProtobufPropertyOrderingBuilder::QProtobufPropertyOrderingBuilder(QByteArray packageName)
: d_ptr(new QProtobufPropertyOrderingBuilderPrivate())
{
d_ptr->packageName = std::move(packageName);
}
QProtobufPropertyOrderingBuilder::~QProtobufPropertyOrderingBuilder()
{
delete d_ptr;
}
void QProtobufPropertyOrderingBuilder::addV0Field(QByteArray jsonName, uint fieldNumber,
uint propertyIndex, FieldFlags flags)
{
Q_D(QProtobufPropertyOrderingBuilder);
Q_ASSERT_X(fieldNumber <= ProtobufFieldNumMax && fieldNumber >= ProtobufFieldNumMin,
"QProtobufPropertyOrderingBuilder::addV0Field",
"Field number is out of the valid field number range.");
Q_ASSERT(d->fields.size() < ProtobufFieldMaxCount);
d->fields.push_back({ std::move(jsonName), fieldNumber, propertyIndex, flags });
}
/*!
\internal
Builds the QProtobufPropertyOrdering object from the builder data.
The Data struct must be manually destructed (var->~Data()) and then
free()d by the caller.
*/
QProtobufPropertyOrdering::Data *QProtobufPropertyOrderingBuilder::build() const
{
Q_D(const QProtobufPropertyOrderingBuilder);
if (d->fields.size() > ProtobufFieldMaxCount)
return nullptr;
qsizetype charSpaceNeeded = NullTerminator + d->packageName.size() + NullTerminator;
for (const auto &field : d->fields)
charSpaceNeeded += field.jsonName.size() + NullTerminator;
const size_t uintSpaceNeeded = sizeof(QProtobufPropertyOrdering::Data)
+ d->fields.size() * sizeof(uint) * 4 + sizeof(uint) /* eos marker offset */;
static_assert(sizeof(QProtobufPropertyOrdering::Data) == 24,
"Data size changed, update builder code");
const size_t spaceNeeded = uintSpaceNeeded + charSpaceNeeded;
auto *storage = static_cast<char *>(calloc(1, spaceNeeded));
auto *data = new (storage) QProtobufPropertyOrdering::Data();
auto raii = qScopeGuard([data] { data->~Data(); free(data); });
data->version = 0u;
data->numFields = uint(d->fields.size());
data->fieldNumberOffset = uint(d->fields.size() * 1 + 1);
data->propertyIndexOffset = uint(d->fields.size() * 2 + 1);
data->flagsOffset = uint(d->fields.size() * 3 + 1);
data->fullPackageNameSize = uint(d->packageName.size());
using NonConstTag = QProtobufPropertyOrdering::NonConstTag;
QProtobufPropertyOrdering ordering{data};
uint *uintData = ordering.uint_data(NonConstTag{});
uint jsonArrayOffset = data->fullPackageNameSize + NullTerminator;
for (uint i = 0; i < data->numFields; ++i) {
const auto &field = d->fields[i];
if (field.fieldNumber > ProtobufFieldNumMax || field.fieldNumber < ProtobufFieldNumMin)
return nullptr;
uintData[i] = jsonArrayOffset;
jsonArrayOffset += field.jsonName.size() + NullTerminator;
uintData[i + data->fieldNumberOffset] = field.fieldNumber;
uintData[i + data->propertyIndexOffset] = field.propertyIndex;
uintData[i + data->flagsOffset] = uint(field.flags.toInt());
}
uintData[d->fields.size()] = jsonArrayOffset;
Q_ASSERT(jsonArrayOffset + NullTerminator == charSpaceNeeded);
char *charData = ordering.char_data(NonConstTag{});
[[maybe_unused]]
char * const charStart = charData;
charData = std::copy_n(d->packageName.constData(), d->packageName.size() + NullTerminator,
charData);
for (auto &field : d->fields) {
charData = std::copy_n(field.jsonName.constData(), field.jsonName.size() + NullTerminator,
charData);
}
charData[0] = '\0'; // Empty string at the end of the char array
++charData; // Trailing null terminator
Q_ASSERT(std::distance(charStart, charData) == charSpaceNeeded);
Q_ASSERT(quintptr(charData) - quintptr(data) == quintptr(spaceNeeded));
raii.dismiss();
return data;
}
} // namespace QtProtobufPrivate
QT_END_NAMESPACE

View File

@ -33,6 +33,7 @@ enum FieldFlag : uint {
Q_DECLARE_FLAGS(FieldFlags, FieldFlag)
Q_DECLARE_OPERATORS_FOR_FLAGS(FieldFlags)
class QProtobufPropertyOrderingBuilder;
struct QProtobufPropertyOrdering
{
const struct Data
@ -54,6 +55,7 @@ struct QProtobufPropertyOrdering
int fieldCount() const { return int(data->numFields); }
private:
friend class QProtobufPropertyOrderingBuilder;
struct NonConstTag {};
uint *uint_data(NonConstTag) const;
char *char_data(NonConstTag) const;

View File

@ -0,0 +1,52 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QPROTOBUFPROPERTYORDERINGBUILDER_P_H
#define QPROTOBUFPROPERTYORDERINGBUILDER_P_H
#include <QtProtobuf/qtprotobufglobal.h>
#include <QtProtobuf/qprotobufpropertyordering.h>
#include <QtCore/qbytearray.h>
// Source is in qprotobufpropertyordering.cpp
// The intent is to share some of the code
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists for the convenience
// of other Qt classes. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
QT_BEGIN_NAMESPACE
namespace QtProtobufPrivate {
class QProtobufPropertyOrderingBuilderPrivate;
class QProtobufPropertyOrderingBuilder
{
public:
Q_PROTOBUF_EXPORT explicit QProtobufPropertyOrderingBuilder(QByteArray packageName);
Q_DISABLE_COPY_MOVE(QProtobufPropertyOrderingBuilder)
Q_PROTOBUF_EXPORT ~QProtobufPropertyOrderingBuilder();
Q_PROTOBUF_EXPORT void addV0Field(QByteArray jsonName, uint fieldNumber, uint propertyIndex,
FieldFlags flags);
Q_PROTOBUF_EXPORT QProtobufPropertyOrdering::Data *build() const;
private:
QProtobufPropertyOrderingBuilderPrivate *d_ptr;
Q_DECLARE_PRIVATE(QProtobufPropertyOrderingBuilder)
};
} // namespace QtProtobufPrivate
QT_END_NAMESPACE
#endif // QPROTOBUFPROPERTYORDERINGBUILDER_P_H

View File

@ -19,6 +19,7 @@ QT_BEGIN_NAMESPACE
constexpr int ProtobufFieldNumMin = 1;
constexpr int ProtobufFieldNumMax = 536870911;
constexpr int ProtobufFieldMaxCount = ProtobufFieldNumMax - ProtobufFieldNumMin + 1;
QT_END_NAMESPACE

View File

@ -31,3 +31,4 @@ if(TARGET Qt6::qtprotobufgen)
endif()
add_subdirectory(qprotobuflazymessagepointer)
add_subdirectory(qprotobufoneof)
add_subdirectory(qprotobufpropertyorderingbuilder)

View File

@ -0,0 +1,16 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
cmake_minimum_required(VERSION 3.16)
project(tst_qprotobufoneof LANGUAGES CXX)
find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
endif()
qt_internal_add_test(tst_qprotobufpropertyorderingbuilder
SOURCES
tst_qprotobufpropertyorderingbuilder.cpp
LIBRARIES
Qt::ProtobufPrivate
)

View File

@ -0,0 +1,63 @@
// Copyright (C) 2022 The Qt Company Ltd.
// Copyright (C) 2022 Alexey Edelev <semlanik@gmail.com>, Viktor Kopp <vifactor@gmail.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QtTest>
#include <QtProtobuf/private/qprotobufpropertyorderingbuilder_p.h>
class tst_QProtobufPropertyOrderingBuilder : public QObject
{
Q_OBJECT
private Q_SLOTS:
void buildObject();
};
struct DataDeleter {
void operator()(QtProtobufPrivate::QProtobufPropertyOrdering::Data *ptr) noexcept
{
free(ptr);
}
};
void tst_QProtobufPropertyOrderingBuilder::buildObject()
{
using namespace QtProtobufPrivate;
QProtobufPropertyOrderingBuilder builder("TestMessage");
builder.addV0Field("field1", 1, 0, FieldFlag::Optional);
builder.addV0Field("field6", 6, 1, FieldFlag::Repeated | FieldFlag::Enum);
builder.addV0Field("field30", 30, 60, FieldFlag::NoFlags);
std::unique_ptr<QProtobufPropertyOrdering::Data, DataDeleter> data(builder.build());
QProtobufPropertyOrdering ordering{data.get()};
QCOMPARE(ordering.getMessageFullName(), QByteArray("TestMessage"));
QCOMPARE(ordering.fieldCount(), 3);
QCOMPARE(ordering.indexOfFieldNumber(1), 0);
QCOMPARE(ordering.indexOfFieldNumber(6), 1);
QCOMPARE(ordering.indexOfFieldNumber(30), 2);
QCOMPARE(ordering.indexOfFieldNumber(10), -1);
QCOMPARE(ordering.getPropertyIndex(0), 0);
QCOMPARE(ordering.getPropertyIndex(1), 1);
QCOMPARE(ordering.getPropertyIndex(2), 60);
QCOMPARE(ordering.getPropertyIndex(3), -1);
QCOMPARE(ordering.getFieldNumber(0), 1);
QCOMPARE(ordering.getFieldNumber(1), 6);
QCOMPARE(ordering.getFieldNumber(2), 30);
QCOMPARE(ordering.getFieldNumber(3), -1);
QCOMPARE(ordering.getJsonName(0), QByteArray("field1"));
QCOMPARE(ordering.getJsonName(1), QByteArray("field6"));
QCOMPARE(ordering.getJsonName(2), QByteArray("field30"));
QCOMPARE(ordering.getJsonName(3), QByteArray());
QCOMPARE(ordering.getFieldFlags(0), FieldFlag::Optional);
QCOMPARE(ordering.getFieldFlags(1), FieldFlag::Repeated | FieldFlag::Enum);
QCOMPARE(ordering.getFieldFlags(2), FieldFlag::NoFlags);
QCOMPARE(ordering.getFieldFlags(3), FieldFlag::NoFlags);
}
QTEST_MAIN(tst_QProtobufPropertyOrderingBuilder)
#include "tst_qprotobufpropertyorderingbuilder.moc"