text: 'use strict';
  
  const { AbstractConnectionManager } = require('../abstract/connection-manager');
  const { AsyncQueue } = require('./async-queue');
  const { logger } = require('../../utils/logger');
  const sequelizeErrors = require('../../errors');
  const DataTypes = require('../../data-types').mssql;
  const parserStore = require('../parserStore')('mssql');
  
  const debug = logger.debugContext('connection:mssql');
  const debugTedious = logger.debugContext('connection:mssql:tedious');
  
  export class MsSqlConnectionManager extends AbstractConnectionManager {
    constructor(dialect, sequelize) {
      super(dialect, sequelize);
      this.lib = this._loadDialectModule('tedious');
      this.refreshTypeParser(DataTypes);
    }
  
    _refreshTypeParser(dataType) {
      parserStore.refresh(dataType);
    }
  
    _clearTypeParser() {
      parserStore.clear();
    }
  
    async connect(config) {
      const connectionConfig = {
        server: config.host,
        authentication: {
          type: 'default',
          options: {
            userName: config.username || undefined,
            password: config.password || undefined,
          },
        },
        options: {
          port: Number.parseInt(config.port, 10),
          database: config.database,
          trustServerCertificate: true,
        },
      };
  
      if (config.dialectOptions) {
        // only set port if no instance name was provided
        if (
          config.dialectOptions.options
          && config.dialectOptions.options.instanceName
        ) {
          delete connectionConfig.options.port;
        }
  
        if (config.dialectOptions.authentication) {
          Object.assign(connectionConfig.authentication, config.dialectOptions.authentication);
        }
  
        Object.assign(connectionConfig.options, config.dialectOptions.options);
      }
  
      try {
        return await new Promise((resolve, reject) => {
          const connection = new this.lib.Connection(connectionConfig);
          if (connection.state === connection.STATE.INITIALIZED) {
            connection.connect();
          }
  
          connection.queue = new AsyncQueue();
          connection.lib = this.lib;
  
          const connectHandler = error => {
            connection.removeListener('end', endHandler);
            connection.removeListener('error', errorHandler);
  
            if (error) {
              return reject(error);
            }
  
            debug('connection acquired');
            resolve(connection);
          };
  
          const endHandler = () => {
            connection.removeListener('connect', connectHandler);
            connection.removeListener('error', errorHandler);
            reject(new Error('Connection was closed by remote server'));
          };
  
          const errorHandler = error => {
            connection.removeListener('connect', connectHandler);
            connection.removeListener('end', endHandler);
            reject(error);
          };
  
          connection.once('error', errorHandler);
          connection.once('end', endHandler);
          connection.once('connect', connectHandler);
  
          /*
           * Permanently attach this event before connection is even acquired
           * tedious sometime emits error even after connect(with error).
           *
           * If we dont attach this even that unexpected error event will crash node process
           *
           * E.g. connectTimeout is set higher than requestTimeout
           */
          connection.on('error', error => {
            switch (error.code) {
              case 'ESOCKET':
              case 'ECONNRESET':
                this.pool.destroy(connection);
            }
          });
  
          if (config.dialectOptions && config.dialectOptions.debug) {
            connection.on('debug', debugTedious.log.bind(debugTedious));
          }
        });
      } catch (error) {
        if (!error.code) {
          throw new sequelizeErrors.ConnectionError(error);
        }
  
        switch (error.code) {
          case 'ESOCKET':
            if (error.message.includes('connect EHOSTUNREACH')) {
              throw new sequelizeErrors.HostNotReachableError(error);
            }
  
            if (error.message.includes('connect ENETUNREACH')) {
              throw new sequelizeErrors.HostNotReachableError(error);
            }
  
            if (error.message.includes('connect EADDRNOTAVAIL')) {
              throw new sequelizeErrors.HostNotReachableError(error);
            }
  
            if (error.message.includes('getaddrinfo ENOTFOUND')) {
              throw new sequelizeErrors.HostNotFoundError(error);
            }
  
            if (error.message.includes('connect ECONNREFUSED')) {
              throw new sequelizeErrors.ConnectionRefusedError(error);
            }
  
            throw new sequelizeErrors.ConnectionError(error);
          case 'ER_ACCESS_DENIED_ERROR':
          case 'ELOGIN':
            throw new sequelizeErrors.AccessDeniedError(error);
          case 'EINVAL':
            throw new sequelizeErrors.InvalidConnectionError(error);
          default:
            throw new sequelizeErrors.ConnectionError(error);
        }
      }
    }
  
    async disconnect(connection) {
      // Don't disconnect a connection that is already disconnected
      if (connection.closed) {
        return;
      }
  
      connection.queue.close();
  
      return new Promise(resolve => {
        connection.on('end', resolve);
        connection.close();
        debug('connection closed');
      });
    }
  
    validate(connection) {
      return connection && (connection.loggedIn || connection.state.name === 'LoggedIn');
    }
  }
  