Skip to content

Commit

Permalink
fix: new database migration strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
OXeu committed Jul 12, 2024
1 parent 85eb56e commit a418db9
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 30 deletions.
27 changes: 24 additions & 3 deletions scripts/dev-migrator.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,53 @@
import * as fs from 'fs';
import * as path from 'path';
import {execSync} from 'child_process';
import { execSync } from 'child_process';
import { fixTopField, getMigrationVersion, isInfoExist, updateMigrationVersion } from './fix-top-field';

const DB_NAME = "rin";
const SQL_DIR = path.join(__dirname, '..', 'server', 'sql');

// Change to the server/sql directory
process.chdir(SQL_DIR);

const migrationVersion = await getMigrationVersion(true, DB_NAME);
const isInfoExistResult = await isInfoExist(true, DB_NAME);
// List all SQL files and sort them
const sqlFiles = fs
.readdirSync(SQL_DIR, {withFileTypes: true})
.readdirSync(SQL_DIR, { withFileTypes: true })
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.sql'))
.map(dirent => dirent.name)
.filter(file => {
const version = parseInt(file.split('-')[0]);
return version > migrationVersion;
})
.sort();

console.log("migration_version:", migrationVersion, "Migration SQL List: ", sqlFiles)

// For each file in the sorted list
for (const file of sqlFiles) {
const filePath = path.join(SQL_DIR, file);
// Run the migration
try {
execSync(`bunx wrangler d1 execute ${DB_NAME} --local --file "${filePath}"`, {stdio: 'inherit'});
execSync(`bunx wrangler d1 execute ${DB_NAME} --local --file "${filePath}"`, { stdio: 'inherit' });
console.log(`Executed ${file}`);
} catch (error) {
console.error(`Failed to execute ${file}: ${error}`);
process.exit(1);
}
}

if (sqlFiles.length === 0) {
console.log("No migration needed.")
} else {
const lastVersion = parseInt(sqlFiles[sqlFiles.length - 1].split('-')[0]);
if (lastVersion > migrationVersion) {
// Update the migration version
await updateMigrationVersion(true, DB_NAME, lastVersion);
}
}

await fixTopField(true, DB_NAME, isInfoExistResult);

// Back to the root directory (optional, as the script ends)
process.chdir(__dirname);
52 changes: 52 additions & 0 deletions scripts/fix-top-field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { $ } from "bun"

export async function fixTopField(isLocal: boolean, db: string, isInfoExistResult: boolean) {
const typ = isLocal ? "local" : "remote"
if (!isInfoExistResult) {
console.log("Legacy database, check top field")
const result = await $`bunx wrangler d1 execute ${db} --${typ} --json --command "SELECT name FROM pragma_table_info('feeds') WHERE name='top'"`.quiet().json()
if (result[0].results.length === 0) {
console.log("Adding top field to feeds table")
await $`bunx wrangler d1 execute ${db} --${typ} --json --command "ALTER TABLE feeds ADD COLUMN top INTEGER DEFAULT 0"`.quiet()
} else {
console.log("Top field already exists in feeds table")
}
} else {
console.log("New database, skip top field check")
}
}

export async function isInfoExist(isLocal: boolean, db: string) {
const typ = isLocal ? "local" : "remote"
const result = await $`bunx wrangler d1 execute ${db} --${typ} --json --command "SELECT name FROM sqlite_master WHERE type='table' AND name='info'"`.quiet().json()
if (result[0].results.length === 0) {
console.log("info table not exists")
return false
} else {
console.log("info table already exists")
return true
}
}

export async function getMigrationVersion(isLocal: boolean, db: string) {
const isInfoExistResult = await isInfoExist(isLocal, db)
if (!isInfoExistResult) {
console.log("Legacy database, migration_version not exists")
return -1
}
const typ = isLocal ? "local" : "remote"
const result = await $`bunx wrangler d1 execute ${db} --${typ} --json --command "SELECT value FROM info WHERE key='migration_version'"`.quiet().json()
if (result[0].results.length === 0) {
console.log("migration_version not exists")
return -1
} else {
console.log("migration_version:", result[0].results[0].value)
return parseInt(result[0].results[0].value)
}
}

