@@ -12,9 +12,13 @@ import { dispose, contextTableName, fieldIdentifier, hiddenColName } from './sym
12
12
import { forEach , clone , contains , tryCatch , hasOwn , getType , assert , identity , warn } from '../utils'
13
13
import { createPredicate , createPkClause , mergeTransactionResult , predicatableQuery , lfFactory } from './helper'
14
14
import { Relationship , RDBType , DataStoreType , LeafType , StatementType , JoinMode } from '../interface/enum'
15
- import { Record , Field , JoinInfo , Query , Clause , Predicate } from '../interface'
16
15
import { SchemaDef , ColumnDef , ParsedSchema , Association , ScopedHandler } from '../interface'
17
16
import { ColumnLeaf , NavigatorLeaf , ExecutorResult , UpsertContext , SelectContext } from '../interface'
17
+ import { Record , Field , JoinInfo , Query , Clause , Predicate , Transaction , TransactionDescriptor , TransactionEffects } from '../interface'
18
+
19
+ const transactionErrorHandler = {
20
+ error : ( ) => warn ( `Execute failed, transaction is already marked for rollback.` )
21
+ }
18
22
19
23
export class Database {
20
24
@@ -24,7 +28,8 @@ export class Database {
24
28
return tableNames . map ( ( name ) => db . getSchema ( ) . table ( name ) )
25
29
}
26
30
27
- public database$ : ConnectableObservable < lf . Database >
31
+ public readonly database$ : ConnectableObservable < lf . Database >
32
+ public readonly inTransaction : boolean = false
28
33
29
34
private schemaDefs = new Map < string , SchemaDef < any > > ( )
30
35
private schemas = new Map < string , ParsedSchema > ( )
@@ -149,8 +154,14 @@ export class Database {
149
154
150
155
const { contextIds, queries } = Mutation . aggregate ( db , muts , [ ] )
151
156
contextIds . forEach ( id => this . storedIds . add ( id ) )
152
- return this . executor ( db , queries )
153
- . do ( { error : ( ) => contextIds . forEach ( id => this . storedIds . delete ( id ) ) } )
157
+ const onError = { error : ( ) => contextIds . forEach ( id => this . storedIds . delete ( id ) ) }
158
+
159
+ if ( this . inTransaction ) {
160
+ this . attachTx ( onError )
161
+ return this . executor ( db , queries )
162
+ }
163
+
164
+ return this . executor ( db , queries ) . do ( onError )
154
165
} )
155
166
}
156
167
@@ -208,7 +219,7 @@ export class Database {
208
219
}
209
220
210
221
delete < T > ( tableName : string , clause : Predicate < T > = { } ) : Observable < ExecutorResult > {
211
- const [ pk , err ] = tryCatch < string > ( this . findPrimaryKey ) ( tableName )
222
+ const [ pk , err ] = tryCatch < string > ( this . findPrimaryKey ) ( tableName )
212
223
if ( err ) {
213
224
return Observable . throw ( err )
214
225
}
@@ -225,13 +236,21 @@ export class Database {
225
236
. concatMap ( ( scopedIds ) => {
226
237
const query = predicatableQuery ( db , table , provider . getPredicate ( ) , StatementType . Delete )
227
238
228
- scopedIds . forEach ( ( entity : any ) =>
239
+ scopedIds . forEach ( ( entity : object ) =>
229
240
this . storedIds . delete ( fieldIdentifier ( tableName , entity [ pk ! ] ) ) )
241
+ const onError = {
242
+ error : ( ) => {
243
+ scopedIds . forEach ( ( entity : object ) =>
244
+ this . storedIds . add ( fieldIdentifier ( tableName , entity [ pk ! ] ) ) )
245
+ }
246
+ }
247
+
248
+ if ( this . inTransaction ) {
249
+ this . attachTx ( onError )
250
+ return this . executor ( db , [ query ] )
251
+ }
230
252
231
- return this . executor ( db , [ query ] ) . do ( { error : ( ) => {
232
- scopedIds . forEach ( ( entity : any ) =>
233
- this . storedIds . add ( fieldIdentifier ( tableName , entity [ pk ! ] ) ) )
234
- } } )
253
+ return this . executor ( db , [ query ] ) . do ( onError )
235
254
} )
236
255
} )
237
256
}
@@ -252,8 +271,14 @@ export class Database {
252
271
const { contextIds, queries } = Mutation . aggregate ( db , insert , update )
253
272
if ( queries . length > 0 ) {
254
273
contextIds . forEach ( id => this . storedIds . add ( id ) )
255
- return this . executor ( db , queries )
256
- . do ( { error : ( ) => contextIds . forEach ( id => this . storedIds . delete ( id ) ) } )
274
+ const onError = { error : ( ) => contextIds . forEach ( id => this . storedIds . delete ( id ) ) }
275
+
276
+ if ( this . inTransaction ) {
277
+ this . attachTx ( onError )
278
+ return this . executor ( db , queries )
279
+ }
280
+
281
+ return this . executor ( db , queries ) . do ( onError )
257
282
} else {
258
283
return Observable . of ( { result : false , insert : 0 , update : 0 , delete : 0 , select : 0 } )
259
284
}
@@ -282,19 +307,32 @@ export class Database {
282
307
removedIds . push ( fieldIdentifier ( tableName , entity [ schema ! . pk ] ) )
283
308
} )
284
309
310
+ const onError = {
311
+ error : ( ) => removedIds . forEach ( ( id : string ) => this . storedIds . add ( id ) )
312
+ }
313
+
285
314
if ( disposeHandler ) {
286
315
const scope = this . createScopedHandler < T > ( db , queries , removedIds )
287
316
return disposeHandler ( rootEntities , scope )
288
317
. do ( ( ) => removedIds . forEach ( ( id : string ) => this . storedIds . delete ( id ) ) )
289
- . concatMap ( ( ) => this . executor ( db , queries ) )
318
+ . concatMap ( ( ) => {
319
+ if ( this . inTransaction ) {
320
+ this . attachTx ( onError )
321
+ return this . executor ( db , queries )
322
+ }
323
+ return this . executor ( db , queries ) . do ( onError )
324
+ } )
290
325
} else {
291
326
removedIds . forEach ( ( id : string ) => this . storedIds . delete ( id ) )
292
- return this . executor ( db , queries )
327
+
328
+ if ( this . inTransaction ) {
329
+ this . attachTx ( onError )
330
+ return this . executor ( db , queries )
331
+ }
332
+
333
+ return this . executor ( db , queries ) . do ( onError )
293
334
}
294
335
} )
295
- . do ( { error : ( ) =>
296
- removedIds . forEach ( ( id : string ) => this . storedIds . add ( id ) )
297
- } )
298
336
} )
299
337
}
300
338
@@ -318,6 +356,81 @@ export class Database {
318
356
} )
319
357
}
320
358
359
+ attachTx ( _ : TransactionEffects ) {
360
+ throw Exception . UnexpectedTransactionUse ( )
361
+ }
362
+
363
+ executor ( db : lf . Database , queries : lf . query . Builder [ ] ) {
364
+ const tx = db . createTransaction ( )
365
+
366
+ return Observable . fromPromise ( tx . exec ( queries ) )
367
+ . do ( transactionErrorHandler )
368
+ . map ( ( ret ) => {
369
+ return {
370
+ result : true ,
371
+ ...mergeTransactionResult ( queries , ret )
372
+ }
373
+ } )
374
+ }
375
+
376
+ transaction ( ) : Observable < Transaction < Database > > {
377
+ type ProxyProperty = Pick < Database , 'attachTx' | 'executor' | 'inTransaction' >
378
+
379
+ return this . database$ . map ( db => {
380
+ const tx = db . createTransaction ( )
381
+ const transactionQueries : lf . query . Builder [ ] = [ ]
382
+ const effects : TransactionEffects [ ] = [ ]
383
+
384
+ const transactionContext : TransactionDescriptor < ProxyProperty > = {
385
+ attachTx : {
386
+ get ( ) {
387
+ return ( handler : TransactionEffects ) => {
388
+ effects . push ( handler )
389
+ }
390
+ }
391
+ } ,
392
+ executor : {
393
+ get ( ) {
394
+ return ( _ : lf . Database , queries : lf . query . Builder [ ] ) => {
395
+ transactionQueries . push ( ...queries )
396
+ return Observable . of ( null )
397
+ }
398
+ }
399
+ } ,
400
+ inTransaction : {
401
+ get ( ) {
402
+ return true
403
+ }
404
+ }
405
+ }
406
+
407
+ const customTx = {
408
+ commit : ( ) => {
409
+ return effects . reduce ( ( acc , curr ) => {
410
+ return acc . do ( curr )
411
+ } , Observable . from ( tx . exec ( transactionQueries ) ) )
412
+ . map ( ( r ) => {
413
+ return {
414
+ result : true ,
415
+ ...mergeTransactionResult ( transactionQueries , r )
416
+ }
417
+ } )
418
+ } ,
419
+ abort : ( ) => {
420
+ effects . length = 0
421
+ transactionQueries . length = 0
422
+ }
423
+ }
424
+
425
+ const ret : Transaction < Database > = [
426
+ Object . create ( this , transactionContext ) ,
427
+ customTx
428
+ ]
429
+
430
+ return ret
431
+ } )
432
+ }
433
+
321
434
private buildTables ( ) {
322
435
this . schemaDefs . forEach ( ( schemaDef , tableName ) => {
323
436
const tableBuilder = this . schemaBuilder ! . createTable ( tableName )
@@ -745,20 +858,4 @@ export class Database {
745
858
}
746
859
}
747
860
748
- private executor ( db : lf . Database , queries : lf . query . Builder [ ] ) {
749
- const tx = db . createTransaction ( )
750
- const handler = {
751
- error : ( ) => warn ( `Execute failed, transaction is already marked for rollback.` )
752
- }
753
-
754
- return Observable . fromPromise ( tx . exec ( queries ) )
755
- . do ( handler )
756
- . map ( ( ret ) => {
757
- return {
758
- result : true ,
759
- ...mergeTransactionResult ( queries , ret )
760
- }
761
- } )
762
- }
763
-
764
861
}
0 commit comments