tedious
Version:
A TDS driver, for connecting to MS SQLServer databases.
1,600 lines (1,291 loc) • 360 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _crypto = _interopRequireDefault(require("crypto"));
var _os = _interopRequireDefault(require("os"));
var _dns = _interopRequireDefault(require("dns"));
var _constants = _interopRequireDefault(require("constants"));
var _stream = require("stream");
var _identity = require("@azure/identity");
var _bulkLoad = _interopRequireDefault(require("./bulk-load"));
var _debug = _interopRequireDefault(require("./debug"));
var _events = require("events");
var _instanceLookup = require("./instance-lookup");
var _transientErrorLookup = require("./transient-error-lookup");
var _packet = require("./packet");
var _preloginPayload = _interopRequireDefault(require("./prelogin-payload"));
var _login7Payload = _interopRequireDefault(require("./login7-payload"));
var _ntlmPayload = _interopRequireDefault(require("./ntlm-payload"));
var _request = _interopRequireDefault(require("./request"));
var _rpcrequestPayload = _interopRequireDefault(require("./rpcrequest-payload"));
var _sqlbatchPayload = _interopRequireDefault(require("./sqlbatch-payload"));
var _messageIo = _interopRequireDefault(require("./message-io"));
var _tokenStreamParser = require("./token/token-stream-parser");
var _transaction = require("./transaction");
var _errors = require("./errors");
var _connector = require("./connector");
var _library = require("./library");
var _tdsVersions = require("./tds-versions");
var _message = _interopRequireDefault(require("./message"));
var _ntlm = require("./ntlm");
var _nodeAbortController = require("node-abort-controller");
var _dataType = require("./data-type");
var _bulkLoadPayload = require("./bulk-load-payload");
var _esAggregateError = _interopRequireDefault(require("es-aggregate-error"));
var _package = require("../package.json");
var _url = require("url");
var _handler = require("./token/handler");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* @private
*/
const KEEP_ALIVE_INITIAL_DELAY = 30 * 1000;
/**
* @private
*/
const DEFAULT_CONNECT_TIMEOUT = 15 * 1000;
/**
* @private
*/
const DEFAULT_CLIENT_REQUEST_TIMEOUT = 15 * 1000;
/**
* @private
*/
const DEFAULT_CANCEL_TIMEOUT = 5 * 1000;
/**
* @private
*/
const DEFAULT_CONNECT_RETRY_INTERVAL = 500;
/**
* @private
*/
const DEFAULT_PACKET_SIZE = 4 * 1024;
/**
* @private
*/
const DEFAULT_TEXTSIZE = 2147483647;
/**
* @private
*/
const DEFAULT_DATEFIRST = 7;
/**
* @private
*/
const DEFAULT_PORT = 1433;
/**
* @private
*/
const DEFAULT_TDS_VERSION = '7_4';
/**
* @private
*/
const DEFAULT_LANGUAGE = 'us_english';
/**
* @private
*/
const DEFAULT_DATEFORMAT = 'mdy';
/**
* @private
*/
const CLEANUP_TYPE = {
NORMAL: 0,
REDIRECT: 1,
RETRY: 2
};
/**
* A [[Connection]] instance represents a single connection to a database server.
*
* ```js
* var Connection = require('tedious').Connection;
* var config = {
* "authentication": {
* ...,
* "options": {...}
* },
* "options": {...}
* };
* var connection = new Connection(config);
* ```
*
* Only one request at a time may be executed on a connection. Once a [[Request]]
* has been initiated (with [[Connection.callProcedure]], [[Connection.execSql]],
* or [[Connection.execSqlBatch]]), another should not be initiated until the
* [[Request]]'s completion callback is called.
*/
class Connection extends _events.EventEmitter {
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* @private
*/
/**
* Note: be aware of the different options field:
* 1. config.authentication.options
* 2. config.options
*
* ```js
* const { Connection } = require('tedious');
*
* const config = {
* "authentication": {
* ...,
* "options": {...}
* },
* "options": {...}
* };
*
* const connection = new Connection(config);
* ```
*
* @param config
*/
constructor(config) {
super();
this.fedAuthRequired = void 0;
this.config = void 0;
this.secureContextOptions = void 0;
this.inTransaction = void 0;
this.transactionDescriptors = void 0;
this.transactionDepth = void 0;
this.isSqlBatch = void 0;
this.curTransientRetryCount = void 0;
this.transientErrorLookup = void 0;
this.closed = void 0;
this.loginError = void 0;
this.debug = void 0;
this.ntlmpacket = void 0;
this.ntlmpacketBuffer = void 0;
this.routingData = void 0;
this.messageIo = void 0;
this.state = void 0;
this.resetConnectionOnNextRequest = void 0;
this.request = void 0;
this.procReturnStatusValue = void 0;
this.socket = void 0;
this.messageBuffer = void 0;
this.connectTimer = void 0;
this.cancelTimer = void 0;
this.requestTimer = void 0;
this.retryTimer = void 0;
this._cancelAfterRequestSent = void 0;
this.databaseCollation = void 0;
if (typeof config !== 'object' || config === null) {
throw new TypeError('The "config" argument is required and must be of type Object.');
}
if (typeof config.server !== 'string') {
throw new TypeError('The "config.server" property is required and must be of type string.');
}
this.fedAuthRequired = false;
let authentication;
if (config.authentication !== undefined) {
if (typeof config.authentication !== 'object' || config.authentication === null) {
throw new TypeError('The "config.authentication" property must be of type Object.');
}
const type = config.authentication.type;
const options = config.authentication.options === undefined ? {} : config.authentication.options;
if (typeof type !== 'string') {
throw new TypeError('The "config.authentication.type" property must be of type string.');
}
if (type !== 'default' && type !== 'ntlm' && type !== 'azure-active-directory-password' && type !== 'azure-active-directory-access-token' && type !== 'azure-active-directory-msi-vm' && type !== 'azure-active-directory-msi-app-service' && type !== 'azure-active-directory-service-principal-secret' && type !== 'azure-active-directory-default') {
throw new TypeError('The "type" property must one of "default", "ntlm", "azure-active-directory-password", "azure-active-directory-access-token", "azure-active-directory-default", "azure-active-directory-msi-vm" or "azure-active-directory-msi-app-service" or "azure-active-directory-service-principal-secret".');
}
if (typeof options !== 'object' || options === null) {
throw new TypeError('The "config.authentication.options" property must be of type object.');
}
if (type === 'ntlm') {
if (typeof options.domain !== 'string') {
throw new TypeError('The "config.authentication.options.domain" property must be of type string.');
}
if (options.userName !== undefined && typeof options.userName !== 'string') {
throw new TypeError('The "config.authentication.options.userName" property must be of type string.');
}
if (options.password !== undefined && typeof options.password !== 'string') {
throw new TypeError('The "config.authentication.options.password" property must be of type string.');
}
authentication = {
type: 'ntlm',
options: {
userName: options.userName,
password: options.password,
domain: options.domain && options.domain.toUpperCase()
}
};
} else if (type === 'azure-active-directory-password') {
if (typeof options.clientId !== 'string') {
throw new TypeError('The "config.authentication.options.clientId" property must be of type string.');
}
if (options.userName !== undefined && typeof options.userName !== 'string') {
throw new TypeError('The "config.authentication.options.userName" property must be of type string.');
}
if (options.password !== undefined && typeof options.password !== 'string') {
throw new TypeError('The "config.authentication.options.password" property must be of type string.');
}
if (options.tenantId !== undefined && typeof options.tenantId !== 'string') {
throw new TypeError('The "config.authentication.options.tenantId" property must be of type string.');
}
authentication = {
type: 'azure-active-directory-password',
options: {
userName: options.userName,
password: options.password,
tenantId: options.tenantId,
clientId: options.clientId
}
};
} else if (type === 'azure-active-directory-access-token') {
if (typeof options.token !== 'string') {
throw new TypeError('The "config.authentication.options.token" property must be of type string.');
}
authentication = {
type: 'azure-active-directory-access-token',
options: {
token: options.token
}
};
} else if (type === 'azure-active-directory-msi-vm') {
if (options.clientId !== undefined && typeof options.clientId !== 'string') {
throw new TypeError('The "config.authentication.options.clientId" property must be of type string.');
}
authentication = {
type: 'azure-active-directory-msi-vm',
options: {
clientId: options.clientId
}
};
} else if (type === 'azure-active-directory-default') {
if (options.clientId !== undefined && typeof options.clientId !== 'string') {
throw new TypeError('The "config.authentication.options.clientId" property must be of type string.');
}
authentication = {
type: 'azure-active-directory-default',
options: {
clientId: options.clientId
}
};
} else if (type === 'azure-active-directory-msi-app-service') {
if (options.clientId !== undefined && typeof options.clientId !== 'string') {
throw new TypeError('The "config.authentication.options.clientId" property must be of type string.');
}
authentication = {
type: 'azure-active-directory-msi-app-service',
options: {
clientId: options.clientId
}
};
} else if (type === 'azure-active-directory-service-principal-secret') {
if (typeof options.clientId !== 'string') {
throw new TypeError('The "config.authentication.options.clientId" property must be of type string.');
}
if (typeof options.clientSecret !== 'string') {
throw new TypeError('The "config.authentication.options.clientSecret" property must be of type string.');
}
if (typeof options.tenantId !== 'string') {
throw new TypeError('The "config.authentication.options.tenantId" property must be of type string.');
}
authentication = {
type: 'azure-active-directory-service-principal-secret',
options: {
clientId: options.clientId,
clientSecret: options.clientSecret,
tenantId: options.tenantId
}
};
} else {
if (options.userName !== undefined && typeof options.userName !== 'string') {
throw new TypeError('The "config.authentication.options.userName" property must be of type string.');
}
if (options.password !== undefined && typeof options.password !== 'string') {
throw new TypeError('The "config.authentication.options.password" property must be of type string.');
}
authentication = {
type: 'default',
options: {
userName: options.userName,
password: options.password
}
};
}
} else {
authentication = {
type: 'default',
options: {
userName: undefined,
password: undefined
}
};
}
this.config = {
server: config.server,
authentication: authentication,
options: {
abortTransactionOnError: false,
appName: undefined,
camelCaseColumns: false,
cancelTimeout: DEFAULT_CANCEL_TIMEOUT,
columnEncryptionKeyCacheTTL: 2 * 60 * 60 * 1000,
// Units: miliseconds
columnEncryptionSetting: false,
columnNameReplacer: undefined,
connectionRetryInterval: DEFAULT_CONNECT_RETRY_INTERVAL,
connectTimeout: DEFAULT_CONNECT_TIMEOUT,
connectionIsolationLevel: _transaction.ISOLATION_LEVEL.READ_COMMITTED,
cryptoCredentialsDetails: {},
database: undefined,
datefirst: DEFAULT_DATEFIRST,
dateFormat: DEFAULT_DATEFORMAT,
debug: {
data: false,
packet: false,
payload: false,
token: false
},
enableAnsiNull: true,
enableAnsiNullDefault: true,
enableAnsiPadding: true,
enableAnsiWarnings: true,
enableArithAbort: true,
enableConcatNullYieldsNull: true,
enableCursorCloseOnCommit: null,
enableImplicitTransactions: false,
enableNumericRoundabort: false,
enableQuotedIdentifier: true,
encrypt: true,
fallbackToDefaultDb: false,
encryptionKeyStoreProviders: undefined,
instanceName: undefined,
isolationLevel: _transaction.ISOLATION_LEVEL.READ_COMMITTED,
language: DEFAULT_LANGUAGE,
localAddress: undefined,
maxRetriesOnTransientErrors: 3,
multiSubnetFailover: false,
packetSize: DEFAULT_PACKET_SIZE,
port: DEFAULT_PORT,
readOnlyIntent: false,
requestTimeout: DEFAULT_CLIENT_REQUEST_TIMEOUT,
rowCollectionOnDone: false,
rowCollectionOnRequestCompletion: false,
serverName: undefined,
serverSupportsColumnEncryption: false,
tdsVersion: DEFAULT_TDS_VERSION,
textsize: DEFAULT_TEXTSIZE,
trustedServerNameAE: undefined,
trustServerCertificate: false,
useColumnNames: false,
useUTC: true,
workstationId: undefined,
lowerCaseGuids: false
}
};
if (config.options) {
if (config.options.port && config.options.instanceName) {
throw new Error('Port and instanceName are mutually exclusive, but ' + config.options.port + ' and ' + config.options.instanceName + ' provided');
}
if (config.options.abortTransactionOnError !== undefined) {
if (typeof config.options.abortTransactionOnError !== 'boolean' && config.options.abortTransactionOnError !== null) {
throw new TypeError('The "config.options.abortTransactionOnError" property must be of type string or null.');
}
this.config.options.abortTransactionOnError = config.options.abortTransactionOnError;
}
if (config.options.appName !== undefined) {
if (typeof config.options.appName !== 'string') {
throw new TypeError('The "config.options.appName" property must be of type string.');
}
this.config.options.appName = config.options.appName;
}
if (config.options.camelCaseColumns !== undefined) {
if (typeof config.options.camelCaseColumns !== 'boolean') {
throw new TypeError('The "config.options.camelCaseColumns" property must be of type boolean.');
}
this.config.options.camelCaseColumns = config.options.camelCaseColumns;
}
if (config.options.cancelTimeout !== undefined) {
if (typeof config.options.cancelTimeout !== 'number') {
throw new TypeError('The "config.options.cancelTimeout" property must be of type number.');
}
this.config.options.cancelTimeout = config.options.cancelTimeout;
}
if (config.options.columnNameReplacer) {
if (typeof config.options.columnNameReplacer !== 'function') {
throw new TypeError('The "config.options.cancelTimeout" property must be of type function.');
}
this.config.options.columnNameReplacer = config.options.columnNameReplacer;
}
if (config.options.connectTimeout !== undefined) {
if (typeof config.options.connectTimeout !== 'number') {
throw new TypeError('The "config.options.connectTimeout" property must be of type number.');
}
this.config.options.connectTimeout = config.options.connectTimeout;
}
if (config.options.connectionIsolationLevel !== undefined) {
(0, _transaction.assertValidIsolationLevel)(config.options.connectionIsolationLevel, 'config.options.connectionIsolationLevel');
this.config.options.connectionIsolationLevel = config.options.connectionIsolationLevel;
}
if (config.options.connectTimeout !== undefined) {
if (typeof config.options.connectTimeout !== 'number') {
throw new TypeError('The "config.options.connectTimeout" property must be of type number.');
}
this.config.options.connectTimeout = config.options.connectTimeout;
}
if (config.options.cryptoCredentialsDetails !== undefined) {
if (typeof config.options.cryptoCredentialsDetails !== 'object' || config.options.cryptoCredentialsDetails === null) {
throw new TypeError('The "config.options.cryptoCredentialsDetails" property must be of type Object.');
}
this.config.options.cryptoCredentialsDetails = config.options.cryptoCredentialsDetails;
}
if (config.options.database !== undefined) {
if (typeof config.options.database !== 'string') {
throw new TypeError('The "config.options.database" property must be of type string.');
}
this.config.options.database = config.options.database;
}
if (config.options.datefirst !== undefined) {
if (typeof config.options.datefirst !== 'number' && config.options.datefirst !== null) {
throw new TypeError('The "config.options.datefirst" property must be of type number.');
}
if (config.options.datefirst !== null && (config.options.datefirst < 1 || config.options.datefirst > 7)) {
throw new RangeError('The "config.options.datefirst" property must be >= 1 and <= 7');
}
this.config.options.datefirst = config.options.datefirst;
}
if (config.options.dateFormat !== undefined) {
if (typeof config.options.dateFormat !== 'string' && config.options.dateFormat !== null) {
throw new TypeError('The "config.options.dateFormat" property must be of type string or null.');
}
this.config.options.dateFormat = config.options.dateFormat;
}
if (config.options.debug) {
if (config.options.debug.data !== undefined) {
if (typeof config.options.debug.data !== 'boolean') {
throw new TypeError('The "config.options.debug.data" property must be of type boolean.');
}
this.config.options.debug.data = config.options.debug.data;
}
if (config.options.debug.packet !== undefined) {
if (typeof config.options.debug.packet !== 'boolean') {
throw new TypeError('The "config.options.debug.packet" property must be of type boolean.');
}
this.config.options.debug.packet = config.options.debug.packet;
}
if (config.options.debug.payload !== undefined) {
if (typeof config.options.debug.payload !== 'boolean') {
throw new TypeError('The "config.options.debug.payload" property must be of type boolean.');
}
this.config.options.debug.payload = config.options.debug.payload;
}
if (config.options.debug.token !== undefined) {
if (typeof config.options.debug.token !== 'boolean') {
throw new TypeError('The "config.options.debug.token" property must be of type boolean.');
}
this.config.options.debug.token = config.options.debug.token;
}
}
if (config.options.enableAnsiNull !== undefined) {
if (typeof config.options.enableAnsiNull !== 'boolean' && config.options.enableAnsiNull !== null) {
throw new TypeError('The "config.options.enableAnsiNull" property must be of type boolean or null.');
}
this.config.options.enableAnsiNull = config.options.enableAnsiNull;
}
if (config.options.enableAnsiNullDefault !== undefined) {
if (typeof config.options.enableAnsiNullDefault !== 'boolean' && config.options.enableAnsiNullDefault !== null) {
throw new TypeError('The "config.options.enableAnsiNullDefault" property must be of type boolean or null.');
}
this.config.options.enableAnsiNullDefault = config.options.enableAnsiNullDefault;
}
if (config.options.enableAnsiPadding !== undefined) {
if (typeof config.options.enableAnsiPadding !== 'boolean' && config.options.enableAnsiPadding !== null) {
throw new TypeError('The "config.options.enableAnsiPadding" property must be of type boolean or null.');
}
this.config.options.enableAnsiPadding = config.options.enableAnsiPadding;
}
if (config.options.enableAnsiWarnings !== undefined) {
if (typeof config.options.enableAnsiWarnings !== 'boolean' && config.options.enableAnsiWarnings !== null) {
throw new TypeError('The "config.options.enableAnsiWarnings" property must be of type boolean or null.');
}
this.config.options.enableAnsiWarnings = config.options.enableAnsiWarnings;
}
if (config.options.enableArithAbort !== undefined) {
if (typeof config.options.enableArithAbort !== 'boolean' && config.options.enableArithAbort !== null) {
throw new TypeError('The "config.options.enableArithAbort" property must be of type boolean or null.');
}
this.config.options.enableArithAbort = config.options.enableArithAbort;
}
if (config.options.enableConcatNullYieldsNull !== undefined) {
if (typeof config.options.enableConcatNullYieldsNull !== 'boolean' && config.options.enableConcatNullYieldsNull !== null) {
throw new TypeError('The "config.options.enableConcatNullYieldsNull" property must be of type boolean or null.');
}
this.config.options.enableConcatNullYieldsNull = config.options.enableConcatNullYieldsNull;
}
if (config.options.enableCursorCloseOnCommit !== undefined) {
if (typeof config.options.enableCursorCloseOnCommit !== 'boolean' && config.options.enableCursorCloseOnCommit !== null) {
throw new TypeError('The "config.options.enableCursorCloseOnCommit" property must be of type boolean or null.');
}
this.config.options.enableCursorCloseOnCommit = config.options.enableCursorCloseOnCommit;
}
if (config.options.enableImplicitTransactions !== undefined) {
if (typeof config.options.enableImplicitTransactions !== 'boolean' && config.options.enableImplicitTransactions !== null) {
throw new TypeError('The "config.options.enableImplicitTransactions" property must be of type boolean or null.');
}
this.config.options.enableImplicitTransactions = config.options.enableImplicitTransactions;
}
if (config.options.enableNumericRoundabort !== undefined) {
if (typeof config.options.enableNumericRoundabort !== 'boolean' && config.options.enableNumericRoundabort !== null) {
throw new TypeError('The "config.options.enableNumericRoundabort" property must be of type boolean or null.');
}
this.config.options.enableNumericRoundabort = config.options.enableNumericRoundabort;
}
if (config.options.enableQuotedIdentifier !== undefined) {
if (typeof config.options.enableQuotedIdentifier !== 'boolean' && config.options.enableQuotedIdentifier !== null) {
throw new TypeError('The "config.options.enableQuotedIdentifier" property must be of type boolean or null.');
}
this.config.options.enableQuotedIdentifier = config.options.enableQuotedIdentifier;
}
if (config.options.encrypt !== undefined) {
if (typeof config.options.encrypt !== 'boolean') {
throw new TypeError('The "config.options.encrypt" property must be of type boolean.');
}
this.config.options.encrypt = config.options.encrypt;
}
if (config.options.fallbackToDefaultDb !== undefined) {
if (typeof config.options.fallbackToDefaultDb !== 'boolean') {
throw new TypeError('The "config.options.fallbackToDefaultDb" property must be of type boolean.');
}
this.config.options.fallbackToDefaultDb = config.options.fallbackToDefaultDb;
}
if (config.options.instanceName !== undefined) {
if (typeof config.options.instanceName !== 'string') {
throw new TypeError('The "config.options.instanceName" property must be of type string.');
}
this.config.options.instanceName = config.options.instanceName;
this.config.options.port = undefined;
}
if (config.options.isolationLevel !== undefined) {
(0, _transaction.assertValidIsolationLevel)(config.options.isolationLevel, 'config.options.isolationLevel');
this.config.options.isolationLevel = config.options.isolationLevel;
}
if (config.options.language !== undefined) {
if (typeof config.options.language !== 'string' && config.options.language !== null) {
throw new TypeError('The "config.options.language" property must be of type string or null.');
}
this.config.options.language = config.options.language;
}
if (config.options.localAddress !== undefined) {
if (typeof config.options.localAddress !== 'string') {
throw new TypeError('The "config.options.localAddress" property must be of type string.');
}
this.config.options.localAddress = config.options.localAddress;
}
if (config.options.multiSubnetFailover !== undefined) {
if (typeof config.options.multiSubnetFailover !== 'boolean') {
throw new TypeError('The "config.options.multiSubnetFailover" property must be of type boolean.');
}
this.config.options.multiSubnetFailover = config.options.multiSubnetFailover;
}
if (config.options.packetSize !== undefined) {
if (typeof config.options.packetSize !== 'number') {
throw new TypeError('The "config.options.packetSize" property must be of type number.');
}
this.config.options.packetSize = config.options.packetSize;
}
if (config.options.port !== undefined) {
if (typeof config.options.port !== 'number') {
throw new TypeError('The "config.options.port" property must be of type number.');
}
if (config.options.port <= 0 || config.options.port >= 65536) {
throw new RangeError('The "config.options.port" property must be > 0 and < 65536');
}
this.config.options.port = config.options.port;
this.config.options.instanceName = undefined;
}
if (config.options.readOnlyIntent !== undefined) {
if (typeof config.options.readOnlyIntent !== 'boolean') {
throw new TypeError('The "config.options.readOnlyIntent" property must be of type boolean.');
}
this.config.options.readOnlyIntent = config.options.readOnlyIntent;
}
if (config.options.requestTimeout !== undefined) {
if (typeof config.options.requestTimeout !== 'number') {
throw new TypeError('The "config.options.requestTimeout" property must be of type number.');
}
this.config.options.requestTimeout = config.options.requestTimeout;
}
if (config.options.maxRetriesOnTransientErrors !== undefined) {
if (typeof config.options.maxRetriesOnTransientErrors !== 'number') {
throw new TypeError('The "config.options.maxRetriesOnTransientErrors" property must be of type number.');
}
if (config.options.maxRetriesOnTransientErrors < 0) {
throw new TypeError('The "config.options.maxRetriesOnTransientErrors" property must be equal or greater than 0.');
}
this.config.options.maxRetriesOnTransientErrors = config.options.maxRetriesOnTransientErrors;
}
if (config.options.connectionRetryInterval !== undefined) {
if (typeof config.options.connectionRetryInterval !== 'number') {
throw new TypeError('The "config.options.connectionRetryInterval" property must be of type number.');
}
if (config.options.connectionRetryInterval <= 0) {
throw new TypeError('The "config.options.connectionRetryInterval" property must be greater than 0.');
}
this.config.options.connectionRetryInterval = config.options.connectionRetryInterval;
}
if (config.options.rowCollectionOnDone !== undefined) {
if (typeof config.options.rowCollectionOnDone !== 'boolean') {
throw new TypeError('The "config.options.rowCollectionOnDone" property must be of type boolean.');
}
this.config.options.rowCollectionOnDone = config.options.rowCollectionOnDone;
}
if (config.options.rowCollectionOnRequestCompletion !== undefined) {
if (typeof config.options.rowCollectionOnRequestCompletion !== 'boolean') {
throw new TypeError('The "config.options.rowCollectionOnRequestCompletion" property must be of type boolean.');
}
this.config.options.rowCollectionOnRequestCompletion = config.options.rowCollectionOnRequestCompletion;
}
if (config.options.tdsVersion !== undefined) {
if (typeof config.options.tdsVersion !== 'string') {
throw new TypeError('The "config.options.tdsVersion" property must be of type string.');
}
this.config.options.tdsVersion = config.options.tdsVersion;
}
if (config.options.textsize !== undefined) {
if (typeof config.options.textsize !== 'number' && config.options.textsize !== null) {
throw new TypeError('The "config.options.textsize" property must be of type number or null.');
}
if (config.options.textsize > 2147483647) {
throw new TypeError('The "config.options.textsize" can\'t be greater than 2147483647.');
} else if (config.options.textsize < -1) {
throw new TypeError('The "config.options.textsize" can\'t be smaller than -1.');
}
this.config.options.textsize = config.options.textsize | 0;
}
if (config.options.trustServerCertificate !== undefined) {
if (typeof config.options.trustServerCertificate !== 'boolean') {
throw new TypeError('The "config.options.trustServerCertificate" property must be of type boolean.');
}
this.config.options.trustServerCertificate = config.options.trustServerCertificate;
}
if (config.options.useColumnNames !== undefined) {
if (typeof config.options.useColumnNames !== 'boolean') {
throw new TypeError('The "config.options.useColumnNames" property must be of type boolean.');
}
this.config.options.useColumnNames = config.options.useColumnNames;
}
if (config.options.useUTC !== undefined) {
if (typeof config.options.useUTC !== 'boolean') {
throw new TypeError('The "config.options.useUTC" property must be of type boolean.');
}
this.config.options.useUTC = config.options.useUTC;
}
if (config.options.workstationId !== undefined) {
if (typeof config.options.workstationId !== 'string') {
throw new TypeError('The "config.options.workstationId" property must be of type string.');
}
this.config.options.workstationId = config.options.workstationId;
}
if (config.options.lowerCaseGuids !== undefined) {
if (typeof config.options.lowerCaseGuids !== 'boolean') {
throw new TypeError('The "config.options.lowerCaseGuids" property must be of type boolean.');
}
this.config.options.lowerCaseGuids = config.options.lowerCaseGuids;
}
}
this.secureContextOptions = this.config.options.cryptoCredentialsDetails;
if (this.secureContextOptions.secureOptions === undefined) {
// If the caller has not specified their own `secureOptions`,
// we set `SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS` here.
// Older SQL Server instances running on older Windows versions have
// trouble with the BEAST workaround in OpenSSL.
// As BEAST is a browser specific exploit, we can just disable this option here.
this.secureContextOptions = Object.create(this.secureContextOptions, {
secureOptions: {
value: _constants.default.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
}
});
}
this.debug = this.createDebug();
this.inTransaction = false;
this.transactionDescriptors = [Buffer.from([0, 0, 0, 0, 0, 0, 0, 0])]; // 'beginTransaction', 'commitTransaction' and 'rollbackTransaction'
// events are utilized to maintain inTransaction property state which in
// turn is used in managing transactions. These events are only fired for
// TDS version 7.2 and beyond. The properties below are used to emulate
// equivalent behavior for TDS versions before 7.2.
this.transactionDepth = 0;
this.isSqlBatch = false;
this.closed = false;
this.messageBuffer = Buffer.alloc(0);
this.curTransientRetryCount = 0;
this.transientErrorLookup = new _transientErrorLookup.TransientErrorLookup();
this.state = this.STATE.INITIALIZED;
this._cancelAfterRequestSent = () => {
this.messageIo.sendMessage(_packet.TYPE.ATTENTION);
this.createCancelTimer();
};
}
connect(connectListener) {
if (this.state !== this.STATE.INITIALIZED) {
throw new _errors.ConnectionError('`.connect` can not be called on a Connection in `' + this.state.name + '` state.');
}
if (connectListener) {
const onConnect = err => {
this.removeListener('error', onError);
connectListener(err);
};
const onError = err => {
this.removeListener('connect', onConnect);
connectListener(err);
};
this.once('connect', onConnect);
this.once('error', onError);
}
this.transitionTo(this.STATE.CONNECTING);
}
/**
* The server has reported that the charset has changed.
*/
on(event, listener) {
return super.on(event, listener);
}
/**
* @private
*/
emit(event, ...args) {
return super.emit(event, ...args);
}
/**
* Closes the connection to the database.
*
* The [[Event_end]] will be emitted once the connection has been closed.
*/
close() {
this.transitionTo(this.STATE.FINAL);
}
/**
* @private
*/
initialiseConnection() {
const signal = this.createConnectTimer();
if (this.config.options.port) {
return this.connectOnPort(this.config.options.port, this.config.options.multiSubnetFailover, signal);
} else {
return (0, _instanceLookup.instanceLookup)({
server: this.config.server,
instanceName: this.config.options.instanceName,
timeout: this.config.options.connectTimeout,
signal: signal
}).then(port => {
process.nextTick(() => {
this.connectOnPort(port, this.config.options.multiSubnetFailover, signal);
});
}, err => {
if (err.name === 'AbortError') {
// Ignore the AbortError for now, this is still handled by the connectTimer firing
return;
}
process.nextTick(() => {
this.emit('connect', new _errors.ConnectionError(err.message, 'EINSTLOOKUP'));
});
});
}
}
/**
* @private
*/
cleanupConnection(cleanupType) {
if (!this.closed) {
this.clearConnectTimer();
this.clearRequestTimer();
this.clearRetryTimer();
this.closeConnection();
if (cleanupType === CLEANUP_TYPE.REDIRECT) {
this.emit('rerouting');
} else if (cleanupType !== CLEANUP_TYPE.RETRY) {
process.nextTick(() => {
this.emit('end');
});
}
const request = this.request;
if (request) {
const err = new _errors.RequestError('Connection closed before request completed.', 'ECLOSE');
request.callback(err);
this.request = undefined;
}
this.closed = true;
this.loginError = undefined;
}
}
/**
* @private
*/
createDebug() {
const debug = new _debug.default(this.config.options.debug);
debug.on('debug', message => {
this.emit('debug', message);
});
return debug;
}
/**
* @private
*/
createTokenStreamParser(message, handler) {
return new _tokenStreamParser.Parser(message, this.debug, handler, this.config.options);
}
connectOnPort(port, multiSubnetFailover, signal) {
const connectOpts = {
host: this.routingData ? this.routingData.server : this.config.server,
port: this.routingData ? this.routingData.port : port,
localAddress: this.config.options.localAddress
};
const connect = multiSubnetFailover ? _connector.connectInParallel : _connector.connectInSequence;
connect(connectOpts, _dns.default.lookup, signal).then(socket => {
process.nextTick(() => {
socket.on('error', error => {
this.socketError(error);
});
socket.on('close', () => {
this.socketClose();
});
socket.on('end', () => {
this.socketEnd();
});
socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY);
this.messageIo = new _messageIo.default(socket, this.config.options.packetSize, this.debug);
this.messageIo.on('secure', cleartext => {
this.emit('secure', cleartext);
});
this.socket = socket;
this.closed = false;
this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port);
this.sendPreLogin();
this.transitionTo(this.STATE.SENT_PRELOGIN);
});
}, err => {
if (err.name === 'AbortError') {
return;
}
process.nextTick(() => {
this.socketError(err);
});
});
}
/**
* @private
*/
closeConnection() {
if (this.socket) {
this.socket.destroy();
}
}
/**
* @private
*/
createConnectTimer() {
const controller = new _nodeAbortController.AbortController();
this.connectTimer = setTimeout(() => {
controller.abort();
this.connectTimeout();
}, this.config.options.connectTimeout);
return controller.signal;
}
/**
* @private
*/
createCancelTimer() {
this.clearCancelTimer();
const timeout = this.config.options.cancelTimeout;
if (timeout > 0) {
this.cancelTimer = setTimeout(() => {
this.cancelTimeout();
}, timeout);
}
}
/**
* @private
*/
createRequestTimer() {
this.clearRequestTimer(); // release old timer, just to be safe
const request = this.request;
const timeout = request.timeout !== undefined ? request.timeout : this.config.options.requestTimeout;
if (timeout) {
this.requestTimer = setTimeout(() => {
this.requestTimeout();
}, timeout);
}
}
/**
* @private
*/
createRetryTimer() {
this.clearRetryTimer();
this.retryTimer = setTimeout(() => {
this.retryTimeout();
}, this.config.options.connectionRetryInterval);
}
/**
* @private
*/
connectTimeout() {
const message = `Failed to connect to ${this.config.server}${this.config.options.port ? `:${this.config.options.port}` : `\\${this.config.options.instanceName}`} in ${this.config.options.connectTimeout}ms`;
this.debug.log(message);
this.emit('connect', new _errors.ConnectionError(message, 'ETIMEOUT'));
this.connectTimer = undefined;
this.dispatchEvent('connectTimeout');
}
/**
* @private
*/
cancelTimeout() {
const message = `Failed to cancel request in ${this.config.options.cancelTimeout}ms`;
this.debug.log(message);
this.dispatchEvent('socketError', new _errors.ConnectionError(message, 'ETIMEOUT'));
}
/**
* @private
*/
requestTimeout() {
this.requestTimer = undefined;
const request = this.request;
request.cancel();
const timeout = request.timeout !== undefined ? request.timeout : this.config.options.requestTimeout;
const message = 'Timeout: Request failed to complete in ' + timeout + 'ms';
request.error = new _errors.RequestError(message, 'ETIMEOUT');
}
/**
* @private
*/
retryTimeout() {
this.retryTimer = undefined;
this.emit('retry');
this.transitionTo(this.STATE.CONNECTING);
}
/**
* @private
*/
clearConnectTimer() {
if (this.connectTimer) {
clearTimeout(this.connectTimer);
this.connectTimer = undefined;
}
}
/**
* @private
*/
clearCancelTimer() {
if (this.cancelTimer) {
clearTimeout(this.cancelTimer);
this.cancelTimer = undefined;
}
}
/**
* @private
*/
clearRequestTimer() {
if (this.requestTimer) {
clearTimeout(this.requestTimer);
this.requestTimer = undefined;
}
}
/**
* @private
*/
clearRetryTimer() {
if (this.retryTimer) {
clearTimeout(this.retryTimer);
this.retryTimer = undefined;
}
}
/**
* @private
*/
transitionTo(newState) {
if (this.state === newState) {
this.debug.log('State is already ' + newState.name);
return;
}
if (this.state && this.state.exit) {
this.state.exit.call(this, newState);
}
this.debug.log('State change: ' + (this.state ? this.state.name : 'undefined') + ' -> ' + newState.name);
this.state = newState;
if (this.state.enter) {
this.state.enter.apply(this);
}
}
/**
* @private
*/
getEventHandler(eventName) {
const handler = this.state.events[eventName];
if (!handler) {
throw new Error(`No event '${eventName}' in state '${this.state.name}'`);
}
return handler;
}
/**
* @private
*/
dispatchEvent(eventName, ...args) {
const handler = this.state.events[eventName];
if (handler) {
handler.apply(this, args);
} else {
this.emit('error', new Error(`No event '${eventName}' in state '${this.state.name}'`));
this.close();
}
}
/**
* @private
*/
socketError(error) {
if (this.state === this.STATE.CONNECTING || this.state === this.STATE.SENT_TLSSSLNEGOTIATION) {
const message = `Failed to connect to ${this.config.server}:${this.config.options.port} - ${error.message}`;
this.debug.log(message);
this.emit('connect', new _errors.ConnectionError(message, 'ESOCKET'));
} else {
const message = `Connection lost - ${error.message}`;
this.debug.log(message);
this.emit('error', new _errors.ConnectionError(message, 'ESOCKET'));
}
this.dispatchEvent('socketError', error);
}
/**
* @private
*/
socketEnd() {
this.debug.log('socket ended');
if (this.state !== this.STATE.FINAL) {
const error = new Error('socket hang up');
error.code = 'ECONNRESET';
this.socketError(error);
}
}
/**
* @private
*/
socketClose() {
this.debug.log('connection to ' + this.config.server + ':' + this.config.options.port + ' closed');
if (this.state === this.STATE.REROUTING) {
this.debug.log('Rerouting to ' + this.routingData.server + ':' + this.routingData.port);
this.dispatchEvent('reconnect');
} else if (this.state === this.STATE.TRANSIENT_FAILURE_RETRY) {
const server = this.routingData ? this.routingData.server : this.config.server;
const port = this.routingData ? this.routingData.port : this.config.options.port;
this.debug.log('Retry after transient failure connecting to ' + server + ':' + port);
this.dispatchEvent('retry');
} else {
this.transitionTo(this.STATE.FINAL);
}
}
/**
* @private
*/
sendPreLogin() {
const [, major, minor, build] = /^(\d+)\.(\d+)\.(\d+)/.exec(_package.version) ?? ['0.0.0', '0', '0', '0'];
const payload = new _preloginPayload.default({
encrypt: this.config.options.encrypt,
version: {
major: Number(major),
minor: Number(minor),
build: Number(build),
subbuild: 0
}
});
this.messageIo.sendMessage(_packet.TYPE.PRELOGIN, payload.data);
this.debug.payload(function () {
return payload.toString(' ');
});
}
/**
* @private
*/
sendLogin7Packet() {
const payload = new _login7Payload.default({
tdsVersion: _tdsVersions.versions[this.config.options.tdsVersion],
packetSize: this.config.options.packetSize,
clientProgVer: 0,
clientPid: process.pid,
connectionId: 0,
clientTimeZone: new Date().getTimezoneOffset(),
clientLcid: 0x00000409
});
const {
authentication
} = this.config;
switch (authentication.type) {
case 'azure-active-directory-password':
payload.fedAuth = {
type: 'ADAL',
echo: this.fedAuthRequired,
workflow: 'default'
};
break;
case 'azure-active-directory-access-token':
payload.fedAuth = {
type: 'SECURITYTOKEN',
echo: this.fedAuthRequired,
fedAuthToken: authentication.options.token
};
break;
case 'azure-active-directory-msi-vm':
case 'azure-active-directory-default':
case 'azure-active-directory-msi-app-service':
case 'azure-active-directory-service-principal-secret':
payload.fedAuth = {
type: 'ADAL',
echo: this.fedAuthRequired,
workflow: 'integrated'
};
break;
case 'ntlm':
payload.sspi = (0, _ntlm.createNTLMRequest)({
domain: authentication.options.domain
});
break;
default:
payload.userName = authentication.options.userName;
payload.password = authentication.options.password;
}
payload.hostname = this.config.options.workstationId || _os.default.hostname();
payload.serverName = this.routingData ? this.routingData.server : this.config.server;
payload.appName = this.config.options.appName || 'Tedious';
payload.libraryName = _library.name;
payload.language = this.config.options.language;
payload.database = this.config.options.database;
payload.clientId = Buffer.from([1, 2, 3, 4, 5, 6]);
payload.readOnlyIntent = this.config.options.readOnlyIntent;
payload.initDbFatal = !this.config.options.fallbackToDefaultDb;
this.routingData = undefined;
this.messageIo.sendMessage(_packet.TYPE.LOGIN7, payload.toBuffer());
this.debug.payload(function () {
return payload.toString(' ');
});
}
/**
* @private
*/
sendFedAuthTokenMessage(token) {
const accessTokenLen = Buffer.byteLength(token, 'ucs2');
const data = Buffer.alloc(8 + accessTokenLen);
let offset = 0;
offset = data.writeUInt32LE(accessTokenLen + 4, offset);
offset = data.writeUInt32LE(accessTokenLen, offset);
data.write(token, offset, 'ucs2');
this.messageIo.sendMessage(_packet.TYPE.FEDAUTH_TOKEN, data); // sent the fedAuth token message, the rest is similar to standard login 7
this.transitionTo(this.STATE.SENT_LOGIN7_WITH_STANDARD_LOGIN);
}
/**
* @private
*/
sendInitialSql() {
const payload = new _sqlbatchPayload.default(this.getInitialSql(), this.currentTransactionDescriptor(), this.config.options);
const message = new _message.default({
type: _packet.TYPE.SQL_BATCH
});
this.messageIo.outgoingMessageStream.write(message);
_stream.Readable.from(payload).pipe(message);
}
/**
* @private
*/
getInitialSql() {
const options = [];
if (this.config.options.enableAnsiNull === true) {
options.push('set ansi_nulls on');
} else if (this.config.options.enableAnsiNull === false) {
options.push('set ansi_nulls off');
}
if (this.config.options.enableAnsiNullDefault === true) {
options.push('set ansi_null_dflt_on on');
} else if (this.config.options.enableAnsiNullDefault === false) {
options.push('set ansi_null_dflt_on off');
}
if (this.config.options.enableAnsiPadding === true) {
options.push('set ansi_padding on');
} else if (this.config.options.enableAnsiPadding === false) {
options.push('set ansi_padding off');
}
if (this.config.options.enableAnsiWarnings === true) {
options.push('set ansi_warnings on');
} else if (this.config.options.enableAnsiWarnings === false) {
options.push('set ansi_warnings off');
}
if (this.config.options.enableArithAbort === true) {
options.push('set arithabort on');
} else if (this.config.options.enableArithAbort === false)