mysql2 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Changelog.md CHANGED
@@ -1,17 +1,27 @@
1
+ 1.5.2 (06/02/2018)
2
+ - perf: Store Compiled Packet Parsers in a global
3
+ cache #722, #723
4
+ - Improve performance of removing connections from
5
+ pools #720
6
+ - use source parameters types with execute, fix
7
+ crash when parameter is undefined #718, #705
8
+ - PromisePool to always use the specified promises
9
+ library #697
10
+
1
11
  1.5.1 (19/11/2017)
2
- - Fix empty buffer incorrectly returned instead of
12
+ - Fix empty buffer incorrectly returned instead of
3
13
  NULL value #668, #671
4
- - promise wrapper: pass sqlMessage from original
14
+ - promise wrapper: pass sqlMessage from original
5
15
  error #682, #678
6
-
16
+
7
17
  1.5.0 (13/11/2017)
8
18
  - Added sqlMessage to Error callback object #665
9
19
  - Normalized sqlState to a string of 5 chars #667
10
20
  as Mysql specifies it
11
- - Remove destroyed promise pool connections from
21
+ - Remove destroyed promise pool connections from
12
22
  pool #674, #672
13
23
  - Expose escape & format methods on connection pool #669, #663
14
- - Support fractional seconds variable precision for
24
+ - Support fractional seconds variable precision for
15
25
  the temporal types #660, #659
16
26
  - fix null values breaking typeCast behaviour #652
17
27
 
package/index.js CHANGED
@@ -2,6 +2,7 @@ var SqlString = require('sqlstring');
2
2
 
3
3
  var Connection = require('./lib/connection.js');
4
4
  var ConnectionConfig = require('./lib/connection_config.js');
5
+ var parserCache = require("./lib/parsers/parser_cache");
5
6
 
