Skip to content

Commit 62cd4d2

Browse files
committed
chore: Add new module for migration
1 parent 1b16975 commit 62cd4d2

File tree

12 files changed

+341
-251
lines changed

12 files changed

+341
-251
lines changed

exposed-core/api/exposed-core.api

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,6 @@ public final class org/jetbrains/exposed/exceptions/DuplicateColumnException : j
7373
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
7474
}
7575

76-
public final class org/jetbrains/exposed/exceptions/ExposedMigrationException : java/lang/RuntimeException {
77-
public fun <init> (Ljava/lang/Exception;Ljava/lang/String;)V
78-
}
79-
8076
public final class org/jetbrains/exposed/exceptions/ExposedSQLException : java/sql/SQLException {
8177
public fun <init> (Ljava/lang/Throwable;Ljava/util/List;Lorg/jetbrains/exposed/sql/Transaction;)V
8278
public final fun causedByQueries ()Ljava/util/List;
@@ -611,10 +607,6 @@ public final class org/jetbrains/exposed/sql/Database {
611607
public final fun getVendor ()Ljava/lang/String;
612608
public final fun getVersion ()Ljava/math/BigDecimal;
613609
public final fun isVersionCovers (Ljava/math/BigDecimal;)Z
614-
public final fun migrate ([Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
615-
public final fun migrate ([Lorg/jetbrains/exposed/sql/Table;Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
616-
public static synthetic fun migrate$default (Lorg/jetbrains/exposed/sql/Database;[Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)V
617-
public static synthetic fun migrate$default (Lorg/jetbrains/exposed/sql/Database;[Lorg/jetbrains/exposed/sql/Table;Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)V
618610
public final fun setUseNestedTransactions (Z)V
619611
public fun toString ()Ljava/lang/String;
620612
}
@@ -2001,12 +1993,9 @@ public final class org/jetbrains/exposed/sql/SchemaUtils {
20011993
public static synthetic fun dropSchema$default (Lorg/jetbrains/exposed/sql/SchemaUtils;[Lorg/jetbrains/exposed/sql/Schema;ZZILjava/lang/Object;)V
20021994
public final fun dropSequence ([Lorg/jetbrains/exposed/sql/Sequence;Z)V
20031995
public static synthetic fun dropSequence$default (Lorg/jetbrains/exposed/sql/SchemaUtils;[Lorg/jetbrains/exposed/sql/Sequence;ZILjava/lang/Object;)V
2004-
public final fun generateMigrationScript ([Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)Ljava/io/File;
2005-
public final fun generateMigrationScript ([Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/String;Z)Ljava/io/File;
2006-
public static synthetic fun generateMigrationScript$default (Lorg/jetbrains/exposed/sql/SchemaUtils;[Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Ljava/io/File;
2007-
public static synthetic fun generateMigrationScript$default (Lorg/jetbrains/exposed/sql/SchemaUtils;[Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Ljava/io/File;
20081996
public final fun listDatabases ()Ljava/util/List;
20091997
public final fun listTables ()Ljava/util/List;
1998+
public final fun logTimeSpent (Ljava/lang/String;ZLkotlin/jvm/functions/Function0;)Ljava/lang/Object;
20101999
public final fun setSchema (Lorg/jetbrains/exposed/sql/Schema;Z)V
20112000
public static synthetic fun setSchema$default (Lorg/jetbrains/exposed/sql/SchemaUtils;Lorg/jetbrains/exposed/sql/Schema;ZILjava/lang/Object;)V
20122001
public final fun sortTablesByReferences (Ljava/lang/Iterable;)Ljava/util/List;

exposed-core/build.gradle.kts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,4 @@ dependencies {
1515
api(kotlin("reflect"))
1616
api(libs.kotlinx.coroutines)
1717
api(libs.slf4j)
18-
api(libs.flyway)
19-
api(libs.flyway.mysql)
20-
api(libs.flyway.oracle)
21-
api(libs.flyway.sqlserver)
2218
}

exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Database.kt

Lines changed: 0 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
package org.jetbrains.exposed.sql
22

3-
import org.flywaydb.core.Flyway
4-
import org.flywaydb.core.api.FlywayException
5-
import org.flywaydb.core.api.output.MigrateResult
63
import org.jetbrains.annotations.TestOnly
7-
import org.jetbrains.exposed.exceptions.ExposedMigrationException
84
import org.jetbrains.exposed.sql.statements.api.ExposedConnection
95
import org.jetbrains.exposed.sql.statements.api.ExposedDatabaseMetadata
106
import org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManager
117
import org.jetbrains.exposed.sql.transactions.TransactionManager
128
import org.jetbrains.exposed.sql.vendors.*
13-
import java.io.File
149
import java.math.BigDecimal
1510
import java.sql.Connection
1611
import java.sql.DriverManager
@@ -109,160 +104,6 @@ class Database private constructor(
109104
*/
110105
internal var dataSourceReadOnly: Boolean = false
111106

112-
/**
113-
* @param tables The tables to which the migration will be applied.
114-
* @param user The user of the database.
115-
* @param password The password of the database.
116-
* @param oldVersion The version to migrate from. Pending migrations up to [oldVersion] are applied before applying the migration to [newVersion].
117-
* @param newVersion The version to migrate to.
118-
* @param migrationTitle The title of the migration.
119-
* @param migrationScriptDirectory The directory in which to create the migration script.
120-
* @param withLogs By default, a description for each intermediate step, as well as its execution time, is logged at
121-
* the INFO level. This can be disabled by setting [withLogs] to `false`.
122-
*
123-
* @throws ExposedMigrationException if the migration fails.
124-
*
125-
* Applies a database migration from [oldVersion] to [newVersion].
126-
*
127-
* If a migration script with the same name already exists, the existing one will be used as is and a new one will
128-
* not be generated. This allows you to generate a migration script before the migration and modify it manually if
129-
* needed.
130-
*/
131-
@ExperimentalDatabaseMigrationApi
132-
@Suppress("LongParameterList", "TooGenericExceptionCaught")
133-
fun migrate(
134-
vararg tables: Table,
135-
user: String,
136-
password: String,
137-
oldVersion: String,
138-
newVersion: String,
139-
migrationTitle: String,
140-
migrationScriptDirectory: String,
141-
withLogs: Boolean = true
142-
) {
143-
val flyway = Flyway
144-
.configure()
145-
.baselineOnMigrate(true)
146-
.baselineVersion(oldVersion)
147-
.dataSource(url, user, password)
148-
.locations("filesystem:$migrationScriptDirectory")
149-
.load()
150-
151-
with(TransactionManager.current()) {
152-
db.dialect.resetCaches()
153-
154-
try {
155-
val migrationScript = File("$migrationScriptDirectory/$migrationTitle.sql")
156-
if (!migrationScript.exists()) {
157-
SchemaUtils.generateMigrationScript(
158-
tables = *tables,
159-
newVersion = newVersion,
160-
title = migrationTitle,
161-
scriptDirectory = migrationScriptDirectory,
162-
withLogs = withLogs
163-
)
164-
}
165-
} catch (exception: Exception) {
166-
throw ExposedMigrationException(
167-
exception = exception,
168-
message = "Failed to generate migration script for migration from $oldVersion to $newVersion: ${exception.message.orEmpty()}"
169-
)
170-
}
171-
172-
try {
173-
SchemaUtils.logTimeSpent("Migrating database from $oldVersion to $newVersion", withLogs = true) {
174-
val migrateResult: MigrateResult = flyway.migrate()
175-
if (withLogs) {
176-
exposedLogger.info("Migration of database ${if (migrateResult.success) "succeeded" else "failed"}.")
177-
}
178-
}
179-
} catch (exception: FlywayException) {
180-
flyway.repair()
181-
throw ExposedMigrationException(
182-
exception = exception,
183-
message = "Migration failed from version $oldVersion to $newVersion: ${exception.message.orEmpty()}"
184-
)
185-
}
186-
187-
db.dialect.resetCaches()
188-
}
189-
}
190-
191-
/**
192-
* @param tables The tables to which the migration will be applied.
193-
* @param dataSource The [DataSource] object to be used as a means of getting a connection.
194-
* @param oldVersion The version to migrate from. Pending migrations up to [oldVersion] are applied before applying the migration to [newVersion].
195-
* @param newVersion The version to migrate to.
196-
* @param migrationTitle The title of the migration.
197-
* @param migrationScriptDirectory The directory in which to create the migration script.
198-
* @param withLogs By default, a description for each intermediate step, as well as its execution time, is logged at
199-
* the INFO level. This can be disabled by setting [withLogs] to `false`.
200-
*
201-
* @throws ExposedMigrationException if the migration fails.
202-
*
203-
* Applies a database migration from [oldVersion] to [newVersion].
204-
* For PostgreSQLNG, "jdbc:pgsql" in the database URL is replaced with "jdbc:postgresql" because the former is not
205-
* supported by Flyway.
206-
*/
207-
@ExperimentalDatabaseMigrationApi
208-
@Suppress("LongParameterList", "TooGenericExceptionCaught")
209-
fun migrate(
210-
vararg tables: Table,
211-
dataSource: DataSource,
212-
oldVersion: String,
213-
newVersion: String,
214-
migrationTitle: String,
215-
migrationScriptDirectory: String,
216-
withLogs: Boolean = true
217-
) {
218-
val flyway = Flyway
219-
.configure()
220-
.baselineOnMigrate(true)
221-
.baselineVersion(oldVersion)
222-
.dataSource(dataSource)
223-
.locations("filesystem:$migrationScriptDirectory")
224-
.load()
225-
226-
with(TransactionManager.current()) {
227-
db.dialect.resetCaches()
228-
229-
try {
230-
val migrationScript = File("$migrationScriptDirectory/$migrationTitle.sql")
231-
if (!migrationScript.exists()) {
232-
SchemaUtils.generateMigrationScript(
233-
tables = *tables,
234-
newVersion = newVersion,
235-
title = migrationTitle,
236-
scriptDirectory = migrationScriptDirectory,
237-
withLogs = withLogs
238-
)
239-
}
240-
} catch (exception: Exception) {
241-
throw ExposedMigrationException(
242-
exception = exception,
243-
message = "Failed to generate migration script for migration from $oldVersion to $newVersion: ${exception.message.orEmpty()}"
244-
)
245-
}
246-
247-
try {
248-
SchemaUtils.logTimeSpent("Migrating database from $oldVersion to $newVersion", withLogs = true) {
249-
val migrateResult: MigrateResult = flyway.migrate()
250-
if (withLogs) {
251-
exposedLogger.info("Migration of database ${if (migrateResult.success) "succeeded" else "failed"}.")
252-
}
253-
}
254-
} catch (exception: FlywayException) {
255-
flyway.repair()
256-
throw ExposedMigrationException(
257-
exception = exception,
258-
message = "Migration failed from version $oldVersion to $newVersion: ${exception.message.orEmpty()}"
259-
)
260-
}
261-
262-
db.dialect.resetCaches()
263-
}
264-
}
265-
266107
companion object {
267108
internal val dialects = ConcurrentHashMap<String, () -> DatabaseDialect>()
268109

exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Exceptions.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,3 @@ internal fun Transaction.throwUnsupportedException(message: String): Nothing = t
7777
message,
7878
db.dialect
7979
)
80-
81-
/** An exception thrown when a database migration fails. */
82-
class ExposedMigrationException(exception: Exception, message: String) : RuntimeException(message, exception)

exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/SchemaUtils.kt

Lines changed: 5 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ import org.jetbrains.exposed.exceptions.ExposedSQLException
44
import org.jetbrains.exposed.sql.SqlExpressionBuilder.asLiteral
55
import org.jetbrains.exposed.sql.transactions.TransactionManager
66
import org.jetbrains.exposed.sql.vendors.*
7-
import java.io.File
87
import java.math.BigDecimal
98

109
/** Utility functions that assist with creating, altering, and dropping database schema objects. */
1110
@Suppress("TooManyFunctions", "LargeClass")
1211
object SchemaUtils {
13-
internal inline fun <R> logTimeSpent(message: String, withLogs: Boolean, block: () -> R): R {
12+
inline fun <R> logTimeSpent(message: String, withLogs: Boolean, block: () -> R): R {
1413
return if (withLogs) {
1514
val start = System.currentTimeMillis()
1615
val answer = block()
@@ -510,11 +509,11 @@ object SchemaUtils {
510509
* Returns the SQL statements that need to be executed to make the existing database schema compatible with
511510
* the table objects defined using Exposed.
512511
*
513-
* **Note:** Some dialects, like SQLite, do not support `ALTER TABLE ADD COLUMN` syntax completely,
514-
* which restricts the behavior when adding some missing columns. Please check the documentation.
515-
*
516512
* By default, a description for each intermediate step, as well as its execution time, is logged at the INFO level.
517513
* This can be disabled by setting [withLogs] to `false`.
514+
*
515+
* **Note:** Some dialects, like SQLite, do not support `ALTER TABLE ADD COLUMN` syntax completely,
516+
* which restricts the behavior when adding some missing columns. Please check the documentation.
518517
*/
519518
fun statementsRequiredToActualizeScheme(vararg tables: Table, withLogs: Boolean = true): List<String> {
520519
val (tablesToCreate, tablesToAlter) = tables.partition { !it.exists() }
@@ -832,66 +831,6 @@ object SchemaUtils {
832831
return toDrop.toList()
833832
}
834833

835-
/**
836-
* @param tables The tables whose changes will be used to generate the migration script.
837-
* @param newVersion The version to migrate to.
838-
* @param title The title of the migration.
839-
* @param scriptDirectory The directory in which to create the migration script.
840-
* @param withLogs By default, a description for each intermediate step, as well as its execution time, is logged at
841-
* the INFO level. This can be disabled by setting [withLogs] to `false`.
842-
*
843-
* @return The generated migration script.
844-
*
845-
* @throws IllegalArgumentException if no argument is passed for the [tables] parameter.
846-
*
847-
* This function simply generates the migration script without applying the migration. The purpose of it is to show
848-
* the user what the migration script will look like before applying the migration.
849-
* This function uses the Flyway naming convention when generating the migration script.
850-
* If a migration script with the same name already exists, its content will be overwritten.
851-
*/
852-
@ExperimentalDatabaseMigrationApi
853-
fun generateMigrationScript(vararg tables: Table, newVersion: String, title: String, scriptDirectory: String, withLogs: Boolean = true): File {
854-
return generateMigrationScript(*tables, scriptName = "V${newVersion}__$title", scriptDirectory = scriptDirectory, withLogs = withLogs)
855-
}
856-
857-
/**
858-
* @param tables The tables whose changes will be used to generate the migration script.
859-
* @param scriptName The name to be used for the generated migration script.
860-
* @param scriptDirectory The directory (path from repository root) in which to create the migration script.
861-
* @param withLogs By default, a description for each intermediate step, as well as its execution time, is logged at
862-
* the INFO level. This can be disabled by setting [withLogs] to `false`.
863-
*
864-
* @return The generated migration script.
865-
*
866-
* @throws IllegalArgumentException if no argument is passed for the [tables] parameter.
867-
*
868-
* This function simply generates the migration script without applying the migration. Its purpose is to show what
869-
* the migration script will look like before applying the migration.
870-
* If a migration script with the same name already exists, its content will be overwritten.
871-
*/
872-
@ExperimentalDatabaseMigrationApi
873-
fun generateMigrationScript(vararg tables: Table, scriptDirectory: String, scriptName: String, withLogs: Boolean = true): File {
874-
require(tables.isNotEmpty()) { "Tables argument must not be empty" }
875-
876-
val allStatements = statementsRequiredForDatabaseMigration(*tables, withLogs = withLogs)
877-
878-
val migrationScript = File("$scriptDirectory/$scriptName.sql")
879-
migrationScript.createNewFile()
880-
881-
// Clear existing content
882-
migrationScript.writeText("")
883-
884-
// Append statements
885-
allStatements.forEach { statement ->
886-
// Add semicolon only if it's not already there
887-
val conditionalSemicolon = if (statement.last() == ';') "" else ";"
888-
889-
migrationScript.appendText("$statement$conditionalSemicolon\n")
890-
}
891-
892-
return migrationScript
893-
}
894-
895834
/**
896835
* Returns the SQL statements that need to be executed to make the existing database schema compatible with
897836
* the table objects defined using Exposed. Unlike [statementsRequiredToActualizeScheme], DROP/DELETE statements are
@@ -903,6 +842,7 @@ object SchemaUtils {
903842
* By default, a description for each intermediate step, as well as its execution time, is logged at the INFO level.
904843
* This can be disabled by setting [withLogs] to `false`.
905844
*/
845+
@ExperimentalDatabaseMigrationApi
906846
fun statementsRequiredForDatabaseMigration(vararg tables: Table, withLogs: Boolean = true): List<String> {
907847
val (tablesToCreate, tablesToAlter) = tables.partition { !it.exists() }
908848
val createStatements = logTimeSpent("Preparing create tables statements", withLogs) {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
public final class ExposedMigrationException : java/lang/RuntimeException {
2+
public fun <init> (Ljava/lang/Exception;Ljava/lang/String;)V
3+
}
4+
5+
public final class MigrationUtils {
6+
public static final field INSTANCE LMigrationUtils;
7+
public final fun generateMigrationScript ([Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)Ljava/io/File;
8+
public final fun generateMigrationScript ([Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/String;Z)Ljava/io/File;
9+
public static synthetic fun generateMigrationScript$default (LMigrationUtils;[Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Ljava/io/File;
10+
public static synthetic fun generateMigrationScript$default (LMigrationUtils;[Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Ljava/io/File;
11+
public final fun migrate (Lorg/jetbrains/exposed/sql/Database;[Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
12+
public final fun migrate (Lorg/jetbrains/exposed/sql/Database;[Lorg/jetbrains/exposed/sql/Table;Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
13+
public static synthetic fun migrate$default (LMigrationUtils;Lorg/jetbrains/exposed/sql/Database;[Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)V
14+
public static synthetic fun migrate$default (LMigrationUtils;Lorg/jetbrains/exposed/sql/Database;[Lorg/jetbrains/exposed/sql/Table;Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)V
15+
}
16+

exposed-migration/build.gradle.kts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
plugins {
2+
kotlin("jvm")
3+
}
4+
5+
repositories {
6+
mavenCentral()
7+
}
8+
9+
dependencies {
10+
api(project(":exposed-core"))
11+
12+
api(libs.flyway)
13+
api(libs.flyway.mysql)
14+
api(libs.flyway.oracle)
15+
api(libs.flyway.sqlserver)
16+
17+
testImplementation(project(":exposed-tests"))
18+
19+
testImplementation(libs.junit)
20+
testImplementation(kotlin("test-junit"))
21+
22+
testCompileOnly(libs.pgjdbc.ng)
23+
}
24+
25+
tasks.test {
26+
useJUnitPlatform()
27+
}
28+
29+
kotlin {
30+
jvmToolchain(8)
31+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/** An exception thrown when a database migration fails. */
2+
class ExposedMigrationException(exception: Exception, message: String) : RuntimeException(message, exception)

0 commit comments

Comments
 (0)