mirror of https://github.com/waku-org/nwaku.git
139 lines
4.2 KiB
Nim
139 lines
4.2 KiB
Nim
{.push raises: [].}
|
|
|
|
import
|
|
std/[strutils, sequtils, os, algorithm],
|
|
stew/results,
|
|
chronicles
|
|
import
|
|
../sqlite
|
|
|
|
|
|
logScope:
|
|
topics = "sqlite"
|
|
|
|
|
|
## Migration scripts
|
|
|
|
proc getMigrationScriptVersion(path: string): DatabaseResult[int64] =
|
|
let name = extractFilename(path)
|
|
let parts = name.split("_", 1)
|
|
|
|
try:
|
|
let version = parseInt(parts[0])
|
|
return ok(version)
|
|
except ValueError:
|
|
return err("failed to parse file version: " & name)
|
|
|
|
proc isSqlScript(path: string): bool =
|
|
path.toLower().endsWith(".sql")
|
|
|
|
|
|
proc listSqlScripts(path: string): DatabaseResult[seq[string]] =
|
|
var scripts = newSeq[string]()
|
|
|
|
try:
|
|
for scriptPath in walkDirRec(path):
|
|
if isSqlScript(scriptPath):
|
|
scripts.add(scriptPath)
|
|
else:
|
|
debug "invalid migration script", file=scriptPath
|
|
except OSError:
|
|
return err("failed to list migration scripts: " & getCurrentExceptionMsg())
|
|
|
|
ok(scripts)
|
|
|
|
|
|
proc filterMigrationScripts(paths: seq[string], lowVersion, highVersion: int64, direction: string = "up"): seq[string] =
|
|
## Returns migration scripts whose version fall between lowVersion and highVersion (inclusive)
|
|
let filterPredicate = proc(script: string): bool =
|
|
if not isSqlScript(script):
|
|
return false
|
|
|
|
if direction != "" and not script.toLower().endsWith("." & direction & ".sql"):
|
|
return false
|
|
|
|
let scriptVersionRes = getMigrationScriptVersion(script)
|
|
if scriptVersionRes.isErr():
|
|
return false
|
|
|
|
let scriptVersion = scriptVersionRes.value
|
|
return lowVersion < scriptVersion and scriptVersion <= highVersion
|
|
|
|
paths.filter(filterPredicate)
|
|
|
|
|
|
proc sortMigrationScripts(paths: seq[string]): seq[string] =
|
|
## Sort migration scripts paths alphabetically
|
|
paths.sorted(system.cmp[string])
|
|
|
|
|
|
proc loadMigrationScripts(paths: seq[string]): DatabaseResult[seq[string]] =
|
|
var loadedScripts = newSeq[string]()
|
|
|
|
for script in paths:
|
|
try:
|
|
loadedScripts.add(readFile(script))
|
|
except OSError, IOError:
|
|
return err("failed to load script '" & script & "': " & getCurrentExceptionMsg())
|
|
|
|
ok(loadedScripts)
|
|
|
|
|
|
proc breakIntoStatements(script: string): seq[string] =
|
|
var statements = newSeq[string]()
|
|
|
|
for chunk in script.split(';'):
|
|
if chunk.strip().isEmptyOrWhitespace():
|
|
continue
|
|
|
|
let statement = chunk.strip() & ";"
|
|
statements.add(statement)
|
|
|
|
statements
|
|
|
|
|
|
proc migrate*(db: SqliteDatabase, targetVersion: int64, migrationsScriptsDir: string): DatabaseResult[void] =
|
|
## Compares the `user_version` of the sqlite database with the provided `targetVersion`, then
|
|
## it runs migration scripts if the `user_version` is outdated. The `migrationScriptsDir` path
|
|
## points to the directory holding the migrations scripts once the db is updated, it sets the
|
|
## `user_version` to the `tragetVersion`.
|
|
##
|
|
## NOTE: Down migration it is not currently supported
|
|
let userVersion = ?db.getUserVersion()
|
|
|
|
if userVersion == targetVersion:
|
|
debug "database schema is up to date", userVersion=userVersion, targetVersion=targetVersion
|
|
return ok()
|
|
|
|
info "database schema is outdated", userVersion=userVersion, targetVersion=targetVersion
|
|
|
|
# Load migration scripts
|
|
var migrationScriptsPaths = ?listSqlScripts(migrationsScriptsDir)
|
|
migrationScriptsPaths = filterMigrationScripts(migrationScriptsPaths, lowVersion=userVersion, highVersion=targetVersion, direction="up")
|
|
migrationScriptsPaths = sortMigrationScripts(migrationScriptsPaths)
|
|
|
|
if migrationScriptsPaths.len <= 0:
|
|
debug "no scripts to be run"
|
|
return ok()
|
|
|
|
let scripts = ?loadMigrationScripts(migrationScriptsPaths)
|
|
|
|
# Run the migration scripts
|
|
for script in scripts:
|
|
|
|
for statement in script.breakIntoStatements():
|
|
debug "executing migration statement", statement=statement
|
|
|
|
let execRes = db.query(statement, NoopRowHandler)
|
|
if execRes.isErr():
|
|
error "failed to execute migration statement", statement=statement, error=execRes.error
|
|
return err("failed to execute migration statement")
|
|
|
|
debug "migration statement executed succesfully", statement=statement
|
|
|
|
# Update user_version
|
|
?db.setUserVersion(targetVersion)
|
|
|
|
debug "database user_version updated", userVersion=targetVersion
|
|
ok()
|