FIRQueryTests.mm 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200
  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. // with one inequality: a>2 || b==1.
  748. FIRFilter *filter2 = [FIRFilter orFilterWithFilters:@[
  749. [FIRFilter filterWhereField:@"a" isGreaterThan:@2], [FIRFilter filterWhereField:@"b"
  750. isEqualTo:@1]
  751. ]];
  752. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2]
  753. matchesResult:@[ @"doc5", @"doc2", @"doc3" ]];
  754. // (a==1 && b==0) || (a==3 && b==2)
  755. FIRFilter *filter3 = [FIRFilter orFilterWithFilters:@[
  756. [FIRFilter andFilterWithFilters:@[
  757. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter filterWhereField:@"b" isEqualTo:@0]
  758. ]],
  759. [FIRFilter andFilterWithFilters:@[
  760. [FIRFilter filterWhereField:@"a" isEqualTo:@3], [FIRFilter filterWhereField:@"b" isEqualTo:@2]
  761. ]]
  762. ]];
  763. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter3]
  764. matchesResult:@[ @"doc1", @"doc3" ]];
  765. // a==1 && (b==0 || b==3).
  766. FIRFilter *filter4 = [FIRFilter andFilterWithFilters:@[
  767. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter orFilterWithFilters:@[
  768. [FIRFilter filterWhereField:@"b" isEqualTo:@0], [FIRFilter filterWhereField:@"b" isEqualTo:@3]
  769. ]]
  770. ]];
  771. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter4]
  772. matchesResult:@[ @"doc1", @"doc4" ]];
  773. // (a==2 || b==2) && (a==3 || b==3)
  774. FIRFilter *filter5 = [FIRFilter andFilterWithFilters:@[
  775. [FIRFilter orFilterWithFilters:@[
  776. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" isEqualTo:@2]
  777. ]],
  778. [FIRFilter orFilterWithFilters:@[
  779. [FIRFilter filterWhereField:@"a" isEqualTo:@3], [FIRFilter filterWhereField:@"b" isEqualTo:@3]
  780. ]]
  781. ]];
  782. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter5] matchesResult:@[ @"doc3" ]];
  783. // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2
  784. FIRFilter *filter6 = [FIRFilter orFilterWithFilters:@[
  785. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter filterWhereField:@"b"
  786. isGreaterThan:@0]
  787. ]];
  788. [self checkOnlineAndOfflineQuery:[[collRef queryWhereFilter:filter6] queryLimitedTo:2]
  789. matchesResult:@[ @"doc1", @"doc2" ]];
  790. // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2
  791. // Note: The public query API does not allow implicit ordering when limitToLast is used.
  792. FIRFilter *filter7 = [FIRFilter orFilterWithFilters:@[
  793. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter filterWhereField:@"b"
  794. isGreaterThan:@0]
  795. ]];
  796. [self checkOnlineAndOfflineQuery:[[[collRef queryWhereFilter:filter7] queryLimitedToLast:2]
  797. queryOrderedByField:@"b"]
  798. matchesResult:@[ @"doc3", @"doc4" ]];
  799. // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1
  800. FIRFilter *filter8 = [FIRFilter orFilterWithFilters:@[
  801. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" isEqualTo:@1]
  802. ]];
  803. [self checkOnlineAndOfflineQuery:[[[collRef queryWhereFilter:filter8] queryLimitedTo:1]
  804. queryOrderedByField:@"a"]
  805. matchesResult:@[ @"doc5" ]];
  806. // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT_TO_LAST 1
  807. FIRFilter *filter9 = [FIRFilter orFilterWithFilters:@[
  808. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" isEqualTo:@1]
  809. ]];
  810. [self checkOnlineAndOfflineQuery:[[[collRef queryWhereFilter:filter9] queryLimitedToLast:1]
  811. queryOrderedByField:@"a"]
  812. matchesResult:@[ @"doc2" ]];
  813. // Test with limits without orderBy (the __name__ ordering is the tie breaker).
  814. FIRFilter *filter10 = [FIRFilter orFilterWithFilters:@[
  815. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" isEqualTo:@1]
  816. ]];
  817. [self checkOnlineAndOfflineQuery:[[collRef queryWhereFilter:filter10] queryLimitedTo:1]
  818. matchesResult:@[ @"doc2" ]];
  819. }
  820. - (void)testOrQueriesWithInAndNotIn {
  821. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  822. @"doc1" : @{@"a" : @1, @"b" : @0},
  823. @"doc2" : @{@"b" : @1},
  824. @"doc3" : @{@"a" : @3, @"b" : @2},
  825. @"doc4" : @{@"a" : @1, @"b" : @3},
  826. @"doc5" : @{@"a" : @1},
  827. @"doc6" : @{@"a" : @2}
  828. }];
  829. // a==2 || b in [2,3]
  830. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  831. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" in:@[ @2, @3 ]]
  832. ]];
  833. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  834. matchesResult:@[ @"doc3", @"doc4", @"doc6" ]];
  835. // a==2 || b not-in [2,3]
  836. // Has implicit orderBy b.
  837. FIRFilter *filter2 = [FIRFilter orFilterWithFilters:@[
  838. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b"
  839. notIn:@[ @2, @3 ]]
  840. ]];
  841. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2]
  842. matchesResult:@[ @"doc1", @"doc2" ]];
  843. }
  844. - (void)testOrQueriesWithArrayMembership {
  845. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  846. @"doc1" : @{@"a" : @1, @"b" : @[ @0 ]},
  847. @"doc2" : @{@"b" : @[ @1 ]},
  848. @"doc3" : @{@"a" : @3, @"b" : @[ @2, @7 ]},
  849. @"doc4" : @{@"a" : @1, @"b" : @[ @3, @7 ]},
  850. @"doc5" : @{@"a" : @1},
  851. @"doc6" : @{@"a" : @2}
  852. }];
  853. // a==2 || b array-contains 7
  854. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  855. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b"
  856. arrayContains:@7]
  857. ]];
  858. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  859. matchesResult:@[ @"doc3", @"doc4", @"doc6" ]];
  860. // a==2 || b array-contains-any [0, 3]
  861. FIRFilter *filter2 = [FIRFilter orFilterWithFilters:@[
  862. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b"
  863. arrayContainsAny:@[ @0, @3 ]]
  864. ]];
  865. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2]
  866. matchesResult:@[ @"doc1", @"doc4", @"doc6" ]];
  867. }
  868. - (void)testMultipleInOps {
  869. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  870. @"doc1" : @{@"a" : @1, @"b" : @0},
  871. @"doc2" : @{@"b" : @1},
  872. @"doc3" : @{@"a" : @3, @"b" : @2},
  873. @"doc4" : @{@"a" : @1, @"b" : @3},
  874. @"doc5" : @{@"a" : @1},
  875. @"doc6" : @{@"a" : @2}
  876. }];
  877. // Two IN operations on different fields with disjunction.
  878. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  879. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"b"
  880. in:@[ @0, @2 ]]
  881. ]];
  882. [self checkOnlineAndOfflineQuery:[[collRef queryWhereFilter:filter1] queryOrderedByField:@"a"]
  883. matchesResult:@[ @"doc1", @"doc6", @"doc3" ]];
  884. // Two IN operations on different fields with conjunction.
  885. FIRFilter *filter2 = [FIRFilter andFilterWithFilters:@[
  886. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"b"
  887. in:@[ @0, @2 ]]
  888. ]];
  889. [self checkOnlineAndOfflineQuery:[[collRef queryWhereFilter:filter2] queryOrderedByField:@"a"]
  890. matchesResult:@[ @"doc3" ]];
  891. // Two IN operations on the same field.
  892. // a IN [1,2,3] && a IN [0,1,4] should result in "a==1".
  893. FIRFilter *filter3 = [FIRFilter andFilterWithFilters:@[
  894. [FIRFilter filterWhereField:@"a" in:@[ @1, @2, @3 ]],
  895. [FIRFilter filterWhereField:@"a" in:@[ @0, @1, @4 ]]
  896. ]];
  897. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter3]
  898. matchesResult:@[ @"doc1", @"doc4", @"doc5" ]];
  899. // a IN [2,3] && a IN [0,1,4] is never true and so the result should be an empty set.
  900. FIRFilter *filter4 = [FIRFilter andFilterWithFilters:@[
  901. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"a"
  902. in:@[ @0, @1, @4 ]]
  903. ]];
  904. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter4] matchesResult:@[]];
  905. // a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]).
  906. FIRFilter *filter5 = [FIRFilter orFilterWithFilters:@[
  907. [FIRFilter filterWhereField:@"a" in:@[ @0, @3 ]], [FIRFilter filterWhereField:@"a"
  908. in:@[ @0, @2 ]]
  909. ]];
  910. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter5]
  911. matchesResult:@[ @"doc3", @"doc6" ]];
  912. // Nested composite filter on the same field.
  913. FIRFilter *filter6 = [FIRFilter andFilterWithFilters:@[
  914. [FIRFilter filterWhereField:@"a" in:@[ @1, @3 ]], [FIRFilter orFilterWithFilters:@[
  915. [FIRFilter filterWhereField:@"a" in:@[ @0, @2 ]], [FIRFilter andFilterWithFilters:@[
  916. [FIRFilter filterWhereField:@"b" isGreaterThanOrEqualTo:@1],
  917. [FIRFilter filterWhereField:@"a" in:@[ @1, @3 ]]
  918. ]]
  919. ]]
  920. ]];
  921. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter6]
  922. matchesResult:@[ @"doc3", @"doc4" ]];
  923. // Nested composite filter on different fields.
  924. FIRFilter *filter7 = [FIRFilter andFilterWithFilters:@[
  925. [FIRFilter filterWhereField:@"b" in:@[ @0, @3 ]], [FIRFilter orFilterWithFilters:@[
  926. [FIRFilter filterWhereField:@"b" in:@[ @1 ]], [FIRFilter andFilterWithFilters:@[
  927. [FIRFilter filterWhereField:@"b" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"a"
  928. in:@[ @1, @3 ]]
  929. ]]
  930. ]]
  931. ]];
  932. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter7] matchesResult:@[ @"doc4" ]];
  933. }
  934. - (void)testUseInWithArrayContainsAny {
  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 andFilterWithFilters:@[
  950. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"b"
  951. arrayContainsAny:@[ @0, @7 ]]
  952. ]];
  953. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2] matchesResult:@[ @"doc3" ]];
  954. FIRFilter *filter3 = [FIRFilter orFilterWithFilters:@[
  955. [FIRFilter andFilterWithFilters:@[
  956. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"c"
  957. isEqualTo:@10]
  958. ]],
  959. [FIRFilter filterWhereField:@"b" arrayContainsAny:@[ @0, @7 ]]
  960. ]];
  961. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter3]
  962. matchesResult:@[ @"doc1", @"doc3", @"doc4" ]];
  963. FIRFilter *filter4 = [FIRFilter andFilterWithFilters:@[
  964. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter orFilterWithFilters:@[
  965. [FIRFilter filterWhereField:@"b" arrayContainsAny:@[ @0, @7 ]],
  966. [FIRFilter filterWhereField:@"c" isEqualTo:@20]
  967. ]]
  968. ]];
  969. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter4]
  970. matchesResult:@[ @"doc3", @"doc6" ]];
  971. }
  972. - (void)testUseInWithArrayContains {
  973. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  974. @"doc1" : @{@"a" : @1, @"b" : @[ @0 ]},
  975. @"doc2" : @{@"b" : @[ @1 ]},
  976. @"doc3" : @{@"a" : @3, @"b" : @[ @2, @7 ]},
  977. @"doc4" : @{@"a" : @1, @"b" : @[ @3, @7 ]},
  978. @"doc5" : @{@"a" : @1},
  979. @"doc6" : @{@"a" : @2}
  980. }];
  981. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  982. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"b"
  983. arrayContainsAny:@[ @3 ]]
  984. ]];
  985. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  986. matchesResult:@[ @"doc3", @"doc4", @"doc6" ]];
  987. FIRFilter *filter2 = [FIRFilter andFilterWithFilters:@[
  988. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"b"
  989. arrayContains:@7]
  990. ]];
  991. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2] matchesResult:@[ @"doc3" ]];
  992. FIRFilter *filter3 = [FIRFilter orFilterWithFilters:@[
  993. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter andFilterWithFilters:@[
  994. [FIRFilter filterWhereField:@"b" arrayContains:@3], [FIRFilter filterWhereField:@"a"
  995. isEqualTo:@1]
  996. ]]
  997. ]];
  998. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter3]
  999. matchesResult:@[ @"doc3", @"doc4", @"doc6" ]];
  1000. FIRFilter *filter4 = [FIRFilter andFilterWithFilters:@[
  1001. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter orFilterWithFilters:@[
  1002. [FIRFilter filterWhereField:@"b" arrayContains:@7], [FIRFilter filterWhereField:@"a"
  1003. isEqualTo:@1]
  1004. ]]
  1005. ]];
  1006. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter4] matchesResult:@[ @"doc3" ]];
  1007. }
  1008. - (void)testOrderByEquality {
  1009. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  1010. @"doc1" : @{@"a" : @1, @"b" : @[ @0 ]},
  1011. @"doc2" : @{@"b" : @[ @1 ]},
  1012. @"doc3" : @{@"a" : @3, @"b" : @[ @2, @7 ], @"c" : @10},
  1013. @"doc4" : @{@"a" : @1, @"b" : @[ @3, @7 ]},
  1014. @"doc5" : @{@"a" : @1},
  1015. @"doc6" : @{@"a" : @2, @"c" : @20}
  1016. }];
  1017. [self checkOnlineAndOfflineQuery:[[collRef queryWhereFilter:[FIRFilter filterWhereField:@"a"
  1018. isEqualTo:@1]]
  1019. queryOrderedByField:@"a"]
  1020. matchesResult:@[ @"doc1", @"doc4", @"doc5" ]];
  1021. [self checkOnlineAndOfflineQuery:[[collRef
  1022. queryWhereFilter:[FIRFilter filterWhereField:@"a"
  1023. in:@[ @2, @3 ]]]
  1024. queryOrderedByField:@"a"]
  1025. matchesResult:@[ @"doc6", @"doc3" ]];
  1026. }
  1027. @end