Migration Management and schema version control in PostgreSQL

Version control of any code is essential for every organization. All organizations use their own preferred tools like git, svn, perforce etc. As I was working on a new requirement, which is about porting the current CI/CD pipeline to PostgreSQL, I needed to look for a tool which will take care of the schema changes. In this use case, the customer is maintaining all the schema in a separate directory, and creating the objects/schema during the application bootstrap process. This is the behavior most of the applications do follow, but having a dedicated schema change management system is something unique. In this article, we shall discuss about managing version control schema in PostgreSQL using node-pg-migrate.

There are a good number of open source tools for the schema change management requirements. As a NodeJS developer, I was looking for a tool written Migration Management and schema version control in PostgreSQLin NodeJS for schema version control in PostgreSQL. Fortunately, we found a tool called node-pg-migrate which is written in NodeJS. This tool also follows it's own style of defining the SQL statements. The SQL statements can be managed by a set of predefined javascript functions. By using these functions, we can easily perform the create/alter/drop operations for the SQL objects.

Okay, now let us understand how to perform Schema version control or migration management for PostgreSQL in detail.

Installing node-pg-migrate

We need the latest NodeJS, npm to be installed on the server to install node-pg-migrate. Following commands can be used to perform the installation.

# Installing latest NodeJS
curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash -
sudo yum -y install nodejs

# Installing node-pg-migrate with dependent pg library 
sudo npm install -g node-pg-migrate pg
Creating the First Release

To understand this tool better, let us start with a first release which has only one table in it. You can use the following commands to create the first release.

# Creating a directory for all the SQL release files
$ mkdir releases
$ cd releases/

$ node-pg-migrate create first_release
Created migration -- /home/jagadeesh/releases/migrations/1634906885247_first-release.js

As you see in the above output, this tool actually created a file 1634906885247_first-release.js for this release. We have to edit this file with the list of DDL changes that we are planning to deploy with this release. Now, let us see the contents of this file.

$ cat migrations/1634906885247_first-release.js
/* eslint-disable camelcase */

exports.shorthands = undefined;
exports.up = pgm => {};
exports.down = pgm => {};

In this file, we need to focus on 2 exported objects : up, down that can be used for the following use cases.

up      -> List of SQL statements which have to deploy
down    -> List of SQL statements which will revert this deployment

Now, let us write the first deployment as to deploy only one table and also write the rollback behavior to drop that table. This means, the up behavior is CREATE and the down behavior is DROP.

Find the following up, down object definitions, which we have updated to the file : /home/jagadeesh/releases/migrations/1634906885247_first-release.js

exports.up = (pgm) => {
  pgm.createTable("test", {
    id: "id",
    name: { type: "varchar(1)", notNull: true },
    createdAt: {
      type: "timestamp",
      notNull: true,
      default: pgm.func("current_timestamp"),
    },
  });
};

exports.down = (pgm) => {
  pgm.dropTable("test");
};

In the above file content, we have not used any standard SQL syntax, rather we used node-pg-migrate's functions to create/drop the objects. Also, the JSON style of declaring the table definition is really cool. In the above example, we defined the PostgreSQL table as JSON object with multiple columns : id, name, createdAt and defined the column's specific properties like data type, default value and not null.

And in the down object, we used node-pg-migrate specific function dropTable, dropping the table test to enable the capability of a rollback.

Performing the First Deployment

To perform the deployment and use up command for the node-pg-migrate as below.

$ export DATABASE_URL=postgres:postgres@localhost:5432/postgres
$ node-pg-migrate up
> Migrating files:
> - 1634906885247_first-release
### MIGRATION 1634906885247_first-release (UP) ###
CREATE TABLE "test" (
  "id" serial PRIMARY KEY,
  "name" varchar(1) NOT NULL,
  "createdAt" timestamp DEFAULT current_timestamp NOT NULL
);
INSERT INTO "public"."pgmigrations" (name, run_on) VALUES ('1634906885247_first-release', NOW());

Migrations complete!

From the above results, as you see that the JSON is deserialized as SQL statement, and created the table at the mentioned database location.

Creating the Second Release

As part of the second release, we wanted to add another column to the test table. Following are the steps we followed for performing the same.

# Creating second release
$ node-pg-migrate create second_release
Created migration -- /home/jagadeesh/releases/migrations/1634916250633_second-release.js

# Adding second release changes
$ cat migrations/1634916250633_second-release.js
/* eslint-disable camelcase */

exports.shorthands = undefined;

exports.up = pgm => {
pgm.addColumns('test', {
    another_column: { type: 'text', notNull: true },
 })
};

exports.down = pgm => {
        pgm.dropColumns('test', ['another_column']);
};
Performing the Second Deployment

Now, let us execute the deployment and apply changes to the test table by using the up command.

$ export DATABASE_URL=postgres:postgres@localhost:5432/postgres
$ node-pg-migrate up
> Migrating files:
> - 1634916250633_second-release
### MIGRATION 1634916250633_second-release (UP) ###
ALTER TABLE "test"
  ADD "another_column" text NOT NULL;
INSERT INTO "public"."pgmigrations" (name, run_on) VALUES ('1634916250633_second-release', NOW());

Migrations complete!
Performing a Rollback of the Second Deployment

Let us assume that we decided to rollback the second release. In our example, the rollback of the second release involves reverting the test table changes. By using the node-pg-migrate down command, we can revert the last release changes as seen in the following block.

$ export DATABASE_URL=postgres:postgres@localhost:5432/postgres
$ node-pg-migrate down
> Migrating files:
> - 1634916250633_second-release
### MIGRATION 1634916250633_second-release (DOWN) ###
ALTER TABLE "test"
  DROP "another_column";
DELETE FROM "public"."pgmigrations" WHERE name='1634916250633_second-release';

Migrations complete!

From the above results, we could noticed that the column another_column added as part of the second release has been dropped from the database.

Performing a Rollback of the First Deployment

Assume that you wished to revert the changes done in the first release too. To perform this action, we simple do node-pg-migrate down which will revert the first deployment changes as well.

$ export DATABASE_URL=postgres:postgres@localhost:5432/postgres
$ node-pg-migrate down
> Migrating files:
> - 1634908948307_first-release
### MIGRATION 1634908948307_first-release (DOWN) ###
DROP TABLE "test";
DELETE FROM "public"."pgmigrations" WHERE name='1634908948307_first-release';

Migrations complete!

Whenever we do any operation up and down, then node-pg-migrate will be tracking the release points. Whenever the consecutive up or down happen, then it will follow from the last release point and will execute the release scripts accordingly.

Async Releases

As you have seen so far, the node-pg-migrate is a NodeJS tool, where up and down is calling some set of pre-defined javascript functions. We can also leverage this behavior by calling Promise functions in the up and down sections of the release file.

This async release behavior gives additional flexibility by allowing us to perform releases of all the PostgreSQL related deployments in parallel with other service related changes. For more details about the usage of async releases, please refer to the official documentation of the node-pg-migrate.

Conclusion

As part of the CI/CD requirement, we explored multiple SQL code change management tools. I personally found that node-pg-migrate is developer friendly and a good asset for the CI/CD pipeline. Thanks to it's extensibility, where we can extend it's behavior by writing our own javascript functions. This tool provides many pre-defined PostgreSQL related functions enabling smoother deployments. As of now, I found this tool very reliable and it is doing it's job as expected.

Migrating to PostgreSQL ?

If you are looking to migrate to PostgreSQL or looking for support in managing PostgreSQL databases, please contact us or fill the following form.

Leave a Comment

Your email address will not be published.

Scroll to Top