connect.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. /*
  2. * Copyright (c) 2015-present, Vitaly Tomilov
  3. *
  4. * See the LICENSE file at the top-level directory of this distribution
  5. * for licensing information.
  6. *
  7. * Removal or modification of this copyright notice is prohibited.
  8. */
  9. const {Events} = require('./events');
  10. const {ColorConsole} = require('./utils/color');
  11. const npm = {
  12. utils: require('./utils'),
  13. text: require('./text'),
  14. formatting: require('./formatting')
  15. };
  16. function poolConnect(ctx, db, config) {
  17. return config.promise((resolve, reject) => {
  18. const p = db.$pool;
  19. if (p.ending) {
  20. db.$destroy();
  21. const err = new Error(npm.text.poolDestroyed);
  22. Events.error(ctx.options, err, {
  23. dc: ctx.dc
  24. });
  25. reject(err);
  26. return;
  27. }
  28. p.connect((err, client) => {
  29. if (err) {
  30. Events.error(ctx.options, err, {
  31. cn: npm.utils.getSafeConnection(ctx.cn),
  32. dc: ctx.dc
  33. });
  34. reject(err);
  35. } else {
  36. if ('$useCount' in client) {
  37. // Make sure useCount drops to 1, if it ever reaches maximum integer number;
  38. // We do not drop it to zero, to avoid rerun of initialization queries that
  39. // usually check for useCount === 0;
  40. // istanbul ignore if
  41. if (client.$useCount >= Number.MAX_SAFE_INTEGER) {
  42. client.$useCount = 1; // resetting; cannot auto-test this
  43. } else {
  44. client.$useCount = ++client.$useCount;
  45. }
  46. } else {
  47. Object.defineProperty(client, '$useCount', {
  48. value: 0,
  49. configurable: false,
  50. enumerable: false,
  51. writable: true
  52. });
  53. setSchema(client, ctx);
  54. }
  55. setCtx(client, ctx);
  56. const end = lockClientEnd(client);
  57. client.on('error', onError);
  58. resolve({
  59. client,
  60. useCount: client.$useCount,
  61. release(kill) {
  62. client.end = end;
  63. client.release(kill || client.$connectionError);
  64. Events.disconnect(ctx, client);
  65. client.removeListener('error', onError);
  66. }
  67. });
  68. Events.connect(ctx, client, client.$useCount);
  69. }
  70. });
  71. });
  72. }
  73. function directConnect(ctx, config) {
  74. return config.promise((resolve, reject) => {
  75. const client = new config.pgp.pg.Client(ctx.cn);
  76. client.connect(err => {
  77. if (err) {
  78. Events.error(ctx.options, err, {
  79. cn: npm.utils.getSafeConnection(ctx.cn),
  80. dc: ctx.dc
  81. });
  82. reject(err);
  83. } else {
  84. setSchema(client, ctx);
  85. setCtx(client, ctx);
  86. const end = lockClientEnd(client);
  87. client.on('error', onError);
  88. resolve({
  89. client,
  90. useCount: 0,
  91. release() {
  92. client.end = end;
  93. const p = config.promise((res, rej) => client.end().then(res).catch(rej));
  94. Events.disconnect(ctx, client);
  95. client.removeListener('error', onError);
  96. return p;
  97. }
  98. });
  99. Events.connect(ctx, client, 0);
  100. }
  101. });
  102. });
  103. }
  104. // this event only happens when the connection is lost physically,
  105. // which cannot be tested automatically; removing from coverage:
  106. // istanbul ignore next
  107. function onError(err) {
  108. const ctx = this.$ctx;
  109. const cn = npm.utils.getSafeConnection(ctx.cn);
  110. Events.error(ctx.options, err, {cn, dc: ctx.dc});
  111. if (ctx.cnOptions && typeof ctx.cnOptions.onLost === 'function' && !ctx.notified) {
  112. try {
  113. ctx.cnOptions.onLost.call(this, err, {
  114. cn,
  115. dc: ctx.dc,
  116. start: ctx.start,
  117. client: this
  118. });
  119. } catch (e) {
  120. ColorConsole.error(e && e.stack || e);
  121. }
  122. ctx.notified = true;
  123. }
  124. }
  125. function lockClientEnd(client) {
  126. const end = client.end;
  127. client.end = doNotCall => {
  128. // This call can happen only in the following two cases:
  129. // 1. the client made the call directly, against the library's documentation (invalid code)
  130. // 2. connection with the server broke, and the pool is terminating all clients forcefully.
  131. ColorConsole.error(`${npm.text.clientEnd}\n${npm.utils.getLocalStack(1, 3)}\n`);
  132. if (!doNotCall) {
  133. end.call(client);
  134. }
  135. };
  136. return end;
  137. }
  138. function setCtx(client, ctx) {
  139. Object.defineProperty(client, '$ctx', {
  140. value: ctx,
  141. writable: true
  142. });
  143. }
  144. function setSchema(client, ctx) {
  145. let s = ctx.options.schema;
  146. if (!s) {
  147. return;
  148. }
  149. if (typeof s === 'function') {
  150. s = s.call(ctx.dc, ctx.dc);
  151. }
  152. if (Array.isArray(s)) {
  153. s = s.filter(a => a && typeof a === 'string');
  154. }
  155. if (typeof s === 'string' || (Array.isArray(s) && s.length)) {
  156. client.query(npm.formatting.as.format('SET search_path TO $1:name', [s]), err => {
  157. // istanbul ignore if;
  158. if (err) {
  159. // This is unlikely to ever happen, unless the connection is created faulty,
  160. // and fails on the very first query, which is impossible to test automatically.
  161. throw err;
  162. }
  163. });
  164. }
  165. }
  166. module.exports = config => ({
  167. pool: (ctx, db) => poolConnect(ctx, db, config),
  168. direct: ctx => directConnect(ctx, config)
  169. });