mysql2 3.7.1 → 3.9.0
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/lib/commands/query.js +1 -1
- package/lib/parsers/binary_parser.js +61 -41
- package/lib/parsers/string.js +24 -3
- package/lib/pool.js +1 -0
- package/package.json +2 -2
- package/typings/mysql/lib/Connection.d.ts +32 -13
- package/typings/mysql/lib/parsers/typeCast.d.ts +53 -0
- package/typings/mysql/lib/protocol/sequences/Query.d.ts +34 -15
package/lib/commands/query.js
CHANGED
|
@@ -279,7 +279,7 @@ class Query extends Command {
|
|
|
279
279
|
});
|
|
280
280
|
this.on('end', () => {
|
|
281
281
|
stream.push(null); // pushing null, indicating EOF
|
|
282
|
-
stream.emit('close'); // notify readers that query has completed
|
|
282
|
+
setImmediate(() => stream.emit('close')); // notify readers that query has completed
|
|
283
283
|
});
|
|
284
284
|
this.on('fields', fields => {
|
|
285
285
|
stream.emit('fields', fields); // replicate old emitter
|
|
@@ -80,12 +80,35 @@ function readCodeFor(field, config, options, fieldNum) {
|
|
|
80
80
|
|
|
81
81
|
function compile(fields, options, config) {
|
|
82
82
|
const parserFn = genFunc();
|
|
83
|
-
let i = 0;
|
|
84
83
|
const nullBitmapLength = Math.floor((fields.length + 7 + 2) / 8);
|
|
85
84
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
function wrap(field, packet) {
|
|
86
|
+
return {
|
|
87
|
+
type: typeNames[field.columnType],
|
|
88
|
+
length: field.columnLength,
|
|
89
|
+
db: field.schema,
|
|
90
|
+
table: field.table,
|
|
91
|
+
name: field.name,
|
|
92
|
+
string: function (encoding = field.encoding) {
|
|
93
|
+
if (field.columnType === Types.JSON && encoding === field.encoding) {
|
|
94
|
+
// Since for JSON columns mysql always returns charset 63 (BINARY),
|
|
95
|
+
// we have to handle it according to JSON specs and use "utf8",
|
|
96
|
+
// see https://github.com/sidorares/node-mysql2/issues/1661
|
|
97
|
+
console.warn(
|
|
98
|
+
`typeCast: JSON column "${field.name}" is interpreted as BINARY by default, recommended to manually set utf8 encoding: \`field.string("utf8")\``,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return packet.readLengthCodedString(encoding);
|
|
103
|
+
},
|
|
104
|
+
buffer: function () {
|
|
105
|
+
return packet.readLengthCodedBuffer();
|
|
106
|
+
},
|
|
107
|
+
geometry: function () {
|
|
108
|
+
return packet.parseGeometryValue();
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
89
112
|
|
|
90
113
|
parserFn('(function(){');
|
|
91
114
|
parserFn('return class BinaryRow {');
|
|
@@ -96,24 +119,19 @@ function compile(fields, options, config) {
|
|
|
96
119
|
if (options.rowsAsArray) {
|
|
97
120
|
parserFn(`const result = new Array(${fields.length});`);
|
|
98
121
|
} else {
|
|
99
|
-
parserFn(
|
|
122
|
+
parserFn('const result = {};');
|
|
100
123
|
}
|
|
101
124
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
resultTablesArray = Object.keys(resultTables);
|
|
110
|
-
for (i = 0; i < resultTablesArray.length; i++) {
|
|
111
|
-
parserFn(`result[${helpers.srcEscape(resultTablesArray[i])}] = {};`);
|
|
112
|
-
}
|
|
125
|
+
// Global typeCast
|
|
126
|
+
if (
|
|
127
|
+
typeof config.typeCast === 'function' &&
|
|
128
|
+
typeof options.typeCast !== 'function'
|
|
129
|
+
) {
|
|
130
|
+
options.typeCast = config.typeCast;
|
|
113
131
|
}
|
|
114
132
|
|
|
115
133
|
parserFn('packet.readInt8();'); // status byte
|
|
116
|
-
for (i = 0; i < nullBitmapLength; ++i) {
|
|
134
|
+
for (let i = 0; i < nullBitmapLength; ++i) {
|
|
117
135
|
parserFn(`const nullBitmaskByte${i} = packet.readInt8();`);
|
|
118
136
|
}
|
|
119
137
|
|
|
@@ -123,38 +141,44 @@ function compile(fields, options, config) {
|
|
|
123
141
|
let fieldName = '';
|
|
124
142
|
let tableName = '';
|
|
125
143
|
|
|
126
|
-
for (i = 0; i < fields.length; i++) {
|
|
144
|
+
for (let i = 0; i < fields.length; i++) {
|
|
127
145
|
fieldName = helpers.srcEscape(fields[i].name);
|
|
128
146
|
parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
|
|
129
147
|
|
|
130
148
|
if (typeof options.nestTables === 'string') {
|
|
131
|
-
tableName = helpers.srcEscape(fields[i].table);
|
|
132
149
|
lvalue = `result[${helpers.srcEscape(
|
|
133
|
-
fields[i].table + options.nestTables + fields[i].name
|
|
150
|
+
fields[i].table + options.nestTables + fields[i].name,
|
|
134
151
|
)}]`;
|
|
135
152
|
} else if (options.nestTables === true) {
|
|
136
153
|
tableName = helpers.srcEscape(fields[i].table);
|
|
154
|
+
parserFn(`if (!result[${tableName}]) result[${tableName}] = {};`);
|
|
137
155
|
lvalue = `result[${tableName}][${fieldName}]`;
|
|
138
156
|
} else if (options.rowsAsArray) {
|
|
139
157
|
lvalue = `result[${i.toString(10)}]`;
|
|
140
158
|
} else {
|
|
141
|
-
lvalue = `result[${
|
|
159
|
+
lvalue = `result[${fieldName}]`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (options.typeCast === false) {
|
|
163
|
+
parserFn(`${lvalue} = packet.readLengthCodedBuffer();`);
|
|
164
|
+
} else {
|
|
165
|
+
const fieldWrapperVar = `fieldWrapper${i}`;
|
|
166
|
+
parserFn(`const ${fieldWrapperVar} = wrap(fields[${i}], packet);`);
|
|
167
|
+
const readCode = readCodeFor(fields[i], config, options, i);
|
|
168
|
+
|
|
169
|
+
parserFn(`if (nullBitmaskByte${nullByteIndex} & ${currentFieldNullBit})`);
|
|
170
|
+
parserFn(`${lvalue} = null;`);
|
|
171
|
+
parserFn('else {');
|
|
172
|
+
if (typeof options.typeCast === 'function') {
|
|
173
|
+
parserFn(
|
|
174
|
+
`${lvalue} = options.typeCast(${fieldWrapperVar}, function() { return ${readCode} });`,
|
|
175
|
+
);
|
|
176
|
+
} else {
|
|
177
|
+
parserFn(`${lvalue} = ${readCode};`);
|
|
178
|
+
}
|
|
179
|
+
parserFn('}');
|
|
142
180
|
}
|
|
143
181
|
|
|
144
|
-
// TODO: this used to be an optimisation ( if column marked as NOT_NULL don't include code to check null
|
|
145
|
-
// bitmap at all, but it seems that we can't rely on this flag, see #178
|
|
146
|
-
// TODO: benchmark performance difference
|
|
147
|
-
//
|
|
148
|
-
// if (fields[i].flags & FieldFlags.NOT_NULL) { // don't need to check null bitmap if field can't be null.
|
|
149
|
-
// result.push(lvalue + ' = ' + readCodeFor(fields[i], config));
|
|
150
|
-
// } else if (fields[i].columnType == Types.NULL) {
|
|
151
|
-
// result.push(lvalue + ' = null;');
|
|
152
|
-
// } else {
|
|
153
|
-
parserFn(`if (nullBitmaskByte${nullByteIndex} & ${currentFieldNullBit})`);
|
|
154
|
-
parserFn(`${lvalue} = null;`);
|
|
155
|
-
parserFn('else');
|
|
156
|
-
parserFn(`${lvalue} = ${readCodeFor(fields[i], config, options, i)}`);
|
|
157
|
-
// }
|
|
158
182
|
currentFieldNullBit *= 2;
|
|
159
183
|
if (currentFieldNullBit === 0x100) {
|
|
160
184
|
currentFieldNullBit = 1;
|
|
@@ -166,17 +190,13 @@ function compile(fields, options, config) {
|
|
|
166
190
|
parserFn('}');
|
|
167
191
|
parserFn('};')('})()');
|
|
168
192
|
|
|
169
|
-
/* eslint-enable no-trailing-spaces */
|
|
170
|
-
/* eslint-enable no-spaced-func */
|
|
171
|
-
/* eslint-enable no-unexpected-multiline */
|
|
172
|
-
|
|
173
193
|
if (config.debug) {
|
|
174
194
|
helpers.printDebugWithCode(
|
|
175
195
|
'Compiled binary protocol row parser',
|
|
176
|
-
parserFn.toString()
|
|
196
|
+
parserFn.toString(),
|
|
177
197
|
);
|
|
178
198
|
}
|
|
179
|
-
return parserFn.toFunction();
|
|
199
|
+
return parserFn.toFunction({ wrap });
|
|
180
200
|
}
|
|
181
201
|
|
|
182
202
|
function getBinaryParser(fields, options, config) {
|
package/lib/parsers/string.js
CHANGED
|
@@ -1,13 +1,34 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Iconv = require('iconv-lite');
|
|
4
|
+
const LRU = require('lru-cache').default;
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
const decoderCache = new LRU({
|
|
7
|
+
max: 500,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
exports.decode = function (buffer, encoding, start, end, options) {
|
|
6
11
|
if (Buffer.isEncoding(encoding)) {
|
|
7
12
|
return buffer.toString(encoding, start, end);
|
|
8
13
|
}
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
// Optimize for common case: encoding="short_string", options=undefined.
|
|
16
|
+
let decoder;
|
|
17
|
+
if (!options) {
|
|
18
|
+
decoder = decoderCache.get(encoding);
|
|
19
|
+
if (!decoder) {
|
|
20
|
+
decoder = Iconv.getDecoder(encoding);
|
|
21
|
+
decoderCache.set(encoding, decoder);
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
const decoderArgs = { encoding, options };
|
|
25
|
+
const decoderKey = JSON.stringify(decoderArgs);
|
|
26
|
+
decoder = decoderCache.get(decoderKey);
|
|
27
|
+
if (!decoder) {
|
|
28
|
+
decoder = Iconv.getDecoder(decoderArgs.encoding, decoderArgs.options);
|
|
29
|
+
decoderCache.set(decoderKey, decoder);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
11
32
|
|
|
12
33
|
const res = decoder.write(buffer.slice(start, end));
|
|
13
34
|
const trail = decoder.end();
|
|
@@ -15,7 +36,7 @@ exports.decode = function(buffer, encoding, start, end, options) {
|
|
|
15
36
|
return trail ? res + trail : res;
|
|
16
37
|
};
|
|
17
38
|
|
|
18
|
-
exports.encode = function(string, encoding, options) {
|
|
39
|
+
exports.encode = function (string, encoding, options) {
|
|
19
40
|
if (Buffer.isEncoding(encoding)) {
|
|
20
41
|
return Buffer.from(string, encoding);
|
|
21
42
|
}
|
package/lib/pool.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mysql2",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0",
|
|
4
4
|
"description": "fast mysql driver. Implements core protocol, prepared statements, ssl and compression in native JS",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "typings/mysql/index",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"eslint-config-prettier": "^9.0.0",
|
|
77
77
|
"eslint-plugin-async-await": "0.0.0",
|
|
78
78
|
"eslint-plugin-markdown": "^3.0.0",
|
|
79
|
-
"husky": "^
|
|
79
|
+
"husky": "^9.0.2",
|
|
80
80
|
"lint-staged": "^15.0.1",
|
|
81
81
|
"portfinder": "^1.0.28",
|
|
82
82
|
"prettier": "^3.0.0",
|
|
@@ -19,6 +19,7 @@ import { Connection as PromiseConnection } from '../../../promise.js';
|
|
|
19
19
|
import { AuthPlugin } from './Auth.js';
|
|
20
20
|
import { QueryableBase } from './protocol/sequences/QueryableBase.js';
|
|
21
21
|
import { ExecutableBase } from './protocol/sequences/ExecutableBase.js';
|
|
22
|
+
import { TypeCast } from './parsers/typeCast.js';
|
|
22
23
|
|
|
23
24
|
export interface SslOptions {
|
|
24
25
|
/**
|
|
@@ -172,26 +173,44 @@ export interface ConnectionOptions {
|
|
|
172
173
|
infileStreamFactory?: (path: string) => Readable;
|
|
173
174
|
|
|
174
175
|
/**
|
|
175
|
-
* Determines if column values should be converted to native JavaScript types.
|
|
176
|
-
* to disable type casting, but you can currently do so on either the connection or query level. (Default: true)
|
|
176
|
+
* Determines if column values should be converted to native JavaScript types.
|
|
177
177
|
*
|
|
178
|
-
*
|
|
178
|
+
* @default true
|
|
179
179
|
*
|
|
180
|
-
*
|
|
180
|
+
* It is not recommended (and may go away / change in the future) to disable type casting, but you can currently do so on either the connection or query level.
|
|
181
181
|
*
|
|
182
|
-
*
|
|
183
|
-
* field.buffer()
|
|
184
|
-
* field.geometry()
|
|
182
|
+
* ---
|
|
185
183
|
*
|
|
186
|
-
*
|
|
184
|
+
* You can also specify a function to do the type casting yourself:
|
|
185
|
+
* ```ts
|
|
186
|
+
* (field: Field, next: () => void) => {
|
|
187
|
+
* return next();
|
|
188
|
+
* }
|
|
189
|
+
* ```
|
|
187
190
|
*
|
|
188
|
-
*
|
|
189
|
-
* parser.parseLengthCodedBuffer()
|
|
190
|
-
* parser.parseGeometryValue()
|
|
191
|
+
* ---
|
|
191
192
|
*
|
|
192
|
-
*
|
|
193
|
+
* **WARNING:**
|
|
194
|
+
*
|
|
195
|
+
* YOU MUST INVOKE the parser using one of these three field functions in your custom typeCast callback. They can only be called once:
|
|
196
|
+
*
|
|
197
|
+
* ```js
|
|
198
|
+
* field.string();
|
|
199
|
+
* field.buffer();
|
|
200
|
+
* field.geometry();
|
|
201
|
+
* ```
|
|
202
|
+
|
|
203
|
+
* Which are aliases for:
|
|
204
|
+
*
|
|
205
|
+
* ```js
|
|
206
|
+
* parser.parseLengthCodedString();
|
|
207
|
+
* parser.parseLengthCodedBuffer();
|
|
208
|
+
* parser.parseGeometryValue();
|
|
209
|
+
* ```
|
|
210
|
+
*
|
|
211
|
+
* You can find which field function you need to use by looking at `RowDataPacket.prototype._typeCast`.
|
|
193
212
|
*/
|
|
194
|
-
typeCast?:
|
|
213
|
+
typeCast?: TypeCast;
|
|
195
214
|
|
|
196
215
|
/**
|
|
197
216
|
* A custom query format function
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
type Geometry = {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
type Type = {
|
|
7
|
+
type:
|
|
8
|
+
| 'DECIMAL'
|
|
9
|
+
| 'TINY'
|
|
10
|
+
| 'SHORT'
|
|
11
|
+
| 'LONG'
|
|
12
|
+
| 'FLOAT'
|
|
13
|
+
| 'DOUBLE'
|
|
14
|
+
| 'NULL'
|
|
15
|
+
| 'TIMESTAMP'
|
|
16
|
+
| 'TIMESTAMP2'
|
|
17
|
+
| 'LONGLONG'
|
|
18
|
+
| 'INT24'
|
|
19
|
+
| 'DATE'
|
|
20
|
+
| 'TIME'
|
|
21
|
+
| 'TIME2'
|
|
22
|
+
| 'DATETIME'
|
|
23
|
+
| 'DATETIME2'
|
|
24
|
+
| 'YEAR'
|
|
25
|
+
| 'NEWDATE'
|
|
26
|
+
| 'VARCHAR'
|
|
27
|
+
| 'BIT'
|
|
28
|
+
| 'JSON'
|
|
29
|
+
| 'NEWDECIMAL'
|
|
30
|
+
| 'ENUM'
|
|
31
|
+
| 'SET'
|
|
32
|
+
| 'TINY_BLOB'
|
|
33
|
+
| 'MEDIUM_BLOB'
|
|
34
|
+
| 'LONG_BLOB'
|
|
35
|
+
| 'BLOB'
|
|
36
|
+
| 'VAR_STRING'
|
|
37
|
+
| 'STRING'
|
|
38
|
+
| 'GEOMETRY';
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type Field = Type & {
|
|
42
|
+
length: number;
|
|
43
|
+
db: string;
|
|
44
|
+
table: string;
|
|
45
|
+
name: string;
|
|
46
|
+
string: () => string | null;
|
|
47
|
+
buffer: () => Buffer | null;
|
|
48
|
+
geometry: () => Geometry | Geometry[] | null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type Next = () => void;
|
|
52
|
+
|
|
53
|
+
export type TypeCast = ((field: Field, next: Next) => any) | boolean;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Sequence } from './Sequence.js';
|
|
2
2
|
import { OkPacket, RowDataPacket, FieldPacket } from '../packets/index.js';
|
|
3
3
|
import { Readable } from 'stream';
|
|
4
|
+
import { TypeCast } from '../../parsers/typeCast.js';
|
|
4
5
|
|
|
5
6
|
export interface QueryOptions {
|
|
6
7
|
/**
|
|
@@ -33,26 +34,44 @@ export interface QueryOptions {
|
|
|
33
34
|
nestTables?: any;
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
|
-
* Determines if column values should be converted to native JavaScript types.
|
|
37
|
-
* to disable type casting, but you can currently do so on either the connection or query level. (Default: true)
|
|
37
|
+
* Determines if column values should be converted to native JavaScript types.
|
|
38
38
|
*
|
|
39
|
-
*
|
|
39
|
+
* @default true
|
|
40
40
|
*
|
|
41
|
-
*
|
|
41
|
+
* It is not recommended (and may go away / change in the future) to disable type casting, but you can currently do so on either the connection or query level.
|
|
42
42
|
*
|
|
43
|
-
*
|
|
44
|
-
* field.buffer()
|
|
45
|
-
* field.geometry()
|
|
43
|
+
* ---
|
|
46
44
|
*
|
|
47
|
-
*
|
|
45
|
+
* You can also specify a function to do the type casting yourself:
|
|
46
|
+
* ```ts
|
|
47
|
+
* (field: Field, next: () => void) => {
|
|
48
|
+
* return next();
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
48
51
|
*
|
|
49
|
-
*
|
|
50
|
-
* parser.parseLengthCodedBuffer()
|
|
51
|
-
* parser.parseGeometryValue()
|
|
52
|
+
* ---
|
|
52
53
|
*
|
|
53
|
-
*
|
|
54
|
+
* **WARNING:**
|
|
55
|
+
*
|
|
56
|
+
* YOU MUST INVOKE the parser using one of these three field functions in your custom typeCast callback. They can only be called once:
|
|
57
|
+
*
|
|
58
|
+
* ```js
|
|
59
|
+
* field.string();
|
|
60
|
+
* field.buffer();
|
|
61
|
+
* field.geometry();
|
|
62
|
+
* ```
|
|
63
|
+
|
|
64
|
+
* Which are aliases for:
|
|
65
|
+
*
|
|
66
|
+
* ```js
|
|
67
|
+
* parser.parseLengthCodedString();
|
|
68
|
+
* parser.parseLengthCodedBuffer();
|
|
69
|
+
* parser.parseGeometryValue();
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* You can find which field function you need to use by looking at `RowDataPacket.prototype._typeCast`.
|
|
54
73
|
*/
|
|
55
|
-
typeCast?:
|
|
74
|
+
typeCast?: TypeCast;
|
|
56
75
|
|
|
57
76
|
/**
|
|
58
77
|
* This overrides the same option set at the connection level.
|
|
@@ -137,11 +156,11 @@ declare class Query extends Sequence {
|
|
|
137
156
|
on(event: 'error', listener: (err: QueryError) => any): this;
|
|
138
157
|
on(
|
|
139
158
|
event: 'fields',
|
|
140
|
-
listener: (fields: FieldPacket, index: number) => any
|
|
159
|
+
listener: (fields: FieldPacket, index: number) => any,
|
|
141
160
|
): this;
|
|
142
161
|
on(
|
|
143
162
|
event: 'result',
|
|
144
|
-
listener: (result: RowDataPacket | OkPacket, index: number) => any
|
|
163
|
+
listener: (result: RowDataPacket | OkPacket, index: number) => any,
|
|
145
164
|
): this;
|
|
146
165
|
on(event: 'end', listener: () => any): this;
|
|
147
166
|
}
|