com-lihaoyi / scalasql

Scala ORM to query SQL databases from Scala via concise, type-safe, and familiar case classes and collection operations. Connects to Postgres, MySql, H2, and Sqlite out of the box
195 stars 22 forks source link

Instant type mapper now allows NULL values #38

Closed aboisvert closed 1 month ago

aboisvert commented 1 month ago

When doing a .leftJoin() operation, it's often the case that the right hand side will consist of all null values if the join expression does not match a row.

In my case, I was using an java.time.Instant-typed field, and I was getting exceptions such as,

scala.MatchError: null
    at scalasql.dialects.Dialect$InstantType.get(Dialect.scala:156)
    at scalasql.dialects.Dialect$InstantType.get(Dialect.scala:144)
    at scalasql.core.Queryable$ResultSetIterator.get(Queryable.scala:73)
    at scalasql.core.Expr$ExprQueryable.construct(Expr.scala:50)
    at fanstake.model.Teams$.Teams$$superArg$1$$anonfun$3$$anonfun$2(Team.scala:23)
    at scalasql.query.Table$Internal$TableQueryable.construct(Table.scala:97)
    at scalasql.query.Table$Internal$TableQueryable.construct(Table.scala:97)
    at scalasql.core.Queryable$Row$$anon$1.construct(Queryable.scala:147)
    at scalasql.core.Queryable$Row$$anon$1.construct(Queryable.scala:145)
    at scalasql.core.generated.QueryableRow.Tuple2Queryable$$anonfun$2(Generated.scala:11)
    at scalasql.core.Queryable$Row$TupleNQueryable.construct(Queryable.scala:133)
    at scalasql.core.Queryable$Row$TupleNQueryable.construct(Queryable.scala:133)
    at scalasql.core.generated.QueryableRow.Tuple2Queryable$$anonfun$2(Generated.scala:11)
    at scalasql.core.Queryable$Row$TupleNQueryable.construct(Queryable.scala:133)
    at scalasql.core.Queryable$Row$TupleNQueryable.construct(Queryable.scala:133)
    at scalasql.query.SimpleSelect.queryConstruct(SimpleSelect.scala:225)
    at scalasql.query.SimpleSelect.queryConstruct(SimpleSelect.scala:224)
    at scalasql.query.Query$QueryQueryable.construct(Query.scala:76)
    at scalasql.query.Query$QueryQueryable.construct(Query.scala:76)
    at scalasql.core.DbApi$.scalasql$core$DbApi$Impl$$_$stream$$anonfun$1(DbApi.scala:231)
    at scalasql.core.DbApi$Impl$$anon$1.generate$$anonfun$2(DbApi.scala:417)
    at scalasql.core.DbApi$Impl.configureRunCloseStatement(DbApi.scala:504)
    at scalasql.core.DbApi$Impl$$anon$1.generate(DbApi.scala:420)
    at geny.Generator.foreach(Generator.scala:51)
    at geny.Generator.foreach$(Generator.scala:33)
    at scalasql.core.DbApi$Impl$$anon$1.foreach(DbApi.scala:401)
    at geny.Generator.toBuffer(Generator.scala:127)
    at geny.Generator.toBuffer$(Generator.scala:33)
    at scalasql.core.DbApi$Impl$$anon$1.toBuffer(DbApi.scala:401)
    at geny.Generator.toVector(Generator.scala:135)
    at geny.Generator.toVector$(Generator.scala:33)
    at scalasql.core.DbApi$Impl$$anon$1.toVector(DbApi.scala:401)
    at scalasql.core.DbApi$Impl.run(DbApi.scala:218)
    at fanstake.webapp.controllers.AtlasDTO$.unsafeFindAll(AtlasDTO.scala:88)

The short of it is that Dialect.InstantType, which is a TypeMapper[Instant] didn't allow NULL values to go through.

The fix is trivial - just adding a null pass-through for the match expression.

PS: Similar to InstantType, I noticed that some other TypeMappers do not pass NULL values through. If this PR looks like the right approach to solve this, I can submit a different PR that addresses these other issues.

lihaoyi commented 1 month ago

@aboisvert thanks, this looks great

lihaoyi commented 1 month ago

Let me know if you want to submit further PRs, if so I'll wait for those to land before releasing. If not I can cut a release now for this one

aboisvert commented 1 month ago

Great, thanks! I'd prefer releasing now since we need this fix. That will give me more time to address the others.

lihaoyi commented 1 month ago

@aboisvert 0.1.10 should be out