Skip to content

Commit 5941c69

Browse files
authored
feat: support custom query lifecricle (ali-sdk#104)
1 parent e7d488a commit 5941c69

File tree

6 files changed

+177
-3
lines changed

6 files changed

+177
-3
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,19 @@ const rows = await db.query('SELECT * FROM your_table WHERE id=:id', { id: 123 }
356356
console.log(rows);
357357
```
358358

359+
### Custom query lifecricle
360+
361+
```ts
362+
db.beforeQuery((sql: string) => {
363+
// change sql string
364+
return `/* add custom format here */ ${sql}`;
365+
});
366+
367+
db.afterQuery((sql: string, result: any, execDuration: number, err?: Error) => {
368+
// handle logger here
369+
});
370+
```
371+
359372
## APIs
360373

361374
- `*` Meaning this function is yieldable.

src/client.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,15 @@ export class RDSClient extends Operator {
5757

5858
async getConnection() {
5959
try {
60-
const conn = await this.#pool.getConnection();
61-
return new RDSConnection(conn);
60+
const _conn = await this.#pool.getConnection();
61+
const conn = new RDSConnection(_conn);
62+
if (this.beforeQueryHandler) {
63+
conn.beforeQuery(this.beforeQueryHandler);
64+
}
65+
if (this.afterQueryHandler) {
66+
conn.afterQuery(this.afterQueryHandler);
67+
}
68+
return conn;
6269
} catch (err) {
6370
if (err.name === 'Error') {
6471
err.name = 'RDSClientGetConnectionError';
@@ -80,7 +87,14 @@ export class RDSClient extends Operator {
8087
conn.release();
8188
throw err;
8289
}
83-
return new RDSTransaction(conn);
90+
const tran = new RDSTransaction(conn);
91+
if (this.beforeQueryHandler) {
92+
tran.beforeQuery(this.beforeQueryHandler);
93+
}
94+
if (this.afterQueryHandler) {
95+
tran.afterQuery(this.afterQueryHandler);
96+
}
97+
return tran;
8498
}
8599

86100
/**

src/operator.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { debuglog } from 'node:util';
22
import { SqlString } from './sqlstring';
33
import literals from './literals';
44
import {
5+
AfterQueryHandler, BeforeQueryHandler,
56
DeleteResult,
67
InsertOption, InsertResult,
78
LockResult, LockTableOption,
@@ -15,8 +16,19 @@ const debug = debuglog('ali-rds:operator');
1516
* Operator Interface
1617
*/
1718
export abstract class Operator {
19+
protected beforeQueryHandler?: BeforeQueryHandler;
20+
protected afterQueryHandler?: AfterQueryHandler;
21+
1822
get literals() { return literals; }
1923

24+
beforeQuery(beforeQueryHandler: BeforeQueryHandler) {
25+
this.beforeQueryHandler = beforeQueryHandler;
26+
}
27+
28+
afterQuery(afterQueryHandler: AfterQueryHandler) {
29+
this.afterQueryHandler = afterQueryHandler;
30+
}
31+
2032
escape(value: any, stringifyObjects?: boolean, timeZone?: string): string {
2133
return SqlString.escape(value, stringifyObjects, timeZone);
2234
}
@@ -45,17 +57,33 @@ export abstract class Operator {
4557
if (values) {
4658
sql = this.format(sql, values);
4759
}
60+
if (this.beforeQueryHandler) {
61+
const newSql = this.beforeQueryHandler(sql);
62+
if (newSql) {
63+
sql = newSql;
64+
}
65+
}
4866
debug('query %o', sql);
67+
let execDuration: number;
68+
const queryStart = Date.now();
4969
try {
5070
const rows = await this._query(sql);
71+
execDuration = Date.now() - queryStart;
72+
if (this.afterQueryHandler) {
73+
this.afterQueryHandler(sql, rows, execDuration);
74+
}
5175
if (Array.isArray(rows)) {
5276
debug('query get %o rows', rows.length);
5377
} else {
5478
debug('query result: %o', rows);
5579
}
5680
return rows;
5781
} catch (err) {
82+
execDuration = Date.now() - queryStart;
5883
err.stack = `${err.stack}\n sql: ${sql}`;
84+
if (this.afterQueryHandler) {
85+
this.afterQueryHandler(sql, null, execDuration, err);
86+
}
5987
debug('query error: %o', err);
6088
throw err;
6189
}

src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,6 @@ export type LockTableOption = {
5050
lockType: string;
5151
tableAlias: string;
5252
};
53+
54+
export type BeforeQueryHandler = (sql: string) => string | undefined | void;
55+
export type AfterQueryHandler = (sql: string, result: any, execDuration: number, err?: Error) => void;

test/client.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,4 +1203,59 @@ describe('test/client.test.ts', () => {
12031203
await db2.end();
12041204
});
12051205
});
1206+
1207+
describe('query lifecricle work', () => {
1208+
it('should work on client and transactions', async () => {
1209+
const db = new RDSClient(config);
1210+
let count = 0;
1211+
let lastSql = '';
1212+
db.beforeQuery(sql => {
1213+
count++;
1214+
lastSql = sql;
1215+
});
1216+
let lastArgs: any;
1217+
db.afterQuery((...args) => {
1218+
lastArgs = args;
1219+
});
1220+
await db.query('select * from ?? limit 10', [ table ]);
1221+
assert.equal(lastSql, 'select * from `ali-sdk-test-user` limit 10');
1222+
assert.equal(lastArgs[0], lastSql);
1223+
assert.equal(Array.isArray(lastArgs[1]), true);
1224+
assert.equal(count, 1);
1225+
1226+
await db.beginTransactionScope(async conn => {
1227+
await conn.query(`insert into ??(name, email, gmt_create, gmt_modified)
1228+
values(?, ?, now(), now())`,
1229+
[ table, prefix + 'beginTransactionScope1', prefix + 'm@beginTransactionScope1.com' ]);
1230+
});
1231+
assert.equal(lastSql, 'insert into `ali-sdk-test-user`(name, email, gmt_create, gmt_modified)\n' +
1232+
` values('${prefix}beginTransactionScope1', '${prefix}m@beginTransactionScope1.com', now(), now())`);
1233+
assert.equal(lastArgs[0], lastSql);
1234+
assert.equal(lastArgs[1].affectedRows, 1);
1235+
assert.equal(count, 2);
1236+
1237+
await db.beginDoomedTransactionScope(async conn => {
1238+
await conn.query(`insert into ??(name, email, gmt_create, gmt_modified)
1239+
values(?, ?, now(), now())`,
1240+
[ table, prefix + 'beginDoomedTransactionScope1', prefix + 'm@beginDoomedTransactionScope1.com' ]);
1241+
});
1242+
assert.equal(lastSql, 'insert into `ali-sdk-test-user`(name, email, gmt_create, gmt_modified)\n' +
1243+
` values('${prefix}beginDoomedTransactionScope1', '${prefix}m@beginDoomedTransactionScope1.com', now(), now())`);
1244+
assert.equal(lastArgs[0], lastSql);
1245+
assert.equal(lastArgs[1].affectedRows, 1);
1246+
assert.equal(count, 3);
1247+
1248+
const conn = await db.getConnection();
1249+
await conn.beginTransaction();
1250+
await conn.query(`insert into ??(name, email, gmt_create, gmt_modified)
1251+
values(?, ?, now(), now())`,
1252+
[ table, prefix + 'transaction1', prefix + 'm@transaction1.com' ]);
1253+
await conn.commit();
1254+
assert.equal(lastSql, 'insert into `ali-sdk-test-user`(name, email, gmt_create, gmt_modified)\n' +
1255+
` values('${prefix}transaction1', '${prefix}m@transaction1.com', now(), now())`);
1256+
assert.equal(lastArgs[0], lastSql);
1257+
assert.equal(lastArgs[1].affectedRows, 1);
1258+
assert.equal(count, 4);
1259+
});
1260+
});
12061261
});

test/operator.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,65 @@ describe('test/operator.test.ts', () => {
6565
}
6666
});
6767
});
68+
69+
describe('beforeQuery(), afterQuery()', () => {
70+
class CustomOperator extends Operator {
71+
protected async _query(sql: string): Promise<any> {
72+
// console.log(sql);
73+
if (sql === 'error') throw new Error('mock error');
74+
return { sql };
75+
}
76+
}
77+
78+
it('should override query sql', async () => {
79+
const op = new CustomOperator();
80+
op.beforeQuery(sql => {
81+
return `hello ${sql}`;
82+
});
83+
const result = await op.query('foo');
84+
assert.equal(result.sql, 'hello foo');
85+
});
86+
87+
it('should not override query sql', async () => {
88+
const op = new CustomOperator();
89+
op.beforeQuery(sql => {
90+
assert(sql);
91+
});
92+
const result = await op.query('foo');
93+
assert.equal(result.sql, 'foo');
94+
});
95+
96+
it('should get query result on after hook', async () => {
97+
const op = new CustomOperator();
98+
op.afterQuery((sql, result, execDuration, err) => {
99+
assert.equal(sql, 'foo');
100+
assert.deepEqual(result, { sql });
101+
assert.equal(typeof execDuration, 'number');
102+
assert(execDuration >= 0);
103+
assert.equal(err, undefined);
104+
});
105+
const result = await op.query('foo');
106+
assert.equal(result.sql, 'foo');
107+
});
108+
109+
it('should get query error on after hook', async () => {
110+
const op = new CustomOperator();
111+
op.afterQuery((sql, result, execDuration, err) => {
112+
assert.equal(sql, 'error');
113+
assert.equal(result, null);
114+
assert.equal(typeof execDuration, 'number');
115+
assert(execDuration >= 0);
116+
assert(err instanceof Error);
117+
assert.equal(err.message, 'mock error');
118+
assert.match(err.stack!, /sql: error/);
119+
});
120+
await assert.rejects(async () => {
121+
await op.query('error');
122+
}, (err: any) => {
123+
assert.equal(err.message, 'mock error');
124+
assert.match(err.stack, /sql: error/);
125+
return true;
126+
});
127+
});
128+
});
68129
});

0 commit comments

Comments
 (0)