FSTQuery.mm 28 KB

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