Feature request
Please provide a supported way for an individual migration to opt out of Phinx's automatic transaction wrapper.
Compelling PostgreSQL use case
PostgreSQL forbids CREATE INDEX CONCURRENTLY and DROP INDEX CONCURRENTLY inside a transaction block. Concurrent index creation is essential when indexing a large live table because ordinary CREATE INDEX blocks writes for the duration of the build.
In a concrete production case, the table contains about 204 million rows and an 11 GiB heap. Building a small BRIN index concurrently took 5 minutes 13 seconds. An ordinary index build would prevent ingestion from writing to that table for the entire operation.
Phinx 0.16.12, and the current 0.x branch, unconditionally call beginTransaction() for every migration whenever the adapter reports transaction support:
https://github.com/cakephp/phinx/blob/0.x/src/Phinx/Migration/Manager/Environment.php#L89-L92
A straightforward migration therefore fails:
public function up(): void
{
$this->execute(
'CREATE INDEX CONCURRENTLY events_date_brin_idx '
. 'ON events USING brin (date)'
);
}
ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block
Current workaround
The migration must manually interfere with Phinx's transaction lifecycle:
$this->execute('COMMIT');
$this->execute('CREATE INDEX CONCURRENTLY ...');
$this->execute('BEGIN');
This couples application migrations to Environment::executeMigration() internals, complicates failure handling, and risks breaking migration bookkeeping, dry runs, or future Phinx changes. A connection failure can also leave the workaround unable to restore the transaction Phinx expects.
Running the index separately from Phinx is also undesirable: deployment then has two schema-change systems whose state can diverge.
Proposed API
A per-migration method with a backwards-compatible default would be sufficient:
public function isTransactional(): bool
{
return false;
}
Environment::executeMigration() would wrap the migration only when both conditions hold:
$transactional = $adapter->hasTransactions() && $migration->isTransactional();
A marker interface, attribute, or protected property would also solve the problem. The important requirement is that the decision belongs to the migration rather than requiring the migration to issue COMMIT and BEGIN itself.
Expected behavior
- Existing migrations remain transactional by default.
- Opting out applies consistently to
up(), down(), and change().
- Adapters without transaction support are unaffected.
- The migration is recorded only after its body completes successfully.
- Documentation warns that non-transactional DDL can leave partial state, such as PostgreSQL's invalid index after an interrupted concurrent build, and that migrations must be retry-safe.
Prior art
- Doctrine Migrations exposes this through
isTransactional().
- Rails supports
disable_ddl_transaction! for concurrent indexes.
Feature request
Please provide a supported way for an individual migration to opt out of Phinx's automatic transaction wrapper.
Compelling PostgreSQL use case
PostgreSQL forbids
CREATE INDEX CONCURRENTLYandDROP INDEX CONCURRENTLYinside a transaction block. Concurrent index creation is essential when indexing a large live table because ordinaryCREATE INDEXblocks writes for the duration of the build.In a concrete production case, the table contains about 204 million rows and an 11 GiB heap. Building a small BRIN index concurrently took 5 minutes 13 seconds. An ordinary index build would prevent ingestion from writing to that table for the entire operation.
Phinx 0.16.12, and the current
0.xbranch, unconditionally callbeginTransaction()for every migration whenever the adapter reports transaction support:https://github.com/cakephp/phinx/blob/0.x/src/Phinx/Migration/Manager/Environment.php#L89-L92
A straightforward migration therefore fails:
Current workaround
The migration must manually interfere with Phinx's transaction lifecycle:
This couples application migrations to
Environment::executeMigration()internals, complicates failure handling, and risks breaking migration bookkeeping, dry runs, or future Phinx changes. A connection failure can also leave the workaround unable to restore the transaction Phinx expects.Running the index separately from Phinx is also undesirable: deployment then has two schema-change systems whose state can diverge.
Proposed API
A per-migration method with a backwards-compatible default would be sufficient:
Environment::executeMigration()would wrap the migration only when both conditions hold:A marker interface, attribute, or protected property would also solve the problem. The important requirement is that the decision belongs to the migration rather than requiring the migration to issue
COMMITandBEGINitself.Expected behavior
up(),down(), andchange().Prior art
isTransactional().disable_ddl_transaction!for concurrent indexes.