Scala Anorm Postgresql Error When Storing Byte Array
-
26-06-2021 - |
Question
I have a database table in Scala Playframework defined as
CREATE TABLE account (
id SERIAL,
email TEXT NOT NULL,
buffer BYTEA NOT NULL,
PRIMARY KEY (id)
);
I am using a protocol buffer to serialize an object to a byte array with the following code
DB.withConnection{ implicit c=>
SQL("INSERT INTO device (buffer,secret) VALUES ({secret},{buffer})").on(
"secret"->device.getSecret(),
"buffer"->device.toByteArray()
).executeInsert()
}
The type returned by device.toByteArray()
is Array[Byte]
which should match the database type for that column. However upon executing the code I get
play.core.ActionInvoker$$anonfun$receive$1$$anon$1: Execution exception [[PSQLException: ERROR: column "buffer" is of type bytea but expression is of type character varying
Hint: You will need to rewrite or cast the expression.
Position: 44]]
at play.core.ActionInvoker$$anonfun$receive$1.apply(Invoker.scala:134) [play_2.9.1.jar:2.0.3]
at play.core.ActionInvoker$$anonfun$receive$1.apply(Invoker.scala:115) [play_2.9.1.jar:2.0.3]
at akka.actor.Actor$class.apply(Actor.scala:318) [akka-actor.jar:2.0.2]
at play.core.ActionInvoker.apply(Invoker.scala:113) [play_2.9.1.jar:2.0.3]
at akka.actor.ActorCell.invoke(ActorCell.scala:626) [akka-actor.jar:2.0.2]
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:197) [akka-actor.jar:2.0.2]
Caused by: org.postgresql.util.PSQLException: ERROR: column "buffer" is of type bytea but expression is of type character varying
La solution
Looking at the anorm source and the Postgres docs, it looks like you'll need to add a handler for storing Array[Byte]
properly using setBytes
instead of setObject
, which the current code calls for.
I'd extend the match in anyParameter in the framework to read
value match {
case Some(bd: java.math.BigDecimal) => stmt.setBigDecimal(index, bd)
case Some(b: Array[Byte]) => stmt.setBytes(index, b)
case Some(o) => stmt.setObject(index, o)
// ...
and add
implicit val byteArrayToStatement = new ToStatement[Array[Byte]] {
def set(s: java.sql.PreparedStatement, index: Int, aValue: Array[Byte]): Unit = setAny(index, aValue, s)
}
You should potentially be able to do this outside the framework as well through the type class magic at work here, but I don't have time to figure that out right now.