From 6bf2b865922fa49b2af5f8b52bab2be7222a2da6 Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Mon, 11 Apr 2016 21:08:16 -0500 Subject: [PATCH] Groovy impl: added verbosity levels. Tested and fixed create/up/down. --- .../com/jdblabs/dbmigrate/DbMigrate.groovy | 125 +++++++++++++----- src/main/resources/logback.groovy | 17 +++ 2 files changed, 107 insertions(+), 35 deletions(-) create mode 100644 src/main/resources/logback.groovy diff --git a/src/main/groovy/com/jdblabs/dbmigrate/DbMigrate.groovy b/src/main/groovy/com/jdblabs/dbmigrate/DbMigrate.groovy index 6092346..165843c 100644 --- a/src/main/groovy/com/jdblabs/dbmigrate/DbMigrate.groovy +++ b/src/main/groovy/com/jdblabs/dbmigrate/DbMigrate.groovy @@ -2,9 +2,12 @@ package com.jdblabs.dbmigrate import groovy.sql.Sql +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger as LBLogger import com.jdbernard.util.AnsiEscapeCodeSequence as ANSI import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariDataSource +import java.io.FilenameFilter import java.text.SimpleDateFormat import org.docopt.Docopt import org.slf4j.Logger @@ -12,13 +15,12 @@ import org.slf4j.LoggerFactory public class DbMigrate { - public static final VERSION = "0.2.1" + public static final VERSION = "0.2.2" public static final def DOC = """\ db-migrate.groovy v${VERSION} Usage: - db_migrate [options] create db_migrate [options] up [] db_migrate [options] down [] @@ -27,18 +29,12 @@ Usage: db_migrate (-h | --help) Options: - -c --config Use the given configuration file (defaults to "database.properties"). - -q --quiet Suppress log information. - -v --verbose Print detailed log information. - --very-verbose Print very detailed log information. - -V --version Print the tools version information. - -h --help Print this usage information. """ @@ -62,6 +58,19 @@ Options: // TODO: Setup logging & output levels Logger clilog = LoggerFactory.getLogger("db-migrate.cli") + if (opts['--quiet']) { + ((LBLogger) LOGGER).level = Level.ERROR + ((LBLogger) LoggerFactory.getLogger(LBLogger.ROOT_LOGGER_NAME)) + .level = Level.ERROR } + if (opts['--verbose']) { + ((LBLogger) LOGGER).level = Level.DEBUG + ((LBLogger) LoggerFactory.getLogger(LBLogger.ROOT_LOGGER_NAME)) + .level = Level.INFO } + if (opts['--very-verbose']) { + ((LBLogger) LOGGER).level = Level.TRACE + ((LBLogger) LoggerFactory.getLogger(LBLogger.ROOT_LOGGER_NAME)) + .level = Level.DEBUG } + // Load the configuration file def givenCfg = new Properties() File cfgFile @@ -81,35 +90,43 @@ Options: givenCfg.clear() } } // Check for migrations directory - File migrationsDir = new File(givenCfg["migrations.dir"]) + File migrationsDir = new File(givenCfg["migrations.dir"] ?: 'migrations') if (!migrationsDir.exists() || !migrationsDir.isDirectory()) { clilog.error("'{}' does not exist or is not a directory.", migrationsDir.canonicalPath) System.exit(1) } - // Create the datasource - HikariConfig hcfg = new HikariConfig(givenCfg) - HikariDataSource hds = new HikariDataSource(hcfg) - // Instantiate the DbMigrate instance - DbMigrate dbmigrate = new DbMigrate(new Sql(hds)) + DbMigrate dbmigrate = new DbMigrate(migrationsDir: migrationsDir) - // Execute the appropriate command. + // If we've only been asked to create a new migration, we don't need to + // setup the DB connection. if (opts['create']) { try { List files = dbmigrate.createMigration(opts['']) - clilog.info("Creted new migration files:\n\t${files.name.join('\n\t')}") } + clilog.info("Created new migration files:\n\t${files.name.join('\n\t')}") + return } catch (Exception e) { - clilog.error('Unable to create migration scripts.', e) } } + clilog.error('Unable to create migration scripts.', e) + System.exit(1) } } - else if (opts['up']) dbmigrate.up(opts['']) + // Create the datasource. + Properties dsProps = new Properties() + dsProps.putAll(givenCfg.findAll { it.key != 'migrations.dir' }) + + HikariDataSource hds = new HikariDataSource(new HikariConfig(dsProps)) + + dbmigrate.sql = new Sql(hds) + + // Execute the appropriate command. + if (opts['up']) dbmigrate.up(opts['']) else if (opts['down']) dbmigrate.down(opts[''] ?: 1) } - public File createMigration(String migrationName) { + public List createMigration(String migrationName) { String timestamp = sdf.format(new Date()) File upFile = new File(migrationsDir, "$timestamp-$migrationName-up.sql") - File downFiles = new File(migrationsDir, "$timestamp-$migrationName-down.sql") + File downFile = new File(migrationsDir, "$timestamp-$migrationName-down.sql") upFile.text = "-- UP script for $migrationName ($timestamp)" downFile.text = "-- DOWN script for $migrationName ($timestamp)" @@ -117,6 +134,8 @@ Options: return [upFile, downFile] } public def createMigrationsTable() { + LOGGER.trace('Checking for the existence of the migrations table and ' + + 'creating it if it does not exist.') sql.execute(''' CREATE TABLE IF NOT EXISTS migrations ( id SERIAL PRIMARY KEY, @@ -126,12 +145,14 @@ CREATE TABLE IF NOT EXISTS migrations ( public def diffMigrations() { def results = [notRun: [], missing: []] + LOGGER.trace('Diffing migrations...') results.run = sql.rows('SELECT name FROM migrations ORDER BY name') .collect { it.name }.sort() SortedSet available = new TreeSet<>() - migrationsDir.eachFile { f -> - available << f.name.replaceAll(/-(up|down).sql$/, '') } + available.addAll(migrationsDir + .listFiles({ d, n -> n ==~ /.+-(up|down).sql$/ } as FilenameFilter) + .collect { f -> f.name.replaceAll(/-(up|down).sql$/, '') }) available.each { migrationName -> if (!results.run.contains(migrationName)) @@ -143,23 +164,45 @@ CREATE TABLE IF NOT EXISTS migrations ( results.missing += reults.notRun results.notRun = [] } } + LOGGER.trace('Migrations diff:\n\trun: {}\n\tnot run: {}\n\tmissing: {}', + results.run, results.notRun, results.missing) + return results } public List up(Integer count = null) { createMigrationsTable() def diff = diffMigrations() - List toRun = count < diff.notRun.size() ? - notRun[0.. toRun + + if (!count || count >= diff.notRun.size()) toRun = diff.notRun + else toRun = diff.notRun[0.. down(Integer count = 1) { + public List down(Integer count = 1) { createMigrationsTable() def diff = diffMigrations() - List toRun = count < diff.notRun.size() ? - notRun.reverse()[0.. toRun = count < diff.run.size() ? + diff.run.reverse()[0.. migrationsRun = [] try { + LOGGER.trace("Beginning transaction.") sql.execute('BEGIN') toRun.each { migrationName -> + LOGGER.info(migrationName) File migrationFile = new File(migrationsDir, "$migrationName-${up ? 'up' : 'down'}.sql") @@ -177,19 +222,29 @@ CREATE TABLE IF NOT EXISTS migrations ( throw new FileNotFoundException(migrationFile.canonicalPath + "does not exist or is not a regular file.") - List statements = migrationFile.text.split(/;/) - .findAll { it.trim().length() > 0 && !it.startsWith('--') } - .collect { "$it;" } + LOGGER.trace('Raw statements:\n\n{}\n', migrationFile.text.split(/;/).join('\n')) - statements.each { sql.execute(it) } - if (up) sql.executeInsert( - 'INSERT INTO migrations (name) VALUES (?);', migrationName) + List statements = migrationFile.text.split(/;/) + .collect { it.replaceAll(/--.*$/, '').trim() } + .findAll { it.length() > 0 } + + LOGGER.trace('Statements:\n\n{}\n', statements.join('\n')) + + statements.each { + LOGGER.trace('Executing SQL: {}', it) + sql.execute(it) } + + if (up) sql.execute( + 'INSERT INTO migrations (name) VALUES (?)', migrationName) else sql.execute( - 'DELETE FROM migrations WHERE name = ?;', migrationName) + 'DELETE FROM migrations WHERE name = ?', migrationName) migrationsRun << migrationName } - sql.execute('COMMIT') } + sql.execute('COMMIT') + LOGGER.info('Went {} {} migrations.', + up ? 'up' : 'down', migrationsRun.size()) } + catch (Exception e) { sql.execute('ROLLBACK'); } return migrationsRun } diff --git a/src/main/resources/logback.groovy b/src/main/resources/logback.groovy new file mode 100644 index 0000000..8f99165 --- /dev/null +++ b/src/main/resources/logback.groovy @@ -0,0 +1,17 @@ +import ch.qos.logback.core.*; +import ch.qos.logback.core.encoder.*; +import ch.qos.logback.core.read.*; +import ch.qos.logback.core.rolling.*; +import ch.qos.logback.core.status.*; +import ch.qos.logback.classic.net.*; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; + + +appender("STDOUT", ConsoleAppender) { + encoder(PatternLayoutEncoder) { + pattern = "db-migrate.groovy: %level - %msg%n" + } +} + +root(WARN, ["STDOUT"]) +logger('com.jdblabs.dbmigrate', INFO)