binary_parser.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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 =
  14. options.supportBigNumbers || config.supportBigNumbers;
  15. const bigNumberStrings = options.bigNumberStrings || config.bigNumberStrings;
  16. const timezone = options.timezone || config.timezone;
  17. const dateStrings = options.dateStrings || config.dateStrings;
  18. const unsigned = field.flags & FieldFlags.UNSIGNED;
  19. switch (field.columnType) {
  20. case Types.TINY:
  21. return unsigned ? 'packet.readInt8();' : 'packet.readSInt8();';
  22. case Types.SHORT:
  23. return unsigned ? 'packet.readInt16();' : 'packet.readSInt16();';
  24. case Types.LONG:
  25. case Types.INT24: // in binary protocol int24 is encoded in 4 bytes int32
  26. return unsigned ? 'packet.readInt32();' : 'packet.readSInt32();';
  27. case Types.YEAR:
  28. return 'packet.readInt16()';
  29. case Types.FLOAT:
  30. return 'packet.readFloat();';
  31. case Types.DOUBLE:
  32. return 'packet.readDouble();';
  33. case Types.NULL:
  34. return 'null;';
  35. case Types.DATE:
  36. case Types.DATETIME:
  37. case Types.TIMESTAMP:
  38. case Types.NEWDATE:
  39. if (helpers.typeMatch(field.columnType, dateStrings, Types)) {
  40. return `packet.readDateTimeString(${field.decimals});`;
  41. }
  42. return `packet.readDateTime('${timezone}');`;
  43. case Types.TIME:
  44. return 'packet.readTimeString()';
  45. case Types.DECIMAL:
  46. case Types.NEWDECIMAL:
  47. if (config.decimalNumbers) {
  48. return 'packet.parseLengthCodedFloat();';
  49. }
  50. return 'packet.readLengthCodedString("ascii");';
  51. case Types.GEOMETRY:
  52. return 'packet.parseGeometryValue();';
  53. case Types.JSON:
  54. // Since for JSON columns mysql always returns charset 63 (BINARY),
  55. // we have to handle it according to JSON specs and use "utf8",
  56. // see https://github.com/sidorares/node-mysql2/issues/409
  57. return 'JSON.parse(packet.readLengthCodedString("utf8"));';
  58. case Types.LONGLONG:
  59. if (!supportBigNumbers) {
  60. return unsigned
  61. ? 'packet.readInt64JSNumber();'
  62. : 'packet.readSInt64JSNumber();';
  63. }
  64. if (bigNumberStrings) {
  65. return unsigned
  66. ? 'packet.readInt64String();'
  67. : 'packet.readSInt64String();';
  68. }
  69. return unsigned ? 'packet.readInt64();' : 'packet.readSInt64();';
  70. default:
  71. if (field.characterSet === Charsets.BINARY) {
  72. return 'packet.readLengthCodedBuffer();';
  73. }
  74. return `packet.readLengthCodedString(fields[${fieldNum}].encoding)`;
  75. }
  76. }
  77. function compile(fields, options, config) {
  78. const parserFn = genFunc();
  79. let i = 0;
  80. const nullBitmapLength = Math.floor((fields.length + 7 + 2) / 8);
  81. /* eslint-disable no-trailing-spaces */
  82. /* eslint-disable no-spaced-func */
  83. /* eslint-disable no-unexpected-multiline */
  84. parserFn('(function(){');
  85. parserFn('return class BinaryRow {');
  86. parserFn('constructor() {');
  87. parserFn('}');
  88. parserFn('next(packet, fields, options) {');
  89. if (options.rowsAsArray) {
  90. parserFn(`const result = new Array(${fields.length});`);
  91. } else {
  92. parserFn("const result = {};");
  93. }
  94. const resultTables = {};
  95. let resultTablesArray = [];
  96. if (options.nestTables === true) {
  97. for (i = 0; i < fields.length; i++) {
  98. resultTables[fields[i].table] = 1;
  99. }
  100. resultTablesArray = Object.keys(resultTables);
  101. for (i = 0; i < resultTablesArray.length; i++) {
  102. parserFn(`result[${helpers.srcEscape(resultTablesArray[i])}] = {};`);
  103. }
  104. }
  105. parserFn('packet.readInt8();'); // status byte
  106. for (i = 0; i < nullBitmapLength; ++i) {
  107. parserFn(`const nullBitmaskByte${i} = packet.readInt8();`);
  108. }
  109. let lvalue = '';
  110. let currentFieldNullBit = 4;
  111. let nullByteIndex = 0;
  112. let fieldName = '';
  113. let tableName = '';
  114. for (i = 0; i < fields.length; i++) {
  115. fieldName = helpers.srcEscape(fields[i].name);
  116. parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
  117. if (typeof options.nestTables === 'string') {
  118. tableName = helpers.srcEscape(fields[i].table);
  119. lvalue = `result[${helpers.srcEscape(
  120. fields[i].table + options.nestTables + fields[i].name
  121. )}]`;
  122. } else if (options.nestTables === true) {
  123. tableName = helpers.srcEscape(fields[i].table);
  124. lvalue = `result[${tableName}][${fieldName}]`;
  125. } else if (options.rowsAsArray) {
  126. lvalue = `result[${i.toString(10)}]`;
  127. } else {
  128. lvalue = `result[${helpers.srcEscape(fields[i].name)}]`;
  129. }
  130. // TODO: this used to be an optimisation ( if column marked as NOT_NULL don't include code to check null
  131. // bitmap at all, but it seems that we can't rely on this flag, see #178
  132. // TODO: benchmark performance difference
  133. //
  134. // if (fields[i].flags & FieldFlags.NOT_NULL) { // don't need to check null bitmap if field can't be null.
  135. // result.push(lvalue + ' = ' + readCodeFor(fields[i], config));
  136. // } else if (fields[i].columnType == Types.NULL) {
  137. // result.push(lvalue + ' = null;');
  138. // } else {
  139. parserFn(`if (nullBitmaskByte${nullByteIndex} & ${currentFieldNullBit})`);
  140. parserFn(`${lvalue} = null;`);
  141. parserFn('else');
  142. parserFn(`${lvalue} = ${readCodeFor(fields[i], config, options, i)}`);
  143. // }
  144. currentFieldNullBit *= 2;
  145. if (currentFieldNullBit === 0x100) {
  146. currentFieldNullBit = 1;
  147. nullByteIndex++;
  148. }
  149. }
  150. parserFn('return result;');
  151. parserFn('}');
  152. parserFn('};')('})()');
  153. /* eslint-enable no-trailing-spaces */
  154. /* eslint-enable no-spaced-func */
  155. /* eslint-enable no-unexpected-multiline */
  156. if (config.debug) {
  157. helpers.printDebugWithCode(
  158. 'Compiled binary protocol row parser',
  159. parserFn.toString()
  160. );
  161. }
  162. return parserFn.toFunction();
  163. }
  164. function getBinaryParser(fields, options, config) {
  165. return parserCache.getParser('binary', fields, options, config, compile);
  166. }
  167. module.exports = getBinaryParser;