execute.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. 'use strict';
  2. const CursorType = require('../constants/cursor');
  3. const CommandCodes = require('../constants/commands');
  4. const Types = require('../constants/types');
  5. const Packet = require('../packets/packet');
  6. const CharsetToEncoding = require('../constants/charset_encodings.js');
  7. function isJSON(value) {
  8. return (
  9. Array.isArray(value) ||
  10. value.constructor === Object ||
  11. (typeof value.toJSON === 'function' && !Buffer.isBuffer(value))
  12. );
  13. }
  14. /**
  15. * Converts a value to an object describing type, String/Buffer representation and length
  16. * @param {*} value
  17. */
  18. function toParameter(value, encoding, timezone) {
  19. let type = Types.VAR_STRING;
  20. let length;
  21. let writer = function (value) {
  22. // eslint-disable-next-line no-invalid-this
  23. return Packet.prototype.writeLengthCodedString.call(this, value, encoding);
  24. };
  25. if (value !== null) {
  26. switch (typeof value) {
  27. case 'undefined':
  28. throw new TypeError('Bind parameters must not contain undefined');
  29. case 'number':
  30. type = Types.DOUBLE;
  31. length = 8;
  32. writer = Packet.prototype.writeDouble;
  33. break;
  34. case 'boolean':
  35. value = value | 0;
  36. type = Types.TINY;
  37. length = 1;
  38. writer = Packet.prototype.writeInt8;
  39. break;
  40. case 'object':
  41. if (Object.prototype.toString.call(value) === '[object Date]') {
  42. type = Types.DATETIME;
  43. length = 12;
  44. writer = function (value) {
  45. // eslint-disable-next-line no-invalid-this
  46. return Packet.prototype.writeDate.call(this, value, timezone);
  47. };
  48. } else if (isJSON(value)) {
  49. value = JSON.stringify(value);
  50. type = Types.JSON;
  51. } else if (Buffer.isBuffer(value)) {
  52. length = Packet.lengthCodedNumberLength(value.length) + value.length;
  53. writer = Packet.prototype.writeLengthCodedBuffer;
  54. }
  55. break;
  56. default:
  57. value = value.toString();
  58. }
  59. } else {
  60. value = '';
  61. type = Types.NULL;
  62. }
  63. if (!length) {
  64. length = Packet.lengthCodedStringLength(value, encoding);
  65. }
  66. return { value, type, length, writer };
  67. }
  68. class Execute {
  69. constructor(id, parameters, charsetNumber, timezone) {
  70. this.id = id;
  71. this.parameters = parameters;
  72. this.encoding = CharsetToEncoding[charsetNumber];
  73. this.timezone = timezone;
  74. }
  75. static fromPacket(packet, encoding) {
  76. const stmtId = packet.readInt32();
  77. const flags = packet.readInt8();
  78. const iterationCount = packet.readInt32();
  79. let i = packet.offset;
  80. while (i < packet.end - 1) {
  81. if (
  82. (packet.buffer[i + 1] === Types.VAR_STRING ||
  83. packet.buffer[i + 1] === Types.NULL ||
  84. packet.buffer[i + 1] === Types.DOUBLE ||
  85. packet.buffer[i + 1] === Types.TINY ||
  86. packet.buffer[i + 1] === Types.DATETIME ||
  87. packet.buffer[i + 1] === Types.JSON) &&
  88. packet.buffer[i] === 1 &&
  89. packet.buffer[i + 2] === 0
  90. ) {
  91. break;
  92. } else {
  93. packet.readInt8();
  94. }
  95. i++;
  96. }
  97. const types = [];
  98. for (let i = packet.offset + 1; i < packet.end - 1; i++) {
  99. if (
  100. (packet.buffer[i] === Types.VAR_STRING ||
  101. packet.buffer[i] === Types.NULL ||
  102. packet.buffer[i] === Types.DOUBLE ||
  103. packet.buffer[i] === Types.TINY ||
  104. packet.buffer[i] === Types.DATETIME ||
  105. packet.buffer[i] === Types.JSON) &&
  106. packet.buffer[i + 1] === 0
  107. ) {
  108. types.push(packet.buffer[i]);
  109. packet.skip(2);
  110. }
  111. }
  112. packet.skip(1);
  113. const values = [];
  114. for (let i = 0; i < types.length; i++) {
  115. if (types[i] === Types.VAR_STRING) {
  116. values.push(packet.readLengthCodedString(encoding));
  117. } else if (types[i] === Types.DOUBLE) {
  118. values.push(packet.readDouble());
  119. } else if (types[i] === Types.TINY) {
  120. values.push(packet.readInt8());
  121. } else if (types[i] === Types.DATETIME) {
  122. values.push(packet.readDateTime());
  123. } else if (types[i] === Types.JSON) {
  124. values.push(JSON.parse(packet.readLengthCodedString(encoding)));
  125. }
  126. if (types[i] === Types.NULL) {
  127. values.push(null);
  128. }
  129. }
  130. return { stmtId, flags, iterationCount, values };
  131. }
  132. toPacket() {
  133. // TODO: don't try to calculate packet length in advance, allocate some big buffer in advance (header + 256 bytes?)
  134. // and copy + reallocate if not enough
  135. // 0 + 4 - length, seqId
  136. // 4 + 1 - COM_EXECUTE
  137. // 5 + 4 - stmtId
  138. // 9 + 1 - flags
  139. // 10 + 4 - iteration-count (always 1)
  140. let length = 14;
  141. let parameters;
  142. if (this.parameters && this.parameters.length > 0) {
  143. length += Math.floor((this.parameters.length + 7) / 8);
  144. length += 1; // new-params-bound-flag
  145. length += 2 * this.parameters.length; // type byte for each parameter if new-params-bound-flag is set
  146. parameters = this.parameters.map((value) =>
  147. toParameter(value, this.encoding, this.timezone)
  148. );
  149. length += parameters.reduce(
  150. (accumulator, parameter) => accumulator + parameter.length,
  151. 0
  152. );
  153. }
  154. const buffer = Buffer.allocUnsafe(length);
  155. const packet = new Packet(0, buffer, 0, length);
  156. packet.offset = 4;
  157. packet.writeInt8(CommandCodes.STMT_EXECUTE);
  158. packet.writeInt32(this.id);
  159. packet.writeInt8(CursorType.NO_CURSOR); // flags
  160. packet.writeInt32(1); // iteration-count, always 1
  161. if (parameters) {
  162. let bitmap = 0;
  163. let bitValue = 1;
  164. parameters.forEach((parameter) => {
  165. if (parameter.type === Types.NULL) {
  166. bitmap += bitValue;
  167. }
  168. bitValue *= 2;
  169. if (bitValue === 256) {
  170. packet.writeInt8(bitmap);
  171. bitmap = 0;
  172. bitValue = 1;
  173. }
  174. });
  175. if (bitValue !== 1) {
  176. packet.writeInt8(bitmap);
  177. }
  178. // TODO: explain meaning of the flag
  179. // afaik, if set n*2 bytes with type of parameter are sent before parameters
  180. // if not, previous execution types are used (TODO prooflink)
  181. packet.writeInt8(1); // new-params-bound-flag
  182. // Write parameter types
  183. parameters.forEach((parameter) => {
  184. packet.writeInt8(parameter.type); // field type
  185. packet.writeInt8(0); // parameter flag
  186. });
  187. // Write parameter values
  188. parameters.forEach((parameter) => {
  189. if (parameter.type !== Types.NULL) {
  190. parameter.writer.call(packet, parameter.value);
  191. }
  192. });
  193. }
  194. return packet;
  195. }
  196. }
  197. module.exports = Execute;