Add env configuration, bug in SQL parsing for Nim db_migrate.
* The Nim binary now recognizes the following environment and allows them to
override configured values:
- `DATABASE_DRIVER`: overrides the `driver` config value, selects which
kind of database we expect to connect to.
- `MIGRATIONS_DIR`: overrides the `sqlDir` config value, sets the path to
the directory containing the migration SQL files.
- `DATABASE_URL`: overrides the `connectionString` config value, supplies
connection parameters to the database.
- `DBM_LOG_LEVEL`: overrides the `logLevel` config value, sets the logging
level of db_migrate.
* Fixes a bug in the parsing of migration files. Previously the file was split
on `;` characters and chunks that started with `--` were ignored as comments.
This was wrong in the common case where a block starts with a comment but
then contains SQL further. Such a block was being ignored. The new behavior
is to consider each line and build up queries that way.
* Fixes a logging bug when parsing the configuration. If there was an that
prevented the configuration from loading this in turn prevented logging from
being setup correctly, and so the error was not logged. Now errors that may
occur before logging is setup are hard-coded to be logged to STDERR.
* Changes the logic for creating the `migrations` table to check before
performing the query that creates the table. This is to avoid continually
printing messages about skipping this creation when the table already exists
(which is the normal case). This change is PostgreSQL-specific, but of course
the entire tool is currently PostgreSQL-specific.
This commit is contained in:
@@ -3,7 +3,7 @@ apply plugin: 'application'
|
|||||||
apply plugin: 'maven'
|
apply plugin: 'maven'
|
||||||
|
|
||||||
group = 'com.jdblabs'
|
group = 'com.jdblabs'
|
||||||
version = '0.2.4'
|
version = '0.2.5'
|
||||||
|
|
||||||
mainClassName = 'com.jdblabs.dbmigrate.DbMigrate'
|
mainClassName = 'com.jdblabs.dbmigrate.DbMigrate'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Package
|
# Package
|
||||||
|
|
||||||
bin = @["db_migrate"]
|
bin = @["db_migrate"]
|
||||||
version = "0.2.4"
|
version = "0.2.5"
|
||||||
author = "Jonathan Bernard"
|
author = "Jonathan Bernard"
|
||||||
description = "Simple tool to handle database migrations."
|
description = "Simple tool to handle database migrations."
|
||||||
license = "BSD"
|
license = "BSD"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import org.slf4j.LoggerFactory
|
|||||||
|
|
||||||
public class DbMigrate {
|
public class DbMigrate {
|
||||||
|
|
||||||
public static final VERSION = "0.2.4"
|
public static final VERSION = "0.2.5"
|
||||||
|
|
||||||
public static final def DOC = """\
|
public static final def DOC = """\
|
||||||
db-migrate.groovy v${VERSION}
|
db-migrate.groovy v${VERSION}
|
||||||
|
|||||||
@@ -9,7 +9,13 @@ import algorithm, json, times, os, strutils, docopt, db_postgres, sets,
|
|||||||
type
|
type
|
||||||
DbMigrateConfig* = tuple[ driver, sqlDir, connectionString: string, logLevel: Level ]
|
DbMigrateConfig* = tuple[ driver, sqlDir, connectionString: string, logLevel: Level ]
|
||||||
|
|
||||||
proc createMigrationsTable(conn: DbConn): void =
|
proc ensureMigrationsTableExists(conn: DbConn): void =
|
||||||
|
let tableCount = conn.getValue(sql"""
|
||||||
|
SELECT COUNT(*) FROM information_schema.tables
|
||||||
|
WHERE table_name = 'migrations';""")
|
||||||
|
|
||||||
|
if tableCount.strip == "0":
|
||||||
|
info "Creating the migrations table as it does not already exist."
|
||||||
conn.exec(sql("""
|
conn.exec(sql("""
|
||||||
CREATE TABLE IF NOT EXISTS migrations (
|
CREATE TABLE IF NOT EXISTS migrations (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
@@ -24,15 +30,27 @@ proc loadConfig*(filename: string): DbMigrateConfig =
|
|||||||
## Load DbMigrateConfig from a file.
|
## Load DbMigrateConfig from a file.
|
||||||
let cfg = json.parseFile(filename)
|
let cfg = json.parseFile(filename)
|
||||||
|
|
||||||
var logLevel: Level
|
var logLevel: Level = lvlInfo
|
||||||
if cfg.hasKey("logLevel"):
|
if existsEnv("DBM_LOG_LEVEL"):
|
||||||
|
let idx = find(LevelNames, $getEnv("DBM_LOG_LEVEL").toUpper)
|
||||||
|
logLevel = if idx == -1: lvlInfo else: (Level)(idx)
|
||||||
|
elif cfg.hasKey("logLevel"):
|
||||||
let idx = find(LevelNames, cfg["logLevel"].getStr.toUpper)
|
let idx = find(LevelNames, cfg["logLevel"].getStr.toUpper)
|
||||||
logLevel = if idx == -1: lvlInfo else: (Level)(idx)
|
logLevel = if idx == -1: lvlInfo else: (Level)(idx)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
driver: if cfg.hasKey("driver"): cfg["driver"].getStr else: "postres",
|
driver:
|
||||||
sqlDir: if cfg.hasKey("sqlDir"): cfg["sqlDir"].getStr else: "migrations",
|
if existsEnv("DATABASE_DRIVER"): $getEnv("DATABASE_DRIVER")
|
||||||
connectionString: cfg["connectionString"].getStr,
|
elif cfg.hasKey("driver"): cfg["driver"].getStr
|
||||||
|
else: "postres",
|
||||||
|
sqlDir:
|
||||||
|
if existsEnv("MIGRATIONS_DIR"): $getEnv("MIGRATIONS_DIR")
|
||||||
|
elif cfg.hasKey("sqlDir"): cfg["sqlDir"].getStr
|
||||||
|
else: "migrations",
|
||||||
|
connectionString:
|
||||||
|
if existsEnv("DATABASE_URL"): $getEnv("DATABASE_URL")
|
||||||
|
elif cfg.hasKey("connectionString"): cfg["connectionString"].getStr
|
||||||
|
else: "",
|
||||||
logLevel: logLevel)
|
logLevel: logLevel)
|
||||||
|
|
||||||
proc createMigration*(config: DbMigrateConfig, migrationName: string): seq[string] =
|
proc createMigration*(config: DbMigrateConfig, migrationName: string): seq[string] =
|
||||||
@@ -96,10 +114,21 @@ proc diffMigrations*(pgConn: DbConn, config: DbMigrateConfig):
|
|||||||
missing: missingMigrations)
|
missing: missingMigrations)
|
||||||
|
|
||||||
proc readStatements*(filename: string): seq[SqlQuery] =
|
proc readStatements*(filename: string): seq[SqlQuery] =
|
||||||
let migrationSql = filename.readFile
|
result = @[]
|
||||||
result = migrationSql.split(';').
|
var stmt: string = ""
|
||||||
filter(proc(st: string): bool = st.strip.len > 0 and not st.startsWith("--")).
|
|
||||||
map(proc(st: string): SqlQuery = sql(st & ";"))
|
for line in filename.lines:
|
||||||
|
let l = line.strip
|
||||||
|
if l.len == 0 or l.startsWith("--"): continue
|
||||||
|
|
||||||
|
let parts = line.split(';')
|
||||||
|
stmt &= "\n" & parts[0]
|
||||||
|
|
||||||
|
if parts.len > 1:
|
||||||
|
result.add(sql(stmt & ";"))
|
||||||
|
stmt = parts[1] & "";
|
||||||
|
|
||||||
|
if stmt.strip.len > 0: result.add(sql(stmt))
|
||||||
|
|
||||||
proc up*(pgConn: DbConn, config: DbMigrateConfig, toRun: seq[string]): seq[string] =
|
proc up*(pgConn: DbConn, config: DbMigrateConfig, toRun: seq[string]): seq[string] =
|
||||||
var migrationsRun = newSeq[string]()
|
var migrationsRun = newSeq[string]()
|
||||||
@@ -118,7 +147,8 @@ proc up*(pgConn: DbConn, config: DbMigrateConfig, toRun: seq[string]): seq[strin
|
|||||||
let statements = filename.readStatements
|
let statements = filename.readStatements
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for statement in statements: pgConn.exec(statement)
|
for statement in statements:
|
||||||
|
pgConn.exec(statement)
|
||||||
pgConn.exec(sql"INSERT INTO migrations (name) VALUES (?);", migration)
|
pgConn.exec(sql"INSERT INTO migrations (name) VALUES (?);", migration)
|
||||||
except DbError:
|
except DbError:
|
||||||
pgConn.rollbackWithErr "Migration '" & migration & "' failed:\n\t" &
|
pgConn.rollbackWithErr "Migration '" & migration & "' failed:\n\t" &
|
||||||
@@ -182,7 +212,7 @@ Options:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
let args = docopt(doc, version = "db-migrate 0.2.4")
|
let args = docopt(doc, version = "db-migrate 0.2.5")
|
||||||
|
|
||||||
let exitErr = proc(msg: string): void =
|
let exitErr = proc(msg: string): void =
|
||||||
fatal("db_migrate: " & msg)
|
fatal("db_migrate: " & msg)
|
||||||
@@ -197,9 +227,10 @@ Options:
|
|||||||
try:
|
try:
|
||||||
config = loadConfig(configFilename)
|
config = loadConfig(configFilename)
|
||||||
except IOError:
|
except IOError:
|
||||||
exitErr "Cannot open config file: " & configFilename
|
writeLine(stderr, "db_migrate: Cannot open config file: " & configFilename)
|
||||||
except:
|
except:
|
||||||
exitErr "Error parsing config file: " & configFilename & "\L\t" & getCurrentExceptionMsg()
|
writeLine(stderr, "db_migrate: Error parsing config file: " &
|
||||||
|
configFilename & "\L\t" & getCurrentExceptionMsg())
|
||||||
|
|
||||||
|
|
||||||
logging.addHandler(newConsoleLogger())
|
logging.addHandler(newConsoleLogger())
|
||||||
@@ -233,7 +264,7 @@ Options:
|
|||||||
exitErr "Unable to connect to the database: " & getCurrentExceptionMsg()
|
exitErr "Unable to connect to the database: " & getCurrentExceptionMsg()
|
||||||
(DbConn)(nil)
|
(DbConn)(nil)
|
||||||
|
|
||||||
pgConn.createMigrationsTable
|
pgConn.ensureMigrationsTableExists
|
||||||
|
|
||||||
let (run, notRun, missing) = diffMigrations(pgConn, config)
|
let (run, notRun, missing) = diffMigrations(pgConn, config)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user