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/CMakeLists.txt b/src/realm/CMakeLists.txt index 7141f6ce61c..77b5e5c6a46 100644 --- a/src/realm/CMakeLists.txt +++ b/src/realm/CMakeLists.txt @@ -232,7 +232,6 @@ set(REALM_INSTALL_HEADERS util/memory_stream.hpp util/misc_errors.hpp util/misc_ext_errors.hpp - util/miscellaneous.hpp util/optional.hpp util/overload.hpp util/platform_info.hpp diff --git a/src/realm/alloc_slab.cpp b/src/realm/alloc_slab.cpp index 7aef66d5a26..cede4a52b82 100644 --- a/src/realm/alloc_slab.cpp +++ b/src/realm/alloc_slab.cpp @@ -37,7 +37,6 @@ #include #include -#include #include #include #include diff --git a/src/realm/group.cpp b/src/realm/group.cpp index c792021430f..d3d3f6f6a5a 100644 --- a/src/realm/group.cpp +++ b/src/realm/group.cpp @@ -27,7 +27,6 @@ #include #include -#include #include #include #include diff --git a/src/realm/group_writer.cpp b/src/realm/group_writer.cpp index 41225a60753..9243d23e0b6 100644 --- a/src/realm/group_writer.cpp +++ b/src/realm/group_writer.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include using namespace realm; diff --git a/src/realm/object-store/c_api/notifications.cpp b/src/realm/object-store/c_api/notifications.cpp index 55ca16b27db..057714267c5 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,71 @@ 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 '.' + const char* path = *it; + do { + auto p = find_chr(path, '.'); + StringData property(path, p - path); + path = p; + 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)); + } } - } - return ret; + 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)); + } + } while (*path++ == '.'); + 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/src/realm/query_engine.hpp b/src/realm/query_engine.hpp index c912fadff60..cea9ae49db0 100644 --- a/src/realm/query_engine.hpp +++ b/src/realm/query_engine.hpp @@ -88,7 +88,6 @@ TConditionValue: Type of values in condition column. That is, int64_t, float, #include #include #include -#include #include #include #include diff --git a/src/realm/table.cpp b/src/realm/table.cpp index c22c1581f9c..62b179a92db 100644 --- a/src/realm/table.cpp +++ b/src/realm/table.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include #include diff --git a/src/realm/util/miscellaneous.hpp b/src/realm/util/miscellaneous.hpp deleted file mode 100644 index c45e4f3999a..00000000000 --- a/src/realm/util/miscellaneous.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/************************************************************************* - * - * Copyright 2016 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - **************************************************************************/ - -#ifndef REALM_UTIL_MISCELLANEOUS_HPP -#define REALM_UTIL_MISCELLANEOUS_HPP - -#include - -namespace realm { -namespace util { - -// FIXME: Replace this with std::add_const_t when we switch over to C++14 by -// default. -/// \brief Adds const qualifier, unless T already has the const qualifier -template -using add_const_t = typename std::add_const::type; - -// FIXME: Replace this with std::as_const when we switch over to C++17 by -// default. -/// \brief Forms an lvalue reference to const T -template -constexpr add_const_t& as_const(T& v) noexcept -{ - return v; -} - -/// \brief Disallows rvalue arguments -template -add_const_t& as_const(const T&&) = delete; - -} // namespace util -} // namespace realm - -#endif // REALM_UTIL_MISCELLANEOUS_HPP diff --git a/src/realm/utilities.hpp b/src/realm/utilities.hpp index e699cfc1ff8..90a51031eb2 100644 --- a/src/realm/utilities.hpp +++ b/src/realm/utilities.hpp @@ -361,6 +361,14 @@ constexpr inline size_t round_down(size_t p, size_t align) return r & (~(align - 1)); } +// return pointer to found character or to terminating NUL +static inline const char* find_chr(const char* p, char c) +{ + while (*p && *p != c) { + ++p; + } + return p; +} #ifdef _WIN32 typedef HANDLE FileDesc; 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([&]() {