diff --git a/Makefile b/Makefile index a3cf21c21..c9f15e0cc 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +SHELL := /bin/bash + test-simple: @find test/simple/test-*.js | xargs -n 1 -t node test-system: diff --git a/Readme.md b/Readme.md index 6e2b4bd02..a993a1d33 100644 --- a/Readme.md +++ b/Readme.md @@ -2,11 +2,17 @@ ## Purpose -A node.js module implementing the -[MySQL protocol](http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol). +A pure node.js JavaScript Client implementing the [MySQL protocol](http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol). + +## Current status + +This module was developed for [Transloadit](http://transloadit.com/), a service focused on uploading +and encoding images and videos. It is currently used in production there, but since the service is not +very heavy on database interaction your milage may vary. ## Contributors +* Felix Geisendörfer ([felixge](http://github.com/felixge/node-mysql/commits/master?author=felixge)) - Author and maintainer * Bert Belder ([piscisaureus](http://github.com/felixge/node-mysql/commits/master?author=piscisaureus)) * Alan Gutierrez ([bigeasy](http://github.com/felixge/node-mysql/commits/master?author=bigeasy)) * Brian ([mscdex](http://github.com/felixge/node-mysql/commits/master?author=mscdex)) @@ -15,18 +21,16 @@ A node.js module implementing the ## Sponsors -* [Joyent](http://www.joyent.com/) -* [pinkbike.com](http://pinkbike.com/) +* [Joyent](http://www.joyent.com/) - Main sponsor, you should check out their [node.js hosting](https://no.de/). +* [pinkbike.com](http://pinkbike.com/) - The most awesome biking site there is -I'm working on this driver because I need it for my own startup -([transloadit.com][transloadit]), but it's a big project (~100-200 hours) with -obvious benefits to other companies who are using MySql. +This is a rather large project requiring a significant amount of my limited resources. -So if your company could benefit from a well-engineered node.js mysql driver, -I would greatly appriciate any sponsorship you may be able to provide. All -sponsors will get lifetime display in this readme, priority support on problems, -and votes on roadmap decisions. If you are interested, contact me at -[felix@debuggable.com](mailto:felix@debuggable.com) for details. +If your company could benefit from a well-engineered non-blocking mysql driver, and +wants to support this project, I would greatly appriciate any sponsorship you may be +able to provide. All sponsors will get lifetime display in this readme, priority +support on problems, and votes on roadmap decisions. If you are interested, contact +me at [felix@debuggable.com](mailto:felix@debuggable.com) for details. Of course I'm also happy about code contributions. If you're interested in working on features, just get in touch so we can talk about API design and @@ -60,22 +64,24 @@ However, using a current version of node is encouraged. ## Tutorial var Client = require('mysql').Client, - client = new Client(); + client = new Client(), + TEST_DATABASE = 'nodejs_mysl_test', + TEST_TABLE = 'test'; client.user = 'root'; client.password = 'root'; client.connect(); - client.query('CREATE DATABASE '+TEST_CONFIG.database, function() { - if (err && err.errorNumber != Client.ERROR_DB_CREATE_EXISTS) { + client.query('CREATE DATABASE '+TEST_DATABASE, function(err) { + if (err && err.number != Client.ERROR_DB_CREATE_EXISTS) { throw err; } }); // If no callback is provided, any errors will be emitted as `'error'` // events by the client - client.query('USE '+TEST_CONFIG.database); + client.query('USE '+TEST_DATABASE); client.query( 'CREATE TEMPORARY TABLE '+TEST_TABLE+ @@ -83,13 +89,13 @@ However, using a current version of node is encouraged. 'title VARCHAR(255), '+ 'text TEXT, '+ 'created DATETIME, '+ - 'PRIMARY KEY (id));', + 'PRIMARY KEY (id))' ); client.query( 'INSERT INTO '+TEST_TABLE+' '+ 'SET title = ?, text = ?, created = ?', - ['super cool', 'this is a nice text', '2010-08-16 10:00:23'], + ['super cool', 'this is a nice text', '2010-08-16 10:00:23'] ); var query = client.query( @@ -100,7 +106,7 @@ However, using a current version of node is encouraged. client.query( 'SELECT * FROM '+TEST_TABLE, - gently.expect(function selectCb(err, results, fields) { + function selectCb(err, results, fields) { if (err) { throw err; } @@ -108,7 +114,7 @@ However, using a current version of node is encouraged. console.log(results); console.log(fields); client.end(); - }) + } ); ## API @@ -159,6 +165,9 @@ query. This method returns a `Query` object which can be used to stream incoming row data. +**Warning:** `sql` statements with multiple queries separated by semicolons +are not supported yet. + ### client.ping([cb]) Sends a ping command to the server. @@ -219,6 +228,20 @@ itself will be made available soon. Emitted once the query is finished. In case there is no result set, a `result` parameter is provided which contains the information from the mysql OK packet. +## FAQ + +### How do I compile this module? + +This module is written entirely in JavaScript. There is no dependency on external +C libraries such as libmysql. That means you don't have to compile this module +at all. + +### How can I retrieve the id from the last inserted record? + + client.query('INSERT INTO my_table SET title = ?', function(err, info) { + console.log(info.insertId); + }); + ## Todo At this point the module is ready to be tried out, but a lot of things are yet to be done: @@ -231,6 +254,7 @@ At this point the module is ready to be tried out, but a lot of things are yet t * Performance profiling * Handle re-connect after bad credential error (should query queue be kept?) * Deal with stale connections / other potential network issues +* Decide how to handle queries with multiple statements ## License diff --git a/benchmark/node-mysql/insert-select.js b/benchmark/node-mysql/insert.js similarity index 70% rename from benchmark/node-mysql/insert-select.js rename to benchmark/node-mysql/insert.js index 9e5d2bd25..d6ef00a17 100644 --- a/benchmark/node-mysql/insert-select.js +++ b/benchmark/node-mysql/insert.js @@ -10,8 +10,9 @@ client.query('CREATE DATABASE '+TEST_DB, function(err) { } }); client.query('USE '+TEST_DB); +client.query('DROP TABLE IF EXISTS '+TEST_TABLE); client.query( - 'CREATE TEMPORARY TABLE '+TEST_TABLE+' ('+ + 'CREATE TABLE '+TEST_TABLE+' ('+ 'id INT(11) AUTO_INCREMENT, '+ 'title VARCHAR(255), '+ 'text TEXT, '+ @@ -31,18 +32,8 @@ client.query( insertsPerSecond = inserts / duration; console.log('%d inserts / second', insertsPerSecond.toFixed(2)); - - 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(); - }); + console.log('%d ms', +new Date - start); + client.end(); } }); } diff --git a/benchmark/node-mysql/select.js b/benchmark/node-mysql/select.js new file mode 100644 index 000000000..696d2b3cf --- /dev/null +++ b/benchmark/node-mysql/select.js @@ -0,0 +1,40 @@ +// Last v8 profile when running this test for 500k rows: +// https://gist.github.com/f85c38010c038e5efe2e +require('../../test/common'); +var Client = require('mysql/client'), + client = Client(TEST_CONFIG), + rows = 0; + +client.typeCast = false; + +client.connect(); +client.query('CREATE DATABASE '+TEST_DB, function(err) { + if (err && err.number != Client.ERROR_DB_CREATE_EXISTS) { + throw err; + } +}); + +client.query('USE '+TEST_DB); +var selectStart = +new Date; + +function query() { + client + .query('SELECT * FROM '+TEST_TABLE) + .on('row', function(row) { + rows++; + }) + .on('end', function() { + if (rows < 10000) { + query(); + return; + } + + var duration = (+new Date - selectStart) / 1000, + rowsPerSecond = rows / duration; + console.log('%d rows / second', rowsPerSecond.toFixed(2)); + console.log('%d ms', +new Date - selectStart); + client.end(); + }); +} + +query(); diff --git a/benchmark/php/insert-select.php b/benchmark/php/insert-select.php index d63018662..c3c3a512d 100644 --- a/benchmark/php/insert-select.php +++ b/benchmark/php/insert-select.php @@ -27,6 +27,7 @@ $duration = (microtime(true) - $start); $insertsPerSecond = $INSERTS / $duration; echo sprintf("%d inserts / second\n", $insertsPerSecond); +echo sprintf("%d ms\n", $duration * 1000); $start = microtime(true); $q = mysql_query('SELECT * FROM '.$table); @@ -35,4 +36,5 @@ $duration = (microtime(true) - $start); $rowsPerSecond = $INSERTS / $duration; echo sprintf("%d rows / second\n", $rowsPerSecond); +echo sprintf("%d ms\n", $duration * 1000); ?> diff --git a/lib/mysql/client.js b/lib/mysql/client.js index 4d7580e48..a3e405fec 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -7,7 +7,7 @@ var sys = require('./sys'), OutgoingPacket = require('./outgoing_packet'), Query = require('./query'), EventEmitter = require('events').EventEmitter, - netBinding = process.binding('net'); + netConstants = require('./net_constants'); function Client(properties) { if (!(this instanceof Client)) { @@ -50,7 +50,7 @@ Client.prototype.connect = function(cb) { connection .on('error', function(err) { - if (err.errno & (netBinding.ECONNREFUSED | netBinding.ENOTFOUND)) { + if (err.errno & (netConstants.ECONNREFUSED | netConstants.ENOTFOUND)) { if (cb) { cb(err); return; @@ -94,7 +94,10 @@ Client.prototype.query = function(sql, params, cb) { cb = arguments[1]; } - var query = new Query({typeCast: this.typeCast}); + var query = new Query({ + typeCast: this.typeCast, + sql: sql + }); if (cb) { var rows = [], fields = {}; @@ -351,11 +354,6 @@ Client._packetToUserObject = function(packet) { } return userObject; - - packet.message = packet.errorMessage; - delete packet.errorMessage; - packet.number = packet.errorNumber; - delete packet.errorNumber; }; Client.prototype._debugPacket = function(packet) { diff --git a/lib/mysql/net_constants.js b/lib/mysql/net_constants.js new file mode 100644 index 000000000..1355709ca --- /dev/null +++ b/lib/mysql/net_constants.js @@ -0,0 +1,22 @@ +// Constants have been changing through various node versions. +// First we try to look for them in the old location (in the net binding) +// If this fails, we try to get them from the constants module which +// was added in a later version. If that fails we raise an error; + +var NET_CONSTANTS_NOT_FOUND = 'Unable to detect constants location, please report this.'; + +module.exports = process.binding('net'); + +if ('ECONNREFUSED' in module.exports) { + return; +} + +try { + module.exports = require('constants'); +} catch (e) { + throw new Error(NET_CONSTANTS_NOT_FOUND); +} + +if (!('ECONNREFUSED' in module.exports)) { + throw new Error(NET_CONSTANTS_NOT_FOUND); +} diff --git a/lib/mysql/parser.js b/lib/mysql/parser.js index b737209d9..32e5ed34e 100644 --- a/lib/mysql/parser.js +++ b/lib/mysql/parser.js @@ -85,7 +85,7 @@ Parser.prototype.write = function(buffer) { switch (state) { // PACKET HEADER - case Parser.PACKET_LENGTH: + case 0: // PACKET_LENGTH: if (!packet) { packet = this.packet = new EventEmitter(); packet.index = 0; @@ -101,7 +101,7 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.PACKET_NUMBER: + case 1: // PACKET_NUMBER: // 1 byte packet.number = c; @@ -120,7 +120,7 @@ Parser.prototype.write = function(buffer) { break; // GREETING_PACKET - case Parser.GREETING_PROTOCOL_VERSION: + case 2: // GREETING_PROTOCOL_VERSION: // Nice undocumented MySql gem, the initial greeting can be an error // packet. Happens for too many connections errors. if (c === 0xff) { @@ -134,7 +134,7 @@ Parser.prototype.write = function(buffer) { packet.protocolVersion = c; advance(); break; - case Parser.GREETING_SERVER_VERSION: + case 3: // GREETING_SERVER_VERSION: if (packet.index == 0) { packet.serverVersion = ''; } @@ -146,7 +146,7 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.GREETING_THREAD_ID: + case 4: // GREETING_THREAD_ID: if (packet.index == 0) { packet.threadId = 0; } @@ -158,7 +158,7 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.GREETING_SCRAMBLE_BUFF_1: + case 5: // GREETING_SCRAMBLE_BUFF_1: if (packet.index == 0) { packet.scrambleBuffer = new Buffer(8 + 12); } @@ -170,11 +170,11 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.GREETING_FILLER_1: + case 6: // GREETING_FILLER_1: // 1 byte - 0x00 advance(); break; - case Parser.GREETING_SERVER_CAPABILITIES: + case 7: // GREETING_SERVER_CAPABILITIES: if (packet.index == 0) { packet.serverCapabilities = 0; } @@ -185,11 +185,11 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.GREETING_SERVER_LANGUAGE: + case 8: // GREETING_SERVER_LANGUAGE: packet.serverLanguage = c; advance(); break; - case Parser.GREETING_SERVER_STATUS: + case 9: // GREETING_SERVER_STATUS: if (packet.index == 0) { packet.serverStatus = 0; } @@ -201,13 +201,13 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.GREETING_FILLER_2: + case 10: // GREETING_FILLER_2: // 13 bytes - 0x00 if (packet.index == 12) { advance(); } break; - case Parser.GREETING_SCRAMBLE_BUFF_2: + case 11: // GREETING_SCRAMBLE_BUFF_2: // 12 bytes - not 13 bytes like the protocol spec says ... if (packet.index < 12) { packet.scrambleBuffer[packet.index + 8] = c; @@ -215,7 +215,7 @@ Parser.prototype.write = function(buffer) { break; // OK_PACKET, ERROR_PACKET, or RESULT_SET_HEADER_PACKET - case Parser.FIELD_COUNT: + case 12: // FIELD_COUNT: if (packet.index == 0) { if (c === 0xff) { packet.type = Parser.ERROR_PACKET; @@ -244,7 +244,7 @@ Parser.prototype.write = function(buffer) { break; // ERROR_PACKET - case Parser.ERROR_NUMBER: + case 13: // ERROR_NUMBER: if (packet.index == 0) { packet.errorNumber = 0; } @@ -263,13 +263,13 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.ERROR_SQL_STATE_MARKER: + case 14: // ERROR_SQL_STATE_MARKER: // 1 character - always # packet.sqlStateMarker = String.fromCharCode(c); packet.sqlState = ''; advance(); break; - case Parser.ERROR_SQL_STATE: + case 15: // ERROR_SQL_STATE: // 5 characters if (packet.index < 5) { packet.sqlState += String.fromCharCode(c); @@ -279,20 +279,20 @@ Parser.prototype.write = function(buffer) { advance(Parser.ERROR_MESSAGE); } break; - case Parser.ERROR_MESSAGE: + case 16: // ERROR_MESSAGE: if (packet.received <= packet.length) { packet.errorMessage = (packet.errorMessage || '') + String.fromCharCode(c); } break; // OK_PACKET - case Parser.AFFECTED_ROWS: + case 17: // AFFECTED_ROWS: packet.affectedRows = lengthCoded(packet.affectedRows); break; - case Parser.INSERT_ID: + case 18: // INSERT_ID: packet.insertId = lengthCoded(packet.insertId); break; - case Parser.SERVER_STATUS: + case 19: // SERVER_STATUS: if (packet.index == 0) { packet.serverStatus = 0; } @@ -304,7 +304,7 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.WARNING_COUNT: + case 20: // WARNING_COUNT: if (packet.index == 0) { packet.warningCount = 0; } @@ -317,23 +317,23 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.MESSAGE: + case 21: // MESSAGE: if (packet.received <= packet.length) { packet.message += String.fromCharCode(c); } break; // RESULT_SET_HEADER_PACKET - case Parser.EXTRA_LENGTH: + case 22: // EXTRA_LENGTH: packet.extra = ''; self._lengthCodedStringLength = lengthCoded(self._lengthCodedStringLength); break; - case Parser.EXTRA_STRING: + case 23: // EXTRA_STRING: packet.extra += String.fromCharCode(c); break; // FIELD_PACKET or EOF_PACKET - case Parser.FIELD_CATALOG_LENGTH: + case 24: // FIELD_CATALOG_LENGTH: if (packet.index == 0) { if (c === 0xfe) { packet.type = Parser.EOF_PACKET; @@ -344,7 +344,7 @@ Parser.prototype.write = function(buffer) { } self._lengthCodedStringLength = lengthCoded(self._lengthCodedStringLength); break; - case Parser.FIELD_CATALOG_STRING: + case 25: // FIELD_CATALOG_STRING: if (packet.index == 0) { packet.catalog = ''; } @@ -354,13 +354,13 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.FIELD_DB_LENGTH: + case 26: // FIELD_DB_LENGTH: self._lengthCodedStringLength = lengthCoded(self._lengthCodedStringLength); if (self._lengthCodedStringLength == 0) { advance(); } break; - case Parser.FIELD_DB_STRING: + case 27: // FIELD_DB_STRING: if (packet.index == 0) { packet.db = ''; } @@ -370,13 +370,13 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.FIELD_TABLE_LENGTH: + case 28: // FIELD_TABLE_LENGTH: self._lengthCodedStringLength = lengthCoded(self._lengthCodedStringLength); if (self._lengthCodedStringLength == 0) { advance(); } break; - case Parser.FIELD_TABLE_STRING: + case 29: // FIELD_TABLE_STRING: if (packet.index == 0) { packet.table = ''; } @@ -386,13 +386,13 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.FIELD_ORIGINAL_TABLE_LENGTH: + case 30: // FIELD_ORIGINAL_TABLE_LENGTH: self._lengthCodedStringLength = lengthCoded(self._lengthCodedStringLength); if (self._lengthCodedStringLength == 0) { advance(); } break; - case Parser.FIELD_ORIGINAL_TABLE_STRING: + case 31: // FIELD_ORIGINAL_TABLE_STRING: if (packet.index == 0) { packet.originalTable = ''; } @@ -402,10 +402,10 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.FIELD_NAME_LENGTH: + case 32: // FIELD_NAME_LENGTH: self._lengthCodedStringLength = lengthCoded(self._lengthCodedStringLength); break; - case Parser.FIELD_NAME_STRING: + case 33: // FIELD_NAME_STRING: if (packet.index == 0) { packet.name = ''; } @@ -415,13 +415,13 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.FIELD_ORIGINAL_NAME_LENGTH: + case 34: // FIELD_ORIGINAL_NAME_LENGTH: self._lengthCodedStringLength = lengthCoded(self._lengthCodedStringLength); if (self._lengthCodedStringLength == 0) { advance(); } break; - case Parser.FIELD_ORIGINAL_NAME_STRING: + case 35: // FIELD_ORIGINAL_NAME_STRING: if (packet.index == 0) { packet.originalName = ''; } @@ -431,11 +431,11 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.FIELD_FILLER_1: + case 36: // FIELD_FILLER_1: // 1 bytes - 0x00 advance(); break; - case Parser.FIELD_CHARSET_NR: + case 37: // FIELD_CHARSET_NR: if (packet.index == 0) { packet.charsetNumber = 0; } @@ -447,7 +447,7 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.FIELD_LENGTH: + case 38: // FIELD_LENGTH: if (packet.index == 0) { packet.fieldLength = 0; } @@ -459,11 +459,11 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.FIELD_TYPE: + case 39: // FIELD_TYPE: // 1 byte packet.fieldType = c; advance(); - case Parser.FIELD_FLAGS: + case 40: // FIELD_FLAGS: if (packet.index == 0) { packet.flags = 0; } @@ -475,23 +475,23 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.FIELD_DECIMALS: + case 41: // FIELD_DECIMALS: // 1 byte packet.decimals = c; advance(); break; - case Parser.FIELD_FILLER_2: + case 42: // FIELD_FILLER_2: // 2 bytes - 0x00 if (packet.index == 1) { advance(); } break; - case Parser.FIELD_DEFAULT: + case 43: // FIELD_DEFAULT: // TODO: Only occurs for mysql_list_fields() break; // EOF_PACKET - case Parser.EOF_WARNING_COUNT: + case 44: // EOF_WARNING_COUNT: if (packet.index == 0) { packet.warningCount = 0; } @@ -503,7 +503,7 @@ Parser.prototype.write = function(buffer) { advance(); } break; - case Parser.EOF_SERVER_STATUS: + case 45: // EOF_SERVER_STATUS: if (packet.index == 0) { packet.serverStatus = 0; } @@ -519,7 +519,7 @@ Parser.prototype.write = function(buffer) { } } break; - case Parser.COLUMN_VALUE_LENGTH: + case 46: // COLUMN_VALUE_LENGTH: if (packet.index == 0) { packet.columnLength = 0; packet.type = Parser.ROW_DATA_PACKET; @@ -548,7 +548,7 @@ Parser.prototype.write = function(buffer) { } } break; - case Parser.COLUMN_VALUE_STRING: + case 47: // COLUMN_VALUE_STRING: var remaining = packet.columnLength - packet.index, read; if (i + remaining > buffer.length) { read = buffer.length - i; diff --git a/lib/mysql/query.js b/lib/mysql/query.js index 983bc8c4f..3bcd01e61 100644 --- a/lib/mysql/query.js +++ b/lib/mysql/query.js @@ -8,6 +8,7 @@ var sys = require('./sys'), function Query(properties) { EventEmitter.call(this); + this.sql = null; this.typeCast = true; for (var key in properties) { @@ -30,6 +31,7 @@ Query.prototype._handlePacket = function(packet) { this.emit('end', Client._packetToUserObject(packet)); break; case Parser.ERROR_PACKET: + packet.sql = this.sql; this.emit('error', Client._packetToUserObject(packet)); break; case Parser.FIELD_PACKET: @@ -52,56 +54,60 @@ Query.prototype._handlePacket = function(packet) { } break; case Parser.ROW_DATA_PACKET: - var row = this._row = {}, - field = this._fields[0]; + var row = this._row = {}, field; this._rowIndex = 0; - row[field.name] = ''; packet.on('data', function(buffer, remaining) { + if (!field) { + field = self._fields[self._rowIndex]; + row[field.name] = ''; + } + if (buffer) { row[field.name] += buffer.toString('utf-8'); } else { row[field.name] = null; } - if (remaining == 0) { - self._rowIndex++; - if (self.typeCast && buffer !== null) { - switch (field.fieldType) { - case Query.FIELD_TYPE_TIMESTAMP: - case Query.FIELD_TYPE_DATE: - case Query.FIELD_TYPE_DATETIME: - case Query.FIELD_TYPE_NEWDATE: - row[field.name] = new Date(row[field.name]+'Z'); - break; - case Query.FIELD_TYPE_TINY: - case Query.FIELD_TYPE_SHORT: - case Query.FIELD_TYPE_LONG: - case Query.FIELD_TYPE_LONGLONG: - case Query.FIELD_TYPE_INT24: - case Query.FIELD_TYPE_YEAR: - row[field.name] = parseInt(row[field.name], 10); - break; - case Query.FIELD_TYPE_DECIMAL: - case Query.FIELD_TYPE_FLOAT: - case Query.FIELD_TYPE_DOUBLE: - case Query.FIELD_TYPE_NEWDECIMAL: - row[field.name] = parseFloat(row[field.name]); - break; - } - } + if (remaining !== 0) { + return; + } - if (self._rowIndex == self._fields.length) { - delete self._row; - delete self._rowIndex; - self.emit('row', row); - return; + self._rowIndex++; + if (self.typeCast && buffer !== null) { + switch (field.fieldType) { + case Query.FIELD_TYPE_TIMESTAMP: + case Query.FIELD_TYPE_DATE: + case Query.FIELD_TYPE_DATETIME: + case Query.FIELD_TYPE_NEWDATE: + row[field.name] = new Date(row[field.name]+'Z'); + break; + case Query.FIELD_TYPE_TINY: + case Query.FIELD_TYPE_SHORT: + case Query.FIELD_TYPE_LONG: + case Query.FIELD_TYPE_LONGLONG: + case Query.FIELD_TYPE_INT24: + case Query.FIELD_TYPE_YEAR: + row[field.name] = parseInt(row[field.name], 10); + break; + case Query.FIELD_TYPE_DECIMAL: + case Query.FIELD_TYPE_FLOAT: + case Query.FIELD_TYPE_DOUBLE: + case Query.FIELD_TYPE_NEWDECIMAL: + row[field.name] = parseFloat(row[field.name]); + break; } + } - field = self._fields[self._rowIndex]; - row[field.name] = ''; + if (self._rowIndex == self._fields.length) { + delete self._row; + delete self._rowIndex; + self.emit('row', row); + return; } + + field = null; }); break; } diff --git a/package.json b/package.json index 3150ad4d8..ace6ea2b8 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "name" : "mysql" -, "version": "0.8.0" +, "version": "0.9.0" , "devDependencies": {"gently": ">=0.8.0"} , "main" : "./lib/mysql" , "scripts" : { "test" : "make test" } diff --git a/test/common.js b/test/common.js index 47c8e88da..191182ee4 100644 --- a/test/common.js +++ b/test/common.js @@ -2,7 +2,8 @@ var path = require('path'); require.paths.unshift(path.dirname(__dirname)+'/lib'); var sys = require('mysql/sys'); -if (module.parent.filename.match(/test\/system/)) { +var parent = module.parent.filename; +if (parent.match(/test\/system/) || parent.match(/benchmark/)) { try { global.TEST_CONFIG = require('./config'); } catch (e) { diff --git a/test/simple/test-client.js b/test/simple/test-client.js index ca5cbee45..e5d6e099c 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -4,7 +4,7 @@ var StreamStub = GENTLY.stub('net', 'Stream'), OutgoingPacketStub = GENTLY.stub('./outgoing_packet'), QueryStub = GENTLY.stub('./query'), Parser = require('mysql/parser'), - netBinding = process.binding('net'); + netConstants = require('mysql/net_constants'); for (var k in Parser) { ParserStub[k] = Parser[k]; @@ -119,7 +119,7 @@ test(function connect() { (function testConnectionRefusedError() { var ERR = new Error('ouch'); - ERR.errno = netBinding.ECONNREFUSED; + ERR.errno = netConstants.ECONNREFUSED; CB_DELEGATE = gently.expect(function connectCallback(err) { assert.strictEqual(err, ERR); @@ -197,6 +197,7 @@ test(function query() { QUERY = this; assert.equal(properties.typeCast, client.typeCast); + assert.equal(properties.sql, FORMATED_SQL); var events = ['error', 'field', 'row', 'end']; gently.expect(QUERY, 'on', events.length, function (event, fn) { diff --git a/test/simple/test-query.js b/test/simple/test-query.js index 7a64cccd2..a62a089cd 100644 --- a/test/simple/test-query.js +++ b/test/simple/test-query.js @@ -16,6 +16,7 @@ function test(test) { test(function constructor() { assert.ok(query instanceof EventEmitter); assert.strictEqual(query.typeCast, true); + assert.strictEqual(query.sql, null); assert.equal(new Query({foo: 'bar'}).foo, 'bar'); }); @@ -41,6 +42,7 @@ test(function _handlePacket() { gently.expect(ClientStub, '_packetToUserObject', function (packet) { assert.strictEqual(packet, PACKET); + assert.strictEqual(packet.sql, query.sql); return USER_OBJECT; }); @@ -49,6 +51,7 @@ test(function _handlePacket() { assert.strictEqual(packet, USER_OBJECT); }); + query.sql = 'SELECT bla FROM foo'; query._handlePacket(PACKET); })(); diff --git a/test/system/test-client-connection-error.js b/test/system/test-client-connection-error.js index b61f48fdc..0201a876b 100644 --- a/test/system/test-client-connection-error.js +++ b/test/system/test-client-connection-error.js @@ -2,8 +2,9 @@ require('../common'); var Client = require('mysql').Client, client = Client(TEST_CONFIG), gently = new Gently(), - ECONNREFUSED = process.binding('net').ECONNREFUSED; - ENOTFOUND = process.binding('net').ENOTFOUND; + netConstants = require('mysql/net_constants'); + ECONNREFUSED = netConstants.ECONNREFUSED, + ENOTFOUND = netConstants.ENOTFOUND; client.host = 'BADHOST'; diff --git a/test/system/test-client-query-error.js b/test/system/test-client-query-error.js index 90e0f0f8c..82ec38592 100644 --- a/test/system/test-client-query-error.js +++ b/test/system/test-client-query-error.js @@ -1,5 +1,7 @@ require('../common'); -var Client = require('mysql').Client; +var Client = require('mysql').Client, + INVALID_QUERY_1 = 'first invalid #*&% query', + INVALID_QUERY_2 = 'another #*&% wrong query'; (function testErrorCallback() { // The query callback should receive an error, @@ -12,8 +14,9 @@ var Client = require('mysql').Client; if (error) throw error; })); - client.query('invalid #*&% query', [], gently.expect(function queryCb(error) { + client.query(INVALID_QUERY_1, [], gently.expect(function queryCb(error) { assert.ok(error); + assert.strictEqual(error.sql, INVALID_QUERY_1); client.end(); gently.verify('testErrorCallback'); })); @@ -31,10 +34,11 @@ var Client = require('mysql').Client; if (error) throw error; })); - query = client.query('invalid #*&% query'); + query = client.query(INVALID_QUERY_2); query.on('error', gently.expect(function errCb(error) { assert.ok(error); + assert.strictEqual(error.sql, INVALID_QUERY_2); client.end(); gently.verify('testQueryError'); })); @@ -52,10 +56,11 @@ var Client = require('mysql').Client; if (error) throw error; })); - query = client.query('invalid #*&% query'); + query = client.query(INVALID_QUERY_1); client.on('error', gently.expect(function errCb(error) { assert.ok(error); + assert.strictEqual(error.sql, INVALID_QUERY_1); client.end(); gently.verify('testClientError'); })); @@ -76,13 +81,14 @@ var Client = require('mysql').Client; if (error) throw error; })); - query = client.query('invalid #*&% query'); + query = client.query(INVALID_QUERY_2); query.on('error', dummyHandler); query.removeListener('error', dummyHandler); client.on('error', gently.expect(function errCb(error) { assert.ok(error); + assert.strictEqual(error.sql, INVALID_QUERY_2); client.end(); gently.verify('testRemoveListener'); })); @@ -99,8 +105,9 @@ var Client = require('mysql').Client; if (error) throw error; })); - client.query('invalid #*&% query', [], gently.expect(function queryCb(error) { + client.query(INVALID_QUERY_1, [], gently.expect(function queryCb(error) { assert.ok(error); + assert.strictEqual(error.sql, INVALID_QUERY_1); })); client.query('SHOW STATUS', [], gently.expect(function queryCb(error, rows, fields) { @@ -109,12 +116,14 @@ var Client = require('mysql').Client; assert.equal(Object.keys(fields).length, 2); })); - client.query('invalid #*&% query', [], gently.expect(function queryCb(error) { + client.query(INVALID_QUERY_1, [], gently.expect(function queryCb(error) { assert.ok(error); + assert.strictEqual(error.sql, INVALID_QUERY_1); })); - client.query('invalid #*&% query', [], gently.expect(function errCb(error) { + client.query(INVALID_QUERY_2, [], gently.expect(function errCb(error) { assert.ok(error); + assert.strictEqual(error.sql, INVALID_QUERY_2); client.end(); gently.verify('testSerialError'); }));