• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / 1859

23 Nov 2023 09:42AM UTC coverage: 91.66% (-0.01%) from 91.671%
1859

push

Evergreen

web-flow
Rework KeyPathArray filters for notifications in the C-API. (#7087)

* Add support for keypaths crossing backlins

---------

Co-authored-by: Jørgen Edelbo <jorgen.edelbo@mongodb.com>

92322 of 169178 branches covered (0.0%)

151 of 155 new or added lines in 4 files covered. (97.42%)

77 existing lines in 13 files now uncovered.

231381 of 252435 relevant lines covered (91.66%)

6404136.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.62
/src/realm/object-store/c_api/notifications.cpp
1
#include <realm/object-store/c_api/types.hpp>
2
#include <realm/object-store/c_api/util.hpp>
3
#include <realm/object-store/keypath_helpers.hpp>
4

5
namespace realm::c_api {
6
namespace {
7
struct ObjectNotificationsCallback {
8
    UserdataPtr m_userdata;
9
    realm_on_object_change_func_t m_on_change = nullptr;
10

11
    ObjectNotificationsCallback() = default;
6✔
12
    ObjectNotificationsCallback(ObjectNotificationsCallback&& other)
13
        : m_userdata(std::exchange(other.m_userdata, nullptr))
14
        , m_on_change(std::exchange(other.m_on_change, nullptr))
15
    {
24✔
16
    }
24✔
17

18
    void operator()(const CollectionChangeSet& changes)
19
    {
12✔
20
        if (m_on_change) {
12✔
21
            realm_object_changes_t c{changes};
12✔
22
            m_on_change(m_userdata.get(), &c);
12✔
23
        }
12✔
24
    }
12✔
25
};
26

27
struct CollectionNotificationsCallback {
28
    UserdataPtr m_userdata;
29
    realm_on_collection_change_func_t m_on_change = nullptr;
30

31
    CollectionNotificationsCallback() = default;
18✔
32
    CollectionNotificationsCallback(CollectionNotificationsCallback&& other)
33
        : m_userdata(std::exchange(other.m_userdata, nullptr))
34
        , m_on_change(std::exchange(other.m_on_change, nullptr))
35
    {
72✔
36
    }
72✔
37

38
    void operator()(const CollectionChangeSet& changes)
39
    {
32✔
40
        if (m_on_change) {
32✔
41
            realm_collection_changes_t c{changes};
32✔
42
            m_on_change(m_userdata.get(), &c);
32✔
43
        }
32✔
44
    }
32✔
45
};
46

47
struct DictionaryNotificationsCallback {
48
    UserdataPtr m_userdata;
49
    realm_on_dictionary_change_func_t m_on_change = nullptr;
50

51
    DictionaryNotificationsCallback() = default;
4✔
52
    DictionaryNotificationsCallback(DictionaryNotificationsCallback&& other)
53
        : m_userdata(std::exchange(other.m_userdata, nullptr))
54
        , m_on_change(std::exchange(other.m_on_change, nullptr))
55
    {
4✔
56
    }
4✔
57

58
    void operator()(const DictionaryChangeSet& changes)
59
    {
4✔
60
        if (m_on_change) {
4✔
61
            realm_dictionary_changes_t c{changes};
4✔
62
            m_on_change(m_userdata.get(), &c);
4✔
63
        }
4✔
64
    }
4✔
65
};
66

67
std::optional<KeyPathArray> build_key_path_array(realm_key_path_array_t* key_path_array)
68
{
28✔
69
    std::optional<KeyPathArray> ret;
28✔
70
    if (key_path_array && key_path_array->size()) {
28✔
71
        ret.emplace(std::move(*key_path_array));
8✔
72
    }
8✔
73
    return ret;
28✔
74
}
28✔
75

76
static KeyPathArray create_key_path_array(Group& g, const ObjectSchema& object_schema, const Schema& schema,
77
                                          const char** all_key_paths_begin, const char** all_key_paths_end)
78
{
12✔
79
    KeyPathArray resolved_key_path_array;
12✔
80
    for (auto it = all_key_paths_begin; it != all_key_paths_end; it++) {
20✔
81
        KeyPath resolved_key_path;
12✔
82
        const ObjectSchema* schema_at_index = &object_schema;
12✔
83
        const Property* prop = nullptr;
12✔
84
        // Split string based on '.'
6✔
85
        const char* path = *it;
12✔
86
        do {
18✔
87
            auto p = find_chr(path, '.');
18✔
88
            StringData property(path, p - path);
18✔
89
            path = p;
18✔
90
            if (!schema_at_index) {
18✔
NEW
91
                throw InvalidArgument(
×
NEW
92
                    util::format("Property '%1' in KeyPath '%2' is not a collection of objects or an object "
×
NEW
93
                                 "reference, so it cannot be used as an intermediate keypath element.",
×
NEW
94
                                 prop->public_name, *it));
×
UNCOV
95
            }
×
96
            prop = schema_at_index->property_for_public_name(property);
18✔
97
            if (prop) {
18✔
98
                ColKey col_key = prop->column_key;
14✔
99
                TableKey table_key = schema_at_index->table_key;
14✔
100
                if (prop->type == PropertyType::Object || prop->type == PropertyType::LinkingObjects) {
14✔
101
                    auto found_schema = schema.find(prop->object_type);
4✔
102
                    if (found_schema != schema.end()) {
4✔
103
                        schema_at_index = &*found_schema;
4✔
104
                        if (prop->type == PropertyType::LinkingObjects) {
4✔
105
                            // Find backlink column key
1✔
106
                            auto origin_prop = schema_at_index->property_for_name(prop->link_origin_property_name);
2✔
107
                            auto origin_table = ObjectStore::table_for_object_type(g, schema_at_index->name);
2✔
108
                            col_key = origin_table->get_opposite_column(origin_prop->column_key);
2✔
109
                        }
2✔
110
                    }
4✔
111
                }
4✔
112
                resolved_key_path.emplace_back(table_key, col_key);
14✔
113
            }
14✔
114
            else {
4✔
115
                throw InvalidArgument(util::format("Property '%1' in KeyPath '%2' is not a valid property in %3.",
4✔
116
                                                   property, *it, schema_at_index->name));
4✔
117
            }
4✔
118
        } while (*path++ == '.');
14✔
119
        resolved_key_path_array.push_back(std::move(resolved_key_path));
10✔
120
    }
8✔
121
    return resolved_key_path_array;
10✔
122
}
12✔
123

124
} // namespace
125

126
RLM_API realm_key_path_array_t* realm_create_key_path_array(const realm_t* realm,
127
                                                            const realm_class_key_t object_class_key,
128
                                                            size_t num_key_paths, const char** user_key_paths)
129
{
12✔
130
    return wrap_err([&]() {
12✔
131
        KeyPathArray ret;
12✔
132
        if (user_key_paths) {
12✔
133
            const Schema& schema = (*realm)->schema();
12✔
134
            const ObjectSchema& object_schema = schema_for_table(*realm, TableKey(object_class_key));
12✔
135
            ret = create_key_path_array((*realm)->read_group(), object_schema, schema, user_key_paths,
12✔
136
                                        user_key_paths + num_key_paths);
12✔
137
        }
12✔
138
        return new realm_key_path_array_t(std::move(ret));
12✔
139
    });
12✔
140
}
12✔
141

142
RLM_API realm_notification_token_t* realm_object_add_notification_callback(realm_object_t* obj,
143
                                                                           realm_userdata_t userdata,
144
                                                                           realm_free_userdata_func_t free,
145
                                                                           realm_key_path_array_t* key_path_array,
146
                                                                           realm_on_object_change_func_t on_change)
147
{
6✔
148
    return wrap_err([&]() {
6✔
149
        ObjectNotificationsCallback cb;
6✔
150
        cb.m_userdata = UserdataPtr{userdata, free};
6✔
151
        cb.m_on_change = on_change;
6✔
152
        auto token = obj->add_notification_callback(std::move(cb), build_key_path_array(key_path_array));
6✔
153
        return new realm_notification_token_t{std::move(token)};
6✔
154
    });
6✔
155
}
6✔
156

157
RLM_API bool realm_object_changes_is_deleted(const realm_object_changes_t* changes)
158
{
4✔
159
    return !changes->deletions.empty();
4✔
160
}
4✔
161

162
RLM_API size_t realm_object_changes_get_num_modified_properties(const realm_object_changes_t* changes)
163
{
2✔
164
    return changes->columns.size();
2✔
165
}
2✔
166

167
RLM_API size_t realm_object_changes_get_modified_properties(const realm_object_changes_t* changes,
168
                                                            realm_property_key_t* out_properties, size_t max)
169
{
8✔
170
    if (!out_properties)
8✔
171
        return changes->columns.size();
2✔
172

3✔
173
    size_t i = 0;
6✔
174
    for (const auto& [col_key_val, index_set] : changes->columns) {
8✔
175
        if (i >= max) {
8✔
176
            break;
2✔
177
        }
2✔
178
        out_properties[i] = col_key_val;
6✔
179
        ++i;
6✔
180
    }
6✔
181
    return i;
6✔
182
}
6✔
183

184
RLM_API realm_notification_token_t* realm_list_add_notification_callback(realm_list_t* list,
185
                                                                         realm_userdata_t userdata,
186
                                                                         realm_free_userdata_func_t free,
187
                                                                         realm_key_path_array_t* key_path_array,
188
                                                                         realm_on_collection_change_func_t on_change)
189
{
12✔
190
    return wrap_err([&]() {
12✔
191
        CollectionNotificationsCallback cb;
12✔
192
        cb.m_userdata = UserdataPtr{userdata, free};
12✔
193
        cb.m_on_change = on_change;
12✔
194
        auto token = list->add_notification_callback(std::move(cb), build_key_path_array(key_path_array));
12✔
195
        return new realm_notification_token_t{std::move(token)};
12✔
196
    });
12✔
197
}
12✔
198

199
RLM_API realm_notification_token_t* realm_set_add_notification_callback(realm_set_t* set, realm_userdata_t userdata,
200
                                                                        realm_free_userdata_func_t free,
201
                                                                        realm_key_path_array_t* key_path_array,
202
                                                                        realm_on_collection_change_func_t on_change)
203
{
4✔
204
    return wrap_err([&]() {
4✔
205
        CollectionNotificationsCallback cb;
4✔
206
        cb.m_userdata = UserdataPtr{userdata, free};
4✔
207
        cb.m_on_change = on_change;
4✔
208
        auto token = set->add_notification_callback(std::move(cb), build_key_path_array(key_path_array));
4✔
209
        return new realm_notification_token_t{std::move(token)};
4✔
210
    });
4✔
211
}
4✔
212

213
RLM_API realm_notification_token_t*
214
realm_dictionary_add_notification_callback(realm_dictionary_t* dict, realm_userdata_t userdata,
215
                                           realm_free_userdata_func_t free, realm_key_path_array_t* key_path_array,
216
                                           realm_on_dictionary_change_func_t on_change)
217
{
4✔
218
    return wrap_err([&]() {
4✔
219
        DictionaryNotificationsCallback cb;
4✔
220
        cb.m_userdata = UserdataPtr{userdata, free};
4✔
221
        cb.m_on_change = on_change;
4✔
222
        auto token = dict->add_key_based_notification_callback(std::move(cb), build_key_path_array(key_path_array));
4✔
223
        return new realm_notification_token_t{std::move(token)};
4✔
224
    });
4✔
225
}
4✔
226

227
RLM_API realm_notification_token_t*
228
realm_results_add_notification_callback(realm_results_t* results, realm_userdata_t userdata,
229
                                        realm_free_userdata_func_t free, realm_key_path_array_t* key_path_array,
230
                                        realm_on_collection_change_func_t on_change)
231
{
2✔
232
    return wrap_err([&]() {
2✔
233
        CollectionNotificationsCallback cb;
2✔
234
        cb.m_userdata = UserdataPtr{userdata, free};
2✔
235
        cb.m_on_change = on_change;
2✔
236
        auto token = results->add_notification_callback(std::move(cb), build_key_path_array(key_path_array));
2✔
237
        return new realm_notification_token_t{std::move(token)};
2✔
238
    });
2✔
239
}
2✔
240

241
RLM_API void realm_collection_changes_get_num_ranges(const realm_collection_changes_t* changes,
242
                                                     size_t* out_num_deletion_ranges,
243
                                                     size_t* out_num_insertion_ranges,
244
                                                     size_t* out_num_modification_ranges, size_t* out_num_moves)
245
{
6✔
246
    // FIXME: `std::distance()` has O(n) performance here, which seems ridiculous.
3✔
247

3✔
248
    if (out_num_deletion_ranges)
6✔
249
        *out_num_deletion_ranges = std::distance(changes->deletions.begin(), changes->deletions.end());
6✔
250
    if (out_num_insertion_ranges)
6✔
251
        *out_num_insertion_ranges = std::distance(changes->insertions.begin(), changes->insertions.end());
6✔
252
    if (out_num_modification_ranges)
6✔
253
        *out_num_modification_ranges = std::distance(changes->modifications.begin(), changes->modifications.end());
6✔
254
    if (out_num_moves)
6✔
255
        *out_num_moves = changes->moves.size();
6✔
256
}
6✔
257

258
RLM_API void realm_collection_changes_get_num_changes(const realm_collection_changes_t* changes,
259
                                                      size_t* out_num_deletions, size_t* out_num_insertions,
260
                                                      size_t* out_num_modifications, size_t* out_num_moves,
261
                                                      bool* out_collection_was_cleared)
262
{
6✔
263
    // FIXME: This has O(n) performance, which seems ridiculous.
3✔
264

3✔
265
    if (out_num_deletions)
6✔
266
        *out_num_deletions = changes->deletions.count();
6✔
267
    if (out_num_insertions)
6✔
268
        *out_num_insertions = changes->insertions.count();
6✔
269
    if (out_num_modifications)
6✔
270
        *out_num_modifications = changes->modifications.count();
6✔
271
    if (out_num_moves)
6✔
272
        *out_num_moves = changes->moves.size();
6✔
273
    if (out_collection_was_cleared)
6✔
274
        *out_collection_was_cleared = changes->collection_was_cleared;
6✔
275
}
6✔
276

277
static inline void copy_index_ranges(const IndexSet& index_set, realm_index_range_t* out_ranges, size_t max)
278
{
14✔
279
    size_t i = 0;
14✔
280
    for (auto [from, to] : index_set) {
14✔
281
        if (i >= max)
14✔
282
            return;
×
283
        out_ranges[i++] = realm_index_range_t{from, to};
14✔
284
    }
14✔
285
}
14✔
286

287
RLM_API void realm_collection_changes_get_ranges(
288
    const realm_collection_changes_t* changes, realm_index_range_t* out_deletion_ranges, size_t max_deletion_ranges,
289
    realm_index_range_t* out_insertion_ranges, size_t max_insertion_ranges,
290
    realm_index_range_t* out_modification_ranges, size_t max_modification_ranges,
291
    realm_index_range_t* out_modification_ranges_after, size_t max_modification_ranges_after,
292
    realm_collection_move_t* out_moves, size_t max_moves)
293
{
6✔
294
    if (out_deletion_ranges) {
6✔
295
        copy_index_ranges(changes->deletions, out_deletion_ranges, max_deletion_ranges);
4✔
296
    }
4✔
297
    if (out_insertion_ranges) {
6✔
298
        copy_index_ranges(changes->insertions, out_insertion_ranges, max_insertion_ranges);
6✔
299
    }
6✔
300
    if (out_modification_ranges) {
6✔
301
        copy_index_ranges(changes->modifications, out_modification_ranges, max_modification_ranges);
2✔
302
    }
2✔
303
    if (out_modification_ranges_after) {
6✔
304
        copy_index_ranges(changes->modifications_new, out_modification_ranges_after, max_modification_ranges_after);
2✔
305
    }
2✔
306
    if (out_moves) {
6✔
307
        size_t i = 0;
2✔
308
        for (auto [from, to] : changes->moves) {
1✔
309
            if (i >= max_moves)
×
310
                break;
×
311
            out_moves[i] = realm_collection_move_t{from, to};
×
312
            ++i;
×
313
        }
×
314
    }
2✔
315
}
6✔
316

317
RLM_API void realm_dictionary_get_changes(const realm_dictionary_changes_t* changes, size_t* out_deletions_size,
318
                                          size_t* out_insertion_size, size_t* out_modification_size)
319
{
2✔
320
    if (out_deletions_size)
2✔
321
        *out_deletions_size = changes->deletions.size();
2✔
322
    if (out_insertion_size)
2✔
323
        *out_insertion_size = changes->insertions.size();
2✔
324
    if (out_modification_size)
2✔
325
        *out_modification_size = changes->modifications.size();
2✔
326
}
2✔
327

328
RLM_API void realm_dictionary_get_changed_keys(const realm_dictionary_changes_t* changes,
329
                                               realm_value_t* deletion_keys, size_t* deletions_size,
330
                                               realm_value_t* insertion_keys, size_t* insertions_size,
331
                                               realm_value_t* modification_keys, size_t* modifications_size)
332
{
2✔
333
    auto fill = [](const auto& collection, realm_value_t* out, size_t* n) {
6✔
334
        if (!out || !n)
6✔
335
            return;
2✔
336
        if (collection.size() == 0 || *n < collection.size()) {
4✔
337
            *n = 0;
×
338
            return;
×
339
        }
×
340
        size_t i = 0;
4✔
341
        for (auto val : collection)
4✔
342
            out[i++] = to_capi(val);
6✔
343
        *n = i;
4✔
344
    };
4✔
345

1✔
346
    fill(changes->deletions, deletion_keys, deletions_size);
2✔
347
    fill(changes->insertions, insertion_keys, insertions_size);
2✔
348
    fill(changes->modifications, modification_keys, modifications_size);
2✔
349
}
2✔
350

351
static inline void copy_indices(const IndexSet& index_set, size_t* out_indices, size_t max)
352
{
8✔
353
    size_t i = 0;
8✔
354
    for (auto index : index_set.as_indexes()) {
10✔
355
        if (i >= max)
10✔
356
            return;
×
357
        out_indices[i] = index;
10✔
358
        ++i;
10✔
359
    }
10✔
360
}
8✔
361

362
RLM_API void realm_collection_changes_get_changes(const realm_collection_changes_t* changes, size_t* out_deletions,
363
                                                  size_t max_deletions, size_t* out_insertions, size_t max_insertions,
364
                                                  size_t* out_modifications, size_t max_modifications,
365
                                                  size_t* out_modifications_after, size_t max_modifications_after,
366
                                                  realm_collection_move_t* out_moves, size_t max_moves)
367
{
2✔
368
    if (out_deletions) {
2✔
369
        copy_indices(changes->deletions, out_deletions, max_deletions);
2✔
370
    }
2✔
371
    if (out_insertions) {
2✔
372
        copy_indices(changes->insertions, out_insertions, max_insertions);
2✔
373
    }
2✔
374
    if (out_modifications) {
2✔
375
        copy_indices(changes->modifications, out_modifications, max_modifications);
2✔
376
    }
2✔
377
    if (out_modifications_after) {
2✔
378
        copy_indices(changes->modifications_new, out_modifications_after, max_modifications_after);
2✔
379
    }
2✔
380
    if (out_moves) {
2✔
381
        size_t i = 0;
2✔
382
        for (auto [from, to] : changes->moves) {
1✔
383
            if (i >= max_moves)
×
384
                break;
×
385
            out_moves[i] = realm_collection_move_t{from, to};
×
386
            ++i;
×
387
        }
×
388
    }
2✔
389
}
2✔
390

391
} // namespace realm::c_api
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc