From c3957a84d4e82ace3d8b4417254b920b4e27420c Mon Sep 17 00:00:00 2001 From: stombretrooper Date: Sat, 3 Oct 2020 23:39:43 +0200 Subject: [PATCH 1/8] ADD draft migration class --- lib/migration/migration.classFactory.js | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 lib/migration/migration.classFactory.js diff --git a/lib/migration/migration.classFactory.js b/lib/migration/migration.classFactory.js new file mode 100644 index 0000000..cb7e2d3 --- /dev/null +++ b/lib/migration/migration.classFactory.js @@ -0,0 +1,48 @@ + +/** + * Inject dependencies in migration + * @param {Ilorm} ilorm The ilorm linked with the Migration class + * @returns {Migration} The Migration class bound with this ilorm instance + */ +function injectDependencies({ ilorm, }) { + const migrationsIndex = []; + + /** + * Migration class + * class to manage migration of data + */ + return class Migration { + /** + * Create a migration + * @param {Number} timestamp The timestamp associate with this migration + * @param {Schema} schema The schema bound with this migration + * @param {Function} up Handler called to apply this migration + * @param {Function} down Handler called to rollback this migration + */ + constructor({ timestamp, schema, up, down, }) { + this.time = timestamp; + this.schema = schema; + this.up = up; + this.down = down; + + migrationsIndex.push(this); + migrationsIndex.sort((migrationA, migrationB) => migrationA.time - migrationB.time); + } + + /** + * Apply this migration + */ + up() { + + } + + /** + * Rollback this migration + */ + down() { + + } + }; +} + +module.exports = injectDependencies; From d544842617b19d6b59a2d5270b00fffcc80dd64e Mon Sep 17 00:00:00 2001 From: stombretrooper Date: Sun, 4 Oct 2020 17:31:17 +0200 Subject: [PATCH 2/8] ADD basic test on migration --- spec/common/extra/migration.js | 53 ++++++++++++++++++++++++++++++++++ spec/common/index.js | 3 +- 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 spec/common/extra/migration.js diff --git a/spec/common/extra/migration.js b/spec/common/extra/migration.js new file mode 100644 index 0000000..c59b618 --- /dev/null +++ b/spec/common/extra/migration.js @@ -0,0 +1,53 @@ +const { expect, } = require('chai'); + +const TIME = 1601825250; + +module.exports = (TestContext) => { + const testContext = TestContext.getInvoices(); + + describe('Migration', () => { + const { Schema, } = testContext.ilorm; + + // Always most recent version; + const userSchema = new Schema({ + id: Schema.string(), + firstName: Schema.string(), + lastName: Schema.string(), + age: Schema.number(), + }); + + it('API up / down should work', () => { + userSchema + .up((UserModel) => { + UserModel.query() + .stream() + .on('data', (user) => { + const [ firstName, lastName, ] = user.name.split(' '); + + user.firstName = firstName; + user.lastName = lastName; + user.age = parseInt(user.previous.age); + user.save(); + }); + }) + .down((UserModel) => { + UserModel.query() + .stream() + .on('data', (user) => { + user.name = `${user.firstName} ${user.lastName}`; + user.age = `${user.previous.age}`; + user.save(); + }); + }); + }); + + it('API version should work', () => { + userSchema.version(TIME, { + id: Schema.string(), + name: Schema.string(), + age: Schema.string(), + }); + }); + + }); +}; diff --git a/spec/common/index.js b/spec/common/index.js index 66c942d..0ba9d05 100644 --- a/spec/common/index.js +++ b/spec/common/index.js @@ -1,5 +1,3 @@ -const TEST_DEFAULT_CONFIG = require('./config'); - /* eslint-disable global-require */ const TESTS = { base: { @@ -25,6 +23,7 @@ const TESTS = { extra: { collectionManager: require('./extra/collectionManager'), transaction: require('./extra/transaction'), + migration: require('./extra/migration'), }, }; From 92936da453b9408397f87736509e3a1ee2c8a72a Mon Sep 17 00:00:00 2001 From: stombretrooper Date: Sun, 4 Oct 2020 17:36:57 +0200 Subject: [PATCH 3/8] ADD methods to declare schema version and up and down operations --- lib/schema/baseSchema.class.js | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/lib/schema/baseSchema.class.js b/lib/schema/baseSchema.class.js index 489b710..c5bcde4 100644 --- a/lib/schema/baseSchema.class.js +++ b/lib/schema/baseSchema.class.js @@ -23,6 +23,7 @@ class BaseSchema { * @param {Object} options Options to apply to the schema */ constructor(schemaDefinition, options = {}) { + this.pastSchema = []; this.definition = schemaDefinition; this.properties = Object.keys(this.definition); this.options = { @@ -60,6 +61,44 @@ class BaseSchema { return new this(schema.definition, schema.options); } + /** + * Declare the function to run to apply this schema (in schema migration) + * @param {Function} handler The handler to run + * @returns {Schema} Return schema for chained call + */ + up(handler) { + this.onUp = handler; + + return this; + } + + /** + * Declare the function to rollback from this schema (in schema migration) + * @param {Function} handler The handler to run + * @returns {Schema} Return schema for chained call + */ + down(handler) { + this.onDown = handler; + + return this; + } + + /** + * Declare a past version of the current schema + * @param {Number} timestamp The timestamp associate with this schema version + * @param {Object} schema The previous schema definition + * @returns {Schema} Return version of the schema for chained call + */ + version(timestamp, schema) { + const instantiatedSchema = new this.constructor(schema); + + instantiatedSchema.timestamp = timestamp; + + this.pastSchema.push(instantiatedSchema); + this.pastSchema.sort((schemaA, schemaB) => schemaB.timestamp - schemaA.timestamp); + + return instantiatedSchema; + } /** * Return primary key of the current schema From 02042377523f2e6c2c0fcaf96bbfc22e7c4c0fad Mon Sep 17 00:00:00 2001 From: stombretrooper Date: Sun, 4 Oct 2020 17:49:25 +0200 Subject: [PATCH 4/8] MOD way to chain version --- lib/schema/baseSchema.class.js | 13 ++++++++++++- spec/common/extra/migration.js | 17 ++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/schema/baseSchema.class.js b/lib/schema/baseSchema.class.js index c5bcde4..05d69e7 100644 --- a/lib/schema/baseSchema.class.js +++ b/lib/schema/baseSchema.class.js @@ -89,10 +89,21 @@ class BaseSchema { * @param {Object} schema The previous schema definition * @returns {Schema} Return version of the schema for chained call */ - version(timestamp, schema) { + version(timestamp, schema = null) { + if (this.currentSchema) { + return this.currentSchema.version(timestamp, schema); + } + + if (!schema) { + this.timestamp = timestamp; + + return this; + } + const instantiatedSchema = new this.constructor(schema); instantiatedSchema.timestamp = timestamp; + instantiatedSchema.currentSchema = this; this.pastSchema.push(instantiatedSchema); this.pastSchema.sort((schemaA, schemaB) => schemaB.timestamp - schemaA.timestamp); diff --git a/spec/common/extra/migration.js b/spec/common/extra/migration.js index c59b618..67e031a 100644 --- a/spec/common/extra/migration.js +++ b/spec/common/extra/migration.js @@ -1,6 +1,8 @@ const { expect, } = require('chai'); -const TIME = 1601825250; +const TIME_V1 = 1581125250; +const TIME_V2 = 1601825250; +const TIME_V3 = 1611825250; module.exports = (TestContext) => { const testContext = TestContext.getInvoices(); @@ -18,6 +20,7 @@ module.exports = (TestContext) => { it('API up / down should work', () => { userSchema + .version(TIME_V3) .up((UserModel) => { UserModel.query() .stream() @@ -42,11 +45,19 @@ module.exports = (TestContext) => { }); it('API version should work', () => { - userSchema.version(TIME, { + userSchema.version(TIME_V2, { id: Schema.string(), name: Schema.string(), age: Schema.string(), - }); + }) + .version(TIME_V1, { + id: Schema.string(), + name: Schema.string(), + }); + }); + + it('Should apply past schema', () => { + }); }); From 464c31a9ba9c5858057d7cb2d91873cd78f6bca8 Mon Sep 17 00:00:00 2001 From: stombretrooper Date: Sun, 1 Nov 2020 14:01:57 +0100 Subject: [PATCH 5/8] ADD migration to config --- spec/common/config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/common/config.js b/spec/common/config.js index 2509da5..d8a5db0 100644 --- a/spec/common/config.js +++ b/spec/common/config.js @@ -22,5 +22,6 @@ module.exports = { extra: { collectionManager: true, transaction: true, + migration: true, }, }; From efd30917853146c0bac46c402691fed1161cd3a4 Mon Sep 17 00:00:00 2001 From: stombretrooper Date: Sun, 1 Nov 2020 14:04:01 +0100 Subject: [PATCH 6/8] FIX import break --- spec/common/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/common/index.js b/spec/common/index.js index 0ba9d05..c54ea34 100644 --- a/spec/common/index.js +++ b/spec/common/index.js @@ -1,3 +1,5 @@ +const TEST_DEFAULT_CONFIG = require('./config'); + /* eslint-disable global-require */ const TESTS = { base: { From 8cbbf8e4976ece323234f0bcd8712d038f28cf10 Mon Sep 17 00:00:00 2001 From: stombretrooper Date: Sat, 7 Nov 2020 18:08:28 +0100 Subject: [PATCH 7/8] Add migrationConnector --- .../migrationConnector.classFactory.js | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 lib/migration/migrationConnector.classFactory.js diff --git a/lib/migration/migrationConnector.classFactory.js b/lib/migration/migrationConnector.classFactory.js new file mode 100644 index 0000000..5cffa41 --- /dev/null +++ b/lib/migration/migrationConnector.classFactory.js @@ -0,0 +1,44 @@ +/** + * Inject dependencies to create MigrationConnector + * @param {ilorm} ilorm Ilorm instance bound with the context + * @param {Connector} Connector The connector linked with the Migration Connector + * @returns {MigrationConnector} Return the MigrationConnector + */ +function injectDependencies({ ilorm, Connector, }) { + const { Schema, newModel, } = ilorm; + const migrationSchema = new Schema({ + appliedAt: Schema.date().default(() => Date.now()), + version: Schema.string(), + }); + const modelParams = { + connector: new Connector({ + sourceName: 'ilormMigration', + }), + schema: migrationSchema, + }; + + /** + * MigrationModel class + * Model managed by ilorm to manage migration, only used if the user use migration system + */ + class MigrationModel extends newModel(modelParams) { + /** + * Get last migration + * @returns {MigrationModel} The last migration applied + */ + getLastMigration() { + return this.query() + .appliedAt.useAsSortDesc() + .findOne(); + } + } + + /** + * Manage all migration as a Connector level + */ + return class MigrationConnector { + + }; +} + +module.exports = injectDependencies; From 8974af1aa754badc73ab528e3f508f7bd87b2a9b Mon Sep 17 00:00:00 2001 From: stombretrooper Date: Sat, 7 Nov 2020 18:08:43 +0100 Subject: [PATCH 8/8] MOD improve test on migration --- spec/common/extra/migration.js | 51 ++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/spec/common/extra/migration.js b/spec/common/extra/migration.js index 67e031a..defaff4 100644 --- a/spec/common/extra/migration.js +++ b/spec/common/extra/migration.js @@ -5,10 +5,16 @@ const TIME_V2 = 1601825250; const TIME_V3 = 1611825250; module.exports = (TestContext) => { - const testContext = TestContext.getInvoices(); + const testContext = new TestContext(); describe('Migration', () => { - const { Schema, } = testContext.ilorm; + after(async () => { + await testContext.deleteSource('users'); + + return testContext.finalCleanUp(); + }); + + const { Schema, newModel, } = testContext.ilorm; // Always most recent version; const userSchema = new Schema({ @@ -45,20 +51,49 @@ module.exports = (TestContext) => { }); it('API version should work', () => { - userSchema.version(TIME_V2, { - id: Schema.string(), - name: Schema.string(), - age: Schema.string(), - }) + userSchema + .version(TIME_V2, { + id: Schema.string(), + name: Schema.string(), + age: Schema.string(), + }) .version(TIME_V1, { id: Schema.string(), name: Schema.string(), + }) + .up(async (UserModel) => { + const user = new UserModel(); + + user.id = '12345'; + user.name = 'Guillaume Daix'; + + await user.save(); }); }); - it('Should apply past schema', () => { + let UserModel; + + it('Should build db with the most recent schema', async () => { + UserModel = newModel({ + connector: new testContext.Connector({ + sourceName: 'users', + }), + schema: userSchema, + }); + // Will apply migration in order; + await UserModel.applyMigration(); + + // The migration TIME_V1 insert a user on an old schema, in theory, still in the db; + const user = await UserModel.query().findOne(); + + expect(user.id).to.be.equals('12345'); + expect(user.firstName).to.be.equals('Guillaume'); + expect(user.lastName).to.be.equals('Daix'); }); + it('Should rollback on past schema', () => {}); + + it('Should re-up on last schema', () => {}); }); };