knex
Version:
A batteries-included SQL query & schema builder for Postgres, MySQL and SQLite3 and the Browser
414 lines (329 loc) • 13.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _lodash = require("lodash");
var _inherits = _interopRequireDefault(require("inherits"));
var _client = _interopRequireDefault(require("../../client"));
var _bluebird = _interopRequireDefault(require("bluebird"));
var _formatter = _interopRequireDefault(require("../../formatter"));
var _transaction = _interopRequireDefault(require("./transaction"));
var _compiler = _interopRequireDefault(require("./query/compiler"));
var _compiler2 = _interopRequireDefault(require("./schema/compiler"));
var _tablecompiler = _interopRequireDefault(require("./schema/tablecompiler"));
var _columncompiler = _interopRequireDefault(require("./schema/columncompiler"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
const isArray = Array.isArray;
const SQL_INT4 = {
MIN: -2147483648,
MAX: 2147483647
};
const SQL_BIGINT_SAFE = {
MIN: -9007199254740991,
MAX: 9007199254740991
}; // Always initialize with the "QueryBuilder" and "QueryCompiler" objects, which
// extend the base 'lib/query/builder' and 'lib/query/compiler', respectively.
function Client_MSSQL(config = {}) {
// #1235 mssql module wants 'server', not 'host'. This is to enforce the same
// options object across all dialects.
if (config && config.connection && config.connection.host) {
config.connection.server = config.connection.host;
} // mssql always creates pool :( lets try to unpool it as much as possible
this.mssqlPoolSettings = {
min: 1,
max: 1,
idleTimeoutMillis: Number.MAX_SAFE_INTEGER,
evictionRunIntervalMillis: 0
};
_client.default.call(this, config);
}
(0, _inherits.default)(Client_MSSQL, _client.default);
(0, _lodash.assign)(Client_MSSQL.prototype, {
dialect: 'mssql',
driverName: 'mssql',
_driver() {
const tds = require('tedious');
const mssqlTedious = require('mssql');
const base = require('mssql/lib/base'); // Monkey patch mssql's tedious driver _poolCreate method to fix problem with hanging acquire
// connection, this should be removed when https://github.com/tediousjs/node-mssql/pull/614 is
// merged and released.
// Also since this dialect actually always uses tedious driver (msnodesqlv8 driver should be
// required in different way), it might be better to use tedious directly, because mssql
// driver uses always internally extra generic-pool and just adds one unnecessary layer of
// indirection between database and knex and mssql driver has been lately without maintainer
// (changing implementation to use tedious will be breaking change though).
// TODO: remove mssql implementation all together and use tedious directly
/* istanbul ignore next */
const mssqlVersion = require('mssql/package.json').version;
/* istanbul ignore next */
if (mssqlVersion === '4.1.0') {
mssqlTedious.ConnectionPool.prototype.release = release;
mssqlTedious.ConnectionPool.prototype._poolCreate = _poolCreate;
} else {
const _mssqlVersion$split = mssqlVersion.split('.'),
_mssqlVersion$split2 = _slicedToArray(_mssqlVersion$split, 1),
major = _mssqlVersion$split2[0]; // if version is not ^5.0.0
if (major < 5) {
throw new Error('This knex version only supports mssql driver versions 4.1.0 and 5.0.0+');
}
}
/* istanbul ignore next */
// in some rare situations release is called when stream is interrupted, but
// after pool is already destroyed
function release(connection) {
if (this.pool) {
this.pool.release(connection);
}
}
/* istanbul ignore next */
function _poolCreate() {
// implementation is copy-pasted from https://github.com/tediousjs/node-mssql/pull/614
return new base.Promise((resolve, reject) => {
const cfg = {
userName: this.config.user,
password: this.config.password,
server: this.config.server,
options: Object.assign({}, this.config.options),
domain: this.config.domain
};
cfg.options.database = this.config.database;
cfg.options.port = this.config.port;
cfg.options.connectTimeout = this.config.connectionTimeout || this.config.timeout || 15000;
cfg.options.requestTimeout = this.config.requestTimeout != null ? this.config.requestTimeout : 15000;
cfg.options.tdsVersion = cfg.options.tdsVersion || '7_4';
cfg.options.rowCollectionOnDone = false;
cfg.options.rowCollectionOnRequestCompletion = false;
cfg.options.useColumnNames = false;
cfg.options.appName = cfg.options.appName || 'node-mssql'; // tedious always connect via tcp when port is specified
if (cfg.options.instanceName) delete cfg.options.port;
if (isNaN(cfg.options.requestTimeout)) cfg.options.requestTimeout = 15000;
if (cfg.options.requestTimeout === Infinity) cfg.options.requestTimeout = 0;
if (cfg.options.requestTimeout < 0) cfg.options.requestTimeout = 0;
if (this.config.debug) {
cfg.options.debug = {
packet: true,
token: true,
data: true,
payload: true
};
}
const tedious = new tds.Connection(cfg); // prevent calling resolve again on end event
let alreadyResolved = false;
function safeResolve(err) {
if (!alreadyResolved) {
alreadyResolved = true;
resolve(err);
}
}
function safeReject(err) {
if (!alreadyResolved) {
alreadyResolved = true;
reject(err);
}
}
tedious.once('end', evt => {
safeReject(new base.ConnectionError('Connection ended unexpectedly during connecting'));
});
tedious.once('connect', err => {
if (err) {
err = new base.ConnectionError(err);
return safeReject(err);
}
safeResolve(tedious);
});
tedious.on('error', err => {
if (err.code === 'ESOCKET') {
tedious.hasError = true;
return;
}
this.emit('error', err);
});
if (this.config.debug) {
tedious.on('debug', this.emit.bind(this, 'debug', tedious));
}
});
}
return mssqlTedious;
},
formatter() {
return new MSSQL_Formatter(this, ...arguments);
},
transaction() {
return new _transaction.default(this, ...arguments);
},
queryCompiler() {
return new _compiler.default(this, ...arguments);
},
schemaCompiler() {
return new _compiler2.default(this, ...arguments);
},
tableCompiler() {
return new _tablecompiler.default(this, ...arguments);
},
columnCompiler() {
return new _columncompiler.default(this, ...arguments);
},
wrapIdentifierImpl(value) {
return value !== '*' ? `[${value.replace(/\[/g, '[')}]` : '*';
},
// Get a raw connection, called by the `pool` whenever a new
// connection needs to be added to the pool.
acquireRawConnection() {
return new _bluebird.default((resolver, rejecter) => {
const settings = Object.assign({}, this.connectionSettings);
settings.pool = this.mssqlPoolSettings;
const connection = new this.driver.ConnectionPool(settings);
connection.connect(err => {
if (err) {
return rejecter(err);
}
connection.on('error', err => {
connection.__knex__disposed = err;
});
resolver(connection);
});
});
},
validateConnection(connection) {
if (connection.connected === true) {
return true;
}
return false;
},
// Used to explicitly close a connection, called internally by the pool
// when a connection times out or the pool is shutdown.
destroyRawConnection(connection) {
return connection.close().catch(err => {// some times close will reject just because pool has already been destoyed
// internally by the driver there is nothing we can do in this case
});
},
// Position the bindings for the query.
positionBindings(sql) {
let questionCount = -1;
return sql.replace(/\?/g, function () {
questionCount += 1;
return `@p${questionCount}`;
});
},
// Grab a connection, run the query via the MSSQL streaming interface,
// and pass that through to the stream we've sent back to the client.
_stream(connection, obj, stream) {
if (!obj || typeof obj === 'string') obj = {
sql: obj
};
return new _bluebird.default((resolver, rejecter) => {
stream.on('error', err => {
rejecter(err);
});
stream.on('end', resolver);
const _obj = obj,
sql = _obj.sql;
if (!sql) return resolver();
const req = (connection.tx_ || connection).request(); //req.verbose = true;
req.multiple = true;
req.stream = true;
if (obj.bindings) {
for (let i = 0; i < obj.bindings.length; i++) {
this._setReqInput(req, i, obj.bindings[i]);
}
}
req.pipe(stream);
req.query(sql);
});
},
// Runs the query on the specified connection, providing the bindings
// and any other necessary prep work.
_query(connection, obj) {
const client = this;
if (!obj || typeof obj === 'string') obj = {
sql: obj
};
return new _bluebird.default((resolver, rejecter) => {
const _obj2 = obj,
sql = _obj2.sql;
if (!sql) return resolver();
const req = (connection.tx_ || connection).request(); // req.verbose = true;
req.multiple = true;
if (obj.bindings) {
for (let i = 0; i < obj.bindings.length; i++) {
client._setReqInput(req, i, obj.bindings[i]);
}
}
req.query(sql, (err, recordset) => {
if (err) {
return rejecter(err);
}
obj.response = recordset.recordsets[0];
resolver(obj);
});
});
},
// sets a request input parameter. Detects bigints and decimals and sets type appropriately.
_setReqInput(req, i, binding) {
if (typeof binding == 'number') {
if (binding % 1 !== 0) {
req.input(`p${i}`, this.driver.Decimal(38, 10), binding);
} else if (binding < SQL_INT4.MIN || binding > SQL_INT4.MAX) {
if (binding < SQL_BIGINT_SAFE.MIN || binding > SQL_BIGINT_SAFE.MAX) {
throw new Error(`Bigint must be safe integer or must be passed as string, saw ${binding}`);
}
req.input(`p${i}`, this.driver.BigInt, binding);
} else {
req.input(`p${i}`, this.driver.Int, binding);
}
} else {
req.input(`p${i}`, binding);
}
},
// Process the response as returned from the query.
processResponse(obj, runner) {
if (obj == null) return;
const response = obj.response,
method = obj.method;
if (obj.output) return obj.output.call(runner, response);
switch (method) {
case 'select':
case 'pluck':
case 'first':
if (method === 'pluck') return (0, _lodash.map)(response, obj.pluck);
return method === 'first' ? response[0] : response;
case 'insert':
case 'del':
case 'update':
case 'counter':
if (obj.returning) {
if (obj.returning === '@@rowcount') {
return response[0][''];
}
if (isArray(obj.returning) && obj.returning.length > 1 || obj.returning[0] === '*') {
return response;
} // return an array with values if only one returning value was specified
return (0, _lodash.flatten)((0, _lodash.map)(response, _lodash.values));
}
return response;
default:
return response;
}
}
});
class MSSQL_Formatter extends _formatter.default {
// Accepts a string or array of columns to wrap as appropriate.
columnizeWithPrefix(prefix, target) {
const columns = typeof target === 'string' ? [target] : target;
let str = '',
i = -1;
while (++i < columns.length) {
if (i > 0) str += ', ';
str += prefix + this.wrap(columns[i]);
}
return str;
}
}
var _default = Client_MSSQL;
exports.default = _default;
module.exports = exports.default;