"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _events = require("events");

var _dataType = require("./data-type");

var _errors = require("./errors");

/**
 * ```js
 * const { Request } = require('tedious');
 * const request = new Request("select 42, 'hello world'", (err, rowCount) {
 *   // Request completion callback...
 * });
 * connection.execSql(request);
 * ```
 */
class Request extends _events.EventEmitter {
  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * @private
   */

  /**
   * This event, describing result set columns, will be emitted before row
   * events are emitted. This event may be emited multiple times when more
   * than one recordset is produced by the statement.
   *
   * An array like object, where the columns can be accessed either by index
   * or name. Columns with a name that is an integer are not accessible by name,
   * as it would be interpreted as an array index.
   */

  /**
   * The request has been prepared and can be used in subsequent calls to execute and unprepare.
   */

  /**
   * The request encountered an error and has not been prepared.
   */

  /**
   * A row resulting from execution of the SQL statement.
   */

  /**
   * All rows from a result set have been provided (through `row` events).
   *
   * This token is used to indicate the completion of a SQL statement.
   * As multiple SQL statements can be sent to the server in a single SQL batch, multiple `done` can be generated.
   * An `done` event is emited for each SQL statement in the SQL batch except variable declarations.
   * For execution of SQL statements within stored procedures, `doneProc` and `doneInProc` events are used in place of `done`.
   *
   * If you are using [[Connection.execSql]] then SQL server may treat the multiple calls with the same query as a stored procedure.
   * When this occurs, the `doneProc` and `doneInProc` events may be emitted instead. You must handle both events to ensure complete coverage.
   */

  /**
   * `request.on('doneInProc', function (rowCount, more, rows) { });`
   *
   * Indicates the completion status of a SQL statement within a stored procedure. All rows from a statement
   * in a stored procedure have been provided (through `row` events).
   *
   * This event may also occur when executing multiple calls with the same query using [[execSql]].
   */

  /**
   * Indicates the completion status of a stored procedure. This is also generated for stored procedures
   * executed through SQL statements.\
   * This event may also occur when executing multiple calls with the same query using [[execSql]].
   */

  /**
   * A value for an output parameter (that was added to the request with [[addOutputParameter]]).
   * See also `Using Parameters`.
   */

  /**
   * This event gives the columns by which data is ordered, if `ORDER BY` clause is executed in SQL Server.
   */
  on(event, listener) {
    return super.on(event, listener);
  }
  /**
   * @private
   */


  emit(event, ...args) {
    return super.emit(event, ...args);
  }
  /**
   * @param sqlTextOrProcedure
   *   The SQL statement to be executed
   *
   * @param callback
   *   The callback to execute once the request has been fully completed.
   */


  constructor(sqlTextOrProcedure, callback) {
    super();
    this.sqlTextOrProcedure = void 0;
    this.parameters = void 0;
    this.parametersByName = void 0;
    this.originalParameters = void 0;
    this.preparing = void 0;
    this.canceled = void 0;
    this.paused = void 0;
    this.userCallback = void 0;
    this.handle = void 0;
    this.error = void 0;
    this.connection = void 0;
    this.timeout = void 0;
    this.rows = void 0;
    this.rst = void 0;
    this.rowCount = void 0;
    this.callback = void 0;
    this.sqlTextOrProcedure = sqlTextOrProcedure;
    this.parameters = [];
    this.parametersByName = {};
    this.originalParameters = [];
    this.preparing = false;
    this.handle = undefined;
    this.canceled = false;
    this.paused = false;
    this.error = undefined;
    this.connection = undefined;
    this.timeout = undefined;
    this.userCallback = callback;

    this.callback = function (err, rowCount, rows) {
      if (this.preparing) {
        this.preparing = false;

        if (err) {
          this.emit('error', err);
        } else {
          this.emit('prepared');
        }
      } else {
        this.userCallback(err, rowCount, rows);
        this.emit('requestCompleted');
      }
    };
  }
  /**
   * @param name
   *   The parameter name. This should correspond to a parameter in the SQL,
   *   or a parameter that a called procedure expects. The name should not start with `@`.
   *
   * @param type
   *   One of the supported data types.
   *
   * @param value
   *   The value that the parameter is to be given. The Javascript type of the
   *   argument should match that documented for data types.
   *
   * @param options
   *   Additional type options. Optional.
   */
  // TODO: `type` must be a valid TDS value type


  addParameter(name, type, value, options) {
    if (options == null) {
      options = {};
    }

    const _options = options,
          _options$output = _options.output,
          output = _options$output === void 0 ? false : _options$output,
          length = _options.length,
          precision = _options.precision,
          scale = _options.scale;
    const parameter = {
      type: type,
      name: name,
      value: value,
      output: output,
      length: length,
      precision: precision,
      scale: scale
    };
    this.parameters.push(parameter);
    this.parametersByName[name] = parameter;
  }
  /**
   * @param name
   *   The parameter name. This should correspond to a parameter in the SQL,
   *   or a parameter that a called procedure expects.
   *
   * @param type
   *   One of the supported data types.
   *
   * @param value
   *   The value that the parameter is to be given. The Javascript type of the
   *   argument should match that documented for data types
   *
   * @param options
   *   Additional type options. Optional.
   */


  addOutputParameter(name, type, value, options) {
    if (options == null) {
      options = {};
    }

    options.output = true;
    this.addParameter(name, type, value, options);
  }
  /**
   * @private
   */


  makeParamsParameter(parameters) {
    let paramsParameter = '';

    for (let i = 0, len = parameters.length; i < len; i++) {
      const parameter = parameters[i];

      if (paramsParameter.length > 0) {
        paramsParameter += ', ';
      }

      paramsParameter += '@' + parameter.name + ' ';
      paramsParameter += parameter.type.declaration(parameter);

      if (parameter.output) {
        paramsParameter += ' OUTPUT';
      }
    }

    return paramsParameter;
  }
  /**
   * @private
   */


  transformIntoExecuteSqlRpc() {
    if (this.validateParameters()) {
      return;
    }

    this.originalParameters = this.parameters;
    this.parameters = [];
    this.addParameter('statement', _dataType.typeByName.NVarChar, this.sqlTextOrProcedure);

    if (this.originalParameters.length) {
      this.addParameter('params', _dataType.typeByName.NVarChar, this.makeParamsParameter(this.originalParameters));
    }

    for (let i = 0, len = this.originalParameters.length; i < len; i++) {
      const parameter = this.originalParameters[i];
      this.parameters.push(parameter);
    }

    this.sqlTextOrProcedure = 'sp_executesql';
  }
  /**
   * @private
   */


  transformIntoPrepareRpc() {
    this.originalParameters = this.parameters;
    this.parameters = [];
    this.addOutputParameter('handle', _dataType.typeByName.Int, undefined);
    this.addParameter('params', _dataType.typeByName.NVarChar, this.makeParamsParameter(this.originalParameters));
    this.addParameter('stmt', _dataType.typeByName.NVarChar, this.sqlTextOrProcedure);
    this.sqlTextOrProcedure = 'sp_prepare';
    this.preparing = true;
    this.on('returnValue', (name, value) => {
      if (name === 'handle') {
        this.handle = value;
      } else {
        this.error = (0, _errors.RequestError)(`Tedious > Unexpected output parameter ${name} from sp_prepare`);
      }
    });
  }
  /**
   * @private
   */


  transformIntoUnprepareRpc() {
    this.parameters = [];
    this.addParameter('handle', _dataType.typeByName.Int, this.handle);
    this.sqlTextOrProcedure = 'sp_unprepare';
  }
  /**
   * @private
   */


  transformIntoExecuteRpc(parameters) {
    this.parameters = [];
    this.addParameter('handle', _dataType.typeByName.Int, this.handle);

    for (let i = 0, len = this.originalParameters.length; i < len; i++) {
      const parameter = this.originalParameters[i];
      parameter.value = parameters[parameter.name];
      this.parameters.push(parameter);
    }

    if (this.validateParameters()) {
      return;
    }

    this.sqlTextOrProcedure = 'sp_execute';
  }
  /**
   * @private
   */


  validateParameters() {
    for (let i = 0, len = this.parameters.length; i < len; i++) {
      const parameter = this.parameters[i];
      const value = parameter.type.validate(parameter.value);

      if (value instanceof TypeError) {
        return this.error = new _errors.RequestError('Validation failed for parameter \'' + parameter.name + '\'. ' + value.message, 'EPARAM');
      }

      parameter.value = value;
    }

    return null;
  }
  /**
   * Temporarily suspends the flow of data from the database. No more `row` events will be emitted until [[resume] is called.
   * If this request is already in a paused state, calling [[pause]] has no effect.
   */


  pause() {
    if (this.paused) {
      return;
    }

    this.paused = true;

    if (this.connection) {
      this.connection.pauseRequest(this);
    }
  }
  /**
   * Resumes the flow of data from the database.
   * If this request is not in a paused state, calling [[resume]] has no effect.
   */


  resume() {
    if (!this.paused) {
      return;
    }

    this.paused = false;

    if (this.connection) {
      this.connection.resumeRequest(this);
    }
  }
  /**
   * Cancels a request while waiting for a server response.
   */


  cancel() {
    if (this.canceled) {
      return;
    }

    this.canceled = true;
    this.emit('cancel');
  }
  /**
   * Sets a timeout for this request.
   *
   * @param timeout
   *   The number of milliseconds before the request is considered failed,
   *   or `0` for no timeout. When no timeout is set for the request,
   *   the [[ConnectionOptions.requestTimeout]] of the [[Connection]] is used.
   */


  setTimeout(timeout) {
    this.timeout = timeout;
  }

}

var _default = Request;
exports.default = _default;
module.exports = Request;