FIRQueryTests.mm 52 KB

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