Skip to content

Commit

Permalink
Merge pull request #2 from scala-exercises/paco-connecting-databaase-…
Browse files Browse the repository at this point in the history
…section

Adds Connecting to database section
  • Loading branch information
franciscodr authored Jul 11, 2016
2 parents 3b14b1c + 2b363c8 commit beb1e00
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 1 deletion.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ lazy val doobie = (project in file("."))
"org.scala-exercises" %% "exercise-compiler" % version.value,
"org.scala-exercises" %% "definitions" % version.value,
"org.scalacheck" %% "scalacheck" % "1.12.5",
"com.github.alexarchambault" %% "scalacheck-shapeless_1.12" % "0.3.1",
"org.tpolecat" %% "doobie-core" % "0.2.3",
"org.tpolecat" %% "doobie-contrib-h2" % "0.2.3",
compilerPlugin("org.spire-math" %% "kind-projector" % "0.7.1")
Expand Down
6 changes: 5 additions & 1 deletion project/plugins.sbt
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 src/main/scala/doobie/ConnectingToDatabaseSection.scala
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)
}
}
19 changes: 19 additions & 0 deletions src/main/scala/doobie/DoobieLibrary.scala
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
)
}
52 changes: 52 additions & 0 deletions src/test/scala/exercises/Test.scala
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)
}
}
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
)
)
}

}

0 comments on commit beb1e00

Please # to comment.