UNPKG

10.4 kBJavaScriptView Raw
1const Bluebird = require('bluebird');
2
3const Raw = require('./raw');
4const Ref = require('./ref');
5const Runner = require('./runner');
6const Formatter = require('./formatter');
7const Transaction = require('./transaction');
8
9const QueryBuilder = require('./query/builder');
10const QueryCompiler = require('./query/compiler');
11
12const SchemaBuilder = require('./schema/builder');
13const SchemaCompiler = require('./schema/compiler');
14const TableBuilder = require('./schema/tablebuilder');
15const TableCompiler = require('./schema/tablecompiler');
16const ColumnBuilder = require('./schema/columnbuilder');
17const ColumnCompiler = require('./schema/columncompiler');
18
19const { Pool, TimeoutError } = require('tarn');
20const inherits = require('inherits');
21const { EventEmitter } = require('events');
22
23const { makeEscape } = require('./query/string');
24const { uniqueId, cloneDeep, defaults } = require('lodash');
25
26const Logger = require('./logger');
27
28const debug = require('debug')('knex:client');
29const debugQuery = require('debug')('knex:query');
30const debugBindings = require('debug')('knex:bindings');
31const { POOL_CONFIG_OPTIONS } = require('./constants');
32
33// The base client provides the general structure
34// for a dialect specific client object.
35function Client(config = {}) {
36 this.config = config;
37 this.logger = new Logger(config);
38
39 //Client is a required field, so throw error if it's not supplied.
40 //If 'this.dialect' is set, then this is a 'super()' call, in which case
41 //'client' does not have to be set as it's already assigned on the client prototype.
42
43 if (this.dialect && !this.config.client) {
44 this.logger.warn(
45 `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.`
46 );
47 }
48 const dbClient = this.config.client || this.dialect;
49 if (!dbClient) {
50 throw new Error(`knex: Required configuration option 'client' is missing.`);
51 }
52
53 if (config.version) {
54 this.version = config.version;
55 }
56
57 this.connectionSettings = cloneDeep(config.connection || {});
58 if (this.driverName && config.connection) {
59 this.initializeDriver();
60 if (!config.pool || (config.pool && config.pool.max !== 0)) {
61 this.initializePool(config);
62 }
63 }
64 this.valueForUndefined = this.raw('DEFAULT');
65 if (config.useNullAsDefault) {
66 this.valueForUndefined = null;
67 }
68}
69
70inherits(Client, EventEmitter);
71
72Object.assign(Client.prototype, {
73 formatter(builder) {
74 return new Formatter(this, builder);
75 },
76
77 queryBuilder() {
78 return new QueryBuilder(this);
79 },
80
81 queryCompiler(builder) {
82 return new QueryCompiler(this, builder);
83 },
84
85 schemaBuilder() {
86 return new SchemaBuilder(this);
87 },
88
89 schemaCompiler(builder) {
90 return new SchemaCompiler(this, builder);
91 },
92
93 tableBuilder(type, tableName, fn) {
94 return new TableBuilder(this, type, tableName, fn);
95 },
96
97 tableCompiler(tableBuilder) {
98 return new TableCompiler(this, tableBuilder);
99 },
100
101 columnBuilder(tableBuilder, type, args) {
102 return new ColumnBuilder(this, tableBuilder, type, args);
103 },
104
105 columnCompiler(tableBuilder, columnBuilder) {
106 return new ColumnCompiler(this, tableBuilder, columnBuilder);
107 },
108
109 runner(builder) {
110 return new Runner(this, builder);
111 },
112
113 transaction(container, config, outerTx) {
114 return new Transaction(this, container, config, outerTx);
115 },
116
117 raw() {
118 return new Raw(this).set(...arguments);
119 },
120
121 ref() {
122 return new Ref(this, ...arguments);
123 },
124
125 _formatQuery(sql, bindings, timeZone) {
126 bindings = bindings == null ? [] : [].concat(bindings);
127 let index = 0;
128 return sql.replace(/\\?\?/g, (match) => {
129 if (match === '\\?') {
130 return '?';
131 }
132 if (index === bindings.length) {
133 return match;
134 }
135 const value = bindings[index++];
136 return this._escapeBinding(value, { timeZone });
137 });
138 },
139
140 _escapeBinding: makeEscape({
141 escapeString(str) {
142 return `'${str.replace(/'/g, "''")}'`;
143 },
144 }),
145
146 query(connection, obj) {
147 if (typeof obj === 'string') obj = { sql: obj };
148 obj.bindings = this.prepBindings(obj.bindings);
149
150 const { __knexUid, __knexTxId } = connection;
151
152 this.emit('query', Object.assign({ __knexUid, __knexTxId }, obj));
153 debugQuery(obj.sql, __knexTxId);
154 debugBindings(obj.bindings, __knexTxId);
155
156 obj.sql = this.positionBindings(obj.sql);
157
158 return this._query(connection, obj).catch((err) => {
159 err.message =
160 this._formatQuery(obj.sql, obj.bindings) + ' - ' + err.message;
161 this.emit(
162 'query-error',
163 err,
164 Object.assign({ __knexUid, __knexTxId }, obj)
165 );
166 throw err;
167 });
168 },
169
170 stream(connection, obj, stream, options) {
171 if (typeof obj === 'string') obj = { sql: obj };
172 obj.bindings = this.prepBindings(obj.bindings);
173
174 const { __knexUid, __knexTxId } = connection;
175
176 this.emit('query', Object.assign({ __knexUid, __knexTxId }, obj));
177 debugQuery(obj.sql, __knexTxId);
178 debugBindings(obj.bindings, __knexTxId);
179
180 obj.sql = this.positionBindings(obj.sql);
181
182 return this._stream(connection, obj, stream, options);
183 },
184
185 prepBindings(bindings) {
186 return bindings;
187 },
188
189 positionBindings(sql) {
190 return sql;
191 },
192
193 postProcessResponse(resp, queryContext) {
194 if (this.config.postProcessResponse) {
195 return this.config.postProcessResponse(resp, queryContext);
196 }
197 return resp;
198 },
199
200 wrapIdentifier(value, queryContext) {
201 return this.customWrapIdentifier(
202 value,
203 this.wrapIdentifierImpl,
204 queryContext
205 );
206 },
207
208 customWrapIdentifier(value, origImpl, queryContext) {
209 if (this.config.wrapIdentifier) {
210 return this.config.wrapIdentifier(value, origImpl, queryContext);
211 }
212 return origImpl(value);
213 },
214
215 wrapIdentifierImpl(value) {
216 return value !== '*' ? `"${value.replace(/"/g, '""')}"` : '*';
217 },
218
219 initializeDriver() {
220 try {
221 this.driver = this._driver();
222 } catch (e) {
223 const message = `Knex: run\n$ npm install ${this.driverName} --save`;
224 this.logger.error(`${message}\n${e.message}\n${e.stack}`);
225 throw new Error(`${message}\n${e.message}`);
226 }
227 },
228
229 poolDefaults() {
230 return { min: 2, max: 10, propagateCreateError: true };
231 },
232
233 getPoolSettings(poolConfig) {
234 poolConfig = defaults({}, poolConfig, this.poolDefaults());
235
236 POOL_CONFIG_OPTIONS.forEach((option) => {
237 if (option in poolConfig) {
238 this.logger.warn(
239 [
240 `Pool config option "${option}" is no longer supported.`,
241 `See https://github.com/Vincit/tarn.js for possible pool config options.`,
242 ].join(' ')
243 );
244 }
245 });
246
247 const timeouts = [
248 this.config.acquireConnectionTimeout || 60000,
249 poolConfig.acquireTimeoutMillis,
250 ].filter((timeout) => timeout !== undefined);
251
252 // acquire connection timeout can be set on config or config.pool
253 // choose the smallest, positive timeout setting and set on poolConfig
254 poolConfig.acquireTimeoutMillis = Math.min(...timeouts);
255
256 return Object.assign(poolConfig, {
257 create: () => {
258 return this.acquireRawConnection().then(async (connection) => {
259 connection.__knexUid = uniqueId('__knexUid');
260
261 if (poolConfig.afterCreate) {
262 await Bluebird.promisify(poolConfig.afterCreate)(connection);
263 }
264 return connection;
265 });
266 },
267
268 destroy: (connection) => {
269 if (connection !== void 0) {
270 return this.destroyRawConnection(connection);
271 }
272 },
273
274 validate: (connection) => {
275 if (connection.__knex__disposed) {
276 this.logger.warn(`Connection Error: ${connection.__knex__disposed}`);
277 return false;
278 }
279
280 return this.validateConnection(connection);
281 },
282 });
283 },
284
285 initializePool(config = this.config) {
286 if (this.pool) {
287 this.logger.warn('The pool has already been initialized');
288 return;
289 }
290
291 const tarnPoolConfig = {
292 ...this.getPoolSettings(config.pool),
293 };
294 // afterCreate is an internal knex param, tarn.js does not support it
295 if (tarnPoolConfig.afterCreate) {
296 delete tarnPoolConfig.afterCreate;
297 }
298
299 this.pool = new Pool(tarnPoolConfig);
300 },
301
302 validateConnection(connection) {
303 return true;
304 },
305
306 // Acquire a connection from the pool.
307 acquireConnection() {
308 if (!this.pool) {
309 return Bluebird.reject(new Error('Unable to acquire a connection'));
310 }
311 try {
312 return Bluebird.try(() => this.pool.acquire().promise)
313 .then((connection) => {
314 debug('acquired connection from pool: %s', connection.__knexUid);
315 return connection;
316 })
317 .catch(TimeoutError, () => {
318 throw new Bluebird.TimeoutError(
319 'Knex: Timeout acquiring a connection. The pool is probably full. ' +
320 'Are you missing a .transacting(trx) call?'
321 );
322 });
323 } catch (e) {
324 return Bluebird.reject(e);
325 }
326 },
327
328 // Releases a connection back to the connection pool,
329 // returning a promise resolved when the connection is released.
330 releaseConnection(connection) {
331 debug('releasing connection to pool: %s', connection.__knexUid);
332 const didRelease = this.pool.release(connection);
333
334 if (!didRelease) {
335 debug('pool refused connection: %s', connection.__knexUid);
336 }
337
338 return Bluebird.resolve();
339 },
340
341 // Destroy the current connection pool for the client.
342 destroy(callback) {
343 const maybeDestroy = this.pool && this.pool.destroy();
344
345 return Bluebird.resolve(maybeDestroy)
346 .then(() => {
347 this.pool = void 0;
348
349 if (typeof callback === 'function') {
350 callback();
351 }
352 })
353 .catch((err) => {
354 if (typeof callback === 'function') {
355 callback(err);
356 }
357
358 return Bluebird.reject(err);
359 });
360 },
361
362 // Return the database being used by this client.
363 database() {
364 return this.connectionSettings.database;
365 },
366
367 toString() {
368 return '[object KnexClient]';
369 },
370
371 canCancelQuery: false,
372
373 assertCanCancelQuery() {
374 if (!this.canCancelQuery) {
375 throw new Error('Query cancelling not supported for this dialect');
376 }
377 },
378
379 cancelQuery() {
380 throw new Error('Query cancelling not supported for this dialect');
381 },
382});
383
384module.exports = Client;