repo_description: PostgreSQL interface for Node.js
repo_topics: javascript nodejs postgresql promise-library promises transaction typescript
code: /*
   * 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) {
      return new 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 the 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 new 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 = new 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),
      direct: ctx => directConnect(ctx, config)
  });
  