@naturalcycles/mysql-lib 3.7.1 → 3.9.1

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,78 +1,102 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.mapNameFromMySQL = exports.mapNameToMySQL = exports.mysqlTableStatsToCommonSchemaField = exports.commonSchemaToMySQLDDL = void 0;
4
- const db_lib_1 = require("@naturalcycles/db-lib");
5
- const js_lib_1 = require("@naturalcycles/js-lib");
3
+ exports.mapNameFromMySQL = exports.mapNameToMySQL = exports.mysqlTableStatsToJsonSchemaField = exports.jsonSchemaToMySQLDDL = void 0;
6
4
  const mysql = require("mysql");
7
5
  /**
8
6
  * It currently skips nullability and declares everything as "DEFAULT NULL".
9
7
  */
10
- function commonSchemaToMySQLDDL(schema, opt = {}) {
8
+ function jsonSchemaToMySQLDDL(table, schema, opt = {}) {
11
9
  const { engine = 'InnoDB' } = opt;
12
- const { table, fields } = schema;
13
10
  const lines = [`CREATE TABLE ${mysql.escapeId(table)} (`];
14
- const innerLines = fields.map(f => commonSchemaFieldToDDL(f));
11
+ const innerLines = Object.entries(schema.properties).map(([k, s]) => {
12
+ if (k === 'id') {
13
+ return `id VARCHAR(255) NOT NULL`;
14
+ }
15
+ let type;
16
+ if (s.type === 'string') {
17
+ // can specify isoDate later
18
+ type = 'LONGTEXT';
19
+ }
20
+ else if (s.type === 'integer') {
21
+ type = 'INT(11)';
22
+ }
23
+ else if (s.type === 'number') {
24
+ if (['unixTimestamp', 'int32'].includes(s.format)) {
25
+ type = 'INT(11)';
26
+ }
27
+ else {
28
+ type = 'FLOAT(11)';
29
+ }
30
+ }
31
+ else if (s.type === 'boolean') {
32
+ type = 'TINYINT(1)';
33
+ }
34
+ else if (s.instanceof === 'Buffer') {
35
+ type = 'LONGBLOB';
36
+ }
37
+ else if (s.type === 'null') {
38
+ type = 'VARCHAR(255)';
39
+ }
40
+ else if (s.type === 'array') {
41
+ type = 'LONGTEXT'; // to be JSON.stringified?
42
+ }
43
+ else if (s.type === 'object') {
44
+ type = 'LONGTEXT'; // to be JSON.stringified?
45
+ }
46
+ else {
47
+ // default
48
+ type = 'LONGTEXT';
49
+ }
50
+ const tokens = [mysql.escapeId(mapNameToMySQL(k)), type, `DEFAULT NULL`];
51
+ return tokens.join(' ');
52
+ });
15
53
  innerLines.push(`PRIMARY KEY (id)`);
16
54
  lines.push(innerLines.join(',\n'));
17
55
  lines.push(`) ENGINE=${engine}`);
18
56
  return lines.join('\n');
19
57
  }
20
- exports.commonSchemaToMySQLDDL = commonSchemaToMySQLDDL;
21
- const typeToMySQLType = {
22
- [db_lib_1.DATA_TYPE.STRING]: `LONGTEXT`,
23
- [db_lib_1.DATA_TYPE.LOCAL_DATE]: `VARCHAR(255)`,
24
- [db_lib_1.DATA_TYPE.INT]: `INT(11)`,
25
- [db_lib_1.DATA_TYPE.TIMESTAMP]: `INT(11)`,
26
- [db_lib_1.DATA_TYPE.FLOAT]: `FLOAT(11)`,
27
- [db_lib_1.DATA_TYPE.BOOLEAN]: `TINYINT(1)`,
28
- [db_lib_1.DATA_TYPE.BINARY]: `LONGBLOB`,
29
- [db_lib_1.DATA_TYPE.ARRAY]: `LONGTEXT`,
30
- [db_lib_1.DATA_TYPE.OBJECT]: `LONGTEXT`,
31
- [db_lib_1.DATA_TYPE.NULL]: `VARCHAR(255)`,
32
- [db_lib_1.DATA_TYPE.UNKNOWN]: `LONGTEXT`,
33
- };
34
- function commonSchemaFieldToDDL(f) {
35
- if (f.name === 'id') {
36
- return `id VARCHAR(255) NOT NULL`;
37
- }
38
- const tokens = [
39
- mysql.escapeId(mapNameToMySQL(f.name)),
40
- typeToMySQLType[f.type] || typeToMySQLType[db_lib_1.DATA_TYPE.UNKNOWN],
41
- `DEFAULT NULL`,
42
- ];
43
- return tokens.join(' ');
44
- }
45
- function mysqlTableStatsToCommonSchemaField(s) {
46
- const name = mapNameFromMySQL(s.Field);
47
- const notNull = (s.Null || '').toUpperCase() !== 'YES';
48
- let type = db_lib_1.DATA_TYPE.UNKNOWN;
49
- const t = (s.Type || '').toLowerCase();
50
- if (t) {
58
+ exports.jsonSchemaToMySQLDDL = jsonSchemaToMySQLDDL;
59
+ function mysqlTableStatsToJsonSchemaField(table, stats, logger) {
60
+ const s = {
61
+ $id: `${table}.schema.json`,
62
+ type: 'object',
63
+ properties: {},
64
+ required: [],
65
+ additionalProperties: true,
66
+ };
67
+ stats.forEach(stat => {
68
+ const name = stat.Field;
69
+ const t = stat.Type.toLowerCase();
70
+ const notNull = stat.Null?.toUpperCase() !== 'YES';
71
+ if (notNull) {
72
+ s.required.push(name);
73
+ }
51
74
  if (t.includes('text') || t.includes('char')) {
52
- type = db_lib_1.DATA_TYPE.STRING;
75
+ s.properties[name] = { type: 'string' };
53
76
  }
54
77
  else if (t.includes('lob')) {
55
- type = db_lib_1.DATA_TYPE.BINARY;
78
+ s.properties[name] = { instanceof: 'Buffer' };
56
79
  }
57
80
  else if (t.startsWith('tinyint') || t.includes('(1)')) {
58
- type = db_lib_1.DATA_TYPE.BOOLEAN;
81
+ s.properties[name] = { type: 'boolean' };
59
82
  }
60
83
  else if (t.startsWith('int(')) {
61
- type = db_lib_1.DATA_TYPE.INT;
84
+ s.properties[name] = { type: 'integer' };
62
85
  }
63
86
  else if (t.startsWith('float')) {
64
- type = db_lib_1.DATA_TYPE.FLOAT;
87
+ s.properties[name] = { type: 'number' };
88
+ }
89
+ else {
90
+ logger.log(s);
91
+ throw new Error(`Unknown mysql field type ${name} ${stat.Type}`);
65
92
  }
66
- }
67
- return js_lib_1._filterFalsyValues({
68
- name,
69
- type,
70
- notNull,
71
93
  });
94
+ return s;
72
95
  }
73
- exports.mysqlTableStatsToCommonSchemaField = mysqlTableStatsToCommonSchemaField;
96
+ exports.mysqlTableStatsToJsonSchemaField = mysqlTableStatsToJsonSchemaField;
74
97
  /**
75
98
  * Because MySQL doesn't support `.` in field names and escapes them as tableName + fieldName.
99
+ *
76
100
  * @param name
77
101
  */
78
102
  function mapNameToMySQL(name) {
@@ -83,4 +107,3 @@ function mapNameFromMySQL(name) {
83
107
  return name.replace(/_dot_/g, '.');
84
108
  }
85
109
  exports.mapNameFromMySQL = mapNameFromMySQL;
86
- //# sourceMappingURL=mysql.schema.util.js.map
package/package.json CHANGED
@@ -1,21 +1,20 @@
1
1
  {
2
2
  "name": "@naturalcycles/mysql-lib",
3
- "scripts": {},
4
- "peerDependencies": {
5
- "@naturalcycles/db-lib": ">=7.0.0"
3
+ "scripts": {
4
+ "prepare": "husky install"
6
5
  },
7
6
  "dependencies": {
8
- "@naturalcycles/db-lib": "^7.0.1",
9
- "@naturalcycles/js-lib": "^13.1.5",
10
- "@naturalcycles/nodejs-lib": "^10.5.0",
11
- "@types/mysql": "^2.15.6",
7
+ "@naturalcycles/db-lib": "^8.16.2",
8
+ "@naturalcycles/js-lib": "^14.47.0",
9
+ "@naturalcycles/nodejs-lib": "^12.37.0",
10
+ "@types/mysql": "^2.15.19",
12
11
  "mysql": "^2.17.1"
13
12
  },
14
13
  "devDependencies": {
15
- "@naturalcycles/dev-lib": "^10.3.0",
16
- "@types/node": "^14.0.9",
17
- "dotenv": "^8.0.0",
18
- "jest": "^26.0.1"
14
+ "@naturalcycles/dev-lib": "^12.7.5",
15
+ "@types/node": "^16.10.2",
16
+ "dotenv": "^10.0.0",
17
+ "jest": "^27.2.4"
19
18
  },
20
19
  "files": [
21
20
  "dist",
@@ -35,9 +34,9 @@
35
34
  "url": "https://github.com/NaturalCycles/mysql-lib"
36
35
  },
37
36
  "engines": {
38
- "node": ">=12.13.1"
37
+ "node": ">=14.15.0"
39
38
  },
40
- "version": "3.7.1",
39
+ "version": "3.9.1",
41
40
  "description": "MySQL client implementing CommonDB interface",
42
41
  "keywords": [
43
42
  "mysql",
package/src/index.ts CHANGED
@@ -1,12 +1,7 @@
1
1
  import { getDBAdapter } from './dbAdapter'
2
2
  import { MysqlDB, MysqlDBCfg, MysqlDBOptions, MysqlDBSaveOptions } from './mysql.db'
3
- import { commonSchemaToMySQLDDL } from './schema/mysql.schema.util'
3
+ import { jsonSchemaToMySQLDDL } from './schema/mysql.schema.util'
4
4
 
5
- export {
6
- MysqlDB,
7
- MysqlDBCfg,
8
- MysqlDBOptions,
9
- MysqlDBSaveOptions,
10
- commonSchemaToMySQLDDL,
11
- getDBAdapter,
12
- }
5
+ export type { MysqlDBCfg, MysqlDBOptions, MysqlDBSaveOptions }
6
+
7
+ export { MysqlDB, jsonSchemaToMySQLDDL, getDBAdapter }
package/src/mysql.db.ts CHANGED
@@ -1,32 +1,41 @@
1
+ import { Transform } from 'stream'
2
+ import { promisify } from 'util'
1
3
  import {
2
4
  BaseCommonDB,
3
- BaseDBEntity,
4
5
  CommonDB,
5
6
  CommonDBCreateOptions,
6
7
  CommonDBOptions,
7
8
  CommonDBSaveOptions,
8
- CommonSchema,
9
9
  DBQuery,
10
- ObjectWithId,
11
10
  RunQueryResult,
12
11
  } from '@naturalcycles/db-lib'
13
- import { _filterUndefinedValues, _mapKeys, _mapValues, _Memo } from '@naturalcycles/js-lib'
14
- import { Debug, ReadableTyped } from '@naturalcycles/nodejs-lib'
12
+ import {
13
+ _filterNullishValues,
14
+ _mapKeys,
15
+ _mapValues,
16
+ _Memo,
17
+ AnyObjectWithId,
18
+ CommonLogger,
19
+ commonLoggerPrefix,
20
+ JsonSchemaObject,
21
+ JsonSchemaRootObject,
22
+ ObjectWithId,
23
+ } from '@naturalcycles/js-lib'
24
+ import { ReadableTyped } from '@naturalcycles/nodejs-lib'
15
25
  import { white } from '@naturalcycles/nodejs-lib/dist/colors'
16
26
  import { Connection, Pool, PoolConfig, PoolConnection, QueryOptions, TypeCast } from 'mysql'
17
27
  import * as mysql from 'mysql'
18
- import { Transform } from 'stream'
19
- import { promisify } from 'util'
20
28
  import { dbQueryToSQLDelete, dbQueryToSQLSelect, insertSQL } from './query.util'
21
29
  import {
22
- commonSchemaToMySQLDDL,
30
+ jsonSchemaToMySQLDDL,
23
31
  mapNameFromMySQL,
24
32
  MySQLTableStats,
25
- mysqlTableStatsToCommonSchemaField,
33
+ mysqlTableStatsToJsonSchemaField,
26
34
  } from './schema/mysql.schema.util'
27
35
 
28
36
  export interface MysqlDBOptions extends CommonDBOptions {}
29
- export interface MysqlDBSaveOptions extends CommonDBSaveOptions {}
37
+ export interface MysqlDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId>
38
+ extends CommonDBSaveOptions<ROW> {}
30
39
 
31
40
  /**
32
41
  * @default false / undefined
@@ -41,9 +50,12 @@ export interface MysqlDBCfg extends PoolConfig {
41
50
  * If true - will emit logs of connection events.
42
51
  */
43
52
  debugConnections?: boolean
44
- }
45
53
 
46
- const log = Debug('nc:mysql-lib')
54
+ /**
55
+ * Default to `console`
56
+ */
57
+ logger?: CommonLogger
58
+ }
47
59
 
48
60
  const BOOLEAN_TYPES = new Set(['TINY', 'TINYINT', 'INT'])
49
61
  const BOOLEAN_BIT_TYPES = new Set(['BIT'])
@@ -79,12 +91,13 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
79
91
  // password: MYSQL_PW,
80
92
  // database: MYSQL_DB,
81
93
  ...cfg,
94
+ logger: commonLoggerPrefix(cfg.logger || console, '[mysql]'),
82
95
  }
83
96
  }
84
97
 
85
- cfg!: MysqlDBCfg
98
+ cfg: MysqlDBCfg & { logger: CommonLogger }
86
99
 
87
- async ping(): Promise<void> {
100
+ override async ping(): Promise<void> {
88
101
  const con = await this.getConnection()
89
102
  con.release()
90
103
  }
@@ -92,29 +105,29 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
92
105
  @_Memo()
93
106
  pool(): Pool {
94
107
  const pool = mysql.createPool(this.cfg)
95
- const { host, database } = this.cfg
96
- log(`connected to ${white(host + '/' + database)}`)
108
+ const { host, database, logger } = this.cfg
109
+ logger.log(`connected to ${white(host + '/' + database)}`)
97
110
 
98
111
  if (this.cfg.debugConnections) {
99
112
  pool.on('acquire', con => {
100
- log(`acquire(${con.threadId})`)
113
+ logger.log(`acquire(${con.threadId})`)
101
114
  })
102
115
 
103
116
  pool.on('connection', con => {
104
- log(`connection(${con.threadId})`)
117
+ logger.log(`connection(${con.threadId})`)
105
118
  })
106
119
 
107
120
  pool.on('enqueue', () => {
108
- log(`enqueue`)
121
+ logger.log(`enqueue`)
109
122
  })
110
123
 
111
124
  pool.on('release', con => {
112
- log(`release(${con.threadId})`)
125
+ logger.log(`release(${con.threadId})`)
113
126
  })
114
127
  }
115
128
 
116
129
  pool.on('error', err => {
117
- log.error(`error`, err)
130
+ logger.error(err)
118
131
  })
119
132
 
120
133
  return pool
@@ -122,7 +135,9 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
122
135
 
123
136
  async close(): Promise<void> {
124
137
  const pool = this.pool()
138
+ // eslint-disable-next-line @typescript-eslint/await-thenable
125
139
  await promisify(pool.end.bind(pool))
140
+ this.cfg.logger.log('closed')
126
141
  }
127
142
 
128
143
  /**
@@ -130,7 +145,7 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
130
145
  */
131
146
  async getConnection(): Promise<PoolConnection> {
132
147
  const pool = this.pool()
133
- return promisify(pool.getConnection.bind(pool))()
148
+ return await promisify(pool.getConnection.bind(pool))()
134
149
  }
135
150
 
136
151
  /**
@@ -143,23 +158,23 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
143
158
  const { threadId } = con
144
159
 
145
160
  if (this.cfg.debugConnections) {
146
- log(`createSingleConnection(${threadId})`)
161
+ this.cfg.logger.log(`createSingleConnection(${threadId})`)
147
162
 
148
- con.on('connect', () => log(`createSingleConnection(${threadId}).connect`))
149
- con.on('drain', () => log(`createSingleConnection(${threadId}).drain`))
150
- con.on('enqueue', () => log(`createSingleConnection(${threadId}).enqueue`))
151
- 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`))
152
167
  }
153
168
 
154
169
  con.on('error', err => {
155
- log.error(`createSingleConnection(${threadId}).error`, err)
170
+ this.cfg.logger.error(`createSingleConnection(${threadId}).error`, err)
156
171
  })
157
172
 
158
173
  return con
159
174
  }
160
175
 
161
176
  // GET
162
- async getByIds<ROW extends ObjectWithId>(
177
+ override async getByIds<ROW extends ObjectWithId>(
163
178
  table: string,
164
179
  ids: string[],
165
180
  opt: MysqlDBOptions = {},
@@ -167,27 +182,25 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
167
182
  if (!ids.length) return []
168
183
  const q = new DBQuery<ROW>(table).filterEq('id', ids)
169
184
  const { rows } = await this.runQuery(q, opt)
170
- return rows.map(r => _mapKeys(r, (_v, k) => mapNameFromMySQL(k)) as any)
185
+ return rows.map(r => _mapKeys(r, k => mapNameFromMySQL(k)) as any)
171
186
  }
172
187
 
173
188
  // QUERY
174
- async runQuery<ROW extends ObjectWithId, OUT = ROW>(
189
+ override async runQuery<ROW extends ObjectWithId, OUT = ROW>(
175
190
  q: DBQuery<ROW>,
176
- opt: MysqlDBOptions = {},
191
+ _opt: MysqlDBOptions = {},
177
192
  ): Promise<RunQueryResult<OUT>> {
178
193
  const sql = dbQueryToSQLSelect(q)
179
194
  const rows = await this.runSQL<ROW[]>({ sql })
180
195
  return {
181
- rows: rows.map(
182
- r => _mapKeys(_filterUndefinedValues(r, true), (_v, k) => mapNameFromMySQL(k)) as any,
183
- ),
196
+ rows: rows.map(r => _mapKeys(_filterNullishValues(r, true), k => mapNameFromMySQL(k)) as any),
184
197
  }
185
198
  }
186
199
 
187
200
  async runSQL<RESULT>(q: QueryOptions): Promise<RESULT> {
188
- if (this.cfg.logSQL) log(q.sql, q.values)
201
+ if (this.cfg.logSQL) this.cfg.logger.log(q.sql, q.values)
189
202
 
190
- return new Promise(async (resolve, reject) => {
203
+ return await new Promise<RESULT>((resolve, reject) => {
191
204
  this.pool().query(q, (err, res) => {
192
205
  if (err) return reject(err)
193
206
  resolve(res)
@@ -195,21 +208,21 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
195
208
  })
196
209
  }
197
210
 
198
- async runQueryCount<ROW extends ObjectWithId>(
211
+ override async runQueryCount<ROW extends ObjectWithId>(
199
212
  q: DBQuery<ROW>,
200
- opt?: CommonDBOptions,
213
+ _opt?: CommonDBOptions,
201
214
  ): Promise<number> {
202
- const { rows } = await this.runQuery(q.select(['count(*) as _count']))
215
+ const { rows } = await this.runQuery(q.select(['count(*) as _count' as any]))
203
216
  return (rows[0] as any)._count
204
217
  }
205
218
 
206
- streamQuery<ROW extends ObjectWithId, OUT = ROW>(
219
+ override streamQuery<ROW extends ObjectWithId, OUT = ROW>(
207
220
  q: DBQuery<ROW>,
208
- opt: MysqlDBOptions = {},
221
+ _opt: MysqlDBOptions = {},
209
222
  ): ReadableTyped<OUT> {
210
223
  const sql = dbQueryToSQLSelect(q)
211
224
 
212
- if (this.cfg.logSQL) log(`stream: ${sql}`)
225
+ if (this.cfg.logSQL) this.cfg.logger.log(`stream: ${sql}`)
213
226
 
214
227
  // return this.streamSQL(sql, opt)
215
228
  return this.pool()
@@ -219,17 +232,17 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
219
232
  new Transform({
220
233
  objectMode: true,
221
234
  transform(row, _encoding, cb) {
222
- cb(null, _filterUndefinedValues(row, true))
235
+ cb(null, _filterNullishValues(row, true))
223
236
  },
224
237
  }),
225
238
  )
226
239
  }
227
240
 
228
241
  // SAVE
229
- async saveBatch<ROW extends BaseDBEntity>(
242
+ override async saveBatch<ROW extends ObjectWithId>(
230
243
  table: string,
231
244
  _rows: ROW[],
232
- opt?: MysqlDBSaveOptions,
245
+ _opt?: MysqlDBSaveOptions<ROW>,
233
246
  ): Promise<void> {
234
247
  if (!_rows.length) return
235
248
 
@@ -241,7 +254,7 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
241
254
  )
242
255
 
243
256
  // inserts are split into multiple sentenses to respect the max_packet_size (1Mb usually)
244
- const sqls = insertSQL(table, rows)
257
+ const sqls = insertSQL(table, rows, 'INSERT', this.cfg.logger)
245
258
 
246
259
  for await (const sql of sqls) {
247
260
  await this.runSQL({ sql })
@@ -252,16 +265,16 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
252
265
  /**
253
266
  * Limitation: always returns [], regardless of which rows are actually deleted
254
267
  */
255
- async deleteByIds(table: string, ids: string[], opt?: MysqlDBOptions): Promise<number> {
268
+ override async deleteByIds(table: string, ids: string[], _opt?: MysqlDBOptions): Promise<number> {
256
269
  if (!ids.length) return 0
257
270
  const sql = dbQueryToSQLDelete(new DBQuery(table).filterEq('id', ids))
258
271
  const res = await this.runSQL<any>({ sql })
259
272
  return res.affectedRows
260
273
  }
261
274
 
262
- async deleteByQuery<ROW extends ObjectWithId>(
275
+ override async deleteByQuery<ROW extends ObjectWithId>(
263
276
  q: DBQuery<ROW>,
264
- opt?: CommonDBOptions,
277
+ _opt?: CommonDBOptions,
265
278
  ): Promise<number> {
266
279
  const sql = dbQueryToSQLDelete(q)
267
280
  const res = await this.runSQL<any>({ sql })
@@ -278,27 +291,30 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
278
291
  /**
279
292
  * dropIfExists=true needed as a safety check
280
293
  */
281
- async createTable(schema: CommonSchema, opt: CommonDBCreateOptions = {}): Promise<void> {
282
- if (opt.dropIfExists) await this.dropTable(schema.table)
294
+ override async createTable<ROW extends ObjectWithId>(
295
+ table: string,
296
+ schema: JsonSchemaObject<ROW>,
297
+ opt: CommonDBCreateOptions = {},
298
+ ): Promise<void> {
299
+ if (opt.dropIfExists) await this.dropTable(table)
283
300
 
284
- const sql = commonSchemaToMySQLDDL(schema)
301
+ const sql = jsonSchemaToMySQLDDL(table, schema)
285
302
  await this.runSQL({ sql })
286
303
  }
287
304
 
288
- async getTables(): Promise<string[]> {
289
- return (await this.runSQL<object[]>({ sql: `show tables` }))
305
+ override async getTables(): Promise<string[]> {
306
+ return (await this.runSQL<Record<any, any>[]>({ sql: `show tables` }))
290
307
  .map(r => Object.values(r)[0])
291
308
  .filter(Boolean)
292
309
  }
293
310
 
294
- async getTableSchema<ROW extends ObjectWithId>(table: string): Promise<CommonSchema<ROW>> {
295
- const statsArray = await this.runSQL<MySQLTableStats[]>({
311
+ override async getTableSchema<ROW extends ObjectWithId>(
312
+ table: string,
313
+ ): Promise<JsonSchemaRootObject<ROW>> {
314
+ const stats = await this.runSQL<MySQLTableStats[]>({
296
315
  sql: `describe ${mysql.escapeId(table)}`,
297
316
  })
298
317
 
299
- return {
300
- table,
301
- fields: statsArray.map(stats => mysqlTableStatsToCommonSchemaField(stats)),
302
- }
318
+ return mysqlTableStatsToJsonSchemaField<ROW>(table, stats, this.cfg.logger)
303
319
  }
304
320
  }
package/src/query.util.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { DBQuery } from '@naturalcycles/db-lib'
2
- import { _hb } from '@naturalcycles/js-lib'
2
+ import { DBQueryFilterOperator } from '@naturalcycles/db-lib/src/query/dbQuery'
3
+ import { _hb, CommonLogger } from '@naturalcycles/js-lib'
3
4
  import { white, yellow } from '@naturalcycles/nodejs-lib/dist/colors'
4
5
  import { QueryOptions } from 'mysql'
5
6
  import * as mysql from 'mysql'
@@ -8,7 +9,7 @@ import { mapNameToMySQL } from './schema/mysql.schema.util'
8
9
  const MAX_PACKET_SIZE = 1024 * 1024 // 1Mb
9
10
  const MAX_ROW_SIZE = 800 * 1024 // 1Mb - margin
10
11
 
11
- export function dbQueryToSQLSelect(q: DBQuery): string {
12
+ export function dbQueryToSQLSelect(q: DBQuery<any>): string {
12
13
  const tokens = selectTokens(q)
13
14
 
14
15
  // filters
@@ -23,7 +24,7 @@ export function dbQueryToSQLSelect(q: DBQuery): string {
23
24
  return tokens.join(' ')
24
25
  }
25
26
 
26
- export function dbQueryToSQLDelete(q: DBQuery): string {
27
+ export function dbQueryToSQLDelete(q: DBQuery<any>): string {
27
28
  const tokens = [`DELETE FROM`, mysql.escapeId(q.table)]
28
29
 
29
30
  // filters
@@ -40,12 +41,14 @@ export function dbQueryToSQLDelete(q: DBQuery): string {
40
41
  */
41
42
  export function insertSQL(
42
43
  table: string,
43
- records: object[],
44
+ records: Record<any, any>[],
44
45
  verb: 'INSERT' | 'REPLACE' = 'INSERT',
46
+ logger: CommonLogger = console,
45
47
  ): string[] {
46
48
  // INSERT INTO table_name (column1, column2, column3, ...)
47
49
  // VALUES (value1, value2, value3, ...);
48
50
 
51
+ // eslint-disable-next-line unicorn/no-array-reduce
49
52
  const fieldSet = records.reduce((set: Set<string>, rec) => {
50
53
  Object.keys(rec).forEach(field => set.add(field))
51
54
  return set
@@ -88,7 +91,7 @@ export function insertSQL(
88
91
  sqls.push(sql) // last one
89
92
  }
90
93
 
91
- console.log(
94
+ logger.log(
92
95
  `${white(table)} large sql query (${yellow(_hb(full.length))}) was split into ${yellow(
93
96
  sqls.length,
94
97
  )} chunks`,
@@ -99,7 +102,7 @@ export function insertSQL(
99
102
  // todo: handle "upsert" later
100
103
  }
101
104
 
102
- export function insertSQLSingle(table: string, record: object): string {
105
+ export function insertSQLSingle(table: string, record: Record<any, any>): string {
103
106
  // INSERT INTO table_name (column1, column2, column3, ...)
104
107
  // VALUES (value1, value2, value3, ...);
105
108
 
@@ -122,14 +125,14 @@ export function insertSQLSingle(table: string, record: object): string {
122
125
  return tokens.join(' ')
123
126
  }
124
127
 
125
- export function insertSQLSetSingle(table: string, record: object): QueryOptions {
128
+ export function insertSQLSetSingle(table: string, record: Record<any, any>): QueryOptions {
126
129
  return {
127
130
  sql: `INSERT INTO ${mysql.escapeId(table)} SET ?`,
128
131
  values: [record],
129
132
  }
130
133
  }
131
134
 
132
- export function dbQueryToSQLUpdate(q: DBQuery, record: object): string {
135
+ export function dbQueryToSQLUpdate(q: DBQuery<any>, record: Record<any, any>): string {
133
136
  // var sql = mysql.format('UPDATE posts SET modified = ? WHERE id = ?', [CURRENT_TIMESTAMP, 42]);
134
137
  const tokens = [
135
138
  `UPDATE`,
@@ -148,14 +151,14 @@ function selectTokens(q: DBQuery): string[] {
148
151
  let fields = ['*']
149
152
 
150
153
  if (q._selectedFieldNames) {
151
- fields = q._selectedFieldNames.length ? q._selectedFieldNames : ['id']
154
+ fields = q._selectedFieldNames.length ? (q._selectedFieldNames as string[]) : ['id']
152
155
  }
153
156
 
154
157
  // We don't do `escapeId` cause it'll ruin e.g SELECT `count *` FROM ...
155
158
  return [`SELECT`, fields.map(f => mapNameToMySQL(f)).join(', '), `FROM`, mysql.escapeId(q.table)]
156
159
  }
157
160
 
158
- function offsetLimitTokens(q: DBQuery): string[] {
161
+ function offsetLimitTokens(q: DBQuery<any>): string[] {
159
162
  const tokens: string[] = []
160
163
 
161
164
  if (q._limitValue) {
@@ -178,6 +181,10 @@ function orderTokens(q: DBQuery): string[] {
178
181
  ]
179
182
  }
180
183
 
184
+ const OP_MAP: Partial<Record<DBQueryFilterOperator, string>> = {
185
+ '==': '=',
186
+ }
187
+
181
188
  function whereTokens(q: DBQuery): string[] {
182
189
  if (!q._filters.length) return []
183
190
 
@@ -189,17 +196,21 @@ function whereTokens(q: DBQuery): string[] {
189
196
  // special treatment
190
197
 
191
198
  return [
192
- mysql.escapeId(mapNameToMySQL(f.name)),
193
- f.op === '=' ? 'IS NULL' : 'IS NOT NULL',
199
+ mysql.escapeId(mapNameToMySQL(f.name as string)),
200
+ f.op === '==' ? 'IS NULL' : 'IS NOT NULL',
194
201
  ].join(' ')
195
202
  }
196
203
 
197
204
  if (Array.isArray(f.val)) {
198
205
  // special case for arrays
199
- 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)})`
200
207
  }
201
208
 
202
- return [mysql.escapeId(mapNameToMySQL(f.name)), f.op, mysql.escape(f.val)].join(' ')
209
+ return [
210
+ mysql.escapeId(mapNameToMySQL(f.name as string)),
211
+ OP_MAP[f.op] || f.op,
212
+ mysql.escape(f.val),
213
+ ].join(' ')
203
214
  })
204
215
  .join(' AND '),
205
216
  ]