123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- /*
- * Copyright (c) 2015-present, Vitaly Tomilov
- *
- * See the LICENSE file at the top-level directory of this distribution
- * for licensing information.
- *
- * Removal or modification of this copyright notice is prohibited.
- */
- const {Events} = require('./events');
- const {ColorConsole} = require('./utils/color');
- const npm = {
- utils: require('./utils'),
- text: require('./text'),
- formatting: require('./formatting')
- };
- function poolConnect(ctx, db, config) {
- return config.promise((resolve, reject) => {
- const p = db.$pool;
- if (p.ending) {
- db.$destroy();
- const err = new Error(npm.text.poolDestroyed);
- Events.error(ctx.options, err, {
- dc: ctx.dc
- });
- reject(err);
- return;
- }
- p.connect((err, client) => {
- if (err) {
- Events.error(ctx.options, err, {
- cn: npm.utils.getSafeConnection(ctx.cn),
- dc: ctx.dc
- });
- reject(err);
- } else {
- if ('$useCount' in client) {
- // Make sure useCount drops to 1, if it ever reaches maximum integer number;
- // We do not drop it to zero, to avoid rerun of initialization queries that
- // usually check for useCount === 0;
- // istanbul ignore if
- if (client.$useCount >= Number.MAX_SAFE_INTEGER) {
- client.$useCount = 1; // resetting; cannot auto-test this
- } else {
- client.$useCount = ++client.$useCount;
- }
- } else {
- Object.defineProperty(client, '$useCount', {
- value: 0,
- configurable: false,
- enumerable: false,
- writable: true
- });
- setSchema(client, ctx);
- }
- setCtx(client, ctx);
- const end = lockClientEnd(client);
- client.on('error', onError);
- resolve({
- client,
- useCount: client.$useCount,
- release(kill) {
- client.end = end;
- client.release(kill || client.$connectionError);
- Events.disconnect(ctx, client);
- client.removeListener('error', onError);
- }
- });
- Events.connect(ctx, client, client.$useCount);
- }
- });
- });
- }
- function directConnect(ctx, config) {
- return config.promise((resolve, reject) => {
- const client = new config.pgp.pg.Client(ctx.cn);
- client.connect(err => {
- if (err) {
- Events.error(ctx.options, err, {
- cn: npm.utils.getSafeConnection(ctx.cn),
- dc: ctx.dc
- });
- reject(err);
- } else {
- setSchema(client, ctx);
- setCtx(client, ctx);
- const end = lockClientEnd(client);
- client.on('error', onError);
- resolve({
- client,
- useCount: 0,
- release() {
- client.end = end;
- const p = config.promise((res, rej) => client.end().then(res).catch(rej));
- Events.disconnect(ctx, client);
- client.removeListener('error', onError);
- return p;
- }
- });
- Events.connect(ctx, client, 0);
- }
- });
- });
- }
- // this event only happens when the connection is lost physically,
- // which cannot be tested automatically; removing from coverage:
- // istanbul ignore next
- function onError(err) {
- const ctx = this.$ctx;
- const cn = npm.utils.getSafeConnection(ctx.cn);
- Events.error(ctx.options, err, {cn, dc: ctx.dc});
- if (ctx.cnOptions && typeof ctx.cnOptions.onLost === 'function' && !ctx.notified) {
- try {
- ctx.cnOptions.onLost.call(this, err, {
- cn,
- dc: ctx.dc,
- start: ctx.start,
- client: this
- });
- } catch (e) {
- ColorConsole.error(e && e.stack || e);
- }
- ctx.notified = true;
- }
- }
- function lockClientEnd(client) {
- const end = client.end;
- client.end = doNotCall => {
- // This call can happen only in the following two cases:
- // 1. the client made the call directly, against the library's documentation (invalid code)
- // 2. connection with the server broke, and the pool is terminating all clients forcefully.
- ColorConsole.error(`${npm.text.clientEnd}\n${npm.utils.getLocalStack(1, 3)}\n`);
- if (!doNotCall) {
- end.call(client);
- }
- };
- return end;
- }
- function setCtx(client, ctx) {
- Object.defineProperty(client, '$ctx', {
- value: ctx,
- writable: true
- });
- }
- function setSchema(client, ctx) {
- let s = ctx.options.schema;
- if (!s) {
- return;
- }
- if (typeof s === 'function') {
- s = s.call(ctx.dc, ctx.dc);
- }
- if (Array.isArray(s)) {
- s = s.filter(a => a && typeof a === 'string');
- }
- if (typeof s === 'string' || (Array.isArray(s) && s.length)) {
- client.query(npm.formatting.as.format('SET search_path TO $1:name', [s]), err => {
- // istanbul ignore if;
- if (err) {
- // This is unlikely to ever happen, unless the connection is created faulty,
- // and fails on the very first query, which is impossible to test automatically.
- throw err;
- }
- });
- }
- }
- module.exports = config => ({
- pool: (ctx, db) => poolConnect(ctx, db, config),
- direct: ctx => directConnect(ctx, config)
- });
|