@firstfleet/ffmsnodesqlv8
Version:
MSSQL Task and Promise Wrapper Library
352 lines (318 loc) • 10.9 kB
JavaScript
/* global: bindQuery */
const driverModule = ((() => {
const readerModule = require('./reader').readerModule
const queueModule = require('./queue').queueModule
function DriverMgr (sql) {
const driverCommandEnum = {
CANCEL: 10,
COMMIT: 11,
ROLLBACK: 12,
BEGIN_TRANSACTION: 13,
PREPARE: 14,
FREE_STATEMENT: 15,
QUERY: 16,
CLOSE: 17,
UNBIND: 18
}
const cppDriver = sql
const workQueue = new queueModule.WorkQueue()
const reader = new readerModule.DriverRead(cppDriver, workQueue)
function setUseUTC (utc) {
reader.setUseUTC(utc)
}
function emptyQueue () {
workQueue.emptyQueue()
}
function close (callback) {
workQueue.enqueue(driverCommandEnum.CLOSE, () => {
cppDriver.close(() => {
callback()
})
}, [])
}
function execCancel (qid, queueItem, callback) {
// send cancel directly to driver.
const args = queueItem.args
const cb = args[3]
const peek = workQueue.peek()
function forwardCancel (err) {
setImmediate(() => {
if (err && err.length > 0) {
callback(err[0])
} else {
callback(null)
}
})
}
if (queueItem.operationId === peek.operationId) {
cppDriver.pollingMode(qid, true, () => {
cppDriver.cancelQuery(qid, (e) => {
forwardCancel(e)
})
})
} else {
workQueue.dropItem(queueItem)
setImmediate(() => {
// make a callback on the cancel request with no error.
callback(null)
// invoke the listener as if this has come from driver so user query callback can be invoked.
cb(new Error('Error: [msnodesql] (query removed from q) Operation canceled'))
})
}
return true
}
// if this relates to the active query being executed then immediately send
// the cancel, else the query can be removed from the queue and never submitted to the driver.
function cancel (notify, callback) {
const qid = notify.getQueryId()
if (workQueue.length() === 0) {
setImmediate(() => {
callback(new Error(`Error: [msnodesql] cannot cancel query (empty queue) id ${qid}`))
})
return
}
const first = workQueue.first((_idx, currentItem) => {
if (currentItem.commandType !== driverCommandEnum.QUERY) {
return false
}
const args = currentItem.args
const not = args[0]
const currentQueryId = not.getQueryId()
return qid === currentQueryId
})
if (first) {
if (first.paused) {
execCancel(qid, first, () => {
freeStatement(notify, () => {
workQueue.dropItem(first)
callback(null)
})
})
} else {
execCancel(qid, first, callback)
}
} else {
setImmediate(() => {
callback(new Error(`Error: [msnodesql] cannot cancel query (not found) id ${qid}`))
})
}
}
function objectify (results) {
const names = {}
const lim = results.meta
? results.meta.length
: 0
for (let idx = 0; idx < lim; idx += 1) {
const meta = results.meta[idx]
const name = meta.name
if (name !== '' && !names[name]) {
names[name] = idx
} else {
let extra = 0
let candidate = `Column${idx}`
while (names[candidate]) {
candidate = `Column${idx}_${extra}`
extra += 1
}
names[candidate] = idx
}
}
const rows = []
if (results.rows) {
results.rows.forEach(row => {
const value = {}
Object.keys(names).forEach(name => {
if (Object.prototype.hasOwnProperty.call(names, name)) {
value[name] = row[names[name]]
}
})
rows.push(value)
})
}
return rows
}
function freeStatement (notify, callback) {
const queryId = notify.getQueryId()
if (queryId >= 0) {
workQueue.enqueue(driverCommandEnum.FREE_STATEMENT, () => {
cppDriver.freeStatement(queryId, () => {
callback(queryId)
setImmediate(() => {
notify.emit('free', queryId)
})
workQueue.nextOp()
})
}, [])
} else {
callback(queryId)
setImmediate(() => {
notify.emit('free', queryId)
})
workQueue.nextOp()
}
}
function onStatementComplete (notify, outputParams, callback, results, more) {
if (!more) {
freeStatement(notify, () => {
if (callback) {
callback(null, results, more, outputParams)
}
})
} else {
if (callback) {
callback(null, results, more, outputParams)
}
}
}
// for a stored procedure, the out parameters / return value can
// only be unbound when rest of query completes. The output params
// will now be ready to fetch out of the statement.
function beginTransaction (callback) {
workQueue.enqueue(driverCommandEnum.BEGIN_TRANSACTION, () => {
cppDriver.beginTransaction(err => {
callback(err || null, false)
workQueue.nextOp()
})
}, [])
}
function rollback (callback) {
workQueue.enqueue(driverCommandEnum.ROLLBACK, () => {
cppDriver.rollback(err => {
callback(err || null, false)
workQueue.nextOp()
})
}, [])
}
function commit (callback) {
workQueue.enqueue(driverCommandEnum.COMMIT, () => {
cppDriver.commit(err => {
callback(err || null, false)
workQueue.nextOp()
})
}, [])
}
function prepare (notify, queryOrObj, callback) {
workQueue.enqueue(driverCommandEnum.PREPARE, () => {
cppDriver.prepare(notify.getQueryId(), queryOrObj, (err, meta) => {
callback(err, meta)
workQueue.nextOp()
})
}, [])
}
function readAllPrepared (notify, queryObj, params, cb) {
notify.setOperation(workQueue.enqueue(driverCommandEnum.QUERY,
(notify, query, params, callback) => {
setImmediate(() => {
const q = reader.getQuery(notify, query, params, {
begin: (queryId, query, params, callback) => {
cppDriver.bindQuery(queryId, params, (err, meta) => {
if (callback) {
callback(err, meta)
}
})
},
end: (queryId, outputParams, callback, results, more) => {
if (callback) {
callback(null, results, more, outputParams)
}
}
}, callback)
notify.setQueryWorker(q)
q.begin()
})
}, [notify, queryObj, params, cb]))
}
function read (notify, queryObj, params, cb) {
notify.setOperation(workQueue.enqueue(driverCommandEnum.QUERY, (notify, query, params, callback) => {
setImmediate(() => {
const q = reader.getQuery(notify, query, params, {
begin: (queryId, query, params, callback) => cppDriver.query(queryId, query, params, (err, results, more) => {
if (callback) {
callback(err, results, more)
}
}),
end: (not, outputParams, callback, results, endMore) => {
onStatementComplete(not, outputParams, callback, results, endMore)
}
}, callback)
notify.setQueryWorker(q)
q.begin()
})
}, [notify, queryObj, params, cb]))
}
function readAllQuery (notify, queryObj, params, cb) {
// if paused at head of q then kill this statement to allow driver to set up this one
const peek = workQueue.peek()
if (peek && peek.paused) {
const pausedNotify = peek.args[0]
freeStatement(pausedNotify, () => {
workQueue.dropItem(peek)
read(notify, queryObj, params, cb)
})
} else {
read(notify, queryObj, params, cb)
}
}
function realAllProc (notify, queryObj, params, cb) {
notify.setOperation(workQueue.enqueue(driverCommandEnum.QUERY,
(notify, query, params, callback) => {
setImmediate(() => {
const q = reader.getQuery(notify, query, params, {
begin: (queryId, procedure, params, callback) => {
cppDriver.callProcedure(queryId, procedure, params, (err, results, params) => {
if (callback) {
callback(err, results, params)
}
})
},
// for a stored procedure with multiple statements, only unbind after all
// statements are completed
end: (not, outputParams, callback, results, endMore) => {
if (!endMore) {
const qid = not.getQueryId()
workQueue.enqueue(driverCommandEnum.UNBIND, (a) => {
setImmediate(() => {
cppDriver.unbind(qid, (err, outputVector) => {
if (err && callback) {
callback(err, results)
}
not.emit('output', outputVector)
onStatementComplete(not, outputVector, callback, results, a)
if (!a) {
workQueue.nextOp()
}
})
}, [endMore])
})
} else {
onStatementComplete(not, null, callback, results, endMore)
}
}
}, callback)
notify.setQueryWorker(q)
q.begin()
})
}, [notify, queryObj, params, cb]))
}
return {
setUseUTC: setUseUTC,
cancel: cancel,
commit: commit,
rollback: rollback,
beginTransaction: beginTransaction,
prepare: prepare,
objectify: objectify,
freeStatement: freeStatement,
readAllQuery: readAllQuery,
realAllProc: realAllProc,
readAllPrepared: readAllPrepared,
emptyQueue: emptyQueue,
close: close
}
}
return {
DriverMgr: DriverMgr
}
})())
exports.driverModule = driverModule