Skip to content
Over-Complicated Database Query using higher-kinded data
Scala Shell
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
modules Adjusted code and dependencies for Scala cross-compilation Oct 2, 2019
project
readme
scripts
.editorconfig
.gitignore
.jvmopts
.scalafmt.conf
.tmuxinator.yml Removed unneccessary scripts and replaced some of them with tmuxinator Aug 18, 2019
.travis.yml
LICENSE
README.md Update readme and version Oct 2, 2019
docker-compose.yml Prepared scripts for setting up and removing tables for IT tests Aug 11, 2019
ocdquery.sbt Update dependencies and migrate to Doobie 8.4 Oct 2, 2019
sbt
scalastyle-config.xml
scalastyle-test-config.xml Initial proof of concept Jul 19, 2019
version.sbt

README.md

Over-Complicated Database Query (OCD Query)

Build Status Maven Central License

Doobie queries generated using higher-kinded data.

See complete documentation at GH pages

Instalation

  1. add in your sbt:
libraryDependencies += "io.scalaland" %% "ocdquery-core" % "0.5.0"

(and maybe some optics library like Quicklens or Monocle)

  1. create higher-kinded data representation:
import java.time.LocalDate
import java.util.UUID

final case class TicketF[F[_], C[_]](
  id:      C[UUID],
  name:    F[String],
  surname: F[String],
  from:    F[String],
  to:      F[String],
  date:    F[LocalDate]
)
  1. create a repository:
import cats.Id
import com.softwaremill.quicklens._
import doobie.h2.implicits._
import io.scalaland.ocdquery._

// only have to do it once!
val TicketRepo: Repo.EntityRepo[TicketF] = {
  // I have no idea why shapeless cannot find this Generic on its own :/
  // if you figure it out, please PR!!!
  implicit val ticketRead: doobie.Read[Repo.ForEntity[TicketF]#Entity] =
    QuasiAuto.read(shapeless.Generic[TicketF[Id, Id]])
    
  Repo.forEntity[TicketF](
    "tickets".tableName,
    // I suggest using quicklens or monocle's extension methods
    // as they are more reliable than .copy
    DefaultColumnNames.forEntity[TicketF].modify(_.from).setTo("from_".columnName)
  )
}
  1. generate queries
// build these in you services with type safety!

TicketRepo.insert(
  // no need to pass "empty" fields like "id = Unit"!
  Create.fromTuple(("John", "Smith", "London", "Paris", LocalDate.now))
).run

import io.scalaland.ocdquery.sql._ // common filter syntax like `=`, `<>`

TicketRepo.update.withFilter { columns =>
  (columns.name `=` "John") and (columns.surname `=` "Smith")
}(
  TicketRepo.emptyUpdate.modify(_.data).setTo(LocalDate.now)
).run

TicketRepo.fetch.withSort(_.name, Sort.Ascending).withLimit(5) {
 _.from `=` "London"
}.to[List]

TicketRepo.delete(_.id `=` deletedId).run
  1. perform even joins returning tuples of entities:
val joiner = TicketRepo
  .join(TicketRepo).on(_._1.id, _._2.id) // after .join() we have a tuple!
  .join(TicketRepo).on(_._2.id, _._3.id) // and now triple!
  .fetch.withSort(_._1.name, Sort.Ascending).withLimit(5) { columns =>
    columns._1.name `=` "John"
  }.to[List] // ConnectionIO[(Entity, Entity, Entity)]

Limitations

  • Library assumes that EntityF is flat, and automatic generation of Doobie queries is done in a way which doesn't allow you to use JOINs, nested SELECTs etc. If you need them you can use utilities from RepoMeta to write your own query, while delegating some of the work to RepoMeta (see how Repo does it!).
  • Using EntityF everywhere is definitely not convenient. Also it doesn't let you define default values like e.g. None/Skipped for optional fields. So use them internally, as entities to work with your database and separate them from entities exposed in your API/published language. You can use chimney for turning public instances to and from internal instances,
  • types sometimes confuse compiler, so while it can derive something like shapeless.Generic[TicketF[Id, Id]], it has issues finding Generic.Aux, so Doobie sometimes get's confused - QuasiAuto let you provide the right values explicitly, so that the derivation is not blocked by such silly issue.
You can’t perform that action at this time.