knex/lib/client.js

444 lines
12 KiB
JavaScript
Raw Normal View History

const { Pool, TimeoutError } = require('tarn');
const { EventEmitter } = require('events');
2021-01-31 13:40:13 +03:00
const { promisify } = require('util');
const { makeEscape } = require('./util/string');
const cloneDeep = require('lodash/cloneDeep');
const defaults = require('lodash/defaults');
const uniqueId = require('lodash/uniqueId');
const Runner = require('./execution/runner');
const Transaction = require('./execution/transaction');
const {
executeQuery,
enrichQueryObject,
} = require('./execution/internal/query-executioner');
2021-01-09 17:59:53 +02:00
const QueryBuilder = require('./query/querybuilder');
const QueryCompiler = require('./query/querycompiler');
const SchemaBuilder = require('./schema/builder');
const SchemaCompiler = require('./schema/compiler');
const TableBuilder = require('./schema/tablebuilder');
const TableCompiler = require('./schema/tablecompiler');
const ColumnBuilder = require('./schema/columnbuilder');
const ColumnCompiler = require('./schema/columncompiler');
2020-02-12 23:42:15 +03:00
const { KnexTimeoutError } = require('./util/timeout');
const { outputQuery, unwrapRaw } = require('./formatter/wrappingFormatter');
const { compileCallback } = require('./formatter/formatterUtils');
const Raw = require('./raw');
const Ref = require('./ref');
const Formatter = require('./formatter');
const Logger = require('./logger');
const { POOL_CONFIG_OPTIONS } = require('./constants');
const debug = require('debug')('knex:client');
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.
2021-01-31 13:40:13 +03:00
class Client extends EventEmitter {
constructor(config = {}) {
super();
this.config = config;
this.logger = new Logger(config);
2021-01-31 13:40:13 +03: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.
2021-01-31 13:40:13 +03:00
if (this.dialect && !this.config.client) {
this.logger.warn(
`Using 'this.dialect' to identify the client is deprecated and support for it will be removed in the future. Please use configuration option 'client' instead.`
);
}
const dbClient = this.config.client || this.dialect;
if (!dbClient) {
throw new Error(
`knex: Required configuration option 'client' is missing.`
);
2016-03-02 17:07:05 +01:00
}
2021-01-31 13:40:13 +03:00
if (config.version) {
this.version = config.version;
}
2016-03-02 17:07:05 +01:00
2021-01-31 13:40:13 +03:00
if (config.connection && config.connection instanceof Function) {
this.connectionConfigProvider = config.connection;
this.connectionConfigExpirationChecker = () => true; // causes the provider to be called on first use
} else {
this.connectionSettings = cloneDeep(config.connection || {});
this.connectionConfigExpirationChecker = null;
}
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;
}
}
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);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
queryBuilder() {
return new QueryBuilder(this);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
2021-01-07 23:34:46 +02:00
queryCompiler(builder, formatter) {
return new QueryCompiler(this, builder, formatter);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
schemaBuilder() {
return new SchemaBuilder(this);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
schemaCompiler(builder) {
return new SchemaCompiler(this, builder);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
tableBuilder(type, tableName, fn) {
return new TableBuilder(this, type, tableName, fn);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
tableCompiler(tableBuilder) {
return new TableCompiler(this, tableBuilder);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
columnBuilder(tableBuilder, type, args) {
return new ColumnBuilder(this, tableBuilder, type, args);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
columnCompiler(tableBuilder, columnBuilder) {
return new ColumnCompiler(this, tableBuilder, columnBuilder);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
2017-05-16 18:17:19 +08:00
runner(builder) {
return new Runner(this, builder);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
transaction(container, config, outerTx) {
return new Transaction(this, container, config, outerTx);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
raw() {
return new Raw(this).set(...arguments);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
ref() {
return new Ref(this, ...arguments);
2021-01-31 13:40:13 +03:00
}
query(connection, queryParam) {
const queryObject = enrichQueryObject(connection, queryParam, this);
return executeQuery(connection, queryObject, this);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
stream(connection, queryParam, stream, options) {
const queryObject = enrichQueryObject(connection, queryParam, this);
return this._stream(connection, queryObject, stream, options);
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
prepBindings(bindings) {
return bindings;
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
positionBindings(sql) {
return sql;
2021-01-31 13:40:13 +03:00
}
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;
2021-01-31 13:40:13 +03:00
}
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
);
2021-01-31 13:40:13 +03:00
}
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);
2021-01-31 13:40:13 +03:00
}
wrapIdentifierImpl(value) {
return value !== '*' ? `"${value.replace(/"/g, '""')}"` : '*';
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
initializeDriver() {
2016-03-02 17:07:05 +01:00
try {
this.driver = this._driver();
2016-03-02 17:07:05 +01:00
} catch (e) {
const message = `Knex: run\n$ npm install ${this.driverName} --save`;
this.logger.error(`${message}\n${e.message}\n${e.stack}`);
throw new Error(`${message}\n${e.message}`);
2016-03-02 17:07:05 +01:00
}
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
poolDefaults() {
return { min: 2, max: 10, propagateCreateError: true };
2021-01-31 13:40:13 +03:00
}
getPoolSettings(poolConfig) {
poolConfig = defaults({}, poolConfig, this.poolDefaults());
POOL_CONFIG_OPTIONS.forEach((option) => {
if (option in poolConfig) {
this.logger.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);
const updatePoolConnectionSettingsFromProvider = async () => {
if (!this.connectionConfigProvider) {
return; // static configuration, nothing to update
}
if (
!this.connectionConfigExpirationChecker ||
!this.connectionConfigExpirationChecker()
) {
return; // not expired, reuse existing connection
}
const providerResult = await this.connectionConfigProvider();
if (providerResult.expirationChecker) {
this.connectionConfigExpirationChecker =
providerResult.expirationChecker;
delete providerResult.expirationChecker; // MySQL2 driver warns on receiving extra properties
} else {
this.connectionConfigExpirationChecker = null;
}
this.connectionSettings = providerResult;
};
return Object.assign(poolConfig, {
create: async () => {
await updatePoolConnectionSettingsFromProvider();
const connection = await this.acquireRawConnection();
connection.__knexUid = uniqueId('__knexUid');
if (poolConfig.afterCreate) {
await promisify(poolConfig.afterCreate)(connection);
}
return connection;
},
destroy: (connection) => {
if (connection !== void 0) {
return this.destroyRawConnection(connection);
2016-03-02 17:07:05 +01:00
}
},
validate: (connection) => {
if (connection.__knex__disposed) {
this.logger.warn(`Connection Error: ${connection.__knex__disposed}`);
return false;
}
return this.validateConnection(connection);
},
});
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
initializePool(config = this.config) {
2016-09-13 18:12:23 -04:00
if (this.pool) {
this.logger.warn('The pool has already been initialized');
return;
2016-09-13 18:12:23 -04:00
}
const tarnPoolConfig = {
...this.getPoolSettings(config.pool),
};
// afterCreate is an internal knex param, tarn.js does not support it
if (tarnPoolConfig.afterCreate) {
delete tarnPoolConfig.afterCreate;
}
this.pool = new Pool(tarnPoolConfig);
2021-01-31 13:40:13 +03:00
}
2016-09-13 18:12:23 -04:00
validateConnection(connection) {
return true;
2021-01-31 13:40:13 +03:00
}
2016-09-13 18:12:23 -04:00
2016-03-02 17:07:05 +01:00
// Acquire a connection from the pool.
async acquireConnection() {
if (!this.pool) {
throw new Error('Unable to acquire a connection');
}
2019-06-07 17:30:39 -04:00
try {
const connection = await this.pool.acquire().promise;
debug('acquired connection from pool: %s', connection.__knexUid);
return connection;
} catch (error) {
let convertedError = error;
if (error instanceof TimeoutError) {
convertedError = new KnexTimeoutError(
'Knex: Timeout acquiring a connection. The pool is probably full. ' +
'Are you missing a .transacting(trx) call?'
);
}
throw convertedError;
2019-06-07 17:30:39 -04:00
}
2021-01-31 13:40:13 +03: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.
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();
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
// Destroy the current connection pool for the client.
2021-01-21 20:25:06 +02:00
async destroy(callback) {
try {
if (this.pool && this.pool.destroy) {
await this.pool.destroy();
}
this.pool = undefined;
2021-01-21 20:25:06 +02:00
if (typeof callback === 'function') {
callback();
}
} catch (err) {
if (typeof callback === 'function') {
return callback(err);
}
throw err;
}
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
// Return the database being used by this client.
database() {
return this.connectionSettings.database;
2021-01-31 13:40:13 +03:00
}
2016-03-02 17:07:05 +01:00
toString() {
return '[object KnexClient]';
2021-01-31 13:40:13 +03:00
}
assertCanCancelQuery() {
if (!this.canCancelQuery) {
throw new Error('Query cancelling not supported for this dialect');
}
2021-01-31 13:40:13 +03:00
}
2016-05-26 11:06:33 -07:00
cancelQuery() {
throw new Error('Query cancelling not supported for this dialect');
2021-01-31 13:40:13 +03:00
}
// Formatter part
alias(first, second) {
return first + ' as ' + second;
2021-01-31 13:40:13 +03:00
}
// Checks whether a value is a function... if it is, we compile it
// otherwise we check whether it's a raw
parameter(value, builder, bindingsHolder) {
if (typeof value === 'function') {
return outputQuery(
compileCallback(value, undefined, this, bindingsHolder),
true,
builder,
this
);
}
return unwrapRaw(value, true, builder, this, bindingsHolder) || '?';
2021-01-31 13:40:13 +03:00
}
// Turns a list of values into a list of ?'s, joining them with commas unless
// a "joining" value is specified (e.g. ' and ')
parameterize(values, notSetValue, builder, bindingsHolder) {
if (typeof values === 'function')
return this.parameter(values, builder, bindingsHolder);
values = Array.isArray(values) ? values : [values];
let str = '',
i = -1;
while (++i < values.length) {
if (i > 0) str += ', ';
str += this.parameter(
values[i] === undefined ? notSetValue : values[i],
builder,
bindingsHolder
);
}
return str;
}
// Formats `values` into a parenthesized list of parameters for a `VALUES`
// clause.
//
// [1, 2] -> '(?, ?)'
// [[1, 2], [3, 4]] -> '((?, ?), (?, ?))'
// knex('table') -> '(select * from "table")'
// knex.raw('select ?', 1) -> '(select ?)'
//
values(values, builder, bindingsHolder) {
if (Array.isArray(values)) {
if (Array.isArray(values[0])) {
return `(${values
.map(
(value) =>
`(${this.parameterize(
value,
undefined,
builder,
bindingsHolder
)})`
)
.join(', ')})`;
}
return `(${this.parameterize(
values,
undefined,
builder,
bindingsHolder
)})`;
}
if (values instanceof Raw) {
return `(${this.parameter(values, builder, bindingsHolder)})`;
}
return this.parameter(values, builder, bindingsHolder);
}
2021-01-31 13:40:13 +03:00
}
Object.assign(Client.prototype, {
_escapeBinding: makeEscape({
escapeString(str) {
return `'${str.replace(/'/g, "''")}'`;
},
}),
canCancelQuery: false,
});
2016-03-02 17:07:05 +01:00
module.exports = Client;