@naturalcycles/mysql-lib 3.10.0 → 3.11.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.
@@ -1,7 +1,7 @@
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
7
  export interface MysqlDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId> extends CommonDBSaveOptions<ROW> {
@@ -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 & {
@@ -44,6 +43,11 @@ export declare class MysqlDB extends BaseCommonDB implements CommonDB {
44
43
  getByIds<ROW extends ObjectWithId>(table: string, ids: string[], 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
53
  saveBatch<ROW extends ObjectWithId>(table: string, _rows: ROW[], _opt?: MysqlDBSaveOptions<ROW>): Promise<void>;
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;
@@ -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": "^14.1.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.0",
40
40
  "description": "MySQL client implementing CommonDB interface",
41
41
  "keywords": [
42
42
  "mysql",
package/src/mysql.db.ts CHANGED
@@ -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)) {
@@ -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,
@@ -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>> = {