mssql2
Version:
Microsoft SQL Server client for Node.js (fork)
1,654 lines (1,395 loc) • 59.2 kB
JavaScript
// Generated by CoffeeScript 1.10.0
(function() {
var Connection, ConnectionError, ConnectionString, DRIVERS, EventEmitter, ISOLATION_LEVEL, PreparedStatement, PreparedStatementError, Request, RequestError, TYPES, Table, Transaction, TransactionError, declare, fs, getTypeByValue, global_connection, key, map, ref, ref1, util, value,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
slice = [].slice,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
EventEmitter = require('events').EventEmitter;
util = require('util');
fs = require('fs');
ref = require('./datatypes'), TYPES = ref.TYPES, declare = ref.declare;
ISOLATION_LEVEL = require('./isolationlevel');
DRIVERS = ['msnodesql', 'tedious', 'tds', 'msnodesqlv8'];
Table = require('./table');
ConnectionString = require('./connectionstring');
global_connection = null;
map = [];
/*
Register you own type map.
**Example:**
```
sql.map.register(MyClass, sql.Text);
```
You can also overwrite default type map.
```
sql.map.register(Number, sql.BigInt);
```
@path module.exports.map
@param {*} jstype JS data type.
@param {*} sqltype SQL data type.
*/
map.register = function(jstype, sqltype) {
var i, index, item, len;
for (index = i = 0, len = this.length; i < len; index = ++i) {
item = this[index];
if (!(item.js === jstype)) {
continue;
}
this.splice(index, 1);
break;
}
this.push({
js: jstype,
sql: sqltype
});
return null;
};
map.register(String, TYPES.NVarChar);
map.register(Number, TYPES.Int);
map.register(Boolean, TYPES.Bit);
map.register(Date, TYPES.DateTime);
map.register(Buffer, TYPES.VarBinary);
map.register(Table, TYPES.TVP);
/*
@ignore
*/
getTypeByValue = function(value) {
var i, item, j, k, l, len, len1, len2, len3;
if (value === null || value === void 0) {
return TYPES.NVarChar;
}
switch (typeof value) {
case 'string':
for (i = 0, len = map.length; i < len; i++) {
item = map[i];
if (item.js === String) {
return item.sql;
}
}
return TYPES.NVarChar;
case 'number':
for (j = 0, len1 = map.length; j < len1; j++) {
item = map[j];
if (item.js === Number) {
return item.sql;
}
}
return TYPES.Int;
case 'boolean':
for (k = 0, len2 = map.length; k < len2; k++) {
item = map[k];
if (item.js === Boolean) {
return item.sql;
}
}
return TYPES.Bit;
case 'object':
for (l = 0, len3 = map.length; l < len3; l++) {
item = map[l];
if (value instanceof item.js) {
return item.sql;
}
}
return TYPES.NVarChar;
default:
return TYPES.NVarChar;
}
};
/*
Class Connection.
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.
@property {*} driver Reference to configured Driver.
@event connect Dispatched after connection has established.
@event close Dispatched after connection has closed a pool (by calling close).
*/
Connection = (function(superClass) {
extend(Connection, superClass);
Connection.prototype.connected = false;
Connection.prototype.connecting = false;
Connection.prototype.driver = null;
/*
Create new Connection.
@param {Object|String} config Connection configuration object or connection string.
@callback [callback] A callback which is called after connection has established, or an error has occurred.
@param {Error} err Error on error, otherwise null.
*/
function Connection(config1, callback) {
var base, base1, base2, base3, base4, err, error, ex, ref1;
this.config = config1;
if ('string' === typeof this.config) {
try {
this.config = ConnectionString.resolve(this.config);
} catch (error) {
ex = error;
if (callback) {
return callback(ex);
} else {
throw ex;
}
}
}
if ((base = this.config).driver == null) {
base.driver = 'tedious';
}
if ((base1 = this.config).port == null) {
base1.port = 1433;
}
if ((base2 = this.config).options == null) {
base2.options = {};
}
if ((base3 = this.config).stream == null) {
base3.stream = false;
}
if ((base4 = this.config).parseJSON == null) {
base4.parseJSON = false;
}
if (/^(.*)\\(.*)$/.exec(this.config.server)) {
this.config.server = RegExp.$1;
this.config.options.instanceName = RegExp.$2;
}
if (ref1 = this.config.driver, indexOf.call(DRIVERS, ref1) >= 0) {
this.driver = this.initializeDriver(require("./" + this.config.driver));
} else if (typeof this.config.driver === 'function') {
this.driver = this.initializeDriver(this.config.driver);
} else {
err = new ConnectionError("Unknown driver " + this.config.driver + "!", 'EDRIVER');
if (callback) {
return callback(err);
} else {
throw err;
}
}
if (module.exports.fix) {
this.driver.fix();
}
if (callback) {
this.connect(callback);
}
}
/*
Write message to debug stream.
*/
Connection.prototype._debug = function(msg) {
var ref1;
return (ref1 = this._debugStream) != null ? ref1.write((String(msg).replace(/\x1B\[[0-9;]*m/g, '')) + "\n") : void 0;
};
/*
Initializes driver for this connection. Separated from constructor and used by co-mssql.
@private
@param {Function} driver Loaded driver.
@returns {Connection}
*/
Connection.prototype.initializeDriver = function(driver) {
return driver(Connection, Transaction, Request, ConnectionError, TransactionError, RequestError);
};
/*
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.
@callback [callback] A callback which is called after connection has established, or an error has occurred. If omited, method returns Promise.
@param {Error} err Error on error, otherwise null.
@returns {Connection|Promise}
*/
Connection.prototype.connect = function(callback) {
if (callback != null) {
return this._connect(callback);
}
return new module.exports.Promise((function(_this) {
return function(resolve, reject) {
return _this._connect(function(err) {
if (err) {
return reject(err);
}
return resolve(_this);
});
};
})(this));
};
Connection.prototype._connect = function(callback) {
var go;
if (!this.driver) {
return callback(new ConnectionError("Connection was closed. Create a new instance."));
}
if (this.connected) {
return callback(new ConnectionError("Database is already connected! Call close before connecting to different database.", 'EALREADYCONNECTED'));
}
if (this.connecting) {
return callback(new ConnectionError("Already connecting to database! Call close before connecting to different database.", 'EALREADYCONNECTING'));
}
go = (function(_this) {
return function() {
_this.connecting = true;
return _this.driver.Connection.prototype.connect.call(_this, _this.config, function(err) {
if (!_this.connecting) {
return;
}
_this.connecting = false;
if (err) {
if (_this._debugStream) {
_this._debugStream.removeAllListeners();
_this._debugStream.end();
_this._debugStream = null;
}
} else {
_this.connected = true;
_this.emit('connect');
}
return callback(err);
});
};
})(this);
if (this.config.debug) {
this._debugStream = fs.createWriteStream("./mssql_debug_" + (Date.now()) + ".log");
this._debugStream.once('open', go);
this._debugStream.on('error', function(err) {
if (this.connecting || this.connected) {
return console.error(err.stack);
} else {
this._debugStream.removeListener('open', go);
return callback(new ConnectionError("Failed to open debug stream. " + err.message, 'EDEBUG'));
}
});
} else {
go();
}
return this;
};
/*
Close all active connections in the pool.
@callback [callback] A callback which is called after connection has closed, or an error has occurred. If omited, method returns Promise.
@param {Error} err Error on error, otherwise null.
@returns {Connection|Promise}
*/
Connection.prototype.close = function(callback) {
if (callback != null) {
return this._close(callback);
}
return new module.exports.Promise((function(_this) {
return function(resolve, reject) {
return _this._close(function(err) {
if (err) {
return reject(err);
}
return resolve();
});
};
})(this));
};
Connection.prototype._close = function(callback) {
if (this._debugStream) {
this._debugStream.removeAllListeners();
this._debugStream.end();
this._debugStream = null;
}
if (this.connecting) {
this.connecting = false;
this.driver.Connection.prototype.close.call(this, (function(_this) {
return function(err) {
return callback(err);
};
})(this));
this.driver = null;
} else if (this.connected) {
this.connected = false;
this.driver.Connection.prototype.close.call(this, (function(_this) {
return function(err) {
if (!err) {
_this.connected = false;
_this.emit('close');
}
return callback(err);
};
})(this));
this.driver = null;
} else {
callback(null);
}
return this;
};
/*
Returns new request using this connection.
@returns {Request}
*/
Connection.prototype.request = function() {
return new Request(this);
};
/*
Returns new transaction using this connection.
@returns {Transaction}
*/
Connection.prototype.transaction = function() {
return new Transaction(this);
};
/*
Creates a new query using this connection from a tagged template string.
@param {Array} strings Array of string literals.
@param {...*} keys Values.
@returns {Request}
*/
Connection.prototype.query = function() {
var strings, values;
strings = arguments[0], values = 2 <= arguments.length ? slice.call(arguments, 1) : [];
return new Request(this)._template('query', strings, values);
};
/*
Creates a new batch using this connection from a tagged template string.
@param {Array} strings Array of string literals.
@param {...*} keys Values.
@returns {Request}
*/
Connection.prototype.batch = function() {
var strings, values;
strings = arguments[0], values = 2 <= arguments.length ? slice.call(arguments, 1) : [];
return new Request(this)._template('batch', strings, values);
};
return Connection;
})(EventEmitter);
/*
Class PreparedStatement.
IMPORTANT: Rememeber that each prepared statement means one reserved connection from the pool. Don't forget to unprepare a prepared statement!
@property {Connection} connection Reference to used connection.
@property {Boolean} multiple If `true`, `execute` will handle multiple recordsets.
@property {String} statement Prepared SQL statement.
@property {Request} lastRequest References instance of most recent Request created by executing a statement.
*/
PreparedStatement = (function(superClass) {
extend(PreparedStatement, superClass);
PreparedStatement.prototype._pooledConnection = null;
PreparedStatement.prototype._queue = null;
PreparedStatement.prototype._working = false;
PreparedStatement.prototype._handle = 0;
PreparedStatement.prototype.connection = null;
PreparedStatement.prototype.transaction = null;
PreparedStatement.prototype.prepared = false;
PreparedStatement.prototype.statement = null;
PreparedStatement.prototype.parameters = null;
PreparedStatement.prototype.multiple = false;
PreparedStatement.prototype.stream = null;
PreparedStatement.prototype.lastRequest = null;
/*
Create new Prepared Statement.
@param {String} statement SQL statement.
@param {Connection} [connection] If ommited, global connection is used instead.
*/
function PreparedStatement(connection) {
if (connection instanceof Transaction) {
this.transaction = connection;
this.connection = connection.connection;
} else if (connection instanceof Connection) {
this.connection = connection;
} else {
this.connection = global_connection;
}
this._queue = [];
this.parameters = {};
}
/*
Add an input parameter to the prepared statement.
**Example:**
```
statement.input('input_parameter', sql.Int);
statement.input('input_parameter', sql.VarChar(50));
```
@param {String} name Name of the input parameter without @ char.
@param {*} type SQL data type of input parameter.
@returns {PreparedStatement}
*/
PreparedStatement.prototype.input = function(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();
}
this.parameters[name] = {
name: name,
type: type.type,
io: 1,
length: type.length,
scale: type.scale,
precision: type.precision,
tvpType: type.tvpType
};
return this;
};
/*
Add an output parameter to the prepared statement.
**Example:**
```
statement.output('output_parameter', sql.Int);
statement.output('output_parameter', sql.VarChar(50));
```
@param {String} name Name of the output parameter without @ char.
@param {*} type SQL data type of output parameter.
@returns {PreparedStatement}
*/
PreparedStatement.prototype.output = function(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();
}
this.parameters[name] = {
name: name,
type: type.type,
io: 2,
length: type.length,
scale: type.scale,
precision: type.precision
};
return this;
};
/*
Prepare a statement.
@property {String} [statement] SQL statement to prepare.
@callback [callback] A callback which is called after preparation has completed, or an error has occurred. If omited, method returns Promise.
@param {Error} err Error on error, otherwise null.
@returns {PreparedStatement|Promise}
*/
PreparedStatement.prototype.prepare = function(statement, callback) {
if (callback != null) {
return this._prepare(statement, callback);
}
return new module.exports.Promise((function(_this) {
return function(resolve, reject) {
return _this._prepare(statement, function(err) {
if (err) {
return reject(err);
}
return resolve(_this);
});
};
})(this));
};
PreparedStatement.prototype._prepare = function(statement, callback) {
var done;
if (this._pooledConnection) {
callback(new PreparedStatementError("Statement is already prepared.", 'EALREADYPREPARED'));
return this;
}
if (typeof statement === 'function') {
callback = statement;
statement = void 0;
}
if (statement != null) {
this.statement = statement;
}
done = (function(_this) {
return function(err, connection) {
var name, param, req;
if (err) {
return callback(err);
}
_this._pooledConnection = connection;
req = new Request(_this);
req.stream = false;
req.output('handle', TYPES.Int);
req.input('params', TYPES.NVarChar, ((function() {
var ref1, results;
ref1 = this.parameters;
results = [];
for (name in ref1) {
param = ref1[name];
results.push("@" + name + " " + (declare(param.type, param)) + (param.io === 2 ? " output" : ""));
}
return results;
}).call(_this)).join(','));
req.input('stmt', TYPES.NVarChar, _this.statement);
return req.execute('sp_prepare', function(err) {
if (err) {
if (_this.transaction) {
_this.transaction.next();
} else {
_this.connection.pool.release(_this._pooledConnection);
_this._pooledConnection = null;
}
return callback(err);
}
_this._handle = req.parameters.handle.value;
return callback(null);
});
};
})(this);
if (this.transaction) {
if (!this.transaction._pooledConnection) {
callback(new TransactionError("Transaction has not begun. Call begin() first.", 'ENOTBEGUN'));
return this;
}
this.transaction.queue(done);
} else {
this.connection.pool.acquire(done);
}
return this;
};
/*
Execute next request in queue.
@private
@returns {PreparedStatement}
*/
PreparedStatement.prototype.next = function() {
if (this._queue.length) {
process.nextTick((function(_this) {
return function() {
return _this._queue.shift()(null, _this._pooledConnection);
};
})(this));
} else {
this._working = false;
}
return this;
};
/*
Add request to queue for connection. If queue is empty, execute the request immediately.
@private
@callback callback A callback to call when connection in ready to execute request.
@param {Error} err Error on error, otherwise null.
@param {*} conn Internal driver's connection.
@returns {PreparedStatement}
*/
PreparedStatement.prototype.queue = function(callback) {
if (!this._pooledConnection) {
callback(new PreparedStatementError("Statement is not prepared. Call prepare() first.", 'ENOTPREPARED'));
return this;
}
if (this._working) {
this._queue.push(callback);
} else {
this._working = true;
callback(null, this._pooledConnection);
}
return this;
};
/*
Execute a prepared statement.
@property {String} values An object whose names correspond to the names of parameters that were added to the prepared statement before it was prepared.
@callback [callback] A callback which is called after execution has completed, or an error has occurred. If omited, method returns Promise.
@param {Error} err Error on error, otherwise null.
@returns {Request|Promise}
*/
PreparedStatement.prototype.execute = function(values, callback) {
if (callback != null) {
return this._execute(values, callback);
}
return new module.exports.Promise((function(_this) {
return function(resolve, reject) {
return _this._execute(values, function(err, recordset) {
if (err) {
return reject(err);
}
return resolve(recordset);
});
};
})(this));
};
PreparedStatement.prototype._execute = function(values, callback) {
var name, param, ref1, req;
req = this.lastRequest = new Request(this);
if (this.stream != null) {
req.stream = this.stream;
}
req.input('handle', TYPES.Int, this._handle);
ref1 = this.parameters;
for (name in ref1) {
param = ref1[name];
req.parameters[name] = {
name: name,
type: param.type,
io: param.io,
value: values[name],
length: param.length,
scale: param.scale,
precision: param.precision
};
}
req.execute('sp_execute', (function(_this) {
return function(err, recordsets, returnValue) {
if (err) {
return callback(err);
}
return callback(null, (_this.multiple ? recordsets : recordsets[0]), req.rowsAffected);
};
})(this));
return req;
};
/*
Unprepare a prepared statement.
@callback [callback] A callback which is called after unpreparation has completed, or an error has occurred. If omited, method returns Promise.
@param {Error} err Error on error, otherwise null.
@returns {PreparedStatement|Promise}
*/
PreparedStatement.prototype.unprepare = function(callback) {
if (callback != null) {
return this._unprepare(callback);
}
return new module.exports.Promise((function(_this) {
return function(resolve, reject) {
return _this._unprepare(function(err) {
if (err) {
return reject(err);
}
return resolve();
});
};
})(this));
};
PreparedStatement.prototype._unprepare = function(callback) {
var done, req;
if (!this._pooledConnection) {
callback(new PreparedStatementError("Statement is not prepared. Call prepare() first.", 'ENOTPREPARED'));
return this;
}
done = (function(_this) {
return function(err) {
if (err) {
return callback(err);
}
if (_this.transaction) {
_this.transaction.next();
} else {
_this.connection.pool.release(_this._pooledConnection);
_this._pooledConnection = null;
}
_this._handle = 0;
return callback(null);
};
})(this);
req = new Request(this);
req.stream = false;
req.input('handle', TYPES.Int, this._handle);
req.execute('sp_unprepare', done);
return this;
};
return PreparedStatement;
})(EventEmitter);
/*
Class Transaction.
@property {Connection} connection Reference to used connection.
@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.
@event begin Dispatched when transaction begin.
@event commit Dispatched on successful commit.
@event rollback Dispatched on successful rollback.
*/
Transaction = (function(superClass) {
extend(Transaction, superClass);
Transaction.prototype._pooledConnection = null;
Transaction.prototype._queue = null;
Transaction.prototype._aborted = false;
Transaction.prototype._working = false;
Transaction.prototype.name = "";
Transaction.prototype.connection = null;
Transaction.prototype.isolationLevel = ISOLATION_LEVEL.READ_COMMITTED;
/*
Create new Transaction.
@param {Connection} [connection] If ommited, global connection is used instead.
*/
function Transaction(connection) {
this._abort = bind(this._abort, this);
this.connection = connection != null ? connection : global_connection;
this._queue = [];
}
/*
@private
*/
Transaction.prototype._abort = function() {
return this.connection.driver.Transaction.prototype._abort.call(this);
};
/*
Begin a transaction.
@param {Number} [isolationLevel] Controls the locking and row versioning behavior of TSQL statements issued by a connection.
@callback [callback] A callback which is called after transaction has began, or an error has occurred. If omited, method returns Promise.
@param {Error} err Error on error, otherwise null.
@returns {Transaction|Promise}
*/
Transaction.prototype.begin = function(isolationLevel, callback) {
if (isolationLevel instanceof Function) {
callback = isolationLevel;
isolationLevel = void 0;
}
if (callback != null) {
return this._begin(isolationLevel, callback);
}
return new module.exports.Promise((function(_this) {
return function(resolve, reject) {
return _this._begin(isolationLevel, function(err) {
if (err) {
return reject(err);
}
return resolve(_this);
});
};
})(this));
};
Transaction.prototype._begin = function(isolationLevel, callback) {
if (isolationLevel != null) {
this.isolationLevel = isolationLevel;
}
if (this._pooledConnection) {
callback(new TransactionError("Transaction has already begun.", 'EALREADYBEGUN'));
return this;
}
this.connection.driver.Transaction.prototype.begin.call(this, (function(_this) {
return function(err) {
if (!err) {
_this.emit('begin');
}
return callback(err);
};
})(this));
return this;
};
/*
Commit a transaction.
@callback [callback] A callback which is called after transaction has commited, or an error has occurred. If omited, method returns Promise.
@param {Error} err Error on error, otherwise null.
@returns {Transaction|Promise}
*/
Transaction.prototype.commit = function(callback) {
if (callback != null) {
return this._commit(callback);
}
return new module.exports.Promise((function(_this) {
return function(resolve, reject) {
return _this._commit(function(err) {
if (err) {
return reject(err);
}
return resolve();
});
};
})(this));
};
Transaction.prototype._commit = function(callback) {
if (!this._pooledConnection) {
callback(new TransactionError("Transaction has not begun. Call begin() first.", 'ENOTBEGUN'));
return this;
}
if (this._working) {
callback(new TransactionError("Can't commit transaction. There is a request in progress.", 'EREQINPROG'));
return this;
}
if (this._queue.length) {
callback(new TransactionError("Can't commit transaction. There are request in queue.", 'EREQINPROG'));
return this;
}
this.connection.driver.Transaction.prototype.commit.call(this, (function(_this) {
return function(err) {
if (!err) {
_this.emit('commit');
}
return callback(err);
};
})(this));
return this;
};
/*
Execute next request in queue.
@private
@returns {Transaction}
*/
Transaction.prototype.next = function() {
var toAbort;
if (this._aborted) {
toAbort = this._queue;
this._queue = [];
process.nextTick((function(_this) {
return function() {
var results;
results = [];
while (toAbort.length) {
results.push(toAbort.shift()(new TransactionError("Transaction aborted.", "EABORT")));
}
return results;
};
})(this));
}
this._working = false;
if (this._queue.length) {
process.nextTick((function(_this) {
return function() {
if (_this._aborted) {
return _this.next();
}
_this._working = true;
return _this._queue.shift()(null, _this._pooledConnection);
};
})(this));
}
return this;
};
/*
Add request to queue for connection. If queue is empty, execute the request immediately.
@private
@callback callback A callback to call when connection in ready to execute request.
@param {Error} err Error on error, otherwise null.
@param {*} conn Internal driver's connection.
@returns {Transaction}
*/
Transaction.prototype.queue = function(callback) {
if (!this._pooledConnection) {
callback(new TransactionError("Transaction has not begun. Call begin() first.", 'ENOTBEGUN'));
return this;
}
if (this._working || this._queue.length) {
this._queue.push(callback);
} else {
this._working = true;
callback(null, this._pooledConnection);
}
return this;
};
/*
Returns new request using this transaction.
@returns {Request}
*/
Transaction.prototype.request = function() {
return new Request(this);
};
/*
Rollback a transaction.
@callback [callback] A callback which is called after transaction has rolled back, or an error has occurred. If omited, method returns Promise.
@param {Error} err Error on error, otherwise null.
@returns {Transaction|Promise}
*/
Transaction.prototype.rollback = function(callback) {
if (callback != null) {
return this._rollback(callback);
}
return new module.exports.Promise((function(_this) {
return function(resolve, reject) {
return _this._rollback(function(err) {
if (err) {
return reject(err);
}
return resolve();
});
};
})(this));
};
Transaction.prototype._rollback = function(callback) {
if (this._aborted) {
callback(new TransactionError("Transaction has been aborted.", 'EABORT'));
return this;
}
if (!this._pooledConnection) {
callback(new TransactionError("Transaction has not begun. Call begin() first.", 'ENOTBEGUN'));
return this;
}
if (this._working) {
callback(new TransactionError("Can't rollback transaction. There is a request in progress.", 'EREQINPROG'));
return this;
}
if (this._queue.length) {
this._aborted = true;
}
this.connection.driver.Transaction.prototype.rollback.call(this, (function(_this) {
return function(err) {
if (!err) {
_this.emit('rollback', _this._aborted);
}
return callback(err);
};
})(this));
return this;
};
return Transaction;
})(EventEmitter);
/*
Class Request.
@property {Connection} connection Reference to used connection.
@property {Transaction} transaction Reference to transaction when request was created in transaction.
@property {*} parameters Collection of input and output parameters.
@property {Boolean} verbose If `true`, debug messages are printed to message log.
@property {Boolean} multiple If `true`, `query` will handle multiple recordsets (`execute` always expect multiple recordsets).
@property {Boolean} canceled `true` if request was canceled.
@event recordset Dispatched when metadata for new recordset are parsed.
@event row Dispatched when new row is parsed.
@event done Dispatched when request is complete.
@event error Dispatched on error.
*/
Request = (function(superClass) {
extend(Request, superClass);
Request.prototype.connection = null;
Request.prototype.transaction = null;
Request.prototype.pstatement = null;
Request.prototype.parameters = null;
Request.prototype.verbose = false;
Request.prototype.multiple = false;
Request.prototype.canceled = false;
Request.prototype.stream = null;
Request.prototype.rowsAffected = null;
/*
Create new Request.
@param {Connection|Transaction} connection If ommited, global connection is used instead.
*/
function Request(connection) {
if (connection instanceof Transaction) {
this.transaction = connection;
this.connection = connection.connection;
} else if (connection instanceof PreparedStatement) {
this.pstatement = connection;
this.connection = connection.connection;
} else if (connection instanceof Connection) {
this.connection = connection;
} else {
this.connection = global_connection;
}
this.parameters = {};
}
/*
Log to a function if assigned. Else, use console.log.
*/
Request.prototype._log = function(out) {
if (typeof this.logger === "function") {
return this.logger(out);
} else {
return console.log(out);
}
};
/*
Fetch request from tagged template string.
*/
Request.prototype._template = function(method, strings, values) {
var command, i, index, len, value;
command = [strings[0]];
for (index = i = 0, len = values.length; i < len; index = ++i) {
value = values[index];
this.input("param" + (index + 1), value);
command.push("@param" + (index + 1), strings[index + 1]);
}
return this[method](command.join(''));
};
/*
Acquire connection for this request from connection.
*/
Request.prototype._acquire = function(callback) {
if (this.transaction) {
return this.transaction.queue(callback);
} else if (this.pstatement) {
return this.pstatement.queue(callback);
} else {
if (!this.connection.pool) {
return callback(new ConnectionError("Connection not yet open.", 'ENOTOPEN'));
}
return this.connection.pool.acquire(callback);
}
};
/*
Makes the request dedicated to one connection.
*/
Request.prototype._dedicated = function(connection) {
this._acquire = function(callback) {
return callback(null, connection);
};
this._release = function() {};
return this;
};
/*
Release connection used by this request.
*/
Request.prototype._release = function(connection) {
if (this.transaction) {
return this.transaction.next();
} else if (this.pstatement) {
return this.pstatement.next();
} else {
return this.connection.pool.release(connection);
}
};
/*
Add an input parameter to the request.
**Example:**
```
request.input('input_parameter', value);
request.input('input_parameter', sql.Int, value);
```
@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.
@returns {Request}
*/
Request.prototype.input = function(name, type, value) {
if (/(--| |\/\*|\*\/|')/.test(name)) {
throw new RequestError("SQL injection warning for param '" + name + "'", 'EINJECT');
}
if (arguments.length === 1) {
throw new RequestError("Invalid number of arguments. At least 2 arguments expected.", 'EARGS');
} else if (arguments.length === 2) {
value = type;
type = getTypeByValue(value);
}
if ((value != null ? value.valueOf : void 0) && !(value instanceof Date)) {
value = value.valueOf();
}
if (value === void 0) {
value = null;
}
if (value !== value) {
value = null;
}
if (type instanceof Function) {
type = type();
}
this.parameters[name] = {
name: name,
type: type.type,
io: 1,
value: value,
length: type.length,
scale: type.scale,
precision: type.precision,
tvpType: type.tvpType
};
return this;
};
/*
Add an output parameter to the request.
**Example:**
```
request.output('output_parameter', sql.Int);
request.output('output_parameter', sql.VarChar(50), 'abc');
```
@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.
@returns {Request}
*/
Request.prototype.output = function(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');
}
if ((value != null ? value.valueOf : void 0) && !(value instanceof Date)) {
value = value.valueOf();
}
if (value === void 0) {
value = null;
}
if (value !== value) {
value = null;
}
if (type instanceof Function) {
type = type();
}
this.parameters[name] = {
name: name,
type: type.type,
io: 2,
value: value,
length: type.length,
scale: type.scale,
precision: type.precision
};
return this;
};
/*
Execute the SQL batch.
@param {String} batch T-SQL batch to be executed.
@callback [callback] A callback which is called after execution has completed, or an error has occurred. If omited, method returns Promise.
@param {Error} err Error on error, otherwise null.
@param {*} recordset Recordset.
@returns {Request|Promise}
*/
Request.prototype.batch = function(batch, callback) {
var ref1;
if (this.stream == null) {
this.stream = (ref1 = this.connection) != null ? ref1.config.stream : void 0;
}
this.rowsAffected = 0;
if (this.stream || (callback != null)) {
return this._batch(batch, callback);
}
return new module.exports.Promise((function(_this) {
return function(resolve, reject) {
return _this._batch(batch, function(err, recordset) {
if (err) {
return reject(err);
}
return resolve(recordset);
});
};
})(this));
};
Request.prototype._batch = function(batch, callback) {
if (!this.connection) {
return process.nextTick((function(_this) {
return function() {
var e;
e = new RequestError("No connection is specified for that request.", 'ENOCONN');
if (_this.stream) {
_this.emit('error', e);
return _this.emit('done');
} else {
return callback(e);
}
};
})(this));
}
if (!this.connection.connected) {
return process.nextTick((function(_this) {
return function() {
var e;
e = new ConnectionError("Connection is closed.", 'ECONNCLOSED');
if (_this.stream) {
_this.emit('error', e);
return _this.emit('done');
} else {
return callback(e);
}
};
})(this));
}
this.canceled = false;
this.connection.driver.Request.prototype.batch.call(this, batch, (function(_this) {
return function(err, recordsets) {
if (_this.stream) {
if (err) {
_this.emit('error', err);
}
return _this.emit('done', _this.rowsAffected);
} else {
return callback(err, recordsets, _this.rowsAffected);
}
};
})(this));
return this;
};
/*
Bulk load.
@param {Table} table SQL table.
@callback [callback] A callback which is called after bulk load has completed, or an error has occurred. If omited, method returns Promise.
@param {Error} err Error on error, otherwise null.
@returns {Request|Promise}
*/
Request.prototype.bulk = function(table, callback) {
var ref1;
if (this.stream == null) {
this.stream = (ref1 = this.connection) != null ? ref1.config.stream : void 0;
}
if (this.stream || (callback != null)) {
return this._bulk(table, callback);
}
return new module.exports.Promise((function(_this) {
return function(resolve, reject) {
return _this._bulk(table, function(err, rowCount) {
if (err) {
return reject(err);
}
return resolve(rowCount);
});
};
})(this));
};
Request.prototype._bulk = function(table, callback) {
if (!this.connection) {
return process.nextTick((function(_this) {
return function() {
var e;
e = new RequestError("No connection is specified for that request.", 'ENOCONN');
if (_this.stream) {
_this.emit('error', e);
return _this.emit('done');
} else {
return callback(e);
}
};
})(this));
}
if (!this.connection.connected) {
return process.nextTick((function(_this) {
return function() {
var e;
e = new ConnectionError("Connection is closed.", 'ECONNCLOSED');
if (_this.stream) {
_this.emit('error', e);
return _this.emit('done');
} else {
return callback(e);
}
};
})(this));
}
this.canceled = false;
this.connection.driver.Request.prototype.bulk.call(this, table, (function(_this) {
return function(err, rowCount) {
if (_this.stream) {
if (err) {
_this.emit('error', err);
}
return _this.emit('done');
} else {
return callback(err, rowCount);
}
};
})(this));
return this;
};
/*
Sets request to `stream` mode and pulls all rows from all recordsets to a given stream.
@param {Stream} stream Stream to pipe data into.
@returns {Stream}
*/
Request.prototype.pipe = function(stream) {
this.stream = true;
this.on('row', stream.write.bind(stream));
this.on('error', stream.emit.bind(stream, 'error'));
this.on('done', function() {
return setImmediate(function() {
return stream.end();
});
});
stream.emit('pipe', this);
return stream;
};
/*
Execute the SQL command.
**Example:**
```
var request = new sql.Request();
request.query('select 1 as number', function(err, recordset) {
console.log(recordset[0].number); // return 1
// ...
});
```
You can enable multiple recordsets in querries by `request.multiple = true` command.
```
var request = new sql.Request();
request.multiple = true;
request.query('select 1 as number; select 2 as number', function(err, recordsets) {
console.log(recordsets[0][0].number); // return 1
console.log(recordsets[1][0].number); // return 2
// ...
});
```
@param {String} command T-SQL command to be executed.
@callback [callback] A callback which is called after execution has completed, or an error has occurred. If omited, method returns Promise.
@param {Error} err Error on error, otherwise null.
@param {*} recordset Recordset.
@returns {Request|Promise}
*/
Request.prototype.query = function(command, callback) {
var ref1;
if (this.stream == null) {
this.stream = (ref1 = this.connection) != null ? ref1.config.stream : void 0;
}
this.rowsAffected = 0;
if (this.stream || (callback != null)) {
return this._query(command, callback);
}
return new module.exports.Promise((function(_this) {
return function(resolve, reject) {
return _this._query(command, function(err, recordsets) {
if (err) {
return reject(err);
}
return resolve(recordsets);
});
};
})(this));
};
Request.prototype._query = function(command, callback) {
if (!this.connection) {
return process.nextTick((function(_this) {
return function() {
var e;
e = new RequestError("No connection is specified for that request.", 'ENOCONN');
if (_this.stream) {
_this.emit('error', e);
return _this.emit('done');
} else {
return callback(e);
}
};
})(this));
}
if (!this.connection.connected) {
return process.nextTick((function(_this) {
return function() {
var e;
e = new ConnectionError("Connection is closed.", 'ECONNCLOSED');
if (_this.stream) {
_this.emit('error', e);
return _this.emit('done');
} else {
return callback(e);
}
};
})(this));
}
this.canceled = false;
this.connection.driver.Request.prototype.query.call(this, command, (function(_this) {
return function(err, recordsets) {
if (_this.stream) {
if (err) {
_this.emit('error', err);
}
return _this.emit('done', _this.rowsAffected);
} else {
return callback(err, recordsets, _this.rowsAffected);
}
};
})(this));
return this;
};
/*
Call a stored procedure.
**Example:**
```
var request = new sql.Request();
request.input('input_parameter', sql.Int, value);
request.output('output_parameter', sql.Int);
request.execute('procedure_name', function(err, recordsets, returnValue) {
console.log(recordsets.length); // count of recordsets returned by procedure
console.log(recordset[0].length); // count of rows contained in first recordset
console.log(returnValue); // procedure return value
console.log(recordsets.returnValue); // procedure return value
console.log(request.parameters.output_parameter.value); // output value
// ...
});
```
@param {String} procedure Name of the stored procedure to be executed.
@callback [callback] A callback which is called after execution has completed, or an error has occurred. If omited, method returns Promise.
@param {Error} err Error on error, otherwise null.
@param {Array} recordsets Recordsets.
@param {Number} returnValue Procedure return value.
@returns {Request|Promise}
*/
Request.prototype.execute = function(command, callback) {
var ref1;
if (this.stream == null) {
this.stream = (ref1 = this.connection) != null ? ref1.config.stream : void 0;
}
this.rowsAffected = 0;
if (this.stream || (callback != null)) {
return this._execute(command, callback);
}
return new module.exports.Promise((function(_this) {
return function(resolve, reject) {
return _this._execute(command, function(err, recordset) {
if (err) {
return reject(err);
}
return resolve(recordset);
});
};
})(this));
};
Request.prototype._execute = function(procedure, callback) {
if (!this.connection) {
return process.nextTick(function() {
var e;
e = new RequestError("No connection is specified