Skip to content

Commit

Permalink
Add reverse type lookup for small performance gain
Browse files Browse the repository at this point in the history
closes #2170
  • Loading branch information
dougwilson committed Apr 17, 2019
1 parent d916479 commit 5569e02
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 49 deletions.
1 change: 1 addition & 0 deletions Changes.md
Expand Up @@ -6,6 +6,7 @@ you spot any mistakes.

## HEAD

* Add reverse type lookup for small performance gain #2170
* Fix `connection.threadId` missing on handshake failure
* Fix duplicate packet name in debug output
* Fix no password support for old password protocol
Expand Down
105 changes: 72 additions & 33 deletions lib/protocol/constants/types.js
@@ -1,33 +1,72 @@
// Manually extracted from mysql-5.7.9/include/mysql.h.pp
// some more info here: http://dev.mysql.com/doc/refman/5.5/en/c-api-prepared-statement-type-codes.html
exports.DECIMAL = 0x00; // aka DECIMAL (http://dev.mysql.com/doc/refman/5.0/en/precision-math-decimal-changes.html)
exports.TINY = 0x01; // aka TINYINT, 1 byte
exports.SHORT = 0x02; // aka SMALLINT, 2 bytes
exports.LONG = 0x03; // aka INT, 4 bytes
exports.FLOAT = 0x04; // aka FLOAT, 4-8 bytes
exports.DOUBLE = 0x05; // aka DOUBLE, 8 bytes
exports.NULL = 0x06; // NULL (used for prepared statements, I think)
exports.TIMESTAMP = 0x07; // aka TIMESTAMP
exports.LONGLONG = 0x08; // aka BIGINT, 8 bytes
exports.INT24 = 0x09; // aka MEDIUMINT, 3 bytes
exports.DATE = 0x0a; // aka DATE
exports.TIME = 0x0b; // aka TIME
exports.DATETIME = 0x0c; // aka DATETIME
exports.YEAR = 0x0d; // aka YEAR, 1 byte (don't ask)
exports.NEWDATE = 0x0e; // aka ?
exports.VARCHAR = 0x0f; // aka VARCHAR (?)
exports.BIT = 0x10; // aka BIT, 1-8 byte
exports.TIMESTAMP2 = 0x11; // aka TIMESTAMP with fractional seconds
exports.DATETIME2 = 0x12; // aka DATETIME with fractional seconds
exports.TIME2 = 0x13; // aka TIME with fractional seconds
exports.JSON = 0xf5; // aka JSON
exports.NEWDECIMAL = 0xf6; // aka DECIMAL
exports.ENUM = 0xf7; // aka ENUM
exports.SET = 0xf8; // aka SET
exports.TINY_BLOB = 0xf9; // aka TINYBLOB, TINYTEXT
exports.MEDIUM_BLOB = 0xfa; // aka MEDIUMBLOB, MEDIUMTEXT
exports.LONG_BLOB = 0xfb; // aka LONGBLOG, LONGTEXT
exports.BLOB = 0xfc; // aka BLOB, TEXT
exports.VAR_STRING = 0xfd; // aka VARCHAR, VARBINARY
exports.STRING = 0xfe; // aka CHAR, BINARY
exports.GEOMETRY = 0xff; // aka GEOMETRY
/**
* MySQL type constants
*
* Extracted from version 5.7.19
*
* !! Generated by generate-type-constants.js, do not modify by hand !!
*/

exports.DECIMAL = 0;
exports.TINY = 1;
exports.SHORT = 2;
exports.LONG = 3;
exports.FLOAT = 4;
exports.DOUBLE = 5;
exports.NULL = 6;
exports.TIMESTAMP = 7;
exports.LONGLONG = 8;
exports.INT24 = 9;
exports.DATE = 10;
exports.TIME = 11;
exports.DATETIME = 12;
exports.YEAR = 13;
exports.NEWDATE = 14;
exports.VARCHAR = 15;
exports.BIT = 16;
exports.TIMESTAMP2 = 17;
exports.DATETIME2 = 18;
exports.TIME2 = 19;
exports.JSON = 245;
exports.NEWDECIMAL = 246;
exports.ENUM = 247;
exports.SET = 248;
exports.TINY_BLOB = 249;
exports.MEDIUM_BLOB = 250;
exports.LONG_BLOB = 251;
exports.BLOB = 252;
exports.VAR_STRING = 253;
exports.STRING = 254;
exports.GEOMETRY = 255;

// Lookup-by-number table
exports[0] = 'DECIMAL';
exports[1] = 'TINY';
exports[2] = 'SHORT';
exports[3] = 'LONG';
exports[4] = 'FLOAT';
exports[5] = 'DOUBLE';
exports[6] = 'NULL';
exports[7] = 'TIMESTAMP';
exports[8] = 'LONGLONG';
exports[9] = 'INT24';
exports[10] = 'DATE';
exports[11] = 'TIME';
exports[12] = 'DATETIME';
exports[13] = 'YEAR';
exports[14] = 'NEWDATE';
exports[15] = 'VARCHAR';
exports[16] = 'BIT';
exports[17] = 'TIMESTAMP2';
exports[18] = 'DATETIME2';
exports[19] = 'TIME2';
exports[245] = 'JSON';
exports[246] = 'NEWDECIMAL';
exports[247] = 'ENUM';
exports[248] = 'SET';
exports[249] = 'TINY_BLOB';
exports[250] = 'MEDIUM_BLOB';
exports[251] = 'LONG_BLOB';
exports[252] = 'BLOB';
exports[253] = 'VAR_STRING';
exports[254] = 'STRING';
exports[255] = 'GEOMETRY';
10 changes: 1 addition & 9 deletions lib/protocol/packets/Field.js
Expand Up @@ -9,7 +9,7 @@ function Field(options) {
this.db = options.packet.db;
this.table = options.packet.table;
this.name = options.packet.name;
this.type = typeToString(options.packet.type);
this.type = Types[options.packet.type];
this.length = options.packet.length;
}

Expand All @@ -24,11 +24,3 @@ Field.prototype.buffer = function () {
Field.prototype.geometry = function () {
return this.parser.parseGeometryValue();
};

function typeToString(t) {
for (var k in Types) {
if (Types[k] === t) return k;
}

return undefined;
}
5 changes: 1 addition & 4 deletions lib/protocol/packets/RowDataPacket.js
Expand Up @@ -123,10 +123,7 @@ function typeCast(field, parser, timeZone, supportBigNumbers, bigNumberStrings,

function typeMatch(type, list) {
if (Array.isArray(list)) {
for (var i = 0; i < list.length; i++) {
if (Types[list[i]] === type) return true;
}
return false;
return list.indexOf(Types[type]) !== -1;
} else {
return Boolean(list);
}
Expand Down
18 changes: 15 additions & 3 deletions test/unit/test-Mysql.js
Expand Up @@ -37,12 +37,24 @@ test('Mysql.Types', {
assert.equal(Mysql.Types, common.Types);
},

'string names to integer values': function() {
'contains string to integer values': function() {
var types = Object.keys(Mysql.Types);
assert.ok(types.length > 0);
types.forEach(function (type) {
assert.ok(/^[A-Z_]+/.test(type));
assert.equal(typeof Mysql.Types[type], 'number');
if (!/^[0-9]+$/.test(type)) {
assert.ok(/^[A-Z_]+/.test(type));
assert.equal(typeof Mysql.Types[type], 'number');
}
});
},

'contains integer values to string names': function() {
var types = Object.keys(Mysql.Types);
assert.ok(types.length > 0);
types.forEach(function (type) {
if (/^[0-9]+$/.test(type)) {
assert.equal(typeof Mysql.Types[type], 'string');
}
});
}
});
91 changes: 91 additions & 0 deletions tool/generate-type-constants.js
@@ -0,0 +1,91 @@
#!/usr/bin/env node
var fs = require('fs');
var path = require('path');
var script = path.basename(__filename);

var srcDir = process.argv[2];
if (!srcDir) {
var args = [];
args[0] = process.argv[0].indexOf(' ') !== -1
? '"' + process.argv[0] + '"'
: process.argv[0];
args[1] = process.argv[1].indexOf(' ') !== -1
? '"' + process.argv[1] + '"'
: process.argv[1];
args[2] = path.join('path', 'to', 'mysql', 'src');
console.error('Usage: ' + args.join(' '));
process.exit(1);
}

var types = extractTypes(srcDir);
var targetFile = path.join(__dirname, '..', 'lib', 'protocol', 'constants', 'types.js');
var stream = fs.createWriteStream(targetFile);
var version = extractMySqlVersion(srcDir);

stream.write('/**\n * MySQL type constants\n *\n * Extracted from version ' + version + '\n *\n * !! Generated by ' + script + ', do not modify by hand !!\n */\n\n');

var alignment = types.reduce(maxLength, 0);
for (var i = 0; i < types.length; i++) {
if (i in types) {
stream.write('exports.' + types[i] + (new Array(alignment - types[i].length + 1)).join(' ') + ' = ' + i + ';\n');
}
}

stream.write('\n// Lookup-by-number table\n');

var alignment = String(types.length).length;
for (var i = 0; i < types.length; i++) {
if (i in types) {
stream.write('exports[' + i + ']' + (new Array(alignment - String(i).length + 1)).join(' ') + ' = \'' + types[i] + '\';\n');
}
}

console.log('Wrote constants to ' + targetFile);

function extractMySqlVersion(srcDir) {
var versionFile = path.join(srcDir, 'VERSION');
var contents = fs.readFileSync(versionFile, 'utf-8');
var dictionary = Object.create(null);

contents.split('\n').forEach(function (line) {
var pair = line.split('=');
var key = pair[0];
var val = pair.slice(1).join('=').trimRight();
dictionary[key] = val;
});

return dictionary.MYSQL_VERSION_MAJOR + '.' +
dictionary.MYSQL_VERSION_MINOR + '.' +
dictionary.MYSQL_VERSION_PATCH;
}

function extractTypes(srcDir) {
var headerFile = path.join(srcDir, 'include', 'mysql.h.pp');
var contents = fs.readFileSync(headerFile, 'utf-8');
var enumRegexp = /typedef enum enum_field_types {([^}]*)}/m;
var match = enumRegexp.exec(contents);
var regexp = /([A-Z0-9_]+)(?: *= *([0-9]+))?/g;

if (!match) {
throw new Error('Cannot locate enum enum_field_types in "' + headerFile + '"');
}

var enumContents = match[1];
var index = 0;
var types = [];

while ((match = regexp.exec(enumContents))) {
var name = match[1];
var num = Number(match[2]) || index++;

if (name.indexOf('MYSQL_TYPE_') === 0) {
types[num] = name.substring(11);
}
}

return types;
}

function maxLength(max, value) {
return Math.max(max, value.length);
}

0 comments on commit 5569e02

Please sign in to comment.