6
7
  module.exports.createConnection = function(opts) {
7
8
  return new Connection({ config: new ConnectionConfig(opts) });
@@ -61,3 +62,11 @@ exports.__defineGetter__('Charsets', function() {
61
62
  exports.__defineGetter__('CharsetToEncoding', function() {
62
63
  return require('./lib/constants/charset_encodings.js');
63
64
  });
65
+
66
+ exports.setMaxParserCache = function (max) {
67
+ parserCache.setMaxCache(max);
68
+ };
69
+
70
+ exports.clearParserCache = function () {
71
+ parserCache.clearCache();
72
+ };
@@ -6,7 +6,7 @@ var Packets = require('../packets/index.js');
6
6
 
7
7
  var objectAssign = require('object-assign');
8
8
 
9
- var compileParser = require('../compile_binary_parser.js');
9
+ var getBinaryParser = require('../parsers/binary_parser.js');
10
10
 
11
11
  function Execute(options, callback) {
12
12
  Command.call(this);
@@ -33,13 +33,7 @@ function Execute(options, callback) {
33
33
  util.inherits(Execute, Command);
34
34
 
35
35
  Execute.prototype.buildParserFromFields = function(fields, connection) {
36
- var parserKey = connection.keyFromFields(fields, this.options);
37
- var parser = connection.binaryProtocolParsers[parserKey];
38
- if (!parser) {
39
- parser = compileParser(fields, this.options, connection.config);
40
- connection.binaryProtocolParsers[parserKey] = parser;
41
- }
42
- return parser;
36
+ return getBinaryParser(fields, this.options, connection.config);
43
37
  };
44
38
 
45
39
  Execute.prototype.start = function(packet, connection) {
@@ -50,7 +44,17 @@ Execute.prototype.start = function(packet, connection) {
50
44
  this.parameters,
51
45
  connection.config.charsetNumber
52
46
  );
53
- connection.writePacket(executePacket.toPacket(1));
47
+ //For reasons why this try-catch is here, please see
48
+ // https://github.com/sidorares/node-mysql2/pull/689
49
+ //For additional discussion, see
50
+ // 1. https://github.com/sidorares/node-mysql2/issues/493
51
+ // 2. https://github.com/sidorares/node-mysql2/issues/187
52
+ // 3. https://github.com/sidorares/node-mysql2/issues/480
53
+ try {
54
+ connection.writePacket(executePacket.toPacket(1));
55
+ } catch (error) {
56
+ this.onResult(error)
57
+ }
54
58
  return Execute.prototype.resultsetHeader;
55
59
  };
56
60
 
@@ -7,7 +7,7 @@ var objectAssign = require('object-assign');
7
7
 
8
8
  var Command = require('./command.js');
9
9
  var Packets = require('../packets/index.js');
10
- var compileParser = require('../compile_text_parser.js');
10
+ var getTextParser = require('../parsers/text_parser.js');
11
11
  var ServerStatus = require('../constants/server_status.js');
12
12
  var CharsetToEncoding = require('../constants/charset_encodings.js');
13
13
 
@@ -195,15 +195,10 @@ Query.prototype.readField = function(packet, connection) {
195
195
  }
196
196
 
197
197
  // last field received
198
- if (this._receivedFieldsCount == this._fieldCount) {
198
+ if (this._receivedFieldsCount === this._fieldCount) {
199
199
  var fields = this._fields[this._resultIndex];
200
200
  this.emit('fields', fields);
201
- var parserKey = connection.keyFromFields(fields, this.options);
202
- this._rowParser = connection.textProtocolParsers[parserKey];
203
- if (!this._rowParser) {
204
- this._rowParser = compileParser(fields, this.options, connection.config);
205
- connection.textProtocolParsers[parserKey] = this._rowParser;
206
- }
201
+ this._rowParser = getTextParser(fields, this.options, connection.config);
207
202
  return Query.prototype.fieldsEOF;
208
203
  }
209
204
  return Query.prototype.readField;
package/lib/connection.js CHANGED
@@ -58,19 +58,6 @@ function Connection(opts) {
58
58
  }
59
59
  });
60
60
 
61
- // TODO: make it lru cache
62
- // https://github.com/mercadolibre/node-simple-lru-cache
63
- // or https://github.com/rsms/js-lru
64
- // or https://github.com/monsur/jscache
65
- // or https://github.com/isaacs/node-lru-cache
66
- //
67
- // key is field.name + ':' + field.columnType + ':' field.flags + '/'
68
- this.textProtocolParsers = {};
69
-
70
- // TODO: not sure if cache should be separate (same key as with textProtocolParsers)
71
- // or part of prepared statements cache (key is sql query)
72
- this.binaryProtocolParsers = {};
73
-
74
61
  this.serverCapabilityFlags = 0;
75
62
  this.authorized = false;
76
63
 
@@ -639,25 +626,6 @@ Connection.prototype.resume = function resume() {
639
626
  this.stream.resume();
640
627
  };
641
628
 
642
- Connection.prototype.keyFromFields = function keyFromFields(fields, options) {
643
- var res =
644
- typeof options.nestTables +
645
- '/' +
646
- options.nestTables +
647
- '/' +
648
- options.rowsAsArray +
649
- options.supportBigNumbers +
650
- '/' +
651
- options.bigNumberStrings +
652
- '/' +
653
- typeof options.typeCast;
654
- for (var i = 0; i < fields.length; ++i) {
655
- res +=
656
- '/' + fields[i].name + ':' + fields[i].columnType + ':' + fields[i].flags;
657
- }
658
- return res;
659
- };
660
-
661
629
  Connection.statementKey = function(options) {
662
630
  return (
663
631
  typeof options.nestTables +
@@ -715,6 +683,18 @@ Connection.prototype.execute = function execute(sql, values, cb) {
715
683
  }
716
684
  this._resolveNamedPlaceholders(options);
717
685
 
686
+ // check for values containing undefined
687
+ if (options.values) {
688
+ options.values.forEach(function(val) {
689
+ if (val === undefined) {
690
+ throw new TypeError('Bind parameters must not contain undefined. To pass SQL NULL specify JS null');
691
+ }
692
+ if (typeof val === 'function') {
693
+ throw new TypeError('Bind parameters must not contain function(s). To pass the body of a function as a string call .toString() first')
694
+ }
695
+ });
696
+ }
697
+
718
698
  var executeCommand = new Commands.Execute(options, cb);
719
699
  var prepareCommand = new Commands.Prepare(options, function(err, stmt) {
720
700
  if (err) {
@@ -11,17 +11,70 @@ function Execute(id, parameters, charsetNumber) {
11
11
  this.encoding = CharsetToEncoding[charsetNumber];
12
12
  }
13
13
 
14
- var pad = '000000000000';
15
- function leftPad(num, value) {
16
- var s = value.toString();
17
- // if we don't need to pad
18
- if (s.length >= num) {
19
- return s;
14
+ function isJSON(value) {
15
+ return Array.isArray(value) ||
16
+ value.constructor === Object ||
17
+ (typeof value.toJSON === 'function' && !Buffer.isBuffer(value));
18
+ }
19
+
20
+ /**
21
+ * Converts a value to an object describing type, String/Buffer representation and length
22
+ * @param {*} value
23
+ */
24
+ function toParameter(value, encoding) {
25
+ var type = Types.VAR_STRING;
26
+ var length;
27
+ var writer = function (value) {
28
+ return Packet.prototype.writeLengthCodedString.call(this, value, encoding)
29
+ }
30
+ if (value !== null) {
31
+ switch (typeof value) {
32
+ case 'undefined':
33
+ throw new TypeError('Bind parameters must not contain undefined');
34
+
35
+ case 'number':
36
+ type = Types.DOUBLE;
37
+ length = 8;
38
+ writer = Packet.prototype.writeDouble;
39
+ break;
40
+
41
+ case 'boolean':
42
+ value = value | 0;
43
+ type = Types.TINY;
44
+ length = 1;
45
+ writer = Packet.prototype.writeInt8;
46
+ break;
47
+
48
+ case 'object':
49
+ if (Object.prototype.toString.call(value) == '[object Date]') {
50
+ type = Types.DATETIME;
51
+ length = 12;
52
+ writer = Packet.prototype.writeDate
53
+ } else if (isJSON(value)) {
54
+ value = JSON.stringify(value);
55
+ type = Types.JSON;
56
+ } else if (Buffer.isBuffer(value)) {
57
+ length = Packet.lengthCodedNumberLength(value.length) + value.length;
58
+ writer = Packet.prototype.writeLengthCodedBuffer
59
+ }
60
+ break;
61
+
62
+ default:
63
+ value = value.toString();
20
64
  }
21
- return (pad + s).slice(-num);
65
+ } else {
66
+ value = ''
67
+ type = Types.NULL;
68
+ }
69
+ if (!length) {
70
+ length = Packet.lengthCodedStringLength(value, encoding);
71
+ }
72
+ return { value, type, length, writer };
22
73
  }
23
74
 
24
75
  Execute.prototype.toPacket = function() {
76
+ var self = this;
77
+
25
78
  // TODO: don't try to calculate packet length in advance, allocate some big buffer in advance (header + 256 bytes?)
26
79
  // and copy + reallocate if not enough
27
80
 
@@ -32,33 +85,17 @@ Execute.prototype.toPacket = function() {
32
85
  // 9 + 1 - flags
33
86
  // 10 + 4 - iteration-count (always 1)
34
87
  var length = 14;
88
+ var parameters;
35
89
  if (this.parameters && this.parameters.length > 0) {
36
90
  length += Math.floor((this.parameters.length + 7) / 8);
37
91
  length += 1; // new-params-bound-flag
38
92
  length += 2 * this.parameters.length; // type byte for each parameter if new-params-bound-flag is set
39
- for (i = 0; i < this.parameters.length; i++) {
40
- if (this.parameters[i] !== null) {
41
- if (
42
- Object.prototype.toString.call(this.parameters[i]) == '[object Date]'
43
- ) {
44
- var d = this.parameters[i];
45
- // TODO: move to asMysqlDateTime()
46
- this.parameters[i] =
47
- [d.getFullYear(), d.getMonth() + 1, d.getDate()].join('-') +
48
- ' ' +
49
- [d.getHours(), d.getMinutes(), d.getSeconds()].join(':') +
50
- '.' + leftPad(3, d.getMilliseconds())
51
- ;
52
- }
53
- if (Buffer.isBuffer(this.parameters[i])) {
54
- length += Packet.lengthCodedNumberLength(this.parameters[i].length);
55
- length += this.parameters[i].length;
56
- } else {
57
- var str = this.parameters[i].toString();
58
- length += Packet.lengthCodedStringLength(str, this.encoding);
59
- }
60
- }
61
- }
93
+ parameters = this.parameters.map(function (value) {
94
+ return toParameter(value, self.encoding);
95
+ });
96
+ length += parameters.reduce(function (accumulator, parameter) {
97
+ return accumulator + parameter.length;
98
+ }, 0);
62
99
  }
63
100
 
64
101
  var buffer = Buffer.allocUnsafe(length);
@@ -68,11 +105,11 @@ Execute.prototype.toPacket = function() {
68
105
  packet.writeInt32(this.id);
69
106
  packet.writeInt8(CursorType.NO_CURSOR); // flags
70
107
  packet.writeInt32(1); // iteration-count, always 1
71
- if (this.parameters && this.parameters.length > 0) {
108
+ if (parameters) {
72
109
  var bitmap = 0;
73
110
  var bitValue = 1;
74
- for (i = 0; i < this.parameters.length; i++) {
75
- if (this.parameters[i] === null) {
111
+ parameters.forEach(function (parameter) {
112
+ if (parameter.type === Types.NULL) {
76
113
  bitmap += bitValue;
77
114
  }
78
115
  bitValue *= 2;
@@ -81,7 +118,7 @@ Execute.prototype.toPacket = function() {
81
118
  bitmap = 0;
82
119
  bitValue = 1;
83
120
  }
84
- }
121
+ });
85
122
  if (bitValue != 1) {
86
123
  packet.writeInt8(bitmap);
87
124
  }
@@ -91,27 +128,18 @@ Execute.prototype.toPacket = function() {
91
128
  // if not, previous execution types are used (TODO prooflink)
92
129
  packet.writeInt8(1); // new-params-bound-flag
93
130
 
94
- // TODO: don't typecast always to sting, use parameters type
95
- for (i = 0; i < this.parameters.length; i++) {
96
- if (this.parameters[i] !== null) {
97
- packet.writeInt16(Types.VAR_STRING);
98
- } else {
99
- packet.writeInt16(Types.NULL);
100
- }
101
- }
131
+ // Write parameter types
132
+ parameters.forEach(function (parameter) {
133
+ packet.writeInt8(parameter.type); // field type
134
+ packet.writeInt8(0); // parameter flag
135
+ });
102
136
 
103
- for (i = 0; i < this.parameters.length; i++) {
104
- if (this.parameters[i] !== null) {
105
- if (Buffer.isBuffer(this.parameters[i])) {
106
- packet.writeLengthCodedBuffer(this.parameters[i]);
107
- } else {
108
- packet.writeLengthCodedString(
109
- this.parameters[i].toString(),
110
- this.encoding
111
- );
112
- }
137
+ // Write parameter values
138
+ parameters.forEach(function (parameter) {
139
+ if (parameter.type !== Types.NULL) {
140
+ parameter.writer.call(packet, parameter.value)
113
141
  }
114
- }
142
+ });
115
143
  }
116
144
  return packet;
117
145
  };
@@ -760,6 +760,11 @@ Packet.prototype.writeInt8 = function(n) {
760
760
  this.offset++;
761
761
  };
762
762
 
763
+ Packet.prototype.writeDouble = function(n) {
764
+ this.buffer.writeDoubleLE(n, this.offset);
765
+ this.offset += 8;
766
+ }
767
+
763
768
  Packet.prototype.writeBuffer = function(b) {
764
769
  b.copy(this.buffer, this.offset);
765
770
  this.offset += b.length;
@@ -839,6 +844,18 @@ Packet.prototype.writeLengthCodedNumber = function(n) {
839
844
  return this.offset;
840
845
  };
841
846
 
847
+ Packet.prototype.writeDate = function(d) {
848
+ this.buffer.writeUInt8(11, this.offset);
849
+ this.buffer.writeUInt16LE(d.getFullYear(), this.offset + 1);
850
+ this.buffer.writeUInt8(d.getMonth() + 1, this.offset + 3);
851
+ this.buffer.writeUInt8(d.getDate(), this.offset + 4);
852
+ this.buffer.writeUInt8(d.getHours(), this.offset + 5);
853
+ this.buffer.writeUInt8(d.getMinutes(), this.offset + 6);
854
+ this.buffer.writeUInt8(d.getSeconds(), this.offset + 7);
855
+ this.buffer.writeUInt32LE(d.getMilliseconds() * 1000, this.offset + 8);
856
+ this.offset += 12;
857
+ }
858
+
842
859
  Packet.prototype.writeHeader = function(sequenceId) {
843
860
  var offset = this.offset;
844
861
  this.offset = 0;
@@ -1,10 +1,10 @@
1
- var FieldFlags = require('./constants/field_flags.js');
2
- var Charsets = require('./constants/charsets.js');
3
- var CharsetToEncoding = require('./constants/charset_encodings.js');
4
- var Types = require('./constants/types.js');
5
- var srcEscape = require('./helpers').srcEscape;
1
+ var FieldFlags = require('../constants/field_flags.js');
2
+ var Charsets = require('../constants/charsets.js');
3
+ var CharsetToEncoding = require('../constants/charset_encodings.js');
4
+ var Types = require('../constants/types.js');
5
+ var srcEscape = require('../helpers').srcEscape;
6
6
  var genFunc = require('generate-function');
7
-
7
+ var parserCache = require('./parser_cache.js');
8
8
  var typeNames = [];
9
9
  for (var t in Types) {
10
10
  typeNames[Types[t]] = t;
@@ -181,4 +181,8 @@ function readCodeFor(field, config, options, fieldNum) {
181
181
  }
182
182
  }
183
183
 
184
- module.exports = compile;
184
+ function getBinaryParser(fields, options, config) {
185
+ return parserCache.getParser('binary', fields, options, config, compile);
186
+ }
187
+
188
+ module.exports = getBinaryParser;
@@ -0,0 +1,53 @@
1
+ var LRU = require('lru-cache');
2
+
3
+ var parserCache = new LRU({
4
+ max: 15000
5
+ });
6
+
7
+ function keyFromFields(type, fields, options) {
8
+ var res =
9
+ type +
10
+ '/' +
11
+ typeof options.nestTables +
12
+ '/' +
13
+ options.nestTables +
14
+ '/' +
15
+ options.rowsAsArray +
16
+ options.supportBigNumbers +
17
+ '/' +
18
+ options.bigNumberStrings +
19
+ '/' +
20
+ typeof options.typeCast;
21
+ for (var i = 0; i < fields.length; ++i) {
22
+ res +=
23
+ '/' + fields[i].name + ':' + fields[i].columnType + ':' + fields[i].flags;
24
+ }
25
+ return res;
26
+ }
27
+
28
+ function getParser(type, fields, options, config, compiler) {
29
+ var key = keyFromFields(type, fields, options);
30
+ var parser = parserCache.get(key);
31
+
32
+ if (parser) {
33
+ return parser;
34
+ }
35
+
36
+ parser = compiler(fields, options, config);
37
+ parserCache.set(key, parser);
38
+ return parser;
39
+ }
40
+
41
+ function setMaxCache(max) {
42
+ parserCache.max = max;
43
+ }
44
+
45
+ function clearCache() {
46
+ parserCache.reset();
47
+ }
48
+
49
+ module.exports = {
50
+ getParser: getParser,
51
+ setMaxCache: setMaxCache,
52
+ clearCache: clearCache
53
+ };
@@ -1,8 +1,9 @@
1
- var Types = require('./constants/types.js');
2
- var Charsets = require('./constants/charsets.js');
3
- var CharsetToEncoding = require('./constants/charset_encodings.js');
4
- var srcEscape = require('./helpers').srcEscape;
1
+ var Types = require('../constants/types.js');
2
+ var Charsets = require('../constants/charsets.js');
3
+ var CharsetToEncoding = require('../constants/charset_encodings.js');
4
+ var srcEscape = require('../helpers').srcEscape;
5
5
  var genFunc = require('generate-function');
6
+ var parserCache = require('./parser_cache.js');
6
7
 
7
8
  var typeNames = [];
8
9
  for (var t in Types) {
@@ -189,4 +190,7 @@ function readCodeFor(type, charset, encodingExpr, config, options) {
189
190
  }
190
191
  }
191
192
 
192
- module.exports = compile;
193
+ function getTextParser(fields, options, config) {
194
+ return parserCache.getParser('text', fields, options, config, compile);
195
+ }
196
+ module.exports = getTextParser;
package/lib/pool.js CHANGED
@@ -217,17 +217,10 @@ Pool.prototype.escapeId = function escapeId(value) {
217
217
 
218
218
  function spliceConnection(queue, connection) {
219
219
  var len = queue.length;
220
- if (len) {
221
- if (queue.get(len - 1) === connection) {
222
- queue.pop();
223
- } else {
224
- for (; --len; ) {
225
- if (queue.get(0) === connection) {
226
- queue.shift();
227
- break;
228
- }
229
- queue.push(queue.shift());
230
- }
220
+ for (var i = 0; i < len; i++) {
221
+ if (queue.get(i) === connection) {
222
+ queue.removeOne(i);
223
+ break;
231
224
  }
232
225
  }
233
226
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mysql2",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "fast mysql driver. Implements core protocol, prepared statements, ssl and compression in native JS",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -49,7 +49,7 @@
49
49
  "denque": "^1.1.1",
50
50
  "generate-function": "^2.0.0",
51
51
  "iconv-lite": "^0.4.18",
52
- "long": "^3.2.0",
52
+ "long": "^4.0.0",
53
53
  "lru-cache": "^4.1.1",
54
54
  "named-placeholders": "1.1.1",
55
55
  "object-assign": "^4.1.1",
@@ -68,7 +68,7 @@
68
68
  "eslint-plugin-prettier": "^2.1.1",
69
69
  "husky": "^0.14.0",
70
70
  "is-async-supported": "^1.2.0",
71
- "lint-staged": "^5.0.0",
71
+ "lint-staged": "^6.0.0",
72
72
  "portfinder": "^1.0.10",
73
73
  "prettier": "^1.3.1",
74
74
  "prettier-markdown": "^0.1.6",
package/promise.js CHANGED
@@ -356,7 +356,7 @@ PromisePool.prototype.execute = function(sql, values) {
356
356
  var corePool = this.pool;
357
357
  const localErr = new Error();
358
358
 
359
- return new Promise(function(resolve, reject) {
359
+ return new this.Promise(function(resolve, reject) {
360
360
  corePool.execute(sql, values, makeDoneCb(resolve, reject, localErr));
361
361
  });
362
362
  };
@@ -364,7 +364,7 @@ PromisePool.prototype.execute = function(sql, values) {
364
364
  PromisePool.prototype.end = function() {
365
365
  var corePool = this.pool;
366
366
  const localErr = new Error();
367
- return new Promise(function(resolve, reject) {
367
+ return new this.Promise(function(resolve, reject) {
368
368
  corePool.end(function(err) {
369
369
  if (err) {
370
370
  localErr.message = err.message;