UNPKG

msnodesqlv8

Version:

Microsoft Driver for Node.js SQL Server compatible with all versions of Node.

385 lines (320 loc) 10.7 kB
/** * Created by Stephen on 9/28/2015. */ 'use strict' // private const { driverModule } = require('./driver') const { procedureModule } = require('./procedure') const { notifyModule } = require('./notifier') const { tableModule } = require('./table') const { utilModule } = require('./util') const { BasePromises } = require('./base-promises') const { PreparedStatement } = require('./prepared-statement') const cppDriver = new utilModule.Native().cppDriver class PrivateConnection { constructor (sqlMeta, userTypes, parentFn, p, cb, id) { this.parentFn = parentFn this.callback2 = cb this.native = new cppDriver.Connection() this.driverMgr = new driverModule.DriverMgr(this.native) this.nf = new notifyModule.NotifyFactory() this.connection = new ConnectionWrapper(sqlMeta, userTypes, this.driverMgr, this.defaultCallback, id) this.connection.setUseUTC(true) this.connectObj = p this.version = this.decodeSqlServerVersion(this.connectObj.conn_str) this.connection.setDriverVersion(this.version) this.id = this.connection.id } defaultCallback (err) { if (err) { throw new Error(err) } } queueCb (err) { setImmediate(() => { if (Array.isArray(err) && err.length === 1) { this.callback2(err[0], this.connection) } else { this.callback2(err, this.connection) } }) } validationParams () { return [ new this.nf.LexicalParam('string', this.connectObj.conn_str, 'connection string'), new this.nf.LexicalParam('function', this.callback2, 'callback') ] } open () { this.nf.validateParameters(this.validationParams(), this.parentFn) // this.callback2 = this.callback2 || this.defaultCallback this.native.open(this.connectObj, (e, c) => { this.queueCb(e, c) }) } decodeSqlServerVersion (connectionString) { const myRegexp = /Driver=\{ODBC Driver (.*?) for SQL Server}.*$/g const match = myRegexp.exec(connectionString) return match !== null ? parseInt(match[1]) : 17 } } class ConnectionWrapperPromises extends BasePromises { constructor (connection) { super() this.connection = connection this.me = this this.tm = connection.tableMgr() this.pm = connection.procedureMgr() this.aggregator = new utilModule.QueryAggregator(connection) } async callProc (name, params, options) { return this.aggregator.callProc(name, params, options) } async query (sql, params, options) { return this.aggregator.query(sql, params, options) } async getTable (name) { return this.op(cb => this.tm.getTable(name, cb)) } async getProc (name) { return this.op(cb => this.pm.getProc(name, cb)) } async getUserTypeTable (type) { return this.op(cb => this.connection.getUserTypeTable(type, cb)) } async cancel (q) { return this.op(cb => this.connection.cancel(q, cb)) } async close () { return this.op(cb => this.connection.close(cb)) } async prepare (sqlQuery) { return this.op(cb => this.connection.prepare(sqlQuery, cb)) } async beginTransaction () { return this.op(cb => this.connection.beginTransaction(cb)) } async commit () { return this.op(cb => this.connection.commit(cb)) } async rollback () { return this.op(cb => this.connection.rollback(cb)) } } class ConnectionWrapper { constructor (sqlMeta, userTypes, driver, defCb, name) { this.defaultCallback = defCb this.id = name this.driverMgr = driver this.inst = this this.notifier = new notifyModule.NotifyFactory() this.nextQueryId = 0 this.dead = false this.useUTC = true this.driverVersion = 0 this.maxPreparedColumnSize = null this.useNumericString = false this.procedureCache = null this.tableCache = null this.tables = new tableModule.TableMgr(this, sqlMeta, userTypes, this.tableCache) this.procedures = new procedureModule.ProcedureMgr(this, this.notifier, this.driverMgr, sqlMeta, this.procedureCache) this.promises = new ConnectionWrapperPromises(this) } setSharedCache (pc, tc) { this.procedureCache = pc this.tableCache = tc } getUserTypeTable (name, callback) { this.tables.getUserTypeTable(name, callback) } tableMgr () { return this.tables } getMaxPreparedColumnSize () { return this.maxPreparedColumnSize } setMaxPreparedColumnSize (m) { this.maxPreparedColumnSize = m } getUseUTC () { return this.useUTC } setDriverVersion (v) { this.driverVersion = v if (this.driverVersion > 0) { this.tables.setBcpVersion(this.driverVersion) } } getDriverVersion () { return this.driverVersion } setUseUTC (utc) { this.useUTC = utc this.driverMgr.setUseUTC(utc) } getUseNumericString () { return this.useNumericString } setUseNumericString (uns) { this.useNumericString = uns } procedureMgr () { return this.procedures } isClosed () { return this.dead } close (immediately, callback) { if (this.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 || this.defaultCallback this.dead = true this.driverMgr.close(err => { setImmediate(() => { this.driverMgr.emptyQueue() callback(err, null) }) }) } queryRawNotify (notify, queryOrObj, chunky) { const queryObj = this.notifier.validateQuery(queryOrObj, this.useUTC, 'queryRaw') if (!Object.hasOwnProperty.call(queryObj, 'numeric_string')) { queryObj.numeric_string = this.useNumericString } this.driverMgr.readAllQuery(notify, queryObj, chunky.params, chunky.callback) } queryNotify (notify, queryOrObj, chunky) { this.notifier.validateQuery(queryOrObj, this.useUTC, 'query') const onQueryRaw = (err, results, more) => { if (chunky.callback) { if (err) { chunky.callback(err, null, more) } else { chunky.callback(err, this.driverMgr.objectify(results), more) } } } if (chunky.callback) { this.queryRawNotify(notify, queryOrObj, this.notifier.getChunkyArgs(chunky.params, (err, results, more) => { setImmediate(() => { onQueryRaw(err, results, more) }) })) } else { this.queryRawNotify(notify, queryOrObj, chunky) } } getNotify (queryOrObj) { const qid = this.nextQueryId++ const notify = new this.notifier.StreamEvents() notify.setQueryId(qid) notify.setConn(this.inst) notify.setQueryObj(queryOrObj) return notify } queryRaw (queryOrObj, paramsOrCallback, callback) { if (this.dead) { throw new Error('[msnodesql] Connection is closed.') } const notify = this.getNotify(queryOrObj) const chunky = this.notifier.getChunkyArgs(paramsOrCallback, callback) if (!chunky.callback) { this.queryRawNotify(notify, queryOrObj, chunky) } else { this.queryRawNotify(notify, queryOrObj, this.notifier.getChunkyArgs(chunky.params, (err, results, more) => { setImmediate(() => { chunky.callback(err, results, more) }) })) } return notify } query (queryOrObj, paramsOrCallback, callback) { if (this.dead) { throw new Error('[msnodesql] Connection is closed.') } const notify = this.getNotify(queryOrObj) const chunky = this.notifier.getChunkyArgs(paramsOrCallback, callback) this.queryNotify(notify, queryOrObj, chunky) return notify } beginTransaction (callback) { if (this.dead) { throw new Error('[msnodesql] Connection is closed.') } callback = callback || this.defaultCallback this.driverMgr.beginTransaction(callback) } cancelQuery (notify, callback) { if (this.dead) { throw new Error('[msnodesql] Connection is closed.') } const qo = notify.getQueryObj() const polling = qo.query_polling || false callback = callback || this.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 { this.driverMgr.cancel(notify, (e) => { notify.emit('done') callback(e, null) }) } } commit (callback) { if (this.dead) { throw new Error('[msnodesql] Connection is closed.') } callback = callback || this.defaultCallback this.driverMgr.commit(callback) } rollback (callback) { if (this.dead) { throw new Error('[msnodesql] Connection is closed.') } callback = callback || this.defaultCallback this.driverMgr.rollback(callback) } // inform driver to prepare the sql statement and reserve it for repeated use with parameters. prepare (queryOrObj, callback) { const notify = this.getNotify(queryOrObj) notify.setPrepared() const chunky = this.notifier.getChunkyArgs(callback) const queryObj = this.notifier.validateQuery(queryOrObj, this.useUTC, 'prepare') if (!Object.hasOwnProperty.call(queryObj, 'numeric_string')) { queryObj.numeric_string = this.useNumericString } if (!Object.hasOwnProperty.call(queryObj, 'max_prepared_column_size')) { if (this.maxPreparedColumnSize) { queryObj.max_prepared_column_size = this.maxPreparedColumnSize } } const onPrepare = (err, meta) => { const prepared = new PreparedStatement(this.notifier, this.driverMgr, queryObj.query_str, this.inst, notify, meta) chunky.callback(err, prepared) } this.driverMgr.prepare(notify, queryObj, onPrepare) return notify } callproc (name, paramsOrCb, cb) { return this.procedures.callproc(name, paramsOrCb, cb) } getTable (name, cb) { this.tables.getTable(name, cb) } // returns a promise of aggregated results not a query async callprocAggregator (name, params, options) { return this.promises.callProc(name, params, options) } } exports.PrivateConnection = PrivateConnection