有没有一种方法可以解决Flatbuffers联合中255个类型的限制?

时间:2019-05-01 19:24:43

标签: unions flatbuffers

我正在使用Flatbuffers序列化sql表中的行。我有一个Statement.fbs,它将一条语句定义为Insert,Update,Delete等。该语句具有成员“ Row”,该成员是所有sql表类型的并集。但是,我有超过255个表,并且用flatc编译时出现此错误:

example.com/image-name:tag

我查看了Flatbuffers代码,发现为联合类型自动创建了一个枚举,并且该枚举的基础类型为uint8_t。

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

我可以通过将基本类型指定为Flatbuffer架构文件中的uint16来创建一个处理所有表的枚举。

语句模式:

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

allobjects的行联合有点大,可以包含在这里。

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;

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

2 个答案:

答案 0 :(得分:0)

可悲的是,这是一个设计错误,目前还没有解决方法。可以将其修复为可配置的,但是鉴于依赖于该端口的语言端口数量(一个字节),这将是一个相当大的工作。参见例如此处:https://github.com/google/flatbuffers/issues/4209

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

另一种选择是将类型定义为枚举。现在,您遇到的问题是,您没有类型安全的方式来存储表。这可以通过“嵌套平面缓冲区”来实现,即将联合值存储为字节向量,然后在检查枚举后即可以正确的类型廉价地调用GetRoot。

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

另一种可能是声明一个table RowBaseClass {}或其他任何东西,这将是字段的类型,但是您实际上不会实例化该表。然后根据您使用的语言来回转换到该类型以存储实际表。

答案 1 :(得分:0)

嵌套到255个联合的限制的解决方案非常简单。

allobjects.fbs:

s3_resource.Bucket(bucket_name).put_object(
        Key=s3_path,
        Body=data,
        ServerSideEncryption ="aws:kms"
    )

Statement.fbs:

namespace Database;

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

main.c:

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;

输出:

#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;
}