-
Notifications
You must be signed in to change notification settings - Fork 700
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: EXPOSED-517 Implement missing ExposedDatabaseMetadata methods
- Add copy testing module for TC check
- Loading branch information
Showing
21 changed files
with
3,356 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
jetbrains.db |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
213 changes: 213 additions & 0 deletions
213
...sed-r2dbc-tests/src/main/kotlin/org/jetbrains/exposed/sql/tests/R2dbcDatabaseTestsBase.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
) | ||
} |
127 changes: 127 additions & 0 deletions
127
exposed-r2dbc-tests/src/main/kotlin/org/jetbrains/exposed/sql/tests/TestDB.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | ||
} | ||
} | ||
} |
Oops, something went wrong.