@naturalcycles/db-lib 10.22.0 → 10.24.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/file/localFile.persistence.plugin.js +2 -3
- package/dist/commondao/common.dao.d.ts +1 -1
- package/dist/commondao/common.dao.js +65 -77
- package/dist/commondb/common.db.d.ts +1 -0
- package/dist/commondb/common.db.js +1 -0
- package/dist/pipeline/dbPipelineBackup.js +16 -20
- package/dist/pipeline/dbPipelineCopy.js +19 -21
- package/dist/pipeline/dbPipelineRestore.js +21 -24
- package/dist/testing/commonDaoTest.js +53 -50
- package/package.json +1 -1
- package/src/adapter/file/localFile.persistence.plugin.ts +2 -7
- package/src/commondao/common.dao.ts +32 -45
- package/src/commondb/common.db.ts +2 -0
- package/src/pipeline/dbPipelineBackup.ts +10 -21
- package/src/pipeline/dbPipelineCopy.ts +12 -22
- package/src/pipeline/dbPipelineRestore.ts +14 -27
- package/src/testing/commonDaoTest.ts +52 -49
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { Readable } from 'node:stream';
|
|
2
1
|
import { pMap } from '@naturalcycles/js-lib/promise/pMap.js';
|
|
3
2
|
import { fs2 } from '@naturalcycles/nodejs-lib/fs2';
|
|
4
|
-
import {
|
|
3
|
+
import { createReadStreamAsNDJSON, Pipeline } from '@naturalcycles/nodejs-lib/stream';
|
|
5
4
|
/**
|
|
6
5
|
* Persists in local filesystem as ndjson.
|
|
7
6
|
*/
|
|
@@ -35,6 +34,6 @@ export class LocalFilePersistencePlugin {
|
|
|
35
34
|
await fs2.ensureDirAsync(this.cfg.storagePath);
|
|
36
35
|
const ext = `ndjson${this.cfg.gzip ? '.gz' : ''}`;
|
|
37
36
|
const filePath = `${this.cfg.storagePath}/${table}.${ext}`;
|
|
38
|
-
await
|
|
37
|
+
await Pipeline.fromArray(rows).toNDJsonFile(filePath);
|
|
39
38
|
}
|
|
40
39
|
}
|
|
@@ -107,7 +107,7 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
|
|
|
107
107
|
* and then executing db.saveBatch(chunk) with the concurrency
|
|
108
108
|
* of opt.chunkConcurrency (which defaults to 32).
|
|
109
109
|
*/
|
|
110
|
-
|
|
110
|
+
streamSaveTransforms(opt?: CommonDaoStreamSaveOptions<DBM>): Transform[];
|
|
111
111
|
/**
|
|
112
112
|
* @returns number of deleted items
|
|
113
113
|
*/
|
|
@@ -8,8 +8,8 @@ import { pMap } from '@naturalcycles/js-lib/promise/pMap.js';
|
|
|
8
8
|
import { _stringMapEntries, _stringMapValues, } from '@naturalcycles/js-lib/types';
|
|
9
9
|
import { _passthroughPredicate, _typeCast } from '@naturalcycles/js-lib/types';
|
|
10
10
|
import { stringId } from '@naturalcycles/nodejs-lib';
|
|
11
|
-
import { transformFlatten, transformMapSync, } from '@naturalcycles/nodejs-lib/stream';
|
|
12
|
-
import {
|
|
11
|
+
import { Pipeline, transformFlatten, transformMapSync, } from '@naturalcycles/nodejs-lib/stream';
|
|
12
|
+
import { transformChunk, transformLogProgress, transformMap, transformNoOp, } from '@naturalcycles/nodejs-lib/stream';
|
|
13
13
|
import { DBLibError } from '../cnst.js';
|
|
14
14
|
import { RunnableDBQuery } from '../query/dbQuery.js';
|
|
15
15
|
import { CommonDaoTransaction } from './commonDaoTransaction.js';
|
|
@@ -167,26 +167,24 @@ export class CommonDao {
|
|
|
167
167
|
opt.skipValidation = opt.skipValidation !== false; // default true
|
|
168
168
|
opt.errorMode ||= ErrorMode.SUPPRESS;
|
|
169
169
|
const isPartialQuery = !!q._selectedFieldNames;
|
|
170
|
-
await
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}),
|
|
170
|
+
await Pipeline.from(this.cfg.db.streamQuery(q, opt))
|
|
171
|
+
.map(async (dbm) => {
|
|
172
|
+
if (isPartialQuery)
|
|
173
|
+
return dbm;
|
|
174
|
+
return await this.dbmToBM(dbm, opt);
|
|
175
|
+
}, {
|
|
176
|
+
errorMode: opt.errorMode,
|
|
177
|
+
})
|
|
178
|
+
.map(mapper, {
|
|
179
|
+
...opt,
|
|
180
|
+
predicate: _passthroughPredicate, // to be able to logProgress
|
|
181
|
+
})
|
|
183
182
|
// LogProgress should be AFTER the mapper, to be able to report correct stats
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
]);
|
|
183
|
+
.logProgress({
|
|
184
|
+
metric: q.table,
|
|
185
|
+
...opt,
|
|
186
|
+
})
|
|
187
|
+
.run();
|
|
190
188
|
}
|
|
191
189
|
async streamQueryAsDBMForEach(q, mapper, opt = {}) {
|
|
192
190
|
this.validateQueryIndexes(q); // throws if query uses `excludeFromIndexes` property
|
|
@@ -194,26 +192,23 @@ export class CommonDao {
|
|
|
194
192
|
opt.skipValidation = opt.skipValidation !== false; // default true
|
|
195
193
|
opt.errorMode ||= ErrorMode.SUPPRESS;
|
|
196
194
|
const isPartialQuery = !!q._selectedFieldNames;
|
|
197
|
-
await
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}),
|
|
215
|
-
writableVoid(),
|
|
216
|
-
]);
|
|
195
|
+
await Pipeline.from(this.cfg.db.streamQuery(q, opt))
|
|
196
|
+
.mapSync(dbm => {
|
|
197
|
+
if (isPartialQuery)
|
|
198
|
+
return dbm;
|
|
199
|
+
return this.anyToDBM(dbm, opt);
|
|
200
|
+
}, {
|
|
201
|
+
errorMode: opt.errorMode,
|
|
202
|
+
})
|
|
203
|
+
.map(mapper, {
|
|
204
|
+
...opt,
|
|
205
|
+
predicate: _passthroughPredicate, // to be able to logProgress
|
|
206
|
+
})
|
|
207
|
+
.logProgress({
|
|
208
|
+
metric: q.table,
|
|
209
|
+
...opt,
|
|
210
|
+
})
|
|
211
|
+
.run();
|
|
217
212
|
}
|
|
218
213
|
/**
|
|
219
214
|
* Stream as Readable, to be able to .pipe() it further with support of backpressure.
|
|
@@ -306,19 +301,17 @@ export class CommonDao {
|
|
|
306
301
|
this.validateQueryIndexes(q); // throws if query uses `excludeFromIndexes` property
|
|
307
302
|
q.table = opt.table || q.table;
|
|
308
303
|
opt.errorMode ||= ErrorMode.SUPPRESS;
|
|
309
|
-
await
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}),
|
|
304
|
+
await Pipeline.from(this.cfg.db.streamQuery(q.select(['id']), opt).map(r => r.id))
|
|
305
|
+
.map(mapper, {
|
|
306
|
+
...opt,
|
|
307
|
+
predicate: _passthroughPredicate,
|
|
308
|
+
})
|
|
315
309
|
// LogProgress should be AFTER the mapper, to be able to report correct stats
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
]);
|
|
310
|
+
.logProgress({
|
|
311
|
+
metric: q.table,
|
|
312
|
+
...opt,
|
|
313
|
+
})
|
|
314
|
+
.run();
|
|
322
315
|
}
|
|
323
316
|
/**
|
|
324
317
|
* Mutates!
|
|
@@ -529,7 +522,7 @@ export class CommonDao {
|
|
|
529
522
|
* and then executing db.saveBatch(chunk) with the concurrency
|
|
530
523
|
* of opt.chunkConcurrency (which defaults to 32).
|
|
531
524
|
*/
|
|
532
|
-
|
|
525
|
+
streamSaveTransforms(opt = {}) {
|
|
533
526
|
this.requireWriteAccess();
|
|
534
527
|
const table = opt.table || this.cfg.table;
|
|
535
528
|
opt.skipValidation ??= true;
|
|
@@ -549,7 +542,7 @@ export class CommonDao {
|
|
|
549
542
|
}, {
|
|
550
543
|
errorMode,
|
|
551
544
|
}),
|
|
552
|
-
transformChunk(
|
|
545
|
+
transformChunk(chunkSize),
|
|
553
546
|
transformMap(async (batch) => {
|
|
554
547
|
await this.cfg.db.saveBatch(table, batch, {
|
|
555
548
|
...opt,
|
|
@@ -565,9 +558,6 @@ export class CommonDao {
|
|
|
565
558
|
metric: 'saved',
|
|
566
559
|
...opt,
|
|
567
560
|
}),
|
|
568
|
-
// just to satisfy and simplify typings
|
|
569
|
-
// It's easier to return Transform[], rather than (Transform | Writable)[]
|
|
570
|
-
writableVoid(),
|
|
571
561
|
];
|
|
572
562
|
}
|
|
573
563
|
// DELETE
|
|
@@ -600,26 +590,24 @@ export class CommonDao {
|
|
|
600
590
|
let deleted = 0;
|
|
601
591
|
if (opt.chunkSize) {
|
|
602
592
|
const { chunkSize, chunkConcurrency = 8 } = opt;
|
|
603
|
-
await
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}),
|
|
593
|
+
await Pipeline.from(this.cfg.db.streamQuery(q.select(['id']), opt).map(r => r.id))
|
|
594
|
+
.chunk(chunkSize)
|
|
595
|
+
.map(async (ids) => {
|
|
596
|
+
await this.cfg.db.deleteByIds(q.table, ids, opt);
|
|
597
|
+
deleted += ids.length;
|
|
598
|
+
}, {
|
|
599
|
+
predicate: _passthroughPredicate,
|
|
600
|
+
concurrency: chunkConcurrency,
|
|
601
|
+
errorMode: opt.errorMode || ErrorMode.THROW_IMMEDIATELY,
|
|
602
|
+
})
|
|
614
603
|
// LogProgress should be AFTER the mapper, to be able to report correct stats
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
]);
|
|
604
|
+
.logProgress({
|
|
605
|
+
metric: q.table,
|
|
606
|
+
logEvery: 2, // 500 * 2 === 1000
|
|
607
|
+
chunkSize,
|
|
608
|
+
...opt,
|
|
609
|
+
})
|
|
610
|
+
.run();
|
|
623
611
|
}
|
|
624
612
|
else {
|
|
625
613
|
deleted = await this.cfg.db.deleteByQuery(q, opt);
|
|
@@ -4,8 +4,8 @@ import { pMap } from '@naturalcycles/js-lib/promise/pMap.js';
|
|
|
4
4
|
import { _passthroughMapper } from '@naturalcycles/js-lib/types';
|
|
5
5
|
import { boldWhite, dimWhite, grey, yellow } from '@naturalcycles/nodejs-lib/colors';
|
|
6
6
|
import { fs2 } from '@naturalcycles/nodejs-lib/fs2';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { Pipeline, } from '@naturalcycles/nodejs-lib/stream';
|
|
8
|
+
import { NDJsonStats } from '@naturalcycles/nodejs-lib/stream';
|
|
9
9
|
import { DBQuery } from '../query/dbQuery.js';
|
|
10
10
|
/**
|
|
11
11
|
* Pipeline from input stream(s) to a NDJSON file (optionally gzipped).
|
|
@@ -56,24 +56,20 @@ export async function dbPipelineBackup(opt) {
|
|
|
56
56
|
await fs2.writeJsonAsync(schemaFilePath, schema, { spaces: 2 });
|
|
57
57
|
console.log(`>> ${grey(schemaFilePath)} saved (generated from DB)`);
|
|
58
58
|
}
|
|
59
|
-
await
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
rows++;
|
|
74
|
-
}),
|
|
75
|
-
...createWriteStreamAsNDJSON(filePath),
|
|
76
|
-
]);
|
|
59
|
+
await Pipeline.from(db.streamQuery(q))
|
|
60
|
+
.logProgress({
|
|
61
|
+
...opt,
|
|
62
|
+
logEvery: logEveryPerTable[table] ?? opt.logEvery ?? 1000,
|
|
63
|
+
metric: table,
|
|
64
|
+
})
|
|
65
|
+
.map(mapperPerTable[table] || _passthroughMapper, {
|
|
66
|
+
errorMode,
|
|
67
|
+
...transformMapOptions,
|
|
68
|
+
metric: table,
|
|
69
|
+
})
|
|
70
|
+
.flattenIfNeeded()
|
|
71
|
+
.tap(() => rows++)
|
|
72
|
+
.toNDJsonFile(filePath);
|
|
77
73
|
const { size: sizeBytes } = await fs2.statAsync(filePath);
|
|
78
74
|
const stats = NDJsonStats.create({
|
|
79
75
|
tookMillis: Date.now() - started,
|
|
@@ -3,8 +3,8 @@ import { ErrorMode } from '@naturalcycles/js-lib/error/errorMode.js';
|
|
|
3
3
|
import { pMap } from '@naturalcycles/js-lib/promise/pMap.js';
|
|
4
4
|
import { _passthroughMapper } from '@naturalcycles/js-lib/types';
|
|
5
5
|
import { boldWhite, dimWhite, grey, yellow } from '@naturalcycles/nodejs-lib/colors';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { Pipeline, } from '@naturalcycles/nodejs-lib/stream';
|
|
7
|
+
import { NDJsonStats } from '@naturalcycles/nodejs-lib/stream';
|
|
8
8
|
import { DBQuery } from '../query/dbQuery.js';
|
|
9
9
|
/**
|
|
10
10
|
* Pipeline from input stream(s) to CommonDB .saveBatch().
|
|
@@ -30,25 +30,23 @@ export async function dbPipelineCopy(opt) {
|
|
|
30
30
|
const stream = dbInput.streamQuery(q);
|
|
31
31
|
const started = Date.now();
|
|
32
32
|
let rows = 0;
|
|
33
|
-
await
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}),
|
|
51
|
-
]);
|
|
33
|
+
await Pipeline.from(stream)
|
|
34
|
+
.logProgress({
|
|
35
|
+
logEvery: 1000,
|
|
36
|
+
...opt,
|
|
37
|
+
metric: table,
|
|
38
|
+
})
|
|
39
|
+
.map(mapper, {
|
|
40
|
+
errorMode,
|
|
41
|
+
...transformMapOptions,
|
|
42
|
+
metric: table,
|
|
43
|
+
})
|
|
44
|
+
.flattenIfNeeded()
|
|
45
|
+
.tap(() => rows++)
|
|
46
|
+
.chunk(chunkSize)
|
|
47
|
+
.forEach(async (dbms) => {
|
|
48
|
+
await dbOutput.saveBatch(table, dbms, saveOptions);
|
|
49
|
+
});
|
|
52
50
|
const stats = NDJsonStats.create({
|
|
53
51
|
tookMillis: Date.now() - started,
|
|
54
52
|
rows,
|
|
@@ -6,8 +6,8 @@ import { pMap } from '@naturalcycles/js-lib/promise/pMap.js';
|
|
|
6
6
|
import { _passthroughMapper } from '@naturalcycles/js-lib/types';
|
|
7
7
|
import { boldWhite, dimWhite, grey, yellow } from '@naturalcycles/nodejs-lib/colors';
|
|
8
8
|
import { fs2 } from '@naturalcycles/nodejs-lib/fs2';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { Pipeline, } from '@naturalcycles/nodejs-lib/stream';
|
|
10
|
+
import { NDJsonStats } from '@naturalcycles/nodejs-lib/stream';
|
|
11
11
|
/**
|
|
12
12
|
* Pipeline from NDJSON files in a folder (optionally gzipped) to CommonDB.
|
|
13
13
|
* Allows to define a mapper and a predicate to map/filter objects between input and output.
|
|
@@ -67,28 +67,25 @@ export async function dbPipelineRestore(opt) {
|
|
|
67
67
|
let rows = 0;
|
|
68
68
|
const sizeBytes = sizeByTable[table];
|
|
69
69
|
console.log(`<< ${grey(filePath)} ${dimWhite(_hb(sizeBytes))} started...`);
|
|
70
|
-
await
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
await db.saveBatch(table, dbms, saveOptions);
|
|
90
|
-
}),
|
|
91
|
-
]);
|
|
70
|
+
await Pipeline.fromNDJsonFile(filePath)
|
|
71
|
+
.limitSource(limit)
|
|
72
|
+
.tap(() => rows++)
|
|
73
|
+
.logProgress({
|
|
74
|
+
logEvery: 1000,
|
|
75
|
+
...opt,
|
|
76
|
+
metric: table,
|
|
77
|
+
})
|
|
78
|
+
.filterSync(r => !sinceUpdated || r.updated >= sinceUpdated)
|
|
79
|
+
.map(mapperPerTable[table] || _passthroughMapper, {
|
|
80
|
+
errorMode,
|
|
81
|
+
...transformMapOptions,
|
|
82
|
+
metric: table,
|
|
83
|
+
})
|
|
84
|
+
.flattenIfNeeded()
|
|
85
|
+
.chunk(chunkSize)
|
|
86
|
+
.forEach(async (dbms) => {
|
|
87
|
+
await db.saveBatch(table, dbms, saveOptions);
|
|
88
|
+
});
|
|
92
89
|
const stats = NDJsonStats.create({
|
|
93
90
|
tookMillis: Date.now() - started,
|
|
94
91
|
rows,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { Readable } from 'node:stream';
|
|
2
1
|
import { _sortBy } from '@naturalcycles/js-lib/array/array.util.js';
|
|
3
2
|
import { localTime } from '@naturalcycles/js-lib/datetime/localTime.js';
|
|
4
3
|
import { _deepCopy, _filterObject, _omit, _pick } from '@naturalcycles/js-lib/object';
|
|
5
4
|
import { getJoiValidationFunction } from '@naturalcycles/nodejs-lib/joi';
|
|
6
|
-
import {
|
|
5
|
+
import { Pipeline } from '@naturalcycles/nodejs-lib/stream';
|
|
7
6
|
import { CommonDao } from '../commondao/common.dao.js';
|
|
8
7
|
import { DBQuery } from '../query/dbQuery.js';
|
|
9
8
|
import { createTestItemBM, createTestItemsBM, TEST_TABLE, testItemBMJsonSchema, testItemBMSchema, } from './test.model.js';
|
|
@@ -203,7 +202,7 @@ export async function runCommonDaoTest(db, quirks = {}) {
|
|
|
203
202
|
test('streamSaveTransform', async () => {
|
|
204
203
|
const items2 = createTestItemsBM(2).map(i => ({ ...i, id: i.id + '_str' }));
|
|
205
204
|
const ids = items2.map(i => i.id);
|
|
206
|
-
await
|
|
205
|
+
await Pipeline.fromArray(items2).transformMany(dao.streamSaveTransforms()).run();
|
|
207
206
|
const items2Loaded = await dao.getByIds(ids);
|
|
208
207
|
expectMatch(items2, items2Loaded, quirks);
|
|
209
208
|
// cleanup
|
|
@@ -263,36 +262,38 @@ export async function runCommonDaoTest(db, quirks = {}) {
|
|
|
263
262
|
const expected = [items[0], { ...items[2], k1: 'k1_mod' }];
|
|
264
263
|
expectMatch(expected, rows, quirks);
|
|
265
264
|
});
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
265
|
+
if (support.createTransaction) {
|
|
266
|
+
test('createTransaction happy path', async () => {
|
|
267
|
+
// cleanup
|
|
268
|
+
await dao.query().deleteByQuery();
|
|
269
|
+
// Test that id, created, updated are created
|
|
270
|
+
const now = localTime.nowUnix();
|
|
271
|
+
const row = _omit(item1, ['id', 'created', 'updated']);
|
|
272
|
+
let tx = await dao.createTransaction();
|
|
273
|
+
await tx.save(dao, row);
|
|
274
|
+
await tx.commit();
|
|
275
|
+
const loaded = await dao.query().runQuery();
|
|
276
|
+
expect(loaded.length).toBe(1);
|
|
277
|
+
expect(loaded[0].id).toBeDefined();
|
|
278
|
+
expect(loaded[0].created).toBeGreaterThanOrEqual(now);
|
|
279
|
+
expect(loaded[0].updated).toBe(loaded[0].created);
|
|
280
|
+
tx = await dao.createTransaction();
|
|
281
|
+
await tx.deleteById(dao, loaded[0].id);
|
|
282
|
+
await tx.commit();
|
|
283
|
+
// saveBatch [item1, 2, 3]
|
|
284
|
+
// save item3 with k1: k1_mod
|
|
285
|
+
// delete item2
|
|
286
|
+
// remaining: item1, item3_with_k1_mod
|
|
287
|
+
tx = await dao.createTransaction();
|
|
288
|
+
await tx.saveBatch(dao, items);
|
|
289
|
+
await tx.save(dao, { ...items[2], k1: 'k1_mod' });
|
|
290
|
+
await tx.deleteById(dao, items[1].id);
|
|
291
|
+
await tx.commit();
|
|
292
|
+
const rows = await dao.query().runQuery();
|
|
293
|
+
const expected = [items[0], { ...items[2], k1: 'k1_mod' }];
|
|
294
|
+
expectMatch(expected, rows, quirks);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
296
297
|
test('transaction rollback', async () => {
|
|
297
298
|
const expected = await prepare();
|
|
298
299
|
let err;
|
|
@@ -310,23 +311,25 @@ export async function runCommonDaoTest(db, quirks = {}) {
|
|
|
310
311
|
const rows = await dao.query().runQuery();
|
|
311
312
|
expectMatch(expected, rows, quirks);
|
|
312
313
|
});
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
314
|
+
if (support.createTransaction) {
|
|
315
|
+
test('createTransaction rollback', async () => {
|
|
316
|
+
const expected = await prepare();
|
|
317
|
+
let err;
|
|
318
|
+
try {
|
|
319
|
+
const tx = await dao.createTransaction();
|
|
320
|
+
await tx.deleteById(dao, items[2].id);
|
|
321
|
+
await tx.save(dao, { ...items[0], k1: 5 }); // it should fail here
|
|
322
|
+
await tx.commit();
|
|
323
|
+
}
|
|
324
|
+
catch (err_) {
|
|
325
|
+
err = err_;
|
|
326
|
+
}
|
|
327
|
+
expect(err).toBeDefined();
|
|
328
|
+
expect(err).toBeInstanceOf(Error);
|
|
329
|
+
const rows = await dao.query().runQuery();
|
|
330
|
+
expectMatch(expected, rows, quirks);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
330
333
|
if (support.queries) {
|
|
331
334
|
test('transaction cleanup', async () => {
|
|
332
335
|
await dao.query().deleteByQuery();
|
package/package.json
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import { Readable } from 'node:stream'
|
|
2
1
|
import { pMap } from '@naturalcycles/js-lib/promise/pMap.js'
|
|
3
2
|
import type { ObjectWithId } from '@naturalcycles/js-lib/types'
|
|
4
3
|
import { fs2 } from '@naturalcycles/nodejs-lib/fs2'
|
|
5
|
-
import {
|
|
6
|
-
_pipeline,
|
|
7
|
-
createReadStreamAsNDJSON,
|
|
8
|
-
createWriteStreamAsNDJSON,
|
|
9
|
-
} from '@naturalcycles/nodejs-lib/stream'
|
|
4
|
+
import { createReadStreamAsNDJSON, Pipeline } from '@naturalcycles/nodejs-lib/stream'
|
|
10
5
|
import type { DBSaveBatchOperation } from '../../db.model.js'
|
|
11
6
|
import type { FileDBPersistencePlugin } from './file.db.model.js'
|
|
12
7
|
|
|
@@ -63,6 +58,6 @@ export class LocalFilePersistencePlugin implements FileDBPersistencePlugin {
|
|
|
63
58
|
const ext = `ndjson${this.cfg.gzip ? '.gz' : ''}`
|
|
64
59
|
const filePath = `${this.cfg.storagePath}/${table}.${ext}`
|
|
65
60
|
|
|
66
|
-
await
|
|
61
|
+
await Pipeline.fromArray(rows).toNDJsonFile(filePath)
|
|
67
62
|
}
|
|
68
63
|
}
|
|
@@ -23,17 +23,16 @@ import {
|
|
|
23
23
|
import { _passthroughPredicate, _typeCast } from '@naturalcycles/js-lib/types'
|
|
24
24
|
import { stringId } from '@naturalcycles/nodejs-lib'
|
|
25
25
|
import {
|
|
26
|
+
Pipeline,
|
|
26
27
|
type ReadableTyped,
|
|
27
28
|
transformFlatten,
|
|
28
29
|
transformMapSync,
|
|
29
30
|
} from '@naturalcycles/nodejs-lib/stream'
|
|
30
31
|
import {
|
|
31
|
-
_pipeline,
|
|
32
32
|
transformChunk,
|
|
33
33
|
transformLogProgress,
|
|
34
34
|
transformMap,
|
|
35
35
|
transformNoOp,
|
|
36
|
-
writableVoid,
|
|
37
36
|
} from '@naturalcycles/nodejs-lib/stream'
|
|
38
37
|
import { DBLibError } from '../cnst.js'
|
|
39
38
|
import type {
|
|
@@ -253,9 +252,8 @@ export class CommonDao<
|
|
|
253
252
|
|
|
254
253
|
const isPartialQuery = !!q._selectedFieldNames
|
|
255
254
|
|
|
256
|
-
await
|
|
257
|
-
|
|
258
|
-
transformMap<DBM, BM>(
|
|
255
|
+
await Pipeline.from(this.cfg.db.streamQuery<DBM>(q, opt))
|
|
256
|
+
.map(
|
|
259
257
|
async dbm => {
|
|
260
258
|
if (isPartialQuery) return dbm as any
|
|
261
259
|
return await this.dbmToBM(dbm, opt)
|
|
@@ -263,18 +261,17 @@ export class CommonDao<
|
|
|
263
261
|
{
|
|
264
262
|
errorMode: opt.errorMode,
|
|
265
263
|
},
|
|
266
|
-
)
|
|
267
|
-
|
|
264
|
+
)
|
|
265
|
+
.map(mapper, {
|
|
268
266
|
...opt,
|
|
269
267
|
predicate: _passthroughPredicate, // to be able to logProgress
|
|
270
|
-
})
|
|
268
|
+
})
|
|
271
269
|
// LogProgress should be AFTER the mapper, to be able to report correct stats
|
|
272
|
-
|
|
270
|
+
.logProgress({
|
|
273
271
|
metric: q.table,
|
|
274
272
|
...opt,
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
])
|
|
273
|
+
})
|
|
274
|
+
.run()
|
|
278
275
|
}
|
|
279
276
|
|
|
280
277
|
async streamQueryAsDBMForEach(
|
|
@@ -289,9 +286,8 @@ export class CommonDao<
|
|
|
289
286
|
|
|
290
287
|
const isPartialQuery = !!q._selectedFieldNames
|
|
291
288
|
|
|
292
|
-
await
|
|
293
|
-
|
|
294
|
-
transformMapSync<any, DBM>(
|
|
289
|
+
await Pipeline.from(this.cfg.db.streamQuery<any>(q, opt))
|
|
290
|
+
.mapSync(
|
|
295
291
|
dbm => {
|
|
296
292
|
if (isPartialQuery) return dbm
|
|
297
293
|
return this.anyToDBM(dbm, opt)
|
|
@@ -299,18 +295,16 @@ export class CommonDao<
|
|
|
299
295
|
{
|
|
300
296
|
errorMode: opt.errorMode,
|
|
301
297
|
},
|
|
302
|
-
)
|
|
303
|
-
|
|
298
|
+
)
|
|
299
|
+
.map(mapper, {
|
|
304
300
|
...opt,
|
|
305
301
|
predicate: _passthroughPredicate, // to be able to logProgress
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
transformLogProgress({
|
|
302
|
+
})
|
|
303
|
+
.logProgress({
|
|
309
304
|
metric: q.table,
|
|
310
305
|
...opt,
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
])
|
|
306
|
+
})
|
|
307
|
+
.run()
|
|
314
308
|
}
|
|
315
309
|
|
|
316
310
|
/**
|
|
@@ -434,19 +428,17 @@ export class CommonDao<
|
|
|
434
428
|
q.table = opt.table || q.table
|
|
435
429
|
opt.errorMode ||= ErrorMode.SUPPRESS
|
|
436
430
|
|
|
437
|
-
await
|
|
438
|
-
|
|
439
|
-
transformMap<ID, void>(mapper, {
|
|
431
|
+
await Pipeline.from(this.cfg.db.streamQuery<DBM>(q.select(['id']), opt).map(r => r.id as ID))
|
|
432
|
+
.map(mapper, {
|
|
440
433
|
...opt,
|
|
441
434
|
predicate: _passthroughPredicate,
|
|
442
|
-
})
|
|
435
|
+
})
|
|
443
436
|
// LogProgress should be AFTER the mapper, to be able to report correct stats
|
|
444
|
-
|
|
437
|
+
.logProgress({
|
|
445
438
|
metric: q.table,
|
|
446
439
|
...opt,
|
|
447
|
-
})
|
|
448
|
-
|
|
449
|
-
])
|
|
440
|
+
})
|
|
441
|
+
.run()
|
|
450
442
|
}
|
|
451
443
|
|
|
452
444
|
/**
|
|
@@ -714,7 +706,7 @@ export class CommonDao<
|
|
|
714
706
|
* and then executing db.saveBatch(chunk) with the concurrency
|
|
715
707
|
* of opt.chunkConcurrency (which defaults to 32).
|
|
716
708
|
*/
|
|
717
|
-
|
|
709
|
+
streamSaveTransforms(opt: CommonDaoStreamSaveOptions<DBM> = {}): Transform[] {
|
|
718
710
|
this.requireWriteAccess()
|
|
719
711
|
|
|
720
712
|
const table = opt.table || this.cfg.table
|
|
@@ -742,7 +734,7 @@ export class CommonDao<
|
|
|
742
734
|
errorMode,
|
|
743
735
|
},
|
|
744
736
|
),
|
|
745
|
-
transformChunk<DBM>(
|
|
737
|
+
transformChunk<DBM>(chunkSize),
|
|
746
738
|
transformMap<DBM[], DBM[]>(
|
|
747
739
|
async batch => {
|
|
748
740
|
await this.cfg.db.saveBatch(table, batch, {
|
|
@@ -761,9 +753,6 @@ export class CommonDao<
|
|
|
761
753
|
metric: 'saved',
|
|
762
754
|
...opt,
|
|
763
755
|
}),
|
|
764
|
-
// just to satisfy and simplify typings
|
|
765
|
-
// It's easier to return Transform[], rather than (Transform | Writable)[]
|
|
766
|
-
writableVoid() as Transform,
|
|
767
756
|
]
|
|
768
757
|
}
|
|
769
758
|
|
|
@@ -802,10 +791,9 @@ export class CommonDao<
|
|
|
802
791
|
if (opt.chunkSize) {
|
|
803
792
|
const { chunkSize, chunkConcurrency = 8 } = opt
|
|
804
793
|
|
|
805
|
-
await
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
transformMap<string[], void>(
|
|
794
|
+
await Pipeline.from(this.cfg.db.streamQuery<DBM>(q.select(['id']), opt).map(r => r.id))
|
|
795
|
+
.chunk(chunkSize)
|
|
796
|
+
.map(
|
|
809
797
|
async ids => {
|
|
810
798
|
await this.cfg.db.deleteByIds(q.table, ids, opt)
|
|
811
799
|
deleted += ids.length
|
|
@@ -815,16 +803,15 @@ export class CommonDao<
|
|
|
815
803
|
concurrency: chunkConcurrency,
|
|
816
804
|
errorMode: opt.errorMode || ErrorMode.THROW_IMMEDIATELY,
|
|
817
805
|
},
|
|
818
|
-
)
|
|
806
|
+
)
|
|
819
807
|
// LogProgress should be AFTER the mapper, to be able to report correct stats
|
|
820
|
-
|
|
808
|
+
.logProgress({
|
|
821
809
|
metric: q.table,
|
|
822
810
|
logEvery: 2, // 500 * 2 === 1000
|
|
823
811
|
chunkSize,
|
|
824
812
|
...opt,
|
|
825
|
-
})
|
|
826
|
-
|
|
827
|
-
])
|
|
813
|
+
})
|
|
814
|
+
.run()
|
|
828
815
|
} else {
|
|
829
816
|
deleted = await this.cfg.db.deleteByQuery(q, opt)
|
|
830
817
|
}
|
|
@@ -273,6 +273,7 @@ export interface CommonDBSupport {
|
|
|
273
273
|
bufferValues?: boolean
|
|
274
274
|
nullValues?: boolean
|
|
275
275
|
transactions?: boolean
|
|
276
|
+
createTransaction?: boolean
|
|
276
277
|
timeMachine?: boolean
|
|
277
278
|
multiTableOperations?: boolean
|
|
278
279
|
}
|
|
@@ -294,6 +295,7 @@ export const commonDBFullSupport: Required<CommonDBSupport> = {
|
|
|
294
295
|
bufferValues: true,
|
|
295
296
|
nullValues: true,
|
|
296
297
|
transactions: true,
|
|
298
|
+
createTransaction: true,
|
|
297
299
|
timeMachine: true,
|
|
298
300
|
multiTableOperations: true,
|
|
299
301
|
}
|
|
@@ -6,18 +6,11 @@ import { _passthroughMapper } from '@naturalcycles/js-lib/types'
|
|
|
6
6
|
import { boldWhite, dimWhite, grey, yellow } from '@naturalcycles/nodejs-lib/colors'
|
|
7
7
|
import { fs2 } from '@naturalcycles/nodejs-lib/fs2'
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
transformFlatten,
|
|
9
|
+
Pipeline,
|
|
11
10
|
type TransformLogProgressOptions,
|
|
12
11
|
type TransformMapOptions,
|
|
13
12
|
} from '@naturalcycles/nodejs-lib/stream'
|
|
14
|
-
import {
|
|
15
|
-
_pipeline,
|
|
16
|
-
NDJsonStats,
|
|
17
|
-
transformLogProgress,
|
|
18
|
-
transformMap,
|
|
19
|
-
transformTap,
|
|
20
|
-
} from '@naturalcycles/nodejs-lib/stream'
|
|
13
|
+
import { NDJsonStats } from '@naturalcycles/nodejs-lib/stream'
|
|
21
14
|
import type { CommonDB } from '../commondb/common.db.js'
|
|
22
15
|
import { DBQuery } from '../query/dbQuery.js'
|
|
23
16
|
|
|
@@ -212,24 +205,20 @@ export async function dbPipelineBackup(opt: DBPipelineBackupOptions): Promise<ND
|
|
|
212
205
|
console.log(`>> ${grey(schemaFilePath)} saved (generated from DB)`)
|
|
213
206
|
}
|
|
214
207
|
|
|
215
|
-
await
|
|
216
|
-
|
|
217
|
-
transformLogProgress({
|
|
208
|
+
await Pipeline.from(db.streamQuery(q))
|
|
209
|
+
.logProgress({
|
|
218
210
|
...opt,
|
|
219
211
|
logEvery: logEveryPerTable[table] ?? opt.logEvery ?? 1000,
|
|
220
212
|
metric: table,
|
|
221
|
-
})
|
|
222
|
-
|
|
213
|
+
})
|
|
214
|
+
.map(mapperPerTable[table] || _passthroughMapper, {
|
|
223
215
|
errorMode,
|
|
224
216
|
...transformMapOptions,
|
|
225
217
|
metric: table,
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}),
|
|
231
|
-
...createWriteStreamAsNDJSON(filePath),
|
|
232
|
-
])
|
|
218
|
+
})
|
|
219
|
+
.flattenIfNeeded()
|
|
220
|
+
.tap(() => rows++)
|
|
221
|
+
.toNDJsonFile(filePath)
|
|
233
222
|
|
|
234
223
|
const { size: sizeBytes } = await fs2.statAsync(filePath)
|
|
235
224
|
|
|
@@ -5,19 +5,11 @@ import type { AsyncMapper, BaseDBEntity, UnixTimestamp } from '@naturalcycles/js
|
|
|
5
5
|
import { _passthroughMapper } from '@naturalcycles/js-lib/types'
|
|
6
6
|
import { boldWhite, dimWhite, grey, yellow } from '@naturalcycles/nodejs-lib/colors'
|
|
7
7
|
import {
|
|
8
|
-
|
|
8
|
+
Pipeline,
|
|
9
9
|
type TransformLogProgressOptions,
|
|
10
10
|
type TransformMapOptions,
|
|
11
11
|
} from '@naturalcycles/nodejs-lib/stream'
|
|
12
|
-
import {
|
|
13
|
-
_pipeline,
|
|
14
|
-
NDJsonStats,
|
|
15
|
-
transformChunk,
|
|
16
|
-
transformLogProgress,
|
|
17
|
-
transformMap,
|
|
18
|
-
transformTap,
|
|
19
|
-
writableForEach,
|
|
20
|
-
} from '@naturalcycles/nodejs-lib/stream'
|
|
12
|
+
import { NDJsonStats } from '@naturalcycles/nodejs-lib/stream'
|
|
21
13
|
import type { CommonDB } from '../commondb/common.db.js'
|
|
22
14
|
import type { CommonDBSaveOptions } from '../db.model.js'
|
|
23
15
|
import { DBQuery } from '../query/dbQuery.js'
|
|
@@ -136,25 +128,23 @@ export async function dbPipelineCopy(opt: DBPipelineCopyOptions): Promise<NDJson
|
|
|
136
128
|
const started = Date.now()
|
|
137
129
|
let rows = 0
|
|
138
130
|
|
|
139
|
-
await
|
|
140
|
-
|
|
141
|
-
transformLogProgress({
|
|
131
|
+
await Pipeline.from(stream)
|
|
132
|
+
.logProgress({
|
|
142
133
|
logEvery: 1000,
|
|
143
134
|
...opt,
|
|
144
135
|
metric: table,
|
|
145
|
-
})
|
|
146
|
-
|
|
136
|
+
})
|
|
137
|
+
.map(mapper, {
|
|
147
138
|
errorMode,
|
|
148
139
|
...transformMapOptions,
|
|
149
140
|
metric: table,
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
141
|
+
})
|
|
142
|
+
.flattenIfNeeded()
|
|
143
|
+
.tap(() => rows++)
|
|
144
|
+
.chunk(chunkSize)
|
|
145
|
+
.forEach(async dbms => {
|
|
155
146
|
await dbOutput.saveBatch(table, dbms, saveOptions)
|
|
156
|
-
})
|
|
157
|
-
])
|
|
147
|
+
})
|
|
158
148
|
|
|
159
149
|
const stats = NDJsonStats.create({
|
|
160
150
|
tookMillis: Date.now() - started,
|
|
@@ -9,21 +9,11 @@ import { _passthroughMapper } from '@naturalcycles/js-lib/types'
|
|
|
9
9
|
import { boldWhite, dimWhite, grey, yellow } from '@naturalcycles/nodejs-lib/colors'
|
|
10
10
|
import { fs2 } from '@naturalcycles/nodejs-lib/fs2'
|
|
11
11
|
import {
|
|
12
|
-
|
|
13
|
-
transformFlatten,
|
|
12
|
+
Pipeline,
|
|
14
13
|
type TransformLogProgressOptions,
|
|
15
14
|
type TransformMapOptions,
|
|
16
15
|
} from '@naturalcycles/nodejs-lib/stream'
|
|
17
|
-
import {
|
|
18
|
-
_pipeline,
|
|
19
|
-
NDJsonStats,
|
|
20
|
-
transformChunk,
|
|
21
|
-
transformFilterSync,
|
|
22
|
-
transformLogProgress,
|
|
23
|
-
transformMap,
|
|
24
|
-
transformTap,
|
|
25
|
-
writableForEach,
|
|
26
|
-
} from '@naturalcycles/nodejs-lib/stream'
|
|
16
|
+
import { NDJsonStats } from '@naturalcycles/nodejs-lib/stream'
|
|
27
17
|
import type { CommonDB } from '../commondb/common.db.js'
|
|
28
18
|
import type { CommonDBSaveOptions } from '../db.model.js'
|
|
29
19
|
|
|
@@ -195,28 +185,25 @@ export async function dbPipelineRestore(opt: DBPipelineRestoreOptions): Promise<
|
|
|
195
185
|
|
|
196
186
|
console.log(`<< ${grey(filePath)} ${dimWhite(_hb(sizeBytes))} started...`)
|
|
197
187
|
|
|
198
|
-
await
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
188
|
+
await Pipeline.fromNDJsonFile<BaseDBEntity>(filePath)
|
|
189
|
+
.limitSource(limit)
|
|
190
|
+
.tap(() => rows++)
|
|
191
|
+
.logProgress({
|
|
202
192
|
logEvery: 1000,
|
|
203
193
|
...opt,
|
|
204
194
|
metric: table,
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
: []),
|
|
209
|
-
transformMap(mapperPerTable[table] || _passthroughMapper, {
|
|
195
|
+
})
|
|
196
|
+
.filterSync(r => !sinceUpdated || r.updated >= sinceUpdated)
|
|
197
|
+
.map(mapperPerTable[table] || _passthroughMapper, {
|
|
210
198
|
errorMode,
|
|
211
199
|
...transformMapOptions,
|
|
212
200
|
metric: table,
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
201
|
+
})
|
|
202
|
+
.flattenIfNeeded()
|
|
203
|
+
.chunk(chunkSize)
|
|
204
|
+
.forEach(async dbms => {
|
|
217
205
|
await db.saveBatch(table, dbms, saveOptions)
|
|
218
|
-
})
|
|
219
|
-
])
|
|
206
|
+
})
|
|
220
207
|
|
|
221
208
|
const stats = NDJsonStats.create({
|
|
222
209
|
tookMillis: Date.now() - started,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { Readable } from 'node:stream'
|
|
2
1
|
import { _sortBy } from '@naturalcycles/js-lib/array/array.util.js'
|
|
3
2
|
import { localTime } from '@naturalcycles/js-lib/datetime/localTime.js'
|
|
4
3
|
import { _deepCopy, _filterObject, _omit, _pick } from '@naturalcycles/js-lib/object'
|
|
5
4
|
import { getJoiValidationFunction } from '@naturalcycles/nodejs-lib/joi'
|
|
6
|
-
import {
|
|
5
|
+
import { Pipeline } from '@naturalcycles/nodejs-lib/stream'
|
|
7
6
|
import { CommonDao } from '../commondao/common.dao.js'
|
|
8
7
|
import type { CommonDB } from '../commondb/common.db.js'
|
|
9
8
|
import { DBQuery } from '../query/dbQuery.js'
|
|
@@ -284,7 +283,7 @@ export async function runCommonDaoTest(
|
|
|
284
283
|
const items2 = createTestItemsBM(2).map(i => ({ ...i, id: i.id + '_str' }))
|
|
285
284
|
const ids = items2.map(i => i.id)
|
|
286
285
|
|
|
287
|
-
await
|
|
286
|
+
await Pipeline.fromArray(items2).transformMany(dao.streamSaveTransforms()).run()
|
|
288
287
|
|
|
289
288
|
const items2Loaded = await dao.getByIds(ids)
|
|
290
289
|
expectMatch(items2, items2Loaded, quirks)
|
|
@@ -358,42 +357,44 @@ export async function runCommonDaoTest(
|
|
|
358
357
|
expectMatch(expected, rows, quirks)
|
|
359
358
|
})
|
|
360
359
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
360
|
+
if (support.createTransaction) {
|
|
361
|
+
test('createTransaction happy path', async () => {
|
|
362
|
+
// cleanup
|
|
363
|
+
await dao.query().deleteByQuery()
|
|
364
364
|
|
|
365
|
-
|
|
366
|
-
|
|
365
|
+
// Test that id, created, updated are created
|
|
366
|
+
const now = localTime.nowUnix()
|
|
367
367
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
368
|
+
const row = _omit(item1, ['id', 'created', 'updated'])
|
|
369
|
+
let tx = await dao.createTransaction()
|
|
370
|
+
await tx.save(dao, row)
|
|
371
|
+
await tx.commit()
|
|
372
372
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
373
|
+
const loaded = await dao.query().runQuery()
|
|
374
|
+
expect(loaded.length).toBe(1)
|
|
375
|
+
expect(loaded[0]!.id).toBeDefined()
|
|
376
|
+
expect(loaded[0]!.created).toBeGreaterThanOrEqual(now)
|
|
377
|
+
expect(loaded[0]!.updated).toBe(loaded[0]!.created)
|
|
378
378
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
379
|
+
tx = await dao.createTransaction()
|
|
380
|
+
await tx.deleteById(dao, loaded[0]!.id)
|
|
381
|
+
await tx.commit()
|
|
382
382
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
383
|
+
// saveBatch [item1, 2, 3]
|
|
384
|
+
// save item3 with k1: k1_mod
|
|
385
|
+
// delete item2
|
|
386
|
+
// remaining: item1, item3_with_k1_mod
|
|
387
|
+
tx = await dao.createTransaction()
|
|
388
|
+
await tx.saveBatch(dao, items)
|
|
389
|
+
await tx.save(dao, { ...items[2]!, k1: 'k1_mod' })
|
|
390
|
+
await tx.deleteById(dao, items[1]!.id)
|
|
391
|
+
await tx.commit()
|
|
392
392
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
393
|
+
const rows = await dao.query().runQuery()
|
|
394
|
+
const expected = [items[0], { ...items[2]!, k1: 'k1_mod' }]
|
|
395
|
+
expectMatch(expected, rows, quirks)
|
|
396
|
+
})
|
|
397
|
+
}
|
|
397
398
|
|
|
398
399
|
test('transaction rollback', async () => {
|
|
399
400
|
const expected = await prepare()
|
|
@@ -416,25 +417,27 @@ export async function runCommonDaoTest(
|
|
|
416
417
|
expectMatch(expected, rows, quirks)
|
|
417
418
|
})
|
|
418
419
|
|
|
419
|
-
|
|
420
|
-
|
|
420
|
+
if (support.createTransaction) {
|
|
421
|
+
test('createTransaction rollback', async () => {
|
|
422
|
+
const expected = await prepare()
|
|
421
423
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
424
|
+
let err: any
|
|
425
|
+
try {
|
|
426
|
+
const tx = await dao.createTransaction()
|
|
427
|
+
await tx.deleteById(dao, items[2]!.id)
|
|
428
|
+
await tx.save(dao, { ...items[0]!, k1: 5 as any }) // it should fail here
|
|
429
|
+
await tx.commit()
|
|
430
|
+
} catch (err_) {
|
|
431
|
+
err = err_
|
|
432
|
+
}
|
|
431
433
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
+
expect(err).toBeDefined()
|
|
435
|
+
expect(err).toBeInstanceOf(Error)
|
|
434
436
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
437
|
+
const rows = await dao.query().runQuery()
|
|
438
|
+
expectMatch(expected, rows, quirks)
|
|
439
|
+
})
|
|
440
|
+
}
|
|
438
441
|
|
|
439
442
|
if (support.queries) {
|
|
440
443
|
test('transaction cleanup', async () => {
|