From f48b06b59618750c880e89a1577685a77bd8c3d2 Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Tue, 2 Jul 2019 17:28:52 +0200 Subject: [PATCH] feature: get migrations from DB --- .../fr/postgresjson/migration/Function.kt | 50 ++++-- .../fr/postgresjson/migration/Migrations.kt | 157 +++++++++++++----- .../kotlin/fr/postgresjson/migration/Query.kt | 44 +++-- .../kotlin/fr/postgresjson/MigrationTest.kt | 12 +- src/test/resources/fixtures/down.sql | 5 +- 5 files changed, 191 insertions(+), 77 deletions(-) diff --git a/src/main/kotlin/fr/postgresjson/migration/Function.kt b/src/main/kotlin/fr/postgresjson/migration/Function.kt index c61268c..2aea465 100644 --- a/src/main/kotlin/fr/postgresjson/migration/Function.kt +++ b/src/main/kotlin/fr/postgresjson/migration/Function.kt @@ -1,15 +1,16 @@ package fr.postgresjson.migration import fr.postgresjson.connexion.Connection +import java.util.* import fr.postgresjson.definition.Function as DefinitionFunction class Function( val up: DefinitionFunction, val down: DefinitionFunction, - private val connection: Connection + private val connection: Connection, + override var executedAt: Date? = null ): Migration { val name = up.name - enum class Status(i: Int) { OK(2), UP_FAIL(0), DOWN_FAIL(1) } init { if (up.name !== down.name) { @@ -17,32 +18,51 @@ class Function( } } - override fun up(): Int { + constructor( + up: String, + down: String, + connection: Connection, + executedAt: Date? = null): + this( + DefinitionFunction(up), + DefinitionFunction(down), + connection, + executedAt + ) + + override fun up(): Migration.Status { connection.exec(up.script) - return 1 + // TODO insert to migration Table + return Migration.Status.OK } - override fun down(): Int { + override fun down(): Migration.Status { connection.exec(down.script) - return 1 + // TODO insert to migration Table + return Migration.Status.OK } - override fun test(): Int { + override fun test(): Migration.Status { connection.inTransaction { - connection.exec(up.script) - connection.exec(down.script) + up() + down() it.sendQuery("ROLLBACK"); - } - return 1 + }.join() + + return Migration.Status.OK // TODO } - override fun status(): Int { + override fun status(): Migration.Status { val result = connection.inTransaction { - connection.exec(up.script) - connection.exec(down.script) + up() + down() it.sendQuery("ROLLBACK") }.join() - return result.rowsAffected.toInt() + return Migration.Status.OK // TODO + } + + override fun doExecute(): Boolean { + return executedAt === null } } \ No newline at end of file diff --git a/src/main/kotlin/fr/postgresjson/migration/Migrations.kt b/src/main/kotlin/fr/postgresjson/migration/Migrations.kt index 0297134..d517415 100644 --- a/src/main/kotlin/fr/postgresjson/migration/Migrations.kt +++ b/src/main/kotlin/fr/postgresjson/migration/Migrations.kt @@ -2,24 +2,68 @@ package fr.postgresjson.migration import com.github.jasync.sql.db.util.size import fr.postgresjson.connexion.Connection +import fr.postgresjson.entity.Entity import java.io.File import java.io.FileNotFoundException +import java.util.* import fr.postgresjson.definition.Function as DefinitionFunction +class MigrationEntity( + val filename: String, + val definition: String, + val executedAt: Date, + val up: String, + val down: String, + val version: Int +): Entity(filename) { -interface Migration { - fun up(): Int - fun down(): Int - fun test(): Int - fun status(): Int } -class Migrations(directory: File, private val connection: Connection): Migration { - private val queries: MutableList = mutableListOf() +interface Migration { + var executedAt: Date? + fun up(): Status + fun down(): Status + fun test(): Status + fun status(): Status + fun doExecute(): Boolean + + enum class Status(i: Int) { OK(2), UP_FAIL(0), DOWN_FAIL(1) } +} + +class Migrations(directory: File, private val connection: Connection) { + private val queries: MutableMap = mutableMapOf() private val functions: MutableMap = mutableMapOf() private var initialized = false init { + initDB() + getMigrationFromDB() + getMigrationFromDirectory(directory) + } + + /** + * Get all migration from DB + */ + private fun getMigrationFromDB() { + File(this::class.java.getResource("/sql/migration/findAllFunction.sql").toURI()).let { + connection.select>(it.readText()) + .filterNotNull().map { function -> + functions[function.filename] = Function(function.up, function.down, connection, function.executedAt) + } + } + + File(this::class.java.getResource("/sql/migration/findAllHistory.sql").toURI()).let { + connection.select>(it.readText()) + .filterNotNull().map { query -> + queries[query.filename] = Query(query.filename, query.up, query.down, connection, query.executedAt) + } + } + } + + /** + * Get all migration from Directory + */ + private fun getMigrationFromDirectory(directory: File) { directory.walk().filter { it.isDirectory }.forEach { directory -> @@ -31,7 +75,8 @@ class Migrations(directory: File, private val connection: Connection): Migration try { val down = File("$it.down.sql").readText() val up = file.readText() - addQuery(file.name, up, down) + val name = file.name.substring(0, file.name.size - 7) + addQuery(name, up, down) } catch (e: FileNotFoundException) { throw DownMigrationNotDefined("$it.down.sql", e) } @@ -46,22 +91,25 @@ class Migrations(directory: File, private val connection: Connection): Migration } } + enum class Direction { UP, DOWN } class DownMigrationNotDefined(path: String, cause: FileNotFoundException): Throwable("The file $path whas not found", cause) fun addFunction(definition: DefinitionFunction): Migrations { - functions[definition.name] = Function(definition, definition, connection) - return this - } - - fun addFunction(sql: String): Migrations { - DefinitionFunction(sql).let { - functions[it.name] = Function(it, it, connection) + if (functions[definition.name] === null) { + functions[definition.name] = Function(definition, definition, connection) } return this } + fun addFunction(sql: String): Migrations { + addFunction(DefinitionFunction(sql)) + return this + } + fun addQuery(name: String, up: String, down: String): Migrations { - queries.add(Query(name, up, down, connection)) + if (queries[name] === null) { + queries[name] = Query(name, up, down, connection) + } return this } @@ -77,42 +125,73 @@ class Migrations(directory: File, private val connection: Connection): Migration } } - override fun up(): Int { - initDB() - var count = 0 + fun up(): Map { + val list: MutableMap = mutableMapOf() queries.forEach { - it.up() - ++count + it.value.let { query -> + if (query.doExecute()) { + query.up().let { status -> + list[query.name] = status + } + } + } } - return count - } - - override fun down(): Int { - initDB() - var count = 0 - queries.forEach { - it.down() - ++count + functions.forEach { + it.value.let { function -> + if (function.doExecute()) { + function.up().let { status -> + list[function.name] = status + } + } + } } - return count + return list.toMap() } - override fun test(): Int { - initDB() - var count = 0 + fun down(): Map { + val list: MutableMap = mutableMapOf() + queries.forEach { + it.value.let { query -> + if (query.doExecute()) { + query.down().let { status -> + list[query.name] = status + } + } + } + } + + functions.forEach { + it.value.let { function -> + if (function.doExecute()) { + function.down().let { status -> + list[function.name] = status + } + } + } + } + + return list.toMap() + } + + fun test(): Map, Migration.Status> { + var list: MutableMap, Migration.Status> = mutableMapOf() connection.inTransaction { - count += up() - count += down() + up().map { + list.set(Pair(it.key, Direction.UP), it.value) + } + down().map { + list.set(Pair(it.key, Direction.DOWN), it.value) + } + it.sendQuery("ROLLBACK"); }.join() - return count + return list.toMap() } - override fun status(): Int { - initDB() + fun status(): Map { TODO("not implemented") } } \ No newline at end of file diff --git a/src/main/kotlin/fr/postgresjson/migration/Query.kt b/src/main/kotlin/fr/postgresjson/migration/Query.kt index 94b2405..3924014 100644 --- a/src/main/kotlin/fr/postgresjson/migration/Query.kt +++ b/src/main/kotlin/fr/postgresjson/migration/Query.kt @@ -1,41 +1,51 @@ package fr.postgresjson.migration import fr.postgresjson.connexion.Connection +import fr.postgresjson.entity.Entity +import java.util.* class Query( val name: String, val up: String, val down: String, - private val connection: Connection -): Migration { - enum class Status(i: Int) { OK(2), UP_FAIL(0), DOWN_FAIL(1) } - - override fun up(): Int { + private val connection: Connection, + override var executedAt: Date? = null +): Migration, Entity(name) { + override fun up(): Migration.Status { connection.exec(up) - return 1 + // TODO insert to migration Table + + return Migration.Status.OK } - override fun down(): Int { + override fun down(): Migration.Status { connection.exec(down) - return 1 + // TODO insert to migration Table + + return Migration.Status.OK } - override fun test(): Int { + override fun test(): Migration.Status { connection.inTransaction { - connection.exec(up) - connection.exec(down) + up() + down() it.sendQuery("ROLLBACK"); - } - return 1 + }.join() + + return Migration.Status.OK // TODO } - override fun status(): Int { + override fun status(): Migration.Status { val result = connection.inTransaction { - connection.exec(up) - connection.exec(down) + up() + down() it.sendQuery("ROLLBACK") }.join() - return result.rowsAffected.toInt() + return Migration.Status.OK // TODO + } + + override fun doExecute(): Boolean { + return executedAt === null } } \ No newline at end of file diff --git a/src/test/kotlin/fr/postgresjson/MigrationTest.kt b/src/test/kotlin/fr/postgresjson/MigrationTest.kt index 53a4289..7368d29 100644 --- a/src/test/kotlin/fr/postgresjson/MigrationTest.kt +++ b/src/test/kotlin/fr/postgresjson/MigrationTest.kt @@ -1,7 +1,9 @@ package fr.postgresjson +import fr.postgresjson.migration.Migration import fr.postgresjson.migration.Migrations import org.amshove.kluent.`should be equal to` +import org.amshove.kluent.`should contain` import org.amshove.kluent.invoking import org.amshove.kluent.shouldThrow import org.junit.jupiter.api.Test @@ -14,7 +16,8 @@ class MigrationTest(): TestAbstract() { fun upQuery() { val resources = File(this::class.java.getResource("/sql/migrations").toURI()) val m = Migrations(resources, getConnextion()) - m.up() `should be equal to` 1 + m.up() `should contain` Pair("1", Migration.Status.OK) + m.up().size `should be equal to` 1 } @Test @@ -29,14 +32,15 @@ class MigrationTest(): TestAbstract() { fun downQuery() { val resources = File(this::class.java.getResource("/sql/migrations").toURI()) val m = Migrations(resources, getConnextion()) - m.down() `should be equal to` 1 + m.down() `should contain` Pair("1", Migration.Status.OK) + m.down().size `should be equal to` 1 } @Test fun `test up and down migrations`() { val resources = File(this::class.java.getResource("/sql/real_migrations").toURI()) val m = Migrations(resources, getConnextion()) - m.test() `should be equal to` 2 - m.test() `should be equal to` 2 + m.test().size `should be equal to` 2 + m.test().size `should be equal to` 2 } } \ No newline at end of file diff --git a/src/test/resources/fixtures/down.sql b/src/test/resources/fixtures/down.sql index 2a58ceb..a9d51bc 100644 --- a/src/test/resources/fixtures/down.sql +++ b/src/test/resources/fixtures/down.sql @@ -1,2 +1,3 @@ -drop schema public cascade; -create schema if not exists public; \ No newline at end of file +drop schema if exists public cascade; +create schema if not exists public; +drop schema if exists migration cascade; \ No newline at end of file