Spaces:
Paused
Paused
| ; | |
| const FieldFlags = require('../constants/field_flags.js'); | |
| const Charsets = require('../constants/charsets.js'); | |
| const Types = require('../constants/types.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(field, config, options, fieldNum) { | |
| 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; | |
| const unsigned = field.flags & FieldFlags.UNSIGNED; | |
| switch (field.columnType) { | |
| case Types.TINY: | |
| return unsigned ? 'packet.readInt8();' : 'packet.readSInt8();'; | |
| case Types.SHORT: | |
| return unsigned ? 'packet.readInt16();' : 'packet.readSInt16();'; | |
| case Types.LONG: | |
| case Types.INT24: // in binary protocol int24 is encoded in 4 bytes int32 | |
| return unsigned ? 'packet.readInt32();' : 'packet.readSInt32();'; | |
| case Types.YEAR: | |
| return 'packet.readInt16()'; | |
| case Types.FLOAT: | |
| return 'packet.readFloat();'; | |
| case Types.DOUBLE: | |
| return 'packet.readDouble();'; | |
| case Types.NULL: | |
| return 'null;'; | |
| case Types.DATE: | |
| case Types.DATETIME: | |
| case Types.TIMESTAMP: | |
| case Types.NEWDATE: | |
| if (helpers.typeMatch(field.columnType, dateStrings, Types)) { | |
| return `packet.readDateTimeString(${parseInt(field.decimals, 10)});`; | |
| } | |
| return `packet.readDateTime(${helpers.srcEscape(timezone)});`; | |
| case Types.TIME: | |
| return 'packet.readTimeString()'; | |
| case Types.DECIMAL: | |
| case Types.NEWDECIMAL: | |
| if (config.decimalNumbers) { | |
| return 'packet.parseLengthCodedFloat();'; | |
| } | |
| 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"));'; | |
| case Types.LONGLONG: | |
| if (!supportBigNumbers) { | |
| return unsigned | |
| ? 'packet.readInt64JSNumber();' | |
| : 'packet.readSInt64JSNumber();'; | |
| } | |
| if (bigNumberStrings) { | |
| return unsigned | |
| ? 'packet.readInt64String();' | |
| : 'packet.readSInt64String();'; | |
| } | |
| return unsigned ? 'packet.readInt64();' : 'packet.readSInt64();'; | |
| default: | |
| if (field.characterSet === Charsets.BINARY) { | |
| return 'packet.readLengthCodedBuffer();'; | |
| } | |
| return `packet.readLengthCodedString(fields[${fieldNum}].encoding)`; | |
| } | |
| } | |
| function compile(fields, options, config) { | |
| const parserFn = genFunc(); | |
| const nullBitmapLength = Math.floor((fields.length + 7 + 2) / 8); | |
| function wrap(field, packet) { | |
| 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 packet.readLengthCodedString(encoding); | |
| }, | |
| buffer: function () { | |
| return packet.readLengthCodedBuffer(); | |
| }, | |
| geometry: function () { | |
| return packet.parseGeometryValue(); | |
| }, | |
| }; | |
| } | |
| parserFn('(function(){'); | |
| parserFn('return class BinaryRow {'); | |
| parserFn('constructor() {'); | |
| parserFn('}'); | |
| parserFn('next(packet, fields, options) {'); | |
| if (options.rowsAsArray) { | |
| parserFn(`const result = new Array(${fields.length});`); | |
| } else { | |
| parserFn('const result = {};'); | |
| } | |
| // Global typeCast | |
| if ( | |
| typeof config.typeCast === 'function' && | |
| typeof options.typeCast !== 'function' | |
| ) { | |
| options.typeCast = config.typeCast; | |
| } | |
| parserFn('packet.readInt8();'); // status byte | |
| for (let i = 0; i < nullBitmapLength; ++i) { | |
| parserFn(`const nullBitmaskByte${i} = packet.readInt8();`); | |
| } | |
| let lvalue = ''; | |
| let currentFieldNullBit = 4; | |
| let nullByteIndex = 0; | |
| let fieldName = ''; | |
| let tableName = ''; | |
| 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) { | |
| tableName = helpers.srcEscape(fields[i].table); | |
| parserFn(`if (!result[${tableName}]) result[${tableName}] = {};`); | |
| lvalue = `result[${tableName}][${fieldName}]`; | |
| } else if (options.rowsAsArray) { | |
| lvalue = `result[${i.toString(10)}]`; | |
| } else { | |
| lvalue = `result[${fieldName}]`; | |
| } | |
| parserFn(`if (nullBitmaskByte${nullByteIndex} & ${currentFieldNullBit}) `); | |
| parserFn(`${lvalue} = null;`); | |
| parserFn('else {'); | |
| if (options.typeCast === false) { | |
| parserFn(`${lvalue} = packet.readLengthCodedBuffer();`); | |
| } else { | |
| const fieldWrapperVar = `fieldWrapper${i}`; | |
| parserFn(`const ${fieldWrapperVar} = wrap(fields[${i}], packet);`); | |
| const readCode = readCodeFor(fields[i], config, options, i); | |
| if (typeof options.typeCast === 'function') { | |
| parserFn( | |
| `${lvalue} = options.typeCast(${fieldWrapperVar}, function() { return ${readCode} });`, | |
| ); | |
| } else { | |
| parserFn(`${lvalue} = ${readCode};`); | |
| } | |
| } | |
| parserFn('}'); | |
| currentFieldNullBit *= 2; | |
| if (currentFieldNullBit === 0x100) { | |
| currentFieldNullBit = 1; | |
| nullByteIndex++; | |
| } | |
| } | |
| parserFn('return result;'); | |
| parserFn('}'); | |
| parserFn('};')('})()'); | |
| if (config.debug) { | |
| helpers.printDebugWithCode( | |
| 'Compiled binary protocol row parser', | |
| parserFn.toString(), | |
| ); | |
| } | |
| return parserFn.toFunction({ wrap }); | |
| } | |
| function getBinaryParser(fields, options, config) { | |
| return parserCache.getParser('binary', fields, options, config, compile); | |
| } | |
| module.exports = getBinaryParser; | |