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

migratedb.v1.integrationtest.util.dsl.internal.DatabaseImpl.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2022-2024 The MigrateDB contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package migratedb.v1.integrationtest.util.dsl.internal

import migratedb.v1.core.api.Checksum
import migratedb.v1.core.api.MigrationType
import migratedb.v1.core.api.Version
import migratedb.v1.core.api.configuration.Configuration
import migratedb.v1.core.api.configuration.FluentConfiguration
import migratedb.v1.core.api.internal.database.base.Database
import migratedb.v1.core.api.internal.jdbc.JdbcConnectionFactory
import migratedb.v1.core.internal.callback.NoopCallbackExecutor
import migratedb.v1.core.internal.jdbc.JdbcConnectionFactoryImpl
import migratedb.v1.core.internal.parser.ParsingContextImpl
import migratedb.v1.core.internal.resolver.MigrationInfoHelper
import migratedb.v1.core.internal.schemahistory.SchemaHistory
import migratedb.v1.core.internal.schemahistory.SchemaHistoryFactory
import migratedb.v1.integrationtest.database.DbSystem
import migratedb.v1.integrationtest.util.base.Names
import migratedb.v1.integrationtest.util.base.SafeIdentifier
import migratedb.v1.integrationtest.util.dsl.DatabaseSpec
import migratedb.v1.integrationtest.util.dsl.Dsl.Companion.checksum
import migratedb.v1.integrationtest.util.dsl.Dsl.Companion.toMigrationName
import migratedb.v1.integrationtest.util.dsl.SchemaHistoryEntry
import migratedb.v1.integrationtest.util.dsl.SchemaHistorySpec
import migratedb.v1.testing.util.base.tryAll
import java.sql.Connection
import javax.sql.DataSource

class DatabaseImpl(
    private val databaseInstance: DbSystem.Instance
) : DatabaseSpec, AutoCloseable {
    private var namespace: SafeIdentifier = Names.nextNamespace()
    private var schemaHistory: SchemaHistorySpecImpl? = null
    private var database: Database? = null
    private var schemaHistoryTable: String? = null
    private var initializer: ((Connection) -> Unit)? = null
    private var connectionFactoryToClose: JdbcConnectionFactoryImpl? = null

    override fun schemaHistory(table: String?, block: (SchemaHistorySpec).() -> Unit) {
        this.schemaHistoryTable = table
        this.schemaHistory = SchemaHistorySpecImpl().also(block)
    }

    override fun initializedBy(block: (Connection) -> Unit) {
        initializer = block
    }

    data class MaterializeResult(
        val namespace: SafeIdentifier,
        val adminDataSource: DataSource,
        val database: Database,
        val schemaName: SafeIdentifier?,
        val initializer: ((Connection) -> Unit)?
    )

    fun materialize(): MaterializeResult {
        val schemaName = databaseInstance.createNamespaceIfNotExists(namespace)?.let(databaseInstance::normalizeCase)
        val dataSource = databaseInstance.newAdminConnection(namespace)
        val configuration = FluentConfiguration().also {
            if (schemaName != null) it.schemas(schemaName.toString())
            if (schemaHistoryTable != null) it.table(schemaHistoryTable)
        }
        val db = JdbcConnectionFactoryImpl(dataSource::getConnection, configuration).let { connectionFactory ->
            connectionFactoryToClose = connectionFactory
            databaseInstance.type.createDatabase(configuration, connectionFactory).also {
                database = it
            }
        }

        schemaHistory?.materializeInto(db, configuration)
        return MaterializeResult(
            namespace = namespace,
            adminDataSource = dataSource,
            database = db,
            schemaName = schemaName,
            initializer = initializer
        )
    }

    override fun close() {
        tryAll(
            { database?.close() },
            { databaseInstance.dropNamespaceIfExists(namespace) },
            { connectionFactoryToClose?.close() }
        )
    }

    inner class SchemaHistorySpecImpl : SchemaHistorySpec {
        private val entries = mutableListOf()

        override fun entry(
            version: Any?,
            description: String,
            type: MigrationType,
            success: Boolean,
            installedRank: Int?,
            checksum: Checksum?
        ) {
            entries.add(
                SchemaHistoryEntry(
                    type = type,
                    success = success,
                    version = when (version) {
                        null -> null
                        is Version -> version
                        else -> Version.parse(version.toString())
                    },
                    description = description,
                    checksum = checksum,
                    installedRank = installedRank,
                )
            )
        }

        override fun entry(name: String, type: MigrationType, success: Boolean, checksumDelta: Int) {
            val nameWithDescription = name.toMigrationName()
            val repeatable = nameWithDescription.startsWith("R")
            val prefix = nameWithDescription.take(1)
            val info = MigrationInfoHelper.extractVersionAndDescription(nameWithDescription, prefix, "__", repeatable)
            entry(
                version = info.version,
                description = info.description,
                type = type,
                success = success,
                checksum = nameWithDescription.checksum(checksumDelta)
            )
        }

        fun materializeInto(database: Database, configuration: Configuration) {
            val schemaHistory = getSchemaHistory(configuration, database)
            schemaHistory.create(false)
            entries.forEach { entry ->
                if (entry.installedRank != null) {
                    schemaHistory.addAppliedMigration(
                        entry.installedRank,
                        entry.version,
                        entry.description,
                        entry.type,
                        "n/a",
                        entry.checksum,
                        0,
                        entry.success
                    )
                } else {
                    schemaHistory.addAppliedMigration(
                        entry.version,
                        entry.description,
                        entry.type,
                        "n/a",
                        entry.checksum,
                        0,
                        entry.success
                    )
                }
            }
        }
    }


    companion object {
        /**
         * Creates a new schema history accessor.
         */
        fun getSchemaHistory(
            configuration: Configuration,
            database: Database
        ): SchemaHistory {
            val schema = SchemaHistoryFactory.scanSchemas(configuration, database).defaultSchema
            val sqlScriptExecutorFactory = database.databaseType.createSqlScriptExecutorFactory(
                DummyConnectionFactory,
                NoopCallbackExecutor.INSTANCE
            )
            val sqlScriptFactory = database.databaseType.createSqlScriptFactory(
                configuration,
                ParsingContextImpl()
            )
            return SchemaHistoryFactory.getSchemaHistory(
                configuration,
                sqlScriptExecutorFactory,
                sqlScriptFactory,
                database,
                schema
            )
        }
    }

    /**
     * Dummy object for use with SchemaHistoryFactory.getSchemaHistory, which requires a connection factory even though
     * its connections are never used...
     */
    private object DummyConnectionFactory : JdbcConnectionFactory {
        private fun wontImplement(): Nothing {
            throw UnsupportedOperationException("This is just a dummy")
        }

        override fun getDatabaseType() = wontImplement()

        override fun getJdbcUrl() = wontImplement()

        override fun getDriverInfo() = wontImplement()

        override fun getProductName() = wontImplement()

        override fun openConnection() = wontImplement()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy