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;
 |