有没有办法解决平面缓冲区联合中 255 种类型的限制?

Is there a way to workaround the limit of 255 types in a flatbuffers union?

我正在使用平面缓冲区序列化来自 sql table 的行。我有一个 Statement.fbs 将语句定义为 Insert、Update、Delete 等。该语句有一个成员 "Row",它是所有 sql table 类型的联合。但是,我有超过 255 tables,并且在使用 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。

我没有看到任何更改此行为的选项。

我可以创建一个枚举来处理我的所有 table,方法是在我的平面缓冲区模式文件中将基础类型指定为 uint16。

语句架构:

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;

allobjects Row 联合有点大,无法包含在此处。

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

我想这是联合类型应该只使用一个字节的平面缓冲区的设计决策。我可以接受,但我真的想要一个解决方法。

遗憾的是这是一个设计错误,目前还没有解决方法。将其修复为可配置是可能的,但考虑到依赖它作为一个字节的语言端口的数量,这将是一项相当大的工作。参见例如这里:https://github.com/google/flatbuffers/issues/4209

是的,多个联合是一个笨拙的解决方法。

另一种方法是将类型定义为枚举。但是,现在您遇到的问题是您没有类型安全的方法来存储 table。这可以通过 "nested flatbuffer" 来实现,即将联合值存储为字节向量,一旦你检查了枚举,你就可以用正确的类型廉价地调用 GetRoot。

另一个选项可能是枚举 + 联合,如果唯一类型记录的数量小于 256。例如,您可能有多个行类型,即使它们具有不同的名称,但它们的内容只是一个字符串, 因此它们可以合并为联合类型。

另一个 hack 可能是声明一个 table RowBaseClass {} 或其他什么,这将是字段的类型,但你永远不会真正实例化这个 table。然后根据您使用的语言来回转换为该类型以存储实际的 table。

联合的 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