FSTQuery.mm 25 KB

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