"use strict";
|
|
const deprecate = require('depd')('tedious');
|
|
const crypto = require('crypto');
|
|
const os = require('os'); // $FlowFixMe
|
|
|
const constants = require('constants');
|
|
const _require = require('tls'),
|
createSecureContext = _require.createSecureContext;
|
|
const _require2 = require('adal-node'),
|
AuthenticationContext = _require2.AuthenticationContext;
|
|
const BulkLoad = require('./bulk-load');
|
|
const Debug = require('./debug');
|
|
const EventEmitter = require('events').EventEmitter;
|
|
const InstanceLookup = require('./instance-lookup').InstanceLookup;
|
|
const TransientErrorLookup = require('./transient-error-lookup.js').TransientErrorLookup;
|
|
const TYPE = require('./packet').TYPE;
|
|
const PreloginPayload = require('./prelogin-payload');
|
|
const Login7Payload = require('./login7-payload');
|
|
const NTLMResponsePayload = require('./ntlm-payload');
|
|
const Request = require('./request');
|
|
const RpcRequestPayload = require('./rpcrequest-payload');
|
|
const SqlBatchPayload = require('./sqlbatch-payload');
|
|
const MessageIO = require('./message-io');
|
|
const TokenStreamParser = require('./token/token-stream-parser').Parser;
|
|
const Transaction = require('./transaction').Transaction;
|
|
const ISOLATION_LEVEL = require('./transaction').ISOLATION_LEVEL;
|
|
const ConnectionError = require('./errors').ConnectionError;
|
|
const RequestError = require('./errors').RequestError;
|
|
const Connector = require('./connector').Connector;
|
|
const libraryName = require('./library').name;
|
|
const versions = require('./tds-versions').versions;
|
|
const _require3 = require('./ntlm'),
|
createNTLMRequest = _require3.createNTLMRequest; // A rather basic state machine for managing a connection.
|
// Implements something approximating s3.2.1.
|
|
|
const KEEP_ALIVE_INITIAL_DELAY = 30 * 1000;
|
const DEFAULT_CONNECT_TIMEOUT = 15 * 1000;
|
const DEFAULT_CLIENT_REQUEST_TIMEOUT = 15 * 1000;
|
const DEFAULT_CANCEL_TIMEOUT = 5 * 1000;
|
const DEFAULT_CONNECT_RETRY_INTERVAL = 500;
|
const DEFAULT_PACKET_SIZE = 4 * 1024;
|
const DEFAULT_TEXTSIZE = '2147483647';
|
const DEFAULT_DATEFIRST = 7;
|
const DEFAULT_PORT = 1433;
|
const DEFAULT_TDS_VERSION = '7_4';
|
const DEFAULT_LANGUAGE = 'us_english';
|
const DEFAULT_DATEFORMAT = 'mdy';
|
|
class Connection extends EventEmitter {
|
constructor(config) {
|
super();
|
|
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;
|
this.fedAuthInfoToken = undefined;
|
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.');
|
}
|
|
if (typeof config.authentication.type !== 'string') {
|
throw new TypeError('The "config.authentication.type" property must be of type string.');
|
}
|
|
if (config.authentication.type !== 'default' && config.authentication.type !== 'ntlm' && config.authentication.type !== 'azure-active-directory-password') {
|
throw new TypeError('The "config.authentication.type" property must one of "default", "ntlm" or "azure-active-directory-password".');
|
}
|
|
if (config.authentication.options !== undefined) {
|
if (typeof config.authentication.options !== 'object' || config.authentication.options === null) {
|
throw new TypeError('The "config.authentication.options" property must be of type object.');
|
}
|
|
if (config.authentication.type === 'ntlm') {
|
if (typeof config.authentication.options.domain !== 'string') {
|
throw new TypeError('The "config.authentication.options.domain" property must be of type string.');
|
}
|
}
|
|
if (config.authentication.options.userName !== undefined && typeof config.authentication.options.userName !== 'string') {
|
throw new TypeError('The "config.authentication.options.userName" property must be of type string.');
|
}
|
|
if (config.authentication.options.password !== undefined && typeof config.authentication.options.password !== 'string') {
|
throw new TypeError('The "config.authentication.options.password" property must be of type string.');
|
}
|
}
|
|
authentication = {
|
type: config.authentication.type,
|
options: config.authentication.type === 'ntlm' ? {
|
userName: config.authentication.options.userName,
|
password: config.authentication.options.password,
|
domain: config.authentication.options.domain && config.authentication.options.domain.toUpperCase()
|
} : {
|
userName: config.authentication.options.userName,
|
password: config.authentication.options.password
|
}
|
};
|
} else {
|
if (config.domain !== undefined) {
|
if (typeof config.domain !== 'string') {
|
throw new TypeError('The "config.domain" property must be of type string.');
|
}
|
|
deprecate('The "config.domain" property is deprecated and future tedious versions will no longer support it. Please switch to using the new "config.authentication" property instead.');
|
}
|
|
if (config.userName !== undefined) {
|
if (typeof config.userName !== 'string') {
|
throw new TypeError('The "config.userName" property must be of type string.');
|
}
|
|
deprecate('The "config.userName" property is deprecated and future tedious versions will no longer support it. Please switch to using the new "config.authentication" property instead.');
|
}
|
|
if (config.password !== undefined) {
|
if (typeof config.password !== 'string') {
|
throw new TypeError('The "config.password" property must be of type string.');
|
}
|
|
deprecate('The "config.password" property is deprecated and future tedious versions will no longer support it. Please switch to using the new "config.authentication" property instead.');
|
}
|
|
authentication = {
|
type: config.domain ? 'ntlm' : 'default',
|
options: {
|
userName: config.userName,
|
password: config.password,
|
domain: config.domain && config.domain.toUpperCase()
|
}
|
};
|
}
|
|
this.config = {
|
server: config.server,
|
authentication: authentication,
|
options: {
|
abortTransactionOnError: false,
|
appName: undefined,
|
camelCaseColumns: false,
|
cancelTimeout: DEFAULT_CANCEL_TIMEOUT,
|
columnNameReplacer: undefined,
|
connectionRetryInterval: DEFAULT_CONNECT_RETRY_INTERVAL,
|
connectTimeout: DEFAULT_CONNECT_TIMEOUT,
|
connectionIsolationLevel: 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: false,
|
enableConcatNullYieldsNull: true,
|
enableCursorCloseOnCommit: null,
|
enableImplicitTransactions: false,
|
enableNumericRoundabort: false,
|
enableQuotedIdentifier: true,
|
encrypt: false,
|
fallbackToDefaultDb: false,
|
instanceName: undefined,
|
isolationLevel: 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,
|
tdsVersion: DEFAULT_TDS_VERSION,
|
textsize: DEFAULT_TEXTSIZE,
|
trustServerCertificate: true,
|
useColumnNames: false,
|
useUTC: true
|
}
|
};
|
|
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) {
|
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;
|
} else {
|
deprecate('The default value for `options.encrypt` will change from `false` to `true`. Please pass `false` explicitly if you want to retain current behaviour.');
|
this.config.options.encrypt = false;
|
}
|
|
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) {
|
if (typeof config.options.isolationLevel !== 'number') {
|
throw new TypeError('The "config.options.language" property must be of type numer.');
|
}
|
|
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.');
|
}
|
|
this.config.options.textsize = config.options.textsize;
|
}
|
|
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;
|
}
|
}
|
|
let credentialsDetails = this.config.options.cryptoCredentialsDetails;
|
|
if (credentialsDetails.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.
|
credentialsDetails = Object.create(credentialsDetails, {
|
secureOptions: {
|
value: constants.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
|
}
|
});
|
}
|
|
this.secureContext = createSecureContext(credentialsDetails);
|
this.createDebug();
|
this.createTokenStreamParser();
|
this.inTransaction = false;
|
this.transactionDescriptors = [Buffer.from([0, 0, 0, 0, 0, 0, 0, 0])];
|
this.transitionTo(this.STATE.CONNECTING);
|
|
if (this.config.options.tdsVersion < '7_2') {
|
// '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.curTransientRetryCount = 0;
|
this.transientErrorLookup = new TransientErrorLookup();
|
this.cleanupTypeEnum = {
|
NORMAL: 0,
|
REDIRECT: 1,
|
RETRY: 2
|
};
|
}
|
|
close() {
|
this.transitionTo(this.STATE.FINAL);
|
}
|
|
initialiseConnection() {
|
this.connect();
|
this.createConnectTimer();
|
}
|
|
cleanupConnection(cleanupTypeEnum) {
|
if (!this.closed) {
|
this.clearConnectTimer();
|
this.clearRequestTimer();
|
this.clearRetryTimer();
|
this.closeConnection();
|
|
if (cleanupTypeEnum === this.cleanupTypeEnum.REDIRECT) {
|
this.emit('rerouting');
|
} else if (cleanupTypeEnum !== this.cleanupTypeEnum.RETRY) {
|
this.emit('end');
|
}
|
|
if (this.request) {
|
const err = RequestError('Connection closed before request completed.', 'ECLOSE');
|
this.request.callback(err);
|
this.request = undefined;
|
}
|
|
this.closed = true;
|
this.loggedIn = false;
|
this.loginError = null;
|
}
|
}
|
|
createDebug() {
|
this.debug = new Debug(this.config.options.debug);
|
this.debug.on('debug', message => {
|
this.emit('debug', message);
|
});
|
}
|
|
createTokenStreamParser() {
|
this.tokenStreamParser = new TokenStreamParser(this.debug, undefined, this.config.options);
|
this.tokenStreamParser.on('infoMessage', token => {
|
this.emit('infoMessage', token);
|
});
|
this.tokenStreamParser.on('sspichallenge', token => {
|
if (token.ntlmpacket) {
|
this.ntlmpacket = token.ntlmpacket;
|
this.ntlmpacketBuffer = token.ntlmpacketBuffer;
|
}
|
|
this.emit('sspichallenge', token);
|
});
|
this.tokenStreamParser.on('errorMessage', token => {
|
this.emit('errorMessage', token);
|
|
if (this.loggedIn) {
|
if (this.request) {
|
this.request.error = RequestError(token.message, 'EREQUEST');
|
this.request.error.number = token.number;
|
this.request.error.state = token.state;
|
this.request.error['class'] = token['class'];
|
this.request.error.serverName = token.serverName;
|
this.request.error.procName = token.procName;
|
this.request.error.lineNumber = token.lineNumber;
|
}
|
} else {
|
const isLoginErrorTransient = this.transientErrorLookup.isTransientError(token.number);
|
|
if (isLoginErrorTransient && this.curTransientRetryCount !== this.config.options.maxRetriesOnTransientErrors) {
|
this.debug.log('Initiating retry on transient error = ', token.number);
|
this.transitionTo(this.STATE.TRANSIENT_FAILURE_RETRY);
|
} else {
|
this.loginError = ConnectionError(token.message, 'ELOGIN');
|
}
|
}
|
});
|
this.tokenStreamParser.on('databaseChange', token => {
|
this.emit('databaseChange', token.newValue);
|
});
|
this.tokenStreamParser.on('languageChange', token => {
|
this.emit('languageChange', token.newValue);
|
});
|
this.tokenStreamParser.on('charsetChange', token => {
|
this.emit('charsetChange', token.newValue);
|
});
|
this.tokenStreamParser.on('fedAuthInfo', token => {
|
this.dispatchEvent('fedAuthInfo', token);
|
});
|
this.tokenStreamParser.on('featureExtAck', token => {
|
this.dispatchEvent('featureExtAck', token);
|
});
|
this.tokenStreamParser.on('loginack', token => {
|
if (!token.tdsVersion) {
|
// unsupported TDS version
|
this.loginError = ConnectionError('Server responded with unknown TDS version.', 'ETDS');
|
this.loggedIn = false;
|
return;
|
}
|
|
if (!token['interface']) {
|
// unsupported interface
|
this.loginError = ConnectionError('Server responded with unsupported interface.', 'EINTERFACENOTSUPP');
|
this.loggedIn = false;
|
return;
|
} // use negotiated version
|
|
|
this.config.options.tdsVersion = token.tdsVersion;
|
this.loggedIn = true;
|
});
|
this.tokenStreamParser.on('routingChange', token => {
|
this.routingData = token.newValue;
|
this.dispatchEvent('routingChange');
|
});
|
this.tokenStreamParser.on('packetSizeChange', token => {
|
this.messageIo.packetSize(token.newValue);
|
}); // A new top-level transaction was started. This is not fired
|
// for nested transactions.
|
|
this.tokenStreamParser.on('beginTransaction', token => {
|
this.transactionDescriptors.push(token.newValue);
|
this.inTransaction = true;
|
}); // A top-level transaction was committed. This is not fired
|
// for nested transactions.
|
|
this.tokenStreamParser.on('commitTransaction', () => {
|
this.transactionDescriptors.length = 1;
|
this.inTransaction = false;
|
}); // A top-level transaction was rolled back. This is not fired
|
// for nested transactions. This is also fired if a batch
|
// aborting error happened that caused a rollback.
|
|
this.tokenStreamParser.on('rollbackTransaction', () => {
|
this.transactionDescriptors.length = 1; // An outermost transaction was rolled back. Reset the transaction counter
|
|
this.inTransaction = false;
|
this.emit('rollbackTransaction');
|
});
|
this.tokenStreamParser.on('columnMetadata', token => {
|
if (this.request) {
|
let columns;
|
|
if (this.config.options.useColumnNames) {
|
columns = {};
|
|
for (let j = 0, len = token.columns.length; j < len; j++) {
|
const col = token.columns[j];
|
|
if (columns[col.colName] == null) {
|
columns[col.colName] = col;
|
}
|
}
|
} else {
|
columns = token.columns;
|
}
|
|
this.request.emit('columnMetadata', columns);
|
} else {
|
this.emit('error', new Error("Received 'columnMetadata' when no sqlRequest is in progress"));
|
this.close();
|
}
|
});
|
this.tokenStreamParser.on('order', token => {
|
if (this.request) {
|
this.request.emit('order', token.orderColumns);
|
} else {
|
this.emit('error', new Error("Received 'order' when no sqlRequest is in progress"));
|
this.close();
|
}
|
});
|
this.tokenStreamParser.on('row', token => {
|
if (this.request) {
|
if (this.config.options.rowCollectionOnRequestCompletion) {
|
this.request.rows.push(token.columns);
|
}
|
|
if (this.config.options.rowCollectionOnDone) {
|
this.request.rst.push(token.columns);
|
}
|
|
if (!(this.state === this.STATE.SENT_ATTENTION && this.request.paused)) {
|
this.request.emit('row', token.columns);
|
}
|
} else {
|
this.emit('error', new Error("Received 'row' when no sqlRequest is in progress"));
|
this.close();
|
}
|
});
|
this.tokenStreamParser.on('returnStatus', token => {
|
if (this.request) {
|
// Keep value for passing in 'doneProc' event.
|
this.procReturnStatusValue = token.value;
|
}
|
});
|
this.tokenStreamParser.on('returnValue', token => {
|
if (this.request) {
|
this.request.emit('returnValue', token.paramName, token.value, token.metadata);
|
}
|
});
|
this.tokenStreamParser.on('doneProc', token => {
|
if (this.request) {
|
this.request.emit('doneProc', token.rowCount, token.more, this.procReturnStatusValue, this.request.rst);
|
this.procReturnStatusValue = undefined;
|
|
if (token.rowCount !== undefined) {
|
this.request.rowCount += token.rowCount;
|
}
|
|
if (this.config.options.rowCollectionOnDone) {
|
this.request.rst = [];
|
}
|
}
|
});
|
this.tokenStreamParser.on('doneInProc', token => {
|
if (this.request) {
|
this.request.emit('doneInProc', token.rowCount, token.more, this.request.rst);
|
|
if (token.rowCount !== undefined) {
|
this.request.rowCount += token.rowCount;
|
}
|
|
if (this.config.options.rowCollectionOnDone) {
|
this.request.rst = [];
|
}
|
}
|
});
|
this.tokenStreamParser.on('done', token => {
|
if (this.request) {
|
if (token.attention) {
|
this.dispatchEvent('attention');
|
}
|
|
if (token.sqlError && !this.request.error) {
|
// check if the DONE_ERROR flags was set, but an ERROR token was not sent.
|
this.request.error = RequestError('An unknown error has occurred.', 'UNKNOWN');
|
}
|
|
this.request.emit('done', token.rowCount, token.more, this.request.rst);
|
|
if (token.rowCount !== undefined) {
|
this.request.rowCount += token.rowCount;
|
}
|
|
if (this.config.options.rowCollectionOnDone) {
|
this.request.rst = [];
|
}
|
}
|
});
|
this.tokenStreamParser.on('endOfMessage', () => {
|
// EOM pseudo token received
|
if (this.state === this.STATE.SENT_CLIENT_REQUEST) {
|
this.dispatchEvent('endOfMessageMarkerReceived');
|
}
|
});
|
this.tokenStreamParser.on('resetConnection', () => {
|
this.emit('resetConnection');
|
});
|
this.tokenStreamParser.on('tokenStreamError', error => {
|
this.emit('error', error);
|
this.close();
|
});
|
this.tokenStreamParser.on('drain', () => {
|
// Bridge the release of backpressure from the token stream parser
|
// transform to the packet stream transform.
|
this.messageIo.resume();
|
});
|
}
|
|
connect() {
|
if (this.config.options.port) {
|
return this.connectOnPort(this.config.options.port, this.config.options.multiSubnetFailover);
|
} else {
|
return new InstanceLookup().instanceLookup({
|
server: this.config.server,
|
instanceName: this.config.options.instanceName,
|
timeout: this.config.options.connectTimeout
|
}, (message, port) => {
|
if (this.state === this.STATE.FINAL) {
|
return;
|
}
|
|
if (message) {
|
this.emit('connect', ConnectionError(message, 'EINSTLOOKUP'));
|
} else {
|
this.connectOnPort(port, this.config.options.multiSubnetFailover);
|
}
|
});
|
}
|
}
|
|
connectOnPort(port, multiSubnetFailover) {
|
const connectOpts = {
|
host: this.routingData ? this.routingData.server : this.config.server,
|
port: this.routingData ? this.routingData.port : port,
|
localAddress: this.config.options.localAddress
|
};
|
new Connector(connectOpts, multiSubnetFailover).execute((err, socket) => {
|
if (err) {
|
return this.socketError(err);
|
}
|
|
if (this.state === this.STATE.FINAL) {
|
socket.destroy();
|
return;
|
}
|
|
this.socket = socket;
|
this.socket.on('error', error => {
|
this.socketError(error);
|
});
|
this.socket.on('close', () => {
|
this.socketClose();
|
});
|
this.socket.on('end', () => {
|
this.socketEnd();
|
});
|
this.messageIo = new MessageIO(this.socket, this.config.options.packetSize, this.debug);
|
this.messageIo.on('data', data => {
|
this.dispatchEvent('data', data);
|
});
|
this.messageIo.on('message', () => {
|
this.dispatchEvent('message');
|
});
|
this.messageIo.on('secure', cleartext => {
|
this.emit('secure', cleartext);
|
});
|
this.socketConnect();
|
});
|
}
|
|
closeConnection() {
|
if (this.socket) {
|
this.socket.destroy();
|
}
|
}
|
|
createConnectTimer() {
|
this.connectTimer = setTimeout(() => {
|
this.connectTimeout();
|
}, this.config.options.connectTimeout);
|
}
|
|
createRequestTimer() {
|
this.clearRequestTimer(); // release old timer, just to be safe
|
|
const timeout = this.request.timeout !== undefined ? this.request.timeout : this.config.options.requestTimeout;
|
|
if (timeout) {
|
this.requestTimer = setTimeout(() => {
|
this.requestTimeout();
|
}, timeout);
|
}
|
}
|
|
createRetryTimer() {
|
this.clearRetryTimer();
|
this.retryTimer = setTimeout(() => {
|
this.retryTimeout();
|
}, this.config.options.connectionRetryInterval);
|
}
|
|
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', ConnectionError(message, 'ETIMEOUT'));
|
this.connectTimer = undefined;
|
this.dispatchEvent('connectTimeout');
|
}
|
|
requestTimeout() {
|
this.requestTimer = undefined;
|
this.messageIo.sendMessage(TYPE.ATTENTION);
|
this.transitionTo(this.STATE.SENT_ATTENTION);
|
}
|
|
retryTimeout() {
|
this.retryTimer = undefined;
|
this.emit('retry');
|
this.transitionTo(this.STATE.CONNECTING);
|
}
|
|
clearConnectTimer() {
|
if (this.connectTimer) {
|
clearTimeout(this.connectTimer);
|
}
|
}
|
|
clearRequestTimer() {
|
if (this.requestTimer) {
|
clearTimeout(this.requestTimer);
|
this.requestTimer = undefined;
|
}
|
}
|
|
clearRetryTimer() {
|
if (this.retryTimer) {
|
clearTimeout(this.retryTimer);
|
this.retryTimer = undefined;
|
}
|
}
|
|
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);
|
}
|
}
|
|
dispatchEvent(eventName, ...args) {
|
if (this.state.events[eventName]) {
|
this.state.events[eventName].apply(this, args);
|
} else {
|
this.emit('error', new Error(`No event '${eventName}' in state '${this.state.name}'`));
|
this.close();
|
}
|
}
|
|
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', ConnectionError(message, 'ESOCKET'));
|
} else {
|
const message = `Connection lost - ${error.message}`;
|
this.debug.log(message);
|
this.emit('error', ConnectionError(message, 'ESOCKET'));
|
}
|
|
this.dispatchEvent('socketError', error);
|
}
|
|
socketConnect() {
|
this.socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY);
|
this.closed = false;
|
this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port);
|
this.dispatchEvent('socketConnect');
|
}
|
|
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);
|
}
|
}
|
|
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.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);
|
}
|
}
|
|
sendPreLogin() {
|
const payload = new PreloginPayload({
|
encrypt: this.config.options.encrypt
|
});
|
this.messageIo.sendMessage(TYPE.PRELOGIN, payload.data);
|
this.debug.payload(function () {
|
return payload.toString(' ');
|
});
|
}
|
|
emptyMessageBuffer() {
|
this.messageBuffer = Buffer.alloc(0);
|
}
|
|
addToMessageBuffer(data) {
|
this.messageBuffer = Buffer.concat([this.messageBuffer, data]);
|
}
|
|
processPreLoginResponse() {
|
const preloginPayload = new PreloginPayload(this.messageBuffer);
|
this.debug.payload(function () {
|
return preloginPayload.toString(' ');
|
});
|
|
if (preloginPayload.fedAuthRequired === 1) {
|
this.fedAuthRequired = true;
|
}
|
|
if (preloginPayload.encryptionString === 'ON' || preloginPayload.encryptionString === 'REQ') {
|
if (!this.config.options.encrypt) {
|
this.emit('connect', ConnectionError("Server requires encryption, set 'encrypt' config option to true.", 'EENCRYPT'));
|
return this.close();
|
}
|
|
this.dispatchEvent('tls');
|
} else {
|
this.dispatchEvent('noTls');
|
}
|
}
|
|
sendLogin7Packet(cb) {
|
const payload = new Login7Payload({
|
tdsVersion: 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.authentication;
|
|
switch (authentication.type) {
|
case 'azure-active-directory-password':
|
payload.fedAuth = {
|
type: 'ADAL',
|
echo: this.fedAuthRequired,
|
workflow: 'default'
|
};
|
break;
|
|
case 'ntlm':
|
payload.sspi = createNTLMRequest({
|
domain: authentication.options.domain
|
});
|
break;
|
|
default:
|
payload.userName = authentication.options.userName;
|
payload.password = authentication.options.password;
|
}
|
|
payload.hostname = os.hostname();
|
payload.serverName = this.routingData ? this.routingData.server : this.config.server;
|
payload.appName = this.config.options.appName || 'Tedious';
|
payload.libraryName = libraryName;
|
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(TYPE.LOGIN7, payload.toBuffer());
|
this.debug.payload(function () {
|
return payload.toString(' ');
|
});
|
process.nextTick(cb);
|
}
|
|
sendNTLMResponsePacket() {
|
const authentication = this.config.authentication;
|
const payload = new NTLMResponsePayload({
|
domain: authentication.options.domain,
|
userName: authentication.options.userName,
|
password: authentication.options.password,
|
database: this.config.options.database,
|
appName: this.config.options.appName,
|
packetSize: this.config.options.packetSize,
|
tdsVersion: this.config.options.tdsVersion,
|
ntlmpacket: this.ntlmpacket,
|
additional: this.additional
|
});
|
this.messageIo.sendMessage(TYPE.NTLMAUTH_PKT, payload.data);
|
this.debug.payload(function () {
|
return payload.toString(' ');
|
});
|
process.nextTick(() => {
|
this.transitionTo(this.STATE.SENT_NTLM_RESPONSE);
|
});
|
}
|
|
sendFedAuthResponsePacket(tokenResponse) {
|
const accessTokenLen = Buffer.byteLength(tokenResponse.accessToken, 'ucs2');
|
const data = Buffer.alloc(8 + accessTokenLen);
|
let offset = 0;
|
offset = data.writeUInt32LE(accessTokenLen + 4, offset);
|
offset = data.writeUInt32LE(accessTokenLen, offset);
|
data.write(tokenResponse.accessToken, offset, 'ucs2');
|
this.messageIo.sendMessage(TYPE.FEDAUTH_TOKEN, data); // sent the fedAuth token message, the rest is similar to standard login 7
|
|
process.nextTick(() => {
|
this.transitionTo(this.STATE.SENT_LOGIN7_WITH_STANDARD_LOGIN);
|
});
|
} // Returns false to apply backpressure.
|
|
|
sendDataToTokenStreamParser(data) {
|
return this.tokenStreamParser.addBuffer(data);
|
} // This is an internal method that is called from Request.pause().
|
// It has to check whether the passed Request object represents the currently
|
// active request, because the application might have called Request.pause()
|
// on an old inactive Request object.
|
|
|
pauseRequest(request) {
|
if (this.isRequestActive(request)) {
|
this.tokenStreamParser.pause();
|
}
|
} // This is an internal method that is called from Request.resume().
|
|
|
resumeRequest(request) {
|
if (this.isRequestActive(request)) {
|
this.tokenStreamParser.resume();
|
}
|
} // Returns true if the passed request is the currently active request of the connection.
|
|
|
isRequestActive(request) {
|
return request === this.request && this.state === this.STATE.SENT_CLIENT_REQUEST;
|
}
|
|
sendInitialSql() {
|
const payload = new SqlBatchPayload(this.getInitialSql(), this.currentTransactionDescriptor(), this.config.options);
|
return this.messageIo.sendMessage(TYPE.SQL_BATCH, payload.data);
|
}
|
|
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) {
|
options.push('set arithabort off');
|
}
|
|
if (this.config.options.enableConcatNullYieldsNull === true) {
|
options.push('set concat_null_yields_null on');
|
} else if (this.config.options.enableArithAbort === false) {
|
options.push('set concat_null_yields_null off');
|
}
|
|
if (this.config.options.enableCursorCloseOnCommit === true) {
|
options.push('set cursor_close_on_commit on');
|
} else if (this.config.options.enableCursorCloseOnCommit === false) {
|
options.push('set cursor_close_on_commit off');
|
}
|
|
if (this.config.options.datefirst !== null) {
|
options.push(`set datefirst ${this.config.options.datefirst}`);
|
}
|
|
if (this.config.options.dateFormat !== null) {
|
options.push(`set dateformat ${this.config.options.dateFormat}`);
|
}
|
|
if (this.config.options.enableImplicitTransactions === true) {
|
options.push('set implicit_transactions on');
|
} else if (this.config.options.enableImplicitTransactions === false) {
|
options.push('set implicit_transactions off');
|
}
|
|
if (this.config.options.language !== null) {
|
options.push(`set language ${this.config.options.language}`);
|
}
|
|
if (this.config.options.enableNumericRoundabort === true) {
|
options.push('set numeric_roundabort on');
|
} else if (this.config.options.enableNumericRoundabort === false) {
|
options.push('set numeric_roundabort off');
|
}
|
|
if (this.config.options.enableQuotedIdentifier === true) {
|
options.push('set quoted_identifier on');
|
} else if (this.config.options.enableQuotedIdentifier === false) {
|
options.push('set quoted_identifier off');
|
}
|
|
if (this.config.options.textsize !== null) {
|
options.push(`set textsize ${this.config.options.textsize}`);
|
}
|
|
if (this.config.options.connectionIsolationLevel !== null) {
|
options.push(`set transaction isolation level ${this.getIsolationLevelText(this.config.options.connectionIsolationLevel)}`);
|
}
|
|
if (this.config.options.abortTransactionOnError === true) {
|
options.push('set xact_abort on');
|
} else if (this.config.options.abortTransactionOnError === false) {
|
options.push('set xact_abort off');
|
}
|
|
return options.join('\n');
|
}
|
|
processedInitialSql() {
|
this.clearConnectTimer();
|
this.emit('connect');
|
}
|
|
processLogin7Response() {
|
if (this.loggedIn) {
|
this.dispatchEvent('loggedIn');
|
} else {
|
if (this.loginError) {
|
this.emit('connect', this.loginError);
|
} else {
|
this.emit('connect', ConnectionError('Login failed.', 'ELOGIN'));
|
}
|
|
this.dispatchEvent('loginFailed');
|
}
|
}
|
|
processLogin7NTLMResponse() {
|
if (this.ntlmpacket) {
|
this.dispatchEvent('receivedChallenge');
|
} else {
|
if (this.loginError) {
|
this.emit('connect', this.loginError);
|
} else {
|
this.emit('connect', ConnectionError('Login failed.', 'ELOGIN'));
|
}
|
|
this.dispatchEvent('loginFailed');
|
}
|
}
|
|
processLogin7NTLMAck() {
|
if (this.loggedIn) {
|
this.dispatchEvent('loggedIn');
|
} else {
|
if (this.loginError) {
|
this.emit('connect', this.loginError);
|
} else {
|
this.emit('connect', ConnectionError('Login failed.', 'ELOGIN'));
|
}
|
|
this.dispatchEvent('loginFailed');
|
}
|
}
|
|
execSqlBatch(request) {
|
this.makeRequest(request, TYPE.SQL_BATCH, new SqlBatchPayload(request.sqlTextOrProcedure, this.currentTransactionDescriptor(), this.config.options));
|
}
|
|
execSql(request) {
|
request.transformIntoExecuteSqlRpc();
|
|
if (request.error != null) {
|
process.nextTick(() => {
|
this.debug.log(request.error.message);
|
request.callback(request.error);
|
});
|
return;
|
}
|
|
this.makeRequest(request, TYPE.RPC_REQUEST, new RpcRequestPayload(request, this.currentTransactionDescriptor(), this.config.options));
|
}
|
/**
|
@function newBulkLoad
|
@param {string} table - Table's name.
|
@param {Object} [options] - BulkLoad options.
|
@param {boolean} [options.checkConstraints=false] - Honors constraints during bulk load, it is disabled by default.
|
@param {boolean} [options.fireTriggers=false] - Honors insert triggers during bulk load, it is disabled by default.
|
@param {boolean} [options.keepNulls=false] - Honors null value passed, ignores the default values set on table.
|
@param {boolean} [options.tableLock=false] - Places a bulk update(BU) lock on table while performing bulk load. Uses row locks by default.
|
@param {callback} callback - Function to call after BulkLoad executes.
|
*/
|
|
|
newBulkLoad(table, options, callback) {
|
if (callback === undefined) {
|
callback = options;
|
options = {};
|
}
|
|
if (typeof options !== 'object') {
|
throw new TypeError('"options" argument must be an object');
|
}
|
|
return new BulkLoad(table, this.config.options, options, callback);
|
}
|
|
execBulkLoad(bulkLoad) {
|
bulkLoad.executionStarted = true;
|
const request = new Request(bulkLoad.getBulkInsertSql(), error => {
|
if (error) {
|
if (error.code === 'UNKNOWN') {
|
error.message += ' This is likely because the schema of the BulkLoad does not match the schema of the table you are attempting to insert into.';
|
}
|
|
bulkLoad.error = error;
|
bulkLoad.callback(error);
|
return;
|
}
|
|
this.makeRequest(bulkLoad, TYPE.BULK_LOAD, undefined);
|
});
|
this.execSqlBatch(request);
|
}
|
|
prepare(request) {
|
request.transformIntoPrepareRpc();
|
this.makeRequest(request, TYPE.RPC_REQUEST, new RpcRequestPayload(request, this.currentTransactionDescriptor(), this.config.options));
|
}
|
|
unprepare(request) {
|
request.transformIntoUnprepareRpc();
|
this.makeRequest(request, TYPE.RPC_REQUEST, new RpcRequestPayload(request, this.currentTransactionDescriptor(), this.config.options));
|
}
|
|
execute(request, parameters) {
|
request.transformIntoExecuteRpc(parameters);
|
|
if (request.error != null) {
|
process.nextTick(() => {
|
this.debug.log(request.error.message);
|
request.callback(request.error);
|
});
|
return;
|
}
|
|
this.makeRequest(request, TYPE.RPC_REQUEST, new RpcRequestPayload(request, this.currentTransactionDescriptor(), this.config.options));
|
}
|
|
callProcedure(request) {
|
request.validateParameters();
|
|
if (request.error != null) {
|
process.nextTick(() => {
|
this.debug.log(request.error.message);
|
request.callback(request.error);
|
});
|
return;
|
}
|
|
this.makeRequest(request, TYPE.RPC_REQUEST, new RpcRequestPayload(request, this.currentTransactionDescriptor(), this.config.options));
|
}
|
|
beginTransaction(callback, name, isolationLevel) {
|
isolationLevel || (isolationLevel = this.config.options.isolationLevel);
|
const transaction = new Transaction(name || '', isolationLevel);
|
|
if (this.config.options.tdsVersion < '7_2') {
|
return this.execSqlBatch(new Request('SET TRANSACTION ISOLATION LEVEL ' + transaction.isolationLevelToTSQL() + ';BEGIN TRAN ' + transaction.name, (...args) => {
|
this.transactionDepth++;
|
|
if (this.transactionDepth === 1) {
|
this.inTransaction = true;
|
}
|
|
callback(...args);
|
}));
|
}
|
|
const request = new Request(undefined, err => {
|
return callback(err, this.currentTransactionDescriptor());
|
});
|
return this.makeRequest(request, TYPE.TRANSACTION_MANAGER, transaction.beginPayload(this.currentTransactionDescriptor()));
|
}
|
|
commitTransaction(callback, name) {
|
const transaction = new Transaction(name || '');
|
|
if (this.config.options.tdsVersion < '7_2') {
|
return this.execSqlBatch(new Request('COMMIT TRAN ' + transaction.name, (...args) => {
|
this.transactionDepth--;
|
|
if (this.transactionDepth === 0) {
|
this.inTransaction = false;
|
}
|
|
callback(...args);
|
}));
|
}
|
|
const request = new Request(undefined, callback);
|
return this.makeRequest(request, TYPE.TRANSACTION_MANAGER, transaction.commitPayload(this.currentTransactionDescriptor()));
|
}
|
|
rollbackTransaction(callback, name) {
|
const transaction = new Transaction(name || '');
|
|
if (this.config.options.tdsVersion < '7_2') {
|
return this.execSqlBatch(new Request('ROLLBACK TRAN ' + transaction.name, (...args) => {
|
this.transactionDepth--;
|
|
if (this.transactionDepth === 0) {
|
this.inTransaction = false;
|
}
|
|
callback(...args);
|
}));
|
}
|
|
const request = new Request(undefined, callback);
|
return this.makeRequest(request, TYPE.TRANSACTION_MANAGER, transaction.rollbackPayload(this.currentTransactionDescriptor()));
|
}
|
|
saveTransaction(callback, name) {
|
const transaction = new Transaction(name);
|
|
if (this.config.options.tdsVersion < '7_2') {
|
return this.execSqlBatch(new Request('SAVE TRAN ' + transaction.name, (...args) => {
|
this.transactionDepth++;
|
callback(...args);
|
}));
|
}
|
|
const request = new Request(undefined, callback);
|
return this.makeRequest(request, TYPE.TRANSACTION_MANAGER, transaction.savePayload(this.currentTransactionDescriptor()));
|
}
|
|
transaction(cb, isolationLevel) {
|
if (typeof cb !== 'function') {
|
throw new TypeError('`cb` must be a function');
|
}
|
|
const useSavepoint = this.inTransaction;
|
const name = '_tedious_' + crypto.randomBytes(10).toString('hex');
|
|
const txDone = (err, done, ...args) => {
|
if (err) {
|
if (this.inTransaction && this.state === this.STATE.LOGGED_IN) {
|
return this.rollbackTransaction(txErr => {
|
done(txErr || err, ...args);
|
}, name);
|
} else {
|
return process.nextTick(() => {
|
done(err, ...args);
|
});
|
}
|
} else {
|
if (useSavepoint) {
|
return process.nextTick(() => {
|
if (this.config.options.tdsVersion < '7_2') {
|
this.transactionDepth--;
|
}
|
|
done(null, ...args);
|
});
|
} else {
|
return this.commitTransaction(txErr => {
|
done(txErr, ...args);
|
}, name);
|
}
|
}
|
};
|
|
if (useSavepoint) {
|
return this.saveTransaction(err => {
|
if (err) {
|
return cb(err);
|
}
|
|
if (isolationLevel) {
|
return this.execSqlBatch(new Request('SET transaction isolation level ' + this.getIsolationLevelText(isolationLevel), err => {
|
return cb(err, txDone);
|
}));
|
} else {
|
return cb(null, txDone);
|
}
|
}, name);
|
} else {
|
return this.beginTransaction(err => {
|
if (err) {
|
return cb(err);
|
}
|
|
return cb(null, txDone);
|
}, name, isolationLevel);
|
}
|
}
|
|
makeRequest(request, packetType, payload) {
|
if (this.state !== this.STATE.LOGGED_IN) {
|
const message = 'Requests can only be made in the ' + this.STATE.LOGGED_IN.name + ' state, not the ' + this.state.name + ' state';
|
this.debug.log(message);
|
request.callback(RequestError(message, 'EINVALIDSTATE'));
|
} else {
|
if (packetType === TYPE.SQL_BATCH) {
|
this.isSqlBatch = true;
|
} else {
|
this.isSqlBatch = false;
|
}
|
|
this.request = request;
|
this.request.connection = this;
|
this.request.rowCount = 0;
|
this.request.rows = [];
|
this.request.rst = [];
|
|
if (request instanceof BulkLoad) {
|
const message = request.getMessageStream(); // If the bulkload was not put into streaming mode by the user,
|
// we end the rowToPacketTransform here for them.
|
//
|
// If it was put into streaming mode, it's the user's responsibility
|
// to end the stream.
|
|
if (!request.streamingMode) {
|
request.rowToPacketTransform.end();
|
}
|
|
this.messageIo.outgoingMessageStream.write(message);
|
} else {
|
this.createRequestTimer();
|
this.messageIo.sendMessage(packetType, payload.data, this.resetConnectionOnNextRequest);
|
this.resetConnectionOnNextRequest = false;
|
this.debug.payload(function () {
|
return payload.toString(' ');
|
});
|
}
|
|
this.transitionTo(this.STATE.SENT_CLIENT_REQUEST);
|
|
if (request.paused) {
|
// Request.pause() has been called before the request was started
|
this.pauseRequest(request);
|
}
|
}
|
}
|
|
cancel() {
|
if (this.state !== this.STATE.SENT_CLIENT_REQUEST) {
|
const message = 'Requests can only be canceled in the ' + this.STATE.SENT_CLIENT_REQUEST.name + ' state, not the ' + this.state.name + ' state';
|
this.debug.log(message);
|
return false;
|
} else if (this.request instanceof BulkLoad) {
|
this.debug.log('Canceling a bulk load has not yet been implemented.');
|
return false;
|
} else {
|
this.request.canceled = true;
|
this.messageIo.sendMessage(TYPE.ATTENTION);
|
this.transitionTo(this.STATE.SENT_ATTENTION);
|
return true;
|
}
|
}
|
|
reset(callback) {
|
const request = new Request(this.getInitialSql(), err => {
|
if (this.config.options.tdsVersion < '7_2') {
|
this.inTransaction = false;
|
}
|
|
callback(err);
|
});
|
this.resetConnectionOnNextRequest = true;
|
return this.execSqlBatch(request);
|
}
|
|
currentTransactionDescriptor() {
|
return this.transactionDescriptors[this.transactionDescriptors.length - 1];
|
}
|
|
getIsolationLevelText(isolationLevel) {
|
switch (isolationLevel) {
|
case ISOLATION_LEVEL.READ_UNCOMMITTED:
|
return 'read uncommitted';
|
|
case ISOLATION_LEVEL.REPEATABLE_READ:
|
return 'repeatable read';
|
|
case ISOLATION_LEVEL.SERIALIZABLE:
|
return 'serializable';
|
|
case ISOLATION_LEVEL.SNAPSHOT:
|
return 'snapshot';
|
|
default:
|
return 'read committed';
|
}
|
}
|
|
}
|
|
module.exports = Connection;
|
Connection.prototype.STATE = {
|
CONNECTING: {
|
name: 'Connecting',
|
enter: function enter() {
|
this.initialiseConnection();
|
},
|
events: {
|
socketError: function socketError() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
connectTimeout: function connectTimeout() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
socketConnect: function socketConnect() {
|
this.sendPreLogin();
|
this.transitionTo(this.STATE.SENT_PRELOGIN);
|
}
|
}
|
},
|
SENT_PRELOGIN: {
|
name: 'SentPrelogin',
|
enter: function enter() {
|
this.emptyMessageBuffer();
|
},
|
events: {
|
socketError: function socketError() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
connectTimeout: function connectTimeout() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
data: function data(_data) {
|
this.addToMessageBuffer(_data);
|
},
|
message: function message() {
|
this.processPreLoginResponse();
|
},
|
noTls: function noTls() {
|
this.sendLogin7Packet(() => {
|
const authentication = this.config.authentication;
|
|
if (authentication.type === 'ntlm') {
|
this.transitionTo(this.STATE.SENT_LOGIN7_WITH_NTLM);
|
} else {
|
this.transitionTo(this.STATE.SENT_LOGIN7_WITH_STANDARD_LOGIN);
|
}
|
});
|
},
|
tls: function tls() {
|
this.messageIo.startTls(this.secureContext, this.config.server, this.config.options.trustServerCertificate);
|
this.transitionTo(this.STATE.SENT_TLSSSLNEGOTIATION);
|
}
|
}
|
},
|
REROUTING: {
|
name: 'ReRouting',
|
enter: function enter() {
|
this.cleanupConnection(this.cleanupTypeEnum.REDIRECT);
|
},
|
events: {
|
message: function message() {},
|
socketError: function socketError() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
connectTimeout: function connectTimeout() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
reconnect: function reconnect() {
|
this.transitionTo(this.STATE.CONNECTING);
|
}
|
}
|
},
|
TRANSIENT_FAILURE_RETRY: {
|
name: 'TRANSIENT_FAILURE_RETRY',
|
enter: function enter() {
|
this.curTransientRetryCount++;
|
this.cleanupConnection(this.cleanupTypeEnum.RETRY);
|
},
|
events: {
|
message: function message() {},
|
socketError: function socketError() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
connectTimeout: function connectTimeout() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
retry: function retry() {
|
this.createRetryTimer();
|
}
|
}
|
},
|
SENT_TLSSSLNEGOTIATION: {
|
name: 'SentTLSSSLNegotiation',
|
events: {
|
socketError: function socketError() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
connectTimeout: function connectTimeout() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
data: function data(_data2) {
|
this.messageIo.tlsHandshakeData(_data2);
|
},
|
message: function message() {
|
if (this.messageIo.tlsNegotiationComplete) {
|
this.sendLogin7Packet(() => {
|
const authentication = this.config.authentication;
|
|
if (authentication.type === 'azure-active-directory-password') {
|
this.transitionTo(this.STATE.SENT_LOGIN7_WITH_FEDAUTH);
|
} else if (authentication.type === 'ntlm') {
|
this.transitionTo(this.STATE.SENT_LOGIN7_WITH_NTLM);
|
} else {
|
this.transitionTo(this.STATE.SENT_LOGIN7_WITH_STANDARD_LOGIN);
|
}
|
});
|
}
|
}
|
}
|
},
|
SENT_LOGIN7_WITH_STANDARD_LOGIN: {
|
name: 'SentLogin7WithStandardLogin',
|
events: {
|
socketError: function socketError() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
connectTimeout: function connectTimeout() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
data: function data(_data3) {
|
this.sendDataToTokenStreamParser(_data3);
|
},
|
loggedIn: function loggedIn() {
|
this.transitionTo(this.STATE.LOGGED_IN_SENDING_INITIAL_SQL);
|
},
|
routingChange: function routingChange() {
|
this.transitionTo(this.STATE.REROUTING);
|
},
|
loginFailed: function loginFailed() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
featureExtAck: function featureExtAck(token) {
|
const authentication = this.config.authentication;
|
|
if (authentication.type === 'azure-active-directory-password') {
|
if (token.fedAuth === undefined) {
|
this.loginError = ConnectionError('Did not receive Active Directory authentication acknowledgement');
|
this.loggedIn = false;
|
} else if (token.fedAuth.length !== 0) {
|
this.loginError = ConnectionError(`Active Directory authentication acknowledgment for ${authentication.type} authentication method includes extra data`);
|
this.loggedIn = false;
|
}
|
} else {
|
if (token.fedAuth === undefined) {
|
this.loginError = ConnectionError('Received acknowledgement for unknown feature');
|
this.loggedIn = false;
|
} else {
|
this.loginError = ConnectionError('Did not request Active Directory authentication, but received the acknowledgment');
|
this.loggedIn = false;
|
}
|
}
|
},
|
message: function message() {
|
this.processLogin7Response();
|
}
|
}
|
},
|
SENT_LOGIN7_WITH_NTLM: {
|
name: 'SentLogin7WithNTLMLogin',
|
events: {
|
socketError: function socketError() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
connectTimeout: function connectTimeout() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
data: function data(_data4) {
|
this.sendDataToTokenStreamParser(_data4);
|
},
|
receivedChallenge: function receivedChallenge() {
|
this.sendNTLMResponsePacket();
|
},
|
loginFailed: function loginFailed() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
message: function message() {
|
this.processLogin7NTLMResponse();
|
}
|
}
|
},
|
SENT_NTLM_RESPONSE: {
|
name: 'SentNTLMResponse',
|
events: {
|
socketError: function socketError() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
connectTimeout: function connectTimeout() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
data: function data(_data5) {
|
this.sendDataToTokenStreamParser(_data5);
|
},
|
loggedIn: function loggedIn() {
|
this.transitionTo(this.STATE.LOGGED_IN_SENDING_INITIAL_SQL);
|
},
|
loginFailed: function loginFailed() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
routingChange: function routingChange() {
|
this.transitionTo(this.STATE.REROUTING);
|
},
|
message: function message() {
|
this.processLogin7NTLMAck();
|
}
|
}
|
},
|
SENT_LOGIN7_WITH_FEDAUTH: {
|
name: 'SentLogin7Withfedauth',
|
events: {
|
socketError: function socketError() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
connectTimeout: function connectTimeout() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
data: function data(_data6) {
|
this.sendDataToTokenStreamParser(_data6);
|
},
|
loginFailed: function loginFailed() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
routingChange: function routingChange() {
|
this.transitionTo(this.STATE.REROUTING);
|
},
|
fedAuthInfo: function fedAuthInfo(token) {
|
this.fedAuthInfoToken = token;
|
},
|
message: function message() {
|
if (this.fedAuthInfoToken && this.fedAuthInfoToken.stsurl && this.fedAuthInfoToken.spn) {
|
const clientId = '7f98cb04-cd1e-40df-9140-3bf7e2cea4db';
|
const context = new AuthenticationContext(this.fedAuthInfoToken.stsurl);
|
const authentication = this.config.authentication;
|
context.acquireTokenWithUsernamePassword(this.fedAuthInfoToken.spn, authentication.options.userName, authentication.options.password, clientId, (err, tokenResponse) => {
|
if (err) {
|
this.loginError = ConnectionError('Security token could not be authenticated or authorized.', 'EFEDAUTH');
|
this.emit('connect', this.loginError);
|
this.dispatchEvent('loginFailed');
|
return;
|
}
|
|
this.sendFedAuthResponsePacket(tokenResponse);
|
});
|
} else {
|
if (this.loginError) {
|
this.emit('connect', this.loginError);
|
} else {
|
this.emit('connect', ConnectionError('Login failed.', 'ELOGIN'));
|
}
|
|
this.dispatchEvent('loginFailed');
|
}
|
}
|
}
|
},
|
LOGGED_IN_SENDING_INITIAL_SQL: {
|
name: 'LoggedInSendingInitialSql',
|
enter: function enter() {
|
this.sendInitialSql();
|
},
|
events: {
|
socketError: function socketError() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
connectTimeout: function connectTimeout() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
data: function data(_data7) {
|
this.sendDataToTokenStreamParser(_data7);
|
},
|
message: function message() {
|
this.transitionTo(this.STATE.LOGGED_IN);
|
this.processedInitialSql();
|
}
|
}
|
},
|
LOGGED_IN: {
|
name: 'LoggedIn',
|
events: {
|
socketError: function socketError() {
|
this.transitionTo(this.STATE.FINAL);
|
}
|
}
|
},
|
SENT_CLIENT_REQUEST: {
|
name: 'SentClientRequest',
|
exit: function exit(nextState) {
|
this.clearRequestTimer();
|
|
if (nextState !== this.STATE.FINAL) {
|
this.tokenStreamParser.resume();
|
}
|
},
|
events: {
|
socketError: function socketError(err) {
|
const sqlRequest = this.request;
|
this.request = undefined;
|
sqlRequest.callback(err);
|
this.transitionTo(this.STATE.FINAL);
|
},
|
data: function data(_data8) {
|
this.clearRequestTimer(); // request timer is stopped on first data package
|
|
const ret = this.sendDataToTokenStreamParser(_data8);
|
|
if (ret === false) {
|
// Bridge backpressure from the token stream parser transform to the
|
// packet stream transform.
|
this.messageIo.pause();
|
}
|
},
|
message: function message() {
|
// We have to channel the 'message' (EOM) event through the token stream
|
// parser transform, to keep it in line with the flow of the tokens, when
|
// the incoming data flow is paused and resumed.
|
this.tokenStreamParser.addEndOfMessageMarker();
|
},
|
endOfMessageMarkerReceived: function endOfMessageMarkerReceived() {
|
this.transitionTo(this.STATE.LOGGED_IN);
|
const sqlRequest = this.request;
|
this.request = undefined;
|
|
if (this.config.options.tdsVersion < '7_2' && sqlRequest.error && this.isSqlBatch) {
|
this.inTransaction = false;
|
}
|
|
sqlRequest.callback(sqlRequest.error, sqlRequest.rowCount, sqlRequest.rows);
|
}
|
}
|
},
|
SENT_ATTENTION: {
|
name: 'SentAttention',
|
enter: function enter() {
|
this.attentionReceived = false;
|
},
|
events: {
|
socketError: function socketError() {
|
this.transitionTo(this.STATE.FINAL);
|
},
|
data: function data(_data9) {
|
this.sendDataToTokenStreamParser(_data9);
|
},
|
attention: function attention() {
|
this.attentionReceived = true;
|
},
|
message: function message() {
|
// 3.2.5.7 Sent Attention State
|
// Discard any data contained in the response, until we receive the attention response
|
if (this.attentionReceived) {
|
const sqlRequest = this.request;
|
this.request = undefined;
|
this.transitionTo(this.STATE.LOGGED_IN);
|
|
if (sqlRequest.canceled) {
|
sqlRequest.callback(RequestError('Canceled.', 'ECANCEL'));
|
} else {
|
const timeout = sqlRequest.timeout !== undefined ? sqlRequest.timeout : this.config.options.requestTimeout;
|
const message = 'Timeout: Request failed to complete in ' + timeout + 'ms';
|
sqlRequest.callback(RequestError(message, 'ETIMEOUT'));
|
}
|
}
|
}
|
}
|
},
|
FINAL: {
|
name: 'Final',
|
enter: function enter() {
|
this.cleanupConnection(this.cleanupTypeEnum.NORMAL);
|
},
|
events: {
|
loginFailed: function loginFailed() {// Do nothing. The connection was probably closed by the client code.
|
},
|
connectTimeout: function connectTimeout() {// Do nothing, as the timer should be cleaned up.
|
},
|
message: function message() {// Do nothing
|
},
|
socketError: function socketError() {// Do nothing
|
}
|
}
|
}
|
};
|