Skip to content

Commit

Permalink
Support Anorm ToStatement for ValueEnum
Browse files Browse the repository at this point in the history
  • Loading branch information
cchantep committed Feb 27, 2021
1 parent 8e85aed commit b2982a7
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 26 deletions.
31 changes: 20 additions & 11 deletions README.md
Expand Up @@ -1275,14 +1275,6 @@ libraryDependencies ++= Seq(
)
```

To use with ScalaJS:

```scala
libraryDependencies ++= Seq(
"com.beachape" %%% "enumeratum-anorm" % enumeratumAnormVersion
)
```

### Usage

#### Enum
Expand Down Expand Up @@ -1312,23 +1304,32 @@ case object ShirtSize extends Enum[ShirtSize] with AnormEnum[ShirtSize] {

case class Shirt(size: ShirtSize)

// ---

import java.sql.Connection
import anorm._

// Support Enum as readable Column
def findShirtSize(shirtId: String)(implicit con: Connection): ShirtSize =
SQL"SELECT size FROM shirt_tbl WHERE id = $shirtId".
as(SqlParser.scalar[ShirtSize].single)

// Support Enum as parameter
def updateShirtSize(shirtId: String, size: ShirtSize)(
implicit con: Connection) =
SQL"UPDATE shirt_tbl SET size = ${size} WHERE id = ${shirtId}".execute()
```

#### ValueEnum

```scala
import enumeratum.values.{ IntAnormEnum, IntEnum, IntEnumEntry }
import enumeratum.values.{ IntAnormValueEnum, IntEnum, IntEnumEntry }

sealed abstract class ShirtSize(val value: Int) extends IntEnumEntry

case object ShirtSize extends IntEnum[ShirtSize] with IntAnormEnum[ShirtSize] {
case object ShirtSize extends IntEnum[ShirtSize]
with IntAnormValueEnum[ShirtSize] {

case object Small extends ShirtSize(1)
case object Medium extends ShirtSize(2)
case object Large extends ShirtSize(3)
Expand All @@ -1338,12 +1339,20 @@ case object ShirtSize extends IntEnum[ShirtSize] with IntAnormEnum[ShirtSize] {

case class Shirt(size: ShirtSize)

// ---

import java.sql.Connection
import anorm._

// Support Enum as readable Column
// Support ValueEnum as readable Column
def findShirtSize(shirtId: String)(implicit con: Connection): ShirtSize =
SQL"SELECT size FROM shirt_tbl WHERE id = $shirtId".
as(SqlParser.scalar[ShirtSize].single)

// Support ValueEnum as parameter
def updateShirtSize(shirtId: String, size: ShirtSize)(
implicit con: Connection) =
SQL"UPDATE shirt_tbl SET size = ${size} WHERE id = ${shirtId}".execute()
```

