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

Migrations - Add Multiple Columns Example? #133

Closed
countrdd opened this issue May 10, 2015 · 52 comments
Closed

Migrations - Add Multiple Columns Example? #133

countrdd opened this issue May 10, 2015 · 52 comments

Comments

@countrdd
Copy link

I am somewhat of a newbie, and I have a single add column working, but a couple of questions.

how can I add multiple columns in one migration file...
also, when I rerun my app I am seeing ER_DUP_FIELDNAME: Duplicate column name 'test' when I run the node server.js the next time...which is correct because it has already been created, should I worry abot catching and ignore the error or just let it run as it is?

Also, if there are multiple migration files in the directory what order are they ran?

@abams
Copy link

abams commented May 19, 2015

I was just looking for the answer to this and couldn't find it anywhere. I managed to figure it out though.

'use strict';

module.exports = {
  up: function (queryInterface, Sequelize) {
    return [
      queryInterface.addColumn(
        'users',
        'email',
        {
          type: Sequelize.STRING,
          allowNull: false
        }
      ),
      queryInterface.addColumn(
        'users',
        'encryptedPassword',
        {
          type: Sequelize.STRING,
          allowNull: false
        }
      )
    ];
  },

  down: function (queryInterface, Sequelize) {
    return [
      queryInterface.removeColumn('users', 'email'),
      queryInterface.removeColumn('users', 'encryptedPassword')
    ];
  }
};

Regarding the error, I'd need to see your migrations to have a better idea about what is going on.

Sequelize will run migrations sorted by the filename. You can run

sequelize migration:create --name add-email-and-password-to-user

from the command line to create a new migration prefixed with a timestamp that will order by when they were created.

@sdepold
Copy link
Member

sdepold commented Jun 6, 2015

Hey guys. The migration functions are supposed to return promises. So if you want to do multiple things in a migration, just return a chained promised like this:

return queryInterface.removeColumn('users', 'email').then(function () {
  return queryInterface.removeColumn('users', 'encryptedPassword');
});

@sdepold
Copy link
Member

sdepold commented Jun 6, 2015

as said, we would need to see your migration to answer the question about the error

@connected-rcheung
Copy link

@sdepold That was helpful. I saw no mention of these migration functions returning promises. Was running into some weird race conditions due to returning an array of functions that are asynchronous in nature.

@dantman
Copy link
Contributor

dantman commented Aug 27, 2015

I'd love to add multiple columns in a single migration as well. But chaining the promises actually isn't a good idea.

There currently isn't any type of rollback for column changes. So if you try and add 2 columns in the same migration and the 2nd fails. Then the migration will fail but column no. 1 will still exist. And when you re-run the migrations they will eternally fail because the migration will try to create column no. 1 a second time and conflict.

Thanks to that, as annoying as it is, you actually need a separate migration for each column you want to add, each index you want to add, etc. For your migrations to be reliable.

@dantman
Copy link
Contributor

dantman commented Aug 27, 2015

Though I do wonder if there is a nice alternative to unreliable migrations or being forced to use a separate migration file for each individual change in a large migration.

Something like a multi-migration, ie: a single migration file that actually contains multiple migrations.

"use strict";

module.exports = [
    {
        up: function(migration, Sequelize) {
            return migration
                .addColumn(
                    'FooBar',
                    'B',
                    {
                        type: Sequelize.STRING,
                        allowNull: true
                    });
        },
        down: function(migration) {
            return migration
                .removeColumn('FooBar', 'B');
        }
    },
    {
        up: function(migration, Sequelize) {
            return migration
                .addColumn(
                    'FooBar',
                    'C',
                    {
                        type: Sequelize.STRING,
                        allowNull: true
                    });
        },
        down: function(migration) {
            return migration
                .removeColumn('FooBar', 'C');
        }
    }
];

For these SequelizeMeta would contain either multiple 20150801013124-mig-migration.js#1 entries (with the #1 identifying the individual migrations in the file) or SequelizeMeta would have a second column, a nullable integer indicating which migration within the file the row corresponds to.

@renewooller
Copy link

I also find this annoying. +1 for @dantman's suggestion. As a work around I have found that adding the migration name manually to the SequelizeMeta table allows me to run undo, which, if you structure the rollback symmetrically, will then error out at the same point. This will have the effect of removing the same columns up to the point of error, which is effectively a rollback of a multi column update.

This work around could be made easier by having the sequelize db:migrate:undo be able to specify a specific migration that needs to be undone, or run it on the one that is not in SequelizeMeta. IE sequelize should anticipate that people may want to rollback their migrations when they fail.

@panbanda
Copy link

panbanda commented May 4, 2016

Hey guys, just thought I would share a solution in case you are still looking for one. I don't like super-chaining my commands because it gets hard to read after a while, and I get lost in the indentation. Especially if you're adding a ton of column changes or other alterations. I generally don't like to chain column changes anyway because if the migration fails then you have a big issue on your hands in terms of figuring out what worked and what didn't. So I basically only use chaining for initial table creation + indexes (or else I would need a separate migration for each index, which is painful). I do this because the indexes are erased when calling down() when the table is dropped. So my migrations would look like this:

var promise = require('bluebird');

module.exports = {
  up: function (queryInterface, Sequelize) {
    var actions = [];

    // Create the main table
    actions.push({
      exec: function() {
        return queryInterface.createTable(...);
      }
    });

    // Append the indexes and whatnot
    actions.push({ exec: function() { return queryInterface.addIndex('users', ['...']); }});

    // Go through one by one (with concurrency!) and process each function
    return promise.map(actions, function(item) {
      return item.exec();
    }, { concurrency: 1 });
  },

  down: function (queryInterface, Sequelize) {
    queryInterface.dropTable('users');
  }
};

@parris
Copy link

parris commented Jul 31, 2016

With node 6 the following appears to work...

module.exports = {
    up: function(queryInterface, Sequelize) {
        let migrations = [];

        migrations.push(queryInterface.addColumn(
            'Character',
            'user_id',
            {
                type: Sequelize.STRING,
                allowNull: false,
            }
        ));

        return Promise.all(migrations);
    },

    down: function(queryInterface, Sequelize) {
        let migrations = [];

        migrations.push(queryInterface.removeColumn('Character', 'user_id', Sequelize.STRING));

        return Promise.all(migrations);
    }
};

@dantman
Copy link
Contributor

dantman commented Jul 31, 2016

@parris If you add another add/removeColumn to that migration it will suffer from the same issue as chaining using .then.

If even one of your addColumn commands succeeds but any others fail, then your migrations will be stuck in a state they cannot migrate or escape from. Any attempt to re-run the migration will fail, because it will try to create a column that already exists. Any attempt to undo the migration will also fail, because it will try to remove a column that doesn't exist.

@diosney
Copy link

diosney commented Aug 3, 2016

@dantman I see what do you mean, how can we protect against that?

@diosney
Copy link

diosney commented Aug 3, 2016

@dantman Maybe just like @jylinman proposed, though in the inverse operation triggers an error in the middle, maybe can get the migrations in a recoverable state if the operations are inversed too?

I mean (in the same migration):

up:

  • create model A
  • create model B

down:

  • remove model B
  • remove model A

I'm asking this because I have a migration which change a description column type in all of my models, and it can get in that hole too.

@createthis
Copy link

Aren't transactions the solution to the bad state issue described by dantman? #133 (comment)

I believe that's how Rails deal with multiple DB changes in a single migration. Each migration runs within a transaction. If any individual change fails, the transaction rolls back.

@diosney
Copy link

diosney commented Oct 20, 2016

@createthis Agree. If transactions could be used in the migrations, that will be the solution for this.

@createthis
Copy link

They... can't?

@dantman
Copy link
Contributor

dantman commented Oct 20, 2016

@createthis @diosney MySQL (at least) does not support transactions for ALTER TABLE. Any transactions are implicitly committed before table structure changes start.

To be honest, I don't blame them. Transactions are primarily meant for rolling back data changes. It sounds absurd to try and attempt to apply them to changes that alter the very structure of that data which can implicitly alter values within every single row of a table of undefined size.

@createthis
Copy link

createthis commented Oct 20, 2016

Thanks. That's interesting. I never noticed that. Worked with Rails and MySQL for years. I guess the recent trend toward Postgres spoiled me. So, in sqlite and postgres, would something like this work?

  up: function (queryInterface, Sequelize) {
    var sequelize = queryInterface.sequelize;
    return sequelize.transaction(function (t) {
      var migrations = []; 
      migrations.push(queryInterface.addColumn(
        'products',
        'some_count',
        Sequelize.INTEGER.UNSIGNED,
        {transaction: t}
      )); 
      migrations.push(queryInterface.addColumn(
        'products',
        'some_other_property',
        Sequelize.INTEGER.UNSIGNED,
        {transaction: t}
      )); 
      return Promise.all(migrations);
    }); 
  },

  down: function (queryInterface, Sequelize) {
    var sequelize = queryInterface.sequelize;
    return sequelize.transaction(function (t) {
      var migrations = [];
      migrations.push(queryInterface.removeColumn('products', 'some_count', {transaction: t}));
      migrations.push(queryInterface.removeColumn('products', 'some_other_property', {transaction: t}));
      return Promise.all(migrations);
    });
  }

Seems to work on my end, but I haven't tried to make it fail yet.

@dantman
Copy link
Contributor

dantman commented Oct 20, 2016

@createthis Transaction wise it should work in PG/SQlite. However I'd advise against an array+Promise.all based method. You're doing 2 ALTER table operations in parallel. You're protected from the worst by transactions, but you could still end up with weird race conditions; like the order of columns in your table being different between different runs and confusing changes in error messages if both your alterations fail but which one errors out first differs between migration attempts.

@createthis
Copy link

createthis commented Oct 20, 2016

@dantman actually, in the previous example I gave, I'm getting this:

Sequelize [Node: 6.8.0, CLI: 2.4.0, ORM: 3.24.4, sqlite3: ^3.1.6]

Loaded configuration file "config/config.json".
Using environment "development".
== 20161020154112-AddStatisticsToProducts: reverting =======
Unhandled rejection SequelizeDatabaseError: SQLITE_ERROR: no such table: products

I can delete the sqlite db and run the migrations forward, but any time I try to undo the last migration I get the above error. It's like it's grabbing the wrong table or something because I manually verified the table exists using the sqlite3 cli tool.

@createthis
Copy link

Spent some time debugging this. Turns out the error message in the previous comment was a direct result of the queries happening async as @dantman warned. I'd forgotten that SQLite has no drop column. Sequelize simulates drop column by dropping the table and recreating it. In this case it was dropping the table twice, thus the error on the second drop table. I figured it out by enabling sequelize console logging and examining the SQL queries logged.

Here's my revised strategy (for posterity):

  up: function (queryInterface, Sequelize) {
    var sequelize = queryInterface.sequelize;
    return sequelize.transaction(function (t) {
      return queryInterface.addColumn('products', 'some_count', Sequelize.INTEGER.UNSIGNED, {transaction: t}) 
        .then(function () {
          return queryInterface.addColumn('products', 'some_other_property', Sequelize.INTEGER.UNSIGNED, {transaction: t});
        });
    });     
  },    

  down: function (queryInterface, Sequelize) {
    var sequelize = queryInterface.sequelize;
    return sequelize.transaction(function (t) {
      return queryInterface.removeColumn('products', 'some_count', {transaction: t})
        .then(function () {
          return queryInterface.removeColumn('products', 'some_other_property', {transaction: t});
        });
    });
  }

I've tested this and it works as expected in multiple migrate/undo cycles. The indentation is a little deeper than I'd like, but at least it never goes deeper no matter how many .then clauses are appended.

@dantman
Copy link
Contributor

dantman commented Oct 20, 2016

@createthis Guess you discovered another race condition I didn't think about that makes running db migration steps in parallel a bad idea.

Btw, if you're willing to use Babel for now, then async/await could flatten that a little more.

@eddiejaoude
Copy link
Contributor

Migrations must be sync not async as they must run in order.

Also having multiple migrations per file is not recommended, even if the down is going in reverse order, here is why:

UP

  • Migration A
  • Migration B
  • Migration C

DOWN

  • Migration C
  • Migration B
  • Migration A

If on the UP Migration B fails, then on the down Migration C will also fail as it never occurred in the UP. Now migrations will be in an unrecoverable state.

@iszlailorand
Copy link

@eddiejaoude probably that's why createthis used transactions, if the B will fail then all the migration will rolleback.

@chuan137
Copy link

@createthis you might get better indentation by putting the first query into Promise.resolve

  return Promise.resolve(
    queryInterface(...)
  ).then(() => {
    queryInterface(...)
  })

@goodanthony
Copy link

goodanthony commented May 28, 2017

This what worked:

sequelize migration:create --name add-password-to-user


'use strict';

module.exports = {
 up(queryInterface, Sequelize) {
  return queryInterface.sequelize.transaction(function handleTransaction (t) {
    return Promise.all([
     queryInterface.addColumn('Users', 'password', Sequelize.STRING, {transaction: t}),
     queryInterface.addColumn('Users', 'resetPasswordToken', Sequelize.STRING, {transaction: t}),
     queryInterface.addColumn('Users', 'resetPasswordExpires', Sequelize.DATE, {transaction: t})
    ]);
  });
 },

 down(queryInterface, Sequelize) {
  return queryInterface.sequelize.transaction(function handleTransaction (t) {
    return Promise.all([
      queryInterface.removeColumn('Users', 'password', {transaction: t}),
      queryInterface.removeColumn('Users', 'resetPasswordToken', {transaction: t}),
      queryInterface.removeColumn('Users', 'resetPasswordExpires',  {transaction: t})
    ]);
  });
 }
}

And then
sequelize db:migrate

If not happy

sequelize db:migrate:undo

Respecting the bellow comment from scottbeacher, the above worked for me on a PostgreSQL database.

@scottbeacher
Copy link

@iamfree-com Alter table causes implicit commits, can't use transactions with those. Has already been mentioned.

https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html
https://dev.mysql.com/doc/refman/5.7/en/cannot-roll-back.html

@godfather
Copy link

Guys, I'm posting here only to document the solution that I have used to have a more clean code on scenario where we have multiple removeColumn calls.

Async/await were a good solution for my case:

down: async (queryInterface, Sequelize) => {
  return [
    await queryInterface.removeColumn('users', 'facebook_id'),
    await queryInterface.removeColumn('users', 'twitter_id'),
    await queryInterface.removeColumn('users', 'gender')
  ];
}

@AlexanderHolman146
Copy link

@godfather ah man I have been searching for about 2 hours for a manageable solution to chaining migrations. This is the best solution I have seen so far. Thank you!

@mattcodez
Copy link

Note, there appears to be a difference in a couple of the suggestions proposed here. Returning an array of promises might be invoking a race condition. Depending on what you're doing, this may not matter. I didn't get an error but I had an issue where a column rename wasn't working after running changeColumn on the same column. Switching to a then chain which forces the calls to run sequentially seemed to fix my issue.

@pldilley
Copy link

pldilley commented Jun 3, 2018

Just a thought, but if you use await, my understanding is that the function will effectively be suspended until the promise resolves whereby it will continue to the next line of code. So there should never be a race condition as I understand it?

@orton009
Copy link

orton009 commented Jun 16, 2018

I'm trying to create tables having dependency in this order in my single migration file but on migration I'm facing the error
cannot find property startYear of undefined


`"use strict";
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface
      .createTable("Users", {
        id: {
          allowNull: false,
          autoIncrement: true,
          primaryKey: true,
          type: Sequelize.INTEGER
        },
        googleId: {
          allowNull: true,
          type: Sequelize.STRING
        },
        facebookId: {
          allowNull: true,
          type: Sequelize.STRING
        },
        fullName: {
          type: Sequelize.STRING,
          allowNull: false
        },
        email: {
          type: Sequelize.STRING,
          allowNull: false,
          unique: true,
          validate: {
            isEmail: true
          }
        },
        password: {
          type: Sequelize.STRING,
          allowNull: false,
          validate: {
            isAlphanumeric: true,
            notEmpty: true
          }
        },
        isVerified: {
          type: Sequelize.BOOLEAN,
          allowNull: false,
          defaultValue: false
        },
        createdAt: {
          allowNull: false,
          type: Sequelize.DATE
        },
        updatedAt: {
          allowNull: false,
          type: Sequelize.DATE
        }
      })
      .then(function() {
        return queryInterface
          .createTable("DesignationMasters", {
            id: {
              allowNull: false,
              autoIncrement: true,
              primaryKey: true,
              type: Sequelize.INTEGER
            },
            designationName: {
              type: Sequelize.STRING,
              allowNull: false,
              unique: true
            },
            createdAt: {
              allowNull: false,
              type: Sequelize.DATE
            },
            updatedAt: {
              allowNull: false,
              type: Sequelize.DATE
            }
          })
          .then(function() {
            return queryInterface
              .createTable("companyDetails", {
                id: {
                  allowNull: false,
                  autoIncrement: true,
                  primaryKey: true,
                  type: Sequelize.INTEGER
                },
                companyName: {
                  type: Sequelize.STRING,
                  allowNull: true,
                  defaultValue: null
                },
                userId: {
                  type: Sequelize.INTEGER,
                  allowNull: false,
                  references: {
                    model: "Users",
                    key: "id"
                  }
                },
                designationId: {
                  type: Sequelize.INTEGER,
                  allowNull: false,
                  references: {
                    model: "DesignationMasters",
                    key: "id"
                  }
                },
                startyear: {
                  type: Sequelize.INTEGER,
                  allowNull: true,
                  validate: {
                    isNumeric: true,
                    len: [4, 4]
                  },
                  defaultValue: null
                },
                endYear: {
                  type: Sequelize.INTEGER,
                  validate: {
                    isNumeric: true,
                    len: [4, 4],
                    min: this.startYear
                  },
                  defaultValue: null
                },
                isCurrentWorkplace: {
                  type: Sequelize.BOOLEAN,
                  defaultValue: false
                },
                createdAt: {
                  allowNull: false,
                  type: Sequelize.DATE
                },
                updatedAt: {
                  allowNull: false,
                  type: Sequelize.DATE
                }
              })
              .then(function() {
                return queryInterface
                  .createTable("InterestMasters", {
                    id: {
                      allowNull: false,
                      autoIncrement: true,
                      primaryKey: true,
                      type: Sequelize.INTEGER
                    },
                    interestValue: {
                      allowNull: false,
                      type: Sequelize.STRING
                    },
                    createdAt: {
                      allowNull: false,
                      type: Sequelize.DATE
                    },
                    updatedAt: {
                      allowNull: false,
                      type: Sequelize.DATE
                    }
                  })
                  .then(function() {
                    return queryInterface.createTable("UserInterests", {
                      id: {
                        allowNull: false,
                        autoIncrement: true,
                        primaryKey: true,
                        type: Sequelize.INTEGER
                      },
                      userId: {
                        type: Sequelize.INTEGER,
                        references: {
                          model: "Users",
                          key: "id"
                        },
                        allowNull: false
                      },
                      interestId: {
                        type: Sequelize.INTEGER,
                        references: {
                          key: "InterestMasters",
                          value: "id"
                        },
                        allowNull: false
                      },
                      createdAt: {
                        allowNull: false,
                        type: Sequelize.DATE
                      },
                      updatedAt: {
                        allowNull: false,
                        type: Sequelize.DATE
                      }
                    });
                  });
              });
          });
      });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable("InterestMasters").then(function() {
      return queryInterface.dropTable("Interests").then(function() {
        return queryInterface.dropTable("companyDetails").then(function() {
          return queryInterface
            .dropTable("DesignationMasters")
            .then(function() {
              return queryInterface.dropTable("Users");
            });
        });
      });
    });
  }
};

@pldilley
Copy link

pldilley commented Jun 16, 2018

Hey orton009,

I would use multiple migrations for this and not try to create/drop multiple tables in one migration. Single migrations with multiple methods is more like if you are adding a whole bunch of columns to a table.

If you really want to do it in one method, you can only have ONE return method per javascript function, because return causes a function to terminate and output the returned result: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return

Finally, just a Github tip (and tech websites like this one), you can use three backticks in a row around your code to so that it is readable. Your code above is indecipherable otherwise! Here is how it works:

how to quote code

Similarly for single line, you can surround with single backticks:
single line quote

P.S. As far as I know, using one column as the minimum value of another column is not possible. This sort of thing is kept in the code as business logic and beyond the responsibilities of the database.

  endYear: {
    type: Sequelize.INTEGER,
    validate: {
      isNumeric: true,
      len: [4, 4],
      min: this.startYear  <--- REMOVE THIS, JUST DO IT IN THE CODE!
    },
    defaultValue: null
  },

If you really must do it, it may be possible by using triggers: https://www.siteground.com/kb/mysql-triggers-use/

In any case, this is beyond the scope of this thread - so if you have any more questions regarding how to set minimum values in this way, I would start a new issue thread: https://github.com/sequelize/cli/issues/new

@orton009
Copy link

Can you provide me any resource on how to create multiple migrations in same file and run those migrations in an order in order to maintain table dependencies

@pldilley
Copy link

Dude, it's in this very thread :P Scroll up!

@orton009
Copy link

This is giving me the error "replace " not defined
I tried removing the last table ("UserInterests") from migrations array then it worked fine but I want to create all of my tables with a single migration file.


"use strict";
module.exports = {
  up: (queryInterface, Sequelize) => {
    let migrations = [];

    migrations.push(
      queryInterface.createTable("Users", {
        id: {
          allowNull: false,
          autoIncrement: true,
          primaryKey: true,
          type: Sequelize.INTEGER
        },
        googleId: {
          allowNull: true,
          type: Sequelize.STRING
        },
        facebookId: {
          allowNull: true,
          type: Sequelize.STRING
        },
        fullName: {
          type: Sequelize.STRING,
          allowNull: false
        },
        email: {
          type: Sequelize.STRING,
          allowNull: false,
          unique: true,
          validate: {
            isEmail: true
          }
        },
        password: {
          type: Sequelize.STRING,
          allowNull: false,
          validate: {
            isAlphanumeric: true,
            notEmpty: true
          }
        },
        isVerified: {
          type: Sequelize.BOOLEAN,
          allowNull: false,
          defaultValue: false
        },
        createdAt: {
          allowNull: false,
          type: Sequelize.DATE
        },
        updatedAt: {
          allowNull: false,
          type: Sequelize.DATE
        }
      })
    );
    migrations.push(
      queryInterface.createTable("DesignationMasters", {
        id: {
          allowNull: false,
          autoIncrement: true,
          primaryKey: true,
          type: Sequelize.INTEGER
        },
        designationName: {
          type: Sequelize.STRING,
          allowNull: false,
          unique: true
        },
        createdAt: {
          allowNull: false,
          type: Sequelize.DATE
        },
        updatedAt: {
          allowNull: false,
          type: Sequelize.DATE
        }
      })
    );

    migrations.push(
      queryInterface.createTable("companyDetails", {
        id: {
          allowNull: false,
          autoIncrement: true,
          primaryKey: true,
          type: Sequelize.INTEGER
        },
        companyName: {
          type: Sequelize.STRING,
          allowNull: true,
          defaultValue: null
        },
        userId: {
          type: Sequelize.INTEGER,
          allowNull: false,
          references: {
            model: "Users",
            key: "id"
          }
        },
        designationId: {
          type: Sequelize.INTEGER,
          allowNull: false,
          references: {
            model: "DesignationMasters",
            key: "id"
          }
        },
        startyear: {
          type: Sequelize.INTEGER,
          allowNull: true,
          validate: {
            isNumeric: true,
            len: [4, 4]
          },
          defaultValue: null
        },
        endYear: {
          type: Sequelize.INTEGER,
          validate: {
            isNumeric: true,
            len: [4, 4],
            min: this.startYear
          },
          defaultValue: null
        },
        isCurrentWorkplace: {
          type: Sequelize.BOOLEAN,
          defaultValue: false
        },
        createdAt: {
          allowNull: false,
          type: Sequelize.DATE
        },
        updatedAt: {
          allowNull: false,
          type: Sequelize.DATE
        }
      })
    );

    migrations.push(
      queryInterface.createTable("InterestMasters", {
        id: {
          allowNull: false,
          autoIncrement: true,
          primaryKey: true,
          type: Sequelize.INTEGER
        },
        interestValue: {
          allowNull: false,
          type: Sequelize.STRING
        },
        createdAt: {
          allowNull: false,
          type: Sequelize.DATE
        },
        updatedAt: {
          allowNull: false,
          type: Sequelize.DATE
        }
      })
    );

    migrations.push(
      queryInterface.createTable("UserInterests", {
        id: {
          allowNull: false,
          autoIncrement: true,
          primaryKey: true,
          type: Sequelize.INTEGER
        },
        userId: {
          type: Sequelize.INTEGER,
          references: {
            model: "Users",
            key: "id"
          },
          allowNull: false
        },
        interestId: {
          type: Sequelize.INTEGER,
          references: {
            key: "InterestMasters",
            value: "id"
          },
          allowNull: false
        },
        createdAt: {
          allowNull: false,
          type: Sequelize.DATE
        },
        updatedAt: {
          allowNull: false,
          type: Sequelize.DATE
        }
      })
    );

    return Promise.all(migrations);
  },
  down: (queryInterface, Sequelize) => {
    let migrations = [];

    migrations.push(queryInterface.dropTable("InterestMasters"));
    migrations.push(queryInterface.dropTable("Interests"));
    migrations.push(queryInterface.dropTable("companyDetails"));
    migrations.push(queryInterface.dropTable("DesignationMasters"));
    migrations.push(queryInterface.dropTable("Users"));
  }
};

@pldilley
Copy link

I'm gonna guess that there's a race condition because there are references to other tables.

references: {
            model: "Users",
            key: "id"
          }

I still highly recommend using multiple migrations, or at least two. However if it must be done in one you can probably put the UserInterests into a then:

return Promise.all(migrations)
     .then(() => queryInterface.createTable("UserInterests", {
        id: {
          allowNull: false,
          autoIncrement: true,
          primaryKey: true,
          type: Sequelize.INTEGER
        },
        userId: {
          type: Sequelize.INTEGER,
          references: {
            model: "Users",
            key: "id"
          },
          allowNull: false
        },
        interestId: {
          type: Sequelize.INTEGER,
          references: {
            key: "InterestMasters",
            value: "id"
          },
          allowNull: false
        },
        createdAt: {
          allowNull: false,
          type: Sequelize.DATE
        },
        updatedAt: {
          allowNull: false,
          type: Sequelize.DATE
        }
      }));

@orton009
Copy link

Its not adding the foreign key error

@orton009
Copy link

orton009 commented Jun 17, 2018

If I am using multiple migrations files for multiple tables then how can I control table creation as tables are using foreign key so former tables must be created before them, how can I specify which migrations to take place first and executing dependent migrations later

@pldilley
Copy link

http://bfy.tw/Ie7q

<_<

@shepelevstas
Copy link

I like @iamfree-com's suggestion most, but the next code fails:

down: (queryInterface, Sequelize) => {
    return queryInterface.sequelize.transaction(function handleTransaction (t) {
      return Promise.all([
        
        queryInterface.renameColumn(
          'Users',
          'uid',
          'email',
          {transaction: t}
        ),
        
        queryInterface.removeColumn(
          'Users',
          'active',
          {transaction: t}
        ),
        
      ]);
    });
  }

@eddiejaoude
Copy link
Contributor

@shepelevstas those should be separate migration steps, if one fails, you won't know which one

@shepelevstas
Copy link

Is this OK? Works for me:

down: (queryInterface, Sequelize) => {
    return queryInterface.sequelize.transaction(async transaction => {

      return [
        await queryInterface.renameColumn(
          'Users',
          'uid',
          'email',
          {transaction}
        ),

        await queryInterface.removeColumn(
          'Users',
          'active',
          {transaction}
        )
      ]
    })
  }

@eddiejaoude
Copy link
Contributor

those should be separate migration steps, if one fails, you won't know which one

@shepelevstas ^^

then when you try to rollback or forwards, one of those might have already run successfully and it won't know but when it tries to run it again it will always now fail

@shepelevstas
Copy link

shepelevstas commented Sep 9, 2018

@eddiejaoude No. Transaction makes the whole migration to fail and rollback if any of modifications fail.

@eddiejaoude
Copy link
Contributor

@shepelevstas where did you read that? Its not in the docs. In fact, there is an open issue for that feature, so I dont think it exists yet! But its your product, if you would rather save a few seconds when running the migrations at the cost of it being more fragile, then thats your decision 🤓 . Good luck to you 😉 #1364

@shepelevstas
Copy link

@eddiejaoude it's one of the main tabs in the docs: http://docs.sequelizejs.com/manual/tutorial/transactions.html
I tried it and it works as described.

@eddiejaoude
Copy link
Contributor

eddiejaoude commented Sep 11, 2018

I didn't see you were using .transaction all on the same line, on the phone it was small. Fair enough, however, I still dont know what the benefit is, the performance gain will be minimal, and if this migration fails, you still won't know which one it is. Plus both are running async and changing the same table, depending on the db engine being used, one will probably block the other with a table lock. I think you are trying to solve a problem that doesn't exist and creating potentially a new issue.

I would like to understand the problem you are trying to solve?

@scottbeacher
Copy link

@shepelevstas Alter table (which is what removeColumn and renameColumn will use) causes implicit commits, transactions don't affect those. The change is committed immediately so there is no way to rollback if a later step in the transaction fails. Has already been mentioned several times in this ticket.

https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html
https://dev.mysql.com/doc/refman/5.7/en/cannot-roll-back.html

@shepelevstas
Copy link

shepelevstas commented Sep 13, 2018

I tested it in SQLite, and it properly rolls back the whole transaction if one of the later alterations fails. I checked the SQL query, it creates a backup table during transaction. But, yes, it is better to make alterations in a chain, as some of them interfear with each other. Still transaction helps a lot.

@eddiejaoude
Copy link
Contributor

I am still struggling to see the problem you are trying to solve by running multiple migrations in a single migration file?

@sathiz1993
Copy link

Hello .... I encountered the same scenario where I need to add multiple columns in one migration and this works for me.

'use strict';

module.exports = {
    up: (queryInterface, Sequelize) => {
        return Promise.all([
            queryInterface.addColumn('table_1','column_5', {
                    type: Sequelize.ENUM,
                    defaultValue: 'unverified',
                    values: [
                        'unverified', 'updated'
                    ]
            }),
            queryInterface.addColumn('table_1','column_6', {
                    type: Sequelize.BOOLEAN,
                    defaultValue: 0,
            }),
            queryInterface.addColumn('table_1', 'column_7', {
                    type: Sequelize.BOOLEAN,
                    defaultValue: 0,
            })
        ])
    },
    down: (queryInterface, Sequelize) => {
        return Promise.all([
            queryInterface.removeColumn('table_1', 'column_5'),
            queryInterface.removeColumn('table_2', 'column_6'),
            queryInterface.removeColumn('table_3', 'column_7')
        ])
    }
};

@tolerious
Copy link

Hello .... I encountered the same scenario where I need to add multiple columns in one migration and this works for me.

'use strict';

module.exports = {
    up: (queryInterface, Sequelize) => {
        return Promise.all([
            queryInterface.addColumn('table_1','column_5', {
                    type: Sequelize.ENUM,
                    defaultValue: 'unverified',
                    values: [
                        'unverified', 'updated'
                    ]
            }),
            queryInterface.addColumn('table_1','column_6', {
                    type: Sequelize.BOOLEAN,
                    defaultValue: 0,
            }),
            queryInterface.addColumn('table_1', 'column_7', {
                    type: Sequelize.BOOLEAN,
                    defaultValue: 0,
            })
        ])
    },
    down: (queryInterface, Sequelize) => {
        return Promise.all([
            queryInterface.removeColumn('table_1', 'column_5'),
            queryInterface.removeColumn('table_2', 'column_6'),
            queryInterface.removeColumn('table_3', 'column_7')
        ])
    }
};

this works for me.

codetriage-readme-bot pushed a commit to codetriage-readme-bot/cli that referenced this issue Jun 5, 2019
Create exercism directory on configuration
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests