123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117 |
- #include "catch.hpp"
- #include "util/event_loop.hpp"
- #include "util/index_helpers.hpp"
- #include "util/templated_test_case.hpp"
- #include "util/test_file.hpp"
- #include "impl/object_accessor_impl.hpp"
- #include "impl/realm_coordinator.hpp"
- #include "binding_context.hpp"
- #include "object_schema.hpp"
- #include "property.hpp"
- #include "results.hpp"
- #include "schema.hpp"
- #include <realm/group_shared.hpp>
- #include <realm/link_view.hpp>
- #include <realm/query_engine.hpp>
- #include <realm/query_expression.hpp>
- #if REALM_ENABLE_SYNC
- #include "sync/sync_manager.hpp"
- #include "sync/sync_session.hpp"
- #endif
- namespace realm {
- class TestHelper {
- public:
- static SharedGroup& get_shared_group(SharedRealm const& shared_realm)
- {
- return *Realm::Internal::get_shared_group(*shared_realm);
- }
- };
- }
- using namespace realm;
- using namespace std::string_literals;
- namespace {
- using AnyDict = std::map<std::string, util::Any>;
- using AnyVec = std::vector<util::Any>;
- }
- struct TestContext : CppContext {
- std::map<std::string, AnyDict> defaults;
- using CppContext::CppContext;
- TestContext(TestContext& parent, realm::Property const& prop)
- : CppContext(parent, prop)
- , defaults(parent.defaults)
- { }
- void will_change(Object const&, Property const&) {}
- void did_change() {}
- std::string print(util::Any) { return "not implemented"; }
- bool allow_missing(util::Any) { return false; }
- };
- TEST_CASE("notifications: async delivery") {
- _impl::RealmCoordinator::assert_no_open_realms();
- InMemoryTestFile config;
- config.cache = false;
- config.automatic_change_notifications = false;
- auto r = Realm::get_shared_realm(config);
- r->update_schema({
- {"object", {
- {"value", PropertyType::Int}
- }},
- });
- auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
- auto table = r->read_group().get_table("class_object");
- r->begin_transaction();
- table->add_empty_row(10);
- for (int i = 0; i < 10; ++i)
- table->set_int(0, i, i * 2);
- r->commit_transaction();
- Results results(r, table->where().greater(0, 0).less(0, 10));
- int notification_calls = 0;
- auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- ++notification_calls;
- });
- auto make_local_change = [&] {
- r->begin_transaction();
- table->set_int(0, 0, 4);
- r->commit_transaction();
- };
- auto make_remote_change = [&] {
- auto r2 = coordinator->get_realm();
- r2->begin_transaction();
- r2->read_group().get_table("class_object")->set_int(0, 0, 5);
- r2->commit_transaction();
- };
- SECTION("initial notification") {
- SECTION("is delivered on notify()") {
- REQUIRE(notification_calls == 0);
- advance_and_notify(*r);
- REQUIRE(notification_calls == 1);
- }
- SECTION("is delivered on refresh()") {
- coordinator->on_change();
- REQUIRE(notification_calls == 0);
- r->refresh();
- REQUIRE(notification_calls == 1);
- }
- SECTION("is delivered on begin_transaction()") {
- coordinator->on_change();
- REQUIRE(notification_calls == 0);
- r->begin_transaction();
- REQUIRE(notification_calls == 1);
- r->cancel_transaction();
- }
- SECTION("is delivered on notify() even with autorefresh disabled") {
- r->set_auto_refresh(false);
- REQUIRE(notification_calls == 0);
- advance_and_notify(*r);
- REQUIRE(notification_calls == 1);
- }
- SECTION("refresh() blocks due to initial results not being ready") {
- REQUIRE(notification_calls == 0);
- JoiningThread thread([&] {
- std::this_thread::sleep_for(std::chrono::microseconds(5000));
- coordinator->on_change();
- });
- r->refresh();
- REQUIRE(notification_calls == 1);
- }
- SECTION("begin_transaction() blocks due to initial results not being ready") {
- REQUIRE(notification_calls == 0);
- JoiningThread thread([&] {
- std::this_thread::sleep_for(std::chrono::microseconds(5000));
- coordinator->on_change();
- });
- r->begin_transaction();
- REQUIRE(notification_calls == 1);
- r->cancel_transaction();
- }
- SECTION("notify() does not block due to initial results not being ready") {
- REQUIRE(notification_calls == 0);
- r->notify();
- REQUIRE(notification_calls == 0);
- }
- SECTION("is delivered after invalidate()") {
- r->invalidate();
- SECTION("notify()") {
- coordinator->on_change();
- REQUIRE_FALSE(r->is_in_read_transaction());
- r->notify();
- REQUIRE(notification_calls == 1);
- }
- SECTION("notify() without autorefresh") {
- r->set_auto_refresh(false);
- coordinator->on_change();
- REQUIRE_FALSE(r->is_in_read_transaction());
- r->notify();
- REQUIRE(notification_calls == 1);
- }
- SECTION("refresh()") {
- coordinator->on_change();
- REQUIRE_FALSE(r->is_in_read_transaction());
- r->refresh();
- REQUIRE(notification_calls == 1);
- }
- SECTION("begin_transaction()") {
- coordinator->on_change();
- REQUIRE_FALSE(r->is_in_read_transaction());
- r->begin_transaction();
- REQUIRE(notification_calls == 1);
- r->cancel_transaction();
- }
- }
- SECTION("is delivered by notify() even if there are later versions") {
- REQUIRE(notification_calls == 0);
- coordinator->on_change();
- make_remote_change();
- r->notify();
- REQUIRE(notification_calls == 1);
- }
- }
- advance_and_notify(*r);
- SECTION("notifications for local changes") {
- make_local_change();
- coordinator->on_change();
- REQUIRE(notification_calls == 1);
- SECTION("notify()") {
- r->notify();
- REQUIRE(notification_calls == 2);
- }
- SECTION("notify() without autorefresh") {
- r->set_auto_refresh(false);
- r->notify();
- REQUIRE(notification_calls == 2);
- }
- SECTION("refresh()") {
- r->refresh();
- REQUIRE(notification_calls == 2);
- }
- SECTION("begin_transaction()") {
- r->begin_transaction();
- REQUIRE(notification_calls == 2);
- r->cancel_transaction();
- }
- }
- SECTION("notifications for remote changes") {
- make_remote_change();
- coordinator->on_change();
- REQUIRE(notification_calls == 1);
- SECTION("notify()") {
- r->notify();
- REQUIRE(notification_calls == 2);
- }
- SECTION("notify() without autorefresh") {
- r->set_auto_refresh(false);
- r->notify();
- REQUIRE(notification_calls == 1);
- r->refresh();
- REQUIRE(notification_calls == 2);
- }
- SECTION("refresh()") {
- r->refresh();
- REQUIRE(notification_calls == 2);
- }
- SECTION("begin_transaction()") {
- r->begin_transaction();
- REQUIRE(notification_calls == 2);
- r->cancel_transaction();
- }
- }
- SECTION("notifications are not delivered when the token is destroyed before they are calculated") {
- make_remote_change();
- REQUIRE(notification_calls == 1);
- token = {};
- advance_and_notify(*r);
- REQUIRE(notification_calls == 1);
- }
- SECTION("notifications are not delivered when the token is destroyed before they are delivered") {
- make_remote_change();
- REQUIRE(notification_calls == 1);
- coordinator->on_change();
- token = {};
- r->notify();
- REQUIRE(notification_calls == 1);
- }
- SECTION("notifications are delivered on the next cycle when a new callback is added from within a callback") {
- NotificationToken token2, token3;
- bool called = false;
- token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- token2 = {};
- token3 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- called = true;
- });
- });
- advance_and_notify(*r);
- REQUIRE_FALSE(called);
- advance_and_notify(*r);
- REQUIRE(called);
- }
- SECTION("notifications are delivered on the next cycle when a new callback is added from within a callback") {
- auto results2 = results;
- auto results3 = results;
- NotificationToken token2, token3, token4;
- bool called = false;
- auto check = [&](Results& outer, Results& inner) {
- token2 = outer.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- token2 = {};
- token3 = inner.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- called = true;
- });
- });
- advance_and_notify(*r);
- REQUIRE_FALSE(called);
- advance_and_notify(*r);
- REQUIRE(called);
- };
- SECTION("same Results") {
- check(results, results);
- }
- SECTION("Results which has never had a notifier") {
- check(results, results2);
- }
- SECTION("Results which used to have callbacks but no longer does") {
- SECTION("notifier before active") {
- token3 = results2.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- token3 = {};
- });
- check(results3, results2);
- }
- SECTION("notifier after active") {
- token3 = results2.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- token3 = {};
- });
- check(results, results2);
- }
- }
- SECTION("Results which already has callbacks") {
- SECTION("notifier before active") {
- token4 = results2.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { });
- check(results3, results2);
- }
- SECTION("notifier after active") {
- token4 = results2.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { });
- check(results, results2);
- }
- }
- }
- SECTION("remote changes made before adding a callback from within a callback are not reported") {
- NotificationToken token2, token3;
- bool called = false;
- token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- token2 = {};
- make_remote_change();
- coordinator->on_change();
- token3 = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
- called = true;
- REQUIRE(c.empty());
- REQUIRE(table->get_int(0, 0) == 5);
- });
- });
- advance_and_notify(*r);
- REQUIRE_FALSE(called);
- advance_and_notify(*r);
- REQUIRE(called);
- }
- SECTION("notifications are not delivered when a callback is removed from within a callback") {
- NotificationToken token2, token3;
- token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- token3 = {};
- });
- token3 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- REQUIRE(false);
- });
- advance_and_notify(*r);
- }
- SECTION("removing the current callback does not stop later ones from being called") {
- NotificationToken token2, token3;
- bool called = false;
- token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- token2 = {};
- });
- token3 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- called = true;
- });
- advance_and_notify(*r);
- REQUIRE(called);
- }
- SECTION("the first call of a notification can include changes if it previously ran for a different callback") {
- r->begin_transaction();
- auto token2 = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
- REQUIRE(!c.empty());
- });
- table->set_int(0, table->add_empty_row(), 5);
- r->commit_transaction();
- advance_and_notify(*r);
- }
- SECTION("handling of results not ready") {
- make_remote_change();
- SECTION("notify() does nothing") {
- r->notify();
- REQUIRE(notification_calls == 1);
- coordinator->on_change();
- r->notify();
- REQUIRE(notification_calls == 2);
- }
- SECTION("refresh() blocks") {
- REQUIRE(notification_calls == 1);
- JoiningThread thread([&] {
- std::this_thread::sleep_for(std::chrono::microseconds(5000));
- coordinator->on_change();
- });
- r->refresh();
- REQUIRE(notification_calls == 2);
- }
- SECTION("refresh() advances to the first version with notifiers ready that is at least a recent as the newest at the time it is called") {
- JoiningThread thread([&] {
- std::this_thread::sleep_for(std::chrono::microseconds(5000));
- make_remote_change();
- coordinator->on_change();
- make_remote_change();
- });
-
-
- r->refresh();
- REQUIRE(notification_calls == 2);
- thread.join();
- REQUIRE(notification_calls == 2);
-
- coordinator->on_change();
- r->refresh();
- REQUIRE(notification_calls == 3);
- }
- SECTION("begin_transaction() blocks") {
- REQUIRE(notification_calls == 1);
- JoiningThread thread([&] {
- std::this_thread::sleep_for(std::chrono::microseconds(5000));
- coordinator->on_change();
- });
- r->begin_transaction();
- REQUIRE(notification_calls == 2);
- r->cancel_transaction();
- }
- SECTION("refresh() does not block for results without callbacks") {
- token = {};
-
- r->refresh();
- }
- SECTION("begin_transaction() does not block for results without callbacks") {
- token = {};
-
- r->begin_transaction();
- r->cancel_transaction();
- }
- SECTION("begin_transaction() does not block for Results for different Realms") {
-
-
- make_remote_change();
-
- r->notify();
- REQUIRE(notification_calls == 1);
- }
- }
- SECTION("handling of stale results") {
- make_remote_change();
- coordinator->on_change();
- make_remote_change();
- SECTION("notify() uses the older version") {
- r->notify();
- REQUIRE(notification_calls == 2);
- coordinator->on_change();
- r->notify();
- REQUIRE(notification_calls == 3);
- r->notify();
- REQUIRE(notification_calls == 3);
- }
- SECTION("refresh() blocks") {
- REQUIRE(notification_calls == 1);
- JoiningThread thread([&] {
- std::this_thread::sleep_for(std::chrono::microseconds(5000));
- coordinator->on_change();
- });
- r->refresh();
- REQUIRE(notification_calls == 2);
- }
- SECTION("begin_transaction() blocks") {
- REQUIRE(notification_calls == 1);
- JoiningThread thread([&] {
- std::this_thread::sleep_for(std::chrono::microseconds(5000));
- coordinator->on_change();
- });
- r->begin_transaction();
- REQUIRE(notification_calls == 2);
- r->cancel_transaction();
- }
- }
- SECTION("updates are delivered after invalidate()") {
- r->invalidate();
- make_remote_change();
- SECTION("notify()") {
- coordinator->on_change();
- REQUIRE_FALSE(r->is_in_read_transaction());
- r->notify();
- REQUIRE(notification_calls == 2);
- }
- SECTION("notify() without autorefresh") {
- r->set_auto_refresh(false);
- coordinator->on_change();
- REQUIRE_FALSE(r->is_in_read_transaction());
- r->notify();
- REQUIRE(notification_calls == 1);
- r->refresh();
- REQUIRE(notification_calls == 2);
- }
- SECTION("refresh()") {
- coordinator->on_change();
- REQUIRE_FALSE(r->is_in_read_transaction());
- r->refresh();
- REQUIRE(notification_calls == 2);
- }
- SECTION("begin_transaction()") {
- coordinator->on_change();
- REQUIRE_FALSE(r->is_in_read_transaction());
- r->begin_transaction();
- REQUIRE(notification_calls == 2);
- r->cancel_transaction();
- }
- }
- SECTION("refresh() from within changes_available() do not interfere with notification delivery") {
- struct Context : BindingContext {
- Realm& realm;
- Context(Realm& realm) : realm(realm) { }
- void changes_available() override
- {
- REQUIRE(realm.refresh());
- }
- };
- make_remote_change();
- coordinator->on_change();
- r->set_auto_refresh(false);
- REQUIRE(notification_calls == 1);
- r->notify();
- REQUIRE(notification_calls == 1);
- r->m_binding_context.reset(new Context(*r));
- r->notify();
- REQUIRE(notification_calls == 2);
- }
- SECTION("refresh() from within a notification is a no-op") {
- token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- REQUIRE_FALSE(r->refresh());
- });
- advance_and_notify(*r);
- make_remote_change();
- coordinator->on_change();
- make_remote_change();
- r->notify();
- coordinator->on_change();
- REQUIRE(r->refresh());
- REQUIRE_FALSE(r->refresh());
- }
- SECTION("begin_transaction() from within a notification does not send notifications immediately") {
- bool first = true;
- auto token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- if (first)
- first = false;
- else {
-
- r->begin_transaction();
- r->cancel_transaction();
- }
- });
- advance_and_notify(*r);
- make_remote_change();
- coordinator->on_change();
- make_remote_change();
- r->notify();
- REQUIRE(notification_calls == 2);
- coordinator->on_change();
- REQUIRE_FALSE(r->refresh());
- REQUIRE(notification_calls == 3);
- }
- SECTION("begin_transaction() from within a notification does not break delivering additional notifications") {
- size_t calls = 0;
- token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- if (++calls == 1)
- return;
-
- r->begin_transaction();
- r->cancel_transaction();
- });
- auto results2 = results;
- size_t calls2 = 0;
- auto token2 = results2.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- if (++calls2 == 1)
- return;
- REQUIRE_INDICES(c.insertions, 0);
- });
- advance_and_notify(*r);
- REQUIRE(calls == 1);
- REQUIRE(calls2 == 1);
- make_remote_change();
- coordinator->on_change();
- make_remote_change();
- r->notify();
- REQUIRE(calls == 2);
- REQUIRE(calls2 == 2);
- }
- SECTION("begin_transaction() from within did_change() does not break delivering collection notification") {
- struct Context : BindingContext {
- Realm& realm;
- Context(Realm& realm) : realm(realm) { }
- void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
- {
- if (!realm.is_in_transaction()) {
-
- realm.begin_transaction();
- realm.cancel_transaction();
- }
- }
- };
- r->m_binding_context.reset(new Context(*r));
- make_remote_change();
- coordinator->on_change();
- make_remote_change();
- r->notify();
- }
- SECTION("is_in_transaction() is reported correctly within a notification from begin_transaction() and changes can be made") {
- bool first = true;
- token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- if (first) {
- REQUIRE_FALSE(r->is_in_transaction());
- first = false;
- }
- else {
- REQUIRE(r->is_in_transaction());
- table->set_int(0, 0, 100);
- }
- });
- advance_and_notify(*r);
- make_remote_change();
- coordinator->on_change();
- r->begin_transaction();
- REQUIRE(table->get_int(0, 0) == 100);
- r->cancel_transaction();
- REQUIRE(table->get_int(0, 0) != 100);
- }
- SECTION("invalidate() from within notification is a no-op") {
- token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- r->invalidate();
- REQUIRE(r->is_in_read_transaction());
- });
- advance_and_notify(*r);
- REQUIRE(r->is_in_read_transaction());
- make_remote_change();
- coordinator->on_change();
- r->begin_transaction();
- REQUIRE(r->is_in_transaction());
- r->cancel_transaction();
- }
- SECTION("cancel_transaction() from within notification ends the write transaction started by begin_transaction()") {
- token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- if (r->is_in_transaction())
- r->cancel_transaction();
- });
- advance_and_notify(*r);
- make_remote_change();
- coordinator->on_change();
- r->begin_transaction();
- REQUIRE_FALSE(r->is_in_transaction());
- }
- }
- TEST_CASE("notifications: skip") {
- _impl::RealmCoordinator::assert_no_open_realms();
- InMemoryTestFile config;
- config.cache = false;
- config.automatic_change_notifications = false;
- auto r = Realm::get_shared_realm(config);
- r->update_schema({
- {"object", {
- {"value", PropertyType::Int}
- }},
- });
- auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
- auto table = r->read_group().get_table("class_object");
- r->begin_transaction();
- table->add_empty_row(10);
- for (int i = 0; i < 10; ++i)
- table->set_int(0, i, i * 2);
- r->commit_transaction();
- Results results(r, table->where());
- auto add_callback = [](Results& results, int& calls, CollectionChangeSet& changes) {
- return results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- ++calls;
- changes = std::move(c);
- });
- };
- auto make_local_change = [&](auto& token) {
- r->begin_transaction();
- table->add_empty_row();
- token.suppress_next();
- r->commit_transaction();
- };
- auto make_remote_change = [&] {
- auto r2 = coordinator->get_realm();
- r2->begin_transaction();
- r2->read_group().get_table("class_object")->add_empty_row();
- r2->commit_transaction();
- };
- int calls1 = 0;
- CollectionChangeSet changes1;
- auto token1 = add_callback(results, calls1, changes1);
- SECTION("no notification is sent when only callback is skipped") {
- advance_and_notify(*r);
- REQUIRE(calls1 == 1);
- make_local_change(token1);
- advance_and_notify(*r);
- REQUIRE(calls1 == 1);
- REQUIRE(changes1.empty());
- }
- SECTION("unskipped tokens for the same Results are still delivered") {
- int calls2 = 0;
- CollectionChangeSet changes2;
- auto token2 = add_callback(results, calls2, changes2);
- advance_and_notify(*r);
- REQUIRE(calls1 == 1);
- REQUIRE(calls2 == 1);
- make_local_change(token1);
- advance_and_notify(*r);
- REQUIRE(calls1 == 1);
- REQUIRE(changes1.empty());
- REQUIRE(calls2 == 2);
- REQUIRE_INDICES(changes2.insertions, 10);
- }
- SECTION("unskipped tokens for different Results are still delivered") {
- Results results2(r, table->where());
- int calls2 = 0;
- CollectionChangeSet changes2;
- auto token2 = add_callback(results2, calls2, changes2);
- advance_and_notify(*r);
- REQUIRE(calls1 == 1);
- REQUIRE(calls2 == 1);
- make_local_change(token1);
- advance_and_notify(*r);
- REQUIRE(calls1 == 1);
- REQUIRE(changes1.empty());
- REQUIRE(calls2 == 2);
- REQUIRE_INDICES(changes2.insertions, 10);
- }
- SECTION("additional commits which occur before calculation are merged in") {
- int calls2 = 0;
- CollectionChangeSet changes2;
- auto token2 = add_callback(results, calls2, changes2);
- advance_and_notify(*r);
- REQUIRE(calls1 == 1);
- REQUIRE(calls2 == 1);
- make_local_change(token1);
- make_remote_change();
- advance_and_notify(*r);
- REQUIRE(calls1 == 2);
- REQUIRE_INDICES(changes1.insertions, 11);
- REQUIRE(calls2 == 2);
- REQUIRE_INDICES(changes2.insertions, 10, 11);
- }
- SECTION("additional commits which occur before delivery are merged in") {
- int calls2 = 0;
- CollectionChangeSet changes2;
- auto token2 = add_callback(results, calls2, changes2);
- advance_and_notify(*r);
- REQUIRE(calls1 == 1);
- REQUIRE(calls2 == 1);
- make_local_change(token1);
- coordinator->on_change();
- make_remote_change();
- advance_and_notify(*r);
- REQUIRE(calls1 == 2);
- REQUIRE_INDICES(changes1.insertions, 11);
- REQUIRE(calls2 == 2);
- REQUIRE_INDICES(changes2.insertions, 10, 11);
- }
- SECTION("skipping must be done from within a write transaction") {
- REQUIRE_THROWS(token1.suppress_next());
- }
- SECTION("skipping must be done from the Realm's thread") {
- advance_and_notify(*r);
- r->begin_transaction();
- std::thread([&] {
- REQUIRE_THROWS(token1.suppress_next());
- }).join();
- r->cancel_transaction();
- }
- SECTION("new notifiers do not interfere with skipping") {
- advance_and_notify(*r);
- REQUIRE(calls1 == 1);
- CollectionChangeSet changes;
-
- auto r2 = coordinator->get_realm();
- Results results2(r2, r2->read_group().get_table("class_object")->where());
- int calls2 = 0;
- auto token2 = add_callback(results2, calls2, changes);
- make_local_change(token1);
-
- auto r3 = coordinator->get_realm();
- Results results3(r3, r3->read_group().get_table("class_object")->where());
- int calls3 = 0;
- auto token3 = add_callback(results3, calls3, changes);
- make_remote_change();
-
- auto r4 = coordinator->get_realm();
- Results results4(r4, r4->read_group().get_table("class_object")->where());
- int calls4 = 0;
- auto token4 = add_callback(results4, calls4, changes);
- coordinator->on_change();
- r->notify();
- r2->notify();
- r3->notify();
- r4->notify();
- REQUIRE(calls1 == 2);
- REQUIRE(calls2 == 1);
- REQUIRE(calls3 == 1);
- REQUIRE(calls4 == 1);
- }
- SECTION("skipping only effects the current transaction even if no notification would occur anyway") {
- advance_and_notify(*r);
- REQUIRE(calls1 == 1);
-
- r->begin_transaction();
- token1.suppress_next();
- r->commit_transaction();
- advance_and_notify(*r);
- REQUIRE(calls1 == 1);
-
- r->begin_transaction();
- table->add_empty_row();
- r->commit_transaction();
- advance_and_notify(*r);
- REQUIRE(calls1 == 2);
- }
- SECTION("removing skipped notifier before it gets the chance to run") {
- advance_and_notify(*r);
- REQUIRE(calls1 == 1);
-
- make_local_change(token1);
-
- make_remote_change();
- REQUIRE(calls1 == 1);
-
-
- token1 = {};
- results = {};
- Results results2(r, table->where());
- auto token2 = add_callback(results2, calls1, changes1);
- advance_and_notify(*r);
- REQUIRE(calls1 == 2);
- }
- }
- #if REALM_PLATFORM_APPLE
- TEST_CASE("notifications: async error handling") {
- _impl::RealmCoordinator::assert_no_open_realms();
- InMemoryTestFile config;
- config.cache = false;
- config.automatic_change_notifications = false;
- auto r = Realm::get_shared_realm(config);
- r->update_schema({
- {"object", {
- {"value", PropertyType::Int},
- }},
- });
- auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
- Results results(r, *r->read_group().get_table("class_object"));
- auto r2 = Realm::get_shared_realm(config);
- class OpenFileLimiter {
- public:
- OpenFileLimiter()
- {
-
- getrlimit(RLIMIT_NOFILE, &m_old);
- rlimit rl = m_old;
- rl.rlim_cur = 0;
- setrlimit(RLIMIT_NOFILE, &rl);
- }
- ~OpenFileLimiter()
- {
- setrlimit(RLIMIT_NOFILE, &m_old);
- }
- private:
- rlimit m_old;
- };
- SECTION("error when opening the advancer SG") {
- OpenFileLimiter limiter;
- bool called = false;
- auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE(err);
- REQUIRE_FALSE(called);
- called = true;
- });
- REQUIRE(!called);
- SECTION("error is delivered on notify() without changes") {
- coordinator->on_change();
- REQUIRE(!called);
- r->notify();
- REQUIRE(called);
- }
- SECTION("error is delivered on notify() with changes") {
- r2->begin_transaction(); r2->commit_transaction();
- REQUIRE(!called);
- coordinator->on_change();
- REQUIRE(!called);
- r->notify();
- REQUIRE(called);
- }
- SECTION("error is delivered on refresh() without changes") {
- coordinator->on_change();
- REQUIRE(!called);
- r->refresh();
- REQUIRE(called);
- }
- SECTION("error is delivered on refresh() with changes") {
- r2->begin_transaction(); r2->commit_transaction();
- REQUIRE(!called);
- coordinator->on_change();
- REQUIRE(!called);
- r->refresh();
- REQUIRE(called);
- }
- SECTION("error is delivered on begin_transaction() without changes") {
- coordinator->on_change();
- REQUIRE(!called);
- r->begin_transaction();
- REQUIRE(called);
- r->cancel_transaction();
- }
- SECTION("error is delivered on begin_transaction() with changes") {
- r2->begin_transaction(); r2->commit_transaction();
- REQUIRE(!called);
- coordinator->on_change();
- REQUIRE(!called);
- r->begin_transaction();
- REQUIRE(called);
- r->cancel_transaction();
- }
- SECTION("adding another callback sends the error to only the newly added one") {
- advance_and_notify(*r);
- REQUIRE(called);
- bool called2 = false;
- auto token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE(err);
- REQUIRE_FALSE(called2);
- called2 = true;
- });
- advance_and_notify(*r);
- REQUIRE(called2);
- }
- SECTION("destroying a token from before the error does not remove newly added callbacks") {
- advance_and_notify(*r);
- bool called = false;
- auto token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE(err);
- REQUIRE_FALSE(called);
- called = true;
- });
- token = {};
- advance_and_notify(*r);
- REQUIRE(called);
- }
- SECTION("adding another callback from within an error callback defers delivery") {
- NotificationToken token2;
- token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE(err);
- REQUIRE_FALSE(called);
- called = true;
- });
- });
- advance_and_notify(*r);
- REQUIRE(!called);
- advance_and_notify(*r);
- REQUIRE(called);
- }
- SECTION("adding a callback to a different collection from within the error callback defers delivery") {
- auto results2 = results;
- NotificationToken token2;
- token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- token2 = results2.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE(err);
- REQUIRE_FALSE(called);
- called = true;
- });
- });
- advance_and_notify(*r);
- REQUIRE(!called);
- advance_and_notify(*r);
- REQUIRE(called);
- }
- }
- SECTION("error when opening the executor SG") {
- SECTION("error is delivered asynchronously") {
- bool called = false;
- auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE(err);
- called = true;
- });
- OpenFileLimiter limiter;
- REQUIRE(!called);
- coordinator->on_change();
- REQUIRE(!called);
- r->notify();
- REQUIRE(called);
- }
- SECTION("adding another callback only sends the error to the new one") {
- bool called = false;
- auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE(err);
- REQUIRE_FALSE(called);
- called = true;
- });
- OpenFileLimiter limiter;
- advance_and_notify(*r);
- bool called2 = false;
- auto token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE(err);
- REQUIRE_FALSE(called2);
- called2 = true;
- });
- advance_and_notify(*r);
- REQUIRE(called2);
- }
- }
- }
- #endif
- #if REALM_ENABLE_SYNC
- TEST_CASE("notifications: sync") {
- _impl::RealmCoordinator::assert_no_open_realms();
- SyncServer server(false);
- SyncTestFile config(server);
- config.cache = false;
- config.schema = Schema{
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- SECTION("sync progress commits do not distrupt notifications") {
- auto r = Realm::get_shared_realm(config);
- auto wait_realm = Realm::get_shared_realm(config);
- Results results(r, *r->read_group().get_table("class_object"));
- Results wait_results(wait_realm, *wait_realm->read_group().get_table("class_object"));
- auto token1 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { });
- auto token2 = wait_results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { });
-
- {
- auto write_realm = Realm::get_shared_realm(config);
- write_realm->begin_transaction();
- sync::create_object(write_realm->read_group(), *write_realm->read_group().get_table("class_object"));
- write_realm->commit_transaction();
- }
-
- wait_realm->refresh();
-
-
- server.start();
- wait_for_upload(*r);
-
-
-
- r->refresh();
- }
- }
- #endif
- TEST_CASE("notifications: results") {
- _impl::RealmCoordinator::assert_no_open_realms();
- InMemoryTestFile config;
- config.cache = false;
- config.automatic_change_notifications = false;
- auto r = Realm::get_shared_realm(config);
- r->update_schema({
- {"object", {
- {"value", PropertyType::Int},
- {"link", PropertyType::Object|PropertyType::Nullable, "linked to object"}
- }},
- {"other object", {
- {"value", PropertyType::Int}
- }},
- {"linking object", {
- {"link", PropertyType::Object|PropertyType::Nullable, "object"}
- }},
- {"linked to object", {
- {"value", PropertyType::Int}
- }}
- });
- auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
- auto table = r->read_group().get_table("class_object");
- r->begin_transaction();
- r->read_group().get_table("class_linked to object")->add_empty_row(10);
- table->add_empty_row(10);
- for (int i = 0; i < 10; ++i) {
- table->set_int(0, i, i * 2);
- table->set_link(1, i, i);
- }
- r->commit_transaction();
- auto r2 = coordinator->get_realm();
- auto r2_table = r2->read_group().get_table("class_object");
- Results results(r, table->where().greater(0, 0).less(0, 10));
- SECTION("unsorted notifications") {
- int notification_calls = 0;
- CollectionChangeSet change;
- auto token = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- change = c;
- ++notification_calls;
- });
- advance_and_notify(*r);
- auto write = [&](auto&& f) {
- r->begin_transaction();
- f();
- r->commit_transaction();
- advance_and_notify(*r);
- };
- SECTION("modifications to unrelated tables do not send notifications") {
- write([&] {
- r->read_group().get_table("class_other object")->add_empty_row();
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("irrelevant modifications to linked tables do not send notifications") {
- write([&] {
- r->read_group().get_table("class_linked to object")->add_empty_row();
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("irrelevant modifications to linking tables do not send notifications") {
- write([&] {
- r->read_group().get_table("class_linking object")->add_empty_row();
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("modifications that leave a non-matching row non-matching do not send notifications") {
- write([&] {
- table->set_int(0, 6, 13);
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("deleting non-matching rows does not send a notification") {
- write([&] {
- table->move_last_over(0);
- table->move_last_over(6);
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("swapping adjacent matching and non-matching rows does not send notifications") {
- write([&] {
- table->swap_rows(0, 1);
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("swapping non-adjacent matching and non-matching rows send a single insert/delete pair") {
- write([&] {
- table->swap_rows(0, 2);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.deletions, 1);
- REQUIRE_INDICES(change.insertions, 0);
- }
- SECTION("swapping matching rows sends insert/delete pairs") {
- write([&] {
- table->swap_rows(1, 4);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.deletions, 0, 3);
- REQUIRE_INDICES(change.insertions, 0, 3);
- write([&] {
- table->swap_rows(1, 2);
- table->swap_rows(2, 3);
- table->swap_rows(3, 4);
- });
- REQUIRE(notification_calls == 3);
- REQUIRE_INDICES(change.deletions, 1, 2, 3);
- REQUIRE_INDICES(change.insertions, 0, 1, 2);
- }
- SECTION("swap does not inhibit move collapsing after removals") {
- write([&] {
- table->swap_rows(2, 3);
- table->set_int(0, 3, 100);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.deletions, 1);
- REQUIRE(change.insertions.empty());
- }
- SECTION("modifying a matching row and leaving it matching marks that row as modified") {
- write([&] {
- table->set_int(0, 1, 3);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.modifications, 0);
- REQUIRE_INDICES(change.modifications_new, 0);
- }
- SECTION("modifying a matching row to no longer match marks that row as deleted") {
- write([&] {
- table->set_int(0, 2, 0);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.deletions, 1);
- }
- SECTION("modifying a non-matching row to match marks that row as inserted, but not modified") {
- write([&] {
- table->set_int(0, 7, 3);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.insertions, 4);
- REQUIRE(change.modifications.empty());
- REQUIRE(change.modifications_new.empty());
- }
- SECTION("deleting a matching row marks that row as deleted") {
- write([&] {
- table->move_last_over(3);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.deletions, 2);
- }
- SECTION("moving a matching row via deletion marks that row as moved") {
- write([&] {
- table->where().greater_equal(0, 10).find_all().clear(RemoveMode::unordered);
- table->move_last_over(0);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_MOVES(change, {3, 0});
- }
- SECTION("moving a matching row via subsumption marks that row as modified") {
- write([&] {
- table->where().greater_equal(0, 10).find_all().clear(RemoveMode::unordered);
- table->move_last_over(0);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_MOVES(change, {3, 0});
- }
- SECTION("modifications from multiple transactions are collapsed") {
- r2->begin_transaction();
- r2_table->set_int(0, 0, 6);
- r2->commit_transaction();
- coordinator->on_change();
- r2->begin_transaction();
- r2_table->set_int(0, 1, 0);
- r2->commit_transaction();
- REQUIRE(notification_calls == 1);
- coordinator->on_change();
- r->notify();
- REQUIRE(notification_calls == 2);
- }
- SECTION("inserting a row then modifying it in a second transaction does not report it as modified") {
- r2->begin_transaction();
- size_t ndx = r2_table->add_empty_row();
- r2_table->set_int(0, ndx, 6);
- r2->commit_transaction();
- coordinator->on_change();
- r2->begin_transaction();
- r2_table->set_int(0, ndx, 7);
- r2->commit_transaction();
- advance_and_notify(*r);
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.insertions, 4);
- REQUIRE(change.modifications.empty());
- REQUIRE(change.modifications_new.empty());
- }
- SECTION("modification indices are pre-insert/delete") {
- r->begin_transaction();
- table->set_int(0, 2, 0);
- table->set_int(0, 3, 6);
- r->commit_transaction();
- advance_and_notify(*r);
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.deletions, 1);
- REQUIRE_INDICES(change.modifications, 2);
- REQUIRE_INDICES(change.modifications_new, 1);
- }
- SECTION("notifications are not delivered when collapsing transactions results in no net change") {
- r2->begin_transaction();
- size_t ndx = r2_table->add_empty_row();
- r2_table->set_int(0, ndx, 5);
- r2->commit_transaction();
- coordinator->on_change();
- r2->begin_transaction();
- r2_table->move_last_over(ndx);
- r2->commit_transaction();
- REQUIRE(notification_calls == 1);
- coordinator->on_change();
- r->notify();
- REQUIRE(notification_calls == 1);
- }
- SECTION("inserting a non-matching row at the beginning does not produce a notification") {
- write([&] {
- table->insert_empty_row(1);
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("inserting a matching row at the beginning marks just it as inserted") {
- write([&] {
- table->insert_empty_row(0);
- table->set_int(0, 0, 5);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.insertions, 0);
- }
- }
- SECTION("before/after change callback") {
- struct Callback {
- size_t before_calls = 0;
- size_t after_calls = 0;
- CollectionChangeSet before_change;
- CollectionChangeSet after_change;
- std::function<void(void)> on_before = []{};
- std::function<void(void)> on_after = []{};
- void before(CollectionChangeSet c) {
- before_change = c;
- ++before_calls;
- on_before();
- }
- void after(CollectionChangeSet c) {
- after_change = c;
- ++after_calls;
- on_after();
- }
- void error(std::exception_ptr) {
- FAIL("error() should not be called");
- }
- } callback;
- auto token = results.add_notification_callback(&callback);
- advance_and_notify(*r);
- SECTION("only after() is called for initial results") {
- REQUIRE(callback.before_calls == 0);
- REQUIRE(callback.after_calls == 1);
- REQUIRE(callback.after_change.empty());
- }
- auto write = [&](auto&& func) {
- r2->begin_transaction();
- func(*r2_table);
- r2->commit_transaction();
- advance_and_notify(*r);
- };
- SECTION("both are called after a write") {
- write([&](auto&& t) {
- t.set_int(0, t.add_empty_row(), 5);
- });
- REQUIRE(callback.before_calls == 1);
- REQUIRE(callback.after_calls == 2);
- REQUIRE_INDICES(callback.before_change.insertions, 4);
- REQUIRE_INDICES(callback.after_change.insertions, 4);
- }
- SECTION("deleted objects are usable in before()") {
- callback.on_before = [&] {
- REQUIRE(results.size() == 4);
- REQUIRE_INDICES(callback.before_change.deletions, 0);
- REQUIRE(results.get(0).is_attached());
- REQUIRE(results.get(0).get_int(0) == 2);
- };
- write([&](auto&& t) {
- t.move_last_over(results.get(0).get_index());
- });
- REQUIRE(callback.before_calls == 1);
- REQUIRE(callback.after_calls == 2);
- }
- SECTION("inserted objects are usable in after()") {
- callback.on_after = [&] {
- REQUIRE(results.size() == 5);
- REQUIRE_INDICES(callback.after_change.insertions, 4);
- REQUIRE(results.last()->get_int(0) == 5);
- };
- write([&](auto&& t) {
- t.set_int(0, t.add_empty_row(), 5);
- });
- REQUIRE(callback.before_calls == 1);
- REQUIRE(callback.after_calls == 2);
- }
- }
- SECTION("sorted notifications") {
-
- results = results.sort({*table, {{0}}, {false}});
- int notification_calls = 0;
- CollectionChangeSet change;
- auto token = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- change = c;
- ++notification_calls;
- });
- advance_and_notify(*r);
- auto write = [&](auto&& f) {
- r->begin_transaction();
- f();
- r->commit_transaction();
- advance_and_notify(*r);
- };
- SECTION("swapping rows does not send notifications") {
- write([&] {
- table->swap_rows(2, 3);
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("modifications that leave a non-matching row non-matching do not send notifications") {
- write([&] {
- table->set_int(0, 6, 13);
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("deleting non-matching rows does not send a notification") {
- write([&] {
- table->move_last_over(0);
- table->move_last_over(6);
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("modifying a matching row and leaving it matching marks that row as modified") {
- write([&] {
- table->set_int(0, 1, 3);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.modifications, 3);
- REQUIRE_INDICES(change.modifications_new, 3);
- }
- SECTION("swapping leaves modified rows marked as modified") {
- write([&] {
- table->set_int(0, 1, 3);
- table->swap_rows(1, 2);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.modifications, 3);
- REQUIRE_INDICES(change.modifications_new, 3);
- write([&] {
- table->swap_rows(3, 1);
- table->set_int(0, 1, 7);
- });
- REQUIRE(notification_calls == 3);
- REQUIRE_INDICES(change.modifications, 1);
- REQUIRE_INDICES(change.modifications_new, 1);
- }
- SECTION("modifying a matching row to no longer match marks that row as deleted") {
- write([&] {
- table->set_int(0, 2, 0);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.deletions, 2);
- }
- SECTION("modifying a non-matching row to match marks that row as inserted") {
- write([&] {
- table->set_int(0, 7, 3);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.insertions, 3);
- }
- SECTION("deleting a matching row marks that row as deleted") {
- write([&] {
- table->move_last_over(3);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.deletions, 1);
- }
- SECTION("moving a matching row via deletion does not send a notification") {
- write([&] {
- table->where().greater_equal(0, 10).find_all().clear(RemoveMode::unordered);
- table->move_last_over(0);
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("modifying a matching row to change its position sends insert+delete") {
- write([&] {
- table->set_int(0, 2, 9);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.deletions, 2);
- REQUIRE_INDICES(change.insertions, 0);
- }
- SECTION("modifications from multiple transactions are collapsed") {
- r2->begin_transaction();
- r2_table->set_int(0, 0, 5);
- r2->commit_transaction();
- r2->begin_transaction();
- r2_table->set_int(0, 1, 0);
- r2->commit_transaction();
- REQUIRE(notification_calls == 1);
- advance_and_notify(*r);
- REQUIRE(notification_calls == 2);
- }
- SECTION("moving a matching row by deleting all other rows") {
- r->begin_transaction();
- table->clear();
- table->add_empty_row(2);
- table->set_int(0, 0, 15);
- table->set_int(0, 1, 5);
- r->commit_transaction();
- advance_and_notify(*r);
- write([&] {
- table->move_last_over(0);
- table->add_empty_row();
- table->set_int(0, 1, 3);
- });
- REQUIRE(notification_calls == 3);
- REQUIRE(change.deletions.empty());
- REQUIRE_INDICES(change.insertions, 1);
- }
- }
- SECTION("distinct notifications") {
- results = results.distinct(SortDescriptor(*table, {{0}}));
- int notification_calls = 0;
- CollectionChangeSet change;
- auto token = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- change = c;
- ++notification_calls;
- });
- advance_and_notify(*r);
- auto write = [&](auto&& f) {
- r->begin_transaction();
- f();
- r->commit_transaction();
- advance_and_notify(*r);
- };
- SECTION("modifications that leave a non-matching row non-matching do not send notifications") {
- write([&] {
- table->set_int(0, 6, 13);
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("deleting non-matching rows does not send a notification") {
- write([&] {
- table->move_last_over(0);
- table->move_last_over(6);
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("modifying a matching row and leaving it matching marks that row as modified") {
- write([&] {
- table->set_int(0, 1, 3);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.modifications, 0);
- REQUIRE_INDICES(change.modifications_new, 0);
- }
- SECTION("modifying a non-matching row which is after the distinct results in the table to be a same value \
- in the distinct results doesn't send notification.") {
- write([&] {
- table->set_int(0, 6, 2);
- });
- REQUIRE(notification_calls == 1);
- }
- SECTION("modifying a non-matching row which is before the distinct results in the table to be a same value \
- in the distinct results send insert + delete.") {
- write([&] {
- table->set_int(0, 0, 2);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.deletions, 0);
- REQUIRE_INDICES(change.insertions, 0);
- }
- SECTION("modifying a matching row to duplicated value in distinct results marks that row as deleted") {
- write([&] {
- table->set_int(0, 2, 2);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.deletions, 1);
- }
- SECTION("modifying a non-matching row to match and different value marks that row as inserted") {
- write([&] {
- table->set_int(0, 0, 1);
- });
- REQUIRE(notification_calls == 2);
- REQUIRE_INDICES(change.insertions, 0);
- }
- }
- SECTION("schema changes") {
- CollectionChangeSet change;
- auto token = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- change = c;
- });
- advance_and_notify(*r);
- auto write = [&](auto&& f) {
- r->begin_transaction();
- f();
- r->commit_transaction();
- advance_and_notify(*r);
- };
- SECTION("insert table before observed table") {
- write([&] {
- size_t row = table->add_empty_row();
- table->set_int(0, row, 5);
- r->read_group().insert_table(0, "new table");
- table->insert_empty_row(0);
- table->set_int(0, 0, 5);
- });
- REQUIRE_INDICES(change.insertions, 0, 5);
- }
- auto linked_table = table->get_link_target(1);
- SECTION("insert new column before link column") {
- write([&] {
- linked_table->set_int(0, 1, 5);
- table->insert_column(0, type_Int, "new col");
- linked_table->set_int(0, 2, 5);
- });
- REQUIRE_INDICES(change.modifications, 0, 1);
- }
- SECTION("insert table before link target") {
- write([&] {
- linked_table->set_int(0, 1, 5);
- r->read_group().insert_table(0, "new table");
- linked_table->set_int(0, 2, 5);
- });
- REQUIRE_INDICES(change.modifications, 0, 1);
- }
- }
- }
- TEST_CASE("results: notifications after move") {
- InMemoryTestFile config;
- config.cache = false;
- config.automatic_change_notifications = false;
- auto r = Realm::get_shared_realm(config);
- r->update_schema({
- {"object", {
- {"value", PropertyType::Int},
- }},
- });
- auto table = r->read_group().get_table("class_object");
- auto results = std::make_unique<Results>(r, *table);
- int notification_calls = 0;
- auto token = results->add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- ++notification_calls;
- });
- advance_and_notify(*r);
- auto write = [&](auto&& f) {
- r->begin_transaction();
- f();
- r->commit_transaction();
- advance_and_notify(*r);
- };
- SECTION("notifications continue to work after Results is moved (move-constructor)") {
- Results r(std::move(*results));
- results.reset();
- write([&] {
- table->set_int(0, table->add_empty_row(), 1);
- });
- REQUIRE(notification_calls == 2);
- }
- SECTION("notifications continue to work after Results is moved (move-assignment)") {
- Results r;
- r = std::move(*results);
- results.reset();
- write([&] {
- table->set_int(0, table->add_empty_row(), 1);
- });
- REQUIRE(notification_calls == 2);
- }
- }
- TEST_CASE("results: notifier with no callbacks") {
- _impl::RealmCoordinator::assert_no_open_realms();
- InMemoryTestFile config;
- config.cache = false;
- config.automatic_change_notifications = false;
- auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path);
- auto r = coordinator->get_realm(std::move(config));
- r->update_schema({
- {"object", {
- {"value", PropertyType::Int},
- }},
- });
- auto table = r->read_group().get_table("class_object");
- Results results(r, table->where());
- results.last();
- SECTION("refresh() does not block due to implicit notifier") {
-
-
-
- results.add_notification_callback([](CollectionChangeSet const&, std::exception_ptr) {});
- auto r2 = coordinator->get_realm();
- r2->begin_transaction();
- r2->read_group().get_table("class_object")->add_empty_row();
- r2->commit_transaction();
- r->refresh();
- }
- SECTION("refresh() does not attempt to deliver stale results") {
- results.add_notification_callback([](CollectionChangeSet const&, std::exception_ptr) {});
-
- r->begin_transaction();
- table->add_empty_row();
- r->commit_transaction();
- r->begin_transaction();
-
- coordinator->on_change();
-
- table->add_empty_row();
- r->commit_transaction();
-
-
- r->refresh();
- }
- SECTION("should not pin the source version even after the Realm has been closed") {
- auto r2 = coordinator->get_realm();
- REQUIRE(r != r2);
- r->close();
- auto& shared_group = TestHelper::get_shared_group(r2);
-
-
- REQUIRE(shared_group.get_number_of_versions() == 2);
- auto table = r2->read_group().get_table("class_object");
- r2->begin_transaction();
- table->add_empty_row();
- r2->commit_transaction();
- r2->begin_transaction();
- table->add_empty_row();
- r2->commit_transaction();
-
- REQUIRE(shared_group.get_number_of_versions() == 2);
- }
- }
- TEST_CASE("results: error messages") {
- InMemoryTestFile config;
- config.schema = Schema{
- {"object", {
- {"value", PropertyType::String},
- }},
- };
- auto r = Realm::get_shared_realm(config);
- auto table = r->read_group().get_table("class_object");
- Results results(r, *table);
- r->begin_transaction();
- table->add_empty_row();
- r->commit_transaction();
- SECTION("out of bounds access") {
- REQUIRE_THROWS_WITH(results.get(5), "Requested index 5 greater than max 0");
- }
- SECTION("unsupported aggregate operation") {
- REQUIRE_THROWS_WITH(results.sum(0), "Cannot sum property 'value': operation not supported for 'string' properties");
- }
- }
- TEST_CASE("results: snapshots") {
- InMemoryTestFile config;
- config.cache = false;
- config.automatic_change_notifications = false;
- config.schema = Schema{
- {"object", {
- {"value", PropertyType::Int},
- {"array", PropertyType::Array|PropertyType::Object, "linked to object"}
- }},
- {"linked to object", {
- {"value", PropertyType::Int}
- }}
- };
- auto r = Realm::get_shared_realm(config);
- auto write = [&](auto&& f) {
- r->begin_transaction();
- f();
- r->commit_transaction();
- advance_and_notify(*r);
- };
- SECTION("snapshot of empty Results") {
- Results results;
- auto snapshot = results.snapshot();
- REQUIRE(snapshot.size() == 0);
- }
- SECTION("snapshot of Results based on Table") {
- auto table = r->read_group().get_table("class_object");
- Results results(r, *table);
- {
-
- auto snapshot = results.snapshot();
- REQUIRE(results.size() == 0);
- REQUIRE(snapshot.size() == 0);
- write([=]{
- table->add_empty_row();
- });
- REQUIRE(results.size() == 1);
- REQUIRE(snapshot.size() == 0);
- }
- {
-
-
- auto snapshot = results.snapshot();
- REQUIRE(results.size() == 1);
- REQUIRE(snapshot.size() == 1);
- write([=]{
- table->move_last_over(0);
- });
- REQUIRE(results.size() == 0);
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
-
-
- write([=]{
- table->add_empty_row();
- });
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
- }
- }
- SECTION("snapshot of Results based on LinkView") {
- auto object = r->read_group().get_table("class_object");
- auto linked_to = r->read_group().get_table("class_linked to object");
- write([=]{
- object->add_empty_row();
- });
- LinkViewRef lv = object->get_linklist(1, 0);
- Results results(r, lv);
- {
-
- auto snapshot = results.snapshot();
- REQUIRE(results.size() == 0);
- REQUIRE(snapshot.size() == 0);
- write([=]{
- lv->add(linked_to->add_empty_row());
- });
- REQUIRE(results.size() == 1);
- REQUIRE(snapshot.size() == 0);
- }
- {
-
- auto snapshot = results.snapshot();
- REQUIRE(results.size() == 1);
- REQUIRE(snapshot.size() == 1);
- write([=]{
- lv->remove(0);
- });
- REQUIRE(results.size() == 0);
- REQUIRE(snapshot.size() == 1);
- REQUIRE(snapshot.get(0).is_attached());
-
-
- write([=]{
- linked_to->remove(0);
- });
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
-
- write([=]{
- lv->add(linked_to->add_empty_row());
- });
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
- }
- }
- SECTION("snapshot of Results based on Query") {
- auto table = r->read_group().get_table("class_object");
- Query q = table->column<Int>(0) > 0;
- Results results(r, std::move(q));
- {
-
- auto snapshot = results.snapshot();
- REQUIRE(results.size() == 0);
- REQUIRE(snapshot.size() == 0);
- write([=]{
- table->set_int(0, table->add_empty_row(), 1);
- });
- REQUIRE(results.size() == 1);
- REQUIRE(snapshot.size() == 0);
- }
- {
-
- auto snapshot = results.snapshot();
- REQUIRE(results.size() == 1);
- REQUIRE(snapshot.size() == 1);
- write([=]{
- table->set_int(0, 0, 0);
- });
- REQUIRE(results.size() == 0);
- REQUIRE(snapshot.size() == 1);
- REQUIRE(snapshot.get(0).is_attached());
-
-
- write([=]{
- table->remove(0);
- });
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
-
- write([=]{
- table->set_int(0, table->add_empty_row(), 1);
- });
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
- }
- }
- SECTION("snapshot of Results based on TableView from query") {
- auto table = r->read_group().get_table("class_object");
- Query q = table->column<Int>(0) > 0;
- Results results(r, q.find_all());
- {
-
- auto snapshot = results.snapshot();
- REQUIRE(results.size() == 0);
- REQUIRE(snapshot.size() == 0);
- write([=]{
- table->set_int(0, table->add_empty_row(), 1);
- });
- REQUIRE(results.size() == 1);
- REQUIRE(snapshot.size() == 0);
- }
- {
-
- auto snapshot = results.snapshot();
- REQUIRE(results.size() == 1);
- REQUIRE(snapshot.size() == 1);
- write([=]{
- table->set_int(0, 0, 0);
- });
- REQUIRE(results.size() == 0);
- REQUIRE(snapshot.size() == 1);
- REQUIRE(snapshot.get(0).is_attached());
-
-
- write([=]{
- table->remove(0);
- });
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
-
- write([=]{
- table->set_int(0, table->add_empty_row(), 1);
- });
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
- }
- }
- SECTION("snapshot of Results based on TableView from backlinks") {
- auto object = r->read_group().get_table("class_object");
- auto linked_to = r->read_group().get_table("class_linked to object");
- write([=]{
- linked_to->add_empty_row();
- });
- TableView backlinks = linked_to->get_backlink_view(0, object.get(), 1);
- Results results(r, std::move(backlinks));
- auto lv = object->get_linklist(1, object->add_empty_row());
- {
-
- auto snapshot = results.snapshot();
- REQUIRE(results.size() == 0);
- REQUIRE(snapshot.size() == 0);
- write([=]{
- lv->add(0);
- });
- REQUIRE(results.size() == 1);
- REQUIRE(snapshot.size() == 0);
- }
- {
-
- auto snapshot = results.snapshot();
- REQUIRE(results.size() == 1);
- REQUIRE(snapshot.size() == 1);
- write([=]{
- lv->remove(0);
- });
- REQUIRE(results.size() == 0);
- REQUIRE(snapshot.size() == 1);
- REQUIRE(snapshot.get(0).is_attached());
-
-
- write([=]{
- object->remove(0);
- });
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
-
- write([=]{
- object->add_empty_row();
- auto lv = object->get_linklist(1, object->add_empty_row());
- lv->add(0);
- });
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
- }
- }
- SECTION("snapshot of Results with notification callback registered") {
- auto table = r->read_group().get_table("class_object");
- Query q = table->column<Int>(0) > 0;
- Results results(r, q.find_all());
- auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- });
- advance_and_notify(*r);
- SECTION("snapshot of lvalue") {
- auto snapshot = results.snapshot();
- write([=] {
- table->set_int(0, table->add_empty_row(), 1);
- });
- REQUIRE(snapshot.size() == 0);
- }
- SECTION("snapshot of rvalue") {
- auto snapshot = std::move(results).snapshot();
- write([=] {
- table->set_int(0, table->add_empty_row(), 1);
- });
- REQUIRE(snapshot.size() == 0);
- }
- }
- SECTION("adding notification callback to snapshot throws") {
- auto table = r->read_group().get_table("class_object");
- Query q = table->column<Int>(0) > 0;
- Results results(r, q.find_all());
- auto snapshot = results.snapshot();
- CHECK_THROWS(snapshot.add_notification_callback([](CollectionChangeSet, std::exception_ptr) {}));
- }
- SECTION("accessors should return none for detached row") {
- auto table = r->read_group().get_table("class_object");
- write([=] {
- table->add_empty_row();
- });
- Results results(r, *table);
- auto snapshot = results.snapshot();
- write([=] {;
- table->clear();
- });
- REQUIRE_FALSE(snapshot.get(0).is_attached());
- REQUIRE_FALSE(snapshot.first()->is_attached());
- REQUIRE_FALSE(snapshot.last()->is_attached());
- }
- }
- TEST_CASE("results: distinct") {
- const int N = 10;
- InMemoryTestFile config;
- config.cache = false;
- config.automatic_change_notifications = false;
- auto r = Realm::get_shared_realm(config);
- r->update_schema({
- {"object", {
- {"num1", PropertyType::Int},
- {"string", PropertyType::String},
- {"num2", PropertyType::Int},
- {"num3", PropertyType::Int}
- }},
- });
- auto table = r->read_group().get_table("class_object");
- r->begin_transaction();
- table->add_empty_row(N);
- for (int i = 0; i < N; ++i) {
- table->set_int(0, i, i % 3);
- table->set_string(1, i, util::format("Foo_%1", i % 3).c_str());
- table->set_int(2, i, N - i);
- table->set_int(3, i, i % 2);
- }
-
-
-
-
-
-
-
-
-
-
-
- r->commit_transaction();
- Results results(r, table->where());
- SECTION("Single integer property") {
- Results unique = results.distinct(SortDescriptor(results.get_tableview().get_parent(), {{0}}));
-
-
-
-
- REQUIRE(unique.size() == 3);
- REQUIRE(unique.get(0).get_int(2) == 10);
- REQUIRE(unique.get(1).get_int(2) == 9);
- REQUIRE(unique.get(2).get_int(2) == 8);
- }
- SECTION("Single integer via apply_ordering") {
- DescriptorOrdering ordering;
- ordering.append_sort(SortDescriptor(results.get_tableview().get_parent(), {{0}}));
- ordering.append_distinct(DistinctDescriptor(results.get_tableview().get_parent(), {{0}}));
- Results unique = results.apply_ordering(std::move(ordering));
-
-
-
-
- REQUIRE(unique.size() == 3);
- REQUIRE(unique.get(0).get_int(2) == 10);
- REQUIRE(unique.get(1).get_int(2) == 9);
- REQUIRE(unique.get(2).get_int(2) == 8);
- }
- SECTION("Single string property") {
- Results unique = results.distinct(SortDescriptor(results.get_tableview().get_parent(), {{1}}));
-
-
-
-
- REQUIRE(unique.size() == 3);
- REQUIRE(unique.get(0).get_int(2) == 10);
- REQUIRE(unique.get(1).get_int(2) == 9);
- REQUIRE(unique.get(2).get_int(2) == 8);
- }
- SECTION("Two integer properties combined") {
- Results unique = results.distinct(SortDescriptor(results.get_tableview().get_parent(), {{0}, {2}}));
-
- REQUIRE(unique.size() == N);
- for (int i = 0; i < N; ++i) {
- REQUIRE(unique.get(i).get_string(1) == StringData(util::format("Foo_%1", i % 3).c_str()));
- }
- }
- SECTION("String and integer combined") {
- Results unique = results.distinct(SortDescriptor(results.get_tableview().get_parent(), {{2}, {1}}));
-
- REQUIRE(unique.size() == N);
- for (int i = 0; i < N; ++i) {
- REQUIRE(unique.get(i).get_string(1) == StringData(util::format("Foo_%1", i % 3).c_str()));
- }
- }
-
- SECTION("Order after sort and distinct") {
- Results reverse = results.sort(SortDescriptor(results.get_tableview().get_parent(), {{2}}, {true}));
-
-
-
-
- REQUIRE(reverse.first()->get_int(2) == 1);
- REQUIRE(reverse.last()->get_int(2) == 10);
-
- Results unique = reverse.distinct(SortDescriptor(reverse.get_tableview().get_parent(), {{0}}));
-
-
-
-
- REQUIRE(unique.size() == 3);
- REQUIRE(unique.get(0).get_int(2) == 1);
- REQUIRE(unique.get(1).get_int(2) == 2);
- REQUIRE(unique.get(2).get_int(2) == 3);
- }
- SECTION("Order after distinct and sort") {
- Results unique = results.distinct(SortDescriptor(results.get_tableview().get_parent(), {{0}}));
-
-
-
-
- REQUIRE(unique.size() == 3);
- REQUIRE(unique.first()->get_int(2) == 10);
- REQUIRE(unique.last()->get_int(2) == 8);
-
- Results reverse = unique.sort(SortDescriptor(unique.get_tableview().get_parent(), {{2}}, {true}));
-
-
-
-
- REQUIRE(reverse.size() == 3);
- REQUIRE(reverse.get(0).get_int(2) == 8);
- REQUIRE(reverse.get(1).get_int(2) == 9);
- REQUIRE(reverse.get(2).get_int(2) == 10);
- }
- SECTION("Chaining distinct") {
- Results first = results.distinct(SortDescriptor(results.get_tableview().get_parent(), {{0}}));
- REQUIRE(first.size() == 3);
-
- Results second = first.distinct(SortDescriptor(first.get_tableview().get_parent(), {{3}}));
- REQUIRE(second.size() == 2);
- }
- SECTION("Chaining sort") {
- using cols_0_3 = std::pair<size_t, size_t>;
- Results first = results.sort(SortDescriptor(results.get_tableview().get_parent(), {{0}}));
- Results second = first.sort(SortDescriptor(first.get_tableview().get_parent(), {{3}}));
- REQUIRE(second.size() == 10);
-
-
-
- std::vector<cols_0_3> results
- = {{0, 0}, {0, 0}, {1, 0}, {2, 0}, {2, 0}, {0, 1}, {0, 1}, {1, 1}, {1, 1}, {2, 1}};
- for (size_t i = 0; i < results.size(); ++i) {
- REQUIRE(second.get(i).get_int(0) == results[i].first);
- REQUIRE(second.get(i).get_int(3) == results[i].second);
- }
- }
- SECTION("Distinct is carried over to new queries") {
- Results unique = results.distinct(SortDescriptor(results.get_tableview().get_parent(), {{0}}));
-
-
-
-
- REQUIRE(unique.size() == 3);
- Results filtered = unique.filter(Query(table->where().less(0, 2)));
-
-
-
- REQUIRE(filtered.size() == 2);
- REQUIRE(filtered.get(0).get_int(2) == 10);
- REQUIRE(filtered.get(1).get_int(2) == 9);
- }
- SECTION("Distinct will not forget previous query") {
- Results filtered = results.filter(Query(table->where().greater(2, 5)));
-
-
-
-
-
-
- REQUIRE(filtered.size() == 5);
- Results unique = filtered.distinct(SortDescriptor(filtered.get_tableview().get_parent(), {{0}}));
-
-
-
-
- REQUIRE(unique.size() == 3);
- REQUIRE(unique.get(0).get_int(2) == 10);
- REQUIRE(unique.get(1).get_int(2) == 9);
- REQUIRE(unique.get(2).get_int(2) == 8);
- Results further_filtered = unique.filter(Query(table->where().equal(2, 9)));
-
-
- REQUIRE(further_filtered.size() == 1);
- REQUIRE(further_filtered.get(0).get_int(2) == 9);
- }
- }
- TEST_CASE("results: sort") {
- InMemoryTestFile config;
- config.cache = false;
- config.schema = Schema{
- {"object", {
- {"value", PropertyType::Int},
- {"bool", PropertyType::Bool},
- {"data prop", PropertyType::Data},
- {"link", PropertyType::Object|PropertyType::Nullable, "object 2"},
- {"array", PropertyType::Object|PropertyType::Array, "object 2"},
- }},
- {"object 2", {
- {"value", PropertyType::Int},
- {"link", PropertyType::Object|PropertyType::Nullable, "object"},
- }},
- };
- auto realm = Realm::get_shared_realm(config);
- auto table = realm->read_group().get_table("class_object");
- auto table2 = realm->read_group().get_table("class_object 2");
- Results r(realm, *table);
- SECTION("invalid keypaths") {
- SECTION("empty property name") {
- REQUIRE_THROWS_WITH(r.sort({{"", true}}), "Cannot sort on key path '': missing property name.");
- REQUIRE_THROWS_WITH(r.sort({{".", true}}), "Cannot sort on key path '.': missing property name.");
- REQUIRE_THROWS_WITH(r.sort({{"link.", true}}), "Cannot sort on key path 'link.': missing property name.");
- REQUIRE_THROWS_WITH(r.sort({{".value", true}}), "Cannot sort on key path '.value': missing property name.");
- REQUIRE_THROWS_WITH(r.sort({{"link..value", true}}), "Cannot sort on key path 'link..value': missing property name.");
- }
- SECTION("bad property name") {
- REQUIRE_THROWS_WITH(r.sort({{"not a property", true}}),
- "Cannot sort on key path 'not a property': property 'object.not a property' does not exist.");
- REQUIRE_THROWS_WITH(r.sort({{"link.not a property", true}}),
- "Cannot sort on key path 'link.not a property': property 'object 2.not a property' does not exist.");
- }
- SECTION("subscript primitive") {
- REQUIRE_THROWS_WITH(r.sort({{"value.link", true}}),
- "Cannot sort on key path 'value.link': property 'object.value' of type 'int' may only be the final property in the key path.");
- }
- SECTION("end in link") {
- REQUIRE_THROWS_WITH(r.sort({{"link", true}}),
- "Cannot sort on key path 'link': property 'object.link' of type 'object' cannot be the final property in the key path.");
- REQUIRE_THROWS_WITH(r.sort({{"link.link", true}}),
- "Cannot sort on key path 'link.link': property 'object 2.link' of type 'object' cannot be the final property in the key path.");
- }
- SECTION("sort involving bad property types") {
- REQUIRE_THROWS_WITH(r.sort({{"array", true}}),
- "Cannot sort on key path 'array': property 'object.array' is of unsupported type 'array'.");
- REQUIRE_THROWS_WITH(r.sort({{"array.value", true}}),
- "Cannot sort on key path 'array.value': property 'object.array' is of unsupported type 'array'.");
- REQUIRE_THROWS_WITH(r.sort({{"link.link.array.value", true}}),
- "Cannot sort on key path 'link.link.array.value': property 'object.array' is of unsupported type 'array'.");
- REQUIRE_THROWS_WITH(r.sort({{"data prop", true}}),
- "Cannot sort on key path 'data prop': property 'object.data prop' is of unsupported type 'data'.");
- }
- }
- realm->begin_transaction();
- table->add_empty_row(4);
- table2->add_empty_row(4);
- for (int i = 0; i < 4; ++i) {
- table->set_int(0, i, (i + 2) % 4);
- table->set_bool(1, i, i % 2);
- table->set_link(3, i, 3 - i);
- table2->set_int(0, i, (i + 1) % 4);
- table2->set_link(1, i, i);
- }
- realm->commit_transaction();
-
- #define REQUIRE_ORDER(sort, ...) do { \
- std::vector<size_t> expected = {__VA_ARGS__}; \
- auto results = sort; \
- REQUIRE(results.size() == expected.size()); \
- for (size_t i = 0; i < expected.size(); ++i) \
- REQUIRE(results.get(i).get_index() == expected[i]); \
- } while (0)
- SECTION("sort on single property") {
- REQUIRE_ORDER((r.sort({{"value", true}})),
- 2, 3, 0, 1);
- REQUIRE_ORDER((r.sort({{"value", false}})),
- 1, 0, 3, 2);
- }
- SECTION("sort on two properties") {
- REQUIRE_ORDER((r.sort({{"bool", true}, {"value", true}})),
- 2, 0, 3, 1);
- REQUIRE_ORDER((r.sort({{"bool", false}, {"value", true}})),
- 3, 1, 2, 0);
- REQUIRE_ORDER((r.sort({{"bool", true}, {"value", false}})),
- 0, 2, 1, 3);
- REQUIRE_ORDER((r.sort({{"bool", false}, {"value", false}})),
- 1, 3, 0, 2);
- }
- SECTION("sort over link") {
- REQUIRE_ORDER((r.sort({{"link.value", true}})),
- 0, 3, 2, 1);
- REQUIRE_ORDER((r.sort({{"link.value", false}})),
- 1, 2, 3, 0);
- }
- SECTION("sort over two links") {
- REQUIRE_ORDER((r.sort({{"link.link.value", true}})),
- 1, 0, 3, 2);
- REQUIRE_ORDER((r.sort({{"link.link.value", false}})),
- 2, 3, 0, 1);
- }
- }
- struct ResultsFromTable {
- static Results call(std::shared_ptr<Realm> r, Table* table) {
- return Results(std::move(r), *table);
- }
- };
- struct ResultsFromQuery {
- static Results call(std::shared_ptr<Realm> r, Table* table) {
- return Results(std::move(r), table->where());
- }
- };
- struct ResultsFromTableView {
- static Results call(std::shared_ptr<Realm> r, Table* table) {
- return Results(std::move(r), table->where().find_all());
- }
- };
- struct ResultsFromLinkView {
- static Results call(std::shared_ptr<Realm> r, Table* table) {
- r->begin_transaction();
- auto link_table = r->read_group().get_table("class_linking_object");
- link_table->add_empty_row(1);
- auto link_view = link_table->get_linklist(0, 0);
- for (size_t i = 0; i < table->size(); ++i)
- link_view->add(i);
- r->commit_transaction();
- return Results(r, link_view);
- }
- };
- TEMPLATE_TEST_CASE("results: aggregate", ResultsFromTable, ResultsFromQuery, ResultsFromTableView, ResultsFromLinkView) {
- InMemoryTestFile config;
- config.cache = false;
- config.automatic_change_notifications = false;
- auto r = Realm::get_shared_realm(config);
- r->update_schema({
- {"object", {
- {"int", PropertyType::Int|PropertyType::Nullable},
- {"float", PropertyType::Float|PropertyType::Nullable},
- {"double", PropertyType::Double|PropertyType::Nullable},
- {"date", PropertyType::Date|PropertyType::Nullable},
- }},
- {"linking_object", {
- {"link", PropertyType::Array|PropertyType::Object, "object"}
- }},
- });
- auto table = r->read_group().get_table("class_object");
- SECTION("one row with null values") {
- r->begin_transaction();
- table->add_empty_row(3);
- table->set_int(0, 1, 0);
- table->set_float(1, 1, 0.f);
- table->set_double(2, 1, 0.0);
- table->set_timestamp(3, 1, Timestamp(0, 0));
- table->set_int(0, 2, 2);
- table->set_float(1, 2, 2.f);
- table->set_double(2, 2, 2.0);
- table->set_timestamp(3, 2, Timestamp(2, 0));
-
-
-
-
- r->commit_transaction();
- Results results = TestType::call(r, table.get());
- SECTION("max") {
- REQUIRE(results.max(0)->get_int() == 2);
- REQUIRE(results.max(1)->get_float() == 2.f);
- REQUIRE(results.max(2)->get_double() == 2.0);
- REQUIRE(results.max(3)->get_timestamp() == Timestamp(2, 0));
- }
- SECTION("min") {
- REQUIRE(results.min(0)->get_int() == 0);
- REQUIRE(results.min(1)->get_float() == 0.f);
- REQUIRE(results.min(2)->get_double() == 0.0);
- REQUIRE(results.min(3)->get_timestamp() == Timestamp(0, 0));
- }
- SECTION("average") {
- REQUIRE(results.average(0) == 1.0);
- REQUIRE(results.average(1) == 1.0);
- REQUIRE(results.average(2) == 1.0);
- REQUIRE_THROWS_AS(results.average(3), Results::UnsupportedColumnTypeException);
- }
- SECTION("sum") {
- REQUIRE(results.sum(0)->get_int() == 2);
- REQUIRE(results.sum(1)->get_double() == 2.0);
- REQUIRE(results.sum(2)->get_double() == 2.0);
- REQUIRE_THROWS_AS(results.sum(3), Results::UnsupportedColumnTypeException);
- }
- }
- SECTION("rows with all null values") {
- r->begin_transaction();
- table->add_empty_row(3);
-
-
-
-
- r->commit_transaction();
- Results results = TestType::call(r, table.get());
- SECTION("max") {
- REQUIRE(!results.max(0));
- REQUIRE(!results.max(1));
- REQUIRE(!results.max(2));
- REQUIRE(!results.max(3));
- }
- SECTION("min") {
- REQUIRE(!results.min(0));
- REQUIRE(!results.min(1));
- REQUIRE(!results.min(2));
- REQUIRE(!results.min(3));
- }
- SECTION("average") {
- REQUIRE(!results.average(0));
- REQUIRE(!results.average(1));
- REQUIRE(!results.average(2));
- REQUIRE_THROWS_AS(results.average(3), Results::UnsupportedColumnTypeException);
- }
- SECTION("sum") {
- REQUIRE(results.sum(0)->get_int() == 0);
- REQUIRE(results.sum(1)->get_double() == 0.0);
- REQUIRE(results.sum(2)->get_double() == 0.0);
- REQUIRE_THROWS_AS(results.sum(3), Results::UnsupportedColumnTypeException);
- }
- }
- SECTION("empty") {
- Results results = TestType::call(r, table.get());
- SECTION("max") {
- REQUIRE(!results.max(0));
- REQUIRE(!results.max(1));
- REQUIRE(!results.max(2));
- REQUIRE(!results.max(3));
- }
- SECTION("min") {
- REQUIRE(!results.min(0));
- REQUIRE(!results.min(1));
- REQUIRE(!results.min(2));
- REQUIRE(!results.min(3));
- }
- SECTION("average") {
- REQUIRE(!results.average(0));
- REQUIRE(!results.average(1));
- REQUIRE(!results.average(2));
- REQUIRE_THROWS_AS(results.average(3), Results::UnsupportedColumnTypeException);
- }
- SECTION("sum") {
- REQUIRE(results.sum(0)->get_int() == 0);
- REQUIRE(results.sum(1)->get_double() == 0.0);
- REQUIRE(results.sum(2)->get_double() == 0.0);
- REQUIRE_THROWS_AS(results.sum(3), Results::UnsupportedColumnTypeException);
- }
- }
- }
- TEST_CASE("results: set property value on all objects", "[batch_updates]") {
- InMemoryTestFile config;
- config.automatic_change_notifications = false;
- config.cache = false;
- config.schema = Schema{
- {"AllTypes", {
- {"pk", PropertyType::Int, Property::IsPrimary{true}},
- {"bool", PropertyType::Bool},
- {"int", PropertyType::Int},
- {"float", PropertyType::Float},
- {"double", PropertyType::Double},
- {"string", PropertyType::String},
- {"data", PropertyType::Data},
- {"date", PropertyType::Date},
- {"object", PropertyType::Object|PropertyType::Nullable, "AllTypes"},
- {"list", PropertyType::Array|PropertyType::Object, "AllTypes"},
- {"bool array", PropertyType::Array|PropertyType::Bool},
- {"int array", PropertyType::Array|PropertyType::Int},
- {"float array", PropertyType::Array|PropertyType::Float},
- {"double array", PropertyType::Array|PropertyType::Double},
- {"string array", PropertyType::Array|PropertyType::String},
- {"data array", PropertyType::Array|PropertyType::Data},
- {"date array", PropertyType::Array|PropertyType::Date},
- {"object array", PropertyType::Array|PropertyType::Object, "AllTypes"},
- }, {
- {"parents", PropertyType::LinkingObjects|PropertyType::Array, "AllTypes", "object"},
- }}
- };
- config.schema_version = 0;
- auto realm = Realm::get_shared_realm(config);
- auto table = realm->read_group().get_table("class_AllTypes");
- realm->begin_transaction();
- table->add_empty_row(2);
- realm->commit_transaction();
- Results r(realm, *table);
- TestContext ctx(realm);
- SECTION("non-existing property name") {
- realm->begin_transaction();
- REQUIRE_THROWS_AS(r.set_property_value(ctx, "i dont exist", util::Any(false)), Results::InvalidPropertyException);
- realm->cancel_transaction();
- }
- SECTION("readonly property") {
- realm->begin_transaction();
- REQUIRE_THROWS_AS(r.set_property_value(ctx, "parents", util::Any(false)), ReadOnlyPropertyException);
- realm->cancel_transaction();
- }
- SECTION("primarykey property") {
- realm->begin_transaction();
- REQUIRE_THROWS_AS(r.set_property_value(ctx, "pk", util::Any(1)), std::logic_error);
- realm->cancel_transaction();
- }
- SECTION("set property values removes object from Results") {
- realm->begin_transaction();
- Results results(realm, table->where().equal(2,0));
- CHECK(results.size() == 2);
- r.set_property_value(ctx, "int", util::Any(INT64_C(42)));
- CHECK(results.size() == 0);
- realm->cancel_transaction();
- }
- SECTION("set property value") {
- realm->begin_transaction();
- r.set_property_value<util::Any>(ctx, "bool", util::Any(true));
- for (size_t i = 0; i < r.size(); i++) {
- CHECK(r.get(i).get_bool(1) == true);
- }
- r.set_property_value(ctx, "int", util::Any(INT64_C(42)));
- for (size_t i = 0; i < r.size(); i++) {
- CHECK(r.get(i).get_int(2) == 42);
- }
- r.set_property_value(ctx, "float", util::Any(1.23f));
- for (size_t i = 0; i < r.size(); i++) {
- CHECK(r.get(i).get_float(3) == 1.23f);
- }
- r.set_property_value(ctx, "double", util::Any(1.234));
- for (size_t i = 0; i < r.size(); i++) {
- CHECK(r.get(i).get_double(4) == 1.234);
- }
- r.set_property_value(ctx, "string", util::Any(std::string("abc")));
- for (size_t i = 0; i < r.size(); i++) {
- CHECK(r.get(i).get_string(5) == "abc");
- }
- r.set_property_value(ctx, "data", util::Any(std::string("abc")));
- for (size_t i = 0; i < r.size(); i++) {
- CHECK(r.get(i).get_binary(6) == BinaryData("abc", 3));
- }
- util::Any timestamp = Timestamp(1, 2);
- r.set_property_value(ctx, "date", timestamp);
- for (size_t i = 0; i < r.size(); i++) {
- CHECK(r.get(i).get_timestamp(7) == any_cast<Timestamp>(timestamp));
- }
- size_t object_ndx = table->add_empty_row();
- Object linked_obj(realm, "AllTypes", object_ndx);
- r.set_property_value(ctx, "object", util::Any(linked_obj));
- for (size_t i = 0; i < r.size(); i++) {
- CHECK(r.get(i).get_link(8) == object_ndx);
- }
- size_t list_object_ndx = table->add_empty_row();
- Object list_object(realm, "AllTypes", list_object_ndx);
- r.set_property_value(ctx, "list", util::Any(AnyVector{list_object, list_object}));
- for (size_t i = 0; i < r.size(); i++) {
- auto list = r.get(i).get_linklist(9);
- CHECK(list->size() == 2);
- CHECK(list->get(0).get_index() == list_object_ndx);
- CHECK(list->get(1).get_index() == list_object_ndx);
- }
- auto check_array = [&](size_t col, auto... values) {
- size_t rows = r.size();
- for (size_t i = 0; i < rows; ++i) {
- RowExpr row = r.get(i);
- auto table = row.get_subtable(col);
- size_t j = 0;
- for (auto& value : {values...}) {
- CAPTURE(j);
- REQUIRE(j < row.get_subtable_size(col));
- REQUIRE(value == table->get<typename std::decay<decltype(value)>::type>(0, j));
- ++j;
- }
- }
- };
- r.set_property_value(ctx, "bool array", util::Any(AnyVec{true, false}));
- check_array(10, true, false);
- r.set_property_value(ctx, "int array", util::Any(AnyVec{INT64_C(5), INT64_C(6)}));
- check_array(11, INT64_C(5), INT64_C(6));
- r.set_property_value(ctx, "float array", util::Any(AnyVec{1.1f, 2.2f}));
- check_array(12, 1.1f, 2.2f);
- r.set_property_value(ctx, "double array", util::Any(AnyVec{3.3, 4.4}));
- check_array(13, 3.3, 4.4);
- r.set_property_value(ctx, "string array", util::Any(AnyVec{"a"s, "b"s, "c"s}));
- check_array(14, StringData("a"), StringData("b"), StringData("c"));
-
- r.set_property_value(ctx, "data array", util::Any(AnyVec{"d"s, "e"s, "f"s}));
- check_array(15, BinaryData("d",1), BinaryData("e",1), BinaryData("f",1));
- r.set_property_value(ctx, "date array", util::Any(AnyVec{Timestamp(10,20), Timestamp(20,30), Timestamp(30,40)}));
- check_array(16, Timestamp(10,20), Timestamp(20,30), Timestamp(30,40));
- }
- }
- TEST_CASE("results: limit", "[limit]") {
- InMemoryTestFile config;
- config.cache = false;
- config.automatic_change_notifications = false;
- config.schema = Schema{
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- auto realm = Realm::get_shared_realm(config);
- auto table = realm->read_group().get_table("class_object");
- realm->begin_transaction();
- table->add_empty_row(8);
- for (int i = 0; i < 8; ++i) {
- table->set_int(0, i, (i + 2) % 4);
- }
- realm->commit_transaction();
- Results r(realm, *table);
- SECTION("unsorted") {
- REQUIRE(r.limit(0).size() == 0);
- REQUIRE_ORDER(r.limit(1), 0);
- REQUIRE_ORDER(r.limit(2), 0, 1);
- REQUIRE_ORDER(r.limit(8), 0, 1, 2, 3, 4, 5, 6, 7);
- REQUIRE_ORDER(r.limit(100), 0, 1, 2, 3, 4, 5, 6, 7);
- }
- SECTION("sorted") {
- auto sorted = r.sort({{"value", true}});
- REQUIRE(sorted.limit(0).size() == 0);
- REQUIRE_ORDER(sorted.limit(1), 2);
- REQUIRE_ORDER(sorted.limit(2), 2, 6);
- REQUIRE_ORDER(sorted.limit(8), 2, 6, 3, 7, 0, 4, 1, 5);
- REQUIRE_ORDER(sorted.limit(100), 2, 6, 3, 7, 0, 4, 1, 5);
- }
- SECTION("sort after limit") {
- REQUIRE(r.limit(0).sort({{"value", true}}).size() == 0);
- REQUIRE_ORDER(r.limit(1).sort({{"value", true}}), 0);
- REQUIRE_ORDER(r.limit(3).sort({{"value", true}}), 2, 0, 1);
- REQUIRE_ORDER(r.limit(8).sort({{"value", true}}), 2, 6, 3, 7, 0, 4, 1, 5);
- REQUIRE_ORDER(r.limit(100).sort({{"value", true}}), 2, 6, 3, 7, 0, 4, 1, 5);
- }
- SECTION("distinct") {
- auto sorted = r.distinct({"value"});
- REQUIRE(sorted.limit(0).size() == 0);
- REQUIRE_ORDER(sorted.limit(1), 0);
- REQUIRE_ORDER(sorted.limit(2), 0, 1);
- REQUIRE_ORDER(sorted.limit(8), 0, 1, 2, 3);
- sorted = r.sort({{"value", true}}).distinct({"value"});
- REQUIRE(sorted.limit(0).size() == 0);
- REQUIRE_ORDER(sorted.limit(1), 2);
- REQUIRE_ORDER(sorted.limit(2), 2, 3);
- REQUIRE_ORDER(sorted.limit(8), 2, 3, 0, 1);
- }
- SECTION("notifications on results using all descriptor types") {
- r = r.distinct({"value"}).sort({{"value", false}}).limit(2);
- int notification_calls = 0;
- auto token = r.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- if (notification_calls == 0) {
- REQUIRE(c.empty());
- REQUIRE(r.size() == 2);
- REQUIRE(r.get(0).get_int(0) == 3);
- REQUIRE(r.get(1).get_int(0) == 2);
- } else if (notification_calls == 1) {
- REQUIRE(!c.empty());
- REQUIRE_INDICES(c.insertions, 0);
- REQUIRE_INDICES(c.deletions, 1);
- REQUIRE(c.moves.size() == 0);
- REQUIRE(c.modifications.count() == 0);
- REQUIRE(r.size() == 2);
- REQUIRE(r.get(0).get_int(0) == 5);
- REQUIRE(r.get(1).get_int(0) == 3);
- }
- ++notification_calls;
- });
- advance_and_notify(*realm);
- REQUIRE(notification_calls == 1);
- realm->begin_transaction();
- table->add_empty_row(1);
- table->set_int(0, 8, 5);
- realm->commit_transaction();
- advance_and_notify(*realm);
- REQUIRE(notification_calls == 2);
- }
- SECTION("notifications on only limited results") {
- r = r.limit(2);
- int notification_calls = 0;
- auto token = r.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) {
- REQUIRE_FALSE(err);
- if (notification_calls == 0) {
- REQUIRE(c.empty());
- REQUIRE(r.size() == 2);
- } else if (notification_calls == 1) {
- REQUIRE(!c.empty());
- REQUIRE(c.insertions.count() == 0);
- REQUIRE(c.deletions.count() == 0);
- REQUIRE(c.modifications.count() == 1);
- REQUIRE_INDICES(c.modifications, 1);
- REQUIRE(r.size() == 2);
- }
- ++notification_calls;
- });
- advance_and_notify(*realm);
- REQUIRE(notification_calls == 1);
- realm->begin_transaction();
- table->set_int(0, 1, 5);
- realm->commit_transaction();
- advance_and_notify(*realm);
- REQUIRE(notification_calls == 2);
- }
- SECTION("does not support further filtering") {
- auto limited = r.limit(0);
- REQUIRE_THROWS_AS(limited.filter(table->where()), Results::UnimplementedOperationException);
- }
- }
|