Run DB migrations on startup

This commit is contained in:
Brandon Dyck 2024-07-06 11:41:55 -06:00
parent c9f7675308
commit c7344a9e37
4 changed files with 101 additions and 20 deletions

View File

@ -2,7 +2,11 @@
local migrate = require "migrate"
migrate.migrate()
local DB_FILENAME = "peachy.db"
sqlite3 = require "lsqlite3"
local db = sqlite3.open(DB_FILENAME)
db:exec("PRAGMA journal_mode=WAL;")
migrate.migrate(db)
fm = require "fullmoon"
fm.setTemplate("hello", "Hello, {%& name %}")

View File

@ -3,30 +3,92 @@ local path = require "path"
local filenameRegex = re.compile([=[^([0-9]+).*\.sql$]=])
local function migrate()
local migrations = {}
local seqnums = {}
local paths = GetZipPaths("/migrations/")
for _, p in ipairs(paths) do
-- basename -> sequence number
-- check that sequence number doesn't already exist
local basename = path.basename(p)
local _, seqnum = filenameRegex:search(basename)
seqnum = tonumber(seqnum)
if seqnum and not seqnums[seqnum] then
table.insert(seqnums, seqnum)
table.insert(migrations, {
filename = p,
seqnum = seqnum,
})
local function throwSqlError(db, prefix)
local code = db:errcode()
if code ~= sqlite3.OK and code ~= sqlite3.ROW and code ~= sqlite3.DONE then
if prefix then
error(string.format("%s: %s", prefix, db:errmsg()))
else
print(string.format("found weird migration name: %s", p))
error(db:errmsg())
end
end
end
local function transact(db, func, errorPrefix)
local name = string.format("savepoint_%d", math.abs(math.random(0)))
db:exec(string.format("savepoint "..name))
throwSqlError(db, errorPrefix)
local success, result = pcall(func)
if not success then
db:exec("rollback to "..name)
error(result, errorPrefix)
end
db:exec("release "..name)
return result
end
local function getCurrentMigration(db)
for seqnum in db:urows("select max(seqnum) from migrations") do return seqnum end
throwSqlError(db, "failed to get last migration sequence number", db)
return -1
end
local function recordMigration(db, seqnum)
local stmt = db:prepare("insert into migrations (seqnum) values (?)")
stmt:bind_values(seqnum)
throwSqlError(db, "failed to record migration")
stmt:step()
end
local function migrate(db)
local migrations = {}
local seqnums = {}
local MIGRATION_PATH = "/migrations/"
local paths = GetZipPaths(MIGRATION_PATH)
for _, p in ipairs(paths) do
-- check that sequence number doesn't already exist
if p ~= MIGRATION_PATH then
local basename = path.basename(p)
local _, seqnum = filenameRegex:search(basename)
seqnum = tonumber(seqnum)
if seqnum and not seqnums[seqnum] then
table.insert(seqnums, seqnum)
table.insert(migrations, {
filename = p,
seqnum = seqnum,
})
else
print(string.format("found weird migration name: %s", p))
end
end
end
db:exec[[
create table if not exists migrations (
seqnum integer primary key
) without rowid
]]
throwSqlError(db, "failed to create migrations table")
table.sort(migrations, function(a, b) return a.seqnum < b.seqnum end)
local announced
for i,mig in ipairs(migrations) do
print(string.format("%d: %s", i, mig.filename))
local seqnum = mig.seqnum
local lastSeqnum = getCurrentMigration(db)
if seqnum > lastSeqnum then
if not announced then
print"Applying migrations:"
announced = true
end
print(mig.filename)
local migsql = LoadAsset(mig.filename)
transact(db, function()
db:exec(migsql)
throwSqlError(db, "migration failed")
recordMigration(db, seqnum)
seqnum = seqnum + 1
end)
end
end
end

View File

@ -0,0 +1,6 @@
create table node (
id INT PRIMARY KEY,
title TEXT NOT NULL,
body TEXT NOT NULL
);
insert into node(title, body) values ("First post", "This is the beginning of a beautiful relationship.");

View File

@ -0,0 +1,9 @@
create table node_type (
id INT PRIMARY KEY,
name TEXT NOT NULL
);
insert into node_type(id, name) values (0, "Basic post");
alter table node
add column node_type_id INT NOT NULL references node_type(id) default 0;