static_binary_parser.js 7.4 KB

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