Spaces:
Running
Running
; | |
const Types = require('../constants/types.js'); | |
const Charsets = require('../constants/charsets.js'); | |
const helpers = require('../helpers'); | |
const genFunc = require('generate-function'); | |
const parserCache = require('./parser_cache.js'); | |
const typeNames = []; | |
for (const t in Types) { | |
typeNames[Types[t]] = t; | |
} | |
function readCodeFor(type, charset, encodingExpr, config, options) { | |
const supportBigNumbers = Boolean( | |
options.supportBigNumbers || config.supportBigNumbers, | |
); | |
const bigNumberStrings = Boolean( | |
options.bigNumberStrings || config.bigNumberStrings, | |
); | |
const timezone = options.timezone || config.timezone; | |
const dateStrings = options.dateStrings || config.dateStrings; | |
switch (type) { | |
case Types.TINY: | |
case Types.SHORT: | |
case Types.LONG: | |
case Types.INT24: | |
case Types.YEAR: | |
return 'packet.parseLengthCodedIntNoBigCheck()'; | |
case Types.LONGLONG: | |
if (supportBigNumbers && bigNumberStrings) { | |
return 'packet.parseLengthCodedIntString()'; | |
} | |
return `packet.parseLengthCodedInt(${supportBigNumbers})`; | |
case Types.FLOAT: | |
case Types.DOUBLE: | |
return 'packet.parseLengthCodedFloat()'; | |
case Types.NULL: | |
return 'packet.readLengthCodedNumber()'; | |
case Types.DECIMAL: | |
case Types.NEWDECIMAL: | |
if (config.decimalNumbers) { | |
return 'packet.parseLengthCodedFloat()'; | |
} | |
return 'packet.readLengthCodedString("ascii")'; | |
case Types.DATE: | |
if (helpers.typeMatch(type, dateStrings, Types)) { | |
return 'packet.readLengthCodedString("ascii")'; | |
} | |
return `packet.parseDate(${helpers.srcEscape(timezone)})`; | |
case Types.DATETIME: | |
case Types.TIMESTAMP: | |
if (helpers.typeMatch(type, dateStrings, Types)) { | |
return 'packet.readLengthCodedString("ascii")'; | |
} | |
return `packet.parseDateTime(${helpers.srcEscape(timezone)})`; | |
case Types.TIME: | |
return 'packet.readLengthCodedString("ascii")'; | |
case Types.GEOMETRY: | |
return 'packet.parseGeometryValue()'; | |
case Types.JSON: | |
// Since for JSON columns mysql always returns charset 63 (BINARY), | |
// we have to handle it according to JSON specs and use "utf8", | |
// see https://github.com/sidorares/node-mysql2/issues/409 | |
return 'JSON.parse(packet.readLengthCodedString("utf8"))'; | |
default: | |
if (charset === Charsets.BINARY) { | |
return 'packet.readLengthCodedBuffer()'; | |
} | |
return `packet.readLengthCodedString(${encodingExpr})`; | |
} | |
} | |
function compile(fields, options, config) { | |
// use global typeCast if current query doesn't specify one | |
if ( | |
typeof config.typeCast === 'function' && | |
typeof options.typeCast !== 'function' | |
) { | |
options.typeCast = config.typeCast; | |
} | |
function wrap(field, _this) { | |
return { | |
type: typeNames[field.columnType], | |
length: field.columnLength, | |
db: field.schema, | |
table: field.table, | |
name: field.name, | |
string: function (encoding = field.encoding) { | |
if (field.columnType === Types.JSON && encoding === field.encoding) { | |
// Since for JSON columns mysql always returns charset 63 (BINARY), | |
// we have to handle it according to JSON specs and use "utf8", | |
// see https://github.com/sidorares/node-mysql2/issues/1661 | |
console.warn( | |
`typeCast: JSON column "${field.name}" is interpreted as BINARY by default, recommended to manually set utf8 encoding: \`field.string("utf8")\``, | |
); | |
} | |
return _this.packet.readLengthCodedString(encoding); | |
}, | |
buffer: function () { | |
return _this.packet.readLengthCodedBuffer(); | |
}, | |
geometry: function () { | |
return _this.packet.parseGeometryValue(); | |
}, | |
}; | |
} | |
const parserFn = genFunc(); | |
parserFn('(function () {')('return class TextRow {'); | |
// constructor method | |
parserFn('constructor(fields) {'); | |
// node-mysql typeCast compatibility wrapper | |
// see https://github.com/mysqljs/mysql/blob/96fdd0566b654436624e2375c7b6604b1f50f825/lib/protocol/packets/Field.js | |
if (typeof options.typeCast === 'function') { | |
parserFn('const _this = this;'); | |
parserFn('for(let i=0; i<fields.length; ++i) {'); | |
parserFn('this[`wrap${i}`] = wrap(fields[i], _this);'); | |
parserFn('}'); | |
} | |
parserFn('}'); | |
// next method | |
parserFn('next(packet, fields, options) {'); | |
parserFn('this.packet = packet;'); | |
if (options.rowsAsArray) { | |
parserFn(`const result = new Array(${fields.length});`); | |
} else { | |
parserFn('const result = {};'); | |
} | |
const resultTables = {}; | |
let resultTablesArray = []; | |
if (options.nestTables === true) { | |
for (let i = 0; i < fields.length; i++) { | |
resultTables[fields[i].table] = 1; | |
} | |
resultTablesArray = Object.keys(resultTables); | |
for (let i = 0; i < resultTablesArray.length; i++) { | |
parserFn(`result[${helpers.srcEscape(resultTablesArray[i])}] = {};`); | |
} | |
} | |
let lvalue = ''; | |
let fieldName = ''; | |
for (let i = 0; i < fields.length; i++) { | |
fieldName = helpers.srcEscape(fields[i].name); | |
if (helpers.privateObjectProps.has(fields[i].name)) { | |
throw new Error( | |
`The field name (${fieldName}) can't be the same as an object's private property.`, | |
); | |
} | |
parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`); | |
if (typeof options.nestTables === 'string') { | |
lvalue = `result[${helpers.srcEscape( | |
fields[i].table + options.nestTables + fields[i].name, | |
)}]`; | |
} else if (options.nestTables === true) { | |
lvalue = `result[${helpers.srcEscape(fields[i].table)}][${fieldName}]`; | |
} else if (options.rowsAsArray) { | |
lvalue = `result[${i.toString(10)}]`; | |
} else { | |
lvalue = `result[${fieldName}]`; | |
} | |
if (options.typeCast === false) { | |
parserFn(`${lvalue} = packet.readLengthCodedBuffer();`); | |
} else { | |
const encodingExpr = `fields[${i}].encoding`; | |
const readCode = readCodeFor( | |
fields[i].columnType, | |
fields[i].characterSet, | |
encodingExpr, | |
config, | |
options, | |
); | |
if (typeof options.typeCast === 'function') { | |
parserFn( | |
`${lvalue} = options.typeCast(this.wrap${i}, function() { return ${readCode} });`, | |
); | |
} else { | |
parserFn(`${lvalue} = ${readCode};`); | |
} | |
} | |
} | |
parserFn('return result;'); | |
parserFn('}'); | |
parserFn('};')('})()'); | |
if (config.debug) { | |
helpers.printDebugWithCode( | |
'Compiled text protocol row parser', | |
parserFn.toString(), | |
); | |
} | |
if (typeof options.typeCast === 'function') { | |
return parserFn.toFunction({ wrap }); | |
} | |
return parserFn.toFunction(); | |
} | |
function getTextParser(fields, options, config) { | |
return parserCache.getParser('text', fields, options, config, compile); | |
} | |
module.exports = getTextParser; | |