## Benchmarking
Expand Down
5 changes: 3 additions & 2 deletions enumeratum-anorm/src/main/scala/AnormEnum.scala
Expand Up @@ -8,12 +8,13 @@ import anorm.{Column, ToStatement}
* Provides instances for Anorm typeclasses:
*
* - [[anorm.Column]]
* - [[anorm.ToStatement]]
*/
trait AnormEnum[A <: EnumEntry] { self: Enum[A] =>
implicit lazy val column: Column[A] =
implicit val column: Column[A] =
AnormColumn.column[A](self, insensitive = false)

implicit lazy val toStatement: ToStatement[A] = new ToStatement[A] {
implicit val toStatement: ToStatement[A] = new ToStatement[A] {
def set(s: PreparedStatement, i: Int, v: A) = s.setString(i, v.entryName)
}
}
5 changes: 3 additions & 2 deletions enumeratum-anorm/src/main/scala/AnormInsensitiveEnum.scala
Expand Up @@ -8,12 +8,13 @@ import anorm.{Column, ToStatement}
* Provides insensitive instances for Anorm typeclasses:
*
* - [[anorm.Column]]
* - [[anorm.ToStatement]]
*/
trait AnormInsensitiveEnum[A <: EnumEntry] { self: Enum[A] =>
implicit lazy val column: Column[A] =
implicit val column: Column[A] =
AnormColumn.column[A](self, insensitive = true)

implicit lazy val toStatement = new ToStatement[A] {
implicit val toStatement = new ToStatement[A] {
def set(s: PreparedStatement, i: Int, v: A) =
s.setString(i, v.entryName)
}
Expand Down
5 changes: 3 additions & 2 deletions enumeratum-anorm/src/main/scala/AnormLowercaseEnum.scala
Expand Up @@ -8,12 +8,13 @@ import anorm.{Column, ToStatement}
* Provides lowercase instances for Anorm typeclasses:
*
* - [[anorm.Column]]
* - [[anorm.ToStatement]]
*/
trait AnormLowercaseEnum[A <: EnumEntry] { self: Enum[A] =>
implicit lazy val column: Column[A] =
implicit val column: Column[A] =
AnormColumn.lowercaseOnlyColumn[A](self)

implicit lazy val toStatement = new ToStatement[A] {
implicit val toStatement = new ToStatement[A] {
def set(s: PreparedStatement, i: Int, v: A) =
s.setString(i, v.entryName.toLowerCase)
}
Expand Down
5 changes: 3 additions & 2 deletions enumeratum-anorm/src/main/scala/AnormUppercaseEnum.scala
Expand Up @@ -8,12 +8,13 @@ import anorm.{Column, ToStatement}
* Provides uppercase instances for Anorm typeclasses:
*
* - [[anorm.Column]]
* - [[anorm.ToStatement]]
*/
trait AnormUppercaseEnum[A <: EnumEntry] { self: Enum[A] =>
implicit lazy val column: Column[A] =
implicit val column: Column[A] =
AnormColumn.uppercaseOnlyColumn[A](self)

implicit lazy val toStatement = new ToStatement[A] {
implicit val toStatement = new ToStatement[A] {
def set(s: PreparedStatement, i: Int, v: A) =
s.setString(i, v.entryName.toUpperCase)
}
Expand Down
16 changes: 16 additions & 0 deletions enumeratum-anorm/src/main/scala/values/AnormToStatement.scala
@@ -0,0 +1,16 @@
package enumeratum.values

import java.sql.PreparedStatement

import anorm.ToStatement

private[values] object AnormToStatement {
def apply[ValueType, EntryType <: ValueEnumEntry[ValueType]](
enum: ValueEnum[ValueType, EntryType]
)(
implicit baseToStmt: ToStatement[ValueType]
): ToStatement[EntryType] = new ToStatement[EntryType] {
def set(s: PreparedStatement, i: Int, e: EntryType) =
baseToStmt.set(s, i, e.value)
}
}
24 changes: 17 additions & 7 deletions enumeratum-anorm/src/main/scala/values/AnormValueEnum.scala
@@ -1,6 +1,6 @@
package enumeratum.values

import anorm.Column
import anorm.{Column, ToStatement}

trait AnormValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType]] {
enum: ValueEnum[ValueType, EntryType] =>
Expand All @@ -10,52 +10,62 @@ trait AnormValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType]] {
*/
implicit def column: Column[EntryType]

/**
* ToStatement instance for the entries of this enum
*/
implicit def toStatement: ToStatement[EntryType]
}

/**
* Enum implementation for Int enum members that provides instances for Anorm typeclasses
*/
trait IntAnormValueEnum[EntryType <: IntEnumEntry] extends AnormValueEnum[Int, EntryType] {
this: IntEnum[EntryType] =>
implicit val column: Column[EntryType] = AnormColumn(this)
implicit val column: Column[EntryType] = AnormColumn(this)
implicit val toStatement: ToStatement[EntryType] = AnormToStatement(this)
}

/**
* Enum implementation for Long enum members that provides instances for Anorm typeclasses
*/
trait LongAnormValueEnum[EntryType <: LongEnumEntry] extends AnormValueEnum[Long, EntryType] {
this: LongEnum[EntryType] =>
implicit val column: Column[EntryType] = AnormColumn(this)
implicit val column: Column[EntryType] = AnormColumn(this)
implicit val toStatement: ToStatement[EntryType] = AnormToStatement(this)
}

/**
* Enum implementation for Short enum members that provides instances for Anorm typeclasses
*/
trait ShortAnormValueEnum[EntryType <: ShortEnumEntry] extends AnormValueEnum[Short, EntryType] {
this: ShortEnum[EntryType] =>
implicit val column: Column[EntryType] = AnormColumn(this)
implicit val column: Column[EntryType] = AnormColumn(this)
implicit val toStatement: ToStatement[EntryType] = AnormToStatement(this)
}

/**
* Enum implementation for String enum members that provides instances for Anorm typeclasses
*/
trait StringAnormValueEnum[EntryType <: StringEnumEntry] extends AnormValueEnum[String, EntryType] {
this: StringEnum[EntryType] =>
implicit val column: Column[EntryType] = AnormColumn(this)
implicit val column: Column[EntryType] = AnormColumn(this)
implicit val toStatement: ToStatement[EntryType] = AnormToStatement(this)
}

/**
* Enum implementation for Char enum members that provides instances for Anorm typeclasses
*/
trait CharAnormValueEnum[EntryType <: CharEnumEntry] extends AnormValueEnum[Char, EntryType] {
this: CharEnum[EntryType] =>
implicit val column: Column[EntryType] = AnormColumn(this)
implicit val column: Column[EntryType] = AnormColumn(this)
implicit val toStatement: ToStatement[EntryType] = AnormToStatement(this)
}

/**
* Enum implementation for Byte enum members that provides instances for Anorm typeclasses
*/
trait ByteAnormValueEnum[EntryType <: ByteEnumEntry] extends AnormValueEnum[Byte, EntryType] {
this: ByteEnum[EntryType] =>
implicit val column: Column[EntryType] = AnormColumn(this)
implicit val column: Column[EntryType] = AnormColumn(this)
implicit val toStatement: ToStatement[EntryType] = AnormToStatement(this)
}
49 changes: 49 additions & 0 deletions enumeratum-anorm/src/test/scala/values/AnormToStatementSpec.scala
@@ -0,0 +1,49 @@
package enumeratum.values

import scala.util.control.NonFatal

import anorm._

import org.scalatest.matchers.must.Matchers
import org.scalatest.wordspec.AnyWordSpec

import acolyte.jdbc.{DefinedParameter => DParam, ParameterMetaData => ParamMeta, UpdateExecution}
import acolyte.jdbc.AcolyteDSL.{connection, handleStatement}
import acolyte.jdbc.Implicits._

final class AnormToStatementSpec extends AnyWordSpec with Matchers {
"Sensitive enum" should {
"successfully passed as parameter" when {
def spec(value: Drink, repr: Short) =
value.toString in withConnection(repr) { implicit c =>
SQL"set-short ${value}" match {
case q @ SimpleSql(_, _, _, _) =>
// execute = false: update ok but returns no resultset
// see java.sql.PreparedStatement#execute
q.execute() mustEqual false
}
}

spec(Drink.OrangeJuice, 1)
spec(Drink.AppleJuice, 2)
spec(Drink.Cola, 3)
spec(Drink.Beer, 4)
}
}

// ---

private val SqlShort = ParamMeta.Short

private def withConnection[A](repr: Short)(f: java.sql.Connection => A): A =
f(
connection(
handleStatement withUpdateHandler {
case UpdateExecution("set-short ?", DParam(`repr`, SqlShort) :: Nil) => 1 /* case ok */

case _ =>
throw new Exception("Unexpected execution")

}
))
}

0 comments on commit b2982a7

Please sign in to comment.