msnodesqlv8
Version:
Microsoft Driver for Node.js SQL Server compatible with all versions of Node.
292 lines (251 loc) • 6.47 kB
JavaScript
class AggregatorResults {
constructor (options) {
this.beginAt = new Date()
this.submittedAt = null
this.endAt = null
this.elapsed = 0
this.first = null
this.firstMeta = null
this.meta = []
this.metaElapsed = []
this.counts = []
this.results = []
this.output = null
this.info = null
this.errors = []
this.options = options
this.rows = 0
this.row = null
this.rowRate = 0
this.sql = null
this.returns = null
}
onMeta (meta) {
for (let i = 0; i < meta.length; ++i) {
if (this.options.replaceEmptyColumnNames) {
if (!meta[i].name || meta[i].length === 0) {
meta[i].name = `Column${i}`
}
}
}
this.calcElapsed()
this.meta.push(meta)
this.results.push([])
this.metaElapsed.push(this.elapsed)
if (this.first === null) {
this.first = this.results[0]
this.firstMeta = meta
}
}
onOutput (o) {
this.output = o
if (o.length > 0) {
this.returns = o[0]
}
}
onSubmitted (q) {
this.calcElapsed()
this.submittedAt = new Date()
this.sql = q.query_str
}
onRowCount (count) {
this.counts.push(count)
}
end () {
this.endAt = new Date()
}
onRow () {
this.rows++
this.row = this.newRow()
this.calcElapsed()
this.rowRate = (this.rows / this.elapsed) * 1000
}
onInfo (m) {
if (!this.info) {
this.info = []
}
this.info.push(m.message.substring(m.message.lastIndexOf(']') + 1))
}
calcElapsed () {
this.elapsed = new Date() - this.beginAt
}
onDone () {
this.calcElapsed()
this.row = null
}
lastError () {
return this.errors.length > 0
? this.errors[this.errors.length - 1]
: null
}
resultId () {
return this.meta.length - 1
}
newRow () {
const resultId = this.resultId()
return this.options.raw ? [this.meta[resultId].length] : {}
}
onColumn (c, v) {
const resultId = this.resultId()
const meta = this.meta[resultId]
const results = this.results[resultId]
const row = this.row
if (this.options.raw) {
row[c] = v
} else {
row[meta[c].name] = v
}
if (c === meta.length - 1) {
results.push(row)
}
}
}
class AggregatorOptions {
constructor (options) {
this.timeoutMs = this.getOpt(options, 'timeoutMs', 0)
this.raw = this.getOpt(options, 'raw', false)
this.replaceEmptyColumnNames = this.getOpt(options, 'replaceEmptyColumnNames', false)
}
getOpt (src, p, def) {
if (!src) {
return def
}
let ret
if (Object.hasOwnProperty.call(src, p)) {
ret = src[p]
} else {
ret = def
}
return ret
}
}
class QueryAggregator {
constructor (connectionProxy) {
this.connectionProxy = connectionProxy
}
emptyResults (options) {
return new AggregatorResults(options)
}
async run (q, opt) {
return new Promise((resolve, reject) => {
if (this.connectionProxy.isClosed()) {
reject(new Error('connection is closed.'))
}
let handle = null
const options = new AggregatorOptions(opt)
const ret = this.emptyResults(options)
if (options.timeoutMs) {
handle = this.timeOut(q, options.timeoutMs, (e) => {
if (e) {
ret.errors.push(e)
}
})
}
function onSubmitted (q) {
ret.onSubmitted(q)
}
function onRowCount (count) {
ret.onRowCount(count)
}
function onOutput (o) {
ret.onOutput(o)
}
function onError (e, more) {
ret.errors.push(e)
if (!more) {
e._results = ret
}
}
function onInfo (m) {
ret.onInfo(m)
}
function onMeta (meta) {
ret.onMeta(meta)
}
function onRow () {
ret.onRow()
}
function rejectResolve () {
ret.end()
const last = ret.lastError()
if (last) {
reject(last)
} else {
resolve(ret)
}
}
function onDone () {
if (handle) {
clearTimeout(handle)
}
unSubscribe()
ret.onDone()
// these will not be free at this point as the
// statement can be used over and over again until
// manually free
if (q.isPrepared()) {
rejectResolve()
}
}
function onFree () {
q.removeListener('free', onFree)
rejectResolve()
}
function onColumn (c, v) {
ret.onColumn(c, v)
}
function unSubscribe () {
q.removeListener('submitted', onSubmitted)
q.removeListener('rowcount', onRowCount)
q.removeListener('column', onColumn)
q.removeListener('output', onOutput)
q.removeListener('error', onError)
q.removeListener('info', onInfo)
q.removeListener('meta', onMeta)
q.removeListener('row', onRow)
q.removeListener('done', onDone)
if (q.isPrepared()) {
q.removeListener('free', onFree)
}
}
function subscribe () {
q.on('submitted', onSubmitted)
q.on('rowcount', onRowCount)
q.on('output', onOutput)
q.on('error', onError)
q.on('info', onInfo)
q.on('meta', onMeta)
q.on('row', onRow)
q.on('done', onDone)
q.on('free', onFree)
q.on('column', onColumn)
}
subscribe()
})
}
timeOut (q, timeoutMs, reject) {
return setTimeout(() => {
try {
q.pauseQuery()
q.cancelQuery((e) => {
e = e || new Error(`Query cancelled timeout ${timeoutMs}`)
reject(e)
})
} catch (e) {
reject(e)
}
}, timeoutMs)
}
async callProc (name, params, options) {
const q = this.connectionProxy.callproc(name, params)
return this.run(q, options)
}
async query (sql, params, options) {
const q = this.connectionProxy.query(sql, params)
return this.run(q, options)
}
async queryPrepared (q, options) {
return this.run(q, options)
}
}
exports.QueryAggregator = QueryAggregator