Skip to content

Apostrophes in migration filenames break history recording and can leave changes unrecorded #1753

Description

@Creatixpy

Describe the bug

The CLI accepts migration names containing apostrophes, but applying the generated migration fails when its name is inserted into the migration history table.

With a transactional migration, this prevents application. With a migration using pgm.noTransaction(), schema changes can succeed before the history insert fails, leaving the database changed without a corresponding migration record.

Steps to reproduce

In a new consumer project, install:

npm install node-pg-migrate@9.0.0 pg@8.23.0

Set DATABASE_URL explicitly to an empty disposable PostgreSQL database, then run:

npx node-pg-migrate create "user's-table" \
  -j cjs --migration-filename-format index

npx node-pg-migrate up

Scaffolding succeeds and creates 0001_user's-table.cjs. Applying even the empty generated migration exits with status 1 and PostgreSQL error 42601.

The generated history statement contains:

INSERT INTO "public"."pgmigrations" (name, run_on)
VALUES ('0001_user's-table', NOW());

The apostrophe in the filename terminates the SQL string literal.

I also verified the following nontransactional variant on both versions, using a migration named 001_user's.cjs in a separate migration directory against a fresh database:

exports.up = (pgm) => {
  pgm.noTransaction();
  pgm.createTable('unrecorded_table', { id: 'integer' });
};

Apply it with the same npx node-pg-migrate up command. After the failed run:

SELECT to_regclass('public.unrecorded_table');
-- unrecorded_table

SELECT count(*) FROM public.pgmigrations;
-- 0

The table exists, but the migration is still considered pending.

Expected behavior

Migration names accepted by scaffolding should be recorded as literal values, including apostrophes.

If a filename restriction is intended, validation should reject the name before executing migration SQL.

Impact and technical context

The failure occurs during bookkeeping, after migration statements have already executed. In the nontransactional case, retrying attempts to apply changes that are already present.

Migration._getMarkAsRun() interpolates the migration name directly into SQL string literals. The same helper constructs the deletion statement used during rollback; the executed reproductions above cover normal and nontransactional up migrations.

Useful regression coverage would include CLI-generated names containing apostrophes, up/down bookkeeping, fake migrations, and a nontransactional migration whose history record is successfully written.

System info

  • node-pg-migrate: npm 9.0.0 and upstream commit ad9421519df827b1910e3a47611d6589c45246c4.
  • The upstream build's manifest says 10.0.0-alpha.2; this was a source build, not the published alpha package.
  • Node.js 24.21.0, pg 8.23.0, Ubuntu 24.04 ARM64.
  • Basic failure reproduced on PostgreSQL 16.15 with both versions and PostgreSQL 18.6 with current main.
  • The unrecorded-table variant was also verified on PostgreSQL 16.15 with both versions and PostgreSQL 18.6 with current main.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    c: bugSomething isn't working

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions