Commit iniziale

This commit is contained in:
Paolo A
2025-02-18 22:59:07 +00:00
commit 4bbf35cefb
6879 changed files with 623784 additions and 0 deletions

642
node_modules/mssql/lib/base/request.js generated vendored Normal file
View 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