query.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. 'use strict';
  2. const CommonText = require('./common-text-cmd');
  3. const Errors = require('../misc/errors');
  4. const Parse = require('../misc/parse');
  5. const QUOTE = 0x27;
  6. /**
  7. * Protocol COM_QUERY
  8. * see : https://mariadb.com/kb/en/library/com_query/
  9. */
  10. class Query extends CommonText {
  11. constructor(resolve, reject, options, connOpts, sql, values) {
  12. super(resolve, reject, options, connOpts, sql, values);
  13. }
  14. /**
  15. * Send COM_QUERY
  16. *
  17. * @param out output writer
  18. * @param opts connection options
  19. * @param info connection information
  20. */
  21. start(out, opts, info) {
  22. if (!this.initialValues) {
  23. //shortcut if no parameters
  24. out.startPacket(this);
  25. out.writeInt8(0x03);
  26. if (!this.handleTimeout(out, info)) return;
  27. out.writeString(this.sql);
  28. out.flushBuffer(true);
  29. this.emit('send_end');
  30. return (this.onPacketReceive = this.readResponsePacket);
  31. }
  32. if (this.opts.namedPlaceholders) {
  33. try {
  34. const parsed = Parse.splitQueryPlaceholder(
  35. this.sql,
  36. info,
  37. this.initialValues,
  38. this.displaySql.bind(this)
  39. );
  40. this.queryParts = parsed.parts;
  41. this.values = parsed.values;
  42. } catch (err) {
  43. this.emit('send_end');
  44. return this.throwError(err, info);
  45. }
  46. } else {
  47. this.queryParts = Parse.splitQuery(this.sql);
  48. this.values = Array.isArray(this.initialValues) ? this.initialValues : [this.initialValues];
  49. if (!this.validateParameters(info)) return;
  50. }
  51. out.startPacket(this);
  52. out.writeInt8(0x03);
  53. if (!this.handleTimeout(out, info)) return;
  54. out.writeString(this.queryParts[0]);
  55. this.onPacketReceive = this.readResponsePacket;
  56. //********************************************
  57. // send params
  58. //********************************************
  59. const len = this.queryParts.length;
  60. for (let i = 1; i < len; i++) {
  61. const value = this.values[i - 1];
  62. if (
  63. value !== null &&
  64. typeof value === 'object' &&
  65. typeof value.pipe === 'function' &&
  66. typeof value.read === 'function'
  67. ) {
  68. this.sending = true;
  69. //********************************************
  70. // param is stream,
  71. // now all params will be written by event
  72. //********************************************
  73. this.registerStreamSendEvent(out, info);
  74. this.currentParam = i;
  75. out.writeInt8(QUOTE); //'
  76. value.on('data', function (chunk) {
  77. out.writeBufferEscape(chunk);
  78. });
  79. value.on(
  80. 'end',
  81. function () {
  82. out.writeInt8(QUOTE); //'
  83. out.writeString(this.queryParts[this.currentParam++]);
  84. this.paramWritten();
  85. }.bind(this)
  86. );
  87. return;
  88. } else {
  89. //********************************************
  90. // param isn't stream. directly write in buffer
  91. //********************************************
  92. this.writeParam(out, value, this.opts, info);
  93. out.writeString(this.queryParts[i]);
  94. }
  95. }
  96. out.flushBuffer(true);
  97. this.emit('send_end');
  98. }
  99. /**
  100. * If timeout is set, prepend query with SET STATEMENT max_statement_time=xx FOR, or throw an error
  101. * @param out buffer
  102. * @param info server information
  103. * @returns {boolean} false if an error has been thrown
  104. */
  105. handleTimeout(out, info) {
  106. if (this.opts.timeout) {
  107. if (info.isMariaDB()) {
  108. if (info.hasMinVersion(10, 1, 2)) {
  109. out.writeString('SET STATEMENT max_statement_time=' + this.opts.timeout / 1000 + ' FOR ');
  110. return true;
  111. } else {
  112. const err = Errors.createError(
  113. 'Cannot use timeout for MariaDB server before 10.1.2. timeout value: ' +
  114. this.opts.timeout,
  115. false,
  116. info,
  117. 'HY000',
  118. Errors.ER_TIMEOUT_NOT_SUPPORTED
  119. );
  120. this.emit('send_end');
  121. this.throwError(err, info);
  122. return false;
  123. }
  124. } else {
  125. //not available for MySQL
  126. // max_execution time exist, but only for select, and as hint
  127. const err = Errors.createError(
  128. 'Cannot use timeout for MySQL server. timeout value: ' + this.opts.timeout,
  129. false,
  130. info,
  131. 'HY000',
  132. Errors.ER_TIMEOUT_NOT_SUPPORTED
  133. );
  134. this.emit('send_end');
  135. this.throwError(err, info);
  136. return false;
  137. }
  138. }
  139. return true;
  140. }
  141. /**
  142. * Validate that parameters exists and are defined.
  143. *
  144. * @param info connection info
  145. * @returns {boolean} return false if any error occur.
  146. */
  147. validateParameters(info) {
  148. //validate parameter size.
  149. if (this.queryParts.length - 1 > this.values.length) {
  150. this.emit('send_end');
  151. this.throwNewError(
  152. 'Parameter at position ' + (this.values.length + 1) + ' is not set\n' + this.displaySql(),
  153. false,
  154. info,
  155. 'HY000',
  156. Errors.ER_MISSING_PARAMETER
  157. );
  158. return false;
  159. }
  160. //validate parameter is defined.
  161. for (let i = 0; i < this.queryParts.length - 1; i++) {
  162. if (this.values[i] === undefined) {
  163. this.emit('send_end');
  164. this.throwNewError(
  165. 'Parameter at position ' + (i + 1) + ' is undefined\n' + this.displaySql(),
  166. false,
  167. info,
  168. 'HY000',
  169. Errors.ER_PARAMETER_UNDEFINED
  170. );
  171. return false;
  172. }
  173. }
  174. return true;
  175. }
  176. /**
  177. * Define params events.
  178. * Each parameter indicate that he is written to socket,
  179. * emitting event so next stream parameter can be written.
  180. */
  181. registerStreamSendEvent(out, info) {
  182. // note : Implementation use recursive calls, but stack won't never get near v8 max call stack size
  183. //since event launched for stream parameter only
  184. this.paramWritten = function () {
  185. while (true) {
  186. if (this.currentParam === this.queryParts.length) {
  187. //********************************************
  188. // all parameters are written.
  189. // flush packet
  190. //********************************************
  191. out.flushBuffer(true);
  192. this.sending = false;
  193. this.emit('send_end');
  194. return;
  195. } else {
  196. const value = this.values[this.currentParam - 1];
  197. if (value === null) {
  198. out.writeStringAscii('NULL');
  199. out.writeString(this.queryParts[this.currentParam++]);
  200. continue;
  201. }
  202. if (
  203. typeof value === 'object' &&
  204. typeof value.pipe === 'function' &&
  205. typeof value.read === 'function'
  206. ) {
  207. //********************************************
  208. // param is stream,
  209. //********************************************
  210. out.writeInt8(QUOTE);
  211. value.once(
  212. 'end',
  213. function () {
  214. out.writeInt8(QUOTE);
  215. out.writeString(this.queryParts[this.currentParam++]);
  216. this.paramWritten();
  217. }.bind(this)
  218. );
  219. value.on('data', function (chunk) {
  220. out.writeBufferEscape(chunk);
  221. });
  222. return;
  223. }
  224. //********************************************
  225. // param isn't stream. directly write in buffer
  226. //********************************************
  227. this.writeParam(out, value, this.opts, info);
  228. out.writeString(this.queryParts[this.currentParam++]);
  229. }
  230. }
  231. }.bind(this);
  232. }
  233. }
  234. module.exports = Query;