mssql-ease
Version:
Promise style ease-of-use module for working with Microsoft SQL Server from Node.js.
218 lines (195 loc) • 6.24 kB
JavaScript
const debug = require('debug')('mssql:statement');
const assert = require('assert-plus');
const tds = require('tedious');
const xutil = require('./xutil');
let idSeed = 0;
let statementCount = 0;
class SqlStatement {
constructor(connection, statement) {
assert.object(connection, 'connection');
// in order to destructure internally...
Object.defineProperties(this, {
connection: {
enumerable: true,
value: connection
},
statement: {
enumerable: true,
value: statement
},
id: {
enumerable: true,
value: idSeed++
}
});
debug(`SqlStatement #${this.id} created: ${this.statement}`);
}
async execute(onBind, release) {
assert.optionalFunc(onBind, 'onBind');
assert.optionalBool(release, 'release');
let { connection: { connection: cn }, statement, id } = this;
try {
const statementId = statementCount++;
const beg = process.hrtime();
const output = Object.create(null);
const capture = Object.create(null);
capture.outputCount = 0;
await new Promise((resolve, reject) => {
let req = new tds.Request(statement, (err, rowCount) => {
if (err) {
debug(`Statement #${id}:${statementId} error: ${err}`);
reject(err);
return;
}
const hrtime = process.hrtime(beg);
capture.stats = { rowCount, hrtime };
debug(`StoredProcedure #${id}:${statementId} ${rowCount} rows in ${hrtime[0]}s ${hrtime[1] / 1000000}ms`);
resolve();
});
req.on('returnValue', (parameterName, value, metadata) => {
capture.outputCount++;
output[parameterName] = { value, metadata };
});
if (typeof (onBind) === 'function') {
debug(`StoredProcedure #${id}:${statementId} caller supplied bind`);
onBind(req, tds.TYPES);
}
debug(`StoredProcedure #${id}:${statementId}: calling procedure`);
cn.execSql(req);
});
// surface the results of background processing...
return (capture.outputCount) ?
Object.assign(capture.stats, { output }) :
capture.stats;
} finally {
if (release) {
await cn.relese();
}
}
}
executeRows(onEach, onBind, release) {
onEach = (Array.isArray(onEach)) ? onEach : [onEach];
assert.arrayOfFunc(onEach, 'onEach');
assert.optionalFunc(onBind, 'onBind');
assert.optionalBool(release, 'release');
let self = this;
let { connection: { connection: cn }, statement, id } = this;
return new Promise((resolve, reject) => {
let statementId = statementCount++;
let beg = process.hrtime();
let outputCount = 0;
let outputParameters = Object.create(null);
let resultCount = -1;
let rowCount = 0;
let rowSum = 0;
let rowCounts = [];
function onColumnMetadata() {
if (~resultCount) {
rowCounts.push(rowCount);
rowSum += rowCount;
rowCount = 0;
}
resultCount++;
debug(`SqlStatement #${id}:${statementId} resultset: ${resultCount}.`);
}
function onRow(row) {
rowCount++;
if (resultCount < onEach.length) {
onEach[resultCount](row);
}
}
function onReturnValue(parameterName, value, metadata) {
outputCount++;
outputParameters[parameterName] = {
value,
metadata
};
}
function onCompleted(err) {
if (err) {
debug(`SqlStatement #${id}:${statementId} error: ${err}`);
if (release) {
cn.release()
.then(() => reject(err))
.catch(() => reject(err));
} else {
reject(err);
}
}
}
function done(unused, more, returnStatus) {
if (!more) {
let hrtime = process.hrtime(beg);
if (~resultCount) {
rowCounts.push(rowCount);
rowSum += rowCount;
}
resultCount++;
let result = {
stats: {
hrtime,
resultCount,
rowCounts
}
};
if (returnStatus !== undefined) {
result.returnStatus = returnStatus;
}
if (outputCount) {
result.outputParameters = outputParameters;
}
debug(`SqlStatement #${id}:${statementId} ${rowSum} rows in ${hrtime[0]}s ${hrtime[1] / 1000000}ms`);
if (release) {
self.connection.release()
.then(() => resolve(result))
.catch(reject);
} else {
resolve(result);
}
}
}
try {
let req = new tds.Request(statement, onCompleted);
debug(`SqlStatement #${id}:${statementId}: ${statement}`);
req.on('returnValue', onReturnValue);
req.on('row', onRow);
req.on('error', reject);
//req.on('done', done);
//req.on('doneProc', done);
req.on('requestCompleted', done);
req.on('columnMetadata', onColumnMetadata);
if (typeof (onBind) === 'function') {
debug(`SqlStatement #${id}:${statementId} caller supplied bind`);
onBind(req, tds.TYPES);
}
debug(`SqlStatement #${id}:${statementId}: executing`);
cn.execSql(req);
} catch (err) {
reject(err);
}
});
}
executeObjects(onEach, onBind, release) {
onEach = (Array.isArray(onEach)) ? onEach : [onEach];
assert.arrayOfFunc(onEach, 'onEach');
assert.optionalFunc(onBind, 'onBind');
assert.optionalBool(release, 'release');
onEach = onEach.reduce((acc, each) => {
acc.push(xutil.transformColumnsToObject.bind(null, each));
return acc;
}, []);
return this.executeRows(onEach, onBind, release);
}
}
function create(connection, statement) {
return new Promise((resolve, reject) => {
try {
resolve(new SqlStatement(connection, statement));
} catch (err) {
reject(err);
}
});
}
// Attach module level methods...
SqlStatement.create = create;
module.exports = SqlStatement;