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';
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
import Raw from './raw';
|
|
|
|
import Runner from './runner';
|
|
|
|
import Formatter from './formatter';
|
|
|
|
import Transaction from './transaction';
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
import QueryBuilder from './query/builder';
|
|
|
|
import QueryCompiler from './query/compiler';
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
import SchemaBuilder from './schema/builder';
|
|
|
|
import SchemaCompiler from './schema/compiler';
|
|
|
|
import TableBuilder from './schema/tablebuilder';
|
|
|
|
import TableCompiler from './schema/tablecompiler';
|
|
|
|
import ColumnBuilder from './schema/columnbuilder';
|
|
|
|
import ColumnCompiler from './schema/columncompiler';
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2018-02-07 11:17:17 +02:00
|
|
|
import { Pool, TimeoutError } from 'tarn';
|
2016-05-17 01:01:34 +10:00
|
|
|
import inherits from 'inherits';
|
|
|
|
import { EventEmitter } from 'events';
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2016-09-12 18:01:47 -04:00
|
|
|
import { makeEscape } from './query/string'
|
2018-02-07 11:17:17 +02:00
|
|
|
import { assign, uniqueId, cloneDeep, defaults } from 'lodash'
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2016-05-18 19:59:24 +10:00
|
|
|
const debug = require('debug')('knex:client')
|
|
|
|
const debugQuery = require('debug')('knex:query')
|
2016-09-16 16:04:11 -04:00
|
|
|
const debugBindings = require('debug')('knex:bindings')
|
2016-09-13 18:12:23 -04:00
|
|
|
|
2016-03-02 17:07:05 +01:00
|
|
|
// The base client provides the general structure
|
|
|
|
// for a dialect specific client object.
|
|
|
|
function Client(config = {}) {
|
|
|
|
this.config = config
|
2016-12-11 17:23:44 +01:00
|
|
|
|
|
|
|
//Client is a required field, so throw error if it's not supplied.
|
|
|
|
//If 'this.dialect' is set, then this is a 'super()' call, in which case
|
|
|
|
//'client' does not have to be set as it's already assigned on the client prototype.
|
|
|
|
if(!this.config.client && !this.dialect) {
|
|
|
|
throw new Error(`knex: Required configuration option 'client' is missing.`)
|
|
|
|
}
|
|
|
|
|
2016-03-02 17:07:05 +01:00
|
|
|
this.connectionSettings = cloneDeep(config.connection || {})
|
|
|
|
if (this.driverName && config.connection) {
|
|
|
|
this.initializeDriver()
|
|
|
|
if (!config.pool || (config.pool && config.pool.max !== 0)) {
|
|
|
|
this.initializePool(config)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.valueForUndefined = this.raw('DEFAULT');
|
|
|
|
if (config.useNullAsDefault) {
|
|
|
|
this.valueForUndefined = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
inherits(Client, EventEmitter)
|
|
|
|
|
|
|
|
assign(Client.prototype, {
|
|
|
|
|
2018-02-01 23:41:01 +01:00
|
|
|
formatter(builder) {
|
|
|
|
return new Formatter(this, builder)
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
queryBuilder() {
|
2016-09-12 18:45:35 -04:00
|
|
|
return new QueryBuilder(this)
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
queryCompiler(builder) {
|
2016-09-12 18:45:35 -04:00
|
|
|
return new QueryCompiler(this, builder)
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
schemaBuilder() {
|
2016-09-12 18:45:35 -04:00
|
|
|
return new SchemaBuilder(this)
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
schemaCompiler(builder) {
|
2016-09-12 18:45:35 -04:00
|
|
|
return new SchemaCompiler(this, builder)
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
tableBuilder(type, tableName, fn) {
|
2016-09-12 18:45:35 -04:00
|
|
|
return new TableBuilder(this, type, tableName, fn)
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
tableCompiler(tableBuilder) {
|
2016-09-12 18:45:35 -04:00
|
|
|
return new TableCompiler(this, tableBuilder)
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
columnBuilder(tableBuilder, type, args) {
|
2016-09-12 18:45:35 -04:00
|
|
|
return new ColumnBuilder(this, tableBuilder, type, args)
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
columnCompiler(tableBuilder, columnBuilder) {
|
2016-09-12 18:45:35 -04:00
|
|
|
return new ColumnCompiler(this, tableBuilder, columnBuilder)
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2017-05-16 18:17:19 +08:00
|
|
|
runner(builder) {
|
|
|
|
return new Runner(this, builder)
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
transaction(container, config, outerTx) {
|
2016-09-12 18:45:35 -04:00
|
|
|
return new Transaction(this, container, config, outerTx)
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
raw() {
|
2016-09-12 18:45:35 -04:00
|
|
|
return new Raw(this).set(...arguments)
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-09-12 18:01:47 -04:00
|
|
|
_formatQuery(sql, bindings, timeZone) {
|
|
|
|
bindings = bindings == null ? [] : [].concat(bindings);
|
|
|
|
let index = 0;
|
|
|
|
return sql.replace(/\\?\?/g, (match) => {
|
|
|
|
if (match === '\\?') {
|
|
|
|
return '?'
|
|
|
|
}
|
|
|
|
if (index === bindings.length) {
|
|
|
|
return match
|
|
|
|
}
|
|
|
|
const value = bindings[index++];
|
|
|
|
return this._escapeBinding(value, {timeZone})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
_escapeBinding: makeEscape({
|
|
|
|
escapeString(str) {
|
|
|
|
return `'${str.replace(/'/g, "''")}'`
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
query(connection, obj) {
|
2016-03-02 17:07:05 +01:00
|
|
|
if (typeof obj === 'string') obj = {sql: obj}
|
2017-09-27 13:12:40 +03:00
|
|
|
obj.sql = this.positionBindings(obj.sql);
|
2016-09-12 18:01:47 -04:00
|
|
|
obj.bindings = this.prepBindings(obj.bindings)
|
2018-02-21 13:26:59 +01:00
|
|
|
|
|
|
|
const {__knexUid, __knexTxId} = connection;
|
|
|
|
|
|
|
|
this.emit('query', assign({__knexUid, __knexTxId}, obj))
|
|
|
|
debugQuery(obj.sql, __knexTxId)
|
|
|
|
debugBindings(obj.bindings, __knexTxId)
|
|
|
|
|
2016-09-12 18:01:47 -04:00
|
|
|
return this._query(connection, obj).catch((err) => {
|
|
|
|
err.message = this._formatQuery(obj.sql, obj.bindings) + ' - ' + err.message
|
2018-02-21 13:26:59 +01:00
|
|
|
this.emit('query-error', err, assign({__knexUid, __knexTxId}, obj))
|
2016-03-02 17:07:05 +01:00
|
|
|
throw err
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
stream(connection, obj, stream, options) {
|
2016-03-02 17:07:05 +01:00
|
|
|
if (typeof obj === 'string') obj = {sql: obj}
|
2017-09-27 13:12:40 +03:00
|
|
|
obj.sql = this.positionBindings(obj.sql);
|
|
|
|
obj.bindings = this.prepBindings(obj.bindings)
|
2018-02-21 13:26:59 +01:00
|
|
|
|
|
|
|
const {__knexUid, __knexTxId} = connection;
|
|
|
|
|
|
|
|
this.emit('query', assign({__knexUid, __knexTxId}, obj))
|
|
|
|
debugQuery(obj.sql, __knexTxId)
|
|
|
|
debugBindings(obj.bindings, __knexTxId)
|
|
|
|
|
2016-09-12 18:01:47 -04:00
|
|
|
return this._stream(connection, obj, stream, options)
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
prepBindings(bindings) {
|
2016-05-11 21:01:56 +03:00
|
|
|
return bindings;
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2017-09-27 13:12:40 +03:00
|
|
|
positionBindings(sql) {
|
|
|
|
return sql;
|
|
|
|
},
|
|
|
|
|
2018-02-01 23:41:01 +01:00
|
|
|
postProcessResponse(resp, queryContext) {
|
2017-10-12 18:16:15 +03:00
|
|
|
if (this.config.postProcessResponse) {
|
2018-02-01 23:41:01 +01:00
|
|
|
return this.config.postProcessResponse(resp, queryContext);
|
2017-10-12 18:16:15 +03:00
|
|
|
}
|
|
|
|
return resp;
|
|
|
|
},
|
|
|
|
|
2018-02-01 23:41:01 +01:00
|
|
|
wrapIdentifier(value, queryContext) {
|
|
|
|
return this.customWrapIdentifier(value, this.wrapIdentifierImpl, queryContext);
|
2018-01-03 00:05:48 +02:00
|
|
|
},
|
|
|
|
|
2018-02-01 23:41:01 +01:00
|
|
|
customWrapIdentifier(value, origImpl, queryContext) {
|
2017-09-08 14:58:59 +03:00
|
|
|
if (this.config.wrapIdentifier) {
|
2018-02-01 23:41:01 +01:00
|
|
|
return this.config.wrapIdentifier(value, origImpl, queryContext);
|
2017-09-08 14:58:59 +03:00
|
|
|
}
|
2018-01-03 00:05:48 +02:00
|
|
|
return origImpl(value);
|
2017-09-08 14:58:59 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
wrapIdentifierImpl(value) {
|
2016-05-17 01:01:34 +10:00
|
|
|
return (value !== '*' ? `"${value.replace(/"/g, '""')}"` : '*')
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
initializeDriver() {
|
2016-03-02 17:07:05 +01:00
|
|
|
try {
|
|
|
|
this.driver = this._driver()
|
|
|
|
} catch (e) {
|
2016-05-17 01:01:34 +10:00
|
|
|
helpers.exit(`Knex: run\n$ npm install ${this.driverName} --save\n${e.stack}`)
|
2016-03-02 17:07:05 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-09-06 14:51:29 -05:00
|
|
|
poolDefaults() {
|
2018-02-07 11:17:17 +02:00
|
|
|
return {min: 2, max: 10, propagateCreateError: true}
|
2017-09-06 14:51:29 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
getPoolSettings(poolConfig) {
|
|
|
|
poolConfig = defaults({}, poolConfig, this.poolDefaults());
|
2018-02-07 11:17:17 +02:00
|
|
|
|
|
|
|
[
|
|
|
|
'maxWaitingClients',
|
|
|
|
'testOnBorrow',
|
|
|
|
'fifo',
|
|
|
|
'priorityRange',
|
|
|
|
'autostart',
|
|
|
|
'evictionRunIntervalMillis',
|
|
|
|
'numTestsPerRun',
|
|
|
|
'softIdleTimeoutMillis',
|
|
|
|
'Promise'
|
|
|
|
].forEach(option => {
|
|
|
|
if (option in poolConfig) {
|
|
|
|
helpers.warn([
|
|
|
|
`Pool config option "${option}" is no longer supported.`,
|
|
|
|
`See https://github.com/Vincit/tarn.js for possible pool config options.`
|
|
|
|
].join(' '))
|
2017-09-06 14:51:29 -05:00
|
|
|
}
|
2018-02-07 11:17:17 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
const timeouts = [
|
|
|
|
this.config.acquireConnectionTimeout || 60000,
|
|
|
|
poolConfig.acquireTimeoutMillis
|
|
|
|
].filter(timeout => timeout !== undefined);
|
2017-09-06 14:51:29 -05:00
|
|
|
|
|
|
|
// acquire connection timeout can be set on config or config.pool
|
|
|
|
// choose the smallest, positive timeout setting and set on poolConfig
|
|
|
|
poolConfig.acquireTimeoutMillis = Math.min(...timeouts);
|
|
|
|
|
2018-02-07 11:17:17 +02:00
|
|
|
return Object.assign(poolConfig, {
|
|
|
|
create: () => {
|
|
|
|
return this.acquireRawConnection().tap(connection => {
|
|
|
|
connection.__knexUid = uniqueId('__knexUid')
|
2017-09-06 14:51:29 -05:00
|
|
|
|
2018-02-07 11:17:17 +02:00
|
|
|
if (poolConfig.afterCreate) {
|
|
|
|
return Promise.promisify(poolConfig.afterCreate)(connection)
|
2017-09-06 14:51:29 -05:00
|
|
|
}
|
2018-02-07 11:17:17 +02:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
destroy: (connection) => {
|
|
|
|
if (poolConfig.beforeDestroy) {
|
|
|
|
helpers.warn(`
|
|
|
|
beforeDestroy is deprecated, please open an issue if you use this
|
|
|
|
to discuss alternative apis
|
|
|
|
`)
|
|
|
|
|
|
|
|
poolConfig.beforeDestroy(connection, function() {})
|
|
|
|
}
|
|
|
|
|
|
|
|
if (connection !== void 0) {
|
|
|
|
return this.destroyRawConnection(connection)
|
2016-03-02 17:07:05 +01:00
|
|
|
}
|
2016-03-19 19:14:46 +01:00
|
|
|
},
|
2018-02-07 11:17:17 +02:00
|
|
|
|
|
|
|
validate: (connection) => {
|
|
|
|
if (connection.__knex__disposed) {
|
|
|
|
helpers.warn(`Connection Error: ${connection.__knex__disposed}`)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.validateConnection(connection)
|
|
|
|
}
|
|
|
|
})
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
2016-09-13 18:12:23 -04:00
|
|
|
initializePool(config) {
|
|
|
|
if (this.pool) {
|
|
|
|
helpers.warn('The pool has already been initialized')
|
|
|
|
return
|
|
|
|
}
|
2017-09-06 14:51:29 -05:00
|
|
|
|
2018-02-07 11:17:17 +02:00
|
|
|
this.pool = new Pool(this.getPoolSettings(config.pool))
|
2016-09-13 18:12:23 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
validateConnection(connection) {
|
2018-02-07 11:17:17 +02:00
|
|
|
return true
|
2016-09-13 18:12:23 -04:00
|
|
|
},
|
|
|
|
|
2016-03-02 17:07:05 +01:00
|
|
|
// Acquire a connection from the pool.
|
2016-05-17 01:01:34 +10:00
|
|
|
acquireConnection() {
|
2017-09-06 14:51:29 -05:00
|
|
|
if (!this.pool) {
|
|
|
|
return Promise.reject(new Error('Unable to acquire a connection'))
|
|
|
|
}
|
2018-02-07 11:17:17 +02:00
|
|
|
|
|
|
|
return Promise
|
|
|
|
.try(() => this.pool.acquire().promise)
|
2017-09-06 14:51:29 -05:00
|
|
|
.tap(connection => {
|
|
|
|
debug('acquired connection from pool: %s', connection.__knexUid)
|
|
|
|
})
|
2018-02-07 11:17:17 +02:00
|
|
|
.catch(TimeoutError, () => {
|
2017-09-06 14:51:29 -05:00
|
|
|
throw new Promise.TimeoutError(
|
2016-10-09 14:00:55 -04:00
|
|
|
'Knex: Timeout acquiring a connection. The pool is probably full. ' +
|
|
|
|
'Are you missing a .transacting(trx) call?'
|
2017-09-06 14:51:29 -05:00
|
|
|
)
|
|
|
|
});
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
// Releases a connection back to the connection pool,
|
|
|
|
// returning a promise resolved when the connection is released.
|
2016-05-17 01:01:34 +10:00
|
|
|
releaseConnection(connection) {
|
2017-09-06 14:51:29 -05:00
|
|
|
debug('releasing connection to pool: %s', connection.__knexUid)
|
2018-02-07 11:17:17 +02:00
|
|
|
const didRelease = this.pool.release(connection)
|
|
|
|
|
|
|
|
if (!didRelease) {
|
2017-09-06 14:51:29 -05:00
|
|
|
debug('pool refused connection: %s', connection.__knexUid)
|
2018-02-07 11:17:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.resolve()
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
// Destroy the current connection pool for the client.
|
2016-05-17 01:01:34 +10:00
|
|
|
destroy(callback) {
|
2018-02-07 11:17:17 +02:00
|
|
|
let promise = null
|
|
|
|
|
|
|
|
if (this.pool) {
|
|
|
|
promise = this.pool.destroy()
|
|
|
|
} else {
|
|
|
|
promise = Promise.resolve()
|
|
|
|
}
|
|
|
|
|
|
|
|
return promise.then(() => {
|
|
|
|
this.pool = void 0
|
|
|
|
|
|
|
|
if (typeof callback === 'function') {
|
|
|
|
callback()
|
|
|
|
}
|
|
|
|
}).catch(err => {
|
|
|
|
if (typeof callback === 'function') {
|
|
|
|
callback(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.reject(err)
|
|
|
|
})
|
2016-03-02 17:07:05 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
// Return the database being used by this client.
|
2016-05-17 01:01:34 +10:00
|
|
|
database() {
|
2016-03-02 17:07:05 +01:00
|
|
|
return this.connectionSettings.database
|
|
|
|
},
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
toString() {
|
2016-03-02 17:07:05 +01:00
|
|
|
return '[object KnexClient]'
|
2016-05-26 11:06:33 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
canCancelQuery: false,
|
2016-05-27 14:47:12 -07:00
|
|
|
|
|
|
|
assertCanCancelQuery() {
|
|
|
|
if (!this.canCancelQuery) {
|
|
|
|
throw new Error("Query cancelling not supported for this dialect");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-05-26 11:06:33 -07:00
|
|
|
cancelQuery() {
|
|
|
|
throw new Error("Query cancelling not supported for this dialect")
|
2016-03-02 17:07:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
export default Client
|