zio / zio-protoquill

Quill for Scala 3
Apache License 2.0
205 stars 48 forks source link

fail to build a query when lifting a scala3 enum with a Decoder #230

Open gbicou opened 1 year ago

gbicou commented 1 year ago

Version: 4.6.0 Module: quill-sql Database: sqlite

Actual behavior

Scala 3 enum with a Decoder fails to build query

Models :

enum PostState {
  case DRAFT
  case LIVE
}

case class Post(
  title: String,
  state: PostState
)

Quill context :

case class Context(override val ds: DataSource) extends Quill.Sqlite(Literal, ds) {
  given Encoder[PostState] = encoder(Types.VARCHAR, (index, value, row) => row.setString(index, value.toString))
  given Decoder[PostState] = decoder((index, row, _) => PostState.valueOf(row.getString(index)))

  inline given SchemaMeta[Post] = schemaMeta("post")
  inline def posts              = query[Post]
}

Repository :

final case class Live(ctx: Context) extends PostRepository {
  import ctx.*
  import ctx.given

  override def byString(title: Option[String]): Task[List[Post]] = {
    run(posts.filter(p => lift(title).forall(s => s == p.title)))
    // Quill Query: SELECT p.title, p.state FROM post p WHERE ? IS NULL OR ? = p.title
  }

  override def byEnum(state: Option[PostState]): Task[List[Post]] = {
    run(posts.filter(p => lift(state).forall(s => s == p.state)))
    // ERROR:
    // The Co-Product element PostState.DRAFT was not a Case Class or Value Type. Value-level Co-Products are not supported. Please write a decoder for it's parent-type PostState.
  }
}

Steps to reproduce the behavior

Minimal code to reproduce is available at https://github.com/bicouy0/zio-protoquill-enum-query-fail

Workaround

Unknown

@getquill/maintainers

jdsalchow commented 1 year ago

I just ran into the same thing, turned out it I didn't import ctx.* in my repository. In my case the encoding / decoding worked by just having a given MappedEncoding in both directions and an imported context.

guizmaii commented 4 months ago

Same issue here 🤔

Explained in Discord: https://discord.com/channels/629491597070827530/821565909139062845/1246119588408725614

guizmaii commented 4 months ago

I manage to make my query compile by changing it as following:

// before - doesn't compile
inline def insertOrganizationMembers(members: Iterable[(UserUuid, OrgUuid, OrgRoleDB)]) = {
  quote {
    liftQuery(members).foreach { case (userUuid, orgUuid, role) =>
      organizations_members
        .insert(
          _.user_uuid_fk -> userUuid,
          _.org_uuid_fk  -> orgUuid,
          _.role         -> role,
        )
        .onConflictIgnore
      }
    }
  }

// after - does compile
inline def insertOrganizationMembers(members: Iterable[(UserUuid, OrgUuid, OrgRoleDB)]) = {
  quote {
    liftQuery(members).foreach { t =>
      organizations_members
        .insert(
          _.user_uuid_fk -> t._1,
          _.org_uuid_fk  -> t._2,
          _.role         -> t._3,
        )
        .onConflictIgnore
      }
    }
  }
alterationx10 commented 4 months ago

Perhaps unrelated, but I had a problem today where my custom encoder was not used unless it was named (ie given myEncoder: Encoder[PostState]... vs given Encoder[PostState]), and it was imported by name as well at the call site.

Edit: Tried this in the example code, and it didn't help