scalikejdbcで独自の型を扱う


TreasureDataをscalikejdbcで操作するときに、独自のカラム型に対応する方法を試す。

TreasureData(presto)には、Arrayのカラムが使えてvarcharやintを入れられる。

取得すると、[1,2,3]のようなjson形式の配列で返ってくる。

独自の型とテーブル

型とテーブルの構造は以下とする。

case class TDArray[A](values: Seq[A])
case class Foo(
  id: Int,
  arrStr: TDArray[String],
  arrInt: TDArray[Int],
  time: Long)

object Foo extends SQLSyntaxSupport[Foo] {
  override val tableName = "foo"
  override val columns = Seq("id", "arr_str", "arr_int", "time")
  def apply(rn: ResultName[Foo])(rs: WrappedResultSet): Foo = autoConstruct(rs, rn)
}

これをreadする。

object FooDao {
  val f = Foo.syntax("f")
  def read(id: Int)(implicit session: DBSession = AutoSession): Seq[Foo] = {
    withSQL {
      select
        .from(Foo as f)
    }
      .map(Foo(f.resultName))
      .list().apply()
  }
}

このままだと、TDArrayをどうしたらいいか分からないのでエラーになる。

結果セットから、値を取得するときにTypeBinderを経由して取得しているようなので、TypeBinderに独自の定義を追加すればいい。

型クラスになっているので、implicitで型を定義する。

object TDArray {
  val stringTypeBinder: TypeBinder[TDArray[String]] = {
    TypeBinder(_ getString _)(_ getString _).map { s =>
      if (s == null) TDArray(Seq[String]())
      else TDArray(s.init.tail.replace("\"", "").split(',').map(_.trim))
    }
  }

  implicit val tdString: TypeBinder[TDArray[String]] = stringTypeBinder
  implicit val tdInt: TypeBinder[TDArray[Int]] = stringTypeBinder.map { tda => TDArray(tda.values.map(_.toInt)) }
}

jsonのパースの所はてきとう。

これでreadできるようになった。

もうちょっと説明

まずFooのapplyで使っているautoConstructのマクロで、WrappedResultSetのgetを呼ぶコードを作っている。

WrappedResultSetのgetは、TypeBinderを取得してapplyしてる。構文が分からない場合は"scala 型クラス"でググルとよろし。

なので、TypeBinderのimplicitを定義すればいいというわけだ。

おわり

追記

ちなみにWrappedResultSetのarrayは、TreasureDataのJDBCドライバがgetArrayをサポートしていないのでUnsopported..の例外になる。╭(°ㅂ°`)╮

(´-`).。oO(TreasureDataよく分からん...)