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