knex/src/client.js

363 lines
9.4 KiB
JavaScript
Raw Normal View History

import Promise from 'bluebird';
import * as helpers from './helpers';
2016-03-02 17:07:05 +01: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
import QueryBuilder from './query/builder';
import QueryCompiler from './query/compiler';
2016-03-02 17:07:05 +01: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
import { Pool, TimeoutError } from 'tarn';
import inherits from 'inherits';
import { EventEmitter } from 'events';
2016-03-02 17:07:05 +01:00
import { makeEscape } from './query/string'
import { assign, uniqueId, cloneDeep, defaults } from 'lodash'
2016-03-02 17:07:05 +01: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
//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, {
Add queryContext to schema and query builders (#2314) * feat(query-builder): add hookContext for wrapIdentifier * refactor: use isUndefined * test(transaction): test passing of hookContext * feat(runnner): pass context to postProcessResponse * test(runner): test postProcessResponse for raw responses * test(raw): test passing of hookContext * feat: add hookContext to Raw and SchemaBuilder * test(transaction): fix test for hookContext * chore: fix lint error * fix: check for hookContext before calling it * test(transaction): fix hookContext test * chore: remove whitespace * test(hookContext): test cloning of context object * refactor: hookContext -> queryContext * minor: use more descriptive variable name i.e. refactor: `context` => `queryContext` * fix: remove unnecessary checks for query builder * fix(Raw): pass query builder to formatter * fix(SchemaCompiler): pass schema builder to formatter * refactor: add addQueryContext helper * feat: add queryContext to TableBuilder and ColumnBuilder * fix(TableCompiler): pass table builder to formatter * fix(ColumnCompiler): pass column builder to formatter * fix(pushQuery): fix passing builder to formatter * test(Schema|Table|ColumnCompiler): test passing queryContext * fix(SchemaCompiler): pass queryContext to TableCompiler * fix(TableCompiler): pass queryContext to ColumnCompiler * test: add queryContext tests for all schema dialects * test(TableCompiler): test overwriting queryContext from SchemaCompiler * test(Raw): test passing queryContext to wrapIdentifier * tests: run all the tests
2018-02-01 23:41:01 +01:00
formatter(builder) {
return new Formatter(this, builder)
2016-03-02 17:07:05 +01:00
},
queryBuilder() {
2016-09-12 18:45:35 -04:00
return new QueryBuilder(this)
2016-03-02 17:07:05 +01:00
},
queryCompiler(builder) {
2016-09-12 18:45:35 -04:00
return new QueryCompiler(this, builder)
2016-03-02 17:07:05 +01:00
},
schemaBuilder() {
2016-09-12 18:45:35 -04:00
return new SchemaBuilder(this)
2016-03-02 17:07:05 +01:00
},
schemaCompiler(builder) {
2016-09-12 18:45:35 -04:00
return new SchemaCompiler(this, builder)
2016-03-02 17:07:05 +01: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
},
tableCompiler(tableBuilder) {
2016-09-12 18:45:35 -04:00
return new TableCompiler(this, tableBuilder)
2016-03-02 17:07:05 +01: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
},
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
},
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
},
raw() {
2016-09-12 18:45:35 -04:00
return new Raw(this).set(...arguments)
2016-03-02 17:07:05 +01: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, "''")}'`
}
}),
query(connection, obj) {
2016-03-02 17:07:05 +01:00
if (typeof obj === 'string') obj = {sql: obj}
obj.sql = this.positionBindings(obj.sql);
obj.bindings = this.prepBindings(obj.bindings)
const {__knexUid, __knexTxId} = connection;
this.emit('query', assign({__knexUid, __knexTxId}, obj))
debugQuery(obj.sql, __knexTxId)
debugBindings(obj.bindings, __knexTxId)
return this._query(connection, obj).catch((err) => {
err.message = this._formatQuery(obj.sql, obj.bindings) + ' - ' + err.message
this.emit('query-error', err, assign({__knexUid, __knexTxId}, obj))
2016-03-02 17:07:05 +01:00
throw err
})
},
stream(connection, obj, stream, options) {
2016-03-02 17:07:05 +01:00
if (typeof obj === 'string') obj = {sql: obj}
obj.sql = this.positionBindings(obj.sql);
obj.bindings = this.prepBindings(obj.bindings)
const {__knexUid, __knexTxId} = connection;
this.emit('query', assign({__knexUid, __knexTxId}, obj))
debugQuery(obj.sql, __knexTxId)
debugBindings(obj.bindings, __knexTxId)
return this._stream(connection, obj, stream, options)
2016-03-02 17:07:05 +01:00
},
prepBindings(bindings) {
return bindings;
2016-03-02 17:07:05 +01:00
},
positionBindings(sql) {
return sql;
},
Add queryContext to schema and query builders (#2314) * feat(query-builder): add hookContext for wrapIdentifier * refactor: use isUndefined * test(transaction): test passing of hookContext * feat(runnner): pass context to postProcessResponse * test(runner): test postProcessResponse for raw responses * test(raw): test passing of hookContext * feat: add hookContext to Raw and SchemaBuilder * test(transaction): fix test for hookContext * chore: fix lint error * fix: check for hookContext before calling it * test(transaction): fix hookContext test * chore: remove whitespace * test(hookContext): test cloning of context object * refactor: hookContext -> queryContext * minor: use more descriptive variable name i.e. refactor: `context` => `queryContext` * fix: remove unnecessary checks for query builder * fix(Raw): pass query builder to formatter * fix(SchemaCompiler): pass schema builder to formatter * refactor: add addQueryContext helper * feat: add queryContext to TableBuilder and ColumnBuilder * fix(TableCompiler): pass table builder to formatter * fix(ColumnCompiler): pass column builder to formatter * fix(pushQuery): fix passing builder to formatter * test(Schema|Table|ColumnCompiler): test passing queryContext * fix(SchemaCompiler): pass queryContext to TableCompiler * fix(TableCompiler): pass queryContext to ColumnCompiler * test: add queryContext tests for all schema dialects * test(TableCompiler): test overwriting queryContext from SchemaCompiler * test(Raw): test passing queryContext to wrapIdentifier * tests: run all the tests
2018-02-01 23:41:01 +01:00
postProcessResponse(resp, queryContext) {
if (this.config.postProcessResponse) {
Add queryContext to schema and query builders (#2314) * feat(query-builder): add hookContext for wrapIdentifier * refactor: use isUndefined * test(transaction): test passing of hookContext * feat(runnner): pass context to postProcessResponse * test(runner): test postProcessResponse for raw responses * test(raw): test passing of hookContext * feat: add hookContext to Raw and SchemaBuilder * test(transaction): fix test for hookContext * chore: fix lint error * fix: check for hookContext before calling it * test(transaction): fix hookContext test * chore: remove whitespace * test(hookContext): test cloning of context object * refactor: hookContext -> queryContext * minor: use more descriptive variable name i.e. refactor: `context` => `queryContext` * fix: remove unnecessary checks for query builder * fix(Raw): pass query builder to formatter * fix(SchemaCompiler): pass schema builder to formatter * refactor: add addQueryContext helper * feat: add queryContext to TableBuilder and ColumnBuilder * fix(TableCompiler): pass table builder to formatter * fix(ColumnCompiler): pass column builder to formatter * fix(pushQuery): fix passing builder to formatter * test(Schema|Table|ColumnCompiler): test passing queryContext * fix(SchemaCompiler): pass queryContext to TableCompiler * fix(TableCompiler): pass queryContext to ColumnCompiler * test: add queryContext tests for all schema dialects * test(TableCompiler): test overwriting queryContext from SchemaCompiler * test(Raw): test passing queryContext to wrapIdentifier * tests: run all the tests
2018-02-01 23:41:01 +01:00
return this.config.postProcessResponse(resp, queryContext);
}
return resp;
},
Add queryContext to schema and query builders (#2314) * feat(query-builder): add hookContext for wrapIdentifier * refactor: use isUndefined * test(transaction): test passing of hookContext * feat(runnner): pass context to postProcessResponse * test(runner): test postProcessResponse for raw responses * test(raw): test passing of hookContext * feat: add hookContext to Raw and SchemaBuilder * test(transaction): fix test for hookContext * chore: fix lint error * fix: check for hookContext before calling it * test(transaction): fix hookContext test * chore: remove whitespace * test(hookContext): test cloning of context object * refactor: hookContext -> queryContext * minor: use more descriptive variable name i.e. refactor: `context` => `queryContext` * fix: remove unnecessary checks for query builder * fix(Raw): pass query builder to formatter * fix(SchemaCompiler): pass schema builder to formatter * refactor: add addQueryContext helper * feat: add queryContext to TableBuilder and ColumnBuilder * fix(TableCompiler): pass table builder to formatter * fix(ColumnCompiler): pass column builder to formatter * fix(pushQuery): fix passing builder to formatter * test(Schema|Table|ColumnCompiler): test passing queryContext * fix(SchemaCompiler): pass queryContext to TableCompiler * fix(TableCompiler): pass queryContext to ColumnCompiler * test: add queryContext tests for all schema dialects * test(TableCompiler): test overwriting queryContext from SchemaCompiler * test(Raw): test passing queryContext to wrapIdentifier * tests: run all the tests
2018-02-01 23:41:01 +01:00
wrapIdentifier(value, queryContext) {
return this.customWrapIdentifier(value, this.wrapIdentifierImpl, queryContext);
},
Add queryContext to schema and query builders (#2314) * feat(query-builder): add hookContext for wrapIdentifier * refactor: use isUndefined * test(transaction): test passing of hookContext * feat(runnner): pass context to postProcessResponse * test(runner): test postProcessResponse for raw responses * test(raw): test passing of hookContext * feat: add hookContext to Raw and SchemaBuilder * test(transaction): fix test for hookContext * chore: fix lint error * fix: check for hookContext before calling it * test(transaction): fix hookContext test * chore: remove whitespace * test(hookContext): test cloning of context object * refactor: hookContext -> queryContext * minor: use more descriptive variable name i.e. refactor: `context` => `queryContext` * fix: remove unnecessary checks for query builder * fix(Raw): pass query builder to formatter * fix(SchemaCompiler): pass schema builder to formatter * refactor: add addQueryContext helper * feat: add queryContext to TableBuilder and ColumnBuilder * fix(TableCompiler): pass table builder to formatter * fix(ColumnCompiler): pass column builder to formatter * fix(pushQuery): fix passing builder to formatter * test(Schema|Table|ColumnCompiler): test passing queryContext * fix(SchemaCompiler): pass queryContext to TableCompiler * fix(TableCompiler): pass queryContext to ColumnCompiler * test: add queryContext tests for all schema dialects * test(TableCompiler): test overwriting queryContext from SchemaCompiler * test(Raw): test passing queryContext to wrapIdentifier * tests: run all the tests
2018-02-01 23:41:01 +01:00
customWrapIdentifier(value, origImpl, queryContext) {
if (this.config.wrapIdentifier) {
Add queryContext to schema and query builders (#2314) * feat(query-builder): add hookContext for wrapIdentifier * refactor: use isUndefined * test(transaction): test passing of hookContext * feat(runnner): pass context to postProcessResponse * test(runner): test postProcessResponse for raw responses * test(raw): test passing of hookContext * feat: add hookContext to Raw and SchemaBuilder * test(transaction): fix test for hookContext * chore: fix lint error * fix: check for hookContext before calling it * test(transaction): fix hookContext test * chore: remove whitespace * test(hookContext): test cloning of context object * refactor: hookContext -> queryContext * minor: use more descriptive variable name i.e. refactor: `context` => `queryContext` * fix: remove unnecessary checks for query builder * fix(Raw): pass query builder to formatter * fix(SchemaCompiler): pass schema builder to formatter * refactor: add addQueryContext helper * feat: add queryContext to TableBuilder and ColumnBuilder * fix(TableCompiler): pass table builder to formatter * fix(ColumnCompiler): pass column builder to formatter * fix(pushQuery): fix passing builder to formatter * test(Schema|Table|ColumnCompiler): test passing queryContext * fix(SchemaCompiler): pass queryContext to TableCompiler * fix(TableCompiler): pass queryContext to ColumnCompiler * test: add queryContext tests for all schema dialects * test(TableCompiler): test overwriting queryContext from SchemaCompiler * test(Raw): test passing queryContext to wrapIdentifier * tests: run all the tests
2018-02-01 23:41:01 +01:00
return this.config.wrapIdentifier(value, origImpl, queryContext);
}
return origImpl(value);
},
wrapIdentifierImpl(value) {
return (value !== '*' ? `"${value.replace(/"/g, '""')}"` : '*')
2016-03-02 17:07:05 +01:00
},
initializeDriver() {
2016-03-02 17:07:05 +01:00
try {
this.driver = this._driver()
} catch (e) {
helpers.exit(`Knex: run\n$ npm install ${this.driverName} --save\n${e.stack}`)
2016-03-02 17:07:05 +01:00
}
},
poolDefaults() {
return {min: 2, max: 10, propagateCreateError: true}
},
getPoolSettings(poolConfig) {
poolConfig = defaults({}, poolConfig, this.poolDefaults());
[
'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(' '))
}
})
const timeouts = [
this.config.acquireConnectionTimeout || 60000,
poolConfig.acquireTimeoutMillis
].filter(timeout => timeout !== undefined);
// 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);
return Object.assign(poolConfig, {
create: () => {
return this.acquireRawConnection().tap(connection => {
connection.__knexUid = uniqueId('__knexUid')
if (poolConfig.afterCreate) {
return Promise.promisify(poolConfig.afterCreate)(connection)
}
});
},
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
}
},
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
}
this.pool = new Pool(this.getPoolSettings(config.pool))
2016-09-13 18:12:23 -04:00
},
validateConnection(connection) {
return true
2016-09-13 18:12:23 -04:00
},
2016-03-02 17:07:05 +01:00
// Acquire a connection from the pool.
acquireConnection() {
if (!this.pool) {
return Promise.reject(new Error('Unable to acquire a connection'))
}
return Promise
.try(() => this.pool.acquire().promise)
.tap(connection => {
debug('acquired connection from pool: %s', connection.__knexUid)
})
.catch(TimeoutError, () => {
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?'
)
});
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.
releaseConnection(connection) {
debug('releasing connection to pool: %s', connection.__knexUid)
const didRelease = this.pool.release(connection)
if (!didRelease) {
debug('pool refused connection: %s', connection.__knexUid)
}
return Promise.resolve()
2016-03-02 17:07:05 +01:00
},
// Destroy the current connection pool for the client.
destroy(callback) {
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.
database() {
2016-03-02 17:07:05 +01:00
return this.connectionSettings.database
},
toString() {
2016-03-02 17:07:05 +01:00
return '[object KnexClient]'
2016-05-26 11:06:33 -07:00
},
canCancelQuery: false,
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
}
})
export default Client