932 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			932 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | // This file was modified by Oracle on June 1, 2021.
 | ||
|  | // A comment describing some changes in the strict default SQL mode regarding
 | ||
|  | // non-standard dates was introduced.
 | ||
|  | // Modifications copyright (c) 2021, Oracle and/or its affiliates.
 | ||
|  | 
 | ||
|  | 'use strict'; | ||
|  | 
 | ||
|  | const ErrorCodeToName = require('../constants/errors.js'); | ||
|  | const NativeBuffer = require('buffer').Buffer; | ||
|  | const Long = require('long'); | ||
|  | const StringParser = require('../parsers/string.js'); | ||
|  | const Types = require('../constants/types.js'); | ||
|  | const INVALID_DATE = new Date(NaN); | ||
|  | 
 | ||
|  | // this is nearly duplicate of previous function so generated code is not slower
 | ||
|  | // due to "if (dateStrings)" branching
 | ||
|  | const pad = '000000000000'; | ||
|  | function leftPad(num, value) { | ||
|  |   const s = value.toString(); | ||
|  |   // if we don't need to pad
 | ||
|  |   if (s.length >= num) { | ||
|  |     return s; | ||
|  |   } | ||
|  |   return (pad + s).slice(-num); | ||
|  | } | ||
|  | 
 | ||
|  | // The whole reason parse* function below exist
 | ||
|  | // is because String creation is relatively expensive (at least with V8), and if we have
 | ||
|  | // a buffer with "12345" content ideally we would like to bypass intermediate
 | ||
|  | // "12345" string creation and directly build 12345 number out of
 | ||
|  | // <Buffer 31 32 33 34 35> data.
 | ||
|  | // In my benchmarks the difference is ~25M 8-digit numbers per second vs
 | ||
|  | // 4.5 M using Number(packet.readLengthCodedString())
 | ||
|  | // not used when size is close to max precision as series of *10 accumulate error
 | ||
|  | // and approximate result mihgt be diffreent from (approximate as well) Number(bigNumStringValue))
 | ||
|  | // In the futire node version if speed difference is smaller parse* functions might be removed
 | ||
|  | // don't consider them as Packet public API
 | ||
|  | 
 | ||
|  | const minus = '-'.charCodeAt(0); | ||
|  | const plus = '+'.charCodeAt(0); | ||
|  | 
 | ||
|  | // TODO: handle E notation
 | ||
|  | const dot = '.'.charCodeAt(0); | ||
|  | const exponent = 'e'.charCodeAt(0); | ||
|  | const exponentCapital = 'E'.charCodeAt(0); | ||
|  | 
 | ||
