Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Replace Schema-based derivation of encoder / decoder with ZIO Schema Deriver-based derivation #144

Open
jdegoes opened this issue Jun 13, 2023 · 10 comments

Comments

@jdegoes
Copy link
Member

jdegoes commented Jun 13, 2023

Currently, ZIO JDBC directly converts from Schema to encoders / decoders by generating the appropriate type class instances (JdbcEncoder, JdbcDecoder).

This is not ideal, because it means a user does not have any ability to define a custom encoder / decoder for any part of their ADT.

In order to solve this problem, we can switch from defining Schema => Decoder / Encoder, and instead use the Deriver mechanism built into ZIO Schema, which is designed to solve this precise problem.

Using Deriver to derive JdbcEncoder / JdbcDecoder, we can both automatically support derivation, as well as allow user-defined overrides for parts of a larger data type.

@jdegoes
Copy link
Member Author

jdegoes commented Jun 13, 2023

/bounty $250

@algora-pbc
Copy link

algora-pbc bot commented Jun 13, 2023

💎 $250 bounty • ZIO

Steps to solve:

  1. Start working: Comment /attempt #144 with your implementation plan
  2. Submit work: Create a pull request including /claim #144 in the PR body to claim the bounty
  3. Receive payment: 100% of the bounty is received 2-5 days post-reward. Make sure you are eligible for payouts

Additional opportunities:

  • 🔴 Livestream on Algora TV while solving this bounty & earn $200 upon merge! Make sure to have your camera and microphone on. Comment /livestream once live

Thank you for contributing to zio/zio-jdbc!

Add a bountyShare on socials

Attempt Started (GMT+0) Solution
🔴 @FVelasquezM Jun 17, 2023, 2:03:36 AM WIP
🔴 @Andrapyre May 13, 2024, 2:54:25 AM WIP

@FVelasquezM
Copy link
Contributor

Hi, I'd like to try this one /attempt #144

@algora-pbc
Copy link

algora-pbc bot commented Jun 24, 2023

@FVelasquezM: Reminder that in 7 days the bounty will become up for grabs, so please submit a pull request before then 🙏

@algora-pbc
Copy link

algora-pbc bot commented Jul 1, 2023

The bounty is up for grabs! Everyone is welcome to /attempt #144 🙌

@jirijakes
Copy link

I have been playing with this for a few hours but my lack of experience with zio-schema shows and I could not find non-trivial examples of using Deriver. Could I, please, ask someone to help me verify that the following approach could be the right one?

deriveRecord collects values of columns decoded (1) by JdbcDecoders (fields) in Chunk[Any] and then passes them into record.construct to create record (2). Besides that, used column indices are tracked. So far, it reuses existing decoders for primitives.

Thank you.

val drv: Deriver[JdbcDecoder] = new Deriver[JdbcDecoder] {

  override def deriveRecord[A](
    record: Schema.Record[A],
    fields: => Chunk[WrappedF[zio.jdbc.JdbcDecoder, ?]],
    summoned: => Option[JdbcDecoder[A]]
  ): JdbcDecoder[A] =
    new JdbcDecoder[A] {
      override def unsafeDecode(columnIndex: Int, rs: ResultSet): (Int, A) = {
        val (nextColumn, fieldValues) = fields
          .foldLeft((columnIndex, Chunk.empty[Any])) { case ((col, values), decoder) =>
            decoder
              .unwrap
              .decode(col, rs)     // 1
              .fold(
                e => throw new JdbcDecoderError("Cannot decode value", e, rs.getMetaData(), rs.getRow()),
                (c, value) => (c + 1, values :+ value)
              )
          }

        Unsafe
          .unsafe(record.construct(fieldValues))    // 2
          .fold(
            e => throw new JdbcDecoderError("Cannot construct record", new Exception(e), rs.getMetaData(), rs.getRow()),
            a => (nextColumn - 1, a)
          )          
      }
    }
  …
}.autoAcceptSummoned

users:

id name age
1 Pierre 100
2 John 20
3 Nelly 17
case class User(name: String, age: Int)
case class Id(value: Int)
case class Rec(id: Int, user: User, id2: Id)

given Schema[Rec] = DeriveSchema.gen
given JdbcDecoder[Rec] = Derive.derive(drv)

// query
sql"SELECT id, name, age, id * 2 FROM users".query[Rec].selectAll

/*
  Rec(1, User(Pierre, 100), Id(2))
  Rec(2, User(John, 20), Id(4))
  Rec(3, User(Nelly, 17), Id(6))
*/

// with custom decoder of inner record
given JdbcDecoder[Id] = JdbcDecoder.intDecoder.map(n => Id(n + 1000))

/*
  Rec(1, User(Pierre, 100), Id(1002))
  Rec(2, User(John, 20), Id(1004))
  Rec(3, User(Nelly, 17), Id(1006))
*/

@jirijakes
Copy link

My further experiments and attempts to use zio-jdbc in one of my projects bring me to another question: should the codecs work based on position or names? As for now, they seem to work based on names for records (case classes). This has issue with nested case classes (how to name columns in result set and how to refer to nested values). My attempt above uses positional decoding, I think the example I provided would not be possible with name-based decoding.

@Andrapyre
Copy link

Andrapyre commented May 13, 2024

/attempt #144

Algora profile Completed bounties Tech Active attempts Options
@Andrapyre 1 ZIO bounty
TypeScript, Scala,
Rust & more
Cancel attempt

Copy link

algora-pbc bot commented May 20, 2024

@Andrapyre: Reminder that in 7 days the bounty will become up for grabs, so please submit a pull request before then 🙏

Copy link

algora-pbc bot commented May 27, 2024

The bounty is up for grabs! Everyone is welcome to /attempt #144 🙌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants