diff --git a/README.md b/README.md index 743dec73..f95eb0da 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.1.16 + 2.1.25 com.github.jasync-sql jasync-postgresql - 2.1.16 + 2.1.25 ``` @@ -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.1.16' + compile 'com.github.jasync-sql:jasync-mysql:2.1.25' // postgresql - compile 'com.github.jasync-sql:jasync-postgresql:2.1.16' + compile 'com.github.jasync-sql:jasync-postgresql:2.1.25' } ``` diff --git a/build.gradle.kts b/build.gradle.kts index b351a901..19841109 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.1.25" + version = "2.1.26" apply(plugin = "kotlin") apply(plugin = "maven-publish") diff --git a/r2dbc-mysql/src/main/java/JasyncStatement.kt b/r2dbc-mysql/src/main/java/JasyncStatement.kt index 647eb5c0..fa014789 100644 --- a/r2dbc-mysql/src/main/java/JasyncStatement.kt +++ b/r2dbc-mysql/src/main/java/JasyncStatement.kt @@ -8,9 +8,11 @@ import com.github.jasync.sql.db.mysql.exceptions.MysqlErrors import io.r2dbc.spi.Parameter import io.r2dbc.spi.R2dbcBadGrammarException import io.r2dbc.spi.R2dbcDataIntegrityViolationException +import io.r2dbc.spi.R2dbcNonTransientResourceException import io.r2dbc.spi.R2dbcPermissionDeniedException import io.r2dbc.spi.R2dbcRollbackException import io.r2dbc.spi.R2dbcTimeoutException +import io.r2dbc.spi.R2dbcTransientResourceException import io.r2dbc.spi.Result import io.r2dbc.spi.Statement import mu.KotlinLogging @@ -137,50 +139,108 @@ internal class JasyncStatement(private val clientSupplier: Supplier throwable is MySQLException -> { val errorMessage = throwable.errorMessage - when { - errorMessage.errorCode == MysqlErrors.ER_DBACCESS_DENIED_ERROR -> R2dbcPermissionDeniedException( + when (errorMessage.errorCode) { + MysqlErrors.ER_DBACCESS_DENIED_ERROR, + MysqlErrors.ER_ACCESS_DENIED_ERROR, + MysqlErrors.ER_KILL_DENIED_ERROR, + MysqlErrors.ER_TABLEACCESS_DENIED_ERROR, + MysqlErrors.ER_COLUMNACCESS_DENIED_ERROR, + MysqlErrors.ER_SPECIFIC_ACCESS_DENIED_ERROR, + MysqlErrors.ER_PROCACCESS_DENIED_ERROR, + MysqlErrors.ER_ACCESS_DENIED_NO_PASSWORD_ERROR, + MysqlErrors.ER_ACCESS_DENIED_CHANGE_USER_ERROR -> R2dbcPermissionDeniedException( errorMessage.errorMessage, errorMessage.sqlState, errorMessage.errorCode, throwable ) - - errorMessage.errorCode == MysqlErrors.ER_ACCESS_DENIED_ERROR -> R2dbcPermissionDeniedException( + MysqlErrors.ER_DUP_KEY, + MysqlErrors.ER_BAD_NULL_ERROR, + MysqlErrors.ER_DUP_ENTRY, + MysqlErrors.ER_DUP_UNIQUE, + MysqlErrors.ER_CANNOT_ADD_FOREIGN, + MysqlErrors.ER_NO_REFERENCED_ROW, + MysqlErrors.ER_ROW_IS_REFERENCED, + MysqlErrors.ER_NO_DEFAULT_FOR_FIELD, + MysqlErrors.ER_ROW_IS_REFERENCED_2, + MysqlErrors.ER_NO_REFERENCED_ROW_2, + MysqlErrors.ER_FOREIGN_DUPLICATE_KEY, + MysqlErrors.ER_DUP_UNKNOWN_IN_INDEX, + -> R2dbcDataIntegrityViolationException( errorMessage.errorMessage, errorMessage.sqlState, errorMessage.errorCode, throwable ) - - errorMessage.errorCode == MysqlErrors.ER_DUP_ENTRY -> R2dbcDataIntegrityViolationException( - errorMessage.errorMessage, - errorMessage.sqlState, - errorMessage.errorCode, - throwable - ) - - errorMessage.errorCode == MysqlErrors.ER_PARSE_ERROR -> R2dbcBadGrammarException( + MysqlErrors.ER_TABLE_EXISTS_ERROR, + MysqlErrors.ER_BAD_TABLE_ERROR, + MysqlErrors.ER_BAD_FIELD_ERROR, + MysqlErrors.ER_PARSE_ERROR, + MysqlErrors.ER_ILLEGAL_REFERENCE, + MysqlErrors.ER_NO_SUCH_TABLE, + MysqlErrors.ER_SP_ALREADY_EXISTS, + MysqlErrors.ER_SP_DOES_NOT_EXIST, + MysqlErrors.ER_FUNC_INEXISTENT_NAME_COLLISION, + -> R2dbcBadGrammarException( errorMessage.errorMessage, errorMessage.sqlState, errorMessage.errorCode, sql, throwable ) - - errorMessage.errorCode == 3024 || errorMessage.errorCode == MysqlErrors.ER_QUERY_TIMEOUT -> R2dbcTimeoutException( + MysqlErrors.ER_LOCK_WAIT_TIMEOUT, + MysqlErrors.ER_QUERY_TIMEOUT, + 3024, -> R2dbcTimeoutException( errorMessage.errorMessage, errorMessage.sqlState, errorMessage.errorCode, throwable ) - - errorMessage.errorCode == MysqlErrors.ER_XA_RBROLLBACK -> R2dbcRollbackException( + MysqlErrors.ER_XA_RBROLLBACK, + MysqlErrors.ER_XA_RBTIMEOUT -> R2dbcRollbackException( errorMessage.errorMessage, errorMessage.sqlState, errorMessage.errorCode, throwable ) - - else -> JasyncDatabaseException( - errorMessage.errorMessage, - errorMessage.sqlState, - errorMessage.errorCode, - throwable + MysqlErrors.ER_NET_READ_INTERRUPTED, + MysqlErrors.ER_NET_WRITE_INTERRUPTED, + MysqlErrors.ER_LOCK_DEADLOCK, + MysqlErrors.ER_QUERY_INTERRUPTED, -> R2dbcTransientResourceException( + errorMessage.errorMessage, errorMessage.sqlState, errorMessage.errorCode, throwable ) + else -> when (errorMessage.sqlState.take(2)) { + "0A", "22", "26", "2F", "20", "42", "XA" -> R2dbcBadGrammarException( + errorMessage.errorMessage, + errorMessage.sqlState, + errorMessage.errorCode, + throwable + ) + "25", "28" -> R2dbcPermissionDeniedException( + errorMessage.errorMessage, + errorMessage.sqlState, + errorMessage.errorCode, + throwable + ) + "21", "23" -> R2dbcDataIntegrityViolationException( + errorMessage.errorMessage, + errorMessage.sqlState, + errorMessage.errorCode, + throwable + ) + "H1", "08" -> R2dbcNonTransientResourceException( + errorMessage.errorMessage, + errorMessage.sqlState, + errorMessage.errorCode, + throwable + ) + "40" -> R2dbcRollbackException( + errorMessage.errorMessage, + errorMessage.sqlState, + errorMessage.errorCode, + throwable + ) + else -> JasyncDatabaseException( + errorMessage.errorMessage, + errorMessage.sqlState, + errorMessage.errorCode, + throwable + ) + } } } diff --git a/r2dbc-mysql/src/main/java/MysqlConnectionFactoryProvider.kt b/r2dbc-mysql/src/main/java/MysqlConnectionFactoryProvider.kt index 4c15da6e..b4b903cc 100644 --- a/r2dbc-mysql/src/main/java/MysqlConnectionFactoryProvider.kt +++ b/r2dbc-mysql/src/main/java/MysqlConnectionFactoryProvider.kt @@ -15,6 +15,7 @@ import io.r2dbc.spi.ConnectionFactoryOptions.USER import io.r2dbc.spi.ConnectionFactoryProvider import io.r2dbc.spi.Option import mu.KotlinLogging +import java.nio.file.Paths import java.time.Duration import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -30,11 +31,19 @@ class MysqlConnectionFactoryProvider : ConnectionFactoryProvider { @JvmField val APPLICATION_NAME: Option = Option.valueOf("applicationName") + /** + * Server rsa public key file. + */ + @JvmField + val SERVER_RSA_PUBLIC_KEY_FILE: Option = Option.valueOf("serverRSAPublicKeyFile") + /** * Driver option value. */ const val MYSQL_DRIVER = "mysql" + const val MYSQL_DEFAULT_PORT = 3306 + var CLIENT_FOUND_ROWS: Boolean by ClientFoundRowsDelegate() init { @@ -63,14 +72,15 @@ class MysqlConnectionFactoryProvider : ConnectionFactoryProvider { override fun create(connectionFactoryOptions: ConnectionFactoryOptions): JasyncConnectionFactory { val configuration = Configuration( host = connectionFactoryOptions.getValue(HOST) as String? ?: throw IllegalArgumentException("HOST is missing"), - port = connectionFactoryOptions.getValue(PORT) as Int? ?: throw IllegalArgumentException("PORT is missing"), + port = connectionFactoryOptions.getValue(PORT) as Int? ?: MYSQL_DEFAULT_PORT, username = connectionFactoryOptions.getValue(USER) as String? ?: throw IllegalArgumentException("USER is missing"), 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?, - ssl = MysqlSSLConfigurationFactory.create(connectionFactoryOptions) + ssl = MysqlSSLConfigurationFactory.create(connectionFactoryOptions), + rsaPublicKey = (connectionFactoryOptions.getValue(SERVER_RSA_PUBLIC_KEY_FILE) as String?)?.let { Paths.get(it) } ) return JasyncConnectionFactory(MySQLConnectionFactory(configuration)) } @@ -80,7 +90,6 @@ class MysqlConnectionFactoryProvider : ConnectionFactoryProvider { return when { driver == null || driver != MYSQL_DRIVER -> false !connectionFactoryOptions.hasOption(HOST) -> false - !connectionFactoryOptions.hasOption(PORT) -> false !connectionFactoryOptions.hasOption(USER) -> false else -> true } 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 f44e3dc1..24edfcf5 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 @@ -11,9 +11,7 @@ class MysqlConnectionFactoryProviderTest { @Test fun shouldCreateMysqlConnectionWithMysqlSSLConfigurationFactory() { - - val options = - ConnectionFactoryOptions.parse("r2dbc:mysql://user@host:443/") + val options = ConnectionFactoryOptions.parse("r2dbc:mysql://user@host:443/") // when val result = provider.create(options) @@ -21,4 +19,48 @@ class MysqlConnectionFactoryProviderTest { // then assertEquals(SSLConfiguration(), result.mySQLConnectionFactory.configuration.ssl) } + + @Test + fun shouldUseDefaultPortWhenPortIsNotSpecified() { + val options = ConnectionFactoryOptions.parse("r2dbc:mysql://user@host/") + + // when + val result = provider.create(options) + + // then + assertEquals(3306, result.mySQLConnectionFactory.configuration.port) + } + + @Test + fun shouldUseSpecifiedPort() { + val options = ConnectionFactoryOptions.parse("r2dbc:mysql://user@host:3307/") + + // when + val result = provider.create(options) + + // then + assertEquals(3307, result.mySQLConnectionFactory.configuration.port) + } + + @Test + fun shouldNotUseWhenRsaPublicKeyIsNotSpecified() { + val options = ConnectionFactoryOptions.parse("r2dbc:mysql://user@host/") + + // when + val result = provider.create(options) + + // then + assertEquals(null, result.mySQLConnectionFactory.configuration.rsaPublicKey) + } + + @Test + fun shouldUseSpecifiedRsaPublicKey() { + val options = ConnectionFactoryOptions.parse("r2dbc:mysql://user@host/db?serverRSAPublicKeyFile=rsa.pem") + + // when + val result = provider.create(options) + + // then + assertEquals("rsa.pem", result.mySQLConnectionFactory.configuration.rsaPublicKey.toString()) + } }