Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit b395e26

Browse files
authored
Merge pull request #215 from FrankSalad/v3-transaction
[v3] [ios] transaction
2 parents 63fcf4b + 5a735ab commit b395e26

File tree

6 files changed

+145
-3
lines changed

6 files changed

+145
-3
lines changed

‎docs/api/database.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ firestack.database()
3636
```
3737
Useful for `orderByPriority` queries.
3838

39+
40+
Transaction Support:
41+
```javascript
42+
firestack.database()
43+
.ref('posts/1234/title')
44+
.transaction((title) => 'My Awesome Post');
45+
```
46+
3947
## Unmounted components
4048

4149
Listening to database updates on unmounted components will trigger a warning:

‎ios/Firestack/FirestackDatabase.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
}
1919

2020
@property NSMutableDictionary *dbReferences;
21+
@property NSMutableDictionary *transactions;
22+
@property dispatch_queue_t transactionQueue;
2123

2224
@end
2325

‎ios/Firestack/FirestackDatabase.m‎

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ @interface FirestackDBReference : NSObject
2121
@property FIRDatabaseHandle childRemovedHandler;
2222
@property FIRDatabaseHandle childMovedHandler;
2323
@property FIRDatabaseHandle childValueHandler;
24+
+ (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot;
25+
2426
@end
2527

2628
@implementation FirestackDBReference
@@ -52,7 +54,7 @@ - (void) addEventHandler:(NSString *) eventName
5254
{
5355
if (![self isListeningTo:eventName]) {
5456
id withBlock = ^(FIRDataSnapshot * _Nonnull snapshot) {
55-
NSDictionary *props = [self snapshotToDict:snapshot];
57+
NSDictionary *props = [FirestackDBReference snapshotToDict:snapshot];
5658
[self sendJSEvent:DATABASE_DATA_EVENT
5759
title:eventName
5860
props: @{
@@ -142,7 +144,7 @@ - (void) removeEventHandler:(NSString *) name
142144
[self unsetListeningOn:name];
143145
}
144146

145-
- (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot
147+
+ (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot
146148
{
147149
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
148150
[dict setValue:snapshot.key forKey:@"key"];
@@ -377,6 +379,8 @@ - (id) init
377379
self = [super init];
378380
if (self != nil) {
379381
_dbReferences = [[NSMutableDictionary alloc] init];
382+
_transactions = [[NSMutableDictionary alloc] init];
383+
_transactionQueue = dispatch_queue_create("com.fullstackreact.react-native-firestack", DISPATCH_QUEUE_CONCURRENT);
380384
}
381385
return self;
382386
}
@@ -479,7 +483,85 @@ - (id) init
479483
}
480484
}
481485

486+
RCT_EXPORT_METHOD(beginTransaction:(NSString *) path
487+
withIdentifier:(NSString *) identifier
488+
applyLocally:(BOOL) applyLocally
489+
onComplete:(RCTResponseSenderBlock) onComplete)
490+
{
491+
dispatch_async(_transactionQueue, ^{
492+
NSMutableDictionary *transactionState = [NSMutableDictionary new];
493+
494+
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
495+
[transactionState setObject:sema forKey:@"semaphore"];
496+
497+
FIRDatabaseReference *ref = [self getPathRef:path];
498+
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) {
499+
dispatch_barrier_async(_transactionQueue, ^{
500+
[_transactions setValue:transactionState forKey:identifier];
501+
[self sendEventWithName:DATABASE_TRANSACTION_EVENT
502+
body:@{
503+
@"id": identifier,
504+
@"originalValue": currentData.value
505+
}];
506+
});
507+
// Wait for the event handler to call tryCommitTransaction
508+
// WARNING: This wait occurs on the Firebase Worker Queue
509+
// so if tryCommitTransaction fails to signal the semaphore
510+
// no further blocks will be executed by Firebase until the timeout expires
511+
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC);
512+
BOOL timedout = dispatch_semaphore_wait(sema, delayTime) != 0;
513+
BOOL abort = [transactionState valueForKey:@"abort"] || timedout;
514+
id value = [transactionState valueForKey:@"value"];
515+
dispatch_barrier_async(_transactionQueue, ^{
516+
[_transactions removeObjectForKey:identifier];
517+
});
518+
if (abort) {
519+
return [FIRTransactionResult abort];
520+
} else {
521+
currentData.value = value;
522+
return [FIRTransactionResult successWithValue:currentData];
523+
}
524+
} andCompletionBlock:^(NSError * _Nullable databaseError, BOOL committed, FIRDataSnapshot * _Nullable snapshot) {
525+
if (databaseError != nil) {
526+
NSDictionary *evt = @{
527+
@"errorCode": [NSNumber numberWithInt:[databaseError code]],
528+
@"errorDetails": [databaseError debugDescription],
529+
@"description": [databaseError description]
530+
};
531+
onComplete(@[evt]);
532+
} else {
533+
onComplete(@[[NSNull null], @{
534+
@"committed": [NSNumber numberWithBool:committed],
535+
@"snapshot": [FirestackDBReference snapshotToDict:snapshot],
536+
@"status": @"success",
537+
@"method": @"transaction"
538+
}]);
539+
}
540+
} withLocalEvents:applyLocally];
541+
});
542+
}
482543

544+
RCT_EXPORT_METHOD(tryCommitTransaction:(NSString *) identifier
545+
withData:(NSDictionary *) data
546+
orAbort:(BOOL) abort)
547+
{
548+
__block NSMutableDictionary *transactionState;
549+
dispatch_sync(_transactionQueue, ^{
550+
transactionState = [_transactions objectForKey: identifier];
551+
});
552+
if (!transactionState) {
553+
NSLog(@"tryCommitTransaction for unknown ID %@", identifier);
554+
return;
555+
}
556+
dispatch_semaphore_t sema = [transactionState valueForKey:@"semaphore"];
557+
if (abort) {
558+
[transactionState setValue:@true forKey:@"abort"];
559+
} else {
560+
id newValue = [data valueForKey:@"value"];
561+
[transactionState setValue:newValue forKey:@"value"];
562+
}
563+
dispatch_semaphore_signal(sema);
564+
}
483565

484566
RCT_EXPORT_METHOD(on:(NSString *) path
485567
modifiersString:(NSString *) modifiersString
@@ -634,7 +716,7 @@ - (NSString *) getDBListenerKey:(NSString *) path
634716

635717
// Not sure how to get away from this... yet
636718
- (NSArray<NSString *> *)supportedEvents {
637-
return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT];
719+
return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT, DATABASE_TRANSACTION_EVENT];
638720
}
639721

640722

‎ios/Firestack/FirestackEvents.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ static NSString *const DEBUG_EVENT = @"debug";
2929
// Database
3030
static NSString *const DATABASE_DATA_EVENT = @"database_event";
3131
static NSString *const DATABASE_ERROR_EVENT = @"database_error";
32+
static NSString *const DATABASE_TRANSACTION_EVENT = @"database_transaction_update";
3233

3334
static NSString *const DATABASE_VALUE_EVENT = @"value";
3435
static NSString *const DATABASE_CHILD_ADDED_EVENT = @"child_added";

‎lib/modules/database/index.js‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ export default class Database extends Base {
1919
constructor(firestack: Object, options: Object = {}) {
2020
super(firestack, options);
2121
this.subscriptions = {};
22+
23+
this.transactions = {};
2224
this.errorSubscriptions = {};
25+
2326
this.serverTimeOffset = 0;
2427
this.persistenceEnabled = false;
2528
this.namespace = 'firestack:database';
@@ -34,6 +37,11 @@ export default class Database extends Base {
3437
err => this._handleDatabaseError(err)
3538
);
3639

40+
this.transactionListener = FirestackDatabaseEvt.addListener(
41+
'database_transaction_update',
42+
event => this._handleDatabaseTransaction(event)
43+
);
44+
3745
this.offsetRef = this.ref('.info/serverTimeOffset');
3846

3947
this.offsetRef.on('value', (snapshot) => {
@@ -164,6 +172,34 @@ export default class Database extends Base {
164172
FirestackDatabase.goOffline();
165173
}
166174

175+
addTransaction(path, updateCallback, applyLocally) {
176+
let id = this._generateTransactionID();
177+
this.transactions[id] = updateCallback;
178+
return promisify('beginTransaction', FirestackDatabase)(path, id, applyLocally || false)
179+
.then((v) => {delete this.transactions[id]; return v;},
180+
(e) => {delete this.transactions[id]; throw e;});
181+
}
182+
183+
_generateTransactionID() {
184+
// 10 char random alphanumeric
185+
return Math.random().toString(36).substr(2, 10);
186+
}
187+
188+
_handleDatabaseTransaction(event) {
189+
const {id, originalValue} = event;
190+
let newValue;
191+
try {
192+
const updateCallback = this.transactions[id];
193+
newValue = updateCallback(originalValue);
194+
} finally {
195+
let abort = false;
196+
if (newValue === undefined) {
197+
abort = true;
198+
}
199+
FirestackDatabase.tryCommitTransaction(id, {value: newValue}, abort);
200+
}
201+
}
202+
167203
/**
168204
* INTERNALS
169205
*/

‎lib/modules/database/reference.js‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,19 @@ export default class Reference extends ReferenceBase {
137137
return this.db.off(path, modifiersString, eventName, origCB);
138138
}
139139

140+
transaction(transactionUpdate, onComplete, applyLocally) {
141+
const path = this._dbPath();
142+
return this.db.addTransaction(path, transactionUpdate, applyLocally)
143+
.then((({ snapshot, committed }) => {return {snapshot: new Snapshot(this, snapshot), committed}}).bind(this))
144+
.then(({ snapshot, committed }) => {
145+
if (isFunction(onComplete)) onComplete(null, snapshot);
146+
return {snapshot, committed};
147+
}).catch((e) => {
148+
if (isFunction(onComplete)) return onComplete(e, null);
149+
throw e;
150+
});
151+
}
152+
140153
/**
141154
* MODIFIERS
142155
*/

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /