UNPKG

@jovercao/mssql

Version:

Microsoft SQL Server client for Node.js.

605 lines (545 loc) 17.8 kB
const { parseSqlConnectionString } = require('@tediousjs/connection-string') const deepclone = require('rfdc/default') const shared = require('../shared') const { IDS } = require('../utils') const { EventEmitter } = require('events') const debug = require('debug')('mssql:base') const ConnectionError = require('../error/connection-error') const ISOLATION_LEVEL = require('../isolationlevel') const { TransactionError } = require('../error') const globalConnection = require('../global-connection') class Connection extends EventEmitter { /** * Create new Connection. * * @param {Object|String} config Connection configuration object or connection string. * @param {basicCallback} [callback] A callback which is called after connection has established, or an error has occurred. */ constructor(configOrPool) { super() IDS.add(this, '@Connection') debug('@Connection (%d): created', IDS.get(this)) this._connecting = false this._connection = undefined; this._transaction = undefined; this._activeRequest = undefined; // this._inTransaction = false; this._aborted = undefined; if (!configOrPool) { configOrPool = globalConnection.pool; } if (configOrPool instanceof shared.driver.ConnectionPool) { this._pool = configOrPool this.config = configOrPool.config } else { if (typeof config === 'string') { this.config = this._parseConnectionString(configOrPool) } else { this.config = deepclone(configOrPool) } // set defaults this.config.port = this.config.port || 1433 this.config.options = this.config.options || {} this.config.stream = this.config.stream || false this.config.parseJSON = this.config.parseJSON || false this.config.arrayRowMode = this.config.arrayRowMode || false this.config.validateConnection = 'validateConnection' in this.config ? this.config.validateConnection : true if (/^(.*)\\(.*)$/.exec(this.config.server)) { this.config.server = RegExp.$1 this.config.options.instanceName = RegExp.$2 } } } _parseConnectionString(connectionString) { const parsed = parseSqlConnectionString(connectionString, true, true) return Object.entries(parsed).reduce((config, [key, value]) => { switch (key) { case 'application name': break case 'applicationintent': Object.assign(config.options, { readOnlyIntent: value === 'readonly' }) break case 'asynchronous processing': break case 'attachdbfilename': break case 'authentication': break case 'column encryption setting': break case 'connection timeout': Object.assign(config, { connectionTimeout: value * 1000 }) break case 'connection lifetime': break case 'connectretrycount': break case 'connectretryinterval': Object.assign(config.options, { connectionRetryInterval: value * 1000 }) break case 'context connection': break case 'current language': Object.assign(config.options, { language: value }) break case 'data source': { let server = value let instanceName let port = 1433 if (/^np:/i.test(server)) { throw new Error('Connection via Named Pipes is not supported.') } if (/^tcp:/i.test(server)) { server = server.substr(4) } if (/^(.*)\\(.*)$/.exec(server)) { server = RegExp.$1 instanceName = RegExp.$2 } if (/^(.*),(.*)$/.exec(server)) { server = RegExp.$1.trim() port = parseInt(RegExp.$2.trim(), 10) } if (server === '.' || server === '(.)' || server.toLowerCase() === '(localdb)' || server.toLowerCase() === '(local)') { server = 'localhost' } Object.assign(config, { port, server }) Object.assign(config.options, { instanceName }) break } case 'encrypt': Object.assign(config.options, { encrypt: !!value }) break case 'enlist': break case 'failover partner': break case 'initial catalog': Object.assign(config, { database: value }) break case 'integrated security': break case 'max pool size': Object.assign(config.pool, { max: value }) break case 'min pool size': Object.assign(config.pool, { min: value }) break case 'multipleactiveresultsets': break case 'multisubnetfailover': Object.assign(config.options, { multiSubnetFailover: value }) break case 'network library': break case 'packet size': Object.assign(config.options, { packetSize: value }) break case 'password': Object.assign(config, { password: value }) break case 'persist security info': break case 'poolblockingperiod': break case 'pooling': break case 'replication': break case 'transaction binding': Object.assign(config.options, { enableImplicitTransactions: value.toLowerCase() === 'implicit unbind' }) break case 'transparentnetworkipresolution': break case 'trustservercertificate': Object.assign(config.options, { trustServerCertificate: value }) break case 'type system version': break case 'user id': { let user = value let domain if (/^(.*)\\(.*)$/.exec(user)) { domain = RegExp.$1 user = RegExp.$2 } Object.assign(config, { domain, user }) break } case 'user instance': break case 'workstation id': Object.assign(config.options, { workstationId: value }) break case 'request timeout': Object.assign(config, { requestTimeout: parseInt(value, 10) }) break case 'stream': Object.assign(config, { stream: !!value }) break case 'useutc': Object.assign(config.options, { useUTC: !!value }) break case 'parsejson': Object.assign(config, { parseJSON: !!value }) break } return config }, { options: {}, pool: {} }) } get connected() { return !!this._connection } get inTransaction() { return !!this._transaction; } open() { return this.connect(); } /** * Creates a new connection pool with one active connection. This one initial connection serves as a probe to find out whether the configuration is valid. * * @param {basicCallback} [callback] A callback which is called after connection has established, or an error has occurred. If omited, method returns Promise. * @return {ConnectionPool|Promise} */ async connect() { if (this._connecting) { throw new ConnectionError('Connection is connecting.') } if (this.connected) { throw new ConnectionError('Connection is already opened.') } this._connecting = true; try { if (this._pool) { this._connection = await this._pool.acquire(this); } else { this._connection = await this._connect(); } } finally { this._connecting = false; } } async close() { if (!this.connected) { throw new ConnectionError('The connection has not been opened.') } if (this._closing) { throw new ConnectionError('The connection is closing.') } this._closing = true; try { if (this._pool) { this._pool.release(this._connection) } else { await this._disconnect(this._connection); this._connection = null; } } finally { this._closing = false; } } async saveTransaction(name) { return this.connection.saveTransaction(name); } reset() { if (!this.connected) { throw new ConnectionError('The connection has not been opened.') } return this._connection.reset(); } /** * if use config, open a new connection. */ _connect() { throw new Error('Not implementation.') } /** * if use config, close the connection. */ _disconnect(_tedious) { throw new Error('Not implementation.') } /** * Acquire connection from this connection pool. * * @param {ConnectionPool|Transaction|PreparedStatement} instance Requester. * @param {acquireCallback} [callback] A callback which is called after connection has been acquired, or an error has occurred. If omited, method returns Promise. * @return {ConnectionPool|Promise} */ acquire(instance, callback) { const retrn = (err) => { if (typeof callback === 'function') { if (err) { this.emit('error', err) return callback(err) } return callback(null, this._connection, this.config) } return shared.Promise.resolve(this._connection) } if (this._aborted) { return retrn(new TransactionError("The transaction is automatically rolled back due to a error, pls use `rollback()` to cancel transaction.", 'EABORT')) } if (this._activeRequest) { return retrn(new ConnectionError("Can't acquire connection for the request. There is another request in progress.", 'EREQINPROG')) } if (!this.connected) { return retrn(new ConnectionError('The connection has not been opened.')) } if (instance instanceof shared.driver.Transaction) { if (this._transaction) { return retrn(new TransactionError('The connection transaction is begun.', 'ETRANS')) } this._transaction = instance; this._transaction.on('rollback', () => { this._transaction = null }) this._transaction.on('commit', () => { this._transaction = null }) } else { this._activeRequest = instance; } return retrn() } async beginTrans(isolationLevel) { if (this._transaction) { throw new TransactionError('Connection transaction has been begun.', 'ETRANSEXISTS') } const trans = this.transaction() await trans.begin(isolationLevel); return trans; } async commit() { if (!this._transaction) { throw new TransactionError('Connection transaction has not begun.', 'ENOTBEGUN') } await this._transaction.commit() } async rollback() { if (!this._transaction) { throw new TransactionError('Connection transaction has not begun.', 'ENOTBEGUN') } await this._transaction.rollback() } /** * 不再关闭或者释放连接,而是取消激活请求 * * @param {Connection} connection Previously acquired connection. * @return {ConnectionPool} */ // eslint-disable-next-line no-unused-vars release(_connection) { this._activeRequest = null; return this } /** * Returns new request using this connection. * * @return {Request} */ request() { return new shared.driver.Request(this) } /** * Returns new transaction using this connection. * * @return {Transaction} */ transaction() { return new shared.driver.Transaction(this) } /** * Creates a new query using this connection from a tagged template string. * * @variation 1 * @param {Array} strings Array of string literals. * @param {...*} keys Values. * @return {Request} */ /** * Execute the SQL command. * * @variation 2 * @param {String} command T-SQL command to be executed. * @param {Request~requestCallback} [callback] A callback which is called after execution has completed, or an error has occurred. If omited, method returns Promise. * @return {Request|Promise} */ query() { if (typeof arguments[0] === 'string') { return new shared.driver.Request(this).query(arguments[0], arguments[1]) } const values = Array.prototype.slice.call(arguments) const strings = values.shift() return new shared.driver.Request(this)._template(strings, values, 'query') } /** * Creates a new batch using this connection from a tagged template string. * * @variation 1 * @param {Array} strings Array of string literals. * @param {...*} keys Values. * @return {Request} */ /** * Execute the SQL command. * * @variation 2 * @param {String} command T-SQL command to be executed. * @param {Request~requestCallback} [callback] A callback which is called after execution has completed, or an error has occurred. If omited, method returns Promise. * @return {Request|Promise} */ batch() { if (typeof arguments[0] === 'string') { return new shared.driver.Request(this).batch(arguments[0], arguments[1]) } const values = Array.prototype.slice.call(arguments) const strings = values.shift() return new shared.driver.Request(this)._template(strings, values, 'batch') } // /** // * Begin a transaction. // * // * @param {Number} [isolationLevel] Controls the locking and row versioning behavior of TSQL statements issued by a connection. // * @param {basicCallback} [callback] A callback which is called after transaction has began, or an error has occurred. If omited, method returns Promise. // * @return {Transaction|Promise} // */ // async beginTrans(isolationLevel) { // if (!this.connected) { // throw new ConnectionError('The connection has not been opened.') // } // if (this._inTransaction) { // throw new TransactionError('Transaction has already begun.', 'EALREADYBEGUN') // } // if (isolationLevel) { // if (!Object.keys(ISOLATION_LEVEL).some(key => ISOLATION_LEVEL[key] === isolationLevel)) { // throw new TransactionError('Invalid isolation level.') // } // } // try { // debug('@Connection (%d): transaction begin', IDS.get(this)) // await this._beginTrans(this._connection, isolationLevel) // debug('@Connection (%d): transaction begun', IDS.get(this)) // this.isolationLevel = isolationLevel // this._inTransaction = true // this.emit('begin') // } catch (err) { // this.emit('error', err) // throw err; // } // } // /** // * Commit a transaction. // * // * @param {basicCallback} [callback] A callback which is called after transaction has commited, or an error has occurred. If omited, method returns Promise. // * @return {Transaction|Promise} // */ // async commit() { // if (this._aborted) { // throw new TransactionError("The transaction is automatically rolled back due to a serious error, pls use `rollback()` to cancel transaction.", 'EABORT') // } // if (!this._inTransaction) { // throw new TransactionError('Transaction has not begun. Call begin() first.', 'ENOTBEGUN') // } // if (this._activeInstance) { // throw new TransactionError("Can't commit transaction. There is a request in progress.", 'EREQINPROG') // } // try { // debug('@Connection (%d): transaction commit', IDS.get(this)) // await this._commit(this._connection) // debug('@Connection (%d): transaction commited', IDS.get(this)) // this._inTransaction = false; // } catch (err) { // this.emit('commit') // throw err // } // } // /** // * Rollback a transaction. // * // * @param {basicCallback} [callback] A callback which is called after transaction has rolled back, or an error has occurred. If omited, method returns Promise. // * @return {Transaction|Promise} // */ // async rollback() { // if (!this._inTransaction) { // throw new TransactionError('Transaction has not begun. Call begin() first.', 'ENOTBEGUN') // } // if (this._activeInstance) { // throw new TransactionError("Can't rollback transaction. There is a request in progress.", 'EREQINPROG') // } // try { // debug('@Connection (%d): transaction rollback', IDS.get(this)) // this._inTransaction = false; // if (!this._aborted) { // await this._rollback(this._connection) // } // this.emit('rollback', this._aborted) // this._aborted = undefined; // debug('@Connection (%d): transaction rolled back', IDS.get(this)) // } catch (err) { // this.emit('error', err) // throw err // } // } // /** // * @private // * @param {basicCallback} [callback] // * @return {Transaction} // */ // // eslint-disable-next-line no-unused-vars // _beginTrans(tedious, isolationLevel) { // throw new Error('Not implementation.') // } // // eslint-disable-next-line no-unused-vars // _commit(tedious) { // throw new Error('Not implementation.') // } // // eslint-disable-next-line no-unused-vars // _rollback(tedious) { // throw new Error('Not implementation.') // } } Connection.defaultIsolationLevel = ISOLATION_LEVEL.READ_COMMITTED module.exports = Connection