diff --git a/README.md b/README.md index 8f644ac3..0eee53ab 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,14 @@ For docs and info see the [wiki](https://github.com/jasync-sql/jasync-sql/wiki). com.github.jasync-sql jasync-mysql - 2.2.0 + 2.2.4 com.github.jasync-sql jasync-postgresql - 2.2.0 + 2.2.4 ``` @@ -56,9 +56,9 @@ For docs and info see the [wiki](https://github.com/jasync-sql/jasync-sql/wiki). ```gradle dependencies { // mysql - compile 'com.github.jasync-sql:jasync-mysql:2.2.0' + compile 'com.github.jasync-sql:jasync-mysql:2.2.4' // postgresql - compile 'com.github.jasync-sql:jasync-postgresql:2.2.0' + compile 'com.github.jasync-sql:jasync-postgresql:2.2.4' } ``` diff --git a/build.gradle.kts b/build.gradle.kts index eba2e35a..19635393 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,7 +24,7 @@ apply(plugin = "io.github.gradle-nexus.publish-plugin") allprojects { group = "com.github.jasync-sql" - version = "2.2.4" + version = "2.2.5" apply(plugin = "kotlin") apply(plugin = "maven-publish") diff --git a/db-async-common/src/main/java/com/github/jasync/sql/db/exceptions/InsufficientParametersException.kt b/db-async-common/src/main/java/com/github/jasync/sql/db/exceptions/InsufficientParametersException.kt index ea290f1e..5acb9370 100644 --- a/db-async-common/src/main/java/com/github/jasync/sql/db/exceptions/InsufficientParametersException.kt +++ b/db-async-common/src/main/java/com/github/jasync/sql/db/exceptions/InsufficientParametersException.kt @@ -10,10 +10,11 @@ package com.github.jasync.sql.db.exceptions * @param given the collection given */ @Suppress("RedundantVisibilityModifier") -public class InsufficientParametersException(expected: Int, given: List) : DatabaseException( - "The query contains %s parameters but you gave it %s (%s)".format( +public class InsufficientParametersException(query: String, expected: Int, given: List) : DatabaseException( + "The query contains %s parameters but you gave it %s (%s):${System.lineSeparator()}%s".format( expected, given.size, - given.joinToString(",") + given.joinToString(","), + query ) ) diff --git a/mysql-async/src/main/java/com/github/jasync/sql/db/mysql/MySQLConnection.kt b/mysql-async/src/main/java/com/github/jasync/sql/db/mysql/MySQLConnection.kt index cae6ca6c..2591acf1 100644 --- a/mysql-async/src/main/java/com/github/jasync/sql/db/mysql/MySQLConnection.kt +++ b/mysql-async/src/main/java/com/github/jasync/sql/db/mysql/MySQLConnection.kt @@ -468,7 +468,7 @@ class MySQLConnection @JvmOverloads constructor( this.validateIsReadyForQuery() val totalParameters = params.query.count { it == '?' } if (params.values.length != totalParameters) { - throw InsufficientParametersException(totalParameters, params.values) + throw InsufficientParametersException(params.query, totalParameters, params.values) } val promise = CompletableFuture() this.setQueryPromise(promise) diff --git a/mysql-async/src/test/java/com/github/jasync/sql/db/mysql/QuerySpec.kt b/mysql-async/src/test/java/com/github/jasync/sql/db/mysql/QuerySpec.kt index fe88a836..34c40f47 100644 --- a/mysql-async/src/test/java/com/github/jasync/sql/db/mysql/QuerySpec.kt +++ b/mysql-async/src/test/java/com/github/jasync/sql/db/mysql/QuerySpec.kt @@ -239,10 +239,11 @@ class QuerySpec : ConnectionHelper() { @Test fun `connection should fail if number of args required is different than the number of provided parameters`() { withConnection { connection -> - verifyException(InsufficientParametersException::class.java) { + val query = "select * from some_table where c = ? and b = ?" + verifyException(InsufficientParametersException::class.java, containedInMessage = query) { executePreparedStatement( connection, - "select * from some_table where c = ? and b = ?", + query, listOf("one", "two", "three") ) } diff --git a/mysql-async/src/test/java/com/github/jasync/sql/db/mysql/Utils.kt b/mysql-async/src/test/java/com/github/jasync/sql/db/mysql/Utils.kt index a24a4ed5..2e991788 100644 --- a/mysql-async/src/test/java/com/github/jasync/sql/db/mysql/Utils.kt +++ b/mysql-async/src/test/java/com/github/jasync/sql/db/mysql/Utils.kt @@ -5,6 +5,7 @@ import org.assertj.core.api.Assertions fun verifyException( exType: Class, causeType: Class? = null, + containedInMessage: String? = null, body: () -> Unit ): Throwable { try { @@ -13,6 +14,7 @@ fun verifyException( } catch (e: Exception) { // e.printStackTrace() Assertions.assertThat(e::class.java).isEqualTo(exType) + containedInMessage?.let { Assertions.assertThat(e.message).contains(it) } causeType?.let { Assertions.assertThat(e.cause!!::class.java).isEqualTo(it) } return e.cause ?: e } diff --git a/postgresql-async/src/main/java/com/github/jasync/sql/db/postgresql/PostgreSQLConnection.kt b/postgresql-async/src/main/java/com/github/jasync/sql/db/postgresql/PostgreSQLConnection.kt index 81ba5e56..b7145eb6 100644 --- a/postgresql-async/src/main/java/com/github/jasync/sql/db/postgresql/PostgreSQLConnection.kt +++ b/postgresql-async/src/main/java/com/github/jasync/sql/db/postgresql/PostgreSQLConnection.kt @@ -190,7 +190,7 @@ class PostgreSQLConnection @JvmOverloads constructor( if (holder.paramsCount != params.values.length) { this.clearQueryPromise() - throw InsufficientParametersException(holder.paramsCount, params.values) + throw InsufficientParametersException(params.query, holder.paramsCount, params.values) } this.currentPreparedStatement = Optional.of(holder) diff --git a/postgresql-async/src/test/java/com/github/aysnc/sql/db/Utils.kt b/postgresql-async/src/test/java/com/github/aysnc/sql/db/Utils.kt index bc7d16d6..cafea1ba 100644 --- a/postgresql-async/src/test/java/com/github/aysnc/sql/db/Utils.kt +++ b/postgresql-async/src/test/java/com/github/aysnc/sql/db/Utils.kt @@ -5,6 +5,7 @@ import org.assertj.core.api.Assertions fun verifyException( exType: Class, causeType: Class? = null, + containedInMessage: String? = null, body: () -> Unit ): Throwable { try { @@ -12,6 +13,7 @@ fun verifyException( throw Exception("Expected exception was not thrown: ${exType.simpleName}->${causeType?.simpleName}") } catch (e: Exception) { Assertions.assertThat(e::class.java).isEqualTo(exType) + containedInMessage?.let { Assertions.assertThat(e.message).contains(it) } causeType?.let { Assertions.assertThat(e.cause!!::class.java).isEqualTo(it) } return e.cause ?: e } diff --git a/postgresql-async/src/test/java/com/github/aysnc/sql/db/integration/PreparedStatementSpec.kt b/postgresql-async/src/test/java/com/github/aysnc/sql/db/integration/PreparedStatementSpec.kt index 97339a07..051b6933 100644 --- a/postgresql-async/src/test/java/com/github/aysnc/sql/db/integration/PreparedStatementSpec.kt +++ b/postgresql-async/src/test/java/com/github/aysnc/sql/db/integration/PreparedStatementSpec.kt @@ -77,7 +77,7 @@ class PreparedStatementSpec : DatabaseTestHelper() { fun `prepared statements should raise an exception if the parameter count is different from the given parameters count`() { withHandler { handler -> executeDdl(handler, this.messagesCreate) - verifyException(InsufficientParametersException::class.java) { + verifyException(InsufficientParametersException::class.java, containedInMessage = this.messagesSelectOne) { executePreparedStatement(handler, this.messagesSelectOne) } } @@ -268,9 +268,10 @@ class PreparedStatementSpec : DatabaseTestHelper() { fun `prepared statements should fail if prepared statement has more variables than it was given`() { withHandler { handler -> executeDdl(handler, messagesCreate) - verifyException(InsufficientParametersException::class.java) { + val query = "SELECT * FROM messages WHERE content = ? AND moment = ?" + verifyException(InsufficientParametersException::class.java, containedInMessage = query) { handler.sendPreparedStatement( - "SELECT * FROM messages WHERE content = ? AND moment = ?", + query, listOf("some content") ) } diff --git a/r2dbc-mysql/src/main/java/JasyncClientConnection.kt b/r2dbc-mysql/src/main/java/JasyncClientConnection.kt index 456d6d99..901821e5 100644 --- a/r2dbc-mysql/src/main/java/JasyncClientConnection.kt +++ b/r2dbc-mysql/src/main/java/JasyncClientConnection.kt @@ -43,8 +43,6 @@ class JasyncClientConnection( override fun beginTransaction(definition: TransactionDefinition): Publisher { return Mono.defer { - val setAutoCommit = Mono.from(setAutoCommit(false)) - val setLockWaitTimeout = Mono.justOrEmpty(definition.getAttribute(TransactionDefinition.LOCK_WAIT_TIMEOUT)) .flatMap { timeout -> Mono.from(setLockWaitTimeout(timeout)) } @@ -53,8 +51,7 @@ class JasyncClientConnection( val startTransaction = Mono.from(beginTransaction()) - return@defer Mono.from(setAutoCommit) - .then(setLockWaitTimeout) + return@defer Mono.from(setLockWaitTimeout) .then(changeIsolationLevel) .then(startTransaction) .then() diff --git a/r2dbc-mysql/src/main/java/JasyncRow.kt b/r2dbc-mysql/src/main/java/JasyncRow.kt index d272c9c0..ba74c74a 100644 --- a/r2dbc-mysql/src/main/java/JasyncRow.kt +++ b/r2dbc-mysql/src/main/java/JasyncRow.kt @@ -30,6 +30,14 @@ class JasyncRow(private val rowData: RowData, private val metadata: JasyncMetada return when { requestedType == Object::class.java -> value requestedType == String::class.java -> value?.toString() + requestedType == java.lang.Boolean::class.java -> { + when (value) { + is Number -> value.toInt() != 0 + is String -> value.toBoolean() + is Boolean -> value + else -> throw IllegalStateException("Cannot convert ${value?.javaClass?.simpleName} to Boolean") + } + } value is Number -> { when (requestedType) { java.lang.Long::class.java -> value.toLong() @@ -54,6 +62,12 @@ class JasyncRow(private val rowData: RowData, private val metadata: JasyncMetada else -> throw IllegalStateException("unmatched requested type ${requestedType.simpleName}") } } + value is Boolean && requestedType.isPrimitive -> { + when (requestedType.name) { + "boolean" -> value + else -> throw IllegalStateException("Cannot convert Boolean to ${requestedType.name}") + } + } value is LocalDateTime -> { when (requestedType) { LocalDate::class.java -> value.toLocalDate() diff --git a/r2dbc-mysql/src/main/java/MysqlConnectionFactoryProvider.kt b/r2dbc-mysql/src/main/java/MysqlConnectionFactoryProvider.kt index 38ea337c..67f3a07f 100644 --- a/r2dbc-mysql/src/main/java/MysqlConnectionFactoryProvider.kt +++ b/r2dbc-mysql/src/main/java/MysqlConnectionFactoryProvider.kt @@ -77,8 +77,8 @@ class MysqlConnectionFactoryProvider : ConnectionFactoryProvider { password = connectionFactoryOptions.getValue(PASSWORD)?.toString(), database = connectionFactoryOptions.getValue(DATABASE) as String?, applicationName = connectionFactoryOptions.getValue(APPLICATION_NAME) as String?, - connectionTimeout = (connectionFactoryOptions.getValue(CONNECT_TIMEOUT) as Duration?)?.toMillis()?.toInt() ?: 5000, - queryTimeout = connectionFactoryOptions.getValue(STATEMENT_TIMEOUT) as Duration?, + connectionTimeout = connectionFactoryOptions.getValue(CONNECT_TIMEOUT)?.parseDuration()?.toMillis()?.toInt() ?: 5000, + queryTimeout = connectionFactoryOptions.getValue(STATEMENT_TIMEOUT)?.parseDuration(), ssl = MysqlSSLConfigurationFactory.create(connectionFactoryOptions), rsaPublicKey = (connectionFactoryOptions.getValue(SERVER_RSA_PUBLIC_KEY_FILE) as String?)?.let { Paths.get(it) } ) @@ -97,3 +97,15 @@ class MysqlConnectionFactoryProvider : ConnectionFactoryProvider { override fun getDriver(): String = MYSQL_DRIVER } + +private fun Any.parseDuration(): Duration { + return when (this) { + is Duration -> { + this + } + is String -> { + Duration.parse(this) + } + else -> throw Exception("cant parse $this to Duration") + } +} diff --git a/r2dbc-mysql/src/test/java/com/github/jasync/r2dbc/mysql/JasyncRowTest.kt b/r2dbc-mysql/src/test/java/com/github/jasync/r2dbc/mysql/JasyncRowTest.kt new file mode 100644 index 00000000..44ecad31 --- /dev/null +++ b/r2dbc-mysql/src/test/java/com/github/jasync/r2dbc/mysql/JasyncRowTest.kt @@ -0,0 +1,55 @@ +package com.github.jasync.r2dbc.mysql + +import com.github.jasync.sql.db.RowData +import io.mockk.every +import io.mockk.mockk +import org.junit.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/** + * Unit tests for [JasyncRow]. + */ +internal class JasyncRowTest { + + @Test + fun testBooleanConversion() { + // Mock dependencies + val rowData = mockk() + val metadata = mockk() + + // Setup mock values for different types of data + every { rowData["boolTrue"] } returns true + every { rowData["boolFalse"] } returns false + every { rowData["numZero"] } returns 0 + every { rowData["numOne"] } returns 1 + every { rowData["stringTrue"] } returns "true" + every { rowData["stringFalse"] } returns "false" + every { rowData[0] } returns true + every { rowData[1] } returns 0 + every { rowData[2] } returns "true" + + val row = JasyncRow(rowData, metadata) + + // Test conversion from Boolean to Boolean + assertTrue(row.get("boolTrue", java.lang.Boolean::class.java) as Boolean) + assertFalse(row.get("boolFalse", java.lang.Boolean::class.java) as Boolean) + + // Test conversion from Number to Boolean + assertFalse(row.get("numZero", java.lang.Boolean::class.java) as Boolean) + assertTrue(row.get("numOne", java.lang.Boolean::class.java) as Boolean) + + // Test conversion from String to Boolean + assertTrue(row.get("stringTrue", java.lang.Boolean::class.java) as Boolean) + assertFalse(row.get("stringFalse", java.lang.Boolean::class.java) as Boolean) + + // Test conversion from various types using index + assertTrue(row.get(0, java.lang.Boolean::class.java) as Boolean) + assertFalse(row.get(1, java.lang.Boolean::class.java) as Boolean) + assertTrue(row.get(2, java.lang.Boolean::class.java) as Boolean) + + // Test conversion to primitive boolean - using Boolean class in Kotlin + assertTrue(row.get("boolTrue", java.lang.Boolean::class.java) as Boolean) + assertFalse(row.get("boolFalse", java.lang.Boolean::class.java) as Boolean) + } +} diff --git a/r2dbc-mysql/src/test/java/com/github/jasync/r2dbc/mysql/MysqlConnectionFactoryProviderTest.kt b/r2dbc-mysql/src/test/java/com/github/jasync/r2dbc/mysql/MysqlConnectionFactoryProviderTest.kt index 24edfcf5..63753f6a 100644 --- a/r2dbc-mysql/src/test/java/com/github/jasync/r2dbc/mysql/MysqlConnectionFactoryProviderTest.kt +++ b/r2dbc-mysql/src/test/java/com/github/jasync/r2dbc/mysql/MysqlConnectionFactoryProviderTest.kt @@ -4,6 +4,7 @@ import com.github.jasync.sql.db.SSLConfiguration import io.r2dbc.spi.ConnectionFactoryOptions import org.junit.Assert.assertEquals import org.junit.Test +import java.time.Duration class MysqlConnectionFactoryProviderTest { @@ -63,4 +64,15 @@ class MysqlConnectionFactoryProviderTest { // then assertEquals("rsa.pem", result.mySQLConnectionFactory.configuration.rsaPublicKey.toString()) } + + @Test + fun shouldUseTimeoutAsString() { + val options = ConnectionFactoryOptions.parse("r2dbc:mysql://user@host/db?connectTimeout=PT3S") + + // when + val result = provider.create(options) + + // then + assertEquals(Duration.parse("PT3S").toMillis().toInt(), result.mySQLConnectionFactory.configuration.connectionTimeout) + } } diff --git a/samples/spring-kotlin/README.md b/samples/spring-kotlin/README.md index d3339887..a599471a 100644 --- a/samples/spring-kotlin/README.md +++ b/samples/spring-kotlin/README.md @@ -1,2 +1,2 @@ # spring-kotlin-jasync-sql -reactive applcation base on Spring + Kotlin + [Jasync-sql](https://github.com/jasync-sql/jasync-sql) +reactive application base on Spring + Kotlin + [Jasync-sql](https://github.com/jasync-sql/jasync-sql) diff --git a/samples/spring-kotlin/build.gradle.kts b/samples/spring-kotlin/build.gradle.kts index 365ca879..852c3b94 100644 --- a/samples/spring-kotlin/build.gradle.kts +++ b/samples/spring-kotlin/build.gradle.kts @@ -5,25 +5,30 @@ version = "1.0-SNAPSHOT" plugins { application - kotlin("jvm") version "1.6.10" - kotlin("plugin.spring") version "1.6.10" - id("org.springframework.boot") version "2.6.3" + kotlin("jvm") version "1.9.25" + kotlin("plugin.spring") version "1.9.25" + id("org.springframework.boot") version "3.3.4" + id("io.spring.dependency-management") version "1.1.6" } -apply(plugin = "io.spring.dependency-management") +java { + sourceCompatibility = JavaVersion.VERSION_17 + + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} repositories { mavenCentral() } dependencies { - implementation(kotlin("stdlib-jdk8")) - implementation(kotlin("reflect")) implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.springframework.boot:spring-boot-starter-data-r2dbc") implementation("org.springframework.boot:spring-boot-starter-webflux") testImplementation("org.springframework.boot:spring-boot-starter-test") - runtimeOnly("com.github.jasync-sql:jasync-r2dbc-mysql:2.0.6") + runtimeOnly("com.github.jasync-sql:jasync-r2dbc-mysql:2.2.4") } tasks.test { @@ -32,7 +37,6 @@ tasks.test { tasks.withType { kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } - diff --git a/samples/spring-kotlin/gradle/wrapper/gradle-wrapper.properties b/samples/spring-kotlin/gradle/wrapper/gradle-wrapper.properties index cc321e65..d26953ab 100644 --- a/samples/spring-kotlin/gradle/wrapper/gradle-wrapper.properties +++ b/samples/spring-kotlin/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/samples/spring-kotlin/src/main/resources/schema.sql b/samples/spring-kotlin/src/main/resources/schema.sql new file mode 100644 index 00000000..109fd860 --- /dev/null +++ b/samples/spring-kotlin/src/main/resources/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS `user` ( + `username` VARCHAR(20) NOT NULL, + `password` VARCHAR(100) NULL, + PRIMARY KEY (`username`) +); + +INSERT INTO `user` (username, password) VALUES ('Bob', 'password1'); +INSERT INTO `user` (username, password) VALUES ('Alice', 'password2');