Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sqlite): automatic path provision for 'options.storage' #11853

Merged
merged 6 commits into from
Jan 22, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 14 additions & 4 deletions lib/dialects/sqlite/connection-manager.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

const path = require('path');
const jetpack = require('fs-jetpack');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to use this new library, its unpacked size is ~300KB

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sushantdhiman You're right. This package greatly simplifies the work with the file system, makes the code more concise. In addition, sequelize included it as devDependency. But I did not pay attention to its size. This in my opinion is not critical on the server side, but due to the fact that the file system is used by the sequelize only in ~ one place, I would like to do the following:

  1. Return the fs-jetpack package back to devDependencies and use it only for the convenience of writing tests
  2. Write my own function(similar to jetpack.dir()) to automatically provide the path for sqlite.options.storage

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is only used for creating a path jetpack.dir(path.dirname(options.storage)), right? With Node 10 mkdirSync supports this https://nodejs.org/docs/latest-v10.x/api/fs.html#fs_fs_mkdirsync_path_options

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Since sequelize 6.x will support node version 10 and more, we can actually use fs.mkdir(path, { recursive: true }). Can I do a pull request?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will be great @multum

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, nice catch @sushantdhiman, sorry about that.

const AbstractConnectionManager = require('../abstract/connection-manager');
const Promise = require('../../promise');
const { logger } = require('../../utils/logger');
Expand Down Expand Up @@ -44,19 +46,27 @@ class ConnectionManager extends AbstractConnectionManager {
getConnection(options) {
options = options || {};
options.uuid = options.uuid || 'default';
options.inMemory = (this.sequelize.options.storage || this.sequelize.options.host || ':memory:') === ':memory:' ? 1 : 0;
options.storage = this.sequelize.options.storage || this.sequelize.options.host || ':memory:';
options.inMemory = options.storage === ':memory:' ? 1 : 0;

const dialectOptions = this.sequelize.options.dialectOptions;
options.readWriteMode = dialectOptions && dialectOptions.mode;
options.readWriteMode = dialectOptions && dialectOptions.mode || this.lib.OPEN_READWRITE | this.lib.OPEN_CREATE; // default mode;
multum marked this conversation as resolved.
Show resolved Hide resolved

if (this.connections[options.inMemory || options.uuid]) {
return Promise.resolve(this.connections[options.inMemory || options.uuid]);
}

if (
!options.inMemory &&
options.readWriteMode === (this.lib.OPEN_READWRITE | this.lib.OPEN_CREATE)
multum marked this conversation as resolved.
Show resolved Hide resolved
) {
jetpack.dir(path.dirname(options.storage)); // automatic path provision for `options.storage`
}

return new Promise((resolve, reject) => {
this.connections[options.inMemory || options.uuid] = new this.lib.Database(
this.sequelize.options.storage || this.sequelize.options.host || ':memory:',
options.readWriteMode || this.lib.OPEN_READWRITE | this.lib.OPEN_CREATE, // default mode
options.storage,
options.readWriteMode,
err => {
if (err) return reject(new sequelizeErrors.ConnectionError(err));
debug(`connection acquired ${options.uuid}`);
Expand Down
37 changes: 11 additions & 26 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"bluebird": "^3.7.1",
"debug": "^4.1.1",
"dottie": "^2.0.0",
"fs-jetpack": "^2.2.3",
"inflection": "1.12.0",
"lodash": "^4.17.15",
"moment": "^2.24.0",
Expand Down Expand Up @@ -64,7 +65,6 @@
"eslint": "^6.8.0",
"eslint-plugin-jsdoc": "^4.1.1",
"eslint-plugin-mocha": "^6.2.2",
"fs-jetpack": "^2.2.2",
"husky": "^1.3.1",
"js-combinatorics": "^0.5.5",
"lcov-result-merger": "^3.0.0",
Expand Down
30 changes: 26 additions & 4 deletions test/integration/configuration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,24 @@ if (dialect === 'sqlite') {
describe(Support.getTestDialectTeaser('Configuration'), () => {
describe('Connections problems should fail with a nice message', () => {
it('when we don\'t have the correct server details', () => {
const seq = new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, { storage: '/path/to/no/where/land', logging: false, host: '0.0.0.1', port: config[dialect].port, dialect });
if (dialect === 'sqlite') {
const seq = new Sequelize(
config[dialect].database,
config[dialect].username,
config[dialect].password,
{
storage: '/path/to/no/where/land',
logging: false,
host: '0.0.0.1',
port: config[dialect].port,
dialect,
dialectOptions: { mode: sqlite3.OPEN_READONLY }
}
);
// SQLite doesn't have a breakdown of error codes, so we are unable to discern between the different types of errors.
return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(Sequelize.ConnectionError, 'SQLITE_CANTOPEN: unable to open database file');
}
const seq = new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, { storage: '/path/to/no/where/land', logging: false, host: '0.0.0.1', port: config[dialect].port, dialect });
multum marked this conversation as resolved.
Show resolved Hide resolved
return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith([Sequelize.HostNotReachableError, Sequelize.InvalidConnectionError]);
});

Expand All @@ -33,7 +46,12 @@ describe(Support.getTestDialectTeaser('Configuration'), () => {
return;
}

const seq = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', { logging: false, host: config[dialect].host, port: 1, dialect });
const seq = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', {
logging: false,
host: config[dialect].host,
port: 1,
dialect
});
if (dialect === 'sqlite') {
// SQLite doesn't require authentication and `select 1 as hello` is a valid query, so this should be fulfilled not rejected for it.
return expect(seq.query('select 1 as hello')).to.eventually.be.fulfilled;
Expand All @@ -43,7 +61,11 @@ describe(Support.getTestDialectTeaser('Configuration'), () => {

it('when we don\'t have a valid dialect.', () => {
expect(() => {
new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, { host: '0.0.0.1', port: config[dialect].port, dialect: 'some-fancy-dialect' });
new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {
host: '0.0.0.1',
port: config[dialect].port,
dialect: 'some-fancy-dialect'
});
}).to.throw(Error, 'The dialect some-fancy-dialect is not supported. Supported dialects: mssql, mariadb, mysql, postgres, and sqlite.');
});
});
Expand Down Expand Up @@ -88,7 +110,7 @@ describe(Support.getTestDialectTeaser('Configuration'), () => {
);
})
.then(() => {
// By default, sqlite creates a connection that's READWRITE | CREATE
// By default, sqlite creates a connection that's READWRITE | CREATE
const sequelize = new Sequelize('sqlite://foo', {
storage: p
});
Expand Down
28 changes: 20 additions & 8 deletions test/integration/dialects/sqlite/connection-manager.test.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
'use strict';

const chai = require('chai');
const fs = require('fs');
const jetpack = require('fs-jetpack');
multum marked this conversation as resolved.
Show resolved Hide resolved
const path = require('path');
const expect = chai.expect;
const Support = require('../../support');
const dialect = Support.getTestDialect();
const DataTypes = require('../../../../lib/data-types');

const fileName = `${Math.random()}_test.sqlite`;
const folderName = `${Math.random()}_test_folder`;

if (dialect === 'sqlite') {
describe('[SQLITE Specific] Connection Manager', () => {
after(() => {
fs.unlinkSync(path.join(__dirname, fileName));
jetpack.remove(path.join(__dirname, fileName));
jetpack.remove(path.join(__dirname, folderName));
});

it('close connection and remove journal and wal files', function() {
Expand All @@ -32,19 +34,29 @@ if (dialect === 'sqlite') {
});
})
.then(() => {
expect(fs.existsSync(path.join(__dirname, fileName))).to.be.true;
expect(fs.existsSync(path.join(__dirname, `${fileName}-shm`)), 'shm file should exists').to.be.true;
expect(fs.existsSync(path.join(__dirname, `${fileName}-wal`)), 'wal file should exists').to.be.true;
expect(jetpack.exists(path.join(__dirname, fileName))).to.be.equal('file');
expect(jetpack.exists(path.join(__dirname, `${fileName}-shm`)), 'shm file should exists').to.be.equal('file');
expect(jetpack.exists(path.join(__dirname, `${fileName}-wal`)), 'wal file should exists').to.be.equal('file');

return sequelize.close();
})
.then(() => {
expect(fs.existsSync(path.join(__dirname, fileName))).to.be.true;
expect(fs.existsSync(path.join(__dirname, `${fileName}-shm`)), 'shm file exists').to.be.false;
expect(fs.existsSync(path.join(__dirname, `${fileName}-wal`)), 'wal file exists').to.be.false;
expect(jetpack.exists(path.join(__dirname, fileName))).to.be.equal('file');
expect(jetpack.exists(path.join(__dirname, `${fileName}-shm`)), 'shm file exists').to.be.false;
expect(jetpack.exists(path.join(__dirname, `${fileName}-wal`)), 'wal file exists').to.be.false;

return this.sequelize.query('PRAGMA journal_mode = DELETE');
});
});

it('automatic path provision for `options.storage`', () => {
return Support.createSequelizeInstance({
storage: path.join(__dirname, folderName, fileName)
multum marked this conversation as resolved.
Show resolved Hide resolved
})
.define('User', { username: DataTypes.STRING })
.sync({ force: true }).then(() => {
expect(jetpack.exists(path.join(__dirname, folderName, fileName))).to.be.equal('file');
});
});
});
}