FIRQueryTests.mm 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  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/Source/API/FIRQuery+Internal.h"
  19. #import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
  20. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  21. #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
  22. @interface FIRQueryTests : FSTIntegrationTestCase
  23. @end
  24. @implementation FIRQueryTests
  25. - (void)testLimitQueries {
  26. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  27. @"a" : @{@"k" : @"a"},
  28. @"b" : @{@"k" : @"b"},
  29. @"c" : @{@"k" : @"c"}
  30. }];
  31. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[collRef queryLimitedTo:2]];
  32. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ @{@"k" : @"a"}, @{@"k" : @"b"} ]));
  33. }
  34. - (void)testLimitQueriesWithDescendingSortOrder {
  35. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  36. @"a" : @{@"k" : @"a", @"sort" : @0},
  37. @"b" : @{@"k" : @"b", @"sort" : @1},
  38. @"c" : @{@"k" : @"c", @"sort" : @1},
  39. @"d" : @{@"k" : @"d", @"sort" : @2},
  40. }];
  41. FIRQuerySnapshot *snapshot =
  42. [self readDocumentSetForRef:[[collRef queryOrderedByField:@"sort"
  43. descending:YES] queryLimitedTo:2]];
  44. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  45. (@[ @{@"k" : @"d", @"sort" : @2}, @{@"k" : @"c", @"sort" : @1} ]));
  46. }
  47. - (void)testLimitToLastMustAlsoHaveExplicitOrderBy {
  48. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{}];
  49. FIRQuery *query = [collRef queryLimitedToLast:2];
  50. FSTAssertThrows(
  51. [query getDocumentsWithCompletion:^(FIRQuerySnapshot *documentSet, NSError *error){
  52. }],
  53. @"limit(toLast:) queries require specifying at least one OrderBy() clause.");
  54. }
  55. // Two queries that mapped to the same target ID are referred to as
  56. // "mirror queries". An example for a mirror query is a limitToLast()
  57. // query and a limit() query that share the same backend Target ID.
  58. // Since limitToLast() queries are sent to the backend with a modified
  59. // orderBy() clause, they can map to the same target representation as
  60. // limit() query, even if both queries appear separate to the user.
  61. - (void)testListenUnlistenRelistenSequenceOfMirrorQueries {
  62. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  63. @"a" : @{@"k" : @"a", @"sort" : @0},
  64. @"b" : @{@"k" : @"b", @"sort" : @1},
  65. @"c" : @{@"k" : @"c", @"sort" : @1},
  66. @"d" : @{@"k" : @"d", @"sort" : @2},
  67. }];
  68. // Setup a `limit` query.
  69. FIRQuery *limit = [[collRef queryOrderedByField:@"sort" descending:NO] queryLimitedTo:2];
  70. FSTEventAccumulator *limitAccumulator = [FSTEventAccumulator accumulatorForTest:self];
  71. id<FIRListenerRegistration> limitRegistration =
  72. [limit addSnapshotListener:limitAccumulator.valueEventHandler];
  73. // Setup a mirroring `limitToLast` query.
  74. FIRQuery *limitToLast = [[collRef queryOrderedByField:@"sort"
  75. descending:YES] queryLimitedToLast:2];
  76. FSTEventAccumulator *limitToLastAccumulator = [FSTEventAccumulator accumulatorForTest:self];
  77. id<FIRListenerRegistration> limitToLastRegistration =
  78. [limitToLast addSnapshotListener:limitToLastAccumulator.valueEventHandler];
  79. // Verify both queries get expected result.
  80. FIRQuerySnapshot *snapshot = [limitAccumulator awaitEventWithName:@"Snapshot"];
  81. NSArray *expected = @[ @{@"k" : @"a", @"sort" : @0}, @{@"k" : @"b", @"sort" : @1} ];
  82. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  83. snapshot = [limitToLastAccumulator awaitEventWithName:@"Snapshot"];
  84. expected = @[ @{@"k" : @"b", @"sort" : @1}, @{@"k" : @"a", @"sort" : @0} ];
  85. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  86. // Unlisten then re-listen limit query.
  87. [limitRegistration remove];
  88. [limit addSnapshotListener:[limitAccumulator valueEventHandler]];
  89. // Verify limit query still works.
  90. snapshot = [limitAccumulator awaitEventWithName:@"Snapshot"];
  91. expected = @[ @{@"k" : @"a", @"sort" : @0}, @{@"k" : @"b", @"sort" : @1} ];
  92. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  93. // Add a document that would change the result set.
  94. [self addDocumentRef:collRef data:@{@"k" : @"e", @"sort" : @-1}];
  95. // Verify both queries get expected result.
  96. snapshot = [limitAccumulator awaitEventWithName:@"Snapshot"];
  97. expected = @[ @{@"k" : @"e", @"sort" : @-1}, @{@"k" : @"a", @"sort" : @0} ];
  98. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  99. snapshot = [limitToLastAccumulator awaitEventWithName:@"Snapshot"];
  100. expected = @[ @{@"k" : @"a", @"sort" : @0}, @{@"k" : @"e", @"sort" : @-1} ];
  101. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  102. // Unlisten to limitToLast, update a doc, then relisten to limitToLast
  103. [limitToLastRegistration remove];
  104. [self updateDocumentRef:[collRef documentWithPath:@"a"] data:@{@"k" : @"a", @"sort" : @-2}];
  105. [limitToLast addSnapshotListener:[limitToLastAccumulator valueEventHandler]];
  106. // Verify both queries get expected result.
  107. snapshot = [limitAccumulator awaitEventWithName:@"Snapshot"];
  108. expected = @[ @{@"k" : @"a", @"sort" : @-2}, @{@"k" : @"e", @"sort" : @-1} ];
  109. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  110. snapshot = [limitToLastAccumulator awaitEventWithName:@"Snapshot"];
  111. expected = @[ @{@"k" : @"e", @"sort" : @-1}, @{@"k" : @"a", @"sort" : @-2} ];
  112. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  113. }
  114. - (void)testKeyOrderIsDescendingForDescendingInequality {
  115. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  116. @"a" : @{@"foo" : @42},
  117. @"b" : @{@"foo" : @42.0},
  118. @"c" : @{@"foo" : @42},
  119. @"d" : @{@"foo" : @21},
  120. @"e" : @{@"foo" : @21.0},
  121. @"f" : @{@"foo" : @66},
  122. @"g" : @{@"foo" : @66.0},
  123. }];
  124. FIRQuerySnapshot *snapshot =
  125. [self readDocumentSetForRef:[[collRef queryWhereField:@"foo"
  126. isGreaterThan:@21] queryOrderedByField:@"foo"
  127. descending:YES]];
  128. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"g", @"f", @"c", @"b", @"a" ]));
  129. }
  130. - (void)testUnaryFilterQueries {
  131. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  132. @"a" : @{@"null" : [NSNull null], @"nan" : @(NAN)},
  133. @"b" : @{@"null" : [NSNull null], @"nan" : @0},
  134. @"c" : @{@"null" : @NO, @"nan" : @(NAN)}
  135. }];
  136. FIRQuerySnapshot *results =
  137. [self readDocumentSetForRef:[[collRef queryWhereField:@"null"
  138. isEqualTo:[NSNull null]] queryWhereField:@"nan"
  139. isEqualTo:@(NAN)]];
  140. XCTAssertEqualObjects(FIRQuerySnapshotGetData(results),
  141. (@[ @{@"null" : [NSNull null], @"nan" : @(NAN)} ]));
  142. }
  143. - (void)testQueryWithFieldPaths {
  144. FIRCollectionReference *collRef = [self
  145. collectionRefWithDocuments:@{@"a" : @{@"a" : @1}, @"b" : @{@"a" : @2}, @"c" : @{@"a" : @3}}];
  146. FIRQuery *query = [collRef queryWhereFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
  147. isLessThan:@3];
  148. query = [query queryOrderedByFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
  149. descending:YES];
  150. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:query];
  151. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"b", @"a" ]));
  152. }
  153. - (void)testQueryWithPredicate {
  154. FIRCollectionReference *collRef = [self
  155. collectionRefWithDocuments:@{@"a" : @{@"a" : @1}, @"b" : @{@"a" : @2}, @"c" : @{@"a" : @3}}];
  156. NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a < 3"];
  157. FIRQuery *query = [collRef queryFilteredUsingPredicate:predicate];
  158. query = [query queryOrderedByFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
  159. descending:YES];
  160. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:query];
  161. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"b", @"a" ]));
  162. }
  163. - (void)testFilterOnInfinity {
  164. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  165. @"a" : @{@"inf" : @(INFINITY)},
  166. @"b" : @{@"inf" : @(-INFINITY)}
  167. }];
  168. FIRQuerySnapshot *results = [self readDocumentSetForRef:[collRef queryWhereField:@"inf"
  169. isEqualTo:@(INFINITY)]];
  170. XCTAssertEqualObjects(FIRQuerySnapshotGetData(results), (@[ @{@"inf" : @(INFINITY)} ]));
  171. }
  172. - (void)testCanExplicitlySortByDocumentID {
  173. NSDictionary *testDocs = @{
  174. @"a" : @{@"key" : @"a"},
  175. @"b" : @{@"key" : @"b"},
  176. @"c" : @{@"key" : @"c"},
  177. };
  178. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  179. // Ideally this would be descending to validate it's different than
  180. // the default, but that requires an extra index
  181. FIRQuerySnapshot *docs =
  182. [self readDocumentSetForRef:[collection queryOrderedByFieldPath:[FIRFieldPath documentID]]];
  183. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs),
  184. (@[ testDocs[@"a"], testDocs[@"b"], testDocs[@"c"] ]));
  185. }
  186. - (void)testCanQueryByDocumentID {
  187. NSDictionary *testDocs = @{
  188. @"aa" : @{@"key" : @"aa"},
  189. @"ab" : @{@"key" : @"ab"},
  190. @"ba" : @{@"key" : @"ba"},
  191. @"bb" : @{@"key" : @"bb"},
  192. };
  193. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  194. FIRQuerySnapshot *docs =
  195. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  196. isEqualTo:@"ab"]];
  197. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"] ]));
  198. }
  199. - (void)testCanQueryByDocumentIDs {
  200. NSDictionary *testDocs = @{
  201. @"aa" : @{@"key" : @"aa"},
  202. @"ab" : @{@"key" : @"ab"},
  203. @"ba" : @{@"key" : @"ba"},
  204. @"bb" : @{@"key" : @"bb"},
  205. };
  206. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  207. FIRQuerySnapshot *docs =
  208. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  209. isEqualTo:@"ab"]];
  210. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"] ]));
  211. docs = [self readDocumentSetForRef:[[collection queryWhereFieldPath:[FIRFieldPath documentID]
  212. isGreaterThan:@"aa"]
  213. queryWhereFieldPath:[FIRFieldPath documentID]
  214. isLessThanOrEqualTo:@"ba"]];
  215. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"], testDocs[@"ba"] ]));
  216. }
  217. - (void)testCanQueryByDocumentIDsUsingRefs {
  218. NSDictionary *testDocs = @{
  219. @"aa" : @{@"key" : @"aa"},
  220. @"ab" : @{@"key" : @"ab"},
  221. @"ba" : @{@"key" : @"ba"},
  222. @"bb" : @{@"key" : @"bb"},
  223. };
  224. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  225. FIRQuerySnapshot *docs = [self
  226. readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  227. isEqualTo:[collection documentWithPath:@"ab"]]];
  228. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"] ]));
  229. docs = [self
  230. readDocumentSetForRef:[[collection queryWhereFieldPath:[FIRFieldPath documentID]
  231. isGreaterThan:[collection documentWithPath:@"aa"]]
  232. queryWhereFieldPath:[FIRFieldPath documentID]
  233. isLessThanOrEqualTo:[collection documentWithPath:@"ba"]]];
  234. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"], testDocs[@"ba"] ]));
  235. }
  236. - (void)testWatchSurvivesNetworkDisconnect {
  237. XCTestExpectation *testExpectiation =
  238. [self expectationWithDescription:@"testWatchSurvivesNetworkDisconnect"];
  239. FIRCollectionReference *collectionRef = [self collectionRef];
  240. FIRDocumentReference *docRef = [collectionRef documentWithAutoID];
  241. FIRFirestore *firestore = collectionRef.firestore;
  242. [collectionRef
  243. addSnapshotListenerWithIncludeMetadataChanges:YES
  244. listener:^(FIRQuerySnapshot *snapshot, NSError *error) {
  245. XCTAssertNil(error);
  246. if (!snapshot.empty && !snapshot.metadata.fromCache) {
  247. [testExpectiation fulfill];
  248. }
  249. }];
  250. [firestore disableNetworkWithCompletion:^(NSError *error) {
  251. XCTAssertNil(error);
  252. [docRef setData:@{@"foo" : @"bar"}];
  253. [firestore enableNetworkWithCompletion:^(NSError *error) {
  254. XCTAssertNil(error);
  255. }];
  256. }];
  257. [self awaitExpectations];
  258. }
  259. - (void)testQueriesFireFromCacheWhenOffline {
  260. NSDictionary *testDocs = @{
  261. @"a" : @{@"foo" : @1},
  262. };
  263. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  264. id<FIRListenerRegistration> registration = [collection
  265. addSnapshotListenerWithIncludeMetadataChanges:YES
  266. listener:self.eventAccumulator.valueEventHandler];
  267. FIRQuerySnapshot *querySnap = [self.eventAccumulator awaitEventWithName:@"initial event"];
  268. XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnap), @[ @{@"foo" : @1} ]);
  269. XCTAssertEqual(querySnap.metadata.isFromCache, NO);
  270. [self disableNetwork];
  271. querySnap = [self.eventAccumulator awaitEventWithName:@"offline event with isFromCache=YES"];
  272. XCTAssertEqual(querySnap.metadata.isFromCache, YES);
  273. [self enableNetwork];
  274. querySnap = [self.eventAccumulator awaitEventWithName:@"back online event with isFromCache=NO"];
  275. XCTAssertEqual(querySnap.metadata.isFromCache, NO);
  276. [registration remove];
  277. }
  278. - (void)testDocumentChangesUseNSNotFound {
  279. NSDictionary *testDocs = @{
  280. @"a" : @{@"foo" : @1},
  281. };
  282. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  283. id<FIRListenerRegistration> registration =
  284. [collection addSnapshotListener:self.eventAccumulator.valueEventHandler];
  285. FIRQuerySnapshot *querySnap = [self.eventAccumulator awaitEventWithName:@"initial event"];
  286. XCTAssertEqual(querySnap.documentChanges.count, 1ul);
  287. FIRDocumentChange *change = querySnap.documentChanges[0];
  288. XCTAssertEqual(change.oldIndex, NSNotFound);
  289. XCTAssertEqual(change.newIndex, 0ul);
  290. FIRDocumentReference *doc = change.document.reference;
  291. [self deleteDocumentRef:doc];
  292. querySnap = [self.eventAccumulator awaitEventWithName:@"delete"];
  293. XCTAssertEqual(querySnap.documentChanges.count, 1ul);
  294. change = querySnap.documentChanges[0];
  295. XCTAssertEqual(change.oldIndex, 0ul);
  296. XCTAssertEqual(change.newIndex, NSNotFound);
  297. [registration remove];
  298. }
  299. - (void)testCanHaveMultipleMutationsWhileOffline {
  300. FIRCollectionReference *col = [self collectionRef];
  301. // set a few docs to known values
  302. NSDictionary *initialDocs = @{@"doc1" : @{@"key1" : @"value1"}, @"doc2" : @{@"key2" : @"value2"}};
  303. [self writeAllDocuments:initialDocs toCollection:col];
  304. // go offline for the rest of this test
  305. [self disableNetwork];
  306. // apply *multiple* mutations while offline
  307. [[col documentWithPath:@"doc1"] setData:@{@"key1b" : @"value1b"}];
  308. [[col documentWithPath:@"doc2"] setData:@{@"key2b" : @"value2b"}];
  309. FIRQuerySnapshot *result = [self readDocumentSetForRef:col];
  310. XCTAssertEqualObjects(FIRQuerySnapshotGetData(result), (@[
  311. @{@"key1b" : @"value1b"},
  312. @{@"key2b" : @"value2b"},
  313. ]));
  314. }
  315. - (void)testQueriesCanUseArrayContainsFilters {
  316. NSDictionary *testDocs = @{
  317. @"a" : @{@"array" : @[ @42 ]},
  318. @"b" : @{@"array" : @[ @"a", @42, @"c" ]},
  319. @"c" : @{@"array" : @[ @41.999, @"42", @{@"a" : @[ @42 ]} ]},
  320. @"d" : @{@"array" : @[ @42 ], @"array2" : @[ @"bingo" ]}
  321. };
  322. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  323. // Search for 42
  324. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  325. arrayContains:@42]];
  326. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  327. (@[ testDocs[@"a"], testDocs[@"b"], testDocs[@"d"] ]));
  328. // NOTE: The backend doesn't currently support null, NaN, objects, or arrays, so there isn't much
  329. // of anything else interesting to test.
  330. }
  331. - (void)testQueriesCanUseInFilters {
  332. NSDictionary *testDocs = @{
  333. @"a" : @{@"zip" : @98101},
  334. @"b" : @{@"zip" : @91102},
  335. @"c" : @{@"zip" : @98103},
  336. @"d" : @{@"zip" : @[ @98101 ]},
  337. @"e" : @{@"zip" : @[ @"98101", @{@"zip" : @98101} ]},
  338. @"f" : @{@"zip" : @{@"code" : @500}},
  339. @"g" : @{@"zip" : @[ @98101, @98102 ]}
  340. };
  341. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  342. // Search for zips matching 98101, 98103, and [98101, 98102].
  343. FIRQuerySnapshot *snapshot = [self
  344. readDocumentSetForRef:[collection queryWhereField:@"zip"
  345. in:@[ @98101, @98103, @[ @98101, @98102 ] ]]];
  346. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  347. (@[ testDocs[@"a"], testDocs[@"c"], testDocs[@"g"] ]));
  348. // With objects
  349. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  350. in:@[ @{@"code" : @500} ]]];
  351. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"f"] ]));
  352. }
  353. - (void)testQueriesCanUseInFiltersWithDocIds {
  354. NSDictionary *testDocs = @{
  355. @"aa" : @{@"key" : @"aa"},
  356. @"ab" : @{@"key" : @"ab"},
  357. @"ba" : @{@"key" : @"ba"},
  358. @"bb" : @{@"key" : @"bb"},
  359. };
  360. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  361. FIRQuerySnapshot *snapshot =
  362. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  363. in:@[ @"aa", @"ab" ]]];
  364. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"aa"], testDocs[@"ab"] ]));
  365. }
  366. - (void)testQueriesCanUseArrayContainsAnyFilters {
  367. NSDictionary *testDocs = @{
  368. @"a" : @{@"array" : @[ @42 ]},
  369. @"b" : @{@"array" : @[ @"a", @42, @"c" ]},
  370. @"c" : @{@"array" : @[ @41.999, @"42", @{@"a" : @[ @42 ]} ]},
  371. @"d" : @{@"array" : @[ @42 ], @"array2" : @[ @"bingo" ]},
  372. @"e" : @{@"array" : @[ @43 ]},
  373. @"f" : @{@"array" : @[ @{@"a" : @42} ]},
  374. @"g" : @{@"array" : @42},
  375. };
  376. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  377. // Search for zips matching [42, 43].
  378. FIRQuerySnapshot *snapshot = [self
  379. readDocumentSetForRef:[collection queryWhereField:@"array" arrayContainsAny:@[ @42, @43 ]]];
  380. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  381. (@[ testDocs[@"a"], testDocs[@"b"], testDocs[@"d"], testDocs[@"e"] ]));
  382. // With objects.
  383. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  384. arrayContainsAny:@[ @{@"a" : @42} ]]];
  385. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  386. testDocs[@"f"],
  387. ]));
  388. }
  389. - (void)testCollectionGroupQueries {
  390. // Use .document() to get a random collection group name to use but ensure it starts with 'b'
  391. // for predictable ordering.
  392. NSString *collectionGroup = [NSString
  393. stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID];
  394. NSArray *docPaths = @[
  395. @"abc/123/${collectionGroup}/cg-doc1", @"abc/123/${collectionGroup}/cg-doc2",
  396. @"${collectionGroup}/cg-doc3", @"${collectionGroup}/cg-doc4",
  397. @"def/456/${collectionGroup}/cg-doc5", @"${collectionGroup}/virtual-doc/nested-coll/not-cg-doc",
  398. @"x${collectionGroup}/not-cg-doc", @"${collectionGroup}x/not-cg-doc",
  399. @"abc/123/${collectionGroup}x/not-cg-doc", @"abc/123/x${collectionGroup}/not-cg-doc",
  400. @"abc/${collectionGroup}"
  401. ];
  402. FIRWriteBatch *batch = [self.db batch];
  403. for (NSString *docPath in docPaths) {
  404. NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}"
  405. withString:collectionGroup];
  406. [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]];
  407. }
  408. XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"];
  409. [batch commitWithCompletion:^(NSError *error) {
  410. XCTAssertNil(error);
  411. [expectation fulfill];
  412. }];
  413. [self awaitExpectations];
  414. FIRQuerySnapshot *querySnapshot =
  415. [self readDocumentSetForRef:[self.db collectionGroupWithID:collectionGroup]];
  416. NSArray<NSString *> *ids = FIRQuerySnapshotGetIDs(querySnapshot);
  417. XCTAssertEqualObjects(ids, (@[ @"cg-doc1", @"cg-doc2", @"cg-doc3", @"cg-doc4", @"cg-doc5" ]));
  418. }
  419. - (void)testCollectionGroupQueriesWithStartAtEndAtWithArbitraryDocumentIDs {
  420. // Use .document() to get a random collection group name to use but ensure it starts with 'b'
  421. // for predictable ordering.
  422. NSString *collectionGroup = [NSString
  423. stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID];
  424. NSArray *docPaths = @[
  425. @"a/a/${collectionGroup}/cg-doc1", @"a/b/a/b/${collectionGroup}/cg-doc2",
  426. @"a/b/${collectionGroup}/cg-doc3", @"a/b/c/d/${collectionGroup}/cg-doc4",
  427. @"a/c/${collectionGroup}/cg-doc5", @"${collectionGroup}/cg-doc6", @"a/b/nope/nope"
  428. ];
  429. FIRWriteBatch *batch = [self.db batch];
  430. for (NSString *docPath in docPaths) {
  431. NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}"
  432. withString:collectionGroup];
  433. [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]];
  434. }
  435. XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"];
  436. [batch commitWithCompletion:^(NSError *error) {
  437. XCTAssertNil(error);
  438. [expectation fulfill];
  439. }];
  440. [self awaitExpectations];
  441. FIRQuerySnapshot *querySnapshot = [self
  442. readDocumentSetForRef:[[[[self.db collectionGroupWithID:collectionGroup]
  443. queryOrderedByFieldPath:[FIRFieldPath documentID]]
  444. queryStartingAfterValues:@[ @"a/b" ]]
  445. queryEndingBeforeValues:@[
  446. [NSString stringWithFormat:@"a/b/%@/cg-doc3", collectionGroup]
  447. ]]];
  448. NSArray<NSString *> *ids = FIRQuerySnapshotGetIDs(querySnapshot);
  449. XCTAssertEqualObjects(ids, (@[ @"cg-doc2" ]));
  450. }
  451. - (void)testCollectionGroupQueriesWithWhereFiltersOnArbitraryDocumentIDs {
  452. // Use .document() to get a random collection group name to use but ensure it starts with 'b'
  453. // for predictable ordering.
  454. NSString *collectionGroup = [NSString
  455. stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID];
  456. NSArray *docPaths = @[
  457. @"a/a/${collectionGroup}/cg-doc1", @"a/b/a/b/${collectionGroup}/cg-doc2",
  458. @"a/b/${collectionGroup}/cg-doc3", @"a/b/c/d/${collectionGroup}/cg-doc4",
  459. @"a/c/${collectionGroup}/cg-doc5", @"${collectionGroup}/cg-doc6", @"a/b/nope/nope"
  460. ];
  461. FIRWriteBatch *batch = [self.db batch];
  462. for (NSString *docPath in docPaths) {
  463. NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}"
  464. withString:collectionGroup];
  465. [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]];
  466. }
  467. XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"];
  468. [batch commitWithCompletion:^(NSError *error) {
  469. XCTAssertNil(error);
  470. [expectation fulfill];
  471. }];
  472. [self awaitExpectations];
  473. FIRQuerySnapshot *querySnapshot = [self
  474. readDocumentSetForRef:[[[self.db collectionGroupWithID:collectionGroup]
  475. queryWhereFieldPath:[FIRFieldPath documentID]
  476. isGreaterThanOrEqualTo:@"a/b"]
  477. queryWhereFieldPath:[FIRFieldPath documentID]
  478. isLessThan:[NSString stringWithFormat:@"a/b/%@/cg-doc3",
  479. collectionGroup]]];
  480. NSArray<NSString *> *ids = FIRQuerySnapshotGetIDs(querySnapshot);
  481. XCTAssertEqualObjects(ids, (@[ @"cg-doc2" ]));
  482. }
  483. @end