diff --git a/buildSrc/src/main/kotlin/org/jetbrains/exposed/gradle/Detekt.kt b/buildSrc/src/main/kotlin/org/jetbrains/exposed/gradle/Detekt.kt index d8ab2eb573..adb6f25624 100644 --- a/buildSrc/src/main/kotlin/org/jetbrains/exposed/gradle/Detekt.kt +++ b/buildSrc/src/main/kotlin/org/jetbrains/exposed/gradle/Detekt.kt @@ -6,7 +6,6 @@ import org.gradle.api.Project import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure - fun Project.configureDetekt() { apply() @@ -14,7 +13,9 @@ fun Project.configureDetekt() { ignoreFailures = false buildUponDefaultConfig = true config = files( - rootDir.resolve("detekt/detekt-config.yml").takeIf { it.isFile }, + rootDir.resolve("detekt/detekt-config.yml").takeIf { + it.isFile + }, projectDir.resolve("detekt/detekt-config.yml").takeIf { it.isFile } ) reports { diff --git a/buildSrc/src/main/kotlin/org/jetbrains/exposed/gradle/TestDbDsl.kt b/buildSrc/src/main/kotlin/org/jetbrains/exposed/gradle/TestDbDsl.kt index 90e29cf193..20d365da3f 100644 --- a/buildSrc/src/main/kotlin/org/jetbrains/exposed/gradle/TestDbDsl.kt +++ b/buildSrc/src/main/kotlin/org/jetbrains/exposed/gradle/TestDbDsl.kt @@ -8,9 +8,9 @@ import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.getValue import org.gradle.kotlin.dsl.provideDelegate import org.gradle.kotlin.dsl.register -import java.io.File import java.time.Duration +const val HEALTH_TIMEOUT: Long = 60 class TestDb(val name: String) { internal val dialects = mutableListOf() @@ -76,7 +76,7 @@ private fun Project.configureCompose(db: TestDb) { removeVolumes.set(true) stopContainers.set(false) - waitForHealthyStateTimeout.set(Duration.ofMinutes(60)) + waitForHealthyStateTimeout.set(Duration.ofMinutes(HEALTH_TIMEOUT)) } } diff --git a/exposed-core/api/exposed-core.api b/exposed-core/api/exposed-core.api index b22cf9689b..227d22c3c0 100644 --- a/exposed-core/api/exposed-core.api +++ b/exposed-core/api/exposed-core.api @@ -1814,6 +1814,7 @@ public final class org/jetbrains/exposed/sql/SchemaUtils { public static synthetic fun dropSchema$default (Lorg/jetbrains/exposed/sql/SchemaUtils;[Lorg/jetbrains/exposed/sql/Schema;ZZILjava/lang/Object;)V public final fun dropSequence ([Lorg/jetbrains/exposed/sql/Sequence;Z)V public static synthetic fun dropSequence$default (Lorg/jetbrains/exposed/sql/SchemaUtils;[Lorg/jetbrains/exposed/sql/Sequence;ZILjava/lang/Object;)V + public final fun listDatabases ()Ljava/util/List; public final fun setSchema (Lorg/jetbrains/exposed/sql/Schema;Z)V public static synthetic fun setSchema$default (Lorg/jetbrains/exposed/sql/SchemaUtils;Lorg/jetbrains/exposed/sql/Schema;ZILjava/lang/Object;)V public final fun sortTablesByReferences (Ljava/lang/Iterable;)Ljava/util/List; @@ -2889,7 +2890,9 @@ public final class org/jetbrains/exposed/sql/statements/StatementType : java/lan public static final field GRANT Lorg/jetbrains/exposed/sql/statements/StatementType; public static final field INSERT Lorg/jetbrains/exposed/sql/statements/StatementType; public static final field OTHER Lorg/jetbrains/exposed/sql/statements/StatementType; + public static final field PRAGMA Lorg/jetbrains/exposed/sql/statements/StatementType; public static final field SELECT Lorg/jetbrains/exposed/sql/statements/StatementType; + public static final field SHOW Lorg/jetbrains/exposed/sql/statements/StatementType; public static final field TRUNCATE Lorg/jetbrains/exposed/sql/statements/StatementType; public static final field UPDATE Lorg/jetbrains/exposed/sql/statements/StatementType; public final fun getGroup ()Lorg/jetbrains/exposed/sql/statements/StatementGroup; @@ -3245,6 +3248,7 @@ public abstract interface class org/jetbrains/exposed/sql/vendors/DatabaseDialec public abstract fun getSupportsTernaryAffectedRowValues ()Z public abstract fun getSupportsWindowFrameGroupsMode ()Z public abstract fun isAllowedAsColumnDefault (Lorg/jetbrains/exposed/sql/Expression;)Z + public abstract fun listDatabases ()Ljava/lang/String; public abstract fun modifyColumn (Lorg/jetbrains/exposed/sql/Column;Lorg/jetbrains/exposed/sql/ColumnDiff;)Ljava/util/List; public abstract fun resetCaches ()V public abstract fun resetSchemaCaches ()V @@ -3284,6 +3288,7 @@ public final class org/jetbrains/exposed/sql/vendors/DatabaseDialect$DefaultImpl public static fun getSupportsTernaryAffectedRowValues (Lorg/jetbrains/exposed/sql/vendors/DatabaseDialect;)Z public static fun getSupportsWindowFrameGroupsMode (Lorg/jetbrains/exposed/sql/vendors/DatabaseDialect;)Z public static fun isAllowedAsColumnDefault (Lorg/jetbrains/exposed/sql/vendors/DatabaseDialect;Lorg/jetbrains/exposed/sql/Expression;)Z + public static fun listDatabases (Lorg/jetbrains/exposed/sql/vendors/DatabaseDialect;)Ljava/lang/String; public static fun setSchema (Lorg/jetbrains/exposed/sql/vendors/DatabaseDialect;Lorg/jetbrains/exposed/sql/Schema;)Ljava/lang/String; public static fun tableColumns (Lorg/jetbrains/exposed/sql/vendors/DatabaseDialect;[Lorg/jetbrains/exposed/sql/Table;)Ljava/util/Map; } @@ -3458,6 +3463,7 @@ public class org/jetbrains/exposed/sql/vendors/H2Dialect : org/jetbrains/exposed public fun getSupportsWindowFrameGroupsMode ()Z public fun isAllowedAsColumnDefault (Lorg/jetbrains/exposed/sql/Expression;)Z public final fun isSecondVersion ()Z + public fun listDatabases ()Ljava/lang/String; public fun modifyColumn (Lorg/jetbrains/exposed/sql/Column;Lorg/jetbrains/exposed/sql/ColumnDiff;)Ljava/util/List; public fun toString ()Ljava/lang/String; } @@ -3534,6 +3540,7 @@ public class org/jetbrains/exposed/sql/vendors/OracleDialect : org/jetbrains/exp public fun getSupportsOnlyIdentifiersInGeneratedKeys ()Z public fun getSupportsOrderByNullsFirstLast ()Z public fun isAllowedAsColumnDefault (Lorg/jetbrains/exposed/sql/Expression;)Z + public fun listDatabases ()Ljava/lang/String; public fun modifyColumn (Lorg/jetbrains/exposed/sql/Column;Lorg/jetbrains/exposed/sql/ColumnDiff;)Ljava/util/List; public fun setSchema (Lorg/jetbrains/exposed/sql/Schema;)Ljava/lang/String; } @@ -3552,6 +3559,7 @@ public class org/jetbrains/exposed/sql/vendors/PostgreSQLDialect : org/jetbrains public fun getSupportsOrderByNullsFirstLast ()Z public fun getSupportsWindowFrameGroupsMode ()Z public fun isAllowedAsColumnDefault (Lorg/jetbrains/exposed/sql/Expression;)Z + public fun listDatabases ()Ljava/lang/String; public fun modifyColumn (Lorg/jetbrains/exposed/sql/Column;Lorg/jetbrains/exposed/sql/ColumnDiff;)Ljava/util/List; public fun setSchema (Lorg/jetbrains/exposed/sql/Schema;)Ljava/lang/String; } @@ -3617,6 +3625,7 @@ public class org/jetbrains/exposed/sql/vendors/SQLiteDialect : org/jetbrains/exp public fun getSupportsMultipleGeneratedKeys ()Z public fun getSupportsWindowFrameGroupsMode ()Z public fun isAllowedAsColumnDefault (Lorg/jetbrains/exposed/sql/Expression;)Z + public fun listDatabases ()Ljava/lang/String; } public final class org/jetbrains/exposed/sql/vendors/SQLiteDialect$Companion : org/jetbrains/exposed/sql/vendors/VendorDialect$DialectNameProvider { @@ -3665,6 +3674,7 @@ public abstract class org/jetbrains/exposed/sql/vendors/VendorDialect : org/jetb public fun getSupportsTernaryAffectedRowValues ()Z public fun getSupportsWindowFrameGroupsMode ()Z public fun isAllowedAsColumnDefault (Lorg/jetbrains/exposed/sql/Expression;)Z + public fun listDatabases ()Ljava/lang/String; public fun modifyColumn (Lorg/jetbrains/exposed/sql/Column;Lorg/jetbrains/exposed/sql/ColumnDiff;)Ljava/util/List; protected final fun quoteIdentifierWhenWrongCaseOrNecessary (Ljava/lang/String;Lorg/jetbrains/exposed/sql/Transaction;)Ljava/lang/String; public fun resetCaches ()V diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/SchemaUtils.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/SchemaUtils.kt index d726d2a441..29e3eba435 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/SchemaUtils.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/SchemaUtils.kt @@ -91,6 +91,7 @@ object SchemaUtils { } fun sortTablesByReferences(tables: Iterable) = TableDepthGraph(tables).sorted() + fun checkCycle(vararg tables: Table) = TableDepthGraph(tables.toList()).hasCycle() fun createStatements(vararg tables: Table): List { @@ -122,7 +123,9 @@ object SchemaUtils { @Deprecated( "Will be removed in upcoming releases. Please use overloaded version instead", - ReplaceWith("createFKey(checkNotNull(reference.foreignKey) { \"${"$"}reference does not reference anything\" })"), + ReplaceWith( + "createFKey(checkNotNull(reference.foreignKey) { \"${"$"}reference does not reference anything\" })" + ), DeprecationLevel.HIDDEN ) fun createFKey(reference: Column<*>): List { @@ -135,9 +138,13 @@ object SchemaUtils { fun createFKey(foreignKey: ForeignKeyConstraint): List = with(foreignKey) { val allFromColumnsBelongsToTheSameTable = from.all { it.table == fromTable } - require(allFromColumnsBelongsToTheSameTable) { "not all referencing columns of $foreignKey belong to the same table" } + require( + allFromColumnsBelongsToTheSameTable + ) { "not all referencing columns of $foreignKey belong to the same table" } val allTargetColumnsBelongToTheSameTable = target.all { it.table == targetTable } - require(allTargetColumnsBelongToTheSameTable) { "not all referenced columns of $foreignKey belong to the same table" } + require( + allTargetColumnsBelongToTheSameTable + ) { "not all referenced columns of $foreignKey belong to the same table" } require(from.size == target.size) { "$foreignKey referencing columns are not in accordance with referenced" } require(deleteRule != null || updateRule != null) { "$foreignKey has no reference constraint actions" } require(target.toHashSet().size == target.size) { "not all referenced columns of $foreignKey are unique" } @@ -255,21 +262,25 @@ object SchemaUtils { if (dbSupportsAlterTableWithAddColumn) { // create indexes with new columns - table.indices.filter { index -> index.columns.any { missingTableColumns.contains(it) } }.forEach { statements.addAll(createIndex(it)) } + table.indices.filter { index -> + index.columns.any { + missingTableColumns.contains(it) + } + }.forEach { statements.addAll(createIndex(it)) } // sync existing columns val dataTypeProvider = currentDialect.dataTypeProvider val redoColumns = existingTableColumns.mapValues { (col, existingCol) -> - val columnType = col.columnType - val incorrectNullability = existingCol.nullable != columnType.nullable - // Exposed doesn't support changing sequences on columns - val incorrectAutoInc = existingCol.autoIncrement != columnType.isAutoInc && col.autoIncColumnType?.autoincSeq == null - val incorrectDefaults = existingCol.defaultDbValue != col.dbDefaultValue?.let { - dataTypeProvider.dbDefaultToString(col, it) - } - val incorrectCaseSensitiveName = existingCol.name.inProperCase() != col.nameUnquoted().inProperCase() - ColumnDiff(incorrectNullability, incorrectAutoInc, incorrectDefaults, incorrectCaseSensitiveName) - }.filterValues { it.hasDifferences() } + val columnType = col.columnType + val incorrectNullability = existingCol.nullable != columnType.nullable + // Exposed doesn't support changing sequences on columns + val incorrectAutoInc = existingCol.autoIncrement != columnType.isAutoInc && col.autoIncColumnType?.autoincSeq == null + val incorrectDefaults = existingCol.defaultDbValue != col.dbDefaultValue?.let { + dataTypeProvider.dbDefaultToString(col, it) + } + val incorrectCaseSensitiveName = existingCol.name.inProperCase() != col.nameUnquoted().inProperCase() + ColumnDiff(incorrectNullability, incorrectAutoInc, incorrectDefaults, incorrectCaseSensitiveName) + }.filterValues { it.hasDifferences() } redoColumns.flatMapTo(statements) { (col, changedState) -> col.modifyStatements(changedState) } @@ -305,7 +316,14 @@ object SchemaUtils { for ((foreignKey, existingConstraint) in foreignKeyConstraints) { if (existingConstraint == null) { statements.addAll(createFKey(foreignKey)) - } else if (existingConstraint.targetTable != foreignKey.targetTable || foreignKey.deleteRule != existingConstraint.deleteRule || foreignKey.updateRule != existingConstraint.updateRule) { + continue + } + + val noForeignKey = existingConstraint.targetTable != foreignKey.targetTable + val deleteRuleMismatch = foreignKey.deleteRule != existingConstraint.deleteRule + val updateRuleMismatch = foreignKey.updateRule != existingConstraint.updateRule + + if (noForeignKey || deleteRuleMismatch || updateRuleMismatch) { statements.addAll(existingConstraint.dropStatement()) statements.addAll(createFKey(foreignKey)) } @@ -352,7 +370,8 @@ object SchemaUtils { } catch (exception: ExposedSQLException) { if (currentDialect.requiresAutoCommitOnCreateDrop && !transaction.connection.autoCommit) { throw IllegalStateException( - "${currentDialect.name} requires autoCommit to be enabled for CREATE DATABASE", exception + "${currentDialect.name} requires autoCommit to be enabled for CREATE DATABASE", + exception ) } else { throw exception @@ -360,6 +379,24 @@ object SchemaUtils { } } + /** + * Returns a list of all databases. + * + * @return A list of strings representing the names of all databases. + */ + fun listDatabases(): List { + val transaction = TransactionManager.current() + return with(transaction) { + exec(currentDialect.listDatabases()) { + val result = mutableListOf() + while (it.next()) { + result.add(it.getString(1).lowercase()) + } + result + } ?: emptyList() + } + } + /** * Drops databases * @@ -380,7 +417,8 @@ object SchemaUtils { } catch (exception: ExposedSQLException) { if (currentDialect.requiresAutoCommitOnCreateDrop && !transaction.connection.autoCommit) { throw IllegalStateException( - "${currentDialect.name} requires autoCommit to be enabled for DROP DATABASE", exception + "${currentDialect.name} requires autoCommit to be enabled for DROP DATABASE", + exception ) } else { throw exception @@ -424,7 +462,10 @@ object SchemaUtils { } val executedStatements = createStatements + alterStatements logTimeSpent("Checking mapping consistence", withLogs) { - val modifyTablesStatements = checkMappingConsistence(tables = tables, withLogs).filter { it !in executedStatements } + val modifyTablesStatements = checkMappingConsistence( + tables = tables, + withLogs + ).filter { it !in executedStatements } execStatements(inBatch, modifyTablesStatements) commit() } @@ -446,7 +487,10 @@ object SchemaUtils { } val executedStatements = createStatements + alterStatements val modifyTablesStatements = logTimeSpent("Checking mapping consistence", withLogs) { - checkMappingConsistence(tables = tablesToAlter.toTypedArray(), withLogs).filter { it !in executedStatements } + checkMappingConsistence( + tables = tablesToAlter.toTypedArray(), + withLogs + ).filter { it !in executedStatements } } return executedStatements + modifyTablesStatements } @@ -483,12 +527,15 @@ object SchemaUtils { } val excessiveIndices = - currentDialect.existingIndices(*tables).flatMap { it.value }.groupBy { Triple(it.table, it.unique, it.columns.joinToString { it.name }) } + currentDialect.existingIndices(*tables).flatMap { + it.value + }.groupBy { Triple(it.table, it.unique, it.columns.joinToString { it.name }) } .filter { it.value.size > 1 } if (excessiveIndices.isNotEmpty()) { exposedLogger.warn("List of excessive indices:") excessiveIndices.forEach { (triple, indices) -> - exposedLogger.warn("\t\t\t'${triple.first.tableName}'.'${triple.third}' -> ${indices.joinToString(", ") { it.indexName }}") + val indexNames = indices.joinToString(", ") { it.indexName } + exposedLogger.warn("\t\t\t'${triple.first.tableName}'.'${triple.third}' -> $indexNames") } exposedLogger.info("SQL Queries to remove excessive indices:") excessiveIndices.forEach { @@ -543,7 +590,9 @@ object SchemaUtils { nameDiffers.add(mappedIndex) } - notMappedIndices.getOrPut(table.nameInDatabaseCase()) { hashSetOf() }.addAll(existingTableIndices.subtract(mappedIndices)) + notMappedIndices.getOrPut(table.nameInDatabaseCase()) { + hashSetOf() + }.addAll(existingTableIndices.subtract(mappedIndices)) missingIndices.addAll(mappedIndices.subtract(existingTableIndices)) } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt index 462bb5068c..35fcb08b4d 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt @@ -108,8 +108,7 @@ open class Transaction( @Language("sql") stmt: String, args: Iterable> = emptyList(), explicitStatementType: StatementType? = null - ) = - exec(stmt, args, explicitStatementType) { } + ) = exec(stmt, args, explicitStatementType) { } fun exec( @Language("sql") stmt: String, @@ -126,7 +125,7 @@ open class Transaction( return exec(object : Statement(type, emptyList()) { override fun PreparedStatementApi.executeInternal(transaction: Transaction): T? { val result = when (type) { - StatementType.SELECT, StatementType.EXEC -> executeQuery() + StatementType.SELECT, StatementType.EXEC, StatementType.SHOW, StatementType.PRAGMA -> executeQuery() else -> { executeUpdate() resultSet diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/Statement.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/Statement.kt index 677cbf1ed8..3a92361eb4 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/Statement.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/Statement.kt @@ -126,5 +126,6 @@ enum class StatementGroup { enum class StatementType(val group: StatementGroup) { INSERT(StatementGroup.DML), UPDATE(StatementGroup.DML), DELETE(StatementGroup.DML), SELECT(StatementGroup.DML), CREATE(StatementGroup.DDL), ALTER(StatementGroup.DDL), TRUNCATE(StatementGroup.DDL), DROP(StatementGroup.DDL), - GRANT(StatementGroup.DDL), EXEC(StatementGroup.DML), OTHER(StatementGroup.DDL) + GRANT(StatementGroup.DDL), EXEC(StatementGroup.DML), OTHER(StatementGroup.DDL), + SHOW(StatementGroup.DML), PRAGMA(StatementGroup.DML) } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt index d67bb8ed53..80120c60ec 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt @@ -139,6 +139,7 @@ abstract class DataTypeProvider { } else { "'$e'" } + e is LiteralOp<*> -> "$e" e is Function<*> -> "$e" currentDialect is MysqlDialect -> "$e" @@ -980,6 +981,8 @@ interface DatabaseDialect { fun createDatabase(name: String) = "CREATE DATABASE IF NOT EXISTS ${name.inProperCase()}" + fun listDatabases(): String = "SHOW DATABASES" + fun dropDatabase(name: String) = "DROP DATABASE IF EXISTS ${name.inProperCase()}" fun setSchema(schema: Schema): String = "SET SCHEMA ${schema.identifier}" @@ -1221,6 +1224,7 @@ abstract class VendorDialect( .append(" WHERE ").append(it) .toString() } + else -> { exposedLogger.warn("Index creation with a filter condition is not supported in ${currentDialect.name}") return null @@ -1282,6 +1286,7 @@ abstract class VendorDialect( columns = fieldsList, type = index.indexType, filterCondition = maybeFilterCondition ) } + else -> { "CREATE INDEX $quotedIndexName ON $quotedTableName $fieldsList$maybeFilterCondition" } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt index 50a0796d8e..10b3bf01ff 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt @@ -262,6 +262,8 @@ open class H2Dialect : VendorDialect(dialectName, H2DataTypeProvider, H2Function override fun createDatabase(name: String) = "CREATE SCHEMA IF NOT EXISTS ${name.inProperCase()}" + override fun listDatabases(): String = "SHOW SCHEMAS" + override fun modifyColumn(column: Column<*>, columnDiff: ColumnDiff): List = super.modifyColumn(column, columnDiff).map { it.replace("MODIFY COLUMN", "ALTER COLUMN") } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt index 90ec7b6f11..d9dac0d97a 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt @@ -333,6 +333,8 @@ open class OracleDialect : VendorDialect(dialectName, OracleDataTypeProvider, Or override fun createDatabase(name: String): String = "CREATE DATABASE ${name.inProperCase()}" + override fun listDatabases(): String = error("This operation is not supported by Oracle dialect") + override fun dropDatabase(name: String): String = "DROP DATABASE" override fun setSchema(schema: Schema): String = "ALTER SESSION SET CURRENT_SCHEMA = ${schema.identifier}" diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt index 63dc32ab57..5fe5df1574 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt @@ -324,6 +324,8 @@ open class PostgreSQLDialect : VendorDialect(dialectName, PostgreSQLDataTypeProv override fun createDatabase(name: String): String = "CREATE DATABASE ${name.inProperCase()}" + override fun listDatabases(): String = "SELECT datname FROM pg_database" + override fun dropDatabase(name: String): String = "DROP DATABASE ${name.inProperCase()}" override fun setSchema(schema: Schema): String = "SET search_path TO ${schema.identifier}" diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt index cc6f9926d0..481bd73d32 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt @@ -273,6 +273,8 @@ open class SQLiteDialect : VendorDialect(dialectName, SQLiteDataTypeProvider, SQ override fun createDatabase(name: String) = "ATTACH DATABASE '${name.lowercase()}.db' AS ${name.inProperCase()}" + override fun listDatabases(): String = "SELECT name FROM pragma_database_list" + override fun dropDatabase(name: String) = "DETACH DATABASE ${name.inProperCase()}" companion object : DialectNameProvider("sqlite") { diff --git a/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaTimeDefaultsTest.kt b/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaTimeDefaultsTest.kt index e63120cf7e..f28ddc9134 100644 --- a/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaTimeDefaultsTest.kt +++ b/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaTimeDefaultsTest.kt @@ -47,7 +47,9 @@ class JodaTimeDefaultsTest : JodaTimeBaseTest() { val clientDefault by TableWithDBDefault.clientDefault override fun equals(other: Any?): Boolean { - return (other as? DBDefault)?.let { id == it.id && field == it.field && equalDateTime(t1, it.t1) && equalDateTime(t2, it.t2) } ?: false + return (other as? DBDefault)?.let { + id == it.id && field == it.field && equalDateTime(t1, it.t1) && equalDateTime(t2, it.t2) + } ?: false } override fun hashCode(): Int = id.value.hashCode() @@ -170,6 +172,7 @@ class JodaTimeDefaultsTest : JodaTimeBaseTest() { fun Expression<*>.itOrNull() = when { currentDialectTest.isAllowedAsColumnDefault(this) -> "DEFAULT ${currentDialectTest.dataTypeProvider.processForDefaultValue(this)} NOT NULL" + else -> "NULL" } @@ -191,7 +194,10 @@ class JodaTimeDefaultsTest : JodaTimeBaseTest() { ")" val expected = if (currentDialectTest is OracleDialect || currentDialectTest.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) { - arrayListOf("CREATE SEQUENCE t_id_seq START WITH 1 MINVALUE 1 MAXVALUE 9223372036854775807", baseExpression) + arrayListOf( + "CREATE SEQUENCE t_id_seq START WITH 1 MINVALUE 1 MAXVALUE 9223372036854775807", + baseExpression + ) } else { arrayListOf(baseExpression) } @@ -276,13 +282,13 @@ class JodaTimeDefaultsTest : JodaTimeBaseTest() { } @Test - fun defaultCurrentDateTimeTest() { + fun testDefaultCurrentDateTime() { val testDate = object : IntIdTable("TestDate") { val time = datetime("time").defaultExpression(CurrentDateTime) } withTables(testDate) { - val duration: Long = 2_00 + val duration: Long = 1000 val before = currentDateTime() Thread.sleep(duration) @@ -378,6 +384,7 @@ class JodaTimeDefaultsTest : JodaTimeBaseTest() { fun Expression<*>.itOrNull() = when { currentDialectTest.isAllowedAsColumnDefault(this) -> "DEFAULT ${currentDialectTest.dataTypeProvider.processForDefaultValue(this)} NOT NULL" + else -> "NULL" } diff --git a/exposed-tests/src/main/kotlin/org/jetbrains/exposed/sql/tests/DatabaseTestsBase.kt b/exposed-tests/src/main/kotlin/org/jetbrains/exposed/sql/tests/DatabaseTestsBase.kt index 37de15868e..dbc523a21e 100644 --- a/exposed-tests/src/main/kotlin/org/jetbrains/exposed/sql/tests/DatabaseTestsBase.kt +++ b/exposed-tests/src/main/kotlin/org/jetbrains/exposed/sql/tests/DatabaseTestsBase.kt @@ -17,12 +17,12 @@ import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.junit.runners.Parameterized.Parameters import java.math.BigDecimal -import java.sql.SQLException import java.util.* import kotlin.concurrent.thread val TEST_DIALECTS: HashSet = System.getProperty( - "exposed.test.dialects", "" + "exposed.test.dialects", + "" ).split(",").mapTo(HashSet()) { it.trim().uppercase() } private val registeredOnShutdown = HashSet() @@ -51,12 +51,11 @@ abstract class DatabaseTestsBase { } } - @Parameterized.Parameter(0) lateinit var container: String @Parameterized.Parameter(1) - lateinit var dialect: Any + lateinit var dialect: TestDB @Parameterized.Parameter(2) lateinit var testName: String @@ -66,40 +65,42 @@ abstract class DatabaseTestsBase { if (dbSettings !in registeredOnShutdown) { dbSettings.beforeConnection() - Runtime.getRuntime().addShutdownHook(thread(false) { - dbSettings.afterTestFinished() - registeredOnShutdown.remove(dbSettings) - }) + Runtime.getRuntime().addShutdownHook( + thread(false) { + dbSettings.afterTestFinished() + registeredOnShutdown.remove(dbSettings) + } + ) registeredOnShutdown += dbSettings dbSettings.db = dbSettings.connect() } val database = dbSettings.db!! - try { - transaction(database.transactionManager.defaultIsolationLevel, db = database) { - repetitionAttempts = 1 - registerInterceptor(CurrentTestDBInterceptor) - currentTestDB = dbSettings - statement(dbSettings) - } - } catch (cause: SQLException) { - throw cause - } catch (cause: Throwable) { - throw IllegalStateException("Failed on ${dbSettings.name}", cause) + transaction(database.transactionManager.defaultIsolationLevel, db = database) { + repetitionAttempts = 1 + registerInterceptor(CurrentTestDBInterceptor) + currentTestDB = dbSettings + statement(dbSettings) } } fun withDb(db: List? = null, excludeSettings: List = emptyList(), statement: Transaction.(TestDB) -> Unit) { - val enabledInTests = TestDB.enabledDialects() - val toTest = db?.intersect(enabledInTests) ?: (enabledInTests - excludeSettings) - Assume.assumeTrue(toTest.isNotEmpty()) - toTest.forEach { dbSettings -> - @Suppress("TooGenericExceptionCaught") try { - withDb(dbSettings, statement) - } catch (cause: Throwable) { - throw AssertionError("Failed on ${dbSettings.name}", cause) - } + if (db != null && dialect !in db) { + Assume.assumeFalse(true) + return + } + + if (dialect in excludeSettings) { + Assume.assumeFalse(true) + return + } + + if (dialect !in TestDB.enabledDialects()) { + Assume.assumeFalse(true) + return } + + withDb(dialect, statement) } fun withTables(excludeSettings: List, vararg tables: Table, statement: Transaction.(TestDB) -> Unit) { @@ -138,20 +139,26 @@ abstract class DatabaseTestsBase { } fun withSchemas(excludeSettings: List, vararg schemas: Schema, statement: Transaction.() -> Unit) { - val toTest = TestDB.enabledDialects() - excludeSettings - Assume.assumeTrue(toTest.isNotEmpty()) - toTest.forEach { testDB -> - withDb(testDB) { - if (currentDialectTest.supportsCreateSchema) { - SchemaUtils.createSchema(*schemas) - try { - statement() - commit() // Need commit to persist data before drop schemas - } finally { - val cascade = it != TestDB.SQLSERVER - SchemaUtils.dropSchema(*schemas, cascade = cascade) - commit() - } + if (dialect !in TestDB.enabledDialects()) { + Assume.assumeFalse(true) + return + } + + if (dialect in excludeSettings) { + Assume.assumeFalse(true) + return + } + + withDb(dialect) { + if (currentDialectTest.supportsCreateSchema) { + SchemaUtils.createSchema(*schemas) + try { + statement() + commit() // Need commit to persist data before drop schemas + } finally { + val cascade = it != TestDB.SQLSERVER + SchemaUtils.dropSchema(*schemas, cascade = cascade) + commit() } } } @@ -180,6 +187,10 @@ abstract class DatabaseTestsBase { fun Transaction.isOldMySql(version: String = "8.0") = currentDialectTest is MysqlDialect && !db.isVersionCovers(BigDecimal(version)) protected fun prepareSchemaForTest(schemaName: String): Schema = Schema( - schemaName, defaultTablespace = "USERS", temporaryTablespace = "TEMP ", quota = "20M", on = "USERS" + schemaName, + defaultTablespace = "USERS", + temporaryTablespace = "TEMP ", + quota = "20M", + on = "USERS" ) } diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt index 5aa1ed30f2..925847e4db 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt @@ -495,7 +495,8 @@ class DDLTests : DatabaseTestsBase() { val testTable = object : Table("TestTable") { val number = integer("number") - val blobWithDefault = blob("blobWithDefault").default(defaultBlob) + val blobWithDefault = blob("blobWithDefault") + .default(defaultBlob) } withDb { testDb -> @@ -506,6 +507,7 @@ class DDLTests : DatabaseTestsBase() { } } else -> { + SchemaUtils.drop(testTable) SchemaUtils.create(testTable) testTable.insert { @@ -959,10 +961,14 @@ class DDLTests : DatabaseTestsBase() { val one = prepareSchemaForTest("one") val two = prepareSchemaForTest("two") withSchemas(two, one) { + SchemaUtils.drop(TableFromSchemeOne) SchemaUtils.create(TableFromSchemeOne) + if (currentDialectTest is OracleDialect) { exec("GRANT REFERENCES ON ${TableFromSchemeOne.tableName} to TWO") } + + SchemaUtils.drop(TableFromSchemeTwo) SchemaUtils.create(TableFromSchemeTwo) val idFromOne = TableFromSchemeOne.insertAndGetId { } diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/SchemaTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/SchemaTests.kt index f89b185fc4..d7390659e1 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/SchemaTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/SchemaTests.kt @@ -64,8 +64,10 @@ class SchemaTests : DatabaseTestsBase() { val firstCatalogName = connection.catalog + exec("DROP TABLE IF EXISTS test") exec("CREATE TABLE test(id INT PRIMARY KEY)") SchemaUtils.setSchema(schema) + exec("DROP TABLE IF EXISTS test") exec("CREATE TABLE test(id INT REFERENCES $firstCatalogName.test(id))") val catalogName = connection.catalog diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/CreateDatabaseTest.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/CreateDatabaseTest.kt index 1846e38a42..0ed1c5f1b7 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/CreateDatabaseTest.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/CreateDatabaseTest.kt @@ -3,8 +3,10 @@ package org.jetbrains.exposed.sql.tests.shared.ddl import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.tests.DatabaseTestsBase import org.jetbrains.exposed.sql.tests.TestDB +import org.jetbrains.exposed.sql.tests.shared.assertTrue import org.junit.Test import java.sql.SQLException +import kotlin.test.assertFailsWith class CreateDatabaseTest : DatabaseTestsBase() { @@ -22,6 +24,53 @@ class CreateDatabaseTest : DatabaseTestsBase() { } } + @Test + fun testListDatabasesOracle() { + withDb(TestDB.ORACLE) { + assertFailsWith { + SchemaUtils.listDatabases() + } + } + } + + @Test + fun testListDatabasesPostgres() { + withDb(TestDB.POSTGRESQL) { + connection.autoCommit = true + val dbName = "jetbrains" + val initial = SchemaUtils.listDatabases() + if (dbName in initial) { + SchemaUtils.dropDatabase(dbName) + } + + SchemaUtils.createDatabase(dbName) + val created = SchemaUtils.listDatabases() + assertTrue(dbName in created) + SchemaUtils.dropDatabase(dbName) + val deleted = SchemaUtils.listDatabases() + assertTrue(dbName !in deleted) + connection.autoCommit = false + } + } + + @Test + fun testListDatabases() { + withDb(excludeSettings = listOf(TestDB.ORACLE, TestDB.POSTGRESQL, TestDB.POSTGRESQLNG)) { + val dbName = "jetbrains" + val initial = SchemaUtils.listDatabases() + if (dbName in initial) { + SchemaUtils.dropDatabase(dbName) + } + + SchemaUtils.createDatabase(dbName) + val created = SchemaUtils.listDatabases() + assertTrue(dbName in created) + SchemaUtils.dropDatabase(dbName) + val deleted = SchemaUtils.listDatabases() + assertTrue(dbName !in deleted) + } + } + @Test fun testCreateAndDropDatabaseInPostgresql() { // PostgreSQL needs auto commit to be "ON" to allow create database statement diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/functions/MathFunctionTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/functions/MathFunctionTests.kt index 96964bce9c..a9375dd2ce 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/functions/MathFunctionTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/functions/MathFunctionTests.kt @@ -105,7 +105,7 @@ class MathFunctionTests : FunctionsTestBase() { assertExpressionEqual(BigDecimal("11.2"), SqrtFunction(decimalLiteral(BigDecimal("125.44")))) when (testDb) { - TestDB.MYSQL, TestDB.MARIADB -> { + TestDB.MYSQL, TestDB.MARIADB, TestDB.SQLITE -> { assertExpressionEqual(null, SqrtFunction(intLiteral(-100))) } TestDB.SQLSERVER -> { @@ -114,7 +114,7 @@ class MathFunctionTests : FunctionsTestBase() { assertExpressionEqual(null, SqrtFunction(intLiteral(-100))) } } - TestDB.SQLITE, TestDB.POSTGRESQL, TestDB.POSTGRESQLNG, TestDB.ORACLE -> { + TestDB.POSTGRESQL, TestDB.POSTGRESQLNG, TestDB.ORACLE -> { // SQLite, PSQL, Oracle fail to execute sqrt with negative value expectException { assertExpressionEqual(null, SqrtFunction(intLiteral(-100))) diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/sqlite/ForeignKeyConstraintTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/sqlite/ForeignKeyConstraintTests.kt index dae4a3b481..1ded40692c 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/sqlite/ForeignKeyConstraintTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/sqlite/ForeignKeyConstraintTests.kt @@ -36,6 +36,7 @@ class ForeignKeyConstraintTests : DatabaseTestsBase() { } private fun Transaction.testOnDeleteSetDefault() { + SchemaUtils.drop(Category, Item) SchemaUtils.create(Category, Item) Category.insert {