Recipe: Verifying PostgreSQL schema migrations with plain SQL

Sqitch verify made simple

Sergii Tkachenko
JW Player Engineering
3 min readJan 24, 2019

--

Recipes are the new series of short-form posts to JW Player engineering blog. Their objective is promoting healthy coding hygiene and sharing hard-won practical knowledge.

You can treat them just as cooking recipes, a step-by-step set of instructions. In recipes, we’ll capture day in and day out of our engineers: best security practices, ready-made solutions for specific problems, tips, tricks, and opinions. Recipes may not get deep into the weeds or provide comprehensive details, but they will always be practical and useful.

Problem

In JW Player we use Sqitch to run database migrations. We love Sqitch for its pure plain SQL approach which makes it suitable for use with any language or framework. Database schema changes (called deploy step) and their counterpart rollbacks (called revert step) are easily written in plain SQL, as they’re meant to be.

Having said that, there’s one more step called verify and writing it in plain SQL isn’t as straightforward. This step is used to ensure instructions in deploy step did precisely what you expect them to do.

Sqitch PostgreSQL tutorial provides a couple of examples of verify step.

In one example, they ensure schema flipr exists. The offered approach is rather implicit: it uses division by zero error to check whether the query finds matching rows.

SELECT 1/COUNT(*) FROM information_schema.schemata WHERE schema_name = 'flipr';

No matched records results inCOUNT(*) = 0, which makes PostgreSQL run1/0 and fail with ERROR: division by zero.

Another example is dangerously misleading:

SELECT pg_catalog.has_schema_privilege('flipr', 'usage');

At first glance, this step ensures the current user has usage privilege on schema flipr. SELECT true should pass the verification and SELECT false should fail, right? Well, that’s not what’s happening.

This verify step will not fail on pg_catalog.has_schema_privilege() returning false! It will pass even when the user has no usage rights.

What this step actually does is the same as the first example: verifying that schema flipr exists. If it’s not, has_schema_privilege() throws an error. It has nothing to do with schema privileges.

Solution

PostgreSQL has built-in procedural language PL/pgSQL (yep, mouthful). Postgres v9.5+ brings anonymous functions and ASSERT / RAISE statements into PL/pgSQL. This makes it perfect for test assertions.

You can verify that the matching record is found:

DO $$
BEGIN
ASSERT (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'flipr');
END $$;

This works perfectly with any built-in function returning a boolean:

DO $$
BEGIN
ASSERT (SELECT has_schema_privilege('flipr', 'usage'));
END $$;

This time it does exactly what you’d expect: ensure the current user has usage rights on flipr schema.

You can even use variables to perform more complex checks.

DO $$
DECLARE
result varchar;
BEGIN
result := (SELECT name FROM flipr.pipelines WHERE id = 1);
ASSERT result = 'Example';
END $$;

This example ensures the record with id=1 in pipelines table has name field equal Example.

Remarks

  • The integrity of your PostgreSQL schema migrations can easily be verified using built-in PL/pgSQL procedural language. You can learn about its features in PostgreSQL Documentation.
  • Open source is ingrained in JW Player’s DNA. Open source contributions are highly encouraged. We’ve submitted our PL/pgSQL examples to Sqitch PostgreSQL tutorial: https://github.com/sqitchers/sqitch/pull/425

--

--

Sergii Tkachenko
JW Player Engineering

Senior Software Engineer at @jwplayer. Proprietary by day, MIT by night.