diff --git a/src/Db/Adapter/AbstractAdapter.php b/src/Db/Adapter/AbstractAdapter.php index 4917a237..92a1e022 100644 --- a/src/Db/Adapter/AbstractAdapter.php +++ b/src/Db/Adapter/AbstractAdapter.php @@ -19,7 +19,6 @@ use Cake\Database\Schema\SchemaDialect; use Cake\I18n\Date; use Cake\I18n\DateTime; -use Exception; use InvalidArgumentException; use Migrations\Config\Config; use Migrations\Db\Action\AddColumn; @@ -37,7 +36,6 @@ use Migrations\Db\AlterInstructions; use Migrations\Db\InsertMode; use Migrations\Db\Literal; -use Migrations\Db\Table; use Migrations\Db\Table\CheckConstraint; use Migrations\Db\Table\Column; use Migrations\Db\Table\ForeignKey; @@ -129,21 +127,7 @@ public function setConnection(Connection $connection): AdapterInterface if (!$this->hasTable($this->getSchemaTableName())) { $this->createSchemaTable(); } else { - $table = new Table($this->getSchemaTableName(), [], $this); - if (!$table->hasColumn('migration_name')) { - $table - ->addColumn( - 'migration_name', - 'string', - ['limit' => 100, 'after' => 'version', 'default' => null, 'null' => true], - ) - ->save(); - } - if (!$table->hasColumn('breakpoint')) { - $table - ->addColumn('breakpoint', 'boolean', ['default' => false, 'null' => false]) - ->save(); - } + $this->migrationsTable()->upgradeTable(); } return $this; @@ -357,26 +341,7 @@ public function hasColumn(string $tableName, string $columnName): bool */ public function createSchemaTable(): void { - try { - $options = [ - 'id' => false, - 'primary_key' => 'version', - ]; - - $table = new Table($this->getSchemaTableName(), $options, $this); - $table->addColumn('version', 'biginteger', ['null' => false]) - ->addColumn('migration_name', 'string', ['limit' => 100, 'default' => null, 'null' => true]) - ->addColumn('start_time', 'timestamp', ['default' => null, 'null' => true]) - ->addColumn('end_time', 'timestamp', ['default' => null, 'null' => true]) - ->addColumn('breakpoint', 'boolean', ['default' => false, 'null' => false]) - ->save(); - } catch (Exception $exception) { - throw new InvalidArgumentException( - 'There was a problem creating the schema table: ' . $exception->getMessage(), - (int)$exception->getCode(), - $exception, - ); - } + $this->migrationsTable()->createTable(); } /** @@ -815,6 +780,18 @@ public function getVersions(): array return array_keys($rows); } + /** + * Get the migrations table storage implementation. + * + * @return \Migrations\Db\Adapter\MigrationsTableStorage + * @internal + */ + protected function migrationsTable(): MigrationsTableStorage + { + // TODO Use configure/auto-detect which implmentation to use. + return new MigrationsTableStorage($this, $this->getSchemaTableName()); + } + /** * {@inheritDoc} * @@ -832,10 +809,7 @@ public function getVersionLog(): array default: throw new RuntimeException('Invalid version_order configuration option'); } - $query = $this->getSelectBuilder(); - $query->select('*') - ->from($this->getSchemaTableName()) - ->orderBy($orderBy); + $query = $this->migrationsTable()->getVersions($orderBy); // This will throw an exception if doing a --dry-run without any migrations as phinxlog // does not exist, so in that case, we can just expect to trivially return empty set @@ -862,24 +836,10 @@ public function getVersionLog(): array public function migrated(MigrationInterface $migration, string $direction, string $startTime, string $endTime): AdapterInterface { if (strcasecmp($direction, MigrationInterface::UP) === 0) { - $query = $this->getInsertBuilder(); - $query->insert(['version', 'migration_name', 'start_time', 'end_time', 'breakpoint']) - ->into($this->getSchemaTableName()) - ->values([ - 'version' => (string)$migration->getVersion(), - 'migration_name' => substr($migration->getName(), 0, 100), - 'start_time' => $startTime, - 'end_time' => $endTime, - 'breakpoint' => 0, - ]); - $this->executeQuery($query); + $this->migrationsTable()->recordUp($migration, $startTime, $endTime); } else { // down - $query = $this->getDeleteBuilder(); - $query->delete() - ->from($this->getSchemaTableName()) - ->where(['version' => $migration->getVersion()]); - $this->executeQuery($query); + $this->migrationsTable()->recordDown($migration); } return $this; @@ -890,19 +850,7 @@ public function migrated(MigrationInterface $migration, string $direction, strin */ public function toggleBreakpoint(MigrationInterface $migration): AdapterInterface { - $params = [ - $migration->getVersion(), - ]; - $this->query( - sprintf( - 'UPDATE %1$s SET %2$s = CASE %2$s WHEN true THEN false ELSE true END, %4$s = %4$s WHERE %3$s = ?;', - $this->quoteTableName($this->getSchemaTableName()), - $this->quoteColumnName('breakpoint'), - $this->quoteColumnName('version'), - $this->quoteColumnName('start_time'), - ), - $params, - ); + $this->migrationsTable()->toggleBreakpoint($migration); return $this; } @@ -912,17 +860,7 @@ public function toggleBreakpoint(MigrationInterface $migration): AdapterInterfac */ public function resetAllBreakpoints(): int { - $query = $this->getUpdateBuilder(); - $query->update($this->getSchemaTableName()) - ->set([ - 'breakpoint' => 0, - 'start_time' => $query->identifier('start_time'), - ]) - ->where([ - 'breakpoint !=' => 0, - ]); - - return $this->executeQuery($query); + return $this->migrationsTable()->resetAllBreakpoints(); } /** @@ -954,16 +892,7 @@ public function unsetBreakpoint(MigrationInterface $migration): AdapterInterface */ protected function markBreakpoint(MigrationInterface $migration, bool $state): AdapterInterface { - $query = $this->getUpdateBuilder(); - $query->update($this->getSchemaTableName()) - ->set([ - 'breakpoint' => (int)$state, - 'start_time' => $query->identifier('start_time'), - ]) - ->where([ - 'version' => $migration->getVersion(), - ]); - $this->executeQuery($query); + $this->migrationsTable()->markBreakpoint($migration, $state); return $this; } diff --git a/src/Db/Adapter/MigrationsTableStorage.php b/src/Db/Adapter/MigrationsTableStorage.php new file mode 100644 index 00000000..9c3a1b20 --- /dev/null +++ b/src/Db/Adapter/MigrationsTableStorage.php @@ -0,0 +1,225 @@ +schemaTableName; + } + + /** + * Gets all the migration versions. + * + * @param array $orderBy The order by clause. + * @return \Cake\Database\Query\SelectQuery + */ + public function getVersions(array $orderBy): SelectQuery + { + $query = $this->adapter->getSelectBuilder(); + $query->select('*') + ->from($this->getSchemaTableName()) + ->orderBy($orderBy); + + return $query; + } + + /** + * Records that a migration was run in the database. + * + * @param \Migrations\MigrationInterface $migration Migration + * @param string $startTime Start time + * @param string $endTime End time + * @return void + */ + public function recordUp(MigrationInterface $migration, string $startTime, string $endTime): void + { + $query = $this->adapter->getInsertBuilder(); + $query->insert(['version', 'migration_name', 'start_time', 'end_time', 'breakpoint']) + ->into($this->getSchemaTableName()) + ->values([ + 'version' => (string)$migration->getVersion(), + 'migration_name' => substr($migration->getName(), 0, 100), + 'start_time' => $startTime, + 'end_time' => $endTime, + 'breakpoint' => 0, + ]); + $this->adapter->executeQuery($query); + } + + /** + * Removes the record of a migration having been run. + * + * @param \Migrations\MigrationInterface $migration Migration + * @return void + */ + public function recordDown(MigrationInterface $migration): void + { + $query = $this->adapter->getDeleteBuilder(); + $query->delete() + ->from($this->getSchemaTableName()) + ->where(['version' => (string)$migration->getVersion()]); + $this->adapter->executeQuery($query); + } + + /** + * Toggles the breakpoint state of a migration. + * + * @param \Migrations\MigrationInterface $migration Migration + * @return void + */ + public function toggleBreakpoint(MigrationInterface $migration): void + { + $params = [ + $migration->getVersion(), + ]; + $this->adapter->query( + sprintf( + 'UPDATE %1$s SET %2$s = CASE %2$s WHEN true THEN false ELSE true END, %4$s = %4$s WHERE %3$s = ?;', + $this->adapter->quoteTableName($this->getSchemaTableName()), + $this->adapter->quoteColumnName('breakpoint'), + $this->adapter->quoteColumnName('version'), + $this->adapter->quoteColumnName('start_time'), + ), + $params, + ); + } + + /** + * Resets all breakpoints. + * + * @return int The number of affected rows. + */ + public function resetAllBreakpoints(): int + { + $query = $this->adapter->getUpdateBuilder(); + $query->update($this->getSchemaTableName()) + ->set([ + 'breakpoint' => 0, + 'start_time' => $query->identifier('start_time'), + ]) + ->where([ + 'breakpoint !=' => 0, + ]); + + return $this->adapter->executeQuery($query); + } + + /** + * Marks a migration as a breakpoint or not depending on $state. + * + * @param \Migrations\MigrationInterface $migration Migration + * @param bool $state The breakpoint state to set. + * @return void + */ + public function markBreakpoint(MigrationInterface $migration, bool $state): void + { + $query = $this->adapter->getUpdateBuilder(); + $query->update($this->getSchemaTableName()) + ->set([ + 'breakpoint' => (int)$state, + 'start_time' => $query->identifier('start_time'), + ]) + ->where([ + 'version' => $migration->getVersion(), + ]); + $this->adapter->executeQuery($query); + } + + /** + * Creates the migration storage table + * + * @return void + * @throws \InvalidArgumentException When there is a problem creating the table. + */ + public function createTable(): void + { + try { + $options = [ + 'id' => false, + 'primary_key' => 'version', + ]; + + $table = new Table($this->getSchemaTableName(), $options, $this->adapter); + $table->addColumn('version', 'biginteger', ['null' => false]) + ->addColumn('migration_name', 'string', ['limit' => 100, 'default' => null, 'null' => true]) + ->addColumn('start_time', 'timestamp', ['default' => null, 'null' => true]) + ->addColumn('end_time', 'timestamp', ['default' => null, 'null' => true]) + ->addColumn('breakpoint', 'boolean', ['default' => false, 'null' => false]) + ->save(); + } catch (Exception $exception) { + throw new InvalidArgumentException( + 'There was a problem creating the schema table: ' . $exception->getMessage(), + (int)$exception->getCode(), + $exception, + ); + } + } + + /** + * Upgrades the migration storage table + * + * @return void + */ + public function upgradeTable(): void + { + $table = new Table($this->getSchemaTableName(), [], $this->adapter); + if (!$table->hasColumn('migration_name')) { + $table + ->addColumn( + 'migration_name', + 'string', + ['limit' => 100, 'after' => 'version', 'default' => null, 'null' => true], + ) + ->save(); + } + if (!$table->hasColumn('breakpoint')) { + $table + ->addColumn('breakpoint', 'boolean', ['default' => false, 'null' => false]) + ->save(); + } + } +} diff --git a/src/Db/Adapter/PostgresAdapter.php b/src/Db/Adapter/PostgresAdapter.php index 9f792f1e..feac84b8 100644 --- a/src/Db/Adapter/PostgresAdapter.php +++ b/src/Db/Adapter/PostgresAdapter.php @@ -945,7 +945,6 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $ta */ public function createSchemaTable(): void { - // Create the public/custom schema if it doesn't already exist if ($this->hasSchema($this->getGlobalSchemaName()) === false) { $this->createSchema($this->getGlobalSchemaName()); }