From f437efb439c1770561c1d4dc79318c632e7321d9 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Fri, 9 Jun 2023 21:35:47 +0800 Subject: [PATCH] feat: dynamic retrieval of database connection configuration (#110) test fix: Node.js >= 20 will return localhost ipv6 address --- package.json | 4 +- src/PoolConfig.ts | 19 ++++++++++ src/client.ts | 14 ++++++- src/types.ts | 5 ++- test/PoolConfig.test.ts | 84 +++++++++++++++++++++++++++++++++++++++++ test/client.test.ts | 3 ++ test/config.ts | 3 +- 7 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 src/PoolConfig.ts create mode 100644 test/PoolConfig.test.ts diff --git a/package.json b/package.json index 3ce7c41..3207264 100644 --- a/package.json +++ b/package.json @@ -22,13 +22,13 @@ "devDependencies": { "@eggjs/tsconfig": "^1.3.2", "@types/mocha": "^10.0.1", - "@types/node": "^18.14.6", + "@types/node": "^20.2.5", "egg-bin": "^6.1.2", "eslint": "^8.29.0", "eslint-config-egg": "^12.1.0", "git-contributor": "^2.0.0", "mm": "^3.3.0", - "typescript": "^4.9.5" + "typescript": "^5.1.3" }, "homepage": "https://github.com/ali-sdk/ali-rds", "repository": { diff --git a/src/PoolConfig.ts b/src/PoolConfig.ts new file mode 100644 index 0000000..ece1549 --- /dev/null +++ b/src/PoolConfig.ts @@ -0,0 +1,19 @@ +import MySQLPoolConfig from 'mysql/lib/PoolConfig'; +import type { PoolConfig, ConnectionConfig } from 'mysql'; +import type { GetConnectionConfig } from './types'; + +export class RDSPoolConfig extends MySQLPoolConfig { + #getConnectionConfig: GetConnectionConfig; + + constructor(options: PoolConfig, getConnectionConfig: GetConnectionConfig) { + super(options); + this.#getConnectionConfig = getConnectionConfig; + } + + newConnectionConfig(): ConnectionConfig { + return { + ...super.newConnectionConfig(), + ...this.#getConnectionConfig(), + }; + } +} diff --git a/src/client.ts b/src/client.ts index d22d4bb..a73684f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -6,6 +6,7 @@ import type { PoolConnectionPromisify, RDSClientOptions, TransactionContext, Tra import { Operator } from './operator'; import { RDSConnection } from './connection'; import { RDSTransaction } from './transaction'; +import { RDSPoolConfig } from './PoolConfig'; import literals from './literals'; interface PoolPromisify extends Omit { @@ -35,7 +36,18 @@ export class RDSClient extends Operator { constructor(options: RDSClientOptions) { super(); const { connectionStorage, connectionStorageKey, ...mysqlOptions } = options; - this.#pool = mysql.createPool(mysqlOptions) as unknown as PoolPromisify; + // get connection options from getConnectionConfig method every time + if (mysqlOptions.getConnectionConfig) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const Pool = require('mysql/lib/Pool'); + this.#pool = new Pool({ + config: new RDSPoolConfig(mysqlOptions, mysqlOptions.getConnectionConfig), + }); + // override _needsChangeUser to return false + (this.#pool as any)._needsChangeUser = () => false; + } else { + this.#pool = mysql.createPool(mysqlOptions) as unknown as PoolPromisify; + } [ 'query', 'getConnection', diff --git a/src/types.ts b/src/types.ts index 2f074aa..6a84e75 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,10 +1,13 @@ import { AsyncLocalStorage } from 'async_hooks'; -import type { PoolConnection, PoolConfig } from 'mysql'; +import type { PoolConnection, PoolConfig, ConnectionConfig } from 'mysql'; import { RDSTransaction } from './transaction'; +export type GetConnectionConfig = () => ConnectionConfig; + export interface RDSClientOptions extends PoolConfig { connectionStorageKey?: string; connectionStorage?: AsyncLocalStorage>; + getConnectionConfig?: GetConnectionConfig } export interface PoolConnectionPromisify extends Omit { diff --git a/test/PoolConfig.test.ts b/test/PoolConfig.test.ts new file mode 100644 index 0000000..4c82c29 --- /dev/null +++ b/test/PoolConfig.test.ts @@ -0,0 +1,84 @@ +import { strict as assert } from 'node:assert'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import mm from 'mm'; +import config from './config'; +import { RDSClient } from '../src/client'; + +describe('test/PoolConfig.test.ts', () => { + const prefix = 'prefix-PoolConfig' + process.version + '-'; + const table = 'ali-sdk-test-user'; + let db: RDSClient; + let index = 0; + let newConnectionCount = 0; + before(async () => { + db = new RDSClient({ + // test getConnectionConfig + connectionLimit: 2, + getConnectionConfig() { + console.log('get connection config index: %d', ++index); + return config; + }, + }); + db.pool.on('acquire', conn => { + console.log('acquire connection %o', conn.threadId); + }); + db.pool.on('connection', conn => { + newConnectionCount++; + console.log('new connection %o', conn.threadId); + }); + db.pool.on('enqueue', () => { + console.log('Waiting for available connection slot'); + }); + db.pool.on('release', conn => { + console.log('release connection %o', conn.threadId); + }); + + try { + const sql = await fs.readFile(path.join(__dirname, 'rds_init.sql'), 'utf-8'); + await db.query(sql); + } catch (err) { + console.log('init table error: %s', err); + } + await db.query('delete from ?? where name like ?', [ table, prefix + '%' ]); + }); + + after(async () => { + return await db.end(); + }); + + afterEach(() => { + mm.restore(); + }); + + describe('new RDSClient(options.getConnectionConfig)', () => { + it('should get connection config from newConnectionConfig()', async () => { + assert.equal(db.pool.config.connectionConfig.database, undefined); + assert.equal(index, 1); + assert.equal((db.pool.config as any).newConnectionConfig().database, 'test'); + assert.equal(index, 2); + }); + + it('should connect rds success', async () => { + const rows = await db.query('show tables'); + // console.log(rows); + assert(rows); + assert(Array.isArray(rows)); + assert.equal(index, 2); + const results = await Promise.all([ + db.query('show tables'), + db.query('show tables'), + db.query('show tables'), + ]); + assert.equal(results.length, 3); + assert(results[0]); + assert(Array.isArray(results[0])); + assert(results[1]); + assert(Array.isArray(results[1])); + assert(results[2]); + assert(Array.isArray(results[2])); + assert.equal(index, 3); + assert.equal(newConnectionCount, 2); + }); + }); +}); diff --git a/test/client.test.ts b/test/client.test.ts index 972343b..c8d8349 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -172,6 +172,7 @@ describe('test/client.test.ts', () => { it('should beginTransaction error', async () => { const failDB = new RDSClient({ port: 12312, + host: '127.0.0.1', }); await assert.rejects(async () => { await failDB.beginTransaction(); @@ -339,6 +340,7 @@ describe('test/client.test.ts', () => { it('should beginTransactionScope() error', async () => { const failDB = new RDSClient({ port: 12312, + host: '127.0.0.1', }); await assert.rejects(async () => { await failDB.beginTransactionScope(async () => { @@ -1321,6 +1323,7 @@ describe('test/client.test.ts', () => { it('should throw error when mysql connect fail', async () => { const db = new RDSClient({ port: 33061, + host: '127.0.0.1', }); try { await db.getConnection(); diff --git a/test/config.ts b/test/config.ts index 96a790e..71e07eb 100644 --- a/test/config.ts +++ b/test/config.ts @@ -1,5 +1,6 @@ export default { - host: 'localhost', + // host: 'localhost', + host: '127.0.0.1', port: 3306, user: 'root', password: '',