@naturalcycles/mysql-lib 3.8.0 → 3.9.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
- import { BaseCommonDB, BaseDBEntity, CommonDB, CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions, DBQuery, ObjectWithId, RunQueryResult } from '@naturalcycles/db-lib';
2
- import { JsonSchemaObject, JsonSchemaRootObject } from '@naturalcycles/js-lib';
1
+ import { BaseCommonDB, CommonDB, CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions, DBQuery, RunQueryResult } from '@naturalcycles/db-lib';
2
+ import { AnyObjectWithId, CommonLogger, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId } from '@naturalcycles/js-lib';
3
3
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
4
4
  import { Connection, Pool, PoolConfig, PoolConnection, QueryOptions } from 'mysql';
5
5
  export interface MysqlDBOptions extends CommonDBOptions {
6
6
  }
7
- export interface MysqlDBSaveOptions extends CommonDBSaveOptions {
7
+ export interface MysqlDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId> extends CommonDBSaveOptions<ROW> {
8
8
  }
9
9
  /**
10
10
  * @default false / undefined
@@ -18,10 +18,16 @@ export interface MysqlDBCfg extends PoolConfig {
18
18
  * If true - will emit logs of connection events.
19
19
  */
20
20
  debugConnections?: boolean;
21
+ /**
22
+ * Default to `console`
23
+ */
24
+ logger?: CommonLogger;
21
25
  }
