Skip to content

JoinNullable deserialisation fix #70

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

Merged
merged 3 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -9838,6 +9838,32 @@ db.run(Enclosing.select) ==> Seq(value1, value2)



### DataTypes.JoinNullable proper type mapping



```scala
case class A[T[_]](id: T[Int], bId: T[Option[Int]])
object A extends Table[A]

object Custom extends Enumeration {
val Foo, Bar = Value

implicit def make: String => Value = withName
}

case class B[T[_]](id: T[Int], custom: T[Custom.Value])
object B extends Table[B]
db.run(A.insert.columns(_.id := 1, _.bId := None))
val result = db.run(A.select.leftJoin(B)(_.id === _.id).single)
result._2 ==> None
```






## Optional
Queries using columns that may be `NULL`, `Expr[Option[T]]` or `Option[T]` in Scala
### Optional
Expand Down
17 changes: 13 additions & 4 deletions scalasql/core/src/Queryable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ object Queryable {
var nulls = 0
var nonNulls = 0

def consumeNulls(columnsCount: Int): Boolean = {
val result = Range.inclusive(index + 1, index + columnsCount).forall { i =>
r.getObject(i) == null
}
if (result) index = index + columnsCount
result
}

def get[T](mt: TypeMapper[T]) = {
index += 1
val res = mt.get(r, index)
Expand Down Expand Up @@ -143,10 +151,11 @@ object Queryable {
def walkExprs(q: JoinNullable[Q]) = qr.walkExprs(q.get)

def construct(args: Queryable.ResultSetIterator): Option[R] = {
val startNonNulls = args.nonNulls
val res = qr.construct(args)
if (startNonNulls == args.nonNulls) None
else Option(res)
if (args.consumeNulls(qr.walkLabels().length)) {
None
} else {
Option(qr.construct(args))
}
}

def deconstruct(r: Option[R]): JoinNullable[Q] = JoinNullable(qr.deconstruct(r.get))
Expand Down
3 changes: 1 addition & 2 deletions scalasql/src/dialects/Dialect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,7 @@ trait Dialect extends DialectTypeMappers {
def jdbcType: JDBCType = JDBCType.VARCHAR
def get(r: ResultSet, idx: Int): T = {
val str = r.getString(idx)
if (str == null) null.asInstanceOf[T]
else constructor(str)
constructor(str)
}
def put(r: PreparedStatement, idx: Int, v: T) =
r.setObject(idx, v, java.sql.Types.OTHER)
Expand Down
12 changes: 12 additions & 0 deletions scalasql/test/resources/h2-customer-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ DROP TABLE IF EXISTS product CASCADE;
DROP TABLE IF EXISTS shipping_info CASCADE;
DROP TABLE IF EXISTS purchase CASCADE;
DROP TABLE IF EXISTS data_types CASCADE;
DROP TABLE IF EXISTS a CASCADE;
DROP TABLE IF EXISTS b CASCADE;
DROP TABLE IF EXISTS non_round_trip_types CASCADE;
DROP TABLE IF EXISTS opt_cols CASCADE;
DROP TABLE IF EXISTS nested CASCADE;
Expand Down Expand Up @@ -58,6 +60,16 @@ CREATE TABLE data_types (
-- my_offset_time TIME WITH TIME ZONE,
);

CREATE TABLE a(
id INTEGER,
b_id INTEGER
);

CREATE TABLE b(
id INTEGER,
custom VARCHAR(256)
);

CREATE TABLE non_round_trip_types(
my_zoned_date_time TIMESTAMP WITH TIME ZONE,
my_offset_date_time TIMESTAMP WITH TIME ZONE
Expand Down
12 changes: 12 additions & 0 deletions scalasql/test/resources/mysql-customer-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ DROP TABLE IF EXISTS `product` CASCADE;
DROP TABLE IF EXISTS `shipping_info` CASCADE;
DROP TABLE IF EXISTS `purchase` CASCADE;
DROP TABLE IF EXISTS `data_types` CASCADE;
DROP TABLE IF EXISTS `a` CASCADE;
DROP TABLE IF EXISTS `b` CASCADE;
DROP TABLE IF EXISTS `non_round_trip_types` CASCADE;
DROP TABLE IF EXISTS `opt_cols` CASCADE;
DROP TABLE IF EXISTS `nested` CASCADE;
Expand Down Expand Up @@ -57,6 +59,16 @@ CREATE TABLE data_types (
my_enum ENUM ('foo', 'bar', 'baz')
);

CREATE TABLE a(
id INTEGER,
b_id INTEGER
);

CREATE TABLE b(
id INTEGER,
custom VARCHAR(256)
);

CREATE TABLE non_round_trip_types(
my_zoned_date_time TIMESTAMP,
my_offset_date_time TIMESTAMP
Expand Down
12 changes: 12 additions & 0 deletions scalasql/test/resources/postgres-customer-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ DROP TABLE IF EXISTS product CASCADE;
DROP TABLE IF EXISTS shipping_info CASCADE;
DROP TABLE IF EXISTS purchase CASCADE;
DROP TABLE IF EXISTS data_types CASCADE;
DROP TABLE IF EXISTS a CASCADE;
DROP TABLE IF EXISTS b CASCADE;
DROP TABLE IF EXISTS non_round_trip_types CASCADE;
DROP TABLE IF EXISTS opt_cols CASCADE;
DROP TABLE IF EXISTS nested CASCADE;
Expand Down Expand Up @@ -61,6 +63,16 @@ CREATE TABLE data_types (

);

CREATE TABLE a(
id INTEGER,
b_id INTEGER
);

CREATE TABLE b(
id INTEGER,
custom VARCHAR(256)
);

CREATE TABLE non_round_trip_types(
my_zoned_date_time TIMESTAMP WITH TIME ZONE,
my_offset_date_time TIMESTAMP WITH TIME ZONE
Expand Down
12 changes: 12 additions & 0 deletions scalasql/test/resources/sqlite-customer-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ DROP TABLE IF EXISTS product;
DROP TABLE IF EXISTS shipping_info;
DROP TABLE IF EXISTS purchase;
DROP TABLE IF EXISTS data_types;
DROP TABLE IF EXISTS a;
DROP TABLE IF EXISTS b;
DROP TABLE IF EXISTS non_round_trip_types;
DROP TABLE IF EXISTS nested;
DROP TABLE IF EXISTS enclosing;
Expand Down Expand Up @@ -56,6 +58,16 @@ CREATE TABLE data_types (
-- my_offset_time TIME WITH TIME ZONE,
);

CREATE TABLE a(
id INTEGER,
b_id INTEGER
);

CREATE TABLE b(
id INTEGER,
custom VARCHAR(256)
);

CREATE TABLE non_round_trip_types(
my_zoned_date_time TIMESTAMP,
my_offset_date_time TIMESTAMP
Expand Down
19 changes: 19 additions & 0 deletions scalasql/test/src/datatypes/DataTypesTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -212,5 +212,24 @@ trait DataTypesTests extends ScalaSqlSuite {

}
)
test("JoinNullable proper type mapping") - checker.recorded(
"",
Text {
case class A[T[_]](id: T[Int], bId: T[Option[Int]])
object A extends Table[A]

object Custom extends Enumeration {
val Foo, Bar = Value

implicit def make: String => Value = withName
}

case class B[T[_]](id: T[Int], custom: T[Custom.Value])
object B extends Table[B]
db.run(A.insert.columns(_.id := 1, _.bId := None))
val result = db.run(A.select.leftJoin(B)(_.id === _.id).single)
result._2 ==> None
}
)
}
}