Есть ли способ обойти ограничение в 255 типов в союзе плоских буферов? - PullRequest
0 голосов
/ 01 мая 2019

Я использую flatbuffers для сериализации строк из таблиц SQL.У меня есть Statement.fbs, который определяет оператор как Вставить, Обновить, Удалить и т. Д. У оператора есть член «Строка», который является объединением всех типов таблиц SQL.Однако у меня более 255 таблиц, и я получаю эту ошибку при компиляции с помощью flatc:

$ ~/flatbuffers/flatc --cpp -o gen Statement.fbs
error: /home/jkl/fbtest/allobjects.fbs:773: 18: error: enum value does not fit [0; 255]

Я просмотрел код flatbuffers и вижу, что перечисление автоматически создается для типов объединения и что базовый типэтого перечисления uint8_t.

Я не вижу никаких вариантов для изменения этого поведения.

Я могу создать перечисление, которое обрабатывает все мои таблицы, указав базовый тип для uint16 вмой файл схемы flatbuffer.

Схема оператора:

include "allobjects.fbs";

namespace Database;

enum StatementKind : byte { Unknown = 0, Insert, Update, Delete, Truncate }

table Statement {
  kind:StatementKind;
  truncate:[TableKind];
  row:Row;
}

root_type Statement;

Объединение строк аллобуктов немного велико для включения в него.

union Row {
    TypeA,
    TypeB,
    TypeC,
    Etc,
    ...
}

Полагаю, этоПри проектном решении для плоских буферов типы объединения должны использовать только один байт.Я могу принять это, но мне бы очень хотелось обойти это решение.

Ответы [ 2 ]

0 голосов
/ 02 мая 2019

Решение с вложенным буфером для предела 255 союзов довольно простое.

allobjects.fbs:

namespace Database;

table Garbage {
  gid:ulong;
  type:string;
  weight:uint;
}
... many more ...

Statement.fbs:

include "allobjects.fbs";

namespace Database;

enum StatementKind : byte { Unknown = 0, Insert, Update, Delete, Truncate }

// suppose this enum holds the > 255 Row types
enum TableKind : uint16 { Unknown = 0, Garbage, Etc... }

// this is the "union", but with a type enum beyond ubyte size
table Row {
  kind:TableKind;
  // this payload will be the nested flatbuffer
  payload:[ubyte];
}

table Statement {
  kind:StatementKind;
  truncate:[TableKind];
  row:Row;
}

root_type Statement;

main.c:

#include <iostream>
#include "Statement_generated.h"

void encodeInsertGarbage(unsigned long gid,
                         const std::string& type,
                         unsigned int weight,
                         std::vector<uint8_t>& retbuf)
{
    flatbuffers::FlatBufferBuilder fbb;

    // create Garbage flatbuffer
    // I used the "Direct" version so I didn't have to create a flatbuffer string object
    auto garbage = Database::CreateGarbageDirect(fbb, gid, type.c_str(), weight);
    fbb.Finish(garbage);

    // make [ubyte] from encoded "Garbage" object
    auto payload = fbb.CreateVector(fbb.GetBufferPointer(), fbb.GetSize());

    // make the generic Row homebrewed union
    auto obj = Database::CreateRow(fbb, Database::TableKind_Garbage, payload);
    fbb.Finish(obj);

    // create the Statement - 0 for "truncate" since that is not used for Insert
    auto statement = Database::CreateStatement(fbb, Database::StatementKind_Insert, 0, obj);
    fbb.Finish(statement);

    // copy the resulting flatbuffer to output vector
    // just for this test program, typically you write to a file or socket.
    retbuf.assign(fbb.GetBufferPointer(), fbb.GetBufferPointer() + fbb.GetSize());
}

void decodeInsertGarbage(std::vector<uint8_t>& retbuf)
{
    auto statement = Database::GetStatement(retbuf.data());
    auto tableType = statement->row()->kind();
    auto payload = statement->row()->payload();
    // just using a simple "if" statement here, but a full solution
    // could use an array of getters, indexed by TableKind, then
    // wrap it up nice with a template function to cast the return type
    // like rowGet<Garbage>(payload);
    if (tableType == Database::TableKind_Garbage)
    {
        auto garbage = Database::GetGarbage(payload->Data());
        std::cout << "  gid: " << garbage->gid() << std::endl;
        std::cout << "  type: " << garbage->type()->c_str() << std::endl;
        std::cout << "  weight: " << garbage->weight() << std::endl;
    }
}

int main()
{
    std::vector<uint8_t> iobuf;
    encodeInsertGarbage(0, "solo cups", 12, iobuf);
    decodeInsertGarbage(iobuf);
    return 0;
}

Вывод:

$ ./fbtest 
  gid: 0
  type: solo cups
  weight: 12
0 голосов
/ 01 мая 2019

Это, к сожалению, небольшая ошибка в дизайне, и пока нет обходного пути.Исправить это, чтобы быть настраиваемым, возможно, но это было бы довольно трудоемко, учитывая количество языковых портов, которые полагаются на то, что он является байтом.См., Например, здесь: https://github.com/google/flatbuffers/issues/4209

Да, несколько объединений - это неуклюжий обходной путь.

Альтернативой может быть определение типа как перечисления.Теперь у вас проблема в том, что у вас нет безопасного для хранения таблиц способа.Это может быть достигнуто с помощью «вложенного плоского буфера», то есть сохранения значения объединения в виде вектора байтов, который можно затем дешево вызывать для GetRoot с правильным типом после проверки перечисления.

Другой вариант можетбыть перечислением + объединением, если число уникальных типов записей <256. Например, у вас может быть несколько типов строк, которые, даже если у них разные имена, их содержимое является просто строкой, поэтому их можно объединить длятип объединения. </p>

Другим хаком может быть объявление table RowBaseClass {} или чего-либо еще, что будет типом поля, но вы никогда не создадите экземпляр этой таблицы.Затем вы переходите туда и обратно к этому типу, чтобы сохранить фактическую таблицу, в зависимости от используемого вами языка.

...