@naturalcycles/db-lib 8.60.0 → 9.0.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 -4
- package/dist/adapter/cachedb/cache.db.js +5 -4
- package/dist/adapter/cachedb/cache.db.model.d.ts +2 -2
- package/dist/adapter/file/file.db.d.ts +15 -7
- package/dist/adapter/file/file.db.js +93 -57
- package/dist/adapter/file/localFile.persistence.plugin.js +3 -3
- package/dist/adapter/inmemory/inMemory.db.d.ts +30 -4
- package/dist/adapter/inmemory/inMemory.db.js +89 -33
- package/dist/base.common.db.d.ts +7 -10
- package/dist/base.common.db.js +11 -7
- package/dist/common.db.d.ts +56 -4
- package/dist/common.db.js +23 -0
- package/dist/commondao/common.dao.d.ts +17 -9
- package/dist/commondao/common.dao.js +82 -69
- package/dist/commondao/common.dao.model.d.ts +0 -10
- package/dist/db.model.d.ts +12 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/pipeline/dbPipelineBackup.js +4 -4
- package/dist/pipeline/dbPipelineRestore.js +2 -2
- package/dist/testing/daoTest.d.ts +2 -2
- package/dist/testing/daoTest.js +29 -39
- package/dist/testing/dbTest.d.ts +1 -39
- package/dist/testing/dbTest.js +40 -49
- package/dist/testing/index.d.ts +2 -2
- package/dist/timeseries/commonTimeSeriesDao.js +5 -6
- package/dist/transaction/dbTransaction.util.d.ts +17 -4
- package/dist/transaction/dbTransaction.util.js +46 -22
- package/dist/validation/index.js +2 -2
- package/package.json +1 -1
- package/src/adapter/cachedb/cache.db.model.ts +7 -2
- package/src/adapter/cachedb/cache.db.ts +7 -8
- package/src/adapter/file/file.db.ts +121 -69
- package/src/adapter/file/localFile.persistence.plugin.ts +4 -5
- package/src/adapter/inmemory/inMemory.db.ts +106 -33
- package/src/base.common.db.ts +20 -11
- package/src/common.db.ts +80 -3
- package/src/commondao/common.dao.model.ts +0 -11
- package/src/commondao/common.dao.ts +103 -89
- package/src/db.model.ts +15 -2
- package/src/index.ts +0 -1
- package/src/pipeline/dbPipelineBackup.ts +5 -8
- package/src/pipeline/dbPipelineRestore.ts +3 -4
- package/src/testing/daoTest.ts +32 -52
- package/src/testing/dbTest.ts +42 -119
- package/src/testing/index.ts +2 -12
- package/src/timeseries/commonTimeSeriesDao.ts +5 -6
- package/src/transaction/dbTransaction.util.ts +61 -22
- package/src/validation/index.ts +2 -2
- package/dist/transaction/dbTransaction.d.ts +0 -27
- package/dist/transaction/dbTransaction.js +0 -64
- package/src/transaction/dbTransaction.ts +0 -67
|
@@ -18,7 +18,15 @@ import {
|
|
|
18
18
|
Saved,
|
|
19
19
|
} from '@naturalcycles/js-lib'
|
|
20
20
|
import { readableCreate, ReadableTyped, dimGrey } from '@naturalcycles/nodejs-lib'
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
BaseCommonDB,
|
|
23
|
+
commonDBFullSupport,
|
|
24
|
+
CommonDBSupport,
|
|
25
|
+
DBOperation,
|
|
26
|
+
DBSaveBatchOperation,
|
|
27
|
+
DBTransaction,
|
|
28
|
+
queryInMemory,
|
|
29
|
+
} from '../..'
|
|
22
30
|
import { CommonDB } from '../../common.db'
|
|
23
31
|
import {
|
|
24
32
|
CommonDBOptions,
|
|
@@ -27,7 +35,6 @@ import {
|
|
|
27
35
|
RunQueryResult,
|
|
28
36
|
} from '../../db.model'
|
|
29
37
|
import { DBQuery } from '../../query/dbQuery'
|
|
30
|
-
import { DBTransaction } from '../../transaction/dbTransaction'
|
|
31
38
|
import { FileDBCfg } from './file.db.model'
|
|
32
39
|
|
|
33
40
|
/**
|
|
@@ -41,6 +48,16 @@ import { FileDBCfg } from './file.db.model'
|
|
|
41
48
|
* Each save operation saves *whole* file to the persistence layer.
|
|
42
49
|
*/
|
|
43
50
|
export class FileDB extends BaseCommonDB implements CommonDB {
|
|
51
|
+
override support: CommonDBSupport = {
|
|
52
|
+
...commonDBFullSupport,
|
|
53
|
+
bufferValues: false, // todo: implement
|
|
54
|
+
insertSaveMethod: false,
|
|
55
|
+
updateSaveMethod: false,
|
|
56
|
+
updateByQuery: false,
|
|
57
|
+
createTable: false,
|
|
58
|
+
transactions: false,
|
|
59
|
+
}
|
|
60
|
+
|
|
44
61
|
constructor(cfg: FileDBCfg) {
|
|
45
62
|
super()
|
|
46
63
|
this.cfg = {
|
|
@@ -101,72 +118,6 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
101
118
|
}
|
|
102
119
|
}
|
|
103
120
|
|
|
104
|
-
/**
|
|
105
|
-
* Implementation is optimized for loading/saving _whole files_.
|
|
106
|
-
*/
|
|
107
|
-
override async commitTransaction(tx: DBTransaction, _opt?: CommonDBOptions): Promise<void> {
|
|
108
|
-
// data[table][id] => row
|
|
109
|
-
const data: StringMap<StringMap<ObjectWithId>> = {}
|
|
110
|
-
|
|
111
|
-
// 1. Load all tables data (concurrently)
|
|
112
|
-
const tables = _uniq(tx.ops.map(o => o.table))
|
|
113
|
-
|
|
114
|
-
await pMap(
|
|
115
|
-
tables,
|
|
116
|
-
async table => {
|
|
117
|
-
const rows = await this.loadFile(table)
|
|
118
|
-
data[table] = _by(rows, r => r.id)
|
|
119
|
-
},
|
|
120
|
-
{ concurrency: 16 },
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
const backup = _deepCopy(data)
|
|
124
|
-
|
|
125
|
-
// 2. Apply ops one by one (in order)
|
|
126
|
-
tx.ops.forEach(op => {
|
|
127
|
-
if (op.type === 'deleteByIds') {
|
|
128
|
-
op.ids.forEach(id => delete data[op.table]![id])
|
|
129
|
-
} else if (op.type === 'saveBatch') {
|
|
130
|
-
op.rows.forEach(r => {
|
|
131
|
-
if (!r.id) {
|
|
132
|
-
throw new Error('FileDB: row has an empty id')
|
|
133
|
-
}
|
|
134
|
-
data[op.table]![r.id] = r
|
|
135
|
-
})
|
|
136
|
-
} else {
|
|
137
|
-
throw new Error(`DBOperation not supported: ${(op as any).type}`)
|
|
138
|
-
}
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
// 3. Sort, turn it into ops
|
|
142
|
-
// Not filtering empty arrays, cause it's already filtered in this.saveFiles()
|
|
143
|
-
const ops: DBSaveBatchOperation[] = _stringMapEntries(data).map(([table, map]) => {
|
|
144
|
-
return {
|
|
145
|
-
type: 'saveBatch',
|
|
146
|
-
table,
|
|
147
|
-
rows: this.sortRows(_stringMapValues(map)),
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
// 4. Save all files
|
|
152
|
-
try {
|
|
153
|
-
await this.saveFiles(ops)
|
|
154
|
-
} catch (err) {
|
|
155
|
-
const ops: DBSaveBatchOperation[] = _stringMapEntries(backup).map(([table, map]) => {
|
|
156
|
-
return {
|
|
157
|
-
type: 'saveBatch',
|
|
158
|
-
table,
|
|
159
|
-
rows: this.sortRows(_stringMapValues(map)),
|
|
160
|
-
}
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
// Rollback, ignore rollback error (if any)
|
|
164
|
-
await this.saveFiles(ops).catch(_ => {})
|
|
165
|
-
|
|
166
|
-
throw err
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
121
|
override async runQuery<ROW extends ObjectWithId>(
|
|
171
122
|
q: DBQuery<ROW>,
|
|
172
123
|
_opt?: CommonDBOptions,
|
|
@@ -216,6 +167,27 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
216
167
|
return deleted
|
|
217
168
|
}
|
|
218
169
|
|
|
170
|
+
override async deleteByIds(
|
|
171
|
+
table: string,
|
|
172
|
+
ids: string[],
|
|
173
|
+
_opt?: CommonDBOptions,
|
|
174
|
+
): Promise<number> {
|
|
175
|
+
const byId = _by(await this.loadFile(table), r => r.id)
|
|
176
|
+
|
|
177
|
+
let deleted = 0
|
|
178
|
+
ids.forEach(id => {
|
|
179
|
+
if (!byId[id]) return
|
|
180
|
+
delete byId[id]
|
|
181
|
+
deleted++
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
if (deleted > 0) {
|
|
185
|
+
await this.saveFile(table, _stringMapValues(byId))
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return deleted
|
|
189
|
+
}
|
|
190
|
+
|
|
219
191
|
override async getTableSchema<ROW extends ObjectWithId>(
|
|
220
192
|
table: string,
|
|
221
193
|
): Promise<JsonSchemaRootObject<ROW>> {
|
|
@@ -256,7 +228,11 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
256
228
|
this.logFinished(started, op)
|
|
257
229
|
}
|
|
258
230
|
|
|
259
|
-
|
|
231
|
+
override async createTransaction(): Promise<FileDBTransaction> {
|
|
232
|
+
return new FileDBTransaction(this)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
sortRows<ROW extends ObjectWithId>(rows: ROW[]): ROW[] {
|
|
260
236
|
rows = rows.map(r => _filterUndefinedValues(r))
|
|
261
237
|
|
|
262
238
|
if (this.cfg.sortOnSave) {
|
|
@@ -283,3 +259,79 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
283
259
|
this.cfg.logger?.log(`<< ${op} ${dimGrey(`in ${_since(started)}`)}`)
|
|
284
260
|
}
|
|
285
261
|
}
|
|
262
|
+
|
|
263
|
+
export class FileDBTransaction implements DBTransaction {
|
|
264
|
+
constructor(private db: FileDB) {}
|
|
265
|
+
|
|
266
|
+
ops: DBOperation[] = []
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Implementation is optimized for loading/saving _whole files_.
|
|
270
|
+
*/
|
|
271
|
+
async commit(): Promise<void> {
|
|
272
|
+
// data[table][id] => row
|
|
273
|
+
const data: StringMap<StringMap<ObjectWithId>> = {}
|
|
274
|
+
|
|
275
|
+
// 1. Load all tables data (concurrently)
|
|
276
|
+
const tables = _uniq(this.ops.map(o => o.table))
|
|
277
|
+
|
|
278
|
+
await pMap(
|
|
279
|
+
tables,
|
|
280
|
+
async table => {
|
|
281
|
+
const rows = await this.db.loadFile(table)
|
|
282
|
+
data[table] = _by(rows, r => r.id)
|
|
283
|
+
},
|
|
284
|
+
{ concurrency: 16 },
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
const backup = _deepCopy(data)
|
|
288
|
+
|
|
289
|
+
// 2. Apply ops one by one (in order)
|
|
290
|
+
this.ops.forEach(op => {
|
|
291
|
+
if (op.type === 'deleteByIds') {
|
|
292
|
+
op.ids.forEach(id => delete data[op.table]![id])
|
|
293
|
+
} else if (op.type === 'saveBatch') {
|
|
294
|
+
op.rows.forEach(r => {
|
|
295
|
+
if (!r.id) {
|
|
296
|
+
throw new Error('FileDB: row has an empty id')
|
|
297
|
+
}
|
|
298
|
+
data[op.table]![r.id] = r
|
|
299
|
+
})
|
|
300
|
+
} else {
|
|
301
|
+
throw new Error(`DBOperation not supported: ${(op as any).type}`)
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
// 3. Sort, turn it into ops
|
|
306
|
+
// Not filtering empty arrays, cause it's already filtered in this.saveFiles()
|
|
307
|
+
const ops: DBSaveBatchOperation[] = _stringMapEntries(data).map(([table, map]) => {
|
|
308
|
+
return {
|
|
309
|
+
type: 'saveBatch',
|
|
310
|
+
table,
|
|
311
|
+
rows: this.db.sortRows(_stringMapValues(map)),
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
// 4. Save all files
|
|
316
|
+
try {
|
|
317
|
+
await this.db.saveFiles(ops)
|
|
318
|
+
} catch (err) {
|
|
319
|
+
const ops: DBSaveBatchOperation[] = _stringMapEntries(backup).map(([table, map]) => {
|
|
320
|
+
return {
|
|
321
|
+
type: 'saveBatch',
|
|
322
|
+
table,
|
|
323
|
+
rows: this.db.sortRows(_stringMapValues(map)),
|
|
324
|
+
}
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
// Rollback, ignore rollback error (if any)
|
|
328
|
+
await this.db.saveFiles(ops).catch(_ => {})
|
|
329
|
+
|
|
330
|
+
throw err
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async rollback(): Promise<void> {
|
|
335
|
+
this.ops = []
|
|
336
|
+
}
|
|
337
|
+
}
|
|
@@ -9,8 +9,7 @@ import {
|
|
|
9
9
|
transformToNDJson,
|
|
10
10
|
writablePushToArray,
|
|
11
11
|
_pipeline,
|
|
12
|
-
|
|
13
|
-
_pathExists,
|
|
12
|
+
fs2,
|
|
14
13
|
} from '@naturalcycles/nodejs-lib'
|
|
15
14
|
import { DBSaveBatchOperation } from '../../db.model'
|
|
16
15
|
import { FileDBPersistencePlugin } from './file.db.model'
|
|
@@ -50,11 +49,11 @@ export class LocalFilePersistencePlugin implements FileDBPersistencePlugin {
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
async loadFile<ROW extends ObjectWithId>(table: string): Promise<ROW[]> {
|
|
53
|
-
await
|
|
52
|
+
await fs2.ensureDirAsync(this.cfg.storagePath)
|
|
54
53
|
const ext = `ndjson${this.cfg.gzip ? '.gz' : ''}`
|
|
55
54
|
const filePath = `${this.cfg.storagePath}/${table}.${ext}`
|
|
56
55
|
|
|
57
|
-
if (!(await
|
|
56
|
+
if (!(await fs2.pathExistsAsync(filePath))) return []
|
|
58
57
|
|
|
59
58
|
const transformUnzip = this.cfg.gzip ? [createUnzip()] : []
|
|
60
59
|
|
|
@@ -76,7 +75,7 @@ export class LocalFilePersistencePlugin implements FileDBPersistencePlugin {
|
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
async saveFile<ROW extends ObjectWithId>(table: string, rows: ROW[]): Promise<void> {
|
|
79
|
-
await
|
|
78
|
+
await fs2.ensureDirAsync(this.cfg.storagePath)
|
|
80
79
|
const ext = `ndjson${this.cfg.gzip ? '.gz' : ''}`
|
|
81
80
|
const filePath = `${this.cfg.storagePath}/${table}.${ext}`
|
|
82
81
|
const transformZip = this.cfg.gzip ? [createGzip()] : []
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
CommonLogger,
|
|
17
17
|
_deepCopy,
|
|
18
18
|
_assert,
|
|
19
|
+
_omit,
|
|
19
20
|
} from '@naturalcycles/js-lib'
|
|
20
21
|
import {
|
|
21
22
|
bufferReviver,
|
|
@@ -25,16 +26,24 @@ import {
|
|
|
25
26
|
transformToNDJson,
|
|
26
27
|
writablePushToArray,
|
|
27
28
|
_pipeline,
|
|
28
|
-
_emptyDir,
|
|
29
|
-
_ensureDir,
|
|
30
29
|
dimGrey,
|
|
31
30
|
yellow,
|
|
31
|
+
fs2,
|
|
32
32
|
} from '@naturalcycles/nodejs-lib'
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
CommonDB,
|
|
35
|
+
commonDBFullSupport,
|
|
36
|
+
CommonDBType,
|
|
37
|
+
DBIncrement,
|
|
38
|
+
DBOperation,
|
|
39
|
+
DBPatch,
|
|
40
|
+
queryInMemory,
|
|
41
|
+
} from '../..'
|
|
34
42
|
import {
|
|
35
43
|
CommonDBCreateOptions,
|
|
36
44
|
CommonDBOptions,
|
|
37
45
|
CommonDBSaveOptions,
|
|
46
|
+
DBTransaction,
|
|
38
47
|
RunQueryResult,
|
|
39
48
|
} from '../../db.model'
|
|
40
49
|
import { DBQuery } from '../../query/dbQuery'
|
|
@@ -77,6 +86,12 @@ export interface InMemoryDBCfg {
|
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
export class InMemoryDB implements CommonDB {
|
|
89
|
+
dbType = CommonDBType.document
|
|
90
|
+
|
|
91
|
+
support = {
|
|
92
|
+
...commonDBFullSupport,
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
constructor(cfg?: Partial<InMemoryDBCfg>) {
|
|
81
96
|
this.cfg = {
|
|
82
97
|
// defaults
|
|
@@ -162,6 +177,17 @@ export class InMemoryDB implements CommonDB {
|
|
|
162
177
|
rows: ROW[],
|
|
163
178
|
opt: CommonDBSaveOptions<ROW> = {},
|
|
164
179
|
): Promise<void> {
|
|
180
|
+
const { tx } = opt
|
|
181
|
+
if (tx) {
|
|
182
|
+
;(tx as InMemoryDBTransaction).ops.push({
|
|
183
|
+
type: 'saveBatch',
|
|
184
|
+
table: _table,
|
|
185
|
+
rows,
|
|
186
|
+
opt: _omit(opt, ['tx']),
|
|
187
|
+
})
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
165
191
|
const table = this.cfg.tablesPrefix + _table
|
|
166
192
|
this.data[table] ||= {}
|
|
167
193
|
|
|
@@ -190,16 +216,48 @@ export class InMemoryDB implements CommonDB {
|
|
|
190
216
|
|
|
191
217
|
async deleteByQuery<ROW extends ObjectWithId>(
|
|
192
218
|
q: DBQuery<ROW>,
|
|
193
|
-
|
|
219
|
+
opt: CommonDBOptions = {},
|
|
194
220
|
): Promise<number> {
|
|
195
221
|
const table = this.cfg.tablesPrefix + q.table
|
|
196
|
-
this.data[table]
|
|
222
|
+
if (!this.data[table]) return 0
|
|
223
|
+
const ids = queryInMemory(q, Object.values(this.data[table]!) as ROW[]).map(r => r.id)
|
|
224
|
+
|
|
225
|
+
const { tx } = opt
|
|
226
|
+
if (tx) {
|
|
227
|
+
;(tx as InMemoryDBTransaction).ops.push({
|
|
228
|
+
type: 'deleteByIds',
|
|
229
|
+
table: q.table,
|
|
230
|
+
ids,
|
|
231
|
+
opt: _omit(opt, ['tx']),
|
|
232
|
+
})
|
|
233
|
+
return ids.length
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return await this.deleteByIds(q.table, ids)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async deleteByIds(_table: string, ids: string[], opt: CommonDBOptions = {}): Promise<number> {
|
|
240
|
+
const table = this.cfg.tablesPrefix + _table
|
|
241
|
+
if (!this.data[table]) return 0
|
|
242
|
+
|
|
243
|
+
const { tx } = opt
|
|
244
|
+
if (tx) {
|
|
245
|
+
;(tx as InMemoryDBTransaction).ops.push({
|
|
246
|
+
type: 'deleteByIds',
|
|
247
|
+
table: _table,
|
|
248
|
+
ids,
|
|
249
|
+
opt: _omit(opt, ['tx']),
|
|
250
|
+
})
|
|
251
|
+
return ids.length
|
|
252
|
+
}
|
|
253
|
+
|
|
197
254
|
let count = 0
|
|
198
|
-
|
|
199
|
-
if (!this.data[table]![
|
|
200
|
-
delete this.data[table]![
|
|
255
|
+
ids.forEach(id => {
|
|
256
|
+
if (!this.data[table]![id]) return
|
|
257
|
+
delete this.data[table]![id]
|
|
201
258
|
count++
|
|
202
259
|
})
|
|
260
|
+
|
|
203
261
|
return count
|
|
204
262
|
}
|
|
205
263
|
|
|
@@ -210,6 +268,8 @@ export class InMemoryDB implements CommonDB {
|
|
|
210
268
|
const patchEntries = Object.entries(patch)
|
|
211
269
|
if (!patchEntries.length) return 0
|
|
212
270
|
|
|
271
|
+
// todo: can we support tx here? :thinking:
|
|
272
|
+
|
|
213
273
|
const table = this.cfg.tablesPrefix + q.table
|
|
214
274
|
const rows = queryInMemory(q, Object.values(this.data[table] || {}) as ROW[])
|
|
215
275
|
rows.forEach((row: any) => {
|
|
@@ -249,29 +309,8 @@ export class InMemoryDB implements CommonDB {
|
|
|
249
309
|
return Readable.from(queryInMemory(q, Object.values(this.data[table] || {}) as ROW[]))
|
|
250
310
|
}
|
|
251
311
|
|
|
252
|
-
async
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
for await (const op of tx.ops) {
|
|
257
|
-
if (op.type === 'saveBatch') {
|
|
258
|
-
await this.saveBatch(op.table, op.rows, { ...op.opt, ...opt })
|
|
259
|
-
} else if (op.type === 'deleteByIds') {
|
|
260
|
-
await this.deleteByQuery(DBQuery.create(op.table).filter('id', 'in', op.ids), {
|
|
261
|
-
...op.opt,
|
|
262
|
-
...opt,
|
|
263
|
-
})
|
|
264
|
-
} else {
|
|
265
|
-
throw new Error(`DBOperation not supported: ${(op as any).type}`)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
} catch (err) {
|
|
269
|
-
// rollback
|
|
270
|
-
this.data = backup
|
|
271
|
-
this.cfg.logger!.log('InMemoryDB transaction rolled back')
|
|
272
|
-
|
|
273
|
-
throw err
|
|
274
|
-
}
|
|
312
|
+
async createTransaction(): Promise<DBTransaction> {
|
|
313
|
+
return new InMemoryDBTransaction(this)
|
|
275
314
|
}
|
|
276
315
|
|
|
277
316
|
/**
|
|
@@ -283,7 +322,7 @@ export class InMemoryDB implements CommonDB {
|
|
|
283
322
|
|
|
284
323
|
const started = Date.now()
|
|
285
324
|
|
|
286
|
-
await
|
|
325
|
+
await fs2.emptyDirAsync(persistentStoragePath)
|
|
287
326
|
|
|
288
327
|
const transformZip = persistZip ? [createGzip()] : []
|
|
289
328
|
let tables = 0
|
|
@@ -318,7 +357,7 @@ export class InMemoryDB implements CommonDB {
|
|
|
318
357
|
|
|
319
358
|
const started = Date.now()
|
|
320
359
|
|
|
321
|
-
await
|
|
360
|
+
await fs2.ensureDirAsync(persistentStoragePath)
|
|
322
361
|
|
|
323
362
|
this.data = {} // empty it in the beginning!
|
|
324
363
|
|
|
@@ -349,3 +388,37 @@ export class InMemoryDB implements CommonDB {
|
|
|
349
388
|
)
|
|
350
389
|
}
|
|
351
390
|
}
|
|
391
|
+
|
|
392
|
+
export class InMemoryDBTransaction implements DBTransaction {
|
|
393
|
+
constructor(private db: InMemoryDB) {}
|
|
394
|
+
|
|
395
|
+
ops: DBOperation[] = []
|
|
396
|
+
|
|
397
|
+
async commit(): Promise<void> {
|
|
398
|
+
const backup = _deepCopy(this.db.data)
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
for (const op of this.ops) {
|
|
402
|
+
if (op.type === 'saveBatch') {
|
|
403
|
+
await this.db.saveBatch(op.table, op.rows, op.opt)
|
|
404
|
+
} else if (op.type === 'deleteByIds') {
|
|
405
|
+
await this.db.deleteByIds(op.table, op.ids, op.opt)
|
|
406
|
+
} else {
|
|
407
|
+
throw new Error(`DBOperation not supported: ${(op as any).type}`)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
this.ops = []
|
|
412
|
+
} catch (err) {
|
|
413
|
+
// rollback
|
|
414
|
+
this.db.data = backup
|
|
415
|
+
this.db.cfg.logger!.log('InMemoryDB transaction rolled back')
|
|
416
|
+
|
|
417
|
+
throw err
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async rollback(): Promise<void> {
|
|
422
|
+
this.ops = []
|
|
423
|
+
}
|
|
424
|
+
}
|
package/src/base.common.db.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { JsonSchemaObject, JsonSchemaRootObject, ObjectWithId } from '@naturalcycles/js-lib'
|
|
2
2
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
3
|
-
import { CommonDB } from './common.db'
|
|
4
|
-
import {
|
|
3
|
+
import { CommonDB, CommonDBSupport, CommonDBType } from './common.db'
|
|
4
|
+
import {
|
|
5
|
+
CommonDBOptions,
|
|
6
|
+
CommonDBSaveOptions,
|
|
7
|
+
DBPatch,
|
|
8
|
+
DBTransaction,
|
|
9
|
+
RunQueryResult,
|
|
10
|
+
} from './db.model'
|
|
5
11
|
import { DBQuery } from './query/dbQuery'
|
|
6
|
-
import {
|
|
12
|
+
import { FakeDBTransaction } from './transaction/dbTransaction.util'
|
|
7
13
|
|
|
8
14
|
/* eslint-disable unused-imports/no-unused-vars */
|
|
9
15
|
|
|
@@ -12,6 +18,10 @@ import { DBTransaction } from './transaction/dbTransaction'
|
|
|
12
18
|
* To be extended by actual implementations.
|
|
13
19
|
*/
|
|
14
20
|
export class BaseCommonDB implements CommonDB {
|
|
21
|
+
dbType = CommonDBType.document
|
|
22
|
+
|
|
23
|
+
support: CommonDBSupport = {}
|
|
24
|
+
|
|
15
25
|
async ping(): Promise<void> {
|
|
16
26
|
throw new Error('ping is not implemented')
|
|
17
27
|
}
|
|
@@ -33,7 +43,7 @@ export class BaseCommonDB implements CommonDB {
|
|
|
33
43
|
// no-op
|
|
34
44
|
}
|
|
35
45
|
|
|
36
|
-
async getByIds<ROW extends ObjectWithId>(table: string, ids:
|
|
46
|
+
async getByIds<ROW extends ObjectWithId>(table: string, ids: string[]): Promise<ROW[]> {
|
|
37
47
|
throw new Error('getByIds is not implemented')
|
|
38
48
|
}
|
|
39
49
|
|
|
@@ -69,12 +79,11 @@ export class BaseCommonDB implements CommonDB {
|
|
|
69
79
|
throw new Error('streamQuery is not implemented')
|
|
70
80
|
}
|
|
71
81
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
throw new Error('commitTransaction is not implemented')
|
|
82
|
+
async deleteByIds(table: string, ids: string[], opt?: CommonDBOptions): Promise<number> {
|
|
83
|
+
throw new Error('deleteByIds is not implemented')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async createTransaction(): Promise<DBTransaction> {
|
|
87
|
+
return new FakeDBTransaction(this)
|
|
79
88
|
}
|
|
80
89
|
}
|
package/src/common.db.ts
CHANGED
|
@@ -6,12 +6,44 @@ import {
|
|
|
6
6
|
CommonDBSaveOptions,
|
|
7
7
|
CommonDBStreamOptions,
|
|
8
8
|
DBPatch,
|
|
9
|
+
DBTransaction,
|
|
9
10
|
RunQueryResult,
|
|
10
11
|
} from './db.model'
|
|
11
12
|
import { DBQuery } from './query/dbQuery'
|
|
12
|
-
|
|
13
|
+
|
|
14
|
+
export enum CommonDBType {
|
|
15
|
+
'document' = 'document',
|
|
16
|
+
'relational' = 'relational',
|
|
17
|
+
}
|
|
13
18
|
|
|
14
19
|
export interface CommonDB {
|
|
20
|
+
/**
|
|
21
|
+
* Relational databases are expected to return `null` for all missing properties.
|
|
22
|
+
*/
|
|
23
|
+
dbType: CommonDBType
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Manifest of supported features.
|
|
27
|
+
*/
|
|
28
|
+
support: CommonDBSupport
|
|
29
|
+
|
|
30
|
+
// Support flags indicate which of the CommonDB features are supported by this implementation.
|
|
31
|
+
supportsQueries?: boolean
|
|
32
|
+
supportsDBQueryFilter?: boolean
|
|
33
|
+
supportsDBQueryFilterIn?: boolean
|
|
34
|
+
supportsDBQueryOrder?: boolean
|
|
35
|
+
supportsDBQuerySelectFields?: boolean
|
|
36
|
+
supportsInsertSaveMethod?: boolean
|
|
37
|
+
supportsUpdateSaveMethod?: boolean
|
|
38
|
+
supportsUpdateByQuery?: boolean
|
|
39
|
+
supportsDBIncrement?: boolean
|
|
40
|
+
supportsCreateTable?: boolean
|
|
41
|
+
supportsTableSchemas?: boolean
|
|
42
|
+
supportsStreaming?: boolean
|
|
43
|
+
supportsBufferValues?: boolean
|
|
44
|
+
supportsNullValues?: boolean
|
|
45
|
+
supportsTransactions?: boolean
|
|
46
|
+
|
|
15
47
|
/**
|
|
16
48
|
* Checks that connection/credentials/etc is ok.
|
|
17
49
|
* Also acts as a "warmup request" for a DB.
|
|
@@ -50,7 +82,7 @@ export interface CommonDB {
|
|
|
50
82
|
*/
|
|
51
83
|
getByIds: <ROW extends ObjectWithId>(
|
|
52
84
|
table: string,
|
|
53
|
-
ids:
|
|
85
|
+
ids: string[],
|
|
54
86
|
opt?: CommonDBOptions,
|
|
55
87
|
) => Promise<ROW[]>
|
|
56
88
|
|
|
@@ -84,6 +116,12 @@ export interface CommonDB {
|
|
|
84
116
|
) => Promise<void>
|
|
85
117
|
|
|
86
118
|
// DELETE
|
|
119
|
+
/**
|
|
120
|
+
* Returns number of deleted items.
|
|
121
|
+
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
122
|
+
*/
|
|
123
|
+
deleteByIds: (table: string, ids: string[], opt?: CommonDBOptions) => Promise<number>
|
|
124
|
+
|
|
87
125
|
/**
|
|
88
126
|
* Returns number of deleted items.
|
|
89
127
|
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
@@ -122,5 +160,44 @@ export interface CommonDB {
|
|
|
122
160
|
* Should be implemented as a Transaction (best effort), which means that
|
|
123
161
|
* either ALL or NONE of the operations should be applied.
|
|
124
162
|
*/
|
|
125
|
-
|
|
163
|
+
createTransaction: () => Promise<DBTransaction>
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Manifest of supported features.
|
|
168
|
+
*/
|
|
169
|
+
export interface CommonDBSupport {
|
|
170
|
+
queries?: boolean
|
|
171
|
+
dbQueryFilter?: boolean
|
|
172
|
+
dbQueryFilterIn?: boolean
|
|
173
|
+
dbQueryOrder?: boolean
|
|
174
|
+
dbQuerySelectFields?: boolean
|
|
175
|
+
insertSaveMethod?: boolean
|
|
176
|
+
updateSaveMethod?: boolean
|
|
177
|
+
updateByQuery?: boolean
|
|
178
|
+
dbIncrement?: boolean
|
|
179
|
+
createTable?: boolean
|
|
180
|
+
tableSchemas?: boolean
|
|
181
|
+
streaming?: boolean
|
|
182
|
+
bufferValues?: boolean
|
|
183
|
+
nullValues?: boolean
|
|
184
|
+
transactions?: boolean
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export const commonDBFullSupport: CommonDBSupport = {
|
|
188
|
+
queries: true,
|
|
189
|
+
dbQueryFilter: true,
|
|
190
|
+
dbQueryFilterIn: true,
|
|
191
|
+
dbQueryOrder: true,
|
|
192
|
+
dbQuerySelectFields: true,
|
|
193
|
+
insertSaveMethod: true,
|
|
194
|
+
updateSaveMethod: true,
|
|
195
|
+
updateByQuery: true,
|
|
196
|
+
dbIncrement: true,
|
|
197
|
+
createTable: true,
|
|
198
|
+
tableSchemas: true,
|
|
199
|
+
streaming: true,
|
|
200
|
+
bufferValues: true,
|
|
201
|
+
nullValues: true,
|
|
202
|
+
transactions: true,
|
|
126
203
|
}
|
|
@@ -274,17 +274,6 @@ export interface CommonDaoOptions extends CommonDBOptions {
|
|
|
274
274
|
* Useful e.g in AirtableDB where you can have one Dao to control multiple tables.
|
|
275
275
|
*/
|
|
276
276
|
table?: string
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* If passed - operation will not be performed immediately, but instead "added" to the transaction.
|
|
280
|
-
* In the end - transaction needs to be committed (by calling `commit`).
|
|
281
|
-
* This API is inspired by Datastore API.
|
|
282
|
-
*
|
|
283
|
-
* Only applicable to save* and delete* operations
|
|
284
|
-
*
|
|
285
|
-
* @experimental
|
|
286
|
-
*/
|
|
287
|
-
tx?: boolean
|
|
288
277
|
}
|
|
289
278
|
|
|
290
279
|
export interface CommonDaoSaveOptions<BM extends Partial<ObjectWithId>, DBM extends ObjectWithId>
|