22
26
  export declare class MysqlDB extends BaseCommonDB implements CommonDB {
23
27
  constructor(cfg?: MysqlDBCfg);
24
- cfg: MysqlDBCfg;
28
+ cfg: MysqlDBCfg & {
29
+ logger: CommonLogger;
30
+ };
25
31
  ping(): Promise<void>;
26
32
  pool(): Pool;
27
33
  close(): Promise<void>;
@@ -39,7 +45,7 @@ export declare class MysqlDB extends BaseCommonDB implements CommonDB {
39
45
  runSQL<RESULT>(q: QueryOptions): Promise<RESULT>;
40
46
  runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<number>;
41
47
  streamQuery<ROW extends ObjectWithId, OUT = ROW>(q: DBQuery<ROW>, _opt?: MysqlDBOptions): ReadableTyped<OUT>;
42
- saveBatch<ROW extends BaseDBEntity>(table: string, _rows: ROW[], _opt?: MysqlDBSaveOptions): Promise<void>;
48
+ saveBatch<ROW extends ObjectWithId>(table: string, _rows: ROW[], _opt?: MysqlDBSaveOptions<ROW>): Promise<void>;
43
49
  /**
44
50
  * Limitation: always returns [], regardless of which rows are actually deleted
45
51
  */
@@ -52,7 +58,7 @@ export declare class MysqlDB extends BaseCommonDB implements CommonDB {
52
58
  /**
53
59
  * dropIfExists=true needed as a safety check
54
60
  */
55
- createTable(table: string, schema: JsonSchemaObject, opt?: CommonDBCreateOptions): Promise<void>;
61
+ createTable<ROW extends ObjectWithId>(table: string, schema: JsonSchemaObject<ROW>, opt?: CommonDBCreateOptions): Promise<void>;
56
62
  getTables(): Promise<string[]>;
57
63
  getTableSchema<ROW extends ObjectWithId>(table: string): Promise<JsonSchemaRootObject<ROW>>;
58
64
  }
package/dist/mysql.db.js CHANGED
@@ -6,12 +6,10 @@ const stream_1 = require("stream");
6
6
  const util_1 = require("util");
7
7
  const db_lib_1 = require("@naturalcycles/db-lib");
8
8
  const js_lib_1 = require("@naturalcycles/js-lib");
9
- const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
10
9
  const colors_1 = require("@naturalcycles/nodejs-lib/dist/colors");
11
10
  const mysql = require("mysql");
12
11
  const query_util_1 = require("./query.util");
13
12
  const mysql_schema_util_1 = require("./schema/mysql.schema.util");
14
- const log = (0, nodejs_lib_1.Debug)('nc:mysql-lib');
15
13
  const BOOLEAN_TYPES = new Set(['TINY', 'TINYINT', 'INT']);
16
14
  const BOOLEAN_BIT_TYPES = new Set(['BIT']);
17
15
  const typeCast = (field, next) => {
@@ -44,6 +42,7 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
44
42
  // password: MYSQL_PW,
45
43
  // database: MYSQL_DB,
46
44
  ...cfg,
45
+ logger: (0, js_lib_1.commonLoggerPrefix)(cfg.logger || console, '[mysql]'),
47
46
  };
48
47
  }
49
48
  async ping() {
@@ -52,31 +51,32 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
52
51
  }
53
52
  pool() {
54
53
  const pool = mysql.createPool(this.cfg);
55
- const { host, database } = this.cfg;
56
- log(`connected to ${(0, colors_1.white)(host + '/' + database)}`);
54
+ const { host, database, logger } = this.cfg;
55
+ logger.log(`connected to ${(0, colors_1.white)(host + '/' + database)}`);
57
56
  if (this.cfg.debugConnections) {
58
57
  pool.on('acquire', con => {
59
- log(`acquire(${con.threadId})`);
58
+ logger.log(`acquire(${con.threadId})`);
60
59
  });
61
60
  pool.on('connection', con => {
62
- log(`connection(${con.threadId})`);
61
+ logger.log(`connection(${con.threadId})`);
63
62
  });
64
63
  pool.on('enqueue', () => {
65
- log(`enqueue`);
64
+ logger.log(`enqueue`);
66
65
  });
67
66
  pool.on('release', con => {
68
- log(`release(${con.threadId})`);
67
+ logger.log(`release(${con.threadId})`);
69
68
  });
70
69
  }
71
70
  pool.on('error', err => {
72
- log.error(`error`, err);
71
+ logger.error(err);
73
72
  });
74
73
  return pool;
75
74
  }
76
75
  async close() {
77
76
  const pool = this.pool();
78
77
  // eslint-disable-next-line @typescript-eslint/await-thenable
79
- await (0, util_1.promisify)(pool.end.bind(pool));
78
+ await (0, util_1.promisify)(pool.end.bind(pool))();
79
+ this.cfg.logger.log('closed');
80
80
  }
81
81
  /**
82
82
  * Be careful to always call `con.release()` when you get connection with this method.
@@ -94,14 +94,14 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
94
94
  await (0, util_1.promisify)(con.connect.bind(con))();
95
95
  const { threadId } = con;
96
96
  if (this.cfg.debugConnections) {
97
- log(`createSingleConnection(${threadId})`);
98
- con.on('connect', () => log(`createSingleConnection(${threadId}).connect`));
99
- con.on('drain', () => log(`createSingleConnection(${threadId}).drain`));
100
- con.on('enqueue', () => log(`createSingleConnection(${threadId}).enqueue`));
101
- con.on('end', () => log(`createSingleConnection(${threadId}).end`));
97
+ this.cfg.logger.log(`createSingleConnection(${threadId})`);
98
+ con.on('connect', () => this.cfg.logger.log(`createSingleConnection(${threadId}).connect`));
99
+ con.on('drain', () => this.cfg.logger.log(`createSingleConnection(${threadId}).drain`));
100
+ con.on('enqueue', () => this.cfg.logger.log(`createSingleConnection(${threadId}).enqueue`));
101
+ con.on('end', () => this.cfg.logger.log(`createSingleConnection(${threadId}).end`));
102
102
  }
103
103
  con.on('error', err => {
104
- log.error(`createSingleConnection(${threadId}).error`, err);
104
+ this.cfg.logger.error(`createSingleConnection(${threadId}).error`, err);
105
105
  });
106
106
  return con;
107
107
  }
@@ -116,14 +116,20 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
116
116
  // QUERY
117
117
  async runQuery(q, _opt = {}) {
118
118
  const sql = (0, query_util_1.dbQueryToSQLSelect)(q);
119
- const rows = await this.runSQL({ sql });
119
+ const rows = (await this.runSQL({ sql })).map(row => (0, js_lib_1._mapKeys)((0, js_lib_1._filterUndefinedValues)(row, true), k => (0, mysql_schema_util_1.mapNameFromMySQL)(k)));
120
+ // edge case where 0 fields are selected
121
+ if (q._selectedFieldNames?.length === 0) {
122
+ return {
123
+ rows: rows.map(_ => ({})),
124
+ };
125
+ }
120
126
  return {
121
- rows: rows.map(r => (0, js_lib_1._mapKeys)((0, js_lib_1._filterNullishValues)(r, true), k => (0, mysql_schema_util_1.mapNameFromMySQL)(k))),
127
+ rows,
122
128
  };
123
129
  }
124
130
  async runSQL(q) {
125
131
  if (this.cfg.logSQL)
126
- log(q.sql, q.values);
132
+ this.cfg.logger.log(...[q.sql, q.values].filter(Boolean));
127
133
  return await new Promise((resolve, reject) => {
128
134
  this.pool().query(q, (err, res) => {
129
135
  if (err)
@@ -139,7 +145,7 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
139
145
  streamQuery(q, _opt = {}) {
140
146
  const sql = (0, query_util_1.dbQueryToSQLSelect)(q);
141
147
  if (this.cfg.logSQL)
142
- log(`stream: ${sql}`);
148
+ this.cfg.logger.log(`stream: ${sql}`);
143
149
  // return this.streamSQL(sql, opt)
144
150
  return this.pool()
145
151
  .query(sql)
@@ -147,7 +153,7 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
147
153
  .pipe(new stream_1.Transform({
148
154
  objectMode: true,
149
155
  transform(row, _encoding, cb) {
150
- cb(null, (0, js_lib_1._filterNullishValues)(row, true));
156
+ cb(null, (0, js_lib_1._filterUndefinedValues)(row, true));
151
157
  },
152
158
  }));
153
159
  }
@@ -156,11 +162,11 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
156
162
  if (!_rows.length)
157
163
  return;
158
164
  // Stringify object values
159
- const rows = _rows.map(row => (0, js_lib_1._mapValues)(row, v => {
165
+ const rows = _rows.map(row => (0, js_lib_1._mapValues)(row, (_k, v) => {
160
166
  return v && typeof v === 'object' && !Buffer.isBuffer(v) ? JSON.stringify(v) : v;
161
167
  }));
162
168
  // inserts are split into multiple sentenses to respect the max_packet_size (1Mb usually)
163
- const sqls = (0, query_util_1.insertSQL)(table, rows);
169
+ const sqls = (0, query_util_1.insertSQL)(table, rows, 'REPLACE', this.cfg.logger);
164
170
  for await (const sql of sqls) {
165
171
  await this.runSQL({ sql });
166
172
  }
@@ -205,7 +211,7 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
205
211
  const stats = await this.runSQL({
206
212
  sql: `describe ${mysql.escapeId(table)}`,
207
213
  });
208
- return (0, mysql_schema_util_1.mysqlTableStatsToJsonSchemaField)(table, stats);
214
+ return (0, mysql_schema_util_1.mysqlTableStatsToJsonSchemaField)(table, stats, this.cfg.logger);
209
215
  }
210
216
  }
211
217
  (0, tslib_1.__decorate)([
@@ -1,11 +1,12 @@
1
1
  import { DBQuery } from '@naturalcycles/db-lib';
2
+ import { CommonLogger } from '@naturalcycles/js-lib';
2
3
  import { QueryOptions } from 'mysql';
3
4
  export declare function dbQueryToSQLSelect(q: DBQuery<any>): string;
4
5
  export declare function dbQueryToSQLDelete(q: DBQuery<any>): string;
5
6
  /**
6
7
  * Returns array of sql statements to respect the max sql size.
7
8
  */
8
- export declare function insertSQL(table: string, records: Record<any, any>[], verb?: 'INSERT' | 'REPLACE'): string[];
9
+ export declare function insertSQL(table: string, rows: Record<any, any>[], verb?: 'INSERT' | 'REPLACE', logger?: CommonLogger): string[];
9
10
  export declare function insertSQLSingle(table: string, record: Record<any, any>): string;
10
11
  export declare function insertSQLSetSingle(table: string, record: Record<any, any>): QueryOptions;
11
12
  export declare function dbQueryToSQLUpdate(q: DBQuery<any>, record: Record<any, any>): string;
@@ -30,12 +30,12 @@ exports.dbQueryToSQLDelete = dbQueryToSQLDelete;
30
30
  /**
31
31
  * Returns array of sql statements to respect the max sql size.
32
32
  */
33
- function insertSQL(table, records, verb = 'INSERT') {
33
+ function insertSQL(table, rows, verb = 'INSERT', logger = console) {
34
34
  // INSERT INTO table_name (column1, column2, column3, ...)
35
35
  // VALUES (value1, value2, value3, ...);
36
36
  // eslint-disable-next-line unicorn/no-array-reduce
37
- const fieldSet = records.reduce((set, rec) => {
38
- Object.keys(rec).forEach(field => set.add(field));
37
+ const fieldSet = rows.reduce((set, row) => {
38
+ Object.keys(row).forEach(field => set.add(field));
39
39
  return set;
40
40
  }, new Set());
41
41
  const fields = [...fieldSet];
@@ -46,7 +46,7 @@ function insertSQL(table, records, verb = 'INSERT') {
46
46
  `(` + [...fields].map(f => mysql.escapeId((0, mysql_schema_util_1.mapNameToMySQL)(f))).join(',') + `)`,
47
47
  `VALUES\n`,
48
48
  ].join(' ');
49
- const valueRows = records.map(rec => {
49
+ const valueRows = rows.map(rec => {
50
50
  return `(` + fields.map(k => mysql.escape(rec[k])).join(',') + `)`;
51
51
  });
52
52
  const full = start + valueRows.join(',\n');
@@ -71,7 +71,7 @@ function insertSQL(table, records, verb = 'INSERT') {
71
71
  if (sql) {
72
72
  sqls.push(sql); // last one
73
73
  }
74
- console.log(`${(0, colors_1.white)(table)} large sql query (${(0, colors_1.yellow)((0, js_lib_1._hb)(full.length))}) was split into ${(0, colors_1.yellow)(sqls.length)} chunks`);
74
+ logger.log(`${(0, colors_1.white)(table)} large sql query (${(0, colors_1.yellow)((0, js_lib_1._hb)(full.length))}) was split into ${(0, colors_1.yellow)(sqls.length)} chunks`);
75
75
  return sqls;
76
76
  // todo: handle "upsert" later
77
77
  }
@@ -1,4 +1,4 @@
1
- import { JsonSchemaObject, JsonSchemaRootObject } from '@naturalcycles/js-lib';
1
+ import { CommonLogger, JsonSchemaObject, JsonSchemaRootObject } from '@naturalcycles/js-lib';
2
2
  export interface MySQLTableStats {
3
3
  Field: string;
4
4
  Type: string;
@@ -14,7 +14,7 @@ export interface MySQLSchemaOptions {
14
14
  * It currently skips nullability and declares everything as "DEFAULT NULL".
15
15
  */
16
16
  export declare function jsonSchemaToMySQLDDL(table: string, schema: JsonSchemaObject<any>, opt?: MySQLSchemaOptions): string;
17
- export declare function mysqlTableStatsToJsonSchemaField<T = any>(table: string, stats: MySQLTableStats[]): JsonSchemaRootObject<T>;
17
+ export declare function mysqlTableStatsToJsonSchemaField<T = any>(table: string, stats: MySQLTableStats[], logger: CommonLogger): JsonSchemaRootObject<T>;
18
18
  /**
19
19
  * Because MySQL doesn't support `.` in field names and escapes them as tableName + fieldName.
20
20
  *
@@ -56,7 +56,7 @@ function jsonSchemaToMySQLDDL(table, schema, opt = {}) {
56
56
  return lines.join('\n');
57
57
  }
58
58
  exports.jsonSchemaToMySQLDDL = jsonSchemaToMySQLDDL;
59
- function mysqlTableStatsToJsonSchemaField(table, stats) {
59
+ function mysqlTableStatsToJsonSchemaField(table, stats, logger) {
60
60
  const s = {
61
61
  $id: `${table}.schema.json`,
62
62
  type: 'object',
@@ -80,14 +80,14 @@ function mysqlTableStatsToJsonSchemaField(table, stats) {
80
80
  else if (t.startsWith('tinyint') || t.includes('(1)')) {
81
81
  s.properties[name] = { type: 'boolean' };
82
82
  }
83
- else if (t.startsWith('int(')) {
83
+ else if (t === 'int' || t.startsWith('int(')) {
84
84
  s.properties[name] = { type: 'integer' };
85
85
  }
86
86
  else if (t.startsWith('float')) {
87
87
  s.properties[name] = { type: 'number' };
88
88
  }
89
89
  else {
90
- console.log(s);
90
+ logger.log(s);
91
91
  throw new Error(`Unknown mysql field type ${name} ${stat.Type}`);
92
92
  }
93
93
  });
package/package.json CHANGED
@@ -3,9 +3,6 @@
3
3
  "scripts": {
4
4
  "prepare": "husky install"
5
5
  },
6
- "peerDependencies": {
7
- "@naturalcycles/db-lib": ">=7.0.0"
8
- },
9
6
  "dependencies": {
10
7
  "@naturalcycles/db-lib": "^8.16.2",
11
8
  "@naturalcycles/js-lib": "^14.47.0",
@@ -15,7 +12,7 @@
15
12
  },
16
13
  "devDependencies": {
17
14
  "@naturalcycles/dev-lib": "^12.7.5",
18
- "@types/node": "^16.10.2",
15
+ "@types/node": "^17.0.5",
19
16
  "dotenv": "^10.0.0",
20
17
  "jest": "^27.2.4"
21
18
  },
@@ -39,7 +36,7 @@
39
36
  "engines": {
40
37
  "node": ">=14.15.0"
41
38
  },
42
- "version": "3.8.0",
39
+ "version": "3.9.2",
43
40
  "description": "MySQL client implementing CommonDB interface",
44
41
  "keywords": [
45
42
  "mysql",
package/readme.md CHANGED
@@ -3,12 +3,33 @@
3
3
  > MySQL client implementing CommonDB interface
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@naturalcycles/mysql-lib/latest.svg)](https://www.npmjs.com/package/@naturalcycles/mysql-lib)
6
- [![](https://circleci.com/gh/NaturalCycles/mysql-lib.svg?style=shield&circle-token=123)](https://circleci.com/gh/NaturalCycles/mysql-lib)
7
6
  [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
8
7
 
9
- # Packaging
8
+ # Quick start
10
9
 
11
- - `engines.node >= 10.13`: Latest Node.js LTS
12
- - `main: dist/index.js`: commonjs, es2018
13
- - `types: dist/index.d.ts`: typescript types
14
- - `/src` folder with source `*.ts` files included
10
+ todo
11
+
12
+ # Develop
13
+
14
+ Install MySQL server+client on Mac:
15
+
16
+ ```shell
17
+
18
+ brew install mysql
19
+
20
+ # start
21
+
22
+ mysql.server start
23
+
24
+ # secure
25
+
26
+ mysql_secure_installation
27
+
28
+ # connect
29
+
30
+ mysql -uroot
31
+
32
+ # Default options are read from the following files in the given order:
33
+ # /etc/my.cnf /etc/mysql/my.cnf /opt/homebrew/etc/my.cnf ~/.my.cnf
34
+
35
+ ```
package/src/mysql.db.ts CHANGED
@@ -2,24 +2,26 @@ import { Transform } from 'stream'
2
2
  import { promisify } from 'util'
3
3
  import {
4
4
  BaseCommonDB,
5
- BaseDBEntity,
6
5
  CommonDB,
7
6
  CommonDBCreateOptions,
8
7
  CommonDBOptions,
9
8
  CommonDBSaveOptions,
10
9
  DBQuery,
11
- ObjectWithId,
12
10
  RunQueryResult,
13
11
  } from '@naturalcycles/db-lib'
14
12
  import {
15
- _filterNullishValues,
13
+ _filterUndefinedValues,
16
14
  _mapKeys,
17
15
  _mapValues,
18
16
  _Memo,
17
+ AnyObjectWithId,
18
+ CommonLogger,
19
+ commonLoggerPrefix,
19
20
  JsonSchemaObject,
20
21
  JsonSchemaRootObject,
22
+ ObjectWithId,
21
23
  } from '@naturalcycles/js-lib'
22
- import { Debug, ReadableTyped } from '@naturalcycles/nodejs-lib'
24
+ import { ReadableTyped } from '@naturalcycles/nodejs-lib'
23
25
  import { white } from '@naturalcycles/nodejs-lib/dist/colors'
24
26
  import { Connection, Pool, PoolConfig, PoolConnection, QueryOptions, TypeCast } from 'mysql'
25
27
  import * as mysql from 'mysql'
@@ -32,7 +34,8 @@ import {
32
34
  } from './schema/mysql.schema.util'
33
35
 
34
36
  export interface MysqlDBOptions extends CommonDBOptions {}
35
- export interface MysqlDBSaveOptions extends CommonDBSaveOptions {}
37
+ export interface MysqlDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId>
38
+ extends CommonDBSaveOptions<ROW> {}
36
39
 
37
40
  /**
38
41
  * @default false / undefined
@@ -47,9 +50,12 @@ export interface MysqlDBCfg extends PoolConfig {
47
50
  * If true - will emit logs of connection events.
48
51
  */
49
52
  debugConnections?: boolean
50
- }
51
53
 
52
- const log = Debug('nc:mysql-lib')
54
+ /**
55
+ * Default to `console`
56
+ */
57
+ logger?: CommonLogger
58
+ }
53
59
 
54
60
  const BOOLEAN_TYPES = new Set(['TINY', 'TINYINT', 'INT'])
55
61
  const BOOLEAN_BIT_TYPES = new Set(['BIT'])
@@ -85,10 +91,11 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
85
91
  // password: MYSQL_PW,
86
92
  // database: MYSQL_DB,
87
93
  ...cfg,
94
+ logger: commonLoggerPrefix(cfg.logger || console, '[mysql]'),
88
95
  }
89
96
  }
90
97
 
91
- cfg!: MysqlDBCfg
98
+ cfg: MysqlDBCfg & { logger: CommonLogger }
92
99
 
93
100
  override async ping(): Promise<void> {
94
101
  const con = await this.getConnection()
@@ -98,29 +105,29 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
98
105
  @_Memo()
99
106
  pool(): Pool {
100
107
  const pool = mysql.createPool(this.cfg)
101
- const { host, database } = this.cfg
102
- log(`connected to ${white(host + '/' + database)}`)
108
+ const { host, database, logger } = this.cfg
109
+ logger.log(`connected to ${white(host + '/' + database)}`)
103
110
 
104
111
  if (this.cfg.debugConnections) {
105
112
  pool.on('acquire', con => {
106
- log(`acquire(${con.threadId})`)
113
+ logger.log(`acquire(${con.threadId})`)
107
114
  })
108
115
 
109
116
  pool.on('connection', con => {
110
- log(`connection(${con.threadId})`)
117
+ logger.log(`connection(${con.threadId})`)
111
118
  })
112
119
 
113
120
  pool.on('enqueue', () => {
114
- log(`enqueue`)
121
+ logger.log(`enqueue`)
115
122
  })
116
123
 
117
124
  pool.on('release', con => {
118
- log(`release(${con.threadId})`)
125
+ logger.log(`release(${con.threadId})`)
119
126
  })
120
127
  }
121
128
 
122
129
  pool.on('error', err => {
123
- log.error(`error`, err)
130
+ logger.error(err)
124
131
  })
125
132
 
126
133
  return pool
@@ -129,7 +136,8 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
129
136
  async close(): Promise<void> {
130
137
  const pool = this.pool()
131
138
  // eslint-disable-next-line @typescript-eslint/await-thenable
132
- await promisify(pool.end.bind(pool))
139
+ await promisify(pool.end.bind(pool))()
140
+ this.cfg.logger.log('closed')
133
141
  }
134
142
 
135
143
  /**
@@ -150,16 +158,16 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
150
158
  const { threadId } = con
151
159
 
152
160
  if (this.cfg.debugConnections) {
153
- log(`createSingleConnection(${threadId})`)
161
+ this.cfg.logger.log(`createSingleConnection(${threadId})`)
154
162
 
155
- con.on('connect', () => log(`createSingleConnection(${threadId}).connect`))
156
- con.on('drain', () => log(`createSingleConnection(${threadId}).drain`))
157
- con.on('enqueue', () => log(`createSingleConnection(${threadId}).enqueue`))
158
- con.on('end', () => log(`createSingleConnection(${threadId}).end`))
163
+ con.on('connect', () => this.cfg.logger.log(`createSingleConnection(${threadId}).connect`))
164
+ con.on('drain', () => this.cfg.logger.log(`createSingleConnection(${threadId}).drain`))
165
+ con.on('enqueue', () => this.cfg.logger.log(`createSingleConnection(${threadId}).enqueue`))
166
+ con.on('end', () => this.cfg.logger.log(`createSingleConnection(${threadId}).end`))
159
167
  }
160
168
 
161
169
  con.on('error', err => {
162
- log.error(`createSingleConnection(${threadId}).error`, err)
170
+ this.cfg.logger.error(`createSingleConnection(${threadId}).error`, err)
163
171
  })
164
172
 
165
173
  return con
@@ -183,14 +191,24 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
183
191
  _opt: MysqlDBOptions = {},
184
192
  ): Promise<RunQueryResult<OUT>> {
185
193
  const sql = dbQueryToSQLSelect(q)
186
- const rows = await this.runSQL<ROW[]>({ sql })
194
+ const rows = (await this.runSQL<ROW[]>({ sql })).map(
195
+ row => _mapKeys(_filterUndefinedValues(row, true), k => mapNameFromMySQL(k)) as any,
196
+ )
197
+
198
+ // edge case where 0 fields are selected
199
+ if (q._selectedFieldNames?.length === 0) {
200
+ return {
201
+ rows: rows.map(_ => ({} as any)),
202
+ }
203
+ }
204
+
187
205
  return {
188
- rows: rows.map(r => _mapKeys(_filterNullishValues(r, true), k => mapNameFromMySQL(k)) as any),
206
+ rows,
189
207
  }
190
208
  }
191
209
 
192
210
  async runSQL<RESULT>(q: QueryOptions): Promise<RESULT> {
193
- if (this.cfg.logSQL) log(q.sql, q.values)
211
+ if (this.cfg.logSQL) this.cfg.logger.log(...[q.sql, q.values].filter(Boolean))
194
212
 
195
213
  return await new Promise<RESULT>((resolve, reject) => {
196
214
  this.pool().query(q, (err, res) => {
@@ -204,7 +222,7 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
204
222
  q: DBQuery<ROW>,
205
223
  _opt?: CommonDBOptions,
206
224
  ): Promise<number> {
207
- const { rows } = await this.runQuery(q.select(['count(*) as _count']))
225
+ const { rows } = await this.runQuery(q.select(['count(*) as _count' as any]))
208
226
  return (rows[0] as any)._count
209
227
  }
210
228
 
@@ -214,7 +232,7 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
214
232
  ): ReadableTyped<OUT> {
215
233
  const sql = dbQueryToSQLSelect(q)
216
234
 
217
- if (this.cfg.logSQL) log(`stream: ${sql}`)
235
+ if (this.cfg.logSQL) this.cfg.logger.log(`stream: ${sql}`)
218
236
 
219
237
  // return this.streamSQL(sql, opt)
220
238
  return this.pool()
@@ -224,29 +242,29 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
224
242
  new Transform({
225
243
  objectMode: true,
226
244
  transform(row, _encoding, cb) {
227
- cb(null, _filterNullishValues(row, true))
245
+ cb(null, _filterUndefinedValues(row, true))
228
246
  },
229
247
  }),
230
248
  )
231
249
  }
232
250
 
233
251
  // SAVE
234
- override async saveBatch<ROW extends BaseDBEntity>(
252
+ override async saveBatch<ROW extends ObjectWithId>(
235
253
  table: string,
236
254
  _rows: ROW[],
237
- _opt?: MysqlDBSaveOptions,
255
+ _opt?: MysqlDBSaveOptions<ROW>,
238
256
  ): Promise<void> {
239
257
  if (!_rows.length) return
240
258
 
241
259
  // Stringify object values
242
260
  const rows = _rows.map(row =>
243
- _mapValues(row, v => {
261
+ _mapValues(row, (_k, v) => {
244
262
  return v && typeof v === 'object' && !Buffer.isBuffer(v) ? JSON.stringify(v) : v
245
263
  }),
246
264
  )
247
265
 
248
266
  // inserts are split into multiple sentenses to respect the max_packet_size (1Mb usually)
249
- const sqls = insertSQL(table, rows)
267
+ const sqls = insertSQL(table, rows, 'REPLACE', this.cfg.logger)
250
268
 
251
269
  for await (const sql of sqls) {
252
270
  await this.runSQL({ sql })
@@ -283,9 +301,9 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
283
301
  /**
284
302
  * dropIfExists=true needed as a safety check
285
303
  */
286
- override async createTable(
304
+ override async createTable<ROW extends ObjectWithId>(
287
305
  table: string,
288
- schema: JsonSchemaObject,
306
+ schema: JsonSchemaObject<ROW>,
289
307
  opt: CommonDBCreateOptions = {},
290
308
  ): Promise<void> {
291
309
  if (opt.dropIfExists) await this.dropTable(table)
@@ -307,6 +325,6 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
307
325
  sql: `describe ${mysql.escapeId(table)}`,
308
326
  })
309
327
 
310
- return mysqlTableStatsToJsonSchemaField<ROW>(table, stats)
328
+ return mysqlTableStatsToJsonSchemaField<ROW>(table, stats, this.cfg.logger)
311
329
  }
312
330
  }
package/src/query.util.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DBQuery } from '@naturalcycles/db-lib'
2
2
  import { DBQueryFilterOperator } from '@naturalcycles/db-lib/src/query/dbQuery'
3
- import { _hb } from '@naturalcycles/js-lib'
3
+ import { _hb, CommonLogger } from '@naturalcycles/js-lib'
4
4
  import { white, yellow } from '@naturalcycles/nodejs-lib/dist/colors'
5
5
  import { QueryOptions } from 'mysql'
6
6
  import * as mysql from 'mysql'
@@ -41,15 +41,16 @@ export function dbQueryToSQLDelete(q: DBQuery<any>): string {
41
41
  */
42
42
  export function insertSQL(
43
43
  table: string,
44
- records: Record<any, any>[],
44
+ rows: Record<any, any>[],
45
45
  verb: 'INSERT' | 'REPLACE' = 'INSERT',
46
+ logger: CommonLogger = console,
46
47
  ): string[] {
47
48
  // INSERT INTO table_name (column1, column2, column3, ...)
48
49
  // VALUES (value1, value2, value3, ...);
49
50
 
50
51
  // eslint-disable-next-line unicorn/no-array-reduce
51
- const fieldSet = records.reduce((set: Set<string>, rec) => {
52
- Object.keys(rec).forEach(field => set.add(field))
52
+ const fieldSet = rows.reduce((set: Set<string>, row) => {
53
+ Object.keys(row).forEach(field => set.add(field))
53
54
  return set
54
55
  }, new Set<string>())
55
56
  const fields = [...fieldSet]
@@ -62,7 +63,7 @@ export function insertSQL(
62
63
  `VALUES\n`,
63
64
  ].join(' ')
64
65
 
65
- const valueRows = records.map(rec => {
66
+ const valueRows = rows.map(rec => {
66
67
  return `(` + fields.map(k => mysql.escape(rec[k])).join(',') + `)`
67
68
  })
68
69
 
@@ -90,7 +91,7 @@ export function insertSQL(
90
91
  sqls.push(sql) // last one
91
92
  }
92
93
 
93
- console.log(
94
+ logger.log(
94
95
  `${white(table)} large sql query (${yellow(_hb(full.length))}) was split into ${yellow(
95
96
  sqls.length,
96
97
  )} chunks`,
@@ -146,11 +147,11 @@ export function dbQueryToSQLUpdate(q: DBQuery<any>, record: Record<any, any>): s
146
147
  return mysql.format(tokens.join(' '), Object.values(record))
147
148
  }
148
149
 
149
- function selectTokens(q: DBQuery<any>): string[] {
150
+ function selectTokens(q: DBQuery): string[] {
150
151
  let fields = ['*']
151
152
 
152
153
  if (q._selectedFieldNames) {
153
- fields = q._selectedFieldNames.length ? q._selectedFieldNames : ['id']
154
+ fields = q._selectedFieldNames.length ? (q._selectedFieldNames as string[]) : ['id']
154
155
  }
155
156
 
156
157
  // We don't do `escapeId` cause it'll ruin e.g SELECT `count *` FROM ...
@@ -172,7 +173,7 @@ function offsetLimitTokens(q: DBQuery<any>): string[] {
172
173
  return tokens
173
174
  }
174
175
 
175
- function orderTokens(q: DBQuery<any>): string[] {
176
+ function orderTokens(q: DBQuery): string[] {
176
177
  if (!q._orders.length) return []
177
178
  return [
178
179
  `ORDER BY`,
@@ -184,7 +185,7 @@ const OP_MAP: Partial<Record<DBQueryFilterOperator, string>> = {
184
185
  '==': '=',
185
186
  }
186
187
 
187
- function whereTokens(q: DBQuery<any>): string[] {
188
+ function whereTokens(q: DBQuery): string[] {
188
189
  if (!q._filters.length) return []
189
190
 
190
191
  return [
@@ -195,18 +196,18 @@ function whereTokens(q: DBQuery<any>): string[] {
195
196
  // special treatment
196
197
 
197
198
  return [
198
- mysql.escapeId(mapNameToMySQL(f.name)),
199
+ mysql.escapeId(mapNameToMySQL(f.name as string)),
199
200
  f.op === '==' ? 'IS NULL' : 'IS NOT NULL',
200
201
  ].join(' ')
201
202
  }
202
203
 
203
204
  if (Array.isArray(f.val)) {
204
205
  // special case for arrays
205
- return `${mysql.escapeId(mapNameToMySQL(f.name))} IN (${mysql.escape(f.val)})`
206
+ return `${mysql.escapeId(mapNameToMySQL(f.name as string))} IN (${mysql.escape(f.val)})`
206
207
  }
207
208
 
208
209
  return [
209
- mysql.escapeId(mapNameToMySQL(f.name)),
210
+ mysql.escapeId(mapNameToMySQL(f.name as string)),
210
211
  OP_MAP[f.op] || f.op,
211
212
  mysql.escape(f.val),
212
213
  ].join(' ')
@@ -1,4 +1,5 @@
1
1
  import {
2
+ CommonLogger,
2
3
  JsonSchemaBoolean,
3
4
  JsonSchemaNumber,
4
5
  JsonSchemaObject,
@@ -81,6 +82,7 @@ export function jsonSchemaToMySQLDDL(
81
82
  export function mysqlTableStatsToJsonSchemaField<T = any>(
82
83
  table: string,
83
84
  stats: MySQLTableStats[],
85
+ logger: CommonLogger,
84
86
  ): JsonSchemaRootObject<T> {
85
87
  const s: JsonSchemaRootObject<T> = {
86
88
  $id: `${table}.schema.json`,
@@ -104,12 +106,12 @@ export function mysqlTableStatsToJsonSchemaField<T = any>(
104
106
  s.properties[name] = { instanceof: 'Buffer' }
105
107
  } else if (t.startsWith('tinyint') || t.includes('(1)')) {
106
108
  s.properties[name] = { type: 'boolean' } as JsonSchemaBoolean
107
- } else if (t.startsWith('int(')) {
109
+ } else if (t === 'int' || t.startsWith('int(')) {
108
110
  s.properties[name] = { type: 'integer' } as JsonSchemaNumber
109
111
  } else if (t.startsWith('float')) {
110
112
  s.properties[name] = { type: 'number' } as JsonSchemaNumber
111
113
  } else {
112
- console.log(s)
114
+ logger.log(s)
113
115
  throw new Error(`Unknown mysql field type ${name} ${stat.Type}`)
114
116
  }
115
117
  })
package/CHANGELOG.md DELETED
@@ -1,198 +0,0 @@
1
- # [3.8.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.7.1...v3.8.0) (2021-10-04)
2
-
3
-
4
- ### Features
5
-
6
- * modernize ([6e8f0a2](https://github.com/NaturalCycles/mysql-lib/commit/6e8f0a254a67902cbc185f034f58cd4f6885449d))
7
-
8
- ## [3.7.1](https://github.com/NaturalCycles/mysql-lib/compare/v3.7.0...v3.7.1) (2020-06-03)
9
-
10
-
11
- ### Bug Fixes
12
-
13
- * modernize ([517f695](https://github.com/NaturalCycles/mysql-lib/commit/517f6952feb15059730f4f7443a295bf596053fc))
14
-
15
- # [3.7.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.6.0...v3.7.0) (2020-04-08)
16
-
17
-
18
- ### Features
19
-
20
- * modernize ([e0e2890](https://github.com/NaturalCycles/mysql-lib/commit/e0e2890cb31e88fcbdae63c417d6cc5c6a52d4e8))
21
-
22
- # [3.6.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.5.2...v3.6.0) (2020-03-01)
23
-
24
-
25
- ### Features
26
-
27
- * deps ([008aa10](https://github.com/NaturalCycles/mysql-lib/commit/008aa10d059734d76c2354553a8dc35c8117b3b2))
28
-
29
- ## [3.5.2](https://github.com/NaturalCycles/mysql-lib/compare/v3.5.1...v3.5.2) (2019-11-20)
30
-
31
-
32
- ### Bug Fixes
33
-
34
- * BIT typecast to boolean correctly ([0bc815e](https://github.com/NaturalCycles/mysql-lib/commit/0bc815e025a33ab9fae5405c8b97dd53aae392ca))
35
-
36
- ## [3.5.1](https://github.com/NaturalCycles/mysql-lib/compare/v3.5.0...v3.5.1) (2019-11-20)
37
-
38
-
39
- ### Bug Fixes
40
-
41
- * correctly type-cast booleans with value=NULL ([e208b4c](https://github.com/NaturalCycles/mysql-lib/commit/e208b4ca16f700b6ed7bf95c872f68f934894992))
42
-
43
- # [3.5.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.4.0...v3.5.0) (2019-11-09)
44
-
45
-
46
- ### Features
47
-
48
- * implement getDBAdapter. Allow empty (default) configuration ([bcf12dc](https://github.com/NaturalCycles/mysql-lib/commit/bcf12dc9360f525749f9bb10965046b4c56d849a))
49
-
50
- # [3.4.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.3.1...v3.4.0) (2019-11-09)
51
-
52
-
53
- ### Features
54
-
55
- * auto JSON.stringify objects (and arrays) ([0d9aaed](https://github.com/NaturalCycles/mysql-lib/commit/0d9aaedf8f2920a51dacf13b6723eb98c61b34c3))
56
-
57
- ## [3.3.1](https://github.com/NaturalCycles/mysql-lib/compare/v3.3.0...v3.3.1) (2019-11-09)
58
-
59
-
60
- ### Bug Fixes
61
-
62
- * use LONGBLOB for binary columns ([ae4facc](https://github.com/NaturalCycles/mysql-lib/commit/ae4facc76bc03b8aafa5141199a98bef530f6913))
63
-
64
- # [3.3.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.2.1...v3.3.0) (2019-11-09)
65
-
66
-
67
- ### Features
68
-
69
- * mapNameToMySQL ([c4bf40c](https://github.com/NaturalCycles/mysql-lib/commit/c4bf40cd6aefaf78219a2588ec803cf74004bc07))
70
-
71
- ## [3.2.1](https://github.com/NaturalCycles/mysql-lib/compare/v3.2.0...v3.2.1) (2019-11-09)
72
-
73
-
74
- ### Bug Fixes
75
-
76
- * createTable() ([7983085](https://github.com/NaturalCycles/mysql-lib/commit/7983085017562a4e4a8f7dd38eb41ae6b9fc30ed))
77
-
78
- # [3.2.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.1.0...v3.2.0) (2019-11-09)
79
-
80
-
81
- ### Bug Fixes
82
-
83
- * emoji support ([1262fc4](https://github.com/NaturalCycles/mysql-lib/commit/1262fc4d9ad39c818833b5cfc31b3a65979cdea0))
84
-
85
-
86
- ### Features
87
-
88
- * auto split long sql queries ([c7c780f](https://github.com/NaturalCycles/mysql-lib/commit/c7c780f295d7ded9f418509f3160a512ec54c7e7))
89
-
90
- # [3.1.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.0.0...v3.1.0) (2019-11-08)
91
-
92
-
93
- ### Features
94
-
95
- * commonSchemaToMySQLDDL ([09168c5](https://github.com/NaturalCycles/mysql-lib/commit/09168c563cbb629ca47121a1aac6a125dd23c144))
96
- * implement getTables(), getTableSchema() ([20a7746](https://github.com/NaturalCycles/mysql-lib/commit/20a7746c8c4c88dfca8365d1e8d2decce3f47efa))
97
-
98
- # [3.0.0](https://github.com/NaturalCycles/mysql-lib/compare/v2.0.2...v3.0.0) (2019-11-02)
99
-
100
-
101
- ### Features
102
-
103
- * adapt to db-lib@3 ([7d13f54](https://github.com/NaturalCycles/mysql-lib/commit/7d13f540c92dd59b014d1a502c1bff937fad567f))
104
-
105
-
106
- ### BREAKING CHANGES
107
-
108
- * ^^^
109
-
110
- ## [2.0.2](https://github.com/NaturalCycles/mysql-lib/compare/v2.0.1...v2.0.2) (2019-10-20)
111
-
112
-
113
- ### Bug Fixes
114
-
115
- * adopt to db-lib ([6da7d78](https://github.com/NaturalCycles/mysql-lib/commit/6da7d78))
116
-
117
- ## [2.0.1](https://github.com/NaturalCycles/mysql-lib/compare/v2.0.0...v2.0.1) (2019-10-19)
118
-
119
-
120
- ### Bug Fixes
121
-
122
- * use Readable ([a0d09c1](https://github.com/NaturalCycles/mysql-lib/commit/a0d09c1))
123
-
124
- # [2.0.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.7.1...v2.0.0) (2019-10-18)
125
-
126
-
127
- ### Features
128
-
129
- * implement CommonDB 2.0 ([9de0e9f](https://github.com/NaturalCycles/mysql-lib/commit/9de0e9f))
130
-
131
-
132
- ### BREAKING CHANGES
133
-
134
- * ^^^
135
-
136
- ## [1.7.1](https://github.com/NaturalCycles/mysql-lib/compare/v1.7.0...v1.7.1) (2019-10-18)
137
-
138
-
139
- ### Bug Fixes
140
-
141
- * pin @types/hapi__joi ([3e3cf5a](https://github.com/NaturalCycles/mysql-lib/commit/3e3cf5a))
142
-
143
- # [1.7.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.6.0...v1.7.0) (2019-09-30)
144
-
145
-
146
- ### Features
147
-
148
- * tiny change to use pool.query inst of getConnection() ([6ffe1b4](https://github.com/NaturalCycles/mysql-lib/commit/6ffe1b4))
149
-
150
- # [1.6.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.5.0...v1.6.0) (2019-09-30)
151
-
152
-
153
- ### Features
154
-
155
- * offset ([78710a1](https://github.com/NaturalCycles/mysql-lib/commit/78710a1))
156
-
157
- # [1.5.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.4.0...v1.5.0) (2019-09-30)
158
-
159
-
160
- ### Features
161
-
162
- * testing simpler streamSQL impl ([0887aa3](https://github.com/NaturalCycles/mysql-lib/commit/0887aa3))
163
-
164
- # [1.4.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.3.0...v1.4.0) (2019-09-30)
165
-
166
-
167
- ### Features
168
-
169
- * stream to use it's own separate connection ([43615f3](https://github.com/NaturalCycles/mysql-lib/commit/43615f3))
170
-
171
- # [1.3.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.2.0...v1.3.0) (2019-09-30)
172
-
173
-
174
- ### Features
175
-
176
- * cfg.debugConnections ([70218ba](https://github.com/NaturalCycles/mysql-lib/commit/70218ba))
177
-
178
- # [1.2.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.1.0...v1.2.0) (2019-09-28)
179
-
180
-
181
- ### Features
182
-
183
- * add BIT cast to boolean ([899cce4](https://github.com/NaturalCycles/mysql-lib/commit/899cce4))
184
-
185
- # [1.1.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.0.0...v1.1.0) (2019-09-21)
186
-
187
-
188
- ### Features
189
-
190
- * modernize, implement saveBatch ([5595b6a](https://github.com/NaturalCycles/mysql-lib/commit/5595b6a))
191
-
192
- # 1.0.0 (2019-06-03)
193
-
194
-
195
- ### Features
196
-
197
- * first version ([02620fb](https://github.com/NaturalCycles/mysql-lib/commit/02620fb))
198
- * init project by create-module ([eee50b7](https://github.com/NaturalCycles/mysql-lib/commit/eee50b7))