Skip to content

Commit 1dafefa

Browse files
authored
Merge pull request #142 from WebFiori/dev
feat(schema): add skip/baseline support to SchemaRunner
2 parents e93866d + 98ff685 commit 1dafefa

4 files changed

Lines changed: 391 additions & 1 deletion

File tree

WebFiori/Database/Schema/SchemaChangeRepository.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,29 @@ public function recordChange(DatabaseChange $change): int {
197197
'type' => $change->getType(),
198198
'applied-on' => date('Y-m-d H:i:s'),
199199
'db-name' => $this->getDatabase()->getConnectionInfo()->getDBName(),
200-
'batch' => $change->getBatch()
200+
'batch' => $change->getBatch(),
201+
'status' => 'applied'
202+
])->execute();
203+
204+
return $this->getLastInsertId();
205+
}
206+
207+
/**
208+
* Record a change as skipped (baselined).
209+
*
210+
* @param DatabaseChange $change The change to record
211+
* @param int $batch The batch number to assign
212+
* @return int The ID of the inserted record
213+
*/
214+
public function recordSkipped(DatabaseChange $change, int $batch): int {
215+
$this->getDatabase()->table($this->getTableName())
216+
->insert([
217+
'change_name' => $change->getName(),
218+
'type' => $change->getType(),
219+
'applied-on' => date('Y-m-d H:i:s'),
220+
'db-name' => $this->getDatabase()->getConnectionInfo()->getDBName(),
221+
'batch' => $batch,
222+
'status' => 'skipped'
201223
])->execute();
202224

203225
return $this->getLastInsertId();

WebFiori/Database/Schema/SchemaMigrationsTable.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,12 @@
5959
default: 1,
6060
comment: 'The batch number when this change was applied.'
6161
)]
62+
#[Column(
63+
name: 'status',
64+
type: DataType::VARCHAR,
65+
size: 20,
66+
default: 'applied',
67+
comment: 'Status of the change: applied or skipped.'
68+
)]
6269
class SchemaMigrationsTable {
6370
}

WebFiori/Database/Schema/SchemaRunner.php

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,9 +256,19 @@ public function clearRegisterErrorCallbacks(): void {
256256
* This table stores information about which migrations and seeders
257257
* have been applied, including timestamps and execution status.
258258
* Required for tracking database change history.
259+
*
260+
* If the table already exists but is missing the 'status' column
261+
* (upgrade from older version), the column will be added automatically.
259262
*/
260263
public function createSchemaTable() {
261264
$this->createTables();
265+
266+
// Add status column if missing (upgrade path for existing installations)
267+
try {
268+
$this->table('schema_changes')->select(['status'])->limit(1)->execute();
269+
} catch (\Throwable $e) {
270+
$this->table('schema_changes')->addCol('status')->execute();
271+
}
262272
}
263273

264274
/**
@@ -527,6 +537,127 @@ public function rollbackUpTo(?string $changeName): array {
527537
return $rolled;
528538
}
529539

540+
/**
541+
* Skip a single change without executing it (baseline).
542+
*
543+
* Records the change as 'skipped' in the schema_changes table so it
544+
* won't be executed by future apply() calls.
545+
*
546+
* @param DatabaseChange|string $change The change instance or class name.
547+
* @return bool True if skipped successfully, false if already applied/skipped or not found.
548+
*/
549+
public function skip(DatabaseChange|string $change): bool {
550+
$instance = is_string($change) ? $this->findChangeByName($change) : $change;
551+
552+
if ($instance === null) {
553+
return false;
554+
}
555+
556+
if ($this->isApplied($instance->getName())) {
557+
return false;
558+
}
559+
560+
$batch = $this->getRepository()->getNextBatchNumber();
561+
$this->getRepository()->recordSkipped($instance, $batch);
562+
563+
return true;
564+
}
565+
566+
/**
567+
* Skip all pending changes without executing them (baseline all).
568+
*
569+
* @return array Array of skipped DatabaseChange instances.
570+
*/
571+
public function skipAll(): array {
572+
$skipped = [];
573+
$batch = $this->getRepository()->getNextBatchNumber();
574+
575+
foreach ($this->dbChanges as $change) {
576+
if ($this->isApplied($change->getName())) {
577+
continue;
578+
}
579+
580+
if (!$this->shouldRunInEnvironment($change)) {
581+
continue;
582+
}
583+
584+
$this->getRepository()->recordSkipped($change, $batch);
585+
$skipped[] = $change;
586+
}
587+
588+
return $skipped;
589+
}
590+
591+
/**
592+
* Skip the next N pending changes without executing them.
593+
*
594+
* Changes are skipped in dependency order (same order apply() would use).
595+
*
596+
* @param int $count Number of changes to skip.
597+
* @return array Array of skipped DatabaseChange instances.
598+
*/
599+
public function skipNext(int $count = 1): array {
600+
$skipped = [];
601+
$batch = $this->getRepository()->getNextBatchNumber();
602+
603+
foreach ($this->dbChanges as $change) {
604+
if (count($skipped) >= $count) {
605+
break;
606+
}
607+
608+
if ($this->isApplied($change->getName())) {
609+
continue;
610+
}
611+
612+
if (!$this->shouldRunInEnvironment($change)) {
613+
continue;
614+
}
615+
616+
$this->getRepository()->recordSkipped($change, $batch);
617+
$skipped[] = $change;
618+
}
619+
620+
return $skipped;
621+
}
622+
623+
/**
624+
* Skip all pending changes up to and including the named one.
625+
*
626+
* @param string $changeName The class name of the change to skip up to.
627+
* @return array Array of skipped DatabaseChange instances.
628+
*/
629+
public function skipUpTo(string $changeName): array {
630+
$skipped = [];
631+
$batch = $this->getRepository()->getNextBatchNumber();
632+
633+
foreach ($this->dbChanges as $change) {
634+
if ($this->isApplied($change->getName())) {
635+
if ($change->getName() === $changeName) {
636+
break;
637+
}
638+
639+
continue;
640+
}
641+
642+
if (!$this->shouldRunInEnvironment($change)) {
643+
if ($change->getName() === $changeName) {
644+
break;
645+
}
646+
647+
continue;
648+
}
649+
650+
$this->getRepository()->recordSkipped($change, $batch);
651+
$skipped[] = $change;
652+
653+
if ($change->getName() === $changeName) {
654+
break;
655+
}
656+
}
657+
658+
return $skipped;
659+
}
660+
530661
private function areDependenciesSatisfied(DatabaseChange $change): bool {
531662
foreach ($change->getDependencies() as $depName) {
532663
if (!$this->isApplied($depName)) {

0 commit comments

Comments
 (0)