From 046616ff94de9df77e1ca9b122a247ca88949120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Edelbo?= Date: Fri, 10 Nov 2023 10:17:34 +0100 Subject: [PATCH] Rework KeyPathArray filters for notifications in the C-API --- src/realm.h | 38 +++++----- .../object-store/c_api/notifications.cpp | 68 ++++++++++++++--- src/realm/object-store/c_api/types.hpp | 7 ++ test/object-store/c_api/c_api.cpp | 74 ++++++++++++------- 4 files changed, 130 insertions(+), 57 deletions(-) diff --git a/src/realm.h b/src/realm.h index 47721658038..46dd7f3a0fa 100644 --- a/src/realm.h +++ b/src/realm.h @@ -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; @@ -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; @@ -1606,6 +1595,16 @@ 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. * @@ -1613,7 +1612,7 @@ RLM_API realm_link_t realm_object_as_link(const realm_object_t* object); */ 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); /** @@ -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); /** @@ -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 @@ -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 @@ -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); /** diff --git a/src/realm/object-store/c_api/notifications.cpp b/src/realm/object-store/c_api/notifications.cpp index 55ca16b27db..236a102b375 100644 --- a/src/realm/object-store/c_api/notifications.cpp +++ b/src/realm/object-store/c_api/notifications.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace realm::c_api { namespace { @@ -65,24 +66,69 @@ struct DictionaryNotificationsCallback { std::optional 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 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, diff --git a/src/realm/object-store/c_api/types.hpp b/src/realm/object-store/c_api/types.hpp index f9f5ddce403..b3a521e55c0 100644 --- a/src/realm/object-store/c_api/types.hpp +++ b/src/realm/object-store/c_api/types.hpp @@ -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)) diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index 5ccb72beb4c..8c9937ed5f5 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -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") { @@ -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([&]() {