@naturalcycles/mysql-lib 3.10.0 → 3.11.2

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.
@@ -1,10 +1,10 @@
1
1
  import { BaseCommonDB, CommonDB, CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions, DBQuery, RunQueryResult } from '@naturalcycles/db-lib';
2
2
  import { AnyObjectWithId, CommonLogger, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId } from '@naturalcycles/js-lib';
3
3
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
4
- import { Connection, Pool, PoolConfig, PoolConnection, QueryOptions, TypeCast } from 'mysql';
4
+ import { Connection, Pool, PoolConfig, PoolConnection, QueryOptions } from 'mysql';
5
5
  export interface MysqlDBOptions extends CommonDBOptions {
6
6
  }
7
- export interface MysqlDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId> extends CommonDBSaveOptions<ROW> {
7
+ export interface MysqlDBSaveOptions<ROW extends Partial<ObjectWithId> = AnyObjectWithId> extends CommonDBSaveOptions<ROW> {
8
8
  }
9
9
  /**
10
10
  * @default false / undefined
@@ -23,7 +23,6 @@ export interface MysqlDBCfg extends PoolConfig {
23
23
  */
24
24
  logger?: CommonLogger;
25
25
  }
26
- export declare const typeCast: TypeCast;
27
26
  export declare class MysqlDB extends BaseCommonDB implements CommonDB {
28
27
  constructor(cfg?: MysqlDBCfg);
29
28
  cfg: MysqlDBCfg & {
@@ -41,16 +40,21 @@ export declare class MysqlDB extends BaseCommonDB implements CommonDB {
41
40
  * Be careful to manage this connection yourself (open, release, etc).
42
41
  */
43
42
  createSingleConnection(): Promise<Connection>;
44
- getByIds<ROW extends ObjectWithId>(table: string, ids: string[], opt?: MysqlDBOptions): Promise<ROW[]>;
43
+ getByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], opt?: MysqlDBOptions): Promise<ROW[]>;
45
44
  runQuery<ROW extends ObjectWithId, OUT = ROW>(q: DBQuery<ROW>, _opt?: MysqlDBOptions): Promise<RunQueryResult<OUT>>;
46
45
  runSQL<RESULT>(q: QueryOptions): Promise<RESULT>;
46
+ /**
47
+ * Allows to run semicolon-separated "SQL file".
48
+ * E.g "ddl reset script".
49
+ */
50
+ runSQLString(s: string): Promise<void>;
47
51
  runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<number>;
48
52
  streamQuery<ROW extends ObjectWithId, OUT = ROW>(q: DBQuery<ROW>, _opt?: MysqlDBOptions): ReadableTyped<OUT>;
49
- saveBatch<ROW extends ObjectWithId>(table: string, _rows: ROW[], _opt?: MysqlDBSaveOptions<ROW>): Promise<void>;
53
+ saveBatch<ROW extends Partial<ObjectWithId>>(table: string, _rows: ROW[], opt?: MysqlDBSaveOptions<ROW>): Promise<void>;
50
54
  /**
51
55
  * Limitation: always returns [], regardless of which rows are actually deleted
52
56
  */
53
- deleteByIds(table: string, ids: string[], _opt?: MysqlDBOptions): Promise<number>;
57
+ deleteByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], _opt?: MysqlDBOptions): Promise<number>;
54
58
  deleteByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<number>;
55
59
  /**
56
60
  * Use with caution!
package/dist/mysql.db.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MysqlDB = exports.typeCast = void 0;
3
+ exports.MysqlDB = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const stream_1 = require("stream");
6
6
  const util_1 = require("util");
@@ -31,12 +31,11 @@ const typeCast = (field, next) => {
31
31
  }
32
32
  return next();
33
33
  };
34
- exports.typeCast = typeCast;
35
34
  class MysqlDB extends db_lib_1.BaseCommonDB {
36
35
  constructor(cfg = {}) {
37
36
  super();
38
37
  this.cfg = {
39
- typeCast: exports.typeCast,
38
+ typeCast,
40
39
  // charset: 'utf8mb4', // for emoji support
41
40
  // host: 'localhost',
42
41
  // user: MYSQL_USER,
@@ -139,6 +138,19 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
139
138
  });
140
139
  });
141
140
  }
141
+ /**
142
+ * Allows to run semicolon-separated "SQL file".
143
+ * E.g "ddl reset script".
144
+ */
145
+ async runSQLString(s) {
146
+ const queries = s
147
+ .split(';')
148
+ .map(s => s.trim())
149
+ .filter(Boolean);
150
+ for await (const sql of queries) {
151
+ await this.runSQL({ sql });
152
+ }
153
+ }
142
154
  async runQueryCount(q, _opt) {
143
155
  const { rows } = await this.runQuery(q.select(['count(*) as _count']));
144
156
  return rows[0]._count;
@@ -159,7 +171,7 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
159
171
  }));
