inibase 3.5.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +15 -1
- package/dist/index.js +105 -48
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -130,7 +130,7 @@ export default class Inibase {
|
|
|
130
130
|
* per-table writer lock of every table it mutates, so mutations stage into
|
|
131
131
|
* the database journal and publish only on commit().
|
|
132
132
|
*/
|
|
133
|
-
private
|
|
133
|
+
private activeTransaction;
|
|
134
134
|
constructor(database: string, mainFolder?: string, language?: ErrorLang);
|
|
135
135
|
createError(name: ErrorCode, variable?: string | number | (string | number)[]): Error;
|
|
136
136
|
private validateName;
|
|
@@ -350,6 +350,20 @@ export default class Inibase {
|
|
|
350
350
|
* live file is touched (nothing is published before commit()).
|
|
351
351
|
*/
|
|
352
352
|
rollback(): Promise<void>;
|
|
353
|
+
/**
|
|
354
|
+
* Run `callback` inside a transaction, committing when it resolves and
|
|
355
|
+
* rolling back when it throws. Prefer this over a bare begin/commit pair:
|
|
356
|
+
* the database lock taken by begin() is held for the transaction's whole
|
|
357
|
+
* lifetime, so an exception between begin() and commit() would otherwise
|
|
358
|
+
* block every later write on this database for the full lock-wait timeout.
|
|
359
|
+
*
|
|
360
|
+
* @param tablesOrCallback Pre-lock list (sorted, deadlock-free) or the callback
|
|
361
|
+
* @param maybeCallback The callback, when tables were passed first
|
|
362
|
+
* @param tables Pre-lock list when the callback was passed first
|
|
363
|
+
* @return {*} Whatever the callback resolves to
|
|
364
|
+
*/
|
|
365
|
+
transaction<T>(tablesOrCallback: string[] | ((db: Inibase) => Promise<T>), maybeCallback?: (db: Inibase) => Promise<T>, tables?: string[]): Promise<T>;
|
|
366
|
+
private _transaction;
|
|
353
367
|
/**
|
|
354
368
|
* Snapshot the identity (dev:inode:mtime:size) of every column file and
|
|
355
369
|
* the pagination file. Reading data and then re-verifying this snapshot
|
package/dist/index.js
CHANGED
|
@@ -140,7 +140,7 @@ export default class Inibase {
|
|
|
140
140
|
* per-table writer lock of every table it mutates, so mutations stage into
|
|
141
141
|
* the database journal and publish only on commit().
|
|
142
142
|
*/
|
|
143
|
-
|
|
143
|
+
activeTransaction = null;
|
|
144
144
|
constructor(database, mainFolder = ".", language = "en") {
|
|
145
145
|
this.language = language;
|
|
146
146
|
this.validateName(database);
|
|
@@ -226,7 +226,7 @@ export default class Inibase {
|
|
|
226
226
|
this.validateName(tableName);
|
|
227
227
|
// DDL does not participate in the write-ahead journal: schema surgery
|
|
228
228
|
// inside a transaction would escape the atomic publish/rollback scope.
|
|
229
|
-
if (this.
|
|
229
|
+
if (this.activeTransaction)
|
|
230
230
|
throw this.createError("INVALID_PARAMETERS");
|
|
231
231
|
await this.ensureDatabaseRecovered();
|
|
232
232
|
if (schema)
|
|
@@ -300,7 +300,7 @@ export default class Inibase {
|
|
|
300
300
|
this.validateName(tableName);
|
|
301
301
|
// DDL does not participate in the write-ahead journal: schema surgery
|
|
302
302
|
// inside a transaction would escape the atomic publish/rollback scope.
|
|
303
|
-
if (this.
|
|
303
|
+
if (this.activeTransaction)
|
|
304
304
|
throw this.createError("INVALID_PARAMETERS");
|
|
305
305
|
await this.ensureDatabaseRecovered();
|
|
306
306
|
if (config?.name)
|
|
@@ -1428,7 +1428,7 @@ export default class Inibase {
|
|
|
1428
1428
|
return null;
|
|
1429
1429
|
// A transaction cannot read a dependency table it has already
|
|
1430
1430
|
// staged (no read-your-writes past its commit point).
|
|
1431
|
-
if (this.
|
|
1431
|
+
if (this.activeTransaction?.tables.has(table))
|
|
1432
1432
|
throw this.createError("INVALID_PARAMETERS");
|
|
1433
1433
|
// Batched link-hop reads: during the collection pass a hop
|
|
1434
1434
|
// only records its (table, column, id) triple; the real pass
|
|
@@ -2210,7 +2210,7 @@ export default class Inibase {
|
|
|
2210
2210
|
}
|
|
2211
2211
|
/** Staged per-table entry of the open transaction, or null when none. */
|
|
2212
2212
|
txnTableEntry(tableName) {
|
|
2213
|
-
const txn = this.
|
|
2213
|
+
const txn = this.activeTransaction;
|
|
2214
2214
|
if (!txn)
|
|
2215
2215
|
return null;
|
|
2216
2216
|
let entry = txn.tables.get(tableName);
|
|
@@ -2272,7 +2272,7 @@ export default class Inibase {
|
|
|
2272
2272
|
* would need read-your-writes composition).
|
|
2273
2273
|
*/
|
|
2274
2274
|
async stageTxnOp(tableName, renameList, pagination) {
|
|
2275
|
-
const txn = this.
|
|
2275
|
+
const txn = this.activeTransaction;
|
|
2276
2276
|
const entry = this.txnTableEntry(tableName);
|
|
2277
2277
|
if (!txn || !entry)
|
|
2278
2278
|
throw this.createError("INVALID_PARAMETERS");
|
|
@@ -2317,7 +2317,7 @@ export default class Inibase {
|
|
|
2317
2317
|
return File.runWithLockStore(() => this._begin(tables));
|
|
2318
2318
|
}
|
|
2319
2319
|
async _begin(tables = []) {
|
|
2320
|
-
if (this.
|
|
2320
|
+
if (this.activeTransaction)
|
|
2321
2321
|
throw this.createError("INVALID_PARAMETERS");
|
|
2322
2322
|
await this.ensureDatabaseTmpDir();
|
|
2323
2323
|
// The database lock is the transaction mutex: it serializes
|
|
@@ -2333,16 +2333,16 @@ export default class Inibase {
|
|
|
2333
2333
|
await this.getTable(name); // throws TABLE_NOT_EXISTS
|
|
2334
2334
|
}
|
|
2335
2335
|
const id = randomUUID();
|
|
2336
|
-
this.
|
|
2336
|
+
this.activeTransaction = {
|
|
2337
2337
|
id,
|
|
2338
2338
|
journal: new DatabaseJournal(this.databasePath, id),
|
|
2339
2339
|
tables: new Map(),
|
|
2340
2340
|
};
|
|
2341
|
-
await this.
|
|
2341
|
+
await this.activeTransaction.journal.begin(uniqueTables);
|
|
2342
2342
|
for (const name of uniqueTables) {
|
|
2343
2343
|
await File.lock(join(this.databasePath, name, ".tmp"));
|
|
2344
2344
|
acquired.push(name);
|
|
2345
|
-
this.
|
|
2345
|
+
this.activeTransaction.tables.set(name, {
|
|
2346
2346
|
locked: true,
|
|
2347
2347
|
paginationFrom: "",
|
|
2348
2348
|
lastId: 0,
|
|
@@ -2357,7 +2357,7 @@ export default class Inibase {
|
|
|
2357
2357
|
// lock file).
|
|
2358
2358
|
for (const name of acquired)
|
|
2359
2359
|
await File.unlock(join(this.databasePath, name, ".tmp")).catch(() => { });
|
|
2360
|
-
this.
|
|
2360
|
+
this.activeTransaction = null;
|
|
2361
2361
|
await File.unlock(join(this.databasePath, ".tmp")).catch(() => { });
|
|
2362
2362
|
throw error;
|
|
2363
2363
|
}
|
|
@@ -2371,7 +2371,7 @@ export default class Inibase {
|
|
|
2371
2371
|
* tables, marker -> roll forward all tables).
|
|
2372
2372
|
*/
|
|
2373
2373
|
async commit() {
|
|
2374
|
-
const txn = this.
|
|
2374
|
+
const txn = this.activeTransaction;
|
|
2375
2375
|
if (!txn)
|
|
2376
2376
|
throw this.createError("INVALID_PARAMETERS");
|
|
2377
2377
|
try {
|
|
@@ -2404,6 +2404,25 @@ export default class Inibase {
|
|
|
2404
2404
|
await File.syncDir(join(this.databasePath, tableName));
|
|
2405
2405
|
await File.syncDir(join(this.databasePath, tableName, ".tmp"));
|
|
2406
2406
|
}
|
|
2407
|
+
// A staged table's row count and derived caches were deliberately
|
|
2408
|
+
// left untouched while the transaction was open (nothing is
|
|
2409
|
+
// published before commit), so they must be refreshed here — the
|
|
2410
|
+
// single-table fast path does the same right after commitFiles().
|
|
2411
|
+
// Without this a committed `put` (row count unchanged) would keep
|
|
2412
|
+
// serving sorted/criteria .cache entries built before the write.
|
|
2413
|
+
for (const tableName of txn.tables.keys()) {
|
|
2414
|
+
const entry = txn.tables.get(tableName);
|
|
2415
|
+
if (!entry?.staged.length)
|
|
2416
|
+
continue; // pre-locked but untouched
|
|
2417
|
+
// Only trust entry.total when resolvePagination() recorded it.
|
|
2418
|
+
// A `put` selected by `where` never reads the pagination file
|
|
2419
|
+
// (its row count cannot change), so entry.total is still its
|
|
2420
|
+
// initial 0 — publishing that would corrupt the live counter.
|
|
2421
|
+
if (entry.paginationFrom)
|
|
2422
|
+
this.totalItems.set(`${tableName}-*`, entry.total);
|
|
2423
|
+
if (globalConfig[this.databasePath].tables?.get(tableName)?.config.cache)
|
|
2424
|
+
await this.clearCache(tableName);
|
|
2425
|
+
}
|
|
2407
2426
|
}
|
|
2408
2427
|
catch (error) {
|
|
2409
2428
|
await txn.journal.rollback().catch(() => { });
|
|
@@ -2413,7 +2432,7 @@ export default class Inibase {
|
|
|
2413
2432
|
for (const tableName of [...txn.tables.keys()].sort().reverse())
|
|
2414
2433
|
await File.unlock(join(this.databasePath, tableName, ".tmp"));
|
|
2415
2434
|
await File.unlock(join(this.databasePath, ".tmp"));
|
|
2416
|
-
this.
|
|
2435
|
+
this.activeTransaction = null;
|
|
2417
2436
|
}
|
|
2418
2437
|
}
|
|
2419
2438
|
/**
|
|
@@ -2421,7 +2440,7 @@ export default class Inibase {
|
|
|
2421
2440
|
* live file is touched (nothing is published before commit()).
|
|
2422
2441
|
*/
|
|
2423
2442
|
async rollback() {
|
|
2424
|
-
const txn = this.
|
|
2443
|
+
const txn = this.activeTransaction;
|
|
2425
2444
|
if (!txn)
|
|
2426
2445
|
throw this.createError("INVALID_PARAMETERS");
|
|
2427
2446
|
try {
|
|
@@ -2431,7 +2450,44 @@ export default class Inibase {
|
|
|
2431
2450
|
for (const tableName of [...txn.tables.keys()].sort().reverse())
|
|
2432
2451
|
await File.unlock(join(this.databasePath, tableName, ".tmp"));
|
|
2433
2452
|
await File.unlock(join(this.databasePath, ".tmp"));
|
|
2434
|
-
this.
|
|
2453
|
+
this.activeTransaction = null;
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
/**
|
|
2457
|
+
* Run `callback` inside a transaction, committing when it resolves and
|
|
2458
|
+
* rolling back when it throws. Prefer this over a bare begin/commit pair:
|
|
2459
|
+
* the database lock taken by begin() is held for the transaction's whole
|
|
2460
|
+
* lifetime, so an exception between begin() and commit() would otherwise
|
|
2461
|
+
* block every later write on this database for the full lock-wait timeout.
|
|
2462
|
+
*
|
|
2463
|
+
* @param tablesOrCallback Pre-lock list (sorted, deadlock-free) or the callback
|
|
2464
|
+
* @param maybeCallback The callback, when tables were passed first
|
|
2465
|
+
* @param tables Pre-lock list when the callback was passed first
|
|
2466
|
+
* @return {*} Whatever the callback resolves to
|
|
2467
|
+
*/
|
|
2468
|
+
async transaction(tablesOrCallback, maybeCallback, tables = []) {
|
|
2469
|
+
return File.runWithLockStore(() => this._transaction(tablesOrCallback, maybeCallback, tables));
|
|
2470
|
+
}
|
|
2471
|
+
async _transaction(tablesOrCallback, maybeCallback, tables = []) {
|
|
2472
|
+
const callback = typeof tablesOrCallback === "function" ? tablesOrCallback : maybeCallback;
|
|
2473
|
+
if (!callback)
|
|
2474
|
+
throw this.createError("INVALID_PARAMETERS");
|
|
2475
|
+
if (Array.isArray(tablesOrCallback))
|
|
2476
|
+
tables = tablesOrCallback;
|
|
2477
|
+
await this.begin(tables);
|
|
2478
|
+
let committed = false;
|
|
2479
|
+
try {
|
|
2480
|
+
const result = await callback(this);
|
|
2481
|
+
await this.commit();
|
|
2482
|
+
committed = true;
|
|
2483
|
+
return result;
|
|
2484
|
+
}
|
|
2485
|
+
finally {
|
|
2486
|
+
// commit() already rolled back and cleared the transaction when it
|
|
2487
|
+
// failed, so this only fires for a throwing callback (or one that
|
|
2488
|
+
// never committed) — the case that would otherwise leak the lock.
|
|
2489
|
+
if (!committed && this.activeTransaction)
|
|
2490
|
+
await this.rollback().catch(() => { });
|
|
2435
2491
|
}
|
|
2436
2492
|
}
|
|
2437
2493
|
/**
|
|
@@ -2805,7 +2861,7 @@ export default class Inibase {
|
|
|
2805
2861
|
? options.columns
|
|
2806
2862
|
: [options.columns]));
|
|
2807
2863
|
const tablePath = join(this.databasePath, tableName);
|
|
2808
|
-
if (!this.
|
|
2864
|
+
if (!this.activeTransaction)
|
|
2809
2865
|
await this.ensureDatabaseRecovered();
|
|
2810
2866
|
await this.getTable(tableName);
|
|
2811
2867
|
if (!globalConfig[this.databasePath].tables?.get(tableName)?.schema)
|
|
@@ -2819,13 +2875,13 @@ export default class Inibase {
|
|
|
2819
2875
|
try {
|
|
2820
2876
|
// Inside a transaction the table lock is taken once per txn (and
|
|
2821
2877
|
// held until commit/rollback); otherwise the usual writer lock.
|
|
2822
|
-
if (this.
|
|
2878
|
+
if (this.activeTransaction)
|
|
2823
2879
|
await this.ensureTxnLock(tableName);
|
|
2824
2880
|
else
|
|
2825
2881
|
await File.lock(join(tablePath, ".tmp"));
|
|
2826
2882
|
const { filePath: paginationFilePath, lastId, total: _totalItems, } = await this.resolvePagination(tableName);
|
|
2827
2883
|
let lastIdValue = lastId;
|
|
2828
|
-
if (!this.
|
|
2884
|
+
if (!this.activeTransaction)
|
|
2829
2885
|
this.totalItems.set(`${tableName}-*`, _totalItems);
|
|
2830
2886
|
if (Utils.isArrayOfObjects(clonedData))
|
|
2831
2887
|
for (let index = 0; index < clonedData.length; index++) {
|
|
@@ -2868,7 +2924,7 @@ export default class Inibase {
|
|
|
2868
2924
|
from: paginationFilePath,
|
|
2869
2925
|
to: join(tablePath, `${lastIdValue}-${newTotal}.pagination`),
|
|
2870
2926
|
};
|
|
2871
|
-
if (this.
|
|
2927
|
+
if (this.activeTransaction) {
|
|
2872
2928
|
// Stage: journal the intent (fsynced op entry); the live files
|
|
2873
2929
|
// only change when commit() publishes.
|
|
2874
2930
|
await this.stageTxnOp(tableName, renameList, pagination);
|
|
@@ -2887,7 +2943,7 @@ export default class Inibase {
|
|
|
2887
2943
|
await this.clearCache(tableName);
|
|
2888
2944
|
}
|
|
2889
2945
|
if (returnPostedData) {
|
|
2890
|
-
if (this.
|
|
2946
|
+
if (this.activeTransaction)
|
|
2891
2947
|
// No read-your-writes yet: return the formatted staged rows
|
|
2892
2948
|
// (ids + defaults) instead of a committed-state read.
|
|
2893
2949
|
return (Array.isArray(clonedData) ? clonedData : clonedData);
|
|
@@ -2908,15 +2964,13 @@ export default class Inibase {
|
|
|
2908
2964
|
// not encode it here. Every other table keeps returning the
|
|
2909
2965
|
// encrypted id.
|
|
2910
2966
|
const tableConfig = globalConfig[this.databasePath].tables?.get(tableName)?.config;
|
|
2911
|
-
const postedID = (id) => tableConfig?.decodeID === true
|
|
2912
|
-
? Number(id)
|
|
2913
|
-
: UtilsServer.encodeID(id);
|
|
2967
|
+
const postedID = (id) => tableConfig?.decodeID === true ? Number(id) : UtilsServer.encodeID(id);
|
|
2914
2968
|
return Array.isArray(clonedData)
|
|
2915
2969
|
? (tableConfig?.prepend ? clonedData.toReversed() : clonedData).map(({ id }) => postedID(id))
|
|
2916
2970
|
: postedID(clonedData.id);
|
|
2917
2971
|
}
|
|
2918
2972
|
finally {
|
|
2919
|
-
if (this.
|
|
2973
|
+
if (this.activeTransaction) {
|
|
2920
2974
|
// Staged temps belong to the journal op; commit()/rollback()
|
|
2921
2975
|
// owns them. Temps from a failed pre-stage attempt are cleaned
|
|
2922
2976
|
// here so nothing leaks.
|
|
@@ -2952,7 +3006,7 @@ export default class Inibase {
|
|
|
2952
3006
|
? options.columns
|
|
2953
3007
|
: [options.columns]));
|
|
2954
3008
|
const tablePath = join(this.databasePath, tableName);
|
|
2955
|
-
if (!this.
|
|
3009
|
+
if (!this.activeTransaction)
|
|
2956
3010
|
await this.ensureDatabaseRecovered();
|
|
2957
3011
|
await this.throwErrorIfTableEmpty(tableName);
|
|
2958
3012
|
let clonedData = structuredClone(data);
|
|
@@ -2980,7 +3034,7 @@ export default class Inibase {
|
|
|
2980
3034
|
// row overlaid with the payload. The plan itself is schema-only.
|
|
2981
3035
|
const computedPlan = await this.buildComputedPlan(tableName);
|
|
2982
3036
|
try {
|
|
2983
|
-
if (this.
|
|
3037
|
+
if (this.activeTransaction)
|
|
2984
3038
|
await this.ensureTxnLock(tableName);
|
|
2985
3039
|
else
|
|
2986
3040
|
await File.lock(join(tablePath, ".tmp"));
|
|
@@ -3007,7 +3061,7 @@ export default class Inibase {
|
|
|
3007
3061
|
this.mergeComputedLineRecords(tableName, computedPlan, evaluated, pathesContents, existing);
|
|
3008
3062
|
}
|
|
3009
3063
|
await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content, total))));
|
|
3010
|
-
if (this.
|
|
3064
|
+
if (this.activeTransaction) {
|
|
3011
3065
|
// Stage instead of publishing: row count is unchanged so
|
|
3012
3066
|
// there is no pagination rename to journal.
|
|
3013
3067
|
await this.stageTxnOp(tableName, renameList, null);
|
|
@@ -3020,7 +3074,7 @@ export default class Inibase {
|
|
|
3020
3074
|
await this.clearCache(tableName);
|
|
3021
3075
|
}
|
|
3022
3076
|
if (returnUpdatedData) {
|
|
3023
|
-
if (this.
|
|
3077
|
+
if (this.activeTransaction)
|
|
3024
3078
|
// Reading the committed state would miss the staged
|
|
3025
3079
|
// write (no read-your-writes yet).
|
|
3026
3080
|
throw this.createError("INVALID_PARAMETERS");
|
|
@@ -3028,7 +3082,7 @@ export default class Inibase {
|
|
|
3028
3082
|
}
|
|
3029
3083
|
}
|
|
3030
3084
|
finally {
|
|
3031
|
-
if (this.
|
|
3085
|
+
if (this.activeTransaction) {
|
|
3032
3086
|
// Staged temps belong to the journal op; commit()/rollback()
|
|
3033
3087
|
// owns them.
|
|
3034
3088
|
if (!txnStaged && renameList.length)
|
|
@@ -3075,7 +3129,7 @@ export default class Inibase {
|
|
|
3075
3129
|
try {
|
|
3076
3130
|
// One global lock per table serializes every writer; inside a
|
|
3077
3131
|
// transaction the lock is held for the whole txn.
|
|
3078
|
-
if (this.
|
|
3132
|
+
if (this.activeTransaction)
|
|
3079
3133
|
await this.ensureTxnLock(tableName);
|
|
3080
3134
|
else
|
|
3081
3135
|
await File.lock(join(tablePath, ".tmp"));
|
|
@@ -3104,7 +3158,7 @@ export default class Inibase {
|
|
|
3104
3158
|
this.mergeComputedLineRecords(tableName, computedPlan, evaluated, pathesContents, existing);
|
|
3105
3159
|
}
|
|
3106
3160
|
await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content))));
|
|
3107
|
-
if (this.
|
|
3161
|
+
if (this.activeTransaction) {
|
|
3108
3162
|
await this.stageTxnOp(tableName, renameList, null);
|
|
3109
3163
|
txnStaged = true;
|
|
3110
3164
|
}
|
|
@@ -3114,7 +3168,7 @@ export default class Inibase {
|
|
|
3114
3168
|
await this.clearCache(tableName);
|
|
3115
3169
|
}
|
|
3116
3170
|
if (returnUpdatedData) {
|
|
3117
|
-
if (this.
|
|
3171
|
+
if (this.activeTransaction)
|
|
3118
3172
|
throw this.createError("INVALID_PARAMETERS");
|
|
3119
3173
|
// AWAITED readback: must complete inside the writer lock. An
|
|
3120
3174
|
// async-tail `return this.get(...)` lets the finally below
|
|
@@ -3123,7 +3177,7 @@ export default class Inibase {
|
|
|
3123
3177
|
}
|
|
3124
3178
|
}
|
|
3125
3179
|
finally {
|
|
3126
|
-
if (this.
|
|
3180
|
+
if (this.activeTransaction) {
|
|
3127
3181
|
if (!txnStaged && renameList.length)
|
|
3128
3182
|
await Promise.allSettled(renameList
|
|
3129
3183
|
.filter((pair) => Boolean(pair[0]))
|
|
@@ -3150,7 +3204,7 @@ export default class Inibase {
|
|
|
3150
3204
|
// the lock is held continuously from resolution to publish.
|
|
3151
3205
|
let inlineLock = false;
|
|
3152
3206
|
try {
|
|
3153
|
-
if (this.
|
|
3207
|
+
if (this.activeTransaction)
|
|
3154
3208
|
await this.ensureTxnLock(tableName);
|
|
3155
3209
|
else {
|
|
3156
3210
|
await File.lock(join(tablePath, ".tmp"));
|
|
@@ -3194,7 +3248,7 @@ export default class Inibase {
|
|
|
3194
3248
|
}
|
|
3195
3249
|
async _delete(tableName, where, _whereIsLinesNumbers, _cascadeGuard) {
|
|
3196
3250
|
this.validateName(tableName);
|
|
3197
|
-
if (!this.
|
|
3251
|
+
if (!this.activeTransaction)
|
|
3198
3252
|
await this.ensureDatabaseRecovered();
|
|
3199
3253
|
const tablePath = join(this.databasePath, tableName);
|
|
3200
3254
|
await this.throwErrorIfTableEmpty(tableName);
|
|
@@ -3204,7 +3258,7 @@ export default class Inibase {
|
|
|
3204
3258
|
// ops) and publish the empty row count in one journaled commit.
|
|
3205
3259
|
const renameList = [];
|
|
3206
3260
|
try {
|
|
3207
|
-
if (this.
|
|
3261
|
+
if (this.activeTransaction)
|
|
3208
3262
|
await this.ensureTxnLock(tableName);
|
|
3209
3263
|
else
|
|
3210
3264
|
await File.lock(join(tablePath, ".tmp"));
|
|
@@ -3217,7 +3271,7 @@ export default class Inibase {
|
|
|
3217
3271
|
from: paginationFilePath,
|
|
3218
3272
|
to: join(tablePath, `${lastId}-0.pagination`),
|
|
3219
3273
|
};
|
|
3220
|
-
if (this.
|
|
3274
|
+
if (this.activeTransaction) {
|
|
3221
3275
|
await this.stageTxnOp(tableName, renameList, pagination);
|
|
3222
3276
|
txnStaged = true;
|
|
3223
3277
|
const stagedEntry = this.txnTableEntry(tableName);
|
|
@@ -3238,7 +3292,7 @@ export default class Inibase {
|
|
|
3238
3292
|
return true;
|
|
3239
3293
|
}
|
|
3240
3294
|
finally {
|
|
3241
|
-
if (this.
|
|
3295
|
+
if (this.activeTransaction) {
|
|
3242
3296
|
if (!txnStaged && renameList.length)
|
|
3243
3297
|
await Promise.allSettled(renameList
|
|
3244
3298
|
.filter((pair) => Boolean(pair[0]))
|
|
@@ -3268,7 +3322,7 @@ export default class Inibase {
|
|
|
3268
3322
|
const renameList = [];
|
|
3269
3323
|
let txnStaged = false;
|
|
3270
3324
|
try {
|
|
3271
|
-
if (this.
|
|
3325
|
+
if (this.activeTransaction)
|
|
3272
3326
|
await this.ensureTxnLock(tableName);
|
|
3273
3327
|
else
|
|
3274
3328
|
await File.lock(join(tablePath, ".tmp"));
|
|
@@ -3281,7 +3335,7 @@ export default class Inibase {
|
|
|
3281
3335
|
from: paginationFilePath,
|
|
3282
3336
|
to: join(tablePath, `${lastId}-${remaining}.pagination`),
|
|
3283
3337
|
};
|
|
3284
|
-
if (this.
|
|
3338
|
+
if (this.activeTransaction) {
|
|
3285
3339
|
await this.stageTxnOp(tableName, renameList, pagination);
|
|
3286
3340
|
txnStaged = true;
|
|
3287
3341
|
const stagedEntry = this.txnTableEntry(tableName);
|
|
@@ -3302,7 +3356,7 @@ export default class Inibase {
|
|
|
3302
3356
|
from: paginationFilePath,
|
|
3303
3357
|
to: join(tablePath, `${lastId}-0.pagination`),
|
|
3304
3358
|
};
|
|
3305
|
-
if (this.
|
|
3359
|
+
if (this.activeTransaction) {
|
|
3306
3360
|
await this.stageTxnOp(tableName, truncateList, pagination);
|
|
3307
3361
|
txnStaged = true;
|
|
3308
3362
|
const stagedEntry = this.txnTableEntry(tableName);
|
|
@@ -3315,7 +3369,7 @@ export default class Inibase {
|
|
|
3315
3369
|
}
|
|
3316
3370
|
// Cache still describes the committed state while a
|
|
3317
3371
|
// transaction is open, so only clear it outside one.
|
|
3318
|
-
if (!this.
|
|
3372
|
+
if (!this.activeTransaction &&
|
|
3319
3373
|
globalConfig[this.databasePath].tables?.get(tableName)?.config.cache)
|
|
3320
3374
|
await this.clearCache(tableName);
|
|
3321
3375
|
// Cascade: rows in other tables referencing the deleted rows
|
|
@@ -3324,7 +3378,7 @@ export default class Inibase {
|
|
|
3324
3378
|
return true;
|
|
3325
3379
|
}
|
|
3326
3380
|
finally {
|
|
3327
|
-
if (this.
|
|
3381
|
+
if (this.activeTransaction) {
|
|
3328
3382
|
if (!txnStaged && renameList.length)
|
|
3329
3383
|
await Promise.allSettled(renameList
|
|
3330
3384
|
.filter((pair) => Boolean(pair[0]))
|
|
@@ -3350,7 +3404,7 @@ export default class Inibase {
|
|
|
3350
3404
|
// lines of a prepend table and remove the wrong row).
|
|
3351
3405
|
let inlineLock = false;
|
|
3352
3406
|
try {
|
|
3353
|
-
if (this.
|
|
3407
|
+
if (this.activeTransaction)
|
|
3354
3408
|
await this.ensureTxnLock(tableName);
|
|
3355
3409
|
else {
|
|
3356
3410
|
await File.lock(join(tablePath, ".tmp"));
|
|
@@ -3424,7 +3478,7 @@ export default class Inibase {
|
|
|
3424
3478
|
// Unreadable/unsupported column -> skip this reference.
|
|
3425
3479
|
// Inside a transaction a broken reference must abort the
|
|
3426
3480
|
// whole cascade (all-or-nothing).
|
|
3427
|
-
if (this.
|
|
3481
|
+
if (this.activeTransaction)
|
|
3428
3482
|
throw error;
|
|
3429
3483
|
}
|
|
3430
3484
|
}
|
|
@@ -3444,7 +3498,7 @@ export default class Inibase {
|
|
|
3444
3498
|
// Cascade is best-effort outside a transaction: never break
|
|
3445
3499
|
// the parent delete. Inside a transaction a cascade failure
|
|
3446
3500
|
// must abort the whole txn (all-or-nothing).
|
|
3447
|
-
if (this.
|
|
3501
|
+
if (this.activeTransaction)
|
|
3448
3502
|
throw error;
|
|
3449
3503
|
}
|
|
3450
3504
|
}
|
|
@@ -3503,7 +3557,9 @@ export default class Inibase {
|
|
|
3503
3557
|
const targetFields = new Map();
|
|
3504
3558
|
for (const column of columns) {
|
|
3505
3559
|
const dot = column.indexOf(".");
|
|
3506
|
-
if (dot <= 0 ||
|
|
3560
|
+
if (dot <= 0 ||
|
|
3561
|
+
dot === column.length - 1 ||
|
|
3562
|
+
column.indexOf(".", dot + 1) !== -1)
|
|
3507
3563
|
throw this.createError("INVALID_PARAMETERS", [
|
|
3508
3564
|
`sum nested: '${column}' must be a single-level child path of an array-of-objects column`,
|
|
3509
3565
|
]);
|
|
@@ -3629,7 +3685,8 @@ export default class Inibase {
|
|
|
3629
3685
|
let matches = true;
|
|
3630
3686
|
for (const p of elementPredicates) {
|
|
3631
3687
|
const pv = predCells.get(p.field.key)?.[Number(lineStr)]?.[i];
|
|
3632
|
-
if (pv === undefined ||
|
|
3688
|
+
if (pv === undefined ||
|
|
3689
|
+
!UtilsServer.compare(p.operator, pv, p.comparedValue, p.field.type)) {
|
|
3633
3690
|
matches = false;
|
|
3634
3691
|
break;
|
|
3635
3692
|
}
|