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 |
|
23 | const { makeEscape } = require('./query/string');
|
24 | const { uniqueId, cloneDeep, defaults } = require('lodash');
|
25 |
|
26 | const Logger = require('./logger');
|
27 |
|
28 | const debug = require('debug')('knex:client');
|
29 | const debugQuery = require('debug')('knex:query');
|
30 | const debugBindings = require('debug')('knex:bindings');
|
31 | const { POOL_CONFIG_OPTIONS } = require('./constants');
|
32 |
|
33 |
|
34 |
|
35 | function Client(config = {}) {
|
36 | this.config = config;
|
37 | this.logger = new Logger(config);
|
38 |
|
39 |
|
40 |
|
41 |
|
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 |
|
70 | inherits(Client, EventEmitter);
|
71 |
|
72 | Object.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 |
|
253 |
|
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 |
|
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 |
|
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 |
|
329 |
|
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 |
|
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 |
|
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 |
|
384 | module.exports = Client;
|