Skip to content

Commit ad478a3

Browse files
committed
feat: implement locks for executeBatch
1 parent 2a0eb4c commit ad478a3

File tree

4 files changed

+65
-35
lines changed

4 files changed

+65
-35
lines changed

package/src/concurrency.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export interface QueuedOperation {
2+
start: () => void
3+
}
4+
5+
export const locks: Record<
6+
string,
7+
{ queue: QueuedOperation[]; inProgress: boolean }
8+
> = {}

package/src/nitro.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import { NitroModules } from 'react-native-nitro-modules'
22
import type { NitroSQLite as NitroSQLiteSpec } from './specs/NitroSQLite.nitro'
3-
import type { PendingTransaction } from './operations/transaction'
43

54
export const HybridNitroSQLite =
65
NitroModules.createHybridObject<NitroSQLiteSpec>('NitroSQLite')
7-
8-
export const locks: Record<
9-
string,
10-
{ queue: PendingTransaction[]; inProgress: boolean }
11-
> = {}

package/src/operations/executeBatch.ts

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,78 @@ import {
33
replaceWithNativeNullValue,
44
} from '../nullHandling'
55
import { HybridNitroSQLite } from '../nitro'
6+
import { locks, type QueuedOperation } from '../concurrency'
67
import type {
78
NativeSQLiteQueryParams,
89
BatchQueryResult,
910
BatchQueryCommand,
1011
NativeBatchQueryCommand,
1112
} from '../types'
13+
import { startNextOperation } from './transaction'
1214

1315
export function executeBatch(
1416
dbName: string,
1517
commands: BatchQueryCommand[]
1618
): BatchQueryResult {
19+
if (locks[dbName] == null)
20+
throw Error(`Nitro SQLite Error: No lock found on db: ${dbName}`)
21+
1722
const transformedCommands = isSimpleNullHandlingEnabled()
1823
? toNativeBatchQueryCommands(commands)
1924
: (commands as NativeBatchQueryCommand[])
2025

21-
const result = HybridNitroSQLite.executeBatch(dbName, transformedCommands)
22-
return result
26+
// If lock is immediately available, execute synchronously
27+
if (!locks[dbName].inProgress && locks[dbName].queue.length === 0) {
28+
locks[dbName].inProgress = true
29+
try {
30+
const result = HybridNitroSQLite.executeBatch(dbName, transformedCommands)
31+
return result
32+
} finally {
33+
locks[dbName].inProgress = false
34+
startNextOperation(dbName)
35+
}
36+
}
37+
38+
// Lock is busy - cannot execute synchronously
39+
throw Error(
40+
`Nitro SQLite Error: Database ${dbName} is busy with another operation. Use executeBatchAsync for queued execution.`
41+
)
2342
}
2443

2544
export async function executeBatchAsync(
2645
dbName: string,
2746
commands: BatchQueryCommand[]
2847
): Promise<BatchQueryResult> {
48+
if (locks[dbName] == null)
49+
throw Error(`Nitro SQLite Error: No lock found on db: ${dbName}`)
50+
2951
const transformedCommands = isSimpleNullHandlingEnabled()
3052
? toNativeBatchQueryCommands(commands)
3153
: (commands as NativeBatchQueryCommand[])
3254

33-
const result = await HybridNitroSQLite.executeBatchAsync(
34-
dbName,
35-
transformedCommands
36-
)
37-
return result
55+
async function run() {
56+
try {
57+
const result = await HybridNitroSQLite.executeBatchAsync(
58+
dbName,
59+
transformedCommands
60+
)
61+
return result
62+
} finally {
63+
locks[dbName]!.inProgress = false
64+
startNextOperation(dbName)
65+
}
66+
}
67+
68+
return new Promise<BatchQueryResult>((resolve, reject) => {
69+
const operation: QueuedOperation = {
70+
start: () => {
71+
run().then(resolve).catch(reject)
72+
},
73+
}
74+
75+
locks[dbName]?.queue.push(operation)
76+
startNextOperation(dbName)
77+
})
3878
}
3979

4080
function toNativeBatchQueryCommands(

package/src/operations/transaction.ts

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { locks, HybridNitroSQLite } from '../nitro'
1+
import { HybridNitroSQLite } from '../nitro'
2+
import { locks, type QueuedOperation } from '../concurrency'
23
import type {
34
QueryResult,
45
Transaction,
@@ -7,19 +8,6 @@ import type {
78
} from '../types'
89
import { execute, executeAsync } from './execute'
910

10-
export interface PendingTransaction {
11-
/*
12-
* The start function should not throw or return a promise because the
13-
* queue just calls it and does not monitor for failures or completions.
14-
*
15-
* It should catch any errors and call the resolve or reject of the wrapping
16-
* promise when complete.
17-
*
18-
* It should also automatically commit or rollback the transaction if needed
19-
*/
20-
start: () => void
21-
}
22-
2311
export const transaction = (
2412
dbName: string,
2513
fn: (tx: Transaction) => Promise<void> | void
@@ -101,36 +89,36 @@ export const transaction = (
10189
} finally {
10290
locks[dbName]!.inProgress = false
10391
isFinalized = false
104-
startNextTransaction(dbName)
92+
startNextOperation(dbName)
10593
}
10694
}
10795

10896
return new Promise((resolve, reject) => {
109-
const tx: PendingTransaction = {
97+
const queuedTransaction: QueuedOperation = {
11098
start: () => {
11199
run().then(resolve).catch(reject)
112100
},
113101
}
114102

115-
locks[dbName]?.queue.push(tx)
116-
startNextTransaction(dbName)
103+
locks[dbName]?.queue.push(queuedTransaction)
104+
startNextOperation(dbName)
117105
})
118106
}
119107

120-
function startNextTransaction(dbName: string) {
108+
export function startNextOperation(dbName: string) {
121109
if (locks[dbName] == null) throw Error(`Lock not found for db: ${dbName}`)
122110

123111
if (locks[dbName].inProgress) {
124-
// Transaction is already in process bail out
112+
// Operation is already in process bail out
125113
return
126114
}
127115

128116
if (locks[dbName].queue.length > 0) {
129117
locks[dbName].inProgress = true
130118

131-
const tx = locks[dbName].queue.shift()!
119+
const operation = locks[dbName].queue.shift()!
132120
setImmediate(() => {
133-
tx.start()
121+
operation.start()
134122
})
135123
}
136124
}

0 commit comments

Comments
 (0)