text_parser.js 6.8 KB

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