mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-19 20:35:42 +00:00
Flatc parser support for nullable scalars (#6026)
* Parser support for nullable scalars * Use older C++ features * use default element * Add a test for json, flexbuffers, and null * test comments and names Co-authored-by: Casper Neo <cneo@google.com>
This commit is contained in:
@@ -296,6 +296,7 @@ struct FieldDef : public Definition {
|
|||||||
shared(false),
|
shared(false),
|
||||||
native_inline(false),
|
native_inline(false),
|
||||||
flexbuffer(false),
|
flexbuffer(false),
|
||||||
|
nullable(false),
|
||||||
nested_flatbuffer(NULL),
|
nested_flatbuffer(NULL),
|
||||||
padding(0) {}
|
padding(0) {}
|
||||||
|
|
||||||
@@ -314,6 +315,8 @@ struct FieldDef : public Definition {
|
|||||||
bool native_inline; // Field will be defined inline (instead of as a pointer)
|
bool native_inline; // Field will be defined inline (instead of as a pointer)
|
||||||
// for native tables if field is a struct.
|
// for native tables if field is a struct.
|
||||||
bool flexbuffer; // This field contains FlexBuffer data.
|
bool flexbuffer; // This field contains FlexBuffer data.
|
||||||
|
bool nullable; // If True, this field is Null (as opposed to default
|
||||||
|
// valued).
|
||||||
StructDef *nested_flatbuffer; // This field contains nested FlatBuffer data.
|
StructDef *nested_flatbuffer; // This field contains nested FlatBuffer data.
|
||||||
size_t padding; // Bytes to always pad after this field.
|
size_t padding; // Bytes to always pad after this field.
|
||||||
};
|
};
|
||||||
@@ -927,6 +930,7 @@ class Parser : public ParserState {
|
|||||||
|
|
||||||
bool SupportsAdvancedUnionFeatures() const;
|
bool SupportsAdvancedUnionFeatures() const;
|
||||||
bool SupportsAdvancedArrayFeatures() const;
|
bool SupportsAdvancedArrayFeatures() const;
|
||||||
|
bool SupportsNullableScalars() const;
|
||||||
Namespace *UniqueNamespace(Namespace *ns);
|
Namespace *UniqueNamespace(Namespace *ns);
|
||||||
|
|
||||||
FLATBUFFERS_CHECKED_ERROR RecurseError();
|
FLATBUFFERS_CHECKED_ERROR RecurseError();
|
||||||
|
|||||||
@@ -721,7 +721,7 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
|
|||||||
// Only cpp, js and ts supports the union vector feature so far.
|
// Only cpp, js and ts supports the union vector feature so far.
|
||||||
if (!SupportsAdvancedUnionFeatures()) {
|
if (!SupportsAdvancedUnionFeatures()) {
|
||||||
return Error(
|
return Error(
|
||||||
"Vectors of unions are not yet supported in all "
|
"Vectors of unions are not yet supported in at least one of "
|
||||||
"the specified programming languages.");
|
"the specified programming languages.");
|
||||||
}
|
}
|
||||||
// For vector of union fields, add a second auto-generated vector field to
|
// For vector of union fields, add a second auto-generated vector field to
|
||||||
@@ -743,6 +743,19 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
|
|||||||
return Error(
|
return Error(
|
||||||
"default values currently only supported for scalars in tables");
|
"default values currently only supported for scalars in tables");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark the nullable scalars. Note that a side effect of ParseSingleValue is
|
||||||
|
// fixing field->value.constant to null.
|
||||||
|
if (IsScalar(type.base_type)) {
|
||||||
|
field->nullable = (field->value.constant == "null");
|
||||||
|
if (field->nullable && !SupportsNullableScalars()) {
|
||||||
|
return Error(
|
||||||
|
"Nullable scalars are not yet supported in at least one the of "
|
||||||
|
"the specified programming languages."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Append .0 if the value has not it (skip hex and scientific floats).
|
// Append .0 if the value has not it (skip hex and scientific floats).
|
||||||
// This suffix needed for generated C++ code.
|
// This suffix needed for generated C++ code.
|
||||||
if (IsFloat(type.base_type)) {
|
if (IsFloat(type.base_type)) {
|
||||||
@@ -1758,6 +1771,12 @@ CheckedError Parser::ParseSingleValue(const std::string *name, Value &e,
|
|||||||
TRY_ECHECK(kTokenStringOrIdent, IsBool(in_type), BASE_TYPE_BOOL);
|
TRY_ECHECK(kTokenStringOrIdent, IsBool(in_type), BASE_TYPE_BOOL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Check for optional scalars.
|
||||||
|
if (!match && IsScalar(in_type) && attribute_ == "null") {
|
||||||
|
e.constant = "null";
|
||||||
|
NEXT();
|
||||||
|
match = true;
|
||||||
|
}
|
||||||
// Check if this could be a string/identifier enum value.
|
// Check if this could be a string/identifier enum value.
|
||||||
// Enum can have only true integer base type.
|
// Enum can have only true integer base type.
|
||||||
if (!match && IsInteger(in_type) && !IsBool(in_type) &&
|
if (!match && IsInteger(in_type) && !IsBool(in_type) &&
|
||||||
@@ -1811,7 +1830,8 @@ CheckedError Parser::ParseSingleValue(const std::string *name, Value &e,
|
|||||||
// This flag forces to check default scalar values or metadata of field.
|
// This flag forces to check default scalar values or metadata of field.
|
||||||
// For JSON parser the flag should be false.
|
// For JSON parser the flag should be false.
|
||||||
// If it is set for JSON each value will be checked twice (see ParseTable).
|
// If it is set for JSON each value will be checked twice (see ParseTable).
|
||||||
if (check_now && IsScalar(match_type)) {
|
// Special case 'null' since atot can't handle that.
|
||||||
|
if (check_now && IsScalar(match_type) && e.constant != "null") {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
switch (match_type) {
|
switch (match_type) {
|
||||||
#define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, ...) \
|
#define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, ...) \
|
||||||
@@ -2236,6 +2256,11 @@ CheckedError Parser::CheckClash(std::vector<FieldDef *> &fields,
|
|||||||
return NoError();
|
return NoError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool Parser::SupportsNullableScalars() const {
|
||||||
|
return opts.lang_to_generate == 0; // No support yet.
|
||||||
|
}
|
||||||
|
|
||||||
bool Parser::SupportsAdvancedUnionFeatures() const {
|
bool Parser::SupportsAdvancedUnionFeatures() const {
|
||||||
return opts.lang_to_generate != 0 &&
|
return opts.lang_to_generate != 0 &&
|
||||||
(opts.lang_to_generate &
|
(opts.lang_to_generate &
|
||||||
|
|||||||
@@ -3416,6 +3416,81 @@ void TestEmbeddedBinarySchema() {
|
|||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NullableScalarsTest() {
|
||||||
|
// Simple schemas and a "has nullable scalar" sentinal.
|
||||||
|
std::vector<std::string> schemas;
|
||||||
|
schemas.push_back("table Monster { mana : int; }");
|
||||||
|
schemas.push_back("table Monster { mana : int = 42; }");
|
||||||
|
schemas.push_back("table Monster { mana : int = null; }");
|
||||||
|
schemas.push_back("table Monster { mana : long; }");
|
||||||
|
schemas.push_back("table Monster { mana : long = 42; }");
|
||||||
|
schemas.push_back("table Monster { mana : long = null; }");
|
||||||
|
schemas.push_back("table Monster { mana : float; }");
|
||||||
|
schemas.push_back("table Monster { mana : float = 42; }");
|
||||||
|
schemas.push_back("table Monster { mana : float = null; }");
|
||||||
|
schemas.push_back("table Monster { mana : double; }");
|
||||||
|
schemas.push_back("table Monster { mana : double = 42; }");
|
||||||
|
schemas.push_back("table Monster { mana : double = null; }");
|
||||||
|
schemas.push_back("table Monster { mana : bool; }");
|
||||||
|
schemas.push_back("table Monster { mana : bool = 42; }");
|
||||||
|
schemas.push_back("table Monster { mana : bool = null; }");
|
||||||
|
|
||||||
|
// Check the FieldDef is correctly set.
|
||||||
|
for (auto schema = schemas.begin(); schema < schemas.end(); schema++) {
|
||||||
|
const bool has_null = schema->find("null") != std::string::npos;
|
||||||
|
flatbuffers::Parser parser;
|
||||||
|
TEST_ASSERT(parser.Parse(schema->c_str()));
|
||||||
|
const auto *mana = parser.structs_.Lookup("Monster")->fields.Lookup("mana");
|
||||||
|
TEST_EQ(mana->nullable, has_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if nullable scalars are allowed for each language.
|
||||||
|
const int kNumLanguages = 17;
|
||||||
|
for (int lang=0; lang<kNumLanguages; lang++) {
|
||||||
|
flatbuffers::IDLOptions opts;
|
||||||
|
opts.lang_to_generate |= 1 << lang;
|
||||||
|
for (auto schema = schemas.begin(); schema < schemas.end(); schema++) {
|
||||||
|
const bool has_null = schema->find("null") != std::string::npos;
|
||||||
|
flatbuffers::Parser parser(opts);
|
||||||
|
// Its not supported in any language yet so has_null means error.
|
||||||
|
TEST_EQ(parser.Parse(schema->c_str()), !has_null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParseFlexbuffersFromJsonWithNullTest() {
|
||||||
|
// Test nulls are handled appropriately through flexbuffers to exercise other
|
||||||
|
// code paths of ParseSingleValue in the nullable scalars change.
|
||||||
|
// TODO(cneo): Json -> Flatbuffers test once some language can generate code
|
||||||
|
// with nullable scalars.
|
||||||
|
{
|
||||||
|
char json[] = "{\"opt_field\": 123 }";
|
||||||
|
flatbuffers::Parser parser;
|
||||||
|
flexbuffers::Builder flexbuild;
|
||||||
|
parser.ParseFlexBuffer(json, nullptr, &flexbuild);
|
||||||
|
auto root = flexbuffers::GetRoot(flexbuild.GetBuffer());
|
||||||
|
TEST_EQ(root.AsMap()["opt_field"].AsInt64(), 123);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
char json[] = "{\"opt_field\": 123.4 }";
|
||||||
|
flatbuffers::Parser parser;
|
||||||
|
flexbuffers::Builder flexbuild;
|
||||||
|
parser.ParseFlexBuffer(json, nullptr, &flexbuild);
|
||||||
|
auto root = flexbuffers::GetRoot(flexbuild.GetBuffer());
|
||||||
|
TEST_EQ(root.AsMap()["opt_field"].AsDouble(), 123.4);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
char json[] = "{\"opt_field\": null }";
|
||||||
|
flatbuffers::Parser parser;
|
||||||
|
flexbuffers::Builder flexbuild;
|
||||||
|
parser.ParseFlexBuffer(json, nullptr, &flexbuild);
|
||||||
|
auto root = flexbuffers::GetRoot(flexbuild.GetBuffer());
|
||||||
|
TEST_ASSERT(!root.AsMap().IsTheEmptyMap());
|
||||||
|
TEST_ASSERT(root.AsMap()["opt_field"].IsNull());
|
||||||
|
TEST_EQ(root.ToString(), std::string("{ opt_field: null }"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int FlatBufferTests() {
|
int FlatBufferTests() {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
|
||||||
@@ -3503,6 +3578,8 @@ int FlatBufferTests() {
|
|||||||
TestMonsterExtraFloats();
|
TestMonsterExtraFloats();
|
||||||
FixedLengthArrayTest();
|
FixedLengthArrayTest();
|
||||||
NativeTypeTest();
|
NativeTypeTest();
|
||||||
|
NullableScalarsTest();
|
||||||
|
ParseFlexbuffersFromJsonWithNullTest();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user