|  | class Packet { | ||
|  |   constructor(id, buffer, start, end) { | ||
|  |     // hot path, enable checks when testing only
 | ||
|  |     // if (!Buffer.isBuffer(buffer) || typeof start == 'undefined' || typeof end == 'undefined')
 | ||
|  |     //  throw new Error('invalid packet');
 | ||
|  |     this.sequenceId = id; | ||
|  |     this.numPackets = 1; | ||
|  |     this.buffer = buffer; | ||
|  |     this.start = start; | ||
|  |     this.offset = start + 4; | ||
|  |     this.end = end; | ||
|  |   } | ||
|  | 
 | ||
|  |   // ==============================
 | ||
|  |   // readers
 | ||
|  |   // ==============================
 | ||
|  |   reset() { | ||
|  |     this.offset = this.start + 4; | ||
|  |   } | ||
|  | 
 | ||
|  |   length() { | ||
|  |     return this.end - this.start; | ||
|  |   } | ||
|  | 
 | ||
|  |   slice() { | ||
|  |     return this.buffer.slice(this.start, this.end); | ||
|  |   } | ||
|  | 
 | ||
|  |   dump() { | ||
|  |     // eslint-disable-next-line no-console
 | ||
|  |     console.log( | ||
|  |       [this.buffer.asciiSlice(this.start, this.end)], | ||
|  |       this.buffer.slice(this.start, this.end), | ||
|  |       this.length(), | ||
|  |       this.sequenceId | ||
|  |     ); | ||
|  |   } | ||
|  | 
 | ||
|  |   haveMoreData() { | ||
|  |     return this.end > this.offset; | ||
|  |   } | ||
|  | 
 | ||
|  |   skip(num) { | ||
|  |     this.offset += num; | ||
|  |   } | ||
|  | 
 | ||
|  |   readInt8() { | ||
|  |     return this.buffer[this.offset++]; | ||
|  |   } | ||
|  | 
 | ||
|  |   readInt16() { | ||
|  |     this.offset += 2; | ||
|  |     return this.buffer.readUInt16LE(this.offset - 2); | ||
|  |   } | ||
|  | 
 | ||
|  |   readInt24() { | ||
|  |     return this.readInt16() + (this.readInt8() << 16); | ||
|  |   } | ||
|  | 
 | ||
|  |   readInt32() { | ||
|  |     this.offset += 4; | ||
|  |     return this.buffer.readUInt32LE(this.offset - 4); | ||
|  |   } | ||
|  | 
 | ||
|  |   readSInt8() { | ||
|  |     return this.buffer.readInt8(this.offset++); | ||
|  |   } | ||
|  | 
 | ||
|  |   readSInt16() { | ||
|  |     this.offset += 2; | ||
|  |     return this.buffer.readInt16LE(this.offset - 2); | ||
|  |   } | ||
|  | 
 | ||
|  |   readSInt32() { | ||
|  |     this.offset += 4; | ||
|  |     return this.buffer.readInt32LE(this.offset - 4); | ||
|  |   } | ||
|  | 
 | ||
|  |   readInt64JSNumber() { | ||
|  |     const word0 = this.readInt32(); | ||
|  |     const word1 = this.readInt32(); | ||
|  |     const l = new Long(word0, word1, true); | ||
|  |     return l.toNumber(); | ||
|  |   } | ||
|  | 
 | ||
|  |   readSInt64JSNumber() { | ||
|  |     const word0 = this.readInt32(); | ||
|  |     const word1 = this.readInt32(); | ||
|  |     if (!(word1 & 0x80000000)) { | ||
|  |       return word0 + 0x100000000 * word1; | ||
|  |     } | ||
|  |     const l = new Long(word0, word1, false); | ||
|  |     return l.toNumber(); | ||
|  |   } | ||
|  | 
 | ||
|  |   readInt64String() { | ||
|  |     const word0 = this.readInt32(); | ||
|  |     const word1 = this.readInt32(); | ||
|  |     const res = new Long(word0, word1, true); | ||
|  |     return res.toString(); | ||
|  |   } | ||
|  | 
 | ||
|  |   readSInt64String() { | ||
|  |     const word0 = this.readInt32(); | ||
|  |     const word1 = this.readInt32(); | ||
|  |     const res = new Long(word0, word1, false); | ||
|  |     return res.toString(); | ||
|  |   } | ||
|  | 
 | ||
|  |   readInt64() { | ||
|  |     const word0 = this.readInt32(); | ||
|  |     const word1 = this.readInt32(); | ||
|  |     let res = new Long(word0, word1, true); | ||
|  |     const resNumber = res.toNumber(); | ||
|  |     const resString = res.toString(); | ||
|  |     res = resNumber.toString() === resString ? resNumber : resString; | ||
|  |     return res; | ||
|  |   } | ||
|  | 
 | ||
|  |   readSInt64() { | ||
|  |     const word0 = this.readInt32(); | ||
|  |     const word1 = this.readInt32(); | ||
|  |     let res = new Long(word0, word1, false); | ||
|  |     const resNumber = res.toNumber(); | ||
|  |     const resString = res.toString(); | ||
|  |     res = resNumber.toString() === resString ? resNumber : resString; | ||
|  |     return res; | ||
|  |   } | ||
|  | 
 | ||
|  |   isEOF() { | ||
|  |     return this.buffer[this.offset] === 0xfe && this.length() < 13; | ||
|  |   } | ||
|  | 
 | ||
|  |   eofStatusFlags() { | ||
|  |     return this.buffer.readInt16LE(this.offset + 3); | ||
|  |   } | ||
|  | 
 | ||
|  |   eofWarningCount() { | ||
|  |     return this.buffer.readInt16LE(this.offset + 1); | ||
|  |   } | ||
|  | 
 | ||
|  |   readLengthCodedNumber(bigNumberStrings, signed) { | ||
|  |     const byte1 = this.buffer[this.offset++]; | ||
|  |     if (byte1 < 251) { | ||
|  |       return byte1; | ||
|  |     } | ||
|  |     return this.readLengthCodedNumberExt(byte1, bigNumberStrings, signed); | ||
|  |   } | ||
|  | 
 | ||
|  |   readLengthCodedNumberSigned(bigNumberStrings) { | ||
|  |     return this.readLengthCodedNumber(bigNumberStrings, true); | ||
|  |   } | ||
|  | 
 | ||
|  |   readLengthCodedNumberExt(tag, bigNumberStrings, signed) { | ||
|  |     let word0, word1; | ||
|  |     let res; | ||
|  |     if (tag === 0xfb) { | ||
|  |       return null; | ||
|  |     } | ||
|  |     if (tag === 0xfc) { | ||
|  |       return this.readInt8() + (this.readInt8() << 8); | ||
|  |     } | ||
|  |     if (tag === 0xfd) { | ||
|  |       return this.readInt8() + (this.readInt8() << 8) + (this.readInt8() << 16); | ||
|  |     } | ||
|  |     if (tag === 0xfe) { | ||
|  |       // TODO: check version
 | ||
|  |       // Up to MySQL 3.22, 0xfe was followed by a 4-byte integer.
 | ||
|  |       word0 = this.readInt32(); | ||
|  |       word1 = this.readInt32(); | ||
|  |       if (word1 === 0) { | ||
|  |         return word0; // don't convert to float if possible
 | ||
|  |       } | ||
|  |       if (word1 < 2097152) { | ||
|  |         // max exact float point int, 2^52 / 2^32
 | ||
|  |         return word1 * 0x100000000 + word0; | ||
|  |       } | ||
|  |       res = new Long(word0, word1, !signed); // Long need unsigned
 | ||
|  |       const resNumber = res.toNumber(); | ||
|  |       const resString = res.toString(); | ||
|  |       res = resNumber.toString() === resString ? resNumber : resString; | ||
|  |       return bigNumberStrings ? resString : res; | ||
|  |     } | ||
|  |     // eslint-disable-next-line no-console
 | ||
|  |     console.trace(); | ||
|  |     throw new Error(`Should not reach here: ${tag}`); | ||
|  |   } | ||
|  | 
 | ||
|  |   readFloat() { | ||
|  |     const res = this.buffer.readFloatLE(this.offset); | ||
|  |     this.offset += 4; | ||
|  |     return res; | ||
|  |   } | ||
|  | 
 | ||
|  |   readDouble() { | ||
|  |     const res = this.buffer.readDoubleLE(this.offset); | ||
|  |     this.offset += 8; | ||
|  |     return res; | ||
|  |   } | ||
|  | 
 | ||
|  |   readBuffer(len) { | ||
|  |     if (typeof len === 'undefined') { | ||
|  |       len = this.end - this.offset; | ||
|  |     } | ||
|  |     this.offset += len; | ||
|  |     return this.buffer.slice(this.offset - len, this.offset); | ||
|  |   } | ||
|  | 
 | ||
|  |   // DATE, DATETIME and TIMESTAMP
 | ||
|  |   readDateTime(timezone) { | ||
|  |     if (!timezone || timezone === 'Z' || timezone === 'local') { | ||
|  |       const length = this.readInt8(); | ||
|  |       if (length === 0xfb) { | ||
|  |         return null; | ||
|  |       } | ||
|  |       let y = 0; | ||
|  |       let m = 0; | ||
|  |       let d = 0; | ||
|  |       let H = 0; | ||
|  |       let M = 0; | ||
|  |       let S = 0; | ||
|  |       let ms = 0; | ||
|  |       if (length > 3) { | ||
|  |         y = this.readInt16(); | ||
|  |         m = this.readInt8(); | ||
|  |         d = this.readInt8(); | ||
|  |       } | ||
|  |       if (length > 6) { | ||
|  |         H = this.readInt8(); | ||
|  |         M = this.readInt8(); | ||
|  |         S = this.readInt8(); | ||
|  |       } | ||
|  |       if (length > 10) { | ||
|  |         ms = this.readInt32() / 1000; | ||
|  |       } | ||
|  |       // NO_ZERO_DATE mode and NO_ZERO_IN_DATE mode are part of the strict
 | ||
|  |       // default SQL mode used by MySQL 8.0. This means that non-standard
 | ||
|  |       // dates like '0000-00-00' become NULL. For older versions and other
 | ||
|  |       // possible MySQL flavours we still need to account for the
 | ||
|  |       // non-standard behaviour.
 | ||
|  |       if (y + m + d + H + M + S + ms === 0) { | ||
|  |         return INVALID_DATE; | ||
|  |       } | ||
|  |       if (timezone === 'Z') { | ||
|  |         return new Date(Date.UTC(y, m - 1, d, H, M, S, ms)); | ||
|  |       } | ||
|  |       return new Date(y, m - 1, d, H, M, S, ms); | ||
|  |     } | ||
|  |     let str = this.readDateTimeString(6, 'T', null); | ||
|  |     if (str.length === 10) { | ||
|  |       str += 'T00:00:00'; | ||
|  |     } | ||
|  |     return new Date(str + timezone); | ||
|  |   } | ||
|  | 
 | ||
|  |   readDateTimeString(decimals, timeSep, columnType) { | ||
|  |     const length = this.readInt8(); | ||
|  |     let y = 0; | ||
|  |     let m = 0; | ||
|  |     let d = 0; | ||
|  |     let H = 0; | ||
|  |     let M = 0; | ||
|  |     let S = 0; | ||
|  |     let ms = 0; | ||
|  |     let str; | ||
|  |     if (length > 3) { | ||
|  |       y = this.readInt16(); | ||
|  |       m = this.readInt8(); | ||
|  |       d = this.readInt8(); | ||
|  |       str = [leftPad(4, y), leftPad(2, m), leftPad(2, d)].join('-'); | ||
|  |     } | ||
|  |     if (length > 6) { | ||
|  |       H = this.readInt8(); | ||
|  |       M = this.readInt8(); | ||
|  |       S = this.readInt8(); | ||
|  |       str += `${timeSep || ' '}${[ | ||
|  |         leftPad(2, H), | ||
|  |         leftPad(2, M), | ||
|  |         leftPad(2, S), | ||
|  |       ].join(':')}`;
 | ||
|  |     } else if (columnType === Types.DATETIME) { | ||
|  |       str += ' 00:00:00'; | ||
|  |     } | ||
|  |     if (length > 10) { | ||
|  |       ms = this.readInt32(); | ||
|  |       str += '.'; | ||
|  |       if (decimals) { | ||
|  |         ms = leftPad(6, ms); | ||
|  |         if (ms.length > decimals) { | ||
|  |           ms = ms.substring(0, decimals); // rounding is done at the MySQL side, only 0 are here
 | ||
|  |         } | ||
|  |       } | ||
|  |       str += ms; | ||
|  |     } | ||
|  |     return str; | ||
|  |   } | ||
|  | 
 | ||
|  |   // TIME - value as a string, Can be negative
 | ||
|  |   readTimeString(convertTtoMs) { | ||
|  |     const length = this.readInt8(); | ||
|  |     if (length === 0) { | ||
|  |       return '00:00:00'; | ||
|  |     } | ||
|  |     const sign = this.readInt8() ? -1 : 1; // 'isNegative' flag byte
 | ||
|  |     let d = 0; | ||
|  |     let H = 0; | ||
|  |     let M = 0; | ||
|  |     let S = 0; | ||
|  |     let ms = 0; | ||
|  |     if (length > 6) { | ||
|  |       d = this.readInt32(); | ||
|  |       H = this.readInt8(); | ||
|  |       M = this.readInt8(); | ||
|  |       S = this.readInt8(); | ||
|  |     } | ||
|  |     if (length > 10) { | ||
|  |       ms = this.readInt32(); | ||
|  |     } | ||
|  |     if (convertTtoMs) { | ||
|  |       H += d * 24; | ||
|  |       M += H * 60; | ||
|  |       S += M * 60; | ||
|  |       ms += S * 1000; | ||
|  |       ms *= sign; | ||
|  |       return ms; | ||
|  |     } | ||
|  |     // Format follows mySQL TIME format ([-][h]hh:mm:ss[.u[u[u[u[u[u]]]]]])
 | ||
|  |     // For positive times below 24 hours, this makes it equal to ISO 8601 times
 | ||
|  |     return ( | ||
|  |       (sign === -1 ? '-' : '') + | ||
|  |       [leftPad(2, d * 24 + H), leftPad(2, M), leftPad(2, S)].join(':') + | ||
|  |       (ms ? `.${ms}`.replace(/0+$/, '') : '') | ||
|  |     ); | ||
|  |   } | ||
|  | 
 | ||
|  |   readLengthCodedString(encoding) { | ||
|  |     const len = this.readLengthCodedNumber(); | ||
|  |     // TODO: check manually first byte here to avoid polymorphic return type?
 | ||
|  |     if (len === null) { | ||
|  |       return null; | ||
|  |     } | ||
|  |     this.offset += len; | ||
|  |     // TODO: Use characterSetCode to get proper encoding
 | ||
|  |     // https://github.com/sidorares/node-mysql2/pull/374
 | ||
|  |     return StringParser.decode( | ||
|  |       this.buffer, | ||
|  |       encoding, | ||
|  |       this.offset - len, | ||
|  |       this.offset | ||
|  |     ); | ||
|  |   } | ||
|  | 
 | ||
|  |   readLengthCodedBuffer() { | ||
|  |     const len = this.readLengthCodedNumber(); | ||
|  |     if (len === null) { | ||
|  |       return null; | ||
|  |     } | ||
|  |     return this.readBuffer(len); | ||
|  |   } | ||
|  | 
 | ||
|  |   readNullTerminatedString(encoding) { | ||
|  |     const start = this.offset; | ||
|  |     let end = this.offset; | ||
|  |     while (this.buffer[end]) { | ||
|  |       end = end + 1; // TODO: handle OOB check
 | ||
|  |     } | ||
|  |     this.offset = end + 1; | ||
|  |     return StringParser.decode(this.buffer, encoding, start, end); | ||
|  |   } | ||
|  | 
 | ||
|  |   // TODO reuse?
 | ||
|  |   readString(len, encoding) { | ||
|  |     if (typeof len === 'string' && typeof encoding === 'undefined') { | ||
|  |       encoding = len; | ||
|  |       len = undefined; | ||
|  |     } | ||
|  |     if (typeof len === 'undefined') { | ||
|  |       len = this.end - this.offset; | ||
|  |     } | ||
|  |     this.offset += len; | ||
|  |     return StringParser.decode( | ||
|  |       this.buffer, | ||
|  |       encoding, | ||
|  |       this.offset - len, | ||
|  |       this.offset | ||
|  |     ); | ||
|  |   } | ||
|  | 
 | ||
|  |   parseInt(len, supportBigNumbers) { | ||
|  |     if (len === null) { | ||
|  |       return null; | ||
|  |     } | ||
|  |     if (len >= 14 && !supportBigNumbers) { | ||
|  |       const s = this.buffer.toString('ascii', this.offset, this.offset + len); | ||
|  |       this.offset += len; | ||
|  |       return Number(s); | ||
|  |     } | ||
|  |     let result = 0; | ||
|  |     const start = this.offset; | ||
|  |     const end = this.offset + len; | ||
|  |     let sign = 1; | ||
|  |     if (len === 0) { | ||
|  |       return 0; // TODO: assert? exception?
 | ||
|  |     } | ||
|  |     if (this.buffer[this.offset] === minus) { | ||
|  |       this.offset++; | ||
|  |       sign = -1; | ||
|  |     } | ||
|  |     // max precise int is 9007199254740992
 | ||
|  |     let str; | ||
|  |     const numDigits = end - this.offset; | ||
|  |     if (supportBigNumbers) { | ||
|  |       if (numDigits >= 15) { | ||
|  |         str = this.readString(end - this.offset, 'binary'); | ||
|  |         result = parseInt(str, 10); | ||
|  |         if (result.toString() === str) { | ||
|  |           return sign * result; | ||
|  |         } | ||
|  |         return sign === -1 ? `-${str}` : str; | ||
|  |       } | ||
|  |       if (numDigits > 16) { | ||
|  |         str = this.readString(end - this.offset); | ||
|  |         return sign === -1 ? `-${str}` : str; | ||
|  |       } | ||
|  |     } | ||
|  |     if (this.buffer[this.offset] === plus) { | ||
|  |       this.offset++; // just ignore
 | ||
|  |     } | ||
|  |     while (this.offset < end) { | ||
|  |       result *= 10; | ||
|  |       result += this.buffer[this.offset] - 48; | ||
|  |       this.offset++; | ||
|  |     } | ||
|  |     const num = result * sign; | ||
|  |     if (!supportBigNumbers) { | ||
|  |       return num; | ||
|  |     } | ||
|  |     str = this.buffer.toString('ascii', start, end); | ||
|  |     if (num.toString() === str) { | ||
|  |       return num; | ||
|  |     } | ||
|  |     return str; | ||
|  |   } | ||
|  | 
 | ||
|  |   // note that if value of inputNumberAsString is bigger than MAX_SAFE_INTEGER
 | ||
|  |   // ( or smaller than MIN_SAFE_INTEGER ) the parseIntNoBigCheck result might be
 | ||
|  |   // different from what you would get from Number(inputNumberAsString)
 | ||
|  |   // String(parseIntNoBigCheck) <> String(Number(inputNumberAsString)) <> inputNumberAsString
 | ||
|  |   parseIntNoBigCheck(len) { | ||
|  |     if (len === null) { | ||
|  |       return null; | ||
|  |     } | ||
|  |     let result = 0; | ||
|  |     const end = this.offset + len; | ||
|  |     let sign = 1; | ||
|  |     if (len === 0) { | ||
|  |       return 0; // TODO: assert? exception?
 | ||
|  |     } | ||
|  |     if (this.buffer[this.offset] === minus) { | ||
|  |       this.offset++; | ||
|  |       sign = -1; | ||
|  |     } | ||
|  |     if (this.buffer[this.offset] === plus) { | ||
|  |       this.offset++; // just ignore
 | ||
|  |     } | ||
|  |     while (this.offset < end) { | ||
|  |       result *= 10; | ||
|  |       result += this.buffer[this.offset] - 48; | ||
|  |       this.offset++; | ||
|  |     } | ||
|  |     return result * sign; | ||
|  |   } | ||
|  | 
 | ||
|  |   // copy-paste from https://github.com/mysqljs/mysql/blob/master/lib/protocol/Parser.js
 | ||
|  |   parseGeometryValue() { | ||
|  |     const buffer = this.readLengthCodedBuffer(); | ||
|  |     let offset = 4; | ||
|  |     if (buffer === null || !buffer.length) { | ||
|  |       return null; | ||
|  |     } | ||
|  |     function parseGeometry() { | ||
|  |       let x, y, i, j, numPoints, line; | ||
|  |       let result = null; | ||
|  |       const byteOrder = buffer.readUInt8(offset); | ||
|  |       offset += 1; | ||
|  |       const wkbType = byteOrder | ||
|  |         ? buffer.readUInt32LE(offset) | ||
|  |         : buffer.readUInt32BE(offset); | ||
|  |       offset += 4; | ||
|  |       switch (wkbType) { | ||
|  |         case 1: // WKBPoint
 | ||
|  |           x = byteOrder | ||
|  |             ? buffer.readDoubleLE(offset) | ||
|  |             : buffer.readDoubleBE(offset); | ||
|  |           offset += 8; | ||
|  |           y = byteOrder | ||
|  |             ? buffer.readDoubleLE(offset) | ||
|  |             : buffer.readDoubleBE(offset); | ||
|  |           offset += 8; | ||
|  |           result = { x: x, y: y }; | ||
|  |           break; | ||
|  |         case 2: // WKBLineString
 | ||
|  |           numPoints = byteOrder | ||
|  |             ? buffer.readUInt32LE(offset) | ||
|  |             : buffer.readUInt32BE(offset); | ||
|  |           offset += 4; | ||
|  |           result = []; | ||
|  |           for (i = numPoints; i > 0; i--) { | ||
|  |             x = byteOrder | ||
|  |               ? buffer.readDoubleLE(offset) | ||
|  |               : buffer.readDoubleBE(offset); | ||
|  |             offset += 8; | ||
|  |             y = byteOrder | ||
|  |               ? buffer.readDoubleLE(offset) | ||
|  |               : buffer.readDoubleBE(offset); | ||
|  |             offset += 8; | ||
|  |             result.push({ x: x, y: y }); | ||
|  |           } | ||
|  |           break; | ||
|  |         case 3: // WKBPolygon
 | ||
|  |           // eslint-disable-next-line no-case-declarations
 | ||
|  |           const numRings = byteOrder | ||
|  |             ? buffer.readUInt32LE(offset) | ||
|  |             : buffer.readUInt32BE(offset); | ||
|  |           offset += 4; | ||
|  |           result = []; | ||
|  |           for (i = numRings; i > 0; i--) { | ||
|  |             numPoints = byteOrder | ||
|  |               ? buffer.readUInt32LE(offset) | ||
|  |               : buffer.readUInt32BE(offset); | ||
|  |             offset += 4; | ||
|  |             line = []; | ||
|  |             for (j = numPoints; j > 0; j--) { | ||
|  |               x = byteOrder | ||
|  |                 ? buffer.readDoubleLE(offset) | ||
|  |                 : buffer.readDoubleBE(offset); | ||
|  |               offset += 8; | ||
|  |               y = byteOrder | ||
|  |                 ? buffer.readDoubleLE(offset) | ||
|  |                 : buffer.readDoubleBE(offset); | ||
|  |               offset += 8; | ||
|  |               line.push({ x: x, y: y }); | ||
|  |             } | ||
|  |             result.push(line); | ||
|  |           } | ||
|  |           break; | ||
|  |         case 4: // WKBMultiPoint
 | ||
|  |         case 5: // WKBMultiLineString
 | ||
|  |         case 6: // WKBMultiPolygon
 | ||
|  |         case 7: // WKBGeometryCollection
 | ||
|  |           // eslint-disable-next-line no-case-declarations
 | ||
|  |           const num = byteOrder | ||
|  |             ? buffer.readUInt32LE(offset) | ||
|  |             : buffer.readUInt32BE(offset); | ||
|  |           offset += 4; | ||
|  |           result = []; | ||
|  |           for (i = num; i > 0; i--) { | ||
|  |             result.push(parseGeometry()); | ||
|  |           } | ||
|  |           break; | ||
|  |       } | ||
|  |       return result; | ||
|  |     } | ||
|  |     return parseGeometry(); | ||
|  |   } | ||
|  | 
 | ||
|  |   parseVector() { | ||
|  |     const bufLen = this.readLengthCodedNumber(); | ||
|  |     const vectorEnd = this.offset + bufLen; | ||
|  |     const result = []; | ||
|  |     while (this.offset < vectorEnd && this.offset < this.end) { | ||
|  |       result.push(this.readFloat()); | ||
|  |     } | ||
|  |     return result; | ||
|  |   } | ||
|  | 
 | ||
|  |   parseDate(timezone) { | ||
|  |     const strLen = this.readLengthCodedNumber(); | ||
|  |     if (strLen === null) { | ||
|  |       return null; | ||
|  |     } | ||
|  |     if (strLen !== 10) { | ||
|  |       // we expect only YYYY-MM-DD here.
 | ||
|  |       // if for some reason it's not the case return invalid date
 | ||
|  |       return new Date(NaN); | ||
|  |     } | ||
|  |     const y = this.parseInt(4); | ||
|  |     this.offset++; // -
 | ||
|  |     const m = this.parseInt(2); | ||
|  |     this.offset++; // -
 | ||
|  |     const d = this.parseInt(2); | ||
|  |     if (!timezone || timezone === 'local') { | ||
|  |       return new Date(y, m - 1, d); | ||
|  |     } | ||
|  |     if (timezone === 'Z') { | ||
|  |       return new Date(Date.UTC(y, m - 1, d)); | ||
|  |     } | ||
|  |     return new Date( | ||
|  |       `${leftPad(4, y)}-${leftPad(2, m)}-${leftPad(2, d)}T00:00:00${timezone}` | ||
|  |     ); | ||
|  |   } | ||
|  | 
 | ||
|  |   parseDateTime(timezone) { | ||
|  |     const str = this.readLengthCodedString('binary'); | ||
|  |     if (str === null) { | ||
|  |       return null; | ||
|  |     } | ||
|  |     if (!timezone || timezone === 'local') { | ||
|  |       return new Date(str); | ||
|  |     } | ||
|  |     return new Date(`${str}${timezone}`); | ||
|  |   } | ||
|  | 
 | ||
|  |   parseFloat(len) { | ||
|  |     if (len === null) { | ||
|  |       return null; | ||
|  |     } | ||
|  |     let result = 0; | ||
|  |     const end = this.offset + len; | ||
|  |     let factor = 1; | ||
|  |     let pastDot = false; | ||
|  |     let charCode = 0; | ||
|  |     if (len === 0) { | ||
|  |       return 0; // TODO: assert? exception?
 | ||
|  |     } | ||
|  |     if (this.buffer[this.offset] === minus) { | ||
|  |       this.offset++; | ||
|  |       factor = -1; | ||
|  |     } | ||
|  |     if (this.buffer[this.offset] === plus) { | ||
|  |       this.offset++; // just ignore
 | ||
|  |     } | ||
|  |     while (this.offset < end) { | ||
|  |       charCode = this.buffer[this.offset]; | ||
|  |       if (charCode === dot) { | ||
|  |         pastDot = true; | ||
|  |         this.offset++; | ||
|  |       } else if (charCode === exponent || charCode === exponentCapital) { | ||
|  |         this.offset++; | ||
|  |         const exponentValue = this.parseInt(end - this.offset); | ||
|  |         return (result / factor) * Math.pow(10, exponentValue); | ||
|  |       } else { | ||
|  |         result *= 10; | ||
|  |         result += this.buffer[this.offset] - 48; | ||
|  |         this.offset++; | ||
|  |         if (pastDot) { | ||
|  |           factor = factor * 10; | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  |     return result / factor; | ||
|  |   } | ||
|  | 
 | ||
|  |   parseLengthCodedIntNoBigCheck() { | ||
|  |     return this.parseIntNoBigCheck(this.readLengthCodedNumber()); | ||
|  |   } | ||
|  | 
 | ||
|  |   parseLengthCodedInt(supportBigNumbers) { | ||
|  |     return this.parseInt(this.readLengthCodedNumber(), supportBigNumbers); | ||
|  |   } | ||
|  | 
 | ||
|  |   parseLengthCodedIntString() { | ||
|  |     return this.readLengthCodedString('binary'); | ||
|  |   } | ||
|  | 
 | ||
|  |   parseLengthCodedFloat() { | ||
|  |     return this.parseFloat(this.readLengthCodedNumber()); | ||
|  |   } | ||
|  | 
 | ||
|  |   peekByte() { | ||
|  |     return this.buffer[this.offset]; | ||
|  |   } | ||
|  | 
 | ||
|  |   // OxFE is often used as "Alt" flag - not ok, not error.
 | ||
|  |   // For example, it's first byte of AuthSwitchRequest
 | ||
|  |   isAlt() { | ||
|  |     return this.peekByte() === 0xfe; | ||
|  |   } | ||
|  | 
 | ||
|  |   isError() { | ||
|  |     return this.peekByte() === 0xff; | ||
|  |   } | ||
|  | 
 | ||
|  |   asError(encoding) { | ||
|  |     this.reset(); | ||
|  |     this.readInt8(); // fieldCount
 | ||
|  |     const errorCode = this.readInt16(); | ||
|  |     let sqlState = ''; | ||
|  |     if (this.buffer[this.offset] === 0x23) { | ||
|  |       this.skip(1); | ||
|  |       sqlState = this.readBuffer(5).toString(); | ||
|  |     } | ||
|  |     const message = this.readString(undefined, encoding); | ||
|  |     const err = new Error(message); | ||
|  |     err.code = ErrorCodeToName[errorCode]; | ||
|  |     err.errno = errorCode; | ||
|  |     err.sqlState = sqlState; | ||
|  |     err.sqlMessage = message; | ||
|  |     return err; | ||
|  |   } | ||
|  | 
 | ||
|  |   writeInt32(n) { | ||
|  |     this.buffer.writeUInt32LE(n, this.offset); | ||
|  |     this.offset += 4; | ||
|  |   } | ||
|  | 
 | ||
|  |   writeInt24(n) { | ||
|  |     this.writeInt8(n & 0xff); | ||
|  |     this.writeInt16(n >> 8); | ||
|  |   } | ||
|  | 
 | ||
|  |   writeInt16(n) { | ||
|  |     this.buffer.writeUInt16LE(n, this.offset); | ||
|  |     this.offset += 2; | ||
|  |   } | ||
|  | 
 | ||
|  |   writeInt8(n) { | ||
|  |     this.buffer.writeUInt8(n, this.offset); | ||
|  |     this.offset++; | ||
|  |   } | ||
|  | 
 | ||
|  |   writeDouble(n) { | ||
|  |     this.buffer.writeDoubleLE(n, this.offset); | ||
|  |     this.offset += 8; | ||
|  |   } | ||
|  | 
 | ||
|  |   writeBuffer(b) { | ||
|  |     b.copy(this.buffer, this.offset); | ||
|  |     this.offset += b.length; | ||
|  |   } | ||
|  | 
 | ||
|  |   writeNull() { | ||
|  |     this.buffer[this.offset] = 0xfb; | ||
|  |     this.offset++; | ||
|  |   } | ||
|  | 
 | ||
|  |   // TODO: refactor following three?
 | ||
|  |   writeNullTerminatedString(s, encoding) { | ||
|  |     const buf = StringParser.encode(s, encoding); | ||
|  |     this.buffer.length && buf.copy(this.buffer, this.offset); | ||
|  |     this.offset += buf.length; | ||
|  |     this.writeInt8(0); | ||
|  |   } | ||
|  | 
 | ||
|  |   writeString(s, encoding) { | ||
|  |     if (s === null) { | ||
|  |       this.writeInt8(0xfb); | ||
|  |       return; | ||
|  |     } | ||
|  |     if (s.length === 0) { | ||
|  |       return; | ||
|  |     } | ||
|  |     // const bytes = Buffer.byteLength(s, 'utf8');
 | ||
|  |     // this.buffer.write(s, this.offset, bytes, 'utf8');
 | ||
|  |     // this.offset += bytes;
 | ||
|  |     const buf = StringParser.encode(s, encoding); | ||
|  |     this.buffer.length && buf.copy(this.buffer, this.offset); | ||
|  |     this.offset += buf.length; | ||
|  |   } | ||
|  | 
 | ||
|  |   writeLengthCodedString(s, encoding) { | ||
|  |     const buf = StringParser.encode(s, encoding); | ||
|  |     this.writeLengthCodedNumber(buf.length); | ||
|  |     this.buffer.length && buf.copy(this.buffer, this.offset); | ||
|  |     this.offset += buf.length; | ||
|  |   } | ||
|  | 
 | ||
|  |   writeLengthCodedBuffer(b) { | ||
|  |     this.writeLengthCodedNumber(b.length); | ||
|  |     b.copy(this.buffer, this.offset); | ||
|  |     this.offset += b.length; | ||
|  |   } | ||
|  | 
 | ||
|  |   writeLengthCodedNumber(n) { | ||
|  |     if (n < 0xfb) { | ||
|  |       return this.writeInt8(n); | ||
|  |     } | ||
|  |     if (n < 0xffff) { | ||
|  |       this.writeInt8(0xfc); | ||
|  |       return this.writeInt16(n); | ||
|  |     } | ||
|  |     if (n < 0xffffff) { | ||
|  |       this.writeInt8(0xfd); | ||
|  |       return this.writeInt24(n); | ||
|  |     } | ||
|  |     if (n === null) { | ||
|  |       return this.writeInt8(0xfb); | ||
|  |     } | ||
|  |     // TODO: check that n is out of int precision
 | ||
|  |     this.writeInt8(0xfe); | ||
|  |     this.buffer.writeUInt32LE(n, this.offset); | ||
|  |     this.offset += 4; | ||
|  |     this.buffer.writeUInt32LE(n >> 32, this.offset); | ||
|  |     this.offset += 4; | ||
|  |     return this.offset; | ||
|  |   } | ||
|  | 
 | ||
|  |   writeDate(d, timezone) { | ||
|  |     this.buffer.writeUInt8(11, this.offset); | ||
|  |     if (!timezone || timezone === 'local') { | ||
|  |       this.buffer.writeUInt16LE(d.getFullYear(), this.offset + 1); | ||
|  |       this.buffer.writeUInt8(d.getMonth() + 1, this.offset + 3); | ||
|  |       this.buffer.writeUInt8(d.getDate(), this.offset + 4); | ||
|  |       this.buffer.writeUInt8(d.getHours(), this.offset + 5); | ||
|  |       this.buffer.writeUInt8(d.getMinutes(), this.offset + 6); | ||
|  |       this.buffer.writeUInt8(d.getSeconds(), this.offset + 7); | ||
|  |       this.buffer.writeUInt32LE(d.getMilliseconds() * 1000, this.offset + 8); | ||
|  |     } else { | ||
|  |       if (timezone !== 'Z') { | ||
|  |         const offset = | ||
|  |           (timezone[0] === '-' ? -1 : 1) * | ||
|  |           (parseInt(timezone.substring(1, 3), 10) * 60 + | ||
|  |             parseInt(timezone.substring(4), 10)); | ||
|  |         if (offset !== 0) { | ||
|  |           d = new Date(d.getTime() + 60000 * offset); | ||
|  |         } | ||
|  |       } | ||
|  |       this.buffer.writeUInt16LE(d.getUTCFullYear(), this.offset + 1); | ||
|  |       this.buffer.writeUInt8(d.getUTCMonth() + 1, this.offset + 3); | ||
|  |       this.buffer.writeUInt8(d.getUTCDate(), this.offset + 4); | ||
|  |       this.buffer.writeUInt8(d.getUTCHours(), this.offset + 5); | ||
|  |       this.buffer.writeUInt8(d.getUTCMinutes(), this.offset + 6); | ||
|  |       this.buffer.writeUInt8(d.getUTCSeconds(), this.offset + 7); | ||
|  |       this.buffer.writeUInt32LE(d.getUTCMilliseconds() * 1000, this.offset + 8); | ||
|  |     } | ||
|  |     this.offset += 12; | ||
|  |   } | ||
|  | 
 | ||
|  |   writeHeader(sequenceId) { | ||
|  |     const offset = this.offset; | ||
|  |     this.offset = 0; | ||
|  |     this.writeInt24(this.buffer.length - 4); | ||
|  |     this.writeInt8(sequenceId); | ||
|  |     this.offset = offset; | ||
|  |   } | ||
|  | 
 | ||
|  |   clone() { | ||
|  |     return new Packet(this.sequenceId, this.buffer, this.start, this.end); | ||
|  |   } | ||
|  | 
 | ||
|  |   type() { | ||
|  |     if (this.isEOF()) { | ||
|  |       return 'EOF'; | ||
|  |     } | ||
|  |     if (this.isError()) { | ||
|  |       return 'Error'; | ||
|  |     } | ||
|  |     if (this.buffer[this.offset] === 0) { | ||
|  |       return 'maybeOK'; // could be other packet types as well
 | ||
|  |     } | ||
|  |     return ''; | ||
|  |   } | ||
|  | 
 | ||
|  |   static lengthCodedNumberLength(n) { | ||
|  |     if (n < 0xfb) { | ||
|  |       return 1; | ||
|  |     } | ||
|  |     if (n < 0xffff) { | ||
|  |       return 3; | ||
|  |     } | ||
|  |     if (n < 0xffffff) { | ||
|  |       return 5; | ||
|  |     } | ||
|  |     return 9; | ||
|  |   } | ||
|  | 
 | ||
|  |   static lengthCodedStringLength(str, encoding) { | ||
|  |     const buf = StringParser.encode(str, encoding); | ||
|  |     const slen = buf.length; | ||
|  |     return Packet.lengthCodedNumberLength(slen) + slen; | ||
|  |   } | ||
|  | 
 | ||
|  |   static MockBuffer() { | ||
|  |     const noop = function () {}; | ||
|  |     const res = Buffer.alloc(0); | ||
|  |     for (const op in NativeBuffer.prototype) { | ||
|  |       if (typeof res[op] === 'function') { | ||
|  |         res[op] = noop; | ||
|  |       } | ||
|  |     } | ||
|  |     return res; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | module.exports = Packet; |