diff --git a/src/main/kotlin/com/github/vokorm/DB.kt b/src/main/kotlin/com/github/vokorm/DB.kt index b6d42f4..40f7b47 100644 --- a/src/main/kotlin/com/github/vokorm/DB.kt +++ b/src/main/kotlin/com/github/vokorm/DB.kt @@ -11,11 +11,15 @@ import org.sql2o.Connection import org.sql2o.converters.Converter import org.sql2o.converters.ConverterException import org.sql2o.converters.ConvertersProvider +import org.sql2o.quirks.NoQuirks +import org.sql2o.quirks.Quirks +import org.sql2o.quirks.QuirksProvider import java.io.Closeable import java.sql.Timestamp import java.time.Instant import java.time.LocalDate import java.time.ZoneId +import java.util.* import javax.sql.DataSource import javax.validation.NoProviderFoundException import javax.validation.Validation @@ -142,3 +146,45 @@ class VokConvertersProvider : ConvertersProvider { } } } + +/** + * Converts [UUID] to [ByteArray] to be able to store UUID into binary(16). + * See https://github.com/mvysny/vok-orm/issues/8 for more details. + */ +private class MysqlUuidConverter : Converter { + override fun toDatabaseParam(`val`: UUID?): Any? = when(`val`) { + null -> null + else -> `val`.toByteArray() + } + + override fun convert(`val`: Any?): UUID? = when(`val`) { + null -> null + is ByteArray -> uuidFromByteArray(`val`) + is UUID -> `val` + else -> throw IllegalArgumentException("Failed to convert $`val` to UUID") + } +} + +/** + * Works around MySQL drivers not able to convert [UUID] to [ByteArray]. + * See https://github.com/mvysny/vok-orm/issues/8 for more details. + */ +class MysqlQuirks : NoQuirks(mapOf(UUID::class.java to MysqlUuidConverter())) + +/** + * Provides specialized quirks for MySQL. See [MysqlQuirks] for more details. + */ +class VokOrmQuirksProvider : QuirksProvider { + override fun forURL(jdbcUrl: String): Quirks? = when { + jdbcUrl.startsWith("jdbc:mysql:") || jdbcUrl.startsWith("jdbc:mariadb:") -> MysqlQuirks() + else -> null + } + + override fun forObject(jdbcObject: Any): Quirks? { + val className = jdbcObject.javaClass.canonicalName + return when { + className.startsWith("com.mysql.") || className.startsWith("org.mariadb.jdbc.") -> MysqlQuirks() + else -> null + } + } +} diff --git a/src/main/kotlin/com/github/vokorm/Utils.kt b/src/main/kotlin/com/github/vokorm/Utils.kt index ace411f..2a9f5b4 100644 --- a/src/main/kotlin/com/github/vokorm/Utils.kt +++ b/src/main/kotlin/com/github/vokorm/Utils.kt @@ -1,7 +1,8 @@ package com.github.vokorm import org.slf4j.LoggerFactory -import java.io.Closeable +import java.io.* +import java.util.* import javax.validation.ConstraintViolation import javax.validation.ValidationException import javax.validation.Validator @@ -39,4 +40,24 @@ object NoopValidator : Validator { override fun unwrap(type: Class?): T = throw ValidationException("unsupported $type") override fun forExecutables(): ExecutableValidator = throw UnsupportedOperationException("unimplemented") -} \ No newline at end of file +} + +/** + * Converts UUID to a byte array with the length of 16. Writes [UUID.getMostSignificantBits] first, then + * [UUID.getLeastSignificantBits]. + */ +fun UUID.toByteArray(): ByteArray = ByteArrayOutputStream().apply { + val dout = DataOutputStream(this) + dout.writeLong(mostSignificantBits) + dout.writeLong(leastSignificantBits) +}.toByteArray() + +/** + * Reads UUID from [bytes]. + * @param bytes 16-byte-long byte array; first is the [UUID.getMostSignificantBits], then [UUID.getLeastSignificantBits]. + */ +fun uuidFromByteArray(bytes: ByteArray): UUID { + check(bytes.size == 16) { "Expected 16-bytes array but got ${bytes.size}" } + val din = DataInputStream(ByteArrayInputStream(bytes)) + return UUID(din.readLong(), din.readLong()) +} diff --git a/src/main/resources/META-INF/services/org.sql2o.quirks.QuirksProvider b/src/main/resources/META-INF/services/org.sql2o.quirks.QuirksProvider new file mode 100644 index 0000000..c4594fa --- /dev/null +++ b/src/main/resources/META-INF/services/org.sql2o.quirks.QuirksProvider @@ -0,0 +1 @@ +com.github.vokorm.VokOrmQuirksProvider diff --git a/src/test/kotlin/com/github/vokorm/UtilsTest.kt b/src/test/kotlin/com/github/vokorm/UtilsTest.kt new file mode 100644 index 0000000..561ec59 --- /dev/null +++ b/src/test/kotlin/com/github/vokorm/UtilsTest.kt @@ -0,0 +1,12 @@ +package com.github.vokorm + +import com.github.mvysny.dynatest.DynaTest +import java.util.* +import kotlin.test.expect + +class UtilsTest : DynaTest({ + test("UUID-to-ByteArray") { + val uuid = UUID.randomUUID() + expect(uuid) { uuidFromByteArray(uuid.toByteArray()) } + } +})