oyvindberg / typo

Typed Postgresql integration for Scala. Hopes to avoid typos

Home Page:https://oyvindberg.github.io/typo/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Derived Scala code fails compilation: Missing 'Ordering' instance for derived String Enumeration

kolemannix opened this issue · comments

First off, let me say how excited I am to test drive this project! We've got hundreds of tables and over 600 calls to ctx.run, and a mixture of Doobie and Quill throughout the codebase. Our compile times are abysmal, and I'm so excited, looking at the very efficient hand-drived typeclass instances for Doobie, for the compile-time benefits this project may bring us!

I have a String Enumeration named Permission which is used part of a primary key in another table:

// Permission.scala
sealed abstract class Permission(val value: String)

object Permission {
  def apply(str: String): Either[String, Permission] =
    ByName.get(str).toRight(s"'$str' does not match any of the following legal values: $Names")
  def force(str: String): Permission =
    apply(str) match {
      case Left(msg) => sys.error(msg)
      case Right(value) => value
    }
  case object invite extends Permission("invite")
  case object admin extends Permission("admin")
  case object owner extends Permission("owner")
  case object support extends Permission("support")
  case object explore extends Permission("explore")
  case object write extends Permission("write")
  val All: List[Permission] = List(invite, admin, owner, support, explore, write)
  val Names: String = All.map(_.value).mkString(", ")
  val ByName: Map[String, Permission] = All.map(x => (x.value, x)).toMap
              
  implicit lazy val arrayGet: Get[Array[Permission]] = pattern.sql.generated.StringArrayMeta.get.map(_.map(force))
  implicit lazy val arrayPut: Put[Array[Permission]] = pattern.sql.generated.StringArrayMeta.put.contramap(_.map(_.value))
  implicit lazy val get: Get[Permission] = Meta.StringMeta.get.temap(Permission.apply)
  implicit lazy val put: Put[Permission] = Meta.StringMeta.put.contramap(_.value)
  implicit lazy val read: Read[Permission] = Read.fromGet(get)
  implicit lazy val write: Write[Permission] = Write.fromPut(put)
}
...
// UserPermissionId.scala
case class UserPermissionId(userId: AppUserId, permission: Permission)
object UserPermissionId {
  implicit lazy val ordering: Ordering[UserPermissionId] = Ordering.by(x => (x.userId, x.permission))
}

The Ordering[UserPermissionId] fails to compile because Permission has no Ordering.

Additionally (I can file a separate issue if desired), I have a name collision on the local write inside the Permission object. write is one of the Permissions, as well as the name of the derived doobie Write instance. I suppose I could get around this with custom naming? Have not tried yet.

Hey, thanks for the report!

The ordering thing is an oversight, will be fixed with #55 .

As for the name collision, there are many ways to fix it. the easiest is to choose a less-likely name for the typeclass instances.

I think I'll let it cook for a while and maybe do something more structured to avoid naming conflicts.

For now it's rather easy to work around if you customize naming. I updated the section now with this exact example

First off, let me say how excited I am to test drive this project! We've got hundreds of tables and over 600 calls to ctx.run, and a mixture of Doobie and Quill throughout the codebase. Our compile times are abysmal, and I'm so excited, looking at the very efficient hand-drived typeclass instances for Doobie, for the compile-time benefits this project may bring us!

Happy to hear it!

I do find it interesting that you're most enthusiastic about the compile-time improvement instead of the prospect of not having to write all that code in the first place 😄

0.4.1 on the way to maven central

I do find it interesting that you're most enthusiastic about the compile-time improvement instead of the prospect of not having to write all that code in the first place

You write it once; you wait for it to compile thousands of times :)

But anyway, our current setup is that we use Flyway to manage the actual schema, writing the schema by hand. We then manually maintain a "Row" case class for each table 🙃 . We then write an insert/get roundtrip test using Doobie's checking functionality against a local DB to ensure we can roundtrip our Row case classes, doobie also reports any mismatch of the types. Of course I'm excited just to generate the case class from the Schema using Typo, for sure!

Then we're using Quill for what you call "1. CRUD Operations" and "2. Simple Reads" in your docs. We write Doobie queries for 3. Complex Reads and 4. Dynamic Queries. The quill queries are where the vast majority of the compile times come from; we call ctx.run over 500 times; that's a lot of macro invocations that can also trigger macro invocations!

I am, of course, also excited to try your "SQL files" solution to "3. Complex Reads", but my team's most acute painpoint is compile times!