Skip to content

Commit a42d995

Browse files
authored
feat: add changeUser support
1 parent d2976ab commit a42d995

File tree

5 files changed

+326
-28
lines changed

5 files changed

+326
-28
lines changed

README.md

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,41 @@ let connection = mysql.getClient()
177177
let value = connection.escape('Some value to be escaped')
178178
```
179179

180+
You can change the user of an existing connection using the `changeUser()` method. This is useful when you need to switch to a different MySQL user with different permissions.
181+
182+
```javascript
183+
// Change to a different user
184+
await mysql.changeUser({
185+
user: 'newuser',
186+
password: 'newpassword'
187+
})
188+
189+
// Now queries will be executed as the new user
190+
let results = await mysql.query('SELECT * FROM restricted_table')
191+
```
192+
193+
You can also use the `changeUser()` method to change the current database, which is equivalent to the `USE DATABASE` SQL statement:
194+
195+
```javascript
196+
// Change to a different database
197+
await mysql.changeUser({
198+
database: 'new_database' // Change the database only
199+
})
200+
201+
// Now queries will be executed against the new database
202+
let results = await mysql.query('SELECT * FROM new_database_table')
203+
```
204+
205+
Alternatively, you can use the standard SQL `USE DATABASE` statement with the `query()` method:
206+
207+
```javascript
208+
// Change to a different database using SQL
209+
await mysql.query('USE new_database')
210+
211+
// Now queries will be executed against the new database
212+
let results = await mysql.query('SELECT * FROM new_database_table')
213+
```
214+
180215
## Configuration Options
181216

182217
There are two ways to provide a configuration.
@@ -357,6 +392,4 @@ Other tests that use larger configurations were extremely successful too, but I'
357392
Contributions, ideas and bug reports are welcome and greatly appreciated. Please add [issues](https://github.com/jeremydaly/serverless-mysql/issues) for suggestions and bug reports or create a pull request.
358393

359394
## TODO
360-
- Add `changeUser` support
361-
- Add connection retries on failed queries
362-
- Add automated tests and coverage reports
395+
- Add connection retries on failed queries

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ declare namespace serverlessMysql {
114114
getClient(): MySQL.Connection;
115115
getConfig(): MySQL.ConnectionOptions;
116116
getErrorCount(): number;
117+
changeUser(options: MySQL.ConnectionOptions): Promise<boolean>;
117118
};
118119
}
119120

index.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,38 @@ module.exports = (params) => {
262262

263263
} // end query
264264

265+
// Change user method
266+
const changeUser = async (options) => {
267+
// Ensure we have a connection
268+
await connect()
269+
270+
// Return a new promise
271+
return new PromiseLibrary((resolve, reject) => {
272+
if (client !== null) {
273+
// Call the underlying changeUser method
274+
client.changeUser(options, (err) => {
275+
if (err) {
276+
// If connection error, reset client and reject
277+
if (err.code === 'PROTOCOL_CONNECTION_LOST' ||
278+
err.code === 'EPIPE' ||
279+
err.code === 'ECONNRESET') {
280+
resetClient() // reset the client
281+
reject(err)
282+
} else {
283+
// For other errors, just reject
284+
reject(err)
285+
}
286+
} else {
287+
// Successfully changed user
288+
resolve(true)
289+
}
290+
})
291+
} else {
292+
// No client connection exists
293+
reject(new Error('No connection available to change user'))
294+
}
295+
})
296+
} // end changeUser
265297

266298
// Get the max connections (either for this user or total)
267299
const getMaxConnections = async () => {
@@ -459,7 +491,8 @@ module.exports = (params) => {
459491
getCounter,
460492
getClient,
461493
getConfig,
462-
getErrorCount
494+
getErrorCount,
495+
changeUser
463496
}
464497

465498
} // end exports

test/integration/change-user.spec.js

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
'use strict';
2+
3+
const { expect } = require('chai');
4+
const {
5+
createTestConnection,
6+
closeConnection,
7+
setupTestTable
8+
} = require('./helpers/setup');
9+
10+
describe('MySQL changeUser Integration Tests', function () {
11+
this.timeout(10000);
12+
13+
let db;
14+
15+
before(function () {
16+
db = createTestConnection();
17+
});
18+
19+
after(async function () {
20+
try {
21+
if (db) {
22+
await db.end({ timeout: 5000 });
23+
await closeConnection(db);
24+
}
25+
} catch (err) {
26+
console.error('Error closing connection:', err);
27+
}
28+
});
29+
30+
it('should change user successfully if second user credentials are provided', async function () {
31+
const secondUser = process.env.MYSQL_SECOND_USER;
32+
const secondPassword = process.env.MYSQL_SECOND_PASSWORD;
33+
34+
if (!secondUser || !secondPassword) {
35+
console.warn('Skipping full changeUser test - no second user credentials provided');
36+
console.warn('Set MYSQL_SECOND_USER and MYSQL_SECOND_PASSWORD environment variables to test actual user switching');
37+
38+
const changeUserPromise = db.changeUser({
39+
user: db.getConfig().user,
40+
password: db.getConfig().password
41+
});
42+
43+
expect(changeUserPromise).to.be.a('promise');
44+
await changeUserPromise;
45+
return;
46+
}
47+
48+
try {
49+
const initialUserResult = await db.query('SELECT CURRENT_USER() AS user');
50+
const initialUser = initialUserResult[0].user;
51+
52+
await db.changeUser({
53+
user: secondUser,
54+
password: secondPassword
55+
});
56+
57+
const newUserResult = await db.query('SELECT CURRENT_USER() AS user');
58+
const newUser = newUserResult[0].user;
59+
60+
expect(newUser).to.include(secondUser);
61+
expect(newUser).to.not.equal(initialUser);
62+
63+
const config = db.getConfig();
64+
await db.changeUser({
65+
user: config.user,
66+
password: config.password
67+
});
68+
} catch (error) {
69+
if (error.code === 'ER_ACCESS_DENIED_ERROR') {
70+
console.error('Access denied when changing user. Check that the provided credentials are correct and have proper permissions.');
71+
console.error('Error details:', error.message);
72+
}
73+
74+
throw error;
75+
}
76+
});
77+
78+
it('should handle errors when changing to non-existent user', async function () {
79+
try {
80+
await db.changeUser({
81+
user: 'non_existent_user_' + Date.now(),
82+
password: 'wrong_password'
83+
});
84+
85+
expect.fail('Should have thrown an error');
86+
} catch (error) {
87+
expect(error).to.be.an('error');
88+
expect(error).to.have.property('code');
89+
expect(error.message).to.include('Access denied for user', 'Error message should indicate access was denied');
90+
}
91+
});
92+
93+
it('should support changing the database', async function () {
94+
const testDbName = 'serverless_mysql_test_db_' + Date.now().toString().slice(-6);
95+
96+
try {
97+
await db.query(`CREATE DATABASE IF NOT EXISTS ${testDbName}`);
98+
99+
const initialDbResult = await db.query('SELECT DATABASE() AS db');
100+
const initialDb = initialDbResult[0].db;
101+
102+
await db.changeUser({
103+
user: db.getConfig().user,
104+
password: db.getConfig().password,
105+
database: testDbName
106+
});
107+
108+
const newDbResult = await db.query('SELECT DATABASE() AS db');
109+
const newDb = newDbResult[0].db;
110+
111+
expect(newDb).to.equal(testDbName);
112+
expect(newDb).to.not.equal(initialDb);
113+
114+
await db.query(`
115+
CREATE TABLE IF NOT EXISTS test_table (
116+
id INT AUTO_INCREMENT PRIMARY KEY,
117+
name VARCHAR(50) NOT NULL
118+
)
119+
`);
120+
121+
await db.query(`INSERT INTO test_table (name) VALUES ('test')`);
122+
123+
const result = await db.query('SELECT * FROM test_table');
124+
expect(result).to.be.an('array');
125+
expect(result).to.have.lengthOf(1);
126+
expect(result[0].name).to.equal('test');
127+
128+
await db.changeUser({
129+
user: db.getConfig().user,
130+
password: db.getConfig().password,
131+
database: initialDb
132+
});
133+
134+
const finalDbResult = await db.query('SELECT DATABASE() AS db');
135+
expect(finalDbResult[0].db).to.equal(initialDb);
136+
137+
} catch (error) {
138+
throw error;
139+
} finally {
140+
try {
141+
await db.query(`USE ${db.getConfig().database}`);
142+
await db.query(`DROP DATABASE IF EXISTS ${testDbName}`);
143+
} catch (cleanupError) {
144+
console.error('Error during cleanup:', cleanupError);
145+
}
146+
}
147+
});
148+
149+
it('should support changing the database with only database parameter', async function () {
150+
const testDbName = 'serverless_mysql_test_db_' + Date.now().toString().slice(-6);
151+
152+
try {
153+
await db.query(`CREATE DATABASE IF NOT EXISTS ${testDbName}`);
154+
155+
const initialDbResult = await db.query('SELECT DATABASE() AS db');
156+
const initialDb = initialDbResult[0].db;
157+
158+
await db.changeUser({
159+
database: testDbName
160+
});
161+
162+
const newDbResult = await db.query('SELECT DATABASE() AS db');
163+
const newDb = newDbResult[0].db;
164+
165+
expect(newDb).to.equal(testDbName);
166+
expect(newDb).to.not.equal(initialDb);
167+
168+
await db.query(`
169+
CREATE TABLE IF NOT EXISTS test_table_db_only (
170+
id INT AUTO_INCREMENT PRIMARY KEY,
171+
name VARCHAR(50) NOT NULL
172+
)
173+
`);
174+
175+
await db.query(`INSERT INTO test_table_db_only (name) VALUES ('db_only_test')`);
176+
177+
const result = await db.query('SELECT * FROM test_table_db_only');
178+
expect(result).to.be.an('array');
179+
expect(result).to.have.lengthOf(1);
180+
expect(result[0].name).to.equal('db_only_test');
181+
182+
const userResult = await db.query('SELECT CURRENT_USER() AS user');
183+
const currentUser = userResult[0].user;
184+
185+
const configUser = db.getConfig().user;
186+
expect(currentUser).to.include(configUser.split('@')[0]);
187+
188+
await db.changeUser({
189+
database: initialDb
190+
});
191+
192+
const finalDbResult = await db.query('SELECT DATABASE() AS db');
193+
expect(finalDbResult[0].db).to.equal(initialDb);
194+
195+
} catch (error) {
196+
throw error;
197+
} finally {
198+
try {
199+
await db.query(`USE ${db.getConfig().database}`);
200+
await db.query(`DROP DATABASE IF EXISTS ${testDbName}`);
201+
} catch (cleanupError) {
202+
console.error('Error during cleanup:', cleanupError);
203+
}
204+
}
205+
});
206+
});

0 commit comments

Comments
 (0)