text_parser.js 6.7 KB

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