Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions exposed-core/api/exposed-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ public final class org/jetbrains/exposed/v1/core/DatabaseApi$Companion {

public abstract interface class org/jetbrains/exposed/v1/core/DatabaseConfig {
public static final field Companion Lorg/jetbrains/exposed/v1/core/DatabaseConfig$Companion;
public abstract fun getDdl ()Lorg/jetbrains/exposed/v1/core/Ddl;
public abstract fun getDefaultFetchSize ()Ljava/lang/Integer;
public abstract fun getDefaultIsolationLevel ()I
public abstract fun getDefaultMaxAttempts ()I
Expand All @@ -632,6 +633,7 @@ public abstract interface class org/jetbrains/exposed/v1/core/DatabaseConfig {

public class org/jetbrains/exposed/v1/core/DatabaseConfig$Builder {
public fun <init> ()V
public final fun getDdl ()Lorg/jetbrains/exposed/v1/core/Ddl;
public final fun getDefaultFetchSize ()Ljava/lang/Integer;
public fun getDefaultIsolationLevel ()I
public final fun getDefaultMaxAttempts ()I
Expand All @@ -649,6 +651,7 @@ public class org/jetbrains/exposed/v1/core/DatabaseConfig$Builder {
public final fun getSqlLogger ()Lorg/jetbrains/exposed/v1/core/SqlLogger;
public final fun getUseNestedTransactions ()Z
public final fun getWarnLongQueriesDuration ()Ljava/lang/Long;
public final fun setDdl (Lorg/jetbrains/exposed/v1/core/Ddl;)V
public final fun setDefaultFetchSize (Ljava/lang/Integer;)V
public fun setDefaultIsolationLevel (I)V
public final fun setDefaultMaxAttempts (I)V
Expand All @@ -675,6 +678,7 @@ public final class org/jetbrains/exposed/v1/core/DatabaseConfig$Companion {

public class org/jetbrains/exposed/v1/core/DatabaseConfigImpl : org/jetbrains/exposed/v1/core/DatabaseConfig {
public fun <init> (Lorg/jetbrains/exposed/v1/core/DatabaseConfig$Builder;)V
public fun getDdl ()Lorg/jetbrains/exposed/v1/core/Ddl;
public fun getDefaultFetchSize ()Ljava/lang/Integer;
public fun getDefaultIsolationLevel ()I
public fun getDefaultMaxAttempts ()I
Expand All @@ -694,6 +698,17 @@ public class org/jetbrains/exposed/v1/core/DatabaseConfigImpl : org/jetbrains/ex
public fun getWarnLongQueriesDuration ()Ljava/lang/Long;
}

public final class org/jetbrains/exposed/v1/core/Ddl {
public fun <init> (Ljava/util/List;)V
public final fun component1 ()Ljava/util/List;
public final fun copy (Ljava/util/List;)Lorg/jetbrains/exposed/v1/core/Ddl;
public static synthetic fun copy$default (Lorg/jetbrains/exposed/v1/core/Ddl;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/exposed/v1/core/Ddl;
public fun equals (Ljava/lang/Object;)Z
public final fun getTables ()Ljava/util/List;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public abstract interface class org/jetbrains/exposed/v1/core/DdlAware {
public abstract fun createStatement ()Ljava/util/List;
public abstract fun dropStatement ()Ljava/util/List;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface DatabaseConfig {
val logTooMuchResultSetsThreshold: Int
val preserveKeywordCasing: Boolean
val preserveIdentifierCasing: Boolean
val ddl: Ddl?

/**
* The [CoroutineDispatcher] to be used when determining the scope of Exposed transaction if
Expand Down Expand Up @@ -183,13 +184,16 @@ interface DatabaseConfig {
* Default dispatcher is [Dispatchers.IO].
*/
var dispatcher: CoroutineDispatcher = IO

var ddl: Ddl? = null
}

companion object {
operator fun invoke(body: Builder.() -> Unit = {}): DatabaseConfig {
val builder = Builder().apply(body)
require(builder.defaultMaxAttempts > 0) { "defaultMaxAttempts must be set to perform at least 1 attempt." }

println(builder.ddl)
@OptIn(InternalApi::class)
return DatabaseConfigImpl(builder)
}
Expand Down Expand Up @@ -229,6 +233,8 @@ open class DatabaseConfigImpl(private val builder: DatabaseConfig.Builder) : Dat
get() = builder.logTooMuchResultSetsThreshold
override val dispatcher: CoroutineDispatcher
get() = builder.dispatcher
override val ddl: Ddl?
get() = builder.ddl

@OptIn(ExperimentalKeywordApi::class)
override val preserveKeywordCasing: Boolean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.jetbrains.exposed.v1.core

data class Ddl(
val tables: List<Table>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import org.jetbrains.exposed.v1.core.DatabaseConfig
import org.jetbrains.exposed.v1.core.InternalApi
import org.jetbrains.exposed.v1.core.Version
import org.jetbrains.exposed.v1.core.statements.api.IdentifierManagerApi
import org.jetbrains.exposed.v1.core.transactions.withThreadLocalTransaction
import org.jetbrains.exposed.v1.core.vendors.*
import org.jetbrains.exposed.v1.jdbc.statements.api.ExposedConnection
import org.jetbrains.exposed.v1.jdbc.statements.api.JdbcExposedDatabaseMetadata
import org.jetbrains.exposed.v1.jdbc.transactions.JdbcTransactionManager
import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager
import org.jetbrains.exposed.v1.jdbc.vendors.*
import org.slf4j.LoggerFactory
import java.sql.Connection
import java.sql.DriverManager
import java.util.*
Expand Down Expand Up @@ -105,6 +107,8 @@ class Database private constructor(
internal var dataSourceReadOnly: Boolean = false

companion object {
private val logger = LoggerFactory.getLogger(this::class.java)

private val connectionInstanceImpl: DatabaseConnectionAutoRegistration by lazy {
ServiceLoader.load(DatabaseConnectionAutoRegistration::class.java, Database::class.java.classLoader)
.firstOrNull()
Expand Down Expand Up @@ -174,10 +178,12 @@ class Database private constructor(
setupConnection: (Connection) -> Unit = {},
manager: (Database) -> JdbcTransactionManager = { TransactionManager(it) }
): Database {
return Database(explicitVendor, config ?: DatabaseConfig.invoke()) {
val databaseConfig = config ?: DatabaseConfig.invoke()
return Database(explicitVendor, databaseConfig) {
connectionAutoRegistration(getNewConnection().apply { setupConnection(this) })
}.apply {
TransactionManager.registerManager(this, manager(this))
generateSchema(this)
}
}

Expand Down Expand Up @@ -296,6 +302,21 @@ class Database private constructor(
fun getDialectName(url: String) = dialectMapping.entries.firstOrNull { (prefix, _) ->
url.startsWith(prefix)
}?.value

@OptIn(InternalApi::class)
internal fun generateSchema(database: Database) {
if (database.config.ddl == null) {
logger.debug("Schema generation not configured")
return
} else {
withThreadLocalTransaction(TransactionManager.manager.newTransaction(readOnly = false)) {
val exposedTables = database.config.ddl!!.tables
logger.info("Schema generation for tables '{}'", exposedTables.map { it.tableName })
logger.debug("ddl {}", exposedTables.map { it.ddl }.joinToString())
SchemaUtils.create(tables = exposedTables.toTypedArray())
}
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions exposed-r2dbc/api/exposed-r2dbc.api
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ public final class org/jetbrains/exposed/v1/r2dbc/R2dbcDatabaseConfig$Companion
public final class org/jetbrains/exposed/v1/r2dbc/R2dbcDatabaseConfigImpl : org/jetbrains/exposed/v1/core/DatabaseConfig, org/jetbrains/exposed/v1/r2dbc/R2dbcDatabaseConfig {
public fun <init> (Lorg/jetbrains/exposed/v1/r2dbc/R2dbcDatabaseConfig$Builder;)V
public fun getConnectionFactoryOptions ()Lio/r2dbc/spi/ConnectionFactoryOptions;
public fun getDdl ()Lorg/jetbrains/exposed/v1/core/Ddl;
public fun getDefaultFetchSize ()Ljava/lang/Integer;
public fun getDefaultIsolationLevel ()I
public fun getDefaultMaxAttempts ()I
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.jetbrains.exposed.v1.r2dbc.statements.api.R2dbcLocalMetadataImpl
import org.jetbrains.exposed.v1.r2dbc.transactions.R2dbcTransactionManager
import org.jetbrains.exposed.v1.r2dbc.transactions.TransactionManager
import org.jetbrains.exposed.v1.r2dbc.vendors.*
import org.slf4j.LoggerFactory
import java.util.concurrent.ConcurrentHashMap

/**
Expand Down Expand Up @@ -121,6 +122,7 @@ class R2dbcDatabase private constructor(
override val identifierManager: IdentifierManagerApi by lazy { connectionMetadata { identifierManager } }

companion object {
private val logger = LoggerFactory.getLogger(this::class.java)

private val dialectsMetadata = ConcurrentHashMap<String, () -> DatabaseDialectMetadata>()

Expand Down Expand Up @@ -174,7 +176,10 @@ class R2dbcDatabase private constructor(
connectionUrlMode = options.urlMode
TransactionManager.registerManager(this, manager(this))
// ABOVE should be replaced with BELOW when ThreadLocalTransactionManager is fully deprecated
// TransactionManager.registerManager(this, manager(this))
// TransactionManager.registerManager(this, manager(this))\
runBlocking {
generateSchema(this@apply)
}
}
}

Expand Down Expand Up @@ -293,6 +298,21 @@ class R2dbcDatabase private constructor(
private fun getDriver(url: String) = driverMapping.entries.firstOrNull { (prefix, _) ->
url.startsWith(prefix)
}?.value ?: error("Database driver not found for $url")

@OptIn(InternalApi::class)
private suspend fun generateSchema(database: R2dbcDatabase) {
if (database.config.ddl == null) {
logger.debug("Schema generation not configured")
return
} else {
withTransactionContext(TransactionManager.manager.newTransaction(readOnly = false), {
val exposedTables = database.config.ddl!!.tables
logger.info("Schema generation for tables '{}'", exposedTables.map { it.tableName })
logger.debug("ddl {}", exposedTables.map { it.ddl }.joinToString())
SchemaUtils.create(tables = exposedTables.toTypedArray())
})
}
}
}
}

Expand Down
20 changes: 4 additions & 16 deletions exposed-spring-boot-starter/api/exposed-spring-boot-starter.api
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
public class org/jetbrains/exposed/v1/spring/boot/DatabaseInitializer : org/springframework/boot/ApplicationRunner, org/springframework/core/Ordered {
public static final field Companion Lorg/jetbrains/exposed/v1/spring/boot/DatabaseInitializer$Companion;
public static final field DATABASE_INITIALIZER_ORDER I
public fun <init> (Lorg/springframework/context/ApplicationContext;Ljava/util/List;)V
public fun getOrder ()I
public fun run (Lorg/springframework/boot/ApplicationArguments;)V
}

public final class org/jetbrains/exposed/v1/spring/boot/DatabaseInitializer$Companion {
}

public final class org/jetbrains/exposed/v1/spring/boot/DatabaseInitializerKt {
public static final fun discoverExposedTables (Lorg/springframework/context/ApplicationContext;Ljava/util/List;)Ljava/util/List;
}

public final class org/jetbrains/exposed/v1/spring/boot/ExposedAotContribution : org/springframework/beans/factory/aot/BeanFactoryInitializationAotProcessor {
public fun <init> ()V
public fun processAheadOfTime (Lorg/springframework/beans/factory/config/ConfigurableListableBeanFactory;)Lorg/springframework/beans/factory/aot/BeanFactoryInitializationAotContribution;
}

public class org/jetbrains/exposed/v1/spring/boot/autoconfigure/ExposedAutoConfiguration {
public static final field Companion Lorg/jetbrains/exposed/v1/spring/boot/autoconfigure/ExposedAutoConfiguration$Companion;
public fun <init> (Lorg/springframework/context/ApplicationContext;)V
public fun databaseConfig ()Lorg/jetbrains/exposed/v1/core/DatabaseConfig;
public fun databaseInitializer ()Lorg/jetbrains/exposed/v1/spring/boot/DatabaseInitializer;
public fun exposedSpringTransactionAttributeSource ()Lorg/jetbrains/exposed/v1/spring/transaction/ExposedSpringTransactionAttributeSource;
public fun springTransactionManager (Ljavax/sql/DataSource;Lorg/jetbrains/exposed/v1/core/DatabaseConfig;)Lorg/jetbrains/exposed/v1/spring/transaction/SpringTransactionManager;
}

public final class org/jetbrains/exposed/v1/spring/boot/autoconfigure/ExposedAutoConfiguration$Companion {
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
package org.jetbrains.exposed.v1.spring.boot.autoconfigure

import org.jetbrains.exposed.v1.core.DatabaseConfig
import org.jetbrains.exposed.v1.spring.boot.DatabaseInitializer
import org.jetbrains.exposed.v1.core.Ddl
import org.jetbrains.exposed.v1.core.Table
import org.jetbrains.exposed.v1.spring.transaction.ExposedSpringTransactionAttributeSource
import org.jetbrains.exposed.v1.spring.transaction.SpringTransactionManager
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.boot.autoconfigure.AutoConfigurationPackages
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
import org.springframework.context.annotation.Primary
import org.springframework.core.type.filter.AssignableTypeFilter
import org.springframework.core.type.filter.RegexPatternTypeFilter
import org.springframework.transaction.annotation.EnableTransactionManagement
import java.util.regex.Pattern
import javax.sql.DataSource

/**
Expand All @@ -27,7 +32,6 @@ import javax.sql.DataSource
* required values in a separate `@EnableTransactionManagement` on the main configuration class or in a configuration
* file using `spring.aop.proxy-target-class`.
*
* @property applicationContext The Spring ApplicationContext container responsible for managing beans.
*/
@AutoConfiguration(after = [DataSourceAutoConfiguration::class])
@EnableTransactionManagement
Expand All @@ -36,6 +40,9 @@ open class ExposedAutoConfiguration(private val applicationContext: ApplicationC
@Value($$"${spring.exposed.excluded-packages:}#{T(java.util.Collections).emptyList()}")
private lateinit var excludedPackages: List<String>

@Value($$"${spring.exposed.generate-ddl:false}")
private var generateDdl: Boolean = false

@Value($$"${spring.exposed.show-sql:false}")
private var showSql: Boolean = false

Expand All @@ -56,20 +63,13 @@ open class ExposedAutoConfiguration(private val applicationContext: ApplicationC
@Bean
@ConditionalOnMissingBean(DatabaseConfig::class)
open fun databaseConfig(): DatabaseConfig {
return DatabaseConfig {}
return DatabaseConfig {
if (generateDdl) {
this.ddl = Ddl(discoverExposedTables(applicationContext, excludedPackages))
}
}
}

/**
* Returns a [DatabaseInitializer] that auto-creates the database schema, if enabled by the property
* `spring.exposed.generate-ddl` in the application.properties file.
*
* The property `spring.exposed.excluded-packages` can be used to ensure that tables in specified packages are
* not auto-created.
*/
@Bean
@ConditionalOnProperty("spring.exposed.generate-ddl", havingValue = "true", matchIfMissing = false)
open fun databaseInitializer() = DatabaseInitializer(applicationContext, excludedPackages)

/**
* Returns an [ExposedSpringTransactionAttributeSource] instance.
*
Expand All @@ -83,4 +83,19 @@ open class ExposedAutoConfiguration(private val applicationContext: ApplicationC
open fun exposedSpringTransactionAttributeSource(): ExposedSpringTransactionAttributeSource {
return ExposedSpringTransactionAttributeSource()
}

companion object {
/**
* Returns a list of identified tables that extend Exposed's base [Table] class, without searching any packages
* in [excludedPackages].
*/
internal fun discoverExposedTables(applicationContext: ApplicationContext, excludedPackages: List<String>): List<Table> {
val provider = ClassPathScanningCandidateComponentProvider(false)
provider.addIncludeFilter(AssignableTypeFilter(Table::class.java))
excludedPackages.forEach { provider.addExcludeFilter(RegexPatternTypeFilter(Pattern.compile(it.replace(".", "\\.") + ".*"))) }
val packages = AutoConfigurationPackages.get(applicationContext)
val components = packages.flatMap { provider.findCandidateComponents(it) }
return components.mapNotNull { Class.forName(it.beanClassName).kotlin.objectInstance as? Table }
}
}
}
Loading