FSTTransaction.mm 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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/FSTTransaction.h"
  17. #include <map>
  18. #include <utility>
  19. #include <vector>
  20. #import "FIRFirestoreErrors.h"
  21. #import "Firestore/Source/API/FSTUserDataConverter.h"
  22. #import "Firestore/Source/Model/FSTDocument.h"
  23. #import "Firestore/Source/Model/FSTMutation.h"
  24. #import "Firestore/Source/Remote/FSTDatastore.h"
  25. #import "Firestore/Source/Util/FSTUsageValidation.h"
  26. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  27. #include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
  28. #include "Firestore/core/src/firebase/firestore/model/precondition.h"
  29. #include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
  30. #include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
  31. using firebase::firestore::core::ParsedSetData;
  32. using firebase::firestore::core::ParsedUpdateData;
  33. using firebase::firestore::model::DocumentKey;
  34. using firebase::firestore::model::Precondition;
  35. using firebase::firestore::model::SnapshotVersion;
  36. using firebase::firestore::model::DocumentKeySet;
  37. NS_ASSUME_NONNULL_BEGIN
  38. #pragma mark - FSTTransaction
  39. @interface FSTTransaction ()
  40. @property(nonatomic, strong, readonly) FSTDatastore *datastore;
  41. @property(nonatomic, strong, readonly) NSMutableArray *mutations;
  42. @property(nonatomic, assign) BOOL commitCalled;
  43. /**
  44. * An error that may have occurred as a consequence of a write. If set, needs to be raised in the
  45. * completion handler instead of trying to commit.
  46. */
  47. @property(nonatomic, strong, nullable) NSError *lastWriteError;
  48. @end
  49. @implementation FSTTransaction {
  50. std::map<DocumentKey, SnapshotVersion> _readVersions;
  51. }
  52. + (instancetype)transactionWithDatastore:(FSTDatastore *)datastore {
  53. return [[FSTTransaction alloc] initWithDatastore:datastore];
  54. }
  55. - (instancetype)initWithDatastore:(FSTDatastore *)datastore {
  56. self = [super init];
  57. if (self) {
  58. _datastore = datastore;
  59. _mutations = [NSMutableArray array];
  60. _commitCalled = NO;
  61. }
  62. return self;
  63. }
  64. /**
  65. * Every time a document is read, this should be called to record its version. If we read two
  66. * different versions of the same document, this will return an error through its out parameter.
  67. * When the transaction is committed, the versions recorded will be set as preconditions on the
  68. * writes sent to the backend.
  69. */
  70. - (BOOL)recordVersionForDocument:(FSTMaybeDocument *)doc error:(NSError **)error {
  71. HARD_ASSERT(error != nil, "nil error parameter");
  72. *error = nil;
  73. SnapshotVersion docVersion;
  74. if ([doc isKindOfClass:[FSTDocument class]]) {
  75. docVersion = doc.version;
  76. } else if ([doc isKindOfClass:[FSTDeletedDocument class]]) {
  77. // For deleted docs, we must record an explicit no version to build the right precondition
  78. // when writing.
  79. docVersion = SnapshotVersion::None();
  80. } else {
  81. HARD_FAIL("Unexpected document type in transaction: %s", NSStringFromClass([doc class]));
  82. }
  83. if (_readVersions.find(doc.key) == _readVersions.end()) {
  84. _readVersions[doc.key] = docVersion;
  85. return YES;
  86. } else {
  87. if (error) {
  88. *error = [NSError errorWithDomain:FIRFirestoreErrorDomain
  89. code:FIRFirestoreErrorCodeFailedPrecondition
  90. userInfo:@{
  91. NSLocalizedDescriptionKey :
  92. @"A document cannot be read twice within a single transaction."
  93. }];
  94. }
  95. return NO;
  96. }
  97. }
  98. - (void)lookupDocumentsForKeys:(const std::vector<DocumentKey> &)keys
  99. completion:(FSTVoidMaybeDocumentArrayErrorBlock)completion {
  100. [self ensureCommitNotCalled];
  101. if (self.mutations.count) {
  102. FSTThrowInvalidUsage(@"FIRIllegalStateException",
  103. @"All reads in a transaction must be done before any writes.");
  104. }
  105. [self.datastore lookupDocuments:keys
  106. completion:^(NSArray<FSTMaybeDocument *> *_Nullable documents,
  107. NSError *_Nullable error) {
  108. if (error) {
  109. completion(nil, error);
  110. return;
  111. }
  112. for (FSTMaybeDocument *doc in documents) {
  113. NSError *recordError = nil;
  114. if (![self recordVersionForDocument:doc error:&recordError]) {
  115. completion(nil, recordError);
  116. return;
  117. }
  118. }
  119. completion(documents, nil);
  120. }];
  121. }
  122. /** Stores mutations to be written when commitWithCompletion is called. */
  123. - (void)writeMutations:(NSArray<FSTMutation *> *)mutations {
  124. [self ensureCommitNotCalled];
  125. [self.mutations addObjectsFromArray:mutations];
  126. }
  127. /**
  128. * Returns version of this doc when it was read in this transaction as a precondition, or no
  129. * precondition if it was not read.
  130. */
  131. - (Precondition)preconditionForDocumentKey:(const DocumentKey &)key {
  132. const auto iter = _readVersions.find(key);
  133. if (iter == _readVersions.end()) {
  134. return Precondition::None();
  135. } else {
  136. return Precondition::UpdateTime(iter->second);
  137. }
  138. }
  139. /**
  140. * Returns the precondition for a document if the operation is an update, based on the provided
  141. * UpdateOptions. Will return none precondition if an error occurred, in which case it sets the
  142. * error parameter.
  143. */
  144. - (Precondition)preconditionForUpdateWithDocumentKey:(const DocumentKey &)key
  145. error:(NSError **)error {
  146. const auto iter = _readVersions.find(key);
  147. if (iter == _readVersions.end()) {
  148. // Document was not read, so we just use the preconditions for an update.
  149. return Precondition::Exists(true);
  150. }
  151. const SnapshotVersion &version = iter->second;
  152. if (version == SnapshotVersion::None()) {
  153. // The document was read, but doesn't exist.
  154. // Return an error because the precondition is impossible
  155. if (error) {
  156. *error = [NSError
  157. errorWithDomain:FIRFirestoreErrorDomain
  158. code:FIRFirestoreErrorCodeAborted
  159. userInfo:@{
  160. NSLocalizedDescriptionKey : @"Can't update a document that doesn't exist."
  161. }];
  162. }
  163. return Precondition::None();
  164. } else {
  165. // Document exists, just base precondition on document update time.
  166. return Precondition::UpdateTime(version);
  167. }
  168. }
  169. - (void)setData:(ParsedSetData &&)data forDocument:(const DocumentKey &)key {
  170. [self writeMutations:std::move(data).ToMutations(key, [self preconditionForDocumentKey:key])];
  171. }
  172. - (void)updateData:(ParsedUpdateData &&)data forDocument:(const DocumentKey &)key {
  173. NSError *error = nil;
  174. const Precondition precondition = [self preconditionForUpdateWithDocumentKey:key error:&error];
  175. if (precondition.IsNone()) {
  176. HARD_ASSERT(error, "Got nil precondition, but error was not set");
  177. self.lastWriteError = error;
  178. } else {
  179. [self writeMutations:std::move(data).ToMutations(key, precondition)];
  180. }
  181. }
  182. - (void)deleteDocument:(const DocumentKey &)key {
  183. [self writeMutations:@[ [[FSTDeleteMutation alloc]
  184. initWithKey:key
  185. precondition:[self preconditionForDocumentKey:key]] ]];
  186. // Since the delete will be applied before all following writes, we need to ensure that the
  187. // precondition for the next write will be exists without timestamp.
  188. _readVersions[key] = SnapshotVersion::None();
  189. }
  190. - (void)commitWithCompletion:(FSTVoidErrorBlock)completion {
  191. [self ensureCommitNotCalled];
  192. // Once commitWithCompletion is called once, mark this object so it can't be used again.
  193. self.commitCalled = YES;
  194. // If there was an error writing, raise that error now
  195. if (self.lastWriteError) {
  196. completion(self.lastWriteError);
  197. return;
  198. }
  199. // Make a list of read documents that haven't been written.
  200. DocumentKeySet unwritten;
  201. for (const auto &kv : _readVersions) {
  202. unwritten = unwritten.insert(kv.first);
  203. };
  204. // For each mutation, note that the doc was written.
  205. for (FSTMutation *mutation in self.mutations) {
  206. unwritten = unwritten.erase(mutation.key);
  207. }
  208. if (!unwritten.empty()) {
  209. // TODO(klimt): This is a temporary restriction, until "verify" is supported on the backend.
  210. completion([NSError
  211. errorWithDomain:FIRFirestoreErrorDomain
  212. code:FIRFirestoreErrorCodeFailedPrecondition
  213. userInfo:@{
  214. NSLocalizedDescriptionKey : @"Every document read in a transaction must also be "
  215. @"written in that transaction."
  216. }]);
  217. } else {
  218. [self.datastore commitMutations:self.mutations
  219. completion:^(NSError *_Nullable error) {
  220. if (error) {
  221. completion(error);
  222. } else {
  223. completion(nil);
  224. }
  225. }];
  226. }
  227. }
  228. - (void)ensureCommitNotCalled {
  229. if (self.commitCalled) {
  230. FSTThrowInvalidUsage(
  231. @"FIRIllegalStateException",
  232. @"A transaction object cannot be used after its update block has completed.");
  233. }
  234. }
  235. @end
  236. NS_ASSUME_NONNULL_END