FIRCompositeIndexQueryTests.mm 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
  1. /*
  2. * Copyright 2023 Google LLC
  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 "FirebaseCore/Sources/Public/FirebaseCore/FIRTimestamp.h"
  18. #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
  19. #include "Firestore/core/src/util/autoid.h"
  20. using firebase::firestore::util::CreateAutoId;
  21. NS_ASSUME_NONNULL_BEGIN
  22. static NSString *const TEST_ID_FIELD = @"testId";
  23. static NSString *const TTL_FIELD = @"expireAt";
  24. static NSString *const COMPOSITE_INDEX_TEST_COLLECTION = @"composite-index-test-collection";
  25. /**
  26. * This FIRCompositeIndexQueryTests class is designed to facilitate integration
  27. * testing of Firestore queries that require composite indexes within a
  28. * controlled testing environment.
  29. *
  30. * Key Features:
  31. * <ul>
  32. * <li>Runs tests against the dedicated test collection with predefined composite indexes.
  33. * <li>Automatically associates a test ID with documents for data isolation.
  34. * <li>Utilizes TTL policy for automatic test data cleanup.
  35. * <li>Constructs Firestore queries with test ID filters.
  36. * </ul>
  37. */
  38. @interface FIRCompositeIndexQueryTests : FSTIntegrationTestCase
  39. // Creates a new unique identifier for each test case to ensure data isolation.
  40. @property(nonatomic, strong) NSString *testId;
  41. @end
  42. @implementation FIRCompositeIndexQueryTests
  43. - (void)setUp {
  44. [super setUp];
  45. _testId = [NSString stringWithFormat:@"test-id-%s", CreateAutoId().c_str()];
  46. }
  47. #pragma mark - Test Helpers
  48. // Return reference to the static test collection: composite-index-test-collection
  49. - (FIRCollectionReference *)testCollectionRef {
  50. return [self.db collectionWithPath:COMPOSITE_INDEX_TEST_COLLECTION];
  51. }
  52. // Runs a test with specified documents in the COMPOSITE_INDEX_TEST_COLLECTION.
  53. - (FIRCollectionReference *)collectionRefwithTestDocs:
  54. (NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)docs {
  55. FIRCollectionReference *writer = [self testCollectionRef];
  56. // Use a different instance to write the documents
  57. [self writeAllDocuments:[self prepareTestDocuments:docs]
  58. toCollection:[self.firestore collectionWithPath:writer.path]];
  59. return self.testCollectionRef;
  60. }
  61. // Hash the document key with testId.
  62. - (NSString *)toHashedId:(NSString *)docId {
  63. return [NSString stringWithFormat:@"%@-%@", docId, self.testId];
  64. }
  65. - (NSArray<NSString *> *)toHashedIds:(NSArray<NSString *> *)docs {
  66. NSMutableArray<NSString *> *hashedIds = [NSMutableArray arrayWithCapacity:docs.count];
  67. for (NSString *doc in docs) {
  68. [hashedIds addObject:[self toHashedId:doc]];
  69. }
  70. return hashedIds;
  71. }
  72. // Adds test-specific fields to a document, including the testId and expiration date.
  73. - (NSDictionary<NSString *, id> *)addTestSpecificFieldsToDoc:(NSDictionary<NSString *, id> *)doc {
  74. NSMutableDictionary<NSString *, id> *updatedDoc = [doc mutableCopy];
  75. updatedDoc[TEST_ID_FIELD] = self.testId;
  76. int64_t expirationTime =
  77. [[FIRTimestamp timestamp] seconds] + 24 * 60 * 60; // Expire test data after 24 hours
  78. updatedDoc[TTL_FIELD] = [FIRTimestamp timestampWithSeconds:expirationTime nanoseconds:0];
  79. return [updatedDoc copy];
  80. }
  81. // Remove test-specific fields from a Firestore document.
  82. - (NSDictionary<NSString *, id> *)removeTestSpecificFieldsFromDoc:
  83. (NSDictionary<NSString *, id> *)doc {
  84. NSMutableDictionary<NSString *, id> *mutableDoc = [doc mutableCopy];
  85. [mutableDoc removeObjectForKey:TEST_ID_FIELD];
  86. [mutableDoc removeObjectForKey:TTL_FIELD];
  87. // Update the document with the modified data.
  88. return [mutableDoc copy];
  89. }
  90. // Helper method to hash document keys and add test-specific fields for the provided documents.
  91. - (NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)prepareTestDocuments:
  92. (NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)docs {
  93. NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *result =
  94. [NSMutableDictionary dictionaryWithCapacity:docs.count];
  95. for (NSString *key in docs.allKeys) {
  96. NSDictionary<NSString *, id> *doc = docs[key];
  97. NSDictionary<NSString *, id> *updatedDoc = [self addTestSpecificFieldsToDoc:doc];
  98. result[[self toHashedId:key]] = updatedDoc;
  99. }
  100. return [result copy];
  101. }
  102. // Asserts that the result of running the query while online (against the backend/emulator) is
  103. // the same as running it while offline. The expected document Ids are hashed to match the
  104. // actual document IDs created by the test helper.
  105. - (void)assertOnlineAndOfflineResultsMatch:(FIRCollectionReference *)collection
  106. withQuery:(FIRQuery *)query
  107. expectedDocs:(NSArray<NSString *> *)expectedDocs {
  108. // `checkOnlineAndOfflineCollection` first makes sure all documents needed for
  109. // `query` are in the cache. It does so making a `get` on the first argument.
  110. // Since *all* composite index tests use the same collection, this is very inefficient to do.
  111. // Therefore, we should only do so for tests where `TEST_ID_FIELD` matches the current test.
  112. [self checkOnlineAndOfflineCollection:[self compositeIndexQuery:collection]
  113. query:query
  114. matchesResult:[self toHashedIds:expectedDocs]];
  115. }
  116. // Asserts that the IDs in the query snapshot matches the expected Ids. The expected document
  117. // IDs are hashed to match the actual document IDs created by the test helper.
  118. - (void)assertSnapshotResultIdsMatch:(FIRQuerySnapshot *)snapshot
  119. expectedIds:(NSArray<NSString *> *)expectedIds {
  120. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), [self toHashedIds:expectedIds]);
  121. }
  122. // Adds a filter on test id for a query.
  123. - (FIRQuery *)compositeIndexQuery:(FIRQuery *)query_ {
  124. return [query_ queryWhereField:TEST_ID_FIELD isEqualTo:self.testId];
  125. }
  126. // Get a document reference from a document key.
  127. - (FIRDocumentReference *)getDocRef:(FIRCollectionReference *)collection docId:(NSString *)docId {
  128. if (![docId containsString:@"test-id-"]) {
  129. docId = [self toHashedId:docId];
  130. }
  131. return [collection documentWithPath:docId];
  132. }
  133. // Adds a document to a Firestore collection with test-specific fields.
  134. - (FIRDocumentReference *)addDoc:(FIRCollectionReference *)collection
  135. data:(NSDictionary<NSString *, id> *)data {
  136. NSDictionary<NSString *, id> *updatedData = [self addTestSpecificFieldsToDoc:data];
  137. return [self addDocumentRef:collection data:updatedData];
  138. }
  139. // Sets a document in Firestore with test-specific fields.
  140. - (void)setDoc:(FIRDocumentReference *)document data:(NSDictionary<NSString *, id> *)data {
  141. NSDictionary<NSString *, id> *updatedData = [self addTestSpecificFieldsToDoc:data];
  142. return [self mergeDocumentRef:document data:updatedData];
  143. }
  144. - (void)updateDoc:(FIRDocumentReference *)document data:(NSDictionary<NSString *, id> *)data {
  145. [self updateDocumentRef:document data:data];
  146. }
  147. - (void)deleteDoc:(FIRDocumentReference *)document {
  148. [self deleteDocumentRef:document];
  149. }
  150. // Retrieve a single document from Firestore with test-specific fields removed.
  151. // TODO(composite-index-testing) Return sanitized DocumentSnapshot instead of its data.
  152. - (NSDictionary<NSString *, id> *)getSanitizedDocumentData:(FIRDocumentReference *)document {
  153. FIRDocumentSnapshot *docSnapshot = [self readDocumentForRef:document];
  154. return [self removeTestSpecificFieldsFromDoc:docSnapshot.data];
  155. }
  156. // Retrieve multiple documents from Firestore with test-specific fields removed.
  157. // TODO(composite-index-testing) Return sanitized QuerySnapshot instead of its data.
  158. - (NSArray<NSDictionary<NSString *, id> *> *)getSanitizedQueryData:(FIRQuery *)query {
  159. FIRQuerySnapshot *querySnapshot = [self readDocumentSetForRef:query];
  160. NSMutableArray<NSDictionary<NSString *, id> *> *result = [NSMutableArray array];
  161. for (FIRDocumentSnapshot *doc in querySnapshot.documents) {
  162. [result addObject:[self removeTestSpecificFieldsFromDoc:doc.data]];
  163. }
  164. return result;
  165. }
  166. #pragma mark - Test Cases
  167. /*
  168. * Guidance for Creating Tests:
  169. * ----------------------------
  170. * When creating tests that require composite indexes, it is recommended to utilize the
  171. * test helpers in this class. This utility class provides methods for creating
  172. * and setting test documents and running queries with ease, ensuring proper data
  173. * isolation and query construction.
  174. *
  175. * To get started, please refer to the instructions provided in the README file. This will
  176. * guide you through setting up your local testing environment and updating the Terraform
  177. * configuration with any new composite indexes required for your testing scenarios.
  178. *
  179. * Note: Whenever feasible, make use of the current document fields (such as 'a,' 'b,' 'author,'
  180. * 'title') to avoid introducing new composite indexes and surpassing the limit. Refer to the
  181. * guidelines at https://firebase.google.com/docs/firestore/quotas#indexes for further information.
  182. */
  183. - (void)testOrQueriesWithCompositeIndexes {
  184. FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{
  185. @"doc1" : @{@"a" : @1, @"b" : @0},
  186. @"doc2" : @{@"a" : @2, @"b" : @1},
  187. @"doc3" : @{@"a" : @3, @"b" : @2},
  188. @"doc4" : @{@"a" : @1, @"b" : @3},
  189. @"doc5" : @{@"a" : @1, @"b" : @1}
  190. }];
  191. // with one inequality: a>2 || b==1.
  192. FIRQuery *query1 = [collRef
  193. queryWhereFilter:[FIRFilter orFilterWithFilters:@[
  194. [FIRFilter filterWhereField:@"a" isGreaterThan:@2], [FIRFilter filterWhereField:@"b"
  195. isEqualTo:@1]
  196. ]]];
  197. [self assertOnlineAndOfflineResultsMatch:collRef
  198. withQuery:[self compositeIndexQuery:query1]
  199. expectedDocs:@[ @"doc5", @"doc2", @"doc3" ]];
  200. // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2
  201. FIRQuery *query2 =
  202. [collRef queryWhereFilter:[FIRFilter orFilterWithFilters:@[
  203. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter filterWhereField:@"b"
  204. isGreaterThan:@0]
  205. ]]];
  206. [self assertOnlineAndOfflineResultsMatch:collRef
  207. withQuery:[[self compositeIndexQuery:query2] queryLimitedTo:2]
  208. expectedDocs:@[ @"doc1", @"doc2" ]];
  209. // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2
  210. // Note: The public query API does not allow implicit ordering when limitToLast is used.
  211. FIRQuery *query3 =
  212. [collRef queryWhereFilter:[FIRFilter orFilterWithFilters:@[
  213. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter filterWhereField:@"b"
  214. isGreaterThan:@0]
  215. ]]];
  216. [self assertOnlineAndOfflineResultsMatch:collRef
  217. withQuery:[[[self compositeIndexQuery:query3] queryLimitedToLast:2]
  218. queryOrderedByField:@"b"]
  219. expectedDocs:@[ @"doc3", @"doc4" ]];
  220. // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1
  221. FIRQuery *query4 =
  222. [collRef queryWhereFilter:[FIRFilter orFilterWithFilters:@[
  223. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b"
  224. isEqualTo:@1]
  225. ]]];
  226. [self assertOnlineAndOfflineResultsMatch:collRef
  227. withQuery:[[[self compositeIndexQuery:query4] queryLimitedTo:1]
  228. queryOrderedByField:@"a"]
  229. expectedDocs:@[ @"doc5" ]];
  230. // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT_TO_LAST 1
  231. FIRQuery *query5 =
  232. [collRef queryWhereFilter:[FIRFilter orFilterWithFilters:@[
  233. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b"
  234. isEqualTo:@1]
  235. ]]];
  236. [self assertOnlineAndOfflineResultsMatch:collRef
  237. withQuery:[[[self compositeIndexQuery:query5] queryLimitedToLast:1]
  238. queryOrderedByField:@"a"]
  239. expectedDocs:@[ @"doc2" ]];
  240. }
  241. - (void)testCanRunAggregateCollectionGroupQuery {
  242. NSString *collectionGroup = [[self testCollectionRef] collectionID];
  243. NSArray *docPathFormats = @[
  244. @"abc/123/%@/cg-doc1", @"abc/123/%@/cg-doc2", @"%@/cg-doc3", @"%@/cg-doc4",
  245. @"def/456/%@/cg-doc5", @"%@/virtual-doc/nested-coll/not-cg-doc", @"x%@/not-cg-doc",
  246. @"%@x/not-cg-doc", @"abc/123/%@x/not-cg-doc", @"abc/123/x%@/not-cg-doc", @"abc/%@"
  247. ];
  248. FIRWriteBatch *batch = self.db.batch;
  249. for (NSString *format in docPathFormats) {
  250. NSString *path = [NSString stringWithFormat:format, collectionGroup];
  251. [batch setData:[self addTestSpecificFieldsToDoc:@{@"a" : @2}]
  252. forDocument:[self.db documentWithPath:path]];
  253. }
  254. [self commitWriteBatch:batch];
  255. FIRAggregateQuerySnapshot *snapshot = [self
  256. readSnapshotForAggregate:[[self
  257. compositeIndexQuery:[self.db
  258. collectionGroupWithID:collectionGroup]]
  259. aggregate:@[
  260. [FIRAggregateField aggregateFieldForCount],
  261. [FIRAggregateField aggregateFieldForSumOfField:@"a"],
  262. [FIRAggregateField aggregateFieldForAverageOfField:@"a"]
  263. ]]];
  264. // "cg-doc1", "cg-doc2", "cg-doc3", "cg-doc4", "cg-doc5",
  265. XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]],
  266. [NSNumber numberWithLong:5L]);
  267. XCTAssertEqual(
  268. [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"a"]],
  269. [NSNumber numberWithLong:10L]);
  270. XCTAssertEqual(
  271. [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"a"]],
  272. [NSNumber numberWithDouble:2.0]);
  273. }
  274. - (void)testCanPerformMaxAggregations {
  275. FIRCollectionReference *testCollection = [self collectionRefwithTestDocs:@{
  276. @"a" : @{
  277. @"author" : @"authorA",
  278. @"title" : @"titleA",
  279. @"pages" : @100,
  280. @"year" : @1980,
  281. @"rating" : @5.0,
  282. },
  283. @"b" : @{
  284. @"author" : @"authorB",
  285. @"title" : @"titleB",
  286. @"pages" : @50,
  287. @"year" : @2020,
  288. @"rating" : @4.0,
  289. }
  290. }];
  291. // Max is 5, do not exceed
  292. FIRAggregateQuerySnapshot *snapshot =
  293. [self readSnapshotForAggregate:[[self compositeIndexQuery:testCollection] aggregate:@[
  294. [FIRAggregateField aggregateFieldForCount],
  295. [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
  296. [FIRAggregateField aggregateFieldForSumOfField:@"year"],
  297. [FIRAggregateField aggregateFieldForAverageOfField:@"pages"],
  298. [FIRAggregateField aggregateFieldForAverageOfField:@"rating"]
  299. ]]];
  300. // Assert
  301. XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]],
  302. [NSNumber numberWithLong:2L]);
  303. XCTAssertEqual(
  304. [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]],
  305. [NSNumber numberWithLong:150L]);
  306. XCTAssertEqual(
  307. [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"year"]],
  308. [NSNumber numberWithLong:4000L]);
  309. XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField
  310. aggregateFieldForAverageOfField:@"pages"]],
  311. [NSNumber numberWithDouble:75.0]);
  312. XCTAssertEqual([[snapshot valueForAggregateField:[FIRAggregateField
  313. aggregateFieldForAverageOfField:@"rating"]]
  314. doubleValue],
  315. 4.5);
  316. }
  317. - (void)testPerformsAggregationsWhenNaNExistsForSomeFieldValues {
  318. FIRCollectionReference *testCollection = [self collectionRefwithTestDocs:@{
  319. @"a" : @{
  320. @"author" : @"authorA",
  321. @"title" : @"titleA",
  322. @"pages" : @100,
  323. @"year" : @1980,
  324. @"rating" : @5
  325. },
  326. @"b" : @{
  327. @"author" : @"authorB",
  328. @"title" : @"titleB",
  329. @"pages" : @50,
  330. @"year" : @2020,
  331. @"rating" : @4
  332. },
  333. @"c" : @{
  334. @"author" : @"authorC",
  335. @"title" : @"titleC",
  336. @"pages" : @100,
  337. @"year" : @1980,
  338. @"rating" : [NSNumber numberWithFloat:NAN]
  339. },
  340. @"d" : @{
  341. @"author" : @"authorD",
  342. @"title" : @"titleD",
  343. @"pages" : @50,
  344. @"year" : @2020,
  345. @"rating" : @0
  346. }
  347. }];
  348. FIRAggregateQuerySnapshot *snapshot =
  349. [self readSnapshotForAggregate:[[self compositeIndexQuery:testCollection] aggregate:@[
  350. [FIRAggregateField aggregateFieldForSumOfField:@"rating"],
  351. [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
  352. [FIRAggregateField aggregateFieldForAverageOfField:@"rating"],
  353. [FIRAggregateField aggregateFieldForAverageOfField:@"year"]
  354. ]]];
  355. // Sum
  356. XCTAssertEqual(
  357. [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]],
  358. [NSNumber numberWithDouble:NAN]);
  359. XCTAssertEqual(
  360. [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]]
  361. longValue],
  362. 300L);
  363. // Average
  364. XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField
  365. aggregateFieldForAverageOfField:@"rating"]],
  366. [NSNumber numberWithDouble:NAN]);
  367. XCTAssertEqual(
  368. [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"year"]]
  369. doubleValue],
  370. 2000.0);
  371. }
  372. - (void)testPerformsAggregationWhenUsingArrayContainsAnyOperator {
  373. FIRCollectionReference *testCollection = [self collectionRefwithTestDocs:@{
  374. @"a" : @{
  375. @"author" : @"authorA",
  376. @"title" : @"titleA",
  377. @"pages" : @100,
  378. @"year" : @1980,
  379. @"rating" : @[ @5, @1000 ]
  380. },
  381. @"b" : @{
  382. @"author" : @"authorB",
  383. @"title" : @"titleB",
  384. @"pages" : @50,
  385. @"year" : @2020,
  386. @"rating" : @[ @4 ]
  387. },
  388. @"c" : @{
  389. @"author" : @"authorC",
  390. @"title" : @"titleC",
  391. @"pages" : @100,
  392. @"year" : @1980,
  393. @"rating" : @[ @2222, @3 ]
  394. },
  395. @"d" : @{
  396. @"author" : @"authorD",
  397. @"title" : @"titleD",
  398. @"pages" : @50,
  399. @"year" : @2020,
  400. @"rating" : @[ @0 ]
  401. }
  402. }];
  403. FIRAggregateQuerySnapshot *snapshot = [self
  404. readSnapshotForAggregate:[[self
  405. compositeIndexQuery:[testCollection queryWhereField:@"rating"
  406. arrayContainsAny:@[ @5, @3 ]]]
  407. aggregate:@[
  408. [FIRAggregateField aggregateFieldForSumOfField:@"rating"],
  409. [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
  410. [FIRAggregateField aggregateFieldForAverageOfField:@"rating"],
  411. [FIRAggregateField aggregateFieldForAverageOfField:@"pages"],
  412. [FIRAggregateField aggregateFieldForCount]
  413. ]]];
  414. // Count
  415. XCTAssertEqual(
  416. [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]] longValue], 2L);
  417. // Sum
  418. XCTAssertEqual(
  419. [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]]
  420. longValue],
  421. 0L);
  422. XCTAssertEqual(
  423. [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]]
  424. longValue],
  425. 200L);
  426. // Average
  427. XCTAssertEqualObjects(
  428. [snapshot
  429. valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]],
  430. [NSNull null]);
  431. XCTAssertEqual(
  432. [[snapshot valueForAggregateField:[FIRAggregateField
  433. aggregateFieldForAverageOfField:@"pages"]] doubleValue],
  434. 100.0);
  435. }
  436. // Multiple Inequality
  437. - (void)testMultipleInequalityOnDifferentFields {
  438. FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{
  439. @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @0},
  440. @"doc2" : @{@"key" : @"b", @"sort" : @3, @"v" : @1},
  441. @"doc3" : @{@"key" : @"c", @"sort" : @1, @"v" : @3},
  442. @"doc4" : @{@"key" : @"d", @"sort" : @2, @"v" : @2}
  443. }];
  444. // Multiple inequality fields
  445. FIRQuery *query = [[[collRef queryWhereField:@"key"
  446. isNotEqualTo:@"a"] queryWhereField:@"sort"
  447. isLessThanOrEqualTo:@2] queryWhereField:@"v"
  448. isGreaterThan:@2];
  449. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  450. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc3" ])];
  451. // Duplicate inequality fields
  452. query = [[[collRef queryWhereField:@"key"
  453. isNotEqualTo:@"a"] queryWhereField:@"sort"
  454. isLessThanOrEqualTo:@2] queryWhereField:@"sort"
  455. isGreaterThan:@1];
  456. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  457. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc4" ])];
  458. // With multiple IN
  459. query = [[[[collRef queryWhereField:@"key" isGreaterThanOrEqualTo:@"a"] queryWhereField:@"sort"
  460. isLessThanOrEqualTo:@2]
  461. queryWhereField:@"v"
  462. in:@[ @2, @3, @4 ]] queryWhereField:@"sort" in:@[ @2, @3 ]];
  463. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  464. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc4" ])];
  465. // With NOT-IN
  466. query = [[[collRef queryWhereField:@"key"
  467. isGreaterThanOrEqualTo:@"a"] queryWhereField:@"sort"
  468. isLessThanOrEqualTo:@2] queryWhereField:@"v"
  469. notIn:@[ @2, @4, @5 ]];
  470. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  471. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc1", @"doc3" ])];
  472. // With orderby
  473. query = [[[collRef queryWhereField:@"key"
  474. isGreaterThanOrEqualTo:@"a"] queryWhereField:@"sort"
  475. isLessThanOrEqualTo:@2] queryOrderedByField:@"v"
  476. descending:YES];
  477. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  478. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc3", @"doc4", @"doc1" ])];
  479. // With limit
  480. query = [[[[collRef queryWhereField:@"key" isGreaterThanOrEqualTo:@"a"]
  481. queryWhereField:@"sort"
  482. isLessThanOrEqualTo:@2] queryOrderedByField:@"v" descending:YES] queryLimitedTo:2];
  483. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  484. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc3", @"doc4" ])];
  485. // With limitedToLast
  486. query = [[[[collRef queryWhereField:@"key" isGreaterThanOrEqualTo:@"a"]
  487. queryWhereField:@"sort"
  488. isLessThanOrEqualTo:@2] queryOrderedByField:@"v" descending:YES] queryLimitedToLast:2];
  489. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  490. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc4", @"doc1" ])];
  491. }
  492. - (void)testMultipleInequalityOnSpecialValues {
  493. FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{
  494. @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @0},
  495. @"doc2" : @{@"key" : @"b", @"sort" : @(NAN), @"v" : @1},
  496. @"doc3" : @{@"key" : @"c", @"sort" : [NSNull null], @"v" : @3},
  497. @"doc4" : @{@"key" : @"d", @"v" : @0},
  498. @"doc5" : @{@"key" : @"e", @"sort" : @1},
  499. @"doc6" : @{@"key" : @"f", @"sort" : @1, @"v" : @1}
  500. }];
  501. FIRQuery *query = [[collRef queryWhereField:@"key" isNotEqualTo:@"a"] queryWhereField:@"sort"
  502. isLessThanOrEqualTo:@2];
  503. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  504. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc5", @"doc6" ])];
  505. query = [[[collRef queryWhereField:@"key"
  506. isNotEqualTo:@"a"] queryWhereField:@"sort"
  507. isLessThanOrEqualTo:@2] queryWhereField:@"v"
  508. isLessThanOrEqualTo:@1];
  509. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  510. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc6" ])];
  511. }
  512. - (void)testMultipleInequalityWithArrayMembership {
  513. FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{
  514. @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @[ @0 ]},
  515. @"doc2" : @{@"key" : @"b", @"sort" : @1, @"v" : @[ @0, @1, @3 ]},
  516. @"doc3" : @{@"key" : @"c", @"sort" : @1, @"v" : @[]},
  517. @"doc4" : @{@"key" : @"d", @"sort" : @2, @"v" : @[ @1 ]},
  518. @"doc5" : @{@"key" : @"e", @"sort" : @3, @"v" : @[ @2, @4 ]},
  519. @"doc6" : @{@"key" : @"f", @"sort" : @4, @"v" : @[ @(NAN) ]},
  520. @"doc7" : @{@"key" : @"g", @"sort" : @4, @"v" : @[ [NSNull null] ]}
  521. }];
  522. FIRQuery *query = [[[collRef queryWhereField:@"key"
  523. isNotEqualTo:@"a"] queryWhereField:@"sort"
  524. isGreaterThanOrEqualTo:@1] queryWhereField:@"v"
  525. arrayContains:@0];
  526. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  527. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc2" ])];
  528. query = [[[collRef queryWhereField:@"key"
  529. isNotEqualTo:@"a"] queryWhereField:@"sort"
  530. isGreaterThanOrEqualTo:@1] queryWhereField:@"v"
  531. arrayContainsAny:@[ @0, @1 ]];
  532. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  533. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc2", @"doc4" ])];
  534. }
  535. - (NSDictionary<NSString *, id> *)nestedData:(int)number {
  536. return @{
  537. @"name" : [NSString stringWithFormat:@"room %d", number],
  538. @"metadata" : @{@"createdAt" : @(number)},
  539. @"field" : [NSString stringWithFormat:@"field %d", number],
  540. @"field.dot" : @(number),
  541. @"field\\slash" : @(number)
  542. };
  543. }
  544. - (void)testMultipleInequalityWithNestedField {
  545. FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{
  546. @"doc1" : [self nestedData:400],
  547. @"doc2" : [self nestedData:200],
  548. @"doc3" : [self nestedData:100],
  549. @"doc4" : [self nestedData:300]
  550. }];
  551. FIRQuery *query = [[[[collRef queryWhereField:@"metadata.createdAt" isLessThanOrEqualTo:@500]
  552. queryWhereField:@"metadata.createdAt"
  553. isGreaterThan:@100] queryWhereField:@"name"
  554. isNotEqualTo:@"room 200"] queryOrderedByField:@"name" descending:NO];
  555. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  556. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc4", @"doc1" ])];
  557. query = [[[[collRef queryWhereField:@"field" isGreaterThanOrEqualTo:@"field 100"]
  558. queryWhereFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"field.dot" ]]
  559. isNotEqualTo:@300] queryWhereField:@"field\\slash"
  560. isLessThan:@400] queryOrderedByField:@"name" descending:YES];
  561. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  562. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc2", @"doc3" ])];
  563. }
  564. - (void)testMultipleInequalityWithCompositeFilters {
  565. FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{
  566. @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @5},
  567. @"doc2" : @{@"key" : @"aa", @"sort" : @4, @"v" : @4},
  568. @"doc3" : @{@"key" : @"c", @"sort" : @3, @"v" : @3},
  569. @"doc4" : @{@"key" : @"b", @"sort" : @2, @"v" : @2},
  570. @"doc5" : @{@"key" : @"b", @"sort" : @2, @"v" : @1},
  571. @"doc6" : @{@"key" : @"b", @"sort" : @0, @"v" : @0}
  572. }];
  573. FIRQuery *query = [collRef
  574. queryWhereFilter:[FIRFilter orFilterWithFilters:@[
  575. [FIRFilter andFilterWithFilters:@[
  576. [FIRFilter filterWhereField:@"key" isEqualTo:@"b"], [FIRFilter filterWhereField:@"sort"
  577. isLessThanOrEqualTo:@2]
  578. ]],
  579. [FIRFilter andFilterWithFilters:@[
  580. [FIRFilter filterWhereField:@"key" isNotEqualTo:@"b"], [FIRFilter filterWhereField:@"v"
  581. isGreaterThan:@4]
  582. ]]
  583. ]]];
  584. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  585. // Implicitly ordered by: 'key' asc, 'sort' asc, 'v' asc, __name__ asc
  586. [self assertSnapshotResultIdsMatch:snapshot
  587. expectedIds:(@[ @"doc1", @"doc6", @"doc5", @"doc4" ])];
  588. query = [[[collRef
  589. queryWhereFilter:[FIRFilter orFilterWithFilters:@[
  590. [FIRFilter andFilterWithFilters:@[
  591. [FIRFilter filterWhereField:@"key" isEqualTo:@"b"], [FIRFilter filterWhereField:@"sort"
  592. isLessThanOrEqualTo:@2]
  593. ]],
  594. [FIRFilter andFilterWithFilters:@[
  595. [FIRFilter filterWhereField:@"key" isNotEqualTo:@"b"], [FIRFilter filterWhereField:@"v"
  596. isGreaterThan:@4]
  597. ]]
  598. ]]] queryOrderedByField:@"sort" descending:YES] queryOrderedByField:@"key"];
  599. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  600. // Ordered by: 'sort' desc, 'key' asc, 'v' asc, __name__ asc
  601. [self assertSnapshotResultIdsMatch:snapshot
  602. expectedIds:(@[ @"doc5", @"doc4", @"doc1", @"doc6" ])];
  603. query = [collRef
  604. queryWhereFilter:[FIRFilter andFilterWithFilters:@[
  605. [FIRFilter orFilterWithFilters:@[
  606. [FIRFilter andFilterWithFilters:@[
  607. [FIRFilter filterWhereField:@"key" isEqualTo:@"b"], [FIRFilter filterWhereField:@"sort"
  608. isLessThanOrEqualTo:@4]
  609. ]],
  610. [FIRFilter andFilterWithFilters:@[
  611. [FIRFilter filterWhereField:@"key" isNotEqualTo:@"b"], [FIRFilter filterWhereField:@"v"
  612. isGreaterThanOrEqualTo:@4]
  613. ]]
  614. ]],
  615. [FIRFilter orFilterWithFilters:@[
  616. [FIRFilter andFilterWithFilters:@[
  617. [FIRFilter filterWhereField:@"key" isGreaterThan:@"b"],
  618. [FIRFilter filterWhereField:@"sort" isGreaterThanOrEqualTo:@1]
  619. ]],
  620. [FIRFilter andFilterWithFilters:@[
  621. [FIRFilter filterWhereField:@"key" isLessThan:@"b"], [FIRFilter filterWhereField:@"v"
  622. isGreaterThan:@0]
  623. ]]
  624. ]]
  625. ]]];
  626. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  627. // Implicitly ordered by: 'key' asc, 'sort' asc, 'v' asc, __name__ asc
  628. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc1", @"doc2" ])];
  629. }
  630. - (void)testMultipleInequalityFieldsWillBeImplicitlyOrderedLexicographically {
  631. FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{
  632. @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @5},
  633. @"doc2" : @{@"key" : @"aa", @"sort" : @4, @"v" : @4},
  634. @"doc3" : @{@"key" : @"b", @"sort" : @3, @"v" : @3},
  635. @"doc4" : @{@"key" : @"b", @"sort" : @2, @"v" : @2},
  636. @"doc5" : @{@"key" : @"b", @"sort" : @2, @"v" : @1},
  637. @"doc6" : @{@"key" : @"b", @"sort" : @0, @"v" : @0}
  638. }];
  639. FIRQuery *query = [[[collRef queryWhereField:@"key" isNotEqualTo:@"a"]
  640. queryWhereField:@"sort"
  641. isGreaterThan:@1] queryWhereField:@"v" in:@[ @1, @2, @3, @4 ]];
  642. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  643. // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc
  644. [self assertSnapshotResultIdsMatch:snapshot
  645. expectedIds:(@[ @"doc2", @"doc4", @"doc5", @"doc3" ])];
  646. query = [[[collRef queryWhereField:@"sort"
  647. isGreaterThan:@1] queryWhereField:@"key"
  648. isNotEqualTo:@"a"] queryWhereField:@"v"
  649. in:@[ @1, @2, @3, @4 ]];
  650. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  651. // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc
  652. [self assertSnapshotResultIdsMatch:snapshot
  653. expectedIds:(@[ @"doc2", @"doc4", @"doc5", @"doc3" ])];
  654. }
  655. - (void)testMultipleInequalityWithMultipleExplicitOrderBy {
  656. FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{
  657. @"doc1" : @{@"key" : @"a", @"sort" : @5, @"v" : @0},
  658. @"doc2" : @{@"key" : @"aa", @"sort" : @4, @"v" : @0},
  659. @"doc3" : @{@"key" : @"b", @"sort" : @3, @"v" : @1},
  660. @"doc4" : @{@"key" : @"b", @"sort" : @2, @"v" : @1},
  661. @"doc5" : @{@"key" : @"bb", @"sort" : @1, @"v" : @1},
  662. @"doc6" : @{@"key" : @"c", @"sort" : @0, @"v" : @2}
  663. }];
  664. FIRQuery *query = [[[collRef queryWhereField:@"key"
  665. isGreaterThan:@"a"] queryWhereField:@"sort"
  666. isGreaterThanOrEqualTo:@1] queryOrderedByField:@"v"
  667. descending:NO];
  668. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  669. // Ordered by: 'v' asc, 'key' asc, 'sort' asc, __name__ asc
  670. [self assertSnapshotResultIdsMatch:snapshot
  671. expectedIds:(@[ @"doc2", @"doc4", @"doc3", @"doc5" ])];
  672. query = [[[[collRef queryWhereField:@"key" isGreaterThan:@"a"] queryWhereField:@"sort"
  673. isGreaterThanOrEqualTo:@1]
  674. queryOrderedByField:@"v"
  675. descending:NO] queryOrderedByField:@"sort" descending:NO];
  676. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  677. // Ordered by: 'v asc, 'sort' asc, 'key' asc, __name__ asc
  678. [self assertSnapshotResultIdsMatch:snapshot
  679. expectedIds:(@[ @"doc2", @"doc5", @"doc4", @"doc3" ])];
  680. query = [[[collRef queryWhereField:@"key"
  681. isGreaterThan:@"a"] queryWhereField:@"sort"
  682. isGreaterThanOrEqualTo:@1] queryOrderedByField:@"v"
  683. descending:YES];
  684. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  685. // Implicit order by matches the direction of last explicit order by.
  686. // Ordered by: 'v' desc, 'key' desc, 'sort' desc, __name__ desc
  687. [self assertSnapshotResultIdsMatch:snapshot
  688. expectedIds:(@[ @"doc5", @"doc3", @"doc4", @"doc2" ])];
  689. query = [[[[collRef queryWhereField:@"key" isGreaterThan:@"a"] queryWhereField:@"sort"
  690. isGreaterThanOrEqualTo:@1]
  691. queryOrderedByField:@"v"
  692. descending:YES] queryOrderedByField:@"sort" descending:NO];
  693. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  694. // Ordered by: 'v desc, 'sort' asc, 'key' asc, __name__ asc
  695. [self assertSnapshotResultIdsMatch:snapshot
  696. expectedIds:(@[ @"doc5", @"doc4", @"doc3", @"doc2" ])];
  697. }
  698. - (void)testMultipleInequalityInAggregateQuery {
  699. FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{
  700. @"doc1" : @{@"key" : @"a", @"sort" : @5, @"v" : @0},
  701. @"doc2" : @{@"key" : @"aa", @"sort" : @4, @"v" : @0},
  702. @"doc3" : @{@"key" : @"b", @"sort" : @3, @"v" : @1},
  703. @"doc4" : @{@"key" : @"b", @"sort" : @2, @"v" : @1},
  704. @"doc5" : @{@"key" : @"bb", @"sort" : @1, @"v" : @1},
  705. }];
  706. FIRAggregateQuerySnapshot *snapshot =
  707. [self readSnapshotForAggregate:[[self compositeIndexQuery:[[[collRef queryWhereField:@"key"
  708. isGreaterThan:@"a"]
  709. queryWhereField:@"sort"
  710. isGreaterThanOrEqualTo:@1]
  711. queryOrderedByField:@"v"
  712. descending:NO]]
  713. aggregate:@[
  714. [FIRAggregateField aggregateFieldForCount],
  715. [FIRAggregateField aggregateFieldForSumOfField:@"sort"],
  716. [FIRAggregateField aggregateFieldForAverageOfField:@"v"]
  717. ]]];
  718. XCTAssertEqual([snapshot count], [NSNumber numberWithLong:4L]);
  719. snapshot =
  720. [self readSnapshotForAggregate:[[self compositeIndexQuery:[[[collRef queryWhereField:@"key"
  721. isGreaterThan:@"a"]
  722. queryWhereField:@"sort"
  723. isGreaterThanOrEqualTo:@1]
  724. queryWhereField:@"v"
  725. isNotEqualTo:@0]]
  726. aggregate:@[
  727. [FIRAggregateField aggregateFieldForCount],
  728. [FIRAggregateField aggregateFieldForSumOfField:@"sort"],
  729. [FIRAggregateField aggregateFieldForAverageOfField:@"v"],
  730. ]]];
  731. XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]],
  732. [NSNumber numberWithLong:3L]);
  733. XCTAssertEqual(
  734. [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"sort"]]
  735. longValue],
  736. 6L);
  737. XCTAssertEqual(
  738. [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"v"]],
  739. [NSNumber numberWithDouble:1.0]);
  740. }
  741. - (void)testMultipleInequalityFieldsWithDocumentKey {
  742. FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{
  743. @"doc1" : @{@"key" : @"a", @"sort" : @5},
  744. @"doc2" : @{@"key" : @"aa", @"sort" : @4},
  745. @"doc3" : @{@"key" : @"b", @"sort" : @3},
  746. @"doc4" : @{@"key" : @"b", @"sort" : @2},
  747. @"doc5" : @{@"key" : @"bb", @"sort" : @1}
  748. }];
  749. FIRQuery *query = [[[collRef queryWhereField:@"sort" isGreaterThan:@1]
  750. queryWhereField:@"key"
  751. isNotEqualTo:@"a"] queryWhereFieldPath:[FIRFieldPath documentID] isLessThan:@"doc5"];
  752. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  753. // Document Key in inequality field will implicitly ordered to the last.
  754. // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc
  755. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc2", @"doc4", @"doc3" ])];
  756. query = [[[collRef queryWhereFieldPath:[FIRFieldPath documentID]
  757. isLessThan:@"doc5"] queryWhereField:@"sort"
  758. isGreaterThan:@1] queryWhereField:@"key"
  759. isNotEqualTo:@"a"];
  760. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  761. // Changing filters order will not effect implicit order.
  762. // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc
  763. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc2", @"doc4", @"doc3" ])];
  764. query = [[[[collRef queryWhereFieldPath:[FIRFieldPath documentID]
  765. isLessThan:@"doc5"] queryWhereField:@"sort" isGreaterThan:@1]
  766. queryWhereField:@"key"
  767. isNotEqualTo:@"a"] queryOrderedByField:@"sort" descending:YES];
  768. snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]];
  769. // Ordered by: 'sort' desc,'key' desc, __name__ desc
  770. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc2", @"doc3", @"doc4" ])];
  771. }
  772. - (void)testMultipleInequalityReadFromCacheWhenOffline {
  773. FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{
  774. @"doc1" : @{@"key" : @"a", @"sort" : @1},
  775. @"doc2" : @{@"key" : @"aa", @"sort" : @4},
  776. @"doc3" : @{@"key" : @"b", @"sort" : @3},
  777. @"doc4" : @{@"key" : @"b", @"sort" : @2},
  778. }];
  779. FIRQuery *query = [self compositeIndexQuery:[[collRef queryWhereField:@"key" isNotEqualTo:@"a"]
  780. queryWhereField:@"sort"
  781. isLessThanOrEqualTo:@3]];
  782. // populate the cache.
  783. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:query];
  784. XCTAssertEqual(snapshot.count, 2L);
  785. XCTAssertEqual(snapshot.metadata.isFromCache, NO);
  786. [self disableNetwork];
  787. snapshot = [self readDocumentSetForRef:query];
  788. XCTAssertEqual(snapshot.count, 2L);
  789. XCTAssertEqual(snapshot.metadata.isFromCache, YES);
  790. // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc
  791. [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc4", @"doc3" ])];
  792. }
  793. - (void)testMultipleInequalityFromCacheAndFromServer {
  794. FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{
  795. @"doc1" : @{@"a" : @1, @"b" : @0},
  796. @"doc2" : @{@"a" : @2, @"b" : @1},
  797. @"doc3" : @{@"a" : @3, @"b" : @2},
  798. @"doc4" : @{@"a" : @1, @"b" : @3},
  799. @"doc5" : @{@"a" : @1, @"b" : @1},
  800. }];
  801. // implicit AND: a != 1 && b < 2
  802. FIRQuery *query = [[collRef queryWhereField:@"a" isNotEqualTo:@1] queryWhereField:@"b"
  803. isLessThan:@2];
  804. [self assertOnlineAndOfflineResultsMatch:collRef
  805. withQuery:[self compositeIndexQuery:query]
  806. expectedDocs:@[ @"doc2" ]];
  807. // explicit AND: a != 1 && b < 2
  808. query =
  809. [collRef queryWhereFilter:[FIRFilter andFilterWithFilters:@[
  810. [FIRFilter filterWhereField:@"a" isNotEqualTo:@1], [FIRFilter filterWhereField:@"b"
  811. isLessThan:@2]
  812. ]]];
  813. [self assertOnlineAndOfflineResultsMatch:collRef
  814. withQuery:[self compositeIndexQuery:query]
  815. expectedDocs:@[ @"doc2" ]];
  816. // explicit AND: a < 3 && b not-in [2, 3]
  817. // Implicitly ordered by: a asc, b asc, __name__ asc
  818. query = [collRef
  819. queryWhereFilter:[FIRFilter andFilterWithFilters:@[
  820. [FIRFilter filterWhereField:@"a" isLessThan:@3], [FIRFilter filterWhereField:@"b"
  821. notIn:@[ @2, @3 ]]
  822. ]]];
  823. [self assertOnlineAndOfflineResultsMatch:collRef
  824. withQuery:[self compositeIndexQuery:query]
  825. expectedDocs:@[ @"doc1", @"doc5", @"doc2" ]];
  826. // a <3 && b != 0, ordered by: b desc, a desc, __name__ desc
  827. query = [[[[collRef queryWhereField:@"a" isLessThan:@3] queryWhereField:@"b" isNotEqualTo:@0]
  828. queryOrderedByField:@"b"
  829. descending:YES] queryLimitedTo:2];
  830. [self assertOnlineAndOfflineResultsMatch:collRef
  831. withQuery:[self compositeIndexQuery:query]
  832. expectedDocs:@[ @"doc4", @"doc2" ]];
  833. // explicit OR: a>2 || b<1.
  834. query = [collRef
  835. queryWhereFilter:[FIRFilter orFilterWithFilters:@[
  836. [FIRFilter filterWhereField:@"a" isGreaterThan:@2], [FIRFilter filterWhereField:@"b"
  837. isLessThan:@1]
  838. ]]];
  839. [self assertOnlineAndOfflineResultsMatch:collRef
  840. withQuery:[self compositeIndexQuery:query]
  841. expectedDocs:@[ @"doc1", @"doc3" ]];
  842. }
  843. - (void)testMultipleInequalityRejectsIfDocumentKeyIsNotTheLastOrderByField {
  844. FIRCollectionReference *collRef = [self collectionRef];
  845. FIRQuery *query = [[collRef queryWhereField:@"key" isNotEqualTo:@42]
  846. queryOrderedByFieldPath:[FIRFieldPath documentID]];
  847. XCTestExpectation *queryCompletion = [self expectationWithDescription:@"query"];
  848. [query getDocumentsWithCompletion:^(FIRQuerySnapshot *results, NSError *error) {
  849. XCTAssertNil(results);
  850. XCTAssertNotNil(error);
  851. XCTAssertEqual(error.code, FIRFirestoreErrorCodeInvalidArgument);
  852. [queryCompletion fulfill];
  853. }];
  854. [self awaitExpectations];
  855. }
  856. - (void)testMultipleInequalityRejectsIfDocumentKeyAppearsOnlyInEqualityFilter {
  857. FIRCollectionReference *collRef = [self collectionRef];
  858. FIRQuery *query = [[collRef queryWhereField:@"key"
  859. isNotEqualTo:@42] queryWhereFieldPath:[FIRFieldPath documentID]
  860. isEqualTo:@"doc1"];
  861. XCTestExpectation *queryCompletion = [self expectationWithDescription:@"query"];
  862. [query getDocumentsWithCompletion:^(FIRQuerySnapshot *results, NSError *error) {
  863. XCTAssertNil(results);
  864. XCTAssertNotNil(error);
  865. XCTAssertEqual(error.code, FIRFirestoreErrorCodeInvalidArgument);
  866. [queryCompletion fulfill];
  867. }];
  868. [self awaitExpectations];
  869. }
  870. @end
  871. NS_ASSUME_NONNULL_END