160
172
  }
161
173
  // SAVE
162
- async saveBatch(table, _rows, _opt) {
174
+ async saveBatch(table, _rows, opt = {}) {
163
175
  if (!_rows.length)
164
176
  return;
165
177
  // Stringify object values
@@ -167,7 +179,7 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
167
179
  return v && typeof v === 'object' && !Buffer.isBuffer(v) ? JSON.stringify(v) : v;
168
180
  }));
169
181
  // inserts are split into multiple sentenses to respect the max_packet_size (1Mb usually)
170
- const sqls = (0, query_util_1.insertSQL)(table, rows, 'REPLACE', this.cfg.logger);
182
+ const sqls = (0, query_util_1.insertSQL)(table, rows, opt.saveMethod === 'insert' ? 'INSERT' : 'REPLACE', this.cfg.logger);
171
183
  for await (const sql of sqls) {
172
184
  await this.runSQL({ sql });
173
185
  }
@@ -215,7 +227,7 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
215
227
  return (0, mysql_schema_util_1.mysqlTableStatsToJsonSchemaField)(table, stats, this.cfg.logger);
216
228
  }
217
229
  }
218
- (0, tslib_1.__decorate)([
230
+ tslib_1.__decorate([
219
231
  (0, js_lib_1._Memo)()
220
232
  ], MysqlDB.prototype, "pool", null);
221
233
  exports.MysqlDB = MysqlDB;
@@ -3,8 +3,8 @@ import { CommonDBCreateOptions, CommonKeyValueDB, KeyValueDBTuple } from '@natur
3
3
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
4
4
  import { MysqlDB, MysqlDBCfg } from './mysql.db';
5
5
  export declare class MySQLKeyValueDB implements CommonKeyValueDB {
6
- constructor(cfg?: MysqlDBCfg);
7
6
  cfg: MysqlDBCfg;
7
+ constructor(cfg: MysqlDBCfg);
8
8
  db: MysqlDB;
9
9
  ping(): Promise<void>;
10
10
  close(): Promise<void>;
@@ -5,11 +5,8 @@ const stream_1 = require("stream");
5
5
  const js_lib_1 = require("@naturalcycles/js-lib");
6
6
  const mysql_db_1 = require("./mysql.db");
7
7
  class MySQLKeyValueDB {
8
- constructor(cfg = {}) {
9
- this.cfg = {
10
- typeCast: mysql_db_1.typeCast,
11
- ...cfg,
12
- };
8
+ constructor(cfg) {
9
+ this.cfg = cfg;
13
10
  this.db = new mysql_db_1.MysqlDB(this.cfg);
14
11
  }
15
12
  async ping() {
@@ -12,7 +12,7 @@ function dbQueryToSQLSelect(q) {
12
12
  // filters
13
13
  tokens.push(...whereTokens(q));
14
14
  // order
15
- tokens.push(...orderTokens(q));
15
+ tokens.push(...groupOrderTokens(q));
16
16
  // offset/limit
17
17
  tokens.push(...offsetLimitTokens(q));
18
18
  return tokens.join(' ');
@@ -124,7 +124,13 @@ function selectTokens(q) {
124
124
  fields = q._selectedFieldNames.length ? q._selectedFieldNames : ['id'];
125
125
  }
126
126
  // We don't do `escapeId` cause it'll ruin e.g SELECT `count *` FROM ...
127
- return [`SELECT`, fields.map(f => (0, mysql_schema_util_1.mapNameToMySQL)(f)).join(', '), `FROM`, mysql.escapeId(q.table)];
127
+ return [
128
+ `SELECT`,
129
+ q._distinct && 'DISTINCT',
130
+ fields.map(f => (0, mysql_schema_util_1.mapNameToMySQL)(f)).join(', '),
131
+ `FROM`,
132
+ mysql.escapeId(q.table),
133
+ ].filter(Boolean);
128
134
  }
129
135
  function offsetLimitTokens(q) {
130
136
  const tokens = [];
@@ -137,13 +143,15 @@ function offsetLimitTokens(q) {
137
143
  }
138
144
  return tokens;
139
145
  }
140
- function orderTokens(q) {
141
- if (!q._orders.length)
142
- return [];
143
- return [
144
- `ORDER BY`,
145
- q._orders.map(o => `\`${o.name}\` ${o.descending ? 'DESC' : 'ASC'}`).join(', '),
146
- ];
146
+ function groupOrderTokens(q) {
147
+ const t = [];
148
+ if (q._groupByFieldNames?.length) {
149
+ t.push(`GROUP BY`, q._groupByFieldNames.map(c => `\`${c}\``).join(', '));
150
+ }
151
+ if (q._orders.length) {
152
+ t.push(`ORDER BY`, q._orders.map(o => `\`${o.name}\` ${o.descending ? 'DESC' : 'ASC'}`).join(', '));
153
+ }
154
+ return t;
147
155
  }
148
156
  const OP_MAP = {
149
157
  '==': '=',
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "devDependencies": {
14
14
  "@naturalcycles/dev-lib": "^12.7.5",
15
15
  "@types/node": "^17.0.5",
16
- "dotenv": "^10.0.0",
16
+ "dotenv": "^16.0.0",
17
17
  "jest": "^27.2.4"
18
18
  },
19
19
  "files": [
@@ -36,7 +36,7 @@
36
36
  "engines": {
37
37
  "node": ">=14.15.0"
38
38
  },
39
- "version": "3.10.0",
39
+ "version": "3.11.2",
40
40
  "description": "MySQL client implementing CommonDB interface",
41
41
  "keywords": [
42
42
  "mysql",
package/src/mysql.db.ts CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  } from './schema/mysql.schema.util'
35
35
 
36
36
  export interface MysqlDBOptions extends CommonDBOptions {}
37
- export interface MysqlDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId>
37
+ export interface MysqlDBSaveOptions<ROW extends Partial<ObjectWithId> = AnyObjectWithId>
38
38
  extends CommonDBSaveOptions<ROW> {}
39
39
 
40
40
  /**
@@ -60,7 +60,7 @@ export interface MysqlDBCfg extends PoolConfig {
60
60
  const BOOLEAN_TYPES = new Set(['TINY', 'TINYINT', 'INT'])
61
61
  const BOOLEAN_BIT_TYPES = new Set(['BIT'])
62
62
 
63
- export const typeCast: TypeCast = (field, next) => {
63
+ const typeCast: TypeCast = (field, next) => {
64
64
  // cast to boolean
65
65
  if (field.length === 1) {
66
66
  if (BOOLEAN_BIT_TYPES.has(field.type)) {
@@ -176,7 +176,7 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
176
176
  // GET
177
177
  override async getByIds<ROW extends ObjectWithId>(
178
178
  table: string,
179
- ids: string[],
179
+ ids: ROW['id'][],
180
180
  opt: MysqlDBOptions = {},
181
181
  ): Promise<ROW[]> {
182
182
  if (!ids.length) return []
@@ -218,6 +218,21 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
218
218
  })
219
219
  }
220
220
 
221
+ /**
222
+ * Allows to run semicolon-separated "SQL file".
223
+ * E.g "ddl reset script".
224
+ */
225
+ async runSQLString(s: string): Promise<void> {
226
+ const queries = s
227
+ .split(';')
228
+ .map(s => s.trim())
229
+ .filter(Boolean)
230
+
231
+ for await (const sql of queries) {
232
+ await this.runSQL({ sql })
233
+ }
234
+ }
235
+
221
236
  override async runQueryCount<ROW extends ObjectWithId>(
222
237
  q: DBQuery<ROW>,
223
238
  _opt?: CommonDBOptions,
@@ -249,10 +264,10 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
249
264
  }
250
265
 
251
266
  // SAVE
252
- override async saveBatch<ROW extends ObjectWithId>(
267
+ override async saveBatch<ROW extends Partial<ObjectWithId>>(
253
268
  table: string,
254
269
  _rows: ROW[],
255
- _opt?: MysqlDBSaveOptions<ROW>,
270
+ opt: MysqlDBSaveOptions<ROW> = {},
256
271
  ): Promise<void> {
257
272
  if (!_rows.length) return
258
273
 
@@ -264,7 +279,12 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
264
279
  )
265
280
 
266
281
  // inserts are split into multiple sentenses to respect the max_packet_size (1Mb usually)
267
- const sqls = insertSQL(table, rows, 'REPLACE', this.cfg.logger)
282
+ const sqls = insertSQL(
283
+ table,
284
+ rows,
285
+ opt.saveMethod === 'insert' ? 'INSERT' : 'REPLACE',
286
+ this.cfg.logger,
287
+ )
268
288
 
269
289
  for await (const sql of sqls) {
270
290
  await this.runSQL({ sql })
@@ -275,7 +295,11 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
275
295
  /**
276
296
  * Limitation: always returns [], regardless of which rows are actually deleted
277
297
  */
278
- override async deleteByIds(table: string, ids: string[], _opt?: MysqlDBOptions): Promise<number> {
298
+ override async deleteByIds<ROW extends ObjectWithId>(
299
+ table: string,
300
+ ids: ROW['id'][],
301
+ _opt?: MysqlDBOptions,
302
+ ): Promise<number> {
279
303
  if (!ids.length) return 0
280
304
  const sql = dbQueryToSQLDelete(new DBQuery(table).filterEq('id', ids))
281
305
  const res = await this.runSQL<any>({ sql })
@@ -3,7 +3,7 @@ import { CommonDBCreateOptions, CommonKeyValueDB, KeyValueDBTuple } from '@natur
3
3
  import { pMap } from '@naturalcycles/js-lib'
4
4
  import { ReadableTyped } from '@naturalcycles/nodejs-lib'
5
5
  import { QueryOptions } from 'mysql'
6
- import { MysqlDB, MysqlDBCfg, typeCast } from './mysql.db'
6
+ import { MysqlDB, MysqlDBCfg } from './mysql.db'
7
7
 
8
8
  interface KeyValueObject {
9
9
  id: string
@@ -11,17 +11,10 @@ interface KeyValueObject {
11
11
  }
12
12
 
13
13
  export class MySQLKeyValueDB implements CommonKeyValueDB {
14
- constructor(cfg: MysqlDBCfg = {}) {
15
- this.cfg = {
16
- typeCast,
17
- ...cfg,
18
- }
19
-
14
+ constructor(public cfg: MysqlDBCfg) {
20
15
  this.db = new MysqlDB(this.cfg)
21
16
  }
22
17
 
23
- cfg: MysqlDBCfg
24
-
25
18
  db: MysqlDB
26
19
 
27
20
  async ping(): Promise<void> {
package/src/query.util.ts CHANGED
@@ -16,7 +16,7 @@ export function dbQueryToSQLSelect(q: DBQuery<any>): string {
16
16
  tokens.push(...whereTokens(q))
17
17
 
18
18
  // order
19
- tokens.push(...orderTokens(q))
19
+ tokens.push(...groupOrderTokens(q))
20
20
 
21
21
  // offset/limit
22
22
  tokens.push(...offsetLimitTokens(q))
@@ -155,7 +155,13 @@ function selectTokens(q: DBQuery): string[] {
155
155
  }
156
156
 
157
157
  // We don't do `escapeId` cause it'll ruin e.g SELECT `count *` FROM ...
158
- return [`SELECT`, fields.map(f => mapNameToMySQL(f)).join(', '), `FROM`, mysql.escapeId(q.table)]
158
+ return [
159
+ `SELECT`,
160
+ q._distinct && ('DISTINCT' as any),
161
+ fields.map(f => mapNameToMySQL(f)).join(', '),
162
+ `FROM`,
163
+ mysql.escapeId(q.table),
164
+ ].filter(Boolean)
159
165
  }
160
166
 
161
167
  function offsetLimitTokens(q: DBQuery<any>): string[] {
@@ -173,12 +179,21 @@ function offsetLimitTokens(q: DBQuery<any>): string[] {
173
179
  return tokens
174
180
  }
175
181
 
176
- function orderTokens(q: DBQuery): string[] {
177
- if (!q._orders.length) return []
178
- return [
179
- `ORDER BY`,
180
- q._orders.map(o => `\`${o.name}\` ${o.descending ? 'DESC' : 'ASC'}`).join(', '),
181
- ]
182
+ function groupOrderTokens(q: DBQuery): string[] {
183
+ const t: string[] = []
184
+
185
+ if (q._groupByFieldNames?.length) {
186
+ t.push(`GROUP BY`, q._groupByFieldNames.map(c => `\`${c}\``).join(', '))
187
+ }
188
+
189
+ if (q._orders.length) {
190
+ t.push(
191
+ `ORDER BY`,
192
+ q._orders.map(o => `\`${o.name}\` ${o.descending ? 'DESC' : 'ASC'}`).join(', '),
193
+ )
194
+ }
195
+
196
+ return t
182
197
  }
183
198
 
184
199
  const OP_MAP: Partial<Record<DBQueryFilterOperator, string>> = {