@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.
- package/dist/dbAdapter.js +0 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -3
- package/dist/mysql.db.d.ts +18 -11
- package/dist/mysql.db.js +175 -181
- package/dist/query.util.d.ts +7 -6
- package/dist/query.util.js +18 -11
- package/dist/schema/mysql.schema.util.d.ts +4 -3
- package/dist/schema/mysql.schema.util.js +72 -49
- package/package.json +12 -13
- package/src/index.ts +4 -9
- package/src/mysql.db.ts +77 -61
- package/src/query.util.ts +25 -14
- package/src/schema/mysql.schema.util.ts +80 -51
- package/CHANGELOG.md +0 -191
- package/dist/dbAdapter.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mysql.db.js.map +0 -1
- package/dist/query.util.js.map +0 -1
- package/dist/schema/mysql.schema.util.js.map +0 -1
|
@@ -1,78 +1,102 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.mapNameFromMySQL = exports.mapNameToMySQL = exports.
|
|
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
|
|
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 =
|
|
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.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
75
|
+
s.properties[name] = { type: 'string' };
|
|
53
76
|
}
|
|
54
77
|
else if (t.includes('lob')) {
|
|
55
|
-
|
|
78
|
+
s.properties[name] = { instanceof: 'Buffer' };
|
|
56
79
|
}
|
|
57
80
|
else if (t.startsWith('tinyint') || t.includes('(1)')) {
|
|
58
|
-
|
|
81
|
+
s.properties[name] = { type: 'boolean' };
|
|
59
82
|
}
|
|
60
83
|
else if (t.startsWith('int(')) {
|
|
61
|
-
|
|
84
|
+
s.properties[name] = { type: 'integer' };
|
|
62
85
|
}
|
|
63
86
|
else if (t.startsWith('float')) {
|
|
64
|
-
|
|
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.
|
|
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
|
-
|
|
5
|
-
"@naturalcycles/db-lib": ">=7.0.0"
|
|
3
|
+
"scripts": {
|
|
4
|
+
"prepare": "husky install"
|
|
6
5
|
},
|
|
7
6
|
"dependencies": {
|
|
8
|
-
"@naturalcycles/db-lib": "^
|
|
9
|
-
"@naturalcycles/js-lib": "^
|
|
10
|
-
"@naturalcycles/nodejs-lib": "^
|
|
11
|
-
"@types/mysql": "^2.15.
|
|
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": "^
|
|
16
|
-
"@types/node": "^
|
|
17
|
-
"dotenv": "^
|
|
18
|
-
"jest": "^
|
|
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": ">=
|
|
37
|
+
"node": ">=14.15.0"
|
|
39
38
|
},
|
|
40
|
-
"version": "3.
|
|
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 {
|
|
3
|
+
import { jsonSchemaToMySQLDDL } from './schema/mysql.schema.util'
|
|
4
4
|
|
|
5
|
-
export {
|
|
6
|
-
|
|
7
|
-
|
|
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 {
|
|
14
|
-
|
|
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
|
-
|
|
30
|
+
jsonSchemaToMySQLDDL,
|
|
23
31
|
mapNameFromMySQL,
|
|
24
32
|
MySQLTableStats,
|
|
25
|
-
|
|
33
|
+
mysqlTableStatsToJsonSchemaField,
|
|
26
34
|
} from './schema/mysql.schema.util'
|
|
27
35
|
|
|
28
36
|
export interface MysqlDBOptions extends CommonDBOptions {}
|
|
29
|
-
export interface MysqlDBSaveOptions extends
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
235
|
+
cb(null, _filterNullishValues(row, true))
|
|
223
236
|
},
|
|
224
237
|
}),
|
|
225
238
|
)
|
|
226
239
|
}
|
|
227
240
|
|
|
228
241
|
// SAVE
|
|
229
|
-
async saveBatch<ROW extends
|
|
242
|
+
override async saveBatch<ROW extends ObjectWithId>(
|
|
230
243
|
table: string,
|
|
231
244
|
_rows: ROW[],
|
|
232
|
-
|
|
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[],
|
|
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
|
-
|
|
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
|
|
282
|
-
|
|
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 =
|
|
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<
|
|
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>(
|
|
295
|
-
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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 === '
|
|
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 [
|
|
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
|
]
|