export async function updateMigrationVersion(isLocal: boolean, db: string, version: number) {
const typ = isLocal ? "local" : "remote"
await $`bunx wrangler d1 execute ${db} --${typ} --json --command "UPDATE info SET value='${version}' WHERE key='migration_version'"`.quiet()
console.log("Updated migration_version to", version)
}
29 changes: 26 additions & 3 deletions scripts/migrator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { $ } from "bun"
import { readdir } from "node:fs/promises"
import stripIndent from 'strip-indent'
import { fixTopField, getMigrationVersion, isInfoExist, updateMigrationVersion } from "./fix-top-field"

function env(name: string, defaultValue?: string, required = false) {
const env = process.env
Expand All @@ -12,7 +13,7 @@ function env(name: string, defaultValue?: string, required = false) {
}

// must be defined
const renv = (name: string, defaultValue?: string) => env(name, defaultValue, true)
const renv = (name: string, defaultValue?: string) => env(name, defaultValue, true)!

const DB_NAME = renv("DB_NAME", 'rin')
const WORKER_NAME = renv("WORKER_NAME", 'rin-server')
Expand Down Expand Up @@ -102,21 +103,43 @@ if (existing) {
}

console.log(`----------------------------`)

console.log(`Migrating D1 "${DB_NAME}"`)
const migrationVersion = await getMigrationVersion(true, DB_NAME);
const isInfoExistResult = await isInfoExist(true, DB_NAME);

try {
const files = await readdir("./server/sql", { recursive: false })
for (const file of files) {
const sqlFiles = files
.filter(name => name.endsWith('.sql'))
.filter(name => {
const version = parseInt(name.split('-')[0]);
return version > migrationVersion;
})
.sort();
console.log("migration_version:", migrationVersion, "Migration SQL List: ", sqlFiles)
for (const file of sqlFiles) {
await $`bunx wrangler d1 execute ${DB_NAME} --remote --file ./server/sql/${file} -y`
console.log(`Migrated ${file}`)
}
if (sqlFiles.length === 0) {
console.log("No migration needed.")
} else {
const lastVersion = parseInt(sqlFiles[sqlFiles.length - 1].split('-')[0]);
if (lastVersion > migrationVersion) {
// Update the migration version
await updateMigrationVersion(true, DB_NAME, lastVersion);
}
}
} catch (e: any) {
console.error(e.stderr.toString())
process.exit(1)
}

console.log(`Migrated D1 "${DB_NAME}"`)
console.log(`----------------------------`)
console.log(`Patch D1`)
await fixTopField(true, DB_NAME, isInfoExistResult);
console.log(`----------------------------`)
console.log(`Put secrets`)

async function putSecret(name: string, value?: string) {
Expand Down
29 changes: 5 additions & 24 deletions server/sql/0002.sql
Original file line number Diff line number Diff line change
@@ -1,26 +1,7 @@
PRAGMA foreign_keys=off;
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS `feeds_new` (
CREATE TABLE IF NOT EXISTS `info` (
`id` integer PRIMARY KEY NOT NULL,
`alias` text,
`title` text,
`content` text NOT NULL,
`summary` text DEFAULT '' NOT NULL,
`listed` integer DEFAULT 1 NOT NULL,
`draft` integer DEFAULT 1 NOT NULL,
`uid` integer NOT NULL,
`top` integer DEFAULT 0 NOT NULL,
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
FOREIGN KEY (`uid`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
`key` text NOT NULL,
`value` text NOT NULL
);
--> statement-breakpoint
INSERT INTO `feeds_new` (`id`, `alias`, `title`, `content`, `summary`, `listed`, `draft`, `uid`, `created_at`, `updated_at`)
SELECT `id`, `alias`, `title`, `content`, `summary`, `listed`, `draft`, `uid`, `created_at`, `updated_at`
FROM `feeds`;
--> statement-breakpoint
DROP TABLE `feeds`;
--> statement-breakpoint
ALTER TABLE `feeds_new` RENAME TO `feeds`;
--> statement-breakpoint
PRAGMA foreign_keys=on;

INSERT INTO `info` (`key`, `value`) VALUES ('migration_version', '2');
6 changes: 6 additions & 0 deletions server/src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export const visits = sqliteTable("visits", {
createdAt: created_at,
});

export const info = sqliteTable("info", {
id: integer("id").primaryKey(),
key: text("key").notNull(),
value: text("value").notNull(),
});

export const friends = sqliteTable("friends", {
id: integer("id").primaryKey(),
name: text("name").notNull(),
Expand Down

0 comments on commit a418db9

Please sign in to comment.