Skip to content

Commit

Permalink
test: EXPOSED-517 Implement missing ExposedDatabaseMetadata methods
Browse files Browse the repository at this point in the history
- Add copy testing module for TC check
  • Loading branch information
bog-walk committed Feb 19, 2025
1 parent 8e04b54 commit c4e1a63
Show file tree
Hide file tree
Showing 21 changed files with 3,356 additions and 0 deletions.
1 change: 1 addition & 0 deletions exposed-r2dbc-tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
jetbrains.db
53 changes: 53 additions & 0 deletions exposed-r2dbc-tests/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent

plugins {
kotlin("jvm") apply true
}

kotlin {
jvmToolchain(11)
}

repositories {
mavenCentral()
}

dependencies {
implementation(libs.kotlinx.coroutines.reactive)
implementation(libs.kotlinx.coroutines.debug)
implementation(libs.r2dbc.spi)

implementation(kotlin("test-junit"))
implementation(libs.junit)

implementation(project(":exposed-core"))
implementation(project(":exposed-r2dbc"))
implementation(project(":exposed-kotlin-datetime"))

implementation(libs.slf4j)
implementation(libs.log4j.slf4j.impl)
implementation(libs.log4j.api)
implementation(libs.log4j.core)

testRuntimeOnly(libs.r2dbc.h2)
testRuntimeOnly(libs.r2dbc.mariadb)
testRuntimeOnly(libs.r2dbc.mysql)
testRuntimeOnly(libs.r2dbc.oracle)
testRuntimeOnly(libs.r2dbc.postgresql)
testRuntimeOnly(libs.r2dbc.sqlserver)

testImplementation(libs.logcaptor)
testImplementation(libs.kotlinx.coroutines.test)
}

tasks.withType<Test>().configureEach {
if (JavaVersion.VERSION_1_8 > JavaVersion.current()) {
jvmArgs = listOf("-XX:MaxPermSize=256m")
}
testLogging {
events.addAll(listOf(TestLogEvent.PASSED, TestLogEvent.FAILED, TestLogEvent.SKIPPED))
showStandardStreams = true
exceptionFormat = TestExceptionFormat.FULL
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package org.jetbrains.exposed.sql.tests

import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.StatementInterceptor
import org.jetbrains.exposed.sql.transactions.nullableTransactionScope
import org.jetbrains.exposed.sql.transactions.suspendTransaction
import org.jetbrains.exposed.sql.transactions.transactionManager
import org.junit.Assume
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
import java.util.*
import kotlin.concurrent.thread

val TEST_DIALECTS: java.util.HashSet<String> = System.getProperty(
"exposed.test.dialects",
""
).split(",").mapTo(HashSet()) { it.trim().uppercase() }

private val registeredOnShutdown = HashSet<TestDB>()

internal var currentTestDB by nullableTransactionScope<TestDB>()

@RunWith(Parameterized::class)
abstract class R2dbcDatabaseTestsBase {
init {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
}

private object CurrentTestDBInterceptor : StatementInterceptor {
override fun keepUserDataInTransactionStoreOnCommit(userData: Map<Key<*>, Any?>): Map<Key<*>, Any?> {
return userData.filterValues { it is TestDB }
}
}

companion object {
@Parameters(name = "name: {2}, container: {0}, dialect: {1}")
@JvmStatic
fun data(): Collection<Array<Any>> {
val name = System.getProperty("exposed.test.name")
val container = System.getProperty("exposed.test.container")
return TestDB.enabledDialects().map { arrayOf(container, it, name) }
}
}

@Parameterized.Parameter(0)
lateinit var container: String

@Parameterized.Parameter(1)
lateinit var dialect: TestDB

@Parameterized.Parameter(2)
lateinit var testName: String

suspend fun withDb(
dbSettings: TestDB,
configure: (DatabaseConfig.Builder.() -> Unit)? = null,
statement: suspend R2dbcTransaction.(TestDB) -> Unit
) {
Assume.assumeTrue(dialect == dbSettings)

val unregistered = dbSettings !in registeredOnShutdown
val newConfiguration = configure != null && !unregistered

if (unregistered) {
dbSettings.beforeConnection()
Runtime.getRuntime().addShutdownHook(
thread(false) {
dbSettings.afterTestFinished()
registeredOnShutdown.remove(dbSettings)
}
)
registeredOnShutdown += dbSettings
dbSettings.db = dbSettings.connect(configure ?: {})
}

val registeredDb = dbSettings.db!!
if (newConfiguration) {
dbSettings.db = dbSettings.connect(configure ?: {})
}
val database = dbSettings.db!!
suspendTransaction(transactionIsolation = database.transactionManager.defaultIsolationLevel, db = database) {
maxAttempts = 1
registerInterceptor(CurrentTestDBInterceptor)
currentTestDB = dbSettings
statement(dbSettings)
}

// revert any new configuration to not be carried over to the next test in suite
if (configure != null) {
dbSettings.db = registeredDb
}
}

suspend fun withDb(
db: Collection<TestDB>? = null,
excludeSettings: Collection<TestDB> = emptyList(),
configure: (DatabaseConfig.Builder.() -> Unit)? = null,
statement: suspend R2dbcTransaction.(TestDB) -> Unit
) {
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, configure, statement)
}

suspend fun withTables(
excludeSettings: Collection<TestDB>,
vararg tables: Table,
configure: (DatabaseConfig.Builder.() -> Unit)? = null,
statement: suspend R2dbcTransaction.(TestDB) -> Unit
) {
Assume.assumeFalse(dialect in excludeSettings)

withDb(dialect, configure = configure) {
addLogger(StdOutSqlLogger)
try {
SchemaUtils.drop(*tables)
} catch (_: Throwable) {
}

SchemaUtils.create(*tables)
try {
statement(dialect)
commit() // Need commit to persist data before drop tables
} finally {
try {
SchemaUtils.drop(*tables)
commit()
} catch (_: Exception) {
val database = dialect.db!!
suspendTransaction(transactionIsolation = database.transactionManager.defaultIsolationLevel, db = database) {
maxAttempts = 1
SchemaUtils.drop(*tables)
}
}
}
}
}

suspend fun withSchemas(
excludeSettings: List<TestDB>,
vararg schemas: Schema,
configure: (DatabaseConfig.Builder.() -> Unit)? = null,
statement: suspend R2dbcTransaction.() -> Unit
) {
if (dialect !in TestDB.enabledDialects()) {
Assume.assumeFalse(true)
return
}

if (dialect in excludeSettings) {
Assume.assumeFalse(true)
return
}

withDb(dialect, configure) {
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()
}
}
}
}

