210 lines
No EOL
5.3 KiB
Lua
210 lines
No EOL
5.3 KiB
Lua
-- Database migration management
|
|
local M = {}
|
|
|
|
local database = require('notex.database.init')
|
|
|
|
-- Current database version
|
|
local CURRENT_VERSION = 1
|
|
|
|
-- Migration table and version tracking
|
|
local function create_migration_table()
|
|
local query = [[
|
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
version INTEGER PRIMARY KEY,
|
|
applied_at INTEGER NOT NULL
|
|
)
|
|
]]
|
|
return database.execute(query)
|
|
end
|
|
|
|
-- Get current database version
|
|
local function get_database_version()
|
|
local ok, result = database.execute("SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1")
|
|
|
|
if ok and #result > 0 then
|
|
return result[1].version
|
|
end
|
|
|
|
return 0
|
|
end
|
|
|
|
-- Record migration
|
|
local function record_migration(version)
|
|
local query = [[
|
|
INSERT INTO schema_migrations (version, applied_at)
|
|
VALUES (:version, :applied_at)
|
|
]]
|
|
|
|
return database.execute(query, {
|
|
version = version,
|
|
applied_at = os.time()
|
|
})
|
|
end
|
|
|
|
-- Migration definitions
|
|
local migrations = {
|
|
[1] = {
|
|
description = "Initial schema creation",
|
|
up = function()
|
|
local schema = require('notex.database.schema')
|
|
return schema.init()
|
|
end,
|
|
down = function()
|
|
local queries = {
|
|
"DROP TABLE IF EXISTS properties",
|
|
"DROP TABLE IF EXISTS queries",
|
|
"DROP TABLE IF EXISTS schema_metadata",
|
|
"DROP TABLE IF EXISTS documents"
|
|
}
|
|
|
|
for _, query in ipairs(queries) do
|
|
local ok = database.execute(query)
|
|
if not ok then
|
|
return false, "Failed to drop table in rollback"
|
|
end
|
|
end
|
|
|
|
return true, "Rollback completed successfully"
|
|
end
|
|
}
|
|
}
|
|
|
|
-- Initialize migration system
|
|
function M.init()
|
|
local ok, err = create_migration_table()
|
|
if not ok then
|
|
return false, "Failed to create migration table: " .. err
|
|
end
|
|
|
|
local current_version = get_database_version()
|
|
|
|
if current_version == 0 then
|
|
-- Fresh installation - apply current version
|
|
return M.migrate_to(CURRENT_VERSION)
|
|
end
|
|
|
|
return true, string.format("Database at version %d", current_version)
|
|
end
|
|
|
|
-- Migrate to specific version
|
|
function M.migrate_to(target_version)
|
|
local current_version = get_database_version()
|
|
|
|
if target_version < current_version then
|
|
return false, "Downgrade migrations not supported"
|
|
end
|
|
|
|
if target_version > CURRENT_VERSION then
|
|
return false, string.format("Target version %d exceeds maximum version %d", target_version, CURRENT_VERSION)
|
|
end
|
|
|
|
-- Apply migrations sequentially
|
|
for version = current_version + 1, target_version do
|
|
if not migrations[version] then
|
|
return false, string.format("Migration %d not found", version)
|
|
end
|
|
|
|
local migration = migrations[version]
|
|
|
|
print(string.format("Applying migration %d: %s", version, migration.description))
|
|
|
|
local ok, err = migration.up()
|
|
if not ok then
|
|
return false, string.format("Migration %d failed: %s", version, err)
|
|
end
|
|
|
|
local record_ok, record_err = record_migration(version)
|
|
if not record_ok then
|
|
return false, string.format("Failed to record migration %d: %s", version, record_err)
|
|
end
|
|
end
|
|
|
|
return true, string.format("Migrated to version %d successfully", target_version)
|
|
end
|
|
|
|
-- Get migration status
|
|
function M.status()
|
|
local current_version = get_database_version()
|
|
local pending_migrations = {}
|
|
|
|
for version = current_version + 1, CURRENT_VERSION do
|
|
if migrations[version] then
|
|
table.insert(pending_migrations, {
|
|
version = version,
|
|
description = migrations[version].description
|
|
})
|
|
end
|
|
end
|
|
|
|
return {
|
|
current_version = current_version,
|
|
latest_version = CURRENT_VERSION,
|
|
pending_migrations = pending_migrations,
|
|
needs_migration = #pending_migrations > 0
|
|
}
|
|
end
|
|
|
|
-- Get list of all migrations
|
|
function M.list()
|
|
local migration_list = {}
|
|
|
|
for version, migration in pairs(migrations) do
|
|
table.insert(migration_list, {
|
|
version = version,
|
|
description = migration.description,
|
|
applied = version <= get_database_version()
|
|
})
|
|
end
|
|
|
|
table.sort(migration_list, function(a, b) return a.version < b.version end)
|
|
|
|
return migration_list
|
|
end
|
|
|
|
-- Validate database schema
|
|
function M.validate()
|
|
local status = M.status()
|
|
|
|
if status.needs_migration then
|
|
return false, string.format("Database needs migration from version %d to %d",
|
|
status.current_version, status.latest_version)
|
|
end
|
|
|
|
-- Check if all required tables exist
|
|
local tables = { "documents", "properties", "queries", "schema_metadata", "schema_migrations" }
|
|
|
|
for _, table_name in ipairs(tables) do
|
|
local ok, result = database.execute(
|
|
"SELECT name FROM sqlite_master WHERE type='table' AND name=:table_name",
|
|
{ table_name = table_name }
|
|
)
|
|
|
|
if not ok or #result == 0 then
|
|
return false, string.format("Required table '%s' not found", table_name)
|
|
end
|
|
end
|
|
|
|
return true, "Database schema is valid"
|
|
end
|
|
|
|
-- Reset database (for development/testing only)
|
|
function M.reset()
|
|
local queries = {
|
|
"DROP TABLE IF EXISTS properties",
|
|
"DROP TABLE IF EXISTS queries",
|
|
"DROP TABLE IF EXISTS schema_metadata",
|
|
"DROP TABLE IF EXISTS documents",
|
|
"DROP TABLE IF EXISTS schema_migrations"
|
|
}
|
|
|
|
for _, query in ipairs(queries) do
|
|
local ok = database.execute(query)
|
|
if not ok then
|
|
return false, "Failed to reset database"
|
|
end
|
|
end
|
|
|
|
return M.migrate_to(CURRENT_VERSION)
|
|
end
|
|
|
|
return M |