Permalink
Browse files

Adds Selecting data section

  • Loading branch information...
franciscodr committed Jul 11, 2016
1 parent cfb631d commit 945b22ec07bced99d289ef5411eba2cff295873b
@@ -16,8 +16,8 @@ lazy val doobie = (project in file("."))
"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",
"org.tpolecat" %% "doobie-core" % "0.3.0",
"org.tpolecat" %% "doobie-contrib-h2" % "0.3.0",
compilerPlugin("org.spire-math" %% "kind-projector" % "0.7.1")
)
)
@@ -14,6 +14,7 @@ object DoobieLibrary extends Library {
override def color = Some("#5B5988")

override def sections: List[Section] = List(
ConnectingToDatabaseSection
ConnectingToDatabaseSection,
SelectingDataSection
)
}
@@ -0,0 +1,35 @@
package doobie

import java.util.UUID

import doobie.Model.Country
import doobie.imports._

import scalaz.concurrent.Task
import scalaz.std.iterable._

object DoobieUtils {

val xa = DriverManagerTransactor[Task](
driver = "org.h2.Driver",
url = s"jdbc:h2:mem:doobie-exercises-${UUID.randomUUID().toString};DB_CLOSE_DELAY=-1;MODE=PostgreSQL",
user = "sa",
pass = ""
)

val createCountryTable: ConnectionIO[Int] = {
sql"""
CREATE TABLE IF NOT EXISTS country (
code VARCHAR(64),
name VARCHAR(255),
population INT,
gnp DECIMAL(10,2)
)
""".update.run
}

val dropCountryTable: ConnectionIO[Int] = sql"""DROP TABLE IF EXISTS country""".update.run

def insertCountries(countries: List[Country]): ConnectionIO[Int] =
Update[Country]("insert into country (code, name, population, gnp) values (?,?,?,?)").updateMany(countries)
}
@@ -0,0 +1,14 @@
package doobie

object Model {

case class Country(code: String, name: String, population: Long, gnp: Double)

val countries = List(
Country("DEU", "Germany", 82164700, 2133367.00),
Country("ESP", "Spain", 39441700, 553223.00),
Country("FRA", "France", 59225700, 1424285.00),
Country("GBR", "United Kingdom", 59623400, 1378330.00),
Country("USA", "United States of America", 278357000, 8510700.00)
)
}
@@ -0,0 +1,150 @@
package doobie

import doobie.DoobieUtils._
import doobie.Model._
import doobie.imports._
import org.scalaexercises.definitions.Section
import org.scalatest.{FlatSpec, Matchers}

