binary_parser.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. 'use strict';
  2. const FieldFlags = require('../constants/field_flags.js');
  3. const Charsets = require('../constants/charsets.js');
  4. const Types = require('../constants/types.js');
  5. const helpers = require('../helpers');
  6. const genFunc = require('generate-function');
  7. const parserCache = require('./parser_cache.js');
  8. const typeNames = [];
  9. for (const t in Types) {
  10. typeNames[Types[t]] = t;
  11. }
  12. function readCodeFor(field, config, options, fieldNum) {
  13. const supportBigNumbers = Boolean(
  14. options.supportBigNumbers || config.supportBigNumbers
  15. );
  16. const bigNumberStrings = Boolean(
  17. options.bigNumberStrings || config.bigNumberStrings
  18. );
  19. const timezone = options.timezone || config.timezone;
  20. const dateStrings = options.dateStrings || config.dateStrings;
  21. const unsigned = field.flags & FieldFlags.UNSIGNED;
  22. switch (field.columnType) {
  23. case Types.TINY:
  24. return unsigned ? 'packet.readInt8();' : 'packet.readSInt8();';
  25. case Types.SHORT:
  26. return unsigned ? 'packet.readInt16();' : 'packet.readSInt16();';
  27. case Types.LONG:
  28. case Types.INT24: // in binary protocol int24 is encoded in 4 bytes int32
  29. return unsigned ? 'packet.readInt32();' : 'packet.readSInt32();';
  30. case Types.YEAR:
  31. return 'packet.readInt16()';
  32. case Types.FLOAT:
  33. return 'packet.readFloat();';
  34. case Types.DOUBLE:
  35. return 'packet.readDouble();';
  36. case Types.NULL:
  37. return 'null;';
  38. case Types.DATE:
  39. case Types.DATETIME:
  40. case Types.TIMESTAMP:
  41. case Types.NEWDATE:
  42. if (helpers.typeMatch(field.columnType, dateStrings, Types)) {
  43. return `packet.readDateTimeString(${parseInt(field.decimals, 10)}, ${null}, ${field.columnType});`;
  44. }
  45. return `packet.readDateTime(${helpers.srcEscape(timezone)});`;
  46. case Types.TIME:
  47. return 'packet.readTimeString()';
  48. case Types.DECIMAL:
  49. case Types.NEWDECIMAL:
  50. if (config.decimalNumbers) {
  51. return 'packet.parseLengthCodedFloat();';
  52. }
  53. return 'packet.readLengthCodedString("ascii");';
  54. case Types.GEOMETRY:
  55. return 'packet.parseGeometryValue();';
  56. case Types.VECTOR:
  57. return 'packet.parseVector()';
  58. case Types.JSON:
  59. // Since for JSON columns mysql always returns charset 63 (BINARY),
  60. // we have to handle it according to JSON specs and use "utf8",
  61. // see https://github.com/sidorares/node-mysql2/issues/409
  62. return config.jsonStrings
  63. ? 'packet.readLengthCodedString("utf8")'
  64. : 'JSON.parse(packet.readLengthCodedString("utf8"));';
  65. case Types.LONGLONG:
  66. if (!supportBigNumbers) {
  67. return unsigned
  68. ? 'packet.readInt64JSNumber();'
  69. : 'packet.readSInt64JSNumber();';
  70. }
  71. if (bigNumberStrings) {
  72. return unsigned
  73. ? 'packet.readInt64String();'
  74. : 'packet.readSInt64String();';
  75. }
  76. return unsigned ? 'packet.readInt64();' : 'packet.readSInt64();';
  77. default:
  78. if (field.characterSet === Charsets.BINARY) {
  79. return 'packet.readLengthCodedBuffer();';
  80. }
  81. return `packet.readLengthCodedString(fields[${fieldNum}].encoding)`;
  82. }
  83. }
  84. function compile(fields, options, config) {
  85. const parserFn = genFunc();
  86. const nullBitmapLength = Math.floor((fields.length + 7 + 2) / 8);
  87. function wrap(field, packet) {
  88. return {
  89. type: typeNames[field.columnType],
  90. length: field.columnLength,
  91. db: field.schema,
  92. table: field.table,
  93. name: field.name,
  94. string: function (encoding = field.encoding) {
  95. if (field.columnType === Types.JSON && encoding === field.encoding) {
  96. // Since for JSON columns mysql always returns charset 63 (BINARY),
  97. // we have to handle it according to JSON specs and use "utf8",
  98. // see https://github.com/sidorares/node-mysql2/issues/1661
  99. console.warn(
  100. `typeCast: JSON column "${field.name}" is interpreted as BINARY by default, recommended to manually set utf8 encoding: \`field.string("utf8")\``
  101. );
  102. }
  103. if (
  104. [Types.DATETIME, Types.NEWDATE, Types.TIMESTAMP, Types.DATE].includes(
  105. field.columnType
  106. )
  107. ) {
  108. return packet.readDateTimeString(
  109. parseInt(field.decimals, 10),
  110. ' ',
  111. field.columnType
  112. );
  113. }
  114. if (field.columnType === Types.TINY) {
  115. const unsigned = field.flags & FieldFlags.UNSIGNED;
  116. return String(unsigned ? packet.readInt8() : packet.readSInt8());
  117. }
  118. if (field.columnType === Types.TIME) {
  119. return packet.readTimeString();
  120. }
  121. return packet.readLengthCodedString(encoding);
  122. },
  123. buffer: function () {
  124. return packet.readLengthCodedBuffer();
  125. },
  126. geometry: function () {
  127. return packet.parseGeometryValue();
  128. },
  129. };
  130. }
  131. parserFn('(function(){');
  132. parserFn('return class BinaryRow {');
  133. parserFn('constructor() {');
  134. parserFn('}');
  135. parserFn('next(packet, fields, options) {');
  136. if (options.rowsAsArray) {
  137. parserFn(`const result = new Array(${fields.length});`);
  138. } else {
  139. parserFn('const result = {};');
  140. }
  141. // Global typeCast
  142. if (
  143. typeof config.typeCast === 'function' &&
  144. typeof options.typeCast !== 'function'
  145. ) {
  146. options.typeCast = config.typeCast;
  147. }
  148. parserFn('packet.readInt8();'); // status byte
  149. for (let i = 0; i < nullBitmapLength; ++i) {
  150. parserFn(`const nullBitmaskByte${i} = packet.readInt8();`);
  151. }
  152. let lvalue = '';
  153. let currentFieldNullBit = 4;
  154. let nullByteIndex = 0;
  155. let fieldName = '';
  156. let tableName = '';
  157. for (let i = 0; i < fields.length; i++) {
  158. fieldName = helpers.fieldEscape(fields[i].name);
  159. // parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
  160. if (typeof options.nestTables === 'string') {
  161. lvalue = `result[${helpers.fieldEscape(fields[i].table + options.nestTables + fields[i].name)}]`;
  162. } else if (options.nestTables === true) {
  163. tableName = helpers.fieldEscape(fields[i].table);
  164. parserFn(`if (!result[${tableName}]) result[${tableName}] = {};`);
  165. lvalue = `result[${tableName}][${fieldName}]`;
  166. } else if (options.rowsAsArray) {
  167. lvalue = `result[${i.toString(10)}]`;
  168. } else {
  169. lvalue = `result[${fieldName}]`;
  170. }
  171. parserFn(`if (nullBitmaskByte${nullByteIndex} & ${currentFieldNullBit}) `);
  172. parserFn(`${lvalue} = null;`);
  173. parserFn('else {');
  174. if (options.typeCast === false) {
  175. parserFn(`${lvalue} = packet.readLengthCodedBuffer();`);
  176. } else {
  177. const fieldWrapperVar = `fieldWrapper${i}`;
  178. parserFn(`const ${fieldWrapperVar} = wrap(fields[${i}], packet);`);
  179. const readCode = readCodeFor(fields[i], config, options, i);
  180. if (typeof options.typeCast === 'function') {
  181. parserFn(
  182. `${lvalue} = options.typeCast(${fieldWrapperVar}, function() { return ${readCode} });`
  183. );
  184. } else {
  185. parserFn(`${lvalue} = ${readCode};`);
  186. }
  187. }
  188. parserFn('}');
  189. currentFieldNullBit *= 2;
  190. if (currentFieldNullBit === 0x100) {
  191. currentFieldNullBit = 1;
  192. nullByteIndex++;
  193. }
  194. }
  195. parserFn('return result;');
  196. parserFn('}');
  197. parserFn('};')('})()');
  198. if (config.debug) {
  199. helpers.printDebugWithCode(
  200. 'Compiled binary protocol row parser',
  201. parserFn.toString()
  202. );
  203. }
  204. return parserFn.toFunction({ wrap });
  205. }
  206. function getBinaryParser(fields, options, config) {
  207. return parserCache.getParser('binary', fields, options, config, compile);
  208. }
  209. module.exports = getBinaryParser;