@naturalcycles/db-lib 8.41.0 → 8.42.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/adapter/cachedb/cache.db.d.ts +3 -1
- package/dist/adapter/cachedb/cache.db.js +4 -0
- package/dist/adapter/file/file.db.js +24 -4
- package/dist/adapter/file/file.db.model.d.ts +1 -1
- package/dist/adapter/inmemory/inMemory.db.js +23 -14
- package/dist/base.common.db.d.ts +1 -0
- package/dist/base.common.db.js +1 -0
- package/dist/commondao/common.dao.d.ts +8 -1
- package/dist/commondao/common.dao.js +59 -6
- package/dist/commondao/common.dao.model.d.ts +10 -0
- package/dist/db.model.d.ts +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -2
- package/dist/testing/daoTest.js +42 -1
- package/dist/testing/dbTest.d.ts +1 -0
- package/dist/testing/dbTest.js +43 -11
- package/dist/timeseries/commonTimeSeriesDao.js +1 -1
- package/dist/transaction/dbTransaction.d.ts +7 -0
- package/dist/transaction/dbTransaction.js +24 -2
- package/dist/transaction/dbTransaction.util.d.ts +3 -3
- package/dist/transaction/dbTransaction.util.js +55 -55
- package/package.json +2 -3
- package/src/adapter/cachedb/cache.db.ts +7 -1
- package/src/adapter/file/file.db.model.ts +1 -1
- package/src/adapter/file/file.db.ts +28 -5
- package/src/adapter/inmemory/inMemory.db.ts +23 -12
- package/src/base.common.db.ts +1 -0
- package/src/commondao/common.dao.model.ts +11 -0
- package/src/commondao/common.dao.ts +80 -8
- package/src/db.model.ts +2 -0
- package/src/index.ts +1 -2
- package/src/testing/daoTest.ts +54 -1
- package/src/testing/dbTest.ts +53 -10
- package/src/timeseries/commonTimeSeriesDao.ts +1 -1
- package/src/transaction/dbTransaction.ts +26 -1
- package/src/transaction/dbTransaction.util.ts +29 -33
package/src/testing/dbTest.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { pDelay, pMap, _filterObject, _pick, _sortBy } from '@naturalcycles/js-l
|
|
|
2
2
|
import { readableToArray } from '@naturalcycles/nodejs-lib'
|
|
3
3
|
import { CommonDB } from '../common.db'
|
|
4
4
|
import { DBQuery } from '../query/dbQuery'
|
|
5
|
+
import { DBTransaction } from '../transaction/dbTransaction'
|
|
5
6
|
import {
|
|
6
7
|
createTestItemDBM,
|
|
7
8
|
createTestItemsDBM,
|
|
@@ -43,6 +44,8 @@ export interface CommonDBImplementationFeatures {
|
|
|
43
44
|
* they will return `null` for all missing properties.
|
|
44
45
|
*/
|
|
45
46
|
documentDB?: boolean
|
|
47
|
+
|
|
48
|
+
transactions?: boolean
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
/**
|
|
@@ -89,6 +92,7 @@ export function runCommonDBTest(
|
|
|
89
92
|
bufferSupport = true,
|
|
90
93
|
nullValues = true,
|
|
91
94
|
documentDB = true,
|
|
95
|
+
transactions = true,
|
|
92
96
|
} = features
|
|
93
97
|
|
|
94
98
|
// const {
|
|
@@ -190,6 +194,10 @@ export function runCommonDBTest(
|
|
|
190
194
|
await db.saveBatch(TEST_TABLE, items)
|
|
191
195
|
})
|
|
192
196
|
|
|
197
|
+
test('saveBatch should throw on null id', async () => {
|
|
198
|
+
await expect(db.saveBatch(TEST_TABLE, [{ ...item1, id: null as any }])).rejects.toThrow()
|
|
199
|
+
})
|
|
200
|
+
|
|
193
201
|
if (insert) {
|
|
194
202
|
test('saveBatch INSERT method should throw', async () => {
|
|
195
203
|
await expect(db.saveBatch(TEST_TABLE, items, { saveMethod: 'insert' })).rejects.toThrow()
|
|
@@ -290,12 +298,12 @@ export function runCommonDBTest(
|
|
|
290
298
|
// getTables
|
|
291
299
|
test('getTables, getTableSchema (if supported)', async () => {
|
|
292
300
|
const tables = await db.getTables()
|
|
293
|
-
console.log({ tables })
|
|
301
|
+
// console.log({ tables })
|
|
294
302
|
|
|
295
303
|
if (tableSchemas) {
|
|
296
304
|
await pMap(tables, async table => {
|
|
297
305
|
const schema = await db.getTableSchema(table)
|
|
298
|
-
console.log(schema)
|
|
306
|
+
// console.log(schema)
|
|
299
307
|
expect(schema.$id).toBe(`${table}.schema.json`)
|
|
300
308
|
})
|
|
301
309
|
}
|
|
@@ -327,19 +335,54 @@ export function runCommonDBTest(
|
|
|
327
335
|
await db.saveBatch(TEST_TABLE, [item])
|
|
328
336
|
const [loaded] = await db.getByIds<TestItemDBM>(TEST_TABLE, [item.id])
|
|
329
337
|
const b1Loaded = loaded!.b1!
|
|
330
|
-
console.log({
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
})
|
|
338
|
+
// console.log({
|
|
339
|
+
// b11: typeof b1,
|
|
340
|
+
// b12: typeof b1Loaded,
|
|
341
|
+
// l1: b1.length,
|
|
342
|
+
// l2: b1Loaded.length,
|
|
343
|
+
// b1,
|
|
344
|
+
// b1Loaded,
|
|
345
|
+
// })
|
|
338
346
|
expect(b1Loaded).toEqual(b1)
|
|
339
347
|
expect(b1Loaded.toString()).toBe(s)
|
|
340
348
|
})
|
|
341
349
|
}
|
|
342
350
|
|
|
351
|
+
if (transactions) {
|
|
352
|
+
test('transaction happy path', async () => {
|
|
353
|
+
// cleanup
|
|
354
|
+
await db.deleteByQuery(queryAll())
|
|
355
|
+
|
|
356
|
+
// saveBatch [item1, 2, 3]
|
|
357
|
+
// save item3 with k1: k1_mod
|
|
358
|
+
// delete item2
|
|
359
|
+
// remaining: item1, item3_with_k1_mod
|
|
360
|
+
const tx = DBTransaction.create()
|
|
361
|
+
.saveBatch(TEST_TABLE, items)
|
|
362
|
+
.save(TEST_TABLE, { ...items[2]!, k1: 'k1_mod' })
|
|
363
|
+
.deleteById(TEST_TABLE, items[1]!.id)
|
|
364
|
+
|
|
365
|
+
await db.commitTransaction(tx)
|
|
366
|
+
|
|
367
|
+
const { rows } = await db.runQuery(queryAll())
|
|
368
|
+
const expected = [items[0], { ...items[2]!, k1: 'k1_mod' }]
|
|
369
|
+
expectMatch(expected, rows, quirks)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
test('transaction rollback', async () => {
|
|
373
|
+
// It should fail on id == null
|
|
374
|
+
const tx = DBTransaction.create()
|
|
375
|
+
.deleteById(TEST_TABLE, items[2]!.id)
|
|
376
|
+
.save(TEST_TABLE, { ...items[0]!, k1: 5, id: null as any })
|
|
377
|
+
|
|
378
|
+
await expect(db.commitTransaction(tx)).rejects.toThrow()
|
|
379
|
+
|
|
380
|
+
const { rows } = await db.runQuery(queryAll())
|
|
381
|
+
const expected = [items[0], { ...items[2]!, k1: 'k1_mod' }]
|
|
382
|
+
expectMatch(expected, rows, quirks)
|
|
383
|
+
})
|
|
384
|
+
}
|
|
385
|
+
|
|
343
386
|
if (querying) {
|
|
344
387
|
test('cleanup', async () => {
|
|
345
388
|
// CLEAN UP
|
|
@@ -53,7 +53,7 @@ export class CommonTimeSeriesDao {
|
|
|
53
53
|
async commitTransaction(ops: TimeSeriesSaveBatchOp[]): Promise<void> {
|
|
54
54
|
if (!ops.length) return
|
|
55
55
|
|
|
56
|
-
const tx =
|
|
56
|
+
const tx = DBTransaction.create()
|
|
57
57
|
|
|
58
58
|
ops.forEach(op => {
|
|
59
59
|
const rows: ObjectWithId[] = op.dataPoints.map(([ts, v]) => ({
|
|
@@ -6,7 +6,23 @@ import type { CommonDBSaveOptions, DBOperation } from '../db.model'
|
|
|
6
6
|
* Convenience class that stores the list of DBOperations and provides a fluent API to add them.
|
|
7
7
|
*/
|
|
8
8
|
export class DBTransaction {
|
|
9
|
-
public ops: DBOperation[] = []
|
|
9
|
+
protected constructor(public ops: DBOperation[] = []) {}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Convenience method.
|
|
13
|
+
*/
|
|
14
|
+
static create(ops: DBOperation[] = []): DBTransaction {
|
|
15
|
+
return new DBTransaction(ops)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
save<ROW extends ObjectWithId = AnyObjectWithId>(table: string, row: ROW): this {
|
|
19
|
+
this.ops.push({
|
|
20
|
+
type: 'saveBatch',
|
|
21
|
+
table,
|
|
22
|
+
rows: [row],
|
|
23
|
+
})
|
|
24
|
+
return this
|
|
25
|
+
}
|
|
10
26
|
|
|
11
27
|
saveBatch<ROW extends ObjectWithId = AnyObjectWithId>(table: string, rows: ROW[]): this {
|
|
12
28
|
this.ops.push({
|
|
@@ -17,6 +33,15 @@ export class DBTransaction {
|
|
|
17
33
|
return this
|
|
18
34
|
}
|
|
19
35
|
|
|
36
|
+
deleteById(table: string, id: string): this {
|
|
37
|
+
this.ops.push({
|
|
38
|
+
type: 'deleteByIds',
|
|
39
|
+
table,
|
|
40
|
+
ids: [id],
|
|
41
|
+
})
|
|
42
|
+
return this
|
|
43
|
+
}
|
|
44
|
+
|
|
20
45
|
deleteByIds(table: string, ids: string[]): this {
|
|
21
46
|
this.ops.push({
|
|
22
47
|
type: 'deleteByIds',
|
|
@@ -1,30 +1,32 @@
|
|
|
1
|
-
import { StringMap, _stringMapEntries, ObjectWithId } from '@naturalcycles/js-lib'
|
|
2
1
|
import type { CommonDB } from '../common.db'
|
|
3
|
-
import { CommonDBSaveOptions
|
|
2
|
+
import { CommonDBSaveOptions } from '../db.model'
|
|
4
3
|
import { DBTransaction } from './dbTransaction'
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Optimizes the Transaction (list of DBOperations) to do less operations.
|
|
8
7
|
* E.g if you save id1 first and then delete it - this function will turn it into a no-op (self-eliminate).
|
|
8
|
+
* UPD: actually, it will only keep delete, but remove previous ops.
|
|
9
9
|
*
|
|
10
10
|
* Currently only takes into account SaveBatch and DeleteByIds ops.
|
|
11
|
-
* Output ops are maximum
|
|
11
|
+
* Output ops are maximum 1 per entity - save or delete.
|
|
12
12
|
*/
|
|
13
|
+
// Commented out as "overly complicated"
|
|
14
|
+
/*
|
|
13
15
|
export function mergeDBOperations(ops: DBOperation[]): DBOperation[] {
|
|
14
16
|
if (ops.length <= 1) return ops // nothing to optimize there
|
|
15
17
|
|
|
16
18
|
// This map will be saved in the end. Null would mean "delete"
|
|
17
19
|
// saveMap[table][id] => row
|
|
18
|
-
const
|
|
20
|
+
const data: StringMap<StringMap<ObjectWithId | null>> = {}
|
|
19
21
|
|
|
20
22
|
// Merge ops using `saveMap`
|
|
21
23
|
ops.forEach(op => {
|
|
22
|
-
|
|
24
|
+
data[op.table] ||= {}
|
|
23
25
|
|
|
24
26
|
if (op.type === 'saveBatch') {
|
|
25
|
-
op.rows.forEach(r => (
|
|
27
|
+
op.rows.forEach(r => (data[op.table]![r.id] = r))
|
|
26
28
|
} else if (op.type === 'deleteByIds') {
|
|
27
|
-
op.ids.forEach(id => (
|
|
29
|
+
op.ids.forEach(id => (data[op.table]![id] = null))
|
|
28
30
|
} else {
|
|
29
31
|
throw new Error(`DBOperation not supported: ${(op as any).type}`)
|
|
30
32
|
}
|
|
@@ -32,37 +34,31 @@ export function mergeDBOperations(ops: DBOperation[]): DBOperation[] {
|
|
|
32
34
|
|
|
33
35
|
const resultOps: DBOperation[] = []
|
|
34
36
|
|
|
35
|
-
_stringMapEntries(
|
|
36
|
-
const
|
|
37
|
-
|
|
37
|
+
_stringMapEntries(data).forEach(([table, map]) => {
|
|
38
|
+
const saveOp: DBSaveBatchOperation = {
|
|
39
|
+
type: 'saveBatch',
|
|
40
|
+
table,
|
|
41
|
+
rows: _stringMapValues(map).filter(_isTruthy),
|
|
42
|
+
}
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
} else {
|
|
43
|
-
rowsToSave.push(r)
|
|
44
|
-
}
|
|
45
|
-
})
|
|
44
|
+
if (saveOp.rows.length) {
|
|
45
|
+
resultOps.push(saveOp)
|
|
46
|
+
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
rows: rowsToSave,
|
|
52
|
-
})
|
|
48
|
+
const deleteOp: DBDeleteByIdsOperation = {
|
|
49
|
+
type: 'deleteByIds',
|
|
50
|
+
table,
|
|
51
|
+
ids: _stringMapEntries(map).filter(([id, row]) => row === null).map(([id]) => id),
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
if (
|
|
56
|
-
resultOps.push(
|
|
57
|
-
type: 'deleteByIds',
|
|
58
|
-
table,
|
|
59
|
-
ids: idsToDelete,
|
|
60
|
-
})
|
|
54
|
+
if (deleteOp.ids.length) {
|
|
55
|
+
resultOps.push(deleteOp)
|
|
61
56
|
}
|
|
62
57
|
})
|
|
63
58
|
|
|
64
59
|
return resultOps
|
|
65
60
|
}
|
|
61
|
+
*/
|
|
66
62
|
|
|
67
63
|
/**
|
|
68
64
|
* Naive implementation of "Transaction" which just executes all operations one-by-one.
|
|
@@ -74,13 +70,13 @@ export async function commitDBTransactionSimple(
|
|
|
74
70
|
tx: DBTransaction,
|
|
75
71
|
opt?: CommonDBSaveOptions,
|
|
76
72
|
): Promise<void> {
|
|
77
|
-
const ops = mergeDBOperations(tx.ops)
|
|
73
|
+
// const ops = mergeDBOperations(tx.ops)
|
|
78
74
|
|
|
79
|
-
for await (const op of ops) {
|
|
75
|
+
for await (const op of tx.ops) {
|
|
80
76
|
if (op.type === 'saveBatch') {
|
|
81
|
-
await db.saveBatch(op.table, op.rows, opt)
|
|
77
|
+
await db.saveBatch(op.table, op.rows, { ...op.opt, ...opt })
|
|
82
78
|
} else if (op.type === 'deleteByIds') {
|
|
83
|
-
await db.deleteByIds(op.table, op.ids, opt)
|
|
79
|
+
await db.deleteByIds(op.table, op.ids, { ...op.opt, ...opt })
|
|
84
80
|
} else {
|
|
85
81
|
throw new Error(`DBOperation not supported: ${(op as any).type}`)
|
|
86
82
|
}
|