NAME
MojoX::Pg::Migrations::Env - Mojo::Pg migrations that replace environment placeholders in their sql, sponsored by Fibia
SYNOPSIS
use Mojo::Pg;
use MojoX::Pg::Migrations::Env;
my $pg = Mojo::Pg->new('postgresql://app@/app');
$pg->migrations(MojoX::Pg::Migrations::Env->new(pg => $pg, name => 'app'));
$pg->migrations->from_file('db/migrations.sql')->migrate;
# Or look first
for my $placeholder (@{$pg->migrations->placeholders(3)}) {
my ($source, $value) = $pg->migrations->resolve($placeholder->{name}, $placeholder->{fallback});
say "$placeholder->{name} ($source)";
}
With db/migrations.sql containing
-- 3 up
grant select on invoices to ${APP_READONLY_ROLE:-readonly};
-- 3 down
revoke select on invoices from ${APP_READONLY_ROLE:-readonly};
DESCRIPTION
MojoX::Pg::Migrations::Env is a drop-in replacement for Mojo::Pg::Migrations. It is a subclass that loads, selects and applies migrations exactly like its parent, with one difference: placeholders in the migration sql are replaced with values from the environment when the sql is produced. That lets migrations refer to things that differ between environments, for example the name of a role to grant to.
Migrations without placeholders produce the same sql as they do with Mojo::Pg::Migrations, so an existing project can switch class without changing its migrations.
It requires Mojo::Pg 4.04 or newer, the first release where "migrate" in Mojo::Pg::Migrations gets its sql from "sql_for" in Mojo::Pg::Migrations, Mojolicious 7.51 or newer and Perl 5.20 or newer for subroutine signatures.
PLACEHOLDERS
grant select on invoices to ${APP_READONLY_ROLE:-readonly};
- ${NAME}
-
Replaced with the value of
NAMEin "env". A variable that is unset or empty is missing, and producing the sql fails. - ${NAME:-fallback}
-
Replaced with the value of
NAMEin "env", or with the fallback when the variable is unset or empty. The fallback is everything up to the closing brace and can be empty, as in${NAME:-}. - $${NAME}
-
Left as the literal
${NAME}, and$${NAME:-fallback}as the literal${NAME:-fallback}. An escaped placeholder is never looked up and never listed by "placeholders".
A name starts with a letter or an underscore, followed by letters, digits and underscores. Nothing else is touched, so numbered parameters like $1 and dollar quoted function bodies ($$ and $tag$) are safe to use. Take care where a dollar quote is directly followed by a placeholder, because $$${NAME} reads as a dollar sign followed by the escaped form. Put whitespace between them.
Placeholders are replaced everywhere in the sql of a migration, also inside comments and string literals, in both up and down sql. Values are inserted as they are, without quoting.
Values are looked up when the sql is produced, and all sql between two versions is produced before any of it runs. A missing variable therefore aborts "migrate" in Mojo::Pg::Migrations with nothing applied, and only the migrations between the active and the target version need their variables set.
ATTRIBUTES
MojoX::Pg::Migrations::Env inherits all attributes from Mojo::Pg::Migrations and implements the following new ones.
env
my $env = $migrations->env;
$migrations = $migrations->env({APP_READONLY_ROLE => 'reporting'});
Variables that placeholders are looked up in, defaults to %ENV.
METHODS
MojoX::Pg::Migrations::Env inherits all methods from Mojo::Pg::Migrations and implements the following new ones.
label_for
my $label = $migrations->label_for(3);
How a migration is named in error messages, defaults to Migration 3. It is only called when an error is raised. A subclass that knows more about its migrations than their version can override it, and the label becomes the subject of the error.
package My::Migrations;
use Mojo::Base 'MojoX::Pg::Migrations::Env', -signatures;
sub label_for ($self, $version) { "Migration $version (" . $self->title_of($version) . ')' }
# Migration 3 (add readonly grants) needs environment variable APP_READONLY_ROLE, which is not ...
placeholders
my $placeholders = $migrations->placeholders(3);
Placeholders used by a version's up and down sql, in order of first appearance, as hashes with name and fallback, where the fallback is undef for a required variable. A name used more than once is listed once, with the fallback of its first appearance. Escaped placeholders are not listed.
resolve
my ($source, $value) = $migrations->resolve('APP_READONLY_ROLE');
my ($source, $value) = $migrations->resolve('APP_READONLY_ROLE', 'readonly');
Where the value for a placeholder comes from, and the value, without producing any sql. Never dies.
- env
-
"env" has a value for the name that is not empty, which is the value returned.
- fallback
-
"env" has no value for the name, or an empty one, and a fallback was given, which is the value returned.
- missing
-
"env" has no value for the name, or an empty one, and there is no fallback. The value is
undef.
sql_for
my $sql = $migrations->sql_for(5, 10);
Same as "sql_for" in Mojo::Pg::Migrations, with the placeholders of every migration on the way replaced. Dies when a variable is missing, naming the migration that needs it with "label_for".
Migration 3 needs environment variable APP_READONLY_ROLE, which is not set and has no fallback
SPONSORS
This module is sponsored by Fibia.
AUTHOR
David Jack Wange Olrik, djo@cpan.org.
COPYRIGHT AND LICENSE
Copyright (C) 2026, David Jack Wange Olrik.
This program is free software, you can redistribute it and/or modify it under the terms of the MIT license. See the LICENSE file for the full text.
SEE ALSO
Mojo::Pg, Mojo::Pg::Migrations, Mojolicious::Guides, https://mojolicious.org.