text_parser.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. 'use strict';
  2. const Types = require('../constants/types.js');
  3. const Charsets = require('../constants/charsets.js');
  4. const helpers = require('../helpers');
  5. const genFunc = require('generate-function');
  6. const parserCache = require('./parser_cache.js');
  7. const typeNames = [];
  8. for (const t in Types) {
  9. typeNames[Types[t]] = t;
  10. }
  11. function readCodeFor(type, charset, encodingExpr, config, options) {
  12. const supportBigNumbers =
  13. options.supportBigNumbers || config.supportBigNumbers;
  14. const bigNumberStrings = options.bigNumberStrings || config.bigNumberStrings;
  15. const timezone = options.timezone || config.timezone;
  16. switch (type) {
  17. case Types.TINY:
  18. case Types.SHORT:
  19. case Types.LONG:
  20. case Types.INT24:
  21. case Types.YEAR:
  22. return 'packet.parseLengthCodedIntNoBigCheck()';
  23. case Types.LONGLONG:
  24. if (supportBigNumbers && bigNumberStrings) {
  25. return 'packet.parseLengthCodedIntString()';
  26. }
  27. return `packet.parseLengthCodedInt(${supportBigNumbers})`;
  28. case Types.FLOAT:
  29. case Types.DOUBLE:
  30. return 'packet.parseLengthCodedFloat()';
  31. case Types.NULL:
  32. return 'packet.readLengthCodedNumber()';
  33. case Types.DECIMAL:
  34. case Types.NEWDECIMAL:
  35. if (config.decimalNumbers) {
  36. return 'packet.parseLengthCodedFloat()';
  37. }
  38. return 'packet.readLengthCodedString("ascii")';
  39. case Types.DATE:
  40. if (config.dateStrings) {
  41. return 'packet.readLengthCodedString("ascii")';
  42. }
  43. return `packet.parseDate('${timezone}')`;
  44. case Types.DATETIME:
  45. case Types.TIMESTAMP:
  46. if (config.dateStrings) {
  47. return 'packet.readLengthCodedString("ascii")';
  48. }
  49. return `packet.parseDateTime('${timezone}')`;
  50. case Types.TIME:
  51. return 'packet.readLengthCodedString("ascii")';
  52. case Types.GEOMETRY:
  53. return 'packet.parseGeometryValue()';
  54. case Types.JSON:
  55. // Since for JSON columns mysql always returns charset 63 (BINARY),
  56. // we have to handle it according to JSON specs and use "utf8",
  57. // see https://github.com/sidorares/node-mysql2/issues/409
  58. return 'JSON.parse(packet.readLengthCodedString("utf8"))';
  59. default:
  60. if (charset === Charsets.BINARY) {
  61. return 'packet.readLengthCodedBuffer()';
  62. }
  63. return `packet.readLengthCodedString(${encodingExpr})`;
  64. }
  65. }
  66. function compile(fields, options, config) {
  67. // node-mysql typeCast compatibility wrapper
  68. // see https://github.com/mysqljs/mysql/blob/96fdd0566b654436624e2375c7b6604b1f50f825/lib/protocol/packets/Field.js
  69. function wrap(field, type, packet, encoding) {
  70. return {
  71. type: type,
  72. length: field.columnLength,
  73. db: field.schema,
  74. table: field.table,
  75. name: field.name,
  76. string: function() {
  77. return packet.readLengthCodedString(encoding);
  78. },
  79. buffer: function() {
  80. return packet.readLengthCodedBuffer();
  81. },
  82. geometry: function() {
  83. return packet.parseGeometryValue();
  84. }
  85. };
  86. }
  87. // use global typeCast if current query doesn't specify one
  88. if (
  89. typeof config.typeCast === 'function' &&
  90. typeof options.typeCast !== 'function'
  91. ) {
  92. options.typeCast = config.typeCast;
  93. }
  94. const parserFn = genFunc();
  95. let i = 0;
  96. /* eslint-disable no-trailing-spaces */
  97. /* eslint-disable no-spaced-func */
  98. /* eslint-disable no-unexpected-multiline */
  99. parserFn('(function () {')(
  100. 'return function TextRow(packet, fields, options, CharsetToEncoding) {'
  101. );
  102. if (options.rowsAsArray) {
  103. parserFn(`const result = new Array(${fields.length})`);
  104. }
  105. if (typeof options.typeCast === 'function') {
  106. parserFn(`const wrap = ${wrap.toString()}`);
  107. }
  108. const resultTables = {};
  109. let resultTablesArray = [];
  110. if (options.nestTables === true) {
  111. for (i = 0; i < fields.length; i++) {
  112. resultTables[fields[i].table] = 1;
  113. }
  114. resultTablesArray = Object.keys(resultTables);
  115. for (i = 0; i < resultTablesArray.length; i++) {
  116. parserFn(`this[${helpers.srcEscape(resultTablesArray[i])}] = {};`);
  117. }
  118. }
  119. let lvalue = '';
  120. let fieldName = '';
  121. for (i = 0; i < fields.length; i++) {
  122. fieldName = helpers.srcEscape(fields[i].name);
  123. parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
  124. if (typeof options.nestTables === 'string') {
  125. lvalue = `this[${helpers.srcEscape(
  126. fields[i].table + options.nestTables + fields[i].name
  127. )}]`;
  128. } else if (options.nestTables === true) {
  129. lvalue = `this[${helpers.srcEscape(fields[i].table)}][${fieldName}]`;
  130. } else if (options.rowsAsArray) {
  131. lvalue = `result[${i.toString(10)}]`;
  132. } else {
  133. lvalue = `this[${fieldName}]`;
  134. }
  135. const encodingExpr = `CharsetToEncoding[fields[${i}].characterSet]`;
  136. const readCode = readCodeFor(
  137. fields[i].columnType,
  138. fields[i].characterSet,
  139. encodingExpr,
  140. config,
  141. options
  142. );
  143. if (typeof options.typeCast === 'function') {
  144. parserFn(
  145. `${lvalue} = options.typeCast(wrap(fields[${i}], ${helpers.srcEscape(
  146. typeNames[fields[i].columnType]
  147. )}, packet, ${encodingExpr}), function() { return ${readCode};})`
  148. );
  149. } else if (options.typeCast === false) {
  150. parserFn(`${lvalue} = packet.readLengthCodedBuffer();`);
  151. } else {
  152. parserFn(`${lvalue} = ${readCode};`);
  153. }
  154. }
  155. if (options.rowsAsArray) {
  156. parserFn('return result;');
  157. }
  158. parserFn('};')('})()');
  159. /* eslint-enable no-trailing-spaces */
  160. /* eslint-enable no-spaced-func */
  161. /* eslint-enable no-unexpected-multiline */
  162. if (config.debug) {
  163. helpers.printDebugWithCode(
  164. 'Compiled text protocol row parser',
  165. parserFn.toString()
  166. );
  167. }
  168. return parserFn.toFunction();
  169. }
  170. function getTextParser(fields, options, config) {
  171. return parserCache.getParser('text', fields, options, config, compile);
  172. }
  173. module.exports = getTextParser;