Add ShadowJar compatibility and Query file can be define name with comment

Add Migration and Query definition class
Add Docker DB for tests
This commit is contained in:
2020-05-10 21:45:50 +02:00
parent 4c73ef1e0f
commit 8a452a6897
18 changed files with 949 additions and 97 deletions

View File

@@ -1,7 +1,10 @@
package fr.postgresjson.connexion
import java.io.File
import fr.postgresjson.utils.searchSqlFiles
import java.net.URI
import fr.postgresjson.definition.Function as DefinitionFunction
import fr.postgresjson.definition.Function as FunctionDefinition
import fr.postgresjson.definition.Query as QueryDefinition
class Requester(
private val connection: Connection,
@@ -13,17 +16,19 @@ class Requester(
return this
}
fun addQuery(query: QueryDefinition): Requester = addQuery(query.name, query.script)
fun addQuery(name: String, sql: String): Requester {
addQuery(Query(name, sql, connection))
return this
}
fun addQuery(queriesDirectory: File): Requester {
queriesDirectory.walk()
.filter { it.isFile && it.extension == "sql" }
fun addQuery(queriesDirectory: URI): Requester {
queriesDirectory.searchSqlFiles()
.forEach {
val path = it.parentFile.nameWithoutExtension
addQuery("$path/${it.nameWithoutExtension}", it.readText())
if (it is QueryDefinition) {
addQuery(it)
}
}
return this
}
@@ -44,11 +49,12 @@ class Requester(
return this
}
fun addFunction(functionsDirectory: File): Requester {
functionsDirectory.walk()
.filter { it.isFile && it.extension == "sql" }
fun addFunction(functionsDirectory: URI): Requester {
functionsDirectory.searchSqlFiles()
.forEach {
addFunction(it.readText())
if (it is FunctionDefinition) {
addFunction(it)
}
}
return this
}
@@ -69,8 +75,8 @@ class Requester(
class RequesterFactory(
private val connection: Connection,
private val queriesDirectory: File? = null,
private val functionsDirectory: File? = null
private val queriesDirectory: URI? = null,
private val functionsDirectory: URI? = null
) {
constructor(
host: String = "localhost",
@@ -78,8 +84,8 @@ class Requester(
database: String = "dc-project",
username: String = "dc-project",
password: String = "dc-project",
queriesDirectory: File? = null,
functionsDirectory: File? = null
queriesDirectory: URI? = null,
functionsDirectory: URI? = null
) : this(
Connection(host = host, port = port, database = database, username = username, password = password),
queriesDirectory,

View File

@@ -1,14 +1,15 @@
package fr.postgresjson.definition
import java.io.File
import java.nio.file.Path
open class Function(
override val script: String
class Function(
override val script: String,
override var source: Path? = null
) : Resource, ParametersInterface {
val returns: String
override val name: String
override val parameters: List<Parameter>
override var source: File? = null
init {
val functionRegex =
@@ -46,8 +47,7 @@ open class Function(
}
}
abstract class ParseException(message: String, cause: Throwable? = null) : Exception(message, cause)
class FunctionNotFound(cause: Throwable? = null) : ParseException("Function not found in script", cause)
class FunctionNotFound(cause: Throwable? = null) : Resource.ParseException("Function not found in script", cause)
fun getDefinition(): String {
return parameters

View File

@@ -0,0 +1,35 @@
package fr.postgresjson.definition
import java.nio.file.Path
class Migration(
override val script: String,
source: Path
) : Resource {
override val name: String
val direction: Direction
override var source: Path? = null
init {
this.source = source
this.direction = source.fileName.toString()
.let {
when {
it.endsWith(".down.sql") -> Direction.DOWN
it.endsWith(".up.sql") -> Direction.UP
else -> throw MigrationNotFound()
}
}
this.name = source.fileName.toString()
.substringAfterLast("/")
.let {
when (direction) {
Direction.DOWN -> it.substringBefore(".down.sql")
Direction.UP -> it.substringBefore(".up.sql")
}
}
}
class MigrationNotFound(cause: Throwable? = null) : Resource.ParseException("Migration not found in script", cause)
enum class Direction { UP, DOWN }
}

View File

@@ -0,0 +1,25 @@
package fr.postgresjson.definition
import java.nio.file.Path
class Query(
override val script: String,
source: Path
) : Resource {
override var source: Path? = source
override val name: String = getNameFromComment(script) ?: getNameFromFile(source)
/** Try to get name from comment in file */
private fun getNameFromComment(script: String): String? =
"""-- *name ?: ?(?<name>[^ \n]+)"""
.toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
.find(script)?.let {
it.groups["name"]?.value?.trim()
}
/** Try to get name from the filename */
private fun getNameFromFile(source: Path): String = source
.fileName.toString()
.substringAfterLast("/")
.substringBeforeLast(".sql")
}

View File

@@ -1,11 +1,38 @@
package fr.postgresjson.definition
import java.io.File
import java.net.URL
import java.nio.file.Path
interface Resource {
val name: String
val script: String
var source: File?
var source: Path?
open class ParseException(message: String, cause: Throwable? = null) : Exception(message, cause)
companion object {
fun build(file: File): Resource =
build(file.readText(), Path.of(file.toURI()))
fun build(url: URL): Resource =
build(url.readText(), Path.of(url.toURI()))
fun build(resource: String, path: Path): Resource =
try {
Function(resource, path)
} catch (e: ParseException) {
try {
Migration(resource, path)
} catch (e: ParseException) {
try {
Query(resource, path)
} catch (e: ParseException) {
throw ParseException("No SQL resource found")
}
}
}
}
}
interface ResourceCollection {

View File

@@ -2,14 +2,15 @@ package fr.postgresjson.migration
import com.fasterxml.jackson.core.type.TypeReference
import fr.postgresjson.connexion.Connection
import fr.postgresjson.definition.Function.FunctionNotFound
import fr.postgresjson.definition.Migration as DefinitionMigration
import fr.postgresjson.entity.mutable.Entity
import fr.postgresjson.migration.Migration.Action
import fr.postgresjson.migration.Migration.Status
import fr.postgresjson.utils.LoggerDelegate
import fr.postgresjson.utils.searchSqlFiles
import org.slf4j.Logger
import java.io.File
import java.io.FileNotFoundException
import java.net.URI
import java.util.*
import fr.postgresjson.definition.Function as DefinitionFunction
@@ -35,27 +36,27 @@ interface Migration {
data class Migrations private constructor(
private val connection: Connection,
private val queries: MutableMap<String, Query> = mutableMapOf(),
private val migrationsScripts: MutableMap<String, Query> = mutableMapOf(),
private val functions: MutableMap<String, Function> = mutableMapOf()
) {
private var directories: List<File> = emptyList()
private var directories: List<URI> = emptyList()
private val logger: Logger? by LoggerDelegate()
constructor(directory: File, connection: Connection) : this(listOf(directory), connection)
constructor(directory: URI, connection: Connection) : this(listOf(directory), connection)
constructor(directories: List<File>, connection: Connection) : this(connection) {
constructor(directories: List<URI>, connection: Connection) : this(connection) {
initDB()
this.directories = directories
reset()
}
fun reset() {
queries.clear()
migrationsScripts.clear()
functions.clear()
getMigrationFromDB()
getMigrationFromDirectory(directories)
queries.forEach { (_, query) ->
migrationsScripts.forEach { (_, query) ->
if (query.doExecute === null) {
query.doExecute = Action.DOWN
}
@@ -84,7 +85,7 @@ data class Migrations private constructor(
this::class.java.classLoader.getResource("sql/migration/findAllHistory.sql")!!.readText().let {
connection.select<MigrationEntity>(it, object : TypeReference<List<MigrationEntity>>() {})
.map { query ->
queries[query.filename] = Query(query.filename, query.up, query.down, connection, query.executedAt)
migrationsScripts[query.filename] = Query(query.filename, query.up, query.down, connection, query.executedAt)
}
}
}
@@ -92,7 +93,7 @@ data class Migrations private constructor(
/**
* Get all migration from multiples Directories
*/
private fun getMigrationFromDirectory(directory: List<File>) {
private fun getMigrationFromDirectory(directory: List<URI>) {
directory.forEach {
getMigrationFromDirectory(it)
}
@@ -101,29 +102,26 @@ data class Migrations private constructor(
/**
* Get all migration from Directory
*/
private fun getMigrationFromDirectory(directory: File) {
directory.walk().filter {
it.isFile
}.forEach { file ->
if (file.name.endsWith(".up.sql")) {
file.path.substring(0, file.path.length - 7).let {
try {
val down = File("$it.down.sql").readText()
val up = file.readText()
val name = file.name.substring(0, file.name.length - 7)
addQuery(name, up, down)
} catch (e: FileNotFoundException) {
throw DownMigrationNotDefined("$it.down.sql", e)
}
private fun getMigrationFromDirectory(directory: URI) {
val downs: MutableMap<String, DefinitionMigration> = mutableMapOf()
/* Set Down Migration */
directory.searchSqlFiles().apply {
forEach { migration ->
if (migration is DefinitionMigration && migration.direction == DefinitionMigration.Direction.DOWN) {
downs += migration.name to migration
}
} else if (file.name.endsWith(".down.sql")) {
// Nothing
} else {
val fileContent = file.readText()
try {
addFunction(fileContent)
} catch (e: FunctionNotFound) {
// Nothing
}
/* Set up migrations and functions */
forEach { migration ->
if (migration is DefinitionMigration && migration.direction == DefinitionMigration.Direction.UP) {
val down = downs[migration.name] ?: throw DownMigrationNotDefined(migration.name + ".down.sql")
downs -= migration.name
addQuery(migration, down)
} else if (migration is DefinitionFunction) {
addFunction(migration)
}
}
}
@@ -131,7 +129,7 @@ data class Migrations private constructor(
enum class Direction { UP, DOWN }
internal class DownMigrationNotDefined(path: String, cause: FileNotFoundException) :
internal class DownMigrationNotDefined(path: String, cause: FileNotFoundException? = null) :
Throwable("The file $path whas not found", cause)
fun addFunction(newDefinition: DefinitionFunction, callback: (Function) -> Unit = {}): Migrations {
@@ -155,18 +153,21 @@ data class Migrations private constructor(
return this
}
fun addQuery(up: DefinitionMigration, down: DefinitionMigration, callback: (Query) -> Unit = {}): Migrations =
addQuery(up.name, up.script, down.script, callback)
fun addQuery(name: String, up: String, down: String, callback: (Query) -> Unit = {}): Migrations {
if (queries[name] === null) {
queries[name] = Query(name, up, down, connection).apply {
if (migrationsScripts[name] === null) {
migrationsScripts[name] = Query(name, up, down, connection).apply {
doExecute = Action.UP
}
} else {
queries[name]!!.apply {
migrationsScripts[name]!!.apply {
doExecute = Action.OK
}
}
callback(queries[name]!!)
callback(migrationsScripts[name]!!)
return this
}
@@ -191,7 +192,7 @@ data class Migrations private constructor(
internal fun up(): Map<String, Status> {
val list: MutableMap<String, Status> = mutableMapOf()
queries.forEach {
migrationsScripts.forEach {
it.value.let { query ->
if (query.doExecute == Action.UP) {
query.up().let { status ->
@@ -216,7 +217,7 @@ data class Migrations private constructor(
internal fun down(force: Boolean = false): Map<String, Status> {
val list: MutableMap<String, Status> = mutableMapOf()
queries.forEach {
migrationsScripts.forEach {
it.value.let { query ->
if (query.doExecute == Action.DOWN || force) {
query.down().let { status ->
@@ -297,7 +298,7 @@ data class Migrations private constructor(
}
fun copy(): Migrations {
val queriesCopy = queries.map {
val queriesCopy = migrationsScripts.map {
it.key to it.value.copy()
}.toMap().toMutableMap()

View File

@@ -0,0 +1,46 @@
package fr.postgresjson.utils
import fr.postgresjson.definition.Resource
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.net.URI
import java.net.URL
import java.nio.file.FileSystems
import java.nio.file.FileVisitOption
import java.nio.file.Files
import java.nio.file.Path
import kotlin.streams.asSequence
fun URL.searchSqlFiles() = this.toURI().searchSqlFiles()
fun URI.searchSqlFiles() = sequence<Resource> {
val logger: Logger = LoggerFactory.getLogger("sqlFilesSearch")
val uri: URI = this@searchSqlFiles
if (uri.scheme == "jar") {
val relativePath = uri.toString().substringAfter('!')
FileSystems
.newFileSystem(uri, emptyMap<String, Any>())
.getPath(relativePath)
.walk(5)
.asSequence()
.filter { it.fileName.toString().endsWith(".sql") }
.map { this::class.java.getResource(path.toString()) }
.forEach {
logger.debug(it.toString())
yield(Resource.build(it))
}
} else {
uri
.walk(5)
.asSequence()
.map { it.toFile() }
.filter { it.isFile && it.extension == "sql" }
.forEach {
logger.debug(it.toString())
yield(Resource.build(it))
}
}
}
private fun Path.walk(maxDepth: Int = 2147483647, vararg options: FileVisitOption) = Files.walk(this, maxDepth, *options)
private fun URI.walk(maxDepth: Int = 2147483647, vararg options: FileVisitOption) = Files.walk(Path.of(this), maxDepth, *options)