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/Readme.md b/Readme.md index 2fb3acefc..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 @@ -157,9 +169,16 @@ placeholder mechanism described above. Escapes a single `val` for use inside of a sql string. -### client.end() +### 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) @@ -194,11 +213,13 @@ 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 * Compression * Performance profiling +* Handle re-connect after bad credential error (should query queue be kept?) * ? ## License diff --git a/benchmark/insert.js b/benchmark/node-mysql/insert-select.js similarity index 52% rename from benchmark/insert.js rename to benchmark/node-mysql/insert-select.js index 6dc573461..9e5d2bd25 100644 --- a/benchmark/insert.js +++ b/benchmark/node-mysql/insert-select.js @@ -1,4 +1,4 @@ -require('../test/common'); +require('../../test/common'); var Client = require('mysql/client'), client = Client(TEST_CONFIG); @@ -21,10 +21,8 @@ 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() { + client.query('INSERT INTO '+TEST_TABLE+' SET title = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"', function() { inserts++; if (inserts < total) { insertOne(); @@ -33,7 +31,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/benchmark/php/insert-select.php b/benchmark/php/insert-select.php new file mode 100644 index 000000000..d63018662 --- /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 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";', $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_assoc($q)) { +} +$duration = (microtime(true) - $start); +$rowsPerSecond = $INSERTS / $duration; +echo sprintf("%d rows / second\n", $rowsPerSecond); +?> 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'); diff --git a/lib/mysql/client.js b/lib/mysql/client.js index aa98d326d..ed01e2750 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -1,12 +1,13 @@ 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'), 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)) { @@ -26,6 +27,8 @@ function Client(properties) { this.maxPacketSize = 0x01000000; this.charsetNumber = Client.UTF8_UNICODE_CI; this.debug = false; + this.ending = false; + this.connected = false; this._greeting = null; this._queue = []; @@ -48,10 +51,31 @@ 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) { parser.write(b); + }) + .on('end', function() { + if (self.ending) { + self.connected = false; + self.ending = false; + return; + } + + if (!self.connected) { + return; + } + + self.connected = false; + self._prequeue(connect); }); parser @@ -97,7 +121,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) { @@ -105,7 +131,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); @@ -165,10 +191,59 @@ Client.prototype.escape = function(val) { return "'"+val+"'"; }; -Client.prototype.end = function() { - this._connection.end(); +Client.prototype.ping = function(cb) { + var self = this; + this._enqueue(function ping() { + var packet = new OutgoingPacket(1); + packet.writeNumber(1, Client.COM_PING); + self.write(packet); + }, 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() { + 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(); +} + +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); + self.write(packet); + if (cb) { + self._connection.on('end', cb); + } + + self._dequeue(); + }); +}; + +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}); @@ -213,11 +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/lib/mysql/parser.js b/lib/mysql/parser.js index 84f867ae3..87bddf428 100644 --- a/lib/mysql/parser.js +++ b/lib/mysql/parser.js @@ -2,9 +2,10 @@ 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; + 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(); @@ -228,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; @@ -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,21 +283,38 @@ 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(); } 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; // 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 @@ -575,8 +595,10 @@ 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.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/lib/mysql/query.js b/lib/mysql/query.js index abeae2548..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; @@ -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; } 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/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" } 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 = { diff --git a/test/simple/test-client.js b/test/simple/test-client.js index 6e03a3fe6..102c12eea 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]; @@ -30,6 +31,8 @@ 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); @@ -56,10 +59,15 @@ 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) { assert.strictEqual(cb, CB); + CONNECT_FN = task; task(); }); @@ -71,7 +79,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; @@ -99,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'); @@ -109,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) { @@ -117,6 +136,31 @@ 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); + }); + + 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); + })(); }); test(function write() { @@ -264,8 +308,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); @@ -274,6 +323,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(); @@ -317,12 +377,142 @@ 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 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', + 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 = {}; + + gently.expect(CONNECTION, 'destroy'); + client.destroy(); +}); + test(function end() { + var CB = function() {}, + PACKET; + client._connection = {}; - gently.expect(client._connection, 'end'); + 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); + + 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); + }); + + gently.expect(client, '_dequeue'); + }); + fn(); + }); + + client.end(CB); +}); + +test(function _prequeue() { + var FN = gently.expect(function fn() {}), + CB = function() {}; + + client._queue.push(1); - client.end(); + 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() { @@ -395,14 +585,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/simple/test-parser.js b/test/simple/test-parser.js index f7bea4c28..83564bd45 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'); @@ -200,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() { 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); +})); 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); +})); 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(); diff --git a/test/system/test-client-end.js b/test/system/test-client-end.js new file mode 100644 index 000000000..4eb446312 --- /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. +})(); 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(); 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 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(); +})); + 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(); diff --git a/test/system/test-client-timeout.js b/test/system/test-client-timeout.js new file mode 100644 index 000000000..fb6388d6c --- /dev/null +++ b/test/system/test-client-timeout.js @@ -0,0 +1,30 @@ +require('../common'); +var Client = require('mysql').Client, + client = new Client(TEST_CONFIG), + gently = new Gently(), + timeoutHappened = false, + timeout = setTimeout(function() { + if (!timeoutHappened) { + throw new Error('MySql timeout did not happen'); + } + + gently.verify(); + }, 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() { + timeoutHappened = true; + assert.equal(client.connected, false); + + client.query('SELECT 1', gently.expect(function afterTimeoutSelect(err) { + clearTimeout(timeout); + assert.ifError(err); + client.end(); + })); +}); 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();