diff --git a/Changes.md b/Changes.md index 914249879..865ecdca9 100644 --- a/Changes.md +++ b/Changes.md @@ -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 diff --git a/lib/protocol/constants/types.js b/lib/protocol/constants/types.js index 19943e8e5..d55e7a77b 100644 --- a/lib/protocol/constants/types.js +++ b/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'; diff --git a/lib/protocol/packets/Field.js b/lib/protocol/packets/Field.js index bb6336c1a..a5d58edb6 100644 --- a/lib/protocol/packets/Field.js +++ b/lib/protocol/packets/Field.js @@ -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; } @@ -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; -} diff --git a/lib/protocol/packets/RowDataPacket.js b/lib/protocol/packets/RowDataPacket.js index 8313f87fb..b8ec4b895 100644 --- a/lib/protocol/packets/RowDataPacket.js +++ b/lib/protocol/packets/RowDataPacket.js @@ -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); } diff --git a/test/unit/test-Mysql.js b/test/unit/test-Mysql.js index 5bd15fe6d..c12f15699 100644 --- a/test/unit/test-Mysql.js +++ b/test/unit/test-Mysql.js @@ -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'); + } }); } }); diff --git a/tool/generate-type-constants.js b/tool/generate-type-constants.js new file mode 100755 index 000000000..8275c8715 --- /dev/null +++ b/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); +}