From 9ecd2e16c2adb6fe9e3cf2fa8f4e1e310d48a1c2 Mon Sep 17 00:00:00 2001 From: Casper Date: Thu, 16 Jul 2020 13:43:47 -0700 Subject: [PATCH] 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 --- include/flatbuffers/idl.h | 4 +++ src/idl_parser.cpp | 29 ++++++++++++++++-- tests/test.cpp | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 2 deletions(-) diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index 8c02f28..70a2b78 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -296,6 +296,7 @@ struct FieldDef : public Definition { shared(false), native_inline(false), flexbuffer(false), + nullable(false), nested_flatbuffer(NULL), padding(0) {} @@ -314,6 +315,8 @@ struct FieldDef : public Definition { bool native_inline; // Field will be defined inline (instead of as a pointer) // for native tables if field is a struct. 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. size_t padding; // Bytes to always pad after this field. }; @@ -927,6 +930,7 @@ class Parser : public ParserState { bool SupportsAdvancedUnionFeatures() const; bool SupportsAdvancedArrayFeatures() const; + bool SupportsNullableScalars() const; Namespace *UniqueNamespace(Namespace *ns); FLATBUFFERS_CHECKED_ERROR RecurseError(); diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index 6faca7a..5ccd690 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -721,7 +721,7 @@ CheckedError Parser::ParseField(StructDef &struct_def) { // Only cpp, js and ts supports the union vector feature so far. if (!SupportsAdvancedUnionFeatures()) { 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."); } // 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( "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). // This suffix needed for generated C++ code. 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); } } + // 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. // Enum can have only true integer base 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. // For JSON parser the flag should be false. // 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 switch (match_type) { #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, ...) \ @@ -2236,6 +2256,11 @@ CheckedError Parser::CheckClash(std::vector &fields, return NoError(); } + +bool Parser::SupportsNullableScalars() const { + return opts.lang_to_generate == 0; // No support yet. +} + bool Parser::SupportsAdvancedUnionFeatures() const { return opts.lang_to_generate != 0 && (opts.lang_to_generate & diff --git a/tests/test.cpp b/tests/test.cpp index b7ae767..de86f02 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -3416,6 +3416,81 @@ void TestEmbeddedBinarySchema() { 0); } +void NullableScalarsTest() { + // Simple schemas and a "has nullable scalar" sentinal. + std::vector 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; langfind("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() { // clang-format off @@ -3503,6 +3578,8 @@ int FlatBufferTests() { TestMonsterExtraFloats(); FixedLengthArrayTest(); NativeTypeTest(); + NullableScalarsTest(); + ParseFlexbuffersFromJsonWithNullTest(); return 0; } -- 2.7.4