-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from scala-exercises/paco-connecting-databaase-…
…section Adds Connecting to database section
- Loading branch information
Showing
6 changed files
with
233 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,6 @@ | ||
addSbtPlugin("org.scala-exercises" % "sbt-exercise" % "0.1.1", "0.13", "2.10") | ||
resolvers ++= Seq( | ||
Resolver.sonatypeRepo("snapshots") | ||
) | ||
|
||
addSbtPlugin("org.scala-exercises" % "sbt-exercise" % "0.2.1-SNAPSHOT", "0.13", "2.10") | ||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") |
108 changes: 108 additions & 0 deletions
108
src/main/scala/doobie/ConnectingToDatabaseSection.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package doobie | ||
|
||
import org.scalatest._ | ||
import doobie.imports._ | ||
import org.scalaexercises.definitions.Section | ||
|
||
import scalaz._ | ||
import Scalaz._ | ||
import scala.util.Random | ||
import scalaz.concurrent.Task | ||
|
||
/** ==Introduction== | ||
* doobie is a monadic API that provides a number of data types that all work the same way | ||
* but describe computations in different contexts. | ||
* | ||
* In the doobie high level API the most common types we will deal with have the form | ||
* ConnectionIO[A], specifying computations that take place in a context where a | ||
* java.sql.Connection is available, ultimately producing a value of type A. | ||
* | ||
* doobie programs are values. You can compose small programs to build larger programs. Once you | ||
* have constructed a program you wish to run, you interpret it into an effectful target monad of | ||
* your choice (Task or IO for example) and drop it into your main application wherever you like. | ||
* | ||
* ==First programs== | ||
* | ||
* {{{ | ||
* import doobie.imports._ | ||
* import scalaz._, Scalaz._ | ||
* import scalaz.concurrent.Task | ||
* }}} | ||
* | ||
* So let’s start with a ConnectionIO program that simply returns a constant. | ||
* | ||
* {{{ | ||
* val program = 42.point[ConnectionIO] | ||
* program: ConnectionIO[Int] = Return(42) | ||
* }}} | ||
* | ||
* This is a perfectly respectable doobie program, but we can’t run it as-is; we need a Connection | ||
* first. There are several ways to do this, but here let’s use a Transactor. | ||
* | ||
* {{{ | ||
* val xa = DriverManagerTransactor[Task]( | ||
* driver = "org.postgresql.Driver", | ||
* url = "jdbc:postgresql:world", | ||
* user = "postgres", | ||
* pass = "" | ||
* ) | ||
* }}} | ||
* | ||
* A Transactor is simply a structure that knows how to connect to a database, hand out | ||
* connections, and clean them up; and with this knowledge it can transform ConnectionIO ~> Task, | ||
* which gives us something we can run. Specifically it gives us a Task that, when run, will | ||
* connect to the database and run our program in a single transaction. | ||
* | ||
* The DriverManagerTransactor simply delegates to the java.sql.DriverManager to allocate | ||
* connections, which is fine for development but inefficient for production use. | ||
* | ||
* @param name connecting_to_database | ||
*/ | ||
object ConnectingToDatabaseSection extends FlatSpec with Matchers with Section { | ||
|
||
val xa = DriverManagerTransactor[Task]( | ||
driver = "org.h2.Driver", | ||
url = s"jdbc:h2:mem:doobie-exercises-${Random.nextFloat()};DB_CLOSE_DELAY=-1;MODE=PostgreSQL", | ||
user = "sa", | ||
pass = "" | ||
) | ||
|
||
/** | ||
* Right, so let’s do this. | ||
*/ | ||
def constantValue(res0: Int) = | ||
42.point[ConnectionIO].transact(xa).run should be(res0) | ||
|
||
/** We have computed a constant. It’s not very interesting because we never ask the database to | ||
* perform any work, but it’s a first step | ||
* | ||
* We are gonna connect to a database to compute a constant. | ||
* Let’s use the sql string interpolator to construct a query that asks the database to compute | ||
* a constant. The meaning of this program is “run the query, interpret the resultset as | ||
* a stream of Int values, and yield its one and only element.” | ||
*/ | ||
def constantValueFromDatabase(res0: Int) = | ||
sql"select 42".query[Int].unique.transact(xa).run should be(res0) | ||
|
||
/** What if we want to do more than one thing in a transaction? Easy! ConnectionIO is a monad, | ||
* so we can use a for comprehension to compose two smaller programs into one larger program. | ||
*/ | ||
def combineTwoPrograms(res0: (Int, Int)) = { | ||
val largerProgram = for { | ||
a <- sql"select 42".query[Int].unique | ||
b <- sql"select power(5, 2)".query[Int].unique | ||
} yield (a, b) | ||
|
||
largerProgram.transact(xa).run should be(res0) | ||
} | ||
|
||
/** The astute among you will note that we don’t actually need a monad to do this; an applicative | ||
* functor is all we need here. So we could also write the above program as: | ||
*/ | ||
def combineTwoProgramsWithApplicative(res0: Int) = { | ||
val oneProgram = sql"select 42".query[Int].unique | ||
val anotherProgram = sql"select power(5, 2)".query[Int].unique | ||
|
||
(oneProgram |@| anotherProgram) { _ + _ }.transact(xa).run should be(res0) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package doobie | ||
|
||
import org.scalaexercises.definitions.{Library, Section} | ||
|
||
/** doobie is a pure functional JDBC layer for Scala. | ||
* | ||
* @param name doobie | ||
*/ | ||
|
||
object DoobieLibrary extends Library { | ||
override def owner: String = "scala-exercises" | ||
override def repository: String = "exercises-doobie" | ||
|
||
override def color = Some("#5B5988") | ||
|
||
override def sections: List[Section] = List( | ||
ConnectingToDatabaseSection | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package exercises | ||
|
||
import cats.data.Xor | ||
import org.scalacheck.Prop.forAll | ||
import org.scalacheck.{Arbitrary, Gen, Prop} | ||
import org.scalatest.exceptions._ | ||
import shapeless._ | ||
import shapeless.ops.function._ | ||
|
||
object Test { | ||
|
||
def testSuccess[F, R, L <: HList](method: F, answer: L)( | ||
implicit | ||
A: Arbitrary[L], | ||
fntop: FnToProduct.Aux[F, L ⇒ R] | ||
): Prop = { | ||
val rightGen = genRightAnswer(answer) | ||
val rightProp = forAll(rightGen)({ p ⇒ | ||
|
||
val result = Xor.catchOnly[GeneratorDrivenPropertyCheckFailedException]({ | ||
fntop(method)(p) | ||
}) | ||
result match { | ||
case Xor.Left(exc) ⇒ exc.cause match { | ||
case Some(originalException) ⇒ throw originalException | ||
case _ ⇒ false | ||
} | ||
case _ ⇒ true | ||
} | ||
}) | ||
|
||
val wrongGen = genWrongAnswer(answer) | ||
val wrongProp = forAll(wrongGen)({ p ⇒ | ||
Xor.catchNonFatal({ | ||
fntop(method)(p) | ||
}).isLeft | ||
}) | ||
|
||
Prop.all(rightProp, wrongProp) | ||
} | ||
|
||
def genRightAnswer[L <: HList](answer: L): Gen[L] = { | ||
Gen.const(answer) | ||
} | ||
|
||
def genWrongAnswer[L <: HList](l: L)( | ||
implicit | ||
A: Arbitrary[L] | ||
): Gen[L] = { | ||
A.arbitrary.suchThat(_ != l) | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
src/test/scala/exercises/doobie/ConnectingToDatabaseSectionSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package exercises.doobie | ||
|
||
import doobie._ | ||
import exercises.Test | ||
import shapeless.HNil | ||
import org.scalatest.Spec | ||
import org.scalatest.prop.Checkers | ||
import org.scalacheck.Shapeless._ | ||
|
||
class ConnectingToDatabaseSectionSpec extends Spec with Checkers { | ||
|
||
def `compute constant value` = { | ||
check( | ||
Test.testSuccess( | ||
ConnectingToDatabaseSection.constantValue _, | ||
42 :: HNil | ||
) | ||
) | ||
} | ||
|
||
def `compute constant value from database` = { | ||
check( | ||
Test.testSuccess( | ||
ConnectingToDatabaseSection.constantValueFromDatabase _, | ||
42 :: HNil | ||
) | ||
) | ||
} | ||
|
||
def `combine two small programs` = { | ||
check( | ||
Test.testSuccess( | ||
ConnectingToDatabaseSection.combineTwoPrograms _, | ||
(42, 25) :: HNil | ||
) | ||
) | ||
} | ||
|
||
def `combine two small programs with applicative` = { | ||
check( | ||
Test.testSuccess( | ||
ConnectingToDatabaseSection.combineTwoProgramsWithApplicative _, | ||
67 :: HNil | ||
) | ||
) | ||
} | ||
|
||
} |