/**
* We are gonna construct some programs that retrieve data from the database and stream it back,
* mapping to Scala types on the way.
*
* We will be playing with the country table that has the following structure:
* {{{
* CREATE TABLE country (
* code character(3) NOT NULL,
* name text NOT NULL,
* population integer NOT NULL,
* gnp numeric(10,2)
* )
* }}}
*
* For the exercises, the country table will contain:
* {{{
* code name population gnp
* "DEU" "Germany" 82164700 2133367.00
* "ESP" "Spain" 39441700 553223.00
* "FRA" "France", 59225700 1424285.00
* "GBR" "United Kingdom" 59623400 1378330.00
* "USA" "United States of America" 278357000 8510700.00
* }}}
*
* == How to select data ==
*
* As we commented in the previous section, the `sql` string interpolator allow us to create a
* query to select data from the database.
*
* For instance, `sql"select name from country".query[String]` defines a `Query0[String]`, which
* is a one-column query that maps each returned row to a String.
*
* Once we generate this query, we could use several convenience methods to stream the results:
* - `.list`, which accumulates the results to a `List`, in this case yielding a
* `ConnectionIO[List[String]]`.
* - `.vector`, which accumulates to a `Vector`
* - `.to[Coll]`, which accumulates to a type `Coll`, given an implicit `CanBuildFrom`. This works
* with Scala standard library collections.
* - `.accumulate[M[_]: MonadPlus]` which accumulates to a universally quantified monoid `M`.
* This works with many scalaz collections, as well as standard library collections with
* `MonadPlus` instances.
* - `.unique` which returns a single value, raising an exception if there is not exactly
* one row returned.
* - `.option` which returns an Option, raising an exception if there is more than
* one row returned.
* - `.nel` which returns an `NonEmptyList`, raising an exception if there are no rows returned.
* - See the Scaladoc for [[http://tpolecat.github.io/doc/doobie/0.3.0/api/index.html#doobie.util.query$$Query0 `Query0`]]
* for more information on these and other methods.
*
* @param name selecting_data
*/
object SelectingDataSection extends FlatSpec with Matchers with Section {

val initializeData = for {
_ <- createCountryTable
_ <- insertCountries(countries)
} yield Unit

val cleanupData = dropCountryTable

/**
* == Getting info about the countries ==
*
* We can use the `unique` method if the query just returns one row
*/
def selectUniqueCountryName(res0: String) = {

val query = sql"select name from country where code = 'ESP'".query[String]

val persistenceOps = for {
_ <- initializeData
countryNames <- query.unique
_ <- cleanupData
} yield countryNames

val countryName: String = persistenceOps.transact(xa).run

countryName should be(res0)
}

/**
* If we are not sure if the record exists, we can use the `option` method.
*/
def selectOptionalCountryName(res0: Option[String]) = {

val query = sql"select name from country where code = 'ITA'".query[String]

val persistenceOps = for {
_ <- initializeData
countryNames <- query.option
_ <- cleanupData
} yield countryNames

val maybeCountryName: Option[String] = persistenceOps.transact(xa).run

maybeCountryName should be(res0)
}

/**
* When the query can return more than one row, we can use the `list` to accumulate the results
* in a List.
*/
def selectCountryNameList(res0: String) = {

val query = sql"select name from country order by name".query[String]

val persistenceOps = for {
_ <- initializeData
countryNames <- query.list
_ <- cleanupData
} yield countryNames

val countryNames: List[String] = persistenceOps.transact(xa).run

countryNames.head should be(res0)
}

/**
* This is ok, but there’s not much point reading all the results from the database when we only
* want the first few rows.
*
* A different approach could be to use the `process` that gives us a
* `scalaz.stream.Process[ConnectionIO, String]` which emits the results as they arrive from the
* database. By applying a limit with `take` we instruct the process to shut everything down
* (and clean everything up) after the required number of elements have been emitted. This is
* much more efficient than pulling all the rows of the table and then throwing most of them away.
*/
def selectCountryNameListByUsingProcess(res0: Int) = {

val query = sql"select name from country order by name".query[String]

val persistenceOps = for {
_ <- initializeData
countryNames <- query.process.take(3).list
_ <- cleanupData
} yield countryNames

val countryNames: List[String] = persistenceOps.transact(xa).run

countryNames.size should be(res0)
}
}
@@ -0,0 +1,48 @@
package exercises.doobie

import doobie._
import exercises.Test
import org.scalacheck.Shapeless._
import org.scalatest.Spec
import org.scalatest.prop.Checkers
import shapeless.HNil

class SelectingDataSectionSpec extends Spec with Checkers {

def `select country name list` = {
check(
Test.testSuccess(
SelectingDataSection.selectCountryNameList _,
"France" :: HNil
)
)
}

def `select country name list by using process` = {
check(
Test.testSuccess(
SelectingDataSection.selectCountryNameListByUsingProcess _,
3 :: HNil
)
)
}
def `select optional country name` = {
val value: Option[String] = None
check(
Test.testSuccess(
SelectingDataSection.selectOptionalCountryName _,
value :: HNil
)
)
}

def `select unique country name` = {
check(
Test.testSuccess(
SelectingDataSection.selectUniqueCountryName _,
"Spain" :: HNil
)
)
}

}

0 comments on commit 945b22e

Please sign in to comment.