connection-manager.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. "use strict";
  2. const _ = require("lodash");
  3. const AbstractConnectionManager = require("../abstract/connection-manager");
  4. const { logger } = require("../../utils/logger");
  5. const debug = logger.debugContext("connection:pg");
  6. const sequelizeErrors = require("../../errors");
  7. const semver = require("semver");
  8. const dataTypes = require("../../data-types");
  9. const momentTz = require("moment-timezone");
  10. const { promisify } = require("util");
  11. class ConnectionManager extends AbstractConnectionManager {
  12. constructor(dialect, sequelize) {
  13. sequelize.config.port = sequelize.config.port || 5432;
  14. super(dialect, sequelize);
  15. const pgLib = this._loadDialectModule("pg");
  16. this.lib = this.sequelize.config.native ? pgLib.native : pgLib;
  17. this._clearDynamicOIDs();
  18. this._clearTypeParser();
  19. this.refreshTypeParser(dataTypes.postgres);
  20. }
  21. _refreshTypeParser(dataType) {
  22. const arrayParserBuilder = (parser2) => {
  23. return (value) => this.lib.types.arrayParser.create(value, parser2).parse();
  24. };
  25. const rangeParserBuilder = (parser2) => {
  26. return (value) => dataType.parse(value, { parser: parser2 });
  27. };
  28. if (dataType.key.toLowerCase() === "range") {
  29. for (const name in this.nameOidMap) {
  30. const entry = this.nameOidMap[name];
  31. if (!entry.rangeOid)
  32. continue;
  33. const rangeParser = rangeParserBuilder(this.getTypeParser(entry.oid));
  34. const arrayRangeParser = arrayParserBuilder(rangeParser);
  35. this.oidParserMap.set(entry.rangeOid, rangeParser);
  36. if (!entry.arrayRangeOid)
  37. continue;
  38. this.oidParserMap.set(entry.arrayRangeOid, arrayRangeParser);
  39. }
  40. return;
  41. }
  42. const parser = (value) => dataType.parse(value);
  43. const arrayParser = arrayParserBuilder(parser);
  44. if (dataType.key.toLowerCase() === "enum") {
  45. this.enumOids.oids.forEach((oid) => {
  46. this.oidParserMap.set(oid, parser);
  47. });
  48. this.enumOids.arrayOids.forEach((arrayOid) => {
  49. this.oidParserMap.set(arrayOid, arrayParser);
  50. });
  51. return;
  52. }
  53. dataType.types.postgres.forEach((name) => {
  54. if (!this.nameOidMap[name])
  55. return;
  56. this.oidParserMap.set(this.nameOidMap[name].oid, parser);
  57. if (!this.nameOidMap[name].arrayOid)
  58. return;
  59. this.oidParserMap.set(this.nameOidMap[name].arrayOid, arrayParser);
  60. });
  61. }
  62. _clearTypeParser() {
  63. this.oidParserMap = /* @__PURE__ */ new Map();
  64. }
  65. getTypeParser(oid, ...args) {
  66. if (this.oidParserMap.get(oid))
  67. return this.oidParserMap.get(oid);
  68. return this.lib.types.getTypeParser(oid, ...args);
  69. }
  70. async connect(config) {
  71. config.user = config.username;
  72. const connectionConfig = _.pick(config, [
  73. "user",
  74. "password",
  75. "host",
  76. "database",
  77. "port"
  78. ]);
  79. connectionConfig.types = {
  80. getTypeParser: ConnectionManager.prototype.getTypeParser.bind(this)
  81. };
  82. if (config.dialectOptions) {
  83. _.merge(connectionConfig, _.pick(config.dialectOptions, [
  84. "application_name",
  85. "ssl",
  86. "client_encoding",
  87. "binary",
  88. "keepAlive",
  89. "statement_timeout",
  90. "query_timeout",
  91. "connectionTimeoutMillis",
  92. "idle_in_transaction_session_timeout",
  93. "lock_timeout",
  94. "options",
  95. "stream"
  96. ]));
  97. }
  98. const connection = await new Promise((resolve, reject) => {
  99. let responded = false;
  100. const connection2 = new this.lib.Client(connectionConfig);
  101. const parameterHandler = (message) => {
  102. switch (message.parameterName) {
  103. case "server_version":
  104. if (this.sequelize.options.databaseVersion === 0) {
  105. const version = semver.coerce(message.parameterValue).version;
  106. this.sequelize.options.databaseVersion = semver.valid(version) ? version : this.dialect.defaultVersion;
  107. }
  108. break;
  109. case "standard_conforming_strings":
  110. connection2["standard_conforming_strings"] = message.parameterValue;
  111. break;
  112. }
  113. };
  114. const endHandler = () => {
  115. debug("connection timeout");
  116. if (!responded) {
  117. reject(new sequelizeErrors.ConnectionTimedOutError(new Error("Connection timed out")));
  118. }
  119. };
  120. connection2.once("end", endHandler);
  121. if (!this.sequelize.config.native) {
  122. connection2.connection.on("parameterStatus", parameterHandler);
  123. }
  124. connection2.connect((err) => {
  125. responded = true;
  126. if (!this.sequelize.config.native) {
  127. connection2.connection.removeListener("parameterStatus", parameterHandler);
  128. }
  129. if (err) {
  130. if (err.code) {
  131. switch (err.code) {
  132. case "ECONNREFUSED":
  133. reject(new sequelizeErrors.ConnectionRefusedError(err));
  134. break;
  135. case "ENOTFOUND":
  136. reject(new sequelizeErrors.HostNotFoundError(err));
  137. break;
  138. case "EHOSTUNREACH":
  139. reject(new sequelizeErrors.HostNotReachableError(err));
  140. break;
  141. case "EINVAL":
  142. reject(new sequelizeErrors.InvalidConnectionError(err));
  143. break;
  144. default:
  145. reject(new sequelizeErrors.ConnectionError(err));
  146. break;
  147. }
  148. } else {
  149. reject(new sequelizeErrors.ConnectionError(err));
  150. }
  151. } else {
  152. debug("connection acquired");
  153. connection2.removeListener("end", endHandler);
  154. resolve(connection2);
  155. }
  156. });
  157. });
  158. connection.on("error", (error) => {
  159. connection._invalid = true;
  160. debug(`connection error ${error.code || error.message}`);
  161. this.pool.destroy(connection);
  162. });
  163. let query = "";
  164. if (this.sequelize.options.standardConformingStrings !== false && connection["standard_conforming_strings"] !== "on") {
  165. query += "SET standard_conforming_strings=on;";
  166. }
  167. if (this.sequelize.options.clientMinMessages !== void 0) {
  168. console.warn('Usage of "options.clientMinMessages" is deprecated and will be removed in v7.');
  169. console.warn('Please use the sequelize option "dialectOptions.clientMinMessages" instead.');
  170. }
  171. if (!(config.dialectOptions && config.dialectOptions.clientMinMessages && config.dialectOptions.clientMinMessages.toLowerCase() === "ignore" || this.sequelize.options.clientMinMessages === false)) {
  172. const clientMinMessages = config.dialectOptions && config.dialectOptions.clientMinMessages || this.sequelize.options.clientMinMessages || "warning";
  173. query += `SET client_min_messages TO ${clientMinMessages};`;
  174. }
  175. if (!this.sequelize.config.keepDefaultTimezone) {
  176. const isZone = !!momentTz.tz.zone(this.sequelize.options.timezone);
  177. if (isZone) {
  178. query += `SET TIME ZONE '${this.sequelize.options.timezone}';`;
  179. } else {
  180. query += `SET TIME ZONE INTERVAL '${this.sequelize.options.timezone}' HOUR TO MINUTE;`;
  181. }
  182. }
  183. if (query) {
  184. await connection.query(query);
  185. }
  186. if (Object.keys(this.nameOidMap).length === 0 && this.enumOids.oids.length === 0 && this.enumOids.arrayOids.length === 0) {
  187. await this._refreshDynamicOIDs(connection);
  188. }
  189. return connection;
  190. }
  191. async disconnect(connection) {
  192. if (connection._ending) {
  193. debug("connection tried to disconnect but was already at ENDING state");
  194. return;
  195. }
  196. return await promisify((callback) => connection.end(callback))();
  197. }
  198. validate(connection) {
  199. return !connection._invalid && !connection._ending;
  200. }
  201. async _refreshDynamicOIDs(connection) {
  202. const databaseVersion = this.sequelize.options.databaseVersion;
  203. const supportedVersion = "8.3.0";
  204. if ((databaseVersion && semver.gte(databaseVersion, supportedVersion)) === false) {
  205. return;
  206. }
  207. const results = await (connection || this.sequelize).query("WITH ranges AS ( SELECT pg_range.rngtypid, pg_type.typname AS rngtypname, pg_type.typarray AS rngtyparray, pg_range.rngsubtype FROM pg_range LEFT OUTER JOIN pg_type ON pg_type.oid = pg_range.rngtypid)SELECT pg_type.typname, pg_type.typtype, pg_type.oid, pg_type.typarray, ranges.rngtypname, ranges.rngtypid, ranges.rngtyparray FROM pg_type LEFT OUTER JOIN ranges ON pg_type.oid = ranges.rngsubtype WHERE (pg_type.typtype IN('b', 'e'));");
  208. let result = Array.isArray(results) ? results.pop() : results;
  209. if (Array.isArray(result)) {
  210. if (result[0].command === "SET") {
  211. result = result.pop();
  212. }
  213. }
  214. const newNameOidMap = {};
  215. const newEnumOids = { oids: [], arrayOids: [] };
  216. for (const row of result.rows) {
  217. if (row.typtype === "e") {
  218. newEnumOids.oids.push(row.oid);
  219. if (row.typarray)
  220. newEnumOids.arrayOids.push(row.typarray);
  221. continue;
  222. }
  223. newNameOidMap[row.typname] = { oid: row.oid };
  224. if (row.typarray)
  225. newNameOidMap[row.typname].arrayOid = row.typarray;
  226. if (row.rngtypid) {
  227. newNameOidMap[row.typname].rangeOid = row.rngtypid;
  228. if (row.rngtyparray)
  229. newNameOidMap[row.typname].arrayRangeOid = row.rngtyparray;
  230. }
  231. }
  232. this.nameOidMap = newNameOidMap;
  233. this.enumOids = newEnumOids;
  234. this.refreshTypeParser(dataTypes.postgres);
  235. }
  236. _clearDynamicOIDs() {
  237. this.nameOidMap = {};
  238. this.enumOids = { oids: [], arrayOids: [] };
  239. }
  240. }
  241. module.exports = ConnectionManager;
  242. module.exports.ConnectionManager = ConnectionManager;
  243. module.exports.default = ConnectionManager;
  244. //# sourceMappingURL=connection-manager.js.map