Comparing version 2.5.5 to 2.6.0
@@ -7,2 +7,14 @@ # Changes | ||
## v2.6.0 (2015-03-24) | ||
* Add `poolCluster.remove` to remove pools from the cluster #1006 #1007 | ||
* Add optional callback to `poolCluster.end` | ||
* Add `restoreNodeTimeout` option to `PoolCluster` #880 #906 | ||
* Fix LOAD DATA INFILE handling in multiple statements #1036 | ||
* Fix `poolCluster.add` to throw if `PoolCluster` has been closed | ||
* Fix `poolCluster.add` to throw if `id` already defined | ||
* Fix un-catchable error from `PoolCluster` when MySQL server offline #1033 | ||
* Improve speed formatting SQL #1019 | ||
* Support io.js | ||
## v2.5.5 (2015-02-23) | ||
@@ -9,0 +21,0 @@ |
@@ -1,5 +0,7 @@ | ||
var Pool = require('./Pool'); | ||
var PoolConfig = require('./PoolConfig'); | ||
var Util = require('util'); | ||
var EventEmitter = require('events').EventEmitter; | ||
var Pool = require('./Pool'); | ||
var PoolConfig = require('./PoolConfig'); | ||
var PoolNamespace = require('./PoolNamespace'); | ||
var PoolSelector = require('./PoolSelector'); | ||
var Util = require('util'); | ||
var EventEmitter = require('events').EventEmitter; | ||
@@ -16,11 +18,11 @@ module.exports = PoolCluster; | ||
this._canRetry = typeof config.canRetry === 'undefined' ? true : config.canRetry; | ||
this._defaultSelector = config.defaultSelector || 'RR'; | ||
this._removeNodeErrorCount = config.removeNodeErrorCount || 5; | ||
this._defaultSelector = config.defaultSelector || 'RR'; | ||
this._restoreNodeTimeout = config.restoreNodeTimeout || 0; | ||
this._closed = false; | ||
this._findCaches = Object.create(null); | ||
this._lastId = 0; | ||
this._nodes = {}; | ||
this._serviceableNodeIds = []; | ||
this._namespaces = {}; | ||
this._findCaches = {}; | ||
this._namespaces = Object.create(null); | ||
this._nodes = Object.create(null); | ||
} | ||
@@ -30,2 +32,71 @@ | ||
PoolCluster.prototype.add = function add(id, config) { | ||
if (this._closed) { | ||
throw new Error('PoolCluster is closed.'); | ||
} | ||
var nodeId = typeof id === 'object' | ||
? 'CLUSTER::' + (++this._lastId) | ||
: String(id); | ||
if (this._nodes[nodeId] !== undefined) { | ||
throw new Error('Node ID "' + nodeId + '" is already defined in PoolCluster.'); | ||
} | ||
var poolConfig = typeof id !== 'object' | ||
? new PoolConfig(config) | ||
: new PoolConfig(id); | ||
this._nodes[nodeId] = { | ||
id : nodeId, | ||
errorCount : 0, | ||
pool : new Pool({config: poolConfig}), | ||
_offlineUntil : 0 | ||
}; | ||
this._clearFindCaches(); | ||
}; | ||
PoolCluster.prototype.end = function end(callback) { | ||
var cb = callback !== undefined | ||
? callback | ||
: _cb; | ||
if (typeof cb !== 'function') { | ||
throw TypeError('callback argument must be a function') | ||
} | ||
if (this._closed) { | ||
return process.nextTick(cb); | ||
} | ||
this._closed = true; | ||
var calledBack = false; | ||
var nodeIds = Object.keys(this._nodes); | ||
var waitingClose = nodeIds.length; | ||
function onEnd(err) { | ||
if (calledBack) { | ||
return; | ||
} | ||
if (err || --waitingClose === 0) { | ||
calledBack = true; | ||
return cb(err); | ||
} | ||
} | ||
if (waitingClose === 0) { | ||
return process.nextTick(cb); | ||
} | ||
for (var i = 0; i < nodeIds.length; i++) { | ||
var nodeId = nodeIds[i]; | ||
var node = this._nodes[nodeId]; | ||
node.pool.end(onEnd); | ||
} | ||
}; | ||
PoolCluster.prototype.of = function(pattern, selector) { | ||
@@ -36,3 +107,3 @@ pattern = pattern || '*'; | ||
selector = selector.toUpperCase(); | ||
if (typeof Selector[selector] === 'undefined') { | ||
if (typeof PoolSelector[selector] === 'undefined') { | ||
selector = this._defaultSelector; | ||
@@ -50,18 +121,11 @@ } | ||
PoolCluster.prototype.add = function(id, config) { | ||
if (typeof id === 'object') { | ||
config = id; | ||
id = 'CLUSTER::' + (++this._lastId); | ||
} | ||
PoolCluster.prototype.remove = function remove(pattern) { | ||
var foundNodeIds = this._findNodeIds(pattern, true); | ||
if (typeof this._nodes[id] === 'undefined') { | ||
this._nodes[id] = { | ||
id: id, | ||
errorCount: 0, | ||
pool: new Pool({config: new PoolConfig(config)}) | ||
}; | ||
for (var i = 0; i < foundNodeIds.length; i++) { | ||
var node = this._getNode(foundNodeIds[i]); | ||
this._serviceableNodeIds.push(id); | ||
this._clearFindCaches(); | ||
if (node) { | ||
this._removeNode(node); | ||
} | ||
} | ||
@@ -87,65 +151,86 @@ }; | ||
PoolCluster.prototype.end = function() { | ||
if (this._closed) { | ||
return; | ||
PoolCluster.prototype._clearFindCaches = function _clearFindCaches() { | ||
this._findCaches = Object.create(null); | ||
}; | ||
PoolCluster.prototype._decreaseErrorCount = function _decreaseErrorCount(node) { | ||
var errorCount = node.errorCount; | ||
if (errorCount > this._removeNodeErrorCount) { | ||
errorCount = this._removeNodeErrorCount; | ||
} | ||
this._closed = true; | ||
if (errorCount < 1) { | ||
errorCount = 1; | ||
} | ||
for (var id in this._nodes) { | ||
this._nodes[id].pool.end(); | ||
node.errorCount = errorCount - 1; | ||
if (node._offlineUntil) { | ||
node._offlineUntil = 0; | ||
this.emit('online', node.id); | ||
} | ||
}; | ||
PoolCluster.prototype._findNodeIds = function(pattern) { | ||
if (typeof this._findCaches[pattern] !== 'undefined') { | ||
return this._findCaches[pattern]; | ||
} | ||
PoolCluster.prototype._findNodeIds = function _findNodeIds(pattern, includeOffline) { | ||
var currentTime = 0; | ||
var foundNodeIds = this._findCaches[pattern]; | ||
var foundNodeIds; | ||
if (foundNodeIds === undefined) { | ||
var nodeIds = Object.keys(this._nodes); | ||
var wildcard = pattern.substr(-1) === '*'; | ||
var keyword = wildcard | ||
? pattern.substr(0, pattern.length - 1) | ||
: pattern; | ||
if (pattern === '*') { // all | ||
foundNodeIds = this._serviceableNodeIds; | ||
} else if (this._serviceableNodeIds.indexOf(pattern) != -1) { // one | ||
foundNodeIds = [pattern]; | ||
} else if (pattern[pattern.length - 1] === '*') { | ||
// wild matching | ||
var keyword = pattern.substring(pattern.length - 1, 0); | ||
if (wildcard) { | ||
foundNodeIds = keyword.length !== 0 | ||
? nodeIds.filter(function (id) { return id.substr(0, keyword.length) === keyword; }) | ||
: nodeIds; | ||
} else { | ||
var index = nodeIds.indexOf(keyword); | ||
foundNodeIds = nodeIds.slice(index, index + 1); | ||
} | ||
foundNodeIds = this._serviceableNodeIds.filter(function (id) { | ||
return id.indexOf(keyword) === 0; | ||
}); | ||
} else { | ||
foundNodeIds = []; | ||
this._findCaches[pattern] = foundNodeIds; | ||
} | ||
this._findCaches[pattern] = foundNodeIds; | ||
if (includeOffline) { | ||
return foundNodeIds; | ||
} | ||
return foundNodeIds; | ||
return foundNodeIds.filter(function (nodeId) { | ||
var node = this._getNode(nodeId); | ||
if (!node._offlineUntil) { | ||
return true; | ||
} | ||
if (!currentTime) { | ||
currentTime = getMonotonicMilliseconds(); | ||
} | ||
return node._offlineUntil <= currentTime; | ||
}, this); | ||
}; | ||
PoolCluster.prototype._getNode = function(id) { | ||
PoolCluster.prototype._getNode = function _getNode(id) { | ||
return this._nodes[id] || null; | ||
}; | ||
PoolCluster.prototype._increaseErrorCount = function(node) { | ||
if (++node.errorCount >= this._removeNodeErrorCount) { | ||
var index = this._serviceableNodeIds.indexOf(node.id); | ||
if (index !== -1) { | ||
this._serviceableNodeIds.splice(index, 1); | ||
delete this._nodes[node.id]; | ||
PoolCluster.prototype._increaseErrorCount = function _increaseErrorCount(node) { | ||
var errorCount = ++node.errorCount; | ||
this._clearFindCaches(); | ||
if (this._removeNodeErrorCount > errorCount) { | ||
return; | ||
} | ||
node.pool.end(); | ||
this.emit('remove', node.id); | ||
} | ||
if (this._restoreNodeTimeout > 0) { | ||
node._offlineUntil = getMonotonicMilliseconds() + this._restoreNodeTimeout; | ||
this.emit('offline', node.id); | ||
return; | ||
} | ||
}; | ||
PoolCluster.prototype._decreaseErrorCount = function(node) { | ||
if (node.errorCount > 0) { | ||
--node.errorCount; | ||
} | ||
this._removeNode(node); | ||
this.emit('remove', node.id); | ||
}; | ||
@@ -171,92 +256,29 @@ | ||
PoolCluster.prototype._clearFindCaches = function() { | ||
this._findCaches = {}; | ||
}; | ||
PoolCluster.prototype._removeNode = function _removeNode(node) { | ||
delete this._nodes[node.id]; | ||
/** | ||
* PoolNamespace | ||
*/ | ||
function PoolNamespace(cluster, pattern, selector) { | ||
this._cluster = cluster; | ||
this._pattern = pattern; | ||
this._selector = new Selector[selector](); | ||
} | ||
this._clearFindCaches(); | ||
PoolNamespace.prototype.getConnection = function(cb) { | ||
var clusterNode = this._getClusterNode(); | ||
var cluster = this._cluster; | ||
var namespace = this; | ||
if (clusterNode === null) { | ||
var err = new Error('Pool does not exist.') | ||
err.code = 'POOL_NOEXIST'; | ||
return cb(err); | ||
} | ||
cluster._getConnection(clusterNode, function(err, connection) { | ||
var retry = err && cluster._canRetry | ||
&& cluster._findNodeIds(namespace._pattern).length !== 0; | ||
if (retry) { | ||
return namespace.getConnection(cb); | ||
} | ||
if (err) { | ||
return cb(err); | ||
} | ||
cb(null, connection); | ||
}); | ||
node.pool.end(_noop); | ||
}; | ||
PoolNamespace.prototype._getClusterNode = function _getClusterNode() { | ||
var foundNodeIds = this._cluster._findNodeIds(this._pattern); | ||
var nodeId; | ||
function getMonotonicMilliseconds() { | ||
var ms; | ||
switch (foundNodeIds.length) { | ||
case 0: | ||
nodeId = null; | ||
break; | ||
case 1: | ||
nodeId = foundNodeIds[0]; | ||
break; | ||
default: | ||
nodeId = this._selector(foundNodeIds); | ||
break; | ||
if (typeof process.hrtime === 'function') { | ||
ms = process.hrtime(); | ||
ms = ms[0] * 1e3 + ms[1] * 1e-6; | ||
} else { | ||
ms = process.uptime() * 1000; | ||
} | ||
return nodeId !== null | ||
? this._cluster._getNode(nodeId) | ||
: null; | ||
}; | ||
return Math.floor(ms); | ||
} | ||
/** | ||
* Selector | ||
*/ | ||
var Selector = {}; | ||
function _cb(err) { | ||
if (err) { | ||
throw err; | ||
} | ||
} | ||
Selector.RR = function () { | ||
var index = 0; | ||
return function(clusterIds) { | ||
if (index >= clusterIds.length) { | ||
index = 0; | ||
} | ||
var clusterId = clusterIds[index++]; | ||
return clusterId; | ||
}; | ||
}; | ||
Selector.RANDOM = function () { | ||
return function(clusterIds) { | ||
return clusterIds[Math.floor(Math.random() * clusterIds.length)]; | ||
}; | ||
}; | ||
Selector.ORDER = function () { | ||
return function(clusterIds) { | ||
return clusterIds[0]; | ||
}; | ||
}; | ||
function _noop() {} |
@@ -77,2 +77,3 @@ var Sequence = require('./Sequence'); | ||
this._index++; | ||
this._resultSet = null; | ||
this._handleFinalResultPacket(packet); | ||
@@ -79,0 +80,0 @@ } |
@@ -70,11 +70,13 @@ var SqlString = exports; | ||
var index = 0; | ||
return sql.replace(/\?\??/g, function(match) { | ||
if (!values.length) { | ||
if (index === values.length) { | ||
return match; | ||
} | ||
if (match == "??") { | ||
return SqlString.escapeId(values.shift()); | ||
} | ||
return SqlString.escape(values.shift(), stringifyObjects, timeZone); | ||
var value = values[index++]; | ||
return match === '??' | ||
? SqlString.escapeId(value) | ||
: SqlString.escape(value, stringifyObjects, timeZone); | ||
}); | ||
@@ -81,0 +83,0 @@ }; |
{ | ||
"name": "mysql", | ||
"description": "A node.js driver for mysql. It is written in JavaScript, does not require compiling, and is 100% MIT licensed.", | ||
"version": "2.5.5", | ||
"version": "2.6.0", | ||
"license": "MIT", | ||
@@ -20,3 +20,3 @@ "author": "Felix Geisendörfer <felix@debuggable.com> (http://debuggable.com/)", | ||
"devDependencies": { | ||
"istanbul": "0.3.5", | ||
"istanbul": "0.3.8", | ||
"rimraf": "2.2.8", | ||
@@ -27,2 +27,9 @@ "mkdirp": "0.5.0", | ||
}, | ||
"files": [ | ||
"lib/", | ||
"Changes.md", | ||
"License", | ||
"Readme.md", | ||
"index.js" | ||
], | ||
"engines": { | ||
@@ -29,0 +36,0 @@ "node": ">= 0.6" |
@@ -29,2 +29,3 @@ # mysql | ||
- [Server disconnects](#server-disconnects) | ||
- [Performing queries](#performing-queries) | ||
- [Escaping query values](#escaping-query-values) | ||
@@ -223,3 +224,3 @@ - [Escaping query identifiers](#escaping-query-identifiers) | ||
* `multipleStatements`: Allow multiple mysql statements per query. Be careful | ||
with this, it exposes you to SQL injection attacks. (Default: `false`) | ||
with this, it could increase the scope of SQL injection attacks. (Default: `false`) | ||
* `flags`: List of connection flags to use other than the default ones. It is | ||
@@ -443,2 +444,3 @@ also possible to blacklist default ones. For more information, check | ||
// add configurations | ||
poolCluster.add(config); // anonymous group | ||
@@ -449,2 +451,6 @@ poolCluster.add('MASTER', masterConfig); | ||
// remove configurations | ||
poolCluster.remove('SLAVE2'); // By nodeId | ||
poolCluster.remove('SLAVE*'); // By target group : SLAVE1-2 | ||
// Target Group : ALL(anonymous, MASTER, SLAVE1-2), Selector : round-robin(default) | ||
@@ -471,4 +477,6 @@ poolCluster.getConnection(function (err, connection) {}); | ||
// destroy | ||
poolCluster.end(); | ||
// close all connections | ||
poolCluster.end(function (err) { | ||
// all connections in the pool cluster have ended | ||
}); | ||
``` | ||
@@ -480,2 +488,5 @@ | ||
When `errorCount` is greater than `removeNodeErrorCount`, remove a node in the `PoolCluster`. (Default: `5`) | ||
* `restoreNodeTimeout`: If connection fails, specifies the number of milliseconds | ||
before another connection attempt will be made. If set to `0`, then node will bd | ||
removed instead and never re-used. (Default: `0`) | ||
* `defaultSelector`: The default selector. (Default: `RR`) | ||
@@ -533,2 +544,47 @@ * `RR`: Select one alternately. (Round-Robin) | ||
## Performing queries | ||
In the MySQL library library, the most basic way to perform a query is to call | ||
the `.query()` method on an object (like on a `Connection`, `Pool`, `PoolNamespace` | ||
or other similar objects). | ||
The simplest form on query comes as `.query(sqlString, callback)`, where a string | ||
of a MySQL query is the first argument and the second is a callback: | ||
```js | ||
connection.query('SELECT * FROM `books` WHERE `author` = "David"', function (error, results, fields) { | ||
// error will be an Error if one occurred during the query | ||
// results will contain the results of the query | ||
// fields will contain information about the returned results fields (if any) | ||
}); | ||
``` | ||
The second form `.query(sqlString, parameters, callback)` comes when using | ||
placeholders (see [escaping query values](#escaping-query-values)): | ||
```js | ||
connection.query('SELECT * FROM `books` WHERE `author` = ?', ['David'], function (error, results, fields) { | ||
// error will be an Error if one occurred during the query | ||
// results will contain the results of the query | ||
// fields will contain information about the returned results fields (if any) | ||
}); | ||
``` | ||
The third form `.query(options, callback)` comes when using various advanced | ||
options on the query, like [escaping query values](#escaping-query-values), | ||
[joins with overlapping column names](#joins-with-overlapping-column-names), | ||
[timeouts](#timeout), and [type casting](#type-casting). | ||
```js | ||
connection.query({ | ||
sql: 'SELECT * FROM `books` WHERE `author` = ?', | ||
timeout: 40000, // 40s | ||
values: ['David'] | ||
}, function (error, results, fields) { | ||
// error will be an Error if one occurred during the query | ||
// results will contain the results of the query | ||
// fields will contain information about the returned results fields (if any) | ||
}); | ||
``` | ||
## Escaping query values | ||
@@ -566,3 +622,3 @@ | ||
* Numbers are left untouched | ||
* Booleans are converted to `true` / `false` strings | ||
* Booleans are converted to `true` / `false` | ||
* Date objects are converted to `'YYYY-mm-dd HH:ii:ss'` strings | ||
@@ -778,2 +834,7 @@ * Buffers are converted to hex strings, e.g. `X'0fa5'` | ||
confirming the success of a INSERT/UPDATE query. | ||
* It is very important not to leave the result paused too long, or you may | ||
encounter `Error: Connection lost: The server closed the connection.` | ||
The time limit for this is determined by the | ||
[net_write_timeout setting](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_net_write_timeout) | ||
on your MySQL server. | ||
@@ -780,0 +841,0 @@ Additionally you may be interested to know that it is currently not possible to |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
337234
5980
1329