FIRQueryTests.mm 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <FirebaseFirestore/FirebaseFirestore.h>
  17. #import <XCTest/XCTest.h>
  18. #import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
  19. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  20. #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
  21. #include "Firestore/core/test/unit/testutil/testing_hooks_util.h"
  22. namespace {
  23. NSArray<NSString *> *SortedStringsNotIn(NSSet<NSString *> *set, NSSet<NSString *> *remove) {
  24. NSMutableSet<NSString *> *mutableSet = [NSMutableSet setWithSet:set];
  25. [mutableSet minusSet:remove];
  26. return [mutableSet.allObjects sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
  27. }
  28. } // namespace
  29. @interface FIRQueryTests : FSTIntegrationTestCase
  30. @end
  31. @implementation FIRQueryTests
  32. /**
  33. * Checks that running the query while online (against the backend/emulator) results in the same
  34. * documents as running the query while offline. It also checks that both online and offline
  35. * query result is equal to the expected documents.
  36. *
  37. * @param query The query to check.
  38. * @param expectedDocs Array of document keys that are expected to match the query.
  39. */
  40. - (void)checkOnlineAndOfflineQuery:(FIRQuery *)query matchesResult:(NSArray *)expectedDocs {
  41. FIRQuerySnapshot *docsFromServer = [self readDocumentSetForRef:query
  42. source:FIRFirestoreSourceServer];
  43. FIRQuerySnapshot *docsFromCache = [self readDocumentSetForRef:query
  44. source:FIRFirestoreSourceCache];
  45. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(docsFromServer),
  46. FIRQuerySnapshotGetIDs(docsFromCache));
  47. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(docsFromCache), expectedDocs);
  48. }
  49. - (void)testLimitQueries {
  50. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  51. @"a" : @{@"k" : @"a"},
  52. @"b" : @{@"k" : @"b"},
  53. @"c" : @{@"k" : @"c"}
  54. }];
  55. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[collRef queryLimitedTo:2]];
  56. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ @{@"k" : @"a"}, @{@"k" : @"b"} ]));
  57. }
  58. - (void)testLimitQueriesWithDescendingSortOrder {
  59. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  60. @"a" : @{@"k" : @"a", @"sort" : @0},
  61. @"b" : @{@"k" : @"b", @"sort" : @1},
  62. @"c" : @{@"k" : @"c", @"sort" : @1},
  63. @"d" : @{@"k" : @"d", @"sort" : @2},
  64. }];
  65. FIRQuerySnapshot *snapshot =
  66. [self readDocumentSetForRef:[[collRef queryOrderedByField:@"sort"
  67. descending:YES] queryLimitedTo:2]];
  68. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  69. (@[ @{@"k" : @"d", @"sort" : @2}, @{@"k" : @"c", @"sort" : @1} ]));
  70. }
  71. - (void)testLimitToLastMustAlsoHaveExplicitOrderBy {
  72. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{}];
  73. FIRQuery *query = [collRef queryLimitedToLast:2];
  74. FSTAssertThrows([query getDocumentsWithCompletion:^(FIRQuerySnapshot *, NSError *){
  75. }],
  76. @"limit(toLast:) queries require specifying at least one OrderBy() clause.");
  77. }
  78. // Two queries that mapped to the same target ID are referred to as
  79. // "mirror queries". An example for a mirror query is a limitToLast()
  80. // query and a limit() query that share the same backend Target ID.
  81. // Since limitToLast() queries are sent to the backend with a modified
  82. // orderBy() clause, they can map to the same target representation as
  83. // limit() query, even if both queries appear separate to the user.
  84. - (void)testListenUnlistenRelistenSequenceOfMirrorQueries {
  85. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  86. @"a" : @{@"k" : @"a", @"sort" : @0},
  87. @"b" : @{@"k" : @"b", @"sort" : @1},
  88. @"c" : @{@"k" : @"c", @"sort" : @1},
  89. @"d" : @{@"k" : @"d", @"sort" : @2},
  90. }];
  91. // Setup a `limit` query.
  92. FIRQuery *limit = [[collRef queryOrderedByField:@"sort" descending:NO] queryLimitedTo:2];
  93. FSTEventAccumulator *limitAccumulator = [FSTEventAccumulator accumulatorForTest:self];
  94. id<FIRListenerRegistration> limitRegistration =
  95. [limit addSnapshotListener:limitAccumulator.valueEventHandler];
  96. // Setup a mirroring `limitToLast` query.
  97. FIRQuery *limitToLast = [[collRef queryOrderedByField:@"sort"
  98. descending:YES] queryLimitedToLast:2];
  99. FSTEventAccumulator *limitToLastAccumulator = [FSTEventAccumulator accumulatorForTest:self];
  100. id<FIRListenerRegistration> limitToLastRegistration =
  101. [limitToLast addSnapshotListener:limitToLastAccumulator.valueEventHandler];
  102. // Verify both queries get expected result.
  103. FIRQuerySnapshot *snapshot = [limitAccumulator awaitEventWithName:@"Snapshot"];
  104. NSArray *expected = @[ @{@"k" : @"a", @"sort" : @0}, @{@"k" : @"b", @"sort" : @1} ];
  105. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  106. snapshot = [limitToLastAccumulator awaitEventWithName:@"Snapshot"];
  107. expected = @[ @{@"k" : @"b", @"sort" : @1}, @{@"k" : @"a", @"sort" : @0} ];
  108. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  109. // Unlisten then re-listen limit query.
  110. [limitRegistration remove];
  111. [limit addSnapshotListener:[limitAccumulator valueEventHandler]];
  112. // Verify limit query still works.
  113. snapshot = [limitAccumulator awaitEventWithName:@"Snapshot"];
  114. expected = @[ @{@"k" : @"a", @"sort" : @0}, @{@"k" : @"b", @"sort" : @1} ];
  115. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  116. // Add a document that would change the result set.
  117. [self addDocumentRef:collRef data:@{@"k" : @"e", @"sort" : @-1}];
  118. // Verify both queries get expected result.
  119. snapshot = [limitAccumulator awaitEventWithName:@"Snapshot"];
  120. expected = @[ @{@"k" : @"e", @"sort" : @-1}, @{@"k" : @"a", @"sort" : @0} ];
  121. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  122. snapshot = [limitToLastAccumulator awaitEventWithName:@"Snapshot"];
  123. expected = @[ @{@"k" : @"a", @"sort" : @0}, @{@"k" : @"e", @"sort" : @-1} ];
  124. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  125. // Unlisten to limitToLast, update a doc, then relisten to limitToLast
  126. [limitToLastRegistration remove];
  127. [self updateDocumentRef:[collRef documentWithPath:@"a"] data:@{@"k" : @"a", @"sort" : @-2}];
  128. [limitToLast addSnapshotListener:[limitToLastAccumulator valueEventHandler]];
  129. // Verify both queries get expected result.
  130. snapshot = [limitAccumulator awaitEventWithName:@"Snapshot"];
  131. expected = @[ @{@"k" : @"a", @"sort" : @-2}, @{@"k" : @"e", @"sort" : @-1} ];
  132. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  133. snapshot = [limitToLastAccumulator awaitEventWithName:@"Snapshot"];
  134. expected = @[ @{@"k" : @"e", @"sort" : @-1}, @{@"k" : @"a", @"sort" : @-2} ];
  135. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  136. }
  137. - (void)testLimitToLastQueriesWithCursors {
  138. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  139. @"a" : @{@"k" : @"a", @"sort" : @0},
  140. @"b" : @{@"k" : @"b", @"sort" : @1},
  141. @"c" : @{@"k" : @"c", @"sort" : @1},
  142. @"d" : @{@"k" : @"d", @"sort" : @2},
  143. }];
  144. FIRQuerySnapshot *snapshot =
  145. [self readDocumentSetForRef:[[[collRef queryOrderedByField:@"sort"] queryLimitedToLast:3]
  146. queryEndingBeforeValues:@[ @2 ]]];
  147. XCTAssertEqualObjects(
  148. FIRQuerySnapshotGetData(snapshot), (@[
  149. @{@"k" : @"a", @"sort" : @0}, @{@"k" : @"b", @"sort" : @1}, @{@"k" : @"c", @"sort" : @1}
  150. ]));
  151. snapshot = [self readDocumentSetForRef:[[[collRef queryOrderedByField:@"sort"]
  152. queryLimitedToLast:3] queryEndingAtValues:@[ @1 ]]];
  153. XCTAssertEqualObjects(
  154. FIRQuerySnapshotGetData(snapshot), (@[
  155. @{@"k" : @"a", @"sort" : @0}, @{@"k" : @"b", @"sort" : @1}, @{@"k" : @"c", @"sort" : @1}
  156. ]));
  157. snapshot = [self readDocumentSetForRef:[[[collRef queryOrderedByField:@"sort"]
  158. queryLimitedToLast:3] queryStartingAtValues:@[ @2 ]]];
  159. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ @{@"k" : @"d", @"sort" : @2} ]));
  160. snapshot =
  161. [self readDocumentSetForRef:[[[collRef queryOrderedByField:@"sort"] queryLimitedToLast:3]
  162. queryStartingAfterValues:@[ @0 ]]];
  163. XCTAssertEqualObjects(
  164. FIRQuerySnapshotGetData(snapshot), (@[
  165. @{@"k" : @"b", @"sort" : @1}, @{@"k" : @"c", @"sort" : @1}, @{@"k" : @"d", @"sort" : @2}
  166. ]));
  167. snapshot =
  168. [self readDocumentSetForRef:[[[collRef queryOrderedByField:@"sort"] queryLimitedToLast:3]
  169. queryStartingAfterValues:@[ @-1 ]]];
  170. XCTAssertEqualObjects(
  171. FIRQuerySnapshotGetData(snapshot), (@[
  172. @{@"k" : @"b", @"sort" : @1}, @{@"k" : @"c", @"sort" : @1}, @{@"k" : @"d", @"sort" : @2}
  173. ]));
  174. }
  175. - (void)testKeyOrderIsDescendingForDescendingInequality {
  176. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  177. @"a" : @{@"foo" : @42},
  178. @"b" : @{@"foo" : @42.0},
  179. @"c" : @{@"foo" : @42},
  180. @"d" : @{@"foo" : @21},
  181. @"e" : @{@"foo" : @21.0},
  182. @"f" : @{@"foo" : @66},
  183. @"g" : @{@"foo" : @66.0},
  184. }];
  185. FIRQuerySnapshot *snapshot =
  186. [self readDocumentSetForRef:[[collRef queryWhereField:@"foo"
  187. isGreaterThan:@21] queryOrderedByField:@"foo"
  188. descending:YES]];
  189. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"g", @"f", @"c", @"b", @"a" ]));
  190. }
  191. - (void)testUnaryFilterQueries {
  192. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  193. @"a" : @{@"null" : [NSNull null], @"nan" : @(NAN)},
  194. @"b" : @{@"null" : [NSNull null], @"nan" : @0},
  195. @"c" : @{@"null" : @NO, @"nan" : @(NAN)}
  196. }];
  197. FIRQuerySnapshot *results =
  198. [self readDocumentSetForRef:[[collRef queryWhereField:@"null"
  199. isEqualTo:[NSNull null]] queryWhereField:@"nan"
  200. isEqualTo:@(NAN)]];
  201. XCTAssertEqualObjects(FIRQuerySnapshotGetData(results),
  202. (@[ @{@"null" : [NSNull null], @"nan" : @(NAN)} ]));
  203. }
  204. - (void)testQueryWithFieldPaths {
  205. FIRCollectionReference *collRef = [self
  206. collectionRefWithDocuments:@{@"a" : @{@"a" : @1}, @"b" : @{@"a" : @2}, @"c" : @{@"a" : @3}}];
  207. FIRQuery *query = [collRef queryWhereFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
  208. isLessThan:@3];
  209. query = [query queryOrderedByFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
  210. descending:YES];
  211. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:query];
  212. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"b", @"a" ]));
  213. }
  214. - (void)testQueryWithPredicate {
  215. FIRCollectionReference *collRef = [self
  216. collectionRefWithDocuments:@{@"a" : @{@"a" : @1}, @"b" : @{@"a" : @2}, @"c" : @{@"a" : @3}}];
  217. NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a < 3"];
  218. FIRQuery *query = [collRef queryFilteredUsingPredicate:predicate];
  219. query = [query queryOrderedByFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
  220. descending:YES];
  221. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:query];
  222. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"b", @"a" ]));
  223. }
  224. - (void)testFilterOnInfinity {
  225. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  226. @"a" : @{@"inf" : @(INFINITY)},
  227. @"b" : @{@"inf" : @(-INFINITY)}
  228. }];
  229. FIRQuerySnapshot *results = [self readDocumentSetForRef:[collRef queryWhereField:@"inf"
  230. isEqualTo:@(INFINITY)]];
  231. XCTAssertEqualObjects(FIRQuerySnapshotGetData(results), (@[ @{@"inf" : @(INFINITY)} ]));
  232. }
  233. - (void)testCanExplicitlySortByDocumentID {
  234. NSDictionary *testDocs = @{
  235. @"a" : @{@"key" : @"a"},
  236. @"b" : @{@"key" : @"b"},
  237. @"c" : @{@"key" : @"c"},
  238. };
  239. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  240. // Ideally this would be descending to validate it's different than
  241. // the default, but that requires an extra index
  242. FIRQuerySnapshot *docs =
  243. [self readDocumentSetForRef:[collection queryOrderedByFieldPath:[FIRFieldPath documentID]]];
  244. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs),
  245. (@[ testDocs[@"a"], testDocs[@"b"], testDocs[@"c"] ]));
  246. }
  247. - (void)testCanQueryByDocumentID {
  248. NSDictionary *testDocs = @{
  249. @"aa" : @{@"key" : @"aa"},
  250. @"ab" : @{@"key" : @"ab"},
  251. @"ba" : @{@"key" : @"ba"},
  252. @"bb" : @{@"key" : @"bb"},
  253. };
  254. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  255. FIRQuerySnapshot *docs =
  256. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  257. isEqualTo:@"ab"]];
  258. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"] ]));
  259. }
  260. - (void)testCanQueryByDocumentIDs {
  261. NSDictionary *testDocs = @{
  262. @"aa" : @{@"key" : @"aa"},
  263. @"ab" : @{@"key" : @"ab"},
  264. @"ba" : @{@"key" : @"ba"},
  265. @"bb" : @{@"key" : @"bb"},
  266. };
  267. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  268. FIRQuerySnapshot *docs =
  269. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  270. isEqualTo:@"ab"]];
  271. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"] ]));
  272. docs = [self readDocumentSetForRef:[[collection queryWhereFieldPath:[FIRFieldPath documentID]
  273. isGreaterThan:@"aa"]
  274. queryWhereFieldPath:[FIRFieldPath documentID]
  275. isLessThanOrEqualTo:@"ba"]];
  276. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"], testDocs[@"ba"] ]));
  277. }
  278. - (void)testCanQueryByDocumentIDsUsingRefs {
  279. NSDictionary *testDocs = @{
  280. @"aa" : @{@"key" : @"aa"},
  281. @"ab" : @{@"key" : @"ab"},
  282. @"ba" : @{@"key" : @"ba"},
  283. @"bb" : @{@"key" : @"bb"},
  284. };
  285. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  286. FIRQuerySnapshot *docs = [self
  287. readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  288. isEqualTo:[collection documentWithPath:@"ab"]]];
  289. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"] ]));
  290. docs = [self
  291. readDocumentSetForRef:[[collection queryWhereFieldPath:[FIRFieldPath documentID]
  292. isGreaterThan:[collection documentWithPath:@"aa"]]
  293. queryWhereFieldPath:[FIRFieldPath documentID]
  294. isLessThanOrEqualTo:[collection documentWithPath:@"ba"]]];
  295. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"], testDocs[@"ba"] ]));
  296. }
  297. - (void)testWatchSurvivesNetworkDisconnect {
  298. XCTestExpectation *testExpectiation =
  299. [self expectationWithDescription:@"testWatchSurvivesNetworkDisconnect"];
  300. FIRCollectionReference *collectionRef = [self collectionRef];
  301. FIRDocumentReference *docRef = [collectionRef documentWithAutoID];
  302. FIRFirestore *firestore = collectionRef.firestore;
  303. [collectionRef
  304. addSnapshotListenerWithIncludeMetadataChanges:YES
  305. listener:^(FIRQuerySnapshot *snapshot, NSError *error) {
  306. XCTAssertNil(error);
  307. if (!snapshot.empty && !snapshot.metadata.fromCache) {
  308. [testExpectiation fulfill];
  309. }
  310. }];
  311. [firestore disableNetworkWithCompletion:^(NSError *error) {
  312. XCTAssertNil(error);
  313. [docRef setData:@{@"foo" : @"bar"}];
  314. [firestore enableNetworkWithCompletion:^(NSError *error) {
  315. XCTAssertNil(error);
  316. }];
  317. }];
  318. [self awaitExpectations];
  319. }
  320. - (void)testQueriesFireFromCacheWhenOffline {
  321. NSDictionary *testDocs = @{
  322. @"a" : @{@"foo" : @1},
  323. };
  324. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  325. id<FIRListenerRegistration> registration = [collection
  326. addSnapshotListenerWithIncludeMetadataChanges:YES
  327. listener:self.eventAccumulator.valueEventHandler];
  328. FIRQuerySnapshot *querySnap = [self.eventAccumulator awaitEventWithName:@"initial event"];
  329. XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnap), @[ @{@"foo" : @1} ]);
  330. XCTAssertEqual(querySnap.metadata.isFromCache, NO);
  331. [self disableNetwork];
  332. querySnap = [self.eventAccumulator awaitEventWithName:@"offline event with isFromCache=YES"];
  333. XCTAssertEqual(querySnap.metadata.isFromCache, YES);
  334. [self enableNetwork];
  335. querySnap = [self.eventAccumulator awaitEventWithName:@"back online event with isFromCache=NO"];
  336. XCTAssertEqual(querySnap.metadata.isFromCache, NO);
  337. [registration remove];
  338. }
  339. - (void)testQueriesCanRaiseInitialSnapshotFromCachedEmptyResults {
  340. FIRCollectionReference *collection = [self collectionRefWithDocuments:@{}];
  341. // Populate the cache with empty query result.
  342. FIRQuerySnapshot *querySnapshotA = [self readDocumentSetForRef:collection];
  343. XCTAssertFalse(querySnapshotA.metadata.fromCache);
  344. XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnapshotA), @[]);
  345. // Add a snapshot listener whose first event should be raised from cache.
  346. id<FIRListenerRegistration> registration = [collection
  347. addSnapshotListenerWithIncludeMetadataChanges:YES
  348. listener:self.eventAccumulator.valueEventHandler];
  349. FIRQuerySnapshot *querySnapshotB = [self.eventAccumulator awaitEventWithName:@"initial event"];
  350. XCTAssertTrue(querySnapshotB.metadata.fromCache);
  351. XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnapshotB), @[]);
  352. [registration remove];
  353. }
  354. - (void)testQueriesCanRaiseInitialSnapshotFromEmptyDueToDeleteCachedResults {
  355. NSDictionary *testDocs = @{
  356. @"a" : @{@"foo" : @1},
  357. };
  358. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  359. // Populate the cache with a single document.
  360. FIRQuerySnapshot *querySnapshotA = [self readDocumentSetForRef:collection];
  361. XCTAssertFalse(querySnapshotA.metadata.fromCache);
  362. XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnapshotA), @[ @{@"foo" : @1} ]);
  363. // Delete the document, making the cached query result empty.
  364. FIRDocumentReference *docRef = [collection documentWithPath:@"a"];
  365. [self deleteDocumentRef:docRef];
  366. // Add a snapshot listener whose first event should be raised from cache.
  367. id<FIRListenerRegistration> registration = [collection
  368. addSnapshotListenerWithIncludeMetadataChanges:YES
  369. listener:self.eventAccumulator.valueEventHandler];
  370. FIRQuerySnapshot *querySnapshotB = [self.eventAccumulator awaitEventWithName:@"initial event"];
  371. XCTAssertTrue(querySnapshotB.metadata.fromCache);
  372. XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnapshotB), @[]);
  373. [registration remove];
  374. }
  375. - (void)testDocumentChangesUseNSNotFound {
  376. NSDictionary *testDocs = @{
  377. @"a" : @{@"foo" : @1},
  378. };
  379. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  380. id<FIRListenerRegistration> registration =
  381. [collection addSnapshotListener:self.eventAccumulator.valueEventHandler];
  382. FIRQuerySnapshot *querySnap = [self.eventAccumulator awaitEventWithName:@"initial event"];
  383. XCTAssertEqual(querySnap.documentChanges.count, 1ul);
  384. FIRDocumentChange *change = querySnap.documentChanges[0];
  385. XCTAssertEqual(change.oldIndex, NSNotFound);
  386. XCTAssertEqual(change.newIndex, 0ul);
  387. FIRDocumentReference *doc = change.document.reference;
  388. [self deleteDocumentRef:doc];
  389. querySnap = [self.eventAccumulator awaitEventWithName:@"delete"];
  390. XCTAssertEqual(querySnap.documentChanges.count, 1ul);
  391. change = querySnap.documentChanges[0];
  392. XCTAssertEqual(change.oldIndex, 0ul);
  393. XCTAssertEqual(change.newIndex, NSNotFound);
  394. [registration remove];
  395. }
  396. - (void)testCanHaveMultipleMutationsWhileOffline {
  397. FIRCollectionReference *col = [self collectionRef];
  398. // set a few docs to known values
  399. NSDictionary *initialDocs = @{@"doc1" : @{@"key1" : @"value1"}, @"doc2" : @{@"key2" : @"value2"}};
  400. [self writeAllDocuments:initialDocs toCollection:col];
  401. // go offline for the rest of this test
  402. [self disableNetwork];
  403. // apply *multiple* mutations while offline
  404. [[col documentWithPath:@"doc1"] setData:@{@"key1b" : @"value1b"}];
  405. [[col documentWithPath:@"doc2"] setData:@{@"key2b" : @"value2b"}];
  406. FIRQuerySnapshot *result = [self readDocumentSetForRef:col];
  407. XCTAssertEqualObjects(FIRQuerySnapshotGetData(result), (@[
  408. @{@"key1b" : @"value1b"},
  409. @{@"key2b" : @"value2b"},
  410. ]));
  411. }
  412. - (void)testQueriesCanUseNotEqualFilters {
  413. // These documents are ordered by value in "zip" since notEquals filter is an inequality, which
  414. // results in documents being sorted by value.
  415. NSDictionary *testDocs = @{
  416. @"a" : @{@"zip" : @(NAN)},
  417. @"b" : @{@"zip" : @91102},
  418. @"c" : @{@"zip" : @98101},
  419. @"d" : @{@"zip" : @98103},
  420. @"e" : @{@"zip" : @[ @98101 ]},
  421. @"f" : @{@"zip" : @[ @98101, @98102 ]},
  422. @"g" : @{@"zip" : @[ @"98101", @{@"zip" : @98101} ]},
  423. @"h" : @{@"zip" : @{@"code" : @500}},
  424. @"i" : @{@"zip" : [NSNull null]},
  425. @"j" : @{@"code" : @500},
  426. };
  427. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  428. // Search for zips not matching 98101.
  429. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  430. isNotEqualTo:@98101]];
  431. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  432. testDocs[@"a"], testDocs[@"b"], testDocs[@"d"], testDocs[@"e"],
  433. testDocs[@"f"], testDocs[@"g"], testDocs[@"h"]
  434. ]));
  435. // With objects.
  436. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  437. isNotEqualTo:@{@"code" : @500}]];
  438. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  439. testDocs[@"a"], testDocs[@"b"], testDocs[@"c"], testDocs[@"d"],
  440. testDocs[@"e"], testDocs[@"f"], testDocs[@"g"]
  441. ]));
  442. // With null.
  443. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  444. isNotEqualTo:@[ [NSNull null] ]]];
  445. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  446. testDocs[@"a"], testDocs[@"b"], testDocs[@"c"], testDocs[@"d"],
  447. testDocs[@"e"], testDocs[@"f"], testDocs[@"g"], testDocs[@"h"]
  448. ]));
  449. // With NAN.
  450. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip" isNotEqualTo:@(NAN)]];
  451. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  452. testDocs[@"b"], testDocs[@"c"], testDocs[@"d"], testDocs[@"e"],
  453. testDocs[@"f"], testDocs[@"g"], testDocs[@"h"]
  454. ]));
  455. }
  456. - (void)testQueriesCanUseNotEqualFiltersWithDocIds {
  457. NSDictionary *testDocs = @{
  458. @"aa" : @{@"key" : @"aa"},
  459. @"ab" : @{@"key" : @"ab"},
  460. @"ba" : @{@"key" : @"ba"},
  461. @"bb" : @{@"key" : @"bb"},
  462. };
  463. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  464. FIRQuerySnapshot *snapshot =
  465. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  466. isNotEqualTo:@"aa"]];
  467. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  468. (@[ testDocs[@"ab"], testDocs[@"ba"], testDocs[@"bb"] ]));
  469. }
  470. - (void)testQueriesCanUseArrayContainsFilters {
  471. NSDictionary *testDocs = @{
  472. @"a" : @{@"array" : @[ @42 ]},
  473. @"b" : @{@"array" : @[ @"a", @42, @"c" ]},
  474. @"c" : @{@"array" : @[ @41.999, @"42", @{@"a" : @[ @42 ]} ]},
  475. @"d" : @{@"array" : @[ @42 ], @"array2" : @[ @"bingo" ]},
  476. @"e" : @{@"array" : @[ [NSNull null] ]},
  477. @"f" : @{@"array" : @[ @(NAN) ]},
  478. };
  479. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  480. // Search for 42
  481. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  482. arrayContains:@42]];
  483. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  484. (@[ testDocs[@"a"], testDocs[@"b"], testDocs[@"d"] ]));
  485. // With null.
  486. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  487. arrayContains:[NSNull null]]];
  488. XCTAssertTrue(snapshot.isEmpty);
  489. // With NAN.
  490. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  491. arrayContains:@(NAN)]];
  492. XCTAssertTrue(snapshot.isEmpty);
  493. }
  494. - (void)testQueriesCanUseInFilters {
  495. NSDictionary *testDocs = @{
  496. @"a" : @{@"zip" : @98101},
  497. @"b" : @{@"zip" : @91102},
  498. @"c" : @{@"zip" : @98103},
  499. @"d" : @{@"zip" : @[ @98101 ]},
  500. @"e" : @{@"zip" : @[ @"98101", @{@"zip" : @98101} ]},
  501. @"f" : @{@"zip" : @{@"code" : @500}},
  502. @"g" : @{@"zip" : @[ @98101, @98102 ]},
  503. @"h" : @{@"zip" : [NSNull null]},
  504. @"i" : @{@"zip" : @(NAN)}
  505. };
  506. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  507. // Search for zips matching 98101, 98103, and [98101, 98102].
  508. FIRQuerySnapshot *snapshot = [self
  509. readDocumentSetForRef:[collection queryWhereField:@"zip"
  510. in:@[ @98101, @98103, @[ @98101, @98102 ] ]]];
  511. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  512. (@[ testDocs[@"a"], testDocs[@"c"], testDocs[@"g"] ]));
  513. // With objects
  514. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  515. in:@[ @{@"code" : @500} ]]];
  516. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"f"] ]));
  517. // With null.
  518. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip" in:@[ [NSNull null] ]]];
  519. XCTAssertTrue(snapshot.isEmpty);
  520. // With null and a value.
  521. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  522. in:@[ [NSNull null], @98101 ]]];
  523. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"a"] ]));
  524. // With NAN.
  525. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip" in:@[ @(NAN) ]]];
  526. XCTAssertTrue(snapshot.isEmpty);
  527. // With NAN and a value.
  528. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  529. in:@[ @(NAN), @98101 ]]];
  530. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"a"] ]));
  531. }
  532. - (void)testQueriesCanUseInFiltersWithDocIds {
  533. NSDictionary *testDocs = @{
  534. @"aa" : @{@"key" : @"aa"},
  535. @"ab" : @{@"key" : @"ab"},
  536. @"ba" : @{@"key" : @"ba"},
  537. @"bb" : @{@"key" : @"bb"},
  538. };
  539. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  540. FIRQuerySnapshot *snapshot =
  541. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  542. in:@[ @"aa", @"ab" ]]];
  543. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"aa"], testDocs[@"ab"] ]));
  544. }
  545. - (void)testQueriesCanUseNotInFilters {
  546. // These documents are ordered by value in "zip" since the NOT_IN filter is an inequality, which
  547. // results in documents being sorted by value.
  548. NSDictionary *testDocs = @{
  549. @"a" : @{@"zip" : @(NAN)},
  550. @"b" : @{@"zip" : @91102},
  551. @"c" : @{@"zip" : @98101},
  552. @"d" : @{@"zip" : @98103},
  553. @"e" : @{@"zip" : @[ @98101 ]},
  554. @"f" : @{@"zip" : @[ @98101, @98102 ]},
  555. @"g" : @{@"zip" : @[ @"98101", @{@"zip" : @98101} ]},
  556. @"h" : @{@"zip" : @{@"code" : @500}},
  557. @"i" : @{@"zip" : [NSNull null]},
  558. @"j" : @{@"code" : @500},
  559. };
  560. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  561. // Search for zips not matching 98101, 98103, and [98101, 98102].
  562. FIRQuerySnapshot *snapshot = [self
  563. readDocumentSetForRef:[collection queryWhereField:@"zip"
  564. notIn:@[ @98101, @98103, @[ @98101, @98102 ] ]]];
  565. XCTAssertEqualObjects(
  566. FIRQuerySnapshotGetData(snapshot),
  567. (@[ testDocs[@"a"], testDocs[@"b"], testDocs[@"e"], testDocs[@"g"], testDocs[@"h"] ]));
  568. // With objects.
  569. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  570. notIn:@[ @{@"code" : @500} ]]];
  571. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  572. testDocs[@"a"], testDocs[@"b"], testDocs[@"c"], testDocs[@"d"],
  573. testDocs[@"e"], testDocs[@"f"], testDocs[@"g"]
  574. ]));
  575. // With null.
  576. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  577. notIn:@[ [NSNull null] ]]];
  578. XCTAssertTrue(snapshot.isEmpty);
  579. // With NAN.
  580. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip" notIn:@[ @(NAN) ]]];
  581. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  582. testDocs[@"b"], testDocs[@"c"], testDocs[@"d"], testDocs[@"e"],
  583. testDocs[@"f"], testDocs[@"g"], testDocs[@"h"]
  584. ]));
  585. // With NAN and a number.
  586. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  587. notIn:@[ @(NAN), @98101 ]]];
  588. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  589. testDocs[@"b"], testDocs[@"d"], testDocs[@"e"], testDocs[@"f"],
  590. testDocs[@"g"], testDocs[@"h"]
  591. ]));
  592. }
  593. - (void)testQueriesCanUseNotInFiltersWithDocIds {
  594. NSDictionary *testDocs = @{
  595. @"aa" : @{@"key" : @"aa"},
  596. @"ab" : @{@"key" : @"ab"},
  597. @"ba" : @{@"key" : @"ba"},
  598. @"bb" : @{@"key" : @"bb"},
  599. };
  600. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  601. FIRQuerySnapshot *snapshot =
  602. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  603. notIn:@[ @"aa", @"ab" ]]];
  604. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"ba"], testDocs[@"bb"] ]));
  605. }
  606. - (void)testQueriesCanUseArrayContainsAnyFilters {
  607. NSDictionary *testDocs = @{
  608. @"a" : @{@"array" : @[ @42 ]},
  609. @"b" : @{@"array" : @[ @"a", @42, @"c" ]},
  610. @"c" : @{@"array" : @[ @41.999, @"42", @{@"a" : @[ @42 ]} ]},
  611. @"d" : @{@"array" : @[ @42 ], @"array2" : @[ @"bingo" ]},
  612. @"e" : @{@"array" : @[ @43 ]},
  613. @"f" : @{@"array" : @[ @{@"a" : @42} ]},
  614. @"g" : @{@"array" : @42},
  615. @"h" : @{@"array" : @[ [NSNull null] ]},
  616. @"i" : @{@"array" : @[ @(NAN) ]},
  617. };
  618. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  619. // Search for zips matching [42, 43].
  620. FIRQuerySnapshot *snapshot = [self
  621. readDocumentSetForRef:[collection queryWhereField:@"array" arrayContainsAny:@[ @42, @43 ]]];
  622. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  623. (@[ testDocs[@"a"], testDocs[@"b"], testDocs[@"d"], testDocs[@"e"] ]));
  624. // With objects.
  625. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  626. arrayContainsAny:@[ @{@"a" : @42} ]]];
  627. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  628. testDocs[@"f"],
  629. ]));
  630. // With null.
  631. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  632. arrayContainsAny:@[ [NSNull null] ]]];
  633. XCTAssertTrue(snapshot.isEmpty);
  634. // With null and a value.
  635. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  636. arrayContainsAny:@[ [NSNull null], @43 ]]];
  637. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"e"] ]));
  638. // With NAN.
  639. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  640. arrayContainsAny:@[ @(NAN) ]]];
  641. XCTAssertTrue(snapshot.isEmpty);
  642. // With NAN and a value.
  643. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  644. arrayContainsAny:@[ @(NAN), @43 ]]];
  645. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"e"] ]));
  646. }
  647. - (void)testCollectionGroupQueries {
  648. // Use .document() to get a random collection group name to use but ensure it starts with 'b'
  649. // for predictable ordering.
  650. NSString *collectionGroup = [NSString
  651. stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID];
  652. NSArray *docPaths = @[
  653. @"abc/123/${collectionGroup}/cg-doc1", @"abc/123/${collectionGroup}/cg-doc2",
  654. @"${collectionGroup}/cg-doc3", @"${collectionGroup}/cg-doc4",
  655. @"def/456/${collectionGroup}/cg-doc5", @"${collectionGroup}/virtual-doc/nested-coll/not-cg-doc",
  656. @"x${collectionGroup}/not-cg-doc", @"${collectionGroup}x/not-cg-doc",
  657. @"abc/123/${collectionGroup}x/not-cg-doc", @"abc/123/x${collectionGroup}/not-cg-doc",
  658. @"abc/${collectionGroup}"
  659. ];
  660. FIRWriteBatch *batch = [self.db batch];
  661. for (NSString *docPath in docPaths) {
  662. NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}"
  663. withString:collectionGroup];
  664. [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]];
  665. }
  666. XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"];
  667. [batch commitWithCompletion:^(NSError *error) {
  668. XCTAssertNil(error);
  669. [expectation fulfill];
  670. }];
  671. [self awaitExpectations];
  672. FIRQuerySnapshot *querySnapshot =
  673. [self readDocumentSetForRef:[self.db collectionGroupWithID:collectionGroup]];
  674. NSArray<NSString *> *ids = FIRQuerySnapshotGetIDs(querySnapshot);
  675. XCTAssertEqualObjects(ids, (@[ @"cg-doc1", @"cg-doc2", @"cg-doc3", @"cg-doc4", @"cg-doc5" ]));
  676. }
  677. - (void)testCollectionGroupQueriesWithStartAtEndAtWithArbitraryDocumentIDs {
  678. // Use .document() to get a random collection group name to use but ensure it starts with 'b'
  679. // for predictable ordering.
  680. NSString *collectionGroup = [NSString
  681. stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID];
  682. NSArray *docPaths = @[
  683. @"a/a/${collectionGroup}/cg-doc1", @"a/b/a/b/${collectionGroup}/cg-doc2",
  684. @"a/b/${collectionGroup}/cg-doc3", @"a/b/c/d/${collectionGroup}/cg-doc4",
  685. @"a/c/${collectionGroup}/cg-doc5", @"${collectionGroup}/cg-doc6", @"a/b/nope/nope"
  686. ];
  687. FIRWriteBatch *batch = [self.db batch];
  688. for (NSString *docPath in docPaths) {
  689. NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}"
  690. withString:collectionGroup];
  691. [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]];
  692. }
  693. XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"];
  694. [batch commitWithCompletion:^(NSError *error) {
  695. XCTAssertNil(error);
  696. [expectation fulfill];
  697. }];
  698. [self awaitExpectations];
  699. FIRQuerySnapshot *querySnapshot = [self
  700. readDocumentSetForRef:[[[[self.db collectionGroupWithID:collectionGroup]
  701. queryOrderedByFieldPath:[FIRFieldPath documentID]]
  702. queryStartingAfterValues:@[ @"a/b" ]]
  703. queryEndingBeforeValues:@[
  704. [NSString stringWithFormat:@"a/b/%@/cg-doc3", collectionGroup]
  705. ]]];
  706. NSArray<NSString *> *ids = FIRQuerySnapshotGetIDs(querySnapshot);
  707. XCTAssertEqualObjects(ids, (@[ @"cg-doc2" ]));
  708. }
  709. - (void)testCollectionGroupQueriesWithWhereFiltersOnArbitraryDocumentIDs {
  710. // Use .document() to get a random collection group name to use but ensure it starts with 'b'
  711. // for predictable ordering.
  712. NSString *collectionGroup = [NSString
  713. stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID];
  714. NSArray *docPaths = @[
  715. @"a/a/${collectionGroup}/cg-doc1", @"a/b/a/b/${collectionGroup}/cg-doc2",
  716. @"a/b/${collectionGroup}/cg-doc3", @"a/b/c/d/${collectionGroup}/cg-doc4",
  717. @"a/c/${collectionGroup}/cg-doc5", @"${collectionGroup}/cg-doc6", @"a/b/nope/nope"
  718. ];
  719. FIRWriteBatch *batch = [self.db batch];
  720. for (NSString *docPath in docPaths) {
  721. NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}"
  722. withString:collectionGroup];
  723. [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]];
  724. }
  725. XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"];
  726. [batch commitWithCompletion:^(NSError *error) {
  727. XCTAssertNil(error);
  728. [expectation fulfill];
  729. }];
  730. [self awaitExpectations];
  731. FIRQuerySnapshot *querySnapshot = [self
  732. readDocumentSetForRef:[[[self.db collectionGroupWithID:collectionGroup]
  733. queryWhereFieldPath:[FIRFieldPath documentID]
  734. isGreaterThanOrEqualTo:@"a/b"]
  735. queryWhereFieldPath:[FIRFieldPath documentID]
  736. isLessThan:[NSString stringWithFormat:@"a/b/%@/cg-doc3",
  737. collectionGroup]]];
  738. NSArray<NSString *> *ids = FIRQuerySnapshotGetIDs(querySnapshot);
  739. XCTAssertEqualObjects(ids, (@[ @"cg-doc2" ]));
  740. }
  741. - (void)testOrQueries {
  742. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  743. @"doc1" : @{@"a" : @1, @"b" : @0},
  744. @"doc2" : @{@"a" : @2, @"b" : @1},
  745. @"doc3" : @{@"a" : @3, @"b" : @2},
  746. @"doc4" : @{@"a" : @1, @"b" : @3},
  747. @"doc5" : @{@"a" : @1, @"b" : @1}
  748. }];
  749. // Two equalities: a==1 || b==1.
  750. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  751. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter filterWhereField:@"b" isEqualTo:@1]
  752. ]];
  753. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  754. matchesResult:@[ @"doc1", @"doc2", @"doc4", @"doc5" ]];
  755. // (a==1 && b==0) || (a==3 && b==2)
  756. FIRFilter *filter2 = [FIRFilter orFilterWithFilters:@[
  757. [FIRFilter andFilterWithFilters:@[
  758. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter filterWhereField:@"b" isEqualTo:@0]
  759. ]],
  760. [FIRFilter andFilterWithFilters:@[
  761. [FIRFilter filterWhereField:@"a" isEqualTo:@3], [FIRFilter filterWhereField:@"b" isEqualTo:@2]
  762. ]]
  763. ]];
  764. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2]
  765. matchesResult:@[ @"doc1", @"doc3" ]];
  766. // a==1 && (b==0 || b==3).
  767. FIRFilter *filter3 = [FIRFilter andFilterWithFilters:@[
  768. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter orFilterWithFilters:@[
  769. [FIRFilter filterWhereField:@"b" isEqualTo:@0], [FIRFilter filterWhereField:@"b" isEqualTo:@3]
  770. ]]
  771. ]];
  772. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter3]
  773. matchesResult:@[ @"doc1", @"doc4" ]];
  774. // (a==2 || b==2) && (a==3 || b==3)
  775. FIRFilter *filter4 = [FIRFilter andFilterWithFilters:@[
  776. [FIRFilter orFilterWithFilters:@[
  777. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" isEqualTo:@2]
  778. ]],
  779. [FIRFilter orFilterWithFilters:@[
  780. [FIRFilter filterWhereField:@"a" isEqualTo:@3], [FIRFilter filterWhereField:@"b" isEqualTo:@3]
  781. ]]
  782. ]];
  783. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter4] matchesResult:@[ @"doc3" ]];
  784. // Test with limits without orderBy (the __name__ ordering is the tie breaker).
  785. FIRFilter *filter5 = [FIRFilter orFilterWithFilters:@[
  786. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" isEqualTo:@1]
  787. ]];
  788. [self checkOnlineAndOfflineQuery:[[collRef queryWhereFilter:filter5] queryLimitedTo:1]
  789. matchesResult:@[ @"doc2" ]];
  790. }
  791. - (void)testOrQueriesWithCompositeIndexes {
  792. // TODO(orquery): Enable this test against production when possible.
  793. XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator],
  794. "Skip this test if running against production because order-by-equality is not "
  795. "supported yet.");
  796. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  797. @"doc1" : @{@"a" : @1, @"b" : @0},
  798. @"doc2" : @{@"a" : @2, @"b" : @1},
  799. @"doc3" : @{@"a" : @3, @"b" : @2},
  800. @"doc4" : @{@"a" : @1, @"b" : @3},
  801. @"doc5" : @{@"a" : @1, @"b" : @1}
  802. }];
  803. // with one inequality: a>2 || b==1.
  804. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  805. [FIRFilter filterWhereField:@"a" isGreaterThan:@2], [FIRFilter filterWhereField:@"b"
  806. isEqualTo:@1]
  807. ]];
  808. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  809. matchesResult:@[ @"doc5", @"doc2", @"doc3" ]];
  810. // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2
  811. FIRFilter *filter2 = [FIRFilter orFilterWithFilters:@[
  812. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter filterWhereField:@"b"
  813. isGreaterThan:@0]
  814. ]];
  815. [self checkOnlineAndOfflineQuery:[[collRef queryWhereFilter:filter2] queryLimitedTo:2]
  816. matchesResult:@[ @"doc1", @"doc2" ]];
  817. // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2
  818. // Note: The public query API does not allow implicit ordering when limitToLast is used.
  819. FIRFilter *filter3 = [FIRFilter orFilterWithFilters:@[
  820. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter filterWhereField:@"b"
  821. isGreaterThan:@0]
  822. ]];
  823. [self checkOnlineAndOfflineQuery:[[[collRef queryWhereFilter:filter3] queryLimitedToLast:2]
  824. queryOrderedByField:@"b"]
  825. matchesResult:@[ @"doc3", @"doc4" ]];
  826. // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1
  827. FIRFilter *filter4 = [FIRFilter orFilterWithFilters:@[
  828. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" isEqualTo:@1]
  829. ]];
  830. [self checkOnlineAndOfflineQuery:[[[collRef queryWhereFilter:filter4] queryLimitedTo:1]
  831. queryOrderedByField:@"a"]
  832. matchesResult:@[ @"doc5" ]];
  833. // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT_TO_LAST 1
  834. FIRFilter *filter5 = [FIRFilter orFilterWithFilters:@[
  835. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" isEqualTo:@1]
  836. ]];
  837. [self checkOnlineAndOfflineQuery:[[[collRef queryWhereFilter:filter5] queryLimitedToLast:1]
  838. queryOrderedByField:@"a"]
  839. matchesResult:@[ @"doc2" ]];
  840. }
  841. - (void)testOrQueriesWithIn {
  842. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  843. @"doc1" : @{@"a" : @1, @"b" : @0},
  844. @"doc2" : @{@"b" : @1},
  845. @"doc3" : @{@"a" : @3, @"b" : @2},
  846. @"doc4" : @{@"a" : @1, @"b" : @3},
  847. @"doc5" : @{@"a" : @1},
  848. @"doc6" : @{@"a" : @2}
  849. }];
  850. // a==2 || b in [2,3]
  851. FIRFilter *filter = [FIRFilter orFilterWithFilters:@[
  852. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" in:@[ @2, @3 ]]
  853. ]];
  854. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter]
  855. matchesResult:@[ @"doc3", @"doc4", @"doc6" ]];
  856. }
  857. - (void)testOrQueriesWithNotIn {
  858. // TODO(orquery): Enable this test against production when possible.
  859. XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator],
  860. "Skip this test if running against production because it results in a 'missing index' "
  861. "error. The Firestore Emulator, however, does serve these queries");
  862. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  863. @"doc1" : @{@"a" : @1, @"b" : @0},
  864. @"doc2" : @{@"b" : @1},
  865. @"doc3" : @{@"a" : @3, @"b" : @2},
  866. @"doc4" : @{@"a" : @1, @"b" : @3},
  867. @"doc5" : @{@"a" : @1},
  868. @"doc6" : @{@"a" : @2}
  869. }];
  870. // a==2 || b not-in [2,3]
  871. // Has implicit orderBy b.
  872. FIRFilter *filter = [FIRFilter orFilterWithFilters:@[
  873. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b"
  874. notIn:@[ @2, @3 ]]
  875. ]];
  876. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter]
  877. matchesResult:@[ @"doc1", @"doc2" ]];
  878. }
  879. - (void)testOrQueriesWithArrayMembership {
  880. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  881. @"doc1" : @{@"a" : @1, @"b" : @[ @0 ]},
  882. @"doc2" : @{@"b" : @[ @1 ]},
  883. @"doc3" : @{@"a" : @3, @"b" : @[ @2, @7 ]},
  884. @"doc4" : @{@"a" : @1, @"b" : @[ @3, @7 ]},
  885. @"doc5" : @{@"a" : @1},
  886. @"doc6" : @{@"a" : @2}
  887. }];
  888. // a==2 || b array-contains 7
  889. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  890. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b"
  891. arrayContains:@7]
  892. ]];
  893. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  894. matchesResult:@[ @"doc3", @"doc4", @"doc6" ]];
  895. // a==2 || b array-contains-any [0, 3]
  896. FIRFilter *filter2 = [FIRFilter orFilterWithFilters:@[
  897. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b"
  898. arrayContainsAny:@[ @0, @3 ]]
  899. ]];
  900. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2]
  901. matchesResult:@[ @"doc1", @"doc4", @"doc6" ]];
  902. }
  903. - (void)testMultipleInOps {
  904. // TODO(orquery): Enable this test against production when possible.
  905. XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator],
  906. "Skip this test if running against production because it's not yet supported.");
  907. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  908. @"doc1" : @{@"a" : @1, @"b" : @0},
  909. @"doc2" : @{@"b" : @1},
  910. @"doc3" : @{@"a" : @3, @"b" : @2},
  911. @"doc4" : @{@"a" : @1, @"b" : @3},
  912. @"doc5" : @{@"a" : @1},
  913. @"doc6" : @{@"a" : @2}
  914. }];
  915. // Two IN operations on different fields with disjunction.
  916. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  917. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"b"
  918. in:@[ @0, @2 ]]
  919. ]];
  920. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  921. matchesResult:@[ @"doc1", @"doc3", @"doc6" ]];
  922. // Two IN operations on the same field with disjunction.
  923. // a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]).
  924. FIRFilter *filter2 = [FIRFilter orFilterWithFilters:@[
  925. [FIRFilter filterWhereField:@"a" in:@[ @0, @3 ]], [FIRFilter filterWhereField:@"a"
  926. in:@[ @0, @2 ]]
  927. ]];
  928. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2]
  929. matchesResult:@[ @"doc3", @"doc6" ]];
  930. }
  931. - (void)testUsingInWithArrayContainsAny {
  932. // TODO(orquery): Enable this test against production when possible.
  933. XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator],
  934. "Skip this test if running against production because it's not yet supported.");
  935. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  936. @"doc1" : @{@"a" : @1, @"b" : @[ @0 ]},
  937. @"doc2" : @{@"b" : @[ @1 ]},
  938. @"doc3" : @{@"a" : @3, @"b" : @[ @2, @7 ], @"c" : @10},
  939. @"doc4" : @{@"a" : @1, @"b" : @[ @3, @7 ]},
  940. @"doc5" : @{@"a" : @1},
  941. @"doc6" : @{@"a" : @2, @"c" : @20}
  942. }];
  943. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  944. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"b"
  945. arrayContainsAny:@[ @0, @7 ]]
  946. ]];
  947. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  948. matchesResult:@[ @"doc1", @"doc3", @"doc4", @"doc6" ]];
  949. FIRFilter *filter2 = [FIRFilter orFilterWithFilters:@[
  950. [FIRFilter andFilterWithFilters:@[
  951. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"c"
  952. isEqualTo:@10]
  953. ]],
  954. [FIRFilter filterWhereField:@"b" arrayContainsAny:@[ @0, @7 ]]
  955. ]];
  956. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2]
  957. matchesResult:@[ @"doc1", @"doc3", @"doc4" ]];
  958. }
  959. - (void)testUseInWithArrayContains {
  960. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  961. @"doc1" : @{@"a" : @1, @"b" : @[ @0 ]},
  962. @"doc2" : @{@"b" : @[ @1 ]},
  963. @"doc3" : @{@"a" : @3, @"b" : @[ @2, @7 ]},
  964. @"doc4" : @{@"a" : @1, @"b" : @[ @3, @7 ]},
  965. @"doc5" : @{@"a" : @1},
  966. @"doc6" : @{@"a" : @2}
  967. }];
  968. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  969. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"b"
  970. arrayContainsAny:@[ @3 ]]
  971. ]];
  972. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  973. matchesResult:@[ @"doc3", @"doc4", @"doc6" ]];
  974. FIRFilter *filter2 = [FIRFilter andFilterWithFilters:@[
  975. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"b"
  976. arrayContains:@7]
  977. ]];
  978. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2] matchesResult:@[ @"doc3" ]];
  979. FIRFilter *filter3 = [FIRFilter orFilterWithFilters:@[
  980. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter andFilterWithFilters:@[
  981. [FIRFilter filterWhereField:@"b" arrayContains:@3], [FIRFilter filterWhereField:@"a"
  982. isEqualTo:@1]
  983. ]]
  984. ]];
  985. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter3]
  986. matchesResult:@[ @"doc3", @"doc4", @"doc6" ]];
  987. FIRFilter *filter4 = [FIRFilter andFilterWithFilters:@[
  988. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter orFilterWithFilters:@[
  989. [FIRFilter filterWhereField:@"b" arrayContains:@7], [FIRFilter filterWhereField:@"a"
  990. isEqualTo:@1]
  991. ]]
  992. ]];
  993. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter4] matchesResult:@[ @"doc3" ]];
  994. }
  995. - (void)testOrderByEquality {
  996. // TODO(orquery): Enable this test against production when possible.
  997. XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator],
  998. "Skip this test if running against production because order-by-equality is not "
  999. "supported yet.");
  1000. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  1001. @"doc1" : @{@"a" : @1, @"b" : @[ @0 ]},
  1002. @"doc2" : @{@"b" : @[ @1 ]},
  1003. @"doc3" : @{@"a" : @3, @"b" : @[ @2, @7 ], @"c" : @10},
  1004. @"doc4" : @{@"a" : @1, @"b" : @[ @3, @7 ]},
  1005. @"doc5" : @{@"a" : @1},
  1006. @"doc6" : @{@"a" : @2, @"c" : @20}
  1007. }];
  1008. [self checkOnlineAndOfflineQuery:[[collRef queryWhereFilter:[FIRFilter filterWhereField:@"a"
  1009. isEqualTo:@1]]
  1010. queryOrderedByField:@"a"]
  1011. matchesResult:@[ @"doc1", @"doc4", @"doc5" ]];
  1012. [self checkOnlineAndOfflineQuery:[[collRef
  1013. queryWhereFilter:[FIRFilter filterWhereField:@"a"
  1014. in:@[ @2, @3 ]]]
  1015. queryOrderedByField:@"a"]
  1016. matchesResult:@[ @"doc6", @"doc3" ]];
  1017. }
  1018. - (void)testResumingAQueryShouldUseExistenceFilterToDetectDeletes {
  1019. // Set this test to stop when the first failure occurs because some test assertion failures make
  1020. // the rest of the test not applicable or will even crash.
  1021. [self setContinueAfterFailure:NO];
  1022. // Prepare the names and contents of the 100 documents to create.
  1023. NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *testDocs =
  1024. [[NSMutableDictionary alloc] init];
  1025. for (int i = 0; i < 100; i++) {
  1026. [testDocs setValue:@{@"key" : @42} forKey:[NSString stringWithFormat:@"doc%@", @(1000 + i)]];
  1027. }
  1028. // Create 100 documents in a new collection.
  1029. FIRCollectionReference *collRef = [self collectionRefWithDocuments:testDocs];
  1030. // Run a query to populate the local cache with the 100 documents and a resume token.
  1031. FIRQuerySnapshot *querySnapshot1 = [self readDocumentSetForRef:collRef
  1032. source:FIRFirestoreSourceDefault];
  1033. XCTAssertEqual(querySnapshot1.count, 100, @"querySnapshot1.count has an unexpected value");
  1034. NSArray<FIRDocumentReference *> *createdDocuments =
  1035. FIRDocumentReferenceArrayFromQuerySnapshot(querySnapshot1);
  1036. // Delete 50 of the 100 documents. Do this in a transaction, rather than
  1037. // [FIRDocumentReference deleteDocument], to avoid affecting the local cache.
  1038. NSSet<NSString *> *deletedDocumentIds;
  1039. {
  1040. NSMutableArray<NSString *> *deletedDocumentIdsAccumulator = [[NSMutableArray alloc] init];
  1041. XCTestExpectation *expectation = [self expectationWithDescription:@"DeleteTransaction"];
  1042. [collRef.firestore
  1043. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **) {
  1044. for (decltype(createdDocuments.count) i = 0; i < createdDocuments.count; i += 2) {
  1045. FIRDocumentReference *documentToDelete = createdDocuments[i];
  1046. [transaction deleteDocument:documentToDelete];
  1047. [deletedDocumentIdsAccumulator addObject:documentToDelete.documentID];
  1048. }
  1049. return @"document deletion successful";
  1050. }
  1051. completion:^(id, NSError *) {
  1052. [expectation fulfill];
  1053. }];
  1054. [self awaitExpectation:expectation];
  1055. deletedDocumentIds = [NSSet setWithArray:deletedDocumentIdsAccumulator];
  1056. }
  1057. XCTAssertEqual(deletedDocumentIds.count, 50u, @"deletedDocumentIds has the wrong size");
  1058. // Wait for 10 seconds, during which Watch will stop tracking the query and will send an existence
  1059. // filter rather than "delete" events when the query is resumed.
  1060. [NSThread sleepForTimeInterval:10.0f];
  1061. // Resume the query and save the resulting snapshot for verification.
  1062. // Use some internal testing hooks to "capture" the existence filter mismatches to verify them.
  1063. FIRQuerySnapshot *querySnapshot2;
  1064. std::vector<firebase::firestore::util::TestingHooks::ExistenceFilterMismatchInfo>
  1065. existence_filter_mismatches =
  1066. firebase::firestore::testutil::CaptureExistenceFilterMismatches([&] {
  1067. querySnapshot2 = [self readDocumentSetForRef:collRef source:FIRFirestoreSourceDefault];
  1068. });
  1069. // Verify that the snapshot from the resumed query contains the expected documents; that is,
  1070. // that it contains the 50 documents that were _not_ deleted.
  1071. // TODO(b/270731363): Remove the "if" condition below once the Firestore Emulator is fixed to
  1072. // send an existence filter. At the time of writing, the Firestore emulator fails to send an
  1073. // existence filter, resulting in the client including the deleted documents in the snapshot
  1074. // of the resumed query.
  1075. if (!([FSTIntegrationTestCase isRunningAgainstEmulator] && querySnapshot2.count == 100)) {
  1076. NSSet<NSString *> *actualDocumentIds =
  1077. [NSSet setWithArray:FIRQuerySnapshotGetIDs(querySnapshot2)];
  1078. NSSet<NSString *> *expectedDocumentIds;
  1079. {
  1080. NSMutableArray<NSString *> *expectedDocumentIdsAccumulator = [[NSMutableArray alloc] init];
  1081. for (FIRDocumentReference *documentRef in createdDocuments) {
  1082. if (![deletedDocumentIds containsObject:documentRef.documentID]) {
  1083. [expectedDocumentIdsAccumulator addObject:documentRef.documentID];
  1084. }
  1085. }
  1086. expectedDocumentIds = [NSSet setWithArray:expectedDocumentIdsAccumulator];
  1087. }
  1088. if (![actualDocumentIds isEqualToSet:expectedDocumentIds]) {
  1089. NSArray<NSString *> *unexpectedDocumentIds =
  1090. SortedStringsNotIn(actualDocumentIds, expectedDocumentIds);
  1091. NSArray<NSString *> *missingDocumentIds =
  1092. SortedStringsNotIn(expectedDocumentIds, actualDocumentIds);
  1093. XCTFail(@"querySnapshot2 contained %lu documents (expected %lu): "
  1094. @"%lu unexpected and %lu missing; "
  1095. @"unexpected documents: %@; missing documents: %@",
  1096. (unsigned long)actualDocumentIds.count, (unsigned long)expectedDocumentIds.count,
  1097. (unsigned long)unexpectedDocumentIds.count, (unsigned long)missingDocumentIds.count,
  1098. [unexpectedDocumentIds componentsJoinedByString:@", "],
  1099. [missingDocumentIds componentsJoinedByString:@", "]);
  1100. }
  1101. }
  1102. // Skip the verification of the existence filter mismatch when testing against the Firestore
  1103. // emulator because the Firestore emulator fails to to send an existence filter at all.
  1104. // TODO(b/270731363): Enable the verification of the existence filter mismatch once the Firestore
  1105. // emulator is fixed to send an existence filter.
  1106. if ([FSTIntegrationTestCase isRunningAgainstEmulator]) {
  1107. return;
  1108. }
  1109. // Verify that Watch sent an existence filter with the correct counts when the query was resumed.
  1110. XCTAssertEqual(static_cast<int>(existence_filter_mismatches.size()), 1);
  1111. firebase::firestore::util::TestingHooks::ExistenceFilterMismatchInfo &info =
  1112. existence_filter_mismatches[0];
  1113. XCTAssertEqual(info.local_cache_count, 100);
  1114. XCTAssertEqual(info.existence_filter_count, 50);
  1115. }
  1116. @end