@axiosleo/orm-mysql 0.8.6 → 0.9.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/README.md +26 -1
- package/bin/orm-mysql.js +16 -0
- package/commands/generate.js +57 -0
- package/commands/migrate.js +58 -0
- package/examples/migration/.connect.js +9 -0
- package/examples/migration/20240101.create.table.js +28 -0
- package/examples/migration/20240102.1.create.column.js +14 -0
- package/examples/migration/20240102.2.create.index.js +1 -0
- package/index.d.ts +116 -14
- package/package.json +6 -2
- package/src/builder.js +240 -1
- package/src/client.js +18 -24
- package/src/core.js +73 -0
- package/src/migration.d.ts +25 -0
- package/src/migration.js +319 -0
- package/src/operator.js +26 -6
- package/src/query.js +1 -1
- package/src/transaction.js +9 -12
- package/src/utils.js +5 -39
package/README.md
CHANGED
|
@@ -195,6 +195,31 @@ try {
|
|
|
195
195
|
}
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
+
### Migration
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
'use strict';
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @param {import('@axiosleo/orm-mysql').MigrationInterface} migration
|
|
205
|
+
*/
|
|
206
|
+
function up(migration) {
|
|
207
|
+
migration.createDatabase({ database_name: 'demo' });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @param {import('@axiosleo/orm-mysql').MigrationInterface} migration
|
|
212
|
+
*/
|
|
213
|
+
function down(migration) {
|
|
214
|
+
migration.dropDatabase({ database_name: 'demo' });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = {
|
|
218
|
+
up,
|
|
219
|
+
down
|
|
220
|
+
};
|
|
221
|
+
```
|
|
222
|
+
|
|
198
223
|
### Custom query driver
|
|
199
224
|
|
|
200
225
|
```javascript
|
|
@@ -215,7 +240,7 @@ const conn = createClient({
|
|
|
215
240
|
|
|
216
241
|
const hanlder = new QueryHandler(conn, {
|
|
217
242
|
driver: 'custom',
|
|
218
|
-
|
|
243
|
+
queryHandler: (con, options) => {
|
|
219
244
|
const builder = new Builder(options);
|
|
220
245
|
return new Promise((resolve, reject) => {
|
|
221
246
|
if (options.operator === 'select') {
|
package/bin/orm-mysql.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { App } = require('@axiosleo/cli-tool');
|
|
7
|
+
|
|
8
|
+
const app = new App({
|
|
9
|
+
name: 'MySQL ORM CLI',
|
|
10
|
+
desc: 'migrate, model, seed, etc.',
|
|
11
|
+
bin: 'orm-mysql',
|
|
12
|
+
version: '0.9.0',
|
|
13
|
+
commands_dir: path.join(__dirname, '../commands'),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
app.start();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { Command, printer } = require('@axiosleo/cli-tool');
|
|
5
|
+
const { _exists, _write } = require('@axiosleo/cli-tool/src/helper/fs');
|
|
6
|
+
const { _snake_case } = require('@axiosleo/cli-tool/src/helper/str');
|
|
7
|
+
|
|
8
|
+
class GenerateCommand extends Command {
|
|
9
|
+
constructor() {
|
|
10
|
+
super({
|
|
11
|
+
name: 'generate',
|
|
12
|
+
desc: ''
|
|
13
|
+
});
|
|
14
|
+
this.addArgument('name', 'Migration name', 'required', '');
|
|
15
|
+
this.addArgument('dir', 'Migration scripts directory', 'optional', process.cwd());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {*} args
|
|
20
|
+
* @param {*} options
|
|
21
|
+
* @param {string[]} argList
|
|
22
|
+
* @param {import('@axiosleo/cli-tool').App} app
|
|
23
|
+
*/
|
|
24
|
+
async exec(args, options) {
|
|
25
|
+
const { name, dir } = args;
|
|
26
|
+
const fileName = parseInt((new Date()).valueOf() / 1000) + '.' + _snake_case(name) + '.js';
|
|
27
|
+
const filePath = path.join(dir, fileName);
|
|
28
|
+
if (await _exists(filePath)) {
|
|
29
|
+
printer.error('Migration script file already exists: ' + fileName);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const template = `
|
|
33
|
+
'use strict';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {import('@axiosleo/orm-mysql').MigrationInterface} migration
|
|
37
|
+
*/
|
|
38
|
+
function up(migration) {
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {import('@axiosleo/orm-mysql').MigrationInterface} migration
|
|
43
|
+
*/
|
|
44
|
+
function down(migration) {
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
up,
|
|
49
|
+
down
|
|
50
|
+
};
|
|
51
|
+
`;
|
|
52
|
+
await _write(filePath, template);
|
|
53
|
+
printer.info('Migration file created: ' + filePath);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = GenerateCommand;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Command, debug, Workflow } = require('@axiosleo/cli-tool');
|
|
4
|
+
const is = require('@axiosleo/cli-tool/src/helper/is');
|
|
5
|
+
const migration = require('../src/migration');
|
|
6
|
+
|
|
7
|
+
class MigrateCommand extends Command {
|
|
8
|
+
constructor() {
|
|
9
|
+
super({
|
|
10
|
+
name: 'migrate',
|
|
11
|
+
desc: 'Migrate database',
|
|
12
|
+
});
|
|
13
|
+
this.addArgument('action', 'up or down', 'required');
|
|
14
|
+
this.addArgument('dir', 'migration directory', 'optional', process.cwd());
|
|
15
|
+
|
|
16
|
+
this.addOption('debug', 'd', '[false] debug mode', 'optional', false);
|
|
17
|
+
this.addOption('host', null, '[localhost] mysql host', 'optional', 'localhost');
|
|
18
|
+
this.addOption('user', null, '[root] username for connect to the database', 'optional', 'root');
|
|
19
|
+
this.addOption('pass', null, 'password to connect to the database', 'optional', '');
|
|
20
|
+
this.addOption('port', null, '[3306] port number to connect to the database', 'optional', 3306);
|
|
21
|
+
this.addOption('db', null, 'database name', 'optional', '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {*} args
|
|
26
|
+
* @param {*} options
|
|
27
|
+
*/
|
|
28
|
+
async exec(args, options) {
|
|
29
|
+
const workflow = new Workflow(migration);
|
|
30
|
+
try {
|
|
31
|
+
await workflow.start({
|
|
32
|
+
action: args.action,
|
|
33
|
+
config: {
|
|
34
|
+
dir: args.dir
|
|
35
|
+
},
|
|
36
|
+
connection: {
|
|
37
|
+
host: options.host,
|
|
38
|
+
port: is.number(options.port) ?
|
|
39
|
+
options.port : parseInt(options.port),
|
|
40
|
+
user: options.user,
|
|
41
|
+
password: options.pass,
|
|
42
|
+
database: options.db
|
|
43
|
+
},
|
|
44
|
+
debug: options.debug
|
|
45
|
+
});
|
|
46
|
+
process.exit(0);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
if (e.curr && e.curr.error) {
|
|
49
|
+
debug.error(e.curr.error);
|
|
50
|
+
} else {
|
|
51
|
+
debug.log(e);
|
|
52
|
+
}
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = MigrateCommand;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {import('../../').MigrationInterface} migration
|
|
5
|
+
*/
|
|
6
|
+
function up(migration) {
|
|
7
|
+
migration.createTable({
|
|
8
|
+
table_name: 'organization',
|
|
9
|
+
columns: [{
|
|
10
|
+
column_name: 'id',
|
|
11
|
+
type: 'int(11)',
|
|
12
|
+
not_null: true,
|
|
13
|
+
is_primary_key: true,
|
|
14
|
+
}]
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {import('../../').MigrationInterface} migration
|
|
20
|
+
*/
|
|
21
|
+
function down(migration) {
|
|
22
|
+
migration.dropTable({ table_name: 'table1' });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
up,
|
|
27
|
+
down
|
|
28
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';
|
package/index.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export interface TableOption {
|
|
|
50
50
|
|
|
51
51
|
export type QueryOperatorBaseOptions = {
|
|
52
52
|
driver?: string | 'mysql';
|
|
53
|
-
|
|
53
|
+
queryHandler?: QueryHandler;
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
export type QueryOperatorOptions = QueryOperatorBaseOptions & {
|
|
@@ -161,6 +161,17 @@ export declare class QueryHandler {
|
|
|
161
161
|
* @param condition
|
|
162
162
|
*/
|
|
163
163
|
upsert(tableName: string, data: any, condition: Record<string, ConditionValueType>): Promise<OkPacket>;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @param database default is options.database
|
|
167
|
+
*/
|
|
168
|
+
existDatabase(database?: string): Promise<boolean>;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @param table
|
|
172
|
+
* @param database default is options.database
|
|
173
|
+
*/
|
|
174
|
+
existTable(table: string, database?: string): Promise<boolean>;
|
|
164
175
|
}
|
|
165
176
|
|
|
166
177
|
export declare class TransactionOperator extends QueryOperator {
|
|
@@ -256,26 +267,117 @@ export declare class Builder {
|
|
|
256
267
|
|
|
257
268
|
export declare class MySQLClient extends QueryHandler {
|
|
258
269
|
|
|
259
|
-
constructor(options?: ConnectionOptions, name?: string | null | undefined);
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* @param database default is options.database
|
|
263
|
-
*/
|
|
264
|
-
existDatabase(database?: string): Promise<boolean>;
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* @param table
|
|
268
|
-
* @param database default is options.database
|
|
269
|
-
*/
|
|
270
|
-
existTable(table: string, database?: string): Promise<boolean>;
|
|
270
|
+
constructor(options?: ConnectionOptions, name?: string | null | undefined, type?: 'default' | 'promise' | 'pool');
|
|
271
271
|
|
|
272
272
|
/**
|
|
273
|
-
*
|
|
274
273
|
* @param query
|
|
275
274
|
* @param operator default is 'select'
|
|
276
275
|
*/
|
|
277
276
|
execQuery(query: Query, operator?: OperatorType): Promise<QueryResult>;
|
|
278
277
|
|
|
279
278
|
close(): Promise<void>;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
type FieldType =
|
|
282
|
+
'TINYINT' | 'SMALLINT' | 'MEDIUMINT' | 'INT' | 'BIGINT' | 'FLOAT' | 'DOUBLE' | 'DECIMAL' |
|
|
283
|
+
'DATE' | 'TIME' | 'YEAR' | 'DATETIME' | 'TIMESTAMP' |
|
|
284
|
+
'CHAR' | 'VARCHAR' | 'TINYBLOB' | 'TINYTEXT' | 'BLOB' | 'TEXT' | 'MEDIUMBLOB' | 'MEDIUMTEXT' |
|
|
285
|
+
'LONGBLOB' | 'LONGTEXT' | 'ENUM' | 'SET' | 'JSON';
|
|
286
|
+
|
|
287
|
+
interface ColumnItem {
|
|
288
|
+
type: FieldType,
|
|
289
|
+
length?: number,
|
|
290
|
+
unsigned?: boolean,
|
|
291
|
+
allowNull?: boolean,
|
|
292
|
+
default?: string | number | boolean | null | 'timestamp',
|
|
293
|
+
onUpdate?: boolean,
|
|
294
|
+
comment?: string,
|
|
295
|
+
autoIncrement?: boolean,
|
|
296
|
+
primaryKey?: boolean,
|
|
297
|
+
uniqIndex?: boolean
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
interface CreateColumnOptions {
|
|
301
|
+
length?: number,
|
|
302
|
+
unsigned?: boolean,
|
|
303
|
+
allowNull?: boolean,
|
|
304
|
+
default?: string | number | boolean | null | 'timestamp',
|
|
305
|
+
comment?: string,
|
|
306
|
+
autoIncrement?: boolean,
|
|
307
|
+
primaryKey?: boolean,
|
|
308
|
+
uniqIndex?: boolean
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
interface CreateIndexOptions {
|
|
312
|
+
unique?: boolean,
|
|
313
|
+
fulltext?: boolean,
|
|
314
|
+
spatial?: boolean
|
|
315
|
+
}
|
|
280
316
|
|
|
317
|
+
export type ManageBuilderOptions = {
|
|
318
|
+
operator: 'create' | 'drop' | 'alter';
|
|
319
|
+
columns: Record<string, ColumnItem>;
|
|
320
|
+
target: 'table' | 'column' | 'index' | 'foreign_key';
|
|
281
321
|
}
|
|
322
|
+
|
|
323
|
+
export declare class MigrationInterface {
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @param tableName
|
|
327
|
+
* @param columns
|
|
328
|
+
* @param options default engine is InnoDB; default charset is utf8mb4
|
|
329
|
+
*/
|
|
330
|
+
createTable(tableName: string, columns: Record<string, ColumnItem>, options?: {
|
|
331
|
+
engine?: 'InnoDB' | 'MyISAM' | 'MEMORY',
|
|
332
|
+
charset?: string
|
|
333
|
+
}): void;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* @param columnName
|
|
337
|
+
* @param columnType
|
|
338
|
+
* @param tableName
|
|
339
|
+
* @param options allowNull default is true
|
|
340
|
+
*/
|
|
341
|
+
createColumn(columnName: string, columnType: FieldType, tableName: string, options?: {
|
|
342
|
+
length?: number,
|
|
343
|
+
unsigned?: boolean,
|
|
344
|
+
allowNull?: boolean,
|
|
345
|
+
default?: string | number | boolean | null | 'timestamp',
|
|
346
|
+
onUpdate?: string,
|
|
347
|
+
comment?: string,
|
|
348
|
+
autoIncrement?: boolean,
|
|
349
|
+
primaryKey?: boolean,
|
|
350
|
+
uniqIndex?: boolean,
|
|
351
|
+
after?: string
|
|
352
|
+
}): void;
|
|
353
|
+
|
|
354
|
+
createIndex(tableName: string, columns: string[], options?: {
|
|
355
|
+
indexName?: string,
|
|
356
|
+
unique?: boolean,
|
|
357
|
+
fulltext?: boolean,
|
|
358
|
+
spatial?: boolean
|
|
359
|
+
}): void;
|
|
360
|
+
|
|
361
|
+
createForeignKey(options: {
|
|
362
|
+
foreignKey?: string,
|
|
363
|
+
tableName: string,
|
|
364
|
+
columnName: string,
|
|
365
|
+
reference: {
|
|
366
|
+
tableName: string,
|
|
367
|
+
columnName: string,
|
|
368
|
+
onDelete?: 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'NO ACTION' | 'restrict' | 'cascade' | 'set null' | 'no action',
|
|
369
|
+
onUpdate?: 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'NO ACTION' | 'restrict' | 'cascade' | 'set null' | 'no action',
|
|
370
|
+
}
|
|
371
|
+
}): void;
|
|
372
|
+
|
|
373
|
+
dropTable(tableName: string): void;
|
|
374
|
+
|
|
375
|
+
dropColumn(columnName: string, tableName: string): void;
|
|
376
|
+
|
|
377
|
+
dropIndex(indexName: string): void;
|
|
378
|
+
|
|
379
|
+
dropForeignKey(foreign_key: string, tableName: string): void;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export declare function up(migration: MigrationInterface): Promise<void>;
|
|
383
|
+
export declare function down(migration: MigrationInterface): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiosleo/orm-mysql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "MySQL ORM tool",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mysql",
|
|
@@ -10,16 +10,20 @@
|
|
|
10
10
|
"directories": {
|
|
11
11
|
"lib": "src"
|
|
12
12
|
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"orm-mysql": "./bin/orm-mysql.js"
|
|
15
|
+
},
|
|
13
16
|
"scripts": {
|
|
14
17
|
"lint": "tsc ./index.d.ts && eslint --fix src/",
|
|
15
18
|
"test": "mocha --reporter spec --timeout 3000 tests/*.tests.js",
|
|
16
19
|
"test-cov": "nyc -r=lcov -r=html -r=text -r=json mocha -t 10000 -R spec tests/*.tests.js",
|
|
20
|
+
"test-one": "mocha --reporter spec --timeout 3000 ",
|
|
17
21
|
"ci": "npm run lint && npm run test-cov",
|
|
18
22
|
"clear": "rm -rf ./nyc_output ./coverage"
|
|
19
23
|
},
|
|
20
24
|
"license": "MIT",
|
|
21
25
|
"dependencies": {
|
|
22
|
-
"@axiosleo/cli-tool": "^1.
|
|
26
|
+
"@axiosleo/cli-tool": "^1.6.0",
|
|
23
27
|
"mysql2": "^2.3.3",
|
|
24
28
|
"validatorjs": "^3.22.1"
|
|
25
29
|
},
|
package/src/builder.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { debug } = require('@axiosleo/cli-tool');
|
|
3
4
|
const Query = require('./query');
|
|
4
5
|
const is = require('@axiosleo/cli-tool/src/helper/is');
|
|
6
|
+
const { _caml_case, _render } = require('@axiosleo/cli-tool/src/helper/str');
|
|
7
|
+
const { _validate } = require('./utils');
|
|
8
|
+
const { _assign } = require('@axiosleo/cli-tool/src/helper/obj');
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* @param {array} arr
|
|
@@ -14,6 +18,9 @@ const emit = (arr, res) => {
|
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
class Builder {
|
|
21
|
+
/**
|
|
22
|
+
* @param {import('../index').QueryOperatorOptions} options
|
|
23
|
+
*/
|
|
17
24
|
constructor(options) {
|
|
18
25
|
let sql = '';
|
|
19
26
|
this.values = [];
|
|
@@ -80,6 +87,9 @@ class Builder {
|
|
|
80
87
|
sql = tmp.join(' ');
|
|
81
88
|
break;
|
|
82
89
|
}
|
|
90
|
+
case 'manage': {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
83
93
|
default:
|
|
84
94
|
throw new Error('Invalid operator: ' + options.operator);
|
|
85
95
|
}
|
|
@@ -285,6 +295,235 @@ class Builder {
|
|
|
285
295
|
}
|
|
286
296
|
}
|
|
287
297
|
|
|
298
|
+
class ManageSQLBuilder extends Builder {
|
|
299
|
+
/**
|
|
300
|
+
* @param {import('../index').ManageBuilderOptions} options
|
|
301
|
+
*/
|
|
302
|
+
constructor(options) {
|
|
303
|
+
super({ operator: 'manage' });
|
|
304
|
+
// const emitter = new Emitter();
|
|
305
|
+
const action = `${options.operator}_${options.target}`;
|
|
306
|
+
const method = _caml_case(action, false);
|
|
307
|
+
if (!this[method]) {
|
|
308
|
+
throw new Error(`'${options.target}' Unsupported '${options.operator}' operation.`);
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
this.sql = this[method].call(this, options);
|
|
312
|
+
} catch (err) {
|
|
313
|
+
debug.dump(`${options.operator} ${options.target} error: ${err.message}`);
|
|
314
|
+
throw err;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* @param {import('../index').ManageBuilderOptions} options
|
|
320
|
+
*/
|
|
321
|
+
createTable(options) {
|
|
322
|
+
_validate(options, {
|
|
323
|
+
name: 'required|string',
|
|
324
|
+
engine: [{ in: ['InnoDB', 'MyISAM', 'MEMORY'] }],
|
|
325
|
+
charset: 'string'
|
|
326
|
+
});
|
|
327
|
+
if (is.empty(options.columns)) {
|
|
328
|
+
throw new Error('At least one column is required');
|
|
329
|
+
}
|
|
330
|
+
if (!Object.values(options.columns).find(c => c.primaryKey === true)) {
|
|
331
|
+
throw new Error('At least one primary key column is required');
|
|
332
|
+
}
|
|
333
|
+
let columns = Object.keys(options.columns).map(name => {
|
|
334
|
+
return { name, ...options.columns[name] };
|
|
335
|
+
});
|
|
336
|
+
options = _assign({
|
|
337
|
+
engine: 'InnoDB',
|
|
338
|
+
charset: 'utf8mb4'
|
|
339
|
+
}, options, {
|
|
340
|
+
columns: this.createColumns(columns)
|
|
341
|
+
});
|
|
342
|
+
return _render('CREATE TABLE `${name}` ( ${columns} ) ENGINE=${engine} DEFAULT CHARSET=${charset}', options);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
createColumn(options) {
|
|
346
|
+
_validate(options, {
|
|
347
|
+
table: 'required|string',
|
|
348
|
+
name: 'required|string',
|
|
349
|
+
type: 'required|string',
|
|
350
|
+
length: 'number',
|
|
351
|
+
unsigned: 'boolean',
|
|
352
|
+
allowNull: 'boolean',
|
|
353
|
+
default: 'string',
|
|
354
|
+
comment: 'string',
|
|
355
|
+
autoIncrement: 'boolean',
|
|
356
|
+
primaryKey: 'boolean',
|
|
357
|
+
uniqIndex: 'boolean',
|
|
358
|
+
after: 'string'
|
|
359
|
+
});
|
|
360
|
+
return `ALTER TABLE \`${options.table}\` ADD COLUMN ` + this.renderSingleColumn(options);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
createIndex(options) {
|
|
364
|
+
_validate(options, {
|
|
365
|
+
name: 'required|string',
|
|
366
|
+
table: 'required|string',
|
|
367
|
+
columns: 'required|array',
|
|
368
|
+
unique: 'boolean',
|
|
369
|
+
fulltext: 'boolean',
|
|
370
|
+
spatial: 'boolean',
|
|
371
|
+
order: [{ in: ['asc', 'desc'] }],
|
|
372
|
+
visible: 'boolean'
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
return _render('CREATE INDEX `${index_name}` ON `${table_name}` (${column_names}) ${visible}', {
|
|
376
|
+
index_name: options.name,
|
|
377
|
+
table_name: options.table,
|
|
378
|
+
visible: options.visible === false ? 'INVISIBLE' : 'VISIBLE',
|
|
379
|
+
column_names: options.columns.map(c => {
|
|
380
|
+
if (c.indexOf(' ') !== -1) {
|
|
381
|
+
let t = c.split(' ', 2);
|
|
382
|
+
return `\`${t[0]}\` ${t[1].toUpperCase()}`;
|
|
383
|
+
}
|
|
384
|
+
return `\`${c}\``;
|
|
385
|
+
}).join(', ')
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
createForeignKey(options) {
|
|
390
|
+
_validate(options, {
|
|
391
|
+
name: 'required|string',
|
|
392
|
+
table: 'required|string',
|
|
393
|
+
column: 'required|string',
|
|
394
|
+
'reference.tableName': 'required|string',
|
|
395
|
+
'reference.columnName': 'required|string',
|
|
396
|
+
'reference.onUpdate': [{ in: ['RESTRICT', 'CASCADE', 'SET NULL', 'NO ACTION'] }],
|
|
397
|
+
'reference.onDelete': [{ in: ['RESTRICT', 'CASCADE', 'SET NULL', 'NO ACTION'] }]
|
|
398
|
+
});
|
|
399
|
+
return _render('ALTER TABLE `${table_name}` ADD CONSTRAINT `${name}` FOREIGN KEY (`${column_name}`) REFERENCES `${foreign_table}` (`${foreign_column}`) ON DELETE ${on_delete} ON UPDATE ${on_update}', {
|
|
400
|
+
table_name: options.tableName,
|
|
401
|
+
name: options.name,
|
|
402
|
+
column_name: options.columnName,
|
|
403
|
+
foreign_table: options.reference.tableName,
|
|
404
|
+
foreign_column: options.reference.columnName,
|
|
405
|
+
on_delete: options.reference.onDelete || 'NO ACTION',
|
|
406
|
+
on_update: options.reference.onUpdate || 'NO ACTION',
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
dropTable(options) {
|
|
411
|
+
_validate(options, {
|
|
412
|
+
name: 'required|string',
|
|
413
|
+
});
|
|
414
|
+
return _render('DROP TABLE `${name}`', options);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
dropColumn(options) {
|
|
418
|
+
_validate(options, {
|
|
419
|
+
table: 'required|string',
|
|
420
|
+
name: 'required|string',
|
|
421
|
+
});
|
|
422
|
+
return _render('ALTER TABLE `${table}` DROP COLUMN `${name}`', options);
|
|
423
|
+
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
dropIndex(options) {
|
|
427
|
+
_validate(options, {
|
|
428
|
+
name: 'required|string',
|
|
429
|
+
table: 'required|string',
|
|
430
|
+
});
|
|
431
|
+
return _render('DROP INDEX `${name}` ON `${table}`', options);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
dropForeignKey(options) {
|
|
435
|
+
_validate(options, {
|
|
436
|
+
name: 'required|string',
|
|
437
|
+
table: 'required|string',
|
|
438
|
+
});
|
|
439
|
+
return _render('ALTER TABLE `${table}` DROP FOREIGN KEY `${name}`', options);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
createColumns(columns) {
|
|
443
|
+
let primaryColumn = null;
|
|
444
|
+
let indexColumns = [];
|
|
445
|
+
let strs = columns.map(column => {
|
|
446
|
+
let str = this.renderSingleColumn(column);
|
|
447
|
+
if (column.primaryKey === true) {
|
|
448
|
+
primaryColumn = column;
|
|
449
|
+
} else if (column.uniqIndex === true) {
|
|
450
|
+
indexColumns.push(column);
|
|
451
|
+
}
|
|
452
|
+
return str;
|
|
453
|
+
});
|
|
454
|
+
if (primaryColumn) {
|
|
455
|
+
strs.push(`PRIMARY KEY (\`${primaryColumn.name}\`)`);
|
|
456
|
+
strs.push(`UNIQUE INDEX \`${primaryColumn.name}\` (\`${primaryColumn.name}\` ASC) VISIBLE`);
|
|
457
|
+
}
|
|
458
|
+
if (indexColumns.length > 0) {
|
|
459
|
+
indexColumns.forEach((i) => {
|
|
460
|
+
strs.push(`UNIQUE INDEX \`${i.name}\` (\`${i.name}\` ASC) VISIBLE`);
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
return strs.join(', ');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
renderSingleColumn(options) {
|
|
467
|
+
_validate(options, {
|
|
468
|
+
name: 'required|string',
|
|
469
|
+
type: 'required|string',
|
|
470
|
+
default: 'string',
|
|
471
|
+
onUpdate: 'string',
|
|
472
|
+
length: 'integer',
|
|
473
|
+
comment: 'string',
|
|
474
|
+
allowNull: 'boolean',
|
|
475
|
+
autoIncrement: 'boolean',
|
|
476
|
+
collate: 'string',
|
|
477
|
+
primaryKey: 'boolean',
|
|
478
|
+
uniqIndex: 'boolean'
|
|
479
|
+
});
|
|
480
|
+
let type = options.type.toUpperCase();
|
|
481
|
+
if (type === 'STRING') {
|
|
482
|
+
type = 'VARCHAR';
|
|
483
|
+
}
|
|
484
|
+
let str = `\`${options.name}\` ${type}`;
|
|
485
|
+
if (typeof options.length !== 'undefined') {
|
|
486
|
+
str += `(${options.length})`;
|
|
487
|
+
} else if (type === 'INT') {
|
|
488
|
+
str += '(11)';
|
|
489
|
+
} else if (type === 'VARCHAR') {
|
|
490
|
+
str += '(255)';
|
|
491
|
+
}
|
|
492
|
+
if (options.allowNull === false) {
|
|
493
|
+
str += ' NOT NULL';
|
|
494
|
+
}
|
|
495
|
+
if (options.unsigned === true) {
|
|
496
|
+
str += ' UNSIGNED';
|
|
497
|
+
}
|
|
498
|
+
if (typeof options.default !== 'undefined') {
|
|
499
|
+
if (options.primaryKey === true) {
|
|
500
|
+
throw new Error('Primary key can not have default value.');
|
|
501
|
+
}
|
|
502
|
+
if (options.default === null) {
|
|
503
|
+
str += ' DEFAULT NULL';
|
|
504
|
+
} else if (options.default === 'timestamp') {
|
|
505
|
+
str += ' DEFAULT CURRENT_TIMESTAMP';
|
|
506
|
+
} else if (is.string(options.default)) {
|
|
507
|
+
str += ` DEFAULT ${options.default}`;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (options.onUpdate) {
|
|
511
|
+
str += ` ON UPDATE ${options.onUpdate}`;
|
|
512
|
+
}
|
|
513
|
+
if (is.string(options.comment) && is.empty(options.comment) === false) {
|
|
514
|
+
str += ` COMMENT '${options.comment}'`;
|
|
515
|
+
}
|
|
516
|
+
if (options.autoIncrement === true) {
|
|
517
|
+
str += ' AUTO_INCREMENT';
|
|
518
|
+
}
|
|
519
|
+
if (options.after) {
|
|
520
|
+
str += ' AFTER `' + options.after + '`';
|
|
521
|
+
}
|
|
522
|
+
return str;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
288
526
|
module.exports = {
|
|
289
|
-
Builder
|
|
527
|
+
Builder,
|
|
528
|
+
ManageSQLBuilder
|
|
290
529
|
};
|
package/src/client.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
const mysql = require('mysql2');
|
|
4
4
|
const mysqlPromise = require('mysql2/promise');
|
|
5
5
|
const { _validate } = require('./utils');
|
|
6
|
-
const {
|
|
7
|
-
const {
|
|
6
|
+
const { _query } = require('./core');
|
|
7
|
+
const { QueryHandler } = require('./operator');
|
|
8
|
+
const Query = require('./query');
|
|
8
9
|
|
|
9
10
|
const clients = {};
|
|
10
11
|
|
|
@@ -16,9 +17,9 @@ const clients = {};
|
|
|
16
17
|
const createClient = (options, name = null) => {
|
|
17
18
|
_validate(options, {
|
|
18
19
|
host: 'required|string',
|
|
20
|
+
port: 'required|integer',
|
|
19
21
|
user: 'required|string',
|
|
20
22
|
password: 'required|string',
|
|
21
|
-
port: 'required|integer',
|
|
22
23
|
database: 'required|string',
|
|
23
24
|
});
|
|
24
25
|
const key = name ? name :
|
|
@@ -39,9 +40,9 @@ const createClient = (options, name = null) => {
|
|
|
39
40
|
const createPromiseClient = async (options, name = null) => {
|
|
40
41
|
_validate(options, {
|
|
41
42
|
host: 'required|string',
|
|
43
|
+
port: 'required|integer',
|
|
42
44
|
user: 'required|string',
|
|
43
45
|
password: 'required|string',
|
|
44
|
-
port: 'required|integer',
|
|
45
46
|
database: 'required|string',
|
|
46
47
|
});
|
|
47
48
|
const key = name ? name :
|
|
@@ -96,31 +97,24 @@ class MySQLClient extends QueryHandler {
|
|
|
96
97
|
/**
|
|
97
98
|
* @param {mysql.ConnectionOptions} options
|
|
98
99
|
* @param {*} name
|
|
100
|
+
* @param {'default'|'promise'|'pool'} type
|
|
99
101
|
*/
|
|
100
|
-
constructor(options, name = 'default') {
|
|
101
|
-
|
|
102
|
+
constructor(options, name = 'default', type = 'default') {
|
|
103
|
+
let conn;
|
|
104
|
+
switch (type) {
|
|
105
|
+
case 'default':
|
|
106
|
+
conn = createClient(options, name);
|
|
107
|
+
break;
|
|
108
|
+
case 'pool':
|
|
109
|
+
conn = createPool(options, name);
|
|
110
|
+
break;
|
|
111
|
+
default:
|
|
112
|
+
throw new Error(`client type ${type} not found`);
|
|
113
|
+
}
|
|
102
114
|
super(conn);
|
|
103
115
|
this.database = options.database;
|
|
104
116
|
}
|
|
105
117
|
|
|
106
|
-
async existTable(table, database = null) {
|
|
107
|
-
if (!table) {
|
|
108
|
-
throw new Error('table name is required');
|
|
109
|
-
}
|
|
110
|
-
const c = await this.table('information_schema.TABLES')
|
|
111
|
-
.where('TABLE_SCHEMA', database || this.database)
|
|
112
|
-
.where('TABLE_NAME', table)
|
|
113
|
-
.count();
|
|
114
|
-
return !!c;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async existDatabase(database) {
|
|
118
|
-
const c = await this.table('information_schema.SCHEMATA')
|
|
119
|
-
.where('SCHEMA_NAME', database)
|
|
120
|
-
.count();
|
|
121
|
-
return !!c;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
118
|
/**
|
|
125
119
|
* @param {import('./operator').Query} query
|
|
126
120
|
*/
|
package/src/core.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Builder } = require('./builder');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {import('mysql2/promise').Connection} conn
|
|
8
|
+
* @param {*} options
|
|
9
|
+
* @param {*} opt
|
|
10
|
+
* @returns
|
|
11
|
+
*/
|
|
12
|
+
const _query = (conn, options, opt = null) => {
|
|
13
|
+
switch (options.driver) {
|
|
14
|
+
case 'mysql': {
|
|
15
|
+
if (opt === null) {
|
|
16
|
+
const builder = new Builder(options);
|
|
17
|
+
opt = {
|
|
18
|
+
sql: builder.sql,
|
|
19
|
+
values: builder.values || [],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
if (options.transaction) {
|
|
24
|
+
conn.execute(opt.sql, opt.values || []).then((res) => {
|
|
25
|
+
resolve(res[0]);
|
|
26
|
+
}).catch((err) => reject(err));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
conn.query(opt, (err, result) => {
|
|
30
|
+
if (err) {
|
|
31
|
+
reject(err);
|
|
32
|
+
} else {
|
|
33
|
+
resolve(result);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
default: {
|
|
40
|
+
if (typeof options.queryHandler === 'function') {
|
|
41
|
+
const promise = options.queryHandler(conn, options, opt);
|
|
42
|
+
if (promise instanceof Promise) {
|
|
43
|
+
return promise;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
throw new Error('queryHandler must return a promise');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const _execSQL = (conn, sql, values = []) => {
|
|
52
|
+
let opt = { sql, values };
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
if (conn.query instanceof Function) {
|
|
55
|
+
conn.query(opt, (err, result) => {
|
|
56
|
+
if (err) {
|
|
57
|
+
reject(err);
|
|
58
|
+
} else {
|
|
59
|
+
resolve(result);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
conn.execute(opt)
|
|
64
|
+
.then((res) => resolve(res))
|
|
65
|
+
.catch((err) => reject(err));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
_query,
|
|
72
|
+
_execSQL
|
|
73
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type QueryItem = {
|
|
2
|
+
sql: string,
|
|
3
|
+
values: any[],
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type Context = {
|
|
7
|
+
action: 'up' | 'down',
|
|
8
|
+
connection: {
|
|
9
|
+
host: string;
|
|
10
|
+
port: number;
|
|
11
|
+
user: string;
|
|
12
|
+
password: string;
|
|
13
|
+
database: string;
|
|
14
|
+
},
|
|
15
|
+
items: string[],
|
|
16
|
+
task_key: string,
|
|
17
|
+
config: {
|
|
18
|
+
dir: string,
|
|
19
|
+
},
|
|
20
|
+
files: [],
|
|
21
|
+
runtime?: {
|
|
22
|
+
script: string,
|
|
23
|
+
queries: QueryItem[],
|
|
24
|
+
},
|
|
25
|
+
};
|
package/src/migration.js
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { _list, _exists } = require('@axiosleo/cli-tool/src/helper/fs');
|
|
5
|
+
const { createClient, createPool, createPromiseClient } = require('./client');
|
|
6
|
+
const { QueryHandler } = require('./operator');
|
|
7
|
+
const { printer, debug } = require('@axiosleo/cli-tool');
|
|
8
|
+
const { _execSQL } = require('./core');
|
|
9
|
+
const { _render } = require('@axiosleo/cli-tool/src/helper/str');
|
|
10
|
+
const { _foreach } = require('@axiosleo/cli-tool/src/helper/cmd');
|
|
11
|
+
const { _assign } = require('@axiosleo/cli-tool/src/helper/obj');
|
|
12
|
+
const { TransactionHandler } = require('..');
|
|
13
|
+
const { ManageSQLBuilder } = require('./builder');
|
|
14
|
+
|
|
15
|
+
const migrationColumns = [
|
|
16
|
+
{
|
|
17
|
+
name: 'id',
|
|
18
|
+
type: 'int',
|
|
19
|
+
length: 11,
|
|
20
|
+
allowNull: false,
|
|
21
|
+
autoIncrement: true,
|
|
22
|
+
primaryKey: true,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'migration_key',
|
|
26
|
+
type: 'varchar',
|
|
27
|
+
length: 255,
|
|
28
|
+
allowNull: false,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'filename',
|
|
32
|
+
type: 'varchar',
|
|
33
|
+
length: 255,
|
|
34
|
+
allowNull: false,
|
|
35
|
+
uniqIndex: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'created_at',
|
|
39
|
+
type: 'datetime',
|
|
40
|
+
allowNull: false,
|
|
41
|
+
default: 'timestamp',
|
|
42
|
+
}
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* initialize migration
|
|
47
|
+
* @param {import('./migration').Context} context
|
|
48
|
+
*/
|
|
49
|
+
async function init(context) {
|
|
50
|
+
let files = await _list(context.config.dir, false, '.js');
|
|
51
|
+
if (context.action === 'down') {
|
|
52
|
+
files = files.reverse();
|
|
53
|
+
}
|
|
54
|
+
context.files = files.filter(f => f !== '.connect.js');
|
|
55
|
+
|
|
56
|
+
const connectPath = path.join(context.config.dir, '.connect.js');
|
|
57
|
+
if (await _exists(connectPath)) {
|
|
58
|
+
const connect = require(connectPath);
|
|
59
|
+
_assign(context.connection, connect);
|
|
60
|
+
}
|
|
61
|
+
context.task_key = 'migrate_' + context.connection.database;
|
|
62
|
+
let globalConn = createClient({
|
|
63
|
+
...context.connection,
|
|
64
|
+
database: 'mysql'
|
|
65
|
+
});
|
|
66
|
+
let handler = new QueryHandler(globalConn);
|
|
67
|
+
|
|
68
|
+
const database = context.connection.database;
|
|
69
|
+
if (!await handler.existDatabase(database)) {
|
|
70
|
+
printer.yellow('will create database ' + database).println().println();
|
|
71
|
+
await _execSQL(globalConn, _render('CREATE DATABASE `${database_name}` CHARACTER SET ${charset} COLLATE ${collate}', {
|
|
72
|
+
database_name: database,
|
|
73
|
+
charset: 'utf8mb4',
|
|
74
|
+
collate: 'utf8mb4_unicode_ci'
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
globalConn.end();
|
|
78
|
+
const conn = createPool(context.connection, context.task_key);
|
|
79
|
+
context.pool = conn;
|
|
80
|
+
|
|
81
|
+
handler = new QueryHandler(conn);
|
|
82
|
+
if (await handler.existTable(context.task_key, database)) {
|
|
83
|
+
conn.end();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// migration table not exists
|
|
88
|
+
printer.yellow('will create table ' + context.task_key).println();
|
|
89
|
+
let builder = new ManageSQLBuilder({
|
|
90
|
+
operator: 'create',
|
|
91
|
+
target: 'table',
|
|
92
|
+
name: context.task_key,
|
|
93
|
+
columns: migrationColumns
|
|
94
|
+
});
|
|
95
|
+
let res = await _execSQL(conn, builder.sql);
|
|
96
|
+
conn.end();
|
|
97
|
+
if (res.serverStatus !== 2) {
|
|
98
|
+
printer.error('create migration table failed.');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* initialize migration
|
|
105
|
+
* @param {import('./migration').Context} context
|
|
106
|
+
*/
|
|
107
|
+
async function _exec(context, queries) {
|
|
108
|
+
const conn = await createPromiseClient(context.connection, context.task_key + '_transaction');
|
|
109
|
+
const transaction = new TransactionHandler(conn);
|
|
110
|
+
await transaction.begin();
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const files = Object.keys(queries);
|
|
114
|
+
await _foreach(files, async (file) => {
|
|
115
|
+
const hasMigarated = await transaction.table(context.task_key)
|
|
116
|
+
.where('migration_key', context.task_key)
|
|
117
|
+
.where('filename', file)
|
|
118
|
+
.count();
|
|
119
|
+
if (context.action === 'up' && hasMigarated) {
|
|
120
|
+
if (hasMigarated) {
|
|
121
|
+
printer.yellow(`Migration file "${file}" has been migrated.`).println();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
} else if (context.action === 'down' && !hasMigarated) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const sqls = queries[file];
|
|
129
|
+
await _foreach(sqls, async (query) => {
|
|
130
|
+
if (context.debug) {
|
|
131
|
+
debug.log(query.sql);
|
|
132
|
+
}
|
|
133
|
+
await transaction.query(query);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (context.action === 'up') {
|
|
137
|
+
await transaction.table(context.task_key).insert({
|
|
138
|
+
migration_key: context.task_key,
|
|
139
|
+
filename: file,
|
|
140
|
+
created_at: new Date()
|
|
141
|
+
});
|
|
142
|
+
printer.green('Success migrate up: ').yellow(file).println(' ');
|
|
143
|
+
} else {
|
|
144
|
+
const item = await transaction.table(context.task_key)
|
|
145
|
+
.where('migration_key', context.task_key)
|
|
146
|
+
.where('filename', file).find();
|
|
147
|
+
if (item) {
|
|
148
|
+
await transaction.table(context.task_key).where('id', item.id).delete();
|
|
149
|
+
}
|
|
150
|
+
printer.green('Success migrate down: ').yellow(file).println(' ');
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
await transaction.commit();
|
|
154
|
+
} catch (e) {
|
|
155
|
+
await transaction.rollback();
|
|
156
|
+
debug.log(e);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function _initMigration(file, queries = {}) {
|
|
161
|
+
const migration = {};
|
|
162
|
+
let baseAttr = {
|
|
163
|
+
writable: true,
|
|
164
|
+
enumerable: true,
|
|
165
|
+
configurable: true
|
|
166
|
+
};
|
|
167
|
+
Object.defineProperty(migration, 'createTable', {
|
|
168
|
+
value: function (table, columns, options = {}) {
|
|
169
|
+
_assign(options, {
|
|
170
|
+
operator: 'create',
|
|
171
|
+
target: 'table',
|
|
172
|
+
name: table,
|
|
173
|
+
columns
|
|
174
|
+
});
|
|
175
|
+
const builder = new ManageSQLBuilder(options);
|
|
176
|
+
queries[file].push({ sql: builder.sql, values: builder.values });
|
|
177
|
+
}, ...baseAttr
|
|
178
|
+
});
|
|
179
|
+
Object.defineProperty(migration, 'createColumn', {
|
|
180
|
+
value: function (name, type, table, options = {}) {
|
|
181
|
+
_assign(options, {
|
|
182
|
+
operator: 'create',
|
|
183
|
+
target: 'column',
|
|
184
|
+
table,
|
|
185
|
+
name,
|
|
186
|
+
type
|
|
187
|
+
});
|
|
188
|
+
const builder = new ManageSQLBuilder(options);
|
|
189
|
+
queries[file].push({ sql: builder.sql, values: builder.values });
|
|
190
|
+
}, ...baseAttr
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
Object.defineProperty(migration, 'createIndex', {
|
|
194
|
+
value: function (table, columns, options = {}) {
|
|
195
|
+
_assign(options, {
|
|
196
|
+
operator: 'create',
|
|
197
|
+
target: 'index',
|
|
198
|
+
name: options.indexName ? options.indexName : 'idx_' + table + '_' + columns.join('_'),
|
|
199
|
+
table,
|
|
200
|
+
columns
|
|
201
|
+
});
|
|
202
|
+
const builder = new ManageSQLBuilder(options);
|
|
203
|
+
queries[file].push({ sql: builder.sql, values: builder.values });
|
|
204
|
+
}, ...baseAttr
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
Object.defineProperty(migration, 'createForeignKey', {
|
|
208
|
+
value: function (options = {}) {
|
|
209
|
+
_assign(options, {
|
|
210
|
+
operator: 'create',
|
|
211
|
+
target: 'foreignKey',
|
|
212
|
+
name: options.foreignKey ? options.foreignKey : 'fk_' + options.tableName + '_' + options.columnName,
|
|
213
|
+
table: options.tableName,
|
|
214
|
+
column: options.columnName,
|
|
215
|
+
reference: options.reference
|
|
216
|
+
});
|
|
217
|
+
const builder = new ManageSQLBuilder(options);
|
|
218
|
+
queries[file].push({ sql: builder.sql, values: builder.values });
|
|
219
|
+
}, ...baseAttr
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
Object.defineProperty(migration, 'dropTable', {
|
|
223
|
+
value: function (table) {
|
|
224
|
+
const builder = new ManageSQLBuilder({
|
|
225
|
+
operator: 'drop',
|
|
226
|
+
target: 'table',
|
|
227
|
+
name: table
|
|
228
|
+
});
|
|
229
|
+
queries[file].push({ sql: builder.sql, values: builder.values });
|
|
230
|
+
}, ...baseAttr
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
Object.defineProperty(migration, 'dropColumn', {
|
|
234
|
+
value: function (name, table) {
|
|
235
|
+
const builder = new ManageSQLBuilder({
|
|
236
|
+
operator: 'drop',
|
|
237
|
+
target: 'column',
|
|
238
|
+
name,
|
|
239
|
+
table
|
|
240
|
+
});
|
|
241
|
+
queries[file].push({ sql: builder.sql, values: builder.values });
|
|
242
|
+
}, ...baseAttr
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
Object.defineProperty(migration, 'dropIndex', {
|
|
246
|
+
value: function (name, table) {
|
|
247
|
+
const builder = new ManageSQLBuilder({
|
|
248
|
+
operator: 'drop',
|
|
249
|
+
target: 'index',
|
|
250
|
+
name,
|
|
251
|
+
table
|
|
252
|
+
});
|
|
253
|
+
queries[file].push({ sql: builder.sql, values: builder.values });
|
|
254
|
+
}, ...baseAttr
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
Object.defineProperty(migration, 'dropForeignKey', {
|
|
258
|
+
value: function (name, table) {
|
|
259
|
+
const builder = new ManageSQLBuilder({
|
|
260
|
+
operator: 'drop',
|
|
261
|
+
target: 'foreignKey',
|
|
262
|
+
name,
|
|
263
|
+
table
|
|
264
|
+
});
|
|
265
|
+
queries[file].push({ sql: builder.sql, values: builder.values });
|
|
266
|
+
}, ...baseAttr
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
return migration;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* initialize migration
|
|
274
|
+
* @param {import('./migration').Context} context
|
|
275
|
+
*/
|
|
276
|
+
async function run(context) {
|
|
277
|
+
const { files } = context;
|
|
278
|
+
const queries = {};
|
|
279
|
+
await _foreach(files, async (file) => {
|
|
280
|
+
const scriptPath = path.join(context.config.dir, file);
|
|
281
|
+
const script = require(scriptPath);
|
|
282
|
+
queries[file] = [];
|
|
283
|
+
|
|
284
|
+
const migration = _initMigration(file, queries);
|
|
285
|
+
|
|
286
|
+
switch (context.action) {
|
|
287
|
+
case 'up': {
|
|
288
|
+
if (typeof script.up !== 'function') {
|
|
289
|
+
printer.error(`Migration file "${file}" must have a function named up.`);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
await script.up(migration);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
case 'down': {
|
|
296
|
+
if (typeof script.down !== 'function') {
|
|
297
|
+
printer.error(`Migration file "${file}" must have a function named down.`);
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
await script.down(migration);
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
default: {
|
|
304
|
+
throw new Error(`Unknown migration action ${context.action}.`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
await _exec(context, queries);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function end(context) {
|
|
312
|
+
printer.yellow('Done').println(' ');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
module.exports = {
|
|
316
|
+
init,
|
|
317
|
+
run,
|
|
318
|
+
end
|
|
319
|
+
};
|
package/src/operator.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { Builder } = require('./builder');
|
|
4
4
|
const Query = require('./query');
|
|
5
5
|
const Hook = require('./hook');
|
|
6
|
-
const { _query } = require('./
|
|
6
|
+
const { _query } = require('./core');
|
|
7
7
|
|
|
8
8
|
class QueryOperator extends Query {
|
|
9
9
|
/**
|
|
@@ -13,13 +13,13 @@ class QueryOperator extends Query {
|
|
|
13
13
|
super(null);
|
|
14
14
|
this.conn = conn;
|
|
15
15
|
this.options.driver = options.driver || 'mysql';
|
|
16
|
-
this.options.
|
|
16
|
+
this.options.queryHandler = options.queryHandler || null;
|
|
17
17
|
if (this.options.driver !== 'mysql') {
|
|
18
|
-
if (!this.options.
|
|
19
|
-
throw new Error('
|
|
18
|
+
if (!this.options.queryHandler) {
|
|
19
|
+
throw new Error('queryHandler is required');
|
|
20
20
|
}
|
|
21
|
-
if (!(this.options.
|
|
22
|
-
throw new Error('
|
|
21
|
+
if (!(this.options.queryHandler instanceof Function)) {
|
|
22
|
+
throw new Error('queryHandler must be a function');
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -131,6 +131,26 @@ class QueryHandler {
|
|
|
131
131
|
}
|
|
132
132
|
return await this.table(tableName).insert(data);
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
async existTable(table, database = null) {
|
|
136
|
+
if (!table) {
|
|
137
|
+
throw new Error('table name is required');
|
|
138
|
+
}
|
|
139
|
+
const query = new QueryOperator(this.conn, this.options);
|
|
140
|
+
const c = await query.table('information_schema.TABLES')
|
|
141
|
+
.where('TABLE_SCHEMA', database || this.database)
|
|
142
|
+
.where('TABLE_NAME', table)
|
|
143
|
+
.count();
|
|
144
|
+
return !!c;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async existDatabase(database) {
|
|
148
|
+
const query = new QueryOperator(this.conn, this.options);
|
|
149
|
+
const c = await query.table('information_schema.SCHEMATA')
|
|
150
|
+
.where('SCHEMA_NAME', database)
|
|
151
|
+
.count();
|
|
152
|
+
return !!c;
|
|
153
|
+
}
|
|
134
154
|
}
|
|
135
155
|
|
|
136
156
|
module.exports = {
|
package/src/query.js
CHANGED
package/src/transaction.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// eslint-disable-next-line no-unused-vars
|
|
4
4
|
const mysql = require('mysql2/promise');
|
|
5
5
|
const { QueryOperator } = require('./operator');
|
|
6
|
+
const { _query } = require('./core');
|
|
6
7
|
|
|
7
8
|
const levels = {
|
|
8
9
|
RU: 'READ UNCOMMITTED',
|
|
@@ -19,13 +20,13 @@ class TransactionOperator extends QueryOperator {
|
|
|
19
20
|
super(conn);
|
|
20
21
|
this.options.transaction = true;
|
|
21
22
|
this.options.driver = options.driver || 'mysql';
|
|
22
|
-
this.options.
|
|
23
|
+
this.options.queryHandler = options.queryHandler || null;
|
|
23
24
|
if (this.options.driver !== 'mysql') {
|
|
24
|
-
if (!this.options.
|
|
25
|
-
throw new Error('
|
|
25
|
+
if (!this.options.queryHandler) {
|
|
26
|
+
throw new Error('queryHandler is required');
|
|
26
27
|
}
|
|
27
|
-
if (!(this.options.
|
|
28
|
-
throw new Error('
|
|
28
|
+
if (!(this.options.queryHandler instanceof Function)) {
|
|
29
|
+
throw new Error('queryHandler must be a function');
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
}
|
|
@@ -60,13 +61,9 @@ class TransactionHandler {
|
|
|
60
61
|
|
|
61
62
|
async query(options) {
|
|
62
63
|
return new Promise((resolve, reject) => {
|
|
63
|
-
this.conn
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
} else {
|
|
67
|
-
resolve(result);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
64
|
+
_query(this.conn, { transaction: true, driver: 'mysql' }, options)
|
|
65
|
+
.catch((res) => reject(res))
|
|
66
|
+
.then((res) => resolve(res));
|
|
70
67
|
});
|
|
71
68
|
}
|
|
72
69
|
|
package/src/utils.js
CHANGED
|
@@ -1,55 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Validator = require('validatorjs');
|
|
4
|
-
const { Builder } = require('./builder');
|
|
5
4
|
|
|
6
|
-
const _validate = (obj, rules) => {
|
|
5
|
+
const _validate = (obj, rules, throwError = true) => {
|
|
7
6
|
let validation = new Validator(obj, rules);
|
|
8
7
|
validation.check();
|
|
9
8
|
if (validation.fails()) {
|
|
10
9
|
const errors = validation.errors.all();
|
|
11
10
|
const keys = Object.keys(errors);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const _query = async (conn, options, opt = null) => {
|
|
17
|
-
switch (options.driver) {
|
|
18
|
-
case 'mysql': {
|
|
19
|
-
if (opt === null) {
|
|
20
|
-
const builder = new Builder(options);
|
|
21
|
-
opt = {
|
|
22
|
-
sql: builder.sql,
|
|
23
|
-
values: builder.values || [],
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
return new Promise((resolve, reject) => {
|
|
27
|
-
if (options.transaction) {
|
|
28
|
-
conn.execute(opt)
|
|
29
|
-
.then((res) => resolve(res))
|
|
30
|
-
.catch((err) => reject(err));
|
|
31
|
-
} else {
|
|
32
|
-
conn.query(opt, (err, result) => {
|
|
33
|
-
if (err) {
|
|
34
|
-
reject(err);
|
|
35
|
-
} else {
|
|
36
|
-
resolve(result);
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
default: {
|
|
43
|
-
const promise = options.query_handler(conn, options, opt);
|
|
44
|
-
if (!(promise instanceof Promise)) {
|
|
45
|
-
throw new Error('query_handler must return a promise');
|
|
46
|
-
}
|
|
47
|
-
return promise;
|
|
11
|
+
if (throwError) {
|
|
12
|
+
throw new Error(`${keys[0]}: ${errors[keys[0]]}`);
|
|
48
13
|
}
|
|
14
|
+
return errors;
|
|
49
15
|
}
|
|
16
|
+
return null;
|
|
50
17
|
};
|
|
51
18
|
|
|
52
19
|
module.exports = {
|
|
53
20
|
_validate,
|
|
54
|
-
_query
|
|
55
21
|
};
|