-- 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