cashapp / sqldelight

SQLDelight - Generates typesafe Kotlin APIs from SQL
https://cashapp.github.io/sqldelight/
Apache License 2.0
6.15k stars 515 forks source link

Exception in thread "main" java.lang.ClassCastException: class java.nio.HeapByteBuffer cannot be cast to class [B (java.nio.HeapByteBuffer and [B are in module java.base of loader 'bootstrap') #4405

Open 2001zhaozhao opened 1 year ago

2001zhaozhao commented 1 year ago

SQLDelight Version

2.0.0-rc02

SQLDelight Dialect

postgresql on R2DBC

Describe the Bug

On PostgreSQL with R2DBC, an exception is thrown whenever calling a query that returns a column value of the BYTEA (binary) type.

The cause seems to be that r2dbc-postgresql seems to prefer translating the BYTEA type to a ByteBuffer, rather than a byte array, as shown on their README: https://github.com/pgjdbc/r2dbc-postgresql/tree/main

However, SQLDelight's driver tries to cast it into a ByteArray?, causing the error: https://github.com/cashapp/sqldelight/blob/958dbf89850bfe780b9e91470ddf2afc6272f28a/drivers/r2dbc-driver/src/main/kotlin/app/cash/sqldelight/driver/r2dbc/R2dbcDriver.kt#L270

I have tested that this happens with both SELECT queries and UPDATE ... RETURNING queries.

Stacktrace

Exception in thread "main" java.lang.ClassCastException: class java.nio.HeapByteBuffer cannot be cast to class [B (java.nio.HeapByteBuffer and [B are in module java.base of loader 'bootstrap')
    at app.cash.sqldelight.driver.r2dbc.R2dbcCursor.getBytes(R2dbcDriver.kt:217)
    at <lambda in my own code>
    at <lambda in my own code>
    at app.cash.sqldelight.async.coroutines.QueryExtensionsKt$awaitAsOneOrNull$2.invoke(QueryExtensions.kt:42)
    at app.cash.sqldelight.async.coroutines.QueryExtensionsKt$awaitAsOneOrNull$2.invoke(QueryExtensions.kt:29)
    at app.cash.sqldelight.driver.r2dbc.R2dbcDriver$executeQuery$1.invokeSuspend(R2dbcDriver.kt:46)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
leobueno commented 4 months ago

I didn't manage to test it yet, but I believe the change below may fix the problem:

Index: drivers/r2dbc-driver/src/main/kotlin/app/cash/sqldelight/driver/r2dbc/R2dbcDriver.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/drivers/r2dbc-driver/src/main/kotlin/app/cash/sqldelight/driver/r2dbc/R2dbcDriver.kt b/drivers/r2dbc-driver/src/main/kotlin/app/cash/sqldelight/driver/r2dbc/R2dbcDriver.kt
--- a/drivers/r2dbc-driver/src/main/kotlin/app/cash/sqldelight/driver/r2dbc/R2dbcDriver.kt  (revision fb48f4682c4f086d7d2f34e383b901ba4b91fd1a)
+++ b/drivers/r2dbc-driver/src/main/kotlin/app/cash/sqldelight/driver/r2dbc/R2dbcDriver.kt  (date 1717396182529)
@@ -9,6 +9,7 @@
 import io.r2dbc.spi.Connection
 import io.r2dbc.spi.Statement
 import java.math.BigDecimal
+import java.nio.ByteBuffer
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineScope
@@ -320,7 +321,7 @@

   override fun getLong(index: Int): Long? = get<Number>(index)?.toLong()

-  override fun getBytes(index: Int): ByteArray? = get(index)
+  override fun getBytes(index: Int): ByteArray? = get<ByteBuffer>(index)?.array()

   override fun getDouble(index: Int): Double? = get(index)