Skip to content

Commit 1ddbb3f

Browse files
New API and optimizations added to MutableSubscriptionSet (#7008)
* New API and optimizations added to MutableSubscriptionSet * Changes after code review * Update changelog
1 parent e76f494 commit 1ddbb3f

File tree

7 files changed

+203
-10
lines changed

7 files changed

+203
-10
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
### Enhancements
44
* <New feature description> (PR [#????](https://github.com/realm/realm-core/pull/????))
55
* Allow collections of non-embedded links in asymmetric objects. ([PR #7003](https://github.com/realm/realm-core/pull/7003))
6+
* Flexible sync API improvements:
7+
- Erase Subscriptions by class type for C API.
8+
- `MutableSubscriptionSet::erase(iterator)` now runs in constant time.
9+
- Introduce `MutableSubscriptionSet::erase_by_id()`.
10+
- Introduce `MutableSubscriptionSet::erase_by_class_name()`.
11+
([PR #7008](https://github.com/realm/realm-core/pull/7008))
612

713
### Fixed
814
* <How do the end-user experience this issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)

src/realm.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3691,6 +3691,12 @@ RLM_API bool realm_sync_subscription_set_erase_by_query(realm_flx_sync_mutable_s
36913691
*/
36923692
RLM_API bool realm_sync_subscription_set_erase_by_results(realm_flx_sync_mutable_subscription_set_t*,
36933693
realm_results_t*, bool* erased);
3694+
/**
3695+
* Remove all subscriptions for a given class type. If operation completes successfully set the bool out param.
3696+
* @return true if no error occurred, false otherwise (use realm_get_last_error for fetching the error).
3697+
*/
3698+
RLM_API bool realm_sync_subscription_set_erase_by_class_name(realm_flx_sync_mutable_subscription_set_t*, const char*,
3699+
bool* erased);
36943700
/**
36953701
* Commit the subscription_set passed as parameter (in order that all the changes made will take effect)
36963702
* @return pointer to a valid immutable subscription if commit was successful

src/realm/object-store/c_api/sync.cpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -609,13 +609,7 @@ RLM_API bool realm_sync_subscription_set_erase_by_id(realm_flx_sync_mutable_subs
609609
REALM_ASSERT(subscription_set != nullptr && id != nullptr);
610610
*erased = false;
611611
return wrap_err([&] {
612-
auto it = std::find_if(subscription_set->begin(), subscription_set->end(), [id](const Subscription& sub) {
613-
return from_capi(*id) == sub.id;
614-
});
615-
if (it != subscription_set->end()) {
616-
subscription_set->erase(it);
617-
*erased = true;
618-
}
612+
*erased = subscription_set->erase_by_id(from_capi(*id));
619613
return true;
620614
});
621615
}
@@ -655,6 +649,18 @@ RLM_API bool realm_sync_subscription_set_erase_by_results(realm_flx_sync_mutable
655649
});
656650
}
657651

652+
RLM_API bool
653+
realm_sync_subscription_set_erase_by_class_name(realm_flx_sync_mutable_subscription_set_t* subscription_set,
654+
const char* object_class_name, bool* erased)
655+
{
656+
REALM_ASSERT(subscription_set != nullptr && object_class_name != nullptr);
657+
*erased = false;
658+
return wrap_err([&]() {
659+
*erased = subscription_set->erase_by_class_name(object_class_name);
660+
return true;
661+
});
662+
}
663+
658664
RLM_API realm_flx_sync_subscription_set_t*
659665
realm_sync_subscription_set_commit(realm_flx_sync_mutable_subscription_set_t* subscription_set)
660666
{

src/realm/sync/subscriptions.cpp

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
#include <initializer_list>
3434
#include <stdexcept>
3535

36+
#include <algorithm>
37+
3638
namespace realm::sync {
3739
namespace {
3840
// Schema version history:
@@ -273,11 +275,24 @@ void MutableSubscriptionSet::check_is_mutable() const
273275
}
274276
}
275277

278+
// This uses the 'swap and pop' idiom to run in constant time.
279+
// The iterator returned is:
280+
// 1. end(), if the last subscription is removed
281+
// 2. same iterator it is passed (but pointing to the last subscription in set), otherwise
276282
MutableSubscriptionSet::iterator MutableSubscriptionSet::erase(const_iterator it)
277283
{
278284
check_is_mutable();
279285
REALM_ASSERT(it != end());
280-
return m_subs.erase(it);
286+
if (it == std::prev(m_subs.end())) {
287+
m_subs.pop_back();
288+
return end();
289+
}
290+
auto back = std::prev(m_subs.end());
291+
// const_iterator to iterator in constant time (See https://stackoverflow.com/a/10669041)
292+
auto iterator = m_subs.erase(it, it);
293+
std::swap(*iterator, *back);
294+
m_subs.pop_back();
295+
return iterator;
281296
}
282297

283298
bool MutableSubscriptionSet::erase(StringData name)
@@ -286,7 +301,8 @@ bool MutableSubscriptionSet::erase(StringData name)
286301
auto ptr = find(name);
287302
if (!ptr)
288303
return false;
289-
m_subs.erase(m_subs.begin() + (ptr - &m_subs.front()));
304+
auto it = m_subs.begin() + (ptr - &m_subs.front());
305+
erase(it);
290306
return true;
291307
}
292308

@@ -296,7 +312,31 @@ bool MutableSubscriptionSet::erase(const Query& query)
296312
auto ptr = find(query);
297313
if (!ptr)
298314
return false;
299-
m_subs.erase(m_subs.begin() + (ptr - &m_subs.front()));
315+
auto it = m_subs.begin() + (ptr - &m_subs.front());
316+
erase(it);
317+
return true;
318+
}
319+
320+
bool MutableSubscriptionSet::erase_by_class_name(StringData object_class_name)
321+
{
322+
// TODO: Use std::erase_if when switching to C++20.
323+
auto it = std::remove_if(m_subs.begin(), m_subs.end(), [&object_class_name](const Subscription& sub) {
324+
return sub.object_class_name == object_class_name;
325+
});
326+
auto erased = end() - it;
327+
m_subs.erase(it, end());
328+
return erased > 0;
329+
}
330+
331+
bool MutableSubscriptionSet::erase_by_id(ObjectId id)
332+
{
333+
auto it = std::find_if(m_subs.begin(), m_subs.end(), [&id](const Subscription& sub) -> bool {
334+
return sub.id == id;
335+
});
336+
if (it == end()) {
337+
return false;
338+
}
339+
erase(it);
300340
return true;
301341
}
302342

src/realm/sync/subscriptions.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,9 @@ class MutableSubscriptionSet : public SubscriptionSet {
249249
bool erase(StringData name);
250250
bool erase(const Query& query);
251251

252+
bool erase_by_class_name(StringData object_class_name);
253+
bool erase_by_id(ObjectId id);
254+
252255
// Updates the state of the transaction and optionally updates its error information.
253256
//
254257
// You may only set an error_str when the State is State::Error.

test/object-store/c_api/c_api.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5991,6 +5991,27 @@ TEST_CASE("app: flx-sync basic tests", "[sync][flx][c_api][baas]") {
59915991
realm_release(sub_c_1);
59925992
}
59935993

5994+
{
5995+
auto sub = realm_sync_get_latest_subscription_set(&c_wrap_realm);
5996+
auto mut_sub = realm_sync_make_subscription_set_mutable(sub);
5997+
std::size_t index = -1;
5998+
bool inserted = false;
5999+
CHECK(realm_sync_subscription_set_insert_or_assign_query(mut_sub, c_wrap_query_bar, nullptr, &index,
6000+
&inserted));
6001+
CHECK(inserted);
6002+
CHECK(realm_sync_subscription_set_insert_or_assign_query(mut_sub, c_wrap_query_foo, nullptr, &index,
6003+
&inserted));
6004+
CHECK(inserted);
6005+
bool erased = false;
6006+
CHECK(realm_sync_subscription_set_erase_by_class_name(mut_sub, "Obj", &erased));
6007+
CHECK(erased);
6008+
// Nothing to remove when trying again.
6009+
CHECK(realm_sync_subscription_set_erase_by_class_name(mut_sub, "Obj", &erased));
6010+
CHECK_FALSE(erased);
6011+
realm_release(sub);
6012+
realm_release(mut_sub);
6013+
}
6014+
59946015
realm_release(c_wrap_query_foo);
59956016
realm_release(c_wrap_query_bar);
59966017
});

test/test_sync_subscriptions.cpp

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,4 +844,115 @@ TEST(Sync_SyncMetadataSchemaVersions_LegacyTable)
844844
}
845845
}
846846

847+
TEST(Sync_MutableSubscriptionSetOperations)
848+
{
849+
SHARED_GROUP_TEST_PATH(sub_store_path);
850+
SubscriptionStoreFixture fixture(sub_store_path);
851+
auto store = SubscriptionStore::create(fixture.db, [](int64_t) {});
852+
853+
auto read_tr = fixture.db->start_read();
854+
Query query_a(read_tr->get_table("class_a"));
855+
query_a.greater_equal(fixture.bar_col, int64_t(1));
856+
Query query_b(read_tr->get_table(fixture.a_table_key));
857+
query_b.equal(fixture.foo_col, "Realm");
858+
Query query_c(read_tr->get_table(fixture.a_table_key));
859+
860+
// insert_or_assign
861+
{
862+
auto out = store->get_latest().make_mutable_copy();
863+
auto [it, inserted] = out.insert_or_assign("a sub", query_a);
864+
CHECK(inserted);
865+
auto named_id = it->id;
866+
out.insert_or_assign("b sub", query_b);
867+
CHECK_EQUAL(out.size(), 2);
868+
std::tie(it, inserted) = out.insert_or_assign("a sub", query_a);
869+
CHECK_NOT(inserted);
870+
CHECK_EQUAL(it->id, named_id);
871+
CHECK_EQUAL(out.size(), 2);
872+
}
873+
874+
// find
875+
{
876+
auto out = store->get_latest().make_mutable_copy();
877+
auto [it, inserted] = out.insert_or_assign("a sub", query_a);
878+
std::tie(it, inserted) = out.insert_or_assign("b sub", query_b);
879+
CHECK(out.find(query_b));
880+
CHECK(out.find("a sub"));
881+
}
882+
883+
// erase
884+
{
885+
auto out = store->get_latest().make_mutable_copy();
886+
out.insert_or_assign("a sub", query_a);
887+
out.insert_or_assign("b sub", query_b);
888+
out.insert_or_assign("c sub", query_c);
889+
CHECK_EQUAL(out.size(), 3);
890+
auto it = out.erase(out.begin());
891+
// Iterator points to last query inserted due do "swap and pop" idiom.
892+
CHECK_EQUAL(it->query_string, query_c.get_description());
893+
CHECK_EQUAL(out.size(), 2);
894+
CHECK_NOT(out.erase("a sub"));
895+
CHECK_EQUAL(out.size(), 2);
896+
CHECK(out.erase(query_b));
897+
CHECK_EQUAL(out.size(), 1);
898+
CHECK(out.erase("c sub"));
899+
CHECK_EQUAL(out.size(), 0);
900+
}
901+
902+
// erase_by_class_name
903+
{
904+
auto out = store->get_latest().make_mutable_copy();
905+
out.insert_or_assign("a sub", query_a);
906+
out.insert_or_assign("b sub", query_b);
907+
out.insert_or_assign("c sub", query_c);
908+
// Nothing to erase.
909+
CHECK_NOT(out.erase_by_class_name("foo"));
910+
// Erase all queries for the class type of the first query.
911+
CHECK(out.erase_by_class_name(out.begin()->object_class_name));
912+
// No queries left.
913+
CHECK_EQUAL(out.size(), 0);
914+
}
915+
916+
// erase_by_id
917+
{
918+
auto out = store->get_latest().make_mutable_copy();
919+
out.insert_or_assign("a sub", query_a);
920+
out.insert_or_assign("b sub", query_b);
921+
// Nothing to erase.
922+
CHECK_NOT(out.erase_by_id(ObjectId::gen()));
923+
// Erase first query.
924+
CHECK(out.erase_by_id(out.begin()->id));
925+
CHECK_EQUAL(out.size(), 1);
926+
}
927+
928+
// clear
929+
{
930+
auto out = store->get_latest().make_mutable_copy();
931+
out.insert_or_assign("a sub", query_a);
932+
out.insert_or_assign("b sub", query_b);
933+
out.insert_or_assign("c sub", query_c);
934+
CHECK_EQUAL(out.size(), 3);
935+
out.clear();
936+
CHECK_EQUAL(out.size(), 0);
937+
}
938+
939+
// import
940+
{
941+
auto out = store->get_latest().make_mutable_copy();
942+
out.insert_or_assign("a sub", query_a);
943+
out.insert_or_assign("b sub", query_b);
944+
auto subs = out.commit();
945+
946+
// This is an empty subscription set.
947+
auto out2 = store->get_active().make_mutable_copy();
948+
out2.insert_or_assign("c sub", query_c);
949+
out2.import(subs);
950+
// "c sub" is erased when 'import' is used.
951+
CHECK_EQUAL(out2.size(), 2);
952+
// insert "c sub" again.
953+
out2.insert_or_assign("c sub", query_c);
954+
CHECK_EQUAL(out2.size(), 3);
955+
}
956+
}
957+
847958
} // namespace realm::sync

0 commit comments

Comments
 (0)