@axiosleo/orm-mysql 0.3.1 → 0.5.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/.env +1 -1
- package/README.md +60 -1
- package/index.d.ts +63 -3
- package/index.js +21 -2
- package/package.json +1 -1
- package/runtimes/hook.js +104 -0
- package/runtimes/test.js +65 -37
- package/src/builder.js +67 -30
- package/src/client.js +49 -1
- package/src/hook.js +67 -0
- package/src/operator.js +39 -20
- package/src/query.js +4 -1
- package/src/transaction.js +111 -0
package/.env
CHANGED
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @axiosleo/orm-mysql
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
[](https://npmjs.org/package/@axiosleo/orm-mysql)
|
|
5
|
+
[](https://npmjs.org/package/@axiosleo/orm-mysql)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://app.fossa.com/projects/git%2Bgithub.com%2FAxiosLeo%2Fnode-orm-mysql?ref=badge_shield)
|
|
8
|
+
|
|
3
9
|
## Installation
|
|
4
10
|
|
|
5
11
|
```bash
|
|
@@ -46,7 +52,7 @@ query.offset(0); // set offset
|
|
|
46
52
|
let rows = await query.select(); // select
|
|
47
53
|
```
|
|
48
54
|
|
|
49
|
-
### Some Examples
|
|
55
|
+
### Some Query Examples
|
|
50
56
|
|
|
51
57
|
```javascript
|
|
52
58
|
const { createClient, QueryHandler, Query } = require("@axiosleo/orm-mysql");
|
|
@@ -129,6 +135,59 @@ async function subqueryExample() {
|
|
|
129
135
|
}
|
|
130
136
|
```
|
|
131
137
|
|
|
138
|
+
### Hook
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
const { Hook } = require("@axiosleo/orm-mysql");
|
|
142
|
+
|
|
143
|
+
// opt: 'select' | 'find' | 'insert' | 'update' | 'delete' | 'count'
|
|
144
|
+
|
|
145
|
+
Hook.pre(async (options) => {
|
|
146
|
+
debug.log('options', options);
|
|
147
|
+
}, { table: 'table_name', opt: 'insert'});
|
|
148
|
+
|
|
149
|
+
Hook.post(async (options, result) => {
|
|
150
|
+
throw new Error('some error');
|
|
151
|
+
}, { table: 'table_name', opt: 'insert' });
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Transaction
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
const { TransactionHandler, createPromiseClient } = require("@axiosleo/orm-mysql");
|
|
158
|
+
|
|
159
|
+
const conn = createPromiseClient({
|
|
160
|
+
host: process.env.MYSQL_HOST,
|
|
161
|
+
port: process.env.MYSQL_PORT,
|
|
162
|
+
user: process.env.MYSQL_USER,
|
|
163
|
+
password: process.env.MYSQL_PASS,
|
|
164
|
+
database: process.env.MYSQL_DB,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const transaction = new TransactionHandler(connection);
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
// insert user info
|
|
171
|
+
let row = await transaction.table("users").insert({
|
|
172
|
+
name: "Joe",
|
|
173
|
+
age: 18,
|
|
174
|
+
});
|
|
175
|
+
const lastInsertId = row[0].insertId;
|
|
176
|
+
|
|
177
|
+
// insert student info
|
|
178
|
+
await transaction.table("students").insert({
|
|
179
|
+
user_id: lastInsertId,
|
|
180
|
+
});
|
|
181
|
+
await transaction.commit();
|
|
182
|
+
} catch (e) {
|
|
183
|
+
await transaction.rollback();
|
|
184
|
+
throw e;
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
132
188
|
## License
|
|
133
189
|
|
|
134
190
|
This project is open-sourced software licensed under the [MIT](LICENSE).
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
[](https://app.fossa.com/projects/git%2Bgithub.com%2FAxiosLeo%2Fnode-orm-mysql?ref=badge_large)
|
package/index.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Pool,
|
|
2
3
|
OkPacket,
|
|
3
4
|
Connection,
|
|
5
|
+
PoolOptions,
|
|
4
6
|
QueryOptions,
|
|
5
7
|
RowDataPacket,
|
|
6
8
|
ConnectionOptions
|
|
7
9
|
} from 'mysql2';
|
|
8
10
|
|
|
11
|
+
import {
|
|
12
|
+
Connection as PromiseConnection,
|
|
13
|
+
} from 'mysql2/promise';
|
|
14
|
+
|
|
9
15
|
export type Clients = {
|
|
10
16
|
[key: string]: Connection
|
|
11
17
|
}
|
|
@@ -30,7 +36,7 @@ export interface OrderByOptions {
|
|
|
30
36
|
export type OperatorType = 'select' | 'find' | 'insert' | 'update' | 'delete' | 'count';
|
|
31
37
|
|
|
32
38
|
export interface JoinOption {
|
|
33
|
-
table: string;
|
|
39
|
+
table: string | Query;
|
|
34
40
|
table_alias?: string;
|
|
35
41
|
self_column: string;
|
|
36
42
|
foreign_column: string;
|
|
@@ -54,6 +60,8 @@ export interface QueryOperatorOptions {
|
|
|
54
60
|
groupField: string[];
|
|
55
61
|
joins: JoinOption[];
|
|
56
62
|
having: WhereOptions[];
|
|
63
|
+
suffix?: string | null;
|
|
64
|
+
transaction: boolean;
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
export declare class Query {
|
|
@@ -90,6 +98,8 @@ export declare class Query {
|
|
|
90
98
|
join(opt: JoinOption): this;
|
|
91
99
|
}
|
|
92
100
|
|
|
101
|
+
export type QueryResult = any | undefined | RowDataPacket[] | RowDataPacket | OkPacket;
|
|
102
|
+
|
|
93
103
|
export declare class QueryOperator extends Query {
|
|
94
104
|
conn: Connection;
|
|
95
105
|
options: QueryOperatorOptions
|
|
@@ -98,7 +108,7 @@ export declare class QueryOperator extends Query {
|
|
|
98
108
|
|
|
99
109
|
buildSql(operator: OperatorType): { sql: string, values: any[] };
|
|
100
110
|
|
|
101
|
-
exec(): Promise<
|
|
111
|
+
exec(): Promise<QueryResult>;
|
|
102
112
|
|
|
103
113
|
select<T>(): Promise<T[]>;
|
|
104
114
|
|
|
@@ -110,7 +120,12 @@ export declare class QueryOperator extends Query {
|
|
|
110
120
|
|
|
111
121
|
count(): Promise<number>;
|
|
112
122
|
|
|
113
|
-
|
|
123
|
+
/**
|
|
124
|
+
* delete data
|
|
125
|
+
* @param id
|
|
126
|
+
* @param index_field_name default is 'id'
|
|
127
|
+
*/
|
|
128
|
+
delete(id?: number, index_field_name?: string): Promise<OkPacket>;
|
|
114
129
|
}
|
|
115
130
|
|
|
116
131
|
export declare class QueryHandler {
|
|
@@ -125,6 +140,51 @@ export declare class QueryHandler {
|
|
|
125
140
|
upsert(tableName: string, data: any, condition: Record<string, ConditionValueType>): Promise<OkPacket>;
|
|
126
141
|
}
|
|
127
142
|
|
|
143
|
+
export declare class TransactionOperator extends QueryOperator {
|
|
144
|
+
append(suffix: string): this;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export declare class TransactionHandler {
|
|
148
|
+
constructor(conn: PromiseConnection, options?: {
|
|
149
|
+
level: 'READ UNCOMMITTED' | 'RU'
|
|
150
|
+
| 'READ COMMITTED' | 'RC'
|
|
151
|
+
| 'REPEATABLE READ' | 'RR'
|
|
152
|
+
| 'SERIALIZABLE' | 'S'
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
query(options: QueryOptions): Promise<any>;
|
|
156
|
+
|
|
157
|
+
execute(sql: string, values: any[]): Promise<any>;
|
|
158
|
+
|
|
159
|
+
lastInsertId(alias?: string): Promise<number>;
|
|
160
|
+
|
|
161
|
+
table(table: string, alias?: string | null): TransactionOperator;
|
|
162
|
+
|
|
163
|
+
begin(): Promise<void>;
|
|
164
|
+
|
|
165
|
+
commit(): Promise<void>;
|
|
166
|
+
|
|
167
|
+
rollback(): Promise<void>;
|
|
168
|
+
|
|
169
|
+
upsert(tableName: string, data: any, condition: Record<string, ConditionValueType>): Promise<OkPacket>;
|
|
170
|
+
}
|
|
171
|
+
|
|
128
172
|
export function createClient(options: ConnectionOptions, name?: string | null | undefined): Connection;
|
|
129
173
|
|
|
130
174
|
export function getClient(name: string): Connection;
|
|
175
|
+
|
|
176
|
+
export function createPool(options: PoolOptions, name?: string | null | undefined): Pool;
|
|
177
|
+
|
|
178
|
+
export function createPromiseClient(options: ConnectionOptions, name?: string | null | undefined): PromiseConnection;
|
|
179
|
+
|
|
180
|
+
export declare class Hook {
|
|
181
|
+
static pre: (
|
|
182
|
+
callback: (options: QueryOperatorOptions) => void,
|
|
183
|
+
option: { table?: string, opt?: OperatorType }
|
|
184
|
+
) => string;
|
|
185
|
+
|
|
186
|
+
static post: (
|
|
187
|
+
callback: (options: QueryOperatorOptions, result: QueryResult | Error) => void,
|
|
188
|
+
option: { table?: string, opt?: OperatorType }
|
|
189
|
+
) => string;
|
|
190
|
+
}
|
package/index.js
CHANGED
|
@@ -6,13 +6,32 @@ const {
|
|
|
6
6
|
Query
|
|
7
7
|
} = require('./src/operator');
|
|
8
8
|
|
|
9
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
TransactionOperator,
|
|
11
|
+
TransactionHandler
|
|
12
|
+
} = require('./src/transaction');
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
getClient,
|
|
16
|
+
createPool,
|
|
17
|
+
createClient,
|
|
18
|
+
createPromiseClient
|
|
19
|
+
} = require('./src/client');
|
|
20
|
+
|
|
21
|
+
const { Hook } = require('./src/hook');
|
|
10
22
|
|
|
11
23
|
module.exports = {
|
|
24
|
+
Hook,
|
|
25
|
+
|
|
12
26
|
Query,
|
|
13
27
|
QueryHandler,
|
|
14
28
|
QueryOperator,
|
|
15
29
|
|
|
30
|
+
TransactionOperator,
|
|
31
|
+
TransactionHandler,
|
|
32
|
+
|
|
16
33
|
getClient,
|
|
17
|
-
|
|
34
|
+
createPool,
|
|
35
|
+
createClient,
|
|
36
|
+
createPromiseClient
|
|
18
37
|
};
|
package/package.json
CHANGED
package/runtimes/hook.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const EventEmitter = require('events');
|
|
5
|
+
const { debug } = require('@axiosleo/cli-tool');
|
|
6
|
+
const events = {}; // event tree
|
|
7
|
+
const hook = new EventEmitter();
|
|
8
|
+
|
|
9
|
+
const pushEvent = ({ label, table, opt, callback }) => {
|
|
10
|
+
label = label || '*';
|
|
11
|
+
if (!events[label]) {
|
|
12
|
+
events[label] = {};
|
|
13
|
+
}
|
|
14
|
+
table = table || '*';
|
|
15
|
+
if (!events[label][table]) {
|
|
16
|
+
events[label][table] = {};
|
|
17
|
+
}
|
|
18
|
+
opt = opt || '*';
|
|
19
|
+
if (!events[label][table][opt]) {
|
|
20
|
+
events[label][table][opt] = 0;
|
|
21
|
+
}
|
|
22
|
+
events[label][table][opt]++;
|
|
23
|
+
hook.on(`${label}::${table}::${opt}`, callback);
|
|
24
|
+
return { label, table, opt, callback };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const eventRecur = (curr, trace, step, paths, args) => {
|
|
28
|
+
if (step === trace.length) {
|
|
29
|
+
hook.emit(paths.join('::'), ...args);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const t = trace[step];
|
|
33
|
+
if (curr['*']) {
|
|
34
|
+
paths[step] = '*';
|
|
35
|
+
eventRecur(curr[t], trace, step + 1, paths, args);
|
|
36
|
+
}
|
|
37
|
+
if (curr[t]) {
|
|
38
|
+
paths[step] = t;
|
|
39
|
+
eventRecur(curr[t], trace, step + 1, paths, args);
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleEvent = (label, table, opt, ...args) => {
|
|
45
|
+
let curr = events;
|
|
46
|
+
let step = 0;
|
|
47
|
+
let trace = [label, table, opt];
|
|
48
|
+
eventRecur(curr, trace, step, [], args);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
pushEvent({
|
|
52
|
+
label: 'before',
|
|
53
|
+
table: 'table1',
|
|
54
|
+
opt: 'insert',
|
|
55
|
+
callback: (...args) => {
|
|
56
|
+
debug.log(args);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
pushEvent({
|
|
60
|
+
table: 'table1',
|
|
61
|
+
opt: 'insert',
|
|
62
|
+
callback: (...args) => {
|
|
63
|
+
debug.log(args);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
pushEvent({
|
|
67
|
+
label: 'before',
|
|
68
|
+
table: 'table1',
|
|
69
|
+
callback: (...args) => {
|
|
70
|
+
debug.log(args);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
pushEvent({
|
|
74
|
+
label: 'before',
|
|
75
|
+
opt: 'insert',
|
|
76
|
+
callback: (...args) => {
|
|
77
|
+
debug.log(args);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
pushEvent({
|
|
81
|
+
label: 'before',
|
|
82
|
+
callback: (...args) => {
|
|
83
|
+
debug.log(args);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
pushEvent({
|
|
87
|
+
table: 'table1',
|
|
88
|
+
callback: (...args) => {
|
|
89
|
+
debug.log(args);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
pushEvent({
|
|
93
|
+
opt: 'insert',
|
|
94
|
+
callback: (...args) => {
|
|
95
|
+
debug.log(args);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
pushEvent({
|
|
99
|
+
callback: (...args) => {
|
|
100
|
+
debug.log(args);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
debug.log(JSON.stringify(events, null, 2));
|
|
104
|
+
handleEvent('before', 'table1', 'insert', 1, 2, 3);
|
package/runtimes/test.js
CHANGED
|
@@ -1,45 +1,73 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
/* eslint-disable no-console */
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
5
|
+
const path = require('path');
|
|
3
6
|
const dotenv = require('dotenv');
|
|
4
|
-
dotenv.config(
|
|
7
|
+
dotenv.config({
|
|
8
|
+
path: path.join(__dirname, '../.env')
|
|
9
|
+
});
|
|
5
10
|
const { debug } = require('@axiosleo/cli-tool');
|
|
6
11
|
|
|
7
|
-
const mysql = require('mysql2');
|
|
8
|
-
const {
|
|
9
|
-
QueryHandler,
|
|
10
|
-
// Query
|
|
11
|
-
} = require('../src/operator');
|
|
12
|
-
|
|
13
|
-
const conn = mysql.createConnection({
|
|
14
|
-
host: process.env.MYSQL_HOST,
|
|
15
|
-
port: process.env.MYSQL_PORT,
|
|
16
|
-
user: process.env.MYSQL_USER,
|
|
17
|
-
password: process.env.MYSQL_PASS,
|
|
18
|
-
database: process.env.MYSQL_DB,
|
|
19
|
-
});
|
|
20
|
-
const hanlder = new QueryHandler(conn);
|
|
12
|
+
const mysql = require('mysql2/promise');
|
|
21
13
|
|
|
22
|
-
const
|
|
23
|
-
const query = hanlder.table('meta_items_relationship', 'mir')
|
|
24
|
-
.attr('mi_p.meta as p_meta', 'mi_c.meta as c_meta', 'mir.item_parent as item_parent_id', 'mir.item_child as item_child_id');
|
|
25
|
-
const res = query
|
|
26
|
-
.where('mi_p.id', 601)
|
|
27
|
-
.join({
|
|
28
|
-
table: 'meta_items',
|
|
29
|
-
table_alias: 'mi_p',
|
|
30
|
-
self_column: 'mir.item_parent',
|
|
31
|
-
foreign_column: 'mi_p.id',
|
|
32
|
-
// join_type: 'left'
|
|
33
|
-
})
|
|
34
|
-
.join({
|
|
35
|
-
table: 'meta_items',
|
|
36
|
-
table_alias: 'mi_c',
|
|
37
|
-
self_column: 'mir.item_child',
|
|
38
|
-
foreign_column: 'mi_c.id',
|
|
39
|
-
}).buildSql('select');
|
|
40
|
-
const result = await query.select();
|
|
41
|
-
debug.halt(res, result);
|
|
42
|
-
};
|
|
14
|
+
const { TransactionHandler } = require('../src/transaction');
|
|
43
15
|
|
|
44
|
-
|
|
16
|
+
async function main() {
|
|
17
|
+
const items = ['RI0002', 'CB0004'];
|
|
18
|
+
const config = {
|
|
19
|
+
user: process.env.MYSQL_USER,
|
|
20
|
+
password: process.env.MYSQL_PASS,
|
|
21
|
+
host: process.env.MYSQL_HOST,
|
|
22
|
+
port: process.env.MYSQL_PORT,
|
|
23
|
+
database: process.env.MYSQL_DB,
|
|
24
|
+
};
|
|
25
|
+
const connection = await mysql.createConnection(config);
|
|
26
|
+
const transaction = new TransactionHandler(connection);
|
|
27
|
+
await transaction.begin();
|
|
28
|
+
console.log('Finished setting the isolation level to read committed');
|
|
29
|
+
try {
|
|
30
|
+
await transaction.table('product').attr('id', 'name').where('sku', items, 'IN').append('FOR UPDATE').select();
|
|
31
|
+
console.log(`Locked rows for skus ${items.join()}`);
|
|
32
|
+
const [itemsToOrder] = await transaction.table('product').attr('name', 'quantity', 'price').where('sku', items, 'IN').orderBy('id').select();
|
|
33
|
+
console.log('Selected quantities for items');
|
|
34
|
+
let orderTotal = 0;
|
|
35
|
+
let orderItems = [];
|
|
36
|
+
for (let itemToOrder of itemsToOrder) {
|
|
37
|
+
if (itemToOrder.quantity < 1) {
|
|
38
|
+
throw new Error(`One of the items is out of stock ${itemToOrder.name}`);
|
|
39
|
+
}
|
|
40
|
+
console.log(`Quantity for ${itemToOrder.name} is ${itemToOrder.quantity}`);
|
|
41
|
+
orderTotal += itemToOrder.price;
|
|
42
|
+
orderItems.push(itemToOrder.name);
|
|
43
|
+
}
|
|
44
|
+
const res = await transaction.table('sales_order').insert({
|
|
45
|
+
items: orderItems.join(),
|
|
46
|
+
total: orderTotal,
|
|
47
|
+
});
|
|
48
|
+
// const lastInsertId = res[0].insertId;
|
|
49
|
+
debug.log('result', res);
|
|
50
|
+
await debug.pause('pause', {
|
|
51
|
+
items: orderItems.join(),
|
|
52
|
+
total: orderTotal,
|
|
53
|
+
});
|
|
54
|
+
console.log('Order created');
|
|
55
|
+
await transaction.execute(
|
|
56
|
+
'UPDATE product SET quantity=quantity - 1 WHERE sku IN (?, ?)',
|
|
57
|
+
items
|
|
58
|
+
);
|
|
59
|
+
console.log(`Deducted quantities by 1 for ${items.join()}`);
|
|
60
|
+
await transaction.commit();
|
|
61
|
+
const lastInsertId = await transaction.lastInsertId('order_id');
|
|
62
|
+
debug.log(`order created with id ${lastInsertId}`);
|
|
63
|
+
return `order created with id ${lastInsertId}`;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error(`Error occurred while creating order: ${err.message}`, err);
|
|
66
|
+
transaction.rollback();
|
|
67
|
+
console.info('Rollback successful');
|
|
68
|
+
debug.log('error creating order');
|
|
69
|
+
return 'error creating order';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
45
72
|
|
|
73
|
+
main();
|
package/src/builder.js
CHANGED
|
@@ -3,10 +3,21 @@
|
|
|
3
3
|
const Query = require('./query');
|
|
4
4
|
const is = require('@axiosleo/cli-tool/src/helper/is');
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @param {array} arr
|
|
8
|
+
* @param {string} res
|
|
9
|
+
*/
|
|
10
|
+
const emit = (arr, res) => {
|
|
11
|
+
if (res) {
|
|
12
|
+
arr.push(res);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
6
16
|
class Builder {
|
|
7
17
|
constructor(options) {
|
|
8
18
|
let sql = '';
|
|
9
19
|
this.values = [];
|
|
20
|
+
let tmp = [];
|
|
10
21
|
switch (options.operator) {
|
|
11
22
|
case 'find': {
|
|
12
23
|
options.pageLimit = 1;
|
|
@@ -14,51 +25,59 @@ class Builder {
|
|
|
14
25
|
}
|
|
15
26
|
// eslint-disable-next-line no-fallthrough
|
|
16
27
|
case 'select': {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (options.groupField.length) {
|
|
23
|
-
sql += ` GROUP BY ${options.groupField.map(f => this._buildFieldKey(f)).join(',')}`;
|
|
24
|
-
sql += this._buildHaving(options.having);
|
|
25
|
-
} else if (options.having && options.having.length) {
|
|
28
|
+
emit(tmp, `SELECT ${options.attrs ? options.attrs.map((a) => this._buildFieldKey(a)).join(',') : '*'} FROM ${this._buildTables(options.tables)}`);
|
|
29
|
+
emit(tmp, this._buildJoins(options.joins));
|
|
30
|
+
emit(tmp, this._buildContidion(options.conditions));
|
|
31
|
+
emit(tmp, this._buildOrders(options.orders));
|
|
32
|
+
emit(tmp, this._buldPagenation(options.pageLimit, options.pageOffset));
|
|
33
|
+
if (options.having && options.having.length && !options.groupField.length) {
|
|
26
34
|
throw new Error('having is not allowed without "GROUP BY"');
|
|
27
35
|
}
|
|
36
|
+
emit(tmp, this._buildGroupField(options.groupField));
|
|
37
|
+
emit(tmp, this._buildHaving(options.having));
|
|
38
|
+
sql = tmp.join(' ');
|
|
39
|
+
if (options.suffix) {
|
|
40
|
+
sql += ' ' + options.suffix;
|
|
41
|
+
}
|
|
28
42
|
break;
|
|
29
43
|
}
|
|
30
44
|
case 'insert': {
|
|
31
45
|
const fields = this._buildValues(options.data);
|
|
32
|
-
|
|
46
|
+
emit(tmp, `INSERT INTO ${this._buildTables(options.tables)}(${fields.map((f) => `\`${f}\``).join(',')})`);
|
|
47
|
+
emit(tmp, `VALUES (${fields.map((f) => '?').join(',')})`);
|
|
48
|
+
sql = tmp.join(' ');
|
|
33
49
|
break;
|
|
34
50
|
}
|
|
35
51
|
case 'update': {
|
|
36
52
|
const fields = this._buildValues(options.data);
|
|
37
|
-
|
|
53
|
+
emit(tmp, `UPDATE ${this._buildTables(options.tables)}`);
|
|
54
|
+
emit(tmp, `SET ${fields.map((f) => `\`${f}\` = ?`).join(',')}`);
|
|
38
55
|
if (!options.conditions.length) {
|
|
39
56
|
throw new Error('At least one condition is required for update operation');
|
|
40
57
|
}
|
|
41
|
-
|
|
58
|
+
emit(tmp, this._buildContidion(options.conditions));
|
|
59
|
+
sql = tmp.join(' ');
|
|
42
60
|
break;
|
|
43
61
|
}
|
|
44
62
|
case 'delete': {
|
|
45
|
-
|
|
63
|
+
emit(tmp, `DELETE FROM ${this._buildTables(options.tables)}`);
|
|
46
64
|
if (!options.conditions.length) {
|
|
47
65
|
throw new Error('At least one where condition is required for delete operation');
|
|
48
66
|
}
|
|
49
|
-
|
|
67
|
+
emit(tmp, this._buildContidion(options.conditions));
|
|
68
|
+
sql = tmp.join(' ');
|
|
50
69
|
break;
|
|
51
70
|
}
|
|
52
71
|
case 'count': {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (options.groupField.length) {
|
|
57
|
-
sql += ` GROUP BY ${options.groupField.map(f => this._buildFieldKey(f)).join(',')}`;
|
|
58
|
-
sql += this._buildHaving(options.having);
|
|
59
|
-
} else if (options.having && options.having.length) {
|
|
72
|
+
emit(tmp, `SELECT COUNT(*) AS count FROM ${this._buildTables(options.tables)}`);
|
|
73
|
+
emit(tmp, this._buildJoins(options.joins));
|
|
74
|
+
emit(tmp, this._buildContidion(options.conditions));
|
|
75
|
+
if (options.having && options.having.length && !options.groupField.length) {
|
|
60
76
|
throw new Error('having is not allowed without "GROUP BY"');
|
|
61
77
|
}
|
|
78
|
+
emit(tmp, this._buildGroupField(options.groupField));
|
|
79
|
+
emit(tmp, this._buildHaving(options.having));
|
|
80
|
+
sql = tmp.join(' ');
|
|
62
81
|
break;
|
|
63
82
|
}
|
|
64
83
|
default:
|
|
@@ -68,16 +87,31 @@ class Builder {
|
|
|
68
87
|
this.sql = sql;
|
|
69
88
|
}
|
|
70
89
|
|
|
90
|
+
_buildGroupField(groupFields = []) {
|
|
91
|
+
if (!groupFields || !groupFields.length) {
|
|
92
|
+
return '';
|
|
93
|
+
}
|
|
94
|
+
return `GROUP BY ${groupFields.map(f => this._buildFieldKey(f)).join(',')}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
71
97
|
_buildHaving(having) {
|
|
72
|
-
if (!having.length) {
|
|
98
|
+
if (!having || !having.length) {
|
|
73
99
|
return '';
|
|
74
100
|
}
|
|
75
|
-
return this._buildContidion(having, '
|
|
101
|
+
return this._buildContidion(having, 'HAVING ');
|
|
76
102
|
}
|
|
77
103
|
|
|
78
104
|
_buildJoins(joins = []) {
|
|
79
105
|
return joins.map((j) => {
|
|
80
106
|
let { table, alias, self_column, foreign_column, join_type } = j;
|
|
107
|
+
if (table instanceof Query) {
|
|
108
|
+
if (!alias) {
|
|
109
|
+
throw new Error('Alias is required for subquery');
|
|
110
|
+
}
|
|
111
|
+
const builder = new Builder(table.options);
|
|
112
|
+
this.values = this.values.concat(builder.values);
|
|
113
|
+
table = `(${builder.sql})`;
|
|
114
|
+
}
|
|
81
115
|
if (alias) {
|
|
82
116
|
table = `\`${table}\` AS \`${alias}\``;
|
|
83
117
|
} else {
|
|
@@ -86,13 +120,13 @@ class Builder {
|
|
|
86
120
|
let sql = '';
|
|
87
121
|
switch (join_type) {
|
|
88
122
|
case 'left':
|
|
89
|
-
sql = '
|
|
123
|
+
sql = 'LEFT JOIN ';
|
|
90
124
|
break;
|
|
91
125
|
case 'right':
|
|
92
|
-
sql = '
|
|
126
|
+
sql = 'RIGHT JOIN ';
|
|
93
127
|
break;
|
|
94
128
|
default:
|
|
95
|
-
sql = '
|
|
129
|
+
sql = 'INNER JOIN ';
|
|
96
130
|
break;
|
|
97
131
|
}
|
|
98
132
|
sql += `${table} ON ${this._buildFieldWithTableName(self_column)} = ${this._buildFieldWithTableName(foreign_column)}`;
|
|
@@ -101,14 +135,17 @@ class Builder {
|
|
|
101
135
|
}
|
|
102
136
|
|
|
103
137
|
_buildOrders(orders = []) {
|
|
104
|
-
|
|
138
|
+
if (!orders || !orders.length) {
|
|
139
|
+
return '';
|
|
140
|
+
}
|
|
141
|
+
const sql = 'ORDER BY ' + orders.map((o) => {
|
|
105
142
|
return `${this._buildFieldKey(o.sortField)} ${o.sortOrder}`;
|
|
106
143
|
}).join(',');
|
|
107
144
|
return sql;
|
|
108
145
|
}
|
|
109
146
|
|
|
110
147
|
_buildTables(tables) {
|
|
111
|
-
if (!tables.length) {
|
|
148
|
+
if (!tables || !tables.length) {
|
|
112
149
|
throw new Error('At least one table is required');
|
|
113
150
|
}
|
|
114
151
|
return tables.map((t) => {
|
|
@@ -156,10 +193,10 @@ class Builder {
|
|
|
156
193
|
}
|
|
157
194
|
|
|
158
195
|
_buildContidion(conditions, prefix) {
|
|
159
|
-
if (!conditions.length) {
|
|
196
|
+
if (!conditions || !conditions.length) {
|
|
160
197
|
return '';
|
|
161
198
|
}
|
|
162
|
-
let sql = typeof prefix === 'undefined' ? '
|
|
199
|
+
let sql = typeof prefix === 'undefined' ? 'WHERE ' : prefix;
|
|
163
200
|
if (conditions.length) {
|
|
164
201
|
sql += `${conditions.map((c) => {
|
|
165
202
|
if (c.key === null && c.value === null) {
|
package/src/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const mysql = require('mysql2');
|
|
4
|
+
const mysqlPromise = require('mysql2/promise');
|
|
4
5
|
const { validate } = require('./utils');
|
|
5
6
|
|
|
6
7
|
const clients = {};
|
|
@@ -28,6 +29,51 @@ const createClient = (options, name = null) => {
|
|
|
28
29
|
return clients[key];
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @param {mysql.ConnectionOptions} options
|
|
34
|
+
* @param {string|null} name
|
|
35
|
+
* @returns {mysqlPromise.Connection}
|
|
36
|
+
*/
|
|
37
|
+
const createPromiseClient = async (options, name = null) => {
|
|
38
|
+
validate(options, {
|
|
39
|
+
host: 'required|string',
|
|
40
|
+
user: 'required|string',
|
|
41
|
+
password: 'required|string',
|
|
42
|
+
port: 'required|integer',
|
|
43
|
+
database: 'required|string',
|
|
44
|
+
});
|
|
45
|
+
const key = name ? name :
|
|
46
|
+
`${options.host}:${options.port}:${options.user}:${options.password}:${options.database}`;
|
|
47
|
+
if (clients[key]) {
|
|
48
|
+
return clients[key];
|
|
49
|
+
}
|
|
50
|
+
clients[key] = await mysqlPromise.createConnection(options);
|
|
51
|
+
return clients[key];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* create pool
|
|
56
|
+
* @param {mysql.PoolOptions} options
|
|
57
|
+
* @returns {mysql.Pool}
|
|
58
|
+
*/
|
|
59
|
+
const createPool = (options, name = null) => {
|
|
60
|
+
validate(options, {
|
|
61
|
+
host: 'required|string',
|
|
62
|
+
user: 'required|string',
|
|
63
|
+
password: 'required|string',
|
|
64
|
+
port: 'required|integer',
|
|
65
|
+
database: 'required|string',
|
|
66
|
+
});
|
|
67
|
+
const key = name ? name :
|
|
68
|
+
`${options.host}:${options.port}:${options.user}:${options.password}:${options.database}`;
|
|
69
|
+
if (clients[key]) {
|
|
70
|
+
return clients[key];
|
|
71
|
+
}
|
|
72
|
+
const pool = mysql.createPool(options);
|
|
73
|
+
clients[key] = pool;
|
|
74
|
+
return pool;
|
|
75
|
+
};
|
|
76
|
+
|
|
31
77
|
/**
|
|
32
78
|
* get client
|
|
33
79
|
* @param {*} name
|
|
@@ -45,5 +91,7 @@ const getClient = (name) => {
|
|
|
45
91
|
|
|
46
92
|
module.exports = {
|
|
47
93
|
getClient,
|
|
48
|
-
|
|
94
|
+
createPool,
|
|
95
|
+
createClient,
|
|
96
|
+
createPromiseClient
|
|
49
97
|
};
|
package/src/hook.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const EventEmitter = require('events');
|
|
4
|
+
|
|
5
|
+
const events = {}; // event tree
|
|
6
|
+
const hook = new EventEmitter();
|
|
7
|
+
|
|
8
|
+
const pushEvent = ({ label, table, opt, callback }) => {
|
|
9
|
+
label = label || '*';
|
|
10
|
+
if (!events[label]) {
|
|
11
|
+
events[label] = {};
|
|
12
|
+
}
|
|
13
|
+
table = table || '*';
|
|
14
|
+
if (!events[label][table]) {
|
|
15
|
+
events[label][table] = {};
|
|
16
|
+
}
|
|
17
|
+
opt = opt || '*';
|
|
18
|
+
if (!events[label][table][opt]) {
|
|
19
|
+
events[label][table][opt] = 0;
|
|
20
|
+
}
|
|
21
|
+
events[label][table][opt]++;
|
|
22
|
+
hook.on(`${label}::${table}::${opt}`, callback);
|
|
23
|
+
return { label, table, opt, callback };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const eventRecur = (curr, trace, step, paths, args) => {
|
|
27
|
+
if (step === trace.length) {
|
|
28
|
+
hook.emit(paths.join('::'), ...args);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const t = trace[step];
|
|
32
|
+
if (curr['*']) {
|
|
33
|
+
paths[step] = '*';
|
|
34
|
+
eventRecur(curr[t], trace, step + 1, paths, args);
|
|
35
|
+
}
|
|
36
|
+
if (curr[t]) {
|
|
37
|
+
paths[step] = t;
|
|
38
|
+
eventRecur(curr[t], trace, step + 1, paths, args);
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handleEvent = (label, table, opt, ...args) => {
|
|
44
|
+
let curr = events;
|
|
45
|
+
let step = 0;
|
|
46
|
+
let trace = [label, table, opt];
|
|
47
|
+
eventRecur(curr, trace, step, [], args);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
class Hook {
|
|
51
|
+
static pre(callback, { table, opt }) {
|
|
52
|
+
return pushEvent({
|
|
53
|
+
label: 'pre', table, opt, callback
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static post(callback, { table, opt }) {
|
|
58
|
+
return pushEvent({
|
|
59
|
+
label: 'post', table, opt, callback
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
Hook,
|
|
66
|
+
handleEvent
|
|
67
|
+
};
|
package/src/operator.js
CHANGED
|
@@ -2,16 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
const { Builder } = require('./builder');
|
|
4
4
|
const Query = require('./query');
|
|
5
|
+
const { handleEvent } = require('./hook');
|
|
5
6
|
|
|
6
|
-
const query = async (conn, options) => {
|
|
7
|
+
const query = async (conn, options, transaction) => {
|
|
7
8
|
return new Promise((resolve, reject) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
if (transaction) {
|
|
10
|
+
conn.execute(options)
|
|
11
|
+
.then((res) => resolve(res))
|
|
12
|
+
.catch((err) => reject(err));
|
|
13
|
+
} else {
|
|
14
|
+
conn.query(options, (err, result) => {
|
|
15
|
+
if (err) {
|
|
16
|
+
reject(err);
|
|
17
|
+
} else {
|
|
18
|
+
resolve(result);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
15
22
|
});
|
|
16
23
|
};
|
|
17
24
|
|
|
@@ -38,18 +45,30 @@ class QueryOperator extends Query {
|
|
|
38
45
|
const options = {
|
|
39
46
|
sql, values
|
|
40
47
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
const from = this.options.tables.map(t => t.tableName).join(',');
|
|
49
|
+
handleEvent('pre', from, this.options.operator, this.options);
|
|
50
|
+
let res;
|
|
51
|
+
try {
|
|
52
|
+
switch (this.options.operator) {
|
|
53
|
+
case 'find': {
|
|
54
|
+
const tmp = await query(this.conn, options, this.options.transaction);
|
|
55
|
+
res = tmp[0];
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case 'count': {
|
|
59
|
+
const [tmp] = await query(this.conn, options, this.options.transaction);
|
|
60
|
+
res = tmp.count;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
default:
|
|
64
|
+
res = await query(this.conn, options, this.options.transaction);
|
|
49
65
|
}
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
handleEvent('post', from, this.options.operator, this.options, res);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
handleEvent('post', from, this.options.operator, this.options, err);
|
|
69
|
+
throw err;
|
|
52
70
|
}
|
|
71
|
+
return res;
|
|
53
72
|
}
|
|
54
73
|
|
|
55
74
|
async select() {
|
|
@@ -83,9 +102,9 @@ class QueryOperator extends Query {
|
|
|
83
102
|
return await this.exec();
|
|
84
103
|
}
|
|
85
104
|
|
|
86
|
-
async delete(id) {
|
|
105
|
+
async delete(id, index_field_name = 'id') {
|
|
87
106
|
if (id) {
|
|
88
|
-
this.where(
|
|
107
|
+
this.where(index_field_name, id);
|
|
89
108
|
}
|
|
90
109
|
this.options.operator = 'delete';
|
|
91
110
|
return await this.exec();
|
package/src/query.js
CHANGED
|
@@ -10,7 +10,9 @@ class Query {
|
|
|
10
10
|
data: null,
|
|
11
11
|
groupField: [],
|
|
12
12
|
having: [],
|
|
13
|
-
joins: []
|
|
13
|
+
joins: [],
|
|
14
|
+
suffix: null,
|
|
15
|
+
transaction: false
|
|
14
16
|
};
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -119,6 +121,7 @@ class Query {
|
|
|
119
121
|
let lastOpt = this.options.having[this.options.having.length - 1].opt.toUpperCase();
|
|
120
122
|
if (lastOpt !== 'AND' && lastOpt !== 'OR') {
|
|
121
123
|
this.options.having.push({ key: null, opt: 'AND', value: null });
|
|
124
|
+
return this;
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
this.options.having.push({ key, opt, value });
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line no-unused-vars
|
|
4
|
+
const mysql = require('mysql2/promise');
|
|
5
|
+
const { QueryOperator } = require('./operator');
|
|
6
|
+
|
|
7
|
+
const levels = {
|
|
8
|
+
RU: 'READ UNCOMMITTED',
|
|
9
|
+
RC: 'READ COMMITTED',
|
|
10
|
+
RR: 'REPEATABLE READ',
|
|
11
|
+
S: 'SERIALIZABLE'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
class TransactionOperator extends QueryOperator {
|
|
15
|
+
/**
|
|
16
|
+
* @param {*} conn
|
|
17
|
+
*/
|
|
18
|
+
constructor(conn) {
|
|
19
|
+
super(conn);
|
|
20
|
+
this.options.transaction = true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @example LOCK IN SHARE MODE
|
|
25
|
+
* @example FOR UPDATE
|
|
26
|
+
*/
|
|
27
|
+
append(suffix) {
|
|
28
|
+
this.options.suffix = suffix || null;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class TransactionHandler {
|
|
34
|
+
/**
|
|
35
|
+
* @param {mysql.Connection} conn
|
|
36
|
+
* @param {mysql.ConnectionOptions} options
|
|
37
|
+
*/
|
|
38
|
+
constructor(conn, options = {}) {
|
|
39
|
+
this.isbegin = false;
|
|
40
|
+
this.conn = conn;
|
|
41
|
+
this.level = options.level || 'SERIALIZABLE';
|
|
42
|
+
if (levels[this.level]) {
|
|
43
|
+
this.level = levels[this.level];
|
|
44
|
+
}
|
|
45
|
+
if (!Object.values(levels).includes(this.level)) {
|
|
46
|
+
throw new Error('Invalid transaction level: ' + this.level);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async query(options) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
this.conn.query(options, (err, result) => {
|
|
53
|
+
if (err) {
|
|
54
|
+
reject(err);
|
|
55
|
+
} else {
|
|
56
|
+
resolve(result);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async execute(sql, values = []) {
|
|
63
|
+
return this.conn.execute(sql, values);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async lastInsertId(alias = 'insert_id') {
|
|
67
|
+
let sql = `SELECT LAST_INSERT_ID() as ${alias}`;
|
|
68
|
+
const [row] = await this.execute(sql);
|
|
69
|
+
return row && row[0] ? row[0][alias] : 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async begin() {
|
|
73
|
+
this.isbegin = true;
|
|
74
|
+
await this.execute(`SET TRANSACTION ISOLATION LEVEL ${this.level}`);
|
|
75
|
+
await this.conn.beginTransaction();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
table(table, alias = null) {
|
|
79
|
+
if (!this.isbegin) {
|
|
80
|
+
throw new Error('Transaction is not begin');
|
|
81
|
+
}
|
|
82
|
+
return (new TransactionOperator(this.conn)).table(table, alias);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async upsert(tableName, data, condition = {}) {
|
|
86
|
+
const count = await this.table(tableName).whereObject(condition).count();
|
|
87
|
+
if (count) {
|
|
88
|
+
return await this.table(tableName).whereObject(condition).update(data);
|
|
89
|
+
}
|
|
90
|
+
return await this.table(tableName).insert(data);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async commit() {
|
|
94
|
+
if (!this.isbegin) {
|
|
95
|
+
throw new Error('Transaction is not begin');
|
|
96
|
+
}
|
|
97
|
+
await this.conn.commit();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async rollback() {
|
|
101
|
+
if (!this.isbegin) {
|
|
102
|
+
throw new Error('Transaction is not begin');
|
|
103
|
+
}
|
|
104
|
+
await this.conn.rollback();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
TransactionOperator,
|
|
110
|
+
TransactionHandler
|
|
111
|
+
};
|