Skip to content

Commit

Permalink
Make Object::create accept addional properties
Browse files Browse the repository at this point in the history
  • Loading branch information
jedelbo committed Aug 9, 2024
1 parent 8fa7946 commit 7063411
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 28 deletions.
24 changes: 19 additions & 5 deletions src/realm/object-store/impl/object_accessor_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ class CppContext {
// value present. The property is identified both by the name of the
// property and its index within the ObjectScehma's persisted_properties
// array.
util::Optional<std::any> value_for_property(std::any& dict, const Property& prop,
util::Optional<std::any> value_for_property(std::any& dict, const std::string& name,
size_t /* property_index */) const
{
#if REALM_ENABLE_GEOSPATIAL
if (auto geo = std::any_cast<Geospatial>(&dict)) {
if (prop.name == Geospatial::c_geo_point_type_col_name) {
if (name == Geospatial::c_geo_point_type_col_name) {
return geo->get_type_string();
}
else if (prop.name == Geospatial::c_geo_point_coords_col_name) {
else if (name == Geospatial::c_geo_point_coords_col_name) {
std::vector<std::any> coords;
auto&& point = geo->get<GeoPoint>(); // throws
coords.push_back(point.longitude);
Expand All @@ -88,11 +88,11 @@ class CppContext {
}
return coords;
}
REALM_ASSERT_EX(false, prop.name); // unexpected property type
REALM_ASSERT_EX(false, name); // unexpected property type
}
#endif
auto const& v = util::any_cast<AnyDict&>(dict);
auto it = v.find(prop.name);
auto it = v.find(name);
return it == v.end() ? util::none : util::make_optional(it->second);
}

Expand All @@ -118,6 +118,20 @@ class CppContext {
template <typename Func>
void enumerate_dictionary(std::any& value, Func&& fn)
{
#if REALM_ENABLE_GEOSPATIAL
if (auto geo = std::any_cast<Geospatial>(&value)) {
fn(Geospatial::c_geo_point_type_col_name, std::any(geo->get_type_string()));
std::vector<std::any> coords;
auto&& point = geo->get<GeoPoint>(); // throws
coords.push_back(point.longitude);
coords.push_back(point.latitude);
if (point.has_altitude()) {
coords.push_back(*point.get_altitude());
}
fn(Geospatial::c_geo_point_coords_col_name, std::any(coords));
return;
}
#endif
for (auto&& v : util::any_cast<AnyDict&>(value))
fn(v.first, v.second);
}
Expand Down
60 changes: 37 additions & 23 deletions src/realm/object-store/object_accessor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm, Obj
// or throw an exception if updating is disabled.
if (auto primary_prop = object_schema.primary_key_property()) {
auto primary_value =
ctx.value_for_property(value, *primary_prop, primary_prop - &object_schema.persisted_properties[0]);
ctx.value_for_property(value, primary_prop->name, primary_prop - &object_schema.persisted_properties[0]);
if (!primary_value)
primary_value = ctx.default_value_for_property(object_schema, *primary_prop);
if (!primary_value && !is_nullable(primary_prop->type))
Expand Down Expand Up @@ -417,30 +417,44 @@ Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm, Obj
// that.
if (out_row && object_schema.table_type != ObjectSchema::ObjectType::TopLevelAsymmetric)
*out_row = obj;
for (size_t i = 0; i < object_schema.persisted_properties.size(); ++i) {
auto& prop = object_schema.persisted_properties[i];
// If table has primary key, it must have been set during object creation
if (prop.is_primary && skip_primary)
continue;

auto v = ctx.value_for_property(value, prop, i);
if (!created && !v)
continue;

bool is_default = false;
if (!v) {
v = ctx.default_value_for_property(object_schema, prop);
is_default = true;

std::unordered_set<StringData> props_supplied;
ctx.enumerate_dictionary(value, [&](StringData name, auto&& value) {
if (auto prop = object_schema.property_for_name(name)) {
if (!prop->is_primary || !skip_primary)
object.set_property_value_impl(ctx, *prop, value, policy, false);
props_supplied.insert(name);
}
else {
object.set_additional_property_value_impl(ctx, name, value, policy);
}
// We consider null or a missing value to be equivalent to an empty
// array/set for historical reasons; the original implementation did this
// accidentally and it's not worth changing.
if ((!v || ctx.is_null(*v)) && !is_nullable(prop.type) && !is_collection(prop.type)) {
if (prop.is_primary || !ctx.allow_missing(value))
throw MissingPropertyValueException(object_schema.name, prop.name);
});

if (created) {
// assign default values
for (size_t i = 0; i < object_schema.persisted_properties.size(); ++i) {
auto& prop = object_schema.persisted_properties[i];
// If table has primary key, it must have been set during object creation
if (prop.is_primary && skip_primary)
continue;

bool already_set = props_supplied.count(prop.name);
if (already_set)
continue;

bool is_default = true;
auto v = ctx.default_value_for_property(object_schema, prop);

// We consider null or a missing value to be equivalent to an empty
// array/set for historical reasons; the original implementation did this
// accidentally and it's not worth changing.
if ((!v || ctx.is_null(*v)) && !is_nullable(prop.type) && !is_collection(prop.type)) {
if (prop.is_primary || !ctx.allow_missing(value))
throw MissingPropertyValueException(object_schema.name, prop.name);
}
if (v)
object.set_property_value_impl(ctx, prop, *v, policy, is_default);
}
if (v)
object.set_property_value_impl(ctx, prop, *v, policy, is_default);
}
if (object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric) {
return Object{};
Expand Down
94 changes: 94 additions & 0 deletions test/object-store/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,100 @@ class CreatePolicyRecordingContext {
mutable CreatePolicy last_create_policy;
};

TEST_CASE("object with flexible schema") {
using namespace std::string_literals;
_impl::RealmCoordinator::assert_no_open_realms();

InMemoryTestFile config;
config.automatic_change_notifications = false;
config.schema_mode = SchemaMode::AdditiveExplicit;
config.flexible_schema = true;
config.schema = Schema{{
"table",
{
{"_id", PropertyType::Int, Property::IsPrimary{true}},
},
}};

config.schema_version = 0;
auto r = Realm::get_shared_realm(config);

TestContext d(r);
auto create = [&](std::any&& value, CreatePolicy policy = CreatePolicy::ForceCreate) {
r->begin_transaction();
auto obj = Object::create(d, r, *r->schema().find("table"), value, policy);
r->commit_transaction();
return obj;
};

SECTION("create object") {
auto object = create(AnyDict{
{"_id", INT64_C(1)},
{"bool", true},
{"int", INT64_C(5)},
{"float", 2.2f},
{"double", 3.3},
{"string", "hello"s},
{"date", Timestamp(10, 20)},
{"object id", ObjectId("000000000000000000000001")},
{"decimal", Decimal128("1.23e45")},
{"uuid", UUID("3b241101-abba-baba-caca-4136c566a962")},
{"mixed", "mixed"s},

{"bool array", AnyVec{true, false}},
{"int array", AnyVec{INT64_C(5), INT64_C(6)}},
{"float array", AnyVec{1.1f, 2.2f}},
{"double array", AnyVec{3.3, 4.4}},
{"string array", AnyVec{"a"s, "b"s, "c"s}},
{"date array", AnyVec{Timestamp(10, 20), Timestamp(30, 40)}},
{"object id array", AnyVec{ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA"), ObjectId("BBBBBBBBBBBBBBBBBBBBBBBB")}},
{"decimal array", AnyVec{Decimal128("1.23e45"), Decimal128("6.78e9")}},
{"uuid array", AnyVec{UUID(), UUID("3b241101-e2bb-4255-8caf-4136c566a962")}},
{"mixed array",
AnyVec{25, "b"s, 1.45, util::none, Timestamp(30, 40), Decimal128("1.23e45"),
ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA"), UUID("3b241101-e2bb-4255-8caf-4136c566a962")}},
{"dictionary", AnyDict{{"key", "value"s}}},
});

Obj obj = object.get_obj();
REQUIRE(obj.get<Int>("_id") == 1);
REQUIRE(obj.get<Bool>("bool") == true);
REQUIRE(obj.get<Int>("int") == 5);
REQUIRE(obj.get<float>("float") == 2.2f);
REQUIRE(obj.get<double>("double") == 3.3);
REQUIRE(obj.get<String>("string") == "hello");
REQUIRE(obj.get<Timestamp>("date") == Timestamp(10, 20));
REQUIRE(obj.get<ObjectId>("object id") == ObjectId("000000000000000000000001"));
REQUIRE(obj.get<Decimal128>("decimal") == Decimal128("1.23e45"));
REQUIRE(obj.get<UUID>("uuid") == UUID("3b241101-abba-baba-caca-4136c566a962"));
REQUIRE(obj.get<Mixed>("mixed") == Mixed("mixed"));

auto check_array = [&](StringData prop, auto... values) {
auto vec = get_vector({values...});
using U = typename decltype(vec)::value_type;
auto list = obj.get_list_ptr<Mixed>(prop);
size_t i = 0;
for (auto value : vec) {
CAPTURE(i);
REQUIRE(i < list->size());
REQUIRE(value == list->get(i).get<U>());
++i;
}
};
check_array("bool array", true, false);
check_array("int array", INT64_C(5), INT64_C(6));
check_array("float array", 1.1f, 2.2f);
check_array("double array", 3.3, 4.4);
check_array("string array", StringData("a"), StringData("b"), StringData("c"));
check_array("date array", Timestamp(10, 20), Timestamp(30, 40));
check_array("object id array", ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA"), ObjectId("BBBBBBBBBBBBBBBBBBBBBBBB"));
check_array("decimal array", Decimal128("1.23e45"), Decimal128("6.78e9"));
check_array("uuid array", UUID(), UUID("3b241101-e2bb-4255-8caf-4136c566a962"));

REQUIRE(obj.get_dictionary_ptr("dictionary")->get("key") == Mixed("value"));
}
}

TEST_CASE("object") {
using namespace std::string_literals;
_impl::RealmCoordinator::assert_no_open_realms();
Expand Down

0 comments on commit 7063411

Please sign in to comment.