mysql
Advanced tools
Comparing version 2.0.0-alpha3 to 2.0.0-alpha4
@@ -7,2 +7,21 @@ # Changes | ||
## v2.0.0-alpha4 (2012-10-03) | ||
* Fix some OOB errors on resume() | ||
* Fix quick pause() / resume() usage | ||
* Properly parse host denied / similar errors | ||
* Add Connection.ChangeUser functionality | ||
* Make sure changeUser errors are fatal | ||
* Enable formatting nested arrays for bulk inserts | ||
* Add Connection.escape functionality | ||
* Renamed 'close' to 'end' event | ||
* Return parsed object instead of Buffer for GEOMETRY types | ||
* Allow nestTables inline (using a string instead of a boolean) | ||
* Check for ZEROFILL_FLAG and format number accordingly | ||
* Add timezone support (default: local) | ||
* Add custom typeCast functionality | ||
* Export mysql column types | ||
* Add connection flags functionality (#237) | ||
* Exports drain event when queue finishes processing (#272, #271, #306) | ||
## v2.0.0-alpha3 (2012-06-12) | ||
@@ -9,0 +28,0 @@ |
@@ -1,5 +0,10 @@ | ||
var Connection = require('./lib/Connection'); | ||
var Connection = require('./lib/Connection'); | ||
var ConnectionConfig = require('./lib/ConnectionConfig'); | ||
var Types = require('./lib/protocol/constants/types'); | ||
exports.createConnection = function(config) { | ||
return new Connection({config: config}); | ||
return new Connection({config: new ConnectionConfig(config)}); | ||
}; | ||
exports.escape = require('./lib/protocol/SqlString').escape; | ||
exports.Types = Types; |
@@ -1,7 +0,7 @@ | ||
var Net = require('net'); | ||
var Config = require('./Config'); | ||
var Protocol = require('./protocol/Protocol'); | ||
var SqlString = require('./protocol/SqlString'); | ||
var EventEmitter = require('events').EventEmitter; | ||
var Util = require('util'); | ||
var Net = require('net'); | ||
var ConnectionConfig = require('./ConnectionConfig'); | ||
var Protocol = require('./protocol/Protocol'); | ||
var SqlString = require('./protocol/SqlString'); | ||
var EventEmitter = require('events').EventEmitter; | ||
var Util = require('util'); | ||
@@ -13,5 +13,6 @@ module.exports = Connection; | ||
this.config = new Config(options.config); | ||
this.config = options.config; | ||
this._socket = options.socket; | ||
this._timezone = options.config.timezone; | ||
this._protocol = new Protocol({config: this.config}); | ||
@@ -25,7 +26,5 @@ this._connectCalled = false; | ||
if (!this._socket) { | ||
this._socket = (this.config.socketPath) | ||
? Net.createConnection(this.config.socketPath) | ||
: Net.createConnection(this.config.port, this.config.host); | ||
} | ||
this._socket = (this.config.socketPath) | ||
? Net.createConnection(this.config.socketPath) | ||
: Net.createConnection(this.config.port, this.config.host); | ||
@@ -37,3 +36,4 @@ this._socket.pipe(this._protocol); | ||
this._protocol.on('unhandledError', this._handleProtocolError.bind(this)); | ||
this._protocol.on('close', this._handleProtocolClose.bind(this)); | ||
this._protocol.on('drain', this._handleProtocolDrain.bind(this)); | ||
this._protocol.on('end', this._handleProtocolEnd.bind(this)); | ||
} | ||
@@ -44,2 +44,23 @@ | ||
Connection.prototype.changeUser = function(options, cb){ | ||
this._implyConnect(); | ||
if (typeof options === 'function') { | ||
cb = options; | ||
options = {}; | ||
} | ||
var charsetNumber = (options.charset) | ||
? Config.getCharsetNumber(options.charset) | ||
: this.config.charsetNumber; | ||
return this._protocol.changeUser({ | ||
user : options.user || this.config.user, | ||
password : options.password || this.config.password, | ||
database : options.database || this.config.database, | ||
charsetNumber : charsetNumber, | ||
currentConfig : this.config | ||
}, cb); | ||
}; | ||
Connection.prototype.query = function(sql, values, cb) { | ||
@@ -69,2 +90,3 @@ this._implyConnect(); | ||
options.sql = this.format(options.sql, values || []); | ||
options.timeZone = this._timezone; | ||
@@ -100,7 +122,7 @@ if (!('typeCast' in options)) { | ||
Connection.prototype.escape = function(value) { | ||
return SqlString.escape(value); | ||
return SqlString.escape(value, true, this._timezone); | ||
}; | ||
Connection.prototype.format = function(sql, values) { | ||
return SqlString.format(sql, values); | ||
return SqlString.format(sql, values, this._timezone); | ||
}; | ||
@@ -116,6 +138,10 @@ | ||
Connection.prototype._handleProtocolClose = function(err) { | ||
this.emit('close', err); | ||
Connection.prototype._handleProtocolDrain = function(err) { | ||
this.emit('drain', err); | ||
}; | ||
Connection.prototype._handleProtocolEnd = function(err) { | ||
this.emit('end', err); | ||
}; | ||
Connection.prototype._implyConnect = function() { | ||
@@ -122,0 +148,0 @@ if (!this._connectCalled) { |
@@ -13,7 +13,12 @@ module.exports = ErrorPacket; | ||
ErrorPacket.prototype.parse = function(parser) { | ||
this.fieldCount = parser.parseUnsignedNumber(1); | ||
this.errno = parser.parseUnsignedNumber(2); | ||
this.sqlStateMarker = parser.parseString(1); | ||
this.sqlState = parser.parseString(5); | ||
this.message = parser.parsePacketTerminatedString(); | ||
this.fieldCount = parser.parseUnsignedNumber(1); | ||
this.errno = parser.parseUnsignedNumber(2); | ||
// sqlStateMarker ('#' = 0x23) indicates error packet format | ||
if (parser.peak() === 0x23) { | ||
this.sqlStateMarker = parser.parseString(1); | ||
this.sqlState = parser.parseString(5); | ||
} | ||
this.message = parser.parsePacketTerminatedString(); | ||
}; | ||
@@ -24,5 +29,9 @@ | ||
writer.writeUnsignedNumber(2, this.errno); | ||
writer.writeString(this.sqlStateMarker || '#'); | ||
writer.writeString(this.sqlState || '12345'); | ||
if (this.sqlStateMarker) { | ||
writer.writeString(this.sqlStateMarker); | ||
writer.writeString(this.sqlState); | ||
} | ||
writer.writeString(this.message); | ||
}; |
@@ -19,2 +19,3 @@ module.exports = FieldPacket; | ||
this.default = options.default; | ||
this.zeroFill = options.zeroFill; | ||
} | ||
@@ -37,2 +38,5 @@ | ||
// parsed flags | ||
this.zeroFill = (this.flags & 0x0040 ? true : false); | ||
if (parser.reachedPacketEnd()) { | ||
@@ -39,0 +43,0 @@ return; |
@@ -45,5 +45,10 @@ module.exports = HandshakeInitializationPacket; | ||
// According to the docs this should be 0x00 terminated, but MariaDB does | ||
// not do this, so we assume this string to be packet terminated. Any trailing | ||
// 0x00 byte will be automatically ignored. | ||
// not do this, so we assume this string to be packet terminated. | ||
this.pluginData = parser.parsePacketTerminatedString(); | ||
// However, if there is a trailing '\0', strip it | ||
var lastChar = this.pluginData.length - 1; | ||
if (this.pluginData[lastChar] === '\0') { | ||
this.pluginData = this.pluginData.substr(0, lastChar); | ||
} | ||
}; | ||
@@ -50,0 +55,0 @@ |
@@ -18,2 +18,9 @@ module.exports = OkPacket; | ||
this.message = parser.parsePacketTerminatedString(); | ||
this.changedRows = 0; | ||
var m = this.message.match(/\schanged:\s*(\d+)/i); | ||
if (m !== null) { | ||
this.changedRows = parseInt(m[1], 10); | ||
} | ||
}; | ||
@@ -20,0 +27,0 @@ |
@@ -8,11 +8,24 @@ var Types = require('../constants/types'); | ||
RowDataPacket.prototype.parse = function(parser, fieldPackets, typeCast, nestTables) { | ||
RowDataPacket.prototype.parse = function(parser, fieldPackets, typeCast, nestTables, timeZone) { | ||
var self = this; | ||
for (var i = 0; i < fieldPackets.length; i++) { | ||
var fieldPacket = fieldPackets[i]; | ||
var value; | ||
var value = (typeCast) | ||
? this._typeCast(fieldPacket, parser) | ||
: parser.parseLengthCodedString(); | ||
if (typeof typeCast == "function") { | ||
value = typeCast(fieldPacket, parser, timeZone, function () { | ||
return self._typeCast(fieldPacket, parser, timeZone); | ||
}); | ||
} else { | ||
value = (typeCast) | ||
? this._typeCast(fieldPacket, parser, timeZone) | ||
: ( (fieldPacket.charsetNr === Charsets.BINARY) | ||
? parser.parseLengthCodedBuffer() | ||
: parser.parseLengthCodedString() ); | ||
} | ||
if (nestTables) { | ||
if (typeof nestTables == "string" && nestTables.length) { | ||
this[fieldPacket.table + nestTables + fieldPacket.name] = value; | ||
} else if (nestTables) { | ||
this[fieldPacket.table] = this[fieldPacket.table] || {}; | ||
@@ -26,3 +39,3 @@ this[fieldPacket.table][fieldPacket.name] = value; | ||
RowDataPacket.prototype._typeCast = function(field, parser) { | ||
RowDataPacket.prototype._typeCast = function(field, parser, timeZone) { | ||
switch (field.type) { | ||
@@ -34,5 +47,17 @@ case Types.TIMESTAMP: | ||
var dateString = parser.parseLengthCodedString(); | ||
return (dateString === null) | ||
? dateString | ||
: new Date(dateString); | ||
if (dateString === null) { | ||
return null; | ||
} | ||
// Otherwise JS will assume the string to be in GMT rather than local | ||
// time which is not what we want here. We always try to treat date | ||
// objects and strings as if they were in local time. | ||
if (field.type === Types.DATE) { | ||
dateString += ' 00:00:00'; | ||
} else if (timeZone != 'local') { | ||
// no timezone for date columns, there's no time.. so there's no time..zone | ||
dateString += timeZone; | ||
} | ||
return new Date(dateString); | ||
case Types.TINY: | ||
@@ -46,3 +71,3 @@ case Types.SHORT: | ||
var numberString = parser.parseLengthCodedString(); | ||
return (numberString === null) | ||
return (numberString === null || (field.zeroFill && numberString[0] == "0")) | ||
? numberString | ||
@@ -61,2 +86,4 @@ : Number(numberString); | ||
: parser.parseLengthCodedString(); | ||
case Types.GEOMETRY: | ||
return parser.parseGeometryValue(); | ||
default: | ||
@@ -63,0 +90,0 @@ return parser.parseLengthCodedString(); |
@@ -9,3 +9,3 @@ var IEEE_754_BINARY_64_PRECISION = Math.pow(2, 53); | ||
this._buffer = null; | ||
this._buffer = new Buffer(0); | ||
this._longPacketBuffers = []; | ||
@@ -15,3 +15,3 @@ this._offset = 0; | ||
this._packetHeader = null; | ||
this._packetParser = options.packetParser || function() {}; | ||
this._onPacket = options.onPacket || function() {}; | ||
this._nextPacketNumber = 0; | ||
@@ -51,10 +51,25 @@ this._encoding = 'utf-8'; | ||
this._longPacketBuffers.push(this._buffer.slice(this._offset, this._packetEnd)); | ||
} else { | ||
this._combineLongPacketBuffers(); | ||
this._packetParser(this._packetHeader); | ||
this._advanceToNextPacket(); | ||
continue; | ||
} | ||
this._offset = this._packetEnd; | ||
this._packetHeader = null; | ||
this._packetEnd = null; | ||
this._combineLongPacketBuffers(); | ||
// Try...finally to ensure exception safety. Unfortunately this is costing | ||
// us up to ~10% performance in some benchmarks. | ||
var hadException = true; | ||
try { | ||
this._onPacket(this._packetHeader); | ||
hadException = false; | ||
} finally { | ||
this._advanceToNextPacket(); | ||
// If we had an exception, the parser while loop will be broken out | ||
// of after the finally block. So we need to make sure to re-enter it | ||
// to continue parsing any bytes that may already have been received. | ||
if (hadException) { | ||
process.nextTick(this.write.bind(this)); | ||
} | ||
} | ||
} | ||
@@ -64,8 +79,8 @@ }; | ||
Parser.prototype.append = function(newBuffer) { | ||
var oldBuffer = this._buffer; | ||
if (!oldBuffer) { | ||
this._buffer = newBuffer; | ||
// If resume() is called, we don't pass a buffer to write() | ||
if (!newBuffer) { | ||
return; | ||
} | ||
var oldBuffer = this._buffer; | ||
var bytesRemaining = this._bytesRemaining(); | ||
@@ -91,4 +106,6 @@ var newLength = bytesRemaining + newBuffer.length; | ||
this._paused = false; | ||
// A little hacky, but does the trick of resuming the parser | ||
this.write(new Buffer(0)); | ||
// nextTick() to avoid entering write() multiple times within the same stack | ||
// which would cause problems as write manipulates the state of the object. | ||
process.nextTick(this.write.bind(this)); | ||
}; | ||
@@ -226,2 +243,59 @@ | ||
Parser.prototype.parseGeometryValue = function() { | ||
var buffer = this.parseLengthCodedBuffer(); | ||
var offset = 4; | ||
if (buffer === null) { | ||
return null; | ||
} | ||
function parseGeometry() { | ||
var result = null; | ||
var byteOrder = buffer.readUInt8(offset); offset += 1; | ||
var wkbType = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4; | ||
switch(wkbType) { | ||
case 1: // WKBPoint | ||
var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8; | ||
var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8; | ||
result = {x: x, y: y}; | ||
break; | ||
case 2: // WKBLineString | ||
var numPoints = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4; | ||
result = []; | ||
for(var i=numPoints;i>0;i--) { | ||
var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8; | ||
var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8; | ||
result.push({x: x, y: y}); | ||
} | ||
break; | ||
case 3: // WKBPolygon | ||
var numRings = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4; | ||
result = []; | ||
for(var i=numRings;i>0;i--) { | ||
var numPoints = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4; | ||
var line = []; | ||
for(var j=numPoints;j>0;j--) { | ||
var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8; | ||
var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8; | ||
line.push({x: x, y: y}); | ||
} | ||
result.push(line); | ||
} | ||
break; | ||
case 4: // WKBMultiPoint | ||
case 5: // WKBMultiLineString | ||
case 6: // WKBMultiPolygon | ||
case 7: // WKBGeometryCollection | ||
var num = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4; | ||
var result = []; | ||
for(var i=num;i>0;i--) { | ||
result.push(parseGeometry()); | ||
} | ||
break; | ||
} | ||
return result; | ||
} | ||
return parseGeometry(); | ||
} | ||
Parser.prototype.reachedPacketEnd = function() { | ||
@@ -292,1 +366,7 @@ return this._offset === this._packetEnd; | ||
}; | ||
Parser.prototype._advanceToNextPacket = function() { | ||
this._offset = this._packetEnd; | ||
this._packetHeader = null; | ||
this._packetEnd = null; | ||
}; |
@@ -19,24 +19,16 @@ var Parser = require('./Parser'); | ||
this._parser = new Parser({packetParser: this._parsePacket.bind(this)}); | ||
this._config = options.config || {}; | ||
this._callback = null; | ||
this._fatalError = null; | ||
this._quitSequence = null; | ||
this._handshakeSequence = null; | ||
this._destroyed = false; | ||
this._queue = []; | ||
this._parser = new Parser({onPacket: this._parsePacket.bind(this)}); | ||
this._config = options.config || {}; | ||
this._callback = null; | ||
this._fatalError = null; | ||
this._quitSequence = null; | ||
this._handshakeSequence = null; | ||
this._destroyed = false; | ||
this._queue = []; | ||
this._handshakeInitializationPacket = null; | ||
} | ||
Protocol.prototype.write = function(buffer) { | ||
try { | ||
this._parser.write(buffer); | ||
return true; | ||
} catch (err) { | ||
err.code = err.code || 'PROTOCOL_PARSER_EXCEPTION'; | ||
err.fatal = true; | ||
this._delegateError(err); | ||
} | ||
return false; | ||
this._parser.write(buffer); | ||
return true; | ||
}; | ||
@@ -52,2 +44,6 @@ | ||
Protocol.prototype.changeUser = function(options, cb) { | ||
return this._enqueue(new Sequences.ChangeUser(options, cb)); | ||
}; | ||
Protocol.prototype.quit = function(cb) { | ||
@@ -61,3 +57,3 @@ return this._quitSequence = this._enqueue(new Sequences.Quit(cb)); | ||
this._quitSequence.end(); | ||
this.emit('close'); | ||
this.emit('end'); | ||
return; | ||
@@ -159,4 +155,7 @@ } | ||
if (Packet === Packets.HandshakeInitializationPacket) { | ||
this._handshakeInitializationPacket = packet; | ||
} | ||
sequence[Packet.name](packet); | ||
}; | ||
@@ -202,6 +201,15 @@ | ||
var sequence = this._queue[0]; | ||
if (sequence) { | ||
this._parser.resetPacketNumber(); | ||
sequence.start(); | ||
if (!sequence) { | ||
this.emit('drain'); | ||
return; | ||
} | ||
this._parser.resetPacketNumber(); | ||
if (sequence.constructor == Sequences.ChangeUser) { | ||
sequence.start(this._handshakeInitializationPacket); | ||
return; | ||
} | ||
sequence.start(); | ||
}; | ||
@@ -243,3 +251,3 @@ | ||
if (err.fatal) { | ||
this.emit('close', err); | ||
this.emit('end', err); | ||
} | ||
@@ -246,0 +254,0 @@ }; |
@@ -18,2 +18,3 @@ var Sequence = require('./Sequence'); | ||
this.nestTables = options.nestTables || false; | ||
this.timeZone = options.timeZone || "Z"; | ||
@@ -63,11 +64,14 @@ this._resultSet = null; | ||
Query.prototype['OkPacket'] = function(packet) { | ||
if (!this._callback) { | ||
this.emit('result', packet, this._index); | ||
} else { | ||
this._results.push(packet); | ||
this._fields.push(undefined); | ||
// try...finally for exception safety | ||
try { | ||
if (!this._callback) { | ||
this.emit('result', packet, this._index); | ||
} else { | ||
this._results.push(packet); | ||
this._fields.push(undefined); | ||
} | ||
} finally { | ||
this._index++; | ||
this._handleFinalResultPacket(packet); | ||
} | ||
this._index++; | ||
this._handleFinalResultPacket(packet); | ||
}; | ||
@@ -141,3 +145,3 @@ | ||
Query.prototype['RowDataPacket'] = function(packet, parser) { | ||
packet.parse(parser, this._resultSet.fieldPackets, this.typeCast, this.nestTables); | ||
packet.parse(parser, this._resultSet.fieldPackets, this.typeCast, this.nestTables, this.timeZone); | ||
@@ -144,0 +148,0 @@ if (this._callback) { |
@@ -53,4 +53,2 @@ var Util = require('util'); | ||
this._ended = true; | ||
var self = this; | ||
var args = arguments; | ||
@@ -61,15 +59,16 @@ if (err) { | ||
// Escape stack (so try..catch in Protocol#write does not interfer here) | ||
process.nextTick(function() { | ||
// try...finally for exception safety | ||
try { | ||
if (err) { | ||
self.emit('error', err); | ||
this.emit('error', err); | ||
} | ||
if (self._callback) { | ||
self._callback.apply(self, args); | ||
} finally { | ||
try { | ||
if (this._callback) { | ||
this._callback.apply(this, arguments); | ||
} | ||
} finally { | ||
this.emit('end'); | ||
} | ||
self.emit('end'); | ||
}); | ||
} | ||
}; | ||
@@ -82,4 +81,3 @@ | ||
Sequence.prototype['ErrorPacket'] = function(packet) { | ||
var err = Sequence.packetToError(packet); | ||
this.end(err); | ||
this.end(this._packetToError(packet)); | ||
}; | ||
@@ -86,0 +84,0 @@ |
var SqlString = exports; | ||
SqlString.escape = function(val, stringifyObjects) { | ||
SqlString.escape = function(val, stringifyObjects, timeZone) { | ||
if (val === undefined || val === null) { | ||
@@ -14,3 +14,3 @@ return 'NULL'; | ||
if (val instanceof Date) { | ||
val = SqlString.dateToString(val); | ||
val = SqlString.dateToString(val, timeZone || "Z"); | ||
} | ||
@@ -23,3 +23,3 @@ | ||
if (Array.isArray(val)) { | ||
return val.map(SqlString.escape).join(', '); | ||
return SqlString.arrayToList(val, timeZone); | ||
} | ||
@@ -31,3 +31,3 @@ | ||
} else { | ||
return SqlString.objectToValues(val); | ||
return SqlString.objectToValues(val, timeZone); | ||
} | ||
@@ -50,9 +50,10 @@ } | ||
function zeroPad(number) { | ||
return (number < 10) | ||
? '0' + number | ||
: number; | ||
} | ||
SqlString.arrayToList = function(array, timeZone) { | ||
return array.map(function(v) { | ||
if (Array.isArray(v)) return '(' + SqlString.arrayToList(v) + ')'; | ||
return SqlString.escape(v, true, timeZone); | ||
}).join(', '); | ||
}; | ||
SqlString.format = function(sql, values) { | ||
SqlString.format = function(sql, values, timeZone) { | ||
values = [].concat(values); | ||
@@ -65,14 +66,25 @@ | ||
return SqlString.escape(values.shift()); | ||
return SqlString.escape(values.shift(), false, timeZone); | ||
}); | ||
}; | ||
SqlString.dateToString = function(date) { | ||
var year = date.getFullYear(); | ||
var month = zeroPad(date.getMonth() + 1); | ||
var day = zeroPad(date.getDate()); | ||
var hour = zeroPad(date.getHours()); | ||
var minute = zeroPad(date.getMinutes()); | ||
var second = zeroPad(date.getSeconds()); | ||
SqlString.dateToString = function(date, timeZone) { | ||
var dt = new Date(date); | ||
if (timeZone != 'local') { | ||
tz = convertTimezone(timeZone); | ||
dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000)); | ||
if (tz !== false) { | ||
dt.setTime(dt.getTime() + (tz * 60000)); | ||
} | ||
} | ||
var year = dt.getFullYear(); | ||
var month = zeroPad(dt.getMonth() + 1); | ||
var day = zeroPad(dt.getDate()); | ||
var hour = zeroPad(dt.getHours()); | ||
var minute = zeroPad(dt.getMinutes()); | ||
var second = zeroPad(dt.getSeconds()); | ||
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second; | ||
@@ -96,3 +108,3 @@ }; | ||
SqlString.objectToValues = function(object) { | ||
SqlString.objectToValues = function(object, timeZone) { | ||
var values = []; | ||
@@ -105,3 +117,3 @@ for (var key in object) { | ||
values.push('`' + key + '` = ' + SqlString.escape(value, true)); | ||
values.push('`' + key + '` = ' + SqlString.escape(value, true, timeZone)); | ||
} | ||
@@ -111,1 +123,15 @@ | ||
}; | ||
function zeroPad(number) { | ||
return (number < 10) ? '0' + number : number; | ||
} | ||
function convertTimezone(tz) { | ||
if (tz == "Z") return 0; | ||
var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/); | ||
if (m) { | ||
return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60; | ||
} | ||
return false; | ||
} |
@@ -5,3 +5,3 @@ { | ||
"description": "A node.js driver for mysql. It is written in JavaScript, does not require compiling, and is 100% MIT licensed.", | ||
"version": "2.0.0-alpha3", | ||
"version": "2.0.0-alpha4", | ||
"repository": { | ||
@@ -8,0 +8,0 @@ "url": "" |
179
Readme.md
# node-mysql | ||
[![Build Status](https://secure.travis-ci.org/felixge/node-mysql.png?branch=v2.0)](http://travis-ci.org/felixge/node-mysql) | ||
[![Build Status](https://secure.travis-ci.org/felixge/node-mysql.png)](http://travis-ci.org/felixge/node-mysql) | ||
@@ -8,3 +8,3 @@ ## Install | ||
```bash | ||
npm install mysql@2.0.0-alpha3 | ||
npm install mysql@2.0.0-alpha4 | ||
``` | ||
@@ -41,6 +41,6 @@ | ||
connection.query('SELECT 1', function(err, rows, fields) { | ||
connection.query('SELECT 1 + 1 AS solution', function(err, rows, fields) { | ||
if (err) throw err; | ||
console.log('Query result: ', rows); | ||
console.log('The solution is: ', rows[0].solution); | ||
}); | ||
@@ -71,2 +71,3 @@ | ||
[Andrey Hristov]: http://andrey.hristov.com/ | ||
## Sponsors | ||
@@ -91,8 +92,8 @@ | ||
So far all community activity has happened via the GitHub Issue system, however | ||
additionally I have just started a mailing list and IRC channel where people | ||
can ask questions and discuss things: | ||
If you'd like to discuss this module, or ask questions about it, please use one | ||
of the following: | ||
* **Mailing list**: https://groups.google.com/forum/#!forum/node-mysql | ||
* **IRC Channel**: #node-mysql (on freenode.net) | ||
* **IRC Channel**: #node.js (on freenode.net, I pay attention to any message | ||
including the term `mysql`) | ||
@@ -142,5 +143,6 @@ ## Establishing connections | ||
* `user`: The MySQL user to authenticate as. | ||
* `password`: The passqword of that MySQL user. | ||
* `password`: The password of that MySQL user. | ||
* `database`: Name of the database to use for this connection (Optional). | ||
* `charset`: The charset for the connection. (Default: `'UTF8_GENERAL_CI'`) | ||
* `timezone`: The timezone used to store local dates. (Default: `'local'`) | ||
* `insecureAuth`: Allow connecting to MySQL instances that ask for the old | ||
@@ -153,2 +155,4 @@ (insecure) authentication method. (Default: `false`) | ||
with this, it exposes you to SQL injection attacks. (Default: `false) | ||
* `flags`: List of connection flags to use other than the default ones. It is | ||
also possible to blacklist default ones. For more information, check [Connection Flags](#connection-flags). | ||
@@ -159,3 +163,3 @@ In addition to passing these options as an object, you can also use a url | ||
```js | ||
var connection = mysql.createConnection('mysql://user:pass@host/db?debug=true&charset=BIG5_CHINESE_CI'); | ||
var connection = mysql.createConnection('mysql://user:pass@host/db?debug=true&charset=BIG5_CHINESE_CI&timezone=-0700'); | ||
``` | ||
@@ -183,3 +187,3 @@ | ||
An alternative way to end the connection is to call the `destroy()` method. | ||
This will cause an immediate termination of the underlaying socket. | ||
This will cause an immediate termination of the underlying socket. | ||
Additionally `destroy()` guarantees that no more events or callbacks will be | ||
@@ -194,5 +198,29 @@ triggered for the connection. | ||
## Switching users / altering connection state | ||
MySQL offers a changeUser command that allows you to alter the current user and | ||
other aspects of the connection without shutting down the underlying socket: | ||
```js | ||
connection.changeUser({user : 'john'}, function(err) { | ||
if (err) throw err; | ||
}); | ||
``` | ||
The available options for this feature are: | ||
* `user`: The name of the new user (defaults to the previous one). | ||
* `password`: The password of the new user (defaults to the previous one). | ||
* `charset`: The new charset (defaults to the previous one). | ||
* `database`: The new database (defaults to the previous one). | ||
A sometimes useful side effect of this functionality is that this function also | ||
resets any connection state (variables, transactions, etc.). | ||
Errors encountered during this operation are treated as fatal connection errors | ||
by this module. | ||
## Server disconnects | ||
You may loose the connection to a MySQL server due to network problems, the | ||
You may lose the connection to a MySQL server due to network problems, the | ||
server timing you out, or the server crashing. All of these events are | ||
@@ -203,14 +231,24 @@ considered fatal errors, and will have the `err.code = | ||
The best way to be notified about a connection termination is to listen for the | ||
`'close'` event: | ||
The best way to handle such unexpected disconnects is shown below: | ||
```js | ||
connection.on('close', function(err) { | ||
if (err) { | ||
// We did not expect this connection to terminate | ||
function handleDisconnect(connection) { | ||
connection.on('error', function(err) { | ||
if (!err.fatal) { | ||
return; | ||
} | ||
if (err.code !== 'PROTOCOL_CONNECTION_LOST') { | ||
throw err; | ||
} | ||
console.log('Re-connecting lost connection: ' + err.stack); | ||
connection = mysql.createConnection(connection.config); | ||
} else { | ||
// We expected this to happen, end() was called. | ||
} | ||
}); | ||
handleDisconnect(connection); | ||
connection.connect(); | ||
}); | ||
} | ||
handleDisconnect(connection); | ||
``` | ||
@@ -222,6 +260,4 @@ | ||
Please note that you will also receive a `'close'` event with an `err` argument | ||
when a connection attempt fails because of bad credentials. If you find this | ||
cumbersome to work with, please post to the node-mysql mailing list to discuss | ||
improvements. | ||
This logic will also be part of connection pool support once I add that to this | ||
library. | ||
@@ -261,3 +297,5 @@ ## Escaping query values | ||
* Strings are safely escaped | ||
* Arrays are turned into list, e.g. ['a', 'b'] turns into `'a', 'b'` | ||
* Arrays are turned into list, e.g. `['a', 'b']` turns into `'a', 'b'` | ||
* Nested arrays are turned into grouped lists (for bulk inserts), e.g. `[['a', | ||
'b'], ['c', 'd']]` turns into `('a', 'b'), ('c', 'd')` | ||
* Objects are turned into `key = 'val'` pairs. Nested objects are cast to | ||
@@ -282,2 +320,11 @@ strings. | ||
If you feel the need to escape queries by yourself, you can also use the escaping | ||
function directly: | ||
```js | ||
var query = "SELECT * FROM posts WHERE title=" + mysql.escape("Hello MySQL"); | ||
console.log(query); // SELECT * FROM posts WHERE title='Hello MySQL' | ||
``` | ||
## Getting the id of an inserted row | ||
@@ -338,3 +385,3 @@ | ||
amount and size of your rows. | ||
* `pause()` / `resume()` operate on the underlaying socket and parser. You are | ||
* `pause()` / `resume()` operate on the underlying socket and parser. You are | ||
guaranteed that no more `'result'` events will fire after calling `pause()`. | ||
@@ -348,3 +395,3 @@ * You MUST NOT provide a callback to the `query()` method when streaming rows. | ||
have a good use case for streaming large fields to and from MySQL, I'd love to | ||
get your thoughts and conributions on this. | ||
get your thoughts and contributions on this. | ||
@@ -431,2 +478,18 @@ ## Multiple statement queries | ||
Or use a string separator to have your results merged. | ||
```js | ||
var options = {sql: '...', nestTables: '_'}; | ||
connection.query(options, function(err, results) { | ||
/* results will be an array like this now: | ||
[{ | ||
table1_fieldA: '...', | ||
table1_fieldB: '...', | ||
table2_fieldA: '...', | ||
table2_fieldB: '...' | ||
}, ...] | ||
*/ | ||
}); | ||
``` | ||
## Error handling | ||
@@ -442,3 +505,3 @@ | ||
`'ER_ACCESS_DENIED_ERROR'`), a node.js error (e.g. `'ECONNREFUSED'`) or an | ||
internal error (e.g. `'PROTOCOL_PARSER_EXCEPTION'`). | ||
internal error (e.g. `'PROTOCOL_CONNECTION_LOST'`). | ||
* `err.fatal`: Boolean, indicating if this error is terminal to the connection | ||
@@ -501,3 +564,3 @@ object. | ||
**tl;dr:** This module does not want you to to deal with silent failures. You | ||
**tl;dr:** This module does not want you to deal with silent failures. You | ||
should always provide callbacks to your method calls. If you want to ignore | ||
@@ -507,6 +570,12 @@ this advice and suppress unhandled errors, you can do this: | ||
```js | ||
// I am Chuck Noris: | ||
// I am Chuck Norris: | ||
connection.on('error', function() {}); | ||
``` | ||
## Exception Safety | ||
This module is exception safe. That means you can continue to use it, even if | ||
one of your callback functions throws an error which you're catching using | ||
'uncaughtException' or a domain. | ||
## Type casting | ||
@@ -541,3 +610,3 @@ | ||
* VARBINARY | ||
* BIT (last byte will be filled with 0 bits as neccessary) | ||
* BIT (last byte will be filled with 0 bits as necessary) | ||
@@ -575,2 +644,48 @@ ### String | ||
## Connection Flags | ||
If, for any reason, you would like to change the default connection flags, you | ||
can use the connection option `flags`. Pass a string with a comma separated list | ||
of items to add to the default flags. If you don't want a default flag to be used | ||
prepend the flag with a minus sign. To add a flag that is not in the default list, don't prepend it with a plus sign, just write the flag name (case insensitive). | ||
**Please note that some available flags that are not default are still not supported | ||
(e.g.: SSL, Compression). Use at your own risk.** | ||
### Example | ||
The next example blacklists FOUND_ROWS flag from default connection flags. | ||
```js | ||
var connection = mysql.createConnection("mysql://localhost/test?flags=-FOUND_ROWS") | ||
``` | ||
### Default Flags | ||
- LONG_PASSWORD | ||
- FOUND_ROWS | ||
- LONG_FLAG | ||
- CONNECT_WITH_DB | ||
- ODBC | ||
- LOCAL_FILES | ||
- IGNORE_SPACE | ||
- PROTOCOL_41 | ||
- IGNORE_SIGPIPE | ||
- TRANSACTIONS | ||
- RESERVED | ||
- SECURE_CONNECTION | ||
- MULTI_RESULTS | ||
- MULTI_STATEMENTS (used if `multipleStatements` option is activated) | ||
### Other Available Flags | ||
- NO_SCHEMA | ||
- COMPRESS | ||
- INTERACTIVE | ||
- SSL | ||
- PS_MULTI_RESULTS | ||
- PLUGIN_AUTH | ||
- SSL_VERIFY_SERVER_CERT | ||
- REMEMBER_OPTIONS | ||
## Debugging and reporting problems | ||
@@ -577,0 +692,0 @@ |
@@ -23,8 +23,11 @@ var common = exports; | ||
common.isTravis = function() { | ||
return Boolean(process.env.CI); | ||
}; | ||
common.createConnection = function(config) { | ||
var isTravis = Boolean(process.env.CI); | ||
if (isTravis) { | ||
if (common.isTravis()) { | ||
// see: http://about.travis-ci.org/docs/user/database-setup/ | ||
config = _.extend({ | ||
user: 'root', | ||
user: 'root' | ||
}, config); | ||
@@ -36,4 +39,4 @@ } else { | ||
user : process.env.MYSQL_USER, | ||
password : process.env.MYSQL_PASSWORD, | ||
}, config) | ||
password : process.env.MYSQL_PASSWORD | ||
}, config); | ||
} | ||
@@ -40,0 +43,0 @@ |
@@ -44,3 +44,3 @@ // An experimental fake MySQL server for tricky integration tests. Expanded | ||
this._socket = socket; | ||
this._parser = new Parser({packetParser: this._parsePacket.bind(this)}); | ||
this._parser = new Parser({onPacket: this._parsePacket.bind(this)}); | ||
@@ -47,0 +47,0 @@ this._handshakeInitializationPacket = null; |
@@ -1,1 +0,7 @@ | ||
require('urun')(__dirname) | ||
var options = {}; | ||
if (process.env.FILTER) { | ||
options.include = new RegExp(process.env.FILTER + '.*\\.js$'); | ||
} | ||
require('urun')(__dirname, options); |
@@ -40,2 +40,6 @@ var common = require('../../common'); | ||
'nested arrays are turned into grouped lists': function() { | ||
assert.equal(SqlString.escape([[1,2,3], [4,5,6], ['a', 'b', {nested: true}]]), "(1, 2, 3), (4, 5, 6), ('a', 'b', '[object Object]')"); | ||
}, | ||
'nested objects inside arrays are cast to strings': function() { | ||
@@ -73,3 +77,3 @@ assert.equal(SqlString.escape([1, {nested: true}, 2]), "1, '[object Object]', 2"); | ||
'\u001 (ascii 26) gets replaced with \\Z': function() { | ||
'\u001a (ascii 26) gets replaced with \\Z': function() { | ||
assert.equal(SqlString.escape('Sup\u001aer'), "'Sup\\Zer'"); | ||
@@ -88,3 +92,3 @@ }, | ||
var expected = '2012-05-07 11:42:03'; | ||
var date = new Date(expected); | ||
var date = new Date(Date.UTC(2012, 4, 7, 11, 42, 3)); | ||
var string = SqlString.escape(date); | ||
@@ -108,3 +112,3 @@ | ||
assert.equal(SqlString.escape(Infinity), 'Infinity'); | ||
}, | ||
} | ||
}); | ||
@@ -111,0 +115,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
202099
108
4831
695
24
4