215 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			215 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | 'use strict'; | ||
|  | 
 | ||
|  | const CursorType = require('../constants/cursor'); | ||
|  | const CommandCodes = require('../constants/commands'); | ||
|  | const Types = require('../constants/types'); | ||
|  | const Packet = require('../packets/packet'); | ||
|  | const CharsetToEncoding = require('../constants/charset_encodings.js'); | ||
|  | 
 | ||
|  | function isJSON(value) { | ||
|  |   return ( | ||
|  |     Array.isArray(value) || | ||
|  |     value.constructor === Object || | ||
|  |     (typeof value.toJSON === 'function' && !Buffer.isBuffer(value)) | ||
|  |   ); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Converts a value to an object describing type, String/Buffer representation and length | ||
|  |  * @param {*} value | ||
|  |  */ | ||
|  | function toParameter(value, encoding, timezone) { | ||
|  |   let type = Types.VAR_STRING; | ||
|  |   let length; | ||
|  |   let writer = function (value) { | ||
|  |     // eslint-disable-next-line no-invalid-this
 | ||
|  |     return Packet.prototype.writeLengthCodedString.call(this, value, encoding); | ||
|  |   }; | ||
|  |   if (value !== null) { | ||
|  |     switch (typeof value) { | ||
|  |       case 'undefined': | ||
|  |         throw new TypeError('Bind parameters must not contain undefined'); | ||
|  | 
 | ||
|  |       case 'number': | ||
|  |         type = Types.DOUBLE; | ||
|  |         length = 8; | ||
|  |         writer = Packet.prototype.writeDouble; | ||
|  |         break; | ||
|  | 
 | ||
|  |       case 'boolean': | ||
|  |         value = value | 0; | ||
|  |         type = Types.TINY; | ||
|  |         length = 1; | ||
|  |         writer = Packet.prototype.writeInt8; | ||
|  |         break; | ||
|  | 
 | ||
|  |       case 'object': | ||
|  |         if (Object.prototype.toString.call(value) === '[object Date]') { | ||
|  |           type = Types.DATETIME; | ||
|  |           length = 12; | ||
|  |           writer = function (value) { | ||
|  |             // eslint-disable-next-line no-invalid-this
 | ||
|  |             return Packet.prototype.writeDate.call(this, value, timezone); | ||
|  |           }; | ||
|  |         } else if (isJSON(value)) { | ||
|  |           value = JSON.stringify(value); | ||
|  |           type = Types.JSON; | ||
|  |         } else if (Buffer.isBuffer(value)) { | ||
|  |           length = Packet.lengthCodedNumberLength(value.length) + value.length; | ||
|  |           writer = Packet.prototype.writeLengthCodedBuffer; | ||
|  |         } | ||
|  |         break; | ||
|  | 
 | ||
|  |       default: | ||
|  |         value = value.toString(); | ||
|  |     } | ||
|  |   } else { | ||
|  |     value = ''; | ||
|  |     type = Types.NULL; | ||
|  |   } | ||
|  |   if (!length) { | ||
|  |     length = Packet.lengthCodedStringLength(value, encoding); | ||
|  |   } | ||
|  |   return { value, type, length, writer }; | ||
|  | } | ||
|  | 
 | ||
|  | class Execute { | ||
|  |   constructor(id, parameters, charsetNumber, timezone) { | ||
|  |     this.id = id; | ||
|  |     this.parameters = parameters; | ||
|  |     this.encoding = CharsetToEncoding[charsetNumber]; | ||
|  |     this.timezone = timezone; | ||
|  |   } | ||
|  | 
 | ||
|  |   static fromPacket(packet, encoding) { | ||
|  |     const stmtId = packet.readInt32(); | ||
|  |     const flags = packet.readInt8(); | ||
|  |     const iterationCount = packet.readInt32(); | ||
|  | 
 | ||
|  |     let i = packet.offset; | ||
|  |     while (i < packet.end - 1) { | ||
|  |       if ( | ||
|  |         (packet.buffer[i + 1] === Types.VAR_STRING || | ||
|  |           packet.buffer[i + 1] === Types.NULL || | ||
|  |           packet.buffer[i + 1] === Types.DOUBLE || | ||
|  |           packet.buffer[i + 1] === Types.TINY || | ||
|  |           packet.buffer[i + 1] === Types.DATETIME || | ||
|  |           packet.buffer[i + 1] === Types.JSON) && | ||
|  |         packet.buffer[i] === 1 && | ||
|  |         packet.buffer[i + 2] === 0 | ||
|  |       ) { | ||
|  |         break; | ||
|  |       } else { | ||
|  |         packet.readInt8(); | ||
|  |       } | ||
|  |       i++; | ||
|  |     } | ||
|  | 
 | ||
|  |     const types = []; | ||
|  | 
 | ||
|  |     for (let i = packet.offset + 1; i < packet.end - 1; i++) { | ||
|  |       if ( | ||
|  |         (packet.buffer[i] === Types.VAR_STRING || | ||
|  |           packet.buffer[i] === Types.NULL || | ||
|  |           packet.buffer[i] === Types.DOUBLE || | ||
|  |           packet.buffer[i] === Types.TINY || | ||
|  |           packet.buffer[i] === Types.DATETIME || | ||
|  |           packet.buffer[i] === Types.JSON) && | ||
|  |         packet.buffer[i + 1] === 0 | ||
|  |       ) { | ||
|  |         types.push(packet.buffer[i]); | ||
|  |         packet.skip(2); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     packet.skip(1); | ||
|  | 
 | ||
|  |     const values = []; | ||
|  |     for (let i = 0; i < types.length; i++) { | ||
|  |       if (types[i] === Types.VAR_STRING) { | ||
|  |         values.push(packet.readLengthCodedString(encoding)); | ||
|  |       } else if (types[i] === Types.DOUBLE) { | ||
|  |         values.push(packet.readDouble()); | ||
|  |       } else if (types[i] === Types.TINY) { | ||
|  |         values.push(packet.readInt8()); | ||
|  |       } else if (types[i] === Types.DATETIME) { | ||
|  |         values.push(packet.readDateTime()); | ||
|  |       } else if (types[i] === Types.JSON) { | ||
|  |         values.push(JSON.parse(packet.readLengthCodedString(encoding))); | ||
|  |       } | ||
|  |       if (types[i] === Types.NULL) { | ||
|  |         values.push(null); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     return { stmtId, flags, iterationCount, values }; | ||
|  |   } | ||
|  | 
 | ||
|  |   toPacket() { | ||
|  |     // TODO: don't try to calculate packet length in advance, allocate some big buffer in advance (header + 256 bytes?)
 | ||
|  |     // and copy + reallocate if not enough
 | ||
|  |     // 0 + 4 - length, seqId
 | ||
|  |     // 4 + 1 - COM_EXECUTE
 | ||
|  |     // 5 + 4 - stmtId
 | ||
|  |     // 9 + 1 - flags
 | ||
|  |     // 10 + 4 - iteration-count (always 1)
 | ||
|  |     let length = 14; | ||
|  |     let parameters; | ||
|  |     if (this.parameters && this.parameters.length > 0) { | ||
|  |       length += Math.floor((this.parameters.length + 7) / 8); | ||
|  |       length += 1; // new-params-bound-flag
 | ||
|  |       length += 2 * this.parameters.length; // type byte for each parameter if new-params-bound-flag is set
 | ||
|  |       parameters = this.parameters.map((value) => | ||
|  |         toParameter(value, this.encoding, this.timezone) | ||
|  |       ); | ||
|  |       length += parameters.reduce( | ||
|  |         (accumulator, parameter) => accumulator + parameter.length, | ||
|  |         0 | ||
|  |       ); | ||
|  |     } | ||
|  |     const buffer = Buffer.allocUnsafe(length); | ||
|  |     const packet = new Packet(0, buffer, 0, length); | ||
|  |     packet.offset = 4; | ||
|  |     packet.writeInt8(CommandCodes.STMT_EXECUTE); | ||
|  |     packet.writeInt32(this.id); | ||
|  |     packet.writeInt8(CursorType.NO_CURSOR); // flags
 | ||
|  |     packet.writeInt32(1); // iteration-count, always 1
 | ||
|  |     if (parameters) { | ||
|  |       let bitmap = 0; | ||
|  |       let bitValue = 1; | ||
|  |       parameters.forEach((parameter) => { | ||
|  |         if (parameter.type === Types.NULL) { | ||
|  |           bitmap += bitValue; | ||
|  |         } | ||
|  |         bitValue *= 2; | ||
|  |         if (bitValue === 256) { | ||
|  |           packet.writeInt8(bitmap); | ||
|  |           bitmap = 0; | ||
|  |           bitValue = 1; | ||
|  |         } | ||
|  |       }); | ||
|  |       if (bitValue !== 1) { | ||
|  |         packet.writeInt8(bitmap); | ||
|  |       } | ||
|  |       // TODO: explain meaning of the flag
 | ||
|  |       // afaik, if set n*2 bytes with type of parameter are sent before parameters
 | ||
|  |       // if not, previous execution types are used (TODO prooflink)
 | ||
|  |       packet.writeInt8(1); // new-params-bound-flag
 | ||
|  |       // Write parameter types
 | ||
|  |       parameters.forEach((parameter) => { | ||
|  |         packet.writeInt8(parameter.type); // field type
 | ||
|  |         packet.writeInt8(0); // parameter flag
 | ||
|  |       }); | ||
|  |       // Write parameter values
 | ||
|  |       parameters.forEach((parameter) => { | ||
|  |         if (parameter.type !== Types.NULL) { | ||
|  |           parameter.writer.call(packet, parameter.value); | ||
|  |         } | ||
|  |       }); | ||
|  |     } | ||
|  |     return packet; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | module.exports = Execute; |