Play 2.3 Anorm の NamedParameter

  • 4
    Like
  • 0
    Comment
More than 1 year has passed since last update.

Play 2.1 から Play 2.3 にアップデートした際の Anorm 関連のメモです。

Play 2.1 Anorm

Play 2.1 の Anorm では、SimpleSql#on の可変長引数に、変数で渡したい時は、値を ParameterValue に変換しておく必要がありました。

// NG: Seq[(Symbol, Any)]
val params1 = Seq(
  'email -> "foo@example.net",
  'id -> 1
)
SQL("UPDATE users SET email = {email} WHERE id = {id}")
    .on(params1: _*).executeUpdate()
// [error]  found   : Seq[(Symbol, Any)]
// [error]  required: Seq[(Any, anorm.ParameterValue[_])]

// OK: Seq[(Symbol, ParameterValue[_ >: String with Int])]
val params2 = Seq(
  'email -> toParameterValue("bar@example.net"),
  'id -> toParameterValue(2)
)
SQL("UPDATE users SET email = {email} WHERE id = {id}")
    .on(params2: _*).executeUpdate()

Play 2.3 Anorm

Play 2.3 の Anorm では SimpleSql#on の引数が NamedParameter に変更されていました。これまでの (Any, ParameterValue[_]) の別名ではなく、以下のように新たにクラス定義されているので、先述の Play 2.1 の方法は使えなくなります。

case class NamedParameter(name: String, value: ParameterValue)

以下のように Seq の型パラメータに NamedParameter を指定して、暗黙変換するように修正する必要があります。

val params = Seq[NamedParameter](
  'email -> "foo@example.net",
  'id -> 1
)
SQL("UPDATE users SET email = {email} WHERE id = {id}")
    .on(params: _*).executeUpdate()

// 事前に値を ParameterValue に暗黙変換しておく方法
val values = Seq[ParameterValue]("bar@example.net", 2)
SQL("UPDATE users SET email = {email} WHERE id = {id}")
    .on('email -> values(0), 'id -> values(1)).executeUpdate()

注意すべき点として ParameterValue 自体も変更されていて Any からの暗黙変換を受け付けなくなっています。

val id: Any = 1
val row = SQL("SELECT * FROM users WHERE id = {id}")
    .on('id -> id).apply().head
// [error] No implicit view available from Any => anorm.ParameterValue

上記のように明示的に Any で渡すことはないと思いますが、パラメータを Seq に入れておいて map でまとめて変換しようと考えてしまうと、値の型が混在して Any になった時にコンパイルが通りません。

// NG: Seq[(Symbol, Any)]
val params = Seq('id -> 1, 'name -> "foo") map { t =>
  val np: NamedParameter = t; np
}

// NG: Seq[Any]
val values = Seq(123, "abc") map { v =>
  val pv: ParameterValue = v; pv
}

anorm.features.anyToStatement をインポートすることで Any も暗黙変換されるようになりますが、非推奨です。

anorm.ToStatement.dateToStatement を見ると、パラメータ値が java.util.Date の場合、PreparedStatement 値として java.sql.Timestamp へ暗黙変換を行なっています。パラメータ値の型が Any だと java.util.Date のまま java.sql.PreparedStatement#setObject でセットされるので、JDBC によってはランタイムエラーが起こってしまうようです。