Skip to content

Commit 12979b2

Browse files
nwoltmandougwilson
authored andcommitted
Performance improvements for string escaping in SqlString
closes mysqljs#1390
1 parent 0fd2f7b commit 12979b2

File tree

3 files changed

+58
-33
lines changed

3 files changed

+58
-33
lines changed

Changes.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ you spot any mistakes.
1010
* Fix Query stream to emit close after ending #1349 #1350
1111
* Fix type cast for BIGINT columns when number is negative #1376
1212
* Performance improvements for array/object escaping in SqlString #1331
13+
* Performance improvements for string escaping in SqlString #1390
1314
* Support Node.js 6.x
1415
* Update `bignumber.js` to 2.3.0
1516
* Update `readable-stream` to 1.1.14

lib/protocol/SqlString.js

+48-33
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1-
var SqlString = exports;
1+
var SqlString = exports;
2+
var charsRegex = /[\0\b\t\n\r\x1a\"\'\\]/g;
3+
var charsMap = {
4+
'\0': '\\0',
5+
'\b': '\\b',
6+
'\t': '\\t',
7+
'\n': '\\n',
8+
'\r': '\\r',
9+
'\x1a': '\\Z',
10+
'"': '\\"',
11+
'\'': '\\\'',
12+
'\\': '\\\\'
13+
};
214

315
SqlString.escapeId = function escapeId(val, forbidQualified) {
416
if (Array.isArray(val)) {
@@ -26,40 +38,21 @@ SqlString.escape = function escape(val, stringifyObjects, timeZone) {
2638
switch (typeof val) {
2739
case 'boolean': return (val) ? 'true' : 'false';
2840
case 'number': return val+'';
41+
case 'object':
42+
if (val instanceof Date) {
43+
val = SqlString.dateToString(val, timeZone || 'local');
44+
} else if (Array.isArray(val)) {
45+
return SqlString.arrayToList(val, timeZone);
46+
} else if (Buffer.isBuffer(val)) {
47+
return SqlString.bufferToString(val);
48+
} else if (stringifyObjects) {
49+
val = val.toString();
50+
} else {
51+
return SqlString.objectToValues(val, timeZone);
52+
}
2953
}
3054

31-
if (val instanceof Date) {
32-
val = SqlString.dateToString(val, timeZone || 'local');
33-
}
34-
35-
if (Buffer.isBuffer(val)) {
36-
return SqlString.bufferToString(val);
37-
}
38-
39-
if (Array.isArray(val)) {
40-
return SqlString.arrayToList(val, timeZone);
41-
}
42-
43-
if (typeof val === 'object') {
44-
if (stringifyObjects) {
45-
val = val.toString();
46-
} else {
47-
return SqlString.objectToValues(val, timeZone);
48-
}
49-
}
50-
51-
val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
52-
switch(s) {
53-
case "\0": return "\\0";
54-
case "\n": return "\\n";
55-
case "\r": return "\\r";
56-
case "\b": return "\\b";
57-
case "\t": return "\\t";
58-
case "\x1a": return "\\Z";
59-
default: return "\\"+s;
60-
}
61-
});
62-
return "'"+val+"'";
55+
return escapeString(val);
6356
};
6457

6558
SqlString.arrayToList = function arrayToList(array, timeZone) {
@@ -156,6 +149,28 @@ SqlString.objectToValues = function objectToValues(object, timeZone) {
156149
return sql;
157150
};
158151

152+
function escapeString(val) {
153+
var chunkIndex = charsRegex.lastIndex = 0;
154+
var escapedVal = '';
155+
var match;
156+
157+
while ((match = charsRegex.exec(val))) {
158+
escapedVal += val.slice(chunkIndex, match.index) + charsMap[match[0]];
159+
chunkIndex = charsRegex.lastIndex;
160+
}
161+
162+
if (chunkIndex === 0) {
163+
// Nothing was escaped
164+
return "'" + val + "'";
165+
}
166+
167+
if (chunkIndex < val.length) {
168+
return "'" + escapedVal + val.slice(chunkIndex) + "'";
169+
}
170+
171+
return "'" + escapedVal + "'";
172+
}
173+
159174
function zeroPad(number, length) {
160175
number = number.toString();
161176
while (number.length < length) {

test/unit/protocol/test-SqlString.js

+9
Original file line numberDiff line numberDiff line change
@@ -76,38 +76,47 @@ test('SqlString.escape', {
7676

7777
'\0 gets escaped': function() {
7878
assert.equal(SqlString.escape('Sup\0er'), "'Sup\\0er'");
79+
assert.equal(SqlString.escape('Super\0'), "'Super\\0'");
7980
},
8081

8182
'\b gets escaped': function() {
8283
assert.equal(SqlString.escape('Sup\ber'), "'Sup\\ber'");
84+
assert.equal(SqlString.escape('Super\b'), "'Super\\b'");
8385
},
8486

8587
'\n gets escaped': function() {
8688
assert.equal(SqlString.escape('Sup\ner'), "'Sup\\ner'");
89+
assert.equal(SqlString.escape('Super\n'), "'Super\\n'");
8790
},
8891

8992
'\r gets escaped': function() {
9093
assert.equal(SqlString.escape('Sup\rer'), "'Sup\\rer'");
94+
assert.equal(SqlString.escape('Super\r'), "'Super\\r'");
9195
},
9296

9397
'\t gets escaped': function() {
9498
assert.equal(SqlString.escape('Sup\ter'), "'Sup\\ter'");
99+
assert.equal(SqlString.escape('Super\t'), "'Super\\t'");
95100
},
96101

97102
'\\ gets escaped': function() {
98103
assert.equal(SqlString.escape('Sup\\er'), "'Sup\\\\er'");
104+
assert.equal(SqlString.escape('Super\\'), "'Super\\\\'");
99105
},
100106

101107
'\u001a (ascii 26) gets replaced with \\Z': function() {
102108
assert.equal(SqlString.escape('Sup\u001aer'), "'Sup\\Zer'");
109+
assert.equal(SqlString.escape('Super\u001a'), "'Super\\Z'");
103110
},
104111

105112
'single quotes get escaped': function() {
106113
assert.equal(SqlString.escape('Sup\'er'), "'Sup\\'er'");
114+
assert.equal(SqlString.escape('Super\''), "'Super\\''");
107115
},
108116

109117
'double quotes get escaped': function() {
110118
assert.equal(SqlString.escape('Sup"er'), "'Sup\\\"er'");
119+
assert.equal(SqlString.escape('Super"'), "'Super\\\"'");
111120
},
112121

113122
'dates are converted to YYYY-MM-DD HH:II:SS.sss': function() {

0 commit comments

Comments
 (0)