Skip to content

Commit 87a4e70

Browse files
committed
wip: reset hashing
for #114
1 parent 972fdb7 commit 87a4e70

File tree

4 files changed

+97
-15
lines changed

4 files changed

+97
-15
lines changed

src/Cli/ExecuteCommand.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,19 @@ public function run(?ArgumentValueList $arguments = null):void {
5757
$migrationCount = $migrator->getMigrationCount();
5858
$migrationFileList = $migrator->getMigrationFileList();
5959

60+
$resetNumber = null;
61+
if($arguments->contains("reset")) {
62+
$resetNumber = $arguments->get("reset")->get();
63+
if(!$resetNumber) {
64+
$lastKey = array_key_last($migrationFileList);
65+
$resetNumber = $migrator->extractNumberFromFilename($migrationFileList[$lastKey]);
66+
}
67+
$resetNumber = (int)$resetNumber;
68+
}
69+
6070
try {
61-
$migrator->checkIntegrity($migrationFileList, $migrationCount);
62-
$migrator->performMigration($migrationFileList, $migrationCount);
71+
$migrator->checkIntegrity($migrationFileList, $resetNumber ?? $migrationCount);
72+
$migrator->performMigration($migrationFileList, $resetNumber ?? $migrationCount);
6373
}
6474
catch(MigrationIntegrityException $exception) {
6575
$this->writeLine(
@@ -106,6 +116,12 @@ public function getOptionalParameterList():array {
106116
"force",
107117
"f",
108118
"Forcefully drop the current schema and run from migration 1"
119+
),
120+
new Parameter(
121+
true,
122+
"reset",
123+
"r",
124+
"Reset the integrity checks to a specific migration number"
109125
)
110126
];
111127
}

src/Migration/Migrator.php

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public function createMigrationTable():void {
8989
$this->dbClient->executeSql(implode("\n", [
9090
"create table if not exists `{$this->tableName}` (",
9191
"`" . self::COLUMN_QUERY_NUMBER . "` int primary key,",
92-
"`" . self::COLUMN_QUERY_HASH . "` varchar(32) not null,",
92+
"`" . self::COLUMN_QUERY_HASH . "` varchar(32) null,",
9393
"`" . self::COLUMN_MIGRATED_AT . "` datetime not null )",
9494
]));
9595
}
@@ -144,26 +144,32 @@ public function checkFileListOrder(array $fileList):void {
144144
/** @param array<string> $migrationFileList */
145145
public function checkIntegrity(
146146
array $migrationFileList,
147-
?int $migrationCount = null
147+
?int $migrationStartFrom = null
148148
):int {
149149
$fileNumber = 0;
150150

151151
foreach($migrationFileList as $i => $file) {
152-
$fileNumber = $i + 1;
152+
$fileNumber = $this->extractNumberFromFilename($file);
153153
$md5 = md5_file($file);
154154

155-
if(is_null($migrationCount)
156-
|| $fileNumber <= $migrationCount) {
155+
if($migrationStartFrom) {
156+
if($fileNumber < $migrationStartFrom) {
157+
continue;
158+
}
159+
}
160+
161+
if(is_null($migrationStartFrom)
162+
|| $fileNumber <= $migrationStartFrom) {
157163
$result = $this->dbClient->executeSql(implode("\n", [
158164
"select `" . self::COLUMN_QUERY_HASH . "`",
159165
"from `{$this->tableName}`",
160166
"where `" . self::COLUMN_QUERY_NUMBER . "` = ?",
161167
"limit 1",
162168
]), [$fileNumber]);
163169

164-
$hashInDb = ($result->fetch())->getString(self::COLUMN_QUERY_HASH);
170+
$hashInDb = ($result->fetch())?->getString(self::COLUMN_QUERY_HASH);
165171

166-
if($hashInDb !== $md5) {
172+
if($hashInDb && $hashInDb !== $md5) {
167173
throw new MigrationIntegrityException($file);
168174
}
169175
}
@@ -172,7 +178,7 @@ public function checkIntegrity(
172178
return $fileNumber;
173179
}
174180

175-
protected function extractNumberFromFilename(string $pathName):int {
181+
public function extractNumberFromFilename(string $pathName):int {
176182
$file = new SplFileInfo($pathName);
177183
$filename = $file->getFilename();
178184
preg_match("/(\d+)-?.*\.sql/", $filename, $matches);
@@ -187,15 +193,15 @@ protected function extractNumberFromFilename(string $pathName):int {
187193
/** @param array<string> $migrationFileList */
188194
public function performMigration(
189195
array $migrationFileList,
190-
int $existingMigrationCount = 0
196+
int $existingFileNumber = 0
191197
):int {
192198
$fileNumber = 0;
193199
$numCompleted = 0;
194200

195201
foreach($migrationFileList as $i => $file) {
196-
$fileNumber = $i + 1;
202+
$fileNumber = $this->extractNumberFromFilename($file);
197203

198-
if($fileNumber <= $existingMigrationCount) {
204+
if($fileNumber <= $existingFileNumber) {
199205
continue;
200206
}
201207

@@ -277,7 +283,7 @@ public function selectSchema():void {
277283
}
278284
}
279285

280-
protected function recordMigrationSuccess(int $number, string $hash):void {
286+
protected function recordMigrationSuccess(int $number, ?string $hash):void {
281287
$now = "now()";
282288

283289
if($this->driver === Settings::DRIVER_SQLITE) {
@@ -295,6 +301,15 @@ protected function recordMigrationSuccess(int $number, string $hash):void {
295301
]), [$number, $hash]);
296302
}
297303

304+
/**
305+
* @param int $numberToForce A null-hashed migration will be marked as
306+
* successful with this number. This will allow the next number to be
307+
* executed out of sequence.
308+
*/
309+
public function resetMigrationSequence(int $numberToForce):void {
310+
$this->recordMigrationSuccess($numberToForce, null);
311+
}
312+
298313
/**
299314
* @codeCoverageIgnore
300315
*/

src/Query/QueryCollectionFactory.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ public function create(string $name):QueryCollection {
2727
}
2828

2929
return $this->queryCollectionCache[$name];
30-
3130
}
3231

3332
public function directoryExists(string $name):bool {

test/phpunit/Migration/MigratorTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,58 @@ public function testForcedMigration(array $fileList) {
279279
self::assertNull($exception,"Exception should not be thrown");
280280
}
281281

282+
/**
283+
* This test needs an explanation because it's not immediately obvious.
284+
* The fileList is generated as usual, but then to simulate a real
285+
* production "messy" codebase, a new migration file is created with a
286+
* much higher sequence (15 higher than the last in the fileList).
287+
*
288+
* Because of this, the migration will fail. However, we reset the
289+
* migration sequence before performing the migration, and even though
290+
* none of the files in fileList are migrated yet, we should only see
291+
* 1 migration take place.
292+
*
293+
* @dataProvider dataMigrationFileList
294+
*/
295+
public function testResetMigration(array $fileList) {
296+
$path = $this->getMigrationDirectory();
297+
$this->createMigrationFiles($fileList, $path);
298+
$settings = $this->createSettings($path);
299+
300+
$migrator = new Migrator(
301+
$settings,
302+
$path,
303+
"_migration",
304+
);
305+
306+
$newNumber = count($fileList) + 15;
307+
308+
$newFileName = str_pad($newNumber, 4, "0", STR_PAD_LEFT);
309+
$newFileName .= "-" . uniqid() . ".sql";
310+
$newFilePath = implode(DIRECTORY_SEPARATOR, [
311+
$path,
312+
$newFileName,
313+
]);
314+
array_push($fileList, $newFileName);
315+
316+
$absoluteFileList = array_map(function($file)use($path) {
317+
return implode(DIRECTORY_SEPARATOR, [
318+
$path,
319+
$file,
320+
]);
321+
},$fileList);
322+
323+
$lastKey = array_key_last($absoluteFileList);
324+
file_put_contents($absoluteFileList[$lastKey], "create table migrated_out_of_order ( id int primary key )");
325+
326+
$migrator->createMigrationTable();
327+
$migrator->resetMigrationSequence($newNumber - 1);
328+
$migrationCount = $migrator->getMigrationCount();
329+
$migrator->checkIntegrity($absoluteFileList, $migrationCount);
330+
$migrationsExecuted = $migrator->performMigration($absoluteFileList, $migrationCount);
331+
self::assertSame(1, $migrationsExecuted);
332+
}
333+
282334
/**
283335
* @dataProvider dataMigrationFileList
284336
* @runInSeparateProcess

0 commit comments

Comments
 (0)