Skip to content

Commit 02b3253

Browse files
committed
First pass at changes to create/destroy manager and get/release connection.
1 parent 4a9cbd1 commit 02b3253

File tree

4 files changed

+214
-183
lines changed

4 files changed

+214
-183
lines changed

machines/create-manager.js

Lines changed: 164 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module.exports = {
1515
'\n'+
1616
'Note that a manager instance does not necessarily need to correspond with a pool though--'+
1717
'it might simply be a container for storing config, or it might refer to multiple pools '+
18-
'(e.g. a ClusterPool from felixge\'s `mysql` package).',
18+
'(e.g. a PoolCluster from felixge\'s `mysql` package).',
1919

2020

2121
inputs: {
@@ -109,8 +109,169 @@ module.exports = {
109109

110110

111111
fn: function (inputs, exits){
112-
// TODO
113-
return exits.error();
112+
var util = require('util');
113+
var Url = require('url');
114+
var felix = require('mysql');
115+
116+
117+
// Note:
118+
// Support for different types of managers is database-specific, and is not
119+
// built into the Waterline driver spec-- however this type of configurability
120+
// can be instrumented using `meta`.
121+
//
122+
// In particular, support for ad-hoc connections (i.e. no pool) and clusters/multiple
123+
// pools (see "PoolCluster": https://github.com/felixge/node-mysql/blob/v2.10.2/Readme.md#poolcluster)
124+
// could be implemented here, using properties on `meta` to determine whether or not
125+
// to have this manager produce connections ad-hoc, from a pool, or from a cluster of pools.
126+
//
127+
// Feel free to fork this driver and customize as you see fit. Also note that
128+
// contributions to the core driver in this area are welcome and greatly appreciated!
129+
130+
131+
132+
// Build a local variable (`_mysqlClientConfig`) to house a dictionary
133+
// of additional MySQL options that will be passed into `.createPool()`
134+
// (Note that these could also be used with `.connect()` or `.createPoolCluster()`)
135+
//
136+
// This is pulled from the `connectionString` and `meta` inputs, and used for
137+
// configuring stuff like `host` and `password`.
138+
//
139+
// For a complete list of available options, see:
140+
// • https://github.com/felixge/node-mysql#connection-options
141+
//
142+
// However, note that supported options are explicitly whitelisted below.
143+
var _mysqlClientConfig = {};
144+
145+
146+
147+
148+
// Validate and parse `meta` (if specified).
149+
if ( !util.isUndefined(inputs.meta) ) {
150+
if ( !util.isObject(inputs.meta) ) {
151+
return exits.error('If provided, `meta` must be a dictionary.');
152+
}
153+
154+
// Use properties of `meta` directly as MySQL client config.
155+
// (note that we're very careful to only stick a property on the client config
156+
// if it was not undefined, just in case that matters)
157+
[
158+
// MySQL Client Options:
159+
// ============================================
160+
161+
// Basic:
162+
'host', 'port', 'database', 'user', 'password',
163+
'charset', 'timezone', 'ssl',
164+
165+
// Advanced:
166+
'connectTimeout', 'stringifyObjects', 'insecureAuth', 'typeCast',
167+
'queryFormat', 'supportBigNumbers', 'bigNumberStrings', 'dateStrings',
168+
'debug', 'trace', 'multipleStatements', 'flags',
169+
170+
// Pool-specific:
171+
'acquireTimeout', 'waitForConnections', 'connectionLimit', 'queueLimit',
172+
173+
].forEach(function (mysqlClientConfKeyName){
174+
if ( !util.isUndefined(inputs.meta[mysqlClientConfKeyName]) ) {
175+
_mysqlClientConfig[mysqlClientConfKeyName] = inputs.meta[mysqlClientConfKeyName];
176+
}
177+
});
178+
179+
180+
// In the future, other special properties of `meta` could be used
181+
// as options for the manager-- e.g. whether or not to use pooling,
182+
// or the connection strings of replicas, etc.
183+
184+
// // Now use other special properties of `meta` as our higher-level
185+
// // logical machinepack options.
186+
// [
187+
// // Machinepack Configuration:
188+
// // ============================================
189+
// '',
190+
// ].forEach(function (pkgConfKeyName) {
191+
// // ...
192+
// });
193+
}
194+
195+
196+
// Validate & parse connection string, pulling out MySQL client config
197+
// (call `malformed` if invalid).
198+
//
199+
// Remember: connection string takes priority over `meta` in the event of a conflict.
200+
try {
201+
var parsedConnectionStr = Url.parse(inputs.connectionString);
202+
203+
// Validate that a protocol was found before other pieces
204+
// (otherwise other parsed info will be very weird and wrong)
205+
if ( !parsedConnectionStr.protocol ) {
206+
throw new Error('Protocol (i.e. `mysql://`) is required in connection string.');
207+
}
208+
209+
// Parse port & host
210+
var DEFAULT_HOST = 'localhost';
211+
var DEFAULT_PORT = 3306;
212+
if (parsedConnectionStr.port) { _mysqlClientConfig.port = +parsedConnectionStr.port; }
213+
else { _mysqlClientConfig.port = DEFAULT_PORT; }
214+
if (parsedConnectionStr.hostname) { _mysqlClientConfig.host = parsedConnectionStr.hostname; }
215+
else { _mysqlClientConfig.host = DEFAULT_HOST; }
216+
217+
// Parse user & password
218+
if ( parsedConnectionStr.auth && util.isString(parsedConnectionStr.auth) ) {
219+
var authPieces = parsedConnectionStr.auth.split(/:/);
220+
if (authPieces[0]) {
221+
_mysqlClientConfig.user = authPieces[0];
222+
}
223+
if (authPieces[1]) {
224+
_mysqlClientConfig.password = authPieces[1];
225+
}
226+
}
227+
228+
// Parse database name
229+
if (util.isString(parsedConnectionStr.pathname) ) {
230+
var _databaseName = parsedConnectionStr.pathname;
231+
// Trim leading and trailing slashes
232+
_databaseName = _databaseName.replace(/^\/+/, '');
233+
_databaseName = _databaseName.replace(/\/+$/, '');
234+
// If anything is left, use it as the database name.
235+
if ( _databaseName ) {
236+
_mysqlClientConfig.database = _databaseName;
237+
}
238+
}
239+
}
240+
catch (_e) {
241+
_e.message = util.format('Provided value (`%s`) is not a valid MySQL connection string.',inputs.connectionString) + ' Error details: '+ _e.message;
242+
return exits.malformed({
243+
error: _e
244+
});
245+
}
246+
247+
// Create a connection pool.
248+
//
249+
// More about using pools with node-mysql:
250+
// • https://github.com/felixge/node-mysql#pooling-connections
251+
var pool = felix.createPool(_mysqlClientConfig);
252+
253+
// Bind an "error" handler in order to handle errors from connections in the pool,
254+
// or from the pool itself. Otherwise, without any further protection, if any MySQL
255+
// connections in the pool die, then the process would crash with an error.
256+
//
257+
// For more background, see:
258+
// • https://github.com/felixge/node-mysql/blob/v2.10.2/Readme.md#error-handling
259+
pool.on('error', function (err){
260+
// When/if something goes wrong in this pool, call the `onUnexpectedFailure` notifier
261+
// (if one was provided)
262+
if (!util.isUndefined(onUnexpectedFailure)) {
263+
onUnexpectedFailure(err || new Error('One or more pooled connections to MySQL database were lost. Did the database server go offline?'));
264+
}
265+
});
266+
267+
// Finally, build and return the manager.
268+
var mgr = {
269+
pool: pool
270+
};
271+
return exits.success({
272+
manager: mgr
273+
});
274+
114275
}
115276

116277

machines/destroy-manager.js

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ module.exports = {
44
friendlyName: 'Destroy manager',
55

66

7-
description: 'Destroy the specified connection manager and destroying all of its active connections.',
8-
9-
10-
extendedDescription: 'This may involve destroying a single connection, destroying a pool and its connections, destroying multiple pools and their connections, or something even more exotic. The implementation is left up to the driver.',
7+
description: 'Destroy the specified connection manager and destroy all of its active connections.',
118

129

1310
inputs: {
@@ -37,9 +34,9 @@ module.exports = {
3734
}
3835
},
3936

40-
badManager: {
41-
friendlyName: 'Bad manager',
42-
description: 'The provided connection manager is no longer active; or possibly never was.',
37+
failed: {
38+
friendlyName: 'Failed',
39+
description: 'Could not destroy the provided connection manager.',
4340
extendedDescription:
4441
'Usually, this means the manager has already been destroyed. But depending on the driver '+
4542
'it could also mean that database cannot be accessed. In production, this can mean that the database '+
@@ -56,8 +53,29 @@ module.exports = {
5653

5754

5855
fn: function (inputs, exits){
59-
// TODO
60-
return exits.error();
56+
var util = require('util');
57+
58+
// Note that if this driver is adapted to support managers which spawn
59+
// ad-hoc connections or manage multiple pools/replicas using PoolCluster,
60+
// then relevant settings would need to be included in the manager instance
61+
// so that the manager could be appropriately destroyed here (in the case of
62+
// ad-hoc connections, leased connections would need to be tracked on the
63+
// manager, and then rounded up and disconnected here.)
64+
//
65+
// For now, since we only support a single pool, we simply destroy it.
66+
//
67+
// For more info, see:
68+
// • https://github.com/felixge/node-mysql/blob/v2.10.2/Readme.md#closing-all-the-connections-in-a-pool
69+
inputs.manager.pool.end(function (err) {
70+
if (err) {
71+
return exits.failed({
72+
error: new Error('Failed to destroy the MySQL connection pool and/or gracefully end all connections in the pool. Details:\n=== === ===\n'+err.stack)
73+
});
74+
}
75+
// All connections in the pool have ended.
76+
console.log('FILLED UP THE POOL WITH GRAVEL.');
77+
return exits.success();
78+
});
6179
}
6280

6381

0 commit comments

Comments
 (0)