Search code examples
sqlscalascalaquery

How to use ScalaQuery to insert a BLOB field?


I used ScalaQuery and Scala.

If I have an Array[Byte] object, how do I insert it into the table?

object TestTable extends BasicTable[Test]("test") {
  def id = column[Long]("mid", O.NotNull)
  def extInfo = column[Blob]("mbody", O.Nullable)

  def * = id ~ extInfo <> (Test, Test.unapply _)
}

case class Test(id: Long, extInfo: Blob)

Can I define the method used def extInfo = column[Array[Byte]]("mbody", O.Nullable), how to operate(UPDATE, INSERT, SELECT) with the BLOB type field?

BTW: no ScalaQuery tag


Solution

  • Since the BLOB field is nullable, I suggest changing its Scala type to Option[Blob], for the following definition:

    object TestTable extends Table[Test]("test") {
      def id = column[Long]("mid")
      def extInfo = column[Option[Blob]]("mbody")
      def * = id ~ extInfo <> (Test, Test.unapply _)
    }
    
    case class Test(id: Long, extInfo: Option[Blob])
    

    You can use a raw, nullable Blob value if you prefer, but then you need to use orElse(null) on the column to actually get a null value out of it (instead of throwing an Exception):

          def * = id ~ extInfo.orElse(null) <> (Test, Test.unapply _)
    

    Now for the actual BLOB handling. Reading is straight-forward: You just get a Blob object in the result which is implemented by the JDBC driver, e.g.:

      Query(TestTable) foreach { t =>
        println("mid=" + t.id + ", mbody = " +
          Option(t.extInfo).map { b => b.getBytes(1, b.length.toInt).mkString })
      }
    

    If you want to insert or update data, you need to create your own BLOBs. A suitable implementation for a stand-alone Blob object is provided by JDBC's RowSet feature:

    import javax.sql.rowset.serial.SerialBlob
    
    TestTable insert Test(1, null)
    TestTable insert Test(2, new SerialBlob(Array[Byte](1,2,3)))
    

    Edit: And here's a TypeMapper[Array[Byte]] for Postgres (whose BLOBs are not yet supported by ScalaQuery):

      implicit object PostgresByteArrayTypeMapper extends
          BaseTypeMapper[Array[Byte]] with TypeMapperDelegate[Array[Byte]] {
        def apply(p: BasicProfile) = this
        val zero = new Array[Byte](0)
        val sqlType = java.sql.Types.BLOB
        override val sqlTypeName = "BYTEA"
        def setValue(v: Array[Byte], p: PositionedParameters) {
          p.pos += 1
          p.ps.setBytes(p.pos, v)
        }
        def setOption(v: Option[Array[Byte]], p: PositionedParameters) {
          p.pos += 1
          if(v eq None) p.ps.setBytes(p.pos, null) else p.ps.setBytes(p.pos, v.get)
        }
        def nextValue(r: PositionedResult) = {
          r.pos += 1
          r.rs.getBytes(r.pos)
        }
        def updateValue(v: Array[Byte], r: PositionedResult) {
          r.pos += 1
          r.rs.updateBytes(r.pos, v)
        }
        override def valueToSQLLiteral(value: Array[Byte]) =
          throw new SQueryException("Cannot convert BYTEA to literal")
      }