From 4b37560640ac2a5d1428127cc143a5e1050103fc Mon Sep 17 00:00:00 2001
From: Daniel Friesel <derf@finalrewind.org>
Date: Mon, 12 Dec 2022 22:35:08 +0100
Subject: database migration: track previosu travelynx version; offer rollback
 hints

---
 lib/Travelynx/Command/database.pm | 79 ++++++++++++++++++++++++++++++++-------
 1 file changed, 66 insertions(+), 13 deletions(-)

(limited to 'lib')

diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm
index e9cffa8..d6158bf 100644
--- a/lib/Travelynx/Command/database.pm
+++ b/lib/Travelynx/Command/database.pm
@@ -1198,6 +1198,20 @@ my @migrations = (
 			}
 		);
 	},
+
+	# v28 -> v29
+	# add pre-migration travelynx version. This way, a failed migration can
+	# print a helpful "git checkout" command.
+	sub {
+		my ($db) = @_;
+		$db->query(
+			qq{
+				alter table schema_version
+					add column travelynx varchar(64);
+				update schema_version set version = 29;
+			}
+		);
+	},
 );
 
 sub sync_stations {
@@ -1393,27 +1407,56 @@ sub setup_db {
 	}
 }
 
+sub failure_hints {
+	my ($old_version) = @_;
+	say STDERR 'This travelynx instance has reached an undefined state:';
+	say STDERR
+'The source code is expecting a different schema version than present in the database.';
+	say STDERR
+'Please file a detailed bug report at <https://github.com/derf/travelynx/issues>';
+	say STDERR 'or send an e-mail to derf+travelynx@finalrewind.org.';
+	if ($old_version) {
+		say STDERR '';
+		say STDERR
+		  "The last migration was performed with travelynx v${old_version}.";
+		say STDERR
+'You may be able to return to a working state with the following command:';
+		say STDERR "git checkout ${old_version}";
+		say STDERR '';
+		say STDERR 'We apologize for any inconvenience.';
+	}
+}
+
 sub migrate_db {
-	my ($db) = @_;
+	my ( $self, $db ) = @_;
 	my $tx = $db->begin;
 
 	my $schema_version = get_schema_version($db);
 	say "Found travelynx schema v${schema_version}";
 
+	my $old_version;
+
+	if ( $schema_version >= 29 ) {
+		$old_version = get_schema_version( $db, 'travelynx' );
+	}
+
 	if ( $schema_version == @migrations ) {
 		say 'Database layout is up-to-date';
 	}
-
-	eval {
-		for my $i ( $schema_version .. $#migrations ) {
-			printf( "Updating to v%d ...\n", $i + 1 );
-			$migrations[$i]($db);
+	else {
+		eval {
+			for my $i ( $schema_version .. $#migrations ) {
+				printf( "Updating to v%d ...\n", $i + 1 );
+				$migrations[$i]($db);
+			}
+			say 'Update complete.';
+		};
+		if ($@) {
+			say STDERR "Migration failed: $@";
+			say STDERR "Rolling back to v${schema_version}";
+			failure_hints($old_version);
+			exit(1);
 		}
-	};
-	if ($@) {
-		say STDERR "Migration failed: $@";
-		say STDERR "Rolling back to v${schema_version}";
-		exit(1);
 	}
 
 	my $iris_version = get_schema_version( $db, 'iris' );
@@ -1426,16 +1469,24 @@ sub migrate_db {
 			say
 "Synchronizing with Travel::Status::DE::IRIS $Travel::Status::DE::IRIS::Stations::VERSION";
 			sync_stations( $db, $iris_version );
+			say 'Synchronization complete.';
 		};
 		if ($@) {
 			say STDERR "Synchronization failed: $@";
-			say STDERR "Rolling back to v${schema_version}";
+			if ( $schema_version != @migrations ) {
+				say STDERR "Rolling back to v${schema_version}";
+				failure_hints($old_version);
+			}
 			exit(1);
 		}
 	}
 
+	$db->update( 'schema_version',
+		{ travelynx => $self->app->config->{version} } );
+
 	if ( get_schema_version($db) == @migrations ) {
 		$tx->commit;
+		say 'Changes committed to database. Have a nice day.';
 	}
 	else {
 		printf STDERR (
@@ -1444,6 +1495,8 @@ sub migrate_db {
 			get_schema_version($db)
 		);
 		say STDERR "Rolling back to v${schema_version}";
+		say STDERR "";
+		failure_hints($old_version);
 		exit(1);
 	}
 }
@@ -1462,7 +1515,7 @@ sub run {
 		if ( not defined get_schema_version($db) ) {
 			setup_db($db);
 		}
-		migrate_db($db);
+		$self->migrate_db($db);
 	}
 	elsif ( $command eq 'has-current-schema' ) {
 		if (    get_schema_version($db) == @migrations
-- 
cgit v1.2.3