@firstfleet/ffmsnodesqlv8
Version:
MSSQL Task and Promise Wrapper Library
540 lines (477 loc) • 15.7 kB
JavaScript
/**
* Created by Stephen on 9/27/2015.
*/
const procedureModule = ((() => {
class BoundProcedure {
constructor (connectionDriverMgr, procedureNotifier, theConnection, procedureMeta, procedureName, pollingEnabled, procedureTimeout) {
const conn = theConnection
const driverMgr = connectionDriverMgr
const notifier = procedureNotifier
const meta = procedureMeta
const name = procedureName
let timeout = procedureTimeout
let polling = pollingEnabled
function setTimeout (t) {
timeout = t
}
function setPolling (b) {
polling = b
}
function getMeta () {
return meta
}
function getName () {
return name
}
function callStoredProcedure (notify, signature, paramsOrCallback, callback) {
const queryOb = {
query_str: signature,
query_timeout: timeout,
query_polling: polling
}
notifier.validateParameters(
[
{
type: 'string',
value: queryOb.query_str,
name: 'query string'
}
],
'callproc'
)
notify.setQueryObj(queryOb)
const chunky = notifier.getChunkyArgs(paramsOrCallback, callback)
function onProcedureRaw (err, results, more, outputParams) {
if (chunky.callback) {
if (err) {
chunky.callback(err)
} else {
chunky.callback(err, driverMgr.objectify(results), outputParams, more)
}
}
}
if (callback) {
driverMgr.realAllProc(notify, queryOb, chunky.params, onProcedureRaw)
} else {
driverMgr.realAllProc(notify, queryOb, chunky.params)
}
return notify
}
function paramsArray (params) {
if (Array.isArray(params)) {
return params
}
return meta.params.reduce((agg, latest, i) => {
if (i === 0) return agg
const name = latest.name.slice(1)
const v = Object.prototype.hasOwnProperty.call(params, name) ? params[name] : null
agg.push(v)
return agg
}, [])
}
function initFullParamVector (meta) {
const vec = meta.params.map(p => {
return {
is_output: p.is_output,
name: p.name.slice(1),
type_id: p.type_id,
max_length: p.max_length,
is_user_defined: p.is_user_defined,
use_default: false,
val: null
}
})
return vec
}
function mapParamObjectToFullParamVector (vec, meta, params) {
for (let i = 0; i < meta.params.length; i += 1) {
const latest = vec[i]
const name = meta.params[i].name.slice(1)
const v = Object.prototype.hasOwnProperty.call(params, name) ? params[name] : null
if (latest.is_user_defined) {
vec[i] = v
} else {
latest.val = v
}
// when using an object based parameter mark those params not included such
// that default keyword is used in sql submitted
const onParams = Object.prototype.hasOwnProperty.call(params, latest.name)
if (!latest.is_output && !onParams) {
latest.use_default = true
}
}
return vec
}
function mapParamArrayToFullParamVector (vec, params) {
let j = 1 // skip the first return code bound
for (let i = 0; i < params.length; i += 1) {
if (params.length < vec.length - 1) {
while (j < vec.length && vec[j].is_output === true) {
j += 1
}
}
if (j >= vec.length) break
if (vec[j].is_user_defined) {
vec[j] = params[i]
} else {
vec[j].val = params[i]
}
j += 1
}
return vec
}
function bindParams (meta, params) {
const vec = initFullParamVector(meta)
const mapped = Array.isArray(params) ? mapParamArrayToFullParamVector(vec, params) : mapParamObjectToFullParamVector(vec, meta, params)
return mapped
}
function forwardError (msg, notify, cb) {
const error = new Error(`${name}: ${msg}`)
if (cb) {
cb(error, null, null)
} else {
setImmediate(() => {
notify.emit('error', error)
})
}
}
function where (list, primitive) {
return list.reduce((agg, latest) => {
if (primitive(latest)) {
agg.push(latest)
}
return agg
}, [])
}
function getProblemParams (list, primitive) {
const failures = where(list, primitive)
if (failures.length > 0) {
const failureNames = failures.map(p => p.name)
const joinedFailureNames = failureNames.join()
return joinedFailureNames
}
return null
}
function checkParamIntegrityOnParamObject (userParamObject) {
const illegalMissingNames = getProblemParams(Object.keys(userParamObject).map(name => {
return {
name: name
}
}), p =>
!Object.prototype.hasOwnProperty.call(meta.paramByName, p.name))
if (illegalMissingNames) {
return `illegal params on param object = ${illegalMissingNames}`
}
}
function checkParamIntegrity (userParamArrayOrObject) {
if (meta.params.length === 0) {
return 'proc could not be found'
}
if (Array.isArray(userParamArrayOrObject) && userParamArrayOrObject.length >= meta.params.length) {
return `illegal parameter count, expected <= ${meta.params.length - 1}`
}
if (!Array.isArray(userParamArrayOrObject)) {
return checkParamIntegrityOnParamObject(userParamArrayOrObject)
}
return null
}
function build (pv, name) {
const pars = pv.reduce((aggr, latest, i) => {
if (i > 0) {
if (latest.use_default) {
aggr.push(`@${latest.name} = DEFAULT`)
} else {
aggr.push(`@${latest.name} = ?`)
}
}
return aggr
}, []).join(', ')
const q = `{ ? = call ${name}(${pars}) }`
return q
}
function privateCall (notify, params, cb) {
const errorMsg = checkParamIntegrity(params)
if (errorMsg) {
forwardError(errorMsg, notify, cb)
setImmediate(() => {
notify.emit('free')
})
return
}
let paramVec = bindParams(meta, params)
const signature = build(paramVec, name)
paramVec = where(paramVec, latest => !latest.use_default)
if (cb) {
callStoredProcedure(notify, signature, paramVec, (err, results, output, more) => {
cb(err, results, output, more)
})
} else {
callStoredProcedure(notify, meta.signature, paramVec)
}
}
function callNotify (paramsOrCb, fn, notify) {
let vec
let cb
if (Array.isArray(paramsOrCb)) {
vec = paramsOrCb
cb = fn
} else if (typeof paramsOrCb === 'function') {
vec = []
cb = paramsOrCb
} else {
vec = paramsOrCb
cb = fn
}
privateCall(notify, vec, cb)
}
function call (paramsOrCb, fn) {
const notify = conn.getNotify()
callNotify(paramsOrCb, fn, notify)
return notify
}
this.paramsArray = paramsArray
this.call = call
this.callNotify = callNotify
this.setTimeout = setTimeout
this.setPolling = setPolling
this.getMeta = getMeta
this.getName = getName
}
}
class ProcedureMgr {
constructor (procedureConnection, procedureNotifier, procedureDriverMgr, metaResolver, sharedCache) {
const cache = sharedCache || {}
const conn = procedureConnection
let timeout = 0
let polling = false
const driverMgr = procedureDriverMgr
const notifier = procedureNotifier
function where (list, primitive) {
return list.reduce((agg, latest) => {
if (primitive(latest)) {
agg.push(latest)
}
return agg
}, [])
}
function returnMeta () {
return {
is_output: true,
name: '@returns',
type_id: 'int',
max_length: 4,
order: 0,
is_user_defined: false,
has_default_value: false,
default_value: null,
collation: null
}
}
function describeProcedure (procedureName, callback) {
const ret = returnMeta()
function mapFn (sql) {
let schemaName = 'dbo'
let unqualifiedTableName = procedureName
const schemaIndex = procedureName.indexOf('.')
if (schemaIndex > 0) {
schemaName = procedureName.substr(0, schemaIndex)
unqualifiedTableName = procedureName.substr(schemaIndex + 1)
}
sql = sql.replace(/<escaped_procedure_name>/g, unqualifiedTableName)
sql = sql.replace(/<schema_name>/g, schemaName)
return sql
}
metaResolver.getProcedureDefinition(conn, procedureName, mapFn).then(results => {
if (results.length > 0) {
results.unshift(ret)
}
callback(null, results)
}).catch(err => {
callback(err, null)
})
}
function descp (p) {
let s = ''
s += `${p.name} [ ${p.type_id}${p.is_output
? ' out '
: ' in '} ] `
return s
}
function summarise (name, pv) {
if (!pv || pv.length === 0) return 'proc does not exist.'
let s = `${descp(pv[0])} ${name}( `
for (let i = 1; i < pv.length; i += 1) {
s += descp(pv[i])
if (i < pv.length - 1) {
s += ', '
}
}
s += ' ) '
return s
}
function build (pv, name) {
const pars = pv.reduce((aggr, latest, i) => {
if (i > 0) {
aggr.push(`${latest.name} = ?`)
}
return aggr
}, []).join(', ')
const q = `{ ? = call ${name}(${pars}) }`
return q
}
function asSelect (pv, procedure) {
const params = []
const parameters = []
pv.forEach(param => {
if (param.name !== '@returns') {
parameters.push(param)
}
})
parameters.forEach(param => {
if (param.is_output) {
const s = `${param.name} ${param.type_id}`
params.push(s)
}
})
let cmdParam = ['@___return___ int'].concat(params).join(', ')
let cmd = `declare ${cmdParam};`
cmd += `exec @___return___ = ${procedure} `
const spp = []
parameters.forEach(param => {
if (param.is_output) {
// output parameter
cmdParam = `${param.name}=${param.name} output`
spp.push(cmdParam)
} else {
// input parameter
cmdParam = param.name + '=?'
spp.push(cmdParam)
}
})
const params2 = []
parameters.forEach(param => {
if (param.is_output) {
let paramName = param.name
if (paramName[0] === '@') {
paramName = paramName.substring(1)
}
cmdParam = `${param.name} as ${paramName}`
params2.push(cmdParam)
}
})
const sppJoined = spp.join(', ')
cmd += sppJoined + ';'
const selectCmd = `select ${['@___return___ as \'___return___\''].concat(params2).join(', ')}`
cmd += selectCmd + ';'
return cmd
}
function constructMeta (name, paramVector) {
paramVector = where(paramVector, p => p.object_id !== null)
const outputParams = where(paramVector, p => p.is_output)
const inputParams = where(paramVector, p => !p.is_output)
const signature = build(paramVector, name)
const select = asSelect(paramVector, name)
const summary = summarise(name, paramVector)
const paramByName = paramVector.reduce((agg, latest) => {
if (latest.name) {
agg[latest.name.slice(1)] = latest
}
return agg
}, {})
const meta = {
select: select,
signature: signature,
summary: summary,
params: paramVector,
outputParams: outputParams,
inputParams: inputParams,
paramByName: paramByName
}
return meta
}
function createProcedure (name, cb) {
let procedure = cache[name]
if (!procedure) {
describeProcedure(name, (err, paramVector) => {
if (!err) {
const meta = constructMeta(name, paramVector)
procedure = new BoundProcedure(driverMgr, notifier, conn, meta, name, polling, timeout)
cache[name] = procedure
cb(null, procedure)
} else {
cb(err, null)
}
})
} else {
cb(null, procedure)
}
}
function describe (name, cb) {
createProcedure(name, p => {
if (p) {
cb(p)
} else {
cb(new Error(`could not get definition of ${name}`))
}
})
}
function getProc (name, cb) {
createProcedure(name, (err, p) => {
cb(err, p)
})
}
function get (name, cb) {
createProcedure(name, (err, p) => {
if (err) {
cb(err)
} else {
cb(p)
}
})
}
function callproc (name, paramsOrCb, cb) {
const notify = conn.getNotify()
createProcedure(name, (e, p) => {
if (e) {
const err = new Error(`unable to construct proc ${name} ${e.message}`)
if (cb) {
cb(err, null, null)
} else {
notify.emit('error', err)
}
} else {
p.callNotify(paramsOrCb, cb, notify)
}
})
return notify
}
function setTimeout (t) {
timeout = t
}
function clear () {
Object.keys(cache).forEach(k => {
delete cache[k]
})
}
function setPolling (b) {
polling = b
}
function getCount () {
return Object.keys(cache).length
}
this.setTimeout = setTimeout
this.setPolling = setPolling
this.callproc = callproc
this.describe = describe
this.getCount = getCount
this.clear = clear
this.get = get // legacy call
this.getProc = getProc
}
}
return {
ProcedureMgr: ProcedureMgr
}
})())
exports.procedureModule = procedureModule