Skip to content

Commit

Permalink
add migrations
Browse files Browse the repository at this point in the history
  • Loading branch information
Johan committed Aug 23, 2023
1 parent ccd43a4 commit 053d43c
Show file tree
Hide file tree
Showing 9 changed files with 412 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@nexys/validation": "^2.1.4",
"@types/pg": "^8.10.2",
"@types/sqlstring": "^2.3.0",
"crc-32": "^1.2.2",
"mysql2": "^2.3.3",
"pg": "^8.11.3",
"sqlstring": "^2.3.2"
Expand Down
18 changes: 18 additions & 0 deletions src/lib/migrations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
migrated from https://github.com/nexys-system/sql-migrations/blob/master/README.md

# SQL-Migrations

[![npm version](https://badge.fury.io/js/@nexys%2Fsql-migrations.svg)](https://www.npmjs.com/package/@nexys/sql-migrations)
[![npm version](https://img.shields.io/npm/v/@nexys%2Fsql-migrations.svg)](https://www.npmjs.com/package/@nexys/sql-migrations)
[![Build and Test Package](https://github.com/nexys-system/sql-migrations/actions/workflows/yarn.yml/badge.svg)](https://github.com/nexys-system/sql-migrations/actions/workflows/yarn.yml)
[![Build and Test Package and (publish)](https://github.com/nexys-system/sql-migrations/actions/workflows/publish.yml/badge.svg)](https://github.com/nexys-system/sql-migrations/actions/workflows/publish.yml)
[![Code style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io/)
[![Bundlephobia](https://badgen.net/bundlephobia/min/@nexys/sql-migrations)](https://bundlephobia.com/result?p=@nexys/sql-migrations)

Migrations for MySQL. Same interfaces and compatibility with [flyway](https://flywaydb.org/)

## Get started

- Install `yarn`
- Build`yarn build`
- Test `yarn test`
6 changes: 6 additions & 0 deletions src/lib/migrations/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as I from "./index";

test("import/exports", () => {
expect(typeof I.Migrations).toEqual("object");
expect(typeof I.Utils).toEqual("object");
});
3 changes: 3 additions & 0 deletions src/lib/migrations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * as Migrations from "./migrations";
export * as Utils from "./utils";
export * as Type from "./type";
103 changes: 103 additions & 0 deletions src/lib/migrations/migrations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {
OkPacket,
ResultSetHeader,
RowDataPacket,
FieldPacket,
} from "mysql2/promise";

//import * as T from "@nexys/sql-migrations/dist/type";
//import * as U from "@nexys/sql-migrations/dist/utils";
import * as T from "./type";
import * as U from "./utils";

type Response = [
OkPacket | ResultSetHeader | RowDataPacket[] | RowDataPacket[][] | OkPacket[],
FieldPacket[]
];

interface SQL {
execQuery: (query: string) => Promise<Response>;
}

// manages migration
// inspiration from flyway - https://flywaydb.org/
export const runMigrations = async (
migrations: T.Migration[],
s: SQL
): Promise<T.MigrationRow[]> => {
U.checkSequence(migrations);
// create table if not exists
//console.log(createMigrationTable);
await s.execQuery(U.createMigrationTable);

// get all migrations
const [r] = await s.execQuery(U.getMigrations);
const y = r as RowDataPacket[] as T.MigrationRow[];

const { installed_rank: lastRank } = U.getLastRow(y);

const pRows = migrations.map(async (migration, i) => {
const version = U.toVersion(migration.version, migration.idx);
const checksum = U.getChecksum(migration.sql);

// find previous migration with same version and compare checksums
if (U.findPreviousMigrations(version, checksum, y)) {
return;
}

const t1 = new Date().getTime();
const [rm] = await s.execQuery(migration.sql);
const t2 = new Date().getTime();

const success: number = getSuccess(rm as OkPacket | OkPacket[]);

const row: T.MigrationRow = U.migrationToRow(
migration.name,
version,
t2 - t1,
success,
checksum,
lastRank + i + 1
);

return row;
});

const rawRows = await Promise.all(pRows);

// note here that we compare raw rows since some can be empyt because the migrations were applied earlier
if (rawRows.length !== migrations.length) {
throw "something went wrong while applying migrations";
}

const rows: T.MigrationRow[] = rawRows.filter(isNotNull);

if (rows.length === 0) {
return [];
}

// enter result in flyway table
const sql = U.migrationsToSQL(rows);
const [rm] = await s.execQuery(sql);
console.log(rm);

return rows;
};

/**
* @note more information on serverStatus: https://github.com/mysqljs/mysql/issues/745 and https://dev.mysql.com/doc/internals/en/status-flags.html
* @param rm: can be an array or a scalar
* @returns the serverstatus of the last call
*/
const getSuccess = (rm: OkPacket | OkPacket[]): number => {
// if array return the last one
if (Array.isArray(rm)) {
const l = rm.length;
return getSuccess(rm[l - 1]);
}

return rm.serverStatus;
};

const isNotNull = <A>(x: A | null | undefined): x is A =>
x !== null && x !== undefined;
19 changes: 19 additions & 0 deletions src/lib/migrations/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export interface Migration {
name: string;
sql: string;
version: number;
idx: number;
}

export interface MigrationRow {
checksum: number;
description: string;
execution_time: number;
installed_by: string;
installed_on: string;
installed_rank: number;
script: string;
success: number;
type: string;
version: string;
}
96 changes: 96 additions & 0 deletions src/lib/migrations/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as M from "./utils";

describe("check sequence", () => {
test("right seq", () => {
const s = [{ version: 0, idx: 1 }];

expect(M.checkSequence(s)).toEqual(undefined);
});

test("right seq2", () => {
const s = [
{ version: 0, idx: 1 },
{ version: 0, idx: 2 },
{ version: 1, idx: 1 },
{ version: 1, idx: 3 },
{ version: 2, idx: 2 },
];

expect(M.checkSequence(s)).toEqual(undefined);
});

test("wrong versions", () => {
const s = [
{ version: 0, idx: 1 },
{ version: 0, idx: 2 },
{ version: 2, idx: 1 },
{ version: 1, idx: 3 },
{ version: 2, idx: 2 },
];

try {
M.checkSequence(s);
} catch (err) {
expect(typeof err).toEqual("object");
}
});
});

test("to version", () => {
expect(M.toVersion(2, 4)).toEqual("2.4");
});

test("to script", () => {
expect(M.toScript("2.4", "myname")).toEqual("v2_4__myname.sql");
});

test("migration to sql", () => {
const row = M.migrationToRow(
"myname",
"2.3",
123,
1,
1234567,
2,
"admin",
new Date(2021, 12, 1, 13, 45)
);

expect(M.migrationsToSQL([row])).toEqual(
"INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (2, 2.3, 'myname', 'SQL', 'v2_3__myname.sql', 1234567, 'admin', '2022-01-01 13:45:00', 123, 1);"
);
});

test("checksum", () => {
const cs = -1064643516;
const s =
"CREATE TABLE `user` (`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `uuid` VARCHAR(64) NOT NULL, `first_name` VARCHAR(512) NOT NULL, `last_name` VARCHAR(512) NOT NULL, `email` VARCHAR(512) NOT NULL, `status_id` BIGINT NOT NULL, `log_date_added` DATETIME NOT NULL DEFAULT NOW(), `instance_id` BIGINT NOT NULL, `lang` VARCHAR(512) NOT NULL);";
expect(M.getChecksum(s)).toEqual(cs);
});

describe("find previous migrations", () => {
const y = [{ version: "2.1", checksum: 123 }];

test("does not exist", () => {
expect(M.findPreviousMigrations("2.2", 23, y)).toEqual(false);
});

test("already exists", () => {
expect(M.findPreviousMigrations("2.1", 123, y)).toEqual(true);
});

test("already exists but different checksum", () => {
try {
M.findPreviousMigrations("2.1", 43, y);
} catch (err) {
expect(typeof err).toEqual("object");
}
});
});

test("get last row", () => {
expect(M.getLastRow([])).toEqual({ installed_rank: 0 });
expect(M.getLastRow([{ installed_rank: 1 }, { installed_rank: 2 }])).toEqual({
installed_rank: 2,
});
});
Loading

0 comments on commit 053d43c

Please sign in to comment.