suspend fun withTables(
vararg tables: Table,
configure: (DatabaseConfig.Builder.() -> Unit)? = null,
statement: suspend R2dbcTransaction.(TestDB) -> Unit
) {
withTables(excludeSettings = emptyList(), tables = tables, configure = configure, statement = statement)
}

suspend fun withSchemas(
vararg schemas: Schema,
configure: (DatabaseConfig.Builder.() -> Unit)? = null,
statement: suspend R2dbcTransaction.() -> Unit
) {
withSchemas(excludeSettings = emptyList(), schemas = schemas, configure = configure, statement = statement)
}

fun addIfNotExistsIfSupported() = if (currentDialectTest.supportsIfNotExists) {
"IF NOT EXISTS "
} else {
""
}

protected fun prepareSchemaForTest(schemaName: String): Schema = Schema(
schemaName,
defaultTablespace = "USERS",
temporaryTablespace = "TEMP ",
quota = "20M",
on = "USERS"
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package org.jetbrains.exposed.sql.tests

import org.jetbrains.exposed.sql.DatabaseConfig
import org.jetbrains.exposed.sql.R2dbcDatabase
import org.jetbrains.exposed.sql.exposedLogger
import org.jetbrains.exposed.sql.transactions.suspendTransaction
import java.sql.Connection
import java.util.*

enum class TestDB(
val connection: () -> String,
val driver: String,
val user: String = "root",
val pass: String = "Exposed_password_1!",
val beforeConnection: suspend () -> Unit = {},
val afterTestFinished: () -> Unit = {},
val dbConfig: DatabaseConfig.Builder.() -> Unit = {}
) {
H2_V2(
{ "r2dbc:h2:mem:///regular;DB_CLOSE_DELAY=-1;" },
"org.h2.Driver",
dbConfig = {
defaultIsolationLevel = Connection.TRANSACTION_READ_COMMITTED
}
),
H2_V2_MYSQL(
{ "r2dbc:h2:mem:///mysql;MODE=MySQL;DB_CLOSE_DELAY=-1" },
"org.h2.Driver"
),
H2_V2_MARIADB(
{ "r2dbc:h2:mem:///mariadb;MODE=MariaDB;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1" },
"org.h2.Driver",
pass = "root"
),
H2_V2_PSQL(
{ "r2dbc:h2:mem:///psql;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DEFAULT_NULL_ORDERING=HIGH;DB_CLOSE_DELAY=-1" },
"org.h2.Driver"
),
H2_V2_ORACLE(
{ "r2dbc:h2:mem:///oracle;MODE=Oracle;DATABASE_TO_LOWER=TRUE;DEFAULT_NULL_ORDERING=HIGH;DB_CLOSE_DELAY=-1" },
"org.h2.Driver"
),
H2_V2_SQLSERVER(
{ "r2dbc:h2:mem:///sqlserver;MODE=MSSQLServer;DB_CLOSE_DELAY=-1" },
"org.h2.Driver"
),
MYSQL_V5(
{
"r2dbc:mysql://${MYSQL_V5.user}:${MYSQL_V5.pass}@127.0.0.1:3001/testdb" +
"?useSSL=false&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull"
},
"com.mysql.jdbc.Driver"
),
MYSQL_V8(
{
"r2dbc:mysql://${MYSQL_V8.user}:${MYSQL_V8.pass}@127.0.0.1:3002/testdb" +
"?useSSL=false&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowPublicKeyRetrieval=true"
},
"com.mysql.cj.jdbc.Driver"
),
MARIADB(
{ "r2dbc:mariadb://${MARIADB.user}:${MARIADB.pass}@127.0.0.1:3000/testdb" },
"org.mariadb.jdbc.Driver"
),
POSTGRESQL(
{ "r2dbc:postgresql://${POSTGRESQL.user}:${POSTGRESQL.pass}@127.0.0.1:3004/postgres?lc_messages=en_US.UTF-8" },
"org.postgresql.Driver"
),
ORACLE(
{ "r2dbc:oracle://${ORACLE.user}:${ORACLE.pass}@127.0.0.1:3003/XEPDB1" },
"oracle.jdbc.OracleDriver",
user = "ExposedTest",
pass = "12345",
beforeConnection = {
Locale.setDefault(Locale.ENGLISH)
val tmp = R2dbcDatabase.connect("r2dbc:oracle://sys%20as%20sysdba:[email protected]:3003/XEPDB1")
suspendTransaction(db = tmp, transactionIsolation = Connection.TRANSACTION_READ_COMMITTED) {
maxAttempts = 1

try {
exec("DROP USER ExposedTest CASCADE")
} catch (_: Exception) {
exposedLogger.warn("Exception on deleting ExposedTest user")
}
exec("CREATE USER ExposedTest ACCOUNT UNLOCK IDENTIFIED BY 12345")
exec("GRANT ALL PRIVILEGES TO ExposedTest")
}
Unit
}
),
SQLSERVER(
{ "r2dbc:mssql://${SQLSERVER.user}:${SQLSERVER.pass}@127.0.0.1:3005" },
"com.microsoft.sqlserver.jdbc.SQLServerDriver",
"SA",
);

var db: R2dbcDatabase? = null

fun connect(configure: DatabaseConfig.Builder.() -> Unit = {}): R2dbcDatabase {
val config = DatabaseConfig {
dbConfig()
configure()
}
return R2dbcDatabase.connect(connection(), databaseConfig = config)
}

companion object {
val ALL_H2 = setOf(H2_V2, H2_V2_MYSQL, H2_V2_PSQL, H2_V2_MARIADB, H2_V2_ORACLE, H2_V2_SQLSERVER)
val ALL_MYSQL = setOf(MYSQL_V5, MYSQL_V8)
val ALL_MARIADB = setOf(MARIADB)
val ALL_MYSQL_MARIADB = ALL_MYSQL + ALL_MARIADB
val ALL_MYSQL_LIKE = ALL_MYSQL_MARIADB + setOf(H2_V2_MYSQL, H2_V2_MARIADB)
val ALL_POSTGRES = setOf(POSTGRESQL)
val ALL_POSTGRES_LIKE = ALL_POSTGRES + setOf(H2_V2_PSQL)
val ALL_ORACLE_LIKE = setOf(ORACLE, H2_V2_ORACLE)
val ALL_SQLSERVER_LIKE = setOf(SQLSERVER, H2_V2_SQLSERVER)
val ALL = TestDB.entries.toSet()

fun enabledDialects(): Set<TestDB> {
if (TEST_DIALECTS.isEmpty()) {
return entries.toSet()
}

return entries.filterTo(enumSetOf()) { it.name in TEST_DIALECTS }
}
}
}
Loading

0 comments on commit c4e1a63

Please sign in to comment.