@naturalcycles/db-lib 9.21.0 → 9.22.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 +2 -2
- package/dist/adapter/cachedb/cache.db.js +4 -3
- package/dist/adapter/file/file.db.js +2 -1
- package/dist/adapter/inmemory/inMemory.db.d.ts +7 -6
- package/dist/adapter/inmemory/inMemory.db.js +15 -13
- package/dist/adapter/inmemory/inMemoryKeyValueDB.d.ts +1 -0
- package/dist/adapter/inmemory/inMemoryKeyValueDB.js +13 -0
- package/dist/base.common.db.d.ts +4 -3
- package/dist/base.common.db.js +5 -2
- package/dist/common.db.d.ts +23 -14
- package/dist/common.db.js +2 -2
- package/dist/commondao/common.dao.d.ts +16 -5
- package/dist/commondao/common.dao.js +37 -8
- package/dist/db.model.d.ts +0 -13
- package/dist/db.model.js +1 -17
- package/dist/kv/commonKeyValueDB.d.ts +16 -2
- package/dist/query/dbQuery.d.ts +2 -2
- package/dist/query/dbQuery.js +2 -2
- package/dist/testing/daoTest.js +26 -0
- package/dist/testing/dbTest.js +21 -23
- package/dist/testing/keyValueDBTest.js +15 -3
- package/package.json +1 -1
- package/src/adapter/cachedb/cache.db.ts +6 -5
- package/src/adapter/file/file.db.ts +2 -1
- package/src/adapter/inmemory/inMemory.db.ts +29 -18
- package/src/adapter/inmemory/inMemoryKeyValueDB.ts +17 -1
- package/src/base.common.db.ts +18 -5
- package/src/common.db.ts +36 -17
- package/src/commondao/common.dao.ts +46 -11
- package/src/db.model.ts +0 -19
- package/src/kv/commonKeyValueDB.ts +17 -2
- package/src/query/dbQuery.ts +2 -3
- package/src/testing/daoTest.ts +28 -0
- package/src/testing/dbTest.ts +22 -28
- package/src/testing/keyValueDBTest.ts +17 -3
|
@@ -86,17 +86,29 @@ function runCommonKeyValueDBTest(db) {
|
|
|
86
86
|
expect(results).toEqual([]);
|
|
87
87
|
});
|
|
88
88
|
if (support.increment) {
|
|
89
|
+
const id = 'nonExistingField';
|
|
90
|
+
const id2 = 'nonExistingField2';
|
|
89
91
|
test('increment on a non-existing field should set the value to 1', async () => {
|
|
90
|
-
const result = await db.increment(test_model_1.TEST_TABLE,
|
|
92
|
+
const result = await db.increment(test_model_1.TEST_TABLE, id);
|
|
91
93
|
expect(result).toBe(1);
|
|
92
94
|
});
|
|
93
95
|
test('increment on a existing field should increase the value by one', async () => {
|
|
94
|
-
const result = await db.increment(test_model_1.TEST_TABLE,
|
|
96
|
+
const result = await db.increment(test_model_1.TEST_TABLE, id);
|
|
95
97
|
expect(result).toBe(2);
|
|
96
98
|
});
|
|
97
99
|
test('increment should increase the value by the specified amount', async () => {
|
|
98
|
-
const result = await db.increment(test_model_1.TEST_TABLE,
|
|
100
|
+
const result = await db.increment(test_model_1.TEST_TABLE, id, 2);
|
|
99
101
|
expect(result).toBe(4);
|
|
100
102
|
});
|
|
103
|
+
test('increment 2 ids at the same time', async () => {
|
|
104
|
+
const result = await db.incrementBatch(test_model_1.TEST_TABLE, {
|
|
105
|
+
[id]: 1,
|
|
106
|
+
[id2]: 2,
|
|
107
|
+
});
|
|
108
|
+
expect(result).toEqual({
|
|
109
|
+
[id]: 5,
|
|
110
|
+
[id2]: 2,
|
|
111
|
+
});
|
|
112
|
+
});
|
|
101
113
|
}
|
|
102
114
|
}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
10
10
|
import { BaseCommonDB } from '../../base.common.db'
|
|
11
11
|
import { CommonDB, commonDBFullSupport, CommonDBSupport } from '../../common.db'
|
|
12
|
-
import {
|
|
12
|
+
import { RunQueryResult } from '../../db.model'
|
|
13
13
|
import { DBQuery } from '../../query/dbQuery'
|
|
14
14
|
import {
|
|
15
15
|
CacheDBCfg,
|
|
@@ -29,6 +29,7 @@ export class CacheDB extends BaseCommonDB implements CommonDB {
|
|
|
29
29
|
override support: CommonDBSupport = {
|
|
30
30
|
...commonDBFullSupport,
|
|
31
31
|
transactions: false,
|
|
32
|
+
increment: false,
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
constructor(cfg: CacheDBCfg) {
|
|
@@ -271,19 +272,19 @@ export class CacheDB extends BaseCommonDB implements CommonDB {
|
|
|
271
272
|
return deletedIds
|
|
272
273
|
}
|
|
273
274
|
|
|
274
|
-
override async
|
|
275
|
+
override async patchByQuery<ROW extends ObjectWithId>(
|
|
275
276
|
q: DBQuery<ROW>,
|
|
276
|
-
patch:
|
|
277
|
+
patch: Partial<ROW>,
|
|
277
278
|
opt: CacheDBOptions = {},
|
|
278
279
|
): Promise<number> {
|
|
279
280
|
let updated: number | undefined
|
|
280
281
|
|
|
281
282
|
if (!opt.onlyCache && !this.cfg.onlyCache) {
|
|
282
|
-
updated = await this.cfg.downstreamDB.
|
|
283
|
+
updated = await this.cfg.downstreamDB.patchByQuery(q, patch, opt)
|
|
283
284
|
}
|
|
284
285
|
|
|
285
286
|
if (!opt.skipCache && !this.cfg.skipCache) {
|
|
286
|
-
const cacheResult = this.cfg.cacheDB.
|
|
287
|
+
const cacheResult = this.cfg.cacheDB.patchByQuery(q, patch, opt)
|
|
287
288
|
if (this.cfg.awaitCache) updated ??= await cacheResult
|
|
288
289
|
}
|
|
289
290
|
|
|
@@ -45,9 +45,10 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
45
45
|
bufferValues: false, // todo: implement
|
|
46
46
|
insertSaveMethod: false,
|
|
47
47
|
updateSaveMethod: false,
|
|
48
|
-
|
|
48
|
+
patchByQuery: false,
|
|
49
49
|
createTable: false,
|
|
50
50
|
transactions: false, // todo
|
|
51
|
+
increment: false,
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
constructor(cfg: FileDBCfg) {
|
|
@@ -3,9 +3,12 @@ import {
|
|
|
3
3
|
_assert,
|
|
4
4
|
_by,
|
|
5
5
|
_deepCopy,
|
|
6
|
+
_isEmptyObject,
|
|
6
7
|
_since,
|
|
7
8
|
_sortObjectDeep,
|
|
9
|
+
_stringMapEntries,
|
|
8
10
|
_stringMapValues,
|
|
11
|
+
AnyObjectWithId,
|
|
9
12
|
CommonLogger,
|
|
10
13
|
generateJsonSchemaFromData,
|
|
11
14
|
JsonSchemaObject,
|
|
@@ -27,9 +30,7 @@ import {
|
|
|
27
30
|
commonDBFullSupport,
|
|
28
31
|
CommonDBTransactionOptions,
|
|
29
32
|
CommonDBType,
|
|
30
|
-
DBIncrement,
|
|
31
33
|
DBOperation,
|
|
32
|
-
DBPatch,
|
|
33
34
|
DBTransactionFn,
|
|
34
35
|
queryInMemory,
|
|
35
36
|
} from '../..'
|
|
@@ -113,7 +114,7 @@ export class InMemoryDB implements CommonDB {
|
|
|
113
114
|
cfg: InMemoryDBCfg
|
|
114
115
|
|
|
115
116
|
// data[table][id] > {id: 'a', created: ... }
|
|
116
|
-
data: StringMap<StringMap<
|
|
117
|
+
data: StringMap<StringMap<AnyObjectWithId>> = {}
|
|
117
118
|
|
|
118
119
|
/**
|
|
119
120
|
* Returns internal "Data snapshot".
|
|
@@ -233,25 +234,14 @@ export class InMemoryDB implements CommonDB {
|
|
|
233
234
|
return count
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
async
|
|
237
|
+
async patchByQuery<ROW extends ObjectWithId>(
|
|
237
238
|
q: DBQuery<ROW>,
|
|
238
|
-
patch:
|
|
239
|
+
patch: Partial<ROW>,
|
|
239
240
|
): Promise<number> {
|
|
240
|
-
|
|
241
|
-
if (!patchEntries.length) return 0
|
|
242
|
-
|
|
241
|
+
if (_isEmptyObject(patch)) return 0
|
|
243
242
|
const table = this.cfg.tablesPrefix + q.table
|
|
244
243
|
const rows = queryInMemory(q, Object.values(this.data[table] || {}) as ROW[])
|
|
245
|
-
rows.forEach((row
|
|
246
|
-
patchEntries.forEach(([k, v]) => {
|
|
247
|
-
if (v instanceof DBIncrement) {
|
|
248
|
-
row[k] = (row[k] || 0) + v.amount
|
|
249
|
-
} else {
|
|
250
|
-
row[k] = v
|
|
251
|
-
}
|
|
252
|
-
})
|
|
253
|
-
})
|
|
254
|
-
|
|
244
|
+
rows.forEach(row => Object.assign(row, patch))
|
|
255
245
|
return rows.length
|
|
256
246
|
}
|
|
257
247
|
|
|
@@ -294,6 +284,27 @@ export class InMemoryDB implements CommonDB {
|
|
|
294
284
|
}
|
|
295
285
|
}
|
|
296
286
|
|
|
287
|
+
async incrementBatch(
|
|
288
|
+
table: string,
|
|
289
|
+
prop: string,
|
|
290
|
+
incrementMap: StringMap<number>,
|
|
291
|
+
_opt?: CommonDBOptions,
|
|
292
|
+
): Promise<StringMap<number>> {
|
|
293
|
+
const tbl = this.cfg.tablesPrefix + table
|
|
294
|
+
this.data[tbl] ||= {}
|
|
295
|
+
|
|
296
|
+
const result: StringMap<number> = {}
|
|
297
|
+
|
|
298
|
+
for (const [id, by] of _stringMapEntries(incrementMap)) {
|
|
299
|
+
this.data[tbl][id] ||= { id }
|
|
300
|
+
const newValue = ((this.data[tbl][id][prop] as number) || 0) + by
|
|
301
|
+
this.data[tbl][id][prop] = newValue
|
|
302
|
+
result[id] = newValue
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return result
|
|
306
|
+
}
|
|
307
|
+
|
|
297
308
|
/**
|
|
298
309
|
* Flushes all tables (all namespaces) at once.
|
|
299
310
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Readable } from 'node:stream'
|
|
2
|
-
import { StringMap } from '@naturalcycles/js-lib'
|
|
2
|
+
import { _stringMapEntries, StringMap } from '@naturalcycles/js-lib'
|
|
3
3
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
4
4
|
import { CommonDBCreateOptions } from '../../db.model'
|
|
5
5
|
import {
|
|
@@ -29,6 +29,7 @@ export class InMemoryKeyValueDB implements CommonKeyValueDB {
|
|
|
29
29
|
ids.forEach(id => delete this.data[table]![id])
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// todo: but should we work with Tuples or Objects?
|
|
32
33
|
async getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]> {
|
|
33
34
|
this.data[table] ||= {}
|
|
34
35
|
return ids.map(id => [id, this.data[table]![id]!] as KeyValueDBTuple).filter(e => e[1])
|
|
@@ -65,4 +66,19 @@ export class InMemoryKeyValueDB implements CommonKeyValueDB {
|
|
|
65
66
|
|
|
66
67
|
return newValue
|
|
67
68
|
}
|
|
69
|
+
|
|
70
|
+
async incrementBatch(table: string, incrementMap: StringMap<number>): Promise<StringMap<number>> {
|
|
71
|
+
this.data[table] ||= {}
|
|
72
|
+
|
|
73
|
+
const result: StringMap<number> = {}
|
|
74
|
+
|
|
75
|
+
for (const [id, by] of _stringMapEntries(incrementMap)) {
|
|
76
|
+
const newValue = parseInt(this.data[table][id]?.toString() || '0') + by
|
|
77
|
+
// todo: but should this.data store Buffer or number for incremented values?
|
|
78
|
+
this.data[table][id] = Buffer.from(String(newValue))
|
|
79
|
+
result[id] = newValue
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result
|
|
83
|
+
}
|
|
68
84
|
}
|
package/src/base.common.db.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
JsonSchemaObject,
|
|
3
|
+
JsonSchemaRootObject,
|
|
4
|
+
ObjectWithId,
|
|
5
|
+
StringMap,
|
|
6
|
+
} from '@naturalcycles/js-lib'
|
|
2
7
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
3
8
|
import { CommonDB, CommonDBSupport, CommonDBType } from './common.db'
|
|
4
9
|
import {
|
|
5
10
|
CommonDBOptions,
|
|
6
11
|
CommonDBSaveOptions,
|
|
7
12
|
CommonDBTransactionOptions,
|
|
8
|
-
DBPatch,
|
|
9
13
|
DBTransactionFn,
|
|
10
14
|
RunQueryResult,
|
|
11
15
|
} from './db.model'
|
|
@@ -50,12 +54,12 @@ export class BaseCommonDB implements CommonDB {
|
|
|
50
54
|
throw new Error('deleteByQuery is not implemented')
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
async
|
|
57
|
+
async patchByQuery<ROW extends ObjectWithId>(
|
|
54
58
|
_q: DBQuery<ROW>,
|
|
55
|
-
_patch:
|
|
59
|
+
_patch: Partial<ROW>,
|
|
56
60
|
_opt?: CommonDBOptions,
|
|
57
61
|
): Promise<number> {
|
|
58
|
-
throw new Error('
|
|
62
|
+
throw new Error('patchByQuery is not implemented')
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
async runQuery<ROW extends ObjectWithId>(_q: DBQuery<ROW>): Promise<RunQueryResult<ROW>> {
|
|
@@ -87,4 +91,13 @@ export class BaseCommonDB implements CommonDB {
|
|
|
87
91
|
await fn(tx)
|
|
88
92
|
// there's no try/catch and rollback, as there's nothing to rollback
|
|
89
93
|
}
|
|
94
|
+
|
|
95
|
+
async incrementBatch(
|
|
96
|
+
_table: string,
|
|
97
|
+
_prop: string,
|
|
98
|
+
_incrementMap: StringMap<number>,
|
|
99
|
+
_opt?: CommonDBOptions,
|
|
100
|
+
): Promise<StringMap<number>> {
|
|
101
|
+
throw new Error('incrementBatch is not implemented')
|
|
102
|
+
}
|
|
90
103
|
}
|
package/src/common.db.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
JsonSchemaObject,
|
|
3
|
+
JsonSchemaRootObject,
|
|
4
|
+
ObjectWithId,
|
|
5
|
+
StringMap,
|
|
6
|
+
} from '@naturalcycles/js-lib'
|
|
2
7
|
import type { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
3
8
|
import {
|
|
4
9
|
CommonDBCreateOptions,
|
|
@@ -6,7 +11,6 @@ import {
|
|
|
6
11
|
CommonDBSaveOptions,
|
|
7
12
|
CommonDBStreamOptions,
|
|
8
13
|
CommonDBTransactionOptions,
|
|
9
|
-
DBPatch,
|
|
10
14
|
DBTransactionFn,
|
|
11
15
|
RunQueryResult,
|
|
12
16
|
} from './db.model'
|
|
@@ -116,7 +120,7 @@ export interface CommonDB {
|
|
|
116
120
|
) => Promise<number>
|
|
117
121
|
|
|
118
122
|
/**
|
|
119
|
-
* Applies patch to the rows
|
|
123
|
+
* Applies patch to all the rows that are matched by the query.
|
|
120
124
|
*
|
|
121
125
|
* Example:
|
|
122
126
|
*
|
|
@@ -124,18 +128,11 @@ export interface CommonDB {
|
|
|
124
128
|
*
|
|
125
129
|
* patch would be { A: 'B' } for that query.
|
|
126
130
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* UPDATE table SET A = A + 1
|
|
130
|
-
*
|
|
131
|
-
* In that case patch would look like:
|
|
132
|
-
* { A: DBIncrement(1) }
|
|
133
|
-
*
|
|
134
|
-
* Returns number of rows affected.
|
|
131
|
+
* Returns the number of rows affected.
|
|
135
132
|
*/
|
|
136
|
-
|
|
133
|
+
patchByQuery: <ROW extends ObjectWithId>(
|
|
137
134
|
q: DBQuery<ROW>,
|
|
138
|
-
patch:
|
|
135
|
+
patch: Partial<ROW>,
|
|
139
136
|
opt?: CommonDBOptions,
|
|
140
137
|
) => Promise<number>
|
|
141
138
|
|
|
@@ -152,6 +149,28 @@ export interface CommonDB {
|
|
|
152
149
|
* unless specified as readOnly in CommonDBTransactionOptions.
|
|
153
150
|
*/
|
|
154
151
|
runInTransaction: (fn: DBTransactionFn, opt?: CommonDBTransactionOptions) => Promise<void>
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Increments a value of a property by a given amount.
|
|
155
|
+
* This is a batch operation, so it allows to increment multiple rows at once.
|
|
156
|
+
*
|
|
157
|
+
* - table - the table to apply operations on
|
|
158
|
+
* - prop - name of the property to increment (in each of the rows passed)
|
|
159
|
+
* - incrementMap - map from id to increment value
|
|
160
|
+
*
|
|
161
|
+
* Example of incrementMap:
|
|
162
|
+
* { rowId1: 2, rowId2: 3 }
|
|
163
|
+
*
|
|
164
|
+
* Returns the incrementMap with the same keys and updated values.
|
|
165
|
+
*
|
|
166
|
+
* @experimental
|
|
167
|
+
*/
|
|
168
|
+
incrementBatch: (
|
|
169
|
+
table: string,
|
|
170
|
+
prop: string,
|
|
171
|
+
incrementMap: StringMap<number>,
|
|
172
|
+
opt?: CommonDBOptions,
|
|
173
|
+
) => Promise<StringMap<number>>
|
|
155
174
|
}
|
|
156
175
|
|
|
157
176
|
/**
|
|
@@ -165,8 +184,8 @@ export interface CommonDBSupport {
|
|
|
165
184
|
dbQuerySelectFields?: boolean
|
|
166
185
|
insertSaveMethod?: boolean
|
|
167
186
|
updateSaveMethod?: boolean
|
|
168
|
-
|
|
169
|
-
|
|
187
|
+
patchByQuery?: boolean
|
|
188
|
+
increment?: boolean
|
|
170
189
|
createTable?: boolean
|
|
171
190
|
tableSchemas?: boolean
|
|
172
191
|
streaming?: boolean
|
|
@@ -183,8 +202,8 @@ export const commonDBFullSupport: CommonDBSupport = {
|
|
|
183
202
|
dbQuerySelectFields: true,
|
|
184
203
|
insertSaveMethod: true,
|
|
185
204
|
updateSaveMethod: true,
|
|
186
|
-
|
|
187
|
-
|
|
205
|
+
patchByQuery: true,
|
|
206
|
+
increment: true,
|
|
188
207
|
createTable: true,
|
|
189
208
|
tableSchemas: true,
|
|
190
209
|
streaming: true,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
ObjectWithId,
|
|
23
23
|
pMap,
|
|
24
24
|
SKIP,
|
|
25
|
+
StringMap,
|
|
25
26
|
UnixTimestampMillisNumber,
|
|
26
27
|
Unsaved,
|
|
27
28
|
ZodSchema,
|
|
@@ -44,7 +45,7 @@ import {
|
|
|
44
45
|
writableVoid,
|
|
45
46
|
} from '@naturalcycles/nodejs-lib'
|
|
46
47
|
import { DBLibError } from '../cnst'
|
|
47
|
-
import { CommonDBTransactionOptions,
|
|
48
|
+
import { CommonDBTransactionOptions, DBTransaction, RunQueryResult } from '../db.model'
|
|
48
49
|
import { DBQuery, RunnableDBQuery } from '../query/dbQuery'
|
|
49
50
|
import {
|
|
50
51
|
CommonDaoCfg,
|
|
@@ -1068,31 +1069,65 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
|
|
|
1068
1069
|
return deleted
|
|
1069
1070
|
}
|
|
1070
1071
|
|
|
1071
|
-
async
|
|
1072
|
-
return await this.updateByQuery(this.query().filterEq('id', id), patch, opt)
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
async updateByIds(ids: ID[], patch: DBPatch<DBM>, opt: CommonDaoOptions = {}): Promise<number> {
|
|
1072
|
+
async patchByIds(ids: ID[], patch: Partial<DBM>, opt: CommonDaoOptions = {}): Promise<number> {
|
|
1076
1073
|
if (!ids.length) return 0
|
|
1077
|
-
return await this.
|
|
1074
|
+
return await this.patchByQuery(this.query().filterIn('id', ids), patch, opt)
|
|
1078
1075
|
}
|
|
1079
1076
|
|
|
1080
|
-
async
|
|
1077
|
+
async patchByQuery(
|
|
1081
1078
|
q: DBQuery<DBM>,
|
|
1082
|
-
patch:
|
|
1079
|
+
patch: Partial<DBM>,
|
|
1083
1080
|
opt: CommonDaoOptions = {},
|
|
1084
1081
|
): Promise<number> {
|
|
1085
1082
|
this.validateQueryIndexes(q) // throws if query uses `excludeFromIndexes` property
|
|
1086
1083
|
this.requireWriteAccess()
|
|
1087
1084
|
this.requireObjectMutability(opt)
|
|
1088
1085
|
q.table = opt.table || q.table
|
|
1089
|
-
const op = `
|
|
1086
|
+
const op = `patchByQuery(${q.pretty()})`
|
|
1090
1087
|
const started = this.logStarted(op, q.table)
|
|
1091
|
-
const updated = await this.cfg.db.
|
|
1088
|
+
const updated = await this.cfg.db.patchByQuery(q, patch, opt)
|
|
1092
1089
|
this.logSaveResult(started, op, q.table)
|
|
1093
1090
|
return updated
|
|
1094
1091
|
}
|
|
1095
1092
|
|
|
1093
|
+
/**
|
|
1094
|
+
* Caveat: it doesn't update created/updated props.
|
|
1095
|
+
*
|
|
1096
|
+
* @experimental
|
|
1097
|
+
*/
|
|
1098
|
+
async increment(prop: keyof DBM, id: ID, by = 1, opt: CommonDaoOptions = {}): Promise<number> {
|
|
1099
|
+
this.requireWriteAccess()
|
|
1100
|
+
this.requireObjectMutability(opt)
|
|
1101
|
+
const { table } = this.cfg
|
|
1102
|
+
const op = `increment`
|
|
1103
|
+
const started = this.logStarted(op, table)
|
|
1104
|
+
const result = await this.cfg.db.incrementBatch(table, prop as string, {
|
|
1105
|
+
[id as string]: by,
|
|
1106
|
+
})
|
|
1107
|
+
this.logSaveResult(started, op, table)
|
|
1108
|
+
return result[id as string]!
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Caveat: it doesn't update created/updated props.
|
|
1113
|
+
*
|
|
1114
|
+
* @experimental
|
|
1115
|
+
*/
|
|
1116
|
+
async incrementBatch(
|
|
1117
|
+
prop: keyof DBM,
|
|
1118
|
+
incrementMap: StringMap<number>,
|
|
1119
|
+
opt: CommonDaoOptions = {},
|
|
1120
|
+
): Promise<StringMap<number>> {
|
|
1121
|
+
this.requireWriteAccess()
|
|
1122
|
+
this.requireObjectMutability(opt)
|
|
1123
|
+
const { table } = this.cfg
|
|
1124
|
+
const op = `incrementBatch`
|
|
1125
|
+
const started = this.logStarted(op, table)
|
|
1126
|
+
const result = await this.cfg.db.incrementBatch(table, prop as string, incrementMap)
|
|
1127
|
+
this.logSaveResult(started, op, table)
|
|
1128
|
+
return result
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1096
1131
|
// CONVERSIONS
|
|
1097
1132
|
|
|
1098
1133
|
async dbmToBM(_dbm: undefined, opt?: CommonDaoOptions): Promise<undefined>
|
package/src/db.model.ts
CHANGED
|
@@ -113,22 +113,3 @@ export enum DBModelType {
|
|
|
113
113
|
DBM = 'DBM',
|
|
114
114
|
BM = 'BM',
|
|
115
115
|
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Allows to construct a query similar to:
|
|
119
|
-
*
|
|
120
|
-
* UPDATE table SET A = A + 1
|
|
121
|
-
*
|
|
122
|
-
* In this case DBIncement.of(1) will be needed.
|
|
123
|
-
*/
|
|
124
|
-
export class DBIncrement {
|
|
125
|
-
private constructor(public amount: number) {}
|
|
126
|
-
|
|
127
|
-
static of(amount: number): DBIncrement {
|
|
128
|
-
return new DBIncrement(amount)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export type DBPatch<ROW extends ObjectWithId> = Partial<
|
|
133
|
-
Record<keyof ROW, ROW[keyof ROW] | DBIncrement>
|
|
134
|
-
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UnixTimestampNumber } from '@naturalcycles/js-lib'
|
|
1
|
+
import { StringMap, UnixTimestampNumber } from '@naturalcycles/js-lib'
|
|
2
2
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
3
3
|
import { CommonDBCreateOptions } from '../db.model'
|
|
4
4
|
|
|
@@ -46,13 +46,28 @@ export interface CommonKeyValueDB {
|
|
|
46
46
|
count: (table: string) => Promise<number>
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
49
|
* Increments the value of a key in a table by a given amount.
|
|
51
50
|
* Default increment is 1 when `by` is not provided.
|
|
52
51
|
*
|
|
53
52
|
* Returns the new value.
|
|
53
|
+
*
|
|
54
|
+
* @experimental
|
|
54
55
|
*/
|
|
55
56
|
increment: (table: string, id: string, by?: number) => Promise<number>
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Perform a batch of Increment operations.
|
|
60
|
+
* Given incrementMap, increment each key of it by the given amount (value of the map).
|
|
61
|
+
*
|
|
62
|
+
* Example:
|
|
63
|
+
* { key1: 2, key2: 3 }
|
|
64
|
+
* would increment `key1` by 2, and `key2` by 3.
|
|
65
|
+
*
|
|
66
|
+
* Returns the incrementMap with the same keys and updated values.
|
|
67
|
+
*
|
|
68
|
+
* @experimental
|
|
69
|
+
*/
|
|
70
|
+
incrementBatch: (table: string, incrementMap: StringMap<number>) => Promise<StringMap<number>>
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
export type KeyValueDBTuple = [key: string, value: Buffer]
|
package/src/query/dbQuery.ts
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
CommonDaoStreamDeleteOptions,
|
|
12
12
|
CommonDaoStreamForEachOptions,
|
|
13
13
|
CommonDaoStreamOptions,
|
|
14
|
-
DBPatch,
|
|
15
14
|
} from '..'
|
|
16
15
|
import { CommonDao } from '../commondao/common.dao'
|
|
17
16
|
import { RunQueryResult } from '../db.model'
|
|
@@ -276,8 +275,8 @@ export class RunnableDBQuery<
|
|
|
276
275
|
return await this.dao.runQueryCount(this, opt)
|
|
277
276
|
}
|
|
278
277
|
|
|
279
|
-
async
|
|
280
|
-
return await this.dao.
|
|
278
|
+
async patchByQuery(patch: Partial<DBM>, opt?: CommonDaoOptions): Promise<number> {
|
|
279
|
+
return await this.dao.patchByQuery(this, patch, opt)
|
|
281
280
|
}
|
|
282
281
|
|
|
283
282
|
async streamQueryForEach(
|
package/src/testing/daoTest.ts
CHANGED
|
@@ -129,6 +129,34 @@ export function runCommonDaoTest(db: CommonDB, quirks: CommonDBImplementationQui
|
|
|
129
129
|
expectMatch(expectedItems, itemsSaved, quirks)
|
|
130
130
|
})
|
|
131
131
|
|
|
132
|
+
if (support.increment) {
|
|
133
|
+
test('increment', async () => {
|
|
134
|
+
await dao.incrementBatch('k3', { id1: 1, id2: 2 })
|
|
135
|
+
let rows = await dao.query().runQuery()
|
|
136
|
+
rows = _sortBy(rows, r => r.id)
|
|
137
|
+
const expected = expectedItems.map(r => {
|
|
138
|
+
if (r.id === 'id1') {
|
|
139
|
+
return {
|
|
140
|
+
...r,
|
|
141
|
+
k3: r.k3! + 1,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (r.id === 'id2') {
|
|
145
|
+
return {
|
|
146
|
+
...r,
|
|
147
|
+
k3: r.k3! + 2,
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return r
|
|
151
|
+
})
|
|
152
|
+
expectMatch(expected, rows, quirks)
|
|
153
|
+
|
|
154
|
+
// reset the changes
|
|
155
|
+
await dao.increment('k3', 'id1', -1)
|
|
156
|
+
await dao.increment('k3', 'id2', -2)
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
132
160
|
// GET not empty
|
|
133
161
|
test('getByIds all items', async () => {
|
|
134
162
|
const rows = await dao.getByIds(items.map(i => i.id).concat('abcd'))
|
package/src/testing/dbTest.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { _deepFreeze, _filterObject, _pick, _sortBy, pMap } from '@naturalcycles/js-lib'
|
|
2
2
|
import { CommonDB, CommonDBType } from '../common.db'
|
|
3
|
-
import { DBIncrement, DBPatch } from '../db.model'
|
|
4
3
|
import { DBQuery } from '../query/dbQuery'
|
|
5
4
|
import {
|
|
6
5
|
createTestItemDBM,
|
|
@@ -317,18 +316,18 @@ export function runCommonDBTest(db: CommonDB, quirks: CommonDBImplementationQuir
|
|
|
317
316
|
})
|
|
318
317
|
}
|
|
319
318
|
|
|
320
|
-
if (support.
|
|
321
|
-
test('
|
|
319
|
+
if (support.patchByQuery) {
|
|
320
|
+
test('patchByQuery', async () => {
|
|
322
321
|
// cleanup, reset initial data
|
|
323
322
|
await db.deleteByQuery(queryAll())
|
|
324
323
|
await db.saveBatch(TEST_TABLE, items)
|
|
325
324
|
|
|
326
|
-
const patch:
|
|
325
|
+
const patch: Partial<TestItemDBM> = {
|
|
327
326
|
k3: 5,
|
|
328
327
|
k2: 'abc',
|
|
329
328
|
}
|
|
330
329
|
|
|
331
|
-
await db.
|
|
330
|
+
await db.patchByQuery(DBQuery.create<TestItemDBM>(TEST_TABLE).filterEq('even', true), patch)
|
|
332
331
|
|
|
333
332
|
const { rows } = await db.runQuery(queryAll())
|
|
334
333
|
const expected = items.map(r => {
|
|
@@ -339,33 +338,28 @@ export function runCommonDBTest(db: CommonDB, quirks: CommonDBImplementationQuir
|
|
|
339
338
|
})
|
|
340
339
|
expectMatch(expected, rows, quirks)
|
|
341
340
|
})
|
|
341
|
+
}
|
|
342
342
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const patch: DBPatch<TestItemDBM> = {
|
|
350
|
-
k3: DBIncrement.of(1),
|
|
351
|
-
k2: 'abcd',
|
|
352
|
-
}
|
|
343
|
+
if (support.increment) {
|
|
344
|
+
test('incrementBatch', async () => {
|
|
345
|
+
// cleanup, reset initial data
|
|
346
|
+
await db.deleteByQuery(queryAll())
|
|
347
|
+
await db.saveBatch(TEST_TABLE, items)
|
|
353
348
|
|
|
354
|
-
|
|
355
|
-
DBQuery.create<TestItemDBM>(TEST_TABLE).filterEq('even', true),
|
|
356
|
-
patch,
|
|
357
|
-
)
|
|
349
|
+
await db.incrementBatch(TEST_TABLE, 'k3', { id1: 1, id2: 2 })
|
|
358
350
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
351
|
+
const { rows } = await db.runQuery(queryAll())
|
|
352
|
+
const expected = items.map(r => {
|
|
353
|
+
if (r.id === 'id1') {
|
|
354
|
+
return { ...r, k3: 2 }
|
|
355
|
+
}
|
|
356
|
+
if (r.id === 'id2') {
|
|
357
|
+
return { ...r, k3: 4 }
|
|
358
|
+
}
|
|
359
|
+
return r
|
|
367
360
|
})
|
|
368
|
-
|
|
361
|
+
expectMatch(expected, rows, quirks)
|
|
362
|
+
})
|
|
369
363
|
}
|
|
370
364
|
|
|
371
365
|
if (support.queries) {
|