2015-12-08 11:37:31 -06:00
|
|
|
|
|
|
|
// MSSQL Client
|
|
|
|
// -------
|
2016-05-18 20:22:50 +10:00
|
|
|
import { assign, map, flatten, values } from 'lodash'
|
2016-05-17 01:01:34 +10:00
|
|
|
import inherits from 'inherits';
|
2016-03-02 16:52:32 +01:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
import Client from '../../client';
|
2016-08-09 17:23:07 -04:00
|
|
|
import Promise from 'bluebird';
|
2016-05-17 01:01:34 +10:00
|
|
|
import * as helpers from '../../helpers';
|
2015-12-08 11:37:31 -06:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
import Transaction from './transaction';
|
|
|
|
import QueryCompiler from './query/compiler';
|
|
|
|
import SchemaCompiler from './schema/compiler';
|
|
|
|
import TableCompiler from './schema/tablecompiler';
|
|
|
|
import ColumnCompiler from './schema/columncompiler';
|
2015-12-08 11:37:31 -06:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
const { isArray } = Array;
|
2015-12-08 11:37:31 -06:00
|
|
|
|
2016-05-26 22:27:55 +02:00
|
|
|
const SQL_INT4 = { MIN : -2147483648, MAX: 2147483647}
|
|
|
|
const SQL_BIGINT_SAFE = { MIN : -9007199254740991, MAX: 9007199254740991}
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
// Always initialize with the "QueryBuilder" and "QueryCompiler" objects, which
|
|
|
|
// extend the base 'lib/query/builder' and 'lib/query/compiler', respectively.
|
2015-12-08 11:37:31 -06:00
|
|
|
function Client_MSSQL(config) {
|
2016-05-17 01:01:34 +10:00
|
|
|
// #1235 mssql module wants 'server', not 'host'. This is to enforce the same
|
|
|
|
// options object across all dialects.
|
2016-03-01 19:29:39 +01:00
|
|
|
if(config && config.connection && config.connection.host) {
|
|
|
|
config.connection.server = config.connection.host;
|
|
|
|
}
|
2015-12-08 11:37:31 -06:00
|
|
|
Client.call(this, config);
|
|
|
|
}
|
|
|
|
inherits(Client_MSSQL, Client);
|
|
|
|
|
|
|
|
assign(Client_MSSQL.prototype, {
|
|
|
|
|
|
|
|
dialect: 'mssql',
|
|
|
|
|
|
|
|
driverName: 'mssql',
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
_driver() {
|
2015-12-08 11:37:31 -06:00
|
|
|
return require('mssql');
|
|
|
|
},
|
2015-12-15 16:54:43 -05:00
|
|
|
|
2016-09-12 18:45:35 -04:00
|
|
|
transaction() {
|
|
|
|
return new Transaction(this, ...arguments)
|
|
|
|
},
|
2015-12-15 16:54:43 -05:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
QueryCompiler,
|
2016-03-08 08:41:13 +01:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
SchemaCompiler,
|
2015-12-08 11:37:31 -06:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
TableCompiler,
|
2015-12-08 11:37:31 -06:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
ColumnCompiler,
|
2015-12-08 11:37:31 -06:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
wrapIdentifier(value) {
|
|
|
|
return (value !== '*' ? `[${value.replace(/\[/g, '\[')}]` : '*')
|
2015-12-08 11:37:31 -06:00
|
|
|
},
|
|
|
|
|
|
|
|
// Get a raw connection, called by the `pool` whenever a new
|
|
|
|
// connection needs to be added to the pool.
|
2016-05-17 01:01:34 +10:00
|
|
|
acquireRawConnection() {
|
|
|
|
const client = this;
|
|
|
|
const connection = new this.driver.Connection(this.connectionSettings);
|
2015-12-08 11:37:31 -06:00
|
|
|
return new Promise(function(resolver, rejecter) {
|
|
|
|
connection.connect(function(err) {
|
|
|
|
if (err) return rejecter(err);
|
|
|
|
connection.on('error', connectionErrorHandler.bind(null, client, connection));
|
|
|
|
connection.on('end', connectionErrorHandler.bind(null, client, connection));
|
|
|
|
resolver(connection);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
2015-12-15 16:54:43 -05:00
|
|
|
|
2015-12-08 11:37:31 -06:00
|
|
|
// Used to explicitly close a connection, called internally by the pool
|
|
|
|
// when a connection times out or the pool is shutdown.
|
2016-05-17 01:01:34 +10:00
|
|
|
destroyRawConnection(connection, cb) {
|
2015-12-08 11:37:31 -06:00
|
|
|
connection.close(cb);
|
|
|
|
},
|
2015-12-15 16:54:43 -05:00
|
|
|
|
2015-12-08 11:37:31 -06:00
|
|
|
// Position the bindings for the query.
|
2016-05-17 01:01:34 +10:00
|
|
|
positionBindings(sql) {
|
|
|
|
let questionCount = -1
|
2015-12-08 11:37:31 -06:00
|
|
|
return sql.replace(/\?/g, function() {
|
|
|
|
questionCount += 1
|
2016-05-17 01:01:34 +10:00
|
|
|
return `@p${questionCount}`
|
2015-12-08 11:37:31 -06:00
|
|
|
})
|
|
|
|
},
|
2015-12-15 16:54:43 -05:00
|
|
|
|
2015-12-08 11:37:31 -06:00
|
|
|
// 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.
|
2016-05-17 01:01:34 +10:00
|
|
|
_stream(connection, obj, stream, options) {
|
2016-05-24 21:46:36 +02:00
|
|
|
const client = this;
|
2015-12-08 11:37:31 -06:00
|
|
|
options = options || {}
|
2015-12-09 17:53:53 -06:00
|
|
|
if (!obj || typeof obj === 'string') obj = {sql: obj}
|
|
|
|
// convert ? params into positional bindings (@p1)
|
|
|
|
obj.sql = this.positionBindings(obj.sql);
|
2015-12-08 11:37:31 -06:00
|
|
|
return new Promise(function(resolver, rejecter) {
|
2015-12-09 17:53:53 -06:00
|
|
|
stream.on('error', rejecter);
|
|
|
|
stream.on('end', resolver);
|
2016-05-17 01:01:34 +10:00
|
|
|
let { sql } = obj
|
2015-12-09 17:53:53 -06:00
|
|
|
if (!sql) return resolver()
|
2016-08-09 17:03:08 -04:00
|
|
|
if (obj.options) {
|
|
|
|
sql = assign({sql}, obj.options)
|
|
|
|
}
|
2016-05-17 01:01:34 +10:00
|
|
|
const req = (connection.tx_ || connection).request();
|
2015-12-09 17:53:53 -06:00
|
|
|
//req.verbose = true;
|
|
|
|
req.multiple = true;
|
|
|
|
req.stream = true;
|
|
|
|
if (obj.bindings) {
|
2016-05-17 01:01:34 +10:00
|
|
|
for (let i = 0; i < obj.bindings.length; i++) {
|
2016-05-24 21:46:36 +02:00
|
|
|
client._setReqInput(req, i, obj.bindings[i])
|
2015-12-09 17:53:53 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
req.pipe(stream)
|
2015-12-15 16:54:43 -05:00
|
|
|
req.query(sql)
|
2015-12-08 11:37:31 -06:00
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
// Runs the query on the specified connection, providing the bindings
|
|
|
|
// and any other necessary prep work.
|
2016-05-17 01:01:34 +10:00
|
|
|
_query(connection, obj) {
|
2016-05-24 21:46:36 +02:00
|
|
|
const client = this;
|
2015-12-08 11:37:31 -06:00
|
|
|
if (!obj || typeof obj === 'string') obj = {sql: obj}
|
|
|
|
// convert ? params into positional bindings (@p1)
|
|
|
|
obj.sql = this.positionBindings(obj.sql);
|
|
|
|
return new Promise(function(resolver, rejecter) {
|
2016-05-17 01:01:34 +10:00
|
|
|
let { sql } = obj
|
2015-12-08 11:37:31 -06:00
|
|
|
if (!sql) return resolver()
|
2016-08-09 17:03:08 -04:00
|
|
|
if (obj.options) {
|
|
|
|
sql = assign({sql}, obj.options)
|
|
|
|
}
|
2016-05-17 01:01:34 +10:00
|
|
|
const req = (connection.tx_ || connection).request();
|
2015-12-09 17:53:53 -06:00
|
|
|
// req.verbose = true;
|
|
|
|
req.multiple = true;
|
2015-12-08 11:37:31 -06:00
|
|
|
if (obj.bindings) {
|
2016-05-17 01:01:34 +10:00
|
|
|
for (let i = 0; i < obj.bindings.length; i++) {
|
2016-05-24 21:46:36 +02:00
|
|
|
client._setReqInput(req, i, obj.bindings[i])
|
2015-12-08 11:37:31 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
req.query(sql, function(err, recordset) {
|
|
|
|
if (err) return rejecter(err)
|
2015-12-09 17:53:53 -06:00
|
|
|
obj.response = recordset[0]
|
2015-12-08 11:37:31 -06:00
|
|
|
resolver(obj)
|
2015-12-15 16:54:43 -05:00
|
|
|
})
|
2015-12-08 11:37:31 -06:00
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2016-05-24 21:46:36 +02:00
|
|
|
// sets a request input parameter. Detects bigints and sets type appropriately.
|
|
|
|
_setReqInput(req, i, binding) {
|
2016-05-26 22:27:55 +02:00
|
|
|
if (typeof binding == 'number' && (binding < SQL_INT4.MIN || binding > SQL_INT4.MAX)) {
|
|
|
|
if (binding < SQL_BIGINT_SAFE.MIN || binding > SQL_BIGINT_SAFE.MAX) {
|
2016-05-24 22:21:57 +02:00
|
|
|
throw new Error(`Bigint must be safe integer or must be passed as string, saw ${binding}`)
|
|
|
|
}
|
2016-05-24 21:46:36 +02:00
|
|
|
req.input(`p${i}`, this.driver.BigInt, binding)
|
|
|
|
} else {
|
|
|
|
req.input(`p${i}`, binding)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-12-08 11:37:31 -06:00
|
|
|
// Process the response as returned from the query.
|
2016-05-17 01:01:34 +10:00
|
|
|
processResponse(obj, runner) {
|
2015-12-08 11:37:31 -06:00
|
|
|
if (obj == null) return;
|
2016-05-17 01:01:34 +10:00
|
|
|
let { response } = obj
|
2016-05-18 19:59:24 +10:00
|
|
|
const { method } = obj
|
2015-12-09 17:53:53 -06:00
|
|
|
if (obj.output) return obj.output.call(runner, response)
|
2015-12-08 11:37:31 -06:00
|
|
|
switch (method) {
|
|
|
|
case 'select':
|
|
|
|
case 'pluck':
|
|
|
|
case 'first':
|
2015-12-09 17:53:53 -06:00
|
|
|
response = helpers.skim(response)
|
2016-03-02 16:52:32 +01:00
|
|
|
if (method === 'pluck') return map(response, obj.pluck)
|
2015-12-09 17:53:53 -06:00
|
|
|
return method === 'first' ? response[0] : response
|
2015-12-08 11:37:31 -06:00
|
|
|
case 'insert':
|
|
|
|
case 'del':
|
|
|
|
case 'update':
|
|
|
|
case 'counter':
|
2015-12-09 17:53:53 -06:00
|
|
|
if (obj.returning) {
|
|
|
|
if (obj.returning === '@@rowcount') {
|
|
|
|
return response[0]['']
|
|
|
|
}
|
2016-05-17 01:01:34 +10:00
|
|
|
|
|
|
|
if (
|
|
|
|
(isArray(obj.returning) && obj.returning.length > 1) ||
|
|
|
|
obj.returning[0] === '*'
|
|
|
|
) {
|
2015-12-09 17:53:53 -06:00
|
|
|
return response;
|
|
|
|
}
|
|
|
|
// return an array with values if only one returning value was specified
|
2016-03-02 16:52:32 +01:00
|
|
|
return flatten(map(response, values));
|
2015-12-09 17:53:53 -06:00
|
|
|
}
|
|
|
|
return response;
|
2015-12-08 11:37:31 -06:00
|
|
|
default:
|
|
|
|
return response
|
|
|
|
}
|
2016-03-19 19:14:46 +01:00
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
ping(resource, callback) {
|
2016-04-13 06:42:30 +02:00
|
|
|
resource.request().query('SELECT 1', callback);
|
2015-12-08 11:37:31 -06:00
|
|
|
}
|
2015-12-15 16:54:43 -05:00
|
|
|
|
2015-12-08 11:37:31 -06:00
|
|
|
})
|
|
|
|
|
|
|
|
// MSSQL Specific error handler
|
|
|
|
function connectionErrorHandler(client, connection, err) {
|
|
|
|
if (connection && err && err.fatal) {
|
|
|
|
if (connection.__knex__disposed) return;
|
|
|
|
connection.__knex__disposed = true;
|
|
|
|
client.pool.destroy(connection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
export default Client_MSSQL
|