execute.js 3.6 KB
var Buffer = require('safe-buffer').Buffer;
var CursorType = require('../constants/cursor');
var CommandCodes = require('../constants/commands');
var Types = require('../constants/types');
var Packet = require('../packets/packet');
var CharsetToEncoding = require('../constants/charset_encodings.js');

function Execute(id, parameters, charsetNumber) {
  this.id = id;
  this.parameters = parameters;
  this.encoding = CharsetToEncoding[charsetNumber];
}

Execute.prototype.toPacket = function() {
  // 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

  var i;
  // 0 + 4 - length, seqId
  // 4 + 1 - COM_EXECUTE
  // 5 + 4 - stmtId
  // 9 + 1 - flags
  // 10 + 4 - iteration-count (always 1)
  var length = 14;
  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
    for (i = 0; i < this.parameters.length; i++) {
      if (this.parameters[i] !== null) {
        if (
          Object.prototype.toString.call(this.parameters[i]) == '[object Date]'
        ) {
          var d = this.parameters[i];
          // TODO: move to asMysqlDateTime()
          this.parameters[i] =
            [d.getFullYear(), d.getMonth() + 1, d.getDate()].join('-') +
            ' ' +
            [d.getHours(), d.getMinutes(), d.getSeconds()].join(':');
        }
        if (Buffer.isBuffer(this.parameters[i])) {
          length += Packet.lengthCodedNumberLength(this.parameters[i].length);
          length += this.parameters[i].length;
        } else {
          var str = this.parameters[i].toString();
          length += Packet.lengthCodedStringLength(str, this.encoding);
        }
      }
    }
  }

  var buffer = Buffer.allocUnsafe(length);
  var 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 (this.parameters && this.parameters.length > 0) {
    var bitmap = 0;
    var bitValue = 1;
    for (i = 0; i < this.parameters.length; i++) {
      if (this.parameters[i] === 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

    // TODO: don't typecast always to sting, use parameters type
    for (i = 0; i < this.parameters.length; i++) {
      if (this.parameters[i] !== null) {
        packet.writeInt16(Types.VAR_STRING);
      } else {
        packet.writeInt16(Types.NULL);
      }
    }

    for (i = 0; i < this.parameters.length; i++) {
      if (this.parameters[i] !== null) {
        if (Buffer.isBuffer(this.parameters[i])) {
          packet.writeLengthCodedBuffer(this.parameters[i]);
        } else {
          packet.writeLengthCodedString(
            this.parameters[i].toString(),
            this.encoding
          );
        }
      }
    }
  }
  return packet;
};

module.exports = Execute;