Skip to content

Commit

Permalink
Merge pull request #2142 from cchantep/anorm-optparser
Browse files Browse the repository at this point in the history
Anorm `?` parser should not hide type/sql mapping error
  • Loading branch information
jroper committed Dec 11, 2013
2 parents c9ee12c + 36fd2a0 commit 3fe7d53
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 13 deletions.
3 changes: 2 additions & 1 deletion framework/src/anorm/src/main/scala/anorm/Anorm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ object Column {

def nonNull[A](transformer: ((Any, MetaDataItem) => MayErr[SqlRequestError, A])): Column[A] = Column[A] {
case (value, meta @ MetaDataItem(qualified, _, _)) =>
if (value != null) transformer(value, meta) else Left(UnexpectedNullableFound(qualified.toString))
if (value != null) transformer(value, meta)
else Left(UnexpectedNullableFound(qualified.toString))
}

implicit def rowToString: Column[String] = {
Expand Down
22 changes: 21 additions & 1 deletion framework/src/anorm/src/main/scala/anorm/SqlParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,16 @@ trait RowParser[+A] extends (Row => SqlResult[A]) {
}
}

/**
* Returns a row parser for optional column,
* that will turn missing or null column as None.
*/
def ? : RowParser[Option[A]] = RowParser { row =>
parent(row) match {
case Success(a) => Success(Some(a))
case Error(_) => Success(None)
case Error(UnexpectedNullableFound(_)) | Error(ColumnNotFound(_, _)) =>
Success(None)
case e @ Error(f) => e
}
}

Expand All @@ -144,10 +150,24 @@ trait RowParser[+A] extends (Row => SqlResult[A]) {

def + : ResultSetParser[List[A]] = ResultSetParser.nonEmptyList(parent)

/**
* Returns a result set parser expecting exactly one row to parse.
*
* {{{
* val b: Boolean = SQL("SELECT flag FROM Test WHERE id = :id").
* on("id" -> 1).as(scalar[Boolean].single)
* }}}
*/
def single = ResultSetParser.single(parent)

/**
* Returns a result set parser for none or one parsed row.
*
* {{{
* val name: Option[String] =
* SQL("SELECT name FROM Country WHERE lang = :lang")
* .on("lang" -> "notFound").as(scalar[String].singleOpt)
* }}}
*/
def singleOpt: ResultSetParser[Option[A]] = ResultSetParser.singleOpt(parent)

Expand Down
52 changes: 41 additions & 11 deletions framework/src/anorm/src/test/scala/anorm/AnormSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,32 @@ object AnormSpec extends Specification with H2Database with AnormTest {

}

"throw exception when single result is missing" in withQueryResult(
fooBarTable) { implicit c =>

SQL("SELECT * FROM test").as(fooBarParser.single).
aka("mapping") must throwA[Exception].like {
case e: Exception => e.getMessage aka "error" mustEqual (
"SqlMappingError(No rows when expecting a single one)")
}
}

"throw exception when there is more than 1 required or option row" in {
withQueryResult(stringList :+ "A" :+ "B") { implicit c =>
lazy val sql = SQL("SELECT 1")

(sql.as(SqlParser.scalar[Int].single)
.aka("single parser") must throwA[Exception].like {
case e: Exception => e.getMessage aka "error" mustEqual (
"SqlMappingError(too many rows when expecting a single one)")
}).and(sql.as(SqlParser.scalar[Int].singleOpt)
.aka("singleOpt parser") must throwA[Exception].like {
case e: Exception => e.getMessage aka "error" mustEqual (
"SqlMappingError(too many rows when expecting a single one)")
})
}
}

"return single string from executed query" in withQueryResult(
"Result for test-proc-1") { implicit c =>

Expand All @@ -95,7 +121,7 @@ object AnormSpec extends Specification with H2Database with AnormTest {

}

"return instance with None for option" in withQueryResult(
"return instance with None for column not found" in withQueryResult(
rowList1(classOf[Long] -> "id") :+ 123l) { implicit c =>

SQL("SELECT * FROM test").as(
Expand All @@ -104,15 +130,19 @@ object AnormSpec extends Specification with H2Database with AnormTest {
} single) aka "mapped data" must_== (123l -> None)

}
}

"throw exception when single result is missing" in withQueryResult(
fooBarTable) { implicit c =>
"throw exception when type doesn't match" in withQueryResult(
fooBarTable :+ (1l, "str", 3)) { implicit c =>

SQL("SELECT * FROM test").as(fooBarParser.single).
aka("mapping") must throwA[Exception](
"No rows when expecting a single one")
}
SQL("SELECT * FROM test").as(
SqlParser.long("id") ~ SqlParser.int("foo").? map {
case id ~ v => (id -> v)
} single) aka "parser" must throwA[Exception].like {
case e: Exception => e.getMessage aka "error" must startWith(
"TypeDoesNotMatch(Cannot convert str:")
}
}
}

"throw exception when type doesn't match" in withQueryResult("str") {
implicit c =>
Expand Down Expand Up @@ -176,9 +206,9 @@ object AnormSpec extends Specification with H2Database with AnormTest {

SQL("SELECT * FROM test").apply()
.map(row => row[String]("foo") -> row[Int]("bar"))
.aka("tuple stream") must_== List("row1" -> 100, "row2" -> 200).toStream

true must beTrue
.aka("tuple stream") mustEqual {
List("row1" -> 100, "row2" -> 200).toStream
}
}

"be parsed from class mapping" in withQueryResult(
Expand Down

0 comments on commit 3fe7d53

Please sign in to comment.