Merge branch 'base-functionalities' into 'master'
Ajout des fonctionnalitées de bases de l'utilitaire Closes #1, #2, and #3 See merge request color-recipes-explorer/database-manager!1
This commit is contained in:
commit
5e7fd754c0
|
@ -10,3 +10,4 @@ gradlew*
|
|||
build/
|
||||
logs/
|
||||
dokka/
|
||||
workdir/*
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "1.4.30"
|
||||
}
|
||||
|
||||
group = "dev.fyloz.colorrecipesexplorer"
|
||||
version = "1.0"
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "1.4.30"
|
||||
id("com.github.johnrengelman.shadow") version "6.1.0"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
@ -14,9 +15,17 @@ repositories {
|
|||
dependencies {
|
||||
implementation("org.liquibase:liquibase-core:4.3.1")
|
||||
|
||||
testImplementation(kotlin("test-junit5"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.0")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.0")
|
||||
// Logging
|
||||
implementation("io.github.microutils:kotlin-logging:1.12.0")
|
||||
implementation("org.slf4j:slf4j-api:1.7.30")
|
||||
implementation("ch.qos.logback:logback-classic:1.0.13")
|
||||
|
||||
// Database drivers
|
||||
implementation("mysql:mysql-connector-java:8.0.22")
|
||||
|
||||
testImplementation("io.mockk:mockk:1.10.6")
|
||||
testImplementation("io.kotest:kotest-runner-junit5:4.4.1")
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
|
@ -25,4 +34,11 @@ tasks.test {
|
|||
|
||||
tasks.withType<KotlinCompile>() {
|
||||
kotlinOptions.jvmTarget = "11"
|
||||
kotlinOptions.useIR = true
|
||||
}
|
||||
|
||||
tasks.withType<Jar> {
|
||||
manifest {
|
||||
attributes["Main-Class"] = "dev.fyloz.colorrecipesexplorer.databasemanager.DatabaseUpdaterKt"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
|
||||
rootProject.name = "DatabaseUpdater"
|
||||
rootProject.name = "DatabaseManager"
|
||||
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
package dev.fyloz.colorrecipesexplorer.databasemanager
|
||||
|
||||
import liquibase.Contexts
|
||||
import liquibase.Liquibase
|
||||
import liquibase.database.DatabaseFactory
|
||||
import liquibase.database.jvm.JdbcConnection
|
||||
import liquibase.resource.ClassLoaderResourceAccessor
|
||||
import mu.KotlinLogging
|
||||
import org.slf4j.Logger
|
||||
import java.sql.Connection
|
||||
import java.sql.DriverManager
|
||||
|
||||
internal const val DATABASE_VERSION_NOT_INITIALIZED = 0
|
||||
|
||||
internal const val METADATA_TABLE_NAME = "updater_metadata"
|
||||
internal const val COLUMN_KEY_NAME = "metadata_key"
|
||||
internal const val COLUMN_VALUE_NAME = "metadata_value"
|
||||
|
||||
internal const val ROW_VERSION_NAME = "version"
|
||||
|
||||
internal const val QUERY_SELECT_VERSION =
|
||||
"SELECT $COLUMN_VALUE_NAME FROM $METADATA_TABLE_NAME WHERE $COLUMN_KEY_NAME='$ROW_VERSION_NAME'"
|
||||
|
||||
|
||||
/** [CreDatabase]'s context. Contains data needed by the utility like the [properties] and the [logger]. */
|
||||
data class DatabaseContext(
|
||||
val properties: DatabaseUpdaterProperties,
|
||||
val logger: Logger
|
||||
)
|
||||
|
||||
/** DSL for [DatabaseContext]. */
|
||||
fun databaseContext(
|
||||
properties: DatabaseUpdaterProperties,
|
||||
logger: Logger = KotlinLogging.logger { },
|
||||
op: DatabaseContext.() -> Unit = {}
|
||||
) = DatabaseContext(properties, logger).apply(op)
|
||||
|
||||
|
||||
class CreDatabase(
|
||||
context: DatabaseContext
|
||||
) {
|
||||
private val properties = context.properties
|
||||
private val logger = context.logger
|
||||
|
||||
/** Connection to the database server */
|
||||
val serverConnection = tryConnect(properties.url)
|
||||
|
||||
private var databaseConnectionOpen = false
|
||||
|
||||
/** Connection to the database */
|
||||
val databaseConnection by lazy {
|
||||
tryConnect(properties.url + properties.dbName)
|
||||
.apply { databaseConnectionOpen = true }
|
||||
}
|
||||
|
||||
private var databaseOpen = false
|
||||
val database by lazy {
|
||||
DatabaseFactory.getInstance().findCorrectDatabaseImplementation(JdbcConnection(databaseConnection))
|
||||
.apply { databaseOpen = true }
|
||||
}
|
||||
|
||||
/** If the database exists */
|
||||
val exists: Boolean by lazy {
|
||||
with(serverConnection.metaData.catalogs) {
|
||||
while (next()) {
|
||||
if (properties.dbName == getString(1)) {
|
||||
return@with true
|
||||
}
|
||||
}
|
||||
// If no catalog has the name of the targeted database, it does not exist
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/** If the updater's metadata table exists in the database */
|
||||
val metadataTableExists by lazy {
|
||||
with(
|
||||
serverConnection.metaData.getTables(properties.dbName, null, METADATA_TABLE_NAME, arrayOf("TABLE"))
|
||||
) {
|
||||
// If there is no result, the table does not exist
|
||||
this.next()
|
||||
}
|
||||
}
|
||||
|
||||
/** The version of the database */
|
||||
val version by lazy { fetchVersion() }
|
||||
|
||||
/** Updates the database to the target version. */
|
||||
fun update() {
|
||||
val targetVersion = properties.targetVersion
|
||||
|
||||
if (version >= targetVersion) {
|
||||
logger.warn("Database version is superior or equals to the target version. No changes will be made.")
|
||||
return
|
||||
}
|
||||
|
||||
logger.info("Updating database from version $version to $targetVersion...")
|
||||
|
||||
val liquibase =
|
||||
Liquibase("/changelogs/changelog.master.$targetVersion.xml", ClassLoaderResourceAccessor(), database)
|
||||
liquibase.update(Contexts())
|
||||
logger.debug("Update to version $targetVersion completed!")
|
||||
liquibase.close()
|
||||
|
||||
logger.info("Update completed!")
|
||||
}
|
||||
|
||||
/** Gets the database version. */
|
||||
private fun fetchVersion(): Int {
|
||||
fun getVersionFromDatabase(): Int {
|
||||
val statement = databaseConnection.createStatement()
|
||||
val results = statement.executeQuery(QUERY_SELECT_VERSION)
|
||||
|
||||
if (!results.next()) {
|
||||
logger.debug("The version row in the metadata table was not found, assuming version $DATABASE_VERSION_NOT_INITIALIZED")
|
||||
return DATABASE_VERSION_NOT_INITIALIZED
|
||||
}
|
||||
|
||||
return results.getInt(COLUMN_VALUE_NAME)
|
||||
}
|
||||
|
||||
// Check if the database exists
|
||||
if (!exists) {
|
||||
throw CreDatabaseException.UnknownDatabase(properties.dbName)
|
||||
}
|
||||
// Check if the metadata table exists
|
||||
if (!metadataTableExists) {
|
||||
logger.debug("The metadata table was not found, assuming version $DATABASE_VERSION_NOT_INITIALIZED")
|
||||
return DATABASE_VERSION_NOT_INITIALIZED
|
||||
}
|
||||
|
||||
return getVersionFromDatabase()
|
||||
}
|
||||
|
||||
/** Try to create a [Connection] with the given [url]. */
|
||||
private fun tryConnect(url: String): Connection =
|
||||
try {
|
||||
DriverManager.getConnection(url, properties.username, properties.password)
|
||||
} catch (ex: Exception) {
|
||||
throw CreDatabaseException.Connection(url, ex)
|
||||
}
|
||||
|
||||
/** Closes all connections to the database. */
|
||||
fun close() {
|
||||
serverConnection.close()
|
||||
if (databaseConnectionOpen) databaseConnection.close()
|
||||
}
|
||||
}
|
||||
|
||||
sealed class CreDatabaseException(message: String, exception: Exception? = null) : Exception(message, exception) {
|
||||
/** Thrown when an error occurs while creating a connection to the database at the given [url]. */
|
||||
class Connection(val url: String, exception: Exception) :
|
||||
CreDatabaseException("Could not create a connection to the database at '$url'", exception)
|
||||
|
||||
/** Thrown when the database with the given [name] cannot be found. */
|
||||
class UnknownDatabase(val name: String) :
|
||||
CreDatabaseException("Unknown database '$name'")
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package dev.fyloz.colorrecipesexplorer.databasemanager
|
||||
|
||||
import mu.KotlinLogging
|
||||
import java.io.File
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
private const val ACTION_ARG_INDEX = 0
|
||||
private const val PROPERTIES_PATH_ARG_INDEX = 1
|
||||
|
||||
private const val ACTION_VERSION_CHECK = "versionCheck"
|
||||
private const val ACTION_UPDATE = "update"
|
||||
|
||||
private var propertiesPath: String? = null
|
||||
private var database: CreDatabase? = null
|
||||
private val logger = KotlinLogging.logger { }
|
||||
|
||||
// Lazy because propertiesPath is needed to load the properties file
|
||||
private val properties by lazy { loadProperties(propertiesPath) }
|
||||
|
||||
internal fun main(args: Array<String>) {
|
||||
logger.info("Starting Color Recipes Explorer database updater...")
|
||||
|
||||
if (args.isEmpty() || args.size < 2) {
|
||||
logger.error("No enough parameters were specified. ")
|
||||
exitLogUsage()
|
||||
}
|
||||
|
||||
val action = args[ACTION_ARG_INDEX]
|
||||
propertiesPath = args[PROPERTIES_PATH_ARG_INDEX]
|
||||
|
||||
try {
|
||||
database = CreDatabase(DatabaseContext(properties, logger))
|
||||
|
||||
// Run the specified action
|
||||
when (action) {
|
||||
ACTION_VERSION_CHECK -> executeVersionCheck(database!!)
|
||||
ACTION_UPDATE -> executeUpdate(database!!)
|
||||
else -> {
|
||||
logger.error("Invalid action '$action'")
|
||||
exitLogUsage()
|
||||
}
|
||||
}
|
||||
} catch (ex: CreDatabaseException) {
|
||||
if (ex is CreDatabaseException.Connection) {
|
||||
logger.error(ex.message, ex)
|
||||
} else {
|
||||
logger.error(ex.message)
|
||||
}
|
||||
exitError()
|
||||
}
|
||||
|
||||
exit()
|
||||
}
|
||||
|
||||
private fun executeVersionCheck(database: CreDatabase) {
|
||||
logger.info("Performing a version check...")
|
||||
logger.info("Target version: ${properties.targetVersion}")
|
||||
|
||||
val databaseVersion = database.version
|
||||
logger.info("Database version: $databaseVersion")
|
||||
|
||||
when (databaseVersion.compareTo(properties.targetVersion)) {
|
||||
1 -> logger.info("Database version is higher than target version!")
|
||||
0 -> logger.info("Database version matches the target version!")
|
||||
-1 -> logger.info("Database version is lower than target version!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeUpdate(database: CreDatabase) {
|
||||
logger.info("Performing an update...")
|
||||
|
||||
database.update()
|
||||
}
|
||||
|
||||
/** Load properties from the given [path]. */
|
||||
private fun loadProperties(path: String?): DatabaseUpdaterProperties {
|
||||
// Stop the utility if propertiesPath is null
|
||||
if (path == null) {
|
||||
logger.error("No path to a properties file was specified")
|
||||
exitError()
|
||||
}
|
||||
|
||||
val file = File(path)
|
||||
logger.debug("Loading properties from ${file.absolutePath}")
|
||||
try {
|
||||
val properties = databaseUpdaterProperties(file)
|
||||
logger.debug("Loaded properties: $properties")
|
||||
return properties
|
||||
} catch (exception: InvalidDatabaseUpdaterPropertiesException) {
|
||||
logger.error(exception.message)
|
||||
exitError()
|
||||
}
|
||||
}
|
||||
|
||||
/** Exits the utility. */
|
||||
private fun exit(): Nothing {
|
||||
database?.close()
|
||||
logger.info("Exiting Color Recipes Explorer database updater without error")
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
/** Exits the utility with an error code and a warning. */
|
||||
private fun exitError(): Nothing {
|
||||
database?.close()
|
||||
logger.warn("Exiting Color Recipes Explorer database updater with an error")
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
/** Exits the utility and logs how to use it. */
|
||||
private fun exitLogUsage(): Nothing {
|
||||
logger.error("Usage: java -jar DatabaseUpdater.jar <action> <path to properties>")
|
||||
logger.error("Actions: ")
|
||||
logger.error("\t- versionCheck: Compare the database's version to the target version specified in the properties")
|
||||
logger.error("\t- update: Update the database to the target version specified in the properties")
|
||||
exitError()
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package dev.fyloz.colorrecipesexplorer.databasemanager
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.util.*
|
||||
|
||||
/** The latest version of the database supported by the utility. The [DatabaseUpdaterProperties] target version cannot be higher than this. */
|
||||
internal const val LATEST_DATABASE_VERSION = 2
|
||||
|
||||
/** The key of the target version property */
|
||||
internal const val PROPERTY_TARGET_VERSION = "database.target-version"
|
||||
|
||||
/** The key of the database server's url property */
|
||||
internal const val PROPERTY_URL = "database.url"
|
||||
|
||||
/** The key of the database's name property */
|
||||
internal const val PROPERTY_DB_NAME = "database.name"
|
||||
|
||||
/** The key of the database's username property */
|
||||
internal const val PROPERTY_USERNAME = "database.username"
|
||||
|
||||
/** The key of the database's password property */
|
||||
internal const val PROPERTY_PASSWORD = "database.password"
|
||||
|
||||
/** The regular expression to validate a database url */
|
||||
internal val DATABASE_URL_REGEX =
|
||||
Regex("^jdbc:\\w+://(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])(:\\d+)?/\$")
|
||||
|
||||
/** The properties used by the utility */
|
||||
data class DatabaseUpdaterProperties(
|
||||
/** The target version of the database */
|
||||
val targetVersion: Int,
|
||||
/** The url of the database server */
|
||||
val url: String,
|
||||
/** The name of the database */
|
||||
val dbName: String,
|
||||
/** The username of the database's user */
|
||||
val username: String,
|
||||
/** The password of the database's user */
|
||||
val password: String
|
||||
) {
|
||||
init {
|
||||
if (targetVersion < 0 || targetVersion > LATEST_DATABASE_VERSION)
|
||||
throw InvalidDatabaseUpdaterPropertiesException.InvalidTargetVersion(targetVersion)
|
||||
|
||||
if (!url.matches(DATABASE_URL_REGEX))
|
||||
throw InvalidDatabaseUpdaterPropertiesException.InvalidUrl(url)
|
||||
}
|
||||
}
|
||||
|
||||
/** DSL for creating an instance of [DatabaseUpdaterProperties]. */
|
||||
fun databaseUpdaterProperties(
|
||||
targetVersion: Int = 0,
|
||||
url: String = "jdbc:driver://host:1234/",
|
||||
dbName: String = "database",
|
||||
username: String = "user",
|
||||
password: String = "pass",
|
||||
op: DatabaseUpdaterProperties.() -> Unit = {}
|
||||
) = DatabaseUpdaterProperties(targetVersion, url, dbName, username, password).apply(op)
|
||||
|
||||
/** DSL for creating an instance of [DatabaseUpdaterProperties] from the given [properties]. */
|
||||
internal fun databaseUpdaterProperties(
|
||||
properties: Properties,
|
||||
op: DatabaseUpdaterProperties.() -> Unit = {}
|
||||
) = with(properties) {
|
||||
/** Gets a [property] from the given [properties]. Calls exitMissingProperty() when the value is null. */
|
||||
fun property(property: String): String =
|
||||
getProperty(property) ?: throw InvalidDatabaseUpdaterPropertiesException.MissingProperty(property)
|
||||
|
||||
databaseUpdaterProperties(
|
||||
targetVersion = with(property(PROPERTY_TARGET_VERSION)) {
|
||||
toIntOrNull() ?: throw InvalidDatabaseUpdaterPropertiesException.TargetVersionFormat(this)
|
||||
},
|
||||
url = property(PROPERTY_URL),
|
||||
dbName = property(PROPERTY_DB_NAME),
|
||||
username = property(PROPERTY_USERNAME),
|
||||
password = property(PROPERTY_PASSWORD),
|
||||
op
|
||||
)
|
||||
}
|
||||
|
||||
/** DSL for creating an instance of [DatabaseUpdaterProperties] from the given [file]. */
|
||||
internal fun databaseUpdaterProperties(
|
||||
file: File,
|
||||
op: DatabaseUpdaterProperties.() -> Unit = {}
|
||||
) = databaseUpdaterProperties(
|
||||
properties = Properties().apply {
|
||||
if (!file.exists() || !file.isFile)
|
||||
throw InvalidDatabaseUpdaterPropertiesException.PropertiesNotFound(file.absolutePath)
|
||||
|
||||
load(FileInputStream(file))
|
||||
},
|
||||
op
|
||||
)
|
||||
|
||||
/** An exception representing an invalid value in a [DatabaseUpdaterProperties] instance. */
|
||||
internal sealed class InvalidDatabaseUpdaterPropertiesException(message: String) : Exception(message) {
|
||||
/** Thrown when the properties file does not exists. */
|
||||
class PropertiesNotFound(val path: String) :
|
||||
InvalidDatabaseUpdaterPropertiesException("Could not find properties file at '$path'")
|
||||
|
||||
/** Thrown when the target version property cannot be represented as an [Int]. */
|
||||
class TargetVersionFormat(val value: String) :
|
||||
InvalidDatabaseUpdaterPropertiesException("Invalid database target version format for value '$value'")
|
||||
|
||||
/** Thrown when the target version property is not in the range {0, [LATEST_DATABASE_VERSION]}. */
|
||||
class InvalidTargetVersion(val targetVersion: Int) :
|
||||
InvalidDatabaseUpdaterPropertiesException("Invalid database target version '$targetVersion'. The database version must be between 0 and $LATEST_DATABASE_VERSION")
|
||||
|
||||
/** Thrown when the url property does not matches the pattern 'jdbc:{driver}://{host}{:port?}/'. */
|
||||
class InvalidUrl(val url: String) :
|
||||
InvalidDatabaseUpdaterPropertiesException("Invalid database url '$url'. The proper format is: jdbc:{driver}://{host}{:port?}/")
|
||||
|
||||
/** Thrown when the given [property] is not defined. */
|
||||
class MissingProperty(val property: String) :
|
||||
InvalidDatabaseUpdaterPropertiesException("The property $property is not defined")
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
package dev.fyloz.colorrecipesexplorer.databaseupdater
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-3.9.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.9.xsd">
|
||||
<changeSet author="william" id="1">
|
||||
<createTable tableName="company">
|
||||
<column name="id" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="name" type="VARCHAR(50)">
|
||||
<constraints nullable="false" unique="true"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="2">
|
||||
<createTable tableName="material">
|
||||
<column name="id" type="bigint">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="inventory_quantity" type="DOUBLE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="is_mix_type" type="BIT(1)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="name" type="VARCHAR(255)">
|
||||
<constraints nullable="false" unique="true"/>
|
||||
</column>
|
||||
<column name="material_type_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="3">
|
||||
<createTable tableName="material_type">
|
||||
<column name="id" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="name" type="VARCHAR(255)">
|
||||
<constraints nullable="false" unique="true"/>
|
||||
</column>
|
||||
<column name="prefix" type="VARCHAR(255)">
|
||||
<constraints nullable="false" unique="true"/>
|
||||
</column>
|
||||
<column defaultValueBoolean="false" name="use_percentages" type="BIT(1)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="4">
|
||||
<createTable tableName="mix">
|
||||
<column name="id" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="location" type="VARCHAR(255)"/>
|
||||
<column name="mix_type_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="recipe_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="5">
|
||||
<createTable tableName="mix_quantity">
|
||||
<column name="id" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="quantity" type="DOUBLE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="material_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="mix_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="mix" type="BIGINT"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="6">
|
||||
<createTable tableName="mix_type">
|
||||
<column name="id" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="name" type="VARCHAR(255)">
|
||||
<constraints unique="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="material_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="7">
|
||||
<createTable tableName="recipe">
|
||||
<column name="id" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="approbation_date" type="VARCHAR(255)"/>
|
||||
<column name="description" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="name" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="note" type="VARCHAR(255)"/>
|
||||
<column name="remark" type="VARCHAR(255)"/>
|
||||
<column name="sample" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="company_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="8">
|
||||
<createTable tableName="recipe_step">
|
||||
<column name="id" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="message" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="recipe_id" type="BIGINT"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="9">
|
||||
<createTable tableName="updater_metadata">
|
||||
<column name="metadata_key" type="VARCHAR(255)">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="metadata_value" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="10">
|
||||
<insert tableName="updater_metadata">
|
||||
<column name="metadata_key" value="version"/>
|
||||
<column name="metadata_value" value="1"/>
|
||||
</insert>
|
||||
<rollback>
|
||||
<delete tableName="updater_metadata">
|
||||
<where>metadata_key='version'</where>
|
||||
</delete>
|
||||
</rollback>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="william" id="11">
|
||||
<createIndex indexName="ix_mix_quantity_mix" tableName="mix_quantity">
|
||||
<column name="mix"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="12">
|
||||
<createIndex indexName="ix_material_material_type_id" tableName="material">
|
||||
<column name="material_type_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="13">
|
||||
<createIndex indexName="ix_mix_quantity_mix_id" tableName="mix_quantity">
|
||||
<column name="mix_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="14">
|
||||
<createIndex indexName="ix_recipe_company_Id" tableName="recipe">
|
||||
<column name="company_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="15">
|
||||
<createIndex indexName="ix_mix_type_material_id" tableName="mix_type">
|
||||
<column name="material_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="16">
|
||||
<createIndex indexName="ix_mix_recipe_id" tableName="mix">
|
||||
<column name="recipe_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="17">
|
||||
<createIndex indexName="ix_mix_mix_type_id" tableName="mix">
|
||||
<column name="mix_type_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="18">
|
||||
<createIndex indexName="ix_recipe_step_recipe_id" tableName="recipe_step">
|
||||
<column name="recipe_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="19">
|
||||
<createIndex indexName="ix_mix_quantity_material_id" tableName="mix_quantity">
|
||||
<column name="material_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="william" id="20">
|
||||
<addForeignKeyConstraint baseColumnNames="mix" baseTableName="mix_quantity"
|
||||
constraintName="fk_mix_quantity_mix_mix" deferrable="false" initiallyDeferred="false"
|
||||
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
|
||||
referencedTableName="mix" validate="true"/>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="21">
|
||||
<addForeignKeyConstraint baseColumnNames="material_type_id" baseTableName="material"
|
||||
constraintName="fk_material_material_type_material_type_id" deferrable="false"
|
||||
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
|
||||
referencedColumnNames="id" referencedTableName="material_type" validate="true"/>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="22">
|
||||
<addForeignKeyConstraint baseColumnNames="mix_id" baseTableName="mix_quantity"
|
||||
constraintName="fk_mix_quantity_mix_mix_id" deferrable="false"
|
||||
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
|
||||
referencedColumnNames="id" referencedTableName="mix" validate="true"/>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="23">
|
||||
<addForeignKeyConstraint baseColumnNames="company_id" baseTableName="recipe"
|
||||
constraintName="fk_recipe_company_company_id" deferrable="false"
|
||||
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
|
||||
referencedColumnNames="id" referencedTableName="company" validate="true"/>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="24">
|
||||
<addForeignKeyConstraint baseColumnNames="material_id" baseTableName="mix_type"
|
||||
constraintName="fk_mix_type_material_material_id" deferrable="false"
|
||||
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
|
||||
referencedColumnNames="id" referencedTableName="material" validate="true"/>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="25">
|
||||
<addForeignKeyConstraint baseColumnNames="recipe_id" baseTableName="mix"
|
||||
constraintName="fk_mix_recipe_recipe_id" deferrable="false" initiallyDeferred="false"
|
||||
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
|
||||
referencedTableName="recipe" validate="true"/>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="26">
|
||||
<addForeignKeyConstraint baseColumnNames="mix_type_id" baseTableName="mix"
|
||||
constraintName="fk_mix_mix_type_mix_type_id" deferrable="false"
|
||||
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
|
||||
referencedColumnNames="id" referencedTableName="mix_type" validate="true"/>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="27">
|
||||
<addForeignKeyConstraint baseColumnNames="recipe_id" baseTableName="recipe_step"
|
||||
constraintName="fk_recipe_step_recipe_recipe_id" deferrable="false"
|
||||
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
|
||||
referencedColumnNames="id" referencedTableName="recipe" validate="true"/>
|
||||
</changeSet>
|
||||
<changeSet author="william" id="28">
|
||||
<addForeignKeyConstraint baseColumnNames="material_id" baseTableName="mix_quantity"
|
||||
constraintName="fk_mix_quantity_material_material_id" deferrable="false"
|
||||
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
|
||||
referencedColumnNames="id" referencedTableName="material" validate="true"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -0,0 +1,182 @@
|
|||
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-3.9.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.9.xsd">
|
||||
<!-- Employees -->
|
||||
<changeSet id="1" author="william">
|
||||
<createTable tableName="employee_group">
|
||||
<column name="id" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="name" type="VARCHAR(255)">
|
||||
<constraints nullable="false" unique="true"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet id="2" author="william">
|
||||
<createTable tableName="employee">
|
||||
<column name="id" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="first_name" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="last_name" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="password" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="default_group_user" type="BIT(1)" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="system_user" type="BIT(1)" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="group_id" type="BIGINT"/>
|
||||
<column name="last_login_time" type="datetime"/>
|
||||
</createTable>
|
||||
<addForeignKeyConstraint baseTableName="employee" baseColumnNames="group_id"
|
||||
constraintName="fk_employee_employee_group_group_id"
|
||||
referencedTableName="employee_group"
|
||||
referencedColumnNames="id"/>
|
||||
<createIndex tableName="employee" indexName="ix_employee_group_id">
|
||||
<column name="group_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet id="3" author="william">
|
||||
<createTable tableName="employee_permission">
|
||||
<column name="employee_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="permission" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<addForeignKeyConstraint baseTableName="employee_permission" baseColumnNames="employee_id"
|
||||
constraintName="fk_employee_permission_employee_employee_id"
|
||||
referencedTableName="employee"
|
||||
referencedColumnNames="id"/>
|
||||
<createIndex tableName="employee_permission" indexName="ix_employee_permission_employee_id">
|
||||
<column name="employee_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet id="4" author="william">
|
||||
<createTable tableName="group_permission">
|
||||
<column name="group_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="permission" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<addForeignKeyConstraint baseTableName="group_permission" baseColumnNames="group_id"
|
||||
constraintName="fk_group_permission_employee_group_group_id"
|
||||
referencedTableName="employee_group"
|
||||
referencedColumnNames="id"/>
|
||||
<createIndex tableName="group_permission" indexName="ix_group_permission_group_id">
|
||||
<column name="group_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<!-- Modèle -->
|
||||
<changeSet id="5" author="william">
|
||||
<dropForeignKeyConstraint baseTableName="mix_quantity" constraintName="fk_mix_quantity_material_material_id"/>
|
||||
<dropForeignKeyConstraint baseTableName="mix_quantity" constraintName="fk_mix_quantity_mix_mix_id"/>
|
||||
<dropForeignKeyConstraint baseTableName="mix_quantity" constraintName="fk_mix_quantity_mix_mix"/>
|
||||
|
||||
<dropIndex tableName="mix_quantity" indexName="ix_mix_quantity_material_id"/>
|
||||
<dropIndex tableName="mix_quantity" indexName="ix_mix_quantity_mix_id"/>
|
||||
<dropIndex tableName="mix_quantity" indexName="ix_mix_quantity_mix"/>
|
||||
|
||||
<dropColumn tableName="mix_quantity" columnName="mix"/>
|
||||
<renameTable oldTableName="mix_quantity" newTableName="mix_material"/>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="mix_material" baseColumnNames="material_id"
|
||||
constraintName="fk_mix_material_material_material_id"
|
||||
referencedTableName="material"
|
||||
referencedColumnNames="id"/>
|
||||
<addForeignKeyConstraint baseTableName="mix_material" baseColumnNames="mix_id"
|
||||
constraintName="fk_mix_material_mix_mix_id"
|
||||
referencedTableName="mix"
|
||||
referencedColumnNames="id"/>
|
||||
|
||||
<createIndex tableName="mix_material" indexName="ix_mix_material_material_id">
|
||||
<column name="material_id"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="mix_material" indexName="ix_mix_material_mix_id">
|
||||
<column name="mix_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet id="6" author="william">
|
||||
<renameColumn tableName="material" oldColumnName="is_mix_type" newColumnName="mix_type"
|
||||
columnDataType="BIT(10)"/>
|
||||
<modifyDataType tableName="material" columnName="inventory_quantity" newDataType="FLOAT(12)"/>
|
||||
<addNotNullConstraint tableName="material" columnName="inventory_quantity" columnDataType="FLOAT(12)"/>
|
||||
</changeSet>
|
||||
<changeSet id="7" author="william">
|
||||
<modifyDataType tableName="recipe" columnName="approbation_date" newDataType="DATE(10)"/>
|
||||
</changeSet>
|
||||
<changeSet id="8" author="william">
|
||||
<addColumn tableName="material_type">
|
||||
<column name="system_type" type="BIT(1)" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
<changeSet id="9" author="william">
|
||||
<modifyDataType tableName="company" columnName="name" newDataType="VARCHAR(255)"/>
|
||||
<addNotNullConstraint tableName="company" columnName="name" columnDataType="VARCHAR(255)"/>
|
||||
</changeSet>
|
||||
<changeSet id="10" author="william">
|
||||
<dropForeignKeyConstraint baseTableName="mix_type" constraintName="fk_mix_type_material_material_id"/>
|
||||
<dropForeignKeyConstraint baseTableName="mix" constraintName="fk_mix_recipe_recipe_id"/>
|
||||
<dropForeignKeyConstraint baseTableName="mix" constraintName="fk_mix_mix_type_mix_type_id"/>
|
||||
<dropForeignKeyConstraint baseTableName="recipe_step" constraintName="fk_recipe_step_recipe_recipe_id"/>
|
||||
<dropForeignKeyConstraint baseTableName="employee" constraintName="fk_employee_employee_group_group_id"/>
|
||||
<dropForeignKeyConstraint baseTableName="employee_permission" constraintName="fk_employee_permission_employee_employee_id"/>
|
||||
<dropForeignKeyConstraint baseTableName="group_permission" constraintName="fk_group_permission_employee_group_group_id"/>
|
||||
<dropForeignKeyConstraint baseTableName="mix_material" constraintName="fk_mix_material_material_material_id"/>
|
||||
<dropForeignKeyConstraint baseTableName="mix_material" constraintName="fk_mix_material_mix_mix_id"/>
|
||||
<dropForeignKeyConstraint baseTableName="material" constraintName="fk_material_material_type_material_type_id"/>
|
||||
<dropForeignKeyConstraint baseTableName="recipe" constraintName="fk_recipe_company_company_id"/>
|
||||
</changeSet>
|
||||
<changeSet id="11" author="william">
|
||||
<addAutoIncrement tableName="employee" columnName="id" columnDataType="BIGINT"/>
|
||||
<addAutoIncrement tableName="employee_group" columnName="id" columnDataType="BIGINT"/>
|
||||
<addAutoIncrement tableName="company" columnName="id" columnDataType="BIGINT"/>
|
||||
<addAutoIncrement tableName="material" columnName="id" columnDataType="BIGINT"/>
|
||||
<addAutoIncrement tableName="material_type" columnName="id" columnDataType="BIGINT"/>
|
||||
<addAutoIncrement tableName="mix" columnName="id" columnDataType="BIGINT"/>
|
||||
<addAutoIncrement tableName="mix_material" columnName="id" columnDataType="BIGINT"/>
|
||||
<addAutoIncrement tableName="mix_type" columnName="id" columnDataType="BIGINT"/>
|
||||
<addAutoIncrement tableName="recipe" columnName="id" columnDataType="BIGINT"/>
|
||||
<addAutoIncrement tableName="recipe_step" columnName="id" columnDataType="BIGINT"/>
|
||||
</changeSet>
|
||||
<changeSet id="12" author="william">
|
||||
<addForeignKeyConstraint baseTableName="mix_type" baseColumnNames="material_id" constraintName="fk_mix_type_material_material_id" referencedTableName="material" referencedColumnNames="id"/>
|
||||
<addForeignKeyConstraint baseTableName="mix" baseColumnNames="recipe_id" constraintName="fk_mix_recipe_recipe_id" referencedTableName="recipe" referencedColumnNames="id"/>
|
||||
<addForeignKeyConstraint baseTableName="mix" baseColumnNames="mix_type_id" constraintName="fk_mix_mix_type_mix_type_id" referencedTableName="mix_type" referencedColumnNames="id"/>
|
||||
<addForeignKeyConstraint baseTableName="recipe_step" baseColumnNames="recipe_id" constraintName="fk_recipe_step_recipe_recipe_id" referencedTableName="recipe" referencedColumnNames="id"/>
|
||||
<addForeignKeyConstraint baseTableName="employee" baseColumnNames="group_id" constraintName="fk_employee_employee_group_group_id" referencedTableName="employee_group" referencedColumnNames="id"/>
|
||||
<addForeignKeyConstraint baseTableName="employee_permission" baseColumnNames="employee_id" constraintName="fk_employee_permission_employee_employee_id" referencedTableName="employee" referencedColumnNames="id"/>
|
||||
<addForeignKeyConstraint baseTableName="group_permission" baseColumnNames="group_id" constraintName="fk_group_permission_employee_group_group_id" referencedTableName="employee_group" referencedColumnNames="id"/>
|
||||
<addForeignKeyConstraint baseTableName="mix_material" baseColumnNames="material_id" constraintName="fk_mix_material_material_material_id" referencedTableName="material" referencedColumnNames="id"/>
|
||||
<addForeignKeyConstraint baseTableName="mix_material" baseColumnNames="mix_id" constraintName="fk_mix_material_mix_mix_id" referencedTableName="mix" referencedColumnNames="id"/>
|
||||
<addForeignKeyConstraint baseTableName="material" baseColumnNames="material_type_id" constraintName="fk_material_material_type_material_type_id" referencedTableName="material_type" referencedColumnNames="id"/>
|
||||
<addForeignKeyConstraint baseTableName="recipe" baseColumnNames="company_id" constraintName="fk_recipe_company_company_id" referencedTableName="company" referencedColumnNames="id"/>
|
||||
</changeSet>
|
||||
<changeSet id="13" author="william">
|
||||
<update tableName="updater_metadata">
|
||||
<column name="metadata_value" value="2"/>
|
||||
<where>metadata_key='version'</where>
|
||||
</update>
|
||||
<rollback>
|
||||
<update tableName="updater_metadata">
|
||||
<column name="metadata_value" value="1"/>
|
||||
<where>metadata_key='version'</where>
|
||||
</update>
|
||||
</rollback>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd">
|
||||
<include file="/changelogs/changelog.1.xml"/>
|
||||
</databaseChangeLog>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd">
|
||||
<include file="/changelogs/changelog.1.xml"/>
|
||||
<include file="/changelogs/changelog.2.xml"/>
|
||||
</databaseChangeLog>
|
|
@ -0,0 +1,5 @@
|
|||
url: jdbc:mysql://172.66.1.1/updater
|
||||
username: root
|
||||
password: pass
|
||||
changeLogFile: changelog.master.xml
|
||||
classpath: /home/william/.m2/repository/mysql/mysql-connector-java/8.0.22/mysql-connector-java-8.0.22.jar
|
|
@ -0,0 +1,34 @@
|
|||
<configuration>
|
||||
<statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>DENY</onMatch>
|
||||
<onMismatch>ACCEPT</onMismatch>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{36}): %msg%n%throwable</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>logs/latest.log</file>
|
||||
<append>true</append>
|
||||
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>logs/du-%d{dd-MM-yyyy}.log.zip</fileNamePattern>
|
||||
<maxHistory>10</maxHistory>
|
||||
<totalSizeCap>100MB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
|
||||
<encoder>
|
||||
<pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger.%M - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
</root>
|
||||
</configuration>
|
|
@ -0,0 +1,186 @@
|
|||
package dev.fyloz.colorrecipesexplorer.databasemanager
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.datatest.forAll
|
||||
import io.kotest.core.spec.style.ShouldSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.spyk
|
||||
import org.slf4j.Logger
|
||||
import java.sql.Connection
|
||||
import java.sql.DriverManager
|
||||
import java.sql.SQLException
|
||||
|
||||
class DatabaseTest : ShouldSpec({
|
||||
val logger = mockk<Logger> {
|
||||
every { debug(any()) } answers {}
|
||||
}
|
||||
val properties = databaseUpdaterProperties()
|
||||
val context = databaseContext(properties, logger)
|
||||
|
||||
fun connectionMock(url: String) = every {
|
||||
DriverManager.getConnection(url, properties.username, properties.password)
|
||||
}
|
||||
|
||||
fun serverConnectionMock() = connectionMock(properties.url)
|
||||
fun databaseConnectionMock() = connectionMock(properties.url + properties.dbName)
|
||||
|
||||
context("CreDatabase") {
|
||||
mockkStatic(DriverManager::class.qualifiedName!!) {
|
||||
fun withDatabase(test: CreDatabase.() -> Unit) {
|
||||
serverConnectionMock() returns mockk()
|
||||
CreDatabase(context).test()
|
||||
}
|
||||
|
||||
context("::new") {
|
||||
should("throws Connection exception when the url is invalid") {
|
||||
serverConnectionMock() throws SQLException()
|
||||
|
||||
val exception = shouldThrow<CreDatabaseException.Connection> { CreDatabase(context) }
|
||||
exception.url shouldBe properties.url
|
||||
}
|
||||
|
||||
should("initialize a connection to the database's server") {
|
||||
val connection = mockk<Connection>()
|
||||
serverConnectionMock() returns connection
|
||||
|
||||
val database = CreDatabase(context)
|
||||
database.serverConnection shouldBe connection
|
||||
}
|
||||
}
|
||||
|
||||
context("::databaseConnection") {
|
||||
val databaseConnectionUrl = properties.url + properties.dbName
|
||||
|
||||
should("throws Connection exception when the url is invalid") {
|
||||
withDatabase {
|
||||
databaseConnectionMock() throws SQLException()
|
||||
|
||||
val exception = shouldThrow<CreDatabaseException.Connection> { databaseConnection }
|
||||
exception.url shouldBe databaseConnectionUrl
|
||||
}
|
||||
}
|
||||
|
||||
should("initialize a connection to the database") {
|
||||
withDatabase {
|
||||
val connection = mockk<Connection>()
|
||||
databaseConnectionMock() returns connection
|
||||
|
||||
databaseConnection shouldBe connection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("::exists should be") {
|
||||
fun withDatabaseCatalog(catalog: String?, test: CreDatabase.() -> Unit) {
|
||||
withDatabase {
|
||||
every { serverConnection.metaData } returns mockk {
|
||||
every { catalogs } returns mockk {
|
||||
every { next() } answers { catalog != null } andThen false
|
||||
every { getString(1) } answers { catalog } andThen null
|
||||
}
|
||||
}
|
||||
test()
|
||||
}
|
||||
}
|
||||
|
||||
should("be true when database's catalogs contains database's name") {
|
||||
withDatabaseCatalog(properties.dbName) {
|
||||
exists shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
should("be false when database's catalogs does not contain given database's name") {
|
||||
withDatabaseCatalog("anotherName") {
|
||||
exists shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
should("be false when database's catalogs are empty") {
|
||||
withDatabaseCatalog(null) {
|
||||
exists shouldBe false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("::metadataTableExists should be") {
|
||||
forAll<Boolean>(
|
||||
"true when query returns a table" to true,
|
||||
"false when query returns nothing" to false
|
||||
) { exists ->
|
||||
withDatabase {
|
||||
every { serverConnection.metaData } returns mockk {
|
||||
every {
|
||||
getTables(properties.dbName, null, METADATA_TABLE_NAME, arrayOf("TABLE"))
|
||||
} returns mockk {
|
||||
every { next() } answers { exists } andThen false
|
||||
}
|
||||
}
|
||||
|
||||
metadataTableExists shouldBe exists
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("::version") {
|
||||
fun withMockedDatabase(
|
||||
databaseExists: Boolean,
|
||||
metadataTableExists: Boolean,
|
||||
versionRowExists: Boolean,
|
||||
test: CreDatabase.() -> Unit
|
||||
) {
|
||||
withDatabase {
|
||||
// Mocking lazy properties does not seems to work, so we need to mock everything
|
||||
every { serverConnection.metaData } returns mockk {
|
||||
every { catalogs } returns mockk {
|
||||
every { next() } answers { databaseExists } andThen false
|
||||
every { getString(1) } answers { properties.dbName }
|
||||
}
|
||||
every {
|
||||
getTables(properties.dbName, null, METADATA_TABLE_NAME, arrayOf("TABLE"))
|
||||
} returns mockk {
|
||||
every { next() } returns metadataTableExists
|
||||
}
|
||||
}
|
||||
databaseConnectionMock() returns mockk {
|
||||
every { createStatement() } returns mockk {
|
||||
every { executeQuery(QUERY_SELECT_VERSION) } returns mockk {
|
||||
every { next() } answers { versionRowExists } andThen false
|
||||
every { getInt(COLUMN_VALUE_NAME) } returns properties.targetVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
test()
|
||||
}
|
||||
}
|
||||
|
||||
should("equals database's version") {
|
||||
withMockedDatabase(databaseExists = true, metadataTableExists = true, versionRowExists = true) {
|
||||
version shouldBe properties.targetVersion
|
||||
}
|
||||
}
|
||||
|
||||
should("throw UnknownDatabase exception when database doesn't exists") {
|
||||
withMockedDatabase(databaseExists = false, metadataTableExists = true, versionRowExists = true) {
|
||||
val exception = shouldThrow<CreDatabaseException.UnknownDatabase> { version }
|
||||
exception.name shouldBe properties.dbName
|
||||
}
|
||||
}
|
||||
|
||||
should("be 0 when the metadata table does not exist") {
|
||||
withMockedDatabase(databaseExists = true, metadataTableExists = false, versionRowExists = true) {
|
||||
version shouldBe 0
|
||||
}
|
||||
}
|
||||
|
||||
should("be 0 when the version row does not exist") {
|
||||
withMockedDatabase(databaseExists = true, metadataTableExists = true, versionRowExists = false) {
|
||||
version shouldBe 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,154 @@
|
|||
package dev.fyloz.colorrecipesexplorer.databasemanager
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.datatest.forAll
|
||||
import io.kotest.core.spec.style.ShouldSpec
|
||||
import io.kotest.core.test.TestContext
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class PropertiesTest : ShouldSpec({
|
||||
val targetVersion = 0
|
||||
val url = "jdbc:driver://url:3306/"
|
||||
val dbName = "database"
|
||||
val username = "user"
|
||||
val password = "pass"
|
||||
|
||||
context("database's url regular expression") {
|
||||
context("matches valid urls") {
|
||||
forAll(
|
||||
"jdbc:mysql://host:9999/",
|
||||
"jdbc:h2://host:9999/",
|
||||
"jdbc:mysql://127.0.0.1:9999/",
|
||||
"jdbc:mysql://host:9999/",
|
||||
"jdbc:mysql://host/"
|
||||
) { url ->
|
||||
url.matches(DATABASE_URL_REGEX) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
context("does not match invalid urls") {
|
||||
forAll(
|
||||
"mysql://host:9999/",
|
||||
"jdbc://host:9999/",
|
||||
"jdbc:mysql:host:9999/",
|
||||
"jdbc:mysql/",
|
||||
"host:9999",
|
||||
"jdbc:mysql://host:9999/database",
|
||||
"jdbc:mysql://host"
|
||||
) { url ->
|
||||
url.matches(DATABASE_URL_REGEX) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
should("not matches empty url") {
|
||||
"".matches(DATABASE_URL_REGEX) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
context("DatabaseUpdaterProperties::new throws") {
|
||||
context("InvalidTargetVersion exception with invalid database target versions") {
|
||||
forAll<Int>(
|
||||
"negative" to -1,
|
||||
"too large" to LATEST_DATABASE_VERSION + 1
|
||||
) { targetVersion ->
|
||||
val exception =
|
||||
shouldThrow<InvalidDatabaseUpdaterPropertiesException.InvalidTargetVersion> {
|
||||
DatabaseUpdaterProperties(targetVersion, url, dbName, username, password)
|
||||
}
|
||||
exception.targetVersion shouldBe targetVersion
|
||||
}
|
||||
}
|
||||
|
||||
context("InvalidUrl exception with invalid database urls") {
|
||||
forAll(
|
||||
"mysql://host:9999/",
|
||||
"jdbc:mysql://:9999"
|
||||
) { url ->
|
||||
val exception =
|
||||
shouldThrow<InvalidDatabaseUpdaterPropertiesException.InvalidUrl> {
|
||||
DatabaseUpdaterProperties(targetVersion, url, dbName, username, password)
|
||||
}
|
||||
exception.url shouldBe url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("DatabaseUpdaterProperties DSL from properties") {
|
||||
val properties = mockk<Properties>()
|
||||
every { properties.getProperty(PROPERTY_URL) } returns url
|
||||
every { properties.getProperty(PROPERTY_DB_NAME) } returns dbName
|
||||
every { properties.getProperty(PROPERTY_USERNAME) } returns username
|
||||
every { properties.getProperty(PROPERTY_PASSWORD) } returns password
|
||||
|
||||
fun withTargetVersion(version: String?, test: () -> Unit) {
|
||||
every { properties.getProperty(PROPERTY_TARGET_VERSION) } returns version
|
||||
test()
|
||||
}
|
||||
|
||||
fun withTargetVersion(version: Int, test: () -> Unit) = withTargetVersion(version.toString(), test)
|
||||
|
||||
should("throw TargetVersionFormat with non-numeric target version") {
|
||||
val invalidTargetVersion = "zero"
|
||||
withTargetVersion(invalidTargetVersion) {
|
||||
val exception = shouldThrow<InvalidDatabaseUpdaterPropertiesException.TargetVersionFormat> {
|
||||
databaseUpdaterProperties(properties)
|
||||
}
|
||||
exception.value shouldBe invalidTargetVersion
|
||||
}
|
||||
}
|
||||
|
||||
should("return instance with correct values") {
|
||||
withTargetVersion(targetVersion) {
|
||||
with(databaseUpdaterProperties(properties)) {
|
||||
this.targetVersion shouldBe targetVersion
|
||||
this.url shouldBe url
|
||||
this.dbName shouldBe dbName
|
||||
this.username shouldBe username
|
||||
this.password shouldBe password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
should("throw MissingProperty when a property is not defined") {
|
||||
withTargetVersion(null) {
|
||||
val exception = shouldThrow<InvalidDatabaseUpdaterPropertiesException.MissingProperty> {
|
||||
databaseUpdaterProperties(properties)
|
||||
}
|
||||
exception.property shouldBe PROPERTY_TARGET_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
afterEach {
|
||||
clearMocks(properties)
|
||||
}
|
||||
}
|
||||
|
||||
context("DatabaseUpdaterProperties DSL from file") {
|
||||
val file = mockk<File>()
|
||||
val fileAbsolutePath = "/config/du.properties"
|
||||
every { file.absolutePath } returns fileAbsolutePath
|
||||
|
||||
should("throw PropertiesNotFound when the file does not exists") {
|
||||
every { file.exists() } returns false
|
||||
every { file.isFile } returns true
|
||||
val exception = shouldThrow<InvalidDatabaseUpdaterPropertiesException.PropertiesNotFound> {
|
||||
databaseUpdaterProperties(file)
|
||||
}
|
||||
exception.path shouldBe fileAbsolutePath
|
||||
}
|
||||
|
||||
should("throw PropertiesNotFound when the file is not a file") {
|
||||
every { file.exists() } returns true
|
||||
every { file.isFile } returns false
|
||||
val exception = shouldThrow<InvalidDatabaseUpdaterPropertiesException.PropertiesNotFound> {
|
||||
databaseUpdaterProperties(file)
|
||||
}
|
||||
exception.path shouldBe fileAbsolutePath
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,9 @@
|
|||
package dev.fyloz.colorrecipesexplorer.databasemanager
|
||||
|
||||
import io.kotest.core.config.AbstractProjectConfig
|
||||
import io.kotest.core.spec.IsolationMode
|
||||
import io.kotest.core.test.AssertionMode
|
||||
|
||||
object TestConfig : AbstractProjectConfig() {
|
||||
override val assertionMode = AssertionMode.Warn
|
||||
}
|
Loading…
Reference in New Issue