Skip to content

Commit ee06b38

Browse files
committed
Make Object::create accept addional properties
1 parent 4ad1fa5 commit ee06b38

File tree

3 files changed

+136
-28
lines changed

3 files changed

+136
-28
lines changed

src/realm/object-store/impl/object_accessor_impl.hpp

+5-5
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,15 @@ class CppContext {
7070
// value present. The property is identified both by the name of the
7171
// property and its index within the ObjectScehma's persisted_properties
7272
// array.
73-
util::Optional<std::any> value_for_property(std::any& dict, const Property& prop,
73+
util::Optional<std::any> value_for_property(std::any& dict, const std::string& name,
7474
size_t /* property_index */) const
7575
{
7676
#if REALM_ENABLE_GEOSPATIAL
7777
if (auto geo = std::any_cast<Geospatial>(&dict)) {
78-
if (prop.name == Geospatial::c_geo_point_type_col_name) {
78+
if (name == Geospatial::c_geo_point_type_col_name) {
7979
return geo->get_type_string();
8080
}
81-
else if (prop.name == Geospatial::c_geo_point_coords_col_name) {
81+
else if (name == Geospatial::c_geo_point_coords_col_name) {
8282
std::vector<std::any> coords;
8383
auto&& point = geo->get<GeoPoint>(); // throws
8484
coords.push_back(point.longitude);
@@ -88,11 +88,11 @@ class CppContext {
8888
}
8989
return coords;
9090
}
91-
REALM_ASSERT_EX(false, prop.name); // unexpected property type
91+
REALM_ASSERT_EX(false, name); // unexpected property type
9292
}
9393
#endif
9494
auto const& v = util::any_cast<AnyDict&>(dict);
95-
auto it = v.find(prop.name);
95+
auto it = v.find(name);
9696
return it == v.end() ? util::none : util::make_optional(it->second);
9797
}
9898

src/realm/object-store/object_accessor.hpp

+37-23
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm, Obj
364364
// or throw an exception if updating is disabled.
365365
if (auto primary_prop = object_schema.primary_key_property()) {
366366
auto primary_value =
367-
ctx.value_for_property(value, *primary_prop, primary_prop - &object_schema.persisted_properties[0]);
367+
ctx.value_for_property(value, primary_prop->name, primary_prop - &object_schema.persisted_properties[0]);
368368
if (!primary_value)
369369
primary_value = ctx.default_value_for_property(object_schema, *primary_prop);
370370
if (!primary_value && !is_nullable(primary_prop->type))
@@ -417,30 +417,44 @@ Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm, Obj
417417
// that.
418418
if (out_row && object_schema.table_type != ObjectSchema::ObjectType::TopLevelAsymmetric)
419419
*out_row = obj;
420-
for (size_t i = 0; i < object_schema.persisted_properties.size(); ++i) {
421-
auto& prop = object_schema.persisted_properties[i];
422-
// If table has primary key, it must have been set during object creation
423-
if (prop.is_primary && skip_primary)
424-
continue;
425-
426-
auto v = ctx.value_for_property(value, prop, i);
427-
if (!created && !v)
428-
continue;
429-
430-
bool is_default = false;
431-
if (!v) {
432-
v = ctx.default_value_for_property(object_schema, prop);
433-
is_default = true;
420+
421+
std::unordered_set<StringData> props_supplied;
422+
ctx.enumerate_dictionary(value, [&](StringData name, auto&& value) {
423+
if (auto prop = object_schema.property_for_name(name)) {
424+
if (!prop->is_primary || !skip_primary)
425+
object.set_property_value_impl(ctx, *prop, value, policy, false);
426+
props_supplied.insert(name);
427+
}
428+
else {
429+
object.set_additional_property_value_impl(ctx, name, value, policy);
434430
}
435-
// We consider null or a missing value to be equivalent to an empty
436-
// array/set for historical reasons; the original implementation did this
437-
// accidentally and it's not worth changing.
438-
if ((!v || ctx.is_null(*v)) && !is_nullable(prop.type) && !is_collection(prop.type)) {
439-
if (prop.is_primary || !ctx.allow_missing(value))
440-
throw MissingPropertyValueException(object_schema.name, prop.name);
431+
});
432+
433+
if (created) {
434+
// assign default values
435+
for (size_t i = 0; i < object_schema.persisted_properties.size(); ++i) {
436+
auto& prop = object_schema.persisted_properties[i];
437+
// If table has primary key, it must have been set during object creation
438+
if (prop.is_primary && skip_primary)
439+
continue;
440+
441+
bool already_set = props_supplied.count(prop.name);
442+
if (already_set)
443+
continue;
444+
445+
bool is_default = true;
446+
auto v = ctx.default_value_for_property(object_schema, prop);
447+
448+
// We consider null or a missing value to be equivalent to an empty
449+
// array/set for historical reasons; the original implementation did this
450+
// accidentally and it's not worth changing.
451+
if ((!v || ctx.is_null(*v)) && !is_nullable(prop.type) && !is_collection(prop.type)) {
452+
if (prop.is_primary || !ctx.allow_missing(value))
453+
throw MissingPropertyValueException(object_schema.name, prop.name);
454+
}
455+
if (v)
456+
object.set_property_value_impl(ctx, prop, *v, policy, is_default);
441457
}
442-
if (v)
443-
object.set_property_value_impl(ctx, prop, *v, policy, is_default);
444458
}
445459
if (object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric) {
446460
return Object{};

test/object-store/object.cpp

+94
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,100 @@ class CreatePolicyRecordingContext {
155155
mutable CreatePolicy last_create_policy;
156156
};
157157

158+
TEST_CASE("object with flexible schema") {
159+
using namespace std::string_literals;
160+
_impl::RealmCoordinator::assert_no_open_realms();
161+
162+
InMemoryTestFile config;
163+
config.automatic_change_notifications = false;
164+
config.schema_mode = SchemaMode::AdditiveExplicit;
165+
config.flexible_schema = true;
166+
config.schema = Schema{{
167+
"table",
168+
{
169+
{"_id", PropertyType::Int, Property::IsPrimary{true}},
170+
},
171+
}};
172+
173+
config.schema_version = 0;
174+
auto r = Realm::get_shared_realm(config);
175+
176+
TestContext d(r);
177+
auto create = [&](std::any&& value, CreatePolicy policy = CreatePolicy::ForceCreate) {
178+
r->begin_transaction();
179+
auto obj = Object::create(d, r, *r->schema().find("table"), value, policy);
180+
r->commit_transaction();
181+
return obj;
182+
};
183+
184+
SECTION("create object") {
185+
auto object = create(AnyDict{
186+
{"_id", INT64_C(1)},
187+
{"bool", true},
188+
{"int", INT64_C(5)},
189+
{"float", 2.2f},
190+
{"double", 3.3},
191+
{"string", "hello"s},
192+
{"date", Timestamp(10, 20)},
193+
{"object id", ObjectId("000000000000000000000001")},
194+
{"decimal", Decimal128("1.23e45")},
195+
{"uuid", UUID("3b241101-abba-baba-caca-4136c566a962")},
196+
{"mixed", "mixed"s},
197+
198+
{"bool array", AnyVec{true, false}},
199+
{"int array", AnyVec{INT64_C(5), INT64_C(6)}},
200+
{"float array", AnyVec{1.1f, 2.2f}},
201+
{"double array", AnyVec{3.3, 4.4}},
202+
{"string array", AnyVec{"a"s, "b"s, "c"s}},
203+
{"date array", AnyVec{Timestamp(10, 20), Timestamp(30, 40)}},
204+
{"object id array", AnyVec{ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA"), ObjectId("BBBBBBBBBBBBBBBBBBBBBBBB")}},
205+
{"decimal array", AnyVec{Decimal128("1.23e45"), Decimal128("6.78e9")}},
206+
{"uuid array", AnyVec{UUID(), UUID("3b241101-e2bb-4255-8caf-4136c566a962")}},
207+
{"mixed array",
208+
AnyVec{25, "b"s, 1.45, util::none, Timestamp(30, 40), Decimal128("1.23e45"),
209+
ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA"), UUID("3b241101-e2bb-4255-8caf-4136c566a962")}},
210+
{"dictionary", AnyDict{{"key", "value"s}}},
211+
});
212+
213+
Obj obj = object.get_obj();
214+
REQUIRE(obj.get<Int>("_id") == 1);
215+
REQUIRE(obj.get<Bool>("bool") == true);
216+
REQUIRE(obj.get<Int>("int") == 5);
217+
REQUIRE(obj.get<float>("float") == 2.2f);
218+
REQUIRE(obj.get<double>("double") == 3.3);
219+
REQUIRE(obj.get<String>("string") == "hello");
220+
REQUIRE(obj.get<Timestamp>("date") == Timestamp(10, 20));
221+
REQUIRE(obj.get<ObjectId>("object id") == ObjectId("000000000000000000000001"));
222+
REQUIRE(obj.get<Decimal128>("decimal") == Decimal128("1.23e45"));
223+
REQUIRE(obj.get<UUID>("uuid") == UUID("3b241101-abba-baba-caca-4136c566a962"));
224+
REQUIRE(obj.get<Mixed>("mixed") == Mixed("mixed"));
225+
226+
auto check_array = [&](StringData prop, auto... values) {
227+
auto vec = get_vector({values...});
228+
using U = typename decltype(vec)::value_type;
229+
auto list = obj.get_list_ptr<Mixed>(prop);
230+
size_t i = 0;
231+
for (auto value : vec) {
232+
CAPTURE(i);
233+
REQUIRE(i < list->size());
234+
REQUIRE(value == list->get(i).get<U>());
235+
++i;
236+
}
237+
};
238+
check_array("bool array", true, false);
239+
check_array("int array", INT64_C(5), INT64_C(6));
240+
check_array("float array", 1.1f, 2.2f);
241+
check_array("double array", 3.3, 4.4);
242+
check_array("string array", StringData("a"), StringData("b"), StringData("c"));
243+
check_array("date array", Timestamp(10, 20), Timestamp(30, 40));
244+
check_array("object id array", ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA"), ObjectId("BBBBBBBBBBBBBBBBBBBBBBBB"));
245+
check_array("decimal array", Decimal128("1.23e45"), Decimal128("6.78e9"));
246+
check_array("uuid array", UUID(), UUID("3b241101-e2bb-4255-8caf-4136c566a962"));
247+
248+
REQUIRE(obj.get_dictionary_ptr("dictionary")->get("key") == Mixed("value"));
249+
}
250+
}
251+
158252
TEST_CASE("object") {
159253
using namespace std::string_literals;
160254
_impl::RealmCoordinator::assert_no_open_realms();

0 commit comments

Comments
 (0)