@naturalcycles/mysql-lib 3.8.1 → 3.10.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.
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/mysql.db.d.ts +11 -5
- package/dist/mysql.db.js +33 -26
- package/dist/mysqlKeyValueDB.d.ts +25 -0
- package/dist/mysqlKeyValueDB.js +128 -0
- package/dist/query.util.d.ts +2 -1
- package/dist/query.util.js +5 -5
- package/dist/schema/mysql.schema.util.d.ts +2 -2
- package/dist/schema/mysql.schema.util.js +3 -3
- package/package.json +2 -5
- package/readme.md +27 -6
- package/src/index.ts +2 -1
- package/src/mysql.db.ts +47 -30
- package/src/mysqlKeyValueDB.ts +155 -0
- package/src/query.util.ts +7 -6
- package/src/schema/mysql.schema.util.ts +4 -2
- package/CHANGELOG.md +0 -205
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getDBAdapter } from './dbAdapter';
|
|
2
2
|
import { MysqlDB, MysqlDBCfg, MysqlDBOptions, MysqlDBSaveOptions } from './mysql.db';
|
|
3
|
+
import { MySQLKeyValueDB } from './mysqlKeyValueDB';
|
|
3
4
|
import { jsonSchemaToMySQLDDL } from './schema/mysql.schema.util';
|
|
4
5
|
export type { MysqlDBCfg, MysqlDBOptions, MysqlDBSaveOptions };
|
|
5
|
-
export { MysqlDB, jsonSchemaToMySQLDDL, getDBAdapter };
|
|
6
|
+
export { MysqlDB, MySQLKeyValueDB, jsonSchemaToMySQLDDL, getDBAdapter };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getDBAdapter = exports.jsonSchemaToMySQLDDL = exports.MysqlDB = void 0;
|
|
3
|
+
exports.getDBAdapter = exports.jsonSchemaToMySQLDDL = exports.MySQLKeyValueDB = exports.MysqlDB = void 0;
|
|
4
4
|
const dbAdapter_1 = require("./dbAdapter");
|
|
5
5
|
Object.defineProperty(exports, "getDBAdapter", { enumerable: true, get: function () { return dbAdapter_1.getDBAdapter; } });
|
|
6
6
|
const mysql_db_1 = require("./mysql.db");
|
|
7
7
|
Object.defineProperty(exports, "MysqlDB", { enumerable: true, get: function () { return mysql_db_1.MysqlDB; } });
|
|
8
|
+
const mysqlKeyValueDB_1 = require("./mysqlKeyValueDB");
|
|
9
|
+
Object.defineProperty(exports, "MySQLKeyValueDB", { enumerable: true, get: function () { return mysqlKeyValueDB_1.MySQLKeyValueDB; } });
|
|
8
10
|
const mysql_schema_util_1 = require("./schema/mysql.schema.util");
|
|
9
11
|
Object.defineProperty(exports, "jsonSchemaToMySQLDDL", { enumerable: true, get: function () { return mysql_schema_util_1.jsonSchemaToMySQLDDL; } });
|
package/dist/mysql.db.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
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';
|
|
4
3
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
5
|
-
import { Connection, Pool, PoolConfig, PoolConnection, QueryOptions } from 'mysql';
|
|
4
|
+
import { Connection, Pool, PoolConfig, PoolConnection, QueryOptions, TypeCast } from 'mysql';
|
|
6
5
|
export interface MysqlDBOptions extends CommonDBOptions {
|
|
7
6
|
}
|
|
8
7
|
export interface MysqlDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId> extends CommonDBSaveOptions<ROW> {
|
|
@@ -19,10 +18,17 @@ export interface MysqlDBCfg extends PoolConfig {
|
|
|
19
18
|
* If true - will emit logs of connection events.
|
|
20
19
|
*/
|
|
21
20
|
debugConnections?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Default to `console`
|
|
23
|
+
*/
|
|
24
|
+
logger?: CommonLogger;
|
|
22
25
|
}
|
|
26
|
+
export declare const typeCast: TypeCast;
|
|
23
27
|
export declare class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
24
28
|
constructor(cfg?: MysqlDBCfg);
|
|
25
|
-
cfg: MysqlDBCfg
|
|
29
|
+
cfg: MysqlDBCfg & {
|
|
30
|
+
logger: CommonLogger;
|
|
31
|
+
};
|
|
26
32
|
ping(): Promise<void>;
|
|
27
33
|
pool(): Pool;
|
|
28
34
|
close(): Promise<void>;
|
package/dist/mysql.db.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MysqlDB = void 0;
|
|
3
|
+
exports.MysqlDB = exports.typeCast = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
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) => {
|
|
@@ -33,17 +31,19 @@ const typeCast = (field, next) => {
|
|
|
33
31
|
}
|
|
34
32
|
return next();
|
|
35
33
|
};
|
|
34
|
+
exports.typeCast = typeCast;
|
|
36
35
|
class MysqlDB extends db_lib_1.BaseCommonDB {
|
|
37
36
|
constructor(cfg = {}) {
|
|
38
37
|
super();
|
|
39
38
|
this.cfg = {
|
|
40
|
-
typeCast,
|
|
39
|
+
typeCast: exports.typeCast,
|
|
41
40
|
// charset: 'utf8mb4', // for emoji support
|
|
42
41
|
// host: 'localhost',
|
|
43
42
|
// user: MYSQL_USER,
|
|
44
43
|
// password: MYSQL_PW,
|
|
45
44
|
// database: MYSQL_DB,
|
|
46
45
|
...cfg,
|
|
46
|
+
logger: (0, js_lib_1.commonLoggerPrefix)(cfg.logger || console, '[mysql]'),
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
async ping() {
|
|
@@ -52,31 +52,32 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
|
|
|
52
52
|
}
|
|
53
53
|
pool() {
|
|
54
54
|
const pool = mysql.createPool(this.cfg);
|
|
55
|
-
const { host, database } = this.cfg;
|
|
56
|
-
log(`connected to ${(0, colors_1.white)(host + '/' + database)}`);
|
|
55
|
+
const { host, database, logger } = this.cfg;
|
|
56
|
+
logger.log(`connected to ${(0, colors_1.white)(host + '/' + database)}`);
|
|
57
57
|
if (this.cfg.debugConnections) {
|
|
58
58
|
pool.on('acquire', con => {
|
|
59
|
-
log(`acquire(${con.threadId})`);
|
|
59
|
+
logger.log(`acquire(${con.threadId})`);
|
|
60
60
|
});
|
|
61
61
|
pool.on('connection', con => {
|
|
62
|
-
log(`connection(${con.threadId})`);
|
|
62
|
+
logger.log(`connection(${con.threadId})`);
|
|
63
63
|
});
|
|
64
64
|
pool.on('enqueue', () => {
|
|
65
|
-
log(`enqueue`);
|
|
65
|
+
logger.log(`enqueue`);
|
|
66
66
|
});
|
|
67
67
|
pool.on('release', con => {
|
|
68
|
-
log(`release(${con.threadId})`);
|
|
68
|
+
logger.log(`release(${con.threadId})`);
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
71
|
pool.on('error', err => {
|
|
72
|
-
|
|
72
|
+
logger.error(err);
|
|
73
73
|
});
|
|
74
74
|
return pool;
|
|
75
75
|
}
|
|
76
76
|
async close() {
|
|
77
77
|
const pool = this.pool();
|
|
78
78
|
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
79
|
-
await (0, util_1.promisify)(pool.end.bind(pool));
|
|
79
|
+
await (0, util_1.promisify)(pool.end.bind(pool))();
|
|
80
|
+
this.cfg.logger.log('closed');
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
82
83
|
* Be careful to always call `con.release()` when you get connection with this method.
|
|
@@ -94,14 +95,14 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
|
|
|
94
95
|
await (0, util_1.promisify)(con.connect.bind(con))();
|
|
95
96
|
const { threadId } = con;
|
|
96
97
|
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`));
|
|
98
|
+
this.cfg.logger.log(`createSingleConnection(${threadId})`);
|
|
99
|
+
con.on('connect', () => this.cfg.logger.log(`createSingleConnection(${threadId}).connect`));
|
|
100
|
+
con.on('drain', () => this.cfg.logger.log(`createSingleConnection(${threadId}).drain`));
|
|
101
|
+
con.on('enqueue', () => this.cfg.logger.log(`createSingleConnection(${threadId}).enqueue`));
|
|
102
|
+
con.on('end', () => this.cfg.logger.log(`createSingleConnection(${threadId}).end`));
|
|
102
103
|
}
|
|
103
104
|
con.on('error', err => {
|
|
104
|
-
|
|
105
|
+
this.cfg.logger.error(`createSingleConnection(${threadId}).error`, err);
|
|
105
106
|
});
|
|
106
107
|
return con;
|
|
107
108
|
}
|
|
@@ -116,14 +117,20 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
|
|
|
116
117
|
// QUERY
|
|
117
118
|
async runQuery(q, _opt = {}) {
|
|
118
119
|
const sql = (0, query_util_1.dbQueryToSQLSelect)(q);
|
|
119
|
-
const rows = await this.runSQL({ sql });
|
|
120
|
+
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)));
|
|
121
|
+
// edge case where 0 fields are selected
|
|
122
|
+
if (q._selectedFieldNames?.length === 0) {
|
|
123
|
+
return {
|
|
124
|
+
rows: rows.map(_ => ({})),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
120
127
|
return {
|
|
121
|
-
rows
|
|
128
|
+
rows,
|
|
122
129
|
};
|
|
123
130
|
}
|
|
124
131
|
async runSQL(q) {
|
|
125
132
|
if (this.cfg.logSQL)
|
|
126
|
-
log(q.sql, q.values);
|
|
133
|
+
this.cfg.logger.log(...[q.sql, q.values].filter(Boolean));
|
|
127
134
|
return await new Promise((resolve, reject) => {
|
|
128
135
|
this.pool().query(q, (err, res) => {
|
|
129
136
|
if (err)
|
|
@@ -139,7 +146,7 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
|
|
|
139
146
|
streamQuery(q, _opt = {}) {
|
|
140
147
|
const sql = (0, query_util_1.dbQueryToSQLSelect)(q);
|
|
141
148
|
if (this.cfg.logSQL)
|
|
142
|
-
log(`stream: ${sql}`);
|
|
149
|
+
this.cfg.logger.log(`stream: ${sql}`);
|
|
143
150
|
// return this.streamSQL(sql, opt)
|
|
144
151
|
return this.pool()
|
|
145
152
|
.query(sql)
|
|
@@ -147,7 +154,7 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
|
|
|
147
154
|
.pipe(new stream_1.Transform({
|
|
148
155
|
objectMode: true,
|
|
149
156
|
transform(row, _encoding, cb) {
|
|
150
|
-
cb(null, (0, js_lib_1.
|
|
157
|
+
cb(null, (0, js_lib_1._filterUndefinedValues)(row, true));
|
|
151
158
|
},
|
|
152
159
|
}));
|
|
153
160
|
}
|
|
@@ -156,11 +163,11 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
|
|
|
156
163
|
if (!_rows.length)
|
|
157
164
|
return;
|
|
158
165
|
// Stringify object values
|
|
159
|
-
const rows = _rows.map(row => (0, js_lib_1._mapValues)(row, v => {
|
|
166
|
+
const rows = _rows.map(row => (0, js_lib_1._mapValues)(row, (_k, v) => {
|
|
160
167
|
return v && typeof v === 'object' && !Buffer.isBuffer(v) ? JSON.stringify(v) : v;
|
|
161
168
|
}));
|
|
162
169
|
// inserts are split into multiple sentenses to respect the max_packet_size (1Mb usually)
|
|
163
|
-
const sqls = (0, query_util_1.insertSQL)(table, rows);
|
|
170
|
+
const sqls = (0, query_util_1.insertSQL)(table, rows, 'REPLACE', this.cfg.logger);
|
|
164
171
|
for await (const sql of sqls) {
|
|
165
172
|
await this.runSQL({ sql });
|
|
166
173
|
}
|
|
@@ -205,7 +212,7 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
|
|
|
205
212
|
const stats = await this.runSQL({
|
|
206
213
|
sql: `describe ${mysql.escapeId(table)}`,
|
|
207
214
|
});
|
|
208
|
-
return (0, mysql_schema_util_1.mysqlTableStatsToJsonSchemaField)(table, stats);
|
|
215
|
+
return (0, mysql_schema_util_1.mysqlTableStatsToJsonSchemaField)(table, stats, this.cfg.logger);
|
|
209
216
|
}
|
|
210
217
|
}
|
|
211
218
|
(0, tslib_1.__decorate)([
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { CommonDBCreateOptions, CommonKeyValueDB, KeyValueDBTuple } from '@naturalcycles/db-lib';
|
|
3
|
+
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
4
|
+
import { MysqlDB, MysqlDBCfg } from './mysql.db';
|
|
5
|
+
export declare class MySQLKeyValueDB implements CommonKeyValueDB {
|
|
6
|
+
constructor(cfg?: MysqlDBCfg);
|
|
7
|
+
cfg: MysqlDBCfg;
|
|
8
|
+
db: MysqlDB;
|
|
9
|
+
ping(): Promise<void>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
createTable(table: string, opt?: CommonDBCreateOptions): Promise<void>;
|
|
12
|
+
getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Use with caution!
|
|
15
|
+
*/
|
|
16
|
+
dropTable(table: string): Promise<void>;
|
|
17
|
+
deleteByIds(table: string, ids: string[]): Promise<void>;
|
|
18
|
+
saveBatch(table: string, entries: KeyValueDBTuple[]): Promise<void>;
|
|
19
|
+
streamIds(table: string, limit?: number): ReadableTyped<string>;
|
|
20
|
+
streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
|
|
21
|
+
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
|
|
22
|
+
beginTransaction(): Promise<void>;
|
|
23
|
+
endTransaction(): Promise<void>;
|
|
24
|
+
count(table: string): Promise<number>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MySQLKeyValueDB = void 0;
|
|
4
|
+
const stream_1 = require("stream");
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
|
+
const mysql_db_1 = require("./mysql.db");
|
|
7
|
+
class MySQLKeyValueDB {
|
|
8
|
+
constructor(cfg = {}) {
|
|
9
|
+
this.cfg = {
|
|
10
|
+
typeCast: mysql_db_1.typeCast,
|
|
11
|
+
...cfg,
|
|
12
|
+
};
|
|
13
|
+
this.db = new mysql_db_1.MysqlDB(this.cfg);
|
|
14
|
+
}
|
|
15
|
+
async ping() {
|
|
16
|
+
await this.db.ping();
|
|
17
|
+
}
|
|
18
|
+
async close() {
|
|
19
|
+
await this.db.close();
|
|
20
|
+
}
|
|
21
|
+
async createTable(table, opt = {}) {
|
|
22
|
+
if (opt.dropIfExists)
|
|
23
|
+
await this.dropTable(table);
|
|
24
|
+
// On blob sizes: https://tableplus.com/blog/2019/10/tinyblob-blob-mediumblob-longblob.html
|
|
25
|
+
// LONGBLOB supports up to 4gb
|
|
26
|
+
// MEDIUMBLOB up to 16Mb
|
|
27
|
+
const sql = `create table ${table} (id VARCHAR(64) PRIMARY KEY, v LONGBLOB NOT NULL)`;
|
|
28
|
+
this.db.cfg.logger.log(sql);
|
|
29
|
+
await this.db.runSQL({ sql });
|
|
30
|
+
}
|
|
31
|
+
async getByIds(table, ids) {
|
|
32
|
+
if (!ids.length)
|
|
33
|
+
return [];
|
|
34
|
+
const sql = `SELECT id,v FROM ${table} where id in (${ids.map(id => `"${id}"`).join(',')})`;
|
|
35
|
+
const rows = await this.db.runSQL({ sql });
|
|
36
|
+
return rows.map(({ id, v }) => [id, v]);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Use with caution!
|
|
40
|
+
*/
|
|
41
|
+
async dropTable(table) {
|
|
42
|
+
await this.db.runSQL({ sql: `DROP TABLE IF EXISTS ${table}` });
|
|
43
|
+
}
|
|
44
|
+
async deleteByIds(table, ids) {
|
|
45
|
+
const sql = `DELETE FROM ${table} WHERE id in (${ids.map(id => `"${id}"`).join(',')})`;
|
|
46
|
+
if (this.cfg.logSQL)
|
|
47
|
+
this.db.cfg.logger.log(sql);
|
|
48
|
+
await this.db.runSQL({ sql });
|
|
49
|
+
}
|
|
50
|
+
async saveBatch(table, entries) {
|
|
51
|
+
const statements = entries.map(([id, buf]) => {
|
|
52
|
+
return {
|
|
53
|
+
sql: `INSERT INTO ${table} (id, v) VALUES (?, ?)`,
|
|
54
|
+
values: [id, buf],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
await (0, js_lib_1.pMap)(statements, async (statement) => {
|
|
58
|
+
if (this.cfg.debug)
|
|
59
|
+
this.db.cfg.logger.log(statement.sql);
|
|
60
|
+
await this.db.runSQL(statement);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
streamIds(table, limit) {
|
|
64
|
+
let sql = `SELECT id FROM ${table}`;
|
|
65
|
+
if (limit)
|
|
66
|
+
sql += ` LIMIT ${limit}`;
|
|
67
|
+
if (this.cfg.logSQL)
|
|
68
|
+
this.db.cfg.logger.log(`stream: ${sql}`);
|
|
69
|
+
return this.db
|
|
70
|
+
.pool()
|
|
71
|
+
.query(sql)
|
|
72
|
+
.stream()
|
|
73
|
+
.pipe(new stream_1.Transform({
|
|
74
|
+
objectMode: true,
|
|
75
|
+
transform(row, _, cb) {
|
|
76
|
+
cb(null, row.id);
|
|
77
|
+
},
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
streamValues(table, limit) {
|
|
81
|
+
let sql = `SELECT v FROM ${table}`;
|
|
82
|
+
if (limit)
|
|
83
|
+
sql += ` LIMIT ${limit}`;
|
|
84
|
+
if (this.cfg.logSQL)
|
|
85
|
+
this.db.cfg.logger.log(`stream: ${sql}`);
|
|
86
|
+
return this.db
|
|
87
|
+
.pool()
|
|
88
|
+
.query(sql)
|
|
89
|
+
.stream()
|
|
90
|
+
.pipe(new stream_1.Transform({
|
|
91
|
+
objectMode: true,
|
|
92
|
+
transform(row, _, cb) {
|
|
93
|
+
cb(null, row.v);
|
|
94
|
+
},
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
streamEntries(table, limit) {
|
|
98
|
+
let sql = `SELECT id,v FROM ${table}`;
|
|
99
|
+
if (limit)
|
|
100
|
+
sql += ` LIMIT ${limit}`;
|
|
101
|
+
if (this.cfg.logSQL)
|
|
102
|
+
this.db.cfg.logger.log(`stream: ${sql}`);
|
|
103
|
+
return this.db
|
|
104
|
+
.pool()
|
|
105
|
+
.query(sql)
|
|
106
|
+
.stream()
|
|
107
|
+
.pipe(new stream_1.Transform({
|
|
108
|
+
objectMode: true,
|
|
109
|
+
transform(row, _, cb) {
|
|
110
|
+
cb(null, [row.id, row.v]);
|
|
111
|
+
},
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
async beginTransaction() {
|
|
115
|
+
await this.db.runSQL({ sql: `BEGIN TRANSACTION` });
|
|
116
|
+
}
|
|
117
|
+
async endTransaction() {
|
|
118
|
+
await this.db.runSQL({ sql: `END TRANSACTION` });
|
|
119
|
+
}
|
|
120
|
+
async count(table) {
|
|
121
|
+
const sql = `SELECT count(*) as cnt FROM ${table}`;
|
|
122
|
+
if (this.cfg.logSQL)
|
|
123
|
+
this.db.cfg.logger.log(sql);
|
|
124
|
+
const rows = await this.db.runSQL({ sql });
|
|
125
|
+
return rows[0].cnt;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.MySQLKeyValueDB = MySQLKeyValueDB;
|
package/dist/query.util.d.ts
CHANGED
|
@@ -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,
|
|
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;
|
package/dist/query.util.js
CHANGED
|
@@ -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,
|
|
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 =
|
|
38
|
-
Object.keys(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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": "^
|
|
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.
|
|
39
|
+
"version": "3.10.0",
|
|
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
|
[](https://www.npmjs.com/package/@naturalcycles/mysql-lib)
|
|
6
|
-
[](https://circleci.com/gh/NaturalCycles/mysql-lib)
|
|
7
6
|
[](https://github.com/prettier/prettier)
|
|
8
7
|
|
|
9
|
-
#
|
|
8
|
+
# Quick start
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getDBAdapter } from './dbAdapter'
|
|
2
2
|
import { MysqlDB, MysqlDBCfg, MysqlDBOptions, MysqlDBSaveOptions } from './mysql.db'
|
|
3
|
+
import { MySQLKeyValueDB } from './mysqlKeyValueDB'
|
|
3
4
|
import { jsonSchemaToMySQLDDL } from './schema/mysql.schema.util'
|
|
4
5
|
|
|
5
6
|
export type { MysqlDBCfg, MysqlDBOptions, MysqlDBSaveOptions }
|
|
6
7
|
|
|
7
|
-
export { MysqlDB, jsonSchemaToMySQLDDL, getDBAdapter }
|
|
8
|
+
export { MysqlDB, MySQLKeyValueDB, jsonSchemaToMySQLDDL, getDBAdapter }
|
package/src/mysql.db.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Transform } from 'stream'
|
|
2
2
|
import { promisify } from 'util'
|
|
3
|
-
import { AnyObjectWithId } from '@naturalcycles/db-lib/src/db.model'
|
|
4
3
|
import {
|
|
5
4
|
BaseCommonDB,
|
|
6
5
|
CommonDB,
|
|
@@ -8,18 +7,21 @@ import {
|
|
|
8
7
|
CommonDBOptions,
|
|
9
8
|
CommonDBSaveOptions,
|
|
10
9
|
DBQuery,
|
|
11
|
-
ObjectWithId,
|
|
12
10
|
RunQueryResult,
|
|
13
11
|
} from '@naturalcycles/db-lib'
|
|
14
12
|
import {
|
|
15
|
-
|
|
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 {
|
|
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'
|
|
@@ -48,14 +50,17 @@ export interface MysqlDBCfg extends PoolConfig {
|
|
|
48
50
|
* If true - will emit logs of connection events.
|
|
49
51
|
*/
|
|
50
52
|
debugConnections?: boolean
|
|
51
|
-
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Default to `console`
|
|
56
|
+
*/
|
|
57
|
+
logger?: CommonLogger
|
|
58
|
+
}
|
|
54
59
|
|
|
55
60
|
const BOOLEAN_TYPES = new Set(['TINY', 'TINYINT', 'INT'])
|
|
56
61
|
const BOOLEAN_BIT_TYPES = new Set(['BIT'])
|
|
57
62
|
|
|
58
|
-
const typeCast: TypeCast = (field, next) => {
|
|
63
|
+
export const typeCast: TypeCast = (field, next) => {
|
|
59
64
|
// cast to boolean
|
|
60
65
|
if (field.length === 1) {
|
|
61
66
|
if (BOOLEAN_BIT_TYPES.has(field.type)) {
|
|
@@ -86,10 +91,11 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
86
91
|
// password: MYSQL_PW,
|
|
87
92
|
// database: MYSQL_DB,
|
|
88
93
|
...cfg,
|
|
94
|
+
logger: commonLoggerPrefix(cfg.logger || console, '[mysql]'),
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
97
|
|
|
92
|
-
cfg
|
|
98
|
+
cfg: MysqlDBCfg & { logger: CommonLogger }
|
|
93
99
|
|
|
94
100
|
override async ping(): Promise<void> {
|
|
95
101
|
const con = await this.getConnection()
|
|
@@ -99,29 +105,29 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
99
105
|
@_Memo()
|
|
100
106
|
pool(): Pool {
|
|
101
107
|
const pool = mysql.createPool(this.cfg)
|
|
102
|
-
const { host, database } = this.cfg
|
|
103
|
-
log(`connected to ${white(host + '/' + database)}`)
|
|
108
|
+
const { host, database, logger } = this.cfg
|
|
109
|
+
logger.log(`connected to ${white(host + '/' + database)}`)
|
|
104
110
|
|
|
105
111
|
if (this.cfg.debugConnections) {
|
|
106
112
|
pool.on('acquire', con => {
|
|
107
|
-
log(`acquire(${con.threadId})`)
|
|
113
|
+
logger.log(`acquire(${con.threadId})`)
|
|
108
114
|
})
|
|
109
115
|
|
|
110
116
|
pool.on('connection', con => {
|
|
111
|
-
log(`connection(${con.threadId})`)
|
|
117
|
+
logger.log(`connection(${con.threadId})`)
|
|
112
118
|
})
|
|
113
119
|
|
|
114
120
|
pool.on('enqueue', () => {
|
|
115
|
-
log(`enqueue`)
|
|
121
|
+
logger.log(`enqueue`)
|
|
116
122
|
})
|
|
117
123
|
|
|
118
124
|
pool.on('release', con => {
|
|
119
|
-
log(`release(${con.threadId})`)
|
|
125
|
+
logger.log(`release(${con.threadId})`)
|
|
120
126
|
})
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
pool.on('error', err => {
|
|
124
|
-
|
|
130
|
+
logger.error(err)
|
|
125
131
|
})
|
|
126
132
|
|
|
127
133
|
return pool
|
|
@@ -130,7 +136,8 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
130
136
|
async close(): Promise<void> {
|
|
131
137
|
const pool = this.pool()
|
|
132
138
|
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
133
|
-
await promisify(pool.end.bind(pool))
|
|
139
|
+
await promisify(pool.end.bind(pool))()
|
|
140
|
+
this.cfg.logger.log('closed')
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
/**
|
|
@@ -151,16 +158,16 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
151
158
|
const { threadId } = con
|
|
152
159
|
|
|
153
160
|
if (this.cfg.debugConnections) {
|
|
154
|
-
log(`createSingleConnection(${threadId})`)
|
|
161
|
+
this.cfg.logger.log(`createSingleConnection(${threadId})`)
|
|
155
162
|
|
|
156
|
-
con.on('connect', () => log(`createSingleConnection(${threadId}).connect`))
|
|
157
|
-
con.on('drain', () => log(`createSingleConnection(${threadId}).drain`))
|
|
158
|
-
con.on('enqueue', () => log(`createSingleConnection(${threadId}).enqueue`))
|
|
159
|
-
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`))
|
|
160
167
|
}
|
|
161
168
|
|
|
162
169
|
con.on('error', err => {
|
|
163
|
-
|
|
170
|
+
this.cfg.logger.error(`createSingleConnection(${threadId}).error`, err)
|
|
164
171
|
})
|
|
165
172
|
|
|
166
173
|
return con
|
|
@@ -184,14 +191,24 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
184
191
|
_opt: MysqlDBOptions = {},
|
|
185
192
|
): Promise<RunQueryResult<OUT>> {
|
|
186
193
|
const sql = dbQueryToSQLSelect(q)
|
|
187
|
-
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
|
+
|
|
188
205
|
return {
|
|
189
|
-
rows
|
|
206
|
+
rows,
|
|
190
207
|
}
|
|
191
208
|
}
|
|
192
209
|
|
|
193
210
|
async runSQL<RESULT>(q: QueryOptions): Promise<RESULT> {
|
|
194
|
-
if (this.cfg.logSQL) log(q.sql, q.values)
|
|
211
|
+
if (this.cfg.logSQL) this.cfg.logger.log(...[q.sql, q.values].filter(Boolean))
|
|
195
212
|
|
|
196
213
|
return await new Promise<RESULT>((resolve, reject) => {
|
|
197
214
|
this.pool().query(q, (err, res) => {
|
|
@@ -215,7 +232,7 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
215
232
|
): ReadableTyped<OUT> {
|
|
216
233
|
const sql = dbQueryToSQLSelect(q)
|
|
217
234
|
|
|
218
|
-
if (this.cfg.logSQL) log(`stream: ${sql}`)
|
|
235
|
+
if (this.cfg.logSQL) this.cfg.logger.log(`stream: ${sql}`)
|
|
219
236
|
|
|
220
237
|
// return this.streamSQL(sql, opt)
|
|
221
238
|
return this.pool()
|
|
@@ -225,7 +242,7 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
225
242
|
new Transform({
|
|
226
243
|
objectMode: true,
|
|
227
244
|
transform(row, _encoding, cb) {
|
|
228
|
-
cb(null,
|
|
245
|
+
cb(null, _filterUndefinedValues(row, true))
|
|
229
246
|
},
|
|
230
247
|
}),
|
|
231
248
|
)
|
|
@@ -241,13 +258,13 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
241
258
|
|
|
242
259
|
// Stringify object values
|
|
243
260
|
const rows = _rows.map(row =>
|
|
244
|
-
_mapValues(row, v => {
|
|
261
|
+
_mapValues(row, (_k, v) => {
|
|
245
262
|
return v && typeof v === 'object' && !Buffer.isBuffer(v) ? JSON.stringify(v) : v
|
|
246
263
|
}),
|
|
247
264
|
)
|
|
248
265
|
|
|
249
266
|
// inserts are split into multiple sentenses to respect the max_packet_size (1Mb usually)
|
|
250
|
-
const sqls = insertSQL(table, rows)
|
|
267
|
+
const sqls = insertSQL(table, rows, 'REPLACE', this.cfg.logger)
|
|
251
268
|
|
|
252
269
|
for await (const sql of sqls) {
|
|
253
270
|
await this.runSQL({ sql })
|
|
@@ -308,6 +325,6 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
308
325
|
sql: `describe ${mysql.escapeId(table)}`,
|
|
309
326
|
})
|
|
310
327
|
|
|
311
|
-
return mysqlTableStatsToJsonSchemaField<ROW>(table, stats)
|
|
328
|
+
return mysqlTableStatsToJsonSchemaField<ROW>(table, stats, this.cfg.logger)
|
|
312
329
|
}
|
|
313
330
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Transform } from 'stream'
|
|
2
|
+
import { CommonDBCreateOptions, CommonKeyValueDB, KeyValueDBTuple } from '@naturalcycles/db-lib'
|
|
3
|
+
import { pMap } from '@naturalcycles/js-lib'
|
|
4
|
+
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
5
|
+
import { QueryOptions } from 'mysql'
|
|
6
|
+
import { MysqlDB, MysqlDBCfg, typeCast } from './mysql.db'
|
|
7
|
+
|
|
8
|
+
interface KeyValueObject {
|
|
9
|
+
id: string
|
|
10
|
+
v: Buffer
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class MySQLKeyValueDB implements CommonKeyValueDB {
|
|
14
|
+
constructor(cfg: MysqlDBCfg = {}) {
|
|
15
|
+
this.cfg = {
|
|
16
|
+
typeCast,
|
|
17
|
+
...cfg,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.db = new MysqlDB(this.cfg)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
cfg: MysqlDBCfg
|
|
24
|
+
|
|
25
|
+
db: MysqlDB
|
|
26
|
+
|
|
27
|
+
async ping(): Promise<void> {
|
|
28
|
+
await this.db.ping()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async close(): Promise<void> {
|
|
32
|
+
await this.db.close()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async createTable(table: string, opt: CommonDBCreateOptions = {}): Promise<void> {
|
|
36
|
+
if (opt.dropIfExists) await this.dropTable(table)
|
|
37
|
+
|
|
38
|
+
// On blob sizes: https://tableplus.com/blog/2019/10/tinyblob-blob-mediumblob-longblob.html
|
|
39
|
+
// LONGBLOB supports up to 4gb
|
|
40
|
+
// MEDIUMBLOB up to 16Mb
|
|
41
|
+
const sql = `create table ${table} (id VARCHAR(64) PRIMARY KEY, v LONGBLOB NOT NULL)`
|
|
42
|
+
this.db.cfg.logger.log(sql)
|
|
43
|
+
await this.db.runSQL({ sql })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]> {
|
|
47
|
+
if (!ids.length) return []
|
|
48
|
+
|
|
49
|
+
const sql = `SELECT id,v FROM ${table} where id in (${ids.map(id => `"${id}"`).join(',')})`
|
|
50
|
+
|
|
51
|
+
const rows = await this.db.runSQL<KeyValueObject[]>({ sql })
|
|
52
|
+
|
|
53
|
+
return rows.map(({ id, v }) => [id, v])
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Use with caution!
|
|
58
|
+
*/
|
|
59
|
+
async dropTable(table: string): Promise<void> {
|
|
60
|
+
await this.db.runSQL({ sql: `DROP TABLE IF EXISTS ${table}` })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async deleteByIds(table: string, ids: string[]): Promise<void> {
|
|
64
|
+
const sql = `DELETE FROM ${table} WHERE id in (${ids.map(id => `"${id}"`).join(',')})`
|
|
65
|
+
if (this.cfg.logSQL) this.db.cfg.logger.log(sql)
|
|
66
|
+
await this.db.runSQL({ sql })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async saveBatch(table: string, entries: KeyValueDBTuple[]): Promise<void> {
|
|
70
|
+
const statements: QueryOptions[] = entries.map(([id, buf]) => {
|
|
71
|
+
return {
|
|
72
|
+
sql: `INSERT INTO ${table} (id, v) VALUES (?, ?)`,
|
|
73
|
+
values: [id, buf],
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
await pMap(statements, async statement => {
|
|
78
|
+
if (this.cfg.debug) this.db.cfg.logger.log(statement.sql)
|
|
79
|
+
await this.db.runSQL(statement)
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
streamIds(table: string, limit?: number): ReadableTyped<string> {
|
|
84
|
+
let sql = `SELECT id FROM ${table}`
|
|
85
|
+
if (limit) sql += ` LIMIT ${limit}`
|
|
86
|
+
if (this.cfg.logSQL) this.db.cfg.logger.log(`stream: ${sql}`)
|
|
87
|
+
|
|
88
|
+
return this.db
|
|
89
|
+
.pool()
|
|
90
|
+
.query(sql)
|
|
91
|
+
.stream()
|
|
92
|
+
.pipe(
|
|
93
|
+
new Transform({
|
|
94
|
+
objectMode: true,
|
|
95
|
+
transform(row, _, cb) {
|
|
96
|
+
cb(null, row.id)
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
streamValues(table: string, limit?: number): ReadableTyped<Buffer> {
|
|
103
|
+
let sql = `SELECT v FROM ${table}`
|
|
104
|
+
if (limit) sql += ` LIMIT ${limit}`
|
|
105
|
+
if (this.cfg.logSQL) this.db.cfg.logger.log(`stream: ${sql}`)
|
|
106
|
+
|
|
107
|
+
return this.db
|
|
108
|
+
.pool()
|
|
109
|
+
.query(sql)
|
|
110
|
+
.stream()
|
|
111
|
+
.pipe(
|
|
112
|
+
new Transform({
|
|
113
|
+
objectMode: true,
|
|
114
|
+
transform(row, _, cb) {
|
|
115
|
+
cb(null, row.v)
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple> {
|
|
122
|
+
let sql = `SELECT id,v FROM ${table}`
|
|
123
|
+
if (limit) sql += ` LIMIT ${limit}`
|
|
124
|
+
if (this.cfg.logSQL) this.db.cfg.logger.log(`stream: ${sql}`)
|
|
125
|
+
|
|
126
|
+
return this.db
|
|
127
|
+
.pool()
|
|
128
|
+
.query(sql)
|
|
129
|
+
.stream()
|
|
130
|
+
.pipe(
|
|
131
|
+
new Transform({
|
|
132
|
+
objectMode: true,
|
|
133
|
+
transform(row, _, cb) {
|
|
134
|
+
cb(null, [row.id, row.v])
|
|
135
|
+
},
|
|
136
|
+
}),
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async beginTransaction(): Promise<void> {
|
|
141
|
+
await this.db.runSQL({ sql: `BEGIN TRANSACTION` })
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async endTransaction(): Promise<void> {
|
|
145
|
+
await this.db.runSQL({ sql: `END TRANSACTION` })
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async count(table: string): Promise<number> {
|
|
149
|
+
const sql = `SELECT count(*) as cnt FROM ${table}`
|
|
150
|
+
if (this.cfg.logSQL) this.db.cfg.logger.log(sql)
|
|
151
|
+
|
|
152
|
+
const rows = await this.db.runSQL<{ cnt: number }[]>({ sql })
|
|
153
|
+
return rows[0]!.cnt
|
|
154
|
+
}
|
|
155
|
+
}
|
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
|
-
|
|
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 =
|
|
52
|
-
Object.keys(
|
|
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 =
|
|
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
|
-
|
|
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`,
|
|
@@ -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
|
-
|
|
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,205 +0,0 @@
|
|
|
1
|
-
## [3.8.1](https://github.com/NaturalCycles/mysql-lib/compare/v3.8.0...v3.8.1) (2021-10-17)
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
### Bug Fixes
|
|
5
|
-
|
|
6
|
-
* adapt to latest db-lib ([9a368ef](https://github.com/NaturalCycles/mysql-lib/commit/9a368ef4b9d964c09520660319059e9cb67bcf76))
|
|
7
|
-
|
|
8
|
-
# [3.8.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.7.1...v3.8.0) (2021-10-04)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
### Features
|
|
12
|
-
|
|
13
|
-
* modernize ([6e8f0a2](https://github.com/NaturalCycles/mysql-lib/commit/6e8f0a254a67902cbc185f034f58cd4f6885449d))
|
|
14
|
-
|
|
15
|
-
## [3.7.1](https://github.com/NaturalCycles/mysql-lib/compare/v3.7.0...v3.7.1) (2020-06-03)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
### Bug Fixes
|
|
19
|
-
|
|
20
|
-
* modernize ([517f695](https://github.com/NaturalCycles/mysql-lib/commit/517f6952feb15059730f4f7443a295bf596053fc))
|
|
21
|
-
|
|
22
|
-
# [3.7.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.6.0...v3.7.0) (2020-04-08)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
### Features
|
|
26
|
-
|
|
27
|
-
* modernize ([e0e2890](https://github.com/NaturalCycles/mysql-lib/commit/e0e2890cb31e88fcbdae63c417d6cc5c6a52d4e8))
|
|
28
|
-
|
|
29
|
-
# [3.6.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.5.2...v3.6.0) (2020-03-01)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
### Features
|
|
33
|
-
|
|
34
|
-
* deps ([008aa10](https://github.com/NaturalCycles/mysql-lib/commit/008aa10d059734d76c2354553a8dc35c8117b3b2))
|
|
35
|
-
|
|
36
|
-
## [3.5.2](https://github.com/NaturalCycles/mysql-lib/compare/v3.5.1...v3.5.2) (2019-11-20)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
### Bug Fixes
|
|
40
|
-
|
|
41
|
-
* BIT typecast to boolean correctly ([0bc815e](https://github.com/NaturalCycles/mysql-lib/commit/0bc815e025a33ab9fae5405c8b97dd53aae392ca))
|
|
42
|
-
|
|
43
|
-
## [3.5.1](https://github.com/NaturalCycles/mysql-lib/compare/v3.5.0...v3.5.1) (2019-11-20)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
### Bug Fixes
|
|
47
|
-
|
|
48
|
-
* correctly type-cast booleans with value=NULL ([e208b4c](https://github.com/NaturalCycles/mysql-lib/commit/e208b4ca16f700b6ed7bf95c872f68f934894992))
|
|
49
|
-
|
|
50
|
-
# [3.5.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.4.0...v3.5.0) (2019-11-09)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
### Features
|
|
54
|
-
|
|
55
|
-
* implement getDBAdapter. Allow empty (default) configuration ([bcf12dc](https://github.com/NaturalCycles/mysql-lib/commit/bcf12dc9360f525749f9bb10965046b4c56d849a))
|
|
56
|
-
|
|
57
|
-
# [3.4.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.3.1...v3.4.0) (2019-11-09)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
### Features
|
|
61
|
-
|
|
62
|
-
* auto JSON.stringify objects (and arrays) ([0d9aaed](https://github.com/NaturalCycles/mysql-lib/commit/0d9aaedf8f2920a51dacf13b6723eb98c61b34c3))
|
|
63
|
-
|
|
64
|
-
## [3.3.1](https://github.com/NaturalCycles/mysql-lib/compare/v3.3.0...v3.3.1) (2019-11-09)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
### Bug Fixes
|
|
68
|
-
|
|
69
|
-
* use LONGBLOB for binary columns ([ae4facc](https://github.com/NaturalCycles/mysql-lib/commit/ae4facc76bc03b8aafa5141199a98bef530f6913))
|
|
70
|
-
|
|
71
|
-
# [3.3.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.2.1...v3.3.0) (2019-11-09)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
### Features
|
|
75
|
-
|
|
76
|
-
* mapNameToMySQL ([c4bf40c](https://github.com/NaturalCycles/mysql-lib/commit/c4bf40cd6aefaf78219a2588ec803cf74004bc07))
|
|
77
|
-
|
|
78
|
-
## [3.2.1](https://github.com/NaturalCycles/mysql-lib/compare/v3.2.0...v3.2.1) (2019-11-09)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
### Bug Fixes
|
|
82
|
-
|
|
83
|
-
* createTable() ([7983085](https://github.com/NaturalCycles/mysql-lib/commit/7983085017562a4e4a8f7dd38eb41ae6b9fc30ed))
|
|
84
|
-
|
|
85
|
-
# [3.2.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.1.0...v3.2.0) (2019-11-09)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
### Bug Fixes
|
|
89
|
-
|
|
90
|
-
* emoji support ([1262fc4](https://github.com/NaturalCycles/mysql-lib/commit/1262fc4d9ad39c818833b5cfc31b3a65979cdea0))
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
### Features
|
|
94
|
-
|
|
95
|
-
* auto split long sql queries ([c7c780f](https://github.com/NaturalCycles/mysql-lib/commit/c7c780f295d7ded9f418509f3160a512ec54c7e7))
|
|
96
|
-
|
|
97
|
-
# [3.1.0](https://github.com/NaturalCycles/mysql-lib/compare/v3.0.0...v3.1.0) (2019-11-08)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
### Features
|
|
101
|
-
|
|
102
|
-
* commonSchemaToMySQLDDL ([09168c5](https://github.com/NaturalCycles/mysql-lib/commit/09168c563cbb629ca47121a1aac6a125dd23c144))
|
|
103
|
-
* implement getTables(), getTableSchema() ([20a7746](https://github.com/NaturalCycles/mysql-lib/commit/20a7746c8c4c88dfca8365d1e8d2decce3f47efa))
|
|
104
|
-
|
|
105
|
-
# [3.0.0](https://github.com/NaturalCycles/mysql-lib/compare/v2.0.2...v3.0.0) (2019-11-02)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
### Features
|
|
109
|
-
|
|
110
|
-
* adapt to db-lib@3 ([7d13f54](https://github.com/NaturalCycles/mysql-lib/commit/7d13f540c92dd59b014d1a502c1bff937fad567f))
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
### BREAKING CHANGES
|
|
114
|
-
|
|
115
|
-
* ^^^
|
|
116
|
-
|
|
117
|
-
## [2.0.2](https://github.com/NaturalCycles/mysql-lib/compare/v2.0.1...v2.0.2) (2019-10-20)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
### Bug Fixes
|
|
121
|
-
|
|
122
|
-
* adopt to db-lib ([6da7d78](https://github.com/NaturalCycles/mysql-lib/commit/6da7d78))
|
|
123
|
-
|
|
124
|
-
## [2.0.1](https://github.com/NaturalCycles/mysql-lib/compare/v2.0.0...v2.0.1) (2019-10-19)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
### Bug Fixes
|
|
128
|
-
|
|
129
|
-
* use Readable ([a0d09c1](https://github.com/NaturalCycles/mysql-lib/commit/a0d09c1))
|
|
130
|
-
|
|
131
|
-
# [2.0.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.7.1...v2.0.0) (2019-10-18)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
### Features
|
|
135
|
-
|
|
136
|
-
* implement CommonDB 2.0 ([9de0e9f](https://github.com/NaturalCycles/mysql-lib/commit/9de0e9f))
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
### BREAKING CHANGES
|
|
140
|
-
|
|
141
|
-
* ^^^
|
|
142
|
-
|
|
143
|
-
## [1.7.1](https://github.com/NaturalCycles/mysql-lib/compare/v1.7.0...v1.7.1) (2019-10-18)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
### Bug Fixes
|
|
147
|
-
|
|
148
|
-
* pin @types/hapi__joi ([3e3cf5a](https://github.com/NaturalCycles/mysql-lib/commit/3e3cf5a))
|
|
149
|
-
|
|
150
|
-
# [1.7.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.6.0...v1.7.0) (2019-09-30)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
### Features
|
|
154
|
-
|
|
155
|
-
* tiny change to use pool.query inst of getConnection() ([6ffe1b4](https://github.com/NaturalCycles/mysql-lib/commit/6ffe1b4))
|
|
156
|
-
|
|
157
|
-
# [1.6.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.5.0...v1.6.0) (2019-09-30)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
### Features
|
|
161
|
-
|
|
162
|
-
* offset ([78710a1](https://github.com/NaturalCycles/mysql-lib/commit/78710a1))
|
|
163
|
-
|
|
164
|
-
# [1.5.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.4.0...v1.5.0) (2019-09-30)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
### Features
|
|
168
|
-
|
|
169
|
-
* testing simpler streamSQL impl ([0887aa3](https://github.com/NaturalCycles/mysql-lib/commit/0887aa3))
|
|
170
|
-
|
|
171
|
-
# [1.4.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.3.0...v1.4.0) (2019-09-30)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
### Features
|
|
175
|
-
|
|
176
|
-
* stream to use it's own separate connection ([43615f3](https://github.com/NaturalCycles/mysql-lib/commit/43615f3))
|
|
177
|
-
|
|
178
|
-
# [1.3.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.2.0...v1.3.0) (2019-09-30)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
### Features
|
|
182
|
-
|
|
183
|
-
* cfg.debugConnections ([70218ba](https://github.com/NaturalCycles/mysql-lib/commit/70218ba))
|
|
184
|
-
|
|
185
|
-
# [1.2.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.1.0...v1.2.0) (2019-09-28)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
### Features
|
|
189
|
-
|
|
190
|
-
* add BIT cast to boolean ([899cce4](https://github.com/NaturalCycles/mysql-lib/commit/899cce4))
|
|
191
|
-
|
|
192
|
-
# [1.1.0](https://github.com/NaturalCycles/mysql-lib/compare/v1.0.0...v1.1.0) (2019-09-21)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
### Features
|
|
196
|
-
|
|
197
|
-
* modernize, implement saveBatch ([5595b6a](https://github.com/NaturalCycles/mysql-lib/commit/5595b6a))
|
|
198
|
-
|
|
199
|
-
# 1.0.0 (2019-06-03)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
### Features
|
|
203
|
-
|
|
204
|
-
* first version ([02620fb](https://github.com/NaturalCycles/mysql-lib/commit/02620fb))
|
|
205
|
-
* init project by create-module ([eee50b7](https://github.com/NaturalCycles/mysql-lib/commit/eee50b7))
|