~aleteoryx/muditaos

ref: master muditaos/scripts/lua/migration/migration.lua -rw-r--r-- 5.7 KiB
2cd0e472 — Lefucjusz [BH-000] Update Harmony 2.10.0 changelog 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
-- Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
-- For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
--- Database migration scripts
-- @module migration
local sqlite3 = require("lsqlite3complete")
local lfs = require("lfs")
local helpers = require("helpers")

local up_script = "up.sql"
local down_script = "down.sql"

migration = {}

--- Return codes
-- @table retcode 
migration.retcode = {
    OK = 0,
    ALREADY_UP_TO_DATE = 1,
    WRONG_TARGET_VERSION = 2
}

local function build_script_array(path, db_name, filename)
    local scripts = {}
    for file in lfs.dir(path .. "/" .. db_name) do
        if file ~= "." and file ~= ".." and tonumber(file) then
            local f = path .. '/' .. db_name .. '/' .. file .. '/' .. filename
            local attr = lfs.attributes(f)
            assert(type(attr) == "table")
            scripts[tonumber(file)] = helpers.read_whole_file(f)
        end
    end
    return scripts
end

local function build_database_path(path, db_name)
    return path .. "/" .. db_name .. ".db"
end

local function db_exec(file, script, version)
    local db = assert(sqlite3.open(file))
    assert(db:exec(script) == sqlite3.OK, string.format("Script execution failed:\n%s\n", script))
    assert(db:exec(string.format("PRAGMA user_version=%u;", version)) == sqlite3.OK,
        string.format("Setting database version: %d failed", version))
    db:close()
end

local function read_db_version(file)
    local db = assert(sqlite3.open(file))
    local stmt = assert(db:prepare("PRAGMA user_version;"))
    local ret = {}
    for v in stmt:urows() do
        ret = v
    end
    db:close()
    return ret
end

local function db_migrate_up(db_path, scripts, target_version)
    local current_version = read_db_version(db_path)

    if current_version == target_version then
        return migration.retcode.ALREADY_UP_TO_DATE
    end

    if current_version > target_version then
        return migration.retcode.WRONG_TARGET_VERSION
    end

    for v = current_version, target_version - 1 do
        db_exec(db_path, scripts[v + 1], v + 1)
    end
    return migration.retcode.OK
end

local function db_migrate_down(db_path, scripts, target_version)
    local current_version = read_db_version(db_path)

    if current_version == target_version then
        return migration.retcode.ALREADY_UP_TO_DATE
    end

    if current_version < target_version then
        return migration.retcode.WRONG_TARGET_VERSION
    end

    for v = current_version, target_version + 1, -1 do
        db_exec(db_path, scripts[v], v - 1)
    end
    return migration.retcode.OK
end

local function print_db_set(db_set)
    print("Database set(name,version):")
    for name, version in pairs(db_set) do
        print(string.format("   '%s':%d", name, version))
    end
end

local function validate_inputs(migration_dir, db_dir)
    assert(helpers.exists(migration_dir), "Migration directory does not exist")
    assert(helpers.exists(db_dir), "Databases directory does not exist")
    return true
end

local function migrate(db_path, scripts_up, scripts_down, target_version)
    local db_version = read_db_version(db_path)

    if db_version > target_version then
        print(string.format("Performing migration-down of '%s' from version %d to %d", db_path, db_version,
            target_version))
        return migration.down(db_path, scripts_down, target_version)
    end
    if db_version < target_version then
        print(
            string.format("Performing migration-up of '%s' from version %d to %d", db_path, db_version, target_version))
        return migration.up(db_path, scripts_up, target_version)
    end

    print(string.format("Migration not needed, '%s' is already the newest version", db_path))
    return migration.retcode.OK
end

--- Perform database migration from lower to higher version
-- @function up
-- @param db_path path to the database we want to perform migration on
-- @param scripts array of {""}
-- @param target_version database version we want to migrate in to
-- @return @{retcode}
function migration.up(db_path, scripts, target_version)
    return db_migrate_up(db_path, scripts, target_version)
end

--- Perform database migration from higher to lower version
-- @function down
-- @param db_path path to the database we want to perform migration on
-- @param scripts array of {""}
-- @param target_version database version we want to migrate in to
-- @return @{retcode}
function migration.down(db_path, scripts, target_version)
    return db_migrate_down(db_path, scripts, target_version)
end

--- Read the database version
-- @function get_db_version
-- @param db_path path to the database
-- @return database version number
function migration.get_db_version(db_path)
    return read_db_version(db_path)
end

--- Perform databases migration(up/down) automatically
-- @function migrate
-- @param db_dir location of the databases
-- @param scripts_dir location of the database migration scripts 
-- @param db_set array of {<"database_name"> = <db_target_version>} entries
-- @return @{retcode}
function migration.migrate(db_dir, scripts_dir, db_set)
    print(string.format("Migration scripts directory: '%s'", scripts_dir))
    print(string.format("Databases directory: '%s'", db_dir))
    print_db_set(db_set)

    for name, version in pairs(db_set) do
        local database_path = build_database_path(db_dir, name)
        local scripts_up = build_script_array(scripts_dir, name, up_script)
        local scripts_down = build_script_array(scripts_dir, name, down_script)
        local ret = migrate(database_path, scripts_up, scripts_down, version)
        if ret ~= migration.retcode.OK then
            return ret
        end
    end

    return migration.retcode.OK
end

return migration