12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116 |
- ////////////////////////////////////////////////////////////////////////////
- //
- // Copyright 2016 Realm Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- ////////////////////////////////////////////////////////////////////////////
- #include "catch2/catch.hpp"
- #include "util/event_loop.hpp"
- #include "util/index_helpers.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();
- });
- // advances to the version after the one it was waiting for, but still
- // not the latest
- r->refresh();
- REQUIRE(notification_calls == 2);
- thread.join();
- REQUIRE(notification_calls == 2);
- // now advances to the latest
- 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 = {};
- // this would deadlock if it waits for the notifier to be ready
- r->refresh();
- }
- SECTION("begin_transaction() does not block for results without callbacks") {
- token = {};
- // this would deadlock if it waits for the notifier to be ready
- r->begin_transaction();
- r->cancel_transaction();
- }
- SECTION("begin_transaction() does not block for Results for different Realms") {
- // this would deadlock if beginning the write on the secondary Realm
- // waited for the primary Realm to be ready
- make_remote_change();
- // sanity check that the notifications never did run
- 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()); // would deadlock if it actually tried to refresh
- });
- advance_and_notify(*r);
- make_remote_change(); // 1
- coordinator->on_change();
- make_remote_change(); // 2
- r->notify(); // advances to version from 1
- coordinator->on_change();
- REQUIRE(r->refresh()); // advances to version from 2
- REQUIRE_FALSE(r->refresh()); // does not advance since it's now up-to-date
- }
- 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 {
- // would deadlock if it tried to send notifications as they aren't ready yet
- r->begin_transaction();
- r->cancel_transaction();
- }
- });
- advance_and_notify(*r);
- make_remote_change(); // 1
- coordinator->on_change();
- make_remote_change(); // 2
- r->notify(); // advances to version from 1
- REQUIRE(notification_calls == 2);
- coordinator->on_change();
- REQUIRE_FALSE(r->refresh()); // we made the commit locally, so no advancing here
- 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;
- // force the read version to advance by beginning a transaction
- 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(); // 1
- coordinator->on_change();
- make_remote_change(); // 2
- r->notify(); // advances to version from 1
- 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()) {
- // advances to version from 2 (and recursively calls this, hence the check above)
- realm.begin_transaction();
- realm.cancel_transaction();
- }
- }
- };
- r->m_binding_context.reset(new Context(*r));
- make_remote_change(); // 1
- coordinator->on_change();
- make_remote_change(); // 2
- r->notify(); // advances to version from 1
- }
- 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;
- // new notifier at a version before the skipped one
- 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);
- // new notifier at the skipped version
- 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();
- // new notifier at version after the skipped one
- 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);
- // would not produce a notification even if it wasn't skipped because no changes were made
- r->begin_transaction();
- token1.suppress_next();
- r->commit_transaction();
- advance_and_notify(*r);
- REQUIRE(calls1 == 1);
- // should now produce a notification
- 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);
- // Set the skip version
- make_local_change(token1);
- // Advance the file to a version after the skip version
- make_remote_change();
- REQUIRE(calls1 == 1);
- // Remove the skipped notifier and add an entirely new notifier, so that
- // notifications need to run but the skip logic shouldn't be used
- 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()
- {
- // Set the max open files to zero so that opening new files will fail
- 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) { });
- // Add an object to the Realm so that notifications are needed
- {
- 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 for the notifications to become ready for the new version
- wait_realm->refresh();
- // Start the server and wait for the Realm to be uploaded so that sync
- // makes some writes to the Realm and bumps the version
- server.start();
- wait_for_upload(*r);
- // Make sure that the notifications still get delivered rather than
- // waiting forever due to that we don't get a commit notification from
- // the commits sync makes to store the upload progress
- 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") {
- // Sort in descending order
- 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(); // force evaluation and creation of TableView
- SECTION("refresh() does not block due to implicit notifier") {
- // Create and then immediately remove a callback because
- // `automatic_change_notifications = false` makes Results not implicitly
- // create a 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(); // would deadlock if there was a callback
- }
- SECTION("refresh() does not attempt to deliver stale results") {
- results.add_notification_callback([](CollectionChangeSet const&, std::exception_ptr) {});
- // Create version 1
- r->begin_transaction();
- table->add_empty_row();
- r->commit_transaction();
- r->begin_transaction();
- // Run async query for version 1
- coordinator->on_change();
- // Create version 2 without ever letting 1 be delivered
- table->add_empty_row();
- r->commit_transaction();
- // Give it a chance to deliver the async query results (and fail, becuse
- // they're for version 1 and the realm is at 2)
- 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);
- // There's always at least 2 live versions because the previous version
- // isn't clean up until the *next* commit
- 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();
- // Would now be 3 if the closed Realm is still pinning the version it was at
- 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);
- {
- // A newly-added row should not appear in the snapshot.
- 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);
- }
- {
- // Removing a row present in the snapshot should not affect the size of the snapshot,
- // but will result in the snapshot returning a detached row accessor.
- 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());
- // Adding a row at the same index that was formerly present in the snapshot shouldn't
- // affect the state of the snapshot.
- 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);
- {
- // A newly-added row should not appear in the snapshot.
- 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);
- }
- {
- // Removing a row from the link list should not affect the snapshot.
- 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());
- // Removing a row present in the snapshot from its table should result in the snapshot
- // returning a detached row accessor.
- write([=]{
- linked_to->remove(0);
- });
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
- // Adding a new row to the link list shouldn't affect the state of the snapshot.
- 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));
- {
- // A newly-added row should not appear in the snapshot.
- 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);
- }
- {
- // Updating a row to no longer match the query criteria should not affect the snapshot.
- 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());
- // Removing a row present in the snapshot from its table should result in the snapshot
- // returning a detached row accessor.
- write([=]{
- table->remove(0);
- });
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
- // Adding a new row that matches the query criteria shouldn't affect the state of the snapshot.
- 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());
- {
- // A newly-added row should not appear in the snapshot.
- 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);
- }
- {
- // Updating a row to no longer match the query criteria should not affect the snapshot.
- 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());
- // Removing a row present in the snapshot from its table should result in the snapshot
- // returning a detached row accessor.
- write([=]{
- table->remove(0);
- });
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
- // Adding a new row that matches the query criteria shouldn't affect the state of the snapshot.
- 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());
- {
- // A newly-added row should not appear in the snapshot.
- auto snapshot = results.snapshot();
- REQUIRE(results.size() == 0);
- REQUIRE(snapshot.size() == 0);
- write([=]{
- lv->add(0);
- });
- REQUIRE(results.size() == 1);
- REQUIRE(snapshot.size() == 0);
- }
- {
- // Removing the link should not affect the snapshot.
- 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());
- // Removing a row present in the snapshot from its table should result in the snapshot
- // returning a detached row accessor.
- write([=]{
- object->remove(0);
- });
- REQUIRE(snapshot.size() == 1);
- REQUIRE(!snapshot.get(0).is_attached());
- // Adding a new link shouldn't affect the state of the snapshot.
- 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);
- }
- // table:
- // 0, Foo_0, 10, 0
- // 1, Foo_1, 9, 1
- // 2, Foo_2, 8, 0
- // 0, Foo_0, 7, 1
- // 1, Foo_1, 6, 0
- // 2, Foo_2, 5, 1
- // 0, Foo_0, 4, 0
- // 1, Foo_1, 3, 1
- // 2, Foo_2, 2, 0
- // 0, Foo_0, 1, 1
- r->commit_transaction();
- Results results(r, table->where());
- SECTION("Single integer property") {
- Results unique = results.distinct(SortDescriptor(results.get_tableview().get_parent(), {{0}}));
- // unique:
- // 0, Foo_0, 10
- // 1, Foo_1, 9
- // 2, Foo_2, 8
- 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));
- // unique:
- // 0, Foo_0, 10
- // 1, Foo_1, 9
- // 2, Foo_2, 8
- 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}}));
- // unique:
- // 0, Foo_0, 10
- // 1, Foo_1, 9
- // 2, Foo_2, 8
- 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}}));
- // unique is the same as the table
- 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}}));
- // unique is the same as the table
- 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()));
- }
- }
- // This section and next section demonstrate that sort().distinct() != distinct().sort()
- SECTION("Order after sort and distinct") {
- Results reverse = results.sort(SortDescriptor(results.get_tableview().get_parent(), {{2}}, {true}));
- // reverse:
- // 0, Foo_0, 1
- // ...
- // 0, Foo_0, 10
- REQUIRE(reverse.first()->get_int(2) == 1);
- REQUIRE(reverse.last()->get_int(2) == 10);
- // distinct() will be applied to the table, after sorting
- Results unique = reverse.distinct(SortDescriptor(reverse.get_tableview().get_parent(), {{0}}));
- // unique:
- // 0, Foo_0, 1
- // 2, Foo_2, 2
- // 1, Foo_1, 3
- 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}}));
- // unique:
- // 0, Foo_0, 10
- // 1, Foo_1, 9
- // 2, Foo_2, 8
- REQUIRE(unique.size() == 3);
- REQUIRE(unique.first()->get_int(2) == 10);
- REQUIRE(unique.last()->get_int(2) == 8);
- // sort() is only applied to unique
- Results reverse = unique.sort(SortDescriptor(unique.get_tableview().get_parent(), {{2}}, {true}));
- // reversed:
- // 2, Foo_2, 8
- // 1, Foo_1, 9
- // 0, Foo_0, 10
- 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);
- // distinct() will not discard the previous applied distinct() calls
- 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);
- // results are ordered first by the last sorted column
- // if any duplicates exist in that column, they are resolved by sorting the
- // previously sorted column. Eg. sort(a).sort(b) == sort(b, a)
- 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}}));
- // unique:
- // 0, Foo_0, 10
- // 1, Foo_1, 9
- // 2, Foo_2, 8
- REQUIRE(unique.size() == 3);
- Results filtered = unique.filter(Query(table->where().less(0, 2)));
- // filtered:
- // 0, Foo_0, 10
- // 1, Foo_1, 9
- 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)));
- // filtered:
- // 0, Foo_0, 10
- // 1, Foo_1, 9
- // 2, Foo_2, 8
- // 0, Foo_0, 7
- // 1, Foo_1, 6
- REQUIRE(filtered.size() == 5);
- Results unique = filtered.distinct(SortDescriptor(filtered.get_tableview().get_parent(), {{0}}));
- // unique:
- // 0, Foo_0, 10
- // 1, Foo_1, 9
- // 2, Foo_2, 8
- 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)));
- // further_filtered:
- // 1, Foo_1, 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();
- /*
- | index | value | bool | link.value | link.link.value |
- |-------|-------|------|------------|-----------------|
- | 0 | 2 | 0 | 0 | 1 |
- | 1 | 3 | 1 | 3 | 0 |
- | 2 | 0 | 0 | 2 | 3 |
- | 3 | 1 | 1 | 1 | 2 |
- */
- #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", "[query][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));
- // table:
- // null, null, null, null,
- // 0, 0, 0, (0, 0)
- // 2, 2, 2, (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);
- // table:
- // null, null, null, null, null
- // null, null, null, null, null
- // null, null, null, null, null
- 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);
- }
- }
|