From 20a542ef556dc0048d6e3d6494db33d3886aaf33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Wed, 29 Sep 2010 18:47:09 +0200 Subject: [PATCH 01/28] Update todo list --- Readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Readme.md b/Readme.md index 2fb3acefc..9140fa21c 100644 --- a/Readme.md +++ b/Readme.md @@ -194,6 +194,7 @@ parameter is provided which contains the information from the mysql OK packet. At this point the module is ready to be tried out, but a lot of things are yet to be done: * Handle timeouts / reconnect +* Pause / resume * Remaining mysql commands * Prepared Statements * Packet's > 16 MB From 1708b1ed350cac9ef7fbe01b4e0ebd531848cad6 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Thu, 30 Sep 2010 14:40:37 +0200 Subject: [PATCH 02/28] Delay client.end until queries are finished This patch also ands an optional callback argument to client.end() so the user can be notified when the connection is closed; this makes testing a little easier. --- Readme.md | 2 +- lib/mysql/client.js | 13 ++++++++--- test/system/test-client-end.js | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 test/system/test-client-end.js diff --git a/Readme.md b/Readme.md index 9140fa21c..afb8c573c 100644 --- a/Readme.md +++ b/Readme.md @@ -157,7 +157,7 @@ placeholder mechanism described above. Escapes a single `val` for use inside of a sql string. -### client.end() +### client.end([cb]) Closes the connection to the server. diff --git a/lib/mysql/client.js b/lib/mysql/client.js index aa98d326d..b0dd7659b 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -165,10 +165,17 @@ Client.prototype.escape = function(val) { return "'"+val+"'"; }; -Client.prototype.end = function() { - this._connection.end(); -}; +Client.prototype.end = function(cb) { + var self = this; + this._enqueue(function() { + self._connection.end(); + if (cb) { + cb(); + } + self._dequeue(); + }); +}; Client.prototype._enqueue = function(fn, delegate) { this._queue.push({fn: fn, delegate: delegate}); diff --git a/test/system/test-client-end.js b/test/system/test-client-end.js new file mode 100644 index 000000000..698b1a612 --- /dev/null +++ b/test/system/test-client-end.js @@ -0,0 +1,40 @@ +require('../common'); +var Client = require('mysql').Client; + +(function testImmediateEnd() { + var gently = new Gently(), + client = new Client(TEST_CONFIG); + + client.connect(gently.expect(function connectCb(err, result) { + assert.ifError(err); + })); + + client.end(gently.expect(function endCb() { + gently.verify('testImmediateEnd'); + })); + + // If client.end is not correctly run *after* connect has finished, + // the connection is never closed, and this test will run forever. +})(); + +(function testEndAfterQuery() { + var gently = new Gently(), + client = new Client(TEST_CONFIG); + + client.connect(gently.expect(function connectCb(err, result) { + assert.ifError(err); + })); + + client.query('SHOW STATUS', [], gently.expect(function queryCb(error, rows, fields) { + assert.ifError(error); + assert.equal(rows.length >= 50, true); + assert.equal(Object.keys(fields).length, 2); + })); + + client.end(gently.expect(function endCb() { + gently.verify('testEndAfterQuery'); + })); + + // If client.end is not run *after* query is done, either the + // connection is never closed or the query throws a 'Stream not writable' error. +})(); \ No newline at end of file From 626265e444c06376badce44e641fad4b23718adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Sat, 9 Oct 2010 10:18:45 +0200 Subject: [PATCH 03/28] Send proper COM_QUIT packet for client.end() --- lib/mysql/client.js | 8 ++++---- test/simple/test-client.js | 27 +++++++++++++++++++++++++-- test/system/test-client-end.js | 2 +- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/mysql/client.js b/lib/mysql/client.js index b0dd7659b..79f9cfa74 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -167,13 +167,13 @@ Client.prototype.escape = function(val) { Client.prototype.end = function(cb) { var self = this; - this._enqueue(function() { - self._connection.end(); + var packet = new OutgoingPacket(1); + packet.writeNumber(1, Client.COM_QUIT); + self.write(packet); if (cb) { - cb(); + self._connection.on('end', cb); } - self._dequeue(); }); }; diff --git a/test/simple/test-client.js b/test/simple/test-client.js index 6e03a3fe6..7aca1a451 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -318,11 +318,34 @@ test(function escape() { }); test(function end() { + var CB = function() {}, + PACKET; + client._connection = {}; - gently.expect(client._connection, 'end'); + gently.expect(client, '_enqueue', function (fn, cb) { + gently.expect(OutgoingPacketStub, 'new', function(size, number) { + PACKET = this; + assert.equal(size, 1); + + gently.expect(this, 'writeNumber', function (length, val) { + assert.equal(length, 1); + assert.equal(val, Client.COM_QUIT); + }); + + gently.expect(client, 'write', function (packet) { + assert.strictEqual(packet, PACKET); + }); + + gently.expect(client._connection, 'on', function (event, fn) { + assert.equal(event, 'end'); + assert.strictEqual(fn, CB); + }); + }); + fn(); + }); - client.end(); + client.end(CB); }); test(function _enqueue() { diff --git a/test/system/test-client-end.js b/test/system/test-client-end.js index 698b1a612..4eb446312 100644 --- a/test/system/test-client-end.js +++ b/test/system/test-client-end.js @@ -37,4 +37,4 @@ var Client = require('mysql').Client; // If client.end is not run *after* query is done, either the // connection is never closed or the query throws a 'Stream not writable' error. -})(); \ No newline at end of file +})(); From 974c7d6727100f10430a48b023ed3219fcfc9ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Sat, 9 Oct 2010 11:06:31 +0200 Subject: [PATCH 04/28] Implemented client.ping() --- lib/mysql/client.js | 9 +++++++++ test/simple/test-client.js | 26 ++++++++++++++++++++++++++ test/system/test-client-ping.js | 12 ++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 test/system/test-client-ping.js diff --git a/lib/mysql/client.js b/lib/mysql/client.js index 79f9cfa74..7a424c818 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -165,6 +165,15 @@ Client.prototype.escape = function(val) { return "'"+val+"'"; }; +Client.prototype.ping = function(cb) { + var self = this; + this._enqueue(function() { + var packet = new OutgoingPacket(1); + packet.writeNumber(1, Client.COM_PING); + self.write(packet); + }, cb); +}; + Client.prototype.end = function(cb) { var self = this; this._enqueue(function() { diff --git a/test/simple/test-client.js b/test/simple/test-client.js index 7aca1a451..bfd0572ce 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -317,6 +317,32 @@ test(function escape() { assert.equal(client.escape('Sup"er'), "'Sup\\\"er'"); }); +test(function ping() { + var CB = function() {}, + PACKET; + + gently.expect(client, '_enqueue', function (fn, cb) { + gently.expect(OutgoingPacketStub, 'new', function(size, number) { + PACKET = this; + assert.equal(size, 1); + + gently.expect(this, 'writeNumber', function (length, val) { + assert.equal(length, 1); + assert.equal(val, Client.COM_PING); + }); + + gently.expect(client, 'write', function (packet) { + assert.strictEqual(packet, PACKET); + }); + }); + fn(); + + assert.strictEqual(cb, CB); + }); + + client.ping(CB); +}); + test(function end() { var CB = function() {}, PACKET; diff --git a/test/system/test-client-ping.js b/test/system/test-client-ping.js new file mode 100644 index 000000000..34665128a --- /dev/null +++ b/test/system/test-client-ping.js @@ -0,0 +1,12 @@ +require('../common'); +var Client = require('mysql').Client, + client = Client(TEST_CONFIG), + gently = new Gently(); + +client.connect(); + +client.ping(gently.expect(function pingCb(err) { + if (err) throw err; +})); + +client.end(); From f1b1d504ac4cf063f8df68d934d9f2d7cdb8bbf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Sat, 9 Oct 2010 11:24:02 +0200 Subject: [PATCH 05/28] Added select performance test to benchmark Also boosting performance by 25% by explicitely type-casting the buffer into a string. --- benchmark/insert.js | 13 ++++++++++++- lib/mysql/query.js | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/benchmark/insert.js b/benchmark/insert.js index 6dc573461..05ca8d63a 100644 --- a/benchmark/insert.js +++ b/benchmark/insert.js @@ -33,7 +33,18 @@ client.query( insertsPerSecond = inserts / duration; console.log('%d inserts / second', insertsPerSecond.toFixed(2)); - client.end(); + + client.typeCast = false; + var selectStart = +new Date; + client + .query('SELECT * FROM '+TEST_TABLE) + .on('row', function(row) {}) + .on('end', function() { + var duration = (+new Date - selectStart) / 1000, + rowsPerSecond = inserts / duration; + console.log('%d rows / second', rowsPerSecond.toFixed(2)); + client.end(); + }); } }); } diff --git a/lib/mysql/query.js b/lib/mysql/query.js index abeae2548..79d515b0f 100644 --- a/lib/mysql/query.js +++ b/lib/mysql/query.js @@ -60,7 +60,7 @@ Query.prototype._handlePacket = function(packet) { packet.on('data', function(buffer, remaining) { if (buffer) { - row[field.name] += buffer; + row[field.name] += buffer.toString('utf-8'); } else { row[field.name] = null; } From ad299e8d2c0e443f7222199f5ed6c57476bb5b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Sat, 9 Oct 2010 12:05:45 +0200 Subject: [PATCH 06/28] Add PHP mysql benchmark Hopefuly some stuff is still screwed up, this driver is much slower than PHP at his point. --- .../{insert.js => mysql/insert-select.js} | 4 +- benchmark/php/insert-select.php | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) rename benchmark/{insert.js => mysql/insert-select.js} (94%) create mode 100644 benchmark/php/insert-select.php diff --git a/benchmark/insert.js b/benchmark/mysql/insert-select.js similarity index 94% rename from benchmark/insert.js rename to benchmark/mysql/insert-select.js index 05ca8d63a..f5b4ad242 100644 --- a/benchmark/insert.js +++ b/benchmark/mysql/insert-select.js @@ -1,4 +1,4 @@ -require('../test/common'); +require('../../test/common'); var Client = require('mysql/client'), client = Client(TEST_CONFIG); @@ -21,8 +21,6 @@ client.query( if (err) throw err; var start = +new Date, inserts = 0, total = 10000; - console.log('performing %d inserts ...\n', total); - function insertOne() { client.query('INSERT INTO '+TEST_TABLE+' SET title = ?', ['super'], function() { inserts++; diff --git a/benchmark/php/insert-select.php b/benchmark/php/insert-select.php new file mode 100644 index 000000000..eedda3fe9 --- /dev/null +++ b/benchmark/php/insert-select.php @@ -0,0 +1,38 @@ + 'localhost', + 'port' => 3306, + 'user' => 'root', + 'password' => 'root', + 'db' => 'node_mysql_test', + 'table' => 'post', +); +extract($config); + +$connection = mysql_connect($host, $user, $password); +mysql_query('USE '.$db, $connection); +mysql_query('CREATE TEMPORARY TABLE '.$table.' ('. +'id INT(11) AUTO_INCREMENT, '. +'title VARCHAR(255), '. +'text TEXT, '. +'created DATETIME, '. +'PRIMARY KEY (id));', $connection); + +$start = microtime(true); +for ($i = 0; $i < $INSERTS; $i++) { + mysql_query('INSERT INTO '.$table.' SET title = "super";', $connection); +} +$duration = (microtime(true) - $start); +$insertsPerSecond = $INSERTS / $duration; +echo sprintf("%d inserts / second\n", $insertsPerSecond); + +$start = microtime(true); +$q = mysql_query('SELECT * FROM '.$table); +while ($a = mysql_fetch_row($q)) { +} +$duration = (microtime(true) - $start); +$rowsPerSecond = $INSERTS / $duration; +echo sprintf("%d rows / second\n", $rowsPerSecond); +?> From f692d9002b80eb3bf0b8a95e92deeedf028fb560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Sat, 9 Oct 2010 18:03:45 +0200 Subject: [PATCH 07/28] Make benchmarks more comparable --- benchmark/mysql/insert-select.js | 2 +- benchmark/php/insert-select.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmark/mysql/insert-select.js b/benchmark/mysql/insert-select.js index f5b4ad242..9e5d2bd25 100644 --- a/benchmark/mysql/insert-select.js +++ b/benchmark/mysql/insert-select.js @@ -22,7 +22,7 @@ client.query( var start = +new Date, inserts = 0, total = 10000; function insertOne() { - client.query('INSERT INTO '+TEST_TABLE+' SET title = ?', ['super'], function() { + client.query('INSERT INTO '+TEST_TABLE+' SET title = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"', function() { inserts++; if (inserts < total) { insertOne(); diff --git a/benchmark/php/insert-select.php b/benchmark/php/insert-select.php index eedda3fe9..d63018662 100644 --- a/benchmark/php/insert-select.php +++ b/benchmark/php/insert-select.php @@ -22,7 +22,7 @@ $start = microtime(true); for ($i = 0; $i < $INSERTS; $i++) { - mysql_query('INSERT INTO '.$table.' SET title = "super";', $connection); + mysql_query('INSERT INTO '.$table.' SET title = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";', $connection); } $duration = (microtime(true) - $start); $insertsPerSecond = $INSERTS / $duration; @@ -30,7 +30,7 @@ $start = microtime(true); $q = mysql_query('SELECT * FROM '.$table); -while ($a = mysql_fetch_row($q)) { +while ($a = mysql_fetch_assoc($q)) { } $duration = (microtime(true) - $start); $rowsPerSecond = $INSERTS / $duration; From e0bd11109ac7b24ae6f926bd2859017bbe97beb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Mon, 11 Oct 2010 10:12:06 +0200 Subject: [PATCH 08/28] Replace Math.pow with pre-computed powers Get's our select benchmark from 19k to 23k rows / sec. --- lib/mysql/parser.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/mysql/parser.js b/lib/mysql/parser.js index 84f867ae3..f7758d2a4 100644 --- a/lib/mysql/parser.js +++ b/lib/mysql/parser.js @@ -4,7 +4,8 @@ if (global.GENTLY) require = GENTLY.hijack(require); var sys = require('sys'), Buffer = require('buffer').Buffer, - EventEmitter = require('events').EventEmitter; + EventEmitter = require('events').EventEmitter, + POWS = [1, 256, 65536, 16777216]; function Parser() { EventEmitter.call(this); @@ -55,7 +56,9 @@ Parser.prototype.write = function(buffer) { return 0; } - val += Math.pow(256, packet.index - 1) * c; + if (c) { + val += POWS[packet.index - 1] * c; + } if (packet.index === self._lengthCodedLength) { self._lengthCodedLength = null; @@ -92,7 +95,7 @@ Parser.prototype.write = function(buffer) { } // 3 bytes - Little endian - packet.length += Math.pow(256, packet.index) * c; + packet.length += POWS[packet.index] * c; if (packet.index == 2) { advance(); @@ -141,7 +144,7 @@ Parser.prototype.write = function(buffer) { } // 4 bytes = probably Little endian, protocol docs are not clear - packet.threadId += Math.pow(256, packet.index) * c; + packet.threadId += POWS[packet.index] * c; if (packet.index == 3) { advance(); @@ -168,7 +171,7 @@ Parser.prototype.write = function(buffer) { packet.serverCapabilities = 0; } // 2 bytes = probably Little endian, protocol docs are not clear - packet.serverCapabilities += Math.pow(256, packet.index) * c; + packet.serverCapabilities += POWS[packet.index] * c; if (packet.index == 1) { advance(); @@ -184,7 +187,7 @@ Parser.prototype.write = function(buffer) { } // 2 bytes = probably Little endian, protocol docs are not clear - packet.serverStatus += Math.pow(256, packet.index) * c; + packet.serverStatus += POWS[packet.index] * c; if (packet.index == 1) { advance(); @@ -239,7 +242,7 @@ Parser.prototype.write = function(buffer) { } // 2 bytes = Little endian - packet.errorNumber += Math.pow(256, packet.index) * c; + packet.errorNumber += POWS[packet.index] * c; if (packet.index == 1) { advance(); @@ -280,7 +283,7 @@ Parser.prototype.write = function(buffer) { } // 2 bytes - Little endian - packet.serverStatus += Math.pow(256, packet.index) * c; + packet.serverStatus += POWS[packet.index] * c; if (packet.index == 1) { advance(); From f2e895c19efc65ccbeb3588e39450a961728cd41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Mon, 11 Oct 2010 10:22:09 +0200 Subject: [PATCH 09/28] Implemented client.destroy() Also added further documentation for client.end() to distinguish the two. --- Readme.md | 9 ++++++++- lib/mysql/client.js | 4 ++++ test/simple/test-client.js | 7 +++++++ test/system/test-client-destroy.js | 11 +++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 test/system/test-client-destroy.js diff --git a/Readme.md b/Readme.md index afb8c573c..3c805fb63 100644 --- a/Readme.md +++ b/Readme.md @@ -157,9 +157,16 @@ placeholder mechanism described above. Escapes a single `val` for use inside of a sql string. +### client.destroy([cb]) + +Forces the client connection to be destroyed right away. This is not a +nice way to terminate the connection, use with caution. + ### client.end([cb]) -Closes the connection to the server. +Schedule a COM_QUIT packet for closing the connection. All currently queued +queries will still execute before the graceful termination of the connection +is attempted. ### client event: 'error' (err) diff --git a/lib/mysql/client.js b/lib/mysql/client.js index 7a424c818..98cabc965 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -174,6 +174,10 @@ Client.prototype.ping = function(cb) { }, cb); }; +Client.prototype.destroy = function() { + this._connection.destroy(); +} + Client.prototype.end = function(cb) { var self = this; this._enqueue(function() { diff --git a/test/simple/test-client.js b/test/simple/test-client.js index bfd0572ce..4fcfa9b71 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -343,6 +343,13 @@ test(function ping() { client.ping(CB); }); +test(function destroy() { + var CONNECTION = client._connection = {}; + + gently.expect(CONNECTION, 'destroy'); + client.destroy(); +}); + test(function end() { var CB = function() {}, PACKET; diff --git a/test/system/test-client-destroy.js b/test/system/test-client-destroy.js new file mode 100644 index 000000000..b76dd7e37 --- /dev/null +++ b/test/system/test-client-destroy.js @@ -0,0 +1,11 @@ +require('../common'); +var Client = require('mysql').Client; + +var gently = new Gently(), + client = new Client(TEST_CONFIG); + +client.connect(function() { + throw new Error('Destroy did not prevent client from connecting.'); +}); + +client.destroy(); From 62becf9f7209244b913f0c618d5ca3150fa3e048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Mon, 11 Oct 2010 10:40:46 +0200 Subject: [PATCH 10/28] Makefile targets for benchmarking Also moved node-mysql benchmark directory --- Makefile | 8 +++++++- benchmark/{mysql => node-mysql}/insert-select.js | 0 2 files changed, 7 insertions(+), 1 deletion(-) rename benchmark/{mysql => node-mysql}/insert-select.js (100%) diff --git a/Makefile b/Makefile index 89de88a3e..14c43a5ad 100644 --- a/Makefile +++ b/Makefile @@ -2,5 +2,11 @@ test: @find test/{simple,system}/test-*.js | xargs -n 1 -t node test-all: test @find test/system/slow/test-*.js | xargs -n 1 -t node +benchmark-node-mysql: + @find benchmark/node-mysql/*.js | xargs -n 1 -t node +benchmark-php: + @find benchmark/php/*.php | xargs -n 1 -t php +benchmark-all: benchmark-node-mysql benchmark-php +benchmark: benchmark-node-mysql -.PHONY: test +.PHONY: test benchmark diff --git a/benchmark/mysql/insert-select.js b/benchmark/node-mysql/insert-select.js similarity index 100% rename from benchmark/mysql/insert-select.js rename to benchmark/node-mysql/insert-select.js From 4f0f096004b38c155602f3c378346edd16166484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Tue, 12 Oct 2010 20:43:35 +0200 Subject: [PATCH 11/28] Explictely name enqueued functions This makes inspecting client._queue much easier. --- lib/mysql/client.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mysql/client.js b/lib/mysql/client.js index 98cabc965..9e943f952 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -105,7 +105,7 @@ Client.prototype.query = function(sql, params, cb) { }); } - this._enqueue(function() { + this._enqueue(function query() { var packet = new OutgoingPacket(1 + Buffer.byteLength(sql, 'utf-8')); packet.writeNumber(1, Client.COM_QUERY); @@ -167,7 +167,7 @@ Client.prototype.escape = function(val) { Client.prototype.ping = function(cb) { var self = this; - this._enqueue(function() { + this._enqueue(function ping() { var packet = new OutgoingPacket(1); packet.writeNumber(1, Client.COM_PING); self.write(packet); @@ -180,7 +180,7 @@ Client.prototype.destroy = function() { Client.prototype.end = function(cb) { var self = this; - this._enqueue(function() { + this._enqueue(function end() { var packet = new OutgoingPacket(1); packet.writeNumber(1, Client.COM_QUIT); self.write(packet); From 2db5bbdb449686733290390cb3c196d9af3d43be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Tue, 12 Oct 2010 20:47:18 +0200 Subject: [PATCH 12/28] Fix reconnect Now one can reconnect a client that has been disconnected using `.end()`. --- lib/mysql/client.js | 2 ++ test/simple/test-client.js | 2 ++ test/system/test-client-reconnect.js | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 test/system/test-client-reconnect.js diff --git a/lib/mysql/client.js b/lib/mysql/client.js index 9e943f952..c46474920 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -187,6 +187,8 @@ Client.prototype.end = function(cb) { if (cb) { self._connection.on('end', cb); } + + self._dequeue(); }); }; diff --git a/test/simple/test-client.js b/test/simple/test-client.js index 4fcfa9b71..acb48291f 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -374,6 +374,8 @@ test(function end() { assert.equal(event, 'end'); assert.strictEqual(fn, CB); }); + + gently.expect(client, '_dequeue'); }); fn(); }); diff --git a/test/system/test-client-reconnect.js b/test/system/test-client-reconnect.js new file mode 100644 index 000000000..89ba27559 --- /dev/null +++ b/test/system/test-client-reconnect.js @@ -0,0 +1,18 @@ +require('../common'); +var Client = require('mysql').Client, + gently = new Gently(), + client = new Client(TEST_CONFIG); + +client.connect(gently.expect(function connectCb(err, result) { + assert.ifError(err); +})); + +client.end(gently.expect(function endCb() { +})); + +client.connect(gently.expect(function connectCb2(err, result) { + assert.ifError(err); + + client.end(); +})); + From 4a90b68358560fa6db685494a9e610cec00e3b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Wed, 13 Oct 2010 22:42:02 +0200 Subject: [PATCH 13/28] Implement missing warningCount for OK packets Forgot to implement this somehow : ) --- lib/mysql/parser.js | 16 +++++++++++++++- test/simple/test-parser.js | 7 ++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/mysql/parser.js b/lib/mysql/parser.js index f7758d2a4..56fae08ce 100644 --- a/lib/mysql/parser.js +++ b/lib/mysql/parser.js @@ -289,9 +289,22 @@ Parser.prototype.write = function(buffer) { advance(); } break; + case Parser.WARNING_COUNT: + if (packet.index == 0) { + packet.warningCount = 0; + } + + // 2 bytes - Little endian + packet.warningCount += POWS[packet.index] * c; + + if (packet.index == 1) { + packet.message = ''; + advance(); + } + break; case Parser.MESSAGE: if (packet.received <= packet.length) { - packet.message = (packet.message || '') + String.fromCharCode(c); + packet.message += String.fromCharCode(c); } break; @@ -578,6 +591,7 @@ Parser.ERROR_MESSAGE = s++; Parser.AFFECTED_ROWS = s++; Parser.INSERT_ID = s++; Parser.SERVER_STATUS = s++; +Parser.WARNING_COUNT = s++; Parser.MESSAGE = s++; Parser.EXTRA = s++; Parser.FIELD_CATALOG_LENGTH = s++; diff --git a/test/simple/test-parser.js b/test/simple/test-parser.js index f7bea4c28..e0f0aea98 100644 --- a/test/simple/test-parser.js +++ b/test/simple/test-parser.js @@ -159,7 +159,7 @@ test(function write() { })(); (function testOkPacket() { - parser.write(new Buffer([13, 0, 0, 1])); + parser.write(new Buffer([15, 0, 0, 1])); var packet = parser.packet; parser.write(new Buffer([0x00])); @@ -176,6 +176,11 @@ test(function write() { parser.write(new Buffer([42, 113])); assert.equal(packet.serverStatus, Math.pow(256, 0) * 42 + Math.pow(256, 1) * 113); + parser.write(new Buffer([32, 153])); + assert.equal(packet.warningCount, Math.pow(256, 0) * 32 + Math.pow(256, 1) * 153); + + assert.strictEqual(packet.message, ''); + gently.expect(parser, 'emit', function(event, val) { assert.equal(event, 'packet'); assert.equal(packet.message, 'abcdef'); From dfc5075053d2ece94dc6e44c16d086f7791b9f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Wed, 13 Oct 2010 22:43:29 +0200 Subject: [PATCH 14/28] Add index.js module --- index.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 index.js diff --git a/index.js b/index.js new file mode 100644 index 000000000..782e199a3 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/mysql'); From ed9419e6de1dcd7c95dc4c6f60e9979152f17caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Wed, 13 Oct 2010 22:57:20 +0200 Subject: [PATCH 15/28] Backwards compatibilty --- lib/mysql/client.js | 2 +- lib/mysql/parser.js | 2 +- lib/mysql/query.js | 2 +- lib/mysql/sys.js | 6 ++++++ test/common.js | 5 ++--- 5 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 lib/mysql/sys.js diff --git a/lib/mysql/client.js b/lib/mysql/client.js index c46474920..c3f3d8a15 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -1,6 +1,6 @@ if (global.GENTLY) require = GENTLY.hijack(require); -var sys = require('sys'), +var sys = require('./sys'), Stream = require('net').Stream, auth = require('./auth'), Parser = require('./parser'), diff --git a/lib/mysql/parser.js b/lib/mysql/parser.js index 56fae08ce..7cb49f8cb 100644 --- a/lib/mysql/parser.js +++ b/lib/mysql/parser.js @@ -2,7 +2,7 @@ if (global.GENTLY) require = GENTLY.hijack(require); // see: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol -var sys = require('sys'), +var sys = require('./sys'), Buffer = require('buffer').Buffer, EventEmitter = require('events').EventEmitter, POWS = [1, 256, 65536, 16777216]; diff --git a/lib/mysql/query.js b/lib/mysql/query.js index 79d515b0f..33fcabc66 100644 --- a/lib/mysql/query.js +++ b/lib/mysql/query.js @@ -1,6 +1,6 @@ if (global.GENTLY) require = GENTLY.hijack(require); -var sys = require('sys'), +var sys = require('./sys'), EventEmitter = require('events').EventEmitter, Parser = require('./parser'), Client; diff --git a/lib/mysql/sys.js b/lib/mysql/sys.js new file mode 100644 index 000000000..e9493e9ba --- /dev/null +++ b/lib/mysql/sys.js @@ -0,0 +1,6 @@ +// Backwards compatibility ... +try { + module.exports = require('util'); +} catch (e) { + module.exports = require('sys'); +} diff --git a/test/common.js b/test/common.js index 78becb9c7..d89624c52 100644 --- a/test/common.js +++ b/test/common.js @@ -1,7 +1,6 @@ -var path = require('path') - , sys = require('sys'); - +var path = require('path'); require.paths.unshift(path.dirname(__dirname)+'/lib'); +var sys = require('mysql/sys'); global.TEST_DB = 'node_mysql_test'; global.TEST_CONFIG = { From 81edd835df95add9631224cf1188c877e908fcd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Thu, 14 Oct 2010 12:46:39 +0200 Subject: [PATCH 16/28] Initial support for reconnecting on timeout Needs more some more testing, and probably also some way of being configured. --- lib/mysql/client.js | 17 +++++++++++++++ test/simple/test-client.js | 35 ++++++++++++++++++++++++++++-- test/system/test-client-timeout.js | 22 +++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 test/system/test-client-timeout.js diff --git a/lib/mysql/client.js b/lib/mysql/client.js index c3f3d8a15..3d8846715 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -26,6 +26,7 @@ function Client(properties) { this.maxPacketSize = 0x01000000; this.charsetNumber = Client.UTF8_UNICODE_CI; this.debug = false; + this.ending = false; this._greeting = null; this._queue = []; @@ -52,6 +53,14 @@ Client.prototype.connect = function(cb) { }) .on('data', function(b) { parser.write(b); + }) + .on('end', function() { + if (self.ending) { + self.ending = false; + return; + } + + self._prequeue(connect); }); parser @@ -180,6 +189,9 @@ Client.prototype.destroy = function() { Client.prototype.end = function(cb) { var self = this; + + this.ending = true; + this._enqueue(function end() { var packet = new OutgoingPacket(1); packet.writeNumber(1, Client.COM_QUIT); @@ -192,6 +204,11 @@ Client.prototype.end = function(cb) { }); }; +Client.prototype._prequeue = function(fn, delegate) { + this._queue.unshift({fn: fn, delegate: delegate}); + fn(); +}; + Client.prototype._enqueue = function(fn, delegate) { this._queue.push({fn: fn, delegate: delegate}); if (this._queue.length == 1) { diff --git a/test/simple/test-client.js b/test/simple/test-client.js index acb48291f..6225cf18e 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -30,6 +30,7 @@ test(function constructor() { assert.strictEqual(client.typeCast, true); assert.strictEqual(client.debug, false); + assert.strictEqual(client.ending, false); assert.strictEqual(client.flags, Client.defaultFlags); assert.strictEqual(client.maxPacketSize, 0x01000000); @@ -56,10 +57,12 @@ test(function connect() { var CONNECTION, PARSER, onConnection = {}, - CB = function() {}; + CB = function() {}, + CONNECT_FN; gently.expect(client, '_enqueue', function(task, cb) { assert.strictEqual(cb, CB); + CONNECT_FN = task; task(); }); @@ -71,7 +74,7 @@ test(function connect() { assert.equal(host, client.host); }); - var events = ['error', 'data']; + var events = ['error', 'data', 'end']; gently.expect(CONNECTION, 'on', events.length, function(event, fn) { assert.equal(event, events.shift()); onConnection[event] = fn; @@ -117,6 +120,20 @@ test(function connect() { onConnection.data(BUFFER ); })(); + + (function testUnexpectedEnd() { + gently.expect(client, '_prequeue', function(fn) { + assert.strictEqual(fn, CONNECT_FN); + }); + + onConnection.end(); + })(); + + (function testExpectedEnd() { + client.ending = true; + onConnection.end(); + assert.equal(client.ending, false); + })(); }); test(function write() { @@ -357,6 +374,8 @@ test(function end() { client._connection = {}; gently.expect(client, '_enqueue', function (fn, cb) { + assert.equal(client.ending, true); + gently.expect(OutgoingPacketStub, 'new', function(size, number) { PACKET = this; assert.equal(size, 1); @@ -383,6 +402,18 @@ test(function end() { client.end(CB); }); +test(function _prequeue() { + var FN = gently.expect(function fn() {}), + CB = function() {}; + + client._queue.push(1); + + client._prequeue(FN, CB); + assert.equal(client._queue.length, 2); + assert.strictEqual(client._queue[0].fn, FN); + assert.strictEqual(client._queue[0].delegate, CB); +}); + test(function _enqueue() { var FN = gently.expect(function fn() {}), CB = function() {}; diff --git a/test/system/test-client-timeout.js b/test/system/test-client-timeout.js new file mode 100644 index 000000000..2463e25ba --- /dev/null +++ b/test/system/test-client-timeout.js @@ -0,0 +1,22 @@ +require('../common'); +var Client = require('mysql').Client, + client = new Client(TEST_CONFIG), + gently = new Gently(), + timeout = setTimeout(function() { + throw new Error('MySql timeout did not happen'); + }, 5000); + +client.connect(); + +// Not sure if we need all 3 of these, but they do the trick +client.query('SET interactive_timeout = 1'); +client.query('SET wait_timeout = 1'); +client.query('SET net_read_timeout = 1'); + +client._connection.on('end', function() { + client.query('SELECT 1', gently.expect(function afterTimeoutSelect(err) { + clearTimeout(timeout); + assert.ifError(err); + client.end(); + })); +}); From 4ce2fa893feb499a38dc0a61e064a7c45050f41a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Thu, 14 Oct 2010 12:53:27 +0200 Subject: [PATCH 17/28] Do not reconnect on authentication failure --- lib/mysql/client.js | 5 +++++ test/simple/test-client.js | 8 ++++++++ test/system/test-client-bad-credentials.js | 10 ++++++++++ 3 files changed, 23 insertions(+) create mode 100644 test/system/test-client-bad-credentials.js diff --git a/lib/mysql/client.js b/lib/mysql/client.js index 3d8846715..e22396083 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -27,6 +27,7 @@ function Client(properties) { this.charsetNumber = Client.UTF8_UNICODE_CI; this.debug = false; this.ending = false; + this.connected = false; this._greeting = null; this._queue = []; @@ -60,6 +61,10 @@ Client.prototype.connect = function(cb) { return; } + if (!self.connected) { + return; + } + self._prequeue(connect); }); diff --git a/test/simple/test-client.js b/test/simple/test-client.js index 6225cf18e..e9bbfce04 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -31,6 +31,7 @@ test(function constructor() { assert.strictEqual(client.typeCast, true); assert.strictEqual(client.debug, false); assert.strictEqual(client.ending, false); + assert.strictEqual(client.connected, false); assert.strictEqual(client.flags, Client.defaultFlags); assert.strictEqual(client.maxPacketSize, 0x01000000); @@ -121,7 +122,14 @@ test(function connect() { onConnection.data(BUFFER ); })(); + (function testEndBeforeConnected() { + gently.expect(client, '_prequeue', 0); + onConnection.end(); + })(); + (function testUnexpectedEnd() { + client.connected = true; + gently.expect(client, '_prequeue', function(fn) { assert.strictEqual(fn, CONNECT_FN); }); diff --git a/test/system/test-client-bad-credentials.js b/test/system/test-client-bad-credentials.js new file mode 100644 index 000000000..e05d7a8cb --- /dev/null +++ b/test/system/test-client-bad-credentials.js @@ -0,0 +1,10 @@ +require('../common'); +var Client = require('mysql').Client, + client = Client(TEST_CONFIG), + gently = new Gently(); + +client.password = 'WRONG PASSWORD'; + +client.connect(gently.expect(function connectCb(err, result) { + assert.equal(err.number, Client.ERROR_ACCESS_DENIED_ERROR); +})); From 2b3258c06435ae31e0a3d656ec379680efd09634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Thu, 14 Oct 2010 13:09:04 +0200 Subject: [PATCH 18/28] Fix timeout reconnect Also make sure client.connect is always set properly --- lib/mysql/client.js | 3 +++ test/simple/test-client.js | 7 +++++++ test/system/test-client-timeout.js | 10 +++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/mysql/client.js b/lib/mysql/client.js index e22396083..9a0d43716 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -57,6 +57,7 @@ Client.prototype.connect = function(cb) { }) .on('end', function() { if (self.ending) { + self.connected = false; self.ending = false; return; } @@ -65,6 +66,7 @@ Client.prototype.connect = function(cb) { return; } + self.connected = false; self._prequeue(connect); }); @@ -258,6 +260,7 @@ Client.prototype._handlePacket = function(packet) { } if (type == Parser.OK_PACKET) { + this.connected = true; if (delegate) { delegate(null, Client._packetToUserObject(packet)); } diff --git a/test/simple/test-client.js b/test/simple/test-client.js index e9bbfce04..b33904f1c 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -135,12 +135,16 @@ test(function connect() { }); onConnection.end(); + assert.equal(client.connected, false); })(); (function testExpectedEnd() { + client.connected = false; client.ending = true; + onConnection.end(); assert.equal(client.ending, false); + assert.equal(client.connected, false); })(); }); @@ -492,14 +496,17 @@ test(function _handlePacket() { client._queue = [TASK]; client._handlePacket(PACKET); + assert.equal(client.connected, true); })(); (function testNoDelegateOk() { var PACKET = {type: Parser.OK_PACKET}; client._queue = [{}]; + client.connected = false; gently.expect(client, '_dequeue'); client._handlePacket(PACKET); + assert.equal(client.connected, true); })(); (function testNormalError() { diff --git a/test/system/test-client-timeout.js b/test/system/test-client-timeout.js index 2463e25ba..fb6388d6c 100644 --- a/test/system/test-client-timeout.js +++ b/test/system/test-client-timeout.js @@ -2,8 +2,13 @@ require('../common'); var Client = require('mysql').Client, client = new Client(TEST_CONFIG), gently = new Gently(), + timeoutHappened = false, timeout = setTimeout(function() { - throw new Error('MySql timeout did not happen'); + if (!timeoutHappened) { + throw new Error('MySql timeout did not happen'); + } + + gently.verify(); }, 5000); client.connect(); @@ -14,6 +19,9 @@ client.query('SET wait_timeout = 1'); client.query('SET net_read_timeout = 1'); client._connection.on('end', function() { + timeoutHappened = true; + assert.equal(client.connected, false); + client.query('SELECT 1', gently.expect(function afterTimeoutSelect(err) { clearTimeout(timeout); assert.ifError(err); From db328a141ff500f17e67fd40821aaad3fe554f13 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Thu, 30 Sep 2010 22:51:44 +0200 Subject: [PATCH 19/28] Client emits error only if the query has no error handler --- lib/mysql/client.js | 4 +++- test/simple/test-client.js | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/mysql/client.js b/lib/mysql/client.js index 9a0d43716..6921c5ab4 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -113,7 +113,9 @@ Client.prototype.query = function(sql, params, cb) { } else { query .on('error', function(err) { - self.emit('error', err); + if (query.listeners('error').length <= 1) { + self.emit('error', err); + } self._dequeue(); }) .on('end', function(result) { diff --git a/test/simple/test-client.js b/test/simple/test-client.js index b33904f1c..1390a6aa4 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -9,6 +9,10 @@ for (var k in Parser) { ParserStub[k] = Parser[k]; }; +QueryStub.prototype.listeners = function() { + return []; +}; + var Client = require('mysql/client'); function test(test) { From e3bc7dbca83936959e7358da44e1bc5fac9f53fc Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Thu, 30 Sep 2010 22:52:21 +0200 Subject: [PATCH 20/28] Tests for query error behavior --- test/system/test-client-query-error.js | 121 +++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 test/system/test-client-query-error.js diff --git a/test/system/test-client-query-error.js b/test/system/test-client-query-error.js new file mode 100644 index 000000000..90e0f0f8c --- /dev/null +++ b/test/system/test-client-query-error.js @@ -0,0 +1,121 @@ +require('../common'); +var Client = require('mysql').Client; + +(function testErrorCallback() { + // The query callback should receive an error, + // the client should not throw. + + var gently = new Gently(), + client = new Client(TEST_CONFIG); + + client.connect(gently.expect(function connectCb(error) { + if (error) throw error; + })); + + client.query('invalid #*&% query', [], gently.expect(function queryCb(error) { + assert.ok(error); + client.end(); + gently.verify('testErrorCallback'); + })); +})(); + +(function testQueryError() { + // The query 'error' handler should be called, + // the client should not throw. + + var gently = new Gently(), + client = new Client(TEST_CONFIG), + query; + + client.connect(gently.expect(function connectCb(error) { + if (error) throw error; + })); + + query = client.query('invalid #*&% query'); + + query.on('error', gently.expect(function errCb(error) { + assert.ok(error); + client.end(); + gently.verify('testQueryError'); + })); +})(); + +(function testClientError() { + // The query should not throw, + // the client's error handler should be called. + + var gently = new Gently(), + client = new Client(TEST_CONFIG), + query; + + client.connect(gently.expect(function connectCb(error) { + if (error) throw error; + })); + + query = client.query('invalid #*&% query'); + + client.on('error', gently.expect(function errCb(error) { + assert.ok(error); + client.end(); + gently.verify('testClientError'); + })); +})(); + +(function testRemoveListener() { + // The query's error handler should not be called and the query must not throw, + // the client's error handler should be called. + + var gently = new Gently(), + client = new Client(TEST_CONFIG), + query, + dummyHandler = function() { + throw new Error('The dummy handler should not be called'); + }; + + client.connect(gently.expect(function connectCb(error) { + if (error) throw error; + })); + + query = client.query('invalid #*&% query'); + + query.on('error', dummyHandler); + query.removeListener('error', dummyHandler); + + client.on('error', gently.expect(function errCb(error) { + assert.ok(error); + client.end(); + gently.verify('testRemoveListener'); + })); +})(); + +(function testSerialError() { + // Query errors should not leave the client in a broken state, + // subsequent (correct) queries should work fine. + + var gently = new Gently(), + client = new Client(TEST_CONFIG); + + client.connect(gently.expect(function connectCb(error) { + if (error) throw error; + })); + + client.query('invalid #*&% query', [], gently.expect(function queryCb(error) { + assert.ok(error); + })); + + client.query('SHOW STATUS', [], gently.expect(function queryCb(error, rows, fields) { + assert.ifError(error); + assert.equal(rows.length >= 50, true); + assert.equal(Object.keys(fields).length, 2); + })); + + client.query('invalid #*&% query', [], gently.expect(function queryCb(error) { + assert.ok(error); + })); + + client.query('invalid #*&% query', [], gently.expect(function errCb(error) { + assert.ok(error); + client.end(); + gently.verify('testSerialError'); + })); +})(); \ No newline at end of file From ffdc745476da2be8cd71ca576f9bdf732c432e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Thu, 14 Oct 2010 13:14:58 +0200 Subject: [PATCH 21/28] Proper unit test for query error listener handling --- test/simple/test-client.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/test/simple/test-client.js b/test/simple/test-client.js index 1390a6aa4..21a661f67 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -9,10 +9,6 @@ for (var k in Parser) { ParserStub[k] = Parser[k]; }; -QueryStub.prototype.listeners = function() { - return []; -}; - var Client = require('mysql/client'); function test(test) { @@ -297,8 +293,13 @@ test(function query() { }); gently.expect(client, '_enqueue', function() { - (function testQueryErr() { + (function testQueryErrWithoutListener() { var ERR = new Error('oh oh'); + gently.expect(QUERY, 'listeners', function (event) { + assert.equal(event, 'error'); + return [1]; + }); + gently.expect(client, 'emit', function (event, err) { assert.equal(event, 'error'); assert.strictEqual(err, ERR); @@ -307,6 +308,17 @@ test(function query() { queryEmit.error(ERR); })(); + (function testQueryErrWithListener() { + var ERR = new Error('oh oh'); + gently.expect(QUERY, 'listeners', function (event) { + assert.equal(event, 'error'); + return [1, 2]; + }); + gently.expect(client, 'emit', 0); + gently.expect(client, '_dequeue'); + queryEmit.error(ERR); + })(); + (function testQuerySimpleEnd() { gently.expect(client, '_dequeue'); queryEmit.end(); From 8f599e32d92be5da432e44a695f18517e17ca90a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Thu, 14 Oct 2010 13:26:18 +0200 Subject: [PATCH 22/28] Handle ECONNREFUSED properly --- lib/mysql/client.js | 10 +++++++++- test/simple/test-client.js | 21 ++++++++++++++++++--- test/system/test-client-connection-error.js | 11 +++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 test/system/test-client-connection-error.js diff --git a/lib/mysql/client.js b/lib/mysql/client.js index 6921c5ab4..1eed2c9c4 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -6,7 +6,8 @@ var sys = require('./sys'), Parser = require('./parser'), OutgoingPacket = require('./outgoing_packet'), Query = require('./query'), - EventEmitter = require('events').EventEmitter; + EventEmitter = require('events').EventEmitter, + netBinding = process.binding('net'); function Client(properties) { if (!(this instanceof Client)) { @@ -50,6 +51,13 @@ Client.prototype.connect = function(cb) { connection.connect(self.port, self.host); connection .on('error', function(err) { + if (err.errno == netBinding.ECONNREFUSED) { + if (cb) { + cb(err); + } + return; + } + self.emit('error', err); }) .on('data', function(b) { diff --git a/test/simple/test-client.js b/test/simple/test-client.js index 21a661f67..45b8003ba 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -3,7 +3,8 @@ var StreamStub = GENTLY.stub('net', 'Stream'), ParserStub = GENTLY.stub('./parser'), OutgoingPacketStub = GENTLY.stub('./outgoing_packet'), QueryStub = GENTLY.stub('./query'), - Parser = require('mysql/parser'); + Parser = require('mysql/parser'), + netBinding = process.binding('net'); for (var k in Parser) { ParserStub[k] = Parser[k]; @@ -58,7 +59,10 @@ test(function connect() { var CONNECTION, PARSER, onConnection = {}, - CB = function() {}, + CB = function() { + CB_DELEGATE.apply(this, arguments); + }, + CB_DELEGATE, CONNECT_FN; gently.expect(client, '_enqueue', function(task, cb) { @@ -103,7 +107,7 @@ test(function connect() { assert.strictEqual(client._connection, CONNECTION); assert.strictEqual(client._parser, PARSER); - (function testConnectionError() { + (function testRandomConnectionError() { var ERR = new Error('ouch'); gently.expect(client, 'emit', function(event, err) { assert.equal(event, 'error'); @@ -113,6 +117,17 @@ test(function connect() { onConnection.error(ERR); })(); + (function testConnectionRefusedError() { + var ERR = new Error('ouch'); + ERR.errno = netBinding.ECONNREFUSED; + + CB_DELEGATE = gently.expect(function connectCallback(err) { + assert.strictEqual(err, ERR); + }); + + onConnection.error(ERR); + })(); + (function testOnConnectionData() { var BUFFER = {}; gently.expect(PARSER, 'write', function(buffer) { diff --git a/test/system/test-client-connection-error.js b/test/system/test-client-connection-error.js new file mode 100644 index 000000000..c26f0f064 --- /dev/null +++ b/test/system/test-client-connection-error.js @@ -0,0 +1,11 @@ +require('../common'); +var Client = require('mysql').Client, + client = Client(TEST_CONFIG), + gently = new Gently(), + ECONNREFUSED = process.binding('net').ECONNREFUSED; + +client.host = 'BADHOST'; + +client.connect(gently.expect(function connectCb(err, result) { + assert.equal(err.errno, ECONNREFUSED); +})); From f7aeead4ab1abde876d7692d5d8f3562c95a5529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Thu, 14 Oct 2010 14:26:20 +0200 Subject: [PATCH 23/28] Update readme --- Readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Readme.md b/Readme.md index 3c805fb63..0ab471590 100644 --- a/Readme.md +++ b/Readme.md @@ -207,6 +207,7 @@ At this point the module is ready to be tried out, but a lot of things are yet t * Packet's > 16 MB * Compression * Performance profiling +* Handle re-connect after bad credential error (should query queue be kept?) * ? ## License From 636d27e13d6312528cfaec499d5bdf458cf830d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Thu, 14 Oct 2010 14:35:15 +0200 Subject: [PATCH 24/28] useDatabase --- lib/mysql/client.js | 10 ++++++++ test/simple/test-client.js | 32 +++++++++++++++++++++++++ test/system/test-client-use-database.js | 12 ++++++++++ 3 files changed, 54 insertions(+) create mode 100644 test/system/test-client-use-database.js diff --git a/lib/mysql/client.js b/lib/mysql/client.js index 1eed2c9c4..757f27f25 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -200,6 +200,16 @@ Client.prototype.ping = function(cb) { }, cb); }; +Client.prototype.useDatabase = function(database, cb) { + var self = this; + this._enqueue(function ping() { + var packet = new OutgoingPacket(1 + Buffer.byteLength(database, 'utf-8')); + packet.writeNumber(1, Client.COM_INIT_DB); + packet.write(database, 'utf-8'); + self.write(packet); + }, cb); +}; + Client.prototype.destroy = function() { this._connection.destroy(); } diff --git a/test/simple/test-client.js b/test/simple/test-client.js index 45b8003ba..8dbf80f10 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -403,6 +403,38 @@ test(function ping() { client.ping(CB); }); +test(function useDatabase() { + var CB = function() {}, + DB = 'foo', + PACKET; + + gently.expect(client, '_enqueue', function (fn, cb) { + gently.expect(OutgoingPacketStub, 'new', function(size, number) { + PACKET = this; + assert.equal(size, 1 + Buffer.byteLength(DB, 'utf-8')); + + gently.expect(this, 'writeNumber', function (length, val) { + assert.equal(length, 1); + assert.equal(val, Client.COM_INIT_DB); + }); + + gently.expect(PACKET, 'write', function(str, encoding) { + assert.equal(str, DB); + assert.equal(encoding, 'utf-8'); + }); + + gently.expect(client, 'write', function (packet) { + assert.strictEqual(packet, PACKET); + }); + }); + fn(); + + assert.strictEqual(cb, CB); + }); + + client.useDatabase(DB, CB); +}); + test(function destroy() { var CONNECTION = client._connection = {}; diff --git a/test/system/test-client-use-database.js b/test/system/test-client-use-database.js new file mode 100644 index 000000000..766bdd3a0 --- /dev/null +++ b/test/system/test-client-use-database.js @@ -0,0 +1,12 @@ +require('../common'); +var Client = require('mysql').Client, + client = Client(TEST_CONFIG), + gently = new Gently(); + +client.connect(); + +client.useDatabase(TEST_DB, gently.expect(function useDbCb(err) { + if (err) throw err; +})); + +client.end(); From b0bf778c2d040c4e99122f4934c3a3ab3d790f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Thu, 14 Oct 2010 15:15:12 +0200 Subject: [PATCH 25/28] Fix parsing RESULT_SET_HEADER_PACKET extra part --- lib/mysql/parser.js | 13 +++++++++---- test/simple/test-parser.js | 10 ++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/mysql/parser.js b/lib/mysql/parser.js index 7cb49f8cb..87bddf428 100644 --- a/lib/mysql/parser.js +++ b/lib/mysql/parser.js @@ -231,7 +231,7 @@ Parser.prototype.write = function(buffer) { this.receivingFieldPackets = true; packet.type = Parser.RESULT_SET_HEADER_PACKET; - packet.fieldCount = lengthCoded(packet.fieldCount, Parser.EXTRA); + packet.fieldCount = lengthCoded(packet.fieldCount, Parser.EXTRA_LENGTH); break; @@ -309,8 +309,12 @@ Parser.prototype.write = function(buffer) { break; // RESULT_SET_HEADER_PACKET - case Parser.EXTRA: - packet.extra = lengthCoded(packet.extra); + case Parser.EXTRA_LENGTH: + packet.extra = ''; + self._lengthCodedStringLength = lengthCoded(self._lengthCodedStringLength); + break; + case Parser.EXTRA_STRING: + packet.extra += String.fromCharCode(c); break; // FIELD_PACKET or EOF_PACKET @@ -593,7 +597,8 @@ Parser.INSERT_ID = s++; Parser.SERVER_STATUS = s++; Parser.WARNING_COUNT = s++; Parser.MESSAGE = s++; -Parser.EXTRA = s++; +Parser.EXTRA_LENGTH = s++; +Parser.EXTRA_STRING = s++; Parser.FIELD_CATALOG_LENGTH = s++; Parser.FIELD_CATALOG_STRING = s++; Parser.FIELD_DB_LENGTH = s++; diff --git a/test/simple/test-parser.js b/test/simple/test-parser.js index e0f0aea98..83564bd45 100644 --- a/test/simple/test-parser.js +++ b/test/simple/test-parser.js @@ -205,20 +205,22 @@ test(function write() { (function testResultHeaderPacketWithExtra() { parser.receivingFieldPackets = false; - parser.write(new Buffer([2, 0, 0, 1])); + parser.write(new Buffer([5, 0, 0, 1])); var packet = parser.packet; parser.write(new Buffer([23])); - assert.equal(parser.state, Parser.EXTRA); + assert.equal(parser.state, Parser.EXTRA_LENGTH); assert.equal(packet.fieldCount, 23); + parser.write(new Buffer([3])); + gently.expect(parser, 'emit', function(event, val) { assert.equal(event, 'packet'); assert.equal(val.type, Parser.RESULT_SET_HEADER_PACKET); - assert.equal(val.extra, 51); + assert.equal(val.extra, 'abc'); }); - parser.write(new Buffer([51])); + parser.write(new Buffer('abc')); })(); (function testFieldPacket() { From 92756400d66b15517e2c7a0f5af9c3d65cfb1293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Thu, 14 Oct 2010 15:16:56 +0200 Subject: [PATCH 26/28] client.statistics() --- lib/mysql/client.js | 13 +++++++++++-- test/simple/test-client.js | 26 ++++++++++++++++++++++++++ test/system/test-client-statistics.js | 14 ++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 test/system/test-client-statistics.js diff --git a/lib/mysql/client.js b/lib/mysql/client.js index 757f27f25..ed01e2750 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -200,6 +200,15 @@ Client.prototype.ping = function(cb) { }, cb); }; +Client.prototype.statistics = function(cb) { + var self = this; + this._enqueue(function ping() { + var packet = new OutgoingPacket(1); + packet.writeNumber(1, Client.COM_STATISTICS); + self.write(packet); + }, cb); +}; + Client.prototype.useDatabase = function(database, cb) { var self = this; this._enqueue(function ping() { @@ -279,12 +288,12 @@ Client.prototype._handlePacket = function(packet) { return; } - if (type == Parser.OK_PACKET) { + if (type != Parser.ERROR_PACKET) { this.connected = true; if (delegate) { delegate(null, Client._packetToUserObject(packet)); } - } else if (type == Parser.ERROR_PACKET) { + } else { packet = Client._packetToUserObject(packet); if (delegate) { delegate(packet); diff --git a/test/simple/test-client.js b/test/simple/test-client.js index 8dbf80f10..102c12eea 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -403,6 +403,32 @@ test(function ping() { client.ping(CB); }); +test(function statistics() { + var CB = function() {}, + PACKET; + + gently.expect(client, '_enqueue', function (fn, cb) { + gently.expect(OutgoingPacketStub, 'new', function(size, number) { + PACKET = this; + assert.equal(size, 1); + + gently.expect(this, 'writeNumber', function (length, val) { + assert.equal(length, 1); + assert.equal(val, Client.COM_STATISTICS); + }); + + gently.expect(client, 'write', function (packet) { + assert.strictEqual(packet, PACKET); + }); + }); + fn(); + + assert.strictEqual(cb, CB); + }); + + client.statistics(CB); +}); + test(function useDatabase() { var CB = function() {}, DB = 'foo', diff --git a/test/system/test-client-statistics.js b/test/system/test-client-statistics.js new file mode 100644 index 000000000..7b6c7ebc2 --- /dev/null +++ b/test/system/test-client-statistics.js @@ -0,0 +1,14 @@ +require('../common'); +var Client = require('mysql').Client, + client = Client(TEST_CONFIG), + gently = new Gently(); + +client.connect(); + +client.statistics(gently.expect(function statisticsCb(err, statistics) { + if (err) throw err; + + assert.ok(statistics.extra.match(/time/i)); +})); + +client.end(); From 5628337585fe99cf3a8bb923801867e227478121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Thu, 14 Oct 2010 15:29:09 +0200 Subject: [PATCH 27/28] Update docs --- Readme.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Readme.md b/Readme.md index 0ab471590..ee7ef8efe 100644 --- a/Readme.md +++ b/Readme.md @@ -148,6 +148,18 @@ query. This method returns a `Query` object which can be used to stream incoming row data. +### client.ping([cb]) + +Sends a ping command to the server. + +### client.useDatbase(database, [cb]) + +Same as issuing a `'USE '` query. + +### client.statistics([cb]) + +Returns some server statistics provided by MySql. + ### client.format(sql, params) Allows to safely insert a list of `params` into a `sql` string using the From ceec17d45ee041c235572bfdd371f31d8463de63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Thu, 14 Oct 2010 15:29:32 +0200 Subject: [PATCH 28/28] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1c679f438..596970239 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "name" : "mysql" -, "version": "0.6.0" +, "version": "0.7.0" , "dependencies": {"gently": ">=0.8.0"} , "main" : "./lib/mysql" }