Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Adds MultiColumn queries section #7

Merged
merged 1 commit into from
Jul 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/main/scala/doobie/DoobieLibrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ object DoobieLibrary extends Library {

override def sections: List[Section] = List(
ConnectingToDatabaseSection,
SelectingDataSection
SelectingDataSection,
MultiColumnQueriesSection
)
}
2 changes: 0 additions & 2 deletions src/main/scala/doobie/DoobieUtils.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package doobie

import java.util.UUID

import doobie.Model._
import doobie.imports._
import doobie.free.{ drivermanager => FD }
Expand Down
6 changes: 5 additions & 1 deletion src/main/scala/doobie/Model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ object Model {

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

case class Code(code: String)

case class CountryInfo(name: String, pop: Int, gnp: Option[Double])

val countries = List(
Country("DEU", "Germany", 82164700, Option(2133367.00)),
Country("ESP", "Spain", 39441700, Option(553223.00)),
Country("ESP", "Spain", 39441700, None),
Country("FRA", "France", 59225700, Option(1424285.00)),
Country("GBR", "United Kingdom", 59623400, Option(1378330.00)),
Country("USA", "United States of America", 278357000, Option(8510700.00))
Expand Down
144 changes: 144 additions & 0 deletions src/main/scala/doobie/MultiColumnQueriesSection.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package doobie

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

/**
* So far, we have constructed queries that return single-column results. These results were mapped
* to Scala types. But how can we deal with multi-column queries?
*
* In this section, we'll see what alternatives '''doobie''' offers us to work with multi-column
* queries.
*
* As in previous sections, we'll keep working with the 'country' table:
* {{{
* code name population gnp
* "DEU" "Germany" 82164700 2133367.00
* "ESP" "Spain" 39441700 null
* "FRA" "France", 59225700 1424285.00
* "GBR" "United Kingdom" 59623400 1378330.00
* "USA" "United States of America" 278357000 8510700.00
* }}}
*
* @param name multi_column_queries
*/
object MultiColumnQueriesSection extends FlatSpec with Matchers with Section {

/**
* We can select multiple columns and map them to a tuple. The `gnp` column in our table is
* nullable so we’ll select that one into an `Option[Double]`.
*/
def selectMultipleColumnsUsingTuple(res0: Option[Double]) = {

val (name, population, gnp) =
sql"select name, population, gnp from country where code = 'ESP'"
.query[(String, Int, Option[Double])]
.unique
.transact(xa)
.run

gnp should be(res0)
}

/**
* doobie automatically supports row mappings for atomic column types, as well as options,
* tuples, `HList`s, shapeless records, and case classes thereof. So let’s try the same query
* with an `HList`
*/
def selectMultipleColumnsUsingHList(res0: String) = {

type CountryHListType = String :: Int :: Option[Double] :: HNil

val hlist: CountryHListType =
sql"select name, population, gnp from country where code = 'FRA'"
.query[CountryHListType]
.unique
.transact(xa)
.run

hlist.head should be(res0)
}

/**
* And with a shapeless record:
*/
def selectMultipleColumnsUsingRecord(res0: Int) = {

type Rec = Record.`'name -> String, 'pop -> Int, 'gnp -> Option[Double]`.T

val record: Rec =
sql"select name, population, gnp from country where code = 'USA'"
.query[Rec]
.unique
.transact(xa)
.run

record('pop) should be(res0)
}

/**
* And again, mapping rows to a case class.
*
* {{{
* case class Country(code: String, name: String, population: Long, gnp: Option[Double])
* }}}
*/
def selectMultipleColumnsUsingCaseClass(res0: String) = {

val country =
sql"select code, name, population, gnp from country where name = 'United Kingdom'"
.query[Country]
.unique
.transact(xa)
.run

country.code should be(res0)
}

/**
* You can also nest case classes, `HList`s, shapeless records, and/or tuples arbitrarily as long
* as the eventual members are of supported columns types. For instance, here we map the same set
* of columns to a tuple of two case classes:
*
* {{{
* case class Code(code: String)
* case class CountryInfo(name: String, pop: Int, gnp: Option[Double])
* }}}
*/
def selectMultipleColumnsUsingNestedCaseClass(res0: String) = {

val (code, country) =
sql"select code, name, population, gnp from country where code = 'ESP'"
.query[(Code, CountryInfo)]
.unique
.transact(xa)
.run

country.name should be(res0)
}

/**
* And just for fun, since the `Code` values are constructed from the primary key, let’s turn the
* results into a `Map`. Trivial but useful.
*/
def selectMultipleColumnsUsingMap(res0: String, res1: Option[CountryInfo]) = {

val notFoundCountry = CountryInfo("Not Found", 0, None)

val countriesMap: Map[Code, CountryInfo] =
sql"select code, name, population, gnp from country"
.query[(Code, CountryInfo)]
.list
.transact(xa)
.run
.toMap

countriesMap.getOrElse(Code("DEU"), notFoundCountry).name should be(res0)
countriesMap.get(Code("ITA")) should be(res1)
}
}
2 changes: 1 addition & 1 deletion src/main/scala/doobie/SelectingDataSection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import org.scalatest.{FlatSpec, Matchers}
* {{{
* code name population gnp
* "DEU" "Germany" 82164700 2133367.00
* "ESP" "Spain" 39441700 553223.00
* "ESP" "Spain" 39441700 null
* "FRA" "France", 59225700 1424285.00
* "GBR" "United Kingdom" 59623400 1378330.00
* "USA" "United States of America" 278357000 8510700.00
Expand Down
67 changes: 67 additions & 0 deletions src/test/scala/doobie/MultiColumnQueriesSectionSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package doobie

import doobie.Model.CountryInfo
import org.scalacheck.Shapeless._
import org.scalaexercises.Test
import org.scalatest.Spec
import org.scalatest.prop.Checkers
import shapeless.HNil

class MultiColumnQueriesSectionSpec extends Spec with Checkers {

def `select multiple columns using tuple` = {
val validResult: Option[Double] = None
check(
Test.testSuccess(
MultiColumnQueriesSection.selectMultipleColumnsUsingTuple _,
validResult :: HNil
)
)
}

def `select multiple columns using HList` = {
check(
Test.testSuccess(
MultiColumnQueriesSection.selectMultipleColumnsUsingHList _,
"France" :: HNil
)
)
}

def `select multiple columns using Record` = {
check(
Test.testSuccess(
MultiColumnQueriesSection.selectMultipleColumnsUsingRecord _,
278357000 :: HNil
)
)
}

def `select multiple columns using case class` = {
check(
Test.testSuccess(
MultiColumnQueriesSection.selectMultipleColumnsUsingCaseClass _,
"GBR" :: HNil
)
)
}

def `select multiple columns using nested case class` = {
check(
Test.testSuccess(
MultiColumnQueriesSection.selectMultipleColumnsUsingNestedCaseClass _,
"Spain" :: HNil
)
)
}

def `select multiple columns using map` = {
val validResult: Option[CountryInfo] = None
check(
Test.testSuccess(
MultiColumnQueriesSection.selectMultipleColumnsUsingMap _,
"Germany" :: validResult :: HNil
)
)
}
}