fediglam/src/main/db/migrations.zig

197 lines
5.3 KiB
Zig
Raw Normal View History

2022-07-30 07:26:35 +00:00
const sql = @import("sql");
const DateTime = @import("util").DateTime;
pub const Migration = struct {
name: []const u8,
up: []const u8,
down: []const u8,
};
fn firstIndexOf(str: []const u8, char: u8) ?usize {
for (str) |ch, i| {
if (ch == char) return i;
}
return null;
}
fn execStmt(db: *sql.Sqlite, stmt_sql: []const u8) !void {
const stmt = try db.prepare(stmt_sql);
defer stmt.finalize();
while (try stmt.step()) |_| {}
}
fn execScript(db: *sql.Sqlite, script: []const u8) !void {
try execStmt(db, "BEGIN;");
errdefer {
_ = execStmt(db, "ROLLBACK;") catch unreachable;
}
var remaining = script;
while (firstIndexOf(remaining, ';')) |last| {
try execStmt(db, remaining[0 .. last + 1]);
remaining = remaining[last + 1 ..];
}
try execStmt(db, "COMMIT;");
}
fn wasMigrationRan(db: *sql.Sqlite, name: []const u8) !bool {
const stmt = try db.prepare("SELECT COUNT(*) FROM migration WHERE name = ?;");
defer stmt.finalize();
try stmt.bindText(1, name);
const result = (try stmt.step()).?;
const count = try result.getI64(0);
return count != 0;
}
fn markMigrationAsRan(db: *sql.Sqlite, name: []const u8) !void {
const stmt = try db.prepare("INSERT INTO migration(name) VALUES(?);");
defer stmt.finalize();
try stmt.bindText(1, name);
_ = try stmt.step();
}
pub fn up(db: *sql.Sqlite) !void {
try execScript(db, create_migration_table);
for (migrations) |migration| {
if (!try wasMigrationRan(db, migration.name)) {
try execScript(db, migration.up);
try markMigrationAsRan(db, migration.name);
}
}
}
const create_migration_table =
\\CREATE TABLE IF NOT EXISTS
\\migration(
\\ name TEXT NOT NULL PRIMARY KEY,
\\ applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
\\) STRICT;
;
// NOTE: Until the first public release, i may collapse multiple
// migrations into a single one. this will require db recreation
const migrations: []const Migration = &.{
.{
.name = "users and actors",
.up =
\\CREATE TABLE user(
\\ id TEXT NOT NULL PRIMARY KEY,
\\ username TEXT NOT NULL,
\\
\\ created_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP
\\) STRICT;
\\
\\CREATE TABLE actor(
\\ user_id TEXT NOT NULL PRIMARY KEY REFERENCES user(id),
\\ public_id TEXT NOT NULL
\\) STRICT;
\\
\\CREATE TABLE local_user(
\\ user_id TEXT NOT NULL PRIMARY KEY REFERENCES user(id),
\\
\\ email TEXT,
\\
\\ hashed_password TEXT NOT NULL,
\\ password_changed_at INTEGER NOT NULL
\\) STRICT;
,
.down =
\\DROP TABLE local_user;
\\DROP TABLE actor;
\\DROP TABLE user;
,
},
.{
.name = "notes",
.up =
\\CREATE TABLE note(
\\ id TEXT NOT NULL,
\\
\\ content TEXT NOT NULL,
\\ author_id TEXT NOT NULL REFERENCES actor(id),
\\
\\ created_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP
\\) STRICT;
,
.down = "DROP TABLE note;",
},
.{
.name = "note reactions",
.up =
\\CREATE TABLE reaction(
\\ id TEXT NOT NULL PRIMARY KEY,
\\
\\ reactor_id TEXT NOT NULL REFERENCES actor(id),
\\ note_id TEXT NOT NULL REFERENCES note(id),
\\
\\ created_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP
\\) STRICT;
,
.down = "DROP TABLE reaction;",
},
.{
.name = "user tokens",
.up =
\\CREATE TABLE token(
\\ id TEXT NOT NULL PRIMARY KEY,
\\
\\ hash BLOB UNIQUE NOT NULL,
\\ user_id TEXT NOT NULL REFERENCES local_user(id),
\\
\\ issued_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP
\\) STRICT;
,
.down = "DROP TABLE token;",
},
.{
.name = "user invites",
.up =
\\CREATE TABLE invite(
\\ id TEXT NOT NULL PRIMARY KEY,
\\
\\ name TEXT NOT NULL,
\\ invite_code TEXT NOT NULL UNIQUE,
\\ created_by TEXT NOT NULL REFERENCES local_user(id),
\\
\\ max_uses INTEGER,
\\
\\ created_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,
\\ expires_at INTEGER
\\) STRICT;
\\ALTER TABLE local_user ADD COLUMN invite_id TEXT REFERENCES invite(id);
,
.down =
\\ALTER TABLE local_user DROP COLUMN invite_id;
\\DROP TABLE invite;
,
},
2022-08-02 04:33:23 +00:00
.{
.name = "communities",
.up =
\\CREATE TABLE community(
\\ id TEXT NOT NULL PRIMARY KEY,
\\
\\ name TEXT NOT NULL,
\\ host TEXT NOT NULL UNIQUE,
\\ scheme TEXT NOT NULL CHECK (scheme IN ('http', 'https')),
\\
\\ created_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP
\\) STRICT;
\\ALTER TABLE user ADD COLUMN community_id TEXT REFERENCES community(id);
\\ALTER TABLE invite ADD COLUMN to_community TEXT REFERENCES community(id);
,
.down =
\\ALTER TABLE invite DROP COLUMN to_community;
\\ALTER TABLE user DROP COLUMN community_id;
\\DROP TABLE community;
,
},
2022-07-30 07:26:35 +00:00
};