-
Notifications
You must be signed in to change notification settings - Fork 370
Description
Issue Description
Environment
- Scala Version: 3.3.3
- Doobie Version: 1.0.0-RC5
- Database: MySQL
When using Doobie’sWrite[T]
derivation, any value that needs to be derived from the case class fields (rather than being a direct field) is not persisted in the database. However, manually mapping and passing the derived value in the SQL query works as expected.
This suggests that Doobie’s Write
instance does not correctly handle computed values when inserting via Update[T]
.
Expected Behavior
- Derived values (such as converting a
ZonedDateTime
to itsZoneId
) should be persisted correctly when usingWrite[T]
in an insert statement. Write[T]
should respect all transformations applied viacontramap
.
Actual Behavior
- Any field that is derived within
Write[T]
and not a direct property of the case class is ignored or stored asNULL
. - Using manual mapping in the SQL query works as expected, meaning the issue lies in the
Write[T]
derivation.
Reproduction Steps
Database Schema
MySQL
CREATE TABLE parentAvailability (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
startDate DATE NOT NULL,
timeZone VARCHAR(40)
);
Scala Case Class
case class ParentAvailability(
id: Option[Long] = None,
startDate: ZonedDateTime
)
Read and Write Instances
✅ Read Mapping (Works Correctly)
implicit val readAvailability: Read[ParentAvailability] = Read[(Long, LocalDate, String)].map {
case (id, startDate, timeZone) =>
val zoneId = ZoneId.of(timeZone)
ParentAvailability(
Some(id),
startDate.atStartOfDay(zoneId)
)
}
❌ Write Mapping (Fails to Persist Derived timeZone
)
implicit val writeAvailability: Write[(Option[Long], LocalDate, String)] = Write[(Option[Long], LocalDate, String)].contramap {
case ParentAvailability(id, startDate) =>
val startDateStr = startDate.toLocalDate
val timeZoneStr = startDate.getZone.getId // This is derived but not persisting
(id, startDateStr, timeZoneStr)
}
Insert Methods
❌ Failing Method: Using Write[T]
def insertParentAvailabilityUsingWrite(availability: ParentAvailability): EitherT[doobie.ConnectionIO, Throwable, Int] =
val insertParentAvailability =
"INSERT INTO Store.parentAvailability (id, startDate, timeZone) VALUES (?,?,?)"
EitherT(Update[ParentAvailability](insertParentAvailability)(writeAvailability).run(availability).attempt)
📌 Issue: timeZone
fails with error or always stored as NULL
if NULL value allowed for column when using this method.
✅ Working Method: Manual Mapping
def insertParentAvailabilityManualMap(availability: ParentAvailability): EitherT[doobie.ConnectionIO, Throwable, Int] =
EitherT(sql"""
INSERT INTO Store.parentAvailability (startDate, timeZone)
VALUES (${availability.startDate.toLocalDate}, ${availability.startDate.getZone.getId})
""".update.withUniqueGeneratedKeys[Int]("id").attempt)
📌 This works correctly and persists the derived value.
Key Issue: Write[T]
Does Not Handle Derived Values
When a value is not explicitly stored in the case class but needs to be derived from other fields, Write[T]
does not ensure its proper inclusion in the database.
For example, timeZone
is derived from startDate
in Write[T]
, but it is never actually written when using Update[T]
.
Questions & Possible Causes
-
Does Doobie’s
Write[T]
ignore computed values incontramap
?- The manual insert correctly retrieves
startDate.getZone.getId
, butwriteAvailability
doesn’t seem to pass it correctly.
- The manual insert correctly retrieves
-
Should
Write[T]
allow transformations that introduce new values?- Perhaps
Write
only works when fields map 1:1 to the table columns.
- Perhaps
Workarounds & Fixes
- Use manual mapping in SQL queries instead of relying on
Write[T]
for computed values. - Enable SQL logging to confirm what values Doobie is actually passing.
Request for Help
Has anyone else encountered this issue with Doobie’s Write[T]
?
- Is this expected behavior for
Write[T]
? - Should Doobie allow values derived within
contramap
to be written? - Are there best practices to ensure computed values are correctly inserted?
Any insights would be greatly appreciated! 🙌