Initial commit
This commit is contained in:
500
node_modules/knex/lib/dialects/mssql/index.js
generated
vendored
Executable file
500
node_modules/knex/lib/dialects/mssql/index.js
generated
vendored
Executable file
@@ -0,0 +1,500 @@
|
||||
// MSSQL Client
|
||||
// -------
|
||||
const map = require('lodash/map');
|
||||
const isNil = require('lodash/isNil');
|
||||
|
||||
const Client = require('../../client');
|
||||
const MSSQL_Formatter = require('./mssql-formatter');
|
||||
const Transaction = require('./transaction');
|
||||
const QueryCompiler = require('./query/mssql-querycompiler');
|
||||
const SchemaCompiler = require('./schema/mssql-compiler');
|
||||
const TableCompiler = require('./schema/mssql-tablecompiler');
|
||||
const ViewCompiler = require('./schema/mssql-viewcompiler');
|
||||
const ColumnCompiler = require('./schema/mssql-columncompiler');
|
||||
const QueryBuilder = require('../../query/querybuilder');
|
||||
const { setHiddenProperty } = require('../../util/security');
|
||||
|
||||
const debug = require('debug')('knex:mssql');
|
||||
|
||||
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.
|
||||
class Client_MSSQL extends Client {
|
||||
constructor(config = {}) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('knex').Config} options
|
||||
*/
|
||||
_generateConnection() {
|
||||
const settings = this.connectionSettings;
|
||||
settings.options = settings.options || {};
|
||||
|
||||
/** @type {import('tedious').ConnectionConfig} */
|
||||
const cfg = {
|
||||
authentication: {
|
||||
type: settings.type || 'default',
|
||||
options: {
|
||||
userName: settings.userName || settings.user,
|
||||
password: settings.password,
|
||||
domain: settings.domain,
|
||||
token: settings.token,
|
||||
clientId: settings.clientId,
|
||||
clientSecret: settings.clientSecret,
|
||||
tenantId: settings.tenantId,
|
||||
msiEndpoint: settings.msiEndpoint,
|
||||
},
|
||||
},
|
||||
server: settings.server || settings.host,
|
||||
options: {
|
||||
database: settings.database,
|
||||
encrypt: settings.encrypt || false,
|
||||
port: settings.port || 1433,
|
||||
connectTimeout: settings.connectionTimeout || settings.timeout || 15000,
|
||||
requestTimeout: !isNil(settings.requestTimeout)
|
||||
? settings.requestTimeout
|
||||
: 15000,
|
||||
rowCollectionOnDone: false,
|
||||
rowCollectionOnRequestCompletion: false,
|
||||
useColumnNames: false,
|
||||
tdsVersion: settings.options.tdsVersion || '7_4',
|
||||
appName: settings.options.appName || 'knex',
|
||||
trustServerCertificate: false,
|
||||
...settings.options,
|
||||
},
|
||||
};
|
||||
|
||||
if (cfg.authentication.options.password) {
|
||||
setHiddenProperty(cfg.authentication.options);
|
||||
}
|
||||
|
||||
// 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 (settings.debug) {
|
||||
cfg.options.debug = {
|
||||
packet: true,
|
||||
token: true,
|
||||
data: true,
|
||||
payload: true,
|
||||
};
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
_driver() {
|
||||
const tds = require('tedious');
|
||||
|
||||
return tds;
|
||||
}
|
||||
|
||||
formatter() {
|
||||
return new MSSQL_Formatter(this, ...arguments);
|
||||
}
|
||||
|
||||
transaction() {
|
||||
return new Transaction(this, ...arguments);
|
||||
}
|
||||
|
||||
queryCompiler() {
|
||||
return new QueryCompiler(this, ...arguments);
|
||||
}
|
||||
|
||||
schemaCompiler() {
|
||||
return new SchemaCompiler(this, ...arguments);
|
||||
}
|
||||
|
||||
tableCompiler() {
|
||||
return new TableCompiler(this, ...arguments);
|
||||
}
|
||||
|
||||
viewCompiler() {
|
||||
return new ViewCompiler(this, ...arguments);
|
||||
}
|
||||
queryBuilder() {
|
||||
const b = new QueryBuilder(this);
|
||||
return b;
|
||||
}
|
||||
|
||||
columnCompiler() {
|
||||
return new ColumnCompiler(this, ...arguments);
|
||||
}
|
||||
|
||||
wrapIdentifierImpl(value) {
|
||||
if (value === '*') {
|
||||
return '*';
|
||||
}
|
||||
|
||||
return `[${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 Promise((resolver, rejecter) => {
|
||||
debug('connection::connection new connection requested');
|
||||
const Driver = this._driver();
|
||||
const settings = Object.assign({}, this._generateConnection());
|
||||
|
||||
const connection = new Driver.Connection(settings);
|
||||
|
||||
connection.connect((err) => {
|
||||
if (err) {
|
||||
debug('connection::connect error: %s', err.message);
|
||||
return rejecter(err);
|
||||
}
|
||||
|
||||
debug('connection::connect connected to server');
|
||||
|
||||
connection.connected = true;
|
||||
connection.on('error', (e) => {
|
||||
debug('connection::error message=%s', e.message);
|
||||
connection.__knex__disposed = e;
|
||||
connection.connected = false;
|
||||
});
|
||||
|
||||
connection.once('end', () => {
|
||||
connection.connected = false;
|
||||
connection.__knex__disposed = 'Connection to server was terminated.';
|
||||
debug('connection::end connection ended.');
|
||||
});
|
||||
|
||||
return resolver(connection);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
validateConnection(connection) {
|
||||
return connection && connection.connected;
|
||||
}
|
||||
|
||||
// Used to explicitly close a connection, called internally by the pool
|
||||
// when a connection times out or the pool is shutdown.
|
||||
destroyRawConnection(connection) {
|
||||
debug('connection::destroy');
|
||||
|
||||
return new Promise((resolve) => {
|
||||
connection.once('end', () => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
connection.close();
|
||||
});
|
||||
}
|
||||
|
||||
// Position the bindings for the query.
|
||||
positionBindings(sql) {
|
||||
let questionCount = -1;
|
||||
return sql.replace(/\\?\?/g, (match) => {
|
||||
if (match === '\\?') {
|
||||
return '?';
|
||||
}
|
||||
|
||||
questionCount += 1;
|
||||
return `@p${questionCount}`;
|
||||
});
|
||||
}
|
||||
|
||||
_chomp(connection) {
|
||||
if (connection.state.name === 'LoggedIn') {
|
||||
const nextRequest = this.requestQueue.pop();
|
||||
if (nextRequest) {
|
||||
debug(
|
||||
'connection::query executing query, %d more in queue',
|
||||
this.requestQueue.length
|
||||
);
|
||||
|
||||
connection.execSql(nextRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_enqueueRequest(request, connection) {
|
||||
this.requestQueue.push(request);
|
||||
this._chomp(connection);
|
||||
}
|
||||
|
||||
_makeRequest(query, callback) {
|
||||
const Driver = this._driver();
|
||||
const sql = typeof query === 'string' ? query : query.sql;
|
||||
let rowCount = 0;
|
||||
|
||||
if (!sql) throw new Error('The query is empty');
|
||||
|
||||
debug('request::request sql=%s', sql);
|
||||
|
||||
const request = new Driver.Request(sql, (err, remoteRowCount) => {
|
||||
if (err) {
|
||||
debug('request::error message=%s', err.message);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
rowCount = remoteRowCount;
|
||||
debug('request::callback rowCount=%d', rowCount);
|
||||
});
|
||||
|
||||
request.on('prepared', () => {
|
||||
debug('request %s::request prepared', this.id);
|
||||
});
|
||||
|
||||
request.on('done', (rowCount, more) => {
|
||||
debug('request::done rowCount=%d more=%s', rowCount, more);
|
||||
});
|
||||
|
||||
request.on('doneProc', (rowCount, more) => {
|
||||
debug(
|
||||
'request::doneProc id=%s rowCount=%d more=%s',
|
||||
request.id,
|
||||
rowCount,
|
||||
more
|
||||
);
|
||||
});
|
||||
|
||||
request.on('doneInProc', (rowCount, more) => {
|
||||
debug(
|
||||
'request::doneInProc id=%s rowCount=%d more=%s',
|
||||
request.id,
|
||||
rowCount,
|
||||
more
|
||||
);
|
||||
});
|
||||
|
||||
request.once('requestCompleted', () => {
|
||||
debug('request::completed id=%s', request.id);
|
||||
return callback(null, rowCount);
|
||||
});
|
||||
|
||||
request.on('error', (err) => {
|
||||
debug('request::error id=%s message=%s', request.id, err.message);
|
||||
return callback(err);
|
||||
});
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
// 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, query, /** @type {NodeJS.ReadWriteStream} */ stream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = this._makeRequest(query, (err) => {
|
||||
if (err) {
|
||||
stream.emit('error', err);
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
|
||||
request.on('row', (row) => {
|
||||
stream.write(
|
||||
row.reduce(
|
||||
(prev, curr) => ({
|
||||
...prev,
|
||||
[curr.metadata.colName]: curr.value,
|
||||
}),
|
||||
{}
|
||||
)
|
||||
);
|
||||
});
|
||||
request.on('error', (err) => {
|
||||
stream.emit('error', err);
|
||||
reject(err);
|
||||
});
|
||||
request.once('requestCompleted', () => {
|
||||
stream.end();
|
||||
resolve();
|
||||
});
|
||||
|
||||
this._assignBindings(request, query.bindings);
|
||||
this._enqueueRequest(request, connection);
|
||||
});
|
||||
}
|
||||
|
||||
_assignBindings(request, bindings) {
|
||||
if (Array.isArray(bindings)) {
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
const binding = bindings[i];
|
||||
this._setReqInput(request, i, binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_scaleForBinding(binding) {
|
||||
if (binding % 1 === 0) {
|
||||
throw new Error(`The binding value ${binding} must be a decimal number.`);
|
||||
}
|
||||
|
||||
return { scale: 10 };
|
||||
}
|
||||
|
||||
_typeForBinding(binding) {
|
||||
const Driver = this._driver();
|
||||
|
||||
if (
|
||||
this.connectionSettings.options &&
|
||||
this.connectionSettings.options.mapBinding
|
||||
) {
|
||||
const result = this.connectionSettings.options.mapBinding(binding);
|
||||
if (result) {
|
||||
return [result.value, result.type];
|
||||
}
|
||||
}
|
||||
|
||||
switch (typeof binding) {
|
||||
case 'string':
|
||||
return [binding, Driver.TYPES.NVarChar];
|
||||
case 'boolean':
|
||||
return [binding, Driver.TYPES.Bit];
|
||||
case 'number': {
|
||||
if (binding % 1 !== 0) {
|
||||
return [binding, Driver.TYPES.Float];
|
||||
}
|
||||
|
||||
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}`
|
||||
);
|
||||
}
|
||||
|
||||
return [binding, Driver.TYPES.BigInt];
|
||||
}
|
||||
|
||||
return [binding, Driver.TYPES.Int];
|
||||
}
|
||||
default: {
|
||||
if (binding instanceof Date) {
|
||||
return [binding, Driver.TYPES.DateTime];
|
||||
}
|
||||
|
||||
if (binding instanceof Buffer) {
|
||||
return [binding, Driver.TYPES.VarBinary];
|
||||
}
|
||||
|
||||
return [binding, Driver.TYPES.NVarChar];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Runs the query on the specified connection, providing the bindings
|
||||
// and any other necessary prep work.
|
||||
_query(connection, query) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const rows = [];
|
||||
const request = this._makeRequest(query, (err, count) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
query.response = rows;
|
||||
|
||||
process.nextTick(() => this._chomp(connection));
|
||||
|
||||
resolve(query);
|
||||
});
|
||||
|
||||
request.on('row', (row) => {
|
||||
debug('request::row');
|
||||
rows.push(row);
|
||||
});
|
||||
|
||||
this._assignBindings(request, query.bindings);
|
||||
this._enqueueRequest(request, connection);
|
||||
});
|
||||
}
|
||||
|
||||
// sets a request input parameter. Detects bigints and decimals and sets type appropriately.
|
||||
_setReqInput(req, i, inputBinding) {
|
||||
const [binding, tediousType] = this._typeForBinding(inputBinding);
|
||||
const bindingName = 'p'.concat(i);
|
||||
let options;
|
||||
|
||||
if (typeof binding === 'number' && binding % 1 !== 0) {
|
||||
options = this._scaleForBinding(binding);
|
||||
}
|
||||
|
||||
debug(
|
||||
'request::binding pos=%d type=%s value=%s',
|
||||
i,
|
||||
tediousType.name,
|
||||
binding
|
||||
);
|
||||
|
||||
if (Buffer.isBuffer(binding)) {
|
||||
options = {
|
||||
length: 'max',
|
||||
};
|
||||
}
|
||||
|
||||
req.addParameter(bindingName, tediousType, binding, options);
|
||||
}
|
||||
|
||||
// Process the response as returned from the query.
|
||||
processResponse(query, runner) {
|
||||
if (query == null) return;
|
||||
let { response } = query;
|
||||
const { method } = query;
|
||||
|
||||
if (query.output) {
|
||||
return query.output.call(runner, response);
|
||||
}
|
||||
|
||||
response = response.map((row) =>
|
||||
row.reduce((columns, r) => {
|
||||
const colName = r.metadata.colName;
|
||||
|
||||
if (columns[colName]) {
|
||||
if (!Array.isArray(columns[colName])) {
|
||||
columns[colName] = [columns[colName]];
|
||||
}
|
||||
|
||||
columns[colName].push(r.value);
|
||||
} else {
|
||||
columns[colName] = r.value;
|
||||
}
|
||||
|
||||
return columns;
|
||||
}, {})
|
||||
);
|
||||
|
||||
if (query.output) return query.output.call(runner, response);
|
||||
switch (method) {
|
||||
case 'select':
|
||||
return response;
|
||||
case 'first':
|
||||
return response[0];
|
||||
case 'pluck':
|
||||
return map(response, query.pluck);
|
||||
case 'insert':
|
||||
case 'del':
|
||||
case 'update':
|
||||
case 'counter':
|
||||
if (query.returning) {
|
||||
if (query.returning === '@@rowcount') {
|
||||
return response[0][''];
|
||||
}
|
||||
}
|
||||
return response;
|
||||
default:
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(Client_MSSQL.prototype, {
|
||||
requestQueue: [],
|
||||
|
||||
dialect: 'mssql',
|
||||
|
||||
driverName: 'mssql',
|
||||
});
|
||||
|
||||
module.exports = Client_MSSQL;
|
||||
Reference in New Issue
Block a user