common-text-cmd.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. 'use strict';
  2. const ResultSet = require('./resultset');
  3. const FieldDetail = require('../const/field-detail');
  4. const FieldType = require('../const/field-type');
  5. const Long = require('long');
  6. const moment = require('moment-timezone');
  7. const QUOTE = 0x27;
  8. class CommonText extends ResultSet {
  9. constructor(resolve, reject, cmdOpts, connOpts, sql, values) {
  10. super(resolve, reject);
  11. this.configAssign(connOpts, cmdOpts);
  12. this.sql = sql;
  13. this.initialValues = values;
  14. this.getDateQuote = this.opts.tz
  15. ? this.opts.tz === 'Etc/UTC'
  16. ? CommonText.getUtcDate
  17. : CommonText.getTimezoneDate
  18. : CommonText.getLocalDate;
  19. }
  20. /**
  21. * Write (and escape) current parameter value to output writer
  22. *
  23. * @param out output writer
  24. * @param value current parameter
  25. * @param opts connection options
  26. * @param info connection information
  27. */
  28. writeParam(out, value, opts, info) {
  29. switch (typeof value) {
  30. case 'boolean':
  31. out.writeStringAscii(value ? 'true' : 'false');
  32. break;
  33. case 'bigint':
  34. case 'number':
  35. out.writeStringAscii('' + value);
  36. break;
  37. case 'object':
  38. if (value === null) {
  39. out.writeStringAscii('NULL');
  40. } else if (Object.prototype.toString.call(value) === '[object Date]') {
  41. out.writeStringAscii(this.getDateQuote(value, opts));
  42. } else if (Buffer.isBuffer(value)) {
  43. out.writeStringAscii("_BINARY '");
  44. out.writeBufferEscape(value);
  45. out.writeInt8(QUOTE);
  46. } else if (typeof value.toSqlString === 'function') {
  47. out.writeStringEscapeQuote(String(value.toSqlString()));
  48. } else if (Long.isLong(value)) {
  49. out.writeStringAscii(value.toString());
  50. } else if (Array.isArray(value)) {
  51. if (opts.arrayParenthesis) {
  52. out.writeStringAscii('(');
  53. }
  54. for (let i = 0; i < value.length; i++) {
  55. if (i !== 0) out.writeStringAscii(',');
  56. this.writeParam(out, value[i], opts, info);
  57. }
  58. if (opts.arrayParenthesis) {
  59. out.writeStringAscii(')');
  60. }
  61. } else {
  62. if (
  63. value.type != null &&
  64. [
  65. 'Point',
  66. 'LineString',
  67. 'Polygon',
  68. 'MultiPoint',
  69. 'MultiLineString',
  70. 'MultiPolygon',
  71. 'GeometryCollection'
  72. ].includes(value.type)
  73. ) {
  74. //GeoJSON format.
  75. let prefix =
  76. (info.isMariaDB() && info.hasMinVersion(10, 1, 4)) ||
  77. (!info.isMariaDB() && info.hasMinVersion(5, 7, 6))
  78. ? 'ST_'
  79. : '';
  80. switch (value.type) {
  81. case 'Point':
  82. out.writeStringAscii(
  83. prefix +
  84. "PointFromText('POINT(" +
  85. CommonText.geoPointToString(value.coordinates) +
  86. ")')"
  87. );
  88. break;
  89. case 'LineString':
  90. out.writeStringAscii(
  91. prefix +
  92. "LineFromText('LINESTRING(" +
  93. CommonText.geoArrayPointToString(value.coordinates) +
  94. ")')"
  95. );
  96. break;
  97. case 'Polygon':
  98. out.writeStringAscii(
  99. prefix +
  100. "PolygonFromText('POLYGON(" +
  101. CommonText.geoMultiArrayPointToString(value.coordinates) +
  102. ")')"
  103. );
  104. break;
  105. case 'MultiPoint':
  106. out.writeStringAscii(
  107. prefix +
  108. "MULTIPOINTFROMTEXT('MULTIPOINT(" +
  109. CommonText.geoArrayPointToString(value.coordinates) +
  110. ")')"
  111. );
  112. break;
  113. case 'MultiLineString':
  114. out.writeStringAscii(
  115. prefix +
  116. "MLineFromText('MULTILINESTRING(" +
  117. CommonText.geoMultiArrayPointToString(value.coordinates) +
  118. ")')"
  119. );
  120. break;
  121. case 'MultiPolygon':
  122. out.writeStringAscii(
  123. prefix +
  124. "MPolyFromText('MULTIPOLYGON(" +
  125. CommonText.geoMultiPolygonToString(value.coordinates) +
  126. ")')"
  127. );
  128. break;
  129. case 'GeometryCollection':
  130. out.writeStringAscii(
  131. prefix +
  132. "GeomCollFromText('GEOMETRYCOLLECTION(" +
  133. CommonText.geometricCollectionToString(value.geometries) +
  134. ")')"
  135. );
  136. break;
  137. }
  138. } else {
  139. if (opts.permitSetMultiParamEntries) {
  140. let first = true;
  141. for (let key in value) {
  142. const val = value[key];
  143. if (typeof val === 'function') continue;
  144. if (first) {
  145. first = false;
  146. } else {
  147. out.writeStringAscii(',');
  148. }
  149. out.writeString('`' + key + '`');
  150. out.writeStringAscii('=');
  151. this.writeParam(out, val, opts, info);
  152. }
  153. if (first) out.writeStringEscapeQuote(JSON.stringify(value));
  154. } else {
  155. out.writeStringEscapeQuote(JSON.stringify(value));
  156. }
  157. }
  158. }
  159. break;
  160. default:
  161. out.writeStringEscapeQuote(value);
  162. }
  163. }
  164. static geometricCollectionToString(geo) {
  165. if (!geo) return '';
  166. let st = '';
  167. for (let i = 0; i < geo.length; i++) {
  168. //GeoJSON format.
  169. st += i !== 0 ? ',' : '';
  170. switch (geo[i].type) {
  171. case 'Point':
  172. st += 'POINT(' + CommonText.geoPointToString(geo[i].coordinates) + ')';
  173. break;
  174. case 'LineString':
  175. st += 'LINESTRING(' + CommonText.geoArrayPointToString(geo[i].coordinates) + ')';
  176. break;
  177. case 'Polygon':
  178. st += 'POLYGON(' + CommonText.geoMultiArrayPointToString(geo[i].coordinates) + ')';
  179. break;
  180. case 'MultiPoint':
  181. st += 'MULTIPOINT(' + CommonText.geoArrayPointToString(geo[i].coordinates) + ')';
  182. break;
  183. case 'MultiLineString':
  184. st +=
  185. 'MULTILINESTRING(' + CommonText.geoMultiArrayPointToString(geo[i].coordinates) + ')';
  186. break;
  187. case 'MultiPolygon':
  188. st += 'MULTIPOLYGON(' + CommonText.geoMultiPolygonToString(geo[i].coordinates) + ')';
  189. break;
  190. }
  191. }
  192. return st;
  193. }
  194. static geoMultiPolygonToString(coords) {
  195. if (!coords) return '';
  196. let st = '';
  197. for (let i = 0; i < coords.length; i++) {
  198. st += (i !== 0 ? ',(' : '(') + CommonText.geoMultiArrayPointToString(coords[i]) + ')';
  199. }
  200. return st;
  201. }
  202. static geoMultiArrayPointToString(coords) {
  203. if (!coords) return '';
  204. let st = '';
  205. for (let i = 0; i < coords.length; i++) {
  206. st += (i !== 0 ? ',(' : '(') + CommonText.geoArrayPointToString(coords[i]) + ')';
  207. }
  208. return st;
  209. }
  210. static geoArrayPointToString(coords) {
  211. if (!coords) return '';
  212. let st = '';
  213. for (let i = 0; i < coords.length; i++) {
  214. st += (i !== 0 ? ',' : '') + CommonText.geoPointToString(coords[i]);
  215. }
  216. return st;
  217. }
  218. static geoPointToString(coords) {
  219. if (!coords) return '';
  220. return (isNaN(coords[0]) ? '' : coords[0]) + ' ' + (isNaN(coords[1]) ? '' : coords[1]);
  221. }
  222. parseRowAsArray(columns, packet, connOpts) {
  223. const row = new Array(this._columnCount);
  224. for (let i = 0; i < this._columnCount; i++) {
  225. row[i] = this._getValue(i, columns[i], this.opts, connOpts, packet);
  226. }
  227. return row;
  228. }
  229. parseRowNested(columns, packet, connOpts) {
  230. const row = {};
  231. for (let i = 0; i < this._columnCount; i++) {
  232. if (!row[this.tableHeader[i][0]]) row[this.tableHeader[i][0]] = {};
  233. row[this.tableHeader[i][0]][this.tableHeader[i][1]] = this._getValue(
  234. i,
  235. columns[i],
  236. this.opts,
  237. connOpts,
  238. packet
  239. );
  240. }
  241. return row;
  242. }
  243. parseRowStd(columns, packet, connOpts) {
  244. const row = {};
  245. for (let i = 0; i < this._columnCount; i++) {
  246. row[this.tableHeader[i]] = this._getValue(i, columns[i], this.opts, connOpts, packet);
  247. }
  248. return row;
  249. }
  250. castTextWrapper(column, opts, connOpts, packet) {
  251. column.string = () => packet.readStringLength();
  252. column.buffer = () => packet.readBufferLengthEncoded();
  253. column.float = () => packet.readFloatLengthCoded();
  254. column.int = () => packet.readIntLengthEncoded();
  255. column.long = () =>
  256. packet.readLongLengthEncoded(
  257. opts.supportBigInt,
  258. opts.supportBigNumbers,
  259. opts.bigNumberStrings,
  260. (column.flags & FieldDetail.UNSIGNED) > 0
  261. );
  262. column.decimal = () => packet.readDecimalLengthEncoded(opts.bigNumberStrings);
  263. column.date = () => packet.readDateTime(opts);
  264. column.geometry = () => {
  265. return column.readGeometry();
  266. };
  267. }
  268. readCastValue(index, column, opts, connOpts, packet) {
  269. this.castTextWrapper(column, opts, connOpts, packet);
  270. return opts.typeCast(
  271. column,
  272. this.readRowData.bind(this, index, column, opts, connOpts, packet)
  273. );
  274. }
  275. /**
  276. * Read row data.
  277. *
  278. * @param index current data index in row
  279. * @param column associate metadata
  280. * @param opts query options
  281. * @param connOpts connection options
  282. * @param packet row packet
  283. * @returns {*} data
  284. */
  285. readRowData(index, column, opts, connOpts, packet) {
  286. switch (column.columnType) {
  287. case FieldType.TINY:
  288. case FieldType.SHORT:
  289. case FieldType.LONG:
  290. case FieldType.INT24:
  291. case FieldType.YEAR:
  292. return packet.readIntLengthEncoded();
  293. case FieldType.FLOAT:
  294. case FieldType.DOUBLE:
  295. return packet.readFloatLengthCoded();
  296. case FieldType.LONGLONG:
  297. return packet.readLongLengthEncoded(
  298. opts.supportBigInt,
  299. opts.supportBigNumbers,
  300. opts.bigNumberStrings,
  301. (column.flags & FieldDetail.UNSIGNED) > 0
  302. );
  303. case FieldType.DECIMAL:
  304. case FieldType.NEWDECIMAL:
  305. return packet.readDecimalLengthEncoded(opts.bigNumberStrings);
  306. case FieldType.DATE:
  307. if (opts.dateStrings) {
  308. return packet.readAsciiStringLengthEncoded();
  309. }
  310. return packet.readDate();
  311. case FieldType.DATETIME:
  312. case FieldType.TIMESTAMP:
  313. if (opts.dateStrings) {
  314. return packet.readAsciiStringLengthEncoded();
  315. }
  316. return packet.readDateTime(opts);
  317. case FieldType.TIME:
  318. return packet.readAsciiStringLengthEncoded();
  319. case FieldType.GEOMETRY:
  320. return packet.readGeometry(column.dataTypeName);
  321. case FieldType.JSON:
  322. //for mysql only => parse string as JSON object
  323. return JSON.parse(packet.readStringLengthEncoded('utf8'));
  324. default:
  325. if (column.dataTypeFormat && column.dataTypeFormat === 'json' && opts.autoJsonMap) {
  326. return JSON.parse(packet.readStringLengthEncoded('utf8'));
  327. }
  328. if (column.collation.index === 63) {
  329. return packet.readBufferLengthEncoded();
  330. }
  331. const string = packet.readStringLength();
  332. if (column.flags & 2048) {
  333. //SET
  334. return string == null ? null : string === '' ? [] : string.split(',');
  335. }
  336. return string;
  337. }
  338. }
  339. }
  340. function getDatePartQuote(year, mon, day, hour, min, sec, ms) {
  341. //return 'YYYY-MM-DD HH:MM:SS' datetime format
  342. //see https://mariadb.com/kb/en/library/datetime/
  343. return (
  344. "'" +
  345. (year > 999 ? year : year > 99 ? '0' + year : year > 9 ? '00' + year : '000' + year) +
  346. '-' +
  347. (mon < 10 ? '0' : '') +
  348. mon +
  349. '-' +
  350. (day < 10 ? '0' : '') +
  351. day +
  352. ' ' +
  353. (hour < 10 ? '0' : '') +
  354. hour +
  355. ':' +
  356. (min < 10 ? '0' : '') +
  357. min +
  358. ':' +
  359. (sec < 10 ? '0' : '') +
  360. sec +
  361. '.' +
  362. (ms > 99 ? ms : ms > 9 ? '0' + ms : '00' + ms) +
  363. "'"
  364. );
  365. }
  366. function getLocalDate(date, opts) {
  367. const year = date.getFullYear();
  368. const mon = date.getMonth() + 1;
  369. const day = date.getDate();
  370. const hour = date.getHours();
  371. const min = date.getMinutes();
  372. const sec = date.getSeconds();
  373. const ms = date.getMilliseconds();
  374. return getDatePartQuote(year, mon, day, hour, min, sec, ms);
  375. }
  376. function getUtcDate(date, opts) {
  377. const year = date.getUTCFullYear();
  378. const mon = date.getUTCMonth() + 1;
  379. const day = date.getUTCDate();
  380. const hour = date.getUTCHours();
  381. const min = date.getUTCMinutes();
  382. const sec = date.getUTCSeconds();
  383. const ms = date.getUTCMilliseconds();
  384. return getDatePartQuote(year, mon, day, hour, min, sec, ms);
  385. }
  386. function getTimezoneDate(date, opts) {
  387. if (date.getMilliseconds() != 0) {
  388. return moment.tz(date, opts.tz).format("'YYYY-MM-DD HH:mm:ss.SSS'");
  389. }
  390. return moment.tz(date, opts.tz).format("'YYYY-MM-DD HH:mm:ss'");
  391. }
  392. module.exports = CommonText;
  393. module.exports.getTimezoneDate = getTimezoneDate;
  394. module.exports.getUtcDate = getUtcDate;
  395. module.exports.getLocalDate = getLocalDate;