FIRQuery.mm 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  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 "FIRQuery.h"
  17. #include <memory>
  18. #include <utility>
  19. #import "FIRDocumentReference.h"
  20. #import "FIRFirestoreErrors.h"
  21. #import "Firestore/Source/API/FIRDocumentReference+Internal.h"
  22. #import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h"
  23. #import "Firestore/Source/API/FIRFieldPath+Internal.h"
  24. #import "Firestore/Source/API/FIRFieldValue+Internal.h"
  25. #import "Firestore/Source/API/FIRFirestore+Internal.h"
  26. #import "Firestore/Source/API/FIRFirestoreSource+Internal.h"
  27. #import "Firestore/Source/API/FIRListenerRegistration+Internal.h"
  28. #import "Firestore/Source/API/FIRQuery+Internal.h"
  29. #import "Firestore/Source/API/FIRQuerySnapshot+Internal.h"
  30. #import "Firestore/Source/API/FIRQuery_Init.h"
  31. #import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h"
  32. #import "Firestore/Source/API/FSTUserDataConverter.h"
  33. #import "Firestore/Source/Core/FSTEventManager.h"
  34. #import "Firestore/Source/Core/FSTFirestoreClient.h"
  35. #import "Firestore/Source/Core/FSTQuery.h"
  36. #import "Firestore/Source/Model/FSTDocument.h"
  37. #import "Firestore/Source/Model/FSTFieldValue.h"
  38. #include "Firestore/core/src/firebase/firestore/api/input_validation.h"
  39. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  40. #include "Firestore/core/src/firebase/firestore/model/field_path.h"
  41. #include "Firestore/core/src/firebase/firestore/model/field_value.h"
  42. #include "Firestore/core/src/firebase/firestore/model/resource_path.h"
  43. #include "Firestore/core/src/firebase/firestore/util/error_apple.h"
  44. #include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
  45. #include "Firestore/core/src/firebase/firestore/util/statusor.h"
  46. #include "Firestore/core/src/firebase/firestore/util/string_apple.h"
  47. namespace util = firebase::firestore::util;
  48. using firebase::firestore::api::MakeSource;
  49. using firebase::firestore::api::Source;
  50. using firebase::firestore::api::ThrowInvalidArgument;
  51. using firebase::firestore::core::AsyncEventListener;
  52. using firebase::firestore::core::EventListener;
  53. using firebase::firestore::core::ViewSnapshot;
  54. using firebase::firestore::model::DocumentKey;
  55. using firebase::firestore::model::FieldPath;
  56. using firebase::firestore::model::FieldValue;
  57. using firebase::firestore::model::ResourcePath;
  58. using firebase::firestore::util::MakeNSError;
  59. using firebase::firestore::util::StatusOr;
  60. NS_ASSUME_NONNULL_BEGIN
  61. @interface FIRQuery ()
  62. @property(nonatomic, strong, readonly) FSTQuery *query;
  63. @end
  64. @implementation FIRQuery (Internal)
  65. + (instancetype)referenceWithQuery:(FSTQuery *)query firestore:(FIRFirestore *)firestore {
  66. return [[FIRQuery alloc] initWithQuery:query firestore:firestore];
  67. }
  68. @end
  69. @implementation FIRQuery
  70. #pragma mark - Constructor Methods
  71. - (instancetype)initWithQuery:(FSTQuery *)query firestore:(FIRFirestore *)firestore {
  72. if (self = [super init]) {
  73. _query = query;
  74. _firestore = firestore;
  75. }
  76. return self;
  77. }
  78. #pragma mark - NSObject Methods
  79. - (BOOL)isEqual:(nullable id)other {
  80. if (other == self) return YES;
  81. if (![[other class] isEqual:[self class]]) return NO;
  82. return [self isEqualToQuery:other];
  83. }
  84. - (BOOL)isEqualToQuery:(nullable FIRQuery *)query {
  85. if (self == query) return YES;
  86. if (query == nil) return NO;
  87. return [self.firestore isEqual:query.firestore] && [self.query isEqual:query.query];
  88. }
  89. - (NSUInteger)hash {
  90. NSUInteger hash = [self.firestore hash];
  91. hash = hash * 31u + [self.query hash];
  92. return hash;
  93. }
  94. #pragma mark - Public Methods
  95. - (void)getDocumentsWithCompletion:(void (^)(FIRQuerySnapshot *_Nullable snapshot,
  96. NSError *_Nullable error))completion {
  97. [self getDocumentsWithSource:FIRFirestoreSourceDefault completion:completion];
  98. }
  99. - (void)getDocumentsWithSource:(FIRFirestoreSource)publicSource
  100. completion:(void (^)(FIRQuerySnapshot *_Nullable snapshot,
  101. NSError *_Nullable error))completion {
  102. Source source = MakeSource(publicSource);
  103. if (source == Source::Cache) {
  104. [self.firestore.client getDocumentsFromLocalCache:self completion:completion];
  105. return;
  106. }
  107. ListenOptions listenOptions(
  108. /*include_query_metadata_changes=*/true,
  109. /*include_document_metadata_changes=*/true,
  110. /*wait_for_sync_when_online=*/true);
  111. dispatch_semaphore_t registered = dispatch_semaphore_create(0);
  112. __block id<FIRListenerRegistration> listenerRegistration;
  113. FIRQuerySnapshotBlock listener = ^(FIRQuerySnapshot *snapshot, NSError *error) {
  114. if (error) {
  115. completion(nil, error);
  116. return;
  117. }
  118. // Remove query first before passing event to user to avoid user actions affecting the
  119. // now stale query.
  120. dispatch_semaphore_wait(registered, DISPATCH_TIME_FOREVER);
  121. [listenerRegistration remove];
  122. if (snapshot.metadata.fromCache && source == Source::Server) {
  123. completion(nil, [NSError errorWithDomain:FIRFirestoreErrorDomain
  124. code:FIRFirestoreErrorCodeUnavailable
  125. userInfo:@{
  126. NSLocalizedDescriptionKey :
  127. @"Failed to get documents from server. (However, these "
  128. @"documents may exist in the local cache. Run again "
  129. @"without setting source to FirestoreSourceServer to "
  130. @"retrieve the cached documents.)"
  131. }]);
  132. } else {
  133. completion(snapshot, nil);
  134. }
  135. };
  136. listenerRegistration = [self addSnapshotListenerInternalWithOptions:listenOptions
  137. listener:listener];
  138. dispatch_semaphore_signal(registered);
  139. }
  140. - (id<FIRListenerRegistration>)addSnapshotListener:(FIRQuerySnapshotBlock)listener {
  141. return [self addSnapshotListenerWithIncludeMetadataChanges:NO listener:listener];
  142. }
  143. - (id<FIRListenerRegistration>)
  144. addSnapshotListenerWithIncludeMetadataChanges:(BOOL)includeMetadataChanges
  145. listener:(FIRQuerySnapshotBlock)listener {
  146. auto options = ListenOptions::FromIncludeMetadataChanges(includeMetadataChanges);
  147. return [self addSnapshotListenerInternalWithOptions:options listener:listener];
  148. }
  149. - (id<FIRListenerRegistration>)addSnapshotListenerInternalWithOptions:(ListenOptions)internalOptions
  150. listener:
  151. (FIRQuerySnapshotBlock)listener {
  152. Firestore *firestore = self.firestore.wrapped;
  153. FSTQuery *query = self.query;
  154. // Convert from ViewSnapshots to QuerySnapshots.
  155. auto view_listener = EventListener<ViewSnapshot>::Create(
  156. [listener, firestore, query](StatusOr<ViewSnapshot> maybe_snapshot) {
  157. if (!maybe_snapshot.status().ok()) {
  158. listener(nil, MakeNSError(maybe_snapshot.status()));
  159. return;
  160. }
  161. ViewSnapshot snapshot = std::move(maybe_snapshot).ValueOrDie();
  162. SnapshotMetadata metadata(snapshot.has_pending_writes(), snapshot.from_cache());
  163. listener([[FIRQuerySnapshot alloc] initWithFirestore:firestore
  164. originalQuery:query
  165. snapshot:std::move(snapshot)
  166. metadata:std::move(metadata)],
  167. nil);
  168. });
  169. // Call the view_listener on the user Executor.
  170. auto async_listener = AsyncEventListener<ViewSnapshot>::Create(firestore->client().userExecutor,
  171. std::move(view_listener));
  172. std::shared_ptr<QueryListener> query_listener =
  173. [firestore->client() listenToQuery:query options:internalOptions listener:async_listener];
  174. return [[FSTListenerRegistration alloc]
  175. initWithRegistration:ListenerRegistration(firestore->client(), std::move(async_listener),
  176. std::move(query_listener))];
  177. }
  178. - (FIRQuery *)queryWhereField:(NSString *)field isEqualTo:(id)value {
  179. return [self queryWithFilterOperator:FSTRelationFilterOperatorEqual field:field value:value];
  180. }
  181. - (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isEqualTo:(id)value {
  182. return [self queryWithFilterOperator:FSTRelationFilterOperatorEqual
  183. path:path.internalValue
  184. value:value];
  185. }
  186. - (FIRQuery *)queryWhereField:(NSString *)field isLessThan:(id)value {
  187. return [self queryWithFilterOperator:FSTRelationFilterOperatorLessThan field:field value:value];
  188. }
  189. - (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isLessThan:(id)value {
  190. return [self queryWithFilterOperator:FSTRelationFilterOperatorLessThan
  191. path:path.internalValue
  192. value:value];
  193. }
  194. - (FIRQuery *)queryWhereField:(NSString *)field isLessThanOrEqualTo:(id)value {
  195. return [self queryWithFilterOperator:FSTRelationFilterOperatorLessThanOrEqual
  196. field:field
  197. value:value];
  198. }
  199. - (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isLessThanOrEqualTo:(id)value {
  200. return [self queryWithFilterOperator:FSTRelationFilterOperatorLessThanOrEqual
  201. path:path.internalValue
  202. value:value];
  203. }
  204. - (FIRQuery *)queryWhereField:(NSString *)field isGreaterThan:(id)value {
  205. return [self queryWithFilterOperator:FSTRelationFilterOperatorGreaterThan
  206. field:field
  207. value:value];
  208. }
  209. - (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isGreaterThan:(id)value {
  210. return [self queryWithFilterOperator:FSTRelationFilterOperatorGreaterThan
  211. path:path.internalValue
  212. value:value];
  213. }
  214. - (FIRQuery *)queryWhereField:(NSString *)field arrayContains:(id)value {
  215. return [self queryWithFilterOperator:FSTRelationFilterOperatorArrayContains
  216. field:field
  217. value:value];
  218. }
  219. - (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path arrayContains:(id)value {
  220. return [self queryWithFilterOperator:FSTRelationFilterOperatorArrayContains
  221. path:path.internalValue
  222. value:value];
  223. }
  224. - (FIRQuery *)queryWhereField:(NSString *)field isGreaterThanOrEqualTo:(id)value {
  225. return [self queryWithFilterOperator:FSTRelationFilterOperatorGreaterThanOrEqual
  226. field:field
  227. value:value];
  228. }
  229. - (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isGreaterThanOrEqualTo:(id)value {
  230. return [self queryWithFilterOperator:FSTRelationFilterOperatorGreaterThanOrEqual
  231. path:path.internalValue
  232. value:value];
  233. }
  234. - (FIRQuery *)queryFilteredUsingComparisonPredicate:(NSPredicate *)predicate {
  235. NSComparisonPredicate *comparison = (NSComparisonPredicate *)predicate;
  236. if (comparison.comparisonPredicateModifier != NSDirectPredicateModifier) {
  237. ThrowInvalidArgument("Invalid query. Predicate cannot have an aggregate modifier.");
  238. }
  239. NSString *path;
  240. id value = nil;
  241. if ([comparison.leftExpression expressionType] == NSKeyPathExpressionType &&
  242. [comparison.rightExpression expressionType] == NSConstantValueExpressionType) {
  243. path = comparison.leftExpression.keyPath;
  244. value = comparison.rightExpression.constantValue;
  245. switch (comparison.predicateOperatorType) {
  246. case NSEqualToPredicateOperatorType:
  247. return [self queryWhereField:path isEqualTo:value];
  248. case NSLessThanPredicateOperatorType:
  249. return [self queryWhereField:path isLessThan:value];
  250. case NSLessThanOrEqualToPredicateOperatorType:
  251. return [self queryWhereField:path isLessThanOrEqualTo:value];
  252. case NSGreaterThanPredicateOperatorType:
  253. return [self queryWhereField:path isGreaterThan:value];
  254. case NSGreaterThanOrEqualToPredicateOperatorType:
  255. return [self queryWhereField:path isGreaterThanOrEqualTo:value];
  256. default:; // Fallback below to throw assertion.
  257. }
  258. } else if ([comparison.leftExpression expressionType] == NSConstantValueExpressionType &&
  259. [comparison.rightExpression expressionType] == NSKeyPathExpressionType) {
  260. path = comparison.rightExpression.keyPath;
  261. value = comparison.leftExpression.constantValue;
  262. switch (comparison.predicateOperatorType) {
  263. case NSEqualToPredicateOperatorType:
  264. return [self queryWhereField:path isEqualTo:value];
  265. case NSLessThanPredicateOperatorType:
  266. return [self queryWhereField:path isGreaterThan:value];
  267. case NSLessThanOrEqualToPredicateOperatorType:
  268. return [self queryWhereField:path isGreaterThanOrEqualTo:value];
  269. case NSGreaterThanPredicateOperatorType:
  270. return [self queryWhereField:path isLessThan:value];
  271. case NSGreaterThanOrEqualToPredicateOperatorType:
  272. return [self queryWhereField:path isLessThanOrEqualTo:value];
  273. default:; // Fallback below to throw assertion.
  274. }
  275. } else {
  276. ThrowInvalidArgument(
  277. "Invalid query. Predicate comparisons must include a key path and a constant.");
  278. }
  279. // Fallback cases of unsupported comparison operator.
  280. switch (comparison.predicateOperatorType) {
  281. case NSCustomSelectorPredicateOperatorType:
  282. ThrowInvalidArgument("Invalid query. Custom predicate filters are not supported.");
  283. break;
  284. default:
  285. ThrowInvalidArgument("Invalid query. Operator type %s is not supported.",
  286. comparison.predicateOperatorType);
  287. }
  288. }
  289. - (FIRQuery *)queryFilteredUsingCompoundPredicate:(NSPredicate *)predicate {
  290. NSCompoundPredicate *compound = (NSCompoundPredicate *)predicate;
  291. if (compound.compoundPredicateType != NSAndPredicateType || compound.subpredicates.count == 0) {
  292. ThrowInvalidArgument("Invalid query. Only compound queries using AND are supported.");
  293. }
  294. FIRQuery *query = self;
  295. for (NSPredicate *pred in compound.subpredicates) {
  296. query = [query queryFilteredUsingPredicate:pred];
  297. }
  298. return query;
  299. }
  300. - (FIRQuery *)queryFilteredUsingPredicate:(NSPredicate *)predicate {
  301. if ([predicate isKindOfClass:[NSComparisonPredicate class]]) {
  302. return [self queryFilteredUsingComparisonPredicate:predicate];
  303. } else if ([predicate isKindOfClass:[NSCompoundPredicate class]]) {
  304. return [self queryFilteredUsingCompoundPredicate:predicate];
  305. } else if ([predicate isKindOfClass:[[NSPredicate
  306. predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) {
  307. return true;
  308. }] class]]) {
  309. ThrowInvalidArgument("Invalid query. Block-based predicates are not supported. Please use "
  310. "predicateWithFormat to create predicates instead.");
  311. } else {
  312. ThrowInvalidArgument("Invalid query. Expect comparison or compound of comparison predicate. "
  313. "Please use predicateWithFormat to create predicates.");
  314. }
  315. }
  316. - (FIRQuery *)queryOrderedByField:(NSString *)field {
  317. return [self queryOrderedByFieldPath:[FIRFieldPath pathWithDotSeparatedString:field]
  318. descending:NO];
  319. }
  320. - (FIRQuery *)queryOrderedByFieldPath:(FIRFieldPath *)fieldPath {
  321. return [self queryOrderedByFieldPath:fieldPath descending:NO];
  322. }
  323. - (FIRQuery *)queryOrderedByField:(NSString *)field descending:(BOOL)descending {
  324. return [self queryOrderedByFieldPath:[FIRFieldPath pathWithDotSeparatedString:field]
  325. descending:descending];
  326. }
  327. - (FIRQuery *)queryOrderedByFieldPath:(FIRFieldPath *)fieldPath descending:(BOOL)descending {
  328. [self validateNewOrderByPath:fieldPath.internalValue];
  329. if (self.query.startAt) {
  330. ThrowInvalidArgument(
  331. "Invalid query. You must not specify a starting point before specifying the order by.");
  332. }
  333. if (self.query.endAt) {
  334. ThrowInvalidArgument(
  335. "Invalid query. You must not specify an ending point before specifying the order by.");
  336. }
  337. FSTSortOrder *sortOrder = [FSTSortOrder sortOrderWithFieldPath:fieldPath.internalValue
  338. ascending:!descending];
  339. return [FIRQuery referenceWithQuery:[self.query queryByAddingSortOrder:sortOrder]
  340. firestore:self.firestore];
  341. }
  342. - (FIRQuery *)queryLimitedTo:(NSInteger)limit {
  343. if (limit <= 0) {
  344. ThrowInvalidArgument("Invalid Query. Query limit (%s) is invalid. Limit must be positive.",
  345. limit);
  346. }
  347. return [FIRQuery referenceWithQuery:[self.query queryBySettingLimit:limit] firestore:_firestore];
  348. }
  349. - (FIRQuery *)queryStartingAtDocument:(FIRDocumentSnapshot *)snapshot {
  350. FSTBound *bound = [self boundFromSnapshot:snapshot isBefore:YES];
  351. return [FIRQuery referenceWithQuery:[self.query queryByAddingStartAt:bound]
  352. firestore:self.firestore];
  353. }
  354. - (FIRQuery *)queryStartingAtValues:(NSArray *)fieldValues {
  355. FSTBound *bound = [self boundFromFieldValues:fieldValues isBefore:YES];
  356. return [FIRQuery referenceWithQuery:[self.query queryByAddingStartAt:bound]
  357. firestore:self.firestore];
  358. }
  359. - (FIRQuery *)queryStartingAfterDocument:(FIRDocumentSnapshot *)snapshot {
  360. FSTBound *bound = [self boundFromSnapshot:snapshot isBefore:NO];
  361. return [FIRQuery referenceWithQuery:[self.query queryByAddingStartAt:bound]
  362. firestore:self.firestore];
  363. }
  364. - (FIRQuery *)queryStartingAfterValues:(NSArray *)fieldValues {
  365. FSTBound *bound = [self boundFromFieldValues:fieldValues isBefore:NO];
  366. return [FIRQuery referenceWithQuery:[self.query queryByAddingStartAt:bound]
  367. firestore:self.firestore];
  368. }
  369. - (FIRQuery *)queryEndingBeforeDocument:(FIRDocumentSnapshot *)snapshot {
  370. FSTBound *bound = [self boundFromSnapshot:snapshot isBefore:YES];
  371. return [FIRQuery referenceWithQuery:[self.query queryByAddingEndAt:bound]
  372. firestore:self.firestore];
  373. }
  374. - (FIRQuery *)queryEndingBeforeValues:(NSArray *)fieldValues {
  375. FSTBound *bound = [self boundFromFieldValues:fieldValues isBefore:YES];
  376. return [FIRQuery referenceWithQuery:[self.query queryByAddingEndAt:bound]
  377. firestore:self.firestore];
  378. }
  379. - (FIRQuery *)queryEndingAtDocument:(FIRDocumentSnapshot *)snapshot {
  380. FSTBound *bound = [self boundFromSnapshot:snapshot isBefore:NO];
  381. return [FIRQuery referenceWithQuery:[self.query queryByAddingEndAt:bound]
  382. firestore:self.firestore];
  383. }
  384. - (FIRQuery *)queryEndingAtValues:(NSArray *)fieldValues {
  385. FSTBound *bound = [self boundFromFieldValues:fieldValues isBefore:NO];
  386. return [FIRQuery referenceWithQuery:[self.query queryByAddingEndAt:bound]
  387. firestore:self.firestore];
  388. }
  389. #pragma mark - Private Methods
  390. /** Private helper for all of the queryWhereField: methods. */
  391. - (FIRQuery *)queryWithFilterOperator:(FSTRelationFilterOperator)filterOperator
  392. field:(NSString *)field
  393. value:(id)value {
  394. return [self queryWithFilterOperator:filterOperator
  395. path:[FIRFieldPath pathWithDotSeparatedString:field].internalValue
  396. value:value];
  397. }
  398. - (FIRQuery *)queryWithFilterOperator:(FSTRelationFilterOperator)filterOperator
  399. path:(const FieldPath &)fieldPath
  400. value:(id)value {
  401. FSTFieldValue *fieldValue;
  402. if (fieldPath.IsKeyFieldPath()) {
  403. if (filterOperator == FSTRelationFilterOperatorArrayContains) {
  404. ThrowInvalidArgument("Invalid query. You can't perform arrayContains queries on document ID "
  405. "since document IDs are not arrays.");
  406. }
  407. if ([value isKindOfClass:[NSString class]]) {
  408. NSString *documentKey = (NSString *)value;
  409. if (documentKey.length == 0) {
  410. ThrowInvalidArgument("Invalid query. When querying by document ID you must provide a valid "
  411. "document ID, but it was an empty string.");
  412. }
  413. if (![self.query isCollectionGroupQuery] && [documentKey containsString:@"/"]) {
  414. ThrowInvalidArgument("Invalid query. When querying a collection by document ID you must "
  415. "provide a plain document ID, but '%s' contains a '/' character.",
  416. documentKey);
  417. }
  418. ResourcePath path =
  419. self.query.path.Append(ResourcePath::FromString([documentKey UTF8String]));
  420. if (!DocumentKey::IsDocumentKey(path)) {
  421. ThrowInvalidArgument("Invalid query. When querying a collection group by document ID, the "
  422. "value provided must result in a valid document path, but '%s' is not "
  423. "because it has an odd number of segments.",
  424. path.CanonicalString());
  425. }
  426. fieldValue =
  427. [FSTReferenceValue referenceValue:[FSTDocumentKey keyWithDocumentKey:DocumentKey{path}]
  428. databaseID:self.firestore.databaseID];
  429. } else if ([value isKindOfClass:[FIRDocumentReference class]]) {
  430. FIRDocumentReference *ref = (FIRDocumentReference *)value;
  431. fieldValue = [FSTReferenceValue referenceValue:[FSTDocumentKey keyWithDocumentKey:ref.key]
  432. databaseID:self.firestore.databaseID];
  433. } else {
  434. ThrowInvalidArgument("Invalid query. When querying by document ID you must provide a valid "
  435. "string or DocumentReference, but it was of type: %s",
  436. NSStringFromClass([value class]));
  437. }
  438. } else {
  439. fieldValue = [self.firestore.dataConverter parsedQueryValue:value];
  440. }
  441. FSTFilter *filter = [FSTFilter filterWithField:fieldPath
  442. filterOperator:filterOperator
  443. value:fieldValue];
  444. if ([filter isKindOfClass:[FSTRelationFilter class]]) {
  445. [self validateNewRelationFilter:(FSTRelationFilter *)filter];
  446. }
  447. return [FIRQuery referenceWithQuery:[self.query queryByAddingFilter:filter]
  448. firestore:self.firestore];
  449. }
  450. - (void)validateNewRelationFilter:(FSTRelationFilter *)filter {
  451. if ([filter isInequality]) {
  452. const FieldPath *existingField = [self.query inequalityFilterField];
  453. if (existingField && *existingField != filter.field) {
  454. ThrowInvalidArgument(
  455. "Invalid Query. All where filters with an inequality "
  456. "(lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) must be on the same "
  457. "field. But you have inequality filters on '%s' and '%s'",
  458. existingField->CanonicalString(), filter.field.CanonicalString());
  459. }
  460. const FieldPath *firstOrderByField = [self.query firstSortOrderField];
  461. if (firstOrderByField) {
  462. [self validateOrderByField:*firstOrderByField matchesInequalityField:filter.field];
  463. }
  464. } else if (filter.filterOperator == FSTRelationFilterOperatorArrayContains) {
  465. if ([self.query hasArrayContainsFilter]) {
  466. ThrowInvalidArgument("Invalid Query. Queries only support a single arrayContains filter.");
  467. }
  468. }
  469. }
  470. - (void)validateNewOrderByPath:(const FieldPath &)fieldPath {
  471. if (![self.query firstSortOrderField]) {
  472. // This is the first order by. It must match any inequality.
  473. const FieldPath *inequalityField = [self.query inequalityFilterField];
  474. if (inequalityField) {
  475. [self validateOrderByField:fieldPath matchesInequalityField:*inequalityField];
  476. }
  477. }
  478. }
  479. - (void)validateOrderByField:(const FieldPath &)orderByField
  480. matchesInequalityField:(const FieldPath &)inequalityField {
  481. if (orderByField != inequalityField) {
  482. ThrowInvalidArgument("Invalid query. You have a where filter with an inequality "
  483. "(lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) on field "
  484. "'%s' and so you must also use '%s' as your first queryOrderedBy field, "
  485. "but your first queryOrderedBy is currently on field '%s' instead.",
  486. inequalityField.CanonicalString(), inequalityField.CanonicalString(),
  487. orderByField.CanonicalString());
  488. }
  489. }
  490. /**
  491. * Create a FSTBound from a query given the document.
  492. *
  493. * Note that the FSTBound will always include the key of the document and the position will be
  494. * unambiguous.
  495. *
  496. * Will throw if the document does not contain all fields of the order by of
  497. * the query or if any of the fields in the order by are an uncommitted server
  498. * timestamp.
  499. */
  500. - (FSTBound *)boundFromSnapshot:(FIRDocumentSnapshot *)snapshot isBefore:(BOOL)isBefore {
  501. if (![snapshot exists]) {
  502. ThrowInvalidArgument("Invalid query. You are trying to start or end a query using a document "
  503. "that doesn't exist.");
  504. }
  505. FSTDocument *document = snapshot.internalDocument;
  506. NSMutableArray<FSTFieldValue *> *components = [NSMutableArray array];
  507. // Because people expect to continue/end a query at the exact document provided, we need to
  508. // use the implicit sort order rather than the explicit sort order, because it's guaranteed to
  509. // contain the document key. That way the position becomes unambiguous and the query
  510. // continues/ends exactly at the provided document. Without the key (by using the explicit sort
  511. // orders), multiple documents could match the position, yielding duplicate results.
  512. for (FSTSortOrder *sortOrder in self.query.sortOrders) {
  513. if (sortOrder.field == FieldPath::KeyFieldPath()) {
  514. [components addObject:[FSTReferenceValue
  515. referenceValue:[FSTDocumentKey keyWithDocumentKey:document.key]
  516. databaseID:self.firestore.databaseID]];
  517. } else {
  518. FSTFieldValue *value = [document fieldForPath:sortOrder.field];
  519. if (value.type == FieldValue::Type::ServerTimestamp) {
  520. ThrowInvalidArgument(
  521. "Invalid query. You are trying to start or end a query using a document for which the "
  522. "field '%s' is an uncommitted server timestamp. (Since the value of this field is "
  523. "unknown, you cannot start/end a query with it.)",
  524. sortOrder.field.CanonicalString());
  525. } else if (value != nil) {
  526. [components addObject:value];
  527. } else {
  528. ThrowInvalidArgument(
  529. "Invalid query. You are trying to start or end a query using a document for which the "
  530. "field '%s' (used as the order by) does not exist.",
  531. sortOrder.field.CanonicalString());
  532. }
  533. }
  534. }
  535. return [FSTBound boundWithPosition:components isBefore:isBefore];
  536. }
  537. /** Converts a list of field values to an FSTBound. */
  538. - (FSTBound *)boundFromFieldValues:(NSArray<id> *)fieldValues isBefore:(BOOL)isBefore {
  539. // Use explicit sort order because it has to match the query the user made
  540. NSArray<FSTSortOrder *> *explicitSortOrders = self.query.explicitSortOrders;
  541. if (fieldValues.count > explicitSortOrders.count) {
  542. ThrowInvalidArgument("Invalid query. You are trying to start or end a query using more values "
  543. "than were specified in the order by.");
  544. }
  545. NSMutableArray<FSTFieldValue *> *components = [NSMutableArray array];
  546. [fieldValues enumerateObjectsUsingBlock:^(id rawValue, NSUInteger idx, BOOL *stop) {
  547. FSTSortOrder *sortOrder = explicitSortOrders[idx];
  548. if (sortOrder.field == FieldPath::KeyFieldPath()) {
  549. if (![rawValue isKindOfClass:[NSString class]]) {
  550. ThrowInvalidArgument("Invalid query. Expected a string for the document ID.");
  551. }
  552. NSString *documentID = (NSString *)rawValue;
  553. if (![self.query isCollectionGroupQuery] && [documentID containsString:@"/"]) {
  554. ThrowInvalidArgument("Invalid query. When querying a collection and ordering by document "
  555. "ID, you must pass a plain document ID, but '%s' contains a slash.",
  556. documentID);
  557. }
  558. ResourcePath path = self.query.path.Append(ResourcePath::FromString([documentID UTF8String]));
  559. if (!DocumentKey::IsDocumentKey(path)) {
  560. ThrowInvalidArgument("Invalid query. When querying a collection group and ordering by "
  561. "document ID, you must pass a value that results in a valid document "
  562. "path, but '%s' is not because it contains an odd number of segments.",
  563. path.CanonicalString());
  564. }
  565. DocumentKey key{path};
  566. [components
  567. addObject:[FSTReferenceValue referenceValue:[FSTDocumentKey keyWithDocumentKey:key]
  568. databaseID:self.firestore.databaseID]];
  569. } else {
  570. FSTFieldValue *fieldValue = [self.firestore.dataConverter parsedQueryValue:rawValue];
  571. [components addObject:fieldValue];
  572. }
  573. }];
  574. return [FSTBound boundWithPosition:components isBefore:isBefore];
  575. }
  576. @end
  577. NS_ASSUME_NONNULL_END