Run DB migrations on startup
This commit is contained in:
parent
c9f7675308
commit
c7344a9e37
@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
local migrate = require "migrate"
|
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 = require "fullmoon"
|
||||||
fm.setTemplate("hello", "Hello, {%& name %}")
|
fm.setTemplate("hello", "Hello, {%& name %}")
|
||||||
|
@ -3,30 +3,92 @@ local path = require "path"
|
|||||||
|
|
||||||
local filenameRegex = re.compile([=[^([0-9]+).*\.sql$]=])
|
local filenameRegex = re.compile([=[^([0-9]+).*\.sql$]=])
|
||||||
|
|
||||||
local function migrate()
|
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
|
||||||
|
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 migrations = {}
|
||||||
local seqnums = {}
|
local seqnums = {}
|
||||||
local paths = GetZipPaths("/migrations/")
|
local MIGRATION_PATH = "/migrations/"
|
||||||
|
local paths = GetZipPaths(MIGRATION_PATH)
|
||||||
for _, p in ipairs(paths) do
|
for _, p in ipairs(paths) do
|
||||||
-- basename -> sequence number
|
|
||||||
-- check that sequence number doesn't already exist
|
-- check that sequence number doesn't already exist
|
||||||
local basename = path.basename(p)
|
if p ~= MIGRATION_PATH then
|
||||||
local _, seqnum = filenameRegex:search(basename)
|
local basename = path.basename(p)
|
||||||
seqnum = tonumber(seqnum)
|
local _, seqnum = filenameRegex:search(basename)
|
||||||
if seqnum and not seqnums[seqnum] then
|
seqnum = tonumber(seqnum)
|
||||||
table.insert(seqnums, seqnum)
|
if seqnum and not seqnums[seqnum] then
|
||||||
table.insert(migrations, {
|
table.insert(seqnums, seqnum)
|
||||||
filename = p,
|
table.insert(migrations, {
|
||||||
seqnum = seqnum,
|
filename = p,
|
||||||
})
|
seqnum = seqnum,
|
||||||
else
|
})
|
||||||
print(string.format("found weird migration name: %s", p))
|
else
|
||||||
|
print(string.format("found weird migration name: %s", p))
|
||||||
|
end
|
||||||
end
|
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)
|
table.sort(migrations, function(a, b) return a.seqnum < b.seqnum end)
|
||||||
|
local announced
|
||||||
for i,mig in ipairs(migrations) do
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -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.");
|
@ -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;
|
Loading…
Reference in New Issue
Block a user