Skip to content

Commit

Permalink
Rework KeyPathArray filters for notifications in the C-API
Browse files Browse the repository at this point in the history
  • Loading branch information
jedelbo committed Nov 10, 2023
1 parent 7556b53 commit 046616f
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 57 deletions.
38 changes: 18 additions & 20 deletions src/realm.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ typedef void (*realm_free_userdata_func_t)(realm_userdata_t userdata);
typedef realm_userdata_t (*realm_clone_userdata_func_t)(const realm_userdata_t userdata);
typedef void (*realm_on_object_store_thread_callback_t)(realm_userdata_t userdata);
typedef bool (*realm_on_object_store_error_callback_t)(realm_userdata_t userdata, const char*);
typedef struct realm_key_path_array realm_key_path_array_t;

/* Accessor types */
typedef struct realm_object realm_object_t;
Expand Down Expand Up @@ -201,18 +202,6 @@ typedef struct realm_value {
} RLM_ANON_UNION_MEMBER(values);
realm_value_type_e type;
} realm_value_t;
typedef struct realm_key_path_elem {
realm_class_key_t object;
realm_property_key_t property;
} realm_key_path_elem_t;
typedef struct realm_key_path {
size_t nb_elements;
realm_key_path_elem_t* path_elements;
} realm_key_path_t;
typedef struct realm_key_path_array {
size_t nb_elements;
realm_key_path_t* paths;
} realm_key_path_array_t;
typedef struct realm_query_arg {
size_t nb_args;
bool is_list;
Expand Down Expand Up @@ -1606,14 +1595,24 @@ RLM_API realm_class_key_t realm_object_get_table(const realm_object_t* object);
*/
RLM_API realm_link_t realm_object_as_link(const realm_object_t* object);

/**
* Helper method for making it easier to to convert SDK input to the underlying
* `realm_key_path_array_t`.
*
* @return A pointer to the converted key path array. NULL in case of an error.
*/
RLM_API realm_key_path_array_t* realm_create_key_path_array(const realm_t* realm,
const realm_class_key_t object_class_key,
int user_key_paths_count, const char** user_key_paths);

/**
* Subscribe to notifications for this object.
*
* @return A non-null pointer if no exception occurred.
*/
RLM_API realm_notification_token_t* realm_object_add_notification_callback(realm_object_t*, realm_userdata_t userdata,
realm_free_userdata_func_t userdata_free,
realm_key_path_array_t*,
realm_key_path_array_t* key_path_array,
realm_on_object_change_func_t on_change);

/**
Expand Down Expand Up @@ -1872,7 +1871,7 @@ RLM_API bool realm_list_remove_all(realm_list_t*);
*/
RLM_API realm_notification_token_t* realm_list_add_notification_callback(realm_list_t*, realm_userdata_t userdata,
realm_free_userdata_func_t userdata_free,
realm_key_path_array_t*,
realm_key_path_array_t* key_path_array,
realm_on_collection_change_func_t on_change);

/**
Expand Down Expand Up @@ -2165,7 +2164,7 @@ RLM_API bool realm_set_remove_all(realm_set_t*);
*/
RLM_API realm_notification_token_t* realm_set_add_notification_callback(realm_set_t*, realm_userdata_t userdata,
realm_free_userdata_func_t userdata_free,
realm_key_path_array_t*,
realm_key_path_array_t* key_path_array,
realm_on_collection_change_func_t on_change);
/**
* Get an set from a thread-safe reference, potentially originating in a
Expand Down Expand Up @@ -2345,10 +2344,9 @@ RLM_API bool realm_dictionary_clear(realm_dictionary_t*);
*
* @return A non-null pointer if no exception occurred.
*/
RLM_API realm_notification_token_t*
realm_dictionary_add_notification_callback(realm_dictionary_t*, realm_userdata_t userdata,
realm_free_userdata_func_t userdata_free, realm_key_path_array_t*,
realm_on_dictionary_change_func_t on_change);
RLM_API realm_notification_token_t* realm_dictionary_add_notification_callback(
realm_dictionary_t*, realm_userdata_t userdata, realm_free_userdata_func_t userdata_free,
realm_key_path_array_t* key_path_array, realm_on_dictionary_change_func_t on_change);

/**
* Get an dictionary from a thread-safe reference, potentially originating in a
Expand Down Expand Up @@ -2696,7 +2694,7 @@ RLM_API bool realm_results_average(realm_results_t*, realm_property_key_t, realm
RLM_API realm_notification_token_t* realm_results_add_notification_callback(realm_results_t*,
realm_userdata_t userdata,
realm_free_userdata_func_t userdata_free,
realm_key_path_array_t*,
realm_key_path_array_t* key_path_array,
realm_on_collection_change_func_t);

/**
Expand Down
68 changes: 57 additions & 11 deletions src/realm/object-store/c_api/notifications.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <realm/object-store/c_api/types.hpp>
#include <realm/object-store/c_api/util.hpp>
#include <realm/object-store/keypath_helpers.hpp>

namespace realm::c_api {
namespace {
Expand Down Expand Up @@ -65,24 +66,69 @@ struct DictionaryNotificationsCallback {

std::optional<KeyPathArray> build_key_path_array(realm_key_path_array_t* key_path_array)
{
if (key_path_array) {
KeyPathArray ret;
for (size_t i = 0; i < key_path_array->nb_elements; i++) {
realm_key_path_t* key_path = key_path_array->paths + i;
ret.emplace_back();
KeyPath& kp = ret.back();
for (size_t j = 0; j < key_path->nb_elements; j++) {
realm_key_path_elem_t* path_elem = key_path->path_elements + j;
kp.emplace_back(TableKey(path_elem->object), ColKey(path_elem->property));
std::optional<KeyPathArray> ret;
if (key_path_array && key_path_array->size()) {
ret.emplace(std::move(*key_path_array));
}
return ret;
}

static KeyPathArray create_key_path_array(const ObjectSchema& object_schema, const Schema& schema,
const char** all_key_paths_begin, const char** all_key_paths_end)
{
KeyPathArray resolved_key_path_array;
for (auto it = all_key_paths_begin; it != all_key_paths_end; it++) {
KeyPath resolved_key_path;
const ObjectSchema* schema_at_index = &object_schema;
const Property* prop = nullptr;
// Split string based on '.'
std::stringstream test(*it);
std::string property;
while (std::getline(test, property, '.')) {
if (!schema_at_index) {
auto found_schema = schema.find(prop->object_type);
if (found_schema != schema.end()) {
schema_at_index = &*found_schema;
}
else {
throw InvalidArgument(
util::format("Property '%1' in KeyPath '%2' is not a collection of objects or an object "
"reference, so it cannot be used as an intermediate keypath element.",
prop->public_name, *it));
}
}
prop = schema_at_index->property_for_public_name(property);
if (prop) {
resolved_key_path.emplace_back(schema_at_index->table_key, prop->column_key);
schema_at_index = nullptr;
}
else {
throw InvalidArgument(util::format("Property '%1' in KeyPath '%2' is not a valid property in %3.",
property, *it, schema_at_index->name));
}
}
return ret;
resolved_key_path_array.push_back(std::move(resolved_key_path));
}
return std::nullopt;
return resolved_key_path_array;
}

} // namespace

RLM_API realm_key_path_array_t* realm_create_key_path_array(const realm_t* realm,
const realm_class_key_t object_class_key,
int user_key_paths_count, const char** user_key_paths)
{
return wrap_err([&]() {
KeyPathArray ret;
if (user_key_paths) {
const Schema& schema = (*realm)->schema();
const ObjectSchema& object_schema = schema_for_table(*realm, TableKey(object_class_key));
ret = create_key_path_array(object_schema, schema, user_key_paths, user_key_paths + user_key_paths_count);
}
return new realm_key_path_array_t(std::move(ret));
});
}

RLM_API realm_notification_token_t* realm_object_add_notification_callback(realm_object_t* obj,
realm_userdata_t userdata,
realm_free_userdata_func_t free,
Expand Down
7 changes: 7 additions & 0 deletions src/realm/object-store/c_api/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,13 @@ struct realm_dictionary : realm::c_api::WrapC, realm::object_store::Dictionary {
}
};

struct realm_key_path_array : realm::c_api::WrapC, realm::KeyPathArray {
explicit realm_key_path_array(realm::KeyPathArray kpa)
: realm::KeyPathArray(std::move(kpa))
{
}
};

struct realm_object_changes : realm::c_api::WrapC, realm::CollectionChangeSet {
explicit realm_object_changes(realm::CollectionChangeSet changes)
: realm::CollectionChangeSet(std::move(changes))
Expand Down
74 changes: 48 additions & 26 deletions test/object-store/c_api/c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2742,28 +2742,49 @@ TEST_CASE("C API - properties", "[c_api]") {
CHECK(checked(realm_list_insert(bars.get(), 0, bar_link_val)));
});

realm_key_path_elem_t bar_strings[] = {{class_bar.key, bar_doubles_key}};
realm_key_path_t key_path_bar_strings[] = {{1, bar_strings}};
realm_key_path_array_t key_path_array = {1, key_path_bar_strings};
auto token = cptr_checked(realm_list_add_notification_callback(bars.get(), &state, nullptr,
&key_path_array, on_change));
checked(realm_refresh(realm, nullptr));

state.called = false;
write([&]() {
checked(realm_set_value(obj2.get(), bar_doubles_key, rlm_double_val(5.0), false));
});
REQUIRE(state.called);
CHECK(!state.error);
CHECK(state.changes);

state.called = false;
write([&]() {
checked(realm_list_insert(strings.get(), 0, str1));
checked(realm_list_insert(strings.get(), 1, str2));
checked(realm_list_insert(strings.get(), 2, null));
});
REQUIRE(!state.called);
SECTION("using valid key") {
const char* bar_strings[1] = {"doubles"};
auto key_path_array = realm_create_key_path_array(realm, class_bar.key, 1, bar_strings);
REQUIRE(key_path_array);
auto token = cptr_checked(realm_list_add_notification_callback(bars.get(), &state, nullptr,
key_path_array, on_change));
realm_release(key_path_array);
checked(realm_refresh(realm, nullptr));

state.called = false;
write([&]() {
checked(realm_set_value(obj2.get(), bar_doubles_key, rlm_double_val(5.0), false));
});
REQUIRE(state.called);
CHECK(!state.error);
CHECK(state.changes);

state.called = false;
write([&]() {
checked(realm_list_insert(strings.get(), 0, str1));
checked(realm_list_insert(strings.get(), 1, str2));
checked(realm_list_insert(strings.get(), 2, null));
});
REQUIRE(!state.called);
}
SECTION("using invalid key") {
const char* bar_strings[1] = {"dobles"};
auto key_path_array = realm_create_key_path_array(realm, class_bar.key, 1, bar_strings);
REQUIRE(!key_path_array);
realm_clear_last_error();
}
SECTION("using valid nesting") {
const char* bar_strings[1] = {"sub.int"};
auto key_path_array = realm_create_key_path_array(realm, class_bar.key, 1, bar_strings);
REQUIRE(key_path_array);
realm_release(key_path_array);
}
SECTION("using invalid nesting") {
const char* bar_strings[1] = {"doubles.age"};
auto key_path_array = realm_create_key_path_array(realm, class_bar.key, 1, bar_strings);
REQUIRE(!key_path_array);
realm_clear_last_error();
}
}

SECTION("insertion, deletion, modification, modification after") {
Expand Down Expand Up @@ -3904,11 +3925,12 @@ TEST_CASE("C API - properties", "[c_api]") {
CHECK(n == 0);
}
SECTION("modifying the object while observing a specific value") {
realm_key_path_elem_t origin_value[] = {{class_foo.key, foo_int_key}};
realm_key_path_t key_path_origin_value[] = {{1, origin_value}};
realm_key_path_array_t key_path_array = {1, key_path_origin_value};
const char* foo_strings[1] = {"public_int"};
auto key_path_array = realm_create_key_path_array(realm, class_foo.key, 1, foo_strings);
REQUIRE(key_path_array);
auto token = cptr(
realm_object_add_notification_callback(obj1.get(), &state, nullptr, &key_path_array, on_change));
realm_object_add_notification_callback(obj1.get(), &state, nullptr, key_path_array, on_change));
realm_release(key_path_array);
checked(realm_refresh(realm, nullptr));
state.called = false;
write([&]() {
Expand Down

0 comments on commit 046616f

Please sign in to comment.