'use strict'; const ResultSet = require('./resultset'); const FieldDetail = require('../const/field-detail'); const FieldType = require('../const/field-type'); const Long = require('long'); const moment = require('moment-timezone'); const QUOTE = 0x27; class CommonText extends ResultSet { constructor(resolve, reject, cmdOpts, connOpts, sql, values) { super(resolve, reject); this.configAssign(connOpts, cmdOpts); this.sql = sql; this.initialValues = values; this.getDateQuote = this.opts.tz ? this.opts.tz === 'Etc/UTC' ? CommonText.getUtcDate : CommonText.getTimezoneDate : CommonText.getLocalDate; } /** * Write (and escape) current parameter value to output writer * * @param out output writer * @param value current parameter * @param opts connection options * @param info connection information */ writeParam(out, value, opts, info) { switch (typeof value) { case 'boolean': out.writeStringAscii(value ? 'true' : 'false'); break; case 'bigint': case 'number': out.writeStringAscii('' + value); break; case 'object': if (value === null) { out.writeStringAscii('NULL'); } else if (Object.prototype.toString.call(value) === '[object Date]') { out.writeStringAscii(this.getDateQuote(value, opts)); } else if (Buffer.isBuffer(value)) { out.writeStringAscii("_BINARY '"); out.writeBufferEscape(value); out.writeInt8(QUOTE); } else if (typeof value.toSqlString === 'function') { out.writeStringEscapeQuote(String(value.toSqlString())); } else if (Long.isLong(value)) { out.writeStringAscii(value.toString()); } else if (Array.isArray(value)) { if (opts.arrayParenthesis) { out.writeStringAscii('('); } for (let i = 0; i < value.length; i++) { if (i !== 0) out.writeStringAscii(','); this.writeParam(out, value[i], opts, info); } if (opts.arrayParenthesis) { out.writeStringAscii(')'); } } else { if ( value.type != null && [ 'Point', 'LineString', 'Polygon', 'MultiPoint', 'MultiLineString', 'MultiPolygon', 'GeometryCollection' ].includes(value.type) ) { //GeoJSON format. let prefix = (info.isMariaDB() && info.hasMinVersion(10, 1, 4)) || (!info.isMariaDB() && info.hasMinVersion(5, 7, 6)) ? 'ST_' : ''; switch (value.type) { case 'Point': out.writeStringAscii( prefix + "PointFromText('POINT(" + CommonText.geoPointToString(value.coordinates) + ")')" ); break; case 'LineString': out.writeStringAscii( prefix + "LineFromText('LINESTRING(" + CommonText.geoArrayPointToString(value.coordinates) + ")')" ); break; case 'Polygon': out.writeStringAscii( prefix + "PolygonFromText('POLYGON(" + CommonText.geoMultiArrayPointToString(value.coordinates) + ")')" ); break; case 'MultiPoint': out.writeStringAscii( prefix + "MULTIPOINTFROMTEXT('MULTIPOINT(" + CommonText.geoArrayPointToString(value.coordinates) + ")')" ); break; case 'MultiLineString': out.writeStringAscii( prefix + "MLineFromText('MULTILINESTRING(" + CommonText.geoMultiArrayPointToString(value.coordinates) + ")')" ); break; case 'MultiPolygon': out.writeStringAscii( prefix + "MPolyFromText('MULTIPOLYGON(" + CommonText.geoMultiPolygonToString(value.coordinates) + ")')" ); break; case 'GeometryCollection': out.writeStringAscii( prefix + "GeomCollFromText('GEOMETRYCOLLECTION(" + CommonText.geometricCollectionToString(value.geometries) + ")')" ); break; } } else { if (opts.permitSetMultiParamEntries) { let first = true; for (let key in value) { const val = value[key]; if (typeof val === 'function') continue; if (first) { first = false; } else { out.writeStringAscii(','); } out.writeString('`' + key + '`'); out.writeStringAscii('='); this.writeParam(out, val, opts, info); } if (first) out.writeStringEscapeQuote(JSON.stringify(value)); } else { out.writeStringEscapeQuote(JSON.stringify(value)); } } } break; default: out.writeStringEscapeQuote(value); } } static geometricCollectionToString(geo) { if (!geo) return ''; let st = ''; for (let i = 0; i < geo.length; i++) { //GeoJSON format. st += i !== 0 ? ',' : ''; switch (geo[i].type) { case 'Point': st += 'POINT(' + CommonText.geoPointToString(geo[i].coordinates) + ')'; break; case 'LineString': st += 'LINESTRING(' + CommonText.geoArrayPointToString(geo[i].coordinates) + ')'; break; case 'Polygon': st += 'POLYGON(' + CommonText.geoMultiArrayPointToString(geo[i].coordinates) + ')'; break; case 'MultiPoint': st += 'MULTIPOINT(' + CommonText.geoArrayPointToString(geo[i].coordinates) + ')'; break; case 'MultiLineString': st += 'MULTILINESTRING(' + CommonText.geoMultiArrayPointToString(geo[i].coordinates) + ')'; break; case 'MultiPolygon': st += 'MULTIPOLYGON(' + CommonText.geoMultiPolygonToString(geo[i].coordinates) + ')'; break; } } return st; } static geoMultiPolygonToString(coords) { if (!coords) return ''; let st = ''; for (let i = 0; i < coords.length; i++) { st += (i !== 0 ? ',(' : '(') + CommonText.geoMultiArrayPointToString(coords[i]) + ')'; } return st; } static geoMultiArrayPointToString(coords) { if (!coords) return ''; let st = ''; for (let i = 0; i < coords.length; i++) { st += (i !== 0 ? ',(' : '(') + CommonText.geoArrayPointToString(coords[i]) + ')'; } return st; } static geoArrayPointToString(coords) { if (!coords) return ''; let st = ''; for (let i = 0; i < coords.length; i++) { st += (i !== 0 ? ',' : '') + CommonText.geoPointToString(coords[i]); } return st; } static geoPointToString(coords) { if (!coords) return ''; return (isNaN(coords[0]) ? '' : coords[0]) + ' ' + (isNaN(coords[1]) ? '' : coords[1]); } parseRowAsArray(columns, packet, connOpts) { const row = new Array(this._columnCount); for (let i = 0; i < this._columnCount; i++) { row[i] = this._getValue(i, columns[i], this.opts, connOpts, packet); } return row; } parseRowNested(columns, packet, connOpts) { const row = {}; for (let i = 0; i < this._columnCount; i++) { if (!row[this.tableHeader[i][0]]) row[this.tableHeader[i][0]] = {}; row[this.tableHeader[i][0]][this.tableHeader[i][1]] = this._getValue( i, columns[i], this.opts, connOpts, packet ); } return row; } parseRowStd(columns, packet, connOpts) { const row = {}; for (let i = 0; i < this._columnCount; i++) { row[this.tableHeader[i]] = this._getValue(i, columns[i], this.opts, connOpts, packet); } return row; } castTextWrapper(column, opts, connOpts, packet) { column.string = () => packet.readStringLength(); column.buffer = () => packet.readBufferLengthEncoded(); column.float = () => packet.readFloatLengthCoded(); column.int = () => packet.readIntLengthEncoded(); column.long = () => packet.readLongLengthEncoded( opts.supportBigInt, opts.supportBigNumbers, opts.bigNumberStrings, (column.flags & FieldDetail.UNSIGNED) > 0 ); column.decimal = () => packet.readDecimalLengthEncoded(opts.bigNumberStrings); column.date = () => packet.readDateTime(opts); column.geometry = () => { return column.readGeometry(); }; } readCastValue(index, column, opts, connOpts, packet) { this.castTextWrapper(column, opts, connOpts, packet); return opts.typeCast( column, this.readRowData.bind(this, index, column, opts, connOpts, packet) ); } /** * Read row data. * * @param index current data index in row * @param column associate metadata * @param opts query options * @param connOpts connection options * @param packet row packet * @returns {*} data */ readRowData(index, column, opts, connOpts, packet) { switch (column.columnType) { case FieldType.TINY: case FieldType.SHORT: case FieldType.LONG: case FieldType.INT24: case FieldType.YEAR: return packet.readIntLengthEncoded(); case FieldType.FLOAT: case FieldType.DOUBLE: return packet.readFloatLengthCoded(); case FieldType.LONGLONG: return packet.readLongLengthEncoded( opts.supportBigInt, opts.supportBigNumbers, opts.bigNumberStrings, (column.flags & FieldDetail.UNSIGNED) > 0 ); case FieldType.DECIMAL: case FieldType.NEWDECIMAL: return packet.readDecimalLengthEncoded(opts.bigNumberStrings); case FieldType.DATE: if (opts.dateStrings) { return packet.readAsciiStringLengthEncoded(); } return packet.readDate(); case FieldType.DATETIME: case FieldType.TIMESTAMP: if (opts.dateStrings) { return packet.readAsciiStringLengthEncoded(); } return packet.readDateTime(opts); case FieldType.TIME: return packet.readAsciiStringLengthEncoded(); case FieldType.GEOMETRY: return packet.readGeometry(column.dataTypeName); case FieldType.JSON: //for mysql only => parse string as JSON object return JSON.parse(packet.readStringLengthEncoded('utf8')); default: if (column.dataTypeFormat && column.dataTypeFormat === 'json' && opts.autoJsonMap) { return JSON.parse(packet.readStringLengthEncoded('utf8')); } if (column.collation.index === 63) { return packet.readBufferLengthEncoded(); } const string = packet.readStringLength(); if (column.flags & 2048) { //SET return string == null ? null : string === '' ? [] : string.split(','); } return string; } } } function getDatePartQuote(year, mon, day, hour, min, sec, ms) { //return 'YYYY-MM-DD HH:MM:SS' datetime format //see https://mariadb.com/kb/en/library/datetime/ return ( "'" + (year > 999 ? year : year > 99 ? '0' + year : year > 9 ? '00' + year : '000' + year) + '-' + (mon < 10 ? '0' : '') + mon + '-' + (day < 10 ? '0' : '') + day + ' ' + (hour < 10 ? '0' : '') + hour + ':' + (min < 10 ? '0' : '') + min + ':' + (sec < 10 ? '0' : '') + sec + '.' + (ms > 99 ? ms : ms > 9 ? '0' + ms : '00' + ms) + "'" ); } function getLocalDate(date, opts) { const year = date.getFullYear(); const mon = date.getMonth() + 1; const day = date.getDate(); const hour = date.getHours(); const min = date.getMinutes(); const sec = date.getSeconds(); const ms = date.getMilliseconds(); return getDatePartQuote(year, mon, day, hour, min, sec, ms); } function getUtcDate(date, opts) { const year = date.getUTCFullYear(); const mon = date.getUTCMonth() + 1; const day = date.getUTCDate(); const hour = date.getUTCHours(); const min = date.getUTCMinutes(); const sec = date.getUTCSeconds(); const ms = date.getUTCMilliseconds(); return getDatePartQuote(year, mon, day, hour, min, sec, ms); } function getTimezoneDate(date, opts) { if (date.getMilliseconds() != 0) { return moment.tz(date, opts.tz).format("'YYYY-MM-DD HH:mm:ss.SSS'"); } return moment.tz(date, opts.tz).format("'YYYY-MM-DD HH:mm:ss'"); } module.exports = CommonText; module.exports.getTimezoneDate = getTimezoneDate; module.exports.getUtcDate = getUtcDate; module.exports.getLocalDate = getLocalDate;