Commit iniziale
This commit is contained in:
643
node_modules/mssql/lib/base/connection-pool.js
generated
vendored
Normal file
643
node_modules/mssql/lib/base/connection-pool.js
generated
vendored
Normal file
@@ -0,0 +1,643 @@
|
||||
'use strict'
|
||||
|
||||
const { EventEmitter } = require('node:events')
|
||||
const debug = require('debug')('mssql:base')
|
||||
const { parseSqlConnectionString } = require('@tediousjs/connection-string')
|
||||
const tarn = require('tarn')
|
||||
const { IDS } = require('../utils')
|
||||
const ConnectionError = require('../error/connection-error')
|
||||
const shared = require('../shared')
|
||||
const clone = require('rfdc/default')
|
||||
const { MSSQLError } = require('../error')
|
||||
|
||||
/**
|
||||
* Class ConnectionPool.
|
||||
*
|
||||
* Internally, each `Connection` instance is a separate pool of TDS connections. Once you create a new `Request`/`Transaction`/`Prepared Statement`, a new TDS connection is acquired from the pool and reserved for desired action. Once the action is complete, connection is released back to the pool.
|
||||
*
|
||||
* @property {Boolean} connected If true, connection is established.
|
||||
* @property {Boolean} connecting If true, connection is being established.
|
||||
*
|
||||
* @fires ConnectionPool#connect
|
||||
* @fires ConnectionPool#close
|
||||
*/
|
||||
|
||||
class ConnectionPool 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 (config, callback) {
|
||||
super()
|
||||
|
||||
IDS.add(this, 'ConnectionPool')
|
||||
debug('pool(%d): created', IDS.get(this))
|
||||
|
||||
this._connectStack = []
|
||||
this._closeStack = []
|
||||
|
||||
this._connected = false
|
||||
this._connecting = false
|
||||
this._healthy = false
|
||||
|
||||
if (typeof config === 'string') {
|
||||
try {
|
||||
this.config = this.constructor.parseConnectionString(config)
|
||||
} catch (ex) {
|
||||
if (typeof callback === 'function') {
|
||||
return setImmediate(callback, ex)
|
||||
}
|
||||
throw ex
|
||||
}
|
||||
} else {
|
||||
this.config = clone(config)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
const namedServer = /^(.*)\\(.*)$/.exec(this.config.server)
|
||||
if (namedServer) {
|
||||
this.config.server = namedServer[1]
|
||||
this.config.options.instanceName = namedServer[2]
|
||||
}
|
||||
|
||||
if (typeof this.config.options.useColumnNames !== 'undefined' && this.config.options.useColumnNames !== true) {
|
||||
const ex = new MSSQLError('Invalid options `useColumnNames`, use `arrayRowMode` instead')
|
||||
if (typeof callback === 'function') {
|
||||
return setImmediate(callback, ex)
|
||||
}
|
||||
throw ex
|
||||
}
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
this.connect(callback)
|
||||
}
|
||||
}
|
||||
|
||||
get connected () {
|
||||
return this._connected
|
||||
}
|
||||
|
||||
get connecting () {
|
||||
return this._connecting
|
||||
}
|
||||
|
||||
get healthy () {
|
||||
return this._healthy
|
||||
}
|
||||
|
||||
static parseConnectionString (connectionString) {
|
||||
return this._parseConnectionString(connectionString)
|
||||
}
|
||||
|
||||
static _parseAuthenticationType (type, entries) {
|
||||
switch (type.toLowerCase()) {
|
||||
case 'active directory integrated':
|
||||
if (entries.includes('token')) {
|
||||
return 'azure-active-directory-access-token'
|
||||
} else if (['client id', 'client secret', 'tenant id'].every(entry => entries.includes(entry))) {
|
||||
return 'azure-active-directory-service-principal-secret'
|
||||
} else if (['client id', 'msi endpoint', 'msi secret'].every(entry => entries.includes(entry))) {
|
||||
return 'azure-active-directory-msi-app-service'
|
||||
} else if (['client id', 'msi endpoint'].every(entry => entries.includes(entry))) {
|
||||
return 'azure-active-directory-msi-vm'
|
||||
}
|
||||
return 'azure-active-directory-default'
|
||||
case 'active directory password':
|
||||
return 'azure-active-directory-password'
|
||||
case 'ntlm':
|
||||
return 'ntlm'
|
||||
default:
|
||||
return 'default'
|
||||
}
|
||||
}
|
||||
|
||||
static _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':
|
||||
Object.assign(config, {
|
||||
authentication_type: this._parseAuthenticationType(value, Object.keys(parsed))
|
||||
})
|
||||
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 'client id':
|
||||
Object.assign(config, {
|
||||
clientId: value
|
||||
})
|
||||
break
|
||||
case 'client secret':
|
||||
Object.assign(config, {
|
||||
clientSecret: value
|
||||
})
|
||||
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)
|
||||
}
|
||||
const namedServerParts = /^(.*)\\(.*)$/.exec(server)
|
||||
if (namedServerParts) {
|
||||
server = namedServerParts[1].trim()
|
||||
instanceName = namedServerParts[2].trim()
|
||||
}
|
||||
const serverParts = /^(.*),(.*)$/.exec(server)
|
||||
if (serverParts) {
|
||||
server = serverParts[1].trim()
|
||||
port = parseInt(serverParts[2].trim(), 10)
|
||||
} else {
|
||||
const instanceParts = /^(.*),(.*)$/.exec(instanceName)
|
||||
if (instanceParts) {
|
||||
instanceName = instanceParts[1].trim()
|
||||
port = parseInt(instanceParts[2].trim(), 10)
|
||||
}
|
||||
}
|
||||
if (server === '.' || server === '(.)' || server.toLowerCase() === '(localdb)' || server.toLowerCase() === '(local)') {
|
||||
server = 'localhost'
|
||||
}
|
||||
Object.assign(config, {
|
||||
port,
|
||||
server
|
||||
})
|
||||
if (instanceName) {
|
||||
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 'msi endpoint':
|
||||
Object.assign(config, {
|
||||
msiEndpoint: value
|
||||
})
|
||||
break
|
||||
case 'msi secret':
|
||||
Object.assign(config, {
|
||||
msiSecret: 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 'tenant id':
|
||||
Object.assign(config, {
|
||||
tenantId: value
|
||||
})
|
||||
break
|
||||
case 'token':
|
||||
Object.assign(config, {
|
||||
token: value
|
||||
})
|
||||
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
|
||||
const domainUser = /^(.*)\\(.*)$/.exec(user)
|
||||
if (domainUser) {
|
||||
domain = domainUser[1]
|
||||
user = domainUser[2]
|
||||
}
|
||||
if (domain) {
|
||||
Object.assign(config, {
|
||||
domain
|
||||
})
|
||||
}
|
||||
if (user) {
|
||||
Object.assign(config, {
|
||||
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: {} })
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire connection from this connection pool.
|
||||
*
|
||||
* @param {ConnectionPool|Transaction|PreparedStatement} requester 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 (requester, callback) {
|
||||
const acquirePromise = shared.Promise.resolve(this._acquire()).catch(err => {
|
||||
this.emit('error', err)
|
||||
throw err
|
||||
})
|
||||
if (typeof callback === 'function') {
|
||||
acquirePromise.then(connection => callback(null, connection, this.config)).catch(callback)
|
||||
return this
|
||||
}
|
||||
|
||||
return acquirePromise
|
||||
}
|
||||
|
||||
_acquire () {
|
||||
if (!this.pool) {
|
||||
return shared.Promise.reject(new ConnectionError('Connection not yet open.', 'ENOTOPEN'))
|
||||
} else if (this.pool.destroyed) {
|
||||
return shared.Promise.reject(new ConnectionError('Connection is closing', 'ENOTOPEN'))
|
||||
}
|
||||
|
||||
return this.pool.acquire().promise
|
||||
}
|
||||
|
||||
/**
|
||||
* Release connection back to the pool.
|
||||
*
|
||||
* @param {Connection} connection Previously acquired connection.
|
||||
* @return {ConnectionPool}
|
||||
*/
|
||||
|
||||
release (connection) {
|
||||
debug('connection(%d): released', IDS.get(connection))
|
||||
|
||||
if (this.pool) {
|
||||
this.pool.release(connection)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
|
||||
connect (callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this._connect(callback)
|
||||
return this
|
||||
}
|
||||
|
||||
return new shared.Promise((resolve, reject) => {
|
||||
return this._connect(err => {
|
||||
if (err) return reject(err)
|
||||
resolve(this)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {basicCallback} callback
|
||||
*/
|
||||
|
||||
_connect (callback) {
|
||||
if (this._connected) {
|
||||
debug('pool(%d): already connected, executing connect callback immediately', IDS.get(this))
|
||||
return setImmediate(callback, null, this)
|
||||
}
|
||||
|
||||
this._connectStack.push(callback)
|
||||
|
||||
if (this._connecting) {
|
||||
return
|
||||
}
|
||||
|
||||
this._connecting = true
|
||||
debug('pool(%d): connecting', IDS.get(this))
|
||||
|
||||
// create one test connection to check if everything is ok
|
||||
this._poolCreate().then((connection) => {
|
||||
debug('pool(%d): connected', IDS.get(this))
|
||||
this._healthy = true
|
||||
|
||||
return this._poolDestroy(connection).then(() => {
|
||||
// prepare pool
|
||||
this.pool = new tarn.Pool(
|
||||
Object.assign({
|
||||
create: () => this._poolCreate()
|
||||
.then(connection => {
|
||||
this._healthy = true
|
||||
return connection
|
||||
})
|
||||
.catch(err => {
|
||||
if (this.pool.numUsed() + this.pool.numFree() <= 0) {
|
||||
this._healthy = false
|
||||
}
|
||||
throw err
|
||||
}),
|
||||
validate: this._poolValidate.bind(this),
|
||||
destroy: this._poolDestroy.bind(this),
|
||||
max: 10,
|
||||
min: 0,
|
||||
idleTimeoutMillis: 30000,
|
||||
propagateCreateError: true
|
||||
}, this.config.pool)
|
||||
)
|
||||
|
||||
this._connecting = false
|
||||
this._connected = true
|
||||
})
|
||||
}).then(() => {
|
||||
this._connectStack.forEach((cb) => {
|
||||
setImmediate(cb, null, this)
|
||||
})
|
||||
}).catch(err => {
|
||||
this._connecting = false
|
||||
this._connectStack.forEach((cb) => {
|
||||
setImmediate(cb, err)
|
||||
})
|
||||
}).then(() => {
|
||||
this._connectStack = []
|
||||
})
|
||||
}
|
||||
|
||||
get size () {
|
||||
return this.pool.numFree() + this.pool.numUsed() + this.pool.numPendingCreates()
|
||||
}
|
||||
|
||||
get available () {
|
||||
return this.pool.numFree()
|
||||
}
|
||||
|
||||
get pending () {
|
||||
return this.pool.numPendingAcquires()
|
||||
}
|
||||
|
||||
get borrowed () {
|
||||
return this.pool.numUsed()
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all active connections in the pool.
|
||||
*
|
||||
* @param {basicCallback} [callback] A callback which is called after connection has closed, or an error has occurred. If omited, method returns Promise.
|
||||
* @return {ConnectionPool|Promise}
|
||||
*/
|
||||
|
||||
close (callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this._close(callback)
|
||||
return this
|
||||
}
|
||||
|
||||
return new shared.Promise((resolve, reject) => {
|
||||
this._close(err => {
|
||||
if (err) return reject(err)
|
||||
resolve(this)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {basicCallback} callback
|
||||
*/
|
||||
|
||||
_close (callback) {
|
||||
// we don't allow pools in a connecting state to be closed because it means there are far too many
|
||||
// edge cases to deal with
|
||||
if (this._connecting) {
|
||||
debug('pool(%d): close called while connecting', IDS.get(this))
|
||||
setImmediate(callback, new ConnectionError('Cannot close a pool while it is connecting'))
|
||||
}
|
||||
|
||||
if (!this.pool) {
|
||||
debug('pool(%d): already closed, executing close callback immediately', IDS.get(this))
|
||||
return setImmediate(callback, null)
|
||||
}
|
||||
|
||||
this._closeStack.push(callback)
|
||||
|
||||
if (this.pool.destroyed) return
|
||||
|
||||
this._connecting = this._connected = this._healthy = false
|
||||
|
||||
this.pool.destroy().then(() => {
|
||||
debug('pool(%d): pool closed, removing pool reference and executing close callbacks', IDS.get(this))
|
||||
this.pool = null
|
||||
this._closeStack.forEach(cb => {
|
||||
setImmediate(cb, null)
|
||||
})
|
||||
}).catch(err => {
|
||||
this.pool = null
|
||||
this._closeStack.forEach(cb => {
|
||||
setImmediate(cb, err)
|
||||
})
|
||||
}).then(() => {
|
||||
this._closeStack = []
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConnectionPool
|
||||
138
node_modules/mssql/lib/base/index.js
generated
vendored
Normal file
138
node_modules/mssql/lib/base/index.js
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
'use strict'
|
||||
|
||||
const ConnectionPool = require('./connection-pool')
|
||||
const PreparedStatement = require('./prepared-statement')
|
||||
const Request = require('./request')
|
||||
const Transaction = require('./transaction')
|
||||
const { ConnectionError, TransactionError, RequestError, PreparedStatementError, MSSQLError } = require('../error')
|
||||
const shared = require('../shared')
|
||||
const Table = require('../table')
|
||||
const ISOLATION_LEVEL = require('../isolationlevel')
|
||||
const { TYPES } = require('../datatypes')
|
||||
const { connect, close, on, off, removeListener, query, batch } = require('../global-connection')
|
||||
|
||||
module.exports = {
|
||||
ConnectionPool,
|
||||
Transaction,
|
||||
Request,
|
||||
PreparedStatement,
|
||||
ConnectionError,
|
||||
TransactionError,
|
||||
RequestError,
|
||||
PreparedStatementError,
|
||||
MSSQLError,
|
||||
driver: shared.driver,
|
||||
exports: {
|
||||
ConnectionError,
|
||||
TransactionError,
|
||||
RequestError,
|
||||
PreparedStatementError,
|
||||
MSSQLError,
|
||||
Table,
|
||||
ISOLATION_LEVEL,
|
||||
TYPES,
|
||||
MAX: 65535, // (1 << 16) - 1
|
||||
map: shared.map,
|
||||
getTypeByValue: shared.getTypeByValue,
|
||||
connect,
|
||||
close,
|
||||
on,
|
||||
removeListener,
|
||||
off,
|
||||
query,
|
||||
batch
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(module.exports, 'Promise', {
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
return shared.Promise
|
||||
},
|
||||
set: (value) => {
|
||||
shared.Promise = value
|
||||
}
|
||||
})
|
||||
|
||||
Object.defineProperty(module.exports, 'valueHandler', {
|
||||
enumerable: true,
|
||||
value: shared.valueHandler,
|
||||
writable: false,
|
||||
configurable: false
|
||||
})
|
||||
|
||||
for (const key in TYPES) {
|
||||
const value = TYPES[key]
|
||||
module.exports.exports[key] = value
|
||||
module.exports.exports[key.toUpperCase()] = value
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback Request~requestCallback
|
||||
* @param {Error} err Error on error, otherwise null.
|
||||
* @param {Object} [result] Request result.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback Request~bulkCallback
|
||||
* @param {Error} err Error on error, otherwise null.
|
||||
* @param {Number} [rowsAffected] Number of affected rows.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback basicCallback
|
||||
* @param {Error} err Error on error, otherwise null.
|
||||
* @param {Connection} [connection] Acquired connection.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback acquireCallback
|
||||
* @param {Error} err Error on error, otherwise null.
|
||||
* @param {Connection} [connection] Acquired connection.
|
||||
* @param {Object} [config] Connection config
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dispatched after connection has established.
|
||||
* @event ConnectionPool#connect
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dispatched after connection has closed a pool (by calling close).
|
||||
* @event ConnectionPool#close
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dispatched when transaction begin.
|
||||
* @event Transaction#begin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dispatched on successful commit.
|
||||
* @event Transaction#commit
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dispatched on successful rollback.
|
||||
* @event Transaction#rollback
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dispatched when metadata for new recordset are parsed.
|
||||
* @event Request#recordset
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dispatched when new row is parsed.
|
||||
* @event Request#row
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dispatched when request is complete.
|
||||
* @event Request#done
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dispatched on error.
|
||||
* @event Request#error
|
||||
*/
|
||||
384
node_modules/mssql/lib/base/prepared-statement.js
generated
vendored
Normal file
384
node_modules/mssql/lib/base/prepared-statement.js
generated
vendored
Normal file
@@ -0,0 +1,384 @@
|
||||
'use strict'
|
||||
|
||||
const debug = require('debug')('mssql:base')
|
||||
const { EventEmitter } = require('node:events')
|
||||
const { IDS, objectHasProperty } = require('../utils')
|
||||
const globalConnection = require('../global-connection')
|
||||
const { TransactionError, PreparedStatementError } = require('../error')
|
||||
const shared = require('../shared')
|
||||
const { TYPES, declare } = require('../datatypes')
|
||||
|
||||
/**
|
||||
* Class PreparedStatement.
|
||||
*
|
||||
* IMPORTANT: Rememeber that each prepared statement means one reserved connection from the pool. Don't forget to unprepare a prepared statement!
|
||||
*
|
||||
* @property {String} statement Prepared SQL statement.
|
||||
*/
|
||||
|
||||
class PreparedStatement extends EventEmitter {
|
||||
/**
|
||||
* Creates a new Prepared Statement.
|
||||
*
|
||||
* @param {ConnectionPool|Transaction} [holder]
|
||||
*/
|
||||
|
||||
constructor (parent) {
|
||||
super()
|
||||
|
||||
IDS.add(this, 'PreparedStatement')
|
||||
debug('ps(%d): created', IDS.get(this))
|
||||
|
||||
this.parent = parent || globalConnection.pool
|
||||
this._handle = 0
|
||||
this.prepared = false
|
||||
this.parameters = {}
|
||||
}
|
||||
|
||||
get config () {
|
||||
return this.parent.config
|
||||
}
|
||||
|
||||
get connected () {
|
||||
return this.parent.connected
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire connection from connection pool.
|
||||
*
|
||||
* @param {Request} request Request.
|
||||
* @param {ConnectionPool~acquireCallback} [callback] A callback which is called after connection has established, or an error has occurred. If omited, method returns Promise.
|
||||
* @return {PreparedStatement|Promise}
|
||||
*/
|
||||
|
||||
acquire (request, callback) {
|
||||
if (!this._acquiredConnection) {
|
||||
setImmediate(callback, new PreparedStatementError('Statement is not prepared. Call prepare() first.', 'ENOTPREPARED'))
|
||||
return this
|
||||
}
|
||||
|
||||
if (this._activeRequest) {
|
||||
setImmediate(callback, new TransactionError("Can't acquire connection for the request. There is another request in progress.", 'EREQINPROG'))
|
||||
return this
|
||||
}
|
||||
|
||||
this._activeRequest = request
|
||||
setImmediate(callback, null, this._acquiredConnection, this._acquiredConfig)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Release connection back to the pool.
|
||||
*
|
||||
* @param {Connection} connection Previously acquired connection.
|
||||
* @return {PreparedStatement}
|
||||
*/
|
||||
|
||||
release (connection) {
|
||||
if (connection === this._acquiredConnection) {
|
||||
this._activeRequest = null
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an input parameter to the prepared statement.
|
||||
*
|
||||
* @param {String} name Name of the input parameter without @ char.
|
||||
* @param {*} type SQL data type of input parameter.
|
||||
* @return {PreparedStatement}
|
||||
*/
|
||||
|
||||
input (name, type) {
|
||||
if (/--| |\/\*|\*\/|'/.test(name)) {
|
||||
throw new PreparedStatementError(`SQL injection warning for param '${name}'`, 'EINJECT')
|
||||
}
|
||||
|
||||
if (arguments.length < 2) {
|
||||
throw new PreparedStatementError('Invalid number of arguments. 2 arguments expected.', 'EARGS')
|
||||
}
|
||||
|
||||
if (type instanceof Function) {
|
||||
type = type()
|
||||
}
|
||||
|
||||
if (objectHasProperty(this.parameters, name)) {
|
||||
throw new PreparedStatementError(`The parameter name ${name} has already been declared. Parameter names must be unique`, 'EDUPEPARAM')
|
||||
}
|
||||
|
||||
this.parameters[name] = {
|
||||
name,
|
||||
type: type.type,
|
||||
io: 1,
|
||||
length: type.length,
|
||||
scale: type.scale,
|
||||
precision: type.precision,
|
||||
tvpType: type.tvpType
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an input parameter on the request.
|
||||
*
|
||||
* @param {String} name Name of the input parameter without @ char.
|
||||
* @param {*} [type] SQL data type of input parameter. If you omit type, module automaticaly decide which SQL data type should be used based on JS data type.
|
||||
* @param {*} value Input parameter value. `undefined` and `NaN` values are automatically converted to `null` values.
|
||||
* @return {Request}
|
||||
*/
|
||||
|
||||
replaceInput (name, type, value) {
|
||||
delete this.parameters[name]
|
||||
|
||||
return this.input(name, type, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an output parameter to the prepared statement.
|
||||
*
|
||||
* @param {String} name Name of the output parameter without @ char.
|
||||
* @param {*} type SQL data type of output parameter.
|
||||
* @return {PreparedStatement}
|
||||
*/
|
||||
|
||||
output (name, type) {
|
||||
if (/--| |\/\*|\*\/|'/.test(name)) {
|
||||
throw new PreparedStatementError(`SQL injection warning for param '${name}'`, 'EINJECT')
|
||||
}
|
||||
|
||||
if (arguments.length < 2) {
|
||||
throw new PreparedStatementError('Invalid number of arguments. 2 arguments expected.', 'EARGS')
|
||||
}
|
||||
|
||||
if (type instanceof Function) type = type()
|
||||
|
||||
if (objectHasProperty(this.parameters, name)) {
|
||||
throw new PreparedStatementError(`The parameter name ${name} has already been declared. Parameter names must be unique`, 'EDUPEPARAM')
|
||||
}
|
||||
|
||||
this.parameters[name] = {
|
||||
name,
|
||||
type: type.type,
|
||||
io: 2,
|
||||
length: type.length,
|
||||
scale: type.scale,
|
||||
precision: type.precision
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an output parameter on the request.
|
||||
*
|
||||
* @param {String} name Name of the output parameter without @ char.
|
||||
* @param {*} type SQL data type of output parameter.
|
||||
* @return {PreparedStatement}
|
||||
*/
|
||||
|
||||
replaceOutput (name, type) {
|
||||
delete this.parameters[name]
|
||||
|
||||
return this.output(name, type)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a statement.
|
||||
*
|
||||
* @param {String} statement SQL statement to prepare.
|
||||
* @param {basicCallback} [callback] A callback which is called after preparation has completed, or an error has occurred. If omited, method returns Promise.
|
||||
* @return {PreparedStatement|Promise}
|
||||
*/
|
||||
|
||||
prepare (statement, callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this._prepare(statement, callback)
|
||||
return this
|
||||
}
|
||||
|
||||
return new shared.Promise((resolve, reject) => {
|
||||
this._prepare(statement, err => {
|
||||
if (err) return reject(err)
|
||||
resolve(this)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {String} statement
|
||||
* @param {basicCallback} callback
|
||||
*/
|
||||
|
||||
_prepare (statement, callback) {
|
||||
debug('ps(%d): prepare', IDS.get(this))
|
||||
|
||||
if (typeof statement === 'function') {
|
||||
callback = statement
|
||||
statement = undefined
|
||||
}
|
||||
|
||||
if (this.prepared) {
|
||||
return setImmediate(callback, new PreparedStatementError('Statement is already prepared.', 'EALREADYPREPARED'))
|
||||
}
|
||||
|
||||
this.statement = statement || this.statement
|
||||
|
||||
this.parent.acquire(this, (err, connection, config) => {
|
||||
if (err) return callback(err)
|
||||
|
||||
this._acquiredConnection = connection
|
||||
this._acquiredConfig = config
|
||||
|
||||
const req = new shared.driver.Request(this)
|
||||
req.stream = false
|
||||
req.output('handle', TYPES.Int)
|
||||
req.input('params', TYPES.NVarChar, ((() => {
|
||||
const result = []
|
||||
for (const name in this.parameters) {
|
||||
if (!objectHasProperty(this.parameters, name)) {
|
||||
continue
|
||||
}
|
||||
const param = this.parameters[name]
|
||||
result.push(`@${name} ${declare(param.type, param)}${param.io === 2 ? ' output' : ''}`)
|
||||
}
|
||||
return result
|
||||
})()).join(','))
|
||||
req.input('stmt', TYPES.NVarChar, this.statement)
|
||||
req.execute('sp_prepare', (err, result) => {
|
||||
if (err) {
|
||||
this.parent.release(this._acquiredConnection)
|
||||
this._acquiredConnection = null
|
||||
this._acquiredConfig = null
|
||||
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
debug('ps(%d): prepared', IDS.get(this))
|
||||
|
||||
this._handle = result.output.handle
|
||||
this.prepared = true
|
||||
|
||||
callback(null)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a prepared statement.
|
||||
*
|
||||
* @param {Object} values An object whose names correspond to the names of parameters that were added to the prepared statement before it was prepared.
|
||||
* @param {basicCallback} [callback] A callback which is called after execution has completed, or an error has occurred. If omited, method returns Promise.
|
||||
* @return {Request|Promise}
|
||||
*/
|
||||
|
||||
execute (values, callback) {
|
||||
if (this.stream || (typeof callback === 'function')) {
|
||||
return this._execute(values, callback)
|
||||
}
|
||||
|
||||
return new shared.Promise((resolve, reject) => {
|
||||
this._execute(values, (err, recordset) => {
|
||||
if (err) return reject(err)
|
||||
resolve(recordset)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} values
|
||||
* @param {basicCallback} callback
|
||||
*/
|
||||
|
||||
_execute (values, callback) {
|
||||
const req = new shared.driver.Request(this)
|
||||
req.stream = this.stream
|
||||
req.arrayRowMode = this.arrayRowMode
|
||||
req.input('handle', TYPES.Int, this._handle)
|
||||
|
||||
// copy parameters with new values
|
||||
for (const name in this.parameters) {
|
||||
if (!objectHasProperty(this.parameters, name)) {
|
||||
continue
|
||||
}
|
||||
const param = this.parameters[name]
|
||||
req.parameters[name] = {
|
||||
name,
|
||||
type: param.type,
|
||||
io: param.io,
|
||||
value: values[name],
|
||||
length: param.length,
|
||||
scale: param.scale,
|
||||
precision: param.precision
|
||||
}
|
||||
}
|
||||
|
||||
req.execute('sp_execute', (err, result) => {
|
||||
if (err) return callback(err)
|
||||
|
||||
callback(null, result)
|
||||
})
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
/**
|
||||
* Unprepare a prepared statement.
|
||||
*
|
||||
* @param {basicCallback} [callback] A callback which is called after unpreparation has completed, or an error has occurred. If omited, method returns Promise.
|
||||
* @return {PreparedStatement|Promise}
|
||||
*/
|
||||
|
||||
unprepare (callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this._unprepare(callback)
|
||||
return this
|
||||
}
|
||||
|
||||
return new shared.Promise((resolve, reject) => {
|
||||
this._unprepare(err => {
|
||||
if (err) return reject(err)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {basicCallback} callback
|
||||
*/
|
||||
|
||||
_unprepare (callback) {
|
||||
debug('ps(%d): unprepare', IDS.get(this))
|
||||
|
||||
if (!this.prepared) {
|
||||
return setImmediate(callback, new PreparedStatementError('Statement is not prepared. Call prepare() first.', 'ENOTPREPARED'))
|
||||
}
|
||||
|
||||
if (this._activeRequest) {
|
||||
return setImmediate(callback, new TransactionError("Can't unprepare the statement. There is a request in progress.", 'EREQINPROG'))
|
||||
}
|
||||
|
||||
const req = new shared.driver.Request(this)
|
||||
req.stream = false
|
||||
req.input('handle', TYPES.Int, this._handle)
|
||||
req.execute('sp_unprepare', err => {
|
||||
if (err) return callback(err)
|
||||
|
||||
this.parent.release(this._acquiredConnection)
|
||||
this._acquiredConnection = null
|
||||
this._acquiredConfig = null
|
||||
this._handle = 0
|
||||
this.prepared = false
|
||||
|
||||
debug('ps(%d): unprepared', IDS.get(this))
|
||||
|
||||
return callback(null)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PreparedStatement
|
||||
642
node_modules/mssql/lib/base/request.js
generated
vendored
Normal file
642
node_modules/mssql/lib/base/request.js
generated
vendored
Normal file
@@ -0,0 +1,642 @@
|
||||
'use strict'
|
||||
|
||||
const debug = require('debug')('mssql:base')
|
||||
const { EventEmitter } = require('node:events')
|
||||
const { Readable } = require('node:stream')
|
||||
const { IDS, objectHasProperty } = require('../utils')
|
||||
const globalConnection = require('../global-connection')
|
||||
const { RequestError, ConnectionError } = require('../error')
|
||||
const { TYPES } = require('../datatypes')
|
||||
const shared = require('../shared')
|
||||
|
||||
/**
|
||||
* Class Request.
|
||||
*
|
||||
* @property {Transaction} transaction Reference to transaction when request was created in transaction.
|
||||
* @property {*} parameters Collection of input and output parameters.
|
||||
* @property {Boolean} canceled `true` if request was canceled.
|
||||
*
|
||||
* @fires Request#recordset
|
||||
* @fires Request#row
|
||||
* @fires Request#done
|
||||
* @fires Request#error
|
||||
*/
|
||||
|
||||
class Request extends EventEmitter {
|
||||
/**
|
||||
* Create new Request.
|
||||
*
|
||||
* @param {Connection|ConnectionPool|Transaction|PreparedStatement} parent If omitted, global connection is used instead.
|
||||
*/
|
||||
|
||||
constructor (parent) {
|
||||
super()
|
||||
|
||||
IDS.add(this, 'Request')
|
||||
debug('request(%d): created', IDS.get(this))
|
||||
|
||||
this.canceled = false
|
||||
this._paused = false
|
||||
this.parent = parent || globalConnection.pool
|
||||
this.parameters = {}
|
||||
this.stream = null
|
||||
this.arrayRowMode = null
|
||||
}
|
||||
|
||||
get paused () {
|
||||
return this._paused
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate sql string and set input parameters from tagged template string.
|
||||
*
|
||||
* @param {Template literal} template
|
||||
* @return {String}
|
||||
*/
|
||||
template () {
|
||||
const values = Array.prototype.slice.call(arguments)
|
||||
const strings = values.shift()
|
||||
return this._template(strings, values)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch request from tagged template string.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} strings
|
||||
* @param {Array} values
|
||||
* @param {String} [method] If provided, method is automatically called with serialized command on this object.
|
||||
* @return {Request}
|
||||
*/
|
||||
|
||||
_template (strings, values, method) {
|
||||
const command = [strings[0]]
|
||||
|
||||
for (let index = 0; index < values.length; index++) {
|
||||
const value = values[index]
|
||||
// if value is an array, prepare each items as it's own comma separated parameter
|
||||
if (Array.isArray(value)) {
|
||||
for (let parameterIndex = 0; parameterIndex < value.length; parameterIndex++) {
|
||||
this.input(`param${index + 1}_${parameterIndex}`, value[parameterIndex])
|
||||
command.push(`@param${index + 1}_${parameterIndex}`)
|
||||
if (parameterIndex < value.length - 1) {
|
||||
command.push(', ')
|
||||
}
|
||||
}
|
||||
command.push(strings[index + 1])
|
||||
} else {
|
||||
this.input(`param${index + 1}`, value)
|
||||
command.push(`@param${index + 1}`, strings[index + 1])
|
||||
}
|
||||
}
|
||||
|
||||
if (method) {
|
||||
return this[method](command.join(''))
|
||||
} else {
|
||||
return command.join('')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an input parameter to the request.
|
||||
*
|
||||
* @param {String} name Name of the input parameter without @ char.
|
||||
* @param {*} [type] SQL data type of input parameter. If you omit type, module automaticaly decide which SQL data type should be used based on JS data type.
|
||||
* @param {*} value Input parameter value. `undefined` and `NaN` values are automatically converted to `null` values.
|
||||
* @return {Request}
|
||||
*/
|
||||
|
||||
input (name, type, value) {
|
||||
if (/--| |\/\*|\*\/|'/.test(name)) {
|
||||
throw new RequestError(`SQL injection warning for param '${name}'`, 'EINJECT')
|
||||
}
|
||||
|
||||
if (arguments.length < 2) {
|
||||
throw new RequestError('Invalid number of arguments. At least 2 arguments expected.', 'EARGS')
|
||||
} else if (arguments.length === 2) {
|
||||
value = type
|
||||
type = shared.getTypeByValue(value)
|
||||
}
|
||||
|
||||
// support for custom data types
|
||||
if (value && typeof value.valueOf === 'function' && !(value instanceof Date)) value = value.valueOf()
|
||||
|
||||
if (value === undefined) value = null // undefined to null
|
||||
if (typeof value === 'number' && isNaN(value)) value = null // NaN to null
|
||||
if (type instanceof Function) type = type()
|
||||
|
||||
if (objectHasProperty(this.parameters, name)) {
|
||||
throw new RequestError(`The parameter name ${name} has already been declared. Parameter names must be unique`, 'EDUPEPARAM')
|
||||
}
|
||||
|
||||
this.parameters[name] = {
|
||||
name,
|
||||
type: type.type,
|
||||
io: 1,
|
||||
value,
|
||||
length: type.length,
|
||||
scale: type.scale,
|
||||
precision: type.precision,
|
||||
tvpType: type.tvpType
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an input parameter on the request.
|
||||
*
|
||||
* @param {String} name Name of the input parameter without @ char.
|
||||
* @param {*} [type] SQL data type of input parameter. If you omit type, module automaticaly decide which SQL data type should be used based on JS data type.
|
||||
* @param {*} value Input parameter value. `undefined` and `NaN` values are automatically converted to `null` values.
|
||||
* @return {Request}
|
||||
*/
|
||||
|
||||
replaceInput (name, type, value) {
|
||||
delete this.parameters[name]
|
||||
|
||||
return this.input(name, type, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an output parameter to the request.
|
||||
*
|
||||
* @param {String} name Name of the output parameter without @ char.
|
||||
* @param {*} type SQL data type of output parameter.
|
||||
* @param {*} [value] Output parameter value initial value. `undefined` and `NaN` values are automatically converted to `null` values. Optional.
|
||||
* @return {Request}
|
||||
*/
|
||||
|
||||
output (name, type, value) {
|
||||
if (!type) { type = TYPES.NVarChar }
|
||||
|
||||
if (/--| |\/\*|\*\/|'/.test(name)) {
|
||||
throw new RequestError(`SQL injection warning for param '${name}'`, 'EINJECT')
|
||||
}
|
||||
|
||||
if ((type === TYPES.Text) || (type === TYPES.NText) || (type === TYPES.Image)) {
|
||||
throw new RequestError('Deprecated types (Text, NText, Image) are not supported as OUTPUT parameters.', 'EDEPRECATED')
|
||||
}
|
||||
|
||||
// support for custom data types
|
||||
if (value && typeof value.valueOf === 'function' && !(value instanceof Date)) value = value.valueOf()
|
||||
|
||||
if (value === undefined) value = null // undefined to null
|
||||
if (typeof value === 'number' && isNaN(value)) value = null // NaN to null
|
||||
if (type instanceof Function) type = type()
|
||||
|
||||
if (objectHasProperty(this.parameters, name)) {
|
||||
throw new RequestError(`The parameter name ${name} has already been declared. Parameter names must be unique`, 'EDUPEPARAM')
|
||||
}
|
||||
|
||||
this.parameters[name] = {
|
||||
name,
|
||||
type: type.type,
|
||||
io: 2,
|
||||
value,
|
||||
length: type.length,
|
||||
scale: type.scale,
|
||||
precision: type.precision
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an output parameter on the request.
|
||||
*
|
||||
* @param {String} name Name of the output parameter without @ char.
|
||||
* @param {*} type SQL data type of output parameter.
|
||||
* @param {*} [value] Output parameter value initial value. `undefined` and `NaN` values are automatically converted to `null` values. Optional.
|
||||
* @return {Request}
|
||||
*/
|
||||
|
||||
replaceOutput (name, type, value) {
|
||||
delete this.parameters[name]
|
||||
|
||||
return this.output(name, type, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the SQL batch.
|
||||
*
|
||||
* @param {String} batch T-SQL batch 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 (batch, callback) {
|
||||
if (this.stream === null && this.parent) this.stream = this.parent.config.stream
|
||||
if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
|
||||
this.rowsAffected = 0
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
this._batch(batch, (err, recordsets, output, rowsAffected) => {
|
||||
if (this.stream) {
|
||||
if (err) this.emit('error', err)
|
||||
err = null
|
||||
|
||||
this.emit('done', {
|
||||
output,
|
||||
rowsAffected
|
||||
})
|
||||
}
|
||||
|
||||
if (err) return callback(err)
|
||||
callback(null, {
|
||||
recordsets,
|
||||
recordset: recordsets && recordsets[0],
|
||||
output,
|
||||
rowsAffected
|
||||
})
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
// Check is method was called as tagged template
|
||||
if (typeof batch === 'object') {
|
||||
const values = Array.prototype.slice.call(arguments)
|
||||
const strings = values.shift()
|
||||
batch = this._template(strings, values)
|
||||
}
|
||||
|
||||
return new shared.Promise((resolve, reject) => {
|
||||
this._batch(batch, (err, recordsets, output, rowsAffected) => {
|
||||
if (this.stream) {
|
||||
if (err) this.emit('error', err)
|
||||
err = null
|
||||
|
||||
this.emit('done', {
|
||||
output,
|
||||
rowsAffected
|
||||
})
|
||||
}
|
||||
|
||||
if (err) return reject(err)
|
||||
resolve({
|
||||
recordsets,
|
||||
recordset: recordsets && recordsets[0],
|
||||
output,
|
||||
rowsAffected
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {String} batch
|
||||
* @param {Request~requestCallback} callback
|
||||
*/
|
||||
|
||||
_batch (batch, callback) {
|
||||
if (!this.parent) {
|
||||
return setImmediate(callback, new RequestError('No connection is specified for that request.', 'ENOCONN'))
|
||||
}
|
||||
|
||||
if (!this.parent.connected) {
|
||||
return setImmediate(callback, new ConnectionError('Connection is closed.', 'ECONNCLOSED'))
|
||||
}
|
||||
|
||||
this.canceled = false
|
||||
setImmediate(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk load.
|
||||
*
|
||||
* @param {Table} table SQL table.
|
||||
* @param {object} [options] Options to be passed to the underlying driver (tedious only).
|
||||
* @param {Request~bulkCallback} [callback] A callback which is called after bulk load has completed, or an error has occurred. If omited, method returns Promise.
|
||||
* @return {Request|Promise}
|
||||
*/
|
||||
|
||||
bulk (table, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options
|
||||
options = {}
|
||||
} else if (typeof options === 'undefined') {
|
||||
options = {}
|
||||
}
|
||||
|
||||
if (this.stream === null && this.parent) this.stream = this.parent.config.stream
|
||||
if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
|
||||
|
||||
if (this.stream || typeof callback === 'function') {
|
||||
this._bulk(table, options, (err, rowsAffected) => {
|
||||
if (this.stream) {
|
||||
if (err) this.emit('error', err)
|
||||
return this.emit('done', {
|
||||
rowsAffected
|
||||
})
|
||||
}
|
||||
|
||||
if (err) return callback(err)
|
||||
callback(null, {
|
||||
rowsAffected
|
||||
})
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
return new shared.Promise((resolve, reject) => {
|
||||
this._bulk(table, options, (err, rowsAffected) => {
|
||||
if (err) return reject(err)
|
||||
resolve({
|
||||
rowsAffected
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Table} table
|
||||
* @param {object} options
|
||||
* @param {Request~bulkCallback} callback
|
||||
*/
|
||||
|
||||
_bulk (table, options, callback) {
|
||||
if (!this.parent) {
|
||||
return setImmediate(callback, new RequestError('No connection is specified for that request.', 'ENOCONN'))
|
||||
}
|
||||
|
||||
if (!this.parent.connected) {
|
||||
return setImmediate(callback, new ConnectionError('Connection is closed.', 'ECONNCLOSED'))
|
||||
}
|
||||
|
||||
this.canceled = false
|
||||
setImmediate(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap original request in a Readable stream that supports back pressure and return.
|
||||
* It also sets request to `stream` mode and pulls all rows from all recordsets to a given stream.
|
||||
*
|
||||
* @param {Object} streamOptions - optional options to configure the readable stream with like highWaterMark
|
||||
* @return {Stream}
|
||||
*/
|
||||
toReadableStream (streamOptions = {}) {
|
||||
this.stream = true
|
||||
this.pause()
|
||||
const readableStream = new Readable({
|
||||
...streamOptions,
|
||||
objectMode: true,
|
||||
read: (/* size */) => {
|
||||
this.resume()
|
||||
}
|
||||
})
|
||||
this.on('row', (row) => {
|
||||
if (!readableStream.push(row)) {
|
||||
this.pause()
|
||||
}
|
||||
})
|
||||
this.on('error', (error) => {
|
||||
readableStream.emit('error', error)
|
||||
})
|
||||
this.on('done', () => {
|
||||
readableStream.push(null)
|
||||
})
|
||||
return readableStream
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap original request in a Readable stream that supports back pressure and pipe to the Writable stream.
|
||||
* It also sets request to `stream` mode and pulls all rows from all recordsets to a given stream.
|
||||
*
|
||||
* @param {Stream} stream Stream to pipe data into.
|
||||
* @return {Stream}
|
||||
*/
|
||||
pipe (writableStream) {
|
||||
const readableStream = this.toReadableStream()
|
||||
return readableStream.pipe(writableStream)
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the SQL command.
|
||||
*
|
||||
* @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 (command, callback) {
|
||||
if (this.stream === null && this.parent) this.stream = this.parent.config.stream
|
||||
if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
|
||||
this.rowsAffected = 0
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
this._query(command, (err, recordsets, output, rowsAffected, columns) => {
|
||||
if (this.stream) {
|
||||
if (err) this.emit('error', err)
|
||||
err = null
|
||||
|
||||
this.emit('done', {
|
||||
output,
|
||||
rowsAffected
|
||||
})
|
||||
}
|
||||
|
||||
if (err) return callback(err)
|
||||
const result = {
|
||||
recordsets,
|
||||
recordset: recordsets && recordsets[0],
|
||||
output,
|
||||
rowsAffected
|
||||
}
|
||||
if (this.arrayRowMode) result.columns = columns
|
||||
callback(null, result)
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
// Check is method was called as tagged template
|
||||
if (typeof command === 'object') {
|
||||
const values = Array.prototype.slice.call(arguments)
|
||||
const strings = values.shift()
|
||||
command = this._template(strings, values)
|
||||
}
|
||||
|
||||
return new shared.Promise((resolve, reject) => {
|
||||
this._query(command, (err, recordsets, output, rowsAffected, columns) => {
|
||||
if (this.stream) {
|
||||
if (err) this.emit('error', err)
|
||||
err = null
|
||||
|
||||
this.emit('done', {
|
||||
output,
|
||||
rowsAffected
|
||||
})
|
||||
}
|
||||
|
||||
if (err) return reject(err)
|
||||
const result = {
|
||||
recordsets,
|
||||
recordset: recordsets && recordsets[0],
|
||||
output,
|
||||
rowsAffected
|
||||
}
|
||||
if (this.arrayRowMode) result.columns = columns
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {String} command
|
||||
* @param {Request~bulkCallback} callback
|
||||
*/
|
||||
|
||||
_query (command, callback) {
|
||||
if (!this.parent) {
|
||||
return setImmediate(callback, new RequestError('No connection is specified for that request.', 'ENOCONN'))
|
||||
}
|
||||
|
||||
if (!this.parent.connected) {
|
||||
return setImmediate(callback, new ConnectionError('Connection is closed.', 'ECONNCLOSED'))
|
||||
}
|
||||
|
||||
this.canceled = false
|
||||
setImmediate(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a stored procedure.
|
||||
*
|
||||
* @param {String} procedure Name of the stored procedure 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}
|
||||
*/
|
||||
|
||||
execute (command, callback) {
|
||||
if (this.stream === null && this.parent) this.stream = this.parent.config.stream
|
||||
if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
|
||||
this.rowsAffected = 0
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
this._execute(command, (err, recordsets, output, returnValue, rowsAffected, columns) => {
|
||||
if (this.stream) {
|
||||
if (err) this.emit('error', err)
|
||||
err = null
|
||||
|
||||
this.emit('done', {
|
||||
output,
|
||||
rowsAffected,
|
||||
returnValue
|
||||
})
|
||||
}
|
||||
|
||||
if (err) return callback(err)
|
||||
const result = {
|
||||
recordsets,
|
||||
recordset: recordsets && recordsets[0],
|
||||
output,
|
||||
rowsAffected,
|
||||
returnValue
|
||||
}
|
||||
if (this.arrayRowMode) result.columns = columns
|
||||
callback(null, result)
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
return new shared.Promise((resolve, reject) => {
|
||||
this._execute(command, (err, recordsets, output, returnValue, rowsAffected, columns) => {
|
||||
if (this.stream) {
|
||||
if (err) this.emit('error', err)
|
||||
err = null
|
||||
|
||||
this.emit('done', {
|
||||
output,
|
||||
rowsAffected,
|
||||
returnValue
|
||||
})
|
||||
}
|
||||
|
||||
if (err) return reject(err)
|
||||
const result = {
|
||||
recordsets,
|
||||
recordset: recordsets && recordsets[0],
|
||||
output,
|
||||
rowsAffected,
|
||||
returnValue
|
||||
}
|
||||
if (this.arrayRowMode) result.columns = columns
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {String} procedure
|
||||
* @param {Request~bulkCallback} callback
|
||||
*/
|
||||
|
||||
_execute (procedure, callback) {
|
||||
if (!this.parent) {
|
||||
return setImmediate(callback, new RequestError('No connection is specified for that request.', 'ENOCONN'))
|
||||
}
|
||||
|
||||
if (!this.parent.connected) {
|
||||
return setImmediate(callback, new ConnectionError('Connection is closed.', 'ECONNCLOSED'))
|
||||
}
|
||||
|
||||
this.canceled = false
|
||||
setImmediate(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel currently executed request.
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
cancel () {
|
||||
this._cancel()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
||||
_cancel () {
|
||||
this.canceled = true
|
||||
}
|
||||
|
||||
pause () {
|
||||
if (this.stream) {
|
||||
this._pause()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
_pause () {
|
||||
this._paused = true
|
||||
}
|
||||
|
||||
resume () {
|
||||
if (this.stream) {
|
||||
this._resume()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
_resume () {
|
||||
this._paused = false
|
||||
}
|
||||
|
||||
_setCurrentRequest (request) {
|
||||
this._currentRequest = request
|
||||
if (this._paused) {
|
||||
this.pause()
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Request
|
||||
265
node_modules/mssql/lib/base/transaction.js
generated
vendored
Normal file
265
node_modules/mssql/lib/base/transaction.js
generated
vendored
Normal file
@@ -0,0 +1,265 @@
|
||||
'use strict'
|
||||
|
||||
const debug = require('debug')('mssql:base')
|
||||
const { EventEmitter } = require('node:events')
|
||||
const { IDS } = require('../utils')
|
||||
const globalConnection = require('../global-connection')
|
||||
const { TransactionError } = require('../error')
|
||||
const shared = require('../shared')
|
||||
const ISOLATION_LEVEL = require('../isolationlevel')
|
||||
|
||||
/**
|
||||
* Class Transaction.
|
||||
*
|
||||
* @property {Number} isolationLevel Controls the locking and row versioning behavior of TSQL statements issued by a connection. READ_COMMITTED by default.
|
||||
* @property {String} name Transaction name. Empty string by default.
|
||||
*
|
||||
* @fires Transaction#begin
|
||||
* @fires Transaction#commit
|
||||
* @fires Transaction#rollback
|
||||
*/
|
||||
|
||||
class Transaction extends EventEmitter {
|
||||
/**
|
||||
* Create new Transaction.
|
||||
*
|
||||
* @param {Connection} [parent] If ommited, global connection is used instead.
|
||||
*/
|
||||
|
||||
constructor (parent) {
|
||||
super()
|
||||
|
||||
IDS.add(this, 'Transaction')
|
||||
debug('transaction(%d): created', IDS.get(this))
|
||||
|
||||
this.parent = parent || globalConnection.pool
|
||||
this.isolationLevel = Transaction.defaultIsolationLevel
|
||||
this.name = ''
|
||||
}
|
||||
|
||||
get config () {
|
||||
return this.parent.config
|
||||
}
|
||||
|
||||
get connected () {
|
||||
return this.parent.connected
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire connection from connection pool.
|
||||
*
|
||||
* @param {Request} request Request.
|
||||
* @param {ConnectionPool~acquireCallback} [callback] A callback which is called after connection has established, or an error has occurred. If omited, method returns Promise.
|
||||
* @return {Transaction|Promise}
|
||||
*/
|
||||
|
||||
acquire (request, callback) {
|
||||
if (!this._acquiredConnection) {
|
||||
setImmediate(callback, new TransactionError('Transaction has not begun. Call begin() first.', 'ENOTBEGUN'))
|
||||
return this
|
||||
}
|
||||
|
||||
if (this._activeRequest) {
|
||||
setImmediate(callback, new TransactionError("Can't acquire connection for the request. There is another request in progress.", 'EREQINPROG'))
|
||||
return this
|
||||
}
|
||||
|
||||
this._activeRequest = request
|
||||
setImmediate(callback, null, this._acquiredConnection, this._acquiredConfig)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Release connection back to the pool.
|
||||
*
|
||||
* @param {Connection} connection Previously acquired connection.
|
||||
* @return {Transaction}
|
||||
*/
|
||||
|
||||
release (connection) {
|
||||
if (connection === this._acquiredConnection) {
|
||||
this._activeRequest = null
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
|
||||
begin (isolationLevel, callback) {
|
||||
if (isolationLevel instanceof Function) {
|
||||
callback = isolationLevel
|
||||
isolationLevel = undefined
|
||||
}
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
this._begin(isolationLevel, err => {
|
||||
if (!err) {
|
||||
this.emit('begin')
|
||||
}
|
||||
callback(err)
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
return new shared.Promise((resolve, reject) => {
|
||||
this._begin(isolationLevel, err => {
|
||||
if (err) return reject(err)
|
||||
this.emit('begin')
|
||||
resolve(this)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Number} [isolationLevel]
|
||||
* @param {basicCallback} [callback]
|
||||
* @return {Transaction}
|
||||
*/
|
||||
|
||||
_begin (isolationLevel, callback) {
|
||||
if (this._acquiredConnection) {
|
||||
return setImmediate(callback, new TransactionError('Transaction has already begun.', 'EALREADYBEGUN'))
|
||||
}
|
||||
|
||||
this._aborted = false
|
||||
this._rollbackRequested = false
|
||||
if (isolationLevel) {
|
||||
if (Object.keys(ISOLATION_LEVEL).some(key => {
|
||||
return ISOLATION_LEVEL[key] === isolationLevel
|
||||
})) {
|
||||
this.isolationLevel = isolationLevel
|
||||
} else {
|
||||
throw new TransactionError('Invalid isolation level.')
|
||||
}
|
||||
}
|
||||
|
||||
setImmediate(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
|
||||
commit (callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this._commit(err => {
|
||||
if (!err) {
|
||||
this.emit('commit')
|
||||
}
|
||||
callback(err)
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
return new shared.Promise((resolve, reject) => {
|
||||
this._commit(err => {
|
||||
if (err) return reject(err)
|
||||
this.emit('commit')
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {basicCallback} [callback]
|
||||
* @return {Transaction}
|
||||
*/
|
||||
|
||||
_commit (callback) {
|
||||
if (this._aborted) {
|
||||
return setImmediate(callback, new TransactionError('Transaction has been aborted.', 'EABORT'))
|
||||
}
|
||||
|
||||
if (!this._acquiredConnection) {
|
||||
return setImmediate(callback, new TransactionError('Transaction has not begun. Call begin() first.', 'ENOTBEGUN'))
|
||||
}
|
||||
|
||||
if (this._activeRequest) {
|
||||
return setImmediate(callback, new TransactionError("Can't commit transaction. There is a request in progress.", 'EREQINPROG'))
|
||||
}
|
||||
|
||||
setImmediate(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new request using this transaction.
|
||||
*
|
||||
* @return {Request}
|
||||
*/
|
||||
|
||||
request () {
|
||||
return new shared.driver.Request(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
|
||||
rollback (callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this._rollback(err => {
|
||||
if (!err) {
|
||||
this.emit('rollback', this._aborted)
|
||||
}
|
||||
callback(err)
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
return new shared.Promise((resolve, reject) => {
|
||||
return this._rollback(err => {
|
||||
if (err) return reject(err)
|
||||
this.emit('rollback', this._aborted)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {basicCallback} [callback]
|
||||
* @return {Transaction}
|
||||
*/
|
||||
|
||||
_rollback (callback) {
|
||||
if (this._aborted) {
|
||||
return setImmediate(callback, new TransactionError('Transaction has been aborted.', 'EABORT'))
|
||||
}
|
||||
|
||||
if (!this._acquiredConnection) {
|
||||
return setImmediate(callback, new TransactionError('Transaction has not begun. Call begin() first.', 'ENOTBEGUN'))
|
||||
}
|
||||
|
||||
if (this._activeRequest) {
|
||||
return setImmediate(callback, new TransactionError("Can't rollback transaction. There is a request in progress.", 'EREQINPROG'))
|
||||
}
|
||||
|
||||
this._rollbackRequested = true
|
||||
|
||||
setImmediate(callback)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default isolation level used for any transactions that don't explicitly specify an isolation level.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
Transaction.defaultIsolationLevel = ISOLATION_LEVEL.READ_COMMITTED
|
||||
|
||||
module.exports = Transaction
|
||||
Reference in New Issue
Block a user