Anorm 2.6 Migration Guide
This is a guide for migrating from Anorm 2.5 to Anorm 2.6. If you need to migrate from an earlier version of Anorm then you must first follow the Anorm 2.5 Migration Guide.
Note: The dependency group has been updated from com.typesafe.play
to org.playframework.anorm
.
Streaming
The streaming support has been improved for a reactive processing of the results, with new modules.
Akka Stream module
A new Akka module is available to process DB results as Sources.
To do so, the Anorm Akka module must be used.
libraryDependencies ++= Seq(
"org.playframework.anorm" %% "anorm-akka" % "ANORM_VERSION",
"com.typesafe.akka" %% "akka-stream" % "2.4.12")
This module is tested with Akka Stream 2.4.12.
Once this library is available, the query can be used as streaming source.
import java.sql.Connection
import akka.NotUsed
import akka.stream.Materializer
import akka.stream.scaladsl.Source
import anorm._
def resultSource(implicit m: Materializer, con: Connection): Source[String, NotUsed] = AkkaStream.source(SQL"SELECT * FROM Test", SqlParser.scalar[String], ColumnAliaser.empty)
Iteratees module
A new Anorm module is available to ease the integration with Play Iteratees.
It can be added to your project using the following dependencies.
libraryDependencies ++= Seq(
"org.playframework.anorm" %% "anorm-iteratee" % "ANORM_VERSION",
"com.typesafe.play" %% "play-iteratees" % "ITERATEES_VERSION")
For a Play application, as
play-iteratees
is provided there is no need to add this dependency.
Then the parsed results from Anorm can be turned into Enumerator
.
import java.sql.Connection
import scala.concurrent.ExecutionContext.Implicits.global
import anorm._
import play.api.libs.iteratee._
def resultAsEnumerator(implicit con: Connection): Enumerator[String] =
Iteratees.from(SQL"SELECT * FROM Test", SqlParser.scalar[String])
Column aliaser
Consider the following query result.
=> SELECT * FROM test t1 JOIN (SELECT * FROM test WHERE parent_id ISNULL) t2 ON t1.parent_id=t2.id WHERE t1.id='bar';
id | value | parent_id | id | value | parent_id
-----+--------+-----------+-----+--------+-----------
bar | value2 | foo | foo | value1 |
(1 row)
The table aliases t1
and t2
are not supported in JDBC, so Anorm introduces the ColumnAliaser
to be able to define user aliases over columns as following.
import anorm._
val parser: RowParser[(String, String, String, Option[String])] = SqlParser.str("id") ~ SqlParser.str("value") ~ SqlParser.str("parent.value") ~ SqlParser.str("parent.parent_id").? map(SqlParser.flatten)
val aliaser: ColumnAliaser = ColumnAliaser.withPattern((3 to 6).toSet, "parent.")
val res: Try[(String, String, String, Option[String])] = SQL"""SELECT * FROM test t1 JOIN (SELECT * FROM test WHERE parent_id ISNULL) t2 ON t1.parent_id=t2.id WHERE t1.id=${"bar"}""".asTry(parser.single, aliaser)
res.foreach {
case (id, value, parentVal, grandPaId) => ???
}
Column conversions
- A date/timestamp column can now be read as a
Long
, representing the epoch milliseconds.
Macros
The offsetParser[T]
macro is added.
case class Foo(name: String, age: Int)
import anorm._
val findAll = SQL"SELECT uninteresting_col, skip_col, name, age FROM foo"
val fooParser = Macro.offsetParser[Foo](2)
// ignore uninteresting_col & skip_col
There are also new variant of the namedParser
macros, which can be passed a naming strategy. This naming strategy determines the column name for each case class property; e.g. To use snake case:
import anorm.{ Macro, RowParser }, Macro.ColumnNaming
case class Info(name: String, lastModified: Long)
val parser: RowParser[Info] = Macro.namedParser[Info](ColumnNaming.SnakeCase)
/* Generated as:
get[String]("name") ~ get[Long]("last_modified") map {
case name ~ year => Info(name, year)
}
*/
A custom column naming can be defined using ColumnNaming(String => String)
.
The macros can now use already defined RowParser
as sub-parser.
case class Bar(lorem: Float, ipsum: Long)
case class Foo(name: String, bar: Bar, age: Int)
import anorm._
// nested parser
implicit val barParser = Macro.parser[Bar]("bar_lorem", "bar_ipsum")
val fooBar = Macro.namedParser[Foo] /* generated as:
get[String]("name") ~ barParser ~ get[Int]("age") map {
case name ~ bar ~ age => Foo(name, bar, age)
}
*/
val result: Foo = SQL"""SELECT f.name, age, bar_lorem, bar_ipsum
FROM foo f JOIN bar b ON f.name=b.name WHERE f.name=${"Foo"}""".
as(fooBar.single)
ToParameterList
The new typeclass ToParameterList
has been introduced to define more complete encoders for parameters, for example encoder for a case case.
It comes with useful macros to easily generate such parameter conversions.
import anorm.{ Macro, SQL, ToParameterList }
import anorm.NamedParameter, NamedParameter.{ namedWithString => named }
case class Bar(v: Int)
case class Foo(n: Int, bar: Bar)
// Convert all supported properties as parameters
implicit val barToParams: ToParameterList[Bar] = Macro.toParameters()
// Custom-manual statement using-knowing class properties order
SQL("INSERT INTO table(col_w) VALUES({v})").
bind(Bar(1)) // bind as param using implicit barToParams
PostgreSQL
A new module dedicated to PostgreSQL provides parameter and column conversions improved for this database, for JSON values and UUID.
Operations
The new operation .executeInsert1
allows to select columns among the generated keys.
// Choose 'generatedCol' and 'colB' from the generatedKeys
val keys1 = SQL("INSERT INTO Test(x) VALUES ({x})").
on("x" -> "y").executeInsert1("generatedCol", "colB")()
val keys2 = SQL("INSERT INTO Test(x) VALUES ({x})").
on("x" -> "y").executeInsert1("generatedCol")(scalar[String].singleOpt)
Deprecation
The deprecated type MayErr
is no longer part of the API.
The former Column.nonNull
is updated from nonNull[A](transformer: ((Any, MetaDataItem) => MayErr[SqlRequestError, A])): Column[A]
to def nonNull[A](transformer: ((Any, MetaDataItem) => Either[SqlRequestError, A])): Column[A] = Column[A]
.
The deprecated operations .list()
, .single()
and .singleOpt()
on SQL result (e.g. SQL("...").list()
) are now removed, and must be respectively replaced by .as(parser.*)
, .as(parser.single)
and .as(parser.singleOpt)
.
The .getFilledStatement
has been removed.
The former streaming operation .apply()
is now removed, and must be replaced by either .fold
, .foldWhile
or .withResult
.
Type mappings
A new column converter is available:
import anorm.features.columnByteToBoolean
This column will accept bytes (or shorts) as representations for booleans. One useful case is upgrading the MariaDB JDBC driver from v2 to v3, but other databases already behaved that way.