connection-manager.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. 'use strict';
  2. const fs = require('fs');
  3. const path = require('path');
  4. const AbstractConnectionManager = require('../abstract/connection-manager');
  5. const { logger } = require('../../utils/logger');
  6. const debug = logger.debugContext('connection:sqlite');
  7. const dataTypes = require('../../data-types').sqlite;
  8. const sequelizeErrors = require('../../errors');
  9. const parserStore = require('../parserStore')('sqlite');
  10. const { promisify } = require('util');
  11. class ConnectionManager extends AbstractConnectionManager {
  12. constructor(dialect, sequelize) {
  13. super(dialect, sequelize);
  14. // We attempt to parse file location from a connection uri
  15. // but we shouldn't match sequelize default host.
  16. if (this.sequelize.options.host === 'localhost') {
  17. delete this.sequelize.options.host;
  18. }
  19. this.connections = {};
  20. this.lib = this._loadDialectModule('sqlite3');
  21. this.refreshTypeParser(dataTypes);
  22. }
  23. async _onProcessExit() {
  24. await Promise.all(
  25. Object.getOwnPropertyNames(this.connections)
  26. .map(connection => promisify(callback => this.connections[connection].close(callback))())
  27. );
  28. return super._onProcessExit.call(this);
  29. }
  30. // Expose this as a method so that the parsing may be updated when the user has added additional, custom types
  31. _refreshTypeParser(dataType) {
  32. parserStore.refresh(dataType);
  33. }
  34. _clearTypeParser() {
  35. parserStore.clear();
  36. }
  37. async getConnection(options) {
  38. options = options || {};
  39. options.uuid = options.uuid || 'default';
  40. options.storage = this.sequelize.options.storage || this.sequelize.options.host || ':memory:';
  41. options.inMemory = options.storage === ':memory:' ? 1 : 0;
  42. const dialectOptions = this.sequelize.options.dialectOptions;
  43. const defaultReadWriteMode = this.lib.OPEN_READWRITE | this.lib.OPEN_CREATE;
  44. options.readWriteMode = dialectOptions && dialectOptions.mode || defaultReadWriteMode;
  45. if (this.connections[options.inMemory || options.uuid]) {
  46. return this.connections[options.inMemory || options.uuid];
  47. }
  48. if (!options.inMemory && (options.readWriteMode & this.lib.OPEN_CREATE) !== 0) {
  49. // automatic path provision for `options.storage`
  50. fs.mkdirSync(path.dirname(options.storage), { recursive: true });
  51. }
  52. const connection = await new Promise((resolve, reject) => {
  53. this.connections[options.inMemory || options.uuid] = new this.lib.Database(
  54. options.storage,
  55. options.readWriteMode,
  56. err => {
  57. if (err) return reject(new sequelizeErrors.ConnectionError(err));
  58. debug(`connection acquired ${options.uuid}`);
  59. resolve(this.connections[options.inMemory || options.uuid]);
  60. }
  61. );
  62. });
  63. if (this.sequelize.config.password) {
  64. // Make it possible to define and use password for sqlite encryption plugin like sqlcipher
  65. connection.run(`PRAGMA KEY=${this.sequelize.escape(this.sequelize.config.password)}`);
  66. }
  67. if (this.sequelize.options.foreignKeys !== false) {
  68. // Make it possible to define and use foreign key constraints unless
  69. // explicitly disallowed. It's still opt-in per relation
  70. connection.run('PRAGMA FOREIGN_KEYS=ON');
  71. }
  72. return connection;
  73. }
  74. releaseConnection(connection, force) {
  75. if (connection.filename === ':memory:' && force !== true) return;
  76. if (connection.uuid) {
  77. connection.close();
  78. debug(`connection released ${connection.uuid}`);
  79. delete this.connections[connection.uuid];
  80. }
  81. }
  82. }
  83. module.exports = ConnectionManager;
  84. module.exports.ConnectionManager = ConnectionManager;
  85. module.exports.default = ConnectionManager;