@firstfleet/ffmsnodesqlv8
Version:
MSSQL Task and Promise Wrapper Library
478 lines (401 loc) • 14.1 kB
JavaScript
/**
* Created by Stephen on 9/28/2015.
*/
const connectionModule = ((() => {
// private
const cppDriver = require('../build/Release/sqlserverv8.node')
const driverModule = require('./driver').driverModule
const procedureModule = require('./procedure').procedureModule
const notifyModule = require('./notifier').notifyModule
const tableModule = require('./table').tableModule
const userModule = require('./user').userModule
const metaModule = require('./meta').metaModule
const utilModule = require('./util').utilModule
const sqlMeta = new metaModule.Meta()
const userTypes = new userModule.SqlTypes()
class ConnectionWrapper {
constructor (driver, defCb, name) {
const defaultCallback = defCb
const id = name
const driverMgr = driver
const inst = this
const notifier = new notifyModule.NotifyFactory()
let nextQueryId = 0
let dead = false
let useUTC = true
let procedureCache = null
let tableCache = null
function setSharedCache (pc, tc) {
procedureCache = pc
tableCache = tc
}
function getUserTypeTable (name, callback) {
const mapFn = sql => {
let schemaName = 'dbo'
let unqualifiedTableName = name
const schemaIndex = name.indexOf('.')
if (schemaIndex > 0) {
schemaName = name.substr(0, schemaIndex)
unqualifiedTableName = name.substr(schemaIndex + 1)
}
sql = sql.replace(/<user_type_name>/g, unqualifiedTableName)
sql = sql.replace(/<schema_name>/g, schemaName)
return sql
}
sqlMeta.getUserType(inst, name, mapFn).then(res => {
callback(null, new userTypes.Table(name, res))
}).catch(err => {
callback(err, null)
})
}
function tableMgr () {
return tables
}
function getUseUTC () {
return useUTC
}
function setUseUTC (utc) {
useUTC = utc
driverMgr.setUseUTC(utc)
}
function procedureMgr () {
return procedures
}
function close (immediately, callback) {
if (dead) {
return
}
// require only callback
if (typeof immediately === 'function') {
callback = immediately
} else if (typeof immediately !== 'boolean' && immediately !== undefined) {
throw new Error('[msnodesql] Invalid parameters passed to close.')
}
callback = callback || defaultCallback
dead = true
driverMgr.close(err => {
setImmediate(() => {
driverMgr.emptyQueue()
callback(err)
})
})
}
function queryRawNotify (notify, queryOrObj, chunky) {
const queryObj = notifier.validateQuery(queryOrObj, useUTC, 'queryRaw')
driverMgr.readAllQuery(notify, queryObj, chunky.params, chunky.callback)
}
function queryNotify (notify, queryOrObj, chunky) {
notifier.validateQuery(queryOrObj, useUTC, 'query')
const onQueryRaw = (err, results, more) => {
if (chunky.callback) {
if (err) {
chunky.callback(err, null, more)
} else {
chunky.callback(err, driverMgr.objectify(results), more)
}
}
}
if (chunky.callback) {
return queryRawNotify(notify, queryOrObj, notifier.getChunkyArgs(chunky.params, (err, results, more) => {
setImmediate(() => {
onQueryRaw(err, results, more)
})
}))
} else {
queryRawNotify(notify, queryOrObj, chunky)
}
}
function getNotify (queryOrObj) {
const qid = nextQueryId++
const notify = new notifier.StreamEvents()
notify.setQueryId(qid)
notify.setConn(inst)
notify.setQueryObj(queryOrObj)
return notify
}
function queryRaw (queryOrObj, paramsOrCallback, callback) {
if (dead) {
throw new Error('[msnodesql] Connection is closed.')
}
const notify = getNotify(queryOrObj)
const chunky = notifier.getChunkyArgs(paramsOrCallback, callback)
if (!chunky.callback) {
queryRawNotify(notify, queryOrObj, chunky)
} else {
queryRawNotify(notify, queryOrObj, notifier.getChunkyArgs(chunky.params, (err, results, more) => {
setImmediate(() => {
chunky.callback(err, results, more)
})
}))
}
return notify
}
function query (queryOrObj, paramsOrCallback, callback) {
if (dead) {
throw new Error('[msnodesql] Connection is closed.')
}
const notify = getNotify(queryOrObj)
const chunky = notifier.getChunkyArgs(paramsOrCallback, callback)
queryNotify(notify, queryOrObj, chunky)
return notify
}
function beginTransaction (callback) {
if (dead) {
throw new Error('[msnodesql] Connection is closed.')
}
callback = callback || defaultCallback
driverMgr.beginTransaction(callback)
}
function cancelQuery (notify, callback) {
if (dead) {
throw new Error('[msnodesql] Connection is closed.')
}
const qo = notify.getQueryObj()
const polling = qo.query_polling || false
callback = callback || defaultCallback
const paused = notify.isPaused()
const canCancel = paused || polling
if (!canCancel) {
setImmediate(() => {
callback(new Error('Error: [msnodesql] cancel only supported for statements where polling is enabled.'))
})
} else {
driverMgr.cancel(notify, (e) => {
notify.emit('done')
callback(e)
})
}
}
function commit (callback) {
if (dead) {
throw new Error('[msnodesql] Connection is closed.')
}
callback = callback || defaultCallback
driverMgr.commit(callback)
}
function rollback (callback) {
if (dead) {
throw new Error('[msnodesql] Connection is closed.')
}
callback = callback || defaultCallback
driverMgr.rollback(callback)
}
// inform driver to prepare the sql statement and reserve it for repeated use with parameters.
function PreparedStatement (preparedSignature, connection, preparedNotifier, preparedMeta) {
const meta = preparedMeta
const notify = preparedNotifier
const cw = connection
let active = true
const signature = preparedSignature
function getMeta () {
return meta
}
function getSignature () {
return signature
}
function getId () {
return notify.getQueryId()
}
function preparedQuery (paramsOrCallback, callback) {
if (!active) {
if (callback) {
callback(new Error('error; prepared statement has been released.'))
}
}
const chunky = notifier.getChunkyArgs(paramsOrCallback, callback)
const onPreparedQuery = (err, results, more) => {
if (chunky.callback) {
if (err) {
chunky.callback(err)
} else {
chunky.callback(err, driverMgr.objectify(results), more)
}
}
}
if (chunky.callback) {
driverMgr.readAllPrepared(notify, {}, chunky.params, onPreparedQuery)
} else {
driverMgr.readAllPrepared(notify, {}, chunky.params)
}
return notify
}
const free = callback => {
driverMgr.freeStatement(notify, err => {
active = false
if (callback) {
callback(err)
}
})
}
return {
preparedQuery: preparedQuery,
meta: meta,
connection: cw,
free: free,
getMeta: getMeta,
getSignature: getSignature,
getId: getId
}
}
function prepare (queryOrObj, callback) {
const notify = getNotify(queryOrObj)
const chunky = notifier.getChunkyArgs(callback)
queryOrObj = notifier.validateQuery(queryOrObj, useUTC, 'prepare')
const onPrepare = (err, meta) => {
const prepared = new PreparedStatement(queryOrObj.query_str, inst, notify, meta)
chunky.callback(err, prepared)
}
driverMgr.prepare(notify, queryOrObj, onPrepare)
return notify
}
function callproc (name, paramsOrCb, cb) {
return procedures.callproc(name, paramsOrCb, cb)
}
// returns a promise of aggregated results not a query
function callprocAggregator (name, params, options) {
return utilModule.callprocAggregator(this, name, params, options)
}
this.id = id
this.getUserTypeTable = getUserTypeTable
this.cancelQuery = cancelQuery
this.queryNotify = queryNotify
this.queryRaw = queryRaw
this.queryRawNotify = queryRawNotify
this.close = close
this.query = query
this.beginTransaction = beginTransaction
this.commit = commit
this.rollback = rollback
this.tableMgr = tableMgr
this.procedureMgr = procedureMgr
this.prepare = prepare
this.getUseUTC = getUseUTC
this.setUseUTC = setUseUTC
this.getNotify = getNotify
this.callproc = callproc
this.callprocAggregator = callprocAggregator
this.setSharedCache = setSharedCache
const tables = new tableModule.TableMgr(this, sqlMeta, userTypes, tableCache)
const procedures = new procedureModule.ProcedureMgr(this, notifier, driverMgr, sqlMeta, procedureCache)
}
}
let nextID = 0
function getConnectObject (p) {
return typeof (p) === 'string'
? {
conn_str: p,
connect_timeout: 0
}
: p
}
function openFrom (parentFn, params, callback) {
class PrivateConnection {
constructor (p, cb, id) {
const defaultCallback = err => {
if (err) {
throw new Error(err)
}
}
let callback2 = cb
const native = new cppDriver.Connection()
const driverMgr = new driverModule.DriverMgr(native)
const nf = new notifyModule.NotifyFactory()
const connection = new ConnectionWrapper(driverMgr, defaultCallback, id)
connection.setUseUTC(true)
const connectObj = p
const open = () => {
nf.validateParameters(
[
{
type: 'string',
value: connectObj.conn_str,
name: 'connection string'
},
{
type: 'function',
value: callback,
name: 'callback'
}
],
parentFn
)
callback2 = callback2 || defaultCallback
const queueCb = err => {
setImmediate(() => {
if (Array.isArray(err) && err.length === 1) {
callback2(err[0], connection)
} else {
callback2(err, connection)
}
})
}
native.open(connectObj, queueCb)
}
this.id = connection.id
this.connection = connection
this.open = open
return this
}
}
const c = new PrivateConnection(getConnectObject(params), callback, nextID)
nextID += 1
c.open()
return c.connection
}
function queryCloseOnDone (fn, action, connectDetails, queryOrObj, paramsOrCallback, callback) {
let thisConn
const nf = new notifyModule.NotifyFactory()
const args = nf.getChunkyArgs(paramsOrCallback, callback)
const notify = new nf.StreamEvents()
const complete = (err, res, more) => {
if (!more && thisConn !== null) {
thisConn.close(() => {
notify.emit('closed', notify.getQueryId())
if (args.callback !== null) {
args.callback(err, res, more)
}
})
} else {
if (args.callback !== null) {
args.callback(err, res, more)
}
}
}
const args2 = {
params: args.params,
callback: complete
}
const go = (err, conn) => {
notify.setConn(conn)
notify.setQueryObj(queryOrObj)
thisConn = conn
notify.emit('open', notify.getQueryId())
if (err) {
args2.callback(err, null)
} else {
action(conn, notify, args2)
}
}
nf.validateQuery(queryOrObj, true, fn)
openFrom(fn, connectDetails, go)
return notify
}
function query (connectDetails, queryOrObj, paramsOrCallback, callback) {
return queryCloseOnDone('query', (conn, notify, args) => conn.queryNotify(notify, queryOrObj, args), connectDetails, queryOrObj, paramsOrCallback, callback)
}
function queryRaw (connectDetails, queryOrObj, paramsOrCallback, callback) {
return queryCloseOnDone('queryRaw', (conn, notify, args) => conn.queryRawNotify(notify, queryOrObj, args), connectDetails, queryOrObj, paramsOrCallback, callback)
}
function open (params, callback) {
return openFrom('open', params, callback)
}
return {
meta: sqlMeta,
userTypes: userTypes,
query: query,
queryRaw: queryRaw,
open: open
}
})())
exports.connectionModule = connectionModule