mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(db): baseline a database from the pre-ORM Tours branch correctly
That branch numbered its own steps after the upstream step it was cut from, so a database at schema_version 243 to 246 (or 216 to 218) with the Tours tables had four upstream migrations marked as run without ever running them. The baseline now cuts such a version back to the last upstream step, and the Tours migration adds the difficulty cap to a tours table an earlier branch stage created without it.
This commit is contained in:
@@ -128,6 +128,25 @@ async function legacyVersion(connection: Connection, migrator: Migrator): Promis
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* The pre-ORM Tours branch numbered its own steps after the upstream step it
|
||||
* was cut from: 216 to 218 on the older line, 243 to 246 on the later one.
|
||||
* Such a database's schema_version counts Tours steps, not the upstream steps
|
||||
* of the same numbers, so it is cut back to the last upstream step it really
|
||||
* has. The upstream steps then run, and the Tours migration adopts the tables
|
||||
* the branch already created.
|
||||
*/
|
||||
async function withoutToursSteps(connection: Connection, version: number): Promise<number> {
|
||||
const toursLine = version >= 216 && version <= 218 ? 215 : version >= 243 && version <= 246 ? 242 : null;
|
||||
if (toursLine === null) return version;
|
||||
const table: unknown[] = await connection.execute(
|
||||
`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'tour_types'`,
|
||||
);
|
||||
if (table.length === 0) return version;
|
||||
console.log(`[DB] schema_version ${version} was written by the pre-ORM Tours branch; baselining to upstream step ${toursLine}`);
|
||||
return toursLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* The migrations a positional-runner database already has — steps 1..N, never
|
||||
* the baseline (see the file header) — or `[]` for every database that is not
|
||||
@@ -138,8 +157,9 @@ export async function planLegacyBaseline(
|
||||
migrator: Migrator,
|
||||
readSource: ReadSource = readFromDisk,
|
||||
): Promise<string[]> {
|
||||
const version = await legacyVersion(connection, migrator);
|
||||
if (version === null) return [];
|
||||
const recorded = await legacyVersion(connection, migrator);
|
||||
if (recorded === null) return [];
|
||||
const version = await withoutToursSteps(connection, recorded);
|
||||
|
||||
const map = buildLegacyStepMap(await migrator.getPending(), readSource);
|
||||
if (version > map.finalStep) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
import { addColumnIfMissing, tableExists } from '../migration-utils';
|
||||
|
||||
/**
|
||||
* Tours: a facet on `places` for hikes and other single-day routes.
|
||||
@@ -18,7 +19,19 @@ import { Migration } from '@mikro-orm/migrations';
|
||||
export class Migration20200101042000_tours extends Migration {
|
||||
override name = 'Migration20200101042000_tours';
|
||||
|
||||
override up(): void {
|
||||
override async up(): Promise<void> {
|
||||
// The branch's earlier stages created `tours` without the difficulty cap.
|
||||
// `CREATE TABLE IF NOT EXISTS` below leaves such a table as it is, so the
|
||||
// column is added first, while the statements below are still queued.
|
||||
if (await tableExists(this, 'tours')) {
|
||||
await addColumnIfMissing(
|
||||
this,
|
||||
'tours',
|
||||
'max_hiking_difficulty',
|
||||
'max_hiking_difficulty INTEGER NOT NULL DEFAULT 2 CHECK(max_hiking_difficulty BETWEEN 1 AND 6)',
|
||||
);
|
||||
}
|
||||
|
||||
this.addSql(`
|
||||
CREATE TABLE IF NOT EXISTS tour_types (
|
||||
key TEXT PRIMARY KEY,
|
||||
|
||||
@@ -238,4 +238,25 @@ describe('planLegacyBaseline / migrateToHead', () => {
|
||||
await migrate();
|
||||
await expect(migrate()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[246, 242],
|
||||
[243, 242],
|
||||
[217, 215],
|
||||
])('LEGACYBASE-010: a pre-ORM Tours branch database at schema_version %i is baselined to upstream step %i', async (version, upstream) => {
|
||||
const all = await pendingNames(orm);
|
||||
const numbered = all.filter((name) => !UNNUMBERED.includes(name));
|
||||
await rawExec(orm, 'CREATE TABLE schema_version (version INTEGER NOT NULL)');
|
||||
await rawExec(orm, `INSERT INTO schema_version (version) VALUES (${version})`);
|
||||
await rawExec(orm, 'CREATE TABLE tour_types (key TEXT PRIMARY KEY)');
|
||||
expect(await baseline()).toEqual(numbered.slice(0, upstream));
|
||||
});
|
||||
|
||||
it('LEGACYBASE-011: the same schema_version without the Tours tables is an upstream database and keeps its step', async () => {
|
||||
const all = await pendingNames(orm);
|
||||
const numbered = all.filter((name) => !UNNUMBERED.includes(name));
|
||||
await rawExec(orm, 'CREATE TABLE schema_version (version INTEGER NOT NULL)');
|
||||
await rawExec(orm, 'INSERT INTO schema_version (version) VALUES (244)');
|
||||
expect(await baseline()).toEqual(numbered.slice(0, 244));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -136,4 +136,32 @@ describe('Tours migration', () => {
|
||||
await orm.close(true);
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
it('TOURMIG-004: a tours table from an earlier branch stage gains the difficulty cap and keeps its rows', async () => {
|
||||
const orm = await ormBeforeTarget();
|
||||
try {
|
||||
await rawExec(orm, `CREATE TABLE tour_types (
|
||||
key TEXT PRIMARY KEY, label_key TEXT NOT NULL, icon TEXT NOT NULL, color TEXT NOT NULL, routing_profile TEXT,
|
||||
is_sport INTEGER NOT NULL DEFAULT 1, enabled INTEGER NOT NULL DEFAULT 1, sort_order INTEGER NOT NULL DEFAULT 0)`);
|
||||
await rawExec(orm, "INSERT INTO tour_types (key, label_key, icon, color, routing_profile) VALUES ('hike', 'tourTypes.hike', 'Mountain', '#16a34a', 'pedestrian')");
|
||||
await rawExec(orm, `CREATE TABLE tours (
|
||||
place_id INTEGER PRIMARY KEY REFERENCES places(id) ON DELETE CASCADE,
|
||||
tour_type TEXT NOT NULL REFERENCES tour_types(key), distance REAL, elevation_gain REAL, elevation_loss REAL,
|
||||
duration REAL, difficulty TEXT, wanderer_ref TEXT, match_confidence REAL, tour_group_id INTEGER,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP)`);
|
||||
await seedTripWithPlace(orm);
|
||||
await rawExec(orm, "INSERT INTO tours (place_id, tour_type, distance) VALUES (100, 'hike', 8)");
|
||||
|
||||
await migrateTo(orm, TARGET);
|
||||
|
||||
expect(await rawQuery(orm, 'SELECT place_id, distance, max_hiking_difficulty FROM tours')).toEqual([
|
||||
{ place_id: 100, distance: 8, max_hiking_difficulty: 2 },
|
||||
]);
|
||||
expect(await rawQuery(orm, "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'tour_waypoints'")).toEqual([
|
||||
{ name: 'tour_waypoints' },
|
||||
]);
|
||||
} finally {
|
||||
await orm.close(true);
|
||||
}
|
||||
}, 30000);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user