mysql2
Version:
fast mysql driver. Implements core protocol, prepared statements, ssl and compression in native JS
244 lines (225 loc) • 7.24 kB
JavaScript
'use strict';
const { createLRU } = require('lru.min');
const parserCache = createLRU({
max: 15000,
});
// A compiled row parser depends on the parsing options and, per column, on
// exactly the metadata the code generators bake into the function: type,
// charset, flags, decimals, name, MariaDB extended metadata, and the table
// name when rows are nested by table. The cache is keyed by an integer hash
// of that data and every hit is confirmed against the full metadata kept in
// the entry, so a hash collision can never hand out a parser compiled for
// different columns or options.
const TYPE_CAST_FALSE = 1;
const TYPE_CAST_FUNCTION = 2;
const TYPE_CAST_DEFAULT = 3;
function typeCastKind(typeCast) {
if (typeCast === false) {
return TYPE_CAST_FALSE;
}
if (typeof typeCast === 'function') {
return TYPE_CAST_FUNCTION;
}
return TYPE_CAST_DEFAULT;
}
// helpers.typeMatch reads an array by the type names it lists and anything
// else by truthiness
function dateStringsKey(dateStrings) {
if (Array.isArray(dateStrings)) {
return dateStrings.map(String);
}
return Boolean(dateStrings);
}
function nestTablesKey(nestTables) {
if (typeof nestTables === 'string') {
return nestTables;
}
return Boolean(nestTables);
}
function optionBits(type, options, config, nestTables, dateStrings) {
return (
(type === 'binary' ? 1 : 0) |
(options.rowsAsArray ? 2 : 0) |
(options.supportBigNumbers || config.supportBigNumbers ? 4 : 0) |
(options.bigNumberStrings || config.bigNumberStrings ? 8 : 0) |
(typeCastKind(options.typeCast) << 4) |
(options.decimalNumbers ? 64 : 0) |
(config.jsonStrings ? 128 : 0) |
(nestTables === true ? 256 : 0) |
(typeof nestTables === 'string' ? 512 : 0) |
(dateStrings === true ? 1024 : 0) |
(Array.isArray(dateStrings) ? 2048 : 0)
);
}
function mixNumber(hash, value) {
return Math.imul(hash ^ (value | 0), 0x9e3779b1) | 0;
}
function mixString(hash, string) {
if (typeof string !== 'string') {
return mixNumber(hash, string === undefined ? -1 : -2);
}
hash = mixNumber(hash, string.length);
for (let i = 0; i < string.length; ++i) {
hash = (Math.imul(hash, 31) + string.charCodeAt(i)) | 0;
}
return hash;
}
function hashKey(type, fields, options, config) {
const nestTables = nestTablesKey(options.nestTables);
const dateStrings = dateStringsKey(options.dateStrings || config.dateStrings);
const includeTable = nestTables !== false;
let hash = mixNumber(
0x811c9dc5,
optionBits(type, options, config, nestTables, dateStrings)
);
hash = mixString(hash, String(options.timezone || config.timezone));
if (typeof nestTables === 'string') {
hash = mixString(hash, nestTables);
}
if (Array.isArray(dateStrings)) {
for (let i = 0; i < dateStrings.length; ++i) {
hash = mixString(hash, dateStrings[i]);
}
}
hash = mixNumber(hash, fields.length);
for (let i = 0; i < fields.length; ++i) {
const field = fields[i];
hash = mixNumber(
hash,
field.columnType | (field.characterSet << 8) | (field.decimals << 24)
);
hash = mixNumber(hash, field.flags);
hash = mixString(hash, field.name);
if (field.extendedTypeName !== undefined) {
hash = mixString(hash, field.extendedTypeName);
}
if (field.extendedFormat !== undefined) {
hash = mixString(hash, field.extendedFormat);
}
if (includeTable) {
hash = mixString(hash, field.table);
}
}
// small non-negative integers keep the Map keys as Smis
return hash & 0x3fffffff;
}
function sameList(a, b) {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
class Entry {
constructor(parser, type, fields, options, config) {
this.parser = parser;
this.nestTables = nestTablesKey(options.nestTables);
this.dateStrings = dateStringsKey(
options.dateStrings || config.dateStrings
);
this.optionBits = optionBits(
type,
options,
config,
this.nestTables,
this.dateStrings
);
this.timezone = String(options.timezone || config.timezone);
const count = fields.length;
this.columnTypes = new Array(count);
this.characterSets = new Array(count);
this.flags = new Array(count);
this.decimals = new Array(count);
this.names = new Array(count);
this.extendedTypeNames = new Array(count);
this.extendedFormats = new Array(count);
this.tables = this.nestTables === false ? null : new Array(count);
for (let i = 0; i < count; ++i) {
const field = fields[i];
this.columnTypes[i] = field.columnType;
this.characterSets[i] = field.characterSet;
this.flags[i] = field.flags;
this.decimals[i] = field.decimals;
this.names[i] = field.name;
this.extendedTypeNames[i] = field.extendedTypeName;
this.extendedFormats[i] = field.extendedFormat;
if (this.tables !== null) {
this.tables[i] = field.table;
}
}
}
matches(type, fields, options, config) {
const nestTables = nestTablesKey(options.nestTables);
const dateStrings = dateStringsKey(
options.dateStrings || config.dateStrings
);
if (
this.optionBits !==
optionBits(type, options, config, nestTables, dateStrings) ||
this.nestTables !== nestTables ||
this.timezone !== String(options.timezone || config.timezone) ||
this.names.length !== fields.length
) {
return false;
}
// equal option bits already guarantee both sides are lists (or the
// same boolean), so only the list contents are left to compare
if (
Array.isArray(dateStrings) &&
!sameList(this.dateStrings, dateStrings)
) {
return false;
}
for (let i = 0; i < fields.length; ++i) {
const field = fields[i];
if (
this.columnTypes[i] !== field.columnType ||
this.characterSets[i] !== field.characterSet ||
this.flags[i] !== field.flags ||
this.decimals[i] !== field.decimals ||
this.names[i] !== field.name ||
this.extendedTypeNames[i] !== field.extendedTypeName ||
this.extendedFormats[i] !== field.extendedFormat ||
(this.tables !== null && this.tables[i] !== field.table)
) {
return false;
}
}
return true;
}
}
function getParser(type, fields, options, config, compiler) {
const hash = hashKey(type, fields, options, config);
let entries = parserCache.get(hash);
if (entries !== undefined) {
for (let i = 0; i < entries.length; ++i) {
if (entries[i].matches(type, fields, options, config)) {
return entries[i].parser;
}
}
}
const parser = compiler(fields, options, config);
if (entries === undefined) {
entries = [];
parserCache.set(hash, entries);
}
entries.push(new Entry(parser, type, fields, options, config));
return parser;
}
function setMaxCache(max) {
parserCache.resize(max);
}
function clearCache() {
parserCache.clear();
}
module.exports = {
getParser: getParser,
setMaxCache: setMaxCache,
clearCache: clearCache,
_hashKey: hashKey,
_Entry: Entry,
};