-
Notifications
You must be signed in to change notification settings - Fork 359
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
Rework Read/Write & Fix derivation to use custom instances #2136
base: main
Are you sure you want to change the base?
Conversation
2076053
to
1453bf1
Compare
accum += rr.unsafeGet(rs, idx) | ||
idx += rr.length | ||
} | ||
construct(accum.toList) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If accum
is being turned into a list is it more efficient to use ListBuffer
instead of ArrayBuffer
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice - that seems to give a 20% boost in the benchmarks :)
|
||
} | ||
|
||
/** Simple instance wrapping a Put. i.e. single column nullable value */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Put" -> "Get"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed thanks
implicit def fromGet[A](implicit ev: Get[A]): Read[A] = | ||
new Read(List((ev, NoNulls)), ev.unsafeGetNonNullable) {} | ||
/** Simple instance wrapping a Put. i.e. single column non-null value */ | ||
final case class Single[A](get: Get[A]) extends Read[A] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any need for this to be a case class
instead of a class
? It has no concept of equality and its not data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks I've changed this for for both Read & Write
@@ -121,7 +151,7 @@ class ReadSuite extends munit.FunSuite with ReadSuitePlatform { | |||
// This result doesn't seem ideal, because we should know that Int isn't | |||
// nullable, so the correct result is Some((1, None, 3, None)) | |||
// But with how things are wired at the moment this isn't possible |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could remove this comment now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
scalacOptions ++= Seq( | ||
"-Wconf:cat=unused-nowarn:s" | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with the global "-Wconf:cat=unused-nowarn:s" setting is that it is applied everywhere, regardless of each particular case.
Another approach that could allow to tackle this on a case-by-case basis is to make use of scalac-compat – it has nowarn213
and nowarn3
which, if applied, would emit @nowarn
for Scala 2.13 and Scala 3 respectively, and skip it for Scala 2.12.
AFAIR, scalac-compat is provided automatically by sbt-typelevel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks I didn't know about the scalac-compat. I gave it a go but unfortunately somehow while compiling in Scala 3 it thinks @nowarn213
is unused :/
[warn] -- [E198] Unused Symbol Warning: /home/jacob/proj/contrib/doobie/modules/core/src/test/scala/doobie/util/ReadSuite.scala:14:45
[warn] 14 |import org.typelevel.scalaccompat.annotation.nowarn213
[warn] | ^^^^^^^^^
[warn] | unused import
Despite it being used in
}: @nowarn("msg=.*(u|U)nused import.*") |
nowarn
to nowarn213
)
1453bf1
to
25dfdd3
Compare
25dfdd3
to
21e03ed
Compare
Fix both semiauto and automatic derivation to use custom defined Read/Write instances (e.g. in companion objects). A complete rework of Read and Write is unfortunately necessary because with the previous implementation, we cannot simply derive a `Read[Option[A]]` given a `Read[A]` - we'd need to derive `Option[A]` from scratch by resolving `Read[Option[X]]` instances for each of `A`'s columns. After the rework, both `Read` and `Write` are now sealed traits, each with 3 subclasses: - Single: Wrapper over a `Get/Put` - SingleOpt: Wrapper over a `Get/Put`, but is nullable i.e. `Read[Option[A]]`, `Write[Option[A]]` - Composite: A composite of `Read/Write` instances Apart from enabling proper semiauto and automatic derivation, the rework also brings these benefits: - Derivation rules are much simpler (which also translates to faster compile times). In particular, given a `Read/Write[A]` we can trivially derive `Read/Write[Option[A]]`. - We now correctly handle optionality for `Option[CaseClassWithOptionalAndNonOptionalFields]`. More tests will be added for this in a follow up PR to demonstrate Other notes: - Semiauto and Auto derivation of unary product type (e.g. 1 element case class) are removed due to it causing auto derivation to pick the wrong path. It seems unnecessary since Write/Read derivation will yield the same behaviour anyway? Fixes #1054, #2104
21e03ed
to
0360375
Compare
} | ||
|
||
/** A Read instance consists of multiple underlying Read instances */ | ||
class Composite[A, S0, S1](read0: Read[S0], read1: Read[S1], f: (S0, S1) => A) extends Read[A] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After some benchmarking, I have reworked Composite
to be structured more like HList
s rather than List[Read[Any]]
.
With List[Read[Any]]
my understanding is that JVM cannot effectively perform inlining because due to the dynamic length/type nature of the list.
But with a HList-like structure (e.g. Composite(Read[Int], Composite(Read[String], Read[Int]))
) the call graph and types are "constant" which I think made the JIT happier
Using benchmark from #2139, the current implementation has basically no penalty between Read[A] and Read[Option[A]], whereas the previous implementation has around a 50% performance penalty.
[info] Benchmark Mode Cnt Score Error Units
[info] LargeRow.autoDerivedComplex thrpt 5 956584.142 ± 105909.280 ops/s
[info] LargeRow.autoDerivedComplexOpt thrpt 5 925193.390 ± 240656.559 ops/s
[info] LargeRow.rawJdbcComplex thrpt 5 1390634.422 ± 195647.819 ops/s
[info] LargeRow.rawJdbcTuple thrpt 5 1341330.033 ± 248449.784 ops/s
[info] LargeRow.semiautoDerivedComplex thrpt 5 957341.195 ± 155499.630 ops/s
[info] LargeRow.semiautoDerivedComplexOpt thrpt 5 917308.756 ± 181313.503 ops/s
[info] LargeRow.tuple thrpt 5 1068742.195 ± 208005.075 ops/s
[info] LargeRow.tupleOpt thrpt 5 1072509.030 ± 177644.605 ops/s
Fix both semiauto and automatic derivation to use custom defined Read/Write instances (e.g. in companion objects).
A complete rework of Read and Write is unfortunately necessary because with the previous implementation, we cannot simply obtain a
Read[Option[A]]
given aRead[A]
- we'd need to deriveOption[A]
from scratch by resolvingRead[Option[X]]
instances for each ofA
's columns.After the rework, both
Read
andWrite
are now sealed traits, each with 3 subclasses:Get/Put
Get/Put
, but is nullable i.e.Read[Option[A]]
,Write[Option[A]]
Read/Write
instancesApart from enabling proper semiauto and automatic derivation, the rework also brings these benefits:
Read/Write[A]
we can trivially deriveRead/Write[Option[A]]
.Option[CaseClassWithOptionalAndNonOptionalFields]
. More tests will be added for this in a follow up PR to demonstrateOther notes:
Fixes #1054, #2104