All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ryoframework.persitence.evolutions.LiquibaseRunner.kt Maven / Gradle / Ivy

The newest version!
package org.ryoframework.persitence.evolutions

import liquibase.Liquibase
import liquibase.change.AddColumnConfig
import liquibase.change.Change
import liquibase.change.ColumnConfig
import liquibase.change.ConstraintsConfig
import liquibase.change.core.*
import liquibase.changelog.ChangeSet
import liquibase.changelog.DatabaseChangeLog
import liquibase.database.DatabaseFactory
import liquibase.database.jvm.JdbcConnection
import liquibase.resource.ClassLoaderResourceAccessor
import liquibase.statement.DatabaseFunction
import liquibase.structure.core.Column
import java.sql.Connection
import java.util.*

object LiquibaseRunner {

    fun run(evolution: DbEvolution, connection: Connection) {
        val dbConnection = JdbcConnection(connection)
        val database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(dbConnection)
        val changeLog = DatabaseChangeLog()
        val lq = Liquibase(changeLog, ClassLoaderResourceAccessor(), database)
        evolution.changeSets.forEach {
            addChangeSet(changeLog, it)
        }
        if (evolution.changeSets.isEmpty()) {
            throw Error("NO_MIGRATIONS_FOUND")
        }
        database.connection = dbConnection
        lq.update("")
    }

    private fun addChangeSet(dbCL: DatabaseChangeLog, cs: DbEvolution.ChangeSet) {
        val changeSet = ChangeSet("${cs.timestamp}", cs.author, false, false, "",
            cs.contextList, cs.dbmsList, true, dbCL)

        cs.changes.flatMap { change ->
            when (change) {
                is DbEvolution.CreateTable -> createChanges(change)
                is DbEvolution.AlterTable -> createChanges(change)
                else -> TODO()
            }
        }.forEach {
            changeSet.addChange(it)
        }

        dbCL.addChangeSet(changeSet)
    }

    private fun createChanges(def: DbEvolution.CreateTable): List {
        return createChanges(def, CreateTableChange())
    }

    // --------------------------------------------------------------------------------------------------------------

    private fun createChanges(def: DbEvolution.AlterTable) = createChanges(def as DbEvolution.TableChange)

    private fun createChanges(def: DbEvolution.TableChange, createTableChange: CreateTableChange? = null): List {
        val table = def.table
        val changes = ChangesHolder()

        if (createTableChange != null) {
            createTableChange.tableName = table
            changes.tables.add(createTableChange)
        }

        if (def is DbEvolution.AlterTable) {
            def.dropColumns.forEach { column ->
                changes.columns.add(DropColumnChange().apply {
                    this.tableName = table
                    this.columnName = column
                })
            }

            def.renameColumns.forEach { column ->
                changes.columns.add(RenameColumnChange().apply {
                    this.tableName = table
                    this.newColumnName = column.second
                    this.oldColumnName = column.first
                })
            }
        }


        def.columns.forEach {
            val columnName = it.name
            if (it.primaryKey) {
                val column = ColumnConfig.fromName(columnName).setAutoIncrement(it.autoIncrement).apply {
                    type = it.type
                    constraints = ConstraintsConfig().apply {
                        isPrimaryKey = true
                        isNullable = false
                    }
                }
                column.remarks = "PRIMARY_KEY"
                if (createTableChange != null) {
                    createTableChange.addColumn(column)
                } else {
                    changes.columns.add(AddColumnChange().apply {
                        this.tableName = table
                        this.addColumn(AddColumnConfig(Column(column)))
                    })
                }
            } else if (it.foreignKey != null) {
                createColumnChange(changes, table, columnName, it.type, it.nullable, true, it.unique, null, createTableChange, "FOREIGN_KEY_COLUMN")
                changes.indices.add(createForeignKeyChange(table, columnName, it.foreignKey!!))
            } else {
                createColumnChange(changes, table, columnName, it.type, it.nullable, it.indexed, it.unique, it.defaultValue, createTableChange)
            }
        }

        def.uniqueConstraints.forEach {
            // createUniqueIndexChange(table, it.split(","))
            changes.indices.add(createUniqueIndexChange(table, it.split(",")))
        }

        changes.raw.addAll(def.rawSql.map {
            RawSQLChange(it).apply {
                this.isSplitStatements = false
            }
        })

        return changes.compute()
    }

    private fun createColumnChange(changes: ChangesHolder, table: String, columnName: String, type: String, nullable: Boolean, indexed: Boolean, unique: Boolean = false, default: Any? = null, createTableChange: CreateTableChange? = null, remarks: String? = null) {
        val column = createColumnChange(columnName, type, nullable, default, unique)
        if (createTableChange != null) {
            createTableChange.addColumn(column)
        } else {
            changes.columns.add(AddColumnChange().apply {
                this.tableName = table
                this.addColumn(AddColumnConfig(Column(column)).apply {
                    this.constraints = column.constraints
                    this.defaultValue = column.defaultValue
                    this.defaultValueDate = column.defaultValueDate
                    this.defaultValueBoolean = column.defaultValueBoolean
                    this.defaultValueComputed = column.defaultValueComputed
                    this.defaultValueNumeric = column.defaultValueNumeric
                })
            })
        }
        if (unique) changes.indices.add(createUniqueIndexChange(table, listOf(columnName)))
        when {
            type.contains("geometry", true) -> changes.indices.add(createIndexChange(table, columnName, "GIST"))
            // type.contains(Regex("hash|json")) -> changes.indices.add(createIndexChange(table, columnName, "GIN"))
            indexed -> changes.indices.add(createIndexChange(table, columnName))
        }
        column.remarks = remarks

    }

    private fun createColumnChange(name: String, type: String, nullable: Boolean = true, defaultValue: Any? = null, unique: Boolean = false): ColumnConfig {
        val c = ColumnConfig.fromName(name)
        c.type = type
        c.constraints = ConstraintsConfig().apply {
            isNullable = nullable
            isUnique = unique
        }
        if (defaultValue != null) {
            if (defaultValue is Date) {
                c.defaultValueDate = defaultValue
            } else if (defaultValue is Number) {
                c.defaultValueNumeric = defaultValue
            } else if (defaultValue is Boolean) {
                c.defaultValueBoolean = defaultValue
            } else if (defaultValue is String) {
                if (defaultValue.contains("(")) {
                    c.defaultValueComputed = DatabaseFunction(defaultValue.trim())
                } else {
                    c.defaultValue = defaultValue
                }
            } else {
                TODO("")
            }
        }
        return c
    }

    private fun createIndexChange(table: String, column: String, method: String? = null): Change {
        return if (method == null) {
            CreateIndexChange().apply {
                this.indexName = "idx_${table}_$column"
                this.tableName = table
                this.addColumn(AddColumnConfig(Column.fromName(column)))
            }
        } else {
            RawSQLChange("CREATE INDEX idx_${table}_$column ON $table USING $method ($column)")
        }
    }

    private fun createForeignKeyChange(baseTable: String, baseColumn: String, target: DbEvolution.ForeignKey): AddForeignKeyConstraintChange {
        val change = AddForeignKeyConstraintChange()
        change.baseTableName = baseTable
        change.baseColumnNames = baseColumn
        change.constraintName = "fk_${baseTable}_${baseColumn}_${target.table}_${target.column}"
        change.referencedTableName = target.table
        change.referencedColumnNames = target.column

        change.onDelete = target.onDelete
        change.onUpdate = target.onUpdate

        return change
    }

    private fun createUniqueIndexChange(table: String, columns: Collection): Change {
        return CreateIndexChange().apply {
            isUnique = true
            this.indexName = "idx_u_${table}_${columns.joinToString("_")}"
            this.tableName = table
            columns.forEach {
                this.addColumn(AddColumnConfig(Column.fromName(it)))
            }
        }
    }


    private class ChangesHolder {
        val tables = arrayListOf()
        val columns = arrayListOf()
        val indices = arrayListOf()
        val raw = arrayListOf()

        fun compute() = tables.plus(columns).plus(indices).plus(raw)
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy