@naturalcycles/db-lib 8.46.1 → 8.48.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.
@@ -3,7 +3,7 @@ import { Readable } from 'node:stream';
3
3
  import { JsonSchemaObject, JsonSchemaRootObject, ObjectWithId } from '@naturalcycles/js-lib';
4
4
  import { BaseCommonDB } from '../../base.common.db';
5
5
  import { CommonDB } from '../../common.db';
6
- import { CommonDBOptions, RunQueryResult } from '../../db.model';
6
+ import { CommonDBOptions, DBPatch, RunQueryResult } from '../../db.model';
7
7
  import { DBQuery } from '../../query/dbQuery';
8
8
  import { DBTransaction } from '../../transaction/dbTransaction';
9
9
  import { CacheDBCfg, CacheDBCreateOptions, CacheDBOptions, CacheDBSaveOptions, CacheDBStreamOptions } from './cache.db.model';
@@ -23,12 +23,11 @@ export declare class CacheDB extends BaseCommonDB implements CommonDB {
23
23
  getTables(): Promise<string[]>;
24
24
  getTableSchema<ROW extends ObjectWithId>(table: string): Promise<JsonSchemaRootObject<ROW>>;
25
25
  createTable<ROW extends ObjectWithId>(table: string, schema: JsonSchemaObject<ROW>, opt?: CacheDBCreateOptions): Promise<void>;
26
- getByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], opt?: CacheDBSaveOptions<ROW>): Promise<ROW[]>;
27
- deleteByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], opt?: CacheDBOptions): Promise<number>;
28
26
  saveBatch<ROW extends Partial<ObjectWithId>>(table: string, rows: ROW[], opt?: CacheDBSaveOptions<ROW>): Promise<void>;
29
27
  runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CacheDBSaveOptions<ROW>): Promise<RunQueryResult<ROW>>;
30
28
  runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CacheDBOptions): Promise<number>;
31
29
  streamQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CacheDBStreamOptions): Readable;
32
30
  deleteByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CacheDBOptions): Promise<number>;
31
+ updateByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, patch: DBPatch<ROW>, opt?: CacheDBOptions): Promise<number>;
33
32
  commitTransaction(tx: DBTransaction, opt?: CommonDBOptions): Promise<void>;
34
33
  }
@@ -42,57 +42,6 @@ class CacheDB extends base_common_db_1.BaseCommonDB {
42
42
  await this.cfg.cacheDB.createTable(table, schema, opt);
43
43
  }
44
44
  }
45
- async getByIds(table, ids, opt = {}) {
46
- const resultMap = {};
47
- const missingIds = [];
48
- if (!opt.skipCache && !this.cfg.skipCache) {
49
- const results = await this.cfg.cacheDB.getByIds(table, ids, opt);
50
- results.forEach(r => (resultMap[r.id] = r));
51
- missingIds.push(...ids.filter(id => !resultMap[id]));
52
- if (this.cfg.logCached) {
53
- this.cfg.logger?.log(`${table}.getByIds ${results.length} rows from cache: [${results
54
- .map(r => r.id)
55
- .join(', ')}]`);
56
- }
57
- }
58
- if (missingIds.length && !opt.onlyCache && !this.cfg.onlyCache) {
59
- const results = await this.cfg.downstreamDB.getByIds(table, missingIds, opt);
60
- results.forEach(r => (resultMap[r.id] = r));
61
- if (this.cfg.logDownstream) {
62
- this.cfg.logger?.log(`${table}.getByIds ${results.length} rows from downstream: [${results
63
- .map(r => r.id)
64
- .join(', ')}]`);
65
- }
66
- if (!opt.skipCache) {
67
- const cacheResult = this.cfg.cacheDB.saveBatch(table, results, opt);
68
- if (this.cfg.awaitCache)
69
- await cacheResult;
70
- }
71
- }
72
- // return in right order
73
- return ids.map(id => resultMap[id]).filter(Boolean);
74
- }
75
- async deleteByIds(table, ids, opt = {}) {
76
- let deletedIds = 0;
77
- if (!opt.onlyCache && !this.cfg.onlyCache) {
78
- deletedIds = await this.cfg.downstreamDB.deleteByIds(table, ids, opt);
79
- if (this.cfg.logDownstream) {
80
- this.cfg.logger?.log(`${table}.deleteByIds ${deletedIds} rows from downstream`);
81
- }
82
- }
83
- if (!opt.skipCache && !this.cfg.skipCache) {
84
- const cacheResult = this.cfg.cacheDB
85
- .deleteByIds(table, ids, opt)
86
- .then(deletedFromCache => {
87
- if (this.cfg.logCached) {
88
- this.cfg.logger?.log(`${table}.deleteByIds ${deletedFromCache} rows from cache`);
89
- }
90
- });
91
- if (this.cfg.awaitCache)
92
- await cacheResult;
93
- }
94
- return deletedIds;
95
- }
96
45
  async saveBatch(table, rows, opt = {}) {
97
46
  if (!opt.onlyCache && !this.cfg.onlyCache) {
98
47
  await this.cfg.downstreamDB.saveBatch(table, rows, opt);
@@ -195,6 +144,18 @@ class CacheDB extends base_common_db_1.BaseCommonDB {
195
144
  }
196
145
  return deletedIds;
197
146
  }
147
+ async updateByQuery(q, patch, opt = {}) {
148
+ let updated;
149
+ if (!opt.onlyCache && !this.cfg.onlyCache) {
150
+ updated = await this.cfg.downstreamDB.updateByQuery(q, patch, opt);
151
+ }
152
+ if (!opt.skipCache && !this.cfg.skipCache) {
153
+ const cacheResult = this.cfg.cacheDB.updateByQuery(q, patch, opt);
154
+ if (this.cfg.awaitCache)
155
+ updated ??= await cacheResult;
156
+ }
157
+ return updated || 0;
158
+ }
198
159
  async commitTransaction(tx, opt) {
199
160
  await this.cfg.downstreamDB.commitTransaction(tx, opt);
200
161
  await this.cfg.cacheDB.commitTransaction(tx, opt);
@@ -21,7 +21,6 @@ export declare class FileDB extends BaseCommonDB implements CommonDB {
21
21
  cfg: FileDBCfg;
22
22
  ping(): Promise<void>;
23
23
  getTables(): Promise<string[]>;
24
- getByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], _opt?: CommonDBOptions): Promise<ROW[]>;
25
24
  saveBatch<ROW extends Partial<ObjectWithId>>(table: string, rows: ROW[], _opt?: CommonDBSaveOptions<ROW>): Promise<void>;
26
25
  /**
27
26
  * Implementation is optimized for loading/saving _whole files_.
@@ -30,7 +29,6 @@ export declare class FileDB extends BaseCommonDB implements CommonDB {
30
29
  runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<RunQueryResult<ROW>>;
31
30
  runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<number>;
32
31
  streamQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBStreamOptions): ReadableTyped<ROW>;
33
- deleteByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], _opt?: CommonDBOptions): Promise<number>;
34
32
  deleteByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<number>;
35
33
  getTableSchema<ROW extends ObjectWithId>(table: string): Promise<JsonSchemaRootObject<ROW>>;
36
34
  loadFile<ROW extends ObjectWithId>(table: string): Promise<ROW[]>;
@@ -34,10 +34,6 @@ class FileDB extends __1.BaseCommonDB {
34
34
  this.logFinished(started, `getTables() ${tables.length} tables`);
35
35
  return tables;
36
36
  }
37
- async getByIds(table, ids, _opt) {
38
- const byId = (0, js_lib_1._by)(await this.loadFile(table), r => r.id);
39
- return ids.map(id => byId[id]).filter(Boolean);
40
- }
41
37
  async saveBatch(table, rows, _opt) {
42
38
  if (!rows.length)
43
39
  return; // save some api calls
@@ -130,22 +126,6 @@ class FileDB extends __1.BaseCommonDB {
130
126
  });
131
127
  return readable;
132
128
  }
133
- async deleteByIds(table, ids, _opt) {
134
- if (!ids.length)
135
- return 0;
136
- let deleted = 0;
137
- const rows = (await this.loadFile(table)).filter(r => {
138
- if (ids.includes(r.id)) {
139
- deleted++;
140
- return false;
141
- }
142
- return true;
143
- });
144
- if (deleted > 0) {
145
- await this.saveFile(table, rows);
146
- }
147
- return deleted;
148
- }
149
129
  async deleteByQuery(q, _opt) {
150
130
  const byId = (0, js_lib_1._by)(await this.loadFile(q.table), r => r.id);
151
131
  let deleted = 0;
@@ -1,6 +1,6 @@
1
1
  import { JsonSchemaObject, StringMap, JsonSchemaRootObject, ObjectWithId, CommonLogger } from '@naturalcycles/js-lib';
2
2
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
3
- import { CommonDB, DBTransaction } from '../..';
3
+ import { CommonDB, DBPatch, DBTransaction } from '../..';
4
4
  import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions, RunQueryResult } from '../../db.model';
5
5
  import { DBQuery } from '../../query/dbQuery';
6
6
  export interface InMemoryDBCfg {
@@ -52,10 +52,9 @@ export declare class InMemoryDB implements CommonDB {
52
52
  getTables(): Promise<string[]>;
53
53
  getTableSchema<ROW extends ObjectWithId>(_table: string): Promise<JsonSchemaRootObject<ROW>>;
54
54
  createTable<ROW extends ObjectWithId>(_table: string, _schema: JsonSchemaObject<ROW>, opt?: CommonDBCreateOptions): Promise<void>;
55
- getByIds<ROW extends ObjectWithId>(_table: string, ids: ROW['id'][], _opt?: CommonDBOptions): Promise<ROW[]>;
56
55
  saveBatch<ROW extends Partial<ObjectWithId>>(_table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>): Promise<void>;
57
- deleteByIds<ROW extends ObjectWithId>(_table: string, ids: ROW['id'][], _opt?: CommonDBOptions): Promise<number>;
58
56
  deleteByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<number>;
57
+ updateByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, patch: DBPatch<ROW>): Promise<number>;
59
58
  runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<RunQueryResult<ROW>>;
60
59
  runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<number>;
61
60
  streamQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): ReadableTyped<ROW>;
@@ -8,6 +8,7 @@ const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
8
8
  const colors_1 = require("@naturalcycles/nodejs-lib/dist/colors");
9
9
  const fs = require("fs-extra");
10
10
  const __1 = require("../..");
11
+ const dbQuery_1 = require("../../query/dbQuery");
11
12
  class InMemoryDB {
12
13
  constructor(cfg) {
13
14
  // data[table][id] > {id: 'a', created: ... }
@@ -66,11 +67,6 @@ class InMemoryDB {
66
67
  this.data[table] ||= {};
67
68
  }
68
69
  }
69
- async getByIds(_table, ids, _opt) {
70
- const table = this.cfg.tablesPrefix + _table;
71
- this.data[table] ||= {};
72
- return ids.map(id => this.data[table][id]).filter(Boolean);
73
- }
74
70
  async saveBatch(_table, rows, opt = {}) {
75
71
  const table = this.cfg.tablesPrefix + _table;
76
72
  this.data[table] ||= {};
@@ -91,23 +87,35 @@ class InMemoryDB {
91
87
  this.data[table][r.id] = JSON.parse(JSON.stringify(r), nodejs_lib_1.bufferReviver);
92
88
  });
93
89
  }
94
- async deleteByIds(_table, ids, _opt) {
95
- const table = this.cfg.tablesPrefix + _table;
90
+ async deleteByQuery(q, _opt) {
91
+ const table = this.cfg.tablesPrefix + q.table;
96
92
  this.data[table] ||= {};
97
- return ids
98
- .map(id => {
99
- const exists = !!this.data[table][id];
100
- delete this.data[table][id];
101
- if (exists)
102
- return id;
103
- })
104
- .filter(Boolean).length;
93
+ let count = 0;
94
+ (0, __1.queryInMemory)(q, Object.values(this.data[table] || {})).forEach(r => {
95
+ if (!this.data[table][r.id])
96
+ return;
97
+ delete this.data[table][r.id];
98
+ count++;
99
+ });
100
+ return count;
105
101
  }
106
- async deleteByQuery(q, _opt) {
102
+ async updateByQuery(q, patch) {
103
+ const patchEntries = Object.entries(patch);
104
+ if (!patchEntries.length)
105
+ return 0;
107
106
  const table = this.cfg.tablesPrefix + q.table;
108
107
  const rows = (0, __1.queryInMemory)(q, Object.values(this.data[table] || {}));
109
- const ids = rows.map(r => r.id);
110
- return await this.deleteByIds(q.table, ids);
108
+ rows.forEach((row) => {
109
+ patchEntries.forEach(([k, v]) => {
110
+ if (v instanceof __1.DBIncrement) {
111
+ row[k] = (row[k] || 0) + v.amount;
112
+ }
113
+ else {
114
+ row[k] = v;
115
+ }
116
+ });
117
+ });
118
+ return rows.length;
111
119
  }
112
120
  async runQuery(q, _opt) {
113
121
  const table = this.cfg.tablesPrefix + q.table;
@@ -129,7 +137,10 @@ class InMemoryDB {
129
137
  await this.saveBatch(op.table, op.rows, { ...op.opt, ...opt });
130
138
  }
131
139
  else if (op.type === 'deleteByIds') {
132
- await this.deleteByIds(op.table, op.ids, { ...op.opt, ...opt });
140
+ await this.deleteByQuery(dbQuery_1.DBQuery.create(op.table).filter('id', 'in', op.ids), {
141
+ ...op.opt,
142
+ ...opt,
143
+ });
133
144
  }
134
145
  else {
135
146
  throw new Error(`DBOperation not supported: ${op.type}`);
@@ -147,9 +158,7 @@ class InMemoryDB {
147
158
  * Flushes all tables (all namespaces) at once.
148
159
  */
149
160
  async flushToDisk() {
150
- if (!this.cfg.persistenceEnabled) {
151
- throw new Error('flushToDisk() called but persistenceEnabled=false');
152
- }
161
+ (0, js_lib_1._assert)(this.cfg.persistenceEnabled, 'flushToDisk() called but persistenceEnabled=false');
153
162
  const { persistentStoragePath, persistZip } = this.cfg;
154
163
  const started = Date.now();
155
164
  await fs.emptyDir(persistentStoragePath);
@@ -175,9 +184,7 @@ class InMemoryDB {
175
184
  * Restores all tables (all namespaces) at once.
176
185
  */
177
186
  async restoreFromDisk() {
178
- if (!this.cfg.persistentStoragePath) {
179
- throw new Error('restoreFromDisk() called but persistenceEnabled=false');
180
- }
187
+ (0, js_lib_1._assert)(this.cfg.persistenceEnabled, 'restoreFromDisk() called but persistenceEnabled=false');
181
188
  const { persistentStoragePath } = this.cfg;
182
189
  const started = Date.now();
183
190
  await fs.ensureDir(persistentStoragePath);
@@ -1,7 +1,7 @@
1
1
  import { JsonSchemaObject, JsonSchemaRootObject, ObjectWithId } from '@naturalcycles/js-lib';
2
2
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
3
3
  import { CommonDB } from './common.db';
4
- import { CommonDBOptions, CommonDBSaveOptions, RunQueryResult } from './db.model';
4
+ import { CommonDBOptions, CommonDBSaveOptions, DBPatch, RunQueryResult } from './db.model';
5
5
  import { DBQuery } from './query/dbQuery';
6
6
  import { DBTransaction } from './transaction/dbTransaction';
7
7
  /**
@@ -12,14 +12,13 @@ export declare class BaseCommonDB implements CommonDB {
12
12
  ping(): Promise<void>;
13
13
  getTables(): Promise<string[]>;
14
14
  getTableSchema<ROW extends ObjectWithId>(table: string): Promise<JsonSchemaRootObject<ROW>>;
15
- createTable<ROW extends ObjectWithId>(_table: string, _schema: JsonSchemaObject<ROW>): Promise<void>;
16
- deleteByIds<ROW extends ObjectWithId>(_table: string, _ids: ROW['id'][]): Promise<number>;
17
- deleteByQuery<ROW extends ObjectWithId>(_q: DBQuery<ROW>): Promise<number>;
18
- getByIds<ROW extends ObjectWithId>(_table: string, _ids: ROW['id'][]): Promise<ROW[]>;
19
- runQuery<ROW extends ObjectWithId>(_q: DBQuery<ROW>): Promise<RunQueryResult<ROW>>;
20
- runQueryCount<ROW extends ObjectWithId>(_q: DBQuery<ROW>): Promise<number>;
21
- saveBatch<ROW extends Partial<ObjectWithId>>(_table: string, _rows: ROW[], _opt?: CommonDBSaveOptions<ROW>): Promise<void>;
22
- streamQuery<ROW extends ObjectWithId>(_q: DBQuery<ROW>): ReadableTyped<ROW>;
15
+ createTable<ROW extends ObjectWithId>(table: string, schema: JsonSchemaObject<ROW>): Promise<void>;
16
+ deleteByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>): Promise<number>;
17
+ updateByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, patch: DBPatch<ROW>, opt?: CommonDBOptions): Promise<number>;
18
+ runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>): Promise<RunQueryResult<ROW>>;
19
+ runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>): Promise<number>;
20
+ saveBatch<ROW extends Partial<ObjectWithId>>(table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>): Promise<void>;
21
+ streamQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>): ReadableTyped<ROW>;
23
22
  /**
24
23
  * Naive implementation.
25
24
  * Doesn't support rollback on error, hence doesn't pass dbTest.
@@ -1,45 +1,41 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BaseCommonDB = void 0;
4
- const node_stream_1 = require("node:stream");
5
- const dbTransaction_util_1 = require("./transaction/dbTransaction.util");
4
+ /* eslint-disable unused-imports/no-unused-vars */
6
5
  /**
7
6
  * No-op implementation of CommonDB interface.
8
7
  * To be extended by actual implementations.
9
8
  */
10
9
  class BaseCommonDB {
11
- async ping() { }
10
+ async ping() {
11
+ throw new Error('ping is not implemented');
12
+ }
12
13
  async getTables() {
13
- return [];
14
+ throw new Error('getTables is not implemented');
14
15
  }
15
16
  async getTableSchema(table) {
16
- return {
17
- $id: `${table}.schema.json`,
18
- type: 'object',
19
- additionalProperties: true,
20
- properties: {},
21
- required: [],
22
- };
17
+ throw new Error('getTableSchema is not implemented');
18
+ }
19
+ async createTable(table, schema) {
20
+ // no-op
23
21
  }
24
- async createTable(_table, _schema) { }
25
- async deleteByIds(_table, _ids) {
26
- return 0;
22
+ async deleteByQuery(q) {
23
+ throw new Error('deleteByQuery is not implemented');
27
24
  }
28
- async deleteByQuery(_q) {
29
- return 0;
25
+ async updateByQuery(q, patch, opt) {
26
+ throw new Error('updateByQuery is not implemented');
30
27
  }
31
- async getByIds(_table, _ids) {
32
- return [];
28
+ async runQuery(q) {
29
+ throw new Error('runQuery is not implemented');
33
30
  }
34
- async runQuery(_q) {
35
- return { rows: [] };
31
+ async runQueryCount(q) {
32
+ throw new Error('runQueryCount is not implemented');
36
33
  }
37
- async runQueryCount(_q) {
38
- return 0;
34
+ async saveBatch(table, rows, opt) {
35
+ throw new Error('saveBatch is not implemented');
39
36
  }
40
- async saveBatch(_table, _rows, _opt) { }
41
- streamQuery(_q) {
42
- return node_stream_1.Readable.from([]);
37
+ streamQuery(q) {
38
+ throw new Error('streamQuery is not implemented');
43
39
  }
44
40
  /**
45
41
  * Naive implementation.
@@ -47,7 +43,7 @@ class BaseCommonDB {
47
43
  * To be extended.
48
44
  */
49
45
  async commitTransaction(tx, opt) {
50
- await (0, dbTransaction_util_1.commitDBTransactionSimple)(this, tx, opt);
46
+ throw new Error('commitTransaction is not implemented');
51
47
  }
52
48
  }
53
49
  exports.BaseCommonDB = BaseCommonDB;
@@ -1,6 +1,6 @@
1
1
  import { JsonSchemaObject, JsonSchemaRootObject, ObjectWithId } from '@naturalcycles/js-lib';
2
2
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
3
- import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions, CommonDBStreamOptions, RunQueryResult } from './db.model';
3
+ import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions, CommonDBStreamOptions, DBPatch, RunQueryResult } from './db.model';
4
4
  import { DBQuery } from './query/dbQuery';
5
5
  import { DBTransaction } from './transaction/dbTransaction';
6
6
  export interface CommonDB {
@@ -27,11 +27,6 @@ export interface CommonDB {
27
27
  * Caution! dropIfExists defaults to false. If set to true - will actually DROP the table!
28
28
  */
29
29
  createTable<ROW extends ObjectWithId>(table: string, schema: JsonSchemaObject<ROW>, opt?: CommonDBCreateOptions): Promise<void>;
30
- /**
31
- * Order of items returned is not guaranteed to match order of ids.
32
- * (Such limitation exists because Datastore doesn't support it).
33
- */
34
- getByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], opt?: CommonDBOptions): Promise<ROW[]>;
35
30
  /**
36
31
  * Order by 'id' is not supported by all implementations (for example, Datastore doesn't support it).
37
32
  */
@@ -46,8 +41,26 @@ export interface CommonDB {
46
41
  * Returns number of deleted items.
47
42
  * Not supported by all implementations (e.g Datastore will always return same number as number of ids).
48
43
  */
49
- deleteByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], opt?: CommonDBOptions): Promise<number>;
50
44
  deleteByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBOptions): Promise<number>;
45
+ /**
46
+ * Applies patch to the rows returned by the query.
47
+ *
48
+ * Example:
49
+ *
50
+ * UPDATE table SET A = B where $QUERY_CONDITION
51
+ *
52
+ * patch would be { A: 'B' } for that query.
53
+ *
54
+ * Supports "increment query", example:
55
+ *
56
+ * UPDATE table SET A = A + 1
57
+ *
58
+ * In that case patch would look like:
59
+ * { A: DBIncrement(1) }
60
+ *
61
+ * Returns number of rows affected.
62
+ */
63
+ updateByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, patch: DBPatch<ROW>, opt?: CommonDBOptions): Promise<number>;
51
64
  /**
52
65
  * Should be implemented as a Transaction (best effort), which means that
53
66
  * either ALL or NONE of the operations should be applied.
@@ -1,6 +1,6 @@
1
1
  import { AnyObject, AsyncMapper, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId, Saved, Unsaved } from '@naturalcycles/js-lib';
2
2
  import { AjvSchema, ObjectSchemaTyped, ReadableTyped } from '@naturalcycles/nodejs-lib';
3
- import { DBDeleteByIdsOperation, DBModelType, DBOperation, DBSaveBatchOperation, RunQueryResult } from '../db.model';
3
+ import { DBDeleteByIdsOperation, DBModelType, DBOperation, DBPatch, DBSaveBatchOperation, RunQueryResult } from '../db.model';
4
4
  import { DBQuery, RunnableDBQuery } from '../query/dbQuery';
5
5
  import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions } from './common.dao.model';
6
6
  /**
@@ -120,6 +120,9 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
120
120
  deleteByQuery(q: DBQuery<DBM>, opt?: CommonDaoStreamForEachOptions<DBM> & {
121
121
  stream?: boolean;
122
122
  }): Promise<number>;
123
+ updateById(id: ID, patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
124
+ updateByIds(ids: ID[], patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
125
+ updateByQuery(q: DBQuery<DBM>, patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
123
126
  dbmToBM(_dbm: undefined, opt?: CommonDaoOptions): Promise<undefined>;
124
127
  dbmToBM(_dbm?: DBM, opt?: CommonDaoOptions): Promise<Saved<BM>>;
125
128
  dbmsToBM(dbms: DBM[], opt?: CommonDaoOptions): Promise<Saved<BM>[]>;
@@ -112,13 +112,15 @@ class CommonDao {
112
112
  let dbm;
113
113
  if (opt.timeout) {
114
114
  // todo: possibly remove it after debugging is done
115
- dbm = (await (0, js_lib_1.pTimeout)(() => this.cfg.db.getByIds(table, [id]), {
115
+ dbm = await (0, js_lib_1.pTimeout)(async () => {
116
+ return (await this.cfg.db.runQuery(dbQuery_1.DBQuery.create(table).filterEq('id', id))).rows[0];
117
+ }, {
116
118
  timeout: opt.timeout,
117
119
  name: `getById(${table})`,
118
- }))[0];
120
+ });
119
121
  }
120
122
  else {
121
- dbm = (await this.cfg.db.getByIds(table, [id]))[0];
123
+ dbm = (await this.cfg.db.runQuery(dbQuery_1.DBQuery.create(table).filterEq('id', id))).rows[0];
122
124
  }
123
125
  const bm = opt.raw ? dbm : await this.dbmToBM(dbm, opt);
124
126
  this.logResult(started, op, bm, table);
@@ -143,7 +145,7 @@ class CommonDao {
143
145
  const op = `getByIdAsDBM(${id})`;
144
146
  const table = opt.table || this.cfg.table;
145
147
  const started = this.logStarted(op, table);
146
- let [dbm] = await this.cfg.db.getByIds(table, [id]);
148
+ let [dbm] = (await this.cfg.db.runQuery(dbQuery_1.DBQuery.create(table).filterEq('id', id))).rows;
147
149
  if (!opt.raw) {
148
150
  dbm = this.anyToDBM(dbm, opt);
149
151
  }
@@ -156,7 +158,7 @@ class CommonDao {
156
158
  const op = `getByIdAsTM(${id})`;
157
159
  const table = opt.table || this.cfg.table;
158
160
  const started = this.logStarted(op, table);
159
- const [dbm] = await this.cfg.db.getByIds(table, [id]);
161
+ const [dbm] = (await this.cfg.db.runQuery(dbQuery_1.DBQuery.create(table).filterEq('id', id))).rows;
160
162
  if (opt.raw) {
161
163
  this.logResult(started, op, dbm, table);
162
164
  return dbm || null;
@@ -170,7 +172,7 @@ class CommonDao {
170
172
  const op = `getByIds ${ids.length} id(s) (${(0, js_lib_1._truncate)(ids.slice(0, 10).join(', '), 50)})`;
171
173
  const table = opt.table || this.cfg.table;
172
174
  const started = this.logStarted(op, table);
173
- const dbms = await this.cfg.db.getByIds(table, ids);
175
+ const dbms = (await this.cfg.db.runQuery(dbQuery_1.DBQuery.create(table).filterIn('id', ids))).rows;
174
176
  const bms = opt.raw ? dbms : await this.dbmsToBM(dbms, opt);
175
177
  this.logResult(started, op, bms, table);
176
178
  return bms;
@@ -179,7 +181,7 @@ class CommonDao {
179
181
  const op = `getByIdsAsDBM ${ids.length} id(s) (${(0, js_lib_1._truncate)(ids.slice(0, 10).join(', '), 50)})`;
180
182
  const table = opt.table || this.cfg.table;
181
183
  const started = this.logStarted(op, table);
182
- const dbms = await this.cfg.db.getByIds(table, ids);
184
+ const dbms = (await this.cfg.db.runQuery(dbQuery_1.DBQuery.create(table).filterIn('id', ids))).rows;
183
185
  this.logResult(started, op, dbms, table);
184
186
  return dbms;
185
187
  }
@@ -229,7 +231,8 @@ class CommonDao {
229
231
  }
230
232
  async ensureUniqueId(table, dbm) {
231
233
  // todo: retry N times
232
- const existing = await this.cfg.db.getByIds(table, [dbm.id]);
234
+ const existing = (await this.cfg.db.runQuery(dbQuery_1.DBQuery.create(table).filterEq('id', dbm.id)))
235
+ .rows;
233
236
  if (existing.length) {
234
237
  throw new js_lib_1.AppError(cnst_1.DBLibError.NON_UNIQUE_ID, {
235
238
  table,
@@ -656,9 +659,9 @@ class CommonDao {
656
659
  const op = `deleteById(${id})`;
657
660
  const table = opt.table || this.cfg.table;
658
661
  const started = this.logStarted(op, table);
659
- const ids = await this.cfg.db.deleteByIds(table, [id]);
662
+ const count = await this.cfg.db.deleteByQuery(dbQuery_1.DBQuery.create(table).filterEq('id', id));
660
663
  this.logSaveResult(started, op, table);
661
- return ids;
664
+ return count;
662
665
  }
663
666
  async deleteByIds(ids, opt = {}) {
664
667
  this.requireWriteAccess();
@@ -666,9 +669,9 @@ class CommonDao {
666
669
  const op = `deleteByIds(${ids.join(', ')})`;
667
670
  const table = opt.table || this.cfg.table;
668
671
  const started = this.logStarted(op, table);
669
- const deletedIds = await this.cfg.db.deleteByIds(table, ids);
672
+ const count = await this.cfg.db.deleteByQuery(dbQuery_1.DBQuery.create(table).filterIn('id', ids));
670
673
  this.logSaveResult(started, op, table);
671
- return deletedIds;
674
+ return count;
672
675
  }
673
676
  /**
674
677
  * Pass `stream: true` option to use Streaming: it will Stream the query, batch by 500, and execute
@@ -691,7 +694,7 @@ class CommonDao {
691
694
  }),
692
695
  (0, nodejs_lib_1.transformBuffer)({ batchSize }),
693
696
  (0, nodejs_lib_1.transformMap)(async (ids) => {
694
- deleted += await this.cfg.db.deleteByIds(q.table, ids, opt);
697
+ deleted += await this.cfg.db.deleteByQuery(dbQuery_1.DBQuery.create(q.table).filterIn('id', ids), opt);
695
698
  }, {
696
699
  predicate: js_lib_1._passthroughPredicate,
697
700
  }),
@@ -711,6 +714,22 @@ class CommonDao {
711
714
  this.logSaveResult(started, op, q.table);
712
715
  return deleted;
713
716
  }
717
+ async updateById(id, patch, opt = {}) {
718
+ return await this.updateByQuery(this.query().filterEq('id', id), patch, opt);
719
+ }
720
+ async updateByIds(ids, patch, opt = {}) {
721
+ return await this.updateByQuery(this.query().filterIn('id', ids), patch, opt);
722
+ }
723
+ async updateByQuery(q, patch, opt = {}) {
724
+ this.requireWriteAccess();
725
+ this.requireObjectMutability(opt);
726
+ q.table = opt.table || q.table;
727
+ const op = `updateByQuery(${q.pretty()})`;
728
+ const started = this.logStarted(op, q.table);
729
+ const updated = await this.cfg.db.updateByQuery(q, patch, opt);
730
+ this.logSaveResult(started, op, q.table);
731
+ return updated;
732
+ }
714
733
  async dbmToBM(_dbm, opt = {}) {
715
734
  if (!_dbm)
716
735
  return;
@@ -62,3 +62,16 @@ export declare enum DBModelType {
62
62
  BM = "BM",
63
63
  TM = "TM"
64
64
  }
65
+ /**
66
+ * Allows to construct a query similar to:
67
+ *
68
+ * UPDATE table SET A = A + 1
69
+ *
70
+ * In this case DBIncement.of(1) will be needed.
71
+ */
72
+ export declare class DBIncrement {
73
+ amount: number;
74
+ private constructor();
75
+ static of(amount: number): DBIncrement;
76
+ }
77
+ export type DBPatch<ROW extends Partial<ObjectWithId>> = Partial<Record<keyof ROW, ROW[keyof ROW] | DBIncrement>>;
package/dist/db.model.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DBModelType = exports.DBRelation = void 0;
3
+ exports.DBIncrement = exports.DBModelType = exports.DBRelation = void 0;
4
4
  var DBRelation;
5
5
  (function (DBRelation) {
6
6
  DBRelation["ONE_TO_ONE"] = "ONE_TO_ONE";
@@ -12,3 +12,19 @@ var DBModelType;
12
12
  DBModelType["BM"] = "BM";
13
13
  DBModelType["TM"] = "TM";
14
14
  })(DBModelType = exports.DBModelType || (exports.DBModelType = {}));
15
+ /**
16
+ * Allows to construct a query similar to:
17
+ *
18
+ * UPDATE table SET A = A + 1
19
+ *
20
+ * In this case DBIncement.of(1) will be needed.
21
+ */
22
+ class DBIncrement {
23
+ constructor(amount) {
24
+ this.amount = amount;
25
+ }
26
+ static of(amount) {
27
+ return new DBIncrement(amount);
28
+ }
29
+ }
30
+ exports.DBIncrement = DBIncrement;
@@ -1,6 +1,6 @@
1
1
  import { AnyObjectWithId, ObjectWithId, AsyncMapper, Saved, AnyObject } from '@naturalcycles/js-lib';
2
2
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
3
- import { CommonDaoOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions } from '..';
3
+ import { CommonDaoOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, DBPatch } from '..';
4
4
  import { CommonDao } from '../commondao/common.dao';
5
5
  import { RunQueryResult } from '../db.model';
6
6
  /**
@@ -68,6 +68,7 @@ export declare class DBQuery<ROW extends ObjectWithId = AnyObjectWithId> {
68
68
  _distinct: boolean;
69
69
  filter(name: keyof ROW, op: DBQueryFilterOperator, val: any): this;
70
70
  filterEq(name: keyof ROW, val: any): this;
71
+ filterIn(name: keyof ROW, val: any[]): this;
71
72
  limit(limit: number): this;
72
73
  offset(offset: number): this;
73
74
  order(name: keyof ROW, descending?: boolean): this;
@@ -97,6 +98,7 @@ export declare class RunnableDBQuery<BM extends Partial<ObjectWithId<ID>>, DBM e
97
98
  runQueryExtendedAsDBM(opt?: CommonDaoOptions): Promise<RunQueryResult<DBM>>;
98
99
  runQueryExtendedAsTM(opt?: CommonDaoOptions): Promise<RunQueryResult<TM>>;
99
100
  runQueryCount(opt?: CommonDaoOptions): Promise<number>;
101
+ updateByQuery(patch: DBPatch<DBM>, opt?: CommonDaoOptions): Promise<number>;
100
102
  streamQueryForEach(mapper: AsyncMapper<Saved<BM>, void>, opt?: CommonDaoStreamForEachOptions<Saved<BM>>): Promise<void>;
101
103
  streamQueryAsDBMForEach(mapper: AsyncMapper<DBM, void>, opt?: CommonDaoStreamForEachOptions<DBM>): Promise<void>;
102
104
  streamQuery(opt?: CommonDaoStreamOptions): ReadableTyped<Saved<BM>>;