FSTQuery.mm 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876
  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 "Firestore/Source/Core/FSTQuery.h"
  17. #include <memory>
  18. #include <string>
  19. #include <utility>
  20. #import "Firestore/Source/API/FIRFirestore+Internal.h"
  21. #import "Firestore/Source/Model/FSTDocument.h"
  22. #import "Firestore/Source/Model/FSTFieldValue.h"
  23. #import "Firestore/Source/Util/FSTClasses.h"
  24. #import "Firestore/Source/Util/FSTUsageValidation.h"
  25. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  26. #include "Firestore/core/src/firebase/firestore/model/field_path.h"
  27. #include "Firestore/core/src/firebase/firestore/model/resource_path.h"
  28. #include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
  29. #include "Firestore/core/src/firebase/firestore/util/hashing.h"
  30. #include "Firestore/core/src/firebase/firestore/util/string_apple.h"
  31. namespace util = firebase::firestore::util;
  32. using firebase::firestore::model::DocumentKey;
  33. using firebase::firestore::model::FieldPath;
  34. using firebase::firestore::model::ResourcePath;
  35. NS_ASSUME_NONNULL_BEGIN
  36. #pragma mark - FSTRelationFilterOperator functions
  37. /**
  38. * Returns the reverse order (i.e. Ascending => Descending) etc.
  39. */
  40. static constexpr NSComparisonResult ReverseOrder(NSComparisonResult result) {
  41. return static_cast<NSComparisonResult>(-static_cast<NSInteger>(result));
  42. }
  43. NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOperator) {
  44. switch (filterOperator) {
  45. case FSTRelationFilterOperatorLessThan:
  46. return @"<";
  47. case FSTRelationFilterOperatorLessThanOrEqual:
  48. return @"<=";
  49. case FSTRelationFilterOperatorEqual:
  50. return @"==";
  51. case FSTRelationFilterOperatorGreaterThanOrEqual:
  52. return @">=";
  53. case FSTRelationFilterOperatorGreaterThan:
  54. return @">";
  55. case FSTRelationFilterOperatorArrayContains:
  56. return @"array_contains";
  57. default:
  58. HARD_FAIL("Unknown FSTRelationFilterOperator %s", filterOperator);
  59. }
  60. }
  61. @implementation FSTFilter
  62. + (instancetype)filterWithField:(const FieldPath &)field
  63. filterOperator:(FSTRelationFilterOperator)op
  64. value:(FSTFieldValue *)value {
  65. if ([value isEqual:[FSTNullValue nullValue]]) {
  66. if (op != FSTRelationFilterOperatorEqual) {
  67. FSTThrowInvalidUsage(@"InvalidQueryException",
  68. @"Invalid Query. You can only perform equality comparisons on nil / "
  69. "NSNull.");
  70. }
  71. return [[FSTNullFilter alloc] initWithField:field];
  72. } else if ([value isEqual:[FSTDoubleValue nanValue]]) {
  73. if (op != FSTRelationFilterOperatorEqual) {
  74. FSTThrowInvalidUsage(@"InvalidQueryException",
  75. @"Invalid Query. You can only perform equality comparisons on NaN.");
  76. }
  77. return [[FSTNanFilter alloc] initWithField:field];
  78. } else {
  79. return [[FSTRelationFilter alloc] initWithField:field filterOperator:op value:value];
  80. }
  81. }
  82. - (const FieldPath &)field {
  83. @throw FSTAbstractMethodException(); // NOLINT
  84. }
  85. - (BOOL)matchesDocument:(FSTDocument *)document {
  86. @throw FSTAbstractMethodException(); // NOLINT
  87. }
  88. - (NSString *)canonicalID {
  89. @throw FSTAbstractMethodException(); // NOLINT
  90. }
  91. @end
  92. #pragma mark - FSTRelationFilter
  93. @interface FSTRelationFilter () {
  94. /** The left hand side of the relation. A path into a document field. */
  95. firebase::firestore::model::FieldPath _field;
  96. }
  97. /**
  98. * Initializes the receiver relation filter.
  99. *
  100. * @param field A path to a field in the document to filter on. The LHS of the expression.
  101. * @param filterOperator The binary operator to apply.
  102. * @param value A constant value to compare @a field to. The RHS of the expression.
  103. */
  104. - (instancetype)initWithField:(FieldPath)field
  105. filterOperator:(FSTRelationFilterOperator)filterOperator
  106. value:(FSTFieldValue *)value NS_DESIGNATED_INITIALIZER;
  107. /** Returns YES if @a document matches the receiver's constraint. */
  108. - (BOOL)matchesDocument:(FSTDocument *)document;
  109. /**
  110. * A canonical string identifying the filter. Two different instances of equivalent filters will
  111. * return the same canonicalID.
  112. */
  113. - (NSString *)canonicalID;
  114. @end
  115. @implementation FSTRelationFilter
  116. #pragma mark - Constructor methods
  117. - (instancetype)initWithField:(FieldPath)field
  118. filterOperator:(FSTRelationFilterOperator)filterOperator
  119. value:(FSTFieldValue *)value {
  120. self = [super init];
  121. if (self) {
  122. _field = std::move(field);
  123. _filterOperator = filterOperator;
  124. _value = value;
  125. }
  126. return self;
  127. }
  128. #pragma mark - Public Methods
  129. - (BOOL)isInequality {
  130. return self.filterOperator != FSTRelationFilterOperatorEqual &&
  131. self.filterOperator != FSTRelationFilterOperatorArrayContains;
  132. }
  133. - (const firebase::firestore::model::FieldPath &)field {
  134. return _field;
  135. }
  136. #pragma mark - NSObject methods
  137. - (NSString *)description {
  138. return [NSString stringWithFormat:@"%s %@ %@", _field.CanonicalString().c_str(),
  139. FSTStringFromQueryRelationOperator(self.filterOperator),
  140. self.value];
  141. }
  142. - (BOOL)isEqual:(id)other {
  143. if (self == other) {
  144. return YES;
  145. }
  146. if (![other isKindOfClass:[FSTRelationFilter class]]) {
  147. return NO;
  148. }
  149. return [self isEqualToFilter:(FSTRelationFilter *)other];
  150. }
  151. #pragma mark - Private methods
  152. - (BOOL)matchesDocument:(FSTDocument *)document {
  153. if (_field.IsKeyFieldPath()) {
  154. HARD_ASSERT([self.value isKindOfClass:[FSTReferenceValue class]],
  155. "Comparing on key, but filter value not a FSTReferenceValue.");
  156. HARD_ASSERT(self.filterOperator != FSTRelationFilterOperatorArrayContains,
  157. "arrayContains queries don't make sense on document keys.");
  158. FSTReferenceValue *refValue = (FSTReferenceValue *)self.value;
  159. NSComparisonResult comparison = CompareKeys(document.key, refValue.value.key);
  160. return [self matchesComparison:comparison];
  161. } else {
  162. return [self matchesValue:[document fieldForPath:self.field]];
  163. }
  164. }
  165. - (NSString *)canonicalID {
  166. // TODO(b/37283291): This should be collision robust and avoid relying on |description| methods.
  167. return [NSString stringWithFormat:@"%s%@%@", _field.CanonicalString().c_str(),
  168. FSTStringFromQueryRelationOperator(self.filterOperator),
  169. [self.value value]];
  170. }
  171. - (BOOL)isEqualToFilter:(FSTRelationFilter *)other {
  172. if (self.filterOperator != other.filterOperator) {
  173. return NO;
  174. }
  175. if (_field != other.field) {
  176. return NO;
  177. }
  178. if (![self.value isEqual:other.value]) {
  179. return NO;
  180. }
  181. return YES;
  182. }
  183. /** Returns YES if receiver is true with the given value as its LHS. */
  184. - (BOOL)matchesValue:(FSTFieldValue *)other {
  185. if (self.filterOperator == FSTRelationFilterOperatorArrayContains) {
  186. if ([other isMemberOfClass:[FSTArrayValue class]]) {
  187. FSTArrayValue *arrayValue = (FSTArrayValue *)other;
  188. return [arrayValue.internalValue containsObject:self.value];
  189. } else {
  190. return false;
  191. }
  192. } else {
  193. // Only perform comparison queries on types with matching backend order (such as double and
  194. // int).
  195. return self.value.typeOrder == other.typeOrder &&
  196. [self matchesComparison:[other compare:self.value]];
  197. }
  198. }
  199. - (BOOL)matchesComparison:(NSComparisonResult)comparison {
  200. switch (self.filterOperator) {
  201. case FSTRelationFilterOperatorLessThan:
  202. return comparison == NSOrderedAscending;
  203. case FSTRelationFilterOperatorLessThanOrEqual:
  204. return comparison == NSOrderedAscending || comparison == NSOrderedSame;
  205. case FSTRelationFilterOperatorEqual:
  206. return comparison == NSOrderedSame;
  207. case FSTRelationFilterOperatorGreaterThanOrEqual:
  208. return comparison == NSOrderedDescending || comparison == NSOrderedSame;
  209. case FSTRelationFilterOperatorGreaterThan:
  210. return comparison == NSOrderedDescending;
  211. default:
  212. HARD_FAIL("Unknown operator: %s", self.filterOperator);
  213. }
  214. }
  215. @end
  216. #pragma mark - FSTNullFilter
  217. @interface FSTNullFilter () {
  218. FieldPath _field;
  219. }
  220. @end
  221. @implementation FSTNullFilter
  222. - (instancetype)initWithField:(FieldPath)field {
  223. if (self = [super init]) {
  224. _field = std::move(field);
  225. }
  226. return self;
  227. }
  228. - (BOOL)matchesDocument:(FSTDocument *)document {
  229. FSTFieldValue *fieldValue = [document fieldForPath:self.field];
  230. return fieldValue != nil && [fieldValue isEqual:[FSTNullValue nullValue]];
  231. }
  232. - (NSString *)canonicalID {
  233. return [NSString stringWithFormat:@"%s IS NULL", _field.CanonicalString().c_str()];
  234. }
  235. - (const firebase::firestore::model::FieldPath &)field {
  236. return _field;
  237. }
  238. - (NSString *)description {
  239. return [self canonicalID];
  240. }
  241. - (BOOL)isEqual:(id)other {
  242. if (other == self) return YES;
  243. if (![[other class] isEqual:[self class]]) return NO;
  244. return _field == ((FSTNullFilter *)other)->_field;
  245. }
  246. - (NSUInteger)hash {
  247. return util::Hash(_field);
  248. }
  249. @end
  250. #pragma mark - FSTNanFilter
  251. @interface FSTNanFilter () {
  252. FieldPath _field;
  253. }
  254. @end
  255. @implementation FSTNanFilter
  256. - (instancetype)initWithField:(FieldPath)field {
  257. if (self = [super init]) {
  258. _field = std::move(field);
  259. }
  260. return self;
  261. }
  262. - (BOOL)matchesDocument:(FSTDocument *)document {
  263. FSTFieldValue *fieldValue = [document fieldForPath:self.field];
  264. return fieldValue != nil && [fieldValue isEqual:[FSTDoubleValue nanValue]];
  265. }
  266. - (NSString *)canonicalID {
  267. return [NSString stringWithFormat:@"%s IS NaN", _field.CanonicalString().c_str()];
  268. }
  269. - (const firebase::firestore::model::FieldPath &)field {
  270. return _field;
  271. }
  272. - (NSString *)description {
  273. return [self canonicalID];
  274. }
  275. - (BOOL)isEqual:(id)other {
  276. if (other == self) return YES;
  277. if (![[other class] isEqual:[self class]]) return NO;
  278. return _field == ((FSTNanFilter *)other)->_field;
  279. }
  280. - (NSUInteger)hash {
  281. return util::Hash(_field);
  282. }
  283. @end
  284. #pragma mark - FSTSortOrder
  285. @interface FSTSortOrder () {
  286. /** The field to sort by. */
  287. firebase::firestore::model::FieldPath _field;
  288. }
  289. /** Creates a new sort order with the given field and direction. */
  290. - (instancetype)initWithFieldPath:(FieldPath)fieldPath ascending:(BOOL)ascending;
  291. - (NSString *)canonicalID;
  292. @end
  293. @implementation FSTSortOrder
  294. #pragma mark - Constructor methods
  295. + (instancetype)sortOrderWithFieldPath:(FieldPath)fieldPath ascending:(BOOL)ascending {
  296. return [[FSTSortOrder alloc] initWithFieldPath:std::move(fieldPath) ascending:ascending];
  297. }
  298. - (instancetype)initWithFieldPath:(FieldPath)fieldPath ascending:(BOOL)ascending {
  299. self = [super init];
  300. if (self) {
  301. _field = std::move(fieldPath);
  302. _ascending = ascending;
  303. }
  304. return self;
  305. }
  306. - (const firebase::firestore::model::FieldPath &)field {
  307. return _field;
  308. }
  309. #pragma mark - Public methods
  310. - (NSComparisonResult)compareDocument:(FSTDocument *)document1 toDocument:(FSTDocument *)document2 {
  311. NSComparisonResult result;
  312. if (_field == FieldPath::KeyFieldPath()) {
  313. result = CompareKeys(document1.key, document2.key);
  314. } else {
  315. FSTFieldValue *value1 = [document1 fieldForPath:self.field];
  316. FSTFieldValue *value2 = [document2 fieldForPath:self.field];
  317. HARD_ASSERT(value1 != nil && value2 != nil,
  318. "Trying to compare documents on fields that don't exist.");
  319. result = [value1 compare:value2];
  320. }
  321. if (!self.isAscending) {
  322. result = ReverseOrder(result);
  323. }
  324. return result;
  325. }
  326. - (NSString *)canonicalID {
  327. return [NSString stringWithFormat:@"%s%@", _field.CanonicalString().c_str(),
  328. self.isAscending ? @"asc" : @"desc"];
  329. }
  330. - (BOOL)isEqualToSortOrder:(FSTSortOrder *)other {
  331. return _field == other->_field && self.isAscending == other.isAscending;
  332. }
  333. #pragma mark - NSObject methods
  334. - (NSString *)description {
  335. return [NSString stringWithFormat:@"<FSTSortOrder: path:%s dir:%@>",
  336. _field.CanonicalString().c_str(),
  337. self.ascending ? @"asc" : @"desc"];
  338. }
  339. - (BOOL)isEqual:(NSObject *)other {
  340. if (self == other) {
  341. return YES;
  342. }
  343. if (![other isKindOfClass:[FSTSortOrder class]]) {
  344. return NO;
  345. }
  346. return [self isEqualToSortOrder:(FSTSortOrder *)other];
  347. }
  348. - (NSUInteger)hash {
  349. return [self.canonicalID hash];
  350. }
  351. - (instancetype)copyWithZone:(nullable NSZone *)zone {
  352. return self;
  353. }
  354. @end
  355. #pragma mark - FSTBound
  356. @implementation FSTBound
  357. - (instancetype)initWithPosition:(NSArray<FSTFieldValue *> *)position isBefore:(BOOL)isBefore {
  358. if (self = [super init]) {
  359. _position = position;
  360. _before = isBefore;
  361. }
  362. return self;
  363. }
  364. + (instancetype)boundWithPosition:(NSArray<FSTFieldValue *> *)position isBefore:(BOOL)isBefore {
  365. return [[FSTBound alloc] initWithPosition:position isBefore:isBefore];
  366. }
  367. - (NSString *)canonicalString {
  368. // TODO(b/29183165): Make this collision robust.
  369. NSMutableString *string = [NSMutableString string];
  370. if (self.isBefore) {
  371. [string appendString:@"b:"];
  372. } else {
  373. [string appendString:@"a:"];
  374. }
  375. for (FSTFieldValue *component in self.position) {
  376. [string appendFormat:@"%@", component];
  377. }
  378. return string;
  379. }
  380. - (BOOL)sortsBeforeDocument:(FSTDocument *)document
  381. usingSortOrder:(NSArray<FSTSortOrder *> *)sortOrder {
  382. HARD_ASSERT(self.position.count <= sortOrder.count,
  383. "FSTIndexPosition has more components than provided sort order.");
  384. __block NSComparisonResult result = NSOrderedSame;
  385. [self.position enumerateObjectsUsingBlock:^(FSTFieldValue *fieldValue, NSUInteger idx,
  386. BOOL *stop) {
  387. FSTSortOrder *sortOrderComponent = sortOrder[idx];
  388. NSComparisonResult comparison;
  389. if (sortOrderComponent.field == FieldPath::KeyFieldPath()) {
  390. HARD_ASSERT([fieldValue isKindOfClass:[FSTReferenceValue class]],
  391. "FSTBound has a non-key value where the key path is being used %s", fieldValue);
  392. FSTReferenceValue *refValue = (FSTReferenceValue *)fieldValue;
  393. comparison = CompareKeys(refValue.value.key, document.key);
  394. } else {
  395. FSTFieldValue *docValue = [document fieldForPath:sortOrderComponent.field];
  396. HARD_ASSERT(docValue != nil,
  397. "Field should exist since document matched the orderBy already.");
  398. comparison = [fieldValue compare:docValue];
  399. }
  400. if (!sortOrderComponent.isAscending) {
  401. comparison = ReverseOrder(comparison);
  402. }
  403. if (comparison != 0) {
  404. result = comparison;
  405. *stop = YES;
  406. }
  407. }];
  408. return self.isBefore ? result <= NSOrderedSame : result < NSOrderedSame;
  409. }
  410. #pragma mark - NSObject methods
  411. - (NSString *)description {
  412. return [NSString stringWithFormat:@"<FSTBound: position:%@ before:%@>", self.position,
  413. self.isBefore ? @"YES" : @"NO"];
  414. }
  415. - (BOOL)isEqual:(NSObject *)other {
  416. if (self == other) {
  417. return YES;
  418. }
  419. if (![other isKindOfClass:[FSTBound class]]) {
  420. return NO;
  421. }
  422. FSTBound *otherBound = (FSTBound *)other;
  423. return [self.position isEqualToArray:otherBound.position] && self.isBefore == otherBound.isBefore;
  424. }
  425. - (NSUInteger)hash {
  426. return 31 * self.position.hash + (self.isBefore ? 0 : 1);
  427. }
  428. - (instancetype)copyWithZone:(nullable NSZone *)zone {
  429. return self;
  430. }
  431. @end
  432. #pragma mark - FSTQuery
  433. @interface FSTQuery () {
  434. // Cached value of the canonicalID property.
  435. NSString *_canonicalID;
  436. /** The base path of the query. */
  437. ResourcePath _path;
  438. }
  439. /**
  440. * Initializes the receiver with the given query constraints.
  441. *
  442. * @param path The base path of the query.
  443. * @param filters Filters specify which documents to include in the results.
  444. * @param sortOrders The fields and directions to sort the results.
  445. * @param limit If not NSNotFound, only this many results will be returned.
  446. */
  447. - (instancetype)initWithPath:(ResourcePath)path
  448. filterBy:(NSArray<FSTFilter *> *)filters
  449. orderBy:(NSArray<FSTSortOrder *> *)sortOrders
  450. limit:(NSInteger)limit
  451. startAt:(nullable FSTBound *)startAtBound
  452. endAt:(nullable FSTBound *)endAtBound NS_DESIGNATED_INITIALIZER;
  453. /** A list of fields given to sort by. This does not include the implicit key sort at the end. */
  454. @property(nonatomic, strong, readonly) NSArray<FSTSortOrder *> *explicitSortOrders;
  455. /** The memoized list of sort orders */
  456. @property(nonatomic, nullable, strong, readwrite) NSArray<FSTSortOrder *> *memoizedSortOrders;
  457. @end
  458. @implementation FSTQuery
  459. #pragma mark - Constructors
  460. + (instancetype)queryWithPath:(ResourcePath)path {
  461. return [[FSTQuery alloc] initWithPath:std::move(path)
  462. filterBy:@[]
  463. orderBy:@[]
  464. limit:NSNotFound
  465. startAt:nil
  466. endAt:nil];
  467. }
  468. - (instancetype)initWithPath:(ResourcePath)path
  469. filterBy:(NSArray<FSTFilter *> *)filters
  470. orderBy:(NSArray<FSTSortOrder *> *)sortOrders
  471. limit:(NSInteger)limit
  472. startAt:(nullable FSTBound *)startAtBound
  473. endAt:(nullable FSTBound *)endAtBound {
  474. if (self = [super init]) {
  475. _path = std::move(path);
  476. _filters = filters;
  477. _explicitSortOrders = sortOrders;
  478. _limit = limit;
  479. _startAt = startAtBound;
  480. _endAt = endAtBound;
  481. }
  482. return self;
  483. }
  484. #pragma mark - NSObject methods
  485. - (NSString *)description {
  486. return [NSString stringWithFormat:@"<FSTQuery: canonicalID:%@>", self.canonicalID];
  487. }
  488. - (BOOL)isEqual:(id)object {
  489. if (self == object) {
  490. return YES;
  491. }
  492. if (![object isKindOfClass:[FSTQuery class]]) {
  493. return NO;
  494. }
  495. return [self isEqualToQuery:(FSTQuery *)object];
  496. }
  497. - (NSUInteger)hash {
  498. return [self.canonicalID hash];
  499. }
  500. - (instancetype)copyWithZone:(nullable NSZone *)zone {
  501. return self;
  502. }
  503. #pragma mark - Public methods
  504. - (NSArray *)sortOrders {
  505. if (self.memoizedSortOrders == nil) {
  506. const FieldPath *inequalityField = [self inequalityFilterField];
  507. const FieldPath *firstSortOrderField = [self firstSortOrderField];
  508. if (inequalityField && !firstSortOrderField) {
  509. // In order to implicitly add key ordering, we must also add the inequality filter field for
  510. // it to be a valid query. Note that the default inequality field and key ordering is
  511. // ascending.
  512. if (inequalityField->IsKeyFieldPath()) {
  513. self.memoizedSortOrders = @[ [FSTSortOrder sortOrderWithFieldPath:FieldPath::KeyFieldPath()
  514. ascending:YES] ];
  515. } else {
  516. self.memoizedSortOrders = @[
  517. [FSTSortOrder sortOrderWithFieldPath:*inequalityField ascending:YES],
  518. [FSTSortOrder sortOrderWithFieldPath:FieldPath::KeyFieldPath() ascending:YES]
  519. ];
  520. }
  521. } else {
  522. HARD_ASSERT(!inequalityField || *inequalityField == *firstSortOrderField,
  523. "First orderBy %s should match inequality field %s.",
  524. firstSortOrderField->CanonicalString(), inequalityField->CanonicalString());
  525. __block BOOL foundKeyOrder = NO;
  526. NSMutableArray *result = [NSMutableArray array];
  527. for (FSTSortOrder *sortOrder in self.explicitSortOrders) {
  528. [result addObject:sortOrder];
  529. if (sortOrder.field == FieldPath::KeyFieldPath()) {
  530. foundKeyOrder = YES;
  531. }
  532. }
  533. if (!foundKeyOrder) {
  534. // The direction of the implicit key ordering always matches the direction of the last
  535. // explicit sort order
  536. BOOL lastIsAscending =
  537. self.explicitSortOrders.count > 0 ? self.explicitSortOrders.lastObject.ascending : YES;
  538. [result addObject:[FSTSortOrder sortOrderWithFieldPath:FieldPath::KeyFieldPath()
  539. ascending:lastIsAscending]];
  540. }
  541. self.memoizedSortOrders = result;
  542. }
  543. }
  544. return self.memoizedSortOrders;
  545. }
  546. - (instancetype)queryByAddingFilter:(FSTFilter *)filter {
  547. HARD_ASSERT(!DocumentKey::IsDocumentKey(_path), "No filtering allowed for document query");
  548. const FieldPath *newInequalityField = nullptr;
  549. if ([filter isKindOfClass:[FSTRelationFilter class]] &&
  550. [((FSTRelationFilter *)filter) isInequality]) {
  551. newInequalityField = &filter.field;
  552. }
  553. const FieldPath *queryInequalityField = [self inequalityFilterField];
  554. HARD_ASSERT(
  555. !queryInequalityField || !newInequalityField || *queryInequalityField == *newInequalityField,
  556. "Query must only have one inequality field.");
  557. return [[FSTQuery alloc] initWithPath:self.path
  558. filterBy:[self.filters arrayByAddingObject:filter]
  559. orderBy:self.explicitSortOrders
  560. limit:self.limit
  561. startAt:self.startAt
  562. endAt:self.endAt];
  563. }
  564. - (instancetype)queryByAddingSortOrder:(FSTSortOrder *)sortOrder {
  565. HARD_ASSERT(!DocumentKey::IsDocumentKey(_path), "No ordering is allowed for a document query.");
  566. // TODO(klimt): Validate that the same key isn't added twice.
  567. return [[FSTQuery alloc] initWithPath:self.path
  568. filterBy:self.filters
  569. orderBy:[self.explicitSortOrders arrayByAddingObject:sortOrder]
  570. limit:self.limit
  571. startAt:self.startAt
  572. endAt:self.endAt];
  573. }
  574. - (instancetype)queryBySettingLimit:(NSInteger)limit {
  575. return [[FSTQuery alloc] initWithPath:self.path
  576. filterBy:self.filters
  577. orderBy:self.explicitSortOrders
  578. limit:limit
  579. startAt:self.startAt
  580. endAt:self.endAt];
  581. }
  582. - (instancetype)queryByAddingStartAt:(FSTBound *)bound {
  583. return [[FSTQuery alloc] initWithPath:self.path
  584. filterBy:self.filters
  585. orderBy:self.explicitSortOrders
  586. limit:self.limit
  587. startAt:bound
  588. endAt:self.endAt];
  589. }
  590. - (instancetype)queryByAddingEndAt:(FSTBound *)bound {
  591. return [[FSTQuery alloc] initWithPath:self.path
  592. filterBy:self.filters
  593. orderBy:self.explicitSortOrders
  594. limit:self.limit
  595. startAt:self.startAt
  596. endAt:bound];
  597. }
  598. - (BOOL)isDocumentQuery {
  599. return DocumentKey::IsDocumentKey(_path) && self.filters.count == 0;
  600. }
  601. - (BOOL)matchesDocument:(FSTDocument *)document {
  602. return [self pathMatchesDocument:document] && [self orderByMatchesDocument:document] &&
  603. [self filtersMatchDocument:document] && [self boundsMatchDocument:document];
  604. }
  605. - (NSComparator)comparator {
  606. return ^NSComparisonResult(id document1, id document2) {
  607. BOOL didCompareOnKeyField = NO;
  608. for (FSTSortOrder *orderBy in self.sortOrders) {
  609. NSComparisonResult comp = [orderBy compareDocument:document1 toDocument:document2];
  610. if (comp != NSOrderedSame) {
  611. return comp;
  612. }
  613. didCompareOnKeyField = didCompareOnKeyField || orderBy.field == FieldPath::KeyFieldPath();
  614. }
  615. HARD_ASSERT(didCompareOnKeyField, "sortOrder of query did not include key ordering");
  616. return NSOrderedSame;
  617. };
  618. }
  619. - (nullable const FieldPath *)inequalityFilterField {
  620. for (FSTFilter *filter in self.filters) {
  621. if ([filter isKindOfClass:[FSTRelationFilter class]] &&
  622. ((FSTRelationFilter *)filter).isInequality) {
  623. return &filter.field;
  624. }
  625. }
  626. return nullptr;
  627. }
  628. - (BOOL)hasArrayContainsFilter {
  629. for (FSTFilter *filter in self.filters) {
  630. if ([filter isKindOfClass:[FSTRelationFilter class]] &&
  631. ((FSTRelationFilter *)filter).filterOperator == FSTRelationFilterOperatorArrayContains) {
  632. return YES;
  633. }
  634. }
  635. return NO;
  636. }
  637. - (nullable const FieldPath *)firstSortOrderField {
  638. if (self.explicitSortOrders.count > 0) {
  639. return &self.explicitSortOrders.firstObject.field;
  640. }
  641. return nullptr;
  642. }
  643. /** The base path of the query. */
  644. - (const firebase::firestore::model::ResourcePath &)path {
  645. return _path;
  646. }
  647. #pragma mark - Private properties
  648. - (NSString *)canonicalID {
  649. if (_canonicalID) {
  650. return _canonicalID;
  651. }
  652. NSMutableString *canonicalID = [NSMutableString string];
  653. [canonicalID appendFormat:@"%s", _path.CanonicalString().c_str()];
  654. // Add filters.
  655. [canonicalID appendString:@"|f:"];
  656. for (FSTFilter *predicate in self.filters) {
  657. [canonicalID appendFormat:@"%@", [predicate canonicalID]];
  658. }
  659. // Add order by.
  660. [canonicalID appendString:@"|ob:"];
  661. for (FSTSortOrder *orderBy in self.sortOrders) {
  662. [canonicalID appendString:orderBy.canonicalID];
  663. }
  664. // Add limit.
  665. if (self.limit != NSNotFound) {
  666. [canonicalID appendFormat:@"|l:%ld", (long)self.limit];
  667. }
  668. if (self.startAt) {
  669. [canonicalID appendFormat:@"|lb:%@", self.startAt.canonicalString];
  670. }
  671. if (self.endAt) {
  672. [canonicalID appendFormat:@"|ub:%@", self.endAt.canonicalString];
  673. }
  674. _canonicalID = canonicalID;
  675. return canonicalID;
  676. }
  677. #pragma mark - Private methods
  678. - (BOOL)isEqualToQuery:(FSTQuery *)other {
  679. return self.path == other.path && self.limit == other.limit &&
  680. [self.filters isEqual:other.filters] && [self.sortOrders isEqual:other.sortOrders] &&
  681. (self.startAt == other.startAt || [self.startAt isEqual:other.startAt]) &&
  682. (self.endAt == other.endAt || [self.endAt isEqual:other.endAt]);
  683. }
  684. /* Returns YES if the document matches the path for the receiver. */
  685. - (BOOL)pathMatchesDocument:(FSTDocument *)document {
  686. const ResourcePath &documentPath = document.key.path();
  687. if (DocumentKey::IsDocumentKey(_path)) {
  688. // Exact match for document queries.
  689. return self.path == documentPath;
  690. } else {
  691. // Shallow ancestor queries by default.
  692. return self.path.IsPrefixOf(documentPath) && _path.size() == documentPath.size() - 1;
  693. }
  694. }
  695. /**
  696. * A document must have a value for every ordering clause in order to show up in the results.
  697. */
  698. - (BOOL)orderByMatchesDocument:(FSTDocument *)document {
  699. for (FSTSortOrder *orderBy in self.explicitSortOrders) {
  700. const FieldPath &fieldPath = orderBy.field;
  701. // order by key always matches
  702. if (fieldPath != FieldPath::KeyFieldPath() && [document fieldForPath:fieldPath] == nil) {
  703. return NO;
  704. }
  705. }
  706. return YES;
  707. }
  708. /** Returns YES if the document matches all of the filters in the receiver. */
  709. - (BOOL)filtersMatchDocument:(FSTDocument *)document {
  710. for (FSTFilter *filter in self.filters) {
  711. if (![filter matchesDocument:document]) {
  712. return NO;
  713. }
  714. }
  715. return YES;
  716. }
  717. - (BOOL)boundsMatchDocument:(FSTDocument *)document {
  718. if (self.startAt && ![self.startAt sortsBeforeDocument:document usingSortOrder:self.sortOrders]) {
  719. return NO;
  720. }
  721. if (self.endAt && [self.endAt sortsBeforeDocument:document usingSortOrder:self.sortOrders]) {
  722. return NO;
  723. }
  724. return YES;
  725. }
  726. @end
  727. NS_ASSUME